Record the on-air verification and how to run the bench beacon

Both firmware fixes are now confirmed over the air, with the sniffer board
capturing and asn1tools judging the result.

GN lifetime: our station transmits 0x05 (1 s), the value both bench stations
use, where the August captures show 0x83 (3200 s) for the same frames. 397 CAMs
from station 999999 decode and re-encode byte-identically, so the whole
transmit chain is right on the wire, not only in the host tests.

yawRateConfidence: the bench beacon, flashed to a spare board, sends CAMs that
decode and re-encode byte-identically as well (72 of 72 from station
0x0BADC0DE). NOTES.md now says how to flash that beacon and how to check what
it sends, including that it shares the phone pinger's MAC and the two are told
apart by station ID.

The new firmware also runs on the OBU with the phone attached: CAM, DENM and
SPATEM from the bench stations all keep decoding in the app now that messages
are cut to the length their header declares.

Signed reception stays open. The CiT One transmits unsigned and nothing else
here signs, so it needs real roadside traffic, the CiT One switched to signed
mode, or a replay firmware on a spare board.

Two bench facts worth not rediscovering are recorded too: opening COM3's
console resets the OBU and drops the phone's USB link, and every capture taken
before today is truncated at its first corrupted record, because the receiver's
console inserts a CR before every 0x0a byte of the binary pcap stream.
live_capture.py now undoes that, a change that lives in the receiver repo and
is not part of this commit.
This commit is contained in:
Ashin Walpola
2026-09-14 13:33:47 +02:00
parent 0f06cdb339
commit 277c6b20f4
2 changed files with 87 additions and 19 deletions
+41 -19
View File
@@ -9,20 +9,24 @@ Engineering to-do list. The reviewer-facing open items live in
`geonet.c` now writes GN lifetime `0x05` (1 s) instead of `0x83`, which decoded to 3200 s. Changed `geonet.c` now writes GN lifetime `0x05` (1 s) instead of `0x83`, which decoded to 3200 s. Changed
in both `obu-firmware` and `obu-cam-transmistter`. Both still build (IDF 6.1 / 5.5.4), and the in both `obu-firmware` and `obu-cam-transmistter`. Both still build (IDF 6.1 / 5.5.4), and the
compiled `geonet_wrap_shb` stores the new byte, but it has not been seen on air yet. Nothing else compiled `geonet_wrap_shb` stores the new byte. Confirmed on air 2026-09-14. Nothing else
reads this byte (`gn_unwrap.c` ignores it, the app never sees GN headers), so the app does not reads this byte (`gn_unwrap.c` ignores it, the app never sees GN headers), so the app does not
need updating alongside the firmware. need updating alongside the firmware.
Needs: the phone with the app, the OBU ESP32-C5, and a **second** ESP32-C5 running Needs: the phone with the app, the OBU ESP32-C5, and a **second** ESP32-C5 running
`its-g5-receiver-firmware` to capture with. `its-g5-receiver-firmware` to capture with.
- [ ] Flash `obu-firmware` (see `obu-firmware/FLASHING.md`). - [x] Flash `obu-firmware` (done 2026-09-14 on COM3; flash backed up first to
- [ ] Connect the phone, let it send CAMs, and confirm the CAM Pinger's `tx fail` counter stays 0. `Documents/micrOBU_workspace/firmware-backups/COM3-2026-09-14-before-secured-rx.bin`).
- [ ] Capture with the receiver into `its-g5-receiver-firmware/recordings/`. - [x] Capture with the receiver (COM8) into `its-g5-receiver-firmware/recordings/`.
- [ ] Run `python obu-firmware/test/pcap_gn_tally.py its-g5-receiver-firmware/recordings/<capture>.pcap`. - [x] `pcap_gn_tally.py` on capture_20260914_132126.pcap: our station sends SHB, port 2001,
The rows for the phone's pseudonym MACs must show SHB, port 2001, lifetime `0x05`, exactly lifetime `0x05`, same as both bench stations. It was `0x83` in the August captures.
like every other station's CAMs. - [x] Real-station CAMs/DENMs/SPATEM still reach the app (logcat: `handleCamUper`,
- [ ] While the phone is connected: real-station CAMs/DENMs still reach the app (RX path unchanged). `handleDenmUper`, `handleSpatUper` all decoding, 2026-09-14).
- [x] Our own CAMs decode on air: 397 frames from station 999999 decode with asn1tools and
re-encode byte-identically.
- [ ] Confirm the CAM Pinger card's `tx fail` / oversize / CRC counters are 0 (needs a look at the
phone; not readable from the PC).
Partial check possible with one board and no phone: flash it, `idf.py -p COMx monitor`, and look Partial check possible with one board and no phone: flash it, `idf.py -p COMx monitor`, and look
for `OCB @ 5900 MHz - TX/RX armed`. That proves the new build boots and brings the radio up, not for `OCB @ 5900 MHz - TX/RX armed`. That proves the new build boots and brings the radio up, not
@@ -33,11 +37,12 @@ that it transmits correctly.
Its `cam.c` (compiled into that firmware) wrote `yawRateConfidence` as 3 bits / 7 instead of Its `cam.c` (compiled into that firmware) wrote `yawRateConfidence` as 3 bits / 7 instead of
4 bits / unavailable(8), the bug the app fixed on 2026-08-20. Fixed in it and in obu-firmware's 4 bits / unavailable(8), the bug the app fixed on 2026-08-20. Fixed in it and in obu-firmware's
reference copy; asn1tools now decodes the CAM and re-encodes it byte-identically, and it builds on reference copy; asn1tools now decodes the CAM and re-encodes it byte-identically, and it builds on
IDF 5.5.4. No board runs this firmware right now (the production OBU runs obu-firmware), so this IDF 5.5.4. Since 2026-09-14 the spare board on COM10 runs it as a bench beacon:
only matters if it is flashed again:
- [ ] After flashing it: capture, run `pcap_gn_tally.py`, and decode the CAM payload with - [x] Done 2026-09-14: flashed on COM10 and captured on COM8. All 72 CAMs from station
asn1tools (`py -3.11`, modules in `asn1/`). 195936478 (0x0BADC0DE) decode with asn1tools and re-encode byte-identically, so the
4-bit yawRateConfidence is right on air. COM10 now runs this beacon rather than
obu-firmware - reflash it if the spare is needed as an OBU again.
### Signed-message reception and exact payloads (added 2026-09-11) ### Signed-message reception and exact payloads (added 2026-09-11)
@@ -45,17 +50,21 @@ obu-firmware's `gn_unwrap.c` now unwraps TS 103 097 signed packets (signature no
reported as V2X_RX flags bit1) and cuts every message to the length its header declares, dropping reported as V2X_RX flags bit1) and cuts every message to the length its header declares, dropping
the 8 bytes the chip's RX appends to each frame, which were forwarded to the phone until now. the 8 bytes the chip's RX appends to each frame, which were forwarded to the phone until now.
Verified on the host (`obu-firmware/test/host`: chain, replay of all recordings against asn1tools, Verified on the host (`obu-firmware/test/host`: chain, replay of all recordings against asn1tools,
50M-iteration fuzz) and built on IDF 6.1, but not flashed: the production OBU still runs the 50M-iteration fuzz) and flashed to the production OBU on 2026-09-14. The remaining gap is signed
2026-09-10 build. Needs the OBU with this build, the phone, and signed traffic - real vehicles or traffic to receive: real vehicles or
RSUs, since the bench CiT One sends unsigned. A second ESP32 running the receiver firmware is RSUs, since the bench CiT One sends unsigned. A second ESP32 running the receiver firmware is
optional, but shows what was on air at the time. optional, but shows what was on air at the time.
- [ ] Flash obu-firmware (this also carries the GN lifetime fix above). - [x] Flash obu-firmware (done 2026-09-14, COM3).
- [x] Unsigned bench traffic still decodes in the app, with messages now cut to their declared
length (CAM, DENM and SPATEM all decoding in logcat after the flash).
- [ ] Near signed traffic: signed CAMs/DENMs appear in the app, and a simultaneous capture shows - [ ] Near signed traffic: signed CAMs/DENMs appear in the app, and a simultaneous capture shows
them on air (`pcap_gn_tally.py` lists them as `secured`). them on air (`pcap_gn_tally.py` lists them as `secured`). NOT possible at this bench: the
- [ ] Unsigned bench traffic still decodes in the app as before (messages now arrive 8 bytes CiT One transmits unsigned (`ItsGnSecurity = 0`) and nothing else here signs. Needs a drive
shorter). past real RSUs, the CiT One switched to signed mode if its API allows, or a replay firmware
- [ ] The heartbeat's oversize counter still counts over-long messages (e.g. road SPATEMs). on a spare board that re-transmits the recorded signed frames.
- [ ] The heartbeat's oversize counter still counts over-long messages. Not exercised at the
bench: the SPATEMs here are ~340 bytes on air, far below the cap.
## Set up host testing ## Set up host testing
@@ -92,6 +101,19 @@ Suggested order after the host tests exist:
Dropped: building vanetza as a GN/BTP oracle. Real captures (`pcap_gn_tally.py`), the host Dropped: building vanetza as a GN/BTP oracle. Real captures (`pcap_gn_tally.py`), the host
round-trip test and `asn1tools` for UPER cover what it would have checked. round-trip test and `asn1tools` for UPER cover what it would have checked.
## Follow-ups found 2026-09-14
- [ ] **Commit the `live_capture.py` fix in the receiver repo.** Its console inserts a CR before
every LF, which also hits every 0x0a byte of the binary pcap stream, shifting pcap record
headers and frames. A 787 KB capture parsed cleanly for only 82 of ~2000 records, and DENMs
showed up on nonsense BTP ports. `undo_crlf()` now reverses it on the raw stream before
framing; afterwards a capture parsed to EOF and DENMs read as port 2002. This is the
"occasional byte inserted mid-frame" in the older recordings, so **every capture taken before
2026-09-14 is truncated at its first corrupted record** - re-measure anything derived from
them. The change is in the `its-g5-receiver-firmware` repo, uncommitted.
- [ ] **Do not open COM3's console while the phone is attached.** Opening it toggles DTR/RTS on the
CH343 and resets the OBU, which drops the phone's USB link and needs a manual Connect.
## Follow-ups found 2026-09-11 ## Follow-ups found 2026-09-11
- [ ] **App: show the signed flag.** `V2xRxFrame.parse` in `SerialFrame.kt` only reads bit0 of - [ ] **App: show the signed flag.** `V2xRxFrame.parse` in `SerialFrame.kt` only reads bit0 of
+46
View File
@@ -51,3 +51,49 @@ Known gaps, tracked as TODOs in the source: no real GNSS (lat/long hardcoded
0), no real time source (detectionTime/referenceTime hardcoded 0, decodes as 0), no real time source (detectionTime/referenceTime hardcoded 0, decodes as
2004-01-01), fixed (non-rotating) pseudonym MAC, SHB instead of GeoBroadcast 2004-01-01), fixed (non-rotating) pseudonym MAC, SHB instead of GeoBroadcast
(no multi-hop forwarding), unsecured (no IEEE 1609.2 signing). (no multi-hop forwarding), unsecured (no IEEE 1609.2 signing).
## Running it as a bench beacon
This firmware needs no phone: it beacons a CAM every second by itself
(`TX_INTERVAL_MS`) from station `0x0BADC0DE` (195936478), stationType 5
(passengerCar), at the hardcoded bench position, under the fixed MAC
`02:00:00:00:00:01`, on 5900 MHz. That makes it the quickest way to put known,
repeatable traffic on air, and it is how the 4-bit `yawRateConfidence` encoding
was confirmed over the air on 2026-09-14.
A board with only one USB-C port is fine. This firmware's console is on UART0,
so such a board shows no log output, but nothing here needs the console.
Flash it from the toolchain terminal (ESP-IDF 5.5.4, see the table above):
```powershell
cd C:\Users\Ashin\AndroidStudioProjects\MicrOBU\obu-cam-transmistter
idf.py -p COM10 -b 921600 flash
```
Or flash the existing build without any toolchain terminal:
```powershell
cd obu-cam-transmistter\build
C:\Espressif\python_env\idf5.5_py3.11_env\Scripts\python.exe -m esptool --chip esp32c5 -p COM10 -b 921600 write_flash --flash_mode dio --flash_freq 80m --flash_size 2MB 0x2000 bootloader/bootloader.bin 0x8000 partition_table/partition-table.bin 0x10000 obu_firmware.bin
```
It starts beaconing as soon as it boots, so there is nothing to start by hand,
and unplugging it is how you stop it.
**It transmits under the same MAC as the phone's CAM pinger**, so on air the two
are told apart by station ID (195936478 here, 999999 for the pinger), never by
source address.
To see what it is sending, capture on the sniffer board and decode:
```powershell
cd its-g5-receiver-firmware
py -3.11 live_capture.py COM8
py -3.11 ..\obu-firmware\test\pcap_gn_tally.py recordings\capture_<timestamp>.pcap
```
The tally lists it as SHB / port 2001 / lifetime `0x05`. For the message itself,
decode the payload with `asn1tools` against `asn1/cam_1_4_1.asn` +
`asn1/cdd_1_3_1_1.asn`; re-encoding must return the identical bytes. On
2026-09-14, 72 of 72 frames did.