diff --git a/apps/widbgjs/ChangeLog b/apps/widbgjs/ChangeLog new file mode 100644 index 000000000..7b83706bf --- /dev/null +++ b/apps/widbgjs/ChangeLog @@ -0,0 +1 @@ +0.01: First release diff --git a/apps/widbgjs/README.md b/apps/widbgjs/README.md new file mode 100644 index 000000000..dbb8cd9e4 --- /dev/null +++ b/apps/widbgjs/README.md @@ -0,0 +1,29 @@ +# Prerequisites +For this widget to work and to get data from the phone, you need: +- An Android phone + - with xDrip and the helper app installed. + - the Gadgetbridge app (bangle version) for the Android phone +- A BangleJS + - With this widget installed + +# Widget + +## How to use it +Make sure you have all the prerequisites from above. + +The watch should automatically start displaying values, if there is an arrow visible behind the value, the value is within the not-expired-yet time range changeable in the settings standard is 15 minutes. (I will probably change this in the future, to strike through the text to make expired values clearer). + +## Settings +In the settings, you can: +- Disable/hide the widget +- Change the unit from mmol/L to mg/dL +- Set a time at which old BG values expire + + +# Developer +Developed by Phil Roggenbuck (phrogg) + + +# Disclaimer +As well as xdrip you should not use this app to make medical decisions! + diff --git a/apps/widbgjs/metadata.json b/apps/widbgjs/metadata.json new file mode 100644 index 000000000..0639b5a51 --- /dev/null +++ b/apps/widbgjs/metadata.json @@ -0,0 +1,22 @@ +{ + "id": "widbgjs", + "name": "Blood Glucose Widget (xdrip)", + "shortName":"BG Widget", + "icon": "screenshot.png", + "screenshots": [{"url":"screenshot.png"}], + "version":"0.01", + "type": "widget", + "supports": ["BANGLEJS", "BANGLEJS2"], + "readme": "README.md", + "allow_emulator":true, + "description": "Displays the current blood glucose received from xdrip and send over via a helper app on the watch.", + "tags": "widget", + "storage": [ + {"name":"widbgjs.wid.js","url":"widget.js"}, + {"name":"widbgjs.settings.js","url":"settings.js"} + ], + "data": [ + {"name":"widbgjs.json"}, + {"name":"widbgjs.settings.json"} + ] + } diff --git a/apps/widbgjs/screenshot.png b/apps/widbgjs/screenshot.png new file mode 100644 index 000000000..b6e092af2 Binary files /dev/null and b/apps/widbgjs/screenshot.png differ diff --git a/apps/widbgjs/settings.js b/apps/widbgjs/settings.js new file mode 100644 index 000000000..f2eb190bc --- /dev/null +++ b/apps/widbgjs/settings.js @@ -0,0 +1,54 @@ +(function (back) { + const SAVEFILE = "wpbgjs.settings.json"; + + // initialize with default settings... + let s = { + 'unitIsMmol': true, + 'expireThreshold': 600000, + 'hide': false + }; + // ...and overwrite them with any saved values + // This way saved values are preserved if a new version adds more settings + const storage = require('Storage'); + const d = storage.readJSON(SAVEFILE, 1) || {}; + const saved = d.settings || {}; + for (const key in saved) { + s[key] = saved[key]; + } + + function save() { + d.settings = s; + storage.write(SAVEFILE, d); + WIDGETS['widbgjs'].draw(); + } + + E.showMenu({ + '': { 'title': 'BG widget' }, + 'Unit': { + value: s.unitIsMmol, + format: () => (s.unitIsMmol ? 'mmol/L' : 'mg/dL'), + onchange: () => { + s.unitIsMmol = !s.unitIsMmol; + save(); + }, + }, + 'Exp. BG': { + value: s.expireThreshold, + min: 18000, step: 60000, + format: s => (s ? s / 60000 + ' min' : '0'), + onchange: (g) => { + s.expireThreshold = g; + save(); + }, + }, + 'Hide Widget': { + value: s.hide, + format: () => (s.hide ? 'Yes' : 'No'), + onchange: () => { + s.hide = !s.hide; + save(); + }, + }, + '< Back': back, + }); +}); \ No newline at end of file diff --git a/apps/widbgjs/widget.js b/apps/widbgjs/widget.js new file mode 100644 index 000000000..1a1df002d --- /dev/null +++ b/apps/widbgjs/widget.js @@ -0,0 +1,143 @@ +//WIDGETS = {}; // <-- for development only + +(() => { + // persistant vals + let storedData; + let settings; + + function loadSettings() { // stolen from https://github.com/espruino/BangleApps/blob/master/apps/widpedom/widget.js + const d = require('Storage').readJSON("widbgjs.settings.json", 1) || {}; + settings = Object.assign({ + 'unitIsMmol': true, + 'expireThreshold': 600000, + 'reloadInterval': 5 * 60000, + 'hide': false + }, d || {}); + return d; + } + + function loadVals() { + try { + const d = require('Storage').readJSON("widbgjs.json", 1) || {}; + storedData = Object.assign({ + 'bg': null, + 'bgTimeStamp': null, + 'bgDirection': null + }, d || {}); + return d; + } catch(e) { + Bangle.removeFile("widbgjs.json"); + } + return null; + } + + function calculateRotation(bgDirection) { + var a = 90; + // get the arrow right (https://github.com/StephenBlackWasAlreadyTaken/NightWatch/blob/6de1d3775c6e447177c12f387f647628cc8e24ce/mobile/src/main/java/com/dexdrip/stephenblack/nightwatch/Bg.java) + switch (bgDirection) { + case ("DoubleDown"): + g.setColor("#f00"); + a = 180; + break; + case ("SingleDown"): + a = 180; + break; + case ("DoubleUp"): + g.setColor("#f00"); + a = 0; + break; + case ("SingleUp"): + a = 0; + break; + case ("FortyFiveUp"): + a = 45; + break; + case ("FortyFiveDown"): + a = 135; + break; + case ("Flat"): + a = 90; + break; + } + // turn the arrow thanks to (https://forum.espruino.com/conversations/344607/) + const p180 = Math.PI / 180; + // a is defined above + var r = 21; + var x = r * Math.sin(a * p180); + var y = r * Math.cos(a * p180); + + return a * p180; + } + + function getBG(bg) { + var tmp = null; + + try { + if (storedData.bg !== null) { + tmp = bg; + + if (settings.unitIsMmol) { + tmp /= 18; + tmp = tmp.toFixed(1); + } + } + + } catch (e) { } + return tmp; + } + + function isBgTooOld(bgTimeStamp) { + var currTimeInMilli = new Date().valueOf(); + + try { + if (bgTimeStamp === null) { + return true; + } + + if (currTimeInMilli - settings.expireThreshold <= bgTimeStamp) { + return false; + } + } catch (e) { } + return true; + } + + function draw() { + loadSettings(); + try { + if (settings.hide) return; + } catch (e) { } + loadVals(); + + outpt = getBG(storedData.bg); + + if (outpt === null) { // this means no value has been received yet + outpt = "BG"; + bgTimeStamp = "0"; + } + + // prepare to write on the screen + g.reset().clearRect(this.x, this.y, this.x + this.width, this.y + 23); // erase background + g.setFont('Vector', 22); + g.setColor(g.theme.fg); + + // check if the value is too old + if (!isBgTooOld(storedData.bgTimeStamp)) { + g.drawImage(atob("FBQBAGAADwAB+AA/wAduAGZgAGAABgAAYAAGAABgAAYAAGAABgAAYAAGAABgAAYAAGAABgA="), this.x + 60, this.y + 9, { rotate: calculateRotation(storedData.bgDirection) }); + } + g.setColor(g.theme.fg).drawString(outpt, this.x + 5, this.y); + } + + setInterval(function () { + WIDGETS["widbgjs"].draw(WIDGETS["widbgjs"]); + }, 5 * 60000); // update every 5 minutes (%* 60000 + + + // add your widget + WIDGETS["widbgjs"] = { + area: "tl", + width: 72, + draw: draw + }; +})(); + +//Bangle.drawWidgets(); // <-- for development only