diff --git a/apps/widbaroalarm/ChangeLog b/apps/widbaroalarm/ChangeLog new file mode 100644 index 000000000..ec66c5568 --- /dev/null +++ b/apps/widbaroalarm/ChangeLog @@ -0,0 +1 @@ +0.01: Initial version diff --git a/apps/widbaroalarm/README.md b/apps/widbaroalarm/README.md new file mode 100644 index 000000000..6708b1e2b --- /dev/null +++ b/apps/widbaroalarm/README.md @@ -0,0 +1,24 @@ +# Barometer alarm widget + +Get a notification when the pressure reaches defined thresholds. + +![Screenshot](screenshot.png) + +## Settings + +* Interval: check interval of sensor data in minutes. 0 to disable automatic check. +* Low alarm: Toggle low alarm + * Low threshold: Warn when pressure drops below this value +* High alarm: Toggle high alarm + * High threshold: Warn when pressure exceeds above this value +* Change alarm: Warn when pressure changes more than this value in the recent 3 hours (having at least 30 min of data) + 0 to disable this alarm. +* Show widget: Enable/disable widget visibility +* Buzz on alarm: Enable/disable buzzer on alarm + + +## Widget +The widget shows two rows: pressure value of last measurement and pressure average of the the last three hours. + +## Creator +Marco ([myxor](https://github.com/myxor)) diff --git a/apps/widbaroalarm/default.json b/apps/widbaroalarm/default.json new file mode 100644 index 000000000..7dbe6fc17 --- /dev/null +++ b/apps/widbaroalarm/default.json @@ -0,0 +1,10 @@ +{ + "buzz": true, + "lowalarm": false, + "min": 950, + "highalarm": false, + "max": 1030, + "changeIn3h": 2, + "show": true, + "interval": 15 +} diff --git a/apps/widbaroalarm/metadata.json b/apps/widbaroalarm/metadata.json new file mode 100644 index 000000000..3dd02019d --- /dev/null +++ b/apps/widbaroalarm/metadata.json @@ -0,0 +1,19 @@ +{ + "id": "widbaroalarm", + "name": "Barometer alarm widget", + "shortName": "Barometer alarm", + "version": "0.01", + "description": "A widget that can alarm on when the pressure reaches defined thresholds.", + "icon": "widget.png", + "screenshots": [{"url":"screenshot.png"}], + "type": "widget", + "tags": "tool,barometer", + "supports": ["BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"widbaroalarm.wid.js","url":"widget.js"}, + {"name":"widbaroalarm.settings.js","url":"settings.js"}, + {"name":"widbaroalarm.default.json","url":"default.json"} + ], + "data": [{"name":"widbaroalarm.json"}, {"name":"widbaroalarm.log"}] +} diff --git a/apps/widbaroalarm/screenshot.png b/apps/widbaroalarm/screenshot.png new file mode 100644 index 000000000..c398b0aa8 Binary files /dev/null and b/apps/widbaroalarm/screenshot.png differ diff --git a/apps/widbaroalarm/settings.js b/apps/widbaroalarm/settings.js new file mode 100644 index 000000000..d3d2df0ae --- /dev/null +++ b/apps/widbaroalarm/settings.js @@ -0,0 +1,85 @@ +(function(back) { + const SETTINGS_FILE = "widbaroalarm.json"; + const storage = require('Storage'); + let settings = Object.assign( + storage.readJSON("widbaroalarm.default.json", true) || {}, + storage.readJSON(SETTINGS_FILE, true) || {} + ); + + function save(key, value) { + settings[key] = value; + storage.write(SETTINGS_FILE, settings); + } + + function showMainMenu() { + let menu ={ + '': { 'title': 'Barometer alarm widget' }, + /*LANG*/'< Back': back, + "Interval": { + value: settings.interval, + min: 0, + max: 120, + step: 1, + format: x => { + return x != 0 ? x + ' min' : 'off'; + }, + onchange: x => save("interval", x) + }, + "Low alarm": { + value: settings.lowalarm, + format: x => { + return x ? 'Yes' : 'No'; + }, + onchange: x => save("lowalarm", x), + }, + "Low threshold": { + value: settings.min, + min: 600, + max: 1000, + step: 10, + onchange: x => save("min", x), + }, + "High alarm": { + value: settings.highalarm, + format: x => { + return x ? 'Yes' : 'No'; + }, + onchange: x => save("highalarm", x), + }, + "High threshold": { + value: settings.max, + min: 1000, + max: 1100, + step: 10, + onchange: x => save("max", x), + }, + "Change alarm": { + value: settings.changeIn3h, + min: 0, + max: 10, + step: 1, + format: x => { + return x != 0 ? x + ' hPa/3h' : 'off'; + }, + onchange: x => save("changeIn3h", x) + }, + "Show widget": { + value: settings.show, + format: x => { + return x ? 'Yes' : 'No'; + }, + onchange: x => save('show', x) + }, + "Buzz on alarm": { + value: settings.buzz, + format: x => { + return x ? 'Yes' : 'No'; + }, + onchange: x => save('buzz', x) + }, + }; + E.showMenu(menu); + } + + showMainMenu(); +}); diff --git a/apps/widbaroalarm/widget.js b/apps/widbaroalarm/widget.js new file mode 100644 index 000000000..b0a8f110c --- /dev/null +++ b/apps/widbaroalarm/widget.js @@ -0,0 +1,199 @@ +(function() { + let lastPressure; + let avrPressure; + let threeHourAvrPressure; + + const LOG_FILE = "widbaroalarm.log.json"; + const SETTINGS_FILE = "widbaroalarm.json"; + const storage = require('Storage'); + let settings = Object.assign( + storage.readJSON("widbaroalarm.default.json", true) || {}, + storage.readJSON(SETTINGS_FILE, true) || {} + ); + + function setting(key) { + return settings[key]; + } + const interval = setting("interval"); + + let history3 = storage.readJSON(LOG_FILE, true) || []; // history of recent 3 hours + + function showAlarm(text, value, value2, icon) { + if (text == undefined || value == undefined) return; + const Layout = require("Layout"); + layout = new Layout({ + type: "v", + c: [{ + type: "h", + c: [{ + type: "img", + pad: 0, + src: function() { + return icon || require("heatshrink").decompress(atob("jEY4cA///gH4/++mkK30kiWC4H8x3BGDmSGgYDCgmSoEAg3bsAIDpAIFkmSpMAm3btgIFDQwIGNQpTYkAIJwAHEgMoCA0JgMEyBnBCAW3KoQQDhu3oAIH5JnDBAW24IIBEYm2EYwACBCIACA")); + } + }, + { + type: "txt", + id: "title", + font: "6x8:2", + label: "Alarm", + pad: 2 + }, + ] + }, + { + type: "txt", + id: "text", + font: "6x8:2", + label: text, + pad: 3 + }, + { + type: "txt", + id: "value", + font: "6x8:2", + label: value, + halign: 0, + pad: 3 + }, + { + type: "txt", + id: "value2", + font: "6x8:1", + label: value2 || "", + halign: 0, + pad: 3 + }, + ] + }, { + btns: [{ + label: "OK", + cb: function() { + load(); // close + } + }], + lazy: false + }); + + g.clear(); + layout.render(); + + if (setting("buzz") && + !(storage.readJSON('setting.json', 1) || {}).quiet) { + Bangle.buzz(); + } + } + + let alreadyWarned = false; + + function baroHandler(data) { + if (data === undefined) { + setTimeout(() => Bangle.getPressure().then(baroHandler), 500); + } else { + lastPressure = Math.round(data.pressure); + if (lastPressure == undefined || lastPressure <= 0) return; + + const ts = Math.round(Date.now() / 1000); // seconds + const d = { + "ts": ts, + "p": lastPressure + }; + + // delete entries older than 3h + for (let i = 0; i < history3.length; i++) { + if (history3[i]["ts"] < ts - (3 * 60 * 60)) { + history3.shift(); + } + } + // delete oldest entries until we have max 50 + while (history3.length > 50) { + history3.shift(); + } + + history3.push(d); + // write data to storage + storage.writeJSON(LOG_FILE, history3); + + // we need at least three entries for reliable detection + if (history3.length >= 3) { + + // calculate average of recent three entries + avrPressure = (history3[history3.length - 1]["p"] + history3[history3.length - 2]["p"] + history3[history3.length - 3]["p"]) / 3; + + if (setting("lowalarm") && avrPressure <= setting("min")) { + showAlarm("Pressure low", Math.round(avrPressure) + " hPa"); + alreadyWarned = true; + } + if (setting("highalarm") && avrPressure >= setting("max")) { + showAlarm("Pressure high", Math.round(avrPressure) + " hPa"); + alreadyWarned = true; + } + + if (!alreadyWarned) { + // 3h change detection + const threeHourChange = setting("changeIn3h"); + if (threeHourChange > 0) { + // we need at least 30min of data for reliable detection + if (history3[0]["ts"] > ts - (30 * 60)) { + return; + } + + // Average of oldest three entries + const oldestAvgPressure = (history3[0]["p"] + history3[1]["p"] + history3[2]["p"]) / 3; + if (oldestAvgPressure != undefined && oldestAvgPressure > 0) { + const diff = oldestAvgPressure - avrPressure; + if (Math.abs(diff) > threeHourChange) { + showAlarm("Pressure " + (diff > 0 ? "drop" : "raise"), (Math.round(Math.abs(diff) * 10) / 10) + " hPa/3h", + Math.round(oldestAvgPressure) + " hPa -> " + Math.round(avrPressure) + " hPa"); + } + } + } + } + } + + // calculate 3h average for widget + let sum = 0; + for (let i = 0; i < history3.length; i++) { + sum += history3[i]["p"]; + } + threeHourAvrPressure = sum / history3.length; + } + } + + function check() { + Bangle.getPressure().then(baroHandler); + } + + function reload() { + check(); + } + + function draw() { + g.reset(); + if (setting("show") && lastPressure != undefined) { + g.setFont("6x8", 1).setFontAlign(1, 0); + g.drawString(Math.round(lastPressure), this.x + 24, this.y + 6); + if (threeHourAvrPressure != undefined && threeHourAvrPressure > 0) { + g.drawString(Math.round(threeHourAvrPressure), this.x + 24, this.y + 6 + 10); + } + } + } + + if (global.WIDGETS != undefined && typeof WIDGETS === "object") { + WIDGETS["baroalarm"] = { + width: setting("show") ? 24 : 0, + reload: reload, + area: "tr", + draw: draw + }; + } + + // Let's delay check a bit + setTimeout(function() { + check(); + if (interval > 0) { + setInterval(check, interval * 60000); + } + }, 10000); + +})(); diff --git a/apps/widbaroalarm/widget.png b/apps/widbaroalarm/widget.png new file mode 100644 index 000000000..5be292143 Binary files /dev/null and b/apps/widbaroalarm/widget.png differ diff --git a/apps/widbaroalarm/widget24.png b/apps/widbaroalarm/widget24.png new file mode 100644 index 000000000..2f0d5e4ce Binary files /dev/null and b/apps/widbaroalarm/widget24.png differ