Add weather data field (shows icon indicating weather conditions)

This commit is contained in:
Tim Kluge 2024-12-08 11:45:57 +01:00
parent 7d0c16d94c
commit 9d57bfde6f
26 changed files with 239 additions and 47 deletions

View File

@ -24,11 +24,11 @@ Currently, Hammerhead has not yet released an on-device app store for easy insta
After installing this app on your Karoo, you can add a data field showing the relative wind direction or one of the auxiliary fields to your data pages. The relative wind direction will be shown as an arrow image, with an optional overlay of the wind speed in your chosen unit of measurement (default is kilometers per hour).
The app will automatically attempt to download weather data for your current approximate location from the [open-meteo.com](https://open-meteo.com) API once your device has acquired a GPS fix. The API service is free for non-commercial use. Your location is rounded to approximately two kilometers to maintain privacy. The data is updated when you ride more than two kilometers from the location where the weather data was downloaded or after one hour at the latest. If the app cannot connect to the weather service, it will retry the download every minute. Downloading weather data should work on Karoo 2 if you have a SIM card inserted or on Karoo 3 via the companion app.
The app will automatically attempt to download weather data for your current approximate location from the [open-meteo.com](https://open-meteo.com) API once your device has acquired a GPS fix. The API service is free for non-commercial use. Your location is rounded to approximately two kilometers to maintain privacy. The data is updated when you ride more than two kilometers from the location where the weather data was downloaded or after one hour at the latest. If the app cannot connect to the weather service, it will retry the download every minute. Downloading weather data should work on Karoo 2 if you have a SIM card inserted or on Karoo 3 via your phone's internet connection if you have the Karoo companion app installed.
## Credits
- Icons are from [boxicons.com](https://boxicons.com) (MIT-licensed).
- Icons are from [boxicons.com](https://boxicons.com) (MIT-licensed)
- Made possible by the generous usage terms of [open-meteo.com](https://open-meteo.com)
## Links

View File

@ -80,7 +80,7 @@ fun Context.streamCurrentWeatherData(): Flow<OpenMeteoCurrentWeatherResponse> {
Log.e(KarooWinddirExtension.TAG, "Failed to read preferences", e)
null
}
}.filterNotNull().distinctUntilChanged().filter { it.current.time * 1000 >= System.currentTimeMillis() - (1000 * 60 * 60 * 3) }
}.filterNotNull().distinctUntilChanged().filter { it.current.time * 1000 >= System.currentTimeMillis() - (1000 * 60 * 60 * 12) }
}
fun Context.streamSettings(): Flow<WinddirSettings> {
@ -112,8 +112,8 @@ fun Context.streamStats(): Flow<WinddirStats> {
@OptIn(FlowPreview::class)
suspend fun KarooSystemService.makeOpenMeteoHttpRequest(gpsCoordinates: GpsCoordinates, settings: WinddirSettings): HttpResponseState.Complete {
return callbackFlow {
// https://open-meteo.com/en/docs#current=temperature_2m,relative_humidity_2m,apparent_temperature,precipitation,cloud_cover,wind_speed_10m,wind_direction_10m,wind_gusts_10m&hourly=&daily=&location_mode=csv_coordinates&timeformat=unixtime&forecast_days=3
val url = "https://api.open-meteo.com/v1/forecast?latitude=${gpsCoordinates.lat}&longitude=${gpsCoordinates.lon}&current=temperature_2m,relative_humidity_2m,apparent_temperature,precipitation,cloud_cover,wind_speed_10m,wind_direction_10m,wind_gusts_10m,surface_pressure&timeformat=unixtime&wind_speed_unit=${settings.windUnit.id}&precipitation_unit=${settings.precipitationUnit.id}"
// https://open-meteo.com/en/docs#current=temperature_2m,relative_humidity_2m,apparent_temperature,precipitation,weather_code,cloud_cover,wind_speed_10m,wind_direction_10m,wind_gusts_10m&hourly=&daily=&location_mode=csv_coordinates&timeformat=unixtime&forecast_days=3
val url = "https://api.open-meteo.com/v1/forecast?latitude=${gpsCoordinates.lat}&longitude=${gpsCoordinates.lon}&current=weather_code,temperature_2m,relative_humidity_2m,apparent_temperature,precipitation,cloud_cover,wind_speed_10m,wind_direction_10m,wind_gusts_10m,surface_pressure&timeformat=unixtime&wind_speed_unit=${settings.windUnit.id}&precipitation_unit=${settings.precipitationUnit.id}"
Log.d(KarooWinddirExtension.TAG, "Http request to ${url}...")
@ -143,7 +143,7 @@ suspend fun KarooSystemService.makeOpenMeteoHttpRequest(gpsCoordinates: GpsCoord
}
fun KarooSystemService.getHeadingFlow(): Flow<Int> {
// return flowOf(2)
return flowOf(2)
return streamDataFlow(DataType.Type.HEADING)
.mapNotNull { (it as? StreamState.Streaming)?.dataPoint?.singleValue }
@ -153,7 +153,7 @@ fun KarooSystemService.getHeadingFlow(): Flow<Int> {
@OptIn(FlowPreview::class)
fun KarooSystemService.getGpsCoordinateFlow(): Flow<GpsCoordinates> {
// 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 }

View File

@ -9,7 +9,8 @@ import de.timklge.karoowinddir.datatypes.SurfacePressureDataType
import de.timklge.karoowinddir.datatypes.WindDirectionDataType
import de.timklge.karoowinddir.datatypes.WindGustsDataType
import de.timklge.karoowinddir.datatypes.WindSpeedDataType
import de.timklge.karoowinddir.datatypes.WinddirDataType
import de.timklge.karoowinddir.datatypes.RelativeWindDirectionDataType
import de.timklge.karoowinddir.datatypes.WeatherDataType
import de.timklge.karoowinddir.screens.WinddirStats
import io.hammerhead.karooext.KarooSystemService
import io.hammerhead.karooext.extension.KarooExtension
@ -39,7 +40,8 @@ class KarooWinddirExtension : KarooExtension("karoo-winddir", "1.0.0-beta1") {
override val types by lazy {
listOf(
WinddirDataType(karooSystem, applicationContext),
RelativeWindDirectionDataType(karooSystem, applicationContext),
WeatherDataType(karooSystem, applicationContext),
RelativeHumidityDataType(applicationContext),
CloudCoverDataType(applicationContext),
WindSpeedDataType(applicationContext),

View File

@ -13,9 +13,29 @@ data class OpenMeteoData(
@SerialName("surface_pressure") val surfacePressure: Double,
@SerialName("wind_speed_10m") val windSpeed: Double,
@SerialName("wind_direction_10m") val windDirection: Double,
@SerialName("wind_gusts_10m") val windGusts: Double
@SerialName("wind_gusts_10m") val windGusts: Double,
@SerialName("weather_code") val weatherCode: Int,
)
enum class WeatherInterpretation {
CLEAR, CLOUDY, RAINY, SNOWY, DRIZZLE, THUNDERSTORM, UNKNOWN;
companion object {
// WMO weather interpretation codes (WW)
fun fromWeatherCode(code: Int): WeatherInterpretation {
return when(code){
0 -> CLEAR
1, 2, 3 -> CLOUDY
45, 48, 61, 63, 65, 66, 67, 80, 81, 82 -> RAINY
71, 73, 75, 77, 85, 86 -> SNOWY
51, 53, 55, 56, 57 -> DRIZZLE
95, 96, 99 -> THUNDERSTORM
else -> UNKNOWN
}
}
}
}
@Serializable
data class OpenMeteoCurrentWeatherResponse(
val current: OpenMeteoData,

View File

@ -0,0 +1,39 @@
package de.timklge.karoowinddir.datatypes
import android.content.Context
import android.util.Log
import de.timklge.karoowinddir.KarooWinddirExtension
import de.timklge.karoowinddir.OpenMeteoCurrentWeatherResponse
import de.timklge.karoowinddir.streamCurrentWeatherData
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.launch
abstract class BaseDataType(
private val applicationContext: Context,
dataTypeId: String
) : DataTypeImpl("karoo-winddir", dataTypeId) {
abstract fun getValue(data: OpenMeteoCurrentWeatherResponse): Double
override fun startStream(emitter: Emitter<StreamState>) {
Log.d(KarooWinddirExtension.TAG, "start $dataTypeId stream")
val job = CoroutineScope(Dispatchers.IO).launch {
val currentWeatherData = applicationContext.streamCurrentWeatherData()
currentWeatherData.collect { data ->
val value = getValue(data)
Log.d(KarooWinddirExtension.TAG, "$dataTypeId: $value")
emitter.onNext(StreamState.Streaming(DataPoint(dataTypeId, mapOf(DataType.Field.SINGLE to value))))
}
}
emitter.setCancellable {
Log.d(KarooWinddirExtension.TAG, "stop $dataTypeId stream")
job.cancel()
}
}
}

View File

@ -3,7 +3,7 @@ package de.timklge.karoowinddir.datatypes
import android.content.Context
import de.timklge.karoowinddir.OpenMeteoCurrentWeatherResponse
class CloudCoverDataType(context: Context) : WeatherDataType(context, "cloudCover"){
class CloudCoverDataType(context: Context) : BaseDataType(context, "cloudCover"){
override fun getValue(data: OpenMeteoCurrentWeatherResponse): Double {
return data.current.cloudCover.toDouble()
}

View File

@ -3,7 +3,7 @@ package de.timklge.karoowinddir.datatypes
import android.content.Context
import de.timklge.karoowinddir.OpenMeteoCurrentWeatherResponse
class PrecipitationDataType(context: Context) : WeatherDataType(context, "precipitation"){
class PrecipitationDataType(context: Context) : BaseDataType(context, "precipitation"){
override fun getValue(data: OpenMeteoCurrentWeatherResponse): Double {
return data.current.precipitation
}

View File

@ -3,7 +3,7 @@ package de.timklge.karoowinddir.datatypes
import android.content.Context
import de.timklge.karoowinddir.OpenMeteoCurrentWeatherResponse
class RelativeHumidityDataType(context: Context) : WeatherDataType(context, "relativeHumidity"){
class RelativeHumidityDataType(context: Context) : BaseDataType(context, "relativeHumidity"){
override fun getValue(data: OpenMeteoCurrentWeatherResponse): Double {
return data.current.relativeHumidity.toDouble()
}

View File

@ -1,7 +1,7 @@
package de.timklge.karoowinddir.datatypes
import android.util.Log
import android.content.Context
import android.util.Log
import androidx.compose.ui.unit.DpSize
import androidx.glance.appwidget.ExperimentalGlanceRemoteViewsApi
import androidx.glance.appwidget.GlanceRemoteViews
@ -23,7 +23,6 @@ import io.hammerhead.karooext.models.UpdateGraphicConfig
import io.hammerhead.karooext.models.ViewConfig
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ObsoleteCoroutinesApi
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
@ -31,10 +30,11 @@ import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.launch
import kotlin.math.abs
import kotlin.math.cos
import kotlin.math.roundToInt
@OptIn(ExperimentalGlanceRemoteViewsApi::class)
class WinddirDataType(
class RelativeWindDirectionDataType(
private val karooSystem: KarooSystemService,
private val applicationContext: Context
) : DataTypeImpl("karoo-winddir", "winddir") {
@ -59,7 +59,6 @@ class WinddirDataType(
}
override fun startStream(emitter: Emitter<StreamState>) {
Log.d(KarooWinddirExtension.TAG, "start winddir stream")
val job = CoroutineScope(Dispatchers.IO).launch {
val currentWeatherData = applicationContext.streamCurrentWeatherData()
@ -72,19 +71,17 @@ class WinddirDataType(
val windBearing = data.current.windDirection + 180
val diff = (signedAngleDifference(bearing, windBearing) + 360) % 360
Log.d(KarooWinddirExtension.TAG, "wind bearing: $bearing vs $windBearing => $diff")
Log.d(KarooWinddirExtension.TAG, "Wind bearing: $bearing vs $windBearing => $diff")
emitter.onNext(StreamState.Streaming(DataPoint(dataTypeId, mapOf(DataType.Field.SINGLE to diff))))
}
}
emitter.setCancellable {
Log.d(KarooWinddirExtension.TAG, "stop winddir stream")
job.cancel()
}
}
@OptIn(ObsoleteCoroutinesApi::class)
override fun startView(context: Context, config: ViewConfig, emitter: ViewEmitter) {
Log.d(KarooWinddirExtension.TAG, "Starting winddir view with $emitter")
Log.d(KarooWinddirExtension.TAG, "Starting relative wind direction view with $emitter")
val configJob = CoroutineScope(Dispatchers.IO).launch {
emitter.onNext(UpdateGraphicConfig(showHeader = false))
awaitCancellation()
@ -98,15 +95,19 @@ class WinddirDataType(
.combine(context.streamCurrentWeatherData()) { value, data -> value to data }
.combine(context.streamSettings()) { (value, data), settings -> StreamData(value, data, settings) }
.onCompletion {
// Clear view on completion
val result = glance.compose(context, DpSize.Unspecified) { }
emitter.updateView(result.remoteViews)
}
.collect { streamData ->
Log.d(KarooWinddirExtension.TAG, "Updating view")
val windSpeedText = if(streamData.settings.showWindspeedOverlay) "${streamData.data.current.windSpeed.roundToInt()}" else null
Log.d(KarooWinddirExtension.TAG, "Updating relative wind direction view")
val windSpeed = streamData.data.current.windSpeed
val windDirection = streamData.value
val headwindSpeed = cos( (windDirection + 180) * Math.PI / 180.0) * windSpeed
val windSpeedText = if(streamData.settings.showWindspeedOverlay) "${headwindSpeed.roundToInt()}" else null
val result = glance.compose(context, DpSize.Unspecified) {
RelativeWindDirection(streamData.value.roundToInt(), config.textSize, windSpeedText)
RelativeWindDirection(windDirection.roundToInt(), config.textSize, windSpeedText)
}
emitter.updateView(result.remoteViews)

View File

@ -3,7 +3,7 @@ package de.timklge.karoowinddir.datatypes
import android.content.Context
import de.timklge.karoowinddir.OpenMeteoCurrentWeatherResponse
class SurfacePressureDataType(context: Context) : WeatherDataType(context, "surfacePressure"){
class SurfacePressureDataType(context: Context) : BaseDataType(context, "surfacePressure"){
override fun getValue(data: OpenMeteoCurrentWeatherResponse): Double {
return data.current.surfacePressure
}

View File

@ -2,38 +2,89 @@ package de.timklge.karoowinddir.datatypes
import android.content.Context
import android.util.Log
import androidx.compose.ui.unit.DpSize
import androidx.glance.appwidget.ExperimentalGlanceRemoteViewsApi
import androidx.glance.appwidget.GlanceRemoteViews
import de.timklge.karoowinddir.KarooWinddirExtension
import de.timklge.karoowinddir.OpenMeteoCurrentWeatherResponse
import de.timklge.karoowinddir.WeatherInterpretation
import de.timklge.karoowinddir.getHeadingFlow
import de.timklge.karoowinddir.screens.WinddirSettings
import de.timklge.karoowinddir.streamCurrentWeatherData
import de.timklge.karoowinddir.streamSettings
import io.hammerhead.karooext.KarooSystemService
import io.hammerhead.karooext.extension.DataTypeImpl
import io.hammerhead.karooext.internal.Emitter
import io.hammerhead.karooext.internal.ViewEmitter
import io.hammerhead.karooext.models.DataPoint
import io.hammerhead.karooext.models.DataType
import io.hammerhead.karooext.models.StreamState
import io.hammerhead.karooext.models.UpdateGraphicConfig
import io.hammerhead.karooext.models.ViewConfig
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.launch
import kotlin.math.roundToInt
abstract class WeatherDataType(
private val applicationContext: Context,
dataTypeId: String
) : DataTypeImpl("karoo-winddir", dataTypeId) {
abstract fun getValue(data: OpenMeteoCurrentWeatherResponse): Double
@OptIn(ExperimentalGlanceRemoteViewsApi::class)
class WeatherDataType(
private val karooSystem: KarooSystemService,
private val applicationContext: Context
) : DataTypeImpl("karoo-winddir", "weather") {
private val glance = GlanceRemoteViews()
// FIXME: Remove. Currently, the data field will permanently show "no sensor" if no data stream is provided
override fun startStream(emitter: Emitter<StreamState>) {
Log.d(KarooWinddirExtension.TAG, "start $dataTypeId stream")
val job = CoroutineScope(Dispatchers.IO).launch {
val currentWeatherData = applicationContext.streamCurrentWeatherData()
currentWeatherData.collect { data ->
val value = getValue(data)
Log.d(KarooWinddirExtension.TAG, "$dataTypeId: $value")
emitter.onNext(StreamState.Streaming(DataPoint(dataTypeId, mapOf(DataType.Field.SINGLE to value))))
}
currentWeatherData
.collect { data ->
Log.d(KarooWinddirExtension.TAG, "Wind code: ${data.current.weatherCode}")
emitter.onNext(StreamState.Streaming(DataPoint(dataTypeId, mapOf(DataType.Field.SINGLE to data.current.weatherCode.toDouble()))))
}
}
emitter.setCancellable {
Log.d(KarooWinddirExtension.TAG, "stop $dataTypeId stream")
job.cancel()
}
}
override fun startView(context: Context, config: ViewConfig, emitter: ViewEmitter) {
Log.d(KarooWinddirExtension.TAG, "Starting weather view with $emitter")
val configJob = CoroutineScope(Dispatchers.IO).launch {
emitter.onNext(UpdateGraphicConfig(showHeader = false))
awaitCancellation()
}
data class StreamData(val data: OpenMeteoCurrentWeatherResponse, val settings: WinddirSettings)
val viewJob = CoroutineScope(Dispatchers.IO).launch {
context.streamCurrentWeatherData()
.combine(context.streamSettings()) { data, settings -> StreamData(data, settings) }
.onCompletion {
// Clear view on completion
val result = glance.compose(context, DpSize.Unspecified) { }
emitter.updateView(result.remoteViews)
}
.collect { (data, settings) ->
Log.d(KarooWinddirExtension.TAG, "Updating weather view")
val interpretation = WeatherInterpretation.fromWeatherCode(data.current.weatherCode)
val result = glance.compose(context, DpSize.Unspecified) {
Weather(interpretation, data.current.windDirection.roundToInt(), data.current.windSpeed.roundToInt(), data.current.windGusts.roundToInt())
}
emitter.updateView(result.remoteViews)
}
}
emitter.setCancellable {
Log.d(KarooWinddirExtension.TAG, "Stopping winddir view with $emitter")
configJob.cancel()
viewJob.cancel()
}
}
}

View File

@ -0,0 +1,75 @@
package de.timklge.karoowinddir.datatypes
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.TextUnitType
import androidx.compose.ui.unit.dp
import androidx.glance.ColorFilter
import androidx.glance.GlanceModifier
import androidx.glance.Image
import androidx.glance.ImageProvider
import androidx.glance.color.ColorProvider
import androidx.glance.layout.Alignment
import androidx.glance.layout.Column
import androidx.glance.layout.ContentScale
import androidx.glance.layout.Row
import androidx.glance.layout.fillMaxSize
import androidx.glance.layout.fillMaxWidth
import androidx.glance.layout.height
import androidx.glance.layout.padding
import androidx.glance.layout.width
import androidx.glance.preview.ExperimentalGlancePreviewApi
import androidx.glance.preview.Preview
import androidx.glance.text.FontFamily
import androidx.glance.text.Text
import androidx.glance.text.TextStyle
import de.timklge.karoowinddir.R
import de.timklge.karoowinddir.WeatherInterpretation
import de.timklge.karoowinddir.screens.WinddirSettings
fun getWeatherIcon(interpretation: WeatherInterpretation): Int {
return when (interpretation){
WeatherInterpretation.CLEAR -> R.drawable.bx_clear
WeatherInterpretation.CLOUDY -> R.drawable.bx_cloud
WeatherInterpretation.RAINY -> R.drawable.bx_cloud_rain
WeatherInterpretation.SNOWY -> R.drawable.bx_cloud_snow
WeatherInterpretation.DRIZZLE -> R.drawable.bx_cloud_drizzle
WeatherInterpretation.THUNDERSTORM -> R.drawable.bx_cloud_lightning
WeatherInterpretation.UNKNOWN -> R.drawable.question_mark_regular_240
}
}
@OptIn(ExperimentalGlancePreviewApi::class)
@Preview(widthDp = 200, heightDp = 150)
@Composable
fun Weather(current: WeatherInterpretation, windBearing: Int, windSpeed: Int, windGusts: Int) {
Column(modifier = GlanceModifier.fillMaxSize(), horizontalAlignment = Alignment.End) {
Row(modifier = GlanceModifier.defaultWeight(), horizontalAlignment = Alignment.End) {
val imageW = 70
val imageH = (imageW * (280.0 / 400)).toInt()
Image(
modifier = GlanceModifier.height(imageH.dp).width(imageW.dp),
provider = ImageProvider(getWeatherIcon(current)),
contentDescription = "Current weather information",
contentScale = ContentScale.FillBounds,
colorFilter = ColorFilter.tint(ColorProvider(Color.Black, Color.White))
)
}
Row(horizontalAlignment = Alignment.CenterHorizontally, verticalAlignment = Alignment.CenterVertically) {
Image(
modifier = GlanceModifier.height(20.dp).width(12.dp),
provider = ImageProvider(getArrowResourceByBearing(windBearing)),
contentDescription = "Current wind direction",
contentScale = ContentScale.Fit,
colorFilter = ColorFilter.tint(ColorProvider(Color.Black, Color.White))
)
Text(
text = "$windSpeed,$windGusts",
style = TextStyle(color = ColorProvider(Color.Black, Color.White), fontFamily = FontFamily.Monospace, fontSize = TextUnit(18f, TextUnitType.Sp))
)
}
}
}

View File

@ -3,22 +3,17 @@ package de.timklge.karoowinddir.datatypes
import android.content.Context
import android.util.Log
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.TextUnitType
import androidx.compose.ui.unit.dp
import androidx.glance.GlanceModifier
import androidx.glance.appwidget.ExperimentalGlanceRemoteViewsApi
import androidx.glance.appwidget.GlanceRemoteViews
import androidx.glance.background
import androidx.glance.color.ColorProvider
import androidx.glance.layout.Alignment
import androidx.glance.layout.Box
import androidx.glance.layout.fillMaxSize
import androidx.glance.layout.padding
import androidx.glance.text.FontFamily
import androidx.glance.text.FontStyle
import androidx.glance.text.Text
import androidx.glance.text.TextStyle
import de.timklge.karoowinddir.KarooWinddirExtension
@ -36,7 +31,7 @@ import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.launch
import kotlin.math.roundToInt
class WindDirectionDataType(val karooSystem: KarooSystemService, context: Context) : WeatherDataType(context, "windDirection"){
class WindDirectionDataType(val karooSystem: KarooSystemService, context: Context) : BaseDataType(context, "windDirection"){
@OptIn(ExperimentalGlanceRemoteViewsApi::class)
private val glance = GlanceRemoteViews()

View File

@ -3,7 +3,7 @@ package de.timklge.karoowinddir.datatypes
import android.content.Context
import de.timklge.karoowinddir.OpenMeteoCurrentWeatherResponse
class WindGustsDataType(context: Context) : WeatherDataType(context, "windGusts"){
class WindGustsDataType(context: Context) : BaseDataType(context, "windGusts"){
override fun getValue(data: OpenMeteoCurrentWeatherResponse): Double {
return data.current.windGusts
}

View File

@ -3,7 +3,7 @@ package de.timklge.karoowinddir.datatypes
import android.content.Context
import de.timklge.karoowinddir.OpenMeteoCurrentWeatherResponse
class WindSpeedDataType(context: Context) : WeatherDataType(context, "windSpeed"){
class WindSpeedDataType(context: Context) : BaseDataType(context, "windSpeed"){
override fun getValue(data: OpenMeteoCurrentWeatherResponse): Double {
return data.current.windSpeed
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

View File

@ -1,5 +1,5 @@
<resources>
<string name="app_name">Winddir</string>
<string name="app_name">WindDir</string>
<string name="extension_name">winddir</string>
<string name="winddir">Wind direction</string>
<string name="winddir_description">Current wind direction relative to the riding direction</string>
@ -17,4 +17,6 @@
<string name="precipitation_description">Current precipitation (rainfall / snowfall)</string>
<string name="surfacePressure">Surface pressure</string>
<string name="surfacePressure_description">Atmospheric pressure at surface in configured unit</string>
<string name="weather">Weather</string>
<string name="weather_description">Current weather conditions</string>
</resources>

View File

@ -11,6 +11,13 @@
icon="@drawable/ic_launcher"
typeId="winddir" />
<DataType
description="@string/weather_description"
displayName="@string/weather"
graphical="true"
icon="@drawable/ic_launcher"
typeId="weather" />
<DataType
description="@string/relativeHumidity_description"
displayName="@string/relativeHumidity"