bthrm - Better stability and auto reconnect

pull/1353/head
Martin Boonk 2022-01-25 21:16:43 +01:00
parent e997ad59ed
commit 26226ffc9c
4 changed files with 180 additions and 75 deletions

View File

@ -5,3 +5,5 @@
0.03: Prevent readings from internal sensor mixing into BT values
Mark events with src property
Show actual source of event in app
0.04: Automatically reconnect BT sensor
App buzzes if no BTHRM events for more than 3 seconds

View File

@ -1,13 +1,28 @@
(function() {
var log = function() {};//print
//var sf = require("Storage").open("bthrm.log","a");
var log = function(text, param){
/*var logline = Date.now().toFixed(3) + " - " + text;
if (param){
logline += " " + JSON.stringify(param);
}
sf.write(logline + "\n");
print(logline);*/
}
log("Start");
var blockInit = false;
var gatt;
var status;
var currentRetryTimeout;
var initialRetryTime = 40;
var maxRetryTime = 60000;
var retryTime = initialRetryTime;
var origIsHRMOn = Bangle.isHRMOn;
Bangle.isBTHRMOn = function(){
return (status=="searching" || status=="connecting") || (gatt!==undefined);
}
return (gatt!==undefined && gatt.connected);
};
Bangle.isHRMOn = function() {
var settings = require('Storage').readJSON("bthrm.json", true) || {};
@ -18,16 +33,135 @@
return Bangle.isBTHRMOn();
}
return origIsHRMOn() || Bangle.isBTHRMOn();
};
var serviceFilters = [{
services: [
"180d"
]
}];
function retry(){
log("Retry with time " + retryTime);
if (currentRetryTimeout){
log("Clearing timeout " + currentRetryTimeout);
clearTimeout(currentRetryTimeout);
currentRetryTimeout = undefined;
}
var clampedTime = retryTime < 200 ? 200 : initialRetryTime;
currentRetryTimeout = setTimeout(() => {
log("Set timeout for retry as " + clampedTime);
initBt();
}, clampedTime);
retryTime = Math.pow(retryTime, 1.1);
if (retryTime > maxRetryTime){
retryTime = maxRetryTime;
}
}
function onDisconnect(reason) {
log("Disconnect: " + reason);
log("Gatt: ", gatt);
retry();
}
function onCharacteristic(event) {
var settings = require('Storage').readJSON("bthrm.json", true) || {};
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(settings.replace ? "HRM" : "BTHRM", {
bpm: bpm,
confidence: bpm == 0 ? 0 : 100,
src: settings.replace ? "bthrm" : undefined
});
}
Bangle.setBTHRMPower = function(isOn, app) {
var reUseCounter=0;
function initBt() {
log("initBt with blockInit: " + blockInit);
if (blockInit){
retry();
return;
}
blockInit = true;
var connectionPromise;
if (reUseCounter > 3){
log("Reuse counter to high")
if (gatt.connected == true){
try {
log("Force disconnect with gatt: ", gatt);
gatt.disconnect();
} catch(e) {
log("Error during force disconnect", e);
}
}
gatt=undefined;
reUseCounter = 0;
}
if (!gatt){
var requestPromise = NRF.requestDevice({ filters: serviceFilters });
connectionPromise = requestPromise.then(function(device) {
gatt = device.gatt;
log("Gatt after request:", gatt);
gatt.device.on('gattserverdisconnected', onDisconnect);
});
} else {
reUseCounter++;
log("Reusing gatt:", gatt);
connectionPromise = gatt.connect();
}
var servicePromise = connectionPromise.then(function() {
return gatt.getPrimaryService(0x180d);
});
var characteristicPromise = servicePromise.then(function(service) {
log("Got service:", service);
return service.getCharacteristic(0x2A37);
});
var notificationPromise = characteristicPromise.then(function(c) {
log("Got characteristic:", c);
c.on('characteristicvaluechanged', onCharacteristic);
return c.startNotifications();
});
notificationPromise.then(()=>{
log("Wait for notifications");
retryTime = initialRetryTime;
blockInit=false;
});
notificationPromise.catch((e) => {
log("Error:", e);
blockInit = false;
retry();
});
}
Bangle.setBTHRMPower = function(isOn, app) {
var settings = require('Storage').readJSON("bthrm.json", true) || {};
// Do app power handling
if (!app) app="?";
log("setBTHRMPower ->", isOn, app);
if (Bangle._PWR===undefined) Bangle._PWR={};
if (Bangle._PWR.BTHRM===undefined) Bangle._PWR.BTHRM=[];
if (isOn && !Bangle._PWR.BTHRM.includes(app)) Bangle._PWR.BTHRM.push(app);
@ -35,63 +169,19 @@
isOn = Bangle._PWR.BTHRM.length;
// so now we know if we're really on
if (isOn) {
log("setBTHRMPower on", app);
if (!Bangle.isBTHRMOn()) {
log("BTHRM not already on");
status = "searching";
NRF.requestDevice({ filters: [{ services: ['180D'] }] }).then(function(device) {
log("Found device "+device.id);
status = "connecting";
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(settings.replace?"HRM":"BTHRM", {
bpm:bpm,
confidence:100,
src:settings.replace?"bthrm":undefined
});
});
return characteristic.startNotifications();
}).then(function() {
log("Ready");
status = "ok";
}).catch(function(err) {
log("Error",err);
gatt = undefined;
status = "error";
});
initBt();
}
} else { // not on
log("setBTHRMPower off", app);
log("Power off for " + app);
if (gatt) {
log("BTHRM connected - disconnecting");
status = undefined;
try {gatt.disconnect();}catch(e) {
log("BTHRM disconnect error", e);
try {
log("Disconnect with gatt: ", gatt);
gatt.disconnect();
} catch(e) {
log("Error during disconnect", e);
}
blockInit = false;
gatt = undefined;
}
}
@ -100,24 +190,29 @@
var origSetHRMPower = Bangle.setHRMPower;
Bangle.setHRMPower = function(isOn, app) {
log("setHRMPower for " + app + ":" + (isOn?"on":"off"));
var settings = require('Storage').readJSON("bthrm.json", true) || {};
if (settings.enabled || !isOn){
log("Enable BTHRM power");
Bangle.setBTHRMPower(isOn, app);
}
if ((settings.enabled && !settings.replace) || !settings.enabled || !isOn){
log("Enable HRM power");
origSetHRMPower(isOn, app);
}
}
var settings = require('Storage').readJSON("bthrm.json", true) || {};
if (settings.enabled && settings.replace){
if (!(Bangle._PWR===undefined) && !(Bangle._PWR.HRM===undefined)){
for (var i = 0; i < Bangle._PWR.HRM.length; i++){
var app = Bangle._PWR.HRM[i];
origSetHRMPower(0, app);
Bangle.setBTHRMPower(1, app);
if (Bangle._PWR.HRM===undefined) break;
log("Replace HRM event");
if (!(Bangle._PWR===undefined) && !(Bangle._PWR.HRM===undefined)){
for (var i = 0; i < Bangle._PWR.HRM.length; i++){
var app = Bangle._PWR.HRM[i];
log("Moving app " + app);
origSetHRMPower(0, app);
Bangle.setBTHRMPower(1, app);
if (Bangle._PWR.HRM===undefined) break;
}
}
}
}
})();

View File

@ -10,7 +10,9 @@ function draw(y, event, type, counter) {
g.reset();
g.setFontAlign(0,0);
g.clearRect(0,y,g.getWidth(),y+75);
if (type == null || event == null || counter == 0) return;
if (type == null || event == null || counter == 0){
return;
}
var str = event.bpm + "";
g.setFontVector(40).drawString(str,px,y+20);
str = "Confidence: " + event.confidence;
@ -21,21 +23,27 @@ function draw(y, event, type, counter) {
}
function onBtHrm(e) {
print("Event for BT " + JSON.stringify(e));
counterBt += 5;
//print("Event for BT " + JSON.stringify(e));
if (e.bpm == 0){
Bangle.buzz(100,0.2);
}
if (counterBt == 0){
Bangle.buzz(200,0.5);
}
counterBt += 3;
eventBt = e;
}
function onHrm(e) {
print("Event for Int " + JSON.stringify(e));
counterInt += 5;
//print("Event for Int " + JSON.stringify(e));
counterInt += 3;
eventInt = e;
}
Bangle.on('BTHRM', onBtHrm);
Bangle.on('HRM', onHrm);
Bangle.setHRMPower(1,'bthrm')
Bangle.setHRMPower(1,'bthrm');
g.clear();
Bangle.loadWidgets();
@ -47,13 +55,13 @@ g.drawString("Please wait...",g.getWidth()/2,g.getHeight()/2 - 16);
function drawInt(){
counterInt--;
if (counterInt < 0) counterInt = 0;
if (counterInt > 5) counterInt = 5;
if (counterInt > 3) counterInt = 3;
draw(24, eventInt, "HRM", counterInt);
}
function drawBt(){
counterBt--;
if (counterBt < 0) counterBt = 0;
if (counterBt > 5) counterBt = 5;
if (counterBt > 3) counterBt = 3;
draw(100, eventBt, "BTHRM", counterBt);
}

View File

@ -2,7 +2,7 @@
"id": "bthrm",
"name": "Bluetooth Heart Rate Monitor",
"shortName": "BT HRM",
"version": "0.03",
"version": "0.04",
"description": "Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.",
"icon": "app.png",
"type": "app",