mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2026-07-31 07:44:24 +02:00
Huawei: retrive and parse TruSleep data from new devices.
This commit is contained in:
@@ -58,7 +58,7 @@ public class GBDaoGenerator {
|
||||
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
final Schema schema = new Schema(107, MAIN_PACKAGE + ".entities");
|
||||
final Schema schema = new Schema(108, MAIN_PACKAGE + ".entities");
|
||||
|
||||
Entity userAttributes = addUserAttributes(schema);
|
||||
Entity user = addUserInfo(schema, userAttributes);
|
||||
@@ -170,6 +170,8 @@ public class GBDaoGenerator {
|
||||
|
||||
addHuaweiActivitySample(schema, user, device);
|
||||
addHuaweiStressSample(schema, user, device);
|
||||
addHuaweiSleepStageSample(schema, user, device);
|
||||
addHuaweiSleepStatsSample(schema, user, device);
|
||||
|
||||
addUltrahumanActivitySample(schema, user, device);
|
||||
addUltrahumanDeviceStateSample(schema, user, device);
|
||||
@@ -1548,6 +1550,36 @@ public class GBDaoGenerator {
|
||||
return stressSample;
|
||||
}
|
||||
|
||||
private static Entity addHuaweiSleepStageSample(Schema schema, Entity user, Entity device) {
|
||||
Entity sleepStageSample = addEntity(schema, "HuaweiSleepStageSample");
|
||||
addCommonTimeSampleProperties("AbstractTimeSample", sleepStageSample, user, device);
|
||||
sleepStageSample.addIntProperty("stage").notNull();
|
||||
return sleepStageSample;
|
||||
}
|
||||
|
||||
private static Entity addHuaweiSleepStatsSample(Schema schema, Entity user, Entity device) {
|
||||
Entity sample = addEntity(schema, "HuaweiSleepStatsSample");
|
||||
sample.addImport(MAIN_PACKAGE + ".model.SleepScoreSample");
|
||||
addCommonTimeSampleProperties("AbstractTimeSample", sample, user, device);
|
||||
sample.implementsInterface("SleepScoreSample");
|
||||
sample.addIntProperty("sleepScore").notNull().codeBeforeGetter(OVERRIDE);
|
||||
sample.addLongProperty("bedTime").notNull();
|
||||
sample.addLongProperty("risingTime").notNull();
|
||||
sample.addLongProperty("wakeupTime").notNull();
|
||||
sample.addIntProperty("sleepDataQuality").notNull();
|
||||
sample.addIntProperty("deepPart").notNull();
|
||||
sample.addIntProperty("snoreFreq").notNull();
|
||||
sample.addIntProperty("sleepLatency").notNull();
|
||||
sample.addIntProperty("sleepEfficiency").notNull();
|
||||
sample.addIntProperty("minHeartRate").notNull();
|
||||
sample.addIntProperty("maxHeartRate").notNull();
|
||||
sample.addDoubleProperty("minOxygenSaturation").notNull();
|
||||
sample.addDoubleProperty("maxOxygenSaturation").notNull();
|
||||
sample.addDoubleProperty("minBreathRate").notNull();
|
||||
sample.addDoubleProperty("maxBreathRate").notNull();
|
||||
return sample;
|
||||
}
|
||||
|
||||
private static Entity addHuaweiWorkoutSummarySample(Schema schema, Entity user, Entity device) {
|
||||
Entity workoutSummary = addEntity(schema, "HuaweiWorkoutSummarySample");
|
||||
|
||||
|
||||
+16
@@ -40,6 +40,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.SleepScoreSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.StressSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample;
|
||||
@@ -224,6 +225,16 @@ public abstract class HuaweiBRCoordinator extends AbstractBLClassicDeviceCoordin
|
||||
return huaweiCoordinator.supportsFindDeviceAbility();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRemSleep() {return huaweiCoordinator.getSupportsNewTrueSleep(device);}
|
||||
|
||||
@Override
|
||||
public boolean supportsAwakeSleep() {return huaweiCoordinator.getSupportsNewTrueSleep(device);}
|
||||
|
||||
@Override
|
||||
public boolean supportsSleepScore(final GBDevice device) { return huaweiCoordinator.getSupportsNewTrueSleep(device); }
|
||||
|
||||
|
||||
@Override
|
||||
public InstallHandler findInstallHandler(Uri uri, Context context) {
|
||||
return huaweiCoordinator.getInstallHandler(uri, context);
|
||||
@@ -254,6 +265,11 @@ public abstract class HuaweiBRCoordinator extends AbstractBLClassicDeviceCoordin
|
||||
return new HuaweiStressSampleProvider(device, session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TimeSampleProvider<? extends SleepScoreSample> getSleepScoreProvider(final GBDevice device, final DaoSession session) {
|
||||
return new HuaweiSleepStatsSampleProvider(device, session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getStressRanges() {
|
||||
return huaweiCoordinator.getStressRanges();
|
||||
|
||||
+25
-2
@@ -49,11 +49,12 @@ import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiActivitySampleDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiDictData;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiDictDataDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiDictDataValuesDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiSleepStageSampleDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiSleepStatsSampleDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiStressSampleDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutDataSampleDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutPaceSampleDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSpO2SampleDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSummaryAdditionalValuesSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSummaryAdditionalValuesSampleDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSummarySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSummarySampleDao;
|
||||
@@ -128,6 +129,12 @@ public class HuaweiCoordinator {
|
||||
QueryBuilder<?> qb = session.getHuaweiActivitySampleDao().queryBuilder();
|
||||
qb.where(HuaweiActivitySampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
|
||||
|
||||
QueryBuilder<?> sleepQb = session.getHuaweiSleepStageSampleDao().queryBuilder();
|
||||
sleepQb.where(HuaweiSleepStageSampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
|
||||
|
||||
QueryBuilder<?> sleepStatsQb = session.getHuaweiSleepStatsSampleDao().queryBuilder();
|
||||
sleepStatsQb.where(HuaweiSleepStatsSampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
|
||||
|
||||
QueryBuilder<?> stressQb = session.getHuaweiStressSampleDao().queryBuilder();
|
||||
stressQb.where(HuaweiStressSampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
|
||||
|
||||
@@ -281,7 +288,7 @@ public class HuaweiCoordinator {
|
||||
}
|
||||
|
||||
private int getNotificationConstraint(byte which) {
|
||||
return (int)notificationConstraints.getShort(which);
|
||||
return notificationConstraints.getShort(which);
|
||||
}
|
||||
|
||||
public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
|
||||
@@ -727,6 +734,18 @@ public class HuaweiCoordinator {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean supportsDictSleepSync() {
|
||||
if (supportsExpandCapability())
|
||||
return supportsExpandCapability(143);
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean supportsBedTime() {
|
||||
if (supportsExpandCapability())
|
||||
return supportsExpandCapability(199);
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean supportsUnknownGender() {
|
||||
if (supportsExpandCapability())
|
||||
return supportsExpandCapability(0x57);
|
||||
@@ -1086,4 +1105,8 @@ public class HuaweiCoordinator {
|
||||
return new int[]{1800, 1800, 400};
|
||||
}
|
||||
|
||||
public boolean getSupportsNewTrueSleep(final GBDevice device) {
|
||||
return supportsTruSleep() && supportsDictSleepSync();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
@@ -23,4 +23,6 @@ public class HuaweiDictTypes {
|
||||
|
||||
public static final int SKIN_TEMPERATURE_CLASS = 400012;
|
||||
public static final int SKIN_TEMPERATURE_VALUE = 400012430;
|
||||
|
||||
public static final int SLEEP_DETAILS_CLASS = 700013;
|
||||
}
|
||||
|
||||
+15
@@ -41,6 +41,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.SleepScoreSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.StressSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample;
|
||||
@@ -233,6 +234,15 @@ public abstract class HuaweiLECoordinator extends AbstractBLEDeviceCoordinator i
|
||||
return huaweiCoordinator.supportsFindDeviceAbility();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRemSleep() {return huaweiCoordinator.getSupportsNewTrueSleep(device);}
|
||||
|
||||
@Override
|
||||
public boolean supportsAwakeSleep() {return huaweiCoordinator.getSupportsNewTrueSleep(device);}
|
||||
|
||||
@Override
|
||||
public boolean supportsSleepScore(final GBDevice device) { return huaweiCoordinator.getSupportsNewTrueSleep(device); }
|
||||
|
||||
@Override
|
||||
public InstallHandler findInstallHandler(Uri uri, Context context) {
|
||||
return huaweiCoordinator.getInstallHandler(uri, context);
|
||||
@@ -263,6 +273,11 @@ public abstract class HuaweiLECoordinator extends AbstractBLEDeviceCoordinator i
|
||||
return new HuaweiStressSampleProvider(device, session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TimeSampleProvider<? extends SleepScoreSample> getSleepScoreProvider(final GBDevice device, final DaoSession session) {
|
||||
return new HuaweiSleepStatsSampleProvider(device, session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getStressRanges() {
|
||||
return huaweiCoordinator.getStressRanges();
|
||||
|
||||
+91
-16
@@ -21,7 +21,9 @@ import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import de.greenrobot.dao.AbstractDao;
|
||||
@@ -33,6 +35,8 @@ import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiActivitySampleDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiSleepStageSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiSleepStatsSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutDataSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutDataSampleDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSummarySample;
|
||||
@@ -42,6 +46,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
|
||||
public class HuaweiSampleProvider extends AbstractSampleProvider<HuaweiActivitySample> {
|
||||
|
||||
/*
|
||||
* We save all data by saving a marker at the begin and end. We do not actively use these for
|
||||
* showing the data at the moment, but the samples are still saved as such, to keep the table
|
||||
@@ -61,6 +66,9 @@ public class HuaweiSampleProvider extends AbstractSampleProvider<HuaweiActivityS
|
||||
|
||||
public static final int DEEP_SLEEP = 0x07;
|
||||
public static final int LIGHT_SLEEP = 0x06;
|
||||
|
||||
public static final int TRUSLEEP_REM = 0x5656;
|
||||
public static final int TRUSLEEP_AWAKE = 0x5658;
|
||||
}
|
||||
|
||||
public HuaweiSampleProvider(GBDevice device, DaoSession session) {
|
||||
@@ -69,26 +77,24 @@ public class HuaweiSampleProvider extends AbstractSampleProvider<HuaweiActivityS
|
||||
|
||||
@Override
|
||||
public ActivityKind normalizeType(int rawType) {
|
||||
switch (rawType) {
|
||||
case RawTypes.DEEP_SLEEP:
|
||||
return ActivityKind.DEEP_SLEEP;
|
||||
case RawTypes.LIGHT_SLEEP:
|
||||
return ActivityKind.LIGHT_SLEEP;
|
||||
default:
|
||||
return ActivityKind.UNKNOWN;
|
||||
}
|
||||
return switch (rawType) {
|
||||
case RawTypes.DEEP_SLEEP -> ActivityKind.DEEP_SLEEP;
|
||||
case RawTypes.LIGHT_SLEEP -> ActivityKind.LIGHT_SLEEP;
|
||||
case RawTypes.TRUSLEEP_REM -> ActivityKind.REM_SLEEP;
|
||||
case RawTypes.TRUSLEEP_AWAKE -> ActivityKind.AWAKE_SLEEP;
|
||||
default -> ActivityKind.UNKNOWN;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public int toRawActivityKind(ActivityKind activityKind) {
|
||||
switch (activityKind) {
|
||||
case DEEP_SLEEP:
|
||||
return RawTypes.DEEP_SLEEP;
|
||||
case LIGHT_SLEEP:
|
||||
return RawTypes.LIGHT_SLEEP;
|
||||
default:
|
||||
return RawTypes.NOT_MEASURED;
|
||||
}
|
||||
return switch (activityKind) {
|
||||
case DEEP_SLEEP -> RawTypes.DEEP_SLEEP;
|
||||
case LIGHT_SLEEP -> RawTypes.LIGHT_SLEEP;
|
||||
case REM_SLEEP -> RawTypes.TRUSLEEP_REM;
|
||||
case AWAKE_SLEEP -> RawTypes.TRUSLEEP_AWAKE;
|
||||
default -> RawTypes.NOT_MEASURED;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -292,6 +298,13 @@ public class HuaweiSampleProvider extends AbstractSampleProvider<HuaweiActivityS
|
||||
return samples;
|
||||
}
|
||||
|
||||
private static int adjustTimeToMinute(int time) {
|
||||
if (time % 60 != 0) {
|
||||
time = (time / 60) * 60;
|
||||
}
|
||||
return time;
|
||||
}
|
||||
|
||||
/*
|
||||
* This takes the following three steps:
|
||||
* - Generate a sample every minute
|
||||
@@ -302,18 +315,25 @@ public class HuaweiSampleProvider extends AbstractSampleProvider<HuaweiActivityS
|
||||
protected List<HuaweiActivitySample> getGBActivitySamples(int timestamp_from, int timestamp_to) {
|
||||
List<HuaweiActivitySample> processedSamples = new ArrayList<>();
|
||||
|
||||
timestamp_from = adjustTimeToMinute(timestamp_from);
|
||||
timestamp_to = adjustTimeToMinute(timestamp_to);
|
||||
|
||||
for (int timestamp = timestamp_from; timestamp <= timestamp_to; timestamp += 60) {
|
||||
processedSamples.add(createDummySample(timestamp));
|
||||
}
|
||||
|
||||
overlayActivitySamples(processedSamples, timestamp_from, timestamp_to);
|
||||
overlayWorkoutSamples(processedSamples, timestamp_from, timestamp_to);
|
||||
overlaySleep(processedSamples, timestamp_from, timestamp_to);
|
||||
|
||||
return processedSamples;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<HuaweiActivitySample> getGBActivitySamplesHighRes(int timestamp_from, int timestamp_to) {
|
||||
timestamp_from = adjustTimeToMinute(timestamp_from);
|
||||
timestamp_to = adjustTimeToMinute(timestamp_to);
|
||||
|
||||
List<HuaweiActivitySample> processedSamples = getRawOrderedActivitySamples(timestamp_from, timestamp_to);
|
||||
addWorkoutSamples(processedSamples, timestamp_from, timestamp_to);
|
||||
// Filter out the end markers before returning
|
||||
@@ -544,4 +564,59 @@ public class HuaweiSampleProvider extends AbstractSampleProvider<HuaweiActivityS
|
||||
newSample.setProvider(this);
|
||||
return newSample;
|
||||
}
|
||||
|
||||
private int toActivityKind(final HuaweiSleepStageSample stageSample) {
|
||||
return switch (stageSample.getStage()) {
|
||||
case 1 -> RawTypes.LIGHT_SLEEP;
|
||||
case 2 -> RawTypes.TRUSLEEP_REM;
|
||||
case 3 -> RawTypes.DEEP_SLEEP;
|
||||
case 4 -> RawTypes.TRUSLEEP_AWAKE;
|
||||
//case 5 -> RawTypes.UNKNOWN;
|
||||
default -> RawTypes.UNKNOWN;
|
||||
};
|
||||
}
|
||||
|
||||
public void overlaySleep(final List<HuaweiActivitySample> samples, final int timestamp_from, final int timestamp_to) {
|
||||
final Map<Long, Integer> stagesMap = new LinkedHashMap<>();
|
||||
final HuaweiSleepStatsSampleProvider sleepStatsSampleProvider = new HuaweiSleepStatsSampleProvider(getDevice(), getSession());
|
||||
final HuaweiSleepStageSampleProvider sleepStageSampleProvider = new HuaweiSleepStageSampleProvider(getDevice(), getSession());
|
||||
|
||||
List<HuaweiSleepStatsSample> sleepStatsSamples = sleepStatsSampleProvider.getSleepSamples(timestamp_from * 1000L, timestamp_to * 1000L);
|
||||
|
||||
if (!sleepStatsSamples.isEmpty()) {
|
||||
for(HuaweiSleepStatsSample sample: sleepStatsSamples) {
|
||||
final List<HuaweiSleepStageSample> stageSamples = sleepStageSampleProvider.getAllSamples(
|
||||
sample.getTimestamp(),
|
||||
sample.getWakeupTime() - 60000L
|
||||
);
|
||||
for (final HuaweiSleepStageSample stageSample : stageSamples) {
|
||||
stagesMap.put(stageSample.getTimestamp(), toActivityKind(stageSample));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!stagesMap.isEmpty()) {
|
||||
if (!samples.isEmpty()) {
|
||||
for (final HuaweiActivitySample sample : samples) {
|
||||
final long ts = sample.getTimestamp();
|
||||
final Integer sleepType = stagesMap.get(ts * 1000L);
|
||||
if (sleepType != null && !sleepType.equals(RawTypes.UNKNOWN)) {
|
||||
sample.setRawKind(sleepType);
|
||||
sample.setRawIntensity(ActivitySample.NOT_MEASURED);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int ts = timestamp_from; ts <= timestamp_to; ts += 60) {
|
||||
final HuaweiActivitySample sample = createDummySample(ts);
|
||||
final Integer sleepType = stagesMap.get(ts * 1000L);
|
||||
if (sleepType != null && !sleepType.equals(RawTypes.UNKNOWN)) {
|
||||
sample.setRawKind(sleepType);
|
||||
sample.setRawIntensity(ActivitySample.NOT_MEASURED);
|
||||
}
|
||||
samples.add(sample);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+198
@@ -0,0 +1,198 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.huawei;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class HuaweiSequenceDataParser {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(HuaweiSequenceDataParser.class);
|
||||
|
||||
public static class SequenceData {
|
||||
private int endTime;
|
||||
private byte[] details;
|
||||
private byte[] extraData;
|
||||
private int startTime;
|
||||
private int summaryType;
|
||||
private int dataVersion; //??
|
||||
private byte[] summary;
|
||||
|
||||
public int getStartTime() { return this.startTime;}
|
||||
public void setStartTime(int startTime) {
|
||||
this.startTime = startTime;
|
||||
}
|
||||
|
||||
public int getEndTime() {
|
||||
return this.endTime;
|
||||
}
|
||||
public void setEndTime(int endTime) {
|
||||
this.endTime = endTime;
|
||||
}
|
||||
|
||||
public int getDataVersion() { return this.dataVersion;}
|
||||
public void setDataVersion(int version) { this.dataVersion = version;}
|
||||
|
||||
public int getSummaryType() { return this.summaryType;}
|
||||
public void setSummaryType(int summaryType) {
|
||||
this.summaryType = summaryType;
|
||||
}
|
||||
|
||||
public void setExtraData(byte[] extraData) { this.extraData = extraData;}
|
||||
|
||||
public byte[] getSummary() { return this.summary;}
|
||||
public void setSummary(byte[] summary) { this.summary = summary;}
|
||||
|
||||
public byte[] getDetails() {
|
||||
return this.details;
|
||||
}
|
||||
public void setDetails(byte[] details) { this.details = details; }
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder sb = new StringBuilder("SequenceData{");
|
||||
sb.append("startTime=").append(startTime);
|
||||
sb.append(", endTime=").append(endTime);
|
||||
sb.append(", version=").append(dataVersion);
|
||||
sb.append(", summaryType=").append(summaryType);
|
||||
sb.append(", summary=").append(Arrays.toString(summary));
|
||||
sb.append(", details=").append(Arrays.toString(details));
|
||||
sb.append(", extraData=").append(Arrays.toString(extraData));
|
||||
sb.append('}');
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
public static class SequenceFileData {
|
||||
|
||||
private long dataSize;
|
||||
private int fileType;
|
||||
private byte[] extraData;
|
||||
private int dictId;
|
||||
private final List<SequenceData> sequenceDataList = new ArrayList<>();
|
||||
private int fileVersion;
|
||||
|
||||
public void setDataSize(long dataSize) {
|
||||
this.dataSize = dataSize;
|
||||
}
|
||||
|
||||
public int getDictId() { return this.dictId; }
|
||||
|
||||
public void setDictId(int dictId) {
|
||||
this.dictId = dictId;
|
||||
}
|
||||
|
||||
public int getFileType() {
|
||||
return this.fileType;
|
||||
}
|
||||
|
||||
public void setFileType(int fileType) {
|
||||
this.fileType = fileType;
|
||||
}
|
||||
|
||||
public int getFileVersion() {
|
||||
return this.fileVersion;
|
||||
}
|
||||
|
||||
public void setFileVersion(int fileVersion) {
|
||||
this.fileVersion = fileVersion;
|
||||
}
|
||||
|
||||
public void setExtraData(byte[] extraData) { this.extraData = extraData;}
|
||||
|
||||
public List<SequenceData> getSequenceDataList() {
|
||||
return this.sequenceDataList;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder sb = new StringBuilder("SequenceFileData{");
|
||||
sb.append("dataSize=").append(dataSize);
|
||||
sb.append(", fileType=").append(fileType);
|
||||
sb.append(", dictId=").append(dictId);
|
||||
sb.append(", fileVersion=").append(fileVersion);
|
||||
sb.append(", sequenceDataList=").append(sequenceDataList);
|
||||
sb.append(", extraData=").append(Arrays.toString(extraData));
|
||||
sb.append('}');
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private static void parseSequenceData(ByteBuffer buffer, SequenceFileData fileData) {
|
||||
|
||||
while (buffer.remaining() >= 32) {
|
||||
SequenceData data = new SequenceData();
|
||||
|
||||
data.setStartTime(buffer.getInt());
|
||||
data.setEndTime(buffer.getInt());
|
||||
data.setDataVersion(buffer.get());
|
||||
data.setSummaryType(buffer.get());
|
||||
int summaryDataLen = buffer.getShort();
|
||||
int detailLen = buffer.getInt();
|
||||
byte[] extraData = new byte[16];
|
||||
buffer.get(extraData);
|
||||
data.setExtraData(extraData);
|
||||
|
||||
// parseSummary
|
||||
if (data.getDataVersion() == 1) {
|
||||
if (buffer.remaining() < summaryDataLen) {
|
||||
LOG.error("summaryDataLen {} exceeds remaining: {}", summaryDataLen, buffer.remaining());
|
||||
} else {
|
||||
byte[] bArr1 = new byte[summaryDataLen];
|
||||
buffer.get(bArr1);
|
||||
data.setSummary(bArr1);
|
||||
}
|
||||
}
|
||||
// parse details
|
||||
// TODO: In some my test files this value is too big. I suppose 20 Mb will be enough for now. Additional investigation required
|
||||
if (detailLen <= 0 || detailLen > (20 * 1025 * 1024)) {
|
||||
LOG.error("detailLen is invalid: {}", detailLen);
|
||||
} else {
|
||||
byte[] bArr2 = new byte[detailLen];
|
||||
if (buffer.remaining() < detailLen) {
|
||||
LOG.error("detailLen {} exceeds remaining: {}", detailLen, buffer.remaining());
|
||||
}
|
||||
buffer.get(bArr2);
|
||||
data.setDetails(bArr2);
|
||||
}
|
||||
if (fileData.getSequenceDataList() != null) {
|
||||
fileData.getSequenceDataList().add(data);
|
||||
}
|
||||
}
|
||||
if (buffer.remaining() > 0) {
|
||||
LOG.error("sequence data remaining: {}", buffer.remaining());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static SequenceFileData parseSequenceFileData(byte[] data) {
|
||||
if(data == null) {
|
||||
LOG.error("Data is null");
|
||||
return null;
|
||||
}
|
||||
if (data.length < 32) {
|
||||
LOG.error("Invalid file length: {}", data.length);
|
||||
return null;
|
||||
}
|
||||
ByteBuffer buffer = ByteBuffer.wrap(data);
|
||||
buffer.order(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
SequenceFileData fileData = new SequenceFileData();
|
||||
fileData.setDataSize(buffer.getInt());
|
||||
fileData.setDictId(buffer.getInt());
|
||||
fileData.setFileType(buffer.getShort());
|
||||
fileData.setFileVersion(buffer.getShort());
|
||||
byte[] extraData = new byte[20];
|
||||
buffer.get(extraData);
|
||||
fileData.setExtraData(extraData);
|
||||
parseSequenceData(buffer, fileData);
|
||||
return fileData;
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.huawei;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import de.greenrobot.dao.AbstractDao;
|
||||
import de.greenrobot.dao.Property;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractTimeSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiSleepStageSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiSleepStageSampleDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
|
||||
public class HuaweiSleepStageSampleProvider extends AbstractTimeSampleProvider<HuaweiSleepStageSample> {
|
||||
public HuaweiSleepStageSampleProvider(final GBDevice device, final DaoSession session) {
|
||||
super(device, session);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public AbstractDao<HuaweiSleepStageSample, ?> getSampleDao() {
|
||||
return getSession().getHuaweiSleepStageSampleDao();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Property getTimestampSampleProperty() {
|
||||
return HuaweiSleepStageSampleDao.Properties.Timestamp;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Property getDeviceIdentifierSampleProperty() {
|
||||
return HuaweiSleepStageSampleDao.Properties.DeviceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HuaweiSleepStageSample createSample() {
|
||||
return new HuaweiSleepStageSample();
|
||||
}
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.huawei;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import de.greenrobot.dao.AbstractDao;
|
||||
import de.greenrobot.dao.Property;
|
||||
import de.greenrobot.dao.query.QueryBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractTimeSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiActivitySampleDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiSleepStatsSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiSleepStatsSampleDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
|
||||
public class HuaweiSleepStatsSampleProvider extends AbstractTimeSampleProvider<HuaweiSleepStatsSample> {
|
||||
public HuaweiSleepStatsSampleProvider(final GBDevice device, final DaoSession session) {
|
||||
super(device, session);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public AbstractDao<HuaweiSleepStatsSample, ?> getSampleDao() {
|
||||
return getSession().getHuaweiSleepStatsSampleDao();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Property getTimestampSampleProperty() {
|
||||
return HuaweiSleepStatsSampleDao.Properties.Timestamp;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Property getDeviceIdentifierSampleProperty() {
|
||||
return HuaweiSleepStatsSampleDao.Properties.DeviceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HuaweiSleepStatsSample createSample() {
|
||||
return new HuaweiSleepStatsSample();
|
||||
}
|
||||
|
||||
public long getLastSleepFetchTimestamp() {
|
||||
QueryBuilder<HuaweiSleepStatsSample> qb = getSampleDao().queryBuilder();
|
||||
Device dbDevice = DBHelper.findDevice(getDevice(), getSession());
|
||||
if (dbDevice == null)
|
||||
return 0;
|
||||
final Property deviceProperty = HuaweiActivitySampleDao.Properties.DeviceId;
|
||||
final Property timestampProperty = HuaweiActivitySampleDao.Properties.Timestamp;
|
||||
|
||||
qb.where(deviceProperty.eq(dbDevice.getId()))
|
||||
.orderDesc(timestampProperty)
|
||||
.limit(1);
|
||||
|
||||
List<HuaweiSleepStatsSample> samples = qb.build().list();
|
||||
if (samples.isEmpty())
|
||||
return 0;
|
||||
|
||||
HuaweiSleepStatsSample sample = samples.get(0);
|
||||
return sample.getWakeupTime();
|
||||
}
|
||||
|
||||
public List<HuaweiSleepStatsSample> getSleepSamples(final long timestampFrom, final long timestampTo) {
|
||||
final QueryBuilder<HuaweiSleepStatsSample> qb = getSampleDao().queryBuilder();
|
||||
final Property fallAsleepProperty = getTimestampSampleProperty();
|
||||
final Property wakeupProperty = HuaweiSleepStatsSampleDao.Properties.WakeupTime;
|
||||
final Device dbDevice = DBHelper.findDevice(getDevice(), getSession());
|
||||
if (dbDevice == null) {
|
||||
// no device, no samples
|
||||
return Collections.emptyList();
|
||||
}
|
||||
final Property deviceProperty = getDeviceIdentifierSampleProperty();
|
||||
qb.where(deviceProperty.eq(dbDevice.getId()),
|
||||
qb.or(
|
||||
qb.and(fallAsleepProperty.ge(timestampFrom), fallAsleepProperty.le(timestampTo)),
|
||||
qb.and(wakeupProperty.ge(timestampFrom), wakeupProperty.le(timestampTo))
|
||||
)
|
||||
).orderAsc(fallAsleepProperty);
|
||||
|
||||
final List<HuaweiSleepStatsSample> samples = qb.build().list();
|
||||
detachFromSession();
|
||||
return samples;
|
||||
}
|
||||
}
|
||||
@@ -322,6 +322,15 @@ public class HuaweiTLV {
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
public List<HuaweiTLV> getAllContainerObjects() {
|
||||
List<HuaweiTLV> returnValue = new ArrayList<>();
|
||||
for (TLV tlv : valueMap) {
|
||||
if (((tlv.getTag() & 0xFF) >>> 7) == 1)
|
||||
returnValue.add(new HuaweiTLV().parse(tlv.getValue()));
|
||||
}
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
public boolean contains(int tag) {
|
||||
for (TLV item : valueMap)
|
||||
if (item.getTag() == (byte) tag)
|
||||
|
||||
+374
@@ -0,0 +1,374 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.huawei;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class HuaweiTrueSleepSequenceDataParser {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(HuaweiTrueSleepSequenceDataParser.class);
|
||||
|
||||
public static class SleepStage {
|
||||
private final long time;
|
||||
private final int stage;
|
||||
|
||||
public SleepStage(long time, int stage) {
|
||||
this.time = time;
|
||||
this.stage = stage;
|
||||
}
|
||||
|
||||
public int getStage() {
|
||||
return stage;
|
||||
}
|
||||
|
||||
public long getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuffer sb = new StringBuffer("SleepStages{");
|
||||
sb.append("time=").append(time);
|
||||
sb.append(", stage=").append(stage);
|
||||
sb.append('}');
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
public static class SleepSummary {
|
||||
private long fallAsleepTime = -1;
|
||||
private long bedTime = -1;
|
||||
private long risingTime = -1;
|
||||
private long wakeupTime = -1;
|
||||
private int validData = -1;
|
||||
private int sleepDataQuality = -1;
|
||||
private int deepPart = -1;
|
||||
private int snoreFreq = -1;
|
||||
private int sleepScore = -1;
|
||||
private int sleepLatency = -1;
|
||||
private int sleepEfficiency = -1;
|
||||
private int minHeartrate = -1;
|
||||
private int maxHeartrate = -1;
|
||||
private double minOxygenSaturation = -1;
|
||||
private double maxOxygenSaturation = -1;
|
||||
private double minBreathrate = -1;
|
||||
private double maxBreathrate = -1;
|
||||
|
||||
public long getFallAsleepTime() { return fallAsleepTime; }
|
||||
|
||||
public void setFallAsleepTime(long fallAsleepTime) { this.fallAsleepTime = fallAsleepTime; }
|
||||
|
||||
public long getBedTime() { return bedTime; }
|
||||
|
||||
public void setBedTime(long bedTime) { this.bedTime = bedTime;}
|
||||
|
||||
public long getRisingTime() { return risingTime;}
|
||||
|
||||
public void setRisingTime(long risingTime) {this.risingTime = risingTime;}
|
||||
|
||||
public long getWakeupTime() {return wakeupTime;}
|
||||
|
||||
public void setWakeupTime(long wakeupTime) {this.wakeupTime = wakeupTime;}
|
||||
|
||||
public int getValidData() { return validData; }
|
||||
|
||||
public void setValidData(int validData) { this.validData = validData;}
|
||||
|
||||
public int getSleepDataQuality() { return sleepDataQuality;}
|
||||
|
||||
public void setSleepDataQuality(int sleepDataQuality) {this.sleepDataQuality = sleepDataQuality;}
|
||||
|
||||
public int getDeepPart() {return deepPart;}
|
||||
|
||||
public void setDeepPart(int deepPart) {this.deepPart = deepPart;}
|
||||
|
||||
public int getSnoreFreq() {return snoreFreq;}
|
||||
|
||||
public void setSnoreFreq(int snoreFreq) {this.snoreFreq = snoreFreq;}
|
||||
|
||||
public int getSleepScore() {return sleepScore;}
|
||||
|
||||
public void setSleepScore(int sleepScore) {this.sleepScore = sleepScore;}
|
||||
|
||||
public int getSleepLatency() {return sleepLatency;}
|
||||
|
||||
public void setSleepLatency(int sleepLatency) {this.sleepLatency = sleepLatency;}
|
||||
|
||||
public int getSleepEfficiency() {return sleepEfficiency;}
|
||||
|
||||
public void setSleepEfficiency(int sleepEfficiency) {this.sleepEfficiency = sleepEfficiency;}
|
||||
|
||||
public int getMinHeartrate() {return minHeartrate;}
|
||||
|
||||
public void setMinHeartrate(int minHeartrate) {this.minHeartrate = minHeartrate;}
|
||||
|
||||
public int getMaxHeartrate() {return maxHeartrate;}
|
||||
|
||||
public void setMaxHeartrate(int maxHeartrate) {this.maxHeartrate = maxHeartrate;}
|
||||
|
||||
public double getMinOxygenSaturation() {return minOxygenSaturation;}
|
||||
|
||||
public void setMinOxygenSaturation(double minOxygenSaturation) {this.minOxygenSaturation = minOxygenSaturation;}
|
||||
|
||||
public double getMaxOxygenSaturation() {return maxOxygenSaturation;}
|
||||
|
||||
public void setMaxOxygenSaturation(double maxOxygenSaturation) {this.maxOxygenSaturation = maxOxygenSaturation;}
|
||||
|
||||
public double getMinBreathrate() {return minBreathrate;}
|
||||
|
||||
public void setMinBreathrate(double minBreathrate) {this.minBreathrate = minBreathrate;}
|
||||
|
||||
public double getMaxBreathrate() {return maxBreathrate;}
|
||||
|
||||
public void setMaxBreathrate(double maxBreathrate) {this.maxBreathrate = maxBreathrate;}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder sb = new StringBuilder("SleepDetails{");
|
||||
sb.append("fallAsleepTime=").append(fallAsleepTime);
|
||||
sb.append(", bedTime=").append(bedTime);
|
||||
sb.append(", risingTime=").append(risingTime);
|
||||
sb.append(", wakeupTime=").append(wakeupTime);
|
||||
sb.append(", validData=").append(validData);
|
||||
sb.append(", sleepDataQuality=").append(sleepDataQuality);
|
||||
sb.append(", deepPart=").append(deepPart);
|
||||
sb.append(", snoreFreq=").append(snoreFreq);
|
||||
sb.append(", sleepScore=").append(sleepScore);
|
||||
sb.append(", sleepLatency=").append(sleepLatency);
|
||||
sb.append(", sleepEfficiency=").append(sleepEfficiency);
|
||||
sb.append(", minHeartrate=").append(minHeartrate);
|
||||
sb.append(", maxHeartrate=").append(maxHeartrate);
|
||||
sb.append(", minOxygenSaturation=").append(minOxygenSaturation);
|
||||
sb.append(", maxOxygenSaturation=").append(maxOxygenSaturation);
|
||||
sb.append(", minBreathrate=").append(minBreathrate);
|
||||
sb.append(", maxBreathrate=").append(maxBreathrate);
|
||||
sb.append('}');
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
public static int readAsInteger(byte[] data, int def) {
|
||||
if (data == null || data.length == 0 || data.length > 4) {
|
||||
return def;
|
||||
}
|
||||
int res = 0;
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
res |= (data[i] & 0xFF) << (((data.length - i) - 1) * 8);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
public static long readAsLong(byte[] data, long def) {
|
||||
if (data == null || data.length == 0 || data.length > 8) {
|
||||
return def;
|
||||
}
|
||||
long res = 0;
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
res |= (long) (data[i] & 0xFF) << (((data.length - i) - 1) * 8);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private static long getValueAsLong(int i, byte[] str2, long def) {
|
||||
if (i == 1 || i == 2 || i == 4) { // int, byte, short (I suppose)
|
||||
return readAsInteger(str2, (int) def);
|
||||
} else if (i == 3) { // long
|
||||
return readAsLong(str2, def);
|
||||
} else if (i == 5) { // string
|
||||
return def;
|
||||
} else if (i == 6) { // double
|
||||
return (long) Double.longBitsToDouble(readAsLong(str2, def));
|
||||
} else {
|
||||
return def;
|
||||
}
|
||||
}
|
||||
|
||||
private static double getValueAsDouble(int i, byte[] str2, double def) {
|
||||
if (i == 1 || i == 2 || i == 4) { // int, byte, short (I suppose)
|
||||
return readAsInteger(str2, (int) def);
|
||||
} else if (i == 3) { // long
|
||||
return readAsLong(str2, (int) def);
|
||||
} else if (i == 5) { // string
|
||||
return def;
|
||||
} else if (i == 6) { // double
|
||||
return Double.longBitsToDouble(readAsLong(str2, (int) def));
|
||||
} else {
|
||||
return def;
|
||||
}
|
||||
}
|
||||
|
||||
private static void fillSleepSummary(SleepSummary details, int dictId, int dataType, byte[] value) {
|
||||
switch (dictId) {
|
||||
case 700013686:
|
||||
long fallAsleepTime = getValueAsLong(dataType, value, -1);
|
||||
if(fallAsleepTime >=0)
|
||||
details.setFallAsleepTime(fallAsleepTime);
|
||||
break;
|
||||
case 700013298:
|
||||
long bedTime = getValueAsLong(dataType, value, -1);
|
||||
if(bedTime >=0)
|
||||
details.setBedTime(bedTime);
|
||||
break;
|
||||
case 700013973:
|
||||
long risingTime = getValueAsLong(dataType, value, -1);
|
||||
if(risingTime >=0)
|
||||
details.setRisingTime(risingTime);
|
||||
break;
|
||||
case 700013156:
|
||||
long wakeupTime = getValueAsLong(dataType, value, -1);
|
||||
if(wakeupTime >=0)
|
||||
details.setWakeupTime(wakeupTime);
|
||||
break;
|
||||
case 700013786:
|
||||
long validData = getValueAsLong(dataType, value, -1);
|
||||
if(validData >=0 && validData <= Integer.MAX_VALUE)
|
||||
details.setValidData((int) validData);
|
||||
break;
|
||||
case 700013254:
|
||||
long sleepDataQuality = getValueAsLong(dataType, value, -1);
|
||||
if(sleepDataQuality >=0 && sleepDataQuality <= Integer.MAX_VALUE)
|
||||
details.setSleepDataQuality((int) sleepDataQuality);
|
||||
break;
|
||||
case 700013679:
|
||||
long deepPart = getValueAsLong(dataType, value, -1);
|
||||
if(deepPart >=0 && deepPart <= Integer.MAX_VALUE)
|
||||
details.setDeepPart((int) deepPart);
|
||||
break;
|
||||
case 700013721:
|
||||
long snoreFreq = getValueAsLong(dataType, value, -1);
|
||||
if(snoreFreq >=0 && snoreFreq <= Integer.MAX_VALUE)
|
||||
details.setSnoreFreq((int) snoreFreq);
|
||||
break;
|
||||
case 700013245:
|
||||
long sleepScore = getValueAsLong(dataType, value, -1);
|
||||
if(sleepScore >=0 && sleepScore <= Integer.MAX_VALUE)
|
||||
details.setSleepScore((int) sleepScore);
|
||||
break;
|
||||
case 700013713:
|
||||
long sleepLatency = getValueAsLong(dataType, value, -1);
|
||||
if(sleepLatency >=0 && sleepLatency <= Integer.MAX_VALUE)
|
||||
details.setSleepLatency((int) sleepLatency);
|
||||
break;
|
||||
case 700013232:
|
||||
long sleepEfficiency = getValueAsLong(dataType, value, -1);
|
||||
if(sleepEfficiency >=0 && sleepEfficiency <= Integer.MAX_VALUE)
|
||||
details.setSleepEfficiency((int) sleepEfficiency);
|
||||
break;
|
||||
case 700013436:
|
||||
long minHeartrate = getValueAsLong(dataType, value, -1);
|
||||
if(minHeartrate >= -1 && minHeartrate <= 255)
|
||||
details.setMinHeartrate((int) minHeartrate);
|
||||
break;
|
||||
case 700013502:
|
||||
long maxHeartrate = getValueAsLong(dataType, value, -1);
|
||||
if(maxHeartrate >=0 && maxHeartrate <= 255)
|
||||
details.setMaxHeartrate((int) maxHeartrate);
|
||||
break;
|
||||
case 700013340:
|
||||
double minOxygenSaturation = getValueAsDouble(dataType, value, -1);
|
||||
if(minOxygenSaturation >=0)
|
||||
details.setMinOxygenSaturation((int) minOxygenSaturation);
|
||||
break;
|
||||
case 700013026:
|
||||
double maxOxygenSaturation = getValueAsDouble(dataType, value, -1);
|
||||
if(maxOxygenSaturation >=0)
|
||||
details.setMaxOxygenSaturation((int) maxOxygenSaturation);
|
||||
break;
|
||||
case 700013646:
|
||||
double minBreathrate = getValueAsDouble(dataType, value, -1);
|
||||
if(minBreathrate >=0 && minBreathrate <= Integer.MAX_VALUE)
|
||||
details.setMinBreathrate((int) minBreathrate);
|
||||
break;
|
||||
case 700013492:
|
||||
double maxBreathrate = getValueAsDouble(dataType, value, -1);
|
||||
if(maxBreathrate >=0 && maxBreathrate <= Integer.MAX_VALUE)
|
||||
details.setMaxBreathrate((int) maxBreathrate);
|
||||
break;
|
||||
default:
|
||||
LOG.info("Unknown dictId: {}", dictId);
|
||||
}
|
||||
}
|
||||
|
||||
private static SleepSummary parseTLVData(byte[] str) {
|
||||
SleepSummary details = new SleepSummary();
|
||||
HuaweiTLV tlv = new HuaweiTLV().parse(str);
|
||||
List<HuaweiTLV> tlvs = tlv.getAllContainerObjects();
|
||||
for (HuaweiTLV tv : tlvs) {
|
||||
List<HuaweiTLV> tlvs2 = tv.getAllContainerObjects();
|
||||
for (HuaweiTLV tv2 : tlvs2) {
|
||||
try {
|
||||
int dictId = tv2.getAsInteger(0x03);
|
||||
int dataType = tv2.getAsInteger(0x04);
|
||||
byte[] value = tv2.getBytes(0x5);
|
||||
fillSleepSummary(details, dictId, dataType, value);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return details;
|
||||
}
|
||||
|
||||
|
||||
public static SleepSummary parseSleepDataSummary(HuaweiSequenceDataParser.SequenceFileData fileData, HuaweiSequenceDataParser.SequenceData data) {
|
||||
// summary type 2 is TLV. TLV works only for fileType != 2 (or maybe only for 1)
|
||||
// currently only data version 1 supported
|
||||
if (data != null && data.getDataVersion() == 1 && data.getSummaryType() == 2 && fileData.getFileType() != 2) {
|
||||
return parseTLVData(data.getSummary());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static long adjustTimeToMinute(long time) {
|
||||
if (time % 60 != 0) {
|
||||
time = (time / 60) * 60;
|
||||
}
|
||||
return time;
|
||||
}
|
||||
|
||||
public static long getTime(long fallAsleepTime, long bedTime, int validData, boolean supportsBedTime) {
|
||||
if (!supportsBedTime || validData == -1) {
|
||||
return adjustTimeToMinute(fallAsleepTime);
|
||||
}
|
||||
return adjustTimeToMinute(bedTime);
|
||||
}
|
||||
|
||||
public static List<SleepStage> parseSleepDetails(byte[] data, long time) {
|
||||
if (data.length % 4 != 0) {
|
||||
LOG.warn("detail length error");
|
||||
return null;
|
||||
}
|
||||
List<SleepStage> result = new ArrayList<>();
|
||||
ByteBuffer buffer = ByteBuffer.wrap(data);
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
while (buffer.remaining() >= 4) {
|
||||
int duration = buffer.getInt();
|
||||
int status = buffer.get();
|
||||
byte[] extra = new byte[3];
|
||||
buffer.get(extra);
|
||||
|
||||
if (duration == 0 || status == 0 || duration % 60 != 0) {
|
||||
LOG.error("invalid record duration: {} status: {} pos: {}", duration, status, buffer.position());
|
||||
continue;
|
||||
}
|
||||
int minutes = duration / 60;
|
||||
if (minutes > 1440) {
|
||||
LOG.error("more than in one day: {} pos: {}", minutes, buffer.position());
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < minutes; i++) {
|
||||
result.add(new SleepStage(time, status));
|
||||
time += 60;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -837,7 +837,7 @@ public class DeviceConfig {
|
||||
case 0xf:
|
||||
break;
|
||||
case 0x11:
|
||||
this.tlv.put(b, 1500012300); // Force AppVersion to 15.0.12.300
|
||||
this.tlv.put(b, 1500106300); // Force AppVersion to 15.1.6.300
|
||||
break;
|
||||
case 0x15:
|
||||
this.tlv.put(b); // Force buildOSPlatformVersion to ""
|
||||
@@ -1857,7 +1857,7 @@ public class DeviceConfig {
|
||||
this.commandId = id;
|
||||
|
||||
// Bits like ext capabilities
|
||||
byte[] capabilities = {(byte) 0xFD, 0x17};
|
||||
byte[] capabilities = {(byte) 0xFD, (byte) 0xF7, 0x53};
|
||||
this.tlv = new HuaweiTLV()
|
||||
.put(0x01, capabilities);
|
||||
|
||||
|
||||
+30
-27
@@ -35,36 +35,29 @@ public class FileDownloadService2C {
|
||||
SLEEP_DATA,
|
||||
RRI,
|
||||
GPS,
|
||||
SEQUENCE_DATA,
|
||||
UNKNOWN; // Never use this as input
|
||||
|
||||
static byte fileTypeToByte(FileType fileType) {
|
||||
switch (fileType) {
|
||||
case SLEEP_STATE:
|
||||
return (byte) 0x0e;
|
||||
case SLEEP_DATA:
|
||||
return (byte) 0x0f;
|
||||
case RRI:
|
||||
return (byte) 0x10;
|
||||
case GPS:
|
||||
return (byte) 0x11;
|
||||
default:
|
||||
throw new RuntimeException();
|
||||
}
|
||||
return switch (fileType) {
|
||||
case SLEEP_STATE -> (byte) 0x0e;
|
||||
case SLEEP_DATA -> (byte) 0x0f;
|
||||
case RRI -> (byte) 0x10;
|
||||
case GPS -> (byte) 0x11;
|
||||
case SEQUENCE_DATA -> (byte) 0x16;
|
||||
default -> throw new RuntimeException();
|
||||
};
|
||||
}
|
||||
|
||||
static FileType byteToFileType(byte b) {
|
||||
switch (b) {
|
||||
case 0x0e:
|
||||
return FileType.SLEEP_STATE;
|
||||
case 0x0f:
|
||||
return FileType.SLEEP_DATA;
|
||||
case 0x10:
|
||||
return FileType.RRI;
|
||||
case 0x11:
|
||||
return FileType.GPS;
|
||||
default:
|
||||
return FileType.UNKNOWN;
|
||||
}
|
||||
return switch (b) {
|
||||
case 0x0e -> FileType.SLEEP_STATE;
|
||||
case 0x0f -> FileType.SLEEP_DATA;
|
||||
case 0x10 -> FileType.RRI;
|
||||
case 0x11 -> FileType.GPS;
|
||||
case 0x16 -> FileType.SEQUENCE_DATA;
|
||||
default -> FileType.UNKNOWN;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,19 +65,25 @@ public class FileDownloadService2C {
|
||||
public static final int id = 0x01;
|
||||
|
||||
public static class Request extends HuaweiPacket {
|
||||
public Request(ParamsProvider paramsProvider, String filename, FileType filetype, int startTime, int endTime) {
|
||||
public Request(ParamsProvider paramsProvider, String filename, FileType filetype, int startTime, int endTime, int dictId) {
|
||||
super(paramsProvider);
|
||||
|
||||
this.serviceId = FileDownloadService2C.id;
|
||||
this.commandId = id;
|
||||
|
||||
|
||||
byte fileId = FileType.fileTypeToByte(filetype);
|
||||
// TODO: start and end time might be optional?
|
||||
this.tlv = new HuaweiTLV()
|
||||
.put(0x01, filename)
|
||||
.put(0x02, FileType.fileTypeToByte(filetype))
|
||||
.put(0x02, fileId)
|
||||
.put(0x05, startTime)
|
||||
.put(0x06, endTime);
|
||||
|
||||
if(dictId > 0 && fileId == 0x16) { // SEQUENCE_DATA
|
||||
this.tlv.put(0x0c, dictId);
|
||||
}
|
||||
|
||||
this.complete = true;
|
||||
}
|
||||
}
|
||||
@@ -190,7 +189,7 @@ public class FileDownloadService2C {
|
||||
public static class RequestBlock extends HuaweiPacket {
|
||||
public static final int id = 0x04;
|
||||
|
||||
public RequestBlock(ParamsProvider paramsProvider, byte fileId, int offset, int size, boolean noEncrypt) {
|
||||
public RequestBlock(ParamsProvider paramsProvider, byte fileId, int offset, int size, boolean noEncrypt, int dictId) {
|
||||
super(paramsProvider);
|
||||
|
||||
this.serviceId = FileDownloadService2C.id;
|
||||
@@ -201,6 +200,10 @@ public class FileDownloadService2C {
|
||||
.put(0x02, offset)
|
||||
.put(0x03, size);
|
||||
|
||||
if(dictId > 0 && fileId == 0x16) { // SEQUENCE_DATA
|
||||
this.tlv.put(0x04, dictId);
|
||||
}
|
||||
|
||||
this.complete = true;
|
||||
this.isEncrypted = !noEncrypt;
|
||||
}
|
||||
|
||||
+22
-1
@@ -123,6 +123,7 @@ public class HuaweiFileDownloadManager {
|
||||
SLEEP_DATA,
|
||||
RRI,
|
||||
GPS,
|
||||
SEQUENCE_DATA,
|
||||
UNKNOWN // Never for input!
|
||||
}
|
||||
|
||||
@@ -151,6 +152,8 @@ public class HuaweiFileDownloadManager {
|
||||
private int startTime = 0;
|
||||
private int endTime = 0;
|
||||
|
||||
private int dictId = 0;
|
||||
|
||||
// GPS type only
|
||||
private short workoutId;
|
||||
private Long databaseId;
|
||||
@@ -180,7 +183,21 @@ public class HuaweiFileDownloadManager {
|
||||
}
|
||||
|
||||
public static FileRequest rriFileRequest(boolean supportsRriNewSync, int startTime, int endTime, FileDownloadCallback fileDownloadCallback) {
|
||||
return new FileRequest("rrisqi_data.bin", FileType.RRI, supportsRriNewSync, startTime, endTime, fileDownloadCallback);
|
||||
return new FileRequest("rrisqi_data.bin", FileType.RRI, supportsRriNewSync, startTime, endTime,fileDownloadCallback);
|
||||
}
|
||||
|
||||
private FileRequest(String filename, FileType fileType, int startTime, int endTime, int dictId, FileDownloadCallback fileDownloadCallback) {
|
||||
this.filename = filename;
|
||||
this.fileType = fileType;
|
||||
this.newSync = true;
|
||||
this.fileDownloadCallback = fileDownloadCallback;
|
||||
this.startTime = startTime;
|
||||
this.endTime = endTime;
|
||||
this.dictId = dictId;
|
||||
}
|
||||
|
||||
public static FileRequest sequenceDataFileRequest(int startTime, int endTime, int dictId, FileDownloadCallback fileDownloadCallback) {
|
||||
return new FileRequest("sequence_data", FileType.SEQUENCE_DATA, startTime, endTime, dictId, fileDownloadCallback);
|
||||
}
|
||||
|
||||
private FileRequest(String filename, FileType fileType, boolean newSync, FileDownloadCallback fileDownloadCallback) {
|
||||
@@ -354,6 +371,10 @@ public class HuaweiFileDownloadManager {
|
||||
public void setNeedVerify(boolean needVerify) {
|
||||
this.needVerify = needVerify;
|
||||
}
|
||||
|
||||
public int getDictId() { return dictId;}
|
||||
|
||||
public void setDictId(int dictId) { this.dictId = dictId;}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+137
-36
@@ -71,9 +71,13 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiDictTypes;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiGpsParser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiSequenceDataParser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiSleepStageSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiSleepStatsSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiStressParser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiStressSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTruSleepParser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTrueSleepSequenceDataParser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.CameraRemote;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.GpsAndTime;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather;
|
||||
@@ -87,6 +91,8 @@ import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiDictData;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiDictDataDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiDictDataValues;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiDictDataValuesDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiSleepStageSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiSleepStatsSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiStressSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutDataSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutDataSampleDao;
|
||||
@@ -924,7 +930,7 @@ public class HuaweiSupportProvider {
|
||||
HuaweiP2PDataDictionarySyncService trackService = new HuaweiP2PDataDictionarySyncService(huaweiP2PManager);
|
||||
trackService.register();
|
||||
}
|
||||
if(getHuaweiCoordinator().supportsNotificationsAddIconTimestamp()) {
|
||||
if (getHuaweiCoordinator().supportsNotificationsAddIconTimestamp()) {
|
||||
if (HuaweiP2PAppIcon.getRegisteredInstance(huaweiP2PManager) == null) {
|
||||
HuaweiP2PAppIcon appIconService = new HuaweiP2PAppIcon(huaweiP2PManager);
|
||||
appIconService.register();
|
||||
@@ -932,11 +938,11 @@ public class HuaweiSupportProvider {
|
||||
}
|
||||
}
|
||||
|
||||
if(getHuaweiCoordinator().supportsThreeCircle() || getHuaweiCoordinator().supportsThreeCircleLite()) {
|
||||
if (getHuaweiCoordinator().supportsThreeCircle() || getHuaweiCoordinator().supportsThreeCircleLite()) {
|
||||
huaweiDataSyncTreeCircleGoals = new HuaweiDataSyncGoals(HuaweiSupportProvider.this);
|
||||
}
|
||||
|
||||
if(getHuaweiCoordinator().supportsFindDeviceAbility()) {
|
||||
if (getHuaweiCoordinator().supportsFindDeviceAbility()) {
|
||||
huaweiDataSyncFindDevice = new HuaweiDataSyncFindDevice(HuaweiSupportProvider.this);
|
||||
}
|
||||
}
|
||||
@@ -1231,9 +1237,9 @@ public class HuaweiSupportProvider {
|
||||
}
|
||||
|
||||
public void setStepsGoal() {
|
||||
if(huaweiDataSyncTreeCircleGoals != null) {
|
||||
if (huaweiDataSyncTreeCircleGoals != null) {
|
||||
int stepGoal = GBApplication.getPrefs().getInt(ActivityUser.PREF_USER_STEPS_GOAL, ActivityUser.defaultUserStepsGoal);
|
||||
if(! huaweiDataSyncTreeCircleGoals.sendStepsGoal(stepGoal)) {
|
||||
if (!huaweiDataSyncTreeCircleGoals.sendStepsGoal(stepGoal)) {
|
||||
LOG.error("Error to set stand goal");
|
||||
}
|
||||
} else {
|
||||
@@ -1246,9 +1252,9 @@ public class HuaweiSupportProvider {
|
||||
}
|
||||
|
||||
public void setCaloriesBurntGoal() {
|
||||
if(huaweiDataSyncTreeCircleGoals != null) {
|
||||
if (huaweiDataSyncTreeCircleGoals != null) {
|
||||
int caloriesBurntGoal = GBApplication.getPrefs().getInt(ActivityUser.PREF_USER_CALORIES_BURNT, ActivityUser.defaultUserCaloriesBurntGoal);
|
||||
if(!huaweiDataSyncTreeCircleGoals.sendCaloriesBurntGoal(caloriesBurntGoal)) {
|
||||
if (!huaweiDataSyncTreeCircleGoals.sendCaloriesBurntGoal(caloriesBurntGoal)) {
|
||||
LOG.error("Error to set calories burnt goal");
|
||||
}
|
||||
} else {
|
||||
@@ -1261,9 +1267,9 @@ public class HuaweiSupportProvider {
|
||||
}
|
||||
|
||||
public void setFatBurnTime() {
|
||||
if(huaweiDataSyncTreeCircleGoals != null) {
|
||||
if (huaweiDataSyncTreeCircleGoals != null) {
|
||||
int fatBurnTimeGoal = GBApplication.getPrefs().getInt(ActivityUser.PREF_USER_GOAL_FAT_BURN_TIME_MINUTES, ActivityUser.defaultUserFatBurnTimeMinutes);
|
||||
if(!huaweiDataSyncTreeCircleGoals.sendExerciseGoal(fatBurnTimeGoal)) {
|
||||
if (!huaweiDataSyncTreeCircleGoals.sendExerciseGoal(fatBurnTimeGoal)) {
|
||||
LOG.error("Error to set exercise goal");
|
||||
}
|
||||
} else {
|
||||
@@ -1276,9 +1282,9 @@ public class HuaweiSupportProvider {
|
||||
}
|
||||
|
||||
public void setStandingTime() {
|
||||
if(huaweiDataSyncTreeCircleGoals != null) {
|
||||
if (huaweiDataSyncTreeCircleGoals != null) {
|
||||
int standingTimeGoal = GBApplication.getPrefs().getInt(PREF_USER_GOAL_STANDING_TIME_HOURS, ActivityUser.defaultUserGoalStandingTimeHours);
|
||||
if(!huaweiDataSyncTreeCircleGoals.sendStandGoal(standingTimeGoal)) {
|
||||
if (!huaweiDataSyncTreeCircleGoals.sendStandGoal(standingTimeGoal)) {
|
||||
LOG.error("Error to set stand goal");
|
||||
}
|
||||
} else {
|
||||
@@ -1291,27 +1297,27 @@ public class HuaweiSupportProvider {
|
||||
}
|
||||
|
||||
public void setActivityReminderStand() {
|
||||
if(huaweiDataSyncTreeCircleGoals != null) {
|
||||
if (huaweiDataSyncTreeCircleGoals != null) {
|
||||
boolean state = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getBoolean(PREF_HUAWEI_ACTIVITY_REMINDER_STAND, true);
|
||||
if(!huaweiDataSyncTreeCircleGoals.sendRemindersStand(state)) {
|
||||
if (!huaweiDataSyncTreeCircleGoals.sendRemindersStand(state)) {
|
||||
LOG.error("Error to set stand reminder");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setActivityReminderProgress() {
|
||||
if(huaweiDataSyncTreeCircleGoals != null) {
|
||||
if (huaweiDataSyncTreeCircleGoals != null) {
|
||||
boolean state = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getBoolean(PREF_HUAWEI_ACTIVITY_REMINDER_PROGRESS, true);
|
||||
if(!huaweiDataSyncTreeCircleGoals.sendRemindersProgress(state)) {
|
||||
if (!huaweiDataSyncTreeCircleGoals.sendRemindersProgress(state)) {
|
||||
LOG.error("Error to set progress reminder");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setActivityReminderGoalReached() {
|
||||
if(huaweiDataSyncTreeCircleGoals != null) {
|
||||
if (huaweiDataSyncTreeCircleGoals != null) {
|
||||
boolean state = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getBoolean(PREF_HUAWEI_ACTIVITY_REMINDER_GOAL_REACHED, true);
|
||||
if(!huaweiDataSyncTreeCircleGoals.sendRemindersGoalReached(state)) {
|
||||
if (!huaweiDataSyncTreeCircleGoals.sendRemindersGoalReached(state)) {
|
||||
LOG.error("Error to set goal reached reminder");
|
||||
}
|
||||
}
|
||||
@@ -1325,7 +1331,7 @@ public class HuaweiSupportProvider {
|
||||
}
|
||||
LOG.debug("startUploadNotificationsAppIcons: {}", iconsToUpload);
|
||||
HuaweiP2PAppIcon appIconService = HuaweiP2PAppIcon.getRegisteredInstance(this.huaweiP2PManager);
|
||||
if(appIconService != null) {
|
||||
if (appIconService != null) {
|
||||
appIconService.addPackageName(new ArrayList<>(iconsToUpload));
|
||||
}
|
||||
}
|
||||
@@ -1398,6 +1404,10 @@ public class HuaweiSupportProvider {
|
||||
HuaweiSampleProvider sampleProvider = new HuaweiSampleProvider(gbDevice, db.getDaoSession());
|
||||
sleepStart = sampleProvider.getLastSleepFetchTimestamp();
|
||||
stepStart = sampleProvider.getLastStepFetchTimestamp();
|
||||
|
||||
HuaweiSleepStatsSampleProvider sleepStatsSampleProvider = new HuaweiSleepStatsSampleProvider(gbDevice, db.getDaoSession());
|
||||
int sleepStart2 = (int) (sleepStatsSampleProvider.getLastSleepFetchTimestamp() / 1000L);
|
||||
sleepStart = Math.max(sleepStart, sleepStart2);
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Exception for getting start times, using 01/01/2000 - 00:00:00.");
|
||||
}
|
||||
@@ -1488,7 +1498,7 @@ public class HuaweiSupportProvider {
|
||||
|
||||
if (P2PSyncService != null) {
|
||||
List<Integer> list = P2PSyncService.checkSupported(this.getHuaweiCoordinator(), Arrays.asList(HuaweiDictTypes.SKIN_TEMPERATURE_CLASS, HuaweiDictTypes.BLOOD_PRESSURE_CLASS));
|
||||
if(!list.isEmpty()) {
|
||||
if (!list.isEmpty()) {
|
||||
syncState.setP2pSync(true);
|
||||
P2PSyncService.startSync(list, complete -> {
|
||||
LOG.info("Sync P2P Data complete");
|
||||
@@ -1616,6 +1626,7 @@ public class HuaweiSupportProvider {
|
||||
}
|
||||
return msgId;
|
||||
}
|
||||
|
||||
public void onNotification(NotificationSpec notificationSpec) {
|
||||
if (!GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()).getBoolean(DeviceSettingsPreferenceConst.PREF_NOTIFICATION_ENABLE, false)) {
|
||||
// Don't send notifications when they are disabled
|
||||
@@ -1903,7 +1914,7 @@ public class HuaweiSupportProvider {
|
||||
db.getDaoSession().getHuaweiWorkoutSummarySampleDao().insertOrReplace(summarySample);
|
||||
|
||||
// We should completely replace values. Delete all and insert again.
|
||||
final DeleteQuery<HuaweiWorkoutSummaryAdditionalValuesSample> tableDeleteQuery = db.getDaoSession().getHuaweiWorkoutSummaryAdditionalValuesSampleDao().queryBuilder()
|
||||
final DeleteQuery<HuaweiWorkoutSummaryAdditionalValuesSample> tableDeleteQuery = db.getDaoSession().getHuaweiWorkoutSummaryAdditionalValuesSampleDao().queryBuilder()
|
||||
.where(HuaweiWorkoutSummaryAdditionalValuesSampleDao.Properties.WorkoutId.eq(workoutId))
|
||||
.buildDelete();
|
||||
tableDeleteQuery.executeDeleteWithoutDetachingEntities();
|
||||
@@ -2391,7 +2402,7 @@ public class HuaweiSupportProvider {
|
||||
boolean automaticStressEnabled = GBApplication
|
||||
.getDeviceSpecificSharedPrefs(getDevice().getAddress())
|
||||
.getBoolean(HuaweiConstants.PREF_HUAWEI_STRESS_SWITCH, false);
|
||||
if(automaticStressEnabled && getLastStressData() == null) {
|
||||
if (automaticStressEnabled && getLastStressData() == null) {
|
||||
SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(this.getDevice().getAddress());
|
||||
SharedPreferences.Editor editor = sharedPrefs.edit();
|
||||
editor.putBoolean(HuaweiConstants.PREF_HUAWEI_STRESS_SWITCH, false);
|
||||
@@ -2411,7 +2422,7 @@ public class HuaweiSupportProvider {
|
||||
|
||||
private void calibrateStress() {
|
||||
|
||||
if(stressCalibration != null) {
|
||||
if (stressCalibration != null) {
|
||||
GB.toast(context.getString(R.string.huawei_stress_calibrate_in_progress), Toast.LENGTH_SHORT, GB.INFO);
|
||||
return;
|
||||
}
|
||||
@@ -2424,7 +2435,7 @@ public class HuaweiSupportProvider {
|
||||
GB.toast(context.getString(R.string.huawei_stress_calibrate_done), Toast.LENGTH_SHORT, GB.INFO);
|
||||
final Intent intent = new Intent(HuaweiStressCalibrationFragment.ACTION_STRESS_RESULT);
|
||||
String str = HuaweiStressParser.stressDataToJsonStr(stressData);
|
||||
if(!TextUtils.isEmpty(str)) {
|
||||
if (!TextUtils.isEmpty(str)) {
|
||||
intent.putExtra(HuaweiStressCalibrationFragment.EXTRA_STRESS_ERROR, false);
|
||||
intent.putExtra(HuaweiStressCalibrationFragment.EXTRA_STRESS_DATA, str);
|
||||
} else {
|
||||
@@ -2449,7 +2460,7 @@ public class HuaweiSupportProvider {
|
||||
GB.toast(context.getString(R.string.huawei_stress_calibrate_error), Toast.LENGTH_SHORT, GB.ERROR);
|
||||
}
|
||||
});
|
||||
if(ret) {
|
||||
if (ret) {
|
||||
GB.toast(context.getString(R.string.huawei_stress_calibrate_started), Toast.LENGTH_SHORT, GB.INFO);
|
||||
} else {
|
||||
GB.toast(context.getString(R.string.huawei_stress_calibrate_in_progress), Toast.LENGTH_SHORT, GB.INFO);
|
||||
@@ -2458,7 +2469,7 @@ public class HuaweiSupportProvider {
|
||||
|
||||
public void storeLastStressData(HuaweiStressParser.StressData data) {
|
||||
String str = HuaweiStressParser.stressDataToJsonStr(data);
|
||||
if(TextUtils.isEmpty(str)) {
|
||||
if (TextUtils.isEmpty(str)) {
|
||||
LOG.error("Failed to store stress data");
|
||||
return;
|
||||
}
|
||||
@@ -2472,7 +2483,7 @@ public class HuaweiSupportProvider {
|
||||
String str = GBApplication
|
||||
.getDeviceSpecificSharedPrefs(this.getDevice().getAddress())
|
||||
.getString(HuaweiConstants.PREF_HUAWEI_STRESS_LAST_DATA, "");
|
||||
if(TextUtils.isEmpty(str)) {
|
||||
if (TextUtils.isEmpty(str)) {
|
||||
LOG.error("Failed to get saved stress data");
|
||||
return null;
|
||||
}
|
||||
@@ -2546,7 +2557,7 @@ public class HuaweiSupportProvider {
|
||||
LOG.info("enter onAppInstall uri: {}", uri);
|
||||
HuaweiFwHelper huaweiFwHelper = new HuaweiFwHelper(uri, getContext());
|
||||
|
||||
if(huaweiFwHelper.isFirmware) {
|
||||
if (huaweiFwHelper.isFirmware) {
|
||||
huaweiOTAManager.startFwUpdate(huaweiFwHelper.fwInfo, uri);
|
||||
return;
|
||||
}
|
||||
@@ -2760,8 +2771,8 @@ public class HuaweiSupportProvider {
|
||||
HuaweiFileDownloadManager.FileRequest request = HuaweiFileDownloadManager.FileRequest.IncomingFileRequest(filename, new HuaweiFileDownloadManager.FileDownloadCallback() {
|
||||
@Override
|
||||
public void downloadComplete(HuaweiFileDownloadManager.FileRequest fileRequest) {
|
||||
LOG.info("Download file: {}", fileRequest.getFilename());
|
||||
huaweiP2PManager.handleFile(fileRequest.getSrcPackage(), fileRequest.getDstPackage(), fileRequest.getSrcFingerprint(), fileRequest.getDstFingerprint(), fileRequest.getFilename(), fileRequest.getData());
|
||||
LOG.info("Download file: {}", fileRequest.getFilename());
|
||||
huaweiP2PManager.handleFile(fileRequest.getSrcPackage(), fileRequest.getDstPackage(), fileRequest.getSrcFingerprint(), fileRequest.getDstFingerprint(), fileRequest.getFilename(), fileRequest.getData());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -2785,11 +2796,97 @@ public class HuaweiSupportProvider {
|
||||
|
||||
}
|
||||
|
||||
private boolean downloadDictTrueSleepData(int start, int end) {
|
||||
LOG.info("supportsDictSleepSync: {}", getHuaweiCoordinator().supportsDictSleepSync());
|
||||
if (!getHuaweiCoordinator().supportsDictSleepSync()) {
|
||||
return false;
|
||||
}
|
||||
huaweiFileDownloadManager.addToQueue(HuaweiFileDownloadManager.FileRequest.sequenceDataFileRequest(
|
||||
start,
|
||||
end,
|
||||
HuaweiDictTypes.SLEEP_DETAILS_CLASS,
|
||||
new HuaweiFileDownloadManager.FileDownloadCallback() {
|
||||
@Override
|
||||
public void downloadComplete(HuaweiFileDownloadManager.FileRequest fileRequest) {
|
||||
HuaweiSequenceDataParser.SequenceFileData sequenceFileData = HuaweiSequenceDataParser.parseSequenceFileData(fileRequest.getData());
|
||||
LOG.info("SLEEP File data: {}", sequenceFileData);
|
||||
|
||||
if (sequenceFileData != null) {
|
||||
final List<HuaweiSleepStatsSample> sleepStatsSamples = new ArrayList<>();
|
||||
final List<HuaweiSleepStageSample> sleepStageSamples = new ArrayList<>();
|
||||
|
||||
for (HuaweiSequenceDataParser.SequenceData sd : sequenceFileData.getSequenceDataList()) {
|
||||
LOG.info("SLEEP SequenceData: {}", sd);
|
||||
HuaweiTrueSleepSequenceDataParser.SleepSummary sleepDataSummary = HuaweiTrueSleepSequenceDataParser.parseSleepDataSummary(sequenceFileData, sd);
|
||||
LOG.info("SLEEP DataSummary: {}", sleepDataSummary);
|
||||
|
||||
if(sleepDataSummary == null)
|
||||
continue;
|
||||
|
||||
HuaweiSleepStatsSample sleepStat = new HuaweiSleepStatsSample();
|
||||
sleepStat.setTimestamp(sleepDataSummary.getFallAsleepTime() * 1000L);
|
||||
sleepStat.setSleepScore(sleepDataSummary.getSleepScore());
|
||||
sleepStat.setBedTime(sleepDataSummary.getBedTime() * 1000L);
|
||||
sleepStat.setRisingTime(sleepDataSummary.getRisingTime() * 1000L);
|
||||
sleepStat.setWakeupTime(sleepDataSummary.getWakeupTime() * 1000L);
|
||||
sleepStat.setSleepDataQuality(sleepDataSummary.getSleepDataQuality());
|
||||
sleepStat.setDeepPart(sleepDataSummary.getDeepPart());
|
||||
sleepStat.setSnoreFreq(sleepDataSummary.getSnoreFreq());
|
||||
sleepStat.setSleepLatency(sleepDataSummary.getSleepLatency());
|
||||
sleepStat.setSleepEfficiency(sleepDataSummary.getSleepEfficiency());
|
||||
sleepStat.setMinHeartRate(sleepDataSummary.getMinHeartrate());
|
||||
sleepStat.setMaxHeartRate(sleepDataSummary.getMaxHeartrate());
|
||||
sleepStat.setMinOxygenSaturation(sleepDataSummary.getMinOxygenSaturation());
|
||||
sleepStat.setMaxOxygenSaturation(sleepDataSummary.getMaxOxygenSaturation());
|
||||
sleepStat.setMinBreathRate(sleepDataSummary.getMinBreathrate());
|
||||
sleepStat.setMaxBreathRate(sleepDataSummary.getMaxBreathrate());
|
||||
sleepStatsSamples.add(sleepStat);
|
||||
|
||||
long time = HuaweiTrueSleepSequenceDataParser.getTime(sleepDataSummary.getFallAsleepTime(), sleepDataSummary.getBedTime(), sleepDataSummary.getValidData(), getHuaweiCoordinator().supportsBedTime());
|
||||
LOG.info("SLEEP Time: {}", time);
|
||||
List<HuaweiTrueSleepSequenceDataParser.SleepStage> stages = HuaweiTrueSleepSequenceDataParser.parseSleepDetails(sd.getDetails(), time);
|
||||
LOG.info("SLEEP Stages: {}", stages);
|
||||
if(stages != null) {
|
||||
for (HuaweiTrueSleepSequenceDataParser.SleepStage st : stages) {
|
||||
HuaweiSleepStageSample sample = new HuaweiSleepStageSample();
|
||||
sample.setTimestamp(st.getTime() * 1000L);
|
||||
sample.setStage(st.getStage());
|
||||
sleepStageSamples.add(sample);
|
||||
}
|
||||
}
|
||||
}
|
||||
try (DBHandler db = GBApplication.acquireDB()) {
|
||||
final DaoSession session = db.getDaoSession();
|
||||
new HuaweiSleepStatsSampleProvider(gbDevice, session).persistForDevice(context, gbDevice, sleepStatsSamples);
|
||||
new HuaweiSleepStageSampleProvider(gbDevice, session).persistForDevice(context, gbDevice, sleepStageSamples);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Cannot save sleep, continue");
|
||||
}
|
||||
|
||||
}
|
||||
syncState.setActivitySync(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void downloadException(HuaweiFileDownloadManager.HuaweiFileDownloadException e) {
|
||||
super.downloadException(e);
|
||||
syncState.setActivitySync(false);
|
||||
}
|
||||
}
|
||||
), true);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean downloadTruSleepData(int start, int end) {
|
||||
|
||||
|
||||
// We only get the data if TruSleep is supported
|
||||
if (!getHuaweiCoordinator().supportsTruSleep())
|
||||
return false;
|
||||
|
||||
if (downloadDictTrueSleepData(start, end))
|
||||
return true;
|
||||
|
||||
HuaweiTruSleepParser.SleepFileDownloadCallback callback = new HuaweiTruSleepParser.SleepFileDownloadCallback(this) {
|
||||
@Override
|
||||
public void syncComplete(byte[] statusData, byte[] sleepData) {
|
||||
@@ -2845,14 +2942,14 @@ public class HuaweiSupportProvider {
|
||||
LOG.debug("Parsing stress file");
|
||||
HuaweiStressParser.RriFileData results = HuaweiStressParser.parseRri(fileRequest.getData());
|
||||
LOG.info("stress result: {}", results);
|
||||
if(results != null && !results.stressData.isEmpty()) {
|
||||
if (results != null && !results.stressData.isEmpty()) {
|
||||
HuaweiStressParser.StressData stressData = results.stressData.get(results.stressData.size() - 1);
|
||||
LOG.info("Last stored stress data: {}", stressData);
|
||||
HuaweiStressParser.StressData currentStressData = getLastStressData();
|
||||
if(currentStressData == null || stressData.endTime > currentStressData.endTime) {
|
||||
if (currentStressData == null || stressData.endTime > currentStressData.endTime) {
|
||||
storeLastStressData(stressData);
|
||||
}
|
||||
for(HuaweiStressParser.StressData dt: results.stressData) {
|
||||
for (HuaweiStressParser.StressData dt : results.stressData) {
|
||||
addStressData(dt.startTime, dt.endTime, dt.score, dt.level);
|
||||
}
|
||||
}
|
||||
@@ -3024,6 +3121,10 @@ public class HuaweiSupportProvider {
|
||||
|
||||
|
||||
public void onTestNewFunction() {
|
||||
gbDevice.setBusyTask(R.string.busy_task_downloading, getContext());
|
||||
gbDevice.sendDeviceUpdateIntent(getContext());
|
||||
|
||||
downloadDictTrueSleepData(0, (int) (System.currentTimeMillis() / 1000));
|
||||
}
|
||||
|
||||
public void onMusicListReq() {
|
||||
@@ -3031,7 +3132,7 @@ public class HuaweiSupportProvider {
|
||||
}
|
||||
|
||||
public void onMusicOperation(int operation, int playlistIndex, String playlistName, ArrayList<Integer> musicIds) {
|
||||
getHuaweiMusicManager().onMusicOperation(operation, playlistIndex, playlistName, musicIds);
|
||||
getHuaweiMusicManager().onMusicOperation(operation, playlistIndex, playlistName, musicIds);
|
||||
}
|
||||
|
||||
public void onSetCannedMessages(final CannedMessagesSpec cannedMessagesSpec) {
|
||||
@@ -3040,13 +3141,13 @@ public class HuaweiSupportProvider {
|
||||
return;
|
||||
}
|
||||
|
||||
if(cannedMessagesSpec.cannedMessages.length == 0) {
|
||||
if (cannedMessagesSpec.cannedMessages.length == 0) {
|
||||
GB.toast(context, HuaweiSupportProvider.this.getContext().getString(R.string.canned_replies_not_empty), Toast.LENGTH_SHORT, GB.WARN);
|
||||
LOG.warn(HuaweiSupportProvider.this.getContext().getString(R.string.canned_replies_not_empty));
|
||||
}
|
||||
|
||||
HuaweiP2PCannedRepliesService cannedRepliesService = HuaweiP2PCannedRepliesService.getRegisteredInstance(huaweiP2PManager);
|
||||
if(cannedRepliesService == null) {
|
||||
if (cannedRepliesService == null) {
|
||||
LOG.warn("P2P canned replies service is not registered");
|
||||
return;
|
||||
}
|
||||
@@ -3054,7 +3155,7 @@ public class HuaweiSupportProvider {
|
||||
}
|
||||
|
||||
public void onFindDevice(boolean start) {
|
||||
if(huaweiDataSyncFindDevice != null) {
|
||||
if (huaweiDataSyncFindDevice != null) {
|
||||
if (start) {
|
||||
huaweiDataSyncFindDevice.sendStartFindDevice();
|
||||
} else {
|
||||
|
||||
+2
-1
@@ -48,7 +48,8 @@ public class GetFileBlockRequest extends Request {
|
||||
this.request.getFileId(),
|
||||
this.request.getCurrentOffset(),
|
||||
this.request.getCurrentBlockSize(),
|
||||
this.request.isNoEncrypt()
|
||||
this.request.isNoEncrypt(),
|
||||
this.request.getDictId()
|
||||
).serialize();
|
||||
else
|
||||
return new FileDownloadService0A.RequestBlock.Request(
|
||||
|
||||
+17
-25
@@ -52,33 +52,25 @@ public class GetFileDownloadInitRequest extends Request {
|
||||
}
|
||||
|
||||
private FileDownloadService2C.FileType convertFileTypeTo2C(HuaweiFileDownloadManager.FileType type) {
|
||||
switch (type) {
|
||||
case SLEEP_STATE:
|
||||
return FileDownloadService2C.FileType.SLEEP_STATE;
|
||||
case SLEEP_DATA:
|
||||
return FileDownloadService2C.FileType.SLEEP_DATA;
|
||||
case RRI:
|
||||
return FileDownloadService2C.FileType.RRI;
|
||||
case GPS:
|
||||
return FileDownloadService2C.FileType.GPS;
|
||||
default:
|
||||
return FileDownloadService2C.FileType.UNKNOWN;
|
||||
}
|
||||
return switch (type) {
|
||||
case SLEEP_STATE -> FileDownloadService2C.FileType.SLEEP_STATE;
|
||||
case SLEEP_DATA -> FileDownloadService2C.FileType.SLEEP_DATA;
|
||||
case RRI -> FileDownloadService2C.FileType.RRI;
|
||||
case GPS -> FileDownloadService2C.FileType.GPS;
|
||||
case SEQUENCE_DATA -> FileDownloadService2C.FileType.SEQUENCE_DATA;
|
||||
default -> FileDownloadService2C.FileType.UNKNOWN;
|
||||
};
|
||||
}
|
||||
|
||||
private HuaweiFileDownloadManager.FileType convertFileTypeFrom2C(FileDownloadService2C.FileType type) {
|
||||
switch (type) {
|
||||
case SLEEP_STATE:
|
||||
return HuaweiFileDownloadManager.FileType.SLEEP_STATE;
|
||||
case SLEEP_DATA:
|
||||
return HuaweiFileDownloadManager.FileType.SLEEP_DATA;
|
||||
case RRI:
|
||||
return HuaweiFileDownloadManager.FileType.RRI;
|
||||
case GPS:
|
||||
return HuaweiFileDownloadManager.FileType.GPS;
|
||||
default:
|
||||
return HuaweiFileDownloadManager.FileType.UNKNOWN;
|
||||
}
|
||||
return switch (type) {
|
||||
case SLEEP_STATE -> HuaweiFileDownloadManager.FileType.SLEEP_STATE;
|
||||
case SLEEP_DATA -> HuaweiFileDownloadManager.FileType.SLEEP_DATA;
|
||||
case RRI -> HuaweiFileDownloadManager.FileType.RRI;
|
||||
case GPS -> HuaweiFileDownloadManager.FileType.GPS;
|
||||
case SEQUENCE_DATA -> HuaweiFileDownloadManager.FileType.SEQUENCE_DATA;
|
||||
default -> HuaweiFileDownloadManager.FileType.UNKNOWN;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -88,7 +80,7 @@ public class GetFileDownloadInitRequest extends Request {
|
||||
FileDownloadService2C.FileType type = convertFileTypeTo2C(request.getFileType());
|
||||
if (type == FileDownloadService2C.FileType.UNKNOWN)
|
||||
throw new RequestCreationException("Cannot convert type " + request.getFileType());
|
||||
return new FileDownloadService2C.FileDownloadInit.Request(paramsProvider, request.getFilename(), type, request.getStartTime(), request.getEndTime()).serialize();
|
||||
return new FileDownloadService2C.FileDownloadInit.Request(paramsProvider, request.getFilename(), type, request.getStartTime(), request.getEndTime(), request.getDictId()).serialize();
|
||||
} else {
|
||||
if (this.request.getFileType() == HuaweiFileDownloadManager.FileType.DEBUG)
|
||||
return new FileDownloadService0A.FileDownloadInit.DebugFilesRequest(paramsProvider).serialize();
|
||||
|
||||
Reference in New Issue
Block a user