From e5e579f3fcf5f720abc74d20493d12685ff4ec2e Mon Sep 17 00:00:00 2001 From: Lars Berning <151194+laberning@users.noreply.github.com> Date: Sat, 20 Mar 2021 22:04:52 +0000 Subject: [PATCH] notifies PM5 central when stroke state changes --- app/ble/FtmsPeripheral.js | 21 ++++++++++----------- app/ble/PeripheralManager.js | 14 +++++--------- app/ble/Pm5Peripheral.js | 19 +++++++------------ app/ble/pm5/Pm5RowingService.js | 17 +++++++++++------ app/engine/RowingEngine.js | 2 +- app/engine/RowingStatistics.js | 6 +++--- app/server.js | 4 ++++ doc/backlog.md | 1 - 8 files changed, 41 insertions(+), 43 deletions(-) diff --git a/app/ble/FtmsPeripheral.js b/app/ble/FtmsPeripheral.js index afabd8f..ecfb03b 100644 --- a/app/ble/FtmsPeripheral.js +++ b/app/ble/FtmsPeripheral.js @@ -12,14 +12,11 @@ (Manufacturer Name String, Model Number String) */ import bleno from '@abandonware/bleno' -import { EventEmitter } from 'events' import FitnessMachineService from './ftms/FitnessMachineService.js' import DeviceInformationService from './ftms/DeviceInformationService.js' import log from 'loglevel' -function createFtmsPeripheral (options) { - const emitter = new EventEmitter() - +function createFtmsPeripheral (controlCallback, options) { const peripheralName = options?.simulateIndoorBike ? 'OpenRowingBike' : 'OpenRowingMonitor' // const peripheralName = options?.simulateIndoorBike ? 'OpenRowingBike' : 'S1 Comms 1' const fitnessMachineService = new FitnessMachineService(options, controlPointCallback) @@ -72,7 +69,7 @@ function createFtmsPeripheral (options) { req: event, res: {} } - emitter.emit('controlPoint', obj) + if (controlCallback) controlCallback(obj) return obj.res } @@ -99,22 +96,24 @@ function createFtmsPeripheral (options) { } } - // deliver current rowing metrics via BLE - function notifyData (data) { - fitnessMachineService.notifyData(data) + // present current rowing metrics to FTMS central + function notifyData (type, data) { + if (type === 'strokeFinished' || type === 'rowingPaused') { + fitnessMachineService.notifyData(data) + } } - // deliver a status change via BLE + // present current rowing status to FTMS central function notifyStatus (status) { fitnessMachineService.notifyStatus(status) } - return Object.assign(emitter, { + return { triggerAdvertising, notifyData, notifyStatus, destroy - }) + } } export { createFtmsPeripheral } diff --git a/app/ble/PeripheralManager.js b/app/ble/PeripheralManager.js index 70ecaec..66ebc00 100644 --- a/app/ble/PeripheralManager.js +++ b/app/ble/PeripheralManager.js @@ -36,7 +36,7 @@ function createPeripheralManager () { } function notifyMetrics (type, metrics) { - peripheral.notifyData(metrics) + peripheral.notifyData(type, metrics) } function notifyStatus (status) { @@ -46,29 +46,25 @@ function createPeripheralManager () { async function createPeripheral (newMode) { if (peripheral) { await peripheral.destroy() - peripheral.off('controlPoint', emitControlPointEvent) } if (newMode === 'PM5') { log.info('bluetooth profile: Concept2 PM5') - peripheral = createPm5Peripheral() + peripheral = createPm5Peripheral(controlCallback) mode = 'PM5' } else if (newMode === 'FTMSBIKE') { log.info('bluetooth profile: FTMS Indoor Bike') - peripheral = createFtmsPeripheral({ + peripheral = createFtmsPeripheral(controlCallback, { simulateIndoorBike: true }) mode = 'FTMSBIKE' } else { log.info('bluetooth profile: FTMS Rower') - peripheral = createFtmsPeripheral({ + peripheral = createFtmsPeripheral(controlCallback, { simulateIndoorBike: false }) mode = 'FTMS' } - // todo: re-emitting is not that great, maybe use callbacks instead - peripheral.on('control', emitControlPointEvent) - peripheral.triggerAdvertising() emitter.emit('control', { @@ -79,7 +75,7 @@ function createPeripheralManager () { }) } - function emitControlPointEvent (event) { + function controlCallback (event) { emitter.emit('control', event) } diff --git a/app/ble/Pm5Peripheral.js b/app/ble/Pm5Peripheral.js index f080f72..4e90519 100644 --- a/app/ble/Pm5Peripheral.js +++ b/app/ble/Pm5Peripheral.js @@ -8,7 +8,6 @@ see: https://www.concept2.co.uk/files/pdf/us/monitors/PM5_BluetoothSmartInterfaceDefinition.pdf */ import bleno from '@abandonware/bleno' -import { EventEmitter } from 'events' import { constants } from './pm5/Pm5Constants.js' import DeviceInformationService from './pm5/DeviceInformationService.js' import GapService from './pm5/GapService.js' @@ -16,9 +15,7 @@ import log from 'loglevel' import Pm5ControlService from './pm5/Pm5ControlService.js' import Pm5RowingService from './pm5/Pm5RowingService.js' -function createPm5Peripheral (options) { - const emitter = new EventEmitter() - +function createPm5Peripheral (controlCallback, options) { const peripheralName = constants.name const deviceInformationService = new DeviceInformationService() const gapService = new GapService() @@ -90,23 +87,21 @@ function createPm5Peripheral (options) { } } - // deliver current rowing metrics via BLE - function notifyData (data) { - rowingService.notify(data) - // fitnessMachineService.notifyData(data) + // present current rowing metrics to C2-PM5 central + function notifyData (type, data) { + rowingService.notifyData(type, data) } - // deliver a status change via BLE + // present current rowing status to C2-PM5 central function notifyStatus (status) { - // fitnessMachineService.notifyStatus(status) } - return Object.assign(emitter, { + return { triggerAdvertising, notifyData, notifyStatus, destroy - }) + } } export { createPm5Peripheral } diff --git a/app/ble/pm5/Pm5RowingService.js b/app/ble/pm5/Pm5RowingService.js index bda2c86..f6e6eb3 100644 --- a/app/ble/pm5/Pm5RowingService.js +++ b/app/ble/pm5/Pm5RowingService.js @@ -75,11 +75,16 @@ export default class PM5RowingService extends bleno.PrimaryService { this.multiplexedCharacteristic = multiplexedCharacteristic } - notify (data) { - this.generalStatus.notify(data) - this.additionalStatus.notify(data) - this.additionalStatus2.notify(data) - this.strokeData.notify(data) - this.additionalStrokeData.notify(data) + notifyData (type, data) { + if (type === 'strokeFinished' || type === 'rowingPaused') { + this.generalStatus.notify(data) + this.additionalStatus.notify(data) + this.additionalStatus2.notify(data) + this.strokeData.notify(data) + this.additionalStrokeData.notify(data) + } else if (type === 'strokeStateChanged') { + // the stroke state is delivered via the GeneralStatus Characteristic, so we only need to notify that one + this.generalStatus.notify(data) + } } } diff --git a/app/engine/RowingEngine.js b/app/engine/RowingEngine.js index 379b45f..8fc3a1a 100644 --- a/app/engine/RowingEngine.js +++ b/app/engine/RowingEngine.js @@ -151,7 +151,7 @@ function createRowingEngine () { } log.debug(`estimated kDamp: ${jMoment * (-1 * kDampEstimatorAverager.weightedAverage())}`) log.info(`estimated omegaDotDivOmegaSquare: ${-1 * kDampEstimatorAverager.weightedAverage()}`) - workoutHandler.handleStrokeStateChange({ + workoutHandler.handleStrokeStateChanged({ strokeState: 'DRIVING' }) } diff --git a/app/engine/RowingStatistics.js b/app/engine/RowingStatistics.js index de5c8ab..f036fb4 100644 --- a/app/engine/RowingStatistics.js +++ b/app/engine/RowingStatistics.js @@ -62,11 +62,11 @@ function createRowingStatistics () { } // initiated when the stroke state changes - function handleStrokeStateChange (state) { + function handleStrokeStateChanged (state) { // todo: wee need a better mechanism to communicate strokeState updates // this is an initial hacky attempt to see if we can use it for the C2-pm5 protocol lastStrokeState = state.strokeState - // emitter.emit('strokeFinished', getMetrics()) + emitter.emit('strokeStateChanged', getMetrics()) } function getMetrics () { @@ -154,7 +154,7 @@ function createRowingStatistics () { return Object.assign(emitter, { handleStroke, handlePause, - handleStrokeStateChange, + handleStrokeStateChanged, reset: resetTraining }) } diff --git a/app/server.js b/app/server.js index cc5ef2a..a2a8832 100644 --- a/app/server.js +++ b/app/server.js @@ -111,6 +111,10 @@ rowingStatistics.on('rowingPaused', (data) => { peripheralManager.notifyMetrics('rowingPaused', metrics) }) +rowingStatistics.on('strokeStateChanged', (metrics) => { + peripheralManager.notifyMetrics('strokeStateChanged', metrics) +}) + rowingStatistics.on('durationUpdate', (data) => { webServer.notifyClients({ durationTotalFormatted: data.durationTotalFormatted diff --git a/doc/backlog.md b/doc/backlog.md index 1c99c2d..b5aec67 100644 --- a/doc/backlog.md +++ b/doc/backlog.md @@ -4,7 +4,6 @@ This is the very minimalistic Backlog for further development of this project. ## Soon -* refactor Stroke Phase Handling in RowingStatistics and pm5Peripheral * Web UI: hint, when screen is not in always on mode * Web UI: replace fullscreen button with exit Button when started from home screen * investigate: occasionally stroke rate is too high - seems to happen after rowing pause