Files
bulltron-ble-commands/docs/command-catalog.md
T

8.8 KiB

BullTron BLE Command Catalog

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
0000fff0-0000-1000-8000-00805f9b34fb Main BMS service BluetoothRegulate constants and service discovery
02f00000-0000-0000-0000-00000000fe00 Version, secret key, OTA BluetoothRegulate constants and setup writes
0000ff00-0000-1000-8000-00805f9b34fb WiFi provisioning/config path WaitConfig constants, not normal BMS telemetry

Characteristics

Characteristic UUID Direction Purpose
0000fff1-0000-1000-8000-00805f9b34fb notify/read Main BMS response/telemetry frames
0000fff2-0000-1000-8000-00805f9b34fb write, notify callback registered Main BMS command frames
0000fff3-0000-1000-8000-00805f9b34fb write/read AT-style BLE module/name/band commands
02f00000-0000-0000-0000-00000000ff04 write/read/notify Version query, AT+VER=?\r\n
02f00000-0000-0000-0000-00000000ff05 write/read Secret key handshake, ASCII HiLink
02f00000-0000-0000-0000-00000000ff02 notify/read OTA response
02f00000-0000-0000-0000-00000000ff01 write OTA data

Frame Format

The main BMS protocol is Modbus-like over BLE.

Read holding/register block:

D203 <start:16-bit> <count:16-bit> <crc16>

Single-register write:

D206 <register:16-bit> <value:16-bit> <crc16>

Multi-register write:

D210 <start:16-bit> <count:16-bit> <payload:count*2 bytes> <crc16>

Password write:

D210 00C9 0003 <utf8 password bytes> <crc16>

Time sync write:

D210 00D4 0003 <YY MM DD HH MM SS> <crc16>

OTA chunk:

8110 <location> <length> <data> <crc16>

The CRC is Modbus CRC16 with polynomial 0xA001, initial value 0xFFFF, and the app returns the final word byte-swapped as four uppercase hex digits.

Response Format

For D203 responses:

D203 <byte_count:8-bit> <payload words> <crc16>

byte_count is the payload byte length. The live-data response uses 0x7C, which is 124 bytes / 62 words.

For D206 and D210 responses, the app treats the acknowledgement as a fixed 8-byte / 16-hex-character frame.

Commands Observed In App

Name Characteristic Operation Payload Response/Notification Meaning
Secret key 02f...ff05 write ASCII HiLink readback/notify 0100 means OK Enables version/band setup path
Query version 02f...ff04 write ASCII AT+VER=?\r\n version notify/read BLE/app/MCU version query
Query/change band fff3 write ASCII AT+BAND=... / query variant AT response BLE module band/region setup
Change BLE name fff3 write ASCII AT+NAME=<name> AT response Rename BLE module
Product info fff3 write ASCII AT+PRODUCTINFO... AT response Product info/config
Read live data fff2 write hex frame D2030000003E<crc> fff1 notify with byte count 7C Reads main 62-word telemetry block
Read last cell voltages fff2 write hex frame D203003E0010<crc> fff1 notify with byte count 20 Reads last/extra cell voltage block
Read password fff2 write hex frame D20300C90003<crc> fff1 notify with byte count 06 Reads 3-word password field
Read residue/calefaction flag fff2 write hex frame D20300640001<crc> fff1 notify with byte count 02 Stores residueTime; 0001 also marks heating true
Sync time fff2 write hex frame D21000D40003<YYMMDDHHMMSS><crc> D210 ack Writes phone time to BMS
Write password fff2 write hex frame D21000C90003<password_hex><crc> D210 ack Sets password/control code
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:

D21000D400031A070A122E2C9FE4

with acknowledgement:

D21000D40003D393

The system/settings screen writes AT+PRODUCTINFO=? to fff3.

Live Telemetry Register Map

Read command:

D203 0000 003E CRC

Response dispatch:

D203 7C <62 words> CRC

Words are parsed as unsigned 16-bit big-endian hex words before scaling.

Register Index Scale / Transform Unit Field
0x0000-0x001F 0-31 raw * 0.001 V cell voltages
0x0020-0x0027 32-39 raw - 40 deg C battery temperature sensors
0x0028 40 raw * 0.1 V pack voltage
0x0029 41 (raw - 30000) * 0.1 A current; negative/positive sign follows firmware convention
0x002A 42 raw / 10 % battery percent / SOC
0x002B 43 raw * 0.001 V highest cell voltage
0x002C 44 raw * 0.001 V lowest cell voltage
0x002F 47 raw enum state 1 = charging, 2 = discharging in live-time UI
0x0030 48 raw * 0.1 Ah remaining capacity
0x0032 50 raw count, capped at 8 count temperature sensor count
0x0033 51 raw cycles cycle count
0x0035 53 1 = on boolean charge MOS
0x0036 54 1 = on boolean discharge MOS
0x0038 56 raw * 0.001 V cell voltage delta
0x003A-0x003D 58-61 16-bit bitmaps bits alarm/status words

The app applies an extra current correction for two recognized voltage profile pairs from settings registers:

Setting values Correction
settings word 0x000C formats as 3.71 and word 0x000E formats as 2.71 multiply current by 3.472
settings word 0x000C formats as 3.72 and word 0x000E formats as 2.72 multiply current by 3.37

Time-To-Empty / Time-To-Full

The UI computes time from live registers instead of just displaying a raw BLE value:

current_A = abs(decoded register 0x0029)
soc_percent = register 0x002A / 10
remaining_capacity_Ah = register 0x0030 * 0.1
rated_capacity_Ah = parsed settings capacity

if register 0x002F == 1:
  label = TimeToFull
  minutes = rated_capacity_Ah * ((100 - soc_percent) / 100) / current_A * 60

if register 0x002F == 2:
  label = TimetoEmpty
  minutes = rated_capacity_Ah * (soc_percent / 100) / current_A * 60

If current is zero or the state is neither 1 nor 2, the UI shows -- h --m.

Dispatch By Read Byte Count

DataAnalysisHelper.analysisPressValueByCan() dispatches D203 replies by byte count:

Byte count Meaning in app
7C main run/live info
20 last battery/cell voltage block
52 settings info
40 version data
06 password data
12 balancing state

In the BullTron system screen, rated capacity is settings word 0 scaled by 0.1 Ah. The charge and discharge MOS controls write single registers 0x00A5 and 0x00A6; live MOS status is read separately from telemetry registers 0x0035 and 0x0036. | 0A | new alarm info | | 4A | device parameter info | | 04 | communication/protocol or production date | | FE | history info | | 18 | serial number | | 02 | residue time / heating flag | | FC | Ali/BMS data 0x00-0x7D | | E0 | Ali/BMS data 0x80-0xEF | | 38 | BLE registers 0x63-0x7E | | 22 | BLE registers 0xDF-0xEF |

Caveats

  • This catalog is code-derived. A live BLE capture is still useful to confirm sign conventions, current correction edge cases, and whether a specific BullTron battery firmware requires link-layer encryption.
  • The app bundles WiFi/Ali IoT flows too; those are separate from the local BLE telemetry path documented here.