Monday, 3 April 2017

HOME AUTOMATION: ELK/NESS M1 Home Alarm Controller with OpenHAB over Serial

When I installed the NESS M1 home automation controller and home alarm I had plans of integrating it in to my overall setup. I purchased an M1XEP with plans of exposing the alarm control to the network but ran in to setup issues that I was never able to resolve.

When I installed my openHAB controller I looked for a binding for the NESS/ELK but was not able to find one. There are some active conversations on the forums but no available bindings, so I decided to try my hand at getting something going. I'm using a USB to Serial adaptor plugged from the serial port of my home alarm directly in to my Raspberry Pi running openHAB2.

OpenHAB Serial Binding

First thing to do is to install and configure the serial binding. I have already covered this in an older post.

Reading Zone States

The easiest thing to start with is to read the zone state from the NESS alarm controller. With serial configured this is simply a matter of reading the serial input data and interpreting the commands based on the well documented serial protocol for the controller.

Here are the files I have used to mirror the state of the motion sensors on my home alarm by reading the zone states:

 Switch AlarmUpdate  "Alarm Update"  { serial="/dev/ttyUSB0@115200" }  
 String AlarmMessage "Alarm Message" { serial="/dev/ttyUSB0@115200" }  
 Group gAlarm_PIR
 Contact Alarm_PIR_Zone1                                                (gAlarm_PIR)
 Contact Alarm_PIR_Zone2                                                (gAlarm_PIR)
 Contact Alarm_PIR_FF_MasterBedroom "Master Bedroom Motion Sensor [%s]" (gAlarm_PIR)
 Contact Alarm_PIR_Zone4                                                (gAlarm_PIR)
 Contact Alarm_PIR_Nursery "Nursery Motion Sensor [%s]"                 (gAlarm_PIR)
 Contact Alarm_PIR_FF_Stairs "Stairs Motion Sensor [%s]"                (gAlarm_PIR)
 Contact Alarm_PIR_GF_Lounge "Lounge Motion Sensor [%s]"                (gAlarm_PIR)
 Contact Alarm_PIR_GF_Stairs "Basement Stairs Motion Sensor [%s]"       (gAlarm_PIR)

 rule "Init"  
     System started  
     // let openHAB settle and give other rules the chance to fire  
     createTimer(now.plusSeconds(180)) [|  
         gAlarm_PIR?.members.forEach[g |  

 import java.lang.Integer.*  
 val AlarmMemoryMessage = "AM"  
 val ZoneChangedMessage = "ZC"  
 val NetworkAdapterPing = "XK"  
 val ZoneStatusTable_NormalUnconfigured = "0"  
 val ZoneStatusTable_NormalOpen = "1"  
 val ZoneStatusTable_NormalEOL = "2"  
 val ZoneStatusTable_NormalShort = "3"  
 val ZoneStatusTable_TroubleOpen = "5"  
 val ZoneStatusTable_TroubleEOL = "6"  
 val ZoneStatusTable_TroubleShort = "7"  
 val ZoneStatusTable_ViolatedOpen = "9"  
 val ZoneStatusTable_ViolatedEOL = "A"  
 val ZoneStatusTable_ViolatedShort = "B"  
 val ZoneStatusTable_BypassedOpen = "D"  
 val ZoneStatusTable_BypassedEOL = "E"  
 val ZoneStatusTable_BypassedShort = "F"  
 rule "Interpret Alarm Command"  
     Item AlarmMessage received update  
     var lines = AlarmMessage.state.toString.split('\n')  
     lines.forEach[line |  
         if(line.length > 6) {  
             var messageLength = line.substring(0,2)  
             var commandType = line.substring(2,4)  
             var checksum = line.substring(line.length-3, line.length-1)  
             //logInfo("alarm", "Message:" + line + " Len:" + messageLength + " Command:" + commandType)  
             if(commandType == AlarmMemoryMessage) {  
                 var alarmMemoryAreas = line.substring(4, 12)  
             else if(commandType == ZoneChangedMessage) {  
                 else if(commandType == ZoneChangedMessage) {  
                 var int zoneNumber = Integer::parseInt(line.substring(4,7))  
                 var zoneStatus = line.substring(7,8)  
                 //logInfo("alarm", "Zone:" + zoneNumber + " Status:" + zoneStatus)  
                 //logInfo("alarm", "gAlarm_PIR:" + gAlarm_PIR.members.filter(g | == "Alarm_PIR_GF_Lounge").toString)  
                 var zone = Alarm_PIR_GF_Lounge  
                 var state = CLOSED  
                 // case statement setting the zone item from the message received  
                 switch zoneNumber {  
                     case zoneNumber == 1 : zone = Alarm_PIR_Zone1  
                     case zoneNumber == 2 : zone = Alarm_PIR_Zone2  
                     case zoneNumber == 3 : zone = Alarm_PIR_FF_MasterBedroom  
                     case zoneNumber == 4 : zone = Alarm_PIR_Zone4  
                     case zoneNumber == 5 : zone = Alarm_PIR_FF_Nursery  
                     case zoneNumber == 6 : zone = Alarm_PIR_FF_Stairs  
                     case zoneNumber == 7 : zone = Alarm_PIR_GF_Lounge  
                     case zoneNumber == 8 : zone = Alarm_PIR_GF_Stairs  
                 // set the state of the zone from the message received  
                 if(zoneStatus == ZoneStatusTable_NormalUnconfigured) {  
                     state = CLOSED  
                 else if(zoneStatus == ZoneStatusTable_NormalOpen) {  
                     state = CLOSED  
                 else if(zoneStatus == ZoneStatusTable_NormalEOL) {  
                     state = CLOSED  
                 else if(zoneStatus == ZoneStatusTable_TroubleOpen) {  
                     state = OPEN  
                 else if(zoneStatus == ZoneStatusTable_TroubleEOL) {  
                     state = OPEN  
                 else if(zoneStatus == ZoneStatusTable_TroubleShort) {  
                     state = OPEN  
                 else if(zoneStatus == ZoneStatusTable_ViolatedOpen) {\  
                     state = OPEN  
                 else if(zoneStatus == ZoneStatusTable_ViolatedEOL) {  
                     state = OPEN  
                 else if(zoneStatus == ZoneStatusTable_ViolatedShort) {  
                     state = OPEN  
                 else {  
                     state = CLOSED  
                 postUpdate(zone, state)  
             else if(commandType == NetworkAdapterPing) {  
             else {  
                 //logInfo("alarm", "No code match")  

Add the following lines to your sitemap:
 Frame label="Ground Floor Stairs" {  
   Text item=Alarm_PIR_GF_Stairs icon="motion"  


View the sitemap and we should see the motion sensor updates coming through

Test the sitemap is working as expected with the command:
 tail -f /var/log/openhab2/openhab.log -f /var/log/openhab2/events.log tail -f /var/log/openhab2/openhab.log -f /var/log/openhab2/events.log  

If everything is working as expected you should see updates coming through:
 ==> /var/log/openhab2/events.log <==  
 2017-04-03 13:44:24.818 [GroupItemStateChangedEvent] - gAlarm_PIR changed from CLOSED to UNDEF through Alarm_PIR_GF_Lounge  
 2017-04-03 13:44:28.191 [ItemStateChangedEvent   ] - AlarmMessage changed from 0AZC007900C2  
  to 0AZC007200C9  
 2017-04-03 13:46:26.579 [ItemStateChangedEvent   ] - Alarm_PIR_GF_Lounge changed from OPEN to CLOSED  


There is a bug in openHAB2 that stops icon updates on the sitemap as the states are updated. It's documented here. The solution is to restart the OpenHAB server.


  1. This looks really good. Do you think it will work with the NESS DX8Deluxe panel?

    1. Not sure, does the DX8 have a serial communication port?

      I've only worked with the M1 which is a home automation controller so it has these features built in by default