openrowingmonitor/app/engine/RowingStatistics.js

133 lines
4.3 KiB
JavaScript

'use strict'
/*
Open Rowing Monitor, https://github.com/laberning/openrowingmonitor
This Module calculates the training specific metrics.
*/
import log from 'loglevel'
import { EventEmitter } from 'events'
import { createAverager } from './Averager.js'
// The number of strokes that are considered when averaging the calculated metrics
// Higher values create more stable metrics but make them less responsive
const numOfDataPointsForAveraging = 3
function createRowingStatistics () {
const emitter = new EventEmitter()
const strokeAverager = createAverager(numOfDataPointsForAveraging)
const powerAverager = createAverager(numOfDataPointsForAveraging)
const speedAverager = createAverager(numOfDataPointsForAveraging)
const powerRatioAverager = createAverager(numOfDataPointsForAveraging)
let trainingRunning = false
let durationTimer
let rowingPausedTimer
let distanceTotal = 0.0
let durationTotal = 0
let strokesTotal = 0
let caloriesTotal = 0.0
function handleStroke (stroke) {
if (!trainingRunning) startTraining()
// if we do not get a stroke for 6 seconds we treat this as a rowing pause
if (rowingPausedTimer)clearInterval(rowingPausedTimer)
rowingPausedTimer = setTimeout(() => pauseRowing(), 6000)
powerAverager.pushValue(stroke.power)
speedAverager.pushValue(stroke.distance / stroke.duration)
powerRatioAverager.pushValue(stroke.durationDrivePhase / stroke.duration)
strokeAverager.pushValue(stroke.duration)
distanceTotal += stroke.distance
strokesTotal++
// based on: http://eodg.atm.ox.ac.uk/user/dudhia/rowing/physics/ergometer.html#section11
caloriesTotal += (4 * powerAverager.weightedAverage() + 350) * (stroke.duration) / 4200
const splitTime = 500.0 / speedAverager.weightedAverage()
emitter.emit('strokeFinished', {
strokesTotal: strokesTotal,
distanceTotal: Math.round(distanceTotal), // meters
caloriesTotal: Math.round(caloriesTotal), // kcal
strokeTime: stroke.duration.toFixed(2), // seconds
power: Math.round(powerAverager.weightedAverage()), // watts
split: splitTime, // seconds/500m
splitFormatted: secondsToTimeString(splitTime),
powerRatio: powerRatioAverager.weightedAverage().toFixed(2),
strokesPerMinute: Math.round(60.0 / strokeAverager.weightedAverage()),
speed: (speedAverager.weightedAverage() * 3.6).toFixed(2) // km/h
})
}
// initiated by the rowing engine in case an impulse was not considered
// because it was to large
function handlePause (duration) {
}
function startTraining () {
trainingRunning = true
startDurationTimer()
}
function stopTraining () {
trainingRunning = false
stopDurationTimer()
if (rowingPausedTimer)clearInterval(rowingPausedTimer)
}
function resetTraining () {
stopTraining()
distanceTotal = 0.0
strokesTotal = 0
caloriesTotal = 0.0
durationTotal = 0
strokeAverager.reset()
powerAverager.reset()
speedAverager.reset()
powerRatioAverager.reset()
}
// clear the displayed metrics in case the user pauses rowing
function pauseRowing () {
strokeAverager.reset()
powerAverager.reset()
speedAverager.reset()
powerRatioAverager.reset()
log.debug('rowing pause detected')
emitter.emit('rowingPaused', {
strokesTotal: strokesTotal,
distanceTotal: Math.round(distanceTotal),
caloriesTotal: Math.round(caloriesTotal)
})
}
function startDurationTimer () {
durationTimer = setInterval(() => {
durationTotal++
emitter.emit('durationUpdate', { durationTotal: secondsToTimeString(durationTotal) })
}, 1000)
}
function stopDurationTimer () {
clearInterval(durationTimer)
durationTimer = undefined
}
// converts a timeStamp in seconds to a human readable hh:mm:ss format
function secondsToTimeString (secondsTimeStamp) {
const hours = Math.floor(secondsTimeStamp / 60 / 60)
const minutes = Math.floor(secondsTimeStamp / 60) - (hours * 60)
const seconds = Math.floor(secondsTimeStamp % 60)
let timeString = hours > 0 ? ` ${hours.toString().padStart(2, '0')}:` : ''
timeString += `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`
return timeString
}
return Object.assign(emitter, {
handleStroke,
handlePause,
reset: resetTraining
})
}
export { createRowingStatistics }