Node in small places

Presented by Carlos Justiniano / NYC JavaScript @ Flatiron / @cjus / ver: 1.0.0

About me

  • Carlos Justiniano, NYC JavaScript @ Flatiron founder
  • First IoT project in 2001
  • Ten years later I learned about NodeJS
  • Developed Node applications in production use since 2011
  • Chief Architect at Flywheel, note: we're hiring!

Acronyms and Vernacular

  • SoC: System on a Chip. Integrates system components into a single chip. Think: processor, GPU, memory and other interface layers for IO
  • PCB: Printed Circuit Board. Integrates components into a convenient package
  • SBC: Single Board Computer. Contains an SoC and integrates other components and interfaces into a single printed circuit board
  • IoT: Internet of Things. Networked devices built using the above tech
  • Microcontroller: Differs from a microprocessor (CPU) in that unlike a CPU it also features memory and other SoC components

What we'll cover

  • Setting up Node on Single Board Computers (SBCs)
  • SBCs communicating with other computers and controlling devices such as LCDs and Lights

What we won't cover

  • We won't cover other popular languages for controlling SBCs, such as C/C++ and Python
  • We can't cover the full breath of what's out there
  • Think of this presentation as a reasonably quick introduction

We will deep dive!

  • But, you won't need to feverishly take notes
  • This presentation and accompanying how-tos are posted in its own github repo
  • https://github.com/cjus/node-in-small-places-presentation

So, why bother with SBCs?

  • Because we can! SBCs are far more approachable than you might expect
  • Low cost facilitates getting started
  • They're also a great way to learn about Linux and machine configuration
  • The resource restrictions get you thinking about how to optimize your applications, that generally helps you create better software
  • And ...

Your knowledge scales!

How we got here

  • The industry benefited from advancements in the creation of mobile devices
  • Demand for mobile devices reduced the cost via economies of scale
  • Educators leveraged this opportunity to make these devices great teaching tools
  • People like us fuel demand for these devices!

Let's begin our deep dive

How SBC's differ from our other computers

  • No keyboard, mouse or screen
  • Memory is severely limited
  • No hard disk, storage is often a microSD card with limited write cycles
  • The saying "you get what you pay for" definitely applies here

How SBC's differ from our other computers

One of the key differences with SBCs is that you get general purpose input output pins, referred to as GPIO pins

GPIO Pins

Allow you to connect to other devices such as sensors, motors, switches and other controllers

And those devices are some of the building blocks of robotics

An SBC - a close up look

Why Raspberry Pi?

  • I chose to feature the Raspberry Pi family of SBCs
  • So, why Pi? After all, there are a lot of other options available
  • I've played with the odroid C2 which is twice as fast with twice the memory of Pi3 at only 10 dollars more
  • The reason for choosing Pi is because it enjoys a large online community with support from addon vendors
  • However, everything I'll cover today will apply to almost any SBC you choose

Raspberry Pi Zero

  • Broadcom BCM2835 ARM11 core processor running at 1GHz
  • 512MB RAM
  • Features a Micro-SD card slot, a mini-HDMI socket for 1080p60 video, Micro-USB sockets for data and power
  • Tiny! at only 2.5 inches by just over 1 inch
  • Runs Linux
  • Cost: only $5 dollars!

DEMO!

Using GPIO pins

This is like the hello world program of embedded systems ;-)

DEMO!

Using GPIO pins

Software Setup

  • First we copy a Linux image onto a microSD card
  • Then we install NodeJS
  • And finally we'll see how our light demos actually work!

OS Setup

OS Setup

Sorry, but we won't do this process live, because it can take up to 30 minutes to complete

  • Detailed How-To: https://github.com/cjus/hydra-cluster/wiki/Setting-up-a-Pi-Zero
  • Download OS from: https://www.raspberrypi.org/downloads/raspbian/
  • Insert a microSD in drive and type:
$ sudo dd if=./2016-05-27-raspbian-jessie-lite.img of=/dev/disk2

OS Setup

  • When the image is copied you can find a boot area of the disk
  • You'll just need to edit two files to establish networking

OS Setup

OS Setup

These settings will allow us to network our Pi over USB

  • Edit config.txt by appending dtoverlay=dwc2 to the end of the file and saving it.
  • Edit cmdline.txt by inserting modules-load=dwc2,g_ether after rootwait. Make sure that the file only contains a single line with no newlines.

Let's connect

  • Safely eject your SD card and insert it into your Pi
  • Connect the USB cable to your Pi to provide power and connectivity

OS Setup

You should be able to ping it...

$ ping raspberrypi.local
PING raspberrypi.local (169.254.2.0): 56 data bytes
64 bytes from 169.254.2.0: icmp_seq=1 ttl=64 time=0.588 ms
64 bytes from 169.254.2.0: icmp_seq=2 ttl=64 time=0.651 ms
64 bytes from 169.254.2.0: icmp_seq=3 ttl=64 time=0.576 ms
64 bytes from 169.254.2.0: icmp_seq=4 ttl=64 time=0.626 ms

SSH

If the former works you can SSH into your Pi with a default user of pi and a password of raspberry

$ ssh pi@raspberrypi.local
pi@raspberrypi.local's password:

DEMO!

Connecting to RPi

Installing Node!

$ sudo apt-get install wget
$ mkdir nodejs
$ cd nodejs
$ wget https://nodejs.org/dist/v6.5.0/node-v6.5.0-linux-armv6l.tar.xz
$ tar -xvf node-v6.5.0-linux-armv6l.tar.xz
$ cd node-v6.5.0-linux-armv6l/
$ sudo cp -R * /usr/local/

You should now have Node installed

$ cd ~
$ node --version
v6.5.0

CODE DEMO!

How our light demos work

  • We created a folder for our light demos: mkdir light-demos; cd light-demos; npm init
  • We just installed the pi-blaster NPM package: npm install pi-blaster

sine.js

const piblaster = require('pi-blaster.js');
const LED_PIN = 4;

let y = 0;
let intervalID = setInterval(() => {
  let s = Math.abs(Math.sin(y/10));
  piblaster.setPwm(LED_PIN, s);
  y += 1;
  if (y > 180) {
    y = 0;
  }
}, 100);

process.on('SIGINT', () => {
  clearInterval(intervalID);
  piblaster.setPwm(LED_PIN, 0);
  setInterval(() => {
    process.exit();
  }, 1000);
});

sos.js

const piblaster = require('pi-blaster.js');
const LED_PIN = 4;
const LED_ON = 1;
const LED_OFF = 0;
const SOS_PATTERN = [
  LED_ON, LED_ON, LED_ON,
  LED_OFF,
  LED_ON, LED_OFF, LED_ON, LED_OFF, LED_ON,
  LED_OFF, LED_OFF,
  LED_ON, LED_ON, LED_ON,
  LED_OFF, LED_OFF, LED_OFF, LED_OFF, LED_OFF, LED_OFF];

let i = 0;
let intervalID = setInterval(() => {
  piblaster.setPwm(LED_PIN, SOS_PATTERN[i]);

  if (i > SOS_PATTERN.length - 1) {
    i = 0;
  } else {
    i += 1;
  }
}, 500);

process.on('SIGINT', () => {
  clearInterval(intervalID);
  piblaster.setPwm(LED_PIN, 0);
  setInterval(() => {
    process.exit();
  }, 1000);
});

DEMO!

Fancier display

Even more displays...

We'll see even more displays later in this presentation

Connecting SBCs

Playing with single board computers is really cool.

Getting them to communicate with one another is even cooler!

Messaging options

There are a lot of ways to handle communication:

  • Express based APIs
  • Web sockets
  • Using an intermediary such as Rabbitmq, MQTT or Redis
  • Over USB using serial communication
  • And even by creating a communication bus via wires to GPIO pins

Messaging options

In our next demos we'll look at serial communication and messaging via Redis pub/sub and ExpressJS APIs

Introducing the Hydra Cluster

  • Here at Flywheel we've built a set of libraries we call Hydra. Hydra allows us to build, deploy, and monitor microservices to the cloud
  • During the past month I built a 16 core cluster to demonstrate Hydra running on low cost hardware doing microservice-y things

Hydra Cluster

Hydra Cluster

  • If you're interested in this sort of thing see the Github Repo for tech info: https://github.com/cjus/hydra-cluster

Communication revisited

Let's get back to communication

Using the Hydra Cluster we'll examine:

  • Serial communication
  • Redis Pub/Sub
  • ExpressJS-based APIs

Serial Communication

  • I built a temperature display using an Arduino microcontroller, a temperature sensor and a 16x2 LCD display

Serial Communication

  • The device is connected to a Raspberry Pi using serial over USB
  • A Node microservice running on a Pi recieves the serial data and sends it to another microservice via messaging
  • A service also renders the uptime and temperature data on a connected 3.5 inch LCD display

CODE DEMO!

Communication over Serial

  • First we'll look at the code for the Arduino microcontroller
  • It's not JavaScript, but it will be easy to follow
  • Then we'll look at how the node app recieves serial data via node-serial NPM package

Arduino Serial Code


#include 
#include 
#include 

#define LCD_WIDTH 16
#define LCD_HEIGHT 2

OneWire ds(11);  // on pin 11
LiquidCrystal lcd(12, 2, 4, 5, 6, 7);

void setup() {
  Serial.begin(9600);
  lcd.begin(LCD_WIDTH, LCD_HEIGHT);
  lcd.clear();
  lcd.setCursor(1, 0);
  lcd.print("HYDRA  CLUSTER");
}

void loop() {
  displayUptime();
  displayTemperature();
  delay(1000);
}

void displayUptime() {
  char buf[LCD_WIDTH];
  unsigned int days = 0;
  unsigned int hours = 0;
  unsigned int mins = 0;
  unsigned int secs = 0;

  secs = millis() / 1000;

  mins = secs / 60;
  hours = mins / 60;
  days = hours / 24;

  secs = secs - (mins * 60);
  mins = mins - (hours * 60);
  hours = hours - (days * 24);

  sprintf(buf, "%02d %02d:%02d:%02d", days, hours, mins, secs);
  lcd.setCursor(0, 1);
  lcd.print(buf);
  Serial.write(buf);
  Serial.write('|');
}

void displayTemperature() {
  int HighByte, LowByte, TReading, SignBit, Tc_100, Tf_100, Whole, Fract;
  char buf[LCD_WIDTH];

  byte i, sensor;
  byte present = 0;
  byte data[12];
  byte addr[8];

  ds.reset_search();
  if (!ds.search(addr)) {
    lcd.setCursor(0,0);
    lcd.print("No more addr.");
    ds.reset_search();
    delay(250);
    return;
  }

  if (OneWire::crc8(addr, 7) != addr[7]) {
    lcd.setCursor(0,0);
    lcd.print("CRC not valid");
    return;
  }

  if (addr[0] != 0x28) {
    lcd.setCursor(0,0);
    lcd.print("Not a DS18B20.");
    return;
  }

  ds.reset();
  ds.select(addr);
  ds.write(0x44, 1);         // start conversion, with parasite power on at the end

  present = ds.reset();
  ds.select(addr);
  ds.write(0xBE);         // Read Scratchpad

  for (i = 0; i < 9; i++) {
    data[i] = ds.read();
  }

  LowByte = data[0];
  HighByte = data[1];
  TReading = (HighByte << 8) + LowByte;
  SignBit = TReading & 0x8000;  // test most sig bit
  if (SignBit) { // negative
    TReading = (TReading ^ 0xffff) + 1; // 2's comp
  }
  Tc_100 = (6 * TReading) + TReading / 4;    // multiply by (100 * 0.0625) or 6.25
  Tf_100 = ((Tc_100 * 9.0) / 5.0  + 32 * 100) / 100;

  sprintf(buf, "%dF", Tf_100);
  int pos = LCD_WIDTH - strlen(buf);
  lcd.setCursor(pos, 1);
  lcd.print(buf);
  Serial.write(buf);
  Serial.write('\n');
}
  

Node Serial Code


const SerialPort = require('serialport');

let portName = '/dev/ttyUSB0';

let myPort = new SerialPort(portName, {
  baudRate: 9600,
  dataBits: 8,
  parity: 'none',
  stopBits: 1,
  flowControl: false
});

myPort.on('open', () => {
  console.log('Open Connection');
});

myPort.on('data', (data) => {
  console.log(data.toString());
});
    

DEMO!

Cluster Temperature Display

  • Let's fire up the cluster and see this in action

Controlling the cluster using HTTP and sockets

MCP, the master control program

  • One of the cluster computers is running an ExpressJS microservice which serves up a ReactJS web app called the MCP
  • The MCP webapp makes REST calls to the MCP service based on the commands we'll issue
  • The MCP service in-turn publishes messages via a socket to Redis for Pub/Sub messaging
  • A "pilights" service running on a Raspberry Pi Zero listens for messages which instruct it on how to animate a light display

Let's have a look at this MCP program

Let's talk code!

  • The Hydra Cluster uses an NPM library called Hydra-core which we use here at Flywheel to create Microservices
  • As we'll see - the library is super easy to use
  • It will be open sourced later this year
  • If you want early access - come see me after the talk!

Pi Lights Service Code


/**
* @name Service
* @description This is the service entry point
*/
const http = require('http');
const cluster = require('cluster');
const os = require('os');
const hydra = require('@flywheelsports/hydra');
const Utils = require('@flywheelsports/jsutils');
const version = require('./package.json').version;
const handler = require('./handler');

let config = require('@flywheelsports/config');
config.init('./config/config.json')
  .then(() => {
    config.version = version;
    config.hydra.serviceVersion = version;

    /**
    * Handling for process invocation as a process master or child process.
    */
    if (config.cluster !== true) {
      initWorker();
    } else {
      if (cluster.isMaster) {
        const numWorkers = config.processes || os.cpus().length;
        console.log(`${config.hydra.serviceName} (v.${config.version})`);
        console.log(`Using environment: ${config.environment}`);
        console.log('info', `Master cluster setting up ${numWorkers} workers...`);

        for (let i = 0; i < numWorkers; i++) {
          cluster.fork();
        }

        /**
         * @param {object} worker - worker process object
         */
        cluster.on('online', (worker) => {
          console.log(`Worker ${worker.process.pid} is online`);
        });

        /**
         * @param {object} worker - worker process object
         * @param {number} code - process exit code
         * @param {number} signal - signal that caused the process shutdown
         */
        cluster.on('exit', (worker, code, signal) => {
          console.log(`Worker ${worker.process.pid} died with code ${code}, and signal: ${signal}`);
          console.log('Starting a new worker');
          cluster.fork();
        });
      } else {
        initWorker();
      }
    }
  });

/**
* @name initWorker
* @summary Initialize the core process functionality.
*/
function initWorker() {
  /**
  * Initialize hydra.
  */
  hydra.init(config.hydra)
    .then(() => {
      return hydra.registerService();
    })
    .then((serviceInfo) => {
      let logEntry = `Starting hydra-router service ${serviceInfo.serviceName} on port ${serviceInfo.servicePort}`;
      hydra.sendToHealthLog('info', logEntry);
      console.log(logEntry);

      hydra.on('log', (entry) => {
        console.log('>>>> ', entry);
      });

      let channel = `iotnode:${serviceInfo.serviceName}`;
      console.log('listening on channel', channel);
      hydra.openSubscriberChannel(channel);
      hydra.subscribeToChannel(channel, function(message) {
        console.log('recieve message', message);
        handler.process(message);
      });
    })
    .catch((err) => {
      console.log('err', err);
    });
}
    

Pi Lights Service Handler Code


/**
* @name Handler
* @description Handle incoming hydra message
*/
const hydra = require('@flywheelsports/hydra');
const Utils = require('@flywheelsports/jsutils');
const XProcess = require('xprocess');

class Handler {
  constructor() {
    this.xProcess = null;
  }

  process(message) {
    console.log('message.bdy.cmd', message.bdy.cmd);
    if (this.xProcess) {
        this.xProcess.close();
    }
    this.xProcess = XProcess.createClient();
    this.xProcess.run('python', [`${message.bdy.cmd}.py`]);
  }
}

module.exports = new Handler();
    

Recap

  • We've seen that we can get started with Node and SBCs for the price of a decent New York lunch
  • Setting one up isn't rocket science
  • Things get really interesting when you linkup SBCs
  • NodeJS runs just fine on these processors

Inside Node

  • Node and V8 are compiled for the underlying processor - which is great!
  • You can start Node with flags intended to tweak V8 memory usage
  • Future versions of Node may optimize for IoT - perhaps MS Chakra?
  • Node on ARM processors: https://nodesource.com/blog/node-and-arm

In many ways - Node's IoT story is just unfolding

Next Steps

Hopefully this presentation has given you a sense of how to use Node in small places.

Want to learn more? Checkout these cool projects:

  • JohnnyFive: https://github.com/rwaldron/johnny-five
  • Cylonjs: https://cylonjs.com/

Next Steps

  • Hydra cluster is still under development
  • Follow the repo for new developments and code releases, You'll find detailed how-tos to get you started
  • https://github.com/cjus/hydra-cluster
  • Follow and reach out to me via Twitter if you have questions @cjus

Contact

  • cjus on Twitter and Github
  • Email: cjus34@gmail.com
  • About: http://cjus.me