Tuesday, September 26, 2017

Getting notifications from a mouse trap using RuuviTag, Raspberry Pi 3, Windows IoT Core (and a string)

I was part of a Kickstarter campaign that ended up with me receiving five RuuviTags. They are small battery powered tags that communicate via Bluetooth Low Energy (BLE) and are equipped with a sensor chip. They are of course extendible and can be flashed with custom firmware. All in all, fun toys to tinker with when time permits.

Since I have a few mouse traps scattered around the house in locations that I rarely visit, I'd like to get a notification when one of them has trapped a mouse. Since the RuuviTag has acceleration data as part of the data available from the sensors (the other are temperature, atmospheric pressure, humidity and battery voltage), I thought this could be used to check whether a mouse trap has been active or not.



My basic setup is this:
A mouse trap with the RuuviTag attached. This is using stock Ruuvi firmware in RAW mode. This means that it at 1Hz interval will broadcast 20 bytes of raw sensor data via bluetooth. I have a Raspberry Pi 3 running Windows IoT Core in my house that will intercept the signal. When a change in the acceleration data is identified, it is interpreted as the mouse trap has been activated (or flipped over or in any way tampered with). If so, a REST call is made to Pushover that in turn will send a notification to my cell phone. I already use Pushover for other notifications in my home such as notifying me that tool batteries are fully loaded, that the car heater hasn't been switched off (if for instance it has been activated in the morning, but we never took the car to work) etc.

Now, one could argue that the sensor should be able to itself identify a change in the acceleration data and then send a notification regarding this instead of offloading the processing to the RPi3. However, since the tags, while having extremely good bluetooth range, can be missed by the RPi3, the current setup will ensure that I always catch a change in acceleration data. Even if I miss out on five transmits from the tag directly after a change in acceleration data, the sixth transmit will differ from the last one received, and hence trigger the logic.

I will however look into turning down the transmit interval since 1Hz is quite often for this need. I guess I can prolong the battery life a bit by doing so.

On the plus side, since I have at least one tag in the garage, I can utilize the other data sent by the tag. The temperature can be used as input to the car heater logic so that the time for the car heater will differ depending on the temperature inside the garage. Right now, I use outdoor temperature, but since the car is inside, it is not 100% correct for optimizing performance/cost.

The code is not advanced at all. I briefly looked into pairing, GATT profiles and other stuff before realizing that I didn't need any of that.

I simply add a BluetoothLEAdvertisementWatcher to catch any BLE signals received.

BluetoothLEAdvertisementWatcher watcher = new BluetoothLEAdvertisementWatcher();
watcher.Received += OnAdvertisementReceived;
watcher.Start();

I also have a small class to hold info regarding my tags and the received data.

bleTags.Add(new BLETag { BTAddress = 230418796132391, IDNumber = 5, Name = "Mouse trap garage" });
bleTags.Add(new BLETag { BTAddress = 241760085512663, IDNumber = 4, Name = "Mouse trap garage #2" });

Then in my OnAdvertisementReceived event, I check the tags in my class against the received BTAddress to see if the advertisement is for a tag I have interest in (there are quite a lot of bluetooth traffic in my house).

bleTags.FindIndex(x => x.BTAddress.Equals(eventArgs.BluetoothAddress));

Then I need to get the second data section from the advertisement since that is where the raw sensor data is stored.

BluetoothLEAdvertisementDataSection BLEDataSection2 = eventArgs.Advertisement.DataSections[1];
var dataReader = DataReader.FromBuffer(BLEDataSection2.Data);
byte[] fileContent = new byte[dataReader.UnconsumedBufferLength];
dataReader.ReadBytes(fileContent);

The data string is then parsed byte by byte according to the specification to get each sensor value.

To check if the orientation has changed, I currently just check if any of the acceleration values has changed more than 200mG, which will give me enough slack to not trigger on normal sensor fluctuation, but catch any movement from the sensor.

public bool OrientationHasChanged()
{
bool retval = false;

if (PreviousSensorData != null)
{
double xdiff = Math.Abs(PreviousSensorData.AccelerationX - SensorData.AccelerationX);
double ydiff = Math.Abs(PreviousSensorData.AccelerationY - SensorData.AccelerationY);
double zdiff = Math.Abs(PreviousSensorData.AccelerationZ - SensorData.AccelerationZ);

double difflimit = 200; // breaking limit in mG
if (xdiff > difflimit || ydiff > difflimit || zdiff > difflimit)
retval = true;
}

return retval;
}

The sensor in turn is currently fastened to a spring loaded arm on the mouse trap using a string so that when the mouse trap is moved ever so slightly or that it is triggered, the tag will move enough to change the acceleration data more than the 200mG needed to indicate a change.

When testing it out, it has showed to work very well. From a mouse meeting its creator to me getting a push notification, it is 1-5 seconds. This due to the sensor broadcasting at 1Hz, not all advertisements getting to the RPi and finally the push notification that has to make it around the world and back to my phone.

The code is most likely going to change. Altering the filtering of advertisements, trying to increase the quality of transmissions so that none are dropped and similar things are in the pipeline.