Skip to main content

BLE Protocol — CFMoto (com.cfmoto.cfmotointernational)

Status: CONFIRMED from jadx decompilation. Values extracted directly from source.


1. GATT UUIDs

All UUIDs are from BleConstant.java and related files.

Primary "TBox" Service (used by 450-series / connected motorcycles)

RoleUUIDSource
Service0000B354-D6D8-C7EC-BDF0-EAB1BFC6BCBCBleConstant.SERVICE_UUID
Write Characteristic0000B356-D6D8-C7EC-BDF0-EAB1BFC6BCBCBleConstant.CHARACTERISTIC_UUID_WRITE
Notify Characteristic0000B357-D6D8-C7EC-BDF0-EAB1BFC6BCBCBleConstant.CHARACTERISTIC_UUID_NOTIFY
RoleUUIDSource
Service0000B360-D6D8-C7EC-BDF0-EAB1BFC6BCBCBleNaviModule.SERVICE_UUID
Characteristic0000B362-D6D8-C7EC-BDF0-EAB1BFC6BCBCCf110Utils.SERVICE_Cmd_UUID (used as CHARACTERISTIC_UUID in BleNaviModule)

CF110 Youth/Child Bike Auth Service (different device type)

RoleUUIDSource
Auth Service0000b358-d6d8-c7ec-bdf0-eab1bfc6bcbcCf110Utils.SERVICE_Auth_UUID
Auth Characteristic0000b360-d6d8-c7eC-bdf0-eab1bfc6bcbcCf110Utils.Char_Auth_UUID
Cmd Service0000B362-D6D8-C7EC-BDF0-EAB1BFC6BCBCCf110Utils.SERVICE_Cmd_UUID
Cmd Characteristic0000B364-D6D8-C7EC-BDF0-EAB1BFC6BCBCCf110Utils.Char_Cmd_UUID

HH40 (another youth/child scooter variant)

RoleUUIDSource
SPP Service0783b03e-8535-b5a0-7140-a304d2495cb7HH40Utils.BLE_SPP_Service
Notify Characteristic0783b03e-8535-b5a0-7140-a304d2495cb8HH40Utils.BLE_SPP_Notify_Characteristic
Write Characteristic0783b03e-8535-b5a0-7140-a304d2495cbaHH40Utils.BLE_SPP_Write_Characteristic
AT Characteristic0000fff6-0000-1000-8000-00805f9b34fbHH40Utils.BLE_SPP_AT_Characteristic

Note: The HH40 and CF110 UUIDs are for small electric child vehicles, NOT the 450-series motorcycle. The primary TBox UUIDs (B354/B356/B357) are the ones relevant to CFMoto 450 MT/SR/NK.


2. Packet Format

There are two packet formats depending on the connection path.

2a. TBox Frame (450-series motorcycle, BleModel)

Used for all communication on SERVICE_UUID / CHARACTERISTIC_UUID_WRITE. Assembled by TboxMessageFrame.generateMessage().

Offset  Len   Description
------ --- -----------
0 2 Header: 0xAB 0xCD (bytes -85,-51 in signed = 0xAB,0xCD)
2 1 Control code (command byte)
3 2 Data length (little-endian, low byte first)
5 N Data payload (Protobuf-encoded)
5+N 1 CRC: sum of bytes[2..5+N-1] mod 256 (byte addition, truncated to byte)
6+N 1 End byte: 0xCF (byte -49 in signed)

Total frame size: N + 7 bytes.

CRC algorithm (from TBoxCrcFrame.calculateCrc()): sum of control-code byte + both length bytes + all data bytes, result truncated to byte (i.e., mod 256, signed).

Decoding (from TboxFrameDecoder.decode()):

  • Validate bArr[0] == 0xAB and bArr[1] == 0xCD
  • Read length: (bArr[3] & 0xFF) | (bArr[4] << 8) (little-endian)
  • Data payload at offsets [5 .. 5+len-1]
  • CRC at [5+len], end byte at [5+len+1]
  • Payload is Protobuf (parsed via com.cfmoto.proto.Meter.*)

2b. CFBleMsg / HH40 Frame (youth bikes, BleModelYouth)

Used by CF110 and HH40 child bikes. Assembled by CFBleMsg.getSendCmdData() and HH40Utils.getSendData().

Offset  Len   Description
------ --- -----------
0 2 Header: 0xAB 0xCD
2 1 Command byte
3 1 Data length (low byte)
4 1 Data length (high byte, always 0)
5 N Data payload (Protobuf-encoded)
5+N 1 CRC: sum of bytes[2..5+N-1] mod 256
6+N 1 End byte: 0xCF

CRC algorithm (from CFBleMsg.cfCrc()): sum of bArr[2] through bArr[len-3] (i.e., bytes after header through before CRC+end), masked to 8 bits.

Receiver framing (HH40Utils.receiver()):

  • Buffer accumulates bytes
  • Sync on 0xAB then 0xCD
  • Length read from bytes[3] and bytes[4] (little-endian short)
  • Packet complete when size == length + 5 and last byte is 0xCF

2c. Navigation Frame (MessageFrame, used by BleNaviModule)

Same outer structure as TBox frame but includes a sequence byte for multi-frame messages:

Offset  Len   Description
------ --- -----------
0 2 Header: 0xAB 0xCD
1 1 Control code (NaviCode byte)
3 2 Data length (little-endian)
5 1 Sequence byte (see below)
6 N Data payload
6+N 1 CRC: sum of control + len-bytes + seq + data bytes, mod 256
7+N 1 End byte: 0xCF

Sequence byte values (from SeqFrame):

ValueMeaning
0xC0 (-64)Single frame
0x80 (-128)Start frame of multi-frame
0x00Middle frame
0x40End frame (OR'd with sequence number)
0x20Need-confirm flag (OR'd in)

Max single-frame data payload: 12 bytes (for road name fragmentation).


3. Control Codes / Commands

TBox Control Codes (TboxControlCode.java)

These are the controlCode bytes in TBox frames. Sent App→Bike on CHARACTERISTIC_UUID_WRITE.

Code (hex)Code (dec)ConstantDescription
0x5A90auth_packageAuth step 1: send encrypted auth package
0x5B91tbox_numTBox device number (response)
0x5C92random_numAuth step 2: send decrypted random number
0x5D93tbox_auth_resuleTBox auth result
0x65101THEMESet display theme
0x66102NAVINavigation data
0x67103lock_controlLock/unlock/power-on/power-off
0x68104PREFERENCEPreferences (max speed limit)
0x69105DISPLAY_UNITSDisplay unit settings
0x6A106find_carFind car (flash/horn/headlight)
0x6B107light_conrtolTurn signal control
0x6C108keep_authKeep-alive / auth keepalive
0x71113charge_optCharging configuration
0x79121kl15KL15 (ignition key)
0x0A10operate_4g4G remote operation
0x0B11rechargeStart recharge
0x0C12operate_4g_complex4G complex operation
0x1521patch_obtain_infoBatch get info (charger state etc.)

Response Control Codes (Bike→App, received on NOTIFY)

Decoded in TboxFrameDecoder.decode(). Note: Java signed bytes, so negative values represent > 127 unsigned.

Code (dec signed)Code (hex unsigned)ConstantDescription
900x5ATboxAUTHAuth package from bike
910x5BTboxRandomNumRandom number (codec string)
920x5CTboxSendNumRESULTSend number result
930x5DTboxAuthResultAuth result (int)
-1070x95PATCH_OBTAIN_INFOCharger connection state + charge state
-250xE7LockControll / NAVIRESLock control result (result + errRes)
-220xEAFindCarFind car result
-210xEBLightControllLight control result
-200xECKEEP_ALIVEKeep-alive response
-150xF1CHARGE_OPTCharge option result
-70xF9KL15KL15 result
-1180x8AOPERATE_4G4G operation result (result + errorCode)
-1170x8BRECHARGERecharge result
-1160x8COPERATE_4G_COMPLEXComplex 4G result (result + errorCode)

Lock Control (lock_control = 0x67) — Meter.Lock protobuf

type and state fields in the Lock protobuf:

OperationtypestateNotes
Unlock11unLock()
Lock12lock()
Power ON73powerOn()
Power OFF74powerOff()
Keep-alive heartbeatUses Heartbeat.setPing(1) via lock_control code 103

Lock response state codes (from bisRes()):

State valueMeaning
0 or 1Unlock success
16Lock succeeded (maps to result 0)
17Lock failed (maps to result 1)
32Power-on succeeded (maps to result 0)
33Power-on failed (maps to result 1)
48Power-off succeeded (maps to result 0)
49Power-off failed (maps to result 1)

Sent via BleNaviModule on the Navi service.

ByteConstantDescription
120stateNavigation state
121directionTurn direction
122distanceDistance
123nextRoadNameNext road name (may fragment)
124remainDistanceRemaining distance
125remainTimeRemaining time
126timeCurrent time

CFBleMsg Command Codes (CF110 child bike)

CodeConstantDescription
10CMD_LBS_InfoDevice location info
11CMD_Setting_InfoCar setting info
12CMD_Write_SettingWrite settings (returns 0/1 result)
13CMD_RF_dis_testRF distance test
14CMD_Cmd_ResultCommand result
15CMD_Get_Dev_VersionGet device version
16CMD_Update_DevDevice OTA update
91Cmd_Dev_EnDevice challenge (encrypted random bytes)
92Cmd_Dev_DeDevice decryption step
93Cmd_Dev_AuthOKDevice auth OK
94Cmd_App_EnApp challenge send
95Cmd_App_DeApp decryption step
96Cmd_App_AuthOKApp auth OK

HH40 Command Codes (HH40Utils.java)

ByteConstantDescription
1CMD_VEHICLE_CONFIG_INFOVehicle configuration
2CMD_TRIP_EVENTTrip event
3CMD_VEHICLE_CYCLE_INFOVehicle cycle info
4CMD_VEHICLE_SETTINGVehicle settings
5CMD_FAULT_INFOFault info
6CMD_KEY_CYCLE_INFOKey cycle info
7CMD_POWER_MODE_MODIFYPower mode modify
8CMD_CYCLING_RANGE_MODIFYCycling range modify
9CMD_CALL_VEHICLECall vehicle (find)
10CMD_OVER_DISTANCE_REMINDEROver-distance reminder
11CMD_SOS_REMINDERSOS reminder
15CMD_VEHICLE_LOCATION_INFOVehicle location info
17CMD_LOCKLock/unlock (action 1=unlock, 2=lock)
18CMD_ELECTRIC_FENCEElectric fence
19CMD_UNIT_SETTINGUnit settings
20CMD_SEND_LOCATIONSend location
-111CMD_REPLY_LOCKLock reply
-110CMD_REPLY_OVER_DISTANCE_SETTINGOver-distance setting reply
-121CMD_REPLY_FAULT_CODE / CMD_REPLY_POWER_MODEFault code / power mode reply
-120CMD_REPLY_CYCLING_RANGECycling range reply
-119CMD_REPLY_CALL_VEHICLECall vehicle reply
-118CMD_REPLY_OVER_DISTANCEOver-distance reply
-117CMD_REPLY_SOSSOS reply
-113CMD_REPLY_GPSGPS reply
-109CMD_REPLY_UNIT_SETTINGUnit setting reply

4. Protobuf Message Types (com.cfmoto.proto)

Payloads are encoded as Protocol Buffers. Key message types:

Meter.AuthPackage — auth step 1

  • info (ByteString): encrypted auth bytes (from EncryptInfoBean.encryptValue, hex-decoded)

Meter.TboxRandomNum — auth step 1 response

  • codec (string): random number string from bike

Meter.RandomNum — auth step 2 send

  • sn (string): decrypted random number (AES256/ECB/PKCS7 decrypt of codec using key)

Meter.TboxAuthResult — auth result

  • result (int): 0 = success

Meter.Lock — lock/unlock/power

  • type (int): operation category
  • state (int): operation state

Meter.Heartbeat — keep-alive

  • ping (int): set to 1

Meter.FindCar — find car

  • doubleflashStatus (bool)
  • headlightStatus (bool)
  • loudspeakerStatus (bool)

Meter.LightControl — turn signal

  • direction (int): uses LightType enum (NONE2=0, RIGHT_OPEN=1, RIGHT_CLOSE=2, LEFT_OPEN=3, LEFT_CLOSE=4)

Meter.ChargeSetting — charge power setting

  • chargePower (int)

Meter.ChargeStatus — recharge command

  • enableCharge (bool): set true

Meter.PatchObtainInfoControl — batch info request

  • groupId (int): set to 1

Meter.PatchObtainInfoResult — batch info response

  • chargerConnState (int)
  • chargState (int)
  • groupId (int)

Meter.Operate4g — 4G command

  • command (int)
  • body (int)
  • msgId (int): System.currentTimeMillis() / 1000

Meter.Operate4gComplex — 4G complex command

  • command (int)
  • body (string)
  • msgId (int)

Meter.Preference — preferences

  • maximumSpeedLimit (int)

Meter.Display — display units

  • distance (DistanceUnit enum)
  • temperature (TemperatureUnit enum)
  • time (TimeUnit enum)
  • languageType (LanguageType enum)

Meter.CommandResult — generic command result

  • result (int)
  • errRes (int)

Meter.CommandResult2 — generic command result with error code

  • result (int)
  • errorCode (int)
  • command (int)

BluetoothOuterClass.Bluetooth — main telemetry (HH40/child bike)

Used for HH40 vehicle data (see data-fields.md for field list).


5. Authentication / Encryption

TBox Auth (450-series motorcycle) — 3-step

  1. Step 1 (App → Bike, code 0x5A): Send Meter.AuthPackage with info = hex-decoded encryptValue from server.
  2. Step 1 response (Bike → App, code 0x5B): Receive Meter.TboxRandomNum.codec (encrypted random number string).
  3. Step 2 (App → Bike, code 0x5C): Send Meter.RandomNum with sn = AES-256/ECB/PKCS7 decrypt of codec using key from server. (Uses BouncyCastle provider.)
  4. Auth result (Bike → App, code 0x5D): Meter.TboxAuthResult.result — 0 = success.
  5. On success, connectStatus advances to 6 (authenticated).

Keys: encryptValue, key, iv come from EncryptInfoBean, which is passed in from the server API (not hard-coded in BLE layer). AES algorithm: AES/ECB/PKCS7Padding via BouncyCastle.

CF110 Auth (child bike) — 4-step challenge-response

  1. App generates 16-char random string (mRandomStr).
  2. App reads MAC+password from scan record (Cf110Utils.getBleMacAndPsw()): bytes [0..5] = MAC, bytes [6..12] = 7-char password.
  3. Auth step 1 (code 94): Send AES/ECB/NoPadding encrypt of mRandomStr using key "CFMOTOHTV" + password.
  4. Device replies with code 91: encrypted challenge bytes.
  5. App decrypts with same AES key → mDecryptByte.
  6. Auth step 4 (code 92): Send mDecryptByte.
  7. Device replies with code 95: if decrypted content matches mRandomStr, send code 96 with [0x00] → auth OK.
  8. Auth confirmed (code 93): device sends auth-OK notification.

6. Connection Flow Summary

See connection-flow.md for detailed sequence. Brief:

  1. Scan by MAC address (stored from server binding).
  2. On connection success: set notify on CHARACTERISTIC_UUID_NOTIFY.
  3. Set MTU to 185.
  4. Run auth sequence (steps above).
  5. On connectStatus == 6: keep-alive loop begins (ping every 2s, 4s timeout).
  6. Commands sent on CHARACTERISTIC_UUID_WRITE, responses received on notify.

7. Scan / Advertisement

  • Manufacturer Specific Data ID: 53199 (decimal) = 0xCFCF — used by CF110 device type. The scan record bytes contain MAC (6 bytes) + password (7 bytes).
  • Device name prefixes (for CarFilter):
    • 9RH0N → CF110-AY10
    • EC30E → CF1000DY
    • ED20E → CF650DY
    • Other devices scanned by MAC address directly.
  • Scan mode: SCAN_MODE_LOW_LATENCY (mode 2), report delay 0, hardware batching enabled.
  • Scan timeout: 15000 ms for youth devices.
  • Reconnect delay: 5000 ms on passive disconnect, 10000 ms on connect fail (youth path). 2000–5500 ms on TBox path.

8. Control Code ↔ Protobuf Message Mapping (450-series TBox)

Confirmed from TboxControlCode.java and BleModel.java. All messages are in com.cfmoto.proto.Meter.

CodeDirectionProtobuf Message
0x0AApp→BikeOperate4g
0x0BApp→BikeChargeStatus
0x0CApp→BikeOperate4gComplex
0x15App→BikePatchObtainInfoControl
0x5AApp→BikeAuthPackage
0x5BBike→AppTboxRandomNum
0x5CApp→BikeRandomNum
0x5DBike→AppTboxAuthResult
0x65App→BikeTheme
0x66App→BikeNavi
0x67App→BikeLock or Heartbeat (ping=1 for heartbeat)
0x68App→BikePreference
0x69App→BikeDisplay
0x6AApp→BikeFindCar
0x6BApp→BikeLightControl
0x6CApp→BikeHeartbeat (keep-alive)
0x71App→BikeChargeSetting
0x79App→BikeKL15
0x8ABike→AppCommandResult2
0x8BBike→AppCommandResult
0x8CBike→AppCommandResult2
0x95Bike→AppPatchObtainInfoResult
0xE7Bike→AppCommandResult
0xEABike→AppCommandResult
0xEBBike→AppCommandResult
0xF9Bike→AppCommandResult