mirror of
https://github.com/dyuri/garmin-repafield.git
synced 2025-12-16 19:24:01 +00:00
#3 gap, grade
This commit is contained in:
parent
35524dd9ba
commit
cb70962403
@ -8,7 +8,7 @@ Trail running focused Garmin watch DataField (for myself)
|
||||
- current
|
||||
- max
|
||||
- histogram
|
||||
- pace
|
||||
- pace (or speed)
|
||||
- current
|
||||
- average
|
||||
- distance
|
||||
@ -16,7 +16,8 @@ Trail running focused Garmin watch DataField (for myself)
|
||||
- current
|
||||
- gain
|
||||
- loss
|
||||
- cadence
|
||||
- grade
|
||||
- cadence (instead of grade) TODO
|
||||
- distance to destination gauge
|
||||
|
||||
# Settings
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
<properties>
|
||||
<property id="appVersion" type="string">1.1.2</property>
|
||||
<property id="appVersion" type="string">1.2.0</property>
|
||||
<property id="themeColor" type="string">0</property>
|
||||
<property id="themeColor2" type="string">0088FF</property>
|
||||
<property id="themeColor3" type="string">FFFF00</property>
|
||||
<property id="hrDisplay" type="number">0</property>
|
||||
<property id="speedNotPace" type="boolean">false</property>
|
||||
<property id="tlFieldData" type="number">2</property>
|
||||
</properties>
|
||||
|
||||
@ -19,6 +19,14 @@
|
||||
<listEntry value="2">@Strings.HrZone</listEntry>
|
||||
</settingConfig>
|
||||
</setting>
|
||||
<setting propertyKey="@Properties.tlFieldData" title="@Strings.TLFieldData">
|
||||
<settingConfig type="list">
|
||||
<listEntry value="0">@Strings.TLFCadence</listEntry>
|
||||
<listEntry value="1">@Strings.TLFGrade</listEntry>
|
||||
<listEntry value="2">@Strings.TLFGAP</listEntry>
|
||||
<!-- listEntry value="3">@Strings.TLFVSpeed</listEntry -->
|
||||
</settingConfig>
|
||||
</setting>
|
||||
<setting propertyKey="@Properties.speedNotPace" title="@Strings.SpeedNotPace">
|
||||
<settingConfig type="boolean" />
|
||||
</setting>
|
||||
|
||||
@ -9,4 +9,9 @@
|
||||
<string id="HrPercentage">Percentage</string>
|
||||
<string id="HrZone">Zone</string>
|
||||
<string id="SpeedNotPace">Show speed instead of pace</string>
|
||||
<string id="TLFieldData">Top-left field data</string>
|
||||
<string id="TLFCadence">Cadence</string>
|
||||
<string id="TLFGrade">Grade</string>
|
||||
<string id="TLFGAP">Grade Adjusted Pace</string>
|
||||
<string id="TLFVSpeed">Vertical Speed</string>
|
||||
</strings>
|
||||
|
||||
31
source/GAP.mc
Normal file
31
source/GAP.mc
Normal file
@ -0,0 +1,31 @@
|
||||
import Toybox.Lang;
|
||||
import Toybox.System;
|
||||
|
||||
// https://medium.com/strava-engineering/an-improved-gap-model-8b07ae8886c3
|
||||
const GRADE_ADJUSTMENT as Array<Array<Numeric>> = [
|
||||
[-0.3, 1.5], [-0.28, 1.4], [-0.24, 1.2], [-0.2, 1.1], [-0.18, 1], [-0.16, 0.9], [-0.1, 0.85], [-0.04, 0.9],
|
||||
[0, 1], [0.04, 1.1], [0.06, 1.2], [0.08, 1.3], [0.1, 1.5], [0.12, 1.6], [0.14, 1.8], [0.17, 2], [0.2, 2.3],
|
||||
[0.24, 2.6], [0.28, 3], [0.32, 3.4]
|
||||
];
|
||||
|
||||
|
||||
function adjustPaceForGrade(pace, grade) {
|
||||
if (grade <= GRADE_ADJUSTMENT[0][0]) {
|
||||
return pace / GRADE_ADJUSTMENT[0][1];
|
||||
}
|
||||
|
||||
var size = GRADE_ADJUSTMENT.size();
|
||||
for (var i = 1; i < size; i++) {
|
||||
if (grade <= GRADE_ADJUSTMENT[i][0]) {
|
||||
var prev = GRADE_ADJUSTMENT[i - 1];
|
||||
var next = GRADE_ADJUSTMENT[i];
|
||||
|
||||
var slope = (next[1] - prev[1]) / (next[0] - prev[0]);
|
||||
var adjustment = prev[1] + slope * (grade - prev[0]);
|
||||
|
||||
return pace / adjustment;
|
||||
}
|
||||
}
|
||||
|
||||
return pace / GRADE_ADJUSTMENT[size - 1][1];
|
||||
}
|
||||
@ -9,6 +9,10 @@ import Toybox.Application;
|
||||
const HR_TYPE_PERCENT = 1;
|
||||
const HR_TYPE_ZONE = 2;
|
||||
|
||||
const TLF_CADENCE = 0;
|
||||
const TLF_GRADE = 1;
|
||||
const TLF_GAP = 2;
|
||||
|
||||
function displayHr(hr as Number, type as Number, zones as Array<Number>) as String {
|
||||
if (hr == 0) {
|
||||
return "-";
|
||||
@ -39,11 +43,14 @@ class RepaFieldView extends WatchUi.DataField {
|
||||
hidden var themeColor3 as Number;
|
||||
hidden var hrDisplayType as Number;
|
||||
hidden var speedNotPace as Boolean;
|
||||
hidden var tlFieldData as Number;
|
||||
hidden var hrZones as Array<Number>;
|
||||
hidden var hrHist as Array<Number>;
|
||||
hidden var hrZoneColors as Array<Number>;
|
||||
hidden var cadenceZones as Array<Number>;
|
||||
hidden var cadenceZoneColors as Array<Number>;
|
||||
hidden var gradeZones as Array<Number>;
|
||||
hidden var gradeZoneColors as Array<Number>;
|
||||
hidden var isDistanceMetric as Boolean;
|
||||
hidden var isElevationMetric as Boolean;
|
||||
hidden var isPaceMetric as Boolean;
|
||||
@ -86,6 +93,7 @@ class RepaFieldView extends WatchUi.DataField {
|
||||
hidden var egain as Number;
|
||||
hidden var edrop as Number;
|
||||
hidden var cadence as Number;
|
||||
hidden var grade as RollingAverage;
|
||||
|
||||
function initialize() {
|
||||
DataField.initialize();
|
||||
@ -95,6 +103,7 @@ class RepaFieldView extends WatchUi.DataField {
|
||||
themeColor3 = Application.Properties.getValue("themeColor3").toNumberWithBase(16);
|
||||
hrDisplayType = Application.Properties.getValue("hrDisplay").toNumber();
|
||||
speedNotPace = Application.Properties.getValue("speedNotPace");
|
||||
tlFieldData = Application.Properties.getValue("tlFieldData").toNumber();
|
||||
|
||||
hrValue = 0;
|
||||
ahrValue = 0;
|
||||
@ -105,6 +114,8 @@ class RepaFieldView extends WatchUi.DataField {
|
||||
hrZoneColors = [Graphics.COLOR_DK_GRAY, Graphics.COLOR_LT_GRAY, Graphics.COLOR_BLUE, Graphics.COLOR_GREEN, Graphics.COLOR_YELLOW, Graphics.COLOR_RED, Graphics.COLOR_DK_RED];
|
||||
cadenceZones = [153, 163, 173, 183];
|
||||
cadenceZoneColors = [Graphics.COLOR_RED, Graphics.COLOR_YELLOW, Graphics.COLOR_GREEN, Graphics.COLOR_BLUE, Graphics.COLOR_PURPLE];
|
||||
gradeZones = [-10, -1, 1, 3, 6, 10, 15];
|
||||
gradeZoneColors = [Graphics.COLOR_PINK, Graphics.COLOR_PURPLE, Graphics.COLOR_LT_GRAY, Graphics.COLOR_BLUE, Graphics.COLOR_GREEN, Graphics.COLOR_YELLOW, Graphics.COLOR_RED, Graphics.COLOR_DK_RED];
|
||||
toDestination = 0.0f;
|
||||
distance = 0.0f;
|
||||
timer = 0;
|
||||
@ -118,6 +129,7 @@ class RepaFieldView extends WatchUi.DataField {
|
||||
egain = 0;
|
||||
edrop = 0;
|
||||
cadence = 0;
|
||||
grade = new RollingAverage(10);
|
||||
|
||||
var settings = System.getDeviceSettings();
|
||||
isDistanceMetric = settings.distanceUnits == System.UNIT_METRIC;
|
||||
@ -218,18 +230,31 @@ class RepaFieldView extends WatchUi.DataField {
|
||||
}
|
||||
|
||||
function compute(info as Activity.Info) as Void {
|
||||
if(info.currentHeartRate != null) {
|
||||
// update rolling values before updating normal fields
|
||||
// only calculate them when some time has passed
|
||||
if (info.timerTime != null && info.timerTime > 0) {
|
||||
// grade
|
||||
if (info.altitude != null && info.elapsedDistance != null) {
|
||||
var altChange = info.altitude - altitude;
|
||||
var distChange = info.elapsedDistance - distance;
|
||||
if (distChange > 0) {
|
||||
grade.insert(altChange / distChange);
|
||||
}
|
||||
}
|
||||
}
|
||||
// update normal fields
|
||||
if (info.currentHeartRate != null) {
|
||||
hrValue = info.currentHeartRate as Number;
|
||||
tickHr(hrValue);
|
||||
} else {
|
||||
hrValue = 0;
|
||||
}
|
||||
if(info.averageHeartRate != null) {
|
||||
if (info.averageHeartRate != null) {
|
||||
ahrValue = info.averageHeartRate as Number;
|
||||
} else {
|
||||
ahrValue = 0;
|
||||
}
|
||||
if(info.maxHeartRate != null) {
|
||||
if (info.maxHeartRate != null) {
|
||||
mhrValue = info.maxHeartRate as Number;
|
||||
} else {
|
||||
mhrValue = 0;
|
||||
@ -429,14 +454,36 @@ class RepaFieldView extends WatchUi.DataField {
|
||||
fElevationLoss.setText(edrop.format("%d"));
|
||||
}
|
||||
|
||||
// cadence
|
||||
// TLF
|
||||
if (fCadence != null) {
|
||||
var cadenceColor = darken(calculateZoneColor(cadence, cadenceZones, cadenceZoneColors), 1.5);
|
||||
fCadence.setColor(cadenceColor);
|
||||
if (cadence != 0) {
|
||||
fCadence.setText(cadence.format("%d"));
|
||||
if (tlFieldData == TLF_GRADE) {
|
||||
var currentGrade = grade.get() * 100;
|
||||
var gradeColor = calculateZoneColor(currentGrade, gradeZones, gradeZoneColors);
|
||||
fCadence.setColor(gradeColor);
|
||||
if (currentGrade >= 10 || currentGrade <= -10) {
|
||||
fCadence.setText(currentGrade.format("%.0f"));
|
||||
} else {
|
||||
fCadence.setText(currentGrade.format("%.1f"));
|
||||
}
|
||||
} else if (tlFieldData == TLF_GAP) {
|
||||
fCadence.setColor(themeColor2);
|
||||
if (pace != 0) {
|
||||
var gap = adjustPaceForGrade(pace, grade.get());
|
||||
// TODO color
|
||||
var gapmin = gap.toNumber();
|
||||
var gapsec = (gap - gapmin) * 60;
|
||||
fCadence.setText(gapmin.format("%d") + ":" + gapsec.format("%02d"));
|
||||
} else {
|
||||
fCadence.setText("-");
|
||||
}
|
||||
} else {
|
||||
fCadence.setText("-");
|
||||
var cadenceColor = calculateZoneColor(cadence, cadenceZones, cadenceZoneColors);
|
||||
fCadence.setColor(cadenceColor);
|
||||
if (cadence != 0) {
|
||||
fCadence.setText(cadence.format("%d"));
|
||||
} else {
|
||||
fCadence.setText("-");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
33
source/RollingAverage.mc
Normal file
33
source/RollingAverage.mc
Normal file
@ -0,0 +1,33 @@
|
||||
import Toybox.Lang;
|
||||
import Toybox.Time;
|
||||
import Toybox.Math;
|
||||
|
||||
class RollingAverage {
|
||||
hidden var _size as Number;
|
||||
hidden var _values as Array<Numeric>;
|
||||
hidden var _index as Number;
|
||||
|
||||
function initialize(size as Number) {
|
||||
_size = size;
|
||||
_values = new[size];
|
||||
_index = 0;
|
||||
}
|
||||
|
||||
function insert(value as Numeric) {
|
||||
_values[_index] = value;
|
||||
_index = (_index + 1) % _size;
|
||||
}
|
||||
|
||||
function get() as Numeric {
|
||||
var sum = 0.0;
|
||||
var count = 0;
|
||||
for (var i = 0; i < _size; i++) {
|
||||
var value = _values[i];
|
||||
if (value != null) {
|
||||
sum += value;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count == 0 ? 0.0 : sum / count;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user