Como criar um dispositivo para WebUSB

Crie um dispositivo para aproveitar ao máximo a API WebUSB.

Reilly Grant
Reilly Grant

Este artigo explica como criar um dispositivo para aproveitar ao máximo a API WebUSB. Para uma breve introdução à API, consulte Acessar dispositivos USB na Web.

Contexto

O USB (Universal Serial Bus) se tornou a interface física mais comum para conectar periféricos a dispositivos de computação móvel e desktop. Além de definir as características elétricas do barramento e um modelo geral para se comunicar com um dispositivo, as especificações de USB incluem um conjunto de especificações de classe de dispositivo. Esses são modelos gerais para classes específicas de dispositivos, como armazenamento, áudio, vídeo, rede etc. que os fabricantes podem implementar. A vantagem dessas especificações de classe de dispositivo é que um fornecedor de sistema operacional pode implementar um único driver com base na especificação da classe (um "driver de classe"), e qualquer dispositivo que implemente essa classe terá suporte. Essa foi uma grande melhoria em relação a cada fabricante que precisava criar os próprios drivers de dispositivo.

No entanto, alguns dispositivos não se encaixam em uma dessas classes de dispositivos padronizadas. Em vez disso, um fabricante pode optar por marcar o dispositivo como implementando a classe específica do fornecedor. Nesse caso, o sistema operacional escolhe qual driver de dispositivo carregar com base nas informações fornecidas no pacote de drivers do fornecedor, geralmente um conjunto de IDs de fornecedores e produtos, conhecidos por implementar um protocolo específico do fornecedor.

Outro recurso do USB é que os dispositivos podem fornecer várias interfaces para o host ao qual estão conectados. Cada interface pode implementar uma classe padronizada ou ser específica do fornecedor. Quando um sistema operacional escolhe os drivers certos para lidar com o dispositivo, cada interface pode ser reivindicada por um driver diferente. Por exemplo, uma webcam USB geralmente fornece duas interfaces, uma implementando a classe de vídeo USB (para a câmera) e outra implementando a classe de áudio USB (para o microfone). O sistema operacional não carrega um único "driver de webcam", mas sim drivers independentes de classe de vídeo e áudio que assumem a responsabilidade pelas funções separadas do dispositivo. Essa composição de classes de interface oferece maior flexibilidade.

Princípios básicos da API

Muitas das classes USB padrão têm APIs da Web correspondentes. Por exemplo, uma página pode capturar vídeos de um dispositivo de classe de vídeo usando getUserMedia() ou receber eventos de entrada de um dispositivo de classe de interface humana (HID, na sigla em inglês) detectando KeyboardEvents ou PointerEvents ou usando a API Gamepad ou a API WebHID. Assim como nem todos os dispositivos implementam uma definição de classe padronizada, nem todos os dispositivos implementam recursos que correspondem às APIs da plataforma da Web existentes. Quando esse é o caso, a API WebUSB pode preencher essa lacuna oferecendo aos sites uma maneira de os sites reivindicarem uma interface específica do fornecedor e implementarem suporte a ela diretamente na página.

Os requisitos específicos para que um dispositivo seja acessível via WebUSB variam um pouco de plataforma para plataforma devido a diferenças na forma como os sistemas operacionais gerenciam dispositivos USB, mas o requisito básico é que o dispositivo ainda não tenha um driver, alegando a interface que a página quer controlar. Pode ser um driver de classe genérico fornecido pelo fornecedor do SO ou um driver de dispositivo fornecido pelo fornecedor. Como os dispositivos USB podem fornecer várias interfaces, cada uma com o próprio driver, é possível criar um dispositivo em que algumas interfaces são reivindicadas por um driver e outras ficam acessíveis ao navegador.

Por exemplo, um teclado USB de última geração pode fornecer uma interface de classe HID que será reivindicada pelo subsistema de entrada do sistema operacional e uma interface específica do fornecedor que permanece disponível para o WebUSB para uso por uma ferramenta de configuração. Essa ferramenta pode ser disponibilizada no site do fabricante, permitindo que o usuário mude aspectos do comportamento do dispositivo, como teclas de macro e efeitos de iluminação, sem instalar nenhum software específico da plataforma. O descritor de configuração desse dispositivo seria mais ou menos assim:

Valor Campo Descrição
Descritor de configuração
0x09 bLength Tamanho deste descritor
0x02 bDescriptorType Descritor de configuração
0x0039 wTotalLength Comprimento total desta série de descritores
0x02 bNumInterfaces Número de interfaces
0x01 bConfigurationValue Configuração 1
0x00 iConfiguration Nome da configuração (nenhum)
0b1010000 bmAttributes Dispositivo autoligado com ativação remota
0x32 bMaxPower A potência máxima é expressa em incrementos de 2 mA
Descritor de interface
0x09 bLength Tamanho deste descritor
0x04 bDescriptorType Descritor de interface
0x00 bInterfaceNumber Interface 0
0x00 bAlternateSetting Configuração alternativa 0 (padrão)
0x01 bNumEndpoints 1 endpoint
0x03 bInterfaceClass Classe de interface HID
0x01 bInterfaceSubClass Subclasse da interface de inicialização
0x01 bInterfaceProtocol Teclado
0x00 iInterface Nome da interface (nenhum)
Descritor HID
0x09 bLength Tamanho deste descritor
0x21 bDescriptorType Descritor HID
0x0101 bcdHID HID versão 1.1
0x00 bCountryCode País de destino do hardware
0x01 bNumDescriptors Número de descritores de classe HID a serem seguidos
0x22 bDescriptorType Tipo de descritor do relatório
0x003F wDescriptorLength Comprimento total do descritor do relatório
Descritor de endpoint
0x07 bLength Tamanho deste descritor
0x05 bDescriptorType Descritor de endpoint
0b10000001 bEndpointAddress Endpoint 1 (Índia)
0b00000011 bmAttributes Interromper
0x0008 wMaxPacketSize Pacotes de 8 bytes
0x0A bInterval Intervalo de 10 ms
Descritor de interface
0x09 bLength Tamanho deste descritor
0x04 bDescriptorType Descritor de interface
0x01 bInterfaceNumber Interface 1
0x00 bAlternateSetting Configuração alternativa 0 (padrão)
0x02 bNumEndpoints 2 endpoints
0xFF bInterfaceClass Classe de interface específica do fornecedor
0x00 bInterfaceSubClass
0x00 bInterfaceProtocol
0x00 iInterface Nome da interface (nenhum)
Descritor de endpoint
0x07 bLength Tamanho deste descritor
0x05 bDescriptorType Descritor de endpoint
0b10000010 bEndpointAddress Endpoint 1 (Índia)
0b00000010 bmAttributes Em massa
0x0040 wMaxPacketSize Pacotes de 64 bytes
0x00 bInterval N/A para endpoints em massa
Descritor de endpoint
0x07 bLength Tamanho deste descritor
0x05 bDescriptorType Descritor de endpoint
0b00000011 bEndpointAddress Endpoint 3 (OUT)
0b00000010 bmAttributes Em massa
0x0040 wMaxPacketSize Pacotes de 64 bytes
0x00 bInterval N/A para endpoints em massa

O descritor de configuração consiste em vários descritores concatenados. Cada um começa com os campos bLength e bDescriptorType para que possam ser identificados. A primeira interface é uma interface HID com um descritor HID associado e um único endpoint usado para entregar eventos de entrada ao sistema operacional. A segunda é uma interface específica do fornecedor com dois endpoints que podem ser usados para enviar comandos ao dispositivo e receber respostas em troca.

Descritores WebUSB

Embora o WebUSB possa funcionar com muitos dispositivos sem modificações de firmware, outra funcionalidade é ativada marcando o dispositivo com descritores específicos que indicam suporte a WebUSB. Por exemplo, é possível especificar um URL de página de destino para o qual o navegador poderá direcionar o usuário quando o dispositivo for conectado.

Captura de tela da notificação WebUSB no Chrome
Notificação WebUSB
.

O Binary Device Object Store (BOS) é um conceito introduzido no USB 3.0, mas que também passou por backport para dispositivos USB 2.0 como parte da versão 2.1. A declaração de suporte ao WebUSB começa com a inclusão do seguinte descritor de recursos da plataforma no descritor do BOS:

Valor Campo Descrição
Descritor do armazenamento de objetos do dispositivo binário
0x05 bLength Tamanho deste descritor
0x0F bDescriptorType Descritor do armazenamento de objetos do dispositivo binário
0x001D wTotalLength Comprimento total desta série de descritores
0x01 bNumDeviceCaps Número de descritores de capacidade do dispositivo no BOS
Descritor de capacidade da plataforma WebUSB
0x18 bLength Tamanho deste descritor
0x10 bDescriptorType Descritor da capacidade do dispositivo
0x05 bDevCapabilityType Descritor de capacidade da plataforma
0x00 bReserved
{0x38, 0xB6, 0x08, 0x34, 0xA9, 0x09, 0xA0, 0x47, 0x8B, 0xFD, 0xA0, 0x76, 0x88, 0x15, 0xB6, 0x65} PlatformCapablityUUID Descritor do GUID da plataforma WebUSB no formato few-endian
0x0100 bcdVersion Descritor WebUSB versão 1.0
0x01 bVendorCode Valor de bRequest para WebUSB
0x01 iLandingPage URL da página de destino

O UUID de capacidade da plataforma o identifica como um descritor de capacidade da plataforma WebUSB (link em inglês), que fornece informações básicas sobre o dispositivo. Para que o navegador busque mais informações sobre o dispositivo, ele usa o valor bVendorCode para emitir outras solicitações ao dispositivo. A única solicitação especificada no momento é GET_URL, que retorna um descritor de URL. Eles são semelhantes aos descritores de string, mas são projetados para codificar URLs no menor número de bytes. Um descritor de URL para "https://google.com" seria assim:

Valor Campo Descrição
Descritor de URL
0x0D bLength Tamanho deste descritor
0x03 bDescriptorType Descritor de URL
0x01 bScheme https://
"google.com" URL Conteúdo do URL codificado em UTF-8

Quando o dispositivo é conectado pela primeira vez, o navegador lê o descritor do BOS emitindo essa transferência de controle GET_DESCRIPTOR padrão:

bmRequestType bRequest wValue wIndex wLength Dados (resposta)
0b10000000 0x06 0x0F00 0x0000 * O descritor BOS

Essa solicitação geralmente é feita duas vezes, a primeira com um wLength grande o suficiente para que o host descubra o valor do campo wTotalLength sem se comprometer com uma transferência grande e novamente quando o tamanho completo do descritor for conhecido.

Se o descritor de recursos da plataforma WebUSB tiver o campo iLandingPage definido como um valor diferente de zero, o navegador vai executar uma solicitação GET_URL específica do WebUSB emitindo uma transferência de controle com o bRequest definido como o valor bVendorCode do descritor de capacidade da plataforma e wValue definido como o valor iLandingPage. O código de solicitação para GET_URL (0x02) é enviado em wIndex:

bmRequestType bRequest wValue wIndex wLength Dados (resposta)
0b11000000 0x01 0x0001 0x0002 * O descritor de URL

Novamente, essa solicitação pode ser emitida duas vezes para verificar primeiro o tamanho do descritor que está sendo lido.

Considerações específicas da plataforma

Embora a API WebUSB tente fornecer uma interface consistente para acessar os desenvolvedores de dispositivos USB, os desenvolvedores ainda precisam estar cientes dos requisitos impostos a aplicativos, como os requisitos de navegadores da Web, para acessar os dispositivos.

macOS

Nada especial é necessário para o macOS. Um site que usa WebUSB pode se conectar ao dispositivo e reivindicar qualquer interface que não seja reivindicada por um driver do kernel ou outro aplicativo.

Linux

O Linux é como o macOS, mas, por padrão, a maioria das distribuições não configura contas de usuário com permissão para abrir dispositivos USB. Um daemon do sistema chamado udev é responsável por atribuir ao usuário e ao grupo com permissão para acessar um dispositivo. Uma regra como essa atribuirá a propriedade de um dispositivo correspondente ao fornecedor e ID de produto fornecidos ao grupo plugdev, que é um grupo comum para usuários com acesso a periféricos:

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

Substitua XXXX pelos IDs de fornecedor e de produto hexadecimais do seu dispositivo. Por exemplo, ATTR{idVendor}=="18d1", ATTR{idProduct}=="4e11" corresponderia a um smartphone Nexus One. Eles precisam ser escritos sem o prefixo normal "0x" e todas as letras minúsculas para serem reconhecidos corretamente. Para encontrar os IDs do seu dispositivo, execute a ferramenta de linha de comando lsusb.

Essa regra precisa ser colocada em um arquivo no diretório /etc/udev/rules.d e entra em vigor assim que o dispositivo é conectado. Não é necessário reiniciar o udev.

Android

A plataforma Android é baseada em Linux, mas não requer nenhuma modificação na configuração do sistema. Por padrão, qualquer dispositivo que não tenha um driver integrado ao sistema operacional fica disponível para o navegador. No entanto, os desenvolvedores precisam estar cientes de que os usuários vão encontrar uma etapa extra ao se conectar ao dispositivo. Quando um usuário seleciona um dispositivo em resposta a uma chamada para requestDevice(), o Android exibe uma solicitação perguntando se permite que o Chrome o acesse. Essa solicitação também reaparece se um usuário retornar a um site que já tem permissão para se conectar a um dispositivo e o site chamar open().

Além disso, mais dispositivos estarão acessíveis no Android do que no Linux para computador, porque menos drivers são incluídos por padrão. Uma omissão importante, por exemplo, é a classe USB CDC-ACM geralmente implementada por adaptadores USB para serial, já que não há API no SDK do Android para se comunicar com um dispositivo serial.

ChromeOS

O ChromeOS também é baseado em Linux e não requer nenhuma modificação na configuração do sistema. O serviço allow_broker controla o acesso a dispositivos USB e permite que o navegador os acesse, desde que haja pelo menos uma interface não reivindicada.

Windows

O modelo de driver do Windows apresenta um requisito adicional. Ao contrário das plataformas acima da capacidade de abrir um dispositivo USB em um app do usuário, não é o padrão, mesmo que nenhum driver seja carregado. Em vez disso, há um driver especial, o WinUSB, que precisa ser carregado para fornecer a interface usada pelos aplicativos para acessar o dispositivo. Isso pode ser feito com um arquivo de informações de driver personalizado (INF) instalado no sistema ou modificando o firmware do dispositivo para fornecer os descritores de compatibilidade do SO Microsoft durante a enumeração.

Arquivo de informações do driver (INF)

Um arquivo de informações do driver informa ao Windows o que fazer ao encontrar um dispositivo pela primeira vez. Como o sistema do usuário já inclui o driver WinUSB, basta que o arquivo INF associe seu fornecedor e ID do produto a essa nova regra de instalação. O arquivo abaixo é um exemplo básico. Salve-o em um arquivo com a extensão .inf, altere as seções marcadas com "X", clique com o botão direito nessa página e escolha "Instalar" no menu de contexto.

[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"

A seção [Dev_AddReg] configura o conjunto de DeviceInterfaceGUIDs para o dispositivo. Cada interface do dispositivo precisa ter um GUID para que um aplicativo possa encontrá-lo e se conectar a ele usando a API do Windows. Use o cmdlet New-Guid do PowerShell ou uma ferramenta on-line para gerar um GUID aleatório.

Para fins de desenvolvimento, a ferramenta Zadig fornece uma interface fácil para substituir o driver carregado para uma interface USB pelo driver WinUSB.

Descritores de compatibilidade do SO Microsoft

A abordagem do arquivo INF acima é complicada, porque exige a configuração prévia da máquina de cada usuário. O Windows 8.1 e versões mais recentes oferecem uma alternativa pelo uso de descritores USB personalizados. Esses descritores fornecem informações ao sistema operacional Windows quando o dispositivo é conectado pela primeira vez, e essas informações normalmente são incluídas no arquivo INF.

Depois de configurar os descritores WebUSB, é fácil também adicionar os descritores de compatibilidade do SO da Microsoft. Primeiro, estenda o descritor de BOS com esse descritor de recurso de plataforma adicional. Atualize wTotalLength e bNumDeviceCaps para considerar isso.

Valor Campo Descrição
Descritor de capacidade da plataforma Microsoft OS 2.0
0x1C bLength Tamanho deste descritor
0x10 bDescriptorType Descritor da capacidade do dispositivo
0x05 bDevCapabilityType Descritor de capacidade da plataforma
0x00 bReserved
{0xDF, 0x60, 0xDD, 0xD8, 0x89, 0x45, 0xC7, 0x4C, 0x9C, 0xD2, 0x65, 0x9D, 0x9E, 0x64, 0x8A, 0x9F} PlatformCapablityUUID GUID do descritor de compatibilidade da plataforma Microsoft OS 2.0 no formato few-endian
0x06030000 dwWindowsVersion Versão mínima compatível do Windows (Windows 8.1)
0x00B2 wMSOSDescriptorSetTotalLength Comprimento total do conjunto do descritor
0x02 bMS_VendorCode Valor bRequest para recuperar mais descritores da Microsoft
0x00 bAltEnumCode O dispositivo não oferece suporte à enumeração alternativa

Assim como nos descritores WebUSB, você precisa escolher um valor bRequest para ser usado pelas transferências de controle relacionadas a esses descritores. Neste exemplo, escolhi 0x02. 0x07, colocado em wIndex, é o comando para recuperar o conjunto de descritores do Microsoft OS 2.0 do dispositivo.

bmRequestType bRequest wValue wIndex wLength Dados (resposta)
0b11000000 0x02 0x0000 0x0007 * Conjunto de descritores do MS OS 2.0

Um dispositivo USB pode ter várias funções. Portanto, a primeira parte do conjunto de descritores descreve com qual função as propriedades a seguir estão associadas. O exemplo abaixo configura a interface 1 de um dispositivo composto. O descritor fornece ao SO duas informações importantes sobre essa interface. O descritor de ID compatível informa ao Windows que esse dispositivo é compatível com o driver WinUSB. O descritor de propriedade do registro funciona de maneira semelhante à seção [Dev_AddReg] do exemplo INF acima, configurando uma propriedade de registro para atribuir a essa função um GUID da interface do dispositivo.

Valor Campo Descrição
Cabeçalho do conjunto de descritor do Microsoft OS 2.0
0x000A wLength Tamanho deste descritor
0x0000 wDescriptorType Descritor do cabeçalho definido
0x06030000 dwWindowsVersion Versão mínima compatível do Windows (Windows 8.1)
0x00B2 wTotalLength Comprimento total do conjunto do descritor
Cabeçalho do subconjunto de configuração do Microsoft OS 2.0
0x0008 wLength Tamanho deste descritor
0x0001 wDescriptorType Descrição do cabeçalho do subconjunto de configuração
0x00 bConfigurationValue Aplica-se à configuração 1 (indexada de 0, apesar das configurações normalmente indexadas de 1)
0x00 bReserved Precisa ser definido como 0.
0x00A8 wTotalLength Comprimento total do subconjunto, incluindo este cabeçalho
Cabeçalho do subconjunto de funções do Microsoft OS 2.0
0x0008 wLength Tamanho deste descritor
0x0002 wDescriptorType Descritor do cabeçalho do subconjunto de função
0x01 bFirstInterface Primeira interface da função
0x00 bReserved Precisa ser definido como 0.
0x00A0 wSubsetLength Comprimento total do subconjunto, incluindo este cabeçalho
Descritor de ID compatível com o Microsoft OS 2.0
0x0014 wLength Tamanho deste descritor
0x0003 wDescriptorType Descritor de ID compatível
"WINUSB\0\0" CompatibileID String ASCII preenchida em 8 bytes
"\0\0\0\0\0\0\0\0" SubCompatibleID String ASCII preenchida em 8 bytes
Descritor de propriedade do registro do Microsoft OS 2.0
0x0084 wLength Tamanho deste descritor
0x0004 wDescriptorType Descritor de propriedade do registro
0x0007 wPropertyDataType REG_MULTI_SZ
0x002A wPropertyNameLength Tamanho do nome da propriedade
"DeviceInterfaceGUIDs\0" PropertyName Nome da propriedade com terminador nulo codificado em UTF-16LE.
0x0050 wPropertyDataLength Tamanho do valor da propriedade
"{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}\0\0" PropertyData GUID mais dois terminadores nulos codificados em UTF-16LE

O Windows só consultará o dispositivo uma vez para conseguir essas informações. Se o dispositivo não responder com descritores válidos, ele não vai perguntar novamente na próxima vez que ele for conectado. A Microsoft forneceu uma lista de entradas de registro de dispositivos USB que descrevem as entradas de registro criadas ao enumerar um dispositivo. Ao teste, exclua as entradas criadas para um dispositivo para forçar o Windows a tentar ler os descritores novamente.

Para mais informações, consulte a postagem do blog da Microsoft sobre como usar esses descritores.

Exemplos

Os exemplos de código que implementam dispositivos compatíveis com WebUSB que incluem descritores WebUSB e descritores do Microsoft OS podem ser encontrados nestes projetos: