merge with upstream
commit
30555f9715
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1 @@
|
|||
0.01: Initial version
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
|
|
@ -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>
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
@ -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
2
core
|
|
@ -1 +1 @@
|
|||
Subproject commit 0fd608f085deff9b39f2db3559ecc88edb232aba
|
||||
Subproject commit bc5b1284f41b0fcfdd264e1e2f12872e0b18c479
|
||||
|
|
@ -23,6 +23,9 @@
|
|||
.filter-nav {
|
||||
display: inline-block;
|
||||
}
|
||||
.device-nav {
|
||||
display: inline-block;
|
||||
}
|
||||
.sort-nav {
|
||||
float: right;
|
||||
}
|
||||
|
|
|
|||
11
index.html
11
index.html
|
|
@ -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
111
loader.js
|
|
@ -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;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in New Issue