diff --git a/apps/bthrv/ChangeLog b/apps/bthrv/ChangeLog index e144fd8f9..eefadac78 100644 --- a/apps/bthrv/ChangeLog +++ b/apps/bthrv/ChangeLog @@ -1,2 +1,3 @@ 0.01: New App! 0.02: Write available data on reset or kill +0.03: Buzz short on every finished measurement and longer if all are done diff --git a/apps/bthrv/app.js b/apps/bthrv/app.js index 067c84f56..fbd0e2d05 100644 --- a/apps/bthrv/app.js +++ b/apps/bthrv/app.js @@ -75,7 +75,6 @@ function write(){ data += "," + rrMax + "," + rrMin + ","+rrNumberOfValues; data += "\n"; file.write(data); - Bangle.buzz(500); } function onBtHrm(e) { @@ -87,6 +86,11 @@ function onBtHrm(e) { if (currentSlot <= hrvSlots.length && (Date.now() - startingTime) > (hrvSlots[currentSlot] * 1000) && !hrvValues[hrvSlots[currentSlot]]){ hrvValues[hrvSlots[currentSlot]] = hrv; currentSlot++; + if (currentSlot == hrvSlots.length){ + Bangle.buzz(500) + } else { + Bangle.buzz(50); + } } } diff --git a/apps/bthrv/metadata.json b/apps/bthrv/metadata.json index 183008034..7c57be682 100644 --- a/apps/bthrv/metadata.json +++ b/apps/bthrv/metadata.json @@ -2,7 +2,7 @@ "id": "bthrv", "name": "Bluetooth Heart Rate variance calculator", "shortName": "BT HRV", - "version": "0.02", + "version": "0.03", "description": "Calculates HRV from a a BT HRM with interval data", "icon": "app.png", "type": "app", diff --git a/apps/circlesclock/ChangeLog b/apps/circlesclock/ChangeLog index 7165f8521..46eddd32b 100644 --- a/apps/circlesclock/ChangeLog +++ b/apps/circlesclock/ChangeLog @@ -20,3 +20,6 @@ Color depending on value (green -> red, red -> green) option Good HRM value will not be overwritten so fast anymore 0.10: Use roboto font for time, date and day of week and center align them +0.11: New color option: foreground color + Improve performance, reduce memory usage + Small optical adjustments diff --git a/apps/circlesclock/app.js b/apps/circlesclock/app.js index 903c7bdb2..a6aa1a8b1 100644 --- a/apps/circlesclock/app.js +++ b/apps/circlesclock/app.js @@ -3,23 +3,8 @@ const storage = require("Storage"); const SunCalc = require("https://raw.githubusercontent.com/mourner/suncalc/master/suncalc.js"); const shoesIcon = atob("EBCBAAAACAAcAB4AHgAeABwwADgGeAZ4AHgAMAAAAHAAIAAA"); -const heartIcon = atob("EBCBAAAAAAAeeD/8P/x//n/+P/w//B/4D/AH4APAAYAAAAAA"); -const powerIcon = atob("EBCBAAAAA8ADwA/wD/AP8A/wD/AP8A/wD/AP8A/wD/AH4AAA"); const temperatureIcon = atob("EBCBAAAAAYADwAJAAkADwAPAA8ADwAfgB+AH4AfgA8ABgAAA"); -const weatherCloudy = atob("EBCBAAAAAAAAAAfgD/Af8H/4//7///////9//z/+AAAAAAAA"); -const weatherSunny = atob("EBCBAAAAAYAQCBAIA8AH4A/wb/YP8A/gB+ARiBAIAYABgAAA"); -const weatherMoon = atob("EBCBAAAAAYAP8B/4P/w//D/8f/5//j/8P/w//B/4D/ABgAAA"); -const weatherPartlyCloudy = atob("EBCBAAAAAAAYQAMAD8AIQBhoW+AOYBwwOBBgHGAGP/wf+AAA"); -const weatherRainy = atob("EBCBAAAAAYAH4AwwOBBgGEAOQAJBgjPOEkgGYAZgA8ABgAAA"); -const weatherPartlyRainy = atob("EBCBAAAAEEAQAAeADMAYaFvoTmAMMDgQIBxhhiGGG9wDwAGA"); -const weatherSnowy = atob("EBCBAAAAAAADwAGAEYg73C50BCAEIC50O9wRiAGAA8AAAAAA"); -const weatherFoggy = atob("EBCBAAAAAAADwAZgDDA4EGAcQAZAAgAAf74AAAAAd/4AAAAA"); -const weatherStormy = atob("EBCBAAAAAYAH4AwwOBBgGEAOQMJAgjmOGcgAgACAAAAAAAAA"); - -const sunSetDown = atob("EBCBAAAAAAABgAAAAAATyAZoBCB//gAAAAAGYAPAAYAAAAAA"); -const sunSetUp = atob("EBCBAAAAAAABgAAAAAATyAZoBCB//gAAAAABgAPABmAAAAAA"); - Graphics.prototype.setFontRobotoRegular50NumericOnly = function(scale) { // Actual height 39 (40 - 2) this.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAB8AAAAAAAfAAAAAAAPwAAAAAAB8AAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAA4AAAAAAB+AAAAAAD/gAAAAAD/4AAAAAH/4AAAAAP/wAAAAAP/gAAAAAf/gAAAAAf/AAAAAA/+AAAAAB/+AAAAAB/8AAAAAD/4AAAAAH/4AAAAAD/wAAAAAA/wAAAAAAPgAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///wAAAB////gAAA////8AAA/////gAAP////8AAH8AAA/gAB8AAAD4AA+AAAAfAAPAAAADwADwAAAA8AA8AAAAPAAPAAAADwADwAAAA8AA8AAAAPAAPgAAAHwAB8AAAD4AAfwAAD+AAD/////AAA/////wAAH////4AAAf///4AAAB///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAPgAAAAAADwAAAAAAB8AAAAAAAfAAAAAAAHgAAAAAAD4AAAAAAA+AAAAAAAPAAAAAAAH/////wAB/////8AA//////AAP/////wAD/////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAAAfgAADwAAP4AAB8AAH+AAA/AAD/gAAfwAB/AAAf8AAfAAAP/AAPgAAH7wAD4AAD88AA8AAB+PAAPAAA/DwADwAAfg8AA8AAPwPAAPAAH4DwADwAH8A8AA+AD+APAAPwB/ADwAB/D/gA8AAf//gAPAAD//wADwAAf/wAA8AAD/4AAPAAAHwAADwAAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAADgAAAHwAA+AAAD8AAP4AAB/AAD/AAA/wAA/wAAf4AAD+AAHwAAAPgAD4APAB8AA+ADwAPAAPAA8ADwADwAPAA8AA8ADwAPAAPAA8ADwADwAfAA8AA8AH4APAAPgD+AHwAB8B/wD4AAf7/+B+AAD//v//AAA//x//wAAD/4P/4AAAf8B/4AAAAYAH4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAAAAAAHwAAAAAAH8AAAAAAD/AAAAAAD/wAAAAAD/8AAAAAB/vAAAAAB/jwAAAAA/g8AAAAA/wPAAAAAfwDwAAAAf4A8AAAAf4APAAAAP8ADwAAAP8AA8AAAH8AAPAAAD/////8AA//////AAP/////wAD/////8AA//////AAAAAAPAAAAAAADwAAAAAAA8AAAAAAAPAAAAAAADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8AAAAB/APwAAH//wD+AAD//8A/wAA///AH+AAP//wAPgAD/B4AB8AA8A+AAfAAPAPAADwADwDwAA8AA8A8AAPAAPAPAADwADwD4AA8AA8A+AAPAAPAPwAHwADwD8AD4AA8AfwD+AAPAH///AADwA///wAA8AH//4AAPAAf/4AAAAAB/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//AAAAAD//+AAAAD///4AAAD////AAAB////4AAA/78D/AAAfw8AH4AAPweAA+AAD4PgAHwAB8DwAA8AAfA8AAPAAHgPAADwAD4DwAA8AA+A8AAPAAPAPgAHwADwD4AB8AA8AfgA+AAPAH+B/gAAAA///wAAAAH//4AAAAA//8AAAAAH/8AAAAAAP4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAAAAAAA8AAAAAAAPAAAAAAADwAAAAAAA8AAAABAAPAAAABwADwAAAB8AA8AAAB/AAPAAAB/wADwAAD/8AA8AAD/8AAPAAD/4AADwAD/4AAA8AD/4AAAPAH/wAAADwH/wAAAA8H/wAAAAPH/wAAAAD3/gAAAAA//gAAAAAP/gAAAAAD/gAAAAAA/AAAAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwA/4AAAH/Af/AAAH/8P/4AAD//n//AAA//7//4AAfx/+A+AAHwD+AHwAD4AfgB8AA8AHwAPAAPAA8ADwADwAPAA8AA8ADwAPAAPAA8ADwADwAfAA8AA+AH4AfAAHwD+AHwAB/D/4D4AAP/+/n+AAD//n//AAAf/w//gAAB/wH/wAAAHwA/4AAAAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+AAAAAAD/8AAAAAD//wAAAAB//+AAAAA///wAAAAf4H+APAAH4AfgDwAD8AB8A8AA+AAfAPAAPAADwDwADwAA8B8AA8AAPAfAAPAADwHgADwAA8D4AA+AAeB+AAHwAHg/AAB+ADwfgAAP8D4/4AAD////8AAAf///8AAAB///+AAAAP//+AAAAAP/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAOAAAB8AAHwAAAfgAD8AAAH4AA/AAAB8AAHwAAAOAAA4AAAAAAAAAAAAAAAAAAAAAAAAAA"), 46, atob("DRUcHBwcHBwcHBwcDA=="), 50+(scale<<8)+(1<<16)); @@ -67,7 +52,7 @@ const colorGreen = '#008000'; const colorBlue = '#0000ff'; const colorYellow = '#ffff00'; const widgetOffset = showWidgets ? 24 : 0; -const dowOffset = circleCount == 3 ? 22 : 24; // dow offset relative to date +const dowOffset = circleCount == 3 ? 20 : 22; // dow offset relative to date const h = g.getHeight() - widgetOffset; const w = g.getWidth(); const hOffset = (circleCount == 3 ? 34 : 30) - widgetOffset; @@ -103,21 +88,24 @@ const circleFontBig = circleCount == 3 ? "Vector:16" : "Vector:12"; const iconOffset = circleCount == 3 ? 6 : 8; const defaultCircleTypes = ["steps", "hr", "battery", "weather"]; +function hideWidgets() { + /* + * we are not drawing the widgets as we are taking over the whole screen + * so we will blank out the draw() functions of each widget and change the + * area to the top bar doesn't get cleared. + */ + if (WIDGETS && typeof WIDGETS === "object") { + for (let wd of WIDGETS) { + wd.draw = () => {}; + wd.area = ""; + } + } +} function draw() { g.clear(true); if (!showWidgets) { - /* - * we are not drawing the widgets as we are taking over the whole screen - * so we will blank out the draw() functions of each widget and change the - * area to the top bar doesn't get cleared. - */ - if (WIDGETS && typeof WIDGETS === "object") { - for (let wd of WIDGETS) { - wd.draw = () => {}; - wd.area = ""; - } - } + hideWidgets(); } else { Bangle.drawWidgets(); } @@ -129,7 +117,7 @@ function draw() { g.setFontRobotoRegular50NumericOnly(); g.setFontAlign(0, -1); g.setColor(colorFg); - g.drawString(locale.time(new Date(), 1), w / 2, h1 + 8); + g.drawString(locale.time(new Date(), 1), w / 2, h1 + 6); now = Math.round(new Date().getTime() / 1000); // date & dow @@ -138,10 +126,19 @@ function draw() { g.drawString(locale.date(new Date()), w / 2, h2); g.drawString(locale.dow(new Date()), w / 2, h2 + dowOffset); - drawCircle(1); - drawCircle(2); - drawCircle(3); - if (circleCount >= 4) drawCircle(4); + // draw the circles a little bit delayed so we decrease the blocking time + setTimeout(function() { + drawCircle(1); + }, 1); + setTimeout(function() { + drawCircle(2); + }, 1); + setTimeout(function() { + drawCircle(3); + }, 1); + setTimeout(function() { + if (circleCount >= 4) drawCircle(4); + }, 1); } function drawCircle(index) { @@ -248,6 +245,9 @@ function getGradientColor(color, percent) { const colorList = [ '#00FF00', '#80FF00', '#FFFF00', '#FF8000', '#FF0000' ]; + if (color == "fg") { + color = colorFg; + } if (color == "green-red") { const colorIndex = Math.round(colorList.length * percent); return colorList[Math.min(colorIndex, colorList.length) - 1] || "#00ff00"; @@ -325,6 +325,8 @@ function drawStepsDistance(w) { function drawHeartRate(w) { if (!w) w = getCircleXPosition("hr"); + const heartIcon = atob("EBCBAAAAAAAeeD/8P/x//n/+P/w//B/4D/AH4APAAYAAAAAA"); + drawCircleBackground(w); const color = getCircleColor("hr"); @@ -349,6 +351,8 @@ function drawBattery(w) { if (!w) w = getCircleXPosition("battery"); const battery = E.getBattery(); + const powerIcon = atob("EBCBAAAAA8ADwA/wD/AP8A/wD/AP8A/wD/AP8A/wD/AH4AAA"); + drawCircleBackground(w); let color = getCircleColor("battery"); @@ -426,6 +430,10 @@ function drawSunProgress(w) { if (!w) w = getCircleXPosition("sunprogress"); const percent = getSunProgress(); + // sunset icons: + const sunSetDown = atob("EBCBAAAAAAABgAAAAAATyAZoBCB//gAAAAAGYAPAAYAAAAAA"); + const sunSetUp = atob("EBCBAAAAAAABgAAAAAATyAZoBCB//gAAAAABgAPABmAAAAAA"); + drawCircleBackground(w); const color = getCircleColor("sunprogress"); @@ -559,6 +567,18 @@ function windAsBeaufort(windInKmh) { */ function getWeatherIconByCode(code) { const codeGroup = Math.round(code / 100); + + // weather icons: + const weatherCloudy = atob("EBCBAAAAAAAAAAfgD/Af8H/4//7///////9//z/+AAAAAAAA"); + const weatherSunny = atob("EBCBAAAAAYAQCBAIA8AH4A/wb/YP8A/gB+ARiBAIAYABgAAA"); + const weatherMoon = atob("EBCBAAAAAYAP8B/4P/w//D/8f/5//j/8P/w//B/4D/ABgAAA"); + const weatherPartlyCloudy = atob("EBCBAAAAAAAYQAMAD8AIQBhoW+AOYBwwOBBgHGAGP/wf+AAA"); + const weatherRainy = atob("EBCBAAAAAYAH4AwwOBBgGEAOQAJBgjPOEkgGYAZgA8ABgAAA"); + const weatherPartlyRainy = atob("EBCBAAAAEEAQAAeADMAYaFvoTmAMMDgQIBxhhiGGG9wDwAGA"); + const weatherSnowy = atob("EBCBAAAAAAADwAGAEYg73C50BCAEIC50O9wRiAGAA8AAAAAA"); + const weatherFoggy = atob("EBCBAAAAAAADwAZgDDA4EGAcQAZAAgAAf74AAAAAd/4AAAAA"); + const weatherStormy = atob("EBCBAAAAAYAH4AwwOBBgGEAOQMJAgjmOGcgAgACAAAAAAAAA"); + switch (codeGroup) { case 2: return weatherStormy; @@ -823,7 +843,6 @@ if (isCircleEnabled("hr")) { enableHRMSensor(); } - Bangle.setUI("clock"); Bangle.loadWidgets(); diff --git a/apps/circlesclock/metadata.json b/apps/circlesclock/metadata.json index 3279ec2cf..b16f14c06 100644 --- a/apps/circlesclock/metadata.json +++ b/apps/circlesclock/metadata.json @@ -1,7 +1,7 @@ { "id": "circlesclock", "name": "Circles clock", "shortName":"Circles clock", - "version":"0.10", + "version":"0.11", "description": "A clock with three or four circles for different data at the bottom in a probably familiar style", "icon": "app.png", "screenshots": [{"url":"screenshot-dark.png"}, {"url":"screenshot-light.png"}, {"url":"screenshot-dark-4.png"}, {"url":"screenshot-light-4.png"}], diff --git a/apps/circlesclock/settings.js b/apps/circlesclock/settings.js index bec539376..0b9e94aca 100644 --- a/apps/circlesclock/settings.js +++ b/apps/circlesclock/settings.js @@ -14,8 +14,10 @@ const valuesCircleTypes = ["empty", "steps", "stepsDist", "hr", "battery", "weather", "sunprogress", "temperature", "pressure", "altitude"]; const namesCircleTypes = ["empty", "steps", "distance", "heart", "battery", "weather", "sun", "temperature", "pressure", "altitude"]; - const valuesColors = ["", "#ff0000", "#00ff00", "#0000ff", "#ffff00", "#ff00ff", "#00ffff", "#fff", "#000", "green-red", "red-green"]; - const namesColors = ["default", "red", "green", "blue", "yellow", "magenta", "cyan", "white", "black", "green->red", "red->green"]; + const valuesColors = ["", "#ff0000", "#00ff00", "#0000ff", "#ffff00", "#ff00ff", + "#00ffff", "#fff", "#000", "green-red", "red-green", "fg"]; + const namesColors = ["default", "red", "green", "blue", "yellow", "magenta", + "cyan", "white", "black", "green->red", "red->green", "foreground"]; const weatherData = ["empty", "humidity", "wind"]; diff --git a/apps/contourclock/ChangeLog b/apps/contourclock/ChangeLog index 9c62e637b..032edc9b5 100644 --- a/apps/contourclock/ChangeLog +++ b/apps/contourclock/ChangeLog @@ -5,3 +5,4 @@ 0.23: Customizer! Unused fonts no longer take up precious memory. 0.24: Added previews to the customizer. 0.25: Fixed a bug that would let widgets change the color of the clock. +0.26: Time formatted to locale diff --git a/apps/contourclock/lib.js b/apps/contourclock/lib.js index 65a4622f4..c4f927953 100644 --- a/apps/contourclock/lib.js +++ b/apps/contourclock/lib.js @@ -1,3 +1,11 @@ +var is12; +function getHours(d) { + var h = d.getHours(); + if (is12===undefined) is12 = (require('Storage').readJSON('setting.json',1)||{})["12hour"]; + if (!is12) return h; + return (h%12==0) ? 12 : h%12; +} + exports.drawClock = function(fontIndex) { var digits = []; fontFile=require("Storage").read("contourclock-"+Math.abs(parseInt(fontIndex+0.5))+".json"); @@ -15,8 +23,8 @@ exports.drawClock = function(fontIndex) { var y = g.getHeight()/2-digits[0].height/2; var date = new Date(); g.clearRect(0,38,g.getWidth()-1,138); - d1=parseInt(date.getHours()/10); - d2=parseInt(date.getHours()%10); + d1=parseInt(getHours(date)/10); + d2=parseInt(getHours(date)%10); d3=10; d4=parseInt(date.getMinutes()/10); d5=parseInt(date.getMinutes()%10); diff --git a/apps/contourclock/metadata.json b/apps/contourclock/metadata.json index 54d799127..ec29c390b 100644 --- a/apps/contourclock/metadata.json +++ b/apps/contourclock/metadata.json @@ -1,7 +1,7 @@ { "id": "contourclock", "name": "Contour Clock", "shortName" : "Contour Clock", - "version":"0.25", + "version":"0.26", "icon": "app.png", "description": "A Minimalist clockface with large Digits. Now with more fonts!", "screenshots" : [{"url":"cc-screenshot-1.png"},{"url":"cc-screenshot-2.png"}], diff --git a/apps/daisy/ChangeLog b/apps/daisy/ChangeLog index 4fdf333e4..254124d4e 100644 --- a/apps/daisy/ChangeLog +++ b/apps/daisy/ChangeLog @@ -1,2 +1,3 @@ 0.01: first release 0.02: added settings menu to change color +0.03: fix metadata.json to allow setting as clock diff --git a/apps/daisy/metadata.json b/apps/daisy/metadata.json index c35bfefa3..f4a9fcbbe 100644 --- a/apps/daisy/metadata.json +++ b/apps/daisy/metadata.json @@ -1,9 +1,10 @@ { "id": "daisy", "name": "Daisy", - "version":"0.02", + "version":"0.03", "dependencies": {"mylocation":"app"}, "description": "A clock based on the Pastel clock with large ring guage for steps", "icon": "app.png", + "type": "clock", "tags": "clock", "supports" : ["BANGLEJS2"], "screenshots": [{"url":"screenshot_daisy2.jpg"}], diff --git a/apps/edisonsball/ChangeLog b/apps/edisonsball/ChangeLog new file mode 100644 index 000000000..b71b8bb0d --- /dev/null +++ b/apps/edisonsball/ChangeLog @@ -0,0 +1,2 @@ +0.01: Initial version +0.02: Added BangleJS Two diff --git a/apps/edisonsball/README.md b/apps/edisonsball/README.md index b8e9ec106..a3b013b6d 100644 --- a/apps/edisonsball/README.md +++ b/apps/edisonsball/README.md @@ -1,4 +1,4 @@ -The application is based on a technique that Thomas Edison used to prevent falling asleep using a steel ball. Essentially the app starts with a display that shows the current HR value that the watch alarm is set to and this can be adjusted with buttons 1 and 3. This HR settng should be the approximate value you want the alarm to trigger and so you should ideally know both what your HR is currently and what your heartrate normally is during sleep. For your current HR according to the watch, you can simply use the HR monitor available in the Espruino app loader, and then from that you can choose a lower value as the target for the alarm and adjust as required. +The application is based on a technique that Thomas Edison used to prevent falling asleep using a steel ball. Essentially the app starts with a display that shows the current HR value that the watch alarm is set to and this can be adjusted with buttons 1 and 3 (Mapped to top touch and bottom touch on Bangle 2). This HR settng should be the approximate value you want the alarm to trigger and so you should ideally know both what your HR is currently and what your heartrate normally is during sleep. For your current HR according to the watch, you can simply use the HR monitor available in the Espruino app loader, and then from that you can choose a lower value as the target for the alarm and adjust as required. When you press the middle button on the side, the HR monitor starts, the alarm will trigger when your heart rate average drops to the limit you’ve set and has a certain level of steadiness that is determined by a assessing the variance over several readings - the sensitivity of this variance can be adjusted in a variable in the app's code under 'ADVANCED SETTINGS' if needed. The code also has a basic logging function which shows, in a CSV file, when you started the HR tracker and when the alarm was triggered. diff --git a/apps/edisonsball/app.js b/apps/edisonsball/app.js index 557155c9a..2aa317829 100644 --- a/apps/edisonsball/app.js +++ b/apps/edisonsball/app.js @@ -3,6 +3,8 @@ var lower_limit_BPM = 49; var upper_limit_BPM = 140; var deviation_threshold = 3; +var ISBANGLEJS1 = process.env.HWVERSION==1; + var target_heartrate = 70; var heartrate_set; @@ -33,25 +35,39 @@ function btn2Pressed() { } function update_target_HR(){ - g.clear(); - g.setColor("#00ff7f"); - g.setFont("6x8", 4); - g.setFontAlign(0,0); // center font + if (process.env.HWVERSION==1) { + g.setColor("#00ff7f"); + g.setFont("6x8", 4); + g.setFontAlign(0,0); // center font - g.drawString(target_heartrate, 120,120); - g.setFont("6x8", 2); - g.setFontAlign(-1,-1); - g.drawString("-", 220, 200); - g.drawString("+", 220, 40); - g.drawString("GO", 210, 120); - - g.setColor("#ffffff"); - g.setFontAlign(0,0); // center font - g.drawString("target HR", 120,90); - - g.setFont("6x8", 1); - g.drawString("if unsure, start with 7-10%\n less than waking average and\n adjust as required", 120,170); + g.drawString(target_heartrate, 120,120); + g.setFont("6x8", 2); + g.setFontAlign(-1,-1); + g.drawString("-", 220, 200); + g.drawString("+", 220, 40); + g.drawString("GO", 210, 120); + + g.setColor("#ffffff"); + g.setFontAlign(0,0); // center font + g.drawString("target HR", 120,90); + + g.setFont("6x8", 1); + g.drawString("if unsure, start with 7-10%\n less than waking average and\n adjust as required", 120,170); + } else { + g.setFont("6x8", 4); + g.setFontAlign(0,0); // center font + g.drawString(target_heartrate, 88,88); + g.setFont("6x8", 2); + g.setFontAlign(-1,-1); + g.drawString("-", 160, 160); + g.drawString("+", 160, 10); + g.drawString("GO", 150, 88); + g.setFontAlign(0,0); // center font + g.drawString("target HR", 88,65); + g.setFont("6x8", 1); + g.drawString("if unsure, start with 7-10%\n less than waking average and\n adjust as required", 88,150); + } g.setFont("6x8",3); g.flip(); @@ -105,8 +121,13 @@ function checkHR() { average_HR = average(HR_samples).toFixed(0); stdev_HR = getStandardDeviation (HR_samples).toFixed(1); - g.drawString("HR: " + average_HR, 120,100); - g.drawString("STDEV: " + stdev_HR, 120,160); + if (ISBANGLEJS1) { + g.drawString("HR: " + average_HR, 120,100); + g.drawString("STDEV: " + stdev_HR, 120,160); + } else { + g.drawString("HR: " + average_HR, 88,60); + g.drawString("STDEV: " + stdev_HR, 88,90); + } HR_samples = []; if(average_HR < target_heartrate && stdev_HR < deviation_threshold){ @@ -131,12 +152,26 @@ function checkHR() { update_target_HR(); -setWatch(btn1Pressed, BTN1, {repeat:true}); -setWatch(btn2Pressed, BTN2, {repeat:true}); -setWatch(btn3Pressed, BTN3, {repeat:true}); +if (ISBANGLEJS1) { + // Bangle 1 + setWatch(btn1Pressed, BTN1, {repeat:true}); + setWatch(btn2Pressed, BTN2, {repeat:true}); + setWatch(btn3Pressed, BTN3, {repeat:true}); +} else { + // Bangle 2 + setWatch(btn2Pressed, BTN1, { repeat: true }); + Bangle.on('touch', function(zone, e) { + if (e.y < g.getHeight() / 2) { + btn1Pressed(); + } + if (e.y > g.getHeight() / 2) { + btn3Pressed(); + } + }); +} + Bangle.on('HRM',function(hrm) { - if(trigger_count < 2){ if (firstBPM) firstBPM=false; // ignore the first one as it's usually rubbish diff --git a/apps/edisonsball/metadata.json b/apps/edisonsball/metadata.json index f429c7b67..dfeb4451e 100644 --- a/apps/edisonsball/metadata.json +++ b/apps/edisonsball/metadata.json @@ -2,11 +2,11 @@ "id": "edisonsball", "name": "Edison's Ball", "shortName": "Edison's Ball", - "version": "0.01", + "version": "0.02", "description": "Hypnagogia/Micro-Sleep alarm for experimental use in exploring sleep transition and combating drowsiness", "icon": "app-icon.png", - "tags": "", - "supports": ["BANGLEJS"], + "tags": "sleep,hyponagogia,quick,nap", + "supports": ["BANGLEJS", "BANGLEJS2"], "readme": "README.md", "storage": [ {"name":"edisonsball.app.js","url":"app.js"}, diff --git a/apps/rolex/ChangeLog b/apps/rolex/ChangeLog index 3bfed4a4e..a1e2cee0a 100644 --- a/apps/rolex/ChangeLog +++ b/apps/rolex/ChangeLog @@ -1,3 +1,4 @@ 0.01: Initial Release 0.02: Minor tweaks for light theme -0.03: Made images 2 bit and fixed theme honoring \ No newline at end of file +0.03: Made images 2 bit and fixed theme honoring +0.04: Fixed date font alignment and changed date font to match a real Rolex diff --git a/apps/rolex/README.md b/apps/rolex/README.md index 4c24aad0c..5339f1d0e 100644 --- a/apps/rolex/README.md +++ b/apps/rolex/README.md @@ -1,11 +1,13 @@ # Rolex -![](screenshot.png) +![](screenshot.png) ![](screenshot1.png) Created with the aid of the Espruino documentation and looking through many of the wonderful exising watchfaces that have been made. This has not been tested on a watch yet as I haven't aquired one but has been tested in the emulator. The hands don't rotate dead on center but they're as close as I could get them to. +Colour switches based on watch theme so if you want a white on black change your watch theme to dark, and for black on white change it to light. + Special thanks to: * rozek (for his updated widget draw code for utilization with background images) * Gordon Williams (Bangle.js, watchapps for reference code and documentation) diff --git a/apps/rolex/app.js b/apps/rolex/app.js index e409704fc..124cb6fee 100644 --- a/apps/rolex/app.js +++ b/apps/rolex/app.js @@ -28,6 +28,14 @@ var imgSec = { buffer : E.toArrayBuffer(atob("v/q//r/+v/qv+q/qq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qv+v/6/D/wD8PDw8PwD/w///6v+qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqr+r//v///X/1X/Vf9V/1X/1///+//q/qq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq6qrqg==")) }; +/* use font closer to Rolex */ + +Graphics.prototype.setFontRolexFont = function(scale) { + // Actual height 12 (12 - 1) + this.setFontCustom(atob("AAAABAACAAAAAYAHgA4AOABgAAAAA/gD/gMBgQBAwGA/4A/gAAAAAAIBAQCB/8D/4AAQAAAAAAAAAwEDAYEBQIEgYxA/CA4MAAAAAAAAgIBAhCBCEDOYHvgCOAAAAAAABgANAAyAHEAf/A/+AAgABAAAAAAADBAcCBsECYIEYgIeAAAAAAAHwAfwB5wGggZBAjGBH4CDgAAACAAYAAgABAMCDwE+APgAYAAAAAAABxwH3wJwgRhA3iB54AhgAAAAAAPhA/iBDMCCQGHgH+AHwAAAAAAAQIAgQAAAAAA="), 46, atob("BAUJCQkJCQkJCQkJBQ=="), 17+(scale<<8)+(1<<16)); + return this; +}; + /* Set variables to get screen width, height and center points */ let W = g.getWidth(); @@ -36,10 +44,6 @@ let cx = W/2; let cy = H/2; let Timeout; -/* set font */ - -require("Font4x5Numeric").add(Graphics); - Bangle.loadWidgets(); /* Custom version of Bangle.drawWidgets (does not clear the widget areas) Thanks to rozek */ @@ -106,9 +110,10 @@ function drawHands() { g.drawImage(imgHour,cx-22*hourSin,cy+22*hourCos,{rotate:hourAngle}); g.drawImage(imgMin,cx-34*minSin,cy+34*minCos,{rotate:minAngle}); g.drawImage(imgSec,cx-25*secSin,cy+25*secCos,{rotate:secAngle}); - g.setFont("4x5Numeric:3"); + g.setFontRolexFont(); g.setColor(g.theme.bg); - g.drawString(d.getDate(),157,81); + g.setFontAlign(0,0,0); + g.drawString(d.getDate(),165,89); } function drawBackground() { diff --git a/apps/rolex/metadata.json b/apps/rolex/metadata.json index e24344dad..f307e60c4 100644 --- a/apps/rolex/metadata.json +++ b/apps/rolex/metadata.json @@ -2,8 +2,8 @@ "name": "rolex", "shortName":"rolex", "icon": "rolex.png", - "screenshots": [{"url":"screenshot.png"}], - "version":"0.03", + "screenshots": [{"url":"screenshot1.png"}], + "version":"0.04", "description": "A rolex like watch face", "tags": "clock", "type": "clock", @@ -14,4 +14,4 @@ {"name":"rolex.app.js","url":"app.js"}, {"name":"rolex.img","url":"app-icon.js","evaluate":true} ] - } \ No newline at end of file + } diff --git a/apps/rolex/screenshot1.png b/apps/rolex/screenshot1.png new file mode 100644 index 000000000..f84b0ef5c Binary files /dev/null and b/apps/rolex/screenshot1.png differ diff --git a/apps/terminalclock/ChangeLog b/apps/terminalclock/ChangeLog index 5560f00bc..6515ab627 100644 --- a/apps/terminalclock/ChangeLog +++ b/apps/terminalclock/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: Rename "Activity" in "Motion" and display the true values for it diff --git a/apps/terminalclock/README.md b/apps/terminalclock/README.md index 2a2bc1204..5a54583d2 100644 --- a/apps/terminalclock/README.md +++ b/apps/terminalclock/README.md @@ -5,5 +5,5 @@ It can display : - time - date - hrm -- activity +- motion - steps diff --git a/apps/terminalclock/app.js b/apps/terminalclock/app.js index fb7bd16cc..ab83a696f 100644 --- a/apps/terminalclock/app.js +++ b/apps/terminalclock/app.js @@ -1,6 +1,5 @@ var locale = require("locale"); var fontColor = g.theme.dark ? "#0f0" : "#000"; -var startY = 24; var paddingY = 2; var font6x8At4Size = 32; var font6x8At2Size = 18; @@ -15,25 +14,25 @@ function setFontSize(pos){ } function clearField(pos){ - var yStartPos = startY + + var yStartPos = Bangle.appRect.y + paddingY * (pos - 1) + font6x8At4Size * Math.min(1, pos-1) + font6x8At2Size * Math.max(0, pos-2); - var yEndPos = startY + + var yEndPos = Bangle.appRect.y + paddingY * (pos - 1) + font6x8At4Size * Math.min(1, pos) + font6x8At2Size * Math.max(0, pos-1); - g.clearRect(0, yStartPos, 240, yEndPos); + g.clearRect(Bangle.appRect.x, yStartPos, Bangle.appRect.x2, yEndPos); } function clearWatchIfNeeded(now){ if(now.getMinutes() % 10 == 0) - g.clearRect(0, startY, 240, 240); + g.clearRect(Bangle.appRect.x, Bangle.appRect.y, Bangle.appRect.x2, Bangle.appRect.y2); } function drawLine(line, pos){ setFontSize(pos); - var yPos = startY + + var yPos = Bangle.appRect.y + paddingY * (pos - 1) + font6x8At4Size * Math.min(1, pos-1) + font6x8At2Size * Math.max(0, pos-2); @@ -76,11 +75,10 @@ function drawHRM(pos){ function drawActivity(pos){ clearField(pos); var health = Bangle.getHealthStatus('last'); - var steps_formated = ">Activity: " + parseInt(health.movement/10); + var steps_formated = ">Motion: " + parseInt(health.movement); drawLine(steps_formated, pos); } - function draw(){ var curPos = 1; g.reset(); @@ -109,7 +107,6 @@ function draw(){ drawInput(now, curPos); } - Bangle.on('HRM',function(hrmInfo) { if(hrmInfo.confidence >= settings.HRMinConfidence) heartRate = hrmInfo.bpm; @@ -127,12 +124,12 @@ var settings = Object.assign({ showActivity: true, showStepCount: true, }, require('Storage').readJSON("terminalclock.json", true) || {}); -// draw immediately at first -draw(); // Show launcher when middle button pressed Bangle.setUI("clock"); // Load widgets Bangle.loadWidgets(); Bangle.drawWidgets(); +// draw immediately at first +draw(); var secondInterval = setInterval(draw, 10000); diff --git a/apps/terminalclock/screenshot1.png b/apps/terminalclock/screenshot1.png index a0d41f495..c0f472102 100644 Binary files a/apps/terminalclock/screenshot1.png and b/apps/terminalclock/screenshot1.png differ diff --git a/apps/terminalclock/screenshot2.png b/apps/terminalclock/screenshot2.png index 21d7aadbf..8bd552a73 100644 Binary files a/apps/terminalclock/screenshot2.png and b/apps/terminalclock/screenshot2.png differ diff --git a/apps/timeandlife/metadata.json b/apps/timeandlife/metadata.json index 86800f16f..acd6b0086 100644 --- a/apps/timeandlife/metadata.json +++ b/apps/timeandlife/metadata.json @@ -1,18 +1,26 @@ { "id": "timeandlife", "name": "Time and Life", - "shortName":"Time and Lfie", + "shortName": "Time and Life", "icon": "app.png", - "version":"0.01", + "version": "0.01", "description": "A simple watchface which displays the time when the screen is tapped and decay according to the rules of Conway's game of life.", "type": "clock", "tags": "clock", - "supports": ["BANGLEJS2"], - "allow_emulator":true, + "supports": [ + "BANGLEJS2" + ], + "allow_emulator": true, "readme": "README.md", "storage": [ - {"name":"timeandlife.app.js","url":"app.js"}, - {"name":"timeandlife.img","url":"app-icon.js","evaluate":true} + { + "name": "timeandlife.app.js", + "url": "app.js" + }, + { + "name": "timeandlife.img", + "url": "app-icon.js", + "evaluate": true + } ] -} - +} \ No newline at end of file diff --git a/apps/widadjust/README.md b/apps/widadjust/README.md new file mode 100644 index 000000000..4f89af54b --- /dev/null +++ b/apps/widadjust/README.md @@ -0,0 +1,57 @@ +# Adjust Clock + +Adjusts clock continually in the background to counter clock drift. + +## Usage + +First you need to determine the clock drift of your watch in PPM (parts per million). + +For example if you measure that your watch clock is too fast by 5 seconds in 24 hours, +then PPM is `5 / (24*60*60) * 1000000 = 57.9`. + +Then set PPM in settings and this widget will continually adjust the clock by that amount. + +## Settings + +See **Basic logic** below for more details. + +- **PPM x 10** - change PPM in steps of 10 +- **PPM x 1** - change PPM in steps of 1 +- **PPM x 0.1** - change PPM in steps of 0.1 +- **Update Interval** - How often to update widget and clock error. +- **Threshold** - Threshold for adjusting clock. + When clock error exceeds this threshold, clock is adjusted with `setTime`. +- **Save State** - If `On` clock error state is saved to file when widget exits, if needed. + That is recommended and default setting. + If `Off` clock error state is forgotten and reset to 0 whenever widget is restarted, + for example when going to Launcher. This can cause significant inaccuracy especially + with large **Update Interval** or **Threshold**. +- **Debug Log** - If `On` some debug information is logged to file `widadjust.log`. + +## Display + +Widget shows clock error in milliseconds and PPM. + +## Basic logic + +- When widget starts, clock error state is loaded from file `widadjust.state`. +- While widget is running, widget display and clock error is updated + periodically (**Update Interval**) according to **PPM**. +- When clock error exceeds **Threshold** clock is adjusted with `setTime`. +- When widget exists, clock error state is saved to file `widadjust.state` if needed. + +## Services + +Other apps/widgets can use `WIDGETS.adjust.now()` to request current adjusted time. +To support also case where this widget isn't present, the following code can be used: + +``` +function adjustedNow() { + return WIDGETS.adjust ? WIDGETS.adjust.now() : Date.now(); +} +``` + +## Acknowledgment + +Uses [Clock Settings](https://icons8.com/icon/tQvI71EfIWy3/clock-settings) +icon by [Icons8](https://icons8.com). diff --git a/apps/widadjust/icon.png b/apps/widadjust/icon.png new file mode 100644 index 000000000..f97394618 Binary files /dev/null and b/apps/widadjust/icon.png differ diff --git a/apps/widadjust/metadata.json b/apps/widadjust/metadata.json new file mode 100644 index 000000000..a308072f5 --- /dev/null +++ b/apps/widadjust/metadata.json @@ -0,0 +1,19 @@ +{ + "id": "widadjust", + "name": "Adjust Clock", + "icon": "icon.png", + "version": "0.01", + "description": "Adjusts clock continually in the background to counter clock drift", + "type": "widget", + "tags": "widget", + "supports": [ "BANGLEJS", "BANGLEJS2" ], + "readme": "README.md", + "storage": [ + { "name": "widadjust.wid.js", "url": "widget.js" }, + { "name": "widadjust.settings.js", "url": "settings.js" } + ], + "data": [ + { "name": "widadjust.json" }, + { "name": "widadjust.state" } + ] +} diff --git a/apps/widadjust/settings.js b/apps/widadjust/settings.js new file mode 100644 index 000000000..5791d763b --- /dev/null +++ b/apps/widadjust/settings.js @@ -0,0 +1,120 @@ +(function(back) { + const SETTINGS_FILE = 'widadjust.json'; + const STATE_FILE = 'widadjust.state'; + + const DEFAULT_ADJUST_THRESHOLD = 100; + let thresholdV = [ 10, 25, 50, 100, 250, 500, 1000 ]; + + const DEFAULT_UPDATE_INTERVAL = 60000; + let intervalV = [ 10000, 30000, 60000, 180000, 600000, 1800000, 3600000 ]; + let intervalN = [ "10 s", "30 s", "1 m", "3 m", "10 m", "30 m", "1 h" ]; + + let stateFileErased = false; + + let settings = Object.assign({ + advanced: false, + saveState: true, + debugLog: false, + ppm: 0, + adjustThreshold: DEFAULT_ADJUST_THRESHOLD, + updateInterval: DEFAULT_UPDATE_INTERVAL, + }, require('Storage').readJSON(SETTINGS_FILE, true) || {}); + + if (thresholdV.indexOf(settings.adjustThreshold) == -1) { + settings.adjustThreshold = DEFAULT_ADJUST_THRESHOLD; + } + + if (intervalV.indexOf(settings.updateInterval) == -1) { + settings.updateInterval = DEFAULT_UPDATE_INTERVAL; + } + + function onPpmChange(v) { + settings.ppm = v; + mainMenu['PPM x 10' ].value = v; + mainMenu['PPM x 1' ].value = v; + mainMenu['PPM x 0.1'].value = v; + } + + let mainMenu = { + '': { 'title' : 'Adjust Clock' }, + + '< Back': () => { + require('Storage').writeJSON(SETTINGS_FILE, settings); + back(); + }, + + /* + // NOT FULLY WORKING YET + 'Mode': { + value: settings.advanced, + format: v => v ? 'Advanced' : 'Basic', + onchange: () => { + settings.advanced = !settings.advanced; + } + }, + */ + + 'PPM x 10' : { + value: settings.ppm, + format: v => v.toFixed(1), + step: 10, + onchange : onPpmChange, + }, + + 'PPM x 1' : { + value: settings.ppm, + format: v => v.toFixed(1), + step: 1, + onchange : onPpmChange, + }, + + 'PPM x 0.1' : { + value: settings.ppm, + format: v => v.toFixed(1), + step: 0.1, + onchange : onPpmChange, + }, + + 'Update Interval': { + value: intervalV.indexOf(settings.updateInterval), + min: 0, + max: intervalV.length - 1, + format: v => intervalN[v], + onchange: v => { + settings.updateInterval = intervalV[v]; + }, + }, + + 'Threshold': { + value: thresholdV.indexOf(settings.adjustThreshold), + min: 0, + max: thresholdV.length - 1, + format: v => thresholdV[v] + " ms", + onchange: v => { + settings.adjustThreshold = thresholdV[v]; + }, + }, + + 'Save State': { + value: settings.saveState, + format: v => v ? 'On' : 'Off', + onchange: () => { + settings.saveState = !settings.saveState; + if (!settings.saveState && !stateFileErased) { + stateFileErased = true; + require("Storage").erase(STATE_FILE); + } + }, + }, + + 'Debug Log': { + value: settings.debugLog, + format: v => v ? 'On' : 'Off', + onchange: () => { + settings.debugLog = !settings.debugLog; + }, + }, + }; + + E.showMenu(mainMenu); +}) diff --git a/apps/widadjust/widget.js b/apps/widadjust/widget.js new file mode 100644 index 000000000..138833783 --- /dev/null +++ b/apps/widadjust/widget.js @@ -0,0 +1,244 @@ +(() => { + // ====================================================================== + // CONST + + const DEBUG_LOG_FILE = 'widadjust.log'; + const SETTINGS_FILE = 'widadjust.json'; + const STATE_FILE = 'widadjust.state'; + + const DEFAULT_ADJUST_THRESHOLD = 100; + const DEFAULT_UPDATE_INTERVAL = 60 * 1000; + const MIN_INTERVAL = 10 * 1000; + + const MAX_CLOCK_ERROR_FROM_SAVED_STATE = 2000; + + const SAVE_STATE_CLOCK_ERROR_DELTA_THRESHOLD = 1; + const SAVE_STATE_CLOCK_ERROR_DELTA_IN_PPM_THRESHOLD = 1; + const SAVE_STATE_PPM_DELTA_THRESHOLD = 1; + + // Widget width. + const WIDTH = 22; + + // ====================================================================== + // VARIABLES + + let settings; + let saved; + + let lastClockCheckTime = Date.now(); + let lastClockErrorUpdateTime; + + let clockError; + let currentUpdateInterval; + let lastPpm = null; + + let debugLogFile = null; + + // ====================================================================== + // FUNCTIONS + + function clockCheck() { + let now = Date.now(); + let elapsed = now - lastClockCheckTime; + lastClockCheckTime = now; + + let prevUpdateInterval = currentUpdateInterval; + currentUpdateInterval = settings.updateInterval; + setTimeout(clockCheck, lastClockCheckTime + currentUpdateInterval - Date.now()); + + // If elapsed time differs a lot from expected, + // some other app probably used setTime to change clock significantly. + // -> reset clock error since elapsed time can't be trusted + if (Math.abs(elapsed - prevUpdateInterval) > 10 * 1000) { + // RESET CLOCK ERROR + + clockError = 0; + lastClockErrorUpdateTime = now; + + debug( + 'Looks like some other app used setTime, so reset clockError. (elapsed = ' + + elapsed.toFixed(0) + ')' + ); + WIDGETS.adjust.draw(); + + } else if (!settings.advanced) { + // UPDATE CLOCK ERROR WITHOUT TEMPERATURE COMPENSATION + + updateClockError(settings.ppm); + } else { + // UPDATE CLOCK ERROR WITH TEMPERATURE COMPENSATION + + Bangle.getPressure().then(d => { + let temp = d.temperature; + updateClockError(settings.ppm0 + settings.ppm1 * temp + settings.ppm2 * temp * temp); + }).catch(e => { + WIDGETS.adjust.draw(); + }); + } + } + + function debug(line) { + console.log(line); + if (debugLogFile !== null) { + debugLogFile.write(line + '\n'); + } + } + + function draw() { + g.reset().setFont('6x8').setFontAlign(0, 0); + g.clearRect(this.x, this.y, this.x + WIDTH - 1, this.y + 23); + g.drawString(Math.round(clockError), this.x + WIDTH/2, this.y + 9); + + if (lastPpm !== null) { + g.setFont('4x6').setFontAlign(0, 1); + g.drawString(lastPpm.toFixed(1), this.x + WIDTH/2, this.y + 23); + } + } + + function loadSettings() { + settings = Object.assign({ + advanced: false, + saveState: true, + debugLog: false, + ppm: 0, + ppm0: 0, + ppm1: 0, + ppm2: 0, + adjustThreshold: DEFAULT_ADJUST_THRESHOLD, + updateInterval: DEFAULT_UPDATE_INTERVAL, + }, require('Storage').readJSON(SETTINGS_FILE, true) || {}); + + if (settings.debugLog) { + if (debugLogFile === null) { + debugLogFile = require('Storage').open(DEBUG_LOG_FILE, 'a'); + } + } else { + debugLogFile = null; + } + + settings.updateInterval = Math.max(settings.updateInterval, MIN_INTERVAL); + } + + function onQuit() { + let now = Date.now(); + // WIP + let ppm = (lastPpm !== null) ? lastPpm : settings.ppm; + let updatedClockError = clockError + (now - lastClockErrorUpdateTime) * ppm / 1000000; + let save = false; + + if (! settings.saveState) { + debug(new Date(now).toISOString() + ' QUIT'); + + } else if (saved === undefined) { + save = true; + debug(new Date(now).toISOString() + ' QUIT & SAVE STATE'); + + } else { + let elapsedSaved = now - saved.time; + let estimatedClockError = saved.clockError + elapsedSaved * saved.ppm / 1000000; + + let clockErrorDelta = updatedClockError - estimatedClockError; + let clockErrorDeltaInPpm = clockErrorDelta / elapsedSaved * 1000000; + let ppmDelta = ppm - saved.ppm; + + let debugA = new Date(now).toISOString() + ' QUIT'; + let debugB = + '\n> ' + updatedClockError.toFixed(2) + ' - ' + estimatedClockError.toFixed(2) + ' = ' + + clockErrorDelta.toFixed(2) + ' (' + + clockErrorDeltaInPpm.toFixed(1) + ' PPM) ; ' + + ppm.toFixed(1) + ' - ' + saved.ppm.toFixed(1) + ' = ' + ppmDelta.toFixed(1); + + if ((Math.abs(clockErrorDelta) >= SAVE_STATE_CLOCK_ERROR_DELTA_THRESHOLD + && Math.abs(clockErrorDeltaInPpm) >= SAVE_STATE_CLOCK_ERROR_DELTA_IN_PPM_THRESHOLD + ) || Math.abs(ppmDelta) >= SAVE_STATE_PPM_DELTA_THRESHOLD + ) + { + save = true; + debug(debugA + ' & SAVE STATE' + debugB); + } else { + debug(debugA + debugB); + } + } + + if (save) { + require('Storage').writeJSON(STATE_FILE, { + counter: (saved === undefined) ? 1 : saved.counter + 1, + time: Math.round(now), + clockError: Math.round(updatedClockError * 1000) / 1000, + ppm: Math.round(ppm * 1000) / 1000, + }); + } + } + + function updateClockError(ppm) { + let now = Date.now(); + let elapsed = now - lastClockErrorUpdateTime; + let drift = elapsed * ppm / 1000000; + clockError += drift; + lastClockErrorUpdateTime = now; + lastPpm = ppm; + + if (Math.abs(clockError) >= settings.adjustThreshold) { + let now = Date.now(); + // Shorter variables are faster to look up and this part is time sensitive. + let e = clockError / 1000; + setTime(getTime() - e); + debug( + new Date(now).toISOString() + ' -> ' + ((now / 1000 - e) % 60).toFixed(3) + + ' SET TIME (' + clockError.toFixed(2) + ')' + ); + clockError = 0; + } + + WIDGETS.adjust.draw(); + } + + // ====================================================================== + // MAIN + + loadSettings(); + + WIDGETS.adjust = { + area: 'tr', + draw: draw, + now: () => { + let now = Date.now(); + // WIP + let ppm = (lastPpm !== null) ? lastPpm : settings.ppm; + let updatedClockError = clockError + (now - lastClockErrorUpdateTime) * ppm / 1000000; + return now - updatedClockError; + }, + width: WIDTH, + }; + + if (settings.saveState) { + saved = require('Storage').readJSON(STATE_FILE, true); + } + + let now = Date.now(); + lastClockErrorUpdateTime = now; + if (saved === undefined) { + clockError = 0; + debug(new Date().toISOString() + ' START'); + } else { + clockError = saved.clockError + (now - saved.time) * saved.ppm / 1000000; + + if (Math.abs(clockError) <= MAX_CLOCK_ERROR_FROM_SAVED_STATE) { + debug( + new Date().toISOString() + ' START & LOAD STATE (' + + clockError.toFixed(2) + ')' + ); + } else { + debug( + new Date().toISOString() + ' START & IGNORE STATE (' + + clockError.toFixed(2) + ')' + ); + clockError = 0; + } + } + + clockCheck(); + + E.on('kill', onQuit); + +})()