forked from espruino/BangleApps
-
Notifications
You must be signed in to change notification settings - Fork 0
/
firmware.js
32 lines (32 loc) · 46.7 KB
/
firmware.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// Generated by BangleApps/bin/firmwaremaker.js
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.name<b.name) return -1;\n if (a.name>b.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 (selected<menuScroll) menuScroll = selected;\n if (menuScroll) g.fillPoly([120,0,100,20,140,20]);\n else g.clearRect(100,0,140,20);\n if (apps.length>n+menuScroll) g.fillPoly([120,239,100,219,140,219]);\n else g.clearRect(100,219,140,239);\n for (var i=0;i<n;i++) {\n var app = apps[i+menuScroll];\n if (!app) break;\n var y = 24+i*64;\n if (i+menuScroll==selected) {\n g.setColor(0.3,0.3,0.3);\n g.fillRect(0,y,239,y+63);\n g.setColor(1,1,1);\n g.drawRect(0,y,239,y+63);\n } else\n g.clearRect(0,y,239,y+63);\n g.drawString(app.name,64,y+32);\n var icon=undefined;\n if (app.icon) icon = s.read(app.icon);\n if (icon) try {g.drawImage(icon,8,y+8);} catch(e){}\n }\n}\ndrawMenu();\nsetWatch(function() {\n if (selected>0) {\n selected--;\n drawMenu();\n }\n}, BTN1, {repeat:true});\nsetWatch(function() {\n if (selected+1<apps.length) {\n selected++;\n drawMenu();\n }\n}, BTN3, {repeat:true});\nsetWatch(function() { // run\n if (!apps[selected].src) return;\n if (require(\"Storage\").read(apps[selected].src)===undefined) {\n E.showMessage(\"App Source\\nNot found\");\n setTimeout(drawMenu, 2000);\n } else {\n E.showMessage(\"Loading...\");\n load(apps[selected].src);\n }\n}, BTN2, {repeat:true,edge:\"falling\"});\n");
require('Storage').write("launch.info","{\"id\":\"launch\",\"name\":\"Launcher\",\"type\":\"launch\",\"src\":\"launch.app.js\",\"sortorder\":-10,\"version\":\"0.01\",\"files\":\"launch.info,launch.app.js\"}");
require('Storage').write("mclock.app.js","// 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<lastText.length;i++) {\n var lastCh = lastText[i];\n var thisCh = thisText[i];\n if (thisCh==\":\") x-=4;\n var ch, chn = n;\n if (lastCh!==undefined &&\n (thisCh-1==lastCh ||\n (thisCh==0 && lastCh==5) ||\n (thisCh==0 && lastCh==9)))\n ch = lastCh;\n else {\n ch = thisCh;\n chn = 0;\n }\n var l = DIGITS[ch](chn,lastCh==5 && thisCh==0);\n l.forEach(c=>{\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.hr<hr)&&(a.last!=day));\nif (active.length) {\n // if there's an alarm, show it\n active = active.sort((a,b)=>a.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;i<l-2;i++) {\n var x = 120+(i-(l-2)/2)*12;\n if (i<sceneNumber-1) {\n g.setColor(-1);\n g.fillCircle(x,230,4);\n } else {\n g.setColor(0);\n g.fillCircle(x,230,4);\n g.setColor(-1);\n g.drawCircle(x,230,4);\n }\n }\n }\n if (sceneNumber < scenes.length-1)\n setTimeout(function() {\n move(1);\n }, 5000);\n}\n\n\n\nBangle.on('swipe',move);\nsetWatch(()=>move(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\"}");