bootloader and clock_info update - checking hash of all js vs just boot.js is the same cost, so we make caches of widgets and clockinfos in bootupdate - this drastically improves boot time

pull/3624/head
Gordon Williams 2024-10-24 19:56:17 +01:00
parent 8a4ebbca7c
commit 1ec8fba5ba
6 changed files with 115 additions and 62 deletions

View File

@ -72,3 +72,6 @@
0.61: Instead of breaking execution with an Exception when updating boot, just use if..else (fix 'Uncaught undefined') 0.61: Instead of breaking execution with an Exception when updating boot, just use if..else (fix 'Uncaught undefined')
0.62: Handle setting for configuring BLE privacy 0.62: Handle setting for configuring BLE privacy
0.63: Only set BLE `display:1` if we have a passkey 0.63: Only set BLE `display:1` if we have a passkey
0.64: Automatically create .widcache and .clkinfocache to speed up loads
Bangle.loadWidgets overwritten with fast version on success
Refuse to work on firmware <2v16 and remove old polyfills

View File

@ -12,14 +12,13 @@ if (DEBUG) {
boot += "var _tm=Date.now()\n"; boot += "var _tm=Date.now()\n";
bootPost += "delete _tm;"; bootPost += "delete _tm;";
} }
if (require('Storage').hash) { // new in 2v11 - helps ensure files haven't changed if (FWVERSION < 216) {
let CRC = E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\.boot\.js/)+E.CRC32(process.env.GIT_COMMIT); E.showMessage(/*LANG*/"Please update Bangle.js firmware\n\nCurrent = "+process.env.VERSION,{title:"ERROR"});
boot += `if(E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\\.boot\\.js/)+E.CRC32(process.env.GIT_COMMIT)!=${CRC})`; throw new Error("Old firmware");
} else {
let CRC = E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\.boot\.js/))+E.CRC32(process.env.GIT_COMMIT);
boot += `if(E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\\.boot\\.js/))+E.CRC32(process.env.GIT_COMMIT)!=${CRC})`;
} }
boot += `{eval(require('Storage').read('bootupdate.js'));print("Storage Updated!")}else{\n`; let CRC = E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\.js$/)+E.CRC32(process.env.GIT_COMMIT);
boot += `if(E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\\.js$/)+E.CRC32(process.env.GIT_COMMIT)!=${CRC})`;
boot += `{eval(require('Storage').read('bootupdate.js'));}else{\n`;
boot += `E.setFlags({pretokenise:1});\n`; boot += `E.setFlags({pretokenise:1});\n`;
boot += `var bleServices = {}, bleServiceOptions = { uart : true};\n`; boot += `var bleServices = {}, bleServiceOptions = { uart : true};\n`;
bootPost += `NRF.setServices(bleServices,bleServiceOptions);delete bleServices,bleServiceOptions;\n`; // executed after other boot code bootPost += `NRF.setServices(bleServices,bleServiceOptions);delete bleServices,bleServiceOptions;\n`; // executed after other boot code
@ -44,7 +43,7 @@ LoopbackA.setConsole(true);\n`;
boot += ` boot += `
Bluetooth.line=""; Bluetooth.line="";
Bluetooth.on('data',function(d) { Bluetooth.on('data',function(d) {
var l = (Bluetooth.line + d).split(/[\\n\\r]/); let l = (Bluetooth.line + d).split(/[\\n\\r]/);
Bluetooth.line = l.pop(); Bluetooth.line = l.pop();
l.forEach(n=>Bluetooth.emit("line",n)); l.forEach(n=>Bluetooth.emit("line",n));
}); });
@ -86,11 +85,8 @@ if (s.bleprivacy || (s.passkey!==undefined && s.passkey.length==6)) {
if (s.blename === false) boot+=`NRF.setAdvertising({},{showName:false});\n`; if (s.blename === false) boot+=`NRF.setAdvertising({},{showName:false});\n`;
if (s.whitelist && !s.whitelist_disabled) boot+=`NRF.on('connect', function(addr) { if (!NRF.ignoreWhitelist) { let whitelist = (require('Storage').readJSON('setting.json',1)||{}).whitelist; if (NRF.resolveAddress !== undefined) { let resolvedAddr = NRF.resolveAddress(addr); if (resolvedAddr !== undefined) addr = resolvedAddr + " (resolved)"; } if (!whitelist.includes(addr)) NRF.disconnect(); }});\n`; if (s.whitelist && !s.whitelist_disabled) boot+=`NRF.on('connect', function(addr) { if (!NRF.ignoreWhitelist) { let whitelist = (require('Storage').readJSON('setting.json',1)||{}).whitelist; if (NRF.resolveAddress !== undefined) { let resolvedAddr = NRF.resolveAddress(addr); if (resolvedAddr !== undefined) addr = resolvedAddr + " (resolved)"; } if (!whitelist.includes(addr)) NRF.disconnect(); }});\n`;
if (s.rotate) boot+=`g.setRotation(${s.rotate&3},${s.rotate>>2});\n` // screen rotation if (s.rotate) boot+=`g.setRotation(${s.rotate&3},${s.rotate>>2});\n` // screen rotation
boot+=`Bangle.loadWidgets=function(){if(!global.WIDGETS)eval(require("Storage").read(".widcache"))};\n`;
// ================================================== FIXING OLDER FIRMWARES // ================================================== FIXING OLDER FIRMWARES
if (FWVERSION<215.068) // 2v15.68 and before had compass heading inverted.
boot += `Bangle.on('mag',e=>{if(!isNaN(e.heading))e.heading=360-e.heading;});
Bangle.getCompass=(c=>(()=>{e=c();if(!isNaN(e.heading))e.heading=360-e.heading;return e;}))(Bangle.getCompass);`;
// deleting stops us getting confused by our own decl. builtins can't be deleted // deleting stops us getting confused by our own decl. builtins can't be deleted
// this is a polyfill without fastloading capability // this is a polyfill without fastloading capability
delete Bangle.showClock; delete Bangle.showClock;
@ -98,18 +94,10 @@ if (!Bangle.showClock) boot += `Bangle.showClock = ()=>{load(".bootcde")};\n`;
delete Bangle.load; delete Bangle.load;
if (!Bangle.load) boot += `Bangle.load = load;\n`; if (!Bangle.load) boot += `Bangle.load = load;\n`;
let date = new Date(); let date = new Date();
delete date.toLocalISOString; // toLocalISOString was only introduced in 2v15
if (!date.toLocalISOString) boot += `Date.prototype.toLocalISOString = function() {
var o = this.getTimezoneOffset();
var d = new Date(this.getTime() - o*60000);
var sign = o>0?"-":"+";
o = Math.abs(o);
return d.toISOString().slice(0,-1)+sign+Math.floor(o/60).toString().padStart(2,0)+(o%60).toString().padStart(2,0);
};\n`;
// show timings // show timings
if (DEBUG) boot += `print(".boot0",0|(Date.now()-_tm),"ms");_tm=Date.now();\n` if (DEBUG) boot += `print(".boot0",0|(Date.now()-_tm),"ms");_tm=Date.now();\n`
// ================================================== BOOT.JS // ================================================== .BOOT0
// Append *.boot.js files. // Append *.boot.js files.
// Name files with a number - eg 'foo.5.boot.js' to enforce order (lowest first). Numbered files get placed before non-numbered // Name files with a number - eg 'foo.5.boot.js' to enforce order (lowest first). Numbered files get placed before non-numbered
// These could change bleServices/bleServiceOptions if needed // These could change bleServices/bleServiceOptions if needed
@ -128,51 +116,105 @@ let bootFiles = require('Storage').list(/\.boot\.js$/).sort((a,b)=>{
}); });
// precalculate file size // precalculate file size
bootPost += "}"; bootPost += "}";
let fileSize = boot.length + bootPost.length; let fileOffset,fileSize;
bootFiles.forEach(bootFile=>{ /* code to output a file, plus preable and postable
// match the size of data we're adding below in bootFiles.forEach when called with dst==undefined it just increments
if (DEBUG) fileSize += 2+bootFile.length+1; // `//${bootFile}\n` comment fileOffset so we can see ho wbig the file has to be */
fileSize += require('Storage').read(bootFile).length+2; // boot code plus ";\n" let outputFile = (dst,src,pre,post) => {"ram";
if (DEBUG) fileSize += 48+E.toJS(bootFile).length; // `print(${E.toJS(bootFile)},0|(Date.now()-_tm),"ms");_tm=Date.now();\n` if (DEBUG) {
}); if (dst) require('Storage').write(dst,`//${src}\n`,fileOffset);
// write file in chunks (so as not to use up all RAM) fileOffset+=2+src.length+1;
require('Storage').write('.boot0',boot,0,fileSize); }
let fileOffset = boot.length; if (pre) {
bootFiles.forEach(bootFile=>{ if (dst) require('Storage').write(dst,pre,fileOffset);
fileOffset+=pre.length;
}
let f = require('Storage').read(src);
if (src.endsWith("clkinfo.js") && f[0]!="(") {
/* we shouldn't have to do this but it seems sometimes (sched 0.28) folks have
used libraries which get added into the clockinfo, and we can't use them directly
to we have to rever back to eval. */
f = `eval(require('Storage').read(${E.toJS(src)}))`;
}
if (dst) {
// we can't just write 'f' in one go because it can be too big
let len = f.length;
let offset = 0;
while (len) {
let chunk = Math.min(len, 2048);
require('Storage').write(dst,f.substr(offset, chunk),fileOffset);
fileOffset+=chunk;
offset+=chunk;
len-=chunk;
}
} else
fileOffset+=f.length;
if (dst) require('Storage').write(dst,post,fileOffset);
fileOffset+=post.length;
if (DEBUG) {
if (dst) require('Storage').write(dst,`print(${E.toJS(src)},0|(Date.now()-_tm),"ms");_tm=Date.now();\n`,fileOffset);
fileOffset += 48+E.toJS(src).length;
}
};
let outputFileComplete = (dst,fn) => {
// we add a semicolon so if the file is wrapped in (function(){ ... }() // we add a semicolon so if the file is wrapped in (function(){ ... }()
// with no semicolon we don't end up with (function(){ ... }()(function(){ ... }() // with no semicolon we don't end up with (function(){ ... }()(function(){ ... }()
// which would cause an error! // which would cause an error!
// we write: // we write:
// "//"+bootFile+"\n"+require('Storage').read(bootFile)+";\n"; // "//"+bootFile+"\n"+require('Storage').read(bootFile)+";\n";
// but we need to do this without ever loading everything into RAM as some // but we need to do this without ever loading everything into RAM as some
// boot files seem to be getting pretty big now. // boot files seem to be getting pretty big now.
if (DEBUG) { outputFile(dst,fn,"",";\n");
require('Storage').write('.boot0',`//${bootFile}\n`,fileOffset); };
fileOffset+=2+bootFile.length+1; fileOffset = boot.length + bootPost.length;
} bootFiles.forEach(fn=>outputFileComplete(undefined,fn)); // just get sizes
let bf = require('Storage').read(bootFile); fileSize = fileOffset;
// we can't just write 'bf' in one go because at least in 2v13 and earlier require('Storage').write('.boot0',boot,0,fileSize);
// Espruino wants to read the whole file into RAM first, and on Bangle.js 1 fileOffset = boot.length;
// it can be too big (especially BTHRM). bootFiles.forEach(fn=>outputFileComplete('.boot0',fn));
let bflen = bf.length;
let bfoffset = 0;
while (bflen) {
let bfchunk = Math.min(bflen, 2048);
require('Storage').write('.boot0',bf.substr(bfoffset, bfchunk),fileOffset);
fileOffset+=bfchunk;
bfoffset+=bfchunk;
bflen-=bfchunk;
}
require('Storage').write('.boot0',";\n",fileOffset);
fileOffset+=2;
if (DEBUG) {
require('Storage').write('.boot0',`print(${E.toJS(bootFile)},0|(Date.now()-_tm),"ms");_tm=Date.now();\n`,fileOffset);
fileOffset += 48+E.toJS(bootFile).length
}
});
require('Storage').write('.boot0',bootPost,fileOffset); require('Storage').write('.boot0',bootPost,fileOffset);
delete boot,bootPost,bootFiles;
// ================================================== .WIDCACHE for widgets
let widgetFiles = require("Storage").list(/\.wid\.js$/);
let widget = `// Made by bootupdate.js\nglobal.WIDGETS={};`, widgetPost = `var W=WIDGETS;WIDGETS={};
Object.keys(W).sort((a,b)=>(0|W[b].sortorder)-(0|W[a].sortorder)).forEach(k=>WIDGETS[k]=W[k]);`; // sort
if (DEBUG) widget+="var _tm=Date.now();";
outputFileComplete = (dst,fn) => {
outputFile(dst,fn,"try{",`}catch(e){print(${E.toJS(fn)},e,e.stack)}\n`);
};
fileOffset = widget.length + widgetPost.length;
widgetFiles.forEach(fn=>outputFileComplete(undefined,fn)); // just get sizes
fileSize = fileOffset;
require('Storage').write('.widcache',widget,0,fileSize);
fileOffset = widget.length;
widgetFiles.forEach(fn=>outputFileComplete('.widcache',fn));
require('Storage').write('.widcache',widgetPost,fileOffset);
delete widget,widgetPost,widgetFiles;
// ================================================== .clkinfocache for clockinfos
let ciFiles = require("Storage").list(/\.clkinfo\.js$/);
let ci = `// Made by bootupdate.js\n`;
if (DEBUG) ci+="var _tm=Date.now();";
outputFileComplete = (dst,fn) => {
outputFile(dst,fn,"try{let a=",`(),b=menu.find(x=>x.name===a.name);if(b)b.items=b.items.concat(a.items)else menu=menu.concat(a);}catch(e){print(${E.toJS(fn)},e,e.stack)}\n`);
};
fileOffset = ci.length;
ciFiles.forEach(fn=>outputFileComplete(undefined,fn)); // just get sizes
fileSize = fileOffset;
require('Storage').write('.clkinfocache',ci,0,fileSize);
fileOffset = ci.length;
ciFiles.forEach(fn=>outputFileComplete('.clkinfocache',fn));
delete ci,ciFiles;
// test with require("clock_info").load()
// ================================================== END
E.showMessage(/*LANG*/"Reloading..."); E.showMessage(/*LANG*/"Reloading...");
} }
// .bootcde should be run automatically after if required, since // .bootcde should be run automatically after if required, since
// we normally get called automatically from '.boot0' // we normally get called automatically from '.boot0'
eval(require('Storage').read('.boot0')); eval(require('Storage').read('.boot0'));
/*
f = require("Storage").read("sched.clkinfo.js")
if (f.startsWith("Modules.addCached")) {
}
*/

View File

@ -1,7 +1,7 @@
{ {
"id": "boot", "id": "boot",
"name": "Bootloader", "name": "Bootloader",
"version": "0.63", "version": "0.64",
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings", "description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
"icon": "bootloader.png", "icon": "bootloader.png",
"type": "bootloader", "type": "bootloader",
@ -11,6 +11,9 @@
{"name":".boot0","url":"boot0.js"}, {"name":".boot0","url":"boot0.js"},
{"name":".bootcde","url":"bootloader.js"}, {"name":".bootcde","url":"bootloader.js"},
{"name":"bootupdate.js","url":"bootupdate.js"} {"name":"bootupdate.js","url":"bootupdate.js"}
],"data": [
{"name":".widcache"},
{"name":".clkinfocache"}
], ],
"sortorder": -10 "sortorder": -10
} }

View File

@ -11,4 +11,5 @@
0.10: Fix focus bug when changing focus between two clock infos 0.10: Fix focus bug when changing focus between two clock infos
0.11: Prepend swipe listener if possible 0.11: Prepend swipe listener if possible
0.12: Add drawFilledImage to allow drawing icons with a separately coloured middle 0.12: Add drawFilledImage to allow drawing icons with a separately coloured middle
0.13: Cache loaded ClockInfos so if we have clockInfoWidget and a clock, we don't load them twice (saves ~300ms) 0.13: Cache loaded ClockInfos so if we have clockInfoWidget and a clock, we don't load them twice (saves ~300ms)
0.14: Check for .clkinfocache and use that if exists (from boot 0.64)

View File

@ -135,10 +135,14 @@ exports.load = function() {
hide : function() { clearInterval(this.interval); delete this.interval; }, hide : function() { clearInterval(this.interval); delete this.interval; },
}); });
} }
var clkInfoCache = require('Storage').read('.clkinfocache');
if (clkInfoCache!==undefined) {
// note: code below is included in clkinfocache by bootupdate.js
// we use clkinfocache if it exists as it's faster
eval(clkInfoCache);
} else require("Storage").list(/clkinfo.js$/).forEach(fn => {
// In case there exists already a menu object b with the same name as the next // In case there exists already a menu object b with the same name as the next
// object a, we append the items. Otherwise we add the new object a to the list. // object a, we append the items. Otherwise we add the new object a to the list.
require("Storage").list(/clkinfo.js$/).forEach(fn => {
try{ try{
var a = eval(require("Storage").read(fn))(); var a = eval(require("Storage").read(fn))();
var b = menu.find(x => x.name === a.name); var b = menu.find(x => x.name === a.name);

View File

@ -1,7 +1,7 @@
{ "id": "clock_info", { "id": "clock_info",
"name": "Clock Info Module", "name": "Clock Info Module",
"shortName": "Clock Info", "shortName": "Clock Info",
"version":"0.13", "version":"0.14",
"description": "A library used by clocks to provide extra information on the clock face (Altitude, BPM, etc)", "description": "A library used by clocks to provide extra information on the clock face (Altitude, BPM, etc)",
"icon": "app.png", "icon": "app.png",
"type": "module", "type": "module",