diff --git a/app/ble/ftms/DeviceInformationService.js b/app/ble/ftms/DeviceInformationService.js index a98d8c6..28f2f64 100644 --- a/app/ble/ftms/DeviceInformationService.js +++ b/app/ble/ftms/DeviceInformationService.js @@ -12,8 +12,6 @@ export default class DeviceInformationService extends bleno.PrimaryService { // uuid of "Device Information Service" uuid: '180a', characteristics: [ - /* new SerialNumberCharacteristic(device), - new HardwareRevisionCharacteristic(device) */ ] }) } diff --git a/app/ble/ftms/IndoorBikeDataCharacteristic.js b/app/ble/ftms/IndoorBikeDataCharacteristic.js index 7aaf320..1f0cb48 100644 --- a/app/ble/ftms/IndoorBikeDataCharacteristic.js +++ b/app/ble/ftms/IndoorBikeDataCharacteristic.js @@ -31,17 +31,20 @@ export default class IndoorBikeDataCharacteristic extends bleno.Characteristic { properties: ['notify'] }) this._updateValueCallback = null + this._subscriberMaxValueSize = null } onSubscribe (maxValueSize, updateValueCallback) { - log.debug(`IndooBikeDataCharacteristic - central subscribed with maxSize: ${maxValueSize}`) + log.debug(`IndoorBikeDataCharacteristic - central subscribed with maxSize: ${maxValueSize}`) this._updateValueCallback = updateValueCallback + this._subscriberMaxValueSize = maxValueSize return this.RESULT_SUCCESS } onUnsubscribe () { - log.debug('IndooBikeDataCharacteristic - central unsubscribed') + log.debug('IndoorBikeDataCharacteristic - central unsubscribed') this._updateValueCallback = null + this._subscriberMaxValueSize = null return this.RESULT_UNLIKELY_ERROR } @@ -56,11 +59,11 @@ export default class IndoorBikeDataCharacteristic extends bleno.Characteristic { const bufferBuilder = new BufferBuilder() // Field flags as defined in the Bluetooth Documentation // Instantaneous speed (default), Total Distance (4), Instantaneous Power (6) - // Total / Expended Energy (8) + // Total / Expended Energy (8), Heart Rate (9), Elapsed Time (11) // 01010000 bufferBuilder.writeUInt8(0x50) - // 00000001 - bufferBuilder.writeUInt8(0x01) + // 00001011 + bufferBuilder.writeUInt8(0x0B) // see https://www.bluetooth.com/specifications/specs/gatt-specification-supplement-3/ // for some of the data types @@ -78,8 +81,16 @@ export default class IndoorBikeDataCharacteristic extends bleno.Characteristic { // period of one hour. bufferBuilder.writeUInt16LE(data.caloriesPerHour) // Energy per minute - bufferBuilder.writeUInt16LE(data.caloriesPerMinute) + bufferBuilder.writeUInt8(data.caloriesPerMinute) + // Heart Rate: Beats per minute with a resolution of 1 + bufferBuilder.writeUInt8(data.heartRate) + // Elapsed Time: Seconds with a resolution of 1 + bufferBuilder.writeUInt16LE(data.durationTotal) + const buffer = bufferBuilder.getBuffer() + if (buffer.length > this._subscriberMaxValueSize) { + log.warn(`IndoorBikeDataCharacteristic - notification of ${buffer.length} bytes is too large for the subscriber`) + } this._updateValueCallback(bufferBuilder.getBuffer()) } return this.RESULT_SUCCESS diff --git a/app/ble/ftms/IndoorBikeFeatureCharacteristic.js b/app/ble/ftms/IndoorBikeFeatureCharacteristic.js index f1b1a13..4a71512 100644 --- a/app/ble/ftms/IndoorBikeFeatureCharacteristic.js +++ b/app/ble/ftms/IndoorBikeFeatureCharacteristic.js @@ -24,12 +24,12 @@ export default class IndoorBikeDataCharacteristic extends bleno.Characteristic { // see https://www.bluetooth.com/specifications/specs/fitness-machine-service-1-0 for details // Fitness Machine Features for the IndoorBikeDataCharacteristic // Total Distance Supported (2), Expended Energy Supported (9), - // Power Measurement Supported (14) - // 00000100 01000010 + // Heart Rate Measurement Supported (10), Elapsed Time Supported (12), Power Measurement Supported (14) + // 00000100 01010110 // Target Setting Features for the IndoorBikeDataCharacteristic // none // 0000000 0000000 - const features = [0x04, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] + const features = [0x04, 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/RowerDataCharacteristic.js b/app/ble/ftms/RowerDataCharacteristic.js index a537ba2..08862ae 100644 --- a/app/ble/ftms/RowerDataCharacteristic.js +++ b/app/ble/ftms/RowerDataCharacteristic.js @@ -24,17 +24,20 @@ export default class RowerDataCharacteristic extends bleno.Characteristic { properties: ['notify'] }) this._updateValueCallback = null + this._subscriberMaxValueSize = null } onSubscribe (maxValueSize, updateValueCallback) { log.debug(`RowerDataCharacteristic - central subscribed with maxSize: ${maxValueSize}`) this._updateValueCallback = updateValueCallback + this._subscriberMaxValueSize = maxValueSize return this.RESULT_SUCCESS } onUnsubscribe () { log.debug('RowerDataCharacteristic - central unsubscribed') this._updateValueCallback = null + this._subscriberMaxValueSize = null return this.RESULT_UNLIKELY_ERROR } @@ -48,13 +51,13 @@ export default class RowerDataCharacteristic extends bleno.Characteristic { const bufferBuilder = new BufferBuilder() // Field flags as defined in the Bluetooth Documentation // Stroke Rate (default), Stroke Count (default), Total Distance (2), Instantaneous Pace (3), - // Instantaneous Power (5), Total / Expended Energy (8) - // todo: might add: Average Stroke Rate (1), Average Pace (4), Average Power (6), Heart Rate (9) - // Elapsed Time (11), Remaining Time (12) + // Instantaneous Power (5), Total / Expended Energy (8), Heart Rate (9), Elapsed Time (11) + // todo: might add: Average Stroke Rate (1), Average Pace (4), Average Power (6) + // Remaining Time (12) // 00101100 bufferBuilder.writeUInt8(0x2c) - // 00000001 - bufferBuilder.writeUInt8(0x01) + // 00001011 + bufferBuilder.writeUInt8(0x0B) // see https://www.bluetooth.com/specifications/specs/gatt-specification-supplement-3/ // for some of the data types @@ -77,8 +80,16 @@ export default class RowerDataCharacteristic extends bleno.Characteristic { // period of one hour. bufferBuilder.writeUInt16LE(data.caloriesPerHour) // Energy per minute - bufferBuilder.writeUInt16LE(data.caloriesPerMinute) + bufferBuilder.writeUInt8(data.caloriesPerMinute) + // Heart Rate: Beats per minute with a resolution of 1 + bufferBuilder.writeUInt8(data.heartRate) + // Elapsed Time: Seconds with a resolution of 1 + bufferBuilder.writeUInt16LE(data.durationTotal) + const buffer = bufferBuilder.getBuffer() + if (buffer.length > this._subscriberMaxValueSize) { + log.warn(`RowerDataCharacteristic - notification of ${buffer.length} bytes is too large for the subscriber`) + } this._updateValueCallback(bufferBuilder.getBuffer()) } return this.RESULT_SUCCESS diff --git a/app/ble/ftms/RowerFeatureCharacteristic.js b/app/ble/ftms/RowerFeatureCharacteristic.js index e9d1584..04e929e 100644 --- a/app/ble/ftms/RowerFeatureCharacteristic.js +++ b/app/ble/ftms/RowerFeatureCharacteristic.js @@ -23,13 +23,14 @@ export default class RowerFeatureCharacteristic extends bleno.Characteristic { 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), Power Measurement Supported (14) - // 00100100 01000010 + // 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, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] + 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/characteristic/AdditionalStatus.js b/app/ble/pm5/characteristic/AdditionalStatus.js index f74e6a6..62a95bd 100644 --- a/app/ble/pm5/characteristic/AdditionalStatus.js +++ b/app/ble/pm5/characteristic/AdditionalStatus.js @@ -44,7 +44,7 @@ export default class AdditionalStatus extends bleno.Characteristic { // strokeRate: UInt8 in strokes/min bufferBuilder.writeUInt8(data.strokesPerMinute) // heartRate: UInt8 in bpm, 255 if invalid - bufferBuilder.writeUInt8(255) + bufferBuilder.writeUInt8(255)// data.heartRate // currentPace: UInt16LE in 0.01 sec/500m // if split is infinite (i.e. while pausing), use the highest possible number bufferBuilder.writeUInt16LE(data.split !== Infinity ? data.split * 100 : 0xFFFF) diff --git a/app/server.js b/app/server.js index 9d984e3..5a18a15 100644 --- a/app/server.js +++ b/app/server.js @@ -87,6 +87,7 @@ rowingStatistics.on('strokeFinished', (data) => { split: data.split, strokesPerMinute: data.strokesPerMinute, speed: data.speed, + heartRate: 0, strokeState: data.strokeState } webServer.notifyClients(metrics) @@ -107,6 +108,7 @@ rowingStatistics.on('rowingPaused', (data) => { splitFormatted: '∞', split: Infinity, speed: 0, + heartRate: 0, strokeState: 'RECOVERY' } webServer.notifyClients(metrics)