diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b57c91bb..c243093c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,3 +26,6 @@ Changed for individual apps are listed in `apps/appname/ChangeLog` * Added ability to specify dependencies (used for `notify` at the moment) * Fixed Promise-based bug in removeApp * Fixed bin/firmwaremaker and bin/apploader CLI to handle binary file uploads correctly +* Added progress bar on Bangle.js for uploads +* Provide a proper error message in case JSON decode fails +* Check you're connecting with a Bangle.js of the correct version diff --git a/README.md b/README.md index 22a12bd5b..240163f6c 100644 --- a/README.md +++ b/README.md @@ -219,7 +219,12 @@ and which gives information about the app for the Launcher. "shortName": "Short name", // short name for launcher "icon": "icon.png", // icon in apps/ "description": "...", // long description (can contain markdown) - "type":"...", // optional(if app) - 'app'/'widget'/'launch'/'bootloader' + "type":"...", // optional(if app) - + // 'app' - an application + // 'widget' - a widget + // 'launch' - replacement launcher app + // 'bootloader' - code that runs at startup only + // 'RAM' - code that runs and doesn't upload anything to storage "tags": "", // comma separated tag list for searching "dependencies" : { "notify":"type" } // optional, app 'types' we depend on // for instance this will use notify/notifyfs is they exist, or will pull in 'notify' @@ -241,7 +246,8 @@ and which gives information about the app for the Launcher. // add an icon to allow your app to be tested "storage": [ // list of files to add to storage - {"name":"appid.js", // filename to use in storage + {"name":"appid.js", // filename to use in storage. + // If name=='RAM', the code is sent directly to Bangle.js and is not saved to a file "url":"", // URL of file to load (currently relative to apps/) "content":"..." // if supplied, this content is loaded directly "evaluate":true // if supplied, data isn't quoted into a String before upload diff --git a/apps.json b/apps.json index 56a1613a1..eecd4271d 100644 --- a/apps.json +++ b/apps.json @@ -1,11 +1,11 @@ [ { "id": "boot", "name": "Bootloader", - "icon": "bootloader.png", - "version":"0.21", - "description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings", "tags": "tool,system", "type":"bootloader", + "icon": "bootloader.png", + "version":"0.22", + "description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings", "storage": [ {"name":".boot0","url":"boot0.js"}, {"name":".bootcde","url":"bootloader.js"} @@ -80,7 +80,7 @@ "name": "Notifications (default)", "shortName":"Notifications", "icon": "notify.png", - "version":"0.03", + "version":"0.05", "description": "A handler for displaying notifications that displays them in a bar at the top of the screen", "tags": "widget", "type": "notify", @@ -93,7 +93,7 @@ "name": "Fullscreen Notifications", "shortName":"Notifications", "icon": "notify.png", - "version":"0.05", + "version":"0.06", "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,6 +118,24 @@ {"name":"welcome.json"} ] }, + { "id": "mywelcome", + "name": "Customised Welcome", + "shortName": "My Welcome", + "icon": "app.png", + "version":"0.11", + "description": "Appears at first boot and explains how to use Bangle.js. Like 'Welcome', but can be customised with a greeting", + "tags": "start,welcome", + "custom":"custom.html", + "storage": [ + {"name":"mywelcome.boot.js","url":"boot.js"}, + {"name":"mywelcome.app.js","url":"app.js"}, + {"name":"mywelcome.settings.js","url":"settings.js"}, + {"name":"mywelcome.img","url":"app-icon.js","evaluate":true} + ], + "data": [ + {"name":"mywelcome.json"} + ] + }, { "id": "gbridge", "name": "Gadgetbridge", "icon": "app.png", @@ -152,7 +170,7 @@ { "id": "setting", "name": "Settings", "icon": "settings.png", - "version":"0.21", + "version":"0.22", "description": "A menu for setting up Bangle.js", "tags": "tool,system", "readme": "README.md", @@ -344,7 +362,7 @@ { "id": "gpsrec", "name": "GPS Recorder", "icon": "app.png", - "version":"0.12", + "version":"0.13", "interface": "interface.html", "description": "Application that allows you to record a GPS track. Can run in background", "tags": "tool,outdoors,gps,widget", @@ -429,6 +447,33 @@ {"name": "weather.json"} ] }, + { "id": "chargeanim", + "name": "Charge Animation", + "icon": "icon.png", + "version":"0.01", + "description": "When charging, show a sideways charging animation and keep the screen on. When removed from the charger load the clock again.", + "tags": "battery", + "allow_emulator":true, + "storage": [ + {"name":"chargeanim.app.js","url":"app.js"}, + {"name":"chargeanim.boot.js","url":"boot.js"}, + {"name":"chargeanim.img","url":"app-icon.js","evaluate":true} + ] + }, + { "id": "bluetoothdock", + "name": "Bluetooth Dock", + "shortName":"Dock", + "icon": "app.png", + "version":"0.01", + "description": "When charging shows the time, scans Bluetooth for known devices (eg temperature) and shows them on the screen", + "tags": "bluetooth", + "readme": "README.md", + "storage": [ + {"name":"bluetoothdock.app.js","url":"app.js"}, + {"name":"bluetoothdock.boot.js","url":"boot.js"}, + {"name":"bluetoothdock.img","url":"app-icon.js","evaluate":true} + ] + }, { "id": "widbat", "name": "Battery Level Widget", "icon": "widget.png", @@ -500,8 +545,8 @@ { "id": "hrm", "name": "Heart Rate Monitor", "icon": "heartrate.png", - "version":"0.01", - "description": "Measure your current heart rate", + "version":"0.02", + "description": "Measure your heart rate and see live sensor data", "tags": "health", "storage": [ {"name":"hrm.app.js","url":"heartrate.js"}, @@ -692,6 +737,19 @@ {"name":"sclock.img","url":"clock-simple-icon.js","evaluate":true} ] }, + { "id": "vibrclock", + "name": "Vibrate Clock", + "icon": "app.png", + "version":"0.01", + "description": "When BTN1 is pressed, vibrate out the time as a series of buzzes, one digit at a time. Hours, then Minutes. Zero is signified by one long buzz. Otherwise a simple digital clock.", + "tags": "clock", + "type":"clock", + "allow_emulator":true, + "storage": [ + {"name":"vibrclock.app.js","url":"app.js"}, + {"name":"vibrclock.img","url":"app-icon.js","evaluate":true} + ] + }, { "id": "svclock", "name": "Simple V-Clock", "icon": "vclock-simple.png", @@ -873,7 +931,7 @@ "id": "gpsinfo", "name": "GPS Info", "icon": "gps-info.png", - "version":"0.03", + "version":"0.04", "description": "An application that displays information about altitude, lat/lon, satellites and time", "tags": "gps", "type": "app", @@ -882,6 +940,16 @@ {"name":"gpsinfo.img","url": "gps-info-icon.js","evaluate": true} ] }, + { "id": "assistedgps", + "name": "Assisted GPS Update", + "icon": "app.png", + "version":"0.01", + "description": "Downloads assisted GPS data to Bangle.js for faster GPS startup and more accurate fixes", + "custom": "custom.html", + "tags": "tool,outdoors", + "type": "RAM", + "storage": [ ] + }, { "id": "pomodo", "name":"Pomodoro", @@ -1219,7 +1287,7 @@ "id": "rpgdice", "name": "RPG dice", "icon": "rpgdice.png", - "version": "0.01", + "version": "0.02", "description": "Simple RPG dice rolling app.", "tags": "game,fun", "type": "app", @@ -1382,7 +1450,7 @@ "name": "BLE Detector", "shortName":"BLE Detector", "icon": "bledetect.png", - "version":"0.02", + "version":"0.03", "description": "Detect BLE devices and show some informations.", "tags": "app,bluetooth,tool", "readme": "README.md", @@ -1560,6 +1628,21 @@ {"name":"hidcam.img","url":"app-icon.js","evaluate":true} ] }, + { "id": "swlclk", + "name": "SWL Clock / Short Wave Listner Clock", + "shortName": "SWL Clock", + "icon": "swlclk.png", + "version":"0.01", + "description": "Display Local, UTC time and some programs on the shorts waves along the day, with the frequencies", + "tags": "tool,clock", + "type":"clock", + "readme": "README.md", + "allow_emulator":true, + "storage": [ + {"name":"swlclk.app.js","url":"app.js"}, + {"name":"swlclk.img","url":"app-icon.js","evaluate":true} + ] + }, { "id": "rclock", "name": "Round clock with seconds, minutes and date", @@ -1667,7 +1750,7 @@ "id": "largeclock", "name": "Large Clock", "icon": "largeclock.png", - "version": "0.06", + "version": "0.07", "description": "A readable and informational digital watch, with date, seconds and moon phase", "readme": "README.md", "tags": "clock", @@ -1714,7 +1797,7 @@ "name": "Xiaomi Plant Sensor", "shortName":"Mi Plant", "icon": "app.png", - "version":"0.01", + "version":"0.02", "description": "Reads and displays data from Xiaomi bluetooth plant moisture sensors", "tags": "xiaomi,mi,plant,ble,bluetooth", "storage": [ @@ -1726,7 +1809,7 @@ "id": "simpletimer", "name": "Timer", "icon": "app.png", - "version": "0.05", + "version": "0.07", "description": "Simple timer, useful when playing board games or cooking", "tags": "timer", "readme": "README.md", @@ -1776,8 +1859,8 @@ "name": "Find Phone", "shortName":"Find Phone", "icon": "app.png", - "version":"0.01", - "description": "Find your phone via Gadgetbridge. Click any button to let your phone ring. 📳", + "version":"0.02", + "description": "Find your phone via Gadgetbridge. Click any button to let your phone ring. 📳 Note: The functionality is available even without this app, just go to Settings, App Settings, Gadgetbridge, Find Phone.", "tags": "tool,android", "readme": "README.md", "allow_emulator": true, @@ -1948,8 +2031,8 @@ "name": "Vertical watch face", "shortName":"Vertical Face", "icon": "app.png", - "version":"0.05", - "description": "A simple vertical watch face with the date.", + "version":"0.07", + "description": "A simple vertical watch face with the date. Heart rate monitor is toggled with BTN1", "tags": "clock", "type":"clock", "allow_emulator":true, @@ -2099,7 +2182,7 @@ "name": "Acceleration Recorder", "shortName":"Accel Rec", "icon": "app.png", - "version":"0.01", + "version":"0.02", "interface": "interface.html", "description": "This app puts the Bangle's accelerometer into 100Hz mode and reads 2 seconds worth of data after movement starts. The data can then be exported back to the PC.", "tags": "", @@ -2165,7 +2248,7 @@ {"id": "counter", "name": "Counter", "icon": "counter_icon.png", - "version": "0.01", + "version": "0.02", "description": "Simple counter", "tags": "tool", "allow_emulator": true, @@ -2217,6 +2300,19 @@ {"name":"cscsensor.img","url":"cscsensor-icon.js","evaluate":true} ] }, + { "id": "fileman", + "name": "File manager", + "shortName":"FileManager", + "icon": "icons8-filing-cabinet-48.png", + "version":"0.01", + "description": "Simple file manager, allows user to examine watch storage and display, load or delete individual files", + "tags": "tools", + "readme": "README.md", + "storage": [ + {"name":"fileman.app.js","url":"fileman.app.js"}, + {"name":"fileman.img","url":"fileman-icon.js","evaluate":true} + ] + }, { "id": "worldclock", "name": "World Clock - 4 time zones", "shortName":"World Clock", @@ -2232,5 +2328,165 @@ {"name":"worldclock.settings.json"}, {"name":"worldclock.img","url":"worldclock-icon.js","evaluate":true} ] - } + }, +{ "id": "digiclock", + "name": "Digital Clock Face", + "shortName":"Digi Clock", + "icon": "digiclock.png", + "version":"0.01", + "description": "A simple digital clock with the time, day, month, and year", + "tags": "clock", + "type" : "clock", + "storage": [ + {"name":"digiclock.app.js","url":"digiclock.js"}, + {"name":"digiclock.img","url":"digiclock-icon.js","evaluate":true} + ] +}, + { "id": "dsdrelay", + "name": "DSD BLE Relay controller", + "shortName":"DSDRelay", + "icon": "icons8-relay-48.png", + "version":"0.01", + "description": "Control BLE relay board from the watch", + "tags": "ble,bluetooth", + "readme": "README.md", + "storage": [ + {"name":"dsdrelay.app.js","url":"dsdrelay.app.js"}, + {"name":"dsdrelay.img","url":"dsdrelay-icon.js","evaluate":true} + ] + }, + { "id": "mandel", + "name": "Mandelbrot", + "shortName":"Mandel", + "icon": "mandel.png", + "version":"0.01", + "description": "Draw a zoomable Mandelbrot set", + "tags": "game", + "readme": "README.md", + "storage": [ + {"name":"mandel.app.js","url":"mandel.min.js"}, + {"name":"mandel.img","url":"mandel-icon.js","evaluate":true} + ] + }, + { + "id": "petrock", + "name": "Pet rock", + "icon": "petrock.png", + "version": "0.02", + "description": "A virtual pet rock with wobbly eyes", + "tags": "game", + "type": "app", + "storage": [ + {"name": "petrock.app.js", "url": "app.js"}, + {"name": "petrock.img", "url": "app-icon.js", "evaluate": true} + ] + }, + { "id": "smartibot", + "name": "Smartibot controller", + "shortName":"Smartibot", + "icon": "app.png", + "version":"0.01", + "description": "Control a [Smartibot Robot](https://thecraftyrobot.net/) straight from your Bangle.js", + "tags": "", + "storage": [ + {"name":"smartibot.app.js","url":"app.js"}, + {"name":"smartibot.img","url":"app-icon.js","evaluate":true} + ] + }, + { "id": "widncr", + "name": "NCR Logo Widget", + "icon": "widget.png", + "version":"0.01", + "description": "Show the NodeConf Remote logo in the top left", + "tags": "widget", + "type":"widget", + "storage": [ + {"name":"widncr.wid.js","url":"widget.js"} + ] + }, + { "id": "ncrclk", + "name": "NCR Clock", + "shortName":"NCR Clock", + "icon": "app.png", + "version":"0.01", + "description": "NodeConf Remote clock", + "tags": "clock", + "type": "clock", + "storage": [ + {"name":"ncrclk.app.js","url":"app.js"}, + {"name":"ncrclk.img","url":"app-icon.js","evaluate":true} + ] + }, +{ "id": "isoclock", + "name": "ISO Compliant Clock Face", + "shortName":"ISO Clock", + "icon": "isoclock.png", + "version":"0.01", + "description": "Tweaked fork of digiclock for ISO date and time", + "tags": "clock", + "type" : "clock", + "storage": [ + {"name":"isoclock.app.js","url":"isoclock.js"}, + {"name":"isoclock.img","url":"isoclock-icon.js","evaluate":true} + ] +}, +{ "id": "gpstimeserver", + "name": "GPS Time Server", + "icon": "widget.png", + "version":"0.01", + "description": "A widget which automatically starts the GPS and turns Bangle.js into a Bluetooth time server.", + "tags": "widget", + "type": "widget", + "readme": "README.md", + "storage": [ + {"name":"gpstimeserver.wid.js","url":"widget.js"} + ] +}, +{ "id": "tilthydro", + "name": "Tilt Hydrometer Display", + "shortName":"Tilt Hydro", + "icon": "app.png", + "version":"0.01", + "description": "A display for the [Tilt Hydrometer](https://tilthydrometer.com/) - [more info here](http://www.espruino.com/Tilt+Hydrometer+Display)", + "tags": "tools,bluetooth", + "storage": [ + {"name":"tilthydro.app.js","url":"app.js"}, + {"name":"tilthydro.img","url":"app-icon.js","evaluate":true} + ] +}, +{ "id": "supmariodark", + "name": "Super mario clock night mode", + "shortName":"supmariodark", + "icon": "supmariodark.png", + "version":"0.01", + "description": "Super mario clock in night mode", + "tags": "clock", + "type" : "clock", + "storage": [ + {"name":"supmariodark.app.js","url":"supmariodark.js"}, + {"name":"supmariodark.img","url":"supmariodark-icon.js","evaluate":true}, + {"name":"supmario30x24.bin","url":"supmario30x24.bin.js"}, + {"name":"supmario30x24.wdt","url":"supmario30x24.wdt.js"}, + {"name":"banner-up.img","url":"banner-up.js","evaluate":true}, + {"name":"banner-down.img","url":"banner-down.js","evaluate":true}, + {"name":"brick2.img","url":"brick2.js","evaluate":true}, + {"name":"enemy.img","url":"enemy.js","evaluate":true}, + {"name":"flower.img","url":"flower.js","evaluate":true}, + {"name":"flower_b.img","url":"flower_b.js","evaluate":true}, + {"name":"mario_wh.img","url":"mario_wh.js","evaluate":true}, + {"name":"pipe.img","url":"pipe.js","evaluate":true} + ] +}, +{ "id": "gmeter", + "name": "G-Meter", + "shortName":"G-Meter", + "icon": "app.png", + "version":"0.01", + "description": "Simple G-Meter", + "tags": "", + "storage": [ + {"name":"gmeter.app.js","url":"app.js"}, + {"name":"gmeter.img","url":"app-icon.js","evaluate":true} + ] +} ] diff --git a/apps/.eslintrc.json b/apps/.eslintrc.json index b8c5408e3..a9bb785ab 100644 --- a/apps/.eslintrc.json +++ b/apps/.eslintrc.json @@ -136,7 +136,7 @@ }, "rules": { "indent": [ - "warn", + "off", 2, { "SwitchCase": 1 diff --git a/apps/accelrec/ChangeLog b/apps/accelrec/ChangeLog index 5560f00bc..7327ae25f 100644 --- a/apps/accelrec/ChangeLog +++ b/apps/accelrec/ChangeLog @@ -1 +1,4 @@ 0.01: New App! +0.02: Increase record time to 5 second + Calculate the time moving in graph display + Trigger on 1.04g now, and record 10 samples before trigger diff --git a/apps/accelrec/app.js b/apps/accelrec/app.js index 5fb91e2e4..65f2a63ca 100644 --- a/apps/accelrec/app.js +++ b/apps/accelrec/app.js @@ -1,23 +1,25 @@ var acc; var HZ = 100; -var SAMPLES = 2*HZ; // 2 seconds +var SAMPLES = 5*HZ; // 5 seconds var SCALE = 5000; -var THRESH = 1.01; +var THRESH = 1.04; var accelx = new Int16Array(SAMPLES); var accely = new Int16Array(SAMPLES); // North var accelz = new Int16Array(SAMPLES); // Into clock face var accelIdx = 0; -var lastAccel = undefined; +var lastAccel; function accelHandlerTrigger(a) {"ram" if (a.mag*2>THRESH) { // *2 because 8g mode tStart = getTime(); g.drawString("Recording",g.getWidth()/2,g.getHeight()/2,1); Bangle.removeListener('accel',accelHandlerTrigger); Bangle.on('accel',accelHandlerRecord); - if (lastAccel) accelHandlerRecord(lastAccel); + lastAccel.forEach(accelHandlerRecord); accelHandlerRecord(a); + } else { + if (lastAccel.length>10) lastAccel.shift(); + lastAccel.push(a); } - lastAccel = a; } function accelHandlerRecord(a) {"ram" var i = accelIdx++; @@ -29,7 +31,8 @@ function accelHandlerRecord(a) {"ram" function recordStart() {"ram" Bangle.setLCDTimeout(0); // force LCD on accelIdx = 0; - lastAccel = undefined; + lastAccel = []; + Bangle.accelWr(0x18,0b01110100); // off, +-8g Bangle.accelWr(0x1B,0x03 | 0x40); // 100hz output, ODR/2 filter Bangle.accelWr(0x18,0b11110100); // +-8g Bangle.setPollInterval(10); // 100hz input @@ -42,8 +45,9 @@ function recordStart() {"ram" function recordStop() {"ram" - console.log("Length:",getTime()-tStart); + //console.log("Length:",getTime()-tStart); Bangle.setPollInterval(80); // default poll interval + Bangle.accelWr(0x18,0b01101100); // off, +-4g Bangle.accelWr(0x1B,0x0); // default 12.5hz output Bangle.accelWr(0x18,0b11101100); // +-4g Bangle.removeListener('accel',accelHandlerRecord); @@ -76,9 +80,14 @@ function showData() { // work out stats var maxAccel = 0; + var tStart = SAMPLES, tEnd = 0; var vel = 0, maxVel = 0; for (var i=0;i0.1) { + if (itEnd) tEnd=i; + } if (a>maxAccel) maxAccel=a; vel += a/HZ; if (vel>maxVel) maxVel=vel; @@ -87,6 +96,7 @@ function showData() { g.setFont("6x8").setFontAlign(1,0); g.drawString("Max Y Accel: "+maxAccel.toFixed(2)+" g",g.getWidth()-14,g.getHeight()-50); g.drawString("Max Y Vel: "+maxVel.toFixed(2)+" m/s",g.getWidth()-14,g.getHeight()-40); + g.drawString("Time moving: "+(tEnd-tStart)/HZ+" s",g.getWidth()-14,g.getHeight()-30); //console.log("End Velocity "+vel); g.setFont("6x8").setFontAlign(0,0,1); g.drawString("FINISH",g.getWidth()-4,g.getHeight()/2); diff --git a/apps/assistedgps/ChangeLog b/apps/assistedgps/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/assistedgps/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/assistedgps/app.png b/apps/assistedgps/app.png new file mode 100644 index 000000000..970e85139 Binary files /dev/null and b/apps/assistedgps/app.png differ diff --git a/apps/assistedgps/custom.html b/apps/assistedgps/custom.html new file mode 100644 index 000000000..e86c660b9 --- /dev/null +++ b/apps/assistedgps/custom.html @@ -0,0 +1,113 @@ + + + + + + +

Assisted GPS

+

GPS can take a long time (~5 minutes) to get an accurate position the first time it is used. + AGPS uploads a few hints to the GPS receiver about satellite positions that allow it + to get a faster, more accurate fix - however they are only valid for a short period of time.

+

You can upload data that covers a longer period of time, but the upload will take longer.

+
+ + + + + +
+

Click

+ + + + + + diff --git a/apps/blackjack/blackjack.app.js b/apps/blackjack/blackjack.app.js index ccc437e58..bbee8137b 100644 --- a/apps/blackjack/blackjack.app.js +++ b/apps/blackjack/blackjack.app.js @@ -59,7 +59,7 @@ function hitMe() { if(playerWeight == 21) EndGameMessdage('WINNER'); else if(playerWeight > 21) - EndGameMessdage('LOOSER'); + EndGameMessdage('LOSER'); } function calcWeight(hand, hideCard) { @@ -188,4 +188,4 @@ setWatch(hitMe, BTN4, {repeat:true, edge:"falling"}); setWatch(stand, BTN5, {repeat:true, edge:"falling"}); setWatch(startGame, BTN1, {repeat:true, edge:"falling"}); -startGame(); \ No newline at end of file +startGame(); diff --git a/apps/bledetect/ChangeLog b/apps/bledetect/ChangeLog index 520ccfa2f..e52015f04 100644 --- a/apps/bledetect/ChangeLog +++ b/apps/bledetect/ChangeLog @@ -1,2 +1,3 @@ 0.01: New App! -0.02: Fixed issue with wrong device informations \ No newline at end of file +0.02: Fixed issue with wrong device informations +0.03: Ensure manufacturer:undefined doesn't overflow screen diff --git a/apps/bledetect/bledetect.js b/apps/bledetect/bledetect.js index 2831b5b62..ca8699f9a 100644 --- a/apps/bledetect/bledetect.js +++ b/apps/bledetect/bledetect.js @@ -18,7 +18,7 @@ function showDeviceInfo(device){ value: device.rssi }, "manufacturer": { - value: device.manufacturer + value: device.manufacturer===undefined ? "-" : device.manufacturer } }; @@ -56,4 +56,4 @@ function waitMessage() { } scan(); -waitMessage(); \ No newline at end of file +waitMessage(); diff --git a/apps/bluetoothdock/ChangeLog b/apps/bluetoothdock/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/bluetoothdock/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/bluetoothdock/README.md b/apps/bluetoothdock/README.md new file mode 100644 index 000000000..37d6dd463 --- /dev/null +++ b/apps/bluetoothdock/README.md @@ -0,0 +1,35 @@ +# Charging Dock + +When charging shows the time, scans Bluetooth for known devices (eg temperature) and shows them on the screen. + +Rotates by 90 degrees if it detects it is sideways, allowing for use +in a Charging Dock. + +When devices are out of range (eg low water level in a plant) they are +highlighted red. + +Currently supported devices: + +* Mi Flora/other Xiaomi +* Bluetooth 0x1809 (eg. [Espruino Apps](https://espruino.github.io/EspruinoApps/#bletemp)) +* Espruino Manufacturer Data (0x0590) + +In the future it'd be nice to support more types of device in the future! + +## Espruino Devices + +To use your own Espruino device, use code like the following: + +``` +var data = {a:1,t:E.getTemperature()}; +NRF.setAdvertising({},{ + showName:false, + manufacturer:0x0590, + manufacturerData:JSON.stringify(data) +}); +``` + +Currently: + +* `t` is the temperature (if defined) +* `t` is the alert status (1 or 0) diff --git a/apps/bluetoothdock/add_to_apps.json b/apps/bluetoothdock/add_to_apps.json new file mode 100644 index 000000000..cb59dcdbe --- /dev/null +++ b/apps/bluetoothdock/add_to_apps.json @@ -0,0 +1,15 @@ +// Create an entry in apps.json as follows: +{ "id": "bluetoothdock", + "name": "Bluetooth Dock", + "shortName":"Dock", + "icon": "app.png", + "version":"0.01", + "description": "When charging shows the time, scans Bluetooth for known devices (eg temperature) and shows them on the screen", + "tags": "bluetooth", + "readme": "README.md", + "storage": [ + {"name":"bluetoothdock.app.js","url":"app.js"}, + {"name":"bluetoothdock.boot.js","url":"boot.js"}, + {"name":"bluetoothdock.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/bluetoothdock/app-icon.js b/apps/bluetoothdock/app-icon.js new file mode 100644 index 000000000..06e21d106 --- /dev/null +++ b/apps/bluetoothdock/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwggNK93gEikO93uC6gWBF6ECkQuVkUikAuVAAIuVAAIuGGZgXDlwuDhWkpWqAARHLkQpChWql3kC4YYHmQXDSQWq0Xu8QXE0AWEgYWESQIuC90qlQwJFwoABFwnyGBBdEC4guC1X/GBAXIVYJdC/4wFUw4XFFYX/GApIDC5BJBC4YwEC6QwEC5pHD+YwE0IXMGAX//U/GAgXNU4X60YwEU5YABnQXC0RhEFxkv+YXCl5iBF4gXKLQM6IgIuBGoIXCIxOqlRaBRoIABFwYXBUheqGAIACFwYXKBoYwBFwwXGVoQuDGAguEC4MzCwUQC4UKBwmvFw2qgczmUikAWCC4OikUzAAQvH+YXCCwcAmQVDC4YwFBIIVEgA3BAALADR48zmAWEh4VBPAS/DAIQXKJwIlDd4f6AgQXIIoSPCFwWqC4IFDL4YAFmAXCFIYXB0RhBKQRvDAAa/Dl4oCC4Mv//ya4gWFC4eiLAQUBFwgXBAA8Bc4qnCFwehC5EAC5AuD0AXRFwYXTFweqwAXJPAQXDFwh2JC5AuE0QXKJAouFLxQwGFwhGLPJAuPMI4uQDBAKD")) diff --git a/apps/bluetoothdock/app.js b/apps/bluetoothdock/app.js new file mode 100644 index 000000000..bb0ef4682 --- /dev/null +++ b/apps/bluetoothdock/app.js @@ -0,0 +1,182 @@ +var deviceInfo = {}; +if (Bangle.getAccel().x < -0.7) + g.setRotation(3); // assume watch in charge cradle +// Tile sizes +var TILESIZE = 60; +// Tiles along width of screen +var TILEX = 4; + +// Map devices to nice names... +var deviceNames = { + "eb:44:c1:71:2e:89 random" : "Office", + "c4:7c:8d:6a:ac:79 public" : "Peacelily" +}; + +var scanHandlers = [ + { filter : {serviceData:{"fe95":{}}}, // Xiaomi + handler : function(device) { + if (!device.serviceData["fe95"]) return; + var d = new DataView(device.serviceData["fe95"]); + var frame = d.getUint16(0,true); + var offset = 5; + if (frame&16) offset+=6; // mac address + if (frame&32) offset+=1; // capabilitities + if (frame&64) { // event + var l = d.getUint8(offset+2); + var code = d.getUint16(offset,true); + if (!deviceInfo[device.id]) deviceInfo[device.id]={id:device.id}; + event = deviceInfo[device.id]; + switch (code) { + case 0x1004: event.temperature = d.getInt16(offset+3,true)/10; break; + case 0x1006: event.humidity = d.getInt16(offset+3)/10; break; + case 0x100D: + event.temperature = d.getInt16(offset+3,true)/10; + event.humidity = d.getInt16(offset+5)/10; break; + case 0x1008: event.moisture = d.getUint8(offset+3); break; + case 0x1009: event.fertility = d.getUint16(offset+3,true)/10; break; + // case 0x1007: break; // 3 bytes? got 84,0,0 or 68,0,0 + default: event.code = code; + event.raw = new Uint8Array(d.buffer, offset+3, l); + break; + } + }}}, { + filter : {serviceData:{"1809":{}}}, // Standard Bluetooth + handler : function(device) { + if (!device.serviceData["1809"]) return; + var d = new DataView(device.serviceData["1809"]); + if (!deviceInfo[device.id]) deviceInfo[device.id]={id:device.id,name:device.name}; + event = deviceInfo[device.id]; + event.temperature = d.getInt16(0,1)/100; + }}, { + filter : { manufacturerData:{0x0590:{}} }, // Espruino + handler : function(device) { + if (!device.manufacturerData) return; + var j; + try { j = JSON.parse(E.toString(device.manufacturerData)); } + catch (e) { return; } // not JSON + if (!deviceInfo[device.id]) deviceInfo[device.id]={id:device.id,name:device.name}; + event = deviceInfo[device.id]; + if (j.t) event.temperature = j.t; + if (j.a) event.alert = j.a; + }} +]; + +function getImgHum() { + return require("heatshrink").decompress(atob("jUoxH+AEtlsoYYDS4ZYDAYaVDLAYFDSQYHDSIZYDBIaPDLAYLDRoZYDBoaLDLAYPDRIZYDCIaHDLAYTDQoZYDCoaDDOQYXAA+JxIYX1utDSwYBAAIzYGiwZUTgpODQpzPGGgY3OdI4aRDIIaMDJIYCDIztDGRwaJP5oaWDAwaRDBAbOC5YcKB5I=")); +} +function getImgTemp() { + return require("heatshrink").decompress(atob("iUqxH+AA2sAAQLHCBASMCAoSLCPOBAAQRfI/5Hn3YACy4ACCL4ADCL5H/I/AQHCRAQJCQwQLCQgQNCQYRQCB4A/ADaPjYqTpSCRYQGCZALFA")); +} + +function drawAlert(tile,x,y) { + g.setFont("Vector",56).setFontAlign(0,0); + g.drawString("!",x+TILESIZE/2,y+10+TILESIZE/2); +} + +function drawMoisture(tile,x,y) { + g.drawImage(getImgHum(),x+2,y+18); + g.setFont("Vector",28); + g.drawString(tile.device.moisture,x+26,y+12); +} + +function drawTemperature(tile,x,y) { + g.drawImage(getImgTemp(),x+3,y+16); + g.setFont("Vector",30); + var t = Math.round(tile.device.temperature); + g.drawString(t,x+25,y+13); +} + +function getTiles() { + var tiles = []; + Object.keys(deviceInfo).forEach(id=>{ + var dev = deviceInfo[id]; + if (dev.alert) { + tiles.push({ + alert: true, device: dev, + draw: drawAlert + }); + } + if (dev.moisture && dev.moisture<40) { + tiles.push({ + alert: true, device: dev, + draw: drawMoisture + }); + } + if (dev.temperature) { + tiles.push({ + device: dev, + draw: drawTemperature + }); + } + }); + tiles.sort((a,b)=>(b.alert|0)-(a.alert|0)) + return tiles; +} + + +g.clear(); +require("Font7x11Numeric7Seg").add(Graphics); +function drawClock() { + var d = new Date(); + var size = 3; + var x = (g.getWidth()/2) - size*6, + y = size; + g.reset(); + g.setFont("7x11Numeric7Seg",size).setFontAlign(1,-1); + g.drawString(d.getHours(), x, y, true); + g.setFontAlign(-1,-1); + if (d.getSeconds()&1) g.drawString(":", x,y); + g.drawString(("0"+d.getMinutes()).substr(-2),x+size*4,y, true); + // draw seconds + g.setFont("7x11Numeric7Seg",size/2); + g.drawString(("0"+d.getSeconds()).substr(-2),x+size*18,y + size*7, true); + // date + var s = d.toString().split(" ").slice(0,4).join(" "); + g.reset().setFontAlign(0,-1); + g.drawString(s,g.getWidth()/2, y + size*12, true); + // keep screen on + g.flip(); +} +function drawTiles() { + // draw tiles + var tiles = getTiles(); + for (var i=0;i<6;i++) { + var x = (i%TILEX)*TILESIZE; + var y = TILESIZE + TILESIZE*((i/TILEX)|0); + g.reset(); + var tile = tiles[i]; + if (tile && tile.alert) { + g.setBgColor(0.5,0,0); + } + g.clearRect(x,y,x+TILESIZE-1,y+TILESIZE-1); + if (tile) { + g.reset().setFont("6x8"); + var t = deviceNames[tile.device.id]; + if (!t) t = tile.device.name || tile.device.id.substr(0,17); + g.drawString(t,x+2,y+2); + tile.draw(tile, x, y); + if (tile.alert) { + g.setColor(1,1,0); + g.drawRect(x,y,x+TILESIZE-1,y+TILESIZE-1); + } + } + } + g.flip(); // keep forcing display on +} + +setInterval(drawClock, 1000); +setInterval(drawTiles, 10000); +drawClock(); +drawTiles(); + +function parseDevice(dev) { + if (!dev.serviceData) dev.serviceData={}; + scanHandlers.forEach(s=>s.handler(dev)); +} +NRF.setScan(parseDevice, { filters: scanHandlers.map(s=>s.filter), timeout: 2000 }); + +if (Bangle.isCharging()) { + Bangle.on("charging", isCharging => { + if (!isCharging) load(); + }); +} diff --git a/apps/bluetoothdock/app.png b/apps/bluetoothdock/app.png new file mode 100644 index 000000000..db489f8c1 Binary files /dev/null and b/apps/bluetoothdock/app.png differ diff --git a/apps/bluetoothdock/boot.js b/apps/bluetoothdock/boot.js new file mode 100644 index 000000000..93d5fe63f --- /dev/null +++ b/apps/bluetoothdock/boot.js @@ -0,0 +1 @@ +Bangle.on("charging", isCharging => { if (isCharging) load("bluetoothdock.app.js"); }); diff --git a/apps/boot/ChangeLog b/apps/boot/ChangeLog index d2f68fd0e..7e9fd4a81 100644 --- a/apps/boot/ChangeLog +++ b/apps/boot/ChangeLog @@ -20,3 +20,4 @@ 0.19: Tweaks to simplify code and lower memory usage 0.20: Allow Gadgetbridge to work even with programmable:off 0.21: Handle echo off char from Gadgetbridge app when programmable:off (fix #558) +0.22: Stop LCD timeout being disabled on first run (when there is no settings.json) diff --git a/apps/boot/boot0.js b/apps/boot/boot0.js index 630252dea..550513b11 100644 --- a/apps/boot/boot0.js +++ b/apps/boot/boot0.js @@ -50,7 +50,7 @@ if (!Bangle.F_BEEPSET) { }); }; } -Bangle.setLCDTimeout(s.timeout); +if (s.timeout!==undefined) Bangle.setLCDTimeout(s.timeout); if (!s.timeout) Bangle.setLCDPower(1); E.setTimeZone(s.timezone); delete s; diff --git a/apps/chargeanim/ChangeLog b/apps/chargeanim/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/chargeanim/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/chargeanim/app-icon.js b/apps/chargeanim/app-icon.js new file mode 100644 index 000000000..0252d9ac2 --- /dev/null +++ b/apps/chargeanim/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwxH+AH4A/AH4ASwoAIF/4pZABYuuGDIv/F/4v/F/4vt1Ivt6Zft1Ivu6fNF9nT6a+JF8SNBF9ouBXxQvhFwQvrRoTuLF8BeDXxQvfFwYAFGgwvdRoYAGd840GX84AC5rCLF8ReLF8wMJF8K+CRpAvmNhQvhdwIuKF8SNLF8guLF8JdML8Yv/F/4v/F/4v/GDguYAH4A/AGYA==")) diff --git a/apps/chargeanim/app.js b/apps/chargeanim/app.js new file mode 100644 index 000000000..c2702337a --- /dev/null +++ b/apps/chargeanim/app.js @@ -0,0 +1,28 @@ +g.clear().flip(); +var imgbat = require("heatshrink").decompress(atob("nlWhH+AH4A/AH4AHwoAQHXQ8pHf47rF6YAXHXQ8OHVo8NHf47/Hf47/Hf47/Hf47/Hf47/Hf47r1I766Y756Z351I766ayTHco6BHfCxBHfI6CdyY7jHQQ73WIayUHcQ6DHew6EHeqxEdyo7gOwo70HQqyVHbyxFHeo6GHeY6Hdyo7cWI47zHQ6yWHbY6IHeKxIABa9MHbI6TQJo7YHUI7YWMKzbQKQYOHdYYPHcK9IWJw7sDKA7hHTA7pWKA7qDKQ7gdwwaTHcyxSHcR2ZHcwZUHcqxUHcLuEHSo7kHSw7gWLI7kHS47iHTA7fdwKxYHcQ6ZHb46bO8A76ADg7/Hf47/Hf47/Hf47/Hf47/Hf47/HbY8uHRg8tHRwA/AH4AsA==")); +var imgbubble = require("heatshrink").decompress(atob("ikQhH+AAc0AAgKEAAwRFCpgMDnVerwULCIuCCYoUGCQQQBnQ9MA4Q3GChI5DEpATIJYISKCY46LCYwANCa4UObJ7INeCoSOCpAOI")); + + var W=240,H=240; +var bubbles = []; +for (var i=0;i<10;i++) { + bubbles.push({y:Math.random()*H,ly:0,x:(0.5+(i<5?i:i+8))*W/18,v:0.6+Math.random(),s:0.5+Math.random()}); +} + +function anim() { + /* we don't use any kind of buffering here. Just draw one image + at a time (image contains a background) too, and there is minimal + flicker. */ + var mx = 120, my = 120; + bubbles.forEach(f=>{ + f.y-=f.v;if (f.y<-24) f.y=H+8; + g.drawImage(imgbubble,f.y,f.x,{scale:f.s}); + }); + g.drawImage(imgbat, mx,my,{rotate:Math.sin(getTime()*2)*0.5-Math.PI/2}); + g.flip(); +} + +setInterval(anim,20); + +Bangle.on("charging", isCharging => { + if (!isCharging) load(); +}); diff --git a/apps/chargeanim/boot.js b/apps/chargeanim/boot.js new file mode 100644 index 000000000..cbc78681b --- /dev/null +++ b/apps/chargeanim/boot.js @@ -0,0 +1 @@ +Bangle.on("charging", isCharging => { if (isCharging) load("chargeanim.app.js"); }); diff --git a/apps/chargeanim/icon.png b/apps/chargeanim/icon.png new file mode 100644 index 000000000..9860c323d Binary files /dev/null and b/apps/chargeanim/icon.png differ diff --git a/apps/counter/ChangeLog b/apps/counter/ChangeLog index 5560f00bc..8d0f821fd 100644 --- a/apps/counter/ChangeLog +++ b/apps/counter/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: Added decrement and touch functions diff --git a/apps/counter/counter.js b/apps/counter/counter.js index 9f77e34d8..86db23ba9 100644 --- a/apps/counter/counter.js +++ b/apps/counter/counter.js @@ -6,22 +6,41 @@ function updateScreen() { g.clearRect(0, 50, 250, 150); g.setFont("Vector",40).setFontAlign(0,0); g.drawString(Math.floor(counter), g.getWidth()/2, 100); + g.drawString('-', 45, 100); + g.drawString('+', 185, 100); } -// add a count by using BTN1 +// add a count by using BTN1 or BTN5 setWatch(() => { counter += 1; updateScreen(); }, BTN1, {repeat:true}); setWatch(() => { - counter = 0; + counter += 1; + updateScreen(); +}, BTN5, {repeat:true}); + +// subtract a count by using BTN3 or BTN4 +setWatch(() => { + counter -= 1; + updateScreen(); +}, BTN4, {repeat:true}); + +setWatch(() => { + counter -= 1; updateScreen(); }, BTN3, {repeat:true}); +// reset by using BTN2 +setWatch(() => { + counter = 0; + updateScreen(); +}, BTN2, {repeat:true}); + g.clear(1).setFont("6x8"); -g.drawString('Use BTN1 to increase\nthe counter by one.\nUse BTN3 to reset counter.', 25, 200); +g.drawString('Tap right or BTN1 to increase\nTap left or BTN3 to decrease\nPress BTN2 to reset.', 25, 200); Bangle.loadWidgets(); Bangle.drawWidgets(); diff --git a/apps/cscsensor/README.md b/apps/cscsensor/README.md index 8ba862241..a31a4dc28 100644 --- a/apps/cscsensor/README.md +++ b/apps/cscsensor/README.md @@ -9,8 +9,10 @@ Currently the app displays the following data: - maximum speed - trip distance traveled - total distance traveled +- an icon with the battery status of the remote sensor Button 1 resets all measurements except total distance traveled. The latter gets preserved by being written to storage every 0.1 miles and upon exiting the app. +If the watch app has not received an update from the sensor for at least 10 seconds, pushing button 3 will attempt to reconnect to the sensor. I do not have access to a cadence sensor at the moment, so only the speed part is currently implemented. Values displayed are imperial or metric (depending on locale), the wheel circumference can be adjusted in the global settings app. diff --git a/apps/cscsensor/cscsensor.app.js b/apps/cscsensor/cscsensor.app.js index 65b50dfe7..c402c06da 100644 --- a/apps/cscsensor/cscsensor.app.js +++ b/apps/cscsensor/cscsensor.app.js @@ -26,8 +26,10 @@ class CSCSensor { this.speedUnit = this.qMetric ? "km/h" : "mph"; this.distUnit = this.qMetric ? "km" : "mi"; this.distFactor = this.qMetric ? 1.609344 : 1; + this.screenInit = true; this.batteryLevel = -1; } + reset() { this.settings.totaldist = this.totaldist; storage.writeJSON(SETTINGS_FILE, this.settings); @@ -35,10 +37,31 @@ class CSCSensor { this.movingTime = 0; this.lastRevsStart = this.lastRevs; this.maxSpeed = 0; + this.screenInit = true; } + setBatteryLevel(level) { - this.batteryLevel = level; + if (level!=this.batteryLevel) { + this.batteryLevel = level; + this.drawBatteryIcon(); + } } + + updateBatteryLevel(event) { + if (event.target.uuid == "0x2a19") this.setBatteryLevel(event.target.value.getUint8(0)); + } + + drawBatteryIcon() { + g.setColor(1, 1, 1).drawRect(10, 55, 20, 75).fillRect(14, 53, 16, 55).setColor(0).fillRect(11, 56, 19, 74); + if (this.batteryLevel!=-1) { + if (this.batteryLevel<25) g.setColor(1, 0, 0); + else if (this.batteryLevel<50) g.setColor(1, 0.5, 0); + else g.setColor(0, 1, 0); + g.fillRect(11, 74-18*this.batteryLevel/100, 19, 74); + } + else g.setFontVector(14).setFontAlign(0, 0, 0).setColor(0xffff).drawString("?", 16, 66); + } + updateScreen() { var dist = this.distFactor*(this.lastRevs-this.lastRevsStart)*this.wheelCirc/63360.0; var ddist = Math.round(100*dist)/100; @@ -48,41 +71,44 @@ class CSCSensor { if (dmins.length<2) dmins = "0"+dmins; var dsecs = (Math.floor(this.movingTime) % 60).toString(); if (dsecs.length<2) dsecs = "0"+dsecs; - var avespeed = (this.movingTime>2 ? Math.round(10*dist/(this.movingTime/3600))/10 : 0); + var avespeed = (this.movingTime>3 ? Math.round(10*dist/(this.movingTime/3600))/10 : 0); var maxspeed = Math.round(10*this.distFactor*this.maxSpeed)/10; - for (var i=0; i<6; ++i) { - if ((i&1)==0) g.setColor(0, 0, 0); - else g.setColor(0.2, 0.1, 0.4); - g.fillRect(0, 48+i*32, 86, 48+(i+1)*32); - if ((i&1)==1) g.setColor(0, 0, 0); - else g.setColor(0.2, 0.1, 0.4); - g.fillRect(87, 48+i*32, 239, 48+(i+1)*32); - g.setColor(0.5, 0.5, 0.5).drawRect(87, 48+i*32, 239, 48+(i+1)*32).drawLine(0, 239, 239, 239).drawRect(0, 48, 87, 239); - } - g.setFontAlign(1, 0, 0).setFontVector(19).setColor(1, 1, 0); - g.drawString("Time:", 87, 66); - g.drawString("Speed:", 87, 98); - g.drawString("Ave spd:", 87, 130); - g.drawString("Max spd:", 87, 162); - g.drawString("Trip:", 87, 194); - g.drawString("Total:", 87, 226); - g.setFontAlign(-1, 0, 0).setFontVector(26).setColor(1, 1, 1);//.clearRect(92, 60, 239, 239); - g.drawString(dmins+":"+dsecs, 92, 66); - g.drawString(dspeed+" "+this.speedUnit, 92, 98); - g.drawString(avespeed + " " + this.speedUnit, 92, 130); - g.drawString(maxspeed + " " + this.speedUnit, 92, 162); - g.drawString(ddist + " " + this.distUnit, 92, 194); - g.drawString(tdist + " " + this.distUnit, 92, 226); - if (this.batteryLevel!=-1) { - g.setColor(1, 1, 1).drawRect(10, 55, 20, 75).fillRect(14, 53, 16, 55); - if (this.batteryLevel<25) g.setColor(1, 0, 0); - else if (this.batteryLevel<50) g.setColor(1, 0.5, 0); - else g.setColor(0, 1, 0); - g.fillRect(11, 74-18*this.batteryLevel/100, 19, 74); - console.log(this.batteryLevel); - this.batteryLevel = -1; + if (this.screenInit) { + for (var i=0; i<6; ++i) { + if ((i&1)==0) g.setColor(0, 0, 0); + else g.setColor(0x30cd); + g.fillRect(0, 48+i*32, 86, 48+(i+1)*32); + if ((i&1)==1) g.setColor(0); + else g.setColor(0x30cd); + g.fillRect(87, 48+i*32, 239, 48+(i+1)*32); + g.setColor(0.5, 0.5, 0.5).drawRect(87, 48+i*32, 239, 48+(i+1)*32).drawLine(0, 239, 239, 239);//.drawRect(0, 48, 87, 239); + g.moveTo(0, 80).lineTo(30, 80).lineTo(30, 48).lineTo(87, 48).lineTo(87, 239).lineTo(0, 239).lineTo(0, 80); + } + g.setFontAlign(1, 0, 0).setFontVector(19).setColor(1, 1, 0); + g.drawString("Time:", 87, 66); + g.drawString("Speed:", 87, 98); + g.drawString("Ave spd:", 87, 130); + g.drawString("Max spd:", 87, 162); + g.drawString("Trip:", 87, 194); + g.drawString("Total:", 87, 226); + this.drawBatteryIcon(); + this.screenInit = false; } + g.setFontAlign(-1, 0, 0).setFontVector(26); + g.setColor(0x30cd).fillRect(88, 49, 238, 79); + g.setColor(0xffff).drawString(dmins+":"+dsecs, 92, 66); + g.setColor(0).fillRect(88, 81, 238, 111); + g.setColor(0xffff).drawString(dspeed+" "+this.speedUnit, 92, 98); + g.setColor(0x30cd).fillRect(88, 113, 238, 143); + g.setColor(0xffff).drawString(avespeed + " " + this.speedUnit, 92, 130); + g.setColor(0).fillRect(88, 145, 238, 175); + g.setColor(0xffff).drawString(maxspeed + " " + this.speedUnit, 92, 162); + g.setColor(0x30cd).fillRect(88, 177, 238, 207); + g.setColor(0xffff).drawString(ddist + " " + this.distUnit, 92, 194); + g.setColor(0).fillRect(88, 209, 238, 238); + g.setColor(0xffff).drawString(tdist + " " + this.distUnit, 92, 226); } + updateSensor(event) { var qChanged = false; if (event.target.uuid == "0x2a5b") { @@ -103,7 +129,7 @@ class CSCSensor { var dBT = (Date.now()-this.lastBangleTime)/1000; this.lastBangleTime = Date.now(); if (dT<0) dT+=64; - if (Math.abs(dT-dBT)>2) dT = dBT; + if (Math.abs(dT-dBT)>3) dT = dBT; this.lastTime = wheelTime; this.speed = this.lastSpeed; if (dRevs>0 && dT>0) { @@ -120,7 +146,7 @@ class CSCSensor { } } this.lastSpeed = this.speed; - if (this.speed > this.maxSpeed) this.maxSpeed = this.speed; + if (this.speed>this.maxSpeed && (this.movingTime>3 || this.speed<20) && this.speed<50) this.maxSpeed = this.speed; } if (qChanged && this.qUpdateScreen) this.updateScreen(); } @@ -132,9 +158,8 @@ function getSensorBatteryLevel(gatt) { gatt.getPrimaryService("180f").then(function(s) { return s.getCharacteristic("2a19"); }).then(function(c) { - return c.readValue(); - }).then(function(d) { - mySensor.setBatteryLevel(d.buffer[0]); + c.on('characteristicvaluechanged', (event)=>mySensor.updateBatteryLevel(event)); + return c.startNotifications(); }); } @@ -159,18 +184,21 @@ function parseDevice(d) { mySensor.updateScreen(); }).catch(function(e) { g.clearRect(0, 60, 239, 239).setColor(1, 0, 0).setFontAlign(0, 0, 0).drawString("ERROR"+e, 120, 120).flip(); - console.log(e); + console.log(e); })} function connection_setup() { + NRF.setScan(); + mySensor.screenInit = true; NRF.setScan(parseDevice, { filters: [{services:["1816"]}], timeout: 2000}); - g.clearRect(0, 60, 239, 239).setFontVector(18).setFontAlign(0, 0, 0).setColor(0, 1, 0); + g.clearRect(0, 48, 239, 239).setFontVector(18).setFontAlign(0, 0, 0).setColor(0, 1, 0); g.drawString("Scanning for CSC sensor...", 120, 120); } connection_setup(); -setWatch(function() { mySensor.reset(); g.clearRect(0, 60, 239, 239); mySensor.updateScreen(); }, BTN1, {repeat:true, debounce:20}); +setWatch(function() { mySensor.reset(); g.clearRect(0, 48, 239, 239); mySensor.updateScreen(); }, BTN1, {repeat:true, debounce:20}); E.on('kill',()=>{ if (gatt!=undefined) gatt.disconnect(); mySensor.settings.totaldist = mySensor.totaldist; storage.writeJSON(SETTINGS_FILE, mySensor.settings); }); +setWatch(function() { if (Date.now()-mySensor.lastBangleTime>10000) connection_setup(); }, BTN3, {repeat:true, debounce:20}); NRF.on('disconnect', connection_setup); Bangle.loadWidgets(); diff --git a/apps/digiclock/ChangeLog b/apps/digiclock/ChangeLog new file mode 100644 index 000000000..0bb55854e --- /dev/null +++ b/apps/digiclock/ChangeLog @@ -0,0 +1 @@ +0.01: App Made! diff --git a/apps/digiclock/digiclock-icon.js b/apps/digiclock/digiclock-icon.js new file mode 100644 index 000000000..737561863 --- /dev/null +++ b/apps/digiclock/digiclock-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("/wA/AH4A/AH4A/ACmsAEQuMlcAAD0rGBQKBFr4ADGBOsqwvjqwvJRsCRFF/8Gg4ADEZYQEgwvWg8+AAgwKCJgvQDgoABF5IRMF5xEBJpBhGCJwvNDQM4AYMNAAQaBnCAFCJ4vNIwQeBAAkxQAwGCmIRFFwIRDF64dDgwGBgwRNF/4v/F/4v/F/4v/F/4dJmIdECIkxF7MHFwUHhoACg4eCAYIACCJ4vNDQIgCAAgICKwoROF5yAEAAgtFCKAvQJpAAICJgvQgEGg4ADFxIwCAAcGBYovRADov6qwvjqwvJ1gvjEoIvHGASRgRoIuJGAYAhFxQA/AH4A/AH4A/ABQ")) diff --git a/apps/digiclock/digiclock.js b/apps/digiclock/digiclock.js new file mode 100644 index 000000000..7f74f2242 --- /dev/null +++ b/apps/digiclock/digiclock.js @@ -0,0 +1,154 @@ +//load fonts +require("Font7x11Numeric7Seg").add(Graphics); +require("FontHaxorNarrow7x17").add(Graphics); +//screen position +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 month = date.getMonth(); + var dateNum = date.getDate(); + var year = date.getFullYear(); + var half = "AM"; + var time = (" " + h).substr(-2) + ":" + ("0" + m).substr(-2); + + //convert day into string + switch (day) { + case 0: + day = "Sunday"; + break; + + case 1: + day = "Monday"; + break; + + case 2: + day = "Tuesday"; + break; + + case 3: + day = "Wednesday"; + break; + + case 4: + day = "Thursday"; + break; + + case 5: + day = "Friday"; + break; + + case 6: + day = "Saturday"; + break; + + default: + day = "ERROR"; + break; + } + + //convert month into String + switch(month) { + case 0: + month = "Jan"; + break; + + case 1: + month = "Feb"; + break; + + case 2: + month = "Mar"; + break; + + case 3: + month = "Apr"; + break; + + case 4: + month = "May"; + break; + + case 5: + month = "Jun"; + break; + + case 6: + month = "Jul"; + break; + + case 7: + month = "Aug"; + break; + + case 8: + month = "Sep"; + break; + + case 9: + month = "Oct"; + break; + + case 10: + month = "Nov"; + break; + + case 11: + month = "Dec"; + break; + + default: + month = "ERROR"; + break; + + } + + if (h > 12) { + half = "PM"; + h = h - 12; + } + //reset graphics + g.reset(); + //draw the time + g.setFont("7x11Numeric7Seg", 5); + g.setFontAlign(1,1); + g.drawString(time, X, Y, true /*clear background*/); + g.setFont("7x11Numeric7Seg", 3); + g.drawString(("0"+date.getSeconds()).substr(-2), X+50, Y, true /*clear background*/); + g.setFontAlign(0,1); + g.setFont("HaxorNarrow7x17", 2); + g.drawString(half, X+30, Y-35, true); + g.setFont("HaxorNarrow7x17", 3); + g.drawString(day, X-60, Y+53, true); + g.drawString(month, X-100, Y+95, true); + g.drawString(dateNum, X-40, Y+95, true); + g.drawString(year, X-90, Y-55, true); + + +} + +//clear screen at startup +g.clear(); +//draw immediatly +draw(); + +var secondInterval = setInterval(draw, 1000); +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ + if (secondInterval) clearInterval(secondInterval); + secondInterval = undefined; + if (on) { + secondInterval = setInterval(draw, 1000); + draw(); // draw immediately + } +}); + +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +setWatch(Bangle.showLauncher, BTN2, {repeat : false, edge: "falling"}); diff --git a/apps/digiclock/digiclock.png b/apps/digiclock/digiclock.png new file mode 100644 index 000000000..e70978c37 Binary files /dev/null and b/apps/digiclock/digiclock.png differ diff --git a/apps/dsdrelay/ChangeLog b/apps/dsdrelay/ChangeLog new file mode 100644 index 000000000..1a3bc1757 --- /dev/null +++ b/apps/dsdrelay/ChangeLog @@ -0,0 +1 @@ +0.01: New app! diff --git a/apps/dsdrelay/README.md b/apps/dsdrelay/README.md new file mode 100644 index 000000000..395aba636 --- /dev/null +++ b/apps/dsdrelay/README.md @@ -0,0 +1,14 @@ +# DSDRelay + +Small app to control DSD Tech BLE relay boards from the watch. I have seen them being sold as 1-, 2- and 4-relay boards. The app shows controls for +4 relays, regardless of the actual configuration of the board connected. + +![](dsdrelay-pic.jpg) + +## Controls +- buttons 1 and 3 cycle the selection of the currently active channel +- swipe right turns the selected channel's relay *on* +- swipe left turns the selected channel's relay *off* + +I only own a 1-relay board, so only the "Ch 1" functionality was tested; the other channels were implemented per the manufacturer's documentation. +In particular, the method for determining the relay states on app startup for channels 2-4 was mostly an educated guess. diff --git a/apps/dsdrelay/dsdrelay-icon.js b/apps/dsdrelay/dsdrelay-icon.js new file mode 100644 index 000000000..ac98e6eea --- /dev/null +++ b/apps/dsdrelay/dsdrelay-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwhC/AH4A/AAcK1QAO0AXFCx4ABFyowGC/4X/C/4X/C48AC6IWEGCIuFAAUN6AED7vdAwgEDC/4X/C/4X86AGGC85fpAH4A/AH4ASA")) diff --git a/apps/dsdrelay/dsdrelay-pic.jpg b/apps/dsdrelay/dsdrelay-pic.jpg new file mode 100644 index 000000000..879f995cb Binary files /dev/null and b/apps/dsdrelay/dsdrelay-pic.jpg differ diff --git a/apps/dsdrelay/dsdrelay.app.js b/apps/dsdrelay/dsdrelay.app.js new file mode 100644 index 000000000..18e7293aa --- /dev/null +++ b/apps/dsdrelay/dsdrelay.app.js @@ -0,0 +1,116 @@ +var device; +var gatt; +var service; +var characteristic; + +// on/off commands +// Channel 1 ON: A00101A2 +// Channel 1 OFF: A00100A1 +// Channel 2 ON: A00201A3 +// Channel 2 OFF: A00200A2 +// Channel 3 ON: A00301A4 +// Channel 3 OFF: A00300A3 +// Channel 4 ON: A00401A5 +// Channel 4 OFF: A00400A4 + +var cmds = [{ on: new Uint8Array([0xa0, 0x01, 0x01, 0xa2]), + off: new Uint8Array([0xa0, 0x01, 0x00, 0xa1]) }, + { on: new Uint8Array([0xa0, 0x02, 0x01, 0xa3]), + off: new Uint8Array([0xa0, 0x02, 0x00, 0xa2]) }, + { on: new Uint8Array([0xa0, 0x03, 0x01, 0xa4]), + off: new Uint8Array([0xa0, 0x03, 0x00, 0xa3]) }, + { on: new Uint8Array([0xa0, 0x04, 0x01, 0xa5]), + off: new Uint8Array([0xa0, 0x04, 0x00, 0xa4]) }]; + +const button_w = 100; +const button_h = 36; +const button_r = button_h/2-4; +const button_sp = 46; + +var n_channels = 4; +var channel = 0; +var channel_state = []; + +function drawButton(x, y, state) { + if (state) g.setColor(0.2, 0.2, 0.95); + else g.setColor(0.5, 0.5, 0.5); + g.fillCircle(x+button_h/2, y+button_h/2, button_h/2). + fillRect(x+button_h/2, y, x+button_w-button_h/2, y+button_h). + fillCircle(x+button_w-button_h/2, y+button_h/2, button_h/2); + g.setColor(0.85, 0.85, 0.85); + if (state) + g.fillCircle(x+button_w-button_h/2, y+button_h/2, button_r); + else + g.fillCircle(x+button_h/2, y+button_h/2, button_r); + g.flip(); +} + +function setup_screen() { + g.clearRect(0, 60, g.getWidth()-1, g.getHeight()-1); + for (var i=0; i<4; ++i) { + g.setFontVector(22).setFontAlign(-1, 0, 0).setColor(0xffff).drawString("Ch"+String(i+1), 16, 60+i*button_sp+button_h/2); + drawButton((g.getWidth()-button_w)/2, 60+i*button_sp, channel_state[i]); + } + moveChannelFrame(channel, channel); +} + +function parseDevice(d) { + device = d; + g.clearRect(0, 60, 239, 239).setFontAlign(0, 0, 0).setColor(0, 1, 0).drawString("Found device", 120, 120).flip(); + device.gatt.connect().then(function(ga) { + gatt = ga; + g.clearRect(0, 60, 239, 239).setFontAlign(0, 0, 0).setColor(0, 1, 0).drawString("Connected", 120, 120).flip(); + return gatt.getPrimaryService("FFE0"); +}).then(function(s) { + service = s; + return service.getCharacteristic("FFE1"); +}).then(function(c) { + characteristic = c; + console.log(c); + return; +}).then(function() { + console.log("Done!"); + g.clearRect(0, 60, 239, 239).setColor(1, 1, 1).flip(); + setup_app(); +}).catch(function(e) { + g.clearRect(0, 60, 239, 239).setColor(1, 0, 0).setFontAlign(0, 0, 0).drawString("ERROR"+e, 120, 120).flip(); + console.log(e); +})} + +function connection_setup() { + NRF.setScan(); + NRF.setScan(parseDevice, { filters: [{services:["FFE0"]}], timeout: 2000}); + g.clearRect(0, 60, 239, 239).setFontVector(18).setFontAlign(0, 0, 0).setColor(0, 1, 0); + g.drawString("Scanning for relay...", 120, 120); +} + +function moveChannelFrame(oldc, newc) { + g.setColor(0).drawRect(8, 60+oldc*button_sp-4, g.getWidth()-8, 60+oldc*button_sp+button_h+4); + g.setColor(0.9, 0.9, 0.9).drawRect(8, 60+newc*button_sp-4, g.getWidth()-8, 60+newc*button_sp+button_h+4); +} + +function setup_app() { + characteristic.readValue().then(function(r) { + for (var i=0; i 38) { + Terminal.println(""); + np = 0; + } + var c = (qStorageFile ? fb.read(1) : fb[i]); + if (c=="\n") np = 0; + if (qJS && !qStorageFile && c==";" && fb[i+1]!="\n") { + Terminal.println(";"); + np = 0; + } + else Terminal.print(c); + } + Terminal.println(""); +} + +function visit_file(fn) { + var menu = { + '' : {'title' : fn + (fn.charCodeAt(fn.length-1)==1 ? "(S)" : "")} + }; + var qJS = fn.endsWith(".js"); + menu['Length: '+get_length(fn)+' bytes'] = function() {}; + menu['Display file'] = function () { display_file(fn, qJS); }; + if (qJS && !fn.endsWith(".wid.js")) menu['Load file'] = function() { load(fn); } + if (fn.endsWith(".img")) menu['Display image'] = function() { g.clear().drawImage(STOR.read(fn),0,20); } + menu['Delete file'] = function () { delete_file(fn); } + menu['< Back'] = drawMenu; + E.showMenu(menu); +} + +function drawMenu() { + nend = (nstart+n0 ? files.length-n : 0; + menu = {}; + drawMenu(); + } + for (var i=nstart; i next"] = function() { + if (nstart+n (f.charCodeAt(f.length-1)>31 || f.charCodeAt(f.length-1)<2)); + return fl; +} + +files = get_pruned_file_list(); +drawMenu(); diff --git a/apps/fileman/icons8-filing-cabinet-48.png b/apps/fileman/icons8-filing-cabinet-48.png new file mode 100644 index 000000000..75774c9ea Binary files /dev/null and b/apps/fileman/icons8-filing-cabinet-48.png differ diff --git a/apps/findphone/ChangeLog b/apps/findphone/ChangeLog index 9297fc6c7..86558abf5 100644 --- a/apps/findphone/ChangeLog +++ b/apps/findphone/ChangeLog @@ -1 +1,2 @@ -0.01: First Version \ No newline at end of file +0.01: First Version +0.02: Remove HID requirement, update screen diff --git a/apps/findphone/README.md b/apps/findphone/README.md index 870847222..c655457a2 100644 --- a/apps/findphone/README.md +++ b/apps/findphone/README.md @@ -2,8 +2,7 @@ Ring your phone via GadgetBridge if you lost it somewhere. -1. Enable HID in settings -2. Connect GadgetBridge -3. Lose phone -4. Open app -5. Click any button or screen +1. Connect GadgetBridge +2. Lose phone +3. Open app +4. Click any button or screen diff --git a/apps/findphone/app.js b/apps/findphone/app.js index a532e3b50..34f729bc7 100644 --- a/apps/findphone/app.js +++ b/apps/findphone/app.js @@ -1,33 +1,33 @@ -var storage = require('Storage'); - //notify your phone -function find(){ - Bluetooth.println(JSON.stringify({t:"findPhone", n:true})); + +var finding = false; + +function draw() { + // show message + g.clear(1); + require("Font8x12").add(Graphics); + g.setFont("8x12",3); + g.setFontAlign(0,0); + if (finding) { + g.drawString("Finding...", g.getWidth()/2, (g.getHeight()/2)-20); + g.drawString("Click to stop", g.getWidth()/2, (g.getHeight()/2)+20); + } else { + g.drawString("Click to find", g.getWidth()/2, g.getHeight()/2); + } + g.flip(); } -//init graphics -g.clear(); -require("Font8x12").add(Graphics); -g.setFont("8x12",3); -g.setFontAlign(0,0); -g.flip(); +function find(){ + finding = !finding; + draw(); + Bluetooth.println("\n"+JSON.stringify({t:"findPhone", n:finding})); +} -//init settings -const settings = storage.readJSON('setting.json',1) || { HID: false }; +draw(); -//check if HID enabled and show message -if (settings.HID=="kb" || settings.HID=="kbmedia") { - g.setColor(0x03E0); - g.drawString("click to find", g.getWidth()/2, g.getHeight()/2); - - //register all buttons and screen to find phone - setWatch(find, BTN1); - setWatch(find, BTN2); - setWatch(find, BTN3); - setWatch(find, BTN4); - setWatch(find, BTN5); - -}else{ - g.setColor(0xf800); - g.drawString("enable HID!", g.getWidth()/2, g.getHeight()/2); -} \ No newline at end of file +//register all buttons and screen to find phone +setWatch(find, BTN1, {repeat:true}); +setWatch(find, BTN2, {repeat:true}); +setWatch(find, BTN3, {repeat:true}); +setWatch(find, BTN4, {repeat:true}); +setWatch(find, BTN5, {repeat:true}); diff --git a/apps/gmeter/app-icon.js b/apps/gmeter/app-icon.js new file mode 100644 index 000000000..664ff3813 --- /dev/null +++ b/apps/gmeter/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwxH+AH4A/AH4ATlgAGFlgylEYdWq+BwOs1gDBq8yGL4eCmQqB64AIGgIyDFzQtBFhIAFGIZcYqxbKMZFWMSoVCLiBiGGCguM2YACGBgub1uJsoAExOtGDK7CFo4sFAAhjIYYQvOmTqGLYetE4mzM4L0JmQvNRpAuCQpAnDqx2GSJxeBFxDnKFwSmIMBheHXYQuPUwxgNBYIWFRh4uECQusF5iOFLwQuRUQIwFSBQ6Bq69GLw+swNdwKMGIgOJCQlXMBK+HXpAbCAAS7F2Z0GYBQJBwQZLVYeBDwREFIo4vMJIi+IDQIqCAgNdF5jwKF4xfBVIovHL5ovMDQztHR4pEER6ovGdwzvFq4TGd6YbFxNl1phHL4JdGaoSlFIYQvHGAMyJQxgHABReBIgsyFxLwHACZeBRwruKYBeJMJy9CLwq+KSBLBCF5ouCXoqOMMBYCERhQuGLxpgDYI5iBQApdM1heNMAdWEg7CJBQI6HqxeOSJQATrouQGDi8PF4wwXLoQvSGAdWeg4AK1i7CFyYxEmRiQwMyFq5iFGIJjK1gtDFzIxFGQNXwI0BFQOBq4sDFrgxHABItfGRgskAH4A/AFwA=")) diff --git a/apps/gmeter/app.js b/apps/gmeter/app.js new file mode 100644 index 000000000..28e82b458 --- /dev/null +++ b/apps/gmeter/app.js @@ -0,0 +1,6 @@ +g.clear(); +g.setFont("6x8",5);g.setFontAlign(-1,0); +Bangle.on('accel',function(accelData) { + g.drawString(" "+accelData.mag.toFixed(1)+" ",75,105,true); + g.drawString('G\'s',75,180,true); +}); diff --git a/apps/gmeter/app.png b/apps/gmeter/app.png new file mode 100644 index 000000000..da298ba43 Binary files /dev/null and b/apps/gmeter/app.png differ diff --git a/apps/gpsinfo/ChangeLog b/apps/gpsinfo/ChangeLog index 90ace259c..ceff7011e 100644 --- a/apps/gpsinfo/ChangeLog +++ b/apps/gpsinfo/ChangeLog @@ -1,2 +1,3 @@ 0.02: Ensure screen doesn't display garbage at startup -0.03: Show number of satellites while waiting for fix \ No newline at end of file +0.03: Show number of satellites while waiting for fix +0.04: Add Maidenhead readout of GPS location diff --git a/apps/gpsinfo/gps-info.js b/apps/gpsinfo/gps-info.js index 836e3a71b..1a8cb2fd1 100644 --- a/apps/gpsinfo/gps-info.js +++ b/apps/gpsinfo/gps-info.js @@ -21,7 +21,35 @@ function formatTime(now) { var date = [fd[0], fd[1], fd[2]].join(" "); return time + " - " + date; } +function getMaidenHead(param1,param2){ + var lat=-100.0; + var lon=0.0; + var U = 'ABCDEFGHIJKLMNOPQRSTUVWX'; + var L = U.toLowerCase(); + lat = param1; + lon = param2; + + lon = lon + 180; + t = lon/20; + fLon = Math.floor(t); + t = (t % fLon)*10; + sqLon = Math.floor(t); + t=(t-sqLon)*24; + subLon = Math.floor(t); + extLon = Math.floor((t-subLon)*10); + + lat = lat + 90; + t = lat/10; + fLat = Math.floor(t); + t = (t % fLat)*10; + sqLat = Math.floor(t); + t=(t-sqLat)*24; + subLat = Math.floor(t); + extLat = Math.floor((t-subLat)*10); + + return U[fLon]+U[fLat]+sqLon+sqLat+L[subLon]+L[subLat]+extLon+extLat; +} function onGPS(fix) { lastFix = fix; g.clear(); @@ -38,15 +66,16 @@ function onGPS(fix) { var speed = fix.speed; var time = formatTime(fix.time); var satellites = fix.satellites; - + var maidenhead = getMaidenHead(lat,lon); var s = 15; g.setFontVector(s); - g.drawString("Altitude: "+alt+" m",10,44); - g.drawString("Lat: "+lat,10,44+20); - g.drawString("Lon: "+lon,10,44+40); - g.drawString("Speed: "+speed.toFixed(1)+" km/h",10,44+60); - g.drawString("Time: "+time,10,44+80); - g.drawString("Satellites: "+satellites,10,44+100); + g.drawString("Altitude: "+alt+" m",10,36); + g.drawString("Lat: "+lat,10,54); + g.drawString("Lon: "+lon,10,72); + g.drawString("Speed: "+speed.toFixed(1)+" km/h",10,90); + g.drawString("Time: "+time,10,108); + g.drawString("Satellites: "+satellites,10,126); + g.drawString("Maidenhead: "+maidenhead,10,144); } else { g.setFontAlign(0, 1); g.setFont("6x8", 2); diff --git a/apps/gpsrec/ChangeLog b/apps/gpsrec/ChangeLog index b002e9914..01788e08f 100644 --- a/apps/gpsrec/ChangeLog +++ b/apps/gpsrec/ChangeLog @@ -13,3 +13,5 @@ 0.10: Can now graph altitude & speed 0.11: Ensure we don't turn GPS off if it was previously on (eg from another app/widget) 0.12: Add option to plot on top of OpenStreetMap tiles (when they are installed on the watch) +0.13: Increase GPS recording accuracy by one decimal place + Ensure default time period is 10 diff --git a/apps/gpsrec/app.js b/apps/gpsrec/app.js index 7b01786a5..c7de29d32 100644 --- a/apps/gpsrec/app.js +++ b/apps/gpsrec/app.js @@ -37,9 +37,9 @@ function showMainMenu() { } }, 'Time Period': { - value: settings.period||1, + value: settings.period||10, min: 1, - max: 60, + max: 120, step: 1, onchange: v => { settings.recording = false; @@ -226,7 +226,7 @@ function plotTrack(info) { g.drawString("N",2,40); g.setColor(1,1,1); } - else { + else { var map = s.readJSON("openstmap.json"); map.center = Bangle.project({lat:map.lat,lon:map.lon}); var clat = (info.minLat+info.maxLat)/2; diff --git a/apps/gpsrec/widget.js b/apps/gpsrec/widget.js index 3d110f500..f07c9e43a 100644 --- a/apps/gpsrec/widget.js +++ b/apps/gpsrec/widget.js @@ -31,8 +31,8 @@ periodCtr = settings.period; if (gpsTrack) gpsTrack.write([ fix.time.getTime(), - fix.lat.toFixed(5), - fix.lon.toFixed(5), + fix.lat.toFixed(6), + fix.lon.toFixed(6), fix.alt ].join(",")+"\n"); } diff --git a/apps/gpstimeserver/ChangeLog b/apps/gpstimeserver/ChangeLog new file mode 100644 index 000000000..4c21f3ace --- /dev/null +++ b/apps/gpstimeserver/ChangeLog @@ -0,0 +1 @@ +0.01: New Widget! diff --git a/apps/gpstimeserver/README.md b/apps/gpstimeserver/README.md new file mode 100644 index 000000000..c6d89d56f --- /dev/null +++ b/apps/gpstimeserver/README.md @@ -0,0 +1,59 @@ +# GPS Time Server + +A widget which automatically starts the GPS and turns Bangle.js into a Bluetooth time server, UUID 0x1805. + +Other Espruino Bluetooth devices can then find it and use it to synchronise time. + +**Note:** Because GPS is kept on, you'll need to keep your Bangle.js on charge for this to be useful. + +## Usage + +Just install this widget, and from then on any app which loads widgets will +display the icon ![](widget.png) in the top left, and Bangle.js will be +broadcasting the current time to any device that connects. + +## Technical + +This implements the [Bluetooth Time Service](https://www.bluetooth.org/docman/handlers/downloaddoc.ashx?doc_id=292957) listed [here](https://www.bluetooth.com/specifications/gatt/). + +The Bluetooth docs are verbose and hard to read, so here's a rundown of how it works. + +* The Bangle advertises service `0x1805` +* You connect to it, and request service `0x1805` and characteristic `0x2A2B` +* A 10 byte array is returned: + +``` +[ + year_lowbyte, + year_highbyte, + month, + day_of_month, + hours, + minutes, + seconds, + day_of_week, // 1=monday...7=sunday + subseconds, // 0..255 + update_reason // 0=unknown currently +] +``` + +``` +//NRF.requestDevice({ filters: [{ services: ['1805'] }] }).then(print) + +var gatt; +NRF.connect("c7:4b:2e:c6:f5:45 random").then(function(g) { + gatt = g; + return gatt.getPrimaryService("1805"); +}).then(function(service) { + return service.getCharacteristic("2A2B"); +}).then(function(c) { + return c.readValue(); +}).then(function(d) { + console.log("Got:", JSON.stringify(d.buffer)); + var year = d.getUint16(0,1); + // ... + gatt.disconnect(); +}).catch(function(e) { + console.log("Something's broken.",e); +}); +``` diff --git a/apps/gpstimeserver/widget.js b/apps/gpstimeserver/widget.js new file mode 100644 index 000000000..5d1dd4c34 --- /dev/null +++ b/apps/gpstimeserver/widget.js @@ -0,0 +1,53 @@ +(() => { + +function getBLECurrentTimeData(d) { + var updateReason = 0; // unknown update reason + return [ + d.getFullYear()&0xFF, + d.getFullYear()>>8, + d.getMonth()+1, + d.getDate(), + d.getHours(), + d.getMinutes(), + d.getSeconds(), + d.getDay() ? d.getDay() : 7/*sunday*/, + Math.floor(d.getMilliseconds()*255/1000), + updateReason + ]; +} + +NRF.setServices({ + 0x1805 : { + 0x2A2B : { + value : getBLECurrentTimeData(new Date()), + readable : true, + notify : true + } + } +}, { advertise: [ '1805' ] }); + +Bangle.on('GPS', function(fix) { + if (fix.time !== undefined) { + NRF.updateServices({ + 0x1805 : { + 0x2A2B : { + value : getBLECurrentTimeData(fix.time), + notify : true + } + } + }); + } +}); +Bangle.setGPSPower(1); + + + function draw() { + g.reset(); + g.drawImage(require("heatshrink").decompress(atob("i0XxH+CR0HhEHEyEOi1AAAMWhAUNisW6/XwICBi0PHpgUC69WAYUWIpcVxAVGsgsLi2sCAOsg4EDiwVPlZYCCoUzss6IwxBE68rDYJBBldlAAVeNpIADNoNdxIWDssrCYMJgKZDF4SZCxGtCollmcJAALFDnTFE1utxNdrtXq9WqwVDeJAVB1tdrwABFgM6maOKwQWCIQgbBmQVJmQVCCwlXF4LoKCoaHDCoSgFAAldCwYtCqxbCLRQVECwNWr4VBr4VJmYWFrpcDCpM6neJC4pdCChEsss7C4+IFRI4DC4LBKCpBQLAAgA=")), this.x, this.y); + } + WIDGETS["gpstimeserver"]={ + area:"tl", // tl (top left), tr (top right), bl (bottom left), br (bottom right) + width: 24, // how wide is the widget? You can change this and call Bangle.drawWidgets() to re-layout + draw:draw // called to draw the widget + }; +})() diff --git a/apps/gpstimeserver/widget.png b/apps/gpstimeserver/widget.png new file mode 100644 index 000000000..793b02551 Binary files /dev/null and b/apps/gpstimeserver/widget.png differ diff --git a/apps/hrm/ChangeLog b/apps/hrm/ChangeLog index 5560f00bc..5715e07c7 100644 --- a/apps/hrm/ChangeLog +++ b/apps/hrm/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: Use HRM data and calculations from Bangle.js (don't access hardware directly) diff --git a/apps/hrm/heartrate.js b/apps/hrm/heartrate.js index 84658e85f..6f0a176d3 100644 --- a/apps/hrm/heartrate.js +++ b/apps/hrm/heartrate.js @@ -1,74 +1,60 @@ Bangle.setLCDPower(1); Bangle.setLCDTimeout(0); -Bangle.ioWr(0x80,0) -x=0; +Bangle.setHRMPower(1); +var hrmInfo, hrmOffset = 0; +var hrmInterval; +function onHRM(h) { + // this is the first time we're called + if (counter!==undefined) { + counter = undefined; + g.clear(); + } + hrmInfo = h; + hrmOffset = 0; + if (hrmInterval) clearInterval(hrmInterval); + hrmInterval = setInterval(readHRM,40); + + var px = g.getWidth()/2; + g.setFontAlign(0,0); + g.clearRect(0,24,239,90); + g.setFont("6x8").drawString("Confidence "+hrmInfo.confidence+"%", px, 75); + var str = hrmInfo.bpm; + g.setFontVector(40).drawString(str,px,45); + px += g.stringWidth(str)/2; + g.setFont("6x8"); + g.drawString("BPM",px+15,45); +} +Bangle.on('HRM', onHRM); + +// It takes 5 secs for us to get the first HRM event +var counter = 5; +function countDown() { + E.showMessage("Please wait...\n"+counter--); + if (counter) setTimeout(countDown, 1000); +} +countDown(); + + var min=0,max=0; var wasHigh = 0, wasLow = 0; var lastHigh = getTime(); var hrmList = []; -var hrm; +var hrmInfo; function readHRM() { - var a = analogRead(D29); - var h = getTime(); - min=Math.min(min*0.97+a*0.03,a); - max=Math.max(max*0.97+a*0.03,a); - y = E.clip(170 - (a*960*4),100,230); - if (x==0) { + if (!hrmInfo) return; + + if (hrmOffset==0) { g.clearRect(0,100,239,239); g.moveTo(-100,0); } - /*g.setColor(0,1,0); - var z = 170 - (min*960*4); g.fillRect(x,z,x,z); - var z = 170 - (max*960*4); g.fillRect(x,z,x,z);*/ - g.setColor(1,1,1); - g.lineTo(x,y); - if ((max-min)>0.005) { - if (4*a > (min+3*max)) { // high - g.setColor(1,0,0); - g.fillRect(x,230,x,239); - g.setColor(1,1,1); - if (!wasHigh && wasLow) { - var currentHrm = 60/(h-lastHigh); - lastHigh = h; - if (currentHrm<250) { - while (hrmList.length>12) hrmList.shift(); - hrmList.push(currentHrm); - // median filter - var t = hrmList.slice(); // copy - t.sort(); - // average the middle 3 - var mid = t.length>>1; - if (mid+2239)x=0; } - -setInterval(readHRM,50); diff --git a/apps/isoclock/ChangeLog b/apps/isoclock/ChangeLog new file mode 100644 index 000000000..cd3ceea5c --- /dev/null +++ b/apps/isoclock/ChangeLog @@ -0,0 +1 @@ +0.01: Created app based on digiclock with some small tweaks. diff --git a/apps/isoclock/isoclock-icon.js b/apps/isoclock/isoclock-icon.js new file mode 100644 index 000000000..261a54c35 --- /dev/null +++ b/apps/isoclock/isoclock-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwUC1QA/ACev/4AG/QLB3ptHvwLB+ALHh4LB6ALHg4LDnkD/8An4eBBYsPgcA+E8BY8AgfAAYILG+ALJF4ILJJwPDBZMDBZMMEQJHJL5J3LBfX/M4PAgaRB/gLC6ZnCmEPNQM8BYpnBWwQLG/4ZBBYvQn7UCC5ILXmAKBI4pfDLoIBB//HR8p0BAA0PBYO9BY9+BYOv/4AG/QLBAH4ASA=")) diff --git a/apps/isoclock/isoclock.js b/apps/isoclock/isoclock.js new file mode 100644 index 000000000..5f63a1248 --- /dev/null +++ b/apps/isoclock/isoclock.js @@ -0,0 +1,95 @@ +//load fonts +require("Font7x11Numeric7Seg").add(Graphics); +require("FontHaxorNarrow7x17").add(Graphics); +//screen position +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 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 + switch (day) { + case 0: + day = "Sunday"; + break; + + case 1: + day = "Monday"; + break; + + case 2: + day = "Tuesday"; + break; + + case 3: + day = "Wednesday"; + break; + + case 4: + day = "Thursday"; + break; + + case 5: + day = "Friday"; + break; + + case 6: + day = "Saturday"; + break; + + default: + day = "ERROR"; + break; + } + + + if (h > 12) { + half = "PM"; + h = h - 12; + } + //reset graphics + 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.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); + + +} + +//clear screen at startup +g.clear(); +//draw immediatly +draw(); + +var secondInterval = setInterval(draw, 1000); +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ + if (secondInterval) clearInterval(secondInterval); + secondInterval = undefined; + if (on) { + secondInterval = setInterval(draw, 1000); + draw(); // draw immediately + } +}); + +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +setWatch(Bangle.showLauncher, BTN2, {repeat : false, edge: "falling"}); diff --git a/apps/isoclock/isoclock.png b/apps/isoclock/isoclock.png new file mode 100644 index 000000000..09cf9661d Binary files /dev/null and b/apps/isoclock/isoclock.png differ diff --git a/apps/largeclock/ChangeLog b/apps/largeclock/ChangeLog index 091f7d65b..d06cc9edf 100644 --- a/apps/largeclock/ChangeLog +++ b/apps/largeclock/ChangeLog @@ -4,3 +4,4 @@ 0.04: Adjust layout to account for new vector font 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 diff --git a/apps/largeclock/largeclock.js b/apps/largeclock/largeclock.js index 6f3d638fa..24127ac15 100644 --- a/apps/largeclock/largeclock.js +++ b/apps/largeclock/largeclock.js @@ -198,7 +198,6 @@ if (BTN3app) setWatch( ); g.clear(); -clearInterval(); drawClockFace(); interval = setInterval(drawClockFace, REFRESH_RATE); diff --git a/apps/locale/locales.js b/apps/locale/locales.js index fc7a545d7..c29a49937 100644 --- a/apps/locale/locales.js +++ b/apps/locale/locales.js @@ -143,10 +143,10 @@ var locales = { int_curr_symbol: "JPY", speed: "kmh", distance: { 0: "m", 1: "km" }, - temperature: "°F", + temperature: "°C", ampm: { 0: "", 1: "" }, timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" }, - datePattern: { 0: "%y/%M/%d", 1: "%y/%m;/%d" }, + datePattern: { 0: "%Y/%m/%d", 1: "%y/%m/%d" }, abmonth: "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec", month: "January,February,March,April,May,June,July,August,September,October,November,December", abday: "Sun,Mon,Tue,Wed,Thu,Fri,Sat", diff --git a/apps/mandel/README.md b/apps/mandel/README.md new file mode 100644 index 000000000..de8148b53 --- /dev/null +++ b/apps/mandel/README.md @@ -0,0 +1,7 @@ +# Mandel + +Draw a colored rendition of the famous Mandelbrot set. Pushing button 2 activates zoom mode: the top and left edge of the zoom region can be moved up/down +with buttons 1 and 3 and left/right by touching the screen left right. Pushing button 2 again allows movement of the bottom and right edge in the same manner. +Pushing button 2 a third time renders the selected region full screen. + +The code uses inlined C code for perfomance reasons. Full source is provided on github. diff --git a/apps/mandel/mandel-icon.js b/apps/mandel/mandel-icon.js new file mode 100644 index 000000000..2c09ad91b --- /dev/null +++ b/apps/mandel/mandel-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwkA/4A/AEGIACQX/C/4X3x4XX/AXV/4XsBoYYFC6IPFC5gWFGAgXSDAgXIXAYXGF5mPA4ICCF6QUGC5wWKI5YVKR5ovWL7CPZX6zvXC5KPMDBYXVFwgXOB4QWFC9GPC65pKC5aBLC/4X/C54A/ADo")) diff --git a/apps/mandel/mandel.app.js b/apps/mandel/mandel.app.js new file mode 100644 index 000000000..e6d8c766e --- /dev/null +++ b/apps/mandel/mandel.app.js @@ -0,0 +1,192 @@ + +var aux = new Float32Array(8); +var p_aux = E.getAddressOf(aux, true); +aux[0] = -2; +aux[1] = -1.5; +aux[2] = 1; +aux[3] = 1.5; + +var buf_height = 40; + +var frameX1=0, frameX2=239, frameY1=0, frameY2=239; +var frameCorner = 0; + +var imbuf = Graphics.createArrayBuffer(240, buf_height, 16); +var imbufaddr = E.getAddressOf(imbuf.buffer, true); +var mimg = { + width :imbuf.getWidth(), + height:imbuf.getHeight(), + bpp :16, + buffer:imbuf.buffer +}; + +var c = E.compiledC(` +// void mandel(int, int) +union shortbytes { + short s; + char b[2]; +}; +void mandel(float *p, short *ib) { + float mincx = p[0]; + float mincy = p[1]; + float maxcx = p[2]; + float maxcy = p[3]; + int minx = (int)p[4]; + int miny = (int)p[5]; + int maxx = (int)p[6]; + int maxy = (int)p[7]; + int pib = 0; + for (int y=miny; y-2 && zr<1 && zi>-1.5 && zi<1.5) { + float nr = zr*zr-zi*zi + cr; + zi = 2*zr*zi + ci; + zr = nr; + } + union shortbytes c, d; + c.s = niter | (niter << 7)&0x7ff | (niter<<13)&0xffff; + d.b[0] = c.b[1]; + d.b[1] = c.b[0]; + ib[pib++] = d.s; + } +} +`); + +function drawRectangle(x1, y1, x2, y2) { + if (frameCorner==1) g.setColor(1, 0, 0); + else g.setColor(1, 1, 1); + g.drawLine(x1, y1, x2, y1).drawLine(x1, y1, x1, y2); + if (frameCorner==2) g.setColor(1, 0, 0); + else g.setColor(1, 1, 1); + g.drawLine(x1, y2, x2, y2).drawLine(x2, y1, x2, y2); +} + +function restoreRow(y) { + mimg.width = 240; + mimg.height = 1; + aux[4] = 0; + aux[5] = y; + aux[6] = 240; + aux[7] = y+1; + c.mandel(p_aux, imbufaddr); + g.drawImage(mimg, 0, y); +} + +function restoreCol(x) { + mimg.width = 1; + mimg.height = 240; + aux[4] = x; + aux[5] = 0; + aux[6] = x+1; + aux[7] = 240; + c.mandel(p_aux, imbufaddr); + g.drawImage(mimg, x, 0); +} + +function moveUp() { + restoreCol(frameX1); + restoreCol(frameX2); + if (frameCorner==1 && frameY1>3) { + restoreRow(frameY1); + frameY1 -= 4; + } + if (frameCorner==2 && frameY2>3) { + restoreRow(frameY2); + frameY2 -= 4; + } + drawRectangle(frameX1, frameY1, frameX2, frameY2); +} + +function moveDown() { + restoreCol(frameX1); + restoreCol(frameX2); + if (frameCorner==1 && frameY1<235) { + restoreRow(frameY1); + frameY1 += 4; + } + if (frameCorner==2 && frameY2<235) { + restoreRow(frameY2); + frameY2 += 4; + } + drawRectangle(frameX1, frameY1, frameX2, frameY2); +} + +function moveRight() { + restoreRow(frameY1); + restoreRow(frameY2); + if (frameCorner==1 && frameX1<235) { + restoreCol(frameX1); + frameX1 += 4; + } + if (frameCorner==2 && frameX2<235) { + restoreCol(frameX2); + frameX2 += 4; + } + drawRectangle(frameX1, frameY1, frameX2, frameY2); +} + +function moveLeft() { + restoreRow(frameY1); + restoreRow(frameY2); + if (frameCorner==1 && frameX1>3) { + restoreCol(frameX1); + frameX1 -= 4; + } + if (frameCorner==2 && frameX2>3) { + restoreCol(frameX2); + frameX2 -= 4; + } + drawRectangle(frameX1, frameY1, frameX2, frameY2); +} + + +function toggleFrame() { + if (frameCorner<2) { + frameCorner++; + drawRectangle(frameX1, frameY1, frameX2, frameY2); + } + else { + frameCorner = 0; + var mincx = aux[0] + (aux[2]-aux[0])*frameX1/240.0; + var maxcx = aux[0] + (aux[2]-aux[0])*frameX2/240.0; + var mincy = aux[1] + (aux[3]-aux[1])*frameY1/240.0; + var maxcy = aux[1] + (aux[3]-aux[1])*frameY2/240.0; + aux[0] = mincx; + aux[1] = mincy; + aux[2] = maxcx; + aux[3] = maxcy; + drawIt(); + } +} + +setWatch(toggleFrame, BTN2, {repeat: true}); +setWatch(moveUp, BTN1, {repeat: true}); +setWatch(moveDown, BTN3, {repeat: true}); +Bangle.on('touch', function(button) { + switch(button) { + case 1: moveLeft(); break; + case 2: moveRight(); break; + } +}); + + +function drawIt() { + aux[4] = 0; + aux[5] = 0; + aux[6] = 240; + aux[7] = buf_height; + mimg.width = 240; + mimg.height = buf_height; + for (var y=0; y<240/buf_height; ++y) { + c.mandel(p_aux, imbufaddr); + aux[5] += buf_height; + aux[7] += buf_height; + g.drawImage(mimg, 0, y*buf_height); + } +} + +setTimeout(drawIt, 50); diff --git a/apps/mandel/mandel.info b/apps/mandel/mandel.info new file mode 100644 index 000000000..d4cddbc56 --- /dev/null +++ b/apps/mandel/mandel.info @@ -0,0 +1 @@ +{"id":"mandel","name":"Mandel","src":"mandel.app.js","icon":"mandel.img"} \ No newline at end of file diff --git a/apps/mandel/mandel.min.js b/apps/mandel/mandel.min.js new file mode 100644 index 000000000..0a1bd5fcd --- /dev/null +++ b/apps/mandel/mandel.min.js @@ -0,0 +1,163 @@ + +var aux = new Float32Array(8); +var p_aux = E.getAddressOf(aux, true); +aux[0] = -2; +aux[1] = -1.5; +aux[2] = 1; +aux[3] = 1.5; + +var buf_height = 40; + +var frameX1=0, frameX2=239, frameY1=0, frameY2=239; +var frameCorner = 0; + +var imbuf = Graphics.createArrayBuffer(240, buf_height, 16); +var imbufaddr = E.getAddressOf(imbuf.buffer, true); +var mimg = { + width :imbuf.getWidth(), + height:imbuf.getHeight(), + bpp :16, + buffer:imbuf.buffer +}; + +var c = (function(){ + var bin=atob("0O0EepDtAFrQ7QFK0O0COpDtAzqf7URK/e7nei3p8EMX7pBa0O0Fev3u53rF68V8F+6QStDtBnr97ud6ACMX7pCK0O0Hev3u53q47gAqF+6QmvfuABq/7ggaTEVi2gzrAwaeRgHrRgYqRvfuCCqu6wUDQkUTRETaB+4QKnPuxXq47sd6HyNn7od6B+4QSsfuhGq47sd6c+5kenbuhWpn7od6n+0iesfuhFrw7kd6de6kWgE7E/D/AxPQ9O7CevHuEPoO3fTu4Xrx7hD6CdW07sF68e4Q+gTdtO7ievHuEPoR1NgBwPMKAEPqQzMDQ8PzByBg8wcHY/MPJyb4EnABMrXnATSp5yfuR2rw7mUKp+6nanfup3rn7icKdu4merDuYHrG573o8IMAAHBDAAAAAA=="); + return { + mandel:E.nativeCall(1, "void(int, int)", bin), + }; +})(); + +function drawRectangle(x1, y1, x2, y2) { + if (frameCorner==1) g.setColor(1, 0, 0); + else g.setColor(1, 1, 1); + g.drawLine(x1, y1, x2, y1).drawLine(x1, y1, x1, y2); + if (frameCorner==2) g.setColor(1, 0, 0); + else g.setColor(1, 1, 1); + g.drawLine(x1, y2, x2, y2).drawLine(x2, y1, x2, y2); +} + +function restoreRow(y) { + mimg.width = 240; + mimg.height = 1; + aux[4] = 0; + aux[5] = y; + aux[6] = 240; + aux[7] = y+1; + c.mandel(p_aux, imbufaddr); + g.drawImage(mimg, 0, y); +} + +function restoreCol(x) { + mimg.width = 1; + mimg.height = 240; + aux[4] = x; + aux[5] = 0; + aux[6] = x+1; + aux[7] = 240; + c.mandel(p_aux, imbufaddr); + g.drawImage(mimg, x, 0); +} + +function moveUp() { + restoreCol(frameX1); + restoreCol(frameX2); + if (frameCorner==1 && frameY1>3) { + restoreRow(frameY1); + frameY1 -= 4; + } + if (frameCorner==2 && frameY2>3) { + restoreRow(frameY2); + frameY2 -= 4; + } + drawRectangle(frameX1, frameY1, frameX2, frameY2); +} + +function moveDown() { + restoreCol(frameX1); + restoreCol(frameX2); + if (frameCorner==1 && frameY1<235) { + restoreRow(frameY1); + frameY1 += 4; + } + if (frameCorner==2 && frameY2<235) { + restoreRow(frameY2); + frameY2 += 4; + } + drawRectangle(frameX1, frameY1, frameX2, frameY2); +} + +function moveRight() { + restoreRow(frameY1); + restoreRow(frameY2); + if (frameCorner==1 && frameX1<235) { + restoreCol(frameX1); + frameX1 += 4; + } + if (frameCorner==2 && frameX2<235) { + restoreCol(frameX2); + frameX2 += 4; + } + drawRectangle(frameX1, frameY1, frameX2, frameY2); +} + +function moveLeft() { + restoreRow(frameY1); + restoreRow(frameY2); + if (frameCorner==1 && frameX1>3) { + restoreCol(frameX1); + frameX1 -= 4; + } + if (frameCorner==2 && frameX2>3) { + restoreCol(frameX2); + frameX2 -= 4; + } + drawRectangle(frameX1, frameY1, frameX2, frameY2); +} + + +function toggleFrame() { + if (frameCorner<2) { + frameCorner++; + drawRectangle(frameX1, frameY1, frameX2, frameY2); + } + else { + frameCorner = 0; + var mincx = aux[0] + (aux[2]-aux[0])*frameX1/240.0; + var maxcx = aux[0] + (aux[2]-aux[0])*frameX2/240.0; + var mincy = aux[1] + (aux[3]-aux[1])*frameY1/240.0; + var maxcy = aux[1] + (aux[3]-aux[1])*frameY2/240.0; + aux[0] = mincx; + aux[1] = mincy; + aux[2] = maxcx; + aux[3] = maxcy; + drawIt(); + } +} + +setWatch(toggleFrame, BTN2, {repeat: true}); +setWatch(moveUp, BTN1, {repeat: true}); +setWatch(moveDown, BTN3, {repeat: true}); +Bangle.on('touch', function(button) { + switch(button) { + case 1: moveLeft(); break; + case 2: moveRight(); break; + } +}); + + +function drawIt() { + aux[4] = 0; + aux[5] = 0; + aux[6] = 240; + aux[7] = buf_height; + mimg.width = 240; + mimg.height = buf_height; + for (var y=0; y<240/buf_height; ++y) { + c.mandel(p_aux, imbufaddr); + aux[5] += buf_height; + aux[7] += buf_height; + g.drawImage(mimg, 0, y*buf_height); + } +} + +setTimeout(drawIt, 50); diff --git a/apps/mandel/mandel.png b/apps/mandel/mandel.png new file mode 100644 index 000000000..64dcb8b1b Binary files /dev/null and b/apps/mandel/mandel.png differ diff --git a/apps/miplant/ChangeLog b/apps/miplant/ChangeLog index 5560f00bc..71da064cb 100644 --- a/apps/miplant/ChangeLog +++ b/apps/miplant/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: Adjust alignment for >1 device found diff --git a/apps/miplant/app.js b/apps/miplant/app.js index f26bfc5b8..b6c4ab89a 100644 --- a/apps/miplant/app.js +++ b/apps/miplant/app.js @@ -49,19 +49,19 @@ eg. { */ function show(event) { g.reset().setFont("6x8"); - var y = 45 + 50*Object.keys(deviceInfo).indexOf(event.id); + var y = 45 + 55*Object.keys(deviceInfo).indexOf(event.id); g.drawString(event.id.substr(0,17),0,y); - g.drawImage(getImgHum(),0,y+15); + g.drawImage(getImgHum(),0,y+10); g.setFont("6x8",2); var t = (event.moisture===undefined) ? "?" : event.moisture; - g.drawString((t+" ").substr(0,3),35,y+25,true); - g.drawImage(getImgFert(),80,y+15); + g.drawString((t+" ").substr(0,3),35,y+20,true); + g.drawImage(getImgFert(),80,y+10); t = Math.round(event.fertility) || "?"; - g.drawString((t+" ").substr(0,3), 120, y+25, true); - g.drawImage(getImgTemp(),160,y+15); + g.drawString((t+" ").substr(0,3), 120, y+20, true); + g.drawImage(getImgTemp(),160,y+10); t = Math.round(event.temperature) || "?"; - g.drawString((t+" ").substr(0,3), 180, y+25, true); + g.drawString((t+" ").substr(0,3), 180, y+20, true); g.flip(); } diff --git a/apps/mywelcome/ChangeLog b/apps/mywelcome/ChangeLog new file mode 100644 index 000000000..bca4ff2dd --- /dev/null +++ b/apps/mywelcome/ChangeLog @@ -0,0 +1,15 @@ +0.01: New App! +0.02: Animate balloon intro +0.03: BTN3 now won't restart when at the end +0.04: Fix regression after tweaks to Storage.readJSON +0.05: Move configuration into App/widget settings +0.06: Move loader into welcome.boot.js +0.07: Run again when updated + Don't run again when settings app is updated (or absent) + Add "Run Now" option to settings +0.08: Don't overwrite existing settings on app update +0.09: Allow welcome to run after a fresh install + More useful app menu + BTN2 now goes to menu on release +0.10: Add birthday style +0.11: Skip double buffering, use 240x240 size diff --git a/apps/mywelcome/app-icon.js b/apps/mywelcome/app-icon.js new file mode 100644 index 000000000..5c1373e17 --- /dev/null +++ b/apps/mywelcome/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwxH+AH4A/AH4AU5gAEFtoxnEwXN53WAAXO5oJB42Wy26AAIueFoPXFggAD4AwEGTQiB6otBFgwAD3QvFGC5dCFxiRGGClhrdbv67BXAIuLMBIwPsIABF4OpLwXOFxjBCF6gtBw2r1mHXoXWFxqQWFwOH62rL4IeB6xeOAAIvHGBYuC6+rR4QvCXpovXw3X1i/DR4QuPR5AvKFQOs6+GF4eod4IvPd5AvLwvWLwQvCv4fBR54vURwOHF4iQCX0yOCF4aQBX0QvHSAoAN3SOSd4WyF4yQPLyhgD1YvDMCJeIFxhgCF47BN4BeHFxpgDSAiRORpAuPMIYAFGBYuaF5aSHFwQvEFqQwOeggSBLa4xNF4X+4wAC/xeCFjIADrYwGBIIvlMQiPDBAOk0gDBz2XF8BlEF4eIxADFF8lcF9n+wIrFF05bHF9AsGF9wupGAYv/F8QupGAov/F/4wOF1gA/AH4Ap")) diff --git a/apps/mywelcome/app.js b/apps/mywelcome/app.js new file mode 100644 index 000000000..23cdd0d49 --- /dev/null +++ b/apps/mywelcome/app.js @@ -0,0 +1,298 @@ +// exec each function from seq one after the other +function animate(seq,period) { + var i = setInterval(function() { + if (seq.length) { + var f = seq.shift(); + if (f) f(); + } else clearInterval(i); + },period); +} + +// Fade in to FG color with angled lines +function fade(col, callback) { + var n = 0; + function f() { + 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++; + if (n<10) setTimeout(f,0); + else callback(); + } + f(); +} + + +var scenes = [ + function() { + console.log("Start app"); + g.clear(1); + eval(require("Storage").read("mywelcome.custom.js")); + },function() { + g.clear(1); + g.setFont("4x6",2); + var n=0; + var i = setInterval(function() { + n+=0.04; + g.setColor(n,n,n); + g.drawImage(Bangle.getLogo(),(240-222)/2,(240-100)/2); + if (n>=1) { + clearInterval(i); + setTimeout(()=>g.drawString("Open",34,144), 500); + setTimeout(()=>g.drawString("Hackable",34,156), 1000); + setTimeout(()=>g.drawString("Smart Watch",34,168), 1500); + } + },50); + },function() { + var img = require("heatshrink").decompress(atob("ptRxH+qYAfvl70mj5gAC0ekvd8FkAAdz3HJAYAH4+eJXWkJJYAF0hK2vfNJaIAB5t7S3fN5/V6wAD6vOTg9SumXy2W3QAB3eXul2JdnO63XAApPEVYvAJQIACJoRQDzBLoJQ3W5/NIwr4GJohMFAAROgJYvVJQiPGABZNN3bsdvYyESwnWJSIAC3RNM3V1JjZAES4nVJSYAB4xMNJrbkE56WD5xLVdB5NbFofNJbgABJh26qREPrFXrlbAAWjFgfWJgRLaTQhMLy5KNJINhsJLDrYrD5xLC6pLa5nGTR7oLq9bJQJMKTAXWJbbnR3RLJSoRMHv4pC5rkec6SaIrBLGw2r2XW1epcoqYeJiOXJYziEsOH2RBBw7lF56Yg5nGc6FScZOGJQPX2TmDFIfVTEBMSc4hLEw5KB6+rsJMH63X6pMf5hMQzBLCq5LD1ZLEJhTlfJiWXTA2GJYpMIcwPNc2O6TAuGRIPX1igDJg/PJmyYDcgXWwxMH1ApC53XcsHAJiVYcg2HJYZME0YpC5vWJkhLNJgLlDTAeFJhF/FQfVJkG6JiGXcomyJgOrJYhMErYqD53NJj7lRzBMDcoeGJhzoBJb3GJiN1qZBCJgWyJYpNF1LigAAXAJiNSJgzlGJgt/JkZLRy9TJgeHJhznFcuSZGw5MHJomjcuhLBqdcJiSaiTChMV1CYxy5LCqdXIAWy6+rJhCalTCN2JgdYH4WHJiGpTF7kDc43W2RMJTUZLQzBLFc4mr6+GJh2jTFmXJYyaEwuyc5Sag4xLZTQmG2WFJhxNaJYZMLJZSaEJoOHTR9/Ja+6JbdTqRNETRRNF1JLV4BLcAANYI5ToK1BLYJhWYJZwABq5NoJZ91JaAABdAZNS0ZLey9SJaRNYv5KM426JZmXuxKUJrKcL0lTzBLKzBKYJrVXvfGSol7EYWXJI27zF1JLQADq5NUrgYB4wAEEIV0comXI7wAFrCcPJgYWBTIIAETIN2JYmWuhMkdSdYCgOeJgueqRLFyzhfTi9bq4TC45MF49TuuXJlpONcogAC0hKB0gHDvZMEqRMpAANSq9crlbJAYADqwRDxGk0mIA4eCTQOeveXJdYAHqxNFdAeIAAQGCrOI0oHEAGVXTRJMGvgGCwRM7TAZMHwQGCvhM1rBMERIhMGAwdZJmtSqVTwNcwJEDJg19cvIADa4d9JhANDJnSLHJgrl6AAhFFAwpZDegjn7vhMGcvwABrJAFJgjl/TQpBBI4jl/AAN8TQhHDcv4ADcJBMDvpM+IYaeDAAhL+qd9SgycEJn7iEAA18Jf7nEcv4AIrJLIcv6aMcv4ADvhMHrJJ/AAbl/c6ZM/AAt9cv7nSIv7nLcv4AHrLl/TRpJBvgnjA==")); + g.reset(); + g.setBgColor("#6633ff"); + var y = 240, speed = 5; + function balloon(callback) { + y-=speed; + var x = (240-77)/2; + g.drawImage(img,x,y); + g.clearRect(x,y+81,x+77,y+81+speed); + if (y>60) setTimeout(balloon,0,callback); + else callback(); + } + fade("#6633ff", function() { + balloon(function() { + g.setColor(-1); + g.setFont("6x8",3); + g.setFontAlign(0,0); + g.drawString("Welcome.",120,160); + }); + }); + setTimeout(function() { + var n=0; + var i = setInterval(function() { + n+=5; + g.scroll(0,-5); + if (n>170) + clearInterval(i); + },20); + },3500); + + },function() { + g.reset(); + g.setBgColor("#ffa800");g.clear(); + g.setFont("6x8",2); + g.setFontAlign(0,0); + var x = 80, y = 35, h=35; + animate([ + ()=>g.drawString("Your",x,y+=h), + ()=>g.drawString("Bangle.js",x,y+=h), + ()=>g.drawString("has",x,y+=h), + ()=>g.drawString("3 buttons",x,y+=h), + ()=>{g.setFont("Vector",36);g.drawString("1",200,40);}, + ()=>g.drawString("2",200,120), + ()=>g.drawString("3",200,200) + ],200); + }, + function() { + g.reset(); + g.setBgColor("#00a8ff");g.clear(); + g.setFontAlign(0,0); + g.setFont("Vector",48); + g.drawString("1",200,40); + g.setFontAlign(-1,-1); + g.setFont("6x8",2); + g.drawString("Move up\nin menus\n\nTurn Bangle.js on\nif it was off", 20,40); + }, + function() { + g.reset(); + g.setBgColor("#00a8ff");g.clear(); + g.setFontAlign(0,0); + g.setFont("Vector",48); + g.drawString("2",200,120); + g.setFontAlign(-1,-1); + g.setFont("6x8",2); + g.drawString("Select menu\nitem\n\nLaunch app\nwhen watch\nis showing", 20,70); + }, + function() { + g.reset(); + g.setBgColor("#00a8ff");g.clear(); + g.setFontAlign(0,0); + g.setFont("Vector",48); + g.drawString("3",200,200); + g.setFontAlign(-1,-1); + g.setFont("6x8",2); + g.drawString("Move down\nin menus\n\nLong press\nto exit app\nand go back\nto clock", 20,100); + }, + function() { + g.reset(); + g.setBgColor("#ff3300");g.clear(); + g.setFontAlign(0,0); + g.setFont("Vector",48); + g.drawString("1",200,40); + g.drawString("2",200,120); + g.setFontAlign(-1,-1); + g.setFont("6x8",2); + g.drawString("If Bangle.js\never stops,\nhold buttons\n1 and 2 for\naround six\nseconds.\n\n\n\nBangle.js will\nthen reboot.", 20,20); + }, + function() { + g.reset(); + g.setBgColor("#00a8ff");g.clear(); + g.setFont("6x8",2); + g.setFontAlign(0,0); + var x = 120, y = 10, h=21; + animate([ + ()=>{g.drawString("Bangle.js has a",x,y+=h); + g.drawString("simple touchscreen",x,y+=h);}, + 0,0, + ()=>{g.drawString("It'll detect touch",x,y+=h*2); + g.drawString("on left and right",x,y+=h);}, + 0,0, + ()=>{g.drawString("Horizontal swipes",x,y+=h*2); + g.drawString("work too. Try now",x,y+=h); + g.drawString("to change page.",x,y+=h);} + ],300); + }, + function() { + g.reset(); + g.setBgColor("#339900");g.clear(); + g.setFont("6x8",2); + g.setFontAlign(0,0); + var x = 120, y = 10, h=21; + animate([ + ()=>{g.drawString("Bangle.js",x,y+=h); + g.drawString("comes with",x,y+=h); + g.drawString("a few simple",x,y+=h); + g.drawString("apps installed",x,y+=h);}, + 0,0, + ()=>{g.drawString("To add more, visit",x,y+=h*2); + g.drawString("banglejs.com/apps",x,y+=h); + g.drawString("with a Bluetooth",x,y+=h); + g.drawString("capable device",x,y+=h);}, + ],400); + }, + function() { + g.reset(); + g.setBgColor("#990066");g.clear(); + g.setFont("6x8",2); + g.setFontAlign(0,0); + var x = 120, y = 10, h=21; + g.drawString("You can also make",x,y+=h); + g.drawString("your own apps!",x,y+=h); + y=160; + g.drawString("Check out",x,y+=h); + g.drawString("banglejs.com",x,y+=h); + + var rx = 0, ry = 0; + var h = Graphics.createArrayBuffer(96,96,1,{msb:true}); + // draw a cube + function draw() { + // rotate + rx += 0.1; + ry += 0.11; + var rcx=Math.cos(rx), + rsx=Math.sin(rx), + rcy=Math.cos(ry), + rsy=Math.sin(ry); + // Project 3D coordinates into 2D + function p(x,y,z) { + var t; + t = x*rcy + z*rsy; + z = z*rcy - x*rsy; + x=t; + t = y*rcx + z*rsx; + z = z*rcx - y*rsx; + y=t; + z += 4; + return [96*(0.5+x/z), 96*(0.5+y/z)]; + } + + var a; + // draw a series of lines to make up our cube + h.clear(); + a = p(-1,-1,-1); h.moveTo(a[0],a[1]); + a = p(1,-1,-1); h.lineTo(a[0],a[1]); + a = p(1,1,-1); h.lineTo(a[0],a[1]); + a = p(-1,1,-1); h.lineTo(a[0],a[1]); + a = p(-1,-1,-1); h.lineTo(a[0],a[1]); + a = p(-1,-1,1); h.moveTo(a[0],a[1]); + a = p(1,-1,1); h.lineTo(a[0],a[1]); + a = p(1,1,1); h.lineTo(a[0],a[1]); + a = p(-1,1,1); h.lineTo(a[0],a[1]); + a = p(-1,-1,1); h.lineTo(a[0],a[1]); + a = p(-1,-1,-1); h.moveTo(a[0],a[1]); + a = p(-1,-1,1); h.lineTo(a[0],a[1]); + a = p(1,-1,-1); h.moveTo(a[0],a[1]); + a = p(1,-1,1); h.lineTo(a[0],a[1]); + a = p(1,1,-1); h.moveTo(a[0],a[1]); + a = p(1,1,1); h.lineTo(a[0],a[1]); + a = p(-1,1,-1); h.moveTo(a[0],a[1]); + a = p(-1,1,1); h.lineTo(a[0],a[1]); + g.drawImage({width:96,height:96,buffer:h.buffer},(240-96)/2,68); + } + + setInterval(draw,50); + }, + function() { + g.reset(); + g.setBgColor("#660099");g.clear(); + g.setFontAlign(0,0); + g.setFont("Vector",36); + g.drawString("2",200,120); + g.setFont("6x8",2); + + var x = 90, y = 30, h=21; + animate([ + ()=>g.drawString("That's it!",x,y+=h), + ()=>{g.drawString("Press",x,y+=h*3); + g.drawString("Button 2",x,y+=h); + g.drawString("to start",x,y+=h); + g.drawString("Bangle.js",x,y+=h);} + ],400); + } +]; + +var sceneNumber = 0; + +function move(dir) { + if (dir>0 && sceneNumber+1 == scenes.length) return; // at the end + sceneNumber = (sceneNumber+dir)%scenes.length; + if (sceneNumber<0) sceneNumber=0; + clearInterval(); + Bangle.setLCDMode(); + g.clear(); + scenes[sceneNumber](); + if (sceneNumber>2) { + var l = scenes.length; + for (var i=0;imove(1), BTN3, {repeat:true}); +setWatch(()=>{ + // If we're on the last page + if (sceneNumber == scenes.length-1) { + load(); + } +}, BTN2, {repeat:true,edge:"falling"}); +setWatch(()=>move(-1), BTN1, {repeat:true}); + +Bangle.setLCDTimeout(0); +Bangle.setLCDPower(1); +move(0); diff --git a/apps/mywelcome/app.png b/apps/mywelcome/app.png new file mode 100644 index 000000000..ebbf254bd Binary files /dev/null and b/apps/mywelcome/app.png differ diff --git a/apps/mywelcome/boot.js b/apps/mywelcome/boot.js new file mode 100644 index 000000000..84d235bc5 --- /dev/null +++ b/apps/mywelcome/boot.js @@ -0,0 +1,9 @@ +(function() { + let s = require('Storage').readJSON('mywelcome.json', 1) || {}; + if (!s.welcomed) { + setTimeout(() => { + require('Storage').write('mywelcome.json', {welcomed: true}) + load('mywelcome.app.js') + }) + } +})() diff --git a/apps/mywelcome/custom.html b/apps/mywelcome/custom.html new file mode 100644 index 000000000..b021b7b1a --- /dev/null +++ b/apps/mywelcome/custom.html @@ -0,0 +1,137 @@ + + + + + +
+

Style: +

+

Line 1:

+

Line 2:

+

Line 3 (smaller):

+

Line 4 (smaller):

+ +

+

+

This is currently Christmas-themed, but more themes will be added in the future.

+ + + + + + diff --git a/apps/mywelcome/settings.js b/apps/mywelcome/settings.js new file mode 100644 index 000000000..cf7208d65 --- /dev/null +++ b/apps/mywelcome/settings.js @@ -0,0 +1,18 @@ +(function(back) { + let settings = require('Storage').readJSON('mywelcome.json', 1) + || require('Storage').readJSON('setting.json', 1) || {} + E.showMenu({ + '': { 'title': 'Welcome App' }, + 'Run next boot': { + value: !settings.welcomed, + format: v => v ? 'Yes' : 'No', + onchange: v => require('Storage').write('mywelcome.json', {welcomed: !v}), + }, + 'Run Now': () => load('mywelcome.app.js'), + 'Turn off & run next': () => { + require('Storage').write('mywelcome.json', {welcomed: false}); + Bangle.off(); + }, + '< Back': back, + }) +}) diff --git a/apps/ncrclk/ChangeLog b/apps/ncrclk/ChangeLog new file mode 100644 index 000000000..68209352b --- /dev/null +++ b/apps/ncrclk/ChangeLog @@ -0,0 +1 @@ +0.01: A copy of the analogimgclk to work for NodeConf Remote diff --git a/apps/ncrclk/app-icon.js b/apps/ncrclk/app-icon.js new file mode 100644 index 000000000..e9c28da6b --- /dev/null +++ b/apps/ncrclk/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwkGswA/AFEiAAMoCqcykWEDQQWW0YYNsQXCn8//8zDgMiwwWNmf/CwICCDAUmIpYWD+YYFkIuKkYTBCogGCmUhGBAuBn4QBF4wJBiR6IFwYRCFoYBCVZBGBRQIYFFwaUBkUWFw4XKBIUhGAwXBEwYXFmcTBIMxC4pGBUgQXCLYc/kMvAgKqBSIheGGInyiQGCn8SC43zCwouDHQfzF4x2DFAgFCCwbaBSAi9CAAPxiMTRIcvEQIYCeQIXI+chiMSn8zGgJeDn8yiQXHBoMzDAMRiEzCwgXBF5IPCCwMQCoZUDYAhHFCQUBgIHFF5YRDkMDCwpfKAAn074UDC5QOHC48xL4jvDF5kznFGC4ovOmciwwXFWwIACB4M0C48hC4x4EC44kB+UYI4h4DGIhHEBIUyjAWEC4JIEF4VPF4shlAXFJAYQD+gXBEAcziReEGAg/F74ECBIIuHC4UhCAIZC+UzOokhkIXHJAMTDAQCGmOEkwXHGASSDAQk4oUSCxAwCiUjC4sooUhFxIYCkMilEzCwMymIgBRg5JGiUiwoDBlUijFCCxgYDIQIXCRZAAJJQIWBCqIA/AC4=")) diff --git a/apps/ncrclk/app.js b/apps/ncrclk/app.js new file mode 100644 index 000000000..acf611b1d --- /dev/null +++ b/apps/ncrclk/app.js @@ -0,0 +1,129 @@ +var locale = require("locale"); + +var bgimg = { + width : 100, height : 100, bpp : 4, + buffer : require("heatshrink").decompress(atob("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")) +} + +function getImg(g, col) { + return { + width:g.getWidth(), + height:g.getHeight(), + bpp:1,transparent:0, + buffer:g.buffer, + palette:new Uint16Array([0,col])}; +} + +var handSizeMin = 40; +var handSizeHr = 25; +var handSizeSec = 50; +var gmin = Graphics.createArrayBuffer(6,handSizeMin*2,1,{msb:true}); +var gminimg = getImg(gmin, 0xFFFF); +var ghr = Graphics.createArrayBuffer(8,handSizeHr*2,1,{msb:true}); +var ghrimg = getImg(ghr, g.setColor("#E0E0E0").getColor()); +var gsec = Graphics.createArrayBuffer(2,handSizeSec*2,1,{msb:true}); +var gsecimg = getImg(gsec, g.setColor("#FF0000").getColor()); +var lastDate; + +// create hand images +var c = gmin.getHeight()/2; +var o = 8; // overhang +gmin.fillCircle(2,2,2); +gmin.fillCircle(2,c+o,2); +gmin.fillRect(0,2,4,c+o); +c = ghr.getHeight()/2; +ghr.fillCircle(4,4,4); +ghr.fillCircle(4,c+o,4); +ghr.fillRect(0,4,7,c+o); +c = gsec.getHeight()/2; +gsec.fillRect(0,1,2,c+o); + +// last positions of hands (in radians) +var lastrmin=0, lastrhr=0, lastrsec=0; + +// draw hands - just the bit of the image that changed +function drawHands(full) { + var d = new Date(); + var rsec = d.getSeconds()*Math.PI/30; + var rmin = d.getMinutes()*Math.PI/30; + // hack so hour hand only moves every 10 minutes + var rhr = (d.getHours() + Math.round(d.getMinutes()/10)/6)*Math.PI/6; + var bounds = {}; + if (!full) { // work out the bounds of the hands + var x1 = (g.getWidth()/2)-10; + var y1 = (g.getHeight()/2)-10 - 36; + var x2 = (g.getWidth()/2)+10; + var y2 = (g.getHeight()/2)+10 - 36; + function addPt(ang, r, ry) { + var x = (g.getWidth()/2) + Math.sin(ang)*r + Math.cos(ang)*ry; + var y = (g.getHeight()/2) - Math.cos(ang)*r + Math.sin(ang)*ry - 36; + //g.setColor("#ff0000").fillRect(x-2,y-2,x+2,y+2); + if (xx2)x2=x; + if (y>y2)y2=y; + } + function addSec(r) { + addPt(r,handSizeSec+5,5);addPt(r,handSizeSec+5,-5); + addPt(r,-(o+10),5);addPt(r,-(o+10),-5); + } + function addMin(r) { + addPt(r,handSizeMin,5);addPt(r,handSizeMin,-5); + addPt(r,-(o+8),5);addPt(r,-(o+8),-5); + } + function addHr(r) { + addPt(r,handSizeHr,8);addPt(r,handSizeHr,-8); + addPt(r,-(o+8),8);addPt(r,-(o+8),-8); + } + if (rsec!=lastrsec) { + addSec(rsec);addSec(lastrsec); + } + if (rmin!=lastrmin) { + addMin(rmin);addMin(lastrmin); + } + if (rhr!=lastrhr) { + addHr(rhr);addHr(lastrhr); + } + bounds = {x:x1,y:y1,width:1+x2-x1,height:1+y2-y1}; + } + + g.drawImages([ + {image:bgimg,x:20,y:25,scale:2}, + {image:ghrimg,x:120,y:120-31,center:true,rotate:rhr}, + {image:gminimg,x:120,y:120-31,center:true,rotate:rmin}, + {image:gsecimg,x:120,y:120-31,center:true,rotate:rsec} + ],bounds); + lastrsec = rsec; + lastrmin = rmin; + lastrhr = rhr; + + // Date + var date = locale.date(new Date(),false); + if (date === lastDate) return; + lastDate = date; + g.reset(); + g.setFont("6x8"); + g.setFontAlign(0,-1); + g.drawString(date, g.getWidth()/2, 232, true); +} + +var secondInterval = setInterval(drawHands,1000); +// handle display switch on/off +Bangle.on('lcdPower', (on) => { + if (secondInterval) { + clearInterval(secondInterval); + secondInterval = undefined; + } + if (on) { + drawHands(); + secondInterval = setInterval(drawHands,1000); + } +}); + +g.clear(); +Bangle.loadWidgets(); +Bangle.drawWidgets(); +drawHands(true); + +// Show launcher when middle button pressed +setWatch(Bangle.showLauncher, BTN2, { repeat: false, edge: "falling" }); diff --git a/apps/ncrclk/app.png b/apps/ncrclk/app.png new file mode 100644 index 000000000..a0139e4ca Binary files /dev/null and b/apps/ncrclk/app.png differ diff --git a/apps/notify/ChangeLog b/apps/notify/ChangeLog index cb974e12d..a1e8e4418 100644 --- a/apps/notify/ChangeLog +++ b/apps/notify/ChangeLog @@ -1,3 +1,4 @@ 0.01: New Library! 0.02: Add notification ID option -0.03: Pass `area{x,y,w,h}` to render callback instead of just `y` \ No newline at end of file +0.03: Pass `area{x,y,w,h}` to render callback instead of just `y` +0.05: Adjust position of notification src text diff --git a/apps/notify/notify.js b/apps/notify/notify.js index d8168e048..6f5261de1 100644 --- a/apps/notify/notify.js +++ b/apps/notify/notify.js @@ -94,8 +94,8 @@ exports.show = function(options) { g.setColor(-1).setFontAlign(-1, -1, 0).setFont("6x8", 2); g.drawString(title.trim().substring(0, 13), x+25,y+3); if (options.title && options.src) { - g.setFont("6x8", 1); - g.drawString(options.src.substring(0, 10), x+215,y+5); + g.setFont("6x8", 1).setFontAlign(1, 1, 0); + g.drawString(options.src.substring(0, 10), g.getWidth()-23,y+18); } y += 20;h -= 20; } diff --git a/apps/notifyfs/ChangeLog b/apps/notifyfs/ChangeLog index b359d314b..16bc0ebb3 100644 --- a/apps/notifyfs/ChangeLog +++ b/apps/notifyfs/ChangeLog @@ -3,3 +3,4 @@ 0.03: Fix custom render callback 0.04: Pass `area{x,y,w,h}` to render callback instead of just `y` 0.05: Fix `g` corruption issue if .hide gets called twice +0.06: Adjust position of notification src text and notifications without title diff --git a/apps/notifyfs/notify.js b/apps/notifyfs/notify.js index 74a8d4912..2c622f624 100644 --- a/apps/notifyfs/notify.js +++ b/apps/notifyfs/notify.js @@ -49,14 +49,13 @@ exports.show = function(options) { if (size>120) {size=120} Bangle.setLCDMode("direct"); let x = 0, - y = 0, + y = 40, w = 240, - h = 240; + h = size; // clear screen g.clear(1); // top bar if (options.title||options.src) { - y=40;h=size; const title = options.title || options.src g.setColor(0x39C7).fillRect(x, y, x+w-1, y+30); g.setColor(-1).setFontAlign(-1, -1, 0).setFont("6x8", 3); @@ -64,7 +63,8 @@ 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 - g.drawString(options.src.substring(0, 10), x+235, y-32); + 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; } diff --git a/apps/petrock/ChangeLog b/apps/petrock/ChangeLog new file mode 100644 index 000000000..fef39743a --- /dev/null +++ b/apps/petrock/ChangeLog @@ -0,0 +1,2 @@ +0.01: New App! +0.02: Ensure eyes are white even if a widget (eg Bluetooth) changes the draw color diff --git a/apps/petrock/app-icon.js b/apps/petrock/app-icon.js new file mode 100644 index 000000000..e05d47f85 --- /dev/null +++ b/apps/petrock/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("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")) diff --git a/apps/petrock/app.js b/apps/petrock/app.js new file mode 100644 index 000000000..dc394a2ef --- /dev/null +++ b/apps/petrock/app.js @@ -0,0 +1,73 @@ +const buffer = Graphics.createArrayBuffer(61, 61, 2, { msb: true }); +const eye = { + width: buffer.getWidth(), + height: buffer.getWidth(), + bpp: 2, + transparent: 2, + buffer: buffer.buffer, +}; +function getRock() { + return { + width: 240, + height: 160, + bpp: 2, + buffer: require("heatshrink").decompress(atob("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")) +}; +} + +let px = 0; +let py = 0; +let vx = 0; +let vy = 0; +let ax = 0; +let ay = 0; +let gx = 0; +let gy = 0; + +function draw() { + vx += ax; + vy += ay; + px += vx; + py += vy; + + const pp = Math.sqrt(px * px + py * py); + + if (pp > 14) { + const vv = Math.sqrt(vx * vx + vy * vy); + const alpha = Math.atan2(px, py); + const ratio = 14 / pp; + + px *= ratio; + py *= ratio; + vx = -0.9 * vv * Math.sin(alpha); + vy = -0.9 * vv * Math.cos(alpha); + } + + buffer.setColor(2); + buffer.fillRect(0, 0, 60, 60); + buffer.setColor(3); + buffer.fillCircle(30, 30, 30); + buffer.setColor(0); + buffer.fillCircle(30 + px, 30 + py, 16); + + g.reset(); // ensure we're drawing in white + g.drawImage(eye, 55, 90); + g.drawImage(eye, 125, 90); +} + +Bangle.setLCDPower(1); +Bangle.setLCDTimeout(0); + +g.clear(1); // ensure we're drawing in white +g.drawImage(getRock(), 0, 40); + +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +Bangle.on('accel', (accel) => { + gx += 0.1 * (accel.x - gx); + gy += 0.1 * (accel.y - gy); + ax = 90 * gx - 100 * accel.x; + ay = 90 * gy - 100 * accel.y; + draw(); +}); diff --git a/apps/petrock/petrock.png b/apps/petrock/petrock.png new file mode 100644 index 000000000..d17283e1c Binary files /dev/null and b/apps/petrock/petrock.png differ diff --git a/apps/rpgdice/ChangeLog b/apps/rpgdice/ChangeLog index 7b83706bf..62806fb71 100755 --- a/apps/rpgdice/ChangeLog +++ b/apps/rpgdice/ChangeLog @@ -1 +1,2 @@ 0.01: First release +0.02: Colour changes dependent on roll result diff --git a/apps/rpgdice/app.js b/apps/rpgdice/app.js index 2007d6ab0..be7b934e2 100755 --- a/apps/rpgdice/app.js +++ b/apps/rpgdice/app.js @@ -14,12 +14,18 @@ function getDie() { } function setColors(lastBounce) { - if (lastBounce) { - bgColor = 0xFFFF; + if (lastBounce && face == getDie()) { + bgColor = 0x0000; // Critical Hit + fgColor = 0xF800; + } else if (lastBounce && face == 1){ + bgColor = 0xF800; // Critical Miss fgColor = 0x0000; - } else { - bgColor = 0x0000 + } else if (lastBounce){ + bgColor = 0x0000; // Other Result fgColor = 0xFFFF; + } else { + bgColor = 0x0000; // Still Rolling + fgColor = 0x7BEF; } } diff --git a/apps/setting/ChangeLog b/apps/setting/ChangeLog index 6a5b1dd76..229337dc9 100644 --- a/apps/setting/ChangeLog +++ b/apps/setting/ChangeLog @@ -24,3 +24,4 @@ 0.20: Fix set time menu, allow dates to roll over 0.21: Add passkey pairing option (BETA) Add whitelist option (fix #78) +0.22: Move HID to BLE menu diff --git a/apps/setting/settings.js b/apps/setting/settings.js index 8fa919535..6e766afd4 100644 --- a/apps/setting/settings.js +++ b/apps/setting/settings.js @@ -61,8 +61,6 @@ const boolFormat = v => v ? "On" : "Off"; function showMainMenu() { var beepV = [false, true, "vib"]; var beepN = ["Off", "Piezo", "Vibrate"]; - var hidV = [false, "kbmedia", "kb", "joy"]; - var hidN = ["Off", "Kbrd & Media", "Kbrd","Joystick"]; const mainmenu = { '': { 'title': 'Settings' }, 'Make Connectable': ()=>makeConnectable(), @@ -101,15 +99,6 @@ function showMainMenu() { }, 'Locale': ()=>showLocaleMenu(), 'Select Clock': ()=>showClockMenu(), - 'HID': { - value: 0 | hidV.indexOf(settings.HID), - min: 0, max: 3, - format: v => hidN[v], - onchange: v => { - settings.HID = hidV[v]; - updateSettings(); - } - }, 'Set Time': ()=>showSetTimeMenu(), 'LCD': ()=>showLCDMenu(), 'Reset Settings': ()=>showResetMenu(), @@ -120,6 +109,8 @@ function showMainMenu() { } function showBLEMenu() { + var hidV = [false, "kbmedia", "kb", "joy"]; + var hidN = ["Off", "Kbrd & Media", "Kbrd","Joystick"]; E.showMenu({ 'BLE': { value: settings.ble, @@ -137,6 +128,15 @@ function showBLEMenu() { updateSettings(); } }, + 'HID': { + value: 0 | hidV.indexOf(settings.HID), + min: 0, max: 3, + format: v => hidN[v], + onchange: v => { + settings.HID = hidV[v]; + updateSettings(); + } + }, 'Passkey BETA': { value: settings.passkey?settings.passkey:"none", onchange: () => setTimeout(showPasskeyMenu) // graphical_menu redraws after the call diff --git a/apps/simpletimer/ChangeLog b/apps/simpletimer/ChangeLog index e548d90fa..f1f3a1ec0 100644 --- a/apps/simpletimer/ChangeLog +++ b/apps/simpletimer/ChangeLog @@ -3,3 +3,5 @@ 0.03: BTN2 to open launcher 0.04: Remember last set time 0.05: Fix buzz that doesn't stop (fix #521) +0.06: Fix buzz error, remove '+' when timer running and add 'back' text (fix #577) +0.07: Fix buzz regression from 0.06 diff --git a/apps/simpletimer/app.js b/apps/simpletimer/app.js index 75c118980..e99761810 100644 --- a/apps/simpletimer/app.js +++ b/apps/simpletimer/app.js @@ -1,15 +1,19 @@ let counter = 0; let setValue = 0; -let counterInterval; +let counterInterval, alarmInterval, buzzInterval; let state; let saved = require("Storage").readJSON("simpletimer.json",true) || {}; const DEBOUNCE = 50; function buzzAndBeep() { + buzzInterval = -1; return Bangle.buzz(1000, 1) .then(() => Bangle.beep(200, 3000)) - .then(() => setTimeout(buzzAndBeep, 5000)); + .then(() => { + if (buzzInterval==-1) + buzzInterval = setTimeout(buzzAndBeep, 5000); + }); } function outOfTime() { @@ -19,7 +23,8 @@ function outOfTime() { g.drawString("Time UP!", 120, 50); counter = setValue; buzzAndBeep(); - setInterval(() => { + if (alarmInterval) clearInterval(alarmInterval); + alarmInterval = setInterval(() => { g.clearRect(0, 70, 220, 160); setTimeout(draw, 200); }, 400); @@ -55,8 +60,12 @@ function countDown() { } function clearIntervals() { - clearInterval(); + if (alarmInterval) clearInterval(alarmInterval); + if (counterInterval) clearInterval(counterInterval); + if (buzzInterval>0) clearTimeout(buzzInterval); + alarmInterval = undefined; counterInterval = undefined; + buzzInterval = undefined; } function set(delta) { @@ -93,16 +102,21 @@ const stateMap = { function changeState() { if (stateMap[state]) stateMap[state](); + drawLabels(); + draw(); } function drawLabels() { g.clear(); g.setFontAlign(-1, 0); g.setFont("6x8", 7); - g.drawString(`+ +`, 35, 180); + if (state != "started") // only when not runnung + g.drawString(`+ +`, 35, 180); g.setFontAlign(0, 0, 3); g.setFont("6x8", 1); - g.drawString(`reset (re)start`, 230, 120); + g.drawString("Reset (re)start", 230, 120); + if (state != "started") // only when not runnung + g.drawString("Back", 230, 120); } function resetTimer(value) { @@ -130,8 +144,7 @@ function addWatch() { { repeat: false, edge: "falling", - }, - ); + }); setWatch( () => { resetTimer(0); diff --git a/apps/smartibot/ChangeLog b/apps/smartibot/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/smartibot/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/smartibot/app-icon.js b/apps/smartibot/app-icon.js new file mode 100644 index 000000000..b118983c1 --- /dev/null +++ b/apps/smartibot/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwkA7oA/AH4At6c9AZIAL7//CAP/+YWB//9F50zC4MzmYGEABYSBABIXL/4AKC/4XV+YUFn4XPYIQACXoIXQFIQXTI4Pzn5LCI6AACmfTU/4XV6YhLABfzDYUznvdmczAwixBC4/fDAICBCAKnBC4LBCn4aCLw/96YMCAYokCABHf/4jIGIIuJKQQNHCwIuKBwaeFA45JKE4g3BO4IANCIIYCAgjuMEwIDCCwY4BJJKaBK4JaDCQaABn5KIBwXfR4PT/oEEfwgAFBAQPCIIQXBDQRlBC44jCC4K5CYoXfQQYvKMQP/maPDn4vCPBAgCeAzGD6ZfIBoIWGeAY4BBQwACmfzBZHTn5PCAH4A/AFQ")) diff --git a/apps/smartibot/app.js b/apps/smartibot/app.js new file mode 100644 index 000000000..88ea5e276 --- /dev/null +++ b/apps/smartibot/app.js @@ -0,0 +1,150 @@ +/*digitalWrite([D4,D6],1) +digitalWrite([D10,D11],1)*/ + +function drawBGBtn() { + var c1 = g.setColor("#ff8100").getColor(); + var c2 = g.setColor("#6beaff").getColor(); + g.drawImage({ + width : 240, height : 240, bpp : 2, + palette : new Uint16Array([0,c1,c2,0xFFFF]), + buffer : require("heatshrink").decompress(atob("AH4A/AH4A/AH4A/AH4A/AH4AngNVADFAHf47/Hf47/Hf47/Hf47/Hf47/Hf47/Hf47/Hdn/6oDBv/1AYNf/o76/472OYQ70HgNXAYNVAYY7/HdgARHf47jr6qBABPVHfX1Hf47/Hf47/Hf47/Hf47/Hf47/HY/9q44Fqo7yGAI6E/tVv47/Hf7vqHYrvBHeX/745DHAP3AgI7xGYV//o7BA4Y7yWwK3FHef/6tXHfIAGHf47/Hf47/Hf47/Hf47Pq/VFgoAJq53p/o7Pvo7pv4DCVQPVq4DBOIIDCAgK1EHctfFYQ7LB4Y7nGAI7CF4V/XgVfBYfVHdI4DF4d/H4Q7Cr7/FHcxwCHZSCDHdIwBUogAFYIIMFHcwuBUwqEFeIQ7qF4R4II5A7nWgLjFIwgKGHc5sBVAwABBIKCGHc4xCNoyBBfQ47oGRBEIHdK0CVQgHHHdZvCPAl/WRA7qWgJwDOwSyHHdQ1FOwP/CA47oOYQ2EXIZFBHdjlDWgX1XAi2HHcwuBGYn9A4S8DPAo7lFwPXNYR0COQYGBAIJIBHdFfWITnD/q7Be4Q+Bv47rFYNXHYI1CIIS6BB4TwEHcpnDGQhECBAZIDHdADFq/1IgY3CHeTtB/6sBG4g7qUwQ7DHQIABHe1f//9PIP1XAI7zOgY+BO97vEOwJxCPAPVHeTrDAAJ4BPIIFCHdfVHYboDegRCCr4KEHcorCGYj7EWoJLBHdSpBdAY7FIgKwBBYo7ldgbiEYAj5FHdAvCHQ6ECI4w7mF4LhEWo6+FHcwAUHf47/Hf47/Hf47/Hf47/Hf47/Hf47/Hf47/HcgA/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AEEC1WqwAd2hQcBAAOgDuhXCAAZbWDow8WDgoABDuSTELTAddDg5aVDrhYIeKjtGPC0qDpOoDtxYKSyYdLSyCyKDqRZMWiCUKDqRZMWiAcLWiIdcShg7faRyUMDqBZOaRzuMDqBZOeBwcNDp5ZOaRwcODtbQOeBwddaBw7f0A7cDtUqHduoHbgdqCQuv/47f//6A4o7R3//9QlIDqIAC1f//wdQgRVGHcKZGwA7PDISSGDqbUFLowdKhQZHHcIhF0A7PdwI7ieAo7QDALvKDqDVGeAodKlQYMHbJeI1A7OSBA7aaw47PdxgdQeBodPdxg7Wa44dOdxo7WMA4dKdyI7XbA47NCoQAIHaIcKMIg7NDpSWDHZrQDLJY7NDpiWCHZrQCLJg7/HazSCHZu/O/47/HamqHdItDHZrSLLIQ7NSpf+HaJaDA4YAGHZoAISgo7PLQY7kdwY7KDogWHHbZgHDp7wCRwg7bbATuEDp4XHHbZfHDp7wN0AdJhTuRLKDwMHaheIDqCQCHcLWFDqDwCZggAEwAdJgQUIEJA7QKoQ7hTIo7KDoyzLHa4dXeAI7JDhIABd5TuFHZYdGKwIlIHaqZBagwdR1ZVGAAWoDpcqCxG/TAwdVHbodk0AdLhQ7cDqA7dDruADpcCHbgdQDhYABLLgdQHbodNlQcN1Ad6aBgABhQ7cDpzQNgECLLgdODhrwPLJwdNShzSPDriUOaRxZQDpiUPaRpZQWhgcQDry0KLCKWLWSCWMWSAdfSxJYSSxYdTSxBYTPBLQSPBJ2UPBIdVLQwcVDo6UUDw4cXDryYCZqoAGhTOWAH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4AjA=")) +}); +} + +function drawBGAccel() { + var c1 = g.setColor("#ff8100").getColor(); + var c2 = g.setColor("#6beaff").getColor(); + g.drawImage({ + width : 240, height : 240, bpp : 2, + palette : new Uint16Array([0,c1,c2,0xFFFF]), + buffer : require("heatshrink").decompress(atob("AH4A/AH4A/AH4A/AH4A/AH4AngNVADFAHf47/Hf47/Hf47/Hf47/Hf47/Hf47/Hf47/Hdn/6oDBv/1AYNf/o72G4Vf/47yOYQ74PANXAYNVAYY7/HdgARHf47jr6qBABP1Hf47/Hf47/Hf47/Hf47/Hf47/Hf47D/tXHAtVHeY0CAAQGBv47xGAI7/HeqzGd4I7y//fHIY4B+4EBHeIzCv/9HYIHDHeS2BW4o7z//Vq475AAw7/Hf47/Hf47/Hf47/HZ9X6osFABNXO9P9HZ99HdN/PASqB6tXAYJxBAYQEBWog7lr4rCHZYPDHc4wBHYQvCv68Cr4LDQ4Q7nHAYvDHY1ff4o7mGgQ7EPYQ7CQQY7pGAKlEAArBBBgo7mFwKmFQgrxCHdQvCPBBHIHc60BcYpGEBQw7nNgKoGAAIJBQQw7nGIRtGQIL6HHdAyIIhA7pWgSqEA447rN4R4Ev6yIHdS0BOAZ2CWQ47qGop2B/4QHHdBzCGwi5DIoI7scoa0C+q4EWw47mFwIzE/oHCXgZ4FHcouB+5rCWgRyDPYIBBJAI7or6xCc4f9HwL3CIoQ7rFwNXHYI1CIIS6BB4g7oM4YyEGgYICJAY7oAYtX+pEDG4Q7rNQQ7DdoP/Xgo/DHdw6BAAI72r///p5B+o71OgPVq4+BHd6vCAYJ2BdgLyC6o7v6rsEBIR4BPIIFCHdo0COwQABPoJCCr4KEHcorCGYj7EWoJLBHdR0BdAY7FIgP9BwILEHcrsDcQjAEfIo7oF4R2GQgZHGHcwvBcIi1HI4o7mACg7/Hf47/Hf47/Hf47/Hf47/Hf47/Hf47/Hf47kAH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/ACECwA75hWgHfMq1A751Wqd3I7BeHEKHYLw4lQ7BeHA6BeHDuCeHDuCeHDuCeHA6DeGzuEeGzuEeHB5COujxGHXA7/Hf47/Hf47/Hf47/Hf47/Hf47/Hf47/AAMgHZsIHdegHZsKHVUCHZ+AHdMKHZ4PGAEcqHZ+oHdOqHZ4HGd0Y7ReFAqBHaDwolQ7ReFAyBHaDwndwIASeExlCACLwmdwIASeEw6TeEzuUeEw77WfcAlQ6T1A7lhQ7T0A7leAQpGVI5NCd0oyDHaI6meAQ7Qd0wqDHaDumeAY7Qd04zCHaA6oeAI7Pd1ArCHZ7uoeAQ7Pd1IABHZ46qAA47HAGY7/Hf47/Hf47/Hf47/Hf47/Hf47/Hf47/Hf8AlQ5BAAeoHecKHYugHecCHYuAduwADeHTu0eAzu0eAzu1eAo62eAbu2eAju2eAju3eAY64eALu4eATu4eATu5AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4AXA==")) +}); +} + +function findDevices() { + E.showMenu(); + E.showMessage("Searching..."); + NRF.findDevices(function(devices) { + if (!devices.length) { + E.showPrompt("No Smartibot found.\nTry again?").then(function(x) { + if (x) findDevices(); else load(); + }); + } else { + var m = { "": {title:"Smartibots"} }; + devices.forEach(dev=>{ + m[dev.id.substr(0,17)] = () => startConnectChoose(dev); + }); + m["Search again"] = () => findDevices(); + m["Back"] = () => load(); + E.showMenu(m); + } + }, {timeout : 2000, filters : [{ name : "Espruino SMARTIBOT" }] }); +} + +function startConnectChoose(device) { + E.showMenu({ + "": {title:"Control Method"}, + "Accelerometer" : () => startConnectAccel(device), + "Button" : () => startConnectBtn(device), + "Back": () => load(), + }); +} + +function startConnectBtn(device) { + E.showMenu(); + startConnect(device, + "\x03\x10var w=digitalWrite.bind(null,[D4,D6,D11,D10])\n", + function(gatt, write) { + function setMotors(val) { write(`\x10w(${val})\n`); } + drawBGBtn(); + g.reset().setFont("6x8",2).setFontAlign(0,0,1).drawString("BACK", 230,200); + var state = 0; + var watches = [ + setWatch(e=>setMotors(state = (state&0b0011) | (e.state<<2)), BTN4, {repeat:true, edge:0}), + setWatch(e=>setMotors(state = (state&0b1100) | e.state), BTN5, {repeat:true, edge:0}), + setWatch(() => { + g.clear(); + watches.forEach(clearWatch); + write(`\x10w(0)\n`); + setTimeout(()=>{ + gatt.disconnect() + findDevices(); + },500); + }, BTN3, {repeat:true}) + ]; + }); +} + +function startConnectAccel(device) { + E.showMenu(); + startConnect(device, + "\x03\x10function w(x,y,z,v){var a=analogWrite;a(D4,x);a(D6,y);a(D10,z);a(D11,v);}\n", + function(gatt, write) { + drawBGAccel(); + g.reset().setFont("6x8",2).setFontAlign(0,0,1).drawString("BACK", 230,200); + Bangle.on("accel", function(a) { + var v = [0,0,0,0]; + if (a.z<-0.5) { + var vel = 0, rot = 0; + if (a.y<-0.2) vel = a.y+0.2; + if (a.y>0.2) vel = a.y-0.2; + if (a.x<-0.2) rot = a.x+0.2; + if (a.x>0.2) rot = a.x-0.2; + var rl = Math.round(-(vel+rot)*200)/100; + var rr = Math.round((vel-rot)*200)/100; + v[0] = (rl>0) ? rl:0; + v[1] = (rl<0) ? -rl:0; + v[2] = (rr>0) ? rr:0; + v[3] = (rr<0) ? -rr:0; + } + write(`\x10w(${v.join(",")})\n`); + }); + setWatch(() => { + g.clear(); + Bangle.removeAllListeners("accel"); + write(`\x10w(0,0,0,0)\n`); + setTimeout(()=>{ + gatt.disconnect() + findDevices(); + },500); + }, BTN3, {repeat:0}) + }); +} + +function startConnect(device, text, callback) { + var gatt, tx; + var busy; + function write(data) { + var cmd = function() { + return tx.writeValue(data); + }; + if (!busy) busy=Promise.resolve().then(cmd).then(()=>busy=false); + else busy = busy.then(cmd).then(()=>busy=false); + } + + E.showMessage("Connecting..."); + return device.gatt.connect().then(function(d) { + gatt = d; + return d.getPrimaryService("6e400001-b5a3-f393-e0a9-e50e24dcca9e"); + }).then(function(s) { + return s.getCharacteristic("6e400002-b5a3-f393-e0a9-e50e24dcca9e"); + }).then(function(c) { + E.showMessage("Uploading..."); + tx = c; + function sender(resolve, reject) { + if (text.length) { + tx.writeValue(text.substr(0,20)).then(function() { + sender(resolve, reject); + }).catch(reject); + text = text.substr(20); + } else { + resolve(); + } + } + return new Promise(sender); + }).then(function() { + callback(gatt, write); + }); +} + +findDevices(); diff --git a/apps/smartibot/app.png b/apps/smartibot/app.png new file mode 100644 index 000000000..2b39faf58 Binary files /dev/null and b/apps/smartibot/app.png differ diff --git a/apps/smartibot/watchface.svg b/apps/smartibot/watchface.svg new file mode 100644 index 000000000..e37276379 --- /dev/null +++ b/apps/smartibot/watchface.svg @@ -0,0 +1,433 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + diff --git a/apps/smartibot/watchface2.svg b/apps/smartibot/watchface2.svg new file mode 100644 index 000000000..448e5e6c8 --- /dev/null +++ b/apps/smartibot/watchface2.svg @@ -0,0 +1,100 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/apps/supmariodark/ChangeLog b/apps/supmariodark/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/supmariodark/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/supmariodark/README.md b/apps/supmariodark/README.md new file mode 100644 index 000000000..3f6b52b9a --- /dev/null +++ b/apps/supmariodark/README.md @@ -0,0 +1,3 @@ +# Super mario watch face night mode + +Super mario 2 watch face night mode diff --git a/apps/supmariodark/Watch-demo.png b/apps/supmariodark/Watch-demo.png new file mode 100644 index 000000000..98f28d7ac Binary files /dev/null and b/apps/supmariodark/Watch-demo.png differ diff --git a/apps/supmariodark/banner-down.js b/apps/supmariodark/banner-down.js new file mode 100644 index 000000000..f3e5d2be2 --- /dev/null +++ b/apps/supmariodark/banner-down.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("5EYxH+rVa/2KxX+6/X/wA/AH4A2XIS/CYoLH/AH4A/Y/4A/AH7H/AH4A/Y/4A/AH7H/AH4A/Y/4A/AH7H/AH4A/Y/5P/AH7H/Y/4A/AH7H/AH4A/Y/4A/AH7H/AH4A/Y87I/AH7G4Y5jWDAH4A/Y+wDBY5IIBa4TJ/AH7F0WoS+DY4jQDCQgA/ACZyLAH5fVXobGDAILRDNP7H/LvC8DYgbJCBYbJDAH4A/AGS5FYYbLDBwgA/ACpwLAH5hXYQgFGJv7H/L/TAFA4gIFAH4A/AGi7CXo4KDAH4AXNxIA/MC5jJMv7H/MP4AD")) diff --git a/apps/supmariodark/banner-up.js b/apps/supmariodark/banner-up.js new file mode 100644 index 000000000..a6f95bc02 --- /dev/null +++ b/apps/supmariodark/banner-up.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("5EYxH+AAsvAH4AYMAxh/MMRj/Y/4A/MZDUNAH4A/AF69IA4tkAH4AWNw5H/L7TAFAohN/Y/5h9YQjGHOA4A/AH4AtZA7LEYovXAH4AUOA5H/LrC8DYgTGCBQRn/Y/5f7XobHEaIZyLAH4A/AFK1EXwbHGCIZT/AH7J3AYLHKa4YA/AH7H2XALHMKH4A/ZHLH/AH4A/Y/4A/AH7H/AH4A/Y/4A/AH7H/AH4A/Y/4A/AH7H/AH4A/Y/7H/AH7H/Y/4A/AH7H/AH4A/Y/4A/AH7H/AH4A/Y8oA=")) diff --git a/apps/supmariodark/brick2.js b/apps/supmariodark/brick2.js new file mode 100644 index 000000000..9819e58ad --- /dev/null +++ b/apps/supmariodark/brick2.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("hsLxH+y3XAAkAA4YDBywAEgAHDAggMRFAIMJAIIABGZJFDIDIpCCZArCBoYMHIQIdCFgsAA=")) diff --git a/apps/supmariodark/enemy.js b/apps/supmariodark/enemy.js new file mode 100644 index 000000000..fca1dc370 --- /dev/null +++ b/apps/supmariodark/enemy.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("iEQxH+AAucAAQKGBw4QKBwoQIBw4QGAoMrBosrBQQdE6AQElfQEIgPEAAgPEDIYPBBQQEEAAvQAAIEGACIwFAAhxIBwoABRwpYCB5ZqFAAYGBB5AQDAoQgJAwgND")) diff --git a/apps/supmariodark/flower.js b/apps/supmariodark/flower.js new file mode 100644 index 000000000..41f4ee742 --- /dev/null +++ b/apps/supmariodark/flower.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("iEQxH+AAfXAAwMEBpARGAoXHAAwQDBom6AAgRFB5wvPJ6BvQAAkSiQIIBIgFHAwIQFAgwFCAgQFEBI4GFFAo0HB5gIEAApuKBg4=")) diff --git a/apps/supmariodark/flower_b.js b/apps/supmariodark/flower_b.js new file mode 100644 index 000000000..1d81df47a --- /dev/null +++ b/apps/supmariodark/flower_b.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("iEQxH+AAfHAAwMEBpARGAoV4AAwQDBom6AAgRFB5wvPJ6BvQAAkSiQKJB5IEDBIIFFBQ4ECAogJHAwoCEGgwPOFYxjNDY/+A")) diff --git a/apps/supmariodark/mario_wh.js b/apps/supmariodark/mario_wh.js new file mode 100644 index 000000000..c55f1ff1f --- /dev/null +++ b/apps/supmariodark/mario_wh.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("iEgxH+AAvPAAYLGBo3P44OOB5wyJ3QABDgICCEAwMFCYYRFBhAQFFggPDEAwbGIgQACJo4QHL4w9JQAogFV5G654rDAgIOHB44+HB5BfGB5IQCBIYPFEAgJIAowJFAAoPI44NDAgILDRIYfHUw4PL/wPNN4IOJCAbNDABQOIA5z1EB6qxEc4wHSWIoPHAA4PJA==")) diff --git a/apps/supmariodark/pipe.js b/apps/supmariodark/pipe.js new file mode 100644 index 000000000..d21aebc79 --- /dev/null +++ b/apps/supmariodark/pipe.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("iUgxH+xQAG64AGBAOKmANBmAACCwwOECKAALCIn+EAYAL/xaBGxIiFCP4R/CP4R/CP4R/CP4RTA=")) diff --git a/apps/supmariodark/supmario30x24.bin.js b/apps/supmariodark/supmario30x24.bin.js new file mode 100644 index 000000000..01974bbb3 --- /dev/null +++ b/apps/supmariodark/supmario30x24.bin.js @@ -0,0 +1 @@ +atob("A/4AA/4AA/4AH//AH//AH//A4AH44AH44AH44AA44AA44AA4/AA4/AA4/AA4H//AH//AH//AA/4AA/4AA/4AAAAAAAA4AAA4AAA4HAA4HAA4HAA4///4///4///4///4///4///4AAA4AAA4AAA4AAA4AAA4AAA4AAAAHAH4HAH4HAH4/A/4/A/4/A/44H/44H/44H/44H444H444H444/444/444/44//A4//A4//A4H4A4H4A4H4A4AAAAAAHAAAHAAAHA4AH44AH44AH44HA44HA44HA44/A44/A44/A4//A4//A4//A4/H/4/H/4/H/44A/A4A/A4A/AAAAAAH4AAH4AAH4AA/4AA/4AA/4AH44AH44AH44A/A4A/A4A/A4A///4///4///4///4///4///4AA4AAA4AAA4AAAAA/4HA/4HA/4HA/4H4/4H4/4H444A444A444A444A444A444A444A444A444A44//44//44//4AH/AAH/AAH/AAAAAA//AA//AA//AH//4H//4H//4/HA4/HA4/HA44HA44HA44HA44HA44HA44HA44H/44H/44H/4AA/AAA/AAA/AAAAA/AAA/AAA/AAA/AAA/AAA/AAA4A/44A/44A/44H/44H/44H/44/AA4/AA4/AA/4AA/4AA/4AA/AAA/AAA/AAAAAAAH4/AH4/AH4/A//A4//A4//A44/A44/A44/A44HA44HA44HA44HA44HA44HA4H4/4H4/4H4/4AA/AAA/AAA/AAAAAH4AAH4AAH4AA//A4//A4//A44HA44HA44HA44HA44HA44HA44HH44HH44HH4///A///A///AH/4AH/4AH/4AAAAAPw/APw/APw/APw/APw/APw/APw/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/4AA/4AA/4A//4A//4A//4//4A//4A//4A//4A//4A//4AA//4A//4A//4AA/4AA/4AA/4AAAA///4///4///4///4///4///44HA44HA44HA44HA44HA44HA4///4///4///4H4/AH4/AH4/AAAAAA/4AA/4AA/4AH//AH//AH//A/AH4/AH4/AH44AA44AA44AA44AA44AA44AA44AA44AA44AA4AAAA///4///4///4///4///4///44AA44AA44AA4/AH4/AH4/AH4H//AH//AH//AA/4AA/4AA/4AAAAA///4///4///4///4///4///44HA44HA44HA44HA44HA44HA44AA44AA44AA4AAAA///4///4///4///4///4///44HAA4HAA4HAA4HAA4HAA4HAA4AAA4AAA4AAAAAAAA/4AA/4AA/4AH//AH//AH//A/AH4/AH4/AH44HA44HA44HA44HA44HA44HA44H/44H/44H/44H/44H/44H/4AAAA///4///4///4///4///4///4AHAAAHAAAHAAAHAAAHAAAHAA///4///4///4///4///4///4AAAA///4///4///4///4///4///4AAAAAAA4AAA4AAA4AAA4AAA4AAA4AAA4AAA4AAA4///4///4///4///A///A///AAAAA///4///4///4///4///4///4A/4AA/4AA/4AH4/AH4/AH4/A/AH4/AH4/AH44AA44AA44AA4AAAA///4///4///4///4///4///4AAA4AAA4AAA4AAA4AAA4AAA4AAAA///4///4///4///4///4///4//4A//4A//4AA//4A//4A//4A//4A//4A//4//4A//4A//4A///4///4///4///4///4///4AAAA///4///4///4///4///4///4//AA//AA//AAA//AA//AA//A///4///4///4///4///4///4AAAAA/4AA/4AA/4AH//AH//AH//A/AH4/AH4/AH44AA44AA44AA44AA44AA44AA4/AH4/AH4/AH4H//AH//AH//AA/4AA/4AA/4AAAAA///4///4///4///4///4///44HAA4HAA4HAA4HAA4HAA4HAA//AA//AA//AAH4AAH4AAH4AAAAAAA/4AA/4AA/4AH//AH//AH//A/AH4/AH4/AH44AA44AA44AA44AA44AA44AA4/AH//AH//AH/H//HH//HH//HA/4HA/4HA/4HAAAA///4///4///4///4///4///44HAA4HAA4HAA4H4A4H4A4H4A///4///4///4H4H4H4H4H4H4AAAAH4A4H4A4H4A4//A4//A4//A44HA44HA44HA44HA44HA44HA44H/44H/44H/44A/A4A/A4A/AAAAA4AAA4AAA4AAA4AAA4AAA4AAA///4///4///4///4///4///44AAA4AAA4AAA4AAA4AAA4AAAAAAA///A///A///A///4///4///4AAA4AAA4AAA4AAA4AAA4AAA4///4///4///4///A///A///AAAAA/4AA/4AA/4AA//4A//4A//4AAH/4AH/4AH/4AH/4AH/4AH/4//4A//4A//4A/4AA/4AA/4AAAAAA/AAA/AAA/AAA//4A//4A//4AA//4A//4A//4A//4A//4A//4//4A//4A//4A//4A//4A//4AA//4A//4A//4A//4A//4A//4//4A//4A//4A/AAA/AAA/AAA/AH4/AH4/AH4/4/4/4/4/4/4A/4AA/4AA/4AA/4AA/4AA/4A/4/4/4/4/4/4/AH4/AH4/AH4AAAA/AAA/AAA/AAA/4AA/4AA/4AAA//4A//4A//4A//4A//4A//4/4AA/4AA/4AA/AAA/AAA/AAAAAAA4A/44A/44A/44H/44H/44H/44/A44/A44/A4/4A4/4A4/4A4/AA4/AA4/AA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA") diff --git a/apps/supmariodark/supmario30x24.wdt.js b/apps/supmariodark/supmario30x24.wdt.js new file mode 100644 index 000000000..f97141e83 --- /dev/null +++ b/apps/supmariodark/supmario30x24.wdt.js @@ -0,0 +1 @@ +atob("FhMWFhYWFhYWFggQEBAQEBATExMTEBAWEwcQEw0ZExkTGRMTExMTHhMTEBAQEBAQ") diff --git a/apps/supmariodark/supmariodark-icon.js b/apps/supmariodark/supmariodark-icon.js new file mode 100644 index 000000000..3d52111e5 --- /dev/null +++ b/apps/supmariodark/supmariodark-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwgGDgeAmALCCQ0IgYeIn/4wYbBxETEIc4xAABBgIAFgf///4gE43eYBIUIxOZAAOYHQ2YC4u7wAEBCwYABGAsDx/5C4MwhO5xO4FomP/GJC4s4CoIBBxGJ3OY3eICwWIHgP5KIQAC3AWB/+P/+JL4OLIYY8BzIXFIwIUBAAWIC4W4OYX/z+Zz+YmYYDCwn/3AXG/OfGAOYzIXI/G4zeYzZ2BzC+CAAOZ3IXD34XD/eILoO5OgJ/CPYYXJTgKQBxKLBF4jAGC4gMBKgIDCKYIWBDwLxFC4m4BwIrBPIJHBEQQwCC4amFIQQICxgECJA7vBLwQOBMIIDB/P+C4IHB/AXBwDYEFwTmB/JIBz/4C4Q3BYwQXFKoLjB3OfUIIgB/3Ix+I5YIBxBfDgEwWgW/3OPF4O//f/9A6B95gBmEzDIk4UAQXBRwJlBAAI7BxB0BhDgBC4pCBI4IVBDoP5TYeYgZ9CC4cBLQQRDdoQXD/c4ToQXBgYABC4OYFAgXGUAQXCMwQXCboYXG3YDC5AuBNARgCDAe4BQIFC/ZSDYAM4xGb3AXCXgX/z4XBGAO734iDxeDhGL3e4mZIBMIeJUgWJLQI0DAoTdBxB4CJAYTBGAQABQgn4YYIjBBQIABxwVBxK2BPQScFOYIIB3YXCzHMLQOJCoOPzO4C4KVCmcQF4OLzHICIOO5nI/4dBCIRGCGwODdgKnBIwIrBxHIC4OP/eZVQQWBwczgYWBgEwnGYxgXCDgPIxGfYIYWBCgQADGAIXBxisBLYKYCBQOIiYWGC4IvDzY+BSgZ9BFo4ABhZHEAAaJCxAXL5hzBCoQEEC5U7CIIABCwQdDze5wAXI3ImDCoQXCxO5zEwOxCGDAAZlBJARGJnCJCABIWIgCEBC5QuJC4RIHCxgXBGAc5GgmDOhBfDc4WDhA0DmYVKC4eJxEwhA0CIhYADmcziCsBGgIcBC5w1GCx4=")) \ No newline at end of file diff --git a/apps/supmariodark/supmariodark.js b/apps/supmariodark/supmariodark.js new file mode 100644 index 000000000..00a20e5fb --- /dev/null +++ b/apps/supmariodark/supmariodark.js @@ -0,0 +1,89 @@ +// place your const, vars, functions or classes here +var s=require("Storage"); +let stid; +let hour = -1; +let minute = -1; +const resetFace = ()=>{ + Bangle.setLCDMode(); + g.setClipRect(0,24,239,239) + g.clear(); +}; +const readImage =(img) =>{ + return (s.read(img)); +}; +const drawFace = ()=>{ + resetFace(); + g.drawImages([ + {image:readImage("pipe.img"),x:180,y:160,scale:2}, + {image:readImage("flower_b.img"),x:180,y:128,scale:2}, + {image:readImage("mario_wh.img"),x:100,y:132,scale:2}, + {image:readImage("enemy.img"),x:20,y:165,scale:2}, + ]).drawImages([ + {image:readImage("brick2.img"),x:0,y:196,repeat:true,scale:2} + ]); + drawBanner(); +}; + +const resetTimer =()=>{ + if (stid) { + clearInterval(stid); + stid = undefined; + } +} +const startTimer =() =>{ + hour = -1; + minute = -1; + stid = setInterval(onHalfSecond,500); +} +const drawBanner = (h) =>{ + if(h == undefined) h=24; + g.drawImages([ + {image:readImage("banner-up.img"),x:g.getWidth()/2-100,y:50}, + {image:readImage("banner-down.img"),x:g.getWidth()/2-100,y:(50+24+h)} + ]) +}; + +const updateTimeBanner = (h,m)=>{ + m = (m<10?'0':'')+m; + h = (h<10?'0':'')+h; + bx1=g.getWidth()/2-90; + by1=50+10; + bx2=g.getWidth()/2+90; + by2=50+62; + + g.setFontCustom(eval(s.read("supmario30x24.bin")), 48, eval(s.read("supmario30x24.wdt")), 24); + g.setClipRect(bx1,by1,bx2,by2).clearRect(bx1,by1,bx2,by2); + g.drawString(h,bx1+35,75).drawString(":",g.getWidth()/2,75).drawString(m,bx1+110,75).flip(); +}; +let om = 0; +const onHalfSecond =()=>{ + var d = new Date(); + var sec = d.getSeconds(); + hour = d.getHours(); + minute = d.getMinutes(); + if(minute>om)updateTimeBanner(hour,minute); + let im, pos; + if(sec%2 == 0){im = "flower_b.img";pos = 20;} + else{im = "flower.img";pos = 25;} + g.setClipRect(180,128,180+32,128+32).clearRect(180,128,180+32,128+32).drawImage(readImage(im),180,128,{scale:2}); + g.setClipRect(20,165,25+32,165+32).clearRect(20,165,25+32,165+32).drawImage(readImage("enemy.img"),pos,165,{scale:2}); + om = minute; +}; + + +Bangle.on('lcdPower', (on) => { + resetTimer(); + if (on) { + om=-1; + startTimer(); + drawFace(); + } else { + resetTimer(); + } +}); +resetTimer(); +Bangle.loadWidgets(); +Bangle.drawWidgets(); +drawFace(); +startTimer(); +setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"}); diff --git a/apps/supmariodark/supmariodark.png b/apps/supmariodark/supmariodark.png new file mode 100644 index 000000000..272ae3f28 Binary files /dev/null and b/apps/supmariodark/supmariodark.png differ diff --git a/apps/swlclk/ChangeLog b/apps/swlclk/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/swlclk/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/swlclk/README.md b/apps/swlclk/README.md new file mode 100644 index 000000000..4a47c3e24 --- /dev/null +++ b/apps/swlclk/README.md @@ -0,0 +1,13 @@ +# SWL Clock + +Display Local, UTC time and some programs on the shorts waves along the day, with the frequencies. + +## Description + +This application allows to read local time and universal time at the same time as well as programs (in French, but modifiable in the source code) of short waves according to the time. +a satellite time-setting function is integrated by pressing the BTN1. +Finally this app is compatible with the Apple Notification Widget made by Jeffmer. + +## Requests + +If you have any bug or feature request, please contact [Renaudgweb](https://github.com/renaudgweb/) diff --git a/apps/swlclk/app-icon.js b/apps/swlclk/app-icon.js new file mode 100644 index 000000000..950fe5639 --- /dev/null +++ b/apps/swlclk/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A2tdrF1wwtF4YxsGAtrsqVwY+IyzGNiX/Mn4AXsoxDspk/AAkWAB80ACIubF6YwKF6dBRYgADh4ABAgNBF74uIF4trF/4vtCQIDNF/6P/X/4dBoIvNoIvfACKPdF/6PknWt6/X1s6F9E6FoIADGAyPhLoQAD1ovnFwoABR84vvR97vvGAJhC1ouGF8YAMF/4vpGCIudAH4A/AH4AqA")) diff --git a/apps/swlclk/app.js b/apps/swlclk/app.js new file mode 100644 index 000000000..d1abeda8a --- /dev/null +++ b/apps/swlclk/app.js @@ -0,0 +1,220 @@ +require("Font7x11Numeric7Seg").add(Graphics); +var locale = require("locale"); +const xyCenter = g.getWidth()/2; + +function drawTime(){ + let d = new Date(); + var da = d.toString().split(" "); + let date = locale.dow(d,1)+" "+locale.date(d,1); + var time = da[4].split(":"); + var hours = time[0], + minutes = time[1], + seconds = time[2]; + + function getUTCTime(d) { + return d.toUTCString().split(' ')[4].split(':').map(function(d){return Number(d);}); + } + 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; + } + } + drawStation(); + + // Local time + g.setFont("6x8",1); + g.setColor("#cccccc"); + g.drawString("Loc",10,85); + + g.setFont("7x11Numeric7Seg",4); + g.setColor("#ffffff"); + g.drawString(`${hours}:${minutes}:${seconds}`, xyCenter, 115, true); + + // UTC time + g.setFont("6x8",1); + g.setColor("#cccccc"); + g.drawString("UTC",10,155); + + g.setFont("7x11Numeric7Seg",4); + g.setColor("#ff0000"); + g.drawString(utc[0]+`:${minutes}:${seconds}`, xyCenter, 185,true); + + // footer date + g.setFont("Vector",20); + g.setColor("#ffffff"); + g.clearRect(180,220,240,240); + g.drawString(date+" @"+beats,xyCenter,230); +} + +function setGpsTime(){ + Bangle.setGPSPower(1); + Bangle.on('GPS',function(fix) { + if (fix.fix) { + var curTime = fix.time.getTime()/1000; + setTime(curTime); + Bangle.setGPSPower(0); + Bangle.buzz(100, 1); + start(); + } else { + stop(); + g.setFont("Vector",10); + g.setColor("#cccccc"); + g.clearRect(0,25,240,80); + g.drawString("Mise à l'heure\npar satellites\nen cours...",xyCenter,40); + } + }); +} + +function setButtons(){ + // BTN 1 + setWatch(() => { + setGpsTime(); + Bangle.beep(500, 4000); + }, BTN1, {edge:"rising", repeat:true}); + + // BTN 2 + setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"}); +} + +var intervalRef = null; +function start(){ + g.reset(); + g.clear(); + Bangle.drawWidgets(); + intervalRef = setInterval(drawTime, 1000); + drawTime(); +} + +function stop(){ + clearInterval(intervalRef); +} + +// ANCS Widget +var SCREENACCESS = { + withApp:true, + request:function(){ + this.withApp=false; + stop(); + clearWatch(); + }, + release:function(){ + this.withApp=true; + start(); + setButtons(); + } +}; + +// handle switch display on by pressing BTN1 +Bangle.on('lcdPower', function(on) { + if (!SCREENACCESS.withApp) return; + if (on) { + start(); + } else { + stop(); + } +}); + +// clean app screen +Bangle.loadWidgets(); +start(); +setButtons(); diff --git a/apps/swlclk/swlclk.png b/apps/swlclk/swlclk.png new file mode 100644 index 000000000..47bb9cf3c Binary files /dev/null and b/apps/swlclk/swlclk.png differ diff --git a/apps/tilthydro/ChangeLog b/apps/tilthydro/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/tilthydro/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/tilthydro/app-icon.js b/apps/tilthydro/app-icon.js new file mode 100644 index 000000000..86fd7d388 --- /dev/null +++ b/apps/tilthydro/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwggNKgtQESUF5gAEDR8M5nFAwdVAwoWJBxFcDBZEBBZNc4ALJCxQMCMZAiLHgRJHBBBsHGAwHHABAnGFxwoHhhdMLAgRErj8RT4hGQJApGTJAaNQrnMIIJDCLx0F5gACCgZeNhgWDCQMMBIJeNC4ZcEVYpCFrgLBC4wVBRxQTBBYMFqAFBLIQXCRw/MCIp4DAoQXBFhIRFEAIFD4oXECIIfBIgQXHIQQXEZIIXDIgIREBoRyCC4TJE4pEDCIgXIZIpEDJYafBAoYXDIoQ/DAgRLCFgQXGBoNVHIgREHQhkDa4ghEVArtEC4bZBCQZfCSAvFcwoVCC4QnBfQaQDFgokCDwL7DaAjVFC4xvCcQ7UEAAwUCeApAHAAxvDSA4AKRgYbEIRR2GMAoAOCQhIDABpGEJAhGSGoRdOFA8FGBxYIGBsFe5AJJEp0MJJVcKpYMJrg7MBwQ9EKIK0PCIIAEZaAZCqtVS5QA==")) diff --git a/apps/tilthydro/app.js b/apps/tilthydro/app.js new file mode 100644 index 000000000..011a19eb6 --- /dev/null +++ b/apps/tilthydro/app.js @@ -0,0 +1,91 @@ +var readings; +var failures = 0; + +function displayInfo(reading) { + g.reset(1).setColor(1,0,0); + g.clearRect(0,24,g.getWidth(),g.getHeight()-48); + g.drawImage(require("heatshrink").decompress(atob("o1GgQIFgOr///0oZLg4PBAAP+oAQJjf/yolBtf/yAQIj/5AocGCJMf+hKF3+AGQ4QFCIRHGg/4DI0B/wHFgRDEBIn9A4u+QJI9Fl6DJhfQApIAFDgkCMwwAE3oEILA4dDj7IKIAPAAYMDIhQABnwDC3AQLgI0CPBQ0FGZsAlwzOGgZnMgO8heAgXgCBUG//QnsAAIIAJr///wgBg+AGJO////+EfgDPJgIPBEQIPBCQIAIn4QB/tAheBKxFAgQQCAwN8loPGgr7Bj4RBDwMD4atGD4PggP+t//BAMtngQFqEvKIMFLAJkBgPnPIu/BgPwAoMP/4NBj94CAgOBBgLIC3/8AYP7aAcUBwW//AHBg//4AmB2gQCt/QDYIMDRoJ6CEQS3B/wOB6AMDNoQiDBoJDBBwIMDRoQiDQAIABBwQMDPQYiBl4QCBwYMCgh6D/dCCARJBBwIMCjfQv56BEQLHEBwW//uv/0DNoX3wAiCSwZdCLwMvyAbBvELyI/CPQYRCoKHC+6gCJgU//xrBAAIZBAAMenrHEcIRNCGIKgC6XQGAaWCoH//1Qdoc9jzHC/z1C/EL/L+Eg/B+DHCAYMLOYNkCAkAvEPLYIwCgCWCAAoPB3AwCMAMD/oUBAAkB+EHPQIwBPQMZEI0AlwBCGBIACgfQEgIEBPQQAIIYMLGAMvOgoAEj5NBvpKB8gQJgXgAQI0BABYfBPgI0BABSaCCgYzMAYL7DABG9AYULTwJnMAAN+CBUHSon4CBMBBYkHPZQuFj6+Jl6GFAwwbK35dDAAcLZI0B/wRGg5hHgQRGhf9Hg8G/+QAwcbCBAjB//lHQVv/KUK3//1Wv/4QKAANr/4ABygKFA==")),24,24); + g.setColor(-1).setFont("6x8",2); + + if (reading=="startup") { + g.setFontAlign(0,0); + g.drawString("Scanning...", g.getWidth()/2, g.getHeight()*2/3); + } else if (!reading) { + g.setFontAlign(0,0); + g.drawString("No Tilt found", g.getWidth()/2, g.getHeight()*2/3); + } else { + g.drawString("Temperature",0,100); + g.drawString("Gravity",0,160); + g.setFontAlign(0,0); + g.drawString(reading.color, g.getWidth()*3/4, 24+40); + g.setFontVector(34); + g.setFontAlign(0,-1); + // we can't use locale directly as it currently is just to the nearest degree + var temp = reading.C.toFixed(1)+"°C"; + if (require("locale").temp(0).endsWith("F")) // check locale + temp = reading.F.toFixed(1)+"°F"; + g.drawString(temp,g.getWidth()/2,120); + g.drawString(reading.gravity,g.getWidth()/2,180); + } + g.flip(); +} + +function arrayBufferToHex (arrayBuffer){ + return (new Uint8Array(arrayBuffer)).slice().map(x=>(256+x).toString(16).substr(-2)).join(""); +} + +var TILT_DEVICES = { + 'a495bb30c5b14b44b5121370f02d74de': 'Black', + 'a495bb60c5b14b44b5121370f02d74de': 'Blue', + 'a495bb20c5b14b44b5121370f02d74de': 'Green', + 'a495bb50c5b14b44b5121370f02d74de': 'Orange', + 'a495bb80c5b14b44b5121370f02d74de': 'Pink', + 'a495bb40c5b14b44b5121370f02d74de': 'Purple', + 'a495bb10c5b14b44b5121370f02d74de': 'Red', + 'a495bb70c5b14b44b5121370f02d74de': 'Yellow', +}; + +function takeReading() { + // scan for 5 seconds max + NRF.setScan(function(device) { + d = new DataView(device.manufacturerData); + if (d.getUint8(4) == 0xbb) { + var hexData = arrayBufferToHex(device.manufacturerData); + var tempF = d.getUint16(18); + var tempC = ( tempF - 32) * 5 / 9; + var gravity = d.getUint16(20) / 1000.0; + var color = TILT_DEVICES[hexData.substr(4,32)]; + readings= { + C:tempC, + F:tempF, + gravity:gravity, + d:device.manufacturerData, + color: color, + }; + failures=0; + NRF.setScan(); + if (notFoundTimeout) clearTimeout(notFoundTimeout); + notFoundTimeout = undefined; + displayInfo(readings); + } + }, { filters: [{ manufacturerData: { 0x004C: {} } }]}); + // stop scanning after 5 seconds + var notFoundTimeout = setTimeout(function() { + NRF.setScan(); + notFoundTimeout = undefined; + failures++; + if (failures>5) displayInfo(); + }, 5000); +} + +Bangle.loadWidgets(); +Bangle.drawWidgets(); + + +// Scan every minute +setInterval(function() { + takeReading(); +}, 60*1000); +// Scan once at boot/upload +displayInfo("startup"); +takeReading(); diff --git a/apps/tilthydro/app.png b/apps/tilthydro/app.png new file mode 100644 index 000000000..c4a4e3243 Binary files /dev/null and b/apps/tilthydro/app.png differ diff --git a/apps/verticalface/ChangeLog b/apps/verticalface/ChangeLog index c30b02411..e26120599 100644 --- a/apps/verticalface/ChangeLog +++ b/apps/verticalface/ChangeLog @@ -1,2 +1,4 @@ 0.04: Fixed day being displayed 0.05: Stop hours being displayed wrong if moving from 2 digits to 1 (fix #516) +0.06: Tweak sizing to allow widgets at top, and add widgets (fix #567) +0.07: Added leading zero to hours and minutes diff --git a/apps/verticalface/app.js b/apps/verticalface/app.js index 52c8e47a7..f9138335f 100644 --- a/apps/verticalface/app.js +++ b/apps/verticalface/app.js @@ -6,22 +6,29 @@ let currentHRM = "CALC"; function drawTimeDate() { var d = new Date(); var h = d.getHours(), m = d.getMinutes(), day = d.getDate(), month = d.getMonth(), weekDay = d.getDay(); + + if (h < 10) { + h = "0" + h; + } + + if (m < 10) { + m = "0" + h; + } var daysOfWeek = ["SUN", "MON", "TUE","WED","THU","FRI","SAT"]; var hours = (" "+h).substr(-2); var mins= ("0"+m).substr(-2); var date = `${daysOfWeek[weekDay]}|${day}|${("0"+(month+1)).substr(-2)}`; - // Reset the state of the graphics library g.reset(); // Set color g.setColor('#2ecc71'); // draw the current time (4x size 7 segment) - g.setFont("8x12",9); + g.setFont("8x12",8); g.setFontAlign(-1,0); // align right bottom - g.drawString(hours, 25, 65, true /*clear background*/); - g.drawString(mins, 25, 155, true /*clear background*/); + g.drawString(hours, 25, 75, true /*clear background*/); + g.drawString(mins, 25, 165, true /*clear background*/); // draw the date (2x size 7 segment) g.setFont("6x8",2); @@ -84,6 +91,10 @@ function drawBattery() { // Clear the screen once, at startup g.clear(); +// Load and draw widgets +Bangle.loadWidgets(); +Bangle.drawWidgets(); + // draw immediately at first drawTimeDate(); drawSteps(); diff --git a/apps/vibrclock/ChangeLog b/apps/vibrclock/ChangeLog new file mode 100644 index 000000000..b4d1ae593 --- /dev/null +++ b/apps/vibrclock/ChangeLog @@ -0,0 +1 @@ +0.01: First commit diff --git a/apps/vibrclock/app-icon.js b/apps/vibrclock/app-icon.js new file mode 100644 index 000000000..c41aa0f9c --- /dev/null +++ b/apps/vibrclock/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwxH+AH4A/AH4ATlgAGFlgylEQUyq1WwOB1msAYIHBmQxeDwQrB1nXABA0BqwxaP4eBFhIAFwIwYLgYtPAAZiWCgMrLhS7BMRUrGCQuN1gvBYxQwCF6S6LF5rEDFyC7MF5zDDF50rD5gvP1iRORpovRSJ6NNF6SRCLzQvSMBksmQdOF6OsmQvLRxwvSSBZqBF5+BmUyUJwvCGBC+QwMrCQMrCZ4vZxErLoJhBGAOsAAgvTwR6OH4QfBmTEBAAYwGwQv7R5uCR8DbOrrvSOoIuHF4VWDZ2CQwOICR3XFxAwCF54ATF5cyUo4vmYB4AM63P53O5/Q6wvKSDfW52j44AC0YFBMEnPFgYAGGBcrYKvWFAnP64wPMAKRV5wuE63W6HIMB6RU6y7E5HO6BfF0aRMGCS9D54EBF4I3BTIhgMGCSOCXYIwBSAICC6ySCF5QwCYYL0PF4fW53OAYjzDF5ZiTR4g0BR4OiR4LxDF5piEwJjLW4QACLgLvBFobvMGJEyqwzBwOCwQ3E6wvFXYLtEF6QxEAAtWBoYmEeYReERx4yNBYowFAAouWABouuAATDE0a7TAH4ASA")) diff --git a/apps/vibrclock/app.js b/apps/vibrclock/app.js new file mode 100644 index 000000000..188470cdc --- /dev/null +++ b/apps/vibrclock/app.js @@ -0,0 +1,97 @@ +// Simple clock from https://www.espruino.com/Bangle.js+Clock +// Load fonts +require("Font7x11Numeric7Seg").add(Graphics); +// Check settings for what type our clock should be +var is12Hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"]; +// position on screen +const X = 160, Y = 140; + +function draw() { + // work out how to display the current time + var d = new Date(); + var h = d.getHours(), m = d.getMinutes(); + if (is12Hour) { + if (h == 0) h = 12; + else if (h>12) h -= 12; + } + var time = (" "+h).substr(-2) + ":" + ("0"+m).substr(-2); + // Reset the state of the graphics library + g.reset(); + // draw the current time (4x size 7 segment) + g.setFont("7x11Numeric7Seg",4); + g.setFontAlign(1,1); // align right bottom + g.drawString(time, X, Y, true /*clear background*/); + // draw the seconds (2x size 7 segment) + g.setFont("7x11Numeric7Seg",2); + g.drawString(("0"+d.getSeconds()).substr(-2), X+30, Y, true /*clear background*/); + // draw the date, in a normal font + g.setFont("6x8"); + g.setFontAlign(0,1); // align center bottom + // pad the date - this clears the background if the date were to change length + var dateStr = " "+require("locale").date(d)+" "; + g.drawString(dateStr, g.getWidth()/2, Y+15, true /*clear background*/); +} + +// Clear the screen once, at startup +g.clear(); +// draw immediately at first +draw(); +var secondInterval = setInterval(draw, 1000); +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ + if (secondInterval) clearInterval(secondInterval); + secondInterval = undefined; + if (on) { + secondInterval = setInterval(draw, 1000); + draw(); // draw immediately + } +}); +// Load widgets +Bangle.loadWidgets(); +Bangle.drawWidgets(); +// Show launcher when middle button pressed +setWatch(Bangle.showLauncher, BTN2, { repeat: false, edge: "falling" }); + +// ====================================== Vibration +// vibrate 0..9 +function vibrateDigit(num) { + if (num==0) return Bangle.buzz(500); + return new Promise(function f(resolve){ + if (num--<=0) return resolve(); + Bangle.buzz(100).then(()=>{ + setTimeout(()=>f(resolve), 200); + }); + }); +} +// vibrate multiple digits (num must be a string) +function vibrateNumber(num) { + return new Promise(function f(resolve){ + if (!num.length) return resolve(); + var digit = num[0]; + num = num.substr(1); + vibrateDigit(digit).then(()=>{ + setTimeout(()=>f(resolve),500); + }); + }); +} + +var vibrateBusy; +function vibrateTime() { + if (vibrateBusy) return; + vibrateBusy = true; + + var d = new Date(); + var hours = d.getHours(), minutes = d.getMinutes(); + if (is12Hour) { + if (hours == 0) hours = 12; + else if (hours>12) hours -= 12; + } + + vibrateNumber(hours.toString()). + then(() => new Promise(resolve=>setTimeout(resolve,500))). + then(() => vibrateNumber(minutes.toString())). + then(() => vibrateBusy=false); +} + +// when BTN1 pressed, vibrate +setWatch(vibrateTime, BTN1, {repeat:true,edge:"rising"}); diff --git a/apps/vibrclock/app.png b/apps/vibrclock/app.png new file mode 100644 index 000000000..79ff0b992 Binary files /dev/null and b/apps/vibrclock/app.png differ diff --git a/apps/widncr/ChangeLog b/apps/widncr/ChangeLog new file mode 100644 index 000000000..4c21f3ace --- /dev/null +++ b/apps/widncr/ChangeLog @@ -0,0 +1 @@ +0.01: New Widget! diff --git a/apps/widncr/widget.js b/apps/widncr/widget.js new file mode 100644 index 000000000..7efa59273 --- /dev/null +++ b/apps/widncr/widget.js @@ -0,0 +1,5 @@ +WIDGETS["ncr"]={area:"tl",width:75,draw:function(){ + g.reset().setColor(1,0.2,0.5); + g.drawImage(atob("SxgCAAAAAAAAAAAAAAAAAAAAAAAAApAKAGpAGqgBqqQG+AAApAKBqq/APA//gP/9C//g//wAA/QPD///wPD//4P//S9VS9GRpA/wPD5V/4PH//9P//y4AD0AH/g/4PD4A99PL//+P//y//HwAL/w99PD+p8vPL//+P//2//LgAP/w9fPD/+9P/L//+P//y4AHwAL/w9P/D4A9D/H//9P//y4AD4AD/Q9D/D4A9B/D//4P//S//h+rQUA9B/D4A9AvA//gP/9C//gf/gAA9AvC0AAAAAGpAFVQAAAABpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaoCqkgAUC+BqpKqQAuAuQaQLggGDAA4A4NBwDAIAACRiQxw0kcgCTAA5DokAYDAIAAAAyAYAqQJgDDFQzGowAMDAIVAABqAMA3AGv5Dqgx8owAMDAOqAAHJAMCTAGgcDAAwUowAYDAIAAAcGAMJCAKgFDAAwAocAwDAIAABgDAk0BgMgDTqkwAoHrQDAOqQHqh7S60e0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"), this.x,this.y); + g.setColor(1,1,1); +}}; diff --git a/apps/widncr/widget.png b/apps/widncr/widget.png new file mode 100644 index 000000000..85543d860 Binary files /dev/null and b/apps/widncr/widget.png differ diff --git a/bin/firmwaremaker.js b/bin/firmwaremaker.js index ce885c394..4e22dd168 100755 --- a/bin/firmwaremaker.js +++ b/bin/firmwaremaker.js @@ -19,6 +19,13 @@ var APPS = [ // IDs of apps to install 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, +}; + var AppInfo = require(ROOTDIR+"/core/js/appinfo.js"); var appjson = JSON.parse(fs.readFileSync(APPJSON).toString()); var appfiles = []; diff --git a/core b/core index 9708e1a15..0389671ba 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 9708e1a15ee20734a24f6f2913078aa8bba625dc +Subproject commit 0389671ba6678a12c9f35644ffb96c190bbe0278 diff --git a/css/main.css b/css/main.css index 4acddf0dc..8e91aec3d 100644 --- a/css/main.css +++ b/css/main.css @@ -5,7 +5,7 @@ } .avatar img { border-radius: 5px 5px 5px 5px; - background: #ddd; + background: #fff; } #toastcontainer { position:fixed; diff --git a/index.html b/index.html index 5aabed4df..a5ae7bff0 100644 --- a/index.html +++ b/index.html @@ -108,6 +108,7 @@
+

Can't connect? Check out the Bangle.js Troubleshooting page

Check out the Source on GitHub, or find out how to add your own app

diff --git a/loader.js b/loader.js index 248d1c2a9..77174a24c 100644 --- a/loader.js +++ b/loader.js @@ -11,10 +11,20 @@ if (window.location.host=="banglejs.com") { 'This is not the official Bangle.js App Loader - you can try the Official Version here.'; } -var APP_SOURCECODE_URL; +var RECOMMENDED_VERSION = "2v08"; +// could check http://www.espruino.com/json/BANGLEJS.json for this + (function() { let username = "espruino"; let githubMatch = window.location.href.match(/\/(\w+)\.github\.io/); if (githubMatch) username = githubMatch[1]; - APP_SOURCECODE_URL = `https://github.com/${username}/BangleApps/tree/master/apps`; + Const.APP_SOURCECODE_URL = `https://github.com/${username}/BangleApps/tree/master/apps`; })(); + +function onFoundDeviceInfo(deviceId, deviceVersion) { + if (deviceId != "BANGLEJS") { + showToast(`You're using ${deviceId}, not a Bangle.js. Did you want espruino.com/apps instead?` ,"warning", 20000); + } else if (versionLess(deviceVersion, RECOMMENDED_VERSION)) { + showToast(`You're using an old Bangle.js firmware (${deviceVersion}). You can update with the instructions here` ,"warning", 20000); + } +} diff --git a/testing/GPS-comms.js b/testing/GPS-comms.js index b9c2c645c..ca0db16b6 100644 --- a/testing/GPS-comms.js +++ b/testing/GPS-comms.js @@ -1,13 +1,13 @@ Bangle.setGPSPower(1) //Bangle.on('GPS',print); -/*Bangle.on('GPS-raw',function (d) { +Bangle.on('GPS-raw',function (d) { if (d[0]=="$") return; if (d.startsWith("\xB5\x62\x05\x01")) print("GPS ACK"); else if (d.startsWith("\xB5\x62\x05\x00")) print("GPS NACK"); // 181,98 sync chars else print("GPS",E.toUint8Array(d).join(",")); -});*/ +}); function writeGPScmd(cmd) { var d = [0xB5,0x62]; // sync chars d = d.concat(cmd); @@ -16,7 +16,7 @@ function writeGPScmd(cmd) { a += d[i]; b += a; } - d.push(a,b); + d.push(a&255,b&255); Serial1.write(d); } function readGPScmd(cmd, callback) { @@ -91,9 +91,9 @@ function getUBX_CFG_GNSS() { for (var i=4;i