With plenty of sensor nodes here at JeeLabs, I’ve been exploring and doodling a bit, to see how MQTT could fit into this. As expected, it’s all very simple and easy to do.
The first task at hand is to take all those “OK …” lines coming out of a JeeLink running RF12demo, and push them into MQTT. Here’s a quick solution, using Python for a change:
import serial
import paho.mqtt.client as mqtt
def on_connect(client, userdata, flags, rc):
print("Connected with result code "+str(rc))
#client.subscribe("#")
def on_message(client, userdata, msg):
# TODO pick up outgoing commands and send them to serial
print(msg.topic+""+str(msg.payload))
client = mqtt.Client()
client.on_connect = on_connect
client.on_message = on_message
client.connect("localhost", 1883, 60) # TODO reconnect as needed
client.loop_start()
ser = serial.Serial('/dev/ttyUSB1', 57600)
while True:
# read incoming lines and split on whitespace
items = ser.readline().split()
# only process lines starting with "OK"
if len(items) > 1 and items[0] == 'OK':
# convert each item string to an int
bytes = [int(i) for i in items[1:]]
# construct the MQTT topic to publish to
topic = 'raw/rf12/868-5/' + str(bytes[0])
# convert incoming bytes to a single hex string
hexStr = ''.join(format(i, '02x') for i in bytes)
# the payload has 4 extra prefix bytes and is a JSON string
payload = '"00000010' + hexStr + '"'
# publish the incoming message
client.publish(topic, payload) #, retain=True)
# debugging
print topic, '=', hexStr
Trivial stuff, once you install this MQTT library. Here is a selection of the messages getting published to MQTT – these are for a bunch of nodes running radioBlip and radioBlip2:
raw/rf12/868-5/3 "0000000000038d09090082666a"
raw/rf12/868-5/3 "0000000000038e09090082666a"
raw/rf12/868-5/3 "0000000000038f090900826666"
What needs to be done next, is to decode these to more meaningful results.
Due to the way MQTT works, we can perform this task in a separate process – so here’s a second Python script to do just that. Note that it subscribes and publishes to MQTT:
import binascii, json, struct, time
import paho.mqtt.client as mqtt
# raw/rf12/868-5/3 "0000000000030f230400"
# raw/rf12/868-5/3 "0000000000033c09090082666a"
# avoid having to use "obj['blah']", can use "obj.blah" instead
# see end of http://stackoverflow.com/questions/4984647
C = type('type_C', (object,), {})
client = mqtt.Client()
def millis():
return int(time.time() * 1000)
def on_connect(client, userdata, flags, rc):
print("Connected with result code "+str(rc))
client.subscribe("raw/#")
def batt_decoder(o, raw):
o.tag = 'BATT-0'
if len(raw) >= 10:
o.ping = struct.unpack('<I', raw[6:10])[0]
if len(raw) >= 13:
o.tag = 'BATT-%d' % (ord(raw[10]) & 0x7F)
o.vpre = 50 + ord(raw[11])
if ord(raw[10]) >= 0x80:
o.vbatt = o.vpre * ord(raw[12]) / 255
elif ord(raw[12]) != 0:
o.vpost = 50 + ord(raw[12])
return True
def on_message(client, userdata, msg):
o = C();
o.time = millis()
o.node = msg.topic[4:]
raw = binascii.unhexlify(msg.payload[1:-1])
if msg.topic == "raw/rf12/868-5/3" and batt_decoder(o, raw):
#print o.__dict__
out = json.dumps(o.__dict__, separators=(',',':'))
client.publish('sensor/' + o.tag, out) #, retain=True)
client.on_connect = on_connect
client.on_message = on_message
client.connect("localhost", 1883, 60)
client.loop_forever()
Here is what gets published, as a result of the above three “raw/…” messages:
sensor/BATT-2 {"node":"rf12/868-5/3","ping":592269,
"vpre":152,"tag":"BATT-2","vbatt":63,"time":1435856290589}
sensor/BATT-2 {"node":"rf12/868-5/3","ping":592270,
"vpre":152,"tag":"BATT-2","vbatt":63,"time":1435856354579}
sensor/BATT-2 {"node":"rf12/868-5/3","ping":592271,
"vpre":152,"tag":"BATT-2","vbatt":60,"time":1435856418569}
So now, the incoming data has been turned into meaningful readings: it’s a node called “BATT-2″, the readings come in roughly every 64 seconds (as expected), and the received counter value is indeed incrementing with each new packet.
Using a dynamic scripting language such as Python (or Lua, or JavaScript) has the advantage that it will remain very simple to extend this decoding logic at any time.
But don’t get me wrong: this is just an exploration – it won’t scale well as it is. We really should deal with decoding logic as data, i.e. manage the set of decoders and their use by different nodes in a database. Perhaps even tie each node to a decoder pulled from GitHub?