Périphériques USB

Ce document explique comment utiliser l'API USB pour communiquer avec des appareils USB. Certains appareils ne sont pas accessibles via l'API USB (pour en savoir plus, consultez la section Mises en garde ci-dessous). Les applications Chrome peuvent également se connecter à des appareils serial et Bluetooth.

Pour en savoir plus sur la connectivité USB, consultez les caractéristiques USB officielles. USB in a NutShell est un cours d'initiation raisonnable qui pourrait vous être utile.

Exigence du fichier manifeste

L'API USB nécessite l'autorisation "usb" dans le fichier manifeste:

"permissions": [
  "usb"
]

En outre, afin d'empêcher l'empreinte digitale, vous devez déclarer tous les types d'appareils auxquels vous souhaitez accéder dans le fichier manifeste. Chaque type d'appareil USB correspond à une paire ID fournisseur/ID produit (VID/PID). Vous pouvez utiliser usb.getDevices pour énumérer les appareils en fonction de leur paire VID/PID.

Vous devez déclarer les paires VID/PID pour chaque type d'appareil que vous souhaitez utiliser sous l'autorisation usbDevices dans le fichier manifeste de votre application, comme illustré dans l'exemple ci-dessous:

"permissions": [
  {
    "usbDevices": [
      {
        "vendorId": 123,
        "productId": 456
      }
    ]
  }
]

Depuis Chrome 57, l'obligation de déclarer tous les types d'appareils dans le fichier manifeste de l'application est assouplie pour les applications exécutées en tant qu'applications kiosque ChromeOS. Pour les applications kiosque, vous pouvez utiliser la propriété d'autorisation interfaceClass pour demander l'autorisation d'accéder aux appareils USB qui:

  • implémenter une interface USB d'une classe d'interface spécifique
  • possèdent une classe de périphérique USB

Par exemple, l'autorisation usbDevices suivante autorise une application à accéder à tous les appareils USB qui implémentent une interface d'imprimante (code de classe d'interface 7) et aux hubs USB (code de classe d'appareil 9):

"permissions": [
  {
    "usbDevices": [
      {"interfaceClass": 7},
      {"interfaceClass": 9}
    ]
  }
]

Pour obtenir la liste des valeurs interfaceClass acceptables, consultez les codes de classe USB.

La propriété interfaceClass peut être combinée à la propriété vendorId pour n'accéder qu'aux appareils USB d'un fournisseur spécifique, comme illustré dans l'exemple suivant:

"permissions": [
  {
    "usbDevices": [
      {
        "vendorId": 123,
        "interfaceClass": 7
      }
    ]
  }
]

Trouver un appareil

Pour déterminer si un ou plusieurs appareils spécifiques sont connectés au système d'un utilisateur, utilisez la méthode usb.getDevices:

chrome.usb.getDevices(enumerateDevicesOptions, callback);
Paramètre (type)Description
EnumerateDevicesOptions (objet)Objet spécifiant à la fois un vendorId (long) et un productId (long) utilisé pour trouver le type d'appareil approprié dans le bus. Votre fichier manifeste doit déclarer la section des autorisations usbDevices qui répertorie toutes les paires vendorId et deviceId auxquelles votre application souhaite accéder.
rappel (fonction)Appelée lorsque l'énumération de l'appareil est terminée. Le rappel sera exécuté avec un paramètre, un tableau d'objets Device avec trois propriétés: device, vendorId et productId. La propriété "device" est un identifiant stable pour un appareil connecté. Il ne changera qu'une fois l'appareil débranché. Les détails de l'identifiant sont opaques et peuvent changer. Ne vous fiez pas à son type actuel.
Si aucun appareil n'est détecté, le tableau sera vide.

Exemple :

function onDeviceFound(devices) {
  this.devices=devices;
  if (devices) {
    if (devices.length > 0) {
      console.log("Device(s) found: "+devices.length);
    } else {
      console.log("Device could not be found");
    }
  } else {
    console.log("Permission denied.");
  }
}

chrome.usb.getDevices({"vendorId": vendorId, "productId": productId}, onDeviceFound);

Ouverture d'un appareil

Une fois les objets Device renvoyés, vous pouvez ouvrir un appareil à l'aide de usb.openDevice pour obtenir un handle de connexion. Vous ne pouvez communiquer avec des périphériques USB qu'à l'aide d'identifiants de connexion.

PropriétéDescription
appareilObjet reçu dans le rappel usb.getDevices.
données (arraybuffer)Contient les données envoyées par l'appareil si le transfert est entrant.

Exemple :

var usbConnection = null;
var onOpenCallback = function(connection) {
  if (connection) {
    usbConnection = connection;
    console.log("Device opened.");
  } else {
    console.log("Device failed to open.");
  }
};

chrome.usb.openDevice(device, onOpenCallback);

Pour simplifier le processus d'ouverture, vous pouvez utiliser la méthode usb.findDevices, qui énumère, demande l'accès et ouvre les appareils en un seul appel:

chrome.usb.findDevices({"vendorId": vendorId, "productId": productId, "interfaceId": interfaceId}, callback);

ce qui équivaut à:

chrome.usb.getDevices({"vendorId": vendorId, "productId": productId}, function (devices) {
  if (!devices) {
    console.log("Error enumerating devices.");
    callback();
    return;
  }
  var connections = [], pendingAccessRequests = devices.length;
  devices.forEach(function (device) {
    chrome.usb.requestAccess(interfaceId, function () {
      // No need to check for errors at this point.
      // Nothing can be done if an error occurs anyway. You should always try
      // to open the device.
      chrome.usb.openDevices(device, function (connection) {
        if (connection) connections.push(connection);
        pendingAccessRequests--;
        if (pendingAccessRequests == 0) {
          callback(connections);
        }
      });
    });
  })
});

Transfert et réception de données via USB depuis un appareil

Le protocole USB définit quatre types de transferts: contrôle, groupé, isochronique et interruption. Ces transferts sont décrits ci-dessous.

Les transferts peuvent avoir lieu dans les deux sens: de l'appareil à l'hôte (entrant) et de l'hôte à l'appareil (sortant). En raison de la nature du protocole USB, les messages entrants et sortants doivent être initiés par l'hôte (l'ordinateur qui exécute l'application Chrome). Pour les messages entrants (d'appareil à hôte), l'hôte (lancé par votre code JavaScript) envoie un message signalé comme "entrant" à l'appareil. Les détails du message dépendent de l'appareil, mais permettent généralement d'identifier ce que vous demandez. L'appareil répond ensuite avec les données demandées. La réponse de l'appareil est gérée par Chrome et envoyée de manière asynchrone au rappel que vous spécifiez dans la méthode de transfert. Un message sortant (d'hôte à appareil) est similaire, mais la réponse ne contient pas les données renvoyées par l'appareil.

Pour chaque message provenant de l'appareil, le rappel spécifié reçoit un objet d'événement avec les propriétés suivantes:

PropriétéDescription
résultatCode (entier)0 correspond à une réussite ; les autres valeurs indiquent un échec. Une chaîne d'erreur peut être
lue à partir de chrome.extension.lastError lorsqu'un échec est
indiqué.
données (arraybuffer)Contient les données envoyées par l'appareil si le transfert est entrant.

Exemple :

var onTransferCallback = function(event) {
   if (event && event.resultCode === 0 && event.data) {
     console.log("got " + event.data.byteLength + " bytes");
   }
};

chrome.usb.bulkTransfer(connectionHandle, transferInfo, onTransferCallback);

CONTROL correspondances

Les transferts de contrôle sont généralement utilisés pour envoyer ou recevoir des paramètres de configuration ou de commande à un périphérique USB. La méthode controlTransfer envoie toujours au point de terminaison 0 ou lit toujours les données à partir du point de terminaison 0, et aucune ClaimInterface n'est requise. Cette méthode est simple et reçoit trois paramètres:

chrome.usb.controlTransfer(connectionHandle, transferInfo, transferCallback)
Paramètre (types)Description
connectionHandleObjet reçu dans un rappel usb.openDevice.
transferInfoObjet de paramètre avec les valeurs du tableau ci-dessous. Pour en savoir plus, consultez les spécifications du protocole de votre périphérique USB.
transferCallback()Appelée une fois le transfert terminé.

Valeurs de l'objet transferInfo:

ValeurDescription
requestType (chaîne)"fournisseur", "standard", "classe" ou "réservé".
destinataire (chaîne)"device", "interface", "endpoint" ou "other".
direction (chaîne)« dans » ou « sort ». Le sens "vers" permet d'informer l'appareil
qu'il doit envoyer des informations à l'hôte. Toutes les communications sur un bus
USB sont lancées par l'hôte. Utilisez donc un transfert "entrant" pour permettre à un appareil de
renvoyer des informations.
requête (entier)Définie par le protocole de votre appareil.
valeur (entier)Définie par le protocole de votre appareil.
index (entier)Définie par le protocole de votre appareil.
longueur (entier)Utilisé uniquement lorsque le sens de circulation est "dans". Notifie l'appareil qu'il s'agit de la quantité de données que l'hôte attend en réponse.
données (arraybuffer)Définie par le protocole de votre appareil, obligatoire lorsque le sens de circulation est "out".

Exemple :

var transferInfo = {
  "requestType": "vendor",
   "recipient": "device",
  "direction": "out",
  "request":  0x31,
  "value": 120,
  "index": 0,
  // Note that the ArrayBuffer, not the TypedArray itself is used.
  "data": new Uint8Array([4, 8, 15, 16, 23, 42]).buffer
};
chrome.usb.controlTransfer(connectionHandle, transferInfo, optionalCallback);

Transferts ISOCHRONOUS

Les transferts isochrones sont le type de transfert USB le plus complexe. Ils sont couramment utilisés pour les flux de données, comme les vidéos et les sons. Pour initier un transfert isochron (entrant ou sortant), vous devez utiliser la méthode usb.isochronousTransfer:

chrome.usb.isochronousTransfer(connectionHandle, isochronousTransferInfo, transferCallback)
ParamètresDescription
connectionHandleObjet reçu dans un rappel usb.openDevice.
isochronousTransferInfoObjet de paramètre avec les valeurs du tableau ci-dessous.
transferCallback()Appelée une fois le transfert terminé.

Valeurs de l'objet isochronousTransferInfo:

ValeurDescription
transfertInfo (objet)Objet avec les attributs suivants:
direction (chaîne) : "in" ou "out".
point de terminaison (entier) : défini par votre appareil. Elle se trouve généralement à l'aide d'un outil d'introspection USB, comme lsusb -v
longueur (entier) : uniquement utilisée lorsque le sens est "in". Informe l'appareil qu'il s'agit de la quantité de données attendue par l'hôte en réponse.
Doit être AU MOINS packets × packetLength.
données (arraybuffer) : définie par le protocole de votre appareil ; utilisée uniquement lorsque la direction est "out" (sortie).
paquets (entier)Nombre total de paquets attendus dans ce transfert.
paquetsLength (entier)Longueur attendue de chaque paquet dans ce transfert.

Exemple :

var transferInfo = {
  "direction": "in",
  "endpoint": 1,
  "length": 2560
};

var isoTransferInfo = {
  "transferInfo": transferInfo,
  "packets": 20,
  "packetLength": 128
};

chrome.usb.isochronousTransfer(connectionHandle, isoTransferInfo, optionalCallback);

Transferts groupés

Les transferts groupés sont généralement utilisés pour transférer de manière fiable une grande quantité de données non urgentes. usb.bulkTransfer comporte trois paramètres:

chrome.usb.bulkTransfer(connectionHandle, transferInfo, transferCallback);
ParamètresDescription
connectionHandleObjet reçu dans un rappel usb.openDevice.
transferInfoObjet de paramètre avec les valeurs du tableau ci-dessous.
transferCallbackAppelée une fois le transfert terminé.

Valeurs de l'objet transferInfo:

ValeurDescription
direction (chaîne)« dans » ou « sort ».
point de terminaison (entier)Définie par le protocole de votre appareil.
longueur (entier)Utilisé uniquement lorsque le sens de circulation est "dans". Notifie l'appareil qu'il s'agit de la quantité de données que l'hôte attend en réponse.
données (ArrayBuffer)Définie par le protocole de votre appareil. Utilisé uniquement lorsque le sens de circulation est "out".

Exemple :

var transferInfo = {
  "direction": "out",
  "endpoint": 1,
  "data": new Uint8Array([4, 8, 15, 16, 23, 42]).buffer
};

Transferts INTERRUPT

Les transferts interrompus sont utilisés pour une petite quantité de données sensibles. Étant donné que toutes les communications USB sont initiées par l'hôte, le code hôte interroge généralement l'appareil régulièrement. L'envoi de transferts IN d'interruption qui entraînent l'envoi de données dans la file d'attente d'interruption (gérée par l'appareil) usb.interruptTransfer comporte trois paramètres:

chrome.usb.interruptTransfer(connectionHandle, transferInfo, transferCallback);
ParamètresDescription
connectionHandleObjet reçu dans un rappel usb.openDevice.
transferInfoObjet de paramètre avec les valeurs du tableau ci-dessous.
transferCallbackAppelée une fois le transfert terminé. Notez que ce rappel ne contient pas la réponse de l'appareil. L'objectif du rappel est simplement d'informer votre code que les demandes de transfert asynchrones ont été traitées.

Valeurs de l'objet transferInfo:

ValeurDescription
direction (chaîne)« dans » ou « sort ».
point de terminaison (entier)Définie par le protocole de votre appareil.
longueur (entier)Utilisé uniquement lorsque le sens de circulation est "dans". Notifie l'appareil qu'il s'agit de la quantité de données que l'hôte attend en réponse.
données (ArrayBuffer)Définie par le protocole de votre appareil. Utilisé uniquement lorsque le sens de circulation est "out".

Exemple :

var transferInfo = {
  "direction": "in",
  "endpoint": 1,
  "length": 2
};
chrome.usb.interruptTransfer(connectionHandle, transferInfo, optionalCallback);

Mises en garde

Tous les appareils ne sont pas accessibles via l'API USB. En général, les appareils ne sont pas accessibles, car le noyau du système d'exploitation ou un pilote natif les empêche du code de l'espace utilisateur. Il peut s'agir, par exemple, d'appareils avec des profils HID sur les systèmes OSX et de clés USB.

Sur la plupart des systèmes Linux, les périphériques USB sont mappés avec des autorisations de lecture seule par défaut. Pour ouvrir un appareil via cette API, l'utilisateur doit également y avoir accès en écriture. Une solution simple consiste à définir une règle udev. Créez un fichier /etc/udev/rules.d/50-yourdevicename.rules avec le contenu suivant:

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

Ensuite, redémarrez simplement le daemon udev: service udev restart. Pour vérifier si les autorisations de l'appareil sont correctement définies, procédez comme suit:

  • Exécutez lsusb pour trouver les numéros de bus et d'appareil.
  • Exécutez ls -al /dev/bus/usb/[bus]/[device]. Ce fichier doit appartenir au groupe "plugdev" et disposer d'autorisations d'écriture de groupe.

Votre application ne peut pas le faire automatiquement, car cette procédure nécessite un accès racine. Nous vous recommandons de fournir des instructions aux utilisateurs finaux et de consulter la section Mises en garde de cette page pour obtenir des explications.

Sous ChromeOS, il vous suffit d'appeler usb.requestAccess. L'agent d'autorisations s'en charge pour vous.