Photo by Brina Blum on Unsplash

Connecting to USB devices with your browser

Getting started with WebUSB

Gerrit Niezen

--

On 5 September 2017 Google released the new WebUSB API as part of Chrome 61. In the past the only way to connect to USB devices from a web app was to build a Chrome App with the chrome.usb (and associated chrome.serial and chrome.hid) libraries. With Chrome Apps being deprecated, this is not an option anymore. Now WebUSB gives us the option to write real web apps that can connect to USB devices without having to install anything!

Pros

  • Connect to USB devices from a website!
  • Support for Android, Chrome OS, Linux, macOS and Windows
  • Write “driverless” code by writing user-space drivers to connect to USB serial devices, instead of having to install a kernel extension or USB driver

Cons

  • Only supported by Google Chrome (and Chromium-based browsers like Opera) so far; WebKit (Safari) not considering support at the moment
  • If an existing driver (like OS built-in HID drivers) captures the device it cannot be claimed over WebUSB, but there are workarounds

Cool, how do I get started?

First off, let’s create a super basic web page that will load your script and show a Connect button:

Now, let’s write a script that waits until everything is loaded, listens for the Connect button being pressed and opens a connection to the device:

For it to work, you need to change VENDOR_ID to the vendor ID of your device. So how do you determine the product ID and vendor ID of the USB device? On macOS, if you click  ➡️ About This Mac ➡️ System Report.. and then click on Hardware ➡️ USB in the left-hand column, you can find all the details of the device you’re looking for:

On Linux, just type lsusb. On Windows, it shouldn’t surprise you that the steps are more involved:

  1. Go to “Device manager”
  2. Find your device, right click on it, select “Properties”
  3. Go to “Details” tab
  4. Select “Hardware IDs” from the drop-down
  5. You will find an entry of a form USB\VID_1A79&PID_6200 where 0x1A79 would be the vendor ID and 0x6200 would be the product ID

Once you’ve filled in the correct vendor ID, opened your new web page and clicked on the Connect button, you should see the new WebUSB permission popup that looks something like this:

Click on the device you want to connect to and then click Connect. If all goes well, the JS console should show you more information on the device, like its manufacturer name and product name:

USB transfers

To send and receive data, everything in USB land happens in terms of transfers, of which there are four types:

  • Control transfers — send a defined request to the device, for example to read information about a device or select configuration settings
  • Interrupt transfers — low latency data, for example keypresses or mouse movements; also the the only way for low-speed devices to send data
  • Bulk transfers — data where a delay can be tolerated, but the fastest way to send data if the bus is idle
  • Isochronous transfers — guaranteed delivery time, but no error correction, so useful for streaming audio and video

Interrupt and bulk transfers are handled by transferIn and tranferOut, while control transfers and isochronous transfers are done using controlTransferIn, controlTransferOut and isochronousTransferIn, isochronousTransferOut respectively.

Note that if an endpoint address of 0x81 is specified in a device spec, it means that it is endpoint 1 and an IN endpoint, wheres 0x01 is endpoint 1 in the OUT direction. So for example, to read from a bulk endpoint 0x81, you’d specify device.tranferIn(1, length).

How do I know if my app is sending the right USB packets?

Capturing USB packets with a tool like Wireshark can show you what is actually being sent over the USB bus. That means that if the USB device has existing software you can run that and see the actual USB packets being sent and received.

Unfortunately Wireshark can only capture USB traffic on Windows and Linux. When installing Wireshark on Windows, be sure to select the USBPCap option to install USB Capture support. On Linux, use the usbmon kernel module.

Accessing the website from another computer

If you want to access the website from another computer, you need to have HTTPS enabled. To set up HTTPS on a production website, you can use a free Let’s Encrypt certificate with (Certbot. If you’re running a local server, you can generate your own certificate with openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout newkey.key -out newkey.crt and then run the following Python script:

You’ll still get a warning in Chrome that the certificate is invalid, but you’ll be able to bypass this warning and WebUSB will still work.

Disabling existing drivers

If an existing driver, like the HID drivers built into the operating system, captures the device it cannot be claimed over WebUSB. However, there are some workarounds.

On Windows

You can use a tool like Zadig to specify which driver you want to use with your device. Or you can write a driver consisting of just an .INF that hides the device from the existing driver so that you can claim the interface. Of course, this means you’ll still have to require the user to install a driver and other apps that depend on the driver won’t be able to access the device.

See Chromium’s source code for an example .INF file. You can load it by going to Device Manager, double-clicking on the relevant device and the clicking Update driver.. on the Driver tab.

If you can’t sign the driver file, you can run unsigned drivers on Windows 10 as follows:

  • right-click on Windows icon, select Command Prompt (Admin)
  • type bcdedit /set testsigning on
  • if it displays an error message, you may need to disable Secure Boot in your UEFI/BIOS first
  • if it displays The operation completed successfully, restart the computer
  • to re-enable, use bcdedit /set test signing off

On Mac

It is possible to write a codeless kernel extension for macOS to dissociate the device from the existing driver so that you can claim the interface. Unfortunately that means asking the user to install the kernel extension, which throws up security warnings on macOS High Sierra and requires a kext signing certificate which needs special approval from Apple.

See this Stack Overflow answer to write a codeless kext for your device. To disable System Integrity Protection, so that you can load a codeless kext with signing it first:

  1. Boot to Recovery OS by restarting your machine and holding down the Command and R keys at startup.
  2. Launch Terminal from the Utilities menu.
  3. Enter the following command: csrutil enable.
  4. Reboot your machine.
  5. To check the current CSR status, type csrutil status in Terminal.

On Linux

You’ll need to write a udev rule. See Writing dev rules for more info.

What do I do if I can’t get it to work?

When you get an error like “Failed to claim interface” there should be more information when you go to chrome://device-log

If you need more help, you can always ask a question on Stack Overflow.

Example code

You can see some code that I’m busy working on in a GitHub repo.

--

--

Gerrit Niezen

I'm into open-source hardware 🔌, solarpunk 🌄 and growing food 🥬.