From 16a8efb7ee1ac611a19f8b40e0890181f04e841c Mon Sep 17 00:00:00 2001 From: AndrewP <> Date: Sat, 30 Nov 2024 20:26:56 +0100 Subject: [PATCH] More workout types and parser for rowing and for treadmill --- .../xiaomi/activity/XiaomiActivityFileId.java | 4 +- .../activity/impl/WorkoutSummaryParser.java | 103 +++++++++++++++--- .../impl/XiaomiSimpleActivityParser.java | 57 ++++++++++ 3 files changed, 144 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/XiaomiActivityFileId.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/XiaomiActivityFileId.java index b1c338d7c..c13cb20a7 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/XiaomiActivityFileId.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/XiaomiActivityFileId.java @@ -197,14 +197,14 @@ public class XiaomiActivityFileId implements Comparable { ACTIVITY_SLEEP(Type.ACTIVITY, 0x08), SPORTS_OUTDOOR_RUNNING(Type.SPORTS, 0x01), SPORTS_OUTDOOR_WALKING_V1(Type.SPORTS, 0x02), - SPORTS_TREADMILL(Type.SPORTS, 0x03), // TODO + SPORTS_TREADMILL(Type.SPORTS, 0x03), SPORTS_OUTDOOR_CYCLING_V2(Type.SPORTS, 0x06), // TODO SPORTS_INDOOR_CYCLING(Type.SPORTS, 0x07), SPORTS_FREESTYLE(Type.SPORTS, 0x08), SPORTS_POOL_SWIMMING(Type.SPORTS, 0x09), SPORTS_HIIT(Type.SPORTS, 0x10), SPORTS_ELLIPTICAL(Type.SPORTS, 0x0B), - SPORTS_ROWING(Type.SPORTS, 0x0D), // TODO + SPORTS_ROWING(Type.SPORTS, 0x0D), SPORTS_ROPESKIPPING(Type.SPORTS, 0x0E), // TODO SPORTS_OUTDOOR_WALKING_V2(Type.SPORTS, 0x16), SPORTS_OUTDOOR_CYCLING(Type.SPORTS, 0x17), diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/WorkoutSummaryParser.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/WorkoutSummaryParser.java index 8d9d534d9..d22486eb7 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/WorkoutSummaryParser.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/WorkoutSummaryParser.java @@ -193,7 +193,6 @@ public class WorkoutSummaryParser extends XiaomiActivityParser implements Activi break; case SPORTS_FREESTYLE: summary.setActivityKind(ActivityKind.EXERCISE.getCode()); - // summary.setActivityKind(ActivityKind.STRENGTH_TRAINING.getCode()); parser = getFreestyleParser(fileId); break; case SPORTS_POOL_SWIMMING: @@ -202,7 +201,6 @@ public class WorkoutSummaryParser extends XiaomiActivityParser implements Activi break; case SPORTS_HIIT: summary.setActivityKind(ActivityKind.HIIT.getCode()); - // summary.setActivityKind(ActivityKind.EXERCISE.getCode()); parser = getHiitParser(fileId); break; case SPORTS_ELLIPTICAL: @@ -215,6 +213,14 @@ public class WorkoutSummaryParser extends XiaomiActivityParser implements Activi case SPORTS_OUTDOOR_CYCLING: parser = getOutdoorCyclingParser(fileId); break; + case SPORTS_TREADMILL: + summary.setActivityKind(ActivityKind.TREADMILL.getCode()); + parser = getTreadmillParser(fileId); + break; + case SPORTS_ROWING: + summary.setActivityKind(ActivityKind.ROWING.getCode()); + parser = getRowingParser(fileId); + break; default: LOG.warn("No workout summary parser for {}", fileId.getSubtypeCode()); break; @@ -269,22 +275,9 @@ public class WorkoutSummaryParser extends XiaomiActivityParser implements Activi builder.addInt(HR_ZONE_FAT_BURN, UNIT_SECONDS); builder.addInt(HR_ZONE_WARM_UP, UNIT_SECONDS); if (version == 5) { - builder.addUnknown(11); - // TODO: next byte is FreestyleType? - // indoor cycling 0x07 - // freestyle 0x08 - // yoga 0x0c - // HIIT 0x10 - // stair climbing 0x2d - // core training 0x2f - // flexibility 0x30 - // pilates 0x31 - // stretching 0x33 - // strength 0x34 - // aerobics 0x36 - // roller skating 0xca - builder.addUnknown(1); // FREESTYLE_TYPE - builder.addUnknown(1); + builder.addUnknown(10); + builder.addShort(XIAOMI_WORKOUT_TYPE, UNIT_NONE); + builder.addUnknown(8); } else { builder.addUnknown(2); builder.addUnknown(4); @@ -679,4 +672,78 @@ public class WorkoutSummaryParser extends XiaomiActivityParser implements Activi return builder.build(); } + + @Nullable + private XiaomiSimpleActivityParser getRowingParser(final XiaomiActivityFileId fileId) { + final int version = fileId.getVersion(); + final int headerSize; + switch (version) { + case 4: + headerSize = 4; + break; + default: + LOG.warn("Unable to parse workout summary version {}", fileId.getVersion()); + return null; + } + + final XiaomiSimpleActivityParser.Builder builder = new XiaomiSimpleActivityParser.Builder(); + builder.setHeaderSize(headerSize); + builder.addInt(TIME_START, UNIT_UNIX_EPOCH_SECONDS); + builder.addInt(TIME_END, UNIT_UNIX_EPOCH_SECONDS); + builder.addInt(ACTIVE_SECONDS, UNIT_SECONDS); + builder.addShort(CALORIES_BURNT, UNIT_KCAL); + builder.addByte(HR_AVG, UNIT_BPM); + builder.addByte(HR_MAX, UNIT_BPM); + builder.addByte(HR_MIN, UNIT_BPM); + builder.addUnknown(7); + builder.addInt(HR_ZONE_EXTREME, UNIT_SECONDS); + builder.addInt(HR_ZONE_ANAEROBIC, UNIT_SECONDS); + builder.addInt(HR_ZONE_AEROBIC, UNIT_SECONDS); + builder.addInt(HR_ZONE_FAT_BURN, UNIT_SECONDS); + builder.addInt(HR_ZONE_WARM_UP, UNIT_SECONDS); + builder.addUnknown(2); + builder.addInt(STROKES, UNIT_STROKES); + builder.addInt(STROKE_RATE_AVG, UNIT_STROKES_PER_MINUTE); + builder.addUnknown(4); // STROKE_RATE_MAX, UNIT_STROKES_PER_MINUTE + builder.addUnknown(31); + return builder.build(); + } + + @Nullable + private XiaomiSimpleActivityParser getTreadmillParser(final XiaomiActivityFileId fileId) { + final int version = fileId.getVersion(); + final int headerSize; + switch (version) { + case 5: + headerSize = 4; + break; + default: + LOG.warn("Unable to parse workout summary version {}", fileId.getVersion()); + return null; + } + + final XiaomiSimpleActivityParser.Builder builder = new XiaomiSimpleActivityParser.Builder(); + builder.setHeaderSize(headerSize); + builder.addInt(TIME_START, UNIT_UNIX_EPOCH_SECONDS); + builder.addInt(TIME_END, UNIT_UNIX_EPOCH_SECONDS); + builder.addInt(ACTIVE_SECONDS, UNIT_SECONDS); + builder.addInt(DISTANCE_METERS, UNIT_METERS); + builder.addShort(CALORIES_BURNT, UNIT_KCAL); + builder.addUnknown(4); + builder.addUnknown(4); + builder.addInt(STEPS, UNIT_STEPS); + builder.addUnknown(2); // MAX_STEPS_PER_MINUTE, UNIT_STEPS_PER_MINUTE + builder.addByte(HR_AVG, UNIT_BPM); + builder.addByte(HR_MAX, UNIT_BPM); + builder.addByte(HR_MIN, UNIT_BPM); + builder.addUnknown(8); + builder.addInt(HR_ZONE_EXTREME, UNIT_SECONDS); + builder.addInt(HR_ZONE_ANAEROBIC, UNIT_SECONDS); + builder.addInt(HR_ZONE_AEROBIC, UNIT_SECONDS); + builder.addInt(HR_ZONE_FAT_BURN, UNIT_SECONDS); + builder.addInt(HR_ZONE_WARM_UP, UNIT_SECONDS); + builder.addUnknown(32); + + return builder.build(); + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/XiaomiSimpleActivityParser.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/XiaomiSimpleActivityParser.java index 898f779cb..d32737890 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/XiaomiSimpleActivityParser.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/XiaomiSimpleActivityParser.java @@ -106,12 +106,69 @@ public class XiaomiSimpleActivityParser { case 2: summary.setActivityKind(ActivityKind.WALKING.getCode()); break; + case 4: + summary.setActivityKind(ActivityKind.TREKKING.getCode()); + break; case 6: summary.setActivityKind(ActivityKind.OUTDOOR_CYCLING.getCode()); break; + case 7: // indoor cycling 0x0007 + summary.setActivityKind(ActivityKind.INDOOR_CYCLING.getCode()); + break; + case 8: // freestyle 0x0008 + summary.setActivityKind(ActivityKind.FREE_TRAINING.getCode()); + break; + case 12: // yoga 0x000c + summary.setActivityKind(ActivityKind.YOGA.getCode()); + break; case 15: summary.setActivityKind(ActivityKind.OUTDOOR_WALKING.getCode()); break; + case 16: // HIIT 0x0010 + summary.setActivityKind(ActivityKind.HIIT.getCode()); + break; + case 201: // skateboard 0x00c9 + summary.setActivityKind(ActivityKind.SKATEBOARDING.getCode()); + break; + case 202: // roller skating 0x00ca + summary.setActivityKind(ActivityKind.ROLLER_SKATING.getCode()); + break; + case 301: // stair climbing 0x012d + summary.setActivityKind(ActivityKind.STAIR_CLIMBER.getCode()); + break; + case 303: // core training 0x012f + summary.setActivityKind(ActivityKind.CORE_TRAINING.getCode()); + break; + case 304: // flexibility 0x0130 + summary.setActivityKind(ActivityKind.FLEXIBILITY.getCode()); + break; + case 305: // pilates 0x0131 + summary.setActivityKind(ActivityKind.PILATES.getCode()); + break; + case 307: // stretching 0x0133 + summary.setActivityKind(ActivityKind.STRETCHING.getCode()); + break; + case 308: // strength 0x0134 + summary.setActivityKind(ActivityKind.STRENGTH_TRAINING.getCode()); + break; + case 310: // aerobics 0x0136 + summary.setActivityKind(ActivityKind.AEROBICS.getCode()); + break; + case 499: // dancing 0x01f3 + summary.setActivityKind(ActivityKind.PLAZA_DANCING.getCode()); + break; + case 607: // table tennis 0x025f + summary.setActivityKind(ActivityKind.TABLE_TENNIS.getCode()); + break; + case 609: // tennis 0x0261 + summary.setActivityKind(ActivityKind.TENNIS.getCode()); + break; + case 700: // ice skating 0x02bc + summary.setActivityKind(ActivityKind.ICE_SKATING.getCode()); + break; + case 709: // skiing 0x02c5 + summary.setActivityKind(ActivityKind.SKIING.getCode()); + break; default: summary.setActivityKind(ActivityKind.UNKNOWN.getCode()); }