I bought a little BLE temperature and humidity sensor for a few quid, the kind that pairs with a phone app and shows you a graph. The graph is fine. The app wants an account, phones home, and only keeps a week of history. I wanted the numbers in my own database, polled on my own schedule, so I set out to talk to the thing directly and skip the app entirely.
The good news with Bluetooth Low Energy is that you rarely need a logic analyser or a soldering iron for this. Everything interesting is in the GATT table, and you can enumerate it from a Linux box with a recent BlueZ. I started with bluetoothctl to find the MAC, then used gatttool to walk the services. Half the battle with these gadgets is just reading the advertised characteristics and guessing which UUID carries the payload.
$ sudo hcitool lescan
LE Scan ...
A4:C1:38:xx:xx:xx LYWSD
$ gatttool -b A4:C1:38:xx:xx:xx --characteristics
handle: 0x0035, uuid: ebe0ccc1-7a0a-4b0c-8a1a-6ff2997da3a6
handle: 0x0038, uuid: ebe0ccc4-7a0a-4b0c-8a1a-6ff2997da3a6
That ccc1 characteristic was the one. Subscribing to notifications on it produced a steady trickle of five-byte packets every few seconds, which is the satisfying moment where a black box turns into a data source.
Decoding the packet was the only part that needed any real thought, and even then not much. The first two bytes were temperature as a little-endian signed integer in hundredths of a degree, the third was humidity as a plain percentage, and the last two were battery voltage in millivolts. I confirmed it by breathing on the sensor and watching the humidity climb, which is about as rigorous as my validation gets on a Saturday. Once the shape was clear, the rest was a twenty-line script with the bluepy library: connect, subscribe, parse, write to the time-series database, disconnect.
A couple of things worth knowing if you try this yourself. These cheap chips do not love holding a connection open, so I poll on a cron schedule rather than keeping a persistent link, and I retry on failure because the connection drops more often than I'd like. And the battery characteristic is genuinely useful: I get a heads-up days before the reading goes flaky, which the vendor app never bothered to surface.
None of this is clever. The whole point of standardised GATT is that you can do exactly this, and the vendor just chose to wrap it in an app and a login. The reverse engineering here was really just reading what the device was already shouting to anyone listening. The thing now feeds a graph I own, updates every few minutes, and has never once asked me to create an account.