CurvatureCalculationMode is the one extensible ENUMERATED in CAM:
ENUMERATED {yawRateUsed(0), yawRateNotUsed(1), unavailable(2), ...}
UPER encodes an extensible ENUMERATED as an extension bit followed by the root
index - 1 + 2 = 3 bits. All three of our encoders wrote only the 2-bit index,
shifting yawRate and the entire low-frequency container one bit early for any
standards-compliant receiver.
It went unnoticed because every end of this project shared the mistake: the
Kotlin codec was ported bit-for-bit from cam.c, so phone and ESP32 agreed
perfectly with each other and with nothing else. Confirmed against the ETSI
ASN.1 in the C-ITS-Parser checkout, where rasn marks this type - and only this
type - #[non_exhaustive].
Fixed in all three copies of the encoder (app CamUperCodec.kt,
obu-firmware/main/cam.c, obu-cam-transmistter/main/cam.c) plus the decoder,
which now rejects rather than misreads a set extension bit. Frame size is
unchanged at 43 bytes. Transmitter reflashed and the phone decodes its CAMs.
Also in this change:
- serial_link: skip send_frame entirely when no USB host is attached, and raise
the tx mutex timeout above the worst-case hold. With the phone unplugged every
write blocked its full timeout while holding the lock, so forwarded CAM_RX
traffic starved the 1 Hz heartbeat - observed as "tx mutex timeout, dropping
frame" on the console, and it would have tripped the phone's link watchdog.
Verified gone on hardware.
- Log decoded and failed CAMs in CamUseCaseRepository. "The app shows nothing"
had two indistinguishable causes; a silent `?: return` made this bug much
harder to find than it needed to be.
- Remove the ESP32 send-only/send-and-receive toggle. Reception can't be
disabled in firmware (raw TX only works while promiscuous), so it was an
app-side filter pretending to be a radio control.
- V2X monitor follows the serial link state on the ESP32 path instead of MQTT,
which is permanently disconnected there; CAM intake is gated on the link being
up, and engine state is cleared when it drops.
- About screen: 0.5.0, Phase 03.
- Track obu-cam-transmistter, the bench CAM transmitter. Its cam.c is compiled
(unlike obu-firmware's reference copy) and must stay bit-identical to the other
two - this commit is what that coupling costs when it's broken.
- Document the two-toolchain split: this project builds on IDF 5.5.4, obu-firmware
on the pinned 6.1. Exporting both in one shell fails confusingly.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
5.1 KiB
obu-firmware — setup & flashing notes
Two toolchains - use a dedicated terminal for each
This project builds against the receiver-firmware's pinned ESP-IDF 6.1.
The separate obu-cam-transmistter project builds against the global ESP-IDF
5.5.4. Exporting both in one PowerShell window fails: the second export
inherits the first's IDF_PYTHON_ENV_PATH and reports every Python dependency
as unmet. Don't run install.bat to "fix" that - open a fresh terminal, or
clear the state with $env:IDF_PYTHON_ENV_PATH = $null; $env:IDF_PATH = $null.
Every new PowerShell session
Activate the toolchain (obu-firmware has no esp-idf of its own — reuse the receiver firmware's already-installed checkout):
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
C:\Users\Ashin\Documents\micrOBU_workspace\its-g5-receiver-firmware\esp-idf\export.ps1
idf.py --version
Build & flash
cd C:\Users\Ashin\AndroidStudioProjects\MicrOBU\obu-firmware
idf.py set-target esp32c5 # only needed once per clean build folder
idf.py build
idf.py -p COM5 -b 921600 flash monitor
Swap COM5 for whatever port the ESP32-C5 enumerates as (Device Manager →
Ports). monitor opens the serial console after flashing — Ctrl+] to exit.
If the build fails
- "includes X.h, provided by Y component(s)... not in the requirements
list" — IDF 5.x split the old monolithic
drivercomponent apart (esp_driver_gpio,esp_driver_uart, etc.). Add the named component toREQUIRESinmain/CMakeLists.txtand rebuild. Already fixed once foresp_driver_gpio+esp_driver_uart— if a new header comes up, same fix. - Otherwise, start clean before re-building:
idf.py fullclean idf.py build
Connecting the phone (ESP32-C5-WIFI6-KIT)
The board has two USB-C ports — use the right one:
- Native USB-C port (labeled for JTAG/native USB, up to 12 Mbps) — this
is where the phone plugs in via USB-OTG. The CAM serial link
(
serial_link.c) runs over the ESP32-C5's native USB Serial/JTAG peripheral on this port, enumerating as a CDC-ACM device under Espressif's VID/PID (0x303A/0x1001). - UART-bridge port (labeled for flashing) — this is what you use for
idf.py flash monitorfrom your PC. Leave the phone unplugged from this one; it only carriesidf.py's flashing protocol and the ESP_LOG console.
The app recognizes the ESP32-C5's VID/PID via a custom probe table in
UsbSerialTransport.kt (the default usb-serial-for-android prober doesn't
know Espressif's device IDs). If the phone doesn't detect anything when
plugged into the native port, first confirm with a tool like "USB Device
Info" (or adb shell dumpsys usb from a PC) that Android sees a USB device
at all — that isolates a bad/charge-only OTG cable from an app-side issue.
Bring-up checklist (phone <-> ESP32-C5 link)
Work down this list — each step isolates the layer below it.
- Flash and install together.
SERIAL_LINK_MAX_PAYLOADis 512 on both sides. A phone at 512 talking to firmware still at 160 (or vice versa) silently rejects every large frame at thelength exceeds max, resyncbranch. Never update one side alone. - Does Android see the device at all? Plug the phone into the native
USB-C port, hit Connect, and read logcat for
UsbSerialTransport. It logs every attached device and each device's interfaces. Empty list = cable / OTG / wrong port, below the app entirely. - Did the right interface get claimed? The C5's USB Serial/JTAG is a
composite device — expect CDC control (class 2) + CDC data (class 10) +
vendor-specific JTAG (class 255) in that dump. Compare against the
ports=count on thematched deviceline. - Is the link alive? The firmware sends a STATUS heartbeat at 1 Hz regardless of radio traffic, and the app marks the link ERROR after ~3.5 s of silence. Connected-and-staying-connected means device→host actually works.
- If it connects but no CAM_RX ever arrives — suspect DTR. The app now
asserts DTR/RTS on open (
openDevice()inUsbSerialTransport.kt), becauseCdcAcmSerialDriverdoesn't do it by default and the ESP32's USB Serial/JTAG endpoint may gate TX on the host opening the CDC line. This is still unverified on real hardware — test it both ways (with thesetDTR(true)call and with it commented out) and record the answer inserial_link.hnext to the VID/PID note, so nobody has to guess again. - Watch the counters, not just "Sent: N". The CAM Pinger card shows
consecutive write failures (phone side) and the firmware's tx-failure /
oversize-drop / CRC-error totals from the heartbeat. A rising
tx failmeans CAMs reach the ESP32 butesp_wifi_80211_txrejects them — a radio problem, not a link problem.
Notes
- No
git submodule updateneeded here — obu-firmware has no pinned submodule of its own, unlike its-g5-receiver-firmware. - Don't use the global "ESP-IDF 5.5 PowerShell" shortcut — always export from the receiver-firmware's pinned checkout, since this firmware's undocumented PHY/driver internals were verified against that specific build.