Refactor and restructure peripheral related code
- Create a new main folder to include ANT and BLE - Move peripheralManager with other files with shared information - Refactor static notify characteristics - Refactor code to be more uniform across the different peripherals
This commit is contained in:
parent
758f2d25ea
commit
3edfe22434
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
@ -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))
|
||||
};
|
||||
}
|
||||
|
|
@ -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')
|
||||
]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -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'
|
||||
}
|
||||
|
|
@ -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 () {
|
||||
|
|
@ -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)
|
||||
|
|
@ -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'
|
||||
|
|
@ -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'
|
||||
|
|
@ -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'
|
||||
|
|
@ -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()
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
'use-strict'
|
||||
|
||||
export const ResultOpCode = {
|
||||
reserved: 0x00,
|
||||
success: 0x01,
|
||||
opCodeNotSupported: 0x02,
|
||||
invalidParameter: 0x03,
|
||||
operationFailed: 0x04,
|
||||
controlNotPermitted: 0x05
|
||||
}
|
||||
|
|
@ -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)
|
||||
]
|
||||
})
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
]
|
||||
})
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
]
|
||||
|
|
@ -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)
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in New Issue