diff --git a/apps.json b/apps.json index 22e8e29c7..31f681fbb 100644 --- a/apps.json +++ b/apps.json @@ -4,7 +4,7 @@ "tags": "tool,system,b2", "type":"bootloader", "icon": "bootloader.png", - "version":"0.27", + "version":"0.28", "description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings", "storage": [ {"name":".boot0","url":"boot0.js"}, @@ -94,7 +94,7 @@ "name": "Notifications (default)", "shortName":"Notifications", "icon": "notify.png", - "version":"0.08", + "version":"0.09", "description": "A handler for displaying notifications that displays them in a bar at the top of the screen", "tags": "widget", "type": "notify", @@ -107,7 +107,7 @@ "name": "Fullscreen Notifications", "shortName":"Notifications", "icon": "notify.png", - "version":"0.08", + "version":"0.10", "description": "A handler for displaying notifications that displays them fullscreen. This may not fully restore the screen after on some apps. See `Notifications (default)` for more information about the notifications library.", "tags": "widget", "type": "notify", @@ -118,7 +118,7 @@ { "id": "welcome", "name": "Welcome", "icon": "app.png", - "version":"0.10", + "version":"0.11", "description": "Appears at first boot and explains how to use Bangle.js", "tags": "start,welcome", "allow_emulator":true, @@ -153,7 +153,7 @@ { "id": "gbridge", "name": "Gadgetbridge", "icon": "app.png", - "version":"0.22", + "version":"0.23", "description": "The default notification handler for Gadgetbridge notifications from Android", "tags": "tool,system,android,widget", "readme": "README.md", @@ -171,7 +171,7 @@ { "id": "mclock", "name": "Morphing Clock", "icon": "clock-morphing.png", - "version":"0.06", + "version":"0.07", "description": "7 segment clock that morphs between minutes and hours", "tags": "clock", "type":"clock", @@ -185,7 +185,7 @@ { "id": "setting", "name": "Settings", "icon": "settings.png", - "version":"0.27", + "version":"0.28", "description": "A menu for setting up Bangle.js", "tags": "tool,system,b2", "readme": "README.md", @@ -258,7 +258,7 @@ { "id": "slidingtext", "name": "Sliding Clock", "icon": "slidingtext.png", - "version":"0.05", + "version":"0.06", "description": "Inspired by the Pebble sliding clock, old times are scrolled off the screen and new times on. You are also able to change language on the fly so you can see the time written in other languages using button 1. Currently English, French, Japanese, Spanish and German are supported", "tags": "clock", "type":"clock", @@ -278,6 +278,33 @@ {"name":"slidingtext.dtfmt.js","url":"slidingtext.dtfmt.js"} ] }, + { "id": "solarclock", + "name": "Solar Clock", + "icon": "solar_clock.png", + "version":"0.02", + "description": "Using your current or chosen location the solar watch face shows the Sun's sky position, time and date. Also allows you to wind backwards and forwards in time to see the sun's position", + "tags": "clock", + "type":"clock", + "allow_emulator":false, + "readme": "README.md", + "custom":"custom.html", + "storage": [ + {"name":"solarclock.app.js","url":"solar_clock.js"}, + {"name":"solarclock.img","url":"solar_clock-icon.js","evaluate":true}, + {"name":"solar_colors.js","url":"solar_colors.js"}, + {"name":"solar_controller.js","url":"solar_controller.js"}, + {"name":"solar_date_utils.js","url":"solar_date_utils.js"}, + {"name":"solar_graphic_utils.js","url":"solar_graphic_utils.js"}, + {"name":"solar_location.js","url":"solar_location.js"}, + {"name":"solar_math_utils.js","url":"solar_math_utils.js"}, + {"name":"solar_loc.Reykjavik.json","url":"solar_loc.Reykjavik.json"}, + {"name":"solar_loc.Hong_Kong.json","url":"solar_loc.Hong_Kong.json"}, + {"name":"solar_loc.Honolulu.json","url":"solar_loc.Honolulu.json"}, + {"name":"solar_loc.Rio.json","url":"solar_loc.Rio.json"}, + {"name":"solar_loc.Tokyo.json","url":"solar_loc.Tokyo.json"}, + {"name":"solar_loc.Seoul.json","url":"solar_loc.Seoul.json"} + ] + }, { "id": "sweepclock", "name": "Sweep Clock", "icon": "sweepclock.png", @@ -296,7 +323,7 @@ "name": "Image background clock", "shortName":"Image Clock", "icon": "app.png", - "version":"0.07", + "version":"0.08", "description": "A clock with an image as a background", "tags": "clock", "type" : "clock", @@ -312,7 +339,7 @@ { "id": "impwclock", "name": "Imprecise Word Clock", "icon": "clock-impword.png", - "version":"0.02", + "version":"0.03", "description": "Imprecise word clock for vacations, weekends, and those who never need accurate time.", "tags": "clock", "type":"clock", @@ -470,7 +497,7 @@ { "id": "gpsrec", "name": "GPS Recorder", "icon": "app.png", - "version":"0.22", + "version":"0.23", "interface": "interface.html", "description": "Application that allows you to record a GPS track. Can run in background", "tags": "tool,outdoors,gps,widget", @@ -533,7 +560,7 @@ { "id": "files", "name": "App Manager", "icon": "files.png", - "version":"0.06", + "version":"0.07", "description": "Show currently installed apps, free space, and allow their deletion from the watch", "tags": "tool,system,files", "storage": [ @@ -683,7 +710,7 @@ { "id": "hrm", "name": "Heart Rate Monitor", "icon": "heartrate.png", - "version":"0.04", + "version":"0.05", "description": "Measure your heart rate and see live sensor data", "tags": "health", "storage": [ @@ -865,7 +892,7 @@ { "id": "sclock", "name": "Simple Clock", "icon": "clock-simple.png", - "version":"0.05", + "version":"0.06", "description": "A Simple Digital Clock", "tags": "clock,b2", "type":"clock", @@ -904,7 +931,7 @@ { "id": "svclock", "name": "Simple V-Clock", "icon": "vclock-simple.png", - "version":"0.01", + "version":"0.02", "description": "Modification of Simple Clock 0.04 to use Vectorfont", "tags": "clock", "type":"clock", @@ -1030,7 +1057,7 @@ { "id": "miclock", "name": "Mixed Clock", "icon": "clock-mixed.png", - "version":"0.04", + "version":"0.05", "description": "A mix of analog and digital Clock", "tags": "clock", "type":"clock", @@ -1132,7 +1159,7 @@ { "id": "boldclk", "name": "Bold Clock", "icon": "bold_clock.png", - "version":"0.04", + "version":"0.05", "description": "Simple, readable and practical clock", "tags": "clock,b2", "type":"clock", @@ -1244,6 +1271,19 @@ {"name":"torch.img","url":"app-icon.js","evaluate":true} ] }, + { "id": "rtorch", + "name": "Red Torch", + "shortName":"RedTorch", + "icon": "app.png", + "version":"0.01", + "description": "Turns screen RED to help you see in the dark without breaking your night vision. Select from the launcher or press BTN3,BTN1,BTN3,BTN1 quickly to start when in any app that shows widgets", + "tags": "tool,torch", + "storage": [ + {"name":"rtorch.app.js","url":"app.js"}, + {"name":"rtorch.wid.js","url":"widget.js"}, + {"name":"rtorch.img","url":"app-icon.js","evaluate":true} + ] + }, { "id": "wohrm", "name": "Workout HRM", "icon": "app.png", @@ -1463,7 +1503,7 @@ { "id": "minionclk", "name": "Minion clock", "icon": "minionclk.png", - "version": "0.04", + "version": "0.05", "description": "Minion themed clock.", "tags": "clock,minion", "type": "clock", @@ -1629,9 +1669,9 @@ "name": "Calculator", "shortName":"Calculator", "icon": "calculator.png", - "version":"0.02", + "version":"0.03", "description": "Basic calculator reminiscent of MacOs's one. Handy for small calculus.", - "tags": "app,tool", + "tags": "app,tool,b2", "storage": [ {"name":"calculator.app.js","url":"app.js"}, {"name":"calculator.img","url":"calculator-icon.js","evaluate":true} @@ -1746,7 +1786,7 @@ "name": "Black Jack game", "shortName":"Black Jack game", "icon": "blackjack.png", - "version":"0.01", + "version":"0.02", "description": "Simple implementation of card game Black Jack", "tags": "game", "allow_emulator":true, @@ -1772,7 +1812,7 @@ "name": "SWL Clock / Short Wave Listner Clock", "shortName": "SWL Clock", "icon": "swlclk.png", - "version":"0.01", + "version":"0.02", "description": "Display Local, UTC time and some programs on the shorts waves along the day, with the frequencies", "tags": "tool,clock", "type":"clock", @@ -1788,7 +1828,7 @@ "name": "Round clock with seconds, minutes and date", "shortName": "Round Clock", "icon": "app.png", - "version": "0.05", + "version": "0.06", "description": "Designed round clock with ticks for minutes and seconds and heart rate indication", "tags": "clock", "type": "clock", @@ -1802,7 +1842,7 @@ "name": "fclock", "shortName": "F Clock", "icon": "app.png", - "version": "0.01", + "version": "0.02", "description": "Simple design of a digital clock", "tags": "clock", "type": "clock", @@ -1904,7 +1944,7 @@ "id": "largeclock", "name": "Large Clock", "icon": "largeclock.png", - "version": "0.07", + "version": "0.09", "description": "A readable and informational digital watch, with date, seconds and moon phase", "readme": "README.md", "tags": "clock", @@ -2097,6 +2137,20 @@ { "name": "jbm8b.img", "url": "app-icon.js", "evaluate": true } ] }, + { + "id": "jbm8b_IT", + "name": "Magic 8 Ball Italiano", + "shortName": "Magic 8 Ball IT", + "icon": "app.png", + "description": "La palla predice il futuro", + "tags": "game", + "version": "0.01", + "allow_emulator":true, + "storage": [ + { "name": "jbm8b_IT.app.js", "url": "app.js" }, + { "name": "jbm8b_IT.img", "url": "app-icon.js", "evaluate": true } + ] + }, { "id": "BLEcontroller", "name": "BLE Customisable Controller with Joystick", "shortName": "BLE Controller", @@ -2583,7 +2637,7 @@ "name": "NCR Clock", "shortName":"NCR Clock", "icon": "app.png", - "version":"0.01", + "version":"0.02", "description": "NodeConf Remote clock", "tags": "clock", "type": "clock", @@ -2596,7 +2650,7 @@ "name": "ISO Compliant Clock Face", "shortName":"ISO Clock", "icon": "isoclock.png", - "version":"0.01", + "version":"0.02", "description": "Tweaked fork of digiclock for ISO date and time", "tags": "clock", "type" : "clock", @@ -2667,7 +2721,7 @@ { "id": "dtlaunch", "name": "Desktop Launcher", "icon": "icon.png", - "version":"0.03", + "version":"0.04", "description": "Desktop style App Launcher with six apps per page - fast access if you have lots of apps installed.", "readme": "README.md", "tags": "tool,system,launcher", @@ -2754,7 +2808,7 @@ { "id": "lazyclock", "name": "Lazy Clock", "icon": "lazyclock.png", - "version":"0.02", + "version":"0.03", "readme": "README.md", "description": "Tells the time, roughly", "tags": "clock", @@ -2794,13 +2848,13 @@ "name": "Game of Life Clock", "shortName":"Conway's Clock", "icon": "app.png", - "version":"0.05", + "version":"0.06", "description": "Modification and clockification of Conway's Game of Life", "tags": "clock", "type" : "clock", "readme": "README.md", "storage": [ - {"name":"lifeclk.app.js","url":"app.js"}, + {"name":"lifeclk.app.js","url":"app.min.js"}, {"name":"lifeclk.img","url":"app-icon.js","evaluate":true} ] }, @@ -2856,7 +2910,7 @@ "name": "Morph Clock+", "shortName":"Morph Clock+", "icon": "mclockplus.png", - "version":"1.0", + "version":"0.02", "description": "Morphing Clock with more readable seconds and date and additional stopwatch", "tags": "clock", "type": "clock", @@ -3086,7 +3140,7 @@ { "id": "simplest", "name": "Simplest Clock", "icon": "simplest.png", - "version":"0.01", + "version":"0.02", "description": "The simplest working clock, acts as a tutorial piece", "tags": "clock", "type":"clock", @@ -3172,19 +3226,35 @@ {"name":"waypoints.json","url":"waypoints.json"} ] }, +{ "id": "banglebridge", + "name": "BangleBridge", + "shortName":"BangleBridge", + "icon": "widget.png", + "version":"0.01", + "description": "Widget that allows Bangle Js to record pair and end data using Bluetooth Low Energy in combination with the BangleBridge Android App", + "tags": "widget", + "type": "widget", + "readme": "README.md", + "storage": [ + {"name":"banglebridge.wid.js","url":"widget.js"}, + {"name":"banglebridge.watch.img","url":"watch.img"}, + {"name":"banglebridge.heart.img","url":"heart.img"} + ] + }, { "id": "qmsched", - "name": "Quiet Mode Schedule", + "name": "Quiet Mode Schedule and Widget", "shortName":"Quiet Mode", "icon": "app.png", - "version":"0.01", + "version":"0.02", "description": "Automatically turn Quiet Mode on or off at set times", "readme": "README.md", - "tags": "tool", + "tags": "tool,widget", "storage": [ {"name":"qmsched","url":"lib.js"}, {"name":"qmsched.app.js","url":"app.js"}, {"name":"qmsched.boot.js","url":"boot.js"}, - {"name":"qmsched.img","url":"icon.js","evaluate":true} + {"name":"qmsched.img","url":"icon.js","evaluate":true}, + {"name":"qmsched.wid.js","url":"widget.js"} ], "data": [ {"name":"qmsched.json"} @@ -3249,7 +3319,7 @@ "name":"Dozenal Time", "shortName":"Dozenal Time", "icon":"app.png", - "version":"0.01", + "version":"0.04", "description":"A dozenal Holocene calendar and dozenal diurnal clock", "tags":"clock", "type":"clock", @@ -3305,7 +3375,7 @@ { "id": "mysticclock", "name": "Mystic Clock", "icon": "mystic-clock.png", - "version":"1.00", + "version":"1.01", "description": "A retro-inspired watchface featuring time, date, and an interactive data display line.", "tags": "clock", "type":"clock", @@ -3316,5 +3386,42 @@ {"name":"mysticclock.settings.js","url":"mystic-clock-settings.js"}, {"name":"mysticclock.img","url":"mystic-clock-icon.js","evaluate":true} ] +}, +{ "id": "hcclock", + "name": "Hi-Contrast Clock", + "icon": "hcclock-icon.png", + "version":"0.01", + "description": "Hi-Contrast Clock : A simple yet very bold clock that aims to be readable in high luninosity environments. Uses big 10x5 pixel digits. Use BTN 1 to switch background and foreground colors.", + "tags": "clock", + "type":"clock", + "allow_emulator":true, + "storage": [ + {"name":"hcclock.app.js","url":"hcclock.app.js"}, + {"name":"hcclock.img","url":"hcclock-icon.js","evaluate":true} + ] +}, +{ "id": "thermomF", + "name": "Fahrenheit Temp", + "icon": "thermf.png", + "version":"0.01", + "description": "A modification of the Thermometer App to display temprature in Fahrenheit", + "tags": "tool", + "storage": [ + {"name":"thermomF.app.js","url":"app.js"}, + {"name":"thermomF.img","url":"app-icon.js","evaluate":true} + ] +}, +{ "id": "carcrazy", + "name": "Car Crazy", + "shortName":"Car Crazy", + "icon": "carcrash.png", + "version":"0.01", + "description": "A simple car game where you try to avoid the other cars by tilting your wrist left and right. Hold down button 2 to start.", + "tags": "game", + "readme": "README.md", + "storage": [ + {"name":"carcrazy.app.js","url":"app.js"}, + {"name":"carcrazy.img","url":"app-icon.js","evaluate":true} + ] } ] diff --git a/apps/banglebridge/README.md b/apps/banglebridge/README.md new file mode 100644 index 000000000..9897971f8 --- /dev/null +++ b/apps/banglebridge/README.md @@ -0,0 +1,10 @@ +Widget that allows Bangle Js to record pair and end data using Bluetooth Low Energy in combination with the BangleBridge Android App +Part of smartPPE project https://jorgepramos.github.io/Smart_PPE/index.html + +# BangleBridge + +Widget that allows Bangle Js to record pair and end data using Bluetooth Low Energy in combination with the BangleBridge Android App. + +## Full Project + +Part of smartPPE project [SmartPEE](https://jorgepramos.github.io/Smart_PPE/index.html). \ No newline at end of file diff --git a/apps/banglebridge/banglebridge.png b/apps/banglebridge/banglebridge.png new file mode 100644 index 000000000..3c1e693fc Binary files /dev/null and b/apps/banglebridge/banglebridge.png differ diff --git a/apps/banglebridge/heart.img b/apps/banglebridge/heart.img new file mode 100644 index 000000000..b8e339b30 --- /dev/null +++ b/apps/banglebridge/heart.img @@ -0,0 +1 @@ +00ˆþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþ þþþþþþþþþþþþþþ þþþþþþþþþþþþþþþþþþ þþþþþþþþþþ þþþþþþþþþþþþþþþ þþþþþþ þþþþþþþþþþþþþ þþþþ þþþþþþþþþþþ þþ ?? þþþþþþþþþ ÿÿ þþþþþþþþ ÿÿ þþþþþþþ ÿÿ þþþþþþ ÿÿ þþþþþþ ?ÿÿÿÿÿÿÿÿÿÿ? þþþþþþ ?ÿÿÿÿÿÿÿÿÿÿ? þþþþþþ ÿÿ ''' 'þþþþþþ ÿÿ' '''þþþþþþ ÿÿ' ''''þþþþþþþ 'ÿÿ' '''''þþþþþþþþ '??'''''''þþþþþþþþþ ''''''''þþþþþþþþþþ ''''''''þþþþþþþþþþþ '''''''''þþþþþþþþþþþþ ''''''''''þþþþþþþþþþþþþ '''''''''''þþþþþþþþþþþþþþþ ''''''''''''þþþþþþþþþþþþþþþþþ ''''''''''''þþþþþþþþþþþþþþþþþþþ '''''''''''''þþþþþþþþþþþþþþþþþþþþþ '''''''''''''þþþþþþþþþþþþþþþþþþþþþþþ ''''''''''''''þþþþþþþþþþþþþþþþþþþþþþþþþ ''''''''''''''þþþþþþþþþþþþþþþþþþþþþþþþþþþ '''''''''''''''þþþþþþþþþþþþþþþþþþþþþþþþþþþþþ '''''''''''''''þþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþ''''''''''''''''þþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþ''''''''''''''þþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþ''''''''''þþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþ''''''''þþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþ''''''þþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþ''þþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþ \ No newline at end of file diff --git a/apps/banglebridge/watch.img b/apps/banglebridge/watch.img new file mode 100644 index 000000000..4a8434583 Binary files /dev/null and b/apps/banglebridge/watch.img differ diff --git a/apps/banglebridge/widget.js b/apps/banglebridge/widget.js new file mode 100644 index 000000000..48078de30 --- /dev/null +++ b/apps/banglebridge/widget.js @@ -0,0 +1,302 @@ +(() => { + /** + * Widget measurements + * Description: + * name: connection.wid.js + *icon: conectionIcon.icon + * + */ + + //Font + + g.setFont("Vector", 100); + //variabangle.Sensorss + let acclS, bttS, compssS, gpsS, hrmS, stepS; //Strings + let accelN, compssN, gpsN, hrmN, stepN; //Num + let prueba = 1; + let data = [0, 0, 0, 0, 0, 0]; + //Constants for redabangle.Sensors code + let storage = require('Storage'); + let deCom = require('heatshrink'); + + + + + //Sensors code + /** + * + * @author Jorge + */ + function accel() { + + Bangle.on('accel', function (acc) { + // acc = {x,y,z,diff,mag} + accelN = acc; + }); + + setInterval(function () { + + acclS = accelN.x + "##" + accelN.y + "##" + accelN.z + "\n" + accelN.diff + "##" + accelN.mag; + data[3] = accelN; + }, 2 * 1000); + + } + + function btt() { + + setInterval(function () { + + bttS = E.getBattery(); //return String + data[2] = E.getBattery(); + }, 15 * 1000); + + } + + + + function compss() { + + Bangle.setCompassPower(1); + Bangle.on('mag', function (mag) { + // mag = {x,y,z,dx,dy,dz,heading} + compssN = mag; + }); + + + setInterval(function () { + + compssS = "A: " + compssN.x + " ## " + compssN.y + " ## " + compssN.z + "\n" + + "B: " + compssN.dx + " ## " + compssN.dy + " ## " + compssN.dz + " ## " + "\n" + + "C: " + compssN.heading; //return String + data[4] = compssN; + }, 2 * 1000); + + } + + + + function gps() { + + Bangle.setGPSPower(1); + Bangle.on('GPS', function (gps) { + // gps = {lat,lon,alt,speed,etc} + gpsN = gps; + + }); + + setInterval(function () { + + gpsS = "A: " + gpsN.lat + " ## " + gpsN.lon + " ## " + gpsN.alt + "\n" + "B: " + gpsN.speed + " ## " + gpsN.course + " ## " + gpsN.time + "\n" + + "C: " + gpsN.satellites + " ## " + gpsN.fix; //return String + // work out how to display the current time + var d = new Date(); + var year = d.getFullYear(); + + var month = d.getMonth() + 1; + var finalMonth = 0; + if (month < 10) { + finalMonth = "0" + month; + } else { + finalMonth = month; + } + var day = d.getDate(); + var finalDay = 0; + if (day < 10) { + finalDay = "0" + day; + } else { + finalDay = day; + } + var h = d.getHours(), + m = d.getMinutes(); + var finalh = 0; + if (h < 10) { + finalh = "0" + h; + } else { + finalh = h; + } + var finalM = 0; + if (m < 10) { + finalM = "0" + m; + } else { + finalM = m; + } + + var s = d.getSeconds(); + var finalS = 0; + if (s < 10) { + finalS = "0" + s; + } else { + finalS = s; + } + var z = d.getMilliseconds(); + var zFinal = new String(z); + zFinal = zFinal.replace('.', ''); + var completeTime = year + "-" + finalMonth + "-" + finalDay + "T" + finalh + ":" + finalM + ":" + finalS + "." + z + "Z"; + var time = h + ":" + ("0" + m).substr(-2); + gpsN.time = completeTime; + data[5] = gpsN; + }, 2 * 1000); + } + + //2021-06-11T19:21:58.000Z + + function hrm() { + + let msr = [0, 0, 0, 0, 0]; + let lastInsert = -1; + + function roundInsert(nueva) { + let indexFinal = (lastInsert + 1) % (msr.length); + //console.log("Index ==> "+ index); + msr[indexFinal] = nueva; + + item = nueva; + lastInsert = indexFinal; + + } + + function normalize(nueva) { + + let normalize = 0; + roundInsert(nueva); + + + msr.forEach(function (number) { + normalize += number; + }); + normalize = normalize / msr.length; + + return normalize; + + } + + + + + setInterval(function () { + + if (!isNaN(hrmN)) { + + + hrmN = normalize(hrmN); + var roundedRate = parseFloat(hrmN).toFixed(2); + hrmS = String.valueOf(roundedRate); //return String + //console.log("array----->" + msr); + data[0] = roundedRate; + + } + + + + + + }, 2 * 1000); + + } + + + function steps() { + + Bangle.on('step', s => { + + stepN = s; + }); + + + setInterval(function () { + + stepS = String.valueOf(stepN); //return String + data[1] = stepN; + }, 2 * 1000); + + + } + + function initSensors() { + + //need power control + Bangle.setHRMPower(1); + + Bangle.on('HRM', function (hrm) { + hrmN = hrm.bpm; + + + }); + console.log("Sensors are being Init...."); + accel(); + btt(); + compss(); + gps(); + hrm(); + steps(); + + } + + var flip = 1; + Bangle.on('lcdPower', function (on) { + /* + prueba ++; + Bangle.drawWidgets(); + g.setFont("Vector", 45); + g.drawString(prueba,100,200);*/ + if (flip == 1) { //when off + + flip = 0; + //Bangle.buzz(1000); + g.clear(); + } else { //when on + + flip = 1; + g.setFont("Vector", 30); + g.drawString(data[0], 65, 180); + Bangle.drawWidgets(); + } + + }); + + + function draw() { + g.drawImage(storage.read("banglebridge.watch.img"),this.x + 1,this.y + 1); + g.drawImage(storage.read("banglebridge.heart.img"), 145, 167); + } + + + // Finally add widget + + + initSensors(); + // Bangle.drawWidgets(); + // Terminal.println("Running BangleBridge"); + data[0] = 80.5; + g.setFont("Vector", 30); + g.drawString(data[0], 65, 180); + // Bangle.drawWidgets(); + setInterval(function () { + //console.log("---------------------------------------------------------------"); + //console.log(data); + //Bluetooth.println(data[0]); + var measurement = { + hrm: data[0], + step: data[1], + batt: data[2], + acc: data[3], + com: data[4], + gps: data[5] + }; + /* g.clear(); + g.drawString(compssS,100,200); + */ + + + + Bluetooth.println(JSON.stringify(measurement) + "#"); + //draw(); + + }, 5 * 1000); + + WIDGETS["banglebridge"]={ + area: "tl", + width: 10, + draw: draw, + }; +})(); //End of Widget diff --git a/apps/banglebridge/widget.png b/apps/banglebridge/widget.png new file mode 100644 index 000000000..3c1e693fc Binary files /dev/null and b/apps/banglebridge/widget.png differ diff --git a/apps/blackjack/ChangeLog b/apps/blackjack/ChangeLog index c941d90e5..25b5f9195 100644 --- a/apps/blackjack/ChangeLog +++ b/apps/blackjack/ChangeLog @@ -1 +1,2 @@ -0.01: New game! BTN4- Hit card, BTN5- Stand \ No newline at end of file +0.01: New game! BTN4- Hit card, BTN5- Stand +0.02: ignore buttons on pauses \ No newline at end of file diff --git a/apps/blackjack/blackjack.app.js b/apps/blackjack/blackjack.app.js index bbee8137b..b88432fd9 100644 --- a/apps/blackjack/blackjack.app.js +++ b/apps/blackjack/blackjack.app.js @@ -18,6 +18,7 @@ const Diamonds = { width : 48, height : 48, bpp : 4, var deck = []; var player = {Hand:[]}; var computer = {Hand:[]}; +var ctx = {ready:true}; function createDeck() { var suits = ["Spades", "Hearts", "Diamonds", "Clubs"]; @@ -44,6 +45,7 @@ function shuffle(a) { } function EndGameMessdage(msg){ + ctx.ready = false; g.drawString(msg, 155, 200); setTimeout(function(){ startGame(); @@ -52,6 +54,7 @@ function EndGameMessdage(msg){ } function hitMe() { + if (!ctx.ready) return; player.Hand.push(deck.pop()); renderOnScreen(1); var playerWeight = calcWeight(player.Hand, 0); @@ -97,6 +100,8 @@ function calcWeight(hand, hideCard) { } function stand(){ + if (!ctx.ready) return; + ctx.ready = false; function sleepFor( sleepDuration ){ console.log("Sleeping..."); var now = new Date().getTime(); @@ -156,6 +161,7 @@ function renderOnScreen(HideCard) { function dealHands() { player.Hand= []; computer.Hand= []; + ctx.ready = false; setTimeout(function(){ player.Hand.push(deck.pop()); @@ -175,6 +181,7 @@ function dealHands() { setTimeout(function(){ computer.Hand.push(deck.pop()); renderOnScreen(1); + ctx.ready = true; }, 2000); } diff --git a/apps/boldclk/ChangeLog b/apps/boldclk/ChangeLog index f5d1af878..c7a4ba7b4 100644 --- a/apps/boldclk/ChangeLog +++ b/apps/boldclk/ChangeLog @@ -1,3 +1,4 @@ 0.02: Modified for use with new bootloader and firmware 0.03: Tweak for more efficient rendering, and firmware 2v06 0.04: Work with themes, smaller screens +0.05: Adjust hand lengths to be within 'tick' points diff --git a/apps/boldclk/bold_clock.js b/apps/boldclk/bold_clock.js index 191b6ec4f..4358b2e29 100644 --- a/apps/boldclk/bold_clock.js +++ b/apps/boldclk/bold_clock.js @@ -23,6 +23,10 @@ tick5.fillRect(0,0,tick5.getWidth()-1, tick5.getHeight()-1); let tick1 = Graphics.createArrayBuffer(8,4,1,{msb:true}); tick1.fillRect(0,0,tick1.getWidth()-1, tick1.getHeight()-1); +// Adjust hand lengths to be within 'tick' points +minute_hand.width=radius-tick1.getWidth()-6; +hour_hand.width=radius-tick5.getWidth()-6; + function big_wheel_x(angle){ return clock_center.x + radius * Math.cos(angle*p180); } diff --git a/apps/boot/ChangeLog b/apps/boot/ChangeLog index ccf97a9eb..4a8c62d59 100644 --- a/apps/boot/ChangeLog +++ b/apps/boot/ChangeLog @@ -26,3 +26,4 @@ 0.25: Fix error in 'no clock app' message 0.26: Remove buzz in setUI polyfill (#750) 0.27: Update polyfill for most recent changes +0.28: Fix double clock load after settings are changed diff --git a/apps/boot/bootupdate.js b/apps/boot/bootupdate.js index 014edd552..4c4efed29 100644 --- a/apps/boot/bootupdate.js +++ b/apps/boot/bootupdate.js @@ -136,8 +136,9 @@ require('Storage').list(/\.boot\.js/).forEach(bootFile=>{ boot += "//"+bootFile+"\n"+require('Storage').read(bootFile)+"\n"; }); boot += "}\n";// initial 'if' -var s = require('Storage').write('.boot0',boot); +require('Storage').write('.boot0',boot); delete boot; E.showMessage("Reloading..."); eval(require('Storage').read('.boot0')); -eval(require('Storage').read('.bootcde')); +// .bootcde should be run automatically after if required, since +// we normally get called automatically from '.boot0' diff --git a/apps/calculator/ChangeLog b/apps/calculator/ChangeLog index 3b9b23270..c711731d7 100644 --- a/apps/calculator/ChangeLog +++ b/apps/calculator/ChangeLog @@ -1,2 +1,3 @@ 0.01: New App! 0.02: fix precision rounding issue + no reset when equals pressed +0.03: Support for different screen sizes and touchscreen diff --git a/apps/calculator/app.js b/apps/calculator/app.js index a736b715d..89520ab94 100644 --- a/apps/calculator/app.js +++ b/apps/calculator/app.js @@ -6,13 +6,11 @@ */ g.clear(); -Graphics.prototype.setFont7x11Numeric7Seg = function() { - this.setFontCustom(atob("ACAB70AYAwBgC94AAAAAAAAAAB7wAAPQhhDCGELwAAAAhDCGEMIXvAAeACAEAIAQPeAA8CEMIYQwhA8AB70IYQwhhCB4AAAIAQAgBAB7wAHvQhhDCGEL3gAPAhDCGEMIXvAAe9CCEEIIQPeAA94EIIQQghA8AB70AYAwBgCAAAAHgQghBCCF7wAHvQhhDCGEIAAAPehBCCEEIAAAAA=="), 46, atob("AgAHBwcHBwcHBwcHAAAAAAAAAAcHBwcHBw=="), 11); -}; +require("Font7x11Numeric7Seg").add(Graphics); var DEFAULT_SELECTION = '5'; -var BOTTOM_MARGIN = 10; var RIGHT_MARGIN = 20; +var RESULT_HEIGHT = 40; var COLORS = { // [normal, selected] DEFAULT: ['#7F8183', '#A6A6A7'], @@ -123,7 +121,7 @@ function drawKey(name, k, selected) { var bMargin = 0; var color = k.color || COLORS.DEFAULT; g.setColor(color[selected ? 1 : 0]); - g.setFont('Vector', 20); + g.setFont('Vector', 20).setFontAlign(0,0); g.fillRect(k.xy[0], k.xy[1], k.xy[2], k.xy[3]); g.setColor(-1); // correct margins to center the texts @@ -141,7 +139,7 @@ function drawKey(name, k, selected) { } else if (name === '%') { rMargin = -3; } - g.drawString(k.val || name, k.xy[0] + RIGHT_MARGIN + rMargin, k.xy[1] + BOTTOM_MARGIN + bMargin); + g.drawString(k.val || name, (k.xy[0] + k.xy[2])/2, (k.xy[1] + k.xy[3])/2); } function getIntWithPrecision(x) { @@ -202,7 +200,7 @@ function displayOutput(num) { var len; var minusMarge = 0; g.setColor(0); - g.fillRect(0, 0, 240, 39); + g.fillRect(0, 0, g.getWidth(), RESULT_HEIGHT-1); g.setColor(-1); if (num === Infinity || num === -Infinity || isNaN(num)) { // handle division by 0 @@ -240,17 +238,12 @@ function displayOutput(num) { num = num.substr(1); } } - - len = (num + '').length; - if (numNumeric < 0 || (numNumeric === 0 && 1/numNumeric === -Infinity)) { - // minus is not available in font 7x11Numeric7Seg, we use Vector - g.setFont('Vector', 20); - g.drawString('-', 220 - (len * 15), 10); - minusMarge = 15; - } + num = num.toString(); + num = num.replace("-","- "); // fix padding for '-' g.setFont('7x11Numeric7Seg', 2); } - g.drawString(num, 220 - (len * 15) + minusMarge, 10); + g.setFontAlign(1,0); + g.drawString(num, g.getWidth()-20, RESULT_HEIGHT/2); } var wasPressedEquals = false; var hasPressedNumber = false; @@ -370,14 +363,6 @@ function buttonPress(val) { } } -for (var k in keys) { - if (keys.hasOwnProperty(k)) { - drawKey(k, keys[k], k == '5'); - } -} -g.setFont('7x11Numeric7Seg', 2.8); -g.drawString('0', 205, 10); - function moveDirection(d) { drawKey(selected, keys[selected]); prevSelected = selected; @@ -385,8 +370,37 @@ function moveDirection(d) { drawKey(selected, keys[selected], true); } -setWatch(_ => moveDirection(0), BTN1, {repeat: true, debounce: 100}); -setWatch(_ => moveDirection(2), BTN3, {repeat: true, debounce: 100}); -setWatch(_ => moveDirection(3), BTN4, {repeat: true, debounce: 100}); -setWatch(_ => moveDirection(1), BTN5, {repeat: true, debounce: 100}); -setWatch(_ => buttonPress(selected), BTN2, {repeat: true, debounce: 100}); +if (global.BTN4) { + setWatch(_ => moveDirection(0), BTN1, {repeat: true, debounce: 100}); + setWatch(_ => moveDirection(2), BTN3, {repeat: true, debounce: 100}); + setWatch(_ => moveDirection(3), BTN4, {repeat: true, debounce: 100}); + setWatch(_ => moveDirection(1), BTN5, {repeat: true, debounce: 100}); + setWatch(_ => buttonPress(selected), BTN2, {repeat: true, debounce: 100}); +} else { // touchscreen? + selected = "NONE"; + Bangle.on('touch',(n,e)=>{ + for (var key in keys) { + var r = keys[key].xy; + if (e.x>=r[0] && e.y>=r[1] && + e.x n*g.getWidth()/240); + } +} +// draw keys +for (var k in keys) { + if (keys.hasOwnProperty(k)) { + drawKey(k, keys[k], k == selected); + } +} +displayOutput(0); diff --git a/apps/carcrazy/ChangeLog b/apps/carcrazy/ChangeLog new file mode 100644 index 000000000..9a50abf42 --- /dev/null +++ b/apps/carcrazy/ChangeLog @@ -0,0 +1 @@ +0.01: Car Crazy is now avialable for testing in beta! diff --git a/apps/carcrazy/README.md b/apps/carcrazy/README.md new file mode 100644 index 000000000..6eef6f8c7 --- /dev/null +++ b/apps/carcrazy/README.md @@ -0,0 +1,3 @@ +![Screenshot 2021-08-23 100720](https://user-images.githubusercontent.com/89286474/130498574-0b5246cb-8553-455c-b6c0-096e5ca1644c.png) + +Form Link: https://forms.office.com/r/HnwYzG9Sk7 diff --git a/apps/carcrazy/app-icon.js b/apps/carcrazy/app-icon.js new file mode 100644 index 000000000..b3f122ea8 --- /dev/null +++ b/apps/carcrazy/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwhC/AH4Ay90A8AWT8HuhwXSCQPuAAIXShwWCC6YuDAAQwWGKQWWFwoJDxAAFF5gHChAXODAhHCxGDmYAE3YAC2AwKxAWFC4gABC4YBCIwYXGoNb2tUpYxDRYsIIw0zqoXBqtVDAIXVDAJiEUYYXNqhhDUYgWGC41VSYgXSjYXHAA4XGJAULDQQXQJALFDGBTXFAAuwgQXV3cP+coC6m////nAXYiIAEC5m4FIYXN2gKBpYXFwIXMkIKBiQXSFwQwCC4hIFC42xiMjmURlYXFDAgXImcziMbC4ynKC4uxC5MAAAsBI4cQAoMRC50AKYYdCDYIQHGBASCC6QwCDgwA/AH4AVA==")) diff --git a/apps/carcrazy/app.js b/apps/carcrazy/app.js new file mode 100644 index 000000000..c7476d18a --- /dev/null +++ b/apps/carcrazy/app.js @@ -0,0 +1,240 @@ +Bangle.setLCDPower(1); +Bangle.setLCDTimeout(0); +var numberofHearts = 3; + +Bangle.setLCDMode("doublebuffered"); + + +//var popUp = require("heatshrink").decompress(atob("isFwMBCJoA==")); + +var backgroundImage = require("heatshrink").decompress(atob("isZxH+woAB6YBBBodXAgYLCCIQUJAgoUOFP4p/FP4p/FP4p/FP4pYA==")); + +var heartImage = require("heatshrink").decompress(atob("hUKxH+ABckAAYIDpFstlIudzpARCtmQyGXz2luYVBkgIBBQXCy4JCCYQKCtgpCE4IJCFQIyFBAwKDBAw/CAogA==")); + +var gameOverImage = require("heatshrink").decompress(atob("kcV4MB+Nj/4AJwATBgfwDAV+AYUP4ADBgP4BAU/AYUHERMP+AiBgP+EQU/4AiBg/4EQX/EQUf8EH8ED/kAAIN/wEfEQ9/FwX+A==")); + +var RedCar = +require("heatshrink").decompress(atob("ol7xH+ABWBqwAqwI5LABKAtIf4AClZASq0rp9zudIDYNIABgSBABd5AAtPEoNPAoUrqxDPlcAkmXy9OIYVsABYSBABWdzwAFIYVzAoMkRSJDCNAJDBkhDiuckFQOevMqklPZBo7DZAUkHagAKIoyKCkl5IwIwBwJDLYYRDrWoMAAgJDCqxDZyxDXzoABIcpAXRRRD/If5D/If5D/If5D9uYASId9OBoIAPpxDvBoRD2AAlOADlPAAckIbJ0QABjeNIf4ACkhDUIgNIUQQEBAAtyIaOXvIAFp4lBp4FBIapFCA4wACyxCRzo2BAAlzEoNzA4ZDVQ4ZDGQyRDHQ4RDgHyQAHIf5DhklIHoRDrudzA4RDNIoRDtFYRD/Iac5rsykgABaANyILQACzudIgTGClcrrowBIZ9ktdkRQhCdRQ8rGAxD/If5D/If4ACzpD/Io5D/IbOI2eIIdowBwJDP1nX1hDtGAJD/Ialkx7eCZdgwDIZ1rIdwwEIf5D/If5DZmU5mRDtGAhDNAAhDrAAhD/IaM5rrLmzpCCIYYwEIZtktdkIcpBCIYgwEIf5DZAARD/If8ylcrpFIIcd5uYpBmRDUAAUyA4JECIb9zEoNdFoZDVnJDkp5DZx+z2YHCIbudAAJDFFYOPIanX65DgIAIACIYWBFYJD/IYSHBx5DQ1idCx5DFAAjITIIjLFFYIvBwJDQLINrtc5IZOWIbq1DIaJXBslkmUHg9IAA1yRCV5AAtPg8rnIrBQ6VkbwcyDQMrAoIAEpxCRpwaGmSwBmUAg4wEIZ1rIYYdBg5DZpBDLGAt6II+BwJ8CIY85mQAFp9OACFPDQ1dIZMrHYJCFLghDHAA4lCAB85DpgwEAARD9rpDHq0rp9zAAcrmTQCIZVkroAQGoNr2YABIY4wCHAlPldWBgMky4ADkhTDIZQAV2fXIZA4JlYABQ4pDCg8HIcePAAJDCFYJDCYAoABZoOBdw0HoFAVoQAdIASKFslkIgSMEIAIACIZBBgIonXZwpDHgBDE/1Wq0rIe8rHYJDFIoRD3IJBD6vRD1AASH/AA5DWoFkAARD9IgIADoA+hrooEGQhDQAAk5IcM5FpJDNkgAGnNAaQjTTC4gACmUklQAEkhDPpFsAAoYCR68yDQ1PzwAFvJD/IbNyuVOAA1WnIAQp4aGuedzpDauWXABFIOgwAKpwdJIghDgpxD/AAVzACRDvADpD/If5D/If5D/If5D/If5D/If5D/If5DGthD/AApD/If8kkhFGuVyIMJCDp4ABIZ5FCRIyKgQgmekgzDIf5DRp1zpzOIZ4I+dZAUqldzvLLQueXuaKiIQ2elYrBAgN5IaSHBRAIADIcOdp8klSSBOgRDNHIQiBpzjDRjDIEvMkEYdzBAIPBIbUAIboiEIaWBq1WYYVyy9yZwTQGAAVOp9OABlzAAdPAAlzzuduYeBGoJCJAAcrYYZFBAAReCADUkRgeezomBaYMrIJpD/Z496YoRFEUoIAaYoQgBEIMkp+BwJCQAARkCRQKJDAD2WEoKEBgBBTZw1zIcNyIYbIRAA1WRQQABEQIAbEAQlBY6hDIAARCcpAiEIbWBlYADpAAcEQhCZAAyrCADI+hAAhCbIc+BqwAawIwS")); + +var OrangeCar = require("heatshrink").decompress(atob("ol7xH+ABXGAFfHHJYAJQFpD/Ia1OgEkpwDBAAUqABYSBABd4AAskEgQFBAgOcQiQXBlQdCGhdyGowAFznGAAnHNQVO4/GRSQSCGIJDPIRhDH4xDD0SHCkhAMCAQ7BIAQbBHaYAKIoyEDKII1C45DJLIRDw0XHIYXGIa5BYZwWiIZKHDIeZFCIf5D/If5D/If5D/If4ADBQgAMIeEkBoQAOlRDvISJDnAAkklQAWDAQbGIcB0JABreNIf5DYIgJFEW4xCRJZYeBznGIagABIQYHDAAQ7SGwQAD44mCp3HBARD64xDDA4ZDbYzRDdPghDs45DQgEkHoRDrE4MkIaCKDIdYyEIf5DRzjeDaAZBaAAOcAAI9B0RmBlQqB0QwDIZvP5/ORQhCcRRAqCGAhD1Q4RD/If5D/ABOiIf6KHIf5DZ63X55DtGAhDN6/X6xDtGAPQIf5DT5/WZdwwB5xDQ54SCIdgwDIf5D/If5DZvHGAgRDrGAhDNAApDqAAhD/IaOc494Icui0RCBIYeiGAZDN5/P5xDkQgRDFGAhD/IbIABuRD/AAZD658qkkAlUqIcdyuUkkkqIaoABvAHBIgRDfFoXGFoZD+45DW6HX6+cIb+czmiIYnOFYJDU6wXB4xDfIAIACIYvQIf5DVCgPX63WZYoAEZCZBEIYnHFYLNCIaSKBvBDoFYKJCIafOIYUklQAFZKecAAsqEoOi5xDCQ6BYBCQVO5zjCAApESkgaGpwqBIwQEBFYRDPCQRD54/HDARDHzlOAAdyuTTHABd4CwIACp3HIZMAHYJCEPQpDHAAjYCTQQAPzgWCAAxDIAAJDXIgRDi5pDHpy0BXokAdoZDK56lBAB4UC64ABIY4wCHAkkkl4BgV4AAZTEIZYAUIZY4LI4IACuQUFITxDD6HQA4TpFIY7NB47vHQj4AE6yKG5w1HIAIACBpBDk6DOHIZn+lTdFIekqpxDFAALeFIeXGII5D/IeHP63X6xD/ABBD/IbIAE45Dh44tJIfHGIb9445FCaCzrGvBDXlVOAAodIISI7HknGAAtyIeQzCIZhTCIad4vEqkgAFp2cACAZGDQZDaIQIAIeZIAIlQdJ0RDkkhD/AAbbEABgcKIcwAcIf5D/If5D/If5D/If5D/If5D/If5D/If5DKI1BDVIQxD8AAMkIc+cAAJBCp0qGYRDPRVBBDAAIyEIf5DRlSbDZwtOuQ+eAAIrCNQIECIZofBRgaJfIQyED43HvBDPPYJDKRC6GD0WcAAUkFQILBIaB9EToRIJRqDIEzgiFBAR0DIZsqPgZDp46FBIZ3OGAMkkkqCogACBQIAFKwIATEQl4aIIJBD4RCJAAZ/EN4olEADCMDZAQlDIJpD/Z47pCZwQACYSgAIzg/CvEqeAPH5xCQRRoAdEoKETIdlyIbQABcgZIDADYiE45BXAAPGIcMqEQnPIbPOEAkqADgiEITIAGEogAYH0BD/ABHH4wAa5wwSA")); + +var PurpleCar = require("heatshrink").decompress(atob("ol74UBitg///BIP/7lVqtUDJUVBwIABq2qABOVCCkolQJC0AwDgWolAQD1EqwBCH0EqCCdKxQuEAAkKwGhCH4Q/CGD7ECDINEAAoQIwBACgQQYwQ+EAAcC1EACAeAlQfDAAheBCG2oCBUCCB8qCCiQBdp3+fxW/CH4Q/CH4QxgQQKwAQD1QQK1QQUmQQKxiH/CBGqCB3/5WACA+j/4Qf14Q/KhWqRI0CCAv+x4QBEYcC1Wo/SpFCBtUlQQBBgISBAAUAgYQB1fVr2qCAP//8qB4WoAwIQB1WVCCEoFwIQCAA36GwMo1AQOlQQM+QQCqWq1YPIAAPqKgYQOqwQOiqpBCBi6DCH4QDYoIOH/gKBCAs/CD2AZoISEBwUAgAQYAYIABgWqfIIQGAAISBAAQHCCDIAICH4Q/CH4Q/CAWoB5UKCB8qCCmK0AxJwQQDgQQKwAQE1Q0ICAuAlQQHlQrBCCdy1EK1QABCAmq0EKgoQBqofBIoIAFWYMqB4QQRuQ5BCAxlBxoQDqyHKyoQUZpLJDCBmq1oQFvRTGAAOlCApFBB4xBFCCdUIY3VBgY=")); + +var LightGreenCar = require("heatshrink").decompress(atob("ol74UBocF///BIP1z9VqtUDJUVBwIABq2qABOVCCkolQJC0AwDgWolAQD1EqwBCH0EqCCdKxQuEAAkKwGhCH4Q/CGD7ECDINEAAoQIwBACgQQYwQ+EAAcC1EACAeAlQfDAAheBCG2oCBUCCB8qCCiQBdp3+fxW/CH4Q/CH4QxgQQKwAQD1QQK1QQUmQQKxiH/CBGqCB3/5WACA+j/4Qf14QxKjGqRI0CCAv+x4QBEYcC1Wo/SpFCBtUlQQBBgISBAAQGBCAOo6te1QQB///lQPC1AGBCAOqyoQQlAuBCAXPAQIAD/Q2BlGoCAgAGCAUqCBnyCAVS1WrB5AAB9RUDCB1WCB0VVIIQMXQYQ/CAbFBBw/8BQIQFn4QewDNBCQgOCgEACDADBAAMC1T5BCAwABCQIACA4QQZABAQ/CH4Q/CH4QC1APKhQQPlQQUxWgGJOCCAcCCBWACAmqGhAQFwEqCA8qFYIQTuWohWqAAIQE1WghUFCANVD4JFBAAqzBlQPCCCNyHIIQGMoONCAdWQ5WVCCjNJZIYQM1WtCAt6KYwAB0oQFIoIPGIIoQTqhDG6oMDA")); + +function getRandomInt(min, max) { + min = Math.ceil(min); + max = Math.floor(max); + return Math.floor(Math.random() * (max - min) + min); //The maximum is exclusive and the minimum is inclusive +} + +function moveEnemyPosition(){ + score += 1; + randomRoadPositionIndicator = getRandomInt(1, 4); + if ((randomRoadPositionIndicator == 1)) { + randomRoadPosition = 85; + }else if((randomRoadPositionIndicator == 2)){ + randomRoadPosition = 120; + }else { + randomRoadPosition = 155; + } +} + +function collision(){ + if(gameStatus == GAMEPLAYING){ + if + ( + (enemyCarFrontY > playerCarFrontY) + && + ( + (enemyCarLeftX > playerCarLeftX && enemyCarLeftX < playerCarRightX) + || + (enemyCarRightX > playerCarLeftX && enemyCarRightX < playerCarRightX) + ) + ){ + // hit + setTimeout(collision, 2500); // wait 2.5 second for the function to actiavte agian. + numberofHearts -= 1; + score -= 1; + Bangle.buzz(); + }else{ + // miss + setTimeout(collision, 1); // try again in 1 milliseconds. + } + } +} + +function storeMyData(data) { + // ensure there are less than 500 elements in the array + while (log.length >= 500) log.shift(); + // append a new item to the array + log.push(data); +} + + +var file = require("Storage").open("CarCrazy.csv","r"); +var currentHighScore = file.readLine(); +if (currentHighScore == undefined) currentHighScore = 0; +var BackgroundStartingPosition = 75; +var carScale = 0.5; +var accel = Bangle.getAccel(); +var playerCarPosition = 120-accel.x*40; +var BackgroundYPosition = BackgroundStartingPosition; +var randomRoadPositionIndicator = getRandomInt(1, 3); +var randomRoadPosition = 120; +var enemyPositonY = 30; +var carWidth = 30; +var carHeight = 60; +var playerCarY = 130; +var enemyCarLeftX; +var enemyCarRightX; +var playerCarLeftX; +var playerCarRightX; +var enemyCarFrontY; +var playerCarFrontY; +var GAMEPLAYING = 1; +var GAMEOVER = 2; +var GAMESTART = 3; +var gameStatus = GAMESTART; +var score = 0; + +moveEnemyPosition(); +collision(); + + +g.setFontAlign(-1,-1); + +function clearHighScore() { + currentHighScore = 0; + file = require("Storage").open("CarCrazy.csv","w"); + file.erase(); +} + +function draw(){ + if(gameStatus == GAMEPLAYING){ + BackgroundYPosition += 10; + accel = Bangle.getAccel(); + playerCarPosition = 120-accel.x*40; + g.flip(); + g.drawImage(backgroundImage,125,BackgroundYPosition, {scale:13,rotate:0}); + g.drawImage(RedCar,playerCarPosition,playerCarY, {scale:carScale,rotate:3.142}); + g.drawImage(OrangeCar,randomRoadPosition,enemyPositonY, {scale:carScale,rotate:0}); + + if(numberofHearts==3){ + g.drawImage(heartImage,10,10, {scale:2,rotate:0}); + g.drawImage(heartImage,10,50, {scale:2,rotate:0}); + g.drawImage(heartImage,10,30, {scale:2,rotate:0}); + }else if(numberofHearts==2){ + g.drawImage(heartImage,10,50, {scale:2,rotate:0}); + g.drawImage(heartImage,10,30, {scale:2,rotate:0}); + }else if(numberofHearts==1){ + g.drawImage(heartImage,10,50, {scale:2,rotate:0}); + }else{ + gameStatus = GAMEOVER; + //clearHighScore(); + if(score >= currentHighScore){ + currentHighScore = score; + file = require("Storage").open("CarCrazy.csv","w"); + file.erase(); + file = require("Storage").open("CarCrazy.csv","w"); + file.write(currentHighScore+"\n"); + } + } + + playerCarFrontY = playerCarY-carHeight/2; + playerCarBackY = playerCarY+carHeight/2; + playerCarLeftX = playerCarPosition-carWidth/2; + playerCarRightX = playerCarPosition+carWidth/2; + + enemyCarFrontY = enemyPositonY+carHeight/2; + enemyCarBackY = enemyPositonY-carHeight/2; + enemyCarLeftX = randomRoadPosition-carWidth/2; + enemyCarRightX = randomRoadPosition+carWidth/2; + + //g.drawRect(playerCarLeftX, playerCarFrontY, playerCarRightX, playerCarBackY); + //g.drawRect(enemyCarLeftX, enemyCarFrontY, enemyCarRightX, enemyCarBackY); + + g.setColor(0,0,0); + g.drawString("Score: "+score,180,5); + g.drawString("HighScore:",178,15); + g.drawString(currentHighScore,205,25); + + if(BackgroundYPosition > 170){ + BackgroundYPosition = BackgroundStartingPosition; + } + + + }else if(gameStatus == GAMEOVER){ + + BackgroundYPosition += 10; + g.flip(); + g.drawImage(backgroundImage,125,BackgroundYPosition, {scale:13,rotate:0}); + g.drawImage(gameOverImage,125,80, {scale:8,rotate:0}); + if(BackgroundYPosition > 170){ + BackgroundYPosition = BackgroundStartingPosition; + } + g.setColor(255,0,0); + g.setFont("6x8",4); + g.drawString("Game Over",13,17); + g.setFont("6x8",1.5); + g.drawString("Score: "+score,10,75); + g.drawString("High",10,100); + g.drawString("Score: " + currentHighScore,10,110); + g.drawString("Hold Button",10,130); + g.drawString("2 To Play",10,140); + g.drawImage(LightGreenCar,180,115, {scale:0.5,rotate:3}); + g.drawImage(PurpleCar,215,115, {scale:0.5,rotate:3}); + }else if(gameStatus == GAMESTART){ + g.flip(); + g.drawImage(backgroundImage,125,BackgroundYPosition, {scale:13,rotate:0}); + g.setColor(255,0,0); + BackgroundYPosition += 10; + g.setFont("6x8",3); + g.drawImage(gameOverImage,125,80, {scale:8,rotate:0}); + g.drawString("Welcome to",13,11); + g.drawString("Car Crazy",13,31); + g.setFont("6x8",1.8); + g.drawString("High",10,80); + g.drawString("Score: "+currentHighScore,10,90); + g.drawString("Hold Button",10,120); + g.drawString("2 To Start",10,130); + g.drawImage(LightGreenCar,180,115, {scale:0.5,rotate:3}); + g.drawImage(PurpleCar,215,115, {scale:0.5,rotate:3}); + //setTimeout(displayPopup, 3000); + } +} +setInterval(draw ,10); + + +function moveEnemyCar(){ + if(gameStatus == GAMEPLAYING){ + enemyPositonY = enemyPositonY + 10; + if((enemyPositonY > 200)){ + enemyPositonY = 30; + moveEnemyPosition(); + } + } +} +setInterval(moveEnemyCar,10); + +setWatch(() => { + if(gameStatus == GAMESTART){ + gameStatus = GAMEPLAYING; + collision(); + enemyPositonY = 0; + score = 0; + }else if(gameStatus == GAMEOVER){ + gameStatus = GAMEPLAYING; + collision(); + enemyPositonY = 0; + numberofHearts = 3; + score = 0; + } +}, BTN2, {repeat:true}); + + + + + + + diff --git a/apps/carcrazy/carcrash.png b/apps/carcrazy/carcrash.png new file mode 100644 index 000000000..1678c54c8 Binary files /dev/null and b/apps/carcrazy/carcrash.png differ diff --git a/apps/doztime/ChangeLog b/apps/doztime/ChangeLog index 5560f00bc..6c4a25b26 100644 --- a/apps/doztime/ChangeLog +++ b/apps/doztime/ChangeLog @@ -1 +1,4 @@ 0.01: New App! +0.02: added emulator capability and display of widgets +0.03: bug of advancing time fixed; doztime now correct within ca. 1 second +0.04: changed time colour from slightly off white to pure white diff --git a/apps/doztime/app.js b/apps/doztime/app.js index 83f536018..38c5acbac 100644 --- a/apps/doztime/app.js +++ b/apps/doztime/app.js @@ -13,11 +13,11 @@ const g_height_t = 48; // height of time region const A1 = [30,30,30,30,31,31,31,31,31,31,30,30]; const B1 = [30,30,30,30,30,31,31,31,31,31,30,30]; const B2 = [30,30,30,30,31,31,31,31,31,30,30,30]; -const timeColour = "#f2f2f2"; +const timeColour = "#ffffff"; const dateColours = ["#ff0000","#ffa500","#ffff00","#00b800","#0000ff","#ff00ff","#ff0080"]; const calen10 = {"size":32,"pt0":[32-g_x_off,16],"step":[20,0],"dx":-4.5,"dy":-4.5}; // positioning for usual calendar line const calen7 = {"size":32,"pt0":[62-g_x_off,16],"step":[20,0],"dx":-4.5,"dy":-4.5}; // positioning for S-day calendar line -const time5 = {"size":48,"pt0":[64-g_x_off,24],"step":[30,0],"dx":-6.5,"dy":-6.5}; // positioning for lull time line; was 64 +const time5 = {"size":48,"pt0":[64-g_x_off,24],"step":[30,0],"dx":-6.5,"dy":-6.5}; // positioning for lull time line const time6 = {"size":48,"pt0":[48-g_x_off,24],"step":[30,0],"dx":-6.5,"dy":-6.5}; // positioning for twinkling time line const baseYear = 11584; const baseDate = Date(2020,11,21); // month values run from 0 to 11 @@ -30,8 +30,11 @@ let lastX = 999999999; let res = {}; //var last_time_log = 0; +var drawtime_timeout; + // Date and time graphics buffers var dateColour = "#ffffff"; // override later +var timeColour2 = timeColour; var g_d = Graphics.createArrayBuffer(g_width,g_height_d,1,{'msb':true}); var g_t = Graphics.createArrayBuffer(g_width,g_height_t,1,{'msb':true}); // Set screen mode and function to write graphics buffers @@ -46,7 +49,7 @@ g.flip = function() height:g_height_d, buffer:g_d.buffer }, g_x_off, g_y_off + g_y_off_d); - g.setColor(timeColour); + g.setColor(timeColour2); g.drawImage( { width:g_width, @@ -118,7 +121,7 @@ function formatDate(res,dateFormat){ return(yyyy+"-"+m+"-"+w+"-"+d); } -function writeDozTime(text,def,colour){ +function writeDozTime(text,def){ let pts = def.pts; let x=def.pt0[0]; let y=def.pt0[1]; @@ -133,6 +136,7 @@ function writeDozTime(text,def,colour){ } } function writeDozDate(text,def,colour){ + dateColour = colour; let pts = def.pts; let x=def.pt0[0]; @@ -177,10 +181,10 @@ function drawTime() { // Write to background buffers, then display on screen writeDozDate(date,calenDef,res.colour); - writeDozTime(time,timeDef,timeColour); + writeDozTime(time,timeDef); g.flip(); // Ready next interval - setTimeout(drawTime,wait); + drawtime_timeout = setTimeout(drawTime,wait); } else { @@ -196,22 +200,14 @@ function modeTime() timeActiveUntil = new Date(); timeActiveUntil.setDate(timeActiveUntil.getDate()); timeActiveUntil.setSeconds(timeActiveUntil.getSeconds()+15); - //Bangle.setLCDPower(true); - clearTimeout(); + if (typeof drawtime_timeout !== 'undefined') + { + clearTimeout(drawtime_timeout); + } drawTime(); } - Bangle.loadWidgets(); - -// Time-logging function -/*function logTime(label) -{ - var d = new Date(); - var t = d.getTime(); - var diff_test = t - last_time_log; - last_time_log = t; - console.log(label + " at time: " + t + ", since last: " + diff_test); -}*/ +Bangle.drawWidgets(); // Functions for weather mode - TODO function drawWeather() {} @@ -222,4 +218,20 @@ Bangle.on('twist', function() { modeTime(); }); -Bangle.drawWidgets(); +// Time fix with GPS +function fixTime() { + Bangle.on("GPS",function cb(g) { + Bangle.setGPSPower(0,"time"); + Bangle.removeListener("GPS",cb); + if (!g.time || (g.time.getFullYear()<2000) || + (g.time.getFullYear()>2200)) { + } else { + // We have a GPS time. Set time + setTime(g.time.getTime()/1000); + } + }); + Bangle.setGPSPower(1,"time"); + setTimeout(fixTime, 10*60*1000); // every 10 minutes +} +// Start time fixing with GPS on next 10 minute interval +setTimeout(fixTime, ((60-(new Date()).getMinutes()) % 10) * 60 * 1000); diff --git a/apps/dtlaunch/ChangeLog b/apps/dtlaunch/ChangeLog index 3df4ab63b..985321e91 100644 --- a/apps/dtlaunch/ChangeLog +++ b/apps/dtlaunch/ChangeLog @@ -1,4 +1,4 @@ 0.01: Initial version 0.02: Multiple pages 0.03: cycle thru pages - +0.04: reset to clock after 2 mins of inactivity diff --git a/apps/dtlaunch/app.js b/apps/dtlaunch/app.js index 329a96958..9bbf3e219 100644 --- a/apps/dtlaunch/app.js +++ b/apps/dtlaunch/app.js @@ -2,6 +2,20 @@ * */ +function wdog(handle,timeout){ + if(handle !== undefined){ + wdog.handle = handle; + wdog.timeout = timeout; + } + if(wdog.timer){ + clearTimeout(wdog.timer) + } + wdog.timer = setTimeout(wdog.handle,wdog.timeout) +} + +// reset after two minutes of inactivity +wdog(load,120000) + var s = require("Storage"); var apps = s.list(/\.info$/).map(app=>{var a=s.readJSON(app,1);return a&&{name:a.name,type:a.type,icon:a.icon,sortorder:a.sortorder,src:a.src};}).filter(app=>app && (app.type=="app" || app.type=="clock" || !app.type)); apps.sort((a,b)=>{ @@ -42,6 +56,7 @@ function drawPage(p){ } Bangle.on("swipe",(dir)=>{ + wdog() selected = 0; oldselected=-1; if (dir<0){ @@ -54,6 +69,7 @@ Bangle.on("swipe",(dir)=>{ }); function nextapp(d){ + wdog(); oldselected = selected; selected+=d; selected = selected<0?5:selected>5?0:selected; diff --git a/apps/fclock/ChangeLog b/apps/fclock/ChangeLog index a8f708a0a..30e049f69 100644 --- a/apps/fclock/ChangeLog +++ b/apps/fclock/ChangeLog @@ -1 +1,2 @@ 0.01: First published version of app +0.02: Move to Bangle.setUI to launcher support diff --git a/apps/fclock/fclock.app.js b/apps/fclock/fclock.app.js index 044cde71f..afa0c5e2d 100644 --- a/apps/fclock/fclock.app.js +++ b/apps/fclock/fclock.app.js @@ -1,206 +1,203 @@ -{ - var minutes; - var seconds; - var hours; - var date; - var first = true; - var locale = require('locale'); - var _12hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"] || false; +var minutes; +var seconds; +var hours; +var date; +var first = true; +var locale = require('locale'); +var _12hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"] || false; - //HR variables - var id = 0; - var grow = true; - var size=10; +//HR variables +var id = 0; +var grow = true; +var size=10; - //Screen dimensions - const screen = { - width: g.getWidth(), - height: g.getWidth(), - middle: g.getWidth() / 2, - center: g.getHeight() / 2, - }; +//Screen dimensions +const screen = { + width: g.getWidth(), + height: g.getWidth(), + middle: g.getWidth() / 2, + center: g.getHeight() / 2, +}; - // Ssettings - const settings = { - time: { - color: '#dddddd', - font: 'Vector', - size: 100, - middle: screen.middle, - center: screen.center, - }, - date: { - color: '#dddddd', - font: 'Vector', - size: 15, - middle: screen.height-17, // at bottom of screen - center: screen.center, - }, - circle: { - colormin: '#ffffff', - colorsec: '#ffffff', - width: 10, - middle: screen.middle, - center: screen.center, - height: screen.height - }, - hr: { - color: '#333333', - size: 20, - x: screen.center, - y: screen.middle + 65 - } - }; +// Ssettings +const settings = { + time: { + color: '#dddddd', + font: 'Vector', + size: 100, + middle: screen.middle, + center: screen.center, + }, + date: { + color: '#dddddd', + font: 'Vector', + size: 15, + middle: screen.height-17, // at bottom of screen + center: screen.center, + }, + circle: { + colormin: '#ffffff', + colorsec: '#ffffff', + width: 10, + middle: screen.middle, + center: screen.center, + height: screen.height + }, + hr: { + color: '#333333', + size: 20, + x: screen.center, + y: screen.middle + 65 + } +}; - const dateStr = function (date) { - return locale.date(new Date(), 1); - }; +const dateStr = function (date) { + return locale.date(new Date(), 1); +}; - const getFormated = function(val) { - if (val<10) { - val='0'+val; - } +const getFormated = function(val) { + if (val<10) { + val='0'+val; + } - return val; - }; + return val; +}; - const drawMin = function (sections, color) { - - g.setFontAlign(0, 0, 0); - g.setColor('#000000'); - g.setFont(settings.time.font, settings.time.size/2); - g.drawString(getFormated(sections-1), settings.time.center+50, settings.time.middle); - g.setColor(settings.time.color); - g.setFont(settings.time.font, settings.time.size/2); - g.drawString(getFormated(sections), settings.time.center+50, settings.time.middle); - }; +const drawMin = function (sections, color) { - const drawSec = function (sections, color) { - g.setFontAlign(0, 0, 0); - g.setColor('#000000'); - g.setFont(settings.time.font, settings.time.size/4); - g.drawString(getFormated(sections-1), settings.time.center+100, settings.time.middle); - g.setColor(settings.time.color); - g.setFont(settings.time.font, settings.time.size/4); - g.drawString(getFormated(sections), settings.time.center+100, settings.time.middle); - }; + g.setFontAlign(0, 0, 0); + g.setColor('#000000'); + g.setFont(settings.time.font, settings.time.size/2); + g.drawString(getFormated(sections-1), settings.time.center+50, settings.time.middle); + g.setColor(settings.time.color); + g.setFont(settings.time.font, settings.time.size/2); + g.drawString(getFormated(sections), settings.time.center+50, settings.time.middle); +}; - const drawClock = function () { +const drawSec = function (sections, color) { + g.setFontAlign(0, 0, 0); + g.setColor('#000000'); + g.setFont(settings.time.font, settings.time.size/4); + g.drawString(getFormated(sections-1), settings.time.center+100, settings.time.middle); + g.setColor(settings.time.color); + g.setFont(settings.time.font, settings.time.size/4); + g.drawString(getFormated(sections), settings.time.center+100, settings.time.middle); +}; - currentTime = new Date(); +const drawClock = function () { - //Get date as a string - date = dateStr(currentTime); - - if(seconds==59) { - g.clear(); - } + currentTime = new Date(); - // Update minutes when needed - if (minutes != currentTime.getMinutes()) { - minutes = currentTime.getMinutes(); - drawMin(minutes, settings.circle.colormin); - } + //Get date as a string + date = dateStr(currentTime); - //Update seconds when needed - if (seconds != currentTime.getSeconds()) { - seconds = currentTime.getSeconds(); - drawSec(seconds, settings.circle.colorsec); - } + if(seconds==59) { + g.clear(); + } - //Write the time as configured in the settings - hours = currentTime.getHours(); - if (_12hour && hours > 13) { - hours = hours - 12; - } + // Update minutes when needed + if (minutes != currentTime.getMinutes()) { + minutes = currentTime.getMinutes(); + drawMin(minutes, settings.circle.colormin); + } - var meridian; + //Update seconds when needed + if (seconds != currentTime.getSeconds()) { + seconds = currentTime.getSeconds(); + drawSec(seconds, settings.circle.colorsec); + } - if (typeof locale.meridian === "function") { - meridian = locale.meridian(new Date()); - } else { - meridian = ""; - } + //Write the time as configured in the settings + hours = currentTime.getHours(); + if (_12hour && hours > 13) { + hours = hours - 12; + } - var timestr; + var meridian; - if (meridian.length > 0 && _12hour) { - timestr = hours + " " + meridian; - } else { - timestr = hours; - } - g.setFontAlign(0, 0, 0); - g.setColor(settings.time.color); - g.setFont(settings.time.font, settings.time.size); - g.drawString(timestr, settings.time.center-40, settings.time.middle); + if (typeof locale.meridian === "function") { + meridian = locale.meridian(new Date()); + } else { + meridian = ""; + } - //Write the date as configured in the settings - g.setColor(settings.date.color); - g.setFont(settings.date.font, settings.date.size); - g.drawString(date, settings.date.center, settings.date.middle); - }; + var timestr; - //setInterval for HR visualisation - const newBeats = function (hr) { - if (id != 0) { - changeInterval(id, 6e3 / hr.bpm); - } else { - id = setInterval(drawHR, 6e3 / hr.bpm); - } - }; + if (meridian.length > 0 && _12hour) { + timestr = hours + " " + meridian; + } else { + timestr = hours; + } + g.setFontAlign(0, 0, 0); + g.setColor(settings.time.color); + g.setFont(settings.time.font, settings.time.size); + g.drawString(timestr, settings.time.center-40, settings.time.middle); - //visualize HR with circles pulsating - const drawHR = function () { - if (grow && size < settings.hr.size) { - size++; - } + //Write the date as configured in the settings + g.setColor(settings.date.color); + g.setFont(settings.date.font, settings.date.size); + g.drawString(date, settings.date.center, settings.date.middle); +}; - if (!grow && size > 3) { - size--; - } +//setInterval for HR visualisation +const newBeats = function (hr) { + if (id != 0) { + changeInterval(id, 6e3 / hr.bpm); + } else { + id = setInterval(drawHR, 6e3 / hr.bpm); + } +}; - if (size == settings.hr.size || size == 3) { - grow = !grow; - } +//visualize HR with circles pulsating +const drawHR = function () { + if (grow && size < settings.hr.size) { + size++; + } - if (grow) { - color = settings.hr.color; - g.setColor(color); - g.fillCircle(settings.hr.x, settings.hr.y, size); - } else { - color = "#000000"; - g.setColor(color); - g.drawCircle(settings.hr.x, settings.hr.y, size); - } - }; + if (!grow && size > 3) { + size--; + } - // clean app screen - g.clear(); - Bangle.loadWidgets(); - Bangle.drawWidgets(); + if (size == settings.hr.size || size == 3) { + grow = !grow; + } - //manage when things should be enabled and not - Bangle.on('lcdPower', function (on) { - if (on) { - Bangle.setHRMPower(1); - } else { - Bangle.setHRMPower(0); - } - }); + if (grow) { + color = settings.hr.color; + g.setColor(color); + g.fillCircle(settings.hr.x, settings.hr.y, size); + } else { + color = "#000000"; + g.setColor(color); + g.drawCircle(settings.hr.x, settings.hr.y, size); + } +}; - // refesh every second - setInterval(drawClock, 1E3); +// clean app screen +g.clear(); +Bangle.loadWidgets(); +Bangle.drawWidgets(); - //start HR monitor and update frequency of update - Bangle.setHRMPower(1); - Bangle.on('HRM', function (d) { - newBeats(d); - }); +//manage when things should be enabled and not +Bangle.on('lcdPower', function (on) { + if (on) { + Bangle.setHRMPower(1); + } else { + Bangle.setHRMPower(0); + } +}); - // draw now - drawClock(); +// refesh every second +setInterval(drawClock, 1E3); - // Show launcher when middle button pressed - setWatch(Bangle.showLauncher, BTN2, { repeat: false, edge: "falling" }); +//start HR monitor and update frequency of update +Bangle.setHRMPower(1); +Bangle.on('HRM', function (d) { + newBeats(d); +}); -} \ No newline at end of file +// draw now +drawClock(); + +// Show launcher when button pressed +Bangle.setUI("clock"); diff --git a/apps/files/ChangeLog b/apps/files/ChangeLog index b4037a733..1908f7e5c 100644 --- a/apps/files/ChangeLog +++ b/apps/files/ChangeLog @@ -2,4 +2,5 @@ 0.03: Add support for data files 0.04: Add functionality to sort apps manually or alphabetically ascending/descending. 0.05: Tweaks to help with memory usage -0.06: Reduce memory usage \ No newline at end of file +0.06: Reduce memory usage +0.07: Allow negative numbers when manual-sorting \ No newline at end of file diff --git a/apps/files/files.js b/apps/files/files.js index 9e6c97702..9ac6ebb35 100644 --- a/apps/files/files.js +++ b/apps/files/files.js @@ -180,7 +180,7 @@ function showSortAppsManually() { appList.reduce((menu, app) => { menu[app.name] = { value: app.sortorder || 0, - min: 0, + min: -appList.length, max: appList.length, step: 1, onchange: val => setSortorder(app, val) diff --git a/apps/gbridge/ChangeLog b/apps/gbridge/ChangeLog index f4837d60a..6e1c5b468 100644 --- a/apps/gbridge/ChangeLog +++ b/apps/gbridge/ChangeLog @@ -22,3 +22,4 @@ 0.20: Reduce memory usage 0.21: Fix HRM setting 0.22: Respect Quiet Mode +0.23: Allow notification dismiss to remove from phone too diff --git a/apps/gbridge/widget.js b/apps/gbridge/widget.js index b4ce71907..2c61e61fa 100644 --- a/apps/gbridge/widget.js +++ b/apps/gbridge/widget.js @@ -1,4 +1,7 @@ (() => { + // Current shown notification, saved for dismissing. + var currentNot = null; + // Music handling const state = { music: "stop", @@ -151,16 +154,23 @@ global.GB = (event) => { switch (event.t) { case "notify": - case "notify-": - if (event.t === "notify") { - require("notify").show(prettifyNotificationEvent(event)); - if (!(require('Storage').readJSON('setting.json',1)||{}).quiet) { - Bangle.buzz(); - } - } else { // notify- - require("notify").hide(event); + currentNot = prettifyNotificationEvent(event); + currentNot.onHide = function() { + // when notification hidden, remove from phone + gbSend({ t:"notify", n:"DISMISS", id:currentNot.id }); + }; + require("notify").show(currentNot); + if (!(require('Storage').readJSON('setting.json',1)||{}).quiet) { + Bangle.buzz(); } break; + case "notify-": + currentNot.t = "notify"; + currentNot.n = "DISMISS"; + gbSend(currentNot); + currentNot = null; + require("notify").hide(event); + break; case "musicinfo": state.musicInfo = event; updateMusic({on: false}); diff --git a/apps/gpsrec/ChangeLog b/apps/gpsrec/ChangeLog index 289fb38d6..8d13df000 100644 --- a/apps/gpsrec/ChangeLog +++ b/apps/gpsrec/ChangeLog @@ -24,3 +24,4 @@ 0.20: Add documentation to explain time needed for getting a time fix 0.21: Fix issue where a period of 1s recorded every 2s, 5s every 6s, and so on 0.22: Ensure Bangle.setGPSPower uses 'gpsrec' as a tag +0.23: Fix issue where tracks wouldn't record when running from OpenStMap if a period hadn't been set up first diff --git a/apps/gpsrec/widget.js b/apps/gpsrec/widget.js index 3d22373ec..6a47f04c5 100644 --- a/apps/gpsrec/widget.js +++ b/apps/gpsrec/widget.js @@ -4,7 +4,7 @@ var fixToggle = false; // toggles once for each reading var gpsTrack; // file for GPS track var gpsOn = false; - var lastFixTime; + var lastFixTime = Date.now(); // draw your widget function draw() { @@ -26,9 +26,7 @@ fixToggle = !fixToggle; WIDGETS["gpsrec"].draw(); if (hasFix) { - var period = 1000000; - if (lastFixTime!==undefined) - period = fix.time.getTime() - lastFixTime; + var period = fix.time.getTime() - lastFixTime; if (period+500 > settings.period*1000) { // round up lastFixTime = fix.time.getTime(); try { @@ -78,8 +76,7 @@ reload(); Bangle.drawWidgets(); // relayout all widgets },plotTrack:function(m) { // m=instance of openstmap module - settings = require("Storage").readJSON("gpsrec.json",1)||{}; - settings.file |= 0; + // if we're here, settings was already loaded var n = settings.file.toString(36); var f = require("Storage").open(".gpsrc"+n,"r"); var l = f.readLine(f); diff --git a/apps/hcclock/ChangeLog b/apps/hcclock/ChangeLog new file mode 100644 index 000000000..0ca30d066 --- /dev/null +++ b/apps/hcclock/ChangeLog @@ -0,0 +1,2 @@ +0.01: base code + diff --git a/apps/hcclock/README.md b/apps/hcclock/README.md new file mode 100644 index 000000000..328f1fe03 --- /dev/null +++ b/apps/hcclock/README.md @@ -0,0 +1,13 @@ +# Hi-Contrast Clock + +A High-contrast, black-on-white or white-on-black clock displaying huge pixel digits. It is purposed for being both elegant and readable in high luminosity environments. The goal is to keep the clock as simple and efficient as possible. + +## Usage + +* BTN 1 switches between the two modes : black-on-white or white-on-black +* That's it! + +## Issues and Requests + +If you have issues, feel free to contact me at https://github.com/peeweek/ + diff --git a/apps/hcclock/hcclock-icon.js b/apps/hcclock/hcclock-icon.js new file mode 100644 index 000000000..2486c6500 --- /dev/null +++ b/apps/hcclock/hcclock-icon.js @@ -0,0 +1 @@ +E.toArrayBuffer(atob("MDAB////////////////////////////////////////////////////////////////////////////////4AABgAAH4AABgAAH///5n//n///5n//n4AABn//n4AABn//n5///n//n5///n//n4AABgAAH4AABgAAH/////////////////////////015urF//3d+vZt//1V5uNV/////////////////5//5gAAH5//5gAAH5//5n//n5//5n//n4AABgAAH4AABgAAH///5n//n///5n//n///5gAAH///5gAAH////////////////////////////////////////////////////////////////////////////////")) diff --git a/apps/hcclock/hcclock-icon.png b/apps/hcclock/hcclock-icon.png new file mode 100644 index 000000000..5d5506249 Binary files /dev/null and b/apps/hcclock/hcclock-icon.png differ diff --git a/apps/hcclock/hcclock.app.js b/apps/hcclock/hcclock.app.js new file mode 100644 index 000000000..98abbc6f3 --- /dev/null +++ b/apps/hcclock/hcclock.app.js @@ -0,0 +1,210 @@ + +////////////////////////////////////////////////////// +// Numbers Rect order (left, top, right, bottom) +// Each number defines a set of rects to draw + +const numbers = +[ + [// Zero + [0, 0, 1, 0.2], + [0, 0.8, 1, 1], + [0, 0, 0.1, 1], + [0.9, 0, 1, 1] + ], + [// One + [0.7, 0, 1, 0.2], + [0.9, 0, 1, 1] + ], + [// Two + [0, 0, 1, 0.2], + [0, 0.4, 1, 0.6], + [0, 0.8, 1, 1], + [0, 0.4, 0.1, 1], + [0.9, 0, 1, 0.6] + ], + [// Three + [0, 0, 1, 0.2], + [0.5, 0.4, 1, 0.6], + [0, 0.8, 1, 1], + [0.9, 0, 1, 1] + ], + [// Four + [0, 0.4, 1, 0.6], + [0, 0, 0.1, 0.6], + [0.9, 0, 1, 1] + ], + [// Five + [0, 0, 1, 0.2], + [0, 0.4, 1, 0.6], + [0, 0.8, 1, 1], + [0, 0, 0.1, 0.6], + [0.9, 0.4, 1, 1] + ], + [// Six + [0, 0, 1, 0.2], + [0, 0.4, 1, 0.6], + [0, 0.8, 1, 1], + [0, 0, 0.1, 1.0], + [0.9, 0.4, 1, 1] + ], + [// Seven + [0.0, 0, 1, 0.2], + [0.9, 0, 1, 1] + ], + [// Eight + [0, 0, 1, 0.2], + [0, 0.4, 1, 0.6], + [0, 0.8, 1, 1], + [0, 0, 0.1, 1], + [0.9, 0, 1, 1] + ], + [// Nine + [0, 0, 1, 0.2], + [0, 0.4, 1, 0.6], + [0, 0.8, 1, 1], + [0, 0, 0.1, 0.6], + [0.9, 0, 1, 1] + ] +]; + +const months = [ "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" ]; + +const interval = 1000; // in ms +const top = 32; + +let ampm = (require("Storage").readJSON("setting.json",1)||{})["12hour"]; + +let bg = 255; +let fg = 0; + +let mins = -1; +let hour = -1; +let day = -1; + +function redraw() { + mins = -1; + hour = -1; + day = -1; + refresh(); +} + +function refresh() { + g.setColor(bg,bg,bg); + g.fillRect(0,45,240,210); + Bangle.drawWidgets(); + updateTime(); +} + +function updateTime() +{ + let now = new Date(); + let m = now.getMinutes(); + let h = now.getHours(); + let mo = now.getMonth(); + let y = now.getFullYear(); + let d = now.getDate(); + + if(h != hour) + { + hour = h; + g.setColor(bg,bg,bg); + g.fillRect(0,60,240,110); + g.setColor(fg,fg,fg); + if(ampm) + h = h%12; + drawDigits(60, h); + } + if(m != mins) + { + mins = m; + g.setColor(bg,bg,bg); + g.fillRect(0,145,240,195); + g.setColor(fg,fg,fg); + drawDigits(145, mins); + } + if(d != day) + { + day = d; + g.setFont("6x8", 2); + g.setFontAlign(0, -1, 0); + g.drawString(fmtDate(d,mo,y,hour), 120, 120); + } +} + +function drawDigits(x, value) +{ + if(!Bangle.isLCDOn()) // No need to draw when LCD Off + return; + + drawChar(Math.floor(value/10), 15, x, 115, x+50); + if(value%10 == 1) + drawChar(value%10, 55, x, 155, x+50); + else + drawChar(value%10, 125, x, 225, x+50); +} + +function drawChar(i, xMin, yMin, xMax, yMax) +{ + numbers[i].forEach(rect => { + r = place(rect, xMin, yMin, xMax, yMax); + g.setColor(fg,fg,fg); + g.fillRect(r[0], r[1], r[2], r[3]); + }); +} + +function place(array, xMin, yMin, xMax, yMax) +{ + return [ + lerp(xMin,xMax,array[0]), + lerp(yMin,yMax,array[1]), + lerp(xMin,xMax,array[2]), + lerp(yMin,yMax,array[3]) + ]; +} + +function lerp(a,b,t) +{ + return a + t*(b-a); +} + +function fmtDate(day,month,year,hour) +{ + if(ampm) + { + let ap = "(AM)"; + if(hour == 0 || hour > 12) + ap = "(PM)"; + return months[month] + " " + day + " " + year + " "+ ap; + } + else + return months[month] + ". " + day + " " + year; +} + +// Handles Flipping colors, then refreshes the UI +function flipColors() +{ + let t = bg; + bg = fg; + fg = t; + redraw(); +} + +////////////////////////////////////////// +// +// MAIN FUNCTION() +// + +// Initialize +g.clear(); +Bangle.loadWidgets(); +redraw(); + +// Define Refresh Interval +setInterval(updateTime, interval); + +// Handle Button Press +setWatch(flipColors, BTN1, true); +setWatch(Bangle.showLauncher, BTN2, false); + +// Handle redraw on LCD on / fullscreen notifications dismissed +Bangle.on('lcdPower', (on) => { if(on) redraw(); }); diff --git a/apps/hrm/ChangeLog b/apps/hrm/ChangeLog index 6cedf8f1b..d27886b15 100644 --- a/apps/hrm/ChangeLog +++ b/apps/hrm/ChangeLog @@ -2,3 +2,4 @@ 0.02: Use HRM data and calculations from Bangle.js (don't access hardware directly) 0.03: Fix timing issues, and use 1/2 scale to keep graph on screen 0.04: Update for new firmwares that have a 'HRM-raw' event +0.05: Tweaks for 'HRM-raw' handling diff --git a/apps/hrm/heartrate.js b/apps/hrm/heartrate.js index 09e8a826e..a6b8e791a 100644 --- a/apps/hrm/heartrate.js +++ b/apps/hrm/heartrate.js @@ -3,6 +3,8 @@ Bangle.setLCDTimeout(0); Bangle.setHRMPower(1); var hrmInfo, hrmOffset = 0; var hrmInterval; +var btm = g.getHeight()-1; + function onHRM(h) { if (counter!==undefined) { // the first time we're called remove @@ -26,7 +28,7 @@ function onHRM(h) { var px = g.getWidth()/2; g.setFontAlign(0,0); - g.clearRect(0,24,239,90); + g.clearRect(0,24,239,80); g.setFont("6x8").drawString("Confidence "+hrmInfo.confidence+"%", px, 75); var str = hrmInfo.bpm; g.setFontVector(40).drawString(str,px,45); @@ -38,17 +40,21 @@ Bangle.on('HRM', onHRM); /* On newer (2v10) firmwares we can subscribe to get HRM events as they happen */ Bangle.on('HRM-raw', function(v) { - var a = v.raw; hrmOffset++; if (hrmOffset>g.getWidth()) { hrmOffset=0; - g.clearRect(0,90,239,239); + g.clearRect(0,80,239,239); g.moveTo(-100,0); } - y = E.clip(170 - (v.raw*2),100,230); - g.setColor(1,1,1); - g.lineTo(hrmOffset, y); + y = E.clip(btm-v.filt/4,btm-10,btm); + g.setColor(1,0,0).fillRect(hrmOffset,btm, hrmOffset, y); + y = E.clip(170 - (v.raw/2),80,btm); + g.setColor(g.theme.fg).lineTo(hrmOffset, y); + if (counter !==undefined) { + counter = undefined; + g.clear(); + } }); // It takes 5 secs for us to get the first HRM event @@ -80,7 +86,6 @@ function readHRM() { var a = hrmInfo.raw[hrmOffset]; hrmOffset++; y = E.clip(170 - (a*2),100,230); - g.setColor(1,1,1); - g.lineTo(hrmOffset, y); + g.setColor(g.theme.fg).lineTo(hrmOffset, y); } } diff --git a/apps/imgclock/ChangeLog b/apps/imgclock/ChangeLog index 20906fb87..01a6a4248 100644 --- a/apps/imgclock/ChangeLog +++ b/apps/imgclock/ChangeLog @@ -5,4 +5,5 @@ Scaling for background images <240px wide 0.05: Fix memory/interval leak when LCD turns on 0.06: Support 12 hour time -0.07: Don't cut off wide date formats \ No newline at end of file +0.07: Don't cut off wide date formats +0.08: Use Bangle.setUI for button/launcher handling diff --git a/apps/imgclock/app.js b/apps/imgclock/app.js index 751647a69..0e4435638 100644 --- a/apps/imgclock/app.js +++ b/apps/imgclock/app.js @@ -84,5 +84,5 @@ Bangle.on('lcdPower',on=>{ draw(); } }); -// Show launcher when middle button pressed -setWatch(Bangle.showLauncher, BTN2, { repeat: false, edge: "falling" }); +// Show launcher when button pressed +Bangle.setUI("clock"); diff --git a/apps/impwclock/ChangeLog b/apps/impwclock/ChangeLog index c6974d37c..0592d4d04 100644 --- a/apps/impwclock/ChangeLog +++ b/apps/impwclock/ChangeLog @@ -1,2 +1,3 @@ 0.01: New App! 0.02: Stopped watchface from flashing every interval +0.03: Move to Bangle.setUI to launcher support diff --git a/apps/impwclock/clock-impword.js b/apps/impwclock/clock-impword.js index 94b92b778..5492eac15 100644 --- a/apps/impwclock/clock-impword.js +++ b/apps/impwclock/clock-impword.js @@ -47,10 +47,9 @@ const activeColorNight = 0xF800 /*red*/ ; const activeColorDay = 0xFFFF /* white */; var hidxPrev; +var showDigitalTime = false; function drawWordClock() { - - // get time var t = new Date(); var h = t.getHours(); @@ -141,7 +140,7 @@ function drawWordClock() { // Display digital time while button 1 is pressed g.clearRect(0, 215, 240, 240); - if (BTN1.read()){ + if (showDigitalTime){ g.setColor(activeColor); g.drawString(time, 120, 215); } @@ -158,8 +157,20 @@ Bangle.drawWidgets(); setInterval(drawWordClock, 1E4); drawWordClock(); -// Show digital time while top button is pressed -setWatch(drawWordClock, BTN1, {repeat:true,edge:"both"}); +// Show digital time while top button is pressed (if we have physical buttons) +if (global.BTN3) setWatch(function() { + showDigitalTime = BTN1.read(); + drawWordClock(); +}, BTN1, {repeat:true,edge:"both"}); -// Show launcher when middle button pressed -setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"}); +// If LCD pressed (on Bangle.js 2) draw digital time +Bangle.on('drag',e=>{ + var pressed = e.b!=0; + if (pressed!=showDigitalTime) { + showDigitalTime = pressed; + drawWordClock(); + } +}); + +// Show launcher when button pressed +Bangle.setUI("clock"); diff --git a/apps/isoclock/ChangeLog b/apps/isoclock/ChangeLog index cd3ceea5c..809091ce4 100644 --- a/apps/isoclock/ChangeLog +++ b/apps/isoclock/ChangeLog @@ -1 +1,2 @@ 0.01: Created app based on digiclock with some small tweaks. +0.02: Swap to Bangle.setUI for launcher/buttons diff --git a/apps/isoclock/isoclock.js b/apps/isoclock/isoclock.js index 5f63a1248..59f28e66e 100644 --- a/apps/isoclock/isoclock.js +++ b/apps/isoclock/isoclock.js @@ -1,82 +1,82 @@ //load fonts require("Font7x11Numeric7Seg").add(Graphics); -require("FontHaxorNarrow7x17").add(Graphics); +require("FontHaxorNarrow7x17").add(Graphics); //screen position -const X = 170; -const Y = 140; +const X = 170; +const Y = 140; function draw() { // Date Variables - var date = new Date(); - var h = date.getHours(); - var m = date.getMinutes(); - var day = date.getDay(); + var date = new Date(); + var h = date.getHours(); + var m = date.getMinutes(); + var day = date.getDay(); var month = date.getMonth()+1; - var dateNum = date.getDate(); - var year = date.getFullYear(); - var half = "AM"; - var time = ("0" + h).substr(-2) + ":" + ("0" + m).substr(-2); - - //convert day into string + var dateNum = date.getDate(); + var year = date.getFullYear(); + var half = "AM"; + var time = ("0" + h).substr(-2) + ":" + ("0" + m).substr(-2); + + //convert day into string switch (day) { case 0: - day = "Sunday"; - break; - + day = "Sunday"; + break; + case 1: - day = "Monday"; - break; - + day = "Monday"; + break; + case 2: - day = "Tuesday"; - break; - + day = "Tuesday"; + break; + case 3: - day = "Wednesday"; - break; - + day = "Wednesday"; + break; + case 4: - day = "Thursday"; - break; - + day = "Thursday"; + break; + case 5: - day = "Friday"; - break; - + day = "Friday"; + break; + case 6: - day = "Saturday"; - break; - + day = "Saturday"; + break; + default: - day = "ERROR"; - break; + day = "ERROR"; + break; } - + if (h > 12) { - half = "PM"; - h = h - 12; + half = "PM"; + h = h - 12; } //reset graphics - g.reset(); + g.reset(); //draw the time g.setFont("7x11Numeric7Seg", 5); g.setFontAlign(1,1); g.drawString(time, X+10, Y, true /*clear background*/); - g.setFont("7x11Numeric7Seg", 3); - g.drawString(("0"+date.getSeconds()).substr(-2), X+55, Y, true /*clear background*/); + g.setFont("7x11Numeric7Seg", 3); + g.drawString(("0"+date.getSeconds()).substr(-2), X+55, Y, true /*clear background*/); g.setFontAlign(0,1); g.setFont("HaxorNarrow7x17", 3); g.drawString(day, X-60, Y+53, true); - g.drawString(year+"-"+month+"-"+dateNum, X-55, Y-55, true); - - + g.drawString(year+"-"+month+"-"+dateNum, X-55, Y-55, true); + + } //clear screen at startup -g.clear(); +g.clear(); //draw immediatly -draw(); +draw(); var secondInterval = setInterval(draw, 1000); // Stop updates when LCD is off, restart when on @@ -92,4 +92,5 @@ Bangle.on('lcdPower',on=>{ Bangle.loadWidgets(); Bangle.drawWidgets(); -setWatch(Bangle.showLauncher, BTN2, {repeat : false, edge: "falling"}); +// Show launcher when button pressed +Bangle.setUI("clock"); diff --git a/apps/jbm8b_IT/ChangeLog b/apps/jbm8b_IT/ChangeLog new file mode 100644 index 000000000..b7b783924 --- /dev/null +++ b/apps/jbm8b_IT/ChangeLog @@ -0,0 +1 @@ +0.01: Cloning Magic 8 Ball and make it speak italian \ No newline at end of file diff --git a/apps/jbm8b_IT/app-icon.js b/apps/jbm8b_IT/app-icon.js new file mode 100644 index 000000000..09bf032a6 --- /dev/null +++ b/apps/jbm8b_IT/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwhBC/AGMrq2B1gAEwNWlYthq2s64AKGYIydFpoAEGLUrFqIADqxcXFqhiDFymBFy7GCF1owTRjCSVlYudeiGsF7/XlaNqSKBeP1mBwJxQMBReO1gaEleBMDBLN1hAC1hhBAoIwNCwQAGlZINqxvFGAIXOSBAXQN4hPBC5yQIVBxfBCAgvQSBC+NFAYRDMwJHOF654DqxkBYooALF6+sbIhkEF8Z3CRIWBR6AvXFAzvQF6wnIYQJgNd5AWNdoLoGBBAvPO5pfYH4IvUUwS/GVBzXBYCpHCq2s1mBDwKOWDwRgNPAwVVMCRLCwIABCZ6OJJSAATLxZgRACJeLAAMrFz9WFxiRgRpoADwIub1guQGDmsXhqSfRiL0G1jqkMRYxRwKLUGK2sFryVEq2B1gAEwNWFkIA/AH4A/AH4AQ")) \ No newline at end of file diff --git a/apps/jbm8b_IT/app.js b/apps/jbm8b_IT/app.js new file mode 100644 index 000000000..13ab3d39d --- /dev/null +++ b/apps/jbm8b_IT/app.js @@ -0,0 +1,79 @@ +const affirmative = [ + 'È certo.', + 'È decisamente\ncosì.', + 'Senza alcun\ndubbio.', + 'Sì,\nsenza dubbio.', + 'Ci puoi\ncontare.', + 'Da quanto\nvedo,\nsì.', + 'Molto\nprobabilmente.', + 'Le prospettive\nsono buone.', + 'Sì.', + 'I segni\nindicano\ndi sì.' +]; +const nonCommittal = [ + 'È difficile\ndirlo,\nprova di nuovo.', + 'Rifai la domanda\npiù tardi.', + 'Meglio non\nrisponderti\nadesso.', + 'Non posso\npredirlo ora.', + 'Concentrati e\nrifai la\ndomanda.' +]; +const negative = [ + 'Non ci\ncontare.', + 'La mia\nrisposta\nè no.', + 'Le mie\nfonti dicono\ndi no.', + 'Le prospettive\nnon sono\nbuone.', + 'È molto\ndubbio.' +]; +const title = 'Magic 8 Ball'; + +const answers = [affirmative, nonCommittal, negative]; + +function getRandomArbitrary(min, max) { + return Math.random() * (max - min) + min; +} + +function predict() { + // affirmative, negative or non-committal + let max = answers.length; + const a = Math.floor(getRandomArbitrary(0, max)); + // sets max compared to answer category + max = answers[a].length; + const b = Math.floor(getRandomArbitrary(0, max)); + // get the answer + const response = answers[a][b]; + return response; +} + +function draw(msg) { + // console.log(msg); + g.clear(); + E.showMessage(msg, title); +} + +function reply(button) { + const theButton = (typeof button === 'undefined' || isNaN(button)) ? 1 : button; + const timer = Math.floor(getRandomArbitrary(0, theButton) * 1000); + // Thinking... + draw('...'); + setTimeout('draw(predict());', timer); +} + +function ask() { + draw('Ponimi una\ndomanda\nSì/No e\ntocca lo\nschermo'); +} + +g.clear(); + +Bangle.loadWidgets(); +Bangle.drawWidgets(); +ask(); + +// Event Handlers + +Bangle.on('touch', (button) => reply(button)); + +setWatch(ask, BTN1, { repeat: true, edge: "falling" }); +setWatch(reply, BTN3, { repeat: true, edge: "falling" }); + +// Back to launcher +setWatch(Bangle.showLauncher, BTN2, { repeat: false, edge: "falling" }); \ No newline at end of file diff --git a/apps/jbm8b_IT/app.png b/apps/jbm8b_IT/app.png new file mode 100644 index 000000000..24c3013de Binary files /dev/null and b/apps/jbm8b_IT/app.png differ diff --git a/apps/largeclock/ChangeLog b/apps/largeclock/ChangeLog index d06cc9edf..6fa9297d8 100644 --- a/apps/largeclock/ChangeLog +++ b/apps/largeclock/ChangeLog @@ -5,3 +5,5 @@ 0.05: Add support for 12 hour time 0.06: Allow to disable BTN1 and BTN3 buttons 0.07: Don't clear all intervals during initialisation +0.08: Use Bangle.setUI for button/launcher handling +0.09: fix font size for latest firmwares diff --git a/apps/largeclock/largeclock.js b/apps/largeclock/largeclock.js index 24127ac15..6e1efeb4c 100644 --- a/apps/largeclock/largeclock.js +++ b/apps/largeclock/largeclock.js @@ -140,13 +140,13 @@ function drawTime(d) { g.clearRect(0, 24, moonX - moonR - 10, 239); g.setColor(1, 1, 1); g.setFontAlign(-1, -1); - g.setFont("Vector", 100); + g.setFont("Vector", 130); g.drawString(hours, 40, 24, true); g.setColor(1, 50, 1); - g.drawString(minutes, 40, 135, true); + g.drawString(minutes, 40, 130, true); g.setFont("Vector", 20); g.setRotation(3); - g.drawString(`${dow} ${day} ${month}`, 50, 10, true); + g.drawString(`${dow} ${day} ${month}`, 60, 10, true); g.drawString(year, is12Hour ? 46 : 75, 205, true); lastMinutes = minutes; } @@ -179,9 +179,9 @@ Bangle.on("lcdPower", function(on) { Bangle.setLCDMode(); -// Show launcher when middle button pressed -clearWatch(); -setWatch(Bangle.showLauncher, BTN2, { repeat: false, edge: "falling" }); +// Show launcher when button pressed +Bangle.setUI("clock"); + if (BTN1app) setWatch( function() { load(BTN1app); diff --git a/apps/launchb2/app.js b/apps/launchb2/app.js index 56f3fd696..183c09745 100644 --- a/apps/launchb2/app.js +++ b/apps/launchb2/app.js @@ -36,6 +36,7 @@ function drawMenu() { } g.clear(); drawMenu(); +g.flip(); // force an update now to make this snappier Bangle.on('drag',e=>{ var dy = e.dy; if (menuScroll - dy < 0) diff --git a/apps/lazyclock/ChangeLog b/apps/lazyclock/ChangeLog index 984d29869..a3f125786 100644 --- a/apps/lazyclock/ChangeLog +++ b/apps/lazyclock/ChangeLog @@ -1,2 +1,3 @@ 0.01: Launch app -0.02: Fix bug with the elusive one o'clock monster; Only change template when going over boundaries; Re-jig wording options \ No newline at end of file +0.02: Fix bug with the elusive one o'clock monster; Only change template when going over boundaries; Re-jig wording options +0.03: Use Bangle.setUI for launcher/buttons diff --git a/apps/lazyclock/lazyclock-app.js b/apps/lazyclock/lazyclock-app.js index 400e26ede..604448ce6 100644 --- a/apps/lazyclock/lazyclock-app.js +++ b/apps/lazyclock/lazyclock-app.js @@ -221,22 +221,13 @@ function addEvents() { } }); - setWatch(switchMode, BTN1, { - repeat: true, - edge: "falling" - }); - - setWatch(Bangle.showLauncher, BTN2, { - repeat: false, - edge: "falling" - }); - - setWatch(() => { - currentFormatter = null; - refreshTime(); - }, BTN3, { - repeat: true, - edge: "falling" + // Show launcher when button pressed + Bangle.setUI("clockupdown", btn=>{ + if (btn<0) switchMode(); + if (btn>0) { + currentFormatter = null; + refreshTime(); + } }); } @@ -245,9 +236,9 @@ function init() { startClock(); Bangle.loadWidgets(); - Bangle.drawWidgets(); + Bangle.drawWidgets(); addEvents(); } -init(); \ No newline at end of file +init(); diff --git a/apps/lifeclk/ChangeLog b/apps/lifeclk/ChangeLog index dfd8b8775..cdde84463 100644 --- a/apps/lifeclk/ChangeLog +++ b/apps/lifeclk/ChangeLog @@ -2,4 +2,5 @@ 0.02: Faster algorithm, hours and minutes are now displayable whenever, using the upper button 2021-01-14 0.03: Ah yes. Some people prefer the 12 hour system 2021-01-14 0.04: Fixed a bug, doesn't run while display's on now 2021-01-18 -0.05: Fixed a bug, doesn't count the time it was asleep when calculating the update time 2021-01-19 \ No newline at end of file +0.05: Fixed a bug, doesn't count the time it was asleep when calculating the update time 2021-01-19 +0.06: Use Bangle.set UI, change to unminified upload to ensure this works ok on Bangle.js diff --git a/apps/lifeclk/app.js b/apps/lifeclk/app.min.js similarity index 98% rename from apps/lifeclk/app.js rename to apps/lifeclk/app.min.js index 6064aa162..51a8ff93c 100644 --- a/apps/lifeclk/app.js +++ b/apps/lifeclk/app.min.js @@ -1,4 +1,4 @@ -Bangle.setLCDTimeout(30); +// Name as .min.js so we don't try and pretokenise (which stops Bangle.js running this somehow) const is12Hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"]; @@ -435,9 +435,11 @@ function showMinAgain(){ } function setButtons(){ - setWatch(showMinAgain, BTN1, {repeat:true,edge:"falling"}); - setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"}); - setWatch(regen, BTN3, {repeat:true,edge:"falling"}); + // Show launcher when button pressed + Bangle.setUI("clockupdown", btn=>{ + if (btn<0) showMinAgain(); + if (btn>0) regen(); + }); } let wentToSleepAt; diff --git a/apps/mclock/ChangeLog b/apps/mclock/ChangeLog index cca1b6e6b..05b422406 100644 --- a/apps/mclock/ChangeLog +++ b/apps/mclock/ChangeLog @@ -4,3 +4,4 @@ 0.05: Add "ram" keyword to allow 2v06 Espruino builds to cache function that needs to be fast Fix issue where first digit could get stuck going from "2x:xx" to " x:xx" (fix #365) 0.06: Support 12 hour time +0.07: Use Bangle.setUI for button/launcher handling diff --git a/apps/mclock/clock-morphing.js b/apps/mclock/clock-morphing.js index 15ab206b9..f1254860b 100644 --- a/apps/mclock/clock-morphing.js +++ b/apps/mclock/clock-morphing.js @@ -216,5 +216,5 @@ Bangle.drawWidgets(); timeInterval = setInterval(showTime, 1000); showTime(); -// Show launcher when middle button pressed -setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"}); +// Show launcher when button pressed +Bangle.setUI("clock"); diff --git a/apps/mclockplus/ChangeLog b/apps/mclockplus/ChangeLog index 835c33353..a1cecc698 100644 --- a/apps/mclockplus/ChangeLog +++ b/apps/mclockplus/ChangeLog @@ -1 +1,2 @@ -1.0: Created app +0.01: Created app +0.02: Use Bangle.setUI for button/launcher handling diff --git a/apps/mclockplus/mclockplus.app.js b/apps/mclockplus/mclockplus.app.js index 495e78f35..4c74ce1be 100644 --- a/apps/mclockplus/mclockplus.app.js +++ b/apps/mclockplus/mclockplus.app.js @@ -310,8 +310,8 @@ Bangle.drawWidgets(); timeInterval = setInterval(showTime, 1000); showTime(); -// Show launcher when middle button pressed -setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"}); +// Show launcher when button pressed +Bangle.setUI("clock"); // Start stopwatch when BTN3 is pressed setWatch(() => {swInterval=setInterval(stopWatch, 1000);stopWatch();}, BTN3, {repeat:false,edge:"falling"}); diff --git a/apps/miclock/ChangeLog b/apps/miclock/ChangeLog index f2e354bc1..e92bad2e3 100644 --- a/apps/miclock/ChangeLog +++ b/apps/miclock/ChangeLog @@ -1,3 +1,4 @@ 0.02: Modified for use with new bootloader and firmware 0.03: Localization 0.04: move jshint to the top +0.05: Use Bangle.setUI for button/launcher handling diff --git a/apps/miclock/clock-mixed.js b/apps/miclock/clock-mixed.js index 0bed137c6..b3d6bea8d 100644 --- a/apps/miclock/clock-mixed.js +++ b/apps/miclock/clock-mixed.js @@ -83,5 +83,5 @@ Bangle.drawWidgets(); setInterval(drawMixedClock, 5E3); drawMixedClock(); -// Show launcher when middle button pressed -setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"}); +// Show launcher when button pressed +Bangle.setUI("clock"); diff --git a/apps/minionclk/ChangeLog b/apps/minionclk/ChangeLog index 27dab7259..a8b6efc81 100644 --- a/apps/minionclk/ChangeLog +++ b/apps/minionclk/ChangeLog @@ -2,3 +2,4 @@ 0.02: Improved date readability, fixed drawing of widgets 0.03: Fixed rendering for Espruino v2.06 0.04: Fixed overlapped rendering of dates +0.05: Use Bangle.setUI for button/launcher handling diff --git a/apps/minionclk/app.js b/apps/minionclk/app.js index f0afbc45c..9648e3d89 100644 --- a/apps/minionclk/app.js +++ b/apps/minionclk/app.js @@ -81,4 +81,5 @@ Bangle.on('lcdPower', (on) => { Bangle.loadWidgets(); startDrawing(); -setWatch(Bangle.showLauncher, BTN2, { repeat: false, edge: 'falling' }); +// Show launcher when button pressed +Bangle.setUI("clock"); diff --git a/apps/mysticclock/ChangeLog b/apps/mysticclock/ChangeLog index 34fe53627..b486a29a1 100644 --- a/apps/mysticclock/ChangeLog +++ b/apps/mysticclock/ChangeLog @@ -1 +1,2 @@ 1.00: First published version. +1.01: Use Bangle.setUI for Launcher/buttons diff --git a/apps/mysticclock/mystic-clock-app.js b/apps/mysticclock/mystic-clock-app.js index 22c2c8982..2d95633fe 100644 --- a/apps/mysticclock/mystic-clock-app.js +++ b/apps/mysticclock/mystic-clock-app.js @@ -200,16 +200,9 @@ if (Bangle.isLCDOn()) { drawAll(); // draw immediately } -// show launcher when middle button pressed -setWatch(Bangle.showLauncher, BTN2, { repeat: false, edge: "falling" }); - -// rotate through info when the buttons are pressed -setWatch(() => { - nextInfo(); +// Show launcher when button pressed +Bangle.setUI("clockupdown", btn=>{ + if (btn<0) prevInfo(); + if (btn>0) nextInfo(); drawAll(); -}, BTN3, { repeat: true }); - -setWatch(() => { - prevInfo(); - drawAll(); -}, BTN1, { repeat: true }); +}); diff --git a/apps/ncrclk/ChangeLog b/apps/ncrclk/ChangeLog index 68209352b..31e5d42c8 100644 --- a/apps/ncrclk/ChangeLog +++ b/apps/ncrclk/ChangeLog @@ -1 +1,2 @@ 0.01: A copy of the analogimgclk to work for NodeConf Remote +0.02: Use Bangle.setUI for button/launcher handling diff --git a/apps/ncrclk/app.js b/apps/ncrclk/app.js index acf611b1d..16724fa5e 100644 --- a/apps/ncrclk/app.js +++ b/apps/ncrclk/app.js @@ -125,5 +125,5 @@ Bangle.loadWidgets(); Bangle.drawWidgets(); drawHands(true); -// Show launcher when middle button pressed -setWatch(Bangle.showLauncher, BTN2, { repeat: false, edge: "falling" }); +// Show launcher when button pressed +Bangle.setUI("clock"); diff --git a/apps/notify/ChangeLog b/apps/notify/ChangeLog index 2b7a4f990..291f32a5a 100644 --- a/apps/notify/ChangeLog +++ b/apps/notify/ChangeLog @@ -5,3 +5,4 @@ 0.06: Support background color 0.07: Auto-calculate height, and pad text down even when there's no title (so it stays on-screen) 0.08: Don't turn on screen during Quiet Mode +0.09: Add onHide callback diff --git a/apps/notify/notify.js b/apps/notify/notify.js index b5ef32d8b..68bc1a954 100644 --- a/apps/notify/notify.js +++ b/apps/notify/notify.js @@ -1,5 +1,6 @@ let pos = 0; let id = null; +let hideCallback = undefined; /** * Fit text into area, trying to insert newlines between words @@ -44,6 +45,7 @@ function fitWords(text,rows,width) { render : function(y) // function callback to render bgColor : int/string // optional background color (default black) titleBgColor : int/string // optional background color for title (default black) + onHide : function() // callback when notification is hidden } */ /* @@ -142,6 +144,8 @@ exports.show = function(options) { } anim(); Bangle.on("touch", exports.hide); + if (options.onHide) + hideCallback = options.onHide; }; /** @@ -152,6 +156,8 @@ exports.show = function(options) { exports.hide = function(options) { options = options||{}; if ("id" in options && options.id!==id) return; + if (hideCallback) hideCallback({id:id}); + hideCallback = undefined; id = null; Bangle.removeListener("touch", exports.hide); function anim() { diff --git a/apps/notifyfs/ChangeLog b/apps/notifyfs/ChangeLog index 974e138f7..ace651a0d 100644 --- a/apps/notifyfs/ChangeLog +++ b/apps/notifyfs/ChangeLog @@ -6,3 +6,5 @@ 0.06: Adjust position of notification src text and notifications without title 0.07: Support background color 0.08: Don't turn on screen during Quiet Mode +0.09: Add onHide callback +0.10: Ensure dismissing a notification dismissal doesn't enter launcher if in clock mode diff --git a/apps/notifyfs/notify.js b/apps/notifyfs/notify.js index 07801cedb..cd8d108d5 100644 --- a/apps/notifyfs/notify.js +++ b/apps/notifyfs/notify.js @@ -1,5 +1,6 @@ let oldg; let id = null; +let hideCallback = null; /** * See notify/notify.js @@ -40,6 +41,7 @@ function fitWords(text,rows,width) { render : function(y) // function callback to render bgColor : int/string // optional background color (default black) titleBgColor : int/string // optional background color for title (default black) + onHide : function() // callback when notification is hidden } */ exports.show = function(options) { @@ -65,7 +67,6 @@ exports.show = function(options) { if (options.title && options.src) { g.setColor(-1).setFontAlign(1, 1, 0).setFont("6x8", 2); // above drawing area, but we are fullscreen - print(options.src.substring(0, 10), w-23, y-4); g.drawString(options.src.substring(0, 10), w-16, y-4); } y += 30;h -= 30; @@ -94,6 +95,8 @@ exports.show = function(options) { Bangle.setLCDPower(1); // light up } Bangle.on("touch", exports.hide); + if (options.onHide) + hideCallback = options.onHide; // Create a fake graphics to hide draw attempts oldg = g; g = Graphics.createArrayBuffer(8,8,1); @@ -108,6 +111,8 @@ exports.show = function(options) { exports.hide = function(options) { options = options||{}; if ("id" in options && options.id!==id) return; + if (hideCallback) hideCallback({id:id}); + hideCallback = undefined; id = null; if (oldg) { g=oldg; @@ -122,7 +127,7 @@ exports.hide = function(options) { Bangle.setLCDPower(1); } // hack for E.showMenu/showAlert/showPrompt - can force a redraw by faking next/back - if (Bangle.btnWatches) { + if (!Bangle.CLOCK && Bangle.btnWatches && Bangle.btnWatches.length==3) { global["\xff"].watches[Bangle.btnWatches[0]].callback(); global["\xff"].watches[Bangle.btnWatches[1]].callback(); } diff --git a/apps/qmsched/ChangeLog b/apps/qmsched/ChangeLog index 7f837e50e..8bae1dba0 100644 --- a/apps/qmsched/ChangeLog +++ b/apps/qmsched/ChangeLog @@ -1 +1,3 @@ 0.01: First version +0.02: Add widget + diff --git a/apps/qmsched/README.md b/apps/qmsched/README.md index 1ccddbf8c..033014789 100644 --- a/apps/qmsched/README.md +++ b/apps/qmsched/README.md @@ -1,5 +1,9 @@ -# Quiet Mode Schedule +# Quiet Mode Schedule and Widget -Automatically turn Quiet Mode on or off at set times. +Automatically turn Quiet Mode on or off at set times, and display a widget when enabled. +### Edit Schedule: ![Main menu](screenshot_main.png) ![Edit Schedule menu](screenshot_edit.png) + +### Widget: +![Widget, quiet mode: silent](screenshot_widget_silent.png) ![Widget, quiet mode: alarms](screenshot_widget_alarms.png) diff --git a/apps/qmsched/app.js b/apps/qmsched/app.js index 7761be31c..105e09ea6 100644 --- a/apps/qmsched/app.js +++ b/apps/qmsched/app.js @@ -5,18 +5,25 @@ const modeNames = ["Off", "Alarms", "Silent"]; let scheds = require("Storage").readJSON("qmsched.json", 1); /*scheds = [ { hr : 6.5, // hours + minutes/60 - last : 0, // last day of the month we fired on - so we don't switch twice in one day! mode : 1, // quiet mode (0/1/2) } ];*/ if (!scheds) { // set default schedule on first load of app scheds = [ - {"hr": 8, "mode": 0, "last": 25}, - {"hr": 22, "mode": 1, "last": 25}, + {"hr": 8, "mode": 0}, + {"hr": 22, "mode": 1}, ]; require("Storage").writeJSON("qmsched.json", scheds); } +if (scheds.length && scheds.some(s => "last" in s)) { + // cleanup: remove "last" values (used by old versions) + scheds = scheds.map(s => { + delete s.last; + return s; + }); + require("Storage").writeJSON("qmsched.json", scheds); +} function formatTime(t) { const hrs = 0|t; @@ -24,11 +31,6 @@ function formatTime(t) { return (" "+hrs).substr(-2)+":"+("0"+mins).substr(-2); } -function getCurrentHr() { - const time = new Date(); - return time.getHours()+(time.getMinutes()/60)+(time.getSeconds()/3600); -} - function showMainMenu() { const menu = { "": {"title": "Quiet Mode"}, @@ -36,8 +38,8 @@ function showMainMenu() { value: (require("Storage").readJSON("setting.json", 1) || {}).quiet|0, format: v => modeNames[v], onchange: function(v) { - if (v<0) v = 2; - if (v>2) v = 0; + if (v<0) {v = 2;} + if (v>2) {v = 0;} require("qmsched").setMode(v); this.value = v; }, @@ -71,8 +73,8 @@ function showEditMenu(index) { "Hours": { value: hrs, onchange: function(v) { - if (v<0) v = 23; - if (v>23) v = 0; + if (v<0) {v = 23;} + if (v>23) {v = 0;} hrs = v; this.value = v; }, // no arrow fn -> preserve 'this' @@ -80,8 +82,8 @@ function showEditMenu(index) { "Minutes": { value: mins, onchange: function(v) { - if (v<0) v = 59; - if (v>59) v = 0; + if (v<0) {v = 59;} + if (v>59) {v = 0;} mins = v; this.value = v; }, // no arrow fn -> preserve 'this' @@ -90,24 +92,17 @@ function showEditMenu(index) { value: mode, format: v => modeNames[v], onchange: function(v) { - if (v<0) v = 2; - if (v>2) v = 0; + if (v<0) {v = 2;} + if (v>2) {v = 0;} mode = v; this.value = v; }, // no arrow fn -> preserve 'this' }, }; function getSched() { - const hr = hrs+(mins/60); - let day = 0; - // If schedule is for tomorrow not today (eg, in the past), set day - if (hr Save"] = function() { diff --git a/apps/qmsched/boot.js b/apps/qmsched/boot.js index 3c53ef3f7..2712cab30 100644 --- a/apps/qmsched/boot.js +++ b/apps/qmsched/boot.js @@ -1,24 +1,19 @@ // apply Quiet Mode schedules (function qm() { let scheds = require("Storage").readJSON("qmsched.json", 1) || []; - if (!scheds.length) return; - let next,idx; - scheds.forEach(function(s, i) { - if (!next || (s.hr+s.last*24)<(next.hr+next.last*24)) { - next = s; - idx = i; - } - }); + if (!scheds.length) { return;} const now = new Date(), - hr = now.getHours()+(now.getMinutes()/60)+(now.getSeconds()/3600); - let t = 3600000*(next.hr-hr); - if (next.last===now.getDate()) t += 86400000; + hr = now.getHours()+(now.getMinutes()/60)+(now.getSeconds()/3600); // current (decimal) hour + scheds.sort((a, b) => a.hr-b.hr); + const tday = scheds.filter(s => s.hr>hr), // scheduled for today + tmor = scheds.filter(s => s.hr<=hr); // scheduled for tomorrow + const next = tday.length ? tday[0] : tmor[0], + mode = next.mode; + let t = 3600000*(next.hr-hr); // timeout in milliseconds + if (t<0) {t += 86400000;} // scheduled for tomorrow: add a day /* update quiet mode at the correct time. */ - setTimeout(function() { - let scheds = require("Storage").readJSON("qmsched.json", 1) || []; - require("qmsched").setMode(scheds[idx].mode); - scheds[idx].last = (new Date()).getDate(); - require("Storage").writeJSON("qmsched.json", scheds); + setTimeout(() => { + require("qmsched").setMode(mode); qm(); // schedule next update }, t); })(); diff --git a/apps/qmsched/lib.js b/apps/qmsched/lib.js index 6cdf4f181..a3d36ed34 100644 --- a/apps/qmsched/lib.js +++ b/apps/qmsched/lib.js @@ -14,4 +14,5 @@ exports.setMode = function(mode) { if (s.brightness && s.brightness!=1) Bangle.setLCDBrightness(s.brightness); } if (mode && s.qmTimeout) Bangle.setLCDTimeout(s.qmTimeout); + if (typeof (WIDGETS)!=="undefined" && "qmsched" in WIDGETS) {WIDGETS["qmsched"].draw();} }; \ No newline at end of file diff --git a/apps/qmsched/screenshot_widget_alarms.png b/apps/qmsched/screenshot_widget_alarms.png new file mode 100644 index 000000000..52dbe2464 Binary files /dev/null and b/apps/qmsched/screenshot_widget_alarms.png differ diff --git a/apps/qmsched/screenshot_widget_silent.png b/apps/qmsched/screenshot_widget_silent.png new file mode 100644 index 000000000..38b133650 Binary files /dev/null and b/apps/qmsched/screenshot_widget_silent.png differ diff --git a/apps/qmsched/widget.js b/apps/qmsched/widget.js new file mode 100644 index 000000000..c602288ad --- /dev/null +++ b/apps/qmsched/widget.js @@ -0,0 +1,32 @@ +WIDGETS["qmsched"] = { + area: "tl", width: 24, draw: function() { + const mode = (require("Storage").readJSON("setting.json", 1) || {}).quiet|0; + if (mode===0) { // Off + if (this.width!==0) { + this.width = 0; + Bangle.drawWidgets(); + } + return; + } + // not Off: make sure width is correct + if (this.width!==24) { + this.width = 24; + Bangle.drawWidgets(); + return; // drawWidgets will call draw again + } + let x = this.x, y = this.y; + g.clearRect(x, y, x+23, y+23); + // quiet mode: draw dim red one-way-street sign + x = this.x+11;y = this.y+11; // center of widget + g.setColor(0.8, 0, 0).fillCircle(x, y, 8); + g.setColor(g.theme.bg).fillRect(x-6, y-2, x+6, y+2); + if (mode>1) {return;} // no alarms + // alarms still on: draw alarm icon in bottom-right corner + x = this.x+18;y = this.y+17; // center of alarm + g.setColor(1, 1, 0) + .fillCircle(x, y, 3) // alarm body + .fillRect(x-5, y+2, x+5, y+3) // bottom ridge + .fillRect(x-1, y-5, x+1, y+5).drawLine(x, y-6, x, y+6) // top+bottom + .drawLine(x+5, y-3, x+3, y-5).drawLine(x-5, y-3, x-3, y-5); // wriggles + }, +}; \ No newline at end of file diff --git a/apps/rclock/ChangeLog b/apps/rclock/ChangeLog index 61bf493c1..915fbc5d7 100644 --- a/apps/rclock/ChangeLog +++ b/apps/rclock/ChangeLog @@ -2,4 +2,6 @@ 0.02: Added support for locale and 12H clock 0.03: Added HR indication to clock 0.04: Update font size and alignment -0.05: Changes which circle show minutes and seconds \ No newline at end of file +0.05: Changes which circle show minutes and seconds +0.06: Avoid function wrapper, use setUI for launcher + Clock face smaller so no longer breaks widgets diff --git a/apps/rclock/rclock.app.js b/apps/rclock/rclock.app.js index ceaffe910..9c219ab3d 100644 --- a/apps/rclock/rclock.app.js +++ b/apps/rclock/rclock.app.js @@ -1,229 +1,226 @@ -{ - var minutes; - var seconds; - var hours; - var date; - var first = true; - var locale = require('locale'); - var _12hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"] || false; +var minutes; +var seconds; +var hours; +var date; +var first = true; +var locale = require('locale'); +var _12hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"] || false; - //HR variables - var id = 0; - var grow = true; - var size=10; +//HR variables +var id = 0; +var grow = true; +var size=10; - //Screen dimensions - const screen = { - width: g.getWidth(), - height: g.getWidth(), - middle: g.getWidth() / 2, - center: g.getHeight() / 2, - }; +//Screen dimensions +const screen = { + width: g.getWidth(), + height: g.getWidth(), + middle: g.getWidth() / 2, + center: g.getHeight() / 2, +}; - // Ssettings - const settings = { - time: { - color: '#D6ED17', - font: 'Vector', - size: 60, - middle: screen.middle, - center: screen.center, - }, - date: { - color: '#D6ED17', - font: 'Vector', - size: 15, - middle: screen.height-17, // at bottom of screen - center: screen.center, - }, - circle: { - colormin: '#ffffff', - colorsec: '#ffffff', - width: 10, - middle: screen.middle, - center: screen.center, - height: screen.height - }, - hr: { - color: '#333333', - size: 10, - x: screen.center, - y: screen.middle + 45 +// Settings +const settings = { + time: { + color: '#D6ED17', + font: 'Vector', + size: 60, + middle: screen.middle, + center: screen.center, + }, + date: { + color: '#D6ED17', + font: 'Vector', + size: 15, + middle: screen.height-17, // at bottom of screen + center: screen.center, + }, + circle: { + colormin: '#ffffff', + colorsec: '#ffffff', + width: 10, + middle: screen.middle, + center: screen.center, + height: screen.height-24 + }, + hr: { + color: '#333333', + size: 10, + x: screen.center, + y: screen.middle + 45 + } +}; + +const dateStr = function (date) { + return locale.date(new Date(), 1); +}; + +const getArcXY = function (centerX, centerY, radius, angle) { + var s, r = []; + s = 2 * Math.PI * angle / 360; + r.push(centerX + Math.round(Math.cos(s) * radius)); + r.push(centerY + Math.round(Math.sin(s) * radius)); + + return r; +}; + +const drawMinArc = function (sections, color) { + g.setColor(color); + var rad = (settings.circle.height / 2) - 40; + var r1 = getArcXY(settings.circle.middle, settings.circle.center, rad, sections * (360 / 60) - 90); + //g.setPixel(r[0],r[1]); + var r2 = getArcXY(settings.circle.middle, settings.circle.center, rad - settings.circle.width, sections * (360 / 60) - 90); + //g.setPixel(r[0],r[1]); + g.drawLine(r1[0], r1[1], r2[0], r2[1]); + g.setColor('#333333'); + g.drawCircle(settings.circle.middle, settings.circle.center, rad - settings.circle.width - 4); +}; + +const drawSecArc = function (sections, color) { + g.setColor(color); + var rad = (settings.circle.height / 2) - 20; + var r1 = getArcXY(settings.circle.middle, settings.circle.center, rad, sections * (360 / 60) - 90); + //g.setPixel(r[0],r[1]); + var r2 = getArcXY(settings.circle.middle, settings.circle.center, rad - settings.circle.width, sections * (360 / 60) - 90); + //g.setPixel(r[0],r[1]); + g.drawLine(r1[0], r1[1], r2[0], r2[1]); + g.setColor('#333333'); + g.drawCircle(settings.circle.middle, settings.circle.center, rad - settings.circle.width - 4); +}; + +const drawClock = function () { + g.reset(); + currentTime = new Date(); + + //Set to initial time when started + if (first == true) { + minutes = currentTime.getMinutes(); + seconds = currentTime.getSeconds(); + for (count = 0; count <= minutes; count++) { + drawMinArc(count, settings.circle.colormin); } - }; - const dateStr = function (date) { - return locale.date(new Date(), 1); - }; + for (count = 0; count <= seconds; count++) { + drawSecArc(count, settings.circle.colorsec); + } + first = false; + } - const getArcXY = function (centerX, centerY, radius, angle) { - var s, r = []; - s = 2 * Math.PI * angle / 360; - r.push(centerX + Math.round(Math.cos(s) * radius)); - r.push(centerY + Math.round(Math.sin(s) * radius)); + // Reset + if (seconds == 59) { + g.setColor('#000000'); + g.fillCircle(settings.circle.middle, settings.circle.center, (settings.circle.height / 2)); + for (count = 0; count <= minutes; count++) { + drawMinArc(count, settings.circle.colormin); + } + } - return r; - }; + //Get date as a string + date = dateStr(currentTime); - const drawMinArc = function (sections, color) { + // Update minutes when needed + if (minutes != currentTime.getMinutes()) { + minutes = currentTime.getMinutes(); + drawMinArc(minutes, settings.circle.colormin); + } + + //Update seconds when needed + if (seconds != currentTime.getSeconds()) { + seconds = currentTime.getSeconds(); + drawSecArc(seconds, settings.circle.colorsec); + } + + //Write the time as configured in the settings + hours = currentTime.getHours(); + if (_12hour && hours > 13) { + hours = hours - 12; + } + + var meridian; + + if (typeof locale.meridian === "function") { + meridian = locale.meridian(new Date()); + } else { + meridian = ""; + } + + var timestr; + + if (meridian.length > 0 && _12hour) { + timestr = hours + " " + meridian; + } else { + timestr = hours; + } + g.setFontAlign(0, 0, 0); + g.setColor(settings.time.color); + g.setFont(settings.time.font, settings.time.size); + g.drawString(timestr, settings.time.center, settings.time.middle); + + //Write the date as configured in the settings + g.setColor(settings.date.color); + g.setFont(settings.date.font, settings.date.size); + g.drawString(date, settings.date.center, settings.date.middle); +}; + +//setInterval for HR visualisation +const newBeats = function (hr) { + if (id != 0) { + changeInterval(id, 6e3 / hr.bpm); + } else { + id = setInterval(drawHR, 6e3 / hr.bpm); + } +}; + +//visualize HR with circles pulsating +const drawHR = function () { + if (grow && size < settings.hr.size) { + size++; + } + + if (!grow && size > 3) { + size--; + } + + if (size == settings.hr.size || size == 3) { + grow = !grow; + } + + if (grow) { + color = settings.hr.color; g.setColor(color); - rad = (settings.circle.height / 2) - 40; - r1 = getArcXY(settings.circle.middle, settings.circle.center, rad, sections * (360 / 60) - 90); - //g.setPixel(r[0],r[1]); - r2 = getArcXY(settings.circle.middle, settings.circle.center, rad - settings.circle.width, sections * (360 / 60) - 90); - //g.setPixel(r[0],r[1]); - g.drawLine(r1[0], r1[1], r2[0], r2[1]); - g.setColor('#333333'); - g.drawCircle(settings.circle.middle, settings.circle.center, rad - settings.circle.width - 4); - }; - - const drawSecArc = function (sections, color) { + g.fillCircle(settings.hr.x, settings.hr.y, size); + } else { + color = "#000000"; g.setColor(color); - rad = (settings.circle.height / 2) - 20; - r1 = getArcXY(settings.circle.middle, settings.circle.center, rad, sections * (360 / 60) - 90); - //g.setPixel(r[0],r[1]); - r2 = getArcXY(settings.circle.middle, settings.circle.center, rad - settings.circle.width, sections * (360 / 60) - 90); - //g.setPixel(r[0],r[1]); - g.drawLine(r1[0], r1[1], r2[0], r2[1]); - g.setColor('#333333'); - g.drawCircle(settings.circle.middle, settings.circle.center, rad - settings.circle.width - 4); - }; + g.drawCircle(settings.hr.x, settings.hr.y, size); + } +}; - const drawClock = function () { +// clean app screen +g.clear(); +Bangle.loadWidgets(); +Bangle.drawWidgets(); - currentTime = new Date(); +//manage when things should be enabled and not +Bangle.on('lcdPower', function (on) { + if (on) { + Bangle.setHRMPower(1); + } else { + Bangle.setHRMPower(0); + } +}); - //Set to initial time when started - if (first == true) { - minutes = currentTime.getMinutes(); - seconds = currentTime.getSeconds(); - for (count = 0; count <= minutes; count++) { - drawMinArc(count, settings.circle.colormin); - } +// refesh every second +setInterval(drawClock, 1E3); - for (count = 0; count <= seconds; count++) { - drawSecArc(count, settings.circle.colorsec); - } - first = false; - } +//start HR monitor and update frequency of update +Bangle.setHRMPower(1); +Bangle.on('HRM', function (d) { + newBeats(d); +}); - // Reset - if (seconds == 59) { - g.setColor('#000000'); - g.fillCircle(settings.circle.middle, settings.circle.center, (settings.circle.height / 2)); - for (count = 0; count <= minutes; count++) { - drawMinArc(count, settings.circle.colormin); - } - } +// draw now +drawClock(); - //Get date as a string - date = dateStr(currentTime); - - // Update minutes when needed - if (minutes != currentTime.getMinutes()) { - minutes = currentTime.getMinutes(); - drawMinArc(minutes, settings.circle.colormin); - } - - //Update seconds when needed - if (seconds != currentTime.getSeconds()) { - seconds = currentTime.getSeconds(); - drawSecArc(seconds, settings.circle.colorsec); - } - - //Write the time as configured in the settings - hours = currentTime.getHours(); - if (_12hour && hours > 13) { - hours = hours - 12; - } - - var meridian; - - if (typeof locale.meridian === "function") { - meridian = locale.meridian(new Date()); - } else { - meridian = ""; - } - - var timestr; - - if (meridian.length > 0 && _12hour) { - timestr = hours + " " + meridian; - } else { - timestr = hours; - } - g.setFontAlign(0, 0, 0); - g.setColor(settings.time.color); - g.setFont(settings.time.font, settings.time.size); - g.drawString(timestr, settings.time.center, settings.time.middle); - - //Write the date as configured in the settings - g.setColor(settings.date.color); - g.setFont(settings.date.font, settings.date.size); - g.drawString(date, settings.date.center, settings.date.middle); - }; - - //setInterval for HR visualisation - const newBeats = function (hr) { - if (id != 0) { - changeInterval(id, 6e3 / hr.bpm); - } else { - id = setInterval(drawHR, 6e3 / hr.bpm); - } - }; - - //visualize HR with circles pulsating - const drawHR = function () { - if (grow && size < settings.hr.size) { - size++; - } - - if (!grow && size > 3) { - size--; - } - - if (size == settings.hr.size || size == 3) { - grow = !grow; - } - - if (grow) { - color = settings.hr.color; - g.setColor(color); - g.fillCircle(settings.hr.x, settings.hr.y, size); - } else { - color = "#000000"; - g.setColor(color); - g.drawCircle(settings.hr.x, settings.hr.y, size); - } - }; - - // clean app screen - g.clear(); - Bangle.loadWidgets(); - Bangle.drawWidgets(); - - //manage when things should be enabled and not - Bangle.on('lcdPower', function (on) { - if (on) { - Bangle.setHRMPower(1); - } else { - Bangle.setHRMPower(0); - } - }); - - // refesh every second - setInterval(drawClock, 1E3); - - //start HR monitor and update frequency of update - Bangle.setHRMPower(1); - Bangle.on('HRM', function (d) { - newBeats(d); - }); - - // draw now - drawClock(); - - // Show launcher when middle button pressed - setWatch(Bangle.showLauncher, BTN2, { repeat: false, edge: "falling" }); - -} \ No newline at end of file +// Show launcher when button pressed +Bangle.setUI("clock"); diff --git a/apps/rtorch/ChangeLog b/apps/rtorch/ChangeLog new file mode 100644 index 000000000..06f10fe08 --- /dev/null +++ b/apps/rtorch/ChangeLog @@ -0,0 +1 @@ +0.01: Cloning torch and making it red :D diff --git a/apps/rtorch/app-icon.js b/apps/rtorch/app-icon.js new file mode 100644 index 000000000..ff1265c9b --- /dev/null +++ b/apps/rtorch/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4UA///oP4gH+t9TCQ1VAAYLpgILunoLK/4LJgf/6oLIh//+oLK/oLIhapBBZEqBYIwDBYu/GAgLE1WvGAgLF1YwEBQcC1WqGAgLGGAgLDhQLBGAdQBYwwCBQgLDGASlFlQLC3/8BYoIBGAXwBQkCFgILC4AuFBYeAFw2v/wLBBQqNCBYOgBQp1B1/qCw5dDFoxdEBQwuBAAOoBQykCHI4uXgZPBFxEP/QuJn5/CFw7DBLpILB9QuHEYP//QuHHYP//wuHKYL0HGAoLJn/8BZMP+ALJgfABRA=")) \ No newline at end of file diff --git a/apps/rtorch/app.js b/apps/rtorch/app.js new file mode 100644 index 000000000..4f6b1d6f7 --- /dev/null +++ b/apps/rtorch/app.js @@ -0,0 +1,22 @@ +Bangle.setLCDPower(1); +Bangle.setLCDTimeout(0); +g.reset(); +c = 1; +function setColor(delta){ + c+=delta; + c = Math.max(c,0); + c = Math.min(c,2); + if (c<1){ + g.setColor(c,0,0); + }else{ + g.setColor(1,c-1,c-1); + } + g.fillRect(0,0,g.getWidth(),g.getHeight()); +} +setColor(0) +// BTN1 light up toward white +// BTN3 light down to red +// BTN2 to reset +setWatch(()=>setColor(0.1), BTN1, { repeat:true, edge:"rising", debounce: 50 }); +setWatch(()=>load(), BTN2); +setWatch(()=>setColor(-0.1), BTN3, { repeat:true, edge:"rising", debounce: 50 }); diff --git a/apps/rtorch/app.png b/apps/rtorch/app.png new file mode 100644 index 000000000..17b0f3efc Binary files /dev/null and b/apps/rtorch/app.png differ diff --git a/apps/rtorch/widget.js b/apps/rtorch/widget.js new file mode 100644 index 000000000..89009266d --- /dev/null +++ b/apps/rtorch/widget.js @@ -0,0 +1,26 @@ +(function() { + var clickTimes = []; + var clickPattern = ""; + var TAPS = 4; // number of taps + var PERIOD = 1; // seconds + + // 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) return; + clickTimes=[getTime()]; + clickPattern="x"; + }); + function tap(e,c) { + clickPattern = clickPattern.substr(-3)+c; + while (clickTimes.length>=TAPS) clickTimes.shift(); + clickTimes.push(e.time); + var clickPeriod = e.time-clickTimes[0]; + if (clickPeriod + + + + + +

Please select watch locations

+ + + + + + +
EnabledName
+ +

Click

+ + + + + + diff --git a/apps/solarclock/solar_clock-icon.js b/apps/solarclock/solar_clock-icon.js new file mode 100644 index 000000000..2dd3ac0e6 --- /dev/null +++ b/apps/solarclock/solar_clock-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("lEowkG/4Av+YTS+svCaMtoYTRp3iAYMyiZONqlFn/ypvSCBMikYDBmcz//x7wTKlvUn4HEjpTKmnkOgvyPZX0oRPBkVEkQsFMA4fBkne93dqSJNLwPuAAJBCFwI+JjoSCAAJ2Bmver4SH+QmDFASiB9onF+adBmgSEAAMv+dECQtEOoMtEwvuT4LdF+C9Cl3u71ECQICB6aIHroJBp3uoICB9oCBbZHyqYTCAArvJn/zCY/iY5QTJ+bgHCYp2BHYX1JAIAGCYdNOwJ3Cno+GDQKfD6gYCp4JBZYJ3E6jmBJwrYCmtFCYk0BQKjBRREzHQkt6gaBmVN7oAB6gmBOo8ykYeDkQABEQK+CUBIHGmjIJ//xD40kZBX09qFB+czJgPyqITK91DQoNEoR1HAAny8hyB+XeYoIALN4fzrwrBABZ3EmorBACCQHAHgA=")) diff --git a/apps/solarclock/solar_clock.js b/apps/solarclock/solar_clock.js new file mode 100644 index 000000000..924090f6e --- /dev/null +++ b/apps/solarclock/solar_clock.js @@ -0,0 +1,559 @@ +/** + * Adrian Kirk 2021-07 + * + * Solar Clock + * + * Using your current or chosen location the solar watch face shows the Sun's sky position, + * time and date. Also allows you to wind backwards and forwards in time to see the sun's position + **/ + +const DateUtils = require("solar_date_utils.js"); +const Math2 = require("solar_math_utils.js"); +const GraphicUtils = require("solar_graphic_utils.js"); +const Colors = require("solar_colors.js"); +const LocationUtils = require("solar_location.js"); +const Locale = require('locale'); + +/** + * The screen info data structure stores all of the + * relevant screen information, such as the sun's position + * where the sunrise line is, etc + */ +var screen_info = { + screen_width : g.getWidth(), + screen_start_x : 0, + screen_centre_x: g.getWidth()/2, + screen_height : (g.getHeight()-100), + screen_start_y : 100, + screen_centre_y : 90 + (g.getHeight()-100)/2, + screen_bg_color : Colors.BLACK, + sun_radius: 8, + sun_x : null, + sun_y : null, + sunrise_y : null, +} +// we set up the image buffer with which we draw the sun with +const img_width=40; +const img_height=30; +var img_buffer = Graphics.createArrayBuffer(img_width,img_height,8); +var img = {width:img_width,height:img_height,bpp:8,transparent:0,buffer:img_buffer.buffer}; +/** + * We use an image buffer to do the sun animation. + * We pass around the img_info structure with the + * image buffer data + */ +var img_info = { + x: null, + y: null, + img: img, + img_buffer: img_buffer +} +const COSINE_COLOUR= Colors.GREY; +const HORIZON_COLOUR = Colors.GREY; +const SolarController = require("solar_controller.js"); +var controller = new SolarController(); +var curr_mode = null; +var draw_full_cosine = true; + +// The draw sun function is responsible for +// drawing the whole sun animation which includes the +// sun, the cosine curve and the sunrise line. +function draw_sun(now, day_info) { + + var now_fraction = (now.getTime() - day_info.day_start.getTime())/DateUtils.DAY_MILLIS; + var now_x = now_fraction * screen_info.screen_width; + if(screen_info.sun_x != null && Math.abs(now_x- screen_info.sun_x) < 1){ + console.log("no sun movement now_x:" + now_x + " screen sun_x:" + screen_info.sun_x); + return false; + } + // now calculate thew new sun coordinates + var now_radians = Math2.TWO_PI *(now_x - screen_info.screen_centre_x)/screen_info.screen_width; + var now_y = screen_info.screen_centre_y - (screen_info.screen_height * Math.cos(now_radians) / 2); + if(screen_info.sun_x != null && Math.abs(now_x - screen_info.sun_x) > 5.0){ + clear_sun(); + } else if(screen_info.sun_y != null && Math.abs(now_y - screen_info.sun_y) > 5.0){ + clear_sun(); + } + // update the screen info with the new sun info + screen_info.sun_x = now_x; + screen_info.sun_y = now_y; + + if(draw_full_cosine){ + //console.log("drawing full cosine"); + GraphicUtils.draw_cosine(screen_info.screen_start_x, + screen_info.screen_width, + COSINE_COLOUR, + screen_info); + draw_full_cosine = false; + } + if(curr_mode == null) { + GraphicUtils.draw_sunrise_line(HORIZON_COLOUR, day_info, screen_info); + } + // decide on the new sun drawing mode and draw + curr_mode = controller.mode(now,day_info,screen_info); + img_info.img_buffer.clear(); + GraphicUtils.set_color(screen_info.screen_bg_color, img_info.img_buffer); + img_info.img_buffer.fillRect(0,0,img_width, img_height); + img_info.x = screen_info.sun_x - img_info.img.width/2; + img_info.y = screen_info.sun_y - img_info.img.height/2; + + var cosine_dist = screen_info.sun_radius/Math.sqrt(2); + GraphicUtils.draw_cosine(img_info.x, + screen_info.sun_x - cosine_dist, + COSINE_COLOUR, + screen_info, + img_info); + GraphicUtils.draw_cosine(screen_info.sun_x + cosine_dist, + screen_info.sun_x + img_width, + COSINE_COLOUR, + screen_info, + img_info); + + curr_mode.draw(now,day_info,screen_info,img_info); + + var sunrise_dist = Math.abs(screen_info.sunrise_y-screen_info.sun_y); + if( sunrise_dist <= img_height) { + GraphicUtils.draw_sunrise_line(HORIZON_COLOUR, day_info, screen_info,img_info); + } else if(sunrise_dist <= img_height*2.5) { + GraphicUtils.draw_sunrise_line(HORIZON_COLOUR, day_info, screen_info); + } + // we draw a blank where the image is going to be drawn to clear out the area + GraphicUtils.set_color(screen_info.screen_bg_color); + g.fillRect(img_info.x,img_info.y-2,img_info.x+img_width,img_info.y + img_height + 2); + g.drawImage(img,img_info.x,img_info.y); + // paint the cosine curve back to the normal color where it just came from + GraphicUtils.draw_cosine(img_info.x - 3, + img_info.x, + COSINE_COLOUR, + screen_info); + GraphicUtils.draw_cosine(img_info.x + img_width, + img_info.x + img_width + 3, + COSINE_COLOUR, + screen_info); + return true; +} + +// clear sun is called when there is a large change in the sun's +// location, such as location change. +function clear_sun(){ + if(img_info.x != null && img_info.y != null) { + GraphicUtils.set_color(screen_info.screen_bg_color); + g.fillRect(img_info.x, img_info.y, img_info.x + img_width, img_info.y + img_width); + GraphicUtils.draw_cosine(img_info.x - 4, + img_info.x + img_width + 4, + COSINE_COLOUR, + screen_info); + } + GraphicUtils.draw_sunrise_line(HORIZON_COLOUR, day_info, screen_info); + screen_info.sun_x = null; + screen_info.sun_y = null; +} + +var last_time = null; +var last_offset = null; +var last_date = null; + +const time_color = Colors.WHITE; +const date_color = Colors.YELLOW; +const DATE_Y_COORD = 35; +const DATE_X_COORD = 5; +const TIME_X_COORD = 140; +const TIME_Y_COORD = 35; +const OFFSET_Y_COORD = 70; +const LOCATION_Y_COORD = 55; + +function write_date(now){ + var new_date = now.getDate(); + if(last_date == null || new_date != last_date){ + GraphicUtils.set_color(screen_info.screen_bg_color); + g.fillRect(DATE_X_COORD,DATE_Y_COORD, DATE_X_COORD+80,DATE_Y_COORD+15); + + g.setFont("Vector",15); + g.setFontAlign(-1,-1,0); + GraphicUtils.set_color(date_color); + var date_str = Locale.dow(now,1) + " " + Math2.format00(now.getDate()); + //console.log("writing date:" + new_date) + g.drawString(date_str, DATE_X_COORD,DATE_Y_COORD); + last_date = new_date; + } +} + +// The info panels mid way down the screen on the left and right +const INFO_PANEL_LINE_Y1 = 90; +const INFO_PANEL_LINE_Y2 = 105; +var gps_status_requires_update = true; + +// The GPS status panel shows the status of the GPS +// when the location is know it shows the +// longitude and latitude. +function write_GPS_status(){ + if(!gps_status_requires_update) + return; + + var gps_coords = location.getCoordinates(); + var gps_coords_msg; + + if(location.isGPSLocation()) { + if(gps_coords == null) { + if (location.getGPSPower() > 0) { + gps_coords_msg = ["Locating","GPS"]; + } else { + gps_coords_msg = ["ERROR","GPS"]; + } + } else { + if (location.getGPSPower() > 0) { + gps_coords_msg = ["Updating","GPS"]; + } + } + } + + if(gps_coords_msg == null){ + gps_coords_msg = ["N:" + Math2.format000_00(gps_coords[1]), + "E:" + Math2.format000_00(gps_coords[0])]; + } + + GraphicUtils.set_color(screen_info.screen_bg_color); + g.fillRect(DATE_X_COORD,INFO_PANEL_LINE_Y1,70,INFO_PANEL_LINE_Y2 + 13); + g.setFont("Vector",13); + g.setFontAlign(-1,-1,0); + GraphicUtils.set_color(Colors.WHITE); + g.drawString(gps_coords_msg[0], DATE_X_COORD, INFO_PANEL_LINE_Y1,1); + g.drawString(gps_coords_msg[1], DATE_X_COORD, INFO_PANEL_LINE_Y2,1); + + gps_status_requires_update = false; +} + +const TWILIGHT_X_COORD = 200; +const NO_TIME = "--:--"; + +var twilight_times_requires_update = true; +// The right panel shows the sun rise and sunset times. +// we make provision for when there is no times available +function write_twilight_times(){ + if(!twilight_times_requires_update) + return; + + var twilight_msg; + if(day_info != null) { + twilight_msg = [format_time(day_info.sunrise_date), format_time(day_info.sunset_date)]; + } else { + twilight_msg = [NO_TIME,NO_TIME]; + } + GraphicUtils.set_color(screen_info.screen_bg_color); + g.fillRect(TWILIGHT_X_COORD,INFO_PANEL_LINE_Y1,240,INFO_PANEL_LINE_Y2 + 13); + g.setFont("Vector",13); + g.setFontAlign(-1,-1,0); + GraphicUtils.set_color(Colors.YELLOW); + GraphicUtils.fill_circle_partial_y(TWILIGHT_X_COORD-15, + INFO_PANEL_LINE_Y1+7, + 7, + INFO_PANEL_LINE_Y1+7, + INFO_PANEL_LINE_Y1); + + g.drawString(twilight_msg[0], TWILIGHT_X_COORD,INFO_PANEL_LINE_Y1,1); + g.setColor(1,0.8,0); + g.drawString(twilight_msg[1], TWILIGHT_X_COORD,INFO_PANEL_LINE_Y2,1); + + twilight_times_requires_update = false; +} + +function write_time(now){ + var new_time = format_time(now); + g.setFont("Vector",35); + g.setFontAlign(-1,-1,0); + if(last_time != null){ + GraphicUtils.set_color(screen_info.screen_bg_color); + g.drawString(last_time, TIME_X_COORD,TIME_Y_COORD); + } + GraphicUtils.set_color(time_color); + g.drawString(new_time, TIME_X_COORD,TIME_Y_COORD); + last_time = new_time; +} + +function format_time(now){ + var time = new Date(now.getTime() - time_offset); + var hours = time.getHours() % 12; + if(hours < 1){ + hours = 12; + } + return Math2.format00(hours) + ":" + Math2.format00(time.getMinutes()); +} + +function write_offset(){ + var new_offset = format_offset(); + g.setFont("Vector",15); + g.setFontAlign(-1,-1,0); + if(last_offset != null){ + GraphicUtils.set_color(screen_info.screen_bg_color); + g.drawString(last_offset, TIME_X_COORD,OFFSET_Y_COORD); + } + GraphicUtils.set_color(time_color); + g.drawString(new_offset, TIME_X_COORD,OFFSET_Y_COORD); + last_offset = new_offset; +} + +function format_offset(){ + if(time_offset == 0) + return ""; + + var hours_offset = Math.abs(time_offset) / DateUtils.HOUR_MILLIS; + var mins_offset = Math.abs(time_offset) / DateUtils.MIN_MILLIS; + var mins_offset_from_hour = mins_offset % 60; + //console.log("mins offset=" + mins_offset + " mins_offset_from_hour=" + mins_offset_from_hour); + var sign = "+"; + if(time_offset < 0) + sign = "-"; + + return sign + Math2.format00(hours_offset) + ":" + Math2.format00(mins_offset_from_hour); +} + +let time_offset = 0; +var day_info = null; +var location = LocationUtils.load_locations(); +var location_requires_update = true; + +function write_location_name() { + if(!location_requires_update) + return; + + var new_location_name = location.getName(); + g.setFont("Vector", 20); + g.setFontAlign(-1, -1, 0); + + GraphicUtils.set_color(screen_info.screen_bg_color); + g.fillRect(DATE_X_COORD, LOCATION_Y_COORD, DATE_X_COORD + 105, LOCATION_Y_COORD + 20); + + if (new_location_name != "local") { + GraphicUtils.set_color(time_color); + g.drawString(new_location_name.replace("_", " "), DATE_X_COORD, LOCATION_Y_COORD); + } + location_requires_update = false; +} + +location.addUpdateListener( + (loc)=>{ + console.log("location update:" + JSON.stringify(loc)); + clear_sun(); + GraphicUtils.draw_sunrise_line(screen_info.screen_bg_color, day_info, screen_info); + day_info = null; + screen_info.sunrise_y = null; + curr_mode = null; + gps_status_requires_update = true; + location_requires_update = true; + twilight_times_requires_update = true; + draw_clock(); + } +); + +// day info is responsible for populating the day_info structure +// The day_info structure holds the sun set and sunset times +// The function also has to detect when the end of the solar +// day has been reached and flip the day over. +function dayInfo(now) { + if (day_info == null || now > day_info.day_end || now < day_info.day_start) { + var coords = location.getCoordinates(); + if(coords != null) { + day_info = DateUtils.sunrise_sunset(now, coords[0], coords[1], location.getUTCOffset()); + twilight_times_requires_update = true; + //console.log("day info:" + JSON.stringify(day_info)); + } else { + day_info = null; + } + } + return day_info; +} + +function time_now() { + var timezone_offset_hours = location.getUTCOffset(); + if(timezone_offset_hours != null) { + var local_offset_hours = -new Date().getTimezoneOffset()/60; + var timezone_offset_millis = + (timezone_offset_hours - local_offset_hours) * DateUtils.HOUR_MILLIS; + return new Date(Date.now() + time_offset + timezone_offset_millis); + } else { + return new Date(Date.now() + time_offset); + } +} + +// The main loop called everytime we want to refresh the +// clock +function draw_clock(){ + var start_time = Date.now(); + var now = time_now(); + + write_location_name(); + write_GPS_status(); + var day_info = dayInfo(now); + if(day_info != null) { + draw_sun(now, day_info); + } + write_time(now); + write_date(now); + write_offset(); + write_twilight_times(); + log_memory_used(); + var time_taken = Date.now() - start_time; + console.log("drawing clock:" + now.toISOString() + " time taken:" + time_taken ); +} + +function log_memory_used() { + var memory = process.memory(); + console.log("memory used:" + memory.usage + + " total:" + memory.total + "->" + + " ->" + memory.usage/memory.total + ); +} + +// Button has a short press action of resetting the offet +// and a long press set the GPS up for refreshing the current +// position. +// To accomodate the long and short press we record when the press +// starts and time to when it is released +var button1pressStart = null; +function button1pressed(){ + if(button1pressStart == null) { + button1pressStart = Date.now(); + } + //console.log("button 1 pressed for:" + (Date.now() - button1pressStart)); + if(BTN1.read()){ + // we look every 100 ms to see if the button is still pressed + // (to makes the button responsive) + setTimeout(button1pressed,100); + } else { + var buttonPressTime = Date.now() - button1pressStart; + button1pressStart = null; + console.log("button press time=" + buttonPressTime); + if (buttonPressTime < 3000) { + //console.log("offset reset"); + time_offset = 0; + clear_sun(); + day_info = null; + } else { + //console.log("requesting gps update"); + location.requestGpsUpdate(); + gps_status_requires_update = true; + } + draw_clock(); + } +} + +// button 3 kicks off a the change to the next location +function button3pressed() { + console.log("button 3 pressed"); + time_offset = 0; + location.nextLocation(); +} + +// button 4 moves the offset back +// keeping the button pressed repeats +// to give the sun the continuous animation +// to achieve this we put in a call back +// to see if its still presses after 50 ms. +function button4pressed(){ + time_offset -= DateUtils.HOUR_MILLIS/4; + draw_clock(); + setTimeout(()=>{ + if(BTN4.read()){ + button4pressed(); + } + }, + 50 + ) +} + +// button 5 moves the offset forward +// keeping the button pressed repeats +// to give the sun the continuous animation +// to achieve this we put in a call back +// to see if its still presses after 50 ms. +function button5pressed(){ + time_offset += DateUtils.HOUR_MILLIS/4; + draw_clock(); + setTimeout(()=>{ + if(BTN5.read()){ + button5pressed(); + } + }, + 50 + ) +} + +// The interval reference for updating the clock +let interval_ref = null; +function clear_timers(){ + if(interval_ref != null) { + clearInterval(interval_ref); + interval_ref = null; + } +} + +function start_timers(){ + console.log("start timers") + var date = new Date(); + var secs = date.getSeconds(); + var nextMinuteStart = 60 - secs; + setTimeout(schedule_draw_clock,nextMinuteStart * 1000); + draw_clock(); +} +function schedule_draw_clock(){ + clear_timers(); + if (Bangle.isLCDOn()) { + interval_ref = setInterval(() => { + if (!Bangle.isLCDOn()) { + console.log("draw clock callback - skipped redraw"); + } else { + draw_clock(); + } + }, DateUtils.MIN_MILLIS + ); + draw_clock(); + } else { + console.log("scheduleDrawClock - skipped not visible"); + } +} + +Bangle.on('lcdPower', (on) => { + if (on) { + console.log("lcdPower: on"); + gps_status_requires_update = true; + time_offset = 0; + start_timers(); + } else { + console.log("lcdPower: off"); + clear_timers(); + } +}); + +Bangle.on('faceUp',function(up){ + if (up && !Bangle.isLCDOn()) { + clear_timers(); + Bangle.setLCDPower(true); + } +}); + +g.clear(); +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +start_timers(); + +// When button 2 is pressed we return to the +// launcher. We run through a shutdown sequence +// we shutdown the location object, which +// shut down the GPS (if running) +// we deference the location and controller to +// to free up memory before the jump is made. +function button2pressed(){ + controller = null; + + location.shutdown(); + location = null; + + Bangle.showLauncher(); +} +setWatch(button2pressed, BTN2,{repeat:false,edge:"falling"}); +// we are timing button 1 so we put a callback in the the rising edge. +setWatch(button1pressed, BTN1,{repeat:true,edge:"rising"}); +setWatch(button3pressed, BTN3,{repeat:true,edge:"falling"}); +setWatch(button4pressed, BTN4,{repeat:true,edge:"rising"}); +setWatch(button5pressed, BTN5,{repeat:true,edge:"rising"}); \ No newline at end of file diff --git a/apps/solarclock/solar_clock.png b/apps/solarclock/solar_clock.png new file mode 100644 index 000000000..81b7d823f Binary files /dev/null and b/apps/solarclock/solar_clock.png differ diff --git a/apps/solarclock/solar_colors.js b/apps/solarclock/solar_colors.js new file mode 100644 index 000000000..a048cf308 --- /dev/null +++ b/apps/solarclock/solar_colors.js @@ -0,0 +1,14 @@ +const _BLACK= [0.0,0.0,0.0]; +const _GREY= [0.5,0.5,0.5]; +const _WHITE= [1.0,1.0,1.0]; +const _YELLOW= [1.0,1.0,0.0]; +const _RED= [1.0,0.0,0.0]; + +const Colors = { + BLACK: _BLACK, + GREY: _GREY, + WHITE: _WHITE, + YELLOW: _YELLOW, + RED: _RED +}; +module.exports = Colors; \ No newline at end of file diff --git a/apps/solarclock/solar_controller.js b/apps/solarclock/solar_controller.js new file mode 100644 index 000000000..5e9a32509 --- /dev/null +++ b/apps/solarclock/solar_controller.js @@ -0,0 +1,232 @@ +const Math2 = require("solar_math_utils.js"); +const DateUtils = require("solar_date_utils.js"); +const GraphicUtils = require("solar_graphic_utils.js"); +const Colors = require("solar_colors.js"); + +const CORONA_GAP = 2; +const CORONA_MIN_LENGTH = 2; +const CORONA_LINES = 12; +const CORONA_RADIUS = 14; +const SUNSET_START_HEIGHT = 10; +const SUNSET_COLOUR = Colors.RED; +const SUNRISE_COLOUR = [1,0.6,0]; +const MIDDAY_COLOUR = [1,1,0.7]; +const NIGHT_COLOUR = Colors.BLACK; + +function daytime_sun_color(now,day_info){ + var now_fraction_of_day =DateUtils.now_fraction_of_day(now,day_info); + if(now > day_info.sunset_date){ + return SUNSET_COLOUR; + } else if(now < day_info.sunrise_date){ + return SUNRISE_COLOUR; + } else if(now < day_info.solar_noon) { + var sunrise_fraction = (day_info.sunrise_date - day_info.day_start) / DateUtils.DAY_MILLIS; + var rise_to_midday_fraction = (now_fraction_of_day - sunrise_fraction) / (0.5 - sunrise_fraction); + return Math2.interpolate(SUNRISE_COLOUR, MIDDAY_COLOUR, rise_to_midday_fraction); + } else { + var sunset_fraction = (day_info.day_end - day_info.sunset_date) / DateUtils.DAY_MILLIS; + var midday_to_sunset_fraction = (now_fraction_of_day - 0.5)/(0.5 - sunset_fraction); + //console.log("sunset_fraction=" + sunset_fraction + " midday_to_sunset_fraction=" + midday_to_sunset_fraction) + return Math2.interpolate(MIDDAY_COLOUR,SUNSET_COLOUR,midday_to_sunset_fraction); + } +} + +function draw_night_sun(sun_x,sun_y,sun_radius,img_info){ + var draw_info = GraphicUtils.draw_info(img_info); + GraphicUtils.set_color(Colors.WHITE,draw_info.buff); + draw_info.buff.fillCircle(sun_x - draw_info.offset_x, + sun_y - draw_info.offset_y, + sun_radius); + GraphicUtils.set_color(NIGHT_COLOUR,draw_info.buff); + draw_info.buff.fillCircle(sun_x - draw_info.offset_x, + sun_y - draw_info.offset_y, + sun_radius-1); +} + +function draw_partial_sun(time, day_info, screen_info,img_info){ + var sun_height = screen_info.sunrise_y - screen_info.sun_y; + if(sun_height > screen_info.sun_radius){ + var sun_color = daytime_sun_color(time,day_info); + var draw_info = GraphicUtils.draw_info(img_info); + GraphicUtils.set_color(sun_color,draw_info.buff); + draw_info.buff.fillCircle(screen_info.sun_x - draw_info.offset_x, + screen_info.sun_y - draw_info.offset_y, + screen_info.sun_radius + ); + } else if(sun_height < -screen_info.sun_radius){ + draw_night_sun(screen_info.sun_x,screen_info.sun_y,screen_info.sun_radius, img_info); + } else { + var draw_info = GraphicUtils.draw_info(img_info); + GraphicUtils.set_color(NIGHT_COLOUR,draw_info.buff); + draw_info.buff.fillCircle(screen_info.sun_x - draw_info.offset_x, + screen_info.sun_y - draw_info.offset_y, + screen_info.sun_radius-1); + var sun_color = daytime_sun_color(time,day_info); + GraphicUtils.set_color(sun_color,draw_info.buff); + draw_info.buff.drawCircle(screen_info.sun_x - draw_info.offset_x, + screen_info.sun_y - draw_info.offset_y, + screen_info.sun_radius); + GraphicUtils.fill_circle_partial_y(screen_info.sun_x, + screen_info.sun_y, + screen_info.sun_radius, + screen_info.sun_y - screen_info.sun_radius, + screen_info.sunrise_y, + img_info + ); + } +} + +function draw_random_background(screen_info, + img_info, + rgb_init, + rgb_step + ){ + var draw_info = GraphicUtils.draw_info(img_info); + var rgb = rgb_init; + var sky_to = Math.min(screen_info.sunrise_y-1, img_info.y + img_info.img.height - 3); + for(var sky_y=img_info.y+3;sky_y0) + rgb[i] = Math2.random_walk(rgb[i],rgb_step[i],1,0); + } + + GraphicUtils.set_color(rgb,draw_info.buff); + draw_info.buff.moveTo(screen_info.sun_x + + Math.random()*img_info.img.width/8 - + 0.4*img_info.img.width - + draw_info.offset_x, + sky_y - draw_info.offset_y); + draw_info.buff.lineTo(screen_info.sun_x + + 0.4*img_info.img.width - + Math.random()*img_info.img.width/8 - + draw_info.offset_x, + sky_y - draw_info.offset_y); + } + GraphicUtils.set_color(NIGHT_COLOUR,draw_info.buff); + draw_info.buff.fillCircle(screen_info.sun_x - draw_info.offset_x, + screen_info.sun_y - draw_info.offset_y, + screen_info.sun_radius+1); +} + +/** + * SolarControllerImpl to SolarMode is a Strategy pattern. + * The sun animation is very different through the different + * sectors of the day so the correct strategy is selected + * for the day sector + */ +class SolarMode { + test(time, day_info, screen_info){ throw "test undefined";} + draw(time, day_info, screen_info, img_buffer_info){ + throw "sun drawing undefined"; + } +} +class DayNightMode extends SolarMode { + test(time, day_info, screen_info){ + return true; + } + // The corona is larger the closer you are to solar noon + _calc_corona_radius(now, day_info){ + if(now < day_info.sunset_date && + now > day_info.sunrise_date){ + var now_fraction_of_day = DateUtils.now_fraction_of_day(now,day_info); + var sunset_fraction = (day_info.sunset_date.getTime() - day_info.day_start.getTime())/DateUtils.DAY_MILLIS; + var now_fraction_from_midday = + 1 - Math.abs(now_fraction_of_day-0.5)/(sunset_fraction-0.5); + return CORONA_RADIUS * now_fraction_from_midday; + } else { + return 0; + } + } + _drawCorona(corona_radius,sun_x,sun_y,sun_radius, draw_info){ + var thickness_rads = (Math2.TWO_PI/CORONA_LINES)/3; + var from_radius = sun_radius + CORONA_GAP; + if(corona_radius > from_radius + CORONA_MIN_LENGTH) { + for (var i = 0; i < CORONA_LINES; i++) { + var to_x1 = sun_x - draw_info.offset_x + from_radius * Math.cos(i * Math2.TWO_PI / CORONA_LINES + thickness_rads); + var to_y1 = sun_y - draw_info.offset_y + from_radius * Math.sin(i * Math2.TWO_PI / CORONA_LINES + thickness_rads); + var to_x2 = sun_x - draw_info.offset_x + from_radius * Math.cos(i * Math2.TWO_PI / CORONA_LINES - thickness_rads); + var to_y2 = sun_y - draw_info.offset_y + from_radius * Math.sin(i * Math2.TWO_PI / CORONA_LINES - thickness_rads); + var to_x3 = sun_x - draw_info.offset_x + corona_radius * Math.cos(i * Math2.TWO_PI / CORONA_LINES); + var to_y3 = sun_y - draw_info.offset_y + corona_radius * Math.sin(i * Math2.TWO_PI / CORONA_LINES); + draw_info.buff.fillPoly([to_x1, to_y1, to_x2, to_y2, to_x3, to_y3]); + } + } + } + draw(now, day_info, screen_info, img_info){ + if(now < day_info.sunrise_date || now > day_info.sunset_date){ + draw_night_sun(screen_info.sun_x,screen_info.sun_y,screen_info.sun_radius, img_info); + } else { + var sun_color = daytime_sun_color(now, day_info); + var corona_radius = this._calc_corona_radius(now, day_info); + var draw_info = GraphicUtils.draw_info(img_info); + GraphicUtils.set_color(sun_color, draw_info.buff); + if (corona_radius > screen_info.sun_radius) { + this._drawCorona(corona_radius, + screen_info.sun_x, + screen_info.sun_y, + screen_info.sun_radius, + draw_info); + } + draw_info.buff.fillCircle(screen_info.sun_x - draw_info.offset_x, + screen_info.sun_y - draw_info.offset_y, + screen_info.sun_radius); + } + } +} +class TwiLightMode extends SolarMode { + test(time, day_info, screen_info){ + if(screen_info.sunrise_y == null) { + console.log("warning no sunrise_defined"); + return false; + } + var sun_height = screen_info.sunrise_y - screen_info.sun_y; + /*console.log("TwilightMode " + + "time=" + time.toISOString() + + " sun_height=" + sun_height + + " sun_radius=" + screen_info.sun_radius + );*/ + if(sun_height > -screen_info.sun_radius && + sun_height < screen_info.sun_radius * 2 + SUNSET_START_HEIGHT + ){ + //console.log("selected TwilightMode"); + return true; + } + return false; + } + draw(time, day_info, screen_info, img_info){ + if(time < day_info.solar_noon) { + draw_random_background(screen_info, + img_info, + [0,0.8,1], + [0.05,0.05,0.0]); + } else { + draw_random_background(screen_info, + img_info, + [1,0.75,Math.random()], + [0,0.05,0.05]); + } + draw_partial_sun(time,day_info,screen_info,img_info); + } +} +class SolarControllerImpl { + constructor(){ + this.solar_modes = [new TwiLightMode()]; + this.default_mode = new DayNightMode() + } + // The mode method is responsible for selecting the + // correct mode to the time given. + mode(time, day_info, screen_info){ + // next we step through the different modes and test then + // one by one. + for(var i=0; i" + solar_noon_datetime.toISOString()); + + var sunrise_time_LST = (solar_noon*1440-HA_sunrise_degrees*4)/1440; + var sunrise_time_LST_datetime = _to_time(now,sunrise_time_LST); + console.log("sunrise_time_LST=" + sunrise_time_LST + + "->" + sunrise_time_LST_datetime.toISOString()); + + var sunset_time_LST =(solar_noon*1440+HA_sunrise_degrees*4)/1440; + var sunset_time_LST_datetime = _to_time(now,sunset_time_LST); + console.log("sunset_time_LST=" + sunset_time_LST + + "->" + sunset_time_LST_datetime.toISOString()); + return { + day_start: new Date(solar_noon_datetime.getTime() - _DAY_MILLIS / 2), + sunrise_date: sunrise_time_LST_datetime, + //sunrise_fraction: sunrise_time_LST, + sunset_date: sunset_time_LST_datetime, + //sunset_fraction: sunset_time_LST, + solar_noon: solar_noon_datetime, + day_end: new Date(solar_noon_datetime.getTime() + _DAY_MILLIS / 2) + }; + }, + now_fraction_of_day: (now,day_info)=>{ + return (now.getTime() - day_info.day_start.getTime())/_DAY_MILLIS; + }, +} +module.exports = DateUtils; \ No newline at end of file diff --git a/apps/solarclock/solar_graphic_utils.js b/apps/solarclock/solar_graphic_utils.js new file mode 100644 index 000000000..28bfc3ad7 --- /dev/null +++ b/apps/solarclock/solar_graphic_utils.js @@ -0,0 +1,100 @@ +var DateUtils = require("solar_date_utils.js"); +var Math2 = require("solar_math_utils.js"); + +function _draw_info(img_info){ + if (img_info == null) { + return { + buff: g, + offset_x: 0, + offset_y: 0 + }; + } else { + return { + buff: img_info.img_buffer, + offset_x: img_info.x, + offset_y: img_info.y + }; + } + +} +const GraphicUtils = { + draw_info : (img_info)=>_draw_info(img_info), + set_color: (color_vector,buff)=>{ + if(buff == null) + buff = g; + + buff.setColor(color_vector[0],color_vector[1],color_vector[2]) + }, + draw_cosine : (from_x,to_x, line_colour, screen_info, img_info)=>{ + //console.log("draw_cosine from_x=" + from_x + " to_x=" + to_x); + var draw_info = _draw_info(img_info); + + draw_info.buff.reset(); + draw_info.buff.setColor(line_colour[0],line_colour[1],line_colour[2]); + var first = true; + for(var x=from_x; x { + if (!this.in_use) + return; + + if (g.fix) { + var loc_info = { + coordinates: [g.lon, g.lat] + }; + console.log("Received gps fixing:" + JSON.stringify(loc_info)); + storage.writeJSON("solar_loc.local.json", loc_info); + this.setGPSPower(0); + if(this.isGPSLocation()){ + this.location_info = loc_info; + this.notifyUpdate(); + } + } + }); + } + setGPSPower(power){ + if(power && !this.gpsCallbackInstalled){ + this.initCallback(); + this.gpsCallbackInstalled = true; + } + this.gpsPower = power; + Bangle.setGPSPower(this.gpsPower); + } + getGPSPower(){return this.gpsPower;} + requestGpsUpdate(){ + if (this.getGPSPower() == 0) { + console.log("updating gps location update"); + this.setGPSPower(1); + } else { + console.log("gps already updating"); + } + } + isGPSLocation(){return this.getName() == 'local';} + addUpdateListener(listener){this.listeners.push(listener);} + nextLocation() { + if(this.locations.length > 1) { + this.idx += 1; + this.idx = this.idx % this.locations.length; + console.log("location now:" + this.getName()); + this.init(); + this.notifyUpdate(); + } else { + console.log("no extra locations found"); + } + } + notifyUpdate(){ + for(var i=0; i{ + var locations; + try { + locations = storage.readJSON(LOCATIONS_FILE); + } catch(e){ + console.log("failed to load locations file:" + e); + } + if(locations == null) + locations = ['local']; + + console.log("loaded locations:" + locations); + var mgr = new LocationManager(locations); + mgr.init(); + return mgr; + } +} +module.exports = LocationUtils; \ No newline at end of file diff --git a/apps/solarclock/solar_locations.json b/apps/solarclock/solar_locations.json new file mode 100644 index 000000000..14d3096b8 --- /dev/null +++ b/apps/solarclock/solar_locations.json @@ -0,0 +1 @@ +["local","Hong_Kong","Tokyo","Reykjavik","Rio","Honolulu"] \ No newline at end of file diff --git a/apps/solarclock/solar_math_utils.js b/apps/solarclock/solar_math_utils.js new file mode 100644 index 000000000..6399dacca --- /dev/null +++ b/apps/solarclock/solar_math_utils.js @@ -0,0 +1,48 @@ +const _TWO_PI = 2 * Math.PI; + +function _format00(num){ + var value = (num | 0); + if(value > 99 || value < 0) + throw "must be between in range 0-99"; + if(value < 10) + return "0" + value.toString(); + else + return value.toString(); +} +const Maths2 = { + TWO_PI: _TWO_PI, + to_radians: (degrees)=> _TWO_PI * degrees / 360, + to_degrees: (radians)=> 360 * radians/ (_TWO_PI), + interpolate: (vector1, vector2, fraction)=>{ + var result = []; + for(var i=0; i< vector1.length; i++){ + var value = vector1[i] + (vector2[i] - vector1[i]) * fraction; + result.push(value); + } + return result; + }, + format00: (num)=>{ + return _format00(num) + }, + format000_00: (num)=>{ + var mantissa = (num | 0); + var mantissa_abs = Math.abs(mantissa); + var remainder = ((Math.abs(num) - mantissa_abs)*100|0); + var sign = "-"; + if(num >= 0) + sign = "+"; + return sign + mantissa_abs.toString() + "." + _format00(remainder); + + }, + random_walk: (value,step,max,min)=>{ + if(Math.random()>0.5){ + value -= step; + } else { + value += step; + } + value = Math.min(value,max); + value = Math.max(value,min); + return value; + } +} +module.exports = Maths2; \ No newline at end of file diff --git a/apps/svclock/ChangeLog b/apps/svclock/ChangeLog index a9e0036a3..671de492c 100644 --- a/apps/svclock/ChangeLog +++ b/apps/svclock/ChangeLog @@ -1 +1,2 @@ 0.01: Modification of SimpleClock 0.04 to use Vectorfont +0.02: Use Bangle.setUI for button/launcher handling diff --git a/apps/svclock/vclock-simple.js b/apps/svclock/vclock-simple.js index 2af8b74a4..f3ab911bc 100644 --- a/apps/svclock/vclock-simple.js +++ b/apps/svclock/vclock-simple.js @@ -80,5 +80,5 @@ setInterval(drawSimpleClock, 15E3); // draw now drawSimpleClock(); -// Show launcher when middle button pressed -setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"}); +// Show launcher when button pressed +Bangle.setUI("clock"); diff --git a/apps/swlclk/ChangeLog b/apps/swlclk/ChangeLog index 5560f00bc..3e072aee3 100644 --- a/apps/swlclk/ChangeLog +++ b/apps/swlclk/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: Fix text alignment, move to setUI diff --git a/apps/swlclk/app.js b/apps/swlclk/app.js index d1abeda8a..24e150323 100644 --- a/apps/swlclk/app.js +++ b/apps/swlclk/app.js @@ -17,126 +17,119 @@ function drawTime(){ var utc = getUTCTime(d); var beats = Math.floor((((utc[0] + 1) % 24) + utc[1] / 60 + utc[2] / 3600) * 1000 / 24); - function drawStation(){ - g.setFont("Vector",10); - g.setColor("#ffffff"); - switch (utc[0]) { - case 0: - g.clearRect(0,25,240,80); - g.drawString("00h00-00h30 Radio Havane Cuba\n15730\n00h00-01h00 Radio for Peace Int.\n9395\n00h30-01h00 Radio Havane Cuba\n5040",xyCenter,30); - break; - case 1: - g.clearRect(0,25,240,80); - g.drawString("01h00-02h00 Radio Roumanie Int.\n6040 7375\n01h00-01h30 R. Argentine vers le monde\n9395",xyCenter,30); - break; - case 2: - g.clearRect(0,25,240,80); - g.drawString("02h30-03h00 R. Argentine vers le monde\n5800",xyCenter,30); - break; - case 4: - g.clearRect(0,25,240,80); - g.drawString("04h00-05h00 R.F.I.\n9790 11700\n04h00-05h00 Voix de la Corée\n13650 15105\n04h30-05h00 A.W.R.\n6155",xyCenter,30); - break; - case 5: - g.clearRect(0,25,240,80); - g.drawString("05h00-05h30 Radio Roumanie Int.\n6015 15340 17520\n05h00-06h00 Radio Ndarason Int.\n5960\n05h30-06h00 Radio Japon\n11730 13840\n",xyCenter,30); - break; - case 6: - g.clearRect(0,25,240,80); - g.drawString("06h00-06h30 B.B.C.\n5875 9440 11620\n06h00-06h30 Voix de l'Amérique\n4960 6180 9885 13830\n06h30-06h45 Vatican News\n11935",xyCenter,30); - break; - case 7: - g.clearRect(0,25,240,80); - g.drawString("07h00-07h30 B.B.C.\n9440 13810?\n07h00-08h00 Radio Chine Int.\n17865\n07h00-08h00 R.F.I.\n11700 13695 15300 17850 21580?",xyCenter,30); - break; - case 8: - g.clearRect(0,25,240,80); - g.drawString("08h00-08h30 A.W.R.\n15145\n08h00-09h00 W.B.C.Q.\n9330\n08h30-09h00 Voix de l'Amérique\n9410 13830 17530",xyCenter,30); - break; - case 9: - g.clearRect(0,25,240,80); - g.drawString("09h00-10h00 R. Argentine vers le monde\n5950\n09h00-10h00 R.F.I.\n13695 15300 15320",xyCenter,30); - break; - case 10: - g.clearRect(0,25,240,80); - g.drawString("10h00-10h30 Voix du Nigéria\n11770\n10h00-11h00 Radio MiAmigo\n6085",xyCenter,30); - break; - case 11: - g.clearRect(0,25,240,80); - g.drawString("11h00-12h00 Voix de la Corée\n11710 11735 13650 15180\n11h30-12h00 Radio Slovaquie Int.\n6005",xyCenter,30); - break; - case 12: - g.clearRect(0,25,240,80); - g.drawString("12h00-12h30 Voix du Vietnam\n7285\n12h00-13h00 Radio MiAmigo\n6085",xyCenter,30); - break; - case 13: - g.clearRect(0,25,240,80); - g.drawString("13h00-14h00 Radio for Peace Int.\n15770\n13h30-14h00 Radio Slovaquie Int.\n6005",xyCenter,30); - break; - case 14: - g.clearRect(0,25,240,80); - g.drawString("14h00-16h00 Radio saoudienne Int.\n17660\n14h00-16h00 Radio Chine Int.\n11920 13670\n14h55-15h25 T.W.R. Swaziland\n9585",xyCenter,30); - break; - case 15: - g.clearRect(0,25,240,80); - g.drawString("15h00-15h30 Radio Tirana\n3985\n15h00-15h30 Radio Nationale Lao\n6130 567",xyCenter,30); - break; - case 16: - g.clearRect(0,25,240,80); - g.drawString("16h00-16h15 Vatican News\n11950\n16h30-17h15 Voix de l'Afrique\n9505",xyCenter,30); - break; - case 17: - g.clearRect(0,25,240,80); - g.drawString("17h00-18h00 R.F.I.\n13740 13770 17850\n17h30-18h25 Voix de la Turquie\n7360",xyCenter,30); - break; - case 18: - g.clearRect(0,25,240,80); - g.drawString("18h00-18h11 Radio Algérie Int.\n13820\n18h30-19h00 Radio Slovaquie Int.\n3985",xyCenter,30); - break; - case 19: - g.clearRect(0,25,240,80); - g.drawString("19h00-19h30 Radio Taiwan Int.\n6005\n19h23-20h23 Voix de la République\nIslamique d'Iran\n7235",xyCenter,30); - break; - case 20: - g.clearRect(0,25,240,80); - g.drawString("20h00-21h15 Radio Le Caire\n9810\n20h00-21h00 Voix de l'Indonésie\n3325 4750\n20h30-20h50 Radio Belarus\n3985",xyCenter,30); - break; - case 21: - g.clearRect(0,25,240,80); - g.drawString("21h00-21h30 Voix de l'Amérique\n5970 9490 9740 11900\n21h00-22h00 Radio for Peace Int.\n6070",xyCenter,30); - break; - case 22: - g.clearRect(0,25,240,80); - g.drawString("22h00-22h15 T.W.R. Bénin\n1566\n22h30-23h00 Radio Extérieure d'Espagne\n9690 11670 11940",xyCenter,30); - break; - case 23: - g.clearRect(0,25,240,80); - g.drawString("23h23-00h23 Voix de la République\nIslamique d'Iran\n7230\n23h30-00h00 R. Argentine vers le monde\n7780",xyCenter,30); - break; - default: - g.clearRect(0,25,240,80); - g.drawString("17h00-18h00 R.F.I.\n13740 15300 17850\n17h00-18h00 R.F.I.\n7205 9790",xyCenter,30); - break; - } + g.reset().setFont("Vector",10).setFontAlign(0,0); + g.setColor("#ffffff"); + switch (utc[0]) { + case 0: + g.clearRect(0,25,240,80); + g.drawString("00h00-00h30 Radio Havane Cuba\n15730\n00h00-01h00 Radio for Peace Int.\n9395\n00h30-01h00 Radio Havane Cuba\n5040",xyCenter,30); + break; + case 1: + g.clearRect(0,25,240,80); + g.drawString("01h00-02h00 Radio Roumanie Int.\n6040 7375\n01h00-01h30 R. Argentine vers le monde\n9395",xyCenter,30); + break; + case 2: + g.clearRect(0,25,240,80); + g.drawString("02h30-03h00 R. Argentine vers le monde\n5800",xyCenter,30); + break; + case 4: + g.clearRect(0,25,240,80); + g.drawString("04h00-05h00 R.F.I.\n9790 11700\n04h00-05h00 Voix de la Corée\n13650 15105\n04h30-05h00 A.W.R.\n6155",xyCenter,30); + break; + case 5: + g.clearRect(0,25,240,80); + g.drawString("05h00-05h30 Radio Roumanie Int.\n6015 15340 17520\n05h00-06h00 Radio Ndarason Int.\n5960\n05h30-06h00 Radio Japon\n11730 13840\n",xyCenter,30); + break; + case 6: + g.clearRect(0,25,240,80); + g.drawString("06h00-06h30 B.B.C.\n5875 9440 11620\n06h00-06h30 Voix de l'Amérique\n4960 6180 9885 13830\n06h30-06h45 Vatican News\n11935",xyCenter,30); + break; + case 7: + g.clearRect(0,25,240,80); + g.drawString("07h00-07h30 B.B.C.\n9440 13810?\n07h00-08h00 Radio Chine Int.\n17865\n07h00-08h00 R.F.I.\n11700 13695 15300 17850 21580?",xyCenter,30); + break; + case 8: + g.clearRect(0,25,240,80); + g.drawString("08h00-08h30 A.W.R.\n15145\n08h00-09h00 W.B.C.Q.\n9330\n08h30-09h00 Voix de l'Amérique\n9410 13830 17530",xyCenter,30); + break; + case 9: + g.clearRect(0,25,240,80); + g.drawString("09h00-10h00 R. Argentine vers le monde\n5950\n09h00-10h00 R.F.I.\n13695 15300 15320",xyCenter,30); + break; + case 10: + g.clearRect(0,25,240,80); + g.drawString("10h00-10h30 Voix du Nigéria\n11770\n10h00-11h00 Radio MiAmigo\n6085",xyCenter,30); + break; + case 11: + g.clearRect(0,25,240,80); + g.drawString("11h00-12h00 Voix de la Corée\n11710 11735 13650 15180\n11h30-12h00 Radio Slovaquie Int.\n6005",xyCenter,30); + break; + case 12: + g.clearRect(0,25,240,80); + g.drawString("12h00-12h30 Voix du Vietnam\n7285\n12h00-13h00 Radio MiAmigo\n6085",xyCenter,30); + break; + case 13: + g.clearRect(0,25,240,80); + g.drawString("13h00-14h00 Radio for Peace Int.\n15770\n13h30-14h00 Radio Slovaquie Int.\n6005",xyCenter,30); + break; + case 14: + g.clearRect(0,25,240,80); + g.drawString("14h00-16h00 Radio saoudienne Int.\n17660\n14h00-16h00 Radio Chine Int.\n11920 13670\n14h55-15h25 T.W.R. Swaziland\n9585",xyCenter,30); + break; + case 15: + g.clearRect(0,25,240,80); + g.drawString("15h00-15h30 Radio Tirana\n3985\n15h00-15h30 Radio Nationale Lao\n6130 567",xyCenter,30); + break; + case 16: + g.clearRect(0,25,240,80); + g.drawString("16h00-16h15 Vatican News\n11950\n16h30-17h15 Voix de l'Afrique\n9505",xyCenter,30); + break; + case 17: + g.clearRect(0,25,240,80); + g.drawString("17h00-18h00 R.F.I.\n13740 13770 17850\n17h30-18h25 Voix de la Turquie\n7360",xyCenter,30); + break; + case 18: + g.clearRect(0,25,240,80); + g.drawString("18h00-18h11 Radio Algérie Int.\n13820\n18h30-19h00 Radio Slovaquie Int.\n3985",xyCenter,30); + break; + case 19: + g.clearRect(0,25,240,80); + g.drawString("19h00-19h30 Radio Taiwan Int.\n6005\n19h23-20h23 Voix de la République\nIslamique d'Iran\n7235",xyCenter,30); + break; + case 20: + g.clearRect(0,25,240,80); + g.drawString("20h00-21h15 Radio Le Caire\n9810\n20h00-21h00 Voix de l'Indonésie\n3325 4750\n20h30-20h50 Radio Belarus\n3985",xyCenter,30); + break; + case 21: + g.clearRect(0,25,240,80); + g.drawString("21h00-21h30 Voix de l'Amérique\n5970 9490 9740 11900\n21h00-22h00 Radio for Peace Int.\n6070",xyCenter,30); + break; + case 22: + g.clearRect(0,25,240,80); + g.drawString("22h00-22h15 T.W.R. Bénin\n1566\n22h30-23h00 Radio Extérieure d'Espagne\n9690 11670 11940",xyCenter,30); + break; + case 23: + g.clearRect(0,25,240,80); + g.drawString("23h23-00h23 Voix de la République\nIslamique d'Iran\n7230\n23h30-00h00 R. Argentine vers le monde\n7780",xyCenter,30); + break; + default: + g.clearRect(0,25,240,80); + g.drawString("17h00-18h00 R.F.I.\n13740 15300 17850\n17h00-18h00 R.F.I.\n7205 9790",xyCenter,30); + break; } - drawStation(); // Local time - g.setFont("6x8",1); - g.setColor("#cccccc"); + g.setFont("6x8",1).setColor("#cccccc"); g.drawString("Loc",10,85); - g.setFont("7x11Numeric7Seg",4); - g.setColor("#ffffff"); + g.setFont("7x11Numeric7Seg",4).setColor("#ffffff"); g.drawString(`${hours}:${minutes}:${seconds}`, xyCenter, 115, true); // UTC time - g.setFont("6x8",1); - g.setColor("#cccccc"); + g.setFont("6x8",1).setColor("#cccccc"); g.drawString("UTC",10,155); - g.setFont("7x11Numeric7Seg",4); - g.setColor("#ff0000"); + g.setFont("7x11Numeric7Seg",4).setColor("#ff0000"); g.drawString(utc[0]+`:${minutes}:${seconds}`, xyCenter, 185,true); // footer date @@ -147,6 +140,7 @@ function drawTime(){ } function setGpsTime(){ + print("set GPS time"); Bangle.setGPSPower(1); Bangle.on('GPS',function(fix) { if (fix.fix) { @@ -157,7 +151,7 @@ function setGpsTime(){ start(); } else { stop(); - g.setFont("Vector",10); + g.reset().setFont("Vector",10).setFontAlign(0,0); g.setColor("#cccccc"); g.clearRect(0,25,240,80); g.drawString("Mise à l'heure\npar satellites\nen cours...",xyCenter,40); @@ -166,14 +160,13 @@ function setGpsTime(){ } function setButtons(){ - // BTN 1 - setWatch(() => { + // Show launcher when button pressed + Bangle.setUI("clockupdown", btn=>{ + if (btn!=-1) return; + // if up pressed, turn GPS on and wait for new time setGpsTime(); Bangle.beep(500, 4000); - }, BTN1, {edge:"rising", repeat:true}); - - // BTN 2 - setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"}); + }); } var intervalRef = null; diff --git a/apps/thermom/ChangeLog b/apps/thermom/ChangeLog new file mode 100644 index 000000000..78fed5826 --- /dev/null +++ b/apps/thermom/ChangeLog @@ -0,0 +1 @@ +0.02: New App! diff --git a/apps/thermomF/app-icon.js b/apps/thermomF/app-icon.js new file mode 100644 index 000000000..fe1fa86b7 --- /dev/null +++ b/apps/thermomF/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwhC/AH4AChGIxGAC6eIAQgARFgUIC9ReCAYJgSC7BHDF6gUBC6ovWI/5Hga/6P/ABsCkABDC/4XxkQXDkQuSAQwXPDQkAC6BBCkQDDC6MCmczFoIXQCQQXBDgQXP2EA2YXBncAhYXR3YXB3YXRCQWznYcCC6ICBAYYXPhYrBApAwPFyQqCIoYuRLwZgDAH4A/")) \ No newline at end of file diff --git a/apps/thermomF/app.js b/apps/thermomF/app.js new file mode 100644 index 000000000..2961e1efc --- /dev/null +++ b/apps/thermomF/app.js @@ -0,0 +1,28 @@ +function onTemperature(p) { + g.reset(1).clearRect(0,24,g.getWidth(),g.getHeight()); + g.setFont("6x8",2).setFontAlign(0,0); + var x = g.getWidth()/2; + var y = g.getHeight()/2 + 10; + g.drawString("Temperature", x, y - 45); + g.setFontVector(70).setFontAlign(0,0); + g.drawString(p.temperature.toFixed(1), x, y); +} + +function drawTemperature() { + if (Bangle.getPressure) { + Bangle.getPressure().then(onTemperature); + } else { + onTemperature({ + temperature : E.getTemperature() * (9/5) + 32 + }); + } +} + + +setInterval(function() { + drawTemperature(); +}, 20000); +drawTemperature(); +E.showMessage("Loading..."); +Bangle.loadWidgets(); +Bangle.drawWidgets(); \ No newline at end of file diff --git a/apps/thermomF/thermf.png b/apps/thermomF/thermf.png new file mode 100644 index 000000000..bb33cb939 Binary files /dev/null and b/apps/thermomF/thermf.png differ diff --git a/apps/welcome/ChangeLog b/apps/welcome/ChangeLog index ce9194c5d..3e70af0ee 100644 --- a/apps/welcome/ChangeLog +++ b/apps/welcome/ChangeLog @@ -12,3 +12,4 @@ More useful app menu BTN2 now goes to menu on release 0.10: Tweaks to reduce memory usage +0.11: Fix initial screen fill colour diff --git a/apps/welcome/app.js b/apps/welcome/app.js index 1b413e080..ebe3e44e0 100644 --- a/apps/welcome/app.js +++ b/apps/welcome/app.js @@ -12,6 +12,7 @@ function animate(seq,period) { function fade(col, callback) { var n = 0; function f() {"ram" + g.setColor(col); for (var i=n;i<240;i+=10) g.drawLine(i,0,0,i).drawLine(i,240,240,i); g.flip(); n++; diff --git a/bin/build_bangle2_c.js b/bin/build_bangle2_c.js new file mode 100755 index 000000000..5b4464691 --- /dev/null +++ b/bin/build_bangle2_c.js @@ -0,0 +1,158 @@ +#!/usr/bin/nodejs +/* +Mashes together a bunch of different apps into a big binary blob. +We then store this *inside* the Bangle.js firmware and can use it +to populate Storage initially. + +Bangle.js 1 doesn't really have anough flash space for this, +but we have enough on v2. +*/ +var SETTINGS = { + pretokenise : true +}; + +var path = require('path'); +var ROOTDIR = path.join(__dirname, '..'); +var APPDIR = ROOTDIR+'/apps'; +var APPJSON = ROOTDIR+'/apps.json'; +var OUTFILE = path.join(ROOTDIR, '../Espruino/libs/banglejs/banglejs2_storage_default.c'); +var APPS = [ // IDs of apps to install + "boot","launchb2","s7clk","setting", + "about","alarm","widlock","widbat","widbt" +]; +var MINIFY = true; + +var fs = require("fs"); +global.Const = { + /* Are we only putting a single app on a device? If so + apps should all be saved as .bootcde and we write info + about the current app into app.info */ + SINGLE_APP_ONLY : false, +}; + + +function atob(input) { + // Copied from https://github.com/strophe/strophejs/blob/e06d027/src/polyfills.js#L149 + // This code was written by Tyler Akins and has been placed in the + // public domain. It would be nice if you left this header intact. + // Base64 code from Tyler Akins -- http://rumkin.com + var keyStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; + + var output = []; + var chr1, chr2, chr3; + var enc1, enc2, enc3, enc4; + var i = 0; + // remove all characters that are not A-Z, a-z, 0-9, +, /, or = + input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ''); + do { + enc1 = keyStr.indexOf(input.charAt(i++)); + enc2 = keyStr.indexOf(input.charAt(i++)); + enc3 = keyStr.indexOf(input.charAt(i++)); + enc4 = keyStr.indexOf(input.charAt(i++)); + + chr1 = (enc1 << 2) | (enc2 >> 4); + chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); + chr3 = ((enc3 & 3) << 6) | enc4; + + output.push(chr1); + + if (enc3 !== 64) { + output.push(chr2); + } + if (enc4 !== 64) { + output.push(chr3); + } + } while (i < input.length); + return new Uint8Array(output); + } + +var AppInfo = require(ROOTDIR+"/core/js/appinfo.js"); +var appjson = JSON.parse(fs.readFileSync(APPJSON).toString()); +var appfiles = []; + +function fileGetter(url) { + console.log("Loading "+url) + if (MINIFY) { + if (url.endsWith(".json")) { + var f = url.slice(0,-5); + console.log("MINIFYING JSON "+f); + var j = eval("("+fs.readFileSync(url).toString("binary")+")"); + var code = JSON.stringify(j); + //console.log(code); + url = f+".min.json"; + fs.writeFileSync(url, code); + } + } + return Promise.resolve(fs.readFileSync(url).toString("binary")); +} + +// If file should be evaluated, try and do it... +function evaluateFile(file) { + var hsStart = 'require("heatshrink").decompress(atob("'; + var hsEnd = '"))'; + if (file.content.startsWith(hsStart) && file.content.endsWith(hsEnd)) { + var heatshrink = require(ROOTDIR+"/core/lib/heatshrink.js"); + var b64 = file.content.slice(hsStart.length, -hsEnd.length); + var decompressed = heatshrink.decompress(atob(b64)); + file.content = ""; + for (var i=0;i { + var app = appjson.find(app=>app.id==appid); + if (app===undefined) throw new Error(`App ${appid} not found`); + return AppInfo.getFiles(app, { + fileGetter : fileGetter, + settings : SETTINGS + }).then(files => { + appfiles = appfiles.concat(files); + }); +})).then(() => { + // work out what goes in storage + var storageContent = ""; + appfiles.forEach((file) => { + //console.log(file); + if (file.evaluate) evaluateFile(file); + var fileLength = file.content.length; + console.log(file.name+" -> "+fileLength+"b"); + // set up header + var header = new Uint8Array(32); + header.fill(0); + header.set([fileLength,fileLength>>8,fileLength>>16,fileLength>>24],0); // length + for (var i=0;ic.charCodeAt()).join(",")+",\n"; +} +cfile += `}; +`; + fs.writeFileSync(OUTFILE, cfile); + console.log("Output written to "+OUTFILE); +}); diff --git a/core b/core index 8d9a012d6..27f9a7125 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 8d9a012d62d40aae1b2304d0149440fb3c022393 +Subproject commit 27f9a7125146a38c4357d679ec783f6e98a983c6 diff --git a/loader.js b/loader.js index 1e4dbd9b9..e0e1399b1 100644 --- a/loader.js +++ b/loader.js @@ -11,7 +11,7 @@ if (window.location.host=="banglejs.com") { 'This is not the official Bangle.js App Loader - you can try the Official Version here.'; } -var RECOMMENDED_VERSION = "2v08"; +var RECOMMENDED_VERSION = "2v09"; // could check http://www.espruino.com/json/BANGLEJS.json for this (function() { diff --git a/modules/Layout.js b/modules/Layout.js new file mode 100644 index 000000000..f21fbe45d --- /dev/null +++ b/modules/Layout.js @@ -0,0 +1,232 @@ +if (!g.theme) { + g.theme = { + fg:-1,bg:0,fgH:-1,bgH:"#008" + }; +} + +function Layout(layout, buttons) { + this._l = this.l = layout; + this.b = buttons; + // Do we have physical buttons? + this.physBtn = process.env.HWVERSION!=2; + this.yOffset = Object.keys(global.WIDGETS).length ? 24 : 0; + + if (buttons) { + var btnHeight = Math.floor((g.getHeight()-this.yOffset) / buttons.length); + if (this.physBtn) { + if (Bangle.btnWatch) Bangle.btnWatch.forEach(clearWatch); + Bangle.btnWatch = []; + if (buttons[0]) Bangle.btnWatch.push(setWatch(pressHandler.bind(this,0), BTN1, {repeat:true,edge:-1})); + if (buttons[1]) Bangle.btnWatch.push(setWatch(pressHandler.bind(this,1), BTN2, {repeat:true,edge:-1})); + if (buttons[2]) Bangle.btnWatch.push(setWatch(pressHandler.bind(this,2), BTN3, {repeat:true,edge:-1})); + this._l.width = g.getWidth()-8; // text width + this._l = {type:"h", content: [ + this._l, + {type:"v", content: buttons.map(b=>(b.type="txt",b.font="6x8",b.height=btnHeight,b.r=1,b))} + ]}; + } else { // no physical buttons, use touchscreen + this._l.width = g.getWidth()-20; // button width + this._l = {type:"h", content: [ + this._l, + {type:"v", content: buttons.map(b=>(b.type="btn",b.height=btnHeight,b.width=32,b.r=1,b))} + ]}; + Bangle.touchHandler = (_,e) => touchHandler(this._l,e); + Bangle.on('touch',Bangle.touchHandler); + } + } +} + +Layout.prototype.remove = function (l) { + if (Bangle.btnWatch) { + Bangle.btnWatch.forEach(clearWatch); + delete Bangle.btnWatch; + } + if (Bangle.touchHandler) { + Bangle.removeListener("touch",Bangle.touchHandler); + delete Bangle.touchHandler; + } +}; + +// Handler for button watch events +function pressHandler(btn,e) { + if (e.time-e.lastTime > 0.75 && this.b[btn].cbl) + this.b[btn].cbl(e); + else + if (this.b[btn].cb) this.b[btn].cb(e); +} + +// Handler for touch events +function touchHandler(l,e) { + if (l.type=="btn" && l.cb && e.x>=l.x && e.y>=l.y && e.x<=l.x+l.w && e.y<=l.y+l.h) + l.cb(e); + if (l.content) l.content.forEach(n => touchHandler(n,e)); +} + + +function updateMin(l) { + switch (l.type) { + case "txt": { + if (l.font.endsWith("%")) + l.font = "Vector"+Math.round(g.getHeight()*l.font.slice(0,-1)/100); + g.setFont(l.font); + l._h = g.getFontHeight(); + l._w = g.stringWidth(l.label); + break; + } + case "btn": { + l._h = 24; + l._w = 14 + l.label.length*8; + break; + } + case "img": { + var im = E.toString(l.src()); + l._h = im.charCodeAt(0); + l._w = im.charCodeAt(1); + break; + } + case "custom": { + // size should already be set up in width/height + l._w = 0; + l._h = 0; + break; + } + case "h": { + l.content.forEach(updateMin); + l._h = l.content.reduce((a,b)=>Math.max(a,b._h+(b.pad<<1)),0); + l._w = l.content.reduce((a,b)=>a+b._w+(b.pad<<1),0); + l.fill |= l.content.some(c=>c.fill); + break; + } + case "v": { + l.content.forEach(updateMin); + l._h = l.content.reduce((a,b)=>a+b._h+(b.pad<<1),0); + l._w = l.content.reduce((a,b)=>Math.max(a,b._w+(b.pad<<1)),0); + l.fill |= l.content.some(c=>c.fill); + break; + } + default: throw "Unknown item type "+l.type; + } + if (l.r&1) { // rotation + var t = l._w;l._w=l._h;l._h=t; + } + l._w = Math.max(l._w, 0|l.width); + l._h = Math.max(l._h, 0|l.height); +} +function render(l) { + if (!l) l = this.l; + g.reset(); + if (l.col) g.setColor(l.col); + switch (l.type) { + case "txt": + g.setFont(l.font).setFontAlign(0,0,l.r).drawString(l.label, l.x+(l.w>>1), l.y+(l.h>>1)); + break; + case "btn": + var poly = [ + l.x,l.y+4, + l.x+4,l.y, + l.x+l.w-5,l.y, + l.x+l.w-1,l.y+4, + l.x+l.w-1,l.y+l.h-5, + l.x+l.w-5,l.y+l.h-1, + l.x+4,l.y+l.h-1, + l.x,l.y+l.h-5, + l.x,l.y+4 + ]; + g.setColor(g.theme.bgH).fillPoly(poly).setColor(l.selected ? g.theme.fgH : g.theme.fg).drawPoly(poly).setFont("4x6",2).setFontAlign(0,0,l.r).drawString(l.label,l.x+l.w/2,l.y+l.h/2); + break; + case "img": + g.drawImage(l.src(), l.x, l.y); + break; + case "custom": + l.render(l); + break; + } + if (l.content) l.content.forEach(render); +} + +Layout.prototype.render = function (l) { + if (!l) l = this._l; + render(l); +}; + +Layout.prototype.layout = function (l) { + // l = current layout element + // exw,exh = extra width/height available + var fill = l.content.reduce((a,l)=>a+(0|l.fill),0); + switch (l.type) { + case "h": { + let x = l.x + (l.w-l._w)/2; + if (fill) { x = l.x; } + l.content.forEach(c => { + c.w = c._w + (c.fill?(l.w-l._w)/fill:0); + c.h = c.fill ? l.h : c._h; + c.x = x; + c.y = l.y + (1+(0|c.valign))*(l.h-c.h)/2; + x += c.w; + if (c.pad) { + x += c.pad*2; + c.x += c.pad; + c.y += c.pad; + } + if (c.content) { + this.layout(c); + } + }); + break; + } + case "v": { + let y = l.y + (l.h-l._h)/2; + if (fill) { y = l.y; } + l.content.forEach(c => { + c.w = c.fill ? l.w : c._w; + c.h = c._h + (c.fill?(l.h-l._h)/fill:0); + c.x = l.x + (1+(0|c.halign))*(l.w-c.w)/2; + c.y = y; + y += c.h; + if (c.pad) { + y += c.pad*2; + c.x += c.pad; + c.y += c.pad; + } + if (c.content) this.layout(c); + }); + break; + } + } +}; +Layout.prototype.debug = function(l,c) { + if (!l) l = this._l; + c=c||1; + g.setColor(c&1,c&2,c&4).drawRect(l.x+c-1, l.y+c-1, l.x+l.w-c, l.y+l.h-c); + c++; + if (l.content) l.content.forEach(n => this.debug(n,c)); +}; +Layout.prototype.update = function() { + var l = this._l; + var w = g.getWidth(); + var y = this.yOffset; + var h = g.getHeight()-y; + // update sizes + updateMin(l); + // center + if (l.fill) { + l.w = w; + l.h = h; + l.x = 0; + l.y = y; + } else { + l.w = l._w; + l.h = l._h; + l.x = (w-l.w)/2; + l.y = y+(h-l.h)/2; + } + // layout children + this.layout(l); +}; + +Layout.prototype.clear = function(l) { + if (!l) l = this._l; + g.reset().clearRect(l.x,l.y,l.x+l.w-1,l.y+l.h-1); +}; + +exports = Layout;