34
Programming IoT Gateways in JavaScript with macchina.io Günter Obiltschnig Applied Informatics Software Engineering GmbH [email protected] @obiltschnig, @macchina_io

Programming IoT Gateways in JavaScript with macchina.io

Embed Size (px)

Citation preview

Programming IoT Gateways in JavaScript with macchina.io

Günter Obiltschnig Applied Informatics Software Engineering GmbH [email protected] @obiltschnig, @macchina_io

About Me

hard-core C++ developer (20+ years), who also likes JavaScript

“full stack++”, embedded, hardware to web frontend + cloud

POCO C++ Libraries (2004)

Applied Informatics GmbH (2006)

my-devices.net (2010)

AIS Radar for iOS (2011)

macchina.io (2013)

m .ioacchinaA modular open source toolkit for building embedded IoT

applications that connect sensors, devices and cloud services.

IoT Gateway

my-devices.netCloud Services

AirVantage, Bluemix, Tinamous, Xively, etc.

HTTP(S) MQTT

Remote Access my-devices.net

device apps local “business logic”

web services web visualization

database discoverability

Mobile/Web Clients

Devices/Sensor Networks

CoAP, IEEE 802.15.4, Modbus, USB,

Bluetooth, RS-232

> open source (Apache 2.0 License)

> built in C++ for best performance and efficiency (JavaScript for parts of web interface)

> modular and extensible

> mature, proven codebase: POCO C++ Libraries, Google V8, Eclipse Paho, SQLiteAngularJS, jQuery, OpenLayers, Ace (text editor),+ Applied Informatics OSP and Remoting frameworks

> C++-to-JavaScript bridge

> Raspberry Pi, Beaglebone, Edison, RED, MangOH, etc.

> prototype on Linux or OS X host, easily deploy to device

> web interface with JavaScript editor

Sensors & Devices Protocols Cloud Services

Temperature, Ambient Light, Humidity, Air Pressure, etc.

HTTP AirVantage

I/O, Trigger, Rotary Encoder MQTT Bluemix

Accelerometer CoAP* Twitter

GNSS/GPS WebEvent Twilio (SMS)

Barcode Reader, RFID* WebTunnel my-devices.net

XBee (ZigBee, IEEE 802.15.4) XBee API any with HTTP/REST APIs

Serial Port Modbus*, CANopen*

* planned

Pro Users and Device Manufacturers

> add device specific APIs

> make devices programmable in JavaScript for partners or end users

> device specific app store (sell additional software features)

> additional frameworks (UPnP, Remoting SOAP and JSON-RPC)

> customizable web user interface

> improved user authentication and authorization

> signed bundles

> pro support

Demo

Inside macchina.io

POCO C++ Libraries

> Started 2004

> ~300.000 LOC

> 1000+ classes

> on GitHub since 2012 1000+ stars400+ forks30-50 clones/day

> ~100 contributors

> Boost License

> http://pocoproject.org

POSIX, WIN32, other (RT)OS API

Foundation

C++ and C Standard LibrariesAp

plic

atio

n

Zip

Net

Crypto

Data

SQLite

ODBC

MySQL

NetSSL

Util

Tools, Utilities and additional Libraries

XML JSON

V8

> Google’s JavaScript Engine

> Used by Chrome/Chromium and node.js

> C++ library, (reasonably) easy to integrate and to extend

> Compiles JavaScript to native code (x86, ARM, MIPS)

> Great performance

> BSD License

Remoting

> Similar to .NET Remoting or Java RMI, but for C++

> Code generator parses annotated C++ header files and generates code(serialization/deserialization, method dispatching, helpers)

> Supports different transports (binary TCP, SOAP, JSON-RPC)

> Used for automatic C++-to-JavaScript bridging

> Will also be used to implement sandbox mechanism

Open Service Platform (OSP)

> Inspired by OSGi, but for C++ (also JavaScript, Python, etc.)

> Dynamic module system based on bundles(Zip files with metadata, shared libs, other files)

> Dependency and lifecycle management

> Services and service registry

> Web Server

POCO Core Libraries(Foundation, XML, Util, Net)

OperatingSystem

API

Std. C/C++ LibrariesService Registry

Portable Runtime Environment

Life

Cyc

le

Man

agem

ent

Bundle Managem

ent

Stand

ard

Service

s

Bundles

install, resolve, start, stop and uninstall bundles

provide services to other bundles and find services

provided by other bundles

manage bundle versionsand dependencies

web server, web- and console-

based management, user authentication and authorization,

preferences, etc.

application-specific functionality and services

Combining POCO C++ Libraries and V8

> JavaScript is single-threaded and garbage-collected

> POCO is multithreaded (specifically web server)

> Make C++ object available to JavaScripteasy for static objects, just provide Wrapper

> Allow JavaScript code to create C++ objectseasy if you don’t care about memory/resource leaks

> Register a callback function called by GC when object is deletedallows you to properly delete underlying c++ object

> However, V8 does not do callbacks when script endswrapped C++ objects won’t be deleted, leaks resulting

> Need to track every C++ object a script creates and clean up afterwards :-(

Automatic JavaScript Wrappers for C++ Objects

// Sensor.h

//@ remote class Sensor: public Device { public: Poco::BasicEvent<const double> valueChanged;

virtual double value() const = 0; virtual bool ready() const = 0; };

Sensor.h

RemoteGen

lots of generated source files

RemoteGen.xml

Service<<generatedFrom>>

IService

ServiceProxyServiceRemoteObject

The interface class has all @remote methods from the service class.

ServiceSkeleton

<<invokes>>

<<generated>>

<<generated>><<generated>>

<<generated>>Service

ServerHelper

<<generated>>

Registers Skeleton, RemoteObject and EventDispatcher (if needed) with the Object

Request Broker.

ServiceProxyFactory

<<creates>>

<<generated>>Service

ClientHelper

<<generated>>

Registers ProxyFactory with the Object Request

Broker.

<<registers>><<registers>>

<<registers>>

ServiceEventSubscriber

<<generated>>Service

EventDispatcher

<<generated>>

<<registers>>

var tempSensor = ...;

tempSensor.on(‘valueChanged', function(ev) { var temp = ev.data; // ... });

if (tempSensor.ready()) { var temp = tempSensor.value(); // ... }

JavaScript Samples

// sensors.js (search sensors by physical quantity)

var sensors = {};

var illuminanceRefs = serviceRegistry.find( 'io.macchina.physicalQuantity == "illuminance"'); if (illuminanceRefs.length > 0) { sensors.illuminance = illuminanceRefs[0].instance(); }

var temperatureRefs = serviceRegistry.find( 'io.macchina.physicalQuantity == "temperature"'); if (temperatureRefs.length > 0) { sensors.temperature = temperatureRefs[0].instance(); }

var humidityRefs = serviceRegistry.find( 'io.macchina.physicalQuantity == "humidity"'); if (humidityRefs.length > 0) { sensors.humidity = humidityRefs[0].instance(); }

module.exports = sensors;

// sensors.js (search sensors by ID)

var sensors = {};

var illuminanceRef = serviceRegistry.findByName( 'io.macchina.xbee.sensor.illuminance#0013A20040A4D7F7'); if (illuminanceRef) { sensors.illuminance = illuminanceRef.instance(); }

var temperatureRef = serviceRegistry.findByName( 'io.macchina.xbee.sensor.temperature#0013A20040A4D7F7'); if (temperatureRef) { sensors.temperature = temperatureRef.instance(); }

var humidityRef = serviceRegistry.findByName( 'io.macchina.xbee.sensor.humidity#0013A20040A4D7F7'); if (humidityRef) { sensors.humidity = humidityRef.instance(); }

module.exports = sensors;

// database.js

var database = {};

database.path = bundle.persistentDirectory + "logger.db"; database.session = new DBSession('SQLite', database.path);

database.logIntervalSeconds = application.config.getInt( "datalogger.intervalSeconds", 30);

database.keepDataSeconds = application.config.getInt( "datalogger.keepDataSeconds", 3600);

module.exports = database;

// logger.js

var sensors = require('sensors.js'); var db = require('database.js');

db.session.execute('PRAGMA journal_mode=WAL'); db.session.execute('CREATE TABLE IF NOT EXISTS datalog ( \ timestamp INTEGER, \ illuminance FLOAT, \ temperature FLOAT, \ humidity FLOAT \ )');

setInterval( function() { db.session.execute('INSERT INTO datalog VALUES (?, ?, ?, ?)', DateTime().epoch, sensors.illuminance.value(), sensors.temperature.value(), sensors.humidity.value()); }, db.logIntervalSeconds*1000);

// logger.js (continued)

setInterval( function() { var cutoffTime = DateTime().epoch - db.keepDataSeconds; db.session.execute('DELETE FROM datalog WHERE timestamp < ?', cutoffTime); }, db.keepDataSeconds*1000);

// history.jss

var db = require(‘../database.js'); var validItems = ['temperature', 'humidity', 'illuminance'];

var data = [];

db.session.pageSize = form.maxItems ? parseInt(form.maxItems) : 20; var item = form.item;

if (validItems.indexOf(item) > -1) { var recordSet = db.session.execute( 'SELECT timestamp, ' + item + ' FROM datalog ORDER BY timestamp DESC');

// history.jss (continued)

for (var row = 0; row < recordSet.rowCount; row++) { var time = recordSet.getValue('timestamp'); var value = recordSet.getValue(item); var date = LocalDateTime('1970-01-01'); date.addSeconds(time); data[recordSet.rowCount - row - 1] = { timestamp: date.format('%H:%M:%S'), value: value }; recordSet.moveNext(); } recordSet.close(); }

response.contentType = 'application/json'; response.write(JSON.stringify(data)); response.send();

// MQTT to AirVantage

var sensors = require('sensors.js');

var mqttClientRefs = serviceRegistry.find( 'io.macchina.mqtt.serverURI == "tcp://na.airvantage.net:1883"'); if (mqttClientRefs.length > 0) { logger.information("MQTT Client found!"); var mqttClient = mqttClientRefs[0].instance();

setInterval(function() { var epoch = "" + 1000*DateTime().epoch; // seconds to milliseconds var payload = {}; payload[epoch] = { "sensors.temperature": sensors.temperature.value(), "sensors.humidity": sensors.humidity.value() "sensors.illuminance": sensors.illuminance.value() }; mqttClient.publish('JA347400060803/messages/json', JSON.stringify(payload), 0); }, 10000); }

// Send SMS via Twilio

function sendSMS(to, message) { var accountSID = application.config.getString("twilio.accountSID"); var authToken = application.config.getString("twilio.authToken"); var from = application.config.getString(“twilio.from");

var twilioHttpRequest = new HTTPRequest( "POST", "https://api.twilio.com/2010-04-01/Accounts/" + accountSID + "/SMS/Messages" ); twilioHttpRequest.authenticate(accountSID, authToken); twilioHttpRequest.contentType = "application/x-www-form-urlencoded"; twilioHttpRequest.content = "From=" + encodeURIComponent(from) + "&To=" + encodeURIComponent(to) + "&Body=" + encodeURIComponent(message); twilioHttpRequest.send(function(result) { logger.information("Twilio SMS Response: ", result.response.status, result.response.content); }); }

var sensors = require('sensors.js');

var enableSMS = true;

sensors.illuminance.on('valueChanged', function(ev) { logger.notice("valueChanged: " + ev.data); if (ev.data < 10) { logger.warning("Lights out!"); if (enableSMS) { sendSMS("+436765166737", "Lights out!"); enableSMS = false; } } else if (ev.data > 50) { enableSMS = true; } });

Q&A

[email protected] | @obiltschnig | obiltschnig.com macchina.io | my-devices.net | pocoproject.org | www.appinf.com