adds features to store recordings with gzip compression, fixes a bug with the recording config
This commit is contained in:
parent
2ccd54a65d
commit
a4a7a1b0a0
|
|
@ -7,10 +7,12 @@
|
|||
Todo: split this into multiple modules
|
||||
*/
|
||||
import log from 'loglevel'
|
||||
import fs from 'fs'
|
||||
import { mkdir } from 'fs/promises'
|
||||
import zlib from 'zlib'
|
||||
import { mkdir, writeFile } from 'fs/promises'
|
||||
import xml2js from 'xml2js'
|
||||
import config from '../tools/ConfigManager.js'
|
||||
import { promisify } from 'util'
|
||||
const gzip = promisify(zlib.gzip)
|
||||
|
||||
function createWorkoutRecorder () {
|
||||
let strokes = []
|
||||
|
|
@ -23,7 +25,7 @@ function createWorkoutRecorder () {
|
|||
}
|
||||
// impulse recordings a currently only used to create raw data files, so we can skip it
|
||||
// if raw data file creation is disabled
|
||||
if (config.recordRawData) {
|
||||
if (config.createRawDataFiles) {
|
||||
rotationImpulses.push(impulse)
|
||||
}
|
||||
}
|
||||
|
|
@ -42,7 +44,7 @@ function createWorkoutRecorder () {
|
|||
async function createTcxFile () {
|
||||
const stringifiedStartTime = startTime.toISOString().replace(/T/, '_').replace(/:/g, '-').replace(/\..+/, '')
|
||||
const directory = `${config.dataDirectory}/recordings/${startTime.getFullYear()}/${(startTime.getMonth() + 1).toString().padStart(2, '0')}`
|
||||
const filename = `${directory}/${stringifiedStartTime}_rowing.tcx`
|
||||
const filename = `${directory}/${stringifiedStartTime}_rowing.tcx${config.gzipTcxFiles ? '.gz' : ''}`
|
||||
log.info(`saving session as tcx file ${filename}...`)
|
||||
|
||||
try {
|
||||
|
|
@ -53,7 +55,7 @@ function createWorkoutRecorder () {
|
|||
}
|
||||
}
|
||||
|
||||
buildAndSaveTcxFile({
|
||||
await buildAndSaveTcxFile({
|
||||
id: startTime.toISOString(),
|
||||
filename,
|
||||
startTime,
|
||||
|
|
@ -64,7 +66,7 @@ function createWorkoutRecorder () {
|
|||
async function createRawDataFile () {
|
||||
const stringifiedStartTime = startTime.toISOString().replace(/T/, '_').replace(/:/g, '-').replace(/\..+/, '')
|
||||
const directory = `${config.dataDirectory}/recordings/${startTime.getFullYear()}/${(startTime.getMonth() + 1).toString().padStart(2, '0')}`
|
||||
const filename = `${directory}/${stringifiedStartTime}_raw.csv`
|
||||
const filename = `${directory}/${stringifiedStartTime}_raw.csv${config.gzipRawDataFiles ? '.gz' : ''}`
|
||||
log.info(`saving session as raw data file ${filename}...`)
|
||||
|
||||
try {
|
||||
|
|
@ -74,11 +76,10 @@ function createWorkoutRecorder () {
|
|||
log.error(`can not create directory ${directory}`, error)
|
||||
}
|
||||
}
|
||||
|
||||
fs.writeFile(filename, rotationImpulses.join('\n'), (err) => { if (err) log.error(err) })
|
||||
await createFile(rotationImpulses.join('\n'), filename, config.gzipRawDataFiles)
|
||||
}
|
||||
|
||||
function buildAndSaveTcxFile (workout) {
|
||||
async function buildAndSaveTcxFile (workout) {
|
||||
let versionArray = process.env.npm_package_version.split('.')
|
||||
if (versionArray.length < 3) versionArray = [0, 0, 0]
|
||||
const lastStroke = workout.strokes[strokes.length - 1]
|
||||
|
|
@ -163,8 +164,7 @@ function createWorkoutRecorder () {
|
|||
}
|
||||
|
||||
const builder = new xml2js.Builder()
|
||||
const tcxXml = builder.buildObject(tcxObject)
|
||||
fs.writeFile(workout.filename, tcxXml, (err) => { if (err) log.error(err) })
|
||||
await createFile(builder.buildObject(tcxObject), workout.filename, config.gzipTcxFiles)
|
||||
}
|
||||
|
||||
async function reset () {
|
||||
|
|
@ -174,29 +174,41 @@ function createWorkoutRecorder () {
|
|||
startTime = undefined
|
||||
}
|
||||
|
||||
async function createFile (content, filename, compress = false) {
|
||||
if (compress) {
|
||||
const gzipContent = await gzip(content)
|
||||
await writeFile(filename, gzipContent, (err) => { if (err) log.error(err) })
|
||||
} else {
|
||||
await writeFile(filename, content, (err) => { if (err) log.error(err) })
|
||||
}
|
||||
}
|
||||
|
||||
function handlePause () {
|
||||
createRecordings()
|
||||
}
|
||||
|
||||
async function createRecordings () {
|
||||
if (!config.recordRawData && !config.createTcxFiles) {
|
||||
if (!config.createRawDataFiles && !config.createTcxFiles) {
|
||||
return
|
||||
}
|
||||
|
||||
const minimumRecordingTimeInSeconds = 10
|
||||
const rotationImpulseTimeTotal = rotationImpulses.reduce((acc, impulse) => acc + impulse, 0)
|
||||
const strokeTimeTotal = strokes.reduce((acc, stroke) => acc + stroke.strokeTime, 0)
|
||||
if (rotationImpulseTimeTotal < minimumRecordingTimeInSeconds || strokeTimeTotal < minimumRecordingTimeInSeconds) {
|
||||
if (Math.max(rotationImpulseTimeTotal, strokeTimeTotal) < minimumRecordingTimeInSeconds) {
|
||||
log.debug(`recording time is less than ${minimumRecordingTimeInSeconds}s, skipping creation of recording files...`)
|
||||
return
|
||||
}
|
||||
|
||||
if (config.recordRawData) {
|
||||
await createRawDataFile()
|
||||
const parallelCalls = []
|
||||
|
||||
if (config.createRawDataFiles) {
|
||||
parallelCalls.push(createRawDataFile())
|
||||
}
|
||||
if (config.createTcxFiles) {
|
||||
await createTcxFile()
|
||||
parallelCalls.push(createTcxFile())
|
||||
}
|
||||
await Promise.all(parallelCalls)
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -56,7 +56,16 @@ export default {
|
|||
createTcxFiles: true,
|
||||
|
||||
// Stores the raw sensor data in CSV files
|
||||
recordRawData: false,
|
||||
createRawDataFiles: false,
|
||||
|
||||
// Apply gzip compression to the recorded tcx training sessions file (tcx.gz)
|
||||
// This will drastically reduce the file size of the files (only around 4% of the original file)
|
||||
// Some training tools can directly work with gzipped tcx file, however for most training websites
|
||||
// you will have to unzip the files before uploading
|
||||
gzipTcxFiles: false,
|
||||
|
||||
// Apply gzip compression to the ras sensor data recording files (csv.gz)
|
||||
gzipRawDataFiles: true,
|
||||
|
||||
// Defines the name that is used to announce the FTMS Rower via Bluetooth Low Energy (BLE)
|
||||
// Some rowing training applications expect that the rowing device is announced with a certain name
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ Another set of settings are the **flankLength** and **numberOfErrorsAllowed** se
|
|||
|
||||
At the level of the stroke detection, there is some additional noise filtering, preventing noise to start a drive- or recovery-phase too early. The settings **minimumDriveTime** and **minimumRecoveryTime** determine the minimum times (in seconds) for the drive and recovery phases. Generally, the drive phase lasts at least 0.500 second, and the recovery phase 1.250 second for recreational rowers.
|
||||
|
||||
For the noise reduction settings and stroke detection settings, you can use the Excel tool. When OpenRowingMonitor records a log (set setting recordRawData to true), 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.
|
||||
For the noise reduction settings and stroke detection settings, you can use the Excel tool. When OpenRowingMonitor records a log (set setting createRawDataFiles to `true`), 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.
|
||||
|
||||
Please note that changing the noise filtering and stroke detection settings will affect your calculated dragFactor. So it is best to start with rowing a few strokes to determine settings for noise filtering and stroke detection, and then move on to the other settings.
|
||||
|
||||
|
|
@ -73,4 +73,4 @@ Some people want it all, and we're happy to give to you when your rower and your
|
|||
* **numOfPhasesForAveragingScreenData**: we average the data from several stroke phases to prevent the monitor and Bluetooth devices to become fidgety. Typically, we set this value to 6, which means 3 strokes (there are two phases in each stroke). However, some Bluetooth devices do their own calculations. And sometimes you really want the feedback on your individual strokes without any punches hold back. Setting this to 1 will result in a very volatile, but direct feedback mechanism on your stroke.
|
||||
* **dampingConstantSmoothing** determines the smoothing of the dragfactor across strokes (if autoAdjustDragFactor is set to true). Normally set at 5 strokes, which prevents wild values to throw off all your measurements. If you have rower that produces very little noise in the data, then it could be an option to reduce. If your machine produces noisy data, this is the one to increase before anything else.
|
||||
* **dampingConstantMaxChange** determines the maximum change between a currently determined dragfactor and the current average of the previous dampingConstantSmoothing strokes (if autoAdjustDragFactor is set to true). This filter reduces spikes in the calculation and thus makes the dragfactor less responsive to changes. The default value of 0.10 implies that the maximum upward/downward change is an increase of the drag with 10%. Please note that this filter still allows changes, it just limits their impact to this percentage. Most rower's dragfactor is relatively constant, however certain hyrid rower's dragfactor changes when the speed changes. To allow for bigger changes within a stroke, increase this setting. When the expected changes are small, set this setting small. When your rower is a hybrid or when you have one configuration for all your damper settings, this should be a bit wider.
|
||||
* **recordRawData**: This is as raw as it gets, as setting this to `true` makes Open Rowing Monitor dump the raw impulse-lengths to a file (see [how we interpret this data](physics_openrowingmonitor.md)).
|
||||
* **createRawDataFiles**: This is as raw as it gets, as setting this to `true` makes Open Rowing Monitor dump the raw impulse-lengths to a file (see [how we interpret this data](physics_openrowingmonitor.md)).
|
||||
|
|
|
|||
Loading…
Reference in New Issue