A API WebUSB torna o USB mais seguro e fácil de usar ao trazê-lo para a Web.
Se eu dissesse "USB" de forma simples e simples, há uma boa chance de você pensar imediatamente em teclados, mouses, áudios, vídeos e dispositivos de armazenamento. Você tem razão, mas vai encontrar outros tipos de dispositivos USB (Universal Serial Bus) por aí.
Esses dispositivos USB não padronizados exigem que os fornecedores de hardware gravem drivers e SDKs específicos da plataforma para que você (o desenvolvedor) possa aproveitá-los. Infelizmente, esse código específico da plataforma tem impedido o uso desses dispositivos pela Web. Esse é um dos motivos pelos quais a API WebUSB foi criada: oferecer uma maneira de expor os serviços de dispositivos USB na Web. Com essa API, os fabricantes de hardware poderão criar SDKs JavaScript multiplataforma para os dispositivos.
Mas o mais importante é que isso torna o USB mais seguro e mais fácil de usar, trazendo-o para a Web.
Vamos conferir o comportamento esperado com a API WebUSB:
- Comprar um dispositivo USB.
- Conecte ao computador. Uma notificação aparece imediatamente, com o site correto a ser acessado para este dispositivo.
- Clique na notificação. O site está pronto para ser usado.
- Clique para se conectar. Um seletor de dispositivo USB será exibido no Chrome, e é possível escolher o dispositivo.
Pronto.
Como seria esse procedimento sem a API WebUSB?
- Instale um aplicativo específico da plataforma.
- Se ele for compatível com meu sistema operacional, verifique se fiz o download correto.
- Instale a coisa. Se tiver sorte, você não vai receber solicitações ou pop-ups do SO assustadores sobre a instalação de drivers/aplicativos da Internet. Se você não tiver sorte, os drivers ou aplicativos instalados não funcionarão e danificará seu computador. Lembre-se de que a Web é feita para conter sites com mau funcionamento.
- Se você usar o recurso apenas uma vez, o código permanecerá no computador até você pensar em removê-lo. Na Web, o espaço para não utilizado é eventualmente recuperado.
Antes de começar
Este artigo pressupõe que você tenha algum conhecimento básico de como o USB funciona. Caso contrário, recomendamos ler o artigo USB em um NutShell. Para mais informações sobre o USB, consulte as especificações oficiais do 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 de desenvolvedores que usam a API WebUSB em campo, adicionamos esse recurso ao Chrome 54 e ao Chrome 57 como um teste de origem.
O teste mais recente terminou em setembro de 2017.
Privacidade e segurança
Somente HTTPS
Devido ao poder desse recurso, ele só funciona em contextos seguros. Isso significa que você precisa criar com o TLS em mente.
É necessário um gesto do usuário
Como medida de segurança, navigator.usb.requestDevice()
só pode
ser chamado com um gesto do usuário, como um toque ou um clique do mouse.
Política de permissões
A política de permissões é um mecanismo que permite que os desenvolvedores ativem e desativem seletivamente vários recursos do navegador e APIs. Ele pode ser definido por um cabeçalho HTTP e/ou um atributo "allow" de iframe.
É possível definir uma política de permissões que controle se o atributo usb
é
exposto no objeto do Navigator ou, em outras palavras, se você permite a WebUSB.
Confira 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 de promessas de JavaScript. Se 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 a que o site tem 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
fornecedor especificado (vendorId
) e, opcionalmente, os identificadores de produto (productId
).
As chaves classCode
, protocolCode
, serialNumber
e subclassCode
também
podem ser definidas neles.
Por exemplo, veja como ter acesso a 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 criei esse número hexadecimal 0x2341
como mágica. Simplesmente pesquisei a palavra "Arduino" nesta Lista de IDs USB.
O device
USB retornado na promessa atendida acima tem algumas informações básicas, mas
importantes, sobre o dispositivo, como a versão USB compatível,
tamanho máximo do pacote, fornecedores e IDs de produto, o número de configurações
possíveis que o dispositivo pode ter. Basicamente, ele contém todos os campos do
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"
});
})
Aliás, se um dispositivo USB anunciar suporte para WebUSB, além de definir um URL da página de destino, o Chrome vai mostrar uma notificação contínua quando o dispositivo USB estiver conectado. Clique nesta notificação para abrir a página de destino.
Fale com uma placa USB do Arduino
Agora vamos conferir 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 (link em inglês) para ativar a WebUSB nos seus esboços.
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); });
Lembre-se de que a biblioteca WebUSB que estou usando está implementando apenas um protocolo de exemplo (com base no protocolo de série USB padrão) e que os fabricantes podem criar qualquer conjunto e tipo de endpoint que quiserem. As transferências de controle são especialmente boas para comandos de configuração pequenos, porque eles têm prioridade de barramento e têm uma estrutura bem definida.
E aqui está o esboço que foi enviado para a placa 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 de terceiros, usada no exemplo de código acima, faz basicamente duas coisas:
- O dispositivo funciona como um WebUSB, permitindo que o Chrome leia o URL da página de destino.
- Ele expõe uma API WebUSB Serial que você pode usar para substituir a padrão.
Confira o código JavaScript novamente. Depois que o device
é selecionado 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, preciso selecionar uma configuração USB disponível com
device.selectConfiguration()
. 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, é necessário chamar
device.controlTransferOut()
para configurar o dispositivo Arduino com os
comandos apropriados para se comunicar pela API WebUSB Serial.
Depois, 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
contendo um data
de DataView que precisa ser analisado adequadamente.
Se você tem familiaridade com USB, isso deve parecer familiar.
quero mais
A API WebUSB permite interagir com todos os tipos de endpoint/transferência 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 que as transferências em BULK com
transferIn(endpointNumber, length)
etransferOut(endpointNumber, data)
. - As transferências ISOCHRONOUS, usadas para streams de dados como vídeo e som, são
processadas com
isochronousTransferIn(endpointNumber, packetLengths)
eisochronousTransferOut(endpointNumber, data, packetLengths)
. - As transferências em BULK, usadas para transferir uma grande quantidade de dados não sensíveis ao tempo de
forma confiável, são processadas com
transferIn(endpointNumber, length)
etransferOut(endpointNumber, data)
.
Você também pode dar uma olhada no projeto WebLight de Mike Tsao, que oferece um exemplo básico da criação de um dispositivo de LED controlado por USB projetado para a API WebUSB (sem usar um Arduino aqui). Você 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 seja 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 cria uma experiência ruim.
// Voluntarily revoke access to this USB device.
await device.forget();
Como forget()
está disponível no Chrome 101 ou mais recente, confira se esse recurso tem
suporte ao seguinte:
if ("usb" in navigator && "forget" in USBDevice.prototype) {
// forget() is supported.
}
Limites para o 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 algumas por vez ajuda a evitar essas limitações. Isso 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 para 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 transferências USB. Cada vez que um bloco é totalmente transmitido, ele notificará seu código informando que precisa 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 de USB no Chrome é mais fácil com a página interna about://device-log
,
onde você pode ver todos os eventos relacionados a dispositivos 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 WebUSB virtuais.
Isso é útil para fazer 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, é necessário adicionar 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 Arduino, por exemplo.
Também é possível adicionar ATTR{idProduct}
para definir uma regra mais específica. Verifique se user
é membro do grupo plugdev
. Depois, basta reconectar seu dispositivo.
Recursos
- Stack Overflow: https://stackoverflow.com/questions/tagged/webusb
- Especificações da API WebUSB: http://wicg.github.io/webusb/ (em inglês)
- Status do recurso do Chrome: https://www.chromestatus.com/feature/5651917954875392
- Problemas de especificação: https://github.com/WICG/webusb/issues (em inglês)
- Bugs de implementação: http://crbug.com?q=component:Blink>USB
- WebUSB ❤ ️Arduino: https://github.com/webusb/arduino (em inglês)
- 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 com a hashtag
#WebUSB
e conte para a gente onde e como você está usando a hashtag.
Agradecimentos
Agradecemos a Joe Medley por analisar este artigo.