A API WebUSB torna o USB mais seguro e fácil de usar, trazendo-o para a Web.
Se eu disse claramente "USB", é muito provável que você pense imediatamente em teclados, mouses, áudio, vídeo e dispositivos de armazenamento. Você está certo, mas há outros tipos de dispositivos Universal Serial Bus (USB) disponíveis.
Esses dispositivos USB não padronizados exigem que os fornecedores de hardware escrevam drivers e SDKs específicos da plataforma para que você (o desenvolvedor) possa aproveitá-los. Infelizmente, esse código específico da plataforma impediu que esses dispositivos fossem usados pela Web. E esse é um dos motivos pelos quais a API WebUSB foi criada: para fornecer uma maneira de expor os serviços de dispositivos USB à Web. Com essa API, os fabricantes de hardware poderão criar SDKs JavaScript multiplataforma para os dispositivos.
Porém, o mais importante é que isso tornará o USB mais seguro e fácil de usar, trazendo-o para a Web.
Vamos conferir o comportamento esperado com a API WebUSB:
- Compre um dispositivo USB.
- Conecte-o ao computador. Uma notificação aparece imediatamente, com o site correto para esse dispositivo.
- Clique na notificação. O site está pronto para uso.
- Clique para conectar, e um seletor de dispositivos USB vai aparecer no Chrome, onde você pode escolher seu dispositivo.
Pronto.
Como seria esse procedimento sem a API WebUSB?
- Instale um aplicativo específico da plataforma.
- Se meu sistema operacional oferece suporte a ele, verifique se fiz o download do arquivo correto.
- Instale o dispositivo. Se você tiver sorte, não vai receber avisos ou pop-ups assustadores do SO avisando sobre a instalação de drivers/aplicativos da Internet. Se você tiver azar, os drivers ou aplicativos instalados vão funcionar mal e prejudicar o computador. Lembre-se de que a Web foi criada para conter sites com problemas de funcionamento.
- Se você usar o recurso apenas uma vez, o código vai permanecer no computador até que você realize que precisa removê-lo. Na Web, o espaço não utilizado é eventualmente recuperado.
Antes de começar
Neste artigo, pressupomos que você tenha algum conhecimento básico sobre como o USB funciona. Caso contrário, recomendo a leitura de USB em uma NutShell. Para informações gerais sobre USB, consulte as especificações oficiais de USB.
A API WebUSB está disponível no Chrome 61.
Disponível para testes de origem
Para receber o máximo de feedback possível dos desenvolvedores que usam a API WebUSB no campo, adicionamos esse recurso ao Chrome 54 e ao Chrome 57 como um teste de origem.
O último julgamento terminou em setembro de 2017.
Privacidade e segurança
Somente HTTPS
Devido à capacidade desse recurso, ele só funciona em contextos seguros. Isso significa que você precisará criar pensando na TLS.
Gesto do usuário obrigatório
Como medida de segurança, navigator.usb.requestDevice()
só pode
ser chamado por um gesto do usuário, como um toque ou um clique do mouse.
Política de permissões
Uma política de permissões é um mecanismo que permite aos desenvolvedores ativar e desativar seletivamente vários recursos e APIs do navegador. Ele pode ser definido por um cabeçalho HTTP e/ou um atributo "allow" de iframe.
Você pode definir uma política de permissões que controle se o atributo usb
será
exposto no objeto do Navigator ou, em outras palavras, se você permite a WebUSB.
Veja abaixo um exemplo de política de cabeçalho em que a WebUSB não é permitida:
Feature-Policy: fullscreen "*"; usb "none"; payment "self" https://payment.example.com
Confira abaixo outro exemplo de política de contêiner em que o USB é permitido:
<iframe allowpaymentrequest allow="usb; fullscreen"></iframe>
Vamos começar a programar
A API WebUSB depende muito das promessas do JavaScript. Se você não estiver familiarizado
com elas, confira este ótimo tutorial de promessas. Mais uma coisa: () => {}
são simplesmente funções de seta do ECMAScript 2015.
Acessar dispositivos USB
Você pode solicitar que o usuário selecione um único dispositivo USB conectado usando
navigator.usb.requestDevice()
ou chamar navigator.usb.getDevices()
para receber uma
lista de todos os dispositivos USB conectados aos quais o site recebeu acesso.
A função navigator.usb.requestDevice()
usa um objeto JavaScript obrigatório
que define filters
. Esses filtros são usados para corresponder qualquer dispositivo USB com o
identificador do fornecedor (vendorId
) e, opcionalmente, do produto (productId
).
As chaves classCode
, protocolCode
, serialNumber
e subclassCode
também podem
ser definidas lá.
Por exemplo, confira como acessar um dispositivo Arduino conectado configurado para permitir a origem.
navigator.usb.requestDevice({ filters: [{ vendorId: 0x2341 }] })
.then(device => {
console.log(device.productName); // "Arduino Micro"
console.log(device.manufacturerName); // "Arduino LLC"
})
.catch(error => { console.error(error); });
Antes que você pergunte, não encontrei esse número hexadecimal
0x2341
como mágica. Simplesmente procurei a palavra "Arduino" nesta lista de IDs USB.
O device
USB retornado na promessa cumprida acima tem algumas informações básicas, mas
importantes sobre o dispositivo, como a versão USB com suporte, o tamanho máximo do pacote, o fornecedor e os IDs de produtos, o número de possíveis
configurações que o dispositivo pode ter. Basicamente, ele contém todos os campos no
descritor USB do dispositivo.
// Get all connected USB devices the website has been granted access to.
navigator.usb.getDevices().then(devices => {
devices.forEach(device => {
console.log(device.productName); // "Arduino Micro"
console.log(device.manufacturerName); // "Arduino LLC"
});
})
Se um dispositivo USB anunciar o suporte ao WebUSB e definir um URL de página de destino, o Chrome vai mostrar uma notificação persistente quando o dispositivo USB estiver conectado. Ao clicar nessa notificação, a página de destino vai abrir.
Falar com uma placa USB Arduino
Agora vamos ver como é fácil se comunicar de uma placa Arduino compatível com WebUSB pela porta USB. Confira as instruções em https://github.com/webusb/arduino para ativar o WebUSB nos seus esquemas.
Não se preocupe, vou abordar todos os métodos de dispositivo WebUSB mencionados abaixo mais adiante neste artigo.
let device;
navigator.usb.requestDevice({ filters: [{ vendorId: 0x2341 }] })
.then(selectedDevice => {
device = selectedDevice;
return device.open(); // Begin a session.
})
.then(() => device.selectConfiguration(1)) // Select configuration #1 for the device.
.then(() => device.claimInterface(2)) // Request exclusive control over interface #2.
.then(() => device.controlTransferOut({
requestType: 'class',
recipient: 'interface',
request: 0x22,
value: 0x01,
index: 0x02})) // Ready to receive data
.then(() => device.transferIn(5, 64)) // Waiting for 64 bytes of data from endpoint #5.
.then(result => {
const decoder = new TextDecoder();
console.log('Received: ' + decoder.decode(result.data));
})
.catch(error => { console.error(error); });
A biblioteca WebUSB que estou usando está apenas implementando um protocolo de exemplo (baseado no protocolo serial USB padrão) e os fabricantes podem criar qualquer conjunto e tipos de endpoints que quiserem. As transferências de controle são especialmente boas para comandos de configuração pequenos, já que elas recebem prioridade de barramento e têm uma estrutura bem definida.
Este é o esboço que foi enviado para a placa do Arduino.
// Third-party WebUSB Arduino library
#include <WebUSB.h>
WebUSB WebUSBSerial(1 /* https:// */, "webusb.github.io/arduino/demos");
#define Serial WebUSBSerial
void setup() {
Serial.begin(9600);
while (!Serial) {
; // Wait for serial port to connect.
}
Serial.write("WebUSB FTW!");
Serial.flush();
}
void loop() {
// Nothing here for now.
}
A biblioteca WebUSB Arduino (link em inglês) de terceiros usada no exemplo de código acima faz basicamente duas coisas:
- O dispositivo atua como um dispositivo WebUSB, permitindo que o Chrome leia o URL da página de destino.
- Ela expõe uma API serial WebUSB que pode ser usada para substituir a padrão.
Confira o código JavaScript novamente. Depois que a device
é selecionada pelo usuário,
o device.open()
executa todas as etapas específicas da plataforma para iniciar uma sessão com o dispositivo
USB. Em seguida, selecione uma configuração USB disponível com
device.selectConfiguration()
. Lembre-se de que uma configuração especifica como o
dispositivo é alimentado, o consumo máximo de energia e o número de interfaces.
Falando em interfaces, também preciso solicitar acesso exclusivo com
device.claimInterface()
, já que os dados só podem ser transferidos para uma interface ou
endpoints associados quando a interface é reivindicada. Por fim, chamar
device.controlTransferOut()
é necessário para configurar o dispositivo Arduino com os
comandos apropriados para se comunicar pela API serial do WebUSB.
A partir daí, device.transferIn()
realiza uma transferência em massa para o
dispositivo para informar que o host está pronto para receber dados em massa. Em seguida, a
promessa é atendida com um objeto result
que contém uma DataView data
, que
precisa ser analisada adequadamente.
Se você já conhece o USB, tudo isso vai parecer familiar.
quero mais
A API WebUSB permite interagir com todos os tipos de transferência/endpoint USB:
- As transferências CONTROL, usadas para enviar ou receber parâmetros de configuração ou comando
para um dispositivo USB, são processadas com
controlTransferIn(setup, length)
econtrolTransferOut(setup, data)
. - As transferências INTERRUPT, usadas para uma pequena quantidade de dados sensíveis ao tempo, são
processadas com os mesmos métodos das transferências BULK com
transferIn(endpointNumber, length)
etransferOut(endpointNumber, data)
. - As transferências ISOCRÔNICAS, usadas para transmissões de dados como vídeo e som, são
processadas com
isochronousTransferIn(endpointNumber, packetLengths)
eisochronousTransferOut(endpointNumber, data, packetLengths)
. - As transferências em massa, usadas para transferir uma grande quantidade de dados não sensíveis ao tempo de
maneira confiável, são processadas com
transferIn(endpointNumber, length)
etransferOut(endpointNumber, data)
.
Você também pode conferir o projeto WebLight (link em inglês) de Mike Tsao, que oferece um exemplo básico de criação de um dispositivo LED controlado por USB projetado para a API WebUSB (sem usar um Arduino). Você vai encontrar hardware, software e firmware.
Revogar acesso a um dispositivo USB
O site pode limpar as permissões para acessar um dispositivo USB que não é mais necessário
chamando forget()
na instância USBDevice
. Por exemplo, para um aplicativo da Web educacional usado em um computador compartilhado com muitos dispositivos, um grande número de permissões acumuladas geradas pelo usuário prejudica a experiência do usuário.
// Voluntarily revoke access to this USB device.
await device.forget();
Como forget()
está disponível no Chrome 101 ou mais recente, verifique se esse recurso tem
suporte com o seguinte:
if ("usb" in navigator && "forget" in USBDevice.prototype) {
// forget() is supported.
}
Limites de tamanho da transferência
Alguns sistemas operacionais impõem limites à quantidade de dados que podem fazer parte de transações USB pendentes. Dividir seus dados em transações menores e enviar apenas alguns de cada vez ajuda a evitar essas limitações. Ele também reduz a quantidade de memória usada e permite que o aplicativo informe o progresso à medida que as transferências são concluídas.
Como várias transferências enviadas a um endpoint sempre são executadas em ordem, é possível melhorar a capacidade enviando vários blocos na fila para evitar a latência entre as transferências USB. Toda vez que um bloco é transmitido totalmente, ele notifica o código para fornecer mais dados, conforme documentado no exemplo de função auxiliar abaixo.
const BULK_TRANSFER_SIZE = 16 * 1024; // 16KB
const MAX_NUMBER_TRANSFERS = 3;
async function sendRawPayload(device, endpointNumber, data) {
let i = 0;
let pendingTransfers = [];
let remainingBytes = data.byteLength;
while (remainingBytes > 0) {
const chunk = data.subarray(
i * BULK_TRANSFER_SIZE,
(i + 1) * BULK_TRANSFER_SIZE
);
// If we've reached max number of transfers, let's wait.
if (pendingTransfers.length == MAX_NUMBER_TRANSFERS) {
await pendingTransfers.shift();
}
// Submit transfers that will be executed in order.
pendingTransfers.push(device.transferOut(endpointNumber, chunk));
remainingBytes -= chunk.byteLength;
i++;
}
// And wait for last remaining transfers to complete.
await Promise.all(pendingTransfers);
}
Dicas
A depuração USB no Chrome ficou mais fácil com a página interna about://device-log
,
em que você pode conferir todos os eventos relacionados ao dispositivo USB em um único lugar.
A página interna about://usb-internals
também é útil e permite
simular a conexão e a desconexão de dispositivos virtuais da WebUSB.
Isso é útil para realizar testes de IU sem hardware real.
Na maioria dos sistemas Linux, os dispositivos USB são mapeados com permissões somente leitura por
padrão. Para permitir que o Chrome abra um dispositivo USB, adicione uma nova regra
udev. Crie um arquivo em /etc/udev/rules.d/50-yourdevicename.rules
com o
seguinte conteúdo:
SUBSYSTEM=="usb", ATTR{idVendor}=="[yourdevicevendor]", MODE="0664", GROUP="plugdev"
em que [yourdevicevendor]
é 2341
se o dispositivo for um Arduino, por exemplo.
ATTR{idProduct}
também pode ser adicionado para uma regra mais específica. Verifique se o
user
é um membro do grupo plugdev
. Depois, basta reconectar seu dispositivo.
Recursos
- Stack Overflow: https://stackoverflow.com/questions/tagged/webusb
- Especificação da API WebUSB: http://wicg.github.io/webusb/ (link em inglês)
- Status do recurso do Chrome: https://www.chromestatus.com/feature/5651917954875392
- Problemas de especificação: https://github.com/WICG/webusb/issues
- Bugs de implementação: http://crbug.com?q=component:Blink>USB
- WebUSB ❤ ️Arduino: https://github.com/webusb/arduino
- IRC: #webusb no IRC do W3C
- Lista de e-mails do WICG: https://lists.w3.org/Archives/Public/public-wicg/
- Projeto WebLight: https://github.com/sowbug/weblight
Envie um tweet para @ChromiumDev usando a hashtag
#WebUSB
e informe onde e como você está usando essa hashtag.
Agradecimentos
Agradecemos a João Medley por revisar este artigo.