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

WIP feat: Async-Iterator interface #1850

Closed
wants to merge 1 commit into from
Closed

Conversation

reconbot
Copy link
Member

@reconbot reconbot commented 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) 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 New Bindings interface #1679 for local and remote state and parity with web serial
  • tests

Example Usage

const { open } = require('@serialport/async-iterator')
const Binding = require('@serialport/bindings')
const ports = await Binding.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!'))

closes #991

@reconbot reconbot changed the title feat: Async-Iterator interface WIP feat: Async-Iterator interface Apr 28, 2019
Copy link

@ThisIsMissEm ThisIsMissEm left a comment

Choose a reason for hiding this comment

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

Left some API comments based on knowledge of other AsyncIterable interfaces.

@stale

This comment has been minimized.

@stale stale bot added the stale-issue label Jul 3, 2019
@stale stale bot closed this Jul 10, 2019
@reconbot reconbot reopened this Sep 3, 2019
@stale stale bot removed the stale-issue label Sep 3, 2019
@reconbot reconbot force-pushed the reconbot/async-iterator branch from 3d72b33 to a6cb875 Compare September 18, 2019 01:02
@reconbot reconbot force-pushed the reconbot/async-iterator branch 3 times, most recently from bf17c44 to 92734b3 Compare October 29, 2019 02:37
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 reconbot force-pushed the reconbot/async-iterator branch from 92734b3 to 5ab68b6 Compare October 30, 2019 03:03
@stale
Copy link

stale bot commented Dec 29, 2019

This issue has been automatically marked as stale because it has not had recent activity. It will be closed in a week no further activity occurs. Feel free continue the discussion or ask for a never-stale label to keep it open. If this is a support issue, consider sharing it on stack overflow to get more eyes on your problem.

@stale stale bot added the stale-issue label Dec 29, 2019
@stale stale bot closed this Jan 5, 2020
@marciot
Copy link

marciot commented Jun 5, 2020

So I am porting a Python program that relies very extensively on writing commands via the serial port and getting replies back synchronously. It is not the kind of thing that can be rewritten as callbacks easily. I came across this thread when looking for a better solution.

The async iterator is not exactly what I was hoping for,, so I thought I would share what does seem to be working for me:

import SerialPort from 'serialport';

class SequentialSerial {
    // Returns a promise that resolves once the serial port is open and ready.
    // The timeout value specifies how long future calls to read() will wait
    // for data before throwing an exception
    open(port, baud, timeout = 1000) {
        this.dec     = new TextDecoder();
        this.timeout = timeout;
        return new Promise((resolve, reject) => {
            this.serial = new SerialPort(port, {baudRate: baud});
            this.serial.on("open",  err => {if(err) reject(err); else resolve();});
        });
    }

    // Returns a promise that resolves once all output data has been written
    flush() {
        return new Promise((resolve, reject) => {
            this.serial.drain(resolve);
        });
    }

    // Returns a promise that resolves after some data has been written
    write(data) {
        return new Promise((resolve, reject) => {
            const okayToProceed = this.serial.write(data);
            if(okayToProceed) {
                resolve();
            } else {
                this.serial.drain(resolve);
            }
        });
    }
    
    // Returns a promise which resolves when "len" bytes have been read.
    // If encoding is "utf8", a string will be returned.
    read(len, encoding) {
        this.readStart = Date.now();
        const tryIt = (serial, resolve, reject) => {
            var result = this.serial.read(len);
            if(result) {
                if(encoding == 'utf8') {
                    result = this.dec.decode(result);
                }
                resolve(result);
            } else {
                if(Date.now() - this.readStart > this.timeout) {
                    reject("SerialTimeout");
                } else {
                    // Using setTimeout here is crucial for allowing the I/O buffer to refill
                    setTimeout(() => tryIt(this.serial, resolve, reject), 0);
                }
            }
        }
        return new Promise((resolve, reject) => {
            tryIt(this.serial, resolve, reject);
        });
    }
    
    // Returns a promise that resolves after a certain number of miliseconds.
    wait(ms) {
        return new Promise((resolve, reject) => {setTimeout(resolve,ms);});
    }
}

async function doSerial() {
    const serial = new SequentialSerial();
    await serial.open("COM6", 9600);
    console.log("Serial port open");
    await serial.wait(5000);
    console.log("Ready");
    await serial.write("hello\r\n");
    await serial.flush();
    const str = await serial.read(7, 'utf8');
    console.log("Read: ", str);
    console.log("Done");
}

doSerial().catch(err => console.log("Serial error:", err));

The whole trick here is to make all the I/O operations into Promises and use ECMA 6 await routines to enforce sequencing. Seems to work well and behaves exactly as I would expect it to work in a sequential language. This way of interacting with a serial port seems natural to me, but I am not a node.js programmer, so that probably plays a part.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging this pull request may close these issues.

Explore Promises in addition to callbacks
3 participants