CMF Watch Pro 2: Improve activity parsing 2 (#4530)

This commit is contained in:
José Rebelo
2026-01-01 17:19:50 +00:00
parent 5ea10473be
commit b173cad550
2 changed files with 63 additions and 4 deletions
@@ -26,8 +26,11 @@ import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.HR_ZONE_FAT_BURN;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.HR_ZONE_MAXIMUM;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.HR_ZONE_WARM_UP;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.PACE_AVG_SECONDS_KM;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.RECOVERY_TIME;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.STEPS;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.TRAINING_EFFECT_AEROBIC;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.TRAINING_EFFECT_ANAEROBIC;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.TRAINING_LOAD;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_BPM;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_KCAL;
@@ -106,7 +109,11 @@ public class CmfWorkoutSummaryParser implements ActivitySummaryParser {
final int distanceMeters = buf.getInt();
summaryData.add(DISTANCE_METERS, distanceMeters, UNIT_METERS);
buf.get(new byte[8]); //?
final int averagePace = buf.getShort();
summaryData.add(PACE_AVG_SECONDS_KM, averagePace, UNIT_SECONDS);
buf.get(new byte[2]); //?
buf.get(new byte[4]); //?
final int endTime = buf.getInt();
summary.setEndTime(new Date(endTime * 1000L));
@@ -119,7 +126,10 @@ public class CmfWorkoutSummaryParser implements ActivitySummaryParser {
final int trainingLoad = buf.getShort();
summaryData.add(TRAINING_LOAD, trainingLoad, UNIT_NONE);
buf.get(new byte[4]); //?
final int effectAerobic = buf.getShort();
summaryData.add(TRAINING_EFFECT_AEROBIC, effectAerobic / 100f, UNIT_NONE);
final int effectAnaerobic = buf.getShort();
summaryData.add(TRAINING_EFFECT_ANAEROBIC, effectAnaerobic / 100f, UNIT_NONE);
final int recoveryTimeMinutes = buf.getShort();
summaryData.add(RECOVERY_TIME, recoveryTimeMinutes * 60, UNIT_SECONDS);
@@ -36,7 +36,7 @@ public class CmfWorkoutSummaryParserTest extends TestBase {
// TODO max pace 6'23''
// TODO avg speed 6.50kmph
// TODO avg pace 6'19"
assertEquals(6d * 60 + 19, summaryData.getNumber(ActivitySummaryEntries.PACE_AVG_SECONDS_KM, -1)); // 6'19"
assertEquals(1544d, summaryData.getNumber(ActivitySummaryEntries.STEPS, -1));
@@ -52,10 +52,59 @@ public class CmfWorkoutSummaryParserTest extends TestBase {
assertEquals(0d, summaryData.getNumber(ActivitySummaryEntries.HR_ZONE_AEROBIC, -1)); // 0 min
assertEquals(6 * 60d, summaryData.getNumber(ActivitySummaryEntries.HR_ZONE_ANAEROBIC, -1)); // 6 min
// TODO aerobic endurance 1.0 / relaxed
assertEquals(1.0d, summaryData.getNumber(ActivitySummaryEntries.TRAINING_EFFECT_AEROBIC, -1)); // 1.0 / relaxed
assertEquals(27d, summaryData.getNumber(ActivitySummaryEntries.TRAINING_LOAD, -1));
assertEquals(3 * 60 * 60d, summaryData.getNumber(ActivitySummaryEntries.RECOVERY_TIME, -1)); // 3h
assertEquals(8d, summaryData.getNumber(ActivitySummaryEntries.ACTIVE_SCORE, -1)); // +8
}
@Test
public void testParseSummary3() {
// From https://codeberg.org/Freeyourgadget/Gadgetbridge/issues/4530
final byte[] bytes = GB.hexStringToByteArray("ec153769380e02afe8039d250000e222000097010000000000002524376901006400e000f401310d0000000001002b000e00281d0100");
final CmfWorkoutSummaryParser parser = new CmfWorkoutSummaryParser(null, getContext());
final BaseActivitySummary summary = new BaseActivitySummary();
summary.setRawSummaryData(bytes);
parser.parseBinaryData(summary, false);
final long durationSeconds = 3600 + 40; // 01:00:40
assertEquals(1765217772000L, summary.getStartTime().getTime()); // 2025-12-08 19:16
assertEquals(summary.getStartTime().getTime() + durationSeconds * 1000L + 1000L /* FIXME why +1? */, summary.getEndTime().getTime());
assertEquals(ActivityKind.OUTDOOR_RUNNING.getCode(), summary.getActivityKind());
final ActivitySummaryData summaryData = ActivitySummaryData.fromJson(summary.getSummaryData());
assertNotNull(summaryData);
assertEquals(8930d, summaryData.getNumber(ActivitySummaryEntries.DISTANCE_METERS, -1)); // 8.93km
assertEquals((double) durationSeconds, summaryData.getNumber(ActivitySummaryEntries.ACTIVE_SECONDS, -1));
assertEquals(1000d, summaryData.getNumber(ActivitySummaryEntries.CALORIES_BURNT, -1));
// TODO max pace 612“
// TODO avg speed 8.83kmph
assertEquals(6d * 60 + 47, summaryData.getNumber(ActivitySummaryEntries.PACE_AVG_SECONDS_KM, -1)); // 647“
assertEquals(9629d, summaryData.getNumber(ActivitySummaryEntries.STEPS, -1));
// TODO avg step rate 158 steps/min
// TODO avg step stride 92cm
assertEquals(175d, summaryData.getNumber(ActivitySummaryEntries.HR_AVG, -1));
// TODO max hr 194
assertEquals(0d, summaryData.getNumber(ActivitySummaryEntries.HR_ZONE_WARM_UP, -1)); // N/A
assertEquals(0d, summaryData.getNumber(ActivitySummaryEntries.HR_ZONE_FAT_BURN, -1)); // 0 min
assertEquals(1 * 60d, summaryData.getNumber(ActivitySummaryEntries.HR_ZONE_AEROBIC, -1)); // 1 min
assertEquals(43 * 60d, summaryData.getNumber(ActivitySummaryEntries.HR_ZONE_ANAEROBIC, -1)); // 43 min
assertEquals(14 * 60d, summaryData.getNumber(ActivitySummaryEntries.HR_ZONE_MAXIMUM, -1)); // 14 min
assertEquals(2.24d, summaryData.getNumber(ActivitySummaryEntries.TRAINING_EFFECT_AEROBIC, -1)); // 2.2 / foundation
assertEquals(5.0d, summaryData.getNumber(ActivitySummaryEntries.TRAINING_EFFECT_ANAEROBIC, -1)); // 5.0 / ultimate
assertEquals(100d, summaryData.getNumber(ActivitySummaryEntries.TRAINING_LOAD, -1));
assertEquals(56 * 60 * 60d + 17 * 60d, summaryData.getNumber(ActivitySummaryEntries.RECOVERY_TIME, -1)); // 56.3h
assertEquals(7d, summaryData.getNumber(ActivitySummaryEntries.ACTIVE_SCORE, -1)); // FIXME should be +73
}
}