Skip to content

Commit 48bd1db

Browse files
committedMar 18, 2025
Use UART.js for comms if available
1 parent 25e82e9 commit 48bd1db

File tree

5 files changed

+184
-117
lines changed

5 files changed

+184
-117
lines changed
 

‎js/comms.js

+180-105
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,131 @@
11
//Puck.debug=3;
2-
console.log("=============================================")
3-
console.log("Type 'Puck.debug=3' for full BLE debug info")
4-
console.log("=============================================")
2+
console.log("================================================")
3+
console.log("Type 'Comms.debug()' to enable Comms debug info")
4+
console.log("================================================")
5+
6+
/*
7+
8+
Puck = {
9+
/// Are we writing debug information? 0 is no, 1 is some, 2 is more, 3 is all.
10+
debug : UART.debug,
11+
/// Used internally to write log information - you can replace this with your own function
12+
log : function(level, s) { if (level <= this.debug) console.log("<UART> "+s)},
13+
/// Called with the current send progress or undefined when done - you can replace this with your own function
14+
writeProgress : function() {},
15+
connect : UART.connect,
16+
write : UART.write,
17+
eval : UART.eval,
18+
isConnected : () => UART.isConnected() && UART.getConnection().isOpen,
19+
getConnection : UART.getConnection,
20+
close : UART.close,
21+
RECEIVED_NOT_IN_DATA_HANDLER : true, // hack for Comms.on
22+
};
23+
// FIXME: disconnected event?
24+
*/
25+
26+
/// Add progress handler so we get nice upload progress shown
27+
{
28+
let COMMS = (typeof UART !== undefined)?UART:Puck;
29+
COMMS.writeProgress = function(charsSent, charsTotal) {
30+
if (charsSent===undefined || charsTotal<10) {
31+
Progress.hide();
32+
return;
33+
}
34+
let percent = Math.round(charsSent*100/charsTotal);
35+
Progress.show({percent: percent});
36+
};
37+
}
538

6-
// TODO: Add Comms.write/eval which return promises, and move over to using those
7-
// FIXME: use UART lib so that we handle errors properly
839
const Comms = {
9-
// Write the given data, returns a promise
10-
write : (data) => new Promise((resolve,reject) => {
40+
// ================================================================================
41+
// Low Level Comms
42+
/// enable debug print statements
43+
debug : () => {
44+
if (typeof UART !== undefined)
45+
UART.debug = 3;
46+
else
47+
Puck.debug = 3;
48+
},
49+
50+
/** Write the given data, returns a promise containing the data received immediately after sending the command
51+
options = {
52+
waitNewLine : bool // wait for a newline (rather than just 300ms of inactivity)
53+
}
54+
*/
55+
write : (data, options) => {
1156
if (data===undefined) throw new Error("Comms.write(undefined) called!")
12-
return Puck.write(data,function(result) {
13-
if (result===null) return reject("");
14-
resolve(result);
15-
});
16-
}),
57+
options = options||{};
58+
if (typeof UART !== undefined) { // New method
59+
return UART.write(data, undefined, !!options.waitNewLine);
60+
} else { // Old method
61+
return new Promise((resolve,reject) =>
62+
Puck.write(data, result => {
63+
if (result===null) return reject("");
64+
resolve(result);
65+
}, !!options.waitNewLine)
66+
);
67+
}
68+
},
69+
/// Evaluate the given expression, return the result as a promise
70+
eval : (expr) => {
71+
if (expr===undefined) throw new Error("Comms.eval(undefined) called!")
72+
if (typeof UART === undefined) { // New method
73+
return UART.eval(expr);
74+
} else { // Old method
75+
return new Promise((resolve,reject) =>
76+
Puck.eval(expr, result => {
77+
if (result===null) return reject("");
78+
resolve(result);
79+
})
80+
);
81+
}
82+
},
83+
/// Return true if we're connected, false if not
84+
isConnected : () => {
85+
if (typeof UART !== undefined) { // New method
86+
return UART.isConnected();
87+
} else { // Old method
88+
return Puck.isConnected();
89+
}
90+
},
91+
/// Get the currently active connection object
92+
getConnection : () => {
93+
if (typeof UART !== undefined) { // New method
94+
return UART.getConnection();
95+
} else { // Old method
96+
return Puck.getConnection();
97+
}
98+
},
99+
// Faking EventEmitter
100+
handlers : {},
101+
on : function(id, callback) { // calling with callback=undefined will disable
102+
if (id!="data") throw new Error("Only data callback is supported");
103+
var connection = Puck.getConnection();
104+
if (!connection) throw new Error("No active connection");
105+
/* This is a bit of a mess - the Puck.js lib only supports one callback with `.on`. If you
106+
do Puck.getConnection().on('data') then it blows away the default one which is used for
107+
.write/.eval and you can't get it back unless you reconnect. So rather than trying to fix the
108+
Puck lib we just copy in the default handler here. */
109+
if (callback===undefined) {
110+
connection.on("data", function(d) { // the default handler
111+
if (!Puck.RECEIVED_NOT_IN_DATA_HANDLER) {
112+
connection.received += d;
113+
connection.hadData = true;
114+
}
115+
if (connection.cb) connection.cb(d);
116+
});
117+
} else {
118+
connection.on("data", function(d) {
119+
if (!Puck.RECEIVED_NOT_IN_DATA_HANDLER) {
120+
connection.received += d;
121+
connection.hadData = true;
122+
}
123+
if (connection.cb) connection.cb(d);
124+
callback(d);
125+
});
126+
}
127+
},
128+
// ================================================================================
17129
// Show a message on the screen (if available)
18130
showMessage : (txt) => {
19131
console.log(`<COMMS> showMessage ${JSON.stringify(txt)}`);
@@ -37,29 +149,32 @@ const Comms = {
37149
}
38150
},
39151
// Reset the device, if opt=="wipe" erase any saved code
40-
reset : (opt) => new Promise((resolve,reject) => {
152+
reset : (opt) => {
41153
let tries = 8;
42-
if (Const.NO_RESET) return resolve();
154+
if (Const.NO_RESET) return Promise.resolve();
43155
console.log("<COMMS> reset");
44-
Puck.write(`\x03\x10reset(${opt=="wipe"?"1":""});\n`,function rstHandler(result) {
156+
157+
function rstHandler(result) {
45158
console.log("<COMMS> reset: got "+JSON.stringify(result));
46-
if (result===null) return reject("Connection failed");
159+
if (result===null) return Promise.reject("Connection failed");
47160
if (result=="" && (tries-- > 0)) {
48161
console.log(`<COMMS> reset: no response. waiting ${tries}...`);
49-
Puck.write("\x03",rstHandler);
162+
return Comms.write("\x03").then(rstHandler);
50163
} else if (result.endsWith("debug>")) {
51164
console.log(`<COMMS> reset: watch in debug mode, interrupting...`);
52-
Puck.write("\x03",rstHandler);
165+
return Comms.write("\x03").then(rstHandler);
53166
} else {
54167
console.log(`<COMMS> reset: rebooted - sending commands to clear out any boot code`);
55168
// see https://github.com/espruino/BangleApps/issues/1759
56-
Puck.write("\x10clearInterval();clearWatch();global.Bangle&&Bangle.removeAllListeners();E.removeAllListeners();global.NRF&&NRF.removeAllListeners();\n",function() {
169+
return Comms.write("\x10clearInterval();clearWatch();global.Bangle&&Bangle.removeAllListeners();E.removeAllListeners();global.NRF&&NRF.removeAllListeners();\n").then(function() {
57170
console.log(`<COMMS> reset: complete.`);
58-
setTimeout(resolve,250);
171+
return new Promise(resolve => setTimeout(resolve, 250))
59172
});
60173
}
61-
});
62-
}),
174+
}
175+
176+
return Comms.write(`\x03\x10reset(${opt=="wipe"?"1":""});\n`).then(rstHandler);
177+
},
63178
// Upload a list of newline-separated commands that start with \x10
64179
// You should call Comms.write("\x10"+Comms.getProgressCmd()+"\n")) first
65180
uploadCommandList : (cmds, currentBytes, maxBytes) => {
@@ -103,13 +218,12 @@ const Comms = {
103218
ignore = true;
104219
}
105220
if (ignore) {
106-
/* Here we have to poke around inside the Puck.js library internals. Basically
221+
/* Here we have to poke around inside the Comms library internals. Basically
107222
it just gave us the first line in the input buffer, but there may have been more.
108223
We take the next line (or undefined) and call ourselves again to handle that.
109224
Just in case, delay a little to give our previous command time to finish.*/
110-
111225
setTimeout(function() {
112-
let connection = Puck.getConnection();
226+
let connection = Comms.getConnection();
113227
let newLineIdx = connection.received.indexOf("\n");
114228
let l = undefined;
115229
if (newLineIdx>=0) {
@@ -126,7 +240,7 @@ const Comms = {
126240
}
127241
// Actually write the command with a 'print OK' at the end, and use responseHandler
128242
// to deal with the response. If OK we call uploadCmd to upload the next block
129-
Puck.write(`${cmd};${Comms.getProgressCmd(currentBytes / maxBytes)}${Const.CONNECTION_DEVICE}.println("OK")\n`,responseHandler, true /* wait for a newline*/);
243+
return Comms.write(`${cmd};${Comms.getProgressCmd(currentBytes / maxBytes)}${Const.CONNECTION_DEVICE}.println("OK")\n`,{waitNewLine:true}).then(responseHandler);
130244
}
131245

132246
uploadCmd()
@@ -201,33 +315,30 @@ const Comms = {
201315
// Get Device ID, version, storage stats, and a JSON list of installed apps
202316
getDeviceInfo : (noReset) => {
203317
Progress.show({title:`Getting device info...`,sticky:true});
204-
return new Promise((resolve,reject) => {
205-
Puck.write("\x03",(result) => {
206-
if (result===null) {
207-
Progress.hide({sticky:true});
208-
return reject("");
209-
}
318+
return Comms.write("\x03").then(result => {
319+
if (result===null) {
320+
Progress.hide({sticky:true});
321+
return Promise.reject("No response");
322+
}
210323

211-
let interrupts = 0;
212-
const checkCtrlC = result => {
213-
if (result.endsWith("debug>")) {
214-
if (interrupts > 3) {
215-
console.log("<COMMS> can't interrupt watch out of debug mode, giving up.", result);
216-
reject("");
217-
return;
218-
}
219-
console.log("<COMMS> watch was in debug mode, interrupting.", result);
220-
// we got a debug prompt - we interrupted the watch while JS was executing
221-
// so we're in debug mode, issue another ctrl-c to bump the watch out of it
222-
Puck.write("\x03", checkCtrlC);
223-
interrupts++;
224-
} else {
225-
resolve(result);
324+
let interrupts = 0;
325+
const checkCtrlC = result => {
326+
if (result.endsWith("debug>")) {
327+
if (interrupts > 3) {
328+
console.log("<COMMS> can't interrupt watch out of debug mode, giving up.", result);
329+
return Promise.reject("Stuck in debug mode");
226330
}
227-
};
331+
console.log("<COMMS> watch was in debug mode, interrupting.", result);
332+
// we got a debug prompt - we interrupted the watch while JS was executing
333+
// so we're in debug mode, issue another ctrl-c to bump the watch out of it
334+
return Comms.write("\x03").then(checkCtrlC);
335+
interrupts++;
336+
} else {
337+
return result;
338+
}
339+
};
228340

229-
checkCtrlC(result);
230-
});
341+
return checkCtrlC(result);
231342
}).
232343
then((result) => new Promise((resolve, reject) => {
233344
console.log("<COMMS> Ctrl-C gave",JSON.stringify(result));
@@ -250,10 +361,10 @@ const Comms = {
250361
cmd = `\x10${device}.print("[");if (!require("fs").statSync("APPINFO"))require("fs").mkdir("APPINFO");require("fs").readdirSync("APPINFO").forEach(f=>{var j=JSON.parse(require("fs").readFileSync("APPINFO/"+f))||"{}";${device}.print(JSON.stringify({id:f.slice(0,-5),version:j.version,files:j.files,data:j.data,type:j.type})+",")});${device}.println(${finalJS})\n`;
251362
else // the default, files in Storage
252363
cmd = `\x10${device}.print("[");require("Storage").list(/\\.info$/).forEach(f=>{var j=require("Storage").readJSON(f,1)||{};${device}.print(JSON.stringify({id:f.slice(0,-5),version:j.version,files:j.files,data:j.data,type:j.type})+",")});${device}.println(${finalJS})\n`;
253-
Puck.write(cmd, (appListStr,err) => {
364+
Comms.write(cmd, {waitNewLine:true}).then(appListStr => {
254365
Progress.hide({sticky:true});
255366
if (!appListStr) appListStr="";
256-
var connection = Puck.getConnection();
367+
var connection = Comms.getConnection();
257368
if (connection) {
258369
appListStr = appListStr+"\n"+connection.received; // add *any* information we have received so far, including what was returned
259370
connection.received = ""; // clear received data just in case
@@ -378,7 +489,7 @@ const Comms = {
378489
if (result=="" && (timeout--)) {
379490
console.log("<COMMS> removeAllApps: no result - waiting some more ("+timeout+").");
380491
// send space and delete - so it's something, but it should just cancel out
381-
Puck.write(" \u0008", handleResult, true /* wait for newline */);
492+
Comms.write(" \u0008", {waitNewLine:true}).then(handleResult);
382493
} else {
383494
Progress.hide({sticky:true});
384495
if (!result || result.trim()!="OK") {
@@ -390,7 +501,7 @@ const Comms = {
390501
}
391502
// Use write with newline here so we wait for it to finish
392503
let cmd = `\x10E.showMessage("Erasing...");require("Storage").eraseAll();${Const.CONNECTION_DEVICE}.println("OK");reset()\n`;
393-
Puck.write(cmd, handleResult, true /* wait for newline */);
504+
Comms.write(cmd,{waitNewLine:true}).then(handleResult);
394505
}).then(() => new Promise(resolve => {
395506
console.log("<COMMS> removeAllApps: Erase complete, waiting 500ms for 'reset()'");
396507
setTimeout(resolve, 500);
@@ -416,25 +527,20 @@ const Comms = {
416527
let cmd = "load();\n";
417528
return Comms.write(cmd);
418529
},
419-
// Check if we're connected
420-
isConnected: () => {
421-
return !!Puck.getConnection();
422-
},
423530
// Force a disconnect from the device
424531
disconnectDevice: () => {
425-
let connection = Puck.getConnection();
532+
let connection = Comms.getConnection();
426533
if (!connection) return;
427534
connection.close();
428535
},
429536
// call back when the connection state changes
430537
watchConnectionChange : cb => {
431-
let connected = Puck.isConnected();
538+
let connected = Comms.isConnected();
432539

433540
//TODO Switch to an event listener when Puck will support it
434541
let interval = setInterval(() => {
435-
if (connected === Puck.isConnected()) return;
436-
437-
connected = Puck.isConnected();
542+
if (connected === Comms.isConnected()) return;
543+
connected = Comms.isConnected();
438544
cb(connected);
439545
}, 1000);
440546

@@ -446,18 +552,16 @@ const Comms = {
446552
// List all files on the device.
447553
// options can be undefined, or {sf:true} for only storage files, or {sf:false} for only normal files
448554
listFiles : (options) => {
449-
return new Promise((resolve,reject) => {
450-
Puck.write("\x03",(result) => {
451-
if (result===null) return reject("");
452-
let args = "";
453-
if (options && options.sf!==undefined) args=`undefined,{sf:${options.sf}}`;
454-
//use encodeURIComponent to serialize octal sequence of append files
455-
Puck.eval(`require("Storage").list(${args}).map(encodeURIComponent)`, (files,err) => {
456-
if (files===null) return reject(err || "");
457-
files = files.map(decodeURIComponent);
458-
console.log("<COMMS> listFiles", files);
459-
resolve(files);
460-
});
555+
return Comms.write(" \x03").then(result => {
556+
if (result===null) return Promise.reject("Ctrl-C failed");
557+
let args = "";
558+
if (options && options.sf!==undefined) args=`undefined,{sf:${options.sf}}`;
559+
//use encodeURIComponent to serialize octal sequence of append files
560+
return Comms.eval(`require("Storage").list(${args}).map(encodeURIComponent)`, (files,err) => {
561+
if (files===null) return Promise.reject(err || "");
562+
files = files.map(decodeURIComponent);
563+
console.log("<COMMS> listFiles", files);
564+
return files;
461565
});
462566
});
463567
},
@@ -467,7 +571,7 @@ const Comms = {
467571
// Use "\xFF" to signal end of file (can't occur in StorageFiles anyway)
468572
let fileContent = "";
469573
let fileSize = undefined;
470-
let connection = Puck.getConnection();
574+
let connection = Comms.getConnection();
471575
connection.received = "";
472576
connection.cb = function(d) {
473577
let finished = false;
@@ -535,33 +639,4 @@ ${Const.CONNECTION_DEVICE}.print("\\xFF");
535639
Comms.uploadCommandList(cmds, 0, cmds.length)
536640
);
537641
},
538-
// Faking EventEmitter
539-
handlers : {},
540-
on : function(id, callback) { // calling with callback=undefined will disable
541-
if (id!="data") throw new Error("Only data callback is supported");
542-
var connection = Puck.getConnection();
543-
if (!connection) throw new Error("No active connection");
544-
/* This is a bit of a mess - the Puck.js lib only supports one callback with `.on`. If you
545-
do Puck.getConnection().on('data') then it blows away the default one which is used for
546-
.write/.eval and you can't get it back unless you reconnect. So rather than trying to fix the
547-
Puck lib we just copy in the default handler here. */
548-
if (callback===undefined) {
549-
connection.on("data", function(d) { // the default handler
550-
if (!Puck.RECEIVED_NOT_IN_DATA_HANDLER) {
551-
connection.received += d;
552-
connection.hadData = true;
553-
}
554-
if (connection.cb) connection.cb(d);
555-
});
556-
} else {
557-
connection.on("data", function(d) {
558-
if (!Puck.RECEIVED_NOT_IN_DATA_HANDLER) {
559-
connection.received += d;
560-
connection.hadData = true;
561-
}
562-
if (connection.cb) connection.cb(d);
563-
callback(d);
564-
});
565-
}
566-
}
567642
};

0 commit comments

Comments
 (0)