/* Copyright (C) 2022-2024 Andreas Shimokawa, FintasticMan, Taavi Eomäe
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see
* Even if they're absolutely not mutually exclusive. *
* So, this function only returns PrecipitationType */ public static final PrecipitationType mapOpenWeatherConditionToPineTimePrecipitation(int openWeatherMapCondition) { switch (openWeatherMapCondition) { // Group 2xx: Thunderstorm case 200: // Thunderstorm with light rain case 201: // Thunderstorm with rain case 202: // Thunderstorm with heavy rain case 210: // Light thunderstorm case 211: // Thunderstorm case 230: // Thunderstorm with light drizzle case 231: // Thunderstorm with drizzle case 232: // Thunderstorm with heavy drizzle case 212: // Heavy thunderstorm case 221: // Ragged thunderstorm return PrecipitationType.Rain; // Group 3xx: Drizzle case 300: // Light intensity drizzle case 301: // Drizzle case 302: // Heavy intensity drizzle case 310: // Light intensity drizzle rain case 311: // Drizzle rain case 312: // Heavy intensity drizzle rain case 313: // Shower rain and drizzle case 314: // Heavy shower rain and drizzle case 321: // Shower drizzle return PrecipitationType.Drizzle; // Group 5xx: Rain case 500: // Light rain case 501: // Moderate rain case 502: // Heavy intensity rain case 503: // Very heavy rain case 504: // Extreme rain return PrecipitationType.Rain; case 511: // Freezing rain return PrecipitationType.FreezingRain; case 520: // Light intensity shower rain case 521: // Shower rain case 522: // Heavy intensity shower rain case 531: // Ragged shower rain return PrecipitationType.Rain; // Group 6xx: Snow case 600: // Light snow case 601: // Snow case 620: // Light shower snow case 602: // Heavy snow return PrecipitationType.Snow; case 611: // Sleet case 612: // Shower sleet return PrecipitationType.Sleet; case 621: // Shower snow case 622: // Heavy shower snow return PrecipitationType.Snow; case 615: // Light rain and snow case 616: // Rain and snow return PrecipitationType.Sleet; // Group 7xx: Atmosphere case 701: // Mist case 711: // Smoke case 721: // Haze case 731: // Sandcase/dust whirls case 741: // Fog case 751: // Sand case 761: // Dust case 762: // Volcanic ash return PrecipitationType.Ash; case 771: // Squalls case 781: // Tornado case 900: // Tornado // Group 800: Clear case 800: // Clear sky return PrecipitationType.None; // Group 80x: Clouds case 801: // Few clouds case 802: // Scattered clouds case 803: // Broken clouds case 804: // Overcast clouds // Group 90x: Extreme case 901: // Tropical storm case 903: // Cold case 904: // Hot case 905: // Windy case 906: // Hail return PrecipitationType.Hail; // Group 9xx: Additional case 951: // Calm case 952: // Light breeze case 953: // Gentle breeze case 954: // Moderate breeze case 955: // Fresh breeze case 956: // Strong breeze case 957: // High windcase near gale case 958: // Gale case 959: // Severe gale case 960: // Storm case 961: // Violent storm case 902: // Hurricane case 962: // Hurricane default: return PrecipitationType.Length; } } ; /** * OpenWeather's condition codes are a bit annoying, * they've mixed obscuration with other weather events. * *
* Even if they're absolutely not mutually exclusive. *
* So, this function only returns ObscurationType */ public static final ObscurationType mapOpenWeatherConditionToPineTimeObscuration(int openWeatherMapCondition) { switch (openWeatherMapCondition) { // Group 2xx: Thunderstorm case 200: // Thunderstorm with light rain case 201: // Thunderstorm with rain case 202: // Thunderstorm with heavy rain case 210: // Light thunderstorm case 211: // Thunderstorm case 230: // Thunderstorm with light drizzle case 231: // Thunderstorm with drizzle case 232: // Thunderstorm with heavy drizzle case 212: // Heavy thunderstorm case 221: // Ragged thunderstorm return ObscurationType.Precipitation; // Group 3xx: Drizzle case 300: // Light intensity drizzle case 301: // Drizzle case 302: // Heavy intensity drizzle case 310: // Light intensity drizzle rain case 311: // Drizzle rain case 312: // Heavy intensity drizzle rain case 313: // Shower rain and drizzle case 314: // Heavy shower rain and drizzle case 321: // Shower drizzle return ObscurationType.Precipitation; // Group 5xx: Rain case 500: // Light rain case 501: // Moderate rain case 502: // Heavy intensity rain case 503: // Very heavy rain case 504: // Extreme rain case 511: // Freezing rain case 520: // Light intensity shower rain case 521: // Shower rain case 522: // Heavy intensity shower rain case 531: // Ragged shower rain return ObscurationType.Precipitation; // Group 6xx: Snow case 600: // Light snow case 601: // Snow case 620: // Light shower snow case 602: // Heavy snow case 611: // Sleet case 612: // Shower sleet case 621: // Shower snow case 622: // Heavy shower snow case 615: // Light rain and snow case 616: // Rain and snow return ObscurationType.Precipitation; // Group 7xx: Atmosphere case 701: // Mist return ObscurationType.Mist; case 711: // Smoke return ObscurationType.Smoke; case 721: // Haze return ObscurationType.Haze; case 731: // Sandcase/dust whirls return ObscurationType.Sand; case 741: // Fog return ObscurationType.Fog; case 751: // Sand return ObscurationType.Sand; case 761: // Dust return ObscurationType.Dust; case 762: // Volcanic ash return ObscurationType.Ash; case 771: // Squalls return ObscurationType.Length; case 781: // Tornado case 900: // Tornado return ObscurationType.Precipitation; // Group 800: Clear case 800: // Clear sky return ObscurationType.Length; // Group 80x: Clouds case 801: // Few clouds case 802: // Scattered clouds case 803: // Broken clouds case 804: // Overcast clouds return ObscurationType.Length; // Group 90x: Extreme case 901: // Tropical storm return ObscurationType.Precipitation; case 903: // Cold case 904: // Hot return ObscurationType.Length; case 905: // Windy case 906: // Hail return ObscurationType.Precipitation; // Group 9xx: Additional case 951: // Calm case 952: // Light breeze case 953: // Gentle breeze case 954: // Moderate breeze case 955: // Fresh breeze case 956: // Strong breeze case 957: // High windcase near gale case 958: // Gale case 959: // Severe gale return ObscurationType.Length; case 960: // Storm case 961: // Violent storm return ObscurationType.Precipitation; case 902: // Hurricane return ObscurationType.Precipitation; case 962: // Hurricane return ObscurationType.Precipitation; default: return ObscurationType.Length; } } ; /** * OpenWeather's condition codes are a bit annoying, * they've mixed precipitation with other weather events. *
* Even if they're absolutely not mutually exclusive. * *
* So, this function only returns SpecialType */ public static final SpecialType mapOpenWeatherConditionToPineTimeSpecial(int openWeatherMapCondition) { switch (openWeatherMapCondition) { // Group 2xx: Thunderstorm case 200: // Thunderstorm with light rain case 201: // Thunderstorm with rain case 202: // Thunderstorm with heavy rain case 210: // Light thunderstorm case 211: // Thunderstorm case 230: // Thunderstorm with light drizzle case 231: // Thunderstorm with drizzle case 232: // Thunderstorm with heavy drizzle case 212: // Heavy thunderstorm case 221: // Ragged thunderstorm // Group 3xx: Drizzle case 300: // Light intensity drizzle case 301: // Drizzle case 302: // Heavy intensity drizzle case 310: // Light intensity drizzle rain case 311: // Drizzle rain case 312: // Heavy intensity drizzle rain case 313: // Shower rain and drizzle case 314: // Heavy shower rain and drizzle case 321: // Shower drizzle // Group 5xx: Rain case 500: // Light rain case 501: // Moderate rain case 502: // Heavy intensity rain case 503: // Very heavy rain case 504: // Extreme rain case 511: // Freezing rain case 520: // Light intensity shower rain case 521: // Shower rain case 522: // Heavy intensity shower rain case 531: // Ragged shower rain // Group 6xx: Snow case 600: // Light snow case 601: // Snow case 620: // Light shower snow case 602: // Heavy snow case 611: // Sleet case 612: // Shower sleet case 621: // Shower snow case 622: // Heavy shower snow case 615: // Light rain and snow case 616: // Rain and snow // Group 7xx: Atmosphere case 701: // Mist case 711: // Smoke case 721: // Haze case 731: // Sandcase/dust whirls case 741: // Fog case 751: // Sand case 761: // Dust case 762: // Volcanic ash return SpecialType.Length; case 771: // Squalls return SpecialType.Squall; case 781: // Tornado case 900: // Tornado // Group 800: Clear case 800: // Clear sky // Group 80x: Clouds case 801: // Few clouds case 802: // Scattered clouds case 803: // Broken clouds case 804: // Overcast clouds // Group 90x: Extreme case 901: // Tropical storm case 903: // Cold return SpecialType.Length; case 904: // Hot return SpecialType.Fire; case 905: // Windy case 906: // Hail // Group 9xx: Additional case 951: // Calm case 952: // Light breeze case 953: // Gentle breeze case 954: // Moderate breeze case 955: // Fresh breeze case 956: // Strong breeze case 957: // High windcase near gale case 958: // Gale case 959: // Severe gale case 960: // Storm case 961: // Violent storm case 902: // Hurricane case 962: // Hurricane default: return SpecialType.Length; } } ; /** * OpenWeather's condition codes are a bit annoying, * they've mixed precipitation with other weather events. *
* Even if they're absolutely not mutually exclusive. * *
* So, this function only returns cloudyness 0-100% */ public static final int mapOpenWeatherConditionToCloudCover(int openWeatherMapCondition) { switch (openWeatherMapCondition) { // Group 2xx: Thunderstorm case 200: // Thunderstorm with light rain case 201: // Thunderstorm with rain case 202: // Thunderstorm with heavy rain case 210: // Light thunderstorm case 211: // Thunderstorm case 230: // Thunderstorm with light drizzle case 231: // Thunderstorm with drizzle case 232: // Thunderstorm with heavy drizzle case 212: // Heavy thunderstorm case 221: // Ragged thunderstorm return 100; // Group 3xx: Drizzle case 300: // Light intensity drizzle case 301: // Drizzle case 302: // Heavy intensity drizzle case 310: // Light intensity drizzle rain case 311: // Drizzle rain case 312: // Heavy intensity drizzle rain case 313: // Shower rain and drizzle case 314: // Heavy shower rain and drizzle case 321: // Shower drizzle return 75; // Group 5xx: Rain case 500: // Light rain case 501: // Moderate rain case 502: // Heavy intensity rain case 503: // Very heavy rain case 504: // Extreme rain case 511: // Freezing rain case 520: // Light intensity shower rain case 521: // Shower rain case 522: // Heavy intensity shower rain case 531: // Ragged shower rain return 75; // Group 6xx: Snow case 600: // Light snow case 601: // Snow case 620: // Light shower snow case 602: // Heavy snow case 611: // Sleet case 612: // Shower sleet case 621: // Shower snow case 622: // Heavy shower snow case 615: // Light rain and snow case 616: // Rain and snow return 75; // Group 7xx: Atmosphere case 701: // Mist case 711: // Smoke case 721: // Haze case 731: // Sandcase/dust whirls case 741: // Fog case 751: // Sand case 761: // Dust return -1; case 762: // Volcanic ash return 100; case 771: // Squalls case 781: // Tornado case 900: // Tornado // Group 800: Clouds & Clear case 800: // Clear sky return 0; case 801: // Few clouds return 25; case 802: // Scattered clouds return 50; case 803: // Broken clouds return 75; case 804: // Overcast clouds return 100; // Group 90x: Extreme case 901: // Tropical storm case 903: // Cold case 904: // Hot case 905: // Windy case 906: // Hail return 75; // Group 9xx: Additional case 951: // Calm case 952: // Light breeze case 953: // Gentle breeze case 954: // Moderate breeze case 955: // Fresh breeze case 956: // Strong breeze case 957: // High windcase near gale case 958: // Gale case 959: // Severe gale case 960: // Storm case 961: // Violent storm case 902: // Hurricane case 962: // Hurricane default: return -1; } } ; /** * List of weather condition codes used to determine display * https://openweathermap.org/weather-conditions */ public static final ConditionType mapOpenWeatherConditionToPineTimeCondition(int openWeatherMapCondition) { switch (openWeatherMapCondition) { // Group 2xx: Thunderstorm case 200: // Thunderstorm with light rain case 201: // Thunderstorm with rain case 202: // Thunderstorm with heavy rain case 210: // Light thunderstorm case 211: // Thunderstorm case 212: // Heavy thunderstorm case 221: // Ragged thunderstorm case 230: // Thunderstorm with light drizzle case 231: // Thunderstorm with drizzle case 232: // Thunderstorm with heavy drizzle return ConditionType.Thunderstorm; // Group 3xx: Drizzle case 300: // Light intensity drizzle case 301: // Drizzle case 302: // Heavy intensity drizzle case 310: // Light intensity drizzle rain case 311: // Drizzle rain case 312: // Heavy intensity drizzle rain case 313: // Shower rain and drizzle case 314: // Heavy shower rain and drizzle case 321: // Shower drizzle return ConditionType.CloudsAndRain; // Group 5xx: Rain case 500: // Light rain case 501: // Moderate rain case 502: // Heavy intensity rain case 503: // Very heavy rain case 504: // Extreme rain return ConditionType.Rain; case 511: // Freezing rain return ConditionType.Snow; case 520: // Light intensity shower rain case 521: // Shower rain case 522: // Heavy intensity shower rain case 531: // Ragged shower rain return ConditionType.CloudsAndRain; // Group 6xx: Snow case 600: // Light snow case 601: // Snow case 602: // Heavy snow case 611: // Sleet case 612: // Light shower sleet case 613: // Shower sleet case 615: // Light rain and snow case 616: // Rain and snow case 620: // Light shower snow case 621: // Shower snow case 622: // Heavy shower snow return ConditionType.Snow; // Group 7xx: Atmosphere case 701: // Mist case 711: // Smoke case 721: // Haze case 731: // Sandcase/dust whirls case 741: // Fog case 751: // Sand case 761: // Dust case 762: // Volcanic ash case 771: // Squalls case 781: // Tornado return ConditionType.Mist; // Group 800: Clear case 800: // Clear sky return ConditionType.ClearSky; // Group 80x: Clouds case 801: // Few clouds return ConditionType.FewClouds; case 802: // Scattered clouds return ConditionType.Clouds; case 803: // Broken clouds case 804: // Overcast clouds return ConditionType.HeavyClouds; default: return ConditionType.Length; } } ; /** * Visibility obscuration types */ public enum ObscurationType { /** * No obscuration */ None(0), /** * Water particles suspended in the air; low visibility; does not fall */ Fog(1), /** * Tiny, dry particles in the air; invisible to the eye; opalescent */ Haze(2), /** * Small fire-created particles suspended in the air */ Smoke(3), /** * Fine rock powder, from for example volcanoes */ Ash(4), /** * Fine particles of earth suspended in the air by the wind */ Dust(5), /** * Fine particles of sand suspended in the air by the wind */ Sand(6), /** * Water particles suspended in the air; low-ish visibility; temperature is near dewpoint */ Mist(7), /** * This is special in the sense that the thing falling down is doing the obscuration */ Precipitation(8), Length(9); public final int value; ObscurationType(int value) { this.value = value; } } /** * Types of precipitation */ public enum PrecipitationType { /** * No precipitation *
* Theoretically we could just _not_ send the event, but then * how do we differentiate between no precipitation and * no information about precipitation */ None(0), /** * Drops larger than a drizzle; also widely separated drizzle */ Rain(1), /** * Fairly uniform rain consisting of fine drops */ Drizzle(2), /** * Rain that freezes upon contact with objects and ground */ FreezingRain(3), /** * Rain + hail; ice pellets; small translucent frozen raindrops */ Sleet(4), /** * Larger ice pellets; falling separately or in irregular clumps */ Hail(5), /** * Hail with smaller grains of ice; mini-snowballs */ SmallHail(6), /** * Snow... */ Snow(7), /** * Frozen drizzle; very small snow crystals */ SnowGrains(8), /** * Needles; columns or plates of ice. Sometimes described as "diamond dust". In very cold regions */ IceCrystals(9), /** * It's raining down ash, e.g. from a volcano */ Ash(10), Length(11); public final int value; PrecipitationType(int value) { this.value = value; } } /** * These are special events that can "enhance" the "experience" of existing weather events */ public enum SpecialType { /** * Strong wind with a sudden onset that lasts at least a minute */ Squall(0), /** * Series of waves in a water body caused by the displacement of a large volume of water */ Tsunami(1), /** * Violent; rotating column of air */ Tornado(2), /** * Unplanned; unwanted; uncontrolled fire in an area */ Fire(3), /** * Thunder and/or lightning */ Thunder(4), Length(5); public final int value; SpecialType(int value) { this.value = value; } } /** * List of weather condition codes used to determine display */ public enum ConditionType { /** * Clear sky */ ClearSky(0), /** * Few clouds */ FewClouds(1), /** * Scattered clouds */ Clouds(2), /** * Broken/heavy clouds */ HeavyClouds(3), /** * Shower rain */ CloudsAndRain(4), /** * Rain */ Rain(5), /** * Thunderstorm */ Thunderstorm(6), /** * Snow */ Snow(7), /** * Mist */ Mist(8), Length(9); public final byte value; ConditionType(int value) { this.value = (byte) value; } } /** * These are used for weather timeline manipulation * that isn't just adding to the stack of weather events */ public enum ControlCodes { /** * How much is stored already */ GetLength(0), /** * This wipes the entire timeline */ DelTimeline(1), /** * There's a currently valid timeline event with the given type */ HasValidEvent(3), Length(4); public final int value; ControlCodes(int value) { this.value = value; } } /** * Events have types * then they're easier to parse after sending them over the air */ public enum EventType { /** * @see WeatherData.Obscuration */ Obscuration(0), /** * @see WeatherData.Precipitation */ Precipitation(1), /** * @see WeatherData.Wind */ Wind(2), /** * @see WeatherData.Temperature */ Temperature(3), /** * @see WeatherData.AirQuality */ AirQuality(4), /** * @see WeatherData.Special */ Special(5), /** * @see WeatherData.Pressure */ Pressure(6), /** * @see WeatherData.Location */ Location(7), /** * @see WeatherData.Clouds */ Clouds(8), /** * @see WeatherData.Humidity */ Humidity(9), Length(10); public final int value; EventType(int value) { this.value = value; } } ; /** * Valid event query */ static public class ValidEventQuery { ControlCodes code = ControlCodes.HasValidEvent; EventType eventType; } ; /** * The header used for further parsing */ static public class TimelineHeader { /** * UNIX timestamp */ long timestamp; /** * Time in seconds until the event expires *
* 32 bits ought to be enough for everyone *
* If there's a newer event of the same type then it overrides this one, even if it hasn't expired */ int expires; /** * What type of weather-related event */ EventType eventType; } /** * Specifies how cloudiness is stored */ static public class Clouds extends TimelineHeader { /** * Cloud coverage in percentage, 0-100% */ byte amount; } /** * Specifies how obscuration is stored */ static public class Obscuration extends TimelineHeader { /** * Type */ ObscurationType type; /** * Visibility distance in meters (0-65535) */ int amount; } /** * Specifies how precipitation is stored */ static public class Precipitation extends TimelineHeader { /** * Type */ PrecipitationType type; /** * How much is it going to rain? In millimeters (0-255) */ int amount; } /** * How wind speed is stored *
* In order to represent bursts of wind instead of constant wind, * you have minimum and maximum speeds. *
* As direction can fluctuate wildly and some watchfaces might wish to display it nicely, * we're following the aerospace industry weather report option of specifying a range. */ static public class Wind extends TimelineHeader { /** * Meters per second (0-255) */ byte speedMin; /** * Meters per second (0-255) */ byte speedMax; /** * Unitless direction between 0-255; approximately 1 unit per 0.71 degrees */ byte directionMin; /** * Unitless direction between 0-255; approximately 1 unit per 0.71 degrees */ byte directionMax; } /** * How temperature is stored *
* As it's annoying to figure out the dewpoint on the watch, * please send it from the companion *
* We don't do floats, microdegrees are not useful. Make sure to multiply. */ static public class Temperature extends TimelineHeader { /** * Temperature °C but multiplied by 100 (e.g. -12.50°C becomes -1250, 0-65535) */ short temperature; /** * Dewpoint °C but multiplied by 100 (e.g. -12.50°C becomes -1250, 0-65535) */ short dewPoint; } /** * How location info is stored *
* This can be mostly static with long expiration, * as it usually is, but it could change during a trip for ex. * so we allow changing it dynamically. *
* Location info can be for some kind of map watchface * or daylight calculations, should those be required. */ static public class Location extends TimelineHeader { /** * Location name */ String location; /** * Altitude relative to sea level in meters (0-65535) */ short altitude; /** * Latitude, EPSG:3857 (Google Maps, Openstreetmaps datum, 0-4294967295) */ int latitude; /** * Longitude, EPSG:3857 (Google Maps, Openstreetmaps datum, 0-4294967295) */ int longitude; } /** * How humidity is stored */ static public class Humidity extends TimelineHeader { /** * Relative humidity, 0-100% */ byte humidity; } /** * How air pressure is stored */ static public class Pressure extends TimelineHeader { /** * Air pressure in hectopascals (hPa, 0-65535) */ short pressure; } /** * How special events are stored */ static public class Special extends TimelineHeader { /** * Special event's type */ SpecialType type; } /** * How air quality is stored *
* These events are a bit more complex because the topic is not simple, * the intention is to heavy-lift the annoying preprocessing from the watch * this allows watchface or watchapp makers to generate accurate alerts and graphics *
* If this needs further enforced standardization, pull requests are welcome */ static public class AirQuality extends TimelineHeader { /** * The name of the pollution *
* for the sake of better compatibility with watchapps * that might want to use this data for say visuals * don't localize the name. *
* Ideally watchapp itself localizes the name, if it's at all needed. *
* E.g. * For generic ones use "PM0.1", "PM5", "PM10" * For chemical compounds use the molecular formula e.g. "NO2", "CO2", "O3" * For pollen use the genus, e.g. "Betula" for birch or "Alternaria" for that mold's spores */ String polluter; /** * Amount of the pollution in SI units, * otherwise it's going to be difficult to create UI, alerts * and so on and for. *
* See more: * https://ec.europa.eu/environment/air/quality/standards.htm * http://www.ourair.org/wp-content/uploads/2012-aaqs2.pdf *
* Example units: * count/m³ for pollen * µgC/m³ for micrograms of organic carbon * µg/m³ sulfates, PM0.1, PM1, PM2, PM10 and so on, dust * mg/m³ CO2, CO * ng/m³ for heavy metals *
* List is not comprehensive, should be improved. * The current ones are what watchapps assume. *
* Note: ppb and ppm to concentration should be calculated on the companion, using * the correct formula (taking into account temperature and air pressure) *
* Note2: The amount is off by times 100, for two decimal places of precision. * E.g. 54.32µg/m³ is 5432 */ int amount; } }