Added automatic Espruino programmer tool
parent
7c6be92667
commit
d7e9469909
|
|
@ -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())`
|
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
|
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
|
## Usage
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
0.01: New App!
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEw4cA/4AB7wJB8/5uX+7uUgH41lSKf4AKpMkyQCCggEDAQVtCAMCCNWUx9JufSrmkCJeKqsiytICJtFkWRCJWAEaARCI5BkEoAGBymJ9eSvXkCJZ9JCLI1DyM9uQRLNYWRpRZMR5ARWAwSPCuWR9MuCJZZIgARGPouTCIcSA4OQAoMW7dt2wCEEZECCI1oCJAADrZyBAAcDuQRByOABQkKDAvbtwRBxu24AMFAAcGCIY/B7AQIhpOC3MjKIVsCJe3jYRCwiPEkARBQg227ieDAQO0CJPhCKHJCK1N0ARI28JCIjUDEY4OBzWRfAoRG3ARBygRH3oPBswRB4QjFfAYgCt9pYoJoEkmbCJONCI1ACJGSiQRE7TXDCIuQEYkmCIhpDEYSCFCIj2DCIOTrYRE6ARDAH4AHA"))
|
||||||
|
|
@ -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();
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 9.4 KiB |
|
|
@ -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> <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>
|
||||||
|
|
@ -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"}
|
||||||
|
]
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue