Skip to content

feat: Drive Mode HUD, OTM Live Feed, PCAP Upload, Demo Mode & UI overhaul (v0.3.0)#4

Open
studio-justin-braun wants to merge 2 commits into
pit711:mainfrom
studio-justin-braun:main
Open

feat: Drive Mode HUD, OTM Live Feed, PCAP Upload, Demo Mode & UI overhaul (v0.3.0)#4
studio-justin-braun wants to merge 2 commits into
pit711:mainfrom
studio-justin-braun:main

Conversation

@studio-justin-braun

Copy link
Copy Markdown

Summary

This PR adds four major new features to the Android app, a full UI overhaul and
several bug fixes. All changes are backwards-compatible — existing settings,
recordings and MQTT configurations are preserved.


New Features

🚗 Drive Mode (experimental)

A full-screen animated driving HUD that replaces the map while in motion.
Activated via a new car-icon mini FAB in the main screen.

What it does:

  • Renders a perspective road view on a custom DriveView Canvas (no bitmaps,
    fully programmatic drawing). Road animation speed is proportional to real
    GPS speed.
  • Traffic light card appears automatically when a signalised intersection
    is within 300–400 m ahead (bearing cone ± 90°). Shows live R/Y/G phase,
    countdown in seconds and distance. Data is sourced from both hardware SPATEM
    frames and the OTM WebSocket simultaneously.
  • Nearby vehicle radar draws amber silhouettes for vehicles ahead (± 35°),
    red outlines with headlight glow for oncoming traffic (150–210°), and a badge
    indicator for close-following vehicles (< 70 m behind).
  • DENM hazard banner appears at the bottom when a hazard event is received
    within 500 m in the direction of travel.
  • OTM WebSocket is auto-enabled when Drive Mode activates; auto-disabled on
    exit (unless the user had already enabled it manually).
  • The Drive Mode FAB repositions itself via ConstraintSet when active so it
    remains accessible in the bottom-right corner over the full-screen view.

Status: Drive Mode is a functional first iteration. The traffic-light
detection and vehicle radar work in the field but visual polish and edge cases
(multi-lane, complex intersections) are still being refined. Feedback and
real-world testing reports are very welcome — please open an issue.

New files: DriveView.kt, ic_drive.xml


📡 OTM Live Feed

Streams live V2X data from wss://opentrafficmap.org/ws_ext?gzip=true directly
onto the existing OSMDroid map layer.

Implementation:

  • OtmWebSocketClient.kt — OkHttp WebSocket with gzip decompression,
    exponential back-off reconnect (2 s → 30 s) and main-thread callback delivery.
  • Parses three distinct OTM message types:
    • delta — incremental updates via top-level upsertPoints[]
    • snapshot — full state dump via points.features[] (different structure!)
    • traffic-light-map-batch — static MAPEM intersection positions used to
      pre-populate the traffic light cache before live SPAT data arrives
  • OtmLiveLayer.kt — separate map overlay with per-entity marker management,
    bounding-box filtering (±10 % of visible area, max 50 km) and refilter() on
    scroll/zoom events.
  • Amber colour scheme (#FF8F00 for vehicles, #00BFA5 for traffic lights,
    etc.) makes OTM markers instantly distinguishable from locally received frames.
  • Toggle via RSS mini FAB; FAB tint turns amber when active.
  • OtmPoint.kt data class; spatSecsLeft field carries countdown seconds.

Dependency added: com.squareup.okhttp3:okhttp:4.12.0

New files: OtmWebSocketClient.kt, OtmLiveLayer.kt, OtmPoint.kt,
ic_live.xml


📤 PCAP Upload

Replays locally recorded .pcap files to any configured MQTT broker — offline,
without the ESP32-C5 hardware. Mirrors the Python pcap-replay.py workflow.

Implementation:

  • PcapUploadActivity.kt — lists all itsg5-*.pcap files from
    getExternalFilesDir() sorted newest-first. Reads standard libpcap format
    (magic 0xa1b2c3d4 LE, 24-byte global header, 16-byte per-packet headers).
  • PcapFileAdapter.ktListAdapter<File> with DiffUtil, shows filename,
    date and human-readable size.
  • Upload runs in lifecycleScope on Dispatchers.IO; progress is reported back
    to the main thread with a bottom card overlay (filename, LinearProgressIndicator,
    packet counter, cancel button).
  • Uses MqttBridge with a -upload-N client-ID suffix to avoid broker-level
    conflicts with the live MQTT bridges already running in MainActivity.
  • Accessible via Upload ↑ icon in the main toolbar (added to menu_main.xml).

New files: PcapUploadActivity.kt, PcapFileAdapter.kt,
activity_pcap_upload.xml, item_pcap_file.xml, ic_upload.xml


🛠 Demo Mode (DevOps)

Generates structurally valid ITS-G5 frames in the current map viewport for
testing without hardware. Toggled from About → DevOps.

Implementation:

  • DemoFrameGenerator.kt — coroutine-based generator (300 ms tick on
    Dispatchers.Main). Creates 7 simulated vehicles and 2 SPATEM RSUs around
    the map centre.
  • Payloads are complete, decodable IEEE 802.11p frames:
    802.11 MAC (24 B) + LLC/SNAP (8 B) + GN Basic (4 B) + GN Common (8 B) + SHB LPV (28 B) + BTP-B (4 B) + app stub (10 B) = 86 bytes.
    ItsG5Decoder.decodeFull() extracts all fields correctly (lat/lon, speed,
    heading, station ID, message type).
  • Frames pass through the identical handleFrames() pipeline as hardware
    frames: PCAP recording, MQTT forwarding, frame log, MarkerLayer, GeigerCounter
    and SPAT light indicator all work on demo data.
  • Station IDs carry 0x00FF_0000_0000 prefix so recordings can be identified
    as synthetic.
  • Vehicles use flat-earth dead-reckoning; redirect toward centre when > 440 m
    away. RSUs cycle RED (50 ticks) → YELLOW (10) → GREEN (60).
  • SettingsBus.demoModeActive flag survives Activity recreation.

New files: DemoFrameGenerator.kt


UI / Design Overhaul

Settings screen

All sections are now wrapped in MaterialCardView (colorSurfaceContainerHigh,
16 dp corner radius, 0 dp elevation) providing clear visual separation between
unrelated groups of controls. Previously everything was a flat scrollable list.

Main screen

  • Controls bar (btnConnect, btnConnectBt): added colorSurface background
    • 4 dp elevation so it visually lifts off the map.
  • Speed overlay and SPAT traffic-light pill: replaced hardcoded #BB000000
    background with @drawable/bg_overlay_rounded (semi-transparent, 10 dp
    corner radius).
  • Log header: elevation=3dp, letterSpacing, monospace stats text.
  • emptyLog alpha reduced to 0.45 for better contrast against the map.

Styles

  • Section.Header: added letterSpacing="0.1" and paddingStart="4dp".
  • New @style/SettingCard for reusable card containers.

Bug Fixes

# Location Description
1 OtmWebSocketClient speedKmh: null crash — getDouble() throws on JSON null; replaced with optDouble(…, NaN).takeUnless { it.isNaN() }
2 OtmWebSocketClient Snapshot messages ignored — snapshot uses points.features[] not upsertPoints[]; parser now falls back correctly
3 OtmWebSocketClient Traffic lights never stored — OTM sends kind: "traffic_light" (underscore); normalised with .replace('_', '-')
4 OtmWebSocketClient Countdown always null — minEndTime / timeStamp are DSRC TimeMarks in milliseconds within the current minute (0–59 999); corrected formula: ((endMs − tsMs + 60 000) % 60 000) / 1 000
5 OtmWebSocketClient traffic-light-map-batch completely ignored — added parseMapBatch() to pre-populate traffic light positions from MAPEM data
6 AboutActivity binding was a local variable in onCreate; updateDemoButton() outside onCreate caused UninitializedPropertyAccessException — promoted to private lateinit var
7 Drive Mode FAB fabDriveMode overlapped fabLocate due to wrong ConstraintSet anchor (bottom_toTopOf=logSplit); fixed by placing it above fabOtmLive in chain, repositioned dynamically via ConstraintSet when drive mode is active
8 Drive Mode layout DriveView only filled 70 % of screen — logSplit guideline not moved to 1.0f; fixed in toggleDriveMode()
9 MqttBridge Added optional clientIdSuffix parameter to avoid MQTT broker-level client-ID conflicts between live bridges and upload bridges
10 Gradle gradle-8.4 incompatible with Java 21 — upgraded to gradle-8.9

Files Changed

app/build.gradle.kts                                 + okhttp3:4.12.0
gradle/wrapper/gradle-wrapper.properties             8.4 → 8.9

New Kotlin sources (8):
  DemoFrameGenerator.kt
  DriveView.kt
  OtmLiveLayer.kt
  OtmPoint.kt
  OtmWebSocketClient.kt
  PcapFileAdapter.kt
  PcapUploadActivity.kt

Modified Kotlin sources (7):
  AboutActivity.kt                                   binding lateinit fix + demo button
  MainActivity.kt                                    Drive Mode, OTM, demo, vehicle cache
  MqttBridge.kt                                      clientIdSuffix param
  OtmWebSocketClient.kt                              complete parser rewrite (3 bug fixes)
  PcapUploadActivity.kt                              MqttBridge suffix
  SettingsBus.kt                                     OnDemoModeChanged + demoModeActive

New layouts / drawables / menu (11):
  activity_pcap_upload.xml
  item_pcap_file.xml
  bg_overlay_rounded.xml
  ic_drive.xml
  ic_live.xml
  ic_upload.xml

Modified layouts / values (6):
  activity_main.xml                                  DriveView, 5 FABs, design improvements
  activity_settings.xml                              MaterialCardView section wrappers
  activity_about.xml                                 DevOps demo button
  menu_main.xml                                      upload + drive actions
  strings.xml                                        upload / OTM / demo / drive strings
  styles.xml                                         SettingCard + Section.Header updates
  values/themes.xml                                  (unchanged)

AndroidManifest.xml                                  PcapUploadActivity registered
README.md                                            v0.3.0 changelog + new feature docs

Testing

Scenario Result
PCAP upload to cits1.opentrafficmap.org ✅ Packets delivered, progress correct
OTM Live Feed — vehicles on map (amber) ✅ Confirmed with emulator + real device
OTM Live Feed — traffic light detection from MAPEM batch ✅ Positions load on connect
OTM Live Feed — SPAT phase update from delta ✅ Phase updates after fix #3
OTM Live Feed — countdown calculation ✅ Correct seconds after fix #4
Demo Mode — recording & MQTT forwarding ✅ PCAP valid in Wireshark, MQTT delivers
Drive Mode — speed animation ✅ Smooth at 0–130 km/h (emulator GPS route)
Drive Mode — traffic light card ✅ Appears at correct distance/bearing
Drive Mode — FAB visibility in both modes ✅ After ConstraintSet fix
Settings — card layout on all screen sizes ✅ Tested 360 dp, 390 dp, 412 dp
Existing features — no regressions ✅ USB/BT connect, recording, MQTT filter

Compatibility

  • Min SDK: 24 (unchanged)
  • Target SDK: 34 (unchanged)
  • Gradle: 8.9 (upgraded from 8.4 for Java 21 compatibility)
  • New permission: none — OTM WebSocket uses the existing INTERNET permission
  • Breaking changes: none

Tested on Android 16 (API 36.1) emulator and physical devices running Android 13/14.
All feedback on Drive Mode real-world behaviour is very appreciated — please open an issue or comment here.

Add a version badge and detailed 0.3.0 release notes to the README. Document new features including Drive Mode (experimental HUD), OTM Live Feed (WebSocket), PCAP upload, Demo Mode, and UI overhaul; update the Features table and architecture diagram text to reflect these additions. Add a Changelog section (0.3.0 and 0.2.3) noting Gradle 8.9/Java 21 compatibility and OkHttp dependency. Small formatting and layout tweaks throughout the file.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ITS-G5 Receiver Setup - Write error

1 participant