4
\$\begingroup\$

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;
\$\endgroup\$
  • \$\begingroup\$ I don't understand the 999ms setTimeout. What is the purpose of that delay ? \$\endgroup\$ – Hugeen Sep 22 '16 at 9:49
  • \$\begingroup\$ I need to make sure there is some delay because it takes time for the machine to return the temperatures. But not so much delay that I am getting the next set of them (I am polling for temps every 1000ms). Ideally this code would be less coupled to downstream usage. It is quite fragile now. \$\endgroup\$ – Klatch Baldar Sep 22 '16 at 14:20
  • \$\begingroup\$ In what way is it fragile? Declare var at data = JSON.parse(dataString);, increase baudRate, I would suggest starting at 115200, 57600, 38400, 19200, 9600. I don't see getTemperatures that is referenced in module.exports.getTemperatures = getTemperatures; \$\endgroup\$ – Xotic750 Sep 22 '16 at 22:32
  • 1
    \$\begingroup\$ Looking at this a little further, I can't help but feel that you are going about something in the wrong way. If your Arduino is measuring temperatures then it feels like it should be just sending out data on the serial, when connected, and that your Javascript should just look at the data presently received. Just now it feels like you are trying to control how fast the data is sent from the Arduino within your script, but in a very strange way, an async polling. \$\endgroup\$ – Xotic750 Sep 23 '16 at 1:24
  • \$\begingroup\$ I agree that it seems wrong; I'm just trying to figure out what would be more right! I know that the temperatures are always ordered, so that if there are multiples calls to getTemperatures in order, the data returned will always be in the same order. I am just trying to figure out how to tie the two functions together. \$\endgroup\$ – Klatch Baldar Sep 23 '16 at 5:54
1
\$\begingroup\$

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);
};
\$\endgroup\$
0
\$\begingroup\$

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);
  }
};
\$\endgroup\$
  • \$\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 Sep 25 '16 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 Sep 25 '16 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 Sep 25 '16 at 18:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.