diff --git a/app/src/main/kotlin/de/timklge/karooheadwind/Extensions.kt b/app/src/main/kotlin/de/timklge/karooheadwind/Extensions.kt index 9160dba..32f8c3d 100644 --- a/app/src/main/kotlin/de/timklge/karooheadwind/Extensions.kt +++ b/app/src/main/kotlin/de/timklge/karooheadwind/Extensions.kt @@ -20,6 +20,7 @@ import kotlinx.coroutines.channels.trySendBlocking 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.filterNotNull @@ -32,6 +33,7 @@ import kotlinx.coroutines.time.debounce import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import java.time.Duration +import kotlin.math.abs import kotlin.math.absoluteValue import kotlin.math.roundToInt import kotlin.time.Duration.Companion.seconds @@ -142,8 +144,42 @@ suspend fun KarooSystemService.makeOpenMeteoHttpRequest(gpsCoordinates: GpsCoord }.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 { + 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 { - return flowOf(2) + // return flowOf(2) return streamDataFlow(DataType.Type.HEADING) .mapNotNull { (it as? StreamState.Streaming)?.dataPoint?.singleValue } @@ -153,7 +189,7 @@ fun KarooSystemService.getHeadingFlow(): Flow { @OptIn(FlowPreview::class) fun KarooSystemService.getGpsCoordinateFlow(): Flow { - return flowOf(GpsCoordinates(52.5164069,13.3784)) + // return flowOf(GpsCoordinates(52.5164069,13.3784)) return streamDataFlow("TYPE_LOCATION_ID") .mapNotNull { (it as? StreamState.Streaming)?.dataPoint?.values } diff --git a/app/src/main/kotlin/de/timklge/karooheadwind/KarooWinddirExtension.kt b/app/src/main/kotlin/de/timklge/karooheadwind/KarooHeadwindExtension.kt similarity index 93% rename from app/src/main/kotlin/de/timklge/karooheadwind/KarooWinddirExtension.kt rename to app/src/main/kotlin/de/timklge/karooheadwind/KarooHeadwindExtension.kt index 2683723..c5a42df 100644 --- a/app/src/main/kotlin/de/timklge/karooheadwind/KarooWinddirExtension.kt +++ b/app/src/main/kotlin/de/timklge/karooheadwind/KarooHeadwindExtension.kt @@ -8,8 +8,8 @@ import de.timklge.karooheadwind.datatypes.RelativeHumidityDataType import de.timklge.karooheadwind.datatypes.SurfacePressureDataType import de.timklge.karooheadwind.datatypes.WindDirectionDataType import de.timklge.karooheadwind.datatypes.WindGustsDataType -import de.timklge.karooheadwind.datatypes.WindSpeedDataType -import de.timklge.karooheadwind.datatypes.RelativeWindDirectionDataType +import de.timklge.karooheadwind.datatypes.HeadwindSpeedDataType +import de.timklge.karooheadwind.datatypes.HeadwindDirectionDataType import de.timklge.karooheadwind.datatypes.WeatherDataType import de.timklge.karooheadwind.screens.HeadwindStats import io.hammerhead.karooext.KarooSystemService @@ -40,11 +40,12 @@ class KarooHeadwindExtension : KarooExtension("karoo-headwind", "1.0.0-beta2") { override val types by lazy { listOf( - RelativeWindDirectionDataType(karooSystem, applicationContext), + HeadwindDirectionDataType(karooSystem, applicationContext), + HeadwindSpeedDataType(karooSystem, applicationContext), WeatherDataType(karooSystem, applicationContext), + HeadwindSpeedDataType(karooSystem, applicationContext), RelativeHumidityDataType(applicationContext), CloudCoverDataType(applicationContext), - WindSpeedDataType(applicationContext), WindGustsDataType(applicationContext), WindDirectionDataType(karooSystem, applicationContext), PrecipitationDataType(applicationContext), diff --git a/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/RelativeWindDirectionDataType.kt b/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/HeadwindDirectionDataType.kt similarity index 76% rename from app/src/main/kotlin/de/timklge/karooheadwind/datatypes/RelativeWindDirectionDataType.kt rename to app/src/main/kotlin/de/timklge/karooheadwind/datatypes/HeadwindDirectionDataType.kt index 7e9c1ae..d9b303e 100644 --- a/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/RelativeWindDirectionDataType.kt +++ b/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/HeadwindDirectionDataType.kt @@ -8,6 +8,7 @@ import androidx.glance.appwidget.GlanceRemoteViews import de.timklge.karooheadwind.KarooHeadwindExtension import de.timklge.karooheadwind.OpenMeteoCurrentWeatherResponse import de.timklge.karooheadwind.getHeadingFlow +import de.timklge.karooheadwind.getRelativeHeadingFlow import de.timklge.karooheadwind.screens.HeadwindSettings import de.timklge.karooheadwind.streamCurrentWeatherData import de.timklge.karooheadwind.streamDataFlow @@ -34,44 +35,16 @@ import kotlin.math.cos import kotlin.math.roundToInt @OptIn(ExperimentalGlanceRemoteViewsApi::class) -class RelativeWindDirectionDataType( +class HeadwindDirectionDataType( private val karooSystem: KarooSystemService, private val applicationContext: Context ) : DataTypeImpl("karoo-headwind", "headwind") { 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) { val job = CoroutineScope(Dispatchers.IO).launch { - val currentWeatherData = applicationContext.streamCurrentWeatherData() - - 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") + karooSystem.getRelativeHeadingFlow(applicationContext) + .collect { 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 result = glance.compose(context, DpSize.Unspecified) { - RelativeWindDirection(windDirection.roundToInt(), config.textSize, windSpeedText) + HeadwindDirection(windDirection.roundToInt(), config.textSize, windSpeedText) } emitter.updateView(result.remoteViews) diff --git a/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/RelativeWindDirectionView.kt b/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/HeadwindDirectionView.kt similarity index 96% rename from app/src/main/kotlin/de/timklge/karooheadwind/datatypes/RelativeWindDirectionView.kt rename to app/src/main/kotlin/de/timklge/karooheadwind/datatypes/HeadwindDirectionView.kt index 5c9921f..791c6a6 100644 --- a/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/RelativeWindDirectionView.kt +++ b/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/HeadwindDirectionView.kt @@ -47,7 +47,7 @@ fun getArrowResourceByBearing(bearing: Int): Int { @OptIn(ExperimentalGlancePreviewApi::class) @Preview(widthDp = 200, heightDp = 150) @Composable -fun RelativeWindDirection(bearing: Int, fontSize: Int, overlayText: String? = null) { +fun HeadwindDirection(bearing: Int, fontSize: Int, overlayText: String? = null) { Box( modifier = GlanceModifier.fillMaxSize().padding(5.dp), contentAlignment = Alignment( diff --git a/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/HeadwindSpeedDataType.kt b/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/HeadwindSpeedDataType.kt new file mode 100644 index 0000000..d1ce203 --- /dev/null +++ b/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/HeadwindSpeedDataType.kt @@ -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) { + 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() + } + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/WeatherView.kt b/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/WeatherView.kt index 95a10b2..b261fff 100644 --- a/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/WeatherView.kt +++ b/app/src/main/kotlin/de/timklge/karooheadwind/datatypes/WeatherView.kt @@ -49,7 +49,7 @@ fun Weather(current: WeatherInterpretation, windBearing: Int, windSpeed: Int, wi modifier = GlanceModifier.height(imageH.dp).width(imageW.dp), provider = ImageProvider(getWeatherIcon(current)), contentDescription = "Current weather information", - contentScale = ContentScale.FillBounds, + contentScale = ContentScale.Fit, colorFilter = ColorFilter.tint(ColorProvider(Color.Black, Color.White)) ) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3847260..0b11645 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,8 +1,8 @@ - WindDir + Headwind headwind - Wind direction - Current wind direction relative to the riding direction + Headwind + Current headwind direction Humidity Relative humidity in percent Cloud cover @@ -19,4 +19,6 @@ Atmospheric pressure at surface in configured unit Weather Current weather conditions + Headwind speed + Current headwind speed \ No newline at end of file diff --git a/app/src/main/res/xml/extension_info.xml b/app/src/main/res/xml/extension_info.xml index 2bc8c85..94bda37 100644 --- a/app/src/main/res/xml/extension_info.xml +++ b/app/src/main/res/xml/extension_info.xml @@ -18,6 +18,13 @@ icon="@drawable/ic_launcher" typeId="weather" /> + +