Urządzenia USB

W tym dokumencie opisujemy, jak używać interfejsu USB API do komunikacji z urządzeniami USB. Niektóre urządzenia nie są dostępne przez interfejs USB API (więcej informacji znajdziesz w sekcji Zastrzeżenia poniżej). Aplikacje Chrome mogą też łączyć się z urządzeniami serial i Bluetooth.

Podstawowe informacje o USB znajdziesz w oficjalnych specyfikacjach USB. USB w NutShell to rozsądny kurs, który może okazać się przydatny.

Wymagania dotyczące pliku manifestu

Interfejs USB API wymaga uprawnienia „usb” w pliku manifestu:

"permissions": [
  "usb"
]

Aby zapobiec odciskom cyfrowym, musisz w pliku manifestu zadeklarować wszystkie typy urządzeń, do których chcesz uzyskać dostęp. Każdy typ urządzenia USB odpowiada parze identyfikator dostawcy i identyfikator produktu (VID/PID). Za pomocą metody usb.getDevices możesz wyliczyć urządzenia według ich pary VID/PID.

Pary VID/PID dla każdego typu urządzenia, którego chcesz używać, musisz zadeklarować w pliku manifestu aplikacji za pomocą uprawnień usbDevices, tak jak w tym przykładzie:

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

Od wersji Chrome 57 wymóg deklarowania wszystkich typów urządzeń w pliku manifestu aplikacji nie jest ograniczony w przypadku aplikacji działających jako aplikacje kiosku w ChromeOS. W przypadku aplikacji kiosku możesz użyć właściwości uprawnienia interfaceClass, aby poprosić o dostęp do urządzeń USB, które:

  • zaimplementować interfejs USB określonej klasy interfejsu
  • mają określoną klasę urządzenia USB.

Na przykład to uprawnienie usbDevices przyznaje aplikacji dostęp do wszystkich urządzeń USB, które mają interfejs drukarki (kod klasy interfejsu 7), i koncentratorów USB (kod klasy urządzenia 9):

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

Listę akceptowanych wartości interfaceClass znajdziesz w artykule Kody klas USB.

Właściwość interfaceClass można połączyć z właściwością vendorId, aby uzyskać dostęp tylko do urządzeń USB od konkretnego dostawcy, co pokazuje ten przykład:

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

Znajdowanie urządzenia

Aby sprawdzić, czy z systemem użytkownika jest połączone konkretne urządzenie, użyj metody usb.getDevices:

chrome.usb.getDevices(enumerateDevicesOptions, callback);
Parametr (typ)Opis
EnumerateDeviceOptions (obiekt)Obiekt określający zarówno parametr vendorId (długi), jak i productId (długi), używany do znalezienia prawidłowego typu urządzenia w autobusie. W pliku manifestu musi być zadeklarowana sekcja uprawnień usbDevices z listą wszystkich par vendorId i deviceId, do których aplikacja chce mieć dostęp.
wywołanie zwrotne (funkcja)Wywoływana po zakończeniu wyliczania urządzeń. Wywołanie zwrotne zostanie wykonane z 1 parametrem – tablicą obiektów Device z 3 właściwościami: device, vendorId i productId. Właściwość urządzenia to stabilny identyfikator połączonego urządzenia. Nie zmieni się, dopóki urządzenie nie zostanie odłączone. Szczegółowe informacje o identyfikatorze są nieprzejrzyste i mogą ulec zmianie. Nie polegaj na bieżącym typie urządzenia.
Jeśli nie zostaną znalezione żadne urządzenia, tablica będzie pusta.

Przykład:

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

Otwieranie urządzenia

Gdy obiekty Device zostaną zwrócone, możesz otworzyć urządzenie, używając usb.openDevice, aby uzyskać uchwyt połączenia. Z urządzeniami USB możesz komunikować się tylko za pomocą uchwytów połączenia.

WłaściwośćOpis
urządzenieObiekt został odebrany w wywołaniu zwrotnym usb.getDevices.
dane (bufor macierzy)Zawiera dane wysłane przez urządzenie, jeśli transfer został przeprowadzony.

Przykład:

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

Aby uprościć proces otwierania, możesz użyć metody usb.findDevices, która oblicza urządzenia, prosi o dostęp i otwiera je w jednym wywołaniu:

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

co jest równoważne z:

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

Przesyłanie danych przez USB i odbieranie danych z urządzenia

Protokół USB definiuje 4 typy przesyłania: kontrolne, zbiorcze, isochrono i przerwane. Informacje o tych transferach zostały opisane poniżej.

Przenoszenie może odbywać się w obu kierunkach: między urządzeniem a hostem (przychodzącym) oraz między hostami (wychodzącymi). Ze względu na charakter protokołu USB zarówno wiadomości przychodzące, jak i wychodzące muszą być inicjowane przez hosta (komputer, na którym działa aplikacja Chrome). W przypadku wiadomości przychodzących (między urządzeniami) host (zainicjowany przez kod JavaScript) wysyła do urządzenia wiadomość oznaczoną jako „przychodząca”. Szczegóły wiadomości zależą od urządzenia, ale zwykle są na niej pewne informacje o tym, czego dotyczy Twoja prośba. W odpowiedzi urządzenie wysyła żądane dane. Odpowiedź urządzenia jest obsługiwana przez Chrome i dostarczana asynchronicznie na wywołanie zwrotne określone w metodzie transferu. Wiadomość wychodząca (między hostami) jest podobna, ale odpowiedź nie zawiera danych zwróconych przez urządzenie.

Dla każdej wiadomości z urządzenia określone wywołanie zwrotne otrzyma obiekt zdarzenia z tymi właściwościami:

WłaściwośćOpis
wynik_kodu (liczba całkowita)0 oznacza sukces, inne wartości wskazują na niepowodzenie. Ciąg błędu może być
odczytywany od chrome.extension.lastError, gdy wskazuje na niepowodzenie
.
dane (bufor macierzy)Zawiera dane wysłane przez urządzenie, jeśli transfer został przeprowadzony.

Przykład:

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 – transfery

Przenoszenie elementów sterujących używa zwykle do wysyłania lub odbierania parametrów konfiguracji lub poleceń na urządzenie USB. Metoda ControlTransfer zawsze wysyła do/odczytywanie danych z punktu końcowego 0 i nie jest wymagana deklaracjaInterface. Metoda jest prosta i otrzymuje 3 parametry:

chrome.usb.controlTransfer(connectionHandle, transferInfo, transferCallback)
Parametr (typy)Opis
connectionHandleObiekt został odebrany w wywołaniu zwrotnym usb.openDevice.
transferInfoObiekt parametru z wartościami z tabeli poniżej. Szczegółowe informacje znajdziesz w specyfikacji protokołu urządzenia USB.
transferCallback()Wywoływane po ukończeniu przenoszenia.

Wartości dla obiektu transferInfo:

WartośćOpis
requestType (ciąg)„vendor”, „standardowy”, „class” lub „zarezerwowany”.
odbiorca (ciąg znaków)„device”, „interface”, „punkt końcowy” lub „inny”.
kierunek (ciąg znaków)„w” lub „wyjście”. Kierunek „w” służy do powiadomienia urządzenia,
że ma wysłać informacje do hosta. Cała komunikacja
USB jest inicjowana przez hosta, więc użyj transferu „in”, aby urządzenie mogło
odesłać informacje.
żądanie (liczba całkowita)Definiowany przez protokół urządzenia.
wartość (liczba całkowita)Definiowany przez protokół urządzenia.
indeks (liczba całkowita)Definiowany przez protokół urządzenia.
długość (liczba całkowita)Używany tylko wtedy, gdy kierunek jest „w”. Powiadamia urządzenie, że jest to ilość danych, której host oczekuje w odpowiedzi.
dane (bufor macierzy)Zdefiniowany przez protokół urządzenia. Wymagane, gdy kierunek jest ustawiony na „na zewnątrz”.

Przykład:

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

Przesyłanie ISOCHRONOUS

Przesyłanie izochroniczne to najbardziej złożony typ transferu USB. Często są wykorzystywane w przypadku strumieni danych, np. wideo i dźwięku. Aby zainicjować transfer izochronowy (przyjmujący lub wychodzący), musisz użyć metody usb.isochronousTransfer:

chrome.usb.isochronousTransfer(connectionHandle, isochronousTransferInfo, transferCallback)
ParametrOpis
connectionHandleObiekt został odebrany w wywołaniu zwrotnym usb.openDevice.
isochronousTransferInfoObiekt parametru z wartościami podanymi w tabeli poniżej.
transferCallback()Wywoływane po ukończeniu przenoszenia.

Wartości dla obiektu isochronousTransferInfo:

WartośćOpis
transferInfo (obiekt)Obiekt z tymi atrybutami:
kierunek (ciąg znaków): "in" lub "out".
punkt końcowy (liczba całkowita): zdefiniowany przez urządzenie. Zwykle można je znaleźć, patrząc na narzędzie do instrospecjacji USB, takie jak lsusb -v
length (długość całkowita): używane tylko wtedy, gdy kierunek to „in”. Powiadamia urządzenie o ilości danych oczekiwanych przez hosta w odpowiedzi.
Powinien wynosić NAJMNIEJ packets × packetLength.
dane (bufor macierzysty): definiowane przez protokół urządzenia. Używana tylko wtedy, gdy kierunek jest „out”.
pakiety (liczba całkowita)Łączna liczba pakietów oczekiwanych w ramach tego transferu.
packageLength (liczba całkowita)Oczekiwana długość każdego pakietu w ramach tego transferu.

Przykład:

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

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

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

Przelewy BULK

Zbiorcze przesyłanie danych zwykle służy do przenoszenia w niezawodny sposób dużej ilości danych, które nie są wrażliwe na czas. usb.bulkTransfer ma 3 parametry:

chrome.usb.bulkTransfer(connectionHandle, transferInfo, transferCallback);
ParametrOpis
connectionHandleObiekt został odebrany w wywołaniu zwrotnym usb.openDevice.
transferInfoObiekt parametru z wartościami podanymi w tabeli poniżej.
transferCallbackWywoływane po ukończeniu przenoszenia.

Wartości dla obiektu transferInfo:

WartośćOpis
kierunek (ciąg znaków)„w” lub „wyjście”.
punkt końcowy (liczba całkowita)Definiowany przez protokół urządzenia.
długość (liczba całkowita)Używany tylko wtedy, gdy kierunek jest „w”. Powiadamia urządzenie, że jest to ilość danych, której host oczekuje w odpowiedzi.
dane (tablica Buffer)Definiowany przez protokół urządzenia. Używany tylko wtedy, gdy kierunek jest ustawiony na „na zewnątrz”.

Przykład:

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

Przesiadki: INTERRUPT

Przerwane transfery są używane w przypadku niewielkiej ilości danych wrażliwych. Ponieważ cała komunikacja USB jest inicjowana przez hosta, kod hosta zwykle okresowo odpytuje urządzenie, przerywając przesyłanie danych inicjujących w trakcie przesyłania danych, co powoduje, że urządzenie odesłało dane, jeśli w kolejce przerwanych jest coś (obsługiwanego przez urządzenie). usb.interruptTransfer ma trzy parametry:

chrome.usb.interruptTransfer(connectionHandle, transferInfo, transferCallback);
ParametrOpis
connectionHandleObiekt został odebrany w wywołaniu zwrotnym usb.openDevice.
transferInfoObiekt parametru z wartościami podanymi w tabeli poniżej.
transferCallbackWywoływane po ukończeniu przenoszenia. Zauważ, że to wywołanie zwrotne nie zawiera odpowiedzi urządzenia. Wywołanie zwrotne ma na celu poinformowanie Twojego kodu o tym, że żądania transferu asynchronicznego zostały przetworzone.

Wartości dla obiektu transferInfo:

WartośćOpis
kierunek (ciąg znaków)„w” lub „wyjście”.
punkt końcowy (liczba całkowita)Definiowany przez protokół urządzenia.
długość (liczba całkowita)Używany tylko wtedy, gdy kierunek jest „w”. Powiadamia urządzenie, że jest to ilość danych, której host oczekuje w odpowiedzi.
dane (tablica Buffer)Definiowany przez protokół urządzenia. Używany tylko wtedy, gdy kierunek jest ustawiony na „na zewnątrz”.

Przykład:

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

Zastrzeżenia

Nie wszystkie urządzenia są dostępne przez interfejs USB API. Ogólnie urządzenia nie są dostępne, ponieważ jądro systemu operacyjnego lub sterownik natywny uniemożliwiają im dostęp do kodu przestrzeni użytkownika. Przykłady to urządzenia z profilami HID w systemach OSX i dyski USB.

W większości systemów Linux urządzenia USB są domyślnie mapowane z uprawnieniami tylko do odczytu. Aby otworzyć urządzenie za pomocą tego interfejsu API, użytkownik musi też mieć na nim uprawnienia do zapisu. Prostym rozwiązaniem jest ustawienie reguły udev. Utwórz plik /etc/udev/rules.d/50-yourdevicename.rules o tej treści:

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

Następnie uruchom ponownie demona udev: service udev restart. Aby sprawdzić, czy uprawnienia urządzenia są prawidłowo skonfigurowane, wykonaj te czynności:

  • Uruchom lsusb, aby znaleźć numery autobusów i urządzeń.
  • Uruchom ls -al /dev/bus/usb/[bus]/[device]. Ten plik powinien należeć do grupy „plugdev” i mieć uprawnienia do zapisu w grupie.

Twoja aplikacja nie może zrobić tego automatycznie, ponieważ ta procedura wymaga dostępu do roota. Zalecamy udostępnienie instrukcji dla użytkowników i podawanie linków do sekcji Zastrzeżenia na tej stronie, w których można znaleźć wyjaśnienie.

W ChromeOS po prostu wywołaj usb.requestAccess. Broker uprawnień zrobi to za Ciebie.