Document BullTron live BLE capture

This commit is contained in:
2026-07-10 19:01:42 +02:00
parent 5b6fdf764b
commit 0a1d7cff6e
4 changed files with 248 additions and 0 deletions
+198
View File
@@ -0,0 +1,198 @@
# BullTron BLE Live Capture
Status: confirmed from Android Bluetooth HCI snoop log captured with the
BullTron app connected to a BullTron battery.
The raw `btsnoop_hci.log` is not committed because it contains unrelated nearby
Bluetooth advertisements and device addresses. This file contains the protocol
facts extracted from the capture.
## Capture Summary
- Format: BTSnoop version 1, HCI UART (H4)
- Tool used for inspection: `btmon`
- Connected LE device: BullTron/Daly-style BLE peripheral
- Connection handle in capture: `65`
- Remote controller manufacturer: Hong Kong Bouffalo Lab Limited
- Remote controller Bluetooth version: Bluetooth 5.0
- App behavior: reconnects automatically, discovers services, negotiates MTU,
subscribes to notifications, then polls BMS registers.
## Discovery And Setup
The capture confirms the app-discovered main BMS service:
| Handle range | UUID | Meaning |
| --- | --- | --- |
| `0x0010`-`0x001c` | `0000fff0-0000-1000-8000-00805f9b34fb` | Main BMS service |
Characteristics under that service:
| Declaration handle | Value handle | UUID | Properties |
| ---: | ---: | --- | --- |
| `0x0011` | `0x0012` | `0000fff1-0000-1000-8000-00805f9b34fb` | read, notify |
| `0x0014` | `0x0015` | `0000fff2-0000-1000-8000-00805f9b34fb` | read, write, write without response |
| `0x001a` | `0x001b` | `0000fff3-0000-1000-8000-00805f9b34fb` | read, write, notify |
Setup observed:
```text
ATT Exchange MTU Request: client RX MTU 517
ATT Exchange MTU Response: server RX MTU 247
LE Set Data Length: TX octets 251
```
The app enables notifications by writing `0100` to a Client Characteristic
Configuration descriptor. The capture view labels that descriptor as handle
`0x0013`.
## Confirmed Main Data Flow
Normal BMS command frames are written with ATT Write Command to handle `0x0015`
(`fff2`). BMS responses arrive as Handle Value Notifications on handle `0x0012`
(`fff1`).
The live telemetry command is repeatedly sent:
```text
TX handle 0x0015 / fff2:
D2030000003ED7B9
```
The battery replies with a `D203` frame with byte count `0x7C`:
```text
RX handle 0x0012 / fff1:
D2037C <124-byte payload> <crc16>
```
In this capture, 28 live-data `D2037C` frames were observed and all had valid
Modbus CRC16 using the same swapped CRC format implemented in
`tools/bulltron_frame.py`.
## Confirmed Read Commands
These `D203` read commands were observed on `fff2` with valid CRCs:
| Payload | Start register | Count | Observed use |
| --- | ---: | ---: | --- |
| `D2030000003ED7B9` | `0x0000` | `0x003E` | Main live telemetry block |
| `D203003E0009F7A3` | `0x003E` | `0x0009` | Extra cell/pack data block |
| `D2030057000CE7BC` | `0x0057` | `0x000C` | Additional info/status block |
| `D20300640001D676` | `0x0064` | `0x0001` | Residue time / heating flag |
| `D20300800029965F` | `0x0080` | `0x0029` | Settings/system block |
| `D20300A900208791` | `0x00A9` | `0x0020` | System/settings block |
| `D20300A900254792` | `0x00A9` | `0x0025` | Extended system/settings block |
| `D20300C90003C656` | `0x00C9` | `0x0003` | Password/control-code field |
| `D20300CC00021797` | `0x00CC` | `0x0002` | Small settings/status field |
| `D20300D900054791` | `0x00D9` | `0x0005` | Small settings/status field |
The app also performs a phone-time sync immediately after setup:
```text
TX handle 0x0015 / fff2:
D21000D400031A070A122E2C9FE4
RX handle 0x0012 / fff1:
D21000D40003D393
```
The timestamp payload decodes as:
```text
1A 07 0A 12 2E 2C
YY MM DD HH MM SS
```
## Confirmed Response Byte Counts
Observed `D203` response byte-count dispatch values:
| Byte count | Count in capture | Meaning |
| ---: | ---: | --- |
| `0x7C` | 28 | Main 62-word live telemetry block |
| `0x52` | 28 | Settings info block |
| `0x40` | 18 | Version/device info block |
| `0x18` | 38 | Serial number / identifier block |
| `0x12` | 42 | Small status block |
| `0x06` | 18 | Password/control-code field |
| `0x04` | 34 | Small status/ack-style block |
| `0x02` | 17 | Residue time / heating flag |
| `0x0A` | 1 | Small status block |
Some notifications contain more than one `D203` frame concatenated. A parser
must split by `D203`, use the response byte count to determine frame length, and
then validate CRC per frame.
## Decoded Live Telemetry Example
One live `D2037C` frame from the capture decodes to:
| Field | Register | Decoded value |
| --- | ---: | --- |
| Cell 1 | `0x0000` | `3.306 V` |
| Cell 2 | `0x0001` | `3.312 V` |
| Cell 3 | `0x0002` | `3.308 V` |
| Cell 4 | `0x0003` | `3.311 V` |
| Pack voltage | `0x0028` | `13.2 V` |
| Current raw | `0x0029` | `29925` |
| Current | `0x0029` | `-7.5 A` using `(raw - 30000) * 0.1` |
| SOC | `0x002A` | `65.2 %` |
| State | `0x002F` | `2`, discharging |
| Remaining capacity | `0x0030` | `107.5 Ah` |
| Temperature sensors | `0x0032` | `2` |
| Cycle count | `0x0033` | `3` |
| Charge MOS | `0x0035` | `1`, on |
| Discharge MOS | `0x0036` | `1`, on |
| Cell voltage delta | `0x0038` | `0.006 V` |
| Alarm/status words | `0x003A`-`0x003D` | `0000 0000 0000 0010` |
Later frames show the same battery around:
```text
pack voltage: 13.2 V
current: -7.4 A
SOC: 65.0-65.2 %
remaining capacity: 107.2-107.5 Ah
state: 2 / discharging
```
This confirms the decompiled formulas for voltage, current, SOC, remaining
capacity, MOS state, cell delta, and alarm bitmaps.
## System Screen Observations
After entering the system code `000000`, the capture shows reads of:
```text
D20300C90003C656
```
with a corresponding response:
```text
D20306303030303030F6AF
```
The payload is ASCII `000000`, confirming register `0x00C9` length `3` as the
password/control-code field.
The app also writes `AT+PRODUCTINFO=?` to handle `0x001B` (`fff3`) during the
system/settings path:
```text
41542B50524F44554354494E464F3D3F
```
ASCII:
```text
AT+PRODUCTINFO=?
```
## Pairing / Bonding Result
This capture supports the earlier code-derived conclusion: normal telemetry did
not require an Android pairing dialog or explicit bond setup. The app connected,
discovered services, negotiated MTU, enabled notifications, and exchanged BMS
frames directly.