1
0
Fork 0
BangleApps/apps/blecsc/blecsc.js

231 lines
7.5 KiB
JavaScript

/**
* This library 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
* - \`status\` - string containing connection status
* - \`data\` - the peripheral sends a notification containing wheel/crank event data
* - \`disconnect\` - the peripheral ends the connection or the connection is lost
*
* cwr/ccr => wheel/crank cumulative revs
* lwet/lcet => wheel/crank last event time in 1/1024s
* wrps/crps => calculated wheel/crank revs per second
* wdt/cdt => time period in seconds between events
* wr => wheel revs
* kph => kilometers per hour
*/
class BLECSC {
constructor() {
this.reconnect = false; // set when start called
this.device = undefined; // set when device found
this.gatt = undefined; // set when connected
// .on("status", => string
// .on("data"
// .on("disconnect"
this.resetStats();
// Set default values and merge with stored values
this.settings = Object.assign({
circum: 2068 // circumference in mm
}, (require('Storage').readJSON('blecsc.json', true) || {}));
}
resetStats() {
this.cwr = undefined;
this.ccr = undefined;
this.lwet = undefined;
this.lcet = undefined;
this.lastCwr = undefined;
this.lastCcr = undefined;
this.lastLwet = undefined;
this.lastLcet = undefined;
this.kph = undefined;
this.wrps = 0; // wheel revs per second
this.crps = 0; // crank revs per second
this.widle = 0; // wheel idle counter
this.cidle = 0; // crank idle counter
//this.batteryLevel = undefined;
}
getDeviceAddress() {
if (!this.device || !this.device.id)
return '00:00:00:00:00:00';
return this.device.id.split(" ")[0];
}
status(txt) {
this.emit("status", txt);
}
/**
* Find and connect to a device which exposes the CSC service.
*
* @return {Promise}
*/
connect() {
this.status("Scanning");
// Find a device, then get the CSC Service and subscribe to
// notifications on the CSC Measurement characteristic.
// NRF.setLowPowerConnection(true);
var reconnect = this.reconnect; // auto-reconnect
return NRF.requestDevice({
timeout: 5000,
filters: [{
services: ["1816"]
}],
}).then(device => {
this.status("Connecting");
this.device = device;
this.device.on('gattserverdisconnected', event => {
this.device = undefined;
this.gatt = undefined;
this.resetStats();
this.status("Disconnected");
this.emit("disconnect", event);
if (reconnect) {// auto-reconnect
reconnect = false;
setTimeout(() => {
if (this.reconnect) this.connect().then(() => {}, () => {});
}, 500);
}
});
return new Promise(resolve => setTimeout(resolve, 150)); // On CooSpo we get a 'Connection Timeout' if we try and connect too soon
}).then(() => {
return this.device.gatt.connect();
}).then(gatt => {
this.status("Connected");
this.gatt = gatt;
return gatt.getPrimaryService("1816");
}).then(service => {
return service.getCharacteristic("2a5b"); // UUID of the CSC measurement characteristic
}).then(characteristic => {
// register for changes on 2a5b
characteristic.on('characteristicvaluechanged', event => {
const flags = event.target.value.getUint8(0);
var offs = 0;
var data = {};
if (flags & 1) { // FLAGS_WREV_BM
this.lastCwr = this.cwr;
this.lastLwet = this.lwet;
this.cwr = event.target.value.getUint32(1, true);
this.lwet = event.target.value.getUint16(5, true);
if (this.lastCwr === undefined) this.lastCwr = this.cwr;
if (this.lastLwet === undefined) this.lastLwet = this.lwet;
if (this.lwet < this.lastLwet) this.lastLwet -= 65536;
let secs = (this.lwet - this.lastLwet) / 1024;
if (secs) {
this.wrps = (this.cwr - this.lastCwr) / secs;
this.widle = 0;
} else {
if (this.widle<5) this.widle++;
else this.wrps = 0;
}
this.kph = this.wrps * this.settings.circum / 3600;
Object.assign(data, { // Notify the 'wheelEvent' handler
cwr: this.cwr, // cumulative wheel revolutions
lwet: this.lwet, // last wheel event time
wrps: this.wrps, // wheel revs per second
wr: this.cwr - this.lastCwr, // wheel revs
wdt : secs, // time period
kph : this.kph
});
offs += 6;
}
if (flags & 2) { // FLAGS_CREV_BM
this.lastCcr = this.ccr;
this.lastLcet = this.lcet;
this.ccr = event.target.value.getUint16(offs + 1, true);
this.lcet = event.target.value.getUint16(offs + 3, true);
if (this.lastCcr === undefined) this.lastCcr = this.ccr;
if (this.lastLcet === undefined) this.lastLcet = this.lcet;
if (this.lcet < this.lastLcet) this.lastLcet -= 65536;
let secs = (this.lcet - this.lastLcet) / 1024;
if (secs) {
this.crps = (this.ccr - this.lastCcr) / secs;
this.cidle = 0;
} else {
if (this.cidle<5) this.cidle++;
else this.crps = 0;
}
Object.assign(data, { // Notify the 'crankEvent' handler
ccr: this.ccr, // cumulative crank revolutions
lcet: this.lcet, // last crank event time
crps: this.crps, // crank revs per second
cdt : secs, // time period
});
}
this.emit("data",data);
});
return characteristic.startNotifications();
/* }).then(() => {
return this.gatt.getPrimaryService("180f");
}).then(service => {
return service.getCharacteristic("2a19");
}).then(characteristic => {
characteristic.on('characteristicvaluechanged', (event)=>{
this.batteryLevel = event.target.value.getUint8(0);
});
return characteristic.startNotifications();*/
}).then(() => {
this.status("Ready");
}, err => {
this.status("Error: " + err);
if (reconnect) { // auto-reconnect
reconnect = false;
setTimeout(() => {
if (this.reconnect) this.connect().then(() => {}, () => {});
}, 500);
}
throw err;
});
}
/**
* Disconnect the device.
*/
disconnect() {
if (!this.gatt) return;
this.gatt.disconnect();
this.gatt = undefined;
}
/* Start trying to connect - will keep searching and attempting to connect*/
start() {
this.reconnect = true;
if (!this.device)
this.connect().then(() => {}, () => {});
}
/* Stop trying to connect, and disconnect */
stop() {
this.reconnect = false;
this.disconnect();
}
}
// Get an instance of BLECSC or create one if it doesn't exist
BLECSC.getInstance = function() {
if (!BLECSC.instance) {
BLECSC.instance = new BLECSC();
}
return BLECSC.instance;
};
exports = BLECSC;
/*
var csc = require("blecsc").getInstance();
csc.on("status", txt => {
print("##", txt);
E.showMessage(txt);
});
csc.on("data", e => print(e));
csc.start();
*/