I spent a good portion of time researching, implementing and perfecting a home automation system based on open source software and open source and commercial hardware. I tried different things and threw out anything I wasn't entirely happy with. Being satisfied with the current setup, I decided to share it with this community giving you the opportunity to avoid the pitfalls that I have encountered.
Earlier attemptsBefore settling on the current technology stack, I used and tested all of the following and replaced them with something better: Siemens Logo!, Domoticz, Vera, FHEM, home-brew rs-485 sensor bus, home-brew can bus sensor bus, Arduino-Yun based sensor nodes, windows-10 IoT, .NET core (c#) for services programming, NETMF, GHI electronics TinyCLR modules and some other stuff I happily forgot about. This doesn't mean these are bad technologies, just that they didn't work well in my setup or that I found something easier, better or cheaper.
Overall architectureBelow is a (somewhat simplified) map of the architecture. Central in the setup is OpenHAB. It interfaces with the electric system (lights, shades, switchable wall outlets, ventilation, metering) through a set of networked Wago PLCs. Other subsystems are connected to OpenHAB, allowing information to flow between them.
From working with the Wago PLCs I found that they are rock-solid and good for automating simple things: press a button to switch on a light, press it again to switch it off again. They are less suited for doing network-intensive tasks and things like date/time calculations. This lead me to using a layered approach: basic automation that should never fail (lights, shades, ventilation,...) are programmed on the PLCs while the more complex things like GUI, networking, sensoring and date/time calculations are handled by openHAB. OpenHAB controls the PLCs over Modbus/TCP. If OpenHAB fails, the PLCs continue to execute the simple important tasks.
CostThe cost of the PLCs was about 16€ per input a few years back. To put this into perspective, wall-mount switches cost between 5€ and 15€ per piece depending on the setup and the finish of the cover (aluminium finish being 10x more expensive than plastic). Considering the simpler cabling (all straight to the cabinet), cheaper cable type (standard low-voltage 0.8mm² multi-wire cable) and the infinite flexibility (any switch can command any output), this setup is not more expensive than any other home automation system.
OpenHABOpenHAB is by far the best open source home automation software that I tested. The learning curve is steep, but after some climbing, the view is astonishing. At its core, openHAB is an integration tool. It defines a common parlance between different types of devices and applications. There are a zillion (OK, maybe a little less, but still quite al lot) "bindings" that connect devices to the internal event bus. There is even a Minecraft binding!
No programming experience is needed to use openHAB, everything is configured in textual configuration files or even directly in PaperUI as of verion 2. When you use Visual Studio Code to edit configuration files, the integrated LSP support helps getting the syntax right.
First thing to do is getting to know the Karaf debugging interface. You connect to it by SSH-ing to port 8101 on the OpenHAB machine. Below are some basic commands that I find to be using a lot. More can be found here.
- bundle:list -> list the installed bindings
- bundle:restart -> restart an add-on (restart/stop/start all work)
- log:tail -> see log and error messages scrolling over the screen
- smarthome:status <item> -> get the status of one item
- smarthome:update <item> <state> -> change the status of an item
- smarthome:things list -> get a list of all devices
- log:set DEBUG <binding> -> set verbosity of one binding
- smarthome:links clear -> clear up item configuration made through discovery process
I use fixed address persistent variables to store item state in the Wago programs. The advantage being that state is preserved during power cycles and program updates and that the state can be referred to from outside of the PLC over Modbus/tcp or HTTP. One area of concern is that the function blocks must be written in such a way that externally triggered state changes are dealt with correctly. Below is an example of a simple on/off light switch function block in IEC 61131-3 Structured Text that handles external state changes well.
FUNCTION_BLOCK SimpleRelais
(* declarations *)
VAR_INPUT
xSwitch2: BOOL;
xSwitch3: BOOL;
xGeneralLightsOut: BOOL := FALSE;
END_VAR
VAR_OUTPUT
xLight: BOOL := FALSE;
END_VAR
VAR
xSwitch: BOOL;
xPreviousSwitchState: BOOL := FALSE;
END_VAR
VAR RETAIN PERSISTENT
wState AT %M* : WORD := 0;
END_VAR
(* program code *)
xSwitch := xSwitch1 OR xSwitch2 OR xSwitch3;
CASE wState OF
0: (* Light is out *)
xLight := FALSE;
IF (NOT xPreviousSwitchState AND xSwitch) THEN
wState := 10;
END_IF
10: (* Light is burning *)
xLight := TRUE;
IF ( (NOT xPreviousSwitchState AND xSwitch)
OR (xGeneralLightsOut) ) THEN
wState := 0;
END_IF
END_CASE
xPreviousSwitchState := xSwitch;
The state variables must be attached to memory address locations in the global Variable_Configuration object of the Wago PLC like this:
POU_Verlichting.CLP_Badkamer.wState AT %MW1 : WORD;
POU_Verlichting.SpotsOverloop.wState AT %MW2 : WORD;
POU_Verlichting.CLP_Bureau.wState AT %MW3 : WORD;
POU_Verlichting.Spots_Zithoek.wState AT %MW4 : WORD;
OpenHAB communicates with the PLC over the modbus binding. There are other ways, but this one proves to be stable. The PLC address mapping is defined in the /etc/openhab2/services/modbus.cfg
file. You may need different definitions for the same PLC for different applications. Below is an example extract from my /etc/openhab2/services/modbus.cfg
file. While wago1a
and wago1b
refer to the same physical PLC, they serve different applications, 1a for switches, 1b for electricity counters. These point to different address areas in de PLC and use different value types.
tcp.wago1a.connection= 192.168.0.41:502
tcp.wago1a.type=holding
tcp.wago1a.start=12288
tcp.wago1a.length=10
tcp.wago1a.valuetype=int16
tcp.wago1b.connection= 192.168.0.41:502
tcp.wago1b.type=holding
tcp.wago1b.start=12348
tcp.wago1b.length=44
tcp.wago1b.valuetype=uint32_swap
This is an example of unidirectional item bindings (PLC to openHAB only) in /etc/openhab2/services/myItems.items
:
Switch vlg_slpk2 {modbus="<[wago2a:8]"}
Switch vlg_slpk3 {modbus="<[wago2a:9]"}
This is an example of bidirectional bindings:
Switch vlg_badkamer {modbus="<[wago2a:1], >[wago2a:1:transformation=MAP(simpleRelay.map)]"}
Switch vlg_overloop {modbus="<[wago2a:2], >[wago2a:2:transformation=MAP(simpleRelay.map)]"}
Switch vlg_bureau {modbus="<[wago2a:3], >[wago2a:3:transformation=MAP(simpleRelay.map)]"}
Switch vlg_zithoek {modbus="<[wago2a:4], >[wago2a:4:transformation=MAP(simpleRelay.map)]"}
The transformation is there because I use 10 for the 'ON' state in the Wago programs. Here is the definition of the transformation (to put in /etc/openhab2/transform/simpleRelay.map
):
ON=10
OFF=0
Energy MonitoringIt's great to have insight in your electricity and gas consumption. For the gas monitoring, a magnetic pulse counter (the black block in the picture below) works well. It hasn't missed one pulse since it was installed.
Eltako devices are quite affordable and pretty accurate for electricity (sub)metering.
The pulses are picked up and counted by the Wago PLCs, transferred to OpenHAB over modbus/TCP and persisted to an InfluxDB time-series nosql database by OpenHAB. Here's a log extract showing the pulse measurements arriving in OpenHAB.
__ _____ ____
____ ____ ___ ____ / / / / | / __ )
/ __ \/ __ \/ _ \/ __ \/ /_/ / /| | / __ |
/ /_/ / /_/ / __/ / / / __ / ___ |/ /_/ /
\____/ .___/\___/_/ /_/_/ /_/_/ |_/_____/
/_/ 2.2.0
Release Build
Hit '<tab>' for a list of available commands
and '[cmd] --help' for help on a specific command.
Hit '<ctrl-d>' or type 'system:shutdown' or 'logout' to shutdown openHAB.
openhab> log:tail
08:50:58.766 [INFO ] pc_elek_algemeen changed from 2359981 to 2359982
08:50:59.886 [INFO ] pc_verlichting changed from 396435 to 396436
08:51:02.113 [INFO ] pc_diepvriezer changed from 2281542 to 2281543
08:51:05.296 [INFO ] pc_TV changed from 3047546 to 3047547
I use Grafana to visualise the measurements in the Influx database.
Here is a good tutorial that explains how to set up influxdb and grafana on openHAB. Just follow the steps described and you're ready to go.
MySensorsI got to know the MySensors framework from my Internet of Poultry project. MySensors allows to build wireless sensors/actuators with very cheap components without having to worry about the difficult RF part of the wireless game. If you know how to program Arduino, you can build your own sensors. The MySensors community (and Nordic) did the difficult work for you.
Here is an example of a home-brew sensor. The sensor itself costs about 15€, the 3D printed housing pretty much the same. The 2€ coin is there for you to appreciate the size.
This sensor lasts about a year on one CR2032 coin cell battery sending temperature and humidity measurements every 20 seconds. The range of the NRF24L01+ radio's isn't great, but the MySensors framework supports repeater nodes. Repeater nodes have to be mains-powered.
I use MySensors mainly to measure temperatures and flow rates of the central heating and ventilation systems.
Here's an extract of my MySensors.things configuration files.
Bridge mysensors:bridge-ser:gateway [ serialPort="/dev/ttyMySensors", sendDelay=200 ]
{
humidity keuken_vochtigheid [ nodeId="3", childId="2" ]
temperature keuken_temperatuur [ nodeId="3", childId="1" ]
temperature collector_garage_depart [ nodeId="2", childId="0" ]
temperature collector_garage_retour [ nodeId="2", childId="10" ]
temperature collector_retour_schuifdeur [ nodeId="2", childId="1" ]
}
And the associated items in the.items configuration file.
/* MySensors items */
Number keuken_vocht { channel="mysensors:humidity:gateway:keuken_vochtigheid:hum"}
Number keuken_temp { channel="mysensors:temperature:gateway:keuken_temperatuur:temp"}
The measurements in the below graph are collected with MySensors. It shows radiator flow temperatures and return temperatures.
The RFXtrx433E is a gateway between openHAB (or other home automation controlers like domoticz and homeseer) and a large number of off-the-shelf 433MHz devices (sensors and actuators). If you have 433MHz devices at home, there's a good change that the RFXcom gateway can decode their signals. The 433Mhz devices typically have a good range and long battery life. Some are really cheap, others (like Netatmo or Oregon Scientific) rather expensive. The gateway also supports Somfy devices.
Here's a picture of a cheap weather station that RFXcom is decoding.
A nice use case for RFXcom are retrofit wall switches. During home construction it's difficult to image how the furniture will be organised in all of the rooms once you live in the house. It turns out we got it mostly wrong in the bedrooms.
KlikAanKlikUit comes to the rescue. Among others, klikaanklikuit has cheap wireless wall switches. You can stick them then anywhere on a wall or even on furniture. OpenHAB picks up the signal though the RFXcom gateway and commands the Wago PLC to take appropriate action. Below are extracts from the relevant configuration files.
RFXcom things file
Bridge rfxcom [ serialPort="/dev/ttyRFXcom", setMode="0D00000303531C02002400000000" ]
{
rain regen [ deviceId="16385", subType="RAIN7"]
wind wind [ deviceId="26113", subType="WIND7"]
lighting2 wirelessSwitch [ deviceId="14830242.10", subType="LIGHTING2"]
}
items file
Switch
Number wind_speed
Number wind_direction {channel="rfxcom:wind:windDirection", expire="2m"}
Number rain
This is an example of a rule that links a wireless switch to the corresponding output on the PLC:
rule "Link Draadloze schakelaar 1 naar licht slaapkamer 4"
when
Item vlg_wirelessSwitch changed
then
vlg_slpk4.sendCommand(vlg_wirelessSwitch.state.toString())
end
I have one wireless switch attached directly onto the head end of a bed. Not pretty, but very effective. :-)
External scriptingI'm not a big fan of the rules and scripting engines in OpenHAB mainly because they're Java-based and Java is not my cup of tea (it's name after coffee anyway). Fortunately, OpenHAB provides an escape route. The internal event bus can be opened up to MQTT. Here is an example of how to configure this.
mqtt.cfg
mosquitto.url=tcp://localhost:1883
mosquitto.clientId=openhab
mosquitto.retain=true
mosquitto.async=false
mqtt-eventbus.cfg
broker=mosquitto
statePublishTopic=openHAB/out/${item}/state
stateSubscribeTopic=openHAB/in/${item}/state
commandSubscribeTopic=openHAB/in/${item}/command
External scripting example: reducing phantom powerThere are some quick-wins when it comes to phantom power. For instance switching off the Internet modem and associated devices at night (the ones that are typically installed in a corner of the garage) . In my case these devices are good for 30W continuous power consumption, that is 262kWh per year. Reducing this with 25% (6h out of 24h) is good for the planet and - to a lesser extent - for the wallet (about 16€ per year).
But there's a better use case for many homes in Belgium. Our dominant telecom provider forces us to use a set-top box that consume no less than 12W when switched off and an impressive 22W when switched on. From studying power consumption graphs I found out that more often than not the set-top box remains switched on when the television is turned off.
Here's the solution.
I installed a relay on the circuit of the wall plug in which the television and set top box are plugged in (luckily, there is a cable going directly from the cabinet to that plug). An alternative solution would be to use a 433MHz wireless relay and command it through the RFXcom gateway.
The relay is controlled by the Wago PLC, the wall switch closest to the TV set switches the relay on. The below Node.js script will switch the relay off whenever the consumption on the wall power outlet is between 10W and 30W (which indicates that the TV set is off) for longer than 15 minutes.
// Script to turn off the wall plug of the TV + digicorder
'use strict';
const moment = require('moment');
const mqtt = require('mqtt');
const schedule = require('node-schedule');
const lowerLimit = 10.0;
const upplimit = 30.0;
const timeFormat = 'HH:mm:ss';
const switchOffDelay = moment.duration(15, 'minutes');
const mqttClient = mqtt.connect('mqtt://home');
var plugOnTvOffSince = undefined;
var currentConsumption = 0.0;
mqttClient.on('connect', function () {
mqttClient.subscribe('openHAB/out/cc_TV/state');
mqttClient.subscribe('openHAB/out/stopc_TV/state');
});
mqttClient.on('message', (topic, message) => {
if (topic === 'openHAB/out/cc_TV/state') {
setCurrentConsumption(parseInt(message.toString()) / 10.0);
}
if (topic === 'openHAB/out/stopc_TV/state' && message.toString() === 'ON') {
plugOnTvOffSince = undefined;
}
});
function setCurrentConsumption(current)
{
currentConsumption = current;
if (plugOnTvOffSince === undefined && currentConsumption > lowerLimit && currentConsumption < upplimit)
plugOnTvOffSince = moment();
if (currentConsumption <= lowerLimit || currentConsumption >= upplimit)
plugOnTvOffSince = undefined;
}
// Run this function every minute
schedule.scheduleJob('* * * * *', function () {
if (plugOnTvOffSince !== undefined) {
var plugOnTvOffDuration = moment.duration(moment().diff(plugOnTvOffSince));
if (plugOnTvOffDuration.asSeconds() > switchOffDelay.asSeconds()) {
mqttClient.publish('openHAB/in/stopc_TV/command', 'OFF');
setCurrentConsumption(0.0);
}
}
});
Assuming this script saves between 12W and 22W during more than 20h every day, this amounts up to 130kWh per year. If every Telenet customer were to run this script, the environmental saving would be 59.000 tons of CO2 per year (assuming the usage conditions are the same for every customer and convenienty ignoring the power consumption of the computer that runs OpenHAB - it can run on a Raspberry Pi consuming 1,5W).
Advantages of external scriptingRunning scripts external to OpenHAB with state exchange over MQTT has some advantages.
First, the script can be run from any computer. This is convenient during coding and debugging. If you notice a problem in a script, just halt it on the server, transfer it to your development machine and run/debug it in your favorite IDE.
Second, you can see the information being exchanged by using any MQTT client tool and you can intervene by sending MQTT commands. I use MQTT-Spy. Below is a screen dump of MQTT-spy during debugging of the above script.
Third, the externalization of the event bus doesn't stand in the way. If you prefer to run a function on the PLC or with an openHAB rule or script, you can. The state changes will still be visible in the MQTT client.
Forth, you can write the external script in your preferred language as long as there is an MQTT library available for it.
If you use Node.js for scripting like I do, you may want to check out PM2. PM2 is an excellent service manager for node.js. It allows you to 'demonize' scripts, get status and performance information and view logs. It will restart your script automatically when it fails. With process metrics, you can have an email sent when one of your scripts fails.
GUIsNo IT system is complete without a GUI. OpenHAB has different GUIs to chose from. I have settled with the simplest one, BasicUI for the time being, but HABPanel is definitely on my TODO list. BasicUI is easy to configure by defining a sitemap. The documentation for building sitemaps is here. The BasicUI can be used both with an app on a phone or tablet as well as in a browser. Here are some screenshots
I leave it to this for now. Maybe I'll come back later to fill in some gaps. I hope this text has inspired you. If you have questions or remarks, please don't hesitate to drop me a note.
Cheers!
Comments