diff --git a/apps/bthrmlite/ChangeLog b/apps/bthrmlite/ChangeLog new file mode 100644 index 000000000..2286a7f70 --- /dev/null +++ b/apps/bthrmlite/ChangeLog @@ -0,0 +1 @@ +0.01: New App! \ No newline at end of file diff --git a/apps/bthrmlite/README.md b/apps/bthrmlite/README.md new file mode 100644 index 000000000..7dd1751a7 --- /dev/null +++ b/apps/bthrmlite/README.md @@ -0,0 +1,42 @@ +# Bluetooth Heart Rate Monitor - Lite + +When this app is installed it overrides Bangle.js's built in heart rate monitor with an external Bluetooth one. + +The [`bthrm` app](https://banglejs.com/apps/?id=bthrm) is a much more sophisticated version of this app, however is uses a lot more +RAM so may not be suitable for Bangle.js 1. + +HRM is requested it searches on Bluetooth for a heart rate monitor, connects, and sends data back using the `Bangle.on('HRM'` event as if it came from the on board monitor. + +This means it's compatible with many Bangle.js apps including: + +* [Heart Rate Widget](https://banglejs.com/apps/#widhrt) +* [Heart Rate Recorder](https://banglejs.com/apps/#heart) + +It it NOT COMPATIBLE with [Heart Rate Monitor](https://banglejs.com/apps/#hrm) +as that requires live sensor data (rather than just BPM readings). + +## Usage + +Just install the app, then install an app that uses the heart rate monitor. + +Once an app requests Heart Rate `bthrmlite` will automatically try and connect to the first bluetooth +heart rate monitor it finds. + +**To disable this and return to normal HRM, uninstall the app** + +## Compatible Heart Rate Monitors + +This works with any heart rate monitor providing the standard Bluetooth +Heart Rate Service (`180D`) and characteristic (`2A37`). + +So far it has been tested on: + +* CooSpo Bluetooth Heart Rate Monitor + +## Internals + +This replaces `Bangle.setHRMPower` with its own implementation. + +## Creator + +Gordon Williams \ No newline at end of file diff --git a/apps/bthrmlite/app-icon.js b/apps/bthrmlite/app-icon.js new file mode 100644 index 000000000..04a5ee610 --- /dev/null +++ b/apps/bthrmlite/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4UA///g3yy06AoIZNitUAg8AgtVqtQAgoRCAwITBAggABAoIABAgsAgIGDoIEDoApDAAwwBFIV1BYo1E+oLTAgQLGJon9BZNXBatdBYRVFBYN/r9fHoxTBBYYlEL4QLFq/a1WUgE///fr4xBv/+1Wq1EAh/3/tX6/fv/6BYOqwCzBBYf9tWq9QLF79X+oLBDIOgKgILEEIIxBGAMVNAP/BYf/BYUFBYJSB6wLC9QLBeAQLBqwLCGAL9BBYmr9X+GAILBbIIlBBYP6/wwBBYMFBYZGB/4XDGAILD34vEcwYLB15HBBYYkBBYWrFwILDKoRTCVIQLCEgQXIEgVaF44YCoRHHAAMUgQuBNgILFgECO4W/BZCPFBYinGBY6/CAArXFBY7vDAAsq1QuB0ALIOwOABY0KEgJGGGAguHDAYDBA==")) diff --git a/apps/bthrmlite/app.png b/apps/bthrmlite/app.png new file mode 100644 index 000000000..40c2ab024 Binary files /dev/null and b/apps/bthrmlite/app.png differ diff --git a/apps/bthrmlite/boot.js b/apps/bthrmlite/boot.js new file mode 100644 index 000000000..96996b0a7 --- /dev/null +++ b/apps/bthrmlite/boot.js @@ -0,0 +1,71 @@ +(function() { + var log = function() {};//print + var gatt; + + Bangle.setHRMPower = function(isOn, app) { + // Do app power handling + if (!app) app="?"; + log("setHRMPower ->", isOn, app); + if (Bangle._PWR===undefined) Bangle._PWR={}; + if (Bangle._PWR.HRM===undefined) Bangle._PWR.HRM=[]; + if (isOn && !Bangle._PWR.HRM.includes(app)) Bangle._PWR.HRM.push(app); + if (!isOn && Bangle._PWR.HRM.includes(app)) Bangle._PWR.HRM = Bangle._PWR.HRM.filter(a=>a!=app); + isOn = Bangle._PWR.HRM.length; + // so now we know if we're really on + if (isOn) { + log("setHRMPower on", app); + if (!gatt) { + log("HRM not already on"); + NRF.requestDevice({ filters: [{ services: ['180D'] }] }).then(function(device) { + log("Found device "+device.id); + device.on('gattserverdisconnected', function(reason) { + gatt = undefined; + }); + return device.gatt.connect(); + }).then(function(g) { + log("Connected"); + gatt = g; + return gatt.getPrimaryService(0x180D); + }).then(function(service) { + return service.getCharacteristic(0x2A37); + }).then(function(characteristic) { + log("Got characteristic"); + characteristic.on('characteristicvaluechanged', function(event) { + var dv = event.target.value; + var flags = dv.getUint8(0); + // 0 = 8 or 16 bit + // 1,2 = sensor contact + // 3 = energy expended shown + // 4 = RR interval + var bpm = (flags&1) ? (dv.getUint16(1)/100/* ? */) : dv.getUint8(1); // 8 or 16 bit + /* var idx = 2 + (flags&1); // index of next field + if (flags&8) idx += 2; // energy expended + if (flags&16) { + var interval = dv.getUint16(idx,1); // in milliseconds + }*/ + Bangle.emit('HRM',{ + bpm:bpm, + confidence:100 + }); + }); + return characteristic.startNotifications(); + }).then(function() { + log("Ready"); + console.log("Done!"); + }).catch(function(err) { + console.log("Error",err); + gatt = undefined; + }); + } + } else { // not on + log("setHRMPower off", app); + if (gatt) { + log("HRM connected - disconnecting"); + try {gatt.disconnect();}catch(e) { + log("HRM disconnect error", e); + } + gatt = undefined; + } + } + }; +})(); \ No newline at end of file diff --git a/apps/bthrmlite/metadata.json b/apps/bthrmlite/metadata.json new file mode 100644 index 000000000..e4b95cca8 --- /dev/null +++ b/apps/bthrmlite/metadata.json @@ -0,0 +1,15 @@ +{ "id": "bthrmlite", + "name": "Bluetooth Heart Rate Monitor (Lite)", + "shortName":"BT HRM", + "version":"0.01", + "description": "Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one. Cut-down version that uses less RAM.", + "icon": "app.png", + "tags": "health,bluetooth", + "type": "bootloader", + "supports" : ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"bthrmlite.boot.js","url":"boot.js"}, + {"name":"bthrmlite.img","url":"app-icon.js","evaluate":true} + ] +} \ No newline at end of file