Serial Connection
the Serial Connection handles input from serial ports (interfaces based on microcontroller boards)
Note: This is based on Webserial API which currently is only supported in Chrome.
SerialPort
This class handles opening and closing of the port connection itself. It is based on a wrapper by @tigoe.
let self;
export class SerialPort {
constructor() {
// if webserial doesn't exist, return false:
if (!navigator.serial) {
alert("WebSerial is not enabled in this browser");
return false;
}
this.autoOpen = true;
self = this;
// basic WebSerial properties:
this.port;
this.reader;
this.serialReadPromise;
this.dataBuffer = "";
this.latestValue = 0;
this.incoming = {
data: null,
};
this.dataEvent = new CustomEvent("data", {
detail: this.incoming,
bubbles: true,
});
this.disconnectEvent = new CustomEvent("disconnect", {
detail: this.incoming,
bubbles: true,
});
navigator.serial.addEventListener("connect", () => this.serialConnect());
navigator.serial.addEventListener("disconnect", () =>
this.serialDisconnect()
);
this.on = (message, handler) => {
parent.addEventListener(message, handler);
};
}
async openPort(baudRate, thisPort) {
try {
if (thisPort == null) {
this.port = await navigator.serial.requestPort();
} else {
this.port = thisPort;
}
// set port settings and open it:
await this.port.open({ baudRate: baudRate });
console.log(this.port.readable);
// start the listenForSerial function:
this.serialReadPromise = this.listenForSerial();
return true;
} catch (err) {
// if there's an error opening the port:
console.error("There was an error opening the serial port:", err);
return false;
}
}
async closePort() {
if (this.port) {
// stop the reader, so you can close the port:
this.reader.cancel();
// wait for the listenForSerial function to stop:
await this.serialReadPromise;
// close the serial port itself:
await this.port.close();
// clear the port variable:
this.port = null;
}
}
async sendSerial(data) {
// if there's no port open, skip this function:
if (!this.port) return;
// if the port's writable:
if (this.port.writable) {
// initialize the writer:
const writer = this.port.writable.getWriter();
// convert the data to be sent to an array:
var output = new TextEncoder().encode(data);
// send it, then release the writer:
writer.write(output).then(writer.releaseLock());
}
}
async listenForSerial() {
// if there's no serial port, return:
if (!this.port) return;
// while the port is open:
while (this.port.readable) {
// initialize the reader:
this.reader = this.port.readable.getReader();
try {
// read incoming serial buffer:
const { value, done } = await this.reader.read();
if (value) {
// convert the input to a text string:
const decodedData = new TextDecoder().decode(value);
// add to buffer
this.dataBuffer += decodedData;
const lines = this.dataBuffer.split("\n");
//empty buffer
if (lines.length > 5) this.dataBuffer = lines.pop();
// get last full value
this.incoming.data = lines[lines.length - 2];
// fire the event:
parent.dispatchEvent(this.dataEvent);
}
if (done) {
break;
}
} catch (error) {
// if there's an error reading the port:
console.log(error);
} finally {
this.reader.releaseLock();
}
}
}
// this event occurs every time a new serial device
// connects via USB:
serialConnect(event) {
console.log(event.target);
self.openPort(event.target);
}
// this event occurs every time a new serial device
// disconnects via USB:
serialDisconnect(event) {
parent.dispatchEvent(this.disconnectEvent);
// console.log(event.target);
this.closePort();
}
}
SerialInput
This a Middlelayer wich controls the SerialPort and recieves an processes its input data.
import { SerialPort } from "./SerialPort";
export class SerialInput {
constructor(baudRate) {
if ("serial" in navigator) {
console.log("serial supported");
} else {
console.log("serial not supported");
}
this.baudRate = baudRate;
this.connected = false;
this.serialData;
this.serial = new SerialPort();
console.log(this.serial);
this.portButton = document.getElementById("serial-button");
if (navigator.serial) {
this.serial.on("data", (e) => this.serialRead(e));
this.serial.on("disconnect", (e) => this.disconnect(e));
this.portButton.addEventListener("click", () => this.openClosePort());
}
}
async openClosePort() {
// if port is open, close it; if closed, open it:
console.log(this.serial);
if (this.serial.port) {
await this.serial.closePort();
this.connected = false;
this.portButton.innerHTML = "open port";
this.portButton.classList.remove("active");
} else {
const connected = await this.serial.openPort(this.baudRate);
if (connected) {
this.connected = true;
this.portButton.innerHTML = "close port";
this.portButton.classList.add("active");
}
}
}
serialRead(event) {
this.serialData = event.detail.data;
}
disconnect(event) {
console.log("disconnect");
console.log(event);
this.connected = false;
this.portButton.innerHTML = "Open Port";
}
}