mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-11-28 21:06:50 +01:00
PineTime Weather support
This commit is contained in:
parent
2928a0e13b
commit
230cbe964b
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2020-2021 Taavi Eomäe
|
||||
/* Copyright (C) 2020-2022 Taavi Eomäe, Stephan Lachnit, ITCactus
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -34,8 +34,14 @@ public class PineTimeJFConstants {
|
||||
public static final UUID UUID_CHARACTERISTICS_MUSIC_REPEAT = UUID.fromString("0000000b-78fc-48fe-8e23-433b3a1942d0");
|
||||
public static final UUID UUID_CHARACTERISTICS_MUSIC_SHUFFLE = UUID.fromString("0000000c-78fc-48fe-8e23-433b3a1942d0");
|
||||
|
||||
public static final UUID UUID_SERVICE_NAVIGATION = UUID.fromString("00010000-78fc-48fe-8e23-433b3a1942d0");
|
||||
|
||||
public static final UUID UUID_CHARACTERISTIC_ALERT_NOTIFICATION_EVENT = UUID.fromString("00020001-78fc-48fe-8e23-433b3a1942d0");
|
||||
|
||||
public static final UUID UUID_SERVICE_WEATHER = UUID.fromString("00040000-78fc-48fe-8e23-433b3a1942d0");
|
||||
public static final UUID UUID_CHARACTERISTIC_WEATHER_DATA = UUID.fromString("00040001-78fc-48fe-8e23-433b3a1942d0");
|
||||
public static final UUID UUID_CHARACTERISTIC_WEATHER_CONTROL = UUID.fromString("00040002-78fc-48fe-8e23-433b3a1942d0");
|
||||
|
||||
// since 1.7. https://github.com/InfiniTimeOrg/InfiniTime/blob/develop/doc/MotionService.md
|
||||
public static final UUID UUID_SERVICE_MOTION = UUID.fromString("00030000-78fc-48fe-8e23-433b3a1942d0");
|
||||
public static final UUID UUID_CHARACTERISTIC_MOTION_STEP_COUNT = UUID.fromString("00030001-78fc-48fe-8e23-433b3a1942d0");
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2016-2021 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
/* Copyright (C) 2016-2022 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti, José Rebelo, Taavi Eomäe
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
@ -126,7 +126,7 @@ public class PineTimeJFCoordinator extends AbstractBLEDeviceCoordinator {
|
||||
|
||||
@Override
|
||||
public boolean supportsWeather() {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -0,0 +1,927 @@
|
||||
/* Copyright (C) 2021-2022 TaaviE
|
||||
|
||||
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 <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.pinetime.weather;
|
||||
|
||||
/**
|
||||
* Implemented based on and other material:
|
||||
* https://en.wikipedia.org/wiki/METAR
|
||||
* https://www.weather.gov/jetstream/obscurationtypes
|
||||
* http://www.faraim.org/aim/aim-4-03-14-493.html
|
||||
*/
|
||||
public class WeatherData {
|
||||
/**
|
||||
* OpenWeather's condition codes are a bit annoying,
|
||||
* they've mixed precipitation with other weather events.
|
||||
* <p>
|
||||
* Even if they're absolutely not mutually exclusive.
|
||||
* <p>
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* Even if they're absolutely not mutually exclusive.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* Even if they're absolutely not mutually exclusive.
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* Even if they're absolutely not mutually exclusive.
|
||||
*
|
||||
* <p>
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
||||
;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* <p>
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* <p>
|
||||
* 32 bits ought to be enough for everyone
|
||||
* <p>
|
||||
* 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
|
||||
* <p>
|
||||
* In order to represent bursts of wind instead of constant wind,
|
||||
* you have minimum and maximum speeds.
|
||||
* <p>
|
||||
* 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
|
||||
* <p>
|
||||
* As it's annoying to figure out the dewpoint on the watch,
|
||||
* please send it from the companion
|
||||
* <p>
|
||||
* 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
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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
|
||||
* <p>
|
||||
* 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
|
||||
* <p>
|
||||
* If this needs further enforced standardization, pull requests are welcome
|
||||
*/
|
||||
static public class AirQuality extends TimelineHeader {
|
||||
/**
|
||||
* The name of the pollution
|
||||
* <p>
|
||||
* for the sake of better compatibility with watchapps
|
||||
* that might want to use this data for say visuals
|
||||
* don't localize the name.
|
||||
* <p>
|
||||
* Ideally watchapp itself localizes the name, if it's at all needed.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* See more:
|
||||
* https://ec.europa.eu/environment/air/quality/standards.htm
|
||||
* http://www.ourair.org/wp-content/uploads/2012-aaqs2.pdf
|
||||
* <p>
|
||||
* 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
|
||||
* <p>
|
||||
* List is not comprehensive, should be improved.
|
||||
* The current ones are what watchapps assume.
|
||||
* <p>
|
||||
* Note: ppb and ppm to concentration should be calculated on the companion, using
|
||||
* the correct formula (taking into account temperature and air pressure)
|
||||
* <p>
|
||||
* Note2: The amount is off by times 100, for two decimal places of precision.
|
||||
* E.g. 54.32µg/m³ is 5432
|
||||
*/
|
||||
int amount;
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2016-2021 Andreas Shimokawa, Carsten Pfeiffer, JF, Sebastian
|
||||
/* Copyright (C) 2016-2022 Andreas Shimokawa, Carsten Pfeiffer, JF, Sebastian
|
||||
Kranz, Taavi Eomäe
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
@ -17,16 +17,25 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.pinetime;
|
||||
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.pinetime.weather.WeatherData.mapOpenWeatherConditionToCloudCover;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.pinetime.weather.WeatherData.mapOpenWeatherConditionToPineTimeObscuration;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.pinetime.weather.WeatherData.mapOpenWeatherConditionToPineTimePrecipitation;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.pinetime.weather.WeatherData.mapOpenWeatherConditionToPineTimeSpecial;
|
||||
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
@ -36,7 +45,8 @@ import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
import java.util.UUID;
|
||||
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
import co.nstant.in.cbor.CborBuilder;
|
||||
import co.nstant.in.cbor.CborEncoder;
|
||||
import no.nordicsemi.android.dfu.DfuLogListener;
|
||||
import no.nordicsemi.android.dfu.DfuProgressListener;
|
||||
import no.nordicsemi.android.dfu.DfuProgressListenerAdapter;
|
||||
@ -55,6 +65,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.pinetime.PineTimeActivitySam
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.pinetime.PineTimeDFUService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.pinetime.PineTimeInstallHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.pinetime.PineTimeJFConstants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.pinetime.weather.WeatherData;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.PineTimeActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
||||
@ -235,6 +246,7 @@ public class PineTimeJFSupport extends AbstractBTLEDeviceSupport implements DfuL
|
||||
addSupportedService(GattService.UUID_SERVICE_DEVICE_INFORMATION);
|
||||
addSupportedService(GattService.UUID_SERVICE_BATTERY_SERVICE);
|
||||
addSupportedService(PineTimeJFConstants.UUID_SERVICE_MUSIC_CONTROL);
|
||||
addSupportedService(PineTimeJFConstants.UUID_SERVICE_WEATHER);
|
||||
addSupportedService(PineTimeJFConstants.UUID_CHARACTERISTIC_ALERT_NOTIFICATION_EVENT);
|
||||
addSupportedService(PineTimeJFConstants.UUID_SERVICE_MOTION);
|
||||
|
||||
@ -481,6 +493,9 @@ public class PineTimeJFSupport extends AbstractBTLEDeviceSupport implements DfuL
|
||||
batteryInfoProfile.requestBatteryInfo(builder);
|
||||
batteryInfoProfile.enableNotify(builder, true);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
builder.requestMtu(256);
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
|
||||
@ -651,7 +666,249 @@ public class PineTimeJFSupport extends AbstractBTLEDeviceSupport implements DfuL
|
||||
|
||||
@Override
|
||||
public void onSendWeather(WeatherSpec weatherSpec) {
|
||||
if (this.firmwareVersionMajor != 1 || this.firmwareVersionMinor <= 7) {
|
||||
// Not supported
|
||||
return;
|
||||
} else {
|
||||
if (weatherSpec.location != null) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
try {
|
||||
new CborEncoder(baos).encode(new CborBuilder()
|
||||
.startMap() // This map is not fixed-size, which is not great, but it might come in a library update
|
||||
.put("Timestamp", System.currentTimeMillis() / 1000L)
|
||||
.put("Expires", 60 * 6) // 6h
|
||||
.put("EventType", WeatherData.EventType.Location.value)
|
||||
.put("Location", weatherSpec.location)
|
||||
.put("Altitude", 0)
|
||||
.put("Latitude", 0)
|
||||
.put("Longitude", 0)
|
||||
.end()
|
||||
.build()
|
||||
);
|
||||
} catch (Exception e) {
|
||||
LOG.warn(String.valueOf(e));
|
||||
}
|
||||
byte[] encodedBytes = baos.toByteArray();
|
||||
TransactionBuilder builder = createTransactionBuilder("WeatherData");
|
||||
safeWriteToCharacteristic(builder,
|
||||
PineTimeJFConstants.UUID_CHARACTERISTIC_WEATHER_DATA,
|
||||
encodedBytes);
|
||||
|
||||
builder.queue(getQueue());
|
||||
}
|
||||
|
||||
// Current condition
|
||||
if (weatherSpec.currentCondition != null) {
|
||||
// We can't do anything with this?
|
||||
}
|
||||
|
||||
// Current humidity
|
||||
if (weatherSpec.currentHumidity > 0) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
try {
|
||||
new CborEncoder(baos).encode(new CborBuilder()
|
||||
.startMap() // This map is not fixed-size, which is not great, but it might come in a library update
|
||||
.put("Timestamp", System.currentTimeMillis() / 1000L)
|
||||
.put("Expires", 60 * 6) // 6h this should be the weather provider's interval, really
|
||||
.put("EventType", WeatherData.EventType.Humidity.value)
|
||||
.put("Humidity", (int) weatherSpec.currentHumidity)
|
||||
.end()
|
||||
.build()
|
||||
);
|
||||
} catch (Exception e) {
|
||||
LOG.warn(String.valueOf(e));
|
||||
}
|
||||
byte[] encodedBytes = baos.toByteArray();
|
||||
TransactionBuilder builder = createTransactionBuilder("WeatherData");
|
||||
safeWriteToCharacteristic(builder,
|
||||
PineTimeJFConstants.UUID_CHARACTERISTIC_WEATHER_DATA,
|
||||
encodedBytes);
|
||||
|
||||
builder.queue(getQueue());
|
||||
}
|
||||
|
||||
// Current temperature
|
||||
if (weatherSpec.currentTemp >= -273.15) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
try {
|
||||
new CborEncoder(baos).encode(new CborBuilder()
|
||||
.startMap() // This map is not fixed-size, which is not great, but it might come in a library update
|
||||
.put("Timestamp", System.currentTimeMillis() / 1000L)
|
||||
.put("Expires", 60 * 6) // 6h this should be the weather provider's interval, really
|
||||
.put("EventType", WeatherData.EventType.Temperature.value)
|
||||
.put("Temperature", (int) (weatherSpec.currentTemp * 100))
|
||||
.put("DewPoint", (int) (-32768))
|
||||
.end()
|
||||
.build()
|
||||
);
|
||||
} catch (Exception e) {
|
||||
LOG.warn(String.valueOf(e));
|
||||
}
|
||||
byte[] encodedBytes = baos.toByteArray();
|
||||
TransactionBuilder builder = createTransactionBuilder("WeatherData");
|
||||
safeWriteToCharacteristic(builder,
|
||||
PineTimeJFConstants.UUID_CHARACTERISTIC_WEATHER_DATA,
|
||||
encodedBytes);
|
||||
|
||||
builder.queue(getQueue());
|
||||
}
|
||||
|
||||
// 24h temperature forecast
|
||||
if (weatherSpec.todayMinTemp >= -273.15 &&
|
||||
weatherSpec.todayMaxTemp >= -273.15) { // Some sanity checking, should really be nullable
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
try {
|
||||
new CborEncoder(baos).encode(new CborBuilder()
|
||||
.startMap() // This map is not fixed-size, which is not great, but it might come in a library update
|
||||
.put("Timestamp", System.currentTimeMillis() / 1000L)
|
||||
.put("Expires", 60 * 60 * 24) // 24h, because the temperature is today's
|
||||
.put("EventType", WeatherData.EventType.Temperature.value)
|
||||
.put("Temperature", (int) (((weatherSpec.todayMinTemp + weatherSpec.todayMaxTemp) / 2) * 100))
|
||||
.put("DewPoint", (int) (-32768))
|
||||
.end()
|
||||
.build()
|
||||
);
|
||||
} catch (Exception e) {
|
||||
LOG.warn(String.valueOf(e));
|
||||
}
|
||||
byte[] encodedBytes = baos.toByteArray();
|
||||
TransactionBuilder builder = createTransactionBuilder("WeatherData");
|
||||
safeWriteToCharacteristic(builder,
|
||||
PineTimeJFConstants.UUID_CHARACTERISTIC_WEATHER_DATA,
|
||||
encodedBytes);
|
||||
|
||||
builder.queue(getQueue());
|
||||
}
|
||||
|
||||
// Wind speed
|
||||
if (weatherSpec.windSpeed != 0.0f) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
try {
|
||||
new CborEncoder(baos).encode(new CborBuilder()
|
||||
.startMap() // This map is not fixed-size, which is not great, but it might come in a library update
|
||||
.put("Timestamp", System.currentTimeMillis() / 1000L)
|
||||
.put("Expires", 60 * 60 * 6) // 6h
|
||||
.put("EventType", WeatherData.EventType.Wind.value)
|
||||
.put("SpeedMin", (int) (weatherSpec.windSpeed / 60 / 60 * 1000))
|
||||
.put("SpeedMax", (int) (weatherSpec.windSpeed / 60 / 60 * 1000))
|
||||
.put("DirectionMin", (int) (0.71 * weatherSpec.windDirection))
|
||||
.put("DirectionMax", (int) (0.71 * weatherSpec.windDirection))
|
||||
.end()
|
||||
.build()
|
||||
);
|
||||
} catch (Exception e) {
|
||||
LOG.warn(String.valueOf(e));
|
||||
}
|
||||
byte[] encodedBytes = baos.toByteArray();
|
||||
TransactionBuilder builder = createTransactionBuilder("WeatherData");
|
||||
safeWriteToCharacteristic(builder,
|
||||
PineTimeJFConstants.UUID_CHARACTERISTIC_WEATHER_DATA,
|
||||
encodedBytes);
|
||||
|
||||
builder.queue(getQueue());
|
||||
}
|
||||
|
||||
// Current weather condition
|
||||
if (mapOpenWeatherConditionToPineTimePrecipitation(weatherSpec.currentConditionCode) != WeatherData.PrecipitationType.Length) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
try {
|
||||
new CborEncoder(baos).encode(new CborBuilder()
|
||||
.startMap() // This map is not fixed-size, which is not great, but it might come in a library update
|
||||
.put("Timestamp", System.currentTimeMillis() / 1000L)
|
||||
.put("Expires", 60 * 60 * 6) // 6h
|
||||
.put("EventType", WeatherData.EventType.Precipitation.value)
|
||||
.put("Type", (int) mapOpenWeatherConditionToPineTimePrecipitation(weatherSpec.currentConditionCode).value)
|
||||
.put("Amount", (int) 0)
|
||||
.end()
|
||||
.build()
|
||||
);
|
||||
} catch (Exception e) {
|
||||
LOG.warn(String.valueOf(e));
|
||||
}
|
||||
byte[] encodedBytes = baos.toByteArray();
|
||||
TransactionBuilder builder = createTransactionBuilder("WeatherData");
|
||||
safeWriteToCharacteristic(builder,
|
||||
PineTimeJFConstants.UUID_CHARACTERISTIC_WEATHER_DATA,
|
||||
encodedBytes);
|
||||
|
||||
builder.queue(getQueue());
|
||||
}
|
||||
|
||||
if (mapOpenWeatherConditionToPineTimeObscuration(weatherSpec.currentConditionCode) != WeatherData.ObscurationType.Length) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
try {
|
||||
new CborEncoder(baos).encode(new CborBuilder()
|
||||
.startMap() // This map is not fixed-size, which is not great, but it might come in a library update
|
||||
.put("Timestamp", System.currentTimeMillis() / 1000L)
|
||||
.put("Expires", 60 * 60 * 6) // 6h
|
||||
.put("EventType", WeatherData.EventType.Obscuration.value)
|
||||
.put("Type", (int) mapOpenWeatherConditionToPineTimeObscuration(weatherSpec.currentConditionCode).value)
|
||||
.put("Amount", (int) 65535)
|
||||
.end()
|
||||
.build()
|
||||
);
|
||||
} catch (Exception e) {
|
||||
LOG.warn(String.valueOf(e));
|
||||
}
|
||||
byte[] encodedBytes = baos.toByteArray();
|
||||
TransactionBuilder builder = createTransactionBuilder("WeatherData");
|
||||
safeWriteToCharacteristic(builder,
|
||||
PineTimeJFConstants.UUID_CHARACTERISTIC_WEATHER_DATA,
|
||||
encodedBytes);
|
||||
|
||||
builder.queue(getQueue());
|
||||
}
|
||||
|
||||
if (mapOpenWeatherConditionToPineTimeSpecial(weatherSpec.currentConditionCode) != WeatherData.SpecialType.Length) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
try {
|
||||
new CborEncoder(baos).encode(new CborBuilder()
|
||||
.startMap() // This map is not fixed-size, which is not great, but it might come in a library update
|
||||
.put("Timestamp", System.currentTimeMillis() / 1000L)
|
||||
.put("Expires", 60 * 60 * 6) // 6h
|
||||
.put("EventType", WeatherData.EventType.Special.value)
|
||||
.put("Type", mapOpenWeatherConditionToPineTimeSpecial(weatherSpec.currentConditionCode).value)
|
||||
.end()
|
||||
.build()
|
||||
);
|
||||
} catch (Exception e) {
|
||||
LOG.warn(String.valueOf(e));
|
||||
}
|
||||
byte[] encodedBytes = baos.toByteArray();
|
||||
TransactionBuilder builder = createTransactionBuilder("WeatherData");
|
||||
safeWriteToCharacteristic(builder,
|
||||
PineTimeJFConstants.UUID_CHARACTERISTIC_WEATHER_DATA,
|
||||
encodedBytes);
|
||||
|
||||
builder.queue(getQueue());
|
||||
}
|
||||
|
||||
if (mapOpenWeatherConditionToCloudCover(weatherSpec.currentConditionCode) != -1) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
try {
|
||||
new CborEncoder(baos).encode(new CborBuilder()
|
||||
.startMap() // This map is not fixed-size, which is not great, but it might come in a library update
|
||||
.put("Timestamp", System.currentTimeMillis() / 1000L)
|
||||
.put("Expires", 60 * 60 * 6) // 6h
|
||||
.put("EventType", WeatherData.EventType.Clouds.value)
|
||||
.put("Amount", (int) (mapOpenWeatherConditionToCloudCover(weatherSpec.currentConditionCode)))
|
||||
.end()
|
||||
.build()
|
||||
);
|
||||
} catch (Exception e) {
|
||||
LOG.warn(String.valueOf(e));
|
||||
}
|
||||
byte[] encodedBytes = baos.toByteArray();
|
||||
TransactionBuilder builder = createTransactionBuilder("WeatherData");
|
||||
safeWriteToCharacteristic(builder,
|
||||
PineTimeJFConstants.UUID_CHARACTERISTIC_WEATHER_DATA,
|
||||
encodedBytes);
|
||||
|
||||
builder.queue(getQueue());
|
||||
}
|
||||
|
||||
LOG.debug("Wrote weather data");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -671,6 +928,8 @@ public class PineTimeJFSupport extends AbstractBTLEDeviceSupport implements DfuL
|
||||
if (characteristic != null &&
|
||||
(characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE) > 0) {
|
||||
builder.write(characteristic, data);
|
||||
} else {
|
||||
LOG.warn("Tried to write to a characteristic that did not exist or was not writable!");
|
||||
}
|
||||
}
|
||||
|
||||
@ -688,7 +947,7 @@ public class PineTimeJFSupport extends AbstractBTLEDeviceSupport implements DfuL
|
||||
versionCmd.hwVersion = info.getHardwareRevision();
|
||||
versionCmd.fwVersion = info.getFirmwareRevision();
|
||||
|
||||
if(versionCmd.fwVersion != null && !versionCmd.fwVersion.isEmpty()) {
|
||||
if (versionCmd.fwVersion != null && !versionCmd.fwVersion.isEmpty()) {
|
||||
// FW version format : "major.minor.patch". Ex : "0.8.2"
|
||||
String[] tokens = StringUtils.split(versionCmd.fwVersion, ".");
|
||||
if (tokens.length == 3) {
|
||||
|
Loading…
Reference in New Issue
Block a user