Created
October 19, 2022 15:59
-
-
Save gfwilliams/95f776176929b58b3603034065c3ec2d to your computer and use it in GitHub Desktop.
bootupdate.js for Bangle.js with ability to dump how long things take
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* This rewrites boot0.js based on current settings. If settings changed then it | |
recalculates, but this avoids us doing a whole bunch of reconfiguration most | |
of the time. */ | |
E.showMessage(/*LANG*/"Updating boot0..."); | |
var DEBUG_TIME = true; // print time taken by each section of boot.js | |
var s = require('Storage').readJSON('setting.json',1)||{}; | |
var BANGLEJS2 = process.env.HWVERSION==2; // Is Bangle.js 2 | |
var boot = "", bootPost = ""; | |
if (DEBUG_TIME) boot += "var t=getTime();"; | |
if (require('Storage').hash) { // new in 2v11 - helps ensure files haven't changed | |
var CRC = E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\.boot\.js/)+E.CRC32(process.env.GIT_COMMIT); | |
boot += `if (E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\\.boot\\.js/)+E.CRC32(process.env.GIT_COMMIT)!=${CRC})`; | |
} else { | |
var 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')); throw "Storage Updated!"}\n`; | |
boot += `E.setFlags({pretokenise:1});\n`; | |
boot += `var bleServices = {}, bleServiceOptions = { uart : true};\n`; | |
bootPost += `NRF.setServices(bleServices, bleServiceOptions);delete bleServices,bleServiceOptions;\n`; // executed after other boot code | |
if (s.ble!==false) { | |
if (s.HID) { // Human interface device | |
if (s.HID=="joy") boot += `Bangle.HID = E.toUint8Array(atob("BQEJBKEBCQGhAAUJGQEpBRUAJQGVBXUBgQKVA3UBgQMFAQkwCTEVgSV/dQiVAoECwMA="));`; | |
else if (s.HID=="com") boot += `Bangle.HID = E.toUint8Array(atob("BQEJAqEBhQEJAaEABQkZASkFFQAlAZUFdQGBApUBdQOBAwUBCTAJMQk4FYElf3UIlQOBBgUMCjgCFYElf3UIlQGBBsDABQEJBqEBhQIFBxngKecVACUBdQGVCIECdQiVAYEBGQApcxUAJXOVBXUIgQDA"));` | |
else if (s.HID=="kb") boot += `Bangle.HID = E.toUint8Array(atob("BQEJBqEBBQcZ4CnnFQAlAXUBlQiBApUBdQiBAZUFdQEFCBkBKQWRApUBdQORAZUGdQgVACVzBQcZAClzgQAJBRUAJv8AdQiVArECwA=="));` | |
else /*kbmedia*/boot += `Bangle.HID = E.toUint8Array(atob("BQEJBqEBhQIFBxngKecVACUBdQGVCIEClQF1CIEBlQV1AQUIGQEpBZEClQF1A5EBlQZ1CBUAJXMFBxkAKXOBAAkFFQAm/wB1CJUCsQLABQwJAaEBhQEVACUBdQGVAQm1gQIJtoECCbeBAgm4gQIJzYECCeKBAgnpgQIJ6oECwA=="));`; | |
boot += `bleServiceOptions.hid=Bangle.HID;\n`; | |
} | |
} | |
if (s.log==2) { // logging to file | |
boot += `_DBGLOG=require("Storage").open("log.txt","a"); | |
`; | |
} if (s.blerepl===false) { // If not programmable, force terminal off Bluetooth | |
if (s.log==2) boot += `_DBGLOG=require("Storage").open("log.txt","a"); | |
LoopbackB.on('data',function(d) {_DBGLOG.write(d);Terminal.write(d);}); | |
LoopbackA.setConsole(true);\n`; | |
else if (s.log) boot += `Terminal.setConsole(true);\n`; // if showing debug, force REPL onto terminal | |
else boot += `E.setConsole(null,{force:true});\n`; // on new (2v05+) firmware we have E.setConsole which allows a 'null' console | |
/* If not programmable add our own handler for Bluetooth data | |
to allow Gadgetbridge commands to be received*/ | |
boot += ` | |
Bluetooth.line=""; | |
Bluetooth.on('data',function(d) { | |
var l = (Bluetooth.line + d).split(/[\\n\\r]/); | |
Bluetooth.line = l.pop(); | |
l.forEach(n=>Bluetooth.emit("line",n)); | |
}); | |
Bluetooth.on('line',function(l) { | |
if (l.startsWith('\x10')) l=l.slice(1); | |
if (l.startsWith('GB({') && l.endsWith('})') && global.GB) | |
try { global.GB(JSON.parse(l.slice(3,-1))); } catch(e) {} | |
});\n`; | |
} else { | |
if (s.log==2) boot += `_DBGLOG=require("Storage").open("log.txt","a"); | |
LoopbackB.on('data',function(d) {_DBGLOG.write(d);Terminal.write(d);}); | |
if (!NRF.getSecurityStatus().connected) LoopbackA.setConsole();\n`; | |
else if (s.log) boot += `if (!NRF.getSecurityStatus().connected) Terminal.setConsole();\n`; // if showing debug, put REPL on terminal (until connection) | |
else boot += `Bluetooth.setConsole(true);\n`; // else if no debug, force REPL to Bluetooth | |
} | |
// we just reset, so BLE should be on. | |
// Don't disconnect if something is already connected to us | |
if (s.ble===false) boot += `if (!NRF.getSecurityStatus().connected) NRF.sleep();\n`; | |
// Set time | |
if (s.timeout!==undefined) boot += `Bangle.setLCDTimeout(${s.timeout});\n`; | |
if (!s.timeout) boot += `Bangle.setLCDPower(1);\n`; | |
boot += `E.setTimeZone(${s.timezone});`; | |
// Set vibrate, beep, etc IF on older firmwares | |
if (!Bangle.F_BEEPSET) { | |
if (!s.vibrate) boot += `Bangle.buzz=Promise.resolve;\n` | |
if (s.beep===false) boot += `Bangle.beep=Promise.resolve;\n` | |
else if (s.beep=="vib" && !BANGLEJS2) boot += `Bangle.beep = function (time, freq) { | |
return new Promise(function(resolve) { | |
if ((0|freq)<=0) freq=4000; | |
if ((0|time)<=0) time=200; | |
if (time>5000) time=5000; | |
analogWrite(D13,0.1,{freq:freq}); | |
setTimeout(function() { | |
digitalWrite(D13,0); | |
resolve(); | |
}, time); | |
}); | |
};\n`; | |
} | |
// Draw out of memory errors onto the screen | |
boot += `E.on('errorFlag', function(errorFlags) { | |
g.reset(1).setColor("#ff0000").setFont("6x8").setFontAlign(0,1).drawString(errorFlags,g.getWidth()/2,g.getHeight()-1).flip(); | |
print("Interpreter error:", errorFlags); | |
E.getErrorFlags(); // clear flags so we get called next time | |
});\n`; | |
// stop users doing bad things! | |
if (global.save) boot += `global.save = function() { throw new Error("You can't use save() on Bangle.js without overwriting the bootloader!"); }\n`; | |
// Apply any settings-specific stuff | |
if (s.options) boot+=`Bangle.setOptions(${E.toJS(s.options)});\n`; | |
if (s.brightness && s.brightness!=1) boot+=`Bangle.setLCDBrightness(${s.brightness});\n`; | |
if (s.passkey!==undefined && s.passkey.length==6) boot+=`NRF.setSecurity({passkey:${E.toJS(s.passkey.toString())}, 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`; | |
} | |
try { | |
Bangle.setUI({}); // In 2v12.xx we added the option for mode to be an object - for 2v12 and earlier, add a fix if it fails with an object supplied | |
} catch(e) { | |
boot += `Bangle._setUI = Bangle.setUI; | |
Bangle.setUI=function(mode, cb) { | |
if (Bangle.uiRemove) { | |
Bangle.uiRemove(); | |
delete Bangle.uiRemove; | |
} | |
if ("object"==typeof mode) { | |
// TODO: handle mode.back? | |
mode = mode.mode; | |
} | |
Bangle._setUI(mode, cb); | |
};\n`; | |
} | |
delete E.showScroller; // deleting stops us getting confused by our own decl. builtins can't be deleted | |
if (!E.showScroller) { // added in 2v11 - this is a limited functionality polyfill | |
boot += `E.showScroller = (function(a){function n(){g.reset();b>=l+c&&(c=1+b-l);b<c&&(c=b);g.setColor(g.theme.fg);for(var d=0;d<l;d++){var m=d+c;if(0>m||m>=a.c)break;var f=24+d*a.h;a.draw(m,{x:0,y:f,w:h,h:a.h});d+c==b&&g.setColor(g.theme.fg).drawRect(0,f,h-1,f+a.h-1).drawRect(1,f+1,h-2,f+a.h-2)}g.setColor(c?g.theme.fg:g.theme.bg);g.fillPoly([e,6,e-14,20,e+14,20]);g.setColor(a.c>l+c?g.theme.fg:g.theme.bg);g.fillPoly([e,k-7,e-14,k-21,e+14,k-21])}if(!a)return Bangle.setUI();var b=0,c=0,h=g.getWidth(), | |
k=g.getHeight(),e=h/2,l=Math.floor((k-48)/a.h);g.reset().clearRect(0,24,h-1,k-1);n();Bangle.setUI("updown",d=>{d?(b+=d,0>b&&(b=a.c-1),b>=a.c&&(b=0),n()):a.select(b)})});\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]}; | |
else if ('object'==typeof src) return { | |
width:("width" in src) ? src.width : src.getWidth(), | |
height:("height" in src) ? src.height : src.getHeight()}; | |
var im = E.toString(src); | |
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) { | |
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")) { | |
var words = unwrappedLine.split(" "); | |
var line = words.shift(); | |
for (var word of words) { | |
if (g.stringWidth(line + " " + word) > maxWidth) { | |
lines.push(line); | |
line = word; | |
} else { | |
line += " " + word; | |
} | |
} | |
lines.push(line); | |
} | |
return lines; | |
};\n`; | |
} | |
delete Bangle.appRect; // deleting stops us getting confused by our own decl. builtins can't be deleted | |
if (!Bangle.appRect) { // added in 2v11 - polyfill for older firmwares | |
boot += `Bangle.appRect = ((y,w,h)=>({x:0,y:0,w:w,h:h,x2:w-1,y2:h-1}))(g.getWidth(),g.getHeight()); | |
(lw=>{ Bangle.loadWidgets = () => { lw(); Bangle.appRect = ((y,w,h)=>({x:0,y:y,w:w,h:h-y,x2:w-1,y2:h-(1+h)}))(global.WIDGETS?24:0,g.getWidth(),g.getHeight()); }; })(Bangle.loadWidgets);\n`; | |
} | |
// Append *.boot.js files | |
// These could change bleServices/bleServiceOptions if needed | |
var bootFiles = require('Storage').list(/\.boot\.js$/).sort((a,b)=>{ | |
var getPriority = /.*\.(\d+)\.boot\.js$/; | |
var aPriority = a.match(getPriority); | |
var bPriority = b.match(getPriority); | |
if (aPriority && bPriority){ | |
return parseInt(aPriority[1]) - parseInt(bPriority[1]); | |
} else if (aPriority && !bPriority){ | |
return -1; | |
} else if (!aPriority && bPriority){ | |
return 1; | |
} | |
return a==b ? 0 : (a>b ? 1 : -1); | |
}); | |
// precalculate file size | |
if (DEBUG_TIME) boot += "print('init',getTime()-t);t=getTime();"; | |
var fileSize = boot.length + bootPost.length; | |
bootFiles.forEach(bootFile=>{ | |
// match the size of data we're adding below in bootFiles.forEach | |
fileSize += 2+bootFile.length+1+require('Storage').read(bootFile).length+2; | |
if (DEBUG_TIME) fileSize += 34+bootFile.length; | |
}); | |
// write file in chunks (so as not to use up all RAM) | |
require('Storage').write('.boot0',boot,0,fileSize); | |
var fileOffset = boot.length; | |
bootFiles.forEach(bootFile=>{ | |
// we add a semicolon so if the file is wrapped in (function(){ ... }() | |
// with no semicolon we don't end up with (function(){ ... }()(function(){ ... }() | |
// which would cause an error! | |
// we write: | |
// "//"+bootFile+"\n"+require('Storage').read(bootFile)+";\n"; | |
// but we need to do this without ever loading everything into RAM as some | |
// boot files seem to be getting pretty big now. | |
require('Storage').write('.boot0',"//"+bootFile+"\n",fileOffset); | |
fileOffset+=2+bootFile.length+1; | |
var bf = require('Storage').read(bootFile); | |
// we can't just write 'bf' in one go because at least in 2v13 and earlier | |
// Espruino wants to read the whole file into RAM first, and on Bangle.js 1 | |
// it can be too big (especially BTHRM). | |
var bflen = bf.length; | |
var bfoffset = 0; | |
while (bflen) { | |
var 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_TIME) { | |
require('Storage').write('.boot0',"print('"+bootFile+"',getTime()-t);t=getTime();",fileOffset); | |
fileOffset+=34+bootFile.length; | |
} | |
}); | |
require('Storage').write('.boot0',bootPost,fileOffset); | |
delete boot; | |
delete bootPost; | |
delete bootFiles; | |
delete fileSize; | |
delete fileOffset; | |
E.showMessage(/*LANG*/"Reloading..."); | |
eval(require('Storage').read('.boot0')); | |
// .bootcde should be run automatically after if required, since | |
// we normally get called automatically from '.boot0' |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment