diff --git a/.eslintrc.json b/.eslintrc.json index 8b7936e..baf6fd8 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -11,7 +11,7 @@ "ecmaVersion": 12, "sourceType": "module" }, - "ignorePatterns": ["**/*.min.js"], + "ignorePatterns": ["**/*.min.js", "**/tools/ConfigManager.js"], "rules": { "camelcase": 0 } diff --git a/.gitignore b/.gitignore index 1e7e2fd..b598574 100644 --- a/.gitignore +++ b/.gitignore @@ -77,3 +77,4 @@ node_modules ._* tmp/ build/ +config/config.js diff --git a/app/ble/CentralService.js b/app/ble/CentralService.js index 4781902..f8b28a5 100644 --- a/app/ble/CentralService.js +++ b/app/ble/CentralService.js @@ -4,12 +4,10 @@ Starts the central manager in a forked thread since noble does not like to run in the same thread as bleno - todo: check if noble would also work if we move this into a worker thread - (would save some ressources) */ import { createCentralManager } from './CentralManager.js' import process from 'process' -import config from '../config.js' +import config from '../tools/ConfigManager.js' import log from 'loglevel' log.setLevel(config.loglevel.default) diff --git a/app/ble/PeripheralManager.js b/app/ble/PeripheralManager.js index 66ebc00..12371f6 100644 --- a/app/ble/PeripheralManager.js +++ b/app/ble/PeripheralManager.js @@ -5,7 +5,7 @@ This manager creates the different Bluetooth Low Energy (BLE) Peripherals and allows switching between them */ -import config from '../config.js' +import config from '../tools/ConfigManager.js' import { createFtmsPeripheral } from './FtmsPeripheral.js' import { createPm5Peripheral } from './Pm5Peripheral.js' import log from 'loglevel' diff --git a/app/config.js b/app/config.js deleted file mode 100644 index c5ca020..0000000 --- a/app/config.js +++ /dev/null @@ -1,19 +0,0 @@ -'use strict' -/* - Open Rowing Monitor, https://github.com/laberning/openrowingmonitor - - This file contains the app specific configuration. - Modify it to your needs. -*/ -import log from 'loglevel' -export default { - loglevel: { - // the default loglevel - default: log.levels.INFO, - // the log level of some modules can be set individually to filter noise - RowingEngine: log.levels.WARN - }, - // selects the Bluetooth Low Energy Profile - // supported modes: FTMS, FTMSBIKE, PM5 - bluetoothMode: 'FTMS' -} diff --git a/app/engine/RowingEngine.js b/app/engine/RowingEngine.js index 8fc3a1a..888ef44 100644 --- a/app/engine/RowingEngine.js +++ b/app/engine/RowingEngine.js @@ -16,51 +16,45 @@ import { createTimer } from './Timer.js' const log = loglevel.getLogger('RowingEngine') -// ***************************************************** -// These constants are specific to your Rowing Machine -// ***************************************************** +function createRowingEngine (rowerSettings) { + // How many impulses are triggered per revolution of the flywheel + // i.e. the number of magnets if used with a reed sensor + const numOfImpulsesPerRevolution = rowerSettings.numOfImpulsesPerRevolution -// How many impulses are triggered per revolution of the flywheel -// i.e. the number of magnets if used with a reed sensor -const numOfImpulsesPerRevolution = 2 + // Needed to determine the damping constant of the rowing machine. This value can be measured in the recovery phase + // of the stroke (some ergometers do this constantly). + // However I still keep it constant here, as I still have to figure out the damping physics of a water rower (see below) + // To measure it for your rowing machine, comment in the logging at the end of "startDrivePhase" function. Then do some + // strokes on the rower and estimate a value. + const omegaDotDivOmegaSquare = rowerSettings.omegaDotDivOmegaSquare -// Needed to determine the damping constant of the rowing machine. This value can be measured in the recovery phase -// of the stroke (some ergometers do this constantly). -// However I still keep it constant here, as I still have to figure out the damping physics of a water rower (see below) -// To measure it for your rowing machine, comment in the logging at the end of "startDrivePhase" function. Then do some -// strokes on the rower and estimate a value. -const omegaDotDivOmegaSquare = 0.046 + // The moment of inertia of the flywheel kg*m^2 + // A way to measure it is outlined here: https://dvernooy.github.io/projects/ergware/, "Flywheel moment of inertia" + // You could also roughly estimate it by just doing some strokes and the comparing the calculated power values for + // plausibility. Note that the power also depends on omegaDotDivOmegaSquare (see above). + const jMoment = rowerSettings.jMoment -// The moment of inertia of the flywheel kg*m^2 -// A way to measure it is outlined here: https://dvernooy.github.io/projects/ergware/, "Flywheel moment of inertia" -// You could also roughly estimate it by just doing some strokes and the comparing the calculated power values for -// plausibility. Note that the power also depends on omegaDotDivOmegaSquare (see above). -const jMoment = 0.49 + // Set this to true if you are using a water rower + // The mass of the water starts rotating, when you pull the handle, and therefore acts + // like a massive flywheel + // Liquids are a tricky thing and therefore the dumping constant does not seem to be + // that constant on water rowers... + // This is WIP, but for now this setting is used to figure out the drive and recovery phases + // differently on water rowers + const liquidFlywheel = rowerSettings.liquidFlywheel -// Set this to true if you are using a water rower -// The mass of the water starts rotating, when you pull the handle, and therefore acts -// like a massive flywheel -// Liquids are a tricky thing and therefore the dumping constant does not seem to be -// that constant on water rowers... -// This is WIP, but for now this setting is used to figure out the drive and recovery phases -// differently on water rowers -const liquidFlywheel = true + // 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 + const c = 2.8 -// 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 -const c = 2.8 + // jMoment * ωdot = -kDamp * ω^2 during non-power part of stroke + const kDamp = jMoment * omegaDotDivOmegaSquare -// jMoment * ωdot = -kDamp * ω^2 during non-power part of stroke -const kDamp = jMoment * omegaDotDivOmegaSquare + // s = (k/c)^(1/3)*θ + const distancePerRevolution = 2.0 * Math.PI * Math.pow((kDamp / c), 1.0 / 3.0) -// s = (k/c)^(1/3)*θ -const distancePerRevolution = 2.0 * Math.PI * Math.pow((kDamp / c), 1.0 / 3.0) - -function createRowingEngine () { let workoutHandler - const kDampEstimatorAverager = createWeightedAverager(3) - let kPower = 0.0 let jPower = 0.0 let kDampEstimator = 0.0 diff --git a/app/server.js b/app/server.js index 67d9de2..6a58d0f 100644 --- a/app/server.js +++ b/app/server.js @@ -10,7 +10,7 @@ import { fork } from 'child_process' import log from 'loglevel' // eslint-disable-next-line no-unused-vars import fs from 'fs' -import config from './config.js' +import config from './tools/ConfigManager.js' import { createRowingEngine } from './engine/RowingEngine.js' import { createRowingStatistics } from './engine/RowingStatistics.js' import { createWebServer } from './WebServer.js' @@ -65,7 +65,7 @@ gpioTimerService.on('message', (dataPoint) => { // fs.appendFile('recordings/wrx700_2magnets_long.csv', `${dataPoint}\n`, (err) => { if (err) log.error(err) }) }) -const rowingEngine = createRowingEngine() +const rowingEngine = createRowingEngine(config.rowerSettings) const rowingStatistics = createRowingStatistics() rowingEngine.notify(rowingStatistics) diff --git a/app/tools/ConfigManager.js b/app/tools/ConfigManager.js new file mode 100644 index 0000000..794ac06 --- /dev/null +++ b/app/tools/ConfigManager.js @@ -0,0 +1,41 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/laberning/openrowingmonitor + + Merges the different config files and presents the configuration to the application +*/ +import defaultConfig from '../../config/default.config.js' + +async function getConfig () { + let customConfig + try { + customConfig = await import('../../config/config.js') + } catch (exception) {} + + return customConfig !== undefined ? deepMerge(defaultConfig, customConfig.default) : defaultConfig +} + +function deepMerge (...objects) { + const isObject = obj => obj && typeof obj === 'object' + + return objects.reduce((prev, obj) => { + Object.keys(obj).forEach(key => { + const pVal = prev[key] + const oVal = obj[key] + + if (Array.isArray(pVal) && Array.isArray(oVal)) { + prev[key] = pVal.concat(...oVal) + } else if (isObject(pVal) && isObject(oVal)) { + prev[key] = deepMerge(pVal, oVal) + } else { + prev[key] = oVal + } + }) + + return prev + }, {}) +} + +const config = await getConfig() + +export default config diff --git a/config/default.config.js b/config/default.config.js new file mode 100644 index 0000000..f272d66 --- /dev/null +++ b/config/default.config.js @@ -0,0 +1,33 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/laberning/openrowingmonitor + + This file contains the default configuration of the Open Rowing Monitor. + + !!! Note that changes to this file will be OVERWRITTEN when you update to a new version + of Open Rowing Monitor. !!! + + To change the settings you should modify the 'config.js' in this folder. If 'config.js' does not + exist, you can use the example file from the 'install' folder. +*/ +import rowerProfiles from './rowerProfiles.js' + +export default { + // available log levels: trace, debug, info, warn, error, silent + loglevel: { + // the default loglevel + default: 'info', + // the log level of of the rowing engine (stroke detection and physics model) + RowingEngine: 'warn' + }, + + // selects the Bluetooth Low Energy Profile + // supported modes: FTMS, FTMSBIKE, PM5 + bluetoothMode: 'FTMS', + + // the rower specific settings. Either choose a profile from config/rowerProfiles.js or + // define the settings individually. If you find good settings for a new rowing device + // please send them to us (together with a raw recording of 10 strokes) so we can add + // the device to the profiles. + rowerSettings: rowerProfiles.DEFAULT +} diff --git a/config/rowerProfiles.js b/config/rowerProfiles.js new file mode 100644 index 0000000..059e76e --- /dev/null +++ b/config/rowerProfiles.js @@ -0,0 +1,57 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/laberning/openrowingmonitor + + This file contains the rower specific settings for different models of ergometers. + + These have been generated by the community. If your rower is not listed here and you did find + good settings for your rowing device please send them to us (together with a raw recording of + 10 strokes) so we can add the device here. +*/ +export default { + + // Profile for an example rower + DEFAULT: { + // How many impulses are triggered per revolution of the flywheel + // i.e. the number of magnets if used with a reed sensor + numOfImpulsesPerRevolution: 1, + + // Needed to determine the damping constant of the rowing machine. This value can be measured in the recovery phase + // of the stroke (some ergometers do this constantly). + // However I still keep it constant here, as I still have to figure out the damping physics of a water rower (see below) + // To measure it for your rowing machine, comment in the logging at the end of "startDrivePhase" function. Then do some + // strokes on the rower and estimate a value. + omegaDotDivOmegaSquare: 0.02, + + // The moment of inertia of the flywheel kg*m^2 + // A way to measure it is outlined here: https://dvernooy.github.io/projects/ergware/, "Flywheel moment of inertia" + // You could also roughly estimate it by just doing some strokes and the comparing the calculated power values for + // plausibility. Note that the power also depends on omegaDotDivOmegaSquare (see above). + jMoment: 0.49, + + // Set this to true if you are using a water rower + // The mass of the water starts rotating, when you pull the handle, and therefore acts + // like a massive flywheel + // Liquids are a tricky thing and therefore the dumping constant does not seem to be + // that constant on water rowers... + // This is WIP, but for now this setting is used to figure out the drive and recovery phases + // differently on water rowers + liquidFlywheel: false + }, + + // Sportstech WRX700 + WRX700: { + numOfImpulsesPerRevolution: 2, + omegaDotDivOmegaSquare: 0.046, + jMoment: 0.49, + liquidFlywheel: true + }, + + // DKN R-320 Air Rower + DKNR320: { + numOfImpulsesPerRevolution: 1, + omegaDotDivOmegaSquare: 0.019, + jMoment: 0.4, + liquidFlywheel: true + } +} diff --git a/docs/installation.md b/docs/installation.md index 0bcfe72..2f99345 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -60,4 +60,4 @@ If your machine does not have something like this or if the sensor is not access * PAS sensor (i.e. from an E-bike) * Optical chopper wheel -You should now adjust the rower specific parameters in `app/engine/RowingEngine.js` to suit your rowing machine. +You should now adjust the rower specific parameters in `config/config.js` to suit your rowing machine. diff --git a/install/config.js b/install/config.js new file mode 100644 index 0000000..a31394e --- /dev/null +++ b/install/config.js @@ -0,0 +1,34 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/laberning/openrowingmonitor + + You can modify this file to configure Open Rowing Monitor to your needs. + This file should be placed in the 'config' folder of Open Rowing Monitor. + + All available configuration parameters are visible in config/config.default.js + To modify a parameter, copy it to this file and modify the value. + + Changes to this file are persisted when you update to new versions. +*/ +// eslint-disable-next-line no-unused-vars +import rowerProfiles from './rowerProfiles.js' + +export default { + /* + // example: change the default log level: + loglevel: { + default: 'debug' + }, + + // example: set a rower profile: + rowerSettings: rowerProfiles.DKNR320 + + // example: set custom rower settings: + rowerSettings: { + numOfImpulsesPerRevolution: 1, + omegaDotDivOmegaSquare: 0.03, + jMoment: 0.3, + liquidFlywheel: false + } + */ +} diff --git a/install/install.sh b/install/install.sh index 3333bbd..f4a2d04 100755 --- a/install/install.sh +++ b/install/install.sh @@ -76,6 +76,9 @@ print print "Downloading and compiling Runtime dependencies..." sudo npm install sudo npm run build +if ! [[ -f "config/config.js" ]]; then + cp install/config.js config/ +fi print print "Setting up Open Rowing Monitor as autostarting system service..." diff --git a/package.json b/package.json index 5823115..beb9e91 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "openrowingmonitor", - "version": "0.7.0", + "version": "0.7.1", "description": "A rowing monitor for rowing exercise machines", "main": "app/server.js", "author": "Lars Berning",