Fix gps flow reset

This commit is contained in:
Tim Kluge 2025-01-03 02:02:43 +01:00
parent d3109e459c
commit e6ee80e60e
3 changed files with 60 additions and 44 deletions

View File

@ -7,5 +7,5 @@
"latestVersionCode": 7, "latestVersionCode": 7,
"developer": "timklge", "developer": "timklge",
"description": "Provides headwind direction, wind speed and other weather data fields", "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."
} }

View File

@ -39,13 +39,13 @@ import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.channels.trySendBlocking import kotlinx.coroutines.channels.trySendBlocking
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.filterNot
import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
@ -124,10 +124,8 @@ suspend fun getErrorWidget(glance: GlanceRemoteViews, context: Context, settings
"Headwind app not set up" "Headwind app not set up"
} else if (headingResponse is HeadingResponse.NoGps){ } else if (headingResponse is HeadingResponse.NoGps){
"No GPS signal" "No GPS signal"
} else if (headingResponse is HeadingResponse.NoWeatherData) {
"No weather data"
} else { } else {
"Unknown error" "Weather data download failed"
} }
Log.d(KarooHeadwindExtension.TAG, "Error widget: $errorMessage") Log.d(KarooHeadwindExtension.TAG, "Error widget: $errorMessage")
@ -334,47 +332,61 @@ fun KarooSystemService.getHeadingFlow(context: Context): Flow<HeadingResponse> {
if (newAcc.size > 3) newAcc.drop(1) else newAcc if (newAcc.size > 3) newAcc.drop(1) else newAcc
} }
.map { data -> .map { data ->
if (data.isEmpty()) return@map HeadingResponse.NoGps Log.i(KarooHeadwindExtension.TAG, "Heading value: $data")
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()
if (data.all { it is HeadingResponse.Value }) {
val avg = data.mapNotNull { (it as? HeadingResponse.Value)?.diff }.average()
HeadingResponse.Value(avg) HeadingResponse.Value(avg)
} else {
data.first()
}
} }
} }
fun <T> concatenate(vararg flows: Flow<T>) = flow { fun <T> concatenate(vararg flows: Flow<T>) = flow {
var hadNullValue = false
for (flow in flows) { for (flow in flows) {
flow.collect { value -> emitAll(flow)
if (!hadNullValue) { }
}
fun<T> Flow<T>.dropNullsIfNullEncountered(): Flow<T?> = flow {
var hadValue = false
collect { value ->
if (!hadValue) {
emit(value) emit(value)
if (value == null) hadNullValue = true if (value != null) hadValue = true
} else { } else {
if (value != null) emit(value) if (value != null) emit(value)
} }
} }
} }
}
@OptIn(FlowPreview::class) @OptIn(FlowPreview::class)
suspend fun KarooSystemService.updateLastKnownGps(context: Context) { suspend fun KarooSystemService.updateLastKnownGps(context: Context) {
while (true) {
getGpsCoordinateFlow(context) getGpsCoordinateFlow(context)
.filterNotNull() .filterNotNull()
.throttle(60 * 1_000) // Only update last known gps position once every minute .throttle(60 * 1_000) // Only update last known gps position once every minute
.collect { gps -> .collect { gps ->
saveLastKnownPosition(context, gps) saveLastKnownPosition(context, gps)
} }
delay(1_000)
}
} }
@OptIn(FlowPreview::class) @OptIn(FlowPreview::class)
fun KarooSystemService.getGpsCoordinateFlow(context: Context): Flow<GpsCoordinates?> { fun KarooSystemService.getGpsCoordinateFlow(context: Context): Flow<GpsCoordinates?> {
// return flowOf(GpsCoordinates(52.5164069,13.3784)) // return flowOf(GpsCoordinates(52.5164069,13.3784))
val initialFlow = flow<GpsCoordinates> { context.getLastKnownPosition() } val initialFlow = flow {
val lastKnownPosition = context.getLastKnownPosition()
if (lastKnownPosition != null) emit(lastKnownPosition)
}
val gpsFlow = streamDataFlow(DataType.Type.LOCATION) val gpsFlow = streamDataFlow(DataType.Type.LOCATION)
.map { (it as? StreamState.Streaming)?.dataPoint?.values } .map { (it as? StreamState.Streaming)?.dataPoint?.values }
@ -384,10 +396,10 @@ fun KarooSystemService.getGpsCoordinateFlow(context: Context): Flow<GpsCoordinat
val bearing = values?.get(DataType.Field.LOC_BEARING) val bearing = values?.get(DataType.Field.LOC_BEARING)
if (lat != null && lon != null){ if (lat != null && lon != null){
Log.d(KarooHeadwindExtension.TAG, "Updated gps coordinates: $lat $lon") // Log.d(KarooHeadwindExtension.TAG, "Updated gps coordinates: $lat $lon")
GpsCoordinates(lat, lon, bearing) GpsCoordinates(lat, lon, bearing)
} else { } else {
Log.w(KarooHeadwindExtension.TAG, "Gps unavailable: $values") // Log.w(KarooHeadwindExtension.TAG, "Gps unavailable: $values")
null null
} }
} }
@ -397,16 +409,7 @@ fun KarooSystemService.getGpsCoordinateFlow(context: Context): Flow<GpsCoordinat
return concatenatedFlow return concatenatedFlow
.combine(context.streamSettings(this)) { gps, settings -> gps to settings } .combine(context.streamSettings(this)) { gps, settings -> gps to settings }
.map { (gps, settings) -> .map { (gps, settings) ->
val rounded = gps?.round(settings.roundLocationTo.km.toDouble()) gps?.round(settings.roundLocationTo.km.toDouble())
if (rounded != null) Log.d(KarooHeadwindExtension.TAG, "Round location to ${settings.roundLocationTo.km} - $rounded")
rounded
} }
.distinctUntilChanged { old, new -> .dropNullsIfNullEncountered()
if (old != null && new != null) {
old.distanceTo(new).absoluteValue < 0.001
} else {
old == new
}
}
.debounce(Duration.ofSeconds(10))
} }

View File

@ -23,15 +23,20 @@ import io.hammerhead.karooext.models.UserProfile
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.retry import kotlinx.coroutines.flow.retry
import kotlinx.coroutines.flow.transformLatest import kotlinx.coroutines.flow.transformLatest
import kotlinx.coroutines.launch 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.hours
import kotlin.time.Duration.Companion.minutes 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?, data class StreamData(val settings: HeadwindSettings, val gps: GpsCoordinates?,
val profile: UserProfile? = null) val profile: UserProfile? = null)
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
@ -86,6 +91,14 @@ class KarooHeadwindExtension : KarooExtension("karoo-headwind", "1.1.3") {
val gpsFlow = karooSystem val gpsFlow = karooSystem
.getGpsCoordinateFlow(this@KarooHeadwindExtension) .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? -> .transformLatest { value: GpsCoordinates? ->
while(true){ while(true){
emit(value) emit(value)