mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2025-01-10 17:11:56 +01:00
Compare commits
4 Commits
789292d318
...
3902db5abd
Author | SHA1 | Date | |
---|---|---|---|
|
3902db5abd | ||
|
58d4ebf509 | ||
|
9b0229cdf0 | ||
|
5e068ee4ca |
@ -221,18 +221,7 @@ public class HuamiService {
|
||||
public static final byte SUCCESS = 0x01;
|
||||
public static final byte COMMAND_ACTIVITY_DATA_START_DATE = 0x01;
|
||||
public static final byte COMMAND_ACTIVITY_DATA_TYPE_ACTIVTY = 0x01;
|
||||
public static final byte COMMAND_ACTIVITY_DATA_TYPE_MANUAL_HEART_RATE = 0x02;
|
||||
public static final byte COMMAND_ACTIVITY_DATA_XXX_DATE = 0x02; // issued on first connect, followd by COMMAND_XXXX_ACTIVITY_DATA instead of COMMAND_FETCH_DATA
|
||||
public static final byte COMMAND_ACTIVITY_DATA_TYPE_PAI = 0x0d;
|
||||
public static final byte COMMAND_ACTIVITY_DATA_TYPE_STRESS_MANUAL = 0x12;
|
||||
public static final byte COMMAND_ACTIVITY_DATA_TYPE_STRESS_AUTOMATIC = 0x13;
|
||||
public static final byte COMMAND_ACTIVITY_DATA_TYPE_SPO2_NORMAL = 0x25;
|
||||
public static final byte COMMAND_ACTIVITY_DATA_TYPE_SPO2_SLEEP = 0x26;
|
||||
public static final byte COMMAND_ACTIVITY_DATA_TYPE_STATISTICS = 0x2c;
|
||||
public static final byte COMMAND_ACTIVITY_DATA_TYPE_TEMPERATURE = 0x2e;
|
||||
public static final byte COMMAND_ACTIVITY_DATA_TYPE_SLEEP_RESPIRATORY_RATE = 0x38;
|
||||
public static final byte COMMAND_ACTIVITY_DATA_TYPE_RESTING_HEART_RATE = 0x3a;
|
||||
public static final byte COMMAND_ACTIVITY_DATA_TYPE_MAX_HEART_RATE = 0x3d;
|
||||
|
||||
public static final byte COMMAND_FIRMWARE_INIT = 0x01; // to UUID_CHARACTERISTIC_FIRMWARE, followed by fw file size in bytes
|
||||
public static final byte COMMAND_FIRMWARE_START_DATA = 0x03; // to UUID_CHARACTERISTIC_FIRMWARE
|
||||
@ -240,8 +229,6 @@ public class HuamiService {
|
||||
public static final byte COMMAND_FIRMWARE_CHECKSUM = 0x04; // to UUID_CHARACTERISTIC_FIRMWARE
|
||||
public static final byte COMMAND_FIRMWARE_REBOOT = 0x05; // to UUID_CHARACTERISTIC_FIRMWARE
|
||||
|
||||
public static final byte[] RESPONSE_ACTIVITY_DATA_START_DATE_SUCCESS = new byte[] { RESPONSE, COMMAND_ACTIVITY_DATA_START_DATE, SUCCESS};
|
||||
|
||||
public static final byte[] WEAR_LOCATION_LEFT_WRIST = new byte[] { 0x20, 0x00, 0x00, 0x02 };
|
||||
public static final byte[] WEAR_LOCATION_RIGHT_WRIST = new byte[] { 0x20, 0x00, 0x00, (byte) 0x82};
|
||||
|
||||
|
@ -19,22 +19,15 @@ package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.DISPLAY_ITEM_BIT_CLOCK;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.ENDPOINT_DISPLAY;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.ENDPOINT_DISPLAY_ITEMS;
|
||||
|
||||
public class AmazfitBipService {
|
||||
public static final UUID UUID_CHARACTERISTIC_WEATHER = UUID.fromString("0000000e-0000-3512-2118-0009af100700");
|
||||
|
||||
public static final byte COMMAND_ACTIVITY_DATA_TYPE_DEBUGLOGS = 0x07;
|
||||
|
||||
public static final byte[] COMMAND_SET_LANGUAGE_SIMPLIFIED_CHINESE = new byte[]{ENDPOINT_DISPLAY, 0x13, 0x00, 0x00};
|
||||
public static final byte[] COMMAND_SET_LANGUAGE_TRADITIONAL_CHINESE = new byte[]{ENDPOINT_DISPLAY, 0x13, 0x00, 0x01};
|
||||
public static final byte[] COMMAND_SET_LANGUAGE_ENGLISH = new byte[]{ENDPOINT_DISPLAY, 0x13, 0x00, 0x02};
|
||||
public static final byte[] COMMAND_SET_LANGUAGE_SPANISH = new byte[]{ENDPOINT_DISPLAY, 0x13, 0x00, 0x03};
|
||||
|
||||
public static final byte COMMAND_ACTIVITY_DATA_TYPE_SPORTS_SUMMARIES = 0x05;
|
||||
public static final byte COMMAND_ACTIVITY_DATA_TYPE_SPORTS_DETAILS = 0x06;
|
||||
|
||||
public static final byte[] COMMAND_ACK_FIND_PHONE_IN_PROGRESS = new byte[]{ENDPOINT_DISPLAY, 0x14, 0x00, 0x00};
|
||||
}
|
||||
|
@ -128,19 +128,19 @@ import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.SleepState;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.WearingState;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.AbstractFetchOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.FetchStatisticsOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.FetchTemperatureOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.FetchHeartRateManualOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.FetchHeartRateMaxOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.FetchHeartRateRestingOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.FetchPaiOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.FetchSleepRespiratoryRateOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.FetchSpo2NormalOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.FetchSportsSummaryOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.FetchStressAutoOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.FetchStressManualOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.HuamiFetchDebugLogsOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch.AbstractFetchOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch.FetchStatisticsOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch.FetchTemperatureOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch.FetchHeartRateManualOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch.FetchHeartRateMaxOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch.FetchHeartRateRestingOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch.FetchPaiOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch.FetchSleepRespiratoryRateOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch.FetchSpo2NormalOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch.FetchSportsSummaryOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch.FetchStressAutoOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch.FetchStressManualOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch.FetchDebugLogsOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsCannedMessagesService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.MediaManager;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.SilentMode;
|
||||
@ -176,10 +176,10 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.common.SimpleNotific
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.actions.StopNotificationAction;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband2.Mi2NotificationStrategy;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband2.Mi2TextNotificationStrategy;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.FetchActivityOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.InitOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.InitOperation2021;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.UpdateFirmwareOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch.FetchActivityOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.init.InitOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.init.InitOperation2021;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.NotificationStrategy;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.RealtimeSamplesSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
|
||||
@ -344,7 +344,7 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements
|
||||
protected Huami2021ChunkedEncoder huami2021ChunkedEncoder;
|
||||
protected Huami2021ChunkedDecoder huami2021ChunkedDecoder;
|
||||
|
||||
private final Queue<AbstractFetchOperation> fetchOperationQueue = new LinkedList<>();
|
||||
private final LinkedList<AbstractFetchOperation> fetchOperationQueue = new LinkedList<>();
|
||||
|
||||
public HuamiSupport() {
|
||||
this(LOG);
|
||||
@ -1675,7 +1675,7 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements
|
||||
}
|
||||
|
||||
if ((dataTypes & RecordedDataTypes.TYPE_DEBUGLOGS) != 0 && coordinator.supportsDebugLogs()) {
|
||||
this.fetchOperationQueue.add(new HuamiFetchDebugLogsOperation(this));
|
||||
this.fetchOperationQueue.add(new FetchDebugLogsOperation(this));
|
||||
}
|
||||
|
||||
if ((dataTypes & RecordedDataTypes.TYPE_STRESS) != 0 && coordinator.supportsStressMeasurement()) {
|
||||
@ -1725,6 +1725,10 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements
|
||||
return fetchOperationQueue.poll();
|
||||
}
|
||||
|
||||
public LinkedList<AbstractFetchOperation> getFetchOperationQueue() {
|
||||
return fetchOperationQueue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnableRealtimeSteps(boolean enable) {
|
||||
try {
|
||||
|
@ -31,14 +31,8 @@ import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip.AmazfitBipFWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.FetchActivityOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.FetchSportsSummaryOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.HuamiFetchDebugLogsOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.NotificationStrategy;
|
||||
|
||||
public class AmazfitBipSupport extends HuamiSupport {
|
||||
|
@ -27,8 +27,8 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip3pro.Amazfit
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip.AmazfitBipSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.UpdateFirmwareOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.UpdateFirmwareOperation2020;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation2020;
|
||||
|
||||
public class AmazfitBip3ProSupport extends AmazfitBipSupport {
|
||||
@Override
|
||||
|
@ -29,13 +29,12 @@ import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbips.AmazfitBipSFWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip.AmazfitBipSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.UpdateFirmwareOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.UpdateFirmwareOperation2020;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.UpdateFirmwareOperationNew;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation2020;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperationNew;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Version;
|
||||
|
||||
public class AmazfitBipSSupport extends AmazfitBipSupport {
|
||||
|
@ -26,8 +26,8 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbipu.AmazfitBip
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbips.AmazfitBipSSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.UpdateFirmwareOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.UpdateFirmwareOperation2020;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation2020;
|
||||
|
||||
public class AmazfitBipUSupport extends AmazfitBipSSupport {
|
||||
|
||||
|
@ -26,8 +26,8 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgtr.AmazfitGTRLiteFWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgts.AmazfitGTSSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.UpdateFirmwareOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.UpdateFirmwareOperationNew;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperationNew;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Version;
|
||||
|
||||
public class AmazfitGTRLiteSupport extends AmazfitGTSSupport {
|
||||
|
@ -24,9 +24,9 @@ import java.io.IOException;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgtr.AmazfitGTRFWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgts.AmazfitGTSSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.UpdateFirmwareOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.UpdateFirmwareOperation2020;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.UpdateFirmwareOperationNew;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation2020;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperationNew;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Version;
|
||||
|
||||
public class AmazfitGTRSupport extends AmazfitGTSSupport {
|
||||
|
@ -28,8 +28,8 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgtr2.AmazfitGTR
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgtr.AmazfitGTRSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.UpdateFirmwareOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.UpdateFirmwareOperation2020;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation2020;
|
||||
|
||||
public class AmazfitGTR2Support extends AmazfitGTRSupport {
|
||||
|
||||
|
@ -27,12 +27,11 @@ import java.io.IOException;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgts.AmazfitGTSFWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip.AmazfitBipSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.UpdateFirmwareOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.UpdateFirmwareOperation2020;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.UpdateFirmwareOperationNew;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation2020;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperationNew;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Version;
|
||||
|
||||
public class AmazfitGTSSupport extends AmazfitBipSupport {
|
||||
|
@ -28,8 +28,8 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgts2.AmazfitGTS
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgts.AmazfitGTSSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.UpdateFirmwareOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.UpdateFirmwareOperation2020;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation2020;
|
||||
|
||||
public class AmazfitGTS2Support extends AmazfitGTSSupport {
|
||||
|
||||
|
@ -35,12 +35,11 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitneo.AmazfitNeoF
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband5.MiBand5Support;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.UpdateFirmwareOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.UpdateFirmwareOperation2020;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation2020;
|
||||
|
||||
|
||||
public class AmazfitNeoSupport extends MiBand5Support {
|
||||
|
@ -26,12 +26,11 @@ import java.io.IOException;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip.AmazfitBipSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitvergel.AmazfitVergeLFWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.UpdateFirmwareOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.UpdateFirmwareOperation2020;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation2020;
|
||||
|
||||
public class AmazfitVergeLSupport extends AmazfitBipSupport {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AmazfitVergeLSupport.class);
|
||||
|
@ -29,8 +29,8 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.miband4.MiBand4FWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband3.MiBand3Support;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.UpdateFirmwareOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.UpdateFirmwareOperationNew;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperationNew;
|
||||
|
||||
public class MiBand4Support extends MiBand3Support {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MiBand4Support.class);
|
||||
|
@ -30,8 +30,8 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huami.miband6.MiBand6FWHelpe
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband5.MiBand5Support;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.UpdateFirmwareOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.UpdateFirmwareOperation2020;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation2020;
|
||||
|
||||
public class MiBand6Support extends MiBand5Support {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MiBand6Support.class);
|
||||
|
@ -1,281 +0,0 @@
|
||||
/* Copyright (C) 2018-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti, José Rebelo, Oleg Vasilev, Sebastian Krey, Your Name
|
||||
|
||||
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 <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations;
|
||||
|
||||
import android.text.format.DateUtils;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.GregorianCalendar;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.Logging;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip.AmazfitBipService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
|
||||
import nodomain.freeyourgadget.gadgetbridge.export.ActivityTrackExporter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.export.GPXExporter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityTrack;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.AbstractHuamiActivityDetailsParser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiActivityDetailsParser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.CheckSums;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
/**
|
||||
* An operation that fetches activity data. For every fetch, a new operation must
|
||||
* be created, i.e. an operation may not be reused for multiple fetches.
|
||||
*/
|
||||
public class FetchSportsDetailsOperation extends AbstractFetchOperation {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(FetchSportsDetailsOperation.class);
|
||||
private final AbstractHuamiActivityDetailsParser detailsParser;
|
||||
private final BaseActivitySummary summary;
|
||||
private final String lastSyncTimeKey;
|
||||
|
||||
private ByteArrayOutputStream buffer;
|
||||
|
||||
FetchSportsDetailsOperation(@NonNull BaseActivitySummary summary,
|
||||
@NonNull AbstractHuamiActivityDetailsParser detailsParser,
|
||||
@NonNull HuamiSupport support,
|
||||
@NonNull String lastSyncTimeKey,
|
||||
int fetchCount) {
|
||||
super(support);
|
||||
setName("fetching sport details");
|
||||
this.summary = summary;
|
||||
this.detailsParser = detailsParser;
|
||||
this.lastSyncTimeKey = lastSyncTimeKey;
|
||||
this.fetchCount = fetchCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String taskDescription() {
|
||||
return getContext().getString(R.string.busy_task_fetch_sports_details);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startFetching(TransactionBuilder builder) {
|
||||
LOG.info("start " + getName());
|
||||
buffer = new ByteArrayOutputStream(1024);
|
||||
GregorianCalendar sinceWhen = getLastSuccessfulSyncTime();
|
||||
startFetching(builder, AmazfitBipService.COMMAND_ACTIVITY_DATA_TYPE_SPORTS_DETAILS, sinceWhen);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean handleActivityFetchFinish(boolean success) {
|
||||
LOG.info(getName() + " has finished round " + fetchCount);
|
||||
|
||||
boolean parseSuccess = true;
|
||||
|
||||
if (success && buffer.size() > 0) {
|
||||
if (detailsParser instanceof HuamiActivityDetailsParser) {
|
||||
((HuamiActivityDetailsParser) detailsParser).setSkipCounterByte(false); // is already stripped
|
||||
}
|
||||
try {
|
||||
ActivityTrack track = detailsParser.parse(buffer.toByteArray());
|
||||
ActivityTrackExporter exporter = createExporter();
|
||||
String trackType = "track";
|
||||
switch (summary.getActivityKind()) {
|
||||
case ActivityKind.TYPE_CYCLING:
|
||||
trackType = getContext().getString(R.string.activity_type_biking);
|
||||
break;
|
||||
case ActivityKind.TYPE_RUNNING:
|
||||
trackType = getContext().getString(R.string.activity_type_running);
|
||||
break;
|
||||
case ActivityKind.TYPE_WALKING:
|
||||
trackType = getContext().getString(R.string.activity_type_walking);
|
||||
break;
|
||||
case ActivityKind.TYPE_HIKING:
|
||||
trackType = getContext().getString(R.string.activity_type_hiking);
|
||||
break;
|
||||
case ActivityKind.TYPE_CLIMBING:
|
||||
trackType = getContext().getString(R.string.activity_type_climbing);
|
||||
break;
|
||||
case ActivityKind.TYPE_SWIMMING:
|
||||
trackType = getContext().getString(R.string.activity_type_swimming);
|
||||
break;
|
||||
}
|
||||
final String rawBytesPath = saveRawBytes();
|
||||
|
||||
String fileName = FileUtils.makeValidFileName("gadgetbridge-" + trackType.toLowerCase() + "-" + DateTimeUtils.formatIso8601(summary.getStartTime()) + ".gpx");
|
||||
File targetFile = new File(FileUtils.getExternalFilesDir(), fileName);
|
||||
|
||||
boolean exportGpxSuccess = true;
|
||||
try {
|
||||
exporter.performExport(track, targetFile);
|
||||
} catch (ActivityTrackExporter.GPXTrackEmptyException ex) {
|
||||
exportGpxSuccess = false;
|
||||
GB.toast(getContext(), "This activity does not contain GPX tracks.", Toast.LENGTH_LONG, GB.ERROR, ex);
|
||||
}
|
||||
|
||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||
if (exportGpxSuccess) {
|
||||
summary.setGpxTrack(targetFile.getAbsolutePath());
|
||||
}
|
||||
if (rawBytesPath != null) {
|
||||
summary.setRawDetailsPath(rawBytesPath);
|
||||
}
|
||||
dbHandler.getDaoSession().getBaseActivitySummaryDao().update(summary);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
GB.toast(getContext(), "Error getting activity details: " + ex.getMessage(), Toast.LENGTH_LONG, GB.ERROR, ex);
|
||||
parseSuccess = false;
|
||||
}
|
||||
}
|
||||
|
||||
final boolean superSuccess = super.handleActivityFetchFinish(success);
|
||||
|
||||
if (success && parseSuccess) {
|
||||
// Always increment the sync timestamp on success, even if we did not get data
|
||||
GregorianCalendar endTime = BLETypeConversions.createCalendar();
|
||||
endTime.setTime(summary.getEndTime());
|
||||
saveLastSyncTimestamp(endTime);
|
||||
|
||||
if (needsAnotherFetch(endTime)) {
|
||||
FetchSportsSummaryOperation nextOperation = new FetchSportsSummaryOperation(getSupport(), fetchCount);
|
||||
try {
|
||||
nextOperation.perform();
|
||||
} catch (IOException ex) {
|
||||
LOG.error("Error starting another round of fetching activity data", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return superSuccess && parseSuccess;
|
||||
}
|
||||
|
||||
private boolean needsAnotherFetch(GregorianCalendar lastSyncTimestamp) {
|
||||
// We have 2 operations per fetch round: summary + details
|
||||
if (fetchCount > 10) {
|
||||
LOG.warn("Already have 5 fetch rounds, not doing another one.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (DateUtils.isToday(lastSyncTimestamp.getTimeInMillis())) {
|
||||
LOG.info("Hopefully no further fetch needed, last synced timestamp is from today.");
|
||||
return false;
|
||||
}
|
||||
if (lastSyncTimestamp.getTimeInMillis() > System.currentTimeMillis()) {
|
||||
LOG.warn("Not doing another fetch since last synced timestamp is in the future: {}", DateTimeUtils.formatDateTime(lastSyncTimestamp.getTime()));
|
||||
return false;
|
||||
}
|
||||
LOG.info("Doing another fetch since last sync timestamp is still too old: {}", DateTimeUtils.formatDateTime(lastSyncTimestamp.getTime()));
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean validChecksum(int crc32) {
|
||||
return crc32 == CheckSums.getCRC32(buffer.toByteArray());
|
||||
}
|
||||
|
||||
private ActivityTrackExporter createExporter() {
|
||||
GPXExporter exporter = new GPXExporter();
|
||||
exporter.setCreator(GBApplication.app().getNameAndVersion());
|
||||
return exporter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to handle the incoming activity data.
|
||||
* There are two kind of messages we currently know:
|
||||
* - the first one is 11 bytes long and contains metadata (how many bytes to expect, when the data starts, etc.)
|
||||
* - the second one is 20 bytes long and contains the actual activity data
|
||||
* <p/>
|
||||
* The first message type is parsed by this method, for every other length of the value param, bufferActivityData is called.
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
@Override
|
||||
protected void handleActivityNotif(byte[] value) {
|
||||
LOG.warn("sports details: " + Logging.formatBytes(value));
|
||||
|
||||
if (!isOperationRunning()) {
|
||||
LOG.error("ignoring sports details notification because operation is not running. Data length: " + value.length);
|
||||
getSupport().logMessageContent(value);
|
||||
return;
|
||||
}
|
||||
|
||||
if (value.length < 2) {
|
||||
LOG.error("unexpected sports details data length: " + value.length);
|
||||
getSupport().logMessageContent(value);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((byte) (lastPacketCounter + 1) == value[0]) {
|
||||
lastPacketCounter++;
|
||||
bufferActivityData(value);
|
||||
} else {
|
||||
GB.toast("Error " + getName() + ", invalid package counter: " + value[0] + ", last was: " + lastPacketCounter, Toast.LENGTH_LONG, GB.ERROR);
|
||||
handleActivityFetchFinish(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Buffers the given activity summary data. If the total size is reached,
|
||||
* it is converted to an object and saved in the database.
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
@Override
|
||||
protected void bufferActivityData(byte[] value) {
|
||||
buffer.write(value, 1, value.length - 1); // skip the counter
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getLastSyncTimeKey() {
|
||||
return lastSyncTimeKey;
|
||||
}
|
||||
|
||||
protected GregorianCalendar getLastSuccessfulSyncTime() {
|
||||
GregorianCalendar calendar = BLETypeConversions.createCalendar();
|
||||
calendar.setTime(summary.getStartTime());
|
||||
return calendar;
|
||||
}
|
||||
|
||||
private String saveRawBytes() {
|
||||
final String fileName = FileUtils.makeValidFileName(String.format("%s.bin", DateTimeUtils.formatIso8601(summary.getStartTime())));
|
||||
FileOutputStream outputStream = null;
|
||||
|
||||
try {
|
||||
final File targetFolder = new File(FileUtils.getExternalFilesDir(), "rawDetails");
|
||||
targetFolder.mkdirs();
|
||||
final File targetFile = new File(targetFolder, fileName);
|
||||
outputStream = new FileOutputStream(targetFile);
|
||||
outputStream.write(buffer.toByteArray());
|
||||
outputStream.close();
|
||||
return targetFile.getAbsolutePath();
|
||||
} catch (final IOException e) {
|
||||
LOG.error("Failed to save raw bytes", e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -1,196 +0,0 @@
|
||||
/* Copyright (C) 2018-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniel
|
||||
Dakhno, Daniele Gobbetti, José Rebelo, Oleg Vasilev, Petr Vaněk
|
||||
|
||||
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 <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations;
|
||||
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.GregorianCalendar;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.Logging;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiActivitySummaryParser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip.AmazfitBipService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.AbstractHuamiActivityDetailsParser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.CheckSums;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
/**
|
||||
* An operation that fetches activity data. For every fetch, a new operation must
|
||||
* be created, i.e. an operation may not be reused for multiple fetches.
|
||||
*/
|
||||
public class FetchSportsSummaryOperation extends AbstractFetchOperation {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(FetchSportsSummaryOperation.class);
|
||||
|
||||
private ByteArrayOutputStream buffer = new ByteArrayOutputStream(140);
|
||||
public FetchSportsSummaryOperation(HuamiSupport support, int fetchCount) {
|
||||
super(support);
|
||||
setName("fetching sport summaries");
|
||||
this.fetchCount = fetchCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String taskDescription() {
|
||||
return getContext().getString(R.string.busy_task_fetch_sports_summaries);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startFetching(TransactionBuilder builder) {
|
||||
LOG.info("start" + getName());
|
||||
GregorianCalendar sinceWhen = getLastSuccessfulSyncTime();
|
||||
startFetching(builder, AmazfitBipService.COMMAND_ACTIVITY_DATA_TYPE_SPORTS_SUMMARIES, sinceWhen);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean handleActivityFetchFinish(boolean success) {
|
||||
LOG.info(getName() + " has finished round " + fetchCount);
|
||||
|
||||
|
||||
BaseActivitySummary summary = null;
|
||||
final DeviceCoordinator coordinator = getDevice().getDeviceCoordinator();
|
||||
final ActivitySummaryParser summaryParser = coordinator.getActivitySummaryParser(getDevice());
|
||||
|
||||
boolean parseSummarySuccess = true;
|
||||
|
||||
if (success && buffer.size() > 0) {
|
||||
summary = new BaseActivitySummary();
|
||||
summary.setStartTime(getLastStartTimestamp().getTime()); // due to a bug this has to be set
|
||||
summary.setRawSummaryData(buffer.toByteArray());
|
||||
try {
|
||||
summary = summaryParser.parseBinaryData(summary);
|
||||
} catch (final Exception e) {
|
||||
GB.toast(getContext(), "Failed to parse activity summary", Toast.LENGTH_LONG, GB.ERROR, e);
|
||||
summary = null;
|
||||
parseSummarySuccess = false;
|
||||
}
|
||||
|
||||
if (summary != null) {
|
||||
summary.setSummaryData(null); // remove json before saving to database,
|
||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||
DaoSession session = dbHandler.getDaoSession();
|
||||
Device device = DBHelper.getDevice(getDevice(), session);
|
||||
User user = DBHelper.getUser(session);
|
||||
summary.setDevice(device);
|
||||
summary.setUser(user);
|
||||
summary.setRawSummaryData(buffer.toByteArray());
|
||||
session.getBaseActivitySummaryDao().insertOrReplace(summary);
|
||||
} catch (Exception ex) {
|
||||
GB.toast(getContext(), "Error saving activity summary", Toast.LENGTH_LONG, GB.ERROR, ex);
|
||||
parseSummarySuccess = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final boolean superSuccess = super.handleActivityFetchFinish(success);
|
||||
boolean getDetailsSuccess = true;
|
||||
|
||||
if (summary != null) {
|
||||
final AbstractHuamiActivityDetailsParser detailsParser = ((HuamiActivitySummaryParser) summaryParser).getDetailsParser(summary);
|
||||
|
||||
FetchSportsDetailsOperation nextOperation = new FetchSportsDetailsOperation(summary, detailsParser, getSupport(), getLastSyncTimeKey(), fetchCount);
|
||||
try {
|
||||
nextOperation.perform();
|
||||
} catch (IOException ex) {
|
||||
GB.toast(getContext(), "Unable to fetch activity details: " + ex.getMessage(), Toast.LENGTH_LONG, GB.ERROR, ex);
|
||||
getDetailsSuccess = false;
|
||||
}
|
||||
}
|
||||
|
||||
return parseSummarySuccess && superSuccess && getDetailsSuccess;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean validChecksum(int crc32) {
|
||||
return crc32 == CheckSums.getCRC32(buffer.toByteArray());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
|
||||
LOG.warn("characteristic read: " + characteristic.getUuid() + ": " + Logging.formatBytes(characteristic.getValue()));
|
||||
return super.onCharacteristicRead(gatt, characteristic, status);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to handle the incoming activity data.
|
||||
* There are two kind of messages we currently know:
|
||||
* - the first one is 11 bytes long and contains metadata (how many bytes to expect, when the data starts, etc.)
|
||||
* - the second one is 20 bytes long and contains the actual activity data
|
||||
* <p/>
|
||||
* The first message type is parsed by this method, for every other length of the value param, bufferActivityData is called.
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
@Override
|
||||
protected void handleActivityNotif(byte[] value) {
|
||||
LOG.warn("sports summary data: " + Logging.formatBytes(value));
|
||||
|
||||
if (!isOperationRunning()) {
|
||||
LOG.error("ignoring activity data notification because operation is not running. Data length: " + value.length);
|
||||
getSupport().logMessageContent(value);
|
||||
return;
|
||||
}
|
||||
|
||||
if (value.length < 2) {
|
||||
LOG.error("unexpected sports summary data length: " + value.length);
|
||||
getSupport().logMessageContent(value);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((byte) (lastPacketCounter + 1) == value[0]) {
|
||||
lastPacketCounter++;
|
||||
bufferActivityData(value);
|
||||
} else {
|
||||
GB.toast("Error " + getName() + ", invalid package counter: " + value[0] + ", last was: " + lastPacketCounter, Toast.LENGTH_LONG, GB.ERROR);
|
||||
handleActivityFetchFinish(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Buffers the given activity summary data. If the total size is reached,
|
||||
* it is converted to an object and saved in the database.
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
@Override
|
||||
protected void bufferActivityData(byte[] value) {
|
||||
buffer.write(value, 1, value.length - 1); // skip the counter
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected String getLastSyncTimeKey() {
|
||||
return "lastSportsActivityTimeMillis";
|
||||
}
|
||||
}
|
@ -15,7 +15,7 @@
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations;
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch;
|
||||
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
@ -28,6 +28,7 @@ import androidx.annotation.NonNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.text.DateFormat;
|
||||
import java.util.Arrays;
|
||||
@ -42,39 +43,41 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.AbstractGattListenerWriteAction;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceBusyAction;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.AbstractHuamiOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.CheckSums;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
|
||||
|
||||
/**
|
||||
* An operation that fetches activity data. For every fetch, a new operation must
|
||||
* be created, i.e. an operation may not be reused for multiple fetches.
|
||||
* An operation that fetches activity data.
|
||||
*/
|
||||
public abstract class AbstractFetchOperation extends AbstractHuamiOperation {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AbstractFetchOperation.class);
|
||||
|
||||
protected byte lastPacketCounter;
|
||||
int fetchCount;
|
||||
protected BluetoothGattCharacteristic characteristicActivityData;
|
||||
protected BluetoothGattCharacteristic characteristicFetch;
|
||||
Calendar startTimestamp;
|
||||
int expectedDataLength = 0;
|
||||
|
||||
public AbstractFetchOperation(HuamiSupport support) {
|
||||
protected Calendar startTimestamp;
|
||||
|
||||
protected int fetchCount;
|
||||
protected byte lastPacketCounter;
|
||||
protected int expectedDataLength = 0;
|
||||
protected final ByteArrayOutputStream buffer = new ByteArrayOutputStream(1024);
|
||||
|
||||
protected boolean operationValid = true; // to mark operation failed midway (eg. out of sync)
|
||||
|
||||
public AbstractFetchOperation(final HuamiSupport support) {
|
||||
super(support);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void enableNeededNotifications(TransactionBuilder builder, boolean enable) {
|
||||
protected void enableNeededNotifications(final TransactionBuilder builder, final boolean enable) {
|
||||
if (!enable) {
|
||||
// dynamically enabled, but always disabled on finish
|
||||
builder.notify(characteristicFetch, enable);
|
||||
builder.notify(characteristicActivityData, enable);
|
||||
builder.notify(characteristicFetch, false);
|
||||
builder.notify(characteristicActivityData, false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,7 +90,7 @@ public abstract class AbstractFetchOperation extends AbstractHuamiOperation {
|
||||
expectedDataLength = 0;
|
||||
lastPacketCounter = -1;
|
||||
|
||||
TransactionBuilder builder = performInitialized(getName());
|
||||
final TransactionBuilder builder = performInitialized(getName());
|
||||
if (fetchCount == 0) {
|
||||
builder.add(new SetDeviceBusyAction(getDevice(), taskDescription(), getContext()));
|
||||
}
|
||||
@ -114,11 +117,11 @@ public abstract class AbstractFetchOperation extends AbstractHuamiOperation {
|
||||
protected abstract String getLastSyncTimeKey();
|
||||
|
||||
@Override
|
||||
public boolean onCharacteristicChanged(BluetoothGatt gatt,
|
||||
BluetoothGattCharacteristic characteristic) {
|
||||
UUID characteristicUUID = characteristic.getUuid();
|
||||
public boolean onCharacteristicChanged(final BluetoothGatt gatt,
|
||||
final BluetoothGattCharacteristic characteristic) {
|
||||
final UUID characteristicUUID = characteristic.getUuid();
|
||||
if (HuamiService.UUID_CHARACTERISTIC_5_ACTIVITY_DATA.equals(characteristicUUID)) {
|
||||
handleActivityNotif(characteristic.getValue());
|
||||
handleActivityData(characteristic.getValue());
|
||||
return true;
|
||||
} else if (HuamiService.UUID_UNKNOWN_CHARACTERISTIC4.equals(characteristicUUID)) {
|
||||
handleActivityMetadata(characteristic.getValue());
|
||||
@ -129,18 +132,15 @@ public abstract class AbstractFetchOperation extends AbstractHuamiOperation {
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the finishing of fetching the activity.
|
||||
* @param success whether fetching was successful
|
||||
* @return whether handling the activity fetch finish was successful
|
||||
* Handles the finishing of fetching the activity. This signals the actual end of this operation.
|
||||
*/
|
||||
@CallSuper
|
||||
protected boolean handleActivityFetchFinish(boolean success) {
|
||||
protected final void onOperationFinished() {
|
||||
final AbstractFetchOperation nextFetchOperation = getSupport().getNextFetchOperation();
|
||||
if (nextFetchOperation != null) {
|
||||
LOG.debug("Performing next operation {}", nextFetchOperation.getName());
|
||||
try {
|
||||
nextFetchOperation.perform();
|
||||
return true;
|
||||
return;
|
||||
} catch (final IOException e) {
|
||||
GB.toast(
|
||||
getContext(),
|
||||
@ -148,17 +148,16 @@ public abstract class AbstractFetchOperation extends AbstractHuamiOperation {
|
||||
Toast.LENGTH_SHORT,
|
||||
GB.ERROR, e
|
||||
);
|
||||
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
LOG.debug("All operations finished");
|
||||
|
||||
GB.updateTransferNotification(null, "", false, 100, getContext());
|
||||
GB.signalActivityDataFinish();
|
||||
operationFinished();
|
||||
unsetBusy();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -168,72 +167,54 @@ public abstract class AbstractFetchOperation extends AbstractHuamiOperation {
|
||||
* @param crc32 the expected checksum
|
||||
* @return whether the checksum was valid
|
||||
*/
|
||||
protected abstract boolean validChecksum(int crc32);
|
||||
protected boolean validChecksum(int crc32) {
|
||||
return crc32 == CheckSums.getCRC32(buffer.toByteArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to handle the incoming activity data.
|
||||
* There are two kind of messages we currently know:
|
||||
* - the first one is 11 bytes long and contains metadata (how many bytes to expect, when the data starts, etc.)
|
||||
* - the second one is 20 bytes long and contains the actual activity data
|
||||
* <p/>
|
||||
* The first message type is parsed by this method, for every other length of the value param, bufferActivityData is called.
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
protected abstract void handleActivityNotif(byte[] value);
|
||||
protected abstract boolean processBufferedData();
|
||||
|
||||
protected abstract void bufferActivityData(byte[] value);
|
||||
protected void handleActivityData(final byte[] value) {
|
||||
LOG.debug("{} data: {}", getName(), Logging.formatBytes(value));
|
||||
|
||||
protected void startFetching(TransactionBuilder builder, byte fetchType, GregorianCalendar sinceWhen) {
|
||||
final String taskName = StringUtils.ensureNotNull(builder.getTaskName());
|
||||
if (!isOperationRunning()) {
|
||||
LOG.error("ignoring {} notification because operation is not running. Data length: {}", getName(), value.length);
|
||||
getSupport().logMessageContent(value);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((byte) (lastPacketCounter + 1) == value[0]) {
|
||||
// TODO we should handle skipped or repeated bytes more gracefully
|
||||
lastPacketCounter++;
|
||||
bufferActivityData(value);
|
||||
} else {
|
||||
GB.toast("Error " + getName() + ", invalid package counter: " + value[0] + ", last was: " + lastPacketCounter, Toast.LENGTH_LONG, GB.ERROR);
|
||||
operationValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected void bufferActivityData(byte[] value) {
|
||||
buffer.write(value, 1, value.length - 1); // skip the counter
|
||||
}
|
||||
|
||||
protected void startFetching(final TransactionBuilder builder, final byte fetchType, final GregorianCalendar sinceWhen) {
|
||||
final HuamiSupport support = getSupport();
|
||||
final boolean isZeppOs = support instanceof ZeppOsSupport;
|
||||
byte[] fetchBytes = BLETypeConversions.join(new byte[]{
|
||||
HuamiService.COMMAND_ACTIVITY_DATA_START_DATE,
|
||||
fetchType},
|
||||
support.getTimeBytes(sinceWhen, support.getFetchOperationsTimeUnit()));
|
||||
builder.add(new AbstractGattListenerWriteAction(getQueue(), characteristicFetch, fetchBytes) {
|
||||
@Override
|
||||
protected boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
|
||||
UUID characteristicUUID = characteristic.getUuid();
|
||||
if (HuamiService.UUID_UNKNOWN_CHARACTERISTIC4.equals(characteristicUUID)) {
|
||||
byte[] value = characteristic.getValue();
|
||||
|
||||
if (ArrayUtils.equals(value, HuamiService.RESPONSE_ACTIVITY_DATA_START_DATE_SUCCESS, 0)) {
|
||||
handleActivityMetadata(value);
|
||||
if (expectedDataLength == 0 && isZeppOs) {
|
||||
// Nothing to receive, if we try to fetch data it will fail
|
||||
sendAckZeppOs(true);
|
||||
} else if (expectedDataLength != 0) {
|
||||
TransactionBuilder newBuilder = createTransactionBuilder(taskName + " Step 2");
|
||||
newBuilder.notify(characteristicActivityData, true);
|
||||
newBuilder.write(characteristicFetch, new byte[]{HuamiService.COMMAND_FETCH_DATA});
|
||||
try {
|
||||
performImmediately(newBuilder);
|
||||
} catch (IOException ex) {
|
||||
GB.toast(getContext(), "Error fetching debug logs: " + ex.getMessage(), Toast.LENGTH_LONG, GB.ERROR, ex);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
handleActivityMetadata(value);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
builder.write(characteristicFetch, fetchBytes);
|
||||
}
|
||||
|
||||
private void handleActivityMetadata(byte[] value) {
|
||||
if (value.length < 3) {
|
||||
LOG.warn("Activity metadata too short: {}", Logging.formatBytes(value));
|
||||
handleActivityFetchFinish(false);
|
||||
onOperationFinished();
|
||||
return;
|
||||
}
|
||||
|
||||
if (value[0] != HuamiService.RESPONSE) {
|
||||
LOG.warn("Activity metadata not a response: {}", Logging.formatBytes(value));
|
||||
handleActivityFetchFinish(false);
|
||||
onOperationFinished();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -245,26 +226,26 @@ public abstract class AbstractFetchOperation extends AbstractHuamiOperation {
|
||||
handleFetchDataResponse(value);
|
||||
return;
|
||||
case HuamiService.COMMAND_ACK_ACTIVITY_DATA:
|
||||
// ignore, this is just the reply to the COMMAND_ACK_ACTIVITY_DATA
|
||||
LOG.info("Got reply to COMMAND_ACK_ACTIVITY_DATA");
|
||||
onOperationFinished();
|
||||
return;
|
||||
default:
|
||||
LOG.warn("Unexpected activity metadata: {}", Logging.formatBytes(value));
|
||||
handleActivityFetchFinish(false);
|
||||
onOperationFinished();
|
||||
}
|
||||
}
|
||||
|
||||
private void handleStartDateResponse(final byte[] value) {
|
||||
if (value[2] != HuamiService.SUCCESS) {
|
||||
LOG.warn("Start date unsuccessful response: {}", Logging.formatBytes(value));
|
||||
handleActivityFetchFinish(false);
|
||||
onOperationFinished();
|
||||
return;
|
||||
}
|
||||
|
||||
// it's 16 on the MB7, with a 0 at the end
|
||||
if (value.length != 15 && (value.length != 16 && value[15] != 0x00)) {
|
||||
LOG.warn("Start date response length: {}", Logging.formatBytes(value));
|
||||
handleActivityFetchFinish(false);
|
||||
onOperationFinished();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -277,7 +258,8 @@ public abstract class AbstractFetchOperation extends AbstractHuamiOperation {
|
||||
|
||||
if (expectedDataLength == 0) {
|
||||
LOG.info("No data to fetch since {}", startTimestamp.getTime());
|
||||
handleActivityFetchFinish(true);
|
||||
sendAck(true);
|
||||
// do not finish the operation - do it in the ack response
|
||||
return;
|
||||
}
|
||||
|
||||
@ -287,85 +269,108 @@ public abstract class AbstractFetchOperation extends AbstractHuamiOperation {
|
||||
GB.updateTransferNotification(taskDescription(),
|
||||
getContext().getString(R.string.FetchActivityOperation_about_to_transfer_since,
|
||||
DateFormat.getDateTimeInstance().format(startTimestamp.getTime())), true, 0, getContext());
|
||||
|
||||
// Trigger the actual data fetch
|
||||
final TransactionBuilder step2builder = createTransactionBuilder(getName() + " Step 2");
|
||||
step2builder.notify(characteristicActivityData, true);
|
||||
step2builder.write(characteristicFetch, new byte[]{HuamiService.COMMAND_FETCH_DATA});
|
||||
try {
|
||||
performImmediately(step2builder);
|
||||
} catch (final IOException e) {
|
||||
GB.toast(getContext(), "Error starting fetch step 2: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e);
|
||||
onOperationFinished();
|
||||
}
|
||||
}
|
||||
|
||||
private void handleFetchDataResponse(final byte[] value) {
|
||||
if (value[2] != HuamiService.SUCCESS) {
|
||||
LOG.warn("Fetch data unsuccessful response: {}", Logging.formatBytes(value));
|
||||
handleActivityFetchFinish(false);
|
||||
onOperationFinished();
|
||||
return;
|
||||
}
|
||||
|
||||
if (value.length != 3 && value.length != 7) {
|
||||
LOG.warn("Fetch data unexpected metadata length: {}", Logging.formatBytes(value));
|
||||
handleActivityFetchFinish(false);
|
||||
onOperationFinished();
|
||||
return;
|
||||
}
|
||||
|
||||
if (value.length == 7 && !validChecksum(BLETypeConversions.toUint32(value, 3))) {
|
||||
LOG.warn("Data checksum invalid");
|
||||
handleActivityFetchFinish(false);
|
||||
sendAckZeppOs(true);
|
||||
// If we're on Zepp OS, ack but keep data on device
|
||||
if (isZeppOs()) {
|
||||
sendAck(true);
|
||||
// do not finish the operation - do it in the ack response
|
||||
return;
|
||||
}
|
||||
onOperationFinished();
|
||||
return;
|
||||
}
|
||||
|
||||
boolean handleFinishSuccess;
|
||||
try {
|
||||
handleFinishSuccess = handleActivityFetchFinish(true);
|
||||
} catch (final Exception e) {
|
||||
LOG.warn("Failed to handle activity fetch finish", e);
|
||||
handleFinishSuccess = false;
|
||||
final boolean success = operationValid && processBufferedData();
|
||||
|
||||
final boolean keepActivityDataOnDevice = !success || HuamiCoordinator.getKeepActivityDataOnDevice(getDevice().getAddress());
|
||||
if (isZeppOs() || !keepActivityDataOnDevice) {
|
||||
sendAck(keepActivityDataOnDevice);
|
||||
// do not finish the operation - do it in the ack response
|
||||
return;
|
||||
}
|
||||
|
||||
final boolean keepActivityDataOnDevice = HuamiCoordinator.getKeepActivityDataOnDevice(getDevice().getAddress());
|
||||
|
||||
sendAckZeppOs(keepActivityDataOnDevice || !handleFinishSuccess);
|
||||
onOperationFinished();
|
||||
}
|
||||
|
||||
protected void sendAckZeppOs(final boolean keepDataOnDevice) {
|
||||
if (!(getSupport() instanceof ZeppOsSupport)) {
|
||||
return;
|
||||
protected void sendAck(final boolean keepDataOnDevice) {
|
||||
final byte[] ackBytes;
|
||||
|
||||
if (isZeppOs()) {
|
||||
LOG.debug("Sending ack, keepDataOnDevice = {}", keepDataOnDevice);
|
||||
|
||||
// 0x01 to ACK, mark as saved on phone (drop from band)
|
||||
// 0x09 to ACK, but keep it marked as not saved
|
||||
// If 0x01 is sent, detailed information seems to be discarded, and is not sent again anymore
|
||||
final byte ackByte = (byte) (keepDataOnDevice ? 0x09 : 0x01);
|
||||
ackBytes = new byte[]{HuamiService.COMMAND_ACK_ACTIVITY_DATA, ackByte};
|
||||
} else {
|
||||
LOG.debug("Sending ack, simple");
|
||||
ackBytes = new byte[]{HuamiService.COMMAND_ACK_ACTIVITY_DATA};
|
||||
}
|
||||
|
||||
LOG.debug("Sending Zepp OS ack, keepDataOnDevice = {}", keepDataOnDevice);
|
||||
|
||||
// 0x01 to ACK, mark as saved on phone (drop from band)
|
||||
// 0x09 to ACK, but keep it marked as not saved
|
||||
// If 0x01 is sent, detailed information seems to be discarded, and is not sent again anymore
|
||||
final byte ackByte = (byte) (keepDataOnDevice ? 0x09 : 0x01);
|
||||
|
||||
try {
|
||||
final TransactionBuilder builder = performInitialized(getName() + " end");
|
||||
builder.write(characteristicFetch, new byte[]{HuamiService.COMMAND_ACK_ACTIVITY_DATA, ackByte});
|
||||
final TransactionBuilder builder = createTransactionBuilder(getName() + " end");
|
||||
builder.write(characteristicFetch, ackBytes);
|
||||
performImmediately(builder);
|
||||
} catch (final IOException e) {
|
||||
LOG.error("Ending failed", e);
|
||||
LOG.error("Failed to send ack", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void setStartTimestamp(Calendar startTimestamp) {
|
||||
private void setStartTimestamp(final Calendar startTimestamp) {
|
||||
this.startTimestamp = startTimestamp;
|
||||
}
|
||||
|
||||
Calendar getLastStartTimestamp() {
|
||||
protected Calendar getLastStartTimestamp() {
|
||||
return startTimestamp;
|
||||
}
|
||||
|
||||
void saveLastSyncTimestamp(@NonNull GregorianCalendar timestamp) {
|
||||
SharedPreferences.Editor editor = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()).edit();
|
||||
protected void saveLastSyncTimestamp(@NonNull final GregorianCalendar timestamp) {
|
||||
final SharedPreferences.Editor editor = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()).edit();
|
||||
editor.putLong(getLastSyncTimeKey(), timestamp.getTimeInMillis());
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
protected GregorianCalendar getLastSuccessfulSyncTime() {
|
||||
long timeStampMillis = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()).getLong(getLastSyncTimeKey(), 0);
|
||||
final long timeStampMillis = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()).getLong(getLastSyncTimeKey(), 0);
|
||||
if (timeStampMillis != 0) {
|
||||
GregorianCalendar calendar = BLETypeConversions.createCalendar();
|
||||
calendar.setTimeInMillis(timeStampMillis);
|
||||
return calendar;
|
||||
}
|
||||
GregorianCalendar calendar = BLETypeConversions.createCalendar();
|
||||
final GregorianCalendar calendar = BLETypeConversions.createCalendar();
|
||||
calendar.add(Calendar.DAY_OF_MONTH, -100);
|
||||
return calendar;
|
||||
}
|
||||
|
||||
protected boolean isZeppOs() {
|
||||
return getSupport() instanceof ZeppOsSupport;
|
||||
}
|
||||
}
|
@ -14,32 +14,22 @@
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations;
|
||||
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.widget.Toast;
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.Locale;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.Logging;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.CheckSums;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
/**
|
||||
* A repeating fetch operation. This operation repeats the fetch up to a certain number of times, or
|
||||
@ -49,21 +39,19 @@ import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
public abstract class AbstractRepeatingFetchOperation extends AbstractFetchOperation {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AbstractRepeatingFetchOperation.class);
|
||||
|
||||
private final ByteArrayOutputStream byteStreamBuffer = new ByteArrayOutputStream(140);
|
||||
protected final HuamiFetchDataType dataType;
|
||||
|
||||
protected final byte dataType;
|
||||
|
||||
public AbstractRepeatingFetchOperation(final HuamiSupport support, final byte dataType, final String dataName) {
|
||||
public AbstractRepeatingFetchOperation(final HuamiSupport support, final HuamiFetchDataType dataType) {
|
||||
super(support);
|
||||
this.dataType = dataType;
|
||||
setName("fetching " + dataName);
|
||||
setName("fetching " + dataType.name());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startFetching(final TransactionBuilder builder) {
|
||||
final GregorianCalendar sinceWhen = getLastSuccessfulSyncTime();
|
||||
LOG.info("start {} since {}", getName(), sinceWhen.getTime());
|
||||
startFetching(builder, dataType, sinceWhen);
|
||||
startFetching(builder, dataType.getCode(), sinceWhen);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -77,21 +65,14 @@ public abstract class AbstractRepeatingFetchOperation extends AbstractFetchOpera
|
||||
protected abstract boolean handleActivityData(GregorianCalendar timestamp, byte[] bytes);
|
||||
|
||||
@Override
|
||||
protected boolean handleActivityFetchFinish(final boolean success) {
|
||||
LOG.info("{} has finished round {}: {}, got {} bytes in buffer", getName(), fetchCount, success, byteStreamBuffer.size());
|
||||
protected boolean processBufferedData() {
|
||||
LOG.info("{} has finished round {}, got {} bytes in buffer", getName(), fetchCount, buffer.size());
|
||||
|
||||
if (!success) {
|
||||
// We need to explicitly ack this, or the next operation will fail fetch will become stuck
|
||||
sendAckZeppOs(true);
|
||||
super.handleActivityFetchFinish(false);
|
||||
return false;
|
||||
if (buffer.size() == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (byteStreamBuffer.size() == 0) {
|
||||
return super.handleActivityFetchFinish(true);
|
||||
}
|
||||
|
||||
final byte[] bytes = byteStreamBuffer.toByteArray();
|
||||
final byte[] bytes = buffer.toByteArray();
|
||||
final GregorianCalendar timestamp = (GregorianCalendar) this.startTimestamp.clone();
|
||||
|
||||
// Uncomment to dump the bytes to external storage for debugging
|
||||
@ -100,7 +81,6 @@ public abstract class AbstractRepeatingFetchOperation extends AbstractFetchOpera
|
||||
final boolean handleSuccess = handleActivityData(timestamp, bytes);
|
||||
|
||||
if (!handleSuccess) {
|
||||
super.handleActivityFetchFinish(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -108,63 +88,12 @@ public abstract class AbstractRepeatingFetchOperation extends AbstractFetchOpera
|
||||
saveLastSyncTimestamp(timestamp);
|
||||
|
||||
if (needsAnotherFetch(timestamp)) {
|
||||
byteStreamBuffer.reset();
|
||||
buffer.reset();
|
||||
|
||||
try {
|
||||
final boolean keepActivityDataOnDevice = HuamiCoordinator.getKeepActivityDataOnDevice(getDevice().getAddress());
|
||||
sendAckZeppOs(keepActivityDataOnDevice);
|
||||
startFetching();
|
||||
return true;
|
||||
} catch (final IOException ex) {
|
||||
LOG.error("Error starting another round of " + getName(), ex);
|
||||
super.handleActivityFetchFinish(false);
|
||||
return false;
|
||||
}
|
||||
getSupport().getFetchOperationQueue().add(0, this);
|
||||
}
|
||||
|
||||
final boolean superSuccess = super.handleActivityFetchFinish(true);
|
||||
postActivityFetchFinish(superSuccess);
|
||||
return superSuccess;
|
||||
}
|
||||
|
||||
protected void postActivityFetchFinish(final boolean success) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean validChecksum(final int crc32) {
|
||||
return crc32 == CheckSums.getCRC32(byteStreamBuffer.toByteArray());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCharacteristicRead(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final int status) {
|
||||
LOG.debug("characteristic read: {}: {}", characteristic.getUuid(), Logging.formatBytes(characteristic.getValue()));
|
||||
return super.onCharacteristicRead(gatt, characteristic, status);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleActivityNotif(final byte[] value) {
|
||||
LOG.debug("{} data: {}", getName(), Logging.formatBytes(value));
|
||||
|
||||
if (!isOperationRunning()) {
|
||||
LOG.error("ignoring {} notification because operation is not running. Data length: {}", getName(), value.length);
|
||||
getSupport().logMessageContent(value);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((byte) (lastPacketCounter + 1) == value[0]) {
|
||||
// TODO we should handle skipped or repeated bytes more gracefully
|
||||
lastPacketCounter++;
|
||||
bufferActivityData(value);
|
||||
} else {
|
||||
GB.toast("Error " + getName() + ", invalid package counter: " + value[0] + ", last was: " + lastPacketCounter, Toast.LENGTH_LONG, GB.ERROR);
|
||||
handleActivityFetchFinish(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void bufferActivityData(final byte[] value) {
|
||||
byteStreamBuffer.write(value, 1, value.length - 1); // skip the counter
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean needsAnotherFetch(final GregorianCalendar lastSyncTimestamp) {
|
@ -15,7 +15,7 @@
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations;
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch;
|
||||
|
||||
import android.widget.Toast;
|
||||
|
||||
@ -36,7 +36,6 @@ import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuamiExtendedActivitySample;
|
||||
@ -44,7 +43,6 @@ import nodomain.freeyourgadget.gadgetbridge.entities.MiBandActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
/**
|
||||
@ -57,7 +55,7 @@ public class FetchActivityOperation extends AbstractRepeatingFetchOperation {
|
||||
private final int sampleSize;
|
||||
|
||||
public FetchActivityOperation(final HuamiSupport support) {
|
||||
super(support, HuamiService.COMMAND_ACTIVITY_DATA_TYPE_ACTIVTY, "activity data");
|
||||
super(support, HuamiFetchDataType.ACTIVITY);
|
||||
this.sampleSize = support.getActivitySampleSize();
|
||||
}
|
||||
|
||||
@ -131,11 +129,6 @@ public class FetchActivityOperation extends AbstractRepeatingFetchOperation {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void postActivityFetchFinish(final boolean success) {
|
||||
GB.signalActivityDataFinish();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean validChecksum(final int crc32) {
|
||||
// TODO actually check it
|
@ -14,9 +14,9 @@
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations;
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch;
|
||||
|
||||
import android.widget.Toast;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -25,27 +25,21 @@ import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.Locale;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip.AmazfitBipService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class HuamiFetchDebugLogsOperation extends AbstractFetchOperation {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(HuamiFetchDebugLogsOperation.class);
|
||||
public class FetchDebugLogsOperation extends AbstractFetchOperation {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(FetchDebugLogsOperation.class);
|
||||
|
||||
private FileOutputStream logOutputStream;
|
||||
|
||||
public HuamiFetchDebugLogsOperation(HuamiSupport support) {
|
||||
public FetchDebugLogsOperation(final HuamiSupport support) {
|
||||
super(support);
|
||||
setName("fetch debug logs");
|
||||
}
|
||||
@ -56,7 +50,7 @@ public class HuamiFetchDebugLogsOperation extends AbstractFetchOperation {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startFetching(TransactionBuilder builder) {
|
||||
protected void startFetching(final TransactionBuilder builder) {
|
||||
File dir;
|
||||
try {
|
||||
dir = FileUtils.getExternalFilesDir();
|
||||
@ -64,39 +58,37 @@ public class HuamiFetchDebugLogsOperation extends AbstractFetchOperation {
|
||||
return;
|
||||
}
|
||||
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd-HHmmss", Locale.US);
|
||||
String filename = "huamidebug_" + dateFormat.format(new Date()) + ".log";
|
||||
final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd-HHmmss", Locale.US);
|
||||
final String filename = "huamidebug_" + dateFormat.format(new Date()) + ".log";
|
||||
|
||||
File outputFile = new File(dir, filename );
|
||||
File outputFile = new File(dir, filename);
|
||||
try {
|
||||
logOutputStream = new FileOutputStream(outputFile);
|
||||
} catch (IOException e) {
|
||||
LOG.warn("could not create file " + outputFile, e);
|
||||
return;
|
||||
}
|
||||
|
||||
GregorianCalendar sinceWhen = BLETypeConversions.createCalendar();
|
||||
sinceWhen.add(Calendar.DAY_OF_MONTH, -10);
|
||||
startFetching(builder, AmazfitBipService.COMMAND_ACTIVITY_DATA_TYPE_DEBUGLOGS, sinceWhen);
|
||||
final GregorianCalendar sinceWhen = getLastSuccessfulSyncTime();
|
||||
startFetching(builder, HuamiFetchDataType.DEBUG_LOGS.getCode(), sinceWhen);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getLastSyncTimeKey() {
|
||||
return null;
|
||||
return "lastDebugTimeMillis";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean handleActivityFetchFinish(boolean success) {
|
||||
protected boolean processBufferedData() {
|
||||
LOG.info("{} data has finished", getName());
|
||||
try {
|
||||
logOutputStream.close();
|
||||
logOutputStream = null;
|
||||
} catch (IOException e) {
|
||||
LOG.warn("could not close output stream", e);
|
||||
} catch (final IOException e) {
|
||||
LOG.error("could not close output stream", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
return super.handleActivityFetchFinish(success);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -106,30 +98,12 @@ public class HuamiFetchDebugLogsOperation extends AbstractFetchOperation {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleActivityNotif(byte[] value) {
|
||||
if (!isOperationRunning()) {
|
||||
LOG.error("ignoring notification because operation is not running. Data length: " + value.length);
|
||||
getSupport().logMessageContent(value);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((byte) (lastPacketCounter + 1) == value[0]) {
|
||||
lastPacketCounter++;
|
||||
bufferActivityData(value);
|
||||
} else {
|
||||
GB.toast("Error " + getName() + " invalid package counter: " + value[0], Toast.LENGTH_LONG, GB.ERROR);
|
||||
handleActivityFetchFinish(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void bufferActivityData(@NonNull byte[] value) {
|
||||
try {
|
||||
logOutputStream.write(value, 1, value.length - 1);
|
||||
} catch (final IOException e) {
|
||||
LOG.warn("could not write to output stream", e);
|
||||
handleActivityFetchFinish(false);
|
||||
LOG.error("could not write to output stream", e);
|
||||
}
|
||||
}
|
||||
}
|
@ -14,7 +14,7 @@
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations;
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch;
|
||||
|
||||
import android.widget.Toast;
|
||||
|
||||
@ -33,15 +33,12 @@ import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiHeartRateManualSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuamiHeartRateManualSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuamiHeartRateManualSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
/**
|
||||
@ -51,7 +48,7 @@ public class FetchHeartRateManualOperation extends AbstractRepeatingFetchOperati
|
||||
private static final Logger LOG = LoggerFactory.getLogger(FetchHeartRateManualOperation.class);
|
||||
|
||||
public FetchHeartRateManualOperation(final HuamiSupport support) {
|
||||
super(support, HuamiService.COMMAND_ACTIVITY_DATA_TYPE_MANUAL_HEART_RATE, "manual hr data");
|
||||
super(support, HuamiFetchDataType.MANUAL_HEART_RATE);
|
||||
}
|
||||
|
||||
@Override
|
@ -14,7 +14,7 @@
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations;
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch;
|
||||
|
||||
import android.widget.Toast;
|
||||
|
||||
@ -32,7 +32,6 @@ import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiHeartRateMaxSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
@ -40,7 +39,6 @@ import nodomain.freeyourgadget.gadgetbridge.entities.HuamiHeartRateMaxSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
/**
|
||||
@ -50,7 +48,7 @@ public class FetchHeartRateMaxOperation extends AbstractRepeatingFetchOperation
|
||||
private static final Logger LOG = LoggerFactory.getLogger(FetchHeartRateMaxOperation.class);
|
||||
|
||||
public FetchHeartRateMaxOperation(final HuamiSupport support) {
|
||||
super(support, HuamiService.COMMAND_ACTIVITY_DATA_TYPE_MAX_HEART_RATE, "max hr data");
|
||||
super(support, HuamiFetchDataType.MAX_HEART_RATE);
|
||||
}
|
||||
|
||||
@Override
|
@ -14,7 +14,7 @@
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations;
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch;
|
||||
|
||||
import android.widget.Toast;
|
||||
|
||||
@ -33,14 +33,12 @@ import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiHeartRateRestingSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuamiHeartRateRestingSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
/**
|
||||
@ -50,7 +48,7 @@ public class FetchHeartRateRestingOperation extends AbstractRepeatingFetchOperat
|
||||
private static final Logger LOG = LoggerFactory.getLogger(FetchHeartRateRestingOperation.class);
|
||||
|
||||
public FetchHeartRateRestingOperation(final HuamiSupport support) {
|
||||
super(support, HuamiService.COMMAND_ACTIVITY_DATA_TYPE_RESTING_HEART_RATE, "resting hr data");
|
||||
super(support, HuamiFetchDataType.RESTING_HEART_RATE);
|
||||
}
|
||||
|
||||
@Override
|
@ -14,7 +14,7 @@
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations;
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch;
|
||||
|
||||
import android.widget.Toast;
|
||||
|
||||
@ -32,14 +32,12 @@ import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiPaiSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuamiPaiSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
/**
|
||||
@ -49,7 +47,7 @@ public class FetchPaiOperation extends AbstractRepeatingFetchOperation {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(FetchPaiOperation.class);
|
||||
|
||||
public FetchPaiOperation(final HuamiSupport support) {
|
||||
super(support, HuamiService.COMMAND_ACTIVITY_DATA_TYPE_PAI, "pai data");
|
||||
super(support, HuamiFetchDataType.PAI);
|
||||
}
|
||||
|
||||
@Override
|
@ -14,7 +14,7 @@
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations;
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch;
|
||||
|
||||
import android.widget.Toast;
|
||||
|
||||
@ -32,14 +32,12 @@ import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiSleepRespiratoryRateSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuamiSleepRespiratoryRateSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
/**
|
||||
@ -49,7 +47,7 @@ public class FetchSleepRespiratoryRateOperation extends AbstractRepeatingFetchOp
|
||||
private static final Logger LOG = LoggerFactory.getLogger(FetchSleepRespiratoryRateOperation.class);
|
||||
|
||||
public FetchSleepRespiratoryRateOperation(final HuamiSupport support) {
|
||||
super(support, HuamiService.COMMAND_ACTIVITY_DATA_TYPE_SLEEP_RESPIRATORY_RATE, "sleep respiratory rate data");
|
||||
super(support, HuamiFetchDataType.SLEEP_RESPIRATORY_RATE);
|
||||
}
|
||||
|
||||
@Override
|
@ -14,7 +14,7 @@
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations;
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch;
|
||||
|
||||
import android.widget.Toast;
|
||||
|
||||
@ -32,7 +32,6 @@ import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiSpo2SampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
@ -40,7 +39,6 @@ import nodomain.freeyourgadget.gadgetbridge.entities.HuamiSpo2Sample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
/**
|
||||
@ -50,7 +48,7 @@ public class FetchSpo2NormalOperation extends AbstractRepeatingFetchOperation {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(FetchSpo2NormalOperation.class);
|
||||
|
||||
public FetchSpo2NormalOperation(final HuamiSupport support) {
|
||||
super(support, HuamiService.COMMAND_ACTIVITY_DATA_TYPE_SPO2_NORMAL, "spo2 normal data");
|
||||
super(support, HuamiFetchDataType.SPO2_NORMAL);
|
||||
}
|
||||
|
||||
@Override
|
@ -14,7 +14,7 @@
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations;
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -24,9 +24,7 @@ import java.nio.ByteOrder;
|
||||
import java.util.GregorianCalendar;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
/**
|
||||
* An operation that fetches SPO2 data for sleep measurements (this requires sleep breathing quality enabled).
|
||||
@ -35,7 +33,7 @@ public class FetchSpo2SleepOperation extends AbstractRepeatingFetchOperation {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(FetchSpo2SleepOperation.class);
|
||||
|
||||
public FetchSpo2SleepOperation(final HuamiSupport support) {
|
||||
super(support, HuamiService.COMMAND_ACTIVITY_DATA_TYPE_SPO2_SLEEP, "spo2 sleep data");
|
||||
super(support, HuamiFetchDataType.SPO2_SLEEP);
|
||||
}
|
||||
|
||||
@Override
|
@ -0,0 +1,222 @@
|
||||
/* Copyright (C) 2018-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti, José Rebelo, Oleg Vasilev, Sebastian Krey, Your Name
|
||||
|
||||
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 <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch;
|
||||
|
||||
import android.text.format.DateUtils;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.GregorianCalendar;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
|
||||
import nodomain.freeyourgadget.gadgetbridge.export.ActivityTrackExporter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.export.GPXExporter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityTrack;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.AbstractHuamiActivityDetailsParser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiActivityDetailsParser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
/**
|
||||
* An operation that fetches activity data. For every fetch, a new operation must
|
||||
* be created, i.e. an operation may not be reused for multiple fetches.
|
||||
*/
|
||||
public class FetchSportsDetailsOperation extends AbstractFetchOperation {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(FetchSportsDetailsOperation.class);
|
||||
private final AbstractHuamiActivityDetailsParser detailsParser;
|
||||
private final BaseActivitySummary summary;
|
||||
private final String lastSyncTimeKey;
|
||||
|
||||
FetchSportsDetailsOperation(@NonNull BaseActivitySummary summary,
|
||||
@NonNull AbstractHuamiActivityDetailsParser detailsParser,
|
||||
@NonNull HuamiSupport support,
|
||||
@NonNull String lastSyncTimeKey,
|
||||
int fetchCount) {
|
||||
super(support);
|
||||
setName("fetching sport details");
|
||||
this.summary = summary;
|
||||
this.detailsParser = detailsParser;
|
||||
this.lastSyncTimeKey = lastSyncTimeKey;
|
||||
this.fetchCount = fetchCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String taskDescription() {
|
||||
return getContext().getString(R.string.busy_task_fetch_sports_details);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startFetching(TransactionBuilder builder) {
|
||||
LOG.info("start " + getName());
|
||||
final GregorianCalendar sinceWhen = getLastSuccessfulSyncTime();
|
||||
startFetching(builder, HuamiFetchDataType.SPORTS_DETAILS.getCode(), sinceWhen);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean processBufferedData() {
|
||||
LOG.info("{} has finished round {}", getName(), fetchCount);
|
||||
|
||||
if (buffer.size() == 0) {
|
||||
LOG.warn("Buffer is empty");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (detailsParser instanceof HuamiActivityDetailsParser) {
|
||||
((HuamiActivityDetailsParser) detailsParser).setSkipCounterByte(false); // is already stripped
|
||||
}
|
||||
|
||||
try {
|
||||
final ActivityTrack track = detailsParser.parse(buffer.toByteArray());
|
||||
final ActivityTrackExporter exporter = createExporter();
|
||||
final String trackType;
|
||||
switch (summary.getActivityKind()) {
|
||||
case ActivityKind.TYPE_CYCLING:
|
||||
trackType = getContext().getString(R.string.activity_type_biking);
|
||||
break;
|
||||
case ActivityKind.TYPE_RUNNING:
|
||||
trackType = getContext().getString(R.string.activity_type_running);
|
||||
break;
|
||||
case ActivityKind.TYPE_WALKING:
|
||||
trackType = getContext().getString(R.string.activity_type_walking);
|
||||
break;
|
||||
case ActivityKind.TYPE_HIKING:
|
||||
trackType = getContext().getString(R.string.activity_type_hiking);
|
||||
break;
|
||||
case ActivityKind.TYPE_CLIMBING:
|
||||
trackType = getContext().getString(R.string.activity_type_climbing);
|
||||
break;
|
||||
case ActivityKind.TYPE_SWIMMING:
|
||||
trackType = getContext().getString(R.string.activity_type_swimming);
|
||||
break;
|
||||
default:
|
||||
trackType = "track";
|
||||
break;
|
||||
}
|
||||
|
||||
final String rawBytesPath = saveRawBytes();
|
||||
|
||||
final String fileName = FileUtils.makeValidFileName("gadgetbridge-" + trackType.toLowerCase() + "-" + DateTimeUtils.formatIso8601(summary.getStartTime()) + ".gpx");
|
||||
final File targetFile = new File(FileUtils.getExternalFilesDir(), fileName);
|
||||
|
||||
boolean exportGpxSuccess = true;
|
||||
try {
|
||||
exporter.performExport(track, targetFile);
|
||||
} catch (final ActivityTrackExporter.GPXTrackEmptyException ex) {
|
||||
exportGpxSuccess = false;
|
||||
}
|
||||
|
||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||
if (exportGpxSuccess) {
|
||||
summary.setGpxTrack(targetFile.getAbsolutePath());
|
||||
}
|
||||
if (rawBytesPath != null) {
|
||||
summary.setRawDetailsPath(rawBytesPath);
|
||||
}
|
||||
dbHandler.getDaoSession().getBaseActivitySummaryDao().update(summary);
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
GB.toast(getContext(), "Error saving activity details: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Always increment the sync timestamp on success, even if we did not get data
|
||||
final GregorianCalendar endTime = BLETypeConversions.createCalendar();
|
||||
endTime.setTime(summary.getEndTime());
|
||||
saveLastSyncTimestamp(endTime);
|
||||
|
||||
if (needsAnotherFetch(endTime)) {
|
||||
final FetchSportsSummaryOperation nextOperation = new FetchSportsSummaryOperation(getSupport(), fetchCount);
|
||||
getSupport().getFetchOperationQueue().add(0, nextOperation);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean needsAnotherFetch(GregorianCalendar lastSyncTimestamp) {
|
||||
// We have 2 operations per fetch round: summary + details
|
||||
if (fetchCount > 10) {
|
||||
LOG.warn("Already have 5 fetch rounds, not doing another one.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (DateUtils.isToday(lastSyncTimestamp.getTimeInMillis())) {
|
||||
LOG.info("Hopefully no further fetch needed, last synced timestamp is from today.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (lastSyncTimestamp.getTimeInMillis() > System.currentTimeMillis()) {
|
||||
LOG.warn("Not doing another fetch since last synced timestamp is in the future: {}", DateTimeUtils.formatDateTime(lastSyncTimestamp.getTime()));
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG.info("Doing another fetch since last sync timestamp is still too old: {}", DateTimeUtils.formatDateTime(lastSyncTimestamp.getTime()));
|
||||
return true;
|
||||
}
|
||||
|
||||
private ActivityTrackExporter createExporter() {
|
||||
final GPXExporter exporter = new GPXExporter();
|
||||
exporter.setCreator(GBApplication.app().getNameAndVersion());
|
||||
return exporter;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getLastSyncTimeKey() {
|
||||
return lastSyncTimeKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GregorianCalendar getLastSuccessfulSyncTime() {
|
||||
final GregorianCalendar calendar = BLETypeConversions.createCalendar();
|
||||
calendar.setTime(summary.getStartTime());
|
||||
return calendar;
|
||||
}
|
||||
|
||||
private String saveRawBytes() {
|
||||
final String fileName = FileUtils.makeValidFileName(String.format("%s.bin", DateTimeUtils.formatIso8601(summary.getStartTime())));
|
||||
FileOutputStream outputStream = null;
|
||||
|
||||
try {
|
||||
final File targetFolder = new File(FileUtils.getExternalFilesDir(), "rawDetails");
|
||||
targetFolder.mkdirs();
|
||||
final File targetFile = new File(targetFolder, fileName);
|
||||
outputStream = new FileOutputStream(targetFile);
|
||||
outputStream.write(buffer.toByteArray());
|
||||
outputStream.close();
|
||||
return targetFile.getAbsolutePath();
|
||||
} catch (final IOException e) {
|
||||
LOG.error("Failed to save raw bytes", e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,119 @@
|
||||
/* Copyright (C) 2018-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniel
|
||||
Dakhno, Daniele Gobbetti, José Rebelo, Oleg Vasilev, Petr Vaněk
|
||||
|
||||
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 <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch;
|
||||
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.GregorianCalendar;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiActivitySummaryParser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.AbstractHuamiActivityDetailsParser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
/**
|
||||
* An operation that fetches activity data. For every fetch, a new operation must
|
||||
* be created, i.e. an operation may not be reused for multiple fetches.
|
||||
*/
|
||||
public class FetchSportsSummaryOperation extends AbstractFetchOperation {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(FetchSportsSummaryOperation.class);
|
||||
|
||||
public FetchSportsSummaryOperation(HuamiSupport support, int fetchCount) {
|
||||
super(support);
|
||||
setName("fetching sport summaries");
|
||||
this.fetchCount = fetchCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String taskDescription() {
|
||||
return getContext().getString(R.string.busy_task_fetch_sports_summaries);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startFetching(TransactionBuilder builder) {
|
||||
LOG.info("start" + getName());
|
||||
final GregorianCalendar sinceWhen = getLastSuccessfulSyncTime();
|
||||
startFetching(builder, HuamiFetchDataType.SPORTS_SUMMARIES.getCode(), sinceWhen);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean processBufferedData() {
|
||||
LOG.info("{} has finished round {}", getName(), fetchCount);
|
||||
|
||||
if (buffer.size() < 2) {
|
||||
LOG.warn("Buffer size {} too small for activity summary", buffer.size());
|
||||
return false;
|
||||
}
|
||||
|
||||
final DeviceCoordinator coordinator = getDevice().getDeviceCoordinator();
|
||||
final ActivitySummaryParser summaryParser = coordinator.getActivitySummaryParser(getDevice());
|
||||
|
||||
BaseActivitySummary summary = new BaseActivitySummary();
|
||||
summary.setStartTime(getLastStartTimestamp().getTime()); // due to a bug this has to be set
|
||||
summary.setRawSummaryData(buffer.toByteArray());
|
||||
try {
|
||||
summary = summaryParser.parseBinaryData(summary);
|
||||
} catch (final Exception e) {
|
||||
GB.toast(getContext(), "Failed to parse activity summary", Toast.LENGTH_LONG, GB.ERROR, e);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (summary == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
summary.setSummaryData(null); // remove json before saving to database,
|
||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||
final DaoSession session = dbHandler.getDaoSession();
|
||||
final Device device = DBHelper.getDevice(getDevice(), session);
|
||||
final User user = DBHelper.getUser(session);
|
||||
summary.setDevice(device);
|
||||
summary.setUser(user);
|
||||
summary.setRawSummaryData(buffer.toByteArray());
|
||||
session.getBaseActivitySummaryDao().insertOrReplace(summary);
|
||||
} catch (final Exception ex) {
|
||||
GB.toast(getContext(), "Error saving activity summary", Toast.LENGTH_LONG, GB.ERROR, ex);
|
||||
return false;
|
||||
}
|
||||
|
||||
final AbstractHuamiActivityDetailsParser detailsParser = ((HuamiActivitySummaryParser) summaryParser).getDetailsParser(summary);
|
||||
final FetchSportsDetailsOperation nextOperation = new FetchSportsDetailsOperation(summary, detailsParser, getSupport(), getLastSyncTimeKey(), fetchCount);
|
||||
getSupport().getFetchOperationQueue().add(0, nextOperation);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getLastSyncTimeKey() {
|
||||
return "lastSportsActivityTimeMillis";
|
||||
}
|
||||
}
|
@ -14,18 +14,15 @@
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations;
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
|
||||
|
||||
/**
|
||||
@ -36,7 +33,7 @@ public class FetchStatisticsOperation extends AbstractRepeatingFetchOperation {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(FetchStatisticsOperation.class);
|
||||
|
||||
public FetchStatisticsOperation(final HuamiSupport support) {
|
||||
super(support, HuamiService.COMMAND_ACTIVITY_DATA_TYPE_STATISTICS, "statistics data");
|
||||
super(support, HuamiFetchDataType.STATISTICS);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -53,14 +50,6 @@ public class FetchStatisticsOperation extends AbstractRepeatingFetchOperation {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GregorianCalendar getLastSuccessfulSyncTime() {
|
||||
// One minute ago - we don't really care about this data, and we don't want to fetch it all
|
||||
final GregorianCalendar sinceWhen = BLETypeConversions.createCalendar();
|
||||
sinceWhen.add(Calendar.MINUTE, -1);
|
||||
return sinceWhen;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getLastSyncTimeKey() {
|
||||
return "lastStatisticsTimeMillis";
|
@ -14,7 +14,7 @@
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations;
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch;
|
||||
|
||||
import android.widget.Toast;
|
||||
|
||||
@ -31,7 +31,6 @@ import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiStressSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
@ -39,7 +38,6 @@ import nodomain.freeyourgadget.gadgetbridge.entities.HuamiStressSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.StressSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
/**
|
||||
@ -49,7 +47,7 @@ public class FetchStressAutoOperation extends AbstractRepeatingFetchOperation {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(FetchStressAutoOperation.class);
|
||||
|
||||
public FetchStressAutoOperation(final HuamiSupport support) {
|
||||
super(support, HuamiService.COMMAND_ACTIVITY_DATA_TYPE_STRESS_AUTOMATIC, "auto stress data");
|
||||
super(support, HuamiFetchDataType.STRESS_AUTOMATIC);
|
||||
}
|
||||
|
||||
@Override
|
@ -14,7 +14,7 @@
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations;
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch;
|
||||
|
||||
import android.widget.Toast;
|
||||
|
||||
@ -32,7 +32,6 @@ import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiStressSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
@ -41,7 +40,6 @@ import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.StressSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
/**
|
||||
@ -51,7 +49,7 @@ public class FetchStressManualOperation extends AbstractRepeatingFetchOperation
|
||||
private static final Logger LOG = LoggerFactory.getLogger(FetchStressManualOperation.class);
|
||||
|
||||
public FetchStressManualOperation(final HuamiSupport support) {
|
||||
super(support, HuamiService.COMMAND_ACTIVITY_DATA_TYPE_STRESS_MANUAL, "manual stress data");
|
||||
super(support, HuamiFetchDataType.STRESS_MANUAL);
|
||||
}
|
||||
|
||||
@Override
|
@ -14,7 +14,7 @@
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations;
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -24,7 +24,6 @@ import java.nio.ByteOrder;
|
||||
import java.util.GregorianCalendar;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
@ -35,7 +34,7 @@ public class FetchTemperatureOperation extends AbstractRepeatingFetchOperation {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(FetchTemperatureOperation.class);
|
||||
|
||||
public FetchTemperatureOperation(final HuamiSupport support) {
|
||||
super(support, HuamiService.COMMAND_ACTIVITY_DATA_TYPE_TEMPERATURE, "body temperature data");
|
||||
super(support, HuamiFetchDataType.TEMPERATURE);
|
||||
}
|
||||
|
||||
@Override
|
@ -0,0 +1,46 @@
|
||||
/* Copyright (C) 2024 José Rebelo
|
||||
|
||||
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 <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch;
|
||||
|
||||
public enum HuamiFetchDataType {
|
||||
ACTIVITY(0x01),
|
||||
MANUAL_HEART_RATE(0x02),
|
||||
SPORTS_SUMMARIES(0x05),
|
||||
SPORTS_DETAILS(0x06),
|
||||
DEBUG_LOGS(0x07),
|
||||
PAI(0x0d),
|
||||
STRESS_MANUAL(0x12),
|
||||
STRESS_AUTOMATIC(0x13),
|
||||
SPO2_NORMAL(0x25),
|
||||
SPO2_SLEEP(0x26),
|
||||
STATISTICS(0x2c),
|
||||
TEMPERATURE(0x2e),
|
||||
SLEEP_RESPIRATORY_RATE(0x38),
|
||||
RESTING_HEART_RATE(0x3a),
|
||||
MAX_HEART_RATE(0x3d),
|
||||
;
|
||||
|
||||
private final byte code;
|
||||
|
||||
HuamiFetchDataType(final int code) {
|
||||
this.code = (byte) code;
|
||||
}
|
||||
|
||||
public byte getCode() {
|
||||
return code;
|
||||
}
|
||||
}
|
@ -15,7 +15,7 @@
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations;
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.init;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.bluetooth.BluetoothGatt;
|
@ -14,7 +14,7 @@
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations;
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.init;
|
||||
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.RESPONSE;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.SUCCESS;
|
@ -14,7 +14,7 @@
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations;
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update;
|
||||
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
@ -14,7 +14,7 @@
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations;
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.widget.Toast;
|
@ -14,7 +14,7 @@
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations;
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
@ -29,7 +29,6 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceBusyAction;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.AbstractHuamiFirmwareInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
|
@ -25,8 +25,8 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppe.ZeppEFWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgtr.AmazfitGTRSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.UpdateFirmwareOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.UpdateFirmwareOperation2020;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation2020;
|
||||
|
||||
public class ZeppESupport extends AmazfitGTRSupport {
|
||||
|
||||
|
@ -112,7 +112,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiDeviceEve
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiPhoneGpsStatus;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiVibrationPatternNotificationType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.UpdateFirmwareOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.operations.ZeppOsFirmwareUpdateOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.operations.ZeppOsAgpsUpdateOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.operations.ZeppOsGpxRouteUploadOperation;
|
||||
|
@ -40,7 +40,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceBusyAction;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetProgressAction;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.UpdateFirmwareOperation2020;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation2020;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.AbstractMiBandOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
|
||||
|
Loading…
Reference in New Issue
Block a user