diff --git a/README.md b/README.md
index 7694b9e..881d6cc 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/resources/settings/properties.xml b/resources/settings/properties.xml
index d3c6ee0..eb55821 100644
--- a/resources/settings/properties.xml
+++ b/resources/settings/properties.xml
@@ -1,8 +1,9 @@
- 1.1.2
+ 1.2.0
0
0088FF
FFFF00
0
false
+ 2
diff --git a/resources/settings/settings.xml b/resources/settings/settings.xml
index 1673980..c0b2595 100644
--- a/resources/settings/settings.xml
+++ b/resources/settings/settings.xml
@@ -19,6 +19,14 @@
@Strings.HrZone
+
+
+ @Strings.TLFCadence
+ @Strings.TLFGrade
+ @Strings.TLFGAP
+
+
+
diff --git a/resources/strings/strings.xml b/resources/strings/strings.xml
index 8e5839f..0e23bee 100644
--- a/resources/strings/strings.xml
+++ b/resources/strings/strings.xml
@@ -9,4 +9,9 @@
Percentage
Zone
Show speed instead of pace
+ Top-left field data
+ Cadence
+ Grade
+ Grade Adjusted Pace
+ Vertical Speed
diff --git a/source/GAP.mc b/source/GAP.mc
new file mode 100644
index 0000000..7332537
--- /dev/null
+++ b/source/GAP.mc
@@ -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> = [
+ [-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];
+}
\ No newline at end of file
diff --git a/source/RepaFieldView.mc b/source/RepaFieldView.mc
index 2c0c7b2..39a6c16 100644
--- a/source/RepaFieldView.mc
+++ b/source/RepaFieldView.mc
@@ -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) 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;
hidden var hrHist as Array;
hidden var hrZoneColors as Array;
hidden var cadenceZones as Array;
hidden var cadenceZoneColors as Array;
+ hidden var gradeZones as Array;
+ hidden var gradeZoneColors as Array;
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("-");
+ }
}
}
diff --git a/source/RollingAverage.mc b/source/RollingAverage.mc
new file mode 100644
index 0000000..ac67f01
--- /dev/null
+++ b/source/RollingAverage.mc
@@ -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;
+ 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;
+ }
+}
\ No newline at end of file