designing a simple serial api for the arduino and node dot js

Sun, Apr 27, 2014

As I work towards a remote Arduino weather station, sitting out in the garden, or even up a hill (with GPRS perhaps?), I started looking into hooking it up to the Raspberry Pi via the serial port, to investigate an initial hardware control API. Now before you go running off at the thought of RS232 and voltage conversions, the Arduino has an internal serial to usb converter, hoorah!

What does this mean? Well, it means you can plug the arduino into the pi using the usb cable and you’ll get a virtual COM port on the pi. So you don’t have the RS232 hassle but you get the ease of various serial libraries.

For this simple API project I have simple goals:

  • The client will run on Node.js
  • The server (arduino) will accept simple commands to control the onboard LED

that’s it. I want a javascript program that will turn the onboard LED on and off. Simples? Simples indeed as ye shall see!

So how do you start? Well, serial communication means byte by byte, one at a time. The arduino can listen on its serial port but it will need to assemble the bytes into commands. How will it know when it’s received a command? The client will need to tell it. So the API needs a ‘stop byte’. When the arduino sees this stop byte it knows to stop reading and assemble the command. It also needs to know what commands it accepts. So I defined all this in a header file:


#ifndef PiTalkTypes_h
#define PiTalkTypes_h

#define MAX_COMMAND_LENGTH 255

char* replyOK = {"OK\n"};

char resetByte = '!';
char stopByte = '#';

char* commandLEDOn = {"LEDON"};
char* commandLEDOff = {"LEDOFF"};

#endif

When the client sends a ‘#’ character the arduino will assemble the command and compare it with its known commands:

LEDON, LEDOFF

The reset byte is there for buffer overflow protection. As the API is based on a char buffer, if the client sends too many characters, I decided to put the API out of action. The only way to use it again is for the client to send a ‘!’ character and the API will reset. If it doesn’t, every time it sends something it’ll get ‘@’ back. You’ll see all that in a moment.

So in a nutshell, we’re bascially doing this on the client:


Arduino arduino = new Arduino();
arduino.ledON();
arduino.ledOFF();

Send the ‘LEDON’ command and the LED comes on. Send the ‘LEDOFF’ command and the LED goes off. Simples.

Here’s the sketch to do that:


#include "PiTalk.h"

int incomingByte = 0;
int charCount = 0;
char command[MAX_COMMAND_LENGTH + 1]; // leave space for \0
bool locked = false;
int led = 13;

void setup() {
  pinMode(led, OUTPUT);
  Serial.begin(9600);
}

void loop() {
  if (Serial.available()) {
    incomingByte = Serial.read();
    
    if ((char)incomingByte == resetByte) {
      locked = false;
      charCount = 0;
      return;
    }
  
    if (locked) {
      Serial.println("@");
      return;
    }
    
    if ((char)incomingByte != stopByte) {
      if (charCount > MAX_COMMAND_LENGTH - 1) {
        Serial.println("@");
        locked = true;
        return;
      }
      else {
        command[charCount] = (char)incomingByte;
        charCount++;
      }
    }
    else {
      command[charCount] = '\0';
      charCount = 0;
      if (strcmp(commandLEDOn, command) == 0) {
        digitalWrite(led, HIGH);
        Serial.println(replyOK);
      }
      else if (strcmp(commandLEDOff, command) == 0) {
        digitalWrite(led, LOW);
        Serial.println(replyOK);
      }
    }
  }
}

Compile and upload the sketch (I used an Arduino UNO btw), open up the serial monitor and enter:

LEDON#

the LED will come on and you’ll get this back:

OK

then enter:

LEDOFF#

the LED will go off and you’ll get this back:

OK

A simple LED API!

Now for the client. Download Node.js and do this:


mkdir pitalkn
cd pitalkn
npm install serialport

Here’s the code I wrote to talk to the API on the Uno. Note that you’ll need to change your virtual COM port from:

/dev/tty.usbmodemfa131


var serialport = require("serialport");
var SerialPort = serialport.SerialPort;

var command = process.argv[2];

var serialPort = new SerialPort("/dev/tty.usbmodemfa131", {
  baudrate: 9600,
    dataBits: 8,
  parity: 'none',
  stopBits: 1,
  flowControl: false,
    parser: serialport.parsers.readline("\r")
});

serialPort.on("open", function () {
  console.log('open');

  serialPort.on('data', function(data) {
        result = data.trim();
        console.log('data received: ' + result);
        if (result === 'OK') {
            console.log('command successful');
        }
        else {
            console.log('command not successful');
        }
  });

    setTimeout(function() {
        console.log("waiting...");
        command = command + '#'
      serialPort.write(command, function(err, results) {
        console.log('err ' + err);
        console.log('results ' + results);
      });
    }, 3000);
    
    serialPort.on('error', function (err) {
        console.error("error", err);
    });
});

To turn on the LED, do this:

node pitalkn LEDON

to turn the LED off do this:

node pitalkn LEDOFF

Now you might be wondering what this line is for:

setTimeout(function()

well, that’s a bit of a gotcha. Whenever you connect to the arduino over the serial connection, it resets itself. So you have to wait until it’s sorted itself out before you can send it commands or read from it.

A very simple hardware API proof of concept.

Now, if you don’t fancy grappling with wifi on the arduino, you can use the Raspberry Pi as a much cheaper alternative to networking sensor data. Just use Node.js on the pi to create a web server and have multiple simple hardware APIs to interface to your collection of arduinos. As long as you can link to them via serial comms.

An exciting alternative connection option is on the horizon though. Bluetooth Low Energy (BLE), which I’ll be exploring once I get hold of an arduino BLE shield…

References:

comments powered by Disqus