Mobilizing the Internet of Things (IoT) Part 2 -- Raspberry Pi 3 Model B

Raspberry Pi 3 Model B

The Raspberry Pi 3 Model B is a small, inexpensive, single board computer often used by hobbyists and developers. You can pick one of these up for under $40.

The Raspberry Pi 3 Model B gets even more interesting when you start hooking it up to things - sensors, motors, switches, etc. It can drive anything from an automatic chicken coop door to a CNC machine.

Getting up and running on the Raspberry Pi 3 Model B

We will be using Node.js combined with the standard Linux mechanism for accessing GPIOs.

Install an OS that supports Node 0.12 or later

You will need to make sure that you have at least Raspbian "Jessie" (8.0) or later. The current images all meet this criteria. Earlier versions of Debian do not support Node 0.12 or later. To install the latest Raspbian image, I followed the instructions in this forum post.

Hook up to the network

I plugged in the Ethernet cable. I have DHCP, so the network "just worked."

Connect to your Raspberry Pi

On my network, I was able to find my Raspberry Pi using the name "raspberrypi.local".

If you need to find it, you can use nmap to find any machine that has port 22 (SSH) open:

$ nmap -p 22 --open -sV 192.168.0.* -Pn

Starting Nmap 7.12 ( https://nmap.org ) at 2016-06-20 16:14 PDT  
...
Nmap scan report for raspberrypi (192.168.0.21)  
Host is up (0.0017s latency).  
PORT   STATE SERVICE VERSION  
22/tcp open  ssh     (protocol 2.0)  
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :  
SF-Port22-TCP:V=7.12%I=7%D=6/20%Time=576878D3%P=x86_64-apple-darwin15.4.0%  
SF:r(NULL,29,"SSH-2\.0-OpenSSH_6\.7p1\x20Raspbian-5\+deb8u2\r\n");  

Then I logged in using ssh to the default "pi" user account (you may get an SSH warning about the keypair, feel free to validate the fingerprint.)

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

Make sure you maximize your storage

In order to install the new software, I made all of my SD card storage space available to the filesystem. Using the raspi-config utility, I chose the "Expand filesystem" option.

raspi-config UI

Install Node 0.12 or later

I updated all of my packages on the system and rebooted (you know, just in case.)

$ sudo apt-get update
$ sudo apt-get upgrade
$ sudo shutdown -r now

Now, the version of Node that comes with the current Raspbian distributions isn't... contemporary:

$ node --version
v0.10.29  

The "co" library used in Synchro requires Node 0.12 or later, so Node will need to be updated. I followed the instructions from https://nodejs.org/en/download/package-manager/ to install a more recent version of Node on a Debian installation:

$ curl -sL https://deb.nodesource.com/setup_4.x | sudo -E bash -
$ sudo apt-get install nodejs
$ node --version
v4.4.0  

I used the 4.X branch -- it's possible 5.X will work fine on this platform.

And updated npm while I was at it.

$ sudo npm install -g npm
/usr/bin/npm -> /usr/lib/node_modules/npm/bin/npm-cli.js
npm@3.8.2 /usr/lib/node_modules/npm  
$ npm --version
3.8.2  

Install Synchro

The instructions for installing Synchro, creating a project and creating an app are just the steps from https://synchro.io/getstarted

$ sudo npm install -g synchro
$ synchro --version
2.2.6  

Create a new Synchro project

$ mkdir SynchroRaspberryPi
$ cd SynchroRaspberryPi/
$ synchro init

Create a new Synchro app

$ synchro new pi-demo
Got config from synchro-api - Using default configuration file: config.json  
App description: Synchro Raspberry Pi Demo  
Processing file: main.js  
Processing file: package.json  
Synchro application 'pi-demo' created  

Run your Node instance

$ node app.js 
[2016-06-20 18:49:47.531] [INFO] app - Synchro server loading - Using default configuration file: config.json
[2016-06-20 18:49:49.162] [INFO] synchro-api - Creating API processor for app at path: pi-demo, debug port is: 6868
[2016-06-20 18:49:50.504] [INFO] api-request-processor - API request processor for app container: pi-demo at path: pi-demo - ready
[2016-06-20 18:49:50.545] [INFO] app - Synchro server listening on port 1337, node version: v4.4.5

Fire up Synchro Studio

Connect to your server IP from your web browser, port 1337, you should see Synchro Studio running on your Raspberry Pi:

Click on "Edit/Debug" to start editing your app.

A GPIO example

Replace your main.js with the following:

var fs = require("fs");

// Main page
//
exports.View =  
{
    title: "pi-demo",
    elements:
    [
        { control: "text", value: "Synchro Raspberry Pi Demo", fontsize: 12 },

        { control: "stackpanel", width: "*", contents: [
            { control: "toggle", caption: "{caption}", binding: { foreach: "gpios", value: "on", onToggle: { command: "toggleGpio", gpio: "{$index}" }} },
            ]}
    ]
}

function setGpio(gpio)  
{
    gpioPath = "/sys/class/gpio/gpio" + gpio.gpio;
    value = gpio.on ? "1" : "0";

    fs.appendFileSync(gpioPath + "/value", value);
    console.log("Set gpio " + gpioPath + " to " + value);
}

function initGpio(gpio)  
{
    gpioPath = "/sys/class/gpio/gpio" + gpio.gpio;

    // Make sure the pin is exported and ready to go

    if (!fs.existsSync(gpioPath))
    {
        fs.appendFileSync('/sys/class/gpio/export', gpio.gpio);
    }

    fs.appendFileSync(gpioPath + "/direction", "out");

    setGpio(gpio);
}

function initGpios(gpios)  
{
    for (index = 0, len = gpios.length; index < len; ++index)
    {
        initGpio(gpios[index]);
    }
}

exports.InitializeViewModel = function(context, session)  
{
    var viewModel =
    {
        gpios: [
            { caption: "Motor 1", gpio: "17", on: false },
            { caption: "Motor 2", gpio: "27", on: false  },
            { caption: "Motor 3", gpio: "22", on: false  },
            { caption: "Motor 4", gpio: "10", on: false  },
        ]
    }
    initGpios(viewModel.gpios);
    return viewModel;
}

exports.Commands =  
{
    toggleGpio: function(context, session, viewModel, params) {
        setGpio(viewModel.gpios[params.gpio]);
    }
}

Let's walk through the interesting parts of this.

var fs = require("fs");  

Top-level require for the fs module, used for accessing the filesystem. Later when we configure the GPIOs we will be accessing the filesystem to accomplish this.

{ control: "stackpanel", width: "*", contents: [
    { control: "toggle", caption: "{caption}", binding: { foreach: "gpios", value: "on", onToggle: { command: "toggleGpio", gpio: "{$index}" }} },
    ]}

This sets up a stack panel with toggle controls for all of the GPIOs. The foreach inside will make multiple toggle controls, one for each element in an array of objects defined in the view model called gpios that have a caption property that will be the caption of the control. When the control is pressed, the toggleGpio command will be called, and that method will be presented with an object containing a gpio element with the array index ($index) of the GPIO. The current value of the toggle control will be stored in the on property.

function setGpio(gpio)  
{
    gpioPath = "/sys/class/gpio/gpio" + gpio.gpio;
    value = gpio.on ? "1" : "0";

    fs.appendFileSync(gpioPath + "/value", value);
    console.log("Set gpio " + gpioPath + " to " + value);
}

This is a helper method for turning on/off a GPIO. The gpio parameter is an object containing both a gpio member (the identifier of the GPIO) and an on member (if this member is Javascript true then the GPIO is turned on, otherwise it is turned off).

function initGpio(gpio)  
{
    gpioPath = "/sys/class/gpio/gpio" + gpio.gpio;

    // Make sure the pin is exported and ready to go

    if (!fs.existsSync(gpioPath))
    {
        fs.appendFileSync('/sys/class/gpio/export', gpio.gpio);
    }

    fs.appendFileSync(gpioPath + "/direction", "out");

    setGpio(gpio);
}

Initializes a single GPIO. If the GPIO is not already enabled, it is enabled and then set up as an output. The members of the gpio parameter are as above for setGpio.

function initGpios(gpios)  
{
    for (index = 0, len = gpios.length; index < len; ++index)
    {
        initGpio(gpios[index]);
    }
}

Initializes an array of GPIOs. Iterates through an array of GPIOs, calling initGpio on each.

exports.InitializeViewModel = function(context, session)  
{
    var viewModel =
    {
        gpios: [
            { caption: "Motor 1", gpio: "17", on: false },
            { caption: "Motor 2", gpio: "27", on: false  },
            { caption: "Motor 3", gpio: "22", on: false  },
            { caption: "Motor 4", gpio: "10", on: false  },
        ]
    }
    initGpios(viewModel.gpios);
    return viewModel;
}

Fills in the default view model. The important part here is the configuration for the GPIOs that is used by both the toggle controls and sent to the toggleGpio command later.

exports.Commands =  
{
    toggleGpio: function(context, session, viewModel, params) {
        setGpio(viewModel.gpios[params.gpio]);
    }
}

The toggleGpio command is called when any of the GPIO toggle controls is pressed. The params object contains the index of the GPIO toggled in the gpio member. So we dereference that in the current view model object. The control binding in the view has already updated the on member of the GPIO in the view model, so the only thing to do is to call the setGpio function to actually set the GPIO state.

View your new UI with Synchro Explorer

On the main server page, click on "Web App" to view your app in a browser, after that you should see your new UI:

You can also point to your new app in Synchro Explorer by adding an endpoint for http://hostip:1337/api/pi-demo (substitute the local host IP address for 'hostip') or by using the QR code on the main server page.

When you tap on the toggle control next to a GPIO, it should turn on or off depending on the toggle control state.

Congratulations! You now have Raspberry Pi functionality in your Synchro environment. You can now access GPIOs, UARTs, I2C, SPI, everything you can do on a Raspberry Pi with JavaScript.