'use strict' /* This test is a test of the Rower object, that tests wether this object fills all fields correctly, given one validated rower, (the Concept2 RowErg) using a validated cycle of strokes. This thoroughly tests the raw physics of the translation of Angular physics to Linear physics. The combination with all possible known rowers is tested when testing the above function RowingStatistics, as these statistics are dependent on these settings as well. */ import { test } from 'uvu' import * as assert from 'uvu/assert' import rowerProfiles from '../../config/rowerProfiles.js' import { replayRowingSession } from '../tools/RowingRecorder.js' import { deepMerge } from '../tools/Helper.js' import { createRower } from './Rower.js' const baseConfig = { // Based on Concept 2 settings, as this is the validation system numOfImpulsesPerRevolution: 6, sprocketRadius: 1.4, maximumStrokeTimeBeforePause: 0.3, // Modification to standard settings to shorten test cases dragFactor: 110, autoAdjustDragFactor: true, minimumDragQuality: 0.95, dragFactorSmoothing: 3, minimumTimeBetweenImpulses: 0.005, maximumTimeBetweenImpulses: 0.017, flankLength: 12, smoothing: 1, minimumStrokeQuality: 0.36, minumumForceBeforeStroke: 20, // Modification to standard settings to shorten test cases minumumRecoverySlope: 0.00070, autoAdjustRecoverySlope: false, // Modification to standard settings to shorten test cases autoAdjustRecoverySlopeMargin: 0.04, minimumDriveTime: 0.04, // Modification to standard settings to shorten test cases minimumRecoveryTime: 0.09, // Modification to standard settings to shorten test cases flywheelInertia: 0.10138, magicConstant: 2.8 } // Test behaviour for no datapoints test('Correct rower behaviour at initialisation', () => { const rower = createRower(baseConfig) testStrokeState(rower, 'WaitingForDrive') testTotalMovingTimeSinceStart(rower, 0) testTotalNumberOfStrokes(rower, 0) testTotalLinearDistanceSinceStart(rower, 0) testCycleDuration(rower, 0.13) // Default value testCycleLinearDistance(rower, 0) testCycleLinearVelocity(rower, 0) testCyclePower(rower, 0) testDriveDuration(rower, 0) testDriveLinearDistance(rower, 0) testDriveLength(rower, 0) testDriveAverageHandleForce(rower, 0) testDrivePeakHandleForce(rower, 0) testRecoveryDuration(rower, 0) testRecoveryDragFactor(rower, 110) testInstantHandlePower(rower, 0) }) // Test behaviour for one datapoint // Test behaviour for three perfect identical strokes, including settingling behaviour of metrics test('Test behaviour for three perfect identical strokes, including settingling behaviour of metrics', () => { const rower = createRower(baseConfig) testStrokeState(rower, 'WaitingForDrive') testTotalMovingTimeSinceStart(rower, 0) testTotalLinearDistanceSinceStart(rower, 0) testTotalNumberOfStrokes(rower, 0) testCycleDuration(rower, 0.13) // Default value testCycleLinearDistance(rower, 0) testCycleLinearVelocity(rower, 0) testCyclePower(rower, 0) testDriveDuration(rower, 0) testDriveLinearDistance(rower, 0) testDriveLength(rower, 0) testDriveAverageHandleForce(rower, 0) testDrivePeakHandleForce(rower, 0) testRecoveryDuration(rower, 0) testRecoveryDragFactor(rower, 110) testInstantHandlePower(rower, 0) // Drive initial stroke starts here rower.handleRotationImpulse(0.011221636) rower.handleRotationImpulse(0.011175504) rower.handleRotationImpulse(0.01116456) rower.handleRotationImpulse(0.011130263) rower.handleRotationImpulse(0.011082613) rower.handleRotationImpulse(0.011081761) rower.handleRotationImpulse(0.011062297) rower.handleRotationImpulse(0.011051853) rower.handleRotationImpulse(0.010973313) rower.handleRotationImpulse(0.010919756) rower.handleRotationImpulse(0.01086431) rower.handleRotationImpulse(0.010800864) rower.handleRotationImpulse(0.010956987) rower.handleRotationImpulse(0.010653396) rower.handleRotationImpulse(0.010648619) rower.handleRotationImpulse(0.010536818) rower.handleRotationImpulse(0.010526151) rower.handleRotationImpulse(0.010511225) rower.handleRotationImpulse(0.010386684) testStrokeState(rower, 'Drive') testTotalMovingTimeSinceStart(rower, 0.077918634) testTotalLinearDistanceSinceStart(rower, 0.2491943602992768) testTotalNumberOfStrokes(rower, 1) testCycleDuration(rower, 0.13) // still default value testCycleLinearDistance(rower, 0.2491943602992768) // Known issue: this shouldn't be filled at this time as the cycle isn't completed yet testCycleLinearVelocity(rower, 0) // This isn't filled after the first drive, as we haven't survived a complete cycle yet testCyclePower(rower, 0) // This isn't filled after the first drive, as we haven't survived a complete cycle yet testDriveDuration(rower, 0) // Shouldn't this one be filled after the first drive? testDriveLinearDistance(rower, 0.2491943602992768) testDriveLength(rower, 0) // Shouldn't this one be filled after the first drive? testDriveAverageHandleForce(rower, 1691.793078056684) testDrivePeakHandleForce(rower, 10246.062011594136) testRecoveryDuration(rower, 0) testRecoveryDragFactor(rower, 110) testInstantHandlePower(rower, 372.0199762100516) // Recovery initial stroke starts here rower.handleRotationImpulse(0.010769) rower.handleRotationImpulse(0.010707554) rower.handleRotationImpulse(0.010722165) rower.handleRotationImpulse(0.01089567) rower.handleRotationImpulse(0.010917504) rower.handleRotationImpulse(0.010997969) rower.handleRotationImpulse(0.011004655) rower.handleRotationImpulse(0.011013618) rower.handleRotationImpulse(0.011058193) rower.handleRotationImpulse(0.010807149) rower.handleRotationImpulse(0.0110626) rower.handleRotationImpulse(0.011090787) rower.handleRotationImpulse(0.011099509) rower.handleRotationImpulse(0.011131862) rower.handleRotationImpulse(0.011209919) testStrokeState(rower, 'Recovery') testTotalMovingTimeSinceStart(rower, 0.23894732900000007) testTotalLinearDistanceSinceStart(rower, 0.7831822752262986) testTotalNumberOfStrokes(rower, 1) testCycleDuration(rower, 0.19636192600000005) testCycleLinearDistance(rower, 0.7831822752262986) testCycleLinearVelocity(rower, 3.2632879039515323) testCyclePower(rower, 97.30254616461225) testDriveDuration(rower, 0.19636192600000005) testDriveLinearDistance(rower, 0.6407854979124261) testDriveLength(rower, 0.2638937829015426) testDriveAverageHandleForce(rower, 851.8820525641245) // This is the first stroke, which always leads to insane data like this testDrivePeakHandleForce(rower, 10246.062011594136) testRecoveryDuration(rower, 0) testRecoveryDragFactor(rower, 110) testInstantHandlePower(rower, 0) // Drive seconds stroke starts here rower.handleRotationImpulse(0.011221636) rower.handleRotationImpulse(0.011175504) rower.handleRotationImpulse(0.01116456) rower.handleRotationImpulse(0.011130263) rower.handleRotationImpulse(0.011082613) rower.handleRotationImpulse(0.011081761) rower.handleRotationImpulse(0.011062297) rower.handleRotationImpulse(0.011051853) rower.handleRotationImpulse(0.010973313) rower.handleRotationImpulse(0.010919756) rower.handleRotationImpulse(0.01086431) rower.handleRotationImpulse(0.010800864) rower.handleRotationImpulse(0.010956987) rower.handleRotationImpulse(0.010653396) rower.handleRotationImpulse(0.010648619) rower.handleRotationImpulse(0.010536818) rower.handleRotationImpulse(0.010526151) rower.handleRotationImpulse(0.010511225) rower.handleRotationImpulse(0.010386684) testStrokeState(rower, 'Drive') testTotalMovingTimeSinceStart(rower, 0.44915539800000004) testTotalLinearDistanceSinceStart(rower, 1.5912564829320934) testTotalNumberOfStrokes(rower, 2) testCycleDuration(rower, 0.34889498300000005) testCycleLinearDistance(rower, 0.9504709850196674) testCycleLinearVelocity(rower, 3.2650920019419694) testCyclePower(rower, 97.46401557792097) testDriveDuration(rower, 0.19636192600000005) testDriveLinearDistance(rower, 0.4520822644211139) testDriveLength(rower, 0.2638937829015426) testDriveAverageHandleForce(rower, 251.04336322997108) testDrivePeakHandleForce(rower, 396.7011215867992) testRecoveryDuration(rower, 0.152533057) testRecoveryDragFactor(rower, 309.02744980039836) testInstantHandlePower(rower, 526.5255378434941) // Recovery second stroke starts here rower.handleRotationImpulse(0.010769) rower.handleRotationImpulse(0.010707554) rower.handleRotationImpulse(0.010722165) rower.handleRotationImpulse(0.01089567) rower.handleRotationImpulse(0.010917504) rower.handleRotationImpulse(0.010997969) rower.handleRotationImpulse(0.011004655) rower.handleRotationImpulse(0.011013618) rower.handleRotationImpulse(0.011058193) rower.handleRotationImpulse(0.010807149) rower.handleRotationImpulse(0.0110626) rower.handleRotationImpulse(0.011090787) rower.handleRotationImpulse(0.011099509) rower.handleRotationImpulse(0.011131862) rower.handleRotationImpulse(0.011209919) testStrokeState(rower, 'Recovery') testTotalMovingTimeSinceStart(rower, 0.6101840930000001) testTotalLinearDistanceSinceStart(rower, 2.3447269236339507) testTotalNumberOfStrokes(rower, 2) testCycleDuration(rower, 0.40310000200000007) testCycleLinearDistance(rower, 1.2055527051229706) testCycleLinearVelocity(rower, 4.6106683482425606) testCyclePower(rower, 274.4414360493952) testDriveDuration(rower, 0.25056694500000004) testDriveLinearDistance(rower, 1.1553213424095137) testDriveLength(rower, 0.3371976114853044) testDriveAverageHandleForce(rower, 290.98159585708896) testDrivePeakHandleForce(rower, 456.9929898648157) testRecoveryDuration(rower, 0.152533057) testRecoveryDragFactor(rower, 309.02744980039836) // As we decelerate the flywheel quite fast, this is expected testInstantHandlePower(rower, 0) // Drive third stroke starts here rower.handleRotationImpulse(0.011221636) rower.handleRotationImpulse(0.011175504) rower.handleRotationImpulse(0.01116456) rower.handleRotationImpulse(0.011130263) rower.handleRotationImpulse(0.011082613) rower.handleRotationImpulse(0.011081761) rower.handleRotationImpulse(0.011062297) rower.handleRotationImpulse(0.011051853) rower.handleRotationImpulse(0.010973313) rower.handleRotationImpulse(0.010919756) rower.handleRotationImpulse(0.01086431) rower.handleRotationImpulse(0.010800864) rower.handleRotationImpulse(0.010956987) rower.handleRotationImpulse(0.010653396) rower.handleRotationImpulse(0.010648619) rower.handleRotationImpulse(0.010536818) rower.handleRotationImpulse(0.010526151) rower.handleRotationImpulse(0.010511225) rower.handleRotationImpulse(0.010386684) testStrokeState(rower, 'Drive') testTotalMovingTimeSinceStart(rower, 0.8203921620000004) testTotalLinearDistanceSinceStart(rower, 3.2991228151896355) testTotalNumberOfStrokes(rower, 3) testCycleDuration(rower, 0.3490464680000002) testCycleLinearDistance(rower, 1.004627254269142) testCycleLinearVelocity(rower, 4.6051278388258226) testCyclePower(rower, 273.453258990202) testDriveDuration(rower, 0.25056694500000004) testDriveLinearDistance(rower, 0.552544989848028) testDriveLength(rower, 0.3371976114853044) testDriveAverageHandleForce(rower, 223.750606354492) testDrivePeakHandleForce(rower, 396.7011215854034) testRecoveryDuration(rower, 0.09847952300000018) testRecoveryDragFactor(rower, 309.02744980039836) testInstantHandlePower(rower, 526.5255378417136) // Recovery third stroke starts here rower.handleRotationImpulse(0.010769) rower.handleRotationImpulse(0.010707554) rower.handleRotationImpulse(0.010722165) rower.handleRotationImpulse(0.01089567) rower.handleRotationImpulse(0.010917504) rower.handleRotationImpulse(0.010997969) rower.handleRotationImpulse(0.011004655) rower.handleRotationImpulse(0.011013618) rower.handleRotationImpulse(0.011058193) rower.handleRotationImpulse(0.010807149) rower.handleRotationImpulse(0.0110626) rower.handleRotationImpulse(0.011090787) rower.handleRotationImpulse(0.011099509) rower.handleRotationImpulse(0.011131862) rower.handleRotationImpulse(0.011209919) testStrokeState(rower, 'Recovery') testTotalMovingTimeSinceStart(rower, 0.9814208570000005) testTotalLinearDistanceSinceStart(rower, 4.052593255891493) testTotalNumberOfStrokes(rower, 3) testCycleDuration(rower, 0.3712367640000004) testCycleLinearDistance(rower, 1.3060154305498848) testCycleLinearVelocity(rower, 4.600477371517923) testCyclePower(rower, 272.62565872880714) testDriveDuration(rower, 0.2727572410000002) testDriveLinearDistance(rower, 1.2557840678364274) testDriveLength(rower, 0.36651914291880905) testDriveAverageHandleForce(rower, 272.7765993429924) testDrivePeakHandleForce(rower, 456.99298986363897) testRecoveryDuration(rower, 0.09847952300000018) testRecoveryDragFactor(rower, 309.02744980039836) testInstantHandlePower(rower, 0) // Dwelling state starts here rower.handleRotationImpulse(0.020769) rower.handleRotationImpulse(0.020707554) rower.handleRotationImpulse(0.020722165) rower.handleRotationImpulse(0.02089567) rower.handleRotationImpulse(0.020917504) rower.handleRotationImpulse(0.020997969) rower.handleRotationImpulse(0.021004655) rower.handleRotationImpulse(0.021013618) rower.handleRotationImpulse(0.021058193) rower.handleRotationImpulse(0.020807149) rower.handleRotationImpulse(0.0210626) rower.handleRotationImpulse(0.021090787) rower.handleRotationImpulse(0.021099509) rower.handleRotationImpulse(0.021131862) rower.handleRotationImpulse(0.021209919) testStrokeState(rower, 'WaitingForDrive') testTotalMovingTimeSinceStart(rower, 1.1137102920000004) testTotalNumberOfStrokes(rower, 3) testTotalLinearDistanceSinceStart(rower, 4.655369608452978) testCycleDuration(rower, 0.4157688410000001) testCycleLinearDistance(rower, 1.90879178311137) testCycleLinearVelocity(rower, 4.590992866421583) testCyclePower(rower, 270.94296880669305) testDriveDuration(rower, 0.2727572410000002) testDriveLinearDistance(rower, 1.2557840678364274) testDriveLength(rower, 0.36651914291880905) testDriveAverageHandleForce(rower, 272.7765993429924) testDrivePeakHandleForce(rower, 456.99298986363897) testRecoveryDuration(rower, 0.1430115999999999) testRecoveryDragFactor(rower, 309.02744980039836) testInstantHandlePower(rower, 0) }) // Test behaviour for noisy upgoing flank // Test behaviour for noisy downgoing flank // Test behaviour for noisy stroke // Test behaviour after reset // Test behaviour for one datapoint // Test behaviour for noisy stroke // Test drag factor calculation // Test Dynamic stroke detection // Test behaviour after reset // Test behaviour with real-life data test('sample data for Sportstech WRX700 should produce plausible results', async () => { const rower = createRower(deepMerge(rowerProfiles.DEFAULT, rowerProfiles.Sportstech_WRX700)) testTotalMovingTimeSinceStart(rower, 0) testTotalLinearDistanceSinceStart(rower, 0) testTotalNumberOfStrokes(rower, 0) testRecoveryDragFactor(rower, rowerProfiles.Sportstech_WRX700.dragFactor) await replayRowingSession(rower.handleRotationImpulse, { filename: 'recordings/WRX700_2magnets.csv', realtime: false, loop: false }) testTotalMovingTimeSinceStart(rower, 46.302522627) testTotalLinearDistanceSinceStart(rower, 166.29596716416734) testTotalNumberOfStrokes(rower, 16) // As dragFactor is static, it should remain in place testRecoveryDragFactor(rower, rowerProfiles.Sportstech_WRX700.dragFactor) }) test('sample data for DKN R-320 should produce plausible results', async () => { const rower = createRower(deepMerge(rowerProfiles.DEFAULT, rowerProfiles.DKN_R320)) testTotalMovingTimeSinceStart(rower, 0) testTotalLinearDistanceSinceStart(rower, 0) testTotalNumberOfStrokes(rower, 0) testRecoveryDragFactor(rower, rowerProfiles.DKN_R320.dragFactor) await replayRowingSession(rower.handleRotationImpulse, { filename: 'recordings/DKNR320.csv', realtime: false, loop: false }) testTotalMovingTimeSinceStart(rower, 21.701535821) testTotalLinearDistanceSinceStart(rower, 70.11298001986664) testTotalNumberOfStrokes(rower, 10) // As dragFactor is static, it should remain in place testRecoveryDragFactor(rower, rowerProfiles.DKN_R320.dragFactor) }) test('sample data for NordicTrack RX800 should produce plausible results', async () => { const rower = createRower(deepMerge(rowerProfiles.DEFAULT, rowerProfiles.NordicTrack_RX800)) testTotalMovingTimeSinceStart(rower, 0) testTotalLinearDistanceSinceStart(rower, 0) testTotalNumberOfStrokes(rower, 0) testRecoveryDragFactor(rower, rowerProfiles.NordicTrack_RX800.dragFactor) await replayRowingSession(rower.handleRotationImpulse, { filename: 'recordings/RX800.csv', realtime: false, loop: false }) testTotalMovingTimeSinceStart(rower, 17.389910236000024) testTotalLinearDistanceSinceStart(rower, 62.49982252262572) testTotalNumberOfStrokes(rower, 8) // As dragFactor is dynamic, it should have changed testRecoveryDragFactor(rower, 493.1277530352103) }) test('A full session for SportsTech WRX700 should produce plausible results', async () => { const rower = createRower(deepMerge(rowerProfiles.DEFAULT, rowerProfiles.Sportstech_WRX700)) testTotalMovingTimeSinceStart(rower, 0) testTotalLinearDistanceSinceStart(rower, 0) testTotalNumberOfStrokes(rower, 0) testRecoveryDragFactor(rower, rowerProfiles.Sportstech_WRX700.dragFactor) await replayRowingSession(rower.handleRotationImpulse, { filename: 'recordings/WRX700_2magnets_session.csv', realtime: false, loop: false }) testTotalMovingTimeSinceStart(rower, 2342.741183077012) testTotalLinearDistanceSinceStart(rower, 8409.62244161274) testTotalNumberOfStrokes(rower, 846) // As dragFactor is static, it should remain in place testRecoveryDragFactor(rower, rowerProfiles.Sportstech_WRX700.dragFactor) }) test('A full session for a Concept2 RowErg should produce plausible results', async () => { const rower = createRower(deepMerge(rowerProfiles.DEFAULT, rowerProfiles.Concept2_RowErg)) testTotalMovingTimeSinceStart(rower, 0) testTotalLinearDistanceSinceStart(rower, 0) testTotalNumberOfStrokes(rower, 0) testRecoveryDragFactor(rower, rowerProfiles.Concept2_RowErg.dragFactor) await replayRowingSession(rower.handleRotationImpulse, { filename: 'recordings/Concept2_RowErg_Session_2000meters.csv', realtime: false, loop: false }) testTotalMovingTimeSinceStart(rower, 590.111937) testTotalLinearDistanceSinceStart(rower, 2029.6932502534587) testTotalNumberOfStrokes(rower, 206) // As dragFactor isn't static, it should have changed testRecoveryDragFactor(rower, 80.79039510767821) }) function testStrokeState (rower, expectedValue) { assert.ok(rower.strokeState() === expectedValue, `strokeState should be ${expectedValue} at ${rower.totalMovingTimeSinceStart()} sec, is ${rower.strokeState()}`) } function testTotalMovingTimeSinceStart (rower, expectedValue) { assert.ok(rower.totalMovingTimeSinceStart() === expectedValue, `totalMovingTimeSinceStart should be ${expectedValue} sec at ${rower.totalMovingTimeSinceStart()} sec, is ${rower.totalMovingTimeSinceStart()}`) } function testTotalNumberOfStrokes (rower, expectedValue) { // Please note there is a stroke 0 assert.ok(rower.totalNumberOfStrokes() + 1 === expectedValue, `totalNumberOfStrokes should be ${expectedValue} at ${rower.totalMovingTimeSinceStart()} sec, is ${rower.totalNumberOfStrokes() + 1}`) } function testTotalLinearDistanceSinceStart (rower, expectedValue) { assert.ok(rower.totalLinearDistanceSinceStart() === expectedValue, `totalLinearDistanceSinceStart should be ${expectedValue} meters at ${rower.totalMovingTimeSinceStart()} sec, is ${rower.totalLinearDistanceSinceStart()}`) } function testCycleDuration (rower, expectedValue) { assert.ok(rower.cycleDuration() === expectedValue, `cycleDuration should be ${expectedValue} sec at ${rower.totalMovingTimeSinceStart()} sec, is ${rower.cycleDuration()}`) } function testCycleLinearDistance (rower, expectedValue) { assert.ok(rower.cycleLinearDistance() === expectedValue, `cycleLinearDistance should be ${expectedValue} meters at ${rower.totalMovingTimeSinceStart()} sec, is ${rower.cycleLinearDistance()}`) } function testCycleLinearVelocity (rower, expectedValue) { assert.ok(rower.cycleLinearVelocity() === expectedValue, `cycleLinearVelocity should be ${expectedValue} m/s at ${rower.totalMovingTimeSinceStart()} sec, is ${rower.cycleLinearVelocity()}`) } function testCyclePower (rower, expectedValue) { assert.ok(rower.cyclePower() === expectedValue, `cyclePower should be ${expectedValue} Watt at ${rower.totalMovingTimeSinceStart()} sec, is ${rower.cyclePower()}`) } function testDriveDuration (rower, expectedValue) { assert.ok(rower.driveDuration() === expectedValue, `driveDuration should be ${expectedValue} sec at ${rower.totalMovingTimeSinceStart()} sec, is ${rower.driveDuration()}`) } function testDriveLinearDistance (rower, expectedValue) { assert.ok(rower.driveLinearDistance() === expectedValue, `driveLinearDistance should be ${expectedValue} meters at ${rower.totalMovingTimeSinceStart()} sec, is ${rower.driveLinearDistance()}`) } function testDriveLength (rower, expectedValue) { assert.ok(rower.driveLength() === expectedValue, `driveLength should be ${expectedValue} meters at ${rower.totalMovingTimeSinceStart()} sec, is ${rower.driveLength()}`) } function testDriveAverageHandleForce (rower, expectedValue) { assert.ok(rower.driveAverageHandleForce() === expectedValue, `driveAverageHandleForce should be ${expectedValue} N at ${rower.totalMovingTimeSinceStart()} sec, is ${rower.driveAverageHandleForce()}`) } function testDrivePeakHandleForce (rower, expectedValue) { assert.ok(rower.drivePeakHandleForce() === expectedValue, `drivePeakHandleForce should be ${expectedValue} N at ${rower.totalMovingTimeSinceStart()} sec, is ${rower.drivePeakHandleForce()}`) } function testRecoveryDuration (rower, expectedValue) { assert.ok(rower.recoveryDuration() === expectedValue, `recoveryDuration should be ${expectedValue} sec at ${rower.totalMovingTimeSinceStart()} sec, is ${rower.recoveryDuration()}`) } function testRecoveryDragFactor (rower, expectedValue) { assert.ok(rower.recoveryDragFactor() === expectedValue, `recoveryDragFactor should be ${expectedValue} N*m*s^2 at ${rower.totalMovingTimeSinceStart()} sec, is ${rower.recoveryDragFactor()}`) } function testInstantHandlePower (rower, expectedValue) { assert.ok(rower.instantHandlePower() === expectedValue, `instantHandlePower should be ${expectedValue} Watt at ${rower.totalMovingTimeSinceStart()} sec, is ${rower.instantHandlePower()}`) } function reportAll (rower) { // eslint-disable-line no-unused-vars assert.ok(0, `time: ${rower.totalMovingTimeSinceStart()}, state ${rower.strokeState()}, No Strokes: ${rower.totalNumberOfStrokes() + 1}, Lin Distance: ${rower.totalLinearDistanceSinceStart()}, cycle dur: ${rower.cycleDuration()}, cycle Lin Dist: ${rower.cycleLinearDistance()}, Lin Velocity: ${rower.cycleLinearVelocity()}, Power: ${rower.cyclePower()}, Drive Dur: ${rower.driveDuration()}, Drive Lin. Dist. ${rower.driveLinearDistance()}, Drive Length: ${rower.driveLength()}, Av. Handle Force: ${rower.driveAverageHandleForce()}, Peak Handle Force: ${rower.drivePeakHandleForce()}, Rec. Dur: ${rower.recoveryDuration()}, Dragfactor: ${rower.recoveryDragFactor()}, Inst Handle Power: ${rower.instantHandlePower()}`) } test.run()