Skip to content

Commit

Permalink
Add support for node.js SerialPort
Browse files Browse the repository at this point in the history
  • Loading branch information
taichunmin committed May 15, 2023
1 parent 03b560d commit 79a423e
Show file tree
Hide file tree
Showing 8 changed files with 944 additions and 671 deletions.
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@

## Browser & OS compatibility

### SerialPort (Node.js)

[Node SerialPort](https://serialport.io/docs/) is a JavaScript library for connecting to serial ports that works in NodeJS and Electron.

### Web Bluetooth API

A subset of the Web Bluetooth API is available in ChromeOS, Chrome for Android 6.0, Mac (Chrome 56) and Windows 10 (Chrome 70). See MDN's [Browser compatibility](https://developer.mozilla.org/docs/Web/API/Web_Bluetooth_API#Browser_compatibility) table for more information.
Expand Down Expand Up @@ -48,12 +52,18 @@ Using npm:

```bash
$ npm install pn532.js

# Also install SerialPort if you want to run in node.js
$ npm install serialport
```

Using yarn:

```bash
$ yarn add pn532.js

# Also install SerialPort if you want to run in node.js
$ yarn add serialport
```

Once the package is installed, you can import the library using `import` or `require`:
Expand Down Expand Up @@ -139,6 +149,12 @@ console.log(JSON.stringify(await pn532usb.getFirmwareVersion())) // {"firmware":
const pn532ble = new Pn532()
pn532ble.use(new Pn532WebbleAdapter()) // A pn532 instance must register exactly one adapter plugin
console.log(JSON.stringify(await pn532ble.getFirmwareVersion())) // {"firmware":"1.6","ic":"PN532","iso14443a":true,"iso14443b":true,"iso18092":true}

// Pn532SerialPortAdapter
import Pn532SerialPortAdapter from 'pn532.js/plugin/SerialPortAdapter.js'
// Run serialport-list to list port, see https://serialport.io/docs/bin-list
pn532.use(new Pn532SerialPortAdapter(), { path: '/dev/tty.usbserial-120' })
console.log(JSON.stringify(await pn532ble.getFirmwareVersion())) // {"firmware":"1.6","ic":"PN532","iso14443a":true,"iso14443b":true,"iso18092":true}
```

### Read UID, ATQA, SAK from Mifare Classic 1k
Expand Down
16 changes: 16 additions & 0 deletions examples/serialport.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import Pn532 from '../src/Pn532.js'
import Pn532SerialPortAdapter from '../src/plugin/SerialPortAdapter.js'

async function main () {
const pn532 = new Pn532()
const path = process.env.SERIAL_PATH
if (!path) throw new Error('env.SERIAL_PATH is not defined')
console.log(`path = ${path}`)
pn532.use(new Pn532SerialPortAdapter(), { path })
console.log(JSON.stringify(await pn532.getFirmwareVersion()))
process.exit(0)
}

// run serialport-list to list port, see https://serialport.io/docs/bin-list
// SERIAL_PATH='/dev/tty.usbserial-120' node examples/serialport.js
main().catch(console.error)
20 changes: 13 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"name": "pn532.js",
"type": "module",
"unpkg": "dist/pn532.min.js",
"version": "0.1.15",
"version": "0.1.16",
"bugs": {
"url": "https://github.com/taichunmin/pn532.js/issues"
},
Expand All @@ -22,7 +22,6 @@
}
],
"dependencies": {
"lodash": "^4.17.21",
"web-serial-polyfill": "^1.0.14"
},
"devDependencies": {
Expand All @@ -34,22 +33,25 @@
"dayjs": "^1.11.7",
"documentation": "^14.0.1",
"dotenv": "^16.0.3",
"eslint": "^8.33.0",
"eslint": "^8.37.0",
"eslint-config-standard": "^17.0.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-n": "^15.6.1",
"eslint-plugin-n": "^15.7.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-pug": "^1.2.5",
"fast-glob": "^3.2.12",
"finalhandler": "^1.2.0",
"html-minifier": "^4.0.0",
"jest": "^29.4.1",
"jest": "^29.5.0",
"jstransformer-sass": "^1.0.0",
"jstransformer-scss": "git+https://github.com/taichunmin/jstransformer-scss.git",
"livereload": "^0.9.3",
"lodash": "^4.17.21",
"node-watch": "^0.7.3",
"pug": "^3.0.2",
"rollup": "^3.14.0",
"rollup": "^3.20.2",
"serialport": "^10.5.0",
"serve-static": "^1.15.0",
"web-streams-polyfill": "^3.2.1"
},
Expand All @@ -71,6 +73,10 @@
"import": "./src/plugin/Hf14a.js",
"default": "./dist/plugin/Hf14a.js"
},
"./plugin/SerialPortAdapter.js": {
"import": "./src/plugin/SerialPortAdapter.js",
"default": "./dist/plugin/SerialPortAdapter.js"
},
"./plugin/LoggerRxTx.js": {
"import": "./src/plugin/LoggerRxTx.js",
"default": "./dist/plugin/LoggerRxTx.js"
Expand Down Expand Up @@ -101,7 +107,7 @@
"url": "https://github.com/taichunmin/pn532.js.git"
},
"resolutions": {
"**/jstransformer-scss": "taichunmin/jstransformer-scss"
"**/jstransformer-scss": "git+https://github.com/taichunmin/jstransformer-scss.git"
},
"scripts": {
"build": "cross-env DEBUG=app:* node ./index.js && yarn docjs",
Expand Down
27 changes: 16 additions & 11 deletions rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,22 @@ import { createRequire } from 'node:module'
import { fileURLToPath } from 'node:url'
import commonjs from '@rollup/plugin-commonjs'
import json from '@rollup/plugin-json'
import resolve from '@rollup/plugin-node-resolve'
import nodeResolve from '@rollup/plugin-node-resolve'
import terser from '@rollup/plugin-terser'

const require = createRequire(import.meta.url)
const pkg = require('./package.json')
const externalDependencies = _.keys(_.omit(pkg.dependencies, ['web-serial-polyfill']))
const externalDependencies = [
..._.keys(pkg.devDependencies),
'stream',
]

// good rollup example: https://github.com/MattiasBuelens/web-streams-polyfill/blob/master/rollup.config.js
const configs = [
// src/main.js
{
input: 'src/main.js',
plugins: [json(), resolve({ browser: true }), commonjs()],
plugins: [json(), nodeResolve({ browser: true }), commonjs()],
external: externalDependencies,
output: _.times(2, isMin => ({
file: 'dist/pn532.js',
Expand All @@ -34,7 +37,7 @@ const configs = [
// src/Crypto1.js
{
input: 'src/Crypto1.js',
plugins: [json(), resolve({ browser: true }), commonjs()],
plugins: [json(), nodeResolve({ browser: true }), commonjs()],
external: [
...externalDependencies,
fileURLToPath(new URL('src/Packet.js', import.meta.url)),
Expand All @@ -57,7 +60,7 @@ const configs = [
// src/Crypto1.js
{
input: 'src/Packet.js',
plugins: [json(), resolve({ browser: true }), commonjs()],
plugins: [json(), nodeResolve({ browser: true }), commonjs()],
external: externalDependencies,
output: _.times(2, isMin => ({
file: 'dist/Packet.js',
Expand All @@ -74,19 +77,21 @@ const configs = [
},

// plugins
..._.map(['Hf14a', 'LoggerRxTx', 'WebbleAdapter', 'WebserialAdapter'], plugin => ({
input: `src/plugin/${plugin}.js`,
plugins: [json(), resolve({ browser: true }), commonjs()],
..._.map(['Hf14a', 'LoggerRxTx', 'SerialPortAdapter', 'WebbleAdapter', 'WebserialAdapter'], input => ({
input: `src/plugin/${input}.js`,
plugins: [json(), nodeResolve({ browser: true }), commonjs()],
external: externalDependencies,
output: _.times(2, isMin => ({
file: `dist/plugin/${plugin}.js`,
file: `dist/plugin/${input}.js`,
format: 'umd',
name: `Pn532${plugin}`,
name: `Pn532${input}`,
globals: {
lodash: '_',
serialport: 'serialport',
stream: 'stream',
},
...(!isMin ? {} : { // for minify
file: `dist/plugin/${plugin}.min.js`,
file: `dist/plugin/${input}.min.js`,
plugins: [terser()],
}),
})),
Expand Down
15 changes: 13 additions & 2 deletions src/Packet.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,10 @@ export default class Packet extends Uint8Array {
}

/**
* Returns a new Packet object merge from two or more Packets
* @param {...Packet} packs two or more Packets to be merged
* Returns a new `Packet` which is the result of concatenating all the `Packet` instances in the `packs` together.
*
* If the `packs` has no items, then a new zero-length `Packet` is returned.
* @param {...Packet} packs List of `Packet` or `Uint8Array` instances to concatenate.
* @example
* const pack = Packet.merge(Packet.fromUtf8('Hello '), Packet.fromUtf8('world.'))
* @returns {Packet} a new Packet object merge from two or more Packets
Expand Down Expand Up @@ -233,6 +235,15 @@ export default class Packet extends Uint8Array {
return (tmp2 ? tmp1.slice(0, tmp2) : tmp1).join('')
}

/**
* The `byteOffset` of the `Packets` underlying `ArrayBuffer` object.
* @example
* console.log(Packet.fromHex('616263').offset)
* // 0
* @member {number}
*/
get offset () { return this.byteOffset }

/**
* the member function will be invoked when using `JSON.stringify`
* @returns {string} A string with format `Packet(length): hex`
Expand Down
108 changes: 108 additions & 0 deletions src/plugin/SerialPortAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import _ from 'lodash'
import { Duplex } from 'stream'
import { SerialPort } from 'serialport'

/**
* @module pn532.js/plugin/SerialPortAdapter
* @example
* import Pn532SerialPortAdapter from 'pn532.js/plugin/SerialPortAdapter'
*
* // Run serialport-list to list port, see https://serialport.io/docs/bin-list
* pn532.use(new Pn532SerialPortAdapter(), { path: '/dev/tty.usbserial-120' })
*/
export default class Pn532SerialPortAdapter {
name = 'adapter'
port = null

install (context, pluginOption = {}) {
const { pn532, utils } = context

if (pn532.$adapter) throw new Error('adapter already exists')

const me = this
pluginOption = {
baudRate: 115200,
...pluginOption,
}

/**
* Determines whether SerialPort is supported.
* @memberof Pn532WebserialAdapter
* @instance
* @async
* @returns {Promise<boolean>} Resolve with a boolean indicating whether or not SerialPort is supported.
*/
async function isSupported () {
return !_.isNil(SerialPort)
}

/**
* Determines whether the connection of adapter is open.
* @memberof Pn532WebserialAdapter
* @instance
* @async
* @returns {boolean} A boolean indicating whether or not the connection of adapter is open.
*/
function isOpen () {
return Boolean(me?.port?.isOpen)
}

/**
* Disconnect the connection of adapter.
* @memberof Pn532WebserialAdapter
* @instance
* @async
* @returns {Promise<null>} Resolve after finished.
*/
async function disconnect () {
if (_.isNil(me.port)) return
await new Promise((resolve, reject) => { me.port.close(err => err ? reject(err) : resolve()) })
}

async function disconnected () {
if (me.port) me.port = null
utils.logTime('device disconnected')
}

/**
* Open the connection of adapter.
* @memberof Pn532WebserialAdapter
* @instance
* @async
* @returns {Promise<null>} Resolve after finished.
*/
async function connect () {
try {
if (!await isSupported()) throw new Error('SerialPort not supported')

// open port
me.port = await new Promise((resolve, reject) => {
const port = new SerialPort(pluginOption, err => err ? reject(err) : resolve(port))
})
utils.logTime(`port connected, path = ${pluginOption.path}, baudRate = ${pluginOption.baudRate}`)
const portWeb = Duplex.toWeb(me.port)
pn532.tx = portWeb
me.port.once('close', disconnected)
portWeb.readable.pipeTo(pn532.rx.writable)

await pn532.sendCommandWakeup()
await pn532.resetSettings()
} catch (err) {
disconnected()
throw err
}
}

pn532.addMiddleware('writePacket', async (ctx, next) => {
if (!isOpen()) await connect()
return await next()
})

return {
connect,
disconnect,
isOpen,
isSupported,
}
}
}
Loading

0 comments on commit 79a423e

Please sign in to comment.