Skip to content

Hardware Abstraction with firmata

peter edited this page Jun 8, 2017 · 4 revisions

Section A: Get ready for using Firmata with Node.js

Get ready for using Firmata protocol communicate to the MPU and MCU in MediaTek Linkit 7688 Duo with Node.js.

Setup MCU

1. Install Arduino IDE

Set up the MCU by launching Arduino IDE 1.6.5 on your computer.

2. Additional Boards Manager URLs

Open the Arduino IDE and on the File menu click Preferences. In Additional Boards Manager URL add:

http://download.labs.mediatek.com/package_mtk_linkit_smart_7688_index.json

arduino_7688duo_1

3. Install Linkit Smart 7688 Duo in Board Manager

In the Tools menu point to Board then click Boards Manager, a LinkIt Smart 7688 Duo item should appear in the boards list. Select the version and click Install. After the installation, your can find Linkit Smart 7688 Duo in Board and select it.

arduino_7688duo_2

4. Copy the Firmata sketch to Arduino

Copy the sketch code to the Arduino IDE. The sketch used in this example is from: https://gist.github.com/edgarsilva/e73c15a019396d6aaef2

arduino_7688duo_3

5. Upload to Linkit 7688 Duo with network port

In the Tools menu point to Port then you can find Linkit Smart 7688 Duo in Serial Ports and Network Ports. Please select the port in Network Ports. Now, you can clink upload!

arduino_7688duo_4

Setup MPU

While you can install firmata using NPM on LinkIt 7688 Duo, the process is a bit long. So you’ll need to install firmata on your computer and then use SCP to transfer the compressed file to the Linkit 7688 Duo.

1. Create a folder /testfirmata and install firmata in it

mkdir testfirmata && cd testfirmata
npm install [email protected]

2. Remove node-serialport modules in /firmata/node_modules

We need to remove it because serialport is already available on LinkIt Smart 7688 system image.

rm -rf node_modules/firmata/node_modules/serialport/

3. Compress the firmata folder

tar -cvf firmata.tar node_modules/firmata

4. Use SCP to transfer the compressed file to the board:

scp firmata.tar [email protected]:/root   # replace the host name or ip with yours Linkit 7688 Duo

5. ssh into the board (Linkit 7688 Duo)

ssh [email protected]   # replace the host name or ip with yours Linkit 7688 Duo

6 Create a folder /app and decompress firmata.tar in it

mkdir app
tar -xvf firmata.tar -C app/

Section B: Build Your SmartObject

Let's abstract a led and a push button into a smart object with firmata.

1. Create smartobj.js in /app folder on Linkit 7688 Duo

cd app 
touch smartobj.js

2 install the smartobject module in /app folder

npm install smartobject

3. Edit smartobj.js, we will use firmata as the hal controller

var Board = require('firmata'),
    SmartObject = require('smartobject');

var board = new Board("/dev/ttyS0");

// hardware initialization, see [1]
var so = new SmartObject({
    board: board,
    ledPin: 2      // Make sure your LED is connected to pin D2 on Linkit 7688 Duo
});

// Create a Light Control Object Instance in so, see [2] and [3]
// Use firmata to communicate with Linkit 7688 Duo, see [4]
so.init('lightCtrl', 0, {
    onOff: {
        read: function (cb) {
            var board = this.parent.hal.board,
                ledPin = this.parent.hal.ledPin,
                ledState = board.pins[ledPin].value;

            process.nextTick(function () {
                cb(null, ledState);
            });
        },
        write: function (val, cb) {
            var board = this.parent.hal.board,
                ledPin = this.parent.hal.ledPin,
                ledState = val ? board.HIGH : board.LOW;

            board.digitalWrite(ledPin, ledState);

            process.nextTick(function () {
                cb(null, board.pins[ledPin].value);
            });
        }
    }
});

// Use so.read() and so.write() methods to blink the led for few times
// We will use a led blink driver to replace this snippet later
var blinkLed = function () {
    var times = 20,
        blinker = setInterval(function () {
            times -= 1;
            if (times === 0)
                clearInterval(blinker);

            so.read('lightCtrl', 0, 'onOff', function (err, data) {
                if (err)
                    return console.log(err);

                so.write('lightCtrl', 0, 'onOff', !data, function (err, val) {
                    if (err)
                        return console.log(err);
                });
            });
        }, 200);
}

board.on("ready", function() {
    console.log('>> Arduino is ready to communicate');

    // Set a mode for a pin
    board.pinMode(so.hal.ledPin, board.MODES.OUTPUT);

    // Blink our led
    blinkLed();
});

4. Test the led

node smartobj.js

5. Make a led blink driver

var Board = require('firmata'),
    SmartObject = require('smartobject');

var board = new Board("/dev/ttyS0");

// Add our led blink driver to hal
var so = new SmartObject({
    board: board,
    ledPin: 2, 
    blinkLed: null      // we'll implement the driver in the setup function
}, function () {
    this.hal.blinkLed = function (times) {
        times = 2 * times;
        
        var blinker = setInterval(function () {
            times -= 1;
            if (times === 0)
                clearInterval(blinker);

            so.read('lightCtrl', 0, 'onOff', function (err, data) {
                if (err)
                    return console.log(err);

                so.write('lightCtrl', 0, 'onOff', !data, function (err, val) {
                    if (err)
                        return console.log(err);
                });
            });
        }, 200);
    }
});

so.init('lightCtrl', 0, {
    // ... code remains the same
});

board.on("ready", function() {
    console.log('>> Arduino is ready to communicate');

    // Set a mode for a pin
    board.pinMode(so.hal.ledPin, board.MODES.OUTPUT);

    // Blink our led by the driver
    so.hal.blinkLed(10);
});

6. Test the led blink driver

node smartobj.js

7. The push button

var Board = require('firmata'),
    SmartObject = require('smartobject');

var board = new Board("/dev/ttyS0");

// Add our led blink driver to hal
var so = new SmartObject({
    board: board,
    ledPin: 2,
    buttonPin: 3,     // Make sure your push button is connected to pin D3 on Linkit 7688 Duo
    blinkLed: null,      
    pollButton: null  // we'll implement the driver in the setup function
}, function () {
    this.hal.blinkLed = function (times) {
        // ... code remains the same
    }

    this.hal.pollButton = function () {
        setInterval(function () {
            so.read('pushButton', 0, 'dInState', function (err, data) {
                var buttonState = data;
                if (err)
                    return console.log(err);

                // Led will light up acoording to the button state
                so.write('lightCtrl', 0, 'onOff', buttonState ? 1 : 0, function () {});
            });
        }, 100);
    };
});

so.init('lightCtrl', 0, {
    // ... code remains the same
});

// see [1]
so.init('pushButton', 0, {
    dInState: {
        read: function (cb) {
            var board = this.parent.hal.board,
                buttonPin = this.parent.hal.buttonPin,
                buttonState = board.pins[buttonPin].value;

            process.nextTick(function () {
                cb(null, buttonState);
            });
        }
    }
});

board.on("ready", function() {
    console.log('>> Arduino is ready to communicate');

    // Set a mode for a pin
    board.pinMode(so.hal.ledPin, board.MODES.OUTPUT);
    board.pinMode(so.hal.buttonPin, board.MODES.INPUT);
                
    // Register to get the digital value 
    board.digitalRead(so.hal.buttonPin, function(value) {});

    // Poll the button
    so.hal.pollButton();
});

8. Test the push button and watch the led

node smartobj.js

Section C: Network Protocol Come In

In this section, we will use lightweight M2M (LWM2M) to build the IoT network. At server side, we'll use coap-shepherd to create the LWM2M server, and use coap-node on our Linkit 7688 Duo machine to create the LWM2M client. You can build a MQTT network with mqtt-shepherd and mqtt-node as well, they follow the similar pattern with smartobject module.

Client-side (On Linkit 7688 Duo)

1. Our so is a module that abstract all the hardware and drivers

Abstracting the hardware is kind of boring, but once we've packaged it up, we can ship it and resuse it anytime (npm publish it, and use it everywhere)! And you know what? To access the hardware is pretty simple with the smartobject read/write interfaces. This is the best practice of the node.js small surface area philosophy.

In step 7@section B, we've build a nice smart object in smartobj.js, let's add the following line to the end of the file:

var Board = require('firmata'),
    SmartObject = require('smartobject');

// ... code remains the same

// take off this line for our demo can go nicely
// (since we'll let the led blink when machine booted up, but the led is also controlled by the button. That conflicts.)
// so.hal.pollButton();

// export our so
module.exports = so;

2. Install the coap-node module in /app folder

npm install coap-node

3. Create a file client.js in /app folder and write some code

var so = require('./smartobj.js'),
    CoapNode = require('coap-node');

// see [1]
var coapNode = new CoapNode('my_cnode', so);

coapNode.on('registered', function () {
    console.log('>> CoAP node is registered to a server');
});

// Register to the server after Arduino ready to communicate
so.hal.board.on('ready', function () {
    console.log('>> Blink the led for few times');
    so.hal.blinkLed(10);

    // Connect to the server 5 seconds later
    setTimeout(function () {
        console.log('>> Register to a server...');
    
        // replace the ip with yours server-side machine 
        coapNode.register('192.168.1.115', 5683, function (err, msg) {    
            console.log(msg);
        });

        // Poll the button
        so.hal.pollButton();
    }, 5000);
});

Server-side (On PC or Raspberry Pi)

1. Create a folder /cserver and a server.js in it

mkdir cserver && cd cserver
touch server.js

2. Install the coap-shepherd module in /cserver folder

npm install coap-shepherd

3. Edit server.js

var cserver = require('coap-shepherd');

// see [1]
cserver.on('ready', function () {
    console.log('>> coap-shepherd server start!');
    console.log('>> Permit devices joining for 180 seconds');
    cserver.permitJoin(180);
});

cserver.start(function (err) {
    if (err) 
        console.log(err);
});

// see [2]
cserver.on('ind', function (msg) {
    switch (msg.type) {
        case 'devIncoming':
            // When our 'my_cnode' comes in, we read the led and button current states back
            var cnode = msg.cnode;
            if (cnode.clientName === 'my_cnode') {
                cnode.readReq('lightCtrl/0/onOff', function (err, rsp) {
                    if (!err)
                        console.log('>> Current led state at machine: ' + rsp.data);    // rsp = { status, data }
                });

                cnode.readReq('pushButton/0/dInState', function (err, rsp) {
                    if (!err)
                        console.log('>> Current button state at machine: ' + rsp.data); // rsp = { status, data }
                });
            }
            break;

        case 'devStatus':
            // When 'my_cnode' is online, we tell the machine to report the led change to server
            var cnode = msg.cnode;
            if (cnode.clientName === 'my_cnode' && msg.data === 'online') {
                // setting for notification of led state reporting
                cnode.observeReq('lightCtrl/0/onOff', function (err, rsp) {
                    console.log('>> Led observation starts: ');
                    console.log(rsp);
                });
            }
            break;

        case 'devNotify':
            var data = msg.data;
            if (data && data.path === '/lightCtrl/0/onOff') {
                console.log('>> Led state at machine changed: ');
                console.log('    ' + data.value);
            }
            break;

        default:
            // Not deal with other msg.type in this example
            break;
    }
});

Section D: Start LWM2M Network

At server-side (on PC or Raspberry Pi)

node server

At client-side (on Linkit 7688 Duo)

node client
Clone this wiki locally