From cb70962403647fdf92dd2bf5fa1ba39cd8c6ca1a Mon Sep 17 00:00:00 2001 From: Gyuri Horak Date: Fri, 27 Oct 2023 16:02:37 +0200 Subject: [PATCH] #3 gap, grade --- README.md | 5 ++- resources/settings/properties.xml | 3 +- resources/settings/settings.xml | 8 ++++ resources/strings/strings.xml | 5 +++ source/GAP.mc | 31 +++++++++++++++ source/RepaFieldView.mc | 65 ++++++++++++++++++++++++++----- source/RollingAverage.mc | 33 ++++++++++++++++ 7 files changed, 138 insertions(+), 12 deletions(-) create mode 100644 source/GAP.mc create mode 100644 source/RollingAverage.mc 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