Ashin Walpola a5ad3dcc5d SPATEM receive, RSU CAM decode, and two ASN.1 encoding fixes
SPATEM over the air
- gn_unwrap.c accepts BTP-B port 2004 alongside 2001/2002. The serial protocol
  already carries the port in its V2X_RX prefix, so nothing else changed there.
  Note the crossover that makes this easy to get wrong: SPATEM is port 2004 but
  messageID 4, while MAPEM is port 2003 and messageID 5.
- SpatemUperCodec decodes SPAT down to per-signal-group phase and timing. The
  bit layout was validated by replaying 79,042 real SPATEMs - the whole
  2026-03-18 drive across 7+ RSUs plus the bench trigger - against asn1tools
  using the ETSI modules. All 79,042 matched on every field, none hit an
  unsupported branch. Two traps are pinned by tests: TimeChangeDetails is the
  one SEQUENCE here that is NOT extensible (5 optional bits, no extension bit),
  and maneuverAssistList cannot be skipped when present - it is variable-length,
  so it has to be walked to find where the next movement starts.
- The V2X list shows one row per intersection with each signal group coloured by
  phase and a countdown where the RSU supplies timing. TimeMark wraps hourly, so
  the countdown corrects for it; without that it reads hugely negative once an
  hour, precisely when someone is watching it.
- Entries expire after 15 s, much shorter than DENM's window: a traffic light
  that stopped updating is not "still green".

  Size caveat, deliberately deferred: SERIAL_LINK_MAX_PAYLOAD is still 512, so a
  SPATEM over ~498 bytes is counted as an oversize drop. The bench RSU sends 58
  bytes and is unaffected, but real road RSUs measured 555 median / 1243 max, so
  roughly 70% would not arrive. Raising the cap also requires enlarging
  RX_FRAME_MAX_LEN and moving rx_item_t off the WiFi driver's callback stack,
  where it would otherwise overflow.

RSU CAM decode
- HighFrequencyContainer is a CHOICE, and a roadside unit picks
  rsuContainerHighFrequency, which carries no kinematics at all. The decoder
  bailed on that branch, so every RSU CAM was dropped - including the bench RSU,
  which sends CAM and SPATEM from the same station id. It now decodes for
  position and stationType.
- RSU CAMs are kept out of UseCaseDetectionEngine. They arrive as a permanently
  stationary station at a fixed point, which is exactly the shape the
  stopped-vehicle and intersection-movement use cases match, and would raise a
  standing false alert for as long as the RSU was in range.

CAM transmit: yawRateConfidence
- YawRateConfidence has nine enumerands (0..8), so UPER needs 4 bits and
  "unavailable" is 8. The encoder wrote 3 bits with value 7 - one bit short and
  the wrong symbol - shifting every field after yawRate for any standards-strict
  receiver. The decoder read 3 bits too, so phone and ESP32 agreed with each
  other and with nothing else.
- This is the third instance of that exact failure mode in this project, after
  CurvatureCalculationMode and the GeoNetworking reserved bytes. A round-trip
  test through our own decoder structurally cannot catch it, so CamEncodeGolden
  Test asserts the bytes asn1tools produces instead: it decoded this encoder's
  output and re-encoded it byte-identically. Confirmed on air afterwards - 26 of
  our own CAMs captured back off the OBU's receiver, all 26 accepted, where the
  same decoder rejected them before.

DENM
- Hazards now expire 60 s after their last repetition. This needs a clock, not
  just a filter: both source flows only emit when a DENM arrives, so a sender
  that drives away or loses power would never trigger a recompute and its hazard
  would stay on screen indefinitely.
- The MQTT path was dropping every DENM for two independent reasons, both found
  by checking the payload against CI-CiT-MQTT_API_Documentation-v6 listing 2.6
  rather than guessing: the station id key is originatingStationId, and
  eventPosition IS a GeoJSON Point rather than an object containing one. Also
  parses termination (presence is the signal), sequenceNumber, stationType and
  the RFC3339 detectionTime. Note roadSideUnit is 15, not 12 - the enumeration
  has a gap after tram(11).

V2X screen
- The decoded CAM/DENM list now renders on the CiT One path too; it was gated to
  the ESP32-C5 path and CiT One fell through to the raw MQTT topic list. Those
  topics move to their own tab, hidden on the ESP32-C5 path where there is no
  broker.

Testing
- Adds org.json as a test-only dependency: the android.jar stub throws
  "not mocked" on every JSONObject call, which made the MQTT payload parsers
  untestable off-device.
- 23 V2X tests pass. EventDetectorTest's 4 failures are pre-existing and
  untouched by this change.
2026-08-20 15:47:14 +02:00
2026-08-10 11:38:01 +02:00
2026-07-24 11:41:29 +02:00
2026-06-03 14:51:31 +02:00
2026-06-03 14:51:31 +02:00
2026-06-08 16:23:08 +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 serves as the HMI for the micrOBU hardware, handling V2X message display, sensor data collection, trip recording, and OBU communication over USB-C, Wi-Fi (dev), and Bluetooth (upcoming).

Platform: Android (Kotlin) · Min SDK: 29 (Android 10) · Target SDK: 36

What it does

Real-time V2X monitoring; subscribes to the OBU's MQTT broker and displays live CAM, DENM, SPAT, MAP, and CPM messages grouped by topic with pretty-printed JSON and TX/RX badges.

DENM transmission; triggers DENM use cases (e.g. stationary vehicle warning hln-sv) on the OBU via the consider it Use Case API (v2x-uca/input/denmtrg) with a single tap.

Sensor monitoring; live readout of phone GNSS, accelerometer, gyroscope, magnetometer, and barometer alongside OBU GNSS for cross-reference.

Trip recording; foreground service records all sensor streams and detects cycling events (braking, turning, stopping) using orientation-independent signal processing. Works fully offline with no OBU connected.

Trip review; past trips displayed on an OpenStreetMap layer with detected events overlaid as coloured pins. Tap any pin for event details.

CSV export; every sensor sample written to a timestamped CSV in real time during a session. 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, V2X Monitor, Sensors, Recording, Trip History, Settings…)
ui/navigation/      Navigation graph and bottom nav bar
viewmodel/          MqttViewModel, SensorViewModel, TripRecordingViewModel
data/mqtt/          MQTT repository, Paho client, exponential-backoff reconnection
data/transport/     USB tethering detection and gateway IP resolution
data/db/            Room database (sessions, trips, detected events)
data/               SensorRepository, TripRepository, CsvExporter
domain/detection/   EventDetector, RunningStats sliding window (orientation-independent)
service/            TripRecordingService (foreground service)

Connectivity

The app uses a phased transport strategy. The MQTT client, topic subscriptions, and all UI are identical across transports; only the underlying network path changes.

Phase Transport Status
Phase 01 Wi-Fi Complete
Phase 02 USB-C tethering Active
Phase 03 Bluetooth BLE Future

The MQTT broker runs on the OBU hardware (Mosquitto 2.0.11, port 1883). In Phase 02, Android USB tethering exposes the OBU as a virtual Ethernet interface at 192.168.42.x. The app auto-detects the gateway IP on plug-in.

Key dependencies

Library Purpose
Jetpack Compose + Material3 UI
Eclipse Paho MQTT OBU communication
Room Local database
Hilt Dependency injection
OSMDroid Trip review map
DataStore Settings persistence
FusedLocationProviderClient GNSS

Getting started

  1. Open in Android Studio (Hedgehog or newer).
  2. Connect a device running Android 10+ (API 29).
  3. Build and run the app module.
  4. For Phase 02 testing: plug the phone into the OBU via USB-C, enable USB tethering on the phone, and the app will detect the interface and connect automatically. Broker IP can be overridden manually in Settings → Connection.
  5. For standalone trip recording: no OBU required. Go to the Record tab and tap Record.

The Wi-Fi transport (Phase 01 broker at 192.168.3.202) remains available in developer builds and can be toggled in Settings → Developer.

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 future antenna coordination all run on the phone to keep the OBU lightweight and power-efficient.

V2X communication uses ITS-G5 (IEEE 802.11p / DSRC) at 5.9 GHz. The app communicates with the OBU exclusively via the consider it MQTT API v6 (processed JSON messages); no ASN.1 encoding in the app.

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%