From 2a9a01e40b569aebbdccbb350205fa419cd3b886 Mon Sep 17 00:00:00 2001 From: Daniele Gobbetti Date: Mon, 6 May 2024 11:32:16 +0200 Subject: [PATCH] Garmin: improve weather information Add Aqi Field Definition and field to today weather and daily forecast, as both are available in WeatherSpec. Add Feels like temperature to hourly forecast but populate with the forecasted temperature as the field is not available in Weatherspec for hourly. Use temperature Field Definition for dew point and add it to today's weather. Fields dew point and air quality could have been removed from the hourly weather definition but are kept in to test compatibility of these changes with watches. --- .../service/devices/garmin/GarminSupport.java | 15 ++++- .../garmin/fit/FieldDefinitionFactory.java | 4 ++ .../devices/garmin/fit/GlobalFITMessage.java | 4 +- .../garmin/fit/PredefinedLocalMessage.java | 6 +- .../FieldDefinitionWeatherAqi.java | 56 +++++++++++++++++++ 5 files changed, 78 insertions(+), 7 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionWeatherAqi.java diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminSupport.java index 059521941..834108f2a 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminSupport.java @@ -369,6 +369,10 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni today.setFieldByName("relative_humidity", weather.currentHumidity); today.setFieldByName("observed_location_lat", weather.latitude); today.setFieldByName("observed_location_long", weather.longitude); + today.setFieldByName("dew_point", weather.dewPoint); + if (null != weather.airQuality) { + today.setFieldByName("air_quality", weather.airQuality.aqi); + } today.setFieldByName("location", weather.location); weatherData.add(today); @@ -380,13 +384,14 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni weatherHourlyForecast.setFieldByName("timestamp", hourly.timestamp); weatherHourlyForecast.setFieldByName("temperature", hourly.temp); weatherHourlyForecast.setFieldByName("condition", hourly.conditionCode); + weatherHourlyForecast.setFieldByName("temperature_feels_like", hourly.temp); //TODO: switch to actual feels like field once Hourly contains this information weatherHourlyForecast.setFieldByName("wind_direction", hourly.windDirection); weatherHourlyForecast.setFieldByName("wind_speed", Math.round(hourly.windSpeed)); weatherHourlyForecast.setFieldByName("precipitation_probability", hourly.precipProbability); weatherHourlyForecast.setFieldByName("relative_humidity", hourly.humidity); -// weatherHourlyForecast.setFieldByName("dew_point", 0); // dew_point sint8 +// weatherHourlyForecast.setFieldByName("dew_point", 0); // TODO: add once Hourly contains this information weatherHourlyForecast.setFieldByName("uv_index", hourly.uvIndex); -// weatherHourlyForecast.setFieldByName("air_quality", 0); // air_quality enum +// weatherHourlyForecast.setFieldByName("air_quality", 0); // TODO: add once Hourly contains this information weatherData.add(weatherHourlyForecast); } } @@ -399,6 +404,9 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni todayDailyForecast.setFieldByName("condition", weather.currentConditionCode); todayDailyForecast.setFieldByName("precipitation_probability", weather.precipProbability); todayDailyForecast.setFieldByName("day_of_week", weather.timestamp); + if (null != weather.airQuality) { + todayDailyForecast.setFieldByName("air_quality", weather.airQuality.aqi); + } weatherData.add(todayDailyForecast); @@ -413,6 +421,9 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni weatherDailyForecast.setFieldByName("high_temperature", daily.maxTemp); weatherDailyForecast.setFieldByName("condition", daily.conditionCode); weatherDailyForecast.setFieldByName("precipitation_probability", daily.precipProbability); + if (null != daily.airQuality) { + weatherDailyForecast.setFieldByName("air_quality", daily.airQuality.aqi); + } weatherDailyForecast.setFieldByName("day_of_week", ts); weatherData.add(weatherDailyForecast); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FieldDefinitionFactory.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FieldDefinitionFactory.java index c9635d04e..497d3c637 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FieldDefinitionFactory.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FieldDefinitionFactory.java @@ -11,6 +11,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefi import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionSleepStage; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionTemperature; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionTimestamp; +import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionWeatherAqi; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionWeatherCondition; public class FieldDefinitionFactory { @@ -41,6 +42,8 @@ public class FieldDefinitionFactory { return new FieldDefinitionLanguage(localNumber, size, baseType, name); case SLEEP_STAGE: return new FieldDefinitionSleepStage(localNumber, size, baseType, name); + case WEATHER_AQI: + return new FieldDefinitionWeatherAqi(localNumber, size, baseType, name); default: return new FieldDefinition(localNumber, size, baseType, name); } @@ -58,5 +61,6 @@ public class FieldDefinitionFactory { WEATHER_CONDITION, LANGUAGE, SLEEP_STAGE, + WEATHER_AQI, } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/GlobalFITMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/GlobalFITMessage.java index 631e2441e..ce021ccf0 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/GlobalFITMessage.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/GlobalFITMessage.java @@ -183,9 +183,9 @@ public class GlobalFITMessage { new FieldDefinitionPrimitive(12, BaseType.ENUM, "day_of_week", FieldDefinitionFactory.FIELD.DAY_OF_WEEK), new FieldDefinitionPrimitive(13, BaseType.SINT8, "high_temperature", FieldDefinitionFactory.FIELD.TEMPERATURE), new FieldDefinitionPrimitive(14, BaseType.SINT8, "low_temperature", FieldDefinitionFactory.FIELD.TEMPERATURE), - new FieldDefinitionPrimitive(15, BaseType.SINT8, "dew_point"), + new FieldDefinitionPrimitive(15, BaseType.SINT8, "dew_point", FieldDefinitionFactory.FIELD.TEMPERATURE), new FieldDefinitionPrimitive(16, BaseType.FLOAT32, "uv_index"), - new FieldDefinitionPrimitive(17, BaseType.ENUM, "air_quality"), + new FieldDefinitionPrimitive(17, BaseType.ENUM, "air_quality", FieldDefinitionFactory.FIELD.WEATHER_AQI), new FieldDefinitionPrimitive(253, BaseType.UINT32, "timestamp", FieldDefinitionFactory.FIELD.TIMESTAMP) )); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/PredefinedLocalMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/PredefinedLocalMessage.java index 1e8d9309b..a1bfc90fd 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/PredefinedLocalMessage.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/PredefinedLocalMessage.java @@ -7,13 +7,13 @@ import java.util.List; public enum PredefinedLocalMessage { TODAY_WEATHER_CONDITIONS(6, GlobalFITMessage.WEATHER, - new int[]{0, 253, 9, 1, 14, 13, 2, 3, 5, 4, 6, 7, 10, 11, 8} + new int[]{0, 253, 9, 1, 14, 13, 2, 3, 5, 4, 6, 7, 10, 11, 17, 15, 8} ), HOURLY_WEATHER_FORECAST(9, GlobalFITMessage.WEATHER, - new int[]{0, 253, 1, 2, 3, 4, 5, 7, 15, 16, 17} + new int[]{0, 253, 1, 2, 3, 4, 5, 6, 7, 15, 16, 17} ), DAILY_WEATHER_FORECAST(10, GlobalFITMessage.WEATHER, - new int[]{0, 253, 14, 13, 2, 5, 12} + new int[]{0, 253, 14, 13, 2, 5, 12, 17} ); private final int type; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionWeatherAqi.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionWeatherAqi.java new file mode 100644 index 000000000..fa25c2170 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/fieldDefinitions/FieldDefinitionWeatherAqi.java @@ -0,0 +1,56 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions; + +import java.nio.ByteBuffer; + +import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FieldDefinition; +import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes.BaseType; + +public class FieldDefinitionWeatherAqi extends FieldDefinition { + + public FieldDefinitionWeatherAqi(int localNumber, int size, BaseType baseType, String name) { + super(localNumber, size, baseType, name, 1, 0); + } + + @Override + public Object decode(ByteBuffer byteBuffer) { + int idx = (int) baseType.decode(byteBuffer, scale, offset); + return AQI_LEVELS.values()[idx]; + } + + @Override + public void encode(ByteBuffer byteBuffer, Object o) { + if (o instanceof AQI_LEVELS) { + baseType.encode(byteBuffer, ((AQI_LEVELS) o).ordinal(), scale, offset); + return; + } + baseType.encode(byteBuffer, aqiAbsoluteValueToIndex((int) o), scale, offset); + } + + private int aqiAbsoluteValueToIndex(int rawValue) { //see https://github.com/breezy-weather/breezy-weather/blob/main/app/src/main/java/org/breezyweather/domain/weather/index/PollutantIndex.kt#L38 + if (rawValue == -1) { + return rawValue; //invalid + } + if (rawValue < 20) { + return AQI_LEVELS.GOOD.ordinal(); + } else if (rawValue < 50) { + return AQI_LEVELS.MODERATE.ordinal(); + } else if (rawValue < 100) { + return AQI_LEVELS.UNHEALTHY_SENSITIVE.ordinal(); + } else if (rawValue < 150) { + return AQI_LEVELS.UNHEALTHY.ordinal(); + } else if (rawValue < 250) { + return AQI_LEVELS.VERY_UNHEALTHY.ordinal(); + } else { + return AQI_LEVELS.HAZARDOUS.ordinal(); + } + } + + public enum AQI_LEVELS { + GOOD, + MODERATE, + UNHEALTHY_SENSITIVE, + UNHEALTHY, + VERY_UNHEALTHY, + HAZARDOUS, + } +}