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:
Abász 2022-12-12 14:45:07 +01:00
parent 758f2d25ea
commit 3edfe22434
47 changed files with 225 additions and 208 deletions

View File

@ -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)
}
}

View File

@ -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))
}
}

View File

@ -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))
};
}

View File

@ -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')
]
})
}
}

View File

@ -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'
}

View File

@ -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 () {

View File

@ -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)

View File

@ -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'

View File

@ -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'

View File

@ -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'

View File

@ -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()

View File

@ -0,0 +1,10 @@
'use-strict'
export const ResultOpCode = {
reserved: 0x00,
success: 0x01,
opCodeNotSupported: 0x02,
invalidParameter: 0x03,
operationFailed: 0x04,
controlNotPermitted: 0x05
}

View File

@ -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)
]
})
}

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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))
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)
]
})
}
}

View File

@ -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)
]
})
}

View File

@ -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
}

View File

@ -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
]

View File

@ -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)
})