1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-12-26 10:35:50 +01:00

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); weatherSpec.forecasts.add(gbForecast);
} }
Weather.getInstance().setWeatherSpec(weatherSpec); Weather.getInstance().setWeatherSpec(new ArrayList<>(Collections.singletonList(weatherSpec)));
} }
GBApplication.deviceService().onSendWeather(new ArrayList<>(Collections.singletonList(Weather.getInstance().getWeatherSpec())));
GBApplication.deviceService().onSendWeather(Weather.getInstance().getWeatherSpec());
} }
}); });
@ -385,18 +384,27 @@ public class DebugActivity extends AbstractGBActivity {
showCachedWeatherButton.setOnClickListener(new View.OnClickListener(){ showCachedWeatherButton.setOnClickListener(new View.OnClickListener(){
@Override @Override
public void onClick(View v) { 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) new MaterialAlertDialogBuilder(DebugActivity.this)
.setCancelable(true) .setCancelable(true)
.setTitle("Cached Weather Data") .setTitle("Choose Location")
.setMessage(weatherInfo) .setItems(weatherLocations, (dialog, which) -> displayWeatherInfo(weatherSpecs.get(which)))
.setPositiveButton(R.string.ok, (dialog, which) -> { .setNegativeButton("Cancel", (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(); .show();
} }
@ -1045,14 +1053,30 @@ public class DebugActivity extends AbstractGBActivity {
return TextUtils.join(separator, mac).toUpperCase(Locale.ROOT); 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 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.ROOT);
final StringBuilder builder = new StringBuilder(); final StringBuilder builder = new StringBuilder();
WeatherSpec weatherSpec = Weather.getInstance().getWeatherSpec();
if (weatherSpec == null) if (weatherSpec == null)
return "Weather cache is empty..."; return "Weather cache is empty.";
builder.append("Location: ").append(weatherSpec.location).append("\n"); builder.append("Location: ").append(weatherSpec.location).append("\n");
builder.append("Timestamp: ").append(weatherSpec.timestamp).append("\n"); builder.append("Timestamp: ").append(weatherSpec.timestamp).append("\n");

View File

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

View File

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

View File

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

View File

@ -25,11 +25,13 @@ import android.os.Bundle;
import android.widget.Toast; import android.widget.Toast;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Objects;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.model.Weather; import nodomain.freeyourgadget.gadgetbridge.model.Weather;
@ -41,16 +43,56 @@ public class GenericWeatherReceiver extends BroadcastReceiver {
public final static String ACTION_GENERIC_WEATHER = "nodomain.freeyourgadget.gadgetbridge.ACTION_GENERIC_WEATHER"; 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_JSON = "WeatherJson";
public final static String EXTRA_WEATHER_SECONDARY_JSON = "WeatherSecondaryJson";
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(final Context context, final Intent intent) {
if (intent != null && ACTION_GENERIC_WEATHER.equals(intent.getAction())) { if (intent == null) {
Bundle bundle = intent.getExtras(); LOG.warn("Intent is null");
if (bundle != null && bundle.containsKey(EXTRA_WEATHER_JSON)) { return;
try { }
JSONObject weatherJson = new JSONObject(bundle.getString(EXTRA_WEATHER_JSON));
WeatherSpec weatherSpec = new WeatherSpec(); if (!ACTION_GENERIC_WEATHER.equals(intent.getAction())) {
LOG.warn("Unknown action {}", intent.getAction());
return;
}
final Bundle bundle = intent.getExtras();
if (bundle == null) {
LOG.warn("Intent has no extras");
return;
}
if (!bundle.containsKey(EXTRA_WEATHER_JSON)) {
LOG.warn("Bundle key {} not found", EXTRA_WEATHER_JSON);
return;
}
try {
final JSONObject primaryWeatherJson = new JSONObject(Objects.requireNonNull(bundle.getString(EXTRA_WEATHER_JSON)));
final WeatherSpec primaryWeather = weatherFromJson(primaryWeatherJson);
final ArrayList<WeatherSpec> weathers = new ArrayList<>();
weathers.add(primaryWeather);
if (bundle.containsKey(EXTRA_WEATHER_SECONDARY_JSON)) {
final JSONArray secondaryWeatherJson = new JSONArray(bundle.getString(EXTRA_WEATHER_SECONDARY_JSON, "[]"));
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.timestamp = safelyGet(weatherJson, Integer.class, "timestamp", (int) (System.currentTimeMillis() / 1000));
weatherSpec.location = safelyGet(weatherJson, String.class, "location", ""); weatherSpec.location = safelyGet(weatherJson, String.class, "location", "");
@ -83,13 +125,13 @@ public class GenericWeatherReceiver extends BroadcastReceiver {
} }
if (weatherJson.has("forecasts")) { if (weatherJson.has("forecasts")) {
JSONArray forecastArray = weatherJson.getJSONArray("forecasts"); final JSONArray forecastArray = weatherJson.getJSONArray("forecasts");
weatherSpec.forecasts = new ArrayList<>(); weatherSpec.forecasts = new ArrayList<>();
for (int i = 0, l = forecastArray.length(); i < l; i++) { for (int i = 0, l = forecastArray.length(); i < l; i++) {
JSONObject forecastJson = forecastArray.getJSONObject(i); final JSONObject forecastJson = forecastArray.getJSONObject(i);
WeatherSpec.Daily forecast = new WeatherSpec.Daily(); final WeatherSpec.Daily forecast = new WeatherSpec.Daily();
forecast.conditionCode = safelyGet(forecastJson, Integer.class, "conditionCode", 0); forecast.conditionCode = safelyGet(forecastJson, Integer.class, "conditionCode", 0);
forecast.humidity = safelyGet(forecastJson, Integer.class, "humidity", 0); forecast.humidity = safelyGet(forecastJson, Integer.class, "humidity", 0);
@ -114,13 +156,13 @@ public class GenericWeatherReceiver extends BroadcastReceiver {
} }
if (weatherJson.has("hourly")) { if (weatherJson.has("hourly")) {
JSONArray forecastArray = weatherJson.getJSONArray("hourly"); final JSONArray forecastArray = weatherJson.getJSONArray("hourly");
weatherSpec.hourly = new ArrayList<>(); weatherSpec.hourly = new ArrayList<>();
for (int i = 0, l = forecastArray.length(); i < l; i++) { for (int i = 0, l = forecastArray.length(); i < l; i++) {
JSONObject forecastJson = forecastArray.getJSONObject(i); final JSONObject forecastJson = forecastArray.getJSONObject(i);
WeatherSpec.Hourly forecast = new WeatherSpec.Hourly(); final WeatherSpec.Hourly forecast = new WeatherSpec.Hourly();
forecast.timestamp = safelyGet(forecastJson, Integer.class, "timestamp", 0); forecast.timestamp = safelyGet(forecastJson, Integer.class, "timestamp", 0);
forecast.temp = safelyGet(forecastJson, Integer.class, "temp", 0); forecast.temp = safelyGet(forecastJson, Integer.class, "temp", 0);
@ -135,15 +177,7 @@ public class GenericWeatherReceiver extends BroadcastReceiver {
} }
} }
LOG.info("Got generic weather for {}", weatherSpec.location); return weatherSpec;
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);
}
}
}
} }
private WeatherSpec.AirQuality toAirQuality(final JSONObject jsonObject) { private WeatherSpec.AirQuality toAirQuality(final JSONObject jsonObject) {

View File

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

View File

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

View File

@ -23,6 +23,9 @@ import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.widget.Toast; import android.widget.Toast;
import java.util.ArrayList;
import java.util.Collections;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.model.Weather; import nodomain.freeyourgadget.gadgetbridge.model.Weather;
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
@ -38,9 +41,10 @@ public class TinyWeatherForecastGermanyReceiver extends BroadcastReceiver {
try { try {
WeatherSpec weatherSpec = bundle.getParcelable("WeatherSpec"); WeatherSpec weatherSpec = bundle.getParcelable("WeatherSpec");
if (weatherSpec != null) { if (weatherSpec != null) {
Weather.getInstance().setWeatherSpec(weatherSpec); ArrayList<WeatherSpec> weatherSpecs = new ArrayList<>(Collections.singletonList(weatherSpec));
weatherSpec.timestamp = (int) (System.currentTimeMillis() / 1000); weatherSpec.timestamp = (int) (System.currentTimeMillis() / 1000);
GBApplication.deviceService().onSendWeather(weatherSpec); Weather.getInstance().setWeatherSpec(weatherSpecs);
GBApplication.deviceService().onSendWeather(weatherSpecs);
} }
} catch (Exception e) { } catch (Exception e) {
GB.toast("Gadgetbridge received broken or incompatible weather data", Toast.LENGTH_SHORT, GB.ERROR, 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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collections;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.model.Weather; import nodomain.freeyourgadget.gadgetbridge.model.Weather;
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
@ -53,8 +56,9 @@ public class WeatherNotificationReceiver extends BroadcastReceiver {
WeatherSpec weatherSpec = parcelableWeather2.weatherSpec; WeatherSpec weatherSpec = parcelableWeather2.weatherSpec;
LOG.info("weather in " + weatherSpec.location + " is " + weatherSpec.currentCondition + " (" + (weatherSpec.currentTemp - 273) + "°C)"); LOG.info("weather in " + weatherSpec.location + " is " + weatherSpec.currentCondition + " (" + (weatherSpec.currentTemp - 273) + "°C)");
Weather.getInstance().setWeatherSpec(weatherSpec); ArrayList<WeatherSpec> weatherSpecs = new ArrayList<>(Collections.singletonList(weatherSpec));
GBApplication.deviceService().onSendWeather(weatherSpec); Weather.getInstance().setWeatherSpec(weatherSpecs);
GBApplication.deviceService().onSendWeather(weatherSpecs);
} }
} }
} }

View File

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

View File

@ -17,6 +17,8 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */ along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.model; package nodomain.freeyourgadget.gadgetbridge.model;
import androidx.annotation.Nullable;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
@ -26,28 +28,47 @@ import org.slf4j.LoggerFactory;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream; import java.io.ObjectInputStream;
import java.io.ObjectOutputStream; import java.io.ObjectOutputStream;
import java.io.ObjectStreamException;
import java.util.ArrayList;
import java.util.List;
public class Weather { public class Weather {
private static final Logger LOG = LoggerFactory.getLogger(Weather.class); 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 JSONObject reconstructedOWMForecast = null;
private File cacheFile; private File cacheFile;
public WeatherSpec getWeatherSpec() { private Weather() {
return weatherSpec; // Use getInstance
} }
public void setWeatherSpec(WeatherSpec weatherSpec) { @Nullable
this.weatherSpec = weatherSpec; 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(); saveToCache();
} }
public JSONObject createReconstructedOWMWeatherReply() { public JSONObject createReconstructedOWMWeatherReply() {
final WeatherSpec weatherSpec = getWeatherSpec();
if (weatherSpec == null) { if (weatherSpec == null) {
return null; return null;
} }
@ -208,6 +229,7 @@ public class Weather {
} }
} }
public static int mapToYahooCondition(int openWeatherMapCondition) { public static int mapToYahooCondition(int openWeatherMapCondition) {
// openweathermap.org conditions: // openweathermap.org conditions:
// http://openweathermap.org/weather-conditions // http://openweathermap.org/weather-conditions
@ -1133,24 +1155,29 @@ public class Weather {
* @param enabled whether caching is enabled * @param enabled whether caching is enabled
*/ */
public void setCacheFile(final File cacheDir, final boolean enabled) { public void setCacheFile(final File cacheDir, final boolean enabled) {
// FIXME: Do not use serializable for this
cacheFile = new File(cacheDir, "weatherCache.bin"); cacheFile = new File(cacheDir, "weatherCache.bin");
if (enabled) { if (enabled) {
LOG.info("Setting weather cache file to {}", cacheFile.getPath()); LOG.info("Setting weather cache file to {}", cacheFile.getPath());
if (cacheFile.isFile() && weatherSpec == null) { if (cacheFile.isFile() && weatherSpecs.isEmpty()) {
try (final FileInputStream f = new FileInputStream(cacheFile)) { try (final ObjectInputStream o = new ObjectInputStream(new FileInputStream(cacheFile))) {
final ObjectInputStream o = new ObjectInputStream(f); final ArrayList<WeatherSpec> cachedSpecs = (ArrayList<WeatherSpec>) o.readObject();
weatherSpecs.addAll(cachedSpecs);
weatherSpec = (WeatherSpec) o.readObject(); } catch (final ObjectStreamException e) {
LOG.error("Failed to deserialize weather from cache", e);
o.close(); // 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) { } catch (final Throwable e) {
LOG.error("Failed to read weather from cache", e); LOG.error("Failed to read weather from cache", e);
weatherSpec = null; // keep cacheFile set - it's most likely an older version
cacheFile = null;
} }
} else if (weatherSpec != null) { } else if (!weatherSpecs.isEmpty()) {
saveToCache(); saveToCache();
} }
} else { } 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. * Save the current weather to cache, if a cache file is enabled and the weather is not null.
*/ */
public void saveToCache() { public void saveToCache() {
if (weatherSpec == null || cacheFile == null) { if (weatherSpecs.isEmpty() || cacheFile == null) {
return; return;
} }
LOG.info("Loading weather from cache {}", cacheFile.getPath()); LOG.info("Loading weather from cache {}", cacheFile.getPath());
try { try (ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream(cacheFile))) {
final FileOutputStream f = new FileOutputStream(cacheFile); o.writeObject(weatherSpecs);
final ObjectOutputStream o = new ObjectOutputStream(f);
o.writeObject(weatherSpec);
o.close();
f.close();
} catch (final Throwable e) { } catch (final Throwable e) {
LOG.error("Failed to save weather to cache", 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 sunSet; // unix epoch timestamp, in seconds
public int moonRise; // unix epoch timestamp, in seconds public int moonRise; // unix epoch timestamp, in seconds
public int moonSet; // 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 latitude;
public float longitude; public float longitude;
public int feelsLikeTemp; // kelvin 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 * If the device can receive weather information, this method can be
* overridden and implemented by the device support class. * overridden and implemented by the device support class. It's guaranteed
* @param weatherSpec weather information * that there is always at least one weatherSpec, with the first being the
* primary weather (not necessarily current location).
* @param weatherSpecs weather information
*/ */
@Override @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; break;
} }
case ACTION_SEND_WEATHER: { case ACTION_SEND_WEATHER: {
WeatherSpec weatherSpec = intent.getParcelableExtra(EXTRA_WEATHER); ArrayList<WeatherSpec> weatherSpecs = (ArrayList<WeatherSpec>) intent.getSerializableExtra(EXTRA_WEATHER);
if (weatherSpec != null) { if (weatherSpecs != null && !weatherSpecs.isEmpty()) {
deviceSupport.onSendWeather(weatherSpec); deviceSupport.onSendWeather(weatherSpecs);
} }
break; break;
} }

View File

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

View File

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

View File

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

View File

@ -584,7 +584,8 @@ public class CmfWatchProSupport extends AbstractBTLEDeviceSupport implements Cmf
} }
@Override @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)? // 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 // Each weather entry takes up 9 bytes
// There are 7 of those weather entries - 7*9 bytes // There are 7 of those weather entries - 7*9 bytes

View File

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

View File

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

View File

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

View File

@ -837,7 +837,9 @@ public class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTransferSer
} }
@Override @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. // 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. // When we have a weather update, set the default location to that location on the band.
// TODO: Support for multiple weather locations // TODO: Support for multiple weather locations

View File

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

View File

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

View File

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

View File

@ -1219,7 +1219,8 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport {
} }
@Override @Override
public void onSendWeather(WeatherSpec weatherSpec) { public void onSendWeather(ArrayList<WeatherSpec> weatherSpecs) {
WeatherSpec weatherSpec = weatherSpecs.get(0);
try { try {
TransactionBuilder builder = performInitialized("setWeather"); TransactionBuilder builder = performInitialized("setWeather");
int currentTemp; 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 * Analyse and decode sensor data from ADXL362 accelerometer
* @param value to decode * @param value to decode

View File

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

View File

@ -1027,7 +1027,9 @@ public class PineTimeJFSupport extends AbstractBTLEDeviceSupport implements DfuL
} }
@Override @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)) { if (this.firmwareVersionMajor < 1 || (this.firmwareVersionMajor == 1 && this.firmwareVersionMinor <= 7)) {
// Not supported // Not supported
return; return;

View File

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

View File

@ -372,9 +372,4 @@ public class SoFlowSupport extends AbstractBTLEDeviceSupport {
GB.toast("Error setting configuration", Toast.LENGTH_LONG, GB.ERROR, e); 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 @Override
public void onSendWeather(WeatherSpec weatherSpec) { public void onSendWeather(ArrayList<WeatherSpec> weatherSpecs) {
WeatherSpec weatherSpec = weatherSpecs.get(0);
if(weatherSpec.forecasts.size() < 4) return; if(weatherSpec.forecasts.size() < 4) return;
ArrayList<WeatherDay> days = new ArrayList<>(); ArrayList<WeatherDay> days = new ArrayList<>();

View File

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

View File

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

View File

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

View File

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

View File

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