6.0 KiB
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:
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:
TX handle 0x0015 / fff2:
D2030000003ED7B9
The battery replies with a D203 frame with byte count 0x7C:
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:
TX handle 0x0015 / fff2:
D21000D400031A070A122E2C9FE4
RX handle 0x0012 / fff1:
D21000D40003D393
The timestamp payload decodes as:
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:
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:
D20300C90003C656
with a corresponding response:
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:
41542B50524F44554354494E464F3D3F
ASCII:
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.