mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-11-28 21:06:50 +01:00
Revert "Revert "PineTime Weather support""
This reverts commit 50ff6a8a6f
.
This commit is contained in:
parent
c36857f063
commit
259a422de6
@ -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.
|
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_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_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_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
|
// 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_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");
|
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
|
Gobbetti, José Rebelo, Taavi Eomäe
|
||||||
|
|
||||||
This file is part of Gadgetbridge.
|
This file is part of Gadgetbridge.
|
||||||
@ -126,7 +126,7 @@ public class PineTimeJFCoordinator extends AbstractBLEDeviceCoordinator {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsWeather() {
|
public boolean supportsWeather() {
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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
|
Kranz, Taavi Eomäe
|
||||||
|
|
||||||
This file is part of Gadgetbridge.
|
This file is part of Gadgetbridge.
|
||||||
@ -17,16 +17,25 @@
|
|||||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.pinetime;
|
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.BluetoothGatt;
|
||||||
import android.bluetooth.BluetoothGattCharacteristic;
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
@ -36,7 +45,8 @@ import java.util.Locale;
|
|||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
import java.util.UUID;
|
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.DfuLogListener;
|
||||||
import no.nordicsemi.android.dfu.DfuProgressListener;
|
import no.nordicsemi.android.dfu.DfuProgressListener;
|
||||||
import no.nordicsemi.android.dfu.DfuProgressListenerAdapter;
|
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.PineTimeDFUService;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.pinetime.PineTimeInstallHandler;
|
import nodomain.freeyourgadget.gadgetbridge.devices.pinetime.PineTimeInstallHandler;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.pinetime.PineTimeJFConstants;
|
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.Device;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.PineTimeActivitySample;
|
import nodomain.freeyourgadget.gadgetbridge.entities.PineTimeActivitySample;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
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_DEVICE_INFORMATION);
|
||||||
addSupportedService(GattService.UUID_SERVICE_BATTERY_SERVICE);
|
addSupportedService(GattService.UUID_SERVICE_BATTERY_SERVICE);
|
||||||
addSupportedService(PineTimeJFConstants.UUID_SERVICE_MUSIC_CONTROL);
|
addSupportedService(PineTimeJFConstants.UUID_SERVICE_MUSIC_CONTROL);
|
||||||
|
addSupportedService(PineTimeJFConstants.UUID_SERVICE_WEATHER);
|
||||||
addSupportedService(PineTimeJFConstants.UUID_CHARACTERISTIC_ALERT_NOTIFICATION_EVENT);
|
addSupportedService(PineTimeJFConstants.UUID_CHARACTERISTIC_ALERT_NOTIFICATION_EVENT);
|
||||||
addSupportedService(PineTimeJFConstants.UUID_SERVICE_MOTION);
|
addSupportedService(PineTimeJFConstants.UUID_SERVICE_MOTION);
|
||||||
|
|
||||||
@ -481,6 +493,9 @@ public class PineTimeJFSupport extends AbstractBTLEDeviceSupport implements DfuL
|
|||||||
batteryInfoProfile.requestBatteryInfo(builder);
|
batteryInfoProfile.requestBatteryInfo(builder);
|
||||||
batteryInfoProfile.enableNotify(builder, true);
|
batteryInfoProfile.enableNotify(builder, true);
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
builder.requestMtu(256);
|
||||||
|
}
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -651,7 +666,249 @@ public class PineTimeJFSupport extends AbstractBTLEDeviceSupport implements DfuL
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSendWeather(WeatherSpec weatherSpec) {
|
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 &&
|
if (characteristic != null &&
|
||||||
(characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE) > 0) {
|
(characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE) > 0) {
|
||||||
builder.write(characteristic, data);
|
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.hwVersion = info.getHardwareRevision();
|
||||||
versionCmd.fwVersion = info.getFirmwareRevision();
|
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"
|
// FW version format : "major.minor.patch". Ex : "0.8.2"
|
||||||
String[] tokens = StringUtils.split(versionCmd.fwVersion, ".");
|
String[] tokens = StringUtils.split(versionCmd.fwVersion, ".");
|
||||||
if (tokens.length == 3) {
|
if (tokens.length == 3) {
|
||||||
|
Loading…
Reference in New Issue
Block a user