Fix UPER encoding of CurvatureCalculationMode; verified on hardware
CurvatureCalculationMode is the one extensible ENUMERATED in CAM:
ENUMERATED {yawRateUsed(0), yawRateNotUsed(1), unavailable(2), ...}
UPER encodes an extensible ENUMERATED as an extension bit followed by the root
index - 1 + 2 = 3 bits. All three of our encoders wrote only the 2-bit index,
shifting yawRate and the entire low-frequency container one bit early for any
standards-compliant receiver.
It went unnoticed because every end of this project shared the mistake: the
Kotlin codec was ported bit-for-bit from cam.c, so phone and ESP32 agreed
perfectly with each other and with nothing else. Confirmed against the ETSI
ASN.1 in the C-ITS-Parser checkout, where rasn marks this type - and only this
type - #[non_exhaustive].
Fixed in all three copies of the encoder (app CamUperCodec.kt,
obu-firmware/main/cam.c, obu-cam-transmistter/main/cam.c) plus the decoder,
which now rejects rather than misreads a set extension bit. Frame size is
unchanged at 43 bytes. Transmitter reflashed and the phone decodes its CAMs.
Also in this change:
- serial_link: skip send_frame entirely when no USB host is attached, and raise
the tx mutex timeout above the worst-case hold. With the phone unplugged every
write blocked its full timeout while holding the lock, so forwarded CAM_RX
traffic starved the 1 Hz heartbeat - observed as "tx mutex timeout, dropping
frame" on the console, and it would have tripped the phone's link watchdog.
Verified gone on hardware.
- Log decoded and failed CAMs in CamUseCaseRepository. "The app shows nothing"
had two indistinguishable causes; a silent `?: return` made this bug much
harder to find than it needed to be.
- Remove the ESP32 send-only/send-and-receive toggle. Reception can't be
disabled in firmware (raw TX only works while promiscuous), so it was an
app-side filter pretending to be a radio control.
- V2X monitor follows the serial link state on the ESP32 path instead of MQTT,
which is permanently disconnected there; CAM intake is gated on the link being
up, and engine state is cleared when it drops.
- About screen: 0.5.0, Phase 03.
- Track obu-cam-transmistter, the bench CAM transmitter. Its cam.c is compiled
(unlike obu-firmware's reference copy) and must stay bit-identical to the other
two - this commit is what that coupling costs when it's broken.
- Document the two-toolchain split: this project builds on IDF 5.5.4, obu-firmware
on the pinned 6.1. Exporting both in one shell fails confusingly.
This commit is contained in:
@@ -84,7 +84,6 @@ class MainActivity : AppCompatActivity() {
|
||||
val useCaseEnabledMap by mqttViewModel.useCaseEnabledMap.collectAsState()
|
||||
val obuHardware by mqttViewModel.obuHardware.collectAsState()
|
||||
val usbSerialState by mqttViewModel.usbSerialState.collectAsState()
|
||||
val espRxMode by mqttViewModel.espRxMode.collectAsState()
|
||||
|
||||
MicrOBUTheme(darkTheme = state.darkTheme) {
|
||||
val view = LocalView.current
|
||||
@@ -253,8 +252,6 @@ class MainActivity : AppCompatActivity() {
|
||||
onMqttPrefsChange = mqttViewModel::updatePrefs,
|
||||
obuHardware = obuHardware,
|
||||
onObuHardwareChange = mqttViewModel::setObuHardware,
|
||||
espRxMode = espRxMode,
|
||||
onEspRxModeChange = mqttViewModel::setEspRxMode,
|
||||
onBack = { navController.popBackStack() },
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
package com.hawhamburg.micr0bu.data.cam
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import com.hawhamburg.micr0bu.data.GnssReading
|
||||
import com.hawhamburg.micr0bu.data.SensorRepository
|
||||
import com.hawhamburg.micr0bu.data.mqtt.MqttConnectionState
|
||||
import com.hawhamburg.micr0bu.data.mqtt.MqttRepository
|
||||
import com.hawhamburg.micr0bu.data.mqtt.ObuHardwarePreferences
|
||||
import com.hawhamburg.micr0bu.data.mqtt.UseCaseAlertPreferences
|
||||
import com.hawhamburg.micr0bu.data.transport.EspRxMode
|
||||
import com.hawhamburg.micr0bu.data.transport.ObuHardware
|
||||
import com.hawhamburg.micr0bu.data.transport.SerialFrameType
|
||||
import com.hawhamburg.micr0bu.data.transport.UsbSerialState
|
||||
import com.hawhamburg.micr0bu.data.transport.UsbSerialTransport
|
||||
import com.hawhamburg.micr0bu.domain.asn1.RealAsn1UperCodec
|
||||
import com.hawhamburg.micr0bu.domain.cam.Cam
|
||||
@@ -36,6 +38,7 @@ import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
private const val TAG = "CamUseCaseRepo"
|
||||
private const val CAM_TOPIC = "v2x-uca/output/json/cam"
|
||||
private const val OBU_GNSS_TOPIC = "v2x/rx/obu_gnss"
|
||||
private const val PRUNE_INTERVAL_MS = 1_000L
|
||||
@@ -78,7 +81,8 @@ class CamUseCaseRepository @Inject constructor(
|
||||
private val engine = UseCaseDetectionEngine()
|
||||
private val sensorRepository = SensorRepository(context)
|
||||
|
||||
@Volatile private var espRxMode: EspRxMode = EspRxMode.SEND_AND_RECEIVE
|
||||
/** Latest selected OBU hardware, so serial-link events only act on the ESP32-C5 path. */
|
||||
@Volatile private var currentHardware: ObuHardware = ObuHardware.CIT_ONE
|
||||
|
||||
private val _ownStationId = MutableStateFlow<Long?>(null)
|
||||
/** The ego OBU's own station ID, learned from `v2x/rx/obu_gnss`. Null until known. */
|
||||
@@ -168,13 +172,26 @@ class CamUseCaseRepository @Inject constructor(
|
||||
scope.launch {
|
||||
usbSerialTransport.incomingFrames.collect { frame ->
|
||||
if (frame.type != SerialFrameType.CAM_RX) return@collect
|
||||
if (espRxMode == EspRxMode.SEND_ONLY) return@collect
|
||||
if (usbSerialTransport.state.value != UsbSerialState.CONNECTED) return@collect
|
||||
handleCamFromSerial(frame.payload)
|
||||
}
|
||||
}
|
||||
|
||||
// Drop everything the serial link taught us the moment it goes down. Without this, the
|
||||
// last-seen positions and their alerts linger on the map and in the use-case panel after
|
||||
// an unplug, which reads as live traffic - the worst kind of stale on a safety display.
|
||||
scope.launch {
|
||||
obuHardwarePrefs.espRxModeFlow.collect { espRxMode = it }
|
||||
usbSerialTransport.state.collect { state ->
|
||||
// ESP32-only: on the CiT One path this transport is permanently DISCONNECTED and
|
||||
// resetting here would wipe perfectly good MQTT-derived state.
|
||||
if (currentHardware == ObuHardware.ESP32_C5 && state != UsbSerialState.CONNECTED) {
|
||||
engine.reset()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scope.launch {
|
||||
obuHardwarePrefs.obuHardwareFlow.collect { currentHardware = it }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,9 +270,25 @@ class CamUseCaseRepository @Inject constructor(
|
||||
private fun handleCamFromSerial(payload: ByteArray) {
|
||||
if (payload.isEmpty()) return
|
||||
val camBytes = payload.copyOfRange(1, payload.size) // payload[0] is RSSI, not part of the CAM
|
||||
val cam = camCodec.decodeCam(camBytes, System.currentTimeMillis()) ?: return
|
||||
val cam = camCodec.decodeCam(camBytes, System.currentTimeMillis())
|
||||
if (cam == null) {
|
||||
// Logged, not silently dropped: "the app shows nothing" has two completely different
|
||||
// causes - frames not arriving at all, versus arriving and failing to decode - and
|
||||
// without this line they're indistinguishable from the outside. rssi is signed.
|
||||
Log.w(
|
||||
TAG,
|
||||
"handleCamFromSerial: decode FAILED for ${camBytes.size}-byte CAM " +
|
||||
"(rssi=${payload[0].toInt()}) - first bytes: ${camBytes.toHexPreview()}",
|
||||
)
|
||||
return
|
||||
}
|
||||
Log.d(TAG, "handleCamFromSerial: decoded station=${cam.stationId} " +
|
||||
"lat=${cam.latitude} lon=${cam.longitude} speed=${cam.speedMps} rssi=${payload[0].toInt()}")
|
||||
if (_ownStationId.value != null && cam.stationId == _ownStationId.value) return // self-heard TX
|
||||
engine.onRemoteCam(cam)
|
||||
_processedCam.tryEmit(cam)
|
||||
}
|
||||
|
||||
private fun ByteArray.toHexPreview(limit: Int = 16): String =
|
||||
take(limit).joinToString(" ") { "%02x".format(it) } + if (size > limit) " ..." else ""
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.longPreferencesKey
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import com.hawhamburg.micr0bu.data.transport.EspRxMode
|
||||
import com.hawhamburg.micr0bu.data.transport.ObuHardware
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@@ -27,7 +26,6 @@ class ObuHardwarePreferences @Inject constructor(
|
||||
) {
|
||||
private object Keys {
|
||||
val OBU_HARDWARE = stringPreferencesKey("obu_hardware")
|
||||
val ESP_RX_MODE = stringPreferencesKey("esp_rx_mode")
|
||||
val OWN_STATION_ID = longPreferencesKey("own_station_id")
|
||||
}
|
||||
|
||||
@@ -39,19 +37,6 @@ class ObuHardwarePreferences @Inject constructor(
|
||||
context.obuHardwareDataStore.edit { prefs -> prefs[Keys.OBU_HARDWARE] = hardware.id }
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the ESP32-C5 path processes received CAM traffic or only transmits — see
|
||||
* [EspRxMode]'s KDoc for what this does and doesn't actually control. Defaults to
|
||||
* [EspRxMode.SEND_AND_RECEIVE] (full duplex, today's existing behavior).
|
||||
*/
|
||||
val espRxModeFlow: Flow<EspRxMode> = context.obuHardwareDataStore.data.map { prefs ->
|
||||
EspRxMode.entries.firstOrNull { it.id == prefs[Keys.ESP_RX_MODE] } ?: EspRxMode.SEND_AND_RECEIVE
|
||||
}
|
||||
|
||||
suspend fun setEspRxMode(mode: EspRxMode) {
|
||||
context.obuHardwareDataStore.edit { prefs -> prefs[Keys.ESP_RX_MODE] = mode.id }
|
||||
}
|
||||
|
||||
/** This device's own CAM StationID, or null if one hasn't been assigned yet. */
|
||||
val ownStationIdFlow: Flow<Long?> = context.obuHardwareDataStore.data.map { prefs ->
|
||||
prefs[Keys.OWN_STATION_ID]
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
package com.hawhamburg.micr0bu.data.transport
|
||||
|
||||
/**
|
||||
* Whether the ESP32-C5 path (Phase 03) processes remote CAM traffic it receives, or only ever
|
||||
* transmits the phone's own CAM.
|
||||
*
|
||||
* **Important nuance:** this does NOT physically disable the ESP32's radio receiver. The
|
||||
* firmware's own promiscuous-mode setup (`main.c`, see comments there) is required for its raw
|
||||
* 802.11p TX path to work at all — ESP-IDF only allows `esp_wifi_80211_tx()` to emit frames when
|
||||
* the MAC is promiscuous or associated to an AP. So the ESP32 always physically receives and
|
||||
* forwards CAM_RX frames over the serial link regardless of this setting; what this setting
|
||||
* actually controls is purely on the phone side — whether [SEND_ONLY] mode ignores those
|
||||
* incoming frames (see [com.hawhamburg.micr0bu.data.cam.CamUseCaseRepository]) instead of
|
||||
* feeding them into the detection engine / UI. Useful for isolating the TX path during bench
|
||||
* testing (e.g. with the [com.hawhamburg.micr0bu.service.CamPinger]) without nearby test traffic
|
||||
* cluttering the use-case alerts or live map.
|
||||
*/
|
||||
enum class EspRxMode(val id: String) {
|
||||
/** Transmit the phone's own CAM only; incoming CAM_RX frames from the ESP32 are discarded. */
|
||||
SEND_ONLY("send_only"),
|
||||
|
||||
/** Normal full-duplex operation: transmit own CAM and process received CAM traffic. */
|
||||
SEND_AND_RECEIVE("send_and_receive"),
|
||||
}
|
||||
@@ -25,8 +25,9 @@ enum class ObuHardware(val id: String) {
|
||||
*
|
||||
* Real serial link + CAM UPER codec are implemented on both sides — see
|
||||
* [com.hawhamburg.micr0bu.data.transport.UsbSerialTransport] (phone) and
|
||||
* `obu-firmware/main/serial_link.c` (firmware). See also [EspRxMode] for the send-only vs
|
||||
* send-and-receive toggle (Settings > Connection).
|
||||
* `obu-firmware/main/serial_link.c` (firmware). Reception is always on: the ESP32 must keep
|
||||
* its receiver enabled for raw TX to work at all (ESP-IDF only emits raw 802.11 frames while
|
||||
* promiscuous or associated), so there is nothing meaningful for the app to toggle.
|
||||
*/
|
||||
ESP32_C5("esp32_c5"),
|
||||
}
|
||||
|
||||
@@ -128,7 +128,17 @@ object CamUperCodec {
|
||||
|
||||
bw.putBits(1023 - (-1023), 11) // curvatureValue: unavailable (not derived - see class KDoc)
|
||||
bw.putBits(7, 3) // curvatureConfidence: unavailable
|
||||
bw.putBits(2, 2) // curvatureCalculationMode: unavailable
|
||||
|
||||
// CurvatureCalculationMode is the ONE extensible ENUMERATED in this message:
|
||||
// ENUMERATED {yawRateUsed(0), yawRateNotUsed(1), unavailable(2), ...}
|
||||
// UPER encodes an extensible ENUMERATED as an extension bit followed by the index into
|
||||
// the root list - 1 + 2 = 3 bits, not 2. Both this encoder and its `cam.c` ancestor wrote
|
||||
// only the 2-bit index, which shifted yawRate and the whole low-frequency container one
|
||||
// bit early for any standards-compliant receiver. It went unnoticed because both ends of
|
||||
// this project shared the same mistake; phone <-> ESP32 agreed perfectly with each other
|
||||
// and with nothing else.
|
||||
bw.putBits(0, 1) // extension bit: value is in the root list
|
||||
bw.putBits(2, 2) // curvatureCalculationMode: unavailable(2)
|
||||
|
||||
val yawRateCentiDegS = cam.yawRateDps
|
||||
?.let { (it * 100.0).roundToInt().coerceIn(-32766, 32766) }
|
||||
@@ -247,7 +257,13 @@ object CamUperCodec {
|
||||
|
||||
br.getBits(11) // curvatureValue
|
||||
br.getBits(3) // curvatureConfidence
|
||||
br.getBits(2) // curvatureCalculationMode
|
||||
|
||||
// CurvatureCalculationMode: extensible ENUMERATED - extension bit, then the root index.
|
||||
// See the matching comment in [encode]. If the extension bit is set the sender used a
|
||||
// value added in a later spec revision, encoded as a length-prefixed extension addition
|
||||
// this decoder can't skip reliably - bail rather than misread everything after it.
|
||||
if (br.getBitsInt(1) != 0) return null
|
||||
br.getBits(2) // curvatureCalculationMode root index
|
||||
|
||||
val yawRateRaw = br.getBitsInt(16) + (-32766)
|
||||
br.getBits(3) // yawRateConfidence
|
||||
|
||||
@@ -138,8 +138,15 @@ fun MqttTopicViewerScreen(
|
||||
viewModel.selectTopic(null)
|
||||
}
|
||||
|
||||
val isConnected = connectionState == MqttConnectionState.CONNECTED
|
||||
val isConnecting = connectionState == MqttConnectionState.CONNECTING
|
||||
val isEsp32 = obuHardware == ObuHardware.ESP32_C5
|
||||
|
||||
// On the ESP32-C5 path there is no MQTT broker, so `connectionState` is permanently
|
||||
// DISCONNECTED and using it here made the screen report "offline" while CAMs streamed in over
|
||||
// serial. Everything on this screen that means "is the OBU link up?" follows the serial link
|
||||
// instead when that hardware is selected.
|
||||
val effectiveState = if (isEsp32) usbSerialState.asConnectionState() else connectionState
|
||||
val isConnected = effectiveState == MqttConnectionState.CONNECTED
|
||||
val isConnecting = effectiveState == MqttConnectionState.CONNECTING
|
||||
|
||||
Column(modifier = Modifier.fillMaxSize()) {
|
||||
|
||||
@@ -175,10 +182,18 @@ fun MqttTopicViewerScreen(
|
||||
Spacer(Modifier.weight(1f))
|
||||
}
|
||||
|
||||
ConnectionChip(connectionState)
|
||||
ConnectionChip(effectiveState)
|
||||
Spacer(Modifier.width(2.dp))
|
||||
IconButton(
|
||||
onClick = { if (isConnected || isConnecting) viewModel.disconnect() else viewModel.connect() },
|
||||
onClick = {
|
||||
// Route to whichever transport this hardware actually uses.
|
||||
if (isEsp32) {
|
||||
if (isConnected || isConnecting) viewModel.disconnectUsbSerial()
|
||||
else viewModel.connectUsbSerial()
|
||||
} else {
|
||||
if (isConnected || isConnecting) viewModel.disconnect() else viewModel.connect()
|
||||
}
|
||||
},
|
||||
colors = IconButtonDefaults.iconButtonColors(
|
||||
contentColor = if (isConnected) ErrorRed else ConnectedGreen,
|
||||
),
|
||||
@@ -206,8 +221,8 @@ fun MqttTopicViewerScreen(
|
||||
activeDenmUseCase = activeDenmUseCase,
|
||||
useCaseAlerts = useCaseAlerts,
|
||||
showDenmTrigger = obuHardware == ObuHardware.CIT_ONE,
|
||||
showCamPinger = obuHardware == ObuHardware.ESP32_C5,
|
||||
isEsp32 = obuHardware == ObuHardware.ESP32_C5,
|
||||
showCamPinger = isEsp32,
|
||||
isEsp32 = isEsp32,
|
||||
denmEvents = denmEvents,
|
||||
usbSerialState = usbSerialState,
|
||||
camPingerActive = camPingerActive,
|
||||
@@ -1209,3 +1224,16 @@ private fun prettyPrintJson(raw: String): String {
|
||||
raw
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps the ESP32-C5 serial link's lifecycle onto the MQTT connection vocabulary the shared
|
||||
* connection UI on this screen already speaks, so one indicator can serve both transports rather
|
||||
* than duplicating the chip and its colours per hardware type.
|
||||
*/
|
||||
private fun UsbSerialState.asConnectionState(): MqttConnectionState = when (this) {
|
||||
UsbSerialState.CONNECTED -> MqttConnectionState.CONNECTED
|
||||
UsbSerialState.DEVICE_ATTACHED,
|
||||
UsbSerialState.PERMISSION_REQUESTED -> MqttConnectionState.CONNECTING
|
||||
UsbSerialState.ERROR -> MqttConnectionState.ERROR
|
||||
UsbSerialState.DISCONNECTED -> MqttConnectionState.DISCONNECTED
|
||||
}
|
||||
|
||||
@@ -47,7 +47,6 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import com.hawhamburg.micr0bu.R
|
||||
import com.hawhamburg.micr0bu.data.mqtt.MqttPrefs
|
||||
import com.hawhamburg.micr0bu.data.transport.EspRxMode
|
||||
import com.hawhamburg.micr0bu.data.transport.ObuHardware
|
||||
import com.hawhamburg.micr0bu.domain.usecase.UseCaseDetectionConfig
|
||||
import com.hawhamburg.micr0bu.domain.usecase.UseCaseType
|
||||
@@ -166,8 +165,6 @@ fun ConnectionSettingsScreen(
|
||||
onMqttPrefsChange: (MqttPrefs) -> Unit,
|
||||
obuHardware: ObuHardware = ObuHardware.CIT_ONE,
|
||||
onObuHardwareChange: (ObuHardware) -> Unit = {},
|
||||
espRxMode: EspRxMode = EspRxMode.SEND_AND_RECEIVE,
|
||||
onEspRxModeChange: (EspRxMode) -> Unit = {},
|
||||
onBack: () -> Unit,
|
||||
) {
|
||||
SubScreen(stringResource(R.string.settings_connection), onBack) {
|
||||
@@ -212,47 +209,6 @@ fun ConnectionSettingsScreen(
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(bottom = 8.dp),
|
||||
)
|
||||
|
||||
// CAM reception mode — see EspRxMode's KDoc for the caveat that this is purely
|
||||
// an app-side filter, not a physical radio-receiver toggle (the ESP32 must keep
|
||||
// its receiver on for TX to keep working at all).
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Text(
|
||||
stringResource(R.string.settings_esp32_rx_mode),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(top = 8.dp),
|
||||
)
|
||||
Spacer(Modifier.height(6.dp))
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(bottom = 4.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
val isSendOnly = espRxMode == EspRxMode.SEND_ONLY
|
||||
OutlinedButton(
|
||||
onClick = { onEspRxModeChange(EspRxMode.SEND_ONLY) },
|
||||
modifier = Modifier.weight(1f),
|
||||
colors = ButtonDefaults.outlinedButtonColors(
|
||||
containerColor = if (isSendOnly) MaterialTheme.colorScheme.primaryContainer else Color.Transparent,
|
||||
contentColor = if (isSendOnly) MaterialTheme.colorScheme.onPrimaryContainer else MaterialTheme.colorScheme.onSurface,
|
||||
),
|
||||
) { Text(stringResource(R.string.settings_esp32_rx_mode_send_only), fontWeight = if (isSendOnly) FontWeight.Bold else FontWeight.Normal) }
|
||||
|
||||
OutlinedButton(
|
||||
onClick = { onEspRxModeChange(EspRxMode.SEND_AND_RECEIVE) },
|
||||
modifier = Modifier.weight(1f),
|
||||
colors = ButtonDefaults.outlinedButtonColors(
|
||||
containerColor = if (!isSendOnly) MaterialTheme.colorScheme.primaryContainer else Color.Transparent,
|
||||
contentColor = if (!isSendOnly) MaterialTheme.colorScheme.onPrimaryContainer else MaterialTheme.colorScheme.onSurface,
|
||||
),
|
||||
) { Text(stringResource(R.string.settings_esp32_rx_mode_send_and_receive), fontWeight = if (!isSendOnly) FontWeight.Bold else FontWeight.Normal) }
|
||||
}
|
||||
Text(
|
||||
stringResource(R.string.settings_esp32_rx_mode_desc),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.8f),
|
||||
modifier = Modifier.padding(bottom = 8.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ 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.EspLinkStatus
|
||||
import com.hawhamburg.micr0bu.data.transport.EspRxMode
|
||||
import com.hawhamburg.micr0bu.data.transport.ObuHardware
|
||||
import com.hawhamburg.micr0bu.data.transport.TransportType
|
||||
import com.hawhamburg.micr0bu.data.transport.UsbNetworkDetector
|
||||
@@ -67,19 +66,6 @@ class MqttViewModel @Inject constructor(
|
||||
viewModelScope.launch { obuHardwarePrefs.setObuHardware(hardware) }
|
||||
}
|
||||
|
||||
/**
|
||||
* ESP32-C5-only: whether received CAM traffic is processed or discarded — see [EspRxMode]'s
|
||||
* KDoc for the important caveat that this doesn't actually disable the ESP32's receiver
|
||||
* (it can't, without also breaking TX).
|
||||
*/
|
||||
val espRxMode: StateFlow<EspRxMode> = obuHardwarePrefs.espRxModeFlow.stateIn(
|
||||
viewModelScope, SharingStarted.Eagerly, EspRxMode.SEND_AND_RECEIVE,
|
||||
)
|
||||
|
||||
fun setEspRxMode(mode: EspRxMode) {
|
||||
viewModelScope.launch { obuHardwarePrefs.setEspRxMode(mode) }
|
||||
}
|
||||
|
||||
/** True when a 192.168.42.x USB-C tethering network is detected. */
|
||||
val usbConnected: StateFlow<Boolean> = usbDetector.usbNetwork
|
||||
.map { it != null }
|
||||
|
||||
@@ -162,7 +162,7 @@
|
||||
<string name="settings_wifi_val">Nur Entwicklermodus — noch nicht implementiert</string>
|
||||
<string name="settings_about">Über</string>
|
||||
<string name="settings_app_version">App-Version</string>
|
||||
<string name="settings_app_version_val">0.4.0 (Phase 02 — USB-C + DENM TX)</string>
|
||||
<string name="settings_app_version_val">0.5.0 (Phase 03 — ESP32-C5-Seriellverbindung + CAM vom Smartphone)</string>
|
||||
<string name="settings_connection">Verbindung</string>
|
||||
<string name="settings_usb_auto_detect">OBU per USB-C automatisch erkennen</string>
|
||||
<string name="settings_usb_manual_ip">OBU-IP (manuell)</string>
|
||||
@@ -170,10 +170,6 @@
|
||||
<string name="settings_obu_hardware_cit_one">CiT One</string>
|
||||
<string name="settings_obu_hardware_esp32">ESP32-C5</string>
|
||||
<string name="settings_obu_hardware_esp32_note">Der ESP32-C5 arbeitet als „dummer" Transceiver: CAM wird auf dem Smartphone erstellt und kodiert, über USB-Seriell an den ESP32 gesendet und über ITS-G5 gesendet. Auf diesem Pfad gibt es keinen MQTT-Broker und keine DENM-Use-Case-Engine — siehe den CAM-Pinger im V2X-Monitor für ein manuelles Testwerkzeug.</string>
|
||||
<string name="settings_esp32_rx_mode">CAM-Empfang</string>
|
||||
<string name="settings_esp32_rx_mode_send_only">Nur senden</string>
|
||||
<string name="settings_esp32_rx_mode_send_and_receive">Senden & Empfangen</string>
|
||||
<string name="settings_esp32_rx_mode_desc">„Nur senden" ignoriert von nahen Stationen empfangene CAM (der ESP32 empfängt sie physisch weiterhin — der Empfänger kann nicht abgeschaltet werden, ohne auch das Senden zu unterbrechen — sie werden nur nicht von der App verarbeitet). Nützlich, um den Sendepfad isoliert zu testen.</string>
|
||||
<string name="settings_usb_transport">Aktiver Transport</string>
|
||||
<string name="settings_transport_usbc">USB-C</string>
|
||||
<string name="settings_transport_wifi">WLAN</string>
|
||||
@@ -270,7 +266,7 @@
|
||||
<string name="settings_platform">Plattform</string>
|
||||
<string name="settings_platform_val">Android / Kotlin / Jetpack Compose</string>
|
||||
<string name="settings_project">Projekt</string>
|
||||
<string name="settings_project_val">MicrOBU — HAW Hamburg</string>
|
||||
<string name="settings_project_val">MicrOBU — HAW Hamburg & consider it GmbH</string>
|
||||
|
||||
<!-- Phase A: Trips (bottom nav) -->
|
||||
<string name="nav_trips">Fahrten</string>
|
||||
|
||||
@@ -163,7 +163,7 @@
|
||||
<string name="settings_wifi_val">Dev mode only — not implemented</string>
|
||||
<string name="settings_about">About</string>
|
||||
<string name="settings_app_version">App version</string>
|
||||
<string name="settings_app_version_val">0.4.0 (Phase 02 — USB-C + DENM TX)</string>
|
||||
<string name="settings_app_version_val">0.5.0 (Phase 03 — ESP32-C5 serial link + phone-built CAM)</string>
|
||||
<string name="settings_connection">Connection</string>
|
||||
<string name="settings_usb_auto_detect">Auto-detect OBU via USB-C</string>
|
||||
<string name="settings_usb_manual_ip">Manual OBU IP</string>
|
||||
@@ -171,10 +171,6 @@
|
||||
<string name="settings_obu_hardware_cit_one">CiT One</string>
|
||||
<string name="settings_obu_hardware_esp32">ESP32-C5</string>
|
||||
<string name="settings_obu_hardware_esp32_note">ESP32-C5 acts as a "dumb" transceiver: CAM is built and encoded on the phone, sent to the ESP32 over USB serial, and broadcast over ITS-G5. No MQTT broker or DENM use-case engine on this path — see the V2X Monitor screen\'s CAM Pinger for a manual test tool.</string>
|
||||
<string name="settings_esp32_rx_mode">CAM Reception</string>
|
||||
<string name="settings_esp32_rx_mode_send_only">Send Only</string>
|
||||
<string name="settings_esp32_rx_mode_send_and_receive">Send & Receive</string>
|
||||
<string name="settings_esp32_rx_mode_desc">"Send Only" ignores CAM received from nearby stations (still physically received by the ESP32 — its receiver can\'t be turned off without also breaking transmit — just not processed by the app). Useful for isolating TX-path testing.</string>
|
||||
<string name="settings_usb_transport">Active transport</string>
|
||||
<string name="settings_transport_usbc">USB-C</string>
|
||||
<string name="settings_transport_wifi">Wi-Fi</string>
|
||||
@@ -271,7 +267,7 @@
|
||||
<string name="settings_platform">Platform</string>
|
||||
<string name="settings_platform_val">Android / Kotlin / Jetpack Compose</string>
|
||||
<string name="settings_project">Project</string>
|
||||
<string name="settings_project_val">MicrOBU — HAW Hamburg</string>
|
||||
<string name="settings_project_val">MicrOBU — HAW Hamburg & consider it GmbH</string>
|
||||
|
||||
<!-- Phase A: Trip Recording (bottom nav) -->
|
||||
<string name="nav_trips">Trips</string>
|
||||
|
||||
Reference in New Issue
Block a user