BangleApps/apps/cycling/blecsc.js

151 lines
4.6 KiB
JavaScript
Raw Normal View History

2022-03-20 08:25:43 +00:00
const SERVICE_UUID = "1816";
// UUID of the CSC measurement characteristic
const MEASUREMENT_UUID = "2a5b";
// Wheel revolution present bit mask
const FLAGS_WREV_BM = 0x01;
// Crank revolution present bit mask
const FLAGS_CREV_BM = 0x02;
/**
* This class communicates with a Bluetooth CSC peripherial using the Espruino NRF library.
*
* ## Usage:
* 1. Register event handlers using the \`on(eventName, handlerFunction)\` method
* You can subscribe to the \`wheelEvent\` and \`crankEvent\` events or you can
* have raw characteristic values passed through using the \`value\` event.
* 2. Search and connect to a BLE CSC peripherial by calling the \`connect()\` method
* 3. To tear down the connection, call the \`disconnect()\` method
*
* ## Events
* - \`wheelEvent\` - the peripharial sends a notification containing wheel event data
* - \`crankEvent\` - the peripharial sends a notification containing crank event data
* - \`value\` - the peripharial sends any CSC characteristic notification (including wheel & crank event)
* - \`disconnect\` - the peripherial ends the connection or the connection is lost
*
* Each event can only have one handler. Any call to \`on()\` will
* replace a previously registered handler for the same event.
*/
class BLECSC {
constructor() {
this.device = undefined;
this.ccInterval = undefined;
this.gatt = undefined;
this.handlers = {
// wheelEvent
// crankEvent
// value
// disconnect
};
}
getDeviceAddress() {
if (!this.device || !this.device.id)
return '00:00:00:00:00:00';
return this.device.id.split(" ")[0];
}
checkConnection() {
if (!this.device)
console.log("no device");
// else
// console.log("rssi: " + this.device.rssi);
}
/**
* Callback for the GATT characteristicvaluechanged event.
* Consumers must not call this method!
*/
onValue(event) {
// Not interested in non-CSC characteristics
if (event.target.uuid != "0x" + MEASUREMENT_UUID) return;
// Notify the generic 'value' handler
if (this.handlers.value) this.handlers.value(event);
const flags = event.target.value.getUint8(0, true);
// Notify the 'wheelEvent' handler
if ((flags & FLAGS_WREV_BM) && this.handlers.wheelEvent) this.handlers.wheelEvent({
cwr: event.target.value.getUint32(1, true), // cumulative wheel revolutions
lwet: event.target.value.getUint16(5, true), // last wheel event time
});
// Notify the 'crankEvent' handler
if ((flags & FLAGS_CREV_BM) && this.handlers.crankEvent) this.handlers.crankEvent({
ccr: event.target.value.getUint16(7, true), // cumulative crank revolutions
lcet: event.target.value.getUint16(9, true), // last crank event time
});
}
/**
* Callback for the NRF disconnect event.
* Consumers must not call this method!
*/
onDisconnect(event) {
console.log("disconnected");
if (this.ccInterval)
clearInterval(this.ccInterval);
if (!this.handlers.disconnect) return;
this.handlers.disconnect(event);
}
/**
* Register an event handler.
*
* @param {string} event wheelEvent|crankEvent|value|disconnect
* @param {function} handler function that will receive the event as its first argument
*/
on(event, handler) {
this.handlers[event] = handler;
}
/**
* Find and connect to a device which exposes the CSC service.
*
* @return {Promise}
*/
connect() {
// Register handler for the disconnect event to be passed throug
NRF.on('disconnect', this.onDisconnect.bind(this));
// Find a device, then get the CSC Service and subscribe to
// notifications on the CSC Measurement characteristic.
// NRF.setLowPowerConnection(true);
return NRF.requestDevice({
timeout: 5000,
filters: [{ services: [SERVICE_UUID] }],
}).then(device => {
this.device = device;
this.device.on('gattserverdisconnected', this.onDisconnect.bind(this));
this.ccInterval = setInterval(this.checkConnection.bind(this), 2000);
return device.gatt.connect();
}).then(gatt => {
this.gatt = gatt;
return gatt.getPrimaryService(SERVICE_UUID);
}).then(service => {
return service.getCharacteristic(MEASUREMENT_UUID);
}).then(characteristic => {
characteristic.on('characteristicvaluechanged', this.onValue.bind(this));
return characteristic.startNotifications();
});
}
/**
* Disconnect the device.
*/
disconnect() {
if (this.ccInterval)
clearInterval(this.ccInterval);
if (!this.gatt) return;
try {
this.gatt.disconnect();
} catch {
//
}
}
}
exports = BLECSC;