La API de WebHID permite que los sitios web accedan a teclados auxiliares alternativos y controles de juegos exóticos.
Existe una larga gama de dispositivos de interfaz humana (HID), como teclados alternativos o controles de juegos exóticos, que son demasiado nuevos, demasiado antiguos o poco comunes para que los controladores de los dispositivos de los sistemas no puedan acceder a ellos. Para solucionar este problema, la API de WebHID proporciona una forma de implementar la lógica específica del dispositivo en JavaScript.
Casos de uso sugeridos
Los dispositivos HID toman entradas de personas o las proporcionan a las personas. Algunos ejemplos de dispositivos incluyen teclados, dispositivos apuntadores (mouse, pantalla táctil, etc.) y controles de juegos. El protocolo HID permite acceder a estos dispositivos en computadoras de escritorio mediante controladores del sistema operativo. La plataforma web admite dispositivos HID ya que depende de estos controladores.
La incapacidad para acceder a dispositivos HID poco comunes es particularmente problemática cuando se trata de teclados auxiliares alternativos (p.ej., Elgato Stream Deck, auriculares Jabra, teclas X) y compatibilidad con controles de juegos exóticos. Los controles de juegos diseñados para computadoras a menudo usan HID para las entradas (botones, joysticks, gatillos) y salidas (LED y ruidos) de los controles de juegos. Lamentablemente, las entradas y salidas del control de juegos no están bien estandarizadas y los navegadores web a menudo requieren una lógica personalizada para dispositivos específicos. Esto es insostenible y genera una mala compatibilidad con dispositivos antiguos y poco comunes. También hace que el navegador dependa de las peculiaridades en el comportamiento de dispositivos específicos.
Terminología
HID consta de dos conceptos fundamentales: informes y descriptores de informes. Los informes son los datos que se intercambian entre un dispositivo y un cliente de software. El descriptor de informes describe el formato y el significado de los datos que admite el dispositivo.
Un HID (dispositivo de interfaz humana) es un tipo de dispositivo que recibe entradas de humanos o proporciona salidas a ellas. También hace referencia al protocolo HID, un estándar para la comunicación bidireccional entre un host y un dispositivo que está diseñado para simplificar el procedimiento de instalación. El protocolo HID se desarrolló originalmente para dispositivos USB, pero desde entonces se implementó sobre muchos otros protocolos, incluido Bluetooth.
Las aplicaciones y los dispositivos HID intercambian datos binarios a través de tres tipos de informes:
Tipo de informe | Descripción |
---|---|
Informe de entradas | Datos que se envían del dispositivo a la aplicación (p. ej., cuando se presiona un botón) |
Informe de salida | Datos que se envían desde la aplicación al dispositivo (p. ej., una solicitud para activar la retroiluminación del teclado) |
Informe de funciones | Datos que pueden enviarse en cualquier dirección. El formato es específico del dispositivo. |
Un descriptor de informe describe el formato binario de los informes que admite el dispositivo. Su estructura es jerárquica y puede agrupar informes como colecciones distintas dentro de la colección de nivel superior. La especificación HID define el formato del descriptor.
El uso de HID es un valor numérico que hace referencia a una entrada o salida estandarizada. Los valores de uso permiten que un dispositivo describa el uso previsto del dispositivo y el propósito de cada campo en sus informes. Por ejemplo, uno se define para el botón izquierdo de un mouse. Los usos también se organizan en páginas de uso, que proporcionan una indicación de la categoría de alto nivel del dispositivo o informe.
Usa la API de WebHID
Detección de funciones
Para verificar si la API de WebHID es compatible, usa el siguiente comando:
if ("hid" in navigator) {
// The WebHID API is supported.
}
Abrir una conexión HID
La API de WebHID tiene un diseño asíncrono para evitar que la IU del sitio web se bloquee cuando se espera una entrada. Esto es importante porque los datos HID se pueden recibir en cualquier momento y esto requiere una forma de escucharlos.
Para abrir una conexión HID, primero accede a un objeto HIDDevice
. Para ello, puedes pedirle al usuario que seleccione un dispositivo llamando a navigator.hid.requestDevice()
, o elegir uno de navigator.hid.getDevices()
, que muestra una lista de dispositivos a los que el sitio web tiene acceso anteriormente.
La función navigator.hid.requestDevice()
toma un objeto obligatorio que define filtros. Estos se usan para hacer coincidir cualquier dispositivo conectado con un identificador de proveedor USB (vendorId
), un identificador de producto USB (productId
), un valor de página de uso (usagePage
) y un valor de uso (usage
). Puedes obtenerlos del repositorio de ID de USB y el documento de tablas de uso de HID.
Los distintos objetos HIDDevice
que muestra esta función representan varias interfaces HID en el mismo dispositivo físico.
// Filter on devices with the Nintendo Switch Joy-Con USB Vendor/Product IDs.
const filters = [
{
vendorId: 0x057e, // Nintendo Co., Ltd
productId: 0x2006 // Joy-Con Left
},
{
vendorId: 0x057e, // Nintendo Co., Ltd
productId: 0x2007 // Joy-Con Right
}
];
// Prompt user to select a Joy-Con device.
const [device] = await navigator.hid.requestDevice({ filters });
// Get all devices the user has previously granted the website access to.
const devices = await navigator.hid.getDevices();
También puedes usar la clave exclusionFilters
opcional en navigator.hid.requestDevice()
para excluir del selector de navegador algunos dispositivos que se sabe que no funcionan correctamente, por ejemplo.
// Request access to a device with vendor ID 0xABCD. The device must also have
// a collection with usage page Consumer (0x000C) and usage ID Consumer
// Control (0x0001). The device with product ID 0x1234 is malfunctioning.
const [device] = await navigator.hid.requestDevice({
filters: [{ vendorId: 0xabcd, usagePage: 0x000c, usage: 0x0001 }],
exclusionFilters: [{ vendorId: 0xabcd, productId: 0x1234 }],
});
Un objeto HIDDevice
contiene identificadores USB de proveedores y productos para la identificación de dispositivos. Su atributo collections
se inicializa con una descripción jerárquica de los formatos de informe del dispositivo.
for (let collection of device.collections) {
// An HID collection includes usage, usage page, reports, and subcollections.
console.log(`Usage: ${collection.usage}`);
console.log(`Usage page: ${collection.usagePage}`);
for (let inputReport of collection.inputReports) {
console.log(`Input report: ${inputReport.reportId}`);
// Loop through inputReport.items
}
for (let outputReport of collection.outputReports) {
console.log(`Output report: ${outputReport.reportId}`);
// Loop through outputReport.items
}
for (let featureReport of collection.featureReports) {
console.log(`Feature report: ${featureReport.reportId}`);
// Loop through featureReport.items
}
// Loop through subcollections with collection.children
}
De forma predeterminada, los dispositivos HIDDevice
se muestran en un estado "cerrado" y deben abrirse llamando a open()
antes de que se puedan enviar o recibir datos.
// Wait for the HID connection to open before sending/receiving data.
await device.open();
Recibir informes de entrada
Una vez que se haya establecido la conexión HID, podrás controlar los informes de entrada entrantes escuchando los eventos "inputreport"
del dispositivo. Esos eventos contienen los datos HID como un objeto DataView
(data
), el dispositivo HID al que pertenece (device
) y el ID de informe de 8 bits asociado con el informe de entrada (reportId
).
Siguiendo con el ejemplo anterior, el siguiente código te muestra cómo detectar qué botón presionó el usuario en un dispositivo Joy-Con derecho para que puedas probarlo en casa.
device.addEventListener("inputreport", event => {
const { data, device, reportId } = event;
// Handle only the Joy-Con Right device and a specific report ID.
if (device.productId !== 0x2007 && reportId !== 0x3f) return;
const value = data.getUint8(0);
if (value === 0) return;
const someButtons = { 1: "A", 2: "X", 4: "B", 8: "Y" };
console.log(`User pressed button ${someButtons[value]}.`);
});
Enviar informes de salida
Para enviar un informe de salida a un dispositivo HID, pasa el ID del informe de 8 bits asociado con el informe de salida (reportId
) y los bytes como un BufferSource
(data
) a device.sendReport()
. La promesa que se muestra se resuelve una vez que se envía el informe. Si el dispositivo HID no usa IDs de informes, establece reportId
en 0.
En el siguiente ejemplo, se aplica a un dispositivo Joy-Con y se muestra cómo hacer que sea ruidoso con informes de salida.
// First, send a command to enable vibration.
// Magical bytes come from https://github.com/mzyy94/joycon-toolweb
const enableVibrationData = [1, 0, 1, 64, 64, 0, 1, 64, 64, 0x48, 0x01];
await device.sendReport(0x01, new Uint8Array(enableVibrationData));
// Then, send a command to make the Joy-Con device rumble.
// Actual bytes are available in the sample below.
const rumbleData = [ /* ... */ ];
await device.sendReport(0x10, new Uint8Array(rumbleData));
Enviar y recibir informes de funciones
Los informes de elementos son el único tipo de informes de datos HID que pueden desplazarse en ambas direcciones. Permiten que los dispositivos y las aplicaciones HID intercambien datos HID no estandarizados. A diferencia de los informes de entrada y salida, la aplicación no recibe ni envía informes de funciones con regularidad.
Para enviar un informe de funciones a un dispositivo HID, pasa el ID del informe de 8 bits asociado con el informe de funciones (reportId
) y los bytes como BufferSource
(data
) a device.sendFeatureReport()
. La promesa que se muestra se resuelve una vez que se envía el informe. Si el dispositivo HID no usa IDs de informes, establece reportId
en 0.
En el siguiente ejemplo, se ilustra el uso de los informes de funciones, en los que se muestra cómo solicitar un dispositivo de retroiluminación del teclado de Apple, abrirlo y hacer que parpadee.
const waitFor = duration => new Promise(r => setTimeout(r, duration));
// Prompt user to select an Apple Keyboard Backlight device.
const [device] = await navigator.hid.requestDevice({
filters: [{ vendorId: 0x05ac, usage: 0x0f, usagePage: 0xff00 }]
});
// Wait for the HID connection to open.
await device.open();
// Blink!
const reportId = 1;
for (let i = 0; i < 10; i++) {
// Turn off
await device.sendFeatureReport(reportId, Uint32Array.from([0, 0]));
await waitFor(100);
// Turn on
await device.sendFeatureReport(reportId, Uint32Array.from([512, 0]));
await waitFor(100);
}
Para recibir un informe de funciones de un dispositivo HID, pasa el ID de informe de 8 bits asociado con el informe de funciones (reportId
) a device.receiveFeatureReport()
. La promesa que se muestra se resuelve con un objeto DataView
que incluye el contenido del informe de funciones. Si el dispositivo HID no usa IDs de informes, establece reportId
en 0.
// Request feature report.
const dataView = await device.receiveFeatureReport(/* reportId= */ 1);
// Read feature report contents with dataView.getInt8(), getUint8(), etc...
Detectar conexión y desconexión
Cuando el sitio web tiene permiso para acceder a un dispositivo HID, puede recibir eventos de conexión y desconexión de forma activa si escucha los eventos "connect"
y "disconnect"
.
navigator.hid.addEventListener("connect", event => {
// Automatically open event.device or warn user a device is available.
});
navigator.hid.addEventListener("disconnect", event => {
// Remove |event.device| from the UI.
});
Cómo revocar el acceso a un dispositivo HID
El sitio web puede liberar permisos para acceder a un dispositivo HID que ya no le interesa conservar llamando a forget()
en la instancia HIDDevice
. Por ejemplo, en el caso de una aplicación web educativa que se usa en una computadora compartida con muchos dispositivos, una gran cantidad de permisos acumulados generados por el usuario crea una experiencia del usuario deficiente.
Si llamas a forget()
en una sola instancia de HIDDevice
, se revocará el acceso a todas las interfaces HID en el mismo dispositivo físico.
// Voluntarily revoke access to this HID device.
await device.forget();
Dado que forget()
está disponible en Chrome 100 o versiones posteriores, verifica si esta función es compatible con lo siguiente:
if ("hid" in navigator && "forget" in HIDDevice.prototype) {
// forget() is supported.
}
Sugerencias para desarrolladores
La depuración de HID en Chrome es fácil con la página interna, about://device-log
, en la que puedes ver todos los eventos relacionados con los dispositivos HID y USB en un solo lugar.
Para consultar el explorador de HID, consulta el volcado de información de los dispositivos HID en un formato legible. Se asigna a partir de los valores de uso a los nombres de cada uso de HID.
En la mayoría de los sistemas Linux, los dispositivos HID se asignan con permisos de solo lectura de forma predeterminada. Para permitir que Chrome abra un dispositivo HID, deberás agregar una nueva regla udev. Crea un archivo en /etc/udev/rules.d/50-yourdevicename.rules
con el siguiente contenido:
KERNEL=="hidraw*", ATTRS{idVendor}=="[yourdevicevendor]", MODE="0664", GROUP="plugdev"
En la línea anterior, [yourdevicevendor]
es 057e
si, por ejemplo, tu dispositivo es un Nintendo Switch Joy-Con. También se puede agregar ATTRS{idProduct}
para una regla más específica. Asegúrate de que tu user
sea un miembro del grupo plugdev
. Luego, solo debes volver a conectar el dispositivo.
Navegadores compatibles
La API de WebHID está disponible en todas las plataformas de escritorio (ChromeOS, Linux, macOS y Windows) en Chrome 89.
Demostraciones
Algunas demostraciones de WebHID se enumeran en web.dev/hid-examples. ¡Échale un vistazo!
Seguridad y privacidad
Los autores de las especificaciones diseñaron e implementaron la API de WebHID con los principios básicos definidos en Cómo controlar el acceso a funciones potentes de la plataforma web, incluidos el control de usuario, la transparencia y la ergonomía. La capacidad de usar esta API está restringida principalmente por un modelo de permisos que otorga acceso a un solo dispositivo HID a la vez. En respuesta a un mensaje del usuario, este debe realizar pasos activos para seleccionar un dispositivo HID en particular.
Para comprender las compensaciones de seguridad, consulta la sección Consideraciones de seguridad y privacidad de la especificación de WebHID.
Además, Chrome inspecciona el uso de cada colección de nivel superior y, si una colección tiene un uso protegido (p.ej., teclado genérico o mouse), el sitio web no podrá enviar ni recibir informes definidos en esa colección. La lista completa de usos protegidos está disponible de forma pública.
Ten en cuenta que los dispositivos HID sensibles a la seguridad (como los dispositivos HID FIDO que se usan para una autenticación más sólida) también se bloquean en Chrome. Consulta los archivos de la lista de elementos bloqueados de USB y la lista de elementos bloqueados de HID.
Comentarios
Al equipo de Chrome le encantaría conocer tus opiniones y experiencias con la API de WebHID.
Cuéntanos sobre el diseño de la API
¿Hay algo acerca de la API que no funciona como se espera? ¿O faltan métodos o propiedades que necesitas para implementar tu idea?
Informa sobre un problema de especificaciones en el repositorio de GitHub de la API de WebHID o agrega tus ideas a un problema existente.
Informar un problema con la implementación
¿Encontraste un error en la implementación de Chrome? ¿La implementación es diferente de las especificaciones?
Consulta Cómo informar errores de WebHID. Asegúrate de incluir todos los detalles que puedas, proporcionar instrucciones simples para reproducir el error y configurar los Componentes como Blink>HID
. Glitch funciona muy bien para compartir repros rápidos y fáciles.
Muestra tu apoyo
¿Planeas usar la API de WebHID? Tu apoyo público ayuda al equipo de Chrome a priorizar funciones y les muestra a otros proveedores de navegadores la importancia de brindar compatibilidad.
Envía un tweet a @ChromiumDev con el hashtag #WebHID
y cuéntanos dónde y cómo lo usas.
Vínculos útiles
- Especificación
- Error de seguimiento
- Entrada de ChromeStatus.com
- Componente de Blink:
Blink>HID
Agradecimientos
Agradecemos a Matt Reynolds y Joe Medley por sus opiniones sobre este artículo. Foto de Nintendo Switch roja y azul de Sara Kurfeß, y una foto de laptop en negro y plateado de Athul Cyriac Ajay en Unsplash