From 6c4d3f41a59d519bc502f6d1a1792c472586398d Mon Sep 17 00:00:00 2001 From: Niko Komin Date: Tue, 29 Aug 2023 11:32:03 -0700 Subject: [PATCH] added new app --- apps/nightwatch/ChangeLog | 1 + apps/nightwatch/README.md | 20 +++ apps/nightwatch/metadata.json | 16 +++ apps/nightwatch/nightwatch.app.info | 6 + apps/nightwatch/nightwatch.app.js | 175 +++++++++++++++++++++++++ apps/nightwatch/nightwatch.icon.js | 1 + apps/nightwatch/nightwatch.icon.png | Bin 0 -> 959 bytes apps/nightwatch/nightwatch.info.js | 6 + apps/nightwatch/nightwatch.settings.js | 25 ++++ apps/nightwatch/screenshot.png | Bin 0 -> 3608 bytes apps/nightwatch/screenshot2.png | Bin 0 -> 3795 bytes 11 files changed, 250 insertions(+) create mode 100644 apps/nightwatch/ChangeLog create mode 100644 apps/nightwatch/README.md create mode 100644 apps/nightwatch/metadata.json create mode 100644 apps/nightwatch/nightwatch.app.info create mode 100644 apps/nightwatch/nightwatch.app.js create mode 100644 apps/nightwatch/nightwatch.icon.js create mode 100644 apps/nightwatch/nightwatch.icon.png create mode 100644 apps/nightwatch/nightwatch.info.js create mode 100644 apps/nightwatch/nightwatch.settings.js create mode 100644 apps/nightwatch/screenshot.png create mode 100644 apps/nightwatch/screenshot2.png diff --git a/apps/nightwatch/ChangeLog b/apps/nightwatch/ChangeLog new file mode 100644 index 000000000..dc179ee9d --- /dev/null +++ b/apps/nightwatch/ChangeLog @@ -0,0 +1 @@ +1.0: first working version of App diff --git a/apps/nightwatch/README.md b/apps/nightwatch/README.md new file mode 100644 index 000000000..6d1749c5d --- /dev/null +++ b/apps/nightwatch/README.md @@ -0,0 +1,20 @@ +# The Nightwatch + +Snuggle into your sleeping bag, hang the Bangle on the tent wall +and check the screen before you fall asleep: + +![](screenshot.png) +![](screenshot2.png) + + +Reads temperature and pressure sensor. Shows current, maximal and minimal values +since the start of the app. Also show a graph of the last 20 measures. + +Swipe left/right between values. + +Screen is updated periodically, time step is configurable in settings. + + +# Creator + +[Niko Komin](https://www.laikaundfreunde.de/niko-komin/) diff --git a/apps/nightwatch/metadata.json b/apps/nightwatch/metadata.json new file mode 100644 index 000000000..90e05214e --- /dev/null +++ b/apps/nightwatch/metadata.json @@ -0,0 +1,16 @@ +{ + "id":"nightwatch", + "readme":"README.md", + "name":"The Nightwatch", + "shortName":"Nightwatch", + "supports" : ["BANGLEJS2"], + "icon":"nightwatch.icon.png", + "screenshots" : [ { "url":"screenshot.png","url":"screenshot2.png" } ], + "version":"1.0", + "description":"Logs sensor readings (currently T and p), show min/max and graph.", + "tags": "tools,outdoors", + "storage": [ + {"name":"nightwatch.app.js","url":"nightwatch.app.js"}, + {"name":"nightwatch.img","url":"nightwatch.icon.js","evaluate":true} + ] +} diff --git a/apps/nightwatch/nightwatch.app.info b/apps/nightwatch/nightwatch.app.info new file mode 100644 index 000000000..36345ce26 --- /dev/null +++ b/apps/nightwatch/nightwatch.app.info @@ -0,0 +1,6 @@ +require("Storage").write("nightwatch.info",{ + "id":"nightwatch", + "name":"nightwatch", + "src":"nightwatch.app.js", + "icon":"nightwatch.icon.png" +}); \ No newline at end of file diff --git a/apps/nightwatch/nightwatch.app.js b/apps/nightwatch/nightwatch.app.js new file mode 100644 index 000000000..035307106 --- /dev/null +++ b/apps/nightwatch/nightwatch.app.js @@ -0,0 +1,175 @@ +// PTLOGGER +// MEASURES p AND T PERIODICALLY AND UPDATES MIN & MAX VALS +// DISPLAYS EITHER OF BOTH + + +var settings = Object.assign({ + dt: 5, //time interval in minutes +}, require('Storage').readJSON("nightwatch.json", true) || {}); + +let dt = settings.dt; +delete settings; + +var timerID; + +const highColor = '#35b779';//#6dcd59; +const lowColor = '#eb005c';//#3d4a89;//#482878; +const normColor = '#000000'; +const historyAmnt = 24; + + +const TData = { + ondisplay:true, + unit: '\xB0C', + accuracy: 1, + value : 100, t_value:'0:00', + values : new Array(historyAmnt), + maxval : -100, t_max:'0:00', + minval : 100, t_min:'0:00' +}; + +const PData = { + ondisplay:false, + unit: 'mbar', + accuracy: 0, + value : 0, t_value:'0:00', + values : new Array(historyAmnt), + maxval : 0, t_max:'0:00', + minval : 10000, t_min:'0:00' +}; + +function minMaxString(val,accuracy,unit,time){ + return time+' '+val.toFixed(accuracy)+unit; +// return val.toFixed(accuracy)+unit+'('+time+')'; +} + +function updateScreen() { + // what are we showing right now? + let data; + if (TData.ondisplay){data = TData;} + else {data = PData;} + + // make strings + let valueString = data.value.toFixed(data.accuracy)+data.unit; + let minString = minMaxString(data.minval, data.accuracy, data.unit, data.t_min); + let maxString = minMaxString(data.maxval, data.accuracy, data.unit, data.t_max); + + // LETS PAINT + g.clear(); + g.setFontAlign(0, 0); + + // MINUM AND MAXIMUM VALUES AND TIMES + g.setFont("Vector:18"); + g.setColor(normColor); + g.drawString(maxString, g.getWidth() / 2, 11); + g.drawString(minString, g.getWidth() / 2, g.getHeight() - 11); + + g.setColor(normColor); + + // TIME OF LAST MEASURE AND SIZE OF INTERVAL + g.setFontAlign(-1, 0); + g.drawString(data.t_value, 0, g.getHeight()/2 - 25); + g.setFontAlign(1, 0); + g.drawString('dt='+dt+'min', g.getWidth() , g.getHeight()/2 - 25); + + //////////////////////////////////////////////////////////// + // GRAPH OF MEASUREMENT HISTORY + g.setFont("Vector:16"); + const graphHeight=35; + const graphWidth=g.getWidth()-30; + const graphLocX = 15; + const graphLocY = g.getHeight() - 16 - 18 - graphHeight; + + // DRAW SOME KIND OF AXES + g.setColor(0.4,0.4,0.4); + g.drawRect(graphLocX,graphLocY,graphLocX+graphWidth,graphLocY+graphHeight); + g.drawLine(graphLocX,graphLocY+graphHeight/2,graphLocX+graphWidth,graphLocY+graphHeight/2); + g.drawLine(graphLocX+graphWidth/2,graphLocY,graphLocX+graphWidth/2,graphLocY+graphHeight); + g.drawLine(graphLocX+graphWidth/4,graphLocY,graphLocX+graphWidth/4,graphLocY+graphHeight); + g.drawLine(graphLocX+3*graphWidth/4,graphLocY,graphLocX+3*graphWidth/4,graphLocY+graphHeight); + g.setColor(normColor); + + // DRAW LINE + require("graph").drawLine(g, data.values, { + x:graphLocX, + y:graphLocY, + width:graphWidth, + height:graphHeight + }); + + let graphMax=Math.max.apply(Math,data.values); + let graphMin=Math.min.apply(Math,data.values); + g.setFontAlign(0, 0); + g.setColor(highColor); + g.drawString(graphMax.toFixed(data.accuracy), g.getWidth()/2, g.getHeight() - 16 - 18 - graphHeight); + g.setColor(lowColor); + g.drawString(graphMin.toFixed(data.accuracy), g.getWidth()/2, g.getHeight() - 16 - 18); + g.setColor(normColor); + + let historyLength = (historyAmnt*dt >= 60)?('-'+historyAmnt*dt/60+'h'):('-'+historyAmnt*dt+'"'); + + g.drawString(historyLength,25, g.getHeight() - 16 - 18 - graphHeight/2); + + //////////////////////////////////////////////////////////// + // LAST MEASURE + g.setFontAlign(0, 0); + g.setFont('Vector:36'); + g.drawString(valueString, g.getWidth() / 2, g.getHeight() / 2); + + data.ondisplay = true; +} + +function updateMinMax( data, currentValue ){ + data.values.push(currentValue); + data.values.shift(); + data.value=currentValue; + + let now = new Date(); + data.t_value = now.getHours()+':'+String(now.getMinutes()).padStart(2, '0'); + if (currentValue < data.minval){data.t_min=data.t_value;data.minval = currentValue;} + if (currentValue > data.maxval){data.t_max=data.t_value;data.maxval = currentValue;} +} + +function switchDisplay(){ + if (TData.ondisplay) {TData.ondisplay=false;PData.ondisplay=true;updateScreen();} + else {PData.ondisplay=false;TData.ondisplay=true;updateScreen();} +} + +function settingsPage(){ + Bangle.on('swipe',function (){}); + eval(require("Storage").read("nightwatch.settings.js"))(()=>load()); + Bangle.on('swipe',switchDisplay); + console.log(3); +} + +function handlePressureSensorReading(data) { + updateMinMax(TData,data.temperature); + updateMinMax(PData,data.pressure); +} + +function startup(){ + // testing in emulator + // handlePressureSensorReading({ "temperature": 28.64251302083, "pressure": 1004.66520303803, "altitude": 71.72072902749 }); + // updateScreen(); + + // ON STARTUP: + // fill current reading into data, + // before `updateMinMax` uses it + Bangle.getPressure().then(d=>{TData.value=d.temperature; + TData.values.fill(d.temperature); + PData.value=d.pressure; + PData.values.fill(d.pressure); + handlePressureSensorReading(d); + updateScreen();}); + Bangle.on('swipe',switchDisplay); + + //Bangle.on('tap',settingsPage); + + timerID = setInterval( function() { + Bangle.getPressure().then(d=>{handlePressureSensorReading(d);updateScreen();}); + }, dt * 60000); + +} + +startup(); + diff --git a/apps/nightwatch/nightwatch.icon.js b/apps/nightwatch/nightwatch.icon.js new file mode 100644 index 000000000..19b4623f0 --- /dev/null +++ b/apps/nightwatch/nightwatch.icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4MA///ospETUQAgc//gFDv4FF/wFP/4FF/5PCgIFChF/AoWA/1/+YFBx/+g4EBAAPAFAIEBEgUDBQYAN/E/AgQvDDoXHDocH4wFDgf8v4RCDooAMj/4AoZcBcM8DOAgFFgJSDAqQAhA==")) diff --git a/apps/nightwatch/nightwatch.icon.png b/apps/nightwatch/nightwatch.icon.png new file mode 100644 index 0000000000000000000000000000000000000000..bf3a3282a2f655eba40d0441711ed16c1a809216 GIT binary patch literal 959 zcmV;w13>(VP)Vs(=D#7dY15`A0EmtJSi%|rFP2^IAo;3<%`y2-Hk*Aq zG!sUyF!8R5|M?Pe+la$PZjrFZh~N6J71vcs&xB^di2Y>&4!^*<7p##pOHJN4!)R1B)doOs4)O$NzqyfyNz7`VAe=@cZeeUMZzwUi*mW#r1DS9 zWKx|*+;|)TR!F1X!6H@yJ`j1pi0hAtC;pT)ITR=49*M_IygAf2WyC27+l^cXV6JzO z#|~Z(M64q@FD5^9K_>q3KlfiX;aeY)Q4~EpKy_DF*HdQEeDBd-oJrud3bOk3j|pE% zSPdW@D#1Mq$=8kCD)CG`0v2&Qc^0|VpWV~b^F*i>UN~maLf`V@R{tpCiim%!`)1iE z@_Vl}16KJ^^ehOCU~XsU_)I4AT@gkV>VVl=Pm2kIZb2Tj!zf z&Ci%I(SYR=HjsQJG#WFbx7)JR=u7N{MGG=O-j~l737?mBxw}4hYMSGf;Dg1)ze`%} z*}`CZM9?rr%dhL6_hvRz#I=&(Kl6O=uLX(URaVeASl6A9_-WZ7>h)UiYo0PjTsHAm zQWwP0k%)+ap1KKeuO#tEd0UitzQp4!>fe!JMBH+rp#gMwak`@%Se)*m^hW88KJtCn z2F?py@|wv+Q52ehjwp(z4`%vGku)yX-Tji!hWdpzn#pFfQ~f;1Q^v^m-K`ew@6U+^ zCVNGm^NuC*hh!Fz8x2_LE;x?UXxQ;e9H+DH5Z%X!{KkV8d7&^H@RNjXB+V>(Q{tBe z-{Y+pzb4k5{KCbMT>Nq>^+H2Lv}x036t~xOIz6TTwVCBYDmLfxlF({o{C7c7z##u}Y6&|0giXZnp;a+*uaWygt3vWEKRgbtN-me1 h)GWWa+O!$1+y}#On>2C^{7nD=002ovPDHLkV1lA$%cuYV literal 0 HcmV?d00001 diff --git a/apps/nightwatch/nightwatch.info.js b/apps/nightwatch/nightwatch.info.js new file mode 100644 index 000000000..ccbc8909a --- /dev/null +++ b/apps/nightwatch/nightwatch.info.js @@ -0,0 +1,6 @@ +require("Storage").write("nightwatch.info",{ + "id":"nightwatch", + "name":"The Nightwatch", + "src":"nightwatch.app.js", + "icon":"nightwatch.icon.png" +}); diff --git a/apps/nightwatch/nightwatch.settings.js b/apps/nightwatch/nightwatch.settings.js new file mode 100644 index 000000000..c543b7343 --- /dev/null +++ b/apps/nightwatch/nightwatch.settings.js @@ -0,0 +1,25 @@ +(function(back) { + var FILE = "nightwatch.json"; + // Load settings + var settings = Object.assign({ + dt: 30, + }, require('Storage').readJSON(FILE, true) || {}); + + function writeSettings() { + require('Storage').writeJSON(FILE, settings); + } + + // Show the menu + E.showMenu({ + "" : { "title" : "nightwatch" }, + "< Back" : () => back(), + 'log freq (min)': { + value: 0|settings.dt, // 0| converts undefined to 0 + min: 1, max: 60, + onchange: v => { + settings.dt = v; + writeSettings(); + } + }, + }); +}); diff --git a/apps/nightwatch/screenshot.png b/apps/nightwatch/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..194c91b28c327b6ca06d06515b1dd81197411ed9 GIT binary patch literal 3608 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 literal 0 HcmV?d00001