mirror of
https://github.com/dyuri/garmin-repafield.git
synced 2025-12-18 12:14: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
|
- current
|
||||||
- max
|
- max
|
||||||
- histogram
|
- histogram
|
||||||
- pace
|
- pace (or speed)
|
||||||
- current
|
- current
|
||||||
- average
|
- average
|
||||||
- distance
|
- distance
|
||||||
@ -16,7 +16,8 @@ Trail running focused Garmin watch DataField (for myself)
|
|||||||
- current
|
- current
|
||||||
- gain
|
- gain
|
||||||
- loss
|
- loss
|
||||||
- cadence
|
- grade
|
||||||
|
- cadence (instead of grade) TODO
|
||||||
- distance to destination gauge
|
- distance to destination gauge
|
||||||
|
|
||||||
# Settings
|
# Settings
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
<properties>
|
<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="themeColor" type="string">0</property>
|
||||||
<property id="themeColor2" type="string">0088FF</property>
|
<property id="themeColor2" type="string">0088FF</property>
|
||||||
<property id="themeColor3" type="string">FFFF00</property>
|
<property id="themeColor3" type="string">FFFF00</property>
|
||||||
<property id="hrDisplay" type="number">0</property>
|
<property id="hrDisplay" type="number">0</property>
|
||||||
<property id="speedNotPace" type="boolean">false</property>
|
<property id="speedNotPace" type="boolean">false</property>
|
||||||
|
<property id="tlFieldData" type="number">2</property>
|
||||||
</properties>
|
</properties>
|
||||||
|
|||||||
@ -19,6 +19,14 @@
|
|||||||
<listEntry value="2">@Strings.HrZone</listEntry>
|
<listEntry value="2">@Strings.HrZone</listEntry>
|
||||||
</settingConfig>
|
</settingConfig>
|
||||||
</setting>
|
</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">
|
<setting propertyKey="@Properties.speedNotPace" title="@Strings.SpeedNotPace">
|
||||||
<settingConfig type="boolean" />
|
<settingConfig type="boolean" />
|
||||||
</setting>
|
</setting>
|
||||||
|
|||||||
@ -9,4 +9,9 @@
|
|||||||
<string id="HrPercentage">Percentage</string>
|
<string id="HrPercentage">Percentage</string>
|
||||||
<string id="HrZone">Zone</string>
|
<string id="HrZone">Zone</string>
|
||||||
<string id="SpeedNotPace">Show speed instead of pace</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>
|
</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_PERCENT = 1;
|
||||||
const HR_TYPE_ZONE = 2;
|
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 {
|
function displayHr(hr as Number, type as Number, zones as Array<Number>) as String {
|
||||||
if (hr == 0) {
|
if (hr == 0) {
|
||||||
return "-";
|
return "-";
|
||||||
@ -39,11 +43,14 @@ class RepaFieldView extends WatchUi.DataField {
|
|||||||
hidden var themeColor3 as Number;
|
hidden var themeColor3 as Number;
|
||||||
hidden var hrDisplayType as Number;
|
hidden var hrDisplayType as Number;
|
||||||
hidden var speedNotPace as Boolean;
|
hidden var speedNotPace as Boolean;
|
||||||
|
hidden var tlFieldData as Number;
|
||||||
hidden var hrZones as Array<Number>;
|
hidden var hrZones as Array<Number>;
|
||||||
hidden var hrHist as Array<Number>;
|
hidden var hrHist as Array<Number>;
|
||||||
hidden var hrZoneColors as Array<Number>;
|
hidden var hrZoneColors as Array<Number>;
|
||||||
hidden var cadenceZones as Array<Number>;
|
hidden var cadenceZones as Array<Number>;
|
||||||
hidden var cadenceZoneColors 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 isDistanceMetric as Boolean;
|
||||||
hidden var isElevationMetric as Boolean;
|
hidden var isElevationMetric as Boolean;
|
||||||
hidden var isPaceMetric as Boolean;
|
hidden var isPaceMetric as Boolean;
|
||||||
@ -86,6 +93,7 @@ class RepaFieldView extends WatchUi.DataField {
|
|||||||
hidden var egain as Number;
|
hidden var egain as Number;
|
||||||
hidden var edrop as Number;
|
hidden var edrop as Number;
|
||||||
hidden var cadence as Number;
|
hidden var cadence as Number;
|
||||||
|
hidden var grade as RollingAverage;
|
||||||
|
|
||||||
function initialize() {
|
function initialize() {
|
||||||
DataField.initialize();
|
DataField.initialize();
|
||||||
@ -95,6 +103,7 @@ class RepaFieldView extends WatchUi.DataField {
|
|||||||
themeColor3 = Application.Properties.getValue("themeColor3").toNumberWithBase(16);
|
themeColor3 = Application.Properties.getValue("themeColor3").toNumberWithBase(16);
|
||||||
hrDisplayType = Application.Properties.getValue("hrDisplay").toNumber();
|
hrDisplayType = Application.Properties.getValue("hrDisplay").toNumber();
|
||||||
speedNotPace = Application.Properties.getValue("speedNotPace");
|
speedNotPace = Application.Properties.getValue("speedNotPace");
|
||||||
|
tlFieldData = Application.Properties.getValue("tlFieldData").toNumber();
|
||||||
|
|
||||||
hrValue = 0;
|
hrValue = 0;
|
||||||
ahrValue = 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];
|
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];
|
cadenceZones = [153, 163, 173, 183];
|
||||||
cadenceZoneColors = [Graphics.COLOR_RED, Graphics.COLOR_YELLOW, Graphics.COLOR_GREEN, Graphics.COLOR_BLUE, Graphics.COLOR_PURPLE];
|
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;
|
toDestination = 0.0f;
|
||||||
distance = 0.0f;
|
distance = 0.0f;
|
||||||
timer = 0;
|
timer = 0;
|
||||||
@ -118,6 +129,7 @@ class RepaFieldView extends WatchUi.DataField {
|
|||||||
egain = 0;
|
egain = 0;
|
||||||
edrop = 0;
|
edrop = 0;
|
||||||
cadence = 0;
|
cadence = 0;
|
||||||
|
grade = new RollingAverage(10);
|
||||||
|
|
||||||
var settings = System.getDeviceSettings();
|
var settings = System.getDeviceSettings();
|
||||||
isDistanceMetric = settings.distanceUnits == System.UNIT_METRIC;
|
isDistanceMetric = settings.distanceUnits == System.UNIT_METRIC;
|
||||||
@ -218,18 +230,31 @@ class RepaFieldView extends WatchUi.DataField {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function compute(info as Activity.Info) as Void {
|
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;
|
hrValue = info.currentHeartRate as Number;
|
||||||
tickHr(hrValue);
|
tickHr(hrValue);
|
||||||
} else {
|
} else {
|
||||||
hrValue = 0;
|
hrValue = 0;
|
||||||
}
|
}
|
||||||
if(info.averageHeartRate != null) {
|
if (info.averageHeartRate != null) {
|
||||||
ahrValue = info.averageHeartRate as Number;
|
ahrValue = info.averageHeartRate as Number;
|
||||||
} else {
|
} else {
|
||||||
ahrValue = 0;
|
ahrValue = 0;
|
||||||
}
|
}
|
||||||
if(info.maxHeartRate != null) {
|
if (info.maxHeartRate != null) {
|
||||||
mhrValue = info.maxHeartRate as Number;
|
mhrValue = info.maxHeartRate as Number;
|
||||||
} else {
|
} else {
|
||||||
mhrValue = 0;
|
mhrValue = 0;
|
||||||
@ -429,14 +454,36 @@ class RepaFieldView extends WatchUi.DataField {
|
|||||||
fElevationLoss.setText(edrop.format("%d"));
|
fElevationLoss.setText(edrop.format("%d"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// cadence
|
// TLF
|
||||||
if (fCadence != null) {
|
if (fCadence != null) {
|
||||||
var cadenceColor = darken(calculateZoneColor(cadence, cadenceZones, cadenceZoneColors), 1.5);
|
if (tlFieldData == TLF_GRADE) {
|
||||||
fCadence.setColor(cadenceColor);
|
var currentGrade = grade.get() * 100;
|
||||||
if (cadence != 0) {
|
var gradeColor = calculateZoneColor(currentGrade, gradeZones, gradeZoneColors);
|
||||||
fCadence.setText(cadence.format("%d"));
|
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 {
|
} 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