/** * 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(); */