From 47575f0f809ced9eff24e6a958f0f505324012d0 Mon Sep 17 00:00:00 2001 From: sebi Date: Mon, 9 Mar 2020 13:24:38 +0100 Subject: [PATCH] New heart rate recorder app --- apps.json | 14 ++++ apps/heart/ChangeLog | 2 + apps/heart/app-icon.js | 1 + apps/heart/app-settings.json | 4 + apps/heart/app.js | 100 +++++++++++++++++++++++ apps/heart/app.png | Bin 0 -> 883 bytes apps/heart/interface.html | 149 +++++++++++++++++++++++++++++++++++ apps/heart/widget.js | 50 ++++++++++++ 8 files changed, 320 insertions(+) create mode 100644 apps/heart/ChangeLog create mode 100644 apps/heart/app-icon.js create mode 100644 apps/heart/app-settings.json create mode 100644 apps/heart/app.js create mode 100644 apps/heart/app.png create mode 100644 apps/heart/interface.html create mode 100644 apps/heart/widget.js diff --git a/apps.json b/apps.json index b078ea6ae..eb2644df9 100644 --- a/apps.json +++ b/apps.json @@ -261,6 +261,20 @@ {"name":"gpsrec.wid.js","url":"widget.js"} ] }, + { "id": "heart", + "name": "Heart Rate Recorder", + "icon": "app.png", + "version":"0.01", + "interface": "interface.html", + "description": "Application that allows you to record your heart rate. Can run in background", + "tags": "tool,health,widget", + "storage": [ + {"name":"heart.app.js","url":"app.js"}, + {"name":"heart.json","url":"app-settings.json","evaluate":true}, + {"name":"heart.img","url":"app-icon.js","evaluate":true}, + {"name":"heart.wid.js","url":"widget.js"} + ] + }, { "id": "slevel", "name": "Spirit Level", "icon": "spiritlevel.png", diff --git a/apps/heart/ChangeLog b/apps/heart/ChangeLog new file mode 100644 index 000000000..4c4db83bc --- /dev/null +++ b/apps/heart/ChangeLog @@ -0,0 +1,2 @@ +0.01: New App! + diff --git a/apps/heart/app-icon.js b/apps/heart/app-icon.js new file mode 100644 index 000000000..a7f67291e --- /dev/null +++ b/apps/heart/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwhC/AH4AWzIAByAHDhIICCpINDDAgIIFpAADBBQuKE4QIIFxgAKC7g9HABSbIBQQXWGxgXEKQxOMC5AhBC66WMC5AuBJ5h3ICoI3LeAwKBBAICBD4TmHC48ACgQCCfxC/HAgYXDL44vFA4YRDAoiOIHAgXFYRAXFBwwIIOw4OGIxKmIC5ylHGAoXIXpBIGLxxIIIx6IJFxwwNCxQwLFxYwLCxgwJFxowJCxwwHFx4wHCyAwFFyIwFCyQYDCygA/AH4AFA")) \ No newline at end of file diff --git a/apps/heart/app-settings.json b/apps/heart/app-settings.json new file mode 100644 index 000000000..d57d970d0 --- /dev/null +++ b/apps/heart/app-settings.json @@ -0,0 +1,4 @@ +{ + "isRecording":false, + "fileNbr":0 +} diff --git a/apps/heart/app.js b/apps/heart/app.js new file mode 100644 index 000000000..366a1068d --- /dev/null +++ b/apps/heart/app.js @@ -0,0 +1,100 @@ +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +var settings = require("Storage").readJSON("heart.json",1)||{}; + +function getFileNbr(n) { + return ".heart"+n.toString(36); +} + +function updateSettings() { + require("Storage").write("heart.json", settings); + if (WIDGETS["heart"]) + WIDGETS["heart"].reload(); +} + +function showMainMenu() { + const mainMenu = { + '': { 'title': 'Heart Recorder' }, + 'RECORD': { + value: !!settings.isRecording, + format: v=>v?"On":"Off", + onchange: v => { + settings.isRecording = v; + updateSettings(); + } + }, + 'File Number': { + value: settings.fileNbr|0, + min: 0, + max: 35, + step: 1, + onchange: v => { + settings.isRecording = false; + settings.fileNbr = v; + updateSettings(); + } + }, + 'View Records': viewRecords, + '< Back': ()=>{load();} + }; + return E.showMenu(mainMenu); +} + +function viewRecords() { + const menu = { + '': { 'title': 'Heart Records' } + }; + var found = false; + for (var n=0;n<36;n++) { + var f = require("Storage").open(getFileNbr(n),"r"); + if (f.readLine()!==undefined) { + menu["Record "+n] = viewRecord.bind(null,n); + found = true; + } + } + if (!found) + menu["No Records Found"] = function(){}; + menu['< Back'] = showMainMenu; + return E.showMenu(menu); +} + +function viewRecord(n) { + const menu = { + '': { 'title': 'Heart Record '+n } + }; + var heartCount = 0; + var heartTime; + var f = require("Storage").open(getFileNbr(n),"r"); + var l = f.readLine(); + if (l!==undefined) { + var c = l.split(","); + heartTime = new Date(c[0]*1000); + } + while (l!==undefined) { + heartCount++; + // TODO: min/max/average of heart rate? + l = f.readLine(); + } + if (heartTime) + menu[" "+heartTime.toString().substr(4,17)] = function(){}; + menu[heartCount+" records"] = function(){}; + // TODO: option to draw it? Just scan through, project using min/max + menu['Erase'] = function() { + E.showPrompt("Delete Record?").then(function(v) { + if (v) { + settings.isRecording = false; + updateSettings(); + var f = require("Storage").open(getFileNbr(n),"r"); + f.erase(); + viewRecords(); + } else + viewRecord(n); + }); + }; + menu['< Back'] = viewRecords; + print(menu); + return E.showMenu(menu); +} + +showMainMenu(); diff --git a/apps/heart/app.png b/apps/heart/app.png new file mode 100644 index 0000000000000000000000000000000000000000..72b2805b860448283e7888c0bbd0c81b1d3866b4 GIT binary patch literal 883 zcmV-(1C0EMP)vd?16wr4LL(MiMb%)I=w9U36cjrl7@9BtqyRPk#v zNN=l*-9E$sdblhlfc1%F)!Bzj4Bn*L+WM-)*fWrt$9p1er}1Ip_&yHfZG4f?cP@qS zV)?VbHn@HuVSL}7uJGKAvjh6Cm551fENwF$!RG-{nEMlWE%+Tvqp$Hewg$X6*Z6*^ zM*K{w_&SW?d5dTNUHpm*gSPhq$1lpOdbf!O@IfQh5nLWzPnh4p?g-lZX_WgsP!xi( z03ORBS&6;akCzo?a12v{gM&ePKaD1XwsE{ra_(1@)zxKj1wM(0|J%ZeOTxZ@;3tbq z+i>=K7N^lOmHr<^gngxWE>`0fMViEB|Mgt)6y-r!i2aP~gZqafj>0qwm*~{JC1$g@ zu}J`MphETwp2M3p&JM&Z=nyt2BEl>XqZJXcGW=8&frn;K7FC zdx}c86TFk*w3&7WbWZ)fL>Oc$Lf*e;Ri#gWf1M$tyXIXmRE_EUd$qmKOgFu3jdMOJWt?QZIhW z5O&zEF@|R?T%pckw`O02mUK2O++_Yk$CTnD$p|BiFv9-~{{eYm*St3QPn-Y%002ov JPDHLkV1n(LqG134 literal 0 HcmV?d00001 diff --git a/apps/heart/interface.html b/apps/heart/interface.html new file mode 100644 index 000000000..7d56e8461 --- /dev/null +++ b/apps/heart/interface.html @@ -0,0 +1,149 @@ + + + + + +
+ + + + + + + diff --git a/apps/heart/widget.js b/apps/heart/widget.js new file mode 100644 index 000000000..7d46aa239 --- /dev/null +++ b/apps/heart/widget.js @@ -0,0 +1,50 @@ +(() => { + var settings = {}; + var hrmToggle = true; // toggles once for each reading + var recFile; // file for heart rate recording + + // draw your widget + function draw() { + if (!settings.isRecording) return; + g.reset(); + g.setFontAlign(0,0); + g.clearRect(this.x,this.y,this.x+23,this.y+23); + g.setColor(hrmToggle?"#ff0000":"#ff8000"); + g.fillCircle(this.x+6,this.y+6,4); // draw heart left circle + g.fillCircle(this.x+16,this.y+6,4); // draw heart right circle + g.fillPoly([this.x+2,this.y+8,this.x+20,this.y+8,this.x+11,this.y+18]); // draw heart bottom triangle + g.setColor(-1); // change color back to be nice to other apps + } + + function onHRM(hrm) { + hrmToggle = !hrmToggle; + WIDGETS["heart"].draw(); + if (recFile) recFile.write([getTime().toFixed(0),hrm.bpm,hrm.confidence].join(",")+"\n"); + } + + // Called by the heart app to reload settings and decide what's + function reload() { + settings = require("Storage").readJSON("heart.json",1)||{}; + settings.fileNbr |= 0; + + Bangle.removeListener('HRM',onHRM); + if (settings.isRecording) { + WIDGETS["heart"].width = 24; + Bangle.on('HRM',onHRM); + Bangle.setHRMPower(1); + var n = settings.fileNbr.toString(36); + recFile = require("Storage").open(".heart"+n,"a"); + } else { + WIDGETS["heart"].width = 0; + Bangle.setHRMPower(0); + recFile = undefined; + } + } + // add the widget + WIDGETS["heart"]={area:"tl",width:24,draw:draw,reload:function() { + reload(); + Bangle.drawWidgets(); // relayout all widgets + }}; + // load settings, set correct widget width + reload(); +})()