updates documentation, fixes typos

This commit is contained in:
Lars Berning 2021-11-07 21:51:11 +01:00
parent 8f064a0aab
commit 02359e819d
No known key found for this signature in database
GPG Key ID: 028E73C9E1D8A0B3
7 changed files with 75 additions and 83 deletions

View File

@ -2,20 +2,20 @@
/*
Open Rowing Monitor, https://github.com/laberning/openrowingmonitor
This keeps an array, which we can ask for an moving average
This Averager can calculate the moving average of a continuous flow of data points
Please note: The array contains flankLength + 1 measured currentDt's, thus flankLength number of flanks between them
They are arranged that dataPoints[0] is the youngest, and dataPoints[flankLength] the youngest
They are arranged that dataPoints[0] is the youngest, and dataPoints[flankLength] the oldest
*/
function createMovingAverager (length, initValue) {
let dataPoints
reset()
function pushValue (dataPoint) {
// add the new dataPoint to the array, we have to move datapoints starting at the oldst ones
// add the new dataPoint to the array, we have to move data points starting at the oldest ones
let i = length - 1
while (i > 0) {
// older datapoints are moved toward the higher numbers
// older data points are moved towards the higher numbers
dataPoints[i] = dataPoints[i - 1]
i = i - 1
}
@ -31,7 +31,7 @@ function createMovingAverager (length, initValue) {
let i = length - 1
let arrayTotal = 0.0
while (i >= 0) {
// summarise the value of the moving average
// summarize the value of the moving average
arrayTotal = arrayTotal + dataPoints[i]
i = i - 1
}

View File

@ -2,7 +2,7 @@
/*
Open Rowing Monitor, https://github.com/laberning/openrowingmonitor
This keeps an array, which we can test for an upgoing or downgoing flank
A Detector used to test for up-going and down-going flanks
Please note: The array contains flankLength + 1 measured currentDt's, thus flankLength number of flanks between them
They are arranged that dataPoints[0] is the youngest, and dataPoints[flankLength] the oldest
@ -24,10 +24,10 @@ function createMovingFlankDetector (rowerSettings) {
const movingAverage = createMovingAverager(rowerSettings.smoothing, rowerSettings.maximumTimeBetweenImpulses)
function pushValue (dataPoint) {
// add the new dataPoint to the array, we have to move datapoints starting at the oldst ones
// add the new dataPoint to the array, we have to move data points starting at the oldest ones
let i = rowerSettings.flankLength
while (i > 0) {
// older datapoints are moved toward the higher numbers
// older data points are moved toward the higher numbers
dirtyDataPoints[i] = dirtyDataPoints[i - 1]
cleanDataPoints[i] = cleanDataPoints[i - 1]
angularVelocity[i] = angularVelocity[i - 1]
@ -70,12 +70,14 @@ function createMovingFlankDetector (rowerSettings) {
function isFlywheelUnpowered () {
let numberOfErrors = 0
if (rowerSettings.naturalDeceleration < 0) {
// A valid natural deceleration of the flywheel has been provided, this has to be maintained for a flanklength to count as an indication for an unpowered flywheel
// Please note that angularAcceleration[] contains flank-information already, so we need to check from rowerSettings.flankLength -1 until 0 flanks
// A valid natural deceleration of the flywheel has been provided, this has to be maintained for a flank length
// to count as an indication for an unpowered flywheel
// Please note that angularAcceleration[] contains flank-information already, so we need to check from
// rowerSettings.flankLength -1 until 0 flanks
let i = rowerSettings.flankLength - 1
while (i >= 0) {
if (angularAcceleration[i] > rowerSettings.naturalDeceleration) {
// There seems to be some power present, so we detecten an error
// There seems to be some power present, so we detected an error
numberOfErrors = numberOfErrors + 1
}
i = i - 1
@ -85,7 +87,8 @@ function createMovingFlankDetector (rowerSettings) {
let i = rowerSettings.flankLength
while (i > 0) {
if (cleanDataPoints[i] >= cleanDataPoints[i - 1]) {
// Oldest interval (dataPoints[i]) is larger than the younger one (datapoint[i-1], as the distance is fixed, we are accelerating
// Oldest interval (dataPoints[i]) is larger than the younger one (datapoint[i-1], as the distance is
// fixed, we are accelerating
numberOfErrors = numberOfErrors + 1
}
i = i - 1
@ -101,8 +104,10 @@ function createMovingFlankDetector (rowerSettings) {
function isFlywheelPowered () {
let numberOfErrors = 0
if (rowerSettings.naturalDeceleration < 0) {
// A valid natural deceleration of the flywheel has been provided, this has to be consistently be encountered for a flanklength to count as an indication of a powered flywheel
// Please note that angularAcceleration[] contains flank-information already, so we need to check from rowerSettings.flankLength -1 until 0 flanks
// A valid natural deceleration of the flywheel has been provided, this has to be consistently encountered
// for a flank length to count as an indication of a powered flywheel
// Please note that angularAcceleration[] contains flank-information already, so we need to check from
// rowerSettings.flankLength -1 until 0 flanks
let i = rowerSettings.flankLength - 1
while (i >= 0) {
if (angularAcceleration[i] < rowerSettings.naturalDeceleration) {
@ -116,7 +121,8 @@ function createMovingFlankDetector (rowerSettings) {
let i = rowerSettings.flankLength
while (i > 1) {
if (cleanDataPoints[i] < cleanDataPoints[i - 1]) {
// Oldest interval (dataPoints[i]) is shorter than the younger one (datapoint[i-1], as the distance is fixed, we discovered a deceleration
// Oldest interval (dataPoints[i]) is shorter than the younger one (datapoint[i-1], as the distance is fixed, we
// discovered a deceleration
numberOfErrors = numberOfErrors + 1
}
i = i - 1
@ -135,7 +141,7 @@ function createMovingFlankDetector (rowerSettings) {
}
function timeToBeginOfFlank () {
// You expect the curve to bend between dirtyDataPoints[rowerSettings.flankLength] and dirtyDataPoints[rowerSettings.flankLength+1],
// We expect the curve to bend between dirtyDataPoints[rowerSettings.flankLength] and dirtyDataPoints[rowerSettings.flankLength+1],
// as acceleration FOLLOWS the start of the pulling the handle, we assume it must have started before that
let i = rowerSettings.flankLength
let total = 0.0
@ -151,9 +157,10 @@ function createMovingFlankDetector (rowerSettings) {
}
function impulseLengthAtBeginFlank () {
// As this is fed into the speed calculation where small changes have big effects, and we typically use it when the curve is in a plateau,
// we return the cleaned data and not the dirty data
// Regardless of the way to determine the acceleration, cleanDataPoints[rowerSettings.flankLength] is always the impuls at the beginning of the flank being investigated
// As this is fed into the speed calculation where small changes have big effects, and we typically use it when
// the curve is in a plateau, we return the cleaned data and not the dirty data
// Regardless of the way to determine the acceleration, cleanDataPoints[rowerSettings.flankLength] is always the
// impulse at the beginning of the flank being investigated
return cleanDataPoints[rowerSettings.flankLength]
}

View File

@ -2,7 +2,7 @@
/*
Open Rowing Monitor, https://github.com/laberning/openrowingmonitor
This averager calculates the average forcast for a moving inteval of a continuous flow
This Averager calculates the average forecast for a moving interval of a continuous flow
of data points for a certain (time) interval
*/
function createMovingIntervalAverager (movingDuration) {

View File

@ -19,7 +19,6 @@ const log = loglevel.getLogger('RowingEngine')
function createRowingEngine (rowerSettings) {
let workoutHandler
const flankDetector = createMovingFlankDetector(rowerSettings)
let movementAllowed = true
let cyclePhase = 'Drive'
let totalTime = 0.0
let totalNumberOfImpulses = 0.0
@ -43,8 +42,8 @@ function createRowingEngine (rowerSettings) {
let currentDragFactor = rowerSettings.dragFactor / 1000000
const movingDragAverage = createMovingAverager(5, currentDragFactor)
let dragFactor = movingDragAverage.getMovingAverage()
const minimumCycleLenght = rowerSettings.minimumDriveTime + rowerSettings.minimumRecoveryTime
let cycleLenght = minimumCycleLenght
const minimumCycleLength = rowerSettings.minimumDriveTime + rowerSettings.minimumRecoveryTime
let cycleLength = minimumCycleLength
let linearCycleVelocity = 0.0
let totalLinearDistance = 0.0
let averagedCyclePower = 0.0
@ -54,12 +53,6 @@ function createRowingEngine (rowerSettings) {
// called if the sensor detected an impulse, currentDt is an interval in seconds
function handleRotationImpulse (currentDt) {
// First we check if the rower is allowed to move
if (movementAllowed !== true) {
// The rower isn't allowed to move
return
}
// impulses that take longer than maximumImpulseTimeBeforePause seconds are considered a pause
if (currentDt > rowerSettings.maximumImpulseTimeBeforePause) {
workoutHandler.handlePause(currentDt)
@ -69,18 +62,19 @@ function createRowingEngine (rowerSettings) {
totalTime += currentDt
totalNumberOfImpulses++
// STEP 2: detect where we are in the rowing phase (drive or recovery)
// detect where we are in the rowing phase (drive or recovery)
flankDetector.pushValue(currentDt)
// Here we implement the finite state machine that goes between "Drive" and "Recovery" phases,
// It will allow a phase-change provinding sufficient time has passed and there is a credible flank
// we implement a finite state machine that goes between "Drive" and "Recovery" phases,
// which allows a phase-change if sufficient time has passed and there is a plausible flank
if (cyclePhase === 'Drive') {
// We currently are in the "Drive" phase, lets determine what the next phase is
if (flankDetector.isFlywheelUnpowered()) {
// The flankdetector detects that the flywheel has no power excerted on it
// The flank detector detects that the flywheel has no power exerted on it
drivePhaseLength = (totalTime - flankDetector.timeToBeginOfFlank()) - drivePhaseStartTime
if (drivePhaseLength >= rowerSettings.minimumDriveTime) {
// We change into the Revocevery phase since we have been long enough in the Drive phase, and we see a clear lack of power excerted on the flywheel
// We change into the "Recovery" phase since we have been long enough in the Drive phase, and we see a clear lack of power
// exerted on the flywheel
startRecoveryPhase(currentDt)
cyclePhase = 'Recovery'
} else {
@ -95,32 +89,33 @@ function createRowingEngine (rowerSettings) {
} else {
// We currently are in the "Recovery" phase, lets determine what the next phase is
if (flankDetector.isFlywheelPowered()) {
// The flankdetector consistently detects some force on the flywheel
// The flank detector consistently detects some force on the flywheel
recoveryPhaseLength = (totalTime - flankDetector.timeToBeginOfFlank()) - recoveryPhaseStartTime
if (recoveryPhaseLength >= rowerSettings.minimumRecoveryTime) {
// We change into the Drive phase if we have been long enough in the Recovery phase, and we see a conistent force being excerted on the flywheel
// We change into the "Drive" phase if we have been long enough in the "Recovery" phase, and we see a consistent force being
// exerted on the flywheel
startDrivePhase(currentDt)
cyclePhase = 'Drive'
} else {
// We see a force, but the recovery phase has been too short, we stay in the recovery phase
// We see a force, but the "Recovery" phase has been too short, we stay in the "Recovery" phase
log.debug(`Time: ${totalTime.toFixed(4)} sec, impuls ${totalNumberOfImpulses}: flank suggests power (${flankDetector.accelerationAtBeginOfFlank().toFixed(1)} rad/s2), but waiting for recoveryPhaseLength (${recoveryPhaseLength.toFixed(4)} sec) to exceed minimumRecoveryTime (${rowerSettings.minimumRecoveryTime} sec)`)
updateRecoveryPhase(currentDt)
}
} else {
// No force on the flywheel, let's continue the drive phase
// No force on the flywheel, let's continue the "Drive" phase
updateRecoveryPhase(currentDt)
}
}
}
function startDrivePhase (currentDt) {
// First, we conclude the recovery phase
// First, we conclude the "Recovery" phase
log.debug('*** recovery phase completed')
if (rowerSettings.minimumRecoveryTime <= recoveryPhaseLength && rowerSettings.minimumDriveTime <= drivePhaseLength) {
// We have a credible cycletime
cycleLenght = recoveryPhaseLength + drivePhaseLength
// We have a plausible cycle time
cycleLength = recoveryPhaseLength + drivePhaseLength
} else {
log.debug(`Cyclelenght isn't credible: recoveryPhaseLength ${recoveryPhaseLength.toFixed(4)} sec, drivePhaseLength = ${drivePhaseLength.toFixed(4)} s, maximumImpulseTimeBeforePause ${rowerSettings.maximumImpulseTimeBeforePause} s`)
log.debug(`CycleLength isn't plausible: recoveryPhaseLength ${recoveryPhaseLength.toFixed(4)} sec, drivePhaseLength = ${drivePhaseLength.toFixed(4)} s, maximumImpulseTimeBeforePause ${rowerSettings.maximumImpulseTimeBeforePause} s`)
}
recoveryPhaseAngularDisplacement = (totalNumberOfImpulses - recoveryPhaseStartAngularDisplacement) * angularDisplacementPerImpulse
@ -132,12 +127,12 @@ function createRowingEngine (rowerSettings) {
currentDragFactor = -1 * rowerSettings.flywheelInertia * ((1 / recoveryStartAngularVelocity) - (1 / recoveryEndAngularVelocity)) / recoveryPhaseLength
if (rowerSettings.autoAdjustDragFactor) {
if (currentDragFactor > (movingDragAverage.getMovingAverage() * 0.75) && currentDragFactor < (movingDragAverage.getMovingAverage() * 1.40)) {
// If the calculated dragfactor is close to that we expect
// If the calculated drag factor is close to what we expect
movingDragAverage.pushValue(currentDragFactor)
dragFactor = movingDragAverage.getMovingAverage()
log.info(`*** Calculated drag factor: ${(currentDragFactor * 1000000).toFixed(2)}`)
} else {
// The calculated drag factor is outside the credible ranges
// 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.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`)
}
@ -159,15 +154,15 @@ function createRowingEngine (rowerSettings) {
currentAngularVelocity = angularDisplacementPerImpulse / currentDt
currentTorque = rowerSettings.flywheelInertia * ((currentAngularVelocity - previousAngularVelocity) / currentDt) + dragFactor * Math.pow(currentAngularVelocity, 2)
}
if (cycleLenght >= minimumCycleLenght) {
// There is no division by zero and the data data is credible
linearCycleVelocity = Math.pow((dragFactor / rowerSettings.magicConstant), 1.0 / 3.0) * ((recoveryPhaseAngularDisplacement + drivePhaseAngularDisplacement) / cycleLenght)
averagedCyclePower = dragFactor * Math.pow((recoveryPhaseAngularDisplacement + drivePhaseAngularDisplacement) / cycleLenght, 3.0)
if (cycleLength >= minimumCycleLength) {
// There is no division by zero and the data data is plausible
linearCycleVelocity = Math.pow((dragFactor / rowerSettings.magicConstant), 1.0 / 3.0) * ((recoveryPhaseAngularDisplacement + drivePhaseAngularDisplacement) / cycleLength)
averagedCyclePower = dragFactor * Math.pow((recoveryPhaseAngularDisplacement + drivePhaseAngularDisplacement) / cycleLength, 3.0)
} else {
log.error(`Time: ${totalTime.toFixed(4)} sec, impuls ${totalNumberOfImpulses}: cycle length was not credible, cycleLenght = ${cycleLenght} sec`)
log.error(`Time: ${totalTime.toFixed(4)} sec, impuls ${totalNumberOfImpulses}: cycle length was not plausible, CycleLength = ${cycleLength} sec`)
}
// Next, we start the Drive Phase
// Next, we start the "Drive" Phase
log.debug(`*** DRIVE phase started at time: ${totalTime.toFixed(4)} sec, impuls number ${totalNumberOfImpulses}`)
strokeNumber++
drivePhaseStartTime = totalTime - flankDetector.timeToBeginOfFlank()
@ -180,7 +175,7 @@ function createRowingEngine (rowerSettings) {
timeSinceStart: totalTime,
// currDragFactor : currentDragFactor,
power: averagedCyclePower,
duration: cycleLenght,
duration: cycleLength,
strokeDistance: driveLinearDistance + recoveryLinearDistance,
durationDrivePhase: drivePhaseLength,
speed: linearCycleVelocity,
@ -211,13 +206,13 @@ function createRowingEngine (rowerSettings) {
}
function startRecoveryPhase (currentDt) {
// First, we conclude the Drive Phase
// First, we conclude the "Drive" Phase
log.debug('*** drive phase completed')
if (rowerSettings.minimumRecoveryTime <= recoveryPhaseLength && rowerSettings.minimumDriveTime <= drivePhaseLength) {
// We have a credible cycletime
cycleLenght = recoveryPhaseLength + drivePhaseLength
// We have a plausible cycle time
cycleLength = recoveryPhaseLength + drivePhaseLength
} else {
log.debug(`Cycleleght wasn't credible: recoveryPhaseLength ${recoveryPhaseLength.toFixed(4)} sec, drivePhaseLength = ${drivePhaseLength.toFixed(4)} s`)
log.debug(`CycleLength wasn't plausible: recoveryPhaseLength ${recoveryPhaseLength.toFixed(4)} sec, drivePhaseLength = ${drivePhaseLength.toFixed(4)} s`)
}
drivePhaseAngularDisplacement = ((totalNumberOfImpulses - flankDetector.noImpulsesToBeginFlank()) - drivePhaseStartAngularDisplacement) * angularDisplacementPerImpulse
// driveEndAngularVelocity = angularDisplacementPerImpulse / flankDetector.impulseLengthAtBeginFlank()
@ -228,17 +223,17 @@ function createRowingEngine (rowerSettings) {
currentAngularVelocity = angularDisplacementPerImpulse / currentDt
currentTorque = rowerSettings.flywheelInertia * ((currentAngularVelocity - previousAngularVelocity) / currentDt) + dragFactor * Math.pow(currentAngularVelocity, 2)
}
// We display the AVERAGE speed in the display, NOT the topspeed of the stroke
if (drivePhaseLength > rowerSettings.minimumDriveTime && cycleLenght > minimumCycleLenght) {
// let's prevent division's by zero and make sure data is credible
linearCycleVelocity = Math.pow((dragFactor / rowerSettings.magicConstant), 1.0 / 3.0) * ((drivePhaseAngularDisplacement + recoveryPhaseAngularDisplacement) / cycleLenght)
// We display the AVERAGE speed in the display, NOT the top speed of the stroke
if (drivePhaseLength > rowerSettings.minimumDriveTime && cycleLength > minimumCycleLength) {
// let's prevent division's by zero and make sure data is plausible
linearCycleVelocity = Math.pow((dragFactor / rowerSettings.magicConstant), 1.0 / 3.0) * ((drivePhaseAngularDisplacement + recoveryPhaseAngularDisplacement) / cycleLength)
// drivePhaseEnergyProduced = rowerSettings.flywheelInertia * ((driveEndAngularVelocity - driveStartAngularVelocity) / drivePhaseLength) * drivePhaseAngularDisplacement + dragFactor * Math.pow(driveEndAngularVelocity, 2) * drivePh$
averagedCyclePower = dragFactor * Math.pow((recoveryPhaseAngularDisplacement + drivePhaseAngularDisplacement) / cycleLenght, 3.0)
averagedCyclePower = dragFactor * Math.pow((recoveryPhaseAngularDisplacement + drivePhaseAngularDisplacement) / cycleLength, 3.0)
} else {
log.error(`Time: ${totalTime.toFixed(4)} sec, impuls ${totalNumberOfImpulses}: cycle length was not credible, drivePhaseLength = ${drivePhaseLength.toFixed(4)} sec, cycleLenght = ${cycleLenght.toFixed(4)} sec`)
log.error(`Time: ${totalTime.toFixed(4)} sec, impuls ${totalNumberOfImpulses}: cycle length was not plausible, drivePhaseLength = ${drivePhaseLength.toFixed(4)} sec, cycleLength = ${cycleLength.toFixed(4)} sec`)
}
// Next, we start the Recovery Phase
// Next, we start the "Recovery" Phase
log.debug(`*** RECOVERY phase started at time: ${totalTime.toFixed(4)} sec, impuls number ${totalNumberOfImpulses}`)
recoveryPhaseStartTime = totalTime - flankDetector.timeToBeginOfFlank()
recoveryPhaseStartAngularDisplacement = totalNumberOfImpulses - flankDetector.noImpulsesToBeginFlank()
@ -253,7 +248,7 @@ function createRowingEngine (rowerSettings) {
workoutHandler.handleStrokeEnd({
timeSinceStart: totalTime,
power: averagedCyclePower,
duration: cycleLenght,
duration: cycleLength,
strokeDistance: driveLinearDistance + recoveryLinearDistance,
durationDrivePhase: drivePhaseLength,
speed: linearCycleVelocity,
@ -282,14 +277,6 @@ function createRowingEngine (rowerSettings) {
}
}
function allowMovement () {
movementAllowed = true
}
function stopMoving () {
movementAllowed = false
}
function reset () {
cyclePhase = 'Drive'
totalTime = 0.0
@ -313,7 +300,7 @@ function createRowingEngine (rowerSettings) {
currentDragFactor = rowerSettings.dragFactor / 1000000
movingDragAverage.reset()
dragFactor = movingDragAverage.getMovingAverage()
cycleLenght = 0.0
cycleLength = 0.0
linearCycleVelocity = 0.0
totalLinearDistance = 0.0
averagedCyclePower = 0.0
@ -325,8 +312,6 @@ function createRowingEngine (rowerSettings) {
return {
handleRotationImpulse,
allowMovement,
stopMoving,
reset,
notify
}

View File

@ -53,7 +53,7 @@ function createRowingStatistics (config) {
function handleStrokeEnd (stroke) {
if (!trainingRunning) startTraining()
// if we do not get a stroke for timeBetweenStrokesBeforePause miliseconds we treat this as a rowing pause
// if we do not get a stroke for timeBetweenStrokesBeforePause milliseconds we treat this as a rowing pause
if (rowingPausedTimer)clearInterval(rowingPausedTimer)
rowingPausedTimer = setTimeout(() => pauseRowing(), timeBetweenStrokesBeforePause)
@ -63,7 +63,7 @@ function createRowingStatistics (config) {
powerAverager.pushValue(stroke.power)
speedAverager.pushValue(stroke.speed)
if (stroke.duration < timeBetweenStrokesBeforePause && stroke.duration > minimumStrokeTime) {
// stroke duration has to be credible to be accepted
// stroke duration has to be plausible to be accepted
powerRatioAverager.pushValue(stroke.durationDrivePhase / stroke.duration)
strokeAverager.pushValue(stroke.duration)
caloriesAveragerMinute.pushValue(calories, stroke.duration)
@ -92,13 +92,13 @@ function createRowingStatistics (config) {
// initiated when the stroke state changes
function handleRecoveryEnd (stroke) {
// todo: wee need a better mechanism to communicate strokeState updates
// todo: we need a better mechanism to communicate strokeState updates
// this is an initial hacky attempt to see if we can use it for the C2-pm5 protocol
durationTotal = stroke.timeSinceStart
powerAverager.pushValue(stroke.power)
speedAverager.pushValue(stroke.speed)
if (stroke.duration < timeBetweenStrokesBeforePause && stroke.duration > minimumStrokeTime) {
// stroke duration has to be credible to be accepted
// stroke duration has to be plausible to be accepted
powerRatioAverager.pushValue(stroke.durationDrivePhase / stroke.duration)
strokeAverager.pushValue(stroke.duration)
} else {
@ -120,9 +120,9 @@ function createRowingStatistics (config) {
instantaneousTorque = stroke.instantaneousTorque
}
// initiated when new heart rate value is received from heart rate sensor
// initiated when a new heart rate value is received from heart rate sensor
function handleHeartrateMeasurement (value) {
// set the heart rate to zero, if we did not receive a value for some time
// set the heart rate to zero if we did not receive a value for some time
if (heartrateResetTimer)clearInterval(heartrateResetTimer)
heartrateResetTimer = setTimeout(() => {
heartrate = 0
@ -194,7 +194,7 @@ function createRowingStatistics (config) {
emitter.emit('rowingPaused')
}
// converts a timeStamp in seconds to a human readable hh:mm:ss format
// converts a timestamp in seconds to a human readable hh:mm:ss format
function secondsToTimeString (secondsTimeStamp) {
if (secondsTimeStamp === Infinity) return '∞'
const hours = Math.floor(secondsTimeStamp / 60 / 60)

View File

@ -2,7 +2,7 @@
/*
Open Rowing Monitor, https://github.com/laberning/openrowingmonitor
The Averager calculates the weighted average of a continuous flow of data points
This Averager can calculate the weighted average of a continuous flow of data points
*/
function createWeightedAverager (maxNumOfDataPoints) {
let dataPoints = []

View File

@ -73,7 +73,7 @@ export default {
// A constant that is commonly used to convert flywheel revolutions to a rowed distance
// see here: http://eodg.atm.ox.ac.uk/user/dudhia/rowing/physics/ergometer.html#section9
// Concept2 seems to use 2.8, which they admit is an arbitrary number which came close
// to their expectations. So for your rower, you have to find a credible distance for your effort.
// to their expectations. So for your rower, you have to find a plausible distance for your effort.
// Also note that the rowed distance also depends on flywheelInertia, so please calibrate that before changing this constant.
// PLEASE NOTE: Increasing this number decreases your rowed meters
magicConstant: 2.8