Garmin: extend support for non-GPS activities

- chart elevation even if the activity has no GPS recording (e.g. treadmill and floor climbing)
- added distance chart
- added stride chart
- added step length chart
- added body energy chart - 24/7 energy level
- added stamina chart - activity specific endurance capacity
- added N2 load (Nitrogen) chart for diving
- added CNS toxicity (Central Nervous System) chart for diving
- added oxygen toxicity summary for diving
- added surface interval summary for diving
- adjusted dive summary layout
This commit is contained in:
Thomas Kuehne
2026-04-11 01:43:45 +02:00
committed by José Rebelo
parent 5b2f982509
commit bde914fad2
4 changed files with 80 additions and 58 deletions
@@ -603,6 +603,12 @@ public class GarminWorkoutParser implements ActivitySummaryParser {
summaryData.add(TRAINING_LOAD, safeRound(session.getTrainingLoadPeak()), UNIT_NONE);
if (diveSummary != null) {
if (diveSummary.getDiveNumber() != null) {
summaryData.add(DIVE_NUMBER, diveSummary.getDiveNumber(), UNIT_NONE);
}
if (diveSummary.getBottomTime() != null) {
summaryData.add(BOTTOM_TIME, diveSummary.getBottomTime(), UNIT_SECONDS);
}
if (diveSummary.getAvgDepth() != null) {
summaryData.add(AVG_DEPTH, diveSummary.getAvgDepth(), UNIT_METERS);
}
@@ -610,22 +616,22 @@ public class GarminWorkoutParser implements ActivitySummaryParser {
summaryData.add(MAX_DEPTH, diveSummary.getMaxDepth(), UNIT_METERS);
}
if (diveSummary.getStartCns() != null) {
summaryData.add(START_CNS, diveSummary.getStartCns(), UNIT_PERCENTAGE);
summaryData.add(START_CNS, diveSummary.getStartCns(), UNIT_PERCENTAGE, true);
}
if (diveSummary.getEndCns() != null) {
summaryData.add(END_CNS, diveSummary.getEndCns(), UNIT_PERCENTAGE);
}
if (diveSummary.getStartN2() != null) {
summaryData.add(START_N2, diveSummary.getStartN2(), UNIT_PERCENTAGE);
summaryData.add(START_N2, diveSummary.getStartN2(), UNIT_PERCENTAGE, true);
}
if (diveSummary.getEndN2() != null) {
summaryData.add(END_N2, diveSummary.getEndN2(), UNIT_PERCENTAGE);
}
if (diveSummary.getDiveNumber() != null) {
summaryData.add(DIVE_NUMBER, diveSummary.getDiveNumber(), UNIT_NONE);
if (diveSummary.getO2Toxicity() != null) {
summaryData.add(OXYGEN_TOXICITY, diveSummary.getO2Toxicity(), UNIT_OXYGEN_TOXICITY_UNITs);
}
if (diveSummary.getBottomTime() != null) {
summaryData.add(BOTTOM_TIME, diveSummary.getBottomTime(), UNIT_SECONDS);
if (diveSummary.getSurfaceInterval() != null) {
summaryData.add(SURFACE_INTERVAL, diveSummary.getSurfaceInterval(), UNIT_SECONDS);
}
}
summaryData.add(TEMPERATURE_MIN, session.getMinTemperature(), UNIT_CELSIUS);
@@ -597,6 +597,8 @@ public class NativeFITMessage {
new FieldDefinitionPrimitive(127, BaseType.SINT32, "ascent_rate", 1000, 0), // m/s
new FieldDefinitionPrimitive(129, BaseType.UINT8, "po2", 100, 0), // %
new FieldDefinitionPrimitive(136, BaseType.UINT8, "wrist_heart_rate"),
new FieldDefinitionPrimitive(137, BaseType.UINT8, "stamina_potential"), // %
new FieldDefinitionPrimitive(138, BaseType.UINT8, "stamina"), // %
new FieldDefinitionPrimitive(139, BaseType.UINT16, "core_temperature", 100, 0), // °C
new FieldDefinitionPrimitive(143, BaseType.UINT8, "body_battery"),
new FieldDefinitionPrimitive(253, BaseType.UINT32, "timestamp", FieldDefinitionFactory.FIELD.TIMESTAMP)
@@ -18,10 +18,7 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages
import androidx.annotation.Nullable;
import java.util.Date;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityPoint;
import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FitRecordDataBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
@@ -458,6 +455,16 @@ public class FitRecord extends RecordData {
return getFieldByNumber(136, Integer.class);
}
@Nullable
public Integer getStaminaPotential() {
return getFieldByNumber(137, Integer.class);
}
@Nullable
public Integer getStamina() {
return getFieldByNumber(138, Integer.class);
}
@Nullable
public Float getCoreTemperature() {
return getFieldByNumber(139, Float.class);
@@ -896,6 +903,16 @@ public class FitRecord extends RecordData {
return this;
}
public Builder setStaminaPotential(final Integer value) {
setFieldByNumber(137, value);
return this;
}
public Builder setStamina(final Integer value) {
setFieldByNumber(138, value);
return this;
}
public Builder setCoreTemperature(final Float value) {
setFieldByNumber(139, value);
return this;
@@ -925,37 +942,43 @@ public class FitRecord extends RecordData {
// manual changes below
public ActivityPoint toActivityPoint() {
final ActivityPoint activityPoint = new ActivityPoint();
activityPoint.setTime(new Date(getComputedTimestamp() * 1000L));
if (getLatitude() != null && getLongitude() != null) {
activityPoint.setLocation(new GPSCoordinate(
getLongitude(),
getLatitude(),
getEnhancedAltitude() != null ? getEnhancedAltitude() : GPSCoordinate.UNKNOWN_ALTITUDE
));
}
if (getHeartRate() != null) {
activityPoint.setHeartRate(getHeartRate());
}
if (getEnhancedSpeed() != null) {
activityPoint.setSpeed(getEnhancedSpeed().floatValue());
}
if (getCadence() != null) {
activityPoint.setCadence(getCadence());
}
if (getPower() != null) {
activityPoint.setPower(getPower());
}
if (getEnhancedRespirationRate() != null) {
activityPoint.setRespiratoryRate(getEnhancedRespirationRate());
}
if (getTemperature() != null) {
activityPoint.setTemperature(getTemperature());
}
if (getDepth() != null) {
activityPoint.setDepth(getDepth());
final ActivityPoint.Builder builder = new ActivityPoint.Builder(getComputedTimestamp() * 1000L);
builder.setBodyEnergy(getBodyBattery());
builder.setCadence(getCadence());
builder.setCnsToxicity(getCnsLoad());
builder.setDepth(getDepth());
builder.setDistance(getDistance());
builder.setHdop(getGpsAccuracy());
builder.setHeartRate(getHeartRate());
builder.setLatitude(getLatitude());
builder.setLongitude(getLongitude());
builder.setN2Load(getN2Load());
builder.setPower(getPower());
builder.setStamina(getStamina());
builder.setStride(getStepLength());
builder.setTemperature(getTemperature());
final Double enhancedAltitude = getEnhancedAltitude();
if (enhancedAltitude == null) {
builder.setAltitude(getAltitude());
} else {
builder.setAltitude(enhancedAltitude);
}
return activityPoint;
final Float enhancedRespirationRate = getEnhancedRespirationRate();
if (enhancedRespirationRate == null) {
builder.setRespiratoryRate(getRespirationRate());
} else {
builder.setRespiratoryRate(enhancedRespirationRate);
}
final Double enhancedSpeed = getEnhancedSpeed();
if (enhancedSpeed == null) {
builder.setSpeed(getSpeed());
} else {
builder.setSpeed(enhancedSpeed.floatValue());
}
return builder.build();
}
}
@@ -1,4 +1,4 @@
/* Copyright (C) 2023-2025 José Rebelo, Thomas Kuehne
/* Copyright (C) 2023-2026 José Rebelo, Thomas Kuehne
This file is part of Gadgetbridge.
@@ -124,24 +124,15 @@ public class GpxTrackPoint extends GPSCoordinate {
}
public ActivityPoint toActivityPoint() {
final ActivityPoint activityPoint = new ActivityPoint();
activityPoint.setTime(time);
activityPoint.setLocation(this);
activityPoint.setHeartRate(heartRate);
if (!Float.isNaN(speed)) {
activityPoint.setSpeed(speed);
}
activityPoint.setCadence(cadence);
if (!Double.isNaN(depth)) {
activityPoint.setDepth(depth);
}
if (!Double.isNaN(temperature)) {
activityPoint.setTemperature(temperature);
}
if (description != null && description.length() > 0) {
activityPoint.setDescription(description);
}
final ActivityPoint.Builder builder = new ActivityPoint.Builder(time);
builder.setLocation(this);
builder.setHeartRate(heartRate);
builder.setSpeed(speed);
builder.setCadence(cadence);
builder.setDepth(depth);
builder.setTemperature(temperature);
builder.setDescription(description);
return activityPoint;
return builder.build();
}
}