2021-10-20 07:50:51 +00:00
< html >
< head >
< link rel = "stylesheet" href = "../../css/spectre.min.css" >
< / head >
< body >
2022-01-27 16:35:30 +00:00
< p > This tool allows you to update the bootloader on < a href = "https://www.espruino.com/Bangle.js2" > Bangle.js 2< / a > devices
from within the App Loader.< / p >
2021-10-20 07:50:51 +00:00
< div id = "fw-unknown" >
2021-12-09 14:56:36 +00:00
< p > < b > Firmware updates using the App Loader are only possible on
2021-10-20 07:50:51 +00:00
Bangle.js 2. For firmware updates on Bangle.js 1 please
2021-12-09 14:56:36 +00:00
< a href = "https://www.espruino.com/Bangle.js#firmware-updates" target = "_blank" > see the Bangle.js 1 instructions< / a > < / b > < / p >
2021-10-20 07:50:51 +00:00
< / div >
2022-01-27 16:35:30 +00:00
< ul >
< p > Your current firmware version is < span id = "fw-version" style = "font-weight:bold" > unknown< / span > and bootloader is < span id = "boot-version" style = "font-weight:bold" > unknown< / span > < / p >
< / ul >
2021-10-20 07:50:51 +00:00
< div id = "fw-ok" style = "display:none" >
2022-01-27 16:35:30 +00:00
< p > If you have an early (KickStarter or developer) Bangle.js device and still have the old 2v10.x bootloader, the Firmware Update
2022-02-01 17:26:24 +00:00
will fail with a message about the bootloader version. If so, please < a href = "bootloader_espruino_2v12_banglejs2.hex" class = "fw-link" > click here to update to bootloader 2v12< / a > and then click the 'Upload' button that appears.< / p >
2021-12-09 14:56:36 +00:00
< div id = "latest-firmware" style = "display:none" >
< p > The currently available Espruino firmware releases are:< / p >
< ul id = "latest-firmware-list" >
< / ul >
2022-01-27 16:35:30 +00:00
< p > To update, click a link above and then click the 'Upload' button that appears.< / p >
< / div >
< a href = "#" id = "advanced-btn" > Advanced ▼< / a >
< div id = "advanced-div" style = "display:none" >
< p > Firmware updates via this tool work differently to the NRF Connect method mentioned on
< a href = "https://www.espruino.com/Bangle.js2#firmware-updates" > the Bangle.js 2 page< / a > . Firmware
is uploaded to a file on the Bangle. Once complete the Bangle reboots and the bootloader copies
the new firmware into internal Storage.< / p >
< p > In addition to the links above, you can upload a hex or zip file directly below. This file should be an < code > .app_hex< / code >
file, *not* the normal < code > .hex< / code > (as that contains the bootloader as well).< / p >
< p > < b > DANGER!< / b > No verification is performed on uploaded ZIP or HEX files - you could
potentially overwrite your bootloader with the wrong binary and brick your Bangle.< / p >
< input class = "form-input" type = "file" id = "fileLoader" accept = ".hex,.app_hex,.zip" / > < br >
2021-12-09 14:56:36 +00:00
< / div >
< p > < button id = "upload" class = "btn btn-primary" style = "display:none" > Upload< / button > < / p >
2021-10-20 07:50:51 +00:00
< / div >
2022-01-27 16:35:30 +00:00
2021-12-09 14:56:36 +00:00
2021-10-20 07:50:51 +00:00
< pre id = "log" > < / pre >
< script src = "../../core/lib/customize.js" > < / script >
2021-12-09 14:56:36 +00:00
< script src = "../../core/lib/espruinotools.js" > < / script >
< script src = "https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.js" > < / script >
2021-10-20 07:50:51 +00:00
< script >
var hexJS; // JS to upload hex
var HEADER_LEN = 16; // size of app flash header
2021-12-09 14:56:36 +00:00
var APP_START = 0x26000;
var APP_MAX_LENGTH = 0xda000; // from linker file - the max size the app can be, for sanity check!
2021-10-20 07:50:51 +00:00
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;
2022-01-27 16:35:30 +00:00
function clearLog() {
document.getElementById('log').innerText = "";
console.log("Log Cleared");
}
2021-10-20 07:50:51 +00:00
function log(t) {
document.getElementById('log').innerText += t+"\n";
console.log(t);
}
function onInit(device) {
2022-01-27 16:35:30 +00:00
console.log("fwupdate init", device);
2021-12-09 14:56:36 +00:00
if (device & & device.version)
document.getElementById("fw-version").innerText = device.version;
2021-10-20 07:50:51 +00:00
if (device & & device.id=="BANGLEJS2") {
document.getElementById("fw-unknown").style = "display:none";
document.getElementById("fw-ok").style = "";
}
2022-01-27 16:35:30 +00:00
Puck.eval("E.CRC32(E.memoryArea(0xF7000,0x7000))", crc => {
console.log("Bootloader CRC = "+crc);
2022-01-28 13:59:32 +00:00
var version = `unknown (CRC ${crc})`;
2022-02-01 20:14:33 +00:00
var ok = true;
if (crc==1339551013) { version = "2v10.219"; ok = false; }
if (crc==1207580954) { version = "2v10.236"; ok = false; }
2022-01-27 16:35:30 +00:00
if (crc==3435933210) version = "2v11.52";
2022-01-28 13:59:32 +00:00
if (crc==46757280) version = "2v11.58";
2022-02-01 20:14:33 +00:00
if (crc==3508163280 || crc==1418074094) version = "2v12";
2022-06-06 13:54:33 +00:00
if (crc==4056371285) version = "2v13";
2022-06-09 16:06:52 +00:00
if (crc==1038322422) version = "2v14";
2022-08-19 07:42:03 +00:00
if (crc==2560806221) version = "2v15";
2022-02-01 20:14:33 +00:00
if (!ok) {
version += `(⚠ update required)`;
}
document.getElementById("boot-version").innerHTML = version;
2022-01-27 16:35:30 +00:00
});
2021-10-20 07:50:51 +00:00
}
function checkForFileOnServer() {
2021-12-09 14:56:36 +00:00
function getURL(url, callback) {
2022-01-27 16:35:30 +00:00
var xhr = new XMLHttpRequest();
2021-10-20 07:50:51 +00:00
xhr.onload = callback;
2022-01-27 16:35:30 +00:00
xhr.open("GET", url);
2021-10-20 07:50:51 +00:00
xhr.responseType = "document";
xhr.send();
}
function getFilesFromURL(url, regex, callback) {
2022-01-27 16:35:30 +00:00
getURL(url, function() {
//console.log(this.responseXML)
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);
});
2021-10-20 07:50:51 +00:00
}
2021-12-10 20:09:14 +00:00
var regex = new RegExp("_banglejs2.*zip$");
2021-12-09 14:56:36 +00:00
2022-01-27 16:35:30 +00:00
var domFirmwareList = document.getElementById("latest-firmware-list");
2021-12-09 14:56:36 +00:00
var domFirmware = document.getElementById("latest-firmware");
console.log("Checking server...");
2021-10-20 07:50:51 +00:00
2022-01-27 16:35:30 +00:00
getFilesFromURL("https://www.espruino.com/binaries/", regex, function(releaseFiles) {
2021-12-09 14:56:36 +00:00
releaseFiles.sort().reverse().forEach(function(f) {
2022-01-27 16:35:30 +00:00
var name = f.substr(f.substr(0,f.length-1).lastIndexOf('/')+1);
2021-12-09 14:56:36 +00:00
console.log("Found "+name);
2022-01-27 16:35:30 +00:00
domFirmwareList.innerHTML += '< li > Release: < a href = "'+f+'" class = "fw-link" > '+name+'< / a > < / li > ';
2021-12-09 14:56:36 +00:00
domFirmware.style = "";
2022-01-27 16:35:30 +00:00
});
getFilesFromURL("https://www.espruino.com/binaries/travis/master/",regex, function(travisFiles) {
travisFiles.forEach(function(f) {
var name = f.substr(f.lastIndexOf('/')+1);
2021-12-09 14:56:36 +00:00
console.log("Found "+name);
2022-01-27 16:35:30 +00:00
domFirmwareList.innerHTML += '< li > Cutting Edge build: < a href = "'+f+'" class = "fw-link" > '+name+'< / a > < / li > ';
2021-12-09 14:56:36 +00:00
domFirmware.style = "";
2022-01-27 16:35:30 +00:00
});
2021-12-09 14:56:36 +00:00
console.log("Finished check for firmware files...");
var fwlinks = document.querySelectorAll(".fw-link");
2021-12-10 20:09:14 +00:00
for (var i=0;i< fwlinks.length ; i + + )
2021-12-09 14:56:36 +00:00
fwlinks[i].addEventListener("click", e => {
e.preventDefault();
2022-01-27 16:35:30 +00:00
downloadURL(e.target.href).then(info=>{
2021-12-09 14:56:36 +00:00
document.getElementById("upload").style = ""; // show upload
});
});
2022-01-27 16:35:30 +00:00
});
});
}
function downloadURL(url) {
clearLog();
log("Downloading "+url);
if (url.endsWith(".zip")) {
return downloadZipFile(url);
} else if (url.endsWith(".hex")) {
return downloadHexFile(url);
} else {
log("Unknown URL "+url+" - expecting .hex or .zip extension");
return Promise.reject();
}
}
function downloadHexFile(url) {
return new Promise(resolve => {
var xhr = new XMLHttpRequest();
xhr.onload = function() {
hexFileLoaded(this.responseText.toString());
resolve();
};
xhr.open("GET", url);
xhr.responseType = "text";
xhr.send();
});
2021-10-20 07:50:51 +00:00
}
2021-12-09 14:56:36 +00:00
function downloadZipFile(url) {
return new Promise((resolve,reject) => {
Espruino.Core.Utils.getBinaryURL(url, (err, binary) => {
if (err) return reject("Unable to download "+url);
resolve(binary);
});
}).then(convertZipFile);
}
function convertZipFile(binary) {
var info = {};
Promise.resolve(binary).then(binary => {
info.binary = binary;
return JSZip.loadAsync(binary)
}).then(function(zipFile) {
info.zipFile = zipFile;
return info.zipFile.file("manifest.json").async("string");
}).then(function(content) {
info.manifest = JSON.parse(content).manifest;
}).then(function(content) {
console.log(info.manifest);
return info.zipFile.file(info.manifest.application.dat_file).async("arraybuffer");
}).then(function(content) {
info.dat_file = content;
}).then(function(content) {
console.log(info.manifest);
return info.zipFile.file(info.manifest.application.bin_file).async("arraybuffer");
}).then(function(content) {
info.bin_file = content;
if (info.bin_file.byteLength > APP_MAX_LENGTH) throw new Error("Firmware file is too big!");
info.storageContents = new Uint8Array(info.bin_file.byteLength + HEADER_LEN)
info.storageContents.set(new Uint8Array(info.bin_file), HEADER_LEN);
2022-01-27 16:35:30 +00:00
console.log("ZIP downloaded and decoded",info);
2021-12-09 14:56:36 +00:00
createJS_app(info.storageContents, APP_START, APP_START+info.bin_file.byteLength);
document.getElementById("upload").style = ""; // show upload
return info;
}).catch(err => log("ERROR:" + err));
2021-10-20 07:50:51 +00:00
}
function handleFileSelect(event) {
2022-01-27 16:35:30 +00:00
clearLog();
2021-10-20 07:50:51 +00:00
if (event.target.files.length!=1) {
log("More than one file selected!");
return;
}
2021-12-09 14:56:36 +00:00
var file = event.target.files[0];
2021-10-20 07:50:51 +00:00
var reader = new FileReader();
2021-12-09 14:56:36 +00:00
if (file.name.endsWith(".hex") || file.name.endsWith(".app_hex")) {
reader.onload = function(event) {
2022-01-27 16:35:30 +00:00
log("HEX uploaded");
2021-12-09 14:56:36 +00:00
document.getElementById("upload").style = ""; // show upload
2022-01-27 16:35:30 +00:00
hexFileLoaded(event.target.result);
2021-12-09 14:56:36 +00:00
};
reader.readAsText(event.target.files[0]);
} else if (file.name.endsWith(".zip")) {
reader.onload = function(event) {
2022-01-27 16:35:30 +00:00
log("ZIP uploaded");
2021-12-09 14:56:36 +00:00
convertZipFile(event.target.result);
};
reader.readAsArrayBuffer(event.target.files[0]);
} else {
log("Unknown file extension for "+file.name);
}
2021-10-20 07:50:51 +00:00
};
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;
}
2021-12-09 14:56:36 +00:00
/* To upload the app, we write to external flash,
binary = Uint8Array of data to flash. Should include HEADER_LEN header, then bytes to flash */
function createJS_app(binary, startAddress, endAddress) {
2021-10-20 07:50:51 +00:00
/* typedef struct {
uint32_t address;
uint32_t size;
uint32_t CRC;
uint32_t version;
} FlashHeader; */
2021-12-09 14:56:36 +00:00
var bin32 = new Uint32Array(binary.buffer);
2021-10-20 07:50:51 +00:00
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`;
2022-01-06 12:13:21 +00:00
hexJS += `\x10if (E.CRC32(E.memoryArea(0xF7000,0x7000))==1339551013) { print("BOOTLOADER 2v10.219 needs update"); load();}\n`;
hexJS += `\x10if (E.CRC32(E.memoryArea(0xF7000,0x7000))==1207580954) { print("BOOTLOADER 2v10.236 needs update"); load();}\n`;
2021-10-20 07:50:51 +00:00
hexJS += '\x10var s = require("Storage");\n';
2021-12-09 14:56:36 +00:00
hexJS += '\x10s.erase(".firmware");\n';
var CHUNKSIZE = 2048;
2021-10-20 07:50:51 +00:00
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';
2022-01-27 16:35:30 +00:00
log("Firmware update ready for upload");
2021-10-20 07:50:51 +00:00
}
// 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';
}
2022-01-06 12:13:21 +00:00
hexJS += `\x10(function() { if (E.CRC32(_fw)!=${crc}) throw "Invalid CRC: 0x"+E.CRC32(_fw).toString(16);\n`;
hexJS += 'E.showMessage("Flashing Bootloader...")\n';
hexJS += 'E.setFlags({unsafeFlash:1})\n';
hexJS += 'var f = require("Flash");\n';
2021-10-20 07:50:51 +00:00
for (var i=startAddress;i< endAddress ; i + = 4096 )
2022-01-06 12:13:21 +00:00
hexJS += 'f.erasePage(0x'+i.toString(16)+');\n';
hexJS += `f.write(_fw,${startAddress});\n`;
hexJS += `})()\n`;
2022-01-27 16:35:30 +00:00
log("Bootloader ready for upload");
2021-10-20 07:50:51 +00:00
}
2022-01-27 16:35:30 +00:00
function hexFileLoaded(hexString) {
var hex = hexString.split("\n"); // array of lines of the hex file
function hexParseLines(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);
}
});
}
2021-10-20 07:50:51 +00:00
// Work out addresses
var startAddress, endAddress = 0;
2022-01-27 16:35:30 +00:00
hexParseLines(function(addr, data) {
2021-10-20 07:50:51 +00:00
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 binary = new Uint8Array(HEADER_LEN + endAddress-startAddress);
binary.fill(0); // actually seems to assume a block is filled with 0 if not complete
2022-01-27 16:35:30 +00:00
hexParseLines(function(addr, data) {
2021-10-20 07:50:51 +00:00
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");
2021-12-09 14:56:36 +00:00
createJS_app(binary, startAddress, endAddress);
2021-10-20 07:50:51 +00:00
}
}
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);
2022-01-27 16:35:30 +00:00
document.getElementById("advanced-btn").addEventListener("click", function() {
document.getElementById("advanced-btn").style = "display:none";
document.getElementById("advanced-div").style = "";
});
2021-12-09 14:56:36 +00:00
setTimeout(checkForFileOnServer, 10);
2021-10-20 07:50:51 +00:00
< / script >
< / body >
< / html >