Skip to content

Commit 7785078

Browse files
committed
1.04: For packet uploads, add ability to ste chunk size, report progress or even skip searching for acks
Add example of uploading a zip
1 parent 7a78944 commit 7785078

File tree

2 files changed

+209
-10
lines changed

2 files changed

+209
-10
lines changed

examples/uartUploadZIP.html

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
<html>
2+
<!-- This example uploads the entire contents of a zip file to Espruino using 2v25's packet
3+
upload system. It uses `fs:true` which causes Espruino to upload to the attached SD card.
4+
You can omit this to upload to internal storage instead. -->
5+
<head>
6+
</head>
7+
<body>
8+
<script src="../uart.js"></script>
9+
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.min.js"></script>
10+
11+
<button onclick="connectAndUploadZIP()">Upload a ZIP file</button>
12+
<button onclick="UART.close();">Disconnect</button>
13+
14+
<div>
15+
<span id="status"></span>
16+
<div id="progress" style="width:100px;border:1px solid black;padding:2px;display:none;"><div id="progressInner" style="background-color:red;width:25%">&nbsp;</div></div>
17+
</div>
18+
<script>
19+
console.log("Use UART.debug=3 for full debug info");
20+
21+
function setStatus(txt, progress) {
22+
document.getElementById("status").innerText = txt;
23+
if (progress===undefined)
24+
document.getElementById("progress").style.display="none";
25+
else {
26+
document.getElementById("progress").style.display="inline-block";
27+
document.getElementById("progressInner").style.width=Math.round(progress*100)+"%";
28+
}
29+
}
30+
31+
32+
function uploadZIP() {
33+
fileOpenDialog({
34+
id:"backup",
35+
type:"arraybuffer",
36+
mimeType:".zip,application/zip"}, function(data) {
37+
if (data===undefined) return;
38+
var promise = Promise.resolve();
39+
var zip = new JSZip();
40+
var cmds = "";
41+
zip.loadAsync(data).then(function(zip) {
42+
console.log(`Reading ZIP`);
43+
zip.forEach(function (path, file){
44+
promise = promise
45+
.then(() => {
46+
setStatus("Decompressing"+path);
47+
return file.async("string");
48+
}).then(data => {
49+
if (data.length==0) {
50+
console.log("Can't restore files of length 0, ignoring "+path);
51+
} else {
52+
console.log("Uploading", path);
53+
setStatus("Uploading "+path, 0);
54+
return UART.getConnection().espruinoSendFile(path,data,{
55+
fs:true,
56+
noACK:true,
57+
chunkSize:1024*7, // 8k packet size limit in protocol
58+
progress: (n,chunks) => setStatus("Uploading "+path, n/chunks)
59+
}).then(() => {
60+
console.log("Uploaded.");
61+
setStatus("");
62+
});
63+
}
64+
});
65+
});
66+
return promise;
67+
})
68+
});
69+
}
70+
71+
function connectAndUploadZIP() {
72+
if (UART.getConnection()) {
73+
uploadZIP();
74+
} else {
75+
UART.write("reset()\n", function() { // or connect
76+
uploadZIP();
77+
});
78+
}
79+
}
80+
81+
// just copied from EspruinoTools to let us pop up a dialog
82+
function fileOpenDialog(options, callback) {
83+
function readerLoaded(e,files,i,options,fileLoader) {
84+
/* Doing reader.readAsText(file) interprets the file as UTF8
85+
which we don't want. */
86+
var result;
87+
if (options.type=="text") {
88+
var a = new Uint8Array(e.target.result);
89+
result = "";
90+
for (var j=0;j<a.length;j++)
91+
result += String.fromCharCode(a[j]);
92+
} else
93+
result = e.target.result;
94+
fileLoader.callback(result, files[i].type, files[i].name);
95+
96+
97+
// If there's a file left to load
98+
if (i < files.length - 1 && options.multi) {
99+
// Load the next file
100+
setupReader(files, i+1,options,fileLoader);
101+
} else {
102+
fileLoader.callback = undefined;
103+
}
104+
}
105+
function setupReader(files,i,options,fileLoader) {
106+
var reader = new FileReader();
107+
reader.onload = function(e) {
108+
readerLoaded(e,files,i,options,fileLoader)
109+
};
110+
if (options.type=="text" || options.type=="arraybuffer") reader.readAsArrayBuffer(files[i]);
111+
else throw new Error("fileOpenDialog: unknown type "+options.type);
112+
}
113+
options = options||{};
114+
options.type = options.type||"text";
115+
options.id = options.id||"default";
116+
var loaderId = options.id+"FileLoader";
117+
var fileLoader = document.getElementById(loaderId);
118+
if (!fileLoader) {
119+
fileLoader = document.createElement("input");
120+
fileLoader.setAttribute("id", loaderId);
121+
fileLoader.setAttribute("type", "file");
122+
fileLoader.setAttribute("style", "z-index:-2000;position:absolute;top:0px;left:0px;display:none;");
123+
if (options.multi)
124+
fileLoader.setAttribute("multiple","multiple");
125+
if (options.mimeType)
126+
fileLoader.setAttribute("accept",options.mimeType);
127+
fileLoader.addEventListener('click', function(e) {
128+
e.target.value = ''; // handle repeated upload of the same file
129+
});
130+
fileLoader.addEventListener('change', function(e) {
131+
if (!fileLoader.callback) return;
132+
133+
var files = e.target.files;
134+
setupReader(files,0,options,fileLoader);
135+
136+
}, false);
137+
document.body.appendChild(fileLoader);
138+
}
139+
fileLoader.callback = callback;
140+
fileLoader.click();
141+
}
142+
143+
</script>
144+
</body>
145+
</html>

uart.js

Lines changed: 64 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,25 @@ As of Espruino 2v25 you can also send data packets:
5252
UART.getConnection().espruinoSendFile("test.txt","This is a test of sending data to Espruino").then(_=>console.log("Done"))
5353
UART.getConnection().espruinoSendFile("test.txt","This is a test of sending data to Espruino's SD card",{fs:true}).then(_=>console.log("Done"))
5454
55+
UART.debug=3;
56+
var s = ""; for (var i=0;i<256;i++) s+=String.fromCharCode(i);
57+
var ss = ""; for (var i=0;i<256;i++) ss+=s;
58+
UART.getConnection().espruinoSendFile("testbin",ss,{fs:true})
59+
60+
61+
UART.debug=3;
62+
var s = ""; for (var i=0;i<256;i++) s+=String.fromCharCode(i);
63+
UART.getConnection().espruinoSendFile("testbin",s,{fs:true})
64+
65+
UART.debug=3;
66+
var s = ""; for (var i=0;i<256;i++) s+=String.fromCharCode(i)
67+
UART.getConnection().espruinoSendFile("testbin",s)
68+
69+
5570
ChangeLog:
5671
5772
...
73+
1.04: For packet uploads, add ability to ste chunk size, report progress or even skip searching for acks
5874
1.03: Added options for restricting what devices appear
5975
Improve Web Serial Disconnection - didn't work before
6076
1.02: Added better on/emit/removeListener handling
@@ -72,6 +88,7 @@ To do:
7288
* move 'connection.received' handling into removeListener and add an upper limit (100k?)
7389
* add a 'line' event for each line of data that's received
7490
* add espruinoEval method using the new packet system
91+
* move XON/XOFF handling into Connection.rxDataHandler
7592
7693
*/
7794
(function (root, factory) {
@@ -157,8 +174,13 @@ To do:
157174
connection.emit('data', data);
158175
}
159176

160-
/* Send a packet of type "RESPONSE/EVAL/EVENT/FILE_SEND/DATA" to Espruino */
161-
espruinoSendPacket(pkType, data) {
177+
/* Send a packet of type "RESPONSE/EVAL/EVENT/FILE_SEND/DATA" to Espruino
178+
options = {
179+
noACK : bool (don't wait to acknowledgement)
180+
}
181+
*/
182+
espruinoSendPacket(pkType, data, options) {
183+
options = options || {};
162184
if ("string"!=typeof data) throw new Error("'data' must be a String");
163185
if (data.length>0x1FFF) throw new Error("'data' too long");
164186
const PKTYPES = {
@@ -183,27 +205,59 @@ To do:
183205
tidy();
184206
reject();
185207
}
186-
connection.parsePackets = true;
187-
connection.on("ack",onACK);
188-
connection.on("nak",onNAK);
208+
if (!options.noACK) {
209+
connection.parsePackets = true;
210+
connection.on("ack",onACK);
211+
connection.on("nak",onNAK);
212+
}
189213
let flags = data.length | PKTYPES[pkType];
190-
write(String.fromCharCode(/*DLE*/16,/*SOH*/1,(flags>>8)&0xFF,flags&0xFF)+data);
214+
connection.write(String.fromCharCode(/*DLE*/16,/*SOH*/1,(flags>>8)&0xFF,flags&0xFF)+data, function() {
215+
// write complete
216+
if (options.noACK) {
217+
resolve(); // if not listening for acks, just resolve immediately
218+
}
219+
});
191220
});
192221
}
193-
/* Send a file to Espruino using 2v25 packets */
222+
/* Send a file to Espruino using 2v25 packets.
223+
options = { // mainly passed to Espruino
224+
fs : true // optional -> write using require("fs") (to SD card)
225+
noACK : bool // (don't wait to acknowledgements)
226+
chunkSize : int // size of chunks to send (default 1024) for safety this depends on how big your device's input buffer is if there isn't flow control
227+
progress : (chunkNo,chunkCount)=>{} // callback to report upload progress
228+
} */
194229
espruinoSendFile(filename, data, options) {
195230
if ("string"!=typeof data) throw new Error("'data' must be a String");
231+
let CHUNK = 1024;
196232
options = options||{};
197233
options.fn = filename;
198234
options.s = data.length;
235+
let packetOptions = {};
236+
let progressHandler = (chunkNo,chunkCount)=>{};
237+
if (options.noACK) {
238+
delete options.noACK;
239+
packetOptions.noACK = true;
240+
}
241+
if (options.chunkSize) {
242+
CHUNK = options.chunkSize;
243+
delete options.chunkSize;
244+
}
245+
if (options.progress) {
246+
progressHandler = options.progress;
247+
delete options.progress;
248+
}
199249
let connection = this;
250+
let packetCount = 0, packetTotal = Math.ceil(data.length/CHUNK)+1;
251+
// always ack the FILE_SEND
252+
progressHandler(0, packetTotal);
200253
return connection.espruinoSendPacket("FILE_SEND",JSON.stringify(options)).then(sendData);
254+
// but if noACK don't ack for data
201255
function sendData() {
256+
progressHandler(++packetCount, packetTotal);
202257
if (data.length==0) return Promise.resolve();
203-
const CHUNK = 512;
204258
let packet = data.substring(0, CHUNK);
205259
data = data.substring(CHUNK);
206-
return connection.espruinoSendPacket("DATA", packet).then(sendData);
260+
return connection.espruinoSendPacket("DATA", packet, packetOptions).then(sendData);
207261
}
208262
}
209263
};
@@ -669,7 +723,7 @@ To do:
669723
// ----------------------------------------------------------
670724

671725
var uart = {
672-
version : "1.03",
726+
version : "1.04",
673727
/// Are we writing debug information? 0 is no, 1 is some, 2 is more, 3 is all.
674728
debug : 1,
675729
/// Should we use flow control? Default is true

0 commit comments

Comments
 (0)