Skip to content

Commit f829482

Browse files
authored
Improved CyclicErrorCorrection (#164)
Improvement of the CyclicErrorCorrection, and more extended use of GoodnessOfFit properties
1 parent ed63851 commit f829482

18 files changed

+1020
-639
lines changed

app/engine/Flywheel.js

Lines changed: 34 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,12 @@ export function createFlywheel (rowerSettings) {
6161
const cyclicErrorFilter = createCyclicErrorFilter(rowerSettings, minimumDragFactorSamples, recoveryDeltaTime)
6262
const strokedetectionMinimalGoodnessOfFit = rowerSettings.minimumStrokeQuality
6363
const minimumRecoverySlope = createWeighedSeries(rowerSettings.dragFactorSmoothing, rowerSettings.minimumRecoverySlope)
64-
let totalTime
65-
let currentAngularDistance
66-
let _deltaTimeBeforeFlank
64+
let rawTime = 0
65+
let rawNumberOfImpulses = 0
66+
let totalTimeSpinning = 0
67+
let totalNumberOfImpulses = 0
68+
let _totalWork = 0
69+
let _deltaTimeBeforeFlank = {}
6770
let _angularVelocityAtBeginFlank
6871
let _angularVelocityBeforeFlank
6972
let _angularAccelerationAtBeginFlank
@@ -72,11 +75,8 @@ export function createFlywheel (rowerSettings) {
7275
let _torqueBeforeFlank
7376
let inRecoveryPhase
7477
let maintainMetrics
75-
let totalNumberOfImpulses
76-
let totalTimeSpinning
77-
let _totalWork
7878
reset()
79-
79+
8080
/**
8181
* @param {float} dataPoint - The lenght of the impuls (currentDt) in seconds
8282
* @description This function is called from Rower.js each time the sensor detected an impulse. It transforms this (via the buffers) into a robust flywheel position, speed and acceleration.
@@ -112,40 +112,41 @@ export function createFlywheel (rowerSettings) {
112112
// value before the shift is certain to be part of a specific rowing phase (i.e. Drive or Recovery), once the buffer is filled completely
113113
totalNumberOfImpulses += 1
114114

115-
_deltaTimeBeforeFlank = cyclicErrorFilter.clean.atSeriesBegin()
116-
totalTimeSpinning += _deltaTimeBeforeFlank
115+
_deltaTimeBeforeFlank = cyclicErrorFilter.atSeriesBegin()
116+
totalTimeSpinning += _deltaTimeBeforeFlank.clean
117117
_angularVelocityBeforeFlank = _angularVelocityAtBeginFlank
118118
_angularAccelerationBeforeFlank = _angularAccelerationAtBeginFlank
119119
// As drag is recalculated at the begin of the drive, we need to recalculate the torque
120120
_torqueBeforeFlank = (rowerSettings.flywheelInertia * _angularAccelerationBeforeFlank + drag.weighedAverage() * Math.pow(_angularVelocityBeforeFlank, 2))
121121

122122
if (inRecoveryPhase) {
123123
// Feed the drag calculation, as we didn't reset the Semaphore in the previous cycle based on the current flank
124-
recoveryDeltaTime.push(totalTimeSpinning, _deltaTimeBeforeFlank)
124+
recoveryDeltaTime.push(totalTimeSpinning, _deltaTimeBeforeFlank.clean, _deltaTimeBeforeFlank.goodnessOfFit)
125125
// Feed the systematic error filter buffer
126-
cyclicErrorFilter.recordRawDatapoint(totalNumberOfImpulses, totalTimeSpinning, cyclicErrorFilter.raw.atSeriesBegin())
126+
cyclicErrorFilter.recordRawDatapoint(totalNumberOfImpulses, totalTimeSpinning, _deltaTimeBeforeFlank.raw)
127127
} else {
128128
// Accumulate the energy total as we are in the drive phase
129129
_totalWork += Math.max(_torqueBeforeFlank * angularDisplacementPerImpulse, 0)
130130
// Process a value in the systematic error filter buffer. We need to do this slowly to prevent radical changes which might disturbe the force curve etc.
131131
cyclicErrorFilter.processNextRawDatapoint()
132132
}
133133
} else {
134-
_deltaTimeBeforeFlank = 0
134+
_deltaTimeBeforeFlank.clean = 0
135135
_angularVelocityBeforeFlank = 0
136136
_angularAccelerationBeforeFlank = 0
137137
_torqueBeforeFlank = 0
138138
}
139139

140140
const cleanCurrentDt = cyclicErrorFilter.applyFilter(dataPoint, totalNumberOfImpulses + flankLength)
141-
totalTime += cleanCurrentDt.value
142-
currentAngularDistance += angularDisplacementPerImpulse
141+
rawTime += cleanCurrentDt.value
142+
rawNumberOfImpulses++
143+
const currentAngularDistance = rawNumberOfImpulses * angularDisplacementPerImpulse
143144

144145
// Let's feed the stroke detection algorithm
145-
_deltaTime.push(totalTime, cleanCurrentDt.value)
146+
_deltaTime.push(rawTime, cleanCurrentDt.value, cleanCurrentDt.goodnessOfFit)
146147

147148
// Calculate the metrics that are needed for more advanced metrics, like the foce curve
148-
_angularDistance.push(totalTime, currentAngularDistance)
149+
_angularDistance.push(rawTime, currentAngularDistance, cleanCurrentDt.goodnessOfFit)
149150
_angularVelocityAtBeginFlank = _angularDistance.firstDerivative(0)
150151
_angularAccelerationAtBeginFlank = _angularDistance.secondDerivative(0)
151152
_torqueAtBeginFlank = (rowerSettings.flywheelInertia * _angularAccelerationAtBeginFlank + drag.weighedAverage() * Math.pow(_angularVelocityAtBeginFlank, 2))
@@ -190,10 +191,10 @@ export function createFlywheel (rowerSettings) {
190191
if (rowerSettings.autoAdjustRecoverySlope) {
191192
// We are allowed to autoadjust stroke detection slope as well, so let's do that
192193
minimumRecoverySlope.push((1 - rowerSettings.autoAdjustRecoverySlopeMargin) * recoveryDeltaTime.slope(), recoveryDeltaTime.goodnessOfFit())
193-
log.debug(`*** Calculated recovery slope: ${recoveryDeltaTime.slope().toFixed(6)}, Goodness of Fit: ${recoveryDeltaTime.goodnessOfFit().toFixed(4)}`)
194+
log.trace(`*** Calculated recovery slope: ${recoveryDeltaTime.slope().toFixed(6)}, Goodness of Fit: ${recoveryDeltaTime.goodnessOfFit().toFixed(4)}`)
194195
} else {
195196
// We aren't allowed to adjust the slope, let's report the slope to help help the user configure it
196-
log.debug(`*** Calculated recovery slope: ${recoveryDeltaTime.slope().toFixed(6)}, Goodness of Fit: ${recoveryDeltaTime.goodnessOfFit().toFixed(4)}, not used as autoAdjustRecoverySlope isn't set to true`)
197+
log.trace(`*** Calculated recovery slope: ${recoveryDeltaTime.slope().toFixed(6)}, Goodness of Fit: ${recoveryDeltaTime.goodnessOfFit().toFixed(4)}, not used as autoAdjustRecoverySlope isn't set to true`)
197198
}
198199
} else {
199200
// As the drag calculation is considered unreliable, we must skip updating the systematic error filter that depends on it
@@ -225,7 +226,7 @@ export function createFlywheel (rowerSettings) {
225226
* @returns {float} the current DeltaTime BEFORE the flank
226227
*/
227228
function deltaTime () {
228-
return _deltaTimeBeforeFlank
229+
return _deltaTimeBeforeFlank.clean
229230
}
230231

231232
/**
@@ -397,27 +398,35 @@ export function createFlywheel (rowerSettings) {
397398
}
398399
}
399400

401+
/**
402+
* @param {float} slope - Recovery slope to be converted
403+
* @returns {float} Dragfactor to be used in all calculations
404+
* @description Helper function to convert a recovery slope into a dragfactor
405+
*/
400406
function slopeToDrag (slope) {
401407
return ((slope * rowerSettings.flywheelInertia) / angularDisplacementPerImpulse)
402408
}
403409

410+
/**
411+
* @description This function is used for clearing all data, returning the flywheel.js to its initial state
412+
*/
404413
function reset () {
405414
maintainMetrics = false
406415
inRecoveryPhase = false
416+
rawTime = 0
417+
rawNumberOfImpulses = 0
418+
totalTimeSpinning = 0
419+
totalNumberOfImpulses = -1
420+
_totalWork = 0
407421
drag.reset()
408422
cyclicErrorFilter.reset()
409423
cyclicErrorFilter.applyFilter(0, flankLength - 1)
410424
recoveryDeltaTime.reset()
411425
_deltaTime.reset()
412426
_angularDistance.reset()
413-
totalTime = 0
414-
currentAngularDistance = 0
415-
totalNumberOfImpulses = -1
416-
totalTimeSpinning = 0
417-
_totalWork = 0
418427
_deltaTime.push(0, 0)
419428
_angularDistance.push(0, 0)
420-
_deltaTimeBeforeFlank = 0
429+
_deltaTimeBeforeFlank.clean = 0
421430
_angularVelocityBeforeFlank = 0
422431
_angularAccelerationBeforeFlank = 0
423432
_torqueAtBeginFlank = 0

0 commit comments

Comments
 (0)