Merge remote-tracking branch 'upstream/master' into develop

pull/606/head
OmegaRogue 2020-12-04 14:23:10 +01:00
commit 9e73c4e9b1
149 changed files with 3942 additions and 244 deletions

View File

@ -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

View File

@ -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

300
apps.json
View File

@ -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}
]
}
]

View File

@ -136,7 +136,7 @@
},
"rules": {
"indent": [
"warn",
"off",
2,
{
"SwitchCase": 1

View File

@ -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

View File

@ -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;i<SAMPLES;i++) {
var a = accely[i]/SCALE;
if (a>0.1) {
if (i<tStart) tStart=i;
if (i>tEnd) 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);

View File

@ -0,0 +1 @@
0.01: New App!

BIN
apps/assistedgps/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,113 @@
<html>
<head>
<link rel="stylesheet" href="../../css/spectre.min.css">
</head>
<body>
<h2>Assisted GPS</h2>
<p>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.</p>
<p>You can upload data that covers a longer period of time, but the upload will take longer.</p>
<div class="form-group">
<label class="form-label">AGPS Validity time</label>
<label class="form-radio">
<input type="radio" name="agpsperiod" value="1d" checked><i class="form-icon"></i> 1 day (8kB)
</label>
<label class="form-radio">
<input type="radio" name="agpsperiod" value="2d"><i class="form-icon"></i> 2 days (14kB)
</label>
<label class="form-radio">
<input type="radio" name="agpsperiod" value="3d"><i class="form-icon"></i> 3 days (20kB)
</label>
<label class="form-radio">
<input type="radio" name="agpsperiod" value="1wk"><i class="form-icon"></i> 1 week (46kB)
</label>
</div>
<p>Click <button id="upload" class="btn btn-primary">Upload</button></p>
<script src="../../core/lib/customize.js"></script>
<script>
// When the 'upload' button is clicked...
document.getElementById("upload").addEventListener("click", function() {
var radios = document.getElementsByName('agpsperiod');
var url = "https://www.espruino.com/agps/assistnow_1d.base64";
for (var i=0; i<radios.length; i++)
if (radios[i].checked)
url = "https://www.espruino.com/agps/assistnow_"+radios[i].value+".base64";
console.log("Sending...");
//var text = document.getElementById("agpsperiod").value;
get(url, function(b64) {
var js = jsFromBase64(b64);
sendCustomizedApp({
storage:[
{name:"RAM", content:js},
]
});
});
});
function UBX_CMD(cmd) {
var d = [0xB5,0x62]; // sync chars
d = d.concat(cmd);
var a=0,b=0;
for (var i=2;i<d.length;i++) {
a += d[i];
b += a;
}
d.push(a&255,b&255);
return d;
}
function UBX_MGA_INI_TIME_UTC() {
var a = new Uint8Array(4+24);
a.set([0x13,0x40,24,0]);
a.set([ 0x10, // 0: type
0, // 1: version
0, // 2: ref - none
0x80] ); // 3: leapsecs - unknown
var d = new Date();
d.setTime(d.getTime()+d.getTimezoneOffset()*60000); // get as UTC
var dv = new DataView(a.buffer, 4);
dv.setUint16(4, d.getFullYear());
dv.setUint8(6, d.getMonth()+1);
dv.setUint8(7, d.getDate());
dv.setUint8(8, d.getHours());
dv.setUint8(9, d.getMinutes());
dv.setUint8(10, d.getSeconds());
dv.setUint16(16, 10*60); // seconds part of accuracy - 10 minutes
return UBX_CMD([].slice.call(a));
}
function jsFromBase64(b64) {
var bin = atob(b64);
var chunkSize = 128;
var js = "\x10Bangle.setGPSPower(1);\n"; // turn GPS on
//js += `\x10Bangle.on('GPS-raw',function (d) { if (d.startsWith("\\xB5\\x62\\x05\\x01")) Terminal.println("GPS ACK"); else if (d.startsWith("\\xB5\\x62\\x05\\x00")) Terminal.println("GPS NACK"); })\n`;
js += "\x10E.showMessage('Uploading...','AGPS');function p(n) {g.fillRect(0,g.getHeight()-80,g.getWidth()*n,g.getHeight()-60);}";
//js += "\x10var t=getTime()+1;while(t>getTime());\n"; // wait 1 sec
js += `\x10Serial1.write(atob("${btoa(String.fromCharCode.apply(null,UBX_MGA_INI_TIME_UTC()))}"))\n`; // set GPS time
for (var i=0;i<bin.length;i+=chunkSize) {
var chunk = bin.substr(i,chunkSize);
js += `\x10p(${Math.round(100*i/bin.length)/100});Serial1.write(atob("${btoa(chunk)}"))\n`;
}
js += "\x10p(1);\n"; // finish progress bar
return js;
}
function get(url,callback) {
console.log("Loading "+url);
var oReq = new XMLHttpRequest();
oReq.addEventListener("load", function() {
var b64 = this.responseText;
console.log("Response received...");
callback(b64);
});
oReq.open("GET", url);
oReq.send();
}
</script>
</body>
</html>

View File

@ -59,7 +59,7 @@ function hitMe() {
if(playerWeight == 21)
EndGameMessdage('WINNER');
else if(playerWeight > 21)
EndGameMessdage('LOOSER');
EndGameMessdage('LOSER');
}
function calcWeight(hand, hideCard) {

View File

@ -1,2 +1,3 @@
0.01: New App!
0.02: Fixed issue with wrong device informations
0.03: Ensure manufacturer:undefined doesn't overflow screen

View File

@ -18,7 +18,7 @@ function showDeviceInfo(device){
value: device.rssi
},
"manufacturer": {
value: device.manufacturer
value: device.manufacturer===undefined ? "-" : device.manufacturer
}
};

View File

@ -0,0 +1 @@
0.01: New App!

View File

@ -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)

View File

@ -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}
]
}

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwggNK93gEikO93uC6gWBF6ECkQuVkUikAuVAAIuVAAIuGGZgXDlwuDhWkpWqAARHLkQpChWql3kC4YYHmQXDSQWq0Xu8QXE0AWEgYWESQIuC90qlQwJFwoABFwnyGBBdEC4guC1X/GBAXIVYJdC/4wFUw4XFFYX/GApIDC5BJBC4YwEC6QwEC5pHD+YwE0IXMGAX//U/GAgXNU4X60YwEU5YABnQXC0RhEFxkv+YXCl5iBF4gXKLQM6IgIuBGoIXCIxOqlRaBRoIABFwYXBUheqGAIACFwYXKBoYwBFwwXGVoQuDGAguEC4MzCwUQC4UKBwmvFw2qgczmUikAWCC4OikUzAAQvH+YXCCwcAmQVDC4YwFBIIVEgA3BAALADR48zmAWEh4VBPAS/DAIQXKJwIlDd4f6AgQXIIoSPCFwWqC4IFDL4YAFmAXCFIYXB0RhBKQRvDAAa/Dl4oCC4Mv//ya4gWFC4eiLAQUBFwgXBAA8Bc4qnCFwehC5EAC5AuD0AXRFwYXTFweqwAXJPAQXDFwh2JC5AuE0QXKJAouFLxQwGFwhGLPJAuPMI4uQDBAKD"))

182
apps/bluetoothdock/app.js Normal file
View File

@ -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();
});
}

BIN
apps/bluetoothdock/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -0,0 +1 @@
Bangle.on("charging", isCharging => { if (isCharging) load("bluetoothdock.app.js"); });

View File

@ -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)

View File

@ -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;

View File

@ -0,0 +1 @@
0.01: New App!

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwxH+AH4A/AH4ASwoAIF/4pZABYuuGDIv/F/4v/F/4vt1Ivt6Zft1Ivu6fNF9nT6a+JF8SNBF9ouBXxQvhFwQvrRoTuLF8BeDXxQvfFwYAFGgwvdRoYAGd840GX84AC5rCLF8ReLF8wMJF8K+CRpAvmNhQvhdwIuKF8SNLF8guLF8JdML8Yv/F/4v/F/4v/GDguYAH4A/AGYA=="))

28
apps/chargeanim/app.js Normal file
View File

@ -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();
});

1
apps/chargeanim/boot.js Normal file
View File

@ -0,0 +1 @@
Bangle.on("charging", isCharging => { if (isCharging) load("chargeanim.app.js"); });

BIN
apps/chargeanim/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1 +1,2 @@
0.01: New App!
0.02: Added decrement and touch functions

View File

@ -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();

View File

@ -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.

View File

@ -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) {
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,16 +71,18 @@ 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;
if (this.screenInit) {
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);
else g.setColor(0x30cd);
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);
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.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);
@ -66,23 +91,24 @@ class CSCSensor {
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;
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();
});
}
@ -163,14 +188,17 @@ function parseDevice(d) {
})}
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();

1
apps/digiclock/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: App Made!

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("/wA/AH4A/AH4A/ACmsAEQuMlcAAD0rGBQKBFr4ADGBOsqwvjqwvJRsCRFF/8Gg4ADEZYQEgwvWg8+AAgwKCJgvQDgoABF5IRMF5xEBJpBhGCJwvNDQM4AYMNAAQaBnCAFCJ4vNIwQeBAAkxQAwGCmIRFFwIRDF64dDgwGBgwRNF/4v/F/4v/F/4v/F/4dJmIdECIkxF7MHFwUHhoACg4eCAYIACCJ4vNDQIgCAAgICKwoROF5yAEAAgtFCKAvQJpAAICJgvQgEGg4ADFxIwCAAcGBYovRADov6qwvjqwvJ1gvjEoIvHGASRgRoIuJGAYAhFxQA/AH4A/AH4A/ABQ"))

154
apps/digiclock/digiclock.js Normal file
View File

@ -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"});

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

1
apps/dsdrelay/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: New app!

14
apps/dsdrelay/README.md Normal file
View File

@ -0,0 +1,14 @@
# DSDRelay
Small app to control DSD Tech BLE relay boards from the watch. I have seen them being sold as 1-, 2- and 4-relay boards. The app shows controls for
4 relays, regardless of the actual configuration of the board connected.
![](dsdrelay-pic.jpg)
## Controls
- buttons 1 and 3 cycle the selection of the currently active channel
- swipe right turns the selected channel's relay *on*
- swipe left turns the selected channel's relay *off*
I only own a 1-relay board, so only the "Ch 1" functionality was tested; the other channels were implemented per the manufacturer's documentation.
In particular, the method for determining the relay states on app startup for channels 2-4 was mostly an educated guess.

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwhC/AH4A/AAcK1QAO0AXFCx4ABFyowGC/4X/C/4X/C48AC6IWEGCIuFAAUN6AED7vdAwgEDC/4X/C/4X86AGGC85fpAH4A/AH4ASA"))

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@ -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<r.buffer.length/4; ++i) channel_state.push(r.buffer[i*4+2]==1);
}).then(setup_screen);
Bangle.on('swipe', function(direction){
switch(direction){
case 1:
drawButton((g.getWidth()-button_w)/2, 60+channel*button_sp, true);
characteristic.writeValue(cmds[channel].on);
break;
case -1:
drawButton((g.getWidth()-button_w)/2, 60+channel*button_sp, false);
characteristic.writeValue(cmds[channel].off);
break;
}});
setWatch(function() { var nc = channel-1; if (nc<0) nc = n_channels-1; moveChannelFrame(channel, nc); channel = nc; }, BTN1, {repeat:true, debounce:30});
setWatch(function() { moveChannelFrame(channel, (channel+1)%n_channels); channel = (channel+1)%n_channels; }, BTN3, {repeat:true, debounce:30});
}
connection_setup();
Bangle.loadWidgets();
Bangle.drawWidgets();

View File

@ -0,0 +1 @@
{"id":"dsdrelay","name":"DSD Relay","src":"dsdrelay.app.js","icon":"dsdrelay.img"}

Binary file not shown.

After

Width:  |  Height:  |  Size: 409 B

1
apps/fileman/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: New app!

11
apps/fileman/README.md Normal file
View File

@ -0,0 +1,11 @@
# FileManager
A small file manager, mostly written for debugging issues on the watch.
Upon opening, the app will display a list of all the files in storage (it will contract the sub-components of a StorageFile into one entry).
When selecting a file the following options appear (depending on file type detected by extension):
- Length: file size in bytes
- Display file: print out file contents on screen (will attempt to add back newlines for minimized JS code)
- Load file [*.js files only, no widgets]: load and execute javascript file
- Display image [*.img files only]: attempt to render file contents as image on screen
- Delete file: delete file (asks for confirmation first, will delete all components of a StorageFile)

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwhC/ABuIABgWIhAXNwAuVGBIXZmYAKC/4XXkQACC9Z3/C7YANC8J3/C8ciAAQXrO/4XbABoXhO/4XjkQACC9Z3/C7YANC78ICxuAC44wOCxAABiMRBKIAtA=="))

101
apps/fileman/fileman.app.js Normal file
View File

@ -0,0 +1,101 @@
const STOR = require("Storage");
const n = 9;
var nstart = 0;
var nend;
var m;
var files;
function delete_file(fn) {
E.showPrompt("Delete\n"+fn+"?", {buttons: {"No":false, "Yes":true}}).then(function(v) {
if (v) {
if (fn.charCodeAt(fn.length-1)==1) {
var fh = STOR.open(fn.substr(0, fn.length-1), "w");
fh.erase();
}
else STOR.erase(fn);
}
}).then(function() { files=get_pruned_file_list(); }).then(drawMenu);
}
function get_length(fn) {
var len;
if (fn.charCodeAt(fn.length-1)==1) {
var fh = STOR.open(fn.substr(0, fn.length-1), "r");
len = fh.getLength();
}
else len = STOR.read(fn).length;
return len;
}
function display_file(fn, qJS) {
g.clear().setColor(1, 1, 1);
var qStorageFile = (fn.charCodeAt(fn.length-1)==1);
var np = 0;
Terminal.println("");
var file_len = get_length(fn);
var fb = (qStorageFile ? STOR.open(fn.substr(0, fn.length-1), "r") : STOR.read(fn));
for (var i=0; i<file_len; ++i) {
if (np++ > 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+n<files.length)?nstart+n : files.length;
var menu = {
'': { 'title': 'Dir('+nstart+'-'+nend+')/'+files.length }
};
menu["< prev"] = function() {
nstart -= n;
if (nstart<0) nstart = files.length-n>0 ? files.length-n : 0;
menu = {};
drawMenu();
}
for (var i=nstart; i<nend; ++i) {
menu[files[i]] = visit_file.bind(null, files[i]);
}
menu["> next"] = function() {
if (nstart+n<files.length) nstart += n;
else nstart = 0;
menu = {};
drawMenu();
m.move(-1);
}
m = E.showMenu(menu);
}
function get_pruned_file_list() {
var fl = STOR.list(/^[^\.]/);
fl.sort();
fl = fl.concat(STOR.list(/^\./));
fl = fl.filter(f => (f.charCodeAt(f.length-1)>31 || f.charCodeAt(f.length-1)<2));
return fl;
}
files = get_pruned_file_list();
drawMenu();

Binary file not shown.

After

Width:  |  Height:  |  Size: 467 B

View File

@ -1 +1,2 @@
0.01: First Version
0.02: Remove HID requirement, update screen

View File

@ -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

View File

@ -1,33 +1,33 @@
var storage = require('Storage');
//notify your phone
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();
}
function find(){
Bluetooth.println(JSON.stringify({t:"findPhone", n:true}));
finding = !finding;
draw();
Bluetooth.println("\n"+JSON.stringify({t:"findPhone", n:finding}));
}
//init graphics
g.clear();
require("Font8x12").add(Graphics);
g.setFont("8x12",3);
g.setFontAlign(0,0);
g.flip();
draw();
//init settings
const settings = storage.readJSON('setting.json',1) || { HID: false };
//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);
}
//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});

1
apps/gmeter/app-icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwxH+AH4A/AH4ATlgAGFlgylEYdWq+BwOs1gDBq8yGL4eCmQqB64AIGgIyDFzQtBFhIAFGIZcYqxbKMZFWMSoVCLiBiGGCguM2YACGBgub1uJsoAExOtGDK7CFo4sFAAhjIYYQvOmTqGLYetE4mzM4L0JmQvNRpAuCQpAnDqx2GSJxeBFxDnKFwSmIMBheHXYQuPUwxgNBYIWFRh4uECQusF5iOFLwQuRUQIwFSBQ6Bq69GLw+swNdwKMGIgOJCQlXMBK+HXpAbCAAS7F2Z0GYBQJBwQZLVYeBDwREFIo4vMJIi+IDQIqCAgNdF5jwKF4xfBVIovHL5ovMDQztHR4pEER6ovGdwzvFq4TGd6YbFxNl1phHL4JdGaoSlFIYQvHGAMyJQxgHABReBIgsyFxLwHACZeBRwruKYBeJMJy9CLwq+KSBLBCF5ouCXoqOMMBYCERhQuGLxpgDYI5iBQApdM1heNMAdWEg7CJBQI6HqxeOSJQATrouQGDi8PF4wwXLoQvSGAdWeg4AK1i7CFyYxEmRiQwMyFq5iFGIJjK1gtDFzIxFGQNXwI0BFQOBq4sDFrgxHABItfGRgskAH4A/AFwA="))

6
apps/gmeter/app.js Normal file
View File

@ -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);
});

BIN
apps/gmeter/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -1,2 +1,3 @@
0.02: Ensure screen doesn't display garbage at startup
0.03: Show number of satellites while waiting for fix
0.04: Add Maidenhead readout of GPS location

View File

@ -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);

View File

@ -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

View File

@ -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;

View File

@ -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");
}

View File

@ -0,0 +1 @@
0.01: New Widget!

View File

@ -0,0 +1,59 @@
# GPS Time Server
A widget which automatically starts the GPS and turns Bangle.js into a Bluetooth time server, UUID 0x1805.
Other Espruino Bluetooth devices can then find it and use it to synchronise time.
**Note:** Because GPS is kept on, you'll need to keep your Bangle.js on charge for this to be useful.
## Usage
Just install this widget, and from then on any app which loads widgets will
display the icon ![](widget.png) in the top left, and Bangle.js will be
broadcasting the current time to any device that connects.
## Technical
This implements the [Bluetooth Time Service](https://www.bluetooth.org/docman/handlers/downloaddoc.ashx?doc_id=292957) listed [here](https://www.bluetooth.com/specifications/gatt/).
The Bluetooth docs are verbose and hard to read, so here's a rundown of how it works.
* The Bangle advertises service `0x1805`
* You connect to it, and request service `0x1805` and characteristic `0x2A2B`
* A 10 byte array is returned:
```
[
year_lowbyte,
year_highbyte,
month,
day_of_month,
hours,
minutes,
seconds,
day_of_week, // 1=monday...7=sunday
subseconds, // 0..255
update_reason // 0=unknown currently
]
```
```
//NRF.requestDevice({ filters: [{ services: ['1805'] }] }).then(print)
var gatt;
NRF.connect("c7:4b:2e:c6:f5:45 random").then(function(g) {
gatt = g;
return gatt.getPrimaryService("1805");
}).then(function(service) {
return service.getCharacteristic("2A2B");
}).then(function(c) {
return c.readValue();
}).then(function(d) {
console.log("Got:", JSON.stringify(d.buffer));
var year = d.getUint16(0,1);
// ...
gatt.disconnect();
}).catch(function(e) {
console.log("Something's broken.",e);
});
```

View File

@ -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
};
})()

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -1 +1,2 @@
0.01: New App!
0.02: Use HRM data and calculations from Bangle.js (don't access hardware directly)

View File

@ -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);*/
for (var i=0;i<2;i++) {
var a = hrmInfo.raw[hrmOffset];
hrmOffset++;
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*4),100,230);
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+2<t.length)
hrm = (t[mid]+t[mid+1]+t[mid+2])/3;
else if (mid<t.length)
hrm = t[mid];
else
hrm = 0;
g.setFontVector(40);
g.setFontAlign(0,0);
g.clearRect(0,0,239,100);
var str = hrm ? Math.round(hrm) : "?";
var px = 120;
g.drawString(str,px,40);
px += g.stringWidth(str)/2;
g.setFont("6x8");
g.drawString("BPM",px+20,60);
g.lineTo(hrmOffset, y);
}
}
wasLow = 0;
wasHigh = 1;
} else if (4*a < (max+3*min)) { // low
wasLow = 1;
} else { // middle
g.setColor(0.5,0,0);
g.fillRect(x,230,x,239);
g.setColor(1,1,1);
wasHigh = 0;
}
}
x++;
if (x>239)x=0;
}
setInterval(readHRM,50);

1
apps/isoclock/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: Created app based on digiclock with some small tweaks.

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwUC1QA/ACev/4AG/QLB3ptHvwLB+ALHh4LB6ALHg4LDnkD/8An4eBBYsPgcA+E8BY8AgfAAYILG+ALJF4ILJJwPDBZMDBZMMEQJHJL5J3LBfX/M4PAgaRB/gLC6ZnCmEPNQM8BYpnBWwQLG/4ZBBYvQn7UCC5ILXmAKBI4pfDLoIBB//HR8p0BAA0PBYO9BY9+BYOv/4AG/QLBAH4ASA="))

95
apps/isoclock/isoclock.js Normal file
View File

@ -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"});

BIN
apps/isoclock/isoclock.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

View File

@ -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

View File

@ -198,7 +198,6 @@ if (BTN3app) setWatch(
);
g.clear();
clearInterval();
drawClockFace();
interval = setInterval(drawClockFace, REFRESH_RATE);

View File

@ -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",

7
apps/mandel/README.md Normal file
View File

@ -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.

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwkA/4A/AEGIACQX/C/4X3x4XX/AXV/4XsBoYYFC6IPFC5gWFGAgXSDAgXIXAYXGF5mPA4ICCF6QUGC5wWKI5YVKR5ovWL7CPZX6zvXC5KPMDBYXVFwgXOB4QWFC9GPC65pKC5aBLC/4X/C54A/ADo"))

192
apps/mandel/mandel.app.js Normal file
View File

@ -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<maxy; ++y)
for (int x=minx; x<maxx; ++x) {
float cr = mincx+(maxcx-mincx)*x/240.0;
float ci = mincy+(maxcy-mincy)*y/240.0;
float zr=0, zi=0;
char niter = 31;
while (--niter && zr>-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);

1
apps/mandel/mandel.info Normal file
View File

@ -0,0 +1 @@
{"id":"mandel","name":"Mandel","src":"mandel.app.js","icon":"mandel.img"}

163
apps/mandel/mandel.min.js vendored Normal file
View File

@ -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);

BIN
apps/mandel/mandel.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 B

View File

@ -1 +1,2 @@
0.01: New App!
0.02: Adjust alignment for >1 device found

View File

@ -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();
}

15
apps/mywelcome/ChangeLog Normal file
View File

@ -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

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwxH+AH4A/AH4AU5gAEFtoxnEwXN53WAAXO5oJB42Wy26AAIueFoPXFggAD4AwEGTQiB6otBFgwAD3QvFGC5dCFxiRGGClhrdbv67BXAIuLMBIwPsIABF4OpLwXOFxjBCF6gtBw2r1mHXoXWFxqQWFwOH62rL4IeB6xeOAAIvHGBYuC6+rR4QvCXpovXw3X1i/DR4QuPR5AvKFQOs6+GF4eod4IvPd5AvLwvWLwQvCv4fBR54vURwOHF4iQCX0yOCF4aQBX0QvHSAoAN3SOSd4WyF4yQPLyhgD1YvDMCJeIFxhgCF47BN4BeHFxpgDSAiRORpAuPMIYAFGBYuaF5aSHFwQvEFqQwOeggSBLa4xNF4X+4wAC/xeCFjIADrYwGBIIvlMQiPDBAOk0gDBz2XF8BlEF4eIxADFF8lcF9n+wIrFF05bHF9AsGF9wupGAYv/F8QupGAov/F/4wOF1gA/AH4Ap"))

298
apps/mywelcome/app.js Normal file
View File

@ -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;i<l-2;i++) {
var x = 120+(i-(l-2)/2)*12;
if (i<sceneNumber-1) {
g.setColor(-1);
g.fillCircle(x,230,4);
} else {
g.setColor(0);
g.fillCircle(x,230,4);
g.setColor(-1);
g.drawCircle(x,230,4);
}
}
}
if (sceneNumber < scenes.length-1)
setTimeout(function() {
move(1);
}, (sceneNumber==0) ? 20000 : 5000);
}
Bangle.on('swipe',move);
setWatch(()=>move(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);

BIN
apps/mywelcome/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

9
apps/mywelcome/boot.js Normal file
View File

@ -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')
})
}
})()

137
apps/mywelcome/custom.html Normal file
View File

@ -0,0 +1,137 @@
<html>
<head>
<link rel="stylesheet" href="../../css/spectre.min.css">
</head>
<body>
<div class="form-group">
<p>Style: <select class="form-select" id="style">
<option>Christmas</option>
<option>Birthday</option>
</select>
</div></p>
<p>Line 1: <input type="text" id="line1" class="form-input" value="Merry Christmas"></p>
<p>Line 2: <input type="text" id="line2" class="form-input" value="Someone"></p>
<p>Line 3 (smaller): <input type="text" id="line3" class="form-input" value="Love from"></p>
<p>Line 4 (smaller): <input type="text" id="line4" class="form-input" value="Espruino Team"></p>
</div>
<p><button id="try" class="btn">Try in Emulator</button></p>
<p><button id="upload" class="btn btn-primary">Upload</button></p>
<p>This is currently Christmas-themed, but more themes will be added in the future.</p>
<script src="../../core/lib/customize.js"></script>
<script>
function getApp() {
// get the text to add
var line1 = document.getElementById("line1").value;
var line2 = document.getElementById("line2").value;
var line3 = document.getElementById("line3").value;
var line4 = document.getElementById("line4").value;
var style = document.getElementById("style").value;
// build the app's text using a templated String
if (style=="Birthday") return `(function() {
var ib = require("heatshrink").decompress(atob("jk0ggGDhOZAAWQCYwMEBxAMFAAIaHyc/+c5DgwMC/84Dg4aCBgwcDBoOf+Y4GBoQEBn4zCI44DBDQ4NEyf4BpgoIBoefxINMBhApEBrQAKBrrrGWpANZHBT7FBpYqIFAYcJBggNOFQwoFDgwMHBwoMIBwYMKBrkykANLmcwBu0zBrMDBv4AFN5gA/ADY"));
var ir = require("heatshrink").decompress(atob("jk0ggGDhvdAAXQCYwMEBxAMFAAIaH6c/+c9DgwMC/8zDg4aC/4YCHIwNB7/zHAwNCAgM/DQwqDAYIaHBonT/oNMFBAND74NNBhApEBrQAKBrrrGWpANZHBT7FBpYqIFAYcJBgkA5oMF7gNFFQwoFDgwMHHIoMIAAPM5gMKBrk0oANLmcwBu0zBrMDBv4AFN5gA/ADYA="));
var ig = require("heatshrink").decompress(atob("jk0ggGDg93AAVwCYwMEBxAMFAAIaHuc/+c3DgwMC/8yDg4aC/4YCHIwNBv/zHAwNCAgM/DQwqDAYIaHBolz+4NMFBANDv8nBpgMIFIgNaABQNddYy1IBrI4KfYoNLFRAoDDhIMEgHnBgt+BooqGFAoqGBg4OFBhAODBhQNcmUgBpczmAN2mYNZgYN/AApvMAH4Ab"));
var igift = require("heatshrink").decompress(atob("q1QxH+ADOi0QbZ5nMHDQAbKgIACKa4ACKnJWVKghW0KgxWTKgxWyKhBWRKhBWwKhRWPKhRWuKhhWNKhhWtKpxWKKhys8KxBU8Ky5U+KypU/KyhU/KyhU/KynGKn5WTKn5WUKmHCADpJJE7uYABZUfKuuYKv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/AAv+Kv5VT/wADyIAaKpIlbABZSEKv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/Kv5V/ADNtKv6rdKzZVwKhAABy5V/Khw"));
var W=240,H=240;
var blns = [];
function updateFlake(f) {
f.im = [ir,ig,ib][Math.round(Math.random()*100)%3];
f.s = 0.4+Math.random()*0.5;
}
for (var i=0;i<6;i++) {
var f = {
y:Math.random()*H,x:(0.5+(i<3?i:i+5))*W/11,
v:0.5+Math.random(),r:0,
t:(0.5+Math.random())*0.15,
};
updateFlake(f);
blns.push(f);
}
function draw() {
blns.forEach(f=>{
f.y-=f.v;f.r+=f.t;
if (f.y<-22) {
f.y=H+22;
updateFlake(f);
}
g.drawImage(f.im,f.x,f.y,{rotate:Math.sin(f.r)/2,scale:f.s});
});
var x = W/2, y = H/2;
g.drawImage(igift,x-43,y-80);
g.setFont("6x8",2).setFontAlign(0,0);
g.drawString(${JSON.stringify(line1)},x,y+=20);
g.drawString(${JSON.stringify(line2)},x,y+=20);
g.setFont("6x8");
g.drawString(${JSON.stringify(line3)},x,y+=20);
g.drawString(${JSON.stringify(line4)},x,y+=10);
g.flip();
}
g.clear();
setInterval(draw,50);
})()`;
// if (style=="Christmas")
return `(function() {
var isnow = require("heatshrink").decompress(atob("jEagQWTgfAAocf+gFDh4FDiARBggVB3AFBl3Agf8jfkn/AgX/v/9/+Agfv/2//YrBgfwh4wCgfghYFJCIYdFFIw1EIIpNFL44FFOIoAP"));
var itree = require("heatshrink").decompress(atob("mtWxH+ADHHDTI0aGuXH5vNGmhqvTYIzBGtoxF6fTG4g4oGgQyBAAZssGoI0Ga1g1FGdo01ZgIAEGmHHNoLSuAAN/rdb0YFBGlgCBGYIABA4YArGYY1CGn4znAAM6GeVd5PQ5Iyurc/vQ0oGZFAn+d4XC3d5GddiGYIEBy+7zoEBGlFhoEcsQ9GT08+oFk1mkGdaVBMgNArnJ6/KzswGs/J6GlrlbqtbvPC5PCy8wGohniMIPJvIpCqmX3e7vI0BqhqlMIY0DqhtBqoEBa0xgBMIIoEqoABGQwzfsIhBv4qHABM50vQGjg1CGaN66DoBGt1ioGd5LoBGjo1PGYNhvLoCa7wnBqgvGA4YzCAgN5GUAsCqoDBmAHCAYU/wPQ0oSDGcBiDqkwAYcxoFd5PX6GdGjrIIqtUAAc3jk5vPC4fCy5pef5I2BTQMcnAHBy+7y95T0oADnFk1ekBpI2aGRUin7NGAA9hsIzVsIgHTAKZBZoPJ5LNDGhBpXGolcwOsrtcA4TNB3bNDGb/+sVin9AoGe6HX5InEvN/TkP+5XQwM/sRsBzqWB4QuKGjvC6HQ4QdDvKWBZYMwmAuHmFUCYNbqibX3fD5O7qolEZQQ0FBwgKDqgJBGiphEDwNUEgJbBFIQqCAgYOCB4IzCnE6GyhYFGoQnDABYzGAAQ1UAAo2NBoQSBnOB0t/Gjo2EABIPCoGe6HX4QzTGRIAEqtVF4QEBBQc4oE4y/J5PCvIxeABk/oADBvO73eXTyAyZMwM/Awd5vIOFGslAr2Av4PLNcU/jmA6HX5I1KasFcn8dTIOd5PJ4SZGGiNhAAIyNn0ckU+ZYe7AAJpJEYJnNGZk+n9kw9cBAcwGoN5aZg1JJJQABm8/oEjoDKC5ALCrUwqh/NrvQ6HDGp04n9doEdoE/sQJBZQZhCqgABGZk6zw0K/1dnVAoNAFwOlCYL1FubJBy4GCGh1AnOX4XC3YzHFYOeCgdV5PQ5OdD4rKBqqYNGYlbv+X3edGY3CGgKMDAAO7JAJgDAClcr2BEYgADaIZ0DL4uXGbDuB6HX5I1GsP+sNhOgWXIhBmWd4Od5PK4TwFGIJoBAYI2BAD0/jlcQoO7AAJaEGQQADGr0/sjNEvOdAoZmDGgw2ZsVAkeAZpQACGZI2VsU/kVGn1bZoPJZogpGGhA4GfRYwBoGC1mlBQbNFFoo0JNxAGCEod/wM6oFAn9iv/J6/Kzo1Ey9/MZQAKCg4GCFgTDEvPCSwI0BC5I0RN4ocEYYPQ5OdHgeXSwTFKGaJyKFYPC3f+MIdbpzFLAD4zB/1OqtbqtOGgYArGAIADGl9UAAI0wGQN5GoQ0vvIABGoI0uGYQABqo0zNOg0uaQY0/GllOGn40//w="));
var W=g.getWidth(),H=g.getHeight();
var flakes = [];
for (var i=0;i<10;i++) {
var f = {
y:Math.random()*H,
x:(0.5+(i<5?i:i+5))*W/15,
v:0.7+Math.random(),
s:0.6+Math.random(),
r:0,
t:0.1*(Math.random()-0.5)
};
f.v = f.s * (1+Math.random());
flakes.push(f);
}
function draw() {
flakes.forEach(f=>{
f.y+=f.v;f.r+=f.t;
if (f.y>H+16) f.y=-8;
g.drawImage(isnow,f.x,f.y,{rotate:f.r,scale:f.s});
});
var x = W/2, y = H/2;
g.drawImage(itree,x-27,y-80);
g.setFont("6x8",2).setFontAlign(0,0);
g.drawString(${JSON.stringify(line1)},x,y+=20);
g.drawString(${JSON.stringify(line2)},x,y+=20);
g.setFont("6x8");
g.drawString(${JSON.stringify(line3)},x,y+=20);
g.drawString(${JSON.stringify(line4)},x,y+=10);
g.flip();
}
g.clear();
setInterval(draw,50);
})();
`;
}
// when 'try' is clicked, load the emulator...
document.getElementById("try").addEventListener("click", function() {
window.open("https://www.espruino.com/ide/emulator.html?code="+encodeURIComponent(getApp())+"&upload");
});
// When the 'upload' button is clicked...
document.getElementById("upload").addEventListener("click", function() {
// send finished app (in addition to contents of app.json)
sendCustomizedApp({
storage:[
{name:"mywelcome.custom.js", url:"app.js", content:getApp()},
]
});
});
</script>
</body>
</html>

View File

@ -0,0 +1,18 @@
(function(back) {
let settings = require('Storage').readJSON('mywelcome.json', 1)
|| require('Storage').readJSON('setting.json', 1) || {}
E.showMenu({
'': { 'title': 'Welcome App' },
'Run next boot': {
value: !settings.welcomed,
format: v => v ? 'Yes' : 'No',
onchange: v => require('Storage').write('mywelcome.json', {welcomed: !v}),
},
'Run Now': () => load('mywelcome.app.js'),
'Turn off & run next': () => {
require('Storage').write('mywelcome.json', {welcomed: false});
Bangle.off();
},
'< Back': back,
})
})

1
apps/ncrclk/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: A copy of the analogimgclk to work for NodeConf Remote

1
apps/ncrclk/app-icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwkGswA/AFEiAAMoCqcykWEDQQWW0YYNsQXCn8//8zDgMiwwWNmf/CwICCDAUmIpYWD+YYFkIuKkYTBCogGCmUhGBAuBn4QBF4wJBiR6IFwYRCFoYBCVZBGBRQIYFFwaUBkUWFw4XKBIUhGAwXBEwYXFmcTBIMxC4pGBUgQXCLYc/kMvAgKqBSIheGGInyiQGCn8SC43zCwouDHQfzF4x2DFAgFCCwbaBSAi9CAAPxiMTRIcvEQIYCeQIXI+chiMSn8zGgJeDn8yiQXHBoMzDAMRiEzCwgXBF5IPCCwMQCoZUDYAhHFCQUBgIHFF5YRDkMDCwpfKAAn074UDC5QOHC48xL4jvDF5kznFGC4ovOmciwwXFWwIACB4M0C48hC4x4EC44kB+UYI4h4DGIhHEBIUyjAWEC4JIEF4VPF4shlAXFJAYQD+gXBEAcziReEGAg/F74ECBIIuHC4UhCAIZC+UzOokhkIXHJAMTDAQCGmOEkwXHGASSDAQk4oUSCxAwCiUjC4sooUhFxIYCkMilEzCwMymIgBRg5JGiUiwoDBlUijFCCxgYDIQIXCRZAAJJQIWBCqIA/AC4="))

129
apps/ncrclk/app.js Normal file
View File

@ -0,0 +1,129 @@
var locale = require("locale");
var bgimg = {
width : 100, height : 100, bpp : 4,
buffer : require("heatshrink").decompress(atob("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"))
}
function getImg(g, col) {
return {
width:g.getWidth(),
height:g.getHeight(),
bpp:1,transparent:0,
buffer:g.buffer,
palette:new Uint16Array([0,col])};
}
var handSizeMin = 40;
var handSizeHr = 25;
var handSizeSec = 50;
var gmin = Graphics.createArrayBuffer(6,handSizeMin*2,1,{msb:true});
var gminimg = getImg(gmin, 0xFFFF);
var ghr = Graphics.createArrayBuffer(8,handSizeHr*2,1,{msb:true});
var ghrimg = getImg(ghr, g.setColor("#E0E0E0").getColor());
var gsec = Graphics.createArrayBuffer(2,handSizeSec*2,1,{msb:true});
var gsecimg = getImg(gsec, g.setColor("#FF0000").getColor());
var lastDate;
// create hand images
var c = gmin.getHeight()/2;
var o = 8; // overhang
gmin.fillCircle(2,2,2);
gmin.fillCircle(2,c+o,2);
gmin.fillRect(0,2,4,c+o);
c = ghr.getHeight()/2;
ghr.fillCircle(4,4,4);
ghr.fillCircle(4,c+o,4);
ghr.fillRect(0,4,7,c+o);
c = gsec.getHeight()/2;
gsec.fillRect(0,1,2,c+o);
// last positions of hands (in radians)
var lastrmin=0, lastrhr=0, lastrsec=0;
// draw hands - just the bit of the image that changed
function drawHands(full) {
var d = new Date();
var rsec = d.getSeconds()*Math.PI/30;
var rmin = d.getMinutes()*Math.PI/30;
// hack so hour hand only moves every 10 minutes
var rhr = (d.getHours() + Math.round(d.getMinutes()/10)/6)*Math.PI/6;
var bounds = {};
if (!full) { // work out the bounds of the hands
var x1 = (g.getWidth()/2)-10;
var y1 = (g.getHeight()/2)-10 - 36;
var x2 = (g.getWidth()/2)+10;
var y2 = (g.getHeight()/2)+10 - 36;
function addPt(ang, r, ry) {
var x = (g.getWidth()/2) + Math.sin(ang)*r + Math.cos(ang)*ry;
var y = (g.getHeight()/2) - Math.cos(ang)*r + Math.sin(ang)*ry - 36;
//g.setColor("#ff0000").fillRect(x-2,y-2,x+2,y+2);
if (x<x1)x1=x;
if (y<y1)y1=y;
if (x>x2)x2=x;
if (y>y2)y2=y;
}
function addSec(r) {
addPt(r,handSizeSec+5,5);addPt(r,handSizeSec+5,-5);
addPt(r,-(o+10),5);addPt(r,-(o+10),-5);
}
function addMin(r) {
addPt(r,handSizeMin,5);addPt(r,handSizeMin,-5);
addPt(r,-(o+8),5);addPt(r,-(o+8),-5);
}
function addHr(r) {
addPt(r,handSizeHr,8);addPt(r,handSizeHr,-8);
addPt(r,-(o+8),8);addPt(r,-(o+8),-8);
}
if (rsec!=lastrsec) {
addSec(rsec);addSec(lastrsec);
}
if (rmin!=lastrmin) {
addMin(rmin);addMin(lastrmin);
}
if (rhr!=lastrhr) {
addHr(rhr);addHr(lastrhr);
}
bounds = {x:x1,y:y1,width:1+x2-x1,height:1+y2-y1};
}
g.drawImages([
{image:bgimg,x:20,y:25,scale:2},
{image:ghrimg,x:120,y:120-31,center:true,rotate:rhr},
{image:gminimg,x:120,y:120-31,center:true,rotate:rmin},
{image:gsecimg,x:120,y:120-31,center:true,rotate:rsec}
],bounds);
lastrsec = rsec;
lastrmin = rmin;
lastrhr = rhr;
// Date
var date = locale.date(new Date(),false);
if (date === lastDate) return;
lastDate = date;
g.reset();
g.setFont("6x8");
g.setFontAlign(0,-1);
g.drawString(date, g.getWidth()/2, 232, true);
}
var secondInterval = setInterval(drawHands,1000);
// handle display switch on/off
Bangle.on('lcdPower', (on) => {
if (secondInterval) {
clearInterval(secondInterval);
secondInterval = undefined;
}
if (on) {
drawHands();
secondInterval = setInterval(drawHands,1000);
}
});
g.clear();
Bangle.loadWidgets();
Bangle.drawWidgets();
drawHands(true);
// Show launcher when middle button pressed
setWatch(Bangle.showLauncher, BTN2, { repeat: false, edge: "falling" });

BIN
apps/ncrclk/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

@ -1,3 +1,4 @@
0.01: New Library!
0.02: Add notification ID option
0.03: Pass `area{x,y,w,h}` to render callback instead of just `y`
0.05: Adjust position of notification src text

View File

@ -94,8 +94,8 @@ exports.show = function(options) {
g.setColor(-1).setFontAlign(-1, -1, 0).setFont("6x8", 2);
g.drawString(title.trim().substring(0, 13), x+25,y+3);
if (options.title && options.src) {
g.setFont("6x8", 1);
g.drawString(options.src.substring(0, 10), x+215,y+5);
g.setFont("6x8", 1).setFontAlign(1, 1, 0);
g.drawString(options.src.substring(0, 10), g.getWidth()-23,y+18);
}
y += 20;h -= 20;
}

View File

@ -3,3 +3,4 @@
0.03: Fix custom render callback
0.04: Pass `area{x,y,w,h}` to render callback instead of just `y`
0.05: Fix `g` corruption issue if .hide gets called twice
0.06: Adjust position of notification src text and notifications without title

View File

@ -49,14 +49,13 @@ exports.show = function(options) {
if (size>120) {size=120}
Bangle.setLCDMode("direct");
let x = 0,
y = 0,
y = 40,
w = 240,
h = 240;
h = size;
// clear screen
g.clear(1);
// top bar
if (options.title||options.src) {
y=40;h=size;
const title = options.title || options.src
g.setColor(0x39C7).fillRect(x, y, x+w-1, y+30);
g.setColor(-1).setFontAlign(-1, -1, 0).setFont("6x8", 3);
@ -64,7 +63,8 @@ exports.show = function(options) {
if (options.title && options.src) {
g.setColor(-1).setFontAlign(1, 1, 0).setFont("6x8", 2);
// above drawing area, but we are fullscreen
g.drawString(options.src.substring(0, 10), x+235, y-32);
print(options.src.substring(0, 10), w-23, y-4);
g.drawString(options.src.substring(0, 10), w-16, y-4);
}
y += 30;h -= 30;
}

2
apps/petrock/ChangeLog Normal file
View File

@ -0,0 +1,2 @@
0.01: New App!
0.02: Ensure eyes are white even if a widget (eg Bluetooth) changes the draw color

1
apps/petrock/app-icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("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"))

73
apps/petrock/app.js Normal file

File diff suppressed because one or more lines are too long

BIN
apps/petrock/petrock.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -1 +1,2 @@
0.01: First release
0.02: Colour changes dependent on roll result

View File

@ -14,12 +14,18 @@ function getDie() {
}
function setColors(lastBounce) {
if (lastBounce) {
bgColor = 0xFFFF;
if (lastBounce && face == getDie()) {
bgColor = 0x0000; // Critical Hit
fgColor = 0xF800;
} else if (lastBounce && face == 1){
bgColor = 0xF800; // Critical Miss
fgColor = 0x0000;
} else {
bgColor = 0x0000
} else if (lastBounce){
bgColor = 0x0000; // Other Result
fgColor = 0xFFFF;
} else {
bgColor = 0x0000; // Still Rolling
fgColor = 0x7BEF;
}
}

View File

@ -24,3 +24,4 @@
0.20: Fix set time menu, allow dates to roll over
0.21: Add passkey pairing option (BETA)
Add whitelist option (fix #78)
0.22: Move HID to BLE menu

Some files were not shown because too many files have changed in this diff Show More