I have a machine that is being controlled by an Arduino. If I send the machine the command '9' it will send back JSON with some sensor temperatures in the format {"temperatures":{"bean_temp":110.75,"env_temp":98.15}}
.
I am exposing a function called getTemperatures()
that I currently call from elsewhere every 1000ms. The serialport
library provides a listener for all the data that gets sent back over the wire from the machine (machine.on('data', handleData)
). There are other types of data that will get sent back from the machine, such as {"action":"handle_pulled"}
. I am filtering the data that comes back over the serial and then assigning temperatures responses to a temperatures
variable that I continuously overwrite. I then give a 999 ms delay before calling back that variable.
This works, but seems fragile (and not elegant).
var serialport = require('serialport');
var ports = require('./ports'),
port = ports.arduino;
var machine = new serialport(port, {
baudRate: 9600,
parser: serialport.parsers.readline("\n")
});
machine.on('data', handleData);
var temperatures = {};
machine.getTemperatures = function(callback) {
sendCommand('9');
setTimeout(function(){
callback(temperatures);
}, 999)
}
function sendCommand(data) {
machine.write(data);
}
function handleData(dataString){
data = JSON.parse(dataString);
if ('temperatures' in data) {
temperatures = data;
}
}
module.exports.getTemperatures = getTemperatures;
2 Answers 2
Nobody wants an explicit setTimeout
in their async code; you want data as soon as possible.
So I'd wrap the Arduino interface in an object that is an EventEmitter
. Have that object listen to any and all incoming data, and dispatch it as temperature
events, leverPulled
events, and whatever else, based on content.
For extra points, make the Arduino object's data detection extensible, so it can, by itself, be dumb. It might just parse any given line as JSON, and then look through its registered data detection/classification methods, to see what kind of packet it got. Then it'll dispatch an event with that name to whomever listens.
So something like:
// in arduino.js or whatever you want to call it
var serialport = require('serialport'),
ports = require('./ports'),
port = ports.arduino;
var machine = new serialport(port, {
baudRate: 9600,
parser: serialport.parsers.readline("\n")
});
var wrapper = new require('events'); // i.e. new EventEmitter
var classifiers = {};
machine.on('data', function (string) {
try {
var json = JSON.parse(string);
for (var type in classifiers) {
if (!classifiers.hasOwnProperty(type)) continue;
// classifiers are just function that return true
// if they recognize the json they're given
if (classifiers[type](json)) {
wrapper.emit(type, json);
break;
}
}
} catch (e) {
wrapper.emit('error', e); // might want to be more elaborate
}
});
wrapper.addClassifier = function (type, callback) {
// might want throw an error if the type already exists
classifiers[type] = callback;
};
wrapper.send = function (string) {
machine.send(string);
};
module.exports = wrapper;
Usage would be something like:
var arduino = require('./arduino');
// give the arduino wrapper a way to recognize temperature packets
arduino.addClassifier('temperature', function (json) {
return ('temperature' in json);
});
module.exports.getTemperature = function (callback) {
// listen for the next incoming temperature reading
// and send it straight to the callback
arduino.once('temperature', callback);
arduino.send('9');
};
And if you want to check for errors, then you can add a timeout - if the temperature
event hasn't fired in, say 500ms from sending the '9'
command, something's probably wrong (and you'll want to remove the listener). You'll also want to give getTemperature
a callback with the Node-typical err, data
parameters, so you can send an error back:
module.exports.getTemperature = function (callback) {
var timeout = null;
var listener = function (json) {
clearTimeout(timeout);
callback(null, json);
};
arduino.once('temperature', listener);
arduino.send('9');
setTimeout(function () {
// if we're here, we didn't hear back from the arduino in time
arduino.removeListener('temperature', listener);
callback(new Error('Timed out'), null);
}, 500);
};
As I mentioned, there is a smell w.r.t. what you are doing. However, that aside, here is some code that you can try. I can't test it, but hopefully it will be of some help.
const SerialPort = require('serialport');
const port = new SerialPort(require('./ports').arduino, {
autoOpen: false,
baudRate: 115200,
dataBits: 8,
parity: 'none',
stopBits: 1,
parser: SerialPort.parsers.readline('\n')
});
let portSatus = 'init';
port.on('error', err => console.log('Error: ', err.message));
port.on('open', () => {
portSatus = 'open';
console.log('Port is open');
setTimeout(() => {
portSatus = 'ready';
console.log('Port is ready');
}, 500);
});
port.on('disconnect', () => {
portSatus = 'disconnected';
console.log('Port disconnected');
});
port.on('closed', () => {
portSatus = 'closed';
console.log('Port is closed');
});
port.open(err => {
if (err) {
console.log('Error opening port: ', err.message);
} else {
console.log('Opening port');
}
});
function dummyCallback(dataArray) {
console.log('Data without callback: ', dataArray);
}
function doCallBackOnData(callBack) {
port.on('data', dataArray => {
console.log('Received data', dataArray);
const data = JSON.parse(dataArray[0]);
data.timeStamp = Date.now();
callBack(data);
doCallBackOnData(dummyCallback);
});
}
doCallBackOnData(dummyCallback);
module.exports = callback => {
if (portSatus === 'ready') {
console.log('Calling command9');
port.write('9', err => {
if (err) {
console.log('command9 error: ', err.message);
callback(err.message);
} else {
console.log('command9 buffered');
port.drain(() => {
console.log('command9 written');
doCallBackOnData(callback);
});
}
});
} else {
console.log('Can not call command9:', portSatus);
callback(portSatus);
}
};
-
\$\begingroup\$ Thanks for the code. I'm trying to understand my way through it. Can you help explain the recursive call to
doCallBackOnData(dummyCallback);
? \$\endgroup\$Klatch Baldar– Klatch Baldar2016年09月25日 16:37:11 +00:00Commented Sep 25, 2016 at 16:37 -
\$\begingroup\$ The docs that I looked at: npmjs.com/package/serialport There are no recursive calls. That code just sets a dummy handler that logs any data that was captured while your actual
callback
has not been sent. \$\endgroup\$Xotic750– Xotic7502016年09月25日 16:59:34 +00:00Commented Sep 25, 2016 at 16:59 -
\$\begingroup\$ Have you executed the code wit your Arduino setup? Did it work? As I said I can not test it. \$\endgroup\$Xotic750– Xotic7502016年09月25日 18:55:54 +00:00Commented Sep 25, 2016 at 18:55
Explore related questions
See similar questions with these tags.
var
atdata = JSON.parse(dataString);
, increasebaudRate
, I would suggest starting at115200, 57600, 38400, 19200, 9600
. I don't seegetTemperatures
that is referenced inmodule.exports.getTemperatures = getTemperatures;
\$\endgroup\$