fix #21: Save last known gps position

This commit is contained in:
Tim Kluge 2025-01-03 00:18:42 +01:00
parent 407755e94b
commit a662cc6757
6 changed files with 96 additions and 16 deletions

View File

@ -48,9 +48,8 @@ import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNot
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.scan
import kotlinx.coroutines.flow.single
import kotlinx.coroutines.flow.timeout
@ -68,6 +67,7 @@ val settingsKey = stringPreferencesKey("settings")
val widgetSettingsKey = stringPreferencesKey("widgetSettings")
val currentDataKey = stringPreferencesKey("current")
val statsKey = stringPreferencesKey("stats")
val lastKnownPositionKey = stringPreferencesKey("lastKnownPosition")
suspend fun saveSettings(context: Context, settings: HeadwindSettings) {
context.dataStore.edit { t ->
@ -93,6 +93,18 @@ suspend fun saveCurrentData(context: Context, forecast: OpenMeteoCurrentWeatherR
}
}
suspend fun saveLastKnownPosition(context: Context, gpsCoordinates: GpsCoordinates) {
Log.i(KarooHeadwindExtension.TAG, "Saving last known position: $gpsCoordinates")
try {
context.dataStore.edit { t ->
t[lastKnownPositionKey] = Json.encodeToString(gpsCoordinates)
}
} catch(e: Throwable){
Log.e(KarooHeadwindExtension.TAG, "Failed to save last known position", e)
}
}
fun KarooSystemService.streamDataFlow(dataTypeId: String): Flow<StreamState> {
return callbackFlow {
val listenerId = addConsumer(OnStreamState.StartStreaming(dataTypeId)) { event: OnStreamState ->
@ -194,6 +206,22 @@ fun Context.streamStats(): Flow<HeadwindStats> {
}.distinctUntilChanged()
}
suspend fun Context.getLastKnownPosition(): GpsCoordinates? {
val settingsJson = dataStore.data.first()
try {
val lastKnownPositionString = settingsJson[lastKnownPositionKey] ?: return null
val lastKnownPosition = jsonWithUnknownKeys.decodeFromString<GpsCoordinates>(
lastKnownPositionString
)
return lastKnownPosition
} catch(e: Throwable){
Log.e(KarooHeadwindExtension.TAG, "Failed to read last known position", e)
return null
}
}
fun KarooSystemService.streamUserProfile(): Flow<UserProfile> {
return callbackFlow {
val listenerId = addConsumer { userProfile: UserProfile ->
@ -268,7 +296,7 @@ sealed class HeadingResponse {
fun KarooSystemService.getRelativeHeadingFlow(context: Context): Flow<HeadingResponse> {
val currentWeatherData = context.streamCurrentWeatherData()
return getHeadingFlow()
return getHeadingFlow(context)
.combine(currentWeatherData) { bearing, data -> bearing to data }
.map { (bearing, data) ->
when {
@ -287,13 +315,12 @@ fun KarooSystemService.getRelativeHeadingFlow(context: Context): Flow<HeadingRes
}
}
fun KarooSystemService.getHeadingFlow(): Flow<HeadingResponse> {
fun KarooSystemService.getHeadingFlow(context: Context): Flow<HeadingResponse> {
// return flowOf(HeadingResponse.Value(20.0))
return streamDataFlow(DataType.Type.LOCATION)
.map { (it as? StreamState.Streaming)?.dataPoint?.values }
.map { values ->
val heading = values?.get(DataType.Field.LOC_BEARING)
return getGpsCoordinateFlow(context)
.map { coords ->
val heading = coords?.bearing
Log.d(KarooHeadwindExtension.TAG, "Updated gps bearing: $heading")
val headingValue = heading?.let { HeadingResponse.Value(it) }
@ -318,24 +345,56 @@ fun KarooSystemService.getHeadingFlow(): Flow<HeadingResponse> {
}
}
fun <T> concatenate(vararg flows: Flow<T>) = 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)
}
}
}
}
@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)
}
}
@OptIn(FlowPreview::class)
fun KarooSystemService.getGpsCoordinateFlow(context: Context): Flow<GpsCoordinates?> {
// return flowOf(GpsCoordinates(52.5164069,13.3784))
return streamDataFlow(DataType.Type.LOCATION)
val initialFlow = flow<GpsCoordinates> { context.getLastKnownPosition() }
val gpsFlow = streamDataFlow(DataType.Type.LOCATION)
.map { (it as? StreamState.Streaming)?.dataPoint?.values }
.map { values ->
val lat = values?.get(DataType.Field.LOC_LATITUDE)
val lon = values?.get(DataType.Field.LOC_LONGITUDE)
val bearing = values?.get(DataType.Field.LOC_BEARING)
if (lat != null && lon != null){
Log.d(KarooHeadwindExtension.TAG, "Updated gps coordinates: $lat $lon")
GpsCoordinates(lat, lon)
GpsCoordinates(lat, lon, bearing)
} else {
Log.w(KarooHeadwindExtension.TAG, "Gps unavailable: $values")
null
}
}
val concatenatedFlow = concatenate(initialFlow, gpsFlow)
return concatenatedFlow
.combine(context.streamSettings(this)) { gps, settings -> gps to settings }
.map { (gps, settings) ->
val rounded = gps?.round(settings.roundLocationTo.km.toDouble())

View File

@ -40,8 +40,10 @@ class KarooHeadwindExtension : KarooExtension("karoo-headwind", "1.1.3") {
const val TAG = "karoo-headwind"
}
lateinit var karooSystem: KarooSystemService
private var updateLastKnownGpsJob: Job? = null
private var serviceJob: Job? = null
override val types by lazy {
@ -71,6 +73,10 @@ class KarooHeadwindExtension : KarooExtension("karoo-headwind", "1.1.3") {
karooSystem = KarooSystemService(applicationContext)
updateLastKnownGpsJob = CoroutineScope(Dispatchers.IO).launch {
karooSystem.updateLastKnownGps(this@KarooHeadwindExtension)
}
serviceJob = CoroutineScope(Dispatchers.IO).launch {
karooSystem.connect { connected ->
if (connected) {

View File

@ -0,0 +1,16 @@
package de.timklge.karooheadwind
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
fun<T> Flow<T>.throttle(timeout: Long): Flow<T> = flow {
var lastEmissionTime = 0L
collect { value ->
val currentTime = System.currentTimeMillis()
if (currentTime - lastEmissionTime >= timeout) {
emit(value)
lastEmissionTime = currentTime
}
}
}

View File

@ -9,7 +9,7 @@ import kotlin.math.sin
import kotlin.math.sqrt
@Serializable
data class GpsCoordinates(val lat: Double, val lon: Double){
data class GpsCoordinates(val lat: Double, val lon: Double, val bearing: Double? = 0.0){
companion object {
private fun roundDegrees(degrees: Double, km: Double): Double {
val nkm = degrees * 111

View File

@ -66,10 +66,9 @@ class WeatherDataType(
val currentWeatherData = applicationContext.streamCurrentWeatherData()
currentWeatherData
.filterNotNull()
.collect { data ->
Log.d(KarooHeadwindExtension.TAG, "Wind code: ${data.current.weatherCode}")
emitter.onNext(StreamState.Streaming(DataPoint(dataTypeId, mapOf(DataType.Field.SINGLE to data.current.weatherCode.toDouble()))))
Log.d(KarooHeadwindExtension.TAG, "Wind code: ${data?.current?.weatherCode}")
emitter.onNext(StreamState.Streaming(DataPoint(dataTypeId, mapOf(DataType.Field.SINGLE to (data?.current?.weatherCode?.toDouble() ?: 0.0)))))
}
}
emitter.setCancellable {
@ -96,7 +95,7 @@ class WeatherDataType(
context.streamCurrentWeatherData()
.combine(context.streamSettings(karooSystem)) { data, settings -> StreamData(data, settings) }
.combine(karooSystem.streamUserProfile()) { data, profile -> data.copy(profile = profile) }
.combine(karooSystem.getHeadingFlow()) { data, heading -> data.copy(headingResponse = heading) }
.combine(karooSystem.getHeadingFlow(context)) { data, heading -> data.copy(headingResponse = heading) }
.collect { (data, settings, userProfile, headingResponse) ->
Log.d(KarooHeadwindExtension.TAG, "Updating weather view")

View File

@ -126,7 +126,7 @@ class WeatherForecastDataType(
.combine(context.streamSettings(karooSystem)) { data, settings -> StreamData(data, settings) }
.combine(karooSystem.streamUserProfile()) { data, profile -> data.copy(profile = profile) }
.combine(context.streamWidgetSettings()) { data, widgetSettings -> data.copy(widgetSettings = widgetSettings) }
.combine(karooSystem.getHeadingFlow()) { data, headingResponse -> data.copy(headingResponse = headingResponse) }
.combine(karooSystem.getHeadingFlow(context)) { data, headingResponse -> data.copy(headingResponse = headingResponse) }
.collect { (data, settings, widgetSettings, userProfile, headingResponse) ->
Log.d(KarooHeadwindExtension.TAG, "Updating weather forecast view")