April 19, 2017

RuuviTag

I backed Ruuvi sometime late last year for a RuuviTag, a battery powered Bluetooth LE microcontroller with an onboard temperature and humidity sensor. As soon as I saw this project I had several ideas. And because I run openHAB at home, naturally, I’d want to collate this date into here for usage in my home automation system. I finally got it in the mail last week, and what surprised me was that they had teamed up the the guy who made espruino and had him port it to the Ruuvi! Javascript on a microcontroller. Pretty impressive stuff. Me and javascript have had some okay times, so I was comfortable that they had javascript here instead of C, which is what Ruuvi natively runs

So battery powered BLE temperature sensor, that runs javascript, and a raspberry pi 3 that is running openhab. Pretty much a perfect mix. Here is how I got them talking to each other

You may be looking at the RuuviTag setup page and see that the default weather station firmware actually works for both RuuviTags. At the time of getting my Ruuvi, it only supported the RuuvTag+, so I looked at using the espruino firmware to get what I needed to work.

Ruuvi

  • Following the instructions here we need to flash the espruino firmware to ruuvi. Quick overview, but the video they have here is quite good
    • Download nRF connect app
    • Put Ruuvi into boot mode (hold R + B, let go of R while still holding B until red light comes and stays on)
    • Connect to the ruuvi with nRF connect
    • Press the “DFU” button that comes up in the app once you are connected, and select the espruino firmware zip file
    • wait a little while
  • Now follow the instructions here to get the IDE up and running to connect to Ruuvi
  • Once you are able to get into the IDE and connect to ruuvi, upload this little snippet. Essentially it gathers the temp data every 60 seconds, converts it to a uint8 array, and then advertises it over BLE
    • after following this guide, you may be asking why I’m not using the ruuvitag package to get all the data var Ruuvitag = require("Ruuvitag");. It seems that this package does not work with the RuuviTag basic. I need to raise this on the forums to see when it will be supported. But for now we can just use the code you would use for the puck.js on the ruuvi, and it does the same job
    • Links to the espruino forum that helped me get this working one, two, three
function tempTo(temp) {
  var f = parseFloat(temp.toFixed(2)) * 100;
  if (tempTo.buf === undefined) tempTo.buf = new Uint8Array(2);
  tempTo.buf[0] = f & 0xff;
  tempTo.buf[1] = f >> 8 & 0xff;
  return tempTo.buf;
}

setInterval(function(fn){
  console.log(E.getTemperature());
},60000);

setInterval(function() {
  NRF.setAdvertising({
    0x1809 : tempTo(E.getTemperature())
  });
}, 60000);

RaspberryPi 3

Now onto the server side. I have a raspberry pi 3 running openhab. This is perfect as it has bluetooth built in. There were a few options to make this work, if you head over to the ruuvi forums there is a java and python module to scan the ruuvi’s and get their data. I was actually looking for something more like this. This is a BLE -> mqtt bridge, that will scan for data on ruuvi (or Puckjs) and then publuish that to a mqtt service. EspruinoHub didn’t quite fit my setup, it is apparently a 2 way bridge, but I only really wanted a 1 way (getting the data). I also did not want to install nodered and all the other dependancies for it. So I decided to use this as a basis and write something else (source here) It does esentially the same job, scans the BLE range and gets any data being advertised, and then publishes it to a mqtt stream. My openHAB server subscribes to these streams to get the data into the system. One use case I have for this is to monitor a bedroom temperature, and then if it gets to cold overnight, turn on the heating. Keep monitoring the temperature and if it gets a bit too hot, turn it off again.

Setup

  • So firstly during my trials, and some help from the Ruuvi Slack channel, we need to upgrade the version of bluez that is on the raspberry pi to the latest. There is apparently an issue in 5.23 that was causing me some grief.
  • First lets install all the dependancies we will need sudo apt-get install bluetooth bluez bluez-hcidump libbluetooth-dev libudev-dev
  • Add the stretch repo (with the newer bluez versions) to apt source list
    • sudo vim /etc/apt/sources.list -> add deb http://mirrordirector.raspbian.org/raspbian/ stretch main contrib non-free rpi
    • sudo vim /etc/apt/apt.conf.d/40defaultrelease -> add APT::Default-Release "jessie";
  • Update package list sudo apt-get update
  • Upgrade bluez using the strech repo sudo apt-get install bluez bluez-hcidump -t stretch
  • Now we need node. I’m going to install the latest, but the LTS version works great too
    • curl -sL https://deb.nodesource.com/setup_7.x | sudo -E bash -
    • sudo apt-get install nodejs npm

So before we go any further, lets do some testing. We can use the tools from the bluez package to scan BLE and see what data we can find

$ sudo hcitool lescan
LE Scan ...
CE:A2:59:0E:85:C2 RuuviTag 85c2
CE:A2:59:0E:85:C2 (unknown)

Cool, we can see ruuvi in the scan! No lets see what data ruuvi is giving us

$ sudo hcitool lescan & sudo hcidump --raw
[1] 18782
LE Scan ...
HCI sniffer - Bluetooth packet analyzer ver 5.43
device: hci0 snap_len: 1500 filter: 0xffffffff
CE:A2:59:0E:85:C2 RuuviTag 85c2
> 04 3E 24 02 01 00 01 C2 85 0E 59 A2 CE 18 02 01 05 05 16 09
  18 46 05 0E 09 52 75 75 76 69 54 61 67 20 38 35 63 32 AC
CE:A2:59:0E:85:C2 (unknown)
> 04 3E 1E 02 01 04 01 C2 85 0E 59 A2 CE 12 11 07 9E CA DC 24
  0E E5 A9 E0 93 F3 A3 B5 01 00 40 6E AC

Pretty neat, we can see a raw stream of hex data that the RuuviTag is giving out. Lets try again with the -x flag (show ascii and hex data)

$ sudo hcitool lescan & sudo hcidump -x
HCI sniffer - Bluetooth packet analyzer ver 5.23
device: hci1 snap_len: 1500 filter: 0xffffffff
> HCI Event: LE Meta Event (0x3e) plen 36
    LE Advertising Report
      ADV_IND - Connectable undirected advertising (0)
      bdaddr CE:A2:59:0E:85:C2 (Random)
      Flags: 0x05
      Unknown type 0x16 with 4 bytes data
      Complete local name: 'RuuviTag 85c2'
      RSSI: -77

More interesting things happening. We can see the name of the tag, its MAC address and RSI. Unforuntely we can’t see the data its advertising. I haven’t been able to find out how to do that just with hcidump but we will now turn to some javascript to get this info!

  • Lets install some node dependacies we will need for this. npm install noble mqtt
    • The noble module is pretty much where all the work happens, and we will just need the mqtt module to send messages to our openhab server
  • I have also borrowed a few bits of code out of the EspruinoHub project, this made my life alot easier. We needed the config.js and attributes.js files out of the lib folder
  • And here is index.js, that does the scanning and prints the data. Its still a bit of work in progress at the moment, but does exactly what I need. Although I do not have multiple RuuviTags, this solution should work just fine for mulitple tags.
var noble = require('noble');
var attributes = require('./attribute');
var config = require('./config');
var mqtt = require('mqtt');
// my mqtt server needs some authentication, if you dont, you can just remove that object
var client  = mqtt.connect('mqtt://10.1.1.115', { username: 'Username', password: 'Password' })
var connected = false;
var inRange = {};
var packetsReceived = 0;
var lastPacketsReceived = 0;
var isScanning = false;

noble.on('stateChange', function(state) {
  if (state === 'poweredOn') {
    noble.startScanning([], true);
  } else {
    noble.stopScanning();
  }
});
client.on('connect', function () {
  connected = true;
});

noble.on('discover', function(peripheral) {
  packetsReceived++;
  var addr = peripheral.address;
  var id = addr;
  if (id in config.known_devices)
    id = config.known_devices[id];
  var entered = !inRange[addr];

  if (entered) {
    inRange[addr] = {
      id : id,
      address : addr,
      peripheral: peripheral,
      name : "?",
      data : {}
    };
  };
  inRange[addr].lastSeen = Date.now();
  inRange[addr].rssi = peripheral.rssi;

  console.log('hello my local name is:');
  console.log('\t' + peripheral.advertisement.localName);

  var serviceData = peripheral.advertisement.serviceData;
  //If the advertising BLE device has any service data, then lets loop throught it and find out what it is
  if (serviceData && serviceData.length) {
    for (var i in serviceData) {
      //If statement to limit the outgoing mqtt stream. We are checking to see if the device is already in our array we made above, and if it hasnt been seen in 60s, and that data hasnt changed
    if (inRange[addr].data[serviceData[i].uuid] &&
        inRange[addr].data[serviceData[i].uuid].payload.toString() == serviceData[i].data.toString() &&
        inRange[addr].data[serviceData[i].uuid].time > Date.now()-60000){
            console.log("waiting....");
        }else{
          //Bit of debugging here so we can see whats going on
      console.log('\t\t' + JSON.stringify(serviceData[i].uuid) + ': ' + JSON.stringify(serviceData[i].data.toString('hex')));
      var n = attributes.getReadableAttributeName(serviceData[i].uuid);
      console.log(JSON.stringify(attributes.decodeAttribute(n,serviceData[i].data))+"\n");
      inRange[addr].data[serviceData[i].uuid] = { payload : serviceData[i].data, time : Date.now() };
      var extendedData = attributes.decodeAttribute(n,serviceData[i].data);
      //Can only sent mqtt if its actually connected, so if connected send data. We are only sending the temp number, not the whole json string which looks like { "temp": 20 } in debug messages above
      if (connected && extendedData.temp) client.publish(inRange[addr].peripheral.id +'/temp', extendedData.temp.toString());
    }
   }
  }
  if (peripheral.advertisement.manufacturerData) {
    console.log('\there is my manufacturer data:');
    console.log('\t\t' + JSON.stringify(peripheral.advertisement.manufacturerData.toString('hex')));
  }
  if (peripheral.advertisement.txPowerLevel !== undefined) {
    console.log('\tmy TX power level is:');
    console.log('\t\t' + peripheral.advertisement.txPowerLevel);
  }

  console.log();
});

If you go ahead and run this, nodejs index.js you will see somethings being printed out to console. Once it picks up Ruuvi, it should tell you the temperature now! - The script is set to only re-send data on change, or ever 60 seconds. This is just so mqtt isn’t flooded with messages

hello my local name is:
        RuuviTag 85c2
                "1809": "0807"
{"temp":18}


hello my local name is:
        RuuviTag 85c2
waiting....

hello my local name is:
        RuuviTag 85c2
waiting....

hello my local name is:
        f022cXhN
        here is my manufacturer data:
                "0f03000276064ef39800d605020000dae480"

Now lets go over to openhab, and makde sure its ready to receive some data - Pre-requisites for this is obviously having mqtt biniding installed and working - vim /etc/openhab2/items/ruuvi.items - Add something like this - Number ruuvi "RuuviTag [%.2f C]" (climate) {mqtt="<[mosq:cea2590e85c2/temp:state:default]"} - So the node script is publishing to a mqtt topic by the ruuvitag ID, which is just the tag’s MAC address without any : - While you have the node script running, tail the openhab log to make sure things are working. tail -f /var/log/openhab2/events.log

2017-04-20 15:17:12.992 [ItemStateChangedEvent     ] - piholeBlockedAds changed from 665 to 674
2017-04-20 15:19:42.802 [ItemStateChangedEvent     ] - ruuvi changed from 18 to 18.25
2017-04-20 15:20:43.626 [ItemStateChangedEvent     ] - ruuvi changed from 18.25 to 18.5
2017-04-20 15:21:42.921 [ItemStateChangedEvent     ] - ruuvi changed from 18.5 to 18
2017-04-20 15:22:16.388 [ItemStateChangedEvent     ] - piholeQueries changed from 14852 to 15265
2017-04-20 15:22:16.397 [ItemStateChangedEvent     ] - piholePercent changed from 4.5381093455427 to 4.5070422535211
2017-04-20 15:22:16.401 [ItemStateChangedEvent     ] - piholeBlockedAds changed from 674 to 688

Excellent. We can see that we are getting updates into openhab. Now make sure you are persisting this data (I’m using the rrdj4 persistance) and you can get some pretty charts to display into your sitemap

Ruuvitag chart

So lets tidy this up and manage the node script with systemd, so we don’t have to have it running in a screen or something funky like that. Just put this into /etc/systemd/system/ruuvi.service

[Unit]
Description=Ruuvi-Scanner

[Service]
ExecStart=/usr/bin/node index.js
Restart=always
User=pi
Group=pi
WorkingDirectory=/home/pi/ruuvi

[Install]
WantedBy=multi-user.target

Now you can start and stop it with systemctl

So here you have it. A RuuviTag, lots of javascript and openHAB. All working together. Now I just need to buy some more, 1 for each room!

I will also upload the files to github, so you can just download it from there too..