1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-06-27 15:30:14 +02:00
Gadgetbridge/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/WeatherSpec.java
José Rebelo 81aef0bf35 Add support for multiple weather locations
Introduce the concept of primary and secondary weathers:

* Primary weather keeps the same behavior as previously across all weather providers, so it's non-breaking. This location is not necessarily the current location, just the primary weather location set by the user.
* The GenericWeatherReceiver now has a new extra WeatherSecondaryJson, that receives a json list with secondary weather locations.

It's guaranteed that the primary weather always exists, so the list of WeatherSpecs provided to devices is never empty. Update all support classes accordingly.
2024-03-29 21:10:40 +00:00

482 lines
16 KiB
Java

/* Copyright (C) 2016-2024 Andreas Shimokawa, Arjan Schrijver, beardhatcode,
Carsten Pfeiffer, Daniele Gobbetti, Enrico Brambilla, José Rebelo, Taavi
Eomäe
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.model;
import android.location.Location;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.Nullable;
import java.io.Serializable;
import java.util.ArrayList;
// FIXME: document me and my fields, including units
public class WeatherSpec implements Parcelable, Serializable {
public static final Creator<WeatherSpec> CREATOR = new Creator<WeatherSpec>() {
@Override
public WeatherSpec createFromParcel(Parcel in) {
return new WeatherSpec(in);
}
@Override
public WeatherSpec[] newArray(int size) {
return new WeatherSpec[size];
}
};
public static final int VERSION = 4;
private static final long serialVersionUID = VERSION;
public int timestamp; // unix epoch timestamp, in seconds
public String location;
public int currentTemp; // kelvin
public int currentConditionCode = 3200; // OpenWeatherMap condition code
public String currentCondition;
public int currentHumidity;
public int todayMaxTemp; // kelvin
public int todayMinTemp; // kelvin
public float windSpeed; // km per hour
public int windDirection; // deg
public float uvIndex;
public int precipProbability; // %
public int dewPoint; // kelvin
public float pressure; // mb
public int cloudCover; // %
public float visibility; // m
public int sunRise; // unix epoch timestamp, in seconds
public int sunSet; // unix epoch timestamp, in seconds
public int moonRise; // unix epoch timestamp, in seconds
public int moonSet; // unix epoch timestamp, in seconds
public int moonPhase; // deg [0, 360[
public float latitude;
public float longitude;
public int feelsLikeTemp; // kelvin
public int isCurrentLocation = -1; // 0 for false, 1 for true, -1 for unknown
public AirQuality airQuality;
// Forecasts from the next day onward, in chronological order, one entry per day.
// It should not include the current or previous days
public ArrayList<Daily> forecasts = new ArrayList<>();
// Hourly forecasts
public ArrayList<Hourly> hourly = new ArrayList<>();
public WeatherSpec() {
}
// Lower bounds of beaufort regions 1 to 12
// Values from https://en.wikipedia.org/wiki/Beaufort_scale
static final float[] beaufort = new float[] { 2, 6, 12, 20, 29, 39, 50, 62, 75, 89, 103, 118 };
// level: 0 1 2 3 4 5 6 7 8 9 10 11 12
public static int toBeaufort(final float speed) {
int l = 0;
while (l < beaufort.length && beaufort[l] < speed) {
l++;
}
return l;
}
public int windSpeedAsBeaufort() {
return toBeaufort(this.windSpeed);
}
@Nullable
public Location getLocation() {
if (latitude == 0 && longitude == 0) {
return null;
}
final Location location = new Location("weatherSpec");
location.setLatitude(latitude);
location.setLongitude(longitude);
return location;
}
protected WeatherSpec(Parcel in) {
int version = in.readInt();
if (version >= 2) {
timestamp = in.readInt();
location = in.readString();
currentTemp = in.readInt();
currentConditionCode = in.readInt();
currentCondition = in.readString();
currentHumidity = in.readInt();
todayMaxTemp = in.readInt();
todayMinTemp = in.readInt();
windSpeed = in.readFloat();
windDirection = in.readInt();
if (version < 4) {
// Deserialize the old Forecast list and convert them to Daily
final ArrayList<Forecast> oldForecasts = new ArrayList<>();
in.readList(oldForecasts, Forecast.class.getClassLoader());
for (final Forecast forecast : oldForecasts) {
final Daily d = new Daily();
d.minTemp = forecast.minTemp;
d.maxTemp = forecast.maxTemp;
d.conditionCode = forecast.conditionCode;
d.humidity = forecast.humidity;
forecasts.add(d);
}
} else {
in.readList(forecasts, Daily.class.getClassLoader());
}
}
if (version >= 3) {
uvIndex = in.readFloat();
precipProbability = in.readInt();
}
if (version >= 4) {
dewPoint = in.readInt();
pressure = in.readFloat();
cloudCover = in.readInt();
visibility = in.readFloat();
sunRise = in.readInt();
sunSet = in.readInt();
moonRise = in.readInt();
moonSet = in.readInt();
moonPhase = in.readInt();
latitude = in.readFloat();
longitude = in.readFloat();
feelsLikeTemp = in.readInt();
isCurrentLocation = in.readInt();
airQuality = in.readParcelable(AirQuality.class.getClassLoader());
in.readList(hourly, Hourly.class.getClassLoader());
}
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(VERSION);
dest.writeInt(timestamp);
dest.writeString(location);
dest.writeInt(currentTemp);
dest.writeInt(currentConditionCode);
dest.writeString(currentCondition);
dest.writeInt(currentHumidity);
dest.writeInt(todayMaxTemp);
dest.writeInt(todayMinTemp);
dest.writeFloat(windSpeed);
dest.writeInt(windDirection);
dest.writeList(forecasts);
dest.writeFloat(uvIndex);
dest.writeInt(precipProbability);
dest.writeInt(dewPoint);
dest.writeFloat(pressure);
dest.writeInt(cloudCover);
dest.writeFloat(visibility);
dest.writeInt(sunRise);
dest.writeInt(sunSet);
dest.writeInt(moonRise);
dest.writeInt(moonSet);
dest.writeInt(moonPhase);
dest.writeFloat(latitude);
dest.writeFloat(longitude);
dest.writeInt(feelsLikeTemp);
dest.writeInt(isCurrentLocation);
dest.writeParcelable(airQuality, 0);
dest.writeList(hourly);
}
@Deprecated // kept for backwards compatibility with old weather apps
public static class Forecast implements Parcelable, Serializable {
private static final long serialVersionUID = 1L;
public static final Creator<Forecast> CREATOR = new Creator<Forecast>() {
@Override
public Forecast createFromParcel(Parcel in) {
return new Forecast(in);
}
@Override
public Forecast[] newArray(int size) {
return new Forecast[size];
}
};
public int minTemp; // Kelvin
public int maxTemp; // Kelvin
public int conditionCode; // OpenWeatherMap condition code
public int humidity;
public Forecast() {
}
public Forecast(int minTemp, int maxTemp, int conditionCode, int humidity) {
this.minTemp = minTemp;
this.maxTemp = maxTemp;
this.conditionCode = conditionCode;
this.humidity = humidity;
}
Forecast(Parcel in) {
minTemp = in.readInt();
maxTemp = in.readInt();
conditionCode = in.readInt();
humidity = in.readInt();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(minTemp);
dest.writeInt(maxTemp);
dest.writeInt(conditionCode);
dest.writeInt(humidity);
}
}
public static class AirQuality implements Parcelable, Serializable {
public static final int VERSION = 1;
private static final long serialVersionUID = VERSION;
public static final Creator<AirQuality> CREATOR = new Creator<AirQuality>() {
@Override
public AirQuality createFromParcel(final Parcel in) {
return new AirQuality(in);
}
@Override
public AirQuality[] newArray(final int size) {
return new AirQuality[size];
}
};
public int aqi = -1; // Air Quality Index - usually the max across all AQI values for pollutants
public float co = -1; // Carbon Monoxide, mg/m^3
public float no2 = -1; // Nitrogen Dioxide, ug/m^3
public float o3 = -1; // Ozone, ug/m^3
public float pm10 = -1; // Particulate Matter, 10 microns or less in diameter, ug/m^3
public float pm25 = -1; // Particulate Matter, 2.5 microns or less in diameter, ug/m^3
public float so2 = -1; // Sulphur Dioxide, ug/m^3
// Air Quality Index values per pollutant
// These are expected to be in the Plume scale (see https://plumelabs.files.wordpress.com/2023/06/plume_aqi_2023.pdf)
// Some apps such as Breezy Weather fallback to the WHO 2021 AQI for pollutants that are not mapped in the Plume AQI
// https://www.who.int/news-room/fact-sheets/detail/ambient-(outdoor)-air-quality-and-health
//
// Breezy Weather implementation for reference:
// - https://github.com/breezy-weather/breezy-weather/blob/main/app/src/main/java/org/breezyweather/common/basic/models/weather/AirQuality.kt
// - https://github.com/breezy-weather/breezy-weather/blob/main/app/src/main/java/org/breezyweather/common/basic/models/options/index/PollutantIndex.kt
public int coAqi = -1;
public int no2Aqi = -1;
public int o3Aqi = -1;
public int pm10Aqi = -1;
public int pm25Aqi = -1;
public int so2Aqi = -1;
public AirQuality() {
}
AirQuality(final Parcel in) {
in.readInt(); // version
aqi = in.readInt();
co = in.readFloat();
no2 = in.readFloat();
o3 = in.readFloat();
pm10 = in.readFloat();
pm25 = in.readFloat();
so2 = in.readFloat();
coAqi = in.readInt();
no2Aqi = in.readInt();
o3Aqi = in.readInt();
pm10Aqi = in.readInt();
pm25Aqi = in.readInt();
so2Aqi = in.readInt();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(final Parcel dest, final int flags) {
dest.writeInt(VERSION);
dest.writeInt(aqi);
dest.writeFloat(co);
dest.writeFloat(no2);
dest.writeFloat(o3);
dest.writeFloat(pm10);
dest.writeFloat(pm25);
dest.writeFloat(so2);
dest.writeInt(coAqi);
dest.writeInt(no2Aqi);
dest.writeInt(o3Aqi);
dest.writeInt(pm10Aqi);
dest.writeInt(pm25Aqi);
dest.writeInt(so2Aqi);
}
}
public static class Daily implements Parcelable, Serializable {
public static final int VERSION = 1;
private static final long serialVersionUID = VERSION;
public static final Creator<Daily> CREATOR = new Creator<Daily>() {
@Override
public Daily createFromParcel(final Parcel in) {
return new Daily(in);
}
@Override
public Daily[] newArray(final int size) {
return new Daily[size];
}
};
public int minTemp; // Kelvin
public int maxTemp; // Kelvin
public int conditionCode; // OpenWeatherMap condition code
public int humidity;
public float windSpeed; // km per hour
public int windDirection; // deg
public float uvIndex;
public int precipProbability; // %
public int sunRise;
public int sunSet;
public int moonRise;
public int moonSet;
public int moonPhase;
public AirQuality airQuality;
public Daily() {
}
Daily(final Parcel in) {
in.readInt(); // version
minTemp = in.readInt();
maxTemp = in.readInt();
conditionCode = in.readInt();
humidity = in.readInt();
windSpeed = in.readFloat();
windDirection = in.readInt();
uvIndex = in.readFloat();
precipProbability = in.readInt();
sunRise = in.readInt();
sunSet = in.readInt();
moonRise = in.readInt();
moonSet = in.readInt();
moonPhase = in.readInt();
airQuality = in.readParcelable(AirQuality.class.getClassLoader());
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(final Parcel dest, final int flags) {
dest.writeInt(VERSION);
dest.writeInt(minTemp);
dest.writeInt(maxTemp);
dest.writeInt(conditionCode);
dest.writeInt(humidity);
dest.writeFloat(windSpeed);
dest.writeInt(windDirection);
dest.writeFloat(uvIndex);
dest.writeInt(precipProbability);
dest.writeInt(sunRise);
dest.writeInt(sunSet);
dest.writeInt(moonRise);
dest.writeInt(moonSet);
dest.writeInt(moonPhase);
dest.writeParcelable(airQuality, 0);
}
public int windSpeedAsBeaufort() {
return toBeaufort(this.windSpeed);
}
}
public static class Hourly implements Parcelable, Serializable {
public static final int VERSION = 1;
private static final long serialVersionUID = VERSION;
public static final Creator<Hourly> CREATOR = new Creator<Hourly>() {
@Override
public Hourly createFromParcel(final Parcel in) {
return new Hourly(in);
}
@Override
public Hourly[] newArray(final int size) {
return new Hourly[size];
}
};
public int timestamp; // unix epoch timestamp, in seconds
public int temp; // Kelvin
public int conditionCode; // OpenWeatherMap condition code
public int humidity;
public float windSpeed; // km per hour
public int windDirection; // deg
public float uvIndex;
public int precipProbability; // %
public Hourly() {
}
Hourly(final Parcel in) {
in.readInt(); // version
timestamp = in.readInt();
temp = in.readInt();
conditionCode = in.readInt();
humidity = in.readInt();
windSpeed = in.readFloat();
windDirection = in.readInt();
uvIndex = in.readFloat();
precipProbability = in.readInt();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(final Parcel dest, final int flags) {
dest.writeInt(VERSION);
dest.writeInt(timestamp);
dest.writeInt(temp);
dest.writeInt(conditionCode);
dest.writeInt(humidity);
dest.writeFloat(windSpeed);
dest.writeInt(windDirection);
dest.writeFloat(uvIndex);
dest.writeInt(precipProbability);
}
public int windSpeedAsBeaufort() {
return toBeaufort(this.windSpeed);
}
}
}