Integrating Telenot complex 400 alarm system

I am going to describe an integration of a Telenot complex 400 alarm system into openHAB. The system is usually used as a standalone alarm system that monitors a number of contacts (usually windows and doors) and raises hell if one of those gets opened while the system is armed. It can also use motion sensors, glass break sensors and other things all the way up to a professional security solution in higher risk environments.
The reason why it is interesting to integrate into our smarthome system, is because itā€™s useful to track if your windows are open or closed, i.e. if you want your automatic sun blinds not to close down behind your back while you are relaxing on your terace, or to turn off the heating or A/C if someone left a window open.
Telenot offers a building management system that communicates with the alarm system via a binary protocol (called GMS protocol in Telenotā€™s german documents). The alarm system will send the state of its alarm contacts every few seconds in both armed and disarmed states. Weā€™lll use this protocol to talk the alarm system into sending those states to us and start using them in openHAB.
A word of caution: The Building management protocol is a two-way street. Not only can you get info from your alarm system, that same protocol can be used to send commands to the alarm system too! A black hatter might use this to tell your system to disarm itself using the cimmunications infrastructure that we have conveniently built for him. While we will not use those functions in our integration, someone that knows the right binary data to send can disable the system without setting foot into your home and entering the security code or otherwise authenticate himself to the system. Especially if you have a clause in your theft protection policy, that you are only covered if the alarm system is turned on, I can only strongly discourage you from enabling the building management protocol on your alarm system.
You have been warned! Take care that you protect your home installation, and make sure that you have locked down external access properly. At the very least, Iā€™d recommend using a proper firewall and VPN server rather than just drilling holes into your NAT and exposing ports.
Assuming that you have adressed those issues successfully, let me explain the setup, shown in the picture below.
You will need:

  • Telenot complex 400 (the GMS protocol will most likely be available on other Telenot units as well, your mileage may vary)
  • RS232 cable
  • Moxa Nport 5110 RS232 <-> IP converter (newer units will probably work too)
  • Host with MQTT broker installed (I use Mosquitto). Python and the PAHO MQTT library for Python installed
  • OpenHAB with MQTT binding enabled and configured

We have the Telenot Complex 400 unit, that monitors the contacts that we are interested in. Thereā€™s an RS232 port on the system mainboard that can be enabled for the GMS protocol by your system integrator. Any ā€œnormalā€ system will have this turned off, so you will have to approach your system integrator to activate it for you. It might be possible to hook this serial cable directly into your openhab host, but thatā€™s not what weā€™ll do (mine simply was too far awayā€¦, and I guess you might have a hard time connecting a RS232 cable to a RasPi anyhowā€¦).
Instead, weā€™ll use a Moxa NPort 5110 serial to UDP converter that takes the signals from itā€™s serial port, and packs them into nicelyformatted UDP datagrams and sends them to our openHAB host (or any host, I just chose to have my UDP packets received by the openHAB host). Also, we will configure the NPort 5110 to send UDP datagrams that we send to it onward via itā€™s RS232 port to the Telenot unit (the GMS protocol is bidirectional, you wonā€™t get data from your complex 400, if you donā€™t send ACKs to its packets). This guy here is the main security risk, since you effectively cannot control, who sends data to the Nport that gets passed on to the complex 400. You can configure some host limitations to limit who is allowed to send data, but since youā€™re not expecting any return data as a black hatter, IP spoofing will probably work. So once again - make doubly sure that your home IP environment is clean and protected. Consider using a physically separate ethernet segment between your openHAB host and the NPort if possible.
We will later touch on a small python script that attaches to a UDP socket to receive the data from the NPort 5110. It will take the data off the wire, convert the binary data into a hex stream, and publish that to the MQTT Topic ā€œC400/InputStringā€.
The script will also subscribe to the MQTT Topic ā€œC400/OutputStringā€ and send the hex stream it gets on that topic to the Nport where it will get passed on to the Complex 400 unit.
On OpenHAB, we will also use the MQTT binding to subscribe to the C400/InputString topic. This is our input data that we will need to decode. Weā€™ll use the C400/OutputString topic to publish our ACK messages and propagate them back to the C400.
Weā€™ll go into the details of how to set up the different components next (bear with me, it takes some time to write it all up, so watch for the posts to update/add over the coming days)

1 Like

NPort 5110 setup
As hinted, you need to setup up the Nport 5110 to transfer data to and from the C400 using the RS232 Port and send the data onwards via UDP packets.
Configure the RS232 Port to: 9600/8N1, Fifo yes, Flow Ctrl none. On the IP side, it needs ā€œUDP Modeā€ Operation mode, packing length 110, no delimiters (delimiter 1and 2 empty, delimiter process ā€œdo nothingā€, Force transmit every 60 msecs. This will collect data, and transmit every 60 msecs if there is no new data arriving on the RS232 port.

The most important settings are the Destination IP Address and Port. Put in the IP address of the machine that will receive the data. In my example, I will use Port 4115 as the port where that machine will listen to data.
Configure ā€˜4116ā€™ and the Local Listen Port. This will cause the NPort to send all UPD data it receives on this port via the RS232 interface to the C400.

If you have Wireshark or tcpdump on your target system go ahead and fire it up to see what data arrives at port 4115. If itā€™s setup correctly you should see an endless stream of 8 byte UDP packets coming in all carrying the Hex Data ā€œ68 02 02 68 40 02 42 16ā€. This is the C400 sending you its ā€œACKā€ message. It expects a very specific ā€œConfirm_ACKā€ message back before it will send data.

c400.py script to read the data
Now, it would be great if we could just feed the data straight into the UDP/TCP binding and work with it, but the binding is limited to pure ASCII streams, and it wonā€™t work with binary data. The workaround that I am using is that I wrote a small python script that launches at boot time, listens to the UDP socket and publishes the data as MQTT messages. For the return direction, it will subscribe to a specific MQTT topic and forward the data to the NPort via a second UDP socket. I have opted to simply convert the data stream into its Hex representation and send it via String items into the MQTT binding. So, i our example above, the data would get sent as a string ā€œ6802026840024216ā€. The return direction has the same format convention. This is easily done with the ā€œhexlify/unhexlifyā€ functions of python.

Hereā€™s the script:

#!/usr/bin/python
#-*- coding: utf-8 -*-

import paho.mqtt.client as mqtt
import socket
import struct
import binascii

def on_connect(mqttc, userdata, flags, rc):
    print('MQTT connected...rc=' + str(rc))

def on_disconnect(mqttc, userdata, rc):
    print('MQTT disconnected...rc=' + str(rc))

def on_message(mqttc, userdata, msg):
    b2 = binascii.unhexlify(msg.payload)
    send.sendto(b2, (SEND_HOST, SEND_PORT))

def on_subscribe(mqttc, userdata, mid, granted_qos):
    print('subscribed (qos=' + str(granted_qos) + ')')

#Listen - listen on localhost, Port 4115
LISTEN_HOST = ''
LISTEN_PORT =  4115

#Send - send to the Nport 5110 to Port 4116
SEND_HOST = 'IP_of_NPort5110'
SEND_PORT = 4116

listen = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
send = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
print 'Socket created'

try:
    listen.bind((LISTEN_HOST, LISTEN_PORT))
except socket.error as msg:
    listen.close()
    print 'Binding to socket failed. Error code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()


mqttc = mqtt.Client()
mqttc.on_connect = on_connect
mqttc.on_disconnect = on_disconnect
mqttc.on_message = on_message
mqttc.connect(host='localhost', port=1883)
mqttc.subscribe(topic='C400/OutputString', qos=0)

mqttc.loop_start()
while True:
   
   data, addr = listen.recvfrom(4096)
    
   b = bytearray(data)
   inputstring  = binascii.hexlify(b)

   mqttc.publish(topic='C400/InputString', payload=inputstring, qos=0) 

As you can see, we need to do a few things to make it work:

  • Install Mosquitto or any other MQTT broker, Python(+pip), PAHO MQTT library for python
  • For the sake of briefness, just use the default installs without any config changes. There are multiple guides to install those packages, so iā€™ll just skip over this part.
  • In any durable installation, you should definitely secure the connection to the MQTT broker with encryption and user ACLs.
  • This script assumes (see mqttc.connect) that the MQTT broker resides on the same host and exposes port 1883 (default)
  • It outputs the data it receives from the NPort on Port 4115 as ā€œC400/InputStringā€ and subscribes to the ā€œC400/OutputStringā€. Everything arriving there gets sent to the Nport
  • I run this script as a system service and have set up a very basic systemd startup script to do so. Hereā€™s what you do
# Put this in /etc/systemd/system/ as c400.service
[Unit]
After=openhab2.service

[Service]
ExecStart=/usr/local/bin/c400.py

[Install]
WantedBy=default.target

Adjust the path to our python script as needed.
Adjust some permissions, and install the service:

chmod 744 /usr/local/bin/c400.py
chmod 664 /etc/systemd/system/c400.service
systemctl daemon-reload
systemctl enable c400.service

Go ahead and run it:

systemctl start c400.service

If you want to check if this worked, just look at the MQTT messages being passed by the broker:

mosquitto_sub -h localhost -p 1883 -v -t '#'

C400/InputString 6802026840024216
C400/OutputString 6802026800020216
C400/InputString 682e2e687302222400000001fffffffffffffffffffffffffffffffffffffffffffffbffffffffffffff0656999999ffffffbe16
C400/OutputString 6802026800020216
C400/InputString 6802026840024216
C400/OutputString 6802026800020216
C400/InputString 683e3e68730232240005000200fbfeffffff9e9e9e9e9e9e9e9effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0656999999ffffffbc16
C400/OutputString 6802026800020216

You can see the messages flowing. This is already with some processing functionality enabled in the OpenHAB host. What you would see is mostly identical to the Wireshark dump, just lots of ā€œACKā€ requests showing up on the C400/InputString Topic.

Letā€™s look at this message flow for a second, since it will help to understand the communication of the GMS protocol. By the way, if you ask your systems integrator to enable the GMS protocol, ask him for a GMS manual as well. They have it available to them, and the exact messages are highly specific to the exact hardware configuration of your C400, so the messages will most likely look different. Itā€™s easiest to figure out those messages with the manual in hand, believe me. GMS was ā€œGebaeudemanagement Systemā€ in german, so in english this might be a ā€œBuilding Management Systemā€ protocol (BMS?). I donā€™t have the english documents available to me, go figureā€¦

GMS protocol

  1. The protocol starts with the C400 sending you an ā€œACKā€ (6802026840024216) request.
  2. You need to respond with a ā€œConfirm_ACKā€ (6802026800020216) message.
  3. After this, the C400 will send its first ā€œrealā€ data packet (the one starting with 682e2eā€¦).
    In this packet, it will encode the status of all the conventional alarm contacts available in the system plus a few internal alarm contacts.
    A ā€œconventionalā€ alarm contact is a classic 4 wire loop with tamper lines and contact lines that connect to a sensor in your window/door. It can report a resting state (usually bit state ā€œ1ā€) and a signal state (ā€œ0ā€). My system has 32 conventional contacts and eight internal contacts (system housing intrusion detection, and so on). All of this is encoded in five bytes in the ā€œ2e2eā€ message.
  4. Respond with another ā€œConfirm_ACKā€
  5. C400 sends ā€œACKā€ again
  6. Respond with another ā€œConfirm_ACKā€
  7. Next, we get another data dump (starting with 683e3eā€¦)
    These are all the (Telenot) bus based contacts (i.e. motion sensors). I donā€™t have any on my system, so I didnā€™t write code to decode them, but with the GMS manual, this should be an easy task. The other interesting data piece in this record is a number of System Status bits, that will tell you if the system is ready to be armed, armed, disarmed or actually sounding the alarm already.
  8. Once you send a Confirm_ACK, the whole communication starts over again, and the C400 will go back to #1 and send an ACK and so on. This means that you will get an update on every contact in your system every three seconds. Assuming that you donā€™t want to spam the event bus with lots of unchanged status info, we need to make sure that we donā€™t just trigger our rules on ā€œreceived updateā€.

If specific events occur (such as the system changing its status from disarmed to armed, or if an alarm occurs), the C400 will send specific status messages instead of the data dumps for the conventional and bus contacts. The message flow is similar. After you receive the message, respond with a ā€œConfirm_ACKā€ followed by a ā€œSend_NORMā€ (6802026840024216) message, which will tell the C400 to resume sending the contact status messages as usual. If you donā€™t send a Confirm_ACK, the C400 will replay its status message over and over again until you confirm. That is why we need to try and make sure that we have code for every status message that we might see and just send a Confirm_ACK afterwards. One could also send a Confirm_ACK if we receive unknown messages. I didnā€™t do this in my code, however. So if your system appears to get stuck and not react to contacts opening and closing, this might be because of an unhandled message stuck in the queue.

Next, weā€™ll discuss how to set up OpenHAB to process the MQTT messages.

So, for openhab, you need to install the MQTT Binding and the MQTT action. While youā€™re at it, installing the Mail action might also be a good idea, since it would be nice to get an Email if your alarm system changes state (armed, disarmed) or if an alarm occurs.

In MQTT.cfg (/etc/openhab2/services), the only directive you will need is:

mqtt.url=tcp://localhost:1883
mqtt.clientId=openhab

Again, this is a minimal example, with no security.

In your Items file, the two items you will need to begin receiving and sending the MQTT messages are:

String EMA_C400InputString "C400 Input String [%s]" {mqtt="<[mqtt:C400/InputString:state:default]"}
String EMA_C400OutputString "C400 Output String [%s]" {mqtt=">[mqtt:C400/OutputString:state:*:default]"}

Thatā€™s it youā€™re ready to exchange data with the C400. Now we just need some code to interpret the data.
While weā€™re at it, letā€™s set up a few items that will hold the data that we are going to extract from the C400 messages:

Number EMA_C400MB1 "C400 alarm contacts conv. 01-08 [%d]" (c400_debug)
Number EMA_C400MB2 "C400 alarm contacts conv. 09-16 [%d]" (c400_debug)
Number EMA_C400MB3 "C400 alarm contacts intern [%d]" (c400_debug)
Number EMA_C400MB4 "C400 alarm contacts conv. 17-24 [%d]" (c400_debug)
Number EMA_C400MB5 "C400 alarm contacts conv. 25-32 [%d]" (c400_debug)
Contact EMA_Sabo_C400 "C400 Casing intrusion detection" (c400_debug)
Contact EMA_0x0000 "C400 (0x0000) conventional contact 1 [MAP(c400.map):%s]" (c400_debug)
Contact EMA_0x0001 "C400 (0x0001) conventional contact 2 [MAP(c400.map):%s]" (c400_debug)
Contact EMA_0x0002 "C400 (0x0002) conventional contact 3 [MAP(c400.map):%s]" (c400_debug)
Contact EMA_0x0003 "C400 (0x0003) conventional contact 4 [MAP(c400.map):%s]" (c400_debug)
Contact EMA_0x0004 "C400 (0x0004) conventional contact 5 [MAP(c400.map):%s]" (c400_debug)
Contact EMA_0x0005 "C400 (0x0005) conventional contact 6 [MAP(c400.map):%s]" (c400_debug)
Contact EMA_0x0006 "C400 (0x0006) conventional contact 7 [MAP(c400.map):%s]" (c400_debug)
Contact EMA_0x0007 "C400 (0x0007) conventional contact 8 [MAP(c400.map):%s]" (c400_debug)
Contact EMA_0x0008 "C400 (0x0008) conventional contact 9 [MAP(c400.map):%s]" (c400_debug)
Contact EMA_0x0009 "C400 (0x0009) conventional contact 10 [MAP(c400.map):%s]" (c400_debug)
Contact EMA_0x000A "C400 (0x000A) conventional contact 11 [MAP(c400.map):%s]" (c400_debug)
Contact EMA_0x000B "C400 (0x000B) conventional contact 12 [MAP(c400.map):%s]" (c400_debug)
Contact EMA_0x000C "C400 (0x000C) conventional contact 13 [MAP(c400.map):%s]" (c400_debug)
Contact EMA_0x000D "C400 (0x000D) conventional contact 14 [MAP(c400.map):%s]" (c400_debug)
Contact EMA_0x000E "C400 (0x000E) conventional contact 15 [MAP(c400.map):%s]" (c400_debug)
Contact EMA_0x000F "C400 (0x000F) conventional contact 16 [MAP(c400.map):%s]" (c400_debug)
Contact EMA_0x0018 "C400 (0x0018) conventional contact 17 [MAP(c400.map):%s]" (c400_debug)
Contact EMA_0x0019 "C400 (0x0019) conventional contact 18 [MAP(c400.map):%s]" (c400_debug)
Contact EMA_0x001A "C400 (0x001A) conventional contact 19 [MAP(c400.map):%s]" (c400_debug)
Contact EMA_0x001B "C400 (0x001B) conventional contact 20 [MAP(c400.map):%s]" (c400_debug)
Contact EMA_0x001C "C400 (0x001C) conventional contact 21 [MAP(c400.map):%s]" (c400_debug)
Contact EMA_0x001D "C400 (0x001D) conventional contact 22 [MAP(c400.map):%s]" (c400_debug)
Contact EMA_0x001E "C400 (0x001E) conventional contact 23 [MAP(c400.map):%s]" (c400_debug)
Contact EMA_0x001F "C400 (0x001F) conventional contact 24 [MAP(c400.map):%s]" (c400_debug)
Contact EMA_0x0020 "C400 (0x0020) conventional contact 25 [MAP(c400.map):%s]" (c400_debug)
Contact EMA_0x0021 "C400 (0x0021) conventional contact 26 [MAP(c400.map):%s]" (c400_debug)
Contact EMA_0x0022 "C400 (0x0022) conventional contact 27 [MAP(c400.map):%s]" (c400_debug)
Contact EMA_0x0023 "C400 (0x0023) conventional contact 28 [MAP(c400.map):%s]" (c400_debug)
Contact EMA_0x0024 "C400 (0x0024) conventional contact 29 [MAP(c400.map):%s]" (c400_debug)
Contact EMA_0x0025 "C400 (0x0025) conventional contact 30 [MAP(c400.map):%s]" (c400_debug)
Contact EMA_0x0026 "C400 (0x0026) conventional contact 31 [MAP(c400.map):%s]" (c400_debug)
Contact EMA_0x0027 "C400 (0x0027) conventional contact 32 [MAP(c400.map):%s]" (c400_debug)
Contact EMA_0x0010 "C400 (0x0010) internal contact enclosure tamper alarm [MAP(c400.map):%s]" (c400_debug)
Contact EMA_0x0011 "C400 (0x0011) internal contact monitoring siren 1 [MAP(c400.map):%s]" (c400_debug)
Contact EMA_0x0012 "C400 (0x0012) internal contact monitoring siren 2 [MAP(c400.map):%s]" (c400_debug)
Contact EMA_0x0013 "C400 (0x0013) internal contact monitoring optical alarm [MAP(c400.map):%s]" (c400_debug)
Contact EMA_0x0014 "C400 (0x0014) internal contact battery failure [MAP(c400.map):%s]" (c400_debug)
Contact EMA_0x0015 "C400 (0x0015) internal contact power outage [MAP(c400.map):%s]" (c400_debug)
Contact EMA_0x0016 "C400 (0x0016) internal contact 7 n/a [MAP(c400.map):%s]" (c400_debug)
Contact EMA_0x0017 "C400 (0x0017) internal contact back-to-base transmission device failure [MAP(c400.map):%s]" (c400_debug)
Number EMA_C400OB1 "C400 Output state Byte 1 [%d]" (c400_debug)
Number EMA_C400OB2 "C400 Output state Byte 2 [%d]" (c400_debug)
Number EMA_C400OB3 "C400 Output state Byte 3 [%d]" (c400_debug)
Number EMA_C400OB4 "C400 Output state Byte 4 [%d]" (c400_debug)
Number EMA_C400OB5 "C400 Output state Byte 5 [%d]" (c400_debug)
Number EMA_C400OB6 "C400 Output state Byte 6 [%d]" (c400_debug)
Number EMA_C400BS1 "C400 Monitored Area 1 Status [%d]" (c400_debug)
Contact EMA_0x0530 "C400 (0x0530) disarmed area 1 [MAP(c400.map):%s]" (c400_debug)
Contact EMA_0x0531 "C400 (0x0531) internally armed area 1 [MAP(c400.map):%s]" (c400_debug)
Contact EMA_0x0532 "C400 (0x0532) externally armed area 1 [MAP(c400.map):%s]" (c400_debug)
Contact EMA_0x0533 "C400 (0x0533) alarm area 1 [MAP(c400.map):%s]" (c400_debug)
Contact EMA_0x0534 "C400 (0x0534) malfuntion area 1 [MAP(c400.map):%s]" (c400_debug)
Contact EMA_0x0535 "C400 (0x0535) ready to arm internally area 1 [MAP(c400.map):%s]" (c400_debug)
Contact EMA_0x0536 "C400 (0x0536) ready to arm externally area 1 [MAP(c400.map):%s]" (c400_debug)
Contact EMA_0x0537 "C400 (0x0537) internal signal horn area 1 [MAP(c400.map):%s]" (c400_debug)
Number EMA_alarm_Y "C400 Alarm - Jahr [%d]" (c400_debug)
Number EMA_alarm_M "C400 Alarm - Month [%d]" (c400_debug)
Number EMA_alarm_D "C400 Alarm - Day [%d]" (c400_debug)
Number EMA_alarm_h "C400 Alarm - Hour [%d]" (c400_debug)
Number EMA_alarm_m "C400 Alarm - Minute [%d]" (c400_debug)
Number EMA_alarm_s "C400 Alarm - Second [%d]" (c400_debug)
String EMA_alarm_datestring "C400 Alarm - DateTime [%s]" (c400_debug)
String EMA_alarm_contact "C400 Alarm - Contact that caused the alarm [%s]" (c400_debug)
Number EMA_arme_Y "C400 extern scharf - Jahr [%d]" (c400_debug)
Number EMA_arme_M "C400 extern scharf - Month [%d]" (c400_debug)
Number EMA_arme_D "C400 extern scharf - Day [%d]" (c400_debug)
Number EMA_arme_h "C400 extern scharf - Hour [%d]" (c400_debug)
Number EMA_arme_m "C400 extern scharf - Minute [%d]" (c400_debug)
Number EMA_arme_s "C400 extern scharf - Second [%d]" (c400_debug)
String EMA_arme_datestring "C400 extern scharf - DateTime [%s]" (c400_debug)
String EMA_arme_contact "C400 extern scharf - Identifikation [%s]" (c400_debug)
Number EMA_armi_Y "C400 intern scharf - Jahr [%d]" (c400_debug)
Number EMA_armi_M "C400 intern scharf - Month [%d]" (c400_debug)
Number EMA_armi_D "C400 intern scharf - Day [%d]" (c400_debug)
Number EMA_armi_h "C400 intern scharf - Hour [%d]" (c400_debug)
Number EMA_armi_m "C400 intern scharf - Minute [%d]" (c400_debug)
Number EMA_armi_s "C400 intern scharf - Second [%d]" (c400_debug)
String EMA_armi_datestring "C400 intern scharf - DateTime [%s]" (c400_debug)
String EMA_armi_contact "C400 intern scharf - Identifikation [%s]" (c400_debug)
Number EMA_disarm_Y "C400 unscharf - Jahr [%d]" (c400_debug)
Number EMA_disarm_M "C400 unscharf - Month [%d]" (c400_debug)
Number EMA_disarm_D "C400 unscharf - Day [%d]" (c400_debug)
Number EMA_disarm_h "C400 unscharf - Hour [%d]" (c400_debug)
Number EMA_disarm_m "C400 unscharf - Minute [%d]" (c400_debug)
Number EMA_disarm_s "C400 unscharf - Second [%d]" (c400_debug)
String EMA_disarm_datestring "C400 unscharf - DateTime [%s]" (c400_debug)
String EMA_disarm_contact "C400 unscharf - Identifikation [%s]" (c400_debug)
// The following items will hold the state of the system.
// Take care to display them as Text items on your sitemaps and not as switches. Hitting those
// switch items will not do anything
// You could also consider passing those items i.e. as KNX GAs to your bus if you have other systems that are interested in the status
Switch EMA_ext_scharf "Alarm system externally armed [%s]" 
Switch EMA_int_scharf "Alarm system internally armed [%s]"
Switch EMA_ext_ready "Alarm system extern ready-to-arm [%s]"
Switch EMA_int_ready "Alarm system intern ready-to-arm [%s]" 
Switch EMA_unscharf "Alarm system  disarmed [%s]" 
Switch EMA_alarm "ALARM [%s]" <alarm> 

Add these items to your sitemap as you see fit.

I use a little mapping file that you can put into the ā€œ/etc/openhab2/transformā€ directory as c400.map.

CLOSED=resting
OPEN=signal
undefined=undefined

It will make those items give their state as ā€œrestingā€ if the contact is closed and ā€œsignalā€ if it is open if you display them on the sitemap.

Now, hereā€™s the rules file that will handle all the message decoing and populate the items. I tried to be generous with the comments, so hopefully it will explain itself.

There are a number of ā€œlogDebugā€ directives in there. Just change them to ā€œlogInfoā€ to see on the console what the system is doing and how it interprets your data

import java.lang.Integer
import org.eclipse.smarthome.model.script.actions.Timer

var String Confirm_ACK="6802026800020216"
var String ACK = "6802026840024216"
var String Send_NORM = "6802026840024216"
var Timer Contacts_Update_timer = null
var Timer Outputs_Update_timer = null
var int Contacts_Update_interval = 600
var int Outputs_Update_interval = 600
var String Email_receivers = "put your email here, if you want to receive mail cotifications of system events"


rule "init"
when 
	System started
then
    //populate the tems with some "safe" default values on system startup before they get initialized from the persistence DB
	sendCommand(EMA_C400MB1,255)
	sendCommand(EMA_C400MB2,255)
	sendCommand(EMA_C400MB3,255)
	sendCommand(EMA_C400MB4,255)
	sendCommand(EMA_C400MB5,255)
	sendCommand(EMA_C400OB1,255)
	sendCommand(EMA_C400OB2,255)
	sendCommand(EMA_C400OB3,255)
	sendCommand(EMA_C400OB4,255)
	sendCommand(EMA_C400OB5,255)
	sendCommand(EMA_C400OB6,255)
	sendCommand(EMA_C400BS1,158)
	sendCommand(EMA_set_unscharf,OFF)
	sendCommand(EMA_set_ext_scharf, OFF)
	sendCommand(EMA_set_int_scharf, OFF)
	sendCommand(EMA_set_panic, OFF)
	sendCommand(EMA_reset_alarm, OFF)
	sendCommand(EMA_sendNORM, OFF)
	sendCommand(EMA_alarm_datestring, "00.00.00 00:00:00")
	sendCommand(EMA_alarm_contact, "")
	sendCommand(EMA_arme_datestring, "00.00.00 00:00:00")
	sendCommand(EMA_arme_contact, "")
	sendCommand(EMA_armi_datestring, "00.00.00 00:00:00")
	sendCommand(EMA_armi_contact, "")
	sendCommand(EMA_disarm_datestring, "00.00.00 00:00:00")
	sendCommand(EMA_disarm_contact, "")
end



rule "Decode C400_InputString"
when
	Item EMA_C400InputString received update
then
// The Input String is the UDP message received from the Telenot Complex 400 alarm system. It sends either ACK messages, or status update messages 
// for alarm contacts (both conventional and Bus based contacts) as well as other system status updates (i.e. power outages or, of course alarms)
// The messages need to be decoded and once decoded, each message needs to be acknowledged by sending a CONFIRM_ACK message, or the system will keep
// resending it until acknowledged by CONFIRM_ACK

	var dec = EMA_C400InputString.state.toString
	logDebug("C400_rules", "len: "+ dec.length.toString + ", str: " + dec)
	var String teststr = "00000000000000000000000000"

// Test for ACK message
	if (dec.substring(0,16) == "6802026840024216") {
		logDebug("C400_rules: Decode: ", "Received ACK message")
		logDebug("C400_rules: Send Reply: ", "Confirm_ACK "+ Confirm_ACK.toString)
		sendCommand(EMA_C400OutputString, Confirm_ACK)
	}	
// Test for Confirm_ACK    
	if (dec.substring(0,16) == "6802026800020216") {
		logDebug("C400_rules: Decode: ", "Received Confirm_ACK message")
		logDebug("C400_rules: Send Reply: ", "Confirm_ACK "+ Confirm_ACK.toString)
		sendCommand(EMA_C400OutputString, Confirm_ACK)
	}

	if (dec.length > 37) {
		teststr = dec.substring(0,26)	
	}
// Test for hte status message containing the status bits for the conventional alarm contacts
// I called it the "2e2e", because that the length the record has on my system. Yours might look a bit different
//depending on the HW configuration of your C400. Just adjust the string to test against with what you're seeing in the 
//MQTT messages. You might also need to adjust the positions to decode the status bytes from in that message
	if (teststr.substring(0,24) == "682e2e687302222400000001") {
		logDebug("C400_rules: Decode ", "Received 2e2e Input state")
		logDebug("C400_rules: Message ",dec)
		
		// Decode Contact Bytes 1-5
		var String Contactbytes = dec.substring(24,34)
		logDebug("C400_rules: Decode 2e2e: conventional Contacts Bytes (1-5)", Contactbytes)
		
		// First contact Byte holds conventional alarm contacts 1 through 8 (from LSB to MSB)
		var String cbyte1 = dec.substring(24,26)
		var int int1 = Integer::parseInt(cbyte1,16)
		logDebug("C400_rules: Decode 2e2e:","byte1: char: "  + cbyte1 + ", Int: " + int1.toString)
		
		// Second contact Byte holds conventional alarm contacts 9 through 16
		var String cbyte2 = dec.substring(26,28)
		var int int2 = Integer::parseInt(cbyte2,16)
		logDebug("C400_rules: Decode 2e2e:","byte2: char: "  + cbyte2 + ", Int: " + int2.toString)
		
		// Third contact Byte hold system internal alarm contacts (such as system case intrusion, etc.)
		var String cbyte3 = dec.substring(28,30)
		var int int3 = Integer::parseInt(cbyte3,16)
		logDebug("C400_rules: Decode 2e2e:","byte3: char: "  + cbyte3 + ", Int: " + int3.toString)
		
		// Fourth contact Byte holds conventional alarm contacts 17 through 24
		var String cbyte4 = dec.substring(30,32)
		var int int4 = Integer::parseInt(cbyte4,16)
		logDebug("C400_rules: Decode 2e2e:","byte4: char: "  + cbyte4 + ", Int: " + int4.toString)
		
		// Fifth contact byte holds conventional alarm contacts 25 through 32
		var String cbyte5 = dec.substring(32,34)
		var int int5 = Integer::parseInt(cbyte5,16)
		logDebug("C400_rules: Decode 2e2e:","byte5: char: "  + cbyte5 + ", Int: " + int5.toString)

		
		// check if there has been a change in the values (C400 sends status every 3 seconds or so), 
		// we don't want to spam our events bus with no change updates
		if ((int1 == (EMA_C400MB1.state as DecimalType).intValue) && 
		    (int2 == (EMA_C400MB2.state as DecimalType).intValue) && 
		    (int3 == (EMA_C400MB3.state as DecimalType).intValue) && 
		    (int4 == (EMA_C400MB4.state as DecimalType).intValue) && 
		    (int5 == (EMA_C400MB5.state as DecimalType).intValue)) {
		     	if (Contacts_Update_timer === null) {
				// Start a Timer to send an update on the Bus even if there was no change for an interval specified by
				// "Contacts_Update_interval" (600 seconds) just to let the Bus know that we are alive
                   // Slight note of caution: I have seen this code throw some null pointer exceptions during system statup at some time. I'm not sure why this might happen yet
				Contacts_Update_timer = createTimer(now.plusSeconds(Contacts_Update_interval)) [|
					postUpdate(EMA_C400MB1,EMA_C400MB1.state)
					postUpdate(EMA_C400MB2,EMA_C400MB2.state)
					postUpdate(EMA_C400MB3,EMA_C400MB3.state)
					postUpdate(EMA_C400MB4,EMA_C400MB4.state)
					postUpdate(EMA_C400MB5,EMA_C400MB5.state)
					Contacts_Update_timer = null	
				]
			}
		// there has been a change, update the Bus (and trigger the rules to evaluate the contacts contained in those bytes)
		} else {
			Contacts_Update_timer = null
			postUpdate(EMA_C400MB1,int1)
			postUpdate(EMA_C400MB2,int2)
			postUpdate(EMA_C400MB3,int3)
			postUpdate(EMA_C400MB4,int4)
			postUpdate(EMA_C400MB5,int5)
	    }

		// Decode complete, send CONFIRM_ACK message	
		sendCommand(EMA_C400OutputString, Confirm_ACK)
		logDebug("C400_rules: 2e2e Decode complete, Send Reply: ", "Confirm_ACK "+ Confirm_ACK.toString)

	}
	
// Test for the status message containing the status bits for the bus based alarm contacts
// I called it the "3e3e", because that the length the record has on my system. Yours might look a bit different
//depending on the HW configuration of your C400. Just adjust the string to test against with what you're seeing in the 
//MQTT messages.
// I don't have any Bus based contacts on my system, so there is not code to decode them. The only thing I am interested in from this message is the status byte for the system, where you can find out if it is armed, disarmed, and so on. 

// Test for 3e3e Output State message
	
	if (teststr.substring(0,24) == "683e3e687302322400050002") {
		logDebug("C400_rules: Decode", "Received 3e3e Output state")
		logDebug("C400_rules: Message ",dec)

		// Decode Output state Bytes 1-7
		var String Outputbytes = dec.substring(24,38)
		logDebug("C400_rules: Decode 3e3e: Output State Bytes (1-6) + Status Byte:", Outputbytes)

		// First Output State Byte
		var String obyte1 = dec.substring(24,26)
		var int int1 = Integer::parseInt(obyte1,16)
		logDebug("C400_rules: Decode 3e3e:","byte1: char: "  + obyte1 + ", Int: " + int1.toString)

		// Second Output State Byte
		var String obyte2 = dec.substring(26,28)
		var int int2 = Integer::parseInt(obyte2,16)
		logDebug("C400_rules: Decode 3e3e:","byte2: char: "  + obyte2 + ", Int: " + int2.toString)

		// Third Output State Byte
		var String obyte3 = dec.substring(28,30)
		var int int3 = Integer::parseInt(obyte3,16)
		logDebug("C400_rules: Decode 3e3e:","byte3: char: "  + obyte3 + ", Int: " + int3.toString)

		// Fourth Output State Byte
		var String obyte4 = dec.substring(30,32)
		var int int4 = Integer::parseInt(obyte4,16)
		logDebug("C400_rules: Decode 3e3e:","byte4: char: "  + obyte4 + ", Int: " + int4.toString)

		// Fifth Output State Byte
		var String obyte5 = dec.substring(32,34)
		var int int5 = Integer::parseInt(obyte5,16)
		logDebug("C400_rules: Decode 3e3e:","byte5: char: "  + obyte5 + ", Int: " + int5.toString)

		// Sixth Output State Byte
		var String obyte6 = dec.substring(34,36)
		var int int6 = Integer::parseInt(obyte6,16)
		logDebug("C400_rules: Decode 3e3e:","byte6: char: "  + obyte6 + ", Int: " + int6.toString)

		// Monitored Area 1 Status Byte
		var String bsbyte1 = dec.substring(36,38)
		var int int7 = Integer::parseInt(bsbyte1,16)
		logDebug("C400_rules: Decode 3e3e:","Status byte: char: "  + bsbyte1 + ", Int: " + int7.toString)

		// check if there has been a change in the values (C400 sends status every 3 seconds or so), 
		// we don't want to spam our events bus with no change updates
		if ((int1 == (EMA_C400OB1.state as DecimalType).intValue) && 
		    (int2 == (EMA_C400OB2.state as DecimalType).intValue) && 
		    (int3 == (EMA_C400OB3.state as DecimalType).intValue) && 
		    (int4 == (EMA_C400OB4.state as DecimalType).intValue) &&
		    (int5 == (EMA_C400OB5.state as DecimalType).intValue) && 
		    (int6 == (EMA_C400OB6.state as DecimalType).intValue) &&  
		    (int7 == (EMA_C400BS1.state as DecimalType).intValue)) {
		     	if (Outputs_Update_timer === null) {
				// Start a Timer to send an update on the Bus even if there was no change for an interval specified by
				// "Contacts_Update_interval (600 seconds)" just to let the Bus know that we are alive
                   // Slight note of caution: I have seen this code throw some null pointer exceptions during system statup at some time. I'm not sure why this might happen yet
				Outputs_Update_timer = createTimer(now.plusSeconds(Outputs_Update_interval)) [|
					postUpdate(EMA_C400OB1,EMA_C400OB1.state)
					postUpdate(EMA_C400OB2,EMA_C400OB2.state)
					postUpdate(EMA_C400OB3,EMA_C400OB3.state)
					postUpdate(EMA_C400OB4,EMA_C400OB4.state)
					postUpdate(EMA_C400OB5,EMA_C400OB5.state)
					postUpdate(EMA_C400OB6,EMA_C400OB6.state)
					postUpdate(EMA_C400BS1,EMA_C400BS1.state)
					Outputs_Update_timer = null	
				]
			}
		// there has been a change, update the Bus (and trigger the rules to evaluate the contacts contained in those bytes)
		} else {
			Outputs_Update_timer = null
			postUpdate(EMA_C400OB1,int1)
			postUpdate(EMA_C400OB2,int2)
			postUpdate(EMA_C400OB3,int3)
			postUpdate(EMA_C400OB4,int4)
			postUpdate(EMA_C400OB5,int5)
			postUpdate(EMA_C400OB6,int6)
			postUpdate(EMA_C400BS1,int7)
	    }

		// Decode complete, send CONFIRM_ACK message
		sendCommand(EMA_C400OutputString, Confirm_ACK)
		logDebug("C400_rules: 3e3e Decode complete, Send Reply: ", "Confirm_ACK "+ Confirm_ACK.toString)
	}

// Test for Alarm signal / alarm signal reset
	if (teststr.substring(0,18) == "683c3c687302050201") {
		logInfo("C400_rules: Decode", "Alarm set/clear message received")
		// Identify Type of signal. "0122" indicates an alarm, "01a2" indicates an alarm reset
		var int type = Integer::parseInt(dec.substring(22,26),16)
		// Decode Time of the alarm occurence
		var int year = Integer::parseInt(dec.substring(30,32),16)
		var int month = Integer::parseInt(dec.substring(34,36),16)
		var int day = Integer::parseInt(dec.substring(36,38),16)
		var int hour = Integer::parseInt(dec.substring(38,40),16)
		var int min = Integer::parseInt(dec.substring(40,42),16)
		var int sec = Integer::parseInt(dec.substring(42,44),16)
		var String contacthex = dec.substring(44,72)
		var int contacthex_len = contacthex.length()
		var int b = 0
		var String contact = ""
	    while (b < contacthex_len) {
			var Integer charcode = Integer::parseInt(contacthex.substring(b,2),16)
			contact += String::valueOf(Character::toChars(charcode))
			b=b+2
		}

		// extracted all the info - update items now
		postUpdate(EMA_alarm_Y, year)
		postUpdate(EMA_alarm_M, month)
		postUpdate(EMA_alarm_D, day)
		postUpdate(EMA_alarm_h, hour)
		postUpdate(EMA_alarm_m, min)
		postUpdate(EMA_alarm_s, sec)
		var String datetime=day.toString+"."+month.toString+"."+year.toString+" "+hour.toString+":"+min.toString+":"+sec.toString
		postUpdate(EMA_alarm_datestring, datetime)
		postUpdate(EMA_alarm_contact, contact)
		if (type=="0122")  {
			logInfo("C400 ALARM set", datetime + ": "+ contact)
			// put code for an alarm sequence here, i.e. turn on lights, make noise.... For now, let's just send a mail
			sendMail(Email_receivers, "C400 ALARM: "+ datetime, contact)
		}
		if (type=="01a2") {
			logInfo("C400 alarm cleared", datetime + ": "+ contact)
			// put code to reset the alarm here, i.e. go back to quiet
			sendMail(Email_receivers, "C400 Alarm zurueckgesetzt: "+ datetime, contact)
		}
		// Decode done, restart the normal communication flow by sending CONFIRM_ACK followed by SEND_NORM
		sendCommand(EMA_C400OutputString, Confirm_ACK)
		sendCommand(EMA_C400OutputString, Send_NORM)	
	}

// Test for info message "sytem externally armed"
	if (teststr.substring(0,26) == "682c2c68730205020005320161") {
		logInfo("C400_rules: Decode", "Received: System externally armed")
		
		// Decode Time of the system going armed
		var int year = Integer::parseInt(dec.substring(30,32),16)
		var int month = Integer::parseInt(dec.substring(34,36),16)
		var int day = Integer::parseInt(dec.substring(36,38),16)
		var int hour = Integer::parseInt(dec.substring(38,40),16)
		var int min = Integer::parseInt(dec.substring(40,42),16)
		var int sec = Integer::parseInt(dec.substring(42,44),16)
		var String contacthex = dec.substring(44,72)
		var int contacthex_len = contacthex.length()
		var int b = 0
		var String contact = ""
	    while (b < contacthex_len) {
			var Integer charcode = Integer::parseInt(contacthex.substring(b,b+2),16)
			contact += String::valueOf(Character::toChars(charcode))
			b=b+2
		}

		// extracted all the info - update items now
		postUpdate(EMA_arme_Y, year)
		postUpdate(EMA_arme_M, month)
		postUpdate(EMA_arme_D, day)
		postUpdate(EMA_arme_h, hour)
		postUpdate(EMA_arme_m, min)
		postUpdate(EMA_arme_s, sec)
		var String datetime=day.toString+"."+month.toString+"."+year.toString+" "+hour.toString+":"+min.toString+":"+sec.toString
		postUpdate(EMA_arme_datestring, datetime)
		postUpdate(EMA_arme_contact, contact)
		
		// Do "stuff" if the system is externally armed
		// For now, let's jsut send a mail
		logInfo("C400_rules:", "Extern scharf: "+ datetime + ": "+ contact)
		sendMail(Email_receivers, "C400 extern scharf: "+ datetime, contact)

		// Decode done, restart the normal communication flow by sending CONFIRM_ACK followed by SEND_NORM
		sendCommand(EMA_C400OutputString, Confirm_ACK)
		sendCommand(EMA_C400OutputString, Send_NORM)	
	}

// Test for info message "sytem internally armed"
	if (teststr.substring(0,26) == "682c2c68730205020005310162") {
		logInfo("C400_rules: Decode", "Received: System internally armed")
		
		// Decode Time of the system going armed
		var int year = Integer::parseInt(dec.substring(30,32),16)
		var int month = Integer::parseInt(dec.substring(34,36),16)
		var int day = Integer::parseInt(dec.substring(36,38),16)
		var int hour = Integer::parseInt(dec.substring(38,40),16)
		var int min = Integer::parseInt(dec.substring(40,42),16)
		var int sec = Integer::parseInt(dec.substring(42,44),16)
		var String contacthex = dec.substring(44,72)
		var int contacthex_len = contacthex.length()
		var int b = 0
		var String contact = ""
	    while (b < contacthex_len) {
			var Integer charcode = Integer::parseInt(contacthex.substring(b,b+2),16)
			contact += String::valueOf(Character::toChars(charcode))
			b=b+2
		}

		// extracted all the info - update items now
		postUpdate(EMA_armi_Y, year)
		postUpdate(EMA_armi_M, month)
		postUpdate(EMA_armi_D, day)
		postUpdate(EMA_armi_h, hour)
		postUpdate(EMA_armi_m, min)
		postUpdate(EMA_armi_s, sec)
		var String datetime=day.toString+"."+month.toString+"."+year.toString+" "+hour.toString+":"+min.toString+":"+sec.toString
		postUpdate(EMA_armi_datestring, datetime)
		postUpdate(EMA_armi_contact, contact)
		
		// Do "stuff" if the system is internally armed
		// For now, just pst an info, that the message has been decoded
		logInfo("C400_rules: ","intern scharf")
		
		// Decode done, restart the normal communication flow by sending CONFIRM_ACK followed by SEND_NORM
		sendCommand(EMA_C400OutputString, Confirm_ACK)
		sendCommand(EMA_C400OutputString, Send_NORM)	
	}

// Test for info message "sytem disarmed"
	if (teststr.substring(0,26) == "682c2c687302050200053001e1") {

		logInfo("C400_rules: Decode", "Received: System disarmed")
		
		// Decode Time of the system going disarmed
		var int year = Integer::parseInt(dec.substring(30,32),16)
		var int month = Integer::parseInt(dec.substring(34,36),16)
		var int day = Integer::parseInt(dec.substring(36,38),16)
		var int hour = Integer::parseInt(dec.substring(38,40),16)
		var int min = Integer::parseInt(dec.substring(40,42),16)
		var int sec = Integer::parseInt(dec.substring(42,44),16)
		var String contacthex = dec.substring(44,72)
		var int contacthex_len = contacthex.length()
		var int b = 0
		var String contact = ""
	    while (b < contacthex_len) {
			var Integer charcode = Integer::parseInt(contacthex.substring(b,b+2),16)
			contact += String::valueOf(Character::toChars(charcode))
			b=b+2
		}

		// extracted all the info - update items now
		postUpdate(EMA_disarm_Y, year)
		postUpdate(EMA_disarm_M, month)
		postUpdate(EMA_disarm_D, day)
		postUpdate(EMA_disarm_h, hour)
		postUpdate(EMA_disarm_m, min)
		postUpdate(EMA_disarm_s, sec)
		var String datetime=day.toString+"."+month.toString+"."+year.toString+" "+hour.toString+":"+min.toString+":"+sec.toString
		postUpdate(EMA_disarm_datestring, datetime)
		postUpdate(EMA_disarm_contact, contact)
		
		// Do "stuff" if the system is disarmed
		// For now, just send a mail
		sendMail(Email_receivers, "C400 unscharf: "+ datetime, contact)
		logInfo("C400_rules:", "unscharf: "+ datetime + ": "+ contact)

		// Decode done, restart the normal communication flow by sending CONFIRM_ACK followed by SEND_NORM
		sendCommand(EMA_C400OutputString, Confirm_ACK)
		sendCommand(EMA_C400OutputString, Send_NORM)	
	}

// Test for info message "system intrusion detection"
	if (teststr.substring(0,26) == "682c2c68730205020100100123") {
		logInfo("C400_rules: Decode", "Received: System intrusion detection")
				
		// Decode done, restart the normal communication flow by sending CONFIRM_ACK followed by SEND_NORM
		sendCommand(EMA_C400OutputString, Confirm_ACK)
		sendCommand(EMA_C400OutputString, Send_NORM)	
	}

// Test for info message "system intrusion cleared"
	if (teststr.substring(0,26) == "682c2c687302050201001001a3") {
		logInfo("C400_rules: Decode", "Received: System intrusion cleared")
				
		// Decode done, restart the normal communication flow by sending CONFIRM_ACK followed by SEND_NORM
		sendCommand(EMA_C400OutputString, Confirm_ACK)
		sendCommand(EMA_C400OutputString, Send_NORM)	
	}

// Test for info message "battery malfunction"
	if (teststr.substring(0,26) == "681a1a68730205020000140133") {
		logInfo("C400_rules: Decode", "Received: System battery malfunction")
				
		// Decode done, restart the normal communication flow by sending CONFIRM_ACK followed by SEND_NORM
		sendCommand(EMA_C400OutputString, Confirm_ACK)
		sendCommand(EMA_C400OutputString, Send_NORM)	
	}

// Test for info message "battery malfunction cleared"
	if (teststr.substring(0,26) == "681a1a687302050200001401b3") {
		logInfo("C400_rules Decode", "Received: System battery malfunction cleared")
				
		// Decode done, restart the normal communication flow by sending CONFIRM_ACK followed by SEND_NORM
		sendCommand(EMA_C400OutputString, Confirm_ACK)
		sendCommand(EMA_C400OutputString, Send_NORM)	
	}

// Test for info message "power outage"
	if (teststr.substring(0,26) == "681a1a68730205020000150132") {
		logInfo("C400_rules: Decode", "Received: power outage detected")

		var int year = Integer::parseInt(dec.substring(30,32),16)
		var int month = Integer::parseInt(dec.substring(34,36),16)
		var int day = Integer::parseInt(dec.substring(36,38),16)
		var int hour = Integer::parseInt(dec.substring(38,40),16)
		var int min = Integer::parseInt(dec.substring(40,42),16)
		var int sec = Integer::parseInt(dec.substring(42,44),16)
		var String contacthex = dec.substring(44,72)
		var int contacthex_len = contacthex.length()
		var int b = 0
		var String contact = ""
	    while (b < contacthex_len) {
			var Integer charcode = Integer::parseInt(contacthex.substring(b,b+2),16)
			contact += String::valueOf(Character::toChars(charcode))
			b=b+2
		}

		
		var String datetime=day.toString+"."+month.toString+"."+year.toString+" "+hour.toString+":"+min.toString+":"+sec.toString
		sendMail(Email_receivers, "C400 Stromausfall: "+ datetime, contact)
		

		// Decode done, restart the normal communication flow by sending CONFIRM_ACK followed by SEND_NORM
		sendCommand(EMA_C400OutputString, Confirm_ACK)
		sendCommand(EMA_C400OutputString, Send_NORM)	
	}

// Test for info message "power outage cleared"
	if (teststr.substring(0,26) == "681a1a687302050200001501b2") {
		logInfo("C400_rules: Decode", "Received: power outage detected")

		var int year = Integer::parseInt(dec.substring(30,32),16)
		var int month = Integer::parseInt(dec.substring(34,36),16)
		var int day = Integer::parseInt(dec.substring(36,38),16)
		var int hour = Integer::parseInt(dec.substring(38,40),16)
		var int min = Integer::parseInt(dec.substring(40,42),16)
		var int sec = Integer::parseInt(dec.substring(42,44),16)
		var String contacthex = dec.substring(44,72)
		var int contacthex_len = contacthex.length()
		var int b = 0
		var String contact = ""
	    while (b < contacthex_len) {
			var Integer charcode = Integer::parseInt(contacthex.substring(b,b+2),16)
			contact += String::valueOf(Character::toChars(charcode))
			b=b+2
		}
		
		var String datetime=day.toString+"."+month.toString+"."+year.toString+" "+hour.toString+":"+min.toString+":"+sec.toString
		sendMail(Email_receivers, "C400 Stromausfall behoben: "+ datetime, contact)
		

		// Decode done, restart the normal communication flow by sending CONFIRM_ACK followed by SEND_NORM
		sendCommand(EMA_C400OutputString, Confirm_ACK)
		sendCommand(EMA_C400OutputString, Send_NORM)	
	}

// Test for info message "optical flasher malfunction"
	if (teststr.substring(0,26) == "681a1a68730205020000130130") {
		logInfo("C400_rules: Decode", "Received: optical flasher malfunction")
				
		// Decode done, restart the normal communication flow by seding CONFIRM_ACK followed by SEND_NORM
		sendCommand(EMA_C400OutputString, Confirm_ACK)
		sendCommand(EMA_C400OutputString, Send_NORM)	
	}

// Test for info message "optical flasher malfunction cleared"
	if (teststr.substring(0,26) == "681a1a687302050200001301b0") {
		logInfo("C400_rules: Decode", "Received: optical flasher malfunction cleared")
				
		// Decode done, restart the normal communication flow by sending CONFIRM_ACK followed by SEND_NORM
		sendCommand(EMA_C400OutputString, Confirm_ACK)
		sendCommand(EMA_C400OutputString, Send_NORM)	
	}

// Test for info message "acoustic alarm horn 1 malfuntion"
	if (teststr.substring(0,26) == "681a1a68730205020000110130") {
		logInfo("C400_rules: Decode", "Reiceived: signal horn 1 malfunction")
				
		// Decode done, restart the normal communication flow by sending CONFIRM_ACK followed by SEND_NORM
		sendCommand(EMA_C400OutputString, Confirm_ACK)
		sendCommand(EMA_C400OutputString, Send_NORM)	
	}

// Test for info message "acoustic alarm horn 1 malfuntion cleared"
	if (teststr.substring(0,26) == "681a1a687302050200001101b0") {
		logInfo("C400_rules: Decode", "Received: signal horn 1 malfunction cleared")
				
		// Decode done, restart the normal communication flow by sending CONFIRM_ACK followed by SEND_NORM
		sendCommand(EMA_C400OutputString, Confirm_ACK)
		sendCommand(EMA_C400OutputString, Send_NORM)	
	}

// Test for info message "acoustic alarm horn 2 malfuntion"
	if (teststr.substring(0,26) == "681a1a68730205020000120130") {
		logInfo("C400_rules: Decode", "Received: signal horn 2 malfunction")
				
		// Decode done, restart the normal communication flow by sending CONFIRM_ACK followed by SEND_NORM
		sendCommand(EMA_C400OutputString, Confirm_ACK)
		sendCommand(EMA_C400OutputString, Send_NORM)	
	}

// Test for info message "acoustic alarm horn 2 malfuntion cleared"
	if (teststr.substring(0,26) == "681a1a687302050200001201b0") {
		logInfo("C400_rules: Decode", "Received: signal horn 2 malfunction cleared")
				
		// Decode done, restart the normal communication flow by sending CONFIRM_ACK followed by SEND_NORM
		sendCommand(EMA_C400OutputString, Confirm_ACK)
		sendCommand(EMA_C400OutputString, Send_NORM)	
	}
end

This is the rules file continued (I needed to split it up across two posts).

// This code and the following system triggers if the decode rule above found some changed status bits and updated the 
//items accordingly. This code will decode the individual bytes and update the items. If you want to have items in your 
//sitemap to display the status of your windows/doors, it might be a good idea to put sendCommands/postUpdates for 
//those items here as well
//For instance, I send those status bits to my KNX bus via some items as well, so that I can use the infor that a window 
//has opened for my sun blinds (don't close them if the terace door is open) or my heating system (turn off the heat to 
//this room if someone opens a window)

// Decode status for conventional alarm contacs 1 through 8 and update the associated contact items
rule "Decode conv. alarm contacts byte1"
	when 
		Item EMA_C400MB1 received update
	then
		var int1 = (EMA_C400MB1.state as DecimalType).intValue
		var int c8 = int1 / 128
		var int c7 = (int1 - c8 * 128) / 64
		var int c6 = (int1 - c8 * 128 - c7 * 64) / 32
		var int c5 = (int1 - c8 * 128 - c7 * 64 - c6 * 32) / 16
		var int c4 = (int1 - c8 * 128 - c7 * 64 - c6 * 32 - c5 * 16) / 8
		var int c3 = (int1 - c8 * 128 - c7 * 64 - c6 * 32 - c5 * 16 - c4 * 8) / 4
		var int c2 = (int1 - c8 * 128 - c7 * 64 - c6 * 32 - c5 * 16 - c4 * 8 - c3 * 4) / 2
		var int c1 = (int1 - c8 * 128 - c7 * 64 - c6 * 32 - c5 * 16 - c4 * 8 - c3 * 4 - c2 * 2) / 1 
		logDebug("C400 rules Decode 2e2e Byte 1: ", int1.toString+": "+c8.toString+c7.toString+c6.toString+c5.toString+c4.toString+c3.toString+c2.toString+c1.toString)
		
		// c1 = (0x0000) conventional contact 01
		// c2 = (0x0001) conventional contact 02
		// c3 = (0x0002) conventional contact 03
		// c4 = (0x0003) conventional contact 04
		// c5 = (0x0004) conventional contact 05
		// c6 = (0x0005) conventional contact 06
		// c7 = (0x0006) conventional contact 07
		// c8 = (0x0007) conventional contact 08
  		
		if (c1 == 1) {
			sendCommand(EMA_0x0000, CLOSED)
		} else {
			sendCommand(EMA_0x0000,OPEN)
		}
		if (c2 == 1) {
			sendCommand(EMA_0x0001, CLOSED)
		} else {
			sendCommand(EMA_0x0001,OPEN)
		}
		if (c3 == 1) {
			sendCommand(EMA_0x0002, CLOSED)
		} else {
			sendCommand(EMA_0x0002,OPEN)
		}
		if (c4 == 1) {
			sendCommand(EMA_0x0003, CLOSED)
		} else {
			sendCommand(EMA_0x0003,OPEN)
		}
		if (c5 == 1) {
			sendCommand(EMA_0x0004, CLOSED)
		} else {
			sendCommand(EMA_0x0004,OPEN)
		}
		if (c6 == 1) {
			sendCommand(EMA_0x0005, CLOSED)
		} else {
			sendCommand(EMA_0x0005,OPEN)
		}
		if (c7 == 1) {
			sendCommand(EMA_0x0006, CLOSED)
		} else {
			sendCommand(EMA_0x0006,OPEN)
		}
		if (c8 == 1) {
			sendCommand(EMA_0x0007, CLOSED)
		} else {
			sendCommand(EMA_0x0007,OPEN)
		}
end

// Decode status for conventional alarm contacs 9 through 16 and update the associated contact items
rule "Decode conv. alarm contacts byte2"
	when 
		Item EMA_C400MB2 received update
	then
		var int1 = (EMA_C400MB2.state as DecimalType).intValue
		var int c8 = int1 / 128
		var int c7 = (int1 - c8 * 128) / 64
		var int c6 = (int1 - c8 * 128 - c7 * 64) / 32
		var int c5 = (int1 - c8 * 128 - c7 * 64 - c6 * 32) / 16
		var int c4 = (int1 - c8 * 128 - c7 * 64 - c6 * 32 - c5 * 16) / 8
		var int c3 = (int1 - c8 * 128 - c7 * 64 - c6 * 32 - c5 * 16 - c4 * 8) / 4
		var int c2 = (int1 - c8 * 128 - c7 * 64 - c6 * 32 - c5 * 16 - c4 * 8 - c3 * 4) / 2
		var int c1 = (int1 - c8 * 128 - c7 * 64 - c6 * 32 - c5 * 16 - c4 * 8 - c3 * 4 - c2 * 2) / 1 
		logInfo("C400 rules Decode 2e2e Byte 2: ", int1.toString+": "+c8.toString+c7.toString+c6.toString+c5.toString+c4.toString+c3.toString+c2.toString+c1.toString)
		
		// c1 = (0x0008) conventional contact 09
		// c2 = (0x0009) conventional contact 10
		// c3 = (0x000A) conventional contact 11
		// c4 = (0x000B) conventional contact 12
		// c5 = (0x000C) conventional contact 13
		// c6 = (0x000D) conventional contact 14
		// c7 = (0x000E) conventional contact 15
		// c8 = (0x000F) conventional contact 16
  		
		if (c1 == 1) {
			sendCommand(EMA_0x0008, CLOSED)
		} else {
			sendCommand(EMA_0x0008,OPEN)
		}
		if (c2 == 1) {
			sendCommand(EMA_0x0009, CLOSED)
		} else {
			sendCommand(EMA_0x0009,OPEN)
		}
		if (c3 == 1) {
			sendCommand(EMA_0x000A, CLOSED)
		} else {
			sendCommand(EMA_0x000A,OPEN)
		}
		if (c4 == 1) {
			sendCommand(EMA_0x000B, CLOSED)
		} else {
			sendCommand(EMA_0x000B,OPEN)
		}
		if (c5 == 1) {
			sendCommand(EMA_0x000C, CLOSED)
		} else {
			sendCommand(EMA_0x000C,OPEN)
		}
		if (c6 == 1) {
			sendCommand(EMA_0x000D, CLOSED)
		} else {
			sendCommand(EMA_0x000D,OPEN)
		}
		if (c7 == 1) {
			sendCommand(EMA_0x000E, CLOSED)
		} else {
			sendCommand(EMA_0x000E,OPEN)
		}
		if (c8 == 1) {
			sendCommand(EMA_0x000F, CLOSED)
		} else {
			sendCommand(EMA_0x000F,OPEN)
		}
end

// Decode status for conventional alarm contacs 17 through 24 and update the associated contact items
rule "Decode conv. alarm contacts byte4"
	when 
		Item EMA_C400MB4 received update
	then
		var int1 = (EMA_C400MB4.state as DecimalType).intValue
		var int c8 = int1 / 128
		var int c7 = (int1 - c8 * 128) / 64
		var int c6 = (int1 - c8 * 128 - c7 * 64) / 32
		var int c5 = (int1 - c8 * 128 - c7 * 64 - c6 * 32) / 16
		var int c4 = (int1 - c8 * 128 - c7 * 64 - c6 * 32 - c5 * 16) / 8
		var int c3 = (int1 - c8 * 128 - c7 * 64 - c6 * 32 - c5 * 16 - c4 * 8) / 4
		var int c2 = (int1 - c8 * 128 - c7 * 64 - c6 * 32 - c5 * 16 - c4 * 8 - c3 * 4) / 2
		var int c1 = (int1 - c8 * 128 - c7 * 64 - c6 * 32 - c5 * 16 - c4 * 8 - c3 * 4 - c2 * 2) / 1 
		logDebug("C400 rules Decode 2e2e Byte 4: ", int1.toString+": "+c8.toString+c7.toString+c6.toString+c5.toString+c4.toString+c3.toString+c2.toString+c1.toString)
		
		// c1 = (0x0018) conventional contact 17
		// c2 = (0x0019) conventional contact 18
		// c3 = (0x001A) conventional contact 19
		// c4 = (0x001B) conventional contact 20
		// c5 = (0x001C) conventional contact 21
		// c6 = (0x001D) conventional contact 22
		// c7 = (0x001E) conventional contact 23
		// c8 = (0x001F) conventional contact 24
  		
		if (c1 == 1) {
			sendCommand(EMA_0x0018, CLOSED)
		} else {
			sendCommand(EMA_0x0018,OPEN)
		}
		if (c2 == 1) {
			sendCommand(EMA_0x0019, CLOSED)
		} else {
			sendCommand(EMA_0x0019,OPEN)
		}
		if (c3 == 1) {
			sendCommand(EMA_0x001A, CLOSED)
		} else {
			sendCommand(EMA_0x001A,OPEN)
		}
		if (c4 == 1) {
			sendCommand(EMA_0x001B, CLOSED)
		} else {
			sendCommand(EMA_0x001B,OPEN)
		}
		if (c5 == 1) {
			sendCommand(EMA_0x001C, CLOSED)
		} else {
			sendCommand(EMA_0x001C,OPEN)
		}
		if (c6 == 1) {
			sendCommand(EMA_0x001D, CLOSED)
		} else {
			sendCommand(EMA_0x001D,OPEN)
		}
		if (c7 == 1) {
			sendCommand(EMA_0x001E, CLOSED)
		} else {
			sendCommand(EMA_0x001E,OPEN)
		}
		if (c8 == 1) {
			sendCommand(EMA_0x001F, CLOSED)
		} else {
			sendCommand(EMA_0x001F,OPEN)
		}
end

// Decode status for conventional alarm contacs 25 through 32 and update the associated contact items
rule "Decode conv. alarm contacts byte5"
	when 
		Item EMA_C400MB5 received update
	then
		var int1 = (EMA_C400MB5.state as DecimalType).intValue
		var int c8 = int1 / 128
		var int c7 = (int1 - c8 * 128) / 64
		var int c6 = (int1 - c8 * 128 - c7 * 64) / 32
		var int c5 = (int1 - c8 * 128 - c7 * 64 - c6 * 32) / 16
		var int c4 = (int1 - c8 * 128 - c7 * 64 - c6 * 32 - c5 * 16) / 8
		var int c3 = (int1 - c8 * 128 - c7 * 64 - c6 * 32 - c5 * 16 - c4 * 8) / 4
		var int c2 = (int1 - c8 * 128 - c7 * 64 - c6 * 32 - c5 * 16 - c4 * 8 - c3 * 4) / 2
		var int c1 = (int1 - c8 * 128 - c7 * 64 - c6 * 32 - c5 * 16 - c4 * 8 - c3 * 4 - c2 * 2) / 1 
		logDebug("C400 rules Decode 2e2e Byte 5: ", int1.toString+": "+c8.toString+c7.toString+c6.toString+c5.toString+c4.toString+c3.toString+c2.toString+c1.toString)

		// c1 = (0x0020) conventional contact 25
		// c2 = (0x0021) conventional contact 26
		// c3 = (0x0022) conventional contact 27
		// c4 = (0x0023) conventional contact 28
		// c5 = (0x0024) conventional contact 29
		// c6 = (0x0025) conventional contact 30
		// c7 = (0x0026) conventional contact 31
		// c8 = (0x0027) conventional contact 32
  		
		if (c1 == 1) {
			sendCommand(EMA_0x0020, CLOSED)
		} else {
			sendCommand(EMA_0x0020, OPEN)
		}
		if (c2 == 1) {
			sendCommand(EMA_0x0021, CLOSED)
		} else {
			sendCommand(EMA_0x0021, OPEN)
		}
		if (c3 == 1) {
			sendCommand(EMA_0x0022, CLOSED)
		} else {
			sendCommand(EMA_0x0022, OPEN)
		}
		if (c4 == 1) {
			sendCommand(EMA_0x0023, CLOSED)
		} else {
			sendCommand(EMA_0x0023, OPEN)
		}
		if (c5 == 1) {
			sendCommand(EMA_0x0024, CLOSED)
		} else {
			sendCommand(EMA_0x0024, OPEN)
		}
		if (c6 == 1) {
			sendCommand(EMA_0x0025, CLOSED)
		} else {
			sendCommand(EMA_0x0025, OPEN)
		}
		if (c7 == 1) {
			sendCommand(EMA_0x0026, CLOSED)
		} else {
			sendCommand(EMA_0x0026, OPEN)
		}
		if (c8 == 1) {
			sendCommand(EMA_0x0027, CLOSED)
		} else {
			sendCommand(EMA_0x0027, OPEN)
		}
end

// Decode status for internal alarm contacs and update the associated contact items
rule "Decode internal alarm contacts byte3"
	when 
		Item EMA_C400MB3 received update
	then
		var int1 = (EMA_C400MB3.state as DecimalType).intValue
		var int c8 = int1 / 128
		var int c7 = (int1 - c8 * 128) / 64
		var int c6 = (int1 - c8 * 128 - c7 * 64) / 32
		var int c5 = (int1 - c8 * 128 - c7 * 64 - c6 * 32) / 16
		var int c4 = (int1 - c8 * 128 - c7 * 64 - c6 * 32 - c5 * 16) / 8
		var int c3 = (int1 - c8 * 128 - c7 * 64 - c6 * 32 - c5 * 16 - c4 * 8) / 4
		var int c2 = (int1 - c8 * 128 - c7 * 64 - c6 * 32 - c5 * 16 - c4 * 8 - c3 * 4) / 2
		var int c1 = (int1 - c8 * 128 - c7 * 64 - c6 * 32 - c5 * 16 - c4 * 8 - c3 * 4 - c2 * 2) / 1 
		logDebug("C400 rules Decode 2e2e Byte 3: ", int1.toString+": "+c8.toString+c7.toString+c6.toString+c5.toString+c4.toString+c3.toString+c2.toString+c1.toString)

		// c1 = (0x0010) enclosure tamper alarm
		// c2 = (0x0011) monitoring contact for siren 1
		// c3 = (0x0012) monitoring contact for siren 2
		// c4 = (0x0013) monitoring contact for optical alarm
		// c5 = (0x0014) battery failure
		// c6 = (0x0015) power outage
		// c7 = (0x0016) n/a
		// c8 = (0x0017) back-to-base transmission device failure
  		
		if (c1 == 1) {
			sendCommand(EMA_0x0010, CLOSED)
			sendCommand(EMA_Sabo_C400, CLOSED)
		} else {
			sendCommand(EMA_0x0010, OPEN)
			sendCommand(EMA_Sabo_C400, OPEN)
		}
		if (c2 == 1) {
			sendCommand(EMA_0x0011, CLOSED)
		} else {
			sendCommand(EMA_0x0011, OPEN)
		}
		if (c3 == 1) {
			sendCommand(EMA_0x0012, CLOSED)
		} else {
			sendCommand(EMA_0x0012, OPEN)
		}
		if (c4 == 1) {
			sendCommand(EMA_0x0013, CLOSED)
		} else {
			sendCommand(EMA_0x0013, OPEN)
		}
		if (c5 == 1) {
			sendCommand(EMA_0x0014, CLOSED)
		} else {
			sendCommand(EMA_0x0014, OPEN)
		}
		if (c6 == 1) {
			sendCommand(EMA_0x0015, CLOSED)
		} else {
			sendCommand(EMA_0x0015, OPEN)
		}
		if (c7 == 1) {
			sendCommand(EMA_0x0016, CLOSED)
		} else {
			sendCommand(EMA_0x0016, OPEN)
		}
		if (c8 == 1) {
			sendCommand(EMA_0x0017, CLOSED)
		} else {
			sendCommand(EMA_0x0017, OPEN)
		}
end

// Decode status byte for monitored area 1 and update the corresponding items

rule "Decode area1 status byte1"
	when 
		Item EMA_C400BS1 received update
	then
		var int1 = (EMA_C400BS1.state as DecimalType).intValue
		var int c8 = int1 / 128
		var int c7 = (int1 - c8 * 128) / 64
		var int c6 = (int1 - c8 * 128 - c7 * 64) / 32
		var int c5 = (int1 - c8 * 128 - c7 * 64 - c6 * 32) / 16
		var int c4 = (int1 - c8 * 128 - c7 * 64 - c6 * 32 - c5 * 16) / 8
		var int c3 = (int1 - c8 * 128 - c7 * 64 - c6 * 32 - c5 * 16 - c4 * 8) / 4
		var int c2 = (int1 - c8 * 128 - c7 * 64 - c6 * 32 - c5 * 16 - c4 * 8 - c3 * 4) / 2
		var int c1 = (int1 - c8 * 128 - c7 * 64 - c6 * 32 - c5 * 16 - c4 * 8 - c3 * 4 - c2 * 2) / 1 
		logDebug("C400 rules Decode 3e3e area1 Status byte: ", int1.toString+": "+c8.toString+c7.toString+c6.toString+c5.toString+c4.toString+c3.toString+c2.toString+c1.toString)
		
		// c1 = (0x0530) disarmed area 1
		// c2 = (0x0531) internally armed area 1
		// c3 = (0x0532) externally armed area 1
		// c4 = (0x0533) ALARM area 1
		// c5 = (0x0534) malfuntion area 1
		// c6 = (0x0535) ready to arm internally area 1
		// c7 = (0x0536) ready to arm externally area 1
		// c8 = (0x0537) Status internal signal horn area 1
  		
		if (c1 == 1) {
			sendCommand(EMA_0x0530, CLOSED)
			sendCommand(EMA_unscharf, OFF)
		} else {
			sendCommand(EMA_0x0530,OPEN)
			sendCommand(EMA_unscharf, ON)
		}
		if (c2 == 1) {
			sendCommand(EMA_0x0531, CLOSED)
			sendCommand(EMA_int_scharf, OFF)
		} else {
			sendCommand(EMA_0x0531,OPEN)
			sendCommand(EMA_int_scharf, ON)
		}
		if (c3 == 1) {
			sendCommand(EMA_0x0532, CLOSED)
			sendCommand(EMA_ext_scharf, OFF)
		} else {
			sendCommand(EMA_0x0532,OPEN)
			sendCommand(EMA_ext_scharf,ON)
		}
		if (c4 == 1) {
			sendCommand(EMA_0x0533, CLOSED)
			sendCommand(EMA_alarm, OFF)
		} else {
			sendCommand(EMA_0x0533,OPEN)
			sendCommand(EMA_alarm,ON)
		}
		if (c5 == 1) {
			sendCommand(EMA_0x0534, CLOSED)
		} else {
			sendCommand(EMA_0x0534,OPEN)
		}
		if (c6 == 1) {
			sendCommand(EMA_0x0535, CLOSED)
			sendCommand(EMA_int_ready, OFF)
		} else {
			sendCommand(EMA_0x0535,OPEN)
			sendCommand(EMA_int_ready,ON)
		}
		if (c7 == 1) {
			sendCommand(EMA_0x0536, CLOSED)
			sendCommand(EMA_ext_ready, OFF)
		} else {
			sendCommand(EMA_0x0536,OPEN)
			sendCommand(EMA_ext_ready,ON)
		}
		if (c8 == 1) {
			sendCommand(EMA_0x0537, CLOSED)
		} else {
			sendCommand(EMA_0x0537,OPEN)
		}
end

Looking at my old code to decode those status bits, I think i need to ask for some leniency from you. That can be coded a bit more elegantly, but Iā€™m a bit lazyā€¦

reserved #5. (yes, it sadly is a rather complex system to integrate)

just saying: bravo for writing this up!
very few people spend time to share their experiences in a howto :slight_smile:
I use a similar (less complex) setup for my Paradox alarm system (based on: Help build binding for Paradox Alarm Panel with IP150)ā€¦ and I still havenā€™t found the time to write a howto also :stuck_out_tongue:

Awesome work. Basically same way an AlarmDecoder(*) work. Iā€™ve integrated with a Honeywell Vista20p through the rs232 directly connected to the Pi, so I didnt quite get why you needed the Moxa Nport 5110? You can also easily replace that with a tiny $2 ESP01 (WiFi) and youā€™ll have a serial-wifi interface, thereā€™s also a lot of other ethernet (wired) alternatives (arduino, pi, c.h.i.p., or simple diy AVRs etc)

Nice job. Very detailed. I would suggest you extend all these how-to into an actual binding :slight_smile: and host your code in github so people can contribute more efficiently.

  • I went with AlarmDecoder anyway since itā€™s a more mature product than my DIY *

Hi Thomas,

thanks for sharing your work. Iā€™m also working on a similar solution. Iā€™ll also have a RS232->IP Converter installed in my Complex 400h, but for the conversion Iā€™m using a node.js program, which does the decoding and also sends the data to OpenHab as a MQTT Message. So, in OpenHAB I only subscribe to a MQTT topic and all the logic is done at the node.js logic which is running in a docker container.

So far, Iā€™m able to read some data (contact sensors), but work is still in progress.

Again, great work and posting!

/Michel

Great to hear. I would love to see your node.js program if only to see how it could have been done different. Feel free to send a PM with the code if you want ;-).

Best,

Thomas

Hi Thomas, so far only my Python version is working, the Javascript based version is under development. Both versions can be found at https://github.com/michelde/telenot/ (node.js version isnā€™t pushed yet)

Regard,
Michel

Just published the node.js version https://github.com/michelde/telenot/

Hello everyone,

I am programming a Telenot binding. Are any of you interested in testing it?
It is based on the experiences of tkuehne and michelm_de.
I look forward to your support.

Regard, Ronny

Hey Ronny,

absolutely. Iā€™m interested to see how you deal with the different system confifgurations and the specific messages they generate.

Best,

Thomas

Hi Thomas,

you can find the sources here:https://github.com/smarthomej/addons/tree/main/bundles/org.smarthomej.binding.telenot

The binding is in an early state, so not every message is supported. Just take a look at the code and tell me what do you think.

Best, Ronny

1 Like

Hi Thomas,

have you already been able to test the binding?
I look forward to your suggestions and ideas.

Best, Ronny

Hi Ronny,

getting to it on the weekend, hopefully. Sorry, itā€™s a bit busy with me having started a new job on the 1st. But I already installed it and have formulated a few ideas of how to recode my old rules most efficiently to make use of the binding.

Cheers,

Tom

One thing I noticed straight away is that one has to create a different thing for each contact. I hate that i.e. about the KNX bninding, because it will mean a cluttered ā€œthingsā€ list all called ā€œTelenot MP-Addressā€. If it all possible, I would greatly prefer if the binding offered a default list of channels directly under the bridge thing. I obviously have no idea if that is even possible, so please forgive me if this suggestion is total rubbish.

Best,

Thomas

Just a small typo I noted while playing around:

In the MB thing, when trying to configure a channel, the UI lists this as an ā€œMP addressā€, where it should probably be an ā€œMB addressā€.

Hi Ronny,

thank you very much. The binding is working smoothly and showing all my mbs for my house. Something I was hoping to see for some years now :slight_smile:

I will test it more in the next weeks. Removing of things was stuck (not yet implemented?). Arming didnā€˜t work as well.

I am now implementing rules for automated opening of shutters when I am opening windows, presence detection for the rooms, etc.

Thank you! What are your next plans?