diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiBRCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiBRCoordinator.java index 677a88a89..ef27fc992 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiBRCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiBRCoordinator.java @@ -144,7 +144,7 @@ public abstract class HuaweiBRCoordinator extends AbstractBLClassicDeviceCoordin @Override public boolean supportsWeather() { - return false; + return huaweiCoordinator.supportsWeather(); } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiCoordinator.java index fe6c36c05..6e0d34856 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiCoordinator.java @@ -336,6 +336,30 @@ public class HuaweiCoordinator { return supportsCommandForService(0x0c, 0x01); } + public boolean supportsWeather() { + return supportsCommandForService(0x0f, 0x01); + } + + public boolean supportsWeatherUnit() { + return supportsCommandForService(0x0f, 0x05); + } + + public boolean supportsWeatherExtended() { + return supportsCommandForService(0x0f, 0x05); + } + + public boolean supportsWeatherForecasts() { + return supportsCommandForService(0x0f, 0x08); + } + + public boolean supportsWeatherMoonRiseSet() { + return supportsCommandForService(0x0f, 0x0a); + } + + public boolean supportsWeatherTides() { + return supportsCommandForService(0x0f, 0x0b); + } + public boolean supportsWorkouts() { return supportsCommandForService(0x17, 0x01); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiLECoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiLECoordinator.java index 782159327..1ea56d7fc 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiLECoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiLECoordinator.java @@ -144,7 +144,7 @@ public abstract class HuaweiLECoordinator extends AbstractBLEDeviceCoordinator i @Override public boolean supportsWeather() { - return false; + return huaweiCoordinator.supportsWeather(); } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiPacket.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiPacket.java index 624a2b739..7527380f8 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiPacket.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiPacket.java @@ -32,6 +32,7 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Alarms; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.AccountRelated; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Calls; +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Workout; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.DeviceConfig; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FindPhone; @@ -475,6 +476,20 @@ public class HuaweiPacket { return new FindPhone.Response(paramsProvider).fromPacket(this); this.isEncrypted = this.attemptDecrypt(); // Helps with debugging return this; + case Weather.id: + switch (this.commandId) { + case Weather.WeatherSupport.id: + return new Weather.WeatherSupport.Response(paramsProvider).fromPacket(this); + case Weather.WeatherExtendedSupport.id: + return new Weather.WeatherExtendedSupport.Response(paramsProvider).fromPacket(this); + case Weather.WeatherStart.id: + return new Weather.WeatherStart.Response(paramsProvider).fromPacket(this); + case Weather.WeatherSunMoonSupport.id: + return new Weather.WeatherSunMoonSupport.Response(paramsProvider).fromPacket(this); + default: + this.isEncrypted = this.attemptDecrypt(); // Helps with debugging + return this; + } case Workout.id: switch (this.commandId) { case Workout.WorkoutCount.id: diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Weather.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Weather.java new file mode 100644 index 000000000..53866dfe8 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Weather.java @@ -0,0 +1,275 @@ +/* Copyright (C) 2024 Martin.JM + + 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 . */ +package nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets; + +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket; +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV; + +public class Weather { + public static final byte id = 0x0f; + + public static class Settings { + // WeatherSupport + public boolean weatherSupported = false; + public boolean windSupported = false; + public boolean pm25Supported = false; + public boolean temperatureSupported = false; + public boolean locationNameSupported = false; + public boolean currentTemperatureSupported = false; + public boolean unitSupported = false; + public boolean airQualityIndexSupported = false; + + // WeatherExtendedSupport + public boolean timeSupported = false; + public boolean sourceSupported = false; + public boolean weatherIconSupported = false; + + // WeatherSunMoonSupport + public boolean sunRiseSetSupported = false; + public boolean moonPhaseSupported = false; + } + + public static class CurrentWeatherRequest extends HuaweiPacket { + public static final byte id = 0x01; + + public CurrentWeatherRequest( + ParamsProvider paramsProvider, + Settings settings, + Byte windDirection, + Byte windSpeed, + Byte lowestTemperature, + Byte highestTemperature, + Short pm25, // TODO: might be float? + String locationName, + Byte currentTemperature, + Byte temperatureUnit, + Short airQualityIndex, + Integer observationTime, + String sourceName + ) { + super(paramsProvider); + + this.serviceId = Weather.id; + this.commandId = id; + this.tlv = new HuaweiTLV(); + + if (lowestTemperature != null && highestTemperature != null && settings.temperatureSupported) { + this.tlv.put(0x85, new HuaweiTLV() + .put(0x06, lowestTemperature) + .put(0x07, highestTemperature) + ); + } + if (windDirection != null && windSpeed != null && settings.windSupported) + this.tlv.put(0x03, (short) ((((short) windDirection) << 8) | ((short) windSpeed))); + if (pm25 != null && settings.pm25Supported) + this.tlv.put(0x04, pm25); + if (locationName != null && settings.locationNameSupported) + this.tlv.put(0x08, locationName); + if (currentTemperature != null && settings.currentTemperatureSupported) + this.tlv.put(0x09, currentTemperature); + if (temperatureUnit != null && settings.unitSupported) + this.tlv.put(0x0a, temperatureUnit); + if (airQualityIndex != null && settings.airQualityIndexSupported) + this.tlv.put(0x0b, airQualityIndex); + if (observationTime != null && settings.timeSupported) + this.tlv.put(0x0c, observationTime); + if (sourceName != null && settings.sourceSupported) + this.tlv.put(0x0e, sourceName); + this.tlv.put(0x0f, (byte) 0); + + this.isEncrypted = true; + this.complete = true; + } + } + + public static class WeatherSupport { + public static final byte id = 0x02; + + public static class Request extends HuaweiPacket { + public Request(ParamsProvider paramsProvider) { + super(paramsProvider); + + this.serviceId = Weather.id; + this.commandId = id; + this.tlv = new HuaweiTLV().put(0x01); + this.isEncrypted = true; + this.complete = true; + } + } + + public static class Response extends HuaweiPacket { + public byte supportedBitmap = 0; + + public boolean weatherSupported = false; + public boolean windSupported = false; + public boolean pm25Supported = false; + public boolean temperatureSupported = false; + public boolean locationNameSupported = false; + public boolean currentTemperatureSupported = false; + public boolean unitSupported = false; + public boolean airQualityIndexSupported = false; + + public Response(ParamsProvider paramsProvider) { + super(paramsProvider); + this.serviceId = Weather.id; + this.commandId = id; + } + + @Override + public void parseTlv() throws ParseException { + if (!this.tlv.contains(0x01)) + throw new MissingTagException(0x01); + this.supportedBitmap = this.tlv.getByte(0x01); + + this.weatherSupported = (this.supportedBitmap & 0x01) != 0; + this.windSupported = (this.supportedBitmap & 0x02) != 0; + this.pm25Supported = (this.supportedBitmap & 0x04) != 0; + this.temperatureSupported = (this.supportedBitmap & 0x08) != 0; + this.locationNameSupported = (this.supportedBitmap & 0x10) != 0; + this.currentTemperatureSupported = (this.supportedBitmap & 0x20) != 0; + this.unitSupported = (this.supportedBitmap & 0x40) != 0; + this.airQualityIndexSupported = (this.supportedBitmap & 0x80) != 0; + } + } + } + + public static class WeatherUnitRequest extends HuaweiPacket { + public static final byte id = 0x05; + + public WeatherUnitRequest(ParamsProvider paramsProvider) { + super(paramsProvider); + + this.serviceId = Weather.id; + this.commandId = id; + this.tlv = new HuaweiTLV().put(0x01, (byte) 0); // TODO: find out what unit is what + this.isEncrypted = true; + this.complete = true; + } + } + + public static class WeatherExtendedSupport { + public static final byte id = 0x06; + + public static class Request extends HuaweiPacket { + public Request(ParamsProvider paramsProvider) { + super(paramsProvider); + + this.serviceId = Weather.id; + this.commandId = id; + this.tlv = new HuaweiTLV().put(0x01); + this.isEncrypted = true; + this.complete = true; + } + } + + public static class Response extends HuaweiPacket { + public short supportedBitmap = 0; + + public boolean timeSupported = false; + public boolean sourceSupported = false; + public boolean weatherIconSupported = false; + + public Response(ParamsProvider paramsProvider) { + super(paramsProvider); + this.serviceId = Weather.id; + this.commandId = id; + } + + @Override + public void parseTlv() throws ParseException { + if (!this.tlv.contains(0x01)) + throw new MissingTagException(0x01); + this.supportedBitmap = this.tlv.getShort(0x01); + + this.timeSupported = (this.supportedBitmap & 0x01) != 0; + this.sourceSupported = (this.supportedBitmap & 0x02) != 0; + this.weatherIconSupported = (this.supportedBitmap & 0x04) != 0; + } + } + } + + public static class WeatherStart { + public static final byte id = 0x09; + + public static class Request extends HuaweiPacket { + + public Request(ParamsProvider paramsProvider) { + super(paramsProvider); + + this.serviceId = Weather.id; + this.commandId = id; + this.tlv = new HuaweiTLV().put(0x01, (byte) 0x03); // TODO: find out what this means + this.isEncrypted = true; + this.complete = true; + } + } + + public static class Response extends HuaweiPacket { + public boolean success = false; + + public Response(ParamsProvider paramsProvider) { + super(paramsProvider); + this.serviceId = Weather.id; + this.commandId = id; + } + + @Override + public void parseTlv() throws ParseException { + this.success = this.tlv.getInteger(0x7f) == 0x000186A0; + } + } + } + + public static class WeatherSunMoonSupport { + public static final byte id = 0x0a; + + public static class Request extends HuaweiPacket { + public Request(ParamsProvider paramsProvider) { + super(paramsProvider); + + this.serviceId = Weather.id; + this.commandId = id; + this.tlv = new HuaweiTLV().put(0x01); + this.isEncrypted = true; + this.complete = true; + } + } + + public static class Response extends HuaweiPacket { + public byte supportedBitmap = 0; + + public boolean sunRiseSetSupported = false; + public boolean moonPhaseSupported = false; + + public Response(ParamsProvider paramsProvider) { + super(paramsProvider); + this.serviceId = Weather.id; + this.commandId = id; + } + + @Override + public void parseTlv() throws ParseException { + if (!this.tlv.contains(0x01)) + throw new MissingTagException(0x01); + this.supportedBitmap = this.tlv.getByte(0x01); + + this.sunRiseSetSupported = (this.supportedBitmap & 0x01) != 0; + this.moonPhaseSupported = (this.supportedBitmap & 0x02) != 0; + } + } + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/AsynchronousResponse.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/AsynchronousResponse.java index 64d15b647..85590477a 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/AsynchronousResponse.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/AsynchronousResponse.java @@ -47,9 +47,11 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.DeviceConfig; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FindPhone; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Menstrual; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.MusicControl; +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather; import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.Request; import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetPhoneInfoRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendMenstrualModifyTimeRequest; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendWeatherDeviceRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetMusicStatusRequest; import nodomain.freeyourgadget.gadgetbridge.util.GB; @@ -95,6 +97,7 @@ public class AsynchronousResponse { handleCallControls(response); handlePhoneInfo(response); handleMenstrualModifyTime(response); + handleWeatherCheck(response); } catch (Request.ResponseParseException e) { LOG.error("Response parse exception", e); } @@ -378,4 +381,18 @@ public class AsynchronousResponse { } } + + private void handleWeatherCheck(HuaweiPacket response) { + if (response.serviceId == Weather.id && response.commandId == 0x04) { + // Send back ok + try { + SendWeatherDeviceRequest sendWeatherDeviceRequest = new SendWeatherDeviceRequest(this.support); + sendWeatherDeviceRequest.doPerform(); + } catch (IOException e) { + e.printStackTrace(); // TODO: Change + } + + // TODO: send back weather? + } + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiBRSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiBRSupport.java index 2a40a9f00..2fed2850e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiBRSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiBRSupport.java @@ -25,6 +25,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; +import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; import nodomain.freeyourgadget.gadgetbridge.service.btbr.AbstractBTBRDeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.btbr.TransactionBuilder; @@ -117,4 +118,9 @@ public class HuaweiBRSupport extends AbstractBTBRDeviceSupport { if (!start) supportProvider.onStopFindPhone(); } + + @Override + public void onSendWeather(WeatherSpec weatherSpec) { + supportProvider.onSendWeather(weatherSpec); + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiLESupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiLESupport.java index f2b920b8f..4c81445a0 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiLESupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiLESupport.java @@ -29,6 +29,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; +import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService; import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; @@ -125,4 +126,9 @@ public class HuaweiLESupport extends AbstractBTLEDeviceSupport { if (!start) supportProvider.onStopFindPhone(); } + + @Override + public void onSendWeather(WeatherSpec weatherSpec) { + supportProvider.onSendWeather(weatherSpec); + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiSupportProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiSupportProvider.java index 5f8cb6a27..2323245d8 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiSupportProvider.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiSupportProvider.java @@ -50,6 +50,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCoordinatorSupp import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCrypto; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiSampleProvider; +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Workout; import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst; import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary; @@ -73,11 +74,18 @@ import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes; +import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetEventAlarmList; import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetNotificationConstraintsRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetSmartAlarmList; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendWeatherCurrentRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendNotifyHeartRateCapabilityRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendNotifyRestHeartRateCapabilityRequest; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendWeatherExtendedSupportRequest; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendWeatherStartRequest; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendWeatherSunMoonSupportRequest; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendWeatherSupportRequest; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendWeatherUnitRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetAutomaticHeartrateRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetAutomaticSpoRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetDisconnectNotification; @@ -156,6 +164,8 @@ public class HuaweiSupportProvider { private MusicStateSpec musicStateSpec = null; private MusicSpec musicSpec = null; + private Weather.Settings weatherSettings = null; + private final HuaweiPacket.ParamsProvider paramsProvider = new HuaweiPacket.ParamsProvider(); protected ResponseManager responseManager = new ResponseManager(this); @@ -1637,4 +1647,75 @@ public class HuaweiSupportProvider { LOG.error("Failed to set language settings request", e); } } + + public void onSendWeather(WeatherSpec weatherSpec) { + if (weatherSettings != null && weatherSettings.weatherSupported) { + try { + SendWeatherCurrentRequest sendWeatherCurrentRequest = new SendWeatherCurrentRequest( + this, + weatherSettings, + weatherSpec + ); + sendWeatherCurrentRequest.doPerform(); + } catch (IOException e) { + // TODO: Use translatable string + GB.toast(context, "Failed to send weather", Toast.LENGTH_SHORT, GB.ERROR, e); + LOG.error("Failed to send weather", e); + } + } else { + // Initialize weather settings + if (!getHuaweiCoordinator().supportsWeather()) { + // TODO: exception? + return; + } + + this.weatherSettings = new Weather.Settings(); + + RequestCallback requestCallback = new RequestCallback(this) { + @Override + public void call() { + this.support.weatherSettings.weatherSupported = true; + this.support.onSendWeather(weatherSpec); + } + }; + + SendWeatherStartRequest weatherStartRequest = new SendWeatherStartRequest(this); + weatherStartRequest.setFinalizeReq(requestCallback); + Request lastRequest = weatherStartRequest; + + if (getHuaweiCoordinator().supportsWeatherUnit()) { + SendWeatherUnitRequest weatherUnitRequest = new SendWeatherUnitRequest(this); + weatherUnitRequest.setFinalizeReq(requestCallback); + lastRequest.nextRequest(weatherUnitRequest); + lastRequest = weatherUnitRequest; + } + + SendWeatherSupportRequest weatherSupportRequest = new SendWeatherSupportRequest(this, weatherSettings); + weatherSupportRequest.setFinalizeReq(requestCallback); + lastRequest.nextRequest(weatherSupportRequest); + lastRequest = weatherSupportRequest; + + if (getHuaweiCoordinator().supportsWeatherExtended()) { + SendWeatherExtendedSupportRequest weatherExtendedSupportRequest = new SendWeatherExtendedSupportRequest(this, weatherSettings); + weatherExtendedSupportRequest.setFinalizeReq(requestCallback); + lastRequest.nextRequest(weatherExtendedSupportRequest); + lastRequest = weatherExtendedSupportRequest; + } + + if (getHuaweiCoordinator().supportsWeatherMoonRiseSet()) { + SendWeatherSunMoonSupportRequest weatherSunMoonSupportRequest = new SendWeatherSunMoonSupportRequest(this, weatherSettings); + weatherSunMoonSupportRequest.setFinalizeReq(requestCallback); + lastRequest.nextRequest(weatherSunMoonSupportRequest); +// lastRequest = weatherSunMoonSupportRequest; + } + + try { + weatherStartRequest.doPerform(); + } catch (IOException e) { + // TODO: Use translatable string + GB.toast(context, "Failed to send initialize weather requests", Toast.LENGTH_SHORT, GB.ERROR, e); + LOG.error("Failed to send initialize weather requests", e); + } + } + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherCurrentRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherCurrentRequest.java new file mode 100644 index 000000000..cd41c2b7b --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherCurrentRequest.java @@ -0,0 +1,67 @@ +/* Copyright (C) 2024 Martin.JM + + 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 . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests; + +import java.util.List; + +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket; +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather; +import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider; + +public class SendWeatherCurrentRequest extends Request { + Weather.Settings settings; + WeatherSpec weatherSpec; + + public SendWeatherCurrentRequest(HuaweiSupportProvider support, Weather.Settings settings, WeatherSpec weatherSpec) { + super(support); + this.serviceId = Weather.id; + this.commandId = Weather.CurrentWeatherRequest.id; + this.settings = settings; + this.weatherSpec = weatherSpec; + } + + @Override + protected List createRequest() throws RequestCreationException { + try { + // TODO: support multiple units + Short pm25 = null; + Short aqi = null; + if (weatherSpec.airQuality != null) { + pm25 = (short) weatherSpec.airQuality.pm25; // TODO: does this work? + aqi = (short) weatherSpec.airQuality.aqi; + } + return new Weather.CurrentWeatherRequest( + this.paramsProvider, + settings, + (byte) weatherSpec.windDirection, + (byte) weatherSpec.windSpeed, + (byte) (weatherSpec.todayMinTemp - 273), + (byte) (weatherSpec.todayMaxTemp - 273), + pm25, + weatherSpec.location, + (byte) (weatherSpec.currentTemp - 273), + (byte) 0, + aqi, + weatherSpec.timestamp, + "Gadgetbridge" + ).serialize(); + } catch (HuaweiPacket.CryptoException e) { + throw new RequestCreationException(e); + } + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherDeviceRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherDeviceRequest.java new file mode 100644 index 000000000..64a3c2ce0 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherDeviceRequest.java @@ -0,0 +1,50 @@ +/* Copyright (C) 2024 Martin.JM + + 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 . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests; + +import java.util.List; + +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket; +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV; +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather; +import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider; + +public class SendWeatherDeviceRequest extends Request { + WeatherSpec weatherSpec; + + public SendWeatherDeviceRequest(HuaweiSupportProvider support) { + super(support); + this.serviceId = Weather.id; + this.commandId = 0x04; + } + + @Override + protected List createRequest() throws RequestCreationException { + try { + // TODO: move this to the weather packet class + HuaweiPacket response = new HuaweiPacket(supportProvider.getParamsProvider()); + response.serviceId = this.serviceId; + response.commandId = this.commandId; + response.setTlv(new HuaweiTLV().put(0x01, 0x186a0)); + response.setEncryption(false); + return response.serialize(); + } catch (HuaweiPacket.CryptoException e) { + throw new RequestCreationException(e); + } + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherExtendedSupportRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherExtendedSupportRequest.java new file mode 100644 index 000000000..cd750904e --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherExtendedSupportRequest.java @@ -0,0 +1,59 @@ +/* Copyright (C) 2024 Martin.JM + + 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 . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket; +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider; + +public class SendWeatherExtendedSupportRequest extends Request { + private static final Logger LOG = LoggerFactory.getLogger(SendWeatherExtendedSupportRequest.class); + + private Weather.Settings settings; + + public SendWeatherExtendedSupportRequest(HuaweiSupportProvider support, Weather.Settings settings) { + super(support); + this.serviceId = Weather.id; + this.commandId = Weather.WeatherExtendedSupport.id; + this.settings = settings; + } + + @Override + protected List createRequest() throws RequestCreationException { + try { + return new Weather.WeatherExtendedSupport.Request(this.paramsProvider).serialize(); + } catch (HuaweiPacket.CryptoException e) { + throw new RequestCreationException(e); + } + } + + @Override + protected void processResponse() throws ResponseParseException { + if (receivedPacket instanceof Weather.WeatherExtendedSupport.Response) { + this.settings.timeSupported = ((Weather.WeatherExtendedSupport.Response) receivedPacket).timeSupported; + this.settings.sourceSupported = ((Weather.WeatherExtendedSupport.Response) receivedPacket).sourceSupported; + this.settings.weatherIconSupported = ((Weather.WeatherExtendedSupport.Response) receivedPacket).weatherIconSupported; + } else { + LOG.error("WeatherExtendedSupport response is not of type WeatherExtendedSupport response"); + } + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherStartRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherStartRequest.java new file mode 100644 index 000000000..cc40c854f --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherStartRequest.java @@ -0,0 +1,65 @@ +/* Copyright (C) 2024 Martin.JM + + 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 . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests; + +import android.widget.Toast; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket; +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider; +import nodomain.freeyourgadget.gadgetbridge.util.GB; + +public class SendWeatherStartRequest extends Request { + private static final Logger LOG = LoggerFactory.getLogger(SendWeatherStartRequest.class); + + public int response = -1; + + public SendWeatherStartRequest(HuaweiSupportProvider support) { + super(support); + this.serviceId = Weather.id; + this.commandId = Weather.WeatherStart.id; + } + + @Override + protected List createRequest() throws RequestCreationException { + try { + return new Weather.WeatherStart.Request(this.paramsProvider).serialize(); + } catch (HuaweiPacket.CryptoException e) { + throw new RequestCreationException(e); + } + } + + @Override + protected void processResponse() throws ResponseParseException { + if (receivedPacket instanceof Weather.WeatherStart.Response) { + if (!((Weather.WeatherStart.Response) receivedPacket).success) { + this.stopChain(); + GB.toast(supportProvider.getContext(), "Received non-ok status for WeatherStart response", Toast.LENGTH_SHORT, GB.INFO); + LOG.info("Received non-ok status for WeatherStart response"); + } + } else { + this.stopChain(); + GB.toast(supportProvider.getContext(), "WeatherStart response is not of type WeatherStart response", Toast.LENGTH_SHORT, GB.ERROR); + LOG.error("WeatherStart response is not of type WeatherStart response"); + } + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherSunMoonSupportRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherSunMoonSupportRequest.java new file mode 100644 index 000000000..ee6884222 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherSunMoonSupportRequest.java @@ -0,0 +1,58 @@ +/* Copyright (C) 2024 Martin.JM + + 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 . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket; +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider; + +public class SendWeatherSunMoonSupportRequest extends Request { + private static final Logger LOG = LoggerFactory.getLogger(SendWeatherSunMoonSupportRequest.class); + + private Weather.Settings settings; + + public SendWeatherSunMoonSupportRequest(HuaweiSupportProvider support, Weather.Settings settings) { + super(support); + this.serviceId = Weather.id; + this.commandId = Weather.WeatherSunMoonSupport.id; + this.settings = settings; + } + + @Override + protected List createRequest() throws RequestCreationException { + try { + return new Weather.WeatherSunMoonSupport.Request(this.paramsProvider).serialize(); + } catch (HuaweiPacket.CryptoException e) { + throw new RequestCreationException(e); + } + } + + @Override + protected void processResponse() throws ResponseParseException { + if (receivedPacket instanceof Weather.WeatherSunMoonSupport.Response) { + this.settings.sunRiseSetSupported = ((Weather.WeatherSunMoonSupport.Response) receivedPacket).sunRiseSetSupported; + this.settings.moonPhaseSupported = ((Weather.WeatherSunMoonSupport.Response) receivedPacket).moonPhaseSupported; + } else { + LOG.error("WeatherExtendedSupport response is not of type WeatherExtendedSupport response"); + } + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherSupportRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherSupportRequest.java new file mode 100644 index 000000000..f06ca9219 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherSupportRequest.java @@ -0,0 +1,64 @@ +/* Copyright (C) 2024 Martin.JM + + 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 . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket; +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider; + +public class SendWeatherSupportRequest extends Request { + private static final Logger LOG = LoggerFactory.getLogger(SendWeatherSupportRequest.class); + + private Weather.Settings settings; + + public SendWeatherSupportRequest(HuaweiSupportProvider support, Weather.Settings settings) { + super(support); + this.serviceId = Weather.id; + this.commandId = Weather.WeatherSupport.id; + this.settings = settings; + } + + @Override + protected List createRequest() throws RequestCreationException { + try { + return new Weather.WeatherSupport.Request(this.paramsProvider).serialize(); + } catch (HuaweiPacket.CryptoException e) { + throw new RequestCreationException(e); + } + } + + @Override + protected void processResponse() throws ResponseParseException { + if (receivedPacket instanceof Weather.WeatherSupport.Response) { + this.settings.weatherSupported = ((Weather.WeatherSupport.Response) receivedPacket).weatherSupported; + this.settings.windSupported = ((Weather.WeatherSupport.Response) receivedPacket).windSupported; + this.settings.pm25Supported = ((Weather.WeatherSupport.Response) receivedPacket).pm25Supported; + this.settings.temperatureSupported = ((Weather.WeatherSupport.Response) receivedPacket).temperatureSupported; + this.settings.locationNameSupported = ((Weather.WeatherSupport.Response) receivedPacket).locationNameSupported; + this.settings.currentTemperatureSupported = ((Weather.WeatherSupport.Response) receivedPacket).currentTemperatureSupported; + this.settings.unitSupported = ((Weather.WeatherSupport.Response) receivedPacket).unitSupported; + this.settings.airQualityIndexSupported = ((Weather.WeatherSupport.Response) receivedPacket).airQualityIndexSupported; + } else { + LOG.error("WeatherSupport response is not of type WeatherSupport response"); + } + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherUnitRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherUnitRequest.java new file mode 100644 index 000000000..647b88b4e --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendWeatherUnitRequest.java @@ -0,0 +1,41 @@ +/* Copyright (C) 2024 Martin.JM + + 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 . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests; + +import java.util.List; + +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket; +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider; + +public class SendWeatherUnitRequest extends Request { + + public SendWeatherUnitRequest(HuaweiSupportProvider support) { + super(support); + this.serviceId = Weather.id; + this.commandId = Weather.WeatherUnitRequest.id; + } + + @Override + protected List createRequest() throws RequestCreationException { + try { + return new Weather.WeatherUnitRequest(this.paramsProvider).serialize(); + } catch (HuaweiPacket.CryptoException e) { + throw new RequestCreationException(e); + } + } +}