merge with upstream

master
hughbarney 2021-10-20 21:17:29 +01:00
commit 30555f9715
20 changed files with 2993 additions and 1738 deletions

View File

@ -217,8 +217,9 @@ and which gives information about the app for the Launcher.
{ "id": "appid", // 7 character app id
"name": "Readable name", // readable name
"shortName": "Short name", // short name for launcher
"icon": "icon.png", // icon in apps/
"version": "0v01", // the version of this app
"description": "...", // long description (can contain markdown)
"icon": "icon.png", // icon in apps/
"type":"...", // optional(if app) -
// 'app' - an application
// 'widget' - a widget
@ -226,6 +227,7 @@ and which gives information about the app for the Launcher.
// '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
"supports": ["BANGLEJS2"], // List of device IDs supported, either BANGLEJS or BANGLEJS2
"dependencies" : { "notify":"type" } // optional, app 'types' we depend on
// for instance this will use notify/notifyfs is they exist, or will pull in 'notify'
"readme": "README.md", // if supplied, a link to a markdown-style text file

4030
apps.json

File diff suppressed because it is too large Load Diff

View File

@ -2,13 +2,14 @@
{ "id": "7chname",
"name": "My app's human readable name",
"shortName":"Short Name",
"icon": "app.png",
"version":"0.01",
"description": "A detailed description of my great app",
"icon": "app.png",
"tags": "",
"supports" : ["BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"7chname.app.js","url":"app.js"},
{"name":"7chname.img","url":"app-icon.js","evaluate":true}
]
}
}

View File

@ -2,11 +2,12 @@
{ "id": "7chname",
"name": "My widget's human readable name",
"shortName":"Short Name",
"icon": "widget.png",
"version":"0.01",
"description": "A detailed description of my great widget",
"tags": "widget",
"icon": "widget.png",
"type": "widget",
"tags": "widget",
"supports" : ["BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"7chname.wid.js","url":"widget.js"}

View File

@ -31,3 +31,6 @@
Fix issues where 'Uncaught Error: Function not found' could happen with multiple .boot.js
0.30: Remove 'Get GPS time' at boot. Latest firmwares keep time through reboots, so this is not needed now
0.31: Add polyfills for g.wrapString, g.imageMetrics, g.stringMetrics
0.32: Fix single quote error in g.wrapString polyfill
improve g.stringMetrics polyfill
Fix issue where re-running bootupdate could disable existing polyfills

View File

@ -81,9 +81,11 @@ if (s.quiet && s.qmTimeout) boot+=`Bangle.setLCDTimeout(${s.qmTimeout});\n`;
if (s.passkey!==undefined && s.passkey.length==6) boot+=`NRF.setSecurity({passkey:${s.passkey}, mitm:1, display:1});\n`;
if (s.whitelist) boot+=`NRF.on('connect', function(addr) { if (!(require('Storage').readJSON('setting.json',1)||{}).whitelist.includes(addr)) NRF.disconnect(); });\n`;
// Pre-2v10 firmwares without a theme/setUI
delete g.theme; // deleting stops us getting confused by our own decl. builtins can't be deleted
if (!g.theme) {
boot += `g.theme={fg:-1,bg:0,fg2:-1,bg2:7,fgH:-1,bgH:0x02F7,dark:true};\n`;
}
delete Bangle.setUI; // deleting stops us getting confused by our own decl. builtins can't be deleted
if (!Bangle.setUI) { // assume this is just for F18 - Q3 should already have it
boot += `Bangle.setUI=function(mode, cb) {
if (Bangle.btnWatches) {
@ -131,6 +133,7 @@ else if (mode=="updown") {
throw new Error("Unknown UI mode");
};\n`;
}
delete g.imageMetrics; // deleting stops us getting confused by our own decl. builtins can't be deleted
if (!g.imageMetrics) { // added in 2v11 - this is a limited functionality polyfill
boot += `Graphics.prototype.imageMetrics=function(src) {
if (src[0]) return {width:src[0],height:src[1]};
@ -141,15 +144,18 @@ if (!g.imageMetrics) { // added in 2v11 - this is a limited functionality polyfi
return {width:im.charCodeAt(0), height:im.charCodeAt(1)};
};\n`;
}
delete g.stringMetrics; // deleting stops us getting confused by our own decl. builtins can't be deleted
if (!g.stringMetrics) { // added in 2v11 - this is a limited functionality polyfill
boot += `Graphics.prototype.stringMetrics=function(txt) {
return {width:this.stringWidth(txt), height:this.getFontHeight()};
txt = txt.toString().split("\\n");
return {width:Math.max.apply(null,txt.map(x=>g.stringWidth(x))), height:this.getFontHeight()*txt.length};
};\n`;
}
delete g.wrapString; // deleting stops us getting confused by our own decl. builtins can't be deleted
if (!g.wrapString) { // added in 2v11 - this is a limited functionality polyfill
boot += `Graphics.prototype.wrapString=function(str, maxWidth) {
var lines = [];
for (var unwrappedLine of str.split("\n")) {
for (var unwrappedLine of str.split("\\n")) {
var words = unwrappedLine.split(" ");
var line = words.shift();
for (var word of words) {

View File

@ -5,3 +5,5 @@
0.11: added Heart Rate Monitor status and ability to turn on/off
0.12: added support for different locales
0.13: Use setUI, work with smaller screens and themes
0.14: Fix BTN1 (fix #853)
Add light/dark theme support

View File

@ -20,6 +20,8 @@ const HRT_FN_MODE = "fn_hrt";
let infoMode = NONE_MODE;
let functionMode = NONE_FN_MODE;
let textCol = g.theme.dark ? "#0f0" : "#080";
function drawAll(){
updateTime();
updateRest(new Date());
@ -45,9 +47,7 @@ function writeLineStart(line){
function writeLine(str,line){
var y = marginTop+line*fontheight;
g.setFont("6x8",fontsize);
//g.setColor(0,1,0);
g.setColor("#0f0");
g.setFontAlign(-1,-1);
g.setColor(textCol).setFontAlign(-1,-1);
g.clearRect(0,y,((str.length+1)*20),y+fontheight-1);
writeLineStart(line);
g.drawString(str,25,y);
@ -56,7 +56,7 @@ function writeLine(str,line){
function drawInfo(line) {
let val;
let str = "";
let col = "#0f0"; // green
let col = textCol; // green
//console.log("drawInfo(), infoMode=" + infoMode + " funcMode=" + functionMode);
@ -64,7 +64,7 @@ function drawInfo(line) {
case NONE_FN_MODE:
break;
case HRT_FN_MODE:
col = "#0ff"; // cyan
col = g.theme.dark ? "#0ff": "#088"; // cyan
str = "HRM: " + (hrtOn ? "ON" : "OFF");
drawModeLine(line,str,col);
return;
@ -72,7 +72,7 @@ function drawInfo(line) {
switch(infoMode) {
case NONE_MODE:
col = "#fff";
col = g.theme.bg;
str = "";
break;
case HRT_MODE:
@ -104,9 +104,8 @@ function drawModeLine(line, str, col) {
g.setColor(col);
var y = marginTop+line*fontheight;
g.fillRect(0, y, 239, y+fontheight-1);
g.setColor(0);
g.setFontAlign(0, -1);
g.drawString(str, g.getWidth()/2, y);
g.setColor(g.theme.bg).setFontAlign(0, 0);
g.drawString(str, g.getWidth()/2, y+fontheight/2);
}
function changeInfoMode() {
@ -193,7 +192,7 @@ Bangle.on('lcdPower',function(on) {
var click = setInterval(updateTime, 1000);
// Show launcher when button pressed
Bangle.setUI("clockupdown", btn=>{
if (btn==0) changeInfoMode();
if (btn==1) changeFunctionMode();
if (btn<0) changeInfoMode();
if (btn>0) changeFunctionMode();
drawAll();
});
});

1
apps/fwupdate/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: Initial version

BIN
apps/fwupdate/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

284
apps/fwupdate/custom.html Normal file
View File

@ -0,0 +1,284 @@
<html>
<head>
<link rel="stylesheet" href="../../css/spectre.min.css">
</head>
<body>
<div id="fw-unknown">
<p>Firmware updates using the App Loader are only possible on
Bangle.js 2. For firmware updates on Bangle.js 1 please
<a href="https://www.espruino.com/Bangle.js#firmware-updates" target="_blank">see the Bangle.js 1 instructions</a></p>
</div>
<div id="fw-ok" style="display:none">
<p>Please upload a hex file here. This file should be the <code>.app_hex</code>
file, *not* the normal <code>.hex</code> (as that contains the bootloader as well).</p>
<input class="form-input" type="file" id="fileLoader" accept=".hex,.app_hex"/><br>
<p><button id="upload" class="btn btn-primary">Upload</button></p>
</div>
<pre id="log"></pre>
<script src="../../core/lib/customize.js"></script>
<script>
var hex;
var hexJS; // JS to upload hex
var HEADER_LEN = 16; // size of app flash header
var MAX_ADDRESS = 0x1000000; // discount anything in hex file above this
var VERSION = 0x12345678; // VERSION! Use this to test firmware in JS land
var DEBUG = false;
function log(t) {
document.getElementById('log').innerText += t+"\n";
console.log(t);
}
function onInit(device) {
console.log(device);
if (device && device.id=="BANGLEJS2") {
document.getElementById("fw-unknown").style = "display:none";
document.getElementById("fw-ok").style = "";
}
}
function checkForFileOnServer() {
/*function getURL(url, callback) {
var xhr = new XMLHttpRequest();
xhr.onload = callback;
baseURL = url;
xhr.open("GET", baseURL);
xhr.responseType = "document";
xhr.send();
}
function getFilesFromURL(url, regex, callback) {
getURL(url, function() {
var files = [];
var elements = this.responseXML.getElementsByTagName("a");
for (var i=0;i<elements.length;i++) {
var href = elements[i].href;
if (regex.exec(href)) {
files.push(href);
}
}
callback(files);
});
}
var regex = new RegExp("_bangle2");
var domFirmware = document.getElementById("latest-firmware");
getFilesFromURL("https://www.espruino.com/binaries/", regex, function(releaseFiles) {
releaseFiles.sort().reverse().forEach(function(f) {
var name = f.substr(f.substr(0,f.length-1).lastIndexOf('/')+1);
domFirmware.innerHTML += 'Release: <a href="'+f+'">'+name+'</a><br/>';
});
getFilesFromURL("https://www.espruino.com/binaries/travis/master/",regex, function(travisFiles) {
travisFiles.forEach(function(f) {
var name = f.substr(f.lastIndexOf('/')+1);
domFirmware.innerHTML += 'Cutting Edge build: <a href="'+f+'">'+name+'</a><br/>';
});
document.getElementById("checking-server").style = "display:none";
document.getElementById("main-ui").style = "";
});
});*/
}
function downloadFile() {
/*response = await fetch(APP_HEX_PATH+"readlink.php?link="+APP_HEX_FILE, {
method: 'GET',
cache: 'no-cache',
});
if (response.ok) {
blob = await response.blob();
data = await blob.text();
document.getElementById("latest-firmware").innerHTML="(<b>"+data.toString()+"</b>)";
}*/
}
function handleFileSelect(event) {
if (event.target.files.length!=1) {
log("More than one file selected!");
return;
}
var reader = new FileReader();
reader.onload = function(event) {
hex = event.target.result.split("\n");
document.getElementById("upload").style = ""; // show upload
fileLoaded();
};
reader.readAsText(event.target.files[0]);
};
function parseLines(dataCallback) {
var addrHi = 0;
hex.forEach(function(hexline) {
if (DEBUG) console.log(hexline);
var bytes = hexline.substr(1,2);
var addrLo = parseInt(hexline.substr(3,4),16);
var cmd = hexline.substr(7,2);
if (cmd=="02") addrHi = parseInt(hexline.substr(9,4),16) << 4; // Extended Segment Address
else if (cmd=="04") addrHi = parseInt(hexline.substr(9,4),16) << 16; // Extended Linear Address
else if (cmd=="00") {
var addr = addrHi + addrLo;
var data = [];
for (var i=0;i<16;i++) data.push(parseInt(hexline.substr(9+(i*2),2),16));
dataCallback(addr,data);
}
});
}
function CRC32(data) {
var crc = 0xFFFFFFFF;
data.forEach(function(d) {
crc^=d;
crc=(crc>>>1)^(0xEDB88320&-(crc&1));
crc=(crc>>>1)^(0xEDB88320&-(crc&1));
crc=(crc>>>1)^(0xEDB88320&-(crc&1));
crc=(crc>>>1)^(0xEDB88320&-(crc&1));
crc=(crc>>>1)^(0xEDB88320&-(crc&1));
crc=(crc>>>1)^(0xEDB88320&-(crc&1));
crc=(crc>>>1)^(0xEDB88320&-(crc&1));
crc=(crc>>>1)^(0xEDB88320&-(crc&1));
});
return (~crc)>>>0; // >>>0 converts to unsigned 32-bit integer
}
function btoa(input) {
var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
var out = "";
var i=0;
while (i<input.length) {
var octet_a = 0|input[i++];
var octet_b = 0;
var octet_c = 0;
var padding = 0;
if (i<input.length) {
octet_b = 0|input[i++];
if (i<input.length) {
octet_c = 0|input[i++];
padding = 0;
} else
padding = 1;
} else
padding = 2;
var triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
out += b64[(triple >> 18) & 63] +
b64[(triple >> 12) & 63] +
((padding>1)?'=':b64[(triple >> 6) & 63]) +
((padding>0)?'=':b64[triple & 63]);
}
return out;
}
// To upload the app, we write to external flash
function createJS_app(binary, bin32, startAddress, endAddress, HEADER_LEN) {
/* typedef struct {
uint32_t address;
uint32_t size;
uint32_t CRC;
uint32_t version;
} FlashHeader; */
bin32[0] = startAddress;
bin32[1] = endAddress - startAddress;
bin32[2] = CRC32(new Uint8Array(binary.buffer, HEADER_LEN));
bin32[3] = VERSION; // VERSION! Use this to test ourselves
console.log("CRC 0x"+bin32[2].toString(16));
hexJS = "";//`\x10if (E.CRC32(E.memoryArea(${startAddress},${endAddress-startAddress}))==${bin32[2]}) { print("FIRMWARE UP TO DATE!"); load();}\n`;
hexJS += '\x10var s = require("Storage");\n';
var CHUNKSIZE = 1024;
for (var i=0;i<binary.length;i+=CHUNKSIZE) {
var l = binary.length-i;
if (l>CHUNKSIZE) l=CHUNKSIZE;
var chunk = btoa(new Uint8Array(binary.buffer, i, l));
hexJS += `\x10s.write('.firmware', atob("${chunk}"), 0x${i.toString(16)}, ${binary.length});\n`;
}
hexJS += '\x10setTimeout(()=>E.showMessage("Rebooting..."),50);\n';
hexJS += '\x10setTimeout(()=>E.reboot(), 1000);\n';
}
// To upload the bootloader, we write to internal flash, right over bootloader
function createJS_bootloader(binary, startAddress, endAddress) {
var crc = CRC32(binary);
console.log("CRC 0x"+crc.toString(16));
hexJS = `\x10if (E.CRC32(E.memoryArea(${startAddress},${endAddress-startAddress}))==${crc}) { print("BOOTLOADER UP TO DATE!"); load();}\n`;
hexJS += `\x10var _fw = new Uint8Array(${binary.length})\n`;
var CHUNKSIZE = 1024;
for (var i=0;i<binary.length;i+=CHUNKSIZE) {
var l = binary.length-i;
if (l>CHUNKSIZE) l=CHUNKSIZE;
var chunk = btoa(new Uint8Array(binary.buffer, binary.byteOffset+i, l));
hexJS += '\x10_fw.set(atob("'+chunk+'"), 0x'+(i).toString(16)+');\n';
}
// hexJS += `\x10(function() {
// if (E.CRC32(_fw)!=${crc}) throw "Invalid CRC!";
// var f = require("Flash");
// for (var i=${startAddress};i<${endAddress};i+=4096) f.erasePage(i);
// f.write(_fw,${startAddress});
// E.reboot();
// })();\n`;
hexJS += `\x10if (E.CRC32(_fw)!=${crc}) throw "Invalid CRC: 0x"+E.CRC32(_fw).toString(16);\n`;
hexJS += '\x10var f = require("Flash");\n';
for (var i=startAddress;i<endAddress;i+=4096)
hexJS += '\x10f.erasePage(0x'+i.toString(16)+');\n';
hexJS += `\x10f.write(_fw,${startAddress});\n`;
// hexJS += '\x10setTimeout(()=>E.showMessage("Rebooting..."),50);\n';
// hexJS += '\x10setTimeout(()=>E.reboot(), 2000);\n';
}
function fileLoaded() {
// Work out addresses
var startAddress, endAddress = 0;
parseLines(function(addr, data) {
if (addr>MAX_ADDRESS) return; // ignore data out of range
if (startAddress === undefined || addr<startAddress)
startAddress = addr;
var end = addr + data.length;
if (end > endAddress)
endAddress = end;
});
console.log(`// Data from 0x${startAddress.toString(16)} to 0x${endAddress.toString(16)} (${endAddress-startAddress} bytes)`);
// Work out data
var HEADER_LEN = 16;
var binary = new Uint8Array(HEADER_LEN + endAddress-startAddress);
binary.fill(0); // actually seems to assume a block is filled with 0 if not complete
var bin32 = new Uint32Array(binary.buffer);
parseLines(function(addr, data) {
if (addr>MAX_ADDRESS) return; // ignore data out of range
var binAddr = HEADER_LEN + addr - startAddress;
binary.set(data, binAddr);
if (DEBUG) console.log("i",addr.toString(16).padStart(8,0), data.map(x=>x.toString(16).padStart(2,0)).join(" "));
//console.log("o",new Uint8Array(binary.buffer, binAddr, data.length));
});
if (startAddress == 0xf7000) {
console.log("Bootloader - Writing to internal flash");
createJS_bootloader(new Uint8Array(binary.buffer, HEADER_LEN), startAddress, endAddress);
} else {
console.log("App - Writing to external flash");
createJS_app(binary, bin32, startAddress, endAddress);
}
}
function handleUpload() {
if (!hexJS) {
log("Hex file not loaded!");
return;
}
sendCustomizedApp({
storage:[
{name:"RAM", content:hexJS},
]
});
}
document.getElementById('fileLoader').addEventListener('change', handleFileSelect, false);
document.getElementById("upload").addEventListener("click", handleUpload);
checkForFileOnServer();
</script>
</body>
</html>

View File

@ -1,2 +1,3 @@
0.03: Fix time output on new firmwares when no GPS time set (fix #104)
0.04: Fix shown UTC time zone sign
0.04: Fix shown UTC time zone sign
0.05: Use new 'layout library for Bangle2, fix #764 by adding a back button

View File

@ -1,68 +1,75 @@
var img = require("heatshrink").decompress(atob("mEwghC/AH8A1QWVhWq0AuVAAIuVAAIwT1WinQwTFwMzmQwTCYMjlUqGCIuBlWi0UzC6JdBIoMjC4UDmAuOkYXBPAWgmczLp2ilUiVAUDC4IwLFwIUBLoJ2BFwQwM1WjCgJ1DFwQwLFwJ1B0SQCkQWDGBQXBCgK9BDgKQBAAgwJOwUzRgIDBC54wCkZdGPBwACRgguDBIIwLFxEJBQIwLFxGaBYQwKFxQwLgAWGmQuBcAQwJC48ifYYwJgUidgsyC4L7DGBIXBdohnBCgL7BcYIXIGAqMCIoL7DL5IwERgIUBLoL7BO5QXBGAK7DkWiOxQXGFwOjFoUyFxZhDgBdCCgJ1CCxYxCgBABkcqOwIuNGAQXC0S9BLpgAFXoIwBmYuPAAYwCLp4wHFyYwDFyYwDFygwCCyoA/AFQA="));
function satelliteImage() {
return require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A/AH4AGnE4F1wvsF34wgFldcLdyMYsoACF1WJF4YxPFzOtF4wxNFzAvKSiIvU1ovIGAkJAAQucF5QxCFwYwbF4QwLrwvjYIVfrwABrtdq9Wqwvkq4oCAAtXmYvi1teE4NXrphCrxoCGAbvdSIoAHNQNeFzQvGeRQvCsowrYYNfF8YwHZQQFCF8QwGF4owjeYovBroHEMERhEF8IwNrtWryYFF8YwCq4vhGBeJF5AwaxIwKwVXFwwvandfMJeJF8M6nZiLGQIvdstfGAVlGBZkCxJeZJQIwCGIRjMFzYACGIc6r/+FsIvGGIYABEzYvPGQYvusovkAH4A/AH4A/ACo="));
}
var fix;
Bangle.setLCDPower(1);
Bangle.setLCDTimeout(0);
var Layout = require("Layout");
Bangle.setGPSPower(1, "app");
Bangle.loadWidgets();
Bangle.drawWidgets();
E.showMessage("Loading..."); // avoid showing rubbish on screen
g.clear();
var fix;
Bangle.setGPSPower(1);
Bangle.on('GPS',function(f) {
fix = f;
g.reset(1);
g.setFont("6x8",2);
g.setFontAlign(0,0);
g.clearRect(90,30,239,90);
if (fix.fix) {
g.drawString("GPS",170,40);
g.drawString("Acquired",170,60);
function setGPSTime() {
if (fix.time!==undefined) {
setTime(fix.time.getTime()/1000);
E.showMessage("System time set", {img:require("heatshrink").decompress(atob("lEo4UBvvv///vEFBYNVAAWq1QFDBAgKGrQJD0oJDtQJD1IICqwGBFoIDByocDwAJBgQeDtWoJwcqDwWq0EAgfAgEKHoQcCBIQeBGAQaBBIQzBytaEwQJDlWlrQmBBIkK0tqBI+ptRNCBIcCBKhECBIh6CAgUL8AJHl/4BI8+3gJRl/8GJH/BI8Ah6MDLIZQB+BjGAAIoBBI84BIaVCAAaVBVIYJEWYLkEXobRDAAbRBcoYACcoT5DEwYJCtQoElWpBINaDwYcB0oJBGQIzCAYIwBDwQGBAAIcCDwYACDgQACBIYIEBQYFDA="))});
} else {
g.drawString("Waiting for",170,40);
g.drawString("GPS Fix",170,60);
E.showMessage("No GPS time to set");
}
g.setFont("6x8");
g.drawString(fix.satellites+" satellites",170,80);
g.clearRect(0,100,239,239);
var t = ["","","","---",""];
if (fix.time!==undefined)
Bangle.removeListener('GPS',onGPS);
setTimeout(function() {
fix = undefined;
layout.forgetLazyState(); // redraw all next time
Bangle.on('GPS',onGPS);
}, 2000);
}
var layout = new Layout( {
type:"v", c: [
{type:"h", c:[
{type:"img", src:satelliteImage },
{ type:"v", fillx:1, c: [
{type:"txt", font:"6x8:2", label:"Waiting\nfor GPS", id:"status" },
{type:"txt", font:"6x8", label:"---", id:"sat" },
]},
]},
{type:"txt", fillx:1, filly:1, font:"6x8:2", label:"---", id:"gpstime" }
]},{lazy:true, btns: [
{ label : "Set", cb : setGPSTime},
{ label : "Back", cb : ()=>load() }
]});
function onGPS(f) {
if (fix===undefined) {
g.clear();
Bangle.drawWidgets();
}
fix = f;
if (fix.fix) {
layout.status.label = "GPS\nAcquired";
} else {
layout.status.label = "Waiting\nfor GPS";
}
layout.sat.label = fix.satellites+" satellites";
var t = ["","---",""];
if (fix.time!==undefined) {
t = fix.time.toString().split(" ");
/*
[
"Sun",
"Nov",
"10",
"2019",
"15:55:35",
"GMT+0100"
]
*/
//g.setFont("6x8",2);
//g.drawString(t[0],120,110); // day
g.setFont("6x8",3);
g.drawString(t[1]+" "+t[2],120,135); // date
g.setFont("6x8",2);
g.drawString(t[3],120,160); // year
g.setFont("6x8",3);
g.drawString(t[4],120,185); // time
if (fix.time) {
// timezone
var tz = (new Date()).getTimezoneOffset()/-60;
if (tz==0) tz="UTC";
else if (tz>0) tz="UTC+"+tz;
else tz="UTC"+tz;
g.setFont("6x8",2);
g.drawString(tz,120,210); // gmt
g.setFontAlign(0,0,3);
g.drawString("Set",230,120);
g.setFontAlign(0,0);
}
});
setInterval(function() {
g.drawImage(img,48,48,{scale:1.5,rotate:Math.sin(getTime()*2)/2});
},100);
setWatch(function() {
if (fix.time!==undefined)
setTime(fix.time.getTime()/1000);
}, BTN2, {repeat:true});
t = [t[1]+" "+t[2],t[3],t[4],t[5],tz];
}
layout.gpstime.label = t.join("\n");
layout.render();
}
Bangle.on('GPS',onGPS);

View File

@ -0,0 +1,83 @@
#!/usr/bin/nodejs
/* Quick hack to add proper 'supports' field to apps.json
*/
var fs = require("fs");
var BASEDIR = __dirname+"/../";
var appsFile, apps;
try {
appsFile = fs.readFileSync(BASEDIR+"apps.json").toString();
} catch (e) {
ERROR("apps.json not found");
}
try{
apps = JSON.parse(appsFile);
} catch (e) {
console.log(e);
var m = e.toString().match(/in JSON at position (\d+)/);
if (m) {
var char = parseInt(m[1]);
console.log("===============================================");
console.log("LINE "+appsFile.substr(0,char).split("\n").length);
console.log("===============================================");
console.log(appsFile.substr(char-10, 20));
console.log("===============================================");
}
console.log(m);
ERROR("apps.json not valid JSON");
}
apps = apps.map((app,appIdx) => {
var tags = [];
if (app.tags) tags = app.tags.split(",").map(t=>t.trim());
var supportsB1 = true;
var supportsB2 = false;
if (tags.includes("b2")) {
tags = tags.filter(x=>x!="b2");
supportsB2 = true;
}
if (tags.includes("bno2")) {
tags = tags.filter(x=>x!="bno2");
supportsB2 = false;
}
if (tags.includes("bno1")) {
tags = tags.filter(x=>x!="bno1");
supportsB1 = false;
}
app.tags = tags.join(",");
app.supports = [];
if (supportsB1) app.supports.push("BANGLEJS");
if (supportsB2) app.supports.push("BANGLEJS2");
return app;
});
var KEY_ORDER = [
"id","name","shortName","version","description","icon","type","tags","supports",
"dependencies", "readme", "custom", "customConnect", "interface",
"allow_emulator", "storage", "data", "sortorder"
];
var JS = JSON.stringify;
var json = "[\n "+apps.map(app=>{
var keys = KEY_ORDER.filter(k=>k in app);
Object.keys(app).forEach(k=>{
if (!KEY_ORDER.includes(k))
throw new Error(`Key named ${k} not known!`);
});
return "{\n "+keys.map(k=>{
var js = JS(app[k]);
if (k=="storage")
js = "[\n "+app.storage.map(s=>JS(s)).join(",\n ")+"\n ]";
return JS(k)+": "+js;
}).join(",\n ")+"\n }";
}).join(",\n ")+"\n]\n";
//console.log(json);
console.log("new apps.json written");
fs.writeFileSync(BASEDIR+"apps.json", json);

View File

@ -51,7 +51,8 @@ try{
const APP_KEYS = [
'id', 'name', 'shortName', 'version', 'icon', 'description', 'tags', 'type',
'sortorder', 'readme', 'custom', 'customConnect', 'interface', 'storage', 'data', 'allow_emulator',
'sortorder', 'readme', 'custom', 'customConnect', 'interface', 'storage', 'data',
'supports', 'allow_emulator',
'dependencies'
];
const STORAGE_KEYS = ['name', 'url', 'content', 'evaluate', 'noOverwite'];
@ -81,6 +82,14 @@ apps.forEach((app,appIdx) => {
if (!app.name) ERROR(`App ${app.id} has no name`);
var isApp = !app.type || app.type=="app";
if (app.name.length>20 && !app.shortName && isApp) ERROR(`App ${app.id} has a long name, but no shortName`);
if (!Array.isArray(app.supports)) ERROR(`App ${app.id} has no 'supports' field or it's not an array`);
else {
app.supports.forEach(dev => {
if (!["BANGLEJS","BANGLEJS2"].includes(dev))
ERROR(`App ${app.id} has unknown device in 'supports' field - ${dev}`);
});
}
if (!app.version) WARN(`App ${app.id} has no version`);
else {
if (!fs.existsSync(appDir+"ChangeLog")) {

2
core

@ -1 +1 @@
Subproject commit 0fd608f085deff9b39f2db3559ecc88edb232aba
Subproject commit bc5b1284f41b0fcfdd264e1e2f12872e0b18c479

View File

@ -23,6 +23,9 @@
.filter-nav {
display: inline-block;
}
.device-nav {
display: inline-block;
}
.sort-nav {
float: right;
}

View File

@ -60,6 +60,17 @@
<div class="container apploader-tab" id="librarycontainer">
<div>
<div class="dropdown devicetype-nav">
<a href="#" class="btn btn-link dropdown-toggle" tabindex="0">
<span>All apps</span><i class="icon icon-caret"></i>
</a>
<!-- menu component -->
<ul class="menu">
<li class="menu-item"><a>All apps</a></li>
<li class="menu-item"><a dt="BANGLEJS">Bangle.js 1</a></li>
<li class="menu-item"><a dt="BANGLEJS2">Bangle.js 2</a></li>
</ul>
</div>
<div class="filter-nav">
<label class="chip active" filterid="">Default</label>
<label class="chip" filterid="clock">Clocks</label>

111
loader.js
View File

@ -14,6 +14,10 @@ if (window.location.host=="banglejs.com") {
var RECOMMENDED_VERSION = "2v10";
// could check http://www.espruino.com/json/BANGLEJS.json for this
// We're only interested in Bangles
DEVICEINFO = DEVICEINFO.filter(x=>x.id.startsWith("BANGLEJS"));
// Set up source code URL
(function() {
let username = "espruino";
let githubMatch = window.location.href.match(/\/(\w+)\.github\.io/);
@ -21,6 +25,7 @@ var RECOMMENDED_VERSION = "2v10";
Const.APP_SOURCECODE_URL = `https://github.com/${username}/BangleApps/tree/master/apps`;
})();
// When a device is found, filter the apps accordingly
function onFoundDeviceInfo(deviceId, deviceVersion) {
if (deviceId != "BANGLEJS" && deviceId != "BANGLEJS2") {
showToast(`You're using ${deviceId}, not a Bangle.js. Did you want <a href="https://espruino.com/apps">espruino.com/apps</a> instead?` ,"warning", 20000);
@ -33,4 +38,110 @@ function onFoundDeviceInfo(deviceId, deviceVersion) {
if (deviceId == "BANGLEJS2") {
Const.MESSAGE_RELOAD = 'Hold button\nto reload';
}
// check against features shown?
filterAppsForDevice(deviceId);
/* if we'd saved a device ID but this device is different, ensure
we ask again next time */
var savedDeviceId = getSavedDeviceId();
if (savedDeviceId!==undefined && savedDeviceId!=deviceId)
setSavedDeviceId(undefined);
}
var originalAppJSON = undefined;
function filterAppsForDevice(deviceId) {
if (originalAppJSON===undefined)
originalAppJSON = appJSON;
var device = DEVICEINFO.find(d=>d.id==deviceId);
// set the device dropdown
document.querySelector(".devicetype-nav span").innerText = device ? device.name : "All apps";
if (!device) {
if (deviceId!==undefined)
showToast(`Device ID ${deviceId} not recognised. Some apps may not work`, "warning");
appJSON = originalAppJSON;
} else {
// Now filter apps
appJSON = originalAppJSON.filter(app => {
var supported = ["BANGLEJS"];
if (!app.supports) {
console.log(`App ${app.id} doesn't include a 'supports' field - ignoring`);
return false;
}
if (app.supports.includes(deviceId)) return true;
//console.log(`Dropping ${app.id} because ${deviceId} is not in supported list ${app.supports.join(",")}`);
return false;
});
}
refreshLibrary();
}
// If 'remember' was checked in the window below, this is the device
function getSavedDeviceId() {
let deviceId = localStorage.getItem("deviceId");
if (("string"==typeof deviceId) && DEVICEINFO.find(d=>d.id == deviceId))
return deviceId;
return undefined;
}
function setSavedDeviceId(deviceId) {
localStorage.setItem("deviceId", deviceId);
}
// At boot, show a window to choose which type of device you have...
window.addEventListener('load', (event) => {
let deviceId = getSavedDeviceId()
if (deviceId !== undefined)
return filterAppsForDevice(deviceId);
var html = `<div class="columns">
${DEVICEINFO.map(d=>`
<div class="column col-6 col-xs-6">
<div class="card devicechooser" deviceid="${d.id}" style="cursor:pointer">
<div class="card-header">
<div class="card-title h5">${d.name}</div>
<!--<div class="card-subtitle text-gray">...</div>-->
</div>
<div class="card-image">
<img src="${d.img}" alt="${d.name}" width="256" height="256" class="img-responsive">
</div>
</div>
</div>`).join("\n")}
</div><div class="columns">
<div class="column col-12">
<div class="form-group">
<label class="form-switch">
<input type="checkbox" id="remember_device">
<i class="form-icon"></i> Don't ask again
</label>
</div>
</div>
</div>`;
showPrompt("Which Bangle.js?",html,{},false);
htmlToArray(document.querySelectorAll(".devicechooser")).forEach(button => {
button.addEventListener("click",event => {
let rememberDevice = document.getElementById("remember_device").checked;
let button = event.currentTarget;
let deviceId = button.getAttribute("deviceid");
hidePrompt();
console.log("Chosen device", deviceId);
setSavedDeviceId(rememberDevice ? deviceId : undefined);
filterAppsForDevice(deviceId);
});
});
});
// Hook onto device chooser dropdown
window.addEventListener('load', (event) => {
htmlToArray(document.querySelectorAll(".devicetype-nav .menu-item")).forEach(button => {
button.addEventListener("click", event => {
var a = event.target;
var deviceId = a.getAttribute("dt")||undefined;
filterAppsForDevice(deviceId); // also sets the device dropdown
setSavedDeviceId(undefined); // ask at startup next time
document.querySelector(".devicetype-nav span").innerText = a.innerText;
});
});
});

View File

@ -169,14 +169,23 @@ function Layout(layout, options) {
Bangle.on('touch',Bangle.touchHandler);
}
// add IDs
// recurse over layout doing some fixing up if needed
var ll = this;
function idRecurser(l) {
function recurser(l) {
// add IDs
if (l.id) ll[l.id] = l;
// fix type up
if (!l.type) l.type="";
if (l.c) l.c.forEach(idRecurser);
// FIXME ':'/fsz not needed in new firmwares - Font:12 is handled internally
// fix fonts for pre-2v11 firmware
if (l.font && l.font.includes(":")) {
var f = l.font.split(":");
l.font = f[0];
l.fsz = f[1];
}
if (l.c) l.c.forEach(recurser);
}
idRecurser(layout);
recurser(this._l);
this.updateNeeded = true;
}
@ -352,12 +361,6 @@ Layout.prototype.update = function() {
"txt" : function(l) {
if (l.font.endsWith("%"))
l.font = "Vector"+Math.round(g.getHeight()*l.font.slice(0,-1)/100);
// FIXME ':'/fsz not needed in new firmwares - it's handled internally
if (l.font.includes(":")) {
var f = l.font.split(":");
l.font = f[0];
l.fsz = f[1];
}
if (l.wrap) {
l._h = l._w = 0;
} else {