Skip to content

Commit dcede45

Browse files
committed
New UART.js that allows us to use the new packet transfer system in Espruino
1 parent c594022 commit dcede45

File tree

1 file changed

+108
-16
lines changed

1 file changed

+108
-16
lines changed

uart.js

+108-16
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,15 @@ UART.ports = ["Web Serial"]; // force only Web Serial to be used
4747
UART.debug = 3; // show all debug messages
4848
etc...
4949
50+
As of Espruino 2v25 you can also send data packets:
51+
52+
UART.getConnection().espruinoSendFile("test.txt","This is a test of sending data to Espruino").then(_=>console.log("Done"))
53+
5054
ChangeLog:
5155
5256
...
57+
1.02: Added better on/emit/removeListener handling
58+
Add .espruinoSendPacket
5359
1.01: Add UART.ports to allow available to user to be restricted
5460
Add configurable baud rate
5561
Updated modal dialog look (with common fn for selector and modal)
@@ -60,6 +66,9 @@ ChangeLog:
6066
To do:
6167
6268
* write/eval/etc should return promises
69+
* move 'connection.received' handling into removeListener and add an upper limit (100k?)
70+
* add a 'line' event for each line of data that's received
71+
* add espruinoEval method using the new packet system
6372
6473
*/
6574
(function (root, factory) {
@@ -105,6 +114,94 @@ To do:
105114
if (uart.log) uart.log(level, s);
106115
}
107116

117+
/// Base connection class - BLE/Serial add their write/etc on top of this
118+
class Connection {
119+
// on/emit work for close/data/open/error/ack/nak events
120+
on(evt,cb) { let e = "on"+evt; if (!this[e]) this[e]=[]; this[e].push(cb); }; // on only works with a single handler
121+
emit(evt,data) { let e = "on"+evt; if (this[e]) this[e].forEach(fn=>fn(data)); };
122+
removeListener(evt,callback) { let e = "on"+evt; if (this[e]) this[e]=this[e].filter(fn=>fn!=callback); };
123+
isOpen = false; // is the connection actually open?
124+
isOpening = true; // in the process of opening a connection?
125+
txInProgress = false; // is transmission in progress?
126+
parsePackets = false; // If set we parse the input stream for Espruino packet data transfers
127+
rxDataHandler(data) { // Called when data is received, and passed it on to event listeners
128+
log(3, "Received "+JSON.stringify(data));
129+
// TODO: handle XON/XOFF centrally here?
130+
if (this.parsePackets) {
131+
for (var i=0;i<data.length;i++) {
132+
let ch = data[i];
133+
if (ch=="\x06") {
134+
log(3, "Got ACK");
135+
this.emit("ack");
136+
ch = undefined;
137+
} else if (ch=="\x15") {
138+
log(3, "Got NAK");
139+
this.emit("nak");
140+
ch = undefined;
141+
}
142+
if (ch===undefined) { // if we're supposed to remove the char, do it
143+
console.log("before",data);
144+
data = data.substring(0,i-1)+data.substring(i+1);
145+
console.log("after",data);
146+
i--;
147+
}
148+
}
149+
}
150+
connection.emit('data', data);
151+
}
152+
153+
/* Send a packet of type "RESPONSE/EVAL/EVENT/FILE_SEND/DATA" to Espruino */
154+
espruinoSendPacket(pkType, data) {
155+
if ("string"!=typeof data) throw new Error("'data' must be a String");
156+
if (data.length>0x1FFF) throw new Error("'data' too long");
157+
const PKTYPES = {
158+
RESPONSE : 0, // Response to an EVAL packet
159+
EVAL : 0x2000, // execute and return the result as RESPONSE packet
160+
EVENT : 0x4000, // parse as JSON and create `E.on('packet', ...)` event
161+
FILE_SEND : 0x6000, // called before DATA, with {fn:"filename",s:123}
162+
DATA : 0x8000, // Sent after FILE_SEND with blocks of data for the file
163+
}
164+
if (!pkType in PKTYPES) throw new Error("'pkType' not one of "+Object.keys(PKTYPES));
165+
let connection = this;
166+
return new Promise((resolve,reject) => {
167+
function tidy() {
168+
connection.removeListener("ack",onACK);
169+
connection.removeListener("nak",onNAK);
170+
}
171+
function onACK(ok) {
172+
tidy();
173+
resolve();
174+
}
175+
function onNAK(ok) {
176+
tidy();
177+
reject();
178+
}
179+
connection.parsePackets = true;
180+
connection.on("ack",onACK);
181+
connection.on("nak",onNAK);
182+
let flags = data.length | PKTYPES[pkType];
183+
write(String.fromCharCode(/*DLE*/16,/*SOH*/1,(flags>>8)&0xFF,flags&0xFF)+data);
184+
});
185+
}
186+
/* Send a file to Espruino using 2v25 packets */
187+
espruinoSendFile(filename, data, options) {
188+
if ("string"!=typeof data) throw new Error("'data' must be a String");
189+
options = options||{};
190+
options.fn = filename;
191+
options.s = data.length;
192+
let connection = this;
193+
return connection.espruinoSendPacket("FILE_SEND",JSON.stringify(options)).then(sendData);
194+
function sendData() {
195+
if (data.length==0) return Promise.resolve();
196+
const CHUNK = 512;
197+
let packet = data.substring(0, CHUNK);
198+
data = data.substring(CHUNK);
199+
return connection.espruinoSendPacket("DATA", packet).then(sendData);
200+
}
201+
}
202+
};
203+
204+
///
108205
var endpoints = [];
109206
endpoints.push({
110207
name : "Web Bluetooth",
@@ -253,9 +350,7 @@ To do:
253350
}
254351
}
255352
}
256-
var str = ab2str(dataview.buffer);
257-
log(3, "Received "+JSON.stringify(str));
258-
connection.emit('data', str);
353+
connection.rxDataHandler(ab2str(dataview.buffer));
259354
});
260355
return rxCharacteristic.startNotifications();
261356
}).then(function() {
@@ -306,16 +401,14 @@ To do:
306401
navigator.serial.requestPort({}).then(function(port) {
307402
log(1, "Connecting to serial port");
308403
serialPort = port;
309-
return port.open({ baudRate: baud });
404+
return port.open({ baudRate: uart.baud });
310405
}).then(function () {
311406
function readLoop() {
312407
var reader = serialPort.readable.getReader();
313408
reader.read().then(function ({ value, done }) {
314409
reader.releaseLock();
315410
if (value) {
316-
var str = ab2str(value.buffer);
317-
log(3, "Received "+JSON.stringify(str));
318-
connection.emit('data', str);
411+
connection.rxDataHandler(ab2str(value.buffer));
319412
}
320413
if (done) {
321414
disconnected();
@@ -344,7 +437,9 @@ To do:
344437
connection.write = function(data, callback) {
345438
var writer = serialPort.writable.getWriter();
346439
// TODO: progress?
440+
log(2, "Sending "+ JSON.stringify(data));
347441
writer.write(str2ab(data)).then(function() {
442+
log(3, "Sent");
348443
callback();
349444
}).catch(function(error) {
350445
log(0,'SEND ERROR: ' + error);
@@ -412,13 +507,7 @@ To do:
412507
// ======================================================================
413508
var connection;
414509
function connect(callback) {
415-
var connection = {
416-
on : function(evt,cb) { this["on"+evt]=cb; },
417-
emit : function(evt,data) { if (this["on"+evt]) this["on"+evt](data); },
418-
isOpen : false,
419-
isOpening : true,
420-
txInProgress : false
421-
};
510+
connection = new Connection();
422511

423512
if (uart.ports.length==0) {
424513
console.error(`UART: No ports in uart.ports`);
@@ -579,7 +668,7 @@ To do:
579668
// ----------------------------------------------------------
580669

581670
var uart = {
582-
version : "1.01",
671+
version : "1.02",
583672
/// Are we writing debug information? 0 is no, 1 is some, 2 is more, 3 is all.
584673
debug : 1,
585674
/// Should we use flow control? Default is true
@@ -636,7 +725,10 @@ To do:
636725
callback();
637726
}
638727
});
639-
}
728+
},
729+
/* This is the list of 'drivers' for Web Bluetooth/Web Serial. It's possible to add to these
730+
and also change 'ports' in order to add your own custom endpoints (eg WebSockets) */
731+
endpoints : endpoints
640732
};
641733
return uart;
642734
}));

0 commit comments

Comments
 (0)