diff --git a/.gitignore b/.gitignore index 438b6250b..8c4c31a89 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,8 @@ _site Desktop.ini .sync_*.db* *.swp +*_BACKUP_* +*_BASE_* +*_LOCAL_* +*_REMOTE_* +*.orig diff --git a/apps/90sclk/settings.js b/apps/90sclk/settings.js index 8f97cd317..74241d603 100644 --- a/apps/90sclk/settings.js +++ b/apps/90sclk/settings.js @@ -21,7 +21,6 @@ '< Back': back, 'Full Screen': { value: settings.fullscreen, - format: () => (settings.fullscreen ? 'Yes' : 'No'), onchange: () => { settings.fullscreen = !settings.fullscreen; save(); diff --git a/apps/agenda/metadata.json b/apps/agenda/metadata.json index 2d5864145..c63cee2e9 100644 --- a/apps/agenda/metadata.json +++ b/apps/agenda/metadata.json @@ -10,8 +10,8 @@ "readme": "README.md", "allow_emulator": true, "storage": [ - {"name":"agenda.app.js","url":"agenda.js"}, - {"name":"agenda.settings.js","url":"settings.js"}, + {"name":"agenda.app.js","url":"agenda.app.js"}, + {"name":"agenda.settings.js","url":"agenda.settings.js"}, {"name":"agenda.clkinfo.js","url":"agenda.clkinfo.js"}, {"name":"agenda.img","url":"agenda-icon.js","evaluate":true} ], diff --git a/apps/android/ChangeLog b/apps/android/ChangeLog index d531e43a9..108242825 100644 --- a/apps/android/ChangeLog +++ b/apps/android/ChangeLog @@ -32,3 +32,4 @@ Allow alarm enable/disable 0.31: Implement API for activity fetching 0.32: Added support for loyalty cards from gadgetbridge +0.33: Fix alarms created in Gadgetbridge not repeating diff --git a/apps/android/boot.js b/apps/android/boot.js index 846fc40a8..63f9b2883 100644 --- a/apps/android/boot.js +++ b/apps/android/boot.js @@ -81,7 +81,12 @@ for (var j = 0; j < event.d.length; j++) { // prevents all alarms from going off at once?? var dow = event.d[j].rep; - if (!dow) dow = 127; //if no DOW selected, set alarm to all DOW + var rp = false; + if (!dow) { + dow = 127; //if no DOW selected, set alarm to all DOW + } else { + rp = true; + } var last = (event.d[j].h * 3600000 + event.d[j].m * 60000 < currentTime) ? (new Date()).getDate() : 0; var a = require("sched").newDefaultAlarm(); a.id = "gb"+j; @@ -89,6 +94,7 @@ a.on = event.d[j].on !== undefined ? event.d[j].on : true; a.t = event.d[j].h * 3600000 + event.d[j].m * 60000; a.dow = ((dow&63)<<1) | (dow>>6); // Gadgetbridge sends DOW in a different format + a.rp = rp; a.last = last; alarms.push(a); } diff --git a/apps/android/metadata.json b/apps/android/metadata.json index 68bd946c5..5babc520b 100644 --- a/apps/android/metadata.json +++ b/apps/android/metadata.json @@ -2,7 +2,7 @@ "id": "android", "name": "Android Integration", "shortName": "Android", - "version": "0.32", + "version": "0.33", "description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.", "icon": "app.png", "tags": "tool,system,messages,notifications,gadgetbridge", diff --git a/apps/angles/ChangeLog b/apps/angles/ChangeLog new file mode 100644 index 000000000..2286a7f70 --- /dev/null +++ b/apps/angles/ChangeLog @@ -0,0 +1 @@ +0.01: New App! \ No newline at end of file diff --git a/apps/angles/app.js b/apps/angles/app.js new file mode 100644 index 000000000..a07c29199 --- /dev/null +++ b/apps/angles/app.js @@ -0,0 +1,49 @@ +g.clear().setRotation(1); +// g.setRotation ALSO changes accelerometer axes +var avrAngle = undefined; +var history = []; + +var R = Bangle.appRect; +var W = g.getWidth(); +var H = g.getHeight(); +var relativeTo = undefined; + +function draw(v) { + if (v===undefined) v = Bangle.getAccel(); + // current angle + var d = Math.sqrt(v.y*v.y + v.z*v.z); + var ang = Math.atan2(-v.x, d)*180/Math.PI; + // Median filter + if (history.length > 10) history.shift(); // pull old reading off the start + history.push(ang); + avrAngle = history.slice().sort()[(history.length-1)>>1]; // median filter + // Render + var x = R.x + R.w/2; + var y = R.y + R.h/2; + g.reset().clearRect(R).setFontAlign(0,0); + var displayAngle = avrAngle; + g.setFont("6x15").drawString("ANGLE (DEGREES)", x, R.y2-8); + if (relativeTo!==undefined) { + g.drawString("RELATIVE TO", x,y-50); + g.setFont("Vector:30").drawString(relativeTo.toFixed(1),x,y-30); + y += 20; + displayAngle = displayAngle-relativeTo; + } + g.setFont("Vector:60").drawString(displayAngle.toFixed(1),x,y); + +} + +draw(); +Bangle.on('accel',draw); + +// Pressing the button turns relative angle on/off +Bangle.setUI({ + mode : "custom", + btn : function(n) { + if (relativeTo===undefined) + relativeTo = avrAngle; + else + relativeTo = undefined; + draw(); + } +}); \ No newline at end of file diff --git a/apps/angles/icon.js b/apps/angles/icon.js new file mode 100644 index 000000000..3f051f95f --- /dev/null +++ b/apps/angles/icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4cA///ov+5lChWMyGuxdzpdj4/lKf4AUkgQPgm0wAiPy2QCBsBkmS6QRNhIRBrVACJlPu2+pdICBcCrVJlvJtIRLifStMl3MtkARKydUyMkzMl0CMKyWWyUk1MkSJXkyR7BogRLgVcydSrVGzLHKgdLyfSpdE3JYKklqTwNJknJYJVkxcSp+pnygKhMs1OSEQOSYhVJl1bCIbBK5Mq7gRCyARJiVbqyPBCIKMKuVM24yBCIIiJnVOqu5CISMKp9JlvJCIRXKpP3nxoCRhUSBwSMNBwaMMgn6yp6DRhUl0mypiMMgM9ksipaMMhMtCINKRhlJmoRBpJuBCBIRGRhUE5I1CpKMLgmZn5ZDGhUAycnRoNMRhTDCsn3tfkRhLnDTwYQLNgSMMUQkyRhbGEkyMKAApFOAH4AGA")) \ No newline at end of file diff --git a/apps/angles/icon.png b/apps/angles/icon.png new file mode 100644 index 000000000..1a4559d44 Binary files /dev/null and b/apps/angles/icon.png differ diff --git a/apps/angles/metadata.json b/apps/angles/metadata.json new file mode 100644 index 000000000..f8a90a305 --- /dev/null +++ b/apps/angles/metadata.json @@ -0,0 +1,15 @@ +{ + "id": "angles", + "name": "Angles (Spirit Level)", + "shortName": "Angles", + "version": "0.01", + "description": "Shows Angle or Relative angle in degrees (Digital Protractor/Inclinometer). Place Bangle sideways against a surface with the button facing away for best readings.", + "icon": "icon.png", + "screenshots": [{"url":"screenshot.png"}], + "tags": "tool", + "supports": ["BANGLEJS2"], + "storage": [ + {"name":"angles.app.js","url":"app.js"}, + {"name":"angles.img","url":"icon.js","evaluate":true} + ] +} diff --git a/apps/angles/screenshot.png b/apps/angles/screenshot.png new file mode 100644 index 000000000..9d631cf74 Binary files /dev/null and b/apps/angles/screenshot.png differ diff --git a/apps/aviatorclk/.gitignore b/apps/aviatorclk/.gitignore new file mode 100644 index 000000000..bdbc0d22e --- /dev/null +++ b/apps/aviatorclk/.gitignore @@ -0,0 +1 @@ +aviatorclk.json diff --git a/apps/aviatorclk/ChangeLog b/apps/aviatorclk/ChangeLog new file mode 100644 index 000000000..971e5b97e --- /dev/null +++ b/apps/aviatorclk/ChangeLog @@ -0,0 +1 @@ +1.00: initial release diff --git a/apps/aviatorclk/README.md b/apps/aviatorclk/README.md new file mode 100644 index 000000000..fe7376b5d --- /dev/null +++ b/apps/aviatorclk/README.md @@ -0,0 +1,36 @@ +# Aviator Clock + +A clock for aviators, with local time and UTC - and the latest METAR +(Meteorological Aerodrome Report) for the nearest airport + + + + +This app depends on the [AVWX module](?id=avwx). Make sure to configure that +module after installing this app. + + +## Features + +- Local time (with optional seconds) +- UTC / Zulu time +- Weekday and day of the month +- Latest METAR for the nearest airport (scrollable) + +Tap the screen in the top or bottom half to scroll the METAR text (in case not +the whole report fits on the screen). + +The colour of the METAR text will change to orange if the report is more than +1h old, and red if it's older than 1.5h. + + +## Settings + +- **Show Seconds**: to conserve battery power, you can turn the seconds display off +- **Invert Scrolling**: swaps the METAR scrolling direction of the top and bottom taps + + +## Author + +Flaparoo [github](https://github.com/flaparoo) + diff --git a/apps/aviatorclk/aviatorclk-icon.js b/apps/aviatorclk/aviatorclk-icon.js new file mode 100644 index 000000000..508769a66 --- /dev/null +++ b/apps/aviatorclk/aviatorclk-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwg96iIACCqMBCwYABiAWQiUiAAUhDBwWGDCAWHDAYuMCw4ABGBYWKGBYuLGBcBLpAXNFxhIKFxgwCIyhIJC58hC44WNC5B2NPBIXbBYIAHNgIXKCpAYEC5AhBII8SDAQXJMI5EEC6ZREC6EhFwkRO4zuCC46AFAgLYEC4YCBIoaADF4gXEKgYXDVBAcCXxBZDkcyDRAXHmILCif//4GEC5f/PQQWB//zbAX/C5gAKC78BC6K/In4WJ+YXW+QXHMAURl4XJeQYWEGALhBC4q+BYYLbDFwowCkLTCRIyNHGArNBC48SFxIXCMApHDOwQXIJAIQCAAaWCDYJGIDAipGFwQWKDAUSDAnzUoIWMDAcjn/zUgQWOPYYADOZJjKFqIAp")) diff --git a/apps/aviatorclk/aviatorclk.app.js b/apps/aviatorclk/aviatorclk.app.js new file mode 100644 index 000000000..1d99fdbde --- /dev/null +++ b/apps/aviatorclk/aviatorclk.app.js @@ -0,0 +1,283 @@ +/* + * Aviator Clock - Bangle.js + * + */ + +const COLOUR_DARK_GREY = 0x4208; // same as: g.setColor(0.25, 0.25, 0.25) +const COLOUR_GREY = 0x8410; // same as: g.setColor(0.5, 0.5, 0.5) +const COLOUR_LIGHT_GREY = 0xc618; // same as: g.setColor(0.75, 0.75, 0.75) +const COLOUR_RED = 0xf800; // same as: g.setColor(1, 0, 0) +const COLOUR_BLUE = 0x001f; // same as: g.setColor(0, 0, 1) +const COLOUR_YELLOW = 0xffe0; // same as: g.setColor(1, 1, 0) +const COLOUR_LIGHT_CYAN = 0x87ff; // same as: g.setColor(0.5, 1, 1) +const COLOUR_DARK_YELLOW = 0x8400; // same as: g.setColor(0.5, 0.5, 0) +const COLOUR_DARK_CYAN = 0x0410; // same as: g.setColor(0, 0.5, 0.5) +const COLOUR_ORANGE = 0xfc00; // same as: g.setColor(1, 0.5, 0) + +const APP_NAME = 'aviatorclk'; + +const horizontalCenter = g.getWidth()/2; +const mainTimeHeight = 38; +const secondaryFontHeight = 22; +const dateColour = ( g.theme.dark ? COLOUR_YELLOW : COLOUR_BLUE ); +const UTCColour = ( g.theme.dark ? COLOUR_LIGHT_CYAN : COLOUR_DARK_CYAN ); +const separatorColour = ( g.theme.dark ? COLOUR_LIGHT_GREY : COLOUR_DARK_GREY ); + +const avwx = require('avwx'); + + +// read in the settings +var settings = Object.assign({ + showSeconds: true, + invertScrolling: false, +}, require('Storage').readJSON(APP_NAME+'.json', true) || {}); + + +// globals +var drawTimeout; +var secondsInterval; +var avwxTimeout; + +var AVWXrequest; +var METAR = ''; +var METARlinesCount = 0; +var METARscollLines = 0; +var METARts; + + + +// date object to time string in format HH:MM[:SS] +// (with a leading 0 for hours if required, unlike the "locale" time() function) +function timeStr(date, seconds) { + let timeStr = date.getHours().toString(); + if (timeStr.length == 1) timeStr = '0' + timeStr; + let minutes = date.getMinutes().toString(); + if (minutes.length == 1) minutes = '0' + minutes; + timeStr += ':' + minutes; + if (seconds) { + let seconds = date.getSeconds().toString(); + if (seconds.length == 1) seconds = '0' + seconds; + timeStr += ':' + seconds; + } + return timeStr; +} + + +// draw the METAR info +function drawAVWX() { + let now = new Date(); + let METARage = 0; // in minutes + if (METARts) { + METARage = Math.floor((now - METARts) / 60000); + } + + g.setBgColor(g.theme.bg); + + let y = Bangle.appRect.y + mainTimeHeight + secondaryFontHeight + 4; + g.clearRect(0, y, g.getWidth(), y + (secondaryFontHeight * 4)); + + g.setFontAlign(0, -1).setFont("Vector", secondaryFontHeight); + if (METARage > 90) { // older than 1.5h + g.setColor(COLOUR_RED); + } else if (METARage > 60) { // older than 1h + g.setColor( g.theme.dark ? COLOUR_ORANGE : COLOUR_DARK_YELLOW ); + } else { + g.setColor(g.theme.fg); + } + let METARlines = g.wrapString(METAR, g.getWidth()); + METARlinesCount = METARlines.length; + METARlines.splice(0, METARscollLines); + g.drawString(METARlines.join("\n"), horizontalCenter, y, true); + + if (! avwxTimeout) { avwxTimeout = setTimeout(updateAVWX, 5 * 60000); } +} + +// update the METAR info +function updateAVWX() { + if (avwxTimeout) clearTimeout(avwxTimeout); + avwxTimeout = undefined; + + METAR = '\nGetting GPS fix'; + METARlinesCount = 0; METARscollLines = 0; + METARts = undefined; + drawAVWX(); + + Bangle.setGPSPower(true, APP_NAME); + Bangle.on('GPS', fix => { + // prevent multiple, simultaneous requests + if (AVWXrequest) { return; } + + if ('fix' in fix && fix.fix != 0 && fix.satellites >= 4) { + Bangle.setGPSPower(false, APP_NAME); + let lat = fix.lat; + let lon = fix.lon; + + METAR = '\nRequesting METAR'; + METARlinesCount = 0; METARscollLines = 0; + METARts = undefined; + drawAVWX(); + + // get latest METAR from nearest airport (via AVWX API) + AVWXrequest = avwx.request('metar/'+lat+','+lon, 'onfail=nearest', data => { + if (avwxTimeout) clearTimeout(avwxTimeout); + avwxTimeout = undefined; + + let METARjson = JSON.parse(data.resp); + + if ('sanitized' in METARjson) { + METAR = METARjson.sanitized; + } else { + METAR = 'No "sanitized" METAR data found!'; + } + METARlinesCount = 0; METARscollLines = 0; + + if ('time' in METARjson) { + METARts = new Date(METARjson.time.dt); + let now = new Date(); + let METARage = Math.floor((now - METARts) / 60000); // in minutes + if (METARage <= 30) { + // some METARs update every 30 min -> attempt to update after METAR is 35min old + avwxTimeout = setTimeout(updateAVWX, (35 - METARage) * 60000); + } else if (METARage <= 60) { + // otherwise, attempt METAR update after it's 65min old + avwxTimeout = setTimeout(updateAVWX, (65 - METARage) * 60000); + } + } else { + METARts = undefined; + } + + drawAVWX(); + AVWXrequest = undefined; + + }, error => { + // AVWX API request failed + console.log(error); + METAR = 'ERR: ' + error; + METARlinesCount = 0; METARscollLines = 0; + METARts = undefined; + drawAVWX(); + AVWXrequest = undefined; + }); + } + }); +} + + +// draw only the seconds part of the main clock +function drawSeconds() { + let now = new Date(); + let seconds = now.getSeconds().toString(); + if (seconds.length == 1) seconds = '0' + seconds; + let y = Bangle.appRect.y + mainTimeHeight - 3; + g.setFontAlign(-1, 1).setFont("Vector", secondaryFontHeight).setColor(COLOUR_GREY); + g.drawString(seconds, horizontalCenter + 54, y, true); +} + +// sync seconds update +function syncSecondsUpdate() { + drawSeconds(); + setTimeout(function() { + drawSeconds(); + secondsInterval = setInterval(drawSeconds, 1000); + }, 1000 - (Date.now() % 1000)); +} + +// set timeout for per-minute updates +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + if (METARts) { + let now = new Date(); + let METARage = Math.floor((now - METARts) / 60000); + if (METARage > 60) { + // the METAR colour might have to be updated: + drawAVWX(); + } + } + draw(); + }, 60000 - (Date.now() % 60000)); +} + +// draw top part of clock (main time, date and UTC) +function draw() { + let now = new Date(); + let nowUTC = new Date(now + (now.getTimezoneOffset() * 1000 * 60)); + + // prepare main clock area + let y = Bangle.appRect.y; + + g.setBgColor(g.theme.bg); + + // main time display + g.setFontAlign(0, -1).setFont("Vector", mainTimeHeight).setColor(g.theme.fg); + g.drawString(timeStr(now, false), horizontalCenter, y, true); + + // prepare second line (UTC and date) + y += mainTimeHeight; + g.clearRect(0, y, g.getWidth(), y + secondaryFontHeight - 1); + + // weekday and day of the month + g.setFontAlign(-1, -1).setFont("Vector", secondaryFontHeight).setColor(dateColour); + g.drawString(require("locale").dow(now, 1).toUpperCase() + ' ' + now.getDate(), 0, y, false); + + // UTC + g.setFontAlign(1, -1).setFont("Vector", secondaryFontHeight).setColor(UTCColour); + g.drawString(timeStr(nowUTC, false) + "Z", g.getWidth(), y, false); + + queueDraw(); +} + + +// initialise +g.clear(true); + +// scroll METAR lines on taps +Bangle.setUI("clockupdown", action => { + switch (action) { + case -1: // top tap + if (settings.invertScrolling) { + if (METARscollLines > 0) + METARscollLines--; + } else { + if (METARscollLines < METARlinesCount - 4) + METARscollLines++; + } + break; + case 1: // bottom tap + if (settings.invertScrolling) { + if (METARscollLines < METARlinesCount - 4) + METARscollLines++; + } else { + if (METARscollLines > 0) + METARscollLines--; + } + break; + default: + // ignore + } + drawAVWX(); +}); + +// load widgets +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +// draw static separator line +y = Bangle.appRect.y + mainTimeHeight + secondaryFontHeight; +g.setColor(separatorColour); +g.drawLine(0, y, g.getWidth(), y); + +// draw times and request METAR +draw(); +if (settings.showSeconds) + syncSecondsUpdate(); +updateAVWX(); + + +// TMP for debugging: +//METAR = 'YAAA 011100Z 21014KT CAVOK 23/08 Q1018 RMK RF000/0000'; +//METAR = 'YAAA 150900Z 14012KT 9999 SCT045 BKN064 26/14 Q1012 RMK RF000/0000 DL-W/DL-NW'; +//METAR = 'YAAA 020030Z VRB CAVOK'; +//METARts = new Date(Date.now() - 61 * 60000); // 61 to trigger warning, 91 to trigger alert + diff --git a/apps/aviatorclk/aviatorclk.png b/apps/aviatorclk/aviatorclk.png new file mode 100644 index 000000000..af88cfbc4 Binary files /dev/null and b/apps/aviatorclk/aviatorclk.png differ diff --git a/apps/aviatorclk/aviatorclk.settings.js b/apps/aviatorclk/aviatorclk.settings.js new file mode 100644 index 000000000..6db212ef1 --- /dev/null +++ b/apps/aviatorclk/aviatorclk.settings.js @@ -0,0 +1,35 @@ +(function(back) { + var FILE = "aviatorclk.json"; + + // Load settings + var settings = Object.assign({ + showSeconds: true, + invertScrolling: false, + }, require('Storage').readJSON(FILE, true) || {}); + + function writeSettings() { + require('Storage').writeJSON(FILE, settings); + } + + // Show the menu + E.showMenu({ + "" : { "title" : "AV8R Clock" }, + "< Back" : () => back(), + 'Show Seconds': { + value: !!settings.showSeconds, // !! converts undefined to false + format: v => v ? "On" : "Off", + onchange: v => { + settings.showSeconds = v; + writeSettings(); + } + }, + 'Invert Scrolling': { + value: !!settings.invertScrolling, // !! converts undefined to false + format: v => v ? "On" : "Off", + onchange: v => { + settings.invertScrolling = v; + writeSettings(); + } + }, + }); +}) diff --git a/apps/aviatorclk/metadata.json b/apps/aviatorclk/metadata.json new file mode 100644 index 000000000..6ae8c4a18 --- /dev/null +++ b/apps/aviatorclk/metadata.json @@ -0,0 +1,20 @@ +{ + "id": "aviatorclk", + "name": "Aviator Clock", + "shortName":"AV8R Clock", + "version":"1.00", + "description": "A clock for aviators, with local time and UTC - and the latest METAR for the nearest airport", + "icon": "aviatorclk.png", + "screenshots": [{ "url": "screenshot.png" }, { "url": "screenshot2.png" }], + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS2"], + "dependencies" : { "avwx": "module" }, + "readme": "README.md", + "storage": [ + { "name":"aviatorclk.app.js", "url":"aviatorclk.app.js" }, + { "name":"aviatorclk.settings.js", "url":"aviatorclk.settings.js" }, + { "name":"aviatorclk.img", "url":"aviatorclk-icon.js", "evaluate":true } + ], + "data": [{ "name":"aviatorclk.json" }] +} diff --git a/apps/aviatorclk/screenshot.png b/apps/aviatorclk/screenshot.png new file mode 100644 index 000000000..127946f42 Binary files /dev/null and b/apps/aviatorclk/screenshot.png differ diff --git a/apps/aviatorclk/screenshot2.png b/apps/aviatorclk/screenshot2.png new file mode 100644 index 000000000..e00e2238b Binary files /dev/null and b/apps/aviatorclk/screenshot2.png differ diff --git a/apps/avwx/ChangeLog b/apps/avwx/ChangeLog new file mode 100644 index 000000000..971e5b97e --- /dev/null +++ b/apps/avwx/ChangeLog @@ -0,0 +1 @@ +1.00: initial release diff --git a/apps/avwx/README.md b/apps/avwx/README.md new file mode 100644 index 000000000..a954d118f --- /dev/null +++ b/apps/avwx/README.md @@ -0,0 +1,41 @@ +# AVWX Module + +This is a module/library to use the [AVWX](https://account.avwx.rest/) Aviation +Weather API. It doesn't include an app. + + +## Configuration + +You will need an AVWX account (see above for link) and generate an API token. +The free "Hobby" plan is normally sufficient, but please consider supporting +the AVWX project. + +After installing the module on your Bangle, use the "interface" page (floppy +disk icon) in the App Loader to set the API token. + + +## Usage + +Include the module in your app with: + + const avwx = require('avwx'); + +Then use the exported function, for example to get the "sanitized" METAR from +the nearest station to a lat/lon coordinate pair: + + reqID = avwx.request('metar/'+lat+','+lon, + 'filter=sanitized&onfail=nearest', + data => { console.log(data); }, + error => { console.log(error); }); + +The returned reqID can be useful to track whether a request has already been +made (ie. the app is still waiting on a response). + +Please consult the [AVWX documentation](https://avwx.docs.apiary.io/) for +information about the available end-points and request parameters. + + +## Author + +Flaparoo [github](https://github.com/flaparoo) + diff --git a/apps/avwx/avwx.js b/apps/avwx/avwx.js new file mode 100644 index 000000000..1a9193b26 --- /dev/null +++ b/apps/avwx/avwx.js @@ -0,0 +1,47 @@ +/* + * AVWX Bangle Module + * + * AVWX doco: https://avwx.docs.apiary.io/ + * test AVWX API request with eg.: curl -X GET 'https://avwx.rest/api/metar/43.9844,-88.5570?token=...' + * + */ + + +const AVWX_BASE_URL = 'https://avwx.rest/api/'; // must end with a slash +const AVWX_CONFIG_FILE = 'avwx.json'; + + +// read in the settings +var AVWXsettings = Object.assign({ + AVWXtoken: '', +}, require('Storage').readJSON(AVWX_CONFIG_FILE, true) || {}); + + +/** + * Make an AVWX API request + * + * @param {string} requestPath API path (after /api/), eg. 'meta/KOSH' + * @param {string} params optional request parameters, eg. 'onfail=nearest' (use '&' in the string to combine multiple params) + * @param {function} successCB callback if the API request was successful - will supply the returned data: successCB(data) + * @param {function} failCB callback in case the API request failed - will supply the error: failCB(error) + * + * @returns {number} the HTTP request ID + * + * Example: + * reqID = avwx.request('metar/'+lat+','+lon, + * 'filter=sanitized&onfail=nearest', + * data => { console.log(data); }, + * error => { console.log(error); }); + * + */ +exports.request = function(requestPath, optParams, successCB, failCB) { + if (! AVWXsettings.AVWXtoken) { + failCB('No AVWX API Token defined!'); + return undefined; + } + let params = 'token='+AVWXsettings.AVWXtoken; + if (optParams) + params += '&'+optParams; + return Bangle.http(AVWX_BASE_URL+requestPath+'?'+params).then(successCB).catch(failCB); +}; + diff --git a/apps/avwx/avwx.png b/apps/avwx/avwx.png new file mode 100644 index 000000000..129c9f9f4 Binary files /dev/null and b/apps/avwx/avwx.png differ diff --git a/apps/avwx/interface.html b/apps/avwx/interface.html new file mode 100644 index 000000000..cdd77cb74 --- /dev/null +++ b/apps/avwx/interface.html @@ -0,0 +1,47 @@ + +
+ + + + +To use the AVWX API, you need an account and generate an API token. The free "Hobby" plan is sufficient, but please consider supporting the AVWX project.
++ + +
++ +
+ + + + + + + + diff --git a/apps/avwx/metadata.json b/apps/avwx/metadata.json new file mode 100644 index 000000000..0b07f32d4 --- /dev/null +++ b/apps/avwx/metadata.json @@ -0,0 +1,18 @@ +{ + "id": "avwx", + "name": "AVWX Module", + "shortName":"AVWX", + "version":"1.00", + "description": "Module/library for the AVWX API", + "icon": "avwx.png", + "type": "module", + "tags": "outdoors", + "supports": ["BANGLEJS2"], + "provides_modules": ["avwx"], + "readme": "README.md", + "interface": "interface.html", + "storage": [ + { "name":"avwx", "url":"avwx.js" } + ], + "data": [{ "name":"avwx.json" }] +} diff --git a/apps/banglexercise/settings.js b/apps/banglexercise/settings.js index 3208c6eca..0b52acd72 100644 --- a/apps/banglexercise/settings.js +++ b/apps/banglexercise/settings.js @@ -11,7 +11,6 @@ '< Back': back, 'Buzz': { value: "buzz" in settings ? settings.buzz : false, - format: () => (settings.buzz ? 'Yes' : 'No'), onchange: () => { settings.buzz = !settings.buzz; save('buzz', settings.buzz); diff --git a/apps/binaryclk/ChangeLog b/apps/binaryclk/ChangeLog index a78cbe479..7b6810faa 100644 --- a/apps/binaryclk/ChangeLog +++ b/apps/binaryclk/ChangeLog @@ -1,2 +1,3 @@ 0.01: Added app 0.02: Removed unneeded squares +0.03: Added settings with fullscreen option diff --git a/apps/binaryclk/app.js b/apps/binaryclk/app.js index 8b030ccff..94c906104 100644 --- a/apps/binaryclk/app.js +++ b/apps/binaryclk/app.js @@ -1,3 +1,7 @@ +var settings = Object.assign({ + fullscreen: false, +}, require('Storage').readJSON("binaryclk.json", true) || {}); + function draw() { var dt = new Date(); var h = dt.getHours(), m = dt.getMinutes(); @@ -11,10 +15,14 @@ function draw() { g.clearRect(Bangle.appRect); let i = 0; + var gap = 8; + var mgn = 20; + if (settings.fullscreen) { + gap = 12; + mgn = 0; + } const sq = 29; - const gap = 8; - const mgn = 20; - const pos = sq + gap; + var pos = sq + gap; for (let r = 3; r >= 0; r--) { for (let c = 0; c < 4; c++) { @@ -26,14 +34,15 @@ function draw() { } i++; } - g.clearRect(mgn/2 + gap, mgn + gap, mgn/2 + gap + sq, mgn + 2 * gap + 2 * sq); - g.clearRect(mgn/2 + 3 * gap + 2 * sq, mgn + gap, mgn/2 + 3 * gap + 3 * sq, mgn + gap + sq); + g.clearRect(mgn/2 + gap, mgn + gap, mgn/2 + gap + sq, mgn + 2 * gap + 2 * sq); + g.clearRect(mgn/2 + 3 * gap + 2 * sq, mgn + gap, mgn/2 + 3 * gap + 3 * sq, mgn + gap + sq); } - g.clear(); draw(); var secondInterval = setInterval(draw, 60000); Bangle.setUI("clock"); -Bangle.loadWidgets(); -Bangle.drawWidgets(); +if (!settings.fullscreen) { + Bangle.loadWidgets(); + Bangle.drawWidgets(); +} diff --git a/apps/binaryclk/metadata.json b/apps/binaryclk/metadata.json index a81a39b7a..b4ddc6544 100644 --- a/apps/binaryclk/metadata.json +++ b/apps/binaryclk/metadata.json @@ -1,7 +1,7 @@ { "id": "binaryclk", "name": "Bin Clock", - "version": "0.02", + "version": "0.03", "description": "Clock face to show binary time in 24 hr format", "icon": "app-icon.png", "screenshots": [{"url":"screenshot.png"}], @@ -11,6 +11,8 @@ "allow_emulator": true, "storage": [ {"name":"binaryclk.app.js","url":"app.js"}, + {"name":"binaryclk.settings.js","url":"settings.js"}, {"name":"binaryclk.img","url":"app-icon.js","evaluate":true} - ] + ], + "data": [{"name":"binaryclk.json"}] } diff --git a/apps/binaryclk/settings.js b/apps/binaryclk/settings.js new file mode 100644 index 000000000..0ef30e19d --- /dev/null +++ b/apps/binaryclk/settings.js @@ -0,0 +1,22 @@ +(function(back) { + var FILE = "binaryclk.json"; + var settings = Object.assign({ + fullscreen: false, + }, require('Storage').readJSON(FILE, true) || {}); + + function writeSettings() { + require('Storage').writeJSON(FILE, settings); + } + + E.showMenu({ + "" : { "title" : "Bin Clock" }, + "< Back" : () => back(), + 'Fullscreen': { + value: settings.fullscreen, + onchange: v => { + settings.fullscreen = v; + writeSettings(); + } + }, + }); +}) diff --git a/apps/bwclk/settings.js b/apps/bwclk/settings.js index 116253fda..8bcf0ae0f 100644 --- a/apps/bwclk/settings.js +++ b/apps/bwclk/settings.js @@ -32,7 +32,6 @@ }, 'Show Lock': { value: settings.showLock, - format: () => (settings.showLock ? 'Yes' : 'No'), onchange: () => { settings.showLock = !settings.showLock; save(); @@ -40,7 +39,6 @@ }, 'Hide Colon': { value: settings.hideColon, - format: () => (settings.hideColon ? 'Yes' : 'No'), onchange: () => { settings.hideColon = !settings.hideColon; save(); diff --git a/apps/bwclklite/settings.js b/apps/bwclklite/settings.js index 2d3916a3d..4c59198c6 100644 --- a/apps/bwclklite/settings.js +++ b/apps/bwclklite/settings.js @@ -32,7 +32,6 @@ }, 'Show Lock': { value: settings.showLock, - format: () => (settings.showLock ? 'Yes' : 'No'), onchange: () => { settings.showLock = !settings.showLock; save(); @@ -40,7 +39,6 @@ }, 'Hide Colon': { value: settings.hideColon, - format: () => (settings.hideColon ? 'Yes' : 'No'), onchange: () => { settings.hideColon = !settings.hideColon; save(); diff --git a/apps/calendar/ChangeLog b/apps/calendar/ChangeLog index 6edb54f65..bd8e6117b 100644 --- a/apps/calendar/ChangeLog +++ b/apps/calendar/ChangeLog @@ -17,3 +17,4 @@ 0.15: Edit holidays on device in settings 0.16: Add menu to fast open settings to edit holidays Display Widgets in menus +0.17: Load holidays before events so the latter is not overpainted diff --git a/apps/calendar/calendar.js b/apps/calendar/calendar.js index 7477775ca..f9fd43de8 100644 --- a/apps/calendar/calendar.js +++ b/apps/calendar/calendar.js @@ -43,24 +43,24 @@ const dowLbls = function() { }(); const loadEvents = () => { + // add holidays & other events + events = (require("Storage").readJSON("calendar.days.json",1) || []).map(d => { + const date = new Date(d.date); + const o = {date: date, msg: d.name, type: d.type}; + if (d.repeat) { + o.repeat = d.repeat; + } + return o; + }); // all alarms that run on a specific date - events = (require("Storage").readJSON("sched.json",1) || []).filter(a => a.on && a.date).map(a => { + events = events.concat((require("Storage").readJSON("sched.json",1) || []).filter(a => a.on && a.date).map(a => { const date = new Date(a.date); const time = timeutils.decodeTime(a.t); date.setHours(time.h); date.setMinutes(time.m); date.setSeconds(time.s); return {date: date, msg: a.msg, type: "e"}; - }); - // add holidays & other events - (require("Storage").readJSON("calendar.days.json",1) || []).forEach(d => { - const date = new Date(d.date); - const o = {date: date, msg: d.name, type: d.type}; - if (d.repeat) { - o.repeat = d.repeat; - } - events.push(o); - }); + })); }; const loadSettings = () => { @@ -280,14 +280,12 @@ const showMenu = function() { setUI(); }, /*LANG*/"Exit": () => load(), - /*LANG*/"Settings": () => { - const appSettings = eval(require('Storage').read('calendar.settings.js')); - appSettings(() => { + /*LANG*/"Settings": () => + eval(require('Storage').read('calendar.settings.js'))(() => { loadSettings(); loadEvents(); showMenu(); - }); - }, + }), }; if (require("Storage").read("alarm.app.js")) { menu[/*LANG*/"Launch Alarms"] = () => { diff --git a/apps/calendar/metadata.json b/apps/calendar/metadata.json index e263efe35..895f8f7aa 100644 --- a/apps/calendar/metadata.json +++ b/apps/calendar/metadata.json @@ -1,7 +1,7 @@ { "id": "calendar", "name": "Calendar", - "version": "0.16", + "version": "0.17", "description": "Monthly calendar, displays holidays uploaded from the web interface and scheduled events.", "icon": "calendar.png", "screenshots": [{"url":"screenshot_calendar.png"}], diff --git a/apps/circlesclock/settings.js b/apps/circlesclock/settings.js index 63a2b0f93..ae090c1d7 100644 --- a/apps/circlesclock/settings.js +++ b/apps/circlesclock/settings.js @@ -30,7 +30,6 @@ }, /*LANG*/'show widgets': { value: !!settings.showWidgets, - format: () => (settings.showWidgets ? 'Yes' : 'No'), onchange: x => save('showWidgets', x), }, /*LANG*/'update interval': { @@ -45,7 +44,6 @@ }, /*LANG*/'show big weather': { value: !!settings.showBigWeather, - format: () => (settings.showBigWeather ? 'Yes' : 'No'), onchange: x => save('showBigWeather', x), }, /*LANG*/'colorize icons': ()=>showCircleMenus() @@ -87,8 +85,7 @@ const colorizeIconKey = circleName + "colorizeIcon"; menu[/*LANG*/'circle ' + circleId] = { value: settings[colorizeIconKey] || false, - format: () => (settings[colorizeIconKey]? /*LANG*/'Yes': /*LANG*/'No'), - onchange: x => save(colorizeIconKey, x), + onchange: x => save(colorizeIconKey, x), }; } E.showMenu(menu); diff --git a/apps/clicompleteclk/settings.js b/apps/clicompleteclk/settings.js index 2df20ed3e..0213ead6e 100644 --- a/apps/clicompleteclk/settings.js +++ b/apps/clicompleteclk/settings.js @@ -9,7 +9,6 @@ '': { 'title': 'CLI complete clk' }, 'Show battery': { value: "battery" in settings ? settings.battery : false, - format: () => (settings.battery ? 'Yes' : 'No'), onchange: () => { settings.battery = !settings.battery; save('battery', settings.battery); @@ -27,7 +26,6 @@ }, 'Show weather': { value: "weather" in settings ? settings.weather : false, - format: () => (settings.weather ? 'Yes' : 'No'), onchange: () => { settings.weather = !settings.weather; save('weather', settings.weather); @@ -35,7 +33,6 @@ }, 'Show steps': { value: "steps" in settings ? settings.steps : false, - format: () => (settings.steps ? 'Yes' : 'No'), onchange: () => { settings.steps = !settings.steps; save('steps', settings.steps); @@ -43,7 +40,6 @@ }, 'Show heartrate': { value: "heartrate" in settings ? settings.heartrate : false, - format: () => (settings.heartrate ? 'Yes' : 'No'), onchange: () => { settings.heartrate = !settings.heartrate; save('heartrate', settings.heartrate); diff --git a/apps/dragboard/ChangeLog b/apps/dragboard/ChangeLog index 68cd82cfa..77cc63c98 100644 --- a/apps/dragboard/ChangeLog +++ b/apps/dragboard/ChangeLog @@ -8,3 +8,4 @@ 0.08: Catch and discard swipe events on fw2v19 and up (as well as some cutting edge 2v18 ones), allowing compatability with the Back Swipe app. 0.09: Fix colors settings, where color was stored as string instead of the expected int. +0.10: Fix touch region for letters diff --git a/apps/dragboard/lib.js b/apps/dragboard/lib.js index 78ef11bd4..2e40f3a77 100644 --- a/apps/dragboard/lib.js +++ b/apps/dragboard/lib.js @@ -107,7 +107,7 @@ exports.input = function(options) { "ram"; // ABCDEFGHIJKLMNOPQRSTUVWXYZ // Choose character by draging along red rectangle at bottom of screen - if (event.y >= ( (R.y+R.h) - 12 )) { + if (event.y >= ( (R.y+R.h) - 26 )) { // Translate x-position to character if (event.x < ABCPADDING) { abcHL = 0; } else if (event.x >= 176-ABCPADDING) { abcHL = 25; } @@ -139,7 +139,7 @@ exports.input = function(options) { // 12345678901234567890 // Choose number or puctuation by draging on green rectangle - else if ((event.y < ( (R.y+R.h) - 12 )) && (event.y > ( (R.y+R.h) - 52 ))) { + else if ((event.y < ( (R.y+R.h) - 26 )) && (event.y > ( (R.y+R.h) - 52 ))) { // Translate x-position to character if (event.x < NUMPADDING) { numHL = 0; } else if (event.x > 176-NUMPADDING) { numHL = NUM.length-1; } diff --git a/apps/dragboard/metadata.json b/apps/dragboard/metadata.json index 090c37a01..c4596d7bd 100644 --- a/apps/dragboard/metadata.json +++ b/apps/dragboard/metadata.json @@ -1,6 +1,6 @@ { "id": "dragboard", "name": "Dragboard", - "version":"0.09", + "version":"0.10", "description": "A library for text input via swiping keyboard", "icon": "app.png", "type":"textinput", diff --git a/apps/drained/ChangeLog b/apps/drained/ChangeLog index c7fd27981..8d196b10d 100644 --- a/apps/drained/ChangeLog +++ b/apps/drained/ChangeLog @@ -2,3 +2,4 @@ 0.02: Allow boot exceptions, e.g. to load DST 0.03: Permit exceptions to load in low-power mode, e.g. daylight saving time. Also avoid polluting global scope. +0.04: Enhance menu: enable bluetooth, visit settings & visit recovery diff --git a/apps/drained/app.js b/apps/drained/app.js index e27fcb1d1..37cc8c71d 100644 --- a/apps/drained/app.js +++ b/apps/drained/app.js @@ -61,14 +61,13 @@ var reload = function () { nextDraw = undefined; }, btn: function () { - E.showPrompt("Restore watch to full power?").then(function (v) { - if (v) { - drainedRestore(); - } - else { - reload(); - } - }); + var menu = { + "Restore to full power": drainedRestore, + "Enable BLE": function () { return NRF.wake(); }, + "Settings": function () { return load("setting.app.js"); }, + "Recovery": function () { return Bangle.showRecoveryMenu(); }, + }; + E.showMenu(menu); } }); Bangle.CLOCK = 1; diff --git a/apps/drained/app.ts b/apps/drained/app.ts index f4d33bc44..ed40262fa 100644 --- a/apps/drained/app.ts +++ b/apps/drained/app.ts @@ -79,13 +79,13 @@ const reload = () => { nextDraw = undefined; }, btn: () => { - E.showPrompt("Restore watch to full power?").then(v => { - if(v){ - drainedRestore(); - }else{ - reload(); - } - }) + const menu = { + "Restore to full power": drainedRestore, + "Enable BLE": () => NRF.wake(), + "Settings": () => load("setting.app.js"), + "Recovery": () => Bangle.showRecoveryMenu(), + }; + E.showMenu(menu); } }); Bangle.CLOCK=1; diff --git a/apps/drained/metadata.json b/apps/drained/metadata.json index 6dfdac78d..ea18f6fcf 100644 --- a/apps/drained/metadata.json +++ b/apps/drained/metadata.json @@ -1,12 +1,10 @@ { "id": "drained", "name": "Drained", - "version": "0.03", + "version": "0.04", "description": "Switches to displaying a simple clock when the battery percentage is low, and disables some peripherals", "readme": "README.md", "icon": "icon.png", - "type": "clock", - "tags": "clock", "supports": ["BANGLEJS2"], "allow_emulator": true, "storage": [ diff --git a/apps/forge/ChangeLog b/apps/forge/ChangeLog index 263d4078d..0c651c90b 100644 --- a/apps/forge/ChangeLog +++ b/apps/forge/ChangeLog @@ -1 +1,2 @@ 0.01: attempt to import +0.02: Make it possible for Fastload Utils to fastload into this app. diff --git a/apps/forge/forge.app.js b/apps/forge/forge.app.js index b972e13bb..b179fb540 100644 --- a/apps/forge/forge.app.js +++ b/apps/forge/forge.app.js @@ -1,5 +1,7 @@ // App Forge +"Bangle.loadWidgets()"; // Facilitates fastloading to this app via Fastload Utils, while still not loading widgets on standard `load` calls. + st = require('Storage'); l = /^a\..*\.js$/; diff --git a/apps/forge/metadata.json b/apps/forge/metadata.json index 03671a647..6e13a4df3 100644 --- a/apps/forge/metadata.json +++ b/apps/forge/metadata.json @@ -1,6 +1,6 @@ { "id": "forge", "name": "App Forge", - "version":"0.01", + "version":"0.02", "description": "Easy way to run development versions of your apps", "icon": "app.png", "readme": "README.md", diff --git a/apps/fuzzyw/fuzzyw.settings.js b/apps/fuzzyw/fuzzyw.settings.js index 8feb30bfb..535f91d67 100644 --- a/apps/fuzzyw/fuzzyw.settings.js +++ b/apps/fuzzyw/fuzzyw.settings.js @@ -23,7 +23,6 @@ '< Back': back, 'Show Widgets': { value: settings.showWidgets, - format: () => (settings.showWidgets ? 'Yes' : 'No'), onchange: () => { settings.showWidgets = !settings.showWidgets; save(); diff --git a/apps/fwupdate/custom.html b/apps/fwupdate/custom.html index 066d658be..b917da87b 100644 --- a/apps/fwupdate/custom.html +++ b/apps/fwupdate/custom.html @@ -88,29 +88,34 @@ function onInit(device) { document.getElementById("fw-unknown").style = "display:none"; document.getElementById("fw-ok").style = ""; } - Puck.eval("E.CRC32(E.memoryArea(0xF7000,0x7000))", crc => { - console.log("DFU CRC = "+crc); - var version = `unknown (CRC ${crc})`; + Puck.eval("[E.CRC32(E.memoryArea(0xF7000,0x6000)),E.CRC32(E.memoryArea(0xF7000,0x7000))]", crcs => { + console.log("DFU CRC (6 pages) = "+crcs[0]); + console.log("DFU CRC (7 pages) = "+crcs[1]); + var version = `unknown (CRC ${crcs[1]})`; var ok = true; - if (crc==1339551013) { version = "2v10.219"; ok = false; } - if (crc==1207580954) { version = "2v10.236"; ok = false; } - if (crc==3435933210) version = "2v11.52"; - if (crc==46757280) version = "2v11.58"; - if (crc==3508163280 || crc==1418074094) version = "2v12"; - if (crc==4056371285) version = "2v13"; - if (crc==1038322422) version = "2v14"; - if (crc==2560806221) version = "2v15"; - if (crc==2886730689) version = "2v16"; - if (crc==156320890) version = "2v17"; - if (crc==4012421318) version = "2v18"; - if (crc==1856454048) version = "2v19"; - if (crc==2893810756 || crc==1273571156) version = "2v20"; + if (crcs[0] == 1787004733) { // check 6 page CRC - the 7th page isn't used in 2v20 + version = "2v20"; + } else { // for other versions all 7 pages are used, check those + var crc = crcs[1]; + if (crc==1339551013) { version = "2v10.219"; ok = false; } + if (crc==1207580954) { version = "2v10.236"; ok = false; } + if (crc==3435933210) version = "2v11.52"; + if (crc==46757280) version = "2v11.58"; + if (crc==3508163280 || crc==1418074094) version = "2v12"; + if (crc==4056371285) version = "2v13"; + if (crc==1038322422) version = "2v14"; + if (crc==2560806221) version = "2v15"; + if (crc==2886730689) version = "2v16"; + if (crc==156320890) version = "2v17"; + if (crc==4012421318) version = "2v18"; + if (crc==1856454048) version = "2v19"; + } if (!ok) { version += `(⚠ update required)`; } document.getElementById("boot-version").innerHTML = version; var versionNumber = parseFloat(version.replace(".","").replace("v",".")); - if (versionNumber>=2.15) + if (versionNumber>=2.20) document.getElementById("fw-old-bootloader-msg").style.display = "none"; }); } @@ -426,7 +431,7 @@ function handleUpload() { storage:[ {name:"RAM", content:hexJS}, ] - }); + }, { noFinish: true }); } document.getElementById('fileLoader').addEventListener('change', handleFileSelect, false); diff --git a/apps/health/settings.js b/apps/health/settings.js index 88c8061c6..4d6e1a067 100644 --- a/apps/health/settings.js +++ b/apps/health/settings.js @@ -43,7 +43,6 @@ /*LANG*/"Step Goal Notification": { value: "stepGoalNotification" in settings ? settings.stepGoalNotification : false, - format: () => (settings.stepGoalNotification ? 'Yes' : 'No'), onchange: () => { settings.stepGoalNotification = !settings.stepGoalNotification; setSettings(); diff --git a/apps/intervals/ChangeLog b/apps/intervals/ChangeLog new file mode 100644 index 000000000..761350e07 --- /dev/null +++ b/apps/intervals/ChangeLog @@ -0,0 +1,2 @@ +0.01: First Release +0.02: Changing resolution to seconds instead of 5 seconds \ No newline at end of file diff --git a/apps/intervals/intervals.app.js b/apps/intervals/intervals.app.js index da436b104..db20818ad 100644 --- a/apps/intervals/intervals.app.js +++ b/apps/intervals/intervals.app.js @@ -149,9 +149,9 @@ function showMenu() "START" : function() { startSession(); }, "Sets" : { value : settings.sets,min:0,max:20,step:1,onchange : v => { settings.sets=v; } }, "Work minutes" : { value : settings.workmin,min:0,max:59,step:1,onchange : v => { settings.workmin=v; } }, - "Work seconds" : { value : settings.workseg,min:0,max:59,step:5,onchange : v => { settings.workseg=v; } }, + "Work seconds" : { value : settings.workseg,min:0,max:59,step:1,onchange : v => { settings.workseg=v; } }, "Rest minutes" : { value : settings.restmin,min:0,max:59,step:1,onchange : v => { settings.restmin=v; } }, - "Rest seconds" : { value : settings.restseg,min:0,max:59,step:5,onchange : v => { settings.restseg=v; } }, + "Rest seconds" : { value : settings.restseg,min:0,max:59,step:1,onchange : v => { settings.restseg=v; } }, "Signal type" : { value : settings.buzz,format : v => v?"Buzz":"Beep",onchange : v => { settings.buzz=v; }} }; diff --git a/apps/intervals/metadata.json b/apps/intervals/metadata.json index 32c18ae70..542fb1846 100644 --- a/apps/intervals/metadata.json +++ b/apps/intervals/metadata.json @@ -2,7 +2,7 @@ "id": "intervals", "name": "Intervals App", "shortName": "Intervals", - "version": "0.01", + "version": "0.02", "description": "Intervals for training. It is possible to configure work time and rest time and number of sets.", "icon": "intervals.png", "tags": "", diff --git a/apps/lcars/lcars.settings.js b/apps/lcars/lcars.settings.js index db583741f..2ce3e4fc9 100644 --- a/apps/lcars/lcars.settings.js +++ b/apps/lcars/lcars.settings.js @@ -76,7 +76,6 @@ var bg_code = [ }, 'Full Screen': { value: settings.fullscreen, - format: () => (settings.fullscreen ? 'Yes' : 'No'), onchange: () => { settings.fullscreen = !settings.fullscreen; save(); @@ -120,7 +119,6 @@ var bg_code = [ }, 'Disable alarm functionality': { value: settings.disableAlarms, - format: () => (settings.disableAlarms ? 'Yes' : 'No'), onchange: () => { settings.disableAlarms = !settings.disableAlarms; save(); @@ -128,7 +126,6 @@ var bg_code = [ }, 'Disable data pages functionality': { value: settings.disableData, - format: () => (settings.disableData ? 'Yes' : 'No'), onchange: () => { settings.disableData = !settings.disableData; save(); @@ -136,7 +133,6 @@ var bg_code = [ }, 'Random colors on open': { value: settings.randomColors, - format: () => (settings.randomColors ? 'Yes' : 'No'), onchange: () => { settings.randomColors = !settings.randomColors; save(); diff --git a/apps/limelight/limelight.settings.js b/apps/limelight/limelight.settings.js index aacea2f86..fa1d857d2 100644 --- a/apps/limelight/limelight.settings.js +++ b/apps/limelight/limelight.settings.js @@ -27,13 +27,12 @@ } var font_options = ["Limelight","GochiHand","Grenadier","Monoton"]; - + E.showMenu({ '': { 'title': 'Limelight Clock' }, '< Back': back, 'Full Screen': { value: s.fullscreen, - format: () => (s.fullscreen ? 'Yes' : 'No'), onchange: () => { s.fullscreen = !s.fullscreen; save(); @@ -50,7 +49,6 @@ }, 'Vector Font': { value: s.vector, - format: () => (s.vector ? 'Yes' : 'No'), onchange: () => { s.vector = !s.vector; save(); @@ -68,7 +66,6 @@ }, 'Second Hand': { value: s.secondhand, - format: () => (s.secondhand ? 'Yes' : 'No'), onchange: () => { s.secondhand = !s.secondhand; save(); diff --git a/apps/line_clock/ChangeLog b/apps/line_clock/ChangeLog new file mode 100644 index 000000000..504dc0efe --- /dev/null +++ b/apps/line_clock/ChangeLog @@ -0,0 +1 @@ +0.1: init app diff --git a/apps/line_clock/LICENSE b/apps/line_clock/LICENSE new file mode 100644 index 000000000..404cbc7a0 --- /dev/null +++ b/apps/line_clock/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Paul Spenke + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/apps/line_clock/README.md b/apps/line_clock/README.md new file mode 100644 index 000000000..5789acbbc --- /dev/null +++ b/apps/line_clock/README.md @@ -0,0 +1,11 @@ +# Line Clock + +This app displays a simple, different looking, analog clock. It considers the +currently configured "theme" (and may therefore look different than shown in +the screenshot on your watch depending on which theme you prefer). + + + +## License + +[MIT License](LICENSE) diff --git a/apps/line_clock/app-icon.js b/apps/line_clock/app-icon.js new file mode 100644 index 000000000..eaaf719b4 --- /dev/null +++ b/apps/line_clock/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwgYMJh/4AgUD+AeKgIRDj/+n41O/4RQABcfIJYAEKZgAkL4U/8ARNBwIRP/+AGx6YBPSH/4ASPh/A/hfDAAZAHg/8gP/LguSoARHEwIRFiVJkDCFjgRHgEJkg4CcwQjIAAMEHAUDCoIRB46kIHAkH//xLIw4I8eAnCNKHAYAO/xxEABg4ByASPHAkBKAbUE/5xGhP//wRFv4RDOIYIB//ACQr1FHAIRJAA0TCAP/ZwIALgYRJVowRCj/4BIkBLIgABgRHC/KqFaI4RC5MkJBlPR4UECJizJJwoAKCKImVQAwAJv0HL5S6CbwIjLCKMAn4RDh0/LMKMhWaYAKA=")) diff --git a/apps/line_clock/app-icon.png b/apps/line_clock/app-icon.png new file mode 100644 index 000000000..275353812 Binary files /dev/null and b/apps/line_clock/app-icon.png differ diff --git a/apps/line_clock/app-screenshot.png b/apps/line_clock/app-screenshot.png new file mode 100644 index 000000000..9d7413388 Binary files /dev/null and b/apps/line_clock/app-screenshot.png differ diff --git a/apps/line_clock/app.js b/apps/line_clock/app.js new file mode 100644 index 000000000..eadc46fad --- /dev/null +++ b/apps/line_clock/app.js @@ -0,0 +1,287 @@ +const handWidth = 6; +const hourRadius = 4; +const hourWidth = 8; +const hourLength = 40; +const hourSLength = 20; +const radius = 220; +const lineOffset = 115; +const hourOffset = 32; +const numberOffset = 85; +const numberSize = 22; + +const storage = require('Storage'); + +const SETTINGS_FILE = "line_clock.setting.json"; + +let initialSettings = { + showLock: true, + showMinute: true, +}; + +let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || initialSettings; +for (const key in saved_settings) { + initialSettings[key] = saved_settings[key]; +} + +let gWidth = g.getWidth(), gCenterX = gWidth/2; +let gHeight = g.getHeight(), gCenterY = gHeight/2; + +let currentTime = new Date(); +let currentHour = currentTime.getHours(); +let currentMinute = currentTime.getMinutes(); + +let drawTimeout; + +function imgLock() { + return { + width : 16, height : 16, bpp : 1, + transparent : 0, + buffer : E.toArrayBuffer(atob("A8AH4A5wDDAYGBgYP/w//D/8Pnw+fD58Pnw//D/8P/w=")) + }; +} + +/** + * Retrieves the angle of the hour hand for the current time. + * + * @returns {number} The angle of the hour hand in degrees. + */ +function getHourHandAngle() { + let hourHandAngle = 30 * currentHour; + hourHandAngle += 0.5 * currentMinute; + return hourHandAngle; +} + +let hourAngle = getHourHandAngle(); + +/** + * Converts degrees to radians. + * + * @param {number} degrees - The degrees to be converted to radians. + * @return {number} - The equivalent value in radians. + */ +function degreesToRadians(degrees) { + return degrees * (Math.PI / 180); +} + +/** + * Rotates an array of points around a given angle and radius. + * + * @param {Array} points - The array of points to be rotated. + * @param {number} angle - The angle in degrees to rotate the points. + * @param {number} rad - The radius to offset the rotation. + * @returns {Array} - The array of rotated points. + */ +function rotatePoints(points, angle, rad) { + const ang = degreesToRadians(angle); + const hAng = degreesToRadians(hourAngle); + const rotatedPoints = []; + points.map(function(point) { + return { + x: point.x * Math.cos(ang) - point.y * Math.sin(ang), + y: point.x * Math.sin(ang) + point.y * Math.cos(ang) + }; + }).forEach(function(point) { + rotatedPoints.push(point.x + gCenterX - (rad * Math.sin(hAng))); + rotatedPoints.push(point.y + gCenterY + (rad * Math.cos(hAng))); + }); + return rotatedPoints; +} + +/** + * Draws a hand on the canvas. + * + * @function drawHand + * + * @returns {void} + */ +function drawHand() { + g.setColor(0xF800); + const halfWidth = handWidth / 2; + + const points = [{ + x: -halfWidth, + y: -gHeight + }, { + x: halfWidth, + y: -gHeight + }, { + x: halfWidth, + y: gHeight + }, { + x: -halfWidth, + y: gHeight + }]; + + g.fillPolyAA(rotatePoints(points, hourAngle, 0)); +} + +/** + * Retrieves the hour coordinates for a given small flag. + * @param {boolean} small - Determines if the flag is small. + * @returns {Array} - An array of hour coordinates. + */ +function getHourCoordinates(small) { + const dist = small ? (hourSLength - hourLength) : 0; + const halfWidth = hourWidth / 2; + const gh = gHeight + lineOffset; + return [{ + x: -halfWidth, + y: -gh - dist + }, { + x: halfWidth, + y: -gh - dist + }, { + x: halfWidth, + y: -gh + hourLength + }, { + x: -halfWidth, + y: -gh + hourLength + }]; +} + +/** + * Assign the given time to the hour dot on the clock face. + * + * @param {number} a - The time value to assign to the hour dot. + * @return {void} + */ +function hourDot(a) { + const h = gHeight + lineOffset; + const rotatedPoints = rotatePoints( + [{ + x: 0, + y: -h + hourLength - (hourRadius / 2) + }], a, radius + ); + g.fillCircle(rotatedPoints[0], rotatedPoints[1], hourRadius); +} + +/** + * Convert an hour into a number and display it on the clock face. + * + * @param {number} a - The hour to be converted (between 0 and 360 degrees). + */ +function hourNumber(a) { + const h = gHeight + lineOffset; + const rotatedPoints = rotatePoints( + [{ + x: 0, + y: -h + hourLength + hourOffset + }], a, radius + ); + g.drawString(String(a / 30), rotatedPoints[0], rotatedPoints[1]); +} + +/** + * Draws a number on the display. + * + * @param {number} n - The number to be drawn. + * @return {void} + */ +function drawNumber(n) { + const h = gHeight + lineOffset; + const halfWidth = handWidth / 2; + const rotatedPoints = rotatePoints( + [{ + x: 0, + y: -h + hourLength + numberOffset + }], hourAngle, radius + ); + g.setColor(0xF800); + g.fillCircle(rotatedPoints[0], rotatedPoints[1], numberSize+ halfWidth); + g.setColor(g.theme.bg); + g.fillCircle(rotatedPoints[0], rotatedPoints[1], numberSize - halfWidth); + g.setColor(g.theme.fg); + g.setFont("Vector:"+numberSize); + g.drawString(String(n), rotatedPoints[0], rotatedPoints[1]); +} + +const hourPoints = getHourCoordinates(false); +const hourSPoints = getHourCoordinates(true); + +/** + * Draws an hour on a clock face. + * + * @param {number} h - The hour to be drawn on the clock face. + * @return {undefined} + */ +function drawHour(h) { + if (h === 0) { h= 12; } + if (h === 13) { h= 1; } + g.setColor(g.theme.fg); + g.setFont("Vector:32"); + const a = h * 30; + g.fillPolyAA(rotatePoints(hourPoints, a, radius)); + g.fillPolyAA(rotatePoints(hourSPoints, a + 15, radius)); + hourNumber(a); + hourDot(a + 5); + hourDot(a + 10); + hourDot(a + 20); + hourDot(a + 25); +} + +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +} + +function lockListenerBw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + draw(); +} +Bangle.on('lock', lockListenerBw); + +Bangle.setUI({ + mode : "clock", + // TODO implement https://www.espruino.com/Bangle.js+Fast+Load + // remove : function() { + // Bangle.removeListener('lock', lockListenerBw); + // if (drawTimeout) clearTimeout(drawTimeout); + // drawTimeout = undefined; + // } +}); + +/** + * Draws a clock on the canvas using the current time. + * + * @return {undefined} + */ +function draw() { + queueDraw(); + currentTime = new Date(); + currentHour = currentTime.getHours(); + if (currentHour > 12) { + currentHour -= 12; + } + currentMinute = currentTime.getMinutes(); + + hourAngle = getHourHandAngle(); + + g.clear(); + g.setFontAlign(0, 0); + + g.setColor(g.theme.bg); + g.fillRect(0, 0, gWidth, gHeight); + + if(initialSettings.showLock && Bangle.isLocked()){ + g.setColor(g.theme.fg); + g.drawImage(imgLock(), gWidth-16, 2); + } + + drawHour(currentHour); + drawHour(currentHour-1); + drawHour(currentHour+1); + + + drawHand(); + + if(initialSettings.showMinute){ + drawNumber(currentMinute); + } +} + +draw(); diff --git a/apps/line_clock/metadata.json b/apps/line_clock/metadata.json new file mode 100644 index 000000000..01393efdf --- /dev/null +++ b/apps/line_clock/metadata.json @@ -0,0 +1,19 @@ +{ "id": "line_clock", + "name": "Line Clock", + "shortName":"Line Clock", + "version":"0.1", + "description": "a readable analog clock", + "icon": "app-icon.png", + "type": "clock", + "tags": "clock", + "supports" : ["BANGLEJS2"], + "allow_emulator": true, + "screenshots": [{"url":"app-screenshot.png"}], + "readme": "README.md", + "storage": [ + {"name":"line_clock.app.js","url":"app.js"}, + {"name":"line_clock.img","url":"app-icon.js","evaluate":true}, + {"name":"line_clock.settings.js","url":"settings.js"} + ], + "data":[{"name":"line_clock.setting.json"}] +} diff --git a/apps/line_clock/settings.js b/apps/line_clock/settings.js new file mode 100644 index 000000000..5da04e959 --- /dev/null +++ b/apps/line_clock/settings.js @@ -0,0 +1,37 @@ +(function(back) { + const SETTINGS_FILE = "line_clock.setting.json"; + + // initialize with default settings... + const storage = require('Storage') + let settings = { + showLock: true, + showMinute: true, + }; + let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings; + for (const key in saved_settings) { + settings[key] = saved_settings[key] + } + + function save() { + storage.write(SETTINGS_FILE, settings) + } + + E.showMenu({ + '': { 'title': 'Line Clock' }, + '< Back': back, + 'Show Lock': { + value: settings.showLock, + onchange: () => { + settings.showLock = !settings.showLock; + save(); + }, + }, + 'Show Minute': { + value: settings.showMinute, + onchange: () => { + settings.showMinute = !settings.showMinute; + save(); + }, + } + }); + }) diff --git a/apps/linuxclock/settings.js b/apps/linuxclock/settings.js index 116253fda..8bcf0ae0f 100644 --- a/apps/linuxclock/settings.js +++ b/apps/linuxclock/settings.js @@ -32,7 +32,6 @@ }, 'Show Lock': { value: settings.showLock, - format: () => (settings.showLock ? 'Yes' : 'No'), onchange: () => { settings.showLock = !settings.showLock; save(); @@ -40,7 +39,6 @@ }, 'Hide Colon': { value: settings.hideColon, - format: () => (settings.hideColon ? 'Yes' : 'No'), onchange: () => { settings.hideColon = !settings.hideColon; save(); diff --git a/apps/messageicons/lib.js b/apps/messageicons/lib.js index 4aaddd8c2..c47c94402 100644 --- a/apps/messageicons/lib.js +++ b/apps/messageicons/lib.js @@ -67,4 +67,4 @@ exports.getColor = function(msg,options) { "youtube": "#f00", // https://www.youtube.com/howyoutubeworks/resources/brand-resources/#logos-icons-and-colors }[s]||options.default; }; - \ No newline at end of file + diff --git a/apps/mosaic/mosaic.settings.js b/apps/mosaic/mosaic.settings.js index dcf725b84..ee80cf950 100644 --- a/apps/mosaic/mosaic.settings.js +++ b/apps/mosaic/mosaic.settings.js @@ -25,7 +25,6 @@ '< Back': back, 'Show Widgets': { value: settings.showWidgets, - format: () => (settings.showWidgets ? 'Yes' : 'No'), onchange: () => { settings.showWidgets = !settings.showWidgets; save(); diff --git a/apps/openstmap/ChangeLog b/apps/openstmap/ChangeLog index 1b0edaf34..f0a1e5c5a 100644 --- a/apps/openstmap/ChangeLog +++ b/apps/openstmap/ChangeLog @@ -32,3 +32,6 @@ 0.25: Enable scaled image filtering on 2v19+ firmware 0.26: Ensure that when redrawing, we always cancel any in-progress track draw 0.27: Display message if no map is installed +0.28: Fix rounding errors +0.29: Keep exit at bottom of menu + Speed up latLonToXY for track rendering \ No newline at end of file diff --git a/apps/openstmap/app.js b/apps/openstmap/app.js index d66936c29..2d14fbd2a 100644 --- a/apps/openstmap/app.js +++ b/apps/openstmap/app.js @@ -65,14 +65,16 @@ function redraw() { // Draw the POIs function drawPOI() { + let waypoints; try { - var waypoints = require("waypoints").load(); + waypoints = require("waypoints").load(); } catch (ex) { // Waypoints module not available. return; } g.setFont("Vector", 18); waypoints.forEach((wp, idx) => { + if (wp.lat === undefined || wp.lon === undefined) return; var p = m.latLonToXY(wp.lat, wp.lon); var sz = 2; g.setColor(0,0,0); @@ -80,7 +82,7 @@ function drawPOI() { g.setColor(0,0,0); g.drawString(wp.name, p.x, p.y); //print(wp.name); - }) + }); } function isInside(rect, e, w, h) { diff --git a/apps/openstmap/interface.html b/apps/openstmap/interface.html index 3427fa882..df5c0acc9 100644 --- a/apps/openstmap/interface.html +++ b/apps/openstmap/interface.html @@ -45,7 +45,11 @@