diff --git a/README.md b/README.md index 45643d6df..a0a5e5ba4 100644 --- a/README.md +++ b/README.md @@ -120,7 +120,7 @@ Apps are listed in the Bangle.js menu, accessible from a clock app via the middl #### `app-icon.js` -The icon image and short description is used in the menu entry as selection possibility. +The icon image and short description is used in Bangle.js's launcher. Use the Espruino [image converter](https://www.espruino.com/Image+Converter) and upload your `app.png` file. @@ -136,11 +136,14 @@ Follow this steps to create a readable icon as image string. Replace this line with the image converter output: ``` -require("heatshrink").decompress(atob("mEwwJC/AH4A/AH4AgA==")); +require("heatshrink").decompress(atob("mEwwJC/AH4A/AH4AgA==")) ``` -Keep in mind to use this converter for creating images you like to draw with `g.drawImage()` with your app. +You can also use this converter for creating images you like to draw with `g.drawImage()` with your app. +Apps that need widgets can call `Bangle.loadWidgets()` **once** at startup to load +them, and then `Bangle.drawWidgets()` to draw them onto the screen whenever the app +has call to completely clear the screen. Widgets themselves will update as and when needed. ### Widget Example @@ -149,6 +152,23 @@ The widget example is available in [`apps/_example_widget`](apps/_example_widget * `add_to_apps.json` - insert into `apps.json`, describes the widget to bootloader and loader * `widget.js` - widget code +Widgets are just small bits of code that run whenever an app that supports them +calls `Bangle.loadWidgets()`. If they want to display something in the 24px high +widget bars at the top and bottom of the screen they can add themselves to +the global `WIDGETS` array with: + +``` +WIDGETS["mywidget"]={ + area:"tl", // tl (top left), tr (top right), bl (bottom left), br (bottom right) + width: 24, // how wide is the widget? You can change this and call Bangle.drawWidgets() to re-layout + draw:draw // called to draw the widget +}; +``` + +When the widget is to be drawn, `x` and `y` values are set up in `WIDGETS["mywidget"]` +and `draw` can then use `this.x` and `this.y` to figure out where it needs to draw to. + + ### `app.info` format This is the file that's **auto-generated** and loaded onto Bangle.js by the App Loader, @@ -306,6 +326,11 @@ See [apps/gpsrec/interface.html](the GPS Recorder) for a full example. - 'Welcome' apps define a file called `welcome.js` which the booloader picks up. This then chain-loads the welcome app itself. +- 'Alarm' apps define a file called `alarm.js` which handles the actual alarm window. + +- Locale is handled by `require("locale")`. An app may create a `locale` file in Storage which is +a module that overwrites Bangle.js's default locale. + ### Graphic areas diff --git a/apps.json b/apps.json index ab6542ad3..3ffdbe5d4 100644 --- a/apps.json +++ b/apps.json @@ -2,7 +2,7 @@ { "id": "boot", "name": "Bootloader", "icon": "bootloader.png", - "version":"0.07", + "version":"0.10", "description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings", "tags": "tool,system", "type":"bootloader", @@ -28,7 +28,7 @@ { "id": "about", "name": "About", "icon": "app.png", - "version":"0.01", + "version":"0.04", "description": "Bangle.js About page - showing software version, stats, and a collaborative mural from the Bangle.js KickStarter backers", "tags": "tool,system", "allow_emulator":true, @@ -37,10 +37,23 @@ {"name":"about.img","url":"app-icon.js","evaluate":true} ] }, + { "id": "locale", + "name": "Languages", + "icon": "locale.png", + "version":"0.01", + "description": "Translations for different countries", + "tags": "tool,system,locale,translate", + "type": "locale", + "custom":"locale.html", + "storage": [ + {"name":"locale"} + ], + "sortorder" : -10 + }, { "id": "welcome", "name": "Welcome", "icon": "app.png", - "version":"0.03", + "version":"0.04", "description": "Appears at first boot and explains how to use Bangle.js", "tags": "start,welcome", "allow_emulator":true, @@ -53,7 +66,7 @@ { "id": "gbridge", "name": "Gadgetbridge", "icon": "app.png", - "version":"0.03", + "version":"0.04", "description": "The default notification handler for Gadgetbridge notifications from Android", "tags": "tool,system,android,widget", "storage": [ @@ -79,7 +92,7 @@ { "id": "setting", "name": "Settings", "icon": "settings.png", - "version":"0.05", + "version":"0.06", "description": "A menu for setting up Bangle.js", "tags": "tool,system", "storage": [ @@ -93,7 +106,7 @@ "name": "Default Alarm", "shortName":"Alarms", "icon": "app.png", - "version":"0.02", + "version":"0.04", "description": "Set and respond to alarms", "tags": "tool,alarm,widget", "storage": [ @@ -130,16 +143,17 @@ {"name":"aclock.img","url":"clock-analog-icon.js","evaluate":true} ] }, - { "id": "clck3x2", - "name": "3x2 Pixel Clock", - "icon": "clock3x2.png", - "version":"0.03", - "description": "This is a simple clock using minimalistic 3x2 pixel numerical digits", + { "id": "clock2x3", + "name": "2x3 Pixel Clock", + "icon": "clock2x3.png", + "version":"0.04", + "description": "This is a simple clock using minimalist 2x3 pixel numerical digits", "tags": "clock", + "type": "clock", "allow_emulator":true, "storage": [ - {"name":"clck3x2.app.js","url":"clock3x2.js"}, - {"name":"clck3x2.img","url":"clock3x2-icon.js","evaluate":true} + {"name":"clock2x3.app.js","url":"clock2x3-app.js"}, + {"name":"clock2x3.img","url":"clock2x3-icon.js","evaluate":true} ] }, { "id": "trex", @@ -236,7 +250,7 @@ { "id": "gpsrec", "name": "GPS Recorder", "icon": "app.png", - "version":"0.04", + "version":"0.06", "interface": "interface.html", "description": "Application that allows you to record a GPS track. Can run in background", "tags": "tool,outdoors,gps,widget", @@ -247,6 +261,20 @@ {"name":"gpsrec.wid.js","url":"widget.js"} ] }, + { "id": "heart", + "name": "Heart Rate Recorder", + "icon": "app.png", + "version":"0.01", + "interface": "interface.html", + "description": "Application that allows you to record your heart rate. Can run in background", + "tags": "tool,health,widget", + "storage": [ + {"name":"heart.app.js","url":"app.js"}, + {"name":"heart.json","url":"app-settings.json","evaluate":true}, + {"name":"heart.img","url":"app-icon.js","evaluate":true}, + {"name":"heart.wid.js","url":"widget.js"} + ] + }, { "id": "slevel", "name": "Spirit Level", "icon": "spiritlevel.png", @@ -272,7 +300,7 @@ { "id": "widbat", "name": "Battery Level Widget", "icon": "widget.png", - "version":"0.02", + "version":"0.04", "description": "Show the current battery level and charging status in the top right of the clock", "tags": "widget,battery", "type":"widget", @@ -283,7 +311,7 @@ { "id": "widbt", "name": "Bluetooth Widget", "icon": "widget.png", - "version":"0.01", + "version":"0.03", "description": "Show the current Bluetooth connection status in the top right of the clock", "tags": "widget,bluetooth", "type":"widget", @@ -305,7 +333,7 @@ { "id": "widhrm", "name": "Simple Heart Rate widget", "icon": "widget.png", - "version":"0.01", + "version":"0.03", "description": "When the screen is on, the widget turns on the heart rate monitor and displays the current heart rate (or last known in grey). For this to work well you'll need at least a 15 second LCD Timeout.", "tags": "health,widget", "type": "widget", @@ -316,7 +344,7 @@ { "id": "stetho", "name": "Stethoscope", "icon": "stetho.png", - "version":"0.0198", + "version":"0.01", "description": "Hear your heart rate", "tags": "health", "storage": [ @@ -459,7 +487,7 @@ { "id": "widnceu", "name": "NCEU Logo Widget", "icon": "widget.png", - "version":"0.01", + "version":"0.02", "description": "Show the NodeConf EU logo in the top left", "tags": "widget", "type":"widget", @@ -467,9 +495,6 @@ {"name":"widnceu.wid.js","url":"widget.js"} ] }, - - - { "id": "sclock", "name": "Simple Clock", "icon": "clock-simple.png", @@ -638,7 +663,7 @@ "id": "gpsinfo", "name": "GPS Info", "icon": "gps-info.png", - "version":"0.01", + "version":"0.02", "description": "An application that displays information about altitude, lat/lon, satellites and time", "tags": "gps", "type": "app", @@ -691,7 +716,7 @@ { "id": "widclk", "name": "Digital clock widget", "icon": "widget.png", - "version":"0.01", + "version":"0.03", "description": "A simple digital clock widget", "tags": "widget,clock", "type":"widget", @@ -702,7 +727,7 @@ { "id": "widpedom", "name": "Pedometer widget", "icon": "widget.png", - "version":"0.06", + "version":"0.08", "description": "Daily pedometer widget", "tags": "widget", "type":"widget", @@ -753,6 +778,7 @@ { "id": "flagrse", "name": "Espruino Flag Raiser", "icon": "app.png", + "version":"0.01", "description": "App to send a command to another Espruino to cause it to raise a flag", "tags": "", "storage": [ @@ -774,6 +800,19 @@ {"name":"pipboy.img","url":"app-icon.js","evaluate":true} ] }, + { "id": "torch", + "name": "Torch", + "shortName":"Torch", + "icon": "app.png", + "version":"0.01", + "description": "Turns screen white to help you see in the dark. Select from the launcher or press BTN3 four times in quick succession to start when in normal clock mode", + "tags": "tool,torch", + "storage": [ + {"name":"torch.app.js","url":"app.js"}, + {"name":"torch.wid.js","url":"widget.js"}, + {"name":"torch.img","url":"app-icon.js","evaluate":true} + ] + }, { "id": "wohrm", "name": "Workout Heart Rate Monitor", "icon": "wohrm.png", @@ -783,9 +822,19 @@ "type": "app", "allow_emulator":true, "storage": [ - {"name":"+wohrm","url":"wohrm.json"}, - {"name":"-wohrm","url":"wohrm.js"}, - {"name":"*wohrm","url":"wohrm-icon.js","evaluate":true} + {"name":"wohrm.app.js","url":"wohrm.js"}, + {"name":"wohrm.img","url":"wohrm-icon.js","evaluate":true} + ] + }, + { "id": "widid", + "name": "Bluetooth ID Widget", + "icon": "widget.png", + "version":"0.02", + "description": "Display the last two tuple of your Bangle.js MAC address in the widget section. This is useful for figuring out which Bangle.js to connect to if you have more than one Bangle.js!", + "tags": "widget,address,mac", + "type":"widget", + "storage": [ + {"name":"widid.wid.js","url":"widget.js"} ] } ] diff --git a/apps/_example_app/add_to_apps.json b/apps/_example_app/add_to_apps.json index c75f9ed7d..ca75a7bd8 100644 --- a/apps/_example_app/add_to_apps.json +++ b/apps/_example_app/add_to_apps.json @@ -7,8 +7,7 @@ "description": "A detailed description of my great app", "tags": "", "storage": [ - {"name":"+7chname","url":"app.json"}, - {"name":"-7chname","url":"app.js"}, - {"name":"*7chname","url":"app-icon.js","evaluate":true} + {"name":"7chname.app.js","url":"app.js"}, + {"name":"7chname.img","url":"app-icon.js","evaluate":true} ] } diff --git a/apps/_example_widget/add_to_apps.json b/apps/_example_widget/add_to_apps.json index 3011fe744..5d0057960 100644 --- a/apps/_example_widget/add_to_apps.json +++ b/apps/_example_widget/add_to_apps.json @@ -8,7 +8,6 @@ "tags": "widget", "type": "widget", "storage": [ - {"name":"+7chname","url":"widget.json"}, - {"name":"=7chname","url":"widget.js"} + {"name":"7chname.wid.js","url":"widget.js"} ] } diff --git a/apps/_example_widget/widget.js b/apps/_example_widget/widget.js index 9ee9bfee8..3893e3096 100644 --- a/apps/_example_widget/widget.js +++ b/apps/_example_widget/widget.js @@ -1,17 +1,16 @@ /* run widgets in their own function scope so they don't interfere with currently-running apps */ (() => { - // add the width - var xpos = WIDGETPOS.tr-24;/**/; - WIDGETPOS.tr-= 28;/* the widget width plus some extra pixel to keep distance to others */; - - // draw your widget at xpos function draw() { g.reset(); // reset the graphics context to defaults (color/font/etc) // add your code + g.drawString("X", this.x, this.y); } // add your widget - WIDGETS["mywidget"]={draw:draw}; - + WIDGETS["mywidget"]={ + area:"tl", // tl (top left), tr (top right), bl (bottom left), br (bottom right) + width: 28, // how wide is the widget? You can change this and call Bangle.drawWidgets() to re-layout + draw:draw // called to draw the widget + }; })() diff --git a/apps/about/ChangeLog b/apps/about/ChangeLog index 5560f00bc..2c81c0537 100644 --- a/apps/about/ChangeLog +++ b/apps/about/ChangeLog @@ -1 +1,4 @@ 0.01: New App! +0.02: Update version checker for new filename type +0.03: Actual pixels as of 5 Mar 2020 +0.04: Actual pixels as of 9 Mar 2020 diff --git a/apps/about/app.js b/apps/about/app.js index 35ea07828..dc7b0cad8 100644 --- a/apps/about/app.js +++ b/apps/about/app.js @@ -11,13 +11,13 @@ g.drawString("Powered by Espruino",0,y+=4+h); g.drawString("Version "+ENV.VERSION,0,y+=h); g.drawString("Commit "+ENV.GIT_COMMIT,0,y+=h); function getVersion(name,file) { - var j = s.readJSON(file); + var j = s.readJSON(file,1); var v = ("object"==typeof j)?j.version:false; g.drawString(v?(name+" "+(v?"v"+v:"Unknown")):"NO "+name,0,y+=h); } -getVersion("Bootloader","+boot"); -getVersion("Launcher","+launch"); -getVersion("Settings","+setting"); +getVersion("Bootloader","boot.info"); +getVersion("Launcher","launch.info"); +getVersion("Settings","setting.info"); y+=h; g.drawString(MEM.total+" JS Variables available",0,y+=h); @@ -28,6 +28,6 @@ g.setFontAlign(0,-1); g.drawString(NRF.getAddress(),120,232); g.flip(); -// Pixel chooser 'test' image -g.drawImage(require("heatshrink").decompress(atob("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")),0,135); +// Pixel chooser image +g.drawImage(require("heatshrink").decompress(atob("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")),0,135); g.flip(); diff --git a/apps/alarm/ChangeLog b/apps/alarm/ChangeLog index 248faad95..67feb024f 100644 --- a/apps/alarm/ChangeLog +++ b/apps/alarm/ChangeLog @@ -1,2 +1,4 @@ 0.01: New App! 0.02: Fix issues with alarm scheduling +0.03: More alarm scheduling issues +0.04: Tweaks for variable size widget system diff --git a/apps/alarm/alarm.js b/apps/alarm/alarm.js index 70dc75b49..7f0027bc8 100644 --- a/apps/alarm/alarm.js +++ b/apps/alarm/alarm.js @@ -10,7 +10,7 @@ function formatTime(t) { function getCurrentHr() { var time = new Date(); - return time.getHours()+(time.getMinutes()/60); + return time.getHours()+(time.getMinutes()/60)+(time.getSeconds()/3600); } function showAlarm(alarm) { @@ -47,8 +47,8 @@ function showAlarm(alarm) { // Check for alarms var day = (new Date()).getDate(); -var hr = getCurrentHr(); -var alarms = require("Storage").readJSON("alarm.json")||[]; +var hr = getCurrentHr()+10000; // get current time - 10s in future to ensure we alarm if we've started the app a tad early +var alarms = require("Storage").readJSON("alarm.json",1)||[]; var active = alarms.filter(a=>a.on&&(a.hr { - var alarms = require('Storage').readJSON('alarm.json')||[]; + var alarms = require('Storage').readJSON('alarm.json',1)||[]; alarms = alarms.filter(alarm=>alarm.on); - if (!alarms.length) return; + if (!alarms.length) return; // no alarms, no widget! delete alarms; - // add the width - var xpos = WIDGETPOS.tl; - WIDGETPOS.tl += 24;/* the widget width plus some extra pixel to keep distance to others */; - - // draw your widget at xpos - // add the widget - WIDGETS["alarm"]={draw:function() { + WIDGETS["alarm"]={area:"tl",width:24,draw:function() { g.setColor(-1); - g.drawImage(atob("GBgBAAAAAAAAABgADhhwDDwwGP8YGf+YMf+MM//MM//MA//AA//AA//AA//AA//AA//AB//gD//wD//wAAAAADwAABgAAAAAAAAA"),xpos,0); + g.drawImage(atob("GBgBAAAAAAAAABgADhhwDDwwGP8YGf+YMf+MM//MM//MA//AA//AA//AA//AA//AA//AB//gD//wD//wAAAAADwAABgAAAAAAAAA"),this.x,this.y); }}; })() diff --git a/apps/boot/ChangeLog b/apps/boot/ChangeLog index 3c6dfec29..093f017b2 100644 --- a/apps/boot/ChangeLog +++ b/apps/boot/ChangeLog @@ -4,3 +4,7 @@ 0.05: Add Welcome screen on boot 0.06: Disable GPS time log messages, add (default=1) setting to hide log messages 0.07: Fix issues with alarm scheduling +0.08: Fix issues if BLE=off, 'Make Connectable' is chosen, and the loader resets Bangle.js (fix #108) +0.09: Only check GPS for time after a fresh boot +0.10: Stop users calling save() (fix #125) + If Debug info is set to 'show' don't move to Terminal if connected! diff --git a/apps/boot/boot0.js b/apps/boot/boot0.js index 7741dd376..cb8d49ba0 100644 --- a/apps/boot/boot0.js +++ b/apps/boot/boot0.js @@ -1,7 +1,7 @@ // This ALWAYS runs at boot E.setFlags({pretokenise:1}); // Load settings... -var s = require('Storage').readJSON('setting.json')||{}; +var s = require('Storage').readJSON('setting.json',1)||{}; if (s.ble!==false) { if (s.HID) { // Human interface device Bangle.HID = E.toUint8Array(atob("BQEJBqEBhQIFBxngKecVACUBdQGVCIEClQF1CIEBlQV1AQUIGQEpBZEClQF1A5EBlQZ1CBUAJXMFBxkAKXOBAAkFFQAm/wB1CJUCsQLABQwJAaEBhQEVACUBdQGVAQm1gQIJtoECCbeBAgm4gQIJzYECCeKBAgnpgQIJ6oECwA==")); @@ -12,11 +12,12 @@ if (s.blerepl===false) { // If not programmable, force terminal off Bluetooth if (s.log) Terminal.setConsole(true); // if showing debug, force REPL onto terminal else E.setConsole(null,{force:true}); // on new (2v05+) firmware we have E.setConsole which allows a 'null' console } else { - if (s.log) Terminal.setConsole(); // if showing debug, put REPL on terminal (until connection) + if (s.log && !NRF.getSecurityStatus().connected) Terminal.setConsole(); // if showing debug, put REPL on terminal (until connection) else Bluetooth.setConsole(true); // else if no debug, force REPL to Bluetooth } -// we just reset, so BLE should be on -if (s.ble===false) NRF.sleep(); +// we just reset, so BLE should be on. +// Don't disconnect if something is already connected to us +if (s.ble===false && !NRF.getSecurityStatus().connected) NRF.sleep(); // Set time, vibrate, beep, etc if (!s.vibrate) Bangle.buzz=Promise.resolve; if (!s.beep) Bangle.beep=Promise.resolve; @@ -24,45 +25,27 @@ Bangle.setLCDTimeout(s.timeout); if (!s.timeout) Bangle.setLCDPower(1); E.setTimeZone(s.timezone); delete s; +// stop users doing bad things! +global.save = function() { throw new Error("You can't use save() on Bangle.js without overwriting the bootloader!"); } // check for alarms -function checkAlarm() { - var alarms = require('Storage').readJSON('alarm.json')||[]; - var time = new Date(); - var active = alarms.filter(a=>a.on&&(a.last!=time.getDate())); - if (active.length) { - active = active.sort((a,b)=>a.hr-b.hr); - var hr = time.getHours()+(time.getMinutes()/60)+(time.getSeconds()/3600); - if (!require('Storage').read("alarm.js")) { - console.log("No alarm app!"); - require('Storage').write('alarm.json',"[]") - } else { - var t = 3600000*(active[0].hr-hr); - if (t<1000) t=1000; - /* execute alarm at the correct time. We avoid execing immediately - since this code will get called AGAIN when alarm.js is loaded. alarm.js - will then clearInterval() to get rid of this call so it can proceed - normally. */ - setTimeout(function() { - load("alarm.js"); - },t); - } +var alarms = require('Storage').readJSON('alarm.json',1)||[]; +var time = new Date(); +var active = alarms.filter(a=>a.on&&(a.last!=time.getDate())); +if (active.length) { + active = active.sort((a,b)=>a.hr-b.hr); + var hr = time.getHours()+(time.getMinutes()/60)+(time.getSeconds()/3600); + if (!require('Storage').read("alarm.js")) { + console.log("No alarm app!"); + require('Storage').write('alarm.json',"[]") + } else { + var t = 3600000*(active[0].hr-hr); + if (t<1000) t=1000; + /* execute alarm at the correct time. We avoid execing immediately + since this code will get called AGAIN when alarm.js is loaded. alarm.js + will then clearInterval() to get rid of this call so it can proceed + normally. */ + setTimeout(function() { + load("alarm.js"); + },t); } } -// check to see if our clock is wrong - if it is use GPS time -if ((new Date()).getFullYear()==1970) { - //console.log("Searching for GPS time"); - Bangle.on('GPS',function cb(g) { - Bangle.setGPSPower(0); - Bangle.removeListener("GPS",cb); - if (!g.time || (g.time.getFullYear()<2000) || - (g.time.getFullYear()==2250)) { - //console.log("GPS receiver's time not set"); - return; - } - setTime(g.time.getTime()/1000); - //console.log("GPS time",g.time.toString()); - checkAlarm(); - }); - Bangle.setGPSPower(1); -} else checkAlarm(); -delete checkAlarm; diff --git a/apps/boot/bootloader.js b/apps/boot/bootloader.js index 07a1623eb..febc4fc19 100644 --- a/apps/boot/bootloader.js +++ b/apps/boot/bootloader.js @@ -1,6 +1,5 @@ // This runs after a 'fresh' boot -var settings={}; -try { settings = require("Storage").readJSON('setting.json'); } catch (e) {} +var settings=require("Storage").readJSON('setting.json',1)||{}; if (!settings.welcomed && require("Storage").read("welcome.js")!==undefined) { setTimeout(()=>load("welcome.js")); } else { @@ -8,16 +7,32 @@ if (!settings.welcomed && require("Storage").read("welcome.js")!==undefined) { var clockApp = settings.clock; if (clockApp) clockApp = require("Storage").read(clockApp) if (!clockApp) { - var clockApps = require("Storage").list(/\.info$/).map(app=>{ - try { return require("Storage").readJSON(app); } - catch (e) {} - }).filter(app=>app.type=="clock").sort((a, b) => a.sortorder - b.sortorder); + var clockApps = require("Storage").list(/\.info$/).map(app=>require("Storage").readJSON(app,1)||{}).filter(app=>app.type=="clock").sort((a, b) => a.sortorder - b.sortorder); if (clockApps && clockApps.length > 0) clockApp = require("Storage").read(clockApps[0].src); delete clockApps; } - if (clockApp) eval(clockApp); - else E.showMessage("No Clock Found"); - delete clockApp; + if (!clockApp) clockApp='E.showMessage("No Clock Found")'; + delete settings; + // check to see if our clock is wrong - if it is use GPS time + if ((new Date()).getFullYear()==1970) { + E.showMessage("Searching for\nGPS time"); + Bangle.on('GPS',function cb(g) { + Bangle.setGPSPower(0); + Bangle.removeListener("GPS",cb); + if (!g.time || (g.time.getFullYear()<2000) || + (g.time.getFullYear()==2250)) { + // GPS receiver's time not set - just boot clock anyway + eval(clockApp);delete clockApp; + return; + } + // We have a GPS time. Set time and reboot (to load alarms properly) + setTime(g.time.getTime()/1000); + load(); + }); + Bangle.setGPSPower(1); + } else { + eval(clockApp); + delete clockApp; + } } -delete settings; diff --git a/apps/clck3x2/ChangeLog b/apps/clock2x3/ChangeLog similarity index 73% rename from apps/clck3x2/ChangeLog rename to apps/clock2x3/ChangeLog index c10571cae..88876affa 100644 --- a/apps/clck3x2/ChangeLog +++ b/apps/clock2x3/ChangeLog @@ -1,2 +1,3 @@ 0.02: Modified for use with new bootloader and firmware 0.03: Added 'reset' so we don't get the font color from widgets +0.04: Changed name from clck3x2 to clock2x3 diff --git a/apps/clck3x2/clock3x2.js b/apps/clock2x3/clock2x3-app.js similarity index 95% rename from apps/clck3x2/clock3x2.js rename to apps/clock2x3/clock2x3-app.js index 49c18b747..511a7662b 100644 --- a/apps/clck3x2/clock3x2.js +++ b/apps/clock2x3/clock2x3-app.js @@ -76,8 +76,7 @@ function drawTime() { } let t = d.getSeconds()*1000 + d.getMilliseconds(); - let delta = (60000 - t) % 60000; // time till next minute - idTimeout = setTimeout(drawTime, delta); + idTimeout = setTimeout(drawTime, 60000 - t); // time till next minute } // special function to handle display switch on diff --git a/apps/clck3x2/clock3x2-icon.js b/apps/clock2x3/clock2x3-icon.js similarity index 100% rename from apps/clck3x2/clock3x2-icon.js rename to apps/clock2x3/clock2x3-icon.js diff --git a/apps/clck3x2/clock3x2.png b/apps/clock2x3/clock2x3.png similarity index 100% rename from apps/clck3x2/clock3x2.png rename to apps/clock2x3/clock2x3.png diff --git a/apps/files/files.js b/apps/files/files.js index ab8324b43..31353cf96 100644 --- a/apps/files/files.js +++ b/apps/files/files.js @@ -69,7 +69,7 @@ function showApps() { var list = storage.list(/\.info$/).filter((a)=> { return a !== 'setting.info'; }).sort().map((app) => { - var ret = storage.readJSON(app); + var ret = storage.readJSON(app,1)||{}; ret[''] = app; return ret; }); diff --git a/apps/gbridge/Changelog b/apps/gbridge/ChangeLog similarity index 76% rename from apps/gbridge/Changelog rename to apps/gbridge/ChangeLog index 3180266ad..28789ec04 100644 --- a/apps/gbridge/Changelog +++ b/apps/gbridge/ChangeLog @@ -1,3 +1,4 @@ 0.01: Initial version 0.02: Increase contrast (darker notification background, white text) 0.03: Gadgetbridge widget now shows connection state +0.04: Tweaks for variable size widget system diff --git a/apps/gbridge/widget.js b/apps/gbridge/widget.js index 5eb4f70d4..a787d7e0b 100644 --- a/apps/gbridge/widget.js +++ b/apps/gbridge/widget.js @@ -109,19 +109,17 @@ function draw() { g.setColor(-1); if (NRF.getSecurityStatus().connected) - g.drawImage(require("heatshrink").decompress(atob("i0WwgHExAABCIwJCBYwJEBYkIBQ2ACgvzCwoECx/z/AKDD4WD+YLBEIYKCx//+cvnAKCBwU/mc4/8/HYv//Ev+Y4EEAePn43DBQkzn4rCEIoABBIwKHO4cjmczK42I6mqlqEEBQeIBQaDED4IgDUhi6KaBbmIA==")),xpos+1,1); + g.drawImage(require("heatshrink").decompress(atob("i0WwgHExAABCIwJCBYwJEBYkIBQ2ACgvzCwoECx/z/AKDD4WD+YLBEIYKCx//+cvnAKCBwU/mc4/8/HYv//Ev+Y4EEAePn43DBQkzn4rCEIoABBIwKHO4cjmczK42I6mqlqEEBQeIBQaDED4IgDUhi6KaBbmIA==")),this.x+1,this.y+1); else - g.drawImage(require("heatshrink").decompress(atob("i0WwQFC1WgAgYFDAgIFClQFCwEK1W/AoIPB1f+CAMq1f7/WqwQPB/fq1Gq1/+/4dC/2/CAIaB/YbBAAO///qAoX/B4QbBDQQ7BDQQrBAAWoIIIACIIIVC0ECB4cACAZiBAoRtCAoIDBA")),xpos+1,1); + g.drawImage(require("heatshrink").decompress(atob("i0WwQFC1WgAgYFDAgIFClQFCwEK1W/AoIPB1f+CAMq1f7/WqwQPB/fq1Gq1/+/4dC/2/CAIaB/YbBAAO///qAoX/B4QbBDQQ7BDQQrBAAWoIIIACIIIVC0ECB4cACAZiBAoRtCAoIDBA")),this.x+1,this.y+1); } function changed() { - draw(); + WIDGETS["gbridgew"].draw(); g.flip();// turns screen on } NRF.on('connected',changed); NRF.on('disconnected',changed); -var xpos = WIDGETPOS.tl; -WIDGETPOS.tl+=24; -WIDGETS["gbridgew"]={draw:draw}; +WIDGETS["gbridgew"]={area:"tl",width:24,draw:draw}; })(); diff --git a/apps/gpsinfo/ChangeLog b/apps/gpsinfo/ChangeLog new file mode 100644 index 000000000..50d79e72d --- /dev/null +++ b/apps/gpsinfo/ChangeLog @@ -0,0 +1 @@ +0.02: Ensure screen doesn't display garbage at startup diff --git a/apps/gpsinfo/gps-info.js b/apps/gpsinfo/gps-info.js index 334310755..f7daf245a 100644 --- a/apps/gpsinfo/gps-info.js +++ b/apps/gpsinfo/gps-info.js @@ -2,6 +2,7 @@ var img = require("heatshrink").decompress(atob("mEwghC/AH4AKg9wC6t3u4uVC6wWBI6t Bangle.setGPSPower(1); Bangle.setLCDMode("doublebuffered"); +E.showMessage("Loading..."); // avoid showing rubbish on screen var lastFix = { fix: 0, diff --git a/apps/gpsrec/ChangeLog b/apps/gpsrec/ChangeLog index 668ce1991..9a47bdd9a 100644 --- a/apps/gpsrec/ChangeLog +++ b/apps/gpsrec/ChangeLog @@ -2,3 +2,5 @@ 0.02: Fix GPS time logging 0.03: Fix GPS time display in gpsrec app 0.04: Properly Fix GPS time display in gpsrec app +0.05: Tweaks for variable size widget system +0.06: Ensure widget update itself (fix #118) and change to using icons diff --git a/apps/gpsrec/app.js b/apps/gpsrec/app.js index bac7e92f8..58b4295a6 100644 --- a/apps/gpsrec/app.js +++ b/apps/gpsrec/app.js @@ -1,7 +1,7 @@ Bangle.loadWidgets(); Bangle.drawWidgets(); -var settings = require("Storage").readJSON("gpsrec.json")||{}; +var settings = require("Storage").readJSON("gpsrec.json",1)||{}; function getFN(n) { return ".gpsrc"+n.toString(36); diff --git a/apps/gpsrec/interface.html b/apps/gpsrec/interface.html index d86800923..b87d75b3c 100644 --- a/apps/gpsrec/interface.html +++ b/apps/gpsrec/interface.html @@ -176,7 +176,7 @@ function getTrackList() { for (var i=0;i { var button = event.currentTarget; - var trackid = button.getAttribute("trackid"); + var trackid = parseInt(button.getAttribute("trackid")); var task = button.getAttribute("task"); if (task=="delete") { showModal("Deleting Track..."); diff --git a/apps/gpsrec/widget.js b/apps/gpsrec/widget.js index 7e25a92ad..2ad0cfc8c 100644 --- a/apps/gpsrec/widget.js +++ b/apps/gpsrec/widget.js @@ -1,44 +1,29 @@ (() => { - // add the width - var xpos = WIDGETPOS.tl; - WIDGETPOS.tl += 24;/* the widget width plus some extra pixel to keep distance to others */; var settings = {}; var hasFix = false; var fixToggle = false; // toggles once for each reading var gpsTrack; // file for GPS track var periodCtr = 0; - // draw your widget at xpos + // draw your widget function draw() { + if (!settings.recording) return; g.reset(); - g.setFont("4x6"); - g.setFontAlign(0,0); - g.clearRect(xpos,0,xpos+23,23); - - if (!settings.recording) { - g.setColor("#606060"); + g.drawImage(atob("GBgCAAAAAAAAAAQAAAAAAD8AAAAAAP/AAAAAAP/wAAAAAH/8C9AAAB/8L/QAAAfwv/wAAAHS//wAAAAL//gAAAAf/+AAAAAf/4AAAAL//gAAAAD/+DwAAAB/Uf8AAAAfA//AAAACAf/wAAAAAH/0AAAAAB/wAAAAAAfAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"),this.x,this.y); + if (hasFix) { + g.setColor("#FF0000"); + g.drawImage(fixToggle ? atob("CgoCAAAAA0AAOAAD5AAPwAAAAAAAAAAAAAAAAA==") : atob("CgoCAAABw0AcOAHj5A8PwHwAAvgAB/wABUAAAA=="),this.x,this.y+14); } else { - g.setColor("#ff0000"); - if (hasFix) { - if (fixToggle) { - g.fillCircle(xpos+11,11,9); - g.setColor("#000000"); - } else - g.drawCircle(xpos+11,11,9); - } else { - g.setColor(fixToggle ? "#ff0000" : "#7f0000"); - g.drawString("NO",xpos+12,5); - g.drawString("FIX",xpos+12,19); - } + g.setColor("#0000FF"); + if (fixToggle) + g.setFont("6x8").drawString("?",this.x,this.y+14); } - g.drawString("GPS",xpos+12,12); - g.setColor(-1); // change color back to be nice to other apps } function onGPS(fix) { hasFix = fix.fix; fixToggle = !fixToggle; - draw(); + WIDGETS["gpsrec"].draw(); if (hasFix) { periodCtr--; if (periodCtr<=0) { @@ -53,25 +38,30 @@ } } - // Called by the GPS app to reload settings and decide what's + // Called by the GPS app to reload settings and decide what to do function reload() { - settings = require("Storage").readJSON("gpsrec.json")||{}; + settings = require("Storage").readJSON("gpsrec.json",1)||{}; settings.period = settings.period||1; settings.file |= 0; Bangle.removeListener('GPS',onGPS); if (settings.recording) { + WIDGETS["gpsrec"].width = 24; Bangle.on('GPS',onGPS); Bangle.setGPSPower(1); var n = settings.file.toString(36); gpsTrack = require("Storage").open(".gpsrc"+n,"a"); } else { + WIDGETS["gpsrec"].width = 0; Bangle.setGPSPower(0); gpsTrack = undefined; } - draw(); } - reload(); // add the widget - WIDGETS["gpsrec"]={draw:draw,reload:reload}; + WIDGETS["gpsrec"]={area:"tl",width:24,draw:draw,reload:function() { + reload(); + Bangle.drawWidgets(); // relayout all widgets + }}; + // load settings, set correct widget width + reload(); })() diff --git a/apps/heart/ChangeLog b/apps/heart/ChangeLog new file mode 100644 index 000000000..4c4db83bc --- /dev/null +++ b/apps/heart/ChangeLog @@ -0,0 +1,2 @@ +0.01: New App! + diff --git a/apps/heart/app-icon.js b/apps/heart/app-icon.js new file mode 100644 index 000000000..a7f67291e --- /dev/null +++ b/apps/heart/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwhC/AH4AWzIAByAHDhIICCpINDDAgIIFpAADBBQuKE4QIIFxgAKC7g9HABSbIBQQXWGxgXEKQxOMC5AhBC66WMC5AuBJ5h3ICoI3LeAwKBBAICBD4TmHC48ACgQCCfxC/HAgYXDL44vFA4YRDAoiOIHAgXFYRAXFBwwIIOw4OGIxKmIC5ylHGAoXIXpBIGLxxIIIx6IJFxwwNCxQwLFxYwLCxgwJFxowJCxwwHFx4wHCyAwFFyIwFCyQYDCygA/AH4AFA")) \ No newline at end of file diff --git a/apps/heart/app-settings.json b/apps/heart/app-settings.json new file mode 100644 index 000000000..d57d970d0 --- /dev/null +++ b/apps/heart/app-settings.json @@ -0,0 +1,4 @@ +{ + "isRecording":false, + "fileNbr":0 +} diff --git a/apps/heart/app.js b/apps/heart/app.js new file mode 100644 index 000000000..366a1068d --- /dev/null +++ b/apps/heart/app.js @@ -0,0 +1,100 @@ +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +var settings = require("Storage").readJSON("heart.json",1)||{}; + +function getFileNbr(n) { + return ".heart"+n.toString(36); +} + +function updateSettings() { + require("Storage").write("heart.json", settings); + if (WIDGETS["heart"]) + WIDGETS["heart"].reload(); +} + +function showMainMenu() { + const mainMenu = { + '': { 'title': 'Heart Recorder' }, + 'RECORD': { + value: !!settings.isRecording, + format: v=>v?"On":"Off", + onchange: v => { + settings.isRecording = v; + updateSettings(); + } + }, + 'File Number': { + value: settings.fileNbr|0, + min: 0, + max: 35, + step: 1, + onchange: v => { + settings.isRecording = false; + settings.fileNbr = v; + updateSettings(); + } + }, + 'View Records': viewRecords, + '< Back': ()=>{load();} + }; + return E.showMenu(mainMenu); +} + +function viewRecords() { + const menu = { + '': { 'title': 'Heart Records' } + }; + var found = false; + for (var n=0;n<36;n++) { + var f = require("Storage").open(getFileNbr(n),"r"); + if (f.readLine()!==undefined) { + menu["Record "+n] = viewRecord.bind(null,n); + found = true; + } + } + if (!found) + menu["No Records Found"] = function(){}; + menu['< Back'] = showMainMenu; + return E.showMenu(menu); +} + +function viewRecord(n) { + const menu = { + '': { 'title': 'Heart Record '+n } + }; + var heartCount = 0; + var heartTime; + var f = require("Storage").open(getFileNbr(n),"r"); + var l = f.readLine(); + if (l!==undefined) { + var c = l.split(","); + heartTime = new Date(c[0]*1000); + } + while (l!==undefined) { + heartCount++; + // TODO: min/max/average of heart rate? + l = f.readLine(); + } + if (heartTime) + menu[" "+heartTime.toString().substr(4,17)] = function(){}; + menu[heartCount+" records"] = function(){}; + // TODO: option to draw it? Just scan through, project using min/max + menu['Erase'] = function() { + E.showPrompt("Delete Record?").then(function(v) { + if (v) { + settings.isRecording = false; + updateSettings(); + var f = require("Storage").open(getFileNbr(n),"r"); + f.erase(); + viewRecords(); + } else + viewRecord(n); + }); + }; + menu['< Back'] = viewRecords; + print(menu); + return E.showMenu(menu); +} + +showMainMenu(); diff --git a/apps/heart/app.png b/apps/heart/app.png new file mode 100644 index 000000000..72b2805b8 Binary files /dev/null and b/apps/heart/app.png differ diff --git a/apps/heart/interface.html b/apps/heart/interface.html new file mode 100644 index 000000000..c2f115564 --- /dev/null +++ b/apps/heart/interface.html @@ -0,0 +1,149 @@ + + + + + +
+ + + + + + + diff --git a/apps/heart/widget.js b/apps/heart/widget.js new file mode 100644 index 000000000..7d46aa239 --- /dev/null +++ b/apps/heart/widget.js @@ -0,0 +1,50 @@ +(() => { + var settings = {}; + var hrmToggle = true; // toggles once for each reading + var recFile; // file for heart rate recording + + // draw your widget + function draw() { + if (!settings.isRecording) return; + g.reset(); + g.setFontAlign(0,0); + g.clearRect(this.x,this.y,this.x+23,this.y+23); + g.setColor(hrmToggle?"#ff0000":"#ff8000"); + g.fillCircle(this.x+6,this.y+6,4); // draw heart left circle + g.fillCircle(this.x+16,this.y+6,4); // draw heart right circle + g.fillPoly([this.x+2,this.y+8,this.x+20,this.y+8,this.x+11,this.y+18]); // draw heart bottom triangle + g.setColor(-1); // change color back to be nice to other apps + } + + function onHRM(hrm) { + hrmToggle = !hrmToggle; + WIDGETS["heart"].draw(); + if (recFile) recFile.write([getTime().toFixed(0),hrm.bpm,hrm.confidence].join(",")+"\n"); + } + + // Called by the heart app to reload settings and decide what's + function reload() { + settings = require("Storage").readJSON("heart.json",1)||{}; + settings.fileNbr |= 0; + + Bangle.removeListener('HRM',onHRM); + if (settings.isRecording) { + WIDGETS["heart"].width = 24; + Bangle.on('HRM',onHRM); + Bangle.setHRMPower(1); + var n = settings.fileNbr.toString(36); + recFile = require("Storage").open(".heart"+n,"a"); + } else { + WIDGETS["heart"].width = 0; + Bangle.setHRMPower(0); + recFile = undefined; + } + } + // add the widget + WIDGETS["heart"]={area:"tl",width:24,draw:draw,reload:function() { + reload(); + Bangle.drawWidgets(); // relayout all widgets + }}; + // load settings, set correct widget width + reload(); +})() diff --git a/apps/hidbkbd/hid-binary-keyboard.js b/apps/hidbkbd/hid-binary-keyboard.js index d48d97b47..fa1017714 100644 --- a/apps/hidbkbd/hid-binary-keyboard.js +++ b/apps/hidbkbd/hid-binary-keyboard.js @@ -5,7 +5,7 @@ the touchscreen var storage = require('Storage'); -const settings = storage.readJSON('setting.json') || { HID: false }; +const settings = storage.readJSON('setting.json',1) || { HID: false }; const KEY = { A : 4 , B : 5 , diff --git a/apps/hidkbd/hid-keyboard.js b/apps/hidkbd/hid-keyboard.js index fe850024e..ed406e093 100644 --- a/apps/hidkbd/hid-keyboard.js +++ b/apps/hidkbd/hid-keyboard.js @@ -1,6 +1,6 @@ var storage = require('Storage'); -const settings = storage.readJSON('setting.json') || { HID: false }; +const settings = storage.readJSON('setting.json',1) || { HID: false }; var sendHid, next, prev, toggle, up, down, profile; diff --git a/apps/hidmsic/hid-music.js b/apps/hidmsic/hid-music.js index afee7ade9..034bbd231 100644 --- a/apps/hidmsic/hid-music.js +++ b/apps/hidmsic/hid-music.js @@ -1,6 +1,6 @@ var storage = require('Storage'); -const settings = storage.readJSON('setting.json') || { HID: false }; +const settings = storage.readJSON('setting.json',1) || { HID: false }; var sendHid, next, prev, toggle, up, down, profile; diff --git a/apps/launch/app.js b/apps/launch/app.js index e93c2a330..682122f82 100644 --- a/apps/launch/app.js +++ b/apps/launch/app.js @@ -1,8 +1,5 @@ var s = require("Storage"); -var apps = s.list(/\.info$/).map(app=>{ - try { return s.readJSON(app); } - catch (e) { return {name:"DEAD: "+app.substr(1)} } -}).filter(app=>app.type=="app" || app.type=="clock" || !app.type); +var apps = s.list(/\.info$/).map(app=>s.readJSON(app,1)||{name:"DEAD: "+app.substr(1)}).filter(app=>app.type=="app" || app.type=="clock" || !app.type); apps.sort((a,b)=>{ var n=(0|a.sortorder)-(0|b.sortorder); if (n) return n; // do sortorder first diff --git a/apps/locale/ChangeLog b/apps/locale/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/locale/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/locale/locale.html b/apps/locale/locale.html new file mode 100644 index 000000000..931bde033 --- /dev/null +++ b/apps/locale/locale.html @@ -0,0 +1,88 @@ + + + + + + +

Please choose a language from the following list:

+
+ +
+

Then click

+ + + + + + + diff --git a/apps/locale/locale.png b/apps/locale/locale.png new file mode 100644 index 000000000..1719f6181 Binary files /dev/null and b/apps/locale/locale.png differ diff --git a/apps/locale/locales.js b/apps/locale/locales.js new file mode 100644 index 000000000..e28d285da --- /dev/null +++ b/apps/locale/locales.js @@ -0,0 +1,357 @@ +/* + %Y year four digits + %y last two digits of year (00..99) + %m month (01..12) + %d day of month (e.g, 01) + + %a locale's abbreviated weekday name (e.g., Sun) + %A locale's full weekday name (e.g., Sunday) + %b locale's abbreviated month name (e.g., Jan) + %B locale's full month name (e.g., January) + + %H hour (00..23) + %M minute (00..59) + %S second (00..60) + %p locale's equivalent of either AM or PM; blank if not known + %P like %p, but lower case +*/ + +var locales = { + "en_GB": { // this is default + lang: "en_GB", + decimal_point: ".", + thousands_sep: ",", + currency_symbol: "£", + int_curr_symbol: "GBP", + speed: 'mph', + distance: { "0": "mi", "1": "kmi" }, + temperature: '°C', + ampm: {0:"am",1:"pm"}, + timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" }, + datePattern: { 0: "%b %d %Y", 1: "%d/%m/%Y" }, // Feb 28 2020" // "01/03/2020"(short) + abmonth: "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec", + month: "January,February,March,April,May,June,July,August,September,October,November,December", + abday: "Sun,Mon,Tue,Wed,Thu,Fri,Sat", + day: "Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday", + trans: { /*yes: "yes", Yes: "Yes", no: "no", No: "No", ok: "ok", on: "on", off: "off"*/ }}, + "de_DE": { + lang: "de_DE", + decimal_point: ",", + thousands_sep: ".", + currency_symbol: "\x80", + int_curr_symbol: "EUR", + speed: "kmh", + distance: { 0: "m", 1: "km" }, + temperature: "°C", + ampm: {0:"",1:""}, + timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" }, + datePattern: { 0: "%A, %d. %B %Y", "1": "%d.%m.%Y" }, // Sonntag, 1. März 2020 // 01.01.20 + abmonth: "Jan,Feb,Mär,Apr,Mai,Jun,Jul,Aug,Sep,Okt,Nov,Dez", + month: "Januar,Februar,März,April,Mai,Juni,Juli,August,September,Oktober,November,Dezember", + abday: "So,Mo,Di,Mi,Do,Fr,Sa", + day: "Sonntag,Montag,Dienstag,Mittwoch,Donnerstag,Freitag,Samstag", + trans: { yes: "ja", Yes: "Ja", no: "nein", No: "Nein", ok: "ok", on: "an", off: "aus" }}, + "en_US": { + lang: "en_US", + decimal_point: ".", + thousands_sep: ",", + currency_symbol: "$", + int_curr_symbol: "USD", + speed: "mph", + distance: { 0: "mi", 1: "kmi" }, + temperature: "°F", + ampm: {0:"am",1:"pm"}, + timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" }, + datePattern: { 0: "", 1: "%m/%d/%y" }, + abmonth: "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec", + month: "January,February,March,April,May,June,July,August,September,October,November,December", + abday: "Sun,Mon,Tue,Wed,Thu,Fri,Sat", + day: "Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday", + trans: { yes: "yes", Yes: "Yes", no: "no", No: "No", ok: "ok", on: "on", off: "off" }}, + "en_JP": { // we do not have the font, so it is not ja_JP + lang: "en_JP", + decimal_point: ".", + thousands_sep: ",", + currency_symbol: "¥", + int_curr_symbol: "JPY", + speed: "kmh", + distance: { 0: "m", 1: "km" }, + temperature: "°F", + ampm: {0:"",1:""}, + timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" }, + datePattern: { 0: "%y/%M/%d", 1: "%y/%m;/%d" }, + abmonth: "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec", + month: "January,February,March,April,May,June,July,August,September,October,November,December", + abday: "Sun,Mon,Tue,Wed,Thu,Fri,Sat", + day: "Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday", + trans: { yes: "yes", Yes: "Yes", no: "no", No: "No", ok: "ok", on: "on", off: "off" }}, + "nl_NL": { + lang: "nl_NL", + decimal_point: ",", + thousands_sep: ".", + currency_symbol: "\x80", + int_curr_symbol: "EUR", + speed: "kmh", + distance: { 0: "m", 1: "km" }, + temperature: "°C", + ampm: {0:"",1:""}, + timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" }, + datePattern: { 0: "%A %B %d %Y", 1: "%d.%m.%y" }, // zondag 1 maart 2020 // 01.01.20 + abday: "zo,ma,di,wo,do,vr,za", + day: "zondag,maandag,dinsdag,woensdag,donderdag,vrijdag,zaterdag", + abmonth: "jan,feb,mrt,apr,mei,jun,jul,aug,sep,okt,nov,dec", + month: "januari,februari,maart,april,mei,juni,juli,augustus,september,oktober,november,december", + trans: { yes: "yes", Yes: "Yes", no: "no", No: "No", ok: "ok", on: "on", off: "off" }}, + "en_CA": { + lang: "en_CA", + decimal_point: ".", + thousands_sep: ",", + currency_symbol: "$", + int_curr_symbol: "CAD", + speed: "mph", + distance: { 0: "mi", 1: "kmi" }, + temperature: "°F", + ampm: {0:"am",1:"pm"}, + timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" }, + datePattern: { 0: "%A, %B %d, %Y", "1": "%Y-%m-%d" }, // Sunday, March 1, 2020 // 2012-12-20 + abmonth: "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec", + month: "January,February,March,April,May,June,July,August,September,October,November,December", + abday: "Sun,Mon,Tue,Wed,Thu,Fri,Sat", + day: "Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday", + trans: { yes: "yes", Yes: "Yes", no: "no", No: "No", ok: "ok", on: "on", off: "off" }}, + "fr_FR": { + lang: "fr_FR", + decimal_point: ",", + thousands_sep: ".", + currency_symbol: "\x80", + int_curr_symbol: "EUR", + speed: "kmh", + distance: { 0: "m", 1: "km" }, + temperature: "°C", + ampm: {0:"",1:""}, + timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" }, + datePattern: { 0: "%A %d %B %Y", "1": "%d/%m/%Y" }, // dimanche 1 mars 2020 // 01/03/2020 + abmonth: "janv,févr,mars,avril,mai,juin,juil,août,sept,oct,nov,déc", + month: "janvier,février,mars,avril,mai,juin,juillet,août,septembre,octobre,novembre,décembre", + abday: "dim,lun,mar,mer,jeu,ven,sam", + day: "dimanche,lundi,mardi,mercredi,jeudi,vendredi,samedi", + trans : { yes : "oui", Yes: "Oui", no: "no", No: "No", ok : "ok", on: "on", off: "off" }}, + "sv_SE": { + lang: "sv_SE", + decimal_point: ",", + thousands_sep: ".", + currency_symbol: "kr", + int_curr_symbol: "SKR", + speed: "kmh", + distance: { 0: "m", 1: "km" }, + temperature: "°C", + ampm: {0:"fm",1:"em"}, + timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" }, + datePattern: { 0: "%A %B %d %Y", "1": "%Y-%m-%d" }, // söndag 1 mars 2020 // 2020-03-01 + abmonth: "jan,feb,mars,apr,maj,juni,juli,aug,sep,okt,nov,dec", + month: "januari,februari,mars,april,maj,juni,juli,augusti,september,oktober,november,december", + abday: "sön,mån,tis,ons,tors,fre,lör", + day: "söndag,måndag,tisdag,onsdag,torsdag,fredag,lördag", + trans : { yes : "ja", Yes: "Ja", no: "nej", No: "Nej", ok : "ok", on: "on", off: "off" }}, + "en_AU": { + lang: "en_AU", + decimal_point: ".", + thousands_sep: ",", + currency_symbol: "$", + int_curr_symbol: "AUD", + speed: "mph", + distance: { 0: "mi", 1: "kmi" }, + temperature: "°F", + ampm: {0:"am",1:"pm"}, + timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" }, + datePattern: { 0: "%A, %B %d, %Y", "1": "%m/%d/%y" }, // Sunday, 1 March 2020 // 1/3/20 + abmonth: "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec", + month: "January,February,March,April,May,June,July,August,September,October,November,December", + abday: "Sun,Mon,Tue,Wed,Thu,Fri,Sat", + day: "Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday", + trans: { yes: "yes", Yes: "Yes", no: "no", No: "No", ok: "ok", on: "on", off: "off" }}, + "de_AT": { + lang: "de_AT", + decimal_point: ",", + thousands_sep: ".", + currency_symbol: "\x80", + int_curr_symbol: "EUR", + speed: "kmh", + distance: { 0: "m", 1: "km" }, + temperature: "°C", + timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" }, + datePattern: { 0: "%A, %d. %B %Y", "1": "%d.%m.%y" }, // Sonntag, 1. März 2020 // 01.03.20 + abmonth: "Jän,Feb,März,Apr,Mai,Jun,Jul,Aug,Sep,Okt,Nov,Dez", + month: "Jänner,Februar,März,April,Mai,Juni,Juli,August,September,Oktober,November,Dezember", + abday: "So,Mo,Di,Mi,Do,Fr,Sa", + day: "Sonntag,Montag,Dienstag,Mittwoch,Donnerstag,Freitag,Samstag", + trans: { yes: "ja", Yes: "Ja", no: "nein", No: "Nein", ok: "ok", on: "an", off: "aus" }}, + "en_IL": { + lang: "en_IL", + decimal_point: ",", + thousands_sep: ".", + currency_symbol: "₪", + int_curr_symbol: "ILS", + speed: "kmh", + distance: { 0: "m", 1: "km" }, + temperature: "°F", + ampm: {0:"am",1:"pm"}, + timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" }, + datePattern: { 0: "%A, %B %d, %Y", "1": "%d/%m/%Y" }, // Sunday, 1 March 2020 // 01/03/2020 + abmonth: "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec", + month: "January,February,March,April,May,June,July,August,September,October,November,December", + abday: "Sun,Mon,Tue,Wed,Thu,Fri,Sat", + day: "Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday", + trans: { yes: "yes", Yes: "Yes", no: "no", No: "No", ok: "ok", on: "on", off: "off" }}, + "es_ES": { + lang: "es_ES", + decimal_point: ",", + thousands_sep: ".", + currency_symbol: "\x80", + int_curr_symbol: "EUR", + speed: "kmh", + distance: { 0: "m", 1: "km" }, + temperature: "°C", + ampm: {0:"",1:""}, + timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" }, + datePattern: { 0: "%A, %d de %B de %Y", "1": "%d/%m/%y" }, // domingo, 1 de marzo de 2020 // 01/03/20 + abmonth: "ene,feb,mar,abr,may,jun,jul,ago,sept,oct,nov,dic", + month: "enero,febrero,marzo,abril,mayo,junio,julio,agosto,septiembre,octubre,noviembre,diciembre", + abday: "dom,lun,mar,mié,jue,vie,sáb", + day: "domingo,lunes,martes,miércoles,jueves,viernes,sábado", + trans: { yes : "sí", Yes: "Sí",no: "no", No: "No", ok : "ok", on: "on", off: "off" }}, + "fr_BE": { + lang: "fr_BE", + decimal_point: ",", + thousands_sep: ".", + currency_symbol: "\x80", + int_curr_symbol: "EUR", + speed: "kmh", + distance: { 0: "m", 1: "km" }, + temperature: "°C", + ampm: {0: "",1: ""}, + timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" }, + datePattern: { 0: "%A %B %d %Y", "1": "%d/%m/%y" }, // dimanche 1 mars 2020 // 01/03/20 + abmonth: "anv.,févr.,mars,avril,mai,juin,juil.,août,sept.,oct.,nov.,déc.", + month: "janvier,février,mars,avril,mai,juin,juillet,août,septembre,octobre,novembre,décembre", + abday: "dim,lun,mar,mer,jeu,ven,sam", + day: "dimanche,lundi,mardi,mercredi,jeudi,vendredi,samedi", + trans : { yes : "oui", Yes: "Oui", no: "no", No: "No", ok : "ok", on: "on", off: "off" }}, + "fi_FI": { + lang: "fi_FI", + decimal_point: ",", + thousands_sep: ".", + currency_symbol: "\x80", + int_curr_symbol: "EUR", + speed: "kmh", + distance: { 0: "m", 1: "km" }, + temperature: "°C", + ampm: {0: "ap",1: "ip"}, + timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" }, // 17.00.00 // 17.00 + datePattern: { 0: "%A %d. %B %Y", "1": "%-d/%-m/%Y" }, // sunnuntai 1. maaliskuuta 2020 // 1.3.2020 + abmonth: "tammik,helmik,maalisk,huhtik,toukok,kesäk,heinäk,elok,syysk,lokak,marrask,jouluk", + month: "tammikuuta,helmikuuta,maaliskuuta,huhtikuuta,toukokuuta,kesäkuuta,heinäkuuta,elokuuta,syyskuuta,lokakuuta,marraskuuta,joulukuuta", + abday: "su,ma,ti,ke,to,pe,la", + day: "sunnuntaina,maanantaina,tiistaina,keskiviikkona,torstaina,perjantaina,lauantaina", + trans : { yes : "oui", Yes: "Oui", no: "no", No: "No", ok : "ok", on: "on", off: "off" }}, + "de_CH": { + lang: "de_CH", + decimal_point: ",", + thousands_sep: ".", + currency_symbol: "CHF", + int_curr_symbol: "CHF", + speed: "kmh", + distance: { 0: "m", 1: "km" }, + temperature: "°C", + ampm: {0:"vorm",1:" nachm"}, + timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" }, + datePattern: { 0: "%A, %d. %B %Y", "1": "%d.%m.%Y" }, // Sonntag, 1. März 2020 // 1.3.2020 + abmonth: "Jan,Feb,März,Apr,Mai,Jun,Jul,Aug,Sep,Okt,Nov,Dez", + month: "Januar,Februar,März,April,Mai,Juni,Juli,August,September,Oktober,November,Dezember", + abday: "So,Mo,Di,Mi,Do,Fr,Sa", + day: "Sonntag,Montag,Dienstag,Mittwoch,Donnerstag,Freitag,Samstag", + trans: { yes: "ja", Yes: "Ja", no: "nein", No: "Nein", ok: "ok", on: "an", off: "aus" }}, + "fr_CH": { + lang: "fr_CH", + decimal_point: ",", + thousands_sep: ".", + currency_symbol: "CHF", + int_curr_symbol: "CHF", + speed: "kmh", + distance: { 0: "m", 1: "km" }, + temperature: "°C", + ampm: {0:"AM",1:"PM"}, + timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" }, + datePattern: { 0: "%A %d %B %Y", "1": "%d/%m/%y" }, // dimanche 1 mars 2020 // 01/03/20 + abmonth: "anv.,févr.,mars,avril,mai,juin,juil.,août,sept.,oct.,nov.,déc.", + month: "janvier,février,mars,avril,mai,juin,juillet,août,septembre,octobre,novembre,décembre", + abday: "dim,lun,mar,mer,jeu,ven,sam", + day: "dimanche,lundi,mardi,mercredi,jeudi,vendredi,samedi", + trans : { yes : "oui", Yes: "Oui", no: "no", No: "No", ok : "ok", on: "on", off: "off" }}, + "it_CH": { + lang: "it_CH", + decimal_point: ",", + thousands_sep: ".", + currency_symbol: "CHF", + int_curr_symbol: "CHF", + speed: 'kmh', + distance: { "0": "m", "1": "km" }, + temperature: '°C', + timePattern: { 0: "%HH.%MM.%SS ", 1: "%HH.%MM" }, // 17.00.00 // 17.00 + datePattern: { 0: "%A %B %d %Y", "1": "%d/%m/%Y" }, // sunnuntai 1. maaliskuuta 2020 // 1.3.2020 + abmonth: "gen,feb,mar,apr,mag,giu,lug,ago,set,ott,nov,dic", + month: "gennaio,febbraio,marzo,aprile,maggio,giugno,luglio,agosto,settembre,ottobre,novembre,dicembre", + abday : "dom,lun,mar,mer,gio,ven,sab", + day: "domenica,lunedì,martedì,mercoledì,giovedì,venerdì, sabato", + trans : { yes: "sì", Yes: "Sì", no: "no", No: "No", ok: "ok", on: "on", off: "off" }}, + "wae_CH" : { + lang: "wae_CH", + decimal_point: ",", + thousands_sep: ".", + currency_symbol: "CHF", + int_curr_symbol: "CHF", + speed: 'kmh', + distance: { "0": "m", "1": "km" }, + temperature: '°C', + timePattern: { 0: "%HH.%MM.%SS ", 1: "%HH.%MM" }, // 17.00.00 // 17.00 + datePattern: { 0: "%A, %d. %B %Y", "1": "%Y-%m-%d" }, // Sunntag, 1. Märze 2020 // 2020-03-01 + abmonth: "Jen,Hor,Mär,Abr,Mei,Brá,Hei,Öig,Her,Wím,Win,Chr", + month: "Jenner,Hornig,Märze,Abrille,Meije,Bráčet,Heiwet,Öigšte,Herbštmánet,Wímánet,Wintermánet,Chrištmánet", + abday: "Sun,Män,Ziš,Mit,Fró,Fri,Sam", + day: "Sunntag,Mäntag,Zištag,Mittwuč,Fróntag,Fritag,Samštag", + trans : { yes: "sì", Yes: "Sì", no: "no", No: "No", ok: "ok", on: "on", off: "off" }}, + "tr_TR": { // this is default + lang: "tr_TR", + decimal_point: ",", + thousands_sep: ".", + currency_symbol: "TL", + int_curr_symbol: "TRY", + speed: 'kmh', + distance: { "0": "m", "1": "km" }, + temperature: '°C', + ampm: {0:"öö",1:"ös"}, + timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" }, + datePattern: { 0: "%d %w %Y %A", 1: "%d/%m/%Y" }, // 1 Mart 2020 Pazar // "01/03/2020" + abmonth: "Oca,Sub,Mar,Nis,May,Haz,Tem,Agu,Eyl,Eki,Kas,Ara", + month: "Ocak,Subat,Mart,Nisan,Mayis,Haziran,Temmuz,Agustos,Eylul,Ekim,Kasim,Aralik", + abday: "Paz,Pzt,Sal,Car,Per,Cum,Cmt", + day: "Pazar,Pazartesi,Sali,Carsamba,Persembe,Cuma,Cumartesi", + trans: { yes: "evet", Yes: "Evet", no: "hayir", No: "Hayir", ok: "tamam", on: "acik", off: "kapali" }}, + "hu_HU": { + lang: "hu_HU", + decimal_point: ",", + thousands_sep: " ", + currency_symbol: "Ft", + int_curr_symbol: "HUF", + speed: 'kph', + distance: { "0": "m", "1": "km" }, + temperature: '°C', + ampm: {0:"de",1:"du"}, + timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" }, + datePattern: { 0: "%Y %d %b", 1: "%Y.%m.%d" }, // 2020 Feb 28" // "2020.03.01."(short) + abmonth: "Jan,Feb,Már,Ápr,Máj,Jún,Júl,Aug,Szep,Okt,Nov,Dec", + month: "Január,Február,Március,Április,Május,Június,Július,Augusztus,Szeptember,Október,November,December", + abday: "Vas,Hét,Ke,Szer,Csüt,Pén,Szom", + day: "Vasárnap,Hétfő,Kedd,Szerda,Csütörtök,Péntek,Szombat", + trans: { yes: "igen", Yes: "Igen", no: "nem", No: "Nem", ok: "ok", on: "be", off: "ki" }}, +}; diff --git a/apps/ncstart/start.js b/apps/ncstart/start.js index 208c554fd..f1d8fb52e 100644 --- a/apps/ncstart/start.js +++ b/apps/ncstart/start.js @@ -115,11 +115,9 @@ function info() { } function cleanup() { - try { - var settings = require("Storage").readJSON('setting.json'); - settings.welcomed = true; - require("Storage").write('setting.json',settings); - } catch (e) {} + var settings = require("Storage").readJSON('setting.json',1)||{}; + settings.welcomed = true; + require("Storage").write('setting.json',settings); return Promise.resolve(); } diff --git a/apps/qrcode/qrcode.html b/apps/qrcode/qrcode.html index 8a0027929..5d372aa59 100644 --- a/apps/qrcode/qrcode.html +++ b/apps/qrcode/qrcode.html @@ -4,7 +4,7 @@ -

Enter a URL:

+

Enter a URL:

Try your QR Code:

Click

diff --git a/apps/sclock/clock-simple.js b/apps/sclock/clock-simple.js index 9bedbc2d0..dc0eb159c 100644 --- a/apps/sclock/clock-simple.js +++ b/apps/sclock/clock-simple.js @@ -11,7 +11,7 @@ const yposYear = 175; const yposGMT = 220; // Check settings for what type our clock should be -var is12Hour = (require("Storage").readJSON("setting.json")||{})["12hour"]; +var is12Hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"]; function drawSimpleClock() { // get date diff --git a/apps/setting/ChangeLog b/apps/setting/ChangeLog index b23865f81..1485d5c12 100644 --- a/apps/setting/ChangeLog +++ b/apps/setting/ChangeLog @@ -2,3 +2,4 @@ 0.03: Add support for Welcome app 0.04: Add setting to disable log messages 0.05: Fix Settings json +0.06: Remove distance setting as there's a separate app for Locale now diff --git a/apps/setting/settings-default.json b/apps/setting/settings-default.json index efaf83bc3..2fa66cbdd 100644 --- a/apps/setting/settings-default.json +++ b/apps/setting/settings-default.json @@ -9,6 +9,5 @@ HID : false, // BLE HID mode, off by default clock: null, // a string for the default clock's name "12hour" : false, // 12 or 24 hour clock? - distance : "kilometer" // or "mile" // welcomed : undefined/true (whether welcome app should show) } diff --git a/apps/setting/settings.js b/apps/setting/settings.js index 568a64047..49af98318 100644 --- a/apps/setting/settings.js +++ b/apps/setting/settings.js @@ -21,15 +21,12 @@ function resetSettings() { HID : false, // BLE HID mode, off by default clock: null, // a string for the default clock's name "12hour" : false, // 12 or 24 hour clock? - distance : "kilometer" // or "mile" // welcomed : undefined/true (whether welcome app should show) }; updateSettings(); } -try { - settings = storage.readJSON('setting.json'); -} catch (e) {} +settings = storage.readJSON('setting.json',1); if (!settings) resetSettings(); const boolFormat = v => v ? "On" : "Off"; @@ -143,15 +140,7 @@ function showLocaleMenu() { settings["12hour"] = v; updateSettings(); } - }, - 'Distance/Speed': { - value: settings.distance=="mile", - format: v => v?"mile":"km", - onchange: v => { - settings.distance = v?"mile":"kilometer"; - updateSettings(); - } - }, + } }; return E.showMenu(localemenu); } diff --git a/apps/torch/ChangeLog b/apps/torch/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/torch/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/torch/app-icon.js b/apps/torch/app-icon.js new file mode 100644 index 000000000..13fc792fe --- /dev/null +++ b/apps/torch/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwxH+AH4A/AH4Acq0yF1tWlksF10yqwuuSVIuGSVF/FwySm5nM0YuGSUovBGAIuGAgIukGAQuGAgIvlSQozEF0iSEeowvlGAT1HF0iSDeo4vlegSShF5fMv4uFSLQkC0QACSRguevErld4GBKSDFz4ABF5CSBLsIvLdDK7GFwiPIF0AvEFw4tbFwztKFrguDF4gADFkAuFF44unF4wuoGAouqAAwu/ABtWqwutmUsmQutF4JhKF0iSJdT4uFAoIwGFz4wCFwgECF0qRCFwo3BF0qSDMQiSBF0owCFwgFBF86SBF1qSFF1SSDF1gA/AH4A1A")) diff --git a/apps/torch/app.js b/apps/torch/app.js new file mode 100644 index 000000000..28aa00bd6 --- /dev/null +++ b/apps/torch/app.js @@ -0,0 +1,8 @@ +Bangle.setLCDPower(1); +Bangle.setLCDTimeout(0); +g.reset(); +g.fillRect(0,0,g.getWidth(),g.getHeight()); +// Any button turns off +setWatch(()=>load(), BTN1); +setWatch(()=>load(), BTN2); +setWatch(()=>load(), BTN3); diff --git a/apps/torch/app.png b/apps/torch/app.png new file mode 100644 index 000000000..a02a835f0 Binary files /dev/null and b/apps/torch/app.png differ diff --git a/apps/torch/widget.js b/apps/torch/widget.js new file mode 100644 index 000000000..e3d1ea22f --- /dev/null +++ b/apps/torch/widget.js @@ -0,0 +1,18 @@ +var clickTimes = []; +var CLICK_COUNT = 4; // number of taps +var CLICK_PERIOD = 1; // second + +// we don't actually create/draw a widget here at all... + +Bangle.on("lcdPower",function(on) { + // First click (that turns LCD on) isn't given to + // setWatch, so handle it here + if (on) clickTimes=[getTime()]; +}); +setWatch(function(e) { + while (clickTimes.length>=CLICK_COUNT) clickTimes.shift(); + clickTimes.push(e.time); + var clickPeriod = e.time-clickTimes[0]; + if (clickTimes.length==CLICK_COUNT && clickPeriodmove(1), BTN3, {repeat:true}); setWatch(()=>{ // If we're on the last page if (sceneNumber == scenes.length-1) { - try { - var settings = require("Storage").readJSON('setting.json'); - settings.welcomed = true; - require("Storage").write('setting.json',settings); - } catch (e) {} + var settings = require("Storage").readJSON('setting.json',1)||{}; + settings.welcomed = true; + require("Storage").write('setting.json',settings); load(); } }, BTN2, {repeat:true,edge:"rising"}); diff --git a/apps/widbat/ChangeLog b/apps/widbat/ChangeLog index ba01ef944..cd9993c02 100644 --- a/apps/widbat/ChangeLog +++ b/apps/widbat/ChangeLog @@ -1 +1,3 @@ 0.02: Now refresh battery monitor every minute if LCD on +0.03: Tweaks for variable size widget system +0.04: Ensure redrawing works with variable size widget system diff --git a/apps/widbat/widget.js b/apps/widbat/widget.js index d6f277b50..2f1f29802 100644 --- a/apps/widbat/widget.js +++ b/apps/widbat/widget.js @@ -1,17 +1,15 @@ (function(){ -var img_charge = E.toArrayBuffer(atob("DhgBHOBzgc4HOP////////////////////3/4HgB4AeAHgB4AeAHgB4AeAHg")); var CHARGING = 0x07E0; -var xpos = WIDGETPOS.tr-64; -WIDGETPOS.tr-=68; +function setWidth() { + WIDGETS["bat"].width = 40 + (Bangle.isCharging()?16:0); +} function draw() { - var s = 63; - var x = xpos, y = 0; - g.clearRect(x,y,x+s,y+23); + var s = 39; + var x = this.x, y = this.y; if (Bangle.isCharging()) { - g.setColor(CHARGING).drawImage(img_charge,x,y); + g.setColor(CHARGING).drawImage(atob("DhgBHOBzgc4HOP////////////////////3/4HgB4AeAHgB4AeAHgB4AeAHg"),x,y); x+=16; - s-=16; } g.setColor(-1); g.fillRect(x,y+2,x+s-4,y+21); @@ -20,11 +18,16 @@ function draw() { g.setColor(CHARGING).fillRect(x+4,y+6,x+4+E.getBattery()*(s-12)/100,y+17); g.setColor(-1); } -Bangle.on('charging',function(charging) { draw(); g.flip(); if(charging)Bangle.buzz(); }); +Bangle.on('charging',function(charging) { + if(charging) Bangle.buzz(); + setWidth(); + Bangle.drawWidgets(); // relayout widgets + g.flip(); +}); var batteryInterval; Bangle.on('lcdPower', function(on) { if (on) { - draw(); + WIDGETS["bat"].draw(); // refresh once a minute if LCD on if (!batteryInterval) batteryInterval = setInterval(draw, 60000); @@ -35,5 +38,6 @@ Bangle.on('lcdPower', function(on) { } } }); -WIDGETS["battery"]={draw:draw}; +WIDGETS["bat"]={area:"tr",width:40,draw:draw}; +setWidth(); })() diff --git a/apps/widbt/ChangeLog b/apps/widbt/ChangeLog new file mode 100644 index 000000000..c268d6df0 --- /dev/null +++ b/apps/widbt/ChangeLog @@ -0,0 +1,2 @@ +0.02: Tweaks for variable size widget system +0.03: Ensure redrawing works with variable size widget system diff --git a/apps/widbt/widget.js b/apps/widbt/widget.js index c4074a797..8e96a395d 100644 --- a/apps/widbt/widget.js +++ b/apps/widbt/widget.js @@ -1,22 +1,19 @@ (function(){ var img_bt = E.toArrayBuffer(atob("CxQBBgDgFgJgR4jZMawfAcA4D4NYybEYIwTAsBwDAA==")); -var xpos = WIDGETPOS.tr-24; -WIDGETPOS.tr-=24; function draw() { - var x = xpos, y = 0; + g.reset(); if (NRF.getSecurityStatus().connected) g.setColor(0,0.5,1); else g.setColor(0.3,0.3,0.3); - g.drawImage(img_bt,10+x,2+y); - g.setColor(1,1,1); + g.drawImage(img_bt,10+this.x,2+this.y); } function changed() { - draw(); + WIDGETS["bluetooth"].draw(); g.flip();// turns screen on } NRF.on('connected',changed); NRF.on('disconnected',changed); -WIDGETS["bluetooth"]={draw:draw}; +WIDGETS["bluetooth"]={area:"tr",width:24,draw:draw}; })() diff --git a/apps/widclk/ChangeLog b/apps/widclk/ChangeLog new file mode 100644 index 000000000..5370129cc --- /dev/null +++ b/apps/widclk/ChangeLog @@ -0,0 +1,2 @@ +0.02: Now refresh battery monitor every minute if LCD on +0.03: Ensure redrawing works with variable size widget system diff --git a/apps/widclk/widget.js b/apps/widclk/widget.js index f73f8688b..1d5df36b2 100644 --- a/apps/widclk/widget.js +++ b/apps/widclk/widget.js @@ -1,39 +1,27 @@ (() => { let intervalRef = null; var width = 5 * 6*2 - var xpos = WIDGETPOS.tr - width; - WIDGETPOS.tr -= (width + 2); - + function draw() { - // Widget (0,0,239,23) - let date = new Date(); - var dateArray = date.toString().split(" "); - g.setColor(1,1,1); - g.setFont("6x8", 2); - g.setFontAlign(-1, 0); - g.drawString(dateArray[4].substr(0, 5), xpos, 11, true); // 5 * 6*2 = 60 - g.flip(); + g.reset().setFont("6x8", 2).setFontAlign(-1, 0); + var time = require("locale").time(new Date(),1); + g.drawString(time, this.x, this.y+11, true); // 5 * 6*2 = 60 } function clearTimers(){ - if(intervalRef) { - clearInterval(intervalRef); - intervalRef = null; - } + if(intervalRef) { + clearInterval(intervalRef); + intervalRef = null; + } } function startTimers(){ - if(intervalRef) clearTimers(); - intervalRef = setInterval(draw, 60*1000); - draw(); + intervalRef = setInterval(draw, 60*1000); + WIDGETS["wdclk"].draw(); } Bangle.on('lcdPower', (on) => { - if (on) { - // startTimers(); // comment out as it is called by app anyway - } else { - clearTimers(); - } + clearTimers(); + if (on) startTimers(); }); - // add your widget - WIDGETS["wdclk"]={draw:startTimers}; - -})() \ No newline at end of file + WIDGETS["wdclk"]={area:"tr",width:width,draw:draw}; + if (Bangle.isLCDOn) intervalRef = setInterval(draw, 60*1000); +})() diff --git a/apps/widhrm/ChangeLog b/apps/widhrm/ChangeLog index 4c21f3ace..d6c076227 100644 --- a/apps/widhrm/ChangeLog +++ b/apps/widhrm/ChangeLog @@ -1 +1,3 @@ 0.01: New Widget! +0.02: Tweaks for variable size widget system +0.03: Ensure redrawing works with variable size widget system diff --git a/apps/widhrm/widget.js b/apps/widhrm/widget.js index 3a1d5da09..ca66f8b44 100644 --- a/apps/widhrm/widget.js +++ b/apps/widhrm/widget.js @@ -1,16 +1,14 @@ (() => { - var xpos = WIDGETPOS.tl; - var width = 24; - WIDGETPOS.tl += width+2; var currentBPM = undefined; var lastBPM = undefined; var firstBPM = true; // first reading since sensor turned on function draw() { + var width = 24; g.reset(); g.setFont("6x8", 1); g.setFontAlign(0, 0); - g.clearRect(xpos,15,xpos+width,24); // erase background + g.clearRect(this.x,this.y+15,this.x+width,this.y+23); // erase background var bpm = currentBPM, isCurrent = true; if (bpm===undefined) { bpm = lastBPM; @@ -19,9 +17,9 @@ if (bpm===undefined) bpm = "--"; g.setColor(isCurrent ? "#ffffff" : "#808080"); - g.drawString(bpm, xpos+width/2, 19); + g.drawString(bpm, this.x+width/2, this.y+19); g.setColor(isCurrent ? "#ff0033" : "#808080"); - g.drawImage(atob("CgoCAAABpaQ//9v//r//5//9L//A/+AC+AAFAA=="),xpos+(width-10)/2,1); + g.drawImage(atob("CgoCAAABpaQ//9v//r//5//9L//A/+AC+AAFAA=="),this.x+(width-10)/2,this.y+1); g.setColor(-1); } @@ -31,7 +29,7 @@ Bangle.setHRMPower(1); firstBPM = true; currentBPM = undefined; - draw(); + WIDGETS["hrm"].draw(); } else { Bangle.setHRMPower(0); } @@ -44,10 +42,10 @@ currentBPM = d.bpm; lastBPM = currentBPM; } - draw(); + WIDGETS["hrm"].draw(); }); Bangle.setHRMPower(Bangle.isLCDOn()); // add your widget - WIDGETS["hrm"]={draw:draw}; + WIDGETS["hrm"]={area:"tl",width:24,draw:draw}; })(); diff --git a/apps/widid/ChangeLog b/apps/widid/ChangeLog new file mode 100644 index 000000000..4152a8406 --- /dev/null +++ b/apps/widid/ChangeLog @@ -0,0 +1,2 @@ +0.01: New Widget! +0.02: Tweaks for variable size widget system diff --git a/apps/widid/widget.js b/apps/widid/widget.js new file mode 100644 index 000000000..efbfdfd19 --- /dev/null +++ b/apps/widid/widget.js @@ -0,0 +1,12 @@ +/* jshint esversion: 6 */ +(() => { + var id = NRF.getAddress().substr().substr(12).split(":"); + + // draw your widget at xpos + function draw() { + g.reset().setColor(0, 0.5, 1).setFont("6x8", 1); + g.drawString(id[0], this.x+2, this.y+4, true); + g.drawString(id[1], this.x+2, this.y+14, true); + } + WIDGETS["widid"] = { area:"tr", width:16, draw: draw }; +})(); diff --git a/apps/widid/widget.png b/apps/widid/widget.png new file mode 100644 index 000000000..1f5ce863f Binary files /dev/null and b/apps/widid/widget.png differ diff --git a/apps/widnceu/ChangeLog b/apps/widnceu/ChangeLog new file mode 100644 index 000000000..4152a8406 --- /dev/null +++ b/apps/widnceu/ChangeLog @@ -0,0 +1,2 @@ +0.01: New Widget! +0.02: Tweaks for variable size widget system diff --git a/apps/widnceu/widget.js b/apps/widnceu/widget.js index fa0e11cd1..44f8acdb4 100644 --- a/apps/widnceu/widget.js +++ b/apps/widnceu/widget.js @@ -1,13 +1,5 @@ -(function(){ -var img = E.toArrayBuffer(atob("SxgCAAAAAAAAAAAAAAAAAAAAAAAAALwDwH/gD/0B//Af+AAD4C8f/wL8Dwf/8H//C//C//AAD9C8f/wL9Dw//+H//i4AD0AH8D/C8fAAL/Dz///H//y8ALgAf/D/i8fAALrzz///H//2//PAAv/Dz28f/gLz7z///H//29VPQAv/Dx+8fqQLw/y///H//y4ALwAP/Dwv8fAALwfw//9H//i+qD8FC0DwP8fAALwPwP/4H/+C//A//AADwD8fAAAAAAC/QD+gAAAAL4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGqooBkD+AP0fgvgAAAAAAAAAAL/88C4NBw0NBjQcAAAAAAAAAALQA8C4AAyQDBjAJAAAAAAAAAALQA8C4AAzACBjAKAAAAAAAAAAL/88C4ABTACRjQbAAAAAAAAAAL/48C4AHDACRgvjAAAAAAAAAALQA8C4AcDACBgACAAAAAAAAAALQA+D0BwCQDBgANAAAAAAAAAAL/8P/wHAA0NBgAoAAAAAAAAAAGqoC+AP/0HgAQuAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")); -var xpos = WIDGETPOS.tl; -WIDGETPOS.tl+=75; - - -WIDGETS["nceu"]={draw:()=>{ - var x = xpos, y = 0; - g.setColor(0.17,0.2,0.5); - g.drawImage(img,x,y); +WIDGETS["nceu"]={area:"tl",width:75,draw:function(){ + g.reset().setColor(0.17,0.2,0.5); + g.drawImage(atob("SxgCAAAAAAAAAAAAAAAAAAAAAAAAALwDwH/gD/0B//Af+AAD4C8f/wL8Dwf/8H//C//C//AAD9C8f/wL9Dw//+H//i4AD0AH8D/C8fAAL/Dz///H//y8ALgAf/D/i8fAALrzz///H//2//PAAv/Dz28f/gLz7z///H//29VPQAv/Dx+8fqQLw/y///H//y4ALwAP/Dwv8fAALwfw//9H//i+qD8FC0DwP8fAALwPwP/4H/+C//A//AADwD8fAAAAAAC/QD+gAAAAL4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGqooBkD+AP0fgvgAAAAAAAAAAL/88C4NBw0NBjQcAAAAAAAAAALQA8C4AAyQDBjAJAAAAAAAAAALQA8C4AAzACBjAKAAAAAAAAAAL/88C4ABTACRjQbAAAAAAAAAAL/48C4AHDACRgvjAAAAAAAAAALQA8C4AcDACBgACAAAAAAAAAALQA+D0BwCQDBgANAAAAAAAAAAL/8P/wHAA0NBgAoAAAAAAAAAAGqoC+AP/0HgAQuAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"),this.x,this.y); g.setColor(1,1,1); }}; -})() diff --git a/apps/widpedom/ChangeLog b/apps/widpedom/ChangeLog index b6907b9c7..980494acb 100644 --- a/apps/widpedom/ChangeLog +++ b/apps/widpedom/ChangeLog @@ -3,3 +3,5 @@ 0.04: Fix pedometer reload when going back to clock app 0.05: Add foot icon, use tidier font, and move to the left of the screen 0.06: Fix widget position increment +0.07: Tweaks for variable size widget system +0.08: Ensure redrawing works with variable size widget system diff --git a/apps/widpedom/widget.js b/apps/widpedom/widget.js index 84a4bb211..1cc14fc2c 100644 --- a/apps/widpedom/widget.js +++ b/apps/widpedom/widget.js @@ -1,17 +1,11 @@ (() => { const PEDOMFILE = "wpedom.json"; - // add the width - // WIDGETPOS.tr is originally 208 without any widgets - var xpos = WIDGETPOS.tl; - var width = 24; - WIDGETPOS.tl += (width + 2); - let lastUpdate = new Date(); let stp_today = 0; - // draw your widget at xpos + // draw your widget function draw() { - // Widget (0,0,239,23) + var width = 24; if (stp_today > 99999){ stp_today = stp_today % 100000; // cap to five digits + comma = 6 characters } @@ -24,9 +18,9 @@ g.setFont("6x8", 1); } g.setFontAlign(0, 0); // align to x: center, y: center - g.clearRect(xpos,15,xpos+width,24); // erase background - g.drawString(stps, xpos+width/2, 19); - g.drawImage(atob("CgoCLguH9f2/7+v6/79f56CtAAAD9fw/n8Hx9A=="),xpos+(width-10)/2,2); + g.clearRect(this.x,this.y+15,this.x+width,this.y+23); // erase background + g.drawString(stps, this.x+width/2, this.y+19); + g.drawImage(atob("CgoCLguH9f2/7+v6/79f56CtAAAD9fw/n8Hx9A=="),this.x+(width-10)/2,this.y+2); } Bangle.on('step', (up) => { @@ -39,11 +33,11 @@ } lastUpdate = date; //console.log("up: " + up + " stp: " + stp_today + " " + date.toString()); - if (Bangle.isLCDOn()) draw(); + if (Bangle.isLCDOn()) WIDGETS["wpedom"].draw(); }); // redraw when the LCD turns on Bangle.on('lcdPower', function(on) { - if (on) draw(); + if (on) WIDGETS["wpedom"].draw(); }); // When unloading, save state E.on('kill', () => { @@ -55,9 +49,9 @@ }); // add your widget - WIDGETS["wpedom"]={draw:draw}; + WIDGETS["wpedom"]={area:"tl",width:26,draw:draw}; // Load data at startup - let pedomData = require("Storage").readJSON(PEDOMFILE); + let pedomData = require("Storage").readJSON(PEDOMFILE,1); if (pedomData) { if (pedomData.lastUpdate) lastUpdate = new Date(pedomData.lastUpdate); diff --git a/bin/firmwaremaker.js b/bin/firmwaremaker.js index 8346b3c00..e5db392dd 100755 --- a/bin/firmwaremaker.js +++ b/bin/firmwaremaker.js @@ -10,16 +10,10 @@ var APPDIR = ROOTDIR+'/apps'; var APPJSON = ROOTDIR+'/apps.json'; var OUTFILE = ROOTDIR+'/firmware.js'; var APPS = [ // IDs of apps to install - "boot", - "launch", - "mclock", - "setting", - "astroid", - "gpstime", - "compass", - "sbt", - "sbat" + "boot","launch","mclock","setting", + "about","alarm","widbat","widbt","welcome" ]; +var MINIFY = true; var fs = require("fs"); var AppInfo = require(ROOTDIR+"/appinfo.js"); @@ -28,14 +22,27 @@ var appfiles = []; function fileGetter(url) { console.log("Loading "+url) - /*if (url.endsWith(".js")) { - var f = url.slice(0,-3); - console.log("MINIFYING "+f); - const execSync = require('child_process').execSync; - code = execSync(`espruino --board BANGLEJS --minify ${f}.js -o ${f}.min.js`); - console.log(code.toString()); - url = f+".min.js"; - }*/ + if (MINIFY) { + /*if (url.endsWith(".js")) { + var f = url.slice(0,-3); + console.log("MINIFYING "+f); + const execSync = require('child_process').execSync; + // --config PRETOKENISE=true + // --minify + code = execSync(`espruino --config SET_TIME_ON_WRITE=false --minify --board BANGLEJS ${f}.js -o ${f}.min.js`); + console.log(code.toString()); + url = f+".min.js"; + }*/ + if (url.endsWith(".json")) { + var f = url.slice(0,-5); + console.log("MINIFYING JSON "+f); + var j = eval("("+fs.readFileSync(url).toString()+")"); + var code = JSON.stringify(j); + //console.log(code); + url = f+".min.json"; + fs.writeFileSync(url, code); + } + } return Promise.resolve(fs.readFileSync(url).toString()); } @@ -50,6 +57,9 @@ Promise.all(APPS.map(appid => { var js = "// Generated by BangleApps/bin/firmwaremaker.js\n"; appfiles.forEach((file) => { js += file.cmd+"\n"; + /*if (file.crc && file.evaluate!==true) { + js += `\x10if (E.CRC32(require('Storage').read(${JSON.stringify(file.name)}))!=${file.crc}){console.log("${file.name} invalid");FAIL++}\n`; + }*/ }); fs.writeFileSync(OUTFILE, js); console.log("Output written to "+OUTFILE); diff --git a/bin/sanitycheck.js b/bin/sanitycheck.js old mode 100644 new mode 100755 index a2e912527..d911a20d6 --- a/bin/sanitycheck.js +++ b/bin/sanitycheck.js @@ -18,11 +18,11 @@ try { var BASEDIR = __dirname+"/../"; var APPSDIR = BASEDIR+"apps/"; function ERROR(s) { - console.error(s); + console.error("ERROR: "+s); process.exit(1); } function WARN(s) { - console.log(s); + console.log("Warning: "+s); } var appsFile, apps; @@ -39,13 +39,24 @@ try{ apps.forEach((app,addIdx) => { if (!app.id) ERROR(`App ${appIdx} has no id`); - console.log(`Checking ${app.id}...`); + //console.log(`Checking ${app.id}...`); var appDir = APPSDIR+app.id+"/"; if (!fs.existsSync(APPSDIR+app.id)) ERROR(`App ${app.id} has no directory`); if (!app.name) ERROR(`App ${app.id} has no name`); var isApp = !app.type || app.type=="app"; if (app.name.length>20 && !app.shortName && isApp) ERROR(`App ${app.id} has a long name, but no shortName`); if (!app.version) WARN(`App ${app.id} has no version`); + else { + if (!fs.existsSync(appDir+"ChangeLog")) { + if (app.version != "0.01") + WARN(`App ${app.id} has no ChangeLog`); + } else { + var versions = fs.readFileSync(appDir+"ChangeLog").toString().match(/\d+\.\d+:/g); + var lastChangeLog = versions.pop().slice(0,-1); + if (lastChangeLog != app.version) + WARN(`App ${app.id} app version (${app.version}) and ChangeLog (${lastChangeLog}) don't agree`); + } + } if (!app.description) ERROR(`App ${app.id} has no description`); if (!app.icon) ERROR(`App ${app.id} has no icon`); if (!fs.existsSync(appDir+app.icon)) ERROR(`App ${app.id} icon doesn't exist`); @@ -59,8 +70,10 @@ apps.forEach((app,addIdx) => { fileNames.push(file.name); if (file.url) if (!fs.existsSync(appDir+file.url)) ERROR(`App ${app.id} file ${file.url} doesn't exist`); if (!file.url && !file.content && !app.custom) ERROR(`App ${app.id} file ${file.name} has no contents`); + var fileContents = ""; + if (file.content) fileContents = file.content; + if (file.url) fileContents = fs.readFileSync(appDir+file.url).toString(); if (file.evaluate) { - var fileContents = file.content ? file.content : fs.readFileSync(appDir+file.url).toString(); try { acorn.parse("("+fileContents+")"); } catch(e) { @@ -74,6 +87,21 @@ apps.forEach((app,addIdx) => { ERROR(`App ${app.id}'s ${file.name} has evaluate:true but is not valid JS expression`); } } + if (file.name.endsWith(".js")) { + // TODO: actual lint? + try { + acorn.parse(fileContents); + } catch(e) { + console.log("====================================================="); + console.log(" PARSE OF "+appDir+file.url+" failed."); + console.log(""); + console.log(e); + console.log("====================================================="); + console.log(fileContents); + console.log("====================================================="); + ERROR(`App ${app.id}'s ${file.name} is a JS file but isn't valid JS`); + } + } }); //console.log(fileNames); if (isApp && !fileNames.includes(app.id+".app.js")) ERROR(`App ${app.id} has no entrypoint`); diff --git a/comms.js b/comms.js index 9f685500e..de2c328b3 100644 --- a/comms.js +++ b/comms.js @@ -2,28 +2,47 @@ Puck.debug=3; // FIXME: use UART lib so that we handle errors properly var Comms = { -reset : () => new Promise((resolve,reject) => { - Puck.write("\x03\x10reset();\n", (result) => { - if (result===null) return reject(""); +reset : (opt) => new Promise((resolve,reject) => { + Puck.write(`\x03\x10reset(${opt=="wipe"?"1":""});\n`, (result) => { + if (result===null) return reject("Connection failed"); setTimeout(resolve,500); }); }), uploadApp : (app,skipReset) => { return AppInfo.getFiles(app, httpGet).then(fileContents => { return new Promise((resolve,reject) => { - fileContents = fileContents.map(storageFile=>storageFile.cmd).join("\n")+"\n"; - console.log("uploadApp",fileContents); + console.log("uploadApp",fileContents.map(f=>f.name).join(", ")); + // Upload each file one at a time + function doUploadFiles() { + // No files left - print 'reboot' message + if (fileContents.length==0) { + Puck.write(`\x10E.showMessage('Hold BTN3\\nto reload')\n`,(result) => { + if (result===null) return reject(""); + resolve(app); + }); + return; + } + var f = fileContents.shift(); + console.log(`Upload ${f.name} => ${JSON.stringify(f.content)}`); + // Chould check CRC here if needed instead of returning 'OK'... + // E.CRC32(require("Storage").read(${JSON.stringify(app.name)})) + Puck.write(`\x10${f.cmd};Bluetooth.println("OK")\n`,(result) => { + if (!result || result.trim()!="OK") return reject("Unexpected response "+(result||"")); + doUploadFiles(); + }, true); // wait for a newline + } + // Start the upload function doUpload() { - Puck.write(`\x10E.showMessage('Uploading\\n${app.id}...')\n${fileContents}\x10E.showMessage('Hold BTN3\\nto reload')\n`,(result) => { + Puck.write(`\x10E.showMessage('Uploading\\n${app.id}...')\n`,(result) => { if (result===null) return reject(""); - resolve(appJSON); + doUploadFiles(); }); } if (skipReset) { doUpload(); } else { // reset to ensure we have enough memory to upload what we need to - Comms.reset().then(doUpload) + Comms.reset().then(doUpload, reject) } }); }); @@ -32,7 +51,7 @@ getInstalledApps : () => { return new Promise((resolve,reject) => { Puck.write("\x03",(result) => { if (result===null) return reject(""); - Puck.eval('require("Storage").list(/\.info$/).map(f=>{var j=require("Storage").readJSON(f)||{};j.id=f.slice(0,-5);return j})', (appList,err) => { + Puck.eval('require("Storage").list(/\.info$/).map(f=>{var j=require("Storage").readJSON(f,1)||{};j.id=f.slice(0,-5);return j})', (appList,err) => { if (appList===null) return reject(err || ""); console.log("getInstalledApps", appList); resolve(appList); @@ -41,7 +60,8 @@ getInstalledApps : () => { }); }, removeApp : app => { // expects an app structure - var cmds = app.storage.map(file=>{ + var storage = [{name:app.id+".info"}].concat(app.storage); + var cmds = storage.map(file=>{ return `\x10require("Storage").erase(${toJS(file.name)});\n`; }).join(""); console.log("removeApp", cmds); @@ -53,7 +73,7 @@ removeApp : app => { // expects an app structure })); }, removeAllApps : () => { - return Comms.reset().then(() => new Promise((resolve,reject) => { + return Comms.reset("wipe").then(() => new Promise((resolve,reject) => { // Use write with newline here so we wait for it to finish Puck.write('\x10E.showMessage("Erasing...");require("Storage").eraseAll();Bluetooth.println("OK")\n', (result,err) => { if (!result || result.trim()!="OK") return reject(err || ""); @@ -68,7 +88,7 @@ setTime : () => { var cmd = '\x03\x10setTime('+(d.getTime()/1000)+');'; // in 1v93 we have timezones too cmd += 'E.setTimeZone('+tz+');'; - cmd += "(s=>{s&&(s.timezone="+tz+")&&require('Storage').write('setting.json',s);})(require('Storage').readJSON('setting.json'))\n"; + cmd += "(s=>{s&&(s.timezone="+tz+")&&require('Storage').write('setting.json',s);})(require('Storage').readJSON('setting.json',1))\n"; Puck.write(cmd, (result) => { if (result===null) return reject(""); resolve(); diff --git a/firmware.js b/firmware.js index a6923ab1c..2a3a697a3 100644 --- a/firmware.js +++ b/firmware.js @@ -1,33 +1,32 @@ // Generated by BangleApps/bin/firmwaremaker.js -require('Storage').write(".bootcde","E.setFlags({pretokenise:1});\nvar startapp;\ntry {\n startapp = require('Storage').readJSON('+start');\n} catch (e) {}\nif (startapp) {\n eval(require(\"Storage\").read(startapp.src));\n} else {\n setWatch(function displayMenu() {\n Bangle.setLCDOffset(0); // remove notifications\n Bangle.setLCDMode(\"direct\");\n g.clear();\n // attempt to remove any currently-running code\n clearInterval();\n clearWatch();\n Bangle.removeAllListeners();\n NRF.removeAllListeners();\n Bluetooth.removeAllListeners();\n E.removeAllListeners();\n delete GB;\n delete WIDGETS;\n delete WIDGETPOS;\n delete drawWidgets;\n var s = require(\"Storage\");\n var apps = s.list().filter(a=>a[0]=='+').map(app=>{\n try { return s.readJSON(app); }\n catch (e) { return {name:\"DEAD: \"+app.substr(1)} }\n }).filter(app=>app.type==\"app\" || app.type==\"clock\" || !app.type);\n apps.sort((a,b)=>{\n var n=(0|a.sortorder)-(0|b.sortorder);\n if (n) return n; // do sortorder first\n if (a.nameb.name) return 1;\n return 0;\n });\n var selected = 0;\n var menuScroll = 0;\n var menuShowing = false;\n\n function drawMenu() {\n g.setFont(\"6x8\",2);\n g.setFontAlign(-1,0);\n var n = 3;\n if (selected>=n+menuScroll) menuScroll = 1+selected-n;\n if (selectedn+menuScroll) g.fillPoly([120,239,100,219,140,219]);\n else g.clearRect(100,219,140,239);\n for (var i=0;i0) {\n selected--;\n drawMenu();\n }\n }, BTN1, {repeat:true});\n setWatch(function() {\n if (selected+1a[0]=='=').forEach(widget=>eval(require(\"Storage\").read(widget)));\n setTimeout(drawWidgets,100);\n // load clock if specified\n var clockApp = settings.clock;\n if (clockApp) clockApp = require(\"Storage\").read(clockApp)\n if (!clockApp) {\n var clockApps = require(\"Storage\").list().filter(a=>a[0]=='+').map(app=>{\n try { return require(\"Storage\").readJSON(app); }\n catch (e) {}\n }).filter(app=>app.type==\"clock\").sort((a, b) => a.sortorder - b.sortorder);\n if (clockApps && clockApps.length > 0)\n clockApp = require(\"Storage\").read(clockApps[0].src);\n delete clockApps;\n }\n if (clockApp) eval(clockApp);\n else E.showMessage(\"No Clock Found\");\n delete clockApp;\n}\n"); -require('Storage').write("+mclock",{"name":"Morphing Clock","type":"clock","icon":"*mclock","src":"-mclock","sortorder":-10,"version":"0.01","files":"+mclock,-mclock,*mclock"}); -require('Storage').write("-mclock","(function(){ // make our own scope so this is GC'd when intervals are cleared\n// Offscreen buffer\nvar buf = Graphics.createArrayBuffer(240,86,1,{msb:true});\nfunction flip() {\n g.setColor(1,1,1);\n g.drawImage({width:buf.getWidth(),height:buf.getHeight(),buffer:buf.buffer},0,50);\n}\n// The last time that we displayed\nvar lastTime = \" \";\n// If animating, this is the interval's id\nvar animInterval;\n\n/* Get array of lines from digit d to d+1.\n n is the amount (0..1)\n maxFive is true is this digit only counts 0..5 */\nconst DIGITS = {\n\" \":n=>[],\n\"0\":n=>[\n[n,0,1,0],\n[1,0,1,1],\n[1,1,1,2],\n[n,2,1,2],\n[n,1,n,2],\n[n,0,n,1]],\n\"1\":n=>[\n[1-n,0,1,0],\n[1,0,1,1],\n[1-n,1,1,1],\n[1-n,1,1-n,2],\n[1-n,2,1,2]],\n\"2\":n=>[\n[0,0,1,0],\n[1,0,1,1],\n[0,1,1,1],\n[0,1+n,0,2],\n[1,2-n,1,2],\n[0,2,1,2]],\n\"3\":n=>[\n[0,0,1-n,0],\n[0,0,0,n],\n[1,0,1,1],\n[0,1,1,1],\n[1,1,1,2],\n[n,2,1,2]],\n\"4\":n=>[\n[0,0,0,1],\n[1,0,1-n,0],\n[1,0,1,1-n],\n[0,1,1,1],\n[1,1,1,2],\n[1-n,2,1,2]],\n\"5\": (n,maxFive)=>maxFive ? [ // 5 -> 0\n[0,0,0,1],\n[0,0,1,0],\n[n,1,1,1],\n[1,1,1,2],\n[0,2,1,2],\n[0,2,0,2],\n[1,1-n,1,1],\n[0,1,0,1+n]] : [ // 5 -> 6\n[0,0,0,1],\n[0,0,1,0],\n[0,1,1,1],\n[1,1,1,2],\n[0,2,1,2],\n[0,2-n,0,2]],\n\"6\":n=>[\n[0,0,0,1-n],\n[0,0,1,0],\n[n,1,1,1],\n[1,1-n,1,1],\n[1,1,1,2],\n[n,2,1,2],\n[0,1-n,0,2-2*n]],\n\"7\":n=>[\n[0,0,0,n],\n[0,0,1,0],\n[1,0,1,1],\n[1-n,1,1,1],\n[1,1,1,2],\n[1-n,2,1,2],\n[1-n,1,1-n,2]],\n\"8\":n=>[\n[0,0,0,1],\n[0,0,1,0],\n[1,0,1,1],\n[0,1,1,1],\n[1,1,1,2],\n[0,2,1,2],\n[0,1,0,2-n]],\n\"9\":n=>[\n[0,0,0,1],\n[0,0,1,0],\n[1,0,1,1],\n[0,1,1-n,1],\n[0,1,0,1+n],\n[1,1,1,2],\n[0,2,1,2]],\n\":\":n=>[\n[0.4,0.4,0.6,0.4],\n[0.6,0.4,0.6,0.6],\n[0.6,0.6,0.4,0.6],\n[0.4,0.4,0.4,0.6],\n[0.4,1.4,0.6,1.4],\n[0.6,1.4,0.6,1.6],\n[0.6,1.6,0.4,1.6],\n[0.4,1.4,0.4,1.6]]\n};\n\n/* Draw a transition between lastText and thisText.\n 'n' is the amount - 0..1 */\nfunction draw(lastText,thisText,n) {\n buf.clear();\n var x = 1; // x offset\n const p = 2; // padding around digits\n var y = p; // y offset\n const s = 34; // character size\n for (var i=0;i{\n if (c[0]!=c[2]) // horiz\n buf.fillRect(x+c[0]*s,y+c[1]*s-p,x+c[2]*s,y+c[3]*s+p);\n else if (c[1]!=c[3]) // vert\n buf.fillRect(x+c[0]*s-p,y+c[1]*s,x+c[2]*s+p,y+c[3]*s);\n });\n if (thisCh==\":\") x-=4;\n x+=s+p+7;\n }\n y += 2*s;\n var d = new Date();\n buf.setFont(\"6x8\");\n buf.setFontAlign(-1,-1);\n buf.drawString((\"0\"+d.getSeconds()).substr(-2), x, y-8);\n // date\n buf.setFontAlign(0,-1);\n var date = d.toString().substr(0,15);\n buf.drawString(date, buf.getWidth()/2, y+8);\n flip();\n}\n\n/* Show the current time, and animate if needed */\nfunction showTime() {\n if (!Bangle.isLCDOn()) return;\n if (animInterval) return; // in animation - quit\n var d = new Date();\n var t = (\" \"+d.getHours()).substr(-2)+\":\"+\n (\"0\"+d.getMinutes()).substr(-2);\n var l = lastTime;\n // same - don't animate\n if (t==l) {\n draw(t,l,0);\n return;\n }\n var n = 0;\n animInterval = setInterval(function() {\n n += 1/10;\n if (n>=1) {\n n=1;\n clearInterval(animInterval);\n animInterval=0;\n }\n draw(l,t,n);\n }, 20);\n lastTime = t;\n}\n\nBangle.on('lcdPower',function(on) {\n if (on) {\n showTime();\n drawWidgets();\n }\n});\n\ng.clear();\n// Update time once a second\nsetInterval(showTime, 1000);\nshowTime();\n})();\n"); -require('Storage').write("*mclock",require("heatshrink").decompress(atob("mEwghC/AE8IxAAEwAWVDB4WIDBwWJAAIWPmf//8zDBpFDwYVBAAc4JJYWJDAoXKn4SC+EPAgXzC5JGCx4qDC4n//BIIEIRCEC4v/GBBdHC4xhCIw5dDC5BhCJAgXCRQoXGJAQXEUhAXHJAyNGC5KRCC7p2FC5B4CC5kggQXOBwvyBQMvSA4XL+EIwCoIC8ZHCgYXNO44LBBIiPPCAIwFC5DXGAAMwGAjvPGA4XIwYXHGALBDnAXFhCQHGAaOFwAXGPA4bFC4xIMIxIXDJBJGEC4xICSJCNEIwowEMJBdCFwwXEMJBdCC5BICDA4WDIw4wEAAMzCoMzBAgWIDAwAGCxRJEAAxFJDBgWNDBAWPAH4AYA=="))); -require('Storage').write("+setting",{"name":"Settings","type":"app","icon":"*settings","src":"-settings","version":"0.01","files":"+setting,-setting,=setting,setting.json,*setting"}); -require('Storage').write("-setting","Bangle.setLCDPower(1);\nBangle.setLCDTimeout(0);\n\ng.clear();\nconst storage = require('Storage');\nlet settings;\n\nfunction debug(msg, arg) {\n if (settings.debug)\n console.log(msg, arg);\n}\n\nfunction updateSettings() {\n debug('updating settings', settings);\n //storage.erase('setting.json'); // - not needed, just causes extra writes if settings were the same\n storage.write('setting.json', settings);\n}\n\nfunction resetSettings() {\n settings = {\n ble: true,\n dev: true,\n timeout: 10,\n vibrate: true,\n beep: true,\n timezone: 0,\n HID : false,\n HIDGestures: false,\n debug: false,\n clock: null\n };\n setLCDTimeout(settings.timeout);\n updateSettings();\n}\n\ntry {\n settings = storage.readJSON('setting.json');\n} catch (e) {}\nif (!settings) resetSettings();\n\nconst boolFormat = (v) => v ? \"On\" : \"Off\";\n\nfunction showMainMenu() {\n const mainmenu = {\n '': { 'title': 'Settings' },\n 'BLE': {\n value: settings.ble,\n format: boolFormat,\n onchange: () => {\n settings.ble = !settings.ble;\n updateSettings();\n }\n },\n 'Programmable': {\n value: settings.dev,\n format: boolFormat,\n onchange: () => {\n settings.dev = !settings.dev;\n updateSettings();\n }\n },\n 'LCD Timeout': {\n value: settings.timeout,\n min: 0,\n max: 60,\n step: 5,\n onchange: v => {\n settings.timeout = 0 | v;\n updateSettings();\n Bangle.setLCDTimeout(settings.timeout);\n }\n },\n 'Beep': {\n value: settings.beep,\n format: boolFormat,\n onchange: () => {\n settings.beep = !settings.beep;\n updateSettings();\n if (settings.beep) {\n Bangle.beep(1);\n }\n }\n },\n 'Vibration': {\n value: settings.vibrate,\n format: boolFormat,\n onchange: () => {\n settings.vibrate = !settings.vibrate;\n updateSettings();\n if (settings.vibrate) {\n VIBRATE.write(1);\n setTimeout(()=>VIBRATE.write(0), 10);\n }\n }\n },\n 'Select Clock': showClockMenu,\n 'Time Zone': {\n value: settings.timezone,\n min: -11,\n max: 12,\n step: 1,\n onchange: v => {\n settings.timezone = 0 | v;\n updateSettings();\n }\n },\n 'HID': {\n value: settings.HID,\n format: boolFormat,\n onchange: () => {\n settings.HID = !settings.HID;\n updateSettings();\n }\n },\n 'HID Gestures': {\n value: settings.HIDGestures,\n format: boolFormat,\n onchange: () => {\n settings.HIDGestures = !settings.HIDGestures;\n updateSettings();\n }\n },\n 'Debug': {\n value: settings.debug,\n format: boolFormat,\n onchange: () => {\n settings.debug = !settings.debug;\n updateSettings();\n }\n },\n 'Set Time': showSetTimeMenu,\n 'Make Connectable': makeConnectable,\n 'Reset Settings': showResetMenu,\n 'Turn Off': Bangle.off,\n '< Back': load\n };\n return Bangle.menu(mainmenu);\n}\n\nfunction showResetMenu() {\n const resetmenu = {\n '': { 'title': 'Reset' },\n '< Back': showMainMenu,\n 'Reset Settings': () => {\n E.showPrompt('Reset Settings?').then((v) => {\n if (v) {\n E.showMessage('Resetting');\n resetSettings();\n }\n setTimeout(showMainMenu, 50);\n });\n },\n // this is include for debugging. remove for production\n /*'Erase': () => {\n storage.erase('=setting');\n storage.erase('-setting');\n storage.erase('setting.json');\n storage.erase('*setting');\n storage.erase('+setting');\n E.reboot();\n }*/\n };\n return Bangle.menu(resetmenu);\n}\n\nfunction makeConnectable() {\n try { NRF.wake(); } catch(e) {}\n Bluetooth.setConsole(1);\n var name=\"Bangle.js \"+NRF.getAddress().substr(-5).replace(\":\",\"\");\n E.showPrompt(name+\"\\nStay Connectable?\",{title:\"Connectable\"}).then(r=>{\n if (settings.ble!=r) {\n settings.ble = r;\n updateSettings();\n }\n if (!r) try { NRF.sleep(); } catch(e) {}\n showMainMenu();\n });\n}\nfunction showClockMenu() {\n var clockApps = require(\"Storage\").list().filter(a=>a[0]=='+').map(app=>{\n try { return require(\"Storage\").readJSON(app); }\n catch (e) {}\n }).filter(app=>app.type==\"clock\").sort((a, b) => a.sortorder - b.sortorder);\n const clockMenu = {\n '': {\n 'title': 'Select Clock',\n },\n '< Back': showMainMenu,\n };\n clockApps.forEach((app,index) => {\n var label = app.name;\n if ((!settings.clock && index === 0) || (settings.clock === app.src)) {\n label = \"* \"+label;\n }\n clockMenu[label] = () => {\n if (settings.clock !== app.src) {\n settings.clock = app.src;\n updateSettings();\n showMainMenu();\n }\n };\n });\n if (clockApps.length === 0) {\n clockMenu[\"No Clocks Found\"] = () => {};\n }\n return Bangle.menu(clockMenu);\n}\n\n\n\nfunction showSetTimeMenu() {\n d = new Date();\n const timemenu = {\n '': {\n 'title': 'Set Time',\n 'predraw': function() {\n d = new Date();\n timemenu.Hour.value = d.getHours();\n timemenu.Minute.value = d.getMinutes();\n timemenu.Second.value = d.getSeconds();\n timemenu.Date.value = d.getDate();\n timemenu.Month.value = d.getMonth() + 1;\n timemenu.Year.value = d.getFullYear();\n }\n },\n '< Back': showMainMenu,\n 'Hour': {\n value: d.getHours(),\n min: 0,\n max: 23,\n step: 1,\n onchange: v => {\n d = new Date();\n d.setHours(v);\n setTime(d.getTime()/1000);\n }\n },\n 'Minute': {\n value: d.getMinutes(),\n min: 0,\n max: 59,\n step: 1,\n onchange: v => {\n d = new Date();\n d.setMinutes(v);\n setTime(d.getTime()/1000);\n }\n },\n 'Second': {\n value: d.getSeconds(),\n min: 0,\n max: 59,\n step: 1,\n onchange: v => {\n d = new Date();\n d.setSeconds(v);\n setTime(d.getTime()/1000);\n }\n },\n 'Date': {\n value: d.getDate(),\n min: 1,\n max: 31,\n step: 1,\n onchange: v => {\n d = new Date();\n d.setDate(v);\n setTime(d.getTime()/1000);\n }\n },\n 'Month': {\n value: d.getMonth() + 1,\n min: 1,\n max: 12,\n step: 1,\n onchange: v => {\n d = new Date();\n d.setMonth(v - 1);\n setTime(d.getTime()/1000);\n }\n },\n 'Year': {\n value: d.getFullYear(),\n min: 2019,\n max: 2100,\n step: 1,\n onchange: v => {\n d = new Date();\n d.setFullYear(v);\n setTime(d.getTime()/1000);\n }\n }\n };\n return Bangle.menu(timemenu);\n}\n\nshowMainMenu();\n"); -require('Storage').write("=setting","Bangle.HID = E.toUint8Array(atob(\"BQEJBqEBhQIFBxngKecVACUBdQGVCIEClQF1CIEBlQV1AQUIGQEpBZEClQF1A5EBlQZ1CBUAJXMFBxkAKXOBAAkFFQAm/wB1CJUCsQLABQwJAaEBhQEVACUBdQGVAQm1gQIJtoECCbeBAgm4gQIJzYECCeKBAgnpgQIJ6oECwA==\"));\n\n(function() {\n var s = require('Storage').readJSON('setting.json');\n var adv = { uart: true };\n if (s.ble) {\n if (s.dev)\n Bluetooth.setConsole(true);\n else\n Terminal.setConsole(true);\n if (s.HID) {\n adv.hid = Bangle.HID;\n } else\n delete Bangle.HID;\n }\n NRF.setServices({}, adv);\n // we just reset, so BLE should be on\n try { // disable advertising if BLE should be off\n if (!s.ble) NRF.sleep();\n else NRF.wake();\n } catch(e) {}\n if (!s.vibrate) Bangle.buzz=Promise.resolve;\n if (!s.beep) Bangle.beep=Promise.resolve;\n Bangle.setLCDTimeout(s.timeout);\n if (!s.timeout) Bangle.setLCDPower(1);\n E.setTimeZone(s.timezone);\n})()\n"); -require('Storage').write("setting.json",{ - ble: true, // Bluetooth enabled by default - dev: true, // Espruino IDE enabled by default - timeout: 10, // Default LCD timeout in seconds - vibrate: true, // Vibration enabled by default. App must support - beep: true, // Beep enabled by default. App must support - timezone: 0, // Set the timezone for the device - HID : false, // BLE HID mode, off by default - HIDGestures: false, - debug: false, // Debug mode disabled by default. App must support -}); -require('Storage').write("*setting",require("heatshrink").decompress(atob("mEwghC/AFEiAAgX/C/4SFkADBgQXFBIgECAAYSCkAWGBIoXGyQTHABBZLkUhiMRiQXLIQwVBAAZlIC44tCAAYxGIxIWFGA4XIFwwwHXBAWHGAwXHFxAwGPAYXTX44XDiAJBgIXGyDAHFAYKDMAq+EGAgXNCwwX/C453XU6IWHa6ZFCC6JJCC4hgEAAoOEC5AwIFwhgEBAgwIBoqmGGBIuFVAgXFGAwLFYAoLFGIYtFeA4MGABMpC4pICkBMGBIpGFC4SuIBIoWFAAxZLC/4X/AFQ"))); -require('Storage').write("+astroid",{"name":"Asteroids!","type":"app","icon":"*astroid","src":"-astroid","version":"0.01","files":"+astroid,-astroid,*astroid"}); -require('Storage').write("-astroid","Bangle.setLCDMode(\"doublebuffered\");\n\nvar W = g.getWidth();\nvar H = g.getHeight();\ng.setFontAlign(0,-1);\nvar BTNL = BTN4;\nvar BTNR = BTN5;\nvar BTNU = BTN1;\nvar BTNA = BTN2;\n\nfunction newAst(x,y) {\n var a = {\n x:x,y:y,\n vx:Math.random()-0.5,\n vy:Math.random()-0.5,\n rad:3+Math.random()*5\n };\n return a;\n}\n\nvar running = true;\nvar ship = {};\nvar ammo = [];\nvar ast = [];\nvar score = 0;\nvar level = 4;\nvar timeSinceFired = 0;\nvar lastFrame;\n\nfunction gameStop() {\n running = false;\n g.clear();\n g.drawString(\"Game Over!\",120,(H-6)/2);\n g.flip();\n}\n\nfunction addAsteroids() {\n for (var i=0;i=W) ship.x-=W;\n if (ship.y>=H) ship.y-=H;\n timeSinceFired+=d;\n if (BTNA.read() && timeSinceFired>4) { // fire!\n Bangle.beep(10);\n timeSinceFired = 0;\n ammo.push({\n x:ship.x+Math.cos(ship.r)*4,\n y:ship.y+Math.sin(ship.r)*4,\n vx:Math.cos(ship.r)*3,\n vy:Math.sin(ship.r)*3,\n });\n }\n\n g.clear();\n\n g.drawString(score,120,0);\n var rs = Math.PI*0.8;\n g.drawPoly([\n ship.x+Math.cos(ship.r)*4, ship.y+Math.sin(ship.r)*4,\n ship.x+Math.cos(ship.r+rs)*3, ship.y+Math.sin(ship.r+rs)*3,\n ship.x+Math.cos(ship.r-rs)*3, ship.y+Math.sin(ship.r-rs)*3,\n ],true);\n var na = [];\n ammo.forEach(function(a) {\n a.x += a.vx*d;\n a.y += a.vy*d;\n g.fillRect(a.x-1, a.y, a.x+1, a.y);\n g.fillRect(a.x, a.y-1, a.x, a.y+1);\n var hit = false;\n ast.forEach(function(b) {\n var dx = a.x-b.x;\n var dy = a.y-b.y;\n var d = Math.sqrt(dx*dx+dy*dy);\n if (d=0 && a.y>=0 && a.x=W) a.x-=W;\n if (a.y>=H) a.y-=H;\n if (!a.hit) {\n na.push(a);\n } else if (a.rad>4) {\n Bangle.buzz(100);\n a.hit = false;\n var vx = 1*(Math.random()-0.5);\n var vy = 1*(Math.random()-0.5);\n a.rad/=2;\n na.push({\n x:a.x,\n y:a.y,\n vx:a.vx-vx,\n vy:a.vy-vy,\n rad:a.rad,\n });\n a.vx += vx;\n a.vy += vy;\n na.push(a);\n }\n\n var dx = a.x-ship.x;\n var dy = a.y-ship.y;\n var d = Math.sqrt(dx*dx+dy*dy);\n if (d < a.rad) crashed = true;\n });\n ast=na;\n if (!ast.length) {\n level++;\n addAsteroids();\n }\n g.flip();\n if (crashed) {\n Bangle.buzz(500);\n gameStop();\n }\n}\n\ngameStart();\nsetInterval(onFrame, 50);\n"); -require('Storage').write("*astroid",require("heatshrink").decompress(atob("mEwghC/ADkN6APN7oDGC64AWDRw9DIIgXVLh/eAYQtEFxsN7oqCCQQDBC5oOBC4IDHFxgmBAY4uvGJQuMC5IuLhpdWMBQuMSBQuMF5IubhqHDFyIfGX5giFkWQGYa/KEQcCkQACmA6KFwwWDkUgNJJVGFwgABFwoXGBYIuGC4jrL8AuBmczJAgLBRhAXCFwQXGRhIXGkUjI4i/CapReHC4KEEC6a/KC48jmQXCfQoXNX44XJawx3CX5gXHXwQuJcYYXHFxaaEhIXEFxiyFGIeQFxqEIFxy0HhwuOFAgAFCxowDAAguODA4WRAH4ADA=="))); -require('Storage').write("+gpstime",{"name":"GPS Time","type":"app","icon":"*gpstime","src":"-gpstime","version":"0.01","files":"+gpstime,-gpstime,*gpstime"}); -require('Storage').write("-gpstime","var img = require(\"heatshrink\").decompress(atob(\"mEwghC/AH8A1QWVhWq0AuVAAIuVAAIwT1WinQwTFwMzmQwTCYMjlUqGCIuBlWi0UzC6JdBIoMjC4UDmAuOkYXBPAWgmczLp2ilUiVAUDC4IwLFwIUBLoJ2BFwQwM1WjCgJ1DFwQwLFwJ1B0SQCkQWDGBQXBCgK9BDgKQBAAgwJOwUzRgIDBC54wCkZdGPBwACRgguDBIIwLFxEJBQIwLFxGaBYQwKFxQwLgAWGmQuBcAQwJC48ifYYwJgUidgsyC4L7DGBIXBdohnBCgL7BcYIXIGAqMCIoL7DL5IwERgIUBLoL7BO5QXBGAK7DkWiOxQXGFwOjFoUyFxZhDgBdCCgJ1CCxYxCgBABkcqOwIuNGAQXC0S9BLpgAFXoIwBmYuPAAYwCLp4wHFyYwDFyYwDFygwCCyoA/AFQA=\"));\n\nBangle.setLCDPower(1);\nBangle.setLCDTimeout(0);\n\ng.clear();\n\n\n\nvar fix;\nBangle.on('GPS',function(f) {\n fix = f;\n g.setFont(\"6x8\",2);\n g.setFontAlign(0,0);\n g.clearRect(90,30,239,90);\n if (fix.fix) {\n g.drawString(\"GPS\",170,40);\n g.drawString(\"Acquired\",170,60);\n } else {\n g.drawString(\"Waiting for\",170,40);\n g.drawString(\"GPS Fix\",170,60);\n }\n g.setFont(\"6x8\");\n g.drawString(fix.satellites+\" satellites\",170,80);\n \n g.clearRect(0,100,239,239);\n var t = fix.time.toString().split(\" \");/*\n [\n \"Sun\",\n \"Nov\",\n \"10\",\n \"2019\",\n \"15:55:35\",\n \"GMT+0100\"\n ]\n */\n //g.setFont(\"6x8\",2);\n //g.drawString(t[0],120,110); // day\n g.setFont(\"6x8\",3);\n g.drawString(t[1]+\" \"+t[2],120,135); // date\n g.setFont(\"6x8\",2);\n g.drawString(t[3],120,160); // year\n g.setFont(\"6x8\",3);\n g.drawString(t[4],120,185); // time\n // timezone\n var tz = (new Date()).getTimezoneOffset()/60;\n if (tz==0) tz=\"UTC\";\n else if (tz>0) tz=\"UTC+\"+tz;\n else tz=\"UTC\"+tz;\n g.setFont(\"6x8\",2);\n g.drawString(tz,120,210); // gmt\n g.setFontAlign(0,0,3);\n g.drawString(\"Set\",230,120);\n g.setFontAlign(0,0);\n});\n\nsetInterval(function() {\n g.drawImage(img,48,48,{scale:1.5,rotate:Math.sin(getTime()*2)/2});\n},100);\nsetWatch(function() {\n setTime(fix.time.getTime()/1000);\n}, BTN2, {repeat:true});\n\nBangle.setGPSPower(1)\n"); -require('Storage').write("*gpstime",require("heatshrink").decompress(atob("mEwghC/AH8A1QWVhWq0AuVAAIuVAAIwT1WinQwTFwMzmQwTCYMjlUqGCIuBlWi0UzC6JdBIoMjC4UDmAuOkYXBPAWgmczLp2ilUiVAUDC4IwLFwIUBLoJ2BFwQwM1WjCgJ1DFwQwLFwJ1B0SQCkQWDGBQXBCgK9BDgKQBAAgwJOwUzRgIDBC54wCkZdGPBwACRgguDBIIwLFxEJBQIwLFxGaBYQwKFxQwLgAWGmQuBcAQwJC48ifYYwJgUidgsyC4L7DGBIXBdohnBCgL7BcYIXIGAqMCIoL7DL5IwERgIUBLoL7BO5QXBGAK7DkWiOxQXGFwOjFoUyFxZhDgBdCCgJ1CCxYxCgBABkcqOwIuNGAQXC0S9BLpgAFXoIwBmYuPAAYwCLp4wHFyYwDFyYwDFygwCCyoA/AFQA="))); -require('Storage').write("+compass",{"name":"Compass","type":"app","icon":"*compass","src":"-compass","version":"0.01","files":"+compass,-compass,*compass"}); -require('Storage').write("-compass","g.clear();\ng.setColor(0,0.5,1);\ng.fillCircle(120,130,80,80);\ng.setColor(0,0,0);\ng.fillCircle(120,130,70,70);\n\nfunction arrow(r,c) {\n r=r*Math.PI/180;\n var p = Math.PI/2;\n g.setColor(c);\n g.fillPoly([\n 120+60*Math.sin(r), 130-60*Math.cos(r),\n 120+10*Math.sin(r+p), 130-10*Math.cos(r+p),\n 120+10*Math.sin(r+-p), 130-10*Math.cos(r-p),\n ]);\n}\n\nvar oldHeading = 0;\nBangle.on('mag', function(m) {\n if (!Bangle.isLCDOn()) return;\n g.setFont(\"6x8\",3);\n g.setColor(0);\n g.fillRect(70,0,170,24);\n g.setColor(0xffff);\n g.setFontAlign(0,0);\n g.drawString(isNaN(m.heading)?\"---\":Math.round(m.heading),120,12);\n g.setColor(0,0,0);\n arrow(oldHeading,0);\n arrow(oldHeading+180,0);\n arrow(m.heading,0xF800);\n arrow(m.heading+180,0x001F);\n oldHeading = m.heading;\n});\nBangle.setCompassPower(1);\n"); -require('Storage').write("*compass",require("heatshrink").decompress(atob("mEwghC/AE8IxAAEwAWVDB4WIDBwWJAAIWOwcz///mc4DBhFDwYVBAAYYDJJAWJDAoXKCw//+YXJIwWPCQk/Aof4JBAuHC4v/GBBdHC4nzMIZGHCAIOBC4vz75hDJAgXCCgS9CC4fdAYQXGIwsyCAPyl//nvdVQoXFRofzkYXCCwJGBSIgXFQ4kymcykfdIwZgDC5XzkUyCwJGDC6FNCwPTC5i9FmQXCMgLZFC48zLgMilUv/vdkUjBII9BC6HSC55HD1WiklDNIgXIBok61QYBkSBFC5kqCwMjC6RGB1RcCR4gXIx4MC+Wqkfyl70BEQf4C4+DIwYqBC4XzGAc4C4sISAfz0QDCFgUzRwmAC4wQB+QTCC4f/AYJeCC4hIEPQi9FIwwXDbIzVHC4xICSIYXGRoRGFGAgqFXgouGC4iqDLo4XIJAQYHCwZGHGAgYBXQUzCwYuIDAwAHCxRJEAAxFJDBgWNDBAWPAH4AYA="))); -require('Storage').write("+sbt",{"name":"bluetooth","type":"widget","src":"=sbt","version":"0.01","files":"+sbt,=sbt"}); -require('Storage').write("=sbt","(function(){\nvar img_bt = E.toArrayBuffer(atob(\"CxQBBgDgFgJgR4jZMawfAcA4D4NYybEYIwTAsBwDAA==\"));\nvar xpos = WIDGETPOS.tr-24;\nWIDGETPOS.tr-=24;\n\nfunction draw() {\n var x = xpos, y = 0;\n if (NRF.getSecurityStatus().connected)\n g.setColor(0,0.5,1);\n else\n g.setColor(0.3,0.3,0.3);\n g.drawImage(img_bt,10+x,2+y);\n g.setColor(1,1,1);\n}\nfunction changed() {\n draw();\n g.flip();\n}\nNRF.on('connected',changed);\nNRF.on('disconnected',changed);\nWIDGETS[\"bluetooth\"]={draw:draw};\n})()\n"); -require('Storage').write("+sbat",{"name":"Battery Level","type":"widget","src":"=sbat","version":"0.01","files":"+sbat,=sbat"}); -require('Storage').write("=sbat","(function(){\nvar img_charge = E.toArrayBuffer(atob(\"DhgBHOBzgc4HOP////////////////////3/4HgB4AeAHgB4AeAHgB4AeAHg\"));\nvar CHARGING = 0x07E0;\nvar xpos = WIDGETPOS.tr-64;\nWIDGETPOS.tr-=68;\n\nfunction draw() {\n var s = 63;\n var x = xpos, y = 0;\n g.clearRect(x,y,x+s,y+23);\n if (Bangle.isCharging()) {\n g.setColor(CHARGING).drawImage(img_charge,x,y);\n x+=16;\n s-=16;\n }\n g.setColor(1,1,1);\n g.fillRect(x,y+2,x+s-4,y+21);\n g.clearRect(x+2,y+4,x+s-6,y+19);\n g.fillRect(x+s-3,y+10,x+s,y+14);\n g.setColor(CHARGING).fillRect(x+4,y+6,x+4+E.getBattery()*(s-12)/100,y+17);\n g.setColor(1,1,1);\n}\nBangle.on('charging',function(charging) { draw(); g.flip(); if(charging)Bangle.buzz(); });\nWIDGETS[\"battery\"]={draw:draw};\n})()\n"); +reset(1) +var FAIL=0; +require('Storage').write(".boot0","// This ALWAYS runs at boot\nE.setFlags({pretokenise:1});\n// Load settings...\nvar s = require('Storage').readJSON('setting.json',1)||{};\nif (s.ble!==false) {\n if (s.HID) { // Human interface device\n Bangle.HID = E.toUint8Array(atob(\"BQEJBqEBhQIFBxngKecVACUBdQGVCIEClQF1CIEBlQV1AQUIGQEpBZEClQF1A5EBlQZ1CBUAJXMFBxkAKXOBAAkFFQAm/wB1CJUCsQLABQwJAaEBhQEVACUBdQGVAQm1gQIJtoECCbeBAgm4gQIJzYECCeKBAgnpgQIJ6oECwA==\"));\n NRF.setServices({}, {uart:true, hid:Bangle.HID});\n }\n}\nif (s.blerepl===false) { // If not programmable, force terminal off Bluetooth\n if (s.log) Terminal.setConsole(true); // if showing debug, force REPL onto terminal\n else E.setConsole(null,{force:true}); // on new (2v05+) firmware we have E.setConsole which allows a 'null' console\n} else {\n if (s.log) Terminal.setConsole(); // if showing debug, put REPL on terminal (until connection)\n else Bluetooth.setConsole(true); // else if no debug, force REPL to Bluetooth\n}\n// we just reset, so BLE should be on.\n// Don't disconnect if something is already connected to us\nif (s.ble===false && !NRF.getSecurityStatus().connected) NRF.sleep();\n// Set time, vibrate, beep, etc\nif (!s.vibrate) Bangle.buzz=Promise.resolve;\nif (!s.beep) Bangle.beep=Promise.resolve;\nBangle.setLCDTimeout(s.timeout);\nif (!s.timeout) Bangle.setLCDPower(1);\nE.setTimeZone(s.timezone);\ndelete s;\n// check for alarms\nvar alarms = require('Storage').readJSON('alarm.json',1)||[];\nvar time = new Date();\nvar active = alarms.filter(a=>a.on&&(a.last!=time.getDate()));\nif (active.length) {\n active = active.sort((a,b)=>a.hr-b.hr);\n var hr = time.getHours()+(time.getMinutes()/60)+(time.getSeconds()/3600);\n if (!require('Storage').read(\"alarm.js\")) {\n console.log(\"No alarm app!\");\n require('Storage').write('alarm.json',\"[]\")\n } else {\n var t = 3600000*(active[0].hr-hr);\n if (t<1000) t=1000;\n /* execute alarm at the correct time. We avoid execing immediately\n since this code will get called AGAIN when alarm.js is loaded. alarm.js\n will then clearInterval() to get rid of this call so it can proceed\n normally. */\n setTimeout(function() {\n load(\"alarm.js\");\n },t);\n }\n}\n"); +require('Storage').write(".bootcde","// This runs after a 'fresh' boot\nvar settings=require(\"Storage\").readJSON('setting.json',1)||{};\nif (!settings.welcomed && require(\"Storage\").read(\"welcome.js\")!==undefined) {\n setTimeout(()=>load(\"welcome.js\"));\n} else {\n // load clock if specified\n var clockApp = settings.clock;\n if (clockApp) clockApp = require(\"Storage\").read(clockApp)\n if (!clockApp) {\n var clockApps = require(\"Storage\").list(/\\.info$/).map(app=>require(\"Storage\").readJSON(app,1)||{}).filter(app=>app.type==\"clock\").sort((a, b) => a.sortorder - b.sortorder);\n if (clockApps && clockApps.length > 0)\n clockApp = require(\"Storage\").read(clockApps[0].src);\n delete clockApps;\n }\n if (!clockApp) clockApp='E.showMessage(\"No Clock Found\")';\n delete settings;\n // check to see if our clock is wrong - if it is use GPS time\n if ((new Date()).getFullYear()==1970) {\n E.showMessage(\"Searching for\\nGPS time\");\n Bangle.on('GPS',function cb(g) {\n Bangle.setGPSPower(0);\n Bangle.removeListener(\"GPS\",cb);\n if (!g.time || (g.time.getFullYear()<2000) ||\n (g.time.getFullYear()==2250)) {\n // GPS receiver's time not set - just boot clock anyway\n eval(clockApp);delete clockApp;\n return;\n }\n // We have a GPS time. Set time and reboot (to load alarms properly)\n setTime(g.time.getTime()/1000);\n load();\n });\n Bangle.setGPSPower(1);\n } else {\n eval(clockApp);\n delete clockApp;\n }\n}\n"); +require('Storage').write("boot.info","{\"id\":\"boot\",\"name\":\"Bootloader\",\"type\":\"bootloader\",\"sortorder\":-10,\"version\":\"0.09\",\"files\":\"boot.info,.boot0,.bootcde\"}"); +require('Storage').write("launch.app.js","var s = require(\"Storage\");\nvar apps = s.list(/\\.info$/).map(app=>s.readJSON(app,1)||{name:\"DEAD: \"+app.substr(1)}).filter(app=>app.type==\"app\" || app.type==\"clock\" || !app.type);\napps.sort((a,b)=>{\n var n=(0|a.sortorder)-(0|b.sortorder);\n if (n) return n; // do sortorder first\n if (a.nameb.name) return 1;\n return 0;\n});\nvar selected = 0;\nvar menuScroll = 0;\nvar menuShowing = false;\n\nfunction drawMenu() {\n g.setFont(\"6x8\",2);\n g.setFontAlign(-1,0);\n var n = 3;\n if (selected>=n+menuScroll) menuScroll = 1+selected-n;\n if (selectedn+menuScroll) g.fillPoly([120,239,100,219,140,219]);\n else g.clearRect(100,219,140,239);\n for (var i=0;i0) {\n selected--;\n drawMenu();\n }\n}, BTN1, {repeat:true});\nsetWatch(function() {\n if (selected+1[],\n\"0\":n=>[\n[n,0,1,0],\n[1,0,1,1],\n[1,1,1,2],\n[n,2,1,2],\n[n,1,n,2],\n[n,0,n,1]],\n\"1\":n=>[\n[1-n,0,1,0],\n[1,0,1,1],\n[1-n,1,1,1],\n[1-n,1,1-n,2],\n[1-n,2,1,2]],\n\"2\":n=>[\n[0,0,1,0],\n[1,0,1,1],\n[0,1,1,1],\n[0,1+n,0,2],\n[1,2-n,1,2],\n[0,2,1,2]],\n\"3\":n=>[\n[0,0,1-n,0],\n[0,0,0,n],\n[1,0,1,1],\n[0,1,1,1],\n[1,1,1,2],\n[n,2,1,2]],\n\"4\":n=>[\n[0,0,0,1],\n[1,0,1-n,0],\n[1,0,1,1-n],\n[0,1,1,1],\n[1,1,1,2],\n[1-n,2,1,2]],\n\"5\": (n,maxFive)=>maxFive ? [ // 5 -> 0\n[0,0,0,1],\n[0,0,1,0],\n[n,1,1,1],\n[1,1,1,2],\n[0,2,1,2],\n[0,2,0,2],\n[1,1-n,1,1],\n[0,1,0,1+n]] : [ // 5 -> 6\n[0,0,0,1],\n[0,0,1,0],\n[0,1,1,1],\n[1,1,1,2],\n[0,2,1,2],\n[0,2-n,0,2]],\n\"6\":n=>[\n[0,0,0,1-n],\n[0,0,1,0],\n[n,1,1,1],\n[1,1-n,1,1],\n[1,1,1,2],\n[n,2,1,2],\n[0,1-n,0,2-2*n]],\n\"7\":n=>[\n[0,0,0,n],\n[0,0,1,0],\n[1,0,1,1],\n[1-n,1,1,1],\n[1,1,1,2],\n[1-n,2,1,2],\n[1-n,1,1-n,2]],\n\"8\":n=>[\n[0,0,0,1],\n[0,0,1,0],\n[1,0,1,1],\n[0,1,1,1],\n[1,1,1,2],\n[0,2,1,2],\n[0,1,0,2-n]],\n\"9\":n=>[\n[0,0,0,1],\n[0,0,1,0],\n[1,0,1,1],\n[0,1,1-n,1],\n[0,1,0,1+n],\n[1,1,1,2],\n[0,2,1,2]],\n\":\":n=>[\n[0.4,0.4,0.6,0.4],\n[0.6,0.4,0.6,0.6],\n[0.6,0.6,0.4,0.6],\n[0.4,0.4,0.4,0.6],\n[0.4,1.4,0.6,1.4],\n[0.6,1.4,0.6,1.6],\n[0.6,1.6,0.4,1.6],\n[0.4,1.4,0.4,1.6]]\n};\n\n/* Draw a transition between lastText and thisText.\n 'n' is the amount - 0..1 */\nfunction draw(lastText,thisText,n) {\n buf.clear();\n var x = 1; // x offset\n const p = 2; // padding around digits\n var y = p; // y offset\n const s = 34; // character size\n for (var i=0;i{\n if (c[0]!=c[2]) // horiz\n buf.fillRect(x+c[0]*s,y+c[1]*s-p,x+c[2]*s,y+c[3]*s+p);\n else if (c[1]!=c[3]) // vert\n buf.fillRect(x+c[0]*s-p,y+c[1]*s,x+c[2]*s+p,y+c[3]*s);\n });\n if (thisCh==\":\") x-=4;\n x+=s+p+7;\n }\n y += 2*s;\n var d = new Date();\n buf.setFont(\"6x8\");\n buf.setFontAlign(-1,-1);\n buf.drawString((\"0\"+d.getSeconds()).substr(-2), x, y-8);\n // date\n buf.setFontAlign(0,-1);\n var date = d.toString().substr(0,15);\n buf.drawString(date, buf.getWidth()/2, y+8);\n flip();\n}\n\n/* Show the current time, and animate if needed */\nfunction showTime() {\n if (!Bangle.isLCDOn()) return;\n if (animInterval) return; // in animation - quit\n var d = new Date();\n var t = (\" \"+d.getHours()).substr(-2)+\":\"+\n (\"0\"+d.getMinutes()).substr(-2);\n var l = lastTime;\n // same - don't animate\n if (t==l) {\n draw(t,l,0);\n return;\n }\n var n = 0;\n animInterval = setInterval(function() {\n n += 1/10;\n if (n>=1) {\n n=1;\n clearInterval(animInterval);\n animInterval=0;\n }\n draw(l,t,n);\n }, 20);\n lastTime = t;\n}\n\nBangle.on('lcdPower',function(on) {\n if (on)\n showTime();\n});\n\ng.clear();\nBangle.loadWidgets();\nBangle.drawWidgets();\n// Update time once a second\nsetInterval(showTime, 1000);\nshowTime();\n\n// Show launcher when middle button pressed\nsetWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:\"falling\"});\n"); +require('Storage').write("mclock.img",require("heatshrink").decompress(atob("mEwghC/AE8IxAAEwAWVDB4WIDBwWJAAIWPmf//8zDBpFDwYVBAAc4JJYWJDAoXKn4SC+EPAgXzC5JGCx4qDC4n//BIIEIRCEC4v/GBBdHC4xhCIw5dDC5BhCJAgXCRQoXGJAQXEUhAXHJAyNGC5KRCC7p2FC5B4CC5kggQXOBwvyBQMvSA4XL+EIwCoIC8ZHCgYXNO44LBBIiPPCAIwFC5DXGAAMwGAjvPGA4XIwYXHGALBDnAXFhCQHGAaOFwAXGPA4bFC4xIMIxIXDJBJGEC4xICSJCNEIwowEMJBdCFwwXEMJBdCC5BICDA4WDIw4wEAAMzCoMzBAgWIDAwAGCxRJEAAxFJDBgWNDBAWPAH4AYA=="))); +require('Storage').write("mclock.info","{\"id\":\"mclock\",\"name\":\"Morphing Clock\",\"type\":\"clock\",\"src\":\"mclock.app.js\",\"icon\":\"mclock.img\",\"sortorder\":-9,\"version\":\"0.02\",\"files\":\"mclock.info,mclock.app.js,mclock.img\"}"); +require('Storage').write("setting.app.js","Bangle.loadWidgets();\nBangle.drawWidgets();\n\nconst storage = require('Storage');\nlet settings;\n\nfunction updateSettings() {\n //storage.erase('setting.json'); // - not needed, just causes extra writes if settings were the same\n storage.write('setting.json', settings);\n}\n\nfunction resetSettings() {\n settings = {\n ble: true, // Bluetooth enabled by default\n blerepl: true, // Is REPL on Bluetooth - can Espruino IDE be used?\n log: false, // Do log messages appear on screen?\n timeout: 10, // Default LCD timeout in seconds\n vibrate: true, // Vibration enabled by default. App must support\n beep: true, // Beep enabled by default. App must support\n timezone: 0, // Set the timezone for the device\n HID : false, // BLE HID mode, off by default\n clock: null, // a string for the default clock's name\n \"12hour\" : false, // 12 or 24 hour clock?\n // welcomed : undefined/true (whether welcome app should show)\n };\n updateSettings();\n}\n\nsettings = storage.readJSON('setting.json',1);\nif (!settings) resetSettings();\n\nconst boolFormat = v => v ? \"On\" : \"Off\";\n\nfunction showMainMenu() {\n const mainmenu = {\n '': { 'title': 'Settings' },\n 'Make Connectable': makeConnectable,\n 'BLE': {\n value: settings.ble,\n format: boolFormat,\n onchange: () => {\n settings.ble = !settings.ble;\n updateSettings();\n }\n },\n 'Programmable': {\n value: settings.blerepl,\n format: boolFormat,\n onchange: () => {\n settings.blerepl = !settings.blerepl;\n updateSettings();\n }\n },\n 'Debug info': {\n value: settings.log,\n format: v => v ? \"Show\" : \"Hide\",\n onchange: () => {\n settings.log = !settings.log;\n updateSettings();\n }\n },\n 'LCD Timeout': {\n value: settings.timeout,\n min: 0,\n max: 60,\n step: 5,\n onchange: v => {\n settings.timeout = 0 | v;\n updateSettings();\n Bangle.setLCDTimeout(settings.timeout);\n }\n },\n 'Beep': {\n value: settings.beep,\n format: boolFormat,\n onchange: () => {\n settings.beep = !settings.beep;\n updateSettings();\n if (settings.beep) {\n Bangle.beep(1);\n }\n }\n },\n 'Vibration': {\n value: settings.vibrate,\n format: boolFormat,\n onchange: () => {\n settings.vibrate = !settings.vibrate;\n updateSettings();\n if (settings.vibrate) {\n VIBRATE.write(1);\n setTimeout(()=>VIBRATE.write(0), 10);\n }\n }\n },\n 'Welcome App': {\n value: !settings.welcomed,\n format: boolFormat,\n onchange: v => {\n settings.welcomed = v?undefined:true;\n updateSettings();\n }\n },\n 'Locale': showLocaleMenu,\n 'Select Clock': showClockMenu,\n 'HID': {\n value: settings.HID,\n format: boolFormat,\n onchange: () => {\n settings.HID = !settings.HID;\n updateSettings();\n }\n },\n 'Set Time': showSetTimeMenu,\n 'Reset Settings': showResetMenu,\n 'Turn Off': Bangle.off,\n '< Back': ()=> {load();}\n };\n return E.showMenu(mainmenu);\n}\n\nfunction showLocaleMenu() {\n const localemenu = {\n '': { 'title': 'Locale' },\n '< Back': showMainMenu,\n 'Time Zone': {\n value: settings.timezone,\n min: -11,\n max: 12,\n step: 0.5,\n onchange: v => {\n settings.timezone = v || 0;\n updateSettings();\n }\n },\n 'Clock Style': {\n value: !!settings[\"12hour\"],\n format : v => v?\"12hr\":\"24hr\",\n onchange: v => {\n settings[\"12hour\"] = v;\n updateSettings();\n }\n }\n };\n return E.showMenu(localemenu);\n}\n\nfunction showResetMenu() {\n const resetmenu = {\n '': { 'title': 'Reset' },\n '< Back': showMainMenu,\n 'Reset Settings': () => {\n E.showPrompt('Reset Settings?').then((v) => {\n if (v) {\n E.showMessage('Resetting');\n resetSettings();\n }\n setTimeout(showMainMenu, 50);\n });\n }\n };\n return E.showMenu(resetmenu);\n}\n\nfunction makeConnectable() {\n try { NRF.wake(); } catch(e) {}\n Bluetooth.setConsole(1);\n var name=\"Bangle.js \"+NRF.getAddress().substr(-5).replace(\":\",\"\");\n E.showPrompt(name+\"\\nStay Connectable?\",{title:\"Connectable\"}).then(r=>{\n if (settings.ble!=r) {\n settings.ble = r;\n updateSettings();\n }\n if (!r) try { NRF.sleep(); } catch(e) {}\n showMainMenu();\n });\n}\nfunction showClockMenu() {\n var clockApps = require(\"Storage\").list(/\\.info$/).map(app=>{\n try { return require(\"Storage\").readJSON(app); }\n catch (e) {}\n }).filter(app=>app.type==\"clock\").sort((a, b) => a.sortorder - b.sortorder);\n const clockMenu = {\n '': {\n 'title': 'Select Clock',\n },\n '< Back': showMainMenu,\n };\n clockApps.forEach((app,index) => {\n var label = app.name;\n if ((!settings.clock && index === 0) || (settings.clock === app.src)) {\n label = \"* \"+label;\n }\n clockMenu[label] = () => {\n if (settings.clock !== app.src) {\n settings.clock = app.src;\n updateSettings();\n showMainMenu();\n }\n };\n });\n if (clockApps.length === 0) {\n clockMenu[\"No Clocks Found\"] = () => {};\n }\n return E.showMenu(clockMenu);\n}\n\n\n\nfunction showSetTimeMenu() {\n d = new Date();\n const timemenu = {\n '': {\n 'title': 'Set Time',\n 'predraw': function() {\n d = new Date();\n timemenu.Hour.value = d.getHours();\n timemenu.Minute.value = d.getMinutes();\n timemenu.Second.value = d.getSeconds();\n timemenu.Date.value = d.getDate();\n timemenu.Month.value = d.getMonth() + 1;\n timemenu.Year.value = d.getFullYear();\n }\n },\n '< Back': showMainMenu,\n 'Hour': {\n value: d.getHours(),\n min: 0,\n max: 23,\n step: 1,\n onchange: v => {\n d = new Date();\n d.setHours(v);\n setTime(d.getTime()/1000);\n }\n },\n 'Minute': {\n value: d.getMinutes(),\n min: 0,\n max: 59,\n step: 1,\n onchange: v => {\n d = new Date();\n d.setMinutes(v);\n setTime(d.getTime()/1000);\n }\n },\n 'Second': {\n value: d.getSeconds(),\n min: 0,\n max: 59,\n step: 1,\n onchange: v => {\n d = new Date();\n d.setSeconds(v);\n setTime(d.getTime()/1000);\n }\n },\n 'Date': {\n value: d.getDate(),\n min: 1,\n max: 31,\n step: 1,\n onchange: v => {\n d = new Date();\n d.setDate(v);\n setTime(d.getTime()/1000);\n }\n },\n 'Month': {\n value: d.getMonth() + 1,\n min: 1,\n max: 12,\n step: 1,\n onchange: v => {\n d = new Date();\n d.setMonth(v - 1);\n setTime(d.getTime()/1000);\n }\n },\n 'Year': {\n value: d.getFullYear(),\n min: 2019,\n max: 2100,\n step: 1,\n onchange: v => {\n d = new Date();\n d.setFullYear(v);\n setTime(d.getTime()/1000);\n }\n }\n };\n return E.showMenu(timemenu);\n}\n\nshowMainMenu();\n"); +require('Storage').write("setting.json",{"ble":true,"blerepl":true,"log":false,"timeout":10,"vibrate":true,"beep":true,"timezone":0,"HID":false,"clock":null,"12hour":false}); +require('Storage').write("setting.img",require("heatshrink").decompress(atob("mEwghC/AFEiAAgX/C/4SFkADBgQXFBIgECAAYSCkAWGBIoXGyQTHABBZLkUhiMRiQXLIQwVBAAZlIC44tCAAYxGIxIWFGA4XIFwwwHXBAWHGAwXHFxAwGPAYXTX44XDiAJBgIXGyDAHFAYKDMAq+EGAgXNCwwX/C453XU6IWHa6ZFCC6JJCC4hgEAAoOEC5AwIFwhgEBAgwIBoqmGGBIuFVAgXFGAwLFYAoLFGIYtFeA4MGABMpC4pICkBMGBIpGFC4SuIBIoWFAAxZLC/4X/AFQ"))); +require('Storage').write("setting.info","{\"id\":\"setting\",\"name\":\"Settings\",\"src\":\"setting.app.js\",\"icon\":\"setting.img\",\"sortorder\":-2,\"version\":\"0.06\",\"files\":\"setting.info,setting.app.js,setting.json,setting.img\"}"); +require('Storage').write("about.app.js","var ENV = process.env;\nvar MEM = process.memory();\nvar s = require(\"Storage\");\n\ng.clear(1);\ng.setFont(\"6x8\");\nvar y = 24, h=8;\ng.drawImage(require(\"heatshrink\").decompress(atob(\"vE4gQZWg//AAI3Zh4dCoAd6wAd64Ad2j4d6l4dcn4dC6Adc+AdYv4dUggHG//kgN//AGB1WkDpkOAwsH/gDBgJ4CTRwdGl6RDl/0gHQgJeMDo2/AgcDIAIkBnAdRgJyCAAQdDlgdRgZPDgbWBDoUcDqMPRYcJgEfoA7Uh9AAgQ1BEgIdBngdRKQIACmBbB6AdB2gdRnoEDyB+C8tbbQVpgNAqOkAwMGyEQDoMB1AIBvgdDPYMC+H//7zBg//+fAA4OAgH//twDoMv/4WB3iyEAAPwHINvTYMAv/A/sC6BmBh/wDoP4gIuBdwayBAAP/DoMH4F4ToQSB+EPJQUOgKmDBgIABhAdFB4L7BgfAAYNwjpKChwJBTIQdDiAdFgHgAYIdDmDaCO4MD9Wq14dM+CdCDoU0nDjChyhBAAIdFsgdTZgaVDmPYLJk0LIodDaIcxcILRDSo80jiVECgUAvgDCmG0YQTRHDoTRBgLRCMwJDBnodDeAMDKoUvAIU/DocD6ELDoKRCAIM/LIcGG4PQUIKCBU4PzDoaEB/p3BFQKKCh9ADoXsKIVVqonCtVBoFQcAUKyFwghdB3IPBCwJZCAQMfEgQAL2AGFgZJBDoZgDABEMWYQJFgLwCkACB/gdLWYMCfoQAE35BEDpkH8EfdgYADl4mDl68BABazBFBA2CgK8CABcBUZP/8kBv58CAC1//4ABUQwASn4dgOxoALl4dC4AdYj4d6h4d+wAd6oAd2g4dCAwQA=\")),120,y);\ng.drawString(\"BANGLEJS.COM\",120,y-4);\ng.drawString(\"Powered by Espruino\",0,y+=4+h);\ng.drawString(\"Version \"+ENV.VERSION,0,y+=h);\ng.drawString(\"Commit \"+ENV.GIT_COMMIT,0,y+=h);\nfunction getVersion(name,file) {\n var j = s.readJSON(file,1);\n var v = (\"object\"==typeof j)?j.version:false;\n g.drawString(v?(name+\" \"+(v?\"v\"+v:\"Unknown\")):\"NO \"+name,0,y+=h);\n}\ngetVersion(\"Bootloader\",\"boot.info\");\ngetVersion(\"Launcher\",\"launch.info\");\ngetVersion(\"Settings\",\"setting.info\");\n\ny+=h;\ng.drawString(MEM.total+\" JS Variables available\",0,y+=h);\ng.drawString(\"Storage: \"+(require(\"Storage\").getFree()>>10)+\"k free\",0,y+=h);\nif (ENV.STORAGE) g.drawString(\" \"+(ENV.STORAGE>>10)+\"k total\",0,y+=h);\nif (ENV.SPIFLASH) g.drawString(\"SPI Flash: \"+(ENV.SPIFLASH>>10)+\"k\",0,y+=h);\ng.setFontAlign(0,-1);\ng.drawString(NRF.getAddress(),120,232);\ng.flip();\n\n// Pixel chooser image\ng.drawImage(require(\"heatshrink\").decompress(atob(\"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\")),0,135);\ng.flip();\n"); +require('Storage').write("about.img",require("heatshrink").decompress(atob("mEwxH+AH4A/AH4AQqoAHFtovlFxQzOiEQF0QwJFwIwSFyIwIF6YuTGBQule7IvuEp150d5GBS+DSBwtO5wABGA4vUFxvIFwXO44wJF7hcEAAejYJQvYFpAwJF7ejRQgAHF7BcH44tLF47xGF6QtNF8l5vIqFA4gv/R/4vZABwv25ovudYwAHvIvfp+dFxlPFy4wHp9PvPHFo/HFwIvEFqYxHEINP43G4/H5vNAYIHBBgQuaGAgvEAA4vEFzIxDq0zh5YCAAvHh8zqwud/1lssPh+AF4+ABYIPBFroABnUPnPNFwvNnMPnQRDFzgvCh/OdgKMC5vOBIIvEGC4bESAeB5wAErqODGDIbGMAekFwekLw4wWDY9liAoBrpdEiASIFzdloIpBAAkQoITJF7aSERhQvUDhYATF/4v/F74A/AH4A5A="))); +require('Storage').write("about.info","{\"id\":\"about\",\"name\":\"About\",\"src\":\"about.app.js\",\"icon\":\"about.img\",\"version\":\"0.04\",\"files\":\"about.info,about.app.js,about.img\"}"); +require('Storage').write("alarm.app.js","Bangle.loadWidgets();\nBangle.drawWidgets();\n\nvar alarms = require(\"Storage\").readJSON(\"alarm.json\",1)||[];\n/*alarms = [\n { on : true,\n hr : 6.5, // hours + minutes/60\n msg : \"Eat chocolate\",\n last : 0, // last day of the month we alarmed on - so we don't alarm twice in one day!\n rp : true, // repeat\n }\n];*/\n\nfunction formatTime(t) {\n var hrs = 0|t;\n var mins = Math.round((t-hrs)*60);\n return hrs+\":\"+(\"0\"+mins).substr(-2);\n}\n\nfunction getCurrentHr() {\n var time = new Date();\n return time.getHours()+(time.getMinutes()/60)+(time.getSeconds()/3600);\n}\n\nfunction showMainMenu() {\n const menu = {\n '': { 'title': 'Alarms' },\n 'New Alarm': ()=>editAlarm(-1)\n };\n alarms.forEach((alarm,idx)=>{\n txt = (alarm.on?\"on \":\"off \")+formatTime(alarm.hr);\n if (alarm.rp) txt += \" (repeat)\";\n menu[txt] = function() {\n editAlarm(idx);\n };\n });\n menu['< Back'] = ()=>{load();};\n return E.showMenu(menu);\n}\n\nfunction editAlarm(alarmIndex) {\n var newAlarm = alarmIndex<0;\n var hrs = 12;\n var mins = 0;\n var en = true;\n var repeat = true;\n if (!newAlarm) {\n var a = alarms[alarmIndex];\n hrs = 0|a.hr;\n mins = Math.round((a.hr-hrs)*60);\n en = a.on;\n repeat = a.rp;\n }\n const menu = {\n '': { 'title': 'Alarms' },\n 'Hours': {\n value: hrs,\n onchange: function(v){if (v<0)v=23;if (v>23)v=0;hrs=v;this.value=v;} // no arrow fn -> preserve 'this'\n },\n 'Minutes': {\n value: mins,\n onchange: function(v){if (v<0)v=59;if (v>59)v=0;mins=v;this.value=v;} // no arrow fn -> preserve 'this'\n },\n 'Enabled': {\n value: en,\n format: v=>v?\"On\":\"Off\",\n onchange: v=>en=v\n },\n 'Repeat': {\n value: en,\n format: v=>v?\"Yes\":\"No\",\n onchange: v=>repeat=v\n }\n };\n function getAlarm() {\n var hr = hrs+(mins/60);\n var day = 0;\n // If alarm is for tomorrow not today (eg, in the past), set day\n if (hr < getCurrentHr())\n day = (new Date()).getDate();\n // Save alarm\n return {\n on : en, hr : hr,\n last : day, rp : repeat\n };\n }\n if (newAlarm) {\n menu[\"> New Alarm\"] = function() {\n alarms.push(getAlarm());\n require(\"Storage\").write(\"alarm.json\",JSON.stringify(alarms));\n showMainMenu();\n };\n } else {\n menu[\"> Save\"] = function() {\n alarms[alarmIndex] = getAlarm();\n require(\"Storage\").write(\"alarm.json\",JSON.stringify(alarms));\n showMainMenu();\n };\n }\n menu['< Back'] = showMainMenu;\n return E.showMenu(menu);\n}\n\nshowMainMenu();\n"); +require('Storage').write("alarm.js","// Chances are boot0.js got run already and scheduled *another*\n// 'load(alarm.js)' - so let's remove it first!\nclearInterval();\n\nfunction formatTime(t) {\n var hrs = 0|t;\n var mins = Math.round((t-hrs)*60);\n return hrs+\":\"+(\"0\"+mins).substr(-2);\n}\n\nfunction getCurrentHr() {\n var time = new Date();\n return time.getHours()+(time.getMinutes()/60)+(time.getSeconds()/3600);\n}\n\nfunction showAlarm(alarm) {\n var msg = formatTime(alarm.hr);\n var buzzCount = 10;\n if (alarm.msg)\n msg += \"\\n\"+alarm.msg;\n E.showPrompt(msg,{\n title:\"ALARM!\",\n buttons : {\"Sleep\":true,\"Ok\":false} // default is sleep so it'll come back in 10 mins\n }).then(function(sleep) {\n buzzCount = 0;\n if (sleep) {\n alarm.hr += 10/60; // 10 minutes\n } else {\n alarm.last = (new Date()).getDate();\n if (!alarm.rp) alarm.on = false;\n }\n require(\"Storage\").write(\"alarm.json\",JSON.stringify(alarms));\n load();\n });\n function buzz() {\n Bangle.buzz(100).then(()=>{\n setTimeout(()=>{\n Bangle.buzz(100).then(function() {\n if (buzzCount--)\n setTimeout(buzz, 3000);\n });\n },100);\n });\n }\n buzz();\n}\n\n// Check for alarms\nvar day = (new Date()).getDate();\nvar hr = getCurrentHr()+10000; // get current time - 10s in future to ensure we alarm if we've started the app a tad early\nvar alarms = require(\"Storage\").readJSON(\"alarm.json\",1)||[];\nvar active = alarms.filter(a=>a.on&&(a.hra.hr-b.hr);\n showAlarm(active[0]);\n} else {\n // otherwise just go back to default app\n setTimeout(load, 100);\n}\n"); +require('Storage').write("alarm.json","[]"); +require('Storage').write("alarm.img",require("heatshrink").decompress(atob("mEwwkGswAhiMRCCAREAo4eHBIQLEAgwYHsIJDiwHB5gACBpIhHCoYZEGA4gFCw4ABGA4HEjgXJ4IXGAwcUB4VEmf//8zogICoJIFAodMBoNDCoIADmgJB4gXIFwXDCwoABngwFC4guB4k/CQXwh4EC+YMCC44iBp4qDC4n/+gNBC41sEIJCEC4v/GAPGC4dhXYRdFC4xhCCYIXCdQRdDC5HzegQXCsxGHC45IDCwQXCUgwXHJAIXGRogXJSIIXcOw4XIPAYXcBwv/mEDBAwXOgtQC65QGC5vzoEAJAx3Nmk/mEABIiPN+dDAQIwFC4zXGFwKRCGAjvMFwQECGAgXI4YuGGAUvAgU8C4/EFwwGCAgdMC4p4EFwobFOwoXDJAIoEAApGBC4xIEABJGHGAapEAAqNBFwwXD4heI+YuBC5BIBVQhdHIw4wD5inFS4IKCCxFmigNCokzCoMzogICoIWIsMRjgPCAA3BiMWC48RBQIXJEgMRFxAJCCw4lEC44IECooOIBAaBJKwhgIAH4ACA=="))); +require('Storage').write("alarm.wid.js","(() => {\n var alarms = require('Storage').readJSON('alarm.json',1)||[];\n alarms = alarms.filter(alarm=>alarm.on);\n if (!alarms.length) return; // no alarms, no widget!\n delete alarms;\n // add the widget\n WIDGETS[\"alarm\"]={area:\"tl\",width:24,draw:function() {\n g.setColor(-1);\n g.drawImage(atob(\"GBgBAAAAAAAAABgADhhwDDwwGP8YGf+YMf+MM//MM//MA//AA//AA//AA//AA//AA//AB//gD//wD//wAAAAADwAABgAAAAAAAAA\"),this.x,this.y);\n }};\n})()\n"); +require('Storage').write("alarm.info","{\"id\":\"alarm\",\"name\":\"Alarms\",\"src\":\"alarm.app.js\",\"icon\":\"alarm.img\",\"version\":\"0.04\",\"files\":\"alarm.info,alarm.app.js,alarm.js,alarm.json,alarm.img,alarm.wid.js\"}"); +require('Storage').write("widbat.wid.js","(function(){\nvar CHARGING = 0x07E0;\n\nfunction setWidth() {\n WIDGETS[\"bat\"].width = 40 + (Bangle.isCharging()?16:0);\n}\nfunction draw() {\n var s = 39;\n var x = this.x, y = this.y;\n if (Bangle.isCharging()) {\n g.setColor(CHARGING).drawImage(atob(\"DhgBHOBzgc4HOP////////////////////3/4HgB4AeAHgB4AeAHgB4AeAHg\"),x,y);\n x+=16;\n }\n g.setColor(-1);\n g.fillRect(x,y+2,x+s-4,y+21);\n g.clearRect(x+2,y+4,x+s-6,y+19);\n g.fillRect(x+s-3,y+10,x+s,y+14);\n g.setColor(CHARGING).fillRect(x+4,y+6,x+4+E.getBattery()*(s-12)/100,y+17);\n g.setColor(-1);\n}\nBangle.on('charging',function(charging) {\n if(charging) Bangle.buzz();\n setWidth();\n Bangle.drawWidgets(); // relayout widgets\n g.flip();\n});\nvar batteryInterval;\nBangle.on('lcdPower', function(on) {\n if (on) {\n WIDGETS[\"bat\"].draw();\n // refresh once a minute if LCD on\n if (!batteryInterval)\n batteryInterval = setInterval(draw, 60000);\n } else {\n if (batteryInterval) {\n clearInterval(batteryInterval);\n batteryInterval = undefined;\n }\n }\n});\nWIDGETS[\"bat\"]={area:\"tr\",width:40,draw:draw};\nsetWidth();\n})()\n"); +require('Storage').write("widbat.info","{\"id\":\"widbat\",\"name\":\"Battery Level Widget\",\"type\":\"widget\",\"version\":\"0.04\",\"files\":\"widbat.info,widbat.wid.js\"}"); +require('Storage').write("widbt.wid.js","(function(){\nvar img_bt = E.toArrayBuffer(atob(\"CxQBBgDgFgJgR4jZMawfAcA4D4NYybEYIwTAsBwDAA==\"));\n\nfunction draw() {\n g.reset();\n if (NRF.getSecurityStatus().connected)\n g.setColor(0,0.5,1);\n else\n g.setColor(0.3,0.3,0.3);\n g.drawImage(img_bt,10+this.x,2+this.y);\n}\nfunction changed() {\n WIDGETS[\"bluetooth\"].draw();\n g.flip();// turns screen on\n}\nNRF.on('connected',changed);\nNRF.on('disconnected',changed);\nWIDGETS[\"bluetooth\"]={area:\"tr\",width:24,draw:draw};\n})()\n"); +require('Storage').write("widbt.info","{\"id\":\"widbt\",\"name\":\"Bluetooth Widget\",\"type\":\"widget\",\"version\":\"0.03\",\"files\":\"widbt.info,widbt.wid.js\"}"); +require('Storage').write("welcome.js","eval(require(\"Storage\").read(\"welcome.app.js\"))\n"); +require('Storage').write("welcome.app.js","// exec each function from seq one after the other\nfunction animate(seq,period) {\n var i = setInterval(function() {\n if (seq.length) {\n var f = seq.shift();\n if (f) f();\n } else clearInterval(i);\n },period);\n}\n\n// Fade in to FG color with angled lines\nfunction fade(callback) {\n var n = 0;\n function f() {\n for (var i=n;i<240;i+=10) {\n g.drawLine(i,0,0,i);\n g.drawLine(i,240,240,i);\n }\n g.flip();\n n++;\n if (n<10) setTimeout(f,0);\n else callback();\n }\n f();\n}\n\n\nvar scenes = [\n function() {\n g.clear(1);\n g.setFont(\"4x6\",2);\n var n=0;\n var i = setInterval(function() {\n n+=0.04;\n g.setColor(n,n,n);\n g.drawImage(Bangle.getLogo(),(240-222)/2,(240-100)/2);\n if (n>=1) {\n clearInterval(i);\n setTimeout(()=>g.drawString(\"Open\",34,144), 500);\n setTimeout(()=>g.drawString(\"Hackable\",34,156), 1000);\n setTimeout(()=>g.drawString(\"Smart Watch\",34,168), 1500);\n }\n },50);\n },function() {\n var img = require(\"heatshrink\").decompress(atob(\"ptRxH+qYAfvl70mj5gAC0ekvd8FkAAdz3HJAYAH4+eJXWkJJYAF0hK2vfNJaIAB5t7S3fN5/V6wAD6vOTg9SumXy2W3QAB3eXul2JdnO63XAApPEVYvAJQIACJoRQDzBLoJQ3W5/NIwr4GJohMFAAROgJYvVJQiPGABZNN3bsdvYyESwnWJSIAC3RNM3V1JjZAES4nVJSYAB4xMNJrbkE56WD5xLVdB5NbFofNJbgABJh26qREPrFXrlbAAWjFgfWJgRLaTQhMLy5KNJINhsJLDrYrD5xLC6pLa5nGTR7oLq9bJQJMKTAXWJbbnR3RLJSoRMHv4pC5rkec6SaIrBLGw2r2XW1epcoqYeJiOXJYziEsOH2RBBw7lF56Yg5nGc6FScZOGJQPX2TmDFIfVTEBMSc4hLEw5KB6+rsJMH63X6pMf5hMQzBLCq5LD1ZLEJhTlfJiWXTA2GJYpMIcwPNc2O6TAuGRIPX1igDJg/PJmyYDcgXWwxMH1ApC53XcsHAJiVYcg2HJYZME0YpC5vWJkhLNJgLlDTAeFJhF/FQfVJkG6JiGXcomyJgOrJYhMErYqD53NJj7lRzBMDcoeGJhzoBJb3GJiN1qZBCJgWyJYpNF1LigAAXAJiNSJgzlGJgt/JkZLRy9TJgeHJhznFcuSZGw5MHJomjcuhLBqdcJiSaiTChMV1CYxy5LCqdXIAWy6+rJhCalTCN2JgdYH4WHJiGpTF7kDc43W2RMJTUZLQzBLFc4mr6+GJh2jTFmXJYyaEwuyc5Sag4xLZTQmG2WFJhxNaJYZMLJZSaEJoOHTR9/Ja+6JbdTqRNETRRNF1JLV4BLcAANYI5ToK1BLYJhWYJZwABq5NoJZ91JaAABdAZNS0ZLey9SJaRNYv5KM426JZmXuxKUJrKcL0lTzBLKzBKYJrVXvfGSol7EYWXJI27zF1JLQADq5NUrgYB4wAEEIV0comXI7wAFrCcPJgYWBTIIAETIN2JYmWuhMkdSdYCgOeJgueqRLFyzhfTi9bq4TC45MF49TuuXJlpONcogAC0hKB0gHDvZMEqRMpAANSq9crlbJAYADqwRDxGk0mIA4eCTQOeveXJdYAHqxNFdAeIAAQGCrOI0oHEAGVXTRJMGvgGCwRM7TAZMHwQGCvhM1rBMERIhMGAwdZJmtSqVTwNcwJEDJg19cvIADa4d9JhANDJnSLHJgrl6AAhFFAwpZDegjn7vhMGcvwABrJAFJgjl/TQpBBI4jl/AAN8TQhHDcv4ADcJBMDvpM+IYaeDAAhL+qd9SgycEJn7iEAA18Jf7nEcv4AIrJLIcv6aMcv4ADvhMHrJJ/AAbl/c6ZM/AAt9cv7nSIv7nLcv4AHrLl/TRpJBvgnjA==\"));\n g.reset();\n g.setColor(\"#6633ff\");\n g.setBgColor(\"#6633ff\");\n var y = 240, speed = 5;\n function balloon(callback) {\n y-=speed;\n var x = (240-77)/2;\n g.drawImage(img,x,y);\n g.clearRect(x,y+81,x+77,y+81+speed);\n if (y>60) setTimeout(balloon,0,callback);\n else callback();\n }\n fade(function() {\n balloon(function() {\n g.setColor(-1);\n g.setFont(\"6x8\",3);\n g.setFontAlign(0,0);\n g.drawString(\"Welcome.\",120,160);\n });\n });\n setTimeout(function() {\n var n=0;\n var i = setInterval(function() {\n n+=5;\n g.scroll(0,-5);\n if (n>170)\n clearInterval(i);\n },20);\n },3500);\n\n },function() {\n g.reset();\n g.setBgColor(\"#ffa800\");g.clear();\n g.setFont(\"6x8\",2);\n g.setFontAlign(0,0);\n var x = 80, y = 35, h=35;\n animate([\n ()=>g.drawString(\"Your\",x,y+=h),\n ()=>g.drawString(\"Bangle.js\",x,y+=h),\n ()=>g.drawString(\"has\",x,y+=h),\n ()=>g.drawString(\"3 buttons\",x,y+=h),\n ()=>{g.setFont(\"Vector\",36);g.drawString(\"1\",200,40);},\n ()=>g.drawString(\"2\",200,120),\n ()=>g.drawString(\"3\",200,200)\n ],200);\n },\n function() {\n g.reset();\n g.setBgColor(\"#00a8ff\");g.clear();\n g.setFontAlign(0,0);\n g.setFont(\"Vector\",48);\n g.drawString(\"1\",200,40);\n g.setFontAlign(-1,-1);\n g.setFont(\"6x8\",2);\n g.drawString(\"Move up\\nin menus\\n\\nTurn Bangle.js on\\nif it was off\", 20,40);\n },\n function() {\n g.reset();\n g.setBgColor(\"#00a8ff\");g.clear();\n g.setFontAlign(0,0);\n g.setFont(\"Vector\",48);\n g.drawString(\"2\",200,120);\n g.setFontAlign(-1,-1);\n g.setFont(\"6x8\",2);\n g.drawString(\"Select menu\\nitem\\n\\nLaunch app\\nwhen watch\\nis showing\", 20,70);\n },\n function() {\n g.reset();\n g.setBgColor(\"#00a8ff\");g.clear();\n g.setFontAlign(0,0);\n g.setFont(\"Vector\",48);\n g.drawString(\"3\",200,200);\n g.setFontAlign(-1,-1);\n g.setFont(\"6x8\",2);\n g.drawString(\"Move down\\nin menus\\n\\nLong press\\nto exit app\\nand go back\\nto clock\", 20,100);\n },\n function() {\n g.reset();\n g.setBgColor(\"#ff3300\");g.clear();\n g.setFontAlign(0,0);\n g.setFont(\"Vector\",48);\n g.drawString(\"1\",200,40);\n g.drawString(\"2\",200,120);\n g.setFontAlign(-1,-1);\n g.setFont(\"6x8\",2);\n g.drawString(\"If Bangle.js\\never stops,\\nhold buttons\\n1 and 2 for\\naround six\\nseconds.\\n\\n\\n\\nBangle.js will\\nthen reboot.\", 20,20);\n },\n function() {\n g.reset();\n g.setBgColor(\"#00a8ff\");g.clear();\n g.setFont(\"6x8\",2);\n g.setFontAlign(0,0);\n var x = 120, y = 10, h=21;\n animate([\n ()=>{g.drawString(\"Bangle.js has a\",x,y+=h);\n g.drawString(\"simple touchscreen\",x,y+=h);},\n 0,0,\n ()=>{g.drawString(\"It'll detect touch\",x,y+=h*2);\n g.drawString(\"on left and right\",x,y+=h);},\n 0,0,\n ()=>{g.drawString(\"Horizontal swipes\",x,y+=h*2);\n g.drawString(\"work too. Try now\",x,y+=h);\n g.drawString(\"to change page.\",x,y+=h);}\n ],300);\n },\n function() {\n g.reset();\n g.setBgColor(\"#339900\");g.clear();\n g.setFont(\"6x8\",2);\n g.setFontAlign(0,0);\n var x = 120, y = 10, h=21;\n animate([\n ()=>{g.drawString(\"Bangle.js\",x,y+=h);\n g.drawString(\"comes with\",x,y+=h);\n g.drawString(\"a few simple\",x,y+=h);\n g.drawString(\"apps installed\",x,y+=h);},\n 0,0,\n ()=>{g.drawString(\"To add more, visit\",x,y+=h*2);\n g.drawString(\"banglejs.com/apps\",x,y+=h);\n g.drawString(\"with a Bluetooth\",x,y+=h);\n g.drawString(\"capable device\",x,y+=h);},\n ],400);\n },\n function() {\n g.reset();\n g.setBgColor(\"#990066\");g.clear();\n g.setFont(\"6x8\",2);\n g.setFontAlign(0,0);\n var x = 120, y = 10, h=21;\n g.drawString(\"You can also make\",x,y+=h);\n g.drawString(\"your own apps!\",x,y+=h);\n y=160;\n g.drawString(\"Check out\",x,y+=h);\n g.drawString(\"banglejs.com\",x,y+=h);\n\n var rx = 0, ry = 0;\n var h = Graphics.createArrayBuffer(96,96,1,{msb:true});\n // draw a cube\n function draw() {\n // rotate\n rx += 0.1;\n ry += 0.11;\n var rcx=Math.cos(rx),\n rsx=Math.sin(rx),\n rcy=Math.cos(ry),\n rsy=Math.sin(ry);\n // Project 3D coordinates into 2D\n function p(x,y,z) {\n var t;\n t = x*rcy + z*rsy;\n z = z*rcy - x*rsy;\n x=t;\n t = y*rcx + z*rsx;\n z = z*rcx - y*rsx;\n y=t;\n z += 4;\n return [96*(0.5+x/z), 96*(0.5+y/z)];\n }\n\n var a;\n // draw a series of lines to make up our cube\n h.clear();\n a = p(-1,-1,-1); h.moveTo(a[0],a[1]);\n a = p(1,-1,-1); h.lineTo(a[0],a[1]);\n a = p(1,1,-1); h.lineTo(a[0],a[1]);\n a = p(-1,1,-1); h.lineTo(a[0],a[1]);\n a = p(-1,-1,-1); h.lineTo(a[0],a[1]);\n a = p(-1,-1,1); h.moveTo(a[0],a[1]);\n a = p(1,-1,1); h.lineTo(a[0],a[1]);\n a = p(1,1,1); h.lineTo(a[0],a[1]);\n a = p(-1,1,1); h.lineTo(a[0],a[1]);\n a = p(-1,-1,1); h.lineTo(a[0],a[1]);\n a = p(-1,-1,-1); h.moveTo(a[0],a[1]);\n a = p(-1,-1,1); h.lineTo(a[0],a[1]);\n a = p(1,-1,-1); h.moveTo(a[0],a[1]);\n a = p(1,-1,1); h.lineTo(a[0],a[1]);\n a = p(1,1,-1); h.moveTo(a[0],a[1]);\n a = p(1,1,1); h.lineTo(a[0],a[1]);\n a = p(-1,1,-1); h.moveTo(a[0],a[1]);\n a = p(-1,1,1); h.lineTo(a[0],a[1]);\n g.drawImage({width:96,height:96,buffer:h.buffer},(240-96)/2,68);\n }\n\n setInterval(draw,50);\n },\n function() {\n g.reset();\n g.setBgColor(\"#660099\");g.clear();\n g.setFontAlign(0,0);\n g.setFont(\"Vector\",36);\n g.drawString(\"2\",200,120);\n g.setFont(\"6x8\",2);\n\n var x = 90, y = 30, h=21;\n animate([\n ()=>g.drawString(\"That's it!\",x,y+=h),\n ()=>{g.drawString(\"Press\",x,y+=h*3);\n g.drawString(\"Button 2\",x,y+=h);\n g.drawString(\"to start\",x,y+=h);\n g.drawString(\"Bangle.js\",x,y+=h);}\n ],400);\n }\n];\n\nvar sceneNumber = 0;\n\nfunction move(dir) {\n if (dir>0 && sceneNumber+1 == scenes.length) return; // at the end\n sceneNumber = (sceneNumber+dir)%scenes.length;\n if (sceneNumber<0) sceneNumber=0;\n clearInterval();\n scenes[sceneNumber]();\n if (sceneNumber>1) {\n var l = scenes.length;\n for (var i=0;imove(1), BTN3, {repeat:true});\nsetWatch(()=>{\n // If we're on the last page\n if (sceneNumber == scenes.length-1) {\n var settings = require(\"Storage\").readJSON('setting.json',1)||{};\n settings.welcomed = true;\n require(\"Storage\").write('setting.json',settings);\n load();\n }\n}, BTN2, {repeat:true,edge:\"rising\"});\nsetWatch(()=>move(-1), BTN1, {repeat:true});\n\n\n\nBangle.setLCDTimeout(0);\nBangle.setLCDPower(1);\nmove(0);\n"); +require('Storage').write("welcome.img",require("heatshrink").decompress(atob("mEwxH+AH4A/AH4AU5gAEFtoxnEwXN53WAAXO5oJB42Wy26AAIueFoPXFggAD4AwEGTQiB6otBFgwAD3QvFGC5dCFxiRGGClhrdbv67BXAIuLMBIwPsIABF4OpLwXOFxjBCF6gtBw2r1mHXoXWFxqQWFwOH62rL4IeB6xeOAAIvHGBYuC6+rR4QvCXpovXw3X1i/DR4QuPR5AvKFQOs6+GF4eod4IvPd5AvLwvWLwQvCv4fBR54vURwOHF4iQCX0yOCF4aQBX0QvHSAoAN3SOSd4WyF4yQPLyhgD1YvDMCJeIFxhgCF47BN4BeHFxpgDSAiRORpAuPMIYAFGBYuaF5aSHFwQvEFqQwOeggSBLa4xNF4X+4wAC/xeCFjIADrYwGBIIvlMQiPDBAOk0gDBz2XF8BlEF4eIxADFF8lcF9n+wIrFF05bHF9AsGF9wupGAYv/F8QupGAov/F/4wOF1gA/AH4Ap"))); +require('Storage').write("welcome.info","{\"id\":\"welcome\",\"name\":\"Welcome\",\"src\":\"welcome.app.js\",\"icon\":\"welcome.img\",\"version\":\"0.04\",\"files\":\"welcome.info,welcome.js,welcome.app.js,welcome.img\"}"); diff --git a/img/github-icon-sml.png b/img/github-icon-sml.png new file mode 100644 index 000000000..81ca8f22e Binary files /dev/null and b/img/github-icon-sml.png differ diff --git a/img/github-icon.png b/img/github-icon.png new file mode 100644 index 000000000..aadd0fab9 Binary files /dev/null and b/img/github-icon.png differ diff --git a/index.html b/index.html index eeac78468..f18c05352 100644 --- a/index.html +++ b/index.html @@ -29,13 +29,19 @@ } .chip { cursor: pointer; + } + .tile-content { position: relative; } + .link-github { + position:absolute; + top: 36px; + left: -24px; }
-

App Loader is incompatible with 'old' Bangle.js firmwares - (more info) Please update to the latest firmware or - use the legacy apps. +

Note: If you have a version of Bangle.js firmware before 2v04, please update to the latest firmware or + use the legacy app loader.

@@ -104,15 +109,15 @@