Table of Contents
CAN bus access
There are two very different ways onto the vehicle bus. Know which is which.
Route A — via the companion MCU (works for control)
The head unit talks to a companion MCU over UART (/dev/ttymxc3 @115200). The MCU bridges to
the vehicle CAN bus. The UART is owned by the carmanager CarService; other apps reach it through
a system service (AIDL). The MCU frame carries a “send CAN control frame” address and a control-type
selector (air-conditioning, charging, dashboard, …).
This is the route that actually reads decoded values and sends control commands. The head unit never sees raw CAN frames here, only decoded register values.
Route B — the OBD2 port (diagnostics, not control)
The OBD2 socket is a query-only diagnostic gateway. Through it you can enumerate all 24 on-board ECUs, read their fault codes and data (including a live battery-measurement block), and clear codes — but you cannot control anything without the manufacturer's Security-Access key. It does not carry the internal broadcasts, so it is no use for sniffing live signals. Full write-up — the ECU map, what is readable, how to decode raw values, clearing codes, and the safety caveats:
Safety checklist for tapping the internal (TBox) CAN bus
The internal bus is live and possibly safety-relevant (unlike the isolated OBD2 gateway):
- Listen-only (silent) mode first. A normal CAN node still transmits ACK bits, and at the wrong bitrate emits error frames that can disturb real ECUs. Use the adapter's hardware listen-only mode to identify bus/bitrate before ever transmitting.
- Measure termination. With the car off, measure CAN-H/CAN-L: ~60 Ω = already terminated both ends (add nothing); ~120 Ω = only one end. Keep the stub short.
- Confirm the bitrate (sweep 500k → 250k → 125k in listen-only) before going active.
- Non-destructive tap — back-probe or T-tap; never cut or pierce the harness. Tie adapter ground to chassis ground. Never short CAN-H/L to 12 V or to each other.
- Parked only. Connect with the car asleep, then wake it to capture. Never probe a bus while the vehicle could move.
- Identify the right bus by correlation — trigger a known signal (e.g. AC on, or a lock/unlock) and watch which frames change.
Decoded signal keys
The head unit works with a registry of ~461 action-ID keys
— the values it reads (GET) and commands it sends (SET) over the MCU/CAN bridge. These are
the CarService.getInt(id) / sendCmd(id,…) keys, not raw CAN arbitration IDs (those sit a
layer below in the MCU protocol).
→ Full list, grouped by domain: CAN signal reference. The battery/powertrain subset — the signals most people ask for — is below.
Battery & powertrain
The U5 has a split HV pack (A + B), each reporting its own SoC / voltage / current. Read with
getInt(id) while the car is awake.
| Signal | ID | Constant | Unit / note |
|---|---|---|---|
| Overall SoC | 3335 | WARN_BMS_STATE_OF_CHARGE | % |
| Pack A (main) voltage | 3455 | CARSET_PACK_BMS_VOLTAGE | V — live sample ≈ 356 V |
| Pack A (main) current | 3456 | CARSET_PACK_BMS_CURRENT | A |
| Pack A SoC | 3457 | CARSET_PACK_A_SOC | % |
| Pack B voltage | 3458 | CARSET_PACK_B_VOLTAGE | V |
| Pack B current | 3459 | CARSET_PACK_B_CURRENT | A |
| Pack B SoC | 3460 | CARSET_PACK_B_SOC | % |
| Driving range | 3461 | CARSET_VCU_DRIVING_RANGE | km |
| Energy mode | 3462 | CARSET_VCU_ENERGY_MODE | |
| BMS warning lamp | 3323 | WARN_BMS_WARN_LAMP | telltale |
| Thermal-runaway warning | 3340 | WARN_BMS_THERMALRUNAWAY | telltale |
| Charge time | 492 | CARSET_CHARGE_TIME | SET/est. |
| Discharge (V2L) request | 491 | CARSET_DISCHARGE_REQ | SET |
No State-of-Health (SoH). The firmware decodes SoC but there is no SoH / capacity / cell-voltage key — SoH would have to come from a raw BMS CAN frame or a UDS diagnostic read (see roadmap).
Reading live (adb, car awake): the canbus service logs decoded values under logtag
Canbus_SET (e.g. D/Canbus_SET: mPackBMSVoltage: 355.9). The vehicle CAN only broadcasts while
the car is awake — values go quiet a short while after it parks/locks.
Probing signals live
The ADC Telemetry app has a built-in probe (it already binds ICanbusService), so you
can read or command any key from a shell — handy for checking whether a signal really
exists, what it reports, and in which state. Build/install and signing basics are on the
development page.
Read — geti = getInt, getf = getFloat, geta = getIntArray:
S=com.example.carmqtt/.BridgeService adb shell am startservice -n $S --es op geti --ei key 3457 # pack-1 SoC adb shell am startservice -n $S --es op getf --ei key 3455 # pack-1 voltage adb shell am startservice -n $S --es op geta --ei key 3464 # doors [FL,FR,RL,RR] adb logcat -d -s CarMqtt.Probe # read the results
Example output:
getf key=3455 -> getFloat=355.5 geti key=3460 -> getInt=0 (pack-2 SoC — 0: this car is single-pack) geta key=3464 -> getIntArray=[1, 0, 0, 0] (driver door open)
Command — send = sendData(action,val). ⚠ this actuates the vehicle — only in a safe state:
adb shell am startservice -n $S --es op send --ei key 103 --ei val 1 # A/C ON
Notes: after (re)installing the app give it ~1 s to rebind the canbus service (a probe fired too
early logs canbus service NOT bound); a value of -1 / 0 usually means the signal is at its init
default (not populated on this car — as with pack 2).