The app now speaks the station-link protocol of the new obu-firmware. Esp32Link picks the transport from Settings (UsbSerialTransport or the new BleLinkTransport), tells the previous firmware from the new one by its heartbeat, and runs the session: STATION_CONFIGURE with the current pseudonym MAC (which also starts the board's radio), CREDENTIALS_PROVISION of the bundled demo chain when the board has no ticket, then per message a POTI_UPDATE and a BTP_DATA_REQUEST. Received messages still arrive as V2X_RX frames, so the receive side is unchanged. A board on the previous firmware keeps working for CAM over USB. Settings > Connection > ESP32-C5: link USB-C or Bluetooth, transmit CAM or VAM, "Sign outgoing messages" (on by default). The connection card, top bar and dashboard show the link in use, the pairing passkey and signing counters. - VAM: VamUperCodec (TS 103 300-3 V2.3.1, bytes checked against asn1tools) and VamGenerationRules (clause 6.4, Tables 16/17). - BLE: the firmware's GATT layout (service 0000C175-...), MTU 517, pairing and encryption settled before any other operation (short timeouts during pairing made it loop), backoff between attempts, reasons on the card. - Clock: a PoTi goes to the board once per new fix and never moves the board's clock backwards except for a real correction (>= 60 s); stale and wobbling fix times made the board answer time_regression and restart its stack every few seconds. GnssTimeSource keeps the last measured phone-clock error while GNSS time drops out indoors: the bench phone is 14 minutes fast, and falling back to it made every transmitted timestamp jump by that much. - assets/demo-chain.vcr: throwaway, not EU-registered demo chain generated 2026-09-23 (AT B80B49387A4C12EB, psid 36 and 638). Its private key ships with the app on purpose; receivers verifying against the EU trust list drop what it signs. - Bluetooth permissions requested at start-up on Android 12+. StationLinkTest pins the codec to bytes from the colleague's Python implementation (microbu_link/messages.py). 103 unit tests pass.
61 lines
2.3 KiB
Kotlin
61 lines
2.3 KiB
Kotlin
package com.hawhamburg.micr0bu
|
|
|
|
import com.hawhamburg.micr0bu.domain.vam.VamGenerationRules
|
|
import com.hawhamburg.micr0bu.domain.vam.VamGenerationRules.Kinematics
|
|
import org.junit.Assert.assertFalse
|
|
import org.junit.Assert.assertTrue
|
|
import org.junit.Test
|
|
|
|
/** TS 103 300-3 clause 6.4 items 1 to 4, with the Table 16/17 values. */
|
|
class VamGenerationRulesTest {
|
|
|
|
private val here = Kinematics(53.5546667, 10.0223889, speedMps = 3.0, headingDeg = 90.0)
|
|
|
|
private fun sentAt(ms: Long, k: Kinematics = here) = VamGenerationRules().apply { onSent(ms, k, withLowFrequency = true) }
|
|
|
|
@Test
|
|
fun `the first VAM is always due`() {
|
|
assertTrue(VamGenerationRules().due(0, here))
|
|
}
|
|
|
|
@Test
|
|
fun `nothing is due within T_GenVamMin even after a big jump`() {
|
|
assertFalse(sentAt(1_000).due(1_050, here.copy(latitude = here.latitude + 0.001)))
|
|
}
|
|
|
|
@Test
|
|
fun `a stationary VRU gets one VAM every T_GenVamMax`() {
|
|
val rules = sentAt(1_000)
|
|
assertFalse(rules.due(5_900, here))
|
|
assertTrue(rules.due(6_001, here))
|
|
}
|
|
|
|
@Test
|
|
fun `position, speed and heading changes trigger past their thresholds only`() {
|
|
val rules = sentAt(1_000)
|
|
// ~3.3 m north: under 4 m. ~5.6 m: over.
|
|
assertFalse(rules.due(2_000, here.copy(latitude = here.latitude + 0.00003)))
|
|
assertTrue(rules.due(2_000, here.copy(latitude = here.latitude + 0.00005)))
|
|
assertFalse(rules.due(2_000, here.copy(speedMps = 3.4)))
|
|
assertTrue(rules.due(2_000, here.copy(speedMps = 3.6)))
|
|
assertFalse(rules.due(2_000, here.copy(headingDeg = 93.0)))
|
|
assertTrue(rules.due(2_000, here.copy(headingDeg = 95.0)))
|
|
}
|
|
|
|
@Test
|
|
fun `heading change is measured the short way round north`() {
|
|
val rules = sentAt(1_000, here.copy(headingDeg = 358.0))
|
|
assertFalse(rules.due(2_000, here.copy(headingDeg = 1.0))) // 3 degrees across north
|
|
assertTrue(rules.due(2_000, here.copy(headingDeg = 3.0))) // 5 degrees
|
|
}
|
|
|
|
@Test
|
|
fun `the low-frequency container goes with the first VAM, then every T_GenVamLFMin`() {
|
|
val rules = VamGenerationRules()
|
|
assertTrue(rules.includeLowFrequency(0))
|
|
rules.onSent(0, here, withLowFrequency = true)
|
|
assertFalse(rules.includeLowFrequency(1_999))
|
|
assertTrue(rules.includeLowFrequency(2_000))
|
|
}
|
|
}
|