Ramblings of an aging IT geek
← Ramblings of an aging IT geek
hardware

poking at a four quid bluetooth thermometer

Sniffing the BLE traffic of a cheap fridge thermometer to read its temperature myself, without the dreadful phone app.

A soldering iron and scattered electronics on a bench

The gadget cost about four quid and came with a phone app I refused to keep installed. It is a little Bluetooth fridge thermometer, the sort that sits on a shelf and blinks at you. The app wanted an account, location permission, and a frankly heroic list of other things to tell me a number that the device already knew. So I decided to read it myself.

First step, find out what it actually says. bluetoothctl will scan and connect, but the quickest way to see the shape of the thing is to dump its GATT table with gatttool and look at what characteristics it exposes.

$ sudo hcitool lescan
LE Scan ...
A4:C1:38:xx:xx:xx LYWSD03MMC
$ gatttool -b A4:C1:38:xx:xx:xx --characteristics
handle: 0x0036, char value handle: 0x0037, uuid: ebe0ccc1-...

There is a notify characteristic that, once you enable notifications on it, spits out a handful of bytes every few seconds. That is the actual measurement. No account required, no cloud, just a little device shouting its readings to anyone who asks politely.

A close-up of the gadget's circuit board

The fiddly bit is decoding the bytes. I sat with a notebook and watched the values change while I held the thing in my hand to warm it up, then put it in the fridge to cool it down. The first two bytes moved with temperature, the next one with humidity, and the last with the battery voltage. Once you have that correlation it is just unpacking a little-endian sixteen-bit value and dividing by a hundred:

import struct

def decode(payload: bytes):
    temp = struct.unpack_from("<h", payload, 0)[0] / 100.0
    humidity = payload[2]
    battery_mv = struct.unpack_from("<H", payload, 3)[0]
    return temp, humidity, battery_mv

I will not pretend I got the byte offsets right first time. I had humidity and battery swapped for a good ten minutes, which produced a fridge that was apparently 217 percent humid, and I sat there nodding as though that were plausible. The fix was obvious the moment I actually breathed on the sensor and watched which number jumped.

The whole exercise took an evening and the result is twenty lines of Python feeding a number into my own logging instead of someone else's server. The reverse engineering here is generous as a description: the device is barely hiding anything, it is just wrapped in an app that adds nothing. That is the common case with these cheap things. The hardware is honest and the software is the bit you want to throw away.

Worth doing for its own sake, mind. There is a particular satisfaction in taking a sealed little black box and getting it to talk to you on your own terms, even when the box is a thermometer that cost less than a pint.