L2: Web Serial
Table of Contents
- Why Web Serial?
- The Web Serial API
- Our Web Serial class
- Let’s make stuff
- Activity
- Resources
- Next Lesson
In our previous lesson we dove deeper into asynchronous serial communication, Arduino’s Serial functionality, and how we can write computer programs, like serial_demo.py, to bidirectionally communicate with Arduino.
In this lesson, we will apply our growing serial knowledge to a new context: the web! Now, it may seem a bit weird to use a web browser to communicate with a locally connected device. But, if you think about it, we actually do this all the time using video chat in our web browsers: the w3c MediaDevices API provides regulated access to media input devices like cameras and microphones.
More recently, the w3c spec’d out an API for securely providing access to Universal Serial Bus (USB) devices from web pages called WebUSB. Just like the MediaDevices API, security and privacy is paramount. Web pages requesting access to local USB devices must seek explicit user permission, which is handled through the web browser. Chrome added support for WebUSB in late 2017.
However, WebUSB did not include support for USB-to-serial devices like Arduino. Thus, the Web Serial API was proposed and launched in Chrome 89 (in March 2021). This is what we will be using for the next few lessons.
Why Web Serial?
While we’ve previously taught computer-Arduino serial communication using Processing and Python, using Web Serial let’s us combine Arduino with a creative, fast-changing context: the Web. Web Serial also lets us utilize all of the wonderful web-based tools and APIs like p5js, ml5js, paper.js, three.js, matter.js, and more!
Of course, if your Arduino board has built-in WiFi, you can communicate directly with web servers (as we explore a bit in the ESP32 IoT lesson); however, in this case, we assume either a tethered connection via serial over USB or a local wireless connection via serial over Bluetooth.
Much of what we do with Web Serial could be translated to a WiFi context.
The Web Serial API
In the words of François Beaufort, the Web Serial API:
bridges the web and the physical world by allowing websites to communicate with serial devices, such as microcontrollers and 3D printers
Web Serial is already being used in web tools like Microsoft’s MakeCode, which lets you program microcontrollers via a drag-and-drop visual programming language and Arduino’s Web Editor, which lets you write code from the browser, store work in the cloud, and upload sketches directly from the web.
Does my web browser support Web Serial?
At the time of this writing (May 2021), Chrome and Edge versions 89+ are the only browser to support Web Serial but more should be coming soon! To check if the Web Serial API is supported, view Mozilla’s browser compatibility chart. Alternatively, open your dev tool console on your web browser (on Windows, type ctrl-shift-i
in Chrome or Firefox; on Mac, type cmd-alt-i
).
If true, then there is browser support. If false, then there is not.
How to use the Web Serial API
François Beaufort provides a nice overview of how to use the Web Serial API. Please read their website for detailed information.
But, in short. The Web Serial API is asynchronous and event based. This prevents websites from blocking when awaiting input from Web Serial.
Requesting permission to communicate with a serial device
To open a serial port, we must first request a port. For security, this call is managed by the browser and pops-up a dialog box asking the user to select a serial port and grant the website permission. Code based on François Beaufort’s blog post.
The await
keyword waits for the asynchronous requestPort()
function to return.
Similar to iPython, one amazing feature of JavaScript is that we can dynamically program it in the developer console and even interact with the current website’s variables, etc.
So, you can try the above command yourself. In the browser’s dev tool console, type:
If your Arduino is plugged in, you should see something like this:
Figure. If I type navigator.serial.requestPort()
into Chrome’s dev console with my Arduino Leonardo plugged into my laptop’s USB, I receive the prompt shown above.
Opening the serial port
To open the serial port, we call port.open(SerialOptions)
. SerialOptions is a dictionary with serial option parameters defined as:
These options should look familiar. We have:
baudRate
: the only required option that must be an integer value like 9600 or 115200dataBits
: The number of data bits per frame (either 7 or 8).stopBits
: The number of stop bits at the end of a frame (either 1 or 2).parity
: The parity mode (either “none”, “even” or “odd”).bufferSize
: The size of the read and write buffers that should be created (must be less than 16MB).flowControl
: The flow control mode (either “none” or “hardware”).
So, to open a port with 9600 baud, we would write:
Writing data
To write binary data, we use getWriter()
and write()
. We must call releaseLock()
in order for the serial port to be closed later.
For text data, we use a TextEncoderStream
:
Reading data
Reading data is similar. We use getReader()
and the read()
methods. We’ll describe the text reading solution below. You can learn about binary reading here.
Our Web Serial class
To make it easier to work with Web Serial, we wrote a basic Web Serial JavaScript class called serial.js
.
To use our Web Serial class, you can clone our p5js repo and include serial.js
from _libraries/serial.js
or use the jsDelivr service, which turns any GitHub repo into a CDN and directly serves serial.js
from our GitHub repo.
For the latter, in the <head>
or <body>
of your html file, simply add:
Currently, serial.js
supports just reading/writing text data (rather than binary data) but that shouldn’t affect us!
Event-based functions
serial.js
uses an event-based architecture with callback functions, which is common in web and UI programming (see: Mozilla’s Introduction to Events). The Serial class has four events, which correspond to the connection opening, closing, receiving data, and errors.
To create a new Serial object and subscribe to the events, you would write:
You need not subscribe to all the events—just the ones you need. However, subscribing to all of them does provide you with more information if something goes wrong.
Opening the serial port
To open a serial port, you call connect()
followed by open()
. The method signatures are:
The connect()
method takes two optional parameters:
existingPort
: a previously created serial port (e.g., returned fromnavigator.serial.requestPort()
). This will typically be nullportFilters
: a SerialPortFilter dictionary. This will also typically be null.
The open()
method takes in the previously described SerialOptions dictionary. If no parameter is passed, the dictionary defaults to serialOptions = { baudRate: 9600 }
.
For convenience, there are two additional methods connectAndOpen()
and autoConnectAndOpenPreviouslyApprovedPort()
—we typically use these:
The connectAndOpen()
method simply combines the two connect()
and open()
function calls. The auto-connect function takes advantage of the web browser’s permission caching—you need only approve a device once per webpage.
Let’s make stuff
We’ll begin by running the same Arduino code (SimpleSerialIn.ino) with the same circuit as the previous lesson. The circuit:
Figure. The corresponding circuit for SimpleSerialIn.ino. Made in Fritzing and PowerPoint.
Now, let’s build a simple webpage using Web Serial to interact with (SimpleSerialIn.ino).
Web dev tools
We recommend developing web code in Visual Studio Code (VSCode) with the Live Server extension. Because Web Serial requires device permissioning, you must run your webpage on a server rather than opening up index.html
directly from your OS (in other words, doubling clicking on index.html
in File Explorer or Finder won’t work properly).
To install Live Server, open VSCode and click on Extensions
in the left sidebar (or type ctrl-shift-x
) then search for Live Server in the textbox. At the time of writing, the extension has nearly 12 million installs.
To use Live Server, open a .html
page in VSCode. Then, you can either right-click on the file and select “Open with Live Server” or, in the bottom-right hand corner of VSCode, look for a blue ‘Go Live’ button. Click it and boom, you’re running a web server, serving the webpage! By default, the server will reload whenever the html file or any of its dependencies change!
Basic slider webpage
We’re going to build a simple webpage with a slider that transmits a value between 0 and 255 as a text-encoded string via Web Serial. The Arduino receives the text value and converts it to an int
then writes out that integer via analogWrite
via one of the PWM-enabled pins.
The full app experience should look like this:
Video. Running the SliderOut demo (live page, code) with SimpleSerialIn.ino on the Arduino Leonardo.
Create folder and initial index.html page
To begin, create a folder called SliderOut
and an empty index.html
file within it. Then, in VSCode select File->Open Folder
and select SliderOut
. With the Explorer
view open in VSCode’s left sidebar (ctrl-shift-e
), double click on the index.html
file to open it. Now, VSCode should look something like this:
In index.html
, copy/paste this simple, minimalist html page:
Save the file (ctrl-s
). Now, to make sure everything is working, launch it via Live Server.
There are three ways to launch Live Server—any will work! You can enlarge any of the screenshots below by right-clicking on them and selecting ‘Open Image in New Tab’.
1. Right-click on file in Explorer View | 2. Right-click on file in Editor | 3. Click ‘Go Live’ Button |
---|---|---|
Once launched, your default web browser will open to a web server running at 127.0.0.1
on port 5500 (Live Server’s defaults). The webpage should look like this:
Now, let’s add in a title header in an <h1>
block and some descriptive text:
If you hit ctrl-s
, the website should automatically reload if you still have Live Server running. If not, just relaunch the webpage with Live Server (and keep it running as we build out).
Add a connect button
Because Web Serial requires explicit user permission to connect to a local serial device, we need to add a “connect button”. To do that, we’ll use the HTML <button>
element and specify a callback function called onConnectButtonClick()
(we could name this anything we want but it will need to match the subsequent callback function that we write).
Reload the webpage and open the dev console (which you should almost always keep open when web developing). Click on the “Connect via Serial Port” button and you should see the message “Connect button clicked!” printed to console.
Add and hook up serial.js
Now we need to add in and hook up Web Serial, which we’ll do via the serial.js
library. In HTML, scripts can be placed in the <body>
, <head>
, or both. Pages load from top-to-bottom. In this case, we’ll put it at the top of the <body>
.
Now we need to create the Serial object and add in the callback functions. Add the following to the <script>
block just above async function onConnectButtonClick()
:
While you could save and reload the webpage at this point, nothing noticeably will happen because we haven’t actually hooked up the Serial
object to the connect button yet. So, let’s do that now. Update onConnectButtonClick()
to connect and open the serial port.
Now save and reload. With your Arduino plugged into your computer, try clicking the Connect via Serial Port
button. Once the button is clicked, the web browswer will enumerate all available serial devices and ask for user permission. It should look something like this:
Add and hook up a slider
Finally, let’s add and hook up an interactive slider to select and send values between 0 and 255 as text via serial. Slider widgets are specified as <input type="range">
in HTML.
Below our <button>
HTML in the <body>
, add in the slider. We will specify a minimum, maximum, and initial value as well as a callback function for whenever the slider value changes.
Now, in the <script>
block, add in the onSliderValueChanged()
method. In this function, we’ll grab the new value (src.value
) and transmit it as a string via serial.writeLine(src.value)
.
And that’s it! A fully working Web Serial demo, which should look something like this:
Video. Did I just tape my Arduino + breadboard to my computer screen to make this video? Yes I did!
Polish the interface
We can make a few UI updates to polish the interface. First, let’s hide the connect button after we successfully connect to serial. For this, we’ll change the display style of our button to none
in the onSerialConnectionOpened()
function:
Second, let’s display the value of the slider widget on the webpage. For this, we need to add the following to the HTML:
And then modify the onSliderValueChanged()
method:
We should also initialize the slider-value
textContent when the page first loads so that the slider widget and the text display are in sync. Somewhere at the top of the <script>
block, add in:
Finally, let’s wrap all of the interactive controls (except for the connect button) into their own <div>
with id=interactive-controls
and only show this when we’ve successfully connected to serial. So, the <div>
starts hidden, which is set by style="display:none"
.
Now programatically change the interactive-controls
style to display:block
when a connection is made:
Full slider video demo
Here’s a full video demo of what it should look like:
Video. Running the SliderOut demo (live page, code) with SimpleSerialIn.ino on the Arduino Leonardo.
Simple bidirectional text webpage
For our second and final example, we will build a simple webpage that sends and receives text data via Web Serial. As you type in the provided textbox, the data is immediately transmitted over serial and displayed on the Arduino-connected OLED display. The Arduino echos back received data to the web app, which shows this text in the “Received from Arduino” block. The app looks like this:
Video. Running the DisplayText demo (live page, code) with DisplayTextSerialIn.ino on the Arduino Leonardo.
For our circuit and wiring, we simply need an Arduino and OLED display.
Create new folder and index.html page
As before, create a new folder (we’ll call ours DisplayText
) and an index.html
file with some initial HTML.
Add connect button and initial interface
In the body, add in the connect button and initial interface:
Save and open with Live Server. It should look like this:
Hook up connect button and serial
Now, let’s hook up the connect button and add in the initial Web Serial code—both will be the same as before. Add a <script>
block into the <body>
:
Hook up event listener to textbox
In our web app, you may have noticed that we have no “submit” button. Instead, the text data is sent immediately as the user types in the textbox. To achieve this, we need to hook up an event listener to the textbox. We’ll have the event listener call our function called updateOutputText(e)
whenever there is new input. Inside updateOutputText
, we’ll both send the text data to Arduino via serial and update the <p id="output-text"></p>
with the text sent.
So, add the following to the beginning of our <script>
:
Update received-text with data received from Arduino
Finally, we need to listen for the data received back from the Arduino and update <p id="received-text"></p>
. Make two additions to your existing script:
At this point, you should be able to run the web app and have it communicate with your Arduino. You should try it! We can also polish our app with two UI updates.
Hide UI until serial connection is made
When a serial connection is made, we will hide the connect button and show the text entry interface:
Add CSS
We will also prettify our UI with some basic CSS. Within the DisplayText
project, create the folder css
and the CSS file styles.css
and put in:
Then, in index.html
add the following to the <head>
to hook up the CSS.
You did it! Now, play and experiment!
Full DisplayText video demo
Video. The full DisplayText demo (live page, code) with DisplayTextSerialIn.ino on the Arduino Leonardo.
Activity
For your prototyping journals, either modify or create your own lil web app to communicate with the Arduino. You can, of course, also write custom Arduino code to receive and parse data or to transmit custom data. Take a video, link to your code, and write a brief description and reflection.
Resources
-
Web Serial API Living Document, w3c Community Group Draft Report
-
Read from and write to a serial port from the web, François Beaufort
Next Lesson
In the next lesson, we’ll show how to use p5js with Web Serial. It’s gonna be great fun!