1
0
Fork 0

Added automatic Espruino programmer tool

master
Gordon Williams 2022-07-19 17:13:01 +01:00
parent 7c6be92667
commit d7e9469909
8 changed files with 297 additions and 1 deletions

View File

@ -17,7 +17,7 @@ showing available Espruino devices is popped up.
device being connected to. Use this if you want to print data - eg: `print(E.getBattery())`
When done, click 'Upload'. Your changes will be saved to local storage
so they'll be remembered next time you upload from the same device.s
so they'll be remembered next time you upload from the same device.
## Usage

View File

@ -0,0 +1 @@
0.01: New App!

View File

@ -0,0 +1,42 @@
# Espruino Programmer
Finds Bluetooth devices with a specific name (eg `Puck.js`), connects and uploads code. Great for programming many devices at once!
**WARNING:** This will reprogram **any matching Espruino device within range** while
the app is running. Unless you are careful to remove other devices from the area or
turn them off, you could find some of your devices unexpectedly get programmed!
## Customising
Click on the Customise button in the app loader to set up the programmer.
* First you need to choose the kind of devices you want to upload to. This is
the text that should match the Bluetooth advertising name. So `Puck.js` for Puck.js
devices, or `Bangle.js` for Bangles.
* Now paste in the code you want to write to the device. This is automatically
written to flash (`.bootcde`). See https://www.espruino.com/Saving#save-on-send-to-flash-
for more information.
* Now enter the code that should be sent **after** programming. This code
should make the device so it doesn't advertise on Bluetooth with the Bluetooth
name you entered for the first item. It may also help if it indicates to you that
the device is programmed properly.
* You could turn advertising off with `NRF.sleep()`
* You could change the advertising name with `NRF.setAdvertising({},{name:"Ok"});`
* On a Bangle, you could turn it off with `Bangle.off()`
* Finally scroll down and click `Upload`
* Now you can run the new `Programmer` app on the Bangle.
## Usage
Just run the app, and as soon as it starts it'll start scanning for
devices to upload to!
To stop scanning, long-press the button to return to the clock.
## Notes
* Right now the Espruino Tools used here are unaware of the device they're writing to,
and as a result they don't use Storage and so the size of the files you can
write to the device are quite limited. You should be find with up to 4k of code.
* Currently, code is not minified before upload (so you need to supply pre-minified
code if you want that)

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEw4cA/4AB7wJB8/5uX+7uUgH41lSKf4AKpMkyQCCggEDAQVtCAMCCNWUx9JufSrmkCJeKqsiytICJtFkWRCJWAEaARCI5BkEoAGBymJ9eSvXkCJZ9JCLI1DyM9uQRLNYWRpRZMR5ARWAwSPCuWR9MuCJZZIgARGPouTCIcSA4OQAoMW7dt2wCEEZECCI1oCJAADrZyBAAcDuQRByOABQkKDAvbtwRBxu24AMFAAcGCIY/B7AQIhpOC3MjKIVsCJe3jYRCwiPEkARBQg227ieDAQO0CJPhCKHJCK1N0ARI28JCIjUDEY4OBzWRfAoRG3ARBygRH3oPBswRB4QjFfAYgCt9pYoJoEkmbCJONCI1ACJGSiQRE7TXDCIuQEYkmCIhpDEYSCFCIj2DCIOTrYRE6ARDAH4AHA"))

90
apps/espruinoprog/app.js Normal file
View File

@ -0,0 +1,90 @@
var uart; // require("ble_uart")
var device; // BluetoothDevice
var uploadTimeout; // a timeout used during upload - if we disconnect, kill this
Bangle.loadWidgets();
var json = require("Storage").readJSON("espruinoprog.json",1);
/*var json = { // for example
namePrefix : "Puck.js ",
code : "E.setBootCode('digitalPulse(LED2,1,100);')",
post : "LED.set();NRF.sleep()",
};*/
if ("object" != typeof json) {
E.showAlert("JSON not found","Programmer").then(() => load());
throw new Error("JSON not found");
// stops execution
}
// Set up terminal
var R = Bangle.appRect;
var termg = Graphics.createArrayBuffer(R.w, R.h, 1, {msb:true});
termg.setFont("6x8");
var term;
function showTerminal() {
E.showMenu(); // clear anything that was drawn
if (term) term.print(""); // redraw terminal
}
function scanAndConnect() {
termg.clear();
term = require("VT100").connect(termg, {
charWidth : 6,
charHeight : 8
});
term.print = str => {
for (var i of str) term.char(i);
g.reset().drawImage(termg,R.x,R.y);
};
term.print(`\r\nScanning...\r\n`);
NRF.requestDevice({ filters: [{ namePrefix: json.namePrefix }] }).then(function(dev) {
term.print(`Found ${dev.name||dev.id.substr(0,17)}\r\n`);
device = dev;
term.print(`Connect to ${dev.name||dev.id.substr(0,17)}...\r\n`);
device.removeAllListeners();
device.on('gattserverdisconnected', function(reason) {
if (!uart) return;
term.print(`\r\nDISCONNECTED (${reason})\r\n`);
uart = undefined;
device = undefined;
if (uploadTimeout) clearTimeout(uploadTimeout);
uploadTimeout = undefined;
setTimeout(scanAndConnect, 1000);
});
require("ble_uart").connect(device).then(function(u) {
uart = u;
term.print("Connected...\r\n");
uart.removeAllListeners();
uart.on('data', function(d) { term.print(d); });
uart.write(json.code+"\n").then(() => {
term.print("\r\nUpload Complete...\r\n");
// main upload completed - wait a bit
uploadTimeout = setTimeout(function() {
term.print("\r\nFinal Upload...\r\n");
// now upload the code to run after...
uart.write(json.post+"\n").then(() => {
term.print("\r\nDone.\r\n");
// now wait and disconnect (if not already done!)
uploadTimeout = setTimeout(function() {
term.print("\r\nDisconnecting...\r\n");
if (uart) uart.disconnect();
}, 500);
});
}, 1000);
});
});
}).catch(err => {
if (err.toString().startsWith("No device found")) {
// expected - try again
scanAndConnect();
} else
term.print(`\r\ERROR ${err.toString()}\r\n`);
});
}
// now start
Bangle.drawWidgets();
showTerminal();
scanAndConnect();

BIN
apps/espruinoprog/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

@ -0,0 +1,145 @@
<html>
<head>
<link rel="stylesheet" href="../../css/spectre.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.32.0/codemirror.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.52.2/addon/lint/lint.min.css">
</head>
<body>
<script src="../../core/lib/customize.js"></script>
<script src="../../core/lib/espruinotools.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.32.0/codemirror.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.52.2/mode/javascript/javascript.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.52.2/addon/lint/lint.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.52.2/addon/lint/javascript-lint.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.52.2/addon/hint/javascript-hint.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jshint/2.11.0/jshint.min.js"></script>
<p>Upload code to devices with names starting with:</p>
<p><input type="text" id="nameprefix"></input></p>
<p>Enter your program to upload here:</p>
<p><textarea id="js-code"></textarea></p>
<p>Enter the code to send after upload here:</p>
<p><textarea id="post-code"></textarea></p>
<p>Then click <button id="upload" class="btn btn-primary">Upload</button>&nbsp;<span id="btninfo" style="color:orange"></span> </p>
<p><a id="setdefault">Click here</a> to reset to defaults.</p>
<script>
const LS_JSCODE = "espruinoprog.code";
const LS_POSTCODE = "espruinoprog.postcode";
const LS_NAMEPREFIX = "espruinoprog.namePrefix";
var jseditor,posteditor;
function setDefaults() {
if (localStorage.getItem(LS_JSCODE) === null) {
localStorage.setItem(LS_JSCODE, `// Flash LED2 every 2 seconds
setInterval(function() {
LED2.set();
setTimeout(function() {
LED2.reset();
}, 100);
}, 2000);`);
}
if (localStorage.getItem(LS_POSTCODE) === null) {
localStorage.setItem(LS_POSTCODE, `// Turn red LED on to show programmed
// Bluetooth off to stop this getting re-programmed
LED.set();NRF.sleep();`);
}
if (localStorage.getItem(LS_NAMEPREFIX) === null) {
localStorage.setItem(LS_NAMEPREFIX, "Puck.js");
}
document.getElementById("js-code").value = localStorage.getItem(LS_JSCODE);
if (jseditor) jseditor.setValue(document.getElementById("js-code").value);
document.getElementById("post-code").value = localStorage.getItem(LS_POSTCODE);
if (posteditor) posteditor.setValue(document.getElementById("post-code").value);
document.getElementById("nameprefix").value = localStorage.getItem(LS_NAMEPREFIX);
}
setDefaults();
// The code editor
var lintFlags = {
esversion: 6, // Enable ES6 for literals, arrow fns, binary
evil: true, // don't warn on use of strings in setInterval
laxbreak: true, // don't warn about newlines in expressions
laxcomma: true // don't warn about commas at the start of the line
};
jseditor = CodeMirror.fromTextArea(document.getElementById("js-code"), {
width: "100%",
height: "auto",
matchBrackets: true,
mode: { name: "javascript", globalVars: false },
lineWrapping: true,
showTrailingSpace: true,
lint: lintFlags,
gutters: ["CodeMirror-linenumbers", "CodeMirror-lint-markers"],
lineNumbers: true
});
posteditor = CodeMirror.fromTextArea(document.getElementById("post-code"), {
width: "100%",
height: "auto",
matchBrackets: true,
mode: { name: "javascript", globalVars: false },
lineWrapping: true,
showTrailingSpace: true,
lint: lintFlags,
gutters: ["CodeMirror-linenumbers", "CodeMirror-lint-markers"],
lineNumbers: true
});
function hasWarnings() {
return jseditor.state.lint.marked.length!=0 || posteditor.state.lint.marked.length!=0;
}
var editorChangedTimeout;
function editorChanged() {
if (editorChangedTimeout) clearTimeout(editorChangedTimeout);
editorChangedTimeout = setTimeout(function() {
if (hasWarnings()) {
document.getElementById("btninfo").innerHTML = "There are warnings in the code to be uploaded";
document.getElementById("upload").classList.add("disabled");
} else {
document.getElementById("btninfo").innerHTML = "";
document.getElementById("upload").classList.remove("disabled");
}
}, 500);
}
jseditor.on("change", editorChanged);
posteditor.on("change", editorChanged);
document.getElementById("upload").addEventListener("click", function() {
if (!hasWarnings()) {
var jscode = jseditor.getValue();
var postcode = posteditor.getValue();
var namePrefix = document.getElementById("nameprefix").value;
localStorage.setItem(LS_JSCODE, jscode);
localStorage.setItem(LS_POSTCODE, postcode);
localStorage.setItem(LS_NAMEPREFIX, namePrefix);
Espruino.transform(jscode, {
SET_TIME_ON_WRITE : false, // time would just be out of date
SAVE_ON_SEND : 1, // save to flash
LOAD_STORAGE_FILE : 0, // do not load from storage after saving
// PRETOKENISE : true,
// MINIFICATION_LEVEL : "ESPRIMA", // maybe?
}).then(content => {
sendCustomizedApp({
storage: [{ name: "espruinoprog.json", content: JSON.stringify({
namePrefix : namePrefix,
code : Espruino.Core.CodeWriter.reformatCode(content),
post : Espruino.Core.CodeWriter.reformatCode(postcode)
})}]
});
});
}
});
document.getElementById("setdefault").addEventListener("click", function(e) {
e.preventDefault();
localStorage.removeItem(LS_JSCODE);
localStorage.removeItem(LS_POSTCODE);
localStorage.removeItem(LS_NAMEPREFIX);
setDefaults();
});
</script>
</body>
</html>

View File

@ -0,0 +1,17 @@
{
"id": "espruinoprog",
"name": "Espruino Programmer",
"shortName": "Programmer",
"version": "0.01",
"description": "Finds Bluetooth devices with a specific name (eg 'Puck.js'), connects and uploads code. Great for programming many devices at once!",
"icon": "app.png",
"tags": "tool,bluetooth",
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"custom": "custom.html",
"storage": [
{"name":"espruinoprog.app.js","url":"app.js"},
{"name":"espruinoprog.img","url":"app-icon.js","evaluate":true},
{"name":"espruinoprog.json"}
]
}