Connecting to USB devices with your browser
Getting started with WebUSB
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:
- Go to “Device manager”
- Find your device, right click on it, select “Properties”
- Go to “Details” tab
- Select “Hardware IDs” from the drop-down
- 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:
- Boot to Recovery OS by restarting your machine and holding down the Command and R keys at startup.
- Launch Terminal from the Utilities menu.
- Enter the following command:
csrutil enable
. - Reboot your machine.
- 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.
Originally published at http://gerritniezen.com/2017/10/connecting-to-usb-devices-with-your-browser/.