From 7b8f02ec297eac92381cddafffe164e4a0b56de9 Mon Sep 17 00:00:00 2001 From: Me7c7 Date: Tue, 26 Nov 2024 17:37:19 +0200 Subject: [PATCH] Huawei: Collect and process more workout data: HRZones, RunPaceZones --- .../gadgetbridge/daogen/GBDaoGenerator.java | 22 ++++- .../schema/GadgetbridgeUpdate_89.java | 93 +++++++++++++++++++ .../devices/huawei/HeartRateZonesConfig.java | 24 ++++- .../devices/huawei/HuaweiRunPaceConfig.java | 62 +++++++++++++ .../devices/huawei/HuaweiSportHRZones.java | 18 ++-- .../devices/huawei/packets/FitnessData.java | 21 ++--- .../devices/huawei/packets/Workout.java | 51 +++++++++- .../devices/huawei/HuaweiSupportProvider.java | 19 +++- .../devices/huawei/HuaweiWorkoutGbParser.java | 26 ++---- .../devices/huawei/HuaweiWorkoutUtils.java | 48 ++++++++++ .../requests/SendRunPaceConfigRequest.java | 11 +-- 11 files changed, 344 insertions(+), 51 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_89.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiRunPaceConfig.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiWorkoutUtils.java diff --git a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java index 86d111b78..f5c81147b 100644 --- a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java +++ b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java @@ -54,7 +54,7 @@ public class GBDaoGenerator { public static void main(String[] args) throws Exception { - final Schema schema = new Schema(88, MAIN_PACKAGE + ".entities"); + final Schema schema = new Schema(89, MAIN_PACKAGE + ".entities"); Entity userAttributes = addUserAttributes(schema); Entity user = addUserInfo(schema, userAttributes); @@ -1411,6 +1411,26 @@ public class GBDaoGenerator { workoutSummary.addByteProperty("swimType").notNull(); + workoutSummary.addIntProperty("maxMET").notNull(); + + workoutSummary.addByteProperty("hrZoneType").notNull(); + + workoutSummary.addShortProperty("runPaceZone1Min").notNull(); + workoutSummary.addShortProperty("runPaceZone2Min").notNull(); + workoutSummary.addShortProperty("runPaceZone3Min").notNull(); + workoutSummary.addShortProperty("runPaceZone4Min").notNull(); + workoutSummary.addShortProperty("runPaceZone5Min").notNull(); + workoutSummary.addShortProperty("runPaceZone5Max").notNull(); + + workoutSummary.addShortProperty("runPaceZone1Time").notNull(); + workoutSummary.addShortProperty("runPaceZone2Time").notNull(); + workoutSummary.addShortProperty("runPaceZone3Time").notNull(); + workoutSummary.addShortProperty("runPaceZone4Time").notNull(); + workoutSummary.addShortProperty("runPaceZone5Time").notNull(); + + workoutSummary.addByteProperty("algType").notNull(); + workoutSummary.addIntProperty("trainingPoints").notNull(); + return workoutSummary; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_89.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_89.java new file mode 100644 index 000000000..384e0a36a --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_89.java @@ -0,0 +1,93 @@ +package nodomain.freeyourgadget.gadgetbridge.database.schema; + +import android.database.sqlite.SQLiteDatabase; + +import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; +import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript; +import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSummarySampleDao; + +public class GadgetbridgeUpdate_89 implements DBUpdateScript { + @Override + public void upgradeSchema(final SQLiteDatabase db) { + + if (!DBHelper.existsColumn(HuaweiWorkoutSummarySampleDao.TABLENAME, HuaweiWorkoutSummarySampleDao.Properties.MaxMET.columnName, db)) { + final String statement = "ALTER TABLE " + HuaweiWorkoutSummarySampleDao.TABLENAME + " ADD COLUMN \"" + + HuaweiWorkoutSummarySampleDao.Properties.MaxMET.columnName + "\" INTEGER NOT NULL DEFAULT 0;"; + db.execSQL(statement); + } + if (!DBHelper.existsColumn(HuaweiWorkoutSummarySampleDao.TABLENAME, HuaweiWorkoutSummarySampleDao.Properties.HrZoneType.columnName, db)) { + final String statement = "ALTER TABLE " + HuaweiWorkoutSummarySampleDao.TABLENAME + " ADD COLUMN \"" + + HuaweiWorkoutSummarySampleDao.Properties.HrZoneType.columnName + "\" INTEGER NOT NULL DEFAULT -1;"; + db.execSQL(statement); + } + if (!DBHelper.existsColumn(HuaweiWorkoutSummarySampleDao.TABLENAME, HuaweiWorkoutSummarySampleDao.Properties.RunPaceZone1Min.columnName, db)) { + final String statement = "ALTER TABLE " + HuaweiWorkoutSummarySampleDao.TABLENAME + " ADD COLUMN \"" + + HuaweiWorkoutSummarySampleDao.Properties.RunPaceZone1Min.columnName + "\" INTEGER NOT NULL DEFAULT -1;"; + db.execSQL(statement); + } + if (!DBHelper.existsColumn(HuaweiWorkoutSummarySampleDao.TABLENAME, HuaweiWorkoutSummarySampleDao.Properties.RunPaceZone2Min.columnName, db)) { + final String statement = "ALTER TABLE " + HuaweiWorkoutSummarySampleDao.TABLENAME + " ADD COLUMN \"" + + HuaweiWorkoutSummarySampleDao.Properties.RunPaceZone2Min.columnName + "\" INTEGER NOT NULL DEFAULT -1;"; + db.execSQL(statement); + } + if (!DBHelper.existsColumn(HuaweiWorkoutSummarySampleDao.TABLENAME, HuaweiWorkoutSummarySampleDao.Properties.RunPaceZone3Min.columnName, db)) { + final String statement = "ALTER TABLE " + HuaweiWorkoutSummarySampleDao.TABLENAME + " ADD COLUMN \"" + + HuaweiWorkoutSummarySampleDao.Properties.RunPaceZone3Min.columnName + "\" INTEGER NOT NULL DEFAULT -1;"; + db.execSQL(statement); + } + if (!DBHelper.existsColumn(HuaweiWorkoutSummarySampleDao.TABLENAME, HuaweiWorkoutSummarySampleDao.Properties.RunPaceZone4Min.columnName, db)) { + final String statement = "ALTER TABLE " + HuaweiWorkoutSummarySampleDao.TABLENAME + " ADD COLUMN \"" + + HuaweiWorkoutSummarySampleDao.Properties.RunPaceZone4Min.columnName + "\" INTEGER NOT NULL DEFAULT -1;"; + db.execSQL(statement); + } + if (!DBHelper.existsColumn(HuaweiWorkoutSummarySampleDao.TABLENAME, HuaweiWorkoutSummarySampleDao.Properties.RunPaceZone5Min.columnName, db)) { + final String statement = "ALTER TABLE " + HuaweiWorkoutSummarySampleDao.TABLENAME + " ADD COLUMN \"" + + HuaweiWorkoutSummarySampleDao.Properties.RunPaceZone5Min.columnName + "\" INTEGER NOT NULL DEFAULT -1;"; + db.execSQL(statement); + } + if (!DBHelper.existsColumn(HuaweiWorkoutSummarySampleDao.TABLENAME, HuaweiWorkoutSummarySampleDao.Properties.RunPaceZone5Max.columnName, db)) { + final String statement = "ALTER TABLE " + HuaweiWorkoutSummarySampleDao.TABLENAME + " ADD COLUMN \"" + + HuaweiWorkoutSummarySampleDao.Properties.RunPaceZone5Max.columnName + "\" INTEGER NOT NULL DEFAULT -1;"; + db.execSQL(statement); + } + if (!DBHelper.existsColumn(HuaweiWorkoutSummarySampleDao.TABLENAME, HuaweiWorkoutSummarySampleDao.Properties.RunPaceZone1Time.columnName, db)) { + final String statement = "ALTER TABLE " + HuaweiWorkoutSummarySampleDao.TABLENAME + " ADD COLUMN \"" + + HuaweiWorkoutSummarySampleDao.Properties.RunPaceZone1Time.columnName + "\" INTEGER NOT NULL DEFAULT -1;"; + db.execSQL(statement); + } + if (!DBHelper.existsColumn(HuaweiWorkoutSummarySampleDao.TABLENAME, HuaweiWorkoutSummarySampleDao.Properties.RunPaceZone2Time.columnName, db)) { + final String statement = "ALTER TABLE " + HuaweiWorkoutSummarySampleDao.TABLENAME + " ADD COLUMN \"" + + HuaweiWorkoutSummarySampleDao.Properties.RunPaceZone2Time.columnName + "\" INTEGER NOT NULL DEFAULT -1;"; + db.execSQL(statement); + } + if (!DBHelper.existsColumn(HuaweiWorkoutSummarySampleDao.TABLENAME, HuaweiWorkoutSummarySampleDao.Properties.RunPaceZone3Time.columnName, db)) { + final String statement = "ALTER TABLE " + HuaweiWorkoutSummarySampleDao.TABLENAME + " ADD COLUMN \"" + + HuaweiWorkoutSummarySampleDao.Properties.RunPaceZone3Time.columnName + "\" INTEGER NOT NULL DEFAULT -1;"; + db.execSQL(statement); + } + if (!DBHelper.existsColumn(HuaweiWorkoutSummarySampleDao.TABLENAME, HuaweiWorkoutSummarySampleDao.Properties.RunPaceZone4Time.columnName, db)) { + final String statement = "ALTER TABLE " + HuaweiWorkoutSummarySampleDao.TABLENAME + " ADD COLUMN \"" + + HuaweiWorkoutSummarySampleDao.Properties.RunPaceZone4Time.columnName + "\" INTEGER NOT NULL DEFAULT -1;"; + db.execSQL(statement); + } + if (!DBHelper.existsColumn(HuaweiWorkoutSummarySampleDao.TABLENAME, HuaweiWorkoutSummarySampleDao.Properties.RunPaceZone5Time.columnName, db)) { + final String statement = "ALTER TABLE " + HuaweiWorkoutSummarySampleDao.TABLENAME + " ADD COLUMN \"" + + HuaweiWorkoutSummarySampleDao.Properties.RunPaceZone5Time.columnName + "\" INTEGER NOT NULL DEFAULT -1;"; + db.execSQL(statement); + } + if (!DBHelper.existsColumn(HuaweiWorkoutSummarySampleDao.TABLENAME, HuaweiWorkoutSummarySampleDao.Properties.AlgType.columnName, db)) { + final String statement = "ALTER TABLE " + HuaweiWorkoutSummarySampleDao.TABLENAME + " ADD COLUMN \"" + + HuaweiWorkoutSummarySampleDao.Properties.AlgType.columnName + "\" INTEGER NOT NULL DEFAULT 0;"; + db.execSQL(statement); + } + if (!DBHelper.existsColumn(HuaweiWorkoutSummarySampleDao.TABLENAME, HuaweiWorkoutSummarySampleDao.Properties.TrainingPoints.columnName, db)) { + final String statement = "ALTER TABLE " + HuaweiWorkoutSummarySampleDao.TABLENAME + " ADD COLUMN \"" + + HuaweiWorkoutSummarySampleDao.Properties.TrainingPoints.columnName + "\" INTEGER NOT NULL DEFAULT 0;"; + db.execSQL(statement); + } + } + + @Override + public void downgradeSchema(final SQLiteDatabase db) { + } +} \ No newline at end of file diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HeartRateZonesConfig.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HeartRateZonesConfig.java index 7558f266c..540c8938f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HeartRateZonesConfig.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HeartRateZonesConfig.java @@ -11,12 +11,16 @@ public class HeartRateZonesConfig { public static final int TYPE_SWIMMING = 3; public static final int TYPE_OTHER = 4; + public static final int CALCULATE_METHOD_MHR = 0; + public static final int CALCULATE_METHOD_HRR = 1; + public static final int CALCULATE_METHOD_LTHR = 3; + private static final int DEFAULT_REST_HEART_RATE = 60; public static final int MAXIMUM_HEART_RATE = 220; private final int configType; - private int calculateMethod = 0; // 0 - MHR, 1 - HRR, 3 - LTHR + private int calculateMethod = CALCULATE_METHOD_MHR; // 0 - MHR, 1 - HRR, 3 - LTHR private int maxHRThreshold; private int restHeartRate = DEFAULT_REST_HEART_RATE; @@ -262,4 +266,22 @@ public class HeartRateZonesConfig { return getZoneForHR(heartRate, LTHRAnaerobic, LTHRLactate, LTHRAdvancedAerobic, LTHRBasicAerobic, LTHRWarmUp); } + public int getZoneByMethod(int heartRate, int method) { + if(method == CALCULATE_METHOD_LTHR) { + return getLTHRZone(heartRate); + } else if(method == CALCULATE_METHOD_MHR) { + return getMHRZone(heartRate); + } + return getHHRZone(heartRate); + } + + public static boolean isCalculateMethodValidFroType(int type, int method) { + if(method == CALCULATE_METHOD_LTHR && type == TYPE_UPRIGHT) { + return true; + } + return (method == CALCULATE_METHOD_MHR) || (method == CALCULATE_METHOD_HRR); + + } + + } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiRunPaceConfig.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiRunPaceConfig.java new file mode 100644 index 000000000..a7402fdce --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiRunPaceConfig.java @@ -0,0 +1,62 @@ +package nodomain.freeyourgadget.gadgetbridge.devices.huawei; + +// TODO: make this configurable +// NOTE: algorithms used in this class are generic. So this data can be used with other devices. +// We can move this class to global scope. +public class HuaweiRunPaceConfig { + + private int zone5HIITRunMax = 300; + private int zone5HIITRunMin = 330; + private int zone4AnaerobicMin = 360; + private int zone3LactateThresholdMin = 390; + private int zone2MarathonMin = 420; + private int zone1JogMin = 450; + + public int getZone5HIITRunMax() { + return zone5HIITRunMax; + } + + public void setZone5HIITRunMax(int zone5HIITRunMax) { + this.zone5HIITRunMax = zone5HIITRunMax; + } + + public int getZone5HIITRunMin() { + return zone5HIITRunMin; + } + + public void setZone5HIITRunMin(int zone5HIITRunMin) { + this.zone5HIITRunMin = zone5HIITRunMin; + } + + public int getZone4AnaerobicMin() { + return zone4AnaerobicMin; + } + + public void setZone4AnaerobicMin(int zone4AnaerobicMin) { + this.zone4AnaerobicMin = zone4AnaerobicMin; + } + + public int getZone3LactateThresholdMin() { + return zone3LactateThresholdMin; + } + + public void setZone3LactateThresholdMin(int zone3LactateThresholdMin) { + this.zone3LactateThresholdMin = zone3LactateThresholdMin; + } + + public int getZone2MarathonMin() { + return zone2MarathonMin; + } + + public void setZone2MarathonMin(int zone2MarathonMin) { + this.zone2MarathonMin = zone2MarathonMin; + } + + public int getZone1JogMin() { + return zone1JogMin; + } + + public void setZone1JogMin(int zone1JogMin) { + this.zone1JogMin = zone1JogMin; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiSportHRZones.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiSportHRZones.java index af0a83f7d..b3208e48b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiSportHRZones.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiSportHRZones.java @@ -2,26 +2,26 @@ package nodomain.freeyourgadget.gadgetbridge.devices.huawei; public class HuaweiSportHRZones { private final HeartRateZonesConfig otherHRZonesConfig; - private final HeartRateZonesConfig SittingHRZonesConfig; - private final HeartRateZonesConfig UprightHRZonesConfig; - private final HeartRateZonesConfig SwimmingHRZonesConfig; + private final HeartRateZonesConfig sittingHRZonesConfig; + private final HeartRateZonesConfig uprightHRZonesConfig; + private final HeartRateZonesConfig swimmingHRZonesConfig; public HuaweiSportHRZones(int age) { - this.UprightHRZonesConfig = new HeartRateZonesConfig(HeartRateZonesConfig.TYPE_UPRIGHT, age); - this.SittingHRZonesConfig = new HeartRateZonesConfig(HeartRateZonesConfig.TYPE_SITTING, age); - this.SwimmingHRZonesConfig = new HeartRateZonesConfig(HeartRateZonesConfig.TYPE_SWIMMING, age); + this.uprightHRZonesConfig = new HeartRateZonesConfig(HeartRateZonesConfig.TYPE_UPRIGHT, age); + this.sittingHRZonesConfig = new HeartRateZonesConfig(HeartRateZonesConfig.TYPE_SITTING, age); + this.swimmingHRZonesConfig = new HeartRateZonesConfig(HeartRateZonesConfig.TYPE_SWIMMING, age); this.otherHRZonesConfig = new HeartRateZonesConfig(HeartRateZonesConfig.TYPE_OTHER, age); } public HeartRateZonesConfig getHRZonesConfigByType(int type) { if (type == HeartRateZonesConfig.TYPE_SITTING) { - return this.SittingHRZonesConfig; + return this.sittingHRZonesConfig; } else if (type == HeartRateZonesConfig.TYPE_SWIMMING) { - return this.SwimmingHRZonesConfig; + return this.swimmingHRZonesConfig; } else if (type == HeartRateZonesConfig.TYPE_OTHER) { return this.otherHRZonesConfig; } - return this.UprightHRZonesConfig; + return this.uprightHRZonesConfig; } public byte[] getHRZonesData() { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/FitnessData.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/FitnessData.java index e5b5741ac..e01db6895 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/FitnessData.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/FitnessData.java @@ -23,6 +23,7 @@ import java.util.List; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HeartRateZonesConfig; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiReportThreshold; +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiRunPaceConfig; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV; public class FitnessData { @@ -741,25 +742,19 @@ public class FitnessData { public static final byte id = 0x28; public static class Request extends HuaweiPacket { - public Request(ParamsProvider paramsProvider, - int easyPaceZoneMinValue, - int marathonPaceZoneMinValue, - int lactatePaceZoneMinValue, - int anaerobicPaceZoneMinValue, - int maxOxygenPaceZoneMinValue, - int maxOxygenPaceZoneMaxValue) { + public Request(ParamsProvider paramsProvider, final HuaweiRunPaceConfig runPaceConfig) { super(paramsProvider); this.serviceId = FitnessData.id; this.commandId = id; this.tlv = new HuaweiTLV() - .put(0x01, (short) easyPaceZoneMinValue) - .put(0x02, (short) marathonPaceZoneMinValue) - .put(0x03, (short) lactatePaceZoneMinValue) - .put(0x04, (short) anaerobicPaceZoneMinValue) - .put(0x05, (short) maxOxygenPaceZoneMinValue) - .put(0x06, (short) maxOxygenPaceZoneMaxValue); + .put(0x01, (short) runPaceConfig.getZone1JogMin()) + .put(0x02, (short) runPaceConfig.getZone2MarathonMin()) + .put(0x03, (short) runPaceConfig.getZone3LactateThresholdMin()) + .put(0x04, (short) runPaceConfig.getZone4AnaerobicMin()) + .put(0x05, (short) runPaceConfig.getZone5HIITRunMin()) + .put(0x06, (short) runPaceConfig.getZone5HIITRunMax()); this.complete = true; } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Workout.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Workout.java index 9ad41749d..19c604fc0 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Workout.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/Workout.java @@ -154,6 +154,26 @@ public class Workout { public byte swimType = -1; + public int maxMET = 0; + + public byte hrZoneType = -1; + + public short runPaceZone1Min = -1; + public short runPaceZone2Min = -1; + public short runPaceZone3Min = -1; + public short runPaceZone4Min = -1; + public short runPaceZone5Min = -1; + public short runPaceZone5Max = -1; + + public short runPaceZone1Time = -1; + public short runPaceZone2Time = -1; + public short runPaceZone3Time = -1; + public short runPaceZone4Time = -1; + public short runPaceZone5Time = -1; + + public byte algType = 0; + + public int trainingPoints = -1; public Response(ParamsProvider paramsProvider) { super(paramsProvider); @@ -189,6 +209,8 @@ public class Workout { this.workoutLoad = container.getInteger(0x0d); if (container.contains(0x0e)) this.workoutAerobicEffect = container.getInteger(0x0e); + if (container.contains(0x10)) + this.maxMET = container.getInteger(0x10); if (container.contains(0x11)) this.recoveryTime = container.getShort(0x11); if (container.contains(0x12)) @@ -215,9 +237,36 @@ public class Workout { this.minAltitude = container.getInteger(0x1d); if (container.contains(0x20)) this.workoutAnaerobicEffect = container.getByte(0x20); + if (container.contains(0x24)) + this.hrZoneType = container.getByte(0x24); + if (container.contains(0x50)) + this.runPaceZone1Min = container.getShort(0x50); + if (container.contains(0x51)) + this.runPaceZone2Min = container.getShort(0x51); + if (container.contains(0x52)) + this.runPaceZone3Min = container.getShort(0x52); + if (container.contains(0x53)) + this.runPaceZone4Min = container.getShort(0x53); + if (container.contains(0x54)) + this.runPaceZone5Min = container.getShort(0x54); + if (container.contains(0x55)) + this.runPaceZone5Max = container.getShort(0x55); + if (container.contains(0x56)) + this.runPaceZone1Time = container.getShort(0x56); + if (container.contains(0x57)) + this.runPaceZone2Time = container.getShort(0x57); + if (container.contains(0x58)) + this.runPaceZone3Time = container.getShort(0x58); + if (container.contains(0x59)) + this.runPaceZone4Time = container.getShort(0x59); + if (container.contains(0x5a)) + this.runPaceZone5Time = container.getShort(0x5a); + if (container.contains(0x5d)) + this.algType = container.getByte(0x5d); + if (container.contains(0x63)) + this.trainingPoints = container.getShort(0x63); if (container.contains(0x66)) this.recoveryHeartRates = container.getBytes(0x66); - } } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiSupportProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiSupportProvider.java index 553b50efd..3bf31fcbd 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiSupportProvider.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiSupportProvider.java @@ -1649,8 +1649,25 @@ public class HuaweiSupportProvider { packet.minHeartRatePeak, packet.maxHeartRatePeak, recoveryHeartRates, - packet.swimType + packet.swimType, + packet.maxMET, + packet.hrZoneType, + packet.runPaceZone1Min, + packet.runPaceZone2Min, + packet.runPaceZone3Min, + packet.runPaceZone4Min, + packet.runPaceZone5Min, + packet.runPaceZone5Max, + packet.runPaceZone1Time, + packet.runPaceZone2Time, + packet.runPaceZone3Time, + packet.runPaceZone4Time, + packet.runPaceZone5Time, + packet.algType, + packet.trainingPoints ); + + db.getDaoSession().getHuaweiWorkoutSummarySampleDao().insertOrReplace(summarySample); return summarySample.getWorkoutId(); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiWorkoutGbParser.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiWorkoutGbParser.java index 1bb1c60a3..2323c0485 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiWorkoutGbParser.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiWorkoutGbParser.java @@ -395,7 +395,7 @@ public class HuaweiWorkoutGbParser implements ActivitySummaryParser { summaryData.add(ActivitySummaryEntries.DISTANCE_METERS, summary.getDistance(), ActivitySummaryEntries.UNIT_METERS); summaryData.add(ActivitySummaryEntries.STEPS, summary.getStepCount(), ActivitySummaryEntries.UNIT_STEPS); summaryData.add(ActivitySummaryEntries.ACTIVE_SECONDS, summary.getDuration(), ActivitySummaryEntries.UNIT_SECONDS); - summaryData.add(ActivitySummaryEntries.STATUS, summary.getStatus() & 0xFF, ActivitySummaryEntries.UNIT_NONE); + //summaryData.add(ActivitySummaryEntries.STATUS, summary.getStatus() & 0xFF, ActivitySummaryEntries.UNIT_NONE); summaryData.add(ActivitySummaryEntries.TYPE, summary.getType() & 0xFF, ActivitySummaryEntries.UNIT_NONE); if (summary.getStrokes() != -1) { @@ -492,15 +492,15 @@ public class HuaweiWorkoutGbParser implements ActivitySummaryParser { int sumAltitudeDown = 0; //NOTE: The method of retrieving HR zones from the Huawei watch is not discovered. It may not return zones. - // So they are calculated based on config. Enabled only for running and walking activities for testing. - // Currently only calculated zones based on MHR. - // TODO: Use other methods after the configuration will be implemented. Use calculateMethod on HeartRateZonesConfig class. - // TODO: Enable for other workout types + // So they are calculated based on config. HeartRateZonesConfig HRZonesCfg = null; - if( type == ActivityKind.WALKING || type == ActivityKind.RUNNING) { + Integer zoneType = HuaweiWorkoutUtils.getHRZoneTypeByActivity(type); + int zoneCalculateMethod = summary.getHrZoneType(); + LOG.info("Workout HR Zone Calculate Type: {}", zoneCalculateMethod); + if(zoneType != null && HeartRateZonesConfig.isCalculateMethodValidFroType(zoneType, zoneCalculateMethod)) { ActivityUser activityUser = new ActivityUser(); HuaweiSportHRZones hrSportZones = new HuaweiSportHRZones(activityUser.getAge()); - HRZonesCfg = hrSportZones.getHRZonesConfigByType(HeartRateZonesConfig.TYPE_UPRIGHT); + HRZonesCfg = hrSportZones.getHRZonesConfigByType(zoneType); } int dataDelta = 5; @@ -514,7 +514,7 @@ public class HuaweiWorkoutGbParser implements ActivitySummaryParser { for (HuaweiWorkoutDataSample dataSample : dataSamples) { if(HRZonesCfg != null) { - int zoneIdx = HRZonesCfg.getMHRZone(dataSample.getHeartRate() & 0xFF); + int zoneIdx = HRZonesCfg.getZoneByMethod(dataSample.getHeartRate() & 0xFF, zoneCalculateMethod); if (zoneIdx != -1 && dataIdx < (dataSamples.size() - 1)) { HRZones[zoneIdx] += dataDelta; } @@ -627,7 +627,7 @@ public class HuaweiWorkoutGbParser implements ActivitySummaryParser { if(HRZonesCfg != null) { final double totalTime = Arrays.stream(HRZones).sum(); final List zoneOrder = Arrays.asList(ActivitySummaryEntries.HR_ZONE_WARM_UP, ActivitySummaryEntries.HR_ZONE_FAT_BURN, ActivitySummaryEntries.HR_ZONE_AEROBIC, ActivitySummaryEntries.HR_ZONE_ANAEROBIC, ActivitySummaryEntries.HR_ZONE_EXTREME); - for (int i = 0; i < zoneOrder.size(); i++) { + for (int i = zoneOrder.size() - 1; i >= 0; i--) { double timeInZone = HRZones[i]; LOG.info("Zone: {} {}", zoneOrder.get(i), timeInZone); summaryData.add( @@ -675,8 +675,6 @@ public class HuaweiWorkoutGbParser implements ActivitySummaryParser { } if (stepRatePresent) { - summaryData.add(ActivitySummaryEntries.STEP_RATE_SUM, stepRate, ActivitySummaryEntries.UNIT_SPM); - summaryData.add(ActivitySummaryEntries.STEP_RATE_AVG, avgStepRate, ActivitySummaryEntries.UNIT_SPM); } @@ -724,11 +722,7 @@ public class HuaweiWorkoutGbParser implements ActivitySummaryParser { if(avgStrokeRate == -1) { avgStrokeRate = strokeRate; } - // TODO: find out unit? - summaryData.add(ActivitySummaryEntries.STROKE_RATE_AVG, strokeRate, ActivitySummaryEntries.UNIT_NONE); - - // TODO: find out unit? - summaryData.add(ActivitySummaryEntries.STROKE_RATE_MAX, maxStrokeRate, ActivitySummaryEntries.UNIT_NONE); + summaryData.add(ActivitySummaryEntries.STROKE_RATE_MAX, maxStrokeRate, ActivitySummaryEntries.UNIT_STROKES_PER_MINUTE); } if (heartRateCount > 0) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiWorkoutUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiWorkoutUtils.java new file mode 100644 index 000000000..6073c2907 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiWorkoutUtils.java @@ -0,0 +1,48 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HeartRateZonesConfig; +import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; + +public class HuaweiWorkoutUtils { + private static final Map activityHRZoneType = createActivityHRZoneType(); + + + //TODO: discover and add more activity types. Should be same as in the watch. + private static Map createActivityHRZoneType() { + Map result = new HashMap<>(); + result.put(ActivityKind.RUNNING, HeartRateZonesConfig.TYPE_UPRIGHT); + result.put(ActivityKind.WALKING, HeartRateZonesConfig.TYPE_UPRIGHT); + result.put(ActivityKind.CYCLING, HeartRateZonesConfig.TYPE_SITTING); + result.put(ActivityKind.MOUNTAIN_HIKE, HeartRateZonesConfig.TYPE_UPRIGHT); + result.put(ActivityKind.INDOOR_RUNNING, HeartRateZonesConfig.TYPE_UPRIGHT); + result.put(ActivityKind.POOL_SWIM, HeartRateZonesConfig.TYPE_SWIMMING); + result.put(ActivityKind.INDOOR_CYCLING, HeartRateZonesConfig.TYPE_SITTING); + result.put(ActivityKind.SWIMMING_OPENWATER, HeartRateZonesConfig.TYPE_SWIMMING); + result.put(ActivityKind.INDOOR_WALKING, HeartRateZonesConfig.TYPE_UPRIGHT); + result.put(ActivityKind.HIKING, HeartRateZonesConfig.TYPE_UPRIGHT); + result.put(ActivityKind.JUMP_ROPING, HeartRateZonesConfig.TYPE_UPRIGHT); + result.put(ActivityKind.PINGPONG, HeartRateZonesConfig.TYPE_UPRIGHT); + result.put(ActivityKind.BADMINTON, HeartRateZonesConfig.TYPE_UPRIGHT); + result.put(ActivityKind.TENNIS, HeartRateZonesConfig.TYPE_UPRIGHT); + result.put(ActivityKind.SOCCER, HeartRateZonesConfig.TYPE_UPRIGHT); + result.put(ActivityKind.BASKETBALL, HeartRateZonesConfig.TYPE_UPRIGHT); + result.put(ActivityKind.VOLLEYBALL, HeartRateZonesConfig.TYPE_UPRIGHT); + result.put(ActivityKind.ELLIPTICAL_TRAINER, HeartRateZonesConfig.TYPE_UPRIGHT); + result.put(ActivityKind.ROWING_MACHINE, HeartRateZonesConfig.TYPE_SITTING); + result.put(ActivityKind.STEPPER, HeartRateZonesConfig.TYPE_UPRIGHT); + result.put(ActivityKind.YOGA, HeartRateZonesConfig.TYPE_OTHER); + return Collections.unmodifiableMap(result); + } + + public static Integer getHRZoneTypeByActivity(ActivityKind type) { + if(activityHRZoneType.containsKey(type)) { + return activityHRZoneType.get(type); + } + return null; + } + +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendRunPaceConfigRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendRunPaceConfigRequest.java index bb1be876f..68d0329dc 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendRunPaceConfigRequest.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendRunPaceConfigRequest.java @@ -6,6 +6,7 @@ import org.slf4j.LoggerFactory; import java.util.List; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket; +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiRunPaceConfig; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FitnessData; import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider; @@ -26,15 +27,7 @@ public class SendRunPaceConfigRequest extends Request { @Override protected List createRequest() throws Request.RequestCreationException { try { - //Hardcoded value till interface enable threshold values - return new FitnessData.RunPaceConfig.Request(paramsProvider, - 0x1C2, - 0x1A4, - 0x186, - 0x168, - 0x14A, - 0x12C - ).serialize(); + return new FitnessData.RunPaceConfig.Request(paramsProvider, new HuaweiRunPaceConfig()).serialize(); } catch (HuaweiPacket.CryptoException e) { throw new Request.RequestCreationException(e); }