unifies interface of averagers

This commit is contained in:
Lars Berning 2021-11-08 09:00:45 +01:00
parent 36fe899e81
commit 67e7dbb36c
No known key found for this signature in database
GPG Key ID: 028E73C9E1D8A0B3
9 changed files with 44 additions and 44 deletions

View File

@ -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) {

View File

@ -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

View File

@ -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

View File

@ -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
}
}

View File

@ -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()

View File

@ -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
}
}

View File

@ -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()

View File

@ -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
}
}

View File

@ -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()