From 361e1c65f8140b1310bb72e67100ea4ba76e56fa Mon Sep 17 00:00:00 2001
From: Lars Berning <151194+laberning@users.noreply.github.com>
Date: Tue, 9 Mar 2021 19:39:42 +0000
Subject: [PATCH] adds logging framework, cleans documentation
---
README.md | 15 +++++-----
...itnessMachineControlPointCharacteristic.js | 9 +++---
app/ble/FitnessMachineStatusCharacteristic.js | 11 +++----
app/ble/IndoorBikeDataCharacteristic.js | 9 +++---
app/ble/IndoorBikeFeatureCharacteristic.js | 3 +-
app/ble/RowerDataCharacteristic.js | 7 +++--
app/ble/RowerFeatureCharacteristic.js | 3 +-
app/ble/RowingMachinePeripheral.js | 28 +++++++++---------
app/client/style.css | 6 ++--
app/engine/RowingEngine.js | 17 ++++++-----
app/server.js | 29 ++++++++++---------
app/tools/RowingRecorder.js | 5 ++--
doc/attribution.md | 2 +-
doc/backlog.md | 5 ++--
package-lock.json | 5 ++++
package.json | 1 +
16 files changed, 86 insertions(+), 69 deletions(-)
diff --git a/README.md b/README.md
index 4e54435..9f0d487 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,15 @@
-
-
# Open Rowing Monitor
+
+
+
An open source rowing monitor for rowing exercise machines.
-The Open Rowing Monitor runs on a Raspberry Pi and measures the rotation of the rower's flywheel to calculate rowing specific metrics such as power, split time, speed, stroke rate, distance and calories.
+Open Rowing Monitor runs on a Raspberry Pi and measures the rotation of the rower's flywheel to calculate rowing specific metrics such as power, split time, speed, stroke rate, distance and calories.
-The web interface can be used to view those metrics on any device that runs a browser (i.e. a smartphone that you attach to your rowing machine while training).
+A web interface visualizes those metrics on any device that can run a browser (i.e. a smartphone that you attach to your rowing machine while training).
-The Open Rowing Monitor also implements the Bluetooth Low Energy (BLE) protocol for Fitness Machine Service (FTMS). This allows using your rowing machine with Fitness Applications that support FTMS.
+Open Rowing Monitor also implements the Bluetooth Low Energy (BLE) protocol for Fitness Machine Service (FTMS). This allows using your rowing machine with any Fitness Application that supports FTMS.
FTMS supports different types of fitness machines. Open Rowing Monitor currently supports the type **FTMS Rower** and simulates the type **FTMS Indoor Bike**.
@@ -16,9 +17,9 @@ FTMS supports different types of fitness machines. Open Rowing Monitor currently
**FTMS Indoor Bike** is widely adopted by training applications for bike training. The simulated Indoor Bike offers metrics such as power and distance to the biking application. So why not use your rowing machine to row up a mountain in Zwift, Bkool, Sufferfest or similar :-)
-I basically started this project, because my rowing machine (WRX700) has a very crappy computer and I wanted to build something with more realistic metrics and more features. But there is not much that is specific to that rowing machine. It should run fine with any rowing machine that uses an air or water resistance mechanism.
+I originally started this project, because my rowing machine (WRX700) has a very simple computer and I wanted to build something with a clean interface that calculates more realistic metrics. But there is not much that is specific to that rowing machine. It should run fine with any rowing machine that uses an air or water resistance mechanism.
-Feel free to contact me if you have any questions to this project. Let me know if you run this with a different rowing machine setup so I can expand the documentation.
+Feel free to contact me if you have any questions about this project. Let me know if you run Open Rowing Machine with a different setup so I can expand the documentation.
This project is already in a very usable stage, but some things are still a bit rough on the edges.
diff --git a/app/ble/FitnessMachineControlPointCharacteristic.js b/app/ble/FitnessMachineControlPointCharacteristic.js
index 67df01b..c63fbc2 100644
--- a/app/ble/FitnessMachineControlPointCharacteristic.js
+++ b/app/ble/FitnessMachineControlPointCharacteristic.js
@@ -9,6 +9,7 @@
- Zwift: uses startOrResume and setIndoorBikeSimulationParameters
*/
import bleno from '@abandonware/bleno'
+import log from 'loglevel'
// see https://www.bluetooth.com/specifications/specs/fitness-machine-service-1-0 for details
const ControlPointOpCode = {
@@ -67,7 +68,7 @@ export default class FitnessMachineControlPointCharacteristic extends bleno.Char
case ControlPointOpCode.requestControl:
if (!this.controlled) {
if (this.controlPointCallback({ name: 'requestControl' })) {
- console.log('requestControl sucessful')
+ log.debug('requestControl sucessful')
this.controlled = true
callback(this.buildResponse(opCode, ResultCode.success))
} else {
@@ -95,7 +96,7 @@ export default class FitnessMachineControlPointCharacteristic extends bleno.Char
} else if (controlParameter === 2) {
this.handleSimpleCommand(ControlPointOpCode.stopOrPause, 'pause', callback)
} else {
- console.log(`stopOrPause with invalid controlParameter: ${controlParameter}`)
+ log.error(`stopOrPause with invalid controlParameter: ${controlParameter}`)
}
break
}
@@ -116,7 +117,7 @@ export default class FitnessMachineControlPointCharacteristic extends bleno.Char
}
default:
- console.log(`opCode ${opCode} is not supported`)
+ log.info(`opCode ${opCode} is not supported`)
callback(this.buildResponse(opCode, ResultCode.opCodeNotSupported))
}
}
@@ -130,7 +131,7 @@ export default class FitnessMachineControlPointCharacteristic extends bleno.Char
callback(this.buildResponse(opCode, ResultCode.operationFailed))
}
} else {
- console.log(`initating command '${opName}' requires 'requestControl'`)
+ log.info(`initating command '${opName}' requires 'requestControl'`)
callback(this.buildResponse(opCode, ResultCode.controlNotPermitted))
}
}
diff --git a/app/ble/FitnessMachineStatusCharacteristic.js b/app/ble/FitnessMachineStatusCharacteristic.js
index bf95c7f..0512dff 100644
--- a/app/ble/FitnessMachineStatusCharacteristic.js
+++ b/app/ble/FitnessMachineStatusCharacteristic.js
@@ -10,6 +10,7 @@
shall be exposed by the Server. Otherwise, supporting the Fitness Machine Status characteristic is optional.
*/
import bleno from '@abandonware/bleno'
+import log from 'loglevel'
// see page 67 https://www.bluetooth.com/specifications/specs/fitness-machine-service-1-0
const StatusOpCode = {
@@ -49,20 +50,20 @@ export default class FitnessMachineStatusCharacteristic extends bleno.Characteri
}
onSubscribe (maxValueSize, updateValueCallback) {
- console.log('FitnessMachineStatusCharacteristic - central subscribed')
+ log.debug('FitnessMachineStatusCharacteristic - central subscribed')
this._updateValueCallback = updateValueCallback
return this.RESULT_SUCCESS
}
onUnsubscribe () {
- console.log('FitnessMachineStatusCharacteristic - central unsubscribed')
+ log.debug('FitnessMachineStatusCharacteristic - central unsubscribed')
this._updateValueCallback = null
return this.RESULT_UNLIKELY_ERROR
}
notify (status) {
if (!(status && status.name)) {
- console.log('can not deliver status without name')
+ log.error('can not deliver status without name')
return this.RESULT_SUCCESS
}
if (this._updateValueCallback) {
@@ -78,11 +79,11 @@ export default class FitnessMachineStatusCharacteristic extends bleno.Characteri
buffer.writeUInt8(StatusOpCode.startedOrResumedByUser, 0)
break
default:
- console.log(`status ${status.name} is not supported`)
+ log.error(`status ${status.name} is not supported`)
}
this._updateValueCallback(buffer)
} else {
- // console.log('can not notify status, no central subscribed')
+ log.debug('can not notify status, no central subscribed')
}
return this.RESULT_SUCCESS
}
diff --git a/app/ble/IndoorBikeDataCharacteristic.js b/app/ble/IndoorBikeDataCharacteristic.js
index 4da41f7..ef2a061 100644
--- a/app/ble/IndoorBikeDataCharacteristic.js
+++ b/app/ble/IndoorBikeDataCharacteristic.js
@@ -19,6 +19,7 @@
while in a connection and the interval is not configurable by the Client
*/
import bleno from '@abandonware/bleno'
+import log from 'loglevel'
export default class IndoorBikeDataCharacteristic extends bleno.Characteristic {
constructor () {
@@ -32,13 +33,13 @@ export default class IndoorBikeDataCharacteristic extends bleno.Characteristic {
}
onSubscribe (maxValueSize, updateValueCallback) {
- console.log('IndooBikeDataCharacteristic - central subscribed')
+ log.debug('IndooBikeDataCharacteristic - central subscribed')
this._updateValueCallback = updateValueCallback
return this.RESULT_SUCCESS
};
onUnsubscribe () {
- console.log('IndooBikeDataCharacteristic - central unsubscribed')
+ log.debug('IndooBikeDataCharacteristic - central unsubscribed')
this._updateValueCallback = null
return this.RESULT_UNLIKELY_ERROR
};
@@ -46,7 +47,7 @@ export default class IndoorBikeDataCharacteristic extends bleno.Characteristic {
notify (data) {
// ignore events without the mandatory fields
if (!data.speed) {
- console.log('can not deliver bike data without mandatory fields')
+ log.error('can not deliver bike data without mandatory fields')
return this.RESULT_SUCCESS
}
@@ -87,7 +88,7 @@ export default class IndoorBikeDataCharacteristic extends bleno.Characteristic {
}
this._updateValueCallback(buffer)
} else {
- // console.log('can not notify indoor bike data, no central subscribed')
+ log.debug('can not notify indoor bike data, no central subscribed')
}
return this.RESULT_SUCCESS
}
diff --git a/app/ble/IndoorBikeFeatureCharacteristic.js b/app/ble/IndoorBikeFeatureCharacteristic.js
index 0b0738a..f1b1a13 100644
--- a/app/ble/IndoorBikeFeatureCharacteristic.js
+++ b/app/ble/IndoorBikeFeatureCharacteristic.js
@@ -8,6 +8,7 @@
are supported in IndoorBikeDataCharacteristic and FitnessMachineControlPointCharacteristic.
*/
import bleno from '@abandonware/bleno'
+import log from 'loglevel'
export default class IndoorBikeDataCharacteristic extends bleno.Characteristic {
constructor (uuid, description, value) {
@@ -29,7 +30,7 @@ export default class IndoorBikeDataCharacteristic extends bleno.Characteristic {
// none
// 0000000 0000000
const features = [0x04, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
- console.log('Features of Indoor Bike requested')
+ log.debug('Features of Indoor Bike requested')
callback(this.RESULT_SUCCESS, features.slice(offset, features.length))
}
}
diff --git a/app/ble/RowerDataCharacteristic.js b/app/ble/RowerDataCharacteristic.js
index faabf45..e2e154b 100644
--- a/app/ble/RowerDataCharacteristic.js
+++ b/app/ble/RowerDataCharacteristic.js
@@ -12,6 +12,7 @@
while in a connection and the interval is not configurable by the Client
*/
import bleno from '@abandonware/bleno'
+import log from 'loglevel'
export default class RowerDataCharacteristic extends bleno.Characteristic {
constructor () {
@@ -25,13 +26,13 @@ export default class RowerDataCharacteristic extends bleno.Characteristic {
}
onSubscribe (maxValueSize, updateValueCallback) {
- console.log('RowerDataCharacteristic - central subscribed')
+ log.debug('RowerDataCharacteristic - central subscribed')
this._updateValueCallback = updateValueCallback
return this.RESULT_SUCCESS
};
onUnsubscribe () {
- console.log('RowerDataCharacteristic - central unsubscribed')
+ log.debug('RowerDataCharacteristic - central unsubscribed')
this._updateValueCallback = null
return this.RESULT_UNLIKELY_ERROR
};
@@ -85,7 +86,7 @@ export default class RowerDataCharacteristic extends bleno.Characteristic {
}
this._updateValueCallback(buffer)
} else {
- // console.log('can not notify rower data, no central subscribed')
+ log.debug('can not notify rower data, no central subscribed')
}
return this.RESULT_SUCCESS
}
diff --git a/app/ble/RowerFeatureCharacteristic.js b/app/ble/RowerFeatureCharacteristic.js
index 5bbf1e8..e9d1584 100644
--- a/app/ble/RowerFeatureCharacteristic.js
+++ b/app/ble/RowerFeatureCharacteristic.js
@@ -8,6 +8,7 @@
are supported in RowerDataCharacteristic and FitnessMachineControlPointCharacteristic.
*/
import bleno from '@abandonware/bleno'
+import log from 'loglevel'
export default class RowerFeatureCharacteristic extends bleno.Characteristic {
constructor () {
@@ -29,7 +30,7 @@ export default class RowerFeatureCharacteristic extends bleno.Characteristic {
// none
// 0000000 0000000
const features = [0x24, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
- console.log('Features of Rower requested')
+ log.debug('Features of Rower requested')
callback(this.RESULT_SUCCESS, features.slice(offset, features.length))
};
}
diff --git a/app/ble/RowingMachinePeripheral.js b/app/ble/RowingMachinePeripheral.js
index 61eea8f..e5eb3d2 100644
--- a/app/ble/RowingMachinePeripheral.js
+++ b/app/ble/RowingMachinePeripheral.js
@@ -15,6 +15,7 @@ import bleno from '@abandonware/bleno'
import { EventEmitter } from 'events'
import FitnessMachineService from './FitnessMachineService.js'
import DeviceInformationService from './DeviceInformationService.js'
+import log from 'loglevel'
function createRowingMachinePeripheral (options) {
const emitter = new EventEmitter()
@@ -29,7 +30,7 @@ function createRowingMachinePeripheral (options) {
peripheralName,
[fitnessMachineService.uuid, deviceInformationService.uuid],
(error) => {
- if (error) console.log(error)
+ if (error) log.error(error)
}
)
} else {
@@ -42,46 +43,45 @@ function createRowingMachinePeripheral (options) {
bleno.setServices(
[fitnessMachineService, deviceInformationService],
(error) => {
- if (error) console.log(error)
+ if (error) log.error(error)
})
}
})
bleno.on('accept', (clientAddress) => {
- console.log(`ble central connected: ${clientAddress}`)
+ log.debug(`ble central connected: ${clientAddress}`)
// todo: do we need this?
bleno.updateRssi()
})
bleno.on('disconnect', (clientAddress) => {
- console.log(`ble central disconnected: ${clientAddress}`)
+ log.debug(`ble central disconnected: ${clientAddress}`)
})
- /*
+
bleno.on('platform', (event) => {
- console.log('platform', event)
+ log.debug('platform', event)
})
bleno.on('addressChange', (event) => {
- console.log('addressChange', event)
+ log.debug('addressChange', event)
})
bleno.on('mtuChange', (event) => {
- console.log('mtuChange', event)
+ log.debug('mtuChange', event)
})
bleno.on('advertisingStartError', (event) => {
- console.log('advertisingStartError', event)
+ log.debug('advertisingStartError', event)
})
bleno.on('advertisingStop', (event) => {
- console.log('advertisingStop', event)
+ log.debug('advertisingStop', event)
})
bleno.on('servicesSet', (event) => {
- console.log('servicesSet', event)
+ log.debug('servicesSet', event)
})
bleno.on('servicesSetError', (event) => {
- console.log('servicesSetError', event)
+ log.debug('servicesSetError', event)
})
bleno.on('rssiUpdate', (event) => {
- console.log('rssiUpdate', event)
+ log.debug('rssiUpdate', event)
})
-*/
function controlPointCallback (event) {
const obj = {
diff --git a/app/client/style.css b/app/client/style.css
index faeecf8..96ed13c 100644
--- a/app/client/style.css
+++ b/app/client/style.css
@@ -48,12 +48,12 @@ span.unit {
button {
outline:none;
- background-color: #0059B3;
+ background-color: #00468c;
border: 0;
color: white;
- padding: 1vw 2vw;
+ padding: 1.5vw 2vw;
margin: 1vw;
- font-size: 60%;
+ font-size: 80%;
text-align: center;
text-decoration: none;
display: inline-block;
diff --git a/app/engine/RowingEngine.js b/app/engine/RowingEngine.js
index 1584a57..d341881 100644
--- a/app/engine/RowingEngine.js
+++ b/app/engine/RowingEngine.js
@@ -10,6 +10,7 @@
Physics of Rowing by Anu Dudhia: http://eodg.atm.ox.ac.uk/user/dudhia/rowing/physics
Also Dave Vernooy has some good explanations here: https://dvernooy.github.io/projects/ergware
*/
+import log from 'loglevel'
import { createAverager } from './Averager.js'
import { createTimer } from './Timer.js'
@@ -84,7 +85,7 @@ function createRowingEngine () {
// todo: we should inform the workoutHandler in this case
// (if we want to track the training history)
if (currentDt > 3.0) {
- console.log(`training pause detected: ${currentDt}`)
+ log.debug(`training pause detected: ${currentDt}`)
return
}
@@ -136,25 +137,25 @@ function createRowingEngine () {
if (!isInDrivePhase && !wasInDrivePhase) { updateRecoveryPhase(currentDt) }
timer.updateTimers(currentDt)
- // console.log(`𝑑t: ${currentDt} ω: ${omegaVector[0].toFixed(2)} ωdot: ${omegaDotVector[0].toFixed(2)} ωdotdot: ${omegaDotDot.toFixed(2)} aPos: ${accelerationIsPositive} aChange: ${accelerationIsChanging}`)
+ log.debug(`𝑑t: ${currentDt} ω: ${omegaVector[0].toFixed(2)} ωdot: ${omegaDotVector[0].toFixed(2)} ωdotdot: ${omegaDotDot.toFixed(2)} aPos: ${accelerationIsPositive} aChange: ${accelerationIsChanging}`)
}
function startDrivePhase (currentDt) {
- // console.log('*** drive phase started')
+ log.debug('*** drive phase started')
timer.start('drive')
jPower = 0.0
kPower = 0.0
if (strokeElapsed - driveElapsed !== 0) {
kDampEstimatorAverager.pushValue(kDampEstimator / (strokeElapsed - driveElapsed))
}
- // console.log(`estimated kDamp: ${jMoment * (-1 * kDampEstimatorAverager.weightedAverage())}`)
- // console.log(`estimated omegaDotDivOmegaSquare: ${-1 * kDampEstimatorAverager.weightedAverage()}`)
+ log.debug(`estimated kDamp: ${jMoment * (-1 * kDampEstimatorAverager.weightedAverage())}`)
+ log.debug(`estimated omegaDotDivOmegaSquare: ${-1 * kDampEstimatorAverager.weightedAverage()}`)
}
function updateDrivePhase (currentDt) {
jPower = jPower + jMoment * omegaVector[0] * omegaDotVector[0] * currentDt
kPower = kPower + kDamp * (omegaVector[0] * omegaVector[0] * omegaVector[0]) * currentDt
- // console.log(`Jpower: ${jPower}, kPower: ${kPower}`)
+ log.debug(`Jpower: ${jPower}, kPower: ${kPower}`)
}
function startRecoveryPhase () {
@@ -162,7 +163,7 @@ function createRowingEngine () {
timer.stop('drive')
strokeElapsed = timer.getValue('stroke')
timer.stop('stroke')
- // console.log(`driveElapsed: ${driveElapsed}, strokeElapsed: ${strokeElapsed}`)
+ log.debug(`driveElapsed: ${driveElapsed}, strokeElapsed: ${strokeElapsed}`)
timer.start('stroke')
if (strokeElapsed !== 0 && workoutHandler) {
@@ -177,7 +178,7 @@ function createRowingEngine () {
// stroke finished, reset stroke specific measurements
kDampEstimator = 0.0
strokeDistance = 0
- // console.log('*** recovery phase started')
+ log.debug('*** recovery phase started')
}
function updateRecoveryPhase (currentDt) {
diff --git a/app/server.js b/app/server.js
index f3e04ad..2d19972 100644
--- a/app/server.js
+++ b/app/server.js
@@ -6,10 +6,6 @@
everything together while figuring out the physics and model of the application.
todo: refactor this as we progress
*/
-import { createRowingMachinePeripheral } from './ble/RowingMachinePeripheral.js'
-import { createRowingEngine } from './engine/RowingEngine.js'
-import { createRowingStatistics } from './engine/RowingStatistics.js'
-// import { recordRowingSession } from './tools/RowingRecorder.js'
// import readline from 'readline'
// import fs from 'fs'
import { fork } from 'child_process'
@@ -17,7 +13,14 @@ import WebSocket from 'ws'
import finalhandler from 'finalhandler'
import http from 'http'
import serveStatic from 'serve-static'
+import log from 'loglevel'
+import { createRowingMachinePeripheral } from './ble/RowingMachinePeripheral.js'
+import { createRowingEngine } from './engine/RowingEngine.js'
+import { createRowingStatistics } from './engine/RowingStatistics.js'
+// import { recordRowingSession } from './tools/RowingRecorder.js'
+// sets the global log level
+log.setLevel(log.levels.INFO)
let websocket
// recordRowingSession('recordings/wrx700_2magnets.csv')
const peripheral = createRowingMachinePeripheral({
@@ -28,31 +31,30 @@ peripheral.on('controlPoint', (event) => {
if (event?.req?.name === 'requestControl') {
event.res = true
} else if (event?.req?.name === 'reset') {
- console.log('reset requested')
+ log.debug('reset requested')
rowingStatistics.reset()
peripheral.notifyStatus({ name: 'reset' })
event.res = true
// todo: we could use these controls once we implement a concept of a rowing session
} else if (event?.req?.name === 'stop') {
- console.log('stop requested')
+ log.debug('stop requested')
peripheral.notifyStatus({ name: 'stoppedOrPausedByUser' })
event.res = true
} else if (event?.req?.name === 'pause') {
- console.log('pause requested')
+ log.debug('pause requested')
peripheral.notifyStatus({ name: 'stoppedOrPausedByUser' })
event.res = true
} else if (event?.req?.name === 'startOrResume') {
- console.log('startOrResume requested')
+ log.debug('startOrResume requested')
peripheral.notifyStatus({ name: 'startedOrResumedByUser' })
event.res = true
} else {
- console.log('unhandled Command', event.req)
+ log.info('unhandled Command', event.req)
}
})
const gpioTimerService = fork('./app/gpio/GpioTimerService.js')
gpioTimerService.on('message', (dataPoint) => {
- // console.log(dataPoint)
rowingEngine.handleRotationImpulse(dataPoint)
})
@@ -61,7 +63,7 @@ const rowingStatistics = createRowingStatistics()
rowingEngine.notify(rowingStatistics)
rowingStatistics.on('strokeFinished', (data) => {
- console.log(`stroke: ${data.strokesTotal}, dur: ${data.strokeTime}s, power: ${data.power}w` +
+ log.info(`stroke: ${data.strokesTotal}, dur: ${data.strokeTime}s, power: ${data.power}w` +
`, split: ${data.splitFormatted}, ratio: ${data.powerRatio}, dist: ${data.distanceTotal}m` +
`, cal: ${data.caloriesTotal}kcal, SPM: ${data.strokesPerMinute}, speed: ${data.speed}km/h`)
@@ -110,10 +112,10 @@ wss.on('connection', function connection (ws) {
rowingStatistics.reset()
peripheral.notifyStatus({ name: 'reset' })
} else {
- console.log(`invalid command received: ${data}`)
+ log.info(`invalid command received: ${data}`)
}
} catch (err) {
- console.log(err)
+ log.error(err)
}
})
/*
@@ -155,7 +157,6 @@ function simulateRowing () {
strokesPerMinute: 10 + 20 * (Math.random() - 0.5),
speed: (15 + 20 * (Math.random() - 0.5)).toFixed(2)
}
- // console.log(metrics)
peripheral.notifyData(metrics)
}
*/
diff --git a/app/tools/RowingRecorder.js b/app/tools/RowingRecorder.js
index 42a3434..a59dd13 100644
--- a/app/tools/RowingRecorder.js
+++ b/app/tools/RowingRecorder.js
@@ -7,14 +7,15 @@
import { fork } from 'child_process'
import fs from 'fs'
+import log from 'loglevel'
function recordRowingSession (filename) {
// measure the gpio interrupts in another process, since we need
// to track time close to realtime
const gpioTimerService = fork('./app/tools/GpioTimerService.js')
gpioTimerService.on('message', (dataPoint) => {
- console.log(dataPoint.delta)
- fs.appendFile(filename, `${dataPoint.delta}\n`, (err) => { if (err) console.log(err) })
+ log.debug(dataPoint.delta)
+ fs.appendFile(filename, `${dataPoint.delta}\n`, (err) => { if (err) log.error(err) })
})
}
diff --git a/doc/attribution.md b/doc/attribution.md
index 9030a0d..3efd767 100755
--- a/doc/attribution.md
+++ b/doc/attribution.md
@@ -6,4 +6,4 @@ Open Rowing Monitor uses some great work by others. Thank you for all the great
* Dave Vernooy's project description on [ErgWare](https://dvernooy.github.io/projects/ergware) has some good information on the maths involved in a rowing ergometer.
-* The app icon is based on this [image of a rowing machine](https://thenounproject.com/term/rowing-machine/659265) by [Gan Khoon Lay](https://thenounproject.com/leremy/) licensed under [CC BY 2.0](https://creativecommons.org/licenses/by/2.0/).
\ No newline at end of file
+* The app icon is based on this [image of a rowing machine](https://thenounproject.com/term/rowing-machine/659265) by [Gan Khoon Lay](https://thenounproject.com/leremy/) licensed under [CC BY 2.0](https://creativecommons.org/licenses/by/2.0/).
diff --git a/doc/backlog.md b/doc/backlog.md
index e02639e..14ef6a0 100644
--- a/doc/backlog.md
+++ b/doc/backlog.md
@@ -8,16 +8,15 @@ This is the very minimalistic Backlog for further development of this project.
* handle training interruptions (set stroke specific metrics to "0" if no impulse detected for x seconds)
* check todo markers in code and add them to this backlog
* cleanup of the server.js start file
-* add a logging framework
* figure out where to set the Service Advertising Data (FTMS.pdf p 15)
* Web UI: Replace Fullscreen Button with Exit Button when started from Homescreen
+* investigate bug: crash, when one unsubscribe to BLE "Generic Attribute", probably a bleno bug "handleAttribute.emit is not a function"
* set up a raspi with the installation instructions to see if they are correct
## Later
* add a config file
* presets for rowing machine specific config parameters
-* set a more appropriate ble Appearance (currently 0x2A01 Generic Computer)
* validate FTMS with more Training Applications and harden implementation
* make Web UI a proper Web Application (tooling and SPA framework)
* record the workout and show a visual graph of metrics
@@ -25,5 +24,7 @@ This is the very minimalistic Backlog for further development of this project.
## Ideas
+* add support for BLE Heart Rate Sensor and show pulse
* add Video playback in Background of Web UI
+* implement or integrate some rowing games
* add possibility to define Workouts (i.e. training intervals with goals)
diff --git a/package-lock.json b/package-lock.json
index 554b5bd..905a886 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1627,6 +1627,11 @@
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
"integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168="
},
+ "loglevel": {
+ "version": "1.7.1",
+ "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.7.1.tgz",
+ "integrity": "sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw=="
+ },
"lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
diff --git a/package.json b/package.json
index 862dff6..912cee5 100644
--- a/package.json
+++ b/package.json
@@ -22,6 +22,7 @@
"@abandonware/bleno": "^0.5.1-3",
"finalhandler": "^1.1.2",
"http": "0.0.1-security",
+ "loglevel": "^1.7.1",
"onoff": "^6.0.1",
"serve-static": "^1.14.1",
"ws": "^7.4.3"