Web Serial API with RxJS - Two-Way Reactive Communication between Browser and Serial Hardware
Tags: tutorial, webdev, javascript, 100daysofcode
Published: 2021-01-30 18:25:00

---
title: Web Serial API with RxJS - Two-Way Reactive Communication between Browser and Serial Hardware
published: true
description: The newly launched Web Serial API allows the browser to connect to devices across a serial connection - learn how to combine it with RxJS to make a reactive app
tags: tutorial, webdev, javascript, 100daysofcode
canonical_url: https://tane.dev/2021/01/web-serial-api-with-rxjs-two-way-reactive-communication-between-browser-and-serial-hardware/
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/oiwa5kv1a0xk1mzvzs1x.jpg
---

- [Demo Link](https://rxjs-from-web-serial.stackblitz.io/)
- [Demo Source](https://stackblitz.com/edit/rxjs-from-web-serial?file=index.ts)

Version 89 of Chrome and Edge browsers have released the [Web Serial API](https://reillyeon.github.io/serial/) unflagged which means as a user it's now available for general use rather than being locked behind experimental flags (if you're on an earlier version you can enable **Experimental Web Platform features** in `chrome://flags`)

The API allows for communication between the browser and supported serial hardware such as [Arduino](https://www.arduino.cc/) or [RaspberryPi](https://www.raspberrypi.org/) over USB Serial.

If you don't have any hardware to connect to, you can use Bluetooth Serial - provided your computer has a Bluetooth module. Connect your mobile device to it and use the appropriate software. For Android there is [Serial Bluetooth Terminal](https://play.google.com/store/apps/details?id=de.kai_morich.serial_bluetooth_terminal&hl=en&gl=US) and iOS [BLE to Serial Terminal](https://apps.apple.com/us/app/ble-to-serial-terminal/id1238004134).

## Connecting to a serial device

To request access to a device, a call needs to be made to `navigator.serial.requestPort` - This call **must** be made after a user gesture such as a button click - you cannot just call `requestPort` from your code without some kind of user interaction as this will cause a security violation. You also must call it from a location that does not have s policy set up to disable this (you can see this in the demo above - if you try run it in the editor it won't work due to the `<iframe>` not having the correct policy).

You may also need to install the [w3c-web-serial types](https://www.npmjs.com/package/@types/w3c-web-serial) in your project to make sure you have the available types on the `navigator` object and global types such as `SerialPort`.

To get a port, call `navigator.serial.requestPort` inside the handler - it will return a Promise that contains the port object - you can also wrap it in a `try/catch` to handle when the user cancels device selection.

```ts
const startButton = document.getElementById("start");

startButton.addEventListener("click", async event => {
  try {
    const port = await navigator.serial.requestPort();
    // We can now access the serial device by opening it
    // e.g. await port.open({baudRate: 9600})
  } catch (e) {
    // The prompt has been dismissed without selecting a device.
  }
});
```

The port object once created must be called with the `open` method - the only required property of the options is the `baudRate` which is the maximum bits-per-second transferred but there are [other options](https://reillyeon.github.io/serial/#open-method) based on the requirements of the device.

Once opened the port can return a [ReadableStream](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) and [WritableStream](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream) which allows data to be passed to and from the device.

## Our RxJS Operator

To turn this into an RxJS operator we'll consume the port and set up the functionality to both read and write to the serial bus. You can [read the full source code](https://github.com/rxjs-ninja/rxjs-ninja/blob/main/libs/rxjs/utility/src/lib/from-web-serial.ts) to see how the final Observable was created, but we'll cover the important sections below.

## Reading from the Serial Bus

Once connected, the serial device can start sending data to us - as it's a `ReadableStream` the result will be a `UInt8Array`.

Here we'll set up an iterable reader for our stream - while the result is not `done` and the port is still readable, we'll continue to read the source and emit it to the subscriber of the Observable. If the reader has completed, or the port has been closed we'll end this iteration.

```ts
await port.open({baudRate: 9600});

const process = async (
  result: ReadableStreamReadResult<Uint8Array>
): Promise<ReadableStreamReadResult<Uint8Array>> => {
  subscriber.next(result.value);
  return !result.done || !port.readable
    ? reader.read().then(process)
    : Promise.resolve(result);
};

if (port.readable) {
  reader = port.readable.getReader();
  reader.read().then(process);
}
```

As the output of our Observable is a `Uint8Array`. Depending on your needs you can decode this to the format you need, but in most cases, it will be text content - here we can use a [TextDecoder](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder) to get the value:

```ts
const decoder = new TextDecoder("utf-8");

fromWebSerial(port).pipe(
  tap(value => {
    // Value is a UInt8Array, we can append to a element by decoding it
    outputEl.innerHTML = decoder.decode(value)
  })
).subscribe()
```

## Writing to the Serial Bus

The API also allows for writing data to the device, here we can use another `Observable` that emits a string and provide it to our function as a source, then we can hook it up to the ports `WritableStream`.

Instead of directly writing, we will create a [TextEncoderStream](https://developer.mozilla.org/en-US/docs/Web/API/TransformStream) - this allows us to create a new internal writer that we have more control over - it contains both a reader and writer we use this to connect our
sources.

The reader from our encoder will be piped to the ports `WritableStream`, and the writer passed to [toWritableStream](https://github.com/rxjs-ninja/rxjs-ninja/blob/main/libs/rxjs/utility/src/lib/to-writable-stream.ts) which connects the `Observable` to the writer:

```ts
if (writerSource && port.writable) {
  const encoder = new TextEncoderStream();
  writerEnd = encoder.readable.pipeTo(port.writable);
  const outputStream = encoder.writable;

  writer = outputStream.getWriter();
  writerSource.pipe(toWritableStream(writer, signal)).subscribe();
}
```

Now we can pass the `Observable` and use it to emit our values:

```ts
const emitter$ = new Subject<string>();

fromWebSerial(port, emitter$.asObservable()).subscribe();

emitter$.next('Hello There!');
```

## Creating a Serial Chat App

Now we can read from, and write to, our hardware device the [possibilities are endless](https://github.com/svendahlstrand/web-serial-api) with what we can do - provided the hardware supports it.

For this tutorial, I build a [very basic chat app](https://rxjs-from-web-serial.stackblitz.io/) - using the Bluetooth Serial applications mentioned above you can use it to send and receive text data between devices.

![A screenshot of a web serial mobile app](https://tane.dev/images/web-serial-mobile.jpg)
![A screenshot of a web serial browser app](https://tane.dev/images/web-serial-browser.png)

In the [example code](https://stackblitz.com/edit/rxjs-from-web-serial?file=index.ts) I've set up a button to enable our port request - you should see a popup with a list of devices available for you to use. After connecting a basic chat interface will show up - type in some text and check out your device software - you should see the same message there, and you can then send a message back to the browser.

Hopefully, you've found this tutorial useful and if you do build something with this I'd love to hear about it!

## A collection of pre-built operators and Observables for your projects

![The RxJS Logo, a Ninja jumping over a moon](https://raw.githubusercontent.com/rxjs-ninja/rxjs-ninja/main/assets/logo.png)

[RxJS Ninja](https://rxjs.ninja) - is a collection of over 130 operators for working with various types of data (such as [arrays](https://rxjs.ninja/modules/array.html), [numbers](https://rxjs.ninja/modules/number.html)) and [streams](https://rxjs.ninja/modules/utility.html#Streams) allowing for modifying, filtering and querying the data.

Still in active development, you might find useful operators that provide clearer intent for your RxJS code.

You can check out the source code on [GitHub] (https://github.com/rxjs-ninja/rxjs-ninja).