Como criar um dispositivo para WebUSB

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

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 Universal Serial Bus (USB) se tornou a interface física mais comum para conectar periféricos a dispositivos de computação para computadores e dispositivos móveis. Além de definir as características elétricas do barramento e um modelo geral para se comunicar com um dispositivo, as especificações 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 de dispositivos podem implementar. A vantagem dessas especificações de classe de dispositivo é que um fornecedor do 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. Isso foi uma grande melhoria em relação a cada fabricante precisar escrever os próprios drivers de dispositivo.

No entanto, alguns dispositivos não se encaixam em uma dessas classes padronizadas. Um fabricante pode optar por rotular o dispositivo como implementando a classe específica do fornecedor. Nesse caso, o sistema operacional escolhe qual driver do dispositivo carregar com base nas informações fornecidas no pacote de drivers do fornecedor, normalmente um conjunto de IDs de fornecedor e produto que implementam um protocolo específico do fornecedor.

Outro recurso do USB é que os dispositivos podem oferecer várias interfaces ao 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 processar o dispositivo, cada interface pode ser reivindicada por um driver diferente. Por exemplo, uma webcam USB geralmente oferece duas interfaces, uma que implementa a classe de vídeo USB (para a câmera) e outra que implementa a classe de áudio USB (para o microfone). O sistema operacional não carrega um único "driver de webcam", mas carrega drivers de classe de vídeo e áudio independentes 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ídeo 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 o Gamepad ou a API WebHID. Assim como nem todos os dispositivos implementam uma definição de classe padronizada, nem todos implementam recursos que correspondem às APIs da plataforma da Web. Nesse caso, a API WebUSB pode preencher essa lacuna, oferecendo uma maneira de os sites reivindicarem uma interface específica do fornecedor e implementar o suporte diretamente na página.

Os requisitos específicos para que um dispositivo seja acessível pelo WebUSB variam um pouco de uma plataforma para outra devido às diferenças na forma como os sistemas operacionais gerenciam dispositivos USB. No entanto, o requisito básico é que um dispositivo não pode ter um driver que reivindica 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 são deixadas 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 oferecida 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 de um dispositivo desse tipo ficaria 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 autoalimentado 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 (nenhuma)
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 seguir
0x22 bDescriptorType Tipo de descritor de relatório
0x003F wDescriptorLength Comprimento total do descritor do relatório
Descritor do endpoint
0x07 bLength Tamanho deste descritor
0x05 bDescriptorType Descritor do endpoint
0b10000001 bEndpointAddress Endpoint 1 (IN)
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 (nenhuma)
Descritor do endpoint
0x07 bLength Tamanho deste descritor
0x05 bDescriptorType Descritor do endpoint
0b10000010 bEndpointAddress Endpoint 1 (IN)
0b00000010 bmAttributes Em massa
0x0040 wMaxPacketSize Pacotes de 64 bytes
0x00 bInterval N/A para endpoints em massa
Descritor do endpoint
0x07 bLength Tamanho deste descritor
0x05 bDescriptorType Descritor do endpoint
0b00000011 bEndpointAddress Endpoint 3 (SAÍDA)
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 juntos. 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 enviar eventos de entrada ao sistema operacional. A segunda interface é específica do fornecedor e tem dois endpoints que podem ser usados para enviar comandos ao dispositivo e receber respostas em troca.

Descritores do WebUSB

Embora o WebUSB possa funcionar com muitos dispositivos sem modificações de firmware, a funcionalidade adicional é ativada marcando o dispositivo com descritores específicos que indicam suporte ao WebUSB. Por exemplo, você pode especificar um URL da página de destino para que o navegador direcione o usuário quando o dispositivo estiver conectado.

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

O Binary Device Object Store (BOS) é um conceito introduzido no USB 3.0, mas também foi transferido 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 capacidade da plataforma no descritor BOS:

Valor Campo Descrição
Descriptor do repositório de objetos do dispositivo binário
0x05 bLength Tamanho deste descritor
0x0F bDescriptorType Descriptor do repositório de objetos do dispositivo binário
0x001D wTotalLength Comprimento total desta série de descritores
0x01 bNumDeviceCaps Número de descritores de recursos do dispositivo no BOS
Descriptor de recursos da plataforma WebUSB
0x18 bLength Tamanho deste descritor
0x10 bDescriptorType Descritor de recursos do dispositivo
0x05 bDevCapabilityType Descritor de recursos da plataforma
0x00 bReserved
{0x38, 0xB6, 0x08, 0x34, 0xA9, 0x09, 0xA0, 0x47, 0x8B, 0xFD, 0xA0, 0x76, 0x88, 0x15, 0xB6, 0x65} PlatformCapablityUUID GUID do descritor de recursos da plataforma WebUSB no formato little-endian
0x0100 bcdVersion Descritores do 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 identifica isso como um descritor de capacidade da plataforma WebUSB, 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 foram projetados para codificar URLs nos bytes mais curtos. 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 esta 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 para que o host descubra o valor do campo wTotalLength sem se comprometer com uma transferência grande e, em seguida, quando o comprimento do descritor completo é conhecido.

Se o descritor de capacidade 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 de GET_URL (0x02) vai para wIndex:

bmRequestType bRequest wValue wIndex wLength Dados (resposta)
0b11000000 0x01 0x0001 0x0002 * Descriptor do URL

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

Considerações específicas da plataforma

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

macOS

Não é necessário nada especial para o macOS. Um site que usa o WebUSB pode se conectar ao dispositivo e reivindicar todas as interfaces que não são reivindicadas 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 o usuário e o grupo permitidos para acessar um dispositivo. Uma regra como essa vai atribuir a propriedade de um dispositivo correspondente aos IDs de fornecedor e produto 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 produto hexadecimais do dispositivo, por exemplo, ATTR{idVendor}=="18d1", ATTR{idProduct}=="4e11" corresponderia a um smartphone Nexus One. Eles precisam ser escritos sem o prefixo "0x" e em letras minúsculas para serem reconhecidos corretamente. Para encontrar os IDs do 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 no 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 está disponível para o navegador. 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 mostra uma mensagem perguntando se o Chrome pode acessá-lo. Esse aviso também vai reaparecer 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 serão acessíveis no Android do que no Linux para computador porque menos drivers são incluídos por padrão. Uma omissão notável, por exemplo, é a classe USB CDC-ACM comumente implementada por adaptadores USB-serial, já que não há uma API no SDK do Android para se comunicar com um dispositivo serial.

ChromeOS

O ChromeOS também é baseado no Linux e não requer nenhuma modificação na configuração do sistema. O serviço permission_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 extra. Ao contrário das plataformas acima, a capacidade de abrir um dispositivo USB de um aplicativo do usuário não é o padrão, mesmo que não haja um driver 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 (INF) personalizado instalado no sistema ou modificando o firmware do dispositivo para fornecer os descritores de compatibilidade do SO da 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 o ID do fornecedor e 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, mude as seções marcadas com "X", clique com o botão direito do mouse 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. Todas as interfaces de dispositivo precisam ter um GUID para que um aplicativo possa encontrar e se conectar a elas pela API do Windows. Use o cmdlet do PowerShell New-Guid ou uma ferramenta on-line para gerar um GUID aleatório.

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

Descritores de compatibilidade do SO da Microsoft

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

Depois de configurar os descritores do WebUSB, é fácil adicionar os descritores de compatibilidade do sistema operacional da Microsoft. Primeiro, estenda o descritor BOS com este descritor de recursos de plataforma adicional. Atualize wTotalLength e bNumDeviceCaps para considerar isso.

Valor Campo Descrição
Descriptor de recursos da plataforma Microsoft OS 2.0
0x1C bLength Tamanho deste descritor
0x10 bDescriptorType Descritor de recursos do dispositivo
0x05 bDevCapabilityType Descritor de recursos 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 little-endian
0x06030000 dwWindowsVersion Versão mínima compatível do Windows (Windows 8.1)
0x00B2 wMSOSDescriptorSetTotalLength Comprimento total do conjunto de descritores
0x02 bMS_VendorCode Valor bRequest para recuperar outros descritores da Microsoft
0x00 bAltEnumCode O dispositivo não oferece suporte à enumeração alternativa

Assim como nos descritores do 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 extrair o conjunto de descritores 2.0 do SO da Microsoft 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 a função a que 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 o dispositivo é compatível com o driver WinUSB. O descritor de propriedade do registro funciona de maneira semelhante à seção [Dev_AddReg] do exemplo de INF acima, definindo uma propriedade do registro para atribuir um GUID de interface de dispositivo a essa função.

Valor Campo Descrição
Cabeçalho do conjunto de descritores do Microsoft OS 2.0
0x000A wLength Tamanho deste descritor
0x0000 wDescriptorType Descriptor de cabeçalho do conjunto de descritores
0x06030000 dwWindowsVersion Versão mínima compatível do Windows (Windows 8.1)
0x00B2 wTotalLength Comprimento total do conjunto de descritores
Cabeçalho do subconjunto de configuração do Microsoft OS 2.0
0x0008 wLength Tamanho deste descritor
0x0001 wDescriptorType Desc. do cabeçalho do subconjunto de configuração.
0x00 bConfigurationValue Aplica-se à configuração 1 (indexada a partir de 0, apesar de as configurações normalmente serem indexadas a partir de 1)
0x00 bReserved Precisa ser definido como 0
0x00A8 wTotalLength Comprimento total do subconjunto, incluindo este cabeçalho
Cabeçalho de subconjunto de funções do Microsoft OS 2.0
0x0008 wLength Tamanho deste descritor
0x0002 wDescriptorType Descritor de cabeçalho do subconjunto de funções
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 com preenchimento de 8 bytes
"\0\0\0\0\0\0\0\0" SubCompatibleID String ASCII com preenchimento de 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 Comprimento do nome da propriedade
"DeviceInterfaceGUIDs\0" PropertyName Nome da propriedade com terminador nulo codificado em UTF-16LE
0x0050 wPropertyDataLength Comprimento do valor da propriedade
"{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}\0\0" PropertyData GUID e dois terminadores nulos codificados em UTF-16LE

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

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

Exemplos

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