Reverse engineer a Bluetooth device's protocol with AI
Capture BLE traffic, decompile the vendor app with JADX, let Claude Code read it, test on your own device and bring it into Home Assistant.
Plenty of Bluetooth gadgets only work through a vendor app or a cloud that may not last. With a packet capture, a decompiled app and an agent that can send bytes to the device, people have brought lights, home batteries, printers, remotes and energy meters under local control in an evening or a weekend. This guide is for owners who want their own devices to work with Home Assistant, a script or an ESP32.
Before you start
- A device you own, within radio range of the machine running the agent. Keep the vendor app working so you have something to compare against.
- A Bluetooth adapter on that machine. On Linux, BlueZ provides
bluetoothctlandbtmon. Root access is needed for some captures. - Java 11 or later (64-bit) and JADX, if the device has an Android app.
- An agent: Claude Code in all the projects here except the cat printer, which used ChatGPT in a chat window.
- Python 3 for throwaway probe scripts. bleak is a cross-platform asyncio BLE client.
- Cost: the Amaran run cost $91.34 over about 3.5 hours. The others did not publish costs.
Stay on the interoperability side: talk to your own hardware and keep any keys you pull out of your own device private. Keys hardcoded in a vendor app are often the same in every copy, so publishing them affects other owners.
Step 1: Find the device and what it advertises
Scan with BlueZ and note the address and the service UUIDs:
bluetoothctl [bluetooth]# scan le [bluetooth]# devices [bluetooth]# info AA:BB:CC:DD:EE:FF
scan le limits the scan to Low Energy devices (see man bluetoothctl). The UUIDs matter later. In the Emaldo write-up, Claude matched an advertised UUID to one found in the decompiled app, which confirmed it had the right device and the right code.
Step 2: Capture what the official app sends
A capture of the real app is your ground truth. Pick the method that matches where the app runs.
- iPhone or Mac: Apple's PacketLogger ships in Additional Tools for Xcode. Tracing an iPhone needs Apple's Bluetooth logging profile on the phone and a cable to the Mac. In the cat printer project, the printer data was in the ATT packets.
- Android: turn on Developer options, enable Enable Bluetooth HCI snoop log, then restart Bluetooth. Android's debugging page says most devices store the logs in
data/misc/bluetooth/logs, or you can extract them from a bug report. - Linux host: if you can make the device talk to your computer, record everything the controller sees:
sudo btmon -w capture.btsnoop # save HCI traces in btsnoop format btmon -r capture.btsnoop # read them back later
Change one setting in the app at a time and note the time. The cat printer author found the darkness and line-count fields by diffing packets before and after single changes.
Step 3: Decompile the vendor app
Get the APK from your own phone over adb (see Android app reverse engineering with AI), then decompile it with JADX. Install it from the releases page or a package manager:
brew install jadx # macOS sudo pacman -S jadx # Arch Linux flatpak install flathub com.github.skylot.jadx jadx -d vendor-app/ vendor.apk
Add --show-bad-code to keep methods JADX could not decompile cleanly, and --deobf to rename very short obfuscated names. Both are listed in the JADX README. Desktop apps are worth a look too: Wes Bos's Amaran run decompiled both the desktop and Android apps and found the Pie mesh SDK inside.
Step 4: Let the agent read the code
Start Claude Code in a folder that holds the decompiled tree, your capture and a notes file. Then ask narrow questions, for example:
- "Find every place this app writes to a GATT characteristic. List the UUIDs and the method that builds each payload."
- "Trace how the payload for 'set brightness' is built, byte by byte, including any checksum or encryption."
- "Compare that with packets 120 to 140 in
capture.txt. Do they match?" - "Write what you have confirmed, and what is still a guess, to
PROTOCOL.md."
Emaldo's author had already decompiled the APK, grepped for BLE UUIDs and written up endpoints before opening Claude Code. That head start made the first session productive. For a long unattended run, Claude Code's /goal keeps working until a stated condition holds. Wes Bos used it for the Amaran lights. The docs advise a measurable end state, a stated check and a turn or time limit, such as "a script toggles light 1 on and off, or stop after 20 turns".
Step 5: Test every hypothesis on the live device
This is where projects stop stalling. Kacper Grabowski's energy meter analysis produced pages of notes from a firmware dump without settling the basics. He then gave Claude a VM on an isolated VLAN with the Bluetooth adapter and debug probe passed through, plus one rule: no permanent changes and nothing that risks bricking the device. Within minutes Claude was writing short Python probes and feeding each result into the next guess. When about twenty candidate keys all failed, it set a hardware breakpoint in the encryption routine and read the plaintext from RAM while the ciphertext went out over the air.
Give your agent the same loop: permission to run small scripts against the device, a clear "do not" list (no firmware writes, no factory reset, no pairing changes you cannot undo), and a habit of logging each request and response.
Watch for host-stack quirks. The Fire TV remote client found that BlueZ's input plugin owns the HID-over-GATT service, so writes through bleak fail. It drives the remote through its /dev/hidraw* node instead, which, like btmon, needs root, while a person presses the buttons.
Step 6: Wire it into Home Assistant or an ESP32
Once the protocol is written down, the integration is ordinary code, and the agent does most of it:
- A custom Home Assistant integration. Emaldo became a config flow, a data coordinator and sensor entities. Grabowski's integration set units and device classes so the Energy dashboard treats the readings as energy.
- An MQTT bridge. Michael Still's Bluetti work feeds Home Assistant over MQTT: first a cloud-to-MQTT gateway, then BLE control from an ESP32 running bare-metal Rust.
- Range. Home Assistant's Bluetooth integration can extend its reach through ESPHome Bluetooth proxies. The ESPHome docs say ESP32 and RP2040/RP2350 boards support active GATT connections.
Check it works
Run the device from your own code with the vendor app closed, and compare against a reference. Emaldo's author read solar, grid and load figures off the app screen while Claude queried the device, decoded the 22-byte reply from that, and later checked Home Assistant against an independent grid meter. Leave it running for a few days. Sessions that expire and keys that rotate only show up over time.
Where AI goes wrong
- It reads the code harder only when told to. Claude stalled on Bluetti's authentication crypto until asked to read the decompiled Java more closely. It also mixed up the Modbus and proprietary paths used by different battery types (bluetti-ep760-home-assistant).
- Source code can disagree with the wire. On Emaldo, hours of reading obfuscated Java led to the wrong framing assumption. A short packet capture showed the real format, including method codes byte-swapped relative to the source. Its real-time protocol also used two different keys for two kinds of message (emaldo-home-battery). Emaldo's BLE link turned out to handle Wi-Fi provisioning only; live data came over the network.
- It mines dead ends. On the energy meter, Claude committed to directions and needed telling to stop and try another angle. Static analysis alone stalled. The author did all the physical work and estimates Claude worked out about 80% of the device (abandoned-ble-energy-meter-home-assistant).
- Its tool advice can be off. ChatGPT pushed Wireshark or sniffing hardware for the cat printer. PacketLogger made both unnecessary. Grayscale printing was still unsolved at the time of the write-up (cat-printer-ble-chatgpt).
- It cannot press buttons. The Fire TV mic only turns on after a real voice-key press, so a person had to be in the loop (fire-tv-remote-ble-voice).
Projects that use this
- Amaran lights over Bluetooth mesh:
/goaldecompiles two apps and produces a Python control script. - Bluetti EP760 into Home Assistant: JADX, Claude Code, an MQTT gateway and a Rust ESP32.
- Emaldo home battery: decompiled app plus packet capture, ending in a Home Assistant integration.
- Cat-shaped thermal printer: PacketLogger capture and a 48-byte line format.
- Fire TV remote voice: BlueZ, btmon and hidraw on Linux with a human pressing buttons.
- Abandoned BLE energy meter: a live-device test loop in an isolated VM.
Further reading
- BlueZ (
bluetoothctlandbtmonman pages) - Android Bluetooth debugging
- Apple Bluetooth tools
- JADX
- ESPHome Bluetooth proxy
Sources checked for this guide
- github.com /bluez/bluez
- developer.apple.com /bluetooth
- source.android.com /docs/core/connect/bluetooth/verifying_debugging
- github.com /skylot/jadx
- developer.android.com /tools/adb
- github.com /hbldh/bleak
- esphome.io /components/bluetooth_proxy
- home-assistant.io /integrations/bluetooth
- home-assistant.io /integrations/mqtt
- code.claude.com /docs/en/goal
- wesbos.com /tip/reverse-engineering-amaran-bluetooth-mesh-protocol-claude-code-goal
- madebymikal.com /claude-code-is-scary-good-at-apk-reverse-engineering
- medium.com /@ylenius/how-i-reverse-engineered-my-home-batterys-protocol-in-one-day-with-an-ai-pair-programmer-60de36e75df9
- mertol.substack.com /p/reverse-engineering-a-cat-printer
- github.com /justanotheruseer/fire-remote-client
- medium.com /kingfisher-technology/when-the-cloud-dies-reverse-engineering-an-abandoned-iot-device-264ce7e5a24e