USB 裝置

本文說明如何使用 USB API 與 USB 裝置進行通訊。部分裝置無法透過 USB API 存取 (詳情請參閱下方的注意事項一節)。Chrome 應用程式也可以連線至serial藍牙裝置。

如需 USB 的背景資訊,請參閱官方的 USB 規格NutShell 中的 USB 是相當合理的速成課程,可能對您有所幫助。

資訊清單規定

USB API 需要資訊清單檔案中的「usb」權限:

"permissions": [
  "usb"
]

此外,為防止指紋列印,您必須在資訊清單檔案中宣告要存取的所有裝置類型。每種 USB 裝置類型都會對應到廠商 ID/產品 ID (VID/PID) 組合。您可以使用 usb.getDevices 按 VID/PID 組合列舉裝置。

您必須在應用程式的資訊清單檔案中,透過 usbDevices 權限為要使用的每種裝置類型宣告 VID/PID 組合,如以下範例所示:

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

Chrome 57 版起,對於以 ChromeOS 資訊站應用程式形式執行的應用程式,必須在應用程式資訊清單中宣告所有裝置類型。針對資訊站應用程式,您可以使用 interfaceClass 權限屬性要求存取符合以下條件的 USB 裝置:

  • 實作特定介面類別的 USB 介面
  • 擁有特定的 USB 裝置類別

舉例來說,下列 usbDevices 權限會將應用程式存取權授予所有實作印表機介面 (介面類別代碼 7) 的 USB 裝置,以及 USB 中樞裝置 (裝置類別代碼 9):

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

如需可接受的 interfaceClass 值清單,請參閱「USB 類別代碼」一文。

interfaceClass 屬性可與 vendorId 屬性搭配使用,以便僅存取特定廠商的 USB 裝置,如以下範例所示:

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

尋找裝置

如要判斷是否有一部或多部特定裝置已連線至使用者的系統,請使用 usb.getDevices 方法:

chrome.usb.getDevices(enumerateDevicesOptions, callback);
參數 (類型)說明
EnumerateDevicesOptions (物件)指定 vendorId (長) 和 productId (長) 的物件,用於找出公車上的正確裝置類型。資訊清單必須宣告 usbDevices 權限部分,列出應用程式要存取的所有 vendorIddeviceId 組合。
回呼 (函式)在裝置列舉完成時呼叫。回呼將透過單一參數執行,即 Device 物件的陣列,其中包含三個屬性:devicevendorIdproductId。裝置屬性是已連結裝置的穩定 ID。在裝置未接上電源後,這項設定才會變更。識別碼詳細資料不透明,隨時可能變更。請勿使用目前的類型。
如果找不到任何裝置,陣列中會是空白的。

示例:

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

開啟裝置

傳回 Device 物件後,您可以使用 usb.openDevice 開啟裝置取得連線控制代碼。您只能使用連線控點與 USB 裝置通訊。

屬性說明
裝置usb.getDevices 回呼接收的物件。
資料 (陣列緩衝區)包含裝置在傳輸過程中傳送的資料。

示例:

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

如要簡化開啟程序,您可以使用 usb.findDevices 方法,在單一呼叫中列舉、要求存取權及開啟裝置:

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

這相當於:

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

透過 USB 傳輸及接收裝置的資料

USB 通訊協定可定義四種傳輸類型:控制大量循環中斷。以下將詳細說明這類轉移方式。

轉乘可能有兩種方向:裝置對主機 (傳入) 和主機對裝置 (傳出)。基於 USB 通訊協定的性質,傳入和傳出訊息都必須由主機 (執行 Chrome 應用程式的電腦) 發出。針對內送 (裝置到主機) 訊息,主機 (由您的 JavaScript 程式碼啟動) 會傳送標記為「傳入」的訊息至裝置。訊息的詳細資料視裝置而定,但通常可以找到自己要求的內容。裝置隨即會回應要求的資料。裝置回應會由 Chrome 處理,並以非同步方式傳送至您在傳輸方法中指定的回呼。傳出 (主機對裝置) 訊息很類似,但回應不會包含裝置傳回的資料。

針對裝置的每則訊息,指定的回呼會收到包含以下屬性的事件物件:

屬性說明
resultCode (整數)0 代表成功,其他值則代表失敗。出現失敗時,
可從 chrome.extension.lastError 讀取錯誤字串。
資料 (陣列緩衝區)包含裝置在傳輸過程中傳送的資料。

示例:

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

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

控制轉移

控制轉乘通常用於將設定或指令參數傳送到 USB 裝置。ControlTransfer 方法一律會從端點 0 傳送至/讀取,且不需要 ClaimInterface。該方法非常簡單,會接收三個參數:

chrome.usb.controlTransfer(connectionHandle, transferInfo, transferCallback)
參數 (類型)說明
connectionHandleusb.openDevice 回呼中接收的物件。
transferInfo參數物件中包含下表中的值。詳情請參閱 USB 裝置通訊協定規格。
transferCallback()轉移作業完成後叫用。

transferInfo 物件的值:

說明
requestType (字串)「vendor」、「standard」、「class」或「reserve」。
收件者 (字串)「device」、「介面」、「端點」或「其他」
方向 (字串)「in」或「out」。「往」方向的用途是通知裝置應將資訊傳送給主機。
USB 匯流排上的所有通訊作業都是由主機啟動,
因此請使用「進」傳輸功能,讓裝置能夠
傳回資訊。
要求 (整數)由裝置的通訊協定定義。
值 (整數)由裝置的通訊協定定義。
索引 (整數)由裝置的通訊協定定義。
長度 (整數)只能在方向為「進」時使用。通知裝置這是主機預期的回應資料量。
資料 (陣列緩衝區)由裝置的通訊協定定義 (方向為「出」時必須)。

示例:

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

ISOCHRONOUS 傳輸

非同步傳輸是最複雜的 USB 傳輸類型。通常用於資料串流 例如影片和音效如要啟動循環傳輸 (傳入或傳出),您必須使用 usb.isochronousTransfer 方法:

chrome.usb.isochronousTransfer(connectionHandle, isochronousTransferInfo, transferCallback)
參數說明
connectionHandleusb.openDevice 回呼中接收的物件。
isochronousTransferInfo含下表值的參數物件。
transferCallback()轉移作業完成後叫用。

isochronousTransferInfo 物件的值:

說明
TransferInfo (物件)具有以下屬性的物件:
direction (string):「in」或「out」。
端點 (integer):由裝置定義。通常可以透過 USB 偵測工具 (例如 lsusb -v
長度 (整數) ) 找出方向:只有在方向為「進」時才能使用,通知裝置說明主機應回應的資料量。
應至少為 packets × packetLength
資料 (陣列緩衝區): 由裝置通訊協定定義的;只有在方向為「出」時才能使用。
封包 (整數)這項移轉作業中的預期封包總數。
packageLength (整數)這項移轉作業中每個封包的預期長度。

示例:

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

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

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

BULK 傳輸

大量移轉作業經常以可靠的方式轉移大量的非有時效性資料。usb.bulkTransfer 有三個參數:

chrome.usb.bulkTransfer(connectionHandle, transferInfo, transferCallback);
參數說明
connectionHandleusb.openDevice 回呼中接收的物件。
transferInfo含下表值的參數物件。
transferCallback轉移作業完成後叫用。

transferInfo 物件的值:

說明
方向 (字串)「in」或「out」。
端點 (整數)由裝置的通訊協定定義。
長度 (整數)只能在方向為「進」時使用。通知裝置這是主機預期的回應資料量。
資料 (ArrayBuffer)由裝置通訊協定定義;只有在方向為「出」時才能使用。

示例:

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

INTERRUPT 轉乘

中斷移轉適用於少量時效性的資料。由於所有 USB 通訊都是由主機啟動,因此主機程式碼通常會定期輪詢裝置,然後傳送中斷情形,讓裝置在中斷佇列中發生任何項目 (由裝置維護) 時回傳資料。usb.interruptTransfer 有三個參數:

chrome.usb.interruptTransfer(connectionHandle, transferInfo, transferCallback);
參數說明
connectionHandleusb.openDevice 回呼中接收的物件。
transferInfo含下表值的參數物件。
transferCallback轉移作業完成後叫用。請注意,這個回呼並不包含裝置回應。回呼的用途僅在於通知程式碼已處理非同步轉移要求。

transferInfo 物件的值:

說明
方向 (字串)「in」或「out」。
端點 (整數)由裝置的通訊協定定義。
長度 (整數)只能在方向為「進」時使用。通知裝置這是主機預期的回應資料量。
資料 (ArrayBuffer)由裝置通訊協定定義;只有在方向為「出」時才能使用。

示例:

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

注意事項

並非所有裝置都能透過 USB API 存取。一般而言,使用者無法存取裝置,因為作業系統的核心或原生驅動程式會透過使用者空間程式碼移除裝置。例如在 OSX 系統上設有 HID 設定檔的裝置,以及 USB 隨身碟。

根據預設,在多數 Linux 系統中,USB 裝置對應時會具備唯讀權限。如要透過這個 API 開啟裝置,使用者也必須具有裝置的寫入權限。其中一個簡單的解決方式是設定 udev 規則建立含有以下內容的檔案 /etc/udev/rules.d/50-yourdevicename.rules

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

接著,請重新啟動 udev Daemon:service udev restart。您可以按照下列步驟檢查裝置權限設定是否正確:

  • 執行 lsusb 即可尋找公車和裝置號碼。
  • 執行 ls -al /dev/bus/usb/[bus]/[device]。這個檔案的擁有者應為「plugdev」群組,且具備群組寫入權限。

由於這個程序需要取得 Root 存取權,因此您的應用程式無法自動執行這項操作。建議您向使用者提供操作說明,並連結至本頁的「注意事項」一節。

在 ChromeOS 中,直接呼叫 usb.requestAccess 即可。權限代理程式會為您執行這項作業。