Document BullTron live BLE capture
This commit is contained in:
@@ -26,6 +26,10 @@ This is the app flow used to obtain battery/controller information such as disch
|
||||
8. It writes the current phone time to BMS registers using a `D210` multi-register write at `0x00D4`.
|
||||
9. Main screens periodically send `D203` read frames. Notifications are buffered, frame-split, CRC checked, parsed, then broadcast to UI screens.
|
||||
|
||||
Live capture note: Android requested ATT MTU `517`; the battery responded with
|
||||
server MTU `247`. The app then used handle `0x0015` (`fff2`) for command writes
|
||||
and handle `0x0012` (`fff1`) for notifications.
|
||||
|
||||
## Pairing / Bonding
|
||||
|
||||
- Needs Android bond before normal telemetry: likely no.
|
||||
@@ -109,3 +113,7 @@ The `rated_capacity_Ah` input comes from the settings register set parsed into `
|
||||
6. Optionally query version with ASCII `AT+VER=?\r\n` on `02f...ff04`.
|
||||
7. Write the read frame for registers `0x0000` length `0x003E` to `fff2`.
|
||||
8. Parse the notify response from `fff1`, validate CRC, split into 16-bit big-endian words, then apply the formulas above.
|
||||
|
||||
The live capture confirms that notifications may contain more than one `D203`
|
||||
frame concatenated. Split by frame header and byte count before applying CRC and
|
||||
register decoding.
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
Status: decompiled from BullTron Android app `1.1.33`. Entries are based on code paths, not live BLE captures.
|
||||
|
||||
Update: the main BLE flow and several command frames are now confirmed by an
|
||||
Android HCI snoop capture. See `docs/live-capture.md`.
|
||||
|
||||
## Services
|
||||
|
||||
| Service UUID | Purpose | Evidence |
|
||||
@@ -94,6 +97,38 @@ For `D206` and `D210` responses, the app treats the acknowledgement as a fixed 8
|
||||
| Single register write | `fff2` | write hex frame | `D206<register><value><crc>` | `D206` ack | Generic settings/control write |
|
||||
| Multi-register write | `fff2` | write hex frame | `D210<start><count><payload><crc>` | `D210` ack | Generic settings/config write |
|
||||
|
||||
## Live Capture Confirmed Reads
|
||||
|
||||
The following read frames were observed in the HCI snoop capture. All listed
|
||||
frames have valid Modbus CRC16 using the app's swapped CRC representation.
|
||||
|
||||
| Payload | Start | Count | Notes |
|
||||
| --- | ---: | ---: | --- |
|
||||
| `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 capture also confirms time sync:
|
||||
|
||||
```text
|
||||
D21000D400031A070A122E2C9FE4
|
||||
```
|
||||
|
||||
with acknowledgement:
|
||||
|
||||
```text
|
||||
D21000D40003D393
|
||||
```
|
||||
|
||||
The system/settings screen writes `AT+PRODUCTINFO=?` to `fff3`.
|
||||
|
||||
## Live Telemetry Register Map
|
||||
|
||||
Read command:
|
||||
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user