I bought a little Bluetooth thermometer and humidity sensor for about six quid. The kind with an e-ink display and a coin cell, sold under four different brand names with the same plastic shell. It works fine on its own. The problem is the app. To get the readings off it and into anything useful you have to install a phone app that wants your location, your contacts, and presumably your soul, and even then it only exports a CSV by email.
I have several of these dotted around the house. I want the numbers in my own database, not in someone's cloud. So I spent an evening working out how it actually talks, and it turned out to be far easier than I expected.
what you actually need
You do not need to desolder anything for this. Almost all of these gadgets are Bluetooth Low Energy, and BLE is gloriously easy to snoop on because it broadcasts. The hardware list is short:
- A Linux box with a working Bluetooth adapter. I used a spare adapter on my homelab server.
bluetoothctland the BlueZ stack, which is already there on most distros.nRF Connecton a phone, which is genuinely the best thing Nordic have ever given away for free.
The plan is: find the device, enumerate its services and characteristics, watch what changes when the temperature changes, and then read that value directly.
finding the thing
First, scan for it. bluetoothctl will list everything advertising nearby, which in a block of flats is a depressing number of devices.
bluetoothctl
[bluetooth]# scan on
...
[NEW] Device A4:C1:38:XX:XX:XX LYWSD03MMC
The MAC prefix A4:C1:38 is Telink, which makes the chip inside half of these cheap sensors. That alone told me I was probably in luck, because Telink-based sensors are very well documented by people who got here long before me.
Once you have the address you can connect and dump the GATT table. GATT is the structure BLE uses: a tree of services, each containing characteristics, each with a handle and a UUID. Reading the device is mostly a matter of finding which characteristic holds the data you want.
[bluetooth]# connect A4:C1:38:XX:XX:XX
[bluetooth]# menu gatt
[bluetooth]# list-attributes A4:C1:38:XX:XX:XX
That spits out a wall of UUIDs. Most are boilerplate: device name, battery service, firmware revision. The interesting one was a characteristic that, when I subscribed to notifications, started emitting a five-byte packet every few seconds.
decoding the packet
This is the bit that feels like magic the first time and obvious every time after. I subscribed to the characteristic and watched the bytes while breathing on the sensor to drive the readings up.
T (raw) = bytes[0] | (bytes[1] << 8)
temp_c = T / 100.0
humidity = bytes[2]
battery_mv = bytes[3] | (bytes[4] << 8)
Little-endian, two bytes for temperature as hundredths of a degree, one byte for humidity as a whole percent, two bytes for battery voltage in millivolts. I confirmed it by comparing against the display on the front: the e-ink said 21.4 and my decode said 2140. That is the entire protocol. There is no encryption, no handshake, nothing. The vendor app is doing exactly this and then wrapping it in three megabytes of analytics.
Here is the read in Python using bleak, which is the cross-platform BLE library I reach for now:
import asyncio
from bleak import BleakClient
ADDR = "A4:C1:38:XX:XX:XX"
CHAR = "ebe0ccc1-7a0a-4b0c-8a1a-6ff2997da3a6"
def handle(_, data: bytearray):
temp = int.from_bytes(data[0:2], "little") / 100
hum = data[2]
mv = int.from_bytes(data[3:5], "little")
print(f"{temp:.1f}C {hum}% {mv}mV")
async def main():
async with BleakClient(ADDR) as client:
await client.start_notify(CHAR, handle)
await asyncio.sleep(30)
asyncio.run(main())
Thirty seconds of notifications, then it disconnects. In practice I poll on a timer and push the readings to my metrics stack, but the core is those five bytes.
the firmware rabbit hole
There is a further step I did not take this time but feel obliged to mention, because it is the genuinely clever community work here. There is custom firmware for these Telink sensors that makes them broadcast the readings in the advertisement packet itself, no connection required. That means a single Bluetooth adapter can passively hoover up the readings from a dozen sensors across a house without ever pairing, which scales far better than maintaining a connection to each one.
I will flash that eventually. For now the connect-and-read approach is fine for the three sensors I care about, and it has the pleasing property of working without modifying the device at all. If one of them breaks I have lost six pounds, not a weekend of soldering.
was it worth it
An evening of poking at a cheap gadget, and now I own my data instead of renting it back from an app that wanted my location to read a room temperature. The readings land in my own database, I can graph them however I like, and nothing leaves the house.
The wider point is that "smart" devices are very often this thin. A standard radio protocol, an unencrypted characteristic, and a few bytes of payload. The app is the moat, not the hardware. Knock the app away and most of these things are perfectly happy to talk to whoever asks nicely. It is worth knowing how to ask.