Upload
guenter-obiltschnig
View
274
Download
4
Tags:
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
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 :-(
// Sensor.h
//@ remote class Sensor: public Device { public: Poco::BasicEvent<const double> valueChanged;
virtual double value() const = 0; virtual bool ready() const = 0; };
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(); // ... }
// 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; } });
[email protected] | @obiltschnig | obiltschnig.com macchina.io | my-devices.net | pocoproject.org | www.appinf.com