diff --git a/apps/bthrm/ChangeLog b/apps/bthrm/ChangeLog index 99cf0c670..000c5e3f8 100644 --- a/apps/bthrm/ChangeLog +++ b/apps/bthrm/ChangeLog @@ -40,3 +40,4 @@ 0.16: Set powerdownRequested correctly on BTHRM power on Additional logging on errors Add debug option for disabling active scanning +0.17: New GUI based on layout library diff --git a/apps/bthrm/bthrm.js b/apps/bthrm/bthrm.js index fadf2a5d8..b07e7bd37 100644 --- a/apps/bthrm/bthrm.js +++ b/apps/bthrm/bthrm.js @@ -1,5 +1,5 @@ -var intervalInt; -var intervalBt; +const BPM_FONT_SIZE="19%"; +const VALUE_TIMEOUT=3000; var BODY_LOCS = { 0: 'Other', @@ -7,46 +7,119 @@ var BODY_LOCS = { 2: 'Wrist', 3: 'Finger', 4: 'Hand', - 5: 'Ear Lobe', + 5: 'Earlobe', 6: 'Foot', +}; + +var Layout = require("Layout"); + +function border(l,c) { + g.setColor(c).drawLine(l.x+l.w*0.05, l.y-4, l.x+l.w*0.95, l.y-4); } -function clear(y){ - g.reset(); - g.clearRect(0,y,g.getWidth(),y+75); -} - -function draw(y, type, event) { - clear(y); - var px = g.getWidth()/2; - var str = event.bpm + ""; - g.reset(); - g.setFontAlign(0,0); - g.setFontVector(40).drawString(str,px,y+20); - str = "Event: " + type; - if (type === "HRM") { - str += " Confidence: " + event.confidence; - g.setFontVector(12).drawString(str,px,y+40); - str = " Source: " + (event.src ? event.src : "internal"); - g.setFontVector(12).drawString(str,px,y+50); +function getRow(id, text, additionalInfo){ + let additional = []; + let l = { + type:"h", c: [ + { + type:"v", + width: g.getWidth()*0.4, + c: [ + {type:"txt", halign:1, font:"8%", label:text, id:id+"text" }, + {type:"txt", halign:1, font:BPM_FONT_SIZE, label:"--", id:id, bgCol: g.theme.bg } + ] + },{ + type:undefined, fillx:1 + },{ + type:"v", + valign: -1, + width: g.getWidth()*0.45, + c: additional + },{ + type:undefined, width:g.getWidth()*0.05 + } + ] + }; + for (let i of additionalInfo){ + let label = {type:"txt", font:"6x8", label:i + ":" }; + let value = {type:"txt", font:"6x8", label:"--", id:id + i }; + additional.push({type:"h", halign:-1, c:[ label, {type:undefined, fillx:1}, value ]}); } - if (type === "BTHRM"){ - if (event.battery) str += " Bat: " + (event.battery ? event.battery : ""); - g.setFontVector(12).drawString(str,px,y+40); - str= ""; - if (event.location) str += "Loc: " + BODY_LOCS[event.location]; - if (event.rr && event.rr.length > 0) str += " RR: " + event.rr.join(","); - g.setFontVector(12).drawString(str,px,y+50); - str= ""; - if (event.contact) str += " Contact: " + event.contact; - if (event.energy) str += " kJoule: " + event.energy.toFixed(0); - g.setFontVector(12).drawString(str,px,y+60); + + return l; +} + +var layout = new Layout( { + type:"v", c: [ + getRow("int", "INT", ["Confidence"]), + getRow("agg", "HRM", ["Confidence", "Source"]), + getRow("bt", "BT", ["Battery","Location","Contact", "RR", "Energy"]), + { type:undefined, height:8 } //dummy to protect debug output + ] +}, { + lazy:true +}); + +var int,agg,bt; +var firstEvent = true; + +function draw(){ + if (!(int || agg || bt)) return; + + if (firstEvent) { + g.clearRect(Bangle.appRect); + firstEvent = false; + } + + let now = Date.now(); + + if (int && int.time > (now - VALUE_TIMEOUT)){ + layout.int.label = int.bpm; + if (!isNaN(int.confidence)) layout.intConfidence.label = int.confidence; + } else { + layout.int.label = "--"; + layout.intConfidence.label = "--"; + } + + if (agg && agg.time > (now - VALUE_TIMEOUT)){ + layout.agg.label = agg.bpm; + if (!isNaN(agg.confidence)) layout.aggConfidence.label = agg.confidence; + if (agg.src) layout.aggSource.label = agg.src; + } else { + layout.agg.label = "--"; + layout.aggConfidence.label = "--"; + layout.aggSource.label = "--"; + } + + if (bt && bt.time > (now - VALUE_TIMEOUT)) { + layout.bt.label = bt.bpm; + if (!isNaN(bt.battery)) layout.btBattery.label = bt.battery + "%"; + if (bt.rr) layout.btRR.label = bt.rr.join(","); + if (!isNaN(bt.location)) layout.btLocation.label = BODY_LOCS[bt.location]; + if (bt.contact !== undefined) layout.btContact.label = bt.contact ? "Yes":"No"; + if (!isNaN(bt.energy)) layout.btEnergy.label = bt.energy.toFixed(0) + "kJ"; + } else { + layout.bt.label = "--"; + layout.btBattery.label = "--"; + layout.btRR.label = "--"; + layout.btLocation.label = "--"; + layout.btContact.label = "--"; + layout.btEnergy.label = "--"; + } + + layout.update(); + layout.render(); + let first = true; + for (let c of layout.l.c){ + if (first) { + first = false; + continue; + } + if (c.type && c.type == "h") + border(c,g.theme.fg); } } -var firstEventBt = true; -var firstEventInt = true; - // This can get called for the boot code to show what's happening function showStatusInfo(txt) { @@ -57,41 +130,26 @@ function showStatusInfo(txt) { } function onBtHrm(e) { - if (firstEventBt){ - clear(24); - firstEventBt = false; - } - draw(100, "BTHRM", e); - if (e.bpm === 0){ - Bangle.buzz(100,0.2); - } - if (intervalBt){ - clearInterval(intervalBt); - } - intervalBt = setInterval(()=>{ - clear(100); - }, 2000); + bt = e; + bt.time = Date.now(); } -function onHrm(e) { - if (firstEventInt){ - clear(24); - firstEventInt = false; - } - draw(24, "HRM", e); - if (intervalInt){ - clearInterval(intervalInt); - } - intervalInt = setInterval(()=>{ - clear(24); - }, 2000); +function onInt(e) { + int = e; + int.time = Date.now(); } +function onAgg(e) { + agg = e; + agg.time = Date.now(); +} var settings = require('Storage').readJSON("bthrm.json", true) || {}; Bangle.on('BTHRM', onBtHrm); -Bangle.on('HRM', onHrm); +Bangle.on('HRM_int', onInt); +Bangle.on('HRM', onAgg); + Bangle.setHRMPower(1,'bthrm'); if (!(settings.startWithHrm)){ @@ -103,10 +161,11 @@ Bangle.loadWidgets(); Bangle.drawWidgets(); if (Bangle.setBTHRMPower){ g.reset().setFont("6x8",2).setFontAlign(0,0); - g.drawString("Please wait...",g.getWidth()/2,g.getHeight()/2 - 24); + g.drawString("Please wait...",g.getWidth()/2,g.getHeight()/2); + setInterval(draw, 1000); } else { g.reset().setFont("6x8",2).setFontAlign(0,0); - g.drawString("BTHRM disabled",g.getWidth()/2,g.getHeight()/2 + 32); + g.drawString("BTHRM disabled",g.getWidth()/2,g.getHeight()/2); } E.on('kill', ()=>Bangle.setBTHRMPower(0,'bthrm')); diff --git a/apps/bthrm/lib.js b/apps/bthrm/lib.js index f5e0e1e5b..a792167ca 100644 --- a/apps/bthrm/lib.js +++ b/apps/bthrm/lib.js @@ -553,14 +553,15 @@ exports.enable = () => { if (settings.replace){ // register a listener for original HRM events and emit as HRM_int - Bangle.on("HRM", (e) => { - e.modified = true; + Bangle.on("HRM", (o) => { + let e = Object.assign({},o); log("Emitting HRM_int", e); Bangle.emit("HRM_int", e); if (fallbackActive){ // if fallback to internal HRM is active, emit as HRM_R to which everyone listens - log("Emitting HRM_R(int)", e); - Bangle.emit("HRM_R", e); + o.src = "int"; + log("Emitting HRM_R(int)", o); + Bangle.emit("HRM_R", o); } }); @@ -576,6 +577,13 @@ exports.enable = () => { if (name == "HRM") o("HRM_R", cb); else o(name, cb); })(Bangle.removeListener); + } else { + Bangle.on("HRM", (o)=>{ + o.src = "int"; + let e = Object.assign({},o); + log("Emitting HRM_int", e); + Bangle.emit("HRM_int", e); + }); } Bangle.origSetHRMPower = Bangle.setHRMPower; diff --git a/apps/bthrm/metadata.json b/apps/bthrm/metadata.json index 18c34ea33..fea274ff3 100644 --- a/apps/bthrm/metadata.json +++ b/apps/bthrm/metadata.json @@ -2,9 +2,10 @@ "id": "bthrm", "name": "Bluetooth Heart Rate Monitor", "shortName": "BT HRM", - "version": "0.16", + "version": "0.17", "description": "Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.", "icon": "app.png", + "screenshots": [{"url":"screen.png"}], "type": "app", "tags": "health,bluetooth,hrm,bthrm", "supports": ["BANGLEJS","BANGLEJS2"], diff --git a/apps/bthrm/screen.png b/apps/bthrm/screen.png new file mode 100644 index 000000000..6b6b85227 Binary files /dev/null and b/apps/bthrm/screen.png differ