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

L'API WebUSB rend la clé USB plus sûre et plus facile à utiliser en la mettant sur le Web.

François Beaufort
François Beaufort

Si je disais simplement « USB », il y a de fortes chances que vous pensez immédiatement aux claviers, aux souris, aux périphériques audio, vidéo et de stockage. Vous êtes mais vous trouverez d'autres types de périphériques USB (Universal Serial Bus) ici.

Ces périphériques USB non standardisés nécessitent des fournisseurs de matériel qu'ils écrivent des données spécifiques à la plate-forme les pilotes et les SDK pour que vous (le développeur) en profitiez. Malheureusement, ce code spécifique à la plate-forme a toujours empêché ces appareils d'être utilisés 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 de périphériques USB au Web. Avec cette API, le matériel les fabricants pourront créer des SDK JavaScript multiplates-formes pour leurs appareils.

Mais surtout, cela rendra l'utilisation de la clé USB plus sûre et plus facile à utiliser sur le 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 bouton site Web vers lequel accéder pour cet appareil.
  3. Cliquez sur la notification. Le site Web est prêt à l'emploi !
  4. Cliquez pour vous connecter. Un sélecteur de périphérique USB s'affiche dans Chrome, où vous pouvez choisissez votre appareil.

Et voilà !

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

  1. Installer 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 du système d'exploitation ni de pop-ups effrayants vous prévient de l'installation de pilotes/d'applications depuis Internet. Si vous n'avez pas de chance, les pilotes ou les applications installés fonctionnent mal et votre ordinateur. N'oubliez pas que le Web est conçu pour contenir les applications sites Web).
  4. Si vous n'utilisez la fonctionnalité qu'une seule fois, le code reste sur votre ordinateur jusqu'à ce que vous pensez à la supprimer. (Sur le Web, l'espace pour les ressources inutilisées reclaimed.)

Avant de commencer

Cet article suppose que vous avez des connaissances de base sur le fonctionnement de la clé USB. Sinon, je nous vous recommandons de lire l'article USB dans NutShell. Pour obtenir des informations contextuelles sur USB, consultez les caractéristiques USB officielles.

L'API WebUSB est disponible dans Chrome 61.

Disponible pour les phases d'évaluation

Afin de recevoir autant de commentaires que possible de la part des développeurs utilisant l'interface WebUSB API dans le champ, nous l'avons déjà ajoutée dans Chrome 54 et 57 en tant que phase 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 les contextes sécurisés. Cela signifie vous devrez compiler en tenant compte de TLS.

Geste de l'utilisateur requis

Par mesure de sécurité, navigator.usb.requestDevice() ne peut être appelé par un geste de l'utilisateur comme 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 de manière sélective et désactive diverses fonctionnalités et API du navigateur. Elle peut être définie via une requête un en-tête et/ou un iFrame "allow" .

Vous pouvez définir une règle d'autorisation qui contrôle si l'attribut usb est sur l'objet Navigateur, ou en d'autres termes, 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 la connexion USB:

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

Commençons à coder

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

Accéder aux périphériques USB

Vous pouvez soit inviter l'utilisateur à sélectionner un seul périphérique USB connecté en utilisant navigator.usb.requestDevice() ou appelez navigator.usb.getDevices() pour obtenir une la liste de tous les appareils USB connectés auxquels le site Web a accès.

La fonction navigator.usb.requestDevice() utilise un objet JavaScript obligatoire qui définit filters. Ces filtres sont utilisés pour faire correspondre n'importe quel périphérique USB avec le les identifiants donnés du fournisseur (vendorId) et, éventuellement, du produit (productId). Les clés classCode, protocolCode, serialNumber et subclassCode peuvent également y être défini.

<ph type="x-smartling-placeholder">
</ph> Capture d&#39;écran de l&#39;invite utilisateur d&#39;un appareil USB dans Chrome
Invite de l'utilisateur d'un appareil USB.

Par exemple, voici comment accéder à un appareil Arduino connecté 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 de poser la question, je n'ai pas trouvé par magie la valeur hexadécimale 0x2341 numéro. J'ai simplement recherché le mot "Arduino" figurant dans cette liste d'ID USB.

Le port USB device renvoyé dans la promesse exécutée ci-dessus possède des éléments de base, mais des informations importantes sur l'appareil, telles que la version USB compatible, la taille maximale des paquets, les ID de fournisseur et de produit, le nombre d'identifiants de configuration de l'appareil. Il contient en fait tous les champs 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 indique qu'il est compatible avec WebUSB, ainsi que définir l'URL d'une page de destination, Chrome affiche une notification persistante Le périphérique USB est branché. Cliquez sur cette notification pour ouvrir la page de destination.

<ph type="x-smartling-placeholder">
</ph> Capture d&#39;écran de la notification WebUSB dans Chrome
Notification WebUSB

Parler à une carte USB Arduino

Voyons maintenant à quel point il est facile de communiquer depuis un câble Carte Arduino sur le port USB. Consultez les instructions à l’adresse https://github.com/webusb/arduino permet d'activer vos croquis via WebUSB.

Ne vous inquiétez pas, je couvrirai toutes les méthodes de périphérique WebUSB mentionnées ci-dessous plus tard dans cet article.

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

Gardez à l'esprit que la bibliothèque WebUSB que j'utilise ne fait qu'implémenter un exemple de protocole (basé sur le protocole série USB standard), les fabricants peuvent créer tous les ensembles et types de points de terminaison de leur choix. Les transferts de contrôle sont particulièrement utiles pour les petites commandes de configuration, ils reçoivent la priorité 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 WebUSB Arduino tierce utilisée dans l'exemple de code ci-dessus accepte en fait deux choses:

  • L'appareil fait office d'appareil 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 device, device.open() exécute toutes les étapes spécifiques à la plate-forme pour démarrer une session avec la clé USB. appareil. Ensuite, je dois sélectionner une configuration USB disponible avec device.selectConfiguration() Rappelez-vous qu'une configuration spécifie la manière est alimenté, sa consommation d'énergie maximale et le nombre d'interfaces. En parlant d'interfaces, je dois également demander un accès exclusif avec device.claimInterface(), car les données ne peuvent être transférées que vers une interface associés lorsque l'interface est revendiquée. Enfin, j'appelle device.controlTransferOut() est nécessaire pour configurer l'appareil Arduino avec la les commandes appropriées pour communiquer via l'API WebUSB Serial.

Ensuite, device.transferIn() effectue un transfert groupé vers le 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ée de manière appropriée.

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

J'en veux plus

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

  • Les transferts CONTROL permettent d'envoyer ou de recevoir des configurations ou des commandes à un appareil USB sont gérés avec controlTransferIn(setup, length) et controlTransferOut(setup, data).
  • Les transferts d'INTERRUPTION, utilisés pour une petite quantité de données sensibles dans le temps, sont géré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 géré avec isochronousTransferIn(endpointNumber, packetLengths) et isochronousTransferOut(endpointNumber, data, packetLengths)
  • Les transferts groupés permettent de transférer une grande quantité de données non urgentes de manière fiable, sont gérés avec transferIn(endpointNumber, length) et transferOut(endpointNumber, data)

Vous pouvez également regarder le projet WebLight de Mike Tsao, fournit un exemple complet de la construction d'un dispositif LED contrôlé par USB conçu pour l'API WebUSB (sans Arduino ici). Vous trouverez du matériel, des logiciels, et du micrologiciel.

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

Le site Web peut supprimer les autorisations d'accès à un périphérique 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 le nombre d'autorisations générées par l'utilisateur nuit à 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 taille de transfert

Certains systèmes d'exploitation imposent des limites sur la quantité de données pouvant faire partie transactions USB en attente. Diviser vos données en transactions plus petites et uniquement en soumettant quelques-uns à 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 transferts terminés.

Étant donné que les transferts multiples envoyés à un point de terminaison s'exécutent toujours dans l'ordre, possible d'améliorer le débit en envoyant plusieurs fragments en file d'attente pour éviter la latence entre les transferts USB. Chaque fois qu'un fragment est entièrement transmis, signaler au code qu'il doit fournir plus de données, comme indiqué dans l'application 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 USB dans Chrome est plus facile grâce à la page interne about://device-log où vous pouvez voir tous les événements liés aux périphériques USB en un seul endroit.

<ph type="x-smartling-placeholder">
</ph> Capture d&#39;écran de la page du journal de l&#39;appareil pour déboguer WebUSB dans Chrome
Page de journal de l'appareil dans Chrome pour déboguer l'API WebUSB

La page interne about://usb-internals est également pratique et vous permet pour simuler la connexion et la déconnexion de périphériques WebUSB virtuels. Cela est utile pour tester l'interface utilisateur sans matériel réel.

<ph type="x-smartling-placeholder">
</ph> Capture d&#39;écran de la page interne pour déboguer WebUSB dans Chrome
Page interne dans Chrome pour déboguer l'API WebUSB

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

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. Il vous suffira ensuite de reconnecter votre appareil.

Ressources

Envoyez un tweet à @ChromiumDev en utilisant le hashtag. #WebUSB et n'hésitez pas à nous dire où et comment vous l'utilisez.

Remerciements

Merci à Joe Medley d'avoir lu cet article.