From 5b6fdf764b3b8c9de79d34aa86ad464a32844323 Mon Sep 17 00:00:00 2001 From: acidburns Date: Fri, 10 Jul 2026 12:01:39 +0200 Subject: [PATCH] Polish README workflow overview --- README.md | 138 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 129 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 32dbd29..2db6e75 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,10 @@ Reverse-engineering notes for the BullTron Android app BLE protocol. +This repository documents the custom BLE GATT protocol used by the BullTron +Android app to read battery telemetry such as SOC, current, voltage, remaining +capacity, and estimated time-to-empty/time-to-full. + ## Target App - App label: BullTron @@ -13,20 +17,136 @@ Reverse-engineering notes for the BullTron Android app BLE protocol. - Signer CN: `smart_daly` - Signer cert SHA-256: `996caf450ecdb34d0ba45d2b2c6a2df56243338c7be3b7b497168af4e3d00ae4` -The XAPK was unpacked and the base APK code was verified against the separately mirrored PGYER APK: all three DEX files have identical SHA-256 hashes. The APK was decompiled with apktool and JADX. JADX reported some failed methods, so a few UI mappings were cross-checked against smali. +The XAPK was unpacked and the base APK code was checked against a separately +mirrored PGYER APK. All three DEX files matched by SHA-256. The APK was +decompiled with apktool and JADX; a few mappings were cross-checked against +smali where JADX reported failed methods. ## Main Finding -Normal telemetry does not use the standard BLE Battery Service. The app connects as a BLE GATT client, subscribes to a custom BullTron/Daly-style service, writes Modbus-like frames to a write characteristic, then parses notify frames from a read/notify characteristic. +Normal telemetry does not use the standard BLE Battery Service. The app connects +as a BLE GATT client, subscribes to a custom BullTron/Daly-style service, writes +Modbus-like command frames to a write characteristic, then parses notify frames +from a read/notify characteristic. -Android pairing/bonding is not explicitly required in the app's normal BMS flow. The core path uses `connect`, MTU request, service discovery, notifications, and writes. Generic library code contains bonding helpers, but the BullTron telemetry path does not call `createBond()`. +Android pairing/bonding does not appear to be required for normal telemetry. The +core BullTron path uses connect, MTU request, service discovery, notifications, +and writes. Generic library code contains bonding helpers, but the normal BMS +telemetry path does not call `createBond()`. -## Documents +## Quick Start -- `docs/ble-flow.md` - connection/setup/query flow, pairing answer, and value derivation -- `docs/command-catalog.md` - services, characteristics, command frames, and telemetry register map -- `tools/extract-ble-symbols.py` - helper for scanning JADX/apktool output for BLE UUIDs and GATT calls +Build the main live-data read frame: -## Status +```sh +python3 tools/bulltron_frame.py read 0 62 +``` -The local repo contains the reverse-engineering results. Pushing to `git@git.mannheim.ccc.de:C3MA/bulltron-ble-commands.git` is currently blocked because C3MA Gitea does not allow organization push-to-create for this repo. Create an empty `C3MA/bulltron-ble-commands` repository or provide a token with repo creation rights, then push `main`. +Expected output: + +```text +D2030000003ED7B9 +``` + +Send that frame to characteristic `0000fff2-0000-1000-8000-00805f9b34fb` +after subscribing to notifications on +`0000fff1-0000-1000-8000-00805f9b34fb`. + +## App Workflow + +The app's normal telemetry flow is: + +1. Scan/select a BLE device in the app UI. +2. Connect with the Inuker BLE client wrapper. +3. Request MTU `512`. +4. Discover services and find `0000fff0-0000-1000-8000-00805f9b34fb`. +5. Subscribe to notifications on `fff1`. +6. Write ASCII `HiLink` to the secret-key characteristic + `02f00000-0000-0000-0000-00000000ff05`. +7. If the key readback is `0100`, query version/band information with AT-style + commands. +8. Write phone time to BMS registers with a `D210` multi-register write at + register `0x00D4`. +9. Periodically write `D203` read frames to `fff2`. +10. Receive notifications on `fff1`, split/buffer frames, validate CRC, parse + 16-bit big-endian words, and update the UI. + +## Minimal BLE Workflow + +For a custom client that only needs live telemetry: + +1. Connect to the device over BLE GATT. +2. Request a large MTU if possible. The app requests `512`. +3. Discover services. +4. Subscribe to notifications on `fff1` under service `fff0`. +5. Write ASCII `HiLink` to `02f...ff05`. +6. Optionally query version with ASCII `AT+VER=?\r\n` on `02f...ff04`. +7. Write the main read frame to `fff2`: + + ```text + D2030000003ED7B9 + ``` + +8. Parse the notify response from `fff1`. + +The expected live-data response has byte count `0x7C`, meaning 124 bytes / 62 +16-bit words: + +```text +D203 7C <62 words> CRC +``` + +## Live Telemetry Map + +The main read command reads registers `0x0000..0x003D`: + +```text +D203 0000 003E CRC +``` + +Important fields: + +| Register | Meaning | Formula | +| ---: | --- | --- | +| `0x0000`-`0x001F` | Cell voltages | `raw * 0.001 V` | +| `0x0020`-`0x0027` | Battery temperatures | `raw - 40 deg C` | +| `0x0028` | Pack voltage | `raw * 0.1 V` | +| `0x0029` | Current | `(raw - 30000) * 0.1 A`, with app-specific correction for two voltage profiles | +| `0x002A` | SOC / battery percent | `raw / 10 %` | +| `0x002F` | MOS / direction state | `1 = charging`, `2 = discharging` | +| `0x0030` | Remaining capacity | `raw * 0.1 Ah` | +| `0x0033` | Cycle count | raw count | +| `0x0035` | Charge MOS | `1 = on` | +| `0x0036` | Discharge MOS | `1 = on` | +| `0x003A`-`0x003D` | Alarm/status words | 16-bit bitmaps | + +Time-to-empty/time-to-full is computed by the app instead of read as one live +BLE value: + +```text +if register 0x002F == 1: + time-to-full = rated_capacity_Ah * (100 - SOC_percent) / abs(current_A) * 60 + +if register 0x002F == 2: + time-to-empty = rated_capacity_Ah * SOC_percent / abs(current_A) * 60 +``` + +## Repository Contents + +- `docs/ble-flow.md` - detailed connection/setup/query workflow, pairing answer, + and value derivation. +- `docs/command-catalog.md` - services, characteristics, frame formats, observed + commands, and telemetry register map. +- `evidence/apk-info.md` - APK/XAPK provenance, hashes, signer info, and + decompilation notes. +- `tools/bulltron_frame.py` - helper for building read/write frames with the + same CRC format used by the app. +- `tools/extract-ble-symbols.py` - helper for scanning JADX/apktool output for + BLE UUIDs and GATT calls. + +## Caveats + +These findings are derived from the decompiled Android app, not from a live BLE +capture. A capture is still useful to confirm firmware-specific behavior, current +sign conventions, correction edge cases, and whether a specific battery requires +link-layer encryption despite the app not initiating pairing itself.