A few weeks ago I received my RuuviTag. It is a small Bluetooth BLE device which contains a few sensors, including temperature, relative air humidity, air pressure and acceleration. The hardware and the software are both Open Source.
It features a Bosch Sensortec BME280 environmental sensor which can also be found in several wearables.
Node-RED on C.H.I.P
To get the data from the Ruuvi Tag to my Dashboard, I've installed Node-RED on a C.H.I.P microcomputer. This microcomputer features WLAN and Bluetooth, so it's perfect for this use case.
The Node-RED flow looks like this:
(find the JSON code below)
An important node is the function which assigns names to the Ruuvi Tags so they can be easily identified in the database.
For accessing BLE from Node-RED I'm using the node-red-contrib-noble node. To allow access as non-root to bluetooth, run:
sudo setcap cap_net_raw+eip $(eval readlink -f `which node`)
The Ruuvi node can be found here: ojousima/node-red. The InfluxDB node here: node-red-contrib-influxdb.
More information can be found on RuuviLab.
InfluxDB and Grafana
The data is sent to InfluxDB and the visualized by Grafana which is displayed on a touchscreen powered by a RaspberryPI mounted to the wall on my home.
Node-RED Flow JSON export
[
{
"id": "5d49fc45.92f814",
"type": "inject",
"z": "3f454907.eaa58e",
"name": "Start BLE scan",
"topic": "startBLEScan",
"payload": "{ \"scan\": true }",
"payloadType": "json",
"repeat": "120",
"crontab": "",
"once": true,
"x": 252,
"y": 91,
"wires": [
[
"9f8d0dc1.46975"
]
]
},
{
"id": "9f8d0dc1.46975",
"type": "scan ble",
"z": "3f454907.eaa58e",
"uuids": "",
"duplicates": false,
"name": "",
"x": 554,
"y": 92,
"wires": [
[
"5be2f753.9ef68"
]
]
},
{
"id": "5be2f753.9ef68",
"type": "ruuvitag",
"z": "3f454907.eaa58e",
"name": "",
"x": 568,
"y": 293,
"wires": [
[
"cabc4ec8.42d408"
]
]
},
{
"id": "9ffcb1ad.807238",
"type": "inject",
"z": "3f454907.eaa58e",
"name": "Stop BLE scan",
"topic": "stopBLEScan",
"payload": "{ \"scan\": false }",
"payloadType": "json",
"repeat": "120",
"crontab": "",
"once": true,
"x": 252,
"y": 140,
"wires": [
[
"ffc6e9b.2827198"
]
]
},
{
"id": "ffc6e9b.2827198",
"type": "delay",
"z": "3f454907.eaa58e",
"name": "",
"pauseType": "delay",
"timeout": "10",
"timeoutUnits": "seconds",
"rate": "1",
"nbRateUnits": "1",
"rateUnits": "second",
"randomFirst": "1",
"randomLast": "5",
"randomUnits": "seconds",
"drop": false,
"x": 430,
"y": 222,
"wires": [
[
"9f8d0dc1.46975"
]
]
},
{
"id": "860f2dd9.03ea38",
"type": "debug",
"z": "3f454907.eaa58e",
"name": "",
"active": false,
"console": "false",
"complete": "true",
"x": 858.5,
"y": 315,
"wires": [],
"inputLabels": [
"Input"
]
},
{
"id": "cabc4ec8.42d408",
"type": "function",
"z": "3f454907.eaa58e",
"name": "Device naming",
"func": "var device = null;\nif (msg.peripheralUuid == \"f93fb3fdXXXX\") {\n device = \"livingroom\";\n} else if (msg.peripheralUuid == \"da677de0YYY\") { \n device = \"outside\";\n} else if (msg.peripheralUuid == \"d6b542a8ZZZZ\") { \n device = \"bedroom\";\n}\n\nvar formated = {\n payload: [ JSON.parse(msg.payload), { location: device } ]\n}\nreturn formated;",
"outputs": 1,
"noerr": 0,
"x": 663.5,
"y": 417,
"wires": [
[
"860f2dd9.03ea38",
"a8619034.a61de"
]
]
},
{
"id": "a8619034.a61de",
"type": "influxdb out",
"z": "3f454907.eaa58e",
"influxdb": "2ef027b9.6d1718",
"name": "",
"measurement": "ruuvi",
"precision": "",
"retentionPolicy": "",
"x": 873.5,
"y": 472,
"wires": []
},
{
"id": "2ef027b9.6d1718",
"type": "influxdb",
"z": "",
"hostname": "influxdb.example.com",
"port": "443",
"protocol": "http",
"database": "sensors",
"name": "influxdb",
"usetls": true,
"tls": "9cad81c7.ce811"
},
{
"id": "9cad81c7.ce811",
"type": "tls-config",
"z": "",
"name": "",
"cert": "",
"key": "",
"ca": "",
"certname": "",
"keyname": "",
"caname": "",
"verifyservercert": true
}
]