diff --git a/app/ble/common/StaticReadCharacteristic.js b/app/ble/common/StaticReadCharacteristic.js deleted file mode 100644 index ef2248f..0000000 --- a/app/ble/common/StaticReadCharacteristic.js +++ /dev/null @@ -1,22 +0,0 @@ -'use strict' - -import bleno from '@abandonware/bleno' - -export default class StaticReadCharacteristic extends bleno.Characteristic { - constructor (uuid, description, value) { - super({ - uuid, - properties: ['read'], - value: Buffer.isBuffer(value) ? value : Buffer.from(value), - descriptors: [ - new bleno.Descriptor({ - uuid: '2901', - value: description - }) - ] - }) - this.uuid = uuid - this.description = description - this.value = Buffer.isBuffer(value) ? value : Buffer.from(value) - } -} diff --git a/app/ble/ftms/IndoorBikeFeatureCharacteristic.js b/app/ble/ftms/IndoorBikeFeatureCharacteristic.js deleted file mode 100644 index 4c01098..0000000 --- a/app/ble/ftms/IndoorBikeFeatureCharacteristic.js +++ /dev/null @@ -1,36 +0,0 @@ -'use strict' -/* - Open Rowing Monitor, https://github.com/laberning/openrowingmonitor - - This implements the Indoor Bike Feature Characteristic as defined by the specification. - Used to inform the Central about the features that the Open Rowing Monitor supports. - Make sure that The Fitness Machine Features and Target Setting Features that are announced here - 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) { - super({ - // Fitness Machine Feature - uuid: '2ACC', - properties: ['read'], - value: null - }) - } - - onReadRequest (offset, callback) { - // see https://www.bluetooth.com/specifications/specs/fitness-machine-service-1-0 for details - // Fitness Machine Features for the IndoorBikeDataCharacteristic - // Cadence Supported (1), Total Distance Supported (2), Expended Energy Supported (9), - // Heart Rate Measurement Supported (10), Elapsed Time Supported (12), Power Measurement Supported (14) - // 00000110 01010110 - // Target Setting Features for the IndoorBikeDataCharacteristic - // none - // 0000000 0000000 - const features = [0x06, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] - log.debug('Features of Indoor Bike requested') - callback(this.RESULT_SUCCESS, features.slice(offset, features.length)) - } -} diff --git a/app/ble/ftms/RowerFeatureCharacteristic.js b/app/ble/ftms/RowerFeatureCharacteristic.js deleted file mode 100644 index 04e929e..0000000 --- a/app/ble/ftms/RowerFeatureCharacteristic.js +++ /dev/null @@ -1,37 +0,0 @@ -'use strict' -/* - Open Rowing Monitor, https://github.com/laberning/openrowingmonitor - - This implements the Rower Feature Characteristic as defined by the specification. - Used to inform the Central about the features that the Open Rowing Monitor supports. - Make sure that The Fitness Machine Features and Target Setting Features that are announced here - are supported in RowerDataCharacteristic and FitnessMachineControlPointCharacteristic. -*/ -import bleno from '@abandonware/bleno' -import log from 'loglevel' - -export default class RowerFeatureCharacteristic extends bleno.Characteristic { - constructor () { - super({ - // Fitness Machine Feature - uuid: '2ACC', - properties: ['read'], - value: null - }) - } - - onReadRequest (offset, callback) { - // see https://www.bluetooth.com/specifications/specs/fitness-machine-service-1-0 for details - // Fitness Machine Features for the RowerDataCharacteristic - // Total Distance Supported (2), Pace Supported (5), Expended Energy Supported (9), - // Heart Rate Measurement Supported (10), Elapsed Time Supported (bit 12), - // Power Measurement Supported (14) - // 00100100 01010110 - // Target Setting Features for the RowerDataCharacteristic - // none - // 0000000 0000000 - const features = [0x24, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] - log.debug('Features of Rower requested') - callback(this.RESULT_SUCCESS, features.slice(offset, features.length)) - }; -} diff --git a/app/ble/pm5/DeviceInformationService.js b/app/ble/pm5/DeviceInformationService.js deleted file mode 100644 index 9741d54..0000000 --- a/app/ble/pm5/DeviceInformationService.js +++ /dev/null @@ -1,32 +0,0 @@ -'use strict' -/* - Open Rowing Monitor, https://github.com/laberning/openrowingmonitor - - Provides the required Device Information of the PM5 -*/ -import bleno from '@abandonware/bleno' -import { constants, getFullUUID } from './Pm5Constants.js' -import ValueReadCharacteristic from './characteristic/ValueReadCharacteristic.js' - -export default class DeviceInformationService extends bleno.PrimaryService { - constructor () { - super({ - // InformationenService uuid as defined by the PM5 specification - uuid: getFullUUID('0010'), - characteristics: [ - // C2 module number string - new ValueReadCharacteristic(getFullUUID('0011'), constants.model, 'model'), - // C2 serial number string - new ValueReadCharacteristic(getFullUUID('0012'), constants.serial, 'serial'), - // C2 hardware revision string - new ValueReadCharacteristic(getFullUUID('0013'), constants.hardwareRevision, 'hardwareRevision'), - // C2 firmware revision string - new ValueReadCharacteristic(getFullUUID('0014'), constants.firmwareRevision, 'firmwareRevision'), - // C2 manufacturer name string - new ValueReadCharacteristic(getFullUUID('0015'), constants.manufacturer, 'manufacturer'), - // Erg Machine Type - new ValueReadCharacteristic(getFullUUID('0016'), constants.ergMachineType, 'ergMachineType') - ] - }) - } -} diff --git a/app/peripherals/PeripheralConstants.js b/app/peripherals/PeripheralConstants.js new file mode 100644 index 0000000..3e1e136 --- /dev/null +++ b/app/peripherals/PeripheralConstants.js @@ -0,0 +1,16 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/laberning/openrowingmonitor + + Some PM5 specific constants +*/ +export const PeripheralConstants = { + serial: '123456789', + model: 'PM5', + name: 'PM5 123456789 Row', + hardwareRevision: '907', + // See https://www.concept2.com/service/monitors/pm5/firmware for available versions + // please note: hardware versions exclude a software version, and thus might confuse the client + firmwareRevision: '210', + manufacturer: 'Concept2' +} diff --git a/app/ble/PeripheralManager.js b/app/peripherals/PeripheralManager.js similarity index 91% rename from app/ble/PeripheralManager.js rename to app/peripherals/PeripheralManager.js index c75861f..02e54ee 100644 --- a/app/ble/PeripheralManager.js +++ b/app/peripherals/PeripheralManager.js @@ -6,12 +6,12 @@ switching between them */ import config from '../tools/ConfigManager.js' -import { createFtmsPeripheral } from './FtmsPeripheral.js' -import { createPm5Peripheral } from './Pm5Peripheral.js' +import { createFtmsPeripheral } from './ble/FtmsPeripheral.js' +import { createPm5Peripheral } from './ble/Pm5Peripheral.js' import log from 'loglevel' import EventEmitter from 'node:events' -import { createCpsPeripheral } from './CpsPeripheral.js' -import { createCscPeripheral } from './CscPeripheral.js' +import { createCpsPeripheral } from './ble/CpsPeripheral.js' +import { createCscPeripheral } from './ble/CscPeripheral.js' const modes = ['FTMS', 'FTMSBIKE', 'PM5', 'CSC', 'CPS'] function createPeripheralManager () { diff --git a/app/ant/AntManager.js b/app/peripherals/ant/AntManager.js similarity index 100% rename from app/ant/AntManager.js rename to app/peripherals/ant/AntManager.js diff --git a/app/ble/BufferBuilder.js b/app/peripherals/ble/BufferBuilder.js similarity index 100% rename from app/ble/BufferBuilder.js rename to app/peripherals/ble/BufferBuilder.js diff --git a/app/ble/BufferBuilder.test.js b/app/peripherals/ble/BufferBuilder.test.js similarity index 100% rename from app/ble/BufferBuilder.test.js rename to app/peripherals/ble/BufferBuilder.test.js diff --git a/app/ble/CentralManager.js b/app/peripherals/ble/CentralManager.js similarity index 100% rename from app/ble/CentralManager.js rename to app/peripherals/ble/CentralManager.js diff --git a/app/ble/CentralService.js b/app/peripherals/ble/CentralService.js similarity index 91% rename from app/ble/CentralService.js rename to app/peripherals/ble/CentralService.js index f8b28a5..0183d5d 100644 --- a/app/ble/CentralService.js +++ b/app/peripherals/ble/CentralService.js @@ -7,7 +7,7 @@ */ import { createCentralManager } from './CentralManager.js' import process from 'process' -import config from '../tools/ConfigManager.js' +import config from '../../tools/ConfigManager.js' import log from 'loglevel' log.setLevel(config.loglevel.default) diff --git a/app/ble/CpsPeripheral.js b/app/peripherals/ble/CpsPeripheral.js similarity index 98% rename from app/ble/CpsPeripheral.js rename to app/peripherals/ble/CpsPeripheral.js index 5d24e47..89cce87 100644 --- a/app/ble/CpsPeripheral.js +++ b/app/peripherals/ble/CpsPeripheral.js @@ -6,7 +6,7 @@ a Cycling Power Profile */ import bleno from '@abandonware/bleno' -import config from '../tools/ConfigManager.js' +import config from '../../tools/ConfigManager.js' import log from 'loglevel' import CyclingPowerService from './cps/CyclingPowerMeterService.js' import DeviceInformationService from './common/DeviceInformationService.js' diff --git a/app/ble/CscPeripheral.js b/app/peripherals/ble/CscPeripheral.js similarity index 98% rename from app/ble/CscPeripheral.js rename to app/peripherals/ble/CscPeripheral.js index 3c8e99c..6915d13 100644 --- a/app/ble/CscPeripheral.js +++ b/app/peripherals/ble/CscPeripheral.js @@ -6,7 +6,7 @@ a Cycling Speed and Cadence Profile */ import bleno from '@abandonware/bleno' -import config from '../tools/ConfigManager.js' +import config from '../../tools/ConfigManager.js' import log from 'loglevel' import DeviceInformationService from './common/DeviceInformationService.js' import CyclingSpeedCadenceService from './csc/CyclingSpeedCadenceService.js' diff --git a/app/ble/FtmsPeripheral.js b/app/peripherals/ble/FtmsPeripheral.js similarity index 98% rename from app/ble/FtmsPeripheral.js rename to app/peripherals/ble/FtmsPeripheral.js index 7a54392..c0845c6 100644 --- a/app/ble/FtmsPeripheral.js +++ b/app/peripherals/ble/FtmsPeripheral.js @@ -13,7 +13,7 @@ */ import bleno from '@abandonware/bleno' import FitnessMachineService from './ftms/FitnessMachineService.js' -import config from '../tools/ConfigManager.js' +import config from '../../tools/ConfigManager.js' import log from 'loglevel' import DeviceInformationService from './common/DeviceInformationService.js' import AdvertisingDataBuilder from './common/AdvertisingDataBuilder.js' diff --git a/app/ble/Pm5Peripheral.js b/app/peripherals/ble/Pm5Peripheral.js similarity index 96% rename from app/ble/Pm5Peripheral.js rename to app/peripherals/ble/Pm5Peripheral.js index 4e90519..badfe8e 100644 --- a/app/ble/Pm5Peripheral.js +++ b/app/peripherals/ble/Pm5Peripheral.js @@ -8,7 +8,7 @@ see: https://www.concept2.co.uk/files/pdf/us/monitors/PM5_BluetoothSmartInterfaceDefinition.pdf */ import bleno from '@abandonware/bleno' -import { constants } from './pm5/Pm5Constants.js' +import { pm5Constants } from './pm5/Pm5Constants.js' import DeviceInformationService from './pm5/DeviceInformationService.js' import GapService from './pm5/GapService.js' import log from 'loglevel' @@ -16,7 +16,7 @@ import Pm5ControlService from './pm5/Pm5ControlService.js' import Pm5RowingService from './pm5/Pm5RowingService.js' function createPm5Peripheral (controlCallback, options) { - const peripheralName = constants.name + const peripheralName = pm5Constants.name const deviceInformationService = new DeviceInformationService() const gapService = new GapService() const controlService = new Pm5ControlService() diff --git a/app/ble/common/AdvertisingDataBuilder.js b/app/peripherals/ble/common/AdvertisingDataBuilder.js similarity index 100% rename from app/ble/common/AdvertisingDataBuilder.js rename to app/peripherals/ble/common/AdvertisingDataBuilder.js diff --git a/app/ble/common/AdvertisingDataBuilder.test.js b/app/peripherals/ble/common/AdvertisingDataBuilder.test.js similarity index 100% rename from app/ble/common/AdvertisingDataBuilder.test.js rename to app/peripherals/ble/common/AdvertisingDataBuilder.test.js diff --git a/app/peripherals/ble/common/CommonOpCodes.js b/app/peripherals/ble/common/CommonOpCodes.js new file mode 100644 index 0000000..2c8d16e --- /dev/null +++ b/app/peripherals/ble/common/CommonOpCodes.js @@ -0,0 +1,10 @@ +'use-strict' + +export const ResultOpCode = { + reserved: 0x00, + success: 0x01, + opCodeNotSupported: 0x02, + invalidParameter: 0x03, + operationFailed: 0x04, + controlNotPermitted: 0x05 +} diff --git a/app/ble/common/DeviceInformationService.js b/app/peripherals/ble/common/DeviceInformationService.js similarity index 66% rename from app/ble/common/DeviceInformationService.js rename to app/peripherals/ble/common/DeviceInformationService.js index 100f5c4..c0b2daa 100644 --- a/app/ble/common/DeviceInformationService.js +++ b/app/peripherals/ble/common/DeviceInformationService.js @@ -5,6 +5,7 @@ todo: Could provide some info on the device here, maybe OS, Node version etc... */ import bleno from '@abandonware/bleno' +import { PeripheralConstants } from '../../PeripheralConstants.js' import StaticReadCharacteristic from './StaticReadCharacteristic.js' export default class DeviceInformationService extends bleno.PrimaryService { @@ -13,10 +14,10 @@ export default class DeviceInformationService extends bleno.PrimaryService { // uuid of 'Device Information Service' uuid: '180a', characteristics: [ - new StaticReadCharacteristic('2A24', 'Model Number', 'ORM2'), - new StaticReadCharacteristic('2A25', 'Serial Number', '1234'), - new StaticReadCharacteristic('2A28', 'Software Revision', '2'), - new StaticReadCharacteristic('2A29', 'Manufacturer Name', 'OpenRowingMonitor') + new StaticReadCharacteristic('2A24', 'Model Number', PeripheralConstants.model), + new StaticReadCharacteristic('2A25', 'Serial Number', PeripheralConstants.serial), + new StaticReadCharacteristic('2A28', 'Software Revision', PeripheralConstants.firmwareRevision), + new StaticReadCharacteristic('2A29', 'Manufacturer Name', PeripheralConstants.manufacturer) ] }) } diff --git a/app/ble/common/SensorLocation.js b/app/peripherals/ble/common/SensorLocation.js similarity index 100% rename from app/ble/common/SensorLocation.js rename to app/peripherals/ble/common/SensorLocation.js diff --git a/app/ble/pm5/characteristic/ValueReadCharacteristic.js b/app/peripherals/ble/common/StaticNotifyCharacteristic.js similarity index 57% rename from app/ble/pm5/characteristic/ValueReadCharacteristic.js rename to app/peripherals/ble/common/StaticNotifyCharacteristic.js index 7797cd1..37b6181 100644 --- a/app/ble/pm5/characteristic/ValueReadCharacteristic.js +++ b/app/peripherals/ble/common/StaticNotifyCharacteristic.js @@ -1,39 +1,40 @@ 'use strict' -/* - Open Rowing Monitor, https://github.com/laberning/openrowingmonitor - A simple Characteristic that gives read and notify access to a static value - Currently also used as placeholder on a lot of characteristics that are not yet implemented properly -*/ import bleno from '@abandonware/bleno' import log from 'loglevel' -export default class ValueReadCharacteristic extends bleno.Characteristic { - constructor (uuid, value, description) { +export default class StaticNotifyCharacteristic extends bleno.Characteristic { + constructor (uuid, description, value, addRead = false) { super({ uuid, - properties: ['read', 'notify'], - value: null + properties: addRead ? ['read', 'notify'] : ['notify'], + value: null, + descriptors: [ + new bleno.Descriptor({ + uuid: '2901', + value: description + }) + ] }) - this.uuid = uuid - this._value = Buffer.isBuffer(value) ? value : Buffer.from(value) + this._uuid = uuid this._description = description + this._value = Buffer.isBuffer(value) ? value : Buffer.from(value) this._updateValueCallback = null } onReadRequest (offset, callback) { - log.debug(`ValueReadRequest: ${this._description ? this._description : this.uuid}`) + log.debug(`ValueReadRequest: ${this._description ? this._description : this._uuid}`) callback(this.RESULT_SUCCESS, this._value.slice(offset, this._value.length)) } onSubscribe (maxValueSize, updateValueCallback) { - log.debug(`characteristic ${this._description ? this._description : this.uuid} - central subscribed with maxSize: ${maxValueSize}`) + log.debug(`characteristic ${this._description ? this._description : this._uuid} - central subscribed with maxSize: ${maxValueSize}`) this._updateValueCallback = updateValueCallback return this.RESULT_SUCCESS } onUnsubscribe () { - log.debug(`characteristic ${this._description ? this._description : this.uuid} - central unsubscribed`) + log.debug(`characteristic ${this._description ? this._description : this._uuid} - central unsubscribed`) this._updateValueCallback = null return this.RESULT_UNLIKELY_ERROR } diff --git a/app/peripherals/ble/common/StaticReadCharacteristic.js b/app/peripherals/ble/common/StaticReadCharacteristic.js new file mode 100644 index 0000000..71bfbb8 --- /dev/null +++ b/app/peripherals/ble/common/StaticReadCharacteristic.js @@ -0,0 +1,36 @@ +'use strict' + +import bleno from '@abandonware/bleno' +import log from 'loglevel' + +export default class StaticReadCharacteristic extends bleno.Characteristic { + constructor (uuid, description, value, addNotify = false) { + super({ + uuid, + properties: addNotify ? ['read', 'notify'] : ['read'], + value: Buffer.isBuffer(value) ? value : Buffer.from(value), + descriptors: [ + new bleno.Descriptor({ + uuid: '2901', + value: description + }) + ] + }) + this._uuid = uuid + this._description = description + this._value = Buffer.isBuffer(value) ? value : Buffer.from(value) + this._updateValueCallback = null + } + + onSubscribe (maxValueSize, updateValueCallback) { + log.debug(`characteristic ${this._description ? this._description : this._uuid} - central subscribed with maxSize: ${maxValueSize}`) + this._updateValueCallback = updateValueCallback + return this.RESULT_SUCCESS + } + + onUnsubscribe () { + log.debug(`characteristic ${this._description ? this._description : this._uuid} - central unsubscribed`) + this._updateValueCallback = null + return this.RESULT_UNLIKELY_ERROR + } +} diff --git a/app/ble/cps/CpsControlPointCharacteristic.js b/app/peripherals/ble/cps/CpsControlPointCharacteristic.js similarity index 100% rename from app/ble/cps/CpsControlPointCharacteristic.js rename to app/peripherals/ble/cps/CpsControlPointCharacteristic.js diff --git a/app/ble/cps/CpsMeasurementCharacteristic.js b/app/peripherals/ble/cps/CpsMeasurementCharacteristic.js similarity index 100% rename from app/ble/cps/CpsMeasurementCharacteristic.js rename to app/peripherals/ble/cps/CpsMeasurementCharacteristic.js diff --git a/app/ble/cps/CyclingPowerMeterService.js b/app/peripherals/ble/cps/CyclingPowerMeterService.js similarity index 100% rename from app/ble/cps/CyclingPowerMeterService.js rename to app/peripherals/ble/cps/CyclingPowerMeterService.js diff --git a/app/ble/csc/CscControlPointCharacteristic.js b/app/peripherals/ble/csc/CscControlPointCharacteristic.js similarity index 100% rename from app/ble/csc/CscControlPointCharacteristic.js rename to app/peripherals/ble/csc/CscControlPointCharacteristic.js diff --git a/app/ble/csc/CscMeasurementCharacteristic.js b/app/peripherals/ble/csc/CscMeasurementCharacteristic.js similarity index 100% rename from app/ble/csc/CscMeasurementCharacteristic.js rename to app/peripherals/ble/csc/CscMeasurementCharacteristic.js diff --git a/app/ble/csc/CyclingSpeedCadenceService.js b/app/peripherals/ble/csc/CyclingSpeedCadenceService.js similarity index 100% rename from app/ble/csc/CyclingSpeedCadenceService.js rename to app/peripherals/ble/csc/CyclingSpeedCadenceService.js diff --git a/app/ble/ftms/FitnessMachineControlPointCharacteristic.js b/app/peripherals/ble/ftms/FitnessMachineControlPointCharacteristic.js similarity index 84% rename from app/ble/ftms/FitnessMachineControlPointCharacteristic.js rename to app/peripherals/ble/ftms/FitnessMachineControlPointCharacteristic.js index 7d96096..9d52b3c 100644 --- a/app/ble/ftms/FitnessMachineControlPointCharacteristic.js +++ b/app/peripherals/ble/ftms/FitnessMachineControlPointCharacteristic.js @@ -10,6 +10,7 @@ */ import bleno from '@abandonware/bleno' import log from 'loglevel' +import { ResultOpCode } from '../common/CommonOpCodes.js' // see https://www.bluetooth.com/specifications/specs/fitness-machine-service-1-0 for details const ControlPointOpCode = { @@ -37,15 +38,6 @@ const ControlPointOpCode = { responseCode: 0x80 } -const ResultCode = { - reserved: 0x00, - success: 0x01, - opCodeNotSupported: 0x02, - invalidParameter: 0x03, - operationFailed: 0x04, - controlNotPermitted: 0x05 -} - export default class FitnessMachineControlPointCharacteristic extends bleno.Characteristic { constructor (controlPointCallback) { super({ @@ -70,12 +62,12 @@ export default class FitnessMachineControlPointCharacteristic extends bleno.Char if (this.controlPointCallback({ name: 'requestControl' })) { log.debug('requestControl sucessful') this.controlled = true - callback(this.buildResponse(opCode, ResultCode.success)) + callback(this.buildResponse(opCode, ResultOpCode.success)) } else { - callback(this.buildResponse(opCode, ResultCode.operationFailed)) + callback(this.buildResponse(opCode, ResultOpCode.operationFailed)) } } else { - callback(this.buildResponse(opCode, ResultCode.controlNotPermitted)) + callback(this.buildResponse(opCode, ResultOpCode.controlNotPermitted)) } break @@ -109,30 +101,30 @@ export default class FitnessMachineControlPointCharacteristic extends bleno.Char const crr = data.readUInt8(5) * 0.0001 const cw = data.readUInt8(6) * 0.01 if (this.controlPointCallback({ name: 'setIndoorBikeSimulationParameters', value: { windspeed, grade, crr, cw } })) { - callback(this.buildResponse(opCode, ResultCode.success)) + callback(this.buildResponse(opCode, ResultOpCode.success)) } else { - callback(this.buildResponse(opCode, ResultCode.operationFailed)) + callback(this.buildResponse(opCode, ResultOpCode.operationFailed)) } break } default: log.info(`opCode ${opCode} is not supported`) - callback(this.buildResponse(opCode, ResultCode.opCodeNotSupported)) + callback(this.buildResponse(opCode, ResultOpCode.opCodeNotSupported)) } } handleSimpleCommand (opCode, opName, callback) { if (this.controlled) { if (this.controlPointCallback({ name: opName })) { - const response = this.buildResponse(opCode, ResultCode.success) + const response = this.buildResponse(opCode, ResultOpCode.success) callback(response) } else { - callback(this.buildResponse(opCode, ResultCode.operationFailed)) + callback(this.buildResponse(opCode, ResultOpCode.operationFailed)) } } else { log.info(`initating command '${opName}' requires 'requestControl'`) - callback(this.buildResponse(opCode, ResultCode.controlNotPermitted)) + callback(this.buildResponse(opCode, ResultOpCode.controlNotPermitted)) } } diff --git a/app/ble/ftms/FitnessMachineService.js b/app/peripherals/ble/ftms/FitnessMachineService.js similarity index 59% rename from app/ble/ftms/FitnessMachineService.js rename to app/peripherals/ble/ftms/FitnessMachineService.js index d470374..6a411fe 100644 --- a/app/ble/ftms/FitnessMachineService.js +++ b/app/peripherals/ble/ftms/FitnessMachineService.js @@ -18,23 +18,25 @@ import bleno from '@abandonware/bleno' import RowerDataCharacteristic from './RowerDataCharacteristic.js' -import RowerFeatureCharacteristic from './RowerFeatureCharacteristic.js' import IndoorBikeDataCharacteristic from './IndoorBikeDataCharacteristic.js' -import IndoorBikeFeatureCharacteristic from './IndoorBikeFeatureCharacteristic.js' import FitnessMachineControlPointCharacteristic from './FitnessMachineControlPointCharacteristic.js' import FitnessMachineStatusCharacteristic from './FitnessMachineStatusCharacteristic.js' +import StaticReadCharacteristic from '../common/StaticReadCharacteristic.js' +import BufferBuilder from '../BufferBuilder.js' export default class FitnessMachineService extends bleno.PrimaryService { constructor (options, controlPointCallback) { const simulateIndoorBike = options?.simulateIndoorBike === true const dataCharacteristic = simulateIndoorBike ? new IndoorBikeDataCharacteristic() : new RowerDataCharacteristic() - const featureCharacteristic = simulateIndoorBike ? new IndoorBikeFeatureCharacteristic() : new RowerFeatureCharacteristic() const statusCharacteristic = new FitnessMachineStatusCharacteristic() + const ftmsFeaturesBuffer = new BufferBuilder() + ftmsFeaturesBuffer.writeUInt16LE(featuresFlag) + super({ // Fitness Machine uuid: '1826', characteristics: [ - featureCharacteristic, + new StaticReadCharacteristic('2ACC', 'FTMS Feature', ftmsFeaturesBuffer.getBuffer()), dataCharacteristic, new FitnessMachineControlPointCharacteristic(controlPointCallback), statusCharacteristic @@ -52,3 +54,25 @@ export default class FitnessMachineService extends bleno.PrimaryService { this.statusCharacteristic.notify(event) } } + +export const FtmsBikeFeaturesFlags = { + averageSpeedSupported: (0x01 << 0), + cadenceSupported: (0x01 << 1), + totalDistanceSupported: (0x01 << 2), + inclinationSupported: (0x01 << 3), + elevationGainSupported: (0x01 << 4), + paceSupported: (0x01 << 5), + stepCountSupported: (0x01 << 6), + resistanceLevelSupported: (0x01 << 7), + strideCountSupported: (0x01 << 8), + expendedEnergySupported: (0x01 << 9), + heartRateMeasurementSupported: (0x01 << 10), + metabolicEquivalentSupported: (0x01 << 11), + elapsedTimeSupported: (0x01 << 12), + remainingTimeSupported: (0x01 << 13), + powerMeasurementSupported: (0x01 << 14), + forceOnBeltAndPowerOutputSupported: (0x01 << 15), + userDataRetentionSupported: (0x01 << 16) +} + +export const featuresFlag = FtmsBikeFeaturesFlags.cadenceSupported | FtmsBikeFeaturesFlags.totalDistanceSupported | FtmsBikeFeaturesFlags.paceSupported | FtmsBikeFeaturesFlags.expendedEnergySupported | FtmsBikeFeaturesFlags.heartRateMeasurementSupported | FtmsBikeFeaturesFlags.elapsedTimeSupported | FtmsBikeFeaturesFlags.powerMeasurementSupported diff --git a/app/ble/ftms/FitnessMachineStatusCharacteristic.js b/app/peripherals/ble/ftms/FitnessMachineStatusCharacteristic.js similarity index 100% rename from app/ble/ftms/FitnessMachineStatusCharacteristic.js rename to app/peripherals/ble/ftms/FitnessMachineStatusCharacteristic.js diff --git a/app/ble/ftms/IndoorBikeDataCharacteristic.js b/app/peripherals/ble/ftms/IndoorBikeDataCharacteristic.js similarity index 82% rename from app/ble/ftms/IndoorBikeDataCharacteristic.js rename to app/peripherals/ble/ftms/IndoorBikeDataCharacteristic.js index 9a1b71c..f33e05e 100644 --- a/app/ble/ftms/IndoorBikeDataCharacteristic.js +++ b/app/peripherals/ble/ftms/IndoorBikeDataCharacteristic.js @@ -60,10 +60,9 @@ export default class IndoorBikeDataCharacteristic extends bleno.Characteristic { // Field flags as defined in the Bluetooth Documentation // Instantaneous speed (default), Instantaneous Cadence (2), Total Distance (4), // Instantaneous Power (6), Total / Expended Energy (8), Heart Rate (9), Elapsed Time (11) - // 01010100 - bufferBuilder.writeUInt8(0x54) // 00001011 - bufferBuilder.writeUInt8(0x0B) + // 01010100 + bufferBuilder.writeUInt16LE(measurementFlag) // see https://www.bluetooth.com/specifications/specs/gatt-specification-supplement-3/ // for some of the data types @@ -98,3 +97,21 @@ export default class IndoorBikeDataCharacteristic extends bleno.Characteristic { return this.RESULT_SUCCESS } } + +export const RowingMeasurementFlags = { + moreDataPresent: (0x01 << 0), + averageSpeedPresent: (0x01 << 1), + instantaneousCadencePresent: (0x01 << 2), + averageCadencePresent: (0x01 << 3), + totalDistancePresent: (0x01 << 4), + resistanceLevelPresent: (0x01 << 5), + instantaneousPowerPresent: (0x01 << 6), + averagePowerPresent: (0x01 << 7), + expendedEnergyPresent: (0x01 << 8), + heartRatePresent: (0x01 << 9), + metabolicEquivalentPresent: (0x01 << 10), + elapsedTimePresent: (0x01 << 11), + remainingTimePresent: (0x01 << 12) +} + +export const measurementFlag = RowingMeasurementFlags.instantaneousCadencePresent | RowingMeasurementFlags.totalDistancePresent | RowingMeasurementFlags.instantaneousPowerPresent | RowingMeasurementFlags.expendedEnergyPresent | RowingMeasurementFlags.heartRatePresent | RowingMeasurementFlags.elapsedTimePresent diff --git a/app/ble/ftms/RowerDataCharacteristic.js b/app/peripherals/ble/ftms/RowerDataCharacteristic.js similarity index 82% rename from app/ble/ftms/RowerDataCharacteristic.js rename to app/peripherals/ble/ftms/RowerDataCharacteristic.js index a3a3762..81df3f6 100644 --- a/app/ble/ftms/RowerDataCharacteristic.js +++ b/app/peripherals/ble/ftms/RowerDataCharacteristic.js @@ -55,9 +55,8 @@ export default class RowerDataCharacteristic extends bleno.Characteristic { // todo: might add: Average Stroke Rate (1), Average Pace (4), Average Power (6) // Remaining Time (12) // 00101100 - bufferBuilder.writeUInt8(0x2c) + bufferBuilder.writeUInt16LE(measurementFlag) // 00001011 - bufferBuilder.writeUInt8(0x0B) // see https://www.bluetooth.com/specifications/specs/gatt-specification-supplement-3/ // for some of the data types @@ -98,3 +97,21 @@ export default class RowerDataCharacteristic extends bleno.Characteristic { return this.RESULT_SUCCESS } } + +export const RowingMeasurementFlags = { + moreDataPresent: (0x01 << 0), + averageStrokeRatePresent: (0x01 << 1), + totalDistancePresent: (0x01 << 2), + instantaneousPacePresent: (0x01 << 3), + averagePacePresent: (0x01 << 4), + instantaneousPowerPresent: (0x01 << 5), + averagePowerPresent: (0x01 << 6), + resistanceLevelPresent: (0x01 << 7), + expendedEnergyPresent: (0x01 << 8), + heartRatePresent: (0x01 << 9), + metabolicEquivalentPresent: (0x01 << 10), + elapsedTimePresent: (0x01 << 11), + remainingTimePresent: (0x01 << 12) +} + +export const measurementFlag = RowingMeasurementFlags.totalDistancePresent | RowingMeasurementFlags.instantaneousPacePresent | RowingMeasurementFlags.instantaneousPowerPresent | RowingMeasurementFlags.expendedEnergyPresent | RowingMeasurementFlags.heartRatePresent | RowingMeasurementFlags.elapsedTimePresent diff --git a/app/peripherals/ble/pm5/DeviceInformationService.js b/app/peripherals/ble/pm5/DeviceInformationService.js new file mode 100644 index 0000000..42a25ef --- /dev/null +++ b/app/peripherals/ble/pm5/DeviceInformationService.js @@ -0,0 +1,32 @@ +'use strict' +/* + Open Rowing Monitor, https://github.com/laberning/openrowingmonitor + + Provides the required Device Information of the PM5 +*/ +import bleno from '@abandonware/bleno' +import StaticNotifyCharacteristic from '../common/StaticNotifyCharacteristic.js' +import { getFullUUID, pm5Constants } from './Pm5Constants.js' + +export default class DeviceInformationService extends bleno.PrimaryService { + constructor () { + super({ + // InformationenService uuid as defined by the PM5 specification + uuid: getFullUUID('0010'), + characteristics: [ + // C2 module number string + new StaticNotifyCharacteristic(getFullUUID('0011'), 'model', 'PM5', true), + // C2 serial number string + new StaticNotifyCharacteristic(getFullUUID('0012'), 'serial', pm5Constants.serial, true), + // C2 hardware revision string + new StaticNotifyCharacteristic(getFullUUID('0013'), 'hardwareRevision', pm5Constants.hardwareRevision, true), + // C2 firmware revision string + new StaticNotifyCharacteristic(getFullUUID('0014'), 'firmwareRevision', pm5Constants.firmwareRevision, true), + // C2 manufacturer name string + new StaticNotifyCharacteristic(getFullUUID('0015'), 'manufacturer', pm5Constants.manufacturer, true), + // Erg Machine Type + new StaticNotifyCharacteristic(getFullUUID('0016'), 'ergMachineType', pm5Constants.ergMachineType, true) + ] + }) + } +} diff --git a/app/ble/pm5/GapService.js b/app/peripherals/ble/pm5/GapService.js similarity index 52% rename from app/ble/pm5/GapService.js rename to app/peripherals/ble/pm5/GapService.js index f90c42c..168cdd5 100644 --- a/app/ble/pm5/GapService.js +++ b/app/peripherals/ble/pm5/GapService.js @@ -6,8 +6,8 @@ todo: not sure if this is correct, the normal GAP service has 0x1800 */ import bleno from '@abandonware/bleno' -import { constants, getFullUUID } from './Pm5Constants.js' -import ValueReadCharacteristic from './characteristic/ValueReadCharacteristic.js' +import StaticNotifyCharacteristic from '../common/StaticNotifyCharacteristic.js' +import { getFullUUID, pm5Constants } from './Pm5Constants.js' export default class GapService extends bleno.PrimaryService { constructor () { @@ -16,15 +16,15 @@ export default class GapService extends bleno.PrimaryService { uuid: getFullUUID('0000'), characteristics: [ // GAP device name - new ValueReadCharacteristic('2A00', constants.name), + new StaticNotifyCharacteristic('2A00', undefined, pm5Constants.name, true), // GAP appearance - new ValueReadCharacteristic('2A01', [0x00, 0x00]), + new StaticNotifyCharacteristic('2A01', undefined, [0x00, 0x00], true), // GAP peripheral privacy - new ValueReadCharacteristic('2A02', [0x00]), + new StaticNotifyCharacteristic('2A02', undefined, [0x00], true), // GAP reconnect address - new ValueReadCharacteristic('2A03', '00:00:00:00:00:00'), + new StaticNotifyCharacteristic('2A03', undefined, '00:00:00:00:00:00', true), // Peripheral preferred connection parameters - new ValueReadCharacteristic('2A04', [0x18, 0x00, 0x18, 0x00, 0x00, 0x00, 0xE8, 0x03]) + new StaticNotifyCharacteristic('2A04', undefined, [0x18, 0x00, 0x18, 0x00, 0x00, 0x00, 0xE8, 0x03], true) ] }) } diff --git a/app/ble/pm5/Pm5Constants.js b/app/peripherals/ble/pm5/Pm5Constants.js similarity index 73% rename from app/ble/pm5/Pm5Constants.js rename to app/peripherals/ble/pm5/Pm5Constants.js index e4c352d..c717621 100644 --- a/app/ble/pm5/Pm5Constants.js +++ b/app/peripherals/ble/pm5/Pm5Constants.js @@ -1,18 +1,16 @@ 'use strict' + +import { PeripheralConstants } from '../../PeripheralConstants.js' + /* Open Rowing Monitor, https://github.com/laberning/openrowingmonitor Some PM5 specific constants */ -const constants = { - serial: '123456789', - model: 'PM5', - name: 'PM5 123456789 Row', - hardwareRevision: '907', +const pm5Constants = { + ...PeripheralConstants, // See https://www.concept2.com/service/monitors/pm5/firmware for available versions // please note: hardware versions exclude a software version, and thus might confuse the client - firmwareRevision: '210', - manufacturer: 'Concept2', ergMachineType: [0x05] } @@ -23,5 +21,5 @@ function getFullUUID (uuid) { export { getFullUUID, - constants + pm5Constants } diff --git a/app/ble/pm5/Pm5ControlService.js b/app/peripherals/ble/pm5/Pm5ControlService.js similarity index 100% rename from app/ble/pm5/Pm5ControlService.js rename to app/peripherals/ble/pm5/Pm5ControlService.js diff --git a/app/ble/pm5/Pm5RowingService.js b/app/peripherals/ble/pm5/Pm5RowingService.js similarity index 80% rename from app/ble/pm5/Pm5RowingService.js rename to app/peripherals/ble/pm5/Pm5RowingService.js index 8e00cf5..e0fc384 100644 --- a/app/ble/pm5/Pm5RowingService.js +++ b/app/peripherals/ble/pm5/Pm5RowingService.js @@ -20,13 +20,13 @@ */ import bleno from '@abandonware/bleno' import { getFullUUID } from './Pm5Constants.js' -import ValueReadCharacteristic from './characteristic/ValueReadCharacteristic.js' import MultiplexedCharacteristic from './characteristic/MultiplexedCharacteristic.js' import GeneralStatus from './characteristic/GeneralStatus.js' import AdditionalStatus from './characteristic/AdditionalStatus.js' import AdditionalStatus2 from './characteristic/AdditionalStatus2.js' import AdditionalStrokeData from './characteristic/AdditionalStrokeData.js' import StrokeData from './characteristic/StrokeData.js' +import StaticNotifyCharacteristic from '../common/StaticNotifyCharacteristic.js' export default class PM5RowingService extends bleno.PrimaryService { constructor () { @@ -46,23 +46,23 @@ export default class PM5RowingService extends bleno.PrimaryService { // C2 rowing additional status 2 additionalStatus2, // C2 rowing general status and additional status samplerate - new ValueReadCharacteristic(getFullUUID('0034'), 'samplerate', 'samplerate'), + new StaticNotifyCharacteristic(getFullUUID('0034'), 'samplerate', 'samplerate', true), // C2 rowing stroke data strokeData, // C2 rowing additional stroke data additionalStrokeData, // C2 rowing split/interval data - new ValueReadCharacteristic(getFullUUID('0037'), 'split data', 'split data'), + new StaticNotifyCharacteristic(getFullUUID('0037'), 'split data', 'split data', true), // C2 rowing additional split/interval data - new ValueReadCharacteristic(getFullUUID('0038'), 'additional split data', 'additional split data'), + new StaticNotifyCharacteristic(getFullUUID('0038'), 'additional split data', 'additional split data', true), // C2 rowing end of workout summary data - new ValueReadCharacteristic(getFullUUID('0039'), 'workout summary', 'workout summary'), + new StaticNotifyCharacteristic(getFullUUID('0039'), 'workout summary', 'workout summary', true), // C2 rowing end of workout additional summary data - new ValueReadCharacteristic(getFullUUID('003A'), 'additional workout summary', 'additional workout summary'), + new StaticNotifyCharacteristic(getFullUUID('003A'), 'additional workout summary', 'additional workout summary', true), // C2 rowing heart rate belt information - new ValueReadCharacteristic(getFullUUID('003B'), 'heart rate belt information', 'heart rate belt information'), + new StaticNotifyCharacteristic(getFullUUID('003B'), 'heart rate belt information', 'heart rate belt information', true), // C2 force curve data - new ValueReadCharacteristic(getFullUUID('003D'), 'force curve data', 'force curve data'), + new StaticNotifyCharacteristic(getFullUUID('003D'), 'force curve data', 'force curve data', true), // C2 multiplexed information multiplexedCharacteristic ] diff --git a/app/ble/pm5/characteristic/AdditionalStatus.js b/app/peripherals/ble/pm5/characteristic/AdditionalStatus.js similarity index 100% rename from app/ble/pm5/characteristic/AdditionalStatus.js rename to app/peripherals/ble/pm5/characteristic/AdditionalStatus.js diff --git a/app/ble/pm5/characteristic/AdditionalStatus2.js b/app/peripherals/ble/pm5/characteristic/AdditionalStatus2.js similarity index 100% rename from app/ble/pm5/characteristic/AdditionalStatus2.js rename to app/peripherals/ble/pm5/characteristic/AdditionalStatus2.js diff --git a/app/ble/pm5/characteristic/AdditionalStrokeData.js b/app/peripherals/ble/pm5/characteristic/AdditionalStrokeData.js similarity index 100% rename from app/ble/pm5/characteristic/AdditionalStrokeData.js rename to app/peripherals/ble/pm5/characteristic/AdditionalStrokeData.js diff --git a/app/ble/pm5/characteristic/ControlReceive.js b/app/peripherals/ble/pm5/characteristic/ControlReceive.js similarity index 100% rename from app/ble/pm5/characteristic/ControlReceive.js rename to app/peripherals/ble/pm5/characteristic/ControlReceive.js diff --git a/app/ble/pm5/characteristic/ControlTransmit.js b/app/peripherals/ble/pm5/characteristic/ControlTransmit.js similarity index 100% rename from app/ble/pm5/characteristic/ControlTransmit.js rename to app/peripherals/ble/pm5/characteristic/ControlTransmit.js diff --git a/app/ble/pm5/characteristic/GeneralStatus.js b/app/peripherals/ble/pm5/characteristic/GeneralStatus.js similarity index 100% rename from app/ble/pm5/characteristic/GeneralStatus.js rename to app/peripherals/ble/pm5/characteristic/GeneralStatus.js diff --git a/app/ble/pm5/characteristic/MultiplexedCharacteristic.js b/app/peripherals/ble/pm5/characteristic/MultiplexedCharacteristic.js similarity index 100% rename from app/ble/pm5/characteristic/MultiplexedCharacteristic.js rename to app/peripherals/ble/pm5/characteristic/MultiplexedCharacteristic.js diff --git a/app/ble/pm5/characteristic/StrokeData.js b/app/peripherals/ble/pm5/characteristic/StrokeData.js similarity index 100% rename from app/ble/pm5/characteristic/StrokeData.js rename to app/peripherals/ble/pm5/characteristic/StrokeData.js diff --git a/app/server.js b/app/server.js index ecdd20a..965f62e 100644 --- a/app/server.js +++ b/app/server.js @@ -13,12 +13,12 @@ import log from 'loglevel' import config from './tools/ConfigManager.js' import { createRowingStatistics } from './engine/RowingStatistics.js' import { createWebServer } from './WebServer.js' -import { createPeripheralManager } from './ble/PeripheralManager.js' -import { createAntManager } from './ant/AntManager.js' +import { createPeripheralManager } from './peripherals/PeripheralManager.js' // eslint-disable-next-line no-unused-vars import { replayRowingSession } from './tools/RowingRecorder.js' import { createWorkoutRecorder } from './engine/WorkoutRecorder.js' import { createWorkoutUploader } from './engine/WorkoutUploader.js' +import { createAntManager } from './peripherals/ant/AntManager.js' const exec = promisify(child_process.exec) // set the log levels @@ -193,7 +193,7 @@ rowingStatistics.on('rowingStopped', (metrics) => { }) if (config.heartrateMonitorBLE) { - const bleCentralService = child_process.fork('./app/ble/CentralService.js') + const bleCentralService = child_process.fork('./app/peripherals/ble/CentralService.js') bleCentralService.on('message', (heartrateMeasurement) => { rowingStatistics.handleHeartrateMeasurement(heartrateMeasurement) })