diff --git a/app/server.js b/app/server.js index 5ec746a545..ea8fd49a33 100644 --- a/app/server.js +++ b/app/server.js @@ -12,8 +12,10 @@ import { createSessionManager } from './engine/SessionManager.js' import { createWebServer } from './WebServer.js' import { createPeripheralManager } from './peripherals/PeripheralManager.js' import { createRecordingManager } from './recorders/recordingManager.js' -/* eslint-disable-next-line no-unused-vars -- replayRowingSession shouldn't be used in a production environments */ import { replayRowingSession } from './recorders/RowingReplayer.js' +import { parseSimulationArgs } from './tools/SimulationArgs.js' + +const simulationArgs = parseSimulationArgs(process.argv.slice(2)) const exec = promisify(child_process.exec) @@ -147,12 +149,13 @@ async function shutdownApp () { peripheralManager.handleCommand('shutdown') } -/* Uncomment the following lines to simulate a session -setTimeout(function() { - replayRowingSession(handleRotationImpulse, { - filename: 'recordings/Concept2_RowErg_Session_2000meters.csv', // Concept 2, 2000 meter session - realtime: true, - loop: true - }) -}, 30000) -*/ +if (simulationArgs.simulate) { + log.info(`Simulation mode enabled, replaying '${simulationArgs.simulateFile}' after ${simulationArgs.simulateDelay}ms delay (realtime: ${simulationArgs.realtime}, loop: ${simulationArgs.loop})`) + setTimeout(() => { + replayRowingSession(handleRotationImpulse, { + filename: simulationArgs.simulateFile, + realtime: simulationArgs.realtime, + loop: simulationArgs.loop + }) + }, simulationArgs.simulateDelay) +} diff --git a/app/tools/SimulationArgs.js b/app/tools/SimulationArgs.js new file mode 100644 index 0000000000..af09e37bb0 --- /dev/null +++ b/app/tools/SimulationArgs.js @@ -0,0 +1,41 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/JaapvanEkris/openrowingmonitor + + Parses command-line arguments for session simulation. +*/ + +import { parseArgs } from 'util' + +const simulationArgOptions = /** @type {const} */ ({ + simulate: { type: 'boolean', default: false }, + simulateFile: { type: 'string', default: 'recordings/Concept2_RowErg_Session_2000meters.csv' }, + simulateDelay: { type: 'string', default: '30000' }, + simulateOnce: { type: 'boolean', default: false }, + simulateFast: { type: 'boolean', default: false } +}) + +/** + * @param {string[]} argv + */ +function parseSimulationArgs (argv) { + const { values } = parseArgs({ + options: simulationArgOptions, + args: argv, + strict: false + }) + + const parsedDelay = parseInt(String(values.simulateDelay ?? '30000'), 10) + + return { + simulate: !!values.simulate, + simulateFile: String(values.simulateFile ?? 'recordings/Concept2_RowErg_Session_2000meters.csv'), + simulateDelay: Number.isFinite(parsedDelay) && parsedDelay >= 0 ? parsedDelay : 30000, + realtime: !values.simulateFast, + loop: !values.simulateOnce + } +} + +export { + parseSimulationArgs +} diff --git a/app/tools/SimulationArgs.test.js b/app/tools/SimulationArgs.test.js new file mode 100644 index 0000000000..696333eea6 --- /dev/null +++ b/app/tools/SimulationArgs.test.js @@ -0,0 +1,88 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/JaapvanEkris/openrowingmonitor + + Tests for SimulationArgs parsing +*/ +import { test } from 'uvu' +import * as assert from 'uvu/assert' +import { parseSimulationArgs } from './SimulationArgs.js' + +test('defaults when no args are passed', () => { + const result = parseSimulationArgs([]) + assert.is(result.simulate, false) + assert.is(result.simulateFile, 'recordings/Concept2_RowErg_Session_2000meters.csv') + assert.is(result.simulateDelay, 30000) + assert.is(result.realtime, true) + assert.is(result.loop, true) +}) + +test('--simulate enables simulation', () => { + const result = parseSimulationArgs(['--simulate']) + assert.is(result.simulate, true) + assert.is(result.simulateFile, 'recordings/Concept2_RowErg_Session_2000meters.csv') + assert.is(result.simulateDelay, 30000) + assert.is(result.realtime, true) + assert.is(result.loop, true) +}) + +test('--simulateFile overrides default file', () => { + const result = parseSimulationArgs(['--simulate', '--simulateFile', 'recordings/WRX700_2magnets.csv']) + assert.is(result.simulate, true) + assert.is(result.simulateFile, 'recordings/WRX700_2magnets.csv') +}) + +test('--simulateDelay overrides default delay', () => { + const result = parseSimulationArgs(['--simulate', '--simulateDelay', '5000']) + assert.is(result.simulateDelay, 5000) +}) + +test('--simulateOnce disables looping', () => { + const result = parseSimulationArgs(['--simulate', '--simulateOnce']) + assert.is(result.loop, false) + assert.is(result.realtime, true) +}) + +test('--simulateFast disables realtime', () => { + const result = parseSimulationArgs(['--simulate', '--simulateFast']) + assert.is(result.realtime, false) + assert.is(result.loop, true) +}) + +test('all flags combined', () => { + const result = parseSimulationArgs([ + '--simulate', + '--simulateFile', 'recordings/RX800.csv', + '--simulateDelay', '1000', + '--simulateOnce', + '--simulateFast' + ]) + assert.is(result.simulate, true) + assert.is(result.simulateFile, 'recordings/RX800.csv') + assert.is(result.simulateDelay, 1000) + assert.is(result.realtime, false) + assert.is(result.loop, false) +}) + +test('non-numeric delay falls back to default', () => { + const result = parseSimulationArgs(['--simulate', '--simulateDelay', 'abc']) + assert.is(result.simulateDelay, 30000) +}) + +test('empty delay falls back to default', () => { + const result = parseSimulationArgs(['--simulate', '--simulateDelay', '']) + assert.is(result.simulateDelay, 30000) +}) + +test('negative delay falls back to default', () => { + const result = parseSimulationArgs(['--simulate', '--simulateDelay', '-5000']) + assert.is(result.simulateDelay, 30000) +}) + +test('unknown flags are ignored', () => { + const result = parseSimulationArgs(['--simulate', '--unknownFlag']) + assert.is(result.simulate, true) + assert.is(result.simulateDelay, 30000) +}) + +test.run() diff --git a/package.json b/package.json index 41b1efbda9..6657e2f409 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "scripts": { "lint": "eslint ./app ./config && markdownlint-cli2 '**/*.md' '#node_modules'", "start": "node app/server.js", + "simulate": "node app/server.js --simulate", "dev": "vite", "build": "vite build", "build:watch": "vite build --watch",