improves regression tests, renames some settings
This commit is contained in:
parent
2c6819ca02
commit
475f0f4835
|
|
@ -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}`)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -135,7 +135,7 @@ webServer.on('clientConnected', () => {
|
|||
|
||||
/*
|
||||
replayRowingSession(rowingEngine.handleRotationImpulse, {
|
||||
filename: 'recordings/wrx700_2magnets.csv',
|
||||
filename: 'recordings/WRX700_2magnets.csv',
|
||||
realtime: true,
|
||||
loop: true
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue