Ashin Walpola 75d6d3b85c Receive signed ITS messages and forward each at its declared length
Signed packets. A GeoNetworking Basic Header NextHeader of 2 means a
TS 103 097 (IEEE 1609.2) envelope follows, with the Common Header
inside it. gn_unwrap_its rejected all of these, and most real traffic is
signed: the 2026-08-17 capture holds 157 signed frames from 15 source
MACs against 2 unsecured stations. It now opens a COER-encoded
signedData, or a bare unsecuredData, and parses the inner packet as
before. The inner packet comes first inside tbsData, so the certificate
and signature are never parsed, and the signature is not verified - the
firmware has no trust store. Such messages reach the phone with the new
V2X_RX flags bit1, signed but not verified. The app reads only bit0 and
is unaffected until it learns the flag. Encrypted payloads, nested
signing and the legacy v1.2.1 envelope are still rejected. All 157
recorded signed frames have the layout this reads, in all three COER
length forms, and asn1tools decodes every envelope to the same inner
packet.

Payload bounds. Every frame recorded through the ESP32-C5's promiscuous
RX, about 15 000 of them, ends in 8 bytes that are not part of the
802.11 frame and not a valid FCS. obu-firmware reads frames through the
same API and took the rest of the frame as the message, so it forwarded
those 8 bytes to the phone after every message. UPER decoders stop where
the message ends, so nothing visibly broke, but the bytes cost serial
bandwidth and 8 bytes of the DENM's headroom, and they stayed attached
wherever raw payloads were stored or passed on. The payload is now
exactly what the Common Header's payload-length field declares, which is
also what separates a signed message from its signature.

A frame longer than main.c's 800-byte capture buffer is now reported as
truncated instead of being forwarded cut off, and counted as an oversize
drop through the new serial_link_note_oversize_drop, as it was when the
cut-off frame failed serial_link's size check.

Host tests in obu-firmware/test/host build the firmware sources
unmodified with MSYS2 gcc; `make` runs all three.
- test_chain: frames from the firmware's TX code checked byte by byte
  against EN 302 636-4-1 and parsed back, including hand-built signed
  frames, the payload-length rule, the RX trailer, and every truncation
  length against a no-access guard page. 1731 checks, 0 failures.
- test_replay and check_replay.py: all 15 145 recorded frames through
  gn_unwrap_its, cut to 800 bytes as on the board, and re-derived
  independently in Python with the envelope decoded by asn1tools. They
  agree on every record; 15 131 accepted, 157 of them signed. 11 043 of
  the 11 106 distinct messages re-encode byte-identically. The other 63
  fail the same way with the old 8 bytes put back, so the boundary is
  not the cause: 5 are our own CAMs from before the 2026-08-20
  yawRateConfidence fix, and the rest, from other stations, are a
  follow-up in TODO.md.
- fuzz_gn_unwrap: random edits of every recorded frame, each run against
  the guard page. 50 000 000 iterations, no crash.

obu-firmware/test/pcap_gn_tally.py tallies GeoNetworking header fields
per station over captures; it is how the other stations' lifetimes were
measured. TODO.md collects what is still open, including the on-air
check for this change: it builds on IDF 6.1 but has not been flashed.
2026-09-11 20:19:40 +02:00
2026-08-10 11:38:01 +02:00
2026-06-03 14:51:31 +02:00
2026-06-03 14:51:31 +02:00

MicrOBU Android App

Android companion app for the micrOBU; a compact V2X on-board unit developed by HAW Hamburg and consider it GmbH for vulnerable road users (cyclists, e-bike riders, pedestrians).

The app is the HMI for the OBU hardware and, on one of the two supported hardware paths, the entire V2X protocol stack. It handles V2X message display, use case detection, sensor collection, trip recording, and OBU communication over USB-C.

Platform: Android (Kotlin) / Min SDK: 29 (Android 10) / Target SDK: 36 / Version: 0.5.0 (Phase 03)

Project goal

Demonstrate V2X communication with at least one C2C-CC bicycle safety use case working on the ESP32-C5, specifically intersection movement assist: a car approaching an intersection on a path that conflicts with the rider's.

That use case is driven entirely from the periodic position and kinematics vehicles broadcast. It needs no traffic light state and no intersection lane geometry, which is why several scope decisions in this repository look deliberately narrow.

Two hardware paths

The project began against the consider it CiT One and later added the ESP32-C5 as a second option. Both paths are supported at runtime and selected by the rider in Settings > OBU Hardware.

consider it CiT One ESP32-C5
What it is Complete V2X on-board unit Development board acting as a plain radio
Transport IP over USB tethering, MQTT Framed binary protocol over USB CDC serial
Reaches the phone as Processed JSON Raw ASN.1 UPER bytes
V2X stack lives On the OBU On the phone, except the radio and GeoNetworking
Own CAM generated by The OBU, autonomously The phone, transmitted on the phone's clock
DENM trigger Available (manual test tool) Not available
Needs a network Yes, internally No

Both paths converge at CamUseCaseRepository, which normalises whatever arrived into the domain Cam type. Everything above that point, including the entire use case detection engine and all UI, is shared and transport-agnostic. That is what makes the ESP32-C5 a drop-in second OBU rather than a fork of the application.

What it does

Real-time V2X monitoring; live CAM, DENM and SPATEM with a station list, active hazards, live signal phase, and a map view. On the CiT One path the topic viewer additionally shows whatever the broker publishes, grouped by topic with pretty-printed JSON and TX/RX badges.

CAM-based use case detection; correlates the rider's own state with a short per-station history of received CAMs to evaluate five C2C-CC bicycle safety use cases (IMA-B, IMA-S, RTW-B, LTW-B, SMVA/BCW-B) and raises alerts under the three-tier Info / Awareness / Warning model. None of these use cases generates a DENM.

Phone-generated CAM; on the ESP32-C5 path the app builds a CAM from live GNSS and IMU, UPER-encodes it, and pushes it down the serial link for the board to broadcast over ITS-G5.

DENM transmission; CiT One path only. Triggers the stationary vehicle profile (hln-sv, causeCode 94) via the consider it Use Case API. This is a manual antenna and range test tool. It is never triggered by a detected event or a use case alert, and the control is hidden entirely on the ESP32-C5 path.

Trip recording; foreground service records all sensor streams and detects cycling manoeuvres (braking, turning, stopping) using orientation-independent signal processing. Works fully offline with no OBU connected. The detected manoeuvres are neither shown nor stored - their only effect is to raise the CAM transmit rate through the manoeuvre on the ESP32-C5 path.

Trip review; past trips displayed as a route on an OpenStreetMap layer, with duration and distance.

CSV export; every sensor sample written to a timestamped CSV in real time. Trip exports additionally include the V2X messages received and their RSSI. Shareable via the standard Android share sheet.

Architecture

MVVM with Repository pattern throughout. Jetpack Compose for all UI (no XML layouts). Hilt for dependency injection.

ui/screens/         Compose screens (Dashboard, Record, Trips, V2X Monitor, Settings...)
ui/navigation/      Navigation graph and bottom nav bar
viewmodel/          MqttViewModel, SensorViewModel, TripRecordingViewModel
data/mqtt/          MQTT repository, Paho client, exponential-backoff reconnection
data/transport/     UsbSerialTransport, SerialFrame, UsbNetworkDetector, ObuHardware
data/cam/           CamUseCaseRepository; where both hardware paths converge
data/db/            Room database (sessions, trips, detected events, V2X messages)
data/               SensorRepository, TripRepository, CsvExporter, TripExporter
domain/asn1/        BitReader/BitWriter and the CAM, DENM and SPATEM UPER codecs
domain/usecase/     UseCaseDetectionEngine, UseCaseDetectionConfig, AlertLevel, GeoMath
domain/detection/   EventDetector, RunningStats sliding window (orientation-independent)
domain/cam/         Cam, CamParser, PhoneCamBuilder, CamTransmitConfig
service/            TripRecordingService, CamTransmitLoop, CamPinger
obu-firmware/       ESP32-C5 firmware (serial link, GeoNetworking, 802.11 OCB, raw TX)
asn1/               Vendored ETSI ASN.1 modules the codecs are verified against

The domain/ packages contain no Android imports. That is what makes the 41-test JVM suite possible without an emulator or instrumentation.

Connectivity

USB-C on both hardware paths. Bluetooth is not implemented and remains an open question in the requirements.

Transport Path Status
USB-C tethering (IP + MQTT) CiT One Active
USB-C serial (framed binary) ESP32-C5 Active
Wi-Fi CiT One Developer builds only
Bluetooth Either Not implemented

On the CiT One path the MQTT broker runs on the OBU (Mosquitto 2.0.11, port 1883); Android USB tethering exposes it as a virtual Ethernet interface at 192.168.42.x and the app auto-detects the gateway IP on plug-in. On the ESP32-C5 path there is no network layer at all: a private framed protocol runs over the board's native USB-C port as a CDC-ACM device.

Verification

The app hand-encodes and decodes ETSI messages bit by bit on the ESP32-C5 path, which is the highest-risk code in the project. Round-trip tests through the project's own codecs structurally cannot catch a shared mistake about a field's bit width, and this project shipped exactly that bug three times (CurvatureCalculationMode, the GeoNetworking reserved bytes, yawRateConfidence). Phone and ESP32 agreed with each other and with nothing else.

Verification therefore uses an independent oracle: asn1tools compiled from the ETSI modules vendored in asn1/.

  • Golden-byte fixtures assert exact encoder output, with expected values produced by the oracle rather than by this encoder.
  • Bulk replay compares every field over real captures: 79,042 SPATEMs and 1,885 DENMs, zero mismatches.
  • Off-air confirmation: 26 of this project's own CAMs captured back by an independent receiver, all accepted.

Never regenerate a golden fixture from this project's own encoder output. See asn1/README.md.

Documentation

Document Audience
docs/MicrOBU-User-Guide.docx Riders. Setup, screens, what the alerts mean, troubleshooting
docs/MicrOBU-Technical-Documentation.docx Supervisors and stakeholders. Architecture, message path, verification, results, decisions
docs/01-requirements-traceability.md Requirements chapters 0 to 13 mapped to implementation and evidence
05-obu-bench-test-2026-08-25.md Bench campaign T1 to T9, measured results
04-transmit-setup.md Transmitter bring-up, radio configuration diagnosis, the TX bypass
obu-firmware/FLASHING.md Toolchain setup, flashing, phone-to-board bring-up checklist
asn1/README.md ASN.1 module provenance and the fixture regeneration rule
docs/references.bib Standards references as BibTeX

Key dependencies

Library Purpose
Jetpack Compose + Material3 UI
Eclipse Paho MQTT CiT One path communication
usb-serial-for-android ESP32-C5 path communication (custom probe table for Espressif VID/PID)
Room Local database
Hilt Dependency injection
OSMDroid Trip review and live V2X map
DataStore Settings persistence
FusedLocationProviderClient GNSS

Getting started

  1. Open in Android Studio and build the app module. Gradle 8.10.2.
  2. Connect a device running Android 10+ (API 29).
  3. Choose your hardware in Settings > OBU Hardware.

CiT One path. Plug the phone into the OBU via USB-C, enable USB tethering on the phone, and the app detects the interface and connects automatically. Broker IP can be overridden in Settings > Connection.

ESP32-C5 path. Flash obu-firmware/ (see obu-firmware/FLASHING.md), then plug the phone into the board's native USB-C port, not the UART bridge port used for flashing. Tap Connect and grant the USB permission. Use the CAM Pinger on the V2X screen to verify the link and radio without starting a trip.

Standalone trip recording. No OBU required. Go to the Record tab and tap REC.

The Wi-Fi transport (broker at 192.168.3.202) remains available in developer builds via Settings > Developer.

Firmware and app must be flashed and installed together: SERIAL_LINK_MAX_PAYLOAD is 512 on both sides and a mismatch silently rejects every large frame.

Status and known limitations

Bench verified against live ITS-G5 traffic on 2026-08-25: 2868 frames over 305 seconds, zero decode failures, zero USB errors, zero crashes. Not yet road validated.

  • Requirement 11.6, Phase A success criteria, is not met. The bench proves reception. It cannot prove the use case behaves correctly with two genuinely moving stations, because nothing on the bench moves. This is the main open evidence gap for the project's central claim.
  • No message signing. ETSI TS 103 097 is out of scope. Secured frames are rejected rather than mis-parsed.
  • Detection thresholds are untuned engineering estimates, not calibrated against real intersection data.
  • 512-byte serial payload cap. Roughly 70% of real road RSU SPATEMs would be dropped as oversize. Accepted deliberately: the intersection use case is CAM-driven and needs none of it.
  • No automatic reconnect after USB re-enumeration; requires a manual Connect.
  • Station IDs rotate, so they cannot identify a physical unit over time.
  • No backend, no login. Everything is on-device.
  • No MAPEM decoder, so signal groups cannot yet be associated with the rider's lane.

Project context

The micrOBU project is funded under the ZIM program (BMWK) and targets micromobility users in Hamburg. The companion app offloads processing from the compact OBU hardware to the smartphone; GNSS fusion, event detection, and on the ESP32-C5 path the full ASN.1 encoding and decoding all run on the phone to keep the OBU lightweight and power-efficient.

V2X communication uses ITS-G5 (IEEE 802.11p) at 5.9 GHz. On the CiT One path the app communicates via the consider it MQTT API v6 (processed JSON). On the ESP32-C5 path the app performs its own ASN.1 UPER encoding and decoding against the ETSI modules vendored in asn1/.

Owner: HAW Hamburg

S
Description
No description provided
Readme
21 MiB
Languages
Kotlin 64.3%
C 19.5%
C++ 12.6%
Python 2.6%
CMake 0.5%
Other 0.5%