Files
bulltron-ble-commands/docs/live-capture.md
T

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.