unifies interface of averagers
This commit is contained in:
parent
36fe899e81
commit
67e7dbb36c
|
|
@ -47,14 +47,14 @@ function createMovingFlankDetector (rowerSettings) {
|
|||
// lets test if pushing this value would fit the curve we are looking for
|
||||
movingAverage.pushValue(dataPoint)
|
||||
|
||||
if (movingAverage.getMovingAverage() < (rowerSettings.maximumDownwardChange * cleanDataPoints[1]) || movingAverage.getMovingAverage() > (rowerSettings.maximumUpwardChange * cleanDataPoints[1])) {
|
||||
if (movingAverage.getAverage() < (rowerSettings.maximumDownwardChange * cleanDataPoints[1]) || movingAverage.getAverage() > (rowerSettings.maximumUpwardChange * cleanDataPoints[1])) {
|
||||
// impulses are outside plausible ranges, so we assume it is close to the previous one
|
||||
log.debug(`noise filter corrected currentDt, ${dataPoint} was too much of an accelleration/decelleration with respect to ${movingAverage.getMovingAverage()}, changed to previous value, ${cleanDataPoints[1]}`)
|
||||
log.debug(`noise filter corrected currentDt, ${dataPoint} was too much of an accelleration/decelleration with respect to ${movingAverage.getAverage()}, changed to previous value, ${cleanDataPoints[1]}`)
|
||||
movingAverage.replaceLastPushedValue(cleanDataPoints[1])
|
||||
}
|
||||
|
||||
// determine the moving average, to reduce noise
|
||||
cleanDataPoints[0] = movingAverage.getMovingAverage()
|
||||
cleanDataPoints[0] = movingAverage.getAverage()
|
||||
|
||||
// determine the derived data
|
||||
if (cleanDataPoints[0] > 0) {
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ function createRowingEngine (rowerSettings) {
|
|||
let recoveryLinearDistance = 0.0
|
||||
let currentDragFactor = rowerSettings.dragFactor / 1000000
|
||||
const movingDragAverage = createMovingAverager(5, currentDragFactor)
|
||||
let dragFactor = movingDragAverage.getMovingAverage()
|
||||
let dragFactor = movingDragAverage.getAverage()
|
||||
const minimumCycleLength = rowerSettings.minimumDriveTime + rowerSettings.minimumRecoveryTime
|
||||
let cycleLength = minimumCycleLength
|
||||
let linearCycleVelocity = 0.0
|
||||
|
|
@ -126,14 +126,14 @@ function createRowingEngine (rowerSettings) {
|
|||
// Prevent division by zero and keep useless data out of our calculations
|
||||
currentDragFactor = -1 * rowerSettings.flywheelInertia * ((1 / recoveryStartAngularVelocity) - (1 / recoveryEndAngularVelocity)) / recoveryPhaseLength
|
||||
if (rowerSettings.autoAdjustDragFactor) {
|
||||
if (currentDragFactor > (movingDragAverage.getMovingAverage() * 0.75) && currentDragFactor < (movingDragAverage.getMovingAverage() * 1.40)) {
|
||||
if (currentDragFactor > (movingDragAverage.getAverage() * 0.75) && currentDragFactor < (movingDragAverage.getAverage() * 1.40)) {
|
||||
// If the calculated drag factor is close to what we expect
|
||||
movingDragAverage.pushValue(currentDragFactor)
|
||||
dragFactor = movingDragAverage.getMovingAverage()
|
||||
dragFactor = movingDragAverage.getAverage()
|
||||
log.info(`*** Calculated drag factor: ${(currentDragFactor * 1000000).toFixed(2)}`)
|
||||
} else {
|
||||
// The calculated drag factor is outside the plausible range
|
||||
log.info(`Calculated drag factor: ${(currentDragFactor * 1000000).toFixed(2)}, which is too far off the currently used dragfactor of ${movingDragAverage.getMovingAverage() * 1000000}`)
|
||||
log.info(`Calculated drag factor: ${(currentDragFactor * 1000000).toFixed(2)}, which is too far off the currently used dragfactor of ${movingDragAverage.getAverage() * 1000000}`)
|
||||
log.debug(`Time: ${totalTime.toFixed(4)} sec, impuls ${totalNumberOfImpulses}: recoveryStartAngularVelocity = ${recoveryStartAngularVelocity.toFixed(2)} rad/sec, recoveryEndAngularVelocity = ${recoveryEndAngularVelocity.toFixed(2)} rad/sec, recoveryPhaseLength = ${recoveryPhaseLength.toFixed(4)} sec`)
|
||||
}
|
||||
} else {
|
||||
|
|
@ -299,7 +299,7 @@ function createRowingEngine (rowerSettings) {
|
|||
recoveryLinearDistance = 0.0
|
||||
currentDragFactor = rowerSettings.dragFactor / 1000000
|
||||
movingDragAverage.reset()
|
||||
dragFactor = movingDragAverage.getMovingAverage()
|
||||
dragFactor = movingDragAverage.getAverage()
|
||||
cycleLength = 0.0
|
||||
linearCycleVelocity = 0.0
|
||||
totalLinearDistance = 0.0
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ function createRowingStatistics (config) {
|
|||
rowingPausedTimer = setTimeout(() => pauseRowing(), timeBetweenStrokesBeforePause)
|
||||
|
||||
// based on: http://eodg.atm.ox.ac.uk/user/dudhia/rowing/physics/ergometer.html#section11
|
||||
const calories = (4 * powerAverager.weightedAverage() + 350) * (stroke.duration) / 4200
|
||||
const calories = (4 * powerAverager.getAverage() + 350) * (stroke.duration) / 4200
|
||||
durationTotal = stroke.timeSinceStart
|
||||
powerAverager.pushValue(stroke.power)
|
||||
speedAverager.pushValue(stroke.speed)
|
||||
|
|
@ -133,28 +133,28 @@ function createRowingStatistics (config) {
|
|||
}
|
||||
|
||||
function getMetrics () {
|
||||
const splitTime = speedAverager.weightedAverage() !== 0 && lastStrokeSpeed > 0 ? (500.0 / speedAverager.weightedAverage()) : Infinity
|
||||
const splitTime = speedAverager.getAverage() !== 0 && lastStrokeSpeed > 0 ? (500.0 / speedAverager.getAverage()) : Infinity
|
||||
// todo: due to sanitization we currently do not use a consistent time throughout the engine
|
||||
// We will rework this section to use both absolute and sanitized time in the appropriate places.
|
||||
// We will also polish up the events for the recovery and drive phase, so we get clean complete strokes from the first stroke onwards.
|
||||
const averagedStrokeTime = strokeAverager.weightedAverage() > minimumStrokeTime && strokeAverager.weightedAverage() < maximumStrokeTime && lastStrokeSpeed > 0 ? strokeAverager.weightedAverage() : 0 // seconds
|
||||
const averagedStrokeTime = strokeAverager.getAverage() > minimumStrokeTime && strokeAverager.getAverage() < maximumStrokeTime && lastStrokeSpeed > 0 ? strokeAverager.getAverage() : 0 // seconds
|
||||
return {
|
||||
durationTotal,
|
||||
durationTotalFormatted: secondsToTimeString(durationTotal),
|
||||
strokesTotal,
|
||||
distanceTotal: distanceTotal > 0 ? distanceTotal : 0, // meters
|
||||
caloriesTotal: caloriesTotal, // kcal
|
||||
caloriesPerMinute: caloriesAveragerMinute.average() > 0 ? caloriesAveragerMinute.average() : 0,
|
||||
caloriesPerHour: caloriesAveragerHour.average() > 0 ? caloriesAveragerHour.average() : 0,
|
||||
caloriesPerMinute: caloriesAveragerMinute.getAverage() > 0 ? caloriesAveragerMinute.getAverage() : 0,
|
||||
caloriesPerHour: caloriesAveragerHour.getAverage() > 0 ? caloriesAveragerHour.getAverage() : 0,
|
||||
strokeTime: lastStrokeDuration, // seconds
|
||||
distance: lastStrokeDistance > 0 && lastStrokeSpeed > 0 ? lastStrokeDistance : 0, // meters
|
||||
power: powerAverager.weightedAverage() > 0 && lastStrokeSpeed > 0 ? powerAverager.weightedAverage() : 0, // watts
|
||||
power: powerAverager.getAverage() > 0 && lastStrokeSpeed > 0 ? powerAverager.getAverage() : 0, // watts
|
||||
split: splitTime, // seconds/500m
|
||||
splitFormatted: secondsToTimeString(splitTime),
|
||||
powerRatio: powerRatioAverager.weightedAverage() > 0 && lastStrokeSpeed > 0 ? powerRatioAverager.weightedAverage() : 0,
|
||||
powerRatio: powerRatioAverager.getAverage() > 0 && lastStrokeSpeed > 0 ? powerRatioAverager.getAverage() : 0,
|
||||
instantaneousTorque: instantaneousTorque,
|
||||
strokesPerMinute: averagedStrokeTime !== 0 ? (60.0 / averagedStrokeTime) : 0,
|
||||
speed: speedAverager.weightedAverage() > 0 && lastStrokeSpeed > 0 ? (speedAverager.weightedAverage() * 3.6) : 0, // km/h
|
||||
speed: speedAverager.getAverage() > 0 && lastStrokeSpeed > 0 ? (speedAverager.getAverage() * 3.6) : 0, // km/h
|
||||
strokeState: lastStrokeState,
|
||||
heartrate,
|
||||
heartrateBatteryLevel
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ function createMovingAverager (length, initValue) {
|
|||
dataPoints[0] = dataPoint
|
||||
}
|
||||
|
||||
function getMovingAverage () {
|
||||
function getAverage () {
|
||||
let i = length - 1
|
||||
let arrayTotal = 0.0
|
||||
while (i >= 0) {
|
||||
|
|
@ -48,7 +48,7 @@ function createMovingAverager (length, initValue) {
|
|||
return {
|
||||
pushValue,
|
||||
replaceLastPushedValue,
|
||||
getMovingAverage,
|
||||
getAverage,
|
||||
reset
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,20 +9,20 @@ import { createMovingAverager } from './MovingAverager.js'
|
|||
|
||||
test('average should be initValue on empty dataset', () => {
|
||||
const movingAverager = createMovingAverager(10, 5.5)
|
||||
assert.is(movingAverager.getMovingAverage(), 5.5)
|
||||
assert.is(movingAverager.getAverage(), 5.5)
|
||||
})
|
||||
|
||||
test('an averager of length 1 should return the last added value', () => {
|
||||
const movingAverager = createMovingAverager(1, 3)
|
||||
movingAverager.pushValue(9)
|
||||
assert.is(movingAverager.getMovingAverage(), 9)
|
||||
assert.is(movingAverager.getAverage(), 9)
|
||||
})
|
||||
|
||||
test('an averager of length 2 should return average of last 2 added elements', () => {
|
||||
const movingAverager = createMovingAverager(2, 3)
|
||||
movingAverager.pushValue(9)
|
||||
movingAverager.pushValue(4)
|
||||
assert.is(movingAverager.getMovingAverage(), 6.5)
|
||||
assert.is(movingAverager.getAverage(), 6.5)
|
||||
})
|
||||
|
||||
test('elements outside of range should not be considered', () => {
|
||||
|
|
@ -30,7 +30,7 @@ test('elements outside of range should not be considered', () => {
|
|||
movingAverager.pushValue(9)
|
||||
movingAverager.pushValue(4)
|
||||
movingAverager.pushValue(3)
|
||||
assert.is(movingAverager.getMovingAverage(), 3.5)
|
||||
assert.is(movingAverager.getAverage(), 3.5)
|
||||
})
|
||||
|
||||
test('replacing the last element should work as expected', () => {
|
||||
|
|
@ -38,7 +38,7 @@ test('replacing the last element should work as expected', () => {
|
|||
movingAverager.pushValue(9)
|
||||
movingAverager.pushValue(5)
|
||||
movingAverager.replaceLastPushedValue(12)
|
||||
assert.is(movingAverager.getMovingAverage(), 10.5)
|
||||
assert.is(movingAverager.getAverage(), 10.5)
|
||||
})
|
||||
|
||||
test.run()
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ function createMovingIntervalAverager (movingDuration) {
|
|||
reset()
|
||||
|
||||
function pushValue (dataValue, dataDuration) {
|
||||
// add the new dataPoint to the front of the array
|
||||
// add the new data point to the front of the array
|
||||
dataPoints.unshift({ value: dataValue, duration: dataDuration })
|
||||
duration += dataDuration
|
||||
sum += dataValue
|
||||
|
|
@ -23,7 +23,7 @@ function createMovingIntervalAverager (movingDuration) {
|
|||
}
|
||||
}
|
||||
|
||||
function average () {
|
||||
function getAverage () {
|
||||
if (duration > 0) {
|
||||
return sum / duration * movingDuration
|
||||
} else {
|
||||
|
|
@ -39,7 +39,7 @@ function createMovingIntervalAverager (movingDuration) {
|
|||
|
||||
return {
|
||||
pushValue,
|
||||
average,
|
||||
getAverage,
|
||||
reset
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,31 +7,31 @@ import * as assert from 'uvu/assert'
|
|||
|
||||
import { createMovingIntervalAverager } from './MovingIntervalAverager.js'
|
||||
|
||||
test('average of a datapoint with duration of averager is equal to datapoint', () => {
|
||||
test('average of a data point with duration of averager is equal to datapoint', () => {
|
||||
const movingAverager = createMovingIntervalAverager(10)
|
||||
movingAverager.pushValue(5, 10)
|
||||
assert.is(movingAverager.average(), 5)
|
||||
assert.is(movingAverager.getAverage(), 5)
|
||||
})
|
||||
|
||||
test('average of a datapoint with half duration of averager is double to datapoint', () => {
|
||||
test('average of a data point with half duration of averager is double to datapoint', () => {
|
||||
const movingAverager = createMovingIntervalAverager(20)
|
||||
movingAverager.pushValue(5, 10)
|
||||
assert.is(movingAverager.average(), 10)
|
||||
assert.is(movingAverager.getAverage(), 10)
|
||||
})
|
||||
|
||||
test('average of two identical datapoints with half duration of averager is equal to datapoint sum', () => {
|
||||
test('average of two identical data points with half duration of averager is equal to datapoint sum', () => {
|
||||
const movingAverager = createMovingIntervalAverager(20)
|
||||
movingAverager.pushValue(5, 10)
|
||||
movingAverager.pushValue(5, 10)
|
||||
assert.is(movingAverager.average(), 10)
|
||||
assert.is(movingAverager.getAverage(), 10)
|
||||
})
|
||||
|
||||
test('average does not consider datapoints that are outside of duration', () => {
|
||||
test('average does not consider data points that are outside of duration', () => {
|
||||
const movingAverager = createMovingIntervalAverager(20)
|
||||
movingAverager.pushValue(10, 10)
|
||||
movingAverager.pushValue(5, 10)
|
||||
movingAverager.pushValue(5, 10)
|
||||
assert.is(movingAverager.average(), 10)
|
||||
assert.is(movingAverager.getAverage(), 10)
|
||||
})
|
||||
|
||||
test('average works with lots of values', () => {
|
||||
|
|
@ -46,12 +46,12 @@ test('average works with lots of values', () => {
|
|||
for (let i = 0; i < 1000; i++) {
|
||||
movingAverager.pushValue(30, 2)
|
||||
}
|
||||
assert.is(movingAverager.average(), 50000)
|
||||
assert.is(movingAverager.getAverage(), 50000)
|
||||
})
|
||||
|
||||
test('average should return 0 on empty dataset', () => {
|
||||
const movingAverager = createMovingIntervalAverager(10)
|
||||
assert.is(movingAverager.average(), 0)
|
||||
assert.is(movingAverager.getAverage(), 0)
|
||||
})
|
||||
|
||||
test.run()
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ function createWeightedAverager (maxNumOfDataPoints) {
|
|||
let dataPoints = []
|
||||
|
||||
function pushValue (dataPoint) {
|
||||
// add the new dataPoint to the front of the array
|
||||
// add the new data point to the front of the array
|
||||
dataPoints.unshift(dataPoint)
|
||||
// ensure that the array does not get longer than maxNumOfDataPoints
|
||||
if (dataPoints.length > maxNumOfDataPoints) {
|
||||
|
|
@ -16,7 +16,7 @@ function createWeightedAverager (maxNumOfDataPoints) {
|
|||
}
|
||||
}
|
||||
|
||||
function weightedAverage () {
|
||||
function getAverage () {
|
||||
const numOfDataPoints = dataPoints.length
|
||||
if (numOfDataPoints > 0) {
|
||||
const sum = dataPoints
|
||||
|
|
@ -35,7 +35,7 @@ function createWeightedAverager (maxNumOfDataPoints) {
|
|||
|
||||
return {
|
||||
pushValue,
|
||||
weightedAverage,
|
||||
getAverage,
|
||||
reset
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,20 +9,20 @@ import { createWeightedAverager } from './WeightedAverager.js'
|
|||
|
||||
test('average should be 0 on empty dataset', () => {
|
||||
const weightedAverager = createWeightedAverager(10)
|
||||
assert.is(weightedAverager.weightedAverage(), 0)
|
||||
assert.is(weightedAverager.getAverage(), 0)
|
||||
})
|
||||
|
||||
test('average of one value is value', () => {
|
||||
const weightedAverager = createWeightedAverager(10)
|
||||
weightedAverager.pushValue(13.78)
|
||||
assert.is(weightedAverager.weightedAverage(), 13.78)
|
||||
assert.is(weightedAverager.getAverage(), 13.78)
|
||||
})
|
||||
|
||||
test('average of a and b is (2*b + a) / 3', () => {
|
||||
const weightedAverager = createWeightedAverager(10)
|
||||
weightedAverager.pushValue(5) // a
|
||||
weightedAverager.pushValue(2) // b
|
||||
assert.is(weightedAverager.weightedAverage(), 3)
|
||||
assert.is(weightedAverager.getAverage(), 3)
|
||||
})
|
||||
|
||||
test('average should be 0 after reset', () => {
|
||||
|
|
@ -30,7 +30,7 @@ test('average should be 0 after reset', () => {
|
|||
weightedAverager.pushValue(5)
|
||||
weightedAverager.pushValue(2)
|
||||
weightedAverager.reset()
|
||||
assert.is(weightedAverager.weightedAverage(), 0)
|
||||
assert.is(weightedAverager.getAverage(), 0)
|
||||
})
|
||||
|
||||
test('average should be a after pushing a after a reset', () => {
|
||||
|
|
@ -39,7 +39,7 @@ test('average should be a after pushing a after a reset', () => {
|
|||
weightedAverager.pushValue(2)
|
||||
weightedAverager.reset()
|
||||
weightedAverager.pushValue(7)
|
||||
assert.is(weightedAverager.weightedAverage(), 7)
|
||||
assert.is(weightedAverager.getAverage(), 7)
|
||||
})
|
||||
|
||||
test.run()
|
||||
|
|
|
|||
Loading…
Reference in New Issue