From 5a065ca07cdb72e6d9acc2c1925cc2cac3e8a6c8 Mon Sep 17 00:00:00 2001 From: Ben Walding Date: Sun, 29 Apr 2018 14:19:03 +1000 Subject: [PATCH] Add Lap Distance and Lap Distance Remaining dials and telemetry --- src/Train/DialWindow.cpp | 11 +++++ src/Train/ErgFile.cpp | 30 +++++++++++- src/Train/ErgFile.h | 4 +- src/Train/RealtimeData.cpp | 32 +++++++++++++ src/Train/RealtimeData.h | 9 +++- src/Train/TrainSidebar.cpp | 95 ++++++++++++++++++++++++++++++++++++-- src/Train/TrainSidebar.h | 4 ++ 7 files changed, 177 insertions(+), 8 deletions(-) diff --git a/src/Train/DialWindow.cpp b/src/Train/DialWindow.cpp index a91cfa20e..1106397bc 100644 --- a/src/Train/DialWindow.cpp +++ b/src/Train/DialWindow.cpp @@ -241,7 +241,16 @@ DialWindow::telemetryUpdate(const RealtimeData &rtData) valueLabel->setText(QString("%1").arg(value, 0, 'f', 1)); break; + // If the distance remaining is negative, it is either irrelevant, or we've passed the last lap marker + case RealtimeData::LapDistanceRemaining: + if (value < 0) { + valueLabel->setText("N/A"); + break; + } + // intentional fall-through to standard distance rendering + case RealtimeData::Distance: + case RealtimeData::LapDistance: if (!context->athlete->useMetricUnits) value *= MILES_PER_KM; valueLabel->setText(QString("%1").arg(value, 0, 'f', 3)); break; @@ -543,6 +552,8 @@ void DialWindow::seriesChanged() case RealtimeData::LapTime: case RealtimeData::LapTimeRemaining: case RealtimeData::Distance: + case RealtimeData::LapDistance: + case RealtimeData::LapDistanceRemaining: case RealtimeData::LRBalance: case RealtimeData::Lap: case RealtimeData::RI: diff --git a/src/Train/ErgFile.cpp b/src/Train/ErgFile.cpp index eec72510e..05bbb2d25 100644 --- a/src/Train/ErgFile.cpp +++ b/src/Train/ErgFile.cpp @@ -553,14 +553,23 @@ void ErgFile::parseComputrainer(QString p) if (section == END && Points.count() > 0) { valid = true; - // add the last point for a crs file if (mode == CRS) { + // Add the last point for a crs file ErgFilePoint add; add.x = rdist; add.val = 0.0; add.y = ralt; Points.append(add); if (add.y > MaxWatts) MaxWatts=add.y; + + // Add a final meta-lap at the end - causes the system to correctly mark off laps. + // An improvement would be to check for a lap marker at end, and only add this if required. + ErgFileLap lap; + lap.x = rdist; + lap.LapNum = ++lapcounter; + lap.selected = false; + lap.name = lapmarker.cap(2).simplified(); + Laps.append(lap); } // add a start point if it doesn't exist @@ -1014,12 +1023,16 @@ ErgFile::gradientAt(long x, int &lapnum) return Points.at(leftPoint).val; } +// Retrieve the offset for the start of next lap. +// Params: x - current workout distance (m) / time (ms) +// Returns: distance (m) / time (ms) offset for next lap. int ErgFile::nextLap(long x) { if (!isValid()) return -1; // not a valid ergfile // do we need to return the Lap marker? if (Laps.count() > 0) { + // If the current position is before the start the lap, then the lap is next for (int i=0; idistance = x; } +void RealtimeData::setLapDistance(double x) +{ + this->lapDistance = x; +} + +void RealtimeData::setLapDistanceRemaining(double x) +{ + this->lapDistanceRemaining = x; +} + void RealtimeData::setLRBalance(double x) { this->lrbalance = x; @@ -194,6 +204,14 @@ double RealtimeData::getDistance() const { return distance; } +double RealtimeData::getLapDistance() const +{ + return lapDistance; +} +double RealtimeData::getLapDistanceRemaining() const +{ + return lapDistanceRemaining; +} double RealtimeData::getLRBalance() const { return lrbalance; @@ -298,6 +316,12 @@ double RealtimeData::value(DataSeries series) const case Distance: return distance; break; + case LapDistance: return lapDistance; + break; + + case LapDistanceRemaining: return lapDistanceRemaining; + break; + case AltWatts: return altWatts; break; @@ -406,6 +430,8 @@ const QList &RealtimeData::listDataSeries() seriesList << LeftPedalSmoothness; seriesList << RightPedalSmoothness; seriesList << Slope; + seriesList << LapDistance; + seriesList << LapDistanceRemaining; } return seriesList; } @@ -537,6 +563,12 @@ QString RealtimeData::seriesName(DataSeries series) case Slope: return tr("Slope"); break; + + case LapDistance: return tr("Lap Distance"); + break; + + case LapDistanceRemaining: return tr("Lap Distance Remaining"); + break; } } diff --git a/src/Train/RealtimeData.h b/src/Train/RealtimeData.h index e4c84e15c..21377cad3 100644 --- a/src/Train/RealtimeData.h +++ b/src/Train/RealtimeData.h @@ -43,7 +43,8 @@ public: AvgWattsLap, AvgSpeedLap, AvgCadenceLap, AvgHeartRateLap, VirtualSpeed, AltWatts, LRBalance, LapTimeRemaining, LeftTorqueEffectiveness, RightTorqueEffectiveness, - LeftPedalSmoothness, RightPedalSmoothness, Slope}; + LeftPedalSmoothness, RightPedalSmoothness, Slope, + LapDistance, LapDistanceRemaining }; typedef enum dataseries DataSeries; @@ -75,6 +76,8 @@ public: void setJoules(long); void setXPower(long); void setLap(long); + void setLapDistance(double distance); + void setLapDistanceRemaining(double distance); void setLRBalance(double); void setLTE(double); void setRTE(double); @@ -107,6 +110,8 @@ public: long getLapMsecs() const; double getDistance() const; long getLap() const; + double getLapDistance() const; + double getLapDistanceRemaining() const; double getLRBalance() const; double getLTE() const; double getRTE() const; @@ -141,6 +146,8 @@ private: // derived data double distance; + double lapDistance; + double lapDistanceRemaining; double virtualSpeed; double wbal; double hhb, o2hb; diff --git a/src/Train/TrainSidebar.cpp b/src/Train/TrainSidebar.cpp index b7e56e363..37e04a82c 100644 --- a/src/Train/TrainSidebar.cpp +++ b/src/Train/TrainSidebar.cpp @@ -812,11 +812,67 @@ TrainSidebar::workoutTreeWidgetSelectionChanged() foreach(int dev, activeDevices) Devices[dev].controller->setMode(RT_MODE_SPIN); } + updateMetricLapDistanceRemaining(); + // clean last if (prior) delete prior; } +/* + * Calculates the current lap distance + * + * Needs to be called as the lapping occurs, otherwise you get + * skew due to inaccuracies in lap calculations. + */ +void +TrainSidebar::updateMetricLapDistance() +{ + // lapDistance is only relevant for SLOPE ERG files + if (!ergFile || !(status&RT_MODE_SLOPE)) { + displayLapDistance = 0; + return; + } + + // XXX This might have sub-optimal display in the final lap of a file. + double currentposition = displayWorkoutDistance*1000; + double lapmarker = ergFile->currentLap(currentposition); + if (lapmarker == -1) { + displayLapDistance = 0; + return; + } + + displayLapDistance = (currentposition - lapmarker) / (double) 1000; +} + +/* + * Calculates the lap distance remaining in the current lap. + * + * Can be called at any time, but better to just decrement the displayLapDistanceRemaining + * variable as the workout progresses. + */ +void +TrainSidebar::updateMetricLapDistanceRemaining() +{ + // lapDistanceRemaining is only relevant for SLOPE ERG files + if (!ergFile || !(status&RT_MODE_SLOPE)) { + displayLapDistanceRemaining = -1; + return; + } + + // Review what happens when we are at the end of the course and there are no more lap markers. + // perhaps we should look at course length. + double currentposition = displayWorkoutDistance*1000; + double lapmarker = ergFile->nextLap(currentposition); + if (lapmarker == -1) { + // In this case, there are either no lap markers, or we are in last lap (and so no next lap) + displayLapDistanceRemaining = -1; + return; + } + + displayLapDistanceRemaining = (lapmarker - currentposition) / (double) 1000; +} + QStringList TrainSidebar::listWorkoutFiles(const QDir &dir) const { @@ -1352,6 +1408,8 @@ void TrainSidebar::Stop(int deviceStatus) // when stop button is pressed lap_elapsed_msec = 0; lap_time.restart(); displayWorkoutDistance = displayDistance = 0; + displayLapDistance = 0; + displayLapDistanceRemaining = -1; guiUpdate(); emit setNotification(tr("Stopped.."), 2); @@ -1556,6 +1614,8 @@ void TrainSidebar::guiUpdate() // refreshes the telemetry if (dev == kphTelemetry) { rtData.setSpeed(local.getSpeed()); rtData.setDistance(local.getDistance()); + rtData.setLapDistance(local.getLapDistance()); + rtData.setLapDistanceRemaining(local.getLapDistanceRemaining()); } if (dev == wattsTelemetry) { rtData.setWatts(local.getWatts()); @@ -1580,16 +1640,29 @@ void TrainSidebar::guiUpdate() // refreshes the telemetry // only update time & distance if actively running (not just connected, and not running but paused) if ((status&RT_RUNNING) && ((status&RT_PAUSED) == 0)) { // Distance assumes current speed for the last second. from km/h to km/sec - displayDistance += displaySpeed / (5 * 3600); // assumes 200ms refreshrate + double distanceTick = displaySpeed / (5 * 3600); // assumes 200ms refreshrate + displayDistance += distanceTick; + displayLapDistance += distanceTick; + displayLapDistanceRemaining -= distanceTick; - if (!(status&RT_MODE_ERGO) && (context->currentVideoSyncFile())) - { + + if (!(status&RT_MODE_ERGO) && (context->currentVideoSyncFile())) { displayWorkoutDistance = context->currentVideoSyncFile()->km + context->currentVideoSyncFile()->manualOffset; // TODO : graphs to be shown at seek position + } else { + displayWorkoutDistance += distanceTick; } - else - displayWorkoutDistance += displaySpeed / (5 * 3600); // assumes 200ms refreshrate + + // If we just tripped over the end of the lap, we need to look at base data + // to find distance to next lap. This is primarily due to lap display updates + // -0.999 is chosen as a number that is less than 0, but greater than -1 + if (displayLapDistanceRemaining < 0 && displayLapDistanceRemaining > -0.999) { + updateMetricLapDistanceRemaining(); + } + rtData.setDistance(displayDistance); + rtData.setLapDistance(displayLapDistance); + rtData.setLapDistanceRemaining(displayLapDistanceRemaining); // time total_msecs = session_elapsed_msec + session_time.elapsed(); @@ -1632,6 +1705,8 @@ void TrainSidebar::guiUpdate() // refreshes the telemetry rtData.setLapMsecsRemaining(lapTimeRemaining); } else { rtData.setDistance(displayDistance); + rtData.setLapDistance(displayLapDistance); + rtData.setLapDistanceRemaining(displayLapDistanceRemaining); rtData.setMsecs(session_elapsed_msec); rtData.setLapMsecs(lap_elapsed_msec); } @@ -1737,6 +1812,10 @@ void TrainSidebar::newLap() hrcount = 0; spdcount = 0; + // This forces a hard reset of the lap marker. + displayLapDistance = 0; + updateMetricLapDistanceRemaining(); + context->notifyNewLap(); emit setNotification(tr("New lap.."), 2); @@ -1748,6 +1827,8 @@ void TrainSidebar::resetLapTimer() lap_time.restart(); lap_elapsed_msec = 0; lapAudioThisLap = true; + displayLapDistance = 0; + this->updateMetricLapDistanceRemaining(); } // Can be called from the controller - when user steers to scroll display @@ -1836,6 +1917,8 @@ void TrainSidebar::loadUpdate() if(displayWorkoutLap != curLap) { context->notifyNewLap(); + updateMetricLapDistance(); + updateMetricLapDistanceRemaining(); } displayWorkoutLap = curLap; @@ -1852,6 +1935,8 @@ void TrainSidebar::loadUpdate() if(displayWorkoutLap != curLap) { context->notifyNewLap(); + updateMetricLapDistance(); + updateMetricLapDistanceRemaining(); } displayWorkoutLap = curLap; diff --git a/src/Train/TrainSidebar.h b/src/Train/TrainSidebar.h index d0d91c5ef..72c68459b 100644 --- a/src/Train/TrainSidebar.h +++ b/src/Train/TrainSidebar.h @@ -247,6 +247,7 @@ class TrainSidebar : public GcWindow double displayLRBalance, displayLTE, displayRTE, displayLPS, displayRPS; double displaySMO2, displayTHB, displayO2HB, displayHHB; double displayDistance, displayWorkoutDistance; + double displayLapDistance, displayLapDistanceRemaining; long load; double slope; int displayLap; // user increment for Lap @@ -254,6 +255,9 @@ class TrainSidebar : public GcWindow bool lapAudioEnabled; bool lapAudioThisLap; + void updateMetricLapDistance(); + void updateMetricLapDistanceRemaining(); + // for non-zero average calcs int pwrcount, cadcount, hrcount, spdcount, lodcount, grdcount; // for NZ average calc int status;