Source

devices/web-serial-device.js

/**
 * Small convenience class for easily connecting to a serial device from a browser
 * using the [Web Serial API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Web_Serial_API}.
 */
class WebSerialDevice {
  connected = false;

  _baudRate;

  _output = null;

  _input = null;

  _serial = null;

  _outputClosedPromise = null;

  _inputClosedPromise = null;

  /**
   * Create a new WebSerialDevice.
   *
   * @example
   * const device = new WebSerialDevice();
   *
   * @param {Number} [baudRate=115200] - Communication rate.
   * @param {Serial} [serial=navigator.serial] - Web Serial API interface.
   */
  constructor (baudRate = 115200, serial = null) {
    this._baudRate = baudRate;
    this._serial = serial || (globalThis.navigator ? globalThis.navigator.serial : null);
  }

  /**
   * Opens a request to connect to a serial device.
   *
   * @example
   * const device = new WebSerialDevice();
   * device.requestConnection().then(() => {
   *   // ...
   * });
   *
   * @returns {Promise} a promise that resolves when the device is connected, and rejects if the device failed to connect.
   */
  async requestConnection () {
    const port = await this._serial.requestPort();

    await port.open({ baudRate: this._baudRate });

    const encoder = new TextEncoderStream();
    this._outputClosedPromise = encoder.readable.pipeTo(port.writable);

    const decoder = new TextDecoderStream();
    this._inputClosedPromise = port.readable.pipeTo(decoder.writable);

    this._output = encoder.writable.getWriter();
    this._input = decoder.readable.getReader();
    this._port = port;
    this.connected = true;

    const disconnectListener = (event) => {
      if (event.target === port) {
        this.connected = false;

        this._serial.removeEventListener('disconnect', disconnectListener);
      }
    };

    this._serial.addEventListener('disconnect', disconnectListener);

    // Add a small delay so that the OSR has time to "boot" or w/e...
    return new Promise((resolve) => {
      setTimeout(resolve, 2000);
    });
  }

  async disconnect () {
    this.connected = false;

    this._input.cancel();
    this._output.close();

    await this._inputClosedPromise.catch(() => { /* Ignore the error */ });
    await this._outputClosedPromise;
    await this._port.close();

    this._output = null;
    this._input = null;
    this._inputClosedPromise = null;
    this._outputClosedPromise = null;
  }

  /**
   * Write output to the device.
   *
   * @param {String} output - string to send to the device.
   */
  write (output) {
    if (this.connected) {
      this._output.write(output);
    } else {
      throw new Error('No device connected.');
    }
  }
}

export default WebSerialDevice;