Skip to content
New issue

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

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

Already on GitHub? # to your account

New Bindings interface #1679

Open
reconbot opened this issue Sep 23, 2018 · 7 comments
Open

New Bindings interface #1679

reconbot opened this issue Sep 23, 2018 · 7 comments
Labels
feature-request Feature or Enhancement

Comments

@reconbot
Copy link
Member

💥 Proposal

The current binding interface is a result of 8 years of slight modifications. It works pretty good but I have idea for a different interface. This would be internal and the @serialport/stream interface would go mostly unchanged.

What feature you'd like to see

Fewer functions, an async factory, less state.

Motivation

I'd like our binding interface to good enough to replace the current proposed web serial spec and provide an easy to use api that can be wrapped by whatever the latest stream or iterator, approach

I also want the port to have all the info about the port already on it. This needs some thinking through for non exclusive ports (eg open by more than one processes) but that is a much less common use case.

Pitch

import { openPort, listPorts } from '@serialport/bindings'

const ports = await listPorts() /[ { path: '/dev/tty-usb' , vendorId: 'foobar' }]

const path = ports[0].path // '/dev/tty-usb'

const port = await openPort({ path, baudRate: 9600 })

port.baudRate // 9600
port.vendorId  // foobar
port.dtr // true or false
port.descriptor // open file descriptor, can be passed to child processes on linux and used to pass ports around context like to a service worker?

// and then the very similar to what we have now
const data = await port.read()
await port.write(data)

// combine set and update to just an update
await port.udate({ dtr: false, baudRate: 14400, rts: true })

// have port.getState() only return the stuff that is remote state, like cts, dcd, and dsr
const { cts } = await port.getState()

// rejects all pending writes and ends all pending reads with any available data (0 byte buffers or null for no data?)
await port.close()
@reconbot
Copy link
Member Author

reconbot commented Oct 26, 2018

Here is the new API I've settled on. It has an AbstractBinding.open() method which returns an open port. The port as the path, vendor information and all local state as properties. Remote state (CTS, DSR, DCD) is now wrapped in a call to getRemoteState(). To change local state you call setLocalState() for anything you could control locally.

TODO

  • Figure out what can have default values when you open() a port.
  • ensure inflight reads when a port closes makes sense
  • think about how events for remote state changes might work, this would need a lot of c++ work but it's a wonderful future feature and should be in the web spec if possible
  • get feedback from Need prototype implementation WICG/serial#18

You can track my progress on the typescript2 branch.

/**
 * 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. 
*/
export class AbstractBinding implements PortInfo, LocalState {
  locationId?: string
  manufacturer?: string
  path: string
  pnpId?: string
  productId?: string
  serialNumber?: string
  vendorId?: string
  baudRate: number
  brk: boolean
  dataBits: 5 | 6 | 7 | 8
  dtr: boolean
  lock: boolean
  parity: 'none' | 'even' | 'mark' | 'odd' | 'space'
  rts: boolean
  rtscts: boolean
  stopBits: 1 | 1.5 | 2
  descriptor: number
  hasClosed: boolean

  /**
   * Retrieves a list of available serial ports with metadata. The `comName` must be guaranteed, and all other fields should be undefined if unavailable. The `comName` is either the path or an identifier (eg `COM1`) used to open the serialport.
   */
  static async list(): Promise<ReadonlyArray<PortInfo>> {
    throw new Error('#list is not implemented')
  }

  /**
   * Opens a connection to the serial port referenced by the path.
   */
  static async open<T>(this: T, options: OpenOptions): Promise<T> {
    throw new Error('#open is not implemented')
  }

  constructor(opt: ConstructorOptions) {
    throw new Error('Cannot create an AbstractBinding Implemented')
  }

  /**
   * Closes an open connection
   */
  async close() {
    throw new Error('.close is not implemented')
  }

  /**
   * Drain waits until all output data is transmitted to the serial port. An in progress write should be completed before this returns.
   */
  async drain() {
    throw new Error('.drain is not implemented')
  }

  /**
   * Flush (discard) data received but not read, and written but not transmitted.
   */
  async flush() {
    throw new Error('.flush is not implemented')
  }

  /**
   * Get the remote state flags (CTS, DSR, DCD) on the open port.
   */
  async getRemoteState(): Promise<RemoteState> {
    throw new Error('.get is not implemented')
  }

  /**
   * 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 read at least one byte while the port is open. In progress reads must resolve with any available data when the port is closed, if there is no data when a port is closed read 0 bytes.
   */
  async read(buffer: Buffer, offset: number, length: number): Promise<number> {
    throw new Error('.read is not implemented')
  }

  /**
   * Set local state on an open port including updating baudRate and control flags. The state is represented on the object as well as resolved in the promise.
   */
  async setLocalState(options: Partial<LocalState>): Promise<LocalState> {
    throw new Error('.setLocalState is not implemented')
  }

  /**
   * Write bytes to the SerialPort. Only call when there is no pending write operation. In progress writes must error when the port is closed.
   */
  async write(buffer: Buffer): Promise<void> {
    throw new Error('.write is not implemented')
  }
}
export interface ConstructorOptions extends PortInfo, LocalState {
  readonly descriptor: number
}

export interface OpenOptions extends LocalState {
  readonly path: string
}

export interface PortInfo {
  readonly path: string
  readonly locationId?: string
  readonly manufacturer?: string
  readonly pnpId?: string
  readonly productId?: string
  readonly serialNumber?: string
  readonly vendorId?: string
}

export interface LocalState {
  /* The system reported baud rate */
  readonly baudRate: number
  /* Break Suspends character transmission local status */
  readonly brk: boolean
  readonly dataBits: 5 | 6 | 7 | 8
  /* Data terminal Ready local status (local DTR => remote DSR) */
  readonly dtr: boolean
  readonly lock: boolean
  readonly parity: 'none' | 'even' | 'mark' | 'odd' | 'space'
  /* Request To Send local status (local RTS => remote CTS) */
  readonly rts: boolean
  /* enable rts/cts control flow, disables manually setting rts */
  readonly rtscts: boolean
  readonly stopBits: 1 | 1.5 | 2
}

export interface RemoteState {
  /* Clear To Send remote status (remote RTS => local CTS) */
  readonly cts: boolean
  /* Data Carrier Detect remote status */
  readonly dcd: boolean
  /* Data Set Ready remote status (local DSR => remote DTR) */
  readonly dsr: boolean
}

@HipsterBrown
Copy link
Contributor

@reconbot Is this going to be a major version bump for the existing @serialport/bindings package or an underlying architecture change for serialport?

@HipsterBrown
Copy link
Contributor

Also, I like the use of typings to describe the interface. It's a great documentation format.

@reconbot
Copy link
Member Author

reconbot commented Oct 26, 2018 via email

@reconbot
Copy link
Member Author

having worked a little with pyserial today I realized that if you're not streaming data node-serialport is really annoying to work with. pyserial's read() takes a timeout and can return nothing. The proposed read here just waits for data and can't be canceled. We need one or the other or both in a safe way 🤔

@JBtje
Copy link
Contributor

JBtje commented Feb 21, 2019

having worked a little with pyserial today I realized that if you're not streaming data node-serialport is really annoying to work with. pyserial's read() takes a timeout and can return nothing. The proposed read here just waits for data and can't be canceled. We need one or the other or both in a safe way 🤔

Amen... but luckely you guys helped out allot! (see #1758) and with that, using node-serialport all of the sudden becomes quite simple....

reconbot added a commit that referenced this issue Apr 28, 2019
This is the next generation of serial port interfaces. Streams are never going away but the async iterator interface is much more flexible. It can be combined with async-iterator tools (such as [streaming-iterables](https://www.npmjs.com/package/streaming-iterables)) to make buffers and parsers, and can even be combined with our existing stream based parsers.

This is very experimental. I’ve tried to bring a lot of these changes in https://github.com/node-serialport/node-serialport/tree/reconbot/typescript2 but I haven’t had time for a full typescript rewrite. So maybe this smaller api change lets us get some of these advantages without having to rewrite everything.

## Todo
- [ ] api feedback
- [ ] build chain to support modern javascript
- [ ] docs for website
- [ ] abstract away get/set/update borrowing from #1679 for local and remote state and parity with web serial
- [ ] tests

## Example Usage
```js
const { open, list } = require('@serialport/async-iterator')
const ports = await list()
const arduinoPort = ports.find(info => (info.manufacture || '').includes('Arduino'))
const port = await open(arduinoPort)

// read bytes until close
for await (const bytes of port) {
  console.log(`read ${bytes.length} bytes`)
}

// read 12 bytes
const { value, end } = await port.next(12)
console.log(`read ${value.length} bytes / port closed: ${end}`)

// write a buffer
await port.write(Buffer.from('hello!'))
```
reconbot added a commit that referenced this issue Apr 28, 2019
This is the next generation of serial port interfaces. Streams are never going away but the async iterator interface is much more flexible. It can be combined with async-iterator tools (such as [streaming-iterables](https://www.npmjs.com/package/streaming-iterables)) to make buffers and parsers, and can even be combined with our existing stream based parsers.

This is very experimental. I’ve tried to bring a lot of these changes in https://github.com/node-serialport/node-serialport/tree/reconbot/typescript2 but I haven’t had time for a full typescript rewrite. So maybe this smaller api change lets us get some of these advantages without having to rewrite everything.

## Todo
- [ ] api feedback
- [ ] build chain to support modern javascript
- [ ] docs for website and readme
- [ ] abstract away get/set/update borrowing from #1679 for local and remote state and parity with web serial
- [ ] tests

## Example Usage
```js
const { open, list } = require('@serialport/async-iterator')
const ports = await list()
const arduinoPort = ports.find(info => (info.manufacture || '').includes('Arduino'))
const port = await open(arduinoPort)

// read bytes until close
for await (const bytes of port) {
  console.log(`read ${bytes.length} bytes`)
}

// read 12 bytes
const { value, end } = await port.next(12)
console.log(`read ${value.length} bytes / port closed: ${end}`)

// write a buffer
await port.write(Buffer.from('hello!'))
```
reconbot added a commit that referenced this issue Sep 18, 2019
This is the next generation of serial port interfaces. Streams are never going away but the async iterator interface is much more flexible. It can be combined with async-iterator tools (such as [streaming-iterables](https://www.npmjs.com/package/streaming-iterables)) to make buffers and parsers, and can even be combined with our existing stream based parsers.

This is very experimental. I’ve tried to bring a lot of these changes in https://github.com/node-serialport/node-serialport/tree/reconbot/typescript2 but I haven’t had time for a full typescript rewrite. So maybe this smaller api change lets us get some of these advantages without having to rewrite everything.

## Todo
- [ ] api feedback
- [ ] build chain to support modern javascript
- [ ] docs for website and readme
- [ ] abstract away get/set/update borrowing from #1679 for local and remote state and parity with web serial
- [ ] tests

## Example Usage
```js
const { open, list } = require('@serialport/async-iterator')
const ports = await list()
const arduinoPort = ports.find(info => (info.manufacture || '').includes('Arduino'))
const port = await open(arduinoPort)

// read bytes until close
for await (const bytes of port) {
  console.log(`read ${bytes.length} bytes`)
}

// read 12 bytes
const { value, end } = await port.next(12)
console.log(`read ${value.length} bytes / port closed: ${end}`)

// write a buffer
await port.write(Buffer.from('hello!'))
```
reconbot added a commit that referenced this issue Oct 27, 2019
This is the next generation of serial port interfaces. Streams are never going away but the async iterator interface is much more flexible. It can be combined with async-iterator tools (such as [streaming-iterables](https://www.npmjs.com/package/streaming-iterables)) to make buffers and parsers, and can even be combined with our existing stream based parsers.

This is very experimental. I’ve tried to bring a lot of these changes in https://github.com/node-serialport/node-serialport/tree/reconbot/typescript2 but I haven’t had time for a full typescript rewrite. So maybe this smaller api change lets us get some of these advantages without having to rewrite everything.

## Todo
- [ ] api feedback
- [ ] build chain to support modern javascript
- [ ] docs for website and readme
- [ ] abstract away get/set/update borrowing from #1679 for local and remote state and parity with web serial
- [ ] tests

## Example Usage
```js
const { open, list } = require('@serialport/async-iterator')
const ports = await list()
const arduinoPort = ports.find(info => (info.manufacture || '').includes('Arduino'))
const port = await open(arduinoPort)

// read bytes until close
for await (const bytes of port) {
  console.log(`read ${bytes.length} bytes`)
}

// read 12 bytes
const { value, end } = await port.next(12)
console.log(`read ${value.length} bytes / port closed: ${end}`)

// write a buffer
await port.write(Buffer.from('hello!'))
```
reconbot added a commit that referenced this issue Oct 29, 2019
This is the next generation of serial port interfaces. Streams are never going away but the async iterator interface is much more flexible. It can be combined with async-iterator tools (such as [streaming-iterables](https://www.npmjs.com/package/streaming-iterables)) to make buffers and parsers, and can even be combined with our existing stream based parsers.

This is very experimental. I’ve tried to bring a lot of these changes in https://github.com/node-serialport/node-serialport/tree/reconbot/typescript2 but I haven’t had time for a full typescript rewrite. So maybe this smaller api change lets us get some of these advantages without having to rewrite everything.

## Todo
- [ ] api feedback
- [ ] docs for website and readme
- [ ] abstract away get/set/update borrowing from #1679 for local and remote state and parity with web serial
- [ ] tests

## Example Usage
```js
const { open, list } = require('@serialport/async-iterator')
const ports = await list()
const arduinoPort = ports.find(info => (info.manufacture || '').includes('Arduino'))
const port = await open(arduinoPort)

// read bytes until close
for await (const bytes of port) {
  console.log(`read ${bytes.length} bytes`)
}

// read 12 bytes
const { value, end } = await port.next(12)
console.log(`read ${value.length} bytes / port closed: ${end}`)

// write a buffer
await port.write(Buffer.from('hello!'))
```
reconbot added a commit that referenced this issue Oct 29, 2019
This is the next generation of serial port interfaces. Streams are never going away but the async iterator interface is much more flexible. It can be combined with async-iterator tools (such as [streaming-iterables](https://www.npmjs.com/package/streaming-iterables)) to make buffers and parsers, and can even be combined with our existing stream based parsers.

This is very experimental. I’ve tried to bring a lot of these changes in https://github.com/node-serialport/node-serialport/tree/reconbot/typescript2 but I haven’t had time for a full typescript rewrite. So maybe this smaller api change lets us get some of these advantages without having to rewrite everything.

## Todo
- [ ] api feedback
- [ ] docs for website and readme
- [ ] abstract away get/set/update borrowing from #1679 for local and remote state and parity with web serial
- [ ] tests

## Example Usage
```js
const { open, list } = require('@serialport/async-iterator')
const ports = await list()
const arduinoPort = ports.find(info => (info.manufacture || '').includes('Arduino'))
const port = await open(arduinoPort)

// read bytes until close
for await (const bytes of port) {
  console.log(`read ${bytes.length} bytes`)
}

// read 12 bytes
const { value, end } = await port.next(12)
console.log(`read ${value.length} bytes / port closed: ${end}`)

// write a buffer
await port.write(Buffer.from('hello!'))
```
reconbot added a commit that referenced this issue Oct 30, 2019
This is the next generation of serial port interfaces. Streams are never going away but the async iterator interface is much more flexible. It can be combined with async-iterator tools (such as [streaming-iterables](https://www.npmjs.com/package/streaming-iterables)) to make buffers and parsers, and can even be combined with our existing stream based parsers.

This is very experimental. I’ve tried to bring a lot of these changes in https://github.com/node-serialport/node-serialport/tree/reconbot/typescript2 but I haven’t had time for a full typescript rewrite. So maybe this smaller api change lets us get some of these advantages without having to rewrite everything.

## Todo
- [ ] api feedback
- [ ] docs for website and readme
- [ ] abstract away get/set/update borrowing from #1679 for local and remote state and parity with web serial
- [ ] tests

## Example Usage
```js
const { open, list } = require('@serialport/async-iterator')
const ports = await list()
const arduinoPort = ports.find(info => (info.manufacture || '').includes('Arduino'))
const port = await open(arduinoPort)

// read bytes until close
for await (const bytes of port) {
  console.log(`read ${bytes.length} bytes`)
}

// read 12 bytes
const { value, end } = await port.next(12)
console.log(`read ${value.length} bytes / port closed: ${end}`)

// write a buffer
await port.write(Buffer.from('hello!'))
```
@shenzhuxi
Copy link

As I understand, https://github.com/serialport/bindings-cpp/blob/27c1685158f300d00d07e9c2de3f8af415cc8ad2/lib/linux-list.ts is using udevadm and does not check the hierarchy with --attribute-walk
#2126 (comment).

@reconbot What do you think about https://github.com/MadLittleMods/node-usb-detection ? I feel it's a more mature solution.

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
feature-request Feature or Enhancement
Development

No branches or pull requests

4 participants