Accéder aux périphériques USB sur le Web

L'API WebUSB rend l'interface USB plus sûre et plus facile à utiliser en l'intégrant au Web.

François Beaufort
François Beaufort

Si je disais simplement et simplement "USB", il y a de fortes chances que vous penseriez immédiatement aux claviers, aux souris, à l'audio, à la vidéo et aux périphériques de stockage. Vous avez raison, mais vous trouverez d'autres types de périphériques USB (Universal Serial Bus).

Ces appareils USB non standardisés nécessitent que les fournisseurs de matériel écrivent des pilotes et des SDK spécifiques à la plate-forme pour que vous (le développeur) en profitiez. Malheureusement, ce code spécifique à la plate-forme empêchait depuis longtemps l'utilisation de ces appareils par le Web. C'est l'une des raisons pour lesquelles l'API WebUSB a été créée: fournir un moyen d'exposer les services d'appareils USB sur le Web. Avec cette API, les fabricants de matériel pourront créer des SDK JavaScript multiplates-formes pour leurs appareils.

Mais surtout, cela renforcera la sécurité et la facilité d'utilisation de l'USB en l'intégrant au Web.

Voyons le comportement auquel vous pouvez vous attendre avec l'API WebUSB:

  1. Achetez un périphérique USB.
  2. Branchez-le à votre ordinateur. Une notification s'affiche immédiatement, avec le site Web approprié pour cet appareil.
  3. Cliquez sur la notification. Le site Web est là et prêt à être utilisé !
  4. Cliquez pour vous connecter. Un sélecteur de périphérique USB s'affiche dans Chrome, vous permettant de sélectionner votre appareil.

Et voilà !

À quoi ressemblerait cette procédure sans l'API WebUSB ?

  1. Installez une application spécifique à la plate-forme.
  2. Si mon système d'exploitation le prend en charge, vérifiez que j'ai téléchargé la bonne chose.
  3. Installez l'appareil. Avec un peu de chance, vous ne recevrez pas d'invites de système d'exploitation effrayantes ni de fenêtres pop-up vous avertissant de l'installation de pilotes/applications depuis Internet. En cas de malchance, les pilotes ou les applications installés ne fonctionnent pas correctement et nuisent à votre ordinateur. (N'oubliez pas que le Web est conçu pour contenir des sites Web défaillants.)
  4. Si vous n'utilisez cette fonctionnalité qu'une seule fois, le code reste sur votre ordinateur jusqu'à ce que vous décidiez de le supprimer. (Sur le Web, l'espace pour les espaces inutilisés est finalement récupéré.)

Avant de commencer

Cet article suppose que vous disposez de connaissances de base sur le fonctionnement de la clé USB. Si ce n'est pas le cas, nous vous recommandons de lire la documentation USB in a NutShell. Pour en savoir plus sur l'USB, consultez les caractéristiques USB officielles.

L'API WebUSB est disponible dans Chrome 61.

Disponible pour les phases d'évaluation

Afin de recueillir autant de commentaires que possible de la part des développeurs utilisant l'API WebUSB sur le terrain, nous avons précédemment ajouté cette fonctionnalité dans Chrome 54 et Chrome 57 en tant qu'évaluation d'évaluation.

Le dernier essai a pris fin en septembre 2017.

Confidentialité et sécurité

HTTPS uniquement

En raison de la puissance de cette fonctionnalité, elle ne fonctionne que dans des contextes sécurisés. Vous devez donc tenir compte du protocole TLS lors de la compilation.

Geste de l'utilisateur requis

Par mesure de sécurité, navigator.usb.requestDevice() ne peut être appelé que par un geste de l'utilisateur, tel qu'une pression ou un clic de souris.

Règles relatives aux autorisations

Une règle d'autorisation est un mécanisme qui permet aux développeurs d'activer et de désactiver de manière sélective diverses API et fonctionnalités du navigateur. Il peut être défini via un en-tête HTTP et/ou un attribut iFrame "allow".

Vous pouvez définir une règle d'autorisation qui contrôle si l'attribut usb est exposé sur l'objet Navigator ou, autrement dit, si vous autorisez WebUSB.

Vous trouverez ci-dessous un exemple de règle d'en-tête où WebUSB n'est pas autorisé:

Feature-Policy: fullscreen "*"; usb "none"; payment "self" https://payment.example.com

Vous trouverez ci-dessous un autre exemple de règle de conteneur autorisant l'utilisation de l'USB:

<iframe allowpaymentrequest allow="usb; fullscreen"></iframe>

Commençons à coder

L'API WebUSB s'appuie en grande partie sur les promesses JavaScript. Si vous ne les connaissez pas, consultez cet excellent tutoriel sur les promesses. Une dernière chose : les () => {} sont simplement des fonctions fléchées ECMAScript 2015.

Accéder à des périphériques USB

Vous pouvez inviter l'utilisateur à sélectionner un seul appareil USB connecté à l'aide de navigator.usb.requestDevice() ou appeler navigator.usb.getDevices() pour obtenir la liste de tous les appareils USB connectés auxquels le site Web a été autorisé à accéder.

La fonction navigator.usb.requestDevice() utilise un objet JavaScript obligatoire qui définit filters. Ces filtres permettent d'associer tout appareil USB au fournisseur (vendorId) donné et, éventuellement, à l'identifiant produit (productId). Vous pouvez également y définir les clés classCode, protocolCode, serialNumber et subclassCode.

Capture d&#39;écran de l&#39;invite utilisateur d&#39;un périphérique USB dans Chrome
Invite de l'utilisateur de l'appareil USB

Par exemple, voici comment accéder à un appareil Arduino connecté et configuré pour autoriser l'origine.

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); });

Avant que vous ne me le demandiez, je n'ai pas trouvé comme par magie le nombre hexadécimal 0x2341. J'ai simplement recherché le mot"Arduino" dans cette liste des identifiants USB.

La clé USB device renvoyée dans la promesse remplie ci-dessus contient des informations de base, mais importantes, sur l'appareil, telles que la version USB compatible, la taille maximale des paquets, le fournisseur et les ID produit, ainsi que le nombre de configurations possibles de l'appareil. Il contient en fait tous les champs du descripteur USB de l'appareil.

// 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"
  });
})

À ce propos, si un périphérique USB annonce sa compatibilité avec WebUSB et qu'il définit une URL de page de destination, Chrome affiche une notification permanente lorsque le périphérique USB est branché. Cliquez sur cette notification pour ouvrir la page de destination.

Capture d&#39;écran de la notification WebUSB dans Chrome
Notification WebUSB.

Parler à une carte USB Arduino

Bon, voyons maintenant à quel point il est facile de communiquer à partir d'une carte Arduino compatible WebUSB via le port USB. Consultez les instructions sur https://github.com/webusb/arduino pour activer WebUSB vos croquis.

Ne vous inquiétez pas, nous aborderons plus loin dans cet article toutes les méthodes de périphériques WebUSB mentionnées ci-dessous.

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); });

N'oubliez pas que la bibliothèque WebUSB que j'utilise n'implémente qu'un exemple de protocole (basé sur le protocole série USB standard) et que les fabricants peuvent créer n'importe quel ensemble et type de points de terminaison de leur choix. Les transferts de contrôle sont particulièrement utiles pour les petites commandes de configuration, car ils sont prioritaires sur les bus et ont une structure bien définie.

Et voici le croquis qui a été importé sur la carte 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.
}

La bibliothèque tierce WebUSB Arduino utilisée dans l'exemple de code ci-dessus effectue essentiellement deux actions:

  • L'appareil fait office de périphérique WebUSB, ce qui permet à Chrome de lire l'URL de la page de destination.
  • Il expose une API WebUSB Serial que vous pouvez utiliser pour remplacer l'API par défaut.

Examinez à nouveau le code JavaScript. Une fois que l'utilisateur a choisi le device, device.open() exécute toutes les étapes spécifiques à la plate-forme pour démarrer une session avec l'appareil USB. Ensuite, je dois sélectionner une configuration USB disponible avec device.selectConfiguration(). N'oubliez pas qu'une configuration spécifie la manière dont l'appareil est alimenté, sa consommation d'énergie maximale et son nombre d'interfaces. En ce qui concerne les interfaces, je dois également demander un accès exclusif à device.claimInterface(), car les données ne peuvent être transférées vers une interface ou des points de terminaison associés que lorsque l'interface est revendiquée. Enfin, il est nécessaire d'appeler device.controlTransferOut() pour configurer l'appareil Arduino avec les commandes appropriées afin de communiquer via l'API WebUSB Serial.

À partir de là, device.transferIn() effectue un transfert groupé sur l'appareil pour l'informer que l'hôte est prêt à recevoir des données groupées. Ensuite, la promesse est remplie avec un objet result contenant un data DataView qui doit être analysé de manière appropriée.

Si vous connaissez l'USB, tout cela devrait vous sembler assez familier.

Je veux plus

L'API WebUSB vous permet d'interagir avec tous les types de transferts/points de terminaison USB:

  • Les transferts de contrôle, qui permettent d'envoyer ou de recevoir des paramètres de configuration ou de commande à un appareil USB, sont gérés avec controlTransferIn(setup, length) et controlTransferOut(setup, data).
  • Les transferts INTERRUPT, utilisés pour une petite quantité de données sensibles, sont traités avec les mêmes méthodes que les transferts BULK avec transferIn(endpointNumber, length) et transferOut(endpointNumber, data).
  • Les transferts ISOCHRONOUS, utilisés pour les flux de données tels que la vidéo et le son, sont traités avec isochronousTransferIn(endpointNumber, packetLengths) et isochronousTransferOut(endpointNumber, data, packetLengths).
  • Les transferts groupés, utilisés pour transférer de manière fiable une grande quantité de données non urgentes, sont gérés avec transferIn(endpointNumber, length) et transferOut(endpointNumber, data).

Vous pouvez également jeter un œil au projet WebLight de Mike Tsao, qui fournit un exemple concret de création d'un appareil LED contrôlé par USB conçu pour l'API WebUSB (sans utiliser d'Arduino ici). Vous trouverez le matériel, les logiciels et les micrologiciels.

Révoquer l'accès d'un périphérique USB

Le site Web peut nettoyer les autorisations d'accès à un appareil USB dont il n'a plus besoin en appelant forget() sur l'instance USBDevice. Par exemple, pour une application Web éducative utilisée sur un ordinateur partagé avec de nombreux appareils, un grand nombre d'autorisations générées par les utilisateurs nuisent à l'expérience utilisateur.

// Voluntarily revoke access to this USB device.
await device.forget();

Comme forget() est disponible dans Chrome 101 ou version ultérieure, vérifiez si cette fonctionnalité est compatible avec les éléments suivants:

if ("usb" in navigator && "forget" in USBDevice.prototype) {
  // forget() is supported.
}

Limites de la taille de transfert

Certains systèmes d'exploitation imposent des limites sur la quantité de données pouvant faire partie des transactions USB en attente. Diviser vos données en transactions plus petites et n'en envoyer que quelques-unes à la fois permet d'éviter ces limites. Cela réduit également la quantité de mémoire utilisée et permet à votre application de signaler la progression des transferts.

Étant donné que plusieurs transferts soumis à un point de terminaison s'exécutent toujours dans l'ordre, il est possible d'améliorer le débit en envoyant plusieurs fragments en file d'attente afin d'éviter la latence entre les transferts USB. Chaque fois qu'un fragment est entièrement transmis, votre code est informé qu'il doit fournir plus de données, comme indiqué dans l'exemple de fonction d'assistance ci-dessous.

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);
}

Conseils

Le débogage des clés USB dans Chrome est plus facile grâce à la page interne about://device-log, qui rassemble tous les événements liés aux périphériques USB au même endroit.

Capture d&#39;écran de la page du journal de l&#39;appareil pour déboguer WebUSB dans Chrome
Page du journal de l'appareil dans Chrome pour déboguer l'API WebUSB

La page interne about://usb-internals est également pratique et vous permet de simuler la connexion et la déconnexion d'appareils WebUSB virtuels. Cela est utile pour effectuer des tests d'interface utilisateur sans véritable matériel.

Capture d&#39;écran de la page interne permettant de déboguer WebUSB dans Chrome
Page interne de Chrome pour le débogage de l'API WebUSB

Sur la plupart des systèmes Linux, les périphériques USB sont mappés avec des autorisations en lecture seule par défaut. Pour autoriser Chrome à ouvrir un périphérique USB, vous devez ajouter une nouvelle règle udev. Créez un fichier à l'emplacement /etc/udev/rules.d/50-yourdevicename.rules avec le contenu suivant:

SUBSYSTEM=="usb", ATTR{idVendor}=="[yourdevicevendor]", MODE="0664", GROUP="plugdev"

[yourdevicevendor] est 2341 si votre appareil est un Arduino, par exemple. Vous pouvez également ajouter ATTR{idProduct} pour une règle plus spécifique. Assurez-vous que votre user est un membre du groupe plugdev. Ensuite, il vous suffit de reconnecter votre appareil.

Ressources

Envoyez un tweet à @ChromiumDev avec le hashtag #WebUSB, et indiquez-nous où et comment vous l'utilisez.

Remerciements

Merci à Joe Medley d'avoir consulté cet article.