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.
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: