Skip to content

Commit

Permalink
Add Cycling Speed and Cadence to server. (#39)
Browse files Browse the repository at this point in the history
This enables cadence broadcasting via the BLE CSC service. e.g. for apps
like Peloton on iOS/Android.
  • Loading branch information
ptx2 authored Feb 11, 2021
1 parent 0966d80 commit 991e03a
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 2 deletions.
8 changes: 6 additions & 2 deletions src/servers/ble/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {CyclingPowerService} from './services/cycling-power'
import {CyclingSpeedAndCadenceService} from './services/cycling-speed-and-cadence'
import {BleServer} from '../../util/ble-server'

export const DEFAULT_NAME = 'Gymnasticon';
Expand All @@ -14,7 +15,8 @@ export class GymnasticonServer extends BleServer {
*/
constructor(bleno, name=DEFAULT_NAME) {
super(bleno, name, [
new CyclingPowerService()
new CyclingPowerService(),
new CyclingSpeedAndCadenceService(),
])
}

Expand All @@ -27,6 +29,8 @@ export class GymnasticonServer extends BleServer {
* @param {number} measurement.crank.timestamp - timestamp at last crank event.
*/
updateMeasurement(measurement) {
this.services[0].updateMeasurement(measurement)
for (let s of this.services) {
s.updateMeasurement(measurement)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {Characteristic, Descriptor} from '@abandonware/bleno';

/**
* Bluetooth LE GATT CSC Feature Characteristic implementation.
*/
export class CscFeatureCharacteristic extends Characteristic {
constructor() {
super({
uuid: '2a5c',
properties: ['read'],
descriptors: [
new Descriptor({
uuid: '2901',
value: 'CSC Feature'
})
],
value: Buffer.from([2,0]) // crank revolution data
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import {Characteristic, Descriptor} from '@abandonware/bleno';

const FLAG_HASCRANKDATA = (1<<1);
const CRANK_TIMESTAMP_SCALE = 1024 / 1000; // timestamp resolution is 1/1024 sec

/**
* Bluetooth LE GATT CSC Measurement Characteristic implementation.
*/
export class CscMeasurementCharacteristic extends Characteristic {
constructor() {
super({
uuid: '2a5b',
properties: ['notify'],
descriptors: [
new Descriptor({
uuid: '2903',
value: Buffer.alloc(2)
})
]
})
}

/**
* Notify subscriber (e.g. Zwift) of new CSC Measurement.
* @param {object} measurement - new csc measurement.
* @param {object} measurement.crank - last crank event.
* @param {number} measurement.crank.revolutions - revolution count at last crank event.
* @param {number} measurement.crank.timestamp - timestamp at last crank event.
*/
updateMeasurement({ crank }) {
let flags = 0;

const value = Buffer.alloc(5);

const revolutions16bit = crank.revolutions & 0xffff;
const timestamp16bit = Math.round(crank.timestamp * CRANK_TIMESTAMP_SCALE) & 0xffff;
value.writeUInt16LE(revolutions16bit, 1);
value.writeUInt16LE(timestamp16bit, 3);
flags |= FLAG_HASCRANKDATA;

value.writeUInt8(flags, 0);

if (this.updateValueCallback) {
this.updateValueCallback(value)
}
}
}
32 changes: 32 additions & 0 deletions src/servers/ble/services/cycling-speed-and-cadence/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import {PrimaryService} from '@abandonware/bleno';
import {CscMeasurementCharacteristic} from './characteristics/csc-measurement';
import {CscFeatureCharacteristic} from './characteristics/csc-feature';

/**
* Bluetooth LE GATT Cycling Speed and Cadence Service implementation.
*/
export class CyclingSpeedAndCadenceService extends PrimaryService {
/**
* Create a CyclingSpeedAndCadenceService instance.
*/
constructor() {
super({
uuid: '1816',
characteristics: [
new CscMeasurementCharacteristic(),
new CscFeatureCharacteristic(),
]
})
}

/**
* Notify subscriber (e.g. Zwift) of new CSC Measurement.
* @param {object} measurement - new csc measurement.
* @param {object} measurement.crank - last crank event.
* @param {number} measurement.crank.revolutions - revolution count at last crank event.
* @param {number} measurement.crank.timestamp - timestamp at last crank event.
*/
updateMeasurement(measurement) {
this.characteristics[0].updateMeasurement(measurement)
}
}

0 comments on commit 991e03a

Please sign in to comment.