openrowingmonitor/app/server.js

158 lines
5.3 KiB
JavaScript

'use strict'
/*
Open Rowing Monitor, https://github.com/laberning/openrowingmonitor
This start file is currently a mess, as this currently is the devlopment playground to plug
everything together while figuring out the physics and model of the application.
todo: refactor this as we progress
*/
import { fork } from 'child_process'
import log from 'loglevel'
import config from './tools/ConfigManager.js'
import { createRowingEngine } from './engine/RowingEngine.js'
import { createRowingStatistics } from './engine/RowingStatistics.js'
import { createWebServer } from './WebServer.js'
import { createPeripheralManager } from './ble/PeripheralManager.js'
import { createAntManager } from './ant/AntManager.js'
// eslint-disable-next-line no-unused-vars
import { replayRowingSession } from './tools/RowingRecorder.js'
import { createWorkoutRecorder } from './engine/WorkoutRecorder.js'
import { createWorkoutUploader } from './engine/WorkoutUploader.js'
// set the log levels
log.setLevel(config.loglevel.default)
for (const [loggerName, logLevel] of Object.entries(config.loglevel)) {
if (loggerName !== 'default') {
log.getLogger(loggerName).setLevel(logLevel)
}
}
log.info(`==== Open Rowing Monitor ${process.env.npm_package_version || ''} ====\n`)
const peripheralManager = createPeripheralManager()
peripheralManager.on('control', (event) => {
if (event?.req?.name === 'requestControl') {
event.res = true
} else if (event?.req?.name === 'reset') {
log.debug('reset requested')
resetWorkout()
event.res = true
// todo: we could use these controls once we implement a concept of a rowing session
} else if (event?.req?.name === 'stop') {
log.debug('stop requested')
peripheralManager.notifyStatus({ name: 'stoppedOrPausedByUser' })
event.res = true
} else if (event?.req?.name === 'pause') {
log.debug('pause requested')
peripheralManager.notifyStatus({ name: 'stoppedOrPausedByUser' })
event.res = true
} else if (event?.req?.name === 'startOrResume') {
log.debug('startOrResume requested')
peripheralManager.notifyStatus({ name: 'startedOrResumedByUser' })
event.res = true
} else if (event?.req?.name === 'peripheralMode') {
webServer.notifyClients({ peripheralMode: event.req.peripheralMode })
event.res = true
} else {
log.info('unhandled Command', event.req)
}
})
function resetWorkout () {
workoutRecorder.reset()
rowingEngine.reset()
rowingStatistics.reset()
peripheralManager.notifyStatus({ name: 'reset' })
}
const gpioTimerService = fork('./app/gpio/GpioTimerService.js')
gpioTimerService.on('message', handleRotationImpulse)
function handleRotationImpulse (dataPoint) {
workoutRecorder.recordRotationImpulse(dataPoint)
rowingEngine.handleRotationImpulse(dataPoint)
}
const rowingEngine = createRowingEngine(config.rowerSettings)
const rowingStatistics = createRowingStatistics(config)
rowingEngine.notify(rowingStatistics)
const workoutRecorder = createWorkoutRecorder()
const workoutUploader = createWorkoutUploader(workoutRecorder)
rowingStatistics.on('driveFinished', (metrics) => {
webServer.notifyClients(metrics)
peripheralManager.notifyMetrics('strokeStateChanged', metrics)
})
rowingStatistics.on('recoveryFinished', (metrics) => {
log.info(`stroke: ${metrics.strokesTotal}, dur: ${metrics.strokeTime.toFixed(2)}s, power: ${Math.round(metrics.power)}w` +
`, split: ${metrics.splitFormatted}, ratio: ${metrics.powerRatio.toFixed(2)}, dist: ${metrics.distanceTotal.toFixed(1)}m` +
`, cal: ${metrics.caloriesTotal.toFixed(1)}kcal, SPM: ${metrics.strokesPerMinute.toFixed(1)}, speed: ${metrics.speed.toFixed(2)}km/h` +
`, cal/hour: ${metrics.caloriesPerHour.toFixed(1)}kcal, cal/minute: ${metrics.caloriesPerMinute.toFixed(1)}kcal`)
webServer.notifyClients(metrics)
peripheralManager.notifyMetrics('strokeFinished', metrics)
if (metrics.sessionState === 'rowing') {
workoutRecorder.recordStroke(metrics)
}
})
rowingStatistics.on('webMetricsUpdate', (metrics) => {
webServer.notifyClients(metrics)
})
rowingStatistics.on('peripheralMetricsUpdate', (metrics) => {
peripheralManager.notifyMetrics('metricsUpdate', metrics)
})
rowingStatistics.on('rowingPaused', () => {
workoutRecorder.handlePause()
})
if (config.heartrateMonitorBLE) {
const bleCentralService = fork('./app/ble/CentralService.js')
bleCentralService.on('message', (heartrateMeasurement) => {
rowingStatistics.handleHeartrateMeasurement(heartrateMeasurement)
})
}
if (config.heartrateMonitorANT) {
const antManager = createAntManager()
antManager.on('heartrateMeasurement', (heartrateMeasurement) => {
rowingStatistics.handleHeartrateMeasurement(heartrateMeasurement)
})
}
const webServer = createWebServer()
webServer.on('messageReceived', (message) => {
switch (message.command) {
case 'switchPeripheralMode': {
peripheralManager.switchPeripheralMode()
break
}
case 'reset': {
resetWorkout()
break
}
case 'uploadTraining': {
workoutUploader.upload()
break
}
default: {
log.warn('invalid command received:', message)
}
}
})
webServer.on('clientConnected', () => {
webServer.notifyClients({ peripheralMode: peripheralManager.getPeripheralMode() })
})
/*
replayRowingSession(handleRotationImpulse, {
filename: 'recordings/WRX700_2magnets.csv',
realtime: true,
loop: true
})
*/