Stop retaining detected manoeuvres; the CAM rate bump is their only consumer
The detector runs to raise the CAM transmit rate through a manoeuvre. Nothing else read its output once the UI was removed, so keeping the rows was storing data with no reader on the chance it would one day be analysed. Drops the detected_events table in schema v5, deletes DetectedEventEntity and the DAO and repository methods behind it, removes the insertEvent call from the recording service, and removes the per-event rows and their five columns (event_type, confidence, peak_accel, peak_gyro, duration_ms) from the trip CSV along with the events parameter threaded through buildTripCsv and shareTripCsv. A detected manoeuvre now lives for the length of one onDetectedEvent call. MIGRATION_1_2 still creates the table: a v1 install upgrades 1-2-3-4-5 and so creates it before v5 drops it. Removing it from the earlier migration would break that path for anyone who has not upgraded yet. trips.eventCount is kept. Dropping a SQLite column means recreating the table and copying every recorded ride across, which is real risk for one unused integer; the service still writes an accurate count and the CSV header still reports it. It is the only thing left about detected manoeuvres. This closes off the route to the false-positive measurement that 11.3 flags as missing, so 11.3 now says that outright rather than pointing at an export that no longer carries the data. Docs 11.3/11.4, the user guide, the README and the traceability matrix updated to match. 55 tests, 0 failures.
This commit is contained in:
@@ -38,7 +38,7 @@ Both paths converge at `CamUseCaseRepository`, which normalises whatever arrived
|
|||||||
|
|
||||||
**DENM transmission**; CiT One path only. Triggers the stationary vehicle profile (`hln-sv`, causeCode 94) via the consider it Use Case API. This is a manual antenna and range test tool. It is never triggered by a detected event or a use case alert, and the control is hidden entirely on the ESP32-C5 path.
|
**DENM transmission**; CiT One path only. Triggers the stationary vehicle profile (`hln-sv`, causeCode 94) via the consider it Use Case API. This is a manual antenna and range test tool. It is never triggered by a detected event or a use case alert, and the control is hidden entirely on the ESP32-C5 path.
|
||||||
|
|
||||||
**Trip recording**; foreground service records all sensor streams and detects cycling manoeuvres (braking, turning, stopping) using orientation-independent signal processing. Works fully offline with no OBU connected. The detected manoeuvres are not shown in the app - they raise the CAM transmit rate through the manoeuvre on the ESP32-C5 path, and are kept in the trip's CSV export for offline analysis.
|
**Trip recording**; foreground service records all sensor streams and detects cycling manoeuvres (braking, turning, stopping) using orientation-independent signal processing. Works fully offline with no OBU connected. The detected manoeuvres are neither shown nor stored - their only effect is to raise the CAM transmit rate through the manoeuvre on the ESP32-C5 path.
|
||||||
|
|
||||||
**Trip review**; past trips displayed as a route on an OpenStreetMap layer, with duration and distance.
|
**Trip review**; past trips displayed as a route on an OpenStreetMap layer, with duration and distance.
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package com.hawhamburg.micr0bu.data
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import com.hawhamburg.micr0bu.data.db.DetectedEventEntity
|
|
||||||
import com.hawhamburg.micr0bu.data.db.RecordedTripEntity
|
import com.hawhamburg.micr0bu.data.db.RecordedTripEntity
|
||||||
import com.hawhamburg.micr0bu.data.db.V2xMessageEntity
|
import com.hawhamburg.micr0bu.data.db.V2xMessageEntity
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -23,12 +22,15 @@ fun tripFileName(trip: RecordedTripEntity): String =
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds a single combined CSV for one trip: the raw sensor samples recorded alongside it, the
|
* Builds a single combined CSV for one trip: the raw sensor samples recorded alongside it, the
|
||||||
* events the detector fired, the GPS track, and every V2X message seen during the ride — all in
|
* GPS track, and every V2X message seen during the ride — all in one file, ordered by time.
|
||||||
* one file, ordered by time.
|
*
|
||||||
|
* Detected manoeuvres are deliberately absent. The detector exists to raise the CAM transmit
|
||||||
|
* rate (see EventDetector's KDoc); its output is not retained, so there is nothing to export
|
||||||
|
* beyond the per-trip count in the header.
|
||||||
*
|
*
|
||||||
* **Why one file rather than a zip of tables.** The point of the export is correlation: what was
|
* **Why one file rather than a zip of tables.** The point of the export is correlation: what was
|
||||||
* the bike doing when that CAM arrived, what did the detector make of it. Splitting those into
|
* the bike doing when that CAM arrived. Splitting those into separate files pushes the join
|
||||||
* separate files pushes the join onto whoever opens it. A leading `type` column keeps the rows
|
* onto whoever opens it. A leading `type` column keeps the rows
|
||||||
* distinguishable, which is the same shape the existing session CSV already uses, so the two
|
* distinguishable, which is the same shape the existing session CSV already uses, so the two
|
||||||
* remain readable by the same tooling.
|
* remain readable by the same tooling.
|
||||||
*
|
*
|
||||||
@@ -44,7 +46,6 @@ fun tripFileName(trip: RecordedTripEntity): String =
|
|||||||
suspend fun buildTripCsv(
|
suspend fun buildTripCsv(
|
||||||
context: Context,
|
context: Context,
|
||||||
trip: RecordedTripEntity,
|
trip: RecordedTripEntity,
|
||||||
events: List<DetectedEventEntity>,
|
|
||||||
v2xMessages: List<V2xMessageEntity>,
|
v2xMessages: List<V2xMessageEntity>,
|
||||||
): String = withContext(Dispatchers.IO) {
|
): String = withContext(Dispatchers.IO) {
|
||||||
buildString {
|
buildString {
|
||||||
@@ -59,7 +60,6 @@ suspend fun buildTripCsv(
|
|||||||
appendLine()
|
appendLine()
|
||||||
appendLine(
|
appendLine(
|
||||||
"type,timestamp_ms,timestamp_iso,lat,lon,speed_ms,heading_deg," +
|
"type,timestamp_ms,timestamp_iso,lat,lon,speed_ms,heading_deg," +
|
||||||
"event_type,confidence,peak_accel,peak_gyro,duration_ms," +
|
|
||||||
"station_id,station_type,is_own,yaw_rate_dps,rssi_dbm"
|
"station_id,station_type,is_own,yaw_rate_dps,rssi_dbm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -68,16 +68,6 @@ suspend fun buildTripCsv(
|
|||||||
appendLine(
|
appendLine(
|
||||||
"gps,${point.timestamp},${isoUtc.format(Date(point.timestamp))}," +
|
"gps,${point.timestamp},${isoUtc.format(Date(point.timestamp))}," +
|
||||||
"${point.lat},${point.lon},,," +
|
"${point.lat},${point.lon},,," +
|
||||||
",,,,," +
|
|
||||||
",,,"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
for (e in events) {
|
|
||||||
appendLine(
|
|
||||||
"event,${e.timestamp},${isoUtc.format(Date(e.timestamp))}," +
|
|
||||||
"${e.latitude},${e.longitude},${e.speedMps},," +
|
|
||||||
"${e.type},${e.confidence},${e.peakAccelMagnitude},${e.peakGyroMagnitude},${e.durationMs}," +
|
|
||||||
",,,,"
|
",,,,"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -86,14 +76,13 @@ suspend fun buildTripCsv(
|
|||||||
appendLine(
|
appendLine(
|
||||||
"v2x,${m.timestamp},${isoUtc.format(Date(m.timestamp))}," +
|
"v2x,${m.timestamp},${isoUtc.format(Date(m.timestamp))}," +
|
||||||
"${m.latitude},${m.longitude},${m.speedMps},${m.headingDeg}," +
|
"${m.latitude},${m.longitude},${m.speedMps},${m.headingDeg}," +
|
||||||
",,,,," +
|
|
||||||
"${m.stationId},${m.stationType},${m.isOwn},${m.yawRateDps ?: ""},${m.rssiDbm ?: ""}"
|
"${m.stationId},${m.stationType},${m.isOwn},${m.yawRateDps ?: ""},${m.rssiDbm ?: ""}"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Raw sensor samples, copied verbatim from the session CSV. Appended last rather than
|
// Raw sensor samples, copied verbatim from the session CSV. Appended last rather than
|
||||||
// merge-sorted in: a long ride is hundreds of thousands of rows, and sorting them against
|
// merge-sorted in: a long ride is hundreds of thousands of rows, and sorting them against
|
||||||
// the (comparatively tiny) event/V2X sets in memory would defeat the streaming that
|
// the (comparatively tiny) V2X set in memory would defeat the streaming that
|
||||||
// CsvExporter deliberately does. Each row carries its own timestamp, so sort on load.
|
// CsvExporter deliberately does. Each row carries its own timestamp, so sort on load.
|
||||||
val sessionCsv = trip.sessionId?.let { File(File(context.filesDir, "sessions"), "$it.csv") }
|
val sessionCsv = trip.sessionId?.let { File(File(context.filesDir, "sessions"), "$it.csv") }
|
||||||
if (sessionCsv != null && sessionCsv.exists()) {
|
if (sessionCsv != null && sessionCsv.exists()) {
|
||||||
@@ -110,12 +99,11 @@ suspend fun buildTripCsv(
|
|||||||
suspend fun shareTripCsv(
|
suspend fun shareTripCsv(
|
||||||
context: Context,
|
context: Context,
|
||||||
trip: RecordedTripEntity,
|
trip: RecordedTripEntity,
|
||||||
events: List<DetectedEventEntity>,
|
|
||||||
v2xMessages: List<V2xMessageEntity>,
|
v2xMessages: List<V2xMessageEntity>,
|
||||||
) {
|
) {
|
||||||
val fileName = tripFileName(trip)
|
val fileName = tripFileName(trip)
|
||||||
val cacheFile = File(context.cacheDir, fileName)
|
val cacheFile = File(context.cacheDir, fileName)
|
||||||
val csv = buildTripCsv(context, trip, events, v2xMessages)
|
val csv = buildTripCsv(context, trip, v2xMessages)
|
||||||
|
|
||||||
withContext(Dispatchers.IO) { cacheFile.writeText(csv) }
|
withContext(Dispatchers.IO) { cacheFile.writeText(csv) }
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,9 @@ package com.hawhamburg.micr0bu.data
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.hawhamburg.micr0bu.data.db.AppDatabase
|
import com.hawhamburg.micr0bu.data.db.AppDatabase
|
||||||
import com.hawhamburg.micr0bu.data.db.DetectedEventEntity
|
|
||||||
import com.hawhamburg.micr0bu.data.db.RecordedTripEntity
|
import com.hawhamburg.micr0bu.data.db.RecordedTripEntity
|
||||||
import com.hawhamburg.micr0bu.data.db.V2xMessageEntity
|
import com.hawhamburg.micr0bu.data.db.V2xMessageEntity
|
||||||
import com.hawhamburg.micr0bu.domain.cam.Cam
|
import com.hawhamburg.micr0bu.domain.cam.Cam
|
||||||
import com.hawhamburg.micr0bu.domain.detection.DetectedEvent
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -15,7 +13,7 @@ import java.io.File
|
|||||||
private const val TAG = "TripRepository"
|
private const val TAG = "TripRepository"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Repository that abstracts Room access for trips and detected events.
|
* Repository that abstracts Room access for trips and V2X messages.
|
||||||
*
|
*
|
||||||
* All suspend functions are safe to call from a coroutine running on any
|
* All suspend functions are safe to call from a coroutine running on any
|
||||||
* dispatcher; Room executes the actual SQL on its own I/O thread pool.
|
* dispatcher; Room executes the actual SQL on its own I/O thread pool.
|
||||||
@@ -81,14 +79,11 @@ class TripRepository(db: AppDatabase, private val context: Context) {
|
|||||||
* One-shot snapshots for export. The Flow-returning variants above stay observable for the UI;
|
* One-shot snapshots for export. The Flow-returning variants above stay observable for the UI;
|
||||||
* an export wants a value it can write out, not a stream it has to unsubscribe from.
|
* an export wants a value it can write out, not a stream it has to unsubscribe from.
|
||||||
*/
|
*/
|
||||||
suspend fun getEventsForTripOnce(tripId: Long): List<DetectedEventEntity> =
|
|
||||||
dao.getEventsForTrip(tripId).first()
|
|
||||||
|
|
||||||
suspend fun getV2xMessagesForTripOnce(tripId: Long): List<V2xMessageEntity> =
|
suspend fun getV2xMessagesForTripOnce(tripId: Long): List<V2xMessageEntity> =
|
||||||
dao.getV2xMessagesForTrip(tripId).first()
|
dao.getV2xMessagesForTrip(tripId).first()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes a trip and everything belonging to it: detected events and V2X messages go via the
|
* Deletes a trip and everything belonging to it: V2X messages go via the
|
||||||
* schema's CASCADE foreign keys, and the CSV recorded alongside it is removed here.
|
* schema's CASCADE foreign keys, and the CSV recorded alongside it is removed here.
|
||||||
*
|
*
|
||||||
* The CSV is a plain file outside the database, so nothing deletes it implicitly - before
|
* The CSV is a plain file outside the database, so nothing deletes it implicitly - before
|
||||||
@@ -109,32 +104,6 @@ class TripRepository(db: AppDatabase, private val context: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Events ────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Persists a domain [DetectedEvent] for the given [tripId].
|
|
||||||
* Converts the domain model to the Room entity.
|
|
||||||
*/
|
|
||||||
suspend fun insertEvent(tripId: Long, event: DetectedEvent) =
|
|
||||||
dao.insertEvent(
|
|
||||||
DetectedEventEntity(
|
|
||||||
tripId = tripId,
|
|
||||||
timestamp = event.timestamp,
|
|
||||||
type = event.type.name,
|
|
||||||
confidence = event.confidence.name,
|
|
||||||
latitude = event.latitude,
|
|
||||||
longitude = event.longitude,
|
|
||||||
speedMps = event.speedMps.toFloat(),
|
|
||||||
peakAccelMagnitude = event.peakAccelMagnitude.toFloat(),
|
|
||||||
peakGyroMagnitude = event.peakGyroMagnitude.toFloat(),
|
|
||||||
durationMs = event.durationMs,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
/** Emits events for [tripId] ordered by timestamp, updating whenever the DB changes. */
|
|
||||||
fun getEventsForTrip(tripId: Long): Flow<List<DetectedEventEntity>> =
|
|
||||||
dao.getEventsForTrip(tripId)
|
|
||||||
|
|
||||||
// ── V2X messages (Phase 03) ──────────────────────────────────────────────────
|
// ── V2X messages (Phase 03) ──────────────────────────────────────────────────
|
||||||
// Retention policy: only ever called while a trip is actively recording — see
|
// Retention policy: only ever called while a trip is actively recording — see
|
||||||
// V2xMessageEntity's KDoc and CamUseCaseRepository.processedCam's collector in
|
// V2xMessageEntity's KDoc and CamUseCaseRepository.processedCam's collector in
|
||||||
|
|||||||
@@ -11,10 +11,9 @@ import androidx.sqlite.db.SupportSQLiteDatabase
|
|||||||
entities = [
|
entities = [
|
||||||
SessionEntity::class,
|
SessionEntity::class,
|
||||||
RecordedTripEntity::class,
|
RecordedTripEntity::class,
|
||||||
DetectedEventEntity::class,
|
|
||||||
V2xMessageEntity::class,
|
V2xMessageEntity::class,
|
||||||
],
|
],
|
||||||
version = 4,
|
version = 5,
|
||||||
exportSchema = false,
|
exportSchema = false,
|
||||||
)
|
)
|
||||||
abstract class AppDatabase : RoomDatabase() {
|
abstract class AppDatabase : RoomDatabase() {
|
||||||
@@ -34,13 +33,29 @@ abstract class AppDatabase : RoomDatabase() {
|
|||||||
AppDatabase::class.java,
|
AppDatabase::class.java,
|
||||||
"micr0bu.db",
|
"micr0bu.db",
|
||||||
)
|
)
|
||||||
.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4)
|
.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5)
|
||||||
.build()
|
.build()
|
||||||
.also { INSTANCE = it }
|
.also { INSTANCE = it }
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Migrations ────────────────────────────────────────────────────────
|
// ── Migrations ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drops `detected_events`. The cyclist event detector still runs, but its output is now
|
||||||
|
* consumed only by the CAM transmit-rate policy (see EventDetector's KDoc) and is no
|
||||||
|
* longer persisted, displayed, or exported, so the table had no reader left.
|
||||||
|
*
|
||||||
|
* `trips.eventCount` is deliberately kept. Dropping a column means recreating `trips`
|
||||||
|
* and copying every recorded ride across, which is real risk for one unused integer;
|
||||||
|
* the service still writes an accurate count into it and the CSV header still reports it.
|
||||||
|
*/
|
||||||
|
private val MIGRATION_4_5 = object : Migration(4, 5) {
|
||||||
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
|
database.execSQL("DROP INDEX IF EXISTS `index_detected_events_tripId`")
|
||||||
|
database.execSQL("DROP TABLE IF EXISTS `detected_events`")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Two additions:
|
* Two additions:
|
||||||
* - `trips.sessionId` links a trip to the CSV recording session captured alongside it, so
|
* - `trips.sessionId` links a trip to the CSV recording session captured alongside it, so
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
package com.hawhamburg.micr0bu.data.db
|
|
||||||
|
|
||||||
import androidx.room.ColumnInfo
|
|
||||||
import androidx.room.Entity
|
|
||||||
import androidx.room.ForeignKey
|
|
||||||
import androidx.room.PrimaryKey
|
|
||||||
|
|
||||||
/**
|
|
||||||
* One detected cyclist event (braking / turning / stopping) linked to a
|
|
||||||
* [RecordedTripEntity] via the [tripId] foreign key.
|
|
||||||
*
|
|
||||||
* [type] and [confidence] are stored as the enum name strings so that the
|
|
||||||
* database remains human-readable.
|
|
||||||
*/
|
|
||||||
@Entity(
|
|
||||||
tableName = "detected_events",
|
|
||||||
foreignKeys = [
|
|
||||||
ForeignKey(
|
|
||||||
entity = RecordedTripEntity::class,
|
|
||||||
parentColumns = ["id"],
|
|
||||||
childColumns = ["tripId"],
|
|
||||||
onDelete = ForeignKey.CASCADE,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)
|
|
||||||
data class DetectedEventEntity(
|
|
||||||
@PrimaryKey(autoGenerate = true)
|
|
||||||
val id: Long = 0,
|
|
||||||
|
|
||||||
@ColumnInfo(index = true)
|
|
||||||
val tripId: Long,
|
|
||||||
|
|
||||||
/** Wall-clock epoch ms of the first qualifying sensor frame. */
|
|
||||||
val timestamp: Long,
|
|
||||||
|
|
||||||
/** EventType.name — one of BRAKING, TURNING, STOPPING. */
|
|
||||||
val type: String,
|
|
||||||
|
|
||||||
/** Confidence.name — one of HIGH, MEDIUM, LOW. */
|
|
||||||
val confidence: String,
|
|
||||||
|
|
||||||
val latitude: Double,
|
|
||||||
val longitude: Double,
|
|
||||||
val speedMps: Float,
|
|
||||||
val peakAccelMagnitude: Float,
|
|
||||||
val peakGyroMagnitude: Float,
|
|
||||||
|
|
||||||
/** Duration from first qualifying frame to emission (ms). */
|
|
||||||
val durationMs: Long,
|
|
||||||
)
|
|
||||||
@@ -27,17 +27,6 @@ interface TripDao {
|
|||||||
@Query("DELETE FROM trips WHERE id = :id")
|
@Query("DELETE FROM trips WHERE id = :id")
|
||||||
suspend fun deleteTripById(id: Long)
|
suspend fun deleteTripById(id: Long)
|
||||||
|
|
||||||
// ── Events ────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
|
||||||
suspend fun insertEvent(event: DetectedEventEntity)
|
|
||||||
|
|
||||||
@Query("SELECT * FROM detected_events WHERE tripId = :tripId ORDER BY timestamp ASC")
|
|
||||||
fun getEventsForTrip(tripId: Long): Flow<List<DetectedEventEntity>>
|
|
||||||
|
|
||||||
@Query("SELECT COUNT(*) FROM detected_events WHERE tripId = :tripId")
|
|
||||||
suspend fun getEventCountForTrip(tripId: Long): Int
|
|
||||||
|
|
||||||
// ── V2X messages (Phase 03) ──────────────────────────────────────────────────
|
// ── V2X messages (Phase 03) ──────────────────────────────────────────────────
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
|||||||
@@ -142,9 +142,10 @@ class TripRecordingService : Service() {
|
|||||||
private val gpsTrackBuilder = StringBuilder("[")
|
private val gpsTrackBuilder = StringBuilder("[")
|
||||||
private var gpsPointCount = 0
|
private var gpsPointCount = 0
|
||||||
|
|
||||||
// Number of detected events in this trip. Kept only to populate the trips.eventCount
|
// Number of manoeuvres the detector fired during this trip. The only thing kept about
|
||||||
// column; the per-type counts are no longer surfaced anywhere (see EventDetector's KDoc
|
// them: it fills the trips.eventCount column, which predates this change and cannot be
|
||||||
// for why the detector still runs).
|
// dropped without rebuilding the trips table. See EventDetector's KDoc for why the
|
||||||
|
// detector still runs at all.
|
||||||
private var detectedEventCount = 0
|
private var detectedEventCount = 0
|
||||||
|
|
||||||
// ── SensorEventListener ───────────────────────────────────────────────────
|
// ── SensorEventListener ───────────────────────────────────────────────────
|
||||||
@@ -265,13 +266,11 @@ class TripRecordingService : Service() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect detector events. The detector's primary consumer is the CAM transmit rate
|
// Collect detector events. The CAM transmit-rate policy is their only consumer:
|
||||||
// policy below; persistence is retained so a recorded ride can still be analysed
|
// detected manoeuvres are not persisted, exported, or displayed.
|
||||||
// offline, but nothing in the UI reads it.
|
|
||||||
serviceScope.launch {
|
serviceScope.launch {
|
||||||
detector.events.collect { event ->
|
detector.events.collect { _ ->
|
||||||
if (currentTripId < 0) return@collect
|
if (currentTripId < 0) return@collect
|
||||||
repository.insertEvent(currentTripId, event)
|
|
||||||
// Bump the CAM transmit rate through the maneuver, not just at detection instant.
|
// Bump the CAM transmit rate through the maneuver, not just at detection instant.
|
||||||
// No-op on the CiT One path (see CamTransmitLoop's KDoc).
|
// No-op on the CiT One path (see CamTransmitLoop's KDoc).
|
||||||
camTransmitLoop.onDetectedEvent()
|
camTransmitLoop.onDetectedEvent()
|
||||||
|
|||||||
@@ -117,7 +117,6 @@ class TripRecordingViewModel(application: Application) : AndroidViewModel(applic
|
|||||||
shareTripCsv(
|
shareTripCsv(
|
||||||
context = context,
|
context = context,
|
||||||
trip = trip,
|
trip = trip,
|
||||||
events = repository.getEventsForTripOnce(tripId),
|
|
||||||
v2xMessages = repository.getV2xMessagesForTripOnce(tripId),
|
v2xMessages = repository.getV2xMessagesForTripOnce(tripId),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ share no code. The requirement is satisfied twice, by different means.
|
|||||||
| 11.1 | Orientation-Independent Sensor Strategy | **Done** | `SensorRepository.kt` (magnitude-based) | — |
|
| 11.1 | Orientation-Independent Sensor Strategy | **Done** | `SensorRepository.kt` (magnitude-based) | — |
|
||||||
| 11.2 | Running Standard Deviation Event Detector | **Done** | `EventDetector.kt`, `RunningStats.kt` | **18 unit tests, 0 failures** |
|
| 11.2 | Running Standard Deviation Event Detector | **Done** | `EventDetector.kt`, `RunningStats.kt` | **18 unit tests, 0 failures** |
|
||||||
| 11.3 | Trip Recording Architecture | **Done** | `TripRepository.kt`, `TripRecordingService.kt` *(cited)* | — |
|
| 11.3 | Trip Recording Architecture | **Done** | `TripRepository.kt`, `TripRecordingService.kt` *(cited)* | — |
|
||||||
| 11.4 | Data Model | **Done** | `data/db/` Room entities *(cited)* | — |
|
| 11.4 | Data Model | **Partial — scope reduced** | `data/db/` Room entities *(cited)* | `detected_events` dropped in schema v5, see scope note |
|
||||||
| 11.5 | New UI Elements for Phase A | **Partial — scope reduced** | `TripHistoryScreen.kt`, `TripReviewScreen.kt` | event pins/counters removed by decision, see note |
|
| 11.5 | New UI Elements for Phase A | **Partial — scope reduced** | `TripHistoryScreen.kt`, `TripReviewScreen.kt` | event pins/counters removed by decision, see note |
|
||||||
| 11.6 | Phase A Success Criteria | **Partial** | — | needs a real ride; see Open Items |
|
| 11.6 | Phase A Success Criteria | **Partial** | — | needs a real ride; see Open Items |
|
||||||
|
|
||||||
@@ -154,7 +154,10 @@ the coloured event pins and detail sheet on the trip review map, and the event c
|
|||||||
history card — was removed deliberately. A count of the rider's own braking events is not a goal of
|
history card — was removed deliberately. A count of the rider's own braking events is not a goal of
|
||||||
this project. The detector itself still runs: it is the input to the CAM transmit-rate policy
|
this project. The detector itself still runs: it is the input to the CAM transmit-rate policy
|
||||||
(§ 13), which raises the beacon rate from 1 Hz to the elevated rate for five seconds after a
|
(§ 13), which raises the beacon rate from 1 Hz to the elevated rate for five seconds after a
|
||||||
detected manoeuvre. Events remain persisted and exported to CSV for offline analysis.
|
detected manoeuvre. That is now its only effect: the `detected_events` table was dropped in schema
|
||||||
|
v5 and the per-event rows removed from the trip CSV, so a detected manoeuvre is consumed and
|
||||||
|
discarded. `trips.eventCount` is kept as a single integer per ride, since dropping a SQLite column
|
||||||
|
means recreating the table.
|
||||||
|
|
||||||
**Defect note (11.2).** Two defects found while documenting the detector were fixed on 2026-09-07.
|
**Defect note (11.2).** Two defects found while documenting the detector were fixed on 2026-09-07.
|
||||||
The nine threshold overrides in `TripRecordingService`'s constructor were promoted to
|
The nine threshold overrides in `TripRecordingService`'s constructor were promoted to
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user