openrowingmonitor/app/peripherals/ble/cps/CpsMeasurementCharacteristi...

96 lines
3.3 KiB
JavaScript

'use strict'
/*
Open Rowing Monitor, https://github.com/laberning/openrowingmonitor
*/
import bleno from '@abandonware/bleno'
import log from 'loglevel'
import BufferBuilder from '../BufferBuilder.js'
export const cpsMeasurementFeaturesFlags = {
pedalPowerBalancePresent: (0x01 << 0),
pedalPowerBalanceReference: (0x01 << 1),
accumulatedTorquePresent: (0x01 << 2),
accumulatedTorqueSource: (0x01 << 3),
accumulatedTorqueSourceWheel: (0x00 << 3),
accumulatedTorqueSourceCrank: (0x01 << 3),
wheelRevolutionDataPresent: (0x01 << 4),
crankRevolutionDataPresent: (0x01 << 5),
extremeForceMagnitudesPresent: (0x01 << 6),
extremeTorqueMagnitudesPresent: (0x01 << 7),
extremeAnglesPresent: (0x01 << 8),
topDeadSpotAnglePresent: (0x01 << 9),
bottomDeadSpotAnglePresent: (0x01 << 10),
accumulatedEnergyPresent: (0x01 << 11),
offsetCompensationIndicator: (0x01 << 12)
}
export default class CyclingPowerMeasurementCharacteristic extends bleno.Characteristic {
constructor () {
super({
// Cycling Power Meter Measurement
uuid: '2A63',
value: null,
properties: ['notify'],
descriptors: [
new bleno.Descriptor({
uuid: '2901',
value: 'Cycling Power Measurement'
})
]
})
this._updateValueCallback = null
this._subscriberMaxValueSize = null
}
onSubscribe (maxValueSize, updateValueCallback) {
log.debug(`CyclingPowerMeasurementCharacteristic - central subscribed with maxSize: ${maxValueSize}`)
this._updateValueCallback = updateValueCallback
this._subscriberMaxValueSize = maxValueSize
return this.RESULT_SUCCESS
}
onUnsubscribe () {
log.debug('CyclingPowerMeasurementCharacteristic - central unsubscribed')
this._updateValueCallback = null
this._subscriberMaxValueSize = null
return this.RESULT_UNLIKELY_ERROR
}
notify (data) {
// ignore events without the mandatory fields
if (!('cyclePower' in data)) {
log.error('can not deliver bike data without mandatory fields')
return this.RESULT_SUCCESS
}
if (this._updateValueCallback) {
const bufferBuilder = new BufferBuilder()
// Features flag
bufferBuilder.writeUInt16LE(cpsMeasurementFeaturesFlags.wheelRevolutionDataPresent | cpsMeasurementFeaturesFlags.crankRevolutionDataPresent)
// Instantaneous Power
bufferBuilder.writeUInt16LE(Math.round(data.cyclePower))
// Wheel revolution count (basically the distance in cm)
bufferBuilder.writeUInt32LE(Math.round(Math.round(data.totalLinearDistance * 100)))
// Wheel revolution time (ushort with 2048 resolution, resetting in every 32sec)
bufferBuilder.writeUInt16LE(Math.round(data.totalMovingTime * 2048) % Math.pow(2, 16))
// Total stroke count
bufferBuilder.writeUInt16LE(Math.round(data.totalNumberOfStrokes))
// last stroke time time (ushort with 1024 resolution, resetting in every 64sec)
bufferBuilder.writeUInt16LE(Math.round(data.driveLastStartTime * 1024) % Math.pow(2, 16))
const buffer = bufferBuilder.getBuffer()
if (buffer.length > this._subscriberMaxValueSize) {
log.warn(`CyclingPowerMeasurementCharacteristic - notification of ${buffer.length} bytes is too large for the subscriber`)
}
this._updateValueCallback(bufferBuilder.getBuffer())
}
return this.RESULT_SUCCESS
}
}