mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2026-07-31 07:44:24 +02:00
Huawei: sync raw ECG data(no UI)
This commit is contained in:
@@ -15,6 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
package nodomain.freeyourgadget.gadgetbridge.daogen;
|
package nodomain.freeyourgadget.gadgetbridge.daogen;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import de.greenrobot.daogenerator.DaoGenerator;
|
import de.greenrobot.daogenerator.DaoGenerator;
|
||||||
import de.greenrobot.daogenerator.Entity;
|
import de.greenrobot.daogenerator.Entity;
|
||||||
import de.greenrobot.daogenerator.Index;
|
import de.greenrobot.daogenerator.Index;
|
||||||
@@ -56,7 +58,7 @@ public class GBDaoGenerator {
|
|||||||
private static final String TIMESTAMP_TO = "timestampTo";
|
private static final String TIMESTAMP_TO = "timestampTo";
|
||||||
|
|
||||||
public static void main(String[] args) throws Exception {
|
public static void main(String[] args) throws Exception {
|
||||||
final Schema schema = new Schema(120, MAIN_PACKAGE + ".entities");
|
final Schema schema = new Schema(121, MAIN_PACKAGE + ".entities");
|
||||||
|
|
||||||
Entity userAttributes = addUserAttributes(schema);
|
Entity userAttributes = addUserAttributes(schema);
|
||||||
Entity user = addUserInfo(schema, userAttributes);
|
Entity user = addUserInfo(schema, userAttributes);
|
||||||
@@ -187,6 +189,9 @@ public class GBDaoGenerator {
|
|||||||
addHuaweiWorkoutSpO2Sample(schema, huaweiWorkoutSummary);
|
addHuaweiWorkoutSpO2Sample(schema, huaweiWorkoutSummary);
|
||||||
addHuaweiWorkoutSectionsSample(schema, huaweiWorkoutSummary);
|
addHuaweiWorkoutSectionsSample(schema, huaweiWorkoutSummary);
|
||||||
|
|
||||||
|
Entity huaweiEcgSummary = addHuaweiEcgSummarySample(schema, user, device);
|
||||||
|
addHuaweiEcgDataSample(schema, huaweiEcgSummary);
|
||||||
|
|
||||||
Entity huaweiDictData = addHuaweiDictData(schema, user, device);
|
Entity huaweiDictData = addHuaweiDictData(schema, user, device);
|
||||||
addHuaweiDictDataValues(schema, huaweiDictData);
|
addHuaweiDictDataValues(schema, huaweiDictData);
|
||||||
|
|
||||||
@@ -1894,6 +1899,42 @@ public class GBDaoGenerator {
|
|||||||
return workoutSectionsSample;
|
return workoutSectionsSample;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Entity addHuaweiEcgSummarySample(Schema schema, Entity user, Entity device) {
|
||||||
|
Entity ecgSummary = addEntity(schema, "HuaweiEcgSummarySample");
|
||||||
|
|
||||||
|
ecgSummary.setJavaDoc("Contains Huawei Ecg Summary samples (one per measurement)");
|
||||||
|
|
||||||
|
ecgSummary.addLongProperty("ecgId").primaryKey().autoincrement();
|
||||||
|
|
||||||
|
Property deviceId = ecgSummary.addLongProperty("deviceId").notNull().getProperty();
|
||||||
|
ecgSummary.addToOne(device, deviceId);
|
||||||
|
Property userId = ecgSummary.addLongProperty("userId").notNull().getProperty();
|
||||||
|
ecgSummary.addToOne(user, userId);
|
||||||
|
|
||||||
|
ecgSummary.addLongProperty("startTimestamp").notNull().index();
|
||||||
|
ecgSummary.addLongProperty("endTimestamp").notNull().index();
|
||||||
|
ecgSummary.addStringProperty("appVersion").notNull();
|
||||||
|
ecgSummary.addIntProperty("averageHeartRate").notNull();
|
||||||
|
ecgSummary.addLongProperty("arrhythmiaType").notNull().index();
|
||||||
|
ecgSummary.addLongProperty("userSymptoms").notNull();
|
||||||
|
|
||||||
|
return ecgSummary;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Entity addHuaweiEcgDataSample(Schema schema, Entity summaryEntity) {
|
||||||
|
Entity ecgDataSample = addEntity(schema, "HuaweiEcgDataSample");
|
||||||
|
|
||||||
|
ecgDataSample.setJavaDoc("Contains Huawei Ecg Data samples (multiple per summary)");
|
||||||
|
|
||||||
|
Property id = ecgDataSample.addLongProperty("ecgId").primaryKey().notNull().getProperty();
|
||||||
|
ecgDataSample.addToOne(summaryEntity, id);
|
||||||
|
|
||||||
|
ecgDataSample.addIntProperty("timeDelta").notNull().primaryKey();
|
||||||
|
ecgDataSample.addFloatProperty("value").notNull();
|
||||||
|
|
||||||
|
return ecgDataSample;
|
||||||
|
}
|
||||||
|
|
||||||
private static Entity addUltrahumanActivitySample(Schema schema, Entity user, Entity device) {
|
private static Entity addUltrahumanActivitySample(Schema schema, Entity user, Entity device) {
|
||||||
Entity sample = addEntity(schema, "UltrahumanActivitySample");
|
Entity sample = addEntity(schema, "UltrahumanActivitySample");
|
||||||
|
|
||||||
|
|||||||
+12
-1
@@ -50,6 +50,9 @@ import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiActivitySampleDao;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiDictData;
|
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiDictData;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiDictDataDao;
|
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiDictDataDao;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiDictDataValuesDao;
|
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiDictDataValuesDao;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiEcgDataSampleDao;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiEcgSummarySample;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiEcgSummarySampleDao;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiEmotionsSampleDao;
|
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiEmotionsSampleDao;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiHrvValueSampleDao;
|
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiHrvValueSampleDao;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiSleepStageSampleDao;
|
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiSleepStageSampleDao;
|
||||||
@@ -187,8 +190,16 @@ public class HuaweiCoordinator {
|
|||||||
HuaweiDictDataValuesDao.Properties.DictId.eq(data.getDictId())
|
HuaweiDictDataValuesDao.Properties.DictId.eq(data.getDictId())
|
||||||
).buildDelete().executeDeleteWithoutDetachingEntities();
|
).buildDelete().executeDeleteWithoutDetachingEntities();
|
||||||
}
|
}
|
||||||
|
|
||||||
session.getHuaweiDictDataDao().queryBuilder().where(HuaweiDictDataDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
|
session.getHuaweiDictDataDao().queryBuilder().where(HuaweiDictDataDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
|
||||||
|
|
||||||
|
QueryBuilder<HuaweiEcgSummarySample> qb4 = session.getHuaweiEcgSummarySampleDao().queryBuilder();
|
||||||
|
List<HuaweiEcgSummarySample> ecgSummary = qb4.where(HuaweiEcgSummarySampleDao.Properties.DeviceId.eq(deviceId)).build().list();
|
||||||
|
for (HuaweiEcgSummarySample sample : ecgSummary) {
|
||||||
|
session.getHuaweiEcgDataSampleDao().queryBuilder().where(
|
||||||
|
HuaweiEcgDataSampleDao.Properties.EcgId.eq(sample.getEcgId())
|
||||||
|
).buildDelete().executeDeleteWithoutDetachingEntities();
|
||||||
|
}
|
||||||
|
session.getHuaweiEcgSummarySampleDao().queryBuilder().where(HuaweiEcgSummarySampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
|
||||||
}
|
}
|
||||||
|
|
||||||
private SharedPreferences getCapabilitiesSharedPreferences() {
|
private SharedPreferences getCapabilitiesSharedPreferences() {
|
||||||
|
|||||||
+265
@@ -0,0 +1,265 @@
|
|||||||
|
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.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class HuaweiEcgFileParser {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(HuaweiEcgFileParser.class);
|
||||||
|
|
||||||
|
public static class EcgParseException extends Exception {
|
||||||
|
public EcgParseException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class EcgFileData {
|
||||||
|
private final List<EcgData> ecgDataList = new ArrayList<>();
|
||||||
|
private int unkn1;
|
||||||
|
private long unkn2;
|
||||||
|
private byte[] extraData;
|
||||||
|
private int packageNameLength;
|
||||||
|
private String packageName;
|
||||||
|
private int version;
|
||||||
|
|
||||||
|
|
||||||
|
public void setUnkn1(int unkn1) {
|
||||||
|
this.unkn1 = unkn1;
|
||||||
|
}
|
||||||
|
public void setUnkn2(long unkn2) {
|
||||||
|
this.unkn2 = unkn2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getVersion() {
|
||||||
|
return this.version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVersion(int version) {
|
||||||
|
this.version = version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExtraData(byte[] extraData) {
|
||||||
|
this.extraData = extraData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<EcgData> getEcgDataList() {
|
||||||
|
return this.ecgDataList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPackageNameLength() {
|
||||||
|
return this.packageNameLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPackageNameLength(int packageNameLength) {
|
||||||
|
this.packageNameLength = packageNameLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPackageName() {
|
||||||
|
return this.packageName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPackageName(String packageName) {
|
||||||
|
this.packageName = packageName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "EcgFile{" + "ecgDataList=" + ecgDataList +
|
||||||
|
", unkn1=" + unkn1 +
|
||||||
|
", unkn2=" + unkn2 +
|
||||||
|
", extraData='" + Arrays.toString(extraData) + '\'' +
|
||||||
|
", packageNameLength=" + packageNameLength +
|
||||||
|
", packageName='" + packageName + '\'' +
|
||||||
|
", version=" + version +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class EcgData {
|
||||||
|
private long startTime;
|
||||||
|
private long endTime;
|
||||||
|
private int ecgDataLength;
|
||||||
|
private String appVersion;
|
||||||
|
private int averageHeartRate;
|
||||||
|
private long arrhythmiaType;
|
||||||
|
private long userSymptoms;
|
||||||
|
private List<Float> ecgData;
|
||||||
|
|
||||||
|
public long getStartTime() {
|
||||||
|
return this.startTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStartTime(long startTime) {
|
||||||
|
this.startTime = startTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getEndTime() {
|
||||||
|
return this.endTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEndTime(long endTime) {
|
||||||
|
this.endTime = endTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getEcgDataLength() {
|
||||||
|
return this.ecgDataLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEcgDataLength(int ecgDataLength) {
|
||||||
|
this.ecgDataLength = ecgDataLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getArrhythmiaType() {
|
||||||
|
return this.arrhythmiaType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setArrhythmiaType(long arrhythmiaType) {
|
||||||
|
this.arrhythmiaType = arrhythmiaType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAverageHeartRate() {
|
||||||
|
return this.averageHeartRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAverageHeartRate(int averageHeartRate) {
|
||||||
|
this.averageHeartRate = averageHeartRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getUserSymptoms() {
|
||||||
|
return this.userSymptoms;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserSymptoms(long userSymptoms) {
|
||||||
|
this.userSymptoms = userSymptoms;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Float> getEcgData() {
|
||||||
|
return this.ecgData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEcgData(List<Float> ecgData) {
|
||||||
|
this.ecgData = ecgData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAppVersion() {
|
||||||
|
return this.appVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAppVersion(String appVersion) {
|
||||||
|
this.appVersion = appVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "EcgData{" + "startTime=" + startTime +
|
||||||
|
", endTime=" + endTime +
|
||||||
|
", averageHeartRate=" + averageHeartRate +
|
||||||
|
", arrhythmiaType=" + arrhythmiaType +
|
||||||
|
", ecgAppVersion='" + appVersion + '\'' +
|
||||||
|
", ecgDataLength=" + ecgDataLength +
|
||||||
|
", ecgData=" + ecgData +
|
||||||
|
", userSymptoms=" + userSymptoms +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private static long checkTime(long tm) throws EcgParseException {
|
||||||
|
long time = tm * 1000;
|
||||||
|
if (time < 0 || time > System.currentTimeMillis()) {
|
||||||
|
LOG.error("wrong data.");
|
||||||
|
throw new EcgParseException("wrong time: " + tm);
|
||||||
|
}
|
||||||
|
return time;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int checkDataLength(long len) {
|
||||||
|
if (len <= 0 || len > 2400000L) {
|
||||||
|
LOG.error("length is invalid: {}", len);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return (int) len;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void parseEcgData(ByteBuffer buffer, EcgFileData ecgFileData) throws EcgParseException {
|
||||||
|
while (buffer.remaining() >= 56) {
|
||||||
|
EcgData ecgData = new EcgData();
|
||||||
|
long startTime = checkTime(buffer.getInt() & 0xFFFFFFFFL);
|
||||||
|
long endTime = checkTime(buffer.getInt() & 0xFFFFFFFFL);
|
||||||
|
if (startTime > endTime) {
|
||||||
|
throw new EcgParseException("wrong time. startTime: " + startTime + "endTime: " + endTime);
|
||||||
|
}
|
||||||
|
ecgData.setStartTime(startTime);
|
||||||
|
ecgData.setEndTime(endTime);
|
||||||
|
ecgData.setEcgDataLength(checkDataLength(buffer.getInt() & 0xFFFFFFFFL));
|
||||||
|
byte[] appVersionBytes = new byte[32];
|
||||||
|
buffer.get(appVersionBytes);
|
||||||
|
ecgData.setAppVersion(new String(appVersionBytes, StandardCharsets.UTF_8).trim());
|
||||||
|
ecgData.setArrhythmiaType(buffer.getInt() & 0xFFFFFFFFL);
|
||||||
|
ecgData.setAverageHeartRate(buffer.getShort() & 0xFFFF);
|
||||||
|
buffer.getShort(); // unknown, always 0, can be a part of symptom or heart rate
|
||||||
|
ecgData.setUserSymptoms(buffer.getInt() & 0xFFFFFFFFL);
|
||||||
|
|
||||||
|
if (buffer.remaining() >= ecgData.getEcgDataLength()) {
|
||||||
|
ArrayList<Float> arrayList = new ArrayList<>();
|
||||||
|
int i = ecgData.getEcgDataLength();
|
||||||
|
while (i > 0) {
|
||||||
|
float valueOf = buffer.getFloat(); //Float.intBitsToFloat((int) (buffer.getInt() & 0xFFFFFFFFL));
|
||||||
|
if (Float.isNaN(valueOf)) {
|
||||||
|
System.out.println("unitStringFloat isNaN");
|
||||||
|
valueOf = 0.0f;
|
||||||
|
}
|
||||||
|
arrayList.add(valueOf);
|
||||||
|
i -= 4;
|
||||||
|
}
|
||||||
|
ecgData.setEcgData(arrayList);
|
||||||
|
if (ecgFileData.getEcgDataList() != null) {
|
||||||
|
ecgFileData.getEcgDataList().add(ecgData);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new EcgParseException("ecgData is invalid");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static EcgFileData parseEcgFile(byte[] data) throws EcgParseException {
|
||||||
|
if (data.length < 32) {
|
||||||
|
throw new EcgParseException("data is invalid");
|
||||||
|
}
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(data);
|
||||||
|
buffer.order(ByteOrder.BIG_ENDIAN);
|
||||||
|
|
||||||
|
EcgFileData ecgFileData = new EcgFileData();
|
||||||
|
ecgFileData.setUnkn2(buffer.getInt() & 0xFFFFFFFFL);
|
||||||
|
ecgFileData.setUnkn1(buffer.getShort() & 0xFFFF);
|
||||||
|
ecgFileData.setVersion(buffer.getShort() & 0xFFFF);
|
||||||
|
ecgFileData.setPackageNameLength(buffer.get() & 0xFF);
|
||||||
|
byte[] extraData = new byte[23];
|
||||||
|
buffer.get(extraData);
|
||||||
|
ecgFileData.setExtraData(extraData);
|
||||||
|
|
||||||
|
if (ecgFileData.getPackageNameLength() > buffer.remaining()) {
|
||||||
|
throw new EcgParseException("not enough data for package name");
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] packageNameBytes = new byte[ecgFileData.getPackageNameLength()];
|
||||||
|
buffer.get(packageNameBytes);
|
||||||
|
ecgFileData.setPackageName(new String(packageNameBytes, StandardCharsets.UTF_8).trim());
|
||||||
|
|
||||||
|
if (ecgFileData.getVersion() == 0) {
|
||||||
|
parseEcgData(buffer, ecgFileData);
|
||||||
|
} else {
|
||||||
|
LOG.error("version is not supported");
|
||||||
|
}
|
||||||
|
return ecgFileData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
+129
-2
@@ -51,6 +51,7 @@ import java.util.Map;
|
|||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import de.greenrobot.dao.Property;
|
||||||
import de.greenrobot.dao.query.DeleteQuery;
|
import de.greenrobot.dao.query.DeleteQuery;
|
||||||
import de.greenrobot.dao.query.QueryBuilder;
|
import de.greenrobot.dao.query.QueryBuilder;
|
||||||
import kotlin.Triple;
|
import kotlin.Triple;
|
||||||
@@ -72,6 +73,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCoordinatorSupp
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCoordinatorSupplier.HuaweiDeviceType;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCoordinatorSupplier.HuaweiDeviceType;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCrypto;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCrypto;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiDictTypes;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiDictTypes;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiEcgFileParser;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiEmotionsSampleProvider;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiEmotionsSampleProvider;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiGpsParser;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiGpsParser;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiHrvValueSampleProvider;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiHrvValueSampleProvider;
|
||||||
@@ -97,11 +99,17 @@ import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
|
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummaryDao;
|
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummaryDao;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiActivitySample;
|
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiActivitySample;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiActivitySampleDao;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiEcgDataSample;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiEcgDataSampleDao;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiEcgSummarySample;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiEcgSummarySampleDao;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiEmotionsSample;
|
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiEmotionsSample;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiHrvValueSample;
|
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiHrvValueSample;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiSleepApneaSample;
|
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiSleepApneaSample;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiSleepStageSample;
|
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiSleepStageSample;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiSleepStatsSample;
|
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiSleepStatsSample;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiSleepStatsSampleDao;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiStressSample;
|
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiStressSample;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiTemperatureSample;
|
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiTemperatureSample;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutDataSample;
|
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutDataSample;
|
||||||
@@ -1491,6 +1499,7 @@ public class HuaweiSupportProvider {
|
|||||||
|
|
||||||
int sleepStart = 0;
|
int sleepStart = 0;
|
||||||
int stepStart = 0;
|
int stepStart = 0;
|
||||||
|
int ecgStart = 0;
|
||||||
final int end = (int) (System.currentTimeMillis() / 1000);
|
final int end = (int) (System.currentTimeMillis() / 1000);
|
||||||
|
|
||||||
SharedPreferences sharedPreferences = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress());
|
SharedPreferences sharedPreferences = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress());
|
||||||
@@ -1498,6 +1507,7 @@ public class HuaweiSupportProvider {
|
|||||||
if (prefLastSyncTime != 0) {
|
if (prefLastSyncTime != 0) {
|
||||||
sleepStart = (int) (prefLastSyncTime / 1000);
|
sleepStart = (int) (prefLastSyncTime / 1000);
|
||||||
stepStart = (int) (prefLastSyncTime / 1000);
|
stepStart = (int) (prefLastSyncTime / 1000);
|
||||||
|
ecgStart = (int) (prefLastSyncTime / 1000);
|
||||||
|
|
||||||
// Reset for next calls
|
// Reset for next calls
|
||||||
sharedPreferences.edit().putLong("lastSyncTimeMillis", 0).apply();
|
sharedPreferences.edit().putLong("lastSyncTimeMillis", 0).apply();
|
||||||
@@ -1510,6 +1520,24 @@ public class HuaweiSupportProvider {
|
|||||||
HuaweiSleepStatsSampleProvider sleepStatsSampleProvider = new HuaweiSleepStatsSampleProvider(gbDevice, db.getDaoSession());
|
HuaweiSleepStatsSampleProvider sleepStatsSampleProvider = new HuaweiSleepStatsSampleProvider(gbDevice, db.getDaoSession());
|
||||||
int sleepStart2 = (int) (sleepStatsSampleProvider.getLastSleepFetchTimestamp() / 1000L);
|
int sleepStart2 = (int) (sleepStatsSampleProvider.getLastSleepFetchTimestamp() / 1000L);
|
||||||
sleepStart = Math.max(sleepStart, sleepStart2);
|
sleepStart = Math.max(sleepStart, sleepStart2);
|
||||||
|
|
||||||
|
QueryBuilder<HuaweiEcgSummarySample> qb = db.getDaoSession().getHuaweiEcgSummarySampleDao().queryBuilder();
|
||||||
|
Device dbDevice = DBHelper.findDevice(gbDevice, db.getDaoSession());
|
||||||
|
if (dbDevice != null) {
|
||||||
|
final Property deviceProperty = HuaweiEcgSummarySampleDao.Properties.DeviceId;
|
||||||
|
final Property timestampProperty = HuaweiEcgSummarySampleDao.Properties.EndTimestamp;
|
||||||
|
|
||||||
|
qb.where(deviceProperty.eq(dbDevice.getId()))
|
||||||
|
.orderDesc(timestampProperty)
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
List<HuaweiEcgSummarySample> samples = qb.build().list();
|
||||||
|
if (!samples.isEmpty()) {
|
||||||
|
HuaweiEcgSummarySample sample = samples.get(0);
|
||||||
|
ecgStart = (int) (sample.getEndTimestamp() / 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.warn("Exception for getting start times, using 01/01/2000 - 00:00:00.");
|
LOG.warn("Exception for getting start times, using 01/01/2000 - 00:00:00.");
|
||||||
}
|
}
|
||||||
@@ -1519,6 +1547,10 @@ public class HuaweiSupportProvider {
|
|||||||
sleepStart = 946684800;
|
sleepStart = 946684800;
|
||||||
if (stepStart == 0)
|
if (stepStart == 0)
|
||||||
stepStart = 946684800;
|
stepStart = 946684800;
|
||||||
|
|
||||||
|
// To avoid of downloading of very big file and OOM error set ECG start time to step start time.
|
||||||
|
if(ecgStart == 0)
|
||||||
|
ecgStart = stepStart;
|
||||||
}
|
}
|
||||||
final GetSleepDataCountRequest getSleepDataCountRequest;
|
final GetSleepDataCountRequest getSleepDataCountRequest;
|
||||||
if (isBLE()) {
|
if (isBLE()) {
|
||||||
@@ -1536,10 +1568,11 @@ public class HuaweiSupportProvider {
|
|||||||
final GetFitnessTotalsRequest getFitnessTotalsRequest = new GetFitnessTotalsRequest(this);
|
final GetFitnessTotalsRequest getFitnessTotalsRequest = new GetFitnessTotalsRequest(this);
|
||||||
|
|
||||||
final int start = sleepStart;
|
final int start = sleepStart;
|
||||||
|
final int ecgSyncStart = ecgStart;
|
||||||
getFitnessTotalsRequest.setFinalizeReq(new RequestCallback() {
|
getFitnessTotalsRequest.setFinalizeReq(new RequestCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void call() {
|
public void call() {
|
||||||
if (!(downloadTruSleepData(start, end) && downloadStressData(start, end)))
|
if (!(downloadTruSleepData(start, end) && downloadStressData(start, end) && downloadEcgData(ecgSyncStart, end)))
|
||||||
syncState.stopActivitySync();
|
syncState.stopActivitySync();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2121,6 +2154,57 @@ public class HuaweiSupportProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addEcgData(HuaweiEcgFileParser.EcgData data) {
|
||||||
|
try (DBHandler db = GBApplication.acquireDB()) {
|
||||||
|
Long userId = DBHelper.getUser(db.getDaoSession()).getId();
|
||||||
|
Long deviceId = DBHelper.getDevice(gbDevice, db.getDaoSession()).getId();
|
||||||
|
|
||||||
|
// Avoid duplicates
|
||||||
|
QueryBuilder<HuaweiEcgSummarySample> qb = db.getDaoSession().getHuaweiEcgSummarySampleDao().queryBuilder().where(
|
||||||
|
HuaweiEcgSummarySampleDao.Properties.UserId.eq(userId),
|
||||||
|
HuaweiEcgSummarySampleDao.Properties.DeviceId.eq(deviceId),
|
||||||
|
HuaweiEcgSummarySampleDao.Properties.StartTimestamp.eq(data.getStartTime())
|
||||||
|
);
|
||||||
|
List<HuaweiEcgSummarySample> results = qb.build().list();
|
||||||
|
Long ecgId = null;
|
||||||
|
if (!results.isEmpty())
|
||||||
|
ecgId = results.get(0).getEcgId();
|
||||||
|
|
||||||
|
HuaweiEcgSummarySample summarySample = new HuaweiEcgSummarySample(
|
||||||
|
ecgId,
|
||||||
|
deviceId,
|
||||||
|
userId,
|
||||||
|
data.getStartTime(),
|
||||||
|
data.getEndTime(),
|
||||||
|
data.getAppVersion(),
|
||||||
|
data.getAverageHeartRate(),
|
||||||
|
data.getArrhythmiaType(),
|
||||||
|
data.getUserSymptoms()
|
||||||
|
);
|
||||||
|
|
||||||
|
db.getDaoSession().getHuaweiEcgSummarySampleDao().insertOrReplace(summarySample);
|
||||||
|
|
||||||
|
// We should completely replace values. Delete all and insert again.
|
||||||
|
final DeleteQuery<HuaweiEcgDataSample> tableDeleteQuery = db.getDaoSession().getHuaweiEcgDataSampleDao().queryBuilder()
|
||||||
|
.where(HuaweiEcgDataSampleDao.Properties.EcgId.eq(summarySample.getEcgId()))
|
||||||
|
.buildDelete();
|
||||||
|
tableDeleteQuery.executeDeleteWithoutDetachingEntities();
|
||||||
|
|
||||||
|
int sampleRate = (int) (data.getEcgData().size() / ((data.getEndTime() - data.getStartTime())/ 1000));
|
||||||
|
int delta = 0;
|
||||||
|
List<HuaweiEcgDataSample> res = new ArrayList<>();
|
||||||
|
for (Float d: data.getEcgData()) {
|
||||||
|
HuaweiEcgDataSample dataSample = new HuaweiEcgDataSample(summarySample.getEcgId(), delta, d);
|
||||||
|
res.add(dataSample);
|
||||||
|
delta += sampleRate;
|
||||||
|
}
|
||||||
|
db.getDaoSession().getHuaweiEcgDataSampleDao().insertInTx(res);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.error("Failed to add ECG data to database", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void addTotalFitnessData(int steps, int calories, int distance) {
|
public void addTotalFitnessData(int steps, int calories, int distance) {
|
||||||
LOG.debug("FITNESS total steps: {}", steps);
|
LOG.debug("FITNESS total steps: {}", steps);
|
||||||
LOG.debug("FITNESS total calories: {}", calories); // TODO: May actually be kilocalories
|
LOG.debug("FITNESS total calories: {}", calories); // TODO: May actually be kilocalories
|
||||||
@@ -2217,7 +2301,7 @@ public class HuaweiSupportProvider {
|
|||||||
|
|
||||||
// We should completely replace values. Delete all and insert again.
|
// 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))
|
.where(HuaweiWorkoutSummaryAdditionalValuesSampleDao.Properties.WorkoutId.eq(summarySample.getWorkoutId()))
|
||||||
.buildDelete();
|
.buildDelete();
|
||||||
tableDeleteQuery.executeDeleteWithoutDetachingEntities();
|
tableDeleteQuery.executeDeleteWithoutDetachingEntities();
|
||||||
|
|
||||||
@@ -3321,6 +3405,49 @@ public class HuaweiSupportProvider {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean downloadEcgData(int start, int end) {
|
||||||
|
if (!getHuaweiCoordinator().supportsECG())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
syncState.setEcgSync(true);
|
||||||
|
|
||||||
|
huaweiFileDownloadManager.addToQueue(HuaweiFileDownloadManager.FileRequest.ecgAnalysisFileRequest(
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
new HuaweiFileDownloadManager.FileDownloadCallback() {
|
||||||
|
@Override
|
||||||
|
public void downloadComplete(HuaweiFileDownloadManager.FileRequest fileRequest) {
|
||||||
|
if (fileRequest.getData().length != 0) {
|
||||||
|
LOG.debug("Parsing ECG file");
|
||||||
|
|
||||||
|
HuaweiEcgFileParser.EcgFileData results = null;
|
||||||
|
try {
|
||||||
|
results = HuaweiEcgFileParser.parseEcgFile(fileRequest.getData());
|
||||||
|
} catch (HuaweiEcgFileParser.EcgParseException e) {
|
||||||
|
LOG.error("Error parse ECG file", e);
|
||||||
|
}
|
||||||
|
LOG.info("ECG result: {}", results);
|
||||||
|
if (results != null && !results.getEcgDataList().isEmpty()) {
|
||||||
|
for(HuaweiEcgFileParser.EcgData dt: results.getEcgDataList()) {
|
||||||
|
addEcgData(dt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOG.debug("ECG file is empty");
|
||||||
|
}
|
||||||
|
syncState.setEcgSync(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void downloadException(HuaweiFileDownloadManager.HuaweiFileDownloadException e) {
|
||||||
|
super.downloadException(e);
|
||||||
|
syncState.setEcgSync(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
), true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public void nextWorkoutSync(List<Workout.WorkoutCount.Response.WorkoutNumbers> remainder, RequestCallback finalizeReq) {
|
public void nextWorkoutSync(List<Workout.WorkoutCount.Response.WorkoutNumbers> remainder, RequestCallback finalizeReq) {
|
||||||
if (!remainder.isEmpty()) {
|
if (!remainder.isEmpty()) {
|
||||||
GetWorkoutTotalsRequest nextRequest = new GetWorkoutTotalsRequest(
|
GetWorkoutTotalsRequest nextRequest = new GetWorkoutTotalsRequest(
|
||||||
|
|||||||
+16
-2
@@ -18,6 +18,7 @@ class HuaweiSyncState {
|
|||||||
private boolean activitySync = false;
|
private boolean activitySync = false;
|
||||||
private boolean p2pSync = false;
|
private boolean p2pSync = false;
|
||||||
private boolean stressSync = false;
|
private boolean stressSync = false;
|
||||||
|
private boolean ecgSync = false;
|
||||||
private boolean workoutSync = false;
|
private boolean workoutSync = false;
|
||||||
private int workoutGpsDownload = 0;
|
private int workoutGpsDownload = 0;
|
||||||
|
|
||||||
@@ -26,7 +27,7 @@ class HuaweiSyncState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean isSyncActive() {
|
private boolean isSyncActive() {
|
||||||
return activitySync || p2pSync || stressSync || workoutSync || workoutGpsDownload != 0;
|
return activitySync || p2pSync || stressSync || ecgSync || workoutSync || workoutGpsDownload != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String activeSync() {
|
private String activeSync() {
|
||||||
@@ -37,6 +38,8 @@ class HuaweiSyncState {
|
|||||||
retv.append("p2pSync,");
|
retv.append("p2pSync,");
|
||||||
if (stressSync)
|
if (stressSync)
|
||||||
retv.append("stressSync,");
|
retv.append("stressSync,");
|
||||||
|
if (ecgSync)
|
||||||
|
retv.append("ecgSync,");
|
||||||
if (workoutSync)
|
if (workoutSync)
|
||||||
retv.append("workoutSync,");
|
retv.append("workoutSync,");
|
||||||
if (workoutGpsDownload != 0) {
|
if (workoutGpsDownload != 0) {
|
||||||
@@ -95,7 +98,7 @@ class HuaweiSyncState {
|
|||||||
// We cannot do the syncActive check for the P2P sync as it runs in parallel with the activity sync
|
// We cannot do the syncActive check for the P2P sync as it runs in parallel with the activity sync
|
||||||
LOG.debug("Set p2p sync state to {}", state);
|
LOG.debug("Set p2p sync state to {}", state);
|
||||||
this.p2pSync = state;
|
this.p2pSync = state;
|
||||||
if (!state && !this.activitySync && !this.stressSync) {
|
if (!state && !this.activitySync && !this.stressSync && !this.ecgSync) {
|
||||||
this.syncQueue.remove((Integer) RecordedDataTypes.TYPE_ACTIVITY);
|
this.syncQueue.remove((Integer) RecordedDataTypes.TYPE_ACTIVITY);
|
||||||
supportProvider.fetchRecodedDataFromQueue();
|
supportProvider.fetchRecodedDataFromQueue();
|
||||||
}
|
}
|
||||||
@@ -106,6 +109,17 @@ class HuaweiSyncState {
|
|||||||
// We cannot do the syncActive check for the stress sync as it runs in parallel with the activity sync (sleep file specifically)
|
// We cannot do the syncActive check for the stress sync as it runs in parallel with the activity sync (sleep file specifically)
|
||||||
LOG.debug("Set stress sync state to {}", state);
|
LOG.debug("Set stress sync state to {}", state);
|
||||||
this.stressSync = state;
|
this.stressSync = state;
|
||||||
|
if (!state && !this.activitySync && !this.p2pSync && !this.ecgSync) {
|
||||||
|
this.syncQueue.remove((Integer) RecordedDataTypes.TYPE_ACTIVITY);
|
||||||
|
supportProvider.fetchRecodedDataFromQueue();
|
||||||
|
}
|
||||||
|
updateState();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEcgSync(boolean state) {
|
||||||
|
// We cannot do the syncActive check for the stress sync as it runs in parallel with the activity sync (sleep file specifically)
|
||||||
|
LOG.debug("Set ECG sync state to {}", state);
|
||||||
|
this.ecgSync = state;
|
||||||
if (!state && !this.activitySync && !this.p2pSync) {
|
if (!state && !this.activitySync && !this.p2pSync) {
|
||||||
this.syncQueue.remove((Integer) RecordedDataTypes.TYPE_ACTIVITY);
|
this.syncQueue.remove((Integer) RecordedDataTypes.TYPE_ACTIVITY);
|
||||||
supportProvider.fetchRecodedDataFromQueue();
|
supportProvider.fetchRecodedDataFromQueue();
|
||||||
|
|||||||
Reference in New Issue
Block a user