Files
MicrOBU/TODO.md
T
Ashin Walpola 01204a2c22 Give the V2X live map its own screen, and a traffic light per SPATEM
The map was a third view mode inside the V2X Monitor's topic pane, below
the use case alert panel and the DENM/CAM TX cards. On a phone that left
it about a third of the display tall, which is not enough to see where
anything is relative to anything else - the one thing a map is for. It
is now its own destination, V2xMapScreen on route v2x_map, reached from
a map button in that screen's header. The button sits in the header
rather than the view-mode row so it is also reachable from the message
detail pane and does not move as the available modes change with the
selected hardware. The status bar and bottom nav are hidden on this
route; the screen carries its own floating back button, and system back
still works. Both hardware paths get the same screen: everything drawn
comes from CamUseCaseRepository, which already merges the CiT One's MQTT
feed and the ESP32-C5's serial feed into one set of flows.

With the map gone from the toggle row, the row offers a single choice on
the ESP32-C5 path - there is no broker there and `topics` is always
empty - so it is hidden entirely in that mode.

SPATEM markers. Hazards already drew as a warning triangle; signalised
intersections did not draw at all. They now draw as a traffic light with
one lamp lit. Two things are worth knowing, because neither is forced by
the data:

- SPATEM carries signal state but no geometry, which is MAPEM's job and
  MAPEM is not decoded. The only position available is the sending RSU's
  own CAM, so the light is drawn there, and that RSU is drawn once - as
  the light, not as a CAM pin with a light on top of it. An intersection
  whose sender has not been heard over CAM cannot be placed; the map
  says how many rather than dropping them silently.
- Which lamp lights follows the rule DashboardScreen's SignalCard
  already uses, the signal group changing soonest speaking for the
  intersection, so the same intersection reads the same way in both
  places instead of inventing a second convention.

Four drawables rather than one tinted at runtime: setTint recolours
every path in a vector, so a single shared asset would turn the whole
light one flat colour and stop it reading as a traffic light.

Marker reuse. Every incoming message recomposes the map, and the update
block cleared the overlay list and rebuilt every Marker, decoding and
mutating a fresh Drawable per marker - at up to 10 Hz per station. It
also called animateTo(own) on every update, restarting the pan animation
before it could finish. Drawables are now loaded once per alert level
and phase and shared (osmdroid sets the icon's bounds on each draw, so
one instance across markers is safe), Markers are cached by key, and the
overlay list is only reordered, which moves references without
allocating. Following uses setCenter, keeping animateTo for the one move
worth seeing: the rider asking for follow back.

Follow-own now hands over to the rider on the first touch and returns
via the location button, which lights up while following. Before this
the map could not be panned at all while traffic was flowing, since the
next CAM dragged the viewport back.

Also on the map view: tiles scaled to DPI, the floating +/- buttons off
(they sit where the thumb lands and duplicate pinch), a zoom range, and
more tile threads so a pan that exposes a screenful of new tiles is not
served two at a time.

Compiles and the unit tests pass. None of it has been seen with live
traffic; TODO.md lists the on-device checks under "Waiting on hardware",
including which of the HAW RSUs send CAM alongside SPATEM.
2026-09-15 17:23:01 +02:00

127 lines
7.8 KiB
Markdown

# TODO
Engineering to-do list. The reviewer-facing open items live in
`docs/01-requirements-traceability.md` ("Open items"); this file is the working list behind them.
## Waiting on hardware
### On-device check of the full-screen V2X live map (added 2026-09-15)
The live map moved out of the V2X Monitor's view-mode row into its own full-screen destination
(`V2xMapScreen`, route `v2x_map`), reached from the map button in that screen's header. Markers are
now cached and reused across updates instead of being rebuilt on every incoming message, and
SPATEM intersections are drawn as traffic lights at the position of the RSU's own CAM. All of that
compiles and the unit tests pass, but none of it has been seen with live traffic.
Needs: the phone with the app, plus a CAM/DENM/SPATEM source - either the CiT One, or the OBU
ESP32-C5 with a second board or a real RSU transmitting.
- [ ] Both hardware modes: tap the map button, confirm the map fills the screen (no status bar, no
bottom nav) and the back button returns to the V2X Monitor.
- [ ] Panning stays smooth while CAMs are arriving - this is what the marker reuse is for. Compare
against the old behaviour if it still judders.
- [ ] Touching the map stops it recentring; the location FAB resumes follow and lights up.
- [ ] A DENM shows the warning triangle, and a SPATEM intersection shows a traffic light with the
lamp matching the Dashboard's SignalCard for the same intersection.
- [ ] Near a real RSU: confirm the RSU is drawn once, as a traffic light, not as a CAM pin with a
light on top of it. If the RSU sends SPATEM but no CAM, the "signals not shown" note should
appear instead - worth knowing which of the two the HAW RSUs actually do.
### Over-the-air check of the GN lifetime fix (added 2026-09-11)
`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
compiled `geonet_wrap_shb` stores the new byte, but it has not been seen on air yet. Nothing else
reads this byte (`gn_unwrap.c` ignores it, the app never sees GN headers), so the app does not
need updating alongside the firmware.
Needs: the phone with the app, the OBU ESP32-C5, and a **second** ESP32-C5 running
`its-g5-receiver-firmware` to capture with.
- [ ] Flash `obu-firmware` (see `obu-firmware/FLASHING.md`).
- [ ] Connect the phone, let it send CAMs, and confirm the CAM Pinger's `tx fail` counter stays 0.
- [ ] Capture with the receiver into `its-g5-receiver-firmware/recordings/`.
- [ ] Run `python obu-firmware/test/pcap_gn_tally.py its-g5-receiver-firmware/recordings/<capture>.pcap`.
The rows for the phone's pseudonym MACs must show SHB, port 2001, lifetime `0x05`, exactly
like every other station's CAMs.
- [ ] While the phone is connected: real-station CAMs/DENMs still reach the app (RX path unchanged).
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
that it transmits correctly.
### obu-cam-transmistter yawRateConfidence fix (added 2026-09-11)
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
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
only matters if it is flashed again:
- [ ] After flashing it: capture, run `pcap_gn_tally.py`, and decode the CAM payload with
asn1tools (`py -3.11`, modules in `asn1/`).
### Signed-message reception and exact payloads (added 2026-09-11)
obu-firmware's `gn_unwrap.c` now unwraps TS 103 097 signed packets (signature not verified,
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.
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
2026-09-10 build. Needs the OBU with this build, the phone, and signed traffic - real vehicles or
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.
- [ ] Flash obu-firmware (this also carries the GN lifetime fix above).
- [ ] 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`).
- [ ] Unsigned bench traffic still decodes in the app as before (messages now arrive 8 bytes
shorter).
- [ ] The heartbeat's oversize counter still counts over-long messages (e.g. road SPATEMs).
## Set up host testing
- [x] Install MSYS2 UCRT64 gcc (done 2026-09-11: gcc 16.2.0, GNU Make 4.4.1; chosen over WSL,
vanetza is not going to be built). Setup and the PATH gotcha: `obu-firmware/test/host/README.md`.
- [x] Host round-trip test `obu-firmware/test/host/test_chain.c` (`geonet_wrap_shb` ->
`dot11p_build_frame` -> `gn_unwrap_its`, byte-checked against the standard). Done
2026-09-11: 491 checks, 0 failed. Run `make` in that folder before flashing any firmware fix.
- [x] Replay of the recorded captures (`test_replay.c` + `check_replay.py`, independent asn1tools
check). Done 2026-09-11: C and Python agree on all 15 145 records.
- [x] Mutation fuzzer `fuzz_gn_unwrap.c`, inputs against a no-access guard page. Done 2026-09-11:
50 000 000 iterations, no crash. `make` runs a 2 000 000-iteration pass every time.
## Firmware ideas from the vanetza review (2026-09-11, not started)
Suggested order after the host tests exist:
- [x] **Read secured packets (GN NextHeader=2) without verifying them.** Done 2026-09-11 in
`gn_unwrap.c`, host-verified; flagged to the phone as V2X_RX flags bit1. On-air check under
"Waiting on hardware".
- [ ] **Forward the full GeoBroadcast area**: shape (circle/rectangle/ellipse), DistanceB, angle,
appended to the V2X_RX prefix behind a capability bit. Port vanetza's `geonet/areas.cpp`
`inside_or_at_border` to the app, which currently treats every area as a circle.
- [ ] **RX filtering before the serial link**: duplicate detection for GBC (last 8 sequence numbers
per source, as vanetza does), drop our own frames, reject GN version != 1.
- [ ] **Read the DCC-MCO field** (the 4 "reserved" bytes of an SHB header): neighbours' channel
busy ratio for free.
- [ ] **Minimum TX gap in firmware** as a DCC safety net (vanetza reactive table: 60 ms relaxed ...
460 ms restrictive), with a CBR estimate in the heartbeat.
- [ ] **Generic V2X_TX message** (BTP port, SHB/GBC, traffic class, lifetime, area) so the phone can
send DENM and VAM without reflashing. Consider QoS Data frames: vanetza's Cohda receive path
drops non-QoS ones.
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.
## Follow-ups found 2026-09-11
- [ ] **App: show the signed flag.** `V2xRxFrame.parse` in `SerialFrame.kt` only reads bit0 of
the flags byte; read bit1 (signed, not verified) and show it where messages are listed.
- [ ] **Messages that do not decode with asn1tools.** In the recordings, 56 from the CiT One
(`aa:f8:76:7d:bd:ad`: 54 CAMs of 245 bytes, 2 DENMs of 402 bytes) and one 218-byte CAM from
`6e:94:03:1b:05:26` fail against `cam_1_4_1`/`denm_1_3_1` + `cdd_1_3_1_1`, with or without the
old trailing bytes. A newer module version on the sender, or a sender bug; check what the
app's decoders make of them (`check_replay.py` lists the records).