Building a device for WebUSB

Build a device to take full advantage of the WebUSB API.

Published on Updated on

This article explains how to build a device to take full advantage of the WebUSB API. For a brief introduction to the API itself, see Access USB Devices on the Web.

Background

The Universal Serial Bus (USB) has become the most common physical interface for connecting peripherals to desktop and mobile computing devices. In addition to defining the electrical characteristics of the bus and a general model for communicating with a device, USB specifications include a set of device class specifications. These are general models for particular classes of devices such as storage, audio, video, networking, etc. that device manufacturers can implement. The advantage of these device class specifications is that an operating system vendor can implement a single driver based on the class specification (a "class driver") and any device implementing that class will be supported. This was a great improvement over every manufacturer needing to write their own device drivers.

Some devices however don't fit into one of these standardized device classes. A manufacturer may instead choose to label their device as implementing the vendor-specific class. In this case the operating system chooses which device driver to load based on information provided in the vendor's driver package, typically a set of vendor and product IDs which are known to implement a particular vendor-specific protocol.

Another feature of the USB is that devices may provide multiple interfaces to the host they are connected to. Each interface can implement either a standardized class or be vendor-specific. When an operating system chooses the right drivers to handle the device each interface can be claimed by a different driver. For example, a USB webcam typically provides two interfaces, one implementing the USB video class (for the camera) and one implementing the USB audio class (for the microphone). The operating system does not load a single "webcam driver" but instead loads independent video and audio class drivers which take responsibility for the separate functions of the device. This composition of interface classes provides for greater flexibility.

API basics

Many of the standard USB classes have corresponding web APIs. For example, a page can capture video from a video class device using getUserMedia() or receive input events from a human interface (HID) class device by listening for KeyboardEvents or PointerEvents, or by using the Gamepad or the WebHID API. Just as not all devices implement a standardized class definition, not all devices implement features that correspond to existing web platform APIs. When this is the case the WebUSB API can fill that gap by providing a way for sites to claim a vendor-specific interface and implement support for it from directly within their page.

The specific requirements for a device to be accessible via WebUSB vary slightly from platform to platform due to differences in how operating systems manage USB devices but the basic requirement is that a device should not already have a driver claiming the interface the page wants to control. This could be either a generic class driver provided by the OS vendor or a device driver provided by the vendor. As USB devices can provide multiple interfaces, each of which may have its own driver, it is possible to build a device for which some interfaces are claimed by a driver and others are left accessible to the browser.

For example, a high-end USB keyboard may provide an HID class interface that will be claimed by the operating system's input subsystem and a vendor-specific interface that remains available to WebUSB for use by a configuration tool. This tool can be served on the manufacturer's website allowing the user to change aspects of the device's behavior such as macro keys and lighting effects without installing any platform-specific software. Such a device's configuration descriptor would look something like this:

Values in this and other tables in this document are presented in hexadecimal or binary, whichever is more readable. The USB is a little-endian bus and so any integer value larger than 1 byte should be sent least-significant byte first.

ValueFieldDescription
Configuration descriptor
0x09bLengthSize of this descriptor
0x02bDescriptorTypeConfiguration descriptor
0x0039wTotalLengthTotal length of this series of descriptors
0x02bNumInterfacesNumber of interfaces
0x01bConfigurationValueConfiguration 1
0x00iConfigurationConfiguration name (none)
0b1010000bmAttributesSelf-powered device with remote wakeup
0x32bMaxPowerMax Power is expressed in 2 mA increments
Interface descriptor
0x09bLengthSize of this descriptor
0x04bDescriptorTypeInterface descriptor
0x00bInterfaceNumberInterface 0
0x00bAlternateSettingAlternate setting 0 (default)
0x01bNumEndpoints1 endpoint
0x03bInterfaceClassHID interface class
0x01bInterfaceSubClassBoot interface subclass
0x01bInterfaceProtocolKeyboard
0x00iInterfaceInterface name (none)
HID descriptor
0x09bLengthSize of this descriptor
0x21bDescriptorTypeHID descriptor
0x0101bcdHIDHID version 1.1
0x00bCountryCodeHardware target country
0x01bNumDescriptorsNumber of HID class descriptors to follow
0x22bDescriptorTypeReport descriptor type
0x003FwDescriptorLengthTotal length of the Report descriptor
Endpoint descriptor
0x07bLengthSize of this descriptor
0x05bDescriptorTypeEndpoint descriptor
0b10000001bEndpointAddressEndpoint 1 (IN)
0b00000011bmAttributesInterrupt
0x0008wMaxPacketSize8 byte packets
0x0AbInterval10ms interval
Interface descriptor
0x09bLengthSize of this descriptor
0x04bDescriptorTypeInterface descriptor
0x01bInterfaceNumberInterface 1
0x00bAlternateSettingAlternate setting 0 (default)
0x02bNumEndpoints2 endpoints
0xFFbInterfaceClassVendor-specific interface class
0x00bInterfaceSubClass
0x00bInterfaceProtocol
0x00iInterfaceInterface name (none)
Endpoint descriptor
0x07bLengthSize of this descriptor
0x05bDescriptorTypeEndpoint descriptor
0b10000010bEndpointAddressEndpoint 1 (IN)
0b00000010bmAttributesBulk
0x0040wMaxPacketSize64 byte packets
0x00bIntervalN/A for bulk endpoints
Endpoint descriptor
0x07bLengthSize of this descriptor
0x05bDescriptorTypeEndpoint descriptor
0b00000011bEndpointAddressEndpoint 3 (OUT)
0b00000010bmAttributesBulk
0x0040wMaxPacketSize64 byte packets
0x00bIntervalN/A for bulk endpoints

The configuration descriptor consists of multiple descriptors concatenated together. Each begins with bLength and bDescriptorType fields so that they can be identified. The first interface is an HID interface with an associated HID descriptor and a single endpoint used to deliver input events to the operating system. The second interface is a vendor-specific interface with two endpoints that can be used to send commands to the device and receive responses in return.

WebUSB descriptors

While WebUSB can work with many devices without firmware modifications, additional functionality is enabled by marking the device with specific descriptors indicating support for WebUSB. For example, you can specify a landing page URL that the browser can direct the user to when your device is plugged in.

Screenshot of the WebUSB notification in Chrome
WebUSB notification.

The Binary device Object Store (BOS) is a concept introduced in USB 3.0 but has also been backported to USB 2.0 devices as part of version 2.1. Declaring support for WebUSB starts with including the following Platform Capability Descriptor in the BOS descriptor:

ValueFieldDescription
Binary device Object Store descriptor
0x05bLengthSize of this descriptor
0x0FbDescriptorTypeBinary device Object Store descriptor
0x001DwTotalLengthTotal length of this series of descriptors
0x01bNumDeviceCapsNumber of device capability descriptors in the BOS
WebUSB platform capability descriptor
0x18bLengthSize of this descriptor
0x10bDescriptorTypeDevice capability descriptor
0x05bDevCapabilityTypePlatform capability descriptor
0x00bReserved
{0x38, 0xB6, 0x08, 0x34, 0xA9, 0x09, 0xA0, 0x47, 0x8B, 0xFD, 0xA0, 0x76, 0x88, 0x15, 0xB6, 0x65}PlatformCapablityUUIDWebUSB platform capability descriptor GUID in little-endian format
0x0100bcdVersionWebUSB descriptor version 1.0
0x01bVendorCodebRequest value for WebUSB
0x01iLandingPageURL for landing page

The UUID above would be written as {3408b638-09a9-47a0-8bfd-a0768815b665}; however, when sent as part of a USB descriptor its component fields must be sent in little-endian order. This transformation is complex so the proper encoding is given here for reference. See RFC4122.

The platform capability UUID identifies this as a WebUSB Platform Capability descriptor, which provides basic information about the device. For the browser to fetch more information about the device it uses the bVendorCode value to issue additional requests to the device. The only request currently specified is GET_URL which returns a URL descriptor. These are similar to string descriptors but are designed to encode URLs in the fewest bytes. A URL descriptor for "https://google.com" would look like this:

ValueFieldDescription
URL descriptor
0x0DbLengthSize of this descriptor
0x03bDescriptorTypeURL descriptor
0x01bSchemehttps://
"google.com"URLUTF-8 encoded URL content

When your device is first plugged in the browser reads the BOS descriptor by issuing this standard GET_DESCRIPTOR control transfer:

bmRequestTypebRequestwValuewIndexwLengthData (response)
0b100000000x060x0F000x0000*The BOS descriptor

This request is usually made twice, the first time with a large enough wLength so that the host finds out the value of the wTotalLength field without committing to a large transfer and then again when the full descriptor length is known.

If the WebUSB Platform Capability descriptor has the iLandingPage field set to a non-zero value the browser then performs a WebUSB-specific GET_URL request by issuing a control transfer with the bRequest set to the bVendorCode value from the platform capability descriptor and wValue set to the iLandingPage value. The request code for GET_URL (0x02) goes in wIndex:

bmRequestTypebRequestwValuewIndexwLengthData (response)
0b110000000x010x00010x0002*The URL descriptor

Again, this request may be issued twice in order to first probe for the length of the descriptor being read.

Support for displaying a notification when a USB device is plugged in is not available yet on Android.

Platform-specific considerations

While the WebUSB API attempts to provide a consistent interface for accessing USB devices developers should still be aware of requirements imposed on applications such as a web browsers requirements in order to access devices.

macOS

Nothing special is necessary for macOS. A website using WebUSB can connect to the device and claim any interfaces that aren't claimed by a kernel driver or another application.

Linux

Linux is like macOS but by default most distributions do not set up user accounts with permission to open USB devices. A system daemon called udev is responsible for assigning the user and group allowed to access a device. A rule such as this will assign ownership of a device matching the given vendor and product IDs to the plugdev group which is a common group for users with access to peripherals:

SUBSYSTEM=="usb", ATTR{idVendor}=="XXXX", ATTR{idProduct}=="XXXX", GROUP="plugdev"

Replace XXXX with the hexadecimal vendor and product IDs for your device, e.g. ATTR{idVendor}=="18d1", ATTR{idProduct}=="4e11" would match a Nexus One phone. These must be written without the usual "0x" prefix and all lowercase to be recognized correctly. To find the IDs for your device run the command line tool lsusb.

This rule should be placed in a file in the /etc/udev/rules.d directory and takes effect as soon as the device is plugged in. There is no need to restart udev.

Android

The Android platform is based on Linux but does not require any modification to system configuration. By default any device that does not have a driver built into the operating system is available to the browser. Developers should be aware however that users will encounter an additional step when connecting to the device. Once a user selects a device in response to a call to requestDevice(), Android will display a prompt asking whether to allow Chrome to access it. This prompt also reappears if a user returns to a website that already has permission to connect to a device and the website calls open().

In addition more devices will be accessible on Android than on desktop Linux because fewer drivers are included by default. A notable omission, for example, is the USB CDC-ACM class commonly implemented by USB-to-serial adapters as there is no API in the Android SDK for communicating with a serial device.

ChromeOS

ChromeOS is based on Linux as well and also does not require any modification to system configuration. The permission_broker service controls access to USB devices and will allow the browser to access them as long as there is at least one unclaimed interface.

Windows

The Windows driver model introduces an additional requirement. Unlike the platforms above the ability to open a USB device from a user application is not the default, even if there is no driver loaded. Instead there is a special driver, WinUSB, that needs to be loaded in order to provide the interface applications use to access the device. This can be done with either a custom driver information file (INF) installed on the system or by modifying the device firmware to provide the Microsoft OS Compatibility Descriptors during enumeration.

Driver Information File (INF)

A driver information file tells Windows what to do when encountering a device for the first time. Since the user's system already includes the WinUSB driver all that's necessary is for the INF file to associate your vendor and product ID with this new installation rule. The file below is a basic example. Save it to a file with the .inf extension, change the sections marked with "X", then right click on it and choose "Install" from the context menu.

[Version]
Signature = "$Windows NT$"
Class = USBDevice
ClassGUID = {88BAE032-5A81-49f0-BC3D-A4FF138216D6}
Provider = %ManufacturerName%
CatalogFile = WinUSBInstallation.cat
DriverVer = 09/04/2012,13.54.20.543

; ========== Manufacturer/Models sections ===========

[Manufacturer]
%ManufacturerName% = Standard,NTx86,NTia64,NTamd64

[Standard.NTx86]
%USB\MyCustomDevice.DeviceDesc% = USB_Install,USB\VID_XXXX&PID_XXXX

[Standard.NTia64]
%USB\MyCustomDevice.DeviceDesc% = USB_Install,USB\VID_XXXX&PID_XXXX

[Standard.NTamd64]
%USB\MyCustomDevice.DeviceDesc% = USB_Install,USB\VID_XXXX&PID_XXXX

; ========== Class definition ===========

[ClassInstall32]
AddReg = ClassInstall_AddReg

[ClassInstall_AddReg]
HKR,,,,%ClassName%
HKR,,NoInstallClass,,1
HKR,,IconPath,%REG_MULTI_SZ%,"%systemroot%\system32\setupapi.dll,-20"
HKR,,LowerLogoVersion,,5.2

; =================== Installation ===================

[USB_Install]
Include = winusb.inf
Needs = WINUSB.NT

[USB_Install.Services]
Include = winusb.inf
Needs = WINUSB.NT.Services

[USB_Install.HW]
AddReg = Dev_AddReg

[Dev_AddReg]
HKR,,DeviceInterfaceGUIDs,0x10000,"{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}"

; =================== Strings ===================

[Strings]
ManufacturerName = "Your Company Name Here"
ClassName = "Your Company Devices"
USB\MyCustomDevice.DeviceDesc = "Your Device Name Here"

The [Dev_AddReg] section configures the set of DeviceInterfaceGUIDs for the device. Every device interface must have a GUID in order for an application to find and connect to it through the Windows API. Use the New-Guid PowerShell cmdlet or an online tool to generate a random GUID.

For development purposes the Zadig tool provides an easy interface for replacing the driver loaded for a USB interface with the WinUSB driver.

Microsoft OS compatibility descriptors

The INF file approach above is cumbersome because it requires configuring every user's machine ahead of time. Windows 8.1 and higher offers an alternative through the use of custom USB descriptors. These descriptors provide information to the Windows operating system when the device is first plugged in that would normally be included in the INF file.

Once you have WebUSB descriptors set up it is easy to add Microsoft's OS compatibility descriptors as well. First extend the BOS descriptor with this additional platform capability descriptor. Make sure to update wTotalLength and bNumDeviceCaps to account for it.

ValueFieldDescription
Microsoft OS 2.0 platform capability descriptor
0x1CbLengthSize of this descriptor
0x10bDescriptorTypeDevice capability descriptor
0x05bDevCapabilityTypePlatform capability descriptor
0x00bReserved
{0xDF, 0x60, 0xDD, 0xD8, 0x89, 0x45, 0xC7, 0x4C, 0x9C, 0xD2, 0x65, 0x9D, 0x9E, 0x64, 0x8A, 0x9F}PlatformCapablityUUIDMicrosoft OS 2.0 platform compatibility descriptor GUID in little-endian format
0x06030000dwWindowsVersionMinimum compatible Windows version (Windows 8.1)
0x00B2wMSOSDescriptorSetTotalLengthTotal length of the descriptor set
0x02bMS_VendorCodebRequest value for retrieving further Microsoft descriptors
0x00bAltEnumCodeDevice does not support alternate enumeration

As with the WebUSB descriptors you have to pick a bRequest value to be used by control transfers related to these descriptors. In this example I've picked 0x02. 0x07, placed in wIndex, is the command to retrieve the Microsoft OS 2.0 Descriptor Set from the device.

bmRequestTypebRequestwValuewIndexwLengthData (response)
0b110000000x020x00000x0007*MS OS 2.0 Descriptor Set

A USB device can have multiple functions and so the first part of the descriptor set describes which function the properties that follow are associated with. The example below configures interface 1 of a composite device. The descriptor gives the OS two important pieces of information about this interface. The compatible ID descriptor tells Windows that this device is compatible with the WinUSB driver. The registry property descriptor functions similarly to the [Dev_AddReg] section of the INF example above, setting a registry property to assign this function a device interface GUID.

ValueFieldDescription
Microsoft OS 2.0 descriptor set header
0x000AwLengthSize of this descriptor
0x0000wDescriptorTypeDescriptor set header descriptor
0x06030000dwWindowsVersionMinimum compatible Windows version (Windows 8.1)
0x00B2wTotalLengthTotal length of the descriptor set
Microsoft OS 2.0 configuration subset header
0x0008wLengthSize of this descriptor
0x0001wDescriptorTypeConfiguration subset header desc.
0x00bConfigurationValueApplies to configuration 1 (indexed from 0 despite configurations normally indexed from 1)
0x00bReservedMust be set to 0
0x00A8wTotalLengthTotal length of the subset including this header
Microsoft OS 2.0 function subset header
0x0008wLengthSize of this descriptor
0x0002wDescriptorTypeFunction subset header descriptor
0x01bFirstInterfaceFirst interface of the function
0x00bReservedMust be set to 0
0x00A0wSubsetLengthTotal length of the subset including this header
Microsoft OS 2.0 compatible ID descriptor
0x0014wLengthSize of this descriptor
0x0003wDescriptorTypeCompatible ID descriptor
"WINUSB\0\0"CompatibileIDASCII string padded to 8 bytes
"\0\0\0\0\0\0\0\0"SubCompatibleIDASCII string padded to 8 bytes
Microsoft OS 2.0 registry property descriptor
0x0084wLengthSize of this descriptor
0x0004wDescriptorTypeRegistry property descriptor
0x0007wPropertyDataTypeREG_MULTI_SZ
0x002AwPropertyNameLengthLength of the property name
"DeviceInterfaceGUIDs\0"PropertyNameProperty name with null terminator encoded in UTF-16LE
0x0050wPropertyDataLengthLength of the property value
"{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}\0\0"PropertyDataGUID plus two null terminators encoded in UTF-16LE

Windows will only query the device for this information once. If the device does not respond with valid descriptors it will not ask again the next time the device is connected. Microsoft has provided a list of USB Device Registry Entries describing the registry entries created when enumerating a device. When testing delete the entries created for a device for force Windows to try to read the descriptors again.

For more information check out Microsoft's blog post on how to use these descriptors.

Examples

Example code implementing WebUSB-aware devices that include both WebUSB descriptors and Microsoft OS descriptors can be found in these projects:

Updated on Improve article

Back

The Shape Detection API: a picture is worth a thousand words, faces, and barcodes

Next

Stay awake with the Screen Wake Lock API

This site uses cookies to deliver and enhance the quality of its services and to analyze traffic. If you agree, cookies are also used to serve advertising and to personalize the content and advertisements that you see. Learn more about our use of cookies.