diff --git a/_config.yml b/_config.yml index 2f7efbeab..c74188174 100644 --- a/_config.yml +++ b/_config.yml @@ -1 +1 @@ -theme: jekyll-theme-minimal \ No newline at end of file +theme: jekyll-theme-slate \ No newline at end of file diff --git a/apps/alarm/ChangeLog b/apps/alarm/ChangeLog index 41dd93081..9daf7bcbf 100644 --- a/apps/alarm/ChangeLog +++ b/apps/alarm/ChangeLog @@ -24,3 +24,4 @@ 0.23: Fix regression with Days of Week (#1735) 0.24: Automatically save the alarm/timer when the user returns to the main menu using the back arrow Add "Enable All", "Disable All" and "Remove All" actions +0.25: Fix redrawing selected Alarm/Timer entry inside edit submenu diff --git a/apps/alarm/app.js b/apps/alarm/app.js index 3b3421115..90a62afc5 100644 --- a/apps/alarm/app.js +++ b/apps/alarm/app.js @@ -47,8 +47,7 @@ function showMainMenu() { menu[txt] = { value : "\0"+atob(alarm.on?"EhKBAH//v/////////////5//x//j//H+eP+Mf/A//h//z//////////3//g":"EhKBAH//v//8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA///3//g"), onchange : function() { - if (alarm.timer) editTimer(idx, alarm); - else editAlarm(idx, alarm); + setTimeout(alarm.timer ? editTimer : editAlarm, 10, idx, alarm); } }; }); @@ -116,7 +115,7 @@ function editAlarm(alarmIndex, alarm) { }, /*LANG*/'Days': { value: "SMTWTFS".split("").map((d,n)=>a.dow&(1< editDOW(a.dow, d => { + onchange: () => setTimeout(editDOW, 100, a.dow, d => { a.dow = d; a.t = require("sched").encodeTime(t); editAlarm(alarmIndex, a); diff --git a/apps/alarm/metadata.json b/apps/alarm/metadata.json index 2084c2a30..33312beb6 100644 --- a/apps/alarm/metadata.json +++ b/apps/alarm/metadata.json @@ -2,7 +2,7 @@ "id": "alarm", "name": "Alarms & Timers", "shortName": "Alarms", - "version": "0.24", + "version": "0.25", "description": "Set alarms and timers on your Bangle", "icon": "app.png", "tags": "tool,alarm,widget", diff --git a/apps/bradbury/app-icon.js b/apps/bradbury/app-icon.js new file mode 100644 index 000000000..07c4f5582 --- /dev/null +++ b/apps/bradbury/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwcCkGSpEgwQCChICFkgCBgkQoMEyFJAoICByVBkgLBkkSpIaDEwWShEkFgcIBAIdCEYQCBAoQdBAoYsBC4Q7BpICBEYQCDF4Q7CEYYCCEYUSKYYUDyRlCJQQIBNYYvBMoQCBkgjBFgxxCL4REDFgaPEHYgmCIgosCNYZEEDoZ0CNwY7CIIYgDEYtB9+e/dg/4AB2EJkYEB/mC/fn33Ivvz598v4MB/0BgoRCyVHvmW7Mg2EA8uD/EAh/IkGP/8AgVLtkA5El+FJvoRBgmf4Mkh0HkEQo9kyEfkeQofsgf4kmPCIP+h/gwULkkCncEu/ZsmRI4cEv0H8ESpdgEwMjwXI9kTCIOANYkSEYOCncF+UAjuR/ED+FBg/3/f8RgNgiVPkYdBtkT/Egv0Il+AoMfI4PgyX7vkW799F4Nl//4//woH/+0Ztvx7Fs335sk//5EB/IRBhACB77CBpEkgEIgGQoDRBgEggVBgDdBgGAgPv317ku+5cj334t+OSoI+B8gCBtlx7dkuFfgvx4N8yPbvgOB8ACBR4MA9mf4Egz3IgeChEDwDOBx/AjuCoN8y/JgkX4ME2FBjuQn65BgMtwELkGOEYOO4Mh2EJh+Sh/jOIMd+3fskRcwMTEwOWo98gCSBwFJkm2pfgx3II4PBk++/aABhEfwEInpZBvkX7MkJQMl2FHfANBjgCBlmQhHsgwjB33IkeyBAOChMcEwM9+/ZsBHBboMJtv2hd9+FHZANBVoM7kGC/fv2FJ9+GEYOAh//+UIaIMBkkQpEAHwIIBoMgiFJBANJEAMIkGShEkwQIChIIBhIIBhIaCkmQpIFCgmSEwYpDEYwCCpAICBwUEiQdFEwIICyAIDHwQ7CEYYpCEYWSpA7FDocSEwojBCgIaDIgYCBNwR0BNYYjFEwZTDLgQjGOgYvBEYQ7ENYlJFgQCCDohuGTYpBFkhoCSoQICEYIA=")) diff --git a/apps/bradbury/app.js b/apps/bradbury/app.js new file mode 100644 index 000000000..147242689 --- /dev/null +++ b/apps/bradbury/app.js @@ -0,0 +1,115 @@ +require("Font7x11Numeric7Seg").add(Graphics); +require("Font5x9Numeric7Seg").add(Graphics); +require("Font8x12").add(Graphics); +require("FontDylex7x13").add(Graphics); +const X = 98, Y = 46; +var wizible = 0; + +function getImg() { + return require("heatshrink").decompress(atob("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")); +} + +function draw() { + var d = new Date(); + var h = d.getHours() % 12 || 12, m = d.getMinutes(), yyyy = d.getFullYear(), mm = d.getMonth(), dd = d.getDate(); + var time = (""+h).substr(-2) + ":" + ("0"+m).substr(-2); + g.reset(); // Reset the state of the graphics library + g.clear(); + g.drawImage(getImg()); //load bg image + //TIME + g.setFont("7x11Numeric7Seg",2); + g.setFontAlign(1,1); + g.setColor(0,0,1); + g.drawString(time, 97, 53, false /*clear background*/); + g.setColor(0,0,0); + g.drawString(time, 96, 52, false /*clear background*/); + //SECONDS + g.setFont("7x11Numeric7Seg",1); + //g.setFont("5x9Numeric7Seg"); + g.setFontAlign(-1,1); // align right bottom + g.setColor(0,0,1); + g.drawString(("0"+d.getSeconds()).substr(-2), 100, 42, 0); + g.setColor(0,0,0); + g.drawString(("0"+d.getSeconds()).substr(-2), 99, 41, 0); + //DATE + g.setFont("5x9Numeric7Seg",1); + g.setFontAlign(1,1); + g.setColor(0,0,1); + g.drawString(yyyy+" "+("0"+mm)+" "+dd, 100, 65, 0); + g.setColor(0,0,0); + g.drawString(yyyy+" "+("0"+mm)+" "+dd, 99, 64, 0); + //BATTERY + g.setColor(0,0,1); + g.drawString(E.getBattery(), 137, 53, 0); + g.setColor(0,0,0); + g.drawString(E.getBattery(), 136, 52, 0); + //STEPS + g.setColor(0,0,1); + g.drawString(Bangle.getHealthStatus("day").steps, 137, 65, 0); + g.setColor(0,0,0); + g.drawString(Bangle.getHealthStatus("day").steps, 136, 64, 0); + //WEEK DAY + g.setFont("8x12"); + g.setColor(0,0,1); + if (d.getDay()==0) { + g.drawString("SU", 137, 43, 0); + g.setColor(0,0,0); + g.drawString("SU", 136, 42, 0); + } else if (d.getDay()==1) { + g.drawString("MO", 137, 43, 0); + g.setColor(0,0,0); + g.drawString("MO", 136, 42, 0); + } else if (d.getDay()==2) { + g.drawString("TU", 137, 43, 0); + g.setColor(0,0,0); + g.drawString("TU", 136, 42, 0); + } else if (d.getDay()==3) { + g.drawString("WE", 137, 43, 0); + g.setColor(0,0,0); + g.drawString("WE", 136, 42, 0); + } else if (d.getDay()==4) { + g.setFont("Dylex7x13"); + g.drawString("TH", 137, 43, 0); + g.setColor(0,0,0); + g.drawString("TH", 136, 42, 0); + } else if (d.getDay()==5) { + g.drawString("FR", 137, 43, 0); + g.setColor(0,0,0); + g.drawString("FR", 136, 42, 0); + } else { + g.drawString("SA", 137, 43, 0); + g.setColor(0,0,0); + g.drawString("SA", 136, 42, 0); + } + if(wizible==1){ + Bangle.drawWidgets(); + } +} + +// Clear the screen once, at startup +g.clear(); +// draw immediately at first +draw(); +var secondInterval = setInterval(draw, 1000); +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ + if (secondInterval) clearInterval(secondInterval); + secondInterval = undefined; + if (on) { + secondInterval = setInterval(draw, 1000); + draw(); // draw immediately + } +}); +// Show launcher when middle button pressed +Bangle.setUI("clock"); + +//Toggle Widgets +Bangle.loadWidgets(); +Bangle.on('touch', function(button) { + if(wizible==0){ + wizible=1; + } + else if(wizible==1){ + wizible=0; + } +}); diff --git a/apps/bradbury/app.png b/apps/bradbury/app.png new file mode 100644 index 000000000..f7141d15e Binary files /dev/null and b/apps/bradbury/app.png differ diff --git a/apps/bradbury/metadata.json b/apps/bradbury/metadata.json new file mode 100644 index 000000000..456daa381 --- /dev/null +++ b/apps/bradbury/metadata.json @@ -0,0 +1,14 @@ +{ "id": "bradbury", + "name": "Bradbury Watch", + "shortName":"Bradbury", + "icon": "app.png", + "screenshots": [{"url":"screenshot.png"}], + "version":"0.01", + "description": "A watch face based on the classic Seiko model worn by one of my favorite authors. I didn't follow the original lcd layout exactly, opting for larger font for more easily readable time, and adding date, battery level, and step count; read from the device. Tapping the screen toggles visibility of widgets.", + "type": "clock", + "supports":["BANGLEJS2"], + "storage": [ + {"name":"bradbury.app.js","url":"app.js"}, + {"name":"bradbury.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/bradbury/screenshot.png b/apps/bradbury/screenshot.png new file mode 100644 index 000000000..914266668 Binary files /dev/null and b/apps/bradbury/screenshot.png differ diff --git a/apps/contourclock/ChangeLog b/apps/contourclock/ChangeLog index 032edc9b5..d415a604d 100644 --- a/apps/contourclock/ChangeLog +++ b/apps/contourclock/ChangeLog @@ -6,3 +6,4 @@ 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 +0.27: Fixed the timing code, which sometimes did not update for one minute diff --git a/apps/contourclock/app.js b/apps/contourclock/app.js index cdfadd217..d5c97edfa 100644 --- a/apps/contourclock/app.js +++ b/apps/contourclock/app.js @@ -7,6 +7,13 @@ if (settings.fontIndex==undefined) { require('Storage').writeJSON("myapp.json", settings); } +function queueDraw() { + setTimeout(function() { + draw(); + queueDraw(); + }, 60000 - (Date.now() % 60000)); +} + function draw() { var date = new Date(); // Draw day of the week @@ -24,7 +31,5 @@ Bangle.setUI("clock"); g.clear(); Bangle.loadWidgets(); Bangle.drawWidgets(); +queueDraw(); draw(); -setTimeout(function() { - setInterval(draw,60000); -}, 60000 - Date.now() % 60000); diff --git a/apps/contourclock/metadata.json b/apps/contourclock/metadata.json index ec29c390b..eb0dd39fb 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.26", + "version":"0.27", "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/openwind/ChangeLog b/apps/openwind/ChangeLog index 5560f00bc..1e5f791b2 100644 --- a/apps/openwind/ChangeLog +++ b/apps/openwind/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: Fix true wind computation, add swipe gesture to pause GPS diff --git a/apps/openwind/README.md b/apps/openwind/README.md index 1df7ea158..c03ec1401 100644 --- a/apps/openwind/README.md +++ b/apps/openwind/README.md @@ -14,7 +14,9 @@ additionally displayed in red. In this mode, the speed over ground in knots is ## Controls -There are no controls in the main app, but there are two settings in the settings app that can be changed: +In the main app, when true wind mode is enabled (see below), swiping left on the screen will temporarily disable GPS (to preserve battery); a small +red satellite symbol will appear on the bottom right. Swiping right will turn GPS back on. +The settings app provides the following two settings: * True wind: enables or disables true wind calculations; enabling this will turn on GPS inside the app * Mounting angle: mounting relative to the boat of the wind instrument (in degrees) diff --git a/apps/openwind/app.js b/apps/openwind/app.js index b1c8fea4b..db67804f3 100644 --- a/apps/openwind/app.js +++ b/apps/openwind/app.js @@ -1,16 +1,20 @@ OW_CHAR_UUID = '0000cc91-0000-1000-8000-00805f9b34fb'; require("Font7x11Numeric7Seg").add(Graphics); -gatt = {}; -cx = g.getWidth()/2; -cy = 24+(g.getHeight()-24)/2; -w = (g.getWidth()-24)/2; - -gps_course = { spd: 0 }; +var gatt = {}; +var cx = g.getWidth()/2; +var cy = 24+(g.getHeight()-24)/2; +var w = (g.getWidth()-24)/2; +var y1 = 24; +var y2 = g.getHeight()-1; +var gps_course = { spd: 0 }; +var course_marker_len = g.getWidth()/4; var settings = require("Storage").readJSON('openwindsettings.json', 1) || {}; -i = 0; -hullpoly = []; +var pause_gps = false; + +var i = 0; +var hullpoly = []; for (y=-1; y<=1; y+=0.1) { hullpoly[i++] = cx - (y<0 ? 1+y*0.15 : (Math.sqrt(1-0.7*y*y)-Math.sqrt(0.3))/(1-Math.sqrt(0.3)))*w*0.3; hullpoly[i++] = cy - y*w*0.7; @@ -22,21 +26,22 @@ for (y=1; y>=-1; y-=0.1) { function wind_updated(ev) { if (ev.target.uuid == "0xcc91") { - awa = settings.mount_angle-ev.target.value.getInt16(1, true)*0.1; + awa = settings.mount_angle+ev.target.value.getInt16(1, true)*0.1; + if (awa<0) awa += 360; aws = ev.target.value.getInt16(3, true)*0.01; -// console.log(awa, aws); + //console.log(awa, aws); if (gps_course.spd > 0) { - wv = { // wind vector (in fixed reference frame) - lon: Math.sin(Math.PI*(gps_course.course+awa)/180)*aws, - lat: Math.cos(Math.PI*(gps_course.course+awa)/180)*aws + wv = { // wind vector (in "earth" reference frame) + vlon: Math.sin(Math.PI*(gps_course.course+(awa+180))/180)*aws, + vlat: Math.cos(Math.PI*(gps_course.course+(awa+180))/180)*aws }; - twv = { lon: wv.lon+gps_course.lon, lat: wv.lat+gps_course.lat }; - tws = Math.sqrt(Math.pow(twv.lon,2)+Math.pow(twv.lat, 2)); - twa = Math.atan2(twv.lat, twv.lon)*180/Math.PI-gps_course.course; + twv = { vlon: wv.vlon+gps_course.vlon, vlat: wv.vlat+gps_course.vlat }; + tws = Math.sqrt(Math.pow(twv.vlon,2)+Math.pow(twv.vlat, 2)); + twa = 180+Math.atan2(twv.vlon, twv.vlat)*180/Math.PI-gps_course.course; if (twa<0) twa += 360; if (twa>360) twa -=360; } - else { + else { tws = -1; twa = 0; } @@ -57,34 +62,37 @@ function draw_compass(awa, aws, twa, tws) { a = i*Math.PI/2+Math.PI/4; g.drawLineAA(cx+Math.cos(a)*w*0.85, cy+Math.sin(a)*w*0.85, cx+Math.cos(a)*w*0.99, cy+Math.sin(a)*w*0.99); } - g.setColor(0, 1, 0).fillCircle(cx+Math.sin(Math.PI*awa/180)*w*0.9, cy+Math.cos(Math.PI*awa/180)*w*0.9, w*0.1); + g.setColor(0, 1, 0).fillCircle(cx+Math.sin(Math.PI*awa/180)*w*0.9, cy-Math.cos(Math.PI*awa/180)*w*0.9, w*0.1); if (tws>0) g.setColor(1, 0, 0).fillCircle(cx+Math.sin(Math.PI*twa/180)*w*0.9, cy+Math.cos(Math.PI*twa/180)*w*0.9, w*0.1); g.setColor(0, 1, 0).setFont("7x11Numeric7Seg",w*0.06); g.setFontAlign(0, 0, 0).drawString(aws.toFixed(1), cx, cy-0.32*w); - if (tws>0) g.setColor(1, 0, 0).drawString(tws.toFixed(1), cx, cy+0.32*w); - if (settings.truewind && typeof gps_course.spd!=='undefined') { - spd = gps_course.spd/1.852; - g.setColor(g.theme.fg).setFont("7x11Numeric7Seg", w*0.03).setFontAlign(-1, 1, 0).drawString(spd.toFixed(1), 1, g.getHeight()-1); + if (!pause_gps) { + if (tws>0) g.setColor(1, 0, 0).drawString(tws.toFixed(1), cx, cy+0.32*w); + if (settings.truewind && gps_course.spd!=-1) { + spd = gps_course.spd/1.852; + g.setColor(g.theme.fg).setFont("7x11Numeric7Seg", w*0.03).setFontAlign(-1, 1, 0).drawString(spd.toFixed(1), 1, g.getHeight()-1); + } } + if (pause_gps) g.setColor("#f00").drawImage(atob("DAwBEAKARAKQE4DwHkPqPRGKAEAA"),g.getWidth()-15, g.getHeight()-15); } function parseDevice(d) { device = d; console.log("Found device"); - device.gatt.connect().then(function(ga) { - console.log("Connected"); - gatt = ga; - return ga.getPrimaryService("cc90"); -}).then(function(s) { - return s.getCharacteristic("cc91"); -}).then(function(c) { - c.on('characteristicvaluechanged', (event)=>wind_updated(event)); - return c.startNotifications(); -}).then(function() { - console.log("Done!"); -}).catch(function(e) { - console.log("ERROR"+e); -});} + device.gatt.connect().then(function(ga) { + console.log("Connected"); + gatt = ga; + return ga.getPrimaryService("cc90"); + }).then(function(s) { + return s.getCharacteristic("cc91"); + }).then(function(c) { + c.on('characteristicvaluechanged', (event)=>wind_updated(event)); + return c.startNotifications(); + }).then(function() { + console.log("Done!"); + }).catch(function(e) { + console.log("ERROR"+e); + });} function connection_setup() { NRF.setScan(); @@ -96,8 +104,10 @@ if (settings.truewind) { Bangle.on('GPS',function(fix) { if (fix.fix && fix.satellites>3 && fix.speed>2) { // only uses fixes w/ more than 3 sats and speed > 2kph gps_course = - { lon: Math.sin(Math.PI*fix.course/180)*fix.speed/1.852, - lat: Math.cos(Math.PI*fix.course/180)*fix.speed/1.852, + { vlon: Math.sin(Math.PI*fix.course/180)*fix.speed/1.852, + vlat: Math.cos(Math.PI*fix.course/180)*fix.speed/1.852, + lat: fix.lat, + lon: fix.lon, spd: fix.speed, course: fix.course }; @@ -107,6 +117,20 @@ if (settings.truewind) { Bangle.setGPSPower(1, "app"); } +if (settings.truewind) { + Bangle.on("swipe", (d)=>{ + if (d==-1 && !pause_gps) { + pause_gps = true; + Bangle.setGPSPower(0); + draw_compass(0, 0, 0, 0); + } + else if (d==1 && pause_gps) { + pause_gps = false; + Bangle.setGPSPower(1, "app"); + draw_compass(0, 0, 0, 0); + } + }); +} Bangle.loadWidgets(); Bangle.drawWidgets(); draw_compass(0, 0, 0, 0); diff --git a/apps/openwind/metadata.json b/apps/openwind/metadata.json index 9229f7f25..43961cc44 100644 --- a/apps/openwind/metadata.json +++ b/apps/openwind/metadata.json @@ -1,7 +1,7 @@ { "id": "openwind", "name": "OpenWind", "shortName":"OpenWind", - "version":"0.01", + "version":"0.02", "description": "OpenWind", "icon": "openwind.png", "readme": "README.md", diff --git a/apps/quicklaunch/ChangeLog b/apps/quicklaunch/ChangeLog index ec66c5568..ae1d4a848 100644 --- a/apps/quicklaunch/ChangeLog +++ b/apps/quicklaunch/ChangeLog @@ -1 +1,2 @@ 0.01: Initial version +0.02: Moved settings from launcher to settings->apps menu diff --git a/apps/quicklaunch/app-icon.js b/apps/quicklaunch/app-icon.js deleted file mode 100644 index 14ae94823..000000000 --- a/apps/quicklaunch/app-icon.js +++ /dev/null @@ -1 +0,0 @@ -require("heatshrink").decompress(atob("kMigILIgPAAYMD/ADBwcGhkAwM5wcA/+2//Av/Rn/giFoyFggkUrFggEKlAkCiApCx+AAYNGoADBkU4AYMQj4DBvEICANkAoIPBgE2B4MAiMAH4MAwECAYNALYUgBIISCHYMYAoQWBAIMEgAYBAIMBwEDDQNgDwUf/4eBg4DCAA4")) diff --git a/apps/quicklaunch/metadata.json b/apps/quicklaunch/metadata.json index 6411d1a5f..49eafdd35 100644 --- a/apps/quicklaunch/metadata.json +++ b/apps/quicklaunch/metadata.json @@ -1,14 +1,15 @@ -{ "id": "quicklaunch", +{ + "id": "quicklaunch", "name": "Quick Launch", "icon": "app.png", - "version":"0.01", - "description": "Tap or swipe left/right/up/down on your clock face to launch up to five apps of your choice.", + "version":"0.02", + "description": "Tap or swipe left/right/up/down on your clock face to launch up to five apps of your choice. Configurations can be accessed through Settings->Apps.", + "type": "bootloader", "tags": "tools, system", "supports": ["BANGLEJS2"], "storage": [ - {"name":"quicklaunch.app.js","url":"app.js"}, - {"name":"quicklaunch.boot.js","url":"boot.js"}, - {"name":"quicklaunch.img","url":"app-icon.js","evaluate":true} + {"name":"quicklaunch.settings.js","url":"settings.js"}, + {"name":"quicklaunch.boot.js","url":"boot.js"} ], "data": [{"name":"quicklaunch.json"}] } diff --git a/apps/quicklaunch/app.js b/apps/quicklaunch/settings.js similarity index 99% rename from apps/quicklaunch/app.js rename to apps/quicklaunch/settings.js index f2b749e3e..ac4cc5805 100644 --- a/apps/quicklaunch/app.js +++ b/apps/quicklaunch/settings.js @@ -1,3 +1,4 @@ +(function(back) { var settings = Object.assign(require("Storage").readJSON("quicklaunch.json", true) || {}); var apps = require("Storage").list(/\.info$/).map(app=>{var a=require("Storage").readJSON(app,1);return a&&{name:a.name,type:a.type,sortorder:a.sortorder,src:a.src};}).filter(app=>app && (app.type=="app" || app.type=="launch" || app.type=="clock" || !app.type)); @@ -118,3 +119,4 @@ apps.forEach((a)=>{ }); showMainMenu(); +}); \ No newline at end of file diff --git a/apps/scicalc/ChangeLog b/apps/scicalc/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/scicalc/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/scicalc/README.md b/apps/scicalc/README.md new file mode 100644 index 000000000..bbe4b93c4 --- /dev/null +++ b/apps/scicalc/README.md @@ -0,0 +1,18 @@ +# SciCalc + +Simple scientific calculator. I needed one, so I wrote a basic one, no design frills. Input expressions are slightly post processed and then evaluated +by the JS interpreter. + +## Usage + +Buttons are arranged on 3 separate screens, swiping left or right switches between them. Swiping down has the same effect as hitting the "=" button. + +## Features + +The calculator supports the following operations: + + * basic arithmetic: +, -, *, /, ^ (raise to a power), +/- (invert sign), 1/x (inverse), use of parentheses + * trigonometric fucntions: sin, cos, tan, asin, acos, atan + * exponential exp, natural logarithm log, pow function (this one takes 2 comma separated arguments) + * Pi is provided as a constant + * a memory button "M" stores or recalls the last result (after hitting the "=" button or swiping down) diff --git a/apps/scicalc/app-icon.js b/apps/scicalc/app-icon.js new file mode 100644 index 000000000..b8363e6ee --- /dev/null +++ b/apps/scicalc/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwxH+AH4A/AH4AJioAaF1wwSFzowRCQUZo4AWjIvVFy4ABF/4vXyGQAYov/R+sZFy8ZF6oAcF/4vvi4AeF/4SCjseAAMdAx8MAAYvVEAQABAx4v/R/TvvF96PUg8cAAMHd9QuCAAIv/R+rvvF96Pvd94vvR97vvF96Pvd94vvR97vsGDwuQGDouSAH4A/AGwA==")) diff --git a/apps/scicalc/app.js b/apps/scicalc/app.js new file mode 100644 index 000000000..5d914d0c5 --- /dev/null +++ b/apps/scicalc/app.js @@ -0,0 +1,113 @@ +const W = g.getWidth(); +const H = g.getHeight(); + +const dispH = H/5; +const butH = H-dispH; + +const buttons = [[['7', '8', '9'], + ['4', '5', '6'], + ['1', '2', '3'], + ['E', '0', '.']], + [['<', 'M', 'C'], + ['+', '-', '*'], + ['/', '(', ')'], + ['^', ',', '=']], + [['Sin', 'Cos', 'Tan'], + ['Asi', 'Aco', 'Ata'], + ['Pi', '1/x', '+/-'], + ['Log', 'Exp', 'Pow'] + ]]; + +var curPage = 0; +var inputStr = ''; +var memory = ''; +var qResult = false; + +function drawPage (p) { + g.clearRect(0, dispH, W-1, H-1); + g.setFont('Vector', butH/5).setFontAlign(0, 0, 0).setColor(g.theme.fg); + for (x=0; x<3; ++x) + for (y=0; y<4; ++y) + g.drawString(buttons[p][y][x], (x+0.5)*W/3, dispH+(y+0.7)*butH/4); + g.setColor(0.5, 0.5, 0.5); + for (x=1; x<3; ++x) g.drawLine(x*W/3, dispH+0.2*butH/4-2, x*W/3, H-1); + for (y=1; y<4; ++y) g.drawLine(0, dispH+(y+0.2)*butH/4, W-1, dispH+(y+0.2)*butH/4); + g.setColor(g.theme.fg).drawLine(0, dispH+0.2*butH/4-2, W-1, dispH+0.2*butH/4-2); +} + +function updateDisp(s, len) { + var fh = butH/5; + if (s.toString().length>len) s = s.toString().substr(0,len); + g.setFont("Vector", butH/5).setColor(g.theme.fg).setFontAlign(1, 0, 0); + while (g.stringWidth(s) > W-1) { + fh /= 1.05; + g.setFont("Vector", fh); + } + g.clearRect(0, 0, W-1, dispH-1).drawString(s, W-2, dispH/2); + g.setColor(g.theme.fg).drawLine(0, dispH+0.2*butH/4-2, W-1, dispH+0.2*butH/4-2); +} + +function processInp (s) { + var idx = s.indexOf("^"); + if (idx > 0) s = "Math.pow(" + s.slice(0,idx) + "," + s.slice(idx+1, s.length) + ")"; + ['Sin', 'Cos', 'Tan', 'Asin', 'Acos', 'Atan', 'Log', 'Exp', 'Pow'].forEach((x) => { + var i = s.indexOf(x); + while (i>-1) { + s = s.slice(0,i)+"Math."+s.slice(i,i+1).toLowerCase()+s.slice(i+1, s.length); + i = s.indexOf(x, i+6); + } + }); + idx = s.indexOf('Pi'); + if (idx>-1) s = s.slice(0,idx) + "Math.PI" + s.slice(idx+2, s.length); + idx = 0; + s.split('').forEach((x)=>{ if (x=='(') idx++; if (x==')') idx-- }); + s += ')'.repeat(idx); + return s; +} + +function compute() { + var res; + console.log(processInp(inputStr)); + try { res = eval(processInp(inputStr)); } + catch(e) { res = "error"; } + inputStr = res; + qResult = true; + updateDisp(inputStr, 19); +} + +function touchHandler(e, d) { + var x = Math.floor(d.x/(W/3)); + var y = Math.floor((d.y-dispH-0.2*butH/4)/(butH/4)); + var c = buttons[curPage][y][x]; + if (c=="=") { // do the computation + compute(); + return; + } + else if (c=="<" && inputStr.length>0) inputStr = inputStr.slice(0, -1); // delete last character + else if (c=='M' && qResult) memory = inputStr; + else if (c=='M') inputStr += memory; + else if (c=="C") inputStr = ''; // clear + else { + if ("Sin Cos Tan Log Exp Pow".indexOf(c)>-1 && c!='E') c += "("; + if ("Asi Aco Ata".indexOf(c)>-1) c += "n("; + if (c=='1/x') { inputStr = "1/("+inputStr+")"; compute(); return; } + if (c=='+/-') { inputStr = "-("+inputStr+")"; compute(); return; } + if (qResult && "+-*/^".indexOf(c)==-1) inputStr = c + inputStr + ")"; + else inputStr += c; + } + qResult = false; + updateDisp(inputStr, 32); +} + +function swipeHandler(e,d) { + curPage -= e; + if (curPage>buttons.length-1) curPage = 0; + if (curPage<0) curPage = buttons.length-1; + drawPage(curPage); + if (d==1) compute(); +} + +Bangle.on("touch", touchHandler); +Bangle.on("swipe", swipeHandler); +g.clear(); +drawPage(curPage); diff --git a/apps/scicalc/metadata.json b/apps/scicalc/metadata.json new file mode 100644 index 000000000..d2d855dc2 --- /dev/null +++ b/apps/scicalc/metadata.json @@ -0,0 +1,15 @@ +{ "id": "scicalc", + "name": "Scientific Calculator", + "shortName":"SciCalc", + "version":"0.01", + "description": "Scientific calculator", + "icon": "scicalc.png", + "readme": "README.md", + "tags": "app,tool", + "allow_emulator": true, + "supports" : ["BANGLEJS2"], + "storage": [ + {"name":"scicalc.app.js","url":"app.js"}, + {"name":"scicalc.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/scicalc/scicalc.png b/apps/scicalc/scicalc.png new file mode 100644 index 000000000..b5aa6ff7e Binary files /dev/null and b/apps/scicalc/scicalc.png differ diff --git a/apps/sleepphasealarm/ChangeLog b/apps/sleepphasealarm/ChangeLog index 875b3c1da..53c993376 100644 --- a/apps/sleepphasealarm/ChangeLog +++ b/apps/sleepphasealarm/ChangeLog @@ -3,3 +3,5 @@ 0.03: Add compatibility for Bangle.js 2 and new firmware, added "Alarm at " for the alarm time 0.04: Read alarms from new scheduling library, account for higher acceleration sensor noise on Bangle.js 2 0.05: Refactor decodeTime() to scheduling library +0.06: Add logging + use Layout library and display ETA diff --git a/apps/sleepphasealarm/README.md b/apps/sleepphasealarm/README.md new file mode 100644 index 000000000..04388194f --- /dev/null +++ b/apps/sleepphasealarm/README.md @@ -0,0 +1,15 @@ +# Sleep Phase Alarm + +The display shows: + +- the current time +- time of the next alarm or timer +- time difference between current time and alarm time (ETA) +- current state of the ESS algorithm, "Sleep" or "Awake", useful for debugging + +## Logging + +For each day of month (1..31) the ESS states are logged. An entry will be overwritten in the next month, e.g. an entry on the 4th May will overwrite an entry on the 4th April. +The logs can be viewed with the download button: + +![](screenshot.jpg) diff --git a/apps/sleepphasealarm/app.js b/apps/sleepphasealarm/app.js index 236b71c0b..9bde85452 100644 --- a/apps/sleepphasealarm/app.js +++ b/apps/sleepphasealarm/app.js @@ -1,6 +1,10 @@ -const BANGLEJS2 = process.env.HWVERSION == 2; //# check for bangle 2 -const alarms = require("Storage").readJSON("sched.json",1)||[]; +const BANGLEJS2 = process.env.HWVERSION == 2; // check for bangle 2 +const Layout = require("Layout"); +const locale = require('locale'); +const alarms = require("Storage").readJSON("sched.json",1) || []; +const config = require("Storage").readJSON("sleepphasealarm.json",1) || {logs: []}; const active = alarms.filter(a=>a.on); +let logs = []; // Sleep/Wake detection with Estimation of Stationary Sleep-segments (ESS): // Marko Borazio, Eugen Berlin, Nagihan Kücükyildiz, Philipp M. Scholl and Kristof Van Laerhoven, "Towards a Benchmark for Wearable Sleep Analysis with Inertial Wrist-worn Sensing Units", ICHI 2014, Verona, Italy, IEEE Press, 2014. @@ -52,53 +56,45 @@ active.forEach(alarm => { } }); -function drawString(s, y) { //# replaced x: always centered - g.reset(); //# moved up to prevent blue background - g.clearRect(0, y - 12, 239, y + 8); //# minimized upper+lower clearing - g.setFont("Vector", 20); - g.setFontAlign(0, 0); // align centered - g.drawString(s, g.getWidth() / 2, y); //# set x to center -} +var layout = new Layout({ + type:"v", c: [ + {type:"txt", font:"10%", label:"Sleep Phase Alarm", bgCol:g.theme.bgH, fillx: true, height:Bangle.appRect.h/6}, + {type:"txt", font:"16%", label: ' '.repeat(20), id:"date", height:Bangle.appRect.h/6}, + {type:"txt", font:"12%", label: "", id:"alarm_date", height:Bangle.appRect.h/6}, + {type:"txt", font:"10%", label: ' '.repeat(20), id:"eta", height:Bangle.appRect.h/6}, + {type:"txt", font:"12%", label: ' '.repeat(20), id:"state", height:Bangle.appRect.h/6}, + ] +}, {lazy:true}); function drawApp() { - g.clearRect(0,24,239,215); //# no problem var alarmHour = nextAlarm.getHours(); var alarmMinute = nextAlarm.getMinutes(); if (alarmHour < 10) alarmHour = "0" + alarmHour; if (alarmMinute < 10) alarmMinute = "0" + alarmMinute; - const s = "Alarm at " + alarmHour + ":" + alarmMinute + "\n\n"; //# make distinct to time - E.showMessage(s, "Sleep Phase Alarm"); + layout.alarm_date.label = "Alarm at " + alarmHour + ":" + alarmMinute; + layout.render(); function drawTime() { if (Bangle.isLCDOn()) { const now = new Date(); - var nowHour = now.getHours(); - var nowMinute = now.getMinutes(); - var nowSecond = now.getSeconds(); - if (nowHour < 10) nowHour = "0" + nowHour; - if (nowMinute < 10) nowMinute = "0" + nowMinute; - if (nowSecond < 10) nowSecond = "0" + nowSecond; - const time = nowHour + ":" + nowMinute + (BANGLEJS2 ? "" : ":" + nowSecond); //# hide seconds on bangle 2 - drawString(time, BANGLEJS2 ? 85 : 105); //# remove x, adjust height for bangle 2 an newer firmware + layout.date.label = locale.time(now, BANGLEJS2 && Bangle.isLocked() ? 1 : 0); // hide seconds on bangle 2 + const diff = nextAlarm - now; + const diffHour = Math.floor((diff % 86400000) / 3600000).toString(); + const diffMinutes = Math.round(((diff % 86400000) % 3600000) / 60000).toString(); + layout.eta.label = "ETA: -"+ diffHour + ":" + diffMinutes.padStart(2, '0'); + layout.render(); } } - if (BANGLEJS2) { - drawTime(); - setTimeout(_ => { - drawTime(); - setInterval(drawTime, 60000); - }, 60000 - Date.now() % 60000); //# every new minute on bangle 2 - } else { - setInterval(drawTime, 500); // 2Hz - } + drawTime(); + setInterval(drawTime, 500); // 2Hz } var buzzCount = 19; function buzz() { if ((require('Storage').readJSON('setting.json',1)||{}).quiet>1) return; // total silence - Bangle.setLCDPower(1); - Bangle.buzz().then(()=>{ + Bangle.setLCDPower(1); + Bangle.buzz().then(()=>{ if (buzzCount--) { setTimeout(buzz, 500); } else { @@ -108,12 +104,21 @@ function buzz() { }); } +function addLog(time, type) { + logs.push({time: time, type: type}); + require("Storage").writeJSON("sleepphasealarm.json", config); +} + // run var minAlarm = new Date(); var measure = true; if (nextAlarm !== undefined) { - Bangle.loadWidgets(); //# correct widget load draw order + config.logs[nextAlarm.getDate()] = []; // overwrite log on each day of month + logs = config.logs[nextAlarm.getDate()]; + g.clear(); + Bangle.loadWidgets(); Bangle.drawWidgets(); + let swest_last; // minimum alert 30 minutes early minAlarm.setTime(nextAlarm.getTime() - (30*60*1000)); @@ -124,14 +129,26 @@ if (nextAlarm !== undefined) { if (swest !== undefined) { if (Bangle.isLCDOn()) { - drawString(swest ? "Sleep" : "Awake", BANGLEJS2 ? 150 : 180); //# remove x, adjust height + layout.state.label = swest ? "Sleep" : "Awake"; + layout.render(); + } + // log + if (swest_last != swest) { + if (swest) { + addLog(new Date(now - sleepthresh*13/12.5*1000), "sleep"); // calculate begin of no motion phase, 13 values/second at 12.5Hz + } else { + addLog(now, "awake"); + } + swest_last = swest; } } if (now >= nextAlarm) { // The alarm widget should handle this one + addLog(now, "alarm"); setTimeout(load, 1000); } else if (measure && now >= minAlarm && swest === false) { + addLog(now, "alarm"); buzz(); measure = false; } @@ -141,6 +158,4 @@ if (nextAlarm !== undefined) { E.showMessage('No Alarm'); setTimeout(load, 1000); } -// BTN2 to menu, BTN3 to main # on bangle 2 only BTN to main -if (!BANGLEJS2) setWatch(Bangle.showLauncher, BTN2, { repeat: false, edge: "falling" }); setWatch(() => load(), BANGLEJS2 ? BTN : BTN3, { repeat: false, edge: "falling" }); diff --git a/apps/sleepphasealarm/interface.html b/apps/sleepphasealarm/interface.html new file mode 100644 index 000000000..9a7cb0f93 --- /dev/null +++ b/apps/sleepphasealarm/interface.html @@ -0,0 +1,108 @@ + + + + + + +

Please select a wakeup day:

+
+ +
+
+ +
+ + + + + + + diff --git a/apps/sleepphasealarm/metadata.json b/apps/sleepphasealarm/metadata.json index aecfa36e4..f55854f35 100644 --- a/apps/sleepphasealarm/metadata.json +++ b/apps/sleepphasealarm/metadata.json @@ -2,14 +2,17 @@ "id": "sleepphasealarm", "name": "SleepPhaseAlarm", "shortName": "SleepPhaseAlarm", - "version": "0.05", + "version": "0.06", "description": "Uses the accelerometer to estimate sleep and wake states with the principle of Estimation of Stationary Sleep-segments (ESS, see https://ubicomp.eti.uni-siegen.de/home/datasets/ichi14/index.html.en). This app will read the next alarm from the alarm application and will wake you up to 30 minutes early at the best guessed time when you are almost already awake.", "icon": "app.png", "tags": "alarm", "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", "dependencies": {"scheduler":"type"}, "storage": [ {"name":"sleepphasealarm.app.js","url":"app.js"}, {"name":"sleepphasealarm.img","url":"app-icon.js","evaluate":true} - ] + ], + "data": [{"name":"sleepphasealarm.json","storageFile":true}], + "interface": "interface.html" } diff --git a/apps/sleepphasealarm/screenshot.jpg b/apps/sleepphasealarm/screenshot.jpg new file mode 100644 index 000000000..b1fd05dec Binary files /dev/null and b/apps/sleepphasealarm/screenshot.jpg differ