Serial conversations in JavaScript
/Well, I've put it off as long as I could. I've delved into SLIP and DTR lines and allsorts, but today I have to get my head around the way that the JavaScript usb serial library sends and receives data. Way back on day 2 we discovered how a JavaScript application can get a reference to an object that represents a connection to a serial port, now I actually have to use it to move data.
try {
this.port = await navigator.serial.requestPort();
await this.port.open({ baudRate: 115200, bufferSize: 10000 });
}
catch (error) {
return { worked: false, message: `Serial port open failed:${error.message}` };
}
For a refresher, this is what you do to open a port. At the end of this, if the user selects a port properly, you end up with a port object (in a member variable called this.port). We can ask this object to give us reader and writer objects (if it is able to) which we can then use to read and write serial data. The data is always sent and recieved in UInt8Array objects.
Sending serial data
Sending serial data is easy.
async sendBytes(bytes) {
let buffer = "";
let limit = bytes.length<10?bytes.length:10;
for(let i=0;i<limit;i++){
buffer = buffer + bytes[i].toString(10)+":"+bytes[i].toString(16)+" ";
}
console.log(`Sending:${buffer}...`);
const writer = this.port.writable.getWriter();
await writer.write(bytes);
writer.releaseLock();
}
This is my sendBytes method. You give it a UInt8Array and it sends it out. It also prints out the first 10 bytes of the array on the console so you can take a quick peek at what is being sent. Note that it uses the writable property of the port which provides a method called getWriter() that delivers a write object that I called writer. I use this to write the bytes and then release the lock on this writer, effectivly discarding it and making it possible for other code to grab a write and write something. Note also that all of this is awaited so that the writing of the data can take place while other parts of JavaScript keep going.
Receiving serial data
Receiving serial data in JavaScript is hard. The main reason for this is the way that on the whole JavaScript takes a very dim view of programs that hang around and wait for stuff. Python doesn't care so much. In pytool, when the program wants to read something from the serial port it just calls the read method on the serial port and waits for a response. Either something will arrive (good) or nothing will arrive (bad). If nothing arrives the serial port will time out at some point in the future and tell the program that nothing was received. The program can note this and give the remote system another prod to see if it is awake or display a message or whatnot. In the case of JavaScript it looks quite easy to read data:
this.reader = this.port.readable.getReader();
const { value, done } = await this.reader.read();
The tricky bit is not the way that reader returns a result. It returns two items:
- value - the buffer of data you were waiting for
- done - a flag that is set to true if the reader is finished (perhaps it has been closed or the user has unplugged their device)
The syntax of the call above means that when the reader has finished it will return those two values which my program can then test. If done is true I shut everything down and go home. Otherwise I take a look at what is in value, which will be a UInt8Array full of bytes that have been received from the device. The tricky bit is that the read operation will not return untill it has received something. There is also added trickiness, in that if anything bad happens to the data stream the read operation will throw an exception that I need to handle. Bad things that might happen include remote devices sending badly formed serial data, just the sort of thing that happens when you reset something.
So, my code must handle the fact that read may never return (the remote device might not have detected the right baud rate after reset) and the fact that read may explode with an exception, at which point I need to tidy everything up. Tricky indeed.
I quite like a cliff hanger ending. And I've got other things to do today as well as code. So I'll leave it at that for now.