2

I am currently using node-serialport module for serial port communication. I will send a command ATEC and it will respond with ECHO.

However, this process of sending and receiving data is async(after i send the data, i will not know when the data will arrive in the data event), the example code is below:

//Register the data event from the serial port
port.on('data', (data) => {
    console.log(data);
});

//Send data using serialport
port.write('ATEC');

Is there anyway I could write it in this way?

//When i send the command, I could receive the data
port.write('ATEC').then((data)=> {
    console.log(data);
});

Is this possible to achieve?

In http communication using request client, we could do something like

request.get('http:\\google.com')
    .on('response', (res) => {
        console.log(res);
    });

I want to replicate the same behaviour using serialport

Tim
  • 3,755
  • 3
  • 36
  • 57
  • Check this https://www.npmjs.com/package/serial-node it seems to be synchronous, although I haven't tried it myself – Molda Jun 10 '16 at 05:33

2 Answers2

2

I wrap a promise in the serial data receive

function sendSync(port, src) {
    return new Promise((resolve, reject) => {
        port.write(src);
        port.once('data', (data) => {
            resolve(data.toString());
        });

        port.once('error', (err) => {
            reject(err);
        });
    });
}

Please take note, the event is using once instead of on to prevent event from stacking (please check the comments below for more information - thanks @DKebler for spotting it)

Then, I could write the code in sync as below

sendSync(port, 'AThello\n').then((data) => {
    //receive data
});

sendSync(port, 'ATecho\n').then((data) => {
    //receive data
});

or I could use a generator, using co package

 co(function* () {
        const echo = yield sendSync(port, 'echo\n');
        const hello = yield sendSync(port, 'hello 123\n');

        return [echo, hello]
    }).then((result) => {
        console.log(result)
    }).catch((err) => {
        console.error(err);
    })
Tim
  • 3,755
  • 3
  • 36
  • 57
  • In the example above that starts with `function sendSync(port, src) {` wouldn't you want to `port.write(src);` after the two event handlers were setup. – Timothy Vann Sep 25 '16 at 03:52
  • It would not matter cuz, serial port module will queue the data if my event is yet to register, so when I register it, I will get the data from the queue, so no data is lost – Tim Oct 06 '16 at 23:41
  • won't your code as written have the issue addressed by qDot above which is it will keep stacking "data" listeners with each call to SendSync? If so what would be the best place to "removeListerner". If not please explain why it's ok as is. issue related to this question me thinks... http://stackoverflow.com/questions/33018352/how-to-dispose-resources-in-rejected-promises-with-bluebird – DKebler Oct 15 '16 at 02:23
  • When you get the data in the then callback, all the data listener will be disposed automatically, you can proof it by putting console log in the data listener, and make a few sendsync call – Tim Oct 15 '16 at 07:08
  • I put console.log('num data listeners: ', port.listenerCount("data")); in two places. Here what I got after 5 times....5 stacked "data" listeners...have you tried it yourself.....the resolved promise doesn't automatically remove the listener as far my tests show – DKebler Oct 16 '16 at 04:18
  • I switched form .on to .once and that fixed the stacked listener. shows one when called in sendSync and then zero in the .then after the call. – DKebler Oct 16 '16 at 04:33
  • FYI I made another question of this, also having issue of partial response with this code. http://stackoverflow.com/questions/40075466/node-serialport-stacking-listeners-and-not-getting-complete-response-using-a-pro – DKebler Oct 16 '16 at 21:05
1

We have a similar problem in a project I'm working on. Needed a synchronous send/receive loop for serial, and the serialport package makes that kinda weird.

Our solution is to make some sort of queue of functions/promises/generators/etc (depends on your architecture) that the serial port "data" event services. Every time you write something, put a function/promise/etc into the queue.

Let's assume you're just throwing functions into the queue. When the "data" event is fired, it sends the currently aggregated receive buffer as a parameter into the first element of the queue, which can see if it contains all of the data it needs, and if so, does something with it, and removes itself from the queue somehow.

This allows you to handle multiple different kinds of architecture (callback/promise/coroutine/etc) with the same basic mechanism.

As an added bonus: If you have full control of both sides of the protocol, you can add a "\n" to the end of those strings and then use serialport's "readline" parser, so you'll only get data events on whole strings. Might make things a bit easier than constantly checking input validity if it comes in pieces.

Update:

And now that code has been finished and tested (see the ET312 module in http://github.com/metafetish/buttshock-js), here's how I do it:

function writeAndExpect(data, length) {
  return new Promise((resolve, reject) => {
    const buffer = new Buffer(length);
    this._port.write(data, (error) => {
      if (error) {
        reject(error);
        return;
      }
    });
    let offset = 0;
    let handler = (d) => {
      try {
        Uint8Array.from(d).forEach(byte => buffer.writeUInt8(byte, offset));
        offset += d.length;
      } catch (err) {
        reject(err);
        return;
      }
      if (offset === length) {
        resolve(buffer);
        this._port.removeListener("data", handler);
      };
    };
    this._port.on("data", handler);
  });
}

The above function takes a list of uint8s, and an expected amount of data to get back, returns a promise. We write the data, and then set ourselves up as the "data" event handler. We use that to read until we get the amount of data we expect, then resolve the promise, remove ourselves as a "data" listener (this is important, otherwise you'll stack handlers!), and finish.

This code is very specific to my needs, and won't handle cases other than very strict send/receive pairs with known parameters, but it might give you an idea to start with.

qDot
  • 430
  • 3
  • 8
  • Why don't you remove the listener after `reject(err);`. If you have a bunch of errors won't this cause the handlers to stack also? – Timothy Vann Sep 25 '16 at 14:50