1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-11-28 21:06:50 +01:00

Extract hardcoded activity summary entries to constants

This commit is contained in:
José Rebelo 2024-01-06 13:54:06 +00:00
parent 4e54f8137d
commit ab894ae433
7 changed files with 381 additions and 200 deletions

View File

@ -17,6 +17,8 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. */ along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.activities; package nodomain.freeyourgadget.gadgetbridge.activities;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.*;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.ActivityNotFoundException; import android.content.ActivityNotFoundException;
import android.content.Context; import android.content.Context;
@ -86,7 +88,6 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryJsonSummary;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils; import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils; import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.SwipeEvents; import nodomain.freeyourgadget.gadgetbridge.util.SwipeEvents;
@ -464,13 +465,13 @@ public class ActivitySummaryDetail extends AbstractGBActivity {
if (!show_raw_data) { if (!show_raw_data) {
//special casing here + imperial units handling //special casing here + imperial units handling
switch (unit) { switch (unit) {
case "cm": case UNIT_CM:
if (units.equals(UNIT_IMPERIAL)) { if (units.equals(UNIT_IMPERIAL)) {
value = value * 0.0328084; value = value * 0.0328084;
unit = "ft"; unit = "ft";
} }
break; break;
case "meters_second": case UNIT_METERS_PER_SECOND:
if (units.equals(UNIT_IMPERIAL)) { if (units.equals(UNIT_IMPERIAL)) {
value = value * 2.236936D; value = value * 2.236936D;
unit = "mi_h"; unit = "mi_h";
@ -479,7 +480,7 @@ public class ActivitySummaryDetail extends AbstractGBActivity {
unit = "km_h"; unit = "km_h";
} }
break; break;
case "seconds_m": case UNIT_SECONDS_PER_M:
if (units.equals(UNIT_IMPERIAL)) { if (units.equals(UNIT_IMPERIAL)) {
value = value * (1609.344 / 60D); value = value * (1609.344 / 60D);
unit = "minutes_mi"; unit = "minutes_mi";
@ -488,7 +489,7 @@ public class ActivitySummaryDetail extends AbstractGBActivity {
unit = "minutes_km"; unit = "minutes_km";
} }
break; break;
case "seconds_km": case UNIT_SECONDS_PER_KM:
if (units.equals(UNIT_IMPERIAL)) { if (units.equals(UNIT_IMPERIAL)) {
value = value / 60D * 1.609344; value = value / 60D * 1.609344;
unit = "minutes_mi"; unit = "minutes_mi";
@ -497,7 +498,7 @@ public class ActivitySummaryDetail extends AbstractGBActivity {
unit = "minutes_km"; unit = "minutes_km";
} }
break; break;
case "meters": case UNIT_METERS:
if (units.equals(UNIT_IMPERIAL)) { if (units.equals(UNIT_IMPERIAL)) {
value = value * 3.28084D; value = value * 3.28084D;
unit = "ft"; unit = "ft";

View File

@ -16,6 +16,8 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. */ along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.huami; package nodomain.freeyourgadget.gadgetbridge.devices.huami;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.*;
import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.InvalidProtocolBufferException;
import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ArrayUtils;
@ -73,7 +75,7 @@ public class Huami2021ActivitySummaryParser extends HuamiActivitySummaryParser {
if (summaryProto.hasTime()) { if (summaryProto.hasTime()) {
int totalDuration = summaryProto.getTime().getTotalDuration(); int totalDuration = summaryProto.getTime().getTotalDuration();
summary.setEndTime(new Date(startTime.getTime() + totalDuration * 1000L)); 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 // TODO pause durations
} }
@ -82,93 +84,94 @@ public class Huami2021ActivitySummaryParser extends HuamiActivitySummaryParser {
summary.setBaseLatitude(summaryProto.getLocation().getBaseLatitude()); summary.setBaseLatitude(summaryProto.getLocation().getBaseLatitude());
summary.setBaseAltitude(summaryProto.getLocation().getBaseAltitude() / 2); summary.setBaseAltitude(summaryProto.getLocation().getBaseAltitude() / 2);
// TODO: Min/Max Latitude/Longitude // TODO: Min/Max Latitude/Longitude
addSummaryData("baseAltitude", summaryProto.getLocation().getBaseAltitude() / 2, "meters"); addSummaryData(ALTITUDE_BASE, summaryProto.getLocation().getBaseAltitude() / 2, UNIT_METERS);
} }
if (summaryProto.hasHeartRate()) { if (summaryProto.hasHeartRate()) {
addSummaryData("averageHR", summaryProto.getHeartRate().getAvg(), "bpm"); addSummaryData(HR_AVG, summaryProto.getHeartRate().getAvg(), UNIT_BPM);
addSummaryData("maxHR", summaryProto.getHeartRate().getMax(), "bpm"); addSummaryData(HR_MAX, summaryProto.getHeartRate().getMax(), UNIT_BPM);
addSummaryData("minHR", summaryProto.getHeartRate().getMin(), "bpm"); addSummaryData(HR_MIN, summaryProto.getHeartRate().getMin(), UNIT_BPM);
} }
if (summaryProto.hasSteps()) { if (summaryProto.hasSteps()) {
addSummaryData("maxCadence", summaryProto.getSteps().getMaxCadence() * 60, "spm"); addSummaryData(CADENCE_MAX, summaryProto.getSteps().getMaxCadence() * 60, UNIT_SPM);
addSummaryData("averageCadence", summaryProto.getSteps().getAvgCadence() * 60, "spm"); addSummaryData(CADENCE_AVG, summaryProto.getSteps().getAvgCadence() * 60, UNIT_SPM);
addSummaryData("averageStride", summaryProto.getSteps().getAvgStride(), "cm"); addSummaryData(STRIDE_AVG, summaryProto.getSteps().getAvgStride(), UNIT_CM);
addSummaryData("steps", summaryProto.getSteps().getSteps(), "steps_unit"); addSummaryData(STEPS, summaryProto.getSteps().getSteps(), UNIT_STEPS);
} }
if (summaryProto.hasDistance()) { if (summaryProto.hasDistance()) {
addSummaryData("distanceMeters", summaryProto.getDistance().getDistance(), "meters"); addSummaryData(DISTANCE_METERS, summaryProto.getDistance().getDistance(), UNIT_METERS);
} }
if (summaryProto.hasPace()) { if (summaryProto.hasPace()) {
addSummaryData("maxPace", summaryProto.getPace().getBest(), "seconds_m"); addSummaryData(PACE_MAX, summaryProto.getPace().getBest(), UNIT_SECONDS_PER_M);
addSummaryData("averageKMPaceSeconds", summaryProto.getPace().getAvg() * 1000, "seconds_km"); addSummaryData(PACE_AVG_SECONDS_KM, summaryProto.getPace().getAvg() * 1000, UNIT_SECONDS_PER_KM);
} }
if (summaryProto.hasCalories()) { if (summaryProto.hasCalories()) {
addSummaryData("caloriesBurnt", summaryProto.getCalories().getCalories(), "calories_unit"); addSummaryData(CALORIES_BURNT, summaryProto.getCalories().getCalories(), UNIT_KCAL);
} }
if (summaryProto.hasHeartRateZones()) { if (summaryProto.hasHeartRateZones()) {
// TODO hr zones bpm? // TODO hr zones bpm?
if (summaryProto.getHeartRateZones().getZoneTimeCount() == 6) { if (summaryProto.getHeartRateZones().getZoneTimeCount() == 6) {
addSummaryData("hrZoneNa", summaryProto.getHeartRateZones().getZoneTime(0), "seconds"); addSummaryData(HR_ZONE_NA, summaryProto.getHeartRateZones().getZoneTime(0), UNIT_SECONDS);
addSummaryData("hrZoneWarmUp", summaryProto.getHeartRateZones().getZoneTime(1), "seconds"); addSummaryData(HR_ZONE_WARM_UP, summaryProto.getHeartRateZones().getZoneTime(1), UNIT_SECONDS);
addSummaryData("hrZoneFatBurn", summaryProto.getHeartRateZones().getZoneTime(2), "seconds"); addSummaryData(HR_ZONE_FAT_BURN, summaryProto.getHeartRateZones().getZoneTime(2), UNIT_SECONDS);
addSummaryData("hrZoneAerobic", summaryProto.getHeartRateZones().getZoneTime(3), "seconds"); addSummaryData(HR_ZONE_AEROBIC, summaryProto.getHeartRateZones().getZoneTime(3), UNIT_SECONDS);
addSummaryData("hrZoneAnaerobic", summaryProto.getHeartRateZones().getZoneTime(4), "seconds"); addSummaryData(HR_ZONE_ANAEROBIC, summaryProto.getHeartRateZones().getZoneTime(4), UNIT_SECONDS);
addSummaryData("hrZoneExtreme", summaryProto.getHeartRateZones().getZoneTime(5), "seconds"); addSummaryData(HR_ZONE_EXTREME, summaryProto.getHeartRateZones().getZoneTime(5), UNIT_SECONDS);
} else { } else {
LOG.warn("Unexpected number of HR zones {}", summaryProto.getHeartRateZones().getZoneTimeCount()); LOG.warn("Unexpected number of HR zones {}", summaryProto.getHeartRateZones().getZoneTimeCount());
} }
} }
if (summaryProto.hasTrainingEffect()) { if (summaryProto.hasTrainingEffect()) {
addSummaryData("aerobicTrainingEffect", summaryProto.getTrainingEffect().getAerobicTrainingEffect(), ""); addSummaryData(TRAINING_EFFECT_AEROBIC, summaryProto.getTrainingEffect().getAerobicTrainingEffect(), UNIT_NONE);
addSummaryData("anaerobicTrainingEffect", summaryProto.getTrainingEffect().getAnaerobicTrainingEffect(), ""); addSummaryData(TRAINING_EFFECT_ANAEROBIC, summaryProto.getTrainingEffect().getAnaerobicTrainingEffect(), UNIT_NONE);
addSummaryData("currentWorkoutLoad", summaryProto.getTrainingEffect().getCurrentWorkoutLoad(), ""); addSummaryData(WORKOUT_LOAD, summaryProto.getTrainingEffect().getCurrentWorkoutLoad(), UNIT_NONE);
addSummaryData("maximumOxygenUptake", summaryProto.getTrainingEffect().getMaximumOxygenUptake(), "ml/kg/min"); addSummaryData(MAXIMUM_OXYGEN_UPTAKE, summaryProto.getTrainingEffect().getMaximumOxygenUptake(), UNIT_ML_KG_MIN);
} }
if (summaryProto.hasAltitude()) { if (summaryProto.hasAltitude()) {
addSummaryData("maxAltitude", summaryProto.getAltitude().getMaxAltitude() / 200, "meters"); addSummaryData(ALTITUDE_MAX, summaryProto.getAltitude().getMaxAltitude() / 200, UNIT_METERS);
addSummaryData("minAltitude", summaryProto.getAltitude().getMinAltitude() / 200, "meters"); addSummaryData(ALTITUDE_MIN, summaryProto.getAltitude().getMinAltitude() / 200, UNIT_METERS);
addSummaryData("averageAltitude", summaryProto.getAltitude().getAvgAltitude() / 200, "meters"); addSummaryData(ALTITUDE_AVG, summaryProto.getAltitude().getAvgAltitude() / 200, UNIT_METERS);
// TODO totalClimbing // TODO totalClimbing
addSummaryData("elevationGain", summaryProto.getAltitude().getElevationGain() / 100, "meters"); addSummaryData(ELEVATION_GAIN, summaryProto.getAltitude().getElevationGain() / 100, UNIT_METERS);
addSummaryData("elevationLoss", summaryProto.getAltitude().getElevationLoss() / 100, "meters"); addSummaryData(ELEVATION_LOSS, summaryProto.getAltitude().getElevationLoss() / 100, UNIT_METERS);
} }
if (summaryProto.hasElevation()) { if (summaryProto.hasElevation()) {
addSummaryData("ascentSeconds", summaryProto.getElevation().getUphillTime(), "seconds"); addSummaryData(ASCENT_SECONDS, summaryProto.getElevation().getUphillTime(), UNIT_SECONDS);
addSummaryData("descentSeconds", summaryProto.getElevation().getDownhillTime(), "seconds"); addSummaryData(DESCENT_SECONDS, summaryProto.getElevation().getDownhillTime(), UNIT_SECONDS);
} }
if (summaryProto.hasSwimmingData()) { if (summaryProto.hasSwimmingData()) {
addSummaryData("laps", summaryProto.getSwimmingData().getLaps(), "laps_unit"); addSummaryData(LAPS, summaryProto.getSwimmingData().getLaps(), UNIT_LAPS);
switch (summaryProto.getSwimmingData().getLaneLengthUnit()) { switch (summaryProto.getSwimmingData().getLaneLengthUnit()) {
case 0: case 0:
addSummaryData("laneLength", summaryProto.getSwimmingData().getLaneLength(), "meters"); addSummaryData(LANE_LENGTH, summaryProto.getSwimmingData().getLaneLength(), UNIT_METERS);
break; break;
case 1: case 1:
addSummaryData("laneLength", summaryProto.getSwimmingData().getLaneLength(), "yard"); addSummaryData(LANE_LENGTH, summaryProto.getSwimmingData().getLaneLength(), UNIT_YARD);
break; break;
} }
switch (summaryProto.getSwimmingData().getStyle()) { switch (summaryProto.getSwimmingData().getStyle()) {
// TODO i18n these
case 1: case 1:
addSummaryData("swimStyle", "breaststroke"); addSummaryData(SWIM_STYLE, "breaststroke");
break; break;
case 2: case 2:
addSummaryData("swimStyle", "freestyle"); addSummaryData(SWIM_STYLE, "freestyle");
break; break;
} }
addSummaryData("strokes", summaryProto.getSwimmingData().getStrokes(), "strokes_unit"); addSummaryData(STROKES, summaryProto.getSwimmingData().getStrokes(), UNIT_STROKES);
addSummaryData("avgStrokeRate", summaryProto.getSwimmingData().getAvgStrokeRate(), "strokes_minute"); addSummaryData(STROKE_RATE_AVG, summaryProto.getSwimmingData().getAvgStrokeRate(), UNIT_STROKES_PER_MINUTE);
addSummaryData("maxStrokeRate", summaryProto.getSwimmingData().getMaxStrokeRate(), "strokes_minute"); addSummaryData(STROKE_RATE_MAX, summaryProto.getSwimmingData().getMaxStrokeRate(), UNIT_STROKES_PER_MINUTE);
addSummaryData("averageStrokeDistance", summaryProto.getSwimmingData().getAvgDps(), "cm"); addSummaryData(STROKE_DISTANCE_AVG, summaryProto.getSwimmingData().getAvgDps(), UNIT_CM);
addSummaryData("swolfIndex", summaryProto.getSwimmingData().getSwolf(), ""); addSummaryData(SWOLF_INDEX, summaryProto.getSwimmingData().getSwolf(), UNIT_NONE);
} }
} }
} }

View File

@ -17,6 +17,8 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. */ along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.huami; package nodomain.freeyourgadget.gadgetbridge.devices.huami;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.*;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -273,9 +275,9 @@ public class HuamiActivitySummaryParser implements ActivitySummaryParser {
buffer.getInt(); // unknown probably flatDistance = buffer.getFloat(); buffer.getInt(); // unknown probably flatDistance = buffer.getFloat();
flatSeconds = buffer.getInt() / 1000; // ms? flatSeconds = buffer.getInt() / 1000; // ms?
addSummaryData("ascentSeconds", ascentSeconds, "seconds"); addSummaryData(ASCENT_SECONDS, ascentSeconds, UNIT_SECONDS);
addSummaryData("descentSeconds", descentSeconds, "seconds"); addSummaryData(DESCENT_SECONDS, descentSeconds, UNIT_SECONDS);
addSummaryData("flatSeconds", flatSeconds, "seconds"); addSummaryData(FLAT_SECONDS, flatSeconds, UNIT_SECONDS);
} }
averageHR = buffer.getShort(); averageHR = buffer.getShort();
@ -308,62 +310,62 @@ public class HuamiActivitySummaryParser implements ActivitySummaryParser {
// summary.setAveragePace(BLETypeConversions.toUnsigned(averagePace); // summary.setAveragePace(BLETypeConversions.toUnsigned(averagePace);
// summary.setAverageStride(BLETypeConversions.toUnsigned(averageStride); // summary.setAverageStride(BLETypeConversions.toUnsigned(averageStride);
addSummaryData("ascentSeconds", ascentSeconds, "seconds"); addSummaryData(ASCENT_SECONDS, ascentSeconds, UNIT_SECONDS);
addSummaryData("descentSeconds", descentSeconds, "seconds"); addSummaryData(DESCENT_SECONDS, descentSeconds, UNIT_SECONDS);
addSummaryData("flatSeconds", flatSeconds, "seconds"); addSummaryData(FLAT_SECONDS, flatSeconds, UNIT_SECONDS);
addSummaryData("ascentDistance", ascentDistance, "meters"); addSummaryData(ASCENT_DISTANCE, ascentDistance, UNIT_METERS);
addSummaryData("descentDistance", descentDistance, "meters"); addSummaryData(DESCENT_DISTANCE, descentDistance, UNIT_METERS);
addSummaryData("flatDistance", flatDistance, "meters"); addSummaryData(FLAT_DISTANCE, flatDistance, UNIT_METERS);
addSummaryData("distanceMeters", distanceMeters, "meters"); addSummaryData(DISTANCE_METERS, distanceMeters, UNIT_METERS);
// addSummaryData("distanceMeters2", distanceMeters2, "meters"); // addSummaryData("distanceMeters2", distanceMeters2, UNIT_METERS);
addSummaryData("ascentMeters", ascentMeters, "meters"); addSummaryData(ASCENT_METERS, ascentMeters, UNIT_METERS);
addSummaryData("descentMeters", descentMeters, "meters"); addSummaryData(DESCENT_METERS, descentMeters, UNIT_METERS);
if (maxAltitude != -100000) { if (maxAltitude != -100000) {
addSummaryData("maxAltitude", maxAltitude, "meters"); addSummaryData(ALTITUDE_MAX, maxAltitude, UNIT_METERS);
} }
if (minAltitude != 100000) { if (minAltitude != 100000) {
addSummaryData("minAltitude", minAltitude, "meters"); addSummaryData(ALTITUDE_MIN, minAltitude, UNIT_METERS);
} }
if (minAltitude != 100000) { if (minAltitude != 100000) {
addSummaryData("averageAltitude", averageAltitude, "meters"); addSummaryData(ALTITUDE_AVG, averageAltitude, UNIT_METERS);
} }
addSummaryData("steps", steps, "steps_unit"); addSummaryData(STEPS, steps, UNIT_STEPS);
addSummaryData("activeSeconds", activeSeconds, "seconds"); addSummaryData(ACTIVE_SECONDS, activeSeconds, UNIT_SECONDS);
addSummaryData("caloriesBurnt", caloriesBurnt, "calories_unit"); addSummaryData(CALORIES_BURNT, caloriesBurnt, UNIT_KCAL);
addSummaryData("maxSpeed", maxSpeed, "meters_second"); addSummaryData(SPEED_MAX, maxSpeed, UNIT_METERS_PER_SECOND);
addSummaryData("minSpeed", minSpeed, "meters_second"); addSummaryData(SPEED_MIN, minSpeed, UNIT_METERS_PER_SECOND);
addSummaryData("averageSpeed", averageSpeed, "meters_second"); addSummaryData(SPEED_AVG, averageSpeed, UNIT_METERS_PER_SECOND);
addSummaryData("maxCadence", maxCadence, "spm"); addSummaryData(CADENCE_MAX, maxCadence, UNIT_SPM);
addSummaryData("minCadence", minCadence, "spm"); addSummaryData(CADENCE_MIN, minCadence, UNIT_SPM);
addSummaryData("averageCadence", averageCadence, "spm"); addSummaryData(CADENCE_AVG, averageCadence, UNIT_SPM);
if (!(activityKind == ActivityKind.TYPE_ELLIPTICAL_TRAINER || if (!(activityKind == ActivityKind.TYPE_ELLIPTICAL_TRAINER ||
activityKind == ActivityKind.TYPE_JUMP_ROPING || activityKind == ActivityKind.TYPE_JUMP_ROPING ||
activityKind == ActivityKind.TYPE_EXERCISE || activityKind == ActivityKind.TYPE_EXERCISE ||
activityKind == ActivityKind.TYPE_YOGA || activityKind == ActivityKind.TYPE_YOGA ||
activityKind == ActivityKind.TYPE_INDOOR_CYCLING)) { activityKind == ActivityKind.TYPE_INDOOR_CYCLING)) {
addSummaryData("minPace", minPace, "seconds_m"); addSummaryData(PACE_MIN, minPace, UNIT_SECONDS_PER_M);
addSummaryData("maxPace", maxPace, "seconds_m"); addSummaryData(PACE_MAX, maxPace, UNIT_SECONDS_PER_M);
// addSummaryData("averagePace", averagePace, "seconds_m"); // addSummaryData("averagePace", averagePace, UNIT_SECONDS_PER_M);
} }
addSummaryData("totalStride", totalStride, "meters"); addSummaryData(STRIDE_TOTAL, totalStride, UNIT_METERS);
addSummaryData("averageHR", averageHR, "bpm"); addSummaryData(HR_AVG, averageHR, UNIT_BPM);
addSummaryData("maxHR", maxHR, "bpm"); addSummaryData(HR_MAX, maxHR, UNIT_BPM);
addSummaryData("minHR", minHR, "bpm"); addSummaryData(HR_MIN, minHR, UNIT_BPM);
addSummaryData("averageKMPaceSeconds", averageKMPaceSeconds, "seconds_km"); addSummaryData(PACE_AVG_SECONDS_KM, averageKMPaceSeconds, UNIT_SECONDS_PER_KM);
addSummaryData("averageStride", averageStride, "cm"); addSummaryData(STRIDE_AVG, averageStride, UNIT_CM);
addSummaryData("maxStride", maxStride, "cm"); addSummaryData(STRIDE_MAX, maxStride, UNIT_CM);
addSummaryData("minStride", minStride, "cm"); addSummaryData(STRIDE_MIN, minStride, UNIT_CM);
// addSummaryData("averageStride2", averageStride2, "cm"); // addSummaryData("averageStride2", averageStride2, UNIT_CM);
if (activityKind == ActivityKind.TYPE_SWIMMING || activityKind == ActivityKind.TYPE_SWIMMING_OPENWATER) { if (activityKind == ActivityKind.TYPE_SWIMMING || activityKind == ActivityKind.TYPE_SWIMMING_OPENWATER) {
addSummaryData("averageStrokeDistance", averageStrokeDistance, "meters"); addSummaryData(STROKE_DISTANCE_AVG, averageStrokeDistance, UNIT_METERS);
addSummaryData("averageStrokesPerSecond", averageStrokesPerSecond, "strokes_second"); addSummaryData(STROKE_AVG_PER_SECOND, averageStrokesPerSecond, UNIT_STROKES_PER_SECOND);
addSummaryData("averageLapPace", averageLapPace, "second"); addSummaryData(LAP_PACE_AVERAGE, averageLapPace, "second");
addSummaryData("strokes", strokes, "strokes"); addSummaryData(STROKES, strokes, "strokes");
addSummaryData("swolfIndex", swolfIndex, "swolf_index"); addSummaryData(SWOLF_INDEX, swolfIndex, "swolf_index");
String swimStyleName = "unknown"; // TODO: translate here or keep as string identifier here? String swimStyleName = "unknown"; // TODO: translate here or keep as string identifier here?
switch (swimStyle) { switch (swimStyle) {
case 1: case 1:
@ -379,8 +381,8 @@ public class HuamiActivitySummaryParser implements ActivitySummaryParser {
swimStyleName = "medley"; swimStyleName = "medley";
break; break;
} }
addSummaryData("swimStyle", swimStyleName); addSummaryData(SWIM_STYLE, swimStyleName);
addSummaryData("laps", laps, "laps"); addSummaryData(LAPS, laps, "laps");
} }
} }

View File

@ -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 <http://www.gnu.org/licenses/>. */
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";
}

View File

@ -1,5 +1,7 @@
package nodomain.freeyourgadget.gadgetbridge.model; package nodomain.freeyourgadget.gadgetbridge.model;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.*;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
@ -153,35 +155,35 @@ public class ActivitySummaryJsonSummary {
private JSONObject createActivitySummaryGroups(){ private JSONObject createActivitySummaryGroups(){
final Map<String, List<String>> groupDefinitions = new HashMap<String, List<String>>() {{ final Map<String, List<String>> groupDefinitions = new HashMap<String, List<String>>() {{
put("Strokes", Arrays.asList( put("Strokes", Arrays.asList(
"averageStrokeDistance", "averageStrokesPerSecond", "strokes", STROKE_DISTANCE_AVG, STROKE_AVG_PER_SECOND, STROKES,
"avgStrokeRate", "maxStrokeRate" STROKE_RATE_AVG, STROKE_RATE_MAX
)); ));
put("Swimming", Arrays.asList( put("Swimming", Arrays.asList(
"swolfIndex", "swimStyle" SWOLF_INDEX, SWIM_STYLE
)); ));
put("Elevation", Arrays.asList( put("Elevation", Arrays.asList(
"ascentMeters", "descentMeters", "maxAltitude", "minAltitude", "averageAltitude", ASCENT_METERS, DESCENT_METERS, ALTITUDE_MAX, ALTITUDE_MIN, ALTITUDE_AVG,
"baseAltitude", "ascentSeconds", "descentSeconds", "flatSeconds", "ascentDistance", ALTITUDE_BASE, ASCENT_SECONDS, DESCENT_SECONDS, FLAT_SECONDS, ASCENT_DISTANCE,
"descentDistance", "flatDistance", "elevationGain", "elevationLoss" DESCENT_DISTANCE, FLAT_DISTANCE, ELEVATION_GAIN, ELEVATION_LOSS
)); ));
put("Speed", Arrays.asList( put("Speed", Arrays.asList(
"averageSpeed", "maxSpeed", "minSpeed", "averageKMPaceSeconds", "minPace", SPEED_AVG, SPEED_MAX, SPEED_MIN, PACE_AVG_SECONDS_KM, PACE_MIN,
"maxPace", "averageSpeed2", "averageCadence", "maxCadence", "minCadence" PACE_MAX, "averageSpeed2", CADENCE_AVG, CADENCE_MAX, CADENCE_MIN
)); ));
put("Activity", Arrays.asList( put("Activity", Arrays.asList(
"distanceMeters", "steps", "activeSeconds", "caloriesBurnt", "totalStride", DISTANCE_METERS, STEPS, ACTIVE_SECONDS, CALORIES_BURNT, STRIDE_TOTAL,
"averageHR", "maxHR", "minHR", "averageStride", "maxStride", "minStride" HR_AVG, HR_MAX, HR_MIN, STRIDE_AVG, STRIDE_MAX, STRIDE_MIN
)); ));
put("HeartRateZones", Arrays.asList( put("HeartRateZones", Arrays.asList(
"hrZoneNa", "hrZoneWarmUp", "hrZoneFatBurn", "hrZoneAerobic", "hrZoneAnaerobic", HR_ZONE_NA, HR_ZONE_WARM_UP, HR_ZONE_FAT_BURN, HR_ZONE_AEROBIC, HR_ZONE_ANAEROBIC,
"hrZoneExtreme" HR_ZONE_EXTREME
)); ));
put("TrainingEffect", Arrays.asList( put("TrainingEffect", Arrays.asList(
"aerobicTrainingEffect", "anaerobicTrainingEffect", "currentWorkoutLoad", TRAINING_EFFECT_AEROBIC, TRAINING_EFFECT_ANAEROBIC, WORKOUT_LOAD,
"maximumOxygenUptake" MAXIMUM_OXYGEN_UPTAKE
)); ));
put("laps", Arrays.asList( put("laps", Arrays.asList(
"averageLapPace", "laps", "laneLength" LAP_PACE_AVERAGE, LAPS, LANE_LENGTH
)); ));
}}; }};

View File

@ -16,13 +16,46 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. */ along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.impl; 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 android.widget.Toast;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ArrayUtils;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -152,50 +185,52 @@ public class WorkoutSummaryParser extends XiaomiActivityParser implements Activi
final XiaomiSimpleActivityParser.Builder builder = new XiaomiSimpleActivityParser.Builder(); final XiaomiSimpleActivityParser.Builder builder = new XiaomiSimpleActivityParser.Builder();
builder.setHeaderSize(headerSize); builder.setHeaderSize(headerSize);
builder.addInt("startTime", "seconds"); builder.addInt(TIME_START, UNIT_UNIX_EPOCH_SECONDS);
builder.addInt("endTime", "seconds"); builder.addInt(TIME_END, UNIT_UNIX_EPOCH_SECONDS);
builder.addInt("activeSeconds", "seconds"); builder.addInt(ACTIVE_SECONDS, UNIT_SECONDS);
builder.addUnknown(4); builder.addUnknown(4);
builder.addShort("caloriesBurnt", "calories_unit"); builder.addShort(CALORIES_BURNT, UNIT_KCAL);
builder.addUnknown(4); builder.addUnknown(4);
builder.addByte("averageHR", "bpm"); builder.addByte(HR_AVG, UNIT_BPM);
builder.addByte("maxHR", "bpm"); builder.addByte(HR_MAX, UNIT_BPM);
builder.addByte("minHR", "bpm"); builder.addByte(HR_MIN, UNIT_BPM);
builder.addFloat("aerobicTrainingEffect", ""); 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.addUnknown(2);
builder.addShort("recoveryTime", "hours"); builder.addUnknown(4);
builder.addInt("hrZoneExtreme", "seconds"); builder.addFloat(TRAINING_EFFECT_ANAEROBIC, UNIT_NONE);
builder.addInt("hrZoneAnaerobic", "seconds"); // FIXME identify field lengths to align with the header
builder.addInt("hrZoneAerobic", "seconds");
builder.addInt("hrZoneFatBurn", "seconds");
builder.addInt("hrZoneWarmUp", "seconds");
builder.addUnknown(6);
builder.addFloat("anaerobicTrainingEffect", "");
builder.addUnknown(3); builder.addUnknown(3);
builder.addInt("configuredTimeGoal", "seconds"); builder.addInt("configuredTimeGoal", UNIT_SECONDS);
builder.addShort("configuredCaloriesGoal", "calories_unit"); builder.addShort("configuredCaloriesGoal", UNIT_KCAL);
builder.addShort("maximumCaloriesGoal", "calories_unit"); // TODO: mhm? builder.addShort("maximumCaloriesGoal", UNIT_KCAL); // TODO: mhm?
builder.addUnknown(28); builder.addUnknown(28);
builder.addShort("trainingLoad", ""); builder.addShort(WORKOUT_LOAD, UNIT_NONE); // training load
builder.addUnknown(24); builder.addUnknown(24);
builder.addByte("configuredSets", ""); builder.addByte("configuredSets", UNIT_NONE);
builder.addUnknown(13); builder.addUnknown(13);
builder.addInt("startTime2", "seconds"); builder.addInt("startTime2", UNIT_SECONDS);
builder.addInt("endTime2", "seconds"); builder.addInt("endTime2", UNIT_SECONDS);
builder.addInt("goal", ""); // TODO match against goalType builder.addInt("goal", UNIT_NONE); // TODO match against goalType
builder.addInt("duration2", "seconds"); builder.addInt("duration2", UNIT_SECONDS);
builder.addInt("intervalTime", "seconds"); builder.addInt("intervalTime", UNIT_SECONDS);
builder.addUnknown(56); builder.addUnknown(56);
builder.addInt("hrZoneExtreme2", "seconds"); builder.addInt("hrZoneExtreme2", UNIT_SECONDS);
builder.addInt("hrZoneAnaerobic2", "seconds"); builder.addInt("hrZoneAnaerobic2", UNIT_SECONDS);
builder.addInt("hrZoneAerobic2", "seconds"); builder.addInt("hrZoneAerobic2", UNIT_SECONDS);
builder.addInt("hrZoneFatBurn2", "seconds"); builder.addInt("hrZoneFatBurn2", UNIT_SECONDS);
builder.addInt("hrZoneWarmUp2", "seconds"); builder.addInt("hrZoneWarmUp2", UNIT_SECONDS);
builder.addUnknown(16); builder.addUnknown(16);
builder.addShort("vitality_gain", ""); builder.addShort("vitality_gain", UNIT_NONE);
builder.addShort("training_load2", ""); builder.addShort("training_load2", UNIT_NONE);
builder.addShort("recovery_time2", "hours"); builder.addShort("recovery_time2", UNIT_HOURS);
return builder.build(); return builder.build();
} }
@ -215,30 +250,31 @@ public class WorkoutSummaryParser extends XiaomiActivityParser implements Activi
final XiaomiSimpleActivityParser.Builder builder = new XiaomiSimpleActivityParser.Builder(); final XiaomiSimpleActivityParser.Builder builder = new XiaomiSimpleActivityParser.Builder();
builder.setHeaderSize(headerSize); builder.setHeaderSize(headerSize);
builder.addInt("startTime", "seconds"); builder.addInt(TIME_START, UNIT_UNIX_EPOCH_SECONDS);
builder.addInt("endTime", "seconds"); builder.addInt(TIME_END, UNIT_UNIX_EPOCH_SECONDS);
builder.addInt("activeSeconds", "seconds"); builder.addInt(ACTIVE_SECONDS, UNIT_SECONDS);
builder.addInt("distanceMeters", "meters"); builder.addInt(DISTANCE_METERS, UNIT_METERS);
builder.addInt("caloriesBurnt", "calories_unit"); builder.addInt(CALORIES_BURNT, UNIT_KCAL);
builder.addInt("maxPace", "seconds_m"); builder.addInt(PACE_MAX, UNIT_SECONDS_PER_M);
builder.addInt("minPace", "seconds_m"); builder.addInt(PACE_MIN, UNIT_SECONDS_PER_M);
builder.addUnknown(4); builder.addUnknown(4);
builder.addInt("steps", "steps_unit"); builder.addInt(STEPS, UNIT_STEPS);
builder.addUnknown(2); // pace? builder.addUnknown(2); // pace?
builder.addByte("averageHR", "bpm"); builder.addByte(HR_AVG, UNIT_BPM);
builder.addByte("maxHR", "bpm"); builder.addByte(HR_MAX, UNIT_BPM);
builder.addByte("minHR", "bpm"); builder.addByte(HR_MIN, UNIT_BPM);
// FIXME identify field lengths to align with the header
builder.addUnknown(20); builder.addUnknown(20);
builder.addFloat("recoveryValue", "recoveryValue"); builder.addFloat("recoveryValue", "recoveryValue");
builder.addUnknown(9); builder.addUnknown(9);
builder.addByte("recoveryTime", "seconds"); builder.addByte(RECOVERY_TIME, UNIT_SECONDS);
builder.addUnknown(2); builder.addUnknown(2);
builder.addInt("hrZoneExtreme", "seconds"); builder.addInt(HR_ZONE_EXTREME, UNIT_SECONDS);
builder.addInt("hrZoneAnaerobic", "seconds"); builder.addInt(HR_ZONE_ANAEROBIC, UNIT_SECONDS);
builder.addInt("hrZoneAerobic", "seconds"); builder.addInt(HR_ZONE_AEROBIC, UNIT_SECONDS);
builder.addInt("hrZoneFatBurn", "seconds"); builder.addInt(HR_ZONE_FAT_BURN, UNIT_SECONDS);
builder.addInt("hrZoneWarmUp", "seconds"); builder.addInt(HR_ZONE_WARM_UP, UNIT_SECONDS);
builder.addInt("configured_time_goal", "seconds"); builder.addInt("configured_time_goal", UNIT_SECONDS);
return builder.build(); return builder.build();
} }
@ -258,41 +294,41 @@ public class WorkoutSummaryParser extends XiaomiActivityParser implements Activi
final XiaomiSimpleActivityParser.Builder builder = new XiaomiSimpleActivityParser.Builder(); final XiaomiSimpleActivityParser.Builder builder = new XiaomiSimpleActivityParser.Builder();
builder.setHeaderSize(headerSize); builder.setHeaderSize(headerSize);
builder.addShort("xiaomiActivityType", "xiaomiActivityType"); builder.addShort(XIAOMI_WORKOUT_TYPE, XIAOMI_WORKOUT_TYPE);
builder.addInt("startTime", "seconds"); builder.addInt(TIME_START, UNIT_UNIX_EPOCH_SECONDS);
builder.addInt("endTime", "seconds"); builder.addInt(TIME_END, UNIT_UNIX_EPOCH_SECONDS);
builder.addInt("activeSeconds", "seconds"); builder.addInt(ACTIVE_SECONDS, UNIT_SECONDS);
builder.addUnknown(4); builder.addUnknown(4);
builder.addInt("distanceMeters", "meters"); builder.addInt(DISTANCE_METERS, UNIT_METERS);
builder.addUnknown(2); builder.addUnknown(2);
builder.addShort("caloriesBurnt", "calories_unit"); builder.addShort(CALORIES_BURNT, UNIT_KCAL);
builder.addUnknown(12); builder.addUnknown(12);
builder.addInt("steps", "steps_unit"); builder.addInt(STEPS, UNIT_STEPS);
builder.addUnknown(2); builder.addUnknown(2);
builder.addByte("averageHR", "bpm"); builder.addByte(HR_AVG, UNIT_BPM);
builder.addByte("maxHR", "bpm"); builder.addByte(HR_MAX, UNIT_BPM);
builder.addByte("minHR", "bpm"); builder.addByte(HR_MIN, UNIT_BPM);
builder.addUnknown(20); builder.addUnknown(20);
builder.addFloat("recoveryValue", "?"); builder.addFloat("recoveryValue", "?");
builder.addUnknown(9); builder.addUnknown(9);
builder.addByte("recoveryTime", "hours"); builder.addByte(RECOVERY_TIME, UNIT_HOURS);
builder.addUnknown(2); builder.addUnknown(2);
builder.addInt("hrZoneExtreme", "seconds"); builder.addInt(HR_ZONE_EXTREME, UNIT_SECONDS);
builder.addInt("hrZoneAnaerobic", "seconds"); builder.addInt(HR_ZONE_ANAEROBIC, UNIT_SECONDS);
builder.addInt("hrZoneAerobic", "seconds"); builder.addInt(HR_ZONE_AEROBIC, UNIT_SECONDS);
builder.addInt("hrZoneFatBurn", "seconds"); builder.addInt(HR_ZONE_FAT_BURN, UNIT_SECONDS);
builder.addInt("hrZoneWarmUp", "seconds"); builder.addInt(HR_ZONE_WARM_UP, UNIT_SECONDS);
builder.addInt("configuredTimeGoal", "seconds"); builder.addInt("configuredTimeGoal", UNIT_SECONDS);
builder.addShort("configuredCaloriesGoal", "calories_unit"); builder.addShort("configuredCaloriesGoal", UNIT_KCAL);
builder.addInt("configuredDistanceGoal", "meters"); builder.addInt("configuredDistanceGoal", UNIT_METERS);
builder.addUnknown(11); builder.addUnknown(11);
builder.addShort("trainingLoad", ""); builder.addShort(WORKOUT_LOAD, UNIT_NONE); // training load
builder.addUnknown(24); builder.addUnknown(24);
builder.addByte("averageHR2", "bpm"); builder.addByte("averageHR2", UNIT_BPM);
builder.addByte("maxHR2", "bpm"); builder.addByte("maxHR2", UNIT_BPM);
builder.addByte("minHR2", "bpm"); builder.addByte("minHR2", UNIT_BPM);
builder.addUnknown(2); builder.addUnknown(2);
builder.addByte("averageCadence", "spm"); builder.addByte(CADENCE_AVG, UNIT_SPM);
return builder.build(); return builder.build();
} }
@ -312,19 +348,20 @@ public class WorkoutSummaryParser extends XiaomiActivityParser implements Activi
final XiaomiSimpleActivityParser.Builder builder = new XiaomiSimpleActivityParser.Builder(); final XiaomiSimpleActivityParser.Builder builder = new XiaomiSimpleActivityParser.Builder();
builder.setHeaderSize(headerSize); builder.setHeaderSize(headerSize);
builder.addShort("xiaomiWorkoutType", "xiaomiWorkoutType"); builder.addShort(XIAOMI_WORKOUT_TYPE, XIAOMI_WORKOUT_TYPE);
builder.addInt("startTime", "seconds"); builder.addInt(TIME_START, UNIT_UNIX_EPOCH_SECONDS);
builder.addInt("endTime", "seconds"); builder.addInt(TIME_END, UNIT_UNIX_EPOCH_SECONDS);
builder.addInt("activeSeconds", "seconds"); builder.addInt(ACTIVE_SECONDS, UNIT_SECONDS);
builder.addUnknown(4); builder.addUnknown(4);
builder.addInt("distanceMeters", "meters"); builder.addInt(DISTANCE_METERS, UNIT_METERS);
builder.addUnknown(2); builder.addUnknown(2);
builder.addShort("caloriesBurnt", "calories_unit"); builder.addShort(CALORIES_BURNT, UNIT_KCAL);
builder.addUnknown(8); builder.addUnknown(4);
builder.addFloat("maxSpeed", "km_h"); builder.addUnknown(4);
builder.addByte("averageHR", "bpm"); builder.addFloat(SPEED_MAX, UNIT_KMPH);
builder.addByte("maxHR", "bpm"); builder.addByte(HR_AVG, UNIT_BPM);
builder.addByte("minHR", "bpm"); builder.addByte(HR_MAX, UNIT_BPM);
builder.addByte(HR_MIN, UNIT_BPM);
return builder.build(); return builder.build();
} }

View File

@ -16,8 +16,14 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. */ along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.impl; 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.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
@ -26,8 +32,13 @@ import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary; import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class XiaomiSimpleActivityParser { 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 int headerSize;
private final List<XiaomiSimpleDataEntry> dataEntries; private final List<XiaomiSimpleDataEntry> dataEntries;
@ -42,19 +53,35 @@ public class XiaomiSimpleActivityParser {
final byte[] header = new byte[headerSize]; final byte[] header = new byte[headerSize];
buf.get(header); 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); final Number value = dataEntry.get(buf);
if (value == null) { if (value == null) {
LOG.debug("Skipping unknown field {}", i);
continue; continue;
} }
if (dataEntry.getKey().equals("endTime")) { // Each bit in the header marks whether the data is valid or not, in order of the fields
if (dataEntry.getUnit().equals("seconds")) { 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)); summary.setEndTime(new Date(value.intValue() * 1000L));
} else { } 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 // TODO use XiaomiWorkoutType
switch (value.intValue()) { switch (value.intValue()) {
case 2: case 2: