diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySummaryDetail.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySummaryDetail.java index c066cdd33..eb3e2c95d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySummaryDetail.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySummaryDetail.java @@ -17,6 +17,8 @@ along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.activities; +import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.*; + import android.annotation.SuppressLint; import android.content.ActivityNotFoundException; import android.content.Context; @@ -86,7 +88,6 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryJsonSummary; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser; import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils; import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils; -import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper; import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.SwipeEvents; @@ -464,13 +465,13 @@ public class ActivitySummaryDetail extends AbstractGBActivity { if (!show_raw_data) { //special casing here + imperial units handling switch (unit) { - case "cm": + case UNIT_CM: if (units.equals(UNIT_IMPERIAL)) { value = value * 0.0328084; unit = "ft"; } break; - case "meters_second": + case UNIT_METERS_PER_SECOND: if (units.equals(UNIT_IMPERIAL)) { value = value * 2.236936D; unit = "mi_h"; @@ -479,7 +480,7 @@ public class ActivitySummaryDetail extends AbstractGBActivity { unit = "km_h"; } break; - case "seconds_m": + case UNIT_SECONDS_PER_M: if (units.equals(UNIT_IMPERIAL)) { value = value * (1609.344 / 60D); unit = "minutes_mi"; @@ -488,7 +489,7 @@ public class ActivitySummaryDetail extends AbstractGBActivity { unit = "minutes_km"; } break; - case "seconds_km": + case UNIT_SECONDS_PER_KM: if (units.equals(UNIT_IMPERIAL)) { value = value / 60D * 1.609344; unit = "minutes_mi"; @@ -497,7 +498,7 @@ public class ActivitySummaryDetail extends AbstractGBActivity { unit = "minutes_km"; } break; - case "meters": + case UNIT_METERS: if (units.equals(UNIT_IMPERIAL)) { value = value * 3.28084D; unit = "ft"; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/Huami2021ActivitySummaryParser.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/Huami2021ActivitySummaryParser.java index 9dde63c0d..f0bd87c9b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/Huami2021ActivitySummaryParser.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/Huami2021ActivitySummaryParser.java @@ -16,6 +16,8 @@ along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.devices.huami; +import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.*; + import com.google.protobuf.InvalidProtocolBufferException; import org.apache.commons.lang3.ArrayUtils; @@ -73,7 +75,7 @@ public class Huami2021ActivitySummaryParser extends HuamiActivitySummaryParser { if (summaryProto.hasTime()) { int totalDuration = summaryProto.getTime().getTotalDuration(); summary.setEndTime(new Date(startTime.getTime() + totalDuration * 1000L)); - addSummaryData("activeSeconds", summaryProto.getTime().getWorkoutDuration(), "seconds"); + addSummaryData(ACTIVE_SECONDS, summaryProto.getTime().getWorkoutDuration(), UNIT_SECONDS); // TODO pause durations } @@ -82,93 +84,94 @@ public class Huami2021ActivitySummaryParser extends HuamiActivitySummaryParser { summary.setBaseLatitude(summaryProto.getLocation().getBaseLatitude()); summary.setBaseAltitude(summaryProto.getLocation().getBaseAltitude() / 2); // TODO: Min/Max Latitude/Longitude - addSummaryData("baseAltitude", summaryProto.getLocation().getBaseAltitude() / 2, "meters"); + addSummaryData(ALTITUDE_BASE, summaryProto.getLocation().getBaseAltitude() / 2, UNIT_METERS); } if (summaryProto.hasHeartRate()) { - addSummaryData("averageHR", summaryProto.getHeartRate().getAvg(), "bpm"); - addSummaryData("maxHR", summaryProto.getHeartRate().getMax(), "bpm"); - addSummaryData("minHR", summaryProto.getHeartRate().getMin(), "bpm"); + addSummaryData(HR_AVG, summaryProto.getHeartRate().getAvg(), UNIT_BPM); + addSummaryData(HR_MAX, summaryProto.getHeartRate().getMax(), UNIT_BPM); + addSummaryData(HR_MIN, summaryProto.getHeartRate().getMin(), UNIT_BPM); } if (summaryProto.hasSteps()) { - addSummaryData("maxCadence", summaryProto.getSteps().getMaxCadence() * 60, "spm"); - addSummaryData("averageCadence", summaryProto.getSteps().getAvgCadence() * 60, "spm"); - addSummaryData("averageStride", summaryProto.getSteps().getAvgStride(), "cm"); - addSummaryData("steps", summaryProto.getSteps().getSteps(), "steps_unit"); + addSummaryData(CADENCE_MAX, summaryProto.getSteps().getMaxCadence() * 60, UNIT_SPM); + addSummaryData(CADENCE_AVG, summaryProto.getSteps().getAvgCadence() * 60, UNIT_SPM); + addSummaryData(STRIDE_AVG, summaryProto.getSteps().getAvgStride(), UNIT_CM); + addSummaryData(STEPS, summaryProto.getSteps().getSteps(), UNIT_STEPS); } if (summaryProto.hasDistance()) { - addSummaryData("distanceMeters", summaryProto.getDistance().getDistance(), "meters"); + addSummaryData(DISTANCE_METERS, summaryProto.getDistance().getDistance(), UNIT_METERS); } if (summaryProto.hasPace()) { - addSummaryData("maxPace", summaryProto.getPace().getBest(), "seconds_m"); - addSummaryData("averageKMPaceSeconds", summaryProto.getPace().getAvg() * 1000, "seconds_km"); + addSummaryData(PACE_MAX, summaryProto.getPace().getBest(), UNIT_SECONDS_PER_M); + addSummaryData(PACE_AVG_SECONDS_KM, summaryProto.getPace().getAvg() * 1000, UNIT_SECONDS_PER_KM); } if (summaryProto.hasCalories()) { - addSummaryData("caloriesBurnt", summaryProto.getCalories().getCalories(), "calories_unit"); + addSummaryData(CALORIES_BURNT, summaryProto.getCalories().getCalories(), UNIT_KCAL); } if (summaryProto.hasHeartRateZones()) { // TODO hr zones bpm? if (summaryProto.getHeartRateZones().getZoneTimeCount() == 6) { - addSummaryData("hrZoneNa", summaryProto.getHeartRateZones().getZoneTime(0), "seconds"); - addSummaryData("hrZoneWarmUp", summaryProto.getHeartRateZones().getZoneTime(1), "seconds"); - addSummaryData("hrZoneFatBurn", summaryProto.getHeartRateZones().getZoneTime(2), "seconds"); - addSummaryData("hrZoneAerobic", summaryProto.getHeartRateZones().getZoneTime(3), "seconds"); - addSummaryData("hrZoneAnaerobic", summaryProto.getHeartRateZones().getZoneTime(4), "seconds"); - addSummaryData("hrZoneExtreme", summaryProto.getHeartRateZones().getZoneTime(5), "seconds"); + addSummaryData(HR_ZONE_NA, summaryProto.getHeartRateZones().getZoneTime(0), UNIT_SECONDS); + addSummaryData(HR_ZONE_WARM_UP, summaryProto.getHeartRateZones().getZoneTime(1), UNIT_SECONDS); + addSummaryData(HR_ZONE_FAT_BURN, summaryProto.getHeartRateZones().getZoneTime(2), UNIT_SECONDS); + addSummaryData(HR_ZONE_AEROBIC, summaryProto.getHeartRateZones().getZoneTime(3), UNIT_SECONDS); + addSummaryData(HR_ZONE_ANAEROBIC, summaryProto.getHeartRateZones().getZoneTime(4), UNIT_SECONDS); + addSummaryData(HR_ZONE_EXTREME, summaryProto.getHeartRateZones().getZoneTime(5), UNIT_SECONDS); } else { LOG.warn("Unexpected number of HR zones {}", summaryProto.getHeartRateZones().getZoneTimeCount()); } } if (summaryProto.hasTrainingEffect()) { - addSummaryData("aerobicTrainingEffect", summaryProto.getTrainingEffect().getAerobicTrainingEffect(), ""); - addSummaryData("anaerobicTrainingEffect", summaryProto.getTrainingEffect().getAnaerobicTrainingEffect(), ""); - addSummaryData("currentWorkoutLoad", summaryProto.getTrainingEffect().getCurrentWorkoutLoad(), ""); - addSummaryData("maximumOxygenUptake", summaryProto.getTrainingEffect().getMaximumOxygenUptake(), "ml/kg/min"); + addSummaryData(TRAINING_EFFECT_AEROBIC, summaryProto.getTrainingEffect().getAerobicTrainingEffect(), UNIT_NONE); + addSummaryData(TRAINING_EFFECT_ANAEROBIC, summaryProto.getTrainingEffect().getAnaerobicTrainingEffect(), UNIT_NONE); + addSummaryData(WORKOUT_LOAD, summaryProto.getTrainingEffect().getCurrentWorkoutLoad(), UNIT_NONE); + addSummaryData(MAXIMUM_OXYGEN_UPTAKE, summaryProto.getTrainingEffect().getMaximumOxygenUptake(), UNIT_ML_KG_MIN); } if (summaryProto.hasAltitude()) { - addSummaryData("maxAltitude", summaryProto.getAltitude().getMaxAltitude() / 200, "meters"); - addSummaryData("minAltitude", summaryProto.getAltitude().getMinAltitude() / 200, "meters"); - addSummaryData("averageAltitude", summaryProto.getAltitude().getAvgAltitude() / 200, "meters"); + addSummaryData(ALTITUDE_MAX, summaryProto.getAltitude().getMaxAltitude() / 200, UNIT_METERS); + addSummaryData(ALTITUDE_MIN, summaryProto.getAltitude().getMinAltitude() / 200, UNIT_METERS); + addSummaryData(ALTITUDE_AVG, summaryProto.getAltitude().getAvgAltitude() / 200, UNIT_METERS); // TODO totalClimbing - addSummaryData("elevationGain", summaryProto.getAltitude().getElevationGain() / 100, "meters"); - addSummaryData("elevationLoss", summaryProto.getAltitude().getElevationLoss() / 100, "meters"); + addSummaryData(ELEVATION_GAIN, summaryProto.getAltitude().getElevationGain() / 100, UNIT_METERS); + addSummaryData(ELEVATION_LOSS, summaryProto.getAltitude().getElevationLoss() / 100, UNIT_METERS); } if (summaryProto.hasElevation()) { - addSummaryData("ascentSeconds", summaryProto.getElevation().getUphillTime(), "seconds"); - addSummaryData("descentSeconds", summaryProto.getElevation().getDownhillTime(), "seconds"); + addSummaryData(ASCENT_SECONDS, summaryProto.getElevation().getUphillTime(), UNIT_SECONDS); + addSummaryData(DESCENT_SECONDS, summaryProto.getElevation().getDownhillTime(), UNIT_SECONDS); } if (summaryProto.hasSwimmingData()) { - addSummaryData("laps", summaryProto.getSwimmingData().getLaps(), "laps_unit"); + addSummaryData(LAPS, summaryProto.getSwimmingData().getLaps(), UNIT_LAPS); switch (summaryProto.getSwimmingData().getLaneLengthUnit()) { case 0: - addSummaryData("laneLength", summaryProto.getSwimmingData().getLaneLength(), "meters"); + addSummaryData(LANE_LENGTH, summaryProto.getSwimmingData().getLaneLength(), UNIT_METERS); break; case 1: - addSummaryData("laneLength", summaryProto.getSwimmingData().getLaneLength(), "yard"); + addSummaryData(LANE_LENGTH, summaryProto.getSwimmingData().getLaneLength(), UNIT_YARD); break; } switch (summaryProto.getSwimmingData().getStyle()) { + // TODO i18n these case 1: - addSummaryData("swimStyle", "breaststroke"); + addSummaryData(SWIM_STYLE, "breaststroke"); break; case 2: - addSummaryData("swimStyle", "freestyle"); + addSummaryData(SWIM_STYLE, "freestyle"); break; } - addSummaryData("strokes", summaryProto.getSwimmingData().getStrokes(), "strokes_unit"); - addSummaryData("avgStrokeRate", summaryProto.getSwimmingData().getAvgStrokeRate(), "strokes_minute"); - addSummaryData("maxStrokeRate", summaryProto.getSwimmingData().getMaxStrokeRate(), "strokes_minute"); - addSummaryData("averageStrokeDistance", summaryProto.getSwimmingData().getAvgDps(), "cm"); - addSummaryData("swolfIndex", summaryProto.getSwimmingData().getSwolf(), ""); + addSummaryData(STROKES, summaryProto.getSwimmingData().getStrokes(), UNIT_STROKES); + addSummaryData(STROKE_RATE_AVG, summaryProto.getSwimmingData().getAvgStrokeRate(), UNIT_STROKES_PER_MINUTE); + addSummaryData(STROKE_RATE_MAX, summaryProto.getSwimmingData().getMaxStrokeRate(), UNIT_STROKES_PER_MINUTE); + addSummaryData(STROKE_DISTANCE_AVG, summaryProto.getSwimmingData().getAvgDps(), UNIT_CM); + addSummaryData(SWOLF_INDEX, summaryProto.getSwimmingData().getSwolf(), UNIT_NONE); } } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiActivitySummaryParser.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiActivitySummaryParser.java index b6761edd9..fa354e72b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiActivitySummaryParser.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiActivitySummaryParser.java @@ -17,6 +17,8 @@ along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.devices.huami; +import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.*; + import org.json.JSONException; import org.json.JSONObject; import org.slf4j.Logger; @@ -273,9 +275,9 @@ public class HuamiActivitySummaryParser implements ActivitySummaryParser { buffer.getInt(); // unknown probably flatDistance = buffer.getFloat(); flatSeconds = buffer.getInt() / 1000; // ms? - addSummaryData("ascentSeconds", ascentSeconds, "seconds"); - addSummaryData("descentSeconds", descentSeconds, "seconds"); - addSummaryData("flatSeconds", flatSeconds, "seconds"); + addSummaryData(ASCENT_SECONDS, ascentSeconds, UNIT_SECONDS); + addSummaryData(DESCENT_SECONDS, descentSeconds, UNIT_SECONDS); + addSummaryData(FLAT_SECONDS, flatSeconds, UNIT_SECONDS); } averageHR = buffer.getShort(); @@ -308,62 +310,62 @@ public class HuamiActivitySummaryParser implements ActivitySummaryParser { // summary.setAveragePace(BLETypeConversions.toUnsigned(averagePace); // summary.setAverageStride(BLETypeConversions.toUnsigned(averageStride); - addSummaryData("ascentSeconds", ascentSeconds, "seconds"); - addSummaryData("descentSeconds", descentSeconds, "seconds"); - addSummaryData("flatSeconds", flatSeconds, "seconds"); - addSummaryData("ascentDistance", ascentDistance, "meters"); - addSummaryData("descentDistance", descentDistance, "meters"); - addSummaryData("flatDistance", flatDistance, "meters"); + addSummaryData(ASCENT_SECONDS, ascentSeconds, UNIT_SECONDS); + addSummaryData(DESCENT_SECONDS, descentSeconds, UNIT_SECONDS); + addSummaryData(FLAT_SECONDS, flatSeconds, UNIT_SECONDS); + addSummaryData(ASCENT_DISTANCE, ascentDistance, UNIT_METERS); + addSummaryData(DESCENT_DISTANCE, descentDistance, UNIT_METERS); + addSummaryData(FLAT_DISTANCE, flatDistance, UNIT_METERS); - addSummaryData("distanceMeters", distanceMeters, "meters"); - // addSummaryData("distanceMeters2", distanceMeters2, "meters"); - addSummaryData("ascentMeters", ascentMeters, "meters"); - addSummaryData("descentMeters", descentMeters, "meters"); + addSummaryData(DISTANCE_METERS, distanceMeters, UNIT_METERS); + // addSummaryData("distanceMeters2", distanceMeters2, UNIT_METERS); + addSummaryData(ASCENT_METERS, ascentMeters, UNIT_METERS); + addSummaryData(DESCENT_METERS, descentMeters, UNIT_METERS); if (maxAltitude != -100000) { - addSummaryData("maxAltitude", maxAltitude, "meters"); + addSummaryData(ALTITUDE_MAX, maxAltitude, UNIT_METERS); } if (minAltitude != 100000) { - addSummaryData("minAltitude", minAltitude, "meters"); + addSummaryData(ALTITUDE_MIN, minAltitude, UNIT_METERS); } if (minAltitude != 100000) { - addSummaryData("averageAltitude", averageAltitude, "meters"); + addSummaryData(ALTITUDE_AVG, averageAltitude, UNIT_METERS); } - addSummaryData("steps", steps, "steps_unit"); - addSummaryData("activeSeconds", activeSeconds, "seconds"); - addSummaryData("caloriesBurnt", caloriesBurnt, "calories_unit"); - addSummaryData("maxSpeed", maxSpeed, "meters_second"); - addSummaryData("minSpeed", minSpeed, "meters_second"); - addSummaryData("averageSpeed", averageSpeed, "meters_second"); - addSummaryData("maxCadence", maxCadence, "spm"); - addSummaryData("minCadence", minCadence, "spm"); - addSummaryData("averageCadence", averageCadence, "spm"); + addSummaryData(STEPS, steps, UNIT_STEPS); + addSummaryData(ACTIVE_SECONDS, activeSeconds, UNIT_SECONDS); + addSummaryData(CALORIES_BURNT, caloriesBurnt, UNIT_KCAL); + addSummaryData(SPEED_MAX, maxSpeed, UNIT_METERS_PER_SECOND); + addSummaryData(SPEED_MIN, minSpeed, UNIT_METERS_PER_SECOND); + addSummaryData(SPEED_AVG, averageSpeed, UNIT_METERS_PER_SECOND); + addSummaryData(CADENCE_MAX, maxCadence, UNIT_SPM); + addSummaryData(CADENCE_MIN, minCadence, UNIT_SPM); + addSummaryData(CADENCE_AVG, averageCadence, UNIT_SPM); if (!(activityKind == ActivityKind.TYPE_ELLIPTICAL_TRAINER || activityKind == ActivityKind.TYPE_JUMP_ROPING || activityKind == ActivityKind.TYPE_EXERCISE || activityKind == ActivityKind.TYPE_YOGA || activityKind == ActivityKind.TYPE_INDOOR_CYCLING)) { - addSummaryData("minPace", minPace, "seconds_m"); - addSummaryData("maxPace", maxPace, "seconds_m"); - // addSummaryData("averagePace", averagePace, "seconds_m"); + addSummaryData(PACE_MIN, minPace, UNIT_SECONDS_PER_M); + addSummaryData(PACE_MAX, maxPace, UNIT_SECONDS_PER_M); + // addSummaryData("averagePace", averagePace, UNIT_SECONDS_PER_M); } - addSummaryData("totalStride", totalStride, "meters"); - addSummaryData("averageHR", averageHR, "bpm"); - addSummaryData("maxHR", maxHR, "bpm"); - addSummaryData("minHR", minHR, "bpm"); - addSummaryData("averageKMPaceSeconds", averageKMPaceSeconds, "seconds_km"); - addSummaryData("averageStride", averageStride, "cm"); - addSummaryData("maxStride", maxStride, "cm"); - addSummaryData("minStride", minStride, "cm"); - // addSummaryData("averageStride2", averageStride2, "cm"); + addSummaryData(STRIDE_TOTAL, totalStride, UNIT_METERS); + addSummaryData(HR_AVG, averageHR, UNIT_BPM); + addSummaryData(HR_MAX, maxHR, UNIT_BPM); + addSummaryData(HR_MIN, minHR, UNIT_BPM); + addSummaryData(PACE_AVG_SECONDS_KM, averageKMPaceSeconds, UNIT_SECONDS_PER_KM); + addSummaryData(STRIDE_AVG, averageStride, UNIT_CM); + addSummaryData(STRIDE_MAX, maxStride, UNIT_CM); + addSummaryData(STRIDE_MIN, minStride, UNIT_CM); + // addSummaryData("averageStride2", averageStride2, UNIT_CM); if (activityKind == ActivityKind.TYPE_SWIMMING || activityKind == ActivityKind.TYPE_SWIMMING_OPENWATER) { - addSummaryData("averageStrokeDistance", averageStrokeDistance, "meters"); - addSummaryData("averageStrokesPerSecond", averageStrokesPerSecond, "strokes_second"); - addSummaryData("averageLapPace", averageLapPace, "second"); - addSummaryData("strokes", strokes, "strokes"); - addSummaryData("swolfIndex", swolfIndex, "swolf_index"); + addSummaryData(STROKE_DISTANCE_AVG, averageStrokeDistance, UNIT_METERS); + addSummaryData(STROKE_AVG_PER_SECOND, averageStrokesPerSecond, UNIT_STROKES_PER_SECOND); + addSummaryData(LAP_PACE_AVERAGE, averageLapPace, "second"); + addSummaryData(STROKES, strokes, "strokes"); + addSummaryData(SWOLF_INDEX, swolfIndex, "swolf_index"); String swimStyleName = "unknown"; // TODO: translate here or keep as string identifier here? switch (swimStyle) { case 1: @@ -379,8 +381,8 @@ public class HuamiActivitySummaryParser implements ActivitySummaryParser { swimStyleName = "medley"; break; } - addSummaryData("swimStyle", swimStyleName); - addSummaryData("laps", laps, "laps"); + addSummaryData(SWIM_STYLE, swimStyleName); + addSummaryData(LAPS, laps, "laps"); } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummaryEntries.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummaryEntries.java new file mode 100644 index 000000000..525c94d4d --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummaryEntries.java @@ -0,0 +1,109 @@ +/* Copyright (C) 2023 José Rebelo + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.model; + +public class ActivitySummaryEntries { + public static final String TIME_START = "startTime"; + public static final String TIME_END = "endTime"; + public static final String ACTIVE_SECONDS = "activeSeconds"; + + public static final String ALTITUDE_AVG = "averageAltitude"; + public static final String ALTITUDE_BASE = "baseAltitude"; + public static final String ALTITUDE_MAX = "maxAltitude"; + public static final String ALTITUDE_MIN = "minAltitude"; + + public static final String ASCENT_DISTANCE = "ascentDistance"; + public static final String ASCENT_METERS = "ascentMeters"; + public static final String ASCENT_SECONDS = "ascentSeconds"; + public static final String DESCENT_DISTANCE = "descentDistance"; + public static final String DESCENT_METERS = "descentMeters"; + public static final String DESCENT_SECONDS = "descentSeconds"; + public static final String FLAT_DISTANCE = "flatDistance"; + public static final String FLAT_SECONDS = "flatSeconds"; + + public static final String CADENCE_AVG = "averageCadence"; + public static final String CADENCE_MAX = "maxCadence"; + public static final String CADENCE_MIN = "minCadence"; + + public static final String SPEED_AVG = "averageSpeed"; + public static final String SPEED_MAX = "maxSpeed"; + public static final String SPEED_MIN = "minSpeed"; + + public static final String DISTANCE_METERS = "distanceMeters"; + public static final String ELEVATION_GAIN = "elevationGain"; + public static final String ELEVATION_LOSS = "elevationLoss"; + + public static final String HR_AVG = "averageHR"; + public static final String HR_MAX = "maxHR"; + public static final String HR_MIN = "minHR"; + public static final String HR_ZONE_NA = "hrZoneNa"; + public static final String HR_ZONE_WARM_UP = "hrZoneWarmUp"; + public static final String HR_ZONE_FAT_BURN = "hrZoneFatBurn"; + public static final String HR_ZONE_AEROBIC = "hrZoneAerobic"; + public static final String HR_ZONE_ANAEROBIC = "hrZoneAnaerobic"; + public static final String HR_ZONE_EXTREME = "hrZoneExtreme"; + + public static final String LANE_LENGTH = "laneLength"; + public static final String LAPS = "laps"; + public static final String LAP_PACE_AVERAGE = "averageLapPace"; + + public static final String PACE_AVG_SECONDS_KM = "averageKMPaceSeconds"; + public static final String PACE_MAX = "maxPace"; + public static final String PACE_MIN = "minPace"; + public static final String STEPS = "steps"; + public static final String STRIDE_AVG = "averageStride"; + public static final String STRIDE_MAX = "maxStride"; + public static final String STRIDE_MIN = "minStride"; + public static final String STRIDE_TOTAL = "totalStride"; + + public static final String STROKE_DISTANCE_AVG = "averageStrokeDistance"; + public static final String STROKE_AVG_PER_SECOND = "averageStrokesPerSecond"; + public static final String STROKE_RATE_AVG = "avgStrokeRate"; + public static final String STROKE_RATE_MAX = "maxStrokeRate"; + public static final String STROKES = "strokes"; + + public static final String SWIM_STYLE = "swimStyle"; + public static final String SWOLF_INDEX = "swolfIndex"; + + public static final String CALORIES_BURNT = "caloriesBurnt"; + public static final String TRAINING_EFFECT_AEROBIC = "aerobicTrainingEffect"; + public static final String TRAINING_EFFECT_ANAEROBIC = "anaerobicTrainingEffect"; + public static final String WORKOUT_LOAD = "currentWorkoutLoad"; + public static final String MAXIMUM_OXYGEN_UPTAKE = "maximumOxygenUptake"; + public static final String RECOVERY_TIME = "recoveryTime"; + + public static final String UNIT_BPM = "bpm"; + public static final String UNIT_CM = "cm"; + public static final String UNIT_UNIX_EPOCH_SECONDS = "unix_epoch_seconds"; + public static final String UNIT_KCAL = "calories_unit"; + public static final String UNIT_LAPS = "laps_unit"; + public static final String UNIT_METERS = "meters"; + public static final String UNIT_ML_KG_MIN = "ml/kg/min"; + public static final String UNIT_NONE = ""; + public static final String UNIT_HOURS = "hours"; + public static final String UNIT_SECONDS = "seconds"; + public static final String UNIT_SECONDS_PER_KM = "seconds_km"; + public static final String UNIT_SECONDS_PER_M = "seconds_m"; + public static final String UNIT_METERS_PER_SECOND = "meters_second"; + public static final String UNIT_KMPH = "km_h"; + public static final String UNIT_SPM = "spm"; + public static final String UNIT_STEPS = "steps_unit"; + public static final String UNIT_STROKES = "strokes_unit"; + public static final String UNIT_STROKES_PER_MINUTE = "strokes_minute"; + public static final String UNIT_STROKES_PER_SECOND = "strokes_second"; + public static final String UNIT_YARD = "yard"; +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummaryJsonSummary.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummaryJsonSummary.java index e511f5efb..5dbfd8ab5 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummaryJsonSummary.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummaryJsonSummary.java @@ -1,5 +1,7 @@ package nodomain.freeyourgadget.gadgetbridge.model; +import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.*; + import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -153,35 +155,35 @@ public class ActivitySummaryJsonSummary { private JSONObject createActivitySummaryGroups(){ final Map> groupDefinitions = new HashMap>() {{ put("Strokes", Arrays.asList( - "averageStrokeDistance", "averageStrokesPerSecond", "strokes", - "avgStrokeRate", "maxStrokeRate" + STROKE_DISTANCE_AVG, STROKE_AVG_PER_SECOND, STROKES, + STROKE_RATE_AVG, STROKE_RATE_MAX )); put("Swimming", Arrays.asList( - "swolfIndex", "swimStyle" + SWOLF_INDEX, SWIM_STYLE )); put("Elevation", Arrays.asList( - "ascentMeters", "descentMeters", "maxAltitude", "minAltitude", "averageAltitude", - "baseAltitude", "ascentSeconds", "descentSeconds", "flatSeconds", "ascentDistance", - "descentDistance", "flatDistance", "elevationGain", "elevationLoss" + ASCENT_METERS, DESCENT_METERS, ALTITUDE_MAX, ALTITUDE_MIN, ALTITUDE_AVG, + ALTITUDE_BASE, ASCENT_SECONDS, DESCENT_SECONDS, FLAT_SECONDS, ASCENT_DISTANCE, + DESCENT_DISTANCE, FLAT_DISTANCE, ELEVATION_GAIN, ELEVATION_LOSS )); put("Speed", Arrays.asList( - "averageSpeed", "maxSpeed", "minSpeed", "averageKMPaceSeconds", "minPace", - "maxPace", "averageSpeed2", "averageCadence", "maxCadence", "minCadence" + SPEED_AVG, SPEED_MAX, SPEED_MIN, PACE_AVG_SECONDS_KM, PACE_MIN, + PACE_MAX, "averageSpeed2", CADENCE_AVG, CADENCE_MAX, CADENCE_MIN )); put("Activity", Arrays.asList( - "distanceMeters", "steps", "activeSeconds", "caloriesBurnt", "totalStride", - "averageHR", "maxHR", "minHR", "averageStride", "maxStride", "minStride" + DISTANCE_METERS, STEPS, ACTIVE_SECONDS, CALORIES_BURNT, STRIDE_TOTAL, + HR_AVG, HR_MAX, HR_MIN, STRIDE_AVG, STRIDE_MAX, STRIDE_MIN )); put("HeartRateZones", Arrays.asList( - "hrZoneNa", "hrZoneWarmUp", "hrZoneFatBurn", "hrZoneAerobic", "hrZoneAnaerobic", - "hrZoneExtreme" + HR_ZONE_NA, HR_ZONE_WARM_UP, HR_ZONE_FAT_BURN, HR_ZONE_AEROBIC, HR_ZONE_ANAEROBIC, + HR_ZONE_EXTREME )); put("TrainingEffect", Arrays.asList( - "aerobicTrainingEffect", "anaerobicTrainingEffect", "currentWorkoutLoad", - "maximumOxygenUptake" + TRAINING_EFFECT_AEROBIC, TRAINING_EFFECT_ANAEROBIC, WORKOUT_LOAD, + MAXIMUM_OXYGEN_UPTAKE )); put("laps", Arrays.asList( - "averageLapPace", "laps", "laneLength" + LAP_PACE_AVERAGE, LAPS, LANE_LENGTH )); }}; 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 9618a0745..223b6ec3c 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 @@ -16,13 +16,46 @@ along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.impl; +import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.ACTIVE_SECONDS; +import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.CADENCE_AVG; +import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.CALORIES_BURNT; +import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.DISTANCE_METERS; +import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.HR_AVG; +import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.HR_MAX; +import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.HR_MIN; +import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.HR_ZONE_AEROBIC; +import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.HR_ZONE_ANAEROBIC; +import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.HR_ZONE_EXTREME; +import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.HR_ZONE_FAT_BURN; +import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.HR_ZONE_WARM_UP; +import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.PACE_MAX; +import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.PACE_MIN; +import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.RECOVERY_TIME; +import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.SPEED_MAX; +import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.STEPS; +import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.TIME_END; +import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.TIME_START; +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.UNIT_BPM; +import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_HOURS; +import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_KCAL; +import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_KMPH; +import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_METERS; +import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_NONE; +import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_SECONDS; +import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_SECONDS_PER_M; +import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_SPM; +import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_STEPS; +import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_UNIX_EPOCH_SECONDS; +import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.WORKOUT_LOAD; +import static nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.impl.XiaomiSimpleActivityParser.XIAOMI_WORKOUT_TYPE; + import android.widget.Toast; import androidx.annotation.Nullable; import org.apache.commons.lang3.ArrayUtils; -import org.json.JSONException; -import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -152,50 +185,52 @@ public class WorkoutSummaryParser extends XiaomiActivityParser implements Activi final XiaomiSimpleActivityParser.Builder builder = new XiaomiSimpleActivityParser.Builder(); builder.setHeaderSize(headerSize); - builder.addInt("startTime", "seconds"); - builder.addInt("endTime", "seconds"); - builder.addInt("activeSeconds", "seconds"); + builder.addInt(TIME_START, UNIT_UNIX_EPOCH_SECONDS); + builder.addInt(TIME_END, UNIT_UNIX_EPOCH_SECONDS); + builder.addInt(ACTIVE_SECONDS, UNIT_SECONDS); builder.addUnknown(4); - builder.addShort("caloriesBurnt", "calories_unit"); + builder.addShort(CALORIES_BURNT, UNIT_KCAL); builder.addUnknown(4); - builder.addByte("averageHR", "bpm"); - builder.addByte("maxHR", "bpm"); - builder.addByte("minHR", "bpm"); - builder.addFloat("aerobicTrainingEffect", ""); + builder.addByte(HR_AVG, UNIT_BPM); + builder.addByte(HR_MAX, UNIT_BPM); + builder.addByte(HR_MIN, UNIT_BPM); + builder.addFloat(TRAINING_EFFECT_AEROBIC, UNIT_NONE); + builder.addUnknown(1); + builder.addUnknown(1); + builder.addShort(RECOVERY_TIME, UNIT_HOURS); + 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.addShort("recoveryTime", "hours"); - builder.addInt("hrZoneExtreme", "seconds"); - builder.addInt("hrZoneAnaerobic", "seconds"); - builder.addInt("hrZoneAerobic", "seconds"); - builder.addInt("hrZoneFatBurn", "seconds"); - builder.addInt("hrZoneWarmUp", "seconds"); - builder.addUnknown(6); - builder.addFloat("anaerobicTrainingEffect", ""); + builder.addUnknown(4); + builder.addFloat(TRAINING_EFFECT_ANAEROBIC, UNIT_NONE); + // FIXME identify field lengths to align with the header builder.addUnknown(3); - builder.addInt("configuredTimeGoal", "seconds"); - builder.addShort("configuredCaloriesGoal", "calories_unit"); - builder.addShort("maximumCaloriesGoal", "calories_unit"); // TODO: mhm? + builder.addInt("configuredTimeGoal", UNIT_SECONDS); + builder.addShort("configuredCaloriesGoal", UNIT_KCAL); + builder.addShort("maximumCaloriesGoal", UNIT_KCAL); // TODO: mhm? builder.addUnknown(28); - builder.addShort("trainingLoad", ""); + builder.addShort(WORKOUT_LOAD, UNIT_NONE); // training load builder.addUnknown(24); - builder.addByte("configuredSets", ""); + builder.addByte("configuredSets", UNIT_NONE); builder.addUnknown(13); - builder.addInt("startTime2", "seconds"); - builder.addInt("endTime2", "seconds"); - builder.addInt("goal", ""); // TODO match against goalType - builder.addInt("duration2", "seconds"); - builder.addInt("intervalTime", "seconds"); + builder.addInt("startTime2", UNIT_SECONDS); + builder.addInt("endTime2", UNIT_SECONDS); + builder.addInt("goal", UNIT_NONE); // TODO match against goalType + builder.addInt("duration2", UNIT_SECONDS); + builder.addInt("intervalTime", UNIT_SECONDS); builder.addUnknown(56); - builder.addInt("hrZoneExtreme2", "seconds"); - builder.addInt("hrZoneAnaerobic2", "seconds"); - builder.addInt("hrZoneAerobic2", "seconds"); - builder.addInt("hrZoneFatBurn2", "seconds"); - builder.addInt("hrZoneWarmUp2", "seconds"); + builder.addInt("hrZoneExtreme2", UNIT_SECONDS); + builder.addInt("hrZoneAnaerobic2", UNIT_SECONDS); + builder.addInt("hrZoneAerobic2", UNIT_SECONDS); + builder.addInt("hrZoneFatBurn2", UNIT_SECONDS); + builder.addInt("hrZoneWarmUp2", UNIT_SECONDS); builder.addUnknown(16); - builder.addShort("vitality_gain", ""); - builder.addShort("training_load2", ""); - builder.addShort("recovery_time2", "hours"); - + builder.addShort("vitality_gain", UNIT_NONE); + builder.addShort("training_load2", UNIT_NONE); + builder.addShort("recovery_time2", UNIT_HOURS); return builder.build(); } @@ -215,30 +250,31 @@ public class WorkoutSummaryParser extends XiaomiActivityParser implements Activi final XiaomiSimpleActivityParser.Builder builder = new XiaomiSimpleActivityParser.Builder(); builder.setHeaderSize(headerSize); - builder.addInt("startTime", "seconds"); - builder.addInt("endTime", "seconds"); - builder.addInt("activeSeconds", "seconds"); - builder.addInt("distanceMeters", "meters"); - builder.addInt("caloriesBurnt", "calories_unit"); - builder.addInt("maxPace", "seconds_m"); - builder.addInt("minPace", "seconds_m"); + 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.addInt(CALORIES_BURNT, UNIT_KCAL); + builder.addInt(PACE_MAX, UNIT_SECONDS_PER_M); + builder.addInt(PACE_MIN, UNIT_SECONDS_PER_M); builder.addUnknown(4); - builder.addInt("steps", "steps_unit"); + builder.addInt(STEPS, UNIT_STEPS); builder.addUnknown(2); // pace? - builder.addByte("averageHR", "bpm"); - builder.addByte("maxHR", "bpm"); - builder.addByte("minHR", "bpm"); + builder.addByte(HR_AVG, UNIT_BPM); + builder.addByte(HR_MAX, UNIT_BPM); + builder.addByte(HR_MIN, UNIT_BPM); + // FIXME identify field lengths to align with the header builder.addUnknown(20); builder.addFloat("recoveryValue", "recoveryValue"); builder.addUnknown(9); - builder.addByte("recoveryTime", "seconds"); + builder.addByte(RECOVERY_TIME, UNIT_SECONDS); builder.addUnknown(2); - builder.addInt("hrZoneExtreme", "seconds"); - builder.addInt("hrZoneAnaerobic", "seconds"); - builder.addInt("hrZoneAerobic", "seconds"); - builder.addInt("hrZoneFatBurn", "seconds"); - builder.addInt("hrZoneWarmUp", "seconds"); - builder.addInt("configured_time_goal", "seconds"); + 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.addInt("configured_time_goal", UNIT_SECONDS); return builder.build(); } @@ -258,41 +294,41 @@ public class WorkoutSummaryParser extends XiaomiActivityParser implements Activi final XiaomiSimpleActivityParser.Builder builder = new XiaomiSimpleActivityParser.Builder(); builder.setHeaderSize(headerSize); - builder.addShort("xiaomiActivityType", "xiaomiActivityType"); - builder.addInt("startTime", "seconds"); - builder.addInt("endTime", "seconds"); - builder.addInt("activeSeconds", "seconds"); + builder.addShort(XIAOMI_WORKOUT_TYPE, XIAOMI_WORKOUT_TYPE); + builder.addInt(TIME_START, UNIT_UNIX_EPOCH_SECONDS); + builder.addInt(TIME_END, UNIT_UNIX_EPOCH_SECONDS); + builder.addInt(ACTIVE_SECONDS, UNIT_SECONDS); builder.addUnknown(4); - builder.addInt("distanceMeters", "meters"); + builder.addInt(DISTANCE_METERS, UNIT_METERS); builder.addUnknown(2); - builder.addShort("caloriesBurnt", "calories_unit"); + builder.addShort(CALORIES_BURNT, UNIT_KCAL); builder.addUnknown(12); - builder.addInt("steps", "steps_unit"); + builder.addInt(STEPS, UNIT_STEPS); builder.addUnknown(2); - builder.addByte("averageHR", "bpm"); - builder.addByte("maxHR", "bpm"); - builder.addByte("minHR", "bpm"); + builder.addByte(HR_AVG, UNIT_BPM); + builder.addByte(HR_MAX, UNIT_BPM); + builder.addByte(HR_MIN, UNIT_BPM); builder.addUnknown(20); builder.addFloat("recoveryValue", "?"); builder.addUnknown(9); - builder.addByte("recoveryTime", "hours"); + builder.addByte(RECOVERY_TIME, UNIT_HOURS); builder.addUnknown(2); - builder.addInt("hrZoneExtreme", "seconds"); - builder.addInt("hrZoneAnaerobic", "seconds"); - builder.addInt("hrZoneAerobic", "seconds"); - builder.addInt("hrZoneFatBurn", "seconds"); - builder.addInt("hrZoneWarmUp", "seconds"); - builder.addInt("configuredTimeGoal", "seconds"); - builder.addShort("configuredCaloriesGoal", "calories_unit"); - builder.addInt("configuredDistanceGoal", "meters"); + 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.addInt("configuredTimeGoal", UNIT_SECONDS); + builder.addShort("configuredCaloriesGoal", UNIT_KCAL); + builder.addInt("configuredDistanceGoal", UNIT_METERS); builder.addUnknown(11); - builder.addShort("trainingLoad", ""); + builder.addShort(WORKOUT_LOAD, UNIT_NONE); // training load builder.addUnknown(24); - builder.addByte("averageHR2", "bpm"); - builder.addByte("maxHR2", "bpm"); - builder.addByte("minHR2", "bpm"); + builder.addByte("averageHR2", UNIT_BPM); + builder.addByte("maxHR2", UNIT_BPM); + builder.addByte("minHR2", UNIT_BPM); builder.addUnknown(2); - builder.addByte("averageCadence", "spm"); + builder.addByte(CADENCE_AVG, UNIT_SPM); return builder.build(); } @@ -312,19 +348,20 @@ public class WorkoutSummaryParser extends XiaomiActivityParser implements Activi final XiaomiSimpleActivityParser.Builder builder = new XiaomiSimpleActivityParser.Builder(); builder.setHeaderSize(headerSize); - builder.addShort("xiaomiWorkoutType", "xiaomiWorkoutType"); - builder.addInt("startTime", "seconds"); - builder.addInt("endTime", "seconds"); - builder.addInt("activeSeconds", "seconds"); + builder.addShort(XIAOMI_WORKOUT_TYPE, XIAOMI_WORKOUT_TYPE); + builder.addInt(TIME_START, UNIT_UNIX_EPOCH_SECONDS); + builder.addInt(TIME_END, UNIT_UNIX_EPOCH_SECONDS); + builder.addInt(ACTIVE_SECONDS, UNIT_SECONDS); builder.addUnknown(4); - builder.addInt("distanceMeters", "meters"); + builder.addInt(DISTANCE_METERS, UNIT_METERS); builder.addUnknown(2); - builder.addShort("caloriesBurnt", "calories_unit"); - builder.addUnknown(8); - builder.addFloat("maxSpeed", "km_h"); - builder.addByte("averageHR", "bpm"); - builder.addByte("maxHR", "bpm"); - builder.addByte("minHR", "bpm"); + builder.addShort(CALORIES_BURNT, UNIT_KCAL); + builder.addUnknown(4); + builder.addUnknown(4); + builder.addFloat(SPEED_MAX, UNIT_KMPH); + builder.addByte(HR_AVG, UNIT_BPM); + builder.addByte(HR_MAX, UNIT_BPM); + builder.addByte(HR_MIN, UNIT_BPM); 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 203171ede..ec5f21654 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 @@ -16,8 +16,14 @@ along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.impl; +import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.TIME_END; +import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.TIME_START; +import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_UNIX_EPOCH_SECONDS; + import org.json.JSONException; import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.nio.ByteBuffer; import java.util.ArrayList; @@ -26,8 +32,13 @@ import java.util.List; import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary; import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; +import nodomain.freeyourgadget.gadgetbridge.util.GB; public class XiaomiSimpleActivityParser { + private static final Logger LOG = LoggerFactory.getLogger(XiaomiSimpleActivityParser.class); + + public static final String XIAOMI_WORKOUT_TYPE = "xiaomiWorkoutType"; + private final int headerSize; private final List dataEntries; @@ -42,19 +53,35 @@ public class XiaomiSimpleActivityParser { final byte[] header = new byte[headerSize]; buf.get(header); - for (final XiaomiSimpleDataEntry dataEntry : dataEntries) { + LOG.debug("Header: {}", GB.hexdump(header)); + + for (int i = 0; i < dataEntries.size(); i++) { + final XiaomiSimpleDataEntry dataEntry = dataEntries.get(i); + final Number value = dataEntry.get(buf); if (value == null) { + LOG.debug("Skipping unknown field {}", i); continue; } - if (dataEntry.getKey().equals("endTime")) { - if (dataEntry.getUnit().equals("seconds")) { + // Each bit in the header marks whether the data is valid or not, in order of the fields + final boolean validData = (header[i / 8] & (1 << (7 - (i % 8)))) != 0; + // FIXME: We can't use the header before identifying the correct field lenggths for unknown fields + // or parsing gets out of sync with the header and we will potentially ignore valid data + //if (!validData) { + // LOG.debug("Ignoring non-valid data {}", i); + // continue; + //} + + if (dataEntry.getKey().equals(TIME_END)) { + if (dataEntry.getUnit().equals(UNIT_UNIX_EPOCH_SECONDS)) { summary.setEndTime(new Date(value.intValue() * 1000L)); } else { - throw new IllegalArgumentException("endTime should be in seconds"); + throw new IllegalArgumentException("endTime should be an unix epoch"); } - } if (dataEntry.getKey().equals("xiaomiWorkoutType")) { + } else if (dataEntry.getKey().equals(TIME_START)) { + // ignored + } else if (dataEntry.getKey().equals(XIAOMI_WORKOUT_TYPE)) { // TODO use XiaomiWorkoutType switch (value.intValue()) { case 2: