Polish README workflow overview

This commit is contained in:
2026-07-10 12:01:39 +02:00
parent 783ce46c0a
commit 5b6fdf764b
+129 -9
View File
@@ -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.