feat: CAM-based use case detection engine, decouple DENM from sensor triggers

Added:
- UseCaseDetectionEngine: IMA-B, IMA-S, RTW-B, LTW-B, SMVA/BCW-B via per-station
  CAM history, yaw-rate/heading-trend turn detection, Info/Awareness/Warning alerts
- Ego state from obu_gnss (~4Hz, yaw rate) with phone GPS fallback when stale
- Human-readable alert narratives + technical detail in V2X Monitor
- Settings > Use Case Alerts per-use-case toggles

Fixed:
- DENM no longer auto-fires on braking/stopping; manual-only (antenna/RSU test)
- Removed unwired CamBuilder.kt (dead code, contradicted CAM-only architecture)
- TripRecordingService no longer depends on MQTT/OBU connectivity
This commit is contained in:
Ashin Walpola
2026-07-02 15:04:34 +02:00
parent 4b20820ded
commit 7b8a8cbb32
19 changed files with 652 additions and 194 deletions
@@ -1,8 +1,11 @@
package com.hawhamburg.micr0bu
import android.Manifest
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.appcompat.app.AppCompatActivity
import androidx.activity.SystemBarStyle
@@ -46,6 +49,7 @@ import com.hawhamburg.micr0bu.ui.screens.MqttSettingsScreen
import com.hawhamburg.micr0bu.ui.screens.SensorsSettingsScreen
import com.hawhamburg.micr0bu.ui.screens.SettingsScreen
import com.hawhamburg.micr0bu.ui.screens.UnitsSettingsScreen
import com.hawhamburg.micr0bu.ui.screens.UseCaseAlertsSettingsScreen
import com.hawhamburg.micr0bu.ui.theme.MicrOBUTheme
import com.hawhamburg.micr0bu.viewmodel.MqttViewModel
import com.hawhamburg.micr0bu.viewmodel.SensorViewModel
@@ -74,7 +78,10 @@ class MainActivity : AppCompatActivity() {
val activeTransport by mqttViewModel.activeTransport.collectAsState()
val usbConnected by mqttViewModel.usbConnected.collectAsState()
val obuStationTypeWarning by mqttViewModel.obuStationTypeWarning.collectAsState()
val obuStationType by mqttViewModel.obuStationType.collectAsState()
val tripServiceState by tripViewModel.serviceState.collectAsState()
val showBatteryOptPrompt by tripViewModel.showBatteryOptPrompt.collectAsState()
val useCaseEnabledMap by mqttViewModel.useCaseEnabledMap.collectAsState()
MicrOBUTheme(darkTheme = state.darkTheme) {
val view = LocalView.current
@@ -128,6 +135,7 @@ class MainActivity : AppCompatActivity() {
activeTransport = activeTransport,
usbCableConnected = usbConnected,
obuStationTypeWarning = obuStationTypeWarning,
obuStationType = obuStationType,
onNavigateToConnection = { navController.navigate(Screen.Connection.route) },
onNavigateToSensors = {
navController.navigate(Screen.Sensors.route) {
@@ -147,6 +155,7 @@ class MainActivity : AppCompatActivity() {
state = state,
mqttConnectionState = mqttConnectionState,
tripServiceState = tripServiceState,
showBatteryOptPrompt = showBatteryOptPrompt,
onToggleRecording = {
// Start / stop both CSV recording and the event detection service
viewModel.toggleRecording()
@@ -155,6 +164,15 @@ class MainActivity : AppCompatActivity() {
onOpenSessionLog = {
navController.navigate(Screen.Log.route)
},
onOpenBatterySettings = {
startActivity(
Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS).apply {
data = Uri.parse("package:$packageName")
}
)
tripViewModel.recheckBatteryOpt()
},
onDismissBatteryOptPrompt = tripViewModel::dismissBatteryOptPrompt,
)
}
composable(Screen.Log.route) {
@@ -205,10 +223,18 @@ class MainActivity : AppCompatActivity() {
onOpenUnits = { navController.navigate(Screen.SettingsUnits.route) },
onOpenConnection = { navController.navigate(Screen.SettingsConnection.route) },
onOpenMqtt = { navController.navigate(Screen.SettingsMqtt.route) },
onOpenUseCaseAlerts = { navController.navigate(Screen.SettingsUseCaseAlerts.route) },
onOpenDeveloper = { navController.navigate(Screen.SettingsDeveloper.route) },
onOpenAbout = { navController.navigate(Screen.SettingsAbout.route) },
)
}
composable(Screen.SettingsUseCaseAlerts.route) {
UseCaseAlertsSettingsScreen(
enabledMap = useCaseEnabledMap,
onToggle = mqttViewModel::setUseCaseEnabled,
onBack = { navController.popBackStack() },
)
}
composable(Screen.SettingsConnection.route) {
ConnectionSettingsScreen(
mqttPrefs = mqttPrefs,