From cd59511aadd180f18fc72072fc741f36f42ac09c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Rebelo?= Date: Sat, 29 Oct 2022 12:03:25 +0100 Subject: [PATCH] Zepp OS: Refactor config, fix health on GTR 3 and GTS 3 The config refactor in addf7ff6a broke health settings on GTR3 and GTS3 - GTS 3 and GTR 3 health configs use protocol v1. The only difference seems to be that the steps goal is a SHORT instead of an INT. - It needs a refactoring from the ground up to better handle different versions, but this is enough to get the GTR 3 and GTS 3 working. --- .../devices/huami/Huami2021Coordinator.java | 10 +- .../devices/huami/Huami2021Service.java | 11 - .../huami/Huami2021SettingsCustomizer.java | 130 +++++----- .../devices/huami/Huami2021Handler.java | 2 +- .../devices/huami/Huami2021Support.java | 201 ++++----------- .../service/devices/huami/HuamiSupport.java | 2 +- .../huami/operations/InitOperation2021.java | 2 +- .../huami/zeppos/AbstractZeppOsService.java | 4 + .../services/ZeppOsConfigService.java} | 235 +++++++++++++++--- .../services/ZeppOsFileUploadService.java | 18 +- 10 files changed, 333 insertions(+), 282 deletions(-) rename app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/{Huami2021Config.java => zeppos/services/ZeppOsConfigService.java} (87%) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/Huami2021Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/Huami2021Coordinator.java index aa6553276..c4b869d49 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/Huami2021Coordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/Huami2021Coordinator.java @@ -37,7 +37,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.Device; import nodomain.freeyourgadget.gadgetbridge.entities.HuamiExtendedActivitySampleDao; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser; -import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiLanguageType; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiVibrationPatternNotificationType; @@ -318,14 +318,14 @@ public abstract class Huami2021Coordinator extends HuamiCoordinator { } public boolean hasGps(final GBDevice device) { - return supportsConfig(device, Huami2021Config.ConfigArg.WORKOUT_GPS_PRESET); + return supportsConfig(device, ZeppOsConfigService.ConfigArg.WORKOUT_GPS_PRESET); } public boolean supportsAutoBrightness(final GBDevice device) { - return supportsConfig(device, Huami2021Config.ConfigArg.SCREEN_AUTO_BRIGHTNESS); + return supportsConfig(device, ZeppOsConfigService.ConfigArg.SCREEN_AUTO_BRIGHTNESS); } - private boolean supportsConfig(final GBDevice device, final Huami2021Config.ConfigArg config) { - return Huami2021Config.deviceHasConfig(getPrefs(device), config); + private boolean supportsConfig(final GBDevice device, final ZeppOsConfigService.ConfigArg config) { + return ZeppOsConfigService.deviceHasConfig(getPrefs(device), config); } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/Huami2021Service.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/Huami2021Service.java index 3b9bb69bd..9bb84832a 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/Huami2021Service.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/Huami2021Service.java @@ -22,7 +22,6 @@ public class Huami2021Service { */ public static final short CHUNKED2021_ENDPOINT_HTTP = 0x0001; public static final short CHUNKED2021_ENDPOINT_CALENDAR = 0x0007; - public static final short CHUNKED2021_ENDPOINT_CONFIG = 0x000a; public static final short CHUNKED2021_ENDPOINT_WEATHER = 0x000e; public static final short CHUNKED2021_ENDPOINT_ALARMS = 0x000f; public static final short CHUNKED2021_ENDPOINT_CANNED_MESSAGES = 0x0013; @@ -221,16 +220,6 @@ public class Huami2021Service { public static final byte MUSIC_BUTTON_VOLUME_UP = 0x05; public static final byte MUSIC_BUTTON_VOLUME_DOWN = 0x06; - /** - * Config, for {@link Huami2021Service#CHUNKED2021_ENDPOINT_CONFIG}. - */ - public static final byte CONFIG_CMD_CAPABILITIES_REQUEST = 0x01; - public static final byte CONFIG_CMD_CAPABILITIES_RESPONSE = 0x02; - public static final byte CONFIG_CMD_REQUEST = 0x03; - public static final byte CONFIG_CMD_RESPONSE = 0x04; - public static final byte CONFIG_CMD_SET = 0x05; - public static final byte CONFIG_CMD_ACK = 0x06; - /** * Reminders, for {@link Huami2021Service#CHUNKED2021_ENDPOINT_REMINDERS}. */ diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/Huami2021SettingsCustomizer.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/Huami2021SettingsCustomizer.java index 047793526..12e26211f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/Huami2021SettingsCustomizer.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/Huami2021SettingsCustomizer.java @@ -37,7 +37,7 @@ import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSett import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsHandler; import nodomain.freeyourgadget.gadgetbridge.capabilities.GpsCapability; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; -import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiVibrationPatternNotificationType; import nodomain.freeyourgadget.gadgetbridge.util.Prefs; @@ -57,11 +57,11 @@ public class Huami2021SettingsCustomizer extends HuamiSettingsCustomizer { removeUnsupportedElementsFromListPreference(HuamiConst.PREF_SHORTCUTS_SORTABLE, handler, prefs); removeUnsupportedElementsFromListPreference(HuamiConst.PREF_CONTROL_CENTER_SORTABLE, handler, prefs); - for (final Huami2021Config.ConfigArg config : Huami2021Config.ConfigArg.values()) { + for (final ZeppOsConfigService.ConfigArg config : ZeppOsConfigService.ConfigArg.values()) { if (config.getPrefKey() == null) { continue; } - switch (config.getConfigType()) { + switch (config.getConfigType(null)) { case BYTE: case BYTE_LIST: case STRING_LIST: @@ -79,96 +79,96 @@ public class Huami2021SettingsCustomizer extends HuamiSettingsCustomizer { } // Hide all config groups that may not be mapped directly to a preference - final Map> configScreens = new HashMap>() {{ + final Map> configScreens = new HashMap>() {{ put(DeviceSettingsPreferenceConst.PREF_SCREEN_NIGHT_MODE, Arrays.asList( - Huami2021Config.ConfigArg.NIGHT_MODE_MODE, - Huami2021Config.ConfigArg.NIGHT_MODE_SCHEDULED_START, - Huami2021Config.ConfigArg.NIGHT_MODE_SCHEDULED_END + ZeppOsConfigService.ConfigArg.NIGHT_MODE_MODE, + ZeppOsConfigService.ConfigArg.NIGHT_MODE_SCHEDULED_START, + ZeppOsConfigService.ConfigArg.NIGHT_MODE_SCHEDULED_END )); put(DeviceSettingsPreferenceConst.PREF_SCREEN_SLEEP_MODE, Arrays.asList( - Huami2021Config.ConfigArg.SLEEP_MODE_SLEEP_SCREEN, - Huami2021Config.ConfigArg.SLEEP_MODE_SMART_ENABLE + ZeppOsConfigService.ConfigArg.SLEEP_MODE_SLEEP_SCREEN, + ZeppOsConfigService.ConfigArg.SLEEP_MODE_SMART_ENABLE )); put(DeviceSettingsPreferenceConst.PREF_SCREEN_LIFT_WRIST, Arrays.asList( - Huami2021Config.ConfigArg.LIFT_WRIST_MODE, - Huami2021Config.ConfigArg.LIFT_WRIST_SCHEDULED_START, - Huami2021Config.ConfigArg.LIFT_WRIST_SCHEDULED_END, - Huami2021Config.ConfigArg.LIFT_WRIST_RESPONSE_SENSITIVITY + ZeppOsConfigService.ConfigArg.LIFT_WRIST_MODE, + ZeppOsConfigService.ConfigArg.LIFT_WRIST_SCHEDULED_START, + ZeppOsConfigService.ConfigArg.LIFT_WRIST_SCHEDULED_END, + ZeppOsConfigService.ConfigArg.LIFT_WRIST_RESPONSE_SENSITIVITY )); put(DeviceSettingsPreferenceConst.PREF_SCREEN_PASSWORD, Arrays.asList( - Huami2021Config.ConfigArg.PASSWORD_ENABLED, - Huami2021Config.ConfigArg.PASSWORD_TEXT + ZeppOsConfigService.ConfigArg.PASSWORD_ENABLED, + ZeppOsConfigService.ConfigArg.PASSWORD_TEXT )); put(DeviceSettingsPreferenceConst.PREF_SCREEN_ALWAYS_ON_DISPLAY, Arrays.asList( - Huami2021Config.ConfigArg.ALWAYS_ON_DISPLAY_MODE, - Huami2021Config.ConfigArg.ALWAYS_ON_DISPLAY_SCHEDULED_START, - Huami2021Config.ConfigArg.ALWAYS_ON_DISPLAY_SCHEDULED_END, - Huami2021Config.ConfigArg.ALWAYS_ON_DISPLAY_FOLLOW_WATCHFACE, - Huami2021Config.ConfigArg.ALWAYS_ON_DISPLAY_STYLE + ZeppOsConfigService.ConfigArg.ALWAYS_ON_DISPLAY_MODE, + ZeppOsConfigService.ConfigArg.ALWAYS_ON_DISPLAY_SCHEDULED_START, + ZeppOsConfigService.ConfigArg.ALWAYS_ON_DISPLAY_SCHEDULED_END, + ZeppOsConfigService.ConfigArg.ALWAYS_ON_DISPLAY_FOLLOW_WATCHFACE, + ZeppOsConfigService.ConfigArg.ALWAYS_ON_DISPLAY_STYLE )); put(DeviceSettingsPreferenceConst.PREF_SCREEN_AUTO_BRIGHTNESS, Arrays.asList( - Huami2021Config.ConfigArg.SCREEN_AUTO_BRIGHTNESS + ZeppOsConfigService.ConfigArg.SCREEN_AUTO_BRIGHTNESS )); put(DeviceSettingsPreferenceConst.PREF_SCREEN_HEARTRATE_MONITORING, Arrays.asList( - Huami2021Config.ConfigArg.HEART_RATE_ALL_DAY_MONITORING, - Huami2021Config.ConfigArg.HEART_RATE_HIGH_ALERTS, - Huami2021Config.ConfigArg.HEART_RATE_LOW_ALERTS, - Huami2021Config.ConfigArg.HEART_RATE_ACTIVITY_MONITORING, - Huami2021Config.ConfigArg.SLEEP_HIGH_ACCURACY_MONITORING, - Huami2021Config.ConfigArg.SLEEP_BREATHING_QUALITY_MONITORING, - Huami2021Config.ConfigArg.STRESS_MONITORING, - Huami2021Config.ConfigArg.STRESS_RELAXATION_REMINDER, - Huami2021Config.ConfigArg.SPO2_ALL_DAY_MONITORING, - Huami2021Config.ConfigArg.SPO2_LOW_ALERT + ZeppOsConfigService.ConfigArg.HEART_RATE_ALL_DAY_MONITORING, + ZeppOsConfigService.ConfigArg.HEART_RATE_HIGH_ALERTS, + ZeppOsConfigService.ConfigArg.HEART_RATE_LOW_ALERTS, + ZeppOsConfigService.ConfigArg.HEART_RATE_ACTIVITY_MONITORING, + ZeppOsConfigService.ConfigArg.SLEEP_HIGH_ACCURACY_MONITORING, + ZeppOsConfigService.ConfigArg.SLEEP_BREATHING_QUALITY_MONITORING, + ZeppOsConfigService.ConfigArg.STRESS_MONITORING, + ZeppOsConfigService.ConfigArg.STRESS_RELAXATION_REMINDER, + ZeppOsConfigService.ConfigArg.SPO2_ALL_DAY_MONITORING, + ZeppOsConfigService.ConfigArg.SPO2_LOW_ALERT )); put(DeviceSettingsPreferenceConst.PREF_SCREEN_INACTIVITY_EXTENDED, Arrays.asList( - Huami2021Config.ConfigArg.INACTIVITY_WARNINGS_ENABLED, - Huami2021Config.ConfigArg.INACTIVITY_WARNINGS_SCHEDULED_START, - Huami2021Config.ConfigArg.INACTIVITY_WARNINGS_SCHEDULED_END, - Huami2021Config.ConfigArg.INACTIVITY_WARNINGS_DND_ENABLED, - Huami2021Config.ConfigArg.INACTIVITY_WARNINGS_DND_SCHEDULED_START, - Huami2021Config.ConfigArg.INACTIVITY_WARNINGS_DND_SCHEDULED_END + ZeppOsConfigService.ConfigArg.INACTIVITY_WARNINGS_ENABLED, + ZeppOsConfigService.ConfigArg.INACTIVITY_WARNINGS_SCHEDULED_START, + ZeppOsConfigService.ConfigArg.INACTIVITY_WARNINGS_SCHEDULED_END, + ZeppOsConfigService.ConfigArg.INACTIVITY_WARNINGS_DND_ENABLED, + ZeppOsConfigService.ConfigArg.INACTIVITY_WARNINGS_DND_SCHEDULED_START, + ZeppOsConfigService.ConfigArg.INACTIVITY_WARNINGS_DND_SCHEDULED_END )); put(DeviceSettingsPreferenceConst.PREF_HEADER_GPS, Arrays.asList( - Huami2021Config.ConfigArg.WORKOUT_GPS_PRESET, - Huami2021Config.ConfigArg.WORKOUT_GPS_BAND, - Huami2021Config.ConfigArg.WORKOUT_GPS_COMBINATION, - Huami2021Config.ConfigArg.WORKOUT_GPS_SATELLITE_SEARCH, - Huami2021Config.ConfigArg.WORKOUT_AGPS_EXPIRY_REMINDER_ENABLED, - Huami2021Config.ConfigArg.WORKOUT_AGPS_EXPIRY_REMINDER_TIME + ZeppOsConfigService.ConfigArg.WORKOUT_GPS_PRESET, + ZeppOsConfigService.ConfigArg.WORKOUT_GPS_BAND, + ZeppOsConfigService.ConfigArg.WORKOUT_GPS_COMBINATION, + ZeppOsConfigService.ConfigArg.WORKOUT_GPS_SATELLITE_SEARCH, + ZeppOsConfigService.ConfigArg.WORKOUT_AGPS_EXPIRY_REMINDER_ENABLED, + ZeppOsConfigService.ConfigArg.WORKOUT_AGPS_EXPIRY_REMINDER_TIME )); put(DeviceSettingsPreferenceConst.PREF_SCREEN_SOUND_AND_VIBRATION, Arrays.asList( - Huami2021Config.ConfigArg.VOLUME, - Huami2021Config.ConfigArg.CROWN_VIBRATION, - Huami2021Config.ConfigArg.ALERT_TONE, - Huami2021Config.ConfigArg.COVER_TO_MUTE, - Huami2021Config.ConfigArg.VIBRATE_FOR_ALERT, - Huami2021Config.ConfigArg.TEXT_TO_SPEECH + ZeppOsConfigService.ConfigArg.VOLUME, + ZeppOsConfigService.ConfigArg.CROWN_VIBRATION, + ZeppOsConfigService.ConfigArg.ALERT_TONE, + ZeppOsConfigService.ConfigArg.COVER_TO_MUTE, + ZeppOsConfigService.ConfigArg.VIBRATE_FOR_ALERT, + ZeppOsConfigService.ConfigArg.TEXT_TO_SPEECH )); put(DeviceSettingsPreferenceConst.PREF_SCREEN_DO_NOT_DISTURB, Arrays.asList( - Huami2021Config.ConfigArg.DND_MODE, - Huami2021Config.ConfigArg.DND_SCHEDULED_START, - Huami2021Config.ConfigArg.DND_SCHEDULED_END + ZeppOsConfigService.ConfigArg.DND_MODE, + ZeppOsConfigService.ConfigArg.DND_SCHEDULED_START, + ZeppOsConfigService.ConfigArg.DND_SCHEDULED_END )); put(DeviceSettingsPreferenceConst.PREF_HEADER_WORKOUT_DETECTION, Arrays.asList( - Huami2021Config.ConfigArg.WORKOUT_DETECTION_CATEGORY, - Huami2021Config.ConfigArg.WORKOUT_DETECTION_ALERT, - Huami2021Config.ConfigArg.WORKOUT_DETECTION_SENSITIVITY + ZeppOsConfigService.ConfigArg.WORKOUT_DETECTION_CATEGORY, + ZeppOsConfigService.ConfigArg.WORKOUT_DETECTION_ALERT, + ZeppOsConfigService.ConfigArg.WORKOUT_DETECTION_SENSITIVITY )); put(DeviceSettingsPreferenceConst.PREF_SCREEN_OFFLINE_VOICE, Arrays.asList( - Huami2021Config.ConfigArg.OFFLINE_VOICE_RESPOND_TURN_WRIST, - Huami2021Config.ConfigArg.OFFLINE_VOICE_RESPOND_SCREEN_ON, - Huami2021Config.ConfigArg.OFFLINE_VOICE_RESPONSE_DURING_SCREEN_LIGHTING, - Huami2021Config.ConfigArg.OFFLINE_VOICE_LANGUAGE + ZeppOsConfigService.ConfigArg.OFFLINE_VOICE_RESPOND_TURN_WRIST, + ZeppOsConfigService.ConfigArg.OFFLINE_VOICE_RESPOND_SCREEN_ON, + ZeppOsConfigService.ConfigArg.OFFLINE_VOICE_RESPONSE_DURING_SCREEN_LIGHTING, + ZeppOsConfigService.ConfigArg.OFFLINE_VOICE_LANGUAGE )); }}; - for (final Map.Entry> configScreen : configScreens.entrySet()) { + for (final Map.Entry> configScreen : configScreens.entrySet()) { hidePrefIfNoConfigSupported( handler, prefs, configScreen.getKey(), - configScreen.getValue().toArray(new Huami2021Config.ConfigArg[0]) + configScreen.getValue().toArray(new ZeppOsConfigService.ConfigArg[0]) ); } @@ -303,7 +303,7 @@ public class Huami2021SettingsCustomizer extends HuamiSettingsCustomizer { } // Get the list of possible values for this preference, as reported by the band - final List possibleValues = prefs.getList(Huami2021Config.getPrefPossibleValuesKey(prefKey), null); + final List possibleValues = prefs.getList(ZeppOsConfigService.getPrefPossibleValuesKey(prefKey), null); if (possibleValues == null || possibleValues.isEmpty()) { // The band hasn't reported this setting, so we don't know the possible values. // Hide it @@ -360,14 +360,14 @@ public class Huami2021SettingsCustomizer extends HuamiSettingsCustomizer { private void hidePrefIfNoConfigSupported(final DeviceSpecificSettingsHandler handler, final Prefs prefs, final String prefToHide, - final Huami2021Config.ConfigArg... configs) { + final ZeppOsConfigService.ConfigArg... configs) { final Preference pref = handler.findPreference(prefToHide); if (pref == null) { return; } - for (final Huami2021Config.ConfigArg config : configs) { - if (Huami2021Config.deviceHasConfig(prefs, config)) { + for (final ZeppOsConfigService.ConfigArg config : configs) { + if (ZeppOsConfigService.deviceHasConfig(prefs, config)) { // This preference is supported, don't hide return; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021Handler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021Handler.java index bd0b2af00..154e37cd6 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021Handler.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021Handler.java @@ -17,5 +17,5 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.huami; public interface Huami2021Handler { - void handle2021Payload(int type, byte[] payload); + void handle2021Payload(short type, byte[] payload); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021Support.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021Support.java index 687ad1496..a2c364712 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021Support.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021Support.java @@ -17,32 +17,27 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.huami; import static org.apache.commons.lang3.ArrayUtils.subarray; -import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_LANGUAGE; -import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_LANGUAGE_AUTO; -import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_TIMEFORMAT; -import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_TIMEFORMAT_AUTO; import static nodomain.freeyourgadget.gadgetbridge.devices.huami.Huami2021Service.*; import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.SUCCESS; import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_NAME; import static nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions.fromUint16; import static nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions.fromUint8; import static nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions.mapTimeZone; -import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.FITNESS_GOAL_CALORIES; -import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.FITNESS_GOAL_FAT_BURN_TIME; -import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.FITNESS_GOAL_SLEEP; -import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.FITNESS_GOAL_STANDING_TIME; -import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.FITNESS_GOAL_STEPS; -import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.FITNESS_GOAL_WEIGHT; -import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.HEART_RATE_ALL_DAY_MONITORING; -import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.LANGUAGE; -import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.LANGUAGE_FOLLOW_PHONE; -import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.PASSWORD_ENABLED; -import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.PASSWORD_TEXT; -import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.SLEEP_HIGH_ACCURACY_MONITORING; -import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.TEMPERATURE_UNIT; -import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.TIME_FORMAT; -import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigGroup; -import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigSetter; +import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService.ConfigArg.FITNESS_GOAL_CALORIES; +import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService.ConfigArg.FITNESS_GOAL_FAT_BURN_TIME; +import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService.ConfigArg.FITNESS_GOAL_SLEEP; +import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService.ConfigArg.FITNESS_GOAL_STANDING_TIME; +import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService.ConfigArg.FITNESS_GOAL_STEPS; +import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService.ConfigArg.FITNESS_GOAL_WEIGHT; +import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService.ConfigArg.HEART_RATE_ALL_DAY_MONITORING; +import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService.ConfigArg.LANGUAGE; +import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService.ConfigArg.LANGUAGE_FOLLOW_PHONE; +import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService.ConfigArg.PASSWORD_ENABLED; +import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService.ConfigArg.PASSWORD_TEXT; +import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService.ConfigArg.SLEEP_HIGH_ACCURACY_MONITORING; +import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService.ConfigArg.TEMPERATURE_UNIT; +import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService.ConfigArg.TIME_FORMAT; +import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService.ConfigGroup; import android.Manifest; import android.content.Intent; @@ -120,6 +115,8 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.Fet import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.HuamiFetchDebugLogsOperation; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.UpdateFirmwareOperation; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.UpdateFirmwareOperation2021; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsFileUploadService; import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils; import nodomain.freeyourgadget.gadgetbridge.util.BitmapUtil; @@ -144,6 +141,11 @@ public abstract class Huami2021Support extends HuamiSupport { // Services private final ZeppOsFileUploadService fileUploadService = new ZeppOsFileUploadService(this); + private final ZeppOsConfigService configService = new ZeppOsConfigService(this); + private final Map mServiceMap = new HashMap() {{ + put(fileUploadService.getEndpoint(), fileUploadService); + put(configService.getEndpoint(), configService); + }}; public Huami2021Support() { this(LOG); @@ -174,15 +176,15 @@ public abstract class Huami2021Support extends HuamiSupport { @Override public void onSendConfiguration(final String config) { - final ConfigSetter configSetter = new ConfigSetter(); + final ZeppOsConfigService.ConfigSetter configSetter = configService.newSetter(); final Prefs prefs = getDevicePrefs(); try { - if (Huami2021Config.setConfig(prefs, config, configSetter)) { + if (configService.setConfig(prefs, config, configSetter)) { // If the ConfigSetter was able to set the config, just write it and return final TransactionBuilder builder; builder = performInitialized("Sending configuration for option: " + config); - configSetter.write(this, builder); + configSetter.write(builder); builder.queue(getQueue()); return; @@ -453,14 +455,14 @@ public abstract class Huami2021Support extends HuamiSupport { final int goalFatBurnTime = GBApplication.getPrefs().getInt(ActivityUser.PREF_USER_GOAL_FAT_BURN_TIME_MINUTES, ActivityUser.defaultUserFatBurnTimeMinutes); LOG.info("Setting Fitness Goals to steps={}, calories={}, sleep={}, weight={}, standingTime={}, fatBurn={}", goalSteps, goalCalories, goalSleep, goalWeight, goalStandingTime, goalFatBurnTime); - new ConfigSetter() + configService.newSetter() .setInt(FITNESS_GOAL_STEPS, goalSteps) .setShort(FITNESS_GOAL_CALORIES, (short) goalCalories) .setShort(FITNESS_GOAL_SLEEP, (short) (goalSleep * 60)) .setShort(FITNESS_GOAL_WEIGHT, (short) goalWeight) .setShort(FITNESS_GOAL_STANDING_TIME, (short) (goalStandingTime)) .setShort(FITNESS_GOAL_FAT_BURN_TIME, (short) goalFatBurnTime) - .write(this, builder); + .write(builder); return this; } @@ -529,10 +531,10 @@ public abstract class Huami2021Support extends HuamiSupport { return this; } - new ConfigSetter() + configService.newSetter() .setBoolean(PASSWORD_ENABLED, passwordEnabled) .setString(PASSWORD_TEXT, password) - .write(this, builder); + .write(builder); return this; } @@ -897,9 +899,9 @@ public abstract class Huami2021Support extends HuamiSupport { protected Huami2021Support setHeartrateSleepSupport(final TransactionBuilder builder) { final boolean enableHrSleepSupport = MiBandCoordinator.getHeartrateSleepSupport(gbDevice.getAddress()); - new ConfigSetter() + configService.newSetter() .setBoolean(SLEEP_HIGH_ACCURACY_MONITORING, enableHrSleepSupport) - .write(this, builder); + .write(builder); return this; } @@ -955,10 +957,10 @@ public abstract class Huami2021Support extends HuamiSupport { } @Override - protected HuamiSupport setHeartrateMeasurementInterval(TransactionBuilder builder, int minutes) { - new ConfigSetter() + protected HuamiSupport setHeartrateMeasurementInterval(final TransactionBuilder builder, final int minutes) { + configService.newSetter() .setByte(HEART_RATE_ALL_DAY_MONITORING, (byte) minutes) - .write(this, builder); + .write(builder); return this; } @@ -1043,9 +1045,9 @@ public abstract class Huami2021Support extends HuamiSupport { timeFormatByte = 0x00; } - new ConfigSetter() + configService.newSetter() .setByte(TIME_FORMAT, timeFormatByte) - .write(this, builder); + .write(builder); return this; } @@ -1057,7 +1059,7 @@ public abstract class Huami2021Support extends HuamiSupport { setDisplayItems2021( builder, DISPLAY_ITEMS_MENU, - new ArrayList<>(prefs.getList(Huami2021Config.getPrefPossibleValuesKey(HuamiConst.PREF_DISPLAY_ITEMS_SORTABLE), Collections.emptyList())), + new ArrayList<>(prefs.getList(ZeppOsConfigService.getPrefPossibleValuesKey(HuamiConst.PREF_DISPLAY_ITEMS_SORTABLE), Collections.emptyList())), new ArrayList<>(prefs.getList(HuamiConst.PREF_DISPLAY_ITEMS_SORTABLE, Collections.emptyList())) ); return this; @@ -1070,7 +1072,7 @@ public abstract class Huami2021Support extends HuamiSupport { setDisplayItems2021( builder, DISPLAY_ITEMS_SHORTCUTS, - new ArrayList<>(prefs.getList(Huami2021Config.getPrefPossibleValuesKey(HuamiConst.PREF_SHORTCUTS_SORTABLE), Collections.emptyList())), + new ArrayList<>(prefs.getList(ZeppOsConfigService.getPrefPossibleValuesKey(HuamiConst.PREF_SHORTCUTS_SORTABLE), Collections.emptyList())), new ArrayList<>(prefs.getList(HuamiConst.PREF_SHORTCUTS_SORTABLE, Collections.emptyList())) ); return this; @@ -1082,7 +1084,7 @@ public abstract class Huami2021Support extends HuamiSupport { setDisplayItems2021( builder, DISPLAY_ITEMS_CONTROL_CENTER, - new ArrayList<>(prefs.getList(Huami2021Config.getPrefPossibleValuesKey(HuamiConst.PREF_CONTROL_CENTER_SORTABLE), Collections.emptyList())), + new ArrayList<>(prefs.getList(ZeppOsConfigService.getPrefPossibleValuesKey(HuamiConst.PREF_CONTROL_CENTER_SORTABLE), Collections.emptyList())), new ArrayList<>(prefs.getList(HuamiConst.PREF_CONTROL_CENTER_SORTABLE, Collections.emptyList())) ); return this; @@ -1226,9 +1228,9 @@ public abstract class Huami2021Support extends HuamiSupport { break; } - new ConfigSetter() + configService.newSetter() .setByte(TEMPERATURE_UNIT, unitByte) - .write(this, builder); + .write(builder); return this; } @@ -1240,10 +1242,10 @@ public abstract class Huami2021Support extends HuamiSupport { LOG.info("Setting device language to {}", localeString); - new ConfigSetter() + configService.newSetter() .setByte(LANGUAGE, getLanguageId()) .setBoolean(LANGUAGE_FOLLOW_PHONE, localeString.equals("auto")) - .write(this, builder); + .write(builder); return this; } @@ -1361,12 +1363,8 @@ public abstract class Huami2021Support extends HuamiSupport { LOG.info("2021 phase3Initialize..."); setUserInfo(builder); - setFitnessGoal(builder); - - for (final ConfigGroup configGroup : ConfigGroup.values()) { - requestConfig(builder, configGroup); - } + configService.requestAllConfigs(builder); requestCapabilityReminders(builder); fileUploadService.requestCapability(builder); @@ -1416,7 +1414,7 @@ public abstract class Huami2021Support extends HuamiSupport { } @Override - public void handle2021Payload(final int type, final byte[] payload) { + public void handle2021Payload(final short type, final byte[] payload) { if (payload == null || payload.length == 0) { LOG.warn("Empty or null payload for {}", String.format("0x%04x", type)); return; @@ -1424,6 +1422,11 @@ public abstract class Huami2021Support extends HuamiSupport { LOG.debug("Got 2021 payload for {}: {}", String.format("0x%04x", type), GB.hexdump(payload)); + if (mServiceMap.containsKey(type)) { + mServiceMap.get(type).handlePayload(payload); + return; + } + switch (type) { case CHUNKED2021_ENDPOINT_ALARMS: handle2021Alarms(payload); @@ -1437,12 +1440,6 @@ public abstract class Huami2021Support extends HuamiSupport { case CHUNKED2021_ENDPOINT_COMPAT: LOG.warn("Unexpected compat payload {}", GB.hexdump(payload)); return; - case CHUNKED2021_ENDPOINT_CONFIG: - handle2021Config(payload); - return; - case ZeppOsFileUploadService.ENDPOINT: - fileUploadService.handlePayload(payload); - return; case CHUNKED2021_ENDPOINT_WEATHER: handle2021Weather(payload); return; @@ -1660,102 +1657,6 @@ public abstract class Huami2021Support extends HuamiSupport { // TODO update database? } - private void requestConfig(final TransactionBuilder builder, - final ConfigGroup config, - final boolean includeConstraints, - final List args) { - final ByteArrayOutputStream baos = new ByteArrayOutputStream(); - - baos.write(CONFIG_CMD_REQUEST); - baos.write(bool(includeConstraints)); - baos.write(config.getValue()); - baos.write(args.size()); - for (final Huami2021Config.ConfigArg arg : args) { - baos.write(arg.getCode()); - } - - writeToChunked2021(builder, CHUNKED2021_ENDPOINT_CONFIG, baos.toByteArray(), true); - } - - private void requestConfig(final TransactionBuilder builder, final ConfigGroup config) { - requestConfig(builder, config, true, Huami2021Config.ConfigArg.getAllArgsForConfigGroup(config)); - } - - protected void handle2021Config(final byte[] payload) { - switch (payload[0]) { - case CONFIG_CMD_ACK: - LOG.info("Configuration ACK, status = {}", payload[1]); - return; - - case CONFIG_CMD_RESPONSE: - if (payload[1] != 1) { - LOG.warn("Configuration response not success: {}", payload[1]); - return; - } - - handle2021ConfigResponse(payload); - return; - default: - LOG.warn("Unexpected configuration payload byte {}", String.format("0x%02x", payload[0])); - } - } - - private void handle2021ConfigResponse(final byte[] payload) { - final ConfigGroup configGroup = ConfigGroup.fromValue(payload[2]); - if (configGroup == null) { - LOG.warn("Unknown config type {}", String.format("0x%02x", payload[2])); - return; - } - - if (configGroup.getVersion() != payload[3]) { - LOG.warn("Unexpected next byte {} for {}", String.format("0x%02x", payload[3]), configGroup); - return; - } - - final boolean includesConstraints = payload[4] == 0x01; - - int numConfigs = payload[5] & 0xff; - - LOG.info("Got {} configs for {}", numConfigs, configGroup); - - final Map prefs = new Huami2021Config.ConfigParser(configGroup, includesConstraints) - .parse(numConfigs, subarray(payload, 6, payload.length)); - - if (prefs == null) { - return; - } - - final GBDeviceEventUpdatePreferences eventUpdatePreferences = new GBDeviceEventUpdatePreferences(prefs); - evaluateGBDeviceEvent(eventUpdatePreferences); - - if (isInitialized()) { - final TransactionBuilder builder; - boolean hasAutoConfigsToSend = false; - - try { - builder = performInitialized("set auto band configs"); - } catch (final Exception e) { - LOG.error("Failed to set auto band configs", e); - return; - } - - if (prefs.containsKey(PREF_LANGUAGE) && prefs.get(PREF_LANGUAGE).equals(PREF_LANGUAGE_AUTO)) { - // Band is reporting automatic language, we need to send the actual language - setLanguage(builder); - hasAutoConfigsToSend = true; - } - if (prefs.containsKey(PREF_TIMEFORMAT) && prefs.get(PREF_TIMEFORMAT).equals(PREF_TIMEFORMAT_AUTO)) { - // Band is reporting automatic time format, we need to send the actual time format - setTimeFormat(builder); - hasAutoConfigsToSend = true; - } - - if (hasAutoConfigsToSend) { - builder.queue(getQueue()); - } - } - } - protected void handle2021Workout(final byte[] payload) { switch (payload[0]) { case WORKOUT_CMD_APP_OPEN: @@ -1838,7 +1739,7 @@ public abstract class Huami2021Support extends HuamiSupport { LOG.error("Unknown display items type {}", String.format("0x%x", payload[1])); return; } - final String allScreensPrefKey = Huami2021Config.getPrefPossibleValuesKey(prefKey); + final String allScreensPrefKey = ZeppOsConfigService.getPrefPossibleValuesKey(prefKey); final boolean menuHasMoreSection; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java index c11091049..7d9e31492 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java @@ -4111,7 +4111,7 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements } @Override - public void handle2021Payload(int type, byte[] payload) { + public void handle2021Payload(short type, byte[] payload) { if (type == Huami2021Service.CHUNKED2021_ENDPOINT_COMPAT) { LOG.info("got configuration data"); type = 0; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/InitOperation2021.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/InitOperation2021.java index cce9e4b6b..fd5357b04 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/InitOperation2021.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/InitOperation2021.java @@ -132,7 +132,7 @@ public class InitOperation2021 extends InitOperation implements Huami2021Handler } @Override - public void handle2021Payload(final int type, final byte[] payload) { + public void handle2021Payload(final short type, final byte[] payload) { if (type != Huami2021Service.CHUNKED2021_ENDPOINT_AUTH) { this.huamiSupport.handle2021Payload(type, payload); return; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/AbstractZeppOsService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/AbstractZeppOsService.java index 1c11886ee..ccdec4453 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/AbstractZeppOsService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/AbstractZeppOsService.java @@ -30,6 +30,10 @@ public abstract class AbstractZeppOsService { public abstract boolean isEncrypted(); public abstract void handlePayload(final byte[] payload); + protected Huami2021Support getSupport() { + return mSupport; + } + protected void write(final String taskName, final byte[] data) { this.mSupport.writeToChunked2021(taskName, getEndpoint(), data, isEncrypted()); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021Config.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsConfigService.java similarity index 87% rename from app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021Config.java rename to app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsConfigService.java index 5a2743c6b..acaf991ee 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021Config.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsConfigService.java @@ -14,13 +14,12 @@ 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.huami; +package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services; +import static org.apache.commons.lang3.ArrayUtils.subarray; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.*; import static nodomain.freeyourgadget.gadgetbridge.capabilities.password.PasswordCapabilityImpl.PREF_PASSWORD; import static nodomain.freeyourgadget.gadgetbridge.capabilities.password.PasswordCapabilityImpl.PREF_PASSWORD_ENABLED; -import static nodomain.freeyourgadget.gadgetbridge.devices.huami.Huami2021Service.CHUNKED2021_ENDPOINT_CONFIG; -import static nodomain.freeyourgadget.gadgetbridge.devices.huami.Huami2021Service.CONFIG_CMD_SET; import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_EXPOSE_HR_THIRDPARTY; import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_NIGHT_MODE; import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_NIGHT_MODE_END; @@ -59,6 +58,7 @@ import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity; import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst; import nodomain.freeyourgadget.gadgetbridge.capabilities.GpsCapability; import nodomain.freeyourgadget.gadgetbridge.capabilities.WorkoutDetectionCapability; +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences; import nodomain.freeyourgadget.gadgetbridge.devices.huami.ActivateDisplayOnLift; import nodomain.freeyourgadget.gadgetbridge.devices.huami.ActivateDisplayOnLiftSensitivity; import nodomain.freeyourgadget.gadgetbridge.devices.huami.AlwaysOnDisplay; @@ -66,13 +66,143 @@ import nodomain.freeyourgadget.gadgetbridge.devices.miband.DoNotDisturb; import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst; import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions; import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021MenuType; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Support; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiLanguageType; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService; import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.MapUtils; import nodomain.freeyourgadget.gadgetbridge.util.Prefs; import nodomain.freeyourgadget.gadgetbridge.util.StringUtils; -public class Huami2021Config { - private static final Logger LOG = LoggerFactory.getLogger(Huami2021Config.class); +public class ZeppOsConfigService extends AbstractZeppOsService { + private static final Logger LOG = LoggerFactory.getLogger(ZeppOsConfigService.class); + + private static final short ENDPOINT = 0x000a; + + private static final byte CMD_CAPABILITIES_REQUEST = 0x01; + private static final byte CMD_CAPABILITIES_RESPONSE = 0x02; + private static final byte CMD_REQUEST = 0x03; + private static final byte CMD_RESPONSE = 0x04; + private static final byte CMD_SET = 0x05; + private static final byte CMD_ACK = 0x06; + + private final Map mGroupVersions = new HashMap<>(); + + public ZeppOsConfigService(final Huami2021Support support) { + super(support); + } + + @Override + public short getEndpoint() { + return ENDPOINT; + } + + @Override + public boolean isEncrypted() { + return true; + } + + @Override + public void handlePayload(final byte[] payload) { + switch (payload[0]) { + case CMD_ACK: + LOG.info("Configuration ACK, status = {}", payload[1]); + return; + + case CMD_RESPONSE: + if (payload[1] != 1) { + LOG.warn("Configuration response not success: {}", payload[1]); + return; + } + + handle2021ConfigResponse(payload); + return; + default: + LOG.warn("Unexpected configuration payload byte {}", String.format("0x%02x", payload[0])); + } + } + + private boolean sentFitnessGoal = false; + + private void handle2021ConfigResponse(final byte[] payload) { + final ConfigGroup configGroup = ConfigGroup.fromValue(payload[2]); + if (configGroup == null) { + LOG.warn("Unknown config type {}", String.format("0x%02x", payload[2])); + return; + } + + final byte version = payload[3]; + if (configGroup.getVersion() != version) { + // Special case for HEALTH, where we actually support version 1 as well + // TODO: Support multiple versions in a cleaner way... + if (!(configGroup == ConfigGroup.HEALTH && configGroup.getVersion() == 1)) { + LOG.warn("Unexpected version {} for {}", String.format("0x%02x", version), configGroup); + return; + } + } + mGroupVersions.put(configGroup, version); + + final boolean includesConstraints = payload[4] == 0x01; + + int numConfigs = payload[5] & 0xff; + + LOG.info("Got {} configs for {} version {}", numConfigs, configGroup, version); + + final Map prefs = new ZeppOsConfigService.ConfigParser(configGroup, includesConstraints) + .parse(numConfigs, subarray(payload, 6, payload.length)); + + if (prefs == null) { + return; + } + + final GBDeviceEventUpdatePreferences eventUpdatePreferences = new GBDeviceEventUpdatePreferences(prefs); + getSupport().evaluateGBDeviceEvent(eventUpdatePreferences); + + if (getSupport().getDevice().isInitialized()) { + if (prefs.containsKey(PREF_LANGUAGE) && prefs.get(PREF_LANGUAGE).equals(PREF_LANGUAGE_AUTO)) { + // Band is reporting automatic language, we need to send the actual language + getSupport().onSendConfiguration(PREF_LANGUAGE); + } + if (prefs.containsKey(PREF_TIMEFORMAT) && prefs.get(PREF_TIMEFORMAT).equals(PREF_TIMEFORMAT_AUTO)) { + // Band is reporting automatic time format, we need to send the actual time format + getSupport().onSendConfiguration(PREF_TIMEFORMAT); + } + } + + if (configGroup == ConfigGroup.HEALTH && !sentFitnessGoal) { + // We need to send the fitness goal after we got the protocol version + getSupport().onSendConfiguration(PREF_USER_FITNESS_GOAL); + sentFitnessGoal = true; + } + } + + public void requestAllConfigs(final TransactionBuilder builder) { + for (final ConfigGroup configGroup : ConfigGroup.values()) { + requestConfig(builder, configGroup); + } + } + + public void requestConfig(final TransactionBuilder builder, final ConfigGroup config) { + requestConfig(builder, config, true, ZeppOsConfigService.ConfigArg.getAllArgsForConfigGroup(config)); + } + + public void requestConfig(final TransactionBuilder builder, + final ConfigGroup config, + final boolean includeConstraints, + final List args) { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + baos.write(CMD_REQUEST); + baos.write((byte) (includeConstraints ? 1 : 0)); + baos.write(config.getValue()); + baos.write(args.size()); + for (final ZeppOsConfigService.ConfigArg arg : args) { + baos.write(arg.getCode()); + } + + write(builder, baos.toByteArray()); + } public enum ConfigGroup { DISPLAY(0x01, 0x02), @@ -201,7 +331,7 @@ public class Huami2021Config { SPO2_ALL_DAY_MONITORING(ConfigGroup.HEALTH, ConfigType.BOOL, 0x31, PREF_SPO2_ALL_DAY_MONITORING), SPO2_LOW_ALERT(ConfigGroup.HEALTH, ConfigType.BYTE, 0x32, PREF_SPO2_LOW_ALERT_THRESHOLD), FITNESS_GOAL_NOTIFICATION(ConfigGroup.HEALTH, ConfigType.BOOL, 0x51, PREF_USER_FITNESS_GOAL_NOTIFICATION), - FITNESS_GOAL_STEPS(ConfigGroup.HEALTH, ConfigType.INT, 0x52, null), // TODO needs to be handled globally + FITNESS_GOAL_STEPS(ConfigGroup.HEALTH, null /* Special case, handled below */, 0x52, null), // TODO needs to be handled globally FITNESS_GOAL_CALORIES(ConfigGroup.HEALTH, ConfigType.SHORT, 0x53, null), // TODO needs to be handled globally FITNESS_GOAL_WEIGHT(ConfigGroup.HEALTH, ConfigType.SHORT, 0x54, null), // TODO needs to be handled globally FITNESS_GOAL_SLEEP(ConfigGroup.HEALTH, ConfigType.SHORT, 0x55, null), // TODO needs to be handled globally @@ -263,7 +393,27 @@ public class Huami2021Config { return configGroup; } - public ConfigType getConfigType() { + public ConfigType getConfigType(@Nullable final Map groupVersions) { + if (this == FITNESS_GOAL_STEPS) { + if (groupVersions == null) { + return ConfigType.INT; + } + + final Byte groupVersion = groupVersions.get(getConfigGroup()); + if (groupVersion == null) { + LOG.error("Version for {} is not known", getConfigGroup()); + return null; + } + + switch (groupVersion) { + case 0x01: + return ConfigType.SHORT; + case 0x02: + default: + return ConfigType.INT; + } + } + return configType; } @@ -276,7 +426,7 @@ public class Huami2021Config { } public static ConfigArg fromCode(final ConfigGroup configGroup, final byte code) { - for (final Huami2021Config.ConfigArg arg : values()) { + for (final ZeppOsConfigService.ConfigArg arg : values()) { if (arg.getConfigGroup().equals(configGroup) && arg.getCode() == code) { return arg; } @@ -285,8 +435,8 @@ public class Huami2021Config { } public static List getAllArgsForConfigGroup(final ConfigGroup configGroup) { - final List configArgs = new ArrayList<>(); - for (final Huami2021Config.ConfigArg arg : values()) { + final List configArgs = new ArrayList<>(); + for (final ZeppOsConfigService.ConfigArg arg : values()) { if (arg.getConfigGroup().equals(configGroup)) { configArgs.add(arg); } @@ -317,14 +467,14 @@ public class Huami2021Config { * * @return true if the {@link ConfigSetter} was updated for this preference key */ - public static boolean setConfig(final Prefs prefs, final String key, final ConfigSetter setter) { + public boolean setConfig(final Prefs prefs, final String key, final ConfigSetter setter) { final ConfigArg configArg = PREF_TO_CONFIG.get(key); if (configArg == null) { LOG.error("Unknown pref key {}", key); return false; } - switch (configArg.getConfigType()) { + switch (configArg.getConfigType(mGroupVersions)) { case BOOL: setter.setBoolean(configArg, prefs.getBoolean(key, false)); return true; @@ -470,11 +620,15 @@ public class Huami2021Config { return String.format(Locale.ROOT, "huami_2021_known_config_%s", pref.name()); } - public static boolean deviceHasConfig(final Prefs devicePrefs, final Huami2021Config.ConfigArg config) { + public static boolean deviceHasConfig(final Prefs devicePrefs, final ZeppOsConfigService.ConfigArg config) { return devicePrefs.getBoolean(getPrefKnownConfig(config), false); } - public static class ConfigSetter { + public ConfigSetter newSetter() { + return new ConfigSetter(); + } + + public class ConfigSetter { private final Map> arguments = new LinkedHashMap<>(); public ConfigSetter() { @@ -568,13 +722,13 @@ public class Huami2021Config { final Map configArgMap = arguments.get(configGroup); try { - baos.write(CONFIG_CMD_SET); + baos.write(CMD_SET); baos.write(configGroup.getValue()); baos.write(configGroup.getVersion()); baos.write(0x00); // ? baos.write(configArgMap.size()); for (final Map.Entry arg : configArgMap.entrySet()) { - final ConfigType configType = arg.getKey().getConfigType(); + final ConfigType configType = arg.getKey().getConfigType(mGroupVersions); baos.write(arg.getKey().getCode()); baos.write(configType.getValue()); baos.write(arg.getValue()); @@ -586,22 +740,27 @@ public class Huami2021Config { return baos.toByteArray(); } - public void write(final Huami2021Support support, final TransactionBuilder builder) { + public void write(final TransactionBuilder builder) { // Write one command per config group for (final ConfigGroup configGroup : arguments.keySet()) { - support.writeToChunked2021(builder, CHUNKED2021_ENDPOINT_CONFIG, encode(configGroup), true); + ZeppOsConfigService.this.write(builder, encode(configGroup)); } } private void checkArg(final ConfigArg arg, final ConfigType expectedConfigType) { + if (arg.getConfigType(mGroupVersions) == null) { + // Some special cases (STEPS goal) do not have a config type + return; + } + try { - if (!expectedConfigType.equals(arg.getConfigType())) { + if (!expectedConfigType.equals(arg.getConfigType(mGroupVersions))) { throw new IllegalArgumentException( String.format( "Invalid arg type %s for %s, expected %s", expectedConfigType, arg, - arg.getConfigType() + arg.getConfigType(mGroupVersions) ) ); } @@ -616,9 +775,7 @@ public class Huami2021Config { } } - public static class ConfigParser { - private static final Logger LOG = LoggerFactory.getLogger(ConfigParser.class); - + public class ConfigParser { private final ConfigGroup configGroup; private final boolean includesConstraints; @@ -640,7 +797,7 @@ public class Huami2021Config { } final byte configArgByte = buf.get(); - final Huami2021Config.ConfigArg configArg = Huami2021Config.ConfigArg.fromCode(configGroup, configArgByte); + final ZeppOsConfigService.ConfigArg configArg = ZeppOsConfigService.ConfigArg.fromCode(configGroup, configArgByte); if (configArg == null) { LOG.error("Unknown config {} for {}", String.format("0x%02x", configArgByte), configGroup); } @@ -654,8 +811,8 @@ public class Huami2021Config { return prefs; } if (configArg != null) { - if (configType != configArg.getConfigType()) { - LOG.warn("Unexpected arg type {} for {}, expected {}", configType, configArg, configArg.getConfigType()); + if (configType != configArg.getConfigType(mGroupVersions)) { + LOG.warn("Unexpected arg type {} for {}, expected {}", configType, configArg, configArg.getConfigType(mGroupVersions)); } } @@ -762,7 +919,7 @@ public class Huami2021Config { LOG.warn("Unhandled {} pref of type {}", configType, configArg); } - if (configArg != null && argPrefs != null && configType == configArg.getConfigType()) { + if (configArg != null && argPrefs != null && configType == configArg.getConfigType(mGroupVersions)) { prefs.put(getPrefKnownConfig(configArg), true); // Special cases for "follow phone" preferences. We need to ensure that "auto" @@ -787,7 +944,7 @@ public class Huami2021Config { return prefs; } - private static Map convertBooleanToPrefs(final ConfigArg configArg, final ConfigBoolean value) { + private Map convertBooleanToPrefs(final ConfigArg configArg, final ConfigBoolean value) { // Special cases switch (configArg) { case LANGUAGE_FOLLOW_PHONE: @@ -816,7 +973,7 @@ public class Huami2021Config { return null; } - private static Map convertStringToPrefs(final ConfigArg configArg, final ConfigString str) { + private Map convertStringToPrefs(final ConfigArg configArg, final ConfigString str) { // Special cases switch (configArg) { case DATE_FORMAT: @@ -833,7 +990,7 @@ public class Huami2021Config { return null; } - private static Map convertStringListToPrefs(final ConfigArg configArg, final ConfigStringList str) { + private Map convertStringListToPrefs(final ConfigArg configArg, final ConfigStringList str) { final List possibleValues = str.getPossibleValues(); final boolean includesConstraints = !possibleValues.isEmpty(); Map prefs = null; @@ -861,7 +1018,7 @@ public class Huami2021Config { return prefs; } - private static Map convertShortToPrefs(final ConfigArg configArg, final ConfigShort value) { + private Map convertShortToPrefs(final ConfigArg configArg, final ConfigShort value) { if (configArg.getPrefKey() != null) { // The arg maps to a number pref directly final Map prefs = singletonMap(configArg.getPrefKey(), value.getValue()); @@ -877,7 +1034,7 @@ public class Huami2021Config { return null; } - private static Map convertIntToPrefs(final ConfigArg configArg, final ConfigInt value) { + private Map convertIntToPrefs(final ConfigArg configArg, final ConfigInt value) { if (configArg.getPrefKey() != null) { // The arg maps to a number pref directly final Map prefs = singletonMap(configArg.getPrefKey(), value.getValue()); @@ -893,7 +1050,7 @@ public class Huami2021Config { return null; } - private static Map convertDatetimeHhMmToPrefs(final ConfigArg configArg, final ConfigDatetimeHhMm hhmm) { + private Map convertDatetimeHhMmToPrefs(final ConfigArg configArg, final ConfigDatetimeHhMm hhmm) { if (configArg.getPrefKey() != null) { // The arg maps to a hhmm pref directly return singletonMap(configArg.getPrefKey(), hhmm.getValue()); @@ -902,7 +1059,7 @@ public class Huami2021Config { return null; } - private static Map convertByteListToPrefs(final ConfigArg configArg, final ConfigByteList value) { + private Map convertByteListToPrefs(final ConfigArg configArg, final ConfigByteList value) { final byte[] possibleValues = value.getPossibleValues(); final boolean includesConstraints = possibleValues != null && possibleValues.length > 0; Map prefs = null; @@ -931,7 +1088,7 @@ public class Huami2021Config { return prefs; } - private static Map convertByteToPrefs(final ConfigArg configArg, final ConfigByte value) { + private Map convertByteToPrefs(final ConfigArg configArg, final ConfigByte value) { final byte[] possibleValues = value.getPossibleValues(); final boolean includesConstraints = value.getPossibleValues().length > 0; Map prefs = null; @@ -964,7 +1121,7 @@ public class Huami2021Config { decoder = null; break; case HEART_RATE_ALL_DAY_MONITORING: - decoder = Huami2021Config::decodeHeartRateAllDayMonitoring; + decoder = ZeppOsConfigService::decodeHeartRateAllDayMonitoring; break; case SCREEN_TIMEOUT: case HEART_RATE_HIGH_ALERTS: @@ -1023,7 +1180,7 @@ public class Huami2021Config { return prefs; } - private static List decodeByteValues(final byte[] values, final ValueDecoder decoder) { + private List decodeByteValues(final byte[] values, final ValueDecoder decoder) { final List decoded = new ArrayList<>(values.length); for (final byte b : values) { final String decodedByte = decoder.decode(b); @@ -1037,7 +1194,7 @@ public class Huami2021Config { return decoded; } - private static String decodeStringValues(final List values, final ValueDecoder decoder) { + private String decodeStringValues(final List values, final ValueDecoder decoder) { final List decoded = new ArrayList<>(values.size()); for (final String str : values) { final String decodedStr = decoder.decode(str); @@ -1053,7 +1210,7 @@ public class Huami2021Config { return String.join(",", decoded); } - private static Map singletonMap(final String key, final Object value) { + private Map singletonMap(final String key, final Object value) { if (key == null) { LOG.error("Null key in prefs update"); if (BuildConfig.DEBUG) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsFileUploadService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsFileUploadService.java index d635ac3db..da9108c97 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsFileUploadService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsFileUploadService.java @@ -35,17 +35,17 @@ import nodomain.freeyourgadget.gadgetbridge.util.CheckSums; public class ZeppOsFileUploadService extends AbstractZeppOsService { private static final Logger LOG = LoggerFactory.getLogger(ZeppOsFileUploadService.class); - public static final short ENDPOINT = 0x000d; + private static final short ENDPOINT = 0x000d; - public static final byte CMD_CAPABILITIES_REQUEST = 0x01; - public static final byte CMD_CAPABILITIES_RESPONSE = 0x02; - public static final byte CMD_UPLOAD_REQUEST = 0x03; - public static final byte CMD_UPLOAD_RESPONSE = 0x04; - public static final byte CMD_DATA_SEND = 0x10; - public static final byte CMD_DATA_ACK = 0x11; + private static final byte CMD_CAPABILITIES_REQUEST = 0x01; + private static final byte CMD_CAPABILITIES_RESPONSE = 0x02; + private static final byte CMD_UPLOAD_REQUEST = 0x03; + private static final byte CMD_UPLOAD_RESPONSE = 0x04; + private static final byte CMD_DATA_SEND = 0x10; + private static final byte CMD_DATA_ACK = 0x11; - public static final byte FLAG_FIRST_CHUNK = 0x01; - public static final byte FLAG_LAST_CHUNK = 0x02; + private static final byte FLAG_FIRST_CHUNK = 0x01; + private static final byte FLAG_LAST_CHUNK = 0x02; private final Map mSessionRequests = new HashMap<>();