diff --git a/.eslintrc.js b/.eslintrc.js index 0c2016c06..641fca929 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,65 +1,61 @@ module.exports = { - "extends": [ - "eslint:recommended", - "plugin:node/recommended", - "plugin:mocha/recommended", - "plugin:@typescript-eslint/recommended", - ], - "parser": "@typescript-eslint/parser", - "plugins": [ - "prettier", - "node", - "mocha", - "@typescript-eslint", - ], - "env": { - "node": true, - "mocha": true, - "es6": true + extends: ['eslint:recommended', 'plugin:node/recommended', 'plugin:mocha/recommended', 'plugin:@typescript-eslint/recommended'], + parser: '@typescript-eslint/parser', + plugins: ['prettier', 'node', 'mocha', '@typescript-eslint'], + env: { + node: true, + mocha: true, + es6: true, }, - "parserOptions": { - "ecmaVersion": 12, + parserOptions: { + ecmaVersion: 12, }, - "globals": { - "assert": false, - "makeTestFeature": false, - "shouldReject": false + globals: { + assert: false, + makeTestFeature: false, + shouldReject: false, }, - "rules": { - "no-extra-semi": "off", - "no-process-exit": "off", - "no-var": "error", - "node/no-extraneous-require": "off", - "node/no-missing-import": "off", - "node/no-missing-require": "off", - "node/no-unpublished-import": "off", - "node/no-unpublished-require": "off", - "node/no-unsupported-features/es-builtins": "error", - "node/no-unsupported-features/es-syntax": "off", - "node/no-unsupported-features/node-builtins": "error", - "node/shebang": "off", - "object-shorthand": "error", - "prefer-arrow-callback": "error", - "prefer-const": "error", - "prefer-template": "error", - "prettier/prettier": [ - "error", + rules: { + 'no-extra-semi': 'off', //prettier does this + '@typescript-eslint/no-extra-semi': 'off', //prettier does this + 'no-process-exit': 'off', + 'no-var': 'error', + 'node/no-extraneous-import': [ + 'error', { - "singleQuote": true, - "trailingComma": "es5", - "semi": false, - "printWidth": 150, - "arrowParens": "avoid" - } + allowModules: ['sinon', 'chai'], //this gets pulled from monorepo root where the tests are run + }, ], - "mocha/no-exclusive-tests": "error", - "mocha/no-hooks-for-single-case": "off", - "mocha/no-mocha-arrows": "off", - "mocha/no-pending-tests": "error", - "mocha/no-setup-in-describe": "off", - "strict": ["error", "never"], - "valid-jsdoc": "off", - "@typescript-eslint/no-var-requires": "off", // until we get all js ported over - "@typescript-eslint/no-empty-function": "off", - } + 'node/no-missing-import': 'off', + 'node/no-missing-require': 'off', + 'node/no-unpublished-import': 'off', + 'node/no-unpublished-require': 'off', + 'node/no-unsupported-features/es-builtins': 'error', + 'node/no-unsupported-features/es-syntax': 'off', + 'node/no-unsupported-features/node-builtins': 'error', + 'node/shebang': 'off', + 'object-shorthand': 'error', + 'prefer-arrow-callback': 'error', + 'prefer-const': 'error', + 'prefer-template': 'error', + 'prettier/prettier': [ + 'error', + { + singleQuote: true, + trailingComma: 'es5', + semi: false, + printWidth: 150, + arrowParens: 'avoid', + }, + ], + 'mocha/no-exclusive-tests': 'error', + 'mocha/no-hooks-for-single-case': 'off', + 'mocha/no-mocha-arrows': 'off', + 'mocha/no-pending-tests': 'error', + 'mocha/no-setup-in-describe': 'off', + strict: ['error', 'never'], + 'valid-jsdoc': 'off', + '@typescript-eslint/no-var-requires': 'off', // until we get all js ported over + '@typescript-eslint/no-empty-function': 'off', + }, } diff --git a/.gitignore b/.gitignore index 5074ca5c3..70a6d89f2 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ lerna-debug.log node_modules npm-debug.log /packages/**/dist +tsconfig-build.tsbuildinfo +tsconfig.tsbuildinfo diff --git a/.mocharc.js b/.mocharc.js index d30506bf4..fe391c6f5 100644 --- a/.mocharc.js +++ b/.mocharc.js @@ -3,8 +3,7 @@ module.exports = { bail: true, require: [ - 'esbuild-register', - './test/initializers' + 'esbuild-register' ], - 'spec': ['packages/**/*.test.js'], + 'spec': ['packages/**/*.test.ts'], } diff --git a/UPGRADE_GUIDE.md b/UPGRADE_GUIDE.md index 89616d9e2..6870a8685 100644 --- a/UPGRADE_GUIDE.md +++ b/UPGRADE_GUIDE.md @@ -1,3 +1,23 @@ +Upgading from 9x to 10x +------------- + +- Typscript types are now available for all packages +- Serialport no longer supports Node 10 +- SerialPort stream (from npm `serialport` and `@serialport/stream`) now take an options object with a required `path` and `baudRate`. They no longer default to a `baudRate` of 9600 as that's slow and usually not what's needed. +- All packages now have named exports. They export their types and all relevant classes. +- The package `serialport` no longer exports `serialport/test` but instead who classes `{ SerialPort, SerialPortMock }` where the mock is pre-configured with a mock binding. +- Bindings have moved from `@serialport/bindings` to `@serialport/bindings-cpp` they are shipped with `prebuildify` and no longer require a post install download. +- `@serialport/bindings-cpp` leverages N-API and shouldn't need to be upgraded for every node release or rebuild for electron +- Bindings in general now have a new interface with the `@serialport/bindings-interface` type package that replaces `@serialport/bindings-abstract` +- `SerialPortStream` from `@serialport/stream` no longer has a `list()` method as that was a direct call to the bindings. +- `SerialPort` class from the `serialport` package no longer has a static `Bindings` property as it provides the OS specific bindings from `@serialport/bindings-cpp` if you need control of the bindings use `SerialPortStream`. +- `SerialPortStream` methods (and by extension `SerialPort` methods) no longer throw when called with invalid input, their callbacks or the error event will convey input errors. This is because the binding layer now handles input validation as it's different on each platform. +- `SerialPort` is now delivering a few more parsers + +TODO +- stop bits are incorrect + + Upgrading from 8.x to 9.x ------------- - Serialport no longer supports Node 8 diff --git a/bin/find-arduino.js b/bin/find-arduino.ts similarity index 65% rename from bin/find-arduino.js rename to bin/find-arduino.ts index c81b7c636..1247f212d 100755 --- a/bin/find-arduino.js +++ b/bin/find-arduino.ts @@ -1,9 +1,9 @@ -#!/usr/bin/env node +#!/usr/bin/env node -r esbuild-register // outputs the path to an Arduino to stdout or an error to stderror -const SerialPort = require('../packages/serialport') -SerialPort.list().then(ports => { +import { autoDetect } from '@serialport/bindings-cpp' +autoDetect().list().then(ports => { const port = ports.find(port => /arduino/i.test(port.manufacturer)) if (!port) { console.error('Arduino Not found') diff --git a/bin/write-a-lot.js b/bin/write-a-lot.js deleted file mode 100755 index 6d8106144..000000000 --- a/bin/write-a-lot.js +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env node - -process.env.DEBUG = '*' -const SerialPort = require('../packages/serialport') - -// outputs the path to an arduino or nothing -async function findArduino() { - if (process.argv[2]) { - return process.argv[2] - } - const ports = await SerialPort.list() - for (const port of ports) { - if (/arduino/i.test(port.manufacturer)) { - return port.path - } - } - throw new Error('No arduinos found') -} - -findArduino().then( - portName => { - const port = new SerialPort(portName) - port.on('open', () => { - console.log('opened', portName) - // port.write(Buffer.alloc(1024 * 20, 0)); - port.on('data', data => console.log('data', data.toString())) // put the port into flowing mode - // setTimeout(() => { - // console.log('closing'); - // port.close((err) => { - // console.log('closed?', err); - // }); - // }, 5000); - }) - }, - () => { - console.log('no arduino') - } -) - -process.on('unhandledRejection', r => console.log(r, r.stack)) diff --git a/package-lock.json b/package-lock.json index 5d0d5bec3..853d637a0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "hasInstallScript": true, "license": "MIT", "devDependencies": { + "@serialport/bindings-cpp": "^10.6.1", "@tsconfig/node12": "^1.0.9", "@types/chai": "^4.3.0", "@types/chai-subset": "^1.3.3", @@ -1922,6 +1923,62 @@ "@octokit/openapi-types": "^11.2.0" } }, + "node_modules/@serialport/bindings-cpp": { + "version": "10.6.1", + "resolved": "https://registry.npmjs.org/@serialport/bindings-cpp/-/bindings-cpp-10.6.1.tgz", + "integrity": "sha512-wlgiCoImyA0siK0Z1ziXFDv6Ua3Tko9vMyNPAMW/Ywk18vKVKPYvvGlYxhbdK9LGzGdqVXGWmEW15R/S+anzQQ==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@serialport/bindings-interface": "1.2.0", + "@serialport/parser-readline": "10.0.1", + "debug": "^4.3.2", + "node-addon-api": "^4.3.0", + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=12.17.0 <13.0 || >=14.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/bindings-interface": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@serialport/bindings-interface/-/bindings-interface-1.2.0.tgz", + "integrity": "sha512-TLg6z0wyEdfVBIGmRwv0oru7Ijdc3VhH5SDv+q7UXuXvNh+mEpsFedHG205GSGVXbnF1xlCN5cBSMloI74N/gQ==", + "dev": true, + "engines": { + "node": "^12.22 || ^14.13 || >=16" + } + }, + "node_modules/@serialport/parser-delimiter": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-10.0.1.tgz", + "integrity": "sha512-B0c6dm9UCpRU/LhkvRFL3OSbs69VqWU7mjW7tM109JDNS+vw8uJPumXz8Giub6D0xl90J7euH6tBTqERk7048Q==", + "dev": true, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-readline": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-10.0.1.tgz", + "integrity": "sha512-jdKPNka/Nn17k89T5UIyis39EaZHQCmq+83s0icBt2iPBlX8+BrJAUBe8myFpuT22qskTVNzFoTMPOp8pjK/yw==", + "dev": true, + "dependencies": { + "@serialport/parser-delimiter": "10.0.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, "node_modules/@sinonjs/commons": { "version": "1.8.3", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", @@ -7644,6 +7701,12 @@ "@sinonjs/commons": "^1.7.0" } }, + "node_modules/node-addon-api": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", + "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==", + "dev": true + }, "node_modules/node-fetch": { "version": "2.6.6", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz", @@ -7703,6 +7766,17 @@ "node": ">= 6.0.0" } }, + "node_modules/node-gyp-build": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.3.0.tgz", + "integrity": "sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==", + "dev": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, "node_modules/node-gyp/node_modules/chownr": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", @@ -12394,6 +12468,40 @@ "@octokit/openapi-types": "^11.2.0" } }, + "@serialport/bindings-cpp": { + "version": "10.6.1", + "resolved": "https://registry.npmjs.org/@serialport/bindings-cpp/-/bindings-cpp-10.6.1.tgz", + "integrity": "sha512-wlgiCoImyA0siK0Z1ziXFDv6Ua3Tko9vMyNPAMW/Ywk18vKVKPYvvGlYxhbdK9LGzGdqVXGWmEW15R/S+anzQQ==", + "dev": true, + "requires": { + "@serialport/bindings-interface": "1.2.0", + "@serialport/parser-readline": "10.0.1", + "debug": "^4.3.2", + "node-addon-api": "^4.3.0", + "node-gyp-build": "^4.3.0" + } + }, + "@serialport/bindings-interface": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@serialport/bindings-interface/-/bindings-interface-1.2.0.tgz", + "integrity": "sha512-TLg6z0wyEdfVBIGmRwv0oru7Ijdc3VhH5SDv+q7UXuXvNh+mEpsFedHG205GSGVXbnF1xlCN5cBSMloI74N/gQ==", + "dev": true + }, + "@serialport/parser-delimiter": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-10.0.1.tgz", + "integrity": "sha512-B0c6dm9UCpRU/LhkvRFL3OSbs69VqWU7mjW7tM109JDNS+vw8uJPumXz8Giub6D0xl90J7euH6tBTqERk7048Q==", + "dev": true + }, + "@serialport/parser-readline": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-10.0.1.tgz", + "integrity": "sha512-jdKPNka/Nn17k89T5UIyis39EaZHQCmq+83s0icBt2iPBlX8+BrJAUBe8myFpuT22qskTVNzFoTMPOp8pjK/yw==", + "dev": true, + "requires": { + "@serialport/parser-delimiter": "10.0.1" + } + }, "@sinonjs/commons": { "version": "1.8.3", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", @@ -16743,6 +16851,12 @@ } } }, + "node-addon-api": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", + "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==", + "dev": true + }, "node-fetch": { "version": "2.6.6", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz", @@ -16891,6 +17005,12 @@ } } }, + "node-gyp-build": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.3.0.tgz", + "integrity": "sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==", + "dev": true + }, "node-preload": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", diff --git a/package.json b/package.json index 15ce6964b..bb91c948e 100644 --- a/package.json +++ b/package.json @@ -15,16 +15,17 @@ "outdated": "lerna exec --no-bail npm outdated && npm outdated", "postinstall": "lerna bootstrap --no-ci", "publish": "lerna publish --exact", - "test:arduino": "TEST_PORT=$(./bin/find-arduino.js) npm test", + "test:arduino": "TEST_PORT=$(./bin/find-arduino.ts) npm test", "test:watch": "mocha -w", "test": "nyc --reporter=html --reporter=text --reporter lcovonly mocha", - "typecheck": "tsc" + "typecheck": "lerna exec tsc" }, "repository": { "type": "git", "url": "git://github.com/serialport/node-serialport.git" }, "devDependencies": { + "@serialport/bindings-cpp": "^10.6.1", "@tsconfig/node12": "^1.0.9", "@types/chai": "^4.3.0", "@types/chai-subset": "^1.3.3", diff --git a/packages/binding-abstract/.npmignore b/packages/binding-abstract/.npmignore deleted file mode 100644 index a1623b1ea..000000000 --- a/packages/binding-abstract/.npmignore +++ /dev/null @@ -1,3 +0,0 @@ -.DS_Store -*.test.js -CHANGELOG.md diff --git a/packages/binding-abstract/CHANGELOG.md b/packages/binding-abstract/CHANGELOG.md deleted file mode 100644 index 15d799633..000000000 --- a/packages/binding-abstract/CHANGELOG.md +++ /dev/null @@ -1,83 +0,0 @@ -# Change Log - -All notable changes to this project will be documented in this file. -See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. - -# [10.1.0](https://github.com/serialport/node-serialport/compare/v10.0.2...v10.1.0) (2022-01-23) - -**Note:** Version bump only for package @serialport/binding-abstract - - - - - -## [10.0.1](https://github.com/serialport/node-serialport/compare/v10.0.0...v10.0.1) (2021-12-25) - -**Note:** Version bump only for package @serialport/binding-abstract - - - - - -# [10.0.0](https://github.com/serialport/node-serialport/compare/v9.2.8...v10.0.0) (2021-12-11) - -**Note:** Version bump only for package @serialport/binding-abstract - - - - - -## [9.2.3](https://github.com/serialport/node-serialport/compare/v9.2.1...v9.2.3) (2021-09-24) - -**Note:** Version bump only for package @serialport/binding-abstract - - - - - -## [9.2.2](https://github.com/serialport/node-serialport/compare/v9.2.1...v9.2.2) (2021-09-24) - -**Note:** Version bump only for package @serialport/binding-abstract - - - - - -## [9.0.7](https://github.com/serialport/node-serialport/compare/v9.0.6...v9.0.7) (2021-02-22) - -**Note:** Version bump only for package @serialport/binding-abstract - - - - - -## [9.0.2](https://github.com/serialport/node-serialport/compare/v9.0.1...v9.0.2) (2020-10-16) - - -### Bug Fixes - -* while validating for offset, check for offset's value for NaN instead length ([#2124](https://github.com/serialport/node-serialport/issues/2124)) ([4215122](https://github.com/serialport/node-serialport/commit/42151228240c5c818ac5327d6ff5c01398805564)) - - - - - -## [9.0.1](https://github.com/serialport/node-serialport/compare/v9.0.0...v9.0.1) (2020-08-08) - -**Note:** Version bump only for package @serialport/binding-abstract - - - - - -# [9.0.0](https://github.com/serialport/node-serialport/compare/v8.0.8...v9.0.0) (2020-05-10) - -**Note:** Version bump only for package @serialport/binding-abstract - - - - - -## [8.0.6](https://github.com/serialport/node-serialport/compare/v8.0.5...v8.0.6) (2019-12-25) - -**Note:** Version bump only for package @serialport/binding-abstract diff --git a/packages/binding-abstract/README.md b/packages/binding-abstract/README.md index df7bda10f..a526e30ac 100644 --- a/packages/binding-abstract/README.md +++ b/packages/binding-abstract/README.md @@ -1,14 +1,3 @@ # @serialport/BindingAbstract -This Abstract binding class is the base for all serialport bindings. You wouldn't use this class directly but instead extend it to make a new binding for a different platform or underling technology. - -This is currently used for the win32, linux, darwin and mock bindings. - -This is how you use it. -```js -class MockBinding extends AbstractBinding { - constructor(opt) { - super(opt) - } -} -``` +The bindings package has been renamed [`@serialport/bindings-interface`](https://www.npmjs.com/package/@serialport/bindings-interface) and it's code has been moved out of the monorepo to it's [own repository](https://github.com/serialport/bindings-interface/). diff --git a/packages/binding-abstract/lib/index.js b/packages/binding-abstract/lib/index.js deleted file mode 100644 index 2b62c4ec7..000000000 --- a/packages/binding-abstract/lib/index.js +++ /dev/null @@ -1,222 +0,0 @@ -const debug = require('debug')('serialport/binding-abstract') - -/** - * @name Binding - * @type {AbstractBinding} - * @since 5.0.0 - * @description The `Binding` is how Node-SerialPort talks to the underlying system. By default, we auto detect Windows, Linux and OS X, and load the appropriate module for your system. You can assign `SerialPort.Binding` to any binding you like. Find more by searching at [npm](https://npmjs.org/). - Prevent auto loading the default bindings by requiring SerialPort with: - ```js - var SerialPort = require('@serialport/stream'); - SerialPort.Binding = MyBindingClass; - ``` - */ - -/** - * You never have to use `Binding` objects directly. SerialPort uses them to access the underlying hardware. This documentation is geared towards people who are making bindings for different platforms. This class can be inherited from to get type checking for each method. - * @class AbstractBinding - * @param {object} options options for the binding - * @property {boolean} isOpen Required property. `true` if the port is open, `false` otherwise. Should be read-only. - * @throws {TypeError} When given invalid arguments, a `TypeError` is thrown. - * @since 5.0.0 - */ -class AbstractBinding { - /** - * Retrieves a list of available serial ports with metadata. The `path` must be guaranteed, and all other fields should be undefined if unavailable. The `path` is either the path or an identifier (eg `COM1`) used to open the serialport. - * @returns {Promise} resolves to an array of port [info objects](#module_serialport--SerialPort.list). - */ - static async list() { - debug('list') - } - - constructor(opt = {}) { - if (typeof opt !== 'object') { - throw new TypeError('"options" is not an object') - } - } - - /** - * Opens a connection to the serial port referenced by the path. - * @param {string} path the path or com port to open - * @param {openOptions} options openOptions for the serialport - * @returns {Promise} Resolves after the port is opened and configured. - * @rejects {TypeError} When given invalid arguments, a `TypeError` is rejected. - */ - async open(path, options) { - if (!path) { - throw new TypeError('"path" is not a valid port') - } - - if (typeof options !== 'object') { - throw new TypeError('"options" is not an object') - } - debug('open') - - if (this.isOpen) { - throw new Error('Already open') - } - } - - /** - * Closes an open connection - * @returns {Promise} Resolves once the connection is closed. - * @rejects {TypeError} When given invalid arguments, a `TypeError` is rejected. - */ - async close() { - debug('close') - if (!this.isOpen) { - throw new Error('Port is not open') - } - } - - /** - * Request a number of bytes from the SerialPort. This function is similar to Node's [`fs.read`](http://nodejs.org/api/fs.html#fs_fs_read_fd_buffer_offset_length_position_callback) except it will always return at least one byte. - -The in progress reads must error when the port is closed with an error object that has the property `canceled` equal to `true`. Any other error will cause a disconnection. - - * @param {buffer} buffer Accepts a [`Buffer`](http://nodejs.org/api/buffer.html) object. - * @param {integer} offset The offset in the buffer to start writing at. - * @param {integer} length Specifies the maximum number of bytes to read. - * @returns {Promise} Resolves with the number of bytes read after a read operation. - * @rejects {TypeError} When given invalid arguments, a `TypeError` is rejected. - */ - async read(buffer, offset, length) { - if (!Buffer.isBuffer(buffer)) { - throw new TypeError('"buffer" is not a Buffer') - } - - if (typeof offset !== 'number' || isNaN(offset)) { - throw new TypeError(`"offset" is not an integer got "${isNaN(offset) ? 'NaN' : typeof offset}"`) - } - - if (typeof length !== 'number' || isNaN(length)) { - throw new TypeError(`"length" is not an integer got "${isNaN(length) ? 'NaN' : typeof length}"`) - } - - debug('read') - if (buffer.length < offset + length) { - throw new Error('buffer is too small') - } - - if (!this.isOpen) { - throw new Error('Port is not open') - } - } - - /** - * Write bytes to the SerialPort. Only called when there is no pending write operation. - -The in progress writes must error when the port is closed with an error object that has the property `canceled` equal to `true`. Any other error will cause a disconnection. - - * @param {buffer} buffer - Accepts a [`Buffer`](http://nodejs.org/api/buffer.html) object. - * @returns {Promise} Resolves after the data is passed to the operating system for writing. - * @rejects {TypeError} When given invalid arguments, a `TypeError` is rejected. - */ - async write(buffer) { - if (!Buffer.isBuffer(buffer)) { - throw new TypeError('"buffer" is not a Buffer') - } - - debug('write', buffer.length, 'bytes') - if (!this.isOpen) { - debug('write', 'error port is not open') - - throw new Error('Port is not open') - } - } - - /** - * Changes connection settings on an open port. Only `baudRate` is supported. - * @param {object=} options Only supports `baudRate`. - * @param {number=} [options.baudRate] If provided a baud rate that the bindings do not support, it should reject. - * @returns {Promise} Resolves once the port's baud rate changes. - * @rejects {TypeError} When given invalid arguments, a `TypeError` is rejected. - */ - async update(options) { - if (typeof options !== 'object') { - throw TypeError('"options" is not an object') - } - - if (typeof options.baudRate !== 'number') { - throw new TypeError('"options.baudRate" is not a number') - } - - debug('update') - if (!this.isOpen) { - throw new Error('Port is not open') - } - } - - /** - * Set control flags on an open port. - * @param {object=} options All options are operating system default when the port is opened. Every flag is set on each call to the provided or default values. All options are always provided. - * @param {Boolean} [options.brk=false] flag for brk - * @param {Boolean} [options.cts=false] flag for cts - * @param {Boolean} [options.dsr=false] flag for dsr - * @param {Boolean} [options.dtr=true] flag for dtr - * @param {Boolean} [options.rts=true] flag for rts - * @param {Boolean} [options.lowLatency=false] flag for lowLatency mode on Linux - * @returns {Promise} Resolves once the port's flags are set. - * @rejects {TypeError} When given invalid arguments, a `TypeError` is rejected. - */ - async set(options) { - if (typeof options !== 'object') { - throw new TypeError('"options" is not an object') - } - debug('set') - if (!this.isOpen) { - throw new Error('Port is not open') - } - } - - /** - * Get the control flags (CTS, DSR, DCD) on the open port. - * @returns {Promise} Resolves with the retrieved flags. - * @rejects {TypeError} When given invalid arguments, a `TypeError` is rejected. - */ - async get() { - debug('get') - if (!this.isOpen) { - throw new Error('Port is not open') - } - } - - /** - * Get the OS reported baud rate for the open port. - * Used mostly for debugging custom baud rates. - * @returns {Promise} Resolves with the current baud rate. - * @rejects {TypeError} When given invalid arguments, a `TypeError` is rejected. - */ - async getBaudRate() { - debug('getBaudRate') - if (!this.isOpen) { - throw new Error('Port is not open') - } - } - - /** - * Flush (discard) data received but not read, and written but not transmitted. - * @returns {Promise} Resolves once the flush operation finishes. - * @rejects {TypeError} When given invalid arguments, a `TypeError` is rejected. - */ - async flush() { - debug('flush') - if (!this.isOpen) { - throw new Error('Port is not open') - } - } - - /** - * Drain waits until all output data is transmitted to the serial port. An in progress write should be completed before this returns. - * @returns {Promise} Resolves once the drain operation finishes. - * @rejects {TypeError} When given invalid arguments, a `TypeError` is rejected. - */ - async drain() { - debug('drain') - if (!this.isOpen) { - throw new Error('Port is not open') - } - } -} - -module.exports = AbstractBinding diff --git a/packages/binding-abstract/lib/index.test.js b/packages/binding-abstract/lib/index.test.js deleted file mode 100644 index afef45b08..000000000 --- a/packages/binding-abstract/lib/index.test.js +++ /dev/null @@ -1,9 +0,0 @@ -/* eslint-disable no-new */ - -const BindingAbstract = require('../') - -describe('BindingAbstract', () => { - it('constructs', () => { - new BindingAbstract({}) - }) -}) diff --git a/packages/binding-abstract/package-lock.json b/packages/binding-abstract/package-lock.json deleted file mode 100644 index c60917648..000000000 --- a/packages/binding-abstract/package-lock.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "name": "@serialport/binding-abstract", - "version": "10.1.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "@serialport/binding-abstract", - "version": "10.1.0", - "license": "MIT", - "dependencies": { - "debug": "^4.3.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://opencollective.com/serialport/donate" - } - }, - "node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - } - }, - "dependencies": { - "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - } - } -} diff --git a/packages/binding-abstract/package.json b/packages/binding-abstract/package.json deleted file mode 100644 index 81e863403..000000000 --- a/packages/binding-abstract/package.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "@serialport/binding-abstract", - "version": "10.1.0", - "main": "lib", - "keywords": [ - "serialport-binding" - ], - "dependencies": { - "debug": "^4.3.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "publishConfig": { - "access": "public" - }, - "license": "MIT", - "repository": { - "type": "git", - "url": "git://github.com/serialport/node-serialport.git" - }, - "funding": "https://opencollective.com/serialport/donate" -} diff --git a/packages/binding-mock/.npmignore b/packages/binding-mock/.npmignore index 11d827cd7..df4db6a44 100644 --- a/packages/binding-mock/.npmignore +++ b/packages/binding-mock/.npmignore @@ -4,3 +4,4 @@ CHANGELOG.md lib tsconfig.json tsconfig-build.json +tsconfig-build.tsbuildinfo diff --git a/packages/binding-mock/README.md b/packages/binding-mock/README.md index 558293aac..9040ea9c5 100644 --- a/packages/binding-mock/README.md +++ b/packages/binding-mock/README.md @@ -1,11 +1,6 @@ # @serialport/binding-mock -This does some neat stuff. - -This is why you'd use it. - -This is how you use it. -```js +```ts import { MockBinding } from '@serialport/binding-mock' const MockBinding = new MockBinding() ``` diff --git a/packages/binding-mock/lib/index.test.ts b/packages/binding-mock/lib/index.test.ts index a023eaa44..766e4d781 100644 --- a/packages/binding-mock/lib/index.test.ts +++ b/packages/binding-mock/lib/index.test.ts @@ -1,8 +1,9 @@ import { MockBinding } from './' -import { OpenOptions } from '@serialport/bindings-cpp' -import { assert, shouldReject } from '../../../test/initializers/assert' +import { OpenOptions } from '@serialport/bindings-interface' +import { assert, shouldReject } from '../../../test/assert' const openOptions: OpenOptions = { + path: '/dev/exists', baudRate: 9600, dataBits: 8, lock: false, @@ -16,41 +17,31 @@ const openOptions: OpenOptions = { } describe('MockBinding', () => { - it('constructs', () => { - new MockBinding() + afterEach(() => { + MockBinding.reset() }) - let binding: MockBinding - describe('instance method', () => { - beforeEach(() => { - binding = new MockBinding() - }) - describe('open', () => { describe('when phony port not created', () => { - it('should reject', () => { - return shouldReject(binding.open('/dev/ttyUSB0', openOptions)) + it('should reject', async () => { + await shouldReject(MockBinding.open(openOptions)) }) }) describe('when phony port created', () => { beforeEach(() => { - MockBinding.createPort('/dev/ttyUSB0') - }) - - afterEach(() => { - MockBinding.reset() + MockBinding.createPort('/dev/exists') }) it('should open the phony port', async () => { - await binding.open('/dev/ttyUSB0', openOptions) - assert.isTrue(binding.isOpen) + const port = await MockBinding.open(openOptions) + assert.isTrue(port.isOpen) }) it('should have a "port" prop with "info.serialNumber" prop', async () => { - await binding.open('/dev/ttyUSB0', openOptions) - assert.strictEqual(binding.port?.info.serialNumber, '1') + const port = await MockBinding.open(openOptions) + assert.strictEqual(port.port.info.serialNumber, '1') }) }) }) @@ -58,48 +49,33 @@ describe('MockBinding', () => { describe('static method', () => { describe('createPort', () => { - afterEach(() => { - MockBinding.reset() - }) - it('should increment the serialNumber', async () => { - MockBinding.createPort('/dev/ttyUSB0') + MockBinding.createPort('/dev/exists') MockBinding.createPort('/dev/ttyUSB1') - const binding1 = new MockBinding() - await binding1.open('/dev/ttyUSB0', openOptions) - const binding2 = new MockBinding() - await binding2.open('/dev/ttyUSB1', openOptions) - assert.strictEqual(binding2.port?.info.serialNumber, '2') + const port1 = await MockBinding.open(openOptions) + const port2 = await MockBinding.open({ ...openOptions, path: '/dev/ttyUSB1' }) + assert.strictEqual(port1.port.info.serialNumber, '1') + assert.strictEqual(port2.port.info.serialNumber, '2') }) }) describe('reset', () => { beforeEach(async () => { - MockBinding.createPort('/dev/ttyUSB0') - binding = new MockBinding() - await binding.open('/dev/ttyUSB0', openOptions) - assert.strictEqual(binding.port?.info.serialNumber, '1') - await binding.close() - }) - - afterEach(async () => { - // speculative cleanup - try { - await binding.close() - } catch (ignored) { - // ignored - } + MockBinding.createPort('/dev/exists') + const port = await MockBinding.open(openOptions) + assert.strictEqual(port.port?.info.serialNumber, '1') + await port.close() }) - it('should delete any configured phony ports', () => { + it('should delete any configured phony ports', async () => { MockBinding.reset() - return shouldReject(binding.open('/dev/ttyUSB0', openOptions)) + await shouldReject(MockBinding.open(openOptions)) }) it('should reset the serialNumber assigned to the phony port', async () => { MockBinding.reset() - MockBinding.createPort('/dev/ttyUSB0') - await binding.open('/dev/ttyUSB0', openOptions) - assert.strictEqual(binding.port?.info.serialNumber, '1') + MockBinding.createPort('/dev/exists') + const port = await MockBinding.open(openOptions) + assert.strictEqual(port.port.info.serialNumber, '1') }) }) }) diff --git a/packages/binding-mock/lib/index.ts b/packages/binding-mock/lib/index.ts index 84d726030..100c4de89 100644 --- a/packages/binding-mock/lib/index.ts +++ b/packages/binding-mock/lib/index.ts @@ -1,13 +1,14 @@ import debugFactory from 'debug' -import { BindingInterface, PortStatus, SetOptions, UpdateOptions, OpenOptions, PortInfo } from '@serialport/bindings-cpp' -const debug = debugFactory('serialport/bindings-mock') +import { BindingInterface, BindingPortInterface, PortStatus, SetOptions, UpdateOptions, OpenOptions, PortInfo } from '@serialport/bindings-interface' +const debug = debugFactory('serialport/binding-mock') interface MockPortInternal { data: Buffer echo: boolean record: boolean - readyData: Buffer info: PortInfo + maxReadSize: number + readyData?: Buffer openOpt?: OpenOptions } @@ -15,6 +16,7 @@ interface CreatePortOptions { echo?: boolean record?: boolean readyData?: Buffer + maxReadSize?: number manufacturer?: string vendorId?: string productId?: string @@ -37,125 +39,152 @@ export class CanceledError extends Error { } } -/** - * Mock bindings for pretend serialport access - */ -export class MockBinding extends BindingInterface { - pendingRead: null | ((err: null | Error) => void) - port: null | MockPortInternal - lastWrite: null | Buffer - recording: Buffer - writeOperation: null | Promise - isOpen: boolean - serialNumber?: string - - constructor() { - super() - this.pendingRead = null - this.isOpen = false - this.port = null - this.lastWrite = null - this.recording = Buffer.alloc(0) - this.writeOperation = null // in flight promise or null - } +export interface MockBindingInterface extends BindingInterface { + reset(): void + createPort(path: string, opt?: CreatePortOptions): void +} - // Reset mocks - static reset() { +export const MockBinding: MockBindingInterface = { + reset() { ports = {} serialNumber = 0 - } + }, // Create a mock port - static createPort(path: string, opt: CreatePortOptions = {}) { + createPort(path: string, options: CreatePortOptions = {}) { serialNumber++ const optWithDefaults = { echo: false, record: false, - readyData: Buffer.from('READY'), manufacturer: 'The J5 Robotics Company', vendorId: undefined, productId: undefined, - ...opt, + maxReadSize: 1024, + ...options, } ports[path] = { data: Buffer.alloc(0), echo: optWithDefaults.echo, record: optWithDefaults.record, - readyData: Buffer.from(optWithDefaults.readyData), + readyData: optWithDefaults.readyData, + maxReadSize: optWithDefaults.maxReadSize, info: { path, - manufacturer: opt.manufacturer, + manufacturer: optWithDefaults.manufacturer, serialNumber: `${serialNumber}`, pnpId: undefined, locationId: undefined, - vendorId: opt.vendorId, - productId: opt.productId, + vendorId: optWithDefaults.vendorId, + productId: optWithDefaults.productId, }, } - debug(serialNumber, 'created port', JSON.stringify({ path, opt })) - } + debug(serialNumber, 'created port', JSON.stringify({ path, opt: options })) + }, - static async list() { + async list() { + debug(null, 'list') return Object.values(ports).map(port => port.info) - } + }, - // Emit data on a mock port - emitData(data: Buffer | string) { - if (!this.isOpen || !this.port) { - throw new Error('Port must be open to pretend to receive data') - } - const bufferData = Buffer.isBuffer(data) ? data : Buffer.from(data) - debug(this.serialNumber, 'emitting data - pending read:', Boolean(this.pendingRead)) - this.port.data = Buffer.concat([this.port.data, bufferData]) - if (this.pendingRead) { - process.nextTick(this.pendingRead) - this.pendingRead = null + async open(options) { + if (!options || typeof options !== 'object' || Array.isArray(options)) { + throw new TypeError('"options" is not an object') } - } - async open(path: string, options?: OpenOptions) { - if (!path) { + if (!options.path) { throw new TypeError('"path" is not a valid port') } - if (typeof options !== 'object') { - throw new TypeError('"options" is not an object') + if (!options.baudRate) { + throw new TypeError('"baudRate" is not a valid baudRate') } - debug(null, `opening path ${path}`) - if (this.isOpen) { - throw new Error('Already open') + const openOptions: Required = { + dataBits: 8, + lock: true, + stopBits: 1, + parity: 'none', + rtscts: false, + xon: false, + xoff: false, + xany: false, + hupcl: true, + ...options, } + const { path } = openOptions - const port = (this.port = ports[path]) + debug(null, `open: opening path ${path}`) + + const port = ports[path] await resolveNextTick() if (!port) { throw new Error(`Port does not exist - please call MockBinding.createPort('${path}') first`) } - this.serialNumber = port.info.serialNumber + + const serialNumber = port.info.serialNumber if (port.openOpt?.lock) { + debug(serialNumber, `open: Port is locked cannot open`) throw new Error('Port is locked cannot open') } - if (this.isOpen) { - throw new Error('Open: binding is already open') - } + debug(serialNumber, `open: opened path ${path}`) + + port.openOpt = { ...openOptions } + + return new MockPortBinding(port, openOptions) + }, +} + +/** + * Mock bindings for pretend serialport access + */ +export class MockPortBinding implements BindingPortInterface { + readonly openOptions: Required + readonly port: MockPortInternal + private pendingRead: null | ((err: null | Error) => void) + lastWrite: null | Buffer + recording: Buffer + writeOperation: null | Promise + isOpen: boolean + serialNumber?: string - port.openOpt = { ...options } + constructor(port: MockPortInternal, openOptions: Required) { + this.port = port + this.openOptions = openOptions + this.pendingRead = null this.isOpen = true - debug(this.serialNumber, 'port is open') - if (port.echo) { + this.lastWrite = null + this.recording = Buffer.alloc(0) + this.writeOperation = null // in flight promise or null + this.serialNumber = port.info.serialNumber + + if (port.readyData) { + const data = port.readyData process.nextTick(() => { if (this.isOpen) { debug(this.serialNumber, 'emitting ready data') - this.emitData(port.readyData) + this.emitData(data) } }) } } + // Emit data on a mock port + emitData(data: Buffer | string) { + if (!this.isOpen || !this.port) { + throw new Error('Port must be open to pretend to receive data') + } + const bufferData = Buffer.isBuffer(data) ? data : Buffer.from(data) + debug(this.serialNumber, 'emitting data - pending read:', Boolean(this.pendingRead)) + this.port.data = Buffer.concat([this.port.data, bufferData]) + if (this.pendingRead) { + process.nextTick(this.pendingRead) + this.pendingRead = null + } + } + async close(): Promise { debug(this.serialNumber, 'close') if (!this.isOpen) { @@ -167,12 +196,11 @@ export class MockBinding extends BindingInterface { throw new Error('already closed') } - delete port.openOpt + port.openOpt = undefined // reset data on close port.data = Buffer.alloc(0) debug(this.serialNumber, 'port is closed') - this.port = null - delete this.serialNumber + this.serialNumber = undefined this.isOpen = false if (this.pendingRead) { this.pendingRead(new CanceledError('port is closed')) @@ -222,9 +250,12 @@ export class MockBinding extends BindingInterface { } }) } - const data = this.port.data.slice(0, length) + + const lengthToRead = this.port.maxReadSize > length ? length : this.port.maxReadSize + + const data = this.port.data.slice(0, lengthToRead) const bytesRead = data.copy(buffer, offset) - this.port.data = this.port.data.slice(length) + this.port.data = this.port.data.slice(lengthToRead) debug(this.serialNumber, 'read', bytesRead, 'bytes') return { bytesRead, buffer } } diff --git a/packages/binding-mock/package-lock.json b/packages/binding-mock/package-lock.json index 62e44cc24..442bb05d0 100644 --- a/packages/binding-mock/package-lock.json +++ b/packages/binding-mock/package-lock.json @@ -9,7 +9,7 @@ "version": "10.1.0", "license": "MIT", "dependencies": { - "@serialport/bindings-cpp": "^10.4.0", + "@serialport/bindings-interface": "1.2.0", "debug": "^4.3.2" }, "devDependencies": { @@ -22,62 +22,12 @@ "url": "https://opencollective.com/serialport/donate" } }, - "node_modules/@serialport/bindings-cpp": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/@serialport/bindings-cpp/-/bindings-cpp-10.4.0.tgz", - "integrity": "sha512-vV5fadfzovV2iZ9xWjlQOdjEL0U0y5ewjdplx1DG5ox+/Q2Mmi8WEbcoKww6AgouU5qLirQGfYDmcWPlcGMdMw==", - "hasInstallScript": true, - "dependencies": { - "@serialport/binding-abstract": "10.0.1", - "@serialport/parser-readline": "10.0.1", - "debug": "^4.3.2", - "node-addon-api": "^4.3.0", - "node-gyp-build": "^4.3.0" - }, - "engines": { - "node": ">=12.17.0 <13.0 || >=14.0.0" - }, - "funding": { - "url": "https://opencollective.com/serialport/donate" - } - }, - "node_modules/@serialport/bindings-cpp/node_modules/@serialport/binding-abstract": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@serialport/binding-abstract/-/binding-abstract-10.0.1.tgz", - "integrity": "sha512-FWD/uNrz8V3kaTILQTK05Z1LB/LZin8XZelmX/wd1NNlRFAj6V64MIESWhwUy3iPnL1QriFR1k7URHHx3RRgfg==", - "dependencies": { - "debug": "^4.3.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://opencollective.com/serialport/donate" - } - }, - "node_modules/@serialport/parser-delimiter": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-10.0.1.tgz", - "integrity": "sha512-B0c6dm9UCpRU/LhkvRFL3OSbs69VqWU7mjW7tM109JDNS+vw8uJPumXz8Giub6D0xl90J7euH6tBTqERk7048Q==", - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://opencollective.com/serialport/donate" - } - }, - "node_modules/@serialport/parser-readline": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-10.0.1.tgz", - "integrity": "sha512-jdKPNka/Nn17k89T5UIyis39EaZHQCmq+83s0icBt2iPBlX8+BrJAUBe8myFpuT22qskTVNzFoTMPOp8pjK/yw==", - "dependencies": { - "@serialport/parser-delimiter": "10.0.1" - }, + "node_modules/@serialport/bindings-interface": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@serialport/bindings-interface/-/bindings-interface-1.2.0.tgz", + "integrity": "sha512-TLg6z0wyEdfVBIGmRwv0oru7Ijdc3VhH5SDv+q7UXuXvNh+mEpsFedHG205GSGVXbnF1xlCN5cBSMloI74N/gQ==", "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://opencollective.com/serialport/donate" + "node": "^12.22 || ^14.13 || >=16" } }, "node_modules/debug": { @@ -101,21 +51,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, - "node_modules/node-addon-api": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", - "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==" - }, - "node_modules/node-gyp-build": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.3.0.tgz", - "integrity": "sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==", - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" - } - }, "node_modules/typescript": { "version": "4.5.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", @@ -131,40 +66,10 @@ } }, "dependencies": { - "@serialport/bindings-cpp": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/@serialport/bindings-cpp/-/bindings-cpp-10.4.0.tgz", - "integrity": "sha512-vV5fadfzovV2iZ9xWjlQOdjEL0U0y5ewjdplx1DG5ox+/Q2Mmi8WEbcoKww6AgouU5qLirQGfYDmcWPlcGMdMw==", - "requires": { - "@serialport/binding-abstract": "10.0.1", - "@serialport/parser-readline": "10.0.1", - "debug": "^4.3.2", - "node-addon-api": "^4.3.0", - "node-gyp-build": "^4.3.0" - }, - "dependencies": { - "@serialport/binding-abstract": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@serialport/binding-abstract/-/binding-abstract-10.0.1.tgz", - "integrity": "sha512-FWD/uNrz8V3kaTILQTK05Z1LB/LZin8XZelmX/wd1NNlRFAj6V64MIESWhwUy3iPnL1QriFR1k7URHHx3RRgfg==", - "requires": { - "debug": "^4.3.2" - } - } - } - }, - "@serialport/parser-delimiter": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-10.0.1.tgz", - "integrity": "sha512-B0c6dm9UCpRU/LhkvRFL3OSbs69VqWU7mjW7tM109JDNS+vw8uJPumXz8Giub6D0xl90J7euH6tBTqERk7048Q==" - }, - "@serialport/parser-readline": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-10.0.1.tgz", - "integrity": "sha512-jdKPNka/Nn17k89T5UIyis39EaZHQCmq+83s0icBt2iPBlX8+BrJAUBe8myFpuT22qskTVNzFoTMPOp8pjK/yw==", - "requires": { - "@serialport/parser-delimiter": "10.0.1" - } + "@serialport/bindings-interface": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@serialport/bindings-interface/-/bindings-interface-1.2.0.tgz", + "integrity": "sha512-TLg6z0wyEdfVBIGmRwv0oru7Ijdc3VhH5SDv+q7UXuXvNh+mEpsFedHG205GSGVXbnF1xlCN5cBSMloI74N/gQ==" }, "debug": { "version": "4.3.3", @@ -179,16 +84,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, - "node-addon-api": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", - "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==" - }, - "node-gyp-build": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.3.0.tgz", - "integrity": "sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==" - }, "typescript": { "version": "4.5.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", diff --git a/packages/binding-mock/package.json b/packages/binding-mock/package.json index 0eb35255d..727ff7fa9 100644 --- a/packages/binding-mock/package.json +++ b/packages/binding-mock/package.json @@ -8,7 +8,7 @@ "debug" ], "dependencies": { - "@serialport/bindings-cpp": "^10.4.0", + "@serialport/bindings-interface": "1.2.0", "debug": "^4.3.2" }, "devDependencies": { @@ -22,7 +22,7 @@ }, "license": "MIT", "scripts": { - "build": "rm -rf dist && tsc -p tsconfig-build.json" + "build": "tsc --build tsconfig-build.json" }, "repository": { "type": "git", diff --git a/packages/binding-mock/tsconfig-build.json b/packages/binding-mock/tsconfig-build.json index fa72c2dd0..5442270f0 100644 --- a/packages/binding-mock/tsconfig-build.json +++ b/packages/binding-mock/tsconfig-build.json @@ -1,10 +1,12 @@ { "extends": "../../tsconfig-build.json", "compilerOptions": { + "rootDir": "lib", "outDir": "dist", }, "exclude": [ "node_modules", "**/*.test.ts", + "dist", ] } diff --git a/packages/list/.npmignore b/packages/list/.npmignore index a1623b1ea..df4db6a44 100644 --- a/packages/list/.npmignore +++ b/packages/list/.npmignore @@ -1,3 +1,7 @@ .DS_Store *.test.js CHANGELOG.md +lib +tsconfig.json +tsconfig-build.json +tsconfig-build.tsbuildinfo diff --git a/packages/list/lib/index.js b/packages/list/lib/index.ts similarity index 74% rename from packages/list/lib/index.js rename to packages/list/lib/index.ts index f98469b6a..422a1fefd 100644 --- a/packages/list/lib/index.js +++ b/packages/list/lib/index.ts @@ -1,20 +1,21 @@ #!/usr/bin/env node -const { autoDetect } = require('@serialport/bindings-cpp') +import { autoDetect, PortInfo } from '@serialport/bindings-cpp' +import { program, Option } from 'commander' + const { version } = require('../package.json') -const { program, Option } = require('commander') const formatOption = new Option('-f, --format ', 'Format the output').choices(['text', 'json', 'jsonline', 'jsonl']).default('text') program.version(version).description('List available serial ports').addOption(formatOption).parse(process.argv) -function jsonl(ports) { +function jsonl(ports: PortInfo[]) { ports.forEach(port => { console.log(JSON.stringify(port)) }) } -const formatters = { +const formatters: Record void)> = { text(ports) { ports.forEach(port => { console.log(`${port.path}\t${port.pnpId || ''}\t${port.manufacturer || ''}`) @@ -27,7 +28,9 @@ const formatters = { jsonline: jsonl, } -const args = program.opts() +const args = program.opts<{ + format: 'text' | 'json' | 'jsonline' | 'jsonl' +}>() autoDetect() .list() diff --git a/packages/list/package-lock.json b/packages/list/package-lock.json index db9969fe8..7798579f2 100644 --- a/packages/list/package-lock.json +++ b/packages/list/package-lock.json @@ -9,25 +9,14 @@ "version": "10.1.0", "license": "MIT", "dependencies": { - "@serialport/bindings-cpp": "10.4.0", - "commander": "^7.1.0" + "@serialport/bindings-cpp": "10.6.1", + "commander": "^9.0.0" }, "bin": { - "serialport-list": "lib/index.js" + "serialport-list": "dist/index.js" }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://opencollective.com/serialport/donate" - } - }, - "node_modules/@serialport/binding-abstract": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@serialport/binding-abstract/-/binding-abstract-10.0.1.tgz", - "integrity": "sha512-FWD/uNrz8V3kaTILQTK05Z1LB/LZin8XZelmX/wd1NNlRFAj6V64MIESWhwUy3iPnL1QriFR1k7URHHx3RRgfg==", - "dependencies": { - "debug": "^4.3.2" + "devDependencies": { + "typescript": "^4.5.5" }, "engines": { "node": ">=12.0.0" @@ -37,12 +26,12 @@ } }, "node_modules/@serialport/bindings-cpp": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/@serialport/bindings-cpp/-/bindings-cpp-10.4.0.tgz", - "integrity": "sha512-vV5fadfzovV2iZ9xWjlQOdjEL0U0y5ewjdplx1DG5ox+/Q2Mmi8WEbcoKww6AgouU5qLirQGfYDmcWPlcGMdMw==", + "version": "10.6.1", + "resolved": "https://registry.npmjs.org/@serialport/bindings-cpp/-/bindings-cpp-10.6.1.tgz", + "integrity": "sha512-wlgiCoImyA0siK0Z1ziXFDv6Ua3Tko9vMyNPAMW/Ywk18vKVKPYvvGlYxhbdK9LGzGdqVXGWmEW15R/S+anzQQ==", "hasInstallScript": true, "dependencies": { - "@serialport/binding-abstract": "10.0.1", + "@serialport/bindings-interface": "1.2.0", "@serialport/parser-readline": "10.0.1", "debug": "^4.3.2", "node-addon-api": "^4.3.0", @@ -55,6 +44,14 @@ "url": "https://opencollective.com/serialport/donate" } }, + "node_modules/@serialport/bindings-interface": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@serialport/bindings-interface/-/bindings-interface-1.2.0.tgz", + "integrity": "sha512-TLg6z0wyEdfVBIGmRwv0oru7Ijdc3VhH5SDv+q7UXuXvNh+mEpsFedHG205GSGVXbnF1xlCN5cBSMloI74N/gQ==", + "engines": { + "node": "^12.22 || ^14.13 || >=16" + } + }, "node_modules/@serialport/parser-delimiter": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-10.0.1.tgz", @@ -81,11 +78,11 @@ } }, "node_modules/commander": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.1.0.tgz", - "integrity": "sha512-pRxBna3MJe6HKnBGsDyMv8ETbptw3axEdYHoqNh7gu5oDcew8fs0xnivZGm06Ogk8zGAJ9VX+OPEr2GXEQK4dg==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.0.0.tgz", + "integrity": "sha512-JJfP2saEKbQqvW+FI93OYUB4ByV5cizMpFMiiJI8xDbBvQvSkIk0VvQdn1CZ8mqAO8Loq2h0gYTYtDFUZUeERw==", "engines": { - "node": ">= 10" + "node": "^12.20.0 || >=14" } }, "node_modules/debug": { @@ -123,29 +120,39 @@ "node-gyp-build-optional": "optional.js", "node-gyp-build-test": "build-test.js" } + }, + "node_modules/typescript": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", + "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } } }, "dependencies": { - "@serialport/binding-abstract": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@serialport/binding-abstract/-/binding-abstract-10.0.1.tgz", - "integrity": "sha512-FWD/uNrz8V3kaTILQTK05Z1LB/LZin8XZelmX/wd1NNlRFAj6V64MIESWhwUy3iPnL1QriFR1k7URHHx3RRgfg==", - "requires": { - "debug": "^4.3.2" - } - }, "@serialport/bindings-cpp": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/@serialport/bindings-cpp/-/bindings-cpp-10.4.0.tgz", - "integrity": "sha512-vV5fadfzovV2iZ9xWjlQOdjEL0U0y5ewjdplx1DG5ox+/Q2Mmi8WEbcoKww6AgouU5qLirQGfYDmcWPlcGMdMw==", + "version": "10.6.1", + "resolved": "https://registry.npmjs.org/@serialport/bindings-cpp/-/bindings-cpp-10.6.1.tgz", + "integrity": "sha512-wlgiCoImyA0siK0Z1ziXFDv6Ua3Tko9vMyNPAMW/Ywk18vKVKPYvvGlYxhbdK9LGzGdqVXGWmEW15R/S+anzQQ==", "requires": { - "@serialport/binding-abstract": "10.0.1", + "@serialport/bindings-interface": "1.2.0", "@serialport/parser-readline": "10.0.1", "debug": "^4.3.2", "node-addon-api": "^4.3.0", "node-gyp-build": "^4.3.0" } }, + "@serialport/bindings-interface": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@serialport/bindings-interface/-/bindings-interface-1.2.0.tgz", + "integrity": "sha512-TLg6z0wyEdfVBIGmRwv0oru7Ijdc3VhH5SDv+q7UXuXvNh+mEpsFedHG205GSGVXbnF1xlCN5cBSMloI74N/gQ==" + }, "@serialport/parser-delimiter": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-10.0.1.tgz", @@ -160,9 +167,9 @@ } }, "commander": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.1.0.tgz", - "integrity": "sha512-pRxBna3MJe6HKnBGsDyMv8ETbptw3axEdYHoqNh7gu5oDcew8fs0xnivZGm06Ogk8zGAJ9VX+OPEr2GXEQK4dg==" + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.0.0.tgz", + "integrity": "sha512-JJfP2saEKbQqvW+FI93OYUB4ByV5cizMpFMiiJI8xDbBvQvSkIk0VvQdn1CZ8mqAO8Loq2h0gYTYtDFUZUeERw==" }, "debug": { "version": "4.3.3", @@ -186,6 +193,12 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.3.0.tgz", "integrity": "sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==" + }, + "typescript": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", + "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", + "dev": true } } } diff --git a/packages/list/package.json b/packages/list/package.json index b8c66278e..1b21cdea4 100644 --- a/packages/list/package.json +++ b/packages/list/package.json @@ -1,13 +1,17 @@ { "name": "@serialport/list", "version": "10.1.0", - "main": "lib", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", "bin": { - "serialport-list": "./lib/index.js" + "serialport-list": "./dist/index.js" + }, + "scripts": { + "build": "tsc --build tsconfig-build.json" }, "dependencies": { - "@serialport/bindings-cpp": "10.4.0", - "commander": "^7.1.0" + "@serialport/bindings-cpp": "10.6.1", + "commander": "^9.0.0" }, "engines": { "node": ">=12.0.0" @@ -20,5 +24,8 @@ "type": "git", "url": "git://github.com/serialport/node-serialport.git" }, - "funding": "https://opencollective.com/serialport/donate" + "funding": "https://opencollective.com/serialport/donate", + "devDependencies": { + "typescript": "^4.5.5" + } } diff --git a/packages/list/tsconfig-build.json b/packages/list/tsconfig-build.json new file mode 100644 index 000000000..5442270f0 --- /dev/null +++ b/packages/list/tsconfig-build.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig-build.json", + "compilerOptions": { + "rootDir": "lib", + "outDir": "dist", + }, + "exclude": [ + "node_modules", + "**/*.test.ts", + "dist", + ] +} diff --git a/packages/list/tsconfig.json b/packages/list/tsconfig.json new file mode 100644 index 000000000..4082f16a5 --- /dev/null +++ b/packages/list/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tsconfig.json" +} diff --git a/packages/parser-byte-length/.npmignore b/packages/parser-byte-length/.npmignore index a1623b1ea..df4db6a44 100644 --- a/packages/parser-byte-length/.npmignore +++ b/packages/parser-byte-length/.npmignore @@ -1,3 +1,7 @@ .DS_Store *.test.js CHANGELOG.md +lib +tsconfig.json +tsconfig-build.json +tsconfig-build.tsbuildinfo diff --git a/packages/parser-byte-length/lib/index.test.js b/packages/parser-byte-length/lib/index.test.ts similarity index 86% rename from packages/parser-byte-length/lib/index.test.js rename to packages/parser-byte-length/lib/index.test.ts index 7a5204631..f47fd6fdc 100644 --- a/packages/parser-byte-length/lib/index.test.js +++ b/packages/parser-byte-length/lib/index.test.ts @@ -1,7 +1,8 @@ -/* eslint-disable no-new */ +/* eslint-disable @typescript-eslint/no-explicit-any */ -const sinon = require('sinon') -const ByteLengthParser = require('../') +import sinon from 'sinon' +import { ByteLengthParser } from './' +import { assert } from '../../../test/assert' describe('ByteLengthParser', () => { it('emits data events every 8 bytes', () => { @@ -18,10 +19,10 @@ describe('ByteLengthParser', () => { it('throws when not provided with a length', () => { assert.throws(() => { - new ByteLengthParser() + new ByteLengthParser(undefined as any) }) assert.throws(() => { - new ByteLengthParser({}) + new ByteLengthParser({} as any) }) }) @@ -37,7 +38,7 @@ describe('ByteLengthParser', () => { assert.throws(() => { new ByteLengthParser({ length: 'foop', - }) + } as any) }) }) diff --git a/packages/parser-byte-length/lib/index.js b/packages/parser-byte-length/lib/index.ts similarity index 64% rename from packages/parser-byte-length/lib/index.js rename to packages/parser-byte-length/lib/index.ts index a54e1929d..e0ca5ec9d 100644 --- a/packages/parser-byte-length/lib/index.js +++ b/packages/parser-byte-length/lib/index.ts @@ -1,11 +1,14 @@ -const { Transform } = require('stream') +import { Transform, TransformCallback, TransformOptions } from 'stream' + +export interface ByteLengthOptions extends TransformOptions { + /** the number of bytes on each data event */ + length: number +} /** * Emit data every number of bytes - * @extends Transform - * @param {Object} options parser options object - * @param {Number} options.length the number of bytes on each data event - * @summary A transform stream that emits data as a buffer after a specific number of bytes are received. Runs in O(n) time. + * + * A transform stream that emits data as a buffer after a specific number of bytes are received. Runs in O(n) time. * @example const SerialPort = require('serialport') const ByteLength = require('@serialport/parser-byte-length') @@ -13,8 +16,11 @@ const port = new SerialPort('/dev/tty-usbserial1') const parser = port.pipe(new ByteLength({length: 8})) parser.on('data', console.log) // will have 8 bytes per data event */ -class ByteLengthParser extends Transform { - constructor(options = {}) { +export class ByteLengthParser extends Transform { + length: number + private position: number + private buffer: Buffer + constructor(options: ByteLengthOptions) { super(options) if (typeof options.length !== 'number') { @@ -30,7 +36,7 @@ class ByteLengthParser extends Transform { this.buffer = Buffer.alloc(this.length) } - _transform(chunk, encoding, cb) { + _transform(chunk: Buffer, _encoding: BufferEncoding, cb: TransformCallback) { let cursor = 0 while (cursor < chunk.length) { this.buffer[this.position] = chunk[cursor] @@ -45,11 +51,9 @@ class ByteLengthParser extends Transform { cb() } - _flush(cb) { + _flush(cb: TransformCallback) { this.push(this.buffer.slice(0, this.position)) this.buffer = Buffer.alloc(this.length) cb() } } - -module.exports = ByteLengthParser diff --git a/packages/parser-byte-length/package-lock.json b/packages/parser-byte-length/package-lock.json index 6746ad048..ff725516b 100644 --- a/packages/parser-byte-length/package-lock.json +++ b/packages/parser-byte-length/package-lock.json @@ -1,5 +1,43 @@ { "name": "@serialport/parser-byte-length", "version": "10.0.1", - "lockfileVersion": 1 + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "@serialport/parser-byte-length", + "version": "10.0.1", + "license": "MIT", + "devDependencies": { + "typescript": "^4.5.5" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/typescript": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", + "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + } + }, + "dependencies": { + "typescript": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", + "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", + "dev": true + } + } } diff --git a/packages/parser-byte-length/package.json b/packages/parser-byte-length/package.json index d74fb67e7..954c507c2 100644 --- a/packages/parser-byte-length/package.json +++ b/packages/parser-byte-length/package.json @@ -1,7 +1,8 @@ { "name": "@serialport/parser-byte-length", "version": "10.0.1", - "main": "lib", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", "engines": { "node": ">=12.0.0" }, @@ -9,9 +10,15 @@ "access": "public" }, "license": "MIT", + "scripts": { + "build": "tsc --build tsconfig-build.json" + }, "repository": { "type": "git", "url": "git://github.com/serialport/node-serialport.git" }, - "funding": "https://opencollective.com/serialport/donate" + "funding": "https://opencollective.com/serialport/donate", + "devDependencies": { + "typescript": "^4.5.5" + } } diff --git a/packages/parser-byte-length/tsconfig-build.json b/packages/parser-byte-length/tsconfig-build.json new file mode 100644 index 000000000..5442270f0 --- /dev/null +++ b/packages/parser-byte-length/tsconfig-build.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig-build.json", + "compilerOptions": { + "rootDir": "lib", + "outDir": "dist", + }, + "exclude": [ + "node_modules", + "**/*.test.ts", + "dist", + ] +} diff --git a/packages/parser-byte-length/tsconfig.json b/packages/parser-byte-length/tsconfig.json new file mode 100644 index 000000000..4082f16a5 --- /dev/null +++ b/packages/parser-byte-length/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tsconfig.json" +} diff --git a/packages/parser-cctalk/.npmignore b/packages/parser-cctalk/.npmignore index a1623b1ea..df4db6a44 100644 --- a/packages/parser-cctalk/.npmignore +++ b/packages/parser-cctalk/.npmignore @@ -1,3 +1,7 @@ .DS_Store *.test.js CHANGELOG.md +lib +tsconfig.json +tsconfig-build.json +tsconfig-build.tsbuildinfo diff --git a/packages/parser-cctalk/lib/index.test.js b/packages/parser-cctalk/lib/index.test.ts similarity index 96% rename from packages/parser-cctalk/lib/index.test.js rename to packages/parser-cctalk/lib/index.test.ts index cd456ffbf..f89368002 100644 --- a/packages/parser-cctalk/lib/index.test.js +++ b/packages/parser-cctalk/lib/index.test.ts @@ -1,5 +1,6 @@ -const sinon = require('sinon') -const CCTalkParser = require('../') +import sinon from 'sinon' +import { CCTalkParser } from './' +import { assert } from '../../../test/assert' describe('CCTalkParser', () => { it('constructs', () => { diff --git a/packages/parser-cctalk/lib/index.js b/packages/parser-cctalk/lib/index.ts similarity index 76% rename from packages/parser-cctalk/lib/index.js rename to packages/parser-cctalk/lib/index.ts index bfea1eba3..a8b67541b 100644 --- a/packages/parser-cctalk/lib/index.js +++ b/packages/parser-cctalk/lib/index.ts @@ -1,17 +1,23 @@ -const { Transform } = require('stream') +import { Transform, TransformCallback } from 'stream' /** * Parse the CCTalk protocol * @extends Transform - * @summary A transform stream that emits CCTalk packets as they are received. + * + * A transform stream that emits CCTalk packets as they are received. * @example +import { CCTalkParser } from '@serialport/parser-cctalk' const SerialPort = require('serialport') -const CCTalk = require('@serialport/parser-cctalk') const port = new SerialPort('/dev/ttyUSB0') const parser = port.pipe(new CCtalk()) parser.on('data', console.log) */ -class CCTalkParser extends Transform { +export class CCTalkParser extends Transform { + array: number[] + cursor: number + lastByteFetchTime: number + maxDelayBetweenBytesMs: number + constructor(maxDelayBetweenBytesMs = 50) { super() this.array = [] @@ -19,7 +25,8 @@ class CCTalkParser extends Transform { this.lastByteFetchTime = 0 this.maxDelayBetweenBytesMs = maxDelayBetweenBytesMs } - _transform(buffer, _, cb) { + + _transform(buffer: Buffer, encoding: BufferEncoding, cb: TransformCallback) { if (this.maxDelayBetweenBytesMs > 0) { const now = Date.now() if (now - this.lastByteFetchTime > this.maxDelayBetweenBytesMs) { @@ -48,5 +55,3 @@ class CCTalkParser extends Transform { cb() } } - -module.exports = CCTalkParser diff --git a/packages/parser-cctalk/package-lock.json b/packages/parser-cctalk/package-lock.json index 5c21b9d10..5709184db 100644 --- a/packages/parser-cctalk/package-lock.json +++ b/packages/parser-cctalk/package-lock.json @@ -1,5 +1,43 @@ { "name": "@serialport/parser-cctalk", "version": "10.0.1", - "lockfileVersion": 1 + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "@serialport/parser-cctalk", + "version": "10.0.1", + "license": "MIT", + "devDependencies": { + "typescript": "^4.5.5" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/typescript": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", + "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + } + }, + "dependencies": { + "typescript": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", + "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", + "dev": true + } + } } diff --git a/packages/parser-cctalk/package.json b/packages/parser-cctalk/package.json index 786d363e9..123eb1f6e 100644 --- a/packages/parser-cctalk/package.json +++ b/packages/parser-cctalk/package.json @@ -1,7 +1,11 @@ { "name": "@serialport/parser-cctalk", "version": "10.0.1", - "main": "lib", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "scripts": { + "build": "tsc --build tsconfig-build.json" + }, "engines": { "node": ">=12.0.0" }, @@ -13,5 +17,8 @@ "type": "git", "url": "git://github.com/serialport/node-serialport.git" }, - "funding": "https://opencollective.com/serialport/donate" + "funding": "https://opencollective.com/serialport/donate", + "devDependencies": { + "typescript": "^4.5.5" + } } diff --git a/packages/parser-cctalk/tsconfig-build.json b/packages/parser-cctalk/tsconfig-build.json new file mode 100644 index 000000000..5442270f0 --- /dev/null +++ b/packages/parser-cctalk/tsconfig-build.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig-build.json", + "compilerOptions": { + "rootDir": "lib", + "outDir": "dist", + }, + "exclude": [ + "node_modules", + "**/*.test.ts", + "dist", + ] +} diff --git a/packages/parser-cctalk/tsconfig.json b/packages/parser-cctalk/tsconfig.json new file mode 100644 index 000000000..6f83eb665 --- /dev/null +++ b/packages/parser-cctalk/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tsconfig.json", +} diff --git a/packages/parser-delimiter/.npmignore b/packages/parser-delimiter/.npmignore index a1623b1ea..df4db6a44 100644 --- a/packages/parser-delimiter/.npmignore +++ b/packages/parser-delimiter/.npmignore @@ -1,3 +1,7 @@ .DS_Store *.test.js CHANGELOG.md +lib +tsconfig.json +tsconfig-build.json +tsconfig-build.tsbuildinfo diff --git a/packages/parser-delimiter/lib/index.js b/packages/parser-delimiter/lib/index.js deleted file mode 100644 index 1d101485c..000000000 --- a/packages/parser-delimiter/lib/index.js +++ /dev/null @@ -1,49 +0,0 @@ -const { Transform } = require('stream') - -/** - * A transform stream that emits data each time a byte sequence is received. - * @extends Transform - * @summary To use the `Delimiter` parser, provide a delimiter as a string, buffer, or array of bytes. Runs in O(n) time. - * @example -const SerialPort = require('serialport') -const Delimiter = require('@serialport/parser-delimiter') -const port = new SerialPort('/dev/tty-usbserial1') -const parser = port.pipe(new Delimiter({ delimiter: '\n' })) -parser.on('data', console.log) - */ -class DelimiterParser extends Transform { - constructor(options = {}) { - super(options) - - if (options.delimiter === undefined) { - throw new TypeError('"delimiter" is not a bufferable object') - } - - if (options.delimiter.length === 0) { - throw new TypeError('"delimiter" has a 0 or undefined length') - } - - this.includeDelimiter = options.includeDelimiter !== undefined ? options.includeDelimiter : false - this.delimiter = Buffer.from(options.delimiter) - this.buffer = Buffer.alloc(0) - } - - _transform(chunk, encoding, cb) { - let data = Buffer.concat([this.buffer, chunk]) - let position - while ((position = data.indexOf(this.delimiter)) !== -1) { - this.push(data.slice(0, position + (this.includeDelimiter ? this.delimiter.length : 0))) - data = data.slice(position + this.delimiter.length) - } - this.buffer = data - cb() - } - - _flush(cb) { - this.push(this.buffer) - this.buffer = Buffer.alloc(0) - cb() - } -} - -module.exports = DelimiterParser diff --git a/packages/parser-delimiter/lib/index.test.js b/packages/parser-delimiter/lib/index.test.ts similarity index 95% rename from packages/parser-delimiter/lib/index.test.js rename to packages/parser-delimiter/lib/index.test.ts index 90e3a0d47..3b022a9ec 100644 --- a/packages/parser-delimiter/lib/index.test.js +++ b/packages/parser-delimiter/lib/index.test.ts @@ -1,7 +1,7 @@ -/* eslint-disable no-new */ - -const sinon = require('sinon') -const DelimiterParser = require('../') +/* eslint-disable @typescript-eslint/no-explicit-any */ +import sinon from 'sinon' +import { DelimiterParser } from './' +import { assert } from '../../../test/assert' describe('DelimiterParser', () => { it('transforms data to strings split on a delimiter', () => { @@ -51,10 +51,10 @@ describe('DelimiterParser', () => { it('throws when not provided with a delimiter', () => { assert.throws(() => { - new DelimiterParser({}) + new DelimiterParser({} as any) }) assert.throws(() => { - new DelimiterParser() + new (DelimiterParser as any)() }) }) diff --git a/packages/parser-delimiter/lib/index.ts b/packages/parser-delimiter/lib/index.ts new file mode 100644 index 000000000..76a0bcc06 --- /dev/null +++ b/packages/parser-delimiter/lib/index.ts @@ -0,0 +1,57 @@ +import { Transform, TransformCallback, TransformOptions } from 'stream' + +export interface DelimiterOptions extends TransformOptions { + includeDelimiter?: boolean + delimiter: string | Buffer | number[] +} + +/** + * A transform stream that emits data each time a byte sequence is received. + * @extends Transform + * + * To use the `Delimiter` parser, provide a delimiter as a string, buffer, or array of bytes. Runs in O(n) time. + * @example +const SerialPort = require('serialport') +const {DelimiterParser} = require('@serialport/parser-delimiter') +const port = new SerialPort('/dev/tty-usbserial1') +const parser = port.pipe(new DelimiterParser({ delimiter: '\n' })) +parser.on('data', console.log) + */ +export class DelimiterParser extends Transform { + includeDelimiter: boolean + delimiter: Buffer + buffer: Buffer + + constructor({ delimiter, includeDelimiter = false, ...options }: DelimiterOptions) { + super(options) + + if (delimiter === undefined) { + throw new TypeError('"delimiter" is not a bufferable object') + } + + if (delimiter.length === 0) { + throw new TypeError('"delimiter" has a 0 or undefined length') + } + + this.includeDelimiter = includeDelimiter + this.delimiter = Buffer.from(delimiter) + this.buffer = Buffer.alloc(0) + } + + _transform(chunk: Buffer, encoding: BufferEncoding, cb: TransformCallback) { + let data = Buffer.concat([this.buffer, chunk]) + let position + while ((position = data.indexOf(this.delimiter)) !== -1) { + this.push(data.slice(0, position + (this.includeDelimiter ? this.delimiter.length : 0))) + data = data.slice(position + this.delimiter.length) + } + this.buffer = data + cb() + } + + _flush(cb: TransformCallback) { + this.push(this.buffer) + this.buffer = Buffer.alloc(0) + cb() + } +} diff --git a/packages/parser-delimiter/package-lock.json b/packages/parser-delimiter/package-lock.json index 5c3a67c23..7b91e8c03 100644 --- a/packages/parser-delimiter/package-lock.json +++ b/packages/parser-delimiter/package-lock.json @@ -1,5 +1,43 @@ { "name": "@serialport/parser-delimiter", "version": "10.0.1", - "lockfileVersion": 1 + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "@serialport/parser-delimiter", + "version": "10.0.1", + "license": "MIT", + "devDependencies": { + "typescript": "^4.5.5" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/typescript": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", + "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + } + }, + "dependencies": { + "typescript": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", + "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", + "dev": true + } + } } diff --git a/packages/parser-delimiter/package.json b/packages/parser-delimiter/package.json index 2c5032160..5446913ee 100644 --- a/packages/parser-delimiter/package.json +++ b/packages/parser-delimiter/package.json @@ -1,6 +1,7 @@ { "name": "@serialport/parser-delimiter", - "main": "lib", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", "version": "10.0.1", "engines": { "node": ">=12.0.0" @@ -9,9 +10,15 @@ "access": "public" }, "license": "MIT", + "scripts": { + "build": "tsc --build tsconfig-build.json" + }, "repository": { "type": "git", "url": "git://github.com/serialport/node-serialport.git" }, - "funding": "https://opencollective.com/serialport/donate" + "funding": "https://opencollective.com/serialport/donate", + "devDependencies": { + "typescript": "^4.5.5" + } } diff --git a/packages/parser-delimiter/tsconfig-build.json b/packages/parser-delimiter/tsconfig-build.json new file mode 100644 index 000000000..dc913d44f --- /dev/null +++ b/packages/parser-delimiter/tsconfig-build.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig-build.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "lib", + }, + "exclude": [ + "node_modules", + "**/*.test.ts", + "dist", + ] +} diff --git a/packages/parser-delimiter/tsconfig.json b/packages/parser-delimiter/tsconfig.json new file mode 100644 index 000000000..6f83eb665 --- /dev/null +++ b/packages/parser-delimiter/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tsconfig.json", +} diff --git a/packages/parser-inter-byte-timeout/.npmignore b/packages/parser-inter-byte-timeout/.npmignore index a1623b1ea..df4db6a44 100644 --- a/packages/parser-inter-byte-timeout/.npmignore +++ b/packages/parser-inter-byte-timeout/.npmignore @@ -1,3 +1,7 @@ .DS_Store *.test.js CHANGELOG.md +lib +tsconfig.json +tsconfig-build.json +tsconfig-build.tsbuildinfo diff --git a/packages/parser-inter-byte-timeout/lib/index.js b/packages/parser-inter-byte-timeout/lib/index.js deleted file mode 100644 index 0183fe8e5..000000000 --- a/packages/parser-inter-byte-timeout/lib/index.js +++ /dev/null @@ -1,71 +0,0 @@ -const { Transform } = require('stream') - -/** - * Emits data if there is a pause between packets for the specified amount of time. - * @extends Transform - * @param {Object} options parser options object - * @param {Number} options.interval the period of silence in milliseconds after which data is emited - * @param {Number} options.maxBufferSize the maximum number of bytes after which data will be emited. Defaults to 65536. - * @summary A transform stream that emits data as a buffer after not receiving any bytes for the specified amount of time. - * @example -const SerialPort = require('serialport') -const InterByteTimeout = require('@serialport/parser-inter-byte-timeout') -const port = new SerialPort('/dev/tty-usbserial1') -const parser = port.pipe(new InterByteTimeout({interval: 30})) -parser.on('data', console.log) // will emit data if there is a pause between packets greater than 30ms - */ - -class InterByteTimeoutParser extends Transform { - constructor(options) { - super() - options = { maxBufferSize: 65536, ...options } - if (!options.interval) { - throw new TypeError('"interval" is required') - } - - if (typeof options.interval !== 'number' || Number.isNaN(options.interval)) { - throw new TypeError('"interval" is not a number') - } - - if (options.interval < 1) { - throw new TypeError('"interval" is not greater than 0') - } - - if (typeof options.maxBufferSize !== 'number' || Number.isNaN(options.maxBufferSize)) { - throw new TypeError('"maxBufferSize" is not a number') - } - - if (options.maxBufferSize < 1) { - throw new TypeError('"maxBufferSize" is not greater than 0') - } - - this.maxBufferSize = options.maxBufferSize - this.currentPacket = [] - this.interval = options.interval - this.intervalID = -1 - } - _transform(chunk, encoding, cb) { - clearTimeout(this.intervalID) - for (let offset = 0; offset < chunk.length; offset++) { - this.currentPacket.push(chunk[offset]) - if (this.currentPacket.length >= this.maxBufferSize) { - this.emitPacket() - } - } - this.intervalID = setTimeout(this.emitPacket.bind(this), this.interval) - cb() - } - emitPacket() { - clearTimeout(this.intervalID) - if (this.currentPacket.length > 0) { - this.push(Buffer.from(this.currentPacket)) - } - this.currentPacket = [] - } - _flush(cb) { - this.emitPacket() - cb() - } -} - -module.exports = InterByteTimeoutParser diff --git a/packages/parser-inter-byte-timeout/lib/index.test.js b/packages/parser-inter-byte-timeout/lib/index.test.ts similarity index 87% rename from packages/parser-inter-byte-timeout/lib/index.test.js rename to packages/parser-inter-byte-timeout/lib/index.test.ts index 6d0b40f81..b4fc46715 100644 --- a/packages/parser-inter-byte-timeout/lib/index.test.js +++ b/packages/parser-inter-byte-timeout/lib/index.test.ts @@ -1,9 +1,9 @@ -/* eslint-disable no-new */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import sinon from 'sinon' +import { InterByteTimeoutParser } from './' +import { assert } from '../../../test/assert' -const sinon = require('sinon') -const InterByteTimeoutParser = require('../') - -function wait(interval) { +function wait(interval: number) { return new Promise((resolve, reject) => { setTimeout(resolve, interval) if (interval < 1) reject() @@ -34,10 +34,10 @@ describe('InterByteTimeoutParser', () => { new InterByteTimeoutParser({ interval: NaN }) }) assert.throws(() => { - new InterByteTimeoutParser({ interval: 'hello' }) + new InterByteTimeoutParser({ interval: 'hello' } as any) }) assert.throws(() => { - new InterByteTimeoutParser() + new (InterByteTimeoutParser as any)() }) }) @@ -49,7 +49,7 @@ describe('InterByteTimeoutParser', () => { new InterByteTimeoutParser({ maxBufferSize: NaN, interval: 15 }) }) assert.throws(() => { - new InterByteTimeoutParser({ maxBufferSize: 'hello', interval: 15 }) + new InterByteTimeoutParser({ maxBufferSize: 'hello', interval: 15 } as any) }) }) @@ -71,6 +71,7 @@ describe('InterByteTimeoutParser', () => { parser.end() assert(spy.calledOnce, 'expecting 1 data event') }) + it('handles not having any buffered data when stream ends', () => { const spy = sinon.spy() const parser = new InterByteTimeoutParser({ interval: 15 }) diff --git a/packages/parser-inter-byte-timeout/lib/index.ts b/packages/parser-inter-byte-timeout/lib/index.ts new file mode 100644 index 000000000..44e70d05b --- /dev/null +++ b/packages/parser-inter-byte-timeout/lib/index.ts @@ -0,0 +1,83 @@ +import { Transform, TransformCallback, TransformOptions } from 'stream' + +export interface InterByteTimeoutOptions extends TransformOptions { + /** the period of silence in milliseconds after which data is emitted */ + interval: number + /** the maximum number of bytes after which data will be emitted. Defaults to 65536 */ + maxBufferSize?: number +} + +/** + * Emits data if there is a pause between packets for the specified amount of time. + * + * A transform stream that emits data as a buffer after not receiving any bytes for the specified amount of time. + * @example +const SerialPort = require('serialport') +const { InterByteTimeoutParser } = require('@serialport/parser-inter-byte-timeout') +const port = new SerialPort('/dev/tty-usbserial1') +const parser = port.pipe(new InterByteTimeoutParser({interval: 30})) +parser.on('data', console.log) // will emit data if there is a pause between packets greater than 30ms + */ + +export class InterByteTimeoutParser extends Transform { + maxBufferSize: number + currentPacket: number[] + interval: number + intervalID: NodeJS.Timeout | undefined + + constructor({ maxBufferSize = 65536, interval, ...transformOptions }: InterByteTimeoutOptions) { + super(transformOptions) + if (!interval) { + throw new TypeError('"interval" is required') + } + + if (typeof interval !== 'number' || Number.isNaN(interval)) { + throw new TypeError('"interval" is not a number') + } + + if (interval < 1) { + throw new TypeError('"interval" is not greater than 0') + } + + if (typeof maxBufferSize !== 'number' || Number.isNaN(maxBufferSize)) { + throw new TypeError('"maxBufferSize" is not a number') + } + + if (maxBufferSize < 1) { + throw new TypeError('"maxBufferSize" is not greater than 0') + } + + this.maxBufferSize = maxBufferSize + this.currentPacket = [] + this.interval = interval + } + + _transform(chunk: Buffer, encoding: BufferEncoding, cb: TransformCallback) { + if (this.intervalID) { + clearTimeout(this.intervalID) + } + for (let offset = 0; offset < chunk.length; offset++) { + this.currentPacket.push(chunk[offset]) + if (this.currentPacket.length >= this.maxBufferSize) { + this.emitPacket() + } + } + this.intervalID = setTimeout(this.emitPacket.bind(this), this.interval) + cb() + } + + emitPacket() { + if (this.intervalID) { + clearTimeout(this.intervalID) + } + if (this.currentPacket.length > 0) { + this.push(Buffer.from(this.currentPacket)) + } + this.currentPacket = [] + } + + _flush(cb: TransformCallback) { + this.emitPacket() + cb() + } +} diff --git a/packages/parser-inter-byte-timeout/package-lock.json b/packages/parser-inter-byte-timeout/package-lock.json index f7b801b95..67b011820 100644 --- a/packages/parser-inter-byte-timeout/package-lock.json +++ b/packages/parser-inter-byte-timeout/package-lock.json @@ -1,5 +1,43 @@ { "name": "@serialport/parser-inter-byte-timeout", "version": "10.0.1", - "lockfileVersion": 1 + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "@serialport/parser-inter-byte-timeout", + "version": "10.0.1", + "license": "MIT", + "devDependencies": { + "typescript": "^4.5.5" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/typescript": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", + "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + } + }, + "dependencies": { + "typescript": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", + "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", + "dev": true + } + } } diff --git a/packages/parser-inter-byte-timeout/package.json b/packages/parser-inter-byte-timeout/package.json index 65ef8f91e..94ff35d5e 100644 --- a/packages/parser-inter-byte-timeout/package.json +++ b/packages/parser-inter-byte-timeout/package.json @@ -1,7 +1,8 @@ { "name": "@serialport/parser-inter-byte-timeout", "version": "10.0.1", - "main": "lib", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", "engines": { "node": ">=12.0.0" }, @@ -9,9 +10,15 @@ "access": "public" }, "license": "MIT", + "scripts": { + "build": "tsc --build tsconfig-build.json" + }, "repository": { "type": "git", "url": "git://github.com/serialport/node-serialport.git" }, - "funding": "https://opencollective.com/serialport/donate" + "funding": "https://opencollective.com/serialport/donate", + "devDependencies": { + "typescript": "^4.5.5" + } } diff --git a/packages/parser-inter-byte-timeout/tsconfig-build.json b/packages/parser-inter-byte-timeout/tsconfig-build.json new file mode 100644 index 000000000..5442270f0 --- /dev/null +++ b/packages/parser-inter-byte-timeout/tsconfig-build.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig-build.json", + "compilerOptions": { + "rootDir": "lib", + "outDir": "dist", + }, + "exclude": [ + "node_modules", + "**/*.test.ts", + "dist", + ] +} diff --git a/packages/parser-inter-byte-timeout/tsconfig.json b/packages/parser-inter-byte-timeout/tsconfig.json new file mode 100644 index 000000000..4082f16a5 --- /dev/null +++ b/packages/parser-inter-byte-timeout/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tsconfig.json" +} diff --git a/packages/parser-packet-length/.npmignore b/packages/parser-packet-length/.npmignore index a1623b1ea..df4db6a44 100644 --- a/packages/parser-packet-length/.npmignore +++ b/packages/parser-packet-length/.npmignore @@ -1,3 +1,7 @@ .DS_Store *.test.js CHANGELOG.md +lib +tsconfig.json +tsconfig-build.json +tsconfig-build.tsbuildinfo diff --git a/packages/parser-packet-length/lib/index.test.js b/packages/parser-packet-length/lib/index.test.ts similarity index 97% rename from packages/parser-packet-length/lib/index.test.js rename to packages/parser-packet-length/lib/index.test.ts index 2897dcb79..8e3b17c9c 100644 --- a/packages/parser-packet-length/lib/index.test.js +++ b/packages/parser-packet-length/lib/index.test.ts @@ -1,7 +1,6 @@ -/* eslint-disable no-new */ - -const sinon = require('sinon') -const PacketLengthParser = require('../') +import sinon from 'sinon' +import { PacketLengthParser } from './' +import { assert } from '../../../test/assert' describe('DelimiterParser', () => { it('transforms data to packets of correct length starting with delimiter', () => { diff --git a/packages/parser-packet-length/lib/index.js b/packages/parser-packet-length/lib/index.ts similarity index 64% rename from packages/parser-packet-length/lib/index.js rename to packages/parser-packet-length/lib/index.ts index bc3d0cd56..6b1ee5db4 100644 --- a/packages/parser-packet-length/lib/index.js +++ b/packages/parser-packet-length/lib/index.ts @@ -1,4 +1,17 @@ -const { Transform } = require('stream') +import { Transform, TransformCallback, TransformOptions } from 'stream' + +export interface PacketLengthOptions extends TransformOptions { + /** defaults to 0xaa */ + delimiter?: number + /** defaults to 2 */ + packetOverhead?: number + /** defaults to 1 */ + lengthBytes?: number + /** defaults to 1 */ + lengthOffset?: number + /** max packet length defaults to 0xff */ + maxLen?: number +} /** * A transform stream that decodes packets with a delimiter and length of payload @@ -24,26 +37,28 @@ const parser = port.pipe(new PacketLengthParser({ lengthOffset: 2, })) */ -class PacketLengthParser extends Transform { - constructor(options = {}) { +export class PacketLengthParser extends Transform { + buffer: Buffer + start: boolean + opts: { delimiter: number; packetOverhead: number; lengthBytes: number; lengthOffset: number; maxLen: number } + constructor(options: PacketLengthOptions = {}) { super(options) - const opts = { - delimiter: 0xaa, - packetOverhead: 2, - lengthBytes: 1, - lengthOffset: 1, - maxLen: 0xff, + const { delimiter = 0xaa, packetOverhead = 2, lengthBytes = 1, lengthOffset = 1, maxLen = 0xff } = options - ...options, + this.opts = { + delimiter, + packetOverhead, + lengthBytes, + lengthOffset, + maxLen, } - this.opts = opts this.buffer = Buffer.alloc(0) this.start = false } - _transform(chunk, encoding, cb) { + _transform(chunk: Buffer, encoding: BufferEncoding, cb: TransformCallback) { for (let ndx = 0; ndx < chunk.length; ndx++) { const byte = chunk[ndx] @@ -69,11 +84,9 @@ class PacketLengthParser extends Transform { cb() } - _flush(cb) { + _flush(cb: TransformCallback) { this.push(this.buffer) this.buffer = Buffer.alloc(0) cb() } } - -module.exports = PacketLengthParser diff --git a/packages/parser-packet-length/package-lock.json b/packages/parser-packet-length/package-lock.json index d7b6cc6e3..ffa950231 100644 --- a/packages/parser-packet-length/package-lock.json +++ b/packages/parser-packet-length/package-lock.json @@ -1,5 +1,40 @@ { "name": "@serialport/parser-packet-length", "version": "10.0.0", - "lockfileVersion": 1 + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "@serialport/parser-packet-length", + "version": "10.0.0", + "license": "MIT", + "devDependencies": { + "typescript": "^4.5.5" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/typescript": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", + "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + } + }, + "dependencies": { + "typescript": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", + "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", + "dev": true + } + } } diff --git a/packages/parser-packet-length/package.json b/packages/parser-packet-length/package.json index 1fc6f2f3e..505a4a61b 100644 --- a/packages/parser-packet-length/package.json +++ b/packages/parser-packet-length/package.json @@ -1,6 +1,10 @@ { "name": "@serialport/parser-packet-length", - "main": "lib", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "scripts": { + "build": "tsc --build tsconfig-build.json" + }, "version": "10.0.0", "engines": { "node": ">=8.6.0" @@ -12,5 +16,8 @@ "repository": { "type": "git", "url": "git://github.com/serialport/node-serialport.git" + }, + "devDependencies": { + "typescript": "^4.5.5" } } diff --git a/packages/parser-packet-length/tsconfig-build.json b/packages/parser-packet-length/tsconfig-build.json new file mode 100644 index 000000000..5442270f0 --- /dev/null +++ b/packages/parser-packet-length/tsconfig-build.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig-build.json", + "compilerOptions": { + "rootDir": "lib", + "outDir": "dist", + }, + "exclude": [ + "node_modules", + "**/*.test.ts", + "dist", + ] +} diff --git a/packages/parser-packet-length/tsconfig.json b/packages/parser-packet-length/tsconfig.json new file mode 100644 index 000000000..4082f16a5 --- /dev/null +++ b/packages/parser-packet-length/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tsconfig.json" +} diff --git a/packages/parser-readline/.npmignore b/packages/parser-readline/.npmignore index a1623b1ea..df4db6a44 100644 --- a/packages/parser-readline/.npmignore +++ b/packages/parser-readline/.npmignore @@ -1,3 +1,7 @@ .DS_Store *.test.js CHANGELOG.md +lib +tsconfig.json +tsconfig-build.json +tsconfig-build.tsbuildinfo diff --git a/packages/parser-readline/lib/index.test.js b/packages/parser-readline/lib/index.test.ts similarity index 96% rename from packages/parser-readline/lib/index.test.js rename to packages/parser-readline/lib/index.test.ts index f03115081..baa4c3e9a 100644 --- a/packages/parser-readline/lib/index.test.js +++ b/packages/parser-readline/lib/index.test.ts @@ -1,8 +1,8 @@ /* eslint-disable no-new */ -const sinon = require('sinon') - -const ReadlineParser = require('../') +import sinon from 'sinon' +import { ReadlineParser } from './' +import { assert } from '../../../test/assert' describe('ReadlineParser', () => { it('transforms data to strings split on a delimiter', () => { diff --git a/packages/parser-readline/lib/index.js b/packages/parser-readline/lib/index.ts similarity index 50% rename from packages/parser-readline/lib/index.js rename to packages/parser-readline/lib/index.ts index 795f60843..7cb85316c 100644 --- a/packages/parser-readline/lib/index.js +++ b/packages/parser-readline/lib/index.ts @@ -1,21 +1,30 @@ -const DelimiterParser = require('@serialport/parser-delimiter') +import { DelimiterParser } from '@serialport/parser-delimiter' +import { TransformOptions } from 'stream' + +export interface ReadlineOptions extends TransformOptions { + /** defaults to false */ + includeDelimiter?: boolean + /** defaults to \n */ + delimiter?: string | Buffer | number[] + /** Defaults to utf8 */ + encoding?: BufferEncoding +} /** * A transform stream that emits data after a newline delimiter is received. * @summary To use the `Readline` parser, provide a delimiter (defaults to `\n`). Data is emitted as string controllable by the `encoding` option (defaults to `utf8`). - * @extends DelimiterParser * @example const SerialPort = require('serialport') -const Readline = require('@serialport/parser-readline') +const {ReadlineParser} = require('@serialport/parser-readline') const port = new SerialPort('/dev/tty-usbserial1') -const parser = port.pipe(new Readline({ delimiter: '\r\n' })) +const parser = port.pipe(new ReadlineParser({ delimiter: '\r\n' })) parser.on('data', console.log) */ -class ReadLineParser extends DelimiterParser { - constructor(options) { +export class ReadlineParser extends DelimiterParser { + constructor(options?: ReadlineOptions) { const opts = { delimiter: Buffer.from('\n', 'utf8'), - encoding: 'utf8', + encoding: 'utf8' as BufferEncoding, ...options, } @@ -26,5 +35,3 @@ class ReadLineParser extends DelimiterParser { super(opts) } } - -module.exports = ReadLineParser diff --git a/packages/parser-readline/package-lock.json b/packages/parser-readline/package-lock.json index 105c9a6fb..4ea79f328 100644 --- a/packages/parser-readline/package-lock.json +++ b/packages/parser-readline/package-lock.json @@ -1,13 +1,62 @@ { "name": "@serialport/parser-readline", "version": "10.0.1", - "lockfileVersion": 1, + "lockfileVersion": 2, "requires": true, + "packages": { + "": { + "name": "@serialport/parser-readline", + "version": "10.0.1", + "license": "MIT", + "dependencies": { + "@serialport/parser-delimiter": "10.0.1" + }, + "devDependencies": { + "typescript": "^4.5.5" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-delimiter": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-10.0.1.tgz", + "integrity": "sha512-B0c6dm9UCpRU/LhkvRFL3OSbs69VqWU7mjW7tM109JDNS+vw8uJPumXz8Giub6D0xl90J7euH6tBTqERk7048Q==", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/typescript": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", + "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + } + }, "dependencies": { "@serialport/parser-delimiter": { - "version": "9.0.7", - "resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-9.0.7.tgz", - "integrity": "sha512-Vb2NPeXPZ/28M4m5x4OAHFd8jRAeddNCgvL+Q+H/hqFPY1w47JcMLchC7pigRW8Cnt1fklmzfwdNQ8Fb+kMkxQ==" + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-10.0.1.tgz", + "integrity": "sha512-B0c6dm9UCpRU/LhkvRFL3OSbs69VqWU7mjW7tM109JDNS+vw8uJPumXz8Giub6D0xl90J7euH6tBTqERk7048Q==" + }, + "typescript": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", + "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", + "dev": true } } } diff --git a/packages/parser-readline/package.json b/packages/parser-readline/package.json index 3bb54b1e2..0bafb696c 100644 --- a/packages/parser-readline/package.json +++ b/packages/parser-readline/package.json @@ -1,6 +1,7 @@ { "name": "@serialport/parser-readline", - "main": "lib", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", "version": "10.0.1", "dependencies": { "@serialport/parser-delimiter": "10.0.1" @@ -12,9 +13,15 @@ "access": "public" }, "license": "MIT", + "scripts": { + "build": "tsc --build tsconfig-build.json" + }, "repository": { "type": "git", "url": "git://github.com/serialport/node-serialport.git" }, - "funding": "https://opencollective.com/serialport/donate" + "funding": "https://opencollective.com/serialport/donate", + "devDependencies": { + "typescript": "^4.5.5" + } } diff --git a/packages/parser-readline/tsconfig-build.json b/packages/parser-readline/tsconfig-build.json new file mode 100644 index 000000000..d3d030c67 --- /dev/null +++ b/packages/parser-readline/tsconfig-build.json @@ -0,0 +1,15 @@ +{ + "extends": "../../tsconfig-build.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "lib", + }, + "exclude": [ + "node_modules", + "**/*.test.ts", + "dist", + ], + "references": [ + { "path": "../parser-delimiter/tsconfig-build.json"} + ] +} diff --git a/packages/parser-readline/tsconfig.json b/packages/parser-readline/tsconfig.json new file mode 100644 index 000000000..6f83eb665 --- /dev/null +++ b/packages/parser-readline/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tsconfig.json", +} diff --git a/packages/parser-ready/.npmignore b/packages/parser-ready/.npmignore index a1623b1ea..df4db6a44 100644 --- a/packages/parser-ready/.npmignore +++ b/packages/parser-ready/.npmignore @@ -1,3 +1,7 @@ .DS_Store *.test.js CHANGELOG.md +lib +tsconfig.json +tsconfig-build.json +tsconfig-build.tsbuildinfo diff --git a/packages/parser-ready/lib/index.test.js b/packages/parser-ready/lib/index.test.ts similarity index 91% rename from packages/parser-ready/lib/index.test.js rename to packages/parser-ready/lib/index.test.ts index 063e0420e..66ca6ff59 100644 --- a/packages/parser-ready/lib/index.test.js +++ b/packages/parser-ready/lib/index.test.ts @@ -1,8 +1,7 @@ -/* eslint-disable no-new */ - -const sinon = require('sinon') - -const ReadyParser = require('../') +/* eslint-disable @typescript-eslint/no-explicit-any */ +import sinon from 'sinon' +import { ReadyParser } from './' +import { assert } from '../../../test/assert' describe('ReadyParser', () => { it('emits data received after the ready data', () => { @@ -44,10 +43,10 @@ describe('ReadyParser', () => { it('throws when not provided with a delimiter', () => { assert.throws(() => { - new ReadyParser() + new (ReadyParser as any)() }) assert.throws(() => { - new ReadyParser({}) + new ReadyParser({} as any) }) }) diff --git a/packages/parser-ready/lib/index.js b/packages/parser-ready/lib/index.ts similarity index 59% rename from packages/parser-ready/lib/index.js rename to packages/parser-ready/lib/index.ts index 2f7415856..c1238a594 100644 --- a/packages/parser-ready/lib/index.js +++ b/packages/parser-ready/lib/index.ts @@ -1,39 +1,42 @@ -const { Transform } = require('stream') +import { Transform, TransformCallback, TransformOptions } from 'stream' + +export interface ReadyParserOptions extends TransformOptions { + delimiter: string | Buffer | number[] +} /** * A transform stream that waits for a sequence of "ready" bytes before emitting a ready event and emitting data events - * @summary To use the `Ready` parser provide a byte start sequence. After the bytes have been received a ready event is fired and data events are passed through. - * @extends Transform + * + * To use the `Ready` parser provide a byte start sequence. After the bytes have been received a ready event is fired and data events are passed through. * @example const SerialPort = require('serialport') -const Ready = require('@serialport/parser-ready') +const { ReadyParser } = require('@serialport/parser-ready') const port = new SerialPort('/dev/tty-usbserial1') -const parser = port.pipe(new Ready({ delimiter: 'READY' })) +const parser = port.pipe(new ReadyParser({ delimiter: 'READY' })) parser.on('ready', () => console.log('the ready byte sequence has been received')) parser.on('data', console.log) // all data after READY is received */ -class ReadyParser extends Transform { - /** - * - * @param {object} options options for the parser - * @param {string|Buffer|array} options.delimiter data to look for before emitted "ready" - */ - constructor(options = {}) { - if (options.delimiter === undefined) { +export class ReadyParser extends Transform { + delimiter: Buffer + readOffset: number + ready: boolean + + constructor({ delimiter, ...options }: ReadyParserOptions) { + if (delimiter === undefined) { throw new TypeError('"delimiter" is not a bufferable object') } - if (options.delimiter.length === 0) { + if (delimiter.length === 0) { throw new TypeError('"delimiter" has a 0 or undefined length') } super(options) - this.delimiter = Buffer.from(options.delimiter) + this.delimiter = Buffer.from(delimiter) this.readOffset = 0 this.ready = false } - _transform(chunk, encoding, cb) { + _transform(chunk: Buffer, encoding: BufferEncoding, cb: TransformCallback) { if (this.ready) { this.push(chunk) return cb() @@ -59,5 +62,3 @@ class ReadyParser extends Transform { cb() } } - -module.exports = ReadyParser diff --git a/packages/parser-ready/package-lock.json b/packages/parser-ready/package-lock.json index c5c60b8b1..2387bab8f 100644 --- a/packages/parser-ready/package-lock.json +++ b/packages/parser-ready/package-lock.json @@ -1,5 +1,43 @@ { "name": "@serialport/parser-ready", "version": "10.0.1", - "lockfileVersion": 1 + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "@serialport/parser-ready", + "version": "10.0.1", + "license": "MIT", + "devDependencies": { + "typescript": "^4.5.5" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/typescript": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", + "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + } + }, + "dependencies": { + "typescript": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", + "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", + "dev": true + } + } } diff --git a/packages/parser-ready/package.json b/packages/parser-ready/package.json index b5a0e3a0d..c93a81684 100644 --- a/packages/parser-ready/package.json +++ b/packages/parser-ready/package.json @@ -1,6 +1,7 @@ { "name": "@serialport/parser-ready", - "main": "lib", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", "version": "10.0.1", "engines": { "node": ">=12.0.0" @@ -9,9 +10,15 @@ "access": "public" }, "license": "MIT", + "scripts": { + "build": "tsc --build tsconfig-build.json" + }, "repository": { "type": "git", "url": "git://github.com/serialport/node-serialport.git" }, - "funding": "https://opencollective.com/serialport/donate" + "funding": "https://opencollective.com/serialport/donate", + "devDependencies": { + "typescript": "^4.5.5" + } } diff --git a/packages/parser-ready/tsconfig-build.json b/packages/parser-ready/tsconfig-build.json new file mode 100644 index 000000000..d3d030c67 --- /dev/null +++ b/packages/parser-ready/tsconfig-build.json @@ -0,0 +1,15 @@ +{ + "extends": "../../tsconfig-build.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "lib", + }, + "exclude": [ + "node_modules", + "**/*.test.ts", + "dist", + ], + "references": [ + { "path": "../parser-delimiter/tsconfig-build.json"} + ] +} diff --git a/packages/parser-ready/tsconfig.json b/packages/parser-ready/tsconfig.json new file mode 100644 index 000000000..6f83eb665 --- /dev/null +++ b/packages/parser-ready/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tsconfig.json", +} diff --git a/packages/parser-regex/.npmignore b/packages/parser-regex/.npmignore index a1623b1ea..df4db6a44 100644 --- a/packages/parser-regex/.npmignore +++ b/packages/parser-regex/.npmignore @@ -1,3 +1,7 @@ .DS_Store *.test.js CHANGELOG.md +lib +tsconfig.json +tsconfig-build.json +tsconfig-build.tsbuildinfo diff --git a/packages/parser-regex/lib/index.test.js b/packages/parser-regex/lib/index.test.ts similarity index 94% rename from packages/parser-regex/lib/index.test.js rename to packages/parser-regex/lib/index.test.ts index fa0fae132..9ed1744ca 100644 --- a/packages/parser-regex/lib/index.test.js +++ b/packages/parser-regex/lib/index.test.ts @@ -1,8 +1,7 @@ -/* eslint-disable no-new */ - -const sinon = require('sinon') - -const RegexParser = require('../') +/* eslint-disable @typescript-eslint/no-explicit-any */ +import sinon from 'sinon' +import { RegexParser } from './' +import { assert } from '../../../test/assert' describe('RegexParser', () => { it('transforms data to strings split on either carriage return or new line', () => { @@ -35,7 +34,7 @@ describe('RegexParser', () => { it('throws when not provided with a regex', () => { assert.throws(() => { - new RegexParser({}) + new RegexParser({} as any) }) }) diff --git a/packages/parser-regex/lib/index.js b/packages/parser-regex/lib/index.ts similarity index 59% rename from packages/parser-regex/lib/index.js rename to packages/parser-regex/lib/index.ts index ad7175e10..f43c41f26 100644 --- a/packages/parser-regex/lib/index.js +++ b/packages/parser-regex/lib/index.ts @@ -1,10 +1,14 @@ -const { Transform } = require('stream') +import { Transform, TransformCallback, TransformOptions } from 'stream' + +export interface RegexParserOptions extends TransformOptions { + regex: RegExp | string | Buffer +} /** * A transform stream that uses a regular expression to split the incoming text upon. * * To use the `Regex` parser provide a regular expression to split the incoming text upon. Data is emitted as string controllable by the `encoding` option (defaults to `utf8`). - * @extends Transform + * * @example const SerialPort = require('serialport') const Regex = require('@serialport/parser-regex') @@ -12,30 +16,33 @@ const port = new SerialPort('/dev/tty-usbserial1') const parser = port.pipe(new Regex({ regex: /[\r\n]+/ })) parser.on('data', console.log) */ -class RegexParser extends Transform { - constructor(options) { +export class RegexParser extends Transform { + regex: RegExp + data: string + + constructor({ regex, ...options }: RegexParserOptions) { const opts = { - encoding: 'utf8', + encoding: 'utf8' as BufferEncoding, ...options, } - if (opts.regex === undefined) { + if (regex === undefined) { throw new TypeError('"options.regex" must be a regular expression pattern or object') } - if (!(opts.regex instanceof RegExp)) { - opts.regex = new RegExp(opts.regex) + if (!(regex instanceof RegExp)) { + regex = new RegExp(regex.toString()) } super(opts) - this.regex = opts.regex + this.regex = regex this.data = '' } - _transform(chunk, encoding, cb) { + _transform(chunk: string, encoding: BufferEncoding, cb: TransformCallback) { const data = this.data + chunk const parts = data.split(this.regex) - this.data = parts.pop() + this.data = parts.pop() || '' parts.forEach(part => { this.push(part) @@ -43,11 +50,9 @@ class RegexParser extends Transform { cb() } - _flush(cb) { + _flush(cb: TransformCallback) { this.push(this.data) this.data = '' cb() } } - -module.exports = RegexParser diff --git a/packages/parser-regex/package-lock.json b/packages/parser-regex/package-lock.json index bddfd4f57..9e92539b8 100644 --- a/packages/parser-regex/package-lock.json +++ b/packages/parser-regex/package-lock.json @@ -1,5 +1,43 @@ { "name": "@serialport/parser-regex", "version": "10.0.1", - "lockfileVersion": 1 + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "@serialport/parser-regex", + "version": "10.0.1", + "license": "MIT", + "devDependencies": { + "typescript": "^4.5.5" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/typescript": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", + "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + } + }, + "dependencies": { + "typescript": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", + "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", + "dev": true + } + } } diff --git a/packages/parser-regex/package.json b/packages/parser-regex/package.json index 98253f488..f89921b2d 100644 --- a/packages/parser-regex/package.json +++ b/packages/parser-regex/package.json @@ -1,6 +1,7 @@ { "name": "@serialport/parser-regex", - "main": "lib", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", "version": "10.0.1", "engines": { "node": ">=12.0.0" @@ -9,9 +10,15 @@ "access": "public" }, "license": "MIT", + "scripts": { + "build": "tsc --build tsconfig-build.json" + }, "repository": { "type": "git", "url": "git://github.com/serialport/node-serialport.git" }, - "funding": "https://opencollective.com/serialport/donate" + "funding": "https://opencollective.com/serialport/donate", + "devDependencies": { + "typescript": "^4.5.5" + } } diff --git a/packages/parser-regex/tsconfig-build.json b/packages/parser-regex/tsconfig-build.json new file mode 100644 index 000000000..d3d030c67 --- /dev/null +++ b/packages/parser-regex/tsconfig-build.json @@ -0,0 +1,15 @@ +{ + "extends": "../../tsconfig-build.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "lib", + }, + "exclude": [ + "node_modules", + "**/*.test.ts", + "dist", + ], + "references": [ + { "path": "../parser-delimiter/tsconfig-build.json"} + ] +} diff --git a/packages/parser-regex/tsconfig.json b/packages/parser-regex/tsconfig.json new file mode 100644 index 000000000..6f83eb665 --- /dev/null +++ b/packages/parser-regex/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tsconfig.json", +} diff --git a/packages/parser-slip-encoder/.npmignore b/packages/parser-slip-encoder/.npmignore index a1623b1ea..df4db6a44 100644 --- a/packages/parser-slip-encoder/.npmignore +++ b/packages/parser-slip-encoder/.npmignore @@ -1,3 +1,7 @@ .DS_Store *.test.js CHANGELOG.md +lib +tsconfig.json +tsconfig-build.json +tsconfig-build.tsbuildinfo diff --git a/packages/parser-slip-encoder/lib/decoder.test.js b/packages/parser-slip-encoder/lib/decoder.test.ts similarity index 97% rename from packages/parser-slip-encoder/lib/decoder.test.js rename to packages/parser-slip-encoder/lib/decoder.test.ts index 2907ae8df..821c1b954 100644 --- a/packages/parser-slip-encoder/lib/decoder.test.js +++ b/packages/parser-slip-encoder/lib/decoder.test.ts @@ -1,8 +1,6 @@ -/* eslint-disable no-new */ - -const sinon = require('sinon') - -const SlipDecoder = require('./decoder') +import sinon from 'sinon' +import { SlipDecoder } from './decoder' +import { assert } from '../../../test/assert' describe('SlipDecoder', () => { it('Decodes one-byte messages', () => { diff --git a/packages/parser-slip-encoder/lib/decoder.js b/packages/parser-slip-encoder/lib/decoder.ts similarity index 66% rename from packages/parser-slip-encoder/lib/decoder.js rename to packages/parser-slip-encoder/lib/decoder.ts index c387214ff..a0aecbe01 100644 --- a/packages/parser-slip-encoder/lib/decoder.js +++ b/packages/parser-slip-encoder/lib/decoder.ts @@ -1,4 +1,13 @@ -const { Transform } = require('stream') +import { Transform, TransformCallback, TransformOptions } from 'stream' + +export interface SlipDecoderOptions extends TransformOptions { + START?: number + ESC?: number + END?: number + ESC_START?: number + ESC_END?: number + ESC_ESC?: number +} /** * A transform stream that decodes slip encoded data. @@ -13,29 +22,31 @@ const port = new SerialPort('/dev/tty-usbserial1') const parser = port.pipe(new SlipDecoder()) parser.on('data', console.log) */ -class SlipDecoder extends Transform { - constructor(options = {}) { +export class SlipDecoder extends Transform { + opts: { START: number | undefined; ESC: number; END: number; ESC_START: number | undefined; ESC_END: number; ESC_ESC: number } + buffer: Buffer + escape: boolean + start: boolean + constructor(options: SlipDecoderOptions = {}) { super(options) - const opts = { - START: undefined, - ESC: 0xdb, - END: 0xc0, + const { START, ESC = 0xdb, END = 0xc0, ESC_START, ESC_END = 0xdc, ESC_ESC = 0xdd } = options - ESC_START: undefined, - ESC_END: 0xdc, - ESC_ESC: 0xdd, - - ...options, + this.opts = { + START, + ESC, + END, + ESC_START, + ESC_END, + ESC_ESC, } - this.opts = opts this.buffer = Buffer.alloc(0) this.escape = false this.start = false } - _transform(chunk, encoding, cb) { + _transform(chunk: Buffer, encoding: BufferEncoding, cb: TransformCallback) { for (let ndx = 0; ndx < chunk.length; ndx++) { let byte = chunk[ndx] @@ -47,7 +58,7 @@ class SlipDecoder extends Transform { } if (this.escape) { - if (byte === this.opts.ESC_START) { + if (byte === this.opts.ESC_START && this.opts.START) { byte = this.opts.START } else if (byte === this.opts.ESC_ESC) { byte = this.opts.ESC @@ -76,7 +87,7 @@ class SlipDecoder extends Transform { this.escape = false - if (true === this.start) { + if (this.start) { this.buffer = Buffer.concat([this.buffer, Buffer.from([byte])]) } } @@ -84,11 +95,9 @@ class SlipDecoder extends Transform { cb() } - _flush(cb) { + _flush(cb: TransformCallback) { this.push(this.buffer) this.buffer = Buffer.alloc(0) cb() } } - -module.exports = SlipDecoder diff --git a/packages/parser-slip-encoder/lib/encoder.test.js b/packages/parser-slip-encoder/lib/encoder.test.ts similarity index 97% rename from packages/parser-slip-encoder/lib/encoder.test.js rename to packages/parser-slip-encoder/lib/encoder.test.ts index 93c828c5d..eeaa1c748 100644 --- a/packages/parser-slip-encoder/lib/encoder.test.js +++ b/packages/parser-slip-encoder/lib/encoder.test.ts @@ -1,5 +1,6 @@ -const sinon = require('sinon') -const SlipEncoder = require('./encoder') +import sinon from 'sinon' +import { SlipEncoder } from './encoder' +import { assert } from '../../../test/assert' describe('SlipEncoder', () => { it('Adds one delimiter to one-byte messages', () => { diff --git a/packages/parser-slip-encoder/lib/encoder.js b/packages/parser-slip-encoder/lib/encoder.ts similarity index 69% rename from packages/parser-slip-encoder/lib/encoder.js rename to packages/parser-slip-encoder/lib/encoder.ts index 5bb48ae8f..83aee5af9 100644 --- a/packages/parser-slip-encoder/lib/encoder.js +++ b/packages/parser-slip-encoder/lib/encoder.ts @@ -1,4 +1,14 @@ -const { Transform } = require('stream') +import { Transform, TransformCallback, TransformOptions } from 'stream' + +export interface SlipEncoderOptions extends TransformOptions { + START?: number + ESC?: number + END?: number + ESC_START?: number + ESC_END?: number + ESC_ESC?: number + bluetoothQuirk?: boolean +} /** * A transform stream that emits SLIP-encoded data for each incoming packet. @@ -19,25 +29,34 @@ const lineParser = fileReader.pipe(new Readline({ delimiter: '\r\n' })); const encoder = fileReader.pipe(new SlipEncoder({ bluetoothQuirk: false })); encoder.pipe(port); */ -class SlipEncoder extends Transform { - constructor(options = {}) { - super(options) +export class SlipEncoder extends Transform { + opts: { + START: number | undefined + ESC: number + END: number + ESC_START: number | undefined + ESC_END: number + ESC_ESC: number + bluetoothQuirk: boolean + } - const opts = { - START: undefined, - ESC: 0xdb, - END: 0xc0, + constructor(options: SlipEncoderOptions = {}) { + super(options) - ESC_START: undefined, - ESC_END: 0xdc, - ESC_ESC: 0xdd, + const { START, ESC = 0xdb, END = 0xc0, ESC_START, ESC_END = 0xdc, ESC_ESC = 0xdd, bluetoothQuirk = false } = options - ...options, + this.opts = { + START, + ESC, + END, + ESC_START, + ESC_END, + ESC_ESC, + bluetoothQuirk, } - this.opts = opts } - _transform(chunk, encoding, cb) { + _transform(chunk: Buffer, encoding: BufferEncoding, cb: TransformCallback) { const chunkLength = chunk.length if (this.opts.bluetoothQuirk && chunkLength === 0) { @@ -62,7 +81,7 @@ class SlipEncoder extends Transform { for (let i = 0; i < chunkLength; i++) { let byte = chunk[i] - if (byte === this.opts.START) { + if (byte === this.opts.START && this.opts.ESC_START) { encoded[j++] = this.opts.ESC byte = this.opts.ESC_START } else if (byte === this.opts.END) { @@ -81,5 +100,3 @@ class SlipEncoder extends Transform { cb(null, encoded.slice(0, j)) } } - -module.exports = SlipEncoder diff --git a/packages/parser-slip-encoder/lib/index.js b/packages/parser-slip-encoder/lib/index.js deleted file mode 100644 index ed3b80cf6..000000000 --- a/packages/parser-slip-encoder/lib/index.js +++ /dev/null @@ -1,6 +0,0 @@ -const SlipEncoder = require('./encoder') -const SlipDecoder = require('./decoder') - -module.exports = SlipEncoder -module.exports.SlipEncoder = SlipEncoder -module.exports.SlipDecoder = SlipDecoder diff --git a/packages/parser-slip-encoder/lib/index.ts b/packages/parser-slip-encoder/lib/index.ts new file mode 100644 index 000000000..ad6baf554 --- /dev/null +++ b/packages/parser-slip-encoder/lib/index.ts @@ -0,0 +1,2 @@ +export * from './decoder' +export * from './encoder' diff --git a/packages/parser-slip-encoder/package-lock.json b/packages/parser-slip-encoder/package-lock.json index 380531af6..42fb20504 100644 --- a/packages/parser-slip-encoder/package-lock.json +++ b/packages/parser-slip-encoder/package-lock.json @@ -1,5 +1,43 @@ { "name": "@serialport/parser-slip-encoder", "version": "10.0.1", - "lockfileVersion": 1 + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "@serialport/parser-slip-encoder", + "version": "10.0.1", + "license": "MIT", + "devDependencies": { + "typescript": "^4.5.5" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/typescript": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", + "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + } + }, + "dependencies": { + "typescript": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", + "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", + "dev": true + } + } } diff --git a/packages/parser-slip-encoder/package.json b/packages/parser-slip-encoder/package.json index 7ead04136..25174f0c6 100644 --- a/packages/parser-slip-encoder/package.json +++ b/packages/parser-slip-encoder/package.json @@ -1,6 +1,10 @@ { "name": "@serialport/parser-slip-encoder", - "main": "lib", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "scripts": { + "build": "tsc --build tsconfig-build.json" + }, "version": "10.0.1", "engines": { "node": ">=12.0.0" @@ -13,5 +17,8 @@ "type": "git", "url": "git://github.com/serialport/node-serialport.git" }, - "funding": "https://opencollective.com/serialport/donate" + "funding": "https://opencollective.com/serialport/donate", + "devDependencies": { + "typescript": "^4.5.5" + } } diff --git a/packages/parser-slip-encoder/tsconfig-build.json b/packages/parser-slip-encoder/tsconfig-build.json new file mode 100644 index 000000000..5442270f0 --- /dev/null +++ b/packages/parser-slip-encoder/tsconfig-build.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig-build.json", + "compilerOptions": { + "rootDir": "lib", + "outDir": "dist", + }, + "exclude": [ + "node_modules", + "**/*.test.ts", + "dist", + ] +} diff --git a/packages/parser-slip-encoder/tsconfig.json b/packages/parser-slip-encoder/tsconfig.json new file mode 100644 index 000000000..4082f16a5 --- /dev/null +++ b/packages/parser-slip-encoder/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tsconfig.json" +} diff --git a/packages/parser-spacepacket/.npmignore b/packages/parser-spacepacket/.npmignore index 2a8c19d22..df4db6a44 100644 --- a/packages/parser-spacepacket/.npmignore +++ b/packages/parser-spacepacket/.npmignore @@ -1,3 +1,7 @@ .DS_Store *.test.js -CHANGELOG.md \ No newline at end of file +CHANGELOG.md +lib +tsconfig.json +tsconfig-build.json +tsconfig-build.tsbuildinfo diff --git a/packages/parser-spacepacket/lib/index.test.js b/packages/parser-spacepacket/lib/index.test.ts similarity index 89% rename from packages/parser-spacepacket/lib/index.test.js rename to packages/parser-spacepacket/lib/index.test.ts index bc6a25b34..11dfc9a3f 100644 --- a/packages/parser-spacepacket/lib/index.test.js +++ b/packages/parser-spacepacket/lib/index.test.ts @@ -1,7 +1,6 @@ -const { expect } = require('chai') -const sinon = require('sinon') - -const SpacePacketParser = require('./index') +import { expect } from 'chai' +import sinon from 'sinon' +import { SpacePacketParser } from './' const VERSION = '000' const TYPE = '0' @@ -13,7 +12,7 @@ const COUNT = '00000000000001' const DATA_LENGTH = '0000000000000101' const DATA_LENGTH_WITH_2ARY_HEADER = '0000000000010001' -const buildSixCharacterBasicSpacePacket = text => { +const buildSixCharacterBasicSpacePacket = (text: string) => { const data = text.slice(0, 6) const baseArray = [] let headerAsString = `${VERSION}${TYPE}${NO_SECONDARY_HEADER}${API_ID}${UNSEGMENTED}${COUNT}${DATA_LENGTH}` @@ -46,7 +45,7 @@ const buildSixCharacterBasicSpacePacket = text => { } } -const buildSixCharacterBasicSpacePacketWithSecondaryHeaderLength6 = text => { +const buildSixCharacterBasicSpacePacketWithSecondaryHeaderLength6 = (text: string) => { const data = text.slice(0, 6) const baseArray = [] const TIMECO = 'TIMECO' @@ -87,13 +86,8 @@ const buildSixCharacterBasicSpacePacketWithSecondaryHeaderLength6 = text => { } describe('SpacePacketParser', () => { - let test - - beforeEach(() => { - test = new SpacePacketParser() - }) - it('handles a complete space packet buffer sent at once', () => { + const test = new SpacePacketParser() const { buffer, expected } = buildSixCharacterBasicSpacePacket('123456') test.on('data', data => { @@ -104,6 +98,7 @@ describe('SpacePacketParser', () => { }) it('flushes a partial packet if end is called', () => { + const test = new SpacePacketParser() const { buffer } = buildSixCharacterBasicSpacePacket('things') test.on('data', data => { @@ -116,6 +111,7 @@ describe('SpacePacketParser', () => { }) it('handles receiving two space packets at the same time', () => { + const test = new SpacePacketParser() const { buffer, expected } = buildSixCharacterBasicSpacePacket('first1') const { buffer: buff2, expected: expect2 } = buildSixCharacterBasicSpacePacket('second') const dataCallbackSpy = sinon.stub() @@ -129,6 +125,7 @@ describe('SpacePacketParser', () => { }) it('handles receiving parts of a space packet in chunks', () => { + const test = new SpacePacketParser() const { buffer, expected } = buildSixCharacterBasicSpacePacket('partsy') const dataCallbackSpy = sinon.stub() @@ -143,6 +140,7 @@ describe('SpacePacketParser', () => { }) it('handles receiving a complete plus a partial packet at once', () => { + const test = new SpacePacketParser() const { buffer, expected } = buildSixCharacterBasicSpacePacket('part_1') const { buffer: buff2 } = buildSixCharacterBasicSpacePacket('part_2') const onePoint5ishPackets = Buffer.concat([buffer, buff2]).slice(0, 16) @@ -157,13 +155,13 @@ describe('SpacePacketParser', () => { }) it('includes timeCode and ancillaryData fields in a secondary header object', () => { - const test2 = new SpacePacketParser({ timeCodeFieldLength: 6, ancillaryDataFieldLength: 6 }) + const test = new SpacePacketParser({ timeCodeFieldLength: 6, ancillaryDataFieldLength: 6 }) const { buffer, expected } = buildSixCharacterBasicSpacePacketWithSecondaryHeaderLength6('123456') - test2.on('data', data => { + test.on('data', data => { expect(data).to.deep.equal(expected) }) - test2.write(buffer) + test.write(buffer) }) }) diff --git a/packages/parser-spacepacket/lib/index.js b/packages/parser-spacepacket/lib/index.ts similarity index 78% rename from packages/parser-spacepacket/lib/index.js rename to packages/parser-spacepacket/lib/index.ts index 327cd7cb7..95a0fb3fd 100644 --- a/packages/parser-spacepacket/lib/index.js +++ b/packages/parser-spacepacket/lib/index.ts @@ -1,14 +1,28 @@ -const { Transform } = require('stream') +import { Transform, TransformCallback, TransformOptions } from 'stream' +import { HEADER_LENGTH, convertHeaderBufferToObj, SpacePacket, SpacePacketHeader } from './utils' -const { HEADER_LENGTH, convertHeaderBufferToObj } = require('./utils') +export { SpacePacket, SpacePacketHeader } + +export interface SpacePacketOptions extends Omit { + timeCodeFieldLength?: number + ancillaryDataFieldLength?: number +} /** * A Transform stream that accepts a stream of octet data and converts it into an object * representation of a CCSDS Space Packet. See https://public.ccsds.org/Pubs/133x0b2e1.pdf for a * description of the Space Packet format. - * @extends Transform */ -class SpacePacketParser extends Transform { +export class SpacePacketParser extends Transform { + timeCodeFieldLength: number + ancillaryDataFieldLength: number + dataBuffer: Buffer + headerBuffer: Buffer + dataLength: number + expectingHeader: boolean + dataSlice: number + header?: SpacePacketHeader + /** * A Transform stream that accepts a stream of octet data and emits object representations of * CCSDS Space Packets once a packet has been completely received. @@ -16,7 +30,7 @@ class SpacePacketParser extends Transform { * @param {Number} options.timeCodeFieldLength The length of the time code field within the data * @param {Number} options.ancillaryDataFieldLength The length of the ancillary data field within the data */ - constructor(options = {}) { + constructor(options: SpacePacketOptions = {}) { super({ ...options, objectMode: true }) // Set the constants for this Space Packet Connection; these will help us parse incoming data // fields: @@ -36,11 +50,18 @@ class SpacePacketParser extends Transform { * packet(s). */ pushCompletedPacket() { - const completedPacket = { header: { ...this.header } } + if (!this.header) { + throw new Error('Missing header') + } const timeCode = Buffer.from(this.dataBuffer.slice(0, this.timeCodeFieldLength)) const ancillaryData = Buffer.from(this.dataBuffer.slice(this.timeCodeFieldLength, this.timeCodeFieldLength + this.ancillaryDataFieldLength)) const data = Buffer.from(this.dataBuffer.slice(this.dataSlice, this.dataLength)) + const completedPacket: SpacePacket = { + header: { ...this.header }, + data: data.toString(), + } + if (timeCode.length > 0 || ancillaryData.length > 0) { completedPacket.secondaryHeader = {} @@ -53,7 +74,6 @@ class SpacePacketParser extends Transform { } } - completedPacket.data = data.toString() this.push(completedPacket) // If there is an overflow (i.e. we have more data than the packet we just pushed) begin parsing @@ -67,7 +87,7 @@ class SpacePacketParser extends Transform { this.dataBuffer = Buffer.alloc(0) this.expectingHeader = true this.dataLength = 0 - this.header = {} + this.header = undefined } } @@ -75,9 +95,9 @@ class SpacePacketParser extends Transform { * Build the Stream's headerBuffer property from the received Buffer chunk; extract data from it * if it's complete. If there's more to the chunk than just the header, initiate handling the * packet data. - * @param {Buffer} chunk Build the Stream's headerBuffer property from + * @param chunk - Build the Stream's headerBuffer property from */ - extractHeader(chunk) { + extractHeader(chunk: Buffer) { const headerAsBuffer = Buffer.concat([this.headerBuffer, chunk]) const startOfDataBuffer = headerAsBuffer.slice(HEADER_LENGTH) @@ -99,7 +119,7 @@ class SpacePacketParser extends Transform { } } - _transform(chunk, _, cb) { + _transform(chunk: Buffer, encoding: BufferEncoding, cb: TransformCallback) { if (this.expectingHeader) { this.extractHeader(chunk) } else { @@ -113,7 +133,7 @@ class SpacePacketParser extends Transform { cb() } - _flush(cb) { + _flush(cb: TransformCallback) { const remaining = Buffer.concat([this.headerBuffer, this.dataBuffer]) const remainingArray = Array.from(remaining) @@ -121,5 +141,3 @@ class SpacePacketParser extends Transform { cb() } } - -module.exports = SpacePacketParser diff --git a/packages/parser-spacepacket/lib/utils.js b/packages/parser-spacepacket/lib/utils.ts similarity index 61% rename from packages/parser-spacepacket/lib/utils.js rename to packages/parser-spacepacket/lib/utils.ts index 04c4ee754..967551f99 100644 --- a/packages/parser-spacepacket/lib/utils.js +++ b/packages/parser-spacepacket/lib/utils.ts @@ -1,11 +1,9 @@ -const HEADER_LENGTH = 6 +export const HEADER_LENGTH = 6 /** - * For numbers less than 255, will ensure that their string representation is at least 8 - * characters long. - * @param {Number} num Any number + * For numbers less than 255, will ensure that their string representation is at least 8 characters long. */ -const toOctetStr = num => { +const toOctetStr = (num: number) => { let str = Number(num).toString(2) while (str.length < 8) { @@ -15,12 +13,35 @@ const toOctetStr = num => { return str } +export interface SpacePacketHeader { + versionNumber: string | number + identification: { + apid: number + secondaryHeader: number + type: number + } + sequenceControl: { + packetName: number + sequenceFlags: number + } + dataLength: number +} + +export interface SpacePacket { + header: SpacePacketHeader + secondaryHeader?: { + timeCode?: string + ancillaryData?: string + } + data: string +} + /** * Converts a Buffer of any length to an Object representation of a Space Packet header, provided * the received data is in the correct format. - * @param {Buffer} buf The buffer containing the Space Packet Header Data + * @param buf - The buffer containing the Space Packet Header Data */ -const convertHeaderBufferToObj = buf => { +export const convertHeaderBufferToObj = (buf: Buffer): SpacePacketHeader => { const headerStr = Array.from(buf.slice(0, HEADER_LENGTH)).reduce((accum, curr) => `${accum}${toOctetStr(curr)}`, '') const isVersion1 = headerStr.slice(0, 3) === '000' const versionNumber = isVersion1 ? 1 : 'UNKNOWN_VERSION' @@ -45,9 +66,3 @@ const convertHeaderBufferToObj = buf => { dataLength, } } - -module.exports = { - HEADER_LENGTH, - toOctetStr, - convertHeaderBufferToObj, -} diff --git a/packages/parser-spacepacket/package-lock.json b/packages/parser-spacepacket/package-lock.json index dcfebd5c4..ccfb4aa90 100644 --- a/packages/parser-spacepacket/package-lock.json +++ b/packages/parser-spacepacket/package-lock.json @@ -1,5 +1,43 @@ { "name": "@serialport/parser-spacepacket", "version": "10.0.1", - "lockfileVersion": 1 + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "@serialport/parser-spacepacket", + "version": "10.0.1", + "license": "MIT", + "devDependencies": { + "typescript": "^4.5.5" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/typescript": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", + "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + } + }, + "dependencies": { + "typescript": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", + "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", + "dev": true + } + } } diff --git a/packages/parser-spacepacket/package.json b/packages/parser-spacepacket/package.json index 07768432c..3ff362a4e 100644 --- a/packages/parser-spacepacket/package.json +++ b/packages/parser-spacepacket/package.json @@ -1,6 +1,10 @@ { "name": "@serialport/parser-spacepacket", - "main": "lib", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "scripts": { + "build": "tsc --build tsconfig-build.json" + }, "version": "10.0.1", "engines": { "node": ">=12.0.0" @@ -13,5 +17,8 @@ "type": "git", "url": "git://github.com/serialport/node-serialport.git" }, - "funding": "https://opencollective.com/serialport/donate" + "funding": "https://opencollective.com/serialport/donate", + "devDependencies": { + "typescript": "^4.5.5" + } } diff --git a/packages/parser-spacepacket/tsconfig-build.json b/packages/parser-spacepacket/tsconfig-build.json new file mode 100644 index 000000000..5442270f0 --- /dev/null +++ b/packages/parser-spacepacket/tsconfig-build.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig-build.json", + "compilerOptions": { + "rootDir": "lib", + "outDir": "dist", + }, + "exclude": [ + "node_modules", + "**/*.test.ts", + "dist", + ] +} diff --git a/packages/parser-spacepacket/tsconfig.json b/packages/parser-spacepacket/tsconfig.json new file mode 100644 index 000000000..4082f16a5 --- /dev/null +++ b/packages/parser-spacepacket/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tsconfig.json" +} diff --git a/packages/repl/.npmignore b/packages/repl/.npmignore index a1623b1ea..df4db6a44 100644 --- a/packages/repl/.npmignore +++ b/packages/repl/.npmignore @@ -1,3 +1,7 @@ .DS_Store *.test.js CHANGELOG.md +lib +tsconfig.json +tsconfig-build.json +tsconfig-build.tsbuildinfo diff --git a/packages/repl/lib/index.js b/packages/repl/lib/index.ts similarity index 55% rename from packages/repl/lib/index.js rename to packages/repl/lib/index.ts index 281f508e1..88b3d5c86 100644 --- a/packages/repl/lib/index.js +++ b/packages/repl/lib/index.ts @@ -1,10 +1,10 @@ #!/usr/bin/env node -const { promirepl } = require('promirepl') -const repl = require('repl') -const SerialPort = require('serialport') +import { promirepl } from 'promirepl' +import repl from 'repl' +import { SerialPort, SerialPortMock } from 'serialport' -process.env.DEBUG = process.env.DEBUG || '*' +const baudRate = Number(process.env.BAUDRATE) || 9600 // outputs the path to an arduino or nothing async function findArduino() { @@ -14,7 +14,7 @@ async function findArduino() { } const ports = await SerialPort.list() for (const port of ports) { - if (/arduino/i.test(port.manufacturer)) { + if (/arduino/i.test(port.manufacturer || '')) { return port.path } } @@ -25,12 +25,14 @@ async function findArduino() { findArduino() .then(portName => { - console.log(`port = SerialPort("${portName}", { autoOpen: false })`) - console.log('globals { SerialPort, portName, port }') - const port = new SerialPort(portName, { autoOpen: false }) + console.log(`DEBUG=${process.env.DEBUG || ''} # enable debugging with DEBUG=serialport*`) + console.log(`port = SerialPort({ path: "${portName}", autoOpen: false })`) + console.log('globals { SerialPort, SerialPortMock, portName, port }') + const port = new SerialPort({ path: portName, baudRate, autoOpen: false }) const spRepl = repl.start({ prompt: '> ' }) promirepl(spRepl) spRepl.context.SerialPort = SerialPort + spRepl.context.SerialPortMock = SerialPortMock spRepl.context.portName = portName spRepl.context.port = port }) diff --git a/packages/repl/lib/pomirepl.d.ts b/packages/repl/lib/pomirepl.d.ts new file mode 100644 index 000000000..b827ec348 --- /dev/null +++ b/packages/repl/lib/pomirepl.d.ts @@ -0,0 +1 @@ +declare module 'promirepl' diff --git a/packages/repl/package-lock.json b/packages/repl/package-lock.json index ce579fd95..302d33cc7 100644 --- a/packages/repl/package-lock.json +++ b/packages/repl/package-lock.json @@ -1,43 +1,408 @@ { - "name": "@serialport/repl", - "version": "10.1.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "@serialport/repl", - "version": "10.1.0", - "license": "MIT", - "dependencies": { - "promirepl": "^2.0.1" - }, - "bin": { - "serialport-repl": "lib/index.js" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://opencollective.com/serialport/donate" - } - }, - "node_modules/promirepl": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/promirepl/-/promirepl-2.0.1.tgz", - "integrity": "sha512-CUYuyU858eJ4OuQLKhJTE1Mijb1zxuAOnmVSYysau1k58zxT9qf0bJb/3QYrYadzmyuLn4EB82Oki+uHmWqC0w==", - "bin": { - "prominode": "bin/prominode.js" - }, - "engines": { - "node": "^8" - } - } - }, - "dependencies": { - "promirepl": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/promirepl/-/promirepl-2.0.1.tgz", - "integrity": "sha512-CUYuyU858eJ4OuQLKhJTE1Mijb1zxuAOnmVSYysau1k58zxT9qf0bJb/3QYrYadzmyuLn4EB82Oki+uHmWqC0w==" - } - } + "name": "@serialport/repl", + "version": "10.1.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "@serialport/repl", + "version": "10.1.0", + "license": "MIT", + "dependencies": { + "promirepl": "^2.0.1", + "serialport": "10.1.0" + }, + "bin": { + "serialport-repl": "dist/index.js" + }, + "devDependencies": { + "typescript": "^4.5.5" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/binding-abstract": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/@serialport/binding-abstract/-/binding-abstract-10.1.0.tgz", + "integrity": "sha512-P2KUIa6LTX0GKRzgwcEqO7kIG7yI66GuxV1hBv/Lq1rE+fxGj0SXzX2YlRm1vCubFU/102wK0TT2i+oPf2bALQ==", + "dependencies": { + "debug": "^4.3.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/binding-mock": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/@serialport/binding-mock/-/binding-mock-10.1.0.tgz", + "integrity": "sha512-NPj1fGDkUJbSElBnU0mVi2Hw45cXh4Za6gfU3C854JmhdvpGzehiDjOYm8iJB3bCQZMs3wzaYLdE8o8/wAUSEw==", + "dependencies": { + "@serialport/binding-abstract": "10.1.0", + "debug": "^4.3.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/bindings-cpp": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/@serialport/bindings-cpp/-/bindings-cpp-10.4.0.tgz", + "integrity": "sha512-vV5fadfzovV2iZ9xWjlQOdjEL0U0y5ewjdplx1DG5ox+/Q2Mmi8WEbcoKww6AgouU5qLirQGfYDmcWPlcGMdMw==", + "hasInstallScript": true, + "dependencies": { + "@serialport/binding-abstract": "10.0.1", + "@serialport/parser-readline": "10.0.1", + "debug": "^4.3.2", + "node-addon-api": "^4.3.0", + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=12.17.0 <13.0 || >=14.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/bindings-cpp/node_modules/@serialport/binding-abstract": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@serialport/binding-abstract/-/binding-abstract-10.0.1.tgz", + "integrity": "sha512-FWD/uNrz8V3kaTILQTK05Z1LB/LZin8XZelmX/wd1NNlRFAj6V64MIESWhwUy3iPnL1QriFR1k7URHHx3RRgfg==", + "dependencies": { + "debug": "^4.3.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-byte-length": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@serialport/parser-byte-length/-/parser-byte-length-10.0.1.tgz", + "integrity": "sha512-uOQa0KEGT7IIGSWCN53NE5ZYaWoeeGLDCSX+ssDadyQxy47hMHuP/JotdWqHg7lDwxUHe0tDl4SOEeEnDx1l6A==", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-cctalk": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@serialport/parser-cctalk/-/parser-cctalk-10.0.1.tgz", + "integrity": "sha512-boVr8akjX/7iCtMHeFT16ek4m0/oV9YA6A2mstVCpKle2op42qByx3jY5RzQ52c13oQvq1E6tG0lWJrzdTK+Yw==", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-delimiter": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-10.0.1.tgz", + "integrity": "sha512-B0c6dm9UCpRU/LhkvRFL3OSbs69VqWU7mjW7tM109JDNS+vw8uJPumXz8Giub6D0xl90J7euH6tBTqERk7048Q==", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-inter-byte-timeout": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@serialport/parser-inter-byte-timeout/-/parser-inter-byte-timeout-10.0.1.tgz", + "integrity": "sha512-awX0bekMZkjb+kjBHsnizAXNfc/grxIqEKdy9Etc6KhgSmratRnjGa7J0rPFP4bTzYWp5sOqlI0ALwBnWCXedA==", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-readline": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-10.0.1.tgz", + "integrity": "sha512-jdKPNka/Nn17k89T5UIyis39EaZHQCmq+83s0icBt2iPBlX8+BrJAUBe8myFpuT22qskTVNzFoTMPOp8pjK/yw==", + "dependencies": { + "@serialport/parser-delimiter": "10.0.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-ready": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@serialport/parser-ready/-/parser-ready-10.0.1.tgz", + "integrity": "sha512-4hVDrKNJBd0wcCfa1qQAk+MM6mVWc9oIbUPEKJkWdBrrWOqYacx2UpvQWd+3YGJ04hFqEv1feOSaH3/1hUifEg==", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-regex": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@serialport/parser-regex/-/parser-regex-10.0.1.tgz", + "integrity": "sha512-l8ECuUsan33x5pirQZodlmw0q70Jcxy+oHnXJaqchBTRCbtXlE7+PMFJnmNoIHGqDwt0XALbwpvKcnNBrgvT1g==", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/stream": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/@serialport/stream/-/stream-10.1.0.tgz", + "integrity": "sha512-0whwMF9ZYvB4Adjf0dDxUh6rLTuuqNzMkNbuC9EbKNaduUTBzssdbzQVHqa+srGUbmJgy/NKCukqWrUSyNcCPA==", + "dependencies": { + "debug": "^4.3.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/node-addon-api": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", + "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==" + }, + "node_modules/node-gyp-build": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.3.0.tgz", + "integrity": "sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/promirepl": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promirepl/-/promirepl-2.0.1.tgz", + "integrity": "sha512-CUYuyU858eJ4OuQLKhJTE1Mijb1zxuAOnmVSYysau1k58zxT9qf0bJb/3QYrYadzmyuLn4EB82Oki+uHmWqC0w==", + "bin": { + "prominode": "bin/prominode.js" + }, + "engines": { + "node": "^8" + } + }, + "node_modules/serialport": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/serialport/-/serialport-10.1.0.tgz", + "integrity": "sha512-qRTpa85UdRdYKeuUrmo1bK26jAbxpUi9/WfBVlWQBgn58pAKEf2FyAsnn+uYCEszJbycr+jOqSxHM2wRLHRilQ==", + "dependencies": { + "@serialport/binding-mock": "10.1.0", + "@serialport/bindings-cpp": "10.4.0", + "@serialport/parser-byte-length": "10.0.1", + "@serialport/parser-cctalk": "10.0.1", + "@serialport/parser-delimiter": "10.0.1", + "@serialport/parser-inter-byte-timeout": "10.0.1", + "@serialport/parser-readline": "10.0.1", + "@serialport/parser-ready": "10.0.1", + "@serialport/parser-regex": "10.0.1", + "@serialport/stream": "10.1.0", + "debug": "^4.3.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/typescript": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", + "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + } + }, + "dependencies": { + "@serialport/binding-abstract": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/@serialport/binding-abstract/-/binding-abstract-10.1.0.tgz", + "integrity": "sha512-P2KUIa6LTX0GKRzgwcEqO7kIG7yI66GuxV1hBv/Lq1rE+fxGj0SXzX2YlRm1vCubFU/102wK0TT2i+oPf2bALQ==", + "requires": { + "debug": "^4.3.2" + } + }, + "@serialport/binding-mock": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/@serialport/binding-mock/-/binding-mock-10.1.0.tgz", + "integrity": "sha512-NPj1fGDkUJbSElBnU0mVi2Hw45cXh4Za6gfU3C854JmhdvpGzehiDjOYm8iJB3bCQZMs3wzaYLdE8o8/wAUSEw==", + "requires": { + "@serialport/binding-abstract": "10.1.0", + "debug": "^4.3.2" + } + }, + "@serialport/bindings-cpp": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/@serialport/bindings-cpp/-/bindings-cpp-10.4.0.tgz", + "integrity": "sha512-vV5fadfzovV2iZ9xWjlQOdjEL0U0y5ewjdplx1DG5ox+/Q2Mmi8WEbcoKww6AgouU5qLirQGfYDmcWPlcGMdMw==", + "requires": { + "@serialport/binding-abstract": "10.0.1", + "@serialport/parser-readline": "10.0.1", + "debug": "^4.3.2", + "node-addon-api": "^4.3.0", + "node-gyp-build": "^4.3.0" + }, + "dependencies": { + "@serialport/binding-abstract": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@serialport/binding-abstract/-/binding-abstract-10.0.1.tgz", + "integrity": "sha512-FWD/uNrz8V3kaTILQTK05Z1LB/LZin8XZelmX/wd1NNlRFAj6V64MIESWhwUy3iPnL1QriFR1k7URHHx3RRgfg==", + "requires": { + "debug": "^4.3.2" + } + } + } + }, + "@serialport/parser-byte-length": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@serialport/parser-byte-length/-/parser-byte-length-10.0.1.tgz", + "integrity": "sha512-uOQa0KEGT7IIGSWCN53NE5ZYaWoeeGLDCSX+ssDadyQxy47hMHuP/JotdWqHg7lDwxUHe0tDl4SOEeEnDx1l6A==" + }, + "@serialport/parser-cctalk": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@serialport/parser-cctalk/-/parser-cctalk-10.0.1.tgz", + "integrity": "sha512-boVr8akjX/7iCtMHeFT16ek4m0/oV9YA6A2mstVCpKle2op42qByx3jY5RzQ52c13oQvq1E6tG0lWJrzdTK+Yw==" + }, + "@serialport/parser-delimiter": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-10.0.1.tgz", + "integrity": "sha512-B0c6dm9UCpRU/LhkvRFL3OSbs69VqWU7mjW7tM109JDNS+vw8uJPumXz8Giub6D0xl90J7euH6tBTqERk7048Q==" + }, + "@serialport/parser-inter-byte-timeout": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@serialport/parser-inter-byte-timeout/-/parser-inter-byte-timeout-10.0.1.tgz", + "integrity": "sha512-awX0bekMZkjb+kjBHsnizAXNfc/grxIqEKdy9Etc6KhgSmratRnjGa7J0rPFP4bTzYWp5sOqlI0ALwBnWCXedA==" + }, + "@serialport/parser-readline": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-10.0.1.tgz", + "integrity": "sha512-jdKPNka/Nn17k89T5UIyis39EaZHQCmq+83s0icBt2iPBlX8+BrJAUBe8myFpuT22qskTVNzFoTMPOp8pjK/yw==", + "requires": { + "@serialport/parser-delimiter": "10.0.1" + } + }, + "@serialport/parser-ready": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@serialport/parser-ready/-/parser-ready-10.0.1.tgz", + "integrity": "sha512-4hVDrKNJBd0wcCfa1qQAk+MM6mVWc9oIbUPEKJkWdBrrWOqYacx2UpvQWd+3YGJ04hFqEv1feOSaH3/1hUifEg==" + }, + "@serialport/parser-regex": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@serialport/parser-regex/-/parser-regex-10.0.1.tgz", + "integrity": "sha512-l8ECuUsan33x5pirQZodlmw0q70Jcxy+oHnXJaqchBTRCbtXlE7+PMFJnmNoIHGqDwt0XALbwpvKcnNBrgvT1g==" + }, + "@serialport/stream": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/@serialport/stream/-/stream-10.1.0.tgz", + "integrity": "sha512-0whwMF9ZYvB4Adjf0dDxUh6rLTuuqNzMkNbuC9EbKNaduUTBzssdbzQVHqa+srGUbmJgy/NKCukqWrUSyNcCPA==", + "requires": { + "debug": "^4.3.2" + } + }, + "debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node-addon-api": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", + "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==" + }, + "node-gyp-build": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.3.0.tgz", + "integrity": "sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==" + }, + "promirepl": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promirepl/-/promirepl-2.0.1.tgz", + "integrity": "sha512-CUYuyU858eJ4OuQLKhJTE1Mijb1zxuAOnmVSYysau1k58zxT9qf0bJb/3QYrYadzmyuLn4EB82Oki+uHmWqC0w==" + }, + "serialport": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/serialport/-/serialport-10.1.0.tgz", + "integrity": "sha512-qRTpa85UdRdYKeuUrmo1bK26jAbxpUi9/WfBVlWQBgn58pAKEf2FyAsnn+uYCEszJbycr+jOqSxHM2wRLHRilQ==", + "requires": { + "@serialport/binding-mock": "10.1.0", + "@serialport/bindings-cpp": "10.4.0", + "@serialport/parser-byte-length": "10.0.1", + "@serialport/parser-cctalk": "10.0.1", + "@serialport/parser-delimiter": "10.0.1", + "@serialport/parser-inter-byte-timeout": "10.0.1", + "@serialport/parser-readline": "10.0.1", + "@serialport/parser-ready": "10.0.1", + "@serialport/parser-regex": "10.0.1", + "@serialport/stream": "10.1.0", + "debug": "^4.3.2" + } + }, + "typescript": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", + "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", + "dev": true + } + } } diff --git a/packages/repl/package.json b/packages/repl/package.json index d69a7fa92..d5ccf18a7 100644 --- a/packages/repl/package.json +++ b/packages/repl/package.json @@ -1,9 +1,13 @@ { "name": "@serialport/repl", "version": "10.1.0", - "main": "lib", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "scripts": { + "build": "tsc --build tsconfig-build.json" + }, "bin": { - "serialport-repl": "./lib/index.js" + "serialport-repl": "./dist/index.js" }, "dependencies": { "promirepl": "^2.0.1", @@ -20,5 +24,8 @@ "type": "git", "url": "git://github.com/serialport/node-serialport.git" }, - "funding": "https://opencollective.com/serialport/donate" + "funding": "https://opencollective.com/serialport/donate", + "devDependencies": { + "typescript": "^4.5.5" + } } diff --git a/packages/repl/tsconfig-build.json b/packages/repl/tsconfig-build.json new file mode 100644 index 000000000..8df39a2a4 --- /dev/null +++ b/packages/repl/tsconfig-build.json @@ -0,0 +1,15 @@ +{ + "extends": "../../tsconfig-build.json", + "compilerOptions": { + "rootDir": "lib", + "outDir": "dist", + }, + "exclude": [ + "node_modules", + "**/*.test.ts", + "dist", + ], + "references": [ + { "path": "../serialport/tsconfig-build.json"} + ] +} diff --git a/packages/repl/tsconfig.json b/packages/repl/tsconfig.json new file mode 100644 index 000000000..4082f16a5 --- /dev/null +++ b/packages/repl/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tsconfig.json" +} diff --git a/packages/serialport/.npmignore b/packages/serialport/.npmignore index 17bea52fe..d10c22a52 100644 --- a/packages/serialport/.npmignore +++ b/packages/serialport/.npmignore @@ -1,11 +1,13 @@ .DS_Store -*.test.js CHANGELOG.md examples/ node_modules -test-arduino test-config.json test-electron test-manual test/ UPGRADE_GUIDE.md +lib +tsconfig.json +tsconfig-build.json +tsconfig-build.tsbuildinfo diff --git a/packages/serialport/examples/drain.js b/packages/serialport/examples/drain.js index c9160530f..c39e457c7 100644 --- a/packages/serialport/examples/drain.js +++ b/packages/serialport/examples/drain.js @@ -1,7 +1,7 @@ /* eslint-disable node/no-missing-require */ -const SerialPort = require('serialport') -const port = new SerialPort('/dev/my-great-device') +const { SerialPort } = require('serialport') +const port = new SerialPort({ path: '/dev/my-great-device', baudRate: 9600 }) port.on('open', () => { console.log('port opened') diff --git a/packages/serialport/examples/mocking.js b/packages/serialport/examples/mocking.js index 2295ddc44..4aa9b0671 100644 --- a/packages/serialport/examples/mocking.js +++ b/packages/serialport/examples/mocking.js @@ -2,8 +2,8 @@ // Load Serialport with mock bindings // const SerialPort = require('../test'); // from inside the serialport repo -const SerialPort = require('serialport/test') // when installed as a package -const MockBinding = SerialPort.Binding +const { SerialPortMock: SerialPort } = require('serialport') // when installed as a package +const { MockBinding } = SerialPort const portPath = 'COM_ANYTHING' @@ -18,7 +18,7 @@ const portPath = 'COM_ANYTHING' // Create a port MockBinding.createPort(portPath, { echo: false, record: false }) -const port = new SerialPort(portPath) +const port = new SerialPort({ path: portPath, baudRate: 9600 }) port.on('open', () => { console.log('Port opened:\t', port.path) }) diff --git a/packages/serialport/examples/open-callback.js b/packages/serialport/examples/open-callback.js index e9ffbc26a..60e555867 100644 --- a/packages/serialport/examples/open-callback.js +++ b/packages/serialport/examples/open-callback.js @@ -1,8 +1,8 @@ /* eslint-disable node/no-missing-require */ // Constructor callback example -const SerialPort = require('serialport') -const port = new SerialPort('/dev/tty-usbserial1', () => { +const { SerialPort } = require('serialport') +const port = new SerialPort({ path: '/dev/tty-usbserial1', baudRate: 9600 }, () => { console.log('Port Opened') }) diff --git a/packages/serialport/examples/open-event.js b/packages/serialport/examples/open-event.js index e1bbb47e0..8385646ba 100644 --- a/packages/serialport/examples/open-event.js +++ b/packages/serialport/examples/open-event.js @@ -1,8 +1,8 @@ /* eslint-disable node/no-missing-require */ // Open event example -const SerialPort = require('serialport') -const port = new SerialPort('/dev/tty-usbserial1') +const { SerialPort } = require('serialport') +const port = new SerialPort({ path: '/dev/tty-usbserial1', baudRate: 9600 }) port.on('open', () => { console.log('Port Opened') diff --git a/packages/serialport/examples/open-no-auto-open.js b/packages/serialport/examples/open-no-auto-open.js index 6feabbe8b..c303c7b56 100644 --- a/packages/serialport/examples/open-no-auto-open.js +++ b/packages/serialport/examples/open-no-auto-open.js @@ -1,8 +1,8 @@ /* eslint-disable node/no-missing-require */ // When disabling open immediately. -const SerialPort = require('serialport') -const port = new SerialPort('/dev/tty-usbserial1', { autoOpen: false }) +const { SerialPort } = require('serialport') +const port = new SerialPort({ path: '/dev/tty-usbserial1', autoOpen: false }) // If you write before the port is opened the write will be queued // Since there is no callback any write errors will be emitted on an error event diff --git a/packages/serialport/examples/readline.js b/packages/serialport/examples/readline.js index ec37da808..080da069c 100644 --- a/packages/serialport/examples/readline.js +++ b/packages/serialport/examples/readline.js @@ -2,15 +2,15 @@ // Use a Readline parser -const SerialPort = require('serialport') -const parsers = SerialPort.parsers +const { SerialPort, ReadlineParser } = require('serialport') // Use a `\r\n` as a line terminator -const parser = new parsers.Readline({ +const parser = new ReadlineParser({ delimiter: '\r\n', }) -const port = new SerialPort('/dev/tty-usbserial1', { +const port = new SerialPort({ + path: '/dev/tty-usbserial1', baudRate: 57600, }) diff --git a/packages/serialport/examples/socketio/index.js b/packages/serialport/examples/socketio/index.js index 356be17c5..b0178d34d 100644 --- a/packages/serialport/examples/socketio/index.js +++ b/packages/serialport/examples/socketio/index.js @@ -12,9 +12,10 @@ const http = require('http').Server(app) const io = require('socket.io')(http) const tcpPort = process.env.PORT || 3000 -const SerialPort = require('serialport') +const { SerialPort } = require('serialport') -const port = new SerialPort('/dev/cu.usbmodem1411', { +const port = new SerialPort({ + path: '/dev/cu.usbmodem1411', baudRate: 9600, }) diff --git a/packages/serialport/examples/socketio/package.json b/packages/serialport/examples/socketio/package.json index d90e98b04..c56dbf801 100644 --- a/packages/serialport/examples/socketio/package.json +++ b/packages/serialport/examples/socketio/package.json @@ -5,7 +5,7 @@ "main": "index.js", "dependencies": { "express": "^4.14.1", - "serialport": "^7.1.5", + "serialport": "^10.0.0", "socket.io": "^2.2.0" }, "devDependencies": {}, diff --git a/packages/serialport/lib/index.js b/packages/serialport/lib/index.js deleted file mode 100644 index 892eca7dd..000000000 --- a/packages/serialport/lib/index.js +++ /dev/null @@ -1,15 +0,0 @@ -const SerialPort = require('@serialport/stream') -const { autoDetect } = require('@serialport/bindings-cpp') -const parsers = require('./parsers') - -/** - * @type {AbstractBinding} - */ -SerialPort.Binding = autoDetect() - -/** - * @type {Parsers} - */ -SerialPort.parsers = parsers - -module.exports = SerialPort diff --git a/packages/serialport/lib/serialport.test.js b/packages/serialport/lib/index.test.ts similarity index 63% rename from packages/serialport/lib/serialport.test.js rename to packages/serialport/lib/index.test.ts index bcd37c175..ba868b986 100644 --- a/packages/serialport/lib/serialport.test.js +++ b/packages/serialport/lib/index.test.ts @@ -1,59 +1,58 @@ -const crypto = require('crypto') -const SerialPort = require('../') +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { randomBytes } from 'crypto' +import { SerialPort as SerialPortAutoDetect, SerialPortMock } from './' +import { assert } from '../../../test/assert' +import { testOnPlatform } from '../../../test/testOnPlatform' -let platform -switch (process.platform) { - case 'win32': - case 'darwin': - case 'linux': - platform = process.platform - break - default: - throw new Error(`Unknown platform "${process.platform}"`) +const platform = process.platform +if (platform !== 'win32' && platform !== 'darwin' && platform !== 'linux') { + throw new Error(`Unknown platform "${process.platform}"`) } -const readyData = Buffer.from('READY') - -// test everything on our mock binding and natively -const DetectedBinding = SerialPort.Binding -const { MockBinding } = require('@serialport/binding-mock') - -const mockTestPort = '/dev/exists' - -// eslint-disable-next-line no-use-before-define -integrationTest('mock', mockTestPort, MockBinding) - -// eslint-disable-next-line no-use-before-define -integrationTest(platform, process.env.TEST_PORT, DetectedBinding) +const TEST_PORT = process.env.TEST_PORT +const TEST_BAUD = Number(process.env.TEST_BAUDRATE) || 115200 // Be careful to close the ports when you're done with them // Ports are by default exclusively locked so a failure fails all tests -function integrationTest(platform, testPort, Binding) { - const testFeature = makeTestFeature(platform) +testSerialPortClass(SerialPortMock, 'mock', '/dev/exists', 115200) +testSerialPortClass(SerialPortAutoDetect, platform, TEST_PORT, TEST_BAUD) +function testSerialPortClass( + SerialPort: typeof SerialPortAutoDetect | typeof SerialPortMock, + platform: string, + path: string | undefined, + baudRate: number +) { describe(`${platform} SerialPort Integration Tests`, () => { - if (!testPort) { - it(`${platform} tests requires an Arduino loaded with the arduinoEcho program on a serialport set to the TEST_PORT env var`) + if (!path) { + it(`${platform} tests requires a loopback serialport set to the TEST_PORT env var and it's baudrate set on TEST_BAUDRATE (defaults to 115200)`) return } - before(() => { - if (Binding === MockBinding) { - MockBinding.createPort(testPort, { echo: true, readyData }) + beforeEach(() => { + if (platform === 'mock') { + SerialPortMock.MockBinding.createPort('/dev/exists', { echo: true, maxReadSize: 50 }) + } + }) + + afterEach(() => { + if (platform === 'mock') { + SerialPortMock.MockBinding.reset() } - SerialPort.Binding = Binding }) + const openOptions = { path, baudRate } + describe('static Method', () => { describe('.list', () => { it('contains the test port', async () => { - function normalizePath(name) { + function normalizePath(name: string) { const parts = name.split('.') return parts[parts.length - 1].toLowerCase() } const ports = await SerialPort.list() - const foundPort = ports.some(port => normalizePath(port.path) === normalizePath(testPort)) + const foundPort = ports.some(port => normalizePath(port.path) === normalizePath(path)) assert.isTrue(foundPort) }) }) @@ -61,14 +60,14 @@ function integrationTest(platform, testPort, Binding) { describe('constructor', () => { it('provides an error in callback when trying to open an invalid port', done => { - new SerialPort('COMBAD', err => { + new SerialPort({ ...openOptions, path: 'COMBAD' }, err => { assert.instanceOf(err, Error) done() }) }) it('emits an error event when trying to open an invalid port', done => { - const port = new SerialPort('COM99') + const port = new SerialPort({ ...openOptions, path: 'COM99' }) port.on('error', err => { assert.instanceOf(err, Error) done() @@ -78,7 +77,7 @@ function integrationTest(platform, testPort, Binding) { describe('opening and closing', () => { it('can open and close', done => { - const port = new SerialPort(testPort) + const port = new SerialPort(openOptions) port.on('open', () => { assert.isTrue(port.isOpen) port.close() @@ -90,7 +89,7 @@ function integrationTest(platform, testPort, Binding) { }) it('cannot be opened again after open', done => { - const port = new SerialPort(testPort, err => { + const port = new SerialPort(openOptions, err => { assert.isNull(err) port.open(err => { assert.instanceOf(err, Error) @@ -100,7 +99,7 @@ function integrationTest(platform, testPort, Binding) { }) it('cannot be opened while opening', done => { - const port = new SerialPort(testPort, { autoOpen: false }) + const port = new SerialPort({ ...openOptions, autoOpen: false }) port.open(err => { assert.isNull(err) }) @@ -113,7 +112,7 @@ function integrationTest(platform, testPort, Binding) { }) it('can open and close ports repetitively', done => { - const port = new SerialPort(testPort, { autoOpen: false }) + const port = new SerialPort({ ...openOptions, autoOpen: false }) port.open(err => { assert.isNull(err) port.close(err => { @@ -128,20 +127,22 @@ function integrationTest(platform, testPort, Binding) { it('can be read after closing and opening', function (done) { this.timeout(6000) - const port = new SerialPort(testPort, { autoOpen: false }) + const port = new SerialPort({ ...openOptions, autoOpen: false }) port.on('error', done) port.open(err => { assert.isNull(err) + port.write('a') }) port.once('data', () => { port.close() }) - port.once('close', err => { + port.once('close', (err: null | Error) => { assert.isNull(err) port.open(err => { assert.isNull(err) + port.write('a') }) port.once('data', () => { port.close(done) @@ -150,7 +151,7 @@ function integrationTest(platform, testPort, Binding) { }) it('errors if closing during a write', done => { - const port = new SerialPort(testPort, { autoOpen: false }) + const port = new SerialPort({ ...openOptions, autoOpen: false }) port.open(() => { port.on('error', err => { assert.instanceOf(err, Error) @@ -163,8 +164,8 @@ function integrationTest(platform, testPort, Binding) { }) describe('#update', () => { - testFeature('port.update-baudrate', 'allows changing the baud rate of an open port', done => { - const port = new SerialPort(testPort, () => { + testOnPlatform(['linux', 'darwin', 'mock'], 'allows changing the baud rate of an open port', done => { + const port = new SerialPort(openOptions, () => { port.update({ baudRate: 57600 }, err => { assert.isNull(err) port.close(done) @@ -177,21 +178,17 @@ function integrationTest(platform, testPort, Binding) { it('2k test', function (done) { this.timeout(20000) // 2k of random data - const input = crypto.randomBytes(1024 * 2) - const port = new SerialPort(testPort) + const input = Buffer.from(randomBytes(1024).toString('hex')) + const port = new SerialPort(openOptions) port.on('error', done) - const ready = port.pipe(new SerialPort.parsers.Ready({ delimiter: readyData })) - - // this will trigger from the "READY" the arduino sends when it's... ready - ready.on('ready', () => { - port.write(input) - }) + port.write(input) - const readData = Buffer.alloc(input.length, 0) - let bytesRead = 0 - ready.on('data', data => { - bytesRead += data.copy(readData, bytesRead) - if (bytesRead >= input.length) { + let readData = Buffer.alloc(0) + port.on('data', data => { + readData = Buffer.concat([readData, data]) + // console.log('got data', data.length, 'read data', readData.length) + if (readData.length >= input.length) { + console.log('probably done') try { assert.equal(readData.length, input.length, 'write length matches') assert.deepEqual(readData, input, 'read data matches expected readData') @@ -206,7 +203,7 @@ function integrationTest(platform, testPort, Binding) { describe('#flush', () => { it('discards any received data', done => { - const port = new SerialPort(testPort) + const port = new SerialPort(openOptions) port.on('open', () => process.nextTick(() => { port.flush(err => { @@ -230,10 +227,10 @@ function integrationTest(platform, testPort, Binding) { ) }) it('deals with flushing during a read', done => { - const port = new SerialPort(testPort) + const port = new SerialPort(openOptions) port.on('error', done) - const ready = port.pipe(new SerialPort.parsers.Ready({ delimiter: 'READY' })) - ready.on('ready', () => { + port.on('data', () => {}) // enter flowing mode + port.once('data', () => { // we should have a pending read now since we're in flowing mode port.flush(err => { try { @@ -244,12 +241,13 @@ function integrationTest(platform, testPort, Binding) { port.close(done) }) }) + port.write('flush') // trigger the flush }) }) describe('#drain', () => { it('waits for in progress or queued writes to finish', done => { - const port = new SerialPort(testPort) + const port = new SerialPort(openOptions) port.on('error', done) let finishedWrite = false port.write(Buffer.alloc(10), () => { @@ -258,7 +256,7 @@ function integrationTest(platform, testPort, Binding) { port.drain(err => { assert.isNull(err) assert.isTrue(finishedWrite) - done() + port.close(done) }) }) }) diff --git a/packages/serialport/lib/index.ts b/packages/serialport/lib/index.ts new file mode 100644 index 000000000..0bda64dbb --- /dev/null +++ b/packages/serialport/lib/index.ts @@ -0,0 +1,12 @@ +export * from '@serialport/parser-byte-length' +export * from '@serialport/parser-cctalk' +export * from '@serialport/parser-delimiter' +export * from '@serialport/parser-inter-byte-timeout' +export * from '@serialport/parser-packet-length' +export * from '@serialport/parser-readline' +export * from '@serialport/parser-ready' +export * from '@serialport/parser-regex' +export * from '@serialport/parser-slip-encoder' +export * from '@serialport/parser-spacepacket' +export * from './serialport-mock' +export * from './serialport' diff --git a/packages/serialport/lib/parsers.js b/packages/serialport/lib/parsers.js deleted file mode 100644 index 6605c189b..000000000 --- a/packages/serialport/lib/parsers.js +++ /dev/null @@ -1,9 +0,0 @@ -module.exports = { - ByteLength: require('@serialport/parser-byte-length'), - CCTalk: require('@serialport/parser-cctalk'), - Delimiter: require('@serialport/parser-delimiter'), - InterByteTimeout: require('@serialport/parser-inter-byte-timeout'), - Readline: require('@serialport/parser-readline'), - Ready: require('@serialport/parser-ready'), - Regex: require('@serialport/parser-regex'), -} diff --git a/packages/serialport/lib/serialport-mock.ts b/packages/serialport/lib/serialport-mock.ts new file mode 100644 index 000000000..7c947825b --- /dev/null +++ b/packages/serialport/lib/serialport-mock.ts @@ -0,0 +1,17 @@ +import { ErrorCallback, OpenOptions, SerialPortStream } from '@serialport/stream' +import { MockBinding, MockBindingInterface } from '@serialport/binding-mock' + +export type SerialPortMockOpenOptions = Omit, 'binding'> + +export class SerialPortMock extends SerialPortStream { + static list = MockBinding.list + static readonly MockBinding = MockBinding + + constructor(options: SerialPortMockOpenOptions, openCallback?: ErrorCallback) { + const opts: OpenOptions = { + binding: MockBinding, + ...options, + } + super(opts, openCallback) + } +} diff --git a/packages/serialport/lib/serialport.ts b/packages/serialport/lib/serialport.ts new file mode 100644 index 000000000..fc411623a --- /dev/null +++ b/packages/serialport/lib/serialport.ts @@ -0,0 +1,68 @@ +import { ErrorCallback, OpenOptions, SerialPortStream } from '@serialport/stream' +import { autoDetect, AutoDetectTypes } from '@serialport/bindings-cpp' + +const DetectedBinding = autoDetect() + +export type SerialPortOpenOptions = Omit, 'binding'> + +export class SerialPort extends SerialPortStream { + /** + * Retrieves a list of available serial ports with metadata. Only the `path` is guaranteed. If unavailable the other fields will be undefined. The `path` is either the path or an identifier (eg `COM1`) used to open the SerialPort. + * + * We make an effort to identify the hardware attached and have consistent results between systems. Linux and OS X are mostly consistent. Windows relies on 3rd party device drivers for the information and is unable to guarantee the information. On windows If you have a USB connected device can we provide a serial number otherwise it will be `undefined`. The `pnpId` and `locationId` are not the same or present on all systems. The examples below were run with the same Arduino Uno. + * Resolves with the list of available serial ports. + * @example + ```js + // OSX example port + { + path: '/dev/tty.usbmodem1421', + manufacturer: 'Arduino (www.arduino.cc)', + serialNumber: '752303138333518011C1', + pnpId: undefined, + locationId: '14500000', + productId: '0043', + vendorId: '2341' + } + + // Linux example port + { + path: '/dev/ttyACM0', + manufacturer: 'Arduino (www.arduino.cc)', + serialNumber: '752303138333518011C1', + pnpId: 'usb-Arduino__www.arduino.cc__0043_752303138333518011C1-if00', + locationId: undefined, + productId: '0043', + vendorId: '2341' + } + + // Windows example port + { + path: 'COM3', + manufacturer: 'Arduino LLC (www.arduino.cc)', + serialNumber: '752303138333518011C1', + pnpId: 'USB\\VID_2341&PID_0043\\752303138333518011C1', + locationId: 'Port_#0003.Hub_#0001', + productId: '0043', + vendorId: '2341' + } + ``` + + ```js + var SerialPort = require('serialport'); + + // promise approach + SerialPort.list() + .then(ports) {...}); + .catch(err) {...}); + ``` + */ + static list = DetectedBinding.list + + constructor(options: SerialPortOpenOptions, openCallback?: ErrorCallback) { + const opts: OpenOptions = { + binding: DetectedBinding, + ...options, + } + super(opts, openCallback) + } +} diff --git a/packages/serialport/package-lock.json b/packages/serialport/package-lock.json index e6e127650..54199f531 100644 --- a/packages/serialport/package-lock.json +++ b/packages/serialport/package-lock.json @@ -1,175 +1,393 @@ { - "name": "serialport", - "version": "10.1.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "serialport", - "version": "10.1.0", - "license": "MIT", - "dependencies": { - "@serialport/bindings-cpp": "10.4.0", - "debug": "^4.3.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://opencollective.com/serialport/donate" - } - }, - "node_modules/@serialport/binding-abstract": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@serialport/binding-abstract/-/binding-abstract-10.0.1.tgz", - "integrity": "sha512-FWD/uNrz8V3kaTILQTK05Z1LB/LZin8XZelmX/wd1NNlRFAj6V64MIESWhwUy3iPnL1QriFR1k7URHHx3RRgfg==", - "dependencies": { - "debug": "^4.3.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://opencollective.com/serialport/donate" - } - }, - "node_modules/@serialport/bindings-cpp": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/@serialport/bindings-cpp/-/bindings-cpp-10.4.0.tgz", - "integrity": "sha512-vV5fadfzovV2iZ9xWjlQOdjEL0U0y5ewjdplx1DG5ox+/Q2Mmi8WEbcoKww6AgouU5qLirQGfYDmcWPlcGMdMw==", - "hasInstallScript": true, - "dependencies": { - "@serialport/binding-abstract": "10.0.1", - "@serialport/parser-readline": "10.0.1", - "debug": "^4.3.2", - "node-addon-api": "^4.3.0", - "node-gyp-build": "^4.3.0" - }, - "engines": { - "node": ">=12.17.0 <13.0 || >=14.0.0" - }, - "funding": { - "url": "https://opencollective.com/serialport/donate" - } - }, - "node_modules/@serialport/parser-delimiter": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-10.0.1.tgz", - "integrity": "sha512-B0c6dm9UCpRU/LhkvRFL3OSbs69VqWU7mjW7tM109JDNS+vw8uJPumXz8Giub6D0xl90J7euH6tBTqERk7048Q==", - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://opencollective.com/serialport/donate" - } - }, - "node_modules/@serialport/parser-readline": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-10.0.1.tgz", - "integrity": "sha512-jdKPNka/Nn17k89T5UIyis39EaZHQCmq+83s0icBt2iPBlX8+BrJAUBe8myFpuT22qskTVNzFoTMPOp8pjK/yw==", - "dependencies": { - "@serialport/parser-delimiter": "10.0.1" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://opencollective.com/serialport/donate" - } - }, - "node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/node-addon-api": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", - "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==" - }, - "node_modules/node-gyp-build": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.3.0.tgz", - "integrity": "sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==", - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" - } - } - }, - "dependencies": { - "@serialport/binding-abstract": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@serialport/binding-abstract/-/binding-abstract-10.0.1.tgz", - "integrity": "sha512-FWD/uNrz8V3kaTILQTK05Z1LB/LZin8XZelmX/wd1NNlRFAj6V64MIESWhwUy3iPnL1QriFR1k7URHHx3RRgfg==", - "requires": { - "debug": "^4.3.2" - } - }, - "@serialport/bindings-cpp": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/@serialport/bindings-cpp/-/bindings-cpp-10.4.0.tgz", - "integrity": "sha512-vV5fadfzovV2iZ9xWjlQOdjEL0U0y5ewjdplx1DG5ox+/Q2Mmi8WEbcoKww6AgouU5qLirQGfYDmcWPlcGMdMw==", - "requires": { - "@serialport/binding-abstract": "10.0.1", - "@serialport/parser-readline": "10.0.1", - "debug": "^4.3.2", - "node-addon-api": "^4.3.0", - "node-gyp-build": "^4.3.0" - } - }, - "@serialport/parser-delimiter": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-10.0.1.tgz", - "integrity": "sha512-B0c6dm9UCpRU/LhkvRFL3OSbs69VqWU7mjW7tM109JDNS+vw8uJPumXz8Giub6D0xl90J7euH6tBTqERk7048Q==" - }, - "@serialport/parser-readline": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-10.0.1.tgz", - "integrity": "sha512-jdKPNka/Nn17k89T5UIyis39EaZHQCmq+83s0icBt2iPBlX8+BrJAUBe8myFpuT22qskTVNzFoTMPOp8pjK/yw==", - "requires": { - "@serialport/parser-delimiter": "10.0.1" - } - }, - "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node-addon-api": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", - "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==" - }, - "node-gyp-build": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.3.0.tgz", - "integrity": "sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==" - } - } + "name": "serialport", + "version": "10.1.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "serialport", + "version": "10.1.0", + "license": "MIT", + "dependencies": { + "@serialport/binding-mock": "10.1.0", + "@serialport/bindings-cpp": "10.6.1", + "@serialport/parser-byte-length": "10.0.1", + "@serialport/parser-cctalk": "10.0.1", + "@serialport/parser-delimiter": "10.0.1", + "@serialport/parser-inter-byte-timeout": "10.0.1", + "@serialport/parser-packet-length": "10.0.0", + "@serialport/parser-readline": "10.0.1", + "@serialport/parser-ready": "10.0.1", + "@serialport/parser-regex": "10.0.1", + "@serialport/parser-slip-encoder": "10.0.1", + "@serialport/parser-spacepacket": "10.0.1", + "@serialport/stream": "10.1.0", + "debug": "^4.3.2" + }, + "devDependencies": { + "typescript": "^4.5.5" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/binding-abstract": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/@serialport/binding-abstract/-/binding-abstract-10.1.0.tgz", + "integrity": "sha512-P2KUIa6LTX0GKRzgwcEqO7kIG7yI66GuxV1hBv/Lq1rE+fxGj0SXzX2YlRm1vCubFU/102wK0TT2i+oPf2bALQ==", + "dependencies": { + "debug": "^4.3.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/binding-mock": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/@serialport/binding-mock/-/binding-mock-10.1.0.tgz", + "integrity": "sha512-NPj1fGDkUJbSElBnU0mVi2Hw45cXh4Za6gfU3C854JmhdvpGzehiDjOYm8iJB3bCQZMs3wzaYLdE8o8/wAUSEw==", + "dependencies": { + "@serialport/binding-abstract": "10.1.0", + "debug": "^4.3.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/bindings-cpp": { + "version": "10.6.1", + "resolved": "https://registry.npmjs.org/@serialport/bindings-cpp/-/bindings-cpp-10.6.1.tgz", + "integrity": "sha512-wlgiCoImyA0siK0Z1ziXFDv6Ua3Tko9vMyNPAMW/Ywk18vKVKPYvvGlYxhbdK9LGzGdqVXGWmEW15R/S+anzQQ==", + "hasInstallScript": true, + "dependencies": { + "@serialport/bindings-interface": "1.2.0", + "@serialport/parser-readline": "10.0.1", + "debug": "^4.3.2", + "node-addon-api": "^4.3.0", + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=12.17.0 <13.0 || >=14.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/bindings-interface": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@serialport/bindings-interface/-/bindings-interface-1.2.0.tgz", + "integrity": "sha512-TLg6z0wyEdfVBIGmRwv0oru7Ijdc3VhH5SDv+q7UXuXvNh+mEpsFedHG205GSGVXbnF1xlCN5cBSMloI74N/gQ==", + "engines": { + "node": "^12.22 || ^14.13 || >=16" + } + }, + "node_modules/@serialport/parser-byte-length": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@serialport/parser-byte-length/-/parser-byte-length-10.0.1.tgz", + "integrity": "sha512-uOQa0KEGT7IIGSWCN53NE5ZYaWoeeGLDCSX+ssDadyQxy47hMHuP/JotdWqHg7lDwxUHe0tDl4SOEeEnDx1l6A==", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-cctalk": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@serialport/parser-cctalk/-/parser-cctalk-10.0.1.tgz", + "integrity": "sha512-boVr8akjX/7iCtMHeFT16ek4m0/oV9YA6A2mstVCpKle2op42qByx3jY5RzQ52c13oQvq1E6tG0lWJrzdTK+Yw==", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-delimiter": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-10.0.1.tgz", + "integrity": "sha512-B0c6dm9UCpRU/LhkvRFL3OSbs69VqWU7mjW7tM109JDNS+vw8uJPumXz8Giub6D0xl90J7euH6tBTqERk7048Q==", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-inter-byte-timeout": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@serialport/parser-inter-byte-timeout/-/parser-inter-byte-timeout-10.0.1.tgz", + "integrity": "sha512-awX0bekMZkjb+kjBHsnizAXNfc/grxIqEKdy9Etc6KhgSmratRnjGa7J0rPFP4bTzYWp5sOqlI0ALwBnWCXedA==", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-packet-length": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-packet-length/-/parser-packet-length-10.0.0.tgz", + "integrity": "sha512-ps8PfJf2rDnOtWhodx0b8YhEZgn/BvdEslFWcMDnreMWHSc4rl0bSdg91sti7eXsmIIMYnPlg8RbORhkGpJsew==", + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/@serialport/parser-readline": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-10.0.1.tgz", + "integrity": "sha512-jdKPNka/Nn17k89T5UIyis39EaZHQCmq+83s0icBt2iPBlX8+BrJAUBe8myFpuT22qskTVNzFoTMPOp8pjK/yw==", + "dependencies": { + "@serialport/parser-delimiter": "10.0.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-ready": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@serialport/parser-ready/-/parser-ready-10.0.1.tgz", + "integrity": "sha512-4hVDrKNJBd0wcCfa1qQAk+MM6mVWc9oIbUPEKJkWdBrrWOqYacx2UpvQWd+3YGJ04hFqEv1feOSaH3/1hUifEg==", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-regex": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@serialport/parser-regex/-/parser-regex-10.0.1.tgz", + "integrity": "sha512-l8ECuUsan33x5pirQZodlmw0q70Jcxy+oHnXJaqchBTRCbtXlE7+PMFJnmNoIHGqDwt0XALbwpvKcnNBrgvT1g==", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-slip-encoder": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@serialport/parser-slip-encoder/-/parser-slip-encoder-10.0.1.tgz", + "integrity": "sha512-YOIh9FniT1vVjwfpRUNSjy2SNIKohBSNfy7EObepiHMrfzy4/9E+fjWer8yGzFKs8f35stmv9PUBu4Os0oVkbA==", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-spacepacket": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@serialport/parser-spacepacket/-/parser-spacepacket-10.0.1.tgz", + "integrity": "sha512-4Z0bwOQeBa26jzkVpbt4oVcVvXu37RP8ve1cAM/Hv9EwC883L3lc1HPqY3jJRALYHk3VCULI0h1/SoQwBV3WnQ==", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/stream": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/@serialport/stream/-/stream-10.1.0.tgz", + "integrity": "sha512-0whwMF9ZYvB4Adjf0dDxUh6rLTuuqNzMkNbuC9EbKNaduUTBzssdbzQVHqa+srGUbmJgy/NKCukqWrUSyNcCPA==", + "dependencies": { + "debug": "^4.3.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/node-addon-api": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", + "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==" + }, + "node_modules/node-gyp-build": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.3.0.tgz", + "integrity": "sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/typescript": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", + "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + } + }, + "dependencies": { + "@serialport/binding-abstract": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/@serialport/binding-abstract/-/binding-abstract-10.1.0.tgz", + "integrity": "sha512-P2KUIa6LTX0GKRzgwcEqO7kIG7yI66GuxV1hBv/Lq1rE+fxGj0SXzX2YlRm1vCubFU/102wK0TT2i+oPf2bALQ==", + "requires": { + "debug": "^4.3.2" + } + }, + "@serialport/binding-mock": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/@serialport/binding-mock/-/binding-mock-10.1.0.tgz", + "integrity": "sha512-NPj1fGDkUJbSElBnU0mVi2Hw45cXh4Za6gfU3C854JmhdvpGzehiDjOYm8iJB3bCQZMs3wzaYLdE8o8/wAUSEw==", + "requires": { + "@serialport/binding-abstract": "10.1.0", + "debug": "^4.3.2" + } + }, + "@serialport/bindings-cpp": { + "version": "10.6.1", + "resolved": "https://registry.npmjs.org/@serialport/bindings-cpp/-/bindings-cpp-10.6.1.tgz", + "integrity": "sha512-wlgiCoImyA0siK0Z1ziXFDv6Ua3Tko9vMyNPAMW/Ywk18vKVKPYvvGlYxhbdK9LGzGdqVXGWmEW15R/S+anzQQ==", + "requires": { + "@serialport/bindings-interface": "1.2.0", + "@serialport/parser-readline": "10.0.1", + "debug": "^4.3.2", + "node-addon-api": "^4.3.0", + "node-gyp-build": "^4.3.0" + } + }, + "@serialport/bindings-interface": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@serialport/bindings-interface/-/bindings-interface-1.2.0.tgz", + "integrity": "sha512-TLg6z0wyEdfVBIGmRwv0oru7Ijdc3VhH5SDv+q7UXuXvNh+mEpsFedHG205GSGVXbnF1xlCN5cBSMloI74N/gQ==" + }, + "@serialport/parser-byte-length": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@serialport/parser-byte-length/-/parser-byte-length-10.0.1.tgz", + "integrity": "sha512-uOQa0KEGT7IIGSWCN53NE5ZYaWoeeGLDCSX+ssDadyQxy47hMHuP/JotdWqHg7lDwxUHe0tDl4SOEeEnDx1l6A==" + }, + "@serialport/parser-cctalk": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@serialport/parser-cctalk/-/parser-cctalk-10.0.1.tgz", + "integrity": "sha512-boVr8akjX/7iCtMHeFT16ek4m0/oV9YA6A2mstVCpKle2op42qByx3jY5RzQ52c13oQvq1E6tG0lWJrzdTK+Yw==" + }, + "@serialport/parser-delimiter": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-10.0.1.tgz", + "integrity": "sha512-B0c6dm9UCpRU/LhkvRFL3OSbs69VqWU7mjW7tM109JDNS+vw8uJPumXz8Giub6D0xl90J7euH6tBTqERk7048Q==" + }, + "@serialport/parser-inter-byte-timeout": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@serialport/parser-inter-byte-timeout/-/parser-inter-byte-timeout-10.0.1.tgz", + "integrity": "sha512-awX0bekMZkjb+kjBHsnizAXNfc/grxIqEKdy9Etc6KhgSmratRnjGa7J0rPFP4bTzYWp5sOqlI0ALwBnWCXedA==" + }, + "@serialport/parser-packet-length": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-packet-length/-/parser-packet-length-10.0.0.tgz", + "integrity": "sha512-ps8PfJf2rDnOtWhodx0b8YhEZgn/BvdEslFWcMDnreMWHSc4rl0bSdg91sti7eXsmIIMYnPlg8RbORhkGpJsew==" + }, + "@serialport/parser-readline": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-10.0.1.tgz", + "integrity": "sha512-jdKPNka/Nn17k89T5UIyis39EaZHQCmq+83s0icBt2iPBlX8+BrJAUBe8myFpuT22qskTVNzFoTMPOp8pjK/yw==", + "requires": { + "@serialport/parser-delimiter": "10.0.1" + } + }, + "@serialport/parser-ready": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@serialport/parser-ready/-/parser-ready-10.0.1.tgz", + "integrity": "sha512-4hVDrKNJBd0wcCfa1qQAk+MM6mVWc9oIbUPEKJkWdBrrWOqYacx2UpvQWd+3YGJ04hFqEv1feOSaH3/1hUifEg==" + }, + "@serialport/parser-regex": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@serialport/parser-regex/-/parser-regex-10.0.1.tgz", + "integrity": "sha512-l8ECuUsan33x5pirQZodlmw0q70Jcxy+oHnXJaqchBTRCbtXlE7+PMFJnmNoIHGqDwt0XALbwpvKcnNBrgvT1g==" + }, + "@serialport/parser-slip-encoder": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@serialport/parser-slip-encoder/-/parser-slip-encoder-10.0.1.tgz", + "integrity": "sha512-YOIh9FniT1vVjwfpRUNSjy2SNIKohBSNfy7EObepiHMrfzy4/9E+fjWer8yGzFKs8f35stmv9PUBu4Os0oVkbA==" + }, + "@serialport/parser-spacepacket": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@serialport/parser-spacepacket/-/parser-spacepacket-10.0.1.tgz", + "integrity": "sha512-4Z0bwOQeBa26jzkVpbt4oVcVvXu37RP8ve1cAM/Hv9EwC883L3lc1HPqY3jJRALYHk3VCULI0h1/SoQwBV3WnQ==" + }, + "@serialport/stream": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/@serialport/stream/-/stream-10.1.0.tgz", + "integrity": "sha512-0whwMF9ZYvB4Adjf0dDxUh6rLTuuqNzMkNbuC9EbKNaduUTBzssdbzQVHqa+srGUbmJgy/NKCukqWrUSyNcCPA==", + "requires": { + "debug": "^4.3.2" + } + }, + "debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node-addon-api": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", + "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==" + }, + "node-gyp-build": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.3.0.tgz", + "integrity": "sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==" + }, + "typescript": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", + "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", + "dev": true + } + } } diff --git a/packages/serialport/package.json b/packages/serialport/package.json index 1d0856aee..de1a09b64 100644 --- a/packages/serialport/package.json +++ b/packages/serialport/package.json @@ -2,7 +2,11 @@ "name": "serialport", "version": "10.1.0", "description": "Node.js package to access serial ports. Linux, OSX and Windows. Welcome your robotic JavaScript overlords. Better yet, program them!", - "main": "lib", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "scripts": { + "build": "tsc --build tsconfig-build.json" + }, "repository": { "type": "git", "url": "git://github.com/serialport/node-serialport.git" @@ -47,14 +51,17 @@ ], "dependencies": { "@serialport/binding-mock": "10.1.0", - "@serialport/bindings-cpp": "10.4.0", + "@serialport/bindings-cpp": "10.6.1", "@serialport/parser-byte-length": "10.0.1", "@serialport/parser-cctalk": "10.0.1", "@serialport/parser-delimiter": "10.0.1", "@serialport/parser-inter-byte-timeout": "10.0.1", + "@serialport/parser-packet-length": "10.0.0", "@serialport/parser-readline": "10.0.1", "@serialport/parser-ready": "10.0.1", "@serialport/parser-regex": "10.0.1", + "@serialport/parser-slip-encoder": "10.0.1", + "@serialport/parser-spacepacket": "10.0.1", "@serialport/stream": "10.1.0", "debug": "^4.3.2" }, @@ -63,5 +70,8 @@ }, "license": "MIT", "funding": "https://opencollective.com/serialport/donate", - "preferUnplugged": false + "preferUnplugged": false, + "devDependencies": { + "typescript": "^4.5.5" + } } diff --git a/packages/serialport/test-arduino/arduinoEcho/arduinoEcho.ino b/packages/serialport/test-arduino/arduinoEcho/arduinoEcho.ino deleted file mode 100644 index 16d0022df..000000000 --- a/packages/serialport/test-arduino/arduinoEcho/arduinoEcho.ino +++ /dev/null @@ -1,15 +0,0 @@ -#define SET_BAUD_57600 130 -#define SET_BAUD_9600 131 - -void setup() { - pinMode(LED_BUILTIN, OUTPUT); - Serial.begin(9600); - Serial.write("READY"); -} - -void loop() { - while (Serial.available()) { - uint8_t oneByteData = Serial.read(); - Serial.write(oneByteData); - } -} diff --git a/packages/serialport/test-arduino/serialDuplexTest.js b/packages/serialport/test-arduino/serialDuplexTest.js deleted file mode 100755 index a998687d5..000000000 --- a/packages/serialport/test-arduino/serialDuplexTest.js +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env node - -/* -serialDuplexTest.js - -Tests the functionality of the serial port library. -To be used in conjunction with the Arduino sketch ArduinoEcho.ino -*/ - -const SerialPort = require('../') -// eslint-disable-next-line node/no-missing-require -const args = require('commander') - -args - .usage('-p ') - .description('Run printable characters through the serial port') - .option('-p, --port ', 'Path or Name of serial port. See serialportlist for available serial ports.') - .parse(process.argv) - -if (!args.port) { - args.outputHelp() - args.missingArgument('port') - process.exit(-1) -} - -const port = new SerialPort(args.port) // open the serial port: -let output = 32 // ASCII space; lowest printable character -let byteCount = 0 // number of bytes read - -function onOpen() { - console.log('Port Open') - console.log(`Baud Rate: ${port.options.baudRate}`) - const outString = String.fromCharCode(output) - console.log(`Sent:\t\t${outString}`) - port.write(outString) -} - -function onData(data) { - if (output <= 126) { - // highest printable character: ASCII ~ - output++ - } else { - output = 32 // lowest printable character: space - } - console.log(`Received:\t${data}`) - console.log(`Read Events:\t${byteCount}`) - byteCount++ - const outString = String.fromCharCode(output) - port.write(outString) - console.log(`Sent:\t\t${outString}`) -} - -function onClose() { - console.log('port closed') - process.exit(1) -} - -function onError(error) { - console.log(`there was an error with the serial port: ${error}`) - process.exit(1) -} - -port.on('open', onOpen) -port.on('data', onData) -port.on('close', onClose) -port.on('error', onError) diff --git a/packages/serialport/test-arduino/stress.js b/packages/serialport/test-arduino/stress.js deleted file mode 100644 index caea94c7e..000000000 --- a/packages/serialport/test-arduino/stress.js +++ /dev/null @@ -1,126 +0,0 @@ -/* eslint-disable node/no-missing-require, node/no-extraneous-require */ - -// `npm run stress` to run these tests - -const util = require('util') -const SerialPort = require('../') -require('colors') // this modifies String.prototype -// var fs = require('fs'); - -// Installing memwatch-next via package.json fails on node 10 on linux on our CI; -// Pending https://github.com/marcominetti/node-memwatch/issues/15 -let memwatch -try { - memwatch = require('memwatch-next') -} catch (e) { - console.error('Please install memwatch-next to use the stress tests') - process.exit(-1) -} - -describe('the stress', () => { - const testPort = process.env.TEST_PORT - - if (!testPort) { - // eslint-disable-next-line mocha/no-pending-tests - it('cannot be tested as we have no test ports') - return - } - - describe('of 2 minutes of running 1k writes', () => { - it("doesn't leak memory", done => { - const data = Buffer.alloc(1024) - const hd = new memwatch.HeapDiff() - const port = new SerialPort(testPort, {}, false) - port.on('close', done) - - let leaks = 0 - memwatch.on('leak', info => { - // fs.appendFile('leak.log', util.inspect(info)); - console.log(util.inspect(info, { depth: 5 }).red) - leaks++ - }) - - // memwatch.on('stats', function (stats) { - // fs.appendFile('stats.log', util.inspect(stats)); - // }); - - port.on('error', err => { - assert.fail(util.inspect(err)) - done() - }) - - port.on('data', () => {}) - - let writing = true - const write = () => { - if (!writing) { - return - } - port.write(data, write) - } - - port.open(err => { - assert.isUndefined(err) - write() - - setTimeout(() => { - console.log('cleaning up') - // var diff = hd.end(); - // fs.appendFile('heapdiff.log', util.inspect(diff)); - // console.log(util.inspect(diff, {depth: 5}).blue); - writing = false - - if (leaks > 0) { - const diff = hd.end() - // fs.appendFile('heapdiff.log', util.inspect(diff, {depth: 5})); - console.log(util.inspect(diff, { depth: 5 }).red) - assert.fail('leak detected') - } - port.close() - }, 1000 * 60 * 2) - }) - }) - }) - - // describe('of opening and closing ports', function() { - // it("doesn't leak memory", function(done) { - // var hd = new memwatch.HeapDiff(); - // var port = new SerialPort(testPort, {}, false); - - // memwatch.on('leak', function(info) { - // // fs.appendFile('leak.log', util.inspect(info)); - // console.log(util.inspect(info, {depth: 5}).red); - - // var diff = hd.end(); - // // fs.appendFile('heapdiff.log', util.inspect(diff, {depth: 5})); - // console.log(util.inspect(diff, {depth: 5}).red); - // assert.fail('leak detected'); - // port.close(); - // }); - - // var open, close; - // open = function() { - // process.nextTick(function() { - // port.open(close); - // }); - // }; - - // var looping = true; - // close = function() { - // if (looping) { - // process.nextTick(function() { - // port.close(open); - // }); - // } else { - // port.close(); - // } - // }; - // setTimeout(function() { - // looping = false; - // port.on('close', done); - // }, 1000 * 10); - - // open(); - // }); - // }); -}) diff --git a/packages/serialport/test-electron/main.js b/packages/serialport/test-electron/main.js deleted file mode 100644 index b526fcc63..000000000 --- a/packages/serialport/test-electron/main.js +++ /dev/null @@ -1,14 +0,0 @@ -/* eslint-disable node/no-missing-require,no-unused-vars,node/no-unpublished-require,node/no-extraneous-require */ - -const app = require('electron').app - -try { - const serialport = require('../..') -} catch (e) { - console.error('Error loading serialport') - console.error(e) - console.error(e.stack) - app.exit(-1) -} - -app.quit() diff --git a/packages/serialport/test-electron/package.json b/packages/serialport/test-electron/package.json deleted file mode 100644 index a95cce4c1..000000000 --- a/packages/serialport/test-electron/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name" : "your-app", - "version" : "0.1.0", - "main" : "main.js" -} \ No newline at end of file diff --git a/packages/serialport/test.js b/packages/serialport/test.js deleted file mode 100644 index 354050511..000000000 --- a/packages/serialport/test.js +++ /dev/null @@ -1,8 +0,0 @@ -const SerialPort = require('@serialport/stream') -const Binding = require('@serialport/binding-mock') -const parsers = require('./lib/parsers') - -SerialPort.Binding = Binding -SerialPort.parsers = parsers - -module.exports = SerialPort diff --git a/packages/serialport/tsconfig-build.json b/packages/serialport/tsconfig-build.json new file mode 100644 index 000000000..8284cd1f2 --- /dev/null +++ b/packages/serialport/tsconfig-build.json @@ -0,0 +1,29 @@ +{ + "extends": "../../tsconfig-build.json", + "compilerOptions": { + "rootDir": "lib", + "outDir": "dist", + }, + "include": [ + "lib", + ], + "exclude": [ + "node_modules", + "**/*.test.ts", + "dist", + ], + "references": [ + { "path": "../binding-mock/tsconfig-build.json"}, + { "path": "../parser-byte-length/tsconfig-build.json"}, + { "path": "../parser-cctalk/tsconfig-build.json"}, + { "path": "../parser-delimiter/tsconfig-build.json"}, + { "path": "../parser-inter-byte-timeout/tsconfig-build.json"}, + { "path": "../parser-packet-length/tsconfig-build.json"}, + { "path": "../parser-readline/tsconfig-build.json"}, + { "path": "../parser-ready/tsconfig-build.json"}, + { "path": "../parser-regex/tsconfig-build.json"}, + { "path": "../parser-slip-encoder/tsconfig-build.json"}, + { "path": "../parser-spacepacket/tsconfig-build.json"}, + { "path": "../stream/tsconfig-build.json"}, + ] +} diff --git a/packages/serialport/tsconfig.json b/packages/serialport/tsconfig.json new file mode 100644 index 000000000..4082f16a5 --- /dev/null +++ b/packages/serialport/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tsconfig.json" +} diff --git a/packages/stream/.npmignore b/packages/stream/.npmignore index a1623b1ea..df4db6a44 100644 --- a/packages/stream/.npmignore +++ b/packages/stream/.npmignore @@ -1,3 +1,7 @@ .DS_Store *.test.js CHANGELOG.md +lib +tsconfig.json +tsconfig-build.json +tsconfig-build.tsbuildinfo diff --git a/packages/stream/lib/index.js b/packages/stream/lib/index.js deleted file mode 100644 index 52d29b93f..000000000 --- a/packages/stream/lib/index.js +++ /dev/null @@ -1,656 +0,0 @@ -const stream = require('stream') -const util = require('util') -const debug = require('debug')('serialport/stream') - -// VALIDATION -const DATABITS = Object.freeze([5, 6, 7, 8]) -const STOPBITS = Object.freeze([1, 1.5, 2]) -const PARITY = Object.freeze(['none', 'even', 'mark', 'odd', 'space']) -const FLOWCONTROLS = Object.freeze(['xon', 'xoff', 'xany', 'rtscts']) - -const defaultSettings = Object.freeze({ - autoOpen: true, - endOnClose: false, - baudRate: 9600, - dataBits: 8, - hupcl: true, - lock: true, - parity: 'none', - rtscts: false, - stopBits: 1, - xany: false, - xoff: false, - xon: false, - highWaterMark: 64 * 1024, -}) - -const defaultSetFlags = Object.freeze({ - brk: false, - cts: false, - dtr: true, - dts: false, - rts: true, -}) - -function allocNewReadPool(poolSize) { - const pool = Buffer.allocUnsafe(poolSize) - pool.used = 0 - return pool -} - -/** - * A callback called with an error or null. - * @typedef {function} errorCallback - * @param {?error} error - */ - -/** - * A callback called with an error or an object with the modem line values (cts, dsr, dcd). - * @typedef {function} modemBitsCallback - * @param {?error} error - * @param {?object} status - * @param {boolean} [status.cts=false] - * @param {boolean} [status.dsr=false] - * @param {boolean} [status.dcd=false] - */ - -/** - * @typedef {Object} openOptions - * @property {boolean} [autoOpen=true] Automatically opens the port on `nextTick`. - * @property {number=} [baudRate=9600] The baud rate of the port to be opened. This should match one of the commonly available baud rates, such as 110, 300, 1200, 2400, 4800, 9600, 14400, 19200, 38400, 57600, or 115200. Custom rates are supported best effort per platform. The device connected to the serial port is not guaranteed to support the requested baud rate, even if the port itself supports that baud rate. - * @property {number} [dataBits=8] Must be one of these: 8, 7, 6, or 5. - * @property {number} [highWaterMark=65536] The size of the read and write buffers defaults to 64k. - * @property {boolean} [lock=true] Prevent other processes from opening the port. Windows does not currently support `false`. - * @property {number} [stopBits=1] Must be one of these: 1 or 2. - * @property {string} [parity=none] Must be one of these: 'none', 'even', 'mark', 'odd', 'space'. - * @property {boolean} [rtscts=false] flow control setting - * @property {boolean} [xon=false] flow control setting - * @property {boolean} [xoff=false] flow control setting - * @property {boolean} [xany=false] flow control setting - * @property {object=} bindingOptions sets binding-specific options - * @property {Binding=} binding The hardware access binding. `Bindings` are how Node-Serialport talks to the underlying system. By default we auto detect Windows (`WindowsBinding`), Linux (`LinuxBinding`) and OS X (`DarwinBinding`) and load the appropriate module for your system. - * @property {number} [bindingOptions.vmin=1] see [`man termios`](http://linux.die.net/man/3/termios) LinuxBinding and DarwinBinding - * @property {number} [bindingOptions.vtime=0] see [`man termios`](http://linux.die.net/man/3/termios) LinuxBinding and DarwinBinding - */ - -/** - * Create a new serial port object for the `path`. In the case of invalid arguments or invalid options, when constructing a new SerialPort it will throw an error. The port will open automatically by default, which is the equivalent of calling `port.open(openCallback)` in the next tick. You can disable this by setting the option `autoOpen` to `false`. - * @class SerialPort - * @param {string} path - The system path of the serial port you want to open. For example, `/dev/tty.XXX` on Mac/Linux, or `COM1` on Windows. - * @param {openOptions=} options - Port configuration options - * @param {errorCallback=} openCallback - Called after a connection is opened. If this is not provided and an error occurs, it will be emitted on the port's `error` event. The callback will NOT be called if `autoOpen` is set to `false` in the `openOptions` as the open will not be performed. - * @property {number} baudRate The port's baudRate. Use `.update` to change it. Read-only. - * @property {object} binding The binding object backing the port. Read-only. - * @property {boolean} isOpen `true` if the port is open, `false` otherwise. Read-only. (`since 5.0.0`) - * @property {string} path The system path or name of the serial port. Read-only. - * @throws {TypeError} When given invalid arguments, a `TypeError` will be thrown. - * @emits open - * @emits data - * @emits close - * @emits error - * @alias module:serialport - */ -function SerialPort(path, options, openCallback) { - if (!(this instanceof SerialPort)) { - return new SerialPort(path, options, openCallback) - } - - if (options instanceof Function) { - openCallback = options - options = {} - } - - const settings = { ...defaultSettings, ...options } - - stream.Duplex.call(this, { - highWaterMark: settings.highWaterMark, - }) - - const Binding = settings.binding || SerialPort.Binding - - if (!Binding) { - throw new TypeError('"Bindings" is invalid pass it as `options.binding` or set it on `SerialPort.Binding`') - } - - if (!path) { - throw new TypeError(`"path" is not defined: ${path}`) - } - - if (settings.baudrate) { - throw new TypeError(`"baudrate" is an unknown option, did you mean "baudRate"?`) - } - - if (typeof settings.baudRate !== 'number') { - throw new TypeError(`"baudRate" must be a number: ${settings.baudRate}`) - } - - if (DATABITS.indexOf(settings.dataBits) === -1) { - throw new TypeError(`"databits" is invalid: ${settings.dataBits}`) - } - - if (STOPBITS.indexOf(settings.stopBits) === -1) { - throw new TypeError(`"stopbits" is invalid: ${settings.stopbits}`) - } - - if (PARITY.indexOf(settings.parity) === -1) { - throw new TypeError(`"parity" is invalid: ${settings.parity}`) - } - - FLOWCONTROLS.forEach(control => { - if (typeof settings[control] !== 'boolean') { - throw new TypeError(`"${control}" is not boolean: ${settings[control]}`) - } - }) - - const binding = new Binding({ - bindingOptions: settings.bindingOptions, - }) - - Object.defineProperties(this, { - binding: { - enumerable: true, - value: binding, - }, - path: { - enumerable: true, - value: path, - }, - settings: { - enumerable: true, - value: settings, - }, - }) - - this.opening = false - this.closing = false - this._pool = allocNewReadPool(this.settings.highWaterMark) - this._kMinPoolSpace = 128 - - if (this.settings.autoOpen) { - this.open(openCallback) - } -} - -util.inherits(SerialPort, stream.Duplex) - -Object.defineProperties(SerialPort.prototype, { - isOpen: { - enumerable: true, - get() { - return this.binding.isOpen && !this.closing - }, - }, - baudRate: { - enumerable: true, - get() { - return this.settings.baudRate - }, - }, -}) - -/** - * The `error` event's callback is called with an error object whenever there is an error. - * @event error - */ - -SerialPort.prototype._error = function (error, callback) { - if (callback) { - callback.call(this, error) - } else { - this.emit('error', error) - } -} - -SerialPort.prototype._asyncError = function (error, callback) { - process.nextTick(() => this._error(error, callback)) -} - -/** - * The `open` event's callback is called with no arguments when the port is opened and ready for writing. This happens if you have the constructor open immediately (which opens in the next tick) or if you open the port manually with `open()`. See [Useage/Opening a Port](#opening-a-port) for more information. - * @event open - */ - -/** - * Opens a connection to the given serial port. - * @param {errorCallback=} openCallback - Called after a connection is opened. If this is not provided and an error occurs, it will be emitted on the port's `error` event. - * @emits open - * @returns {void} - */ -SerialPort.prototype.open = function (openCallback) { - if (this.isOpen) { - return this._asyncError(new Error('Port is already open'), openCallback) - } - - if (this.opening) { - return this._asyncError(new Error('Port is opening'), openCallback) - } - - this.opening = true - debug('opening', `path: ${this.path}`) - this.binding.open(this.path, this.settings).then( - () => { - debug('opened', `path: ${this.path}`) - this.opening = false - this.emit('open') - if (openCallback) { - openCallback.call(this, null) - } - }, - err => { - this.opening = false - debug('Binding #open had an error', err) - this._error(err, openCallback) - } - ) -} - -/** - * Changes the baud rate for an open port. Throws if you provide a bad argument. Emits an error or calls the callback if the baud rate isn't supported. - * @param {object=} options Only supports `baudRate`. - * @param {number=} [options.baudRate] The baud rate of the port to be opened. This should match one of the commonly available baud rates, such as 110, 300, 1200, 2400, 4800, 9600, 14400, 19200, 38400, 57600, or 115200. Custom rates are supported best effort per platform. The device connected to the serial port is not guaranteed to support the requested baud rate, even if the port itself supports that baud rate. - * @param {errorCallback=} [callback] Called once the port's baud rate changes. If `.update` is called without a callback, and there is an error, an error event is emitted. - * @returns {undefined} - */ -SerialPort.prototype.update = function (options, callback) { - if (typeof options !== 'object') { - throw TypeError('"options" is not an object') - } - - if (!this.isOpen) { - debug('update attempted, but port is not open') - return this._asyncError(new Error('Port is not open'), callback) - } - - const settings = { ...defaultSettings, ...options } - this.settings.baudRate = settings.baudRate - - debug('update', `baudRate: ${settings.baudRate}`) - this.binding.update(this.settings).then( - () => { - debug('binding.update', 'finished') - if (callback) { - callback.call(this, null) - } - }, - err => { - debug('binding.update', 'error', err) - return this._error(err, callback) - } - ) -} - -/** - * Writes data to the given serial port. Buffers written data if the port is not open. - -The write operation is non-blocking. When it returns, data might still not have been written to the serial port. See `drain()`. - -Some devices, like the Arduino, reset when you open a connection to them. In such cases, immediately writing to the device will cause lost data as they wont be ready to receive the data. This is often worked around by having the Arduino send a "ready" byte that your Node program waits for before writing. You can also often get away with waiting around 400ms. - -If a port is disconnected during a write, the write will error in addition to the `close` event. - -From the [stream docs](https://nodejs.org/api/stream.html#stream_writable_write_chunk_encoding_callback) write errors don't always provide the error in the callback, sometimes they use the error event. -> If an error occurs, the callback may or may not be called with the error as its first argument. To reliably detect write errors, add a listener for the 'error' event. - -In addition to the usual `stream.write` arguments (`String` and `Buffer`), `write()` can accept arrays of bytes (positive numbers under 256) which is passed to `Buffer.from([])` for conversion. This extra functionality is pretty sweet. - * @method SerialPort.prototype.write - * @param {(string|array|buffer)} data Accepts a [`Buffer`](http://nodejs.org/api/buffer.html) object, or a type that is accepted by the `Buffer` constructor (e.g. an array of bytes or a string). - * @param {string=} encoding The encoding, if chunk is a string. Defaults to `'utf8'`. Also accepts `'ascii'`, `'base64'`, `'binary'`, and `'hex'` See [Buffers and Character Encodings](https://nodejs.org/api/buffer.html#buffer_buffers_and_character_encodings) for all available options. - * @param {function=} callback Called once the write operation finishes. Data may not yet be flushed to the underlying port. No arguments. - * @returns {boolean} `false` if the stream wishes for the calling code to wait for the `'drain'` event to be emitted before continuing to write additional data; otherwise `true`. - * @since 5.0.0 - */ -const superWrite = SerialPort.prototype.write -SerialPort.prototype.write = function (data, encoding, callback) { - if (Array.isArray(data)) { - data = Buffer.from(data) - } - return superWrite.call(this, data, encoding, callback) -} - -SerialPort.prototype._write = function (data, encoding, callback) { - if (!this.isOpen) { - return this.once('open', function afterOpenWrite() { - this._write(data, encoding, callback) - }) - } - debug('_write', `${data.length} bytes of data`) - this.binding.write(data).then( - () => { - debug('binding.write', 'write finished') - callback(null) - }, - err => { - debug('binding.write', 'error', err) - if (!err.canceled) { - this._disconnected(err) - } - callback(err) - } - ) -} - -SerialPort.prototype._writev = function (data, callback) { - debug('_writev', `${data.length} chunks of data`) - const dataV = data.map(write => write.chunk) - this._write(Buffer.concat(dataV), null, callback) -} - -/** - * Request a number of bytes from the SerialPort. The `read()` method pulls some data out of the internal buffer and returns it. If no data is available to be read, null is returned. By default, the data is returned as a `Buffer` object unless an encoding has been specified using the `.setEncoding()` method. - * @method SerialPort.prototype.read - * @param {number=} size Specify how many bytes of data to return, if available - * @returns {(string|Buffer|null)} The data from internal buffers - * @since 5.0.0 - */ - -/** - * Listening for the `data` event puts the port in flowing mode. Data is emitted as soon as it's received. Data is a `Buffer` object with a varying amount of data in it. The `readLine` parser converts the data into string lines. See the [parsers](https://serialport.io/docs/api-parsers-overview) section for more information on parsers, and the [Node.js stream documentation](https://nodejs.org/api/stream.html#stream_event_data) for more information on the data event. - * @event data - */ - -SerialPort.prototype._read = function (bytesToRead) { - if (!this.isOpen) { - debug('_read', 'queueing _read for after open') - this.once('open', () => { - this._read(bytesToRead) - }) - return - } - - if (!this._pool || this._pool.length - this._pool.used < this._kMinPoolSpace) { - debug('_read', 'discarding the read buffer pool because it is below kMinPoolSpace') - this._pool = allocNewReadPool(this.settings.highWaterMark) - } - - // Grab another reference to the pool in the case that while we're - // in the thread pool another read() finishes up the pool, and - // allocates a new one. - const pool = this._pool - // Read the smaller of rest of the pool or however many bytes we want - const toRead = Math.min(pool.length - pool.used, bytesToRead) - const start = pool.used - - // the actual read. - debug('_read', `reading`, { start, toRead }) - this.binding.read(pool, start, toRead).then( - ({ bytesRead }) => { - debug('binding.read', `finished`, { bytesRead }) - // zero bytes means read means we've hit EOF? Maybe this should be an error - if (bytesRead === 0) { - debug('binding.read', 'Zero bytes read closing readable stream') - this.push(null) - return - } - pool.used += bytesRead - this.push(pool.slice(start, start + bytesRead)) - }, - err => { - debug('binding.read', `error`, err) - if (!err.canceled) { - this._disconnected(err) - } - this._read(bytesToRead) // prime to read more once we're reconnected - } - ) -} - -SerialPort.prototype._disconnected = function (err) { - if (!this.isOpen) { - debug('disconnected aborted because already closed', err) - return - } - debug('disconnected', err) - err.disconnected = true - this.close(null, err) -} - -/** - * The `close` event's callback is called with no arguments when the port is closed. In the case of a disconnect it will be called with a Disconnect Error object (`err.disconnected == true`). In the event of a close error (unlikely), an error event is triggered. - * @event close - */ - -/** - * Closes an open connection. - * - * If there are in progress writes when the port is closed the writes will error. - * @param {errorCallback} callback Called once a connection is closed. - * @param {Error} disconnectError used internally to propagate a disconnect error - * @emits close - * @returns {undefined} - */ -SerialPort.prototype.close = function (callback, disconnectError) { - disconnectError = disconnectError || null - - if (!this.isOpen) { - debug('close attempted, but port is not open') - return this._asyncError(new Error('Port is not open'), callback) - } - - this.closing = true - debug('#close') - this.binding.close().then( - () => { - this.closing = false - debug('binding.close', 'finished') - this.emit('close', disconnectError) - if (this.settings.endOnClose) { - this.emit('end') - } - if (callback) { - callback.call(this, disconnectError) - } - }, - err => { - this.closing = false - debug('binding.close', 'had an error', err) - return this._error(err, callback) - } - ) -} - -/** - * Set control flags on an open port. Uses [`SetCommMask`](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363257(v=vs.85).aspx) for Windows and [`ioctl`](http://linux.die.net/man/4/tty_ioctl) for OS X and Linux. - * @param {object=} options All options are operating system default when the port is opened. Every flag is set on each call to the provided or default values. If options isn't provided default options is used. - * @param {Boolean} [options.brk=false] sets the brk flag - * @param {Boolean} [options.cts=false] sets the cts flag - * @param {Boolean} [options.dsr=false] sets the dsr flag - * @param {Boolean} [options.dtr=true] sets the dtr flag - * @param {Boolean} [options.rts=true] sets the rts flag - * @param {errorCallback=} callback Called once the port's flags have been set. - * @since 5.0.0 - * @returns {undefined} - */ -SerialPort.prototype.set = function (options, callback) { - if (typeof options !== 'object') { - throw TypeError('"options" is not an object') - } - - if (!this.isOpen) { - debug('set attempted, but port is not open') - return this._asyncError(new Error('Port is not open'), callback) - } - - const settings = { ...defaultSetFlags, ...options } - debug('#set', settings) - this.binding.set(settings).then( - () => { - debug('binding.set', 'finished') - if (callback) { - callback.call(this, null) - } - }, - err => { - debug('binding.set', 'had an error', err) - return this._error(err, callback) - } - ) -} - -/** - * Returns the control flags (CTS, DSR, DCD) on the open port. - * Uses [`GetCommModemStatus`](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363258(v=vs.85).aspx) for Windows and [`ioctl`](http://linux.die.net/man/4/tty_ioctl) for mac and linux. - * @param {modemBitsCallback=} callback Called once the modem bits are retrieved. - * @returns {undefined} - */ -SerialPort.prototype.get = function (callback) { - if (!this.isOpen) { - debug('get attempted, but port is not open') - return this._asyncError(new Error('Port is not open'), callback) - } - - debug('#get') - this.binding.get().then( - status => { - debug('binding.get', 'finished') - if (callback) { - callback.call(this, null, status) - } - }, - err => { - debug('binding.get', 'had an error', err) - return this._error(err, callback) - } - ) -} - -/** - * Flush discards data received but not read, and written but not transmitted by the operating system. For more technical details, see [`tcflush(fd, TCIOFLUSH)`](http://linux.die.net/man/3/tcflush) for Mac/Linux and [`FlushFileBuffers`](http://msdn.microsoft.com/en-us/library/windows/desktop/aa364439) for Windows. - * @param {errorCallback=} callback Called once the flush operation finishes. - * @returns {undefined} - */ -SerialPort.prototype.flush = function (callback) { - if (!this.isOpen) { - debug('flush attempted, but port is not open') - return this._asyncError(new Error('Port is not open'), callback) - } - - debug('#flush') - this.binding.flush().then( - () => { - debug('binding.flush', 'finished') - if (callback) { - callback.call(this, null) - } - }, - err => { - debug('binding.flush', 'had an error', err) - return this._error(err, callback) - } - ) -} - -/** - * Waits until all output data is transmitted to the serial port. After any pending write has completed it calls [`tcdrain()`](http://linux.die.net/man/3/tcdrain) or [FlushFileBuffers()](https://msdn.microsoft.com/en-us/library/windows/desktop/aa364439(v=vs.85).aspx) to ensure it has been written to the device. - * @param {errorCallback=} callback Called once the drain operation returns. - * @returns {undefined} - * @example -Write the `data` and wait until it has finished transmitting to the target serial port before calling the callback. This will queue until the port is open and writes are finished. - -```js -function writeAndDrain (data, callback) { - port.write(data); - port.drain(callback); -} -``` - */ -SerialPort.prototype.drain = function (callback) { - debug('drain') - if (!this.isOpen) { - debug('drain queuing on port open') - return this.once('open', () => { - this.drain(callback) - }) - } - this.binding.drain().then( - () => { - debug('binding.drain', 'finished') - if (callback) { - callback.call(this, null) - } - }, - err => { - debug('binding.drain', 'had an error', err) - return this._error(err, callback) - } - ) -} - -/** - * The `pause()` method causes a stream in flowing mode to stop emitting 'data' events, switching out of flowing mode. Any data that becomes available remains in the internal buffer. - * @method SerialPort.prototype.pause - * @see resume - * @since 5.0.0 - * @returns `this` - */ - -/** - * The `resume()` method causes an explicitly paused, `Readable` stream to resume emitting 'data' events, switching the stream into flowing mode. - * @method SerialPort.prototype.resume - * @see pause - * @since 5.0.0 - * @returns `this` - */ - -/** - * Retrieves a list of available serial ports with metadata. Only the `path` is guaranteed. If unavailable the other fields will be undefined. The `path` is either the path or an identifier (eg `COM1`) used to open the SerialPort. - * - * We make an effort to identify the hardware attached and have consistent results between systems. Linux and OS X are mostly consistent. Windows relies on 3rd party device drivers for the information and is unable to guarantee the information. On windows If you have a USB connected device can we provide a serial number otherwise it will be `undefined`. The `pnpId` and `locationId` are not the same or present on all systems. The examples below were run with the same Arduino Uno. - * @type {function} - * @returns {Promise} Resolves with the list of available serial ports. - * @example -```js -// OSX example port -{ - path: '/dev/tty.usbmodem1421', - manufacturer: 'Arduino (www.arduino.cc)', - serialNumber: '752303138333518011C1', - pnpId: undefined, - locationId: '14500000', - productId: '0043', - vendorId: '2341' -} - -// Linux example port -{ - path: '/dev/ttyACM0', - manufacturer: 'Arduino (www.arduino.cc)', - serialNumber: '752303138333518011C1', - pnpId: 'usb-Arduino__www.arduino.cc__0043_752303138333518011C1-if00', - locationId: undefined, - productId: '0043', - vendorId: '2341' -} - -// Windows example port -{ - path: 'COM3', - manufacturer: 'Arduino LLC (www.arduino.cc)', - serialNumber: '752303138333518011C1', - pnpId: 'USB\\VID_2341&PID_0043\\752303138333518011C1', - locationId: 'Port_#0003.Hub_#0001', - productId: '0043', - vendorId: '2341' -} -``` - -```js -var SerialPort = require('serialport'); - -// promise approach -SerialPort.list() - .then(ports) {...}); - .catch(err) {...}); -``` - */ -SerialPort.list = async function (callback) { - debug('.list') - if (!SerialPort.Binding) { - throw new TypeError('No Binding set on `SerialPort.Binding`') - } - if (callback) { - throw new TypeError('SerialPort.list no longer takes a callback and only returns a promise') - } - return SerialPort.Binding.list() -} - -module.exports = SerialPort diff --git a/packages/stream/lib/index.test.js b/packages/stream/lib/index.test.ts similarity index 58% rename from packages/stream/lib/index.test.js rename to packages/stream/lib/index.test.ts index c8a4d49f6..640fd4174 100644 --- a/packages/stream/lib/index.test.js +++ b/packages/stream/lib/index.test.ts @@ -1,22 +1,16 @@ -const chai = require('chai') -const sinon = require('sinon') -const { randomBytes } = require('crypto') -chai.use(require('chai-subset')) -const assert = chai.assert - -const SerialPort = require('../') -const { MockBinding } = require('@serialport/binding-mock') - +/* eslint-disable @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-explicit-any */ +import sinon from 'sinon' +import { randomBytes } from 'crypto' +import { MockBinding, MockBindingInterface } from '@serialport/binding-mock' +import { DisconnectedError, OpenOptions, SerialPortStream } from './' +import { assert } from '../../../test/assert' + +const sandbox = sinon.createSandbox() describe('SerialPort', () => { - let sandbox + const openOpts: OpenOptions = { path: '/dev/exists', baudRate: 9600, binding: MockBinding } beforeEach(() => { - SerialPort.Binding = MockBinding - sandbox = sinon.createSandbox() - MockBinding.createPort('/dev/exists', { - echo: true, - readyData: Buffer.from([]), - }) + MockBinding.createPort('/dev/exists', { echo: true }) }) afterEach(() => { @@ -25,28 +19,23 @@ describe('SerialPort', () => { }) describe('constructor', () => { - it('provides auto construction', done => { - const serialPort = SerialPort - serialPort('/dev/exists', done) - }) - describe('autoOpen', () => { it('opens the port automatically', done => { - new SerialPort('/dev/exists', err => { + new SerialPortStream(openOpts, err => { assert.isNull(err) done() }) }) it('emits the open event', done => { - const port = new SerialPort('/dev/exists') + const port = new SerialPortStream(openOpts) port.on('open', done) }) it("doesn't open if told not to", done => { - const port = new SerialPort('/dev/exists', { autoOpen: false }) + const port = new SerialPortStream({ ...openOpts, autoOpen: false }) port.on('open', () => { - throw new Error("this shouldn't be opening") + done(new Error("this shouldn't be opening")) }) process.nextTick(done) }) @@ -54,7 +43,7 @@ describe('SerialPort', () => { // needs to be passes the callback to open it('passes the error to the callback when an bad port is provided', done => { - new SerialPort('/bad/port', err => { + new SerialPortStream({ ...openOpts, path: '/bad/port' }, err => { assert.instanceOf(err, Error) done() }) @@ -62,7 +51,7 @@ describe('SerialPort', () => { // is this a test for open? it('emits an error when an bad port is provided', done => { - const port = new SerialPort('/bad/port') + const port = new SerialPortStream({ ...openOpts, path: '/bad/port' }) port.once('error', err => { assert.instanceOf(err, Error) done() @@ -70,9 +59,8 @@ describe('SerialPort', () => { }) it('throws an error when bindings are missing', done => { - SerialPort.Binding = undefined try { - new SerialPort('/dev/exists') + new SerialPortStream({ ...openOpts, binding: undefined } as any) } catch (err) { assert.instanceOf(err, Error) done() @@ -81,67 +69,31 @@ describe('SerialPort', () => { it('throws an error when no port is provided', done => { try { - new SerialPort('') + new SerialPortStream({ ...openOpts, path: '' }) } catch (err) { assert.instanceOf(err, Error) done() } }) - it('throws an error when given bad options even with a callback', done => { + it('errors with a non number baudRate even with a callback', done => { try { - new SerialPort('/dev/exists', { baudRate: 'whatever' }, () => {}) + new SerialPortStream({ path: '/dev/exists', baudRate: 'whatever' } as any, () => {}) } catch (err) { assert.instanceOf(err, Error) done() } }) - it('throws an error when given bad baudrate even with a callback', () => { + it('throws an error when given bad baudrate option even with a callback', () => { assert.throws(() => { - new SerialPort('/dev/exists', { baudrate: 9600 }, () => {}) + new SerialPortStream({ path: 'foobar', baudrate: 9600 } as any, () => {}) }) }) it('errors with a non number baudRate', done => { try { - new SerialPort('/bad/port', { baudRate: 'whatever' }) - } catch (err) { - assert.instanceOf(err, Error) - done() - } - }) - - it('errors with invalid databits', done => { - try { - new SerialPort('/dev/exists', { dataBits: 19 }) - } catch (err) { - assert.instanceOf(err, Error) - done() - } - }) - - it('errors with invalid stopbits', done => { - try { - new SerialPort('/dev/exists', { stopBits: 19 }) - } catch (err) { - assert.instanceOf(err, Error) - done() - } - }) - - it('errors with invalid parity', done => { - try { - new SerialPort('/dev/exists', { parity: 'pumpkins' }) - } catch (err) { - assert.instanceOf(err, Error) - done() - } - }) - - it('errors with invalid flow control', done => { - try { - new SerialPort('/dev/exists', { xon: 'pumpkins' }) + new SerialPortStream({ path: '/dev/exists', baudRate: 'whatever' } as any) } catch (err) { assert.instanceOf(err, Error) done() @@ -150,80 +102,48 @@ describe('SerialPort', () => { it('sets valid flow control individually', done => { const options = { + ...openOpts, xon: true, xoff: true, xany: true, rtscts: true, autoOpen: false, } - const port = new SerialPort('/dev/exists', options) + const port = new SerialPortStream(options) assert.isTrue(port.settings.xon) assert.isTrue(port.settings.xoff) assert.isTrue(port.settings.xany) assert.isTrue(port.settings.rtscts) done() }) - - it('allows optional options', done => { - new SerialPort('/dev/exists', () => done()) - }) - }) - - describe('static methods', () => { - describe('Serialport#list', () => { - it('calls to the bindings', async () => { - const spy = sinon.spy(MockBinding, 'list') - const ports = await SerialPort.list() - assert.isArray(ports) - assert(spy.calledOnce) - }) - - it('errors if there are no bindings', async () => { - SerialPort.Binding = null - try { - await SerialPort.list() - } catch (e) { - assert.instanceOf(e, TypeError) - return - } - throw new Error('no expected error') - }) - - it('errors if there is a callback', async () => { - try { - await SerialPort.list(() => {}) - } catch (e) { - assert.instanceOf(e, TypeError) - return - } - throw new Error('no expected error') - }) - }) }) describe('property', () => { describe('.baudRate', () => { it('is a read only property set during construction', () => { - const port = new SerialPort('/dev/exists', { + const port = new SerialPortStream({ + ...openOpts, autoOpen: false, - baudRate: 14400, }) - assert.equal(port.baudRate, 14400) + assert.equal(port.baudRate, 9600) try { - port.baudRate = 9600 + ;(port as any).baudRate = 14400 } catch (e) { assert.instanceOf(e, TypeError) } - assert.equal(port.baudRate, 14400) + assert.equal(port.baudRate, 9600) }) }) describe('.path', () => { it('is a read only property set during construction', () => { - const port = new SerialPort('/dev/exists', { autoOpen: false }) + const port = new SerialPortStream({ + ...openOpts, + autoOpen: false, + }) assert.equal(port.path, '/dev/exists') try { - port.path = 'foo' + ;(port as any).path = 'foo' } catch (e) { assert.instanceOf(e, TypeError) } @@ -233,10 +153,13 @@ describe('SerialPort', () => { describe('.isOpen', () => { it('is a read only property', () => { - const port = new SerialPort('/dev/exists', { autoOpen: false }) + const port = new SerialPortStream({ + ...openOpts, + autoOpen: false, + }) assert.equal(port.isOpen, false) try { - port.isOpen = 'foo' + ;(port as any).isOpen = 'foo' } catch (e) { assert.instanceOf(e, TypeError) } @@ -244,40 +167,47 @@ describe('SerialPort', () => { }) it('returns false when the port is created', done => { - const port = new SerialPort('/dev/exists', { autoOpen: false }) + const port = new SerialPortStream({ + ...openOpts, + autoOpen: false, + }) assert.isFalse(port.isOpen) done() }) it('returns false when the port is opening', done => { - const port = new SerialPort('/dev/exists', { autoOpen: false }) - sandbox.stub(SerialPort.Binding.prototype, 'open').callsFake(() => { + const port = new SerialPortStream({ + ...openOpts, + autoOpen: false, + }) + sandbox.stub(port.settings.binding, 'open').callsFake(() => { assert.isTrue(port.opening) assert.isFalse(port.isOpen) done() + return Promise.resolve({} as any) }) port.open() }) it('returns true when the port is open', done => { - const port = new SerialPort('/dev/exists', () => { + const port = new SerialPortStream(openOpts, () => { assert.isTrue(port.isOpen) done() }) }) it('returns false when the port is closing', done => { - const port = new SerialPort('/dev/exists', {}, () => { + const port = new SerialPortStream(openOpts, () => { + sandbox.stub((port as any).port, 'close').callsFake(async () => { + assert.isFalse(port.isOpen) + done() + }) port.close() }) - sandbox.stub(SerialPort.Binding.prototype, 'close').callsFake(async () => { - assert.isFalse(port.isOpen) - done() - }) }) it('returns false when the port is closed', done => { - const port = new SerialPort('/dev/exists', () => { + const port = new SerialPortStream(openOpts, () => { port.close() }) port.on('close', () => { @@ -290,41 +220,19 @@ describe('SerialPort', () => { describe('instance method', () => { describe('#open', () => { - it('passes the port to the bindings', done => { - const port = new SerialPort('/dev/exists', { autoOpen: false }) - const openSpy = sandbox.spy(port.binding, 'open') - assert.isFalse(port.isOpen) - port.open(err => { - assert.isNull(err) - assert.isTrue(port.isOpen) - assert.isTrue(openSpy.calledWith('/dev/exists')) - done() - }) - }) - - it('passes default options to the bindings', done => { - const defaultOptions = { - baudRate: 9600, - parity: 'none', - xon: false, - xoff: false, - xany: false, - rtscts: false, - hupcl: true, - dataBits: 8, - stopBits: 1, - } - const port = new SerialPort('/dev/exists', { autoOpen: false }) - sandbox.stub(SerialPort.Binding.prototype, 'open').callsFake((path, opt) => { - assert.equal(path, '/dev/exists') - assert.containSubset(opt, defaultOptions) + it('passes the open options to the bindings without the stream stuff', done => { + sandbox.stub(MockBinding, 'open').callsFake(((opt: any) => { + assert.deepEqual(opt, { + path: '/dev/exists', + baudRate: 9600, + }) done() - }) - port.open() + }) as any) + new SerialPortStream(openOpts) }) it('calls back an error when opening an invalid port', done => { - const port = new SerialPort('/dev/unhappy', { autoOpen: false }) + const port = new SerialPortStream({ ...openOpts, path: '/dev/unhappy', autoOpen: false }) port.open(err => { assert.instanceOf(err, Error) done() @@ -333,10 +241,10 @@ describe('SerialPort', () => { it('emits data after being reopened', done => { const data = Buffer.from('Howdy!') - const port = new SerialPort('/dev/exists', () => { + const port = new SerialPortStream(openOpts, () => { port.close(() => { port.open(() => { - port.binding.emitData(data) + port.port?.emitData(data) }) }) port.once('data', res => { @@ -347,7 +255,7 @@ describe('SerialPort', () => { }) it('cannot be opened again after open', done => { - const port = new SerialPort('/dev/exists', () => { + const port = new SerialPortStream(openOpts, () => { port.open(err => { assert.instanceOf(err, Error) done() @@ -356,7 +264,7 @@ describe('SerialPort', () => { }) it('cannot be opened while opening', done => { - const port = new SerialPort('/dev/exists', { autoOpen: false }) + const port = new SerialPortStream({ ...openOpts, autoOpen: false }) port.open(err => { assert.isNull(err) }) @@ -367,8 +275,8 @@ describe('SerialPort', () => { }) it('allows opening after an open error', done => { - const port = new SerialPort('/dev/exists', { autoOpen: false }) - const stub = sandbox.stub(SerialPort.Binding.prototype, 'open').callsFake(() => { + const port = new SerialPortStream({ ...openOpts, autoOpen: false }) + const stub = sandbox.stub(port.settings.binding, 'open').callsFake(() => { return Promise.reject(new Error('Haha no')) }) port.open(err => { @@ -382,21 +290,21 @@ describe('SerialPort', () => { describe('#write', () => { it('writes to the bindings layer', done => { const data = Buffer.from('Crazy!') - const port = new SerialPort('/dev/exists') + const port = new SerialPortStream(openOpts) port.on('open', () => { port.write(data, () => { - assert.deepEqual(data, port.binding.lastWrite) + assert.deepEqual(data, port.port!.lastWrite) done() }) }) }) it('converts strings to buffers', done => { - const port = new SerialPort('/dev/exists') + const port = new SerialPortStream(openOpts) port.on('open', () => { const data = 'Crazy!' port.write(data, () => { - const lastWrite = port.binding.lastWrite + const lastWrite = port.port!.lastWrite assert.deepEqual(Buffer.from(data), lastWrite) done() }) @@ -404,11 +312,11 @@ describe('SerialPort', () => { }) it('converts strings with encodings to buffers', done => { - const port = new SerialPort('/dev/exists') + const port = new SerialPortStream(openOpts) port.on('open', () => { const data = 'C0FFEE' port.write(data, 'hex', () => { - const lastWrite = port.binding.lastWrite + const lastWrite = port.port!.lastWrite assert.deepEqual(Buffer.from(data, 'hex'), lastWrite) done() }) @@ -416,11 +324,11 @@ describe('SerialPort', () => { }) it('converts arrays to buffers', done => { - const port = new SerialPort('/dev/exists') + const port = new SerialPortStream(openOpts) port.on('open', () => { const data = [0, 32, 44, 88] port.write(data, () => { - const lastWrite = port.binding.lastWrite + const lastWrite = port.port!.lastWrite assert.deepEqual(Buffer.from(data), lastWrite) done() }) @@ -428,20 +336,20 @@ describe('SerialPort', () => { }) it('queues writes when the port is closed', done => { - const port = new SerialPort('/dev/exists', { autoOpen: false }) + const port = new SerialPortStream({ ...openOpts, autoOpen: false }) port.write('data', done) port.open() }) it('combines many writes into one', done => { - const port = new SerialPort('/dev/exists', { autoOpen: false }) - const spy = sinon.spy(port.binding, 'write') + const port = new SerialPortStream({ ...openOpts, autoOpen: false }) port.open(() => { + const spy = sinon.spy(port.port!, 'write') port.cork() port.write('abc') port.write(Buffer.from('123'), () => { assert.equal(spy.callCount, 1) - assert.deepEqual(port.binding.lastWrite, Buffer.from('abc123')) + assert.deepEqual(port.port!.lastWrite, Buffer.from('abc123')) done() }) port.uncork() @@ -451,7 +359,7 @@ describe('SerialPort', () => { describe('#close', () => { it('emits a close event for writing consumers', done => { - const port = new SerialPort('/dev/exists', () => { + const port = new SerialPortStream(openOpts, () => { port.on('close', () => { assert.isFalse(port.isOpen) done() @@ -461,7 +369,7 @@ describe('SerialPort', () => { }) it('emits an "end" event for reading consumers when endOnClose is true', done => { - const port = new SerialPort('/dev/exists', { endOnClose: true }) + const port = new SerialPortStream({ ...openOpts, endOnClose: true }) port.on('open', () => { port.on('end', () => { assert.isFalse(port.isOpen) @@ -472,7 +380,7 @@ describe('SerialPort', () => { }) it('doesn\'t emit an "end" event for reading consumers when endOnClose is false', done => { - const port = new SerialPort('/dev/exists') + const port = new SerialPortStream(openOpts) port.on('open', () => { port.on('end', () => { done(new Error('Should not have ended')) @@ -482,7 +390,7 @@ describe('SerialPort', () => { }) it('has a close callback', done => { - const port = new SerialPort('/dev/exists', () => { + const port = new SerialPortStream(openOpts, () => { port.close(() => { assert.isFalse(port.isOpen) done() @@ -498,16 +406,16 @@ describe('SerialPort', () => { return done() } } - const port = new SerialPort('/dev/exists', () => { + const port = new SerialPortStream(openOpts, () => { port.close(doneIfTwice) }) port.on('close', doneIfTwice) }) it('emits an "error" event or error callback but not both', done => { - const port = new SerialPort('/dev/exists', { autoOpen: false }) + const port = new SerialPortStream({ ...openOpts, autoOpen: false }) let called = 0 - const doneIfTwice = function (err) { + const doneIfTwice = function (err: Error | null) { assert.instanceOf(err, Error) called++ if (called === 2) { @@ -520,7 +428,7 @@ describe('SerialPort', () => { }) it('emits a "close" event after being reopened', done => { - const port = new SerialPort('/dev/exists', () => { + const port = new SerialPortStream(openOpts, () => { const closeSpy = sandbox.spy() port.on('close', closeSpy) port.close(() => { @@ -536,7 +444,7 @@ describe('SerialPort', () => { it('errors when the port is not open', done => { const cb = function () {} - const port = new SerialPort('/dev/exists', { autoOpen: false }, cb) + const port = new SerialPortStream({ ...openOpts, autoOpen: false }, cb) port.close(err => { assert.instanceOf(err, Error) done() @@ -544,11 +452,11 @@ describe('SerialPort', () => { }) it('handles errors in callback', done => { - const port = new SerialPort('/dev/exists') - sinon.stub(port.binding, 'close').callsFake(() => { - return Promise.reject(new Error('like tears in the rain')) - }) + const port = new SerialPortStream(openOpts) port.on('open', () => { + sinon.stub(port.port!, 'close').callsFake(async () => { + throw new Error('like tears in the rain') + }) port.close(err => { assert.instanceOf(err, Error) done() @@ -557,11 +465,11 @@ describe('SerialPort', () => { }) it('handles errors in event', done => { - const port = new SerialPort('/dev/exists') - sinon.stub(port.binding, 'close').callsFake(() => { - return Promise.reject(new Error('attack ships on fire off the shoulder of Orion')) - }) + const port = new SerialPortStream(openOpts) port.on('open', () => { + sinon.stub(port.port!, 'close').callsFake(async () => { + throw new Error('attack ships on fire off the shoulder of Orion') + }) port.close() }) port.on('error', err => { @@ -573,35 +481,25 @@ describe('SerialPort', () => { describe('#update', () => { it('errors when the port is not open', done => { - const port = new SerialPort('/dev/exists', { autoOpen: false }) - port.update({}, err => { + const port = new SerialPortStream({ ...openOpts, autoOpen: false }) + port.update({ baudRate: 2 }, err => { assert.instanceOf(err, Error) done() }) }) - it('errors when called without options', done => { - const port = new SerialPort('/dev/exists', { autoOpen: false }) - let errors = 0 - try { - port.update() - } catch (e) { - errors += 1 - assert.instanceOf(e, TypeError) - } - - try { - port.update(() => {}) - } catch (e) { - errors += 1 - assert.instanceOf(e, TypeError) - } - assert.equal(errors, 2) - done() + it('errors when called with bad options', done => { + const port = new SerialPortStream(openOpts) + port.once('open', () => { + port.update({} as any, err => { + assert.instanceOf(err, Error) + done() + }) + }) }) it('can be called without callback', done => { - const port = new SerialPort('/dev/exists', () => { + const port = new SerialPortStream(openOpts, () => { assert.equal(port.baudRate, 9600) port.update({ baudRate: 14400 }) done() @@ -609,7 +507,7 @@ describe('SerialPort', () => { }) it('sets the baudRate on the port', done => { - const port = new SerialPort('/dev/exists', () => { + const port = new SerialPortStream(openOpts, () => { assert.equal(port.baudRate, 9600) port.update({ baudRate: 14400 }, err => { assert.equal(port.baudRate, 14400) @@ -620,12 +518,12 @@ describe('SerialPort', () => { }) it('handles errors in callback', done => { - const port = new SerialPort('/dev/exists') - sinon.stub(port.binding, 'update').callsFake(() => { - return Promise.reject(new Error('like tears in the rain')) - }) + const port = new SerialPortStream(openOpts) port.on('open', () => { - port.update({}, err => { + sinon.stub(port.port!, 'update').callsFake(() => { + return Promise.reject(new Error('like tears in the rain')) + }) + port.update({} as any, err => { assert.instanceOf(err, Error) done() }) @@ -633,12 +531,12 @@ describe('SerialPort', () => { }) it('handles errors in event', done => { - const port = new SerialPort('/dev/exists') - sinon.stub(port.binding, 'update').callsFake(() => { - return Promise.reject(new Error('attack ships on fire off the shoulder of Orion')) - }) + const port = new SerialPortStream(openOpts) port.on('open', () => { - port.update({}) + sinon.stub(port.port!, 'update').callsFake(() => { + return Promise.reject(new Error('attack ships on fire off the shoulder of Orion')) + }) + port.update({} as any) }) port.on('error', err => { assert.instanceOf(err, Error) @@ -649,34 +547,23 @@ describe('SerialPort', () => { describe('#set', () => { it('errors when serialport not open', done => { - const port = new SerialPort('/dev/exists', { autoOpen: false }) + const port = new SerialPortStream({ ...openOpts, autoOpen: false }) port.set({}, err => { assert.instanceOf(err, Error) done() }) }) - it('errors without an options object', done => { - const port = new SerialPort('/dev/exists', { autoOpen: false }) - try { - port.set() - } catch (e) { - assert.instanceOf(e, TypeError) - done() - } - }) - it('sets the flags on the ports bindings', done => { const settings = { brk: true, cts: true, dtr: true, - dts: true, rts: true, } - const port = new SerialPort('/dev/exists', () => { - const spy = sandbox.spy(port.binding, 'set') + const port = new SerialPortStream(openOpts, () => { + const spy = sandbox.spy(port.port!, 'set') port.set(settings, err => { assert.isNull(err) assert(spy.calledWith(settings)) @@ -688,7 +575,6 @@ describe('SerialPort', () => { it('sets missing options to default values', done => { const settings = { cts: true, - dts: true, rts: false, } @@ -696,12 +582,11 @@ describe('SerialPort', () => { brk: false, cts: true, dtr: true, - dts: true, rts: false, } - const port = new SerialPort('/dev/exists', () => { - const spy = sandbox.spy(port.binding, 'set') + const port = new SerialPortStream(openOpts, () => { + const spy = sandbox.spy(port.port!, 'set') port.set(settings, err => { assert.isNull(err) assert(spy.calledWith(filledWithMissing)) @@ -715,12 +600,11 @@ describe('SerialPort', () => { brk: false, cts: false, dtr: true, - dts: false, rts: true, } - const port = new SerialPort('/dev/exists', () => { - const spy = sandbox.spy(port.binding, 'set') + const port = new SerialPortStream(openOpts, () => { + const spy = sandbox.spy(port.port!, 'set') port.set({}, err => { assert.isNull(err) assert(spy.calledWith(defaults)) @@ -730,11 +614,11 @@ describe('SerialPort', () => { }) it('handles errors in callback', done => { - const port = new SerialPort('/dev/exists') - sinon.stub(port.binding, 'set').callsFake(() => { - return Promise.reject(new Error('like tears in the rain')) - }) - port.on('open', () => { + const port = new SerialPortStream(openOpts) + port.once('open', () => { + sinon.stub(port.port!, 'set').callsFake(() => { + return Promise.reject(new Error('like tears in the rain')) + }) port.set({}, err => { assert.instanceOf(err, Error) done() @@ -743,11 +627,11 @@ describe('SerialPort', () => { }) it('handles errors in event', done => { - const port = new SerialPort('/dev/exists') - sinon.stub(port.binding, 'set').callsFake(() => { - return Promise.reject(new Error('attack ships on fire off the shoulder of Orion')) - }) - port.on('open', () => { + const port = new SerialPortStream(openOpts) + port.once('open', () => { + sinon.stub(port.port!, 'set').callsFake(() => { + return Promise.reject(new Error('attack ships on fire off the shoulder of Orion')) + }) port.set({}) }) port.on('error', err => { @@ -759,7 +643,7 @@ describe('SerialPort', () => { describe('#flush', () => { it('errors when serialport not open', done => { - const port = new SerialPort('/dev/exists', { autoOpen: false }) + const port = new SerialPortStream({ ...openOpts, autoOpen: false }) port.flush(err => { assert.instanceOf(err, Error) done() @@ -767,9 +651,9 @@ describe('SerialPort', () => { }) it('calls flush on the bindings', done => { - const port = new SerialPort('/dev/exists') - const spy = sinon.spy(port.binding, 'flush') + const port = new SerialPortStream(openOpts) port.on('open', () => { + const spy = sinon.spy(port.port!, 'flush') port.flush(err => { assert.isNull(err) assert(spy.calledOnce) @@ -779,11 +663,11 @@ describe('SerialPort', () => { }) it('handles errors in callback', done => { - const port = new SerialPort('/dev/exists') - sinon.stub(port.binding, 'flush').callsFake(() => { - return Promise.reject(new Error('like tears in the rain')) - }) + const port = new SerialPortStream(openOpts) port.on('open', () => { + sinon.stub(port.port!, 'flush').callsFake(() => { + return Promise.reject(new Error('like tears in the rain')) + }) port.flush(err => { assert.instanceOf(err, Error) done() @@ -792,11 +676,11 @@ describe('SerialPort', () => { }) it('handles errors in event', done => { - const port = new SerialPort('/dev/exists') - sinon.stub(port.binding, 'flush').callsFake(() => { - return Promise.reject(new Error('attack ships on fire off the shoulder of Orion')) - }) + const port = new SerialPortStream(openOpts) port.on('open', () => { + sinon.stub(port.port!, 'flush').callsFake(() => { + return Promise.reject(new Error('attack ships on fire off the shoulder of Orion')) + }) port.flush() }) port.on('error', err => { @@ -808,7 +692,7 @@ describe('SerialPort', () => { describe('#drain', () => { it('waits for an open port', done => { - const port = new SerialPort('/dev/exists', { autoOpen: false }) + const port = new SerialPortStream({ ...openOpts, autoOpen: false }) port.drain(err => { assert.isNull(err) done() @@ -817,9 +701,9 @@ describe('SerialPort', () => { }) it('calls drain on the bindings', done => { - const port = new SerialPort('/dev/exists') - const spy = sinon.spy(port.binding, 'drain') + const port = new SerialPortStream(openOpts) port.on('open', () => { + const spy = sinon.spy(port.port!, 'drain') port.drain(err => { assert.isNull(err) assert(spy.calledOnce) @@ -829,11 +713,11 @@ describe('SerialPort', () => { }) it('handles errors in callback', done => { - const port = new SerialPort('/dev/exists') - sinon.stub(port.binding, 'drain').callsFake(() => { - return Promise.reject(new Error('like tears in the rain')) - }) + const port = new SerialPortStream(openOpts) port.on('open', () => { + sinon.stub(port.port!, 'drain').callsFake(() => { + return Promise.reject(new Error('like tears in the rain')) + }) port.drain(err => { assert.instanceOf(err, Error) done() @@ -842,11 +726,11 @@ describe('SerialPort', () => { }) it('handles errors in event', done => { - const port = new SerialPort('/dev/exists') - sinon.stub(port.binding, 'drain').callsFake(() => { - return Promise.reject(new Error('attack ships on fire off the shoulder of Orion')) - }) + const port = new SerialPortStream(openOpts) port.on('open', () => { + sinon.stub(port.port!, 'drain').callsFake(() => { + return Promise.reject(new Error('attack ships on fire off the shoulder of Orion')) + }) port.drain() }) port.on('error', err => { @@ -856,7 +740,7 @@ describe('SerialPort', () => { }) it('waits for in progress or queued writes to finish', done => { - const port = new SerialPort('/dev/exists') + const port = new SerialPortStream(openOpts) port.on('error', done) let finishedWrite = false port.write(Buffer.alloc(10), () => { @@ -871,7 +755,7 @@ describe('SerialPort', () => { describe('#get', () => { it('errors when serialport not open', done => { - const port = new SerialPort('/dev/exists', { autoOpen: false }) + const port = new SerialPortStream({ ...openOpts, autoOpen: false }) port.get(err => { assert.instanceOf(err, Error) done() @@ -879,8 +763,8 @@ describe('SerialPort', () => { }) it('gets the status from the ports bindings', done => { - const port = new SerialPort('/dev/exists', () => { - const spy = sandbox.spy(port.binding, 'get') + const port = new SerialPortStream(openOpts, () => { + const spy = sandbox.spy(port.port!, 'get') port.get((err, status) => { assert.isNull(err) assert(spy.calledOnce) @@ -895,49 +779,35 @@ describe('SerialPort', () => { }) it('handles errors in callback', done => { - const port = new SerialPort('/dev/exists') - sinon.stub(port.binding, 'get').callsFake(() => { - return Promise.reject(new Error('like tears in the rain')) - }) + const port = new SerialPortStream(openOpts) port.on('open', () => { + sinon.stub(port.port!, 'get').callsFake(() => { + return Promise.reject(new Error('like tears in the rain')) + }) port.get(err => { assert.instanceOf(err, Error) done() }) }) }) - - it('handles errors in event', done => { - const port = new SerialPort('/dev/exists') - sinon.stub(port.binding, 'get').callsFake(() => { - return Promise.reject(new Error('attack ships on fire off the shoulder of Orion')) - }) - port.on('open', () => { - port.get() - }) - port.on('error', err => { - assert.instanceOf(err, Error) - done() - }) - }) }) }) describe('reading data', () => { it('emits data events by default', done => { const testData = Buffer.from('I am a really short string') - const port = new SerialPort('/dev/exists', () => { + const port = new SerialPortStream(openOpts, () => { port.once('data', recvData => { assert.deepEqual(recvData, testData) done() }) - port.binding.write(testData) + port.port!.write(testData) }) }) it('emits data events with resuming', async () => { const testData = Buffer.from('I am a really short string') - const port = new SerialPort('/dev/exists', { bindingOptions: { echo: true } }) + const port = new SerialPortStream({ ...openOpts }) await new Promise(resolve => port.on('open', resolve)) await new Promise(resolve => port.write(testData, resolve)) await new Promise(resolve => port.once('readable', resolve)) @@ -950,7 +820,8 @@ describe('SerialPort', () => { it('reads more data than the high water mark', async () => { const testData = randomBytes((64 * 1024) / 2 + 1) - const port = new SerialPort('/dev/exists', { bindingOptions: { echo: true } }) + MockBinding.createPort('/dev/exists', { echo: true, maxReadSize: testData.length }) + const port = new SerialPortStream(openOpts) await new Promise(resolve => port.on('open', resolve)) await new Promise(resolve => port.write(testData, resolve)) await new Promise(resolve => port.once('readable', resolve)) @@ -965,7 +836,8 @@ describe('SerialPort', () => { it('reads more data than the high water mark at once', async () => { const testData = randomBytes(64 * 1024 + 1) - const port = new SerialPort('/dev/exists', { bindingOptions: { echo: true } }) + MockBinding.createPort('/dev/exists', { echo: true, maxReadSize: testData.length }) + const port = new SerialPortStream(openOpts) await new Promise(resolve => port.on('open', resolve)) await new Promise(resolve => port.write(testData, resolve)) await new Promise(resolve => port.once('readable', resolve)) @@ -976,30 +848,31 @@ describe('SerialPort', () => { }) it("doesn't error if the port is closed when reading", async () => { - const port = new SerialPort('/dev/exists') + const port = new SerialPortStream(openOpts) await new Promise(resolve => port.on('open', resolve)) port.read() port.read() let err = null port.on('error', error => (err = error)) - await new Promise((resolve, reject) => port.close(err => (err ? reject(err) : resolve()))) + await new Promise((resolve, reject) => port.close(err => (err ? reject(err) : resolve()))) assert.isNull(err) }) }) describe('disconnect close errors', () => { it('emits as a disconnected close event on a bad read', done => { - const port = new SerialPort('/dev/exists') - sinon.stub(port.binding, 'read').callsFake(async () => { - throw new Error('EBAD_ERR') + const port = new SerialPortStream(openOpts, () => { + sinon.stub(port.port!, 'read').callsFake(async () => { + throw new Error('EBAD_ERR') + }) + port.read() }) - port.on('close', err => { - assert.instanceOf(err, Error) - assert.isTrue(err.disconnected) + port.on('close', (err: DisconnectedError | undefined) => { + assert.instanceOf(err, DisconnectedError) + assert.isTrue(err!.disconnected) done() }) port.on('error', done) // this shouldn't be called - port.read() }) }) }) diff --git a/packages/stream/lib/index.ts b/packages/stream/lib/index.ts new file mode 100644 index 000000000..30dc3358a --- /dev/null +++ b/packages/stream/lib/index.ts @@ -0,0 +1,521 @@ +import { Duplex } from 'stream' +import debugFactory from 'debug' +import { SetOptions, BindingInterface, PortInterfaceFromBinding, OpenOptions as BindingOpenOptions } from '@serialport/bindings-interface' +const debug = debugFactory('serialport/stream') + +interface InternalSettings extends OpenOptions { + autoOpen: boolean + endOnClose: boolean + highWaterMark: number +} + +const defaultSetFlags: SetOptions = { + brk: false, + cts: false, + dtr: true, + rts: true, +} + +interface PoolBuffer extends Buffer { + used: number +} + +function allocNewReadPool(poolSize: number): PoolBuffer { + const pool = Buffer.allocUnsafe(poolSize) + ;(pool as PoolBuffer).used = 0 + return pool as PoolBuffer +} + +/** + * A callback called with an error or an object with the modem line values (cts, dsr, dcd). + */ +export type ErrorCallback = (err: Error | null) => void + +export type ModemBitsCallback = (err: Error | null, options?: { cts: boolean; dsr: boolean; dcd: boolean }) => void + +/** + * Options to open a port + */ +export interface OpenOptions extends BindingOpenOptions { + /** + * The hardware access binding. `Bindings` are how Node-Serialport talks to the underlying system. By default we auto detect Windows (`WindowsBinding`), Linux (`LinuxBinding`) and OS X (`DarwinBinding`) and load the appropriate module for your system. + */ + binding: T + + /** Automatically opens the port defaults to true*/ + autoOpen?: boolean + + /** + * The size of the read and write buffers defaults to 64k + */ + highWaterMark?: number + + /** + * Emit 'end' on port close defaults false + */ + endOnClose?: boolean +} + +export class DisconnectedError extends Error { + disconnected: true + constructor(message: string) { + super(message) + this.disconnected = true + } +} + +export class SerialPortStream extends Duplex { + port?: PortInterfaceFromBinding + private _pool: PoolBuffer + private _kMinPoolSpace: number + opening: boolean + closing: boolean + readonly settings: InternalSettings + + /** + * Create a new serial port object for the `path`. In the case of invalid arguments or invalid options, when constructing a new SerialPort it will throw an error. The port will open automatically by default, which is the equivalent of calling `port.open(openCallback)` in the next tick. You can disable this by setting the option `autoOpen` to `false`. + * @param {OpenOptions=} options - Port configuration options + * @param {ErrorCallback=} openCallback - If `autoOpen` is true (the default) it will be provided to `port.open()` and run after the port is opened. The callback will be ignored if `autoOpen` is set to `false`. + * @property {number} baudRate The port's baudRate. Use `.update` to change it. Read-only. + * @property {string} path The system path or name of the serial port. Read-only. + * @property {boolean} isOpen `true` if the port is open, `false` otherwise. Read-only. + * @property {InternalSettings} settings The current settings of the port + * @throws {TypeError} When given invalid arguments, a `TypeError` will be thrown. + * @emits open + * @emits data + * @emits close + * @emits error + */ + constructor(options: OpenOptions, openCallback?: ErrorCallback) { + const settings = { + autoOpen: true, + endOnClose: false, + highWaterMark: 64 * 1024, + ...options, + } + + super({ + highWaterMark: settings.highWaterMark, + }) + + if (!settings.binding) { + throw new TypeError('"Bindings" is invalid pass it as `options.binding`') + } + + if (!settings.path) { + throw new TypeError(`"path" is not defined: ${settings.path}`) + } + + if (typeof settings.baudRate !== 'number') { + throw new TypeError(`"baudRate" must be a number: ${settings.baudRate}`) + } + + this.settings = settings + + this.opening = false + this.closing = false + this._pool = allocNewReadPool(this.settings.highWaterMark) + this._kMinPoolSpace = 128 + + if (this.settings.autoOpen) { + this.open(openCallback) + } + } + + get path(): string { + return this.settings.path + } + + get baudRate(): number { + return this.settings.baudRate + } + + get isOpen(): boolean { + return (this.port?.isOpen ?? false) && !this.closing + } + + private _error(error: Error, callback?: ErrorCallback) { + if (callback) { + callback.call(this, error) + } else { + this.emit('error', error) + } + } + + private _asyncError(error: Error, callback?: ErrorCallback) { + process.nextTick(() => this._error(error, callback)) + } + + /** + * Opens a connection to the given serial port. + * @param {ErrorCallback=} openCallback - Called after a connection is opened. If this is not provided and an error occurs, it will be emitted on the port's `error` event. + * @emits open + */ + open(openCallback?: ErrorCallback): void { + if (this.isOpen) { + return this._asyncError(new Error('Port is already open'), openCallback) + } + + if (this.opening) { + return this._asyncError(new Error('Port is opening'), openCallback) + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { highWaterMark, binding, autoOpen, endOnClose, ...openOptions } = this.settings + + this.opening = true + debug('opening', `path: ${this.path}`) + this.settings.binding.open(openOptions).then( + port => { + debug('opened', `path: ${this.path}`) + this.port = port as PortInterfaceFromBinding + this.opening = false + this.emit('open') + if (openCallback) { + openCallback.call(this, null) + } + }, + err => { + this.opening = false + debug('Binding #open had an error', err) + this._error(err, openCallback) + } + ) + } + + /** + * Changes the baud rate for an open port. Emits an error or calls the callback if the baud rate isn't supported. + * @param {object=} options Only supports `baudRate`. + * @param {number=} [options.baudRate] The baud rate of the port to be opened. This should match one of the commonly available baud rates, such as 110, 300, 1200, 2400, 4800, 9600, 14400, 19200, 38400, 57600, or 115200. Custom rates are supported best effort per platform. The device connected to the serial port is not guaranteed to support the requested baud rate, even if the port itself supports that baud rate. + * @param {ErrorCallback=} [callback] Called once the port's baud rate changes. If `.update` is called without a callback, and there is an error, an error event is emitted. + * @returns {undefined} + */ + update(options: { baudRate: number }, callback?: ErrorCallback) { + if (!this.isOpen || !this.port) { + debug('update attempted, but port is not open') + return this._asyncError(new Error('Port is not open'), callback) + } + + debug('update', `baudRate: ${options.baudRate}`) + this.port.update(options).then( + () => { + debug('binding.update', 'finished') + this.settings.baudRate = options.baudRate + if (callback) { + callback.call(this, null) + } + }, + err => { + debug('binding.update', 'error', err) + return this._error(err, callback) + } + ) + } + + /** + * Writes data to the given serial port. Buffers written data if the port is not open. + + The write operation is non-blocking. When it returns, data might still not have been written to the serial port. See `drain()`. + + Some devices, like the Arduino, reset when you open a connection to them. In such cases, immediately writing to the device will cause lost data as they wont be ready to receive the data. This is often worked around by having the Arduino send a "ready" byte that your Node program waits for before writing. You can also often get away with waiting around 400ms. + + If a port is disconnected during a write, the write will error in addition to the `close` event. + + From the [stream docs](https://nodejs.org/api/stream.html#stream_writable_write_chunk_encoding_callback) write errors don't always provide the error in the callback, sometimes they use the error event. + > If an error occurs, the callback may or may not be called with the error as its first argument. To reliably detect write errors, add a listener for the 'error' event. + + In addition to the usual `stream.write` arguments (`String` and `Buffer`), `write()` can accept arrays of bytes (positive numbers under 256) which is passed to `Buffer.from([])` for conversion. This extra functionality is pretty sweet. + + * @param {(string|array|buffer)} data Accepts a [`Buffer`](http://nodejs.org/api/buffer.html) object, or a type that is accepted by the `Buffer.from` method (e.g. an array of bytes or a string). + * @param {string=} encoding The encoding, if chunk is a string. Defaults to `'utf8'`. Also accepts `'ascii'`, `'base64'`, `'binary'`, and `'hex'` See [Buffers and Character Encodings](https://nodejs.org/api/buffer.html#buffer_buffers_and_character_encodings) for all available options. + * @param {function=} errorCallback Called once the write operation finishes. Data may not yet be flushed to the underlying port. No optional Error. + * @returns {boolean} `false` if the stream wishes for the calling code to wait for the `'drain'` event to be emitted before continuing to write additional data; otherwise `true`. + */ + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + write(chunk: any, encoding?: BufferEncoding, cb?: (error: Error | null | undefined) => void): boolean + // eslint-disable-next-line @typescript-eslint/no-explicit-any + write(chunk: any, cb?: (error: Error | null | undefined) => void): boolean + write( + data: string | Buffer | number[], + encoding?: BufferEncoding | ((error: Error | null | undefined) => void), + callback?: (error: Error | null | undefined) => void + ) { + if (Array.isArray(data)) { + data = Buffer.from(data) + } + if (typeof encoding === 'function') { + return super.write(data, encoding) + } + return super.write(data, encoding, callback) + } + + _write(data: Buffer, encoding: BufferEncoding | undefined, callback: (error: Error | null) => void) { + if (!this.isOpen || !this.port) { + this.once('open', () => { + this._write(data, encoding, callback) + }) + return + } + debug('_write', `${data.length} bytes of data`) + this.port.write(data).then( + () => { + debug('binding.write', 'write finished') + callback(null) + }, + err => { + debug('binding.write', 'error', err) + if (!err.canceled) { + this._disconnected(err) + } + callback(err) + } + ) + } + + _writev(data: Array<{ chunk: Buffer; encoding: BufferEncoding }>, callback: ErrorCallback) { + debug('_writev', `${data.length} chunks of data`) + const dataV = data.map(write => write.chunk) + this._write(Buffer.concat(dataV), undefined, callback) + } + + _read(bytesToRead: number) { + if (!this.isOpen || !this.port) { + debug('_read', 'queueing _read for after open') + this.once('open', () => { + this._read(bytesToRead) + }) + return + } + + if (!this._pool || this._pool.length - this._pool.used < this._kMinPoolSpace) { + debug('_read', 'discarding the read buffer pool because it is below kMinPoolSpace') + this._pool = allocNewReadPool(this.settings.highWaterMark) + } + + // Grab another reference to the pool in the case that while we're + // in the thread pool another read() finishes up the pool, and + // allocates a new one. + const pool = this._pool + // Read the smaller of rest of the pool or however many bytes we want + const toRead = Math.min(pool.length - pool.used, bytesToRead) + const start = pool.used + + // the actual read. + debug('_read', `reading`, { start, toRead }) + this.port.read(pool, start, toRead).then( + ({ bytesRead }) => { + debug('binding.read', `finished`, { bytesRead }) + // zero bytes means read means we've hit EOF? Maybe this should be an error + if (bytesRead === 0) { + debug('binding.read', 'Zero bytes read closing readable stream') + this.push(null) + return + } + pool.used += bytesRead + this.push(pool.slice(start, start + bytesRead)) + }, + err => { + debug('binding.read', `error`, err) + if (!err.canceled) { + this._disconnected(err) + } + this._read(bytesToRead) // prime to read more once we're reconnected + } + ) + } + + _disconnected(err: Error) { + if (!this.isOpen) { + debug('disconnected aborted because already closed', err) + return + } + debug('disconnected', err) + this.close(undefined, new DisconnectedError(err.message)) + } + + /** + * Closes an open connection. + * + * If there are in progress writes when the port is closed the writes will error. + * @param {ErrorCallback} callback Called once a connection is closed. + * @param {Error} disconnectError used internally to propagate a disconnect error + */ + close(callback?: ErrorCallback | undefined, disconnectError: Error | null = null): void { + if (!this.isOpen || !this.port) { + debug('close attempted, but port is not open') + return this._asyncError(new Error('Port is not open'), callback) + } + + this.closing = true + debug('#close') + this.port.close().then( + () => { + this.closing = false + debug('binding.close', 'finished') + this.emit('close', disconnectError) + if (this.settings.endOnClose) { + this.emit('end') + } + if (callback) { + callback.call(this, disconnectError) + } + }, + err => { + this.closing = false + debug('binding.close', 'had an error', err) + return this._error(err, callback) + } + ) + } + + /** + * Set control flags on an open port. Uses [`SetCommMask`](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363257(v=vs.85).aspx) for Windows and [`ioctl`](http://linux.die.net/man/4/tty_ioctl) for OS X and Linux. + * + * All options are operating system default when the port is opened. Every flag is set on each call to the provided or default values. If options isn't provided default options is used. + */ + set(options: SetOptions, callback?: ErrorCallback): void { + if (!this.isOpen || !this.port) { + debug('set attempted, but port is not open') + return this._asyncError(new Error('Port is not open'), callback) + } + + const settings = { ...defaultSetFlags, ...options } + debug('#set', settings) + this.port.set(settings).then( + () => { + debug('binding.set', 'finished') + if (callback) { + callback.call(this, null) + } + }, + err => { + debug('binding.set', 'had an error', err) + return this._error(err, callback) + } + ) + } + + /** + * Returns the control flags (CTS, DSR, DCD) on the open port. + * Uses [`GetCommModemStatus`](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363258(v=vs.85).aspx) for Windows and [`ioctl`](http://linux.die.net/man/4/tty_ioctl) for mac and linux. + */ + get(callback: ModemBitsCallback): void { + if (!this.isOpen || !this.port) { + debug('get attempted, but port is not open') + return this._asyncError(new Error('Port is not open'), callback) + } + + debug('#get') + this.port.get().then( + status => { + debug('binding.get', 'finished') + callback.call(this, null, status) + }, + err => { + debug('binding.get', 'had an error', err) + return this._error(err, callback) + } + ) + } + + /** + * Flush discards data received but not read, and written but not transmitted by the operating system. For more technical details, see [`tcflush(fd, TCIOFLUSH)`](http://linux.die.net/man/3/tcflush) for Mac/Linux and [`FlushFileBuffers`](http://msdn.microsoft.com/en-us/library/windows/desktop/aa364439) for Windows. + */ + flush(callback?: ErrorCallback) { + if (!this.isOpen || !this.port) { + debug('flush attempted, but port is not open') + return this._asyncError(new Error('Port is not open'), callback) + } + + debug('#flush') + this.port.flush().then( + () => { + debug('binding.flush', 'finished') + if (callback) { + callback.call(this, null) + } + }, + err => { + debug('binding.flush', 'had an error', err) + return this._error(err, callback) + } + ) + } + + /** + * Waits until all output data is transmitted to the serial port. After any pending write has completed it calls [`tcdrain()`](http://linux.die.net/man/3/tcdrain) or [FlushFileBuffers()](https://msdn.microsoft.com/en-us/library/windows/desktop/aa364439(v=vs.85).aspx) to ensure it has been written to the device. + * @example + Write the `data` and wait until it has finished transmitting to the target serial port before calling the callback. This will queue until the port is open and writes are finished. + + ```js + function writeAndDrain (data, callback) { + port.write(data); + port.drain(callback); + } + ``` + */ + drain(callback?: ErrorCallback): void { + debug('drain') + if (!this.isOpen || !this.port) { + debug('drain queuing on port open') + this.once('open', () => { + this.drain(callback) + }) + return + } + this.port.drain().then( + () => { + debug('binding.drain', 'finished') + if (callback) { + callback.call(this, null) + } + }, + err => { + debug('binding.drain', 'had an error', err) + return this._error(err, callback) + } + ) + } +} + +/** + * The `error` event's callback is called with an error object whenever there is an error. + * @event error + */ + +/** + * The `open` event's callback is called with no arguments when the port is opened and ready for writing. This happens if you have the constructor open immediately (which opens in the next tick) or if you open the port manually with `open()`. See [Useage/Opening a Port](#opening-a-port) for more information. + * @event open + */ + +/** + * Request a number of bytes from the SerialPort. The `read()` method pulls some data out of the internal buffer and returns it. If no data is available to be read, null is returned. By default, the data is returned as a `Buffer` object unless an encoding has been specified using the `.setEncoding()` method. + * @method SerialPort.prototype.read + * @param {number=} size Specify how many bytes of data to return, if available + * @returns {(string|Buffer|null)} The data from internal buffers + */ + +/** + * Listening for the `data` event puts the port in flowing mode. Data is emitted as soon as it's received. Data is a `Buffer` object with a varying amount of data in it. The `readLine` parser converts the data into string lines. See the [parsers](https://serialport.io/docs/api-parsers-overview) section for more information on parsers, and the [Node.js stream documentation](https://nodejs.org/api/stream.html#stream_event_data) for more information on the data event. + * @event data + */ + +/** + * The `close` event's callback is called with no arguments when the port is closed. In the case of a disconnect it will be called with a Disconnect Error object (`err.disconnected == true`). In the event of a close error (unlikely), an error event is triggered. + * @event close + */ + +/** + * The `pause()` method causes a stream in flowing mode to stop emitting 'data' events, switching out of flowing mode. Any data that becomes available remains in the internal buffer. + * @method SerialPort.prototype.pause + * @see resume + * @returns `this` + */ + +/** + * The `resume()` method causes an explicitly paused, `Readable` stream to resume emitting 'data' events, switching the stream into flowing mode. + * @method SerialPort.prototype.resume + * @see pause + * @returns `this` + */ diff --git a/packages/stream/package-lock.json b/packages/stream/package-lock.json index 24c6e6b9c..e13a8b4fe 100644 --- a/packages/stream/package-lock.json +++ b/packages/stream/package-lock.json @@ -9,8 +9,12 @@ "version": "10.1.0", "license": "MIT", "dependencies": { + "@serialport/bindings-interface": "1.2.0", "debug": "^4.3.2" }, + "devDependencies": { + "typescript": "^4.5.5" + }, "engines": { "node": ">=12.0.0" }, @@ -18,6 +22,14 @@ "url": "https://opencollective.com/serialport/donate" } }, + "node_modules/@serialport/bindings-interface": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@serialport/bindings-interface/-/bindings-interface-1.2.0.tgz", + "integrity": "sha512-TLg6z0wyEdfVBIGmRwv0oru7Ijdc3VhH5SDv+q7UXuXvNh+mEpsFedHG205GSGVXbnF1xlCN5cBSMloI74N/gQ==", + "engines": { + "node": "^12.22 || ^14.13 || >=16" + } + }, "node_modules/debug": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", @@ -38,9 +50,27 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/typescript": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", + "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } } }, "dependencies": { + "@serialport/bindings-interface": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@serialport/bindings-interface/-/bindings-interface-1.2.0.tgz", + "integrity": "sha512-TLg6z0wyEdfVBIGmRwv0oru7Ijdc3VhH5SDv+q7UXuXvNh+mEpsFedHG205GSGVXbnF1xlCN5cBSMloI74N/gQ==" + }, "debug": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", @@ -53,6 +83,12 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "typescript": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", + "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", + "dev": true } } } diff --git a/packages/stream/package.json b/packages/stream/package.json index 2ad97d1f2..1135a2157 100644 --- a/packages/stream/package.json +++ b/packages/stream/package.json @@ -1,12 +1,17 @@ { "name": "@serialport/stream", "version": "10.1.0", - "main": "lib", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "scripts": { + "build": "tsc --build tsconfig-build.json" + }, "dependencies": { + "@serialport/bindings-interface": "1.2.0", "debug": "^4.3.2" }, "devDependencies": { - "@serialport/binding-mock": "10.1.0" + "typescript": "^4.5.5" }, "engines": { "node": ">=12.0.0" diff --git a/packages/stream/tsconfig-build.json b/packages/stream/tsconfig-build.json new file mode 100644 index 000000000..a4d6e59ac --- /dev/null +++ b/packages/stream/tsconfig-build.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig-build.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "lib", + }, + "exclude": [ + "node_modules", + "**/*.test.ts", + "dist", + ], +} diff --git a/packages/stream/tsconfig.json b/packages/stream/tsconfig.json new file mode 100644 index 000000000..6f83eb665 --- /dev/null +++ b/packages/stream/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tsconfig.json", +} diff --git a/packages/terminal/.npmignore b/packages/terminal/.npmignore index a1623b1ea..df4db6a44 100644 --- a/packages/terminal/.npmignore +++ b/packages/terminal/.npmignore @@ -1,3 +1,7 @@ .DS_Store *.test.js CHANGELOG.md +lib +tsconfig.json +tsconfig-build.json +tsconfig-build.tsbuildinfo diff --git a/packages/terminal/lib/index.js b/packages/terminal/lib/index.ts similarity index 72% rename from packages/terminal/lib/index.js rename to packages/terminal/lib/index.ts index 7a8663ba5..890a46b9d 100755 --- a/packages/terminal/lib/index.js +++ b/packages/terminal/lib/index.ts @@ -1,15 +1,15 @@ #!/usr/bin/env node -const { Select } = require('enquirer') -const { program } = require('commander') -const SerialPort = require('@serialport/stream') +import Enquirer from 'enquirer' +import { program } from 'commander' +import { SerialPortStream } from '@serialport/stream' +import { OutputTranslator } from './output-translator' +import { autoDetect } from '@serialport/bindings-cpp' const { version } = require('../package.json') -const { OutputTranslator } = require('./output-translator') -const { autoDetect } = require('@serialport/bindings-cpp') -SerialPort.Binding = autoDetect() +const binding = autoDetect() -const makeNumber = input => Number(input) +const makeNumber = (input: string) => Number(input) program .version(version) @@ -25,23 +25,34 @@ program .option('--flow-ctl ', 'Enable flow control {XONOFF | RTSCTS}.') .parse(process.argv) -const args = program.opts() +const args = program.opts<{ + list: boolean + path?: string + baud: number + databits: 8 | 7 | 6 | 5 + parity: string + stopbits: 1 | 2 | 3 + echo: boolean + flowCtl?: string +}>() const listPorts = async () => { - const ports = await SerialPort.list() + const ports = await binding.list() for (const port of ports) { console.log(`${port.path}\t${port.pnpId || ''}\t${port.manufacturer || ''}`) } } const askForPort = async () => { - const ports = await SerialPort.list() + const ports = await binding.list() if (ports.length === 0) { console.error('No ports detected and none specified') process.exit(2) } - const answer = await new Select({ + // Error in Enquirer types + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const answer = await new (Enquirer as any).Select({ name: 'serial-port-selection', message: 'Select a serial port to open', choices: ports.map((port, i) => ({ @@ -50,13 +61,15 @@ const askForPort = async () => { })), required: true, }).run() - return answer + return answer as string } -const createPort = path => { +const createPort = (path: string) => { console.log(`Opening serial port: ${path} echo: ${args.echo}`) const openOptions = { + path, + binding, baudRate: args.baud, dataBits: args.databits, parity: args.parity, @@ -66,17 +79,17 @@ const createPort = path => { xoff: args.flowCtl === 'XONOFF', } - const port = new SerialPort(path, openOptions) + const port = new SerialPortStream(openOptions) const output = new OutputTranslator() output.pipe(process.stdout) port.pipe(output) - port.on('error', err => { + port.on('error', (err: Error) => { console.error('Error', err) process.exit(1) }) - port.on('close', err => { + port.on('close', (err?: Error) => { console.log('Closed', err) process.exit(err ? 1 : 0) }) diff --git a/packages/terminal/lib/output-translator.js b/packages/terminal/lib/output-translator.ts similarity index 59% rename from packages/terminal/lib/output-translator.js rename to packages/terminal/lib/output-translator.ts index 0b1971e9a..0862afdbe 100644 --- a/packages/terminal/lib/output-translator.js +++ b/packages/terminal/lib/output-translator.ts @@ -1,10 +1,10 @@ -const { Transform } = require('stream') +import { Transform } from 'stream' /** * Convert carriage returns to newlines for output */ -class OutputTranslator extends Transform { - _transform(chunk, _encoding, cb) { +export class OutputTranslator extends Transform { + _transform(chunk: Buffer, _encoding: string, cb: () => void) { for (let index = 0; index < chunk.length; index++) { const byte = chunk[index] if (byte === 0x0d) { @@ -15,4 +15,3 @@ class OutputTranslator extends Transform { cb() } } -module.exports.OutputTranslator = OutputTranslator diff --git a/packages/terminal/package-lock.json b/packages/terminal/package-lock.json index 9b03a17f1..125fc9c8e 100644 --- a/packages/terminal/package-lock.json +++ b/packages/terminal/package-lock.json @@ -9,26 +9,15 @@ "version": "10.1.0", "license": "MIT", "dependencies": { - "@serialport/bindings-cpp": "10.4.0", - "commander": "^8.3.0", - "enquirer": "^2.3.5" + "@serialport/bindings-cpp": "10.6.1", + "commander": "9.0.0", + "enquirer": "2.3.6" }, "bin": { - "serialport-terminal": "lib/index.js" + "serialport-terminal": "dist/index.js" }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://opencollective.com/serialport/donate" - } - }, - "node_modules/@serialport/binding-abstract": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@serialport/binding-abstract/-/binding-abstract-10.0.1.tgz", - "integrity": "sha512-FWD/uNrz8V3kaTILQTK05Z1LB/LZin8XZelmX/wd1NNlRFAj6V64MIESWhwUy3iPnL1QriFR1k7URHHx3RRgfg==", - "dependencies": { - "debug": "^4.3.2" + "devDependencies": { + "typescript": "^4.5.5" }, "engines": { "node": ">=12.0.0" @@ -38,12 +27,12 @@ } }, "node_modules/@serialport/bindings-cpp": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/@serialport/bindings-cpp/-/bindings-cpp-10.4.0.tgz", - "integrity": "sha512-vV5fadfzovV2iZ9xWjlQOdjEL0U0y5ewjdplx1DG5ox+/Q2Mmi8WEbcoKww6AgouU5qLirQGfYDmcWPlcGMdMw==", + "version": "10.6.1", + "resolved": "https://registry.npmjs.org/@serialport/bindings-cpp/-/bindings-cpp-10.6.1.tgz", + "integrity": "sha512-wlgiCoImyA0siK0Z1ziXFDv6Ua3Tko9vMyNPAMW/Ywk18vKVKPYvvGlYxhbdK9LGzGdqVXGWmEW15R/S+anzQQ==", "hasInstallScript": true, "dependencies": { - "@serialport/binding-abstract": "10.0.1", + "@serialport/bindings-interface": "1.2.0", "@serialport/parser-readline": "10.0.1", "debug": "^4.3.2", "node-addon-api": "^4.3.0", @@ -56,6 +45,14 @@ "url": "https://opencollective.com/serialport/donate" } }, + "node_modules/@serialport/bindings-interface": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@serialport/bindings-interface/-/bindings-interface-1.2.0.tgz", + "integrity": "sha512-TLg6z0wyEdfVBIGmRwv0oru7Ijdc3VhH5SDv+q7UXuXvNh+mEpsFedHG205GSGVXbnF1xlCN5cBSMloI74N/gQ==", + "engines": { + "node": "^12.22 || ^14.13 || >=16" + } + }, "node_modules/@serialport/parser-delimiter": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-10.0.1.tgz", @@ -90,17 +87,17 @@ } }, "node_modules/commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.0.0.tgz", + "integrity": "sha512-JJfP2saEKbQqvW+FI93OYUB4ByV5cizMpFMiiJI8xDbBvQvSkIk0VvQdn1CZ8mqAO8Loq2h0gYTYtDFUZUeERw==", "engines": { - "node": ">= 12" + "node": "^12.20.0 || >=14" } }, "node_modules/debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "dependencies": { "ms": "2.1.2" }, @@ -143,29 +140,39 @@ "node-gyp-build-optional": "optional.js", "node-gyp-build-test": "build-test.js" } + }, + "node_modules/typescript": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", + "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } } }, "dependencies": { - "@serialport/binding-abstract": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@serialport/binding-abstract/-/binding-abstract-10.0.1.tgz", - "integrity": "sha512-FWD/uNrz8V3kaTILQTK05Z1LB/LZin8XZelmX/wd1NNlRFAj6V64MIESWhwUy3iPnL1QriFR1k7URHHx3RRgfg==", - "requires": { - "debug": "^4.3.2" - } - }, "@serialport/bindings-cpp": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/@serialport/bindings-cpp/-/bindings-cpp-10.4.0.tgz", - "integrity": "sha512-vV5fadfzovV2iZ9xWjlQOdjEL0U0y5ewjdplx1DG5ox+/Q2Mmi8WEbcoKww6AgouU5qLirQGfYDmcWPlcGMdMw==", + "version": "10.6.1", + "resolved": "https://registry.npmjs.org/@serialport/bindings-cpp/-/bindings-cpp-10.6.1.tgz", + "integrity": "sha512-wlgiCoImyA0siK0Z1ziXFDv6Ua3Tko9vMyNPAMW/Ywk18vKVKPYvvGlYxhbdK9LGzGdqVXGWmEW15R/S+anzQQ==", "requires": { - "@serialport/binding-abstract": "10.0.1", + "@serialport/bindings-interface": "1.2.0", "@serialport/parser-readline": "10.0.1", "debug": "^4.3.2", "node-addon-api": "^4.3.0", "node-gyp-build": "^4.3.0" } }, + "@serialport/bindings-interface": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@serialport/bindings-interface/-/bindings-interface-1.2.0.tgz", + "integrity": "sha512-TLg6z0wyEdfVBIGmRwv0oru7Ijdc3VhH5SDv+q7UXuXvNh+mEpsFedHG205GSGVXbnF1xlCN5cBSMloI74N/gQ==" + }, "@serialport/parser-delimiter": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-10.0.1.tgz", @@ -185,14 +192,14 @@ "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==" }, "commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==" + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.0.0.tgz", + "integrity": "sha512-JJfP2saEKbQqvW+FI93OYUB4ByV5cizMpFMiiJI8xDbBvQvSkIk0VvQdn1CZ8mqAO8Loq2h0gYTYtDFUZUeERw==" }, "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "requires": { "ms": "2.1.2" } @@ -219,6 +226,12 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.3.0.tgz", "integrity": "sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==" + }, + "typescript": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", + "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", + "dev": true } } } diff --git a/packages/terminal/package.json b/packages/terminal/package.json index 0e4530423..f961060f2 100644 --- a/packages/terminal/package.json +++ b/packages/terminal/package.json @@ -1,15 +1,19 @@ { "name": "@serialport/terminal", "version": "10.1.0", - "main": "lib", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "scripts": { + "build": "tsc --build tsconfig-build.json" + }, "bin": { - "serialport-terminal": "./lib/index.js" + "serialport-terminal": "./dist/index.js" }, "dependencies": { - "@serialport/bindings-cpp": "10.4.0", + "@serialport/bindings-cpp": "10.6.1", "@serialport/stream": "10.1.0", - "commander": "^8.3.0", - "enquirer": "^2.3.5" + "commander": "9.0.0", + "enquirer": "2.3.6" }, "engines": { "node": ">=12.0.0" @@ -22,5 +26,8 @@ "type": "git", "url": "git://github.com/serialport/node-serialport.git" }, - "funding": "https://opencollective.com/serialport/donate" + "funding": "https://opencollective.com/serialport/donate", + "devDependencies": { + "typescript": "^4.5.5" + } } diff --git a/packages/terminal/tsconfig-build.json b/packages/terminal/tsconfig-build.json new file mode 100644 index 000000000..dd932a938 --- /dev/null +++ b/packages/terminal/tsconfig-build.json @@ -0,0 +1,16 @@ +{ + "extends": "../../tsconfig-build.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "lib", + }, + "exclude": [ + "node_modules", + "**/*.test.ts", + "dist", + ], + "references": [ + { "path": "../parser-delimiter/tsconfig-build.json"}, + { "path": "../stream/tsconfig-build.json"} + ] +} diff --git a/packages/terminal/tsconfig.json b/packages/terminal/tsconfig.json new file mode 100644 index 000000000..6f83eb665 --- /dev/null +++ b/packages/terminal/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tsconfig.json", +} diff --git a/test-config.json b/test-config.json deleted file mode 100644 index 69ba436f7..000000000 --- a/test-config.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "all": {}, - "win32": { - "open.unlock": false, - "baudrate.25000": false, - "baudrate.25000_check": false, - "port.update-baudrate": false - }, - "darwin": { - "baudrate.25000_check": false, - "baudrate.1000000_check": false, - "baudrate.250000_check": false - } -} diff --git a/test/initializers/assert.ts b/test/assert.ts similarity index 55% rename from test/initializers/assert.ts rename to test/assert.ts index 65ea9d0cc..be0a78e81 100644 --- a/test/initializers/assert.ts +++ b/test/assert.ts @@ -2,7 +2,7 @@ import { assert, use } from 'chai' import chaiSubset from 'chai-subset' use(chaiSubset) -export const shouldReject = async (promise: Promise, errType = Error, message = 'Should have rejected') => { +export const shouldReject = async (promise: Promise, errType = Error, message = 'Should have rejected') => { try { await promise } catch (err) { @@ -13,5 +13,3 @@ export const shouldReject = async (promise: Promise, errType = Error, messa } export { assert } -;(global as any).assert = assert -;(global as any).shouldReject = shouldReject diff --git a/test/initializers/index.js b/test/initializers/index.js deleted file mode 100644 index 486c1a4a6..000000000 --- a/test/initializers/index.js +++ /dev/null @@ -1,6 +0,0 @@ -const fs = require('fs') -const files = fs.readdirSync(__dirname).filter(file => file !== 'index.js' && file.match(/\.(js|ts)$/)) -files.forEach(file => { - console.log(`Loading ${file}`) - require(`./${file}`) -}) diff --git a/test/initializers/test-config.js b/test/initializers/test-config.js deleted file mode 100644 index 5c46070a6..000000000 --- a/test/initializers/test-config.js +++ /dev/null @@ -1,11 +0,0 @@ -const testConfig = require('../../test-config.json') - -global.makeTestFeature = function makeTestFeature(envName) { - const config = { ...testConfig.all, ...testConfig[envName] } - return function testFeature(feature, description, callback) { - if (config[feature] === false) { - return it(`Feature "${feature}" is disabled in "${envName}. "${description}"`) - } - it(description, callback) - } -} diff --git a/test/testOnPlatform.ts b/test/testOnPlatform.ts new file mode 100644 index 000000000..84ba3106c --- /dev/null +++ b/test/testOnPlatform.ts @@ -0,0 +1,7 @@ +/* eslint-disable mocha/no-exports */ +export const testOnPlatform = (platforms: (NodeJS.Platform | 'mock')[], description: string, callback: Mocha.Func) => { + if (!platforms.includes(process.platform)) { + return it(`Disabled on "${process.platform}: "${description}"`) + } + it(description, callback) +} diff --git a/tsconfig-build.json b/tsconfig-build.json index 67681d154..5247b6949 100644 --- a/tsconfig-build.json +++ b/tsconfig-build.json @@ -1,6 +1,7 @@ { "extends": "./tsconfig.json", "compilerOptions": { + "composite": true, "declaration": true, "declarationMap": false, "noEmitOnError": true, diff --git a/tsconfig.json b/tsconfig.json index 6d9ea8e86..f4228e981 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,8 +6,8 @@ "@serialport/*": ["packages/*/lib"], "serialport": ["packages/serialport/lib"] }, - "allowJs": true, + "allowJs": false, "checkJs": false, - "noEmit": true - } + "noEmit": true, + }, }