improves regression tests, renames some settings

This commit is contained in:
Lars Berning 2021-05-02 15:02:58 +02:00
parent 2c6819ca02
commit 475f0f4835
No known key found for this signature in database
GPG Key ID: 028E73C9E1D8A0B3
7 changed files with 64 additions and 50 deletions

View File

@ -56,8 +56,8 @@ function createRowingEngine (rowerSettings) {
let workoutHandler
const kDampEstimatorAverager = createWeightedAverager(3)
const flankDetector = createMovingFlankDetector(rowerSettings.numOfImpulsesPerRevolution, rowerSettings.maximumTimeBetweenMagnets, 0)
let prevDt = rowerSettings.maximumTimeBetweenMagnets
const flankDetector = createMovingFlankDetector(rowerSettings.numOfImpulsesPerRevolution, rowerSettings.maximumTimeBetweenImpulses, 0)
let prevDt = rowerSettings.maximumTimeBetweenImpulses
let kPower = 0.0
let jPower = 0.0
let kDampEstimator = 0.0
@ -92,7 +92,7 @@ function createRowingEngine (rowerSettings) {
// STEP 1: reduce noise in the measurements by applying some sanity checks
// noise filter on the value of currentDt: it should be within sane levels and should not deviate too much from the previous reading
if (currentDt < rowerSettings.minimumTimeBetweenMagnets || currentDt > rowerSettings.maximumTimeBetweenMagnets || currentDt < (rowerSettings.maximumDownwardChange * prevDt) || currentDt > (rowerSettings.maximumUpwardChange * prevDt)) {
if (currentDt < rowerSettings.minimumTimeBetweenImpulses || currentDt > rowerSettings.maximumTimeBetweenImpulses || currentDt < (rowerSettings.maximumDownwardChange * prevDt) || currentDt > (rowerSettings.maximumUpwardChange * prevDt)) {
// impulses are outside plausible ranges, so we assume it is close to the previous one
currentDt = prevDt
log.debug(`noise filter corrected currentDt, ${currentDt} was dubious, changed to ${prevDt}`)

View File

@ -9,6 +9,7 @@ import loglevel from 'loglevel'
import rowerProfiles from '../../config/rowerProfiles.js'
import { createRowingEngine } from './RowingEngine.js'
import { replayRowingSession } from '../tools/RowingRecorder.js'
import { deepMerge } from '../tools/ConfigManager.js'
const log = loglevel.getLogger('RowingEngine.test')
log.setLevel('warn')
@ -32,44 +33,57 @@ const createWorkoutEvaluator = function () {
function getMinStrokePower () {
return strokes.map((stroke) => stroke.power).reduce((acc, power) => Math.max(acc, power))
}
function getDistance () {
return strokes.reduce((acc, stroke) => acc + stroke.distance, 0)
}
return {
handleStroke,
handleStrokeStateChanged,
handlePause,
getNumOfStrokes,
getMaxStrokePower,
getMinStrokePower
getMinStrokePower,
getDistance
}
}
test('sample data for WRX700 should produce plausible results with rower profile', async () => {
const rowingEngine = createRowingEngine(rowerProfiles.WRX700)
const rowingEngine = createRowingEngine(deepMerge(rowerProfiles.DEFAULT, rowerProfiles.WRX700))
const workoutEvaluator = createWorkoutEvaluator()
rowingEngine.notify(workoutEvaluator)
await replayRowingSession(rowingEngine.handleRotationImpulse, { filename: 'recordings/WRX700_2magnets.csv' })
assert.is(workoutEvaluator.getNumOfStrokes(), 16, 'number of strokes does not meet expectation')
assert.ok(workoutEvaluator.getMaxStrokePower() < 220, `maximum stroke power should be below 220w, but is ${workoutEvaluator.getMaxStrokePower()}w`)
assert.ok(workoutEvaluator.getMinStrokePower() > 50, `minimum stroke power should be above 50w, but is ${workoutEvaluator.getMinStrokePower()}w`)
assertPowerRange(workoutEvaluator, 50, 220)
assertDistanceRange(workoutEvaluator, 140, 144)
})
test('sample data for DKNR320 should produce plausible results with rower profile', async () => {
const rowingEngine = createRowingEngine(rowerProfiles.DKNR320)
const rowingEngine = createRowingEngine(deepMerge(rowerProfiles.DEFAULT, rowerProfiles.DKNR320))
const workoutEvaluator = createWorkoutEvaluator()
rowingEngine.notify(workoutEvaluator)
await replayRowingSession(rowingEngine.handleRotationImpulse, { filename: 'recordings/DKNR320.csv' })
assert.is(workoutEvaluator.getNumOfStrokes(), 10, 'number of strokes does not meet expectation')
assert.ok(workoutEvaluator.getMaxStrokePower() < 200, `maximum stroke power should be below 200w, but is ${workoutEvaluator.getMaxStrokePower()}w`)
assert.ok(workoutEvaluator.getMinStrokePower() > 75, `minimum stroke power should be above 75w, but is ${workoutEvaluator.getMinStrokePower()}w`)
assertPowerRange(workoutEvaluator, 75, 200)
assertDistanceRange(workoutEvaluator, 64, 67)
})
test('sample data for RX800 should produce plausible results with rower profile', async () => {
const rowingEngine = createRowingEngine(rowerProfiles.RX800)
const rowingEngine = createRowingEngine(deepMerge(rowerProfiles.DEFAULT, rowerProfiles.RX800))
const workoutEvaluator = createWorkoutEvaluator()
rowingEngine.notify(workoutEvaluator)
await replayRowingSession(rowingEngine.handleRotationImpulse, { filename: 'recordings/RX800.csv' })
assert.is(workoutEvaluator.getNumOfStrokes(), 10, 'number of strokes does not meet expectation')
assert.ok(workoutEvaluator.getMaxStrokePower() < 260, `maximum stroke power should be below 260, but is ${workoutEvaluator.getMaxStrokePower()}w`)
assert.ok(workoutEvaluator.getMinStrokePower() > 160, `minimum stroke power should be above 160w, but is ${workoutEvaluator.getMinStrokePower()}w`)
assertPowerRange(workoutEvaluator, 160, 260)
assertDistanceRange(workoutEvaluator, 88, 92)
})
function assertPowerRange (evaluator, minPower, maxPower) {
assert.ok(evaluator.getMinStrokePower() > minPower, `minimum stroke power should be above ${minPower}w, but is ${evaluator.getMinStrokePower()}w`)
assert.ok(evaluator.getMaxStrokePower() < maxPower, `maximum stroke power should be below ${maxPower}w, but is ${evaluator.getMaxStrokePower()}w`)
}
function assertDistanceRange (evaluator, minDistance, maxDistance) {
console.log(evaluator.getDistance().toFixed(2))
assert.ok(evaluator.getDistance() >= minDistance && evaluator.getDistance() <= maxDistance, `distance should be between ${minDistance}m and ${maxDistance}m, but is ${evaluator.getDistance().toFixed(2)}m`)
}
test.run()

View File

@ -21,7 +21,7 @@ export function createGpioTimerService () {
// setting priority of current process
os.setPriority(-20)
} catch (err) {
log.error('error while setting priority of Gpio-Thread: ', err)
log.debug('need root permission to set priority of Gpio-Thread')
}
// mode can be rising, falling, both
const reedSensor = new Gpio(17, 'in', 'rising')

View File

@ -135,7 +135,7 @@ webServer.on('clientConnected', () => {
/*
replayRowingSession(rowingEngine.handleRotationImpulse, {
filename: 'recordings/wrx700_2magnets.csv',
filename: 'recordings/WRX700_2magnets.csv',
realtime: true,
loop: true
})

View File

@ -15,7 +15,7 @@ async function getConfig () {
return customConfig !== undefined ? deepMerge(defaultConfig, customConfig.default) : defaultConfig
}
function deepMerge (...objects) {
export function deepMerge (...objects) {
const isObject = obj => obj && typeof obj === 'object'
return objects.reduce((prev, obj) => {

View File

@ -10,24 +10,24 @@
*/
export default {
// Profile for an example rower
// The default rower profile
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,
// Filter values for sanity checks
// First are the sane minimum and maximum times between magnets during active rows
minimumTimeBetweenMagnets: 0.014,
maximumTimeBetweenMagnets: 0.5,
// Procentual change between successive intervals
maximumDownwardChange: 0.2, // effectively the maximum deceleration
maximumUpwardChange: 1.75, // effectively the maximum acceleration
// Settings for the phase detection
// Filter Settings to reduce noise in the measured data
// Minimum and maximum duration between impulses in seconds during active rowing. Measurements outside of this range
// will be replaced by a default value.
minimumTimeBetweenImpulses: 0.014,
maximumTimeBetweenImpulses: 0.5,
// Percentage change between successive intervals
maximumDownwardChange: 0.2, // effectively the maximum deceleration
maximumUpwardChange: 1.75, // effectively the maximum acceleration
// Settings for the rowing phase detection (in seconds)
minimumDriveTime: 0.300,
minimumRecoveryTime: 0.750,
// 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)
@ -46,9 +46,9 @@ export default {
// 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.
// Also note that the rowed distance also depends on jMoment, so please calibrate that before changing this constant.
// PLEASE NOTE: INcreasing this number DEcreases your rowed meters
// PLEASE NOTE: Increasing this number decreases your rowed meters
magicConstant: 2.8,
// 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
@ -62,8 +62,8 @@ export default {
// Sportstech WRX700
WRX700: {
numOfImpulsesPerRevolution: 2,
minimumTimeBetweenMagnets: 0.05,
maximumTimeBetweenMagnets: 1,
minimumTimeBetweenImpulses: 0.05,
maximumTimeBetweenImpulses: 1,
maximumDownwardChange: 0.25,
maximumUpwardChange: 2,
minimumDriveTime: 0.500,
@ -76,8 +76,8 @@ export default {
// DKN R-320 Air Rower
DKNR320: {
numOfImpulsesPerRevolution: 1,
minimumTimeBetweenMagnets: 0.15,
maximumTimeBetweenMagnets: 0.5,
minimumTimeBetweenImpulses: 0.15,
maximumTimeBetweenImpulses: 0.5,
maximumDownwardChange: 0.25,
maximumUpwardChange: 1.75,
minimumDriveTime: 0.500,
@ -86,15 +86,15 @@ export default {
jMoment: 0.4,
liquidFlywheel: true
},
// NordicTrack RX800 Air Rower
RX800: {
numOfImpulsesPerRevolution: 4,
liquidFlywheel: false,
// Damper setting 10
minimumTimeBetweenMagnets: 0.018,
maximumTimeBetweenMagnets: 0.0338,
minimumTimeBetweenImpulses: 0.018,
maximumTimeBetweenImpulses: 0.0338,
maximumDownwardChange: 0.69,
maximumUpwardChange: 1.3,
minimumDriveTime: 0.300,
@ -105,8 +105,8 @@ export default {
//
/* Damper setting 8
minimumTimeBetweenMagnets: 0.017,
maximumTimeBetweenMagnets: 0.034,
minimumTimeBetweenImpulses: 0.017,
maximumTimeBetweenImpulses: 0.034,
maximumDownwardChange: 0.8,
maximumUpwardChange: 1.15,
minimumDriveTime: 0.300,
@ -117,8 +117,8 @@ export default {
*/
/* Damper setting 6
minimumTimeBetweenMagnets: 0.017,
maximumTimeBetweenMagnets: 0.034,
minimumTimeBetweenImpulses: 0.017,
maximumTimeBetweenImpulses: 0.034,
maximumDownwardChange: 0.85,
maximumUpwardChange: 1.15,
minimumDriveTime: 0.300,
@ -129,8 +129,8 @@ export default {
*/
/* Damper setting 4
minimumTimeBetweenMagnets: 0.019,
maximumTimeBetweenMagnets: 0.032,
minimumTimeBetweenImpulses: 0.019,
maximumTimeBetweenImpulses: 0.032,
maximumDownwardChange: 0.70,
maximumUpwardChange: 1.30,
minimumDriveTime: 0.300,
@ -141,8 +141,8 @@ export default {
*/
/* Damper setting 2
minimumTimeBetweenMagnets: 0.016,
maximumTimeBetweenMagnets: 0.033,
minimumTimeBetweenImpulses: 0.016,
maximumTimeBetweenImpulses: 0.033,
maximumDownwardChange: 0.85,
maximumUpwardChange: 1.15,
minimumDriveTime: 0.300,

View File

@ -2,18 +2,18 @@
This guide helps you to adjust the rowing monitor specifically for your rower or even for you
## Why have setings
## Why have settings
No rowingmachine is the same, and their physical construction is important for the Rowing Monitor to understand to be able to understand your rowing. Easiest way is to select your rower from owerProfiles.js and put its name in default.config.js instead of "rowerProfiles.DEFAULT".
No rowing machine is the same, and their physical construction is important for the Rowing Monitor to understand to be able to understand your rowing. The easiest way is to select your rower profile from `config/rowerProfiles.js` and put its name in `config.js` (i.e. `rowerSettings: rowerProfiles.WRX700`).
If your rower isn't there, this guide will help you set it up (please send in the data and settings, so we can add it to the OpenRowingMonitor).
If your rower isn't in there, this guide will help you set it up (please send in the data and settings, so we can add it to the OpenRowingMonitor).
Settings important for Open Rowing Monitor:
* numOfImpulsesPerRevolution: tells Open Rowing Monitor how many impulses per rotation of the flywheel to expect. Although sometimes not easy to detect, you can sometimes find it in the manual under the parts-list
* liquidFlywheel: tells OpenRowingMonitor if you are using a waterrower (true) or a solid flywheel with magnetic or air-resistance (false)
* liquidFlywheel: tells OpenRowingMonitor if you are using a water rower (true) or a solid flywheel with magnetic or air-resistance (false)
* omegaDotDivOmegaSquare: tells OpenRowingMonitor how much damping and thus resistance your flywheel is offering. This is typically also dependent on your damper-setting (if present). 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 based on the logging.
* jMoment: The inertia of the flywheel, which in practice influences your power values and distance. This typically is set by rowing and see what kind of power is displayed on the monitor. Typical ranges are weigth dependent (see [this explanation](https://www.rowingmachine-guide.com/tabata-rowing-workouts.html)).
* jMoment: The inertia of the flywheel, which in practice influences your power values and distance. This typically is set by rowing and see what kind of power is displayed on the monitor. Typical ranges are weight dependent (see [this explanation](https://www.rowingmachine-guide.com/tabata-rowing-workouts.html)).
* Noise reduction settings. You should only change these settings if you experience issues.
* minimumTimeBetweenImpulses
* maximumTimeBetweenImpulses
@ -23,7 +23,7 @@ Settings important for Open Rowing Monitor:
* minimumDriveTime
* minimumRecoveryTime
For the noise reduction settings and stroke detection settings, you can use the Excel tool. When OpenRowingMonitor records a log (comment out the line in server.js), you can paste the values in the first column of the "Raw Data" tab (please observe that the Raspberry uses a point as seperator, and your version of Excel might expect a comma). From there, the Excel file simulates the calculations the OpenRowingMonitor makes, allowing you to play with these settings.
For the noise reduction settings and stroke detection settings, you can use the Excel tool. When OpenRowingMonitor records a log (comment out the line in `server.js`), you can paste the values in the first column of the "Raw Data" tab (please observe that the Raspberry uses a point as separator, and your version of Excel might expect a comma). From there, the Excel file simulates the calculations the OpenRowingMonitor makes, allowing you to play with these settings.
By changing the noise reduction settings, you can remove any obvious errors. You don't need to filter everything: it is just to remove obvious errors that might frustrate the stroke detection, but in the end you can't prevent every piece of noise out there. Begin with the noise filtering, when you are satisfied, you can adjust the stroke detection.