Close

Plug-ins

A project log for HomePortal

Home Automation software hub that unifies home sensors and cloud services with a flexible and easy to use browser interface

deandobdeandob 08/07/2016 at 21:590 Comments

Plug-ins are the glue that binds external devices and services together, monitors and controls them and converts their specific protocols to a common message format. Integration is a key aspect of a home automation system and the plug-in manager makes it all happen.

Design

A modular plugin design is critical for separating the concerns of managing the integration of the device / service with the general services of the automation engine. It also means that we can easily add and maintain the semantics of the device / service we are connecting to.

Converting the format of messages the external device / service uses to a common message format

<NETWORK> / <CATEGORY> / <CLASS> / <INSTANCE> / <SCOPE> / <DATA>

'normalises' the message so that it can be manipulated and integrated with other devices / services. See the architecture log for more description of the message format.

Plugins are isolated snippets of code the perform the following functions:

Plugins can be written in .NET or JavaScript and are stored in directories according to the plug-in category (eg. Lighting). The plug-in Manager has the task of loading and validating the plug-in, calling any initialisation tasks the plug-in may need and providing the mechanism to communicate between the plug-in and the automation engine via common functions.

Each plug-in has an associated settings file in the INI file format (as it is easy to read & edit) that describes the following:

The settings file avoids the need to hard code parameters like IP addresses so that configuration changes can be made on the fly without restarting. Plug-in settings file can be edited within the automation console.

Plug-ins are not just about devices

Just as importantly as being able to integrate with the hardware devices around the home, plug-ins are also used to connect and consume Cloud services. The weather dashboard outlined in another log is a good example of mixing local weather data from the home weather station with weather information from the Cloud. The combination of the local device data along with complementary Cloud data make for a more engaging and useful user interface.

Another example of integrating Cloud services is being able to pull in stock data from a source like Yahoo Finance. A plug-in connecting to the Cloud works the same way as connecting to a local device, but this time the inbuilt Node.JS HTTP functionality is used to connect. Once a stock feed is running, many things are possible like alerts if the price exceeds a certain range (eg. generate a SMS from a trigger), history graphs or a stock ticker widget.

Why Node.JS is well suited for Home Automation

One of the main disadvantages of writing my own automation system is that I don't get to leverage a plug-in library from existing automation packages like HomeSeer and OpenHAB. However this hasn't been a problem in practice because of the power and ecosystem of Node.JS.

Node.JS as the platform to host plug-ins has made a huge difference to the plug-in flexibility and enables quick development of even complex device / service integration. The ease of writing in JavaScript & its asynchronous nature along with the almost infinite number of NPM packages available (think libraries for those who know C) is what helps. It means custom plug-in code just concentrates on the device / service integration and the NPM imported packages and Node services handles communication and lower level services like timers reducing significantly the amount of custom coding required.

Most devices use a simple protocol to control them (eg. REST commands) and a lot of the time you are dealing with sensors (ingesting their data) rather than actuators (controlling the device) so you end up with typical plug-in code reading input and reformatting to the standard message format - quite simple and doesn't take long to write. To connect to new devices I start with a template that includes an API to interface with the automation engine.

The Javascript / Node.JS ecosystem is the most active and fastest growing in the developer world today and it gives this automation solution a huge productivity boost - I wonder why other automation packages aren't using it. As an example, say you have installed a new Internet router and you want to be able to monitor line noise and reboot automatically if line speed drops below a threshold. However this router uses SNMP to communicate so how do you do that? Pull in a SNMP module from NPM with a few clicks and your plugin is automatically SNMP enabled and a couple more lines of code to connect and read status by sending an appropriate OID (SNMP message format) to the router, receiving the results back to be sent onto the automation engine. A trigger is configured (in the automation engine) to monitor the router line speed and sends a message to the plug-in to reboot the router with a SNMP message if line speed is slow. Easy with JavaScript, Node and NPM modules!

Plug-in Example

Below is an example of a typical plug-in, it manages the connection to the house solar inverter and monitors the solar power generated in real-time via a serial connection. The serialport NPM module is used to handle communication from / to the serial port. For those who know JavaScript the code below isn't very complicated and didn't take long to write.

The startup function is called when the plug-in is loaded and it initializes the serial port using settings in the settings file. The 'fw' functions are part of the API to integrate the plug-in with the automation engine (eg. access settings, send to the host).

The setInterval function runs at regular intervals (as defined in the settings file) and polls the solar inverter via a poll message sent through the serial port.

The serialRecv function receives the data back from the inverter, does some basic message formatting, and if the value has changed it is sent to the host (via the fw.toHost API function),

"use strict";
 
// Read power generated by solar panel inverter
var com = require("serialport");
var serialSolar;
var CR = "\r";
var oldData = -99;
 
// startup function
function startup() {
    var startStatus = "OK"
    serialSolar = new com.SerialPort(fw.settings.comport, {
            baudrate: +fw.settings.baudrate,
            databits: +fw.settings.databits,
            stopbits: +fw.settings.stopbits,
            parity: fw.settings.parity,
            buffersize: 255
        }, function (err) {
        if (err) fw.log(err + ". Cannot open solar serial port, no solar generation functionality available.")
    });
 
    serialSolar.on("open",function() {
        fw.log("Serial port open on " + fw.settings.comport);
    });
        
    serialSolar.on("data", function(data) {
        serialRecv(data);
    });
    
    serialSolar.on("error", function(err) {
        fw.log("Serial port general error " + err);
        fw.restart(99);
    });
 
    setInterval(pollInv, +fw.settings.pollinterval * 1000, fw.channel0.attrib[0].value);
        
    return startStatus;
}
 
function pollInv(cmd) {
    serialSolar.write(new Buffer(cmd + fw.settings.cmdchar + CR), function (err) {
        if (err) {
            fw.log("Serial write error: " + err);
            fw.restart(99);
        }
    })
}
 
function serialRecv(data) {
        if (data.length > 0) {
            var generated = parseInt(data.toString().split(CR)[0]);
            if (generated < fw.settings.changetol) generated = 0;                        // ignore any spurious watts generated at night
            if (Math.abs(generated - oldData) >= fw.settings.changetol) {
                fw.toHost("Power Out", "W", generated);
                oldData = generated;
            }
    }
}        
 
// Shutdown the plugin
exports.shutPlugin = function shutPlugin(param) {
    //Insert any orderly shutdown code needed here
    return "OK";
}


Plug-in settings file

The plug-in INI file describes the attributes and settings for the plugin. In the solar inverter example, the file keeps the serial port settings, poll interval, protocol command strings and sets up a message channel called Power Out that sends power data messages as integers between the range of 0 and 10,000 with watts as units. Each channel can have an array of additional attributes as required (here it describes the command to retrieve total power generated).

[PluginCfg]
Desc = Read power generated by solar panel inverter
Enabled = true

[General]
ComPort = COM3
BaudRate = 9600
StopBits = 1
DataBits = 8
Parity = none
pollInterval = 6
CmdChar = ?

; Dont broadcast any change less than
ChangeTol = 10

; Define the channel characteristics
[channel0]
Name = Power Out
Desc = Current power generation
Type = integer
IO = output
Min = 0
Max = 5000
Units = watts
[channel0.attrib0]
Name = Power Cmd
Type = Command
Value = PIN

Discussions