extend support for non-GPS activities

This enhances the general GB code _without_ the device specific adjustments required to actually display values:
- 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
This commit is contained in:
Thomas Kuehne
2026-04-11 01:43:45 +02:00
committed by José Rebelo
parent bad2d6f842
commit 9344940260
7 changed files with 758 additions and 117 deletions
@@ -1,3 +1,19 @@
/* Copyright (C) 2025-2026 José Rebelo, a0z, Me7c7, punchdeerflyscorpion, Thomas Kuehne
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 <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.activities.workouts.charts;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_BPM;
@@ -8,6 +24,8 @@ import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_METERS_PER_SECOND;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_MINUTES_PER_100_METERS;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_MINUTES_PER_KM;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_MM;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_PERCENTAGE;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_SECONDS_PER_100_METERS;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_SECONDS_PER_KM;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_SPM;
@@ -48,17 +66,39 @@ public class DefaultWorkoutCharts {
final ActivityKind.CycleUnit cycleUnit = ActivityKind.getCycleUnit(activityKind);
final List<WorkoutChart> charts = new LinkedList<>();
final TimestampTranslation tsTranslation = new TimestampTranslation();
final List<Entry> heartRateDataPoints = new ArrayList<>();
final List<Entry> speedDataPoints = new ArrayList<>();
final List<Entry> cadenceDataPoints = new ArrayList<>();
final List<Entry> elevationDataPoints = new ArrayList<>();
final List<Entry> powerDataPoints = new ArrayList<>();
final List<Entry> respiratoryRatePoints = new ArrayList<>();
final List<Entry> temperatureDataPoints = new ArrayList<>();
final List<Entry> depthDataPoints = new ArrayList<>();
final int initalCapacity = activityPoints.size();
final List<Entry> heartRateDataPoints = new ArrayList<>(initalCapacity);
final List<Entry> speedDataPoints = new ArrayList<>(initalCapacity);
final List<Entry> cadenceDataPoints = new ArrayList<>(initalCapacity);
final List<Entry> elevationDataPoints = new ArrayList<>(initalCapacity);
final List<Entry> powerDataPoints = new ArrayList<>(initalCapacity);
final List<Entry> respiratoryRatePoints = new ArrayList<>(initalCapacity);
final List<Entry> temperatureDataPoints = new ArrayList<>(initalCapacity);
final List<Entry> depthDataPoints = new ArrayList<>(initalCapacity);
final List<Entry> distancePoints = new ArrayList<>(initalCapacity);
final List<Entry> stridePoints = new ArrayList<>(initalCapacity);
final List<Entry> staminaPoints = new ArrayList<>(initalCapacity);
final List<Entry> bodyEnergyPoints = new ArrayList<>(initalCapacity);
final List<Entry> stepLengthPoints = new ArrayList<>(initalCapacity);
final List<Entry> n2LoadPoints = new ArrayList<>(initalCapacity);
final List<Entry> cnsToxicityPoints = new ArrayList<>(initalCapacity);
// some activities / devices provide all points with zero values
boolean hasSpeedValues = false;
boolean hasCadenceValues = false;
boolean hasElevationValues = false;
boolean hasPowerValues = false;
boolean hasRespiratoryRateValues = false;
boolean hasTemperatureValues = false;
boolean hasDepthValues = false;
boolean hasDistanceValues = false;
boolean hasStrideValues = false;
boolean hasBodyEnergyValues = false;
boolean hasStaminaValues = false;
boolean hasStepLengthValues = false;
boolean hasN2LoadValues = false;
boolean hasCnsToxicityValues = false;
final Accumulator cadenceAccumulator = new Accumulator();
final Accumulator temperatureAccumulator = new Accumulator();
@@ -67,47 +107,107 @@ public class DefaultWorkoutCharts {
final long tsShorten = tsTranslation.shorten((int) point.getTime().getTime());
// HR
if (point.getHeartRate() > 0) {
heartRateDataPoints.add(new Entry(tsShorten, point.getHeartRate()));
final int heartRate = point.getHeartRate();
if (heartRate > 0) {
heartRateDataPoints.add(new Entry(tsShorten, heartRate));
}
// Elevation
if (point.getLocation() != null && point.getLocation().hasAltitude()) {
elevationDataPoints.add(new Entry(tsShorten, (float) point.getLocation().getAltitude()));
if (point.getLocation().getAltitude() != 0) {
// Some devices provide all points at zero
hasElevationValues = true;
}
final double elevation = point.getAltitude();
if (elevation > GPSCoordinate.UNKNOWN_ALTITUDE) {
elevationDataPoints.add(new Entry(tsShorten, (float) elevation));
hasElevationValues = hasElevationValues || (elevation != 0.0);
}
// Speed
speedDataPoints.add(new Entry(tsShorten, point.getSpeed()));
if (!hasSpeedValues && point.getSpeed() > 0) {
hasSpeedValues = true;
final float speed = point.getSpeed();
if(speed >= 0.0f) {
speedDataPoints.add(new Entry(tsShorten, speed));
hasSpeedValues = hasSpeedValues || (speed > 0.0f);
}
// Cadence
cadenceDataPoints.add(new Entry(tsShorten, point.getCadence()));
cadenceAccumulator.add(point.getCadence());
if (!hasCadenceValues && point.getCadence() > 0) {
hasCadenceValues = true;
final float cadence = point.getCadence();
if(cadence >= 0.0f){
cadenceDataPoints.add(new Entry(tsShorten, cadence));
cadenceAccumulator.add(cadence);
hasCadenceValues = hasCadenceValues || (cadence > 0.0f);
}
if (point.getPower() >= 0) {
powerDataPoints.add(new Entry(tsShorten, (float) point.getPower()));
final float power = point.getPower();
if (power >= 0.0f) {
powerDataPoints.add(new Entry(tsShorten, power));
hasPowerValues = hasPowerValues || (power > 0.0f);
}
if (point.getRespiratoryRate() >= 0) {
respiratoryRatePoints.add(new Entry(tsShorten, point.getRespiratoryRate()));
final float respiratoryRate = point.getRespiratoryRate();
if (respiratoryRate >= 0.0f) {
respiratoryRatePoints.add(new Entry(tsShorten, respiratoryRate));
hasRespiratoryRateValues = hasRespiratoryRateValues || (respiratoryRate > 0.0f);
}
// Depth (diving activity)
if (point.getDepth() > 0) {
depthDataPoints.add(new Entry(tsShorten, (float) point.getDepth() * -1));
final double depth = point.getDepth();
if (depth >= 0.0) {
depthDataPoints.add(new Entry(tsShorten, (float) -depth));
hasDepthValues = hasDepthValues || (depth > 0.0);
}
// Temperature
if (point.getTemperature() > -273) {
temperatureDataPoints.add(new Entry(tsShorten, (float) point.getTemperature()));
temperatureAccumulator.add(point.getTemperature());
final double temperature = point.getTemperature();
if (temperature > -273) {
temperatureDataPoints.add(new Entry(tsShorten, (float) temperature));
temperatureAccumulator.add(temperature);
hasTemperatureValues = hasTemperatureValues || (temperature != 0.0);
}
// Distance
final double distance = point.getDistance();
if (distance >= 0.0) {
distancePoints.add(new Entry(tsShorten, (float) distance));
hasDistanceValues = hasDistanceValues || (distance > 0);
}
// Stride
final float stride = point.getStride();
if (stride >= 0.0f) {
stridePoints.add(new Entry(tsShorten, stride));
hasStrideValues = hasStrideValues || (stride > 0.0f);
}
// Body Energy
final float bodyEnergy = point.getBodyEnergy();
if (bodyEnergy >= 0.0f) {
bodyEnergyPoints.add(new Entry(tsShorten, bodyEnergy));
hasBodyEnergyValues = hasBodyEnergyValues || (bodyEnergy > 0.0f);
}
// Stamina
final float stamina = point.getStamina();
if (stamina >= 0.0f) {
staminaPoints.add(new Entry(tsShorten, stamina));
hasStaminaValues = hasStaminaValues || (stamina > 0.0f);
}
// Step Length
final int stepLength = point.getStepLength();
if (stepLength >= 0) {
stepLengthPoints.add(new Entry(tsShorten, stepLength));
hasStepLengthValues = hasStepLengthValues || (stepLength > 0);
}
// CNS Toxicity
final float cnsToxicity = point.getCnsToxicity();
if (cnsToxicity >= 0.0f) {
cnsToxicityPoints.add(new Entry(tsShorten, cnsToxicity));
hasCnsToxicityValues = hasCnsToxicityValues || (cnsToxicity > 0.0f);
}
// N2 Load
final float n2Load = point.getN2Load();
if (n2Load >= 0.0f) {
n2LoadPoints.add(new Entry(tsShorten, n2Load));
hasN2LoadValues = hasN2LoadValues || (n2Load > 0.0f);
}
}
@@ -127,22 +227,50 @@ public class DefaultWorkoutCharts {
charts.add(createElevationChart(context, elevationDataPoints));
}
if (!powerDataPoints.isEmpty()) {
if (hasPowerValues && !powerDataPoints.isEmpty()) {
charts.add(createPowerChart(context, powerDataPoints));
}
if (!respiratoryRatePoints.isEmpty()) {
if (hasRespiratoryRateValues && !respiratoryRatePoints.isEmpty()) {
charts.add(createRespiratoryRateChart(context, respiratoryRatePoints));
}
if (!depthDataPoints.isEmpty()) {
if (hasDepthValues && !depthDataPoints.isEmpty()) {
charts.add(createDepthChart(context, depthDataPoints));
}
if (!temperatureDataPoints.isEmpty()) {
if (hasTemperatureValues && !temperatureDataPoints.isEmpty()) {
charts.add(createTemperatureChart(context, temperatureDataPoints, temperatureAccumulator));
}
if (hasDistanceValues && !distancePoints.isEmpty()) {
charts.add(createDistanceChart(context, distancePoints));
}
if (hasStrideValues && !stridePoints.isEmpty()) {
charts.add(createStrideChart(context, stridePoints));
}
if (hasBodyEnergyValues && !bodyEnergyPoints.isEmpty()) {
charts.add(createBodyEnergyChart(context, bodyEnergyPoints));
}
if (hasStaminaValues && !staminaPoints.isEmpty()) {
charts.add(createStaminaChart(context, staminaPoints));
}
if (hasStepLengthValues && !stepLengthPoints.isEmpty()) {
charts.add(createStepLengthChart(context, stepLengthPoints));
}
if (hasCnsToxicityValues && !cnsToxicityPoints.isEmpty()) {
charts.add(createCnsToxicityChart(context, cnsToxicityPoints));
}
if (hasN2LoadValues && !n2LoadPoints.isEmpty()) {
charts.add(createN2LoadChart(context, n2LoadPoints));
}
return charts;
}
@@ -338,12 +466,152 @@ public class DefaultWorkoutCharts {
);
}
private static WorkoutChart createDistanceChart(final Context context,
final List<Entry> distancePoints) {
final String label = String.format("%s(%s)", context.getString(R.string.distance), getUnitString(context, UNIT_METERS));
final LineDataSet dataset = createLineDataSet(context, distancePoints, label, ContextCompat.getColor(context, R.color.chart_line_distance));
final ValueFormatter valueFormatter = new ValueFormatter() {
@Override
public String getFormattedValue(float value) {
return String.valueOf((int) value);
}
};
return new WorkoutChart(
"chart_distance",
context.getString(R.string.distance),
ActivitySummaryEntries.GROUP_DISTANCE,
new LineData(dataset),
valueFormatter,
getUnitString(context, UNIT_METERS)
);
}
private static WorkoutChart createStrideChart(final Context context,
final List<Entry> stridePoints) {
final String label = String.format("%s(%s)", context.getString(R.string.stride), getUnitString(context, UNIT_MM));
final LineDataSet dataset = createLineDataSet(context, stridePoints, label, ContextCompat.getColor(context, R.color.chart_line_stride));
final ValueFormatter valueFormatter = new ValueFormatter() {
@Override
public String getFormattedValue(float value) {
return String.valueOf((int) value);
}
};
return new WorkoutChart(
"chart_stride",
context.getString(R.string.stride),
ActivitySummaryEntries.GROUP_STEPS,
new LineData(dataset),
valueFormatter,
getUnitString(context, UNIT_MM)
);
}
private static WorkoutChart createBodyEnergyChart(final Context context,
final List<Entry> bodyEnergyPoints) {
final String label = String.format("%s(%s)", context.getString(R.string.body_energy), getUnitString(context, UNIT_PERCENTAGE));
final LineDataSet dataset = createLineDataSet(context, bodyEnergyPoints, label, ContextCompat.getColor(context, R.color.chart_line_body_energy));
final ValueFormatter valueFormatter = new ValueFormatter() {
@Override
public String getFormattedValue(float value) {
return String.valueOf((int) value);
}
};
return new WorkoutChart(
"chart_body_energy",
context.getString(R.string.body_energy),
ActivitySummaryEntries.GROUP_TRAINING_EFFECT,
new LineData(dataset),
valueFormatter,
getUnitString(context, UNIT_PERCENTAGE)
);
}
private static WorkoutChart createStaminaChart(final Context context,
final List<Entry> staminaPoints) {
final String label = String.format("%s(%s)", context.getString(R.string.stamina), getUnitString(context, UNIT_PERCENTAGE));
final LineDataSet dataset = createLineDataSet(context, staminaPoints, label, ContextCompat.getColor(context, R.color.chart_line_stamina));
final ValueFormatter valueFormatter = new ValueFormatter() {
@Override
public String getFormattedValue(float value) {
return String.valueOf((int) value);
}
};
return new WorkoutChart(
"chart_stamina",
context.getString(R.string.stamina),
ActivitySummaryEntries.GROUP_TRAINING_EFFECT,
new LineData(dataset),
valueFormatter,
getUnitString(context, UNIT_PERCENTAGE)
);
}
private static WorkoutChart createStepLengthChart(final Context context,
final List<Entry> stepLengthPoints) {
final String label = String.format("%s(%s)", context.getString(R.string.step_length), getUnitString(context, UNIT_MM));
final LineDataSet dataset = createLineDataSet(context, stepLengthPoints, label, ContextCompat.getColor(context, R.color.chart_line_step_length));
final ValueFormatter valueFormatter = new ValueFormatter() {
@Override
public String getFormattedValue(float value) {
return String.valueOf((int) value);
}
};
return new WorkoutChart(
"chart_step_length",
context.getString(R.string.step_length),
ActivitySummaryEntries.GROUP_STEPS,
new LineData(dataset),
valueFormatter,
getUnitString(context, UNIT_MM)
);
}
private static WorkoutChart createCnsToxicityChart(final Context context,
final List<Entry> cnsToxicityPoints) {
final String label = String.format("%s(%s)", context.getString(R.string.diving_cns_toxicity), getUnitString(context, UNIT_PERCENTAGE));
final LineDataSet dataset = createLineDataSet(context, cnsToxicityPoints, label, ContextCompat.getColor(context, R.color.chart_cns_toxicity));
final ValueFormatter valueFormatter = new ValueFormatter() {
@Override
public String getFormattedValue(float value) {
return String.valueOf((int) value);
}
};
return new WorkoutChart(
"chart_cns_toxicity",
context.getString(R.string.diving_cns_toxicity),
ActivitySummaryEntries.GROUP_DIVING,
new LineData(dataset),
valueFormatter,
getUnitString(context, UNIT_PERCENTAGE)
);
}
private static WorkoutChart createN2LoadChart(final Context context,
final List<Entry> n2LoadPoints) {
final String label = String.format("%s(%s)", context.getString(R.string.diving_nitrogen_load), getUnitString(context, UNIT_PERCENTAGE));
final LineDataSet dataset = createLineDataSet(context, n2LoadPoints, label, ContextCompat.getColor(context, R.color.chart_n2_load));
final ValueFormatter valueFormatter = new ValueFormatter() {
@Override
public String getFormattedValue(float value) {
return String.valueOf((int) value);
}
};
return new WorkoutChart(
"chart_n2_load",
context.getString(R.string.diving_nitrogen_load),
ActivitySummaryEntries.GROUP_DIVING,
new LineData(dataset),
valueFormatter,
getUnitString(context, UNIT_PERCENTAGE)
);
}
public static String getUnitString(final Context context, final String unit) {
final int resId = context.getResources().getIdentifier(unit, "string", context.getPackageName());
if (resId != 0) {
return context.getString(resId);
}
return "";
return unit;
}
public static LineDataSet createLineDataSet(final Context context,
@@ -1,3 +1,19 @@
/* Copyright (C) 2025-2026 José Rebelo, a0z, Me7c7, Martin Piatka, Thomas Kuehne
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 <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.activities.workouts.entries
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryData
@@ -280,6 +296,8 @@ object ActivitySummaryGroup {
ActivitySummaryEntries.END_N2,
ActivitySummaryEntries.DIVE_NUMBER,
ActivitySummaryEntries.BOTTOM_TIME,
ActivitySummaryEntries.OXYGEN_TOXICITY,
ActivitySummaryEntries.SURFACE_INTERVAL,
)
)
@@ -315,6 +333,32 @@ object ActivitySummaryGroup {
)
)
// Distance
put(ActivitySummaryEntries.GROUP_DISTANCE, listOf(
ActivitySummaryEntries.DISTANCE_METERS
))
// Steps
put(ActivitySummaryEntries.GROUP_STEPS, listOf(
ActivitySummaryEntries.AVG_GROUND_CONTACT_TIME,
ActivitySummaryEntries.AVG_GROUND_CONTACT_TIME_BALANCE,
ActivitySummaryEntries.AVG_VERTICAL_OSCILLATION,
ActivitySummaryEntries.AVG_VERTICAL_RATIO,
ActivitySummaryEntries.STANDING_COUNT,
ActivitySummaryEntries.STANDING_TIME,
ActivitySummaryEntries.STEPS,
ActivitySummaryEntries.STEP_LENGTH_AVG,
ActivitySummaryEntries.STEP_RATE_AVG,
ActivitySummaryEntries.STEP_RATE_MAX,
ActivitySummaryEntries.STEP_RATE_SUM,
ActivitySummaryEntries.STEP_SPEED_LOSS,
ActivitySummaryEntries.STEP_SPEED_LOSS_PERCENTAGE,
ActivitySummaryEntries.STRIDE_AVG,
ActivitySummaryEntries.STRIDE_MAX,
ActivitySummaryEntries.STRIDE_MIN,
ActivitySummaryEntries.STRIDE_TOTAL,
))
// Other
put(
ActivitySummaryEntries.GROUP_OTHER, listOf<String>(
@@ -323,15 +367,6 @@ object ActivitySummaryGroup {
ActivitySummaryEntries.CALORIES_BURNT,
ActivitySummaryEntries.CALORIES_TOTAL,
ActivitySummaryEntries.CALORIES_RESTING,
ActivitySummaryEntries.STRIDE_AVG,
ActivitySummaryEntries.STRIDE_MAX,
ActivitySummaryEntries.STRIDE_MIN,
ActivitySummaryEntries.STEP_LENGTH_AVG,
ActivitySummaryEntries.CADENCE_AVG,
ActivitySummaryEntries.CADENCE_MAX,
ActivitySummaryEntries.CADENCE_MIN,
ActivitySummaryEntries.STEP_RATE_AVG,
ActivitySummaryEntries.STEP_RATE_MAX,
)
)
}
@@ -1,4 +1,4 @@
/* Copyright (C) 2017-2024 Carsten Pfeiffer, Daniele Gobbetti, José Rebelo
/* Copyright (C) 2017-2026 Carsten Pfeiffer, Daniele Gobbetti, José Rebelo, Thomas Kuehne
This file is part of Gadgetbridge.
@@ -16,37 +16,30 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.model;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.Date;
import java.util.Objects;
import androidx.annotation.Nullable;
// https://www8.garmin.com/xmlschemas/TrackPointExtensionv1.xsd
/*
<trkpt lat="54.8591470" lon="-1.5754310">
<ele>29.2</ele>
<time>2015-07-26T07:43:42Z</time>
<extensions>
<gpxtpx:TrackPointExtension>
<gpxtpx:atemp>11</gpxtpx:atemp>
<gpxtpx:hr>92</gpxtpx:hr>
<gpxtpx:cad>0</gpxtpx:cad>
<gpxtpx:speed>0.5</gpxtpx:speed>
</gpxtpx:TrackPointExtension>
</extensions>
</trkpt>
*/
public class ActivityPoint {
private Date time;
private GPSCoordinate location;
private int heartRate;
private float speed = -1;
private int strideCm = -1;
private int stride = -1;
private int stepLength = -1;
private int cadence = -1;
private int power = -1;
private float power = -1;
private float respiratoryRate = -1;
private double depth = -1;
private double temperature = -273;
private double distance = -1.0;
private float bodyEnergy = -1.0f;
private float stamina = -1.0f;
private float cnsToxicity = -1.0f;
private float n2Load = -1.0f;
private double altitude = GPSCoordinate.UNKNOWN_ALTITUDE;
// e.g. to describe a pause during the activity
private @Nullable String description;
@@ -62,7 +55,7 @@ public class ActivityPoint {
return time;
}
public void setTime(Date time) {
public void setTime(@NonNull Date time) {
this.time = time;
}
@@ -75,36 +68,93 @@ public class ActivityPoint {
this.description = description;
}
@Nullable
public GPSCoordinate getLocation() {
return location;
}
public void setLocation(GPSCoordinate location) {
public void setLocation(@Nullable GPSCoordinate location) {
this.location = location;
}
/// @see ActivitySummaryEntries#UNIT_METERS
public double getAltitude() {
if (altitude > GPSCoordinate.UNKNOWN_ALTITUDE) {
return altitude;
}
if (location != null) {
return location.getAltitude();
}
return GPSCoordinate.UNKNOWN_ALTITUDE;
}
/// 24/7 energy level
///
/// @see ActivitySummaryEntries#UNIT_PERCENTAGE
public float getBodyEnergy() {
return bodyEnergy;
}
/// endurance capacity when doing a specific activity
///
/// @see ActivitySummaryEntries#UNIT_PERCENTAGE
public float getStamina() {
return stamina;
}
/// central nervous system (CNS) toxicity (e.g. Diving)
///
/// @see ActivitySummaryEntries#UNIT_PERCENTAGE
public float getCnsToxicity() {
return cnsToxicity;
}
/// nitrogen (N2) tissue load (e.g. Diving)
/// @see ActivitySummaryEntries#UNIT_PERCENTAGE
public float getN2Load() {
return cnsToxicity;
}
/// @see ActivitySummaryEntries#UNIT_METERS
public double getDistance() {
return distance;
}
/// @see ActivitySummaryEntries#UNIT_METERS
public void setDistance(double distanceInMeter) {
distance = distanceInMeter;
}
/// @see ActivitySummaryEntries#UNIT_BPM
public int getHeartRate() {
return heartRate;
}
/// @see ActivitySummaryEntries#UNIT_BPM
public void setHeartRate(int heartRate) {
this.heartRate = heartRate;
}
/// @see ActivitySummaryEntries#UNIT_METERS_PER_SECOND
public float getSpeed() {
return speed;
}
/// @see ActivitySummaryEntries#UNIT_METERS_PER_SECOND
public void setSpeed(float speed) {
this.speed = speed;
}
public int getStrideCm() {
return strideCm;
/// distance from one foot landing to the same foot landing again
/// @see ActivitySummaryEntries#UNIT_MM
public int getStride() {
return stride;
}
public void setStrideCm(int strideCm) {
this.strideCm = strideCm;
/// distance from one foot landing to the opposite foot landing
/// @see ActivitySummaryEntries#UNIT_MM
public int getStepLength() {
return stepLength;
}
public int getCadence() {
@@ -115,74 +165,127 @@ public class ActivityPoint {
this.cadence = cadence;
}
public int getPower() {
/// @see ActivitySummaryEntries#UNIT_WATT
public float getPower() {
return power;
}
public void setPower(final int power) {
/// @see ActivitySummaryEntries#UNIT_WATT
public void setPower(final float power) {
this.power = power;
}
/// @see ActivitySummaryEntries#UNIT_BREATHS_PER_MIN
public float getRespiratoryRate() {
return respiratoryRate;
}
/// @see ActivitySummaryEntries#UNIT_BREATHS_PER_MIN
public void setRespiratoryRate(final float respiratoryRate) {
this.respiratoryRate = respiratoryRate;
}
public double getTemperature() {return temperature; }
public void setTemperature(final double temperature) { this.temperature = temperature; }
public double getDepth() {return depth; }
public void setDepth(final double depth) { this.depth = depth; }
/// @see ActivitySummaryEntries#UNIT_CELSIUS
public double getTemperature() {
return temperature;
}
/// @see ActivitySummaryEntries#UNIT_CELSIUS
public void setTemperature(final double temperature) {
this.temperature = temperature;
}
/// @see ActivitySummaryEntries#UNIT_METERS
public double getDepth() {
return depth;
}
/// @see ActivitySummaryEntries#UNIT_METERS
public void setDepth(final double depth) {
this.depth = depth;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof ActivityPoint that)) return false;
return heartRate == that.heartRate &&
Float.compare(speed, that.speed) == 0 &&
strideCm == that.strideCm &&
stride == that.stride &&
cadence == that.cadence &&
power == that.power &&
Float.compare(power, that.power) == 0 &&
Float.compare(respiratoryRate, that.respiratoryRate) == 0 &&
Double.compare(depth, that.depth) == 0 &&
Double.compare(temperature, that.temperature) == 0 &&
Objects.equals(time, that.time) &&
Objects.equals(location, that.location) &&
Objects.equals(description, that.description);
Objects.equals(description, that.description) &&
Double.compare(distance, that.distance) == 0 &&
Double.compare(altitude, that.altitude) == 0 &&
Float.compare(bodyEnergy, that.bodyEnergy) == 0 &&
Float.compare(stamina, that.stamina) == 0;
}
@Override
public int hashCode() {
return Objects.hash(time, location, heartRate, speed, strideCm, cadence, power, respiratoryRate, depth, temperature, description);
return Objects.hash(time, location, heartRate, speed, stride, cadence, power, respiratoryRate, depth, temperature, description, distance, altitude, bodyEnergy, stamina);
}
public static class Builder {
private long timeMillis;
private Date date;
private double latitude;
private double longitude;
private double altitude = GPSCoordinate.UNKNOWN_ALTITUDE;
@Nullable
private String description;
private int heartRate;
private float speed = -1;
private int strideCm = -1;
private int cadence = -1;
private int power = -1;
private float respiratoryRate = -1;
private double depth = -1;
private double temperature = -273;
private GPSCoordinate location;
private double latitude = Double.NaN;
private double longitude = Double.NaN;
private double altitude = Double.NaN;
private double hdop = Double.NaN;
private double distance = Double.NaN;
private int heartRate = Integer.MIN_VALUE;
private float speed = Float.NaN;
private int stride = Integer.MIN_VALUE;
private int stepLength = Integer.MIN_VALUE;
private int cadence = Integer.MIN_VALUE;
private float power = Float.NaN;
private float respiratoryRate = Float.NaN;
private double depth = Double.NaN;
private double temperature = Double.NaN;
private float bodyEnergy = Float.NaN;
private float stamina = Float.NaN;
private float cnsToxicity = Float.NaN;
private float n2Load = Float.NaN;
public Builder() {
}
public Builder(long epocMilliSeconds) {
date = new Date(epocMilliSeconds);
}
public Builder(@NonNull Long epocMilliSeconds) {
date = new Date(epocMilliSeconds);
}
public Builder(@NonNull Date date) {
this.date = date;
}
public long getTime() {
return timeMillis;
return date.getTime();
}
public void setTime(final long timeMillis) {
this.timeMillis = timeMillis;
public void setTime(long unixEpocMilliSeconds) {
date = new Date(unixEpocMilliSeconds);
}
public double getLatitude() {
return latitude;
public void setTime(@NonNull Date date) {
this.date = date;
}
public void setLatitude(@Nullable final Double latitude) {
this.latitude = (latitude == null) ? Double.NaN : latitude;
}
public void setLatitude(final double latitude) {
@@ -193,96 +296,311 @@ public class ActivityPoint {
return longitude;
}
public void setLongitude(@Nullable final Double longitude) {
this.longitude = (longitude == null) ? Double.NaN : longitude;
}
public void setLongitude(final double longitude) {
this.longitude = longitude;
}
/// @see ActivitySummaryEntries#UNIT_METERS
public double getAltitude() {
return altitude;
}
/// @see ActivitySummaryEntries#UNIT_METERS
public void setAltitude(@Nullable final Number altitude) {
this.altitude = (altitude == null) ? Double.NaN : altitude.doubleValue();
}
/// @see ActivitySummaryEntries#UNIT_METERS
public void setAltitude(final double altitude) {
this.altitude = altitude;
}
/// @see ActivitySummaryEntries#UNIT_BPM
public int getHeartRate() {
return heartRate;
}
/// @see ActivitySummaryEntries#UNIT_BPM
public void setHeartRate(@Nullable final Number heartRate) {
this.heartRate = (heartRate == null) ? Integer.MIN_VALUE : heartRate.intValue();
}
/// @see ActivitySummaryEntries#UNIT_BPM
public void setHeartRate(final int heartRate) {
this.heartRate = heartRate;
}
/// @see ActivitySummaryEntries#UNIT_METERS_PER_SECOND
public float getSpeed() {
return speed;
}
/// @see ActivitySummaryEntries#UNIT_METERS_PER_SECOND
public void setSpeed(@Nullable final Number speed) {
this.speed = (speed == null) ? Float.NaN : speed.floatValue();
}
/// @see ActivitySummaryEntries#UNIT_METERS_PER_SECOND
public void setSpeed(final float speed) {
this.speed = speed;
}
public int getStrideCm() {
return strideCm;
/// @see ActivitySummaryEntries#UNIT_MM
public int getStride() {
return stride;
}
public void setStrideCm(int strideCm) {
this.strideCm = strideCm;
/// distance from one foot landing to the same foot landing again
/// @see ActivitySummaryEntries#UNIT_MM
public void setStride(@Nullable Number stride) {
this.stride = (stride == null) ? Integer.MIN_VALUE : stride.intValue();
}
/// distance from one foot landing to the same foot landing again
/// @see ActivitySummaryEntries#UNIT_MM
public void setStride(int stride) {
this.stride = stride;
}
public int getCadence() {
return cadence;
}
public void setCadence(@Nullable final Number cadence) {
this.cadence = (cadence == null) ? Integer.MIN_VALUE : cadence.intValue();
}
public void setCadence(final int cadence) {
this.cadence = cadence;
}
public int getPower() {
/// @see ActivitySummaryEntries#UNIT_WATT
public float getPower() {
return power;
}
/// @see ActivitySummaryEntries#UNIT_WATT
public void setPower(@Nullable final Number power) {
this.power = (power == null) ? Integer.MIN_VALUE : power.intValue();
}
/// @see ActivitySummaryEntries#UNIT_WATT
public void setPower(final int power) {
this.power = power;
}
/// @see ActivitySummaryEntries#UNIT_BREATHS_PER_MIN
public float getRespiratoryRate() {
return respiratoryRate;
}
/// @see ActivitySummaryEntries#UNIT_BREATHS_PER_MIN
public void setRespiratoryRate(@Nullable final Number respiratoryRate) {
this.respiratoryRate = (respiratoryRate == null) ? Float.NaN : respiratoryRate.floatValue();
}
/// @see ActivitySummaryEntries#UNIT_BREATHS_PER_MIN
public void setRespiratoryRate(final float respiratoryRate) {
this.respiratoryRate = respiratoryRate;
}
/// @see ActivitySummaryEntries#UNIT_METERS
public double getDepth() {
return depth;
}
/// @see ActivitySummaryEntries#UNIT_METERS
public void setDepth(@Nullable final Number depth) {
this.depth = (depth == null) ? Double.NaN : depth.doubleValue();
}
/// @see ActivitySummaryEntries#UNIT_METERS
public void setDepth(final double depth) {
this.depth = depth;
}
public void setLocation(@Nullable final GPSCoordinate location) {
this.location = location;
}
/// @see ActivitySummaryEntries#UNIT_CELSIUS
public double getTemperature() {
return temperature;
}
/// @see ActivitySummaryEntries#UNIT_CELSIUS
public void setTemperature(@Nullable final Number temperature) {
this.temperature = (temperature == null) ? Double.NaN : temperature.doubleValue();
}
/// @see ActivitySummaryEntries#UNIT_CELSIUS
public void setTemperature(final double temperature) {
this.temperature = temperature;
}
/// horizontal dilution of precision
///
/// @see ActivitySummaryEntries#UNIT_METERS
public void setHdop(@Nullable final Number hdop) {
this.hdop = (hdop == null) ? Double.NaN : hdop.doubleValue();
}
/// horizontal dilution of precision
///
/// @see ActivitySummaryEntries#UNIT_METERS
public void setHdop(final double hdop) {
this.hdop = hdop;
}
/// total traveled distance
///
/// @see ActivitySummaryEntries#UNIT_METERS
public void setDistance(@Nullable final Number distance) {
this.distance = (distance == null) ? Double.NaN : distance.doubleValue();
}
/// total traveled distance
///
/// @see ActivitySummaryEntries#UNIT_METERS
public void setDistance(final double distance) {
this.distance = distance;
}
/// e.g. to describe a pause during the activity
public void setDescription(@Nullable String description) {
this.description = description;
}
/// 24/7 energy level
/// @see ActivitySummaryEntries#UNIT_PERCENTAGE
public void setBodyEnergy(@Nullable Number bodyEnergy) {
this.bodyEnergy = (bodyEnergy == null) ? Float.NaN : bodyEnergy.floatValue();
}
/// 24/7 energy level
/// @see ActivitySummaryEntries#UNIT_PERCENTAGE
public void setBodyEnergy(float bodyBattery) {
this.bodyEnergy = bodyBattery;
}
/// endurance capacity when doing a specific activity
/// @see ActivitySummaryEntries#UNIT_PERCENTAGE
public void setStamina(@Nullable Number stamina) {
this.stamina = (stamina == null) ? Float.NaN : stamina.floatValue();
}
/// endurance capacity when doing a specific activity
/// @see ActivitySummaryEntries#UNIT_PERCENTAGE
public void setStamina(float stamina) {
this.stamina = stamina;
}
/// distance from one foot landing to the opposite foot landing
/// @see ActivitySummaryEntries#UNIT_MM
public void setStepLength(@Nullable Number stepLength){
this.stepLength = (stepLength == null) ? Integer.MIN_VALUE : stepLength.intValue();
}
/// distance from one foot landing to the opposite foot landing
/// @see ActivitySummaryEntries#UNIT_MM
public void setStepLength(int stepLength){
this.stepLength = stepLength;
}
/// central nervous system (CNS) toxicity (e.g. Diving)
/// @see ActivitySummaryEntries#UNIT_PERCENTAGE
public void setCnsToxicity(@Nullable Number cnsToxicity) {
this.cnsToxicity = (cnsToxicity == null) ? Float.NaN : cnsToxicity.floatValue();
}
/// central nervous system (CNS) toxicity (e.g. Diving)
/// @see ActivitySummaryEntries#UNIT_PERCENTAGE
public void setCnsToxicity(float cnsToxicity) {
this.cnsToxicity = cnsToxicity;
}
/// nitrogen (N2) tissue load (e.g. Diving)
/// @see ActivitySummaryEntries#UNIT_PERCENTAGE
public void setN2Load(@Nullable Number n2Load) {
this.n2Load = (n2Load == null) ? Float.NaN : n2Load.floatValue();
}
/// nitrogen (N2) tissue load (e.g. Diving)
/// @see ActivitySummaryEntries#UNIT_PERCENTAGE
public void setN2Load(float n2Load) {
this.n2Load = n2Load;
}
public ActivityPoint build() {
final ActivityPoint activityPoint = new ActivityPoint();
activityPoint.setTime(new Date(timeMillis));
if (latitude != 0 && longitude != 0) {
activityPoint.setLocation(new GPSCoordinate(longitude, latitude, altitude));
final ActivityPoint activityPoint = new ActivityPoint(date);
if (location != null) {
activityPoint.setLocation(location);
} else if (!(Double.isNaN(latitude) || Double.isNaN(longitude))) {
final GPSCoordinate loc;
if (altitude > GPSCoordinate.UNKNOWN_ALTITUDE) {
loc = new GPSCoordinate(longitude, latitude, altitude);
} else {
loc = new GPSCoordinate(longitude, latitude);
}
if (hdop > 0.0) {
loc.setHdop(hdop);
}
activityPoint.setLocation(loc);
} else if (altitude > GPSCoordinate.UNKNOWN_ALTITUDE) {
activityPoint.altitude = altitude;
}
activityPoint.setHeartRate(heartRate);
activityPoint.setSpeed(speed);
activityPoint.setCadence(cadence);
activityPoint.setStrideCm(strideCm);
activityPoint.setPower(power);
activityPoint.setRespiratoryRate(respiratoryRate);
activityPoint.setDepth(depth);
activityPoint.setTemperature(temperature);
if (heartRate > Integer.MIN_VALUE) {
activityPoint.setHeartRate(heartRate);
}
if (!Float.isNaN(speed)) {
activityPoint.setSpeed(speed);
}
if (cadence > Integer.MIN_VALUE) {
activityPoint.setCadence(cadence);
}
if (stride > Integer.MIN_VALUE) {
activityPoint.stride = stride;
}
if (!Float.isNaN(power)) {
activityPoint.setPower(power);
}
if (!Float.isNaN(respiratoryRate)) {
activityPoint.setRespiratoryRate(respiratoryRate);
}
if (!Double.isNaN(depth)) {
activityPoint.setDepth(depth);
}
if (!Double.isNaN(temperature)) {
activityPoint.setTemperature(temperature);
}
if (description != null && description.length() > 0) {
activityPoint.setDescription(description);
}
if (!Double.isNaN(distance)) {
activityPoint.setDistance(distance);
}
if(!Float.isNaN(bodyEnergy)){
activityPoint.bodyEnergy = bodyEnergy;
}
if(!Float.isNaN(stamina)){
activityPoint.stamina = stamina;
}
if(stepLength > Integer.MIN_VALUE){
activityPoint.stepLength = stepLength;
}
if(!Float.isNaN(cnsToxicity)){
activityPoint.cnsToxicity = cnsToxicity;
}
if(!Float.isNaN(n2Load)){
activityPoint.n2Load = n2Load;
}
return activityPoint;
}
}
@@ -257,6 +257,7 @@ public class ActivitySummaryEntries {
public static final String UNIT_NAUTICAL_MILES = "nautical_miles";
public static final String UNIT_KNOTS = "knots";
public static final String UNIT_CELSIUS = "unit_celsius";
public static final String UNIT_OXYGEN_TOXICITY_UNITs = "unit_oxygen_toxicity_units";
public static final String GROUP_PACE = "Pace";
public static final String GROUP_ACTIVITY = "Activity";
@@ -280,6 +281,8 @@ public class ActivitySummaryEntries {
public static final String GROUP_DIVING = "activity_type_diving";
public static final String GROUP_RECOVERY_HEART_RATE = "recovery_heart_rate";
public static final String GROUP_MOVEMENT_EVALUATION = "movement_evaluation";
public static final String GROUP_DISTANCE = "Distance";
public static final String GROUP_STEPS = "Steps";
public static final String AVG_DEPTH = "diving_avg_depth";
public static final String START_CNS = "diving_start_cns";
public static final String END_CNS = "diving_end_cns";
@@ -287,6 +290,8 @@ public class ActivitySummaryEntries {
public static final String END_N2 = "diving_end_n2";
public static final String DIVE_NUMBER = "dive_number";
public static final String BOTTOM_TIME = "diving_bottom_time";
public static final String OXYGEN_TOXICITY = "diving_oxygen_toxicity";
public static final String SURFACE_INTERVAL = "diving_surface_interval";
// DIVING parameters
public static final String MAX_DEPTH = "diving_maximum_diving_depth";
@@ -316,7 +316,7 @@ public class ZeppOsActivityDetailsParser extends AbstractHuamiActivityDetailsPar
final short pace = buf.getShort(); // sec/km
activityPointBuilder.setCadence(cadence);
activityPointBuilder.setStrideCm(stride);
activityPointBuilder.setStride(stride * 10.0f);
if (pace != 0) {
activityPointBuilder.setSpeed(1000f / pace); // s/km -> m/s
}
+7
View File
@@ -102,6 +102,13 @@
<color name="chart_line_stroke_rate" type="color">#6495ED</color>
<color name="chart_highline_dolor" type="color">#ffff8800</color>
<color name="chart_line_depth" type="color">#ff4040ff</color>
<color name="chart_line_distance" type="color">#88FF88</color>
<color name="chart_line_stride" type="color">#FF8888</color>
<color name="chart_line_body_energy" type="color">#884088</color>
<color name="chart_line_stamina" type="color">#8888FF</color>
<color name="chart_line_step_length" type="color">#FF4088</color>
<color name="chart_cns_toxicity" type="color">#FF4040</color>
<color name="chart_n2_load" type="color">#FF8840</color>
<attr name="row_separator" format="color" />
<color name="alternate_row_background_light">#FFEDEDED</color>
+8
View File
@@ -1056,6 +1056,14 @@
<string name="respiratoryrate">Respiratory Rate</string>
<string name="active_calories">Active calories</string>
<string name="distance">Distance</string>
<string name="stride">Stride</string>
<string name="stamina">Stamina</string>
<string name="step_length">Step Length</string>
<string name="diving_cns_toxicity">CNS toxicity</string>
<string name="diving_nitrogen_load">Nitrogen load</string>
<string name="diving_oxygen_toxicity">Oxygen toxicity</string>
<string name="unit_oxygen_toxicity_units">OTU</string>
<string name="diving_surface_interval">Surface interval</string>
<string name="clock">Clock</string>
<string name="heart_rate">Heart rate</string>
<string name="hr_resting">Resting</string>