From e6ee80e60eee51c3f151e6ba0021ee23f8d39183 Mon Sep 17 00:00:00 2001 From: Tim Kluge Date: Fri, 3 Jan 2025 02:02:43 +0100 Subject: [PATCH] Fix gps flow reset --- app/manifest.json | 2 +- .../de/timklge/karooheadwind/Extensions.kt | 87 ++++++++++--------- .../karooheadwind/KarooHeadwindExtension.kt | 15 +++- 3 files changed, 60 insertions(+), 44 deletions(-) diff --git a/app/manifest.json b/app/manifest.json index a31a952..aa1632a 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -7,5 +7,5 @@ "latestVersionCode": 7, "developer": "timklge", "description": "Provides headwind direction, wind speed and other weather data fields", - "releaseNotes": "Add hourly forecast and temperature datafields. Show error message in fields if no weather data or gps is available." + "releaseNotes": "Adds hourly forecast. Shows error message in fields if weather data or gps are unavailable and remembers last known gps position." } \ No newline at end of file diff --git a/app/src/main/kotlin/de/timklge/karooheadwind/Extensions.kt b/app/src/main/kotlin/de/timklge/karooheadwind/Extensions.kt index 0153f5d..75cdffa 100644 --- a/app/src/main/kotlin/de/timklge/karooheadwind/Extensions.kt +++ b/app/src/main/kotlin/de/timklge/karooheadwind/Extensions.kt @@ -39,13 +39,13 @@ import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.channels.trySendBlocking +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.filterNot +import kotlinx.coroutines.flow.emitAll import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flow @@ -124,10 +124,8 @@ suspend fun getErrorWidget(glance: GlanceRemoteViews, context: Context, settings "Headwind app not set up" } else if (headingResponse is HeadingResponse.NoGps){ "No GPS signal" - } else if (headingResponse is HeadingResponse.NoWeatherData) { - "No weather data" } else { - "Unknown error" + "Weather data download failed" } Log.d(KarooHeadwindExtension.TAG, "Error widget: $errorMessage") @@ -207,7 +205,7 @@ fun Context.streamStats(): Flow { } suspend fun Context.getLastKnownPosition(): GpsCoordinates? { - val settingsJson = dataStore.data.first() + val settingsJson = dataStore.data.first() try { val lastKnownPositionString = settingsJson[lastKnownPositionKey] ?: return null @@ -334,47 +332,61 @@ fun KarooSystemService.getHeadingFlow(context: Context): Flow { if (newAcc.size > 3) newAcc.drop(1) else newAcc } .map { data -> - if (data.isEmpty()) return@map HeadingResponse.NoGps + Log.i(KarooHeadwindExtension.TAG, "Heading value: $data") - if (data.all { it is HeadingResponse.Value }) { - val avg = data.mapNotNull { (it as? HeadingResponse.Value)?.diff }.average() - HeadingResponse.Value(avg) - } else { - data.first() - } + if (data.isEmpty()) return@map HeadingResponse.NoGps + if (data.firstOrNull() !is HeadingResponse.Value) return@map data.first() + + val avgValues = data.mapNotNull { (it as? HeadingResponse.Value)?.diff } + + if (avgValues.isEmpty()) return@map HeadingResponse.NoGps + + val avg = avgValues.average() + + HeadingResponse.Value(avg) } } fun concatenate(vararg flows: Flow) = flow { - var hadNullValue = false - for (flow in flows) { - flow.collect { value -> - if (!hadNullValue) { - emit(value) - if (value == null) hadNullValue = true - } else { - if (value != null) emit(value) - } + emitAll(flow) + } +} + +fun Flow.dropNullsIfNullEncountered(): Flow = flow { + var hadValue = false + + collect { value -> + if (!hadValue) { + emit(value) + if (value != null) hadValue = true + } else { + if (value != null) emit(value) } } } @OptIn(FlowPreview::class) suspend fun KarooSystemService.updateLastKnownGps(context: Context) { - getGpsCoordinateFlow(context) - .filterNotNull() - .throttle(60 * 1_000) // Only update last known gps position once every minute - .collect { gps -> - saveLastKnownPosition(context, gps) - } + while (true) { + getGpsCoordinateFlow(context) + .filterNotNull() + .throttle(60 * 1_000) // Only update last known gps position once every minute + .collect { gps -> + saveLastKnownPosition(context, gps) + } + delay(1_000) + } } @OptIn(FlowPreview::class) fun KarooSystemService.getGpsCoordinateFlow(context: Context): Flow { // return flowOf(GpsCoordinates(52.5164069,13.3784)) - val initialFlow = flow { context.getLastKnownPosition() } + val initialFlow = flow { + val lastKnownPosition = context.getLastKnownPosition() + if (lastKnownPosition != null) emit(lastKnownPosition) + } val gpsFlow = streamDataFlow(DataType.Type.LOCATION) .map { (it as? StreamState.Streaming)?.dataPoint?.values } @@ -384,10 +396,10 @@ fun KarooSystemService.getGpsCoordinateFlow(context: Context): Flow gps to settings } .map { (gps, settings) -> - val rounded = gps?.round(settings.roundLocationTo.km.toDouble()) - if (rounded != null) Log.d(KarooHeadwindExtension.TAG, "Round location to ${settings.roundLocationTo.km} - $rounded") - rounded + gps?.round(settings.roundLocationTo.km.toDouble()) } - .distinctUntilChanged { old, new -> - if (old != null && new != null) { - old.distanceTo(new).absoluteValue < 0.001 - } else { - old == new - } - } - .debounce(Duration.ofSeconds(10)) + .dropNullsIfNullEncountered() } \ No newline at end of file diff --git a/app/src/main/kotlin/de/timklge/karooheadwind/KarooHeadwindExtension.kt b/app/src/main/kotlin/de/timklge/karooheadwind/KarooHeadwindExtension.kt index 95af250..5c57f3e 100644 --- a/app/src/main/kotlin/de/timklge/karooheadwind/KarooHeadwindExtension.kt +++ b/app/src/main/kotlin/de/timklge/karooheadwind/KarooHeadwindExtension.kt @@ -23,15 +23,20 @@ import io.hammerhead.karooext.models.UserProfile import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.retry import kotlinx.coroutines.flow.transformLatest import kotlinx.coroutines.launch +import kotlinx.coroutines.time.debounce +import java.time.Duration +import kotlin.math.absoluteValue import kotlin.time.Duration.Companion.hours import kotlin.time.Duration.Companion.minutes @@ -67,7 +72,7 @@ class KarooHeadwindExtension : KarooExtension("karoo-headwind", "1.1.3") { data class StreamData(val settings: HeadwindSettings, val gps: GpsCoordinates?, val profile: UserProfile? = null) - @OptIn(ExperimentalCoroutinesApi::class) + @OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class) override fun onCreate() { super.onCreate() @@ -86,6 +91,14 @@ class KarooHeadwindExtension : KarooExtension("karoo-headwind", "1.1.3") { val gpsFlow = karooSystem .getGpsCoordinateFlow(this@KarooHeadwindExtension) + .distinctUntilChanged { old, new -> + if (old != null && new != null) { + old.distanceTo(new).absoluteValue < 0.001 + } else { + old == new + } + } + .debounce(Duration.ofSeconds(5)) .transformLatest { value: GpsCoordinates? -> while(true){ emit(value)