I think I got it figured out from the github (https://github.com/voodootikigod/node-serialport page... basically it looks like I was missing the "open" event as shown below:
serialPort.on("open", function () {
console.log("open");
serialPort.on("data", function(data) {
console.log("data received: " + data);
});
serialPort.write("SYST:ADDR?\n", function(err, results) {
console.log("err: " + err);
console.log("results: " + results);
});
});
Answer from io2work on Stack Overflow
» npm install serialport
I think I got it figured out from the github (https://github.com/voodootikigod/node-serialport page... basically it looks like I was missing the "open" event as shown below:
serialPort.on("open", function () {
console.log("open");
serialPort.on("data", function(data) {
console.log("data received: " + data);
});
serialPort.write("SYST:ADDR?\n", function(err, results) {
console.log("err: " + err);
console.log("results: " + results);
});
});
Here is another approach which works very well and allows for dynamic addressing of a specific serial device. In my case I am only interested in connecting to the Numato device connected to our integrated system which is why I have the conditional logic in the list callback.
exports.testSerial = function(data) {
serialPort.list(function(err, ports) {
var port = {};
for(var i = 0; i < ports.length; i++) {
try {
if(typeof ports[i].manufacturer != 'undefined' && ports[i].manufacturer.includes("Numato")) {
port = ports[i];
}
} catch(err) {
console.dir(err);
}
}
// the port will be opened via the constructor of this call
var numato = new serial(port.comName, {baudrate : 19200}, function(err) {
if(err) {
return console.dir(err);
}
// by having the write call within the callback you can access it directly w/o using .on()
numato.write('relay ' + data.state + ' ' + data.channel + '\r', function(err) {
if(err) {
console.dir('error writing');
console.dir(err);
}
console.dir('serial message written');
numato.close();
});
});
return true;
});
}
Hope this helps someone in the future! For reference this is with library version 4.0.7.
Videos
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);
})
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.
I figured it out. I put a serial port monitoring application on to see what was happening.
The final answer was
A. When using an array or buffer to pass to the .write() method, it needs to be either a decimal character code (ie. 30 for "0" or 13 for carriage return) or a hex code (ie. 0x30 for "0" or 0x0D for carriage return) for each item in the array. I didn't try unicode or anything else. But see below my thoughts on that.
B. It turns out that in my example code
sp.on("open", function () {
console.log(comPort + ' is open');
sp.write(0x80);
sp.write('123456\r');
});
I needed to be sending the 0x80 as a buffer/array instead. so sp.write([0x80]);, without it sent lots of 00 00 00...s However the LED display vendor's documentation was incorrect as it needed a \r\n at the end and not just a \r.
C. (a bit of a "duh" moment) was simply that Javascript will attempt to convert anything concatenated with a string to a string. So concatenating 0x80 + '123456\r' turns into '128123456\r'.
D. Passing unicode, like sp.write('\u0080') ends up outputing double characters, I'm assuming since unicode is 2 bytes per character, and the string is being treated/expecting single byte encoding. Javascript strings are UTF-16, so I'm guessing the node serialport module doesn't handle it since RS-232 uses 8 bits or less per character which falls into the typical ASCII char set.
I tested this on a console, not over a serial port, but I think it should work for you.
There are two options: 1) you can escape into Unicode or 2) you can use the String.fromCharCode function (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/fromCharCode)
Escaping into Unicode is pretty simple, just use \uXXXX where XXXX is the hex representation of your number
sp.write('\u0080123456\r');
Or you can use String.fromCharCode:
sp.write(String.fromCharCode(128) + '123456\r');