Requesting working example of RFLink <-> OpenHab via MQTT

rflink
Tags: #<Tag:0x00007fadec96fdb0>

(Martin) #1

I am looking into the RFLink Gateway and how to integrate to OH2.

I have set up the RFLink Shield and ArduinoMega - and made a small MQTT gateway on an ESP8266.

So, now I am receiving MQTT messages in OH2 with raw RFLink data in it.

Does anyone have a working rules/script setup to share where you parse the raw data from RFLink into something usable ?

I have a weatherstation which I can see RFLink receives messages from, but I am struggling to place the readings into something useable - like temperature, humidity, rain, wind etc.

Currently, I am not looking to send messages back to RF-devices - just reading sensor data…

I have looked at the binding for RFLink - but that seem to expect serial input - and that is not my thing :wink:


(Rich Koshak) #2

Perhaps if you posted the format of the messages and maybe one or two relevant examples we could provide some suggestions.


(Martin) #3

Yep - here goes:

The raw message comes in one string looking like this (below are 4 seperate messages):

20;AC;Cresta;ID=3601;TEMP=00f0;HUM=57;BAT=OK;
20;AD;Cresta;ID=3601;TEMP=00ef;HUM=57;BAT=OK
20;AE;Cresta;ID=3601;TEMP=00f0;HUM=57;BAT=OK;
20;AF;Cresta;ID=8001;RAIN=05b0;BAT=OK;

the first two numbers are just an identifier (20=receive, 10 would be transmit)
the next two are an incremental hex number per message
then comes the name of the vendor of the device
then an ID, prefixed by “ID=” which I believe identifies the type of device from this vendor (or perhaps it is even more unique, I havent been able to tell)
Then som data - also comma separated - in my case here;
TEMP which comes with a 4 digit hex value (right now it is around 24 degrees here)
HUM which seem to be just the rounded % of humidity
BAT=OK which seems to say that battery is - OK ?
other examples I have seen are RAIN - but I dont know what the value tells me ? - perhaps total mm rain collected since start ?

I have started on a rule which will parse this out - but it is tedious work and I had hoped to see someone who already had done this.


(Rich Koshak) #4

OK, we are dealing with hex encoded values but do you know what sort of value they represent? Mainly I’m concerned about the TEMP and RAIN. Is it a floating point number or did they do something else (e.g. it is an integer but they expect you to divide by 10 or 100).

Assuming that the device is returning celsius I’m going to guess it is encoding one decimal place and they expect you to do the divide by 10.

This is because:

00f0 = 240
00ef = 239

If this is celsius than 24.0 degrees and 23.9 degrees are reasonable values.

HUM is almost certainly percent and we will never see anything except parsable numbers from it.

RAIN is probably expecting you to divide by something too unless you have received almost 1.5 m of rain since installing this sensor, which could be reasonable. In all likelihood, you will have to observe how this number changes over time corresponding with observations of rainfall to figure how this number is being reported.

NOTE: 05b0 = 1456

OK, let’s start parsing the values.

rule "Parse RFLink messages"
when
    Item RFLink_Messages received update
than
    // split the message into parts on the ';'
    val parts = RFLink_Messages.state.toString.split(';')

    // the parts of the message start from zero, the replace removes the ID= from the message
    val id = parts.get(3).replace("ID=",'')

    // look at the fifth part of the message to see which type of message it is and parse accordingly    
    switch parts.get(4) {

        // process a TEMP/HUM/BAT message
        case parts.get(4).startsWith("TEMP"): {
            // Get the TEMP, I broke it down into steps for legibility
            val tempHex = parts.get(4).replace("TEMP=", '')
            val rawTemp = new java.math.BigDecimal(Integer::parseInt(tempHex, 16))
            val adjustedTemp = rawTemp / 10.0

            // assumes you named your Temp Item for this device 3601_Temp, you can do some additional 
            // code to change the 3601 to something more friendly but the point is we use the id to construct
            // the name of the Item to update.
            postUpdate(id+"_Temp", adjustedTemp.toString)

            // Get the Humidity, all on one line for brevity
           val humidity = new java.math.BigDecimal(Integer::parseInt(parts.get(5).replace("HUM=", ''), 16))
           postUpdate(id+"_Hum", humidity.toString)

            // Get the Battery
            val bat = if(parts.get(5) == "BAT=OK") "ON" else "OFF"
            postUpdate(id+"_Bat", bat)
        }

        // process a RAIN/BAT message
        case parts.get(4).startsWith("RAIN") {
            // Get the rain
           val rain = java.math.BigDecimal(Integer::parseInt(parts.get(4).replace("RAIN=", ''), 16))
           // do more math based on what this number represents
           postUpdate(id+"_Rain", rain.toString)

           // Get the battery
           val bat = if(parts.get(5) == "BAT=OK") "ON" else "OFF"
           postUpdate(id+"_Bat", bat)
        }
        default: lofWarn("RFLink", "Received an unknown message type: " + RFLink_Messages.state.toString)
    }
end

I just typed in the above and there are likely errors. Dealing with numerical values like I have is prone to cause the Rules engine problems. I tried to write it in such a way as to avoid most of the potential pitfalls but I could have caused problems.

If it doesn’t work be sure to look at the logs for errors and add lots of logging to find the line it doesn’t like and what everything is equal to.

One final question, how quickly do these messages come in?

One final note to others on the forum. This is the one case where I think it is acceptable and appropriate to use the Actions for sendCommand and postUpdate instead of the Item methods. We don’t have a reference to the Items and there is no need to go through the extra work of pulling them out of a Group like you’ve seen me do all over the place.


Type Conversions
(Jürgen Wissing) #5

Hi, here is a link to the RFLink protocol that is useful to decode the messages.
http://www.rflink.nl/blog2/protref
I’m using a python script to decode the messages, format them into JSON messages and sending them via MQTT to openHAB.
The python script:

# -*- coding: utf-8 -*-
import sys
from serial import *
import io
import struct
import binascii
from bitstring import *
import time
import json
from datetime import datetime
import signal

localtime   = time.localtime()
timeString  = time.strftime("%Y%m%d%H%M%S", localtime)

import paho.mqtt.client as paho

DEBUG = True #False

RECONNECT_DELAY_SECS = 2

def on_connect(mqttc, userdata, flags, rc):
    if DEBUG:
        dt = '{:%Y-%m-%d %H:%M:%S}'.format(datetime.now())
        print('%s Connect %s %s %s' % (dt, str(userdata), str(flags), str(rc) ))
    return

def on_disconnect(client, userdata, rc):
    if rc != 0:
        dt = '{:%Y-%m-%d %H:%M:%S}'.format(datetime.now())
        print("%s Unexpected disconnection. RC = %s" % (dt, rc))
    while rc != 0:
        client.reinitialise(client_id="rflinkgateway")
        time.sleep(RECONNECT_DELAY_SECS)
        dt = '{:%Y-%m-%d %H:%M:%S}'.format(datetime.now())
        print("%s Reconnecting..." % (dt))
        try:
            signal.alarm(5)
            rc = client.connect("pine64.fritz.box", keepalive=30)
            signal.alarm(0)
        except Exception as e:
            dt = '{:%Y-%m-%d %H:%M:%S}'.format(datetime.now())
            print('%s Exception on reconnect' % (dt))
            print(e)
            raise e
        if rc != 0:
            dt = '{:%Y-%m-%d %H:%M:%S}'.format(datetime.now())
            print("%s Reconnect failed. RC = %s" % (dt, rc))
        client.loop_start()
    return

def on_publish(client, userdata, mid):
    return

def main():
    dt = '{:%Y-%m-%d %H:%M:%S}'.format(datetime.now())
    print("%s Start main" % (dt))

    ser = Serial(port='/dev/ttyACM0',baudrate=57600,timeout=4)
    sio = io.TextIOWrapper(io.BufferedRWPair(ser, ser))
    mqttc = paho.Client(client_id="rflinkgateway")
    mqttc.on_connect = on_connect
    mqttc.on_disconnect = on_disconnect
    mqttc.on_publish = on_publish
    mqttc.connect("pine64.fritz.box", keepalive=30)
    mqttc.loop_start()
    while True:
        try:
            line = sio.readline()
            if len(line) > 0:
                if DEBUG:
                    dt = '{:%Y-%m-%d %H:%M:%S}'.format(datetime.now())
                    print("%s <%s>:%s" % (dt, len(line), line))
                if ';' in line:
                    _parts = line.split(';')
                    if DEBUG:
                        dt = '{:%Y-%m-%d %H:%M:%S}'.format(datetime.now())
                        _p = str(_parts)
                        print('%s %s' % (dt, _p))
                    if _parts[0] != '20':
                        continue
                    if _parts[2].startswith('Nodo RadioFrequencyLink'):
                        continue
                    if _parts[2] == 'Alecto V5':
                        if 'ID' in _parts[3]:
                            if _parts[3].split('=')[1] != '00c0':
                                continue
                        else:
                            continue
                        if '=' not in _parts[4]:
                            continue
                        if _parts[4].split('=')[0] != 'TEMP':
                            continue
                        _tempx = _parts[4].split('=')[1]
                        _temp = '0x'+ _tempx
                        a = BitStream(_temp)
                        temp = a.read("intbe:16")
                        if temp < 0:
                            temp = temp & 0b0111111111111
                            temp = temp * -1
                        if '=' not in _parts[5]:
                            continue
                        if _parts[5].split('=')[0] != 'RAIN':
                            continue
                        _rainx = _parts[5].split('=')[1]
                        _rain = '0x'+ _rainx
                        a = BitStream(_rain)
                        rain = a.read("intbe:16")
                        if DEBUG:
                            dt = '{:%Y-%m-%d %H:%M:%S}'.format(datetime.now())
                            tempstr = "%s %1.1f C %1.1f mm" % (dt, temp/10, rain/10)
                            print(tempstr)
                        data = {}
                        data['temp'] = temp/10
                        data['rain'] = rain/10
                        payload = json.dumps(data)
                        (result, mid) = mqttc.publish('/sensors/weather', payload, qos=1)
                    if _parts[2] == 'Kaku':
                        if '=' not in _parts[5]:
                            continue
                        if _parts[5].split('=')[0] != 'CMD':
                            continue
                        if _parts[3].split('=')[0] != 'ID':
                            continue
                        if _parts[3].split('=')[1] != '44':
                            continue
                        _cmd = _parts[5].split('=')[1]
                        if _cmd == 'ON':
                            _status = 'CLOSED'
                        elif _cmd == 'OFF':
                            _status = 'OPEN'
                        else:
                            continue
                        _data = {}
                        _data['status'] = _status
                        payload = json.dumps(_data)
                        (result, mid) = mqttc.publish('/sensors/window/office', payload, qos=1)
                    if _parts[2] == 'Oregon Rain2':
                        if '=' not in _parts[5]:
                            continue
                        if _parts[3].split('=')[0] != 'ID':
                            continue
                        if _parts[3].split('=')[1] != '2A68':
                            continue
                        if _parts[5].split('=')[0] != 'RAIN':
                            continue
                        _rainx = _parts[5].split('=')[1]
                        _rain = '0x'+ _rainx
                        a = BitStream(_rain)
                        rain = a.read("intbe:16")
                        if DEBUG:
                            dt = '{:%Y-%m-%d %H:%M:%S}'.format(datetime.now())
                            tempstr = "%s %1.1f mm" % (dt, rain/10)
                            print(tempstr)
                        data = {}
                        data['rain'] = rain/10
                        payload = json.dumps(data)
                        (result, mid) = mqttc.publish('/sensors/rain', payload, qos=1)
            continue
        except KeyboardInterrupt:
            mqttc.disconnect()
            mqttc.loop_stop()
            ser.close()
            return False
        continue
if __name__ == "__main__":
    if len(sys.argv) < 2:
        while True:
            try:
                result = main()
                dt = '{:%Y-%m-%d %H:%M:%S}'.format(datetime.now())
                print("%s Return from main RC=%s" % (dt, str(result)))
                if result is False:
                    break
            except Exception as e:
                print(e)
                raise
    else:
        sensor = sys.argv[1]
        temp = sys.argv[2]
        publish(sensor,temp)

and the relevant definition of an item:

Number Niederschlag            "Niederschlag [%.1f mm]"	<rain> (Weather) {mqtt="<[mosquitto:/sensors/rain:state:JSONPATH($.rain)]"}

(Martin) #6

Thanks @rlkoshak and @Jue_Wis - this is great input. I am building this as rules in Openhab parsing and evaluating from the raw data which is sent over from RFLink by MQTT. I will post a working setup when I am through…

Right now I need some help regarding calculation of difference - but will post a seperate question on that.