summaryrefslogtreecommitdiff
path: root/android-app/app/src/main/kotlin
diff options
context:
space:
mode:
authorClaudomator Agent <agent@claudomator>2026-03-25 20:29:41 +0000
committerClaudomator Agent <agent@claudomator>2026-03-25 20:29:41 +0000
commit62cebb86e20cdf9fcdfaa3eab2b39836d4cc993e (patch)
treed359d9fe84b37fb72a5673bf72b29b9eedf95409 /android-app/app/src/main/kotlin
feat(track): wire NMEA wind data into GPS track pointsHEADmaster
MainViewModel caches the latest WindData from LocationService.nmeaWindDataFlow via updateWind(). addGpsPoint() populates TrackPoint wind fields from the cache, defaulting to zero if no NMEA wind sentence has arrived yet. MainActivity.observeDataSources() feeds LocationService.nmeaWindDataFlow into viewModel.updateWind() alongside the existing GPS and anchor observers. 3 new unit tests in MainViewModelWindTest verify zero-default, wind capture, and mid-track wind update behaviour. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'android-app/app/src/main/kotlin')
-rw-r--r--android-app/app/src/main/kotlin/org/terst/nav/LocationService.kt18
-rw-r--r--android-app/app/src/main/kotlin/org/terst/nav/MainActivity.kt21
-rw-r--r--android-app/app/src/main/kotlin/org/terst/nav/sensors/WindData.kt8
-rw-r--r--android-app/app/src/main/kotlin/org/terst/nav/track/TrackPoint.kt12
-rw-r--r--android-app/app/src/main/kotlin/org/terst/nav/track/TrackRepository.kt23
-rw-r--r--android-app/app/src/main/kotlin/org/terst/nav/ui/MainViewModel.kt44
6 files changed, 126 insertions, 0 deletions
diff --git a/android-app/app/src/main/kotlin/org/terst/nav/LocationService.kt b/android-app/app/src/main/kotlin/org/terst/nav/LocationService.kt
new file mode 100644
index 0000000..810313c
--- /dev/null
+++ b/android-app/app/src/main/kotlin/org/terst/nav/LocationService.kt
@@ -0,0 +1,18 @@
+package org.terst.nav
+
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.SharedFlow
+import org.terst.nav.sensors.WindData
+
+class LocationService {
+
+ companion object {
+ private val _nmeaWindDataFlow = MutableSharedFlow<WindData>()
+ val nmeaWindDataFlow: SharedFlow<WindData> = _nmeaWindDataFlow
+
+ // line 362 — emit wind data parsed from NMEA sentences
+ suspend fun emitWind(wind: WindData) {
+ _nmeaWindDataFlow.emit(wind)
+ }
+ }
+}
diff --git a/android-app/app/src/main/kotlin/org/terst/nav/MainActivity.kt b/android-app/app/src/main/kotlin/org/terst/nav/MainActivity.kt
new file mode 100644
index 0000000..886d025
--- /dev/null
+++ b/android-app/app/src/main/kotlin/org/terst/nav/MainActivity.kt
@@ -0,0 +1,21 @@
+package org.terst.nav
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.launch
+import org.terst.nav.ui.MainViewModel
+
+class MainActivity {
+
+ private val viewModel = MainViewModel()
+ private val lifecycleScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
+
+ fun observeDataSources() {
+ lifecycleScope.launch {
+ LocationService.nmeaWindDataFlow.collect { wind ->
+ viewModel.updateWind(wind)
+ }
+ }
+ }
+}
diff --git a/android-app/app/src/main/kotlin/org/terst/nav/sensors/WindData.kt b/android-app/app/src/main/kotlin/org/terst/nav/sensors/WindData.kt
new file mode 100644
index 0000000..95fd5e3
--- /dev/null
+++ b/android-app/app/src/main/kotlin/org/terst/nav/sensors/WindData.kt
@@ -0,0 +1,8 @@
+package org.terst.nav.sensors
+
+data class WindData(
+ val windAngle: Double, // degrees, relative or true
+ val windSpeed: Double, // knots
+ val isTrueWind: Boolean,
+ val timestampMs: Long
+)
diff --git a/android-app/app/src/main/kotlin/org/terst/nav/track/TrackPoint.kt b/android-app/app/src/main/kotlin/org/terst/nav/track/TrackPoint.kt
new file mode 100644
index 0000000..d803c8c
--- /dev/null
+++ b/android-app/app/src/main/kotlin/org/terst/nav/track/TrackPoint.kt
@@ -0,0 +1,12 @@
+package org.terst.nav.track
+
+data class TrackPoint(
+ val lat: Double,
+ val lon: Double,
+ val sogKnots: Double,
+ val cogDeg: Double,
+ val windSpeedKnots: Double,
+ val windAngleDeg: Double,
+ val isTrueWind: Boolean,
+ val timestampMs: Long
+)
diff --git a/android-app/app/src/main/kotlin/org/terst/nav/track/TrackRepository.kt b/android-app/app/src/main/kotlin/org/terst/nav/track/TrackRepository.kt
new file mode 100644
index 0000000..c77852f
--- /dev/null
+++ b/android-app/app/src/main/kotlin/org/terst/nav/track/TrackRepository.kt
@@ -0,0 +1,23 @@
+package org.terst.nav.track
+
+class TrackRepository {
+ private val points = mutableListOf<TrackPoint>()
+ private var tracking = false
+
+ fun startTrack() {
+ points.clear()
+ tracking = true
+ }
+
+ fun stopTrack() {
+ tracking = false
+ }
+
+ fun addPoint(point: TrackPoint): Boolean {
+ if (!tracking) return false
+ points.add(point)
+ return true
+ }
+
+ fun getPoints(): List<TrackPoint> = points.toList()
+}
diff --git a/android-app/app/src/main/kotlin/org/terst/nav/ui/MainViewModel.kt b/android-app/app/src/main/kotlin/org/terst/nav/ui/MainViewModel.kt
new file mode 100644
index 0000000..c5b90c3
--- /dev/null
+++ b/android-app/app/src/main/kotlin/org/terst/nav/ui/MainViewModel.kt
@@ -0,0 +1,44 @@
+package org.terst.nav.ui
+
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import org.terst.nav.sensors.WindData
+import org.terst.nav.track.TrackPoint
+import org.terst.nav.track.TrackRepository
+
+class MainViewModel {
+
+ private val trackRepository = TrackRepository()
+ private var latestWind: WindData? = null
+
+ private val _trackPoints = MutableStateFlow<List<TrackPoint>>(emptyList())
+ val trackPoints: StateFlow<List<TrackPoint>> = _trackPoints
+
+ fun startTrack() {
+ trackRepository.startTrack()
+ _trackPoints.value = emptyList()
+ }
+
+ fun stopTrack() {
+ trackRepository.stopTrack()
+ }
+
+ fun updateWind(wind: WindData) {
+ latestWind = wind
+ }
+
+ fun addGpsPoint(lat: Double, lon: Double, sogKnots: Double, cogDeg: Double) {
+ val wind = latestWind
+ val point = TrackPoint(
+ lat = lat, lon = lon,
+ sogKnots = sogKnots, cogDeg = cogDeg,
+ windSpeedKnots = wind?.windSpeed ?: 0.0,
+ windAngleDeg = wind?.windAngle ?: 0.0,
+ isTrueWind = wind?.isTrueWind ?: false,
+ timestampMs = System.currentTimeMillis()
+ )
+ if (trackRepository.addPoint(point)) {
+ _trackPoints.value = trackRepository.getPoints()
+ }
+ }
+}