fix #21: Save last known gps position
This commit is contained in:
parent
407755e94b
commit
a662cc6757
@ -48,9 +48,8 @@ import kotlinx.coroutines.flow.filter
|
|||||||
import kotlinx.coroutines.flow.filterNot
|
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.flowOf
|
import kotlinx.coroutines.flow.flow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.mapNotNull
|
|
||||||
import kotlinx.coroutines.flow.scan
|
import kotlinx.coroutines.flow.scan
|
||||||
import kotlinx.coroutines.flow.single
|
import kotlinx.coroutines.flow.single
|
||||||
import kotlinx.coroutines.flow.timeout
|
import kotlinx.coroutines.flow.timeout
|
||||||
@ -68,6 +67,7 @@ val settingsKey = stringPreferencesKey("settings")
|
|||||||
val widgetSettingsKey = stringPreferencesKey("widgetSettings")
|
val widgetSettingsKey = stringPreferencesKey("widgetSettings")
|
||||||
val currentDataKey = stringPreferencesKey("current")
|
val currentDataKey = stringPreferencesKey("current")
|
||||||
val statsKey = stringPreferencesKey("stats")
|
val statsKey = stringPreferencesKey("stats")
|
||||||
|
val lastKnownPositionKey = stringPreferencesKey("lastKnownPosition")
|
||||||
|
|
||||||
suspend fun saveSettings(context: Context, settings: HeadwindSettings) {
|
suspend fun saveSettings(context: Context, settings: HeadwindSettings) {
|
||||||
context.dataStore.edit { t ->
|
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> {
|
fun KarooSystemService.streamDataFlow(dataTypeId: String): Flow<StreamState> {
|
||||||
return callbackFlow {
|
return callbackFlow {
|
||||||
val listenerId = addConsumer(OnStreamState.StartStreaming(dataTypeId)) { event: OnStreamState ->
|
val listenerId = addConsumer(OnStreamState.StartStreaming(dataTypeId)) { event: OnStreamState ->
|
||||||
@ -194,6 +206,22 @@ fun Context.streamStats(): Flow<HeadwindStats> {
|
|||||||
}.distinctUntilChanged()
|
}.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> {
|
fun KarooSystemService.streamUserProfile(): Flow<UserProfile> {
|
||||||
return callbackFlow {
|
return callbackFlow {
|
||||||
val listenerId = addConsumer { userProfile: UserProfile ->
|
val listenerId = addConsumer { userProfile: UserProfile ->
|
||||||
@ -268,7 +296,7 @@ sealed class HeadingResponse {
|
|||||||
fun KarooSystemService.getRelativeHeadingFlow(context: Context): Flow<HeadingResponse> {
|
fun KarooSystemService.getRelativeHeadingFlow(context: Context): Flow<HeadingResponse> {
|
||||||
val currentWeatherData = context.streamCurrentWeatherData()
|
val currentWeatherData = context.streamCurrentWeatherData()
|
||||||
|
|
||||||
return getHeadingFlow()
|
return getHeadingFlow(context)
|
||||||
.combine(currentWeatherData) { bearing, data -> bearing to data }
|
.combine(currentWeatherData) { bearing, data -> bearing to data }
|
||||||
.map { (bearing, data) ->
|
.map { (bearing, data) ->
|
||||||
when {
|
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 flowOf(HeadingResponse.Value(20.0))
|
||||||
|
|
||||||
return streamDataFlow(DataType.Type.LOCATION)
|
return getGpsCoordinateFlow(context)
|
||||||
.map { (it as? StreamState.Streaming)?.dataPoint?.values }
|
.map { coords ->
|
||||||
.map { values ->
|
val heading = coords?.bearing
|
||||||
val heading = values?.get(DataType.Field.LOC_BEARING)
|
|
||||||
Log.d(KarooHeadwindExtension.TAG, "Updated gps bearing: $heading")
|
Log.d(KarooHeadwindExtension.TAG, "Updated gps bearing: $heading")
|
||||||
val headingValue = heading?.let { HeadingResponse.Value(it) }
|
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)
|
@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))
|
||||||
|
|
||||||
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 { (it as? StreamState.Streaming)?.dataPoint?.values }
|
||||||
.map { values ->
|
.map { values ->
|
||||||
val lat = values?.get(DataType.Field.LOC_LATITUDE)
|
val lat = values?.get(DataType.Field.LOC_LATITUDE)
|
||||||
val lon = values?.get(DataType.Field.LOC_LONGITUDE)
|
val lon = values?.get(DataType.Field.LOC_LONGITUDE)
|
||||||
|
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)
|
GpsCoordinates(lat, lon, bearing)
|
||||||
} else {
|
} else {
|
||||||
Log.w(KarooHeadwindExtension.TAG, "Gps unavailable: $values")
|
Log.w(KarooHeadwindExtension.TAG, "Gps unavailable: $values")
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val concatenatedFlow = concatenate(initialFlow, gpsFlow)
|
||||||
|
|
||||||
|
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())
|
val rounded = gps?.round(settings.roundLocationTo.km.toDouble())
|
||||||
|
|||||||
@ -40,8 +40,10 @@ class KarooHeadwindExtension : KarooExtension("karoo-headwind", "1.1.3") {
|
|||||||
const val TAG = "karoo-headwind"
|
const val TAG = "karoo-headwind"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
lateinit var karooSystem: KarooSystemService
|
lateinit var karooSystem: KarooSystemService
|
||||||
|
|
||||||
|
private var updateLastKnownGpsJob: Job? = null
|
||||||
private var serviceJob: Job? = null
|
private var serviceJob: Job? = null
|
||||||
|
|
||||||
override val types by lazy {
|
override val types by lazy {
|
||||||
@ -71,6 +73,10 @@ class KarooHeadwindExtension : KarooExtension("karoo-headwind", "1.1.3") {
|
|||||||
|
|
||||||
karooSystem = KarooSystemService(applicationContext)
|
karooSystem = KarooSystemService(applicationContext)
|
||||||
|
|
||||||
|
updateLastKnownGpsJob = CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
karooSystem.updateLastKnownGps(this@KarooHeadwindExtension)
|
||||||
|
}
|
||||||
|
|
||||||
serviceJob = CoroutineScope(Dispatchers.IO).launch {
|
serviceJob = CoroutineScope(Dispatchers.IO).launch {
|
||||||
karooSystem.connect { connected ->
|
karooSystem.connect { connected ->
|
||||||
if (connected) {
|
if (connected) {
|
||||||
|
|||||||
16
app/src/main/kotlin/de/timklge/karooheadwind/Throttle.kt
Normal file
16
app/src/main/kotlin/de/timklge/karooheadwind/Throttle.kt
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -9,7 +9,7 @@ import kotlin.math.sin
|
|||||||
import kotlin.math.sqrt
|
import kotlin.math.sqrt
|
||||||
|
|
||||||
@Serializable
|
@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 {
|
companion object {
|
||||||
private fun roundDegrees(degrees: Double, km: Double): Double {
|
private fun roundDegrees(degrees: Double, km: Double): Double {
|
||||||
val nkm = degrees * 111
|
val nkm = degrees * 111
|
||||||
|
|||||||
@ -66,10 +66,9 @@ class WeatherDataType(
|
|||||||
val currentWeatherData = applicationContext.streamCurrentWeatherData()
|
val currentWeatherData = applicationContext.streamCurrentWeatherData()
|
||||||
|
|
||||||
currentWeatherData
|
currentWeatherData
|
||||||
.filterNotNull()
|
|
||||||
.collect { data ->
|
.collect { data ->
|
||||||
Log.d(KarooHeadwindExtension.TAG, "Wind code: ${data.current.weatherCode}")
|
Log.d(KarooHeadwindExtension.TAG, "Wind code: ${data?.current?.weatherCode}")
|
||||||
emitter.onNext(StreamState.Streaming(DataPoint(dataTypeId, mapOf(DataType.Field.SINGLE to data.current.weatherCode.toDouble()))))
|
emitter.onNext(StreamState.Streaming(DataPoint(dataTypeId, mapOf(DataType.Field.SINGLE to (data?.current?.weatherCode?.toDouble() ?: 0.0)))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
emitter.setCancellable {
|
emitter.setCancellable {
|
||||||
@ -96,7 +95,7 @@ class WeatherDataType(
|
|||||||
context.streamCurrentWeatherData()
|
context.streamCurrentWeatherData()
|
||||||
.combine(context.streamSettings(karooSystem)) { data, settings -> StreamData(data, settings) }
|
.combine(context.streamSettings(karooSystem)) { data, settings -> StreamData(data, settings) }
|
||||||
.combine(karooSystem.streamUserProfile()) { data, profile -> data.copy(profile = profile) }
|
.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) ->
|
.collect { (data, settings, userProfile, headingResponse) ->
|
||||||
Log.d(KarooHeadwindExtension.TAG, "Updating weather view")
|
Log.d(KarooHeadwindExtension.TAG, "Updating weather view")
|
||||||
|
|
||||||
|
|||||||
@ -126,7 +126,7 @@ class WeatherForecastDataType(
|
|||||||
.combine(context.streamSettings(karooSystem)) { data, settings -> StreamData(data, settings) }
|
.combine(context.streamSettings(karooSystem)) { data, settings -> StreamData(data, settings) }
|
||||||
.combine(karooSystem.streamUserProfile()) { data, profile -> data.copy(profile = profile) }
|
.combine(karooSystem.streamUserProfile()) { data, profile -> data.copy(profile = profile) }
|
||||||
.combine(context.streamWidgetSettings()) { data, widgetSettings -> data.copy(widgetSettings = widgetSettings) }
|
.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) ->
|
.collect { (data, settings, widgetSettings, userProfile, headingResponse) ->
|
||||||
Log.d(KarooHeadwindExtension.TAG, "Updating weather forecast view")
|
Log.d(KarooHeadwindExtension.TAG, "Updating weather forecast view")
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user