Many sensors, especially those that are ‘passive’ or continuously advertising, communicate over Bluetooth Low Energy (BLE). Here at the office, we have a combination thermometer-and-hydrometer that communicates over iBeacon, Apple’s BLE format. The only problem is that (as of writing this post) Tulip hardware can’t connect to Bluetooth devices - or so I thought.
I grabbed this module off of Amazon, and connected it to my Edge MC’s UART port. All pins on the EMC run at 5 volts - the HM10 can take 5v of power, but I needed to put in a voltage divider to pull EMC’s Tx signal down to 3.3v:
The HM10 can be controlled with AT commands (a full list of which can be found here) - but, it doesn’t wait for a new line before running a command. So, commands must be sent from Node-RED as a ‘package’ (rather than, for example, being able to type them in through a screen session). Sending AT returns the expected response of OK, and we’re good to go.
The Tilt iBeacon sensor is non-connectable - it’ll broadcast its information about once a second, and any nearby device can pick it up. AT+DISI? starts a scan for nearby BLE devices from the EMC, and we can see the result in Node-RED. The flow I used is here and attached at the end of this post, but I pass the scan results through a filter to pull out only the Tilt’s data, identified by UUID:
The raw output of the Tilt comes through as follows:
DISC:4C000215:A495BB30C5B14B44B5121370F02D74DE:004603E347:F0E628CFC757:-063
Reading as an ASCII string, the message includes all the info we need separated by colons:
- DISC: Begin response for a newly discovered device
- 4C000215: Manufacturer & device info
- A495…: The Tilt sensor’s UUID
- 0046…: The actual measured data
- F0E6…: The Tilt sensor’s MAC Address
- -063: Signal RSSI as measured from the HM10 module, in dB
This blog post details each byte of the full message, read instead in binary/hex.
Of the measured data part of this string, the first two bytes/four digits (here, 0046) represent the temperature as measured in degrees Fahrenheit, and the next two bytes/four digits (here, 03E3) represent the specific gravity. Both are hex values, so they need to be converted to decimal (and, in SpG’s case, divided by 1,000).
Once these are converted in a Function node, they’re ready to send over to Tulip! By including both as an object in the Payload, rather than their own headers, the tulip-tag node can automatically recognize and bind the values to their Tulip machine attributes:
The only questionable part is that since the Tilt (and iBeacon generally) doesn’t support a dedicated connection, I loop the AT+DISI? command to simulate a ‘continuous’ monitoring method on the HM10. There’s a bit of luck needed or the scan to run at the same time as the sensor’s advertisement, but as this’ll be used to monitor a fermentation vessel over the course of a few months the current rate of a successful read every minute or two is definitely sufficient.
Next steps are to try to miniaturize this; If I can shrink the size of the voltage divider with some careful soldering, I can contain this all on the back of the EMC with some double-sided tape. This HM10 module supports everything Bluetooth 4.0 as well, so we can start exploring some other options, too (some of the shiny new toys that REEKON showed off at CES last week would be pretty great…)
The flow can be found below for copy and paste into the Node-RED editor -
Flow JSON
[
{
“id”: “3ea97d7.5efe582”,
“type”: “group”,
“z”: “7f0d15a1.52e9bc”,
“style”: {
“stroke”: “#b0a99e”,
“stroke-opacity”: “1”,
“fill”: “none”,
“fill-opacity”: “1”,
“label”: true,
“label-position”: “nw”,
“color”: “#b7b0a6”
},
“nodes”: [
“2d151843.547a78”,
“53cf25fa.1a471c”,
“5c9c7ed7.190d”,
“d12582be.bda8”,
“6b5a3f68.51623”,
“cc94af16.6df02”,
“6d0d60c8.b02d1”,
“4ecc21e5.90bcb”,
“b1947eb5.5a6c”,
“3f8fb89.5de9448”,
“bd5c99f1.8e4238”,
“bf0d2abc.e9ab58”,
“125cba6f.cd4b66”
],
“x”: 734,
“y”: 79,
“w”: 772,
“h”: 502
},
{
“id”: “2d151843.547a78”,
“type”: “looptimer-advanced”,
“z”: “7f0d15a1.52e9bc”,
“g”: “3ea97d7.5efe582”,
“duration”: “3”,
“units”: “Second”,
“maxloops”: “1000”,
“maxtimeout”: “3”,
“maxtimeoutunits”: “Hour”,
“name”: “”,
“x”: 1040,
“y”: 220,
“wires”: [
[
“6b5a3f68.51623”
],
]
},
{
“id”: “53cf25fa.1a471c”,
“type”: “inject”,
“z”: “7f0d15a1.52e9bc”,
“g”: “3ea97d7.5efe582”,
“name”: “Start”,
“props”: [
{
“p”: “payload”
}
],
“repeat”: “”,
“crontab”: “”,
“once”: false,
“onceDelay”: 0.1,
“topic”: “”,
“payload”: “AT+DISI?”,
“payloadType”: “str”,
“x”: 830,
“y”: 200,
“wires”: [
[
“2d151843.547a78”
]
]
},
{
“id”: “5c9c7ed7.190d”,
“type”: “switch”,
“z”: “7f0d15a1.52e9bc”,
“g”: “3ea97d7.5efe582”,
“name”: “Filter Results”,
“property”: “payload”,
“propertyType”: “msg”,
“rules”: [
{
“t”: “cont”,
“v”: “A495BB30C5B14B44B5121370F02D74DE”,
“vt”: “str”
},
{
“t”: “cont”,
“v”: “DISCE”,
“vt”: “str”
},
{
“t”: “cont”,
“v”: “00000000”,
“vt”: “str”
},
{
“t”: “else”
}
],
“checkall”: “true”,
“repair”: false,
“outputs”: 4,
“x”: 1030,
“y”: 400,
“wires”: [
[
“3f8fb89.5de9448”
],
[
“4ecc21e5.90bcb”
],
[
“b1947eb5.5a6c”
],
[
“cc94af16.6df02”
]
]
},
{
“id”: “d12582be.bda8”,
“type”: “debug”,
“z”: “7f0d15a1.52e9bc”,
“g”: “3ea97d7.5efe582”,
“name”: “debug”,
“active”: true,
“tosidebar”: true,
“console”: false,
“tostatus”: false,
“complete”: “true”,
“targetType”: “full”,
“statusVal”: “”,
“statusType”: “auto”,
“x”: 1410,
“y”: 320,
“wires”:
},
{
“id”: “6b5a3f68.51623”,
“type”: “serial out”,
“z”: “7f0d15a1.52e9bc”,
“g”: “3ea97d7.5efe582”,
“name”: “send AT+DISCI?”,
“serial”: “c8241c0.7f214e8”,
“x”: 1270,
“y”: 200,
“wires”:
},
{
“id”: “cc94af16.6df02”,
“type”: “debug”,
“z”: “7f0d15a1.52e9bc”,
“g”: “3ea97d7.5efe582”,
“name”: “show all”,
“active”: false,
“tosidebar”: true,
“console”: false,
“tostatus”: false,
“complete”: “true”,
“targetType”: “full”,
“statusVal”: “”,
“statusType”: “auto”,
“x”: 1220,
“y”: 540,
“wires”:
},
{
“id”: “6d0d60c8.b02d1”,
“type”: “serial in”,
“z”: “7f0d15a1.52e9bc”,
“g”: “3ea97d7.5efe582”,
“name”: “receive”,
“serial”: “c8241c0.7f214e8”,
“x”: 830,
“y”: 400,
“wires”: [
[
“5c9c7ed7.190d”
]
]
},
{
“id”: “4ecc21e5.90bcb”,
“type”: “debug”,
“z”: “7f0d15a1.52e9bc”,
“g”: “3ea97d7.5efe582”,
“name”: “Status messages”,
“active”: false,
“tosidebar”: true,
“console”: false,
“tostatus”: false,
“complete”: “true”,
“targetType”: “full”,
“statusVal”: “”,
“statusType”: “auto”,
“x”: 1250,
“y”: 460,
“wires”:
},
{
“id”: “b1947eb5.5a6c”,
“type”: “debug”,
“z”: “7f0d15a1.52e9bc”,
“g”: “3ea97d7.5efe582”,
“name”: “Non-iBeacons”,
“active”: false,
“tosidebar”: true,
“console”: false,
“tostatus”: false,
“complete”: “true”,
“targetType”: “full”,
“statusVal”: “”,
“statusType”: “auto”,
“x”: 1240,
“y”: 500,
“wires”:
},
{
“id”: “3f8fb89.5de9448”,
“type”: “function”,
“z”: “7f0d15a1.52e9bc”,
“g”: “3ea97d7.5efe582”,
“name”: “parseInts”,
“func”: “msg.raw = msg.payload;\nregex = msg.payload.match(":.{10}:");\nmsg.data = regex.pop();\n\ntempraw = msg.data.substr(1,4); \ntempF = parseInt(tempraw, 16);\n\nspecGraw = msg.data.substr(5,4) \nspecG = parseInt(specGraw, 16)/1000\n\nother = msg.data.substr(9,2)\nmsg.other = parseInt(other,8)\n\n\nmsg.payload = {\n "tempF" : tempF, \n "specG" : specG \n}\n\nreturn msg;”,
“outputs”: 1,
“noerr”: 0,
“initialize”: “”,
“finalize”: “”,
“libs”: ,
“x”: 1220,
“y”: 380,
“wires”: [
[
“d12582be.bda8”,
“125cba6f.cd4b66”
]
]
},
{
“id”: “bd5c99f1.8e4238”,
“type”: “comment”,
“z”: “7f0d15a1.52e9bc”,
“g”: “3ea97d7.5efe582”,
“name”: “Continuous Operation”,
“info”: “”,
“x”: 860,
“y”: 120,
“wires”:
},
{
“id”: “bf0d2abc.e9ab58”,
“type”: “inject”,
“z”: “7f0d15a1.52e9bc”,
“g”: “3ea97d7.5efe582”,
“name”: “Stop”,
“props”: [
{
“p”: “payload”
}
],
“repeat”: “”,
“crontab”: “”,
“once”: false,
“onceDelay”: 0.1,
“topic”: “”,
“payload”: “STOP”,
“payloadType”: “str”,
“x”: 830,
“y”: 240,
“wires”: [
[
“2d151843.547a78”
]
]
},
{
“id”: “125cba6f.cd4b66”,
“type”: “tulip-tag”,
“z”: “7f0d15a1.52e9bc”,
“g”: “3ea97d7.5efe582”,
“name”: “”,
“tagList”: “2b72556b.f616fa”,
“staticTag”: false,
“tagId”: “”,
“x”: 1420,
“y”: 380,
“wires”: [
]
},
{
“id”: “c8241c0.7f214e8”,
“type”: “serial-port”,
“serialport”: “/dev/ttyS1”,
“serialbaud”: “9600”,
“databits”: “8”,
“parity”: “none”,
“stopbits”: “1”,
“waitfor”: “”,
“dtr”: “none”,
“rts”: “none”,
“cts”: “none”,
“dsr”: “none”,
“newline”: “+”,
“bin”: “false”,
“out”: “char”,
“addchar”: “”,
“responsetimeout”: “10000”
},
{
“id”: “2b72556b.f616fa”,
“type”: “tulip-tag-list”,
“name”: “Tilt list”,
“tags”: [
{
“id”: “tempF”,
“label”: “Temperature”,
“type”: “float”
},
{
“id”: “specG”,
“label”: “Specific Gravity”,
“type”: “float”
}
]
}
]