← Back to all guides

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";
  }
}