Phase 03: real CAM UPER codec + ESP32-C5 TX/RX serial link

- Firmware: rewrite obu-firmware TX loop to be serial-driven (no on-chip timer), add promiscuous RX + GeoNetworking/BTP unwrap (gn_unwrap.c), add binary UART framing to the phone (serial_link.c/.h). Drop local cam_encode() - CAM is now built on the phone.
- Kotlin: byte-exact UPER CAM encoder/decoder ported from cam.c (BitWriter/BitReader/CamUperCodec), matching SerialFrame codec, real UsbSerialTransport (usb-serial-for-android), CamTransmitLoop (1Hz base rate, event/geofence boost, ESP32-C5-only), wired into CamUseCaseRepository for RX and TripRecordingService for TX.
- Add V2X message retention: persist all CAM (own+remote) to Room while recording, drop otherwise (DB v2 -> v3 migration).
- Add jitpack repo + usb-serial-for-android dependency.

Fixes: UsbSerialTransport now uses SerialInputOutputManager.start()/stop() (this lib version manages its own thread internally) instead of manual Runnable/Thread submission, which didn't compile.
This commit is contained in:
Ashin Walpola
2026-08-04 17:58:07 +02:00
parent 842d362c8f
commit 33c4ec5998
58 changed files with 8438 additions and 65 deletions
@@ -8,6 +8,8 @@ import com.hawhamburg.micr0bu.data.mqtt.MqttMessage
import com.hawhamburg.micr0bu.data.mqtt.MqttPreferences
import com.hawhamburg.micr0bu.data.mqtt.MqttPrefs
import com.hawhamburg.micr0bu.data.mqtt.MqttRepository
import com.hawhamburg.micr0bu.data.mqtt.ObuHardwarePreferences
import com.hawhamburg.micr0bu.data.transport.ObuHardware
import com.hawhamburg.micr0bu.data.transport.TransportType
import com.hawhamburg.micr0bu.data.transport.UsbNetworkDetector
import com.hawhamburg.micr0bu.domain.denm.DenmUseCase
@@ -30,6 +32,7 @@ class MqttViewModel @Inject constructor(
private val prefs: MqttPreferences,
private val usbDetector: UsbNetworkDetector,
private val camUseCaseRepository: CamUseCaseRepository,
private val obuHardwarePrefs: ObuHardwarePreferences,
) : ViewModel() {
// ── MQTT connection & messages ────────────────────────────────────────────
@@ -48,6 +51,13 @@ class MqttViewModel @Inject constructor(
val activeTransport: StateFlow<TransportType> = repo.activeTransport
/** Which physical OBU (Section 13) is currently selected — CiT One or ESP32-C5. */
val obuHardware: StateFlow<ObuHardware> = repo.obuHardware
fun setObuHardware(hardware: ObuHardware) {
viewModelScope.launch { obuHardwarePrefs.setObuHardware(hardware) }
}
/** True when a 192.168.42.x USB-C tethering network is detected. */
val usbConnected: StateFlow<Boolean> = usbDetector.usbNetwork
.map { it != null }
@@ -111,6 +121,12 @@ class MqttViewModel @Inject constructor(
/** Per-use-case enable/disable state (Settings > Use Case Alerts). */
val useCaseEnabledMap: StateFlow<Map<UseCaseType, Boolean>> = camUseCaseRepository.enabledMap
/** Ego bike's latest known position, for the V2X Monitor live map view (Section 13). */
val ownCamPosition: StateFlow<com.hawhamburg.micr0bu.domain.cam.Cam?> = camUseCaseRepository.ownPosition
/** Latest known CAM per tracked remote road user, for the live map view (Section 13). */
val remoteCamPositions: StateFlow<Map<Long, com.hawhamburg.micr0bu.domain.cam.Cam>> = camUseCaseRepository.remotePositions
/** True if [stationId] is the ego OBU's own — used for OWN/REMOTE badges in the raw message list. */
fun isOwnStationId(stationId: Long): Boolean = camUseCaseRepository.isOwnStationId(stationId)