forked from FOSS/BangleApps
Adding Bangle.js 2 firmware update app
parent
b6d333ae74
commit
cf51c17dc5
11
apps.json
11
apps.json
|
@ -1,4 +1,15 @@
|
|||
[
|
||||
{ "id": "fwupdate",
|
||||
"name": "Firmware Update",
|
||||
"icon": "app.png",
|
||||
"version":"0.01",
|
||||
"description": "Uploads new Espruino firmwares to Bangle.js 2",
|
||||
"custom": "custom.html", "customConnect":true,
|
||||
"tags": "tools,system,b2",
|
||||
"type": "RAM",
|
||||
"storage": [ ],
|
||||
"sortorder" : -20
|
||||
},
|
||||
{ "id": "boot",
|
||||
"name": "Bootloader",
|
||||
"tags": "tool,system,b2",
|
||||
|
|
|
@ -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>
|
Loading…
Reference in New Issue