Get Sensortag TI CC2650STK (10sensors) via Node-Red in openHAB

As described here, I wanted a way to see, if my shutters are horizontally even.

I didn’t find “out-of-the-box” solutions for a wireless tilt sensor, so I tested out the Texas Instruments CC2650STK. There’s three options and I went with the BLE-Version. There’s a Wifi-Version, but the battery time of the BLE is significantly higher of course.

So, what do you need?

  • one or more CC2650STKs of course
  • a raspberry Pi (could be the one running OH2, but I went with a dedicated one as the BLE Signals didn’t go that far)
    I figure, any Hardware capable of BLE and powerful enough for node-red should do.

Prerequisites

This is an excellent guide for preparing a fresh Raspbian (stretch lite in my case) for connecting the sensortags with node-red. You can follow steps 1 through 4 as they’re written from Ian. I decided not to publish the sensor information to the cloud, but instead to use MQTT to connect to OH2. You could of course also use the REST API.

Caveats with the Recipe
What I encountered with a raspberry PI 3 B - that one already has bluetooth on board! - was:

  1. node-RED wasn’t presinstalled (in Strech Lite that was): sudo apt-get install nodered
  2. if you’re not using the bluemix IoT platform, you don’t need to install this node npm install node-red-bluemix-nodes (but it won’t hurt if you do)
  3. configure friendlynames on your sensortagcollector.py with the “-d Option” explained in chapter 7 (neither devices.txt nor the devicenames={} worked for me) the friendlyname is key => this one will be used in nodeRED for the MQTT-Topic in the flow. if it changes on every start you won’t get reliable information.

My thoughts on connecting the sensor to OH2
You could either throw all sensordata into OH2 and let OH2 do all the magic in rules and stuff, meaning to calculate angles, temperatures and all. Or - and this is my approach - you can do all this in node-RED and only update the dedicated items in OH2. As my own OH2 installation is pretty complex already, I’m more convenient with handling all sensor stuff outside OH2 and only update the values in OH2 accordingly.
That’s why:

  • node-RED handles all calculations and complexities
  • node-RED also decides, if new values are sent (or e.g. the hysteres is too small)

node-RED flow
Import this flow instead of the one from the recipe. And be sure to adapt the MQTT-Broker configuration (or replace it with a REST API call).

[{"id":"63e9adae.a89c64","type":"mqtt out","z":"3448d7be.323708","name":"sensortag","topic":"","qos":"2","retain":"false","broker":"64f11e2b.7dfb7","x":580,"y":380,"wires":[]},{"id":"2d6de5dd.9072ba","type":"switch","z":"3448d7be.323708","name":"MQTT send","property":"mqtt","propertyType":"msg","rules":[{"t":"true"}],"checkall":"true","repair":false,"outputs":1,"x":470,"y":220,"wires":[["63e9adae.a89c64"]]},{"id":"756e4631.90cba8","type":"function","z":"3448d7be.323708","name":"JSON String to Object","func":"// reject empty messages\nif ( msg.payload.trim().length === 0 ) {\n    return null;\n}\n\n// This try/catch seems to be needed because\n// occasionally a malformed JSON string is received\n// from the python script.\n// I couldn't see anything wrong with the strings that raise\n// exceptions, but this stops the errors causing\n// irritating problems.\n// UPDATE since adding the split node and the test for\n// zero length (after trimming) above, I haven't\n// seen the json conversion fail. Leaving it here just in case\n\ntry {\n    msg.payload= JSON.parse(msg.payload.trim());\n} catch(err) {\n    node.error(\"Couldn't convert JSON to object:\"+msg.payload);\n    return null;\n}\n\n// Set the data to have come from the device's assigned name\n// The IoT output node must assert the device\n//  type (along with the login credentials for the Pi gateway)!\nmsg.deviceId = msg.payload.devicename;\n\n// There's one JSON per sensor\n// accelerometer, barometer, gyrometer, ...\n// standard topic for now\nmsg.topic=\"sensortag/\" + msg.deviceId + \"/std\";\n\n// calculate current angle of sensor\nif (msg.payload.hasOwnProperty('accelerometer')) {\n    msg.mqtt=false; // no MQTT for now\n    // compare new (rounded on one decimal) angle with rounded old using accelerometer y and z\n    anglenew = Math.round(Math.atan2(msg.payload.accelerometer[1], msg.payload.accelerometer[2]) * 180 / Math.PI * 10) / 10;\n    var angle = context.get('angle')||0;\n\n    // Mounting the sensor head down\n    // -180 degree or 180 degree = 100% horizontal\n    // if more than 5° off, send angle\n    if (anglenew < -175 || anglenew > 175) {\n        // horizontal\n        msg.mqtt = false;\n    } else {\n        // shutter is not horizontal\n        msg.mqtt = true;\n        // don't send updates, if angle doesn't change\n        if (Math.abs(anglenew - angle) < 1) { msg.mqtt=false; }\n    }\n\n    context.set('angle', anglenew);\n    msg.topic=\"sensortag/\" + msg.deviceId + \"/acc\";\n    msg.payload = angle;\n}\n\n// gyroscope\n// actually I don't need it, so it's the JSON in the payload\nif (msg.payload.hasOwnProperty('gyroscope')) {\n    msg.mqtt=true;\n    msg.topic=\"sensortag/\" + msg.deviceId + \"/gyr\";\n}\n\n// Barometer\nif (msg.payload.hasOwnProperty('barometer')) {\n    barnew = msg.payload.barometer[1];\n    barnew = Math.round(barnew * 10) / 10;\n    msg.mqtt=false; // send only if value changed\n    var bar = context.get('bar')||0;\n    if (Math.abs(barnew - bar) > 0.15) { msg.mqtt=true; }\n    context.set('bar', barnew);\n    msg.payload = barnew;\n    msg.topic=\"sensortag/\" + msg.deviceId + \"/bar\";\n}\n\n// Humidity\nif (msg.payload.hasOwnProperty('humidity')) {\n    humnew = JSON.parse(msg.payload.humidity[1]);\n    humnew = Math.round(humnew * 10) / 10;\n    msg.mqtt=false; // send only if value changed\n    var hum = context.get('hum')||0;\n    if (Math.abs(humnew - hum) > 0.15) { msg.mqtt=true; }\n    context.set('hum', humnew);\n    msg.payload = humnew;\n    msg.topic=\"sensortag/\" + msg.deviceId + \"/hum\";\n}\n\n// Light\nif (msg.payload.hasOwnProperty('lightmeter')) {\n    luxnew = JSON.parse(msg.payload.lightmeter);\n    luxnew = Math.round(luxnew * 10) / 10;\n    msg.mqtt=false; // send only if value changed\n    var lux = context.get('lux')||0;\n    if (Math.abs(luxnew - lux) > 2.5) { msg.mqtt=true; }\n    context.set('lux', luxnew);\n    msg.payload = luxnew;\n    msg.topic=\"sensortag/\" + msg.deviceId + \"/lux\";\n}\n\n// IR-Temperature\nif (msg.payload.hasOwnProperty('IRtemperature')) {\n    tmpnew = JSON.parse(msg.payload.IRtemperature[1]);\n    tmpnew = Math.round(tmpnew * 10) / 10;\n    msg.mqtt=false; // send only if value changed\n    var tmp = context.get('tmp')||0;\n    if (Math.abs(tmpnew - tmp) > 0.15) { msg.mqtt=true; }\n    context.set('tmp', tmpnew);\n    msg.payload = tmpnew;\n    msg.topic=\"sensortag/\" + msg.deviceId + \"/tmp\";\n}\n\nreturn msg;\n","outputs":1,"noerr":0,"x":413.7000274658203,"y":61,"wires":[["5d4c24ca.8e753c","2d6de5dd.9072ba"]]},{"id":"5e9d0cff.c96da4","type":"split","z":"3448d7be.323708","name":"","splt":"\\n","spltType":"str","arraySplt":"1","arraySpltType":"len","stream":false,"addname":"","x":230,"y":60,"wires":[["756e4631.90cba8"]]},{"id":"5d4c24ca.8e753c","type":"debug","z":"3448d7be.323708","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","x":690,"y":60,"wires":[]},{"id":"f7dc62d0.62e9b","type":"daemon","z":"3448d7be.323708","name":"Connect to Sensortags","command":"/usr/bin/sudo","args":"python sensortagcollector.py -m accelerometer barometer humidity lightmeter","cr":false,"redo":true,"op":"string","x":166.70001220703125,"y":167,"wires":[["5e9d0cff.c96da4"],[],[]]},{"id":"64f11e2b.7dfb7","type":"mqtt-broker","z":"","name":"YOURNAME","broker":"YOURBROKER","port":"1883","clientid":"nodered1","usetls":false,"compatmode":true,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","closeTopic":"","closeQos":"0","closePayload":"","willTopic":"","willQos":"0","willPayload":""}]

OH2 items
my items-configuration in openHAB2, which listens to the MQTT-Topics from the flow:

/* sensortag01 */
Number		Sensortag_Acc_01	"ST01 angle [%.1f °]"		{ expire="60s,state=180",mqtt="<[mqttoh2:sensortag/st01/acc:state:default]" }
Number		Sensortag_Tmp_01	"ST01 temperature [%.1f °C]"	{ mqtt="<[mqttoh2:sensortag/st01/tmp:state:default]" }
Number		Sensortag_Bar_01	"ST01 barometer [%.1f hP]"	{ mqtt="<[mqttoh2:sensortag/st01/bar:state:default]" }
Number		Sensortag_Hum_01	"ST01 humidity [%.1f %%]"	{ mqtt="<[mqttoh2:sensortag/st01/hum:state:default]" }
Number		Sensortag_Lux_01	"ST01 light [%.1f lux]"		{ mqtt="<[mqttoh2:sensortag/st01/lux:state:default]" }

Number		Sensortag_Acc_02	"ST02 angle [%.1f °]"		{ expire="60s,state=180",mqtt="<[mqttoh2:sensortag/st02/acc:state:default]" }
Number		Sensortag_Tmp_02	"ST02 temperature [%.1f °C]"	{ mqtt="<[mqttoh2:sensortag/st02/tmp:state:default]" }
Number		Sensortag_Bar_02	"ST02 barometer [%.1f hP]"	{ mqtt="<[mqttoh2:sensortag/st02/bar:state:default]" }
Number		Sensortag_Hum_02	"ST02 humidity [%.1f %%]"	{ mqtt="<[mqttoh2:sensortag/st02/hum:state:default]" }
Number		Sensortag_Lux_02	"ST02 light [%.1f lux]"		{ mqtt="<[mqttoh2:sensortag/st02/lux:state:default]" }

...

I hope, it gives you some ideas on how to integrate Sensortag information in openHAB. Any suggestions or improvements - just go ahead! :wink:

1 Like

What I have to dive deeper in, is how I could activate “wake on movement” on the chip. Presently it sends the information frequently, with the wake on movement / WOM functionality I hope to save battery as I only need information on movement … :wink: