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.
+
+
+
+## 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  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/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