From ff761cf1e4cfeeed5b49af9257207d45afcaf27d Mon Sep 17 00:00:00 2001 From: Thomas Kuehne Date: Fri, 18 Apr 2025 17:44:48 +0200 Subject: [PATCH] Ultrahuman optimizations - fetch only new records instead of all records while the device is connected - added PREF_POWER_SAVING support (disables / enables SpO2 sensing) - added HRV visualization - unified and secured Intent handling - added BATTERY_CHARGING_FULL support - honour PREF_TIME_SYNC in onSetTime - various clean-ups --- .../gadgetbridge/daogen/GBDaoGenerator.java | 16 +- .../GenericHrvSummarySampleProvider.java | 56 +++++++ .../ultrahuman/UltrahumanConstants.java | 2 + .../UltrahumanDeviceCardAction.java | 2 + .../UltrahumanDeviceCoordinator.java | 15 +- .../ultrahuman/UltrahumanDeviceSupport.java | 156 +++++++++++------- app/src/main/res/values/strings.xml | 7 +- 7 files changed, 184 insertions(+), 70 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/GenericHrvSummarySampleProvider.java diff --git a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java index 5afc7f185..1c3e4a87a 100644 --- a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java +++ b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java @@ -56,7 +56,7 @@ public class GBDaoGenerator { public static void main(String[] args) throws Exception { - final Schema schema = new Schema(101, MAIN_PACKAGE + ".entities"); + final Schema schema = new Schema(102, MAIN_PACKAGE + ".entities"); Entity userAttributes = addUserAttributes(schema); Entity user = addUserInfo(schema, userAttributes); @@ -193,6 +193,7 @@ public class GBDaoGenerator { addGenericHeartRateSample(schema, user, device); addGenericSpo2Sample(schema, user, device); addGenericStressSample(schema, user, device); + addGenericHrvSummarySample(schema, user, device); addGenericHrvValueSample(schema, user, device); addGenericTemperatureSample(schema, user, device); @@ -1773,6 +1774,19 @@ public class GBDaoGenerator { return heartRateSample; } + private static Entity addGenericHrvSummarySample(Schema schema, Entity user, Entity device) { + Entity hrvSummarySample = addEntity(schema, "GenericHrvSummarySample"); + addCommonTimeSampleProperties("AbstractHrvSummarySample", hrvSummarySample, user, device); + hrvSummarySample.addIntProperty(SAMPLE_HRV_WEEKLY_AVERAGE).codeBeforeGetter(OVERRIDE); + hrvSummarySample.addIntProperty(SAMPLE_HRV_LAST_NIGHT_AVERAGE).codeBeforeGetter(OVERRIDE); + hrvSummarySample.addIntProperty(SAMPLE_HRV_LAST_NIGHT_5MIN_HIGH).codeBeforeGetter(OVERRIDE); + hrvSummarySample.addIntProperty(SAMPLE_HRV_BASELINE_LOW_UPPER).codeBeforeGetter(OVERRIDE); + hrvSummarySample.addIntProperty(SAMPLE_HRV_BASELINE_BALANCED_LOWER).codeBeforeGetter(OVERRIDE); + hrvSummarySample.addIntProperty(SAMPLE_HRV_BASELINE_BALANCED_UPPER).codeBeforeGetter(OVERRIDE); + hrvSummarySample.addIntProperty(SAMPLE_HRV_STATUS_NUM).codeBeforeGetter(OVERRIDE); + return hrvSummarySample; + } + private static Entity addGenericHrvValueSample(Schema schema, Entity user, Entity device) { Entity hrvValueSample = addEntity(schema, "GenericHrvValueSample"); addCommonTimeSampleProperties("AbstractHrvValueSample", hrvValueSample, user, device); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/GenericHrvSummarySampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/GenericHrvSummarySampleProvider.java new file mode 100644 index 000000000..adbf8c61d --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/GenericHrvSummarySampleProvider.java @@ -0,0 +1,56 @@ +/* Copyright (C) 2025 Thomas Kuehne + + 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; + +import androidx.annotation.NonNull; + +import de.greenrobot.dao.AbstractDao; +import de.greenrobot.dao.Property; +import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; +import nodomain.freeyourgadget.gadgetbridge.entities.GenericHrvSummarySample; +import nodomain.freeyourgadget.gadgetbridge.entities.GenericHrvSummarySampleDao; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; + +public class GenericHrvSummarySampleProvider extends AbstractTimeSampleProvider { + public GenericHrvSummarySampleProvider(final GBDevice device, final DaoSession session) { + super(device, session); + } + + @NonNull + @Override + public AbstractDao getSampleDao() { + return getSession().getGenericHrvSummarySampleDao(); + } + + @NonNull + @Override + protected Property getTimestampSampleProperty() { + return GenericHrvSummarySampleDao.Properties.Timestamp; + } + + @NonNull + @Override + protected Property getDeviceIdentifierSampleProperty() { + return GenericHrvSummarySampleDao.Properties.DeviceId; + } + + @Override + public GenericHrvSummarySample createSample() { + return new GenericHrvSummarySample(); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/ultrahuman/UltrahumanConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/ultrahuman/UltrahumanConstants.java index 7083d1d76..42d9da02d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/ultrahuman/UltrahumanConstants.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/ultrahuman/UltrahumanConstants.java @@ -36,4 +36,6 @@ public class UltrahumanConstants { public static final byte OPERATION_PING = 0x59; public static final byte OPERATION_ACTIVATE_AIRPLANE_MODE = 0x70; public static final byte OPERATION_RESET = (byte) 0x98; + public static final byte OPERATION_DISABLE_POWERSAVE = (byte) 0xD1; + public static final byte OPERATION_ENABLE_POWERSAVE = (byte) 0xD2; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/ultrahuman/UltrahumanDeviceCardAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/ultrahuman/UltrahumanDeviceCardAction.java index cb1e15968..3fadbfd22 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/ultrahuman/UltrahumanDeviceCardAction.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/ultrahuman/UltrahumanDeviceCardAction.java @@ -22,6 +22,7 @@ import android.content.Intent; import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import nodomain.freeyourgadget.gadgetbridge.BuildConfig; import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCardAction; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; @@ -58,6 +59,7 @@ public class UltrahumanDeviceCardAction implements DeviceCardAction { .setIcon(Icon) .setPositiveButton(android.R.string.yes, (dialog, whichButton) -> { final Intent intent = new Intent(Action); + intent.setPackage(BuildConfig.APPLICATION_ID); intent.putExtra(GBDevice.EXTRA_DEVICE, device); context.sendBroadcast(intent); }) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/ultrahuman/UltrahumanDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/ultrahuman/UltrahumanDeviceCoordinator.java index 5a2fbfe4c..4c0f1b60b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/ultrahuman/UltrahumanDeviceCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/ultrahuman/UltrahumanDeviceCoordinator.java @@ -32,6 +32,7 @@ import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCardAction; import nodomain.freeyourgadget.gadgetbridge.devices.GenericHeartRateSampleProvider; +import nodomain.freeyourgadget.gadgetbridge.devices.GenericHrvSummarySampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.GenericHrvValueSampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.GenericSpo2SampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.GenericStressSampleProvider; @@ -51,6 +52,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.UltrahumanDeviceStateSample import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; import nodomain.freeyourgadget.gadgetbridge.model.HeartRateSample; +import nodomain.freeyourgadget.gadgetbridge.model.HrvSummarySample; import nodomain.freeyourgadget.gadgetbridge.model.HrvValueSample; import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample; import nodomain.freeyourgadget.gadgetbridge.model.StressSample; @@ -71,7 +73,7 @@ public class UltrahumanDeviceCoordinator extends AbstractBLEDeviceCoordinator { protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException { final Long deviceId = device.getId(); - final Map, Property> daoMap = new HashMap, Property>() {{ + final Map, Property> daoMap = new HashMap<>() {{ put(session.getGenericHeartRateSampleDao(), GenericHeartRateSampleDao.Properties.DeviceId); put(session.getGenericHrvValueSampleDao(), GenericHrvValueSampleDao.Properties.DeviceId); put(session.getGenericSpo2SampleDao(), GenericSpo2SampleDao.Properties.DeviceId); @@ -114,6 +116,11 @@ public class UltrahumanDeviceCoordinator extends AbstractBLEDeviceCoordinator { return "Ultrahuman Healthcare Pvt Ltd"; } + @Override + public TimeSampleProvider getHrvSummarySampleProvider(GBDevice device, DaoSession session) { + return new GenericHrvSummarySampleProvider(device, session); + } + @Override public TimeSampleProvider getHrvValueSampleProvider(GBDevice device, DaoSession session) { return new GenericHrvValueSampleProvider(device, session); @@ -147,7 +154,8 @@ public class UltrahumanDeviceCoordinator extends AbstractBLEDeviceCoordinator { @Override public int[] getSupportedDeviceSpecificSettings(GBDevice device) { return new int[]{ - R.xml.devicesettings_time_sync + R.xml.devicesettings_time_sync, + R.xml.devicesettings_power_saving }; } @@ -183,8 +191,7 @@ public class UltrahumanDeviceCoordinator extends AbstractBLEDeviceCoordinator { @Override public boolean supportsHrvMeasurement(final GBDevice device) { - // TODO - needs getHrvSummarySampleProvider in addition to the implemented getHrvValueSampleProvider - return false; + return true; } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/ultrahuman/UltrahumanDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/ultrahuman/UltrahumanDeviceSupport.java index 0ab006692..724c0cd7e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/ultrahuman/UltrahumanDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/ultrahuman/UltrahumanDeviceSupport.java @@ -30,6 +30,7 @@ import androidx.core.content.ContextCompat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Locale; import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.GBApplication; @@ -57,7 +58,6 @@ import nodomain.freeyourgadget.gadgetbridge.entities.UltrahumanActivitySample; import nodomain.freeyourgadget.gadgetbridge.entities.UltrahumanDeviceStateSample; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.BatteryState; -import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions; import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService; @@ -71,30 +71,30 @@ import nodomain.freeyourgadget.gadgetbridge.util.StringUtils; public class UltrahumanDeviceSupport extends AbstractBTLEDeviceSupport { private static final Logger LOG = LoggerFactory.getLogger(UltrahumanDeviceSupport.class); - private BroadcastReceiver CommandReceiver; + private final UltrahumanBroadcastReceiver CommandReceiver; private int FetchTo; private int FetchFrom; private int FetchCurrent; - public UltrahumanDeviceSupport(DeviceType type) { + public UltrahumanDeviceSupport() { super(LOG); addSupportedService(UltrahumanConstants.UUID_SERVICE_COMMAND); addSupportedService(UltrahumanConstants.UUID_SERVICE_STATE); addSupportedService(GattService.UUID_SERVICE_DEVICE_INFORMATION); + CommandReceiver = new UltrahumanBroadcastReceiver(); + DeviceInfoProfile deviceProfile = new DeviceInfoProfile<>(this); - deviceProfile.addListener(new UltrahumanIntentListener()); + deviceProfile.addListener(CommandReceiver); addSupportedProfile(deviceProfile); } @Override public void dispose() { - BroadcastReceiver receiver = CommandReceiver; - CommandReceiver = null; - - if (receiver != null) { - getContext().unregisterReceiver(receiver); + if (CommandReceiver.Registered) { + CommandReceiver.Registered = false; + getContext().unregisterReceiver(CommandReceiver); } super.dispose(); @@ -115,11 +115,11 @@ public class UltrahumanDeviceSupport extends AbstractBTLEDeviceSupport { builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext())); - if (CommandReceiver == null) { - CommandReceiver = new UltrahumanBroadcastReceiver(); + if (!CommandReceiver.Registered) { IntentFilter filter = new IntentFilter(); filter.addAction(UltrahumanConstants.ACTION_AIRPLANE_MODE); ContextCompat.registerReceiver(getContext(), CommandReceiver, filter, ContextCompat.RECEIVER_EXPORTED); + CommandReceiver.Registered = true; } // trying to read non-existing characteristics sometimes causes odd BLE failures @@ -148,6 +148,9 @@ public class UltrahumanDeviceSupport extends AbstractBTLEDeviceSupport { builder.add(new UltrahumanSetTimeAction(getCharacteristic(UltrahumanConstants.UUID_CHARACTERISTIC_COMMAND))); } + boolean powerSave = getDevicePrefs().getBoolean(DeviceSettingsPreferenceConst.PREF_POWER_SAVING, true); + builder.write(getCharacteristic(UltrahumanConstants.UUID_CHARACTERISTIC_COMMAND), new byte[]{powerSave ? UltrahumanConstants.OPERATION_ENABLE_POWERSAVE : UltrahumanConstants.OPERATION_DISABLE_POWERSAVE}); + builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext())); return builder; @@ -162,12 +165,15 @@ public class UltrahumanDeviceSupport extends AbstractBTLEDeviceSupport { getDevice().setBusyTask(task); getDevice().sendDeviceUpdateIntent(getContext()); - FetchFrom = -1; + // fetch deltas while the device is connected + FetchFrom = (FetchCurrent > 0 && FetchCurrent < Short.MAX_VALUE - 1000) ? FetchCurrent : -1; FetchTo = -1; FetchCurrent = -1; TransactionBuilder builder = new TransactionBuilder("onFetchRecordedData"); - builder.write(getCharacteristic(UltrahumanConstants.UUID_CHARACTERISTIC_COMMAND), new byte[]{UltrahumanConstants.OPERATION_GET_FIRST_RECORDING_NR}); + if (FetchFrom <= 0) { + builder.write(getCharacteristic(UltrahumanConstants.UUID_CHARACTERISTIC_COMMAND), new byte[]{UltrahumanConstants.OPERATION_GET_FIRST_RECORDING_NR}); + } builder.write(getCharacteristic(UltrahumanConstants.UUID_CHARACTERISTIC_COMMAND), new byte[]{UltrahumanConstants.OPERATION_GET_LAST_RECORDING_NR}); if (isConnected()) { @@ -209,19 +215,20 @@ public class UltrahumanDeviceSupport extends AbstractBTLEDeviceSupport { return false; } - byte op = raw[0]; - byte success = raw[1]; - byte result = raw[2]; + final byte op = raw[0]; + final byte success = raw[1]; + final byte result = raw[2]; // ignore checksums for now - algorithm is unknown //byte chk1 = raw[raw.length-1]; //byte chk2 = raw[raw.length-2]; + final String info = String.format(Locale.US, "received BLE command response op=%02x, success=%02x, result=%02x : %s", op, success, result, StringUtils.bytesToHex(raw)); if (success == 0x00) { - LOG.debug("received BLE command response op={}, success={}, result={} : {}", op, success, result, StringUtils.bytesToHex(raw)); + LOG.debug(info); } else if ((0xFF & success) == 0xFF) { - LOG.warn("received BLE command response op={}, success={}, result={} : {}", op, success, result, StringUtils.bytesToHex(raw)); + LOG.warn(info); } else { - LOG.info("received BLE command response op={}, success={}, result={} : {}", op, success, result, StringUtils.bytesToHex(raw)); + LOG.info(info); } Context context = getContext(); @@ -230,13 +237,12 @@ public class UltrahumanDeviceSupport extends AbstractBTLEDeviceSupport { case UltrahumanConstants.OPERATION_GET_RECORDINGS: return decodeRecordings(raw); case UltrahumanConstants.OPERATION_PING: - if (raw[1] != 0x00) { - String message = getContext().getString(R.string.ultrahuman_unhandled_error_response, raw[1], raw[0]); - GB.toast(getContext(), message, Toast.LENGTH_LONG, GB.ERROR); + if (success == 0x00) { + return true; } - return true; + break; case UltrahumanConstants.OPERATION_ACTIVATE_AIRPLANE_MODE: - switch (raw[2]) { + switch (result) { case 0x01: GB.toast(context, context.getString(R.string.ultrahuman_airplane_mode_activated), Toast.LENGTH_LONG, GB.INFO); return true; @@ -247,54 +253,66 @@ public class UltrahumanDeviceSupport extends AbstractBTLEDeviceSupport { GB.toast(context, context.getString(R.string.ultrahuman_airplane_mode_too_full), Toast.LENGTH_LONG, GB.ERROR); return true; } - LOG.warn("set airplane mode - unknown error: {} ", StringUtils.bytesToHex(raw)); - GB.toast(context, context.getString(R.string.ultrahuman_airplane_mode_unknown), Toast.LENGTH_LONG, GB.ERROR); + + String airplaneMessage = getContext().getString(R.string.ultrahuman_airplane_mode_unknown, result); + GB.toast(context, airplaneMessage, Toast.LENGTH_LONG, GB.ERROR); return false; case UltrahumanConstants.OPERATION_GET_FIRST_RECORDING_NR: - if (raw[1] == 0x00 || raw[2] == 0x01) { + if (success == 0x00 && result == 0x01) { FetchFrom = BLETypeConversions.toUint16(raw, 3); if (FetchTo != -1) { fetchRecordingActually(); } return true; - } else { - String message = getContext().getString(R.string.ultrahuman_unhandled_error_response, raw[1], raw[0]); - GB.toast(getContext(), message, Toast.LENGTH_LONG, GB.ERROR); - fetchRecordedDataFinished(); } - return false; + fetchRecordedDataFinished(); + break; case UltrahumanConstants.OPERATION_GET_LAST_RECORDING_NR: - if (raw[1] == 0x00 && raw[2] == 0x01) { + if (success == 0x00 && result == 0x01) { FetchTo = BLETypeConversions.toUint16(raw, 3); if (FetchFrom != -1) { fetchRecordingActually(); } return true; - } else { - String message = getContext().getString(R.string.ultrahuman_unhandled_error_response, raw[1], raw[0]); - GB.toast(getContext(), message, Toast.LENGTH_LONG, GB.ERROR); - fetchRecordedDataFinished(); } - return false; + fetchRecordedDataFinished(); + break; + case UltrahumanConstants.OPERATION_DISABLE_POWERSAVE: + case UltrahumanConstants.OPERATION_ENABLE_POWERSAVE: case UltrahumanConstants.OPERATION_SETTIME: - if (raw[1] != 0x00 || raw[2] != 0x01) { - String message = getContext().getString(R.string.ultrahuman_unhandled_error_response, raw[1], raw[0]); - GB.toast(getContext(), message, Toast.LENGTH_LONG, GB.ERROR); + if (success == 0x00 && result == 0x01) { + return true; } - return true; + break; + default: - LOG.error("unhandled BLE command response: {} ", StringUtils.bytesToHex(raw)); + String message = getContext().getString(R.string.ultrahuman_unhandled_operation_response, StringUtils.bytesToHex(raw)); + GB.toast(getContext(), message, Toast.LENGTH_LONG, GB.ERROR); return false; } + + String message = getContext().getString(R.string.ultrahuman_unhandled_error_response, op, success, result); + GB.toast(getContext(), message, Toast.LENGTH_LONG, GB.ERROR); + return false; } LOG.error("unhandled characteristics {} - {} ", characteristicUUID, StringUtils.bytesToHex(raw)); return false; } + @Override + public void onSendConfiguration(String config) { + if (DeviceSettingsPreferenceConst.PREF_TIME_SYNC.equals(config)) { + onSetTime(); + } else if (DeviceSettingsPreferenceConst.PREF_POWER_SAVING.equals(config)) { + onSetPowerSaveMode(); + } else { + super.onSendConfiguration(config); + } + } @Override public boolean useAutoConnect() { @@ -310,7 +328,7 @@ public class UltrahumanDeviceSupport extends AbstractBTLEDeviceSupport { if ((raw[1] & 0xFF) == 0xEE) { LOG.warn("no historic data recorded"); } else { - String message = getContext().getString(R.string.ultrahuman_unhandled_error_response, raw[1], raw[0]); + String message = getContext().getString(R.string.ultrahuman_unhandled_error_response, raw[0], raw[1], raw[2]); GB.toast(getContext(), message, Toast.LENGTH_LONG, GB.ERROR); } fetchRecordedDataFinished(); @@ -439,7 +457,7 @@ public class UltrahumanDeviceSupport extends AbstractBTLEDeviceSupport { batteryState = BatteryState.BATTERY_NORMAL; break; case 0x03: - batteryState = BatteryState.BATTERY_CHARGING; + batteryState = (batteryLevel > 99) ? BatteryState.BATTERY_CHARGING_FULL : BatteryState.BATTERY_CHARGING; break; default: LOG.warn("DeviceState contains unhandled device state {}: {}", raw[5], StringUtils.bytesToHex(raw)); @@ -488,8 +506,8 @@ public class UltrahumanDeviceSupport extends AbstractBTLEDeviceSupport { GB.signalActivityDataFinish(getDevice()); } - public void activateAirplaneMode() { - sendCommand("ActivateAirplaneMode", new byte[]{UltrahumanConstants.OPERATION_ACTIVATE_AIRPLANE_MODE}); + private void activateAirplaneMode() { + sendCommand("activateAirplaneMode", new byte[]{UltrahumanConstants.OPERATION_ACTIVATE_AIRPLANE_MODE}); } @Override @@ -499,11 +517,10 @@ public class UltrahumanDeviceSupport extends AbstractBTLEDeviceSupport { } } - @Override - public void onSetTime() { - TransactionBuilder builder = new TransactionBuilder("onSetTime"); - UltrahumanSetTimeAction action = new UltrahumanSetTimeAction(getCharacteristic(UltrahumanConstants.UUID_CHARACTERISTIC_COMMAND)); - builder.add(action); + private void onSetPowerSaveMode() { + boolean powerSave = getDevicePrefs().getBoolean(DeviceSettingsPreferenceConst.PREF_POWER_SAVING, true); + TransactionBuilder builder = new TransactionBuilder("onSetPowerSaveMode"); + builder.write(getCharacteristic(UltrahumanConstants.UUID_CHARACTERISTIC_COMMAND), new byte[]{powerSave ? UltrahumanConstants.OPERATION_ENABLE_POWERSAVE : UltrahumanConstants.OPERATION_DISABLE_POWERSAVE}); if (isConnected()) { builder.queue(getQueue()); @@ -512,6 +529,22 @@ public class UltrahumanDeviceSupport extends AbstractBTLEDeviceSupport { } } + @Override + public void onSetTime() { + boolean timeSync = getDevicePrefs().getBoolean(DeviceSettingsPreferenceConst.PREF_TIME_SYNC, true); + if (timeSync) { + TransactionBuilder builder = new TransactionBuilder("onSetTime"); + UltrahumanSetTimeAction action = new UltrahumanSetTimeAction(getCharacteristic(UltrahumanConstants.UUID_CHARACTERISTIC_COMMAND)); + builder.add(action); + + if (isConnected()) { + builder.queue(getQueue()); + } else { + GB.toast(getContext(), R.string.devicestatus_disconnected, Toast.LENGTH_LONG, GB.ERROR); + } + } + } + private void sendCommand(String taskName, byte[] contents) { LOG.debug("sendCommand {} : {}", taskName, StringUtils.bytesToHex(contents)); TransactionBuilder builder = new TransactionBuilder(taskName); @@ -524,9 +557,16 @@ public class UltrahumanDeviceSupport extends AbstractBTLEDeviceSupport { } } - private class UltrahumanBroadcastReceiver extends BroadcastReceiver { + private class UltrahumanBroadcastReceiver extends BroadcastReceiver implements IntentListener { + boolean Registered; + @Override public void onReceive(Context context, Intent intent) { + notify(intent); + } + + @Override + public void notify(Intent intent) { final GBDevice device = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE); if (device != null && !device.equals(getDevice())) { // this intent is for another device @@ -536,15 +576,7 @@ public class UltrahumanDeviceSupport extends AbstractBTLEDeviceSupport { String action = intent.getAction(); if (UltrahumanConstants.ACTION_AIRPLANE_MODE.equals(action)) { activateAirplaneMode(); - } - } - } - - private class UltrahumanIntentListener implements IntentListener { - @Override - public void notify(Intent intent) { - String action = intent.getAction(); - if (DeviceInfoProfile.ACTION_DEVICE_INFO.equals(action)) { + } else if (DeviceInfoProfile.ACTION_DEVICE_INFO.equals(action)) { handleDeviceInfo(intent.getParcelableExtra(DeviceInfoProfile.EXTRA_DEVICE_INFO)); } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 18dc30aee..b3c11c411 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1189,7 +1189,7 @@ Persian Scandinavian Serbian - Ukranian + Ukrainian Armenian Italian French @@ -3875,10 +3875,11 @@ airplane mode activated ring is charging - airplane mode NOT activated ring is fully charged - airplane mode NOT activated - unknown error - airplane mode NOT activated + unknown error %#02x - airplane mode NOT activated Activate airplane mode? airplane mode - received unhandled response code 0x%x for operation 0x%x + unhandled response %2$#02x %3$#02x for operation %1$#02x + unhandled operation response %s Automatic stress test Enable or disable automatic stress test No initial data - calibrate first