fix #6: Add separate headwind speed data field
This commit is contained in:
parent
0830674d62
commit
767e5fe769
@ -20,6 +20,7 @@ import kotlinx.coroutines.channels.trySendBlocking
|
|||||||
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.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.filter
|
import kotlinx.coroutines.flow.filter
|
||||||
import kotlinx.coroutines.flow.filterNotNull
|
import kotlinx.coroutines.flow.filterNotNull
|
||||||
@ -32,6 +33,7 @@ import kotlinx.coroutines.time.debounce
|
|||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
|
import kotlin.math.abs
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
@ -142,8 +144,42 @@ suspend fun KarooSystemService.makeOpenMeteoHttpRequest(gpsCoordinates: GpsCoord
|
|||||||
}.single()
|
}.single()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun signedAngleDifference(angle1: Double, angle2: Double): Double {
|
||||||
|
val a1 = angle1 % 360
|
||||||
|
val a2 = angle2 % 360
|
||||||
|
var diff = abs(a1 - a2)
|
||||||
|
|
||||||
|
val sign = if (a1 < a2) {
|
||||||
|
if (diff > 180.0) -1 else 1
|
||||||
|
} else {
|
||||||
|
if (diff > 180.0) 1 else -1
|
||||||
|
}
|
||||||
|
|
||||||
|
if (diff > 180.0) {
|
||||||
|
diff = 360.0 - diff
|
||||||
|
}
|
||||||
|
|
||||||
|
return sign * diff
|
||||||
|
}
|
||||||
|
|
||||||
|
fun KarooSystemService.getRelativeHeadingFlow(context: Context): Flow<Double> {
|
||||||
|
val currentWeatherData = context.streamCurrentWeatherData()
|
||||||
|
|
||||||
|
return getHeadingFlow()
|
||||||
|
.filter { it >= 0 }
|
||||||
|
.combine(currentWeatherData) { cardinalDirection, data -> cardinalDirection to data }
|
||||||
|
.map { (cardinalDirection, data) ->
|
||||||
|
val bearing = cardinalDirection * 45.0
|
||||||
|
val windBearing = data.current.windDirection + 180
|
||||||
|
val diff = (signedAngleDifference(bearing, windBearing) + 360) % 360
|
||||||
|
Log.d(KarooHeadwindExtension.TAG, "Wind bearing: $bearing vs $windBearing => $diff")
|
||||||
|
|
||||||
|
diff
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun KarooSystemService.getHeadingFlow(): Flow<Int> {
|
fun KarooSystemService.getHeadingFlow(): Flow<Int> {
|
||||||
return flowOf(2)
|
// return flowOf(2)
|
||||||
|
|
||||||
return streamDataFlow(DataType.Type.HEADING)
|
return streamDataFlow(DataType.Type.HEADING)
|
||||||
.mapNotNull { (it as? StreamState.Streaming)?.dataPoint?.singleValue }
|
.mapNotNull { (it as? StreamState.Streaming)?.dataPoint?.singleValue }
|
||||||
@ -153,7 +189,7 @@ fun KarooSystemService.getHeadingFlow(): Flow<Int> {
|
|||||||
|
|
||||||
@OptIn(FlowPreview::class)
|
@OptIn(FlowPreview::class)
|
||||||
fun KarooSystemService.getGpsCoordinateFlow(): Flow<GpsCoordinates> {
|
fun KarooSystemService.getGpsCoordinateFlow(): Flow<GpsCoordinates> {
|
||||||
return flowOf(GpsCoordinates(52.5164069,13.3784))
|
// return flowOf(GpsCoordinates(52.5164069,13.3784))
|
||||||
|
|
||||||
return streamDataFlow("TYPE_LOCATION_ID")
|
return streamDataFlow("TYPE_LOCATION_ID")
|
||||||
.mapNotNull { (it as? StreamState.Streaming)?.dataPoint?.values }
|
.mapNotNull { (it as? StreamState.Streaming)?.dataPoint?.values }
|
||||||
|
|||||||
@ -8,8 +8,8 @@ import de.timklge.karooheadwind.datatypes.RelativeHumidityDataType
|
|||||||
import de.timklge.karooheadwind.datatypes.SurfacePressureDataType
|
import de.timklge.karooheadwind.datatypes.SurfacePressureDataType
|
||||||
import de.timklge.karooheadwind.datatypes.WindDirectionDataType
|
import de.timklge.karooheadwind.datatypes.WindDirectionDataType
|
||||||
import de.timklge.karooheadwind.datatypes.WindGustsDataType
|
import de.timklge.karooheadwind.datatypes.WindGustsDataType
|
||||||
import de.timklge.karooheadwind.datatypes.WindSpeedDataType
|
import de.timklge.karooheadwind.datatypes.HeadwindSpeedDataType
|
||||||
import de.timklge.karooheadwind.datatypes.RelativeWindDirectionDataType
|
import de.timklge.karooheadwind.datatypes.HeadwindDirectionDataType
|
||||||
import de.timklge.karooheadwind.datatypes.WeatherDataType
|
import de.timklge.karooheadwind.datatypes.WeatherDataType
|
||||||
import de.timklge.karooheadwind.screens.HeadwindStats
|
import de.timklge.karooheadwind.screens.HeadwindStats
|
||||||
import io.hammerhead.karooext.KarooSystemService
|
import io.hammerhead.karooext.KarooSystemService
|
||||||
@ -40,11 +40,12 @@ class KarooHeadwindExtension : KarooExtension("karoo-headwind", "1.0.0-beta2") {
|
|||||||
|
|
||||||
override val types by lazy {
|
override val types by lazy {
|
||||||
listOf(
|
listOf(
|
||||||
RelativeWindDirectionDataType(karooSystem, applicationContext),
|
HeadwindDirectionDataType(karooSystem, applicationContext),
|
||||||
|
HeadwindSpeedDataType(karooSystem, applicationContext),
|
||||||
WeatherDataType(karooSystem, applicationContext),
|
WeatherDataType(karooSystem, applicationContext),
|
||||||
|
HeadwindSpeedDataType(karooSystem, applicationContext),
|
||||||
RelativeHumidityDataType(applicationContext),
|
RelativeHumidityDataType(applicationContext),
|
||||||
CloudCoverDataType(applicationContext),
|
CloudCoverDataType(applicationContext),
|
||||||
WindSpeedDataType(applicationContext),
|
|
||||||
WindGustsDataType(applicationContext),
|
WindGustsDataType(applicationContext),
|
||||||
WindDirectionDataType(karooSystem, applicationContext),
|
WindDirectionDataType(karooSystem, applicationContext),
|
||||||
PrecipitationDataType(applicationContext),
|
PrecipitationDataType(applicationContext),
|
||||||
@ -8,6 +8,7 @@ import androidx.glance.appwidget.GlanceRemoteViews
|
|||||||
import de.timklge.karooheadwind.KarooHeadwindExtension
|
import de.timklge.karooheadwind.KarooHeadwindExtension
|
||||||
import de.timklge.karooheadwind.OpenMeteoCurrentWeatherResponse
|
import de.timklge.karooheadwind.OpenMeteoCurrentWeatherResponse
|
||||||
import de.timklge.karooheadwind.getHeadingFlow
|
import de.timklge.karooheadwind.getHeadingFlow
|
||||||
|
import de.timklge.karooheadwind.getRelativeHeadingFlow
|
||||||
import de.timklge.karooheadwind.screens.HeadwindSettings
|
import de.timklge.karooheadwind.screens.HeadwindSettings
|
||||||
import de.timklge.karooheadwind.streamCurrentWeatherData
|
import de.timklge.karooheadwind.streamCurrentWeatherData
|
||||||
import de.timklge.karooheadwind.streamDataFlow
|
import de.timklge.karooheadwind.streamDataFlow
|
||||||
@ -34,44 +35,16 @@ import kotlin.math.cos
|
|||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
@OptIn(ExperimentalGlanceRemoteViewsApi::class)
|
@OptIn(ExperimentalGlanceRemoteViewsApi::class)
|
||||||
class RelativeWindDirectionDataType(
|
class HeadwindDirectionDataType(
|
||||||
private val karooSystem: KarooSystemService,
|
private val karooSystem: KarooSystemService,
|
||||||
private val applicationContext: Context
|
private val applicationContext: Context
|
||||||
) : DataTypeImpl("karoo-headwind", "headwind") {
|
) : DataTypeImpl("karoo-headwind", "headwind") {
|
||||||
private val glance = GlanceRemoteViews()
|
private val glance = GlanceRemoteViews()
|
||||||
|
|
||||||
private fun signedAngleDifference(angle1: Double, angle2: Double): Double {
|
|
||||||
val a1 = angle1 % 360
|
|
||||||
val a2 = angle2 % 360
|
|
||||||
var diff = abs(a1 - a2)
|
|
||||||
|
|
||||||
val sign = if (a1 < a2) {
|
|
||||||
if (diff > 180.0) -1 else 1
|
|
||||||
} else {
|
|
||||||
if (diff > 180.0) 1 else -1
|
|
||||||
}
|
|
||||||
|
|
||||||
if (diff > 180.0) {
|
|
||||||
diff = 360.0 - diff
|
|
||||||
}
|
|
||||||
|
|
||||||
return sign * diff
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun startStream(emitter: Emitter<StreamState>) {
|
override fun startStream(emitter: Emitter<StreamState>) {
|
||||||
val job = CoroutineScope(Dispatchers.IO).launch {
|
val job = CoroutineScope(Dispatchers.IO).launch {
|
||||||
val currentWeatherData = applicationContext.streamCurrentWeatherData()
|
karooSystem.getRelativeHeadingFlow(applicationContext)
|
||||||
|
.collect { diff ->
|
||||||
karooSystem
|
|
||||||
.getHeadingFlow()
|
|
||||||
.filter { it >= 0 }
|
|
||||||
.combine(currentWeatherData) { cardinalDirection, data -> cardinalDirection to data }
|
|
||||||
.collect { (cardinalDirection, data) ->
|
|
||||||
val bearing = cardinalDirection * 45.0
|
|
||||||
val windBearing = data.current.windDirection + 180
|
|
||||||
|
|
||||||
val diff = (signedAngleDifference(bearing, windBearing) + 360) % 360
|
|
||||||
Log.d(KarooHeadwindExtension.TAG, "Wind bearing: $bearing vs $windBearing => $diff")
|
|
||||||
emitter.onNext(StreamState.Streaming(DataPoint(dataTypeId, mapOf(DataType.Field.SINGLE to diff))))
|
emitter.onNext(StreamState.Streaming(DataPoint(dataTypeId, mapOf(DataType.Field.SINGLE to diff))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -107,7 +80,7 @@ class RelativeWindDirectionDataType(
|
|||||||
val windSpeedText = if(streamData.settings.showWindspeedOverlay) "${headwindSpeed.roundToInt()}" else null
|
val windSpeedText = if(streamData.settings.showWindspeedOverlay) "${headwindSpeed.roundToInt()}" else null
|
||||||
|
|
||||||
val result = glance.compose(context, DpSize.Unspecified) {
|
val result = glance.compose(context, DpSize.Unspecified) {
|
||||||
RelativeWindDirection(windDirection.roundToInt(), config.textSize, windSpeedText)
|
HeadwindDirection(windDirection.roundToInt(), config.textSize, windSpeedText)
|
||||||
}
|
}
|
||||||
|
|
||||||
emitter.updateView(result.remoteViews)
|
emitter.updateView(result.remoteViews)
|
||||||
@ -47,7 +47,7 @@ fun getArrowResourceByBearing(bearing: Int): Int {
|
|||||||
@OptIn(ExperimentalGlancePreviewApi::class)
|
@OptIn(ExperimentalGlancePreviewApi::class)
|
||||||
@Preview(widthDp = 200, heightDp = 150)
|
@Preview(widthDp = 200, heightDp = 150)
|
||||||
@Composable
|
@Composable
|
||||||
fun RelativeWindDirection(bearing: Int, fontSize: Int, overlayText: String? = null) {
|
fun HeadwindDirection(bearing: Int, fontSize: Int, overlayText: String? = null) {
|
||||||
Box(
|
Box(
|
||||||
modifier = GlanceModifier.fillMaxSize().padding(5.dp),
|
modifier = GlanceModifier.fillMaxSize().padding(5.dp),
|
||||||
contentAlignment = Alignment(
|
contentAlignment = Alignment(
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
package de.timklge.karooheadwind.datatypes
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import de.timklge.karooheadwind.OpenMeteoCurrentWeatherResponse
|
||||||
|
import de.timklge.karooheadwind.getRelativeHeadingFlow
|
||||||
|
import de.timklge.karooheadwind.screens.HeadwindSettings
|
||||||
|
import de.timklge.karooheadwind.streamCurrentWeatherData
|
||||||
|
import de.timklge.karooheadwind.streamSettings
|
||||||
|
import io.hammerhead.karooext.KarooSystemService
|
||||||
|
import io.hammerhead.karooext.extension.DataTypeImpl
|
||||||
|
import io.hammerhead.karooext.internal.Emitter
|
||||||
|
import io.hammerhead.karooext.models.DataPoint
|
||||||
|
import io.hammerhead.karooext.models.DataType
|
||||||
|
import io.hammerhead.karooext.models.StreamState
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlin.math.cos
|
||||||
|
|
||||||
|
class HeadwindSpeedDataType(
|
||||||
|
private val karooSystem: KarooSystemService,
|
||||||
|
private val context: Context) : DataTypeImpl("karoo-headwind", "headwindSpeed"){
|
||||||
|
|
||||||
|
data class StreamData(val value: Double, val data: OpenMeteoCurrentWeatherResponse, val settings: HeadwindSettings)
|
||||||
|
|
||||||
|
override fun startStream(emitter: Emitter<StreamState>) {
|
||||||
|
val job = CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
karooSystem.getRelativeHeadingFlow(context)
|
||||||
|
.combine(context.streamCurrentWeatherData()) { value, data -> value to data }
|
||||||
|
.combine(context.streamSettings()) { (value, data), settings -> StreamData(value, data, settings) }
|
||||||
|
.collect { streamData ->
|
||||||
|
val windSpeed = streamData.data.current.windSpeed
|
||||||
|
val windDirection = streamData.value
|
||||||
|
val headwindSpeed = cos( (windDirection + 180) * Math.PI / 180.0) * windSpeed
|
||||||
|
|
||||||
|
emitter.onNext(StreamState.Streaming(DataPoint(dataTypeId, mapOf(DataType.Field.SINGLE to headwindSpeed))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emitter.setCancellable {
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -49,7 +49,7 @@ fun Weather(current: WeatherInterpretation, windBearing: Int, windSpeed: Int, wi
|
|||||||
modifier = GlanceModifier.height(imageH.dp).width(imageW.dp),
|
modifier = GlanceModifier.height(imageH.dp).width(imageW.dp),
|
||||||
provider = ImageProvider(getWeatherIcon(current)),
|
provider = ImageProvider(getWeatherIcon(current)),
|
||||||
contentDescription = "Current weather information",
|
contentDescription = "Current weather information",
|
||||||
contentScale = ContentScale.FillBounds,
|
contentScale = ContentScale.Fit,
|
||||||
colorFilter = ColorFilter.tint(ColorProvider(Color.Black, Color.White))
|
colorFilter = ColorFilter.tint(ColorProvider(Color.Black, Color.White))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">WindDir</string>
|
<string name="app_name">Headwind</string>
|
||||||
<string name="extension_name">headwind</string>
|
<string name="extension_name">headwind</string>
|
||||||
<string name="headwind">Wind direction</string>
|
<string name="headwind">Headwind</string>
|
||||||
<string name="headwind_description">Current wind direction relative to the riding direction</string>
|
<string name="headwind_description">Current headwind direction</string>
|
||||||
<string name="relativeHumidity">Humidity</string>
|
<string name="relativeHumidity">Humidity</string>
|
||||||
<string name="relativeHumidity_description">Relative humidity in percent</string>
|
<string name="relativeHumidity_description">Relative humidity in percent</string>
|
||||||
<string name="cloudCover">Cloud cover</string>
|
<string name="cloudCover">Cloud cover</string>
|
||||||
@ -19,4 +19,6 @@
|
|||||||
<string name="surfacePressure_description">Atmospheric pressure at surface in configured unit</string>
|
<string name="surfacePressure_description">Atmospheric pressure at surface in configured unit</string>
|
||||||
<string name="weather">Weather</string>
|
<string name="weather">Weather</string>
|
||||||
<string name="weather_description">Current weather conditions</string>
|
<string name="weather_description">Current weather conditions</string>
|
||||||
|
<string name="headwind_speed">Headwind speed</string>
|
||||||
|
<string name="headwind_speed_description">Current headwind speed</string>
|
||||||
</resources>
|
</resources>
|
||||||
@ -18,6 +18,13 @@
|
|||||||
icon="@drawable/ic_launcher"
|
icon="@drawable/ic_launcher"
|
||||||
typeId="weather" />
|
typeId="weather" />
|
||||||
|
|
||||||
|
<DataType
|
||||||
|
description="@string/headwind_speed_description"
|
||||||
|
displayName="@string/headwind_speed"
|
||||||
|
graphical="false"
|
||||||
|
icon="@drawable/ic_launcher"
|
||||||
|
typeId="headwindSpeed" />
|
||||||
|
|
||||||
<DataType
|
<DataType
|
||||||
description="@string/relativeHumidity_description"
|
description="@string/relativeHumidity_description"
|
||||||
displayName="@string/relativeHumidity"
|
displayName="@string/relativeHumidity"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user