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.
This commit is contained in:
José Rebelo 2024-03-29 21:10:40 +00:00
parent 57fd857de5
commit 81aef0bf35
37 changed files with 297 additions and 190 deletions

View File

@ -374,10 +374,9 @@ public class DebugActivity extends AbstractGBActivity {
weatherSpec.forecasts.add(gbForecast);
}
Weather.getInstance().setWeatherSpec(weatherSpec);
Weather.getInstance().setWeatherSpec(new ArrayList<>(Collections.singletonList(weatherSpec)));
}
GBApplication.deviceService().onSendWeather(Weather.getInstance().getWeatherSpec());
GBApplication.deviceService().onSendWeather(new ArrayList<>(Collections.singletonList(Weather.getInstance().getWeatherSpec())));
}
});
@ -385,18 +384,27 @@ public class DebugActivity extends AbstractGBActivity {
showCachedWeatherButton.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
final String weatherInfo = getWeatherInfo();
final List<WeatherSpec> weatherSpecs = Weather.getInstance().getWeatherSpecs();
if (weatherSpecs == null || weatherSpecs.isEmpty()) {
displayWeatherInfo(null);
return;
} else if (weatherSpecs.size() == 1) {
displayWeatherInfo(weatherSpecs.get(0));
return;
}
final String[] weatherLocations = new String[weatherSpecs.size()];
for (int i = 0; i < weatherSpecs.size(); i++) {
weatherLocations[i] = weatherSpecs.get(i).location;
}
new MaterialAlertDialogBuilder(DebugActivity.this)
.setCancelable(true)
.setTitle("Cached Weather Data")
.setMessage(weatherInfo)
.setPositiveButton(R.string.ok, (dialog, which) -> {
})
.setNeutralButton(android.R.string.copy, (dialog, which) -> {
final ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("Weather Info", weatherInfo);
clipboard.setPrimaryClip(clip);
.setTitle("Choose Location")
.setItems(weatherLocations, (dialog, which) -> displayWeatherInfo(weatherSpecs.get(which)))
.setNegativeButton("Cancel", (dialog, which) -> {
})
.show();
}
@ -1045,14 +1053,30 @@ public class DebugActivity extends AbstractGBActivity {
return TextUtils.join(separator, mac).toUpperCase(Locale.ROOT);
}
private String getWeatherInfo() {
private void displayWeatherInfo(final WeatherSpec weatherSpec) {
final String weatherInfo = getWeatherInfo(weatherSpec);
new MaterialAlertDialogBuilder(DebugActivity.this)
.setCancelable(true)
.setTitle("Cached Weather Data")
.setMessage(weatherInfo)
.setPositiveButton(R.string.ok, (dialog, which) -> {
})
.setNeutralButton(android.R.string.copy, (dialog, which) -> {
final ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("Weather Info", weatherInfo);
clipboard.setPrimaryClip(clip);
})
.show();
}
private String getWeatherInfo(final WeatherSpec weatherSpec) {
final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.ROOT);
final StringBuilder builder = new StringBuilder();
WeatherSpec weatherSpec = Weather.getInstance().getWeatherSpec();
if (weatherSpec == null)
return "Weather cache is empty...";
return "Weather cache is empty.";
builder.append("Location: ").append(weatherSpec.location).append("\n");
builder.append("Timestamp: ").append(weatherSpec.timestamp).append("\n");

View File

@ -135,7 +135,7 @@ public interface EventHandler {
void onTestNewFunction();
void onSendWeather(WeatherSpec weatherSpec);
void onSendWeather(ArrayList<WeatherSpec> weatherSpecs);
void onSetFmFrequency(float frequency);

View File

@ -260,7 +260,8 @@ public class SMAQ2OSSSupport extends AbstractBTLEDeviceSupport {
}
@Override
public void onSendWeather(WeatherSpec weatherSpec) {
public void onSendWeather(ArrayList<WeatherSpec> weatherSpecs) {
WeatherSpec weatherSpec = weatherSpecs.get(0);
try {
TransactionBuilder builder;
builder = performInitialized("Sending current weather");

View File

@ -28,6 +28,7 @@ import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;
import cyanogenmod.weather.CMWeatherManager;
@ -176,8 +177,9 @@ public class CMWeatherReceiver extends BroadcastReceiver implements CMWeatherMan
gbForecast.conditionCode = Weather.mapToOpenWeatherMapCondition(CMtoYahooCondintion(cmForecast.getConditionCode()));
weatherSpec.forecasts.add(gbForecast);
}
Weather.getInstance().setWeatherSpec(weatherSpec);
GBApplication.deviceService().onSendWeather(weatherSpec);
ArrayList<WeatherSpec> weatherSpecs = new ArrayList<>(Collections.singletonList(weatherSpec));
Weather.getInstance().setWeatherSpec(weatherSpecs);
GBApplication.deviceService().onSendWeather(weatherSpecs);
} else {
LOG.info("request has returned null for WeatherInfo");
}

View File

@ -25,11 +25,13 @@ import android.os.Bundle;
import android.widget.Toast;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Objects;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.model.Weather;
@ -41,111 +43,143 @@ public class GenericWeatherReceiver extends BroadcastReceiver {
public final static String ACTION_GENERIC_WEATHER = "nodomain.freeyourgadget.gadgetbridge.ACTION_GENERIC_WEATHER";
public final static String EXTRA_WEATHER_JSON = "WeatherJson";
public final static String EXTRA_WEATHER_SECONDARY_JSON = "WeatherSecondaryJson";
@Override
public void onReceive(Context context, Intent intent) {
if (intent != null && ACTION_GENERIC_WEATHER.equals(intent.getAction())) {
Bundle bundle = intent.getExtras();
if (bundle != null && bundle.containsKey(EXTRA_WEATHER_JSON)) {
try {
JSONObject weatherJson = new JSONObject(bundle.getString(EXTRA_WEATHER_JSON));
public void onReceive(final Context context, final Intent intent) {
if (intent == null) {
LOG.warn("Intent is null");
return;
}
WeatherSpec weatherSpec = new WeatherSpec();
if (!ACTION_GENERIC_WEATHER.equals(intent.getAction())) {
LOG.warn("Unknown action {}", intent.getAction());
return;
}
weatherSpec.timestamp = safelyGet(weatherJson, Integer.class, "timestamp", (int) (System.currentTimeMillis() / 1000));
weatherSpec.location = safelyGet(weatherJson, String.class, "location", "");
weatherSpec.currentTemp = safelyGet(weatherJson, Integer.class, "currentTemp", 0);
weatherSpec.todayMinTemp = safelyGet(weatherJson, Integer.class, "todayMinTemp", 0);
weatherSpec.todayMaxTemp = safelyGet(weatherJson, Integer.class, "todayMaxTemp", 0);
weatherSpec.currentCondition = safelyGet(weatherJson, String.class, "currentCondition", "");
weatherSpec.currentConditionCode = safelyGet(weatherJson, Integer.class, "currentConditionCode", 0);
weatherSpec.currentHumidity = safelyGet(weatherJson, Integer.class, "currentHumidity", 0);
weatherSpec.windSpeed = safelyGet(weatherJson, Number.class, "windSpeed", 0d).floatValue();
weatherSpec.windDirection = safelyGet(weatherJson, Integer.class, "windDirection", 0);
weatherSpec.uvIndex = safelyGet(weatherJson, Number.class, "uvIndex", 0d).floatValue();
weatherSpec.precipProbability = safelyGet(weatherJson, Integer.class, "precipProbability", 0);
weatherSpec.dewPoint = safelyGet(weatherJson, Integer.class, "dewPoint", 0);
weatherSpec.pressure = safelyGet(weatherJson, Number.class, "pressure", 0).floatValue();
weatherSpec.cloudCover = safelyGet(weatherJson, Integer.class, "cloudCover", 0);
weatherSpec.visibility = safelyGet(weatherJson, Number.class, "visibility", 0).floatValue();
weatherSpec.sunRise = safelyGet(weatherJson, Integer.class, "sunRise", 0);
weatherSpec.sunSet = safelyGet(weatherJson, Integer.class, "sunSet", 0);
weatherSpec.moonRise = safelyGet(weatherJson, Integer.class, "moonRise", 0);
weatherSpec.moonSet = safelyGet(weatherJson, Integer.class, "moonSet", 0);
weatherSpec.moonPhase = safelyGet(weatherJson, Integer.class, "moonPhase", 0);
weatherSpec.latitude = safelyGet(weatherJson, Number.class, "latitude", 0).floatValue();
weatherSpec.longitude = safelyGet(weatherJson, Number.class, "longitude", 0).floatValue();
weatherSpec.feelsLikeTemp = safelyGet(weatherJson, Integer.class, "feelsLikeTemp", 0);
weatherSpec.isCurrentLocation = safelyGet(weatherJson, Integer.class, "isCurrentLocation", -1);
final Bundle bundle = intent.getExtras();
if (bundle == null) {
LOG.warn("Intent has no extras");
return;
}
if (weatherJson.has("airQuality")) {
weatherSpec.airQuality = toAirQuality(weatherJson.getJSONObject("airQuality"));
}
if (!bundle.containsKey(EXTRA_WEATHER_JSON)) {
LOG.warn("Bundle key {} not found", EXTRA_WEATHER_JSON);
return;
}
if (weatherJson.has("forecasts")) {
JSONArray forecastArray = weatherJson.getJSONArray("forecasts");
weatherSpec.forecasts = new ArrayList<>();
try {
final JSONObject primaryWeatherJson = new JSONObject(Objects.requireNonNull(bundle.getString(EXTRA_WEATHER_JSON)));
final WeatherSpec primaryWeather = weatherFromJson(primaryWeatherJson);
for (int i = 0, l = forecastArray.length(); i < l; i++) {
JSONObject forecastJson = forecastArray.getJSONObject(i);
final ArrayList<WeatherSpec> weathers = new ArrayList<>();
weathers.add(primaryWeather);
WeatherSpec.Daily forecast = new WeatherSpec.Daily();
if (bundle.containsKey(EXTRA_WEATHER_SECONDARY_JSON)) {
final JSONArray secondaryWeatherJson = new JSONArray(bundle.getString(EXTRA_WEATHER_SECONDARY_JSON, "[]"));
forecast.conditionCode = safelyGet(forecastJson, Integer.class, "conditionCode", 0);
forecast.humidity = safelyGet(forecastJson, Integer.class, "humidity", 0);
forecast.maxTemp = safelyGet(forecastJson, Integer.class, "maxTemp", 0);
forecast.minTemp = safelyGet(forecastJson, Integer.class, "minTemp", 0);
forecast.windSpeed = safelyGet(forecastJson, Number.class, "windSpeed", 0).floatValue();
forecast.windDirection = safelyGet(forecastJson, Integer.class, "windDirection", 0);
forecast.uvIndex = safelyGet(forecastJson, Number.class, "uvIndex", 0d).floatValue();
forecast.precipProbability = safelyGet(forecastJson, Integer.class, "precipProbability", 0);
forecast.sunRise = safelyGet(forecastJson, Integer.class, "sunRise", 0);
forecast.sunSet = safelyGet(forecastJson, Integer.class, "sunSet", 0);
forecast.moonRise = safelyGet(forecastJson, Integer.class, "moonRise", 0);
forecast.moonSet = safelyGet(forecastJson, Integer.class, "moonSet", 0);
forecast.moonPhase = safelyGet(forecastJson, Integer.class, "moonPhase", 0);
if (forecastJson.has("airQuality")) {
forecast.airQuality = toAirQuality(forecastJson.getJSONObject("airQuality"));
}
weatherSpec.forecasts.add(forecast);
}
}
if (weatherJson.has("hourly")) {
JSONArray forecastArray = weatherJson.getJSONArray("hourly");
weatherSpec.hourly = new ArrayList<>();
for (int i = 0, l = forecastArray.length(); i < l; i++) {
JSONObject forecastJson = forecastArray.getJSONObject(i);
WeatherSpec.Hourly forecast = new WeatherSpec.Hourly();
forecast.timestamp = safelyGet(forecastJson, Integer.class, "timestamp", 0);
forecast.temp = safelyGet(forecastJson, Integer.class, "temp", 0);
forecast.conditionCode = safelyGet(forecastJson, Integer.class, "conditionCode", 0);
forecast.humidity = safelyGet(forecastJson, Integer.class, "humidity", 0);
forecast.windSpeed = safelyGet(forecastJson, Number.class, "windSpeed", 0).floatValue();
forecast.windDirection = safelyGet(forecastJson, Integer.class, "windDirection", 0);
forecast.uvIndex = safelyGet(forecastJson, Number.class, "uvIndex", 0d).floatValue();
forecast.precipProbability = safelyGet(forecastJson, Integer.class, "precipProbability", 0);
weatherSpec.hourly.add(forecast);
}
}
LOG.info("Got generic weather for {}", weatherSpec.location);
Weather.getInstance().setWeatherSpec(weatherSpec);
GBApplication.deviceService().onSendWeather(weatherSpec);
} catch (Exception e) {
GB.toast("Gadgetbridge received broken or incompatible weather data", Toast.LENGTH_SHORT, GB.ERROR, e);
for (int i = 0; i < secondaryWeatherJson.length(); i++) {
weathers.add(weatherFromJson(secondaryWeatherJson.getJSONObject(i)));
}
}
LOG.info("Got generic weather for {} locations", weathers.size());
Weather.getInstance().setWeatherSpec(weathers);
GBApplication.deviceService().onSendWeather(weathers);
} catch (final Exception e) {
GB.toast("Gadgetbridge received broken or incompatible weather data", Toast.LENGTH_SHORT, GB.ERROR, e);
}
}
private WeatherSpec weatherFromJson(final JSONObject weatherJson) throws JSONException {
final WeatherSpec weatherSpec = new WeatherSpec();
weatherSpec.timestamp = safelyGet(weatherJson, Integer.class, "timestamp", (int) (System.currentTimeMillis() / 1000));
weatherSpec.location = safelyGet(weatherJson, String.class, "location", "");
weatherSpec.currentTemp = safelyGet(weatherJson, Integer.class, "currentTemp", 0);
weatherSpec.todayMinTemp = safelyGet(weatherJson, Integer.class, "todayMinTemp", 0);
weatherSpec.todayMaxTemp = safelyGet(weatherJson, Integer.class, "todayMaxTemp", 0);
weatherSpec.currentCondition = safelyGet(weatherJson, String.class, "currentCondition", "");
weatherSpec.currentConditionCode = safelyGet(weatherJson, Integer.class, "currentConditionCode", 0);
weatherSpec.currentHumidity = safelyGet(weatherJson, Integer.class, "currentHumidity", 0);
weatherSpec.windSpeed = safelyGet(weatherJson, Number.class, "windSpeed", 0d).floatValue();
weatherSpec.windDirection = safelyGet(weatherJson, Integer.class, "windDirection", 0);
weatherSpec.uvIndex = safelyGet(weatherJson, Number.class, "uvIndex", 0d).floatValue();
weatherSpec.precipProbability = safelyGet(weatherJson, Integer.class, "precipProbability", 0);
weatherSpec.dewPoint = safelyGet(weatherJson, Integer.class, "dewPoint", 0);
weatherSpec.pressure = safelyGet(weatherJson, Number.class, "pressure", 0).floatValue();
weatherSpec.cloudCover = safelyGet(weatherJson, Integer.class, "cloudCover", 0);
weatherSpec.visibility = safelyGet(weatherJson, Number.class, "visibility", 0).floatValue();
weatherSpec.sunRise = safelyGet(weatherJson, Integer.class, "sunRise", 0);
weatherSpec.sunSet = safelyGet(weatherJson, Integer.class, "sunSet", 0);
weatherSpec.moonRise = safelyGet(weatherJson, Integer.class, "moonRise", 0);
weatherSpec.moonSet = safelyGet(weatherJson, Integer.class, "moonSet", 0);
weatherSpec.moonPhase = safelyGet(weatherJson, Integer.class, "moonPhase", 0);
weatherSpec.latitude = safelyGet(weatherJson, Number.class, "latitude", 0).floatValue();
weatherSpec.longitude = safelyGet(weatherJson, Number.class, "longitude", 0).floatValue();
weatherSpec.feelsLikeTemp = safelyGet(weatherJson, Integer.class, "feelsLikeTemp", 0);
weatherSpec.isCurrentLocation = safelyGet(weatherJson, Integer.class, "isCurrentLocation", -1);
if (weatherJson.has("airQuality")) {
weatherSpec.airQuality = toAirQuality(weatherJson.getJSONObject("airQuality"));
}
if (weatherJson.has("forecasts")) {
final JSONArray forecastArray = weatherJson.getJSONArray("forecasts");
weatherSpec.forecasts = new ArrayList<>();
for (int i = 0, l = forecastArray.length(); i < l; i++) {
final JSONObject forecastJson = forecastArray.getJSONObject(i);
final WeatherSpec.Daily forecast = new WeatherSpec.Daily();
forecast.conditionCode = safelyGet(forecastJson, Integer.class, "conditionCode", 0);
forecast.humidity = safelyGet(forecastJson, Integer.class, "humidity", 0);
forecast.maxTemp = safelyGet(forecastJson, Integer.class, "maxTemp", 0);
forecast.minTemp = safelyGet(forecastJson, Integer.class, "minTemp", 0);
forecast.windSpeed = safelyGet(forecastJson, Number.class, "windSpeed", 0).floatValue();
forecast.windDirection = safelyGet(forecastJson, Integer.class, "windDirection", 0);
forecast.uvIndex = safelyGet(forecastJson, Number.class, "uvIndex", 0d).floatValue();
forecast.precipProbability = safelyGet(forecastJson, Integer.class, "precipProbability", 0);
forecast.sunRise = safelyGet(forecastJson, Integer.class, "sunRise", 0);
forecast.sunSet = safelyGet(forecastJson, Integer.class, "sunSet", 0);
forecast.moonRise = safelyGet(forecastJson, Integer.class, "moonRise", 0);
forecast.moonSet = safelyGet(forecastJson, Integer.class, "moonSet", 0);
forecast.moonPhase = safelyGet(forecastJson, Integer.class, "moonPhase", 0);
if (forecastJson.has("airQuality")) {
forecast.airQuality = toAirQuality(forecastJson.getJSONObject("airQuality"));
}
weatherSpec.forecasts.add(forecast);
}
}
if (weatherJson.has("hourly")) {
final JSONArray forecastArray = weatherJson.getJSONArray("hourly");
weatherSpec.hourly = new ArrayList<>();
for (int i = 0, l = forecastArray.length(); i < l; i++) {
final JSONObject forecastJson = forecastArray.getJSONObject(i);
final WeatherSpec.Hourly forecast = new WeatherSpec.Hourly();
forecast.timestamp = safelyGet(forecastJson, Integer.class, "timestamp", 0);
forecast.temp = safelyGet(forecastJson, Integer.class, "temp", 0);
forecast.conditionCode = safelyGet(forecastJson, Integer.class, "conditionCode", 0);
forecast.humidity = safelyGet(forecastJson, Integer.class, "humidity", 0);
forecast.windSpeed = safelyGet(forecastJson, Number.class, "windSpeed", 0).floatValue();
forecast.windDirection = safelyGet(forecastJson, Integer.class, "windDirection", 0);
forecast.uvIndex = safelyGet(forecastJson, Number.class, "uvIndex", 0d).floatValue();
forecast.precipProbability = safelyGet(forecastJson, Integer.class, "precipProbability", 0);
weatherSpec.hourly.add(forecast);
}
}
return weatherSpec;
}
private WeatherSpec.AirQuality toAirQuality(final JSONObject jsonObject) {
final WeatherSpec.AirQuality airQuality = new WeatherSpec.AirQuality();
airQuality.aqi = safelyGet(jsonObject, Integer.class, "aqi", -1);

View File

@ -40,6 +40,7 @@ import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;
import lineageos.weather.LineageWeatherManager;
@ -201,8 +202,9 @@ public class LineageOsWeatherReceiver extends BroadcastReceiver implements Linea
gbForecast.conditionCode = Weather.mapToOpenWeatherMapCondition(LineageOSToYahooCondition(cmForecast.getConditionCode()));
weatherSpec.forecasts.add(gbForecast);
}
Weather.getInstance().setWeatherSpec(weatherSpec);
GBApplication.deviceService().onSendWeather(weatherSpec);
ArrayList<WeatherSpec> weatherSpecs = new ArrayList<>(Collections.singletonList(weatherSpec));
Weather.getInstance().setWeatherSpec(weatherSpecs);
GBApplication.deviceService().onSendWeather(weatherSpecs);
} else {
LOG.info("request has returned null for WeatherInfo");
}

View File

@ -29,6 +29,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.model.Weather;
@ -136,8 +138,9 @@ public class OmniJawsObserver extends ContentObserver {
}
}
Weather.getInstance().setWeatherSpec(weatherSpec);
GBApplication.deviceService().onSendWeather(weatherSpec);
ArrayList<WeatherSpec> weatherSpecs = new ArrayList<>(Collections.singletonList(weatherSpec));
Weather.getInstance().setWeatherSpec(weatherSpecs);
GBApplication.deviceService().onSendWeather(weatherSpecs);
} finally {
c.close();

View File

@ -23,6 +23,9 @@ import android.content.Intent;
import android.os.Bundle;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.Collections;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.model.Weather;
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
@ -38,9 +41,10 @@ public class TinyWeatherForecastGermanyReceiver extends BroadcastReceiver {
try {
WeatherSpec weatherSpec = bundle.getParcelable("WeatherSpec");
if (weatherSpec != null) {
Weather.getInstance().setWeatherSpec(weatherSpec);
ArrayList<WeatherSpec> weatherSpecs = new ArrayList<>(Collections.singletonList(weatherSpec));
weatherSpec.timestamp = (int) (System.currentTimeMillis() / 1000);
GBApplication.deviceService().onSendWeather(weatherSpec);
Weather.getInstance().setWeatherSpec(weatherSpecs);
GBApplication.deviceService().onSendWeather(weatherSpecs);
}
} catch (Exception e) {
GB.toast("Gadgetbridge received broken or incompatible weather data", Toast.LENGTH_SHORT, GB.ERROR, e);

View File

@ -23,6 +23,9 @@ import android.content.Intent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collections;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.model.Weather;
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
@ -53,8 +56,9 @@ public class WeatherNotificationReceiver extends BroadcastReceiver {
WeatherSpec weatherSpec = parcelableWeather2.weatherSpec;
LOG.info("weather in " + weatherSpec.location + " is " + weatherSpec.currentCondition + " (" + (weatherSpec.currentTemp - 273) + "°C)");
Weather.getInstance().setWeatherSpec(weatherSpec);
GBApplication.deviceService().onSendWeather(weatherSpec);
ArrayList<WeatherSpec> weatherSpecs = new ArrayList<>(Collections.singletonList(weatherSpec));
Weather.getInstance().setWeatherSpec(weatherSpecs);
GBApplication.deviceService().onSendWeather(weatherSpecs);
}
}
}

View File

@ -488,9 +488,9 @@ public class GBDeviceService implements DeviceService {
}
@Override
public void onSendWeather(WeatherSpec weatherSpec) {
public void onSendWeather(ArrayList<WeatherSpec> weatherSpecs) {
Intent intent = createIntent().setAction(ACTION_SEND_WEATHER)
.putExtra(EXTRA_WEATHER, (Parcelable) weatherSpec);
.putExtra(EXTRA_WEATHER, weatherSpecs);
invokeService(intent);
}

View File

@ -17,6 +17,8 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.model;
import androidx.annotation.Nullable;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@ -26,28 +28,47 @@ import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamException;
import java.util.ArrayList;
import java.util.List;
public class Weather {
private static final Logger LOG = LoggerFactory.getLogger(Weather.class);
private WeatherSpec weatherSpec = null;
private final ArrayList<WeatherSpec> weatherSpecs = new ArrayList<>();
private JSONObject reconstructedOWMForecast = null;
private File cacheFile;
public WeatherSpec getWeatherSpec() {
return weatherSpec;
private Weather() {
// Use getInstance
}
public void setWeatherSpec(WeatherSpec weatherSpec) {
this.weatherSpec = weatherSpec;
@Nullable
public WeatherSpec getWeatherSpec() {
if (weatherSpecs.isEmpty()) {
return null;
}
return weatherSpecs.get(0);
}
public List<WeatherSpec> getWeatherSpecs() {
return weatherSpecs;
}
public void setWeatherSpec(final List<WeatherSpec> newWeatherSpecs) {
weatherSpecs.clear();
weatherSpecs.addAll(newWeatherSpecs);
saveToCache();
}
public JSONObject createReconstructedOWMWeatherReply() {
final WeatherSpec weatherSpec = getWeatherSpec();
if (weatherSpec == null) {
return null;
}
@ -208,6 +229,7 @@ public class Weather {
}
}
public static int mapToYahooCondition(int openWeatherMapCondition) {
// openweathermap.org conditions:
// http://openweathermap.org/weather-conditions
@ -1133,24 +1155,29 @@ public class Weather {
* @param enabled whether caching is enabled
*/
public void setCacheFile(final File cacheDir, final boolean enabled) {
// FIXME: Do not use serializable for this
cacheFile = new File(cacheDir, "weatherCache.bin");
if (enabled) {
LOG.info("Setting weather cache file to {}", cacheFile.getPath());
if (cacheFile.isFile() && weatherSpec == null) {
try (final FileInputStream f = new FileInputStream(cacheFile)) {
final ObjectInputStream o = new ObjectInputStream(f);
weatherSpec = (WeatherSpec) o.readObject();
o.close();
if (cacheFile.isFile() && weatherSpecs.isEmpty()) {
try (final ObjectInputStream o = new ObjectInputStream(new FileInputStream(cacheFile))) {
final ArrayList<WeatherSpec> cachedSpecs = (ArrayList<WeatherSpec>) o.readObject();
weatherSpecs.addAll(cachedSpecs);
} catch (final ObjectStreamException e) {
LOG.error("Failed to deserialize weather from cache", e);
// keep cacheFile set - it's most likely an older version
} catch (final IOException e) {
LOG.error("Failed to read weather cache file", e);
// Something is wrong with the file
cacheFile = null;
} catch (final Throwable e) {
LOG.error("Failed to read weather from cache", e);
weatherSpec = null;
cacheFile = null;
// keep cacheFile set - it's most likely an older version
}
} else if (weatherSpec != null) {
} else if (!weatherSpecs.isEmpty()) {
saveToCache();
}
} else {
@ -1171,20 +1198,14 @@ public class Weather {
* Save the current weather to cache, if a cache file is enabled and the weather is not null.
*/
public void saveToCache() {
if (weatherSpec == null || cacheFile == null) {
if (weatherSpecs.isEmpty() || cacheFile == null) {
return;
}
LOG.info("Loading weather from cache {}", cacheFile.getPath());
try {
final FileOutputStream f = new FileOutputStream(cacheFile);
final ObjectOutputStream o = new ObjectOutputStream(f);
o.writeObject(weatherSpec);
o.close();
f.close();
try (ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream(cacheFile))) {
o.writeObject(weatherSpecs);
} catch (final Throwable e) {
LOG.error("Failed to save weather to cache", e);
}

View File

@ -64,7 +64,7 @@ public class WeatherSpec implements Parcelable, Serializable {
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
public int moonPhase; // deg [0, 360[
public float latitude;
public float longitude;
public int feelsLikeTemp; // kelvin

View File

@ -1156,11 +1156,13 @@ public abstract class AbstractDeviceSupport implements DeviceSupport {
/**
* If the device can receive weather information, this method can be
* overridden and implemented by the device support class.
* @param weatherSpec weather information
* overridden and implemented by the device support class. It's guaranteed
* that there is always at least one weatherSpec, with the first being the
* primary weather (not necessarily current location).
* @param weatherSpecs weather information
*/
@Override
public void onSendWeather(WeatherSpec weatherSpec) {
public void onSendWeather(ArrayList<WeatherSpec> weatherSpecs) {
}

View File

@ -1031,9 +1031,9 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
break;
}
case ACTION_SEND_WEATHER: {
WeatherSpec weatherSpec = intent.getParcelableExtra(EXTRA_WEATHER);
if (weatherSpec != null) {
deviceSupport.onSendWeather(weatherSpec);
ArrayList<WeatherSpec> weatherSpecs = (ArrayList<WeatherSpec>) intent.getSerializableExtra(EXTRA_WEATHER);
if (weatherSpecs != null && !weatherSpecs.isEmpty()) {
deviceSupport.onSendWeather(weatherSpecs);
}
break;
}

View File

@ -474,11 +474,11 @@ public class ServiceDeviceSupport implements DeviceSupport {
}
@Override
public void onSendWeather(WeatherSpec weatherSpec) {
if (checkBusy("send weather event")) {
public void onSendWeather(ArrayList<WeatherSpec> weatherSpecs) {
if (checkBusy("send weather events")) {
return;
}
delegate.onSendWeather(weatherSpec);
delegate.onSendWeather(weatherSpecs);
}
@Override

View File

@ -27,6 +27,7 @@ import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Objects;
@ -215,7 +216,8 @@ public class AsteroidOSDeviceSupport extends AbstractBTLEDeviceSupport {
}
@Override
public void onSendWeather(WeatherSpec weatherSpec) {
public void onSendWeather(ArrayList<WeatherSpec> weatherSpecs) {
WeatherSpec weatherSpec = weatherSpecs.get(0);
AsteroidOSWeather asteroidOSWeather = new AsteroidOSWeather(weatherSpec);
TransactionBuilder builder = new TransactionBuilder("send weather info");
// Send city name

View File

@ -1644,7 +1644,8 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
}
@Override
public void onSendWeather(WeatherSpec weatherSpec) {
public void onSendWeather(ArrayList<WeatherSpec> weatherSpecs) {
WeatherSpec weatherSpec = weatherSpecs.get(0);
try {
JSONObject o = new JSONObject();
o.put("t", "weather");

View File

@ -584,7 +584,8 @@ public class CmfWatchProSupport extends AbstractBTLEDeviceSupport implements Cmf
}
@Override
public void onSendWeather(final WeatherSpec weatherSpec) {
public void onSendWeather(final ArrayList<WeatherSpec> weatherSpecs) {
final WeatherSpec weatherSpec = weatherSpecs.get(0);
// TODO consider adjusting the condition code for clear/sunny so "clear" at night doesn't show a sunny icon (perhaps 23 decimal)?
// Each weather entry takes up 9 bytes
// There are 7 of those weather entries - 7*9 bytes

View File

@ -550,7 +550,8 @@ public class FitProDeviceSupport extends AbstractBTLEDeviceSupport {
}
@Override
public void onSendWeather(WeatherSpec weatherSpec) {
public void onSendWeather(ArrayList<WeatherSpec> weatherSpecs) {
WeatherSpec weatherSpec = weatherSpecs.get(0);
LOG.debug("FitPro send weather");
short todayMax = (short) (weatherSpec.todayMaxTemp - 273);
short todayMin = (short) (weatherSpec.todayMinTemp - 273);

View File

@ -584,7 +584,8 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
}
@Override
public void onSendWeather(WeatherSpec weatherSpec) {
public void onSendWeather(ArrayList<WeatherSpec> weatherSpecs) {
WeatherSpec weatherSpec = weatherSpecs.get(0);
try {
TransactionBuilder builder = performInitialized("sendWeather");

View File

@ -3101,7 +3101,7 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements
}
@Override
public void onSendWeather(WeatherSpec weatherSpec) {
public void onSendWeather(ArrayList<WeatherSpec> weatherSpecs) {
final DeviceCoordinator coordinator = gbDevice.getDeviceCoordinator();
if (!coordinator.supportsWeather()) {
return;
@ -3118,6 +3118,8 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements
supportsConditionString = false;
}
final WeatherSpec weatherSpec = weatherSpecs.get(0);
MiBandConst.DistanceUnit unit = HuamiCoordinator.getDistanceUnit();
int tz_offset_hours = SimpleTimeZone.getDefault().getOffset(weatherSpec.timestamp * 1000L) / (1000 * 60 * 60);
try {

View File

@ -837,7 +837,9 @@ public class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTransferSer
}
@Override
public void onSendWeather(final WeatherSpec weatherSpec) {
public void onSendWeather(final ArrayList<WeatherSpec> weatherSpecs) {
final WeatherSpec weatherSpec = weatherSpecs.get(0);
// Weather is not sent directly to the bands, they send HTTP requests for each location.
// When we have a weather update, set the default location to that location on the band.
// TODO: Support for multiple weather locations

View File

@ -120,7 +120,7 @@ public class HuaweiBRSupport extends AbstractBTBRDeviceSupport {
}
@Override
public void onSendWeather(WeatherSpec weatherSpec) {
supportProvider.onSendWeather(weatherSpec);
public void onSendWeather(ArrayList<WeatherSpec> weatherSpecs) {
supportProvider.onSendWeather(weatherSpecs);
}
}

View File

@ -128,7 +128,7 @@ public class HuaweiLESupport extends AbstractBTLEDeviceSupport {
}
@Override
public void onSendWeather(WeatherSpec weatherSpec) {
supportProvider.onSendWeather(weatherSpec);
public void onSendWeather(ArrayList<WeatherSpec> weatherSpecs) {
supportProvider.onSendWeather(weatherSpecs);
}
}

View File

@ -1712,13 +1712,15 @@ public class HuaweiSupportProvider {
return Weather.WeatherIcon.UNKNOWN;
}
public void onSendWeather(WeatherSpec weatherSpec) {
public void onSendWeather(ArrayList<WeatherSpec> weatherSpecs) {
// Initialize weather settings and send weather
if (!getHuaweiCoordinator().supportsWeather()) {
LOG.error("onSendWeather called while weather is not supported.");
return;
}
WeatherSpec weatherSpec = weatherSpecs.get(0);
Weather.Settings weatherSettings = new Weather.Settings();
SendWeatherStartRequest weatherStartRequest = new SendWeatherStartRequest(this, weatherSettings);

View File

@ -1219,7 +1219,8 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport {
}
@Override
public void onSendWeather(WeatherSpec weatherSpec) {
public void onSendWeather(ArrayList<WeatherSpec> weatherSpecs) {
WeatherSpec weatherSpec = weatherSpecs.get(0);
try {
TransactionBuilder builder = performInitialized("setWeather");
int currentTemp;

View File

@ -1209,11 +1209,6 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
}
}
@Override
public void onSendWeather(WeatherSpec weatherSpec) {
}
/**
* Analyse and decode sensor data from ADXL362 accelerometer
* @param value to decode

View File

@ -226,9 +226,9 @@ public class PebbleSupport extends AbstractSerialDeviceSupport {
}
@Override
public void onSendWeather(WeatherSpec weatherSpec) {
public void onSendWeather(ArrayList<WeatherSpec> weatherSpecs) {
if (reconnect()) {
super.onSendWeather(weatherSpec);
super.onSendWeather(weatherSpecs);
}
}
}

View File

@ -1027,7 +1027,9 @@ public class PineTimeJFSupport extends AbstractBTLEDeviceSupport implements DfuL
}
@Override
public void onSendWeather(WeatherSpec weatherSpec) {
public void onSendWeather(ArrayList<WeatherSpec> weatherSpecs) {
WeatherSpec weatherSpec = weatherSpecs.get(0);
if (this.firmwareVersionMajor < 1 || (this.firmwareVersionMajor == 1 && this.firmwareVersionMinor <= 7)) {
// Not supported
return;

View File

@ -695,8 +695,8 @@ public class QHybridSupport extends QHybridBaseSupport {
}
@Override
public void onSendWeather(WeatherSpec weatherSpec) {
watchAdapter.onSendWeather(weatherSpec);
public void onSendWeather(ArrayList<WeatherSpec> weatherSpecs) {
watchAdapter.onSendWeather(weatherSpecs.get(0));
}
@Override

View File

@ -372,9 +372,4 @@ public class SoFlowSupport extends AbstractBTLEDeviceSupport {
GB.toast("Error setting configuration", Toast.LENGTH_LONG, GB.ERROR, e);
}
}
@Override
public void onSendWeather(WeatherSpec weatherSpec) {
}
}

View File

@ -599,7 +599,8 @@ public class SonyWena3DeviceSupport extends AbstractBTLEDeviceSupport {
}
@Override
public void onSendWeather(WeatherSpec weatherSpec) {
public void onSendWeather(ArrayList<WeatherSpec> weatherSpecs) {
WeatherSpec weatherSpec = weatherSpecs.get(0);
if(weatherSpec.forecasts.size() < 4) return;
ArrayList<WeatherDay> days = new ArrayList<>();

View File

@ -115,6 +115,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.GB;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.LinkedHashMap;
@ -936,8 +937,8 @@ public class VivomoveHrSupport extends AbstractBTLEDeviceSupport implements File
private boolean foreground;
@Override
public void onSendWeather(WeatherSpec weatherSpec) {
sendWeatherConditions(weatherSpec);
public void onSendWeather(ArrayList<WeatherSpec> weatherSpecs) {
sendWeatherConditions(weatherSpecs.get(0));
}
private final Map<Integer, DirectoryEntry> filesToDownload = new ConcurrentHashMap<>();

View File

@ -383,7 +383,8 @@ public class WaspOSDeviceSupport extends AbstractBTLEDeviceSupport {
}
@Override
public void onSendWeather(WeatherSpec weatherSpec) {
public void onSendWeather(ArrayList<WeatherSpec> weatherSpecs) {
WeatherSpec weatherSpec = weatherSpecs.get(0);
try {
JSONObject o = new JSONObject();
o.put("t", "weather");

View File

@ -398,8 +398,8 @@ public class XiaomiSupport extends AbstractDeviceSupport {
}
@Override
public void onSendWeather(final WeatherSpec weatherSpec) {
weatherService.onSendWeather(weatherSpec);
public void onSendWeather(final ArrayList<WeatherSpec> weatherSpecs) {
weatherService.onSendWeather(weatherSpecs.get(0));
}
@Override

View File

@ -597,7 +597,8 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
}
@Override
public void onSendWeather(WeatherSpec weatherSpec) {
public void onSendWeather(ArrayList<WeatherSpec> weatherSpecs) {
WeatherSpec weatherSpec = weatherSpecs.get(0);
byte[] weather = new byte[weatherSpec.location.getBytes(StandardCharsets.UTF_8).length + 26]; // 26 bytes for weatherdata and overhead
weather[0] = ZeTimeConstants.CMD_PREAMBLE;
weather[1] = ZeTimeConstants.CMD_PUSH_WEATHER_DATA;

View File

@ -264,7 +264,8 @@ public abstract class AbstractSerialDeviceSupport extends AbstractDeviceSupport
}
@Override
public void onSendWeather(WeatherSpec weatherSpec) {
public void onSendWeather(ArrayList<WeatherSpec> weatherSpecs) {
WeatherSpec weatherSpec = weatherSpecs.get(0);
byte[] bytes = gbDeviceProtocol.encodeSendWeather(weatherSpec);
sendToDevice(bytes);
}