Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: platform specific open options for streams #2428

Merged
merged 1 commit into from
Feb 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 13 additions & 2 deletions packages/serialport/lib/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { randomBytes } from 'crypto'
import { SerialPort as SerialPortAutoDetect, SerialPortMock } from './'
import { assert } from '../../../test/assert'
import { testOnPlatform } from '../../../test/testOnPlatform'
import { LinuxBinding, LinuxOpenOptions } from '@serialport/bindings-cpp'

const platform = process.platform
if (platform !== 'win32' && platform !== 'darwin' && platform !== 'linux') {
Expand Down Expand Up @@ -31,13 +32,13 @@ function testSerialPortClass(

beforeEach(() => {
if (platform === 'mock') {
SerialPortMock.MockBinding.createPort('/dev/exists', { echo: true, maxReadSize: 50 })
SerialPortMock.binding.createPort('/dev/exists', { echo: true, maxReadSize: 50 })
}
})

afterEach(() => {
if (platform === 'mock') {
SerialPortMock.MockBinding.reset()
SerialPortMock.binding.reset()
}
})

Expand Down Expand Up @@ -73,6 +74,16 @@ function testSerialPortClass(
done()
})
})

it('allows platform specific options', done => {
new SerialPort({
path: '/bad/port',
baudRate: 9600,
vmin: 10,
} as LinuxOpenOptions).on('error', () => {
done()
})
})
})

describe('opening and closing', () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/serialport/lib/serialport-mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export type SerialPortMockOpenOptions = Omit<OpenOptions<MockBindingInterface>,

export class SerialPortMock extends SerialPortStream<MockBindingInterface> {
static list = MockBinding.list
static readonly MockBinding = MockBinding
static readonly binding = MockBinding

constructor(options: SerialPortMockOpenOptions, openCallback?: ErrorCallback) {
const opts: OpenOptions<MockBindingInterface> = {
Expand Down
15 changes: 8 additions & 7 deletions packages/serialport/lib/serialport.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { ErrorCallback, OpenOptions, SerialPortStream } from '@serialport/stream'
import { autoDetect, AutoDetectTypes } from '@serialport/bindings-cpp'
import { ErrorCallback, OpenOptions, SerialPortStream, StreamOptions } from '@serialport/stream'
import { autoDetect, AutoDetectTypes, OpenOptionsFromBinding } from '@serialport/bindings-cpp'

const DetectedBinding = autoDetect()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since autoDetect returns a non-discriminating union (AutoDetectTypes), I think that is why the type assertion is needed from DetectedBinding later. Is it possible to brand each binding interface to help with this detection narrowing?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I understand - let me try it out

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Branded types are rad but not helping in this case. I'm positive we'll revisit this.


export type SerialPortOpenOptions = Omit<OpenOptions<AutoDetectTypes>, 'binding'>
export type SerialPortOpenOptions<T extends AutoDetectTypes> = Omit<StreamOptions<T>, 'binding'> & OpenOptionsFromBinding<T>

export class SerialPort extends SerialPortStream<AutoDetectTypes> {
export class SerialPort<T extends AutoDetectTypes = AutoDetectTypes> extends SerialPortStream<T> {
/**
* 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.
*
Expand Down Expand Up @@ -57,10 +57,11 @@ export class SerialPort extends SerialPortStream<AutoDetectTypes> {
```
*/
static list = DetectedBinding.list
static readonly binding = DetectedBinding

constructor(options: SerialPortOpenOptions, openCallback?: ErrorCallback) {
const opts: OpenOptions<AutoDetectTypes> = {
binding: DetectedBinding,
constructor(options: SerialPortOpenOptions<T>, openCallback?: ErrorCallback) {
const opts: OpenOptions<T> = {
binding: DetectedBinding as T,
...options,
}
super(opts, openCallback)
Expand Down
36 changes: 16 additions & 20 deletions packages/stream/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
import { Duplex } from 'stream'
import debugFactory from 'debug'
import { SetOptions, BindingInterface, PortInterfaceFromBinding, OpenOptions as BindingOpenOptions } from '@serialport/bindings-interface'
import { SetOptions, BindingInterface, PortInterfaceFromBinding, OpenOptionsFromBinding } from '@serialport/bindings-interface'
const debug = debugFactory('serialport/stream')

interface InternalSettings<T extends BindingInterface> extends OpenOptions<T> {
export class DisconnectedError extends Error {
disconnected: true
constructor(message: string) {
super(message)
this.disconnected = true
}
}

interface InternalSettings<T extends BindingInterface> {
binding: T
autoOpen: boolean
endOnClose: boolean
highWaterMark: number
Expand Down Expand Up @@ -33,12 +42,14 @@ export type ErrorCallback = (err: Error | null) => void

export type ModemBitsCallback = (err: Error | null, options?: { cts: boolean; dsr: boolean; dcd: boolean }) => void

export type OpenOptions<T extends BindingInterface = BindingInterface> = StreamOptions<T> & OpenOptionsFromBinding<T>

/**
* Options to open a port
*/
export interface OpenOptions<T extends BindingInterface> extends BindingOpenOptions {
export interface StreamOptions<T extends BindingInterface> {
/**
* 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.
* The hardware access binding. `Bindings` are how Node-Serialport talks to the underlying system. If you're using the `serialport` package, this defaults to `'@serialport/bindings-cpp'` which auto detects Windows (`WindowsBinding`), Linux (`LinuxBinding`) and OS X (`DarwinBinding`) and load the appropriate module for your system.
*/
binding: T

Expand All @@ -56,31 +67,16 @@ export interface OpenOptions<T extends BindingInterface> extends BindingOpenOpti
endOnClose?: boolean
}

export class DisconnectedError extends Error {
disconnected: true
constructor(message: string) {
super(message)
this.disconnected = true
}
}

export class SerialPortStream<T extends BindingInterface = BindingInterface> extends Duplex {
port?: PortInterfaceFromBinding<T>
private _pool: PoolBuffer
private _kMinPoolSpace: number
opening: boolean
closing: boolean
readonly settings: InternalSettings<T>
readonly settings: InternalSettings<T> & OpenOptionsFromBinding<T>

/**
* 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
Expand Down
2 changes: 1 addition & 1 deletion packages/terminal/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const args = program.opts<{
path?: string
baud: number
databits: 8 | 7 | 6 | 5
parity: string
parity: OpenOptions<AutoDetectTypes>['parity']
stopbits: 1 | 1.5 | 2
echo: boolean
flowCtl?: string
Expand Down