打造支援 WebUSB 的裝置

建構裝置,充分發揮 WebUSB API 的優勢。

本文將說明如何建構裝置,充分發揮 WebUSB API 的優勢。如要簡要瞭解 API 本身,請參閱「在網路上存取 USB 裝置」。

背景

通用序列匯流排 (USB) 已成為連接周邊裝置與電腦和行動運算裝置最常見的實體介面。除了定義匯流排的電氣特性和與裝置通訊的一般模型,USB 規格也包含一組裝置類別規格。這些是裝置製造商可實作的一般裝置類別模型,例如儲存空間、音訊、視訊、網路等。這些裝置類別規格的優點在於,作業系統供應商可以根據類別規格實作單一驅動程式 (「類別驅動程式」),並支援實作該類別的任何裝置。這項功能大幅改善了製造商必須自行編寫裝置驅動程式的情況。

不過,有些裝置不屬於這些標準化裝置類別。製造商可以選擇將裝置標示為實作供應商專屬類別。在這種情況下,作業系統會根據供應商的驅動程式套件提供的資訊,選擇要載入哪個裝置驅動程式,通常是已知可實作特定供應商專屬通訊協定的供應商和產品 ID 組合。

USB 的另一項功能是,裝置可能會為所連結的主機提供多個介面。每個介面都可以實作標準化類別,或為特定供應商專用。當作業系統選擇正確的驅動程式來處理裝置時,每個介面都可以由不同的驅動程式聲明。舉例來說,USB 網路攝影機通常會提供兩個介面,一個是實作 USB 視訊類別 (相機),另一個是實作 USB 音訊類別 (麥克風)。作業系統不會載入單一「網路攝影機驅動程式」,而是載入獨立的視訊和音訊類別驅動程式,負責裝置的個別功能。這種介面類別組合可提供更大的彈性。

API 基本資訊

許多標準 USB 類別都有對應的 Web API。舉例來說,網頁可以使用 getUserMedia() 從影片類別裝置擷取影片,也可以透過監聽 KeyboardEventsPointerEvents,或使用 GamepadWebHID API,從人機介面 (HID) 類別裝置接收輸入事件。就像並非所有裝置都實作標準化類別定義一樣,並非所有裝置都實作與現有網路平台 API 相對應的功能。在這種情況下,WebUSB API 可提供一種方式,讓網站宣告供應商專屬介面,並直接在網頁中實作相關支援。

由於作業系統管理 USB 裝置的方式不同,因此透過 WebUSB 存取裝置的具體要求會因平台而異,但基本要求是裝置不應已具有聲明頁面要控管的介面的驅動程式。這可能是作業系統供應商提供的通用類別驅動程式,或是供應商提供的裝置驅動程式。由於 USB 裝置可提供多個介面,每個介面可能都有自己的驅動程式,因此您可以建構裝置,讓某些介面由驅動程式宣告,其他則可供瀏覽器存取。

舉例來說,高階 USB 鍵盤可能會提供 HID 類別介面,供作業系統的輸入子系統使用,以及供應商專屬介面,供 WebUSB 使用,以便設定工具使用。這項工具可在製造商的網站上提供,讓使用者不必安裝任何特定平台的軟體,即可變更裝置行為的各個層面,例如巨集鍵和燈光效果。這類裝置的設定描述元會如下所示:

欄位 說明
設定描述元
0x09 bLength 這個描述元標記的大小
0x02 bDescriptorType 設定描述元
0x0039 wTotalLength 此系列描述符的總長度
0x02 bNumInterfaces 介面數量
0x01 bConfigurationValue Configuration 1
0x00 iConfiguration 設定名稱 (無)
0b1010000 bmAttributes 具備遠端喚醒功能的自供電裝置
0x32 bMaxPower 最大功率以 2 mA 為單位遞增
介面描述元
0x09 bLength 這個描述元標記的大小
0x04 bDescriptorType 介面描述元
0x00 bInterfaceNumber 介面 0
0x00 bAlternateSetting 替代設定 0 (預設)
0x01 bNumEndpoints 1 個端點
0x03 bInterfaceClass HID 介面類別
0x01 bInterfaceSubClass 啟動介面子類別
0x01 bInterfaceProtocol 鍵盤
0x00 iInterface 介面名稱 (無)
HID 描述元
0x09 bLength 這個描述元標記的大小
0x21 bDescriptorType HID 描述元
0x0101 bcdHID HID 1.1 版
0x00 bCountryCode 硬體指定國家/地區
0x01 bNumDescriptors 要遵循的 HID 類別描述元標記數量
0x22 bDescriptorType 報表描述元類型
0x003F wDescriptorLength Report 描述元資料的總長度
端點描述元
0x07 bLength 這個描述元標記的大小
0x05 bDescriptorType 端點描述元
0b10000001 bEndpointAddress 端點 1 (IN)
0b00000011 bmAttributes 中斷
0x0008 wMaxPacketSize 8 位元組封包
0x0A bInterval 10 毫秒間隔
介面描述元
0x09 bLength 這個描述元標記的大小
0x04 bDescriptorType 介面描述元
0x01 bInterfaceNumber 介面 1
0x00 bAlternateSetting 替代設定 0 (預設)
0x02 bNumEndpoints 2 個端點
0xFF bInterfaceClass 供應商專屬介面類別
0x00 bInterfaceSubClass
0x00 bInterfaceProtocol
0x00 iInterface 介面名稱 (無)
端點描述元
0x07 bLength 這個描述元標記的大小
0x05 bDescriptorType 端點描述元
0b10000010 bEndpointAddress 端點 1 (IN)
0b00000010 bmAttributes 大量
0x0040 wMaxPacketSize 64 位元組封包
0x00 bInterval 不適用於大量端點
端點描述元
0x07 bLength 這個描述元標記的大小
0x05 bDescriptorType 端點描述元
0b00000011 bEndpointAddress 端點 3 (OUT)
0b00000010 bmAttributes 大量
0x0040 wMaxPacketSize 64 位元組封包
0x00 bInterval 不適用於大量端點

設定描述元件由多個連接在一起的描述元件組成。每個欄位的開頭都會以 bLengthbDescriptorType 欄位開始,方便識別。第一個介面是 HID 介面,其中包含相關聯的 HID 描述元和用於將輸入事件傳送至作業系統的單一端點。第二個介面是供應商專屬介面,其中包含兩個端點,可用於向裝置傳送指令,並接收回應。

WebUSB 描述元

雖然 WebUSB 可與許多裝置搭配使用,無須修改韌體,但您可以使用特定描述符標示裝置,表示支援 WebUSB,藉此啟用其他功能。舉例來說,您可以指定到達網頁網址,讓瀏覽器在裝置插上電源時將使用者導向至該網頁。

Chrome 中的 WebUSB 通知螢幕截圖
WebUSB 通知。

二進位元裝置物件儲存庫 (BOS) 是 USB 3.0 中引入的概念,但也已在 2.1 版中回移至 USB 2.0 裝置。如要宣告支援 WebUSB,請先在 BOS 描述元中加入下列平台功能描述元:

欄位 說明
二進位裝置 Object Store 描述元
0x05 bLength 這個描述元標記的大小
0x0F bDescriptorType 二進位裝置 Object Store 描述元
0x001D wTotalLength 此系列描述符的總長度
0x01 bNumDeviceCaps BOS 中的裝置功能描述元數量
WebUSB 平台功能描述元
0x18 bLength 這個描述元標記的大小
0x10 bDescriptorType 裝置功能描述元
0x05 bDevCapabilityType 平台功能描述元
0x00 bReserved
{0x38, 0xB6, 0x08, 0x34, 0xA9, 0x09, 0xA0, 0x47, 0x8B, 0xFD, 0xA0, 0x76, 0x88, 0x15, 0xB6, 0x65} PlatformCapablityUUID WebUSB 平台功能描述元資料 GUID (以位元組由小到大格式表示)
0x0100 bcdVersion WebUSB 描述元 1.0 版
0x01 bVendorCode WebUSB 的 bRequest 值
0x01 iLandingPage 到達網頁網址

平台功能 UUID 會將其識別為 WebUSB 平台功能描述項,可提供裝置的基本資訊。瀏覽器會使用 bVendorCode 值向裝置發出其他要求,以便擷取有關裝置的更多資訊。目前指定的唯一要求是 GET_URL,可傳回網址描述符。這些類似字串描述項,但設計用於以最少位元組編碼網址。"https://google.com" 的網址描述如下:

欄位 說明
網址描述元
0x0D bLength 這個描述元標記的大小
0x03 bDescriptorType 網址描述元
0x01 bScheme https://
"google.com" 網址 採用 UTF-8 編碼的網址內容

當裝置首次插入時,瀏覽器會透過發出這個標準 GET_DESCRIPTOR 控制轉移,讀取 BOS 描述元:

bmRequestType bRequest wValue wIndex wLength 資料 (回應)
0b10000000 0x06 0x0F00 0x0000 * BOS 描述元

這項要求通常會提出兩次,第一次會使用足夠大的 wLength,讓主機在未提交大型傳輸的情況下,找出 wTotalLength 欄位的值,然後在已知完整描述長度的情況下再次提出。

如果 WebUSB 平台功能描述元資料的 iLandingPage 欄位設為非零值,瀏覽器就會透過發出控制轉移,執行 WebUSB 專屬的 GET_URL 要求,其中 bRequest 設為平台功能描述元資料的 bVendorCode 值,而 wValue 設為 iLandingPage 值。GET_URL 的請求碼 (0x02) 會在 wIndex 中:

bmRequestType bRequest wValue wIndex wLength 資料 (回應)
0b11000000 0x01 0x0001 0x0002 * 網址描述元

同樣地,這項要求可能會發出兩次,以便先探查讀取的描述符長度。

平台專屬注意事項

雖然 WebUSB API 會嘗試提供一致的介面,用於存取 USB 裝置,但開發人員仍應瞭解應用程式 (例如網頁瀏覽器) 必須符合哪些要求,才能存取裝置。

macOS

在 macOS 上不需要特別設定。使用 WebUSB 的網站可以連線至裝置,並宣告核心驅動程式或其他應用程式未宣告的任何介面。

Linux

Linux 與 macOS 類似,但根據預設,大多數發行版都不會設定使用者帳戶,以便取得開啟 USB 裝置的權限。系統守護程序 udev 負責指派可存取裝置的使用者和群組。這類規則會將符合指定廠商和產品 ID 的裝置擁有權指派給 plugdev 群組,這是可存取周邊裝置的使用者常見群組:

SUBSYSTEM=="usb", ATTR{idVendor}=="XXXX", ATTR{idProduct}=="XXXX", GROUP="plugdev"

XXXX 替換為裝置的十六進制供應商和產品 ID,例如 ATTR{idVendor}=="18d1", ATTR{idProduct}=="4e11" 會與 Nexus One 手機相符。這些值必須在沒有常見的「0x」前置字串的情況下,以全部小寫字母書寫,才能正確辨識。如要找出裝置的 ID,請執行指令列工具 lsusb

這個規則應放置在 /etc/udev/rules.d 目錄中的檔案中,並在裝置插上電源後立即生效。您不需要重新啟動 udev。

Android

Android 平台是以 Linux 為基礎,但不需要修改系統設定。根據預設,瀏覽器可使用任何未在作業系統中內建驅動程式的裝置。不過,開發人員應注意,使用者連線至裝置時,會遇到額外的步驟。使用者選取裝置以回應對 requestDevice() 的呼叫後,Android 會顯示提示,詢問是否允許 Chrome 存取該裝置。如果使用者返回已具備連線至裝置權限的網站,且該網站呼叫 open(),系統也會再次顯示這則提示。

此外,Android 可存取的裝置數量會比 Linux 桌面版多,因為預設包含的驅動程式較少。舉例來說,USB CDC-ACM 類別通常由 USB 至序列轉接器實作,但 Android SDK 中並未提供與序列裝置通訊的 API,因此這類別並未納入。

ChromeOS

ChromeOS 也是以 Linux 為基礎,因此也不需要修改系統設定。permission_broker 服務會控管 USB 裝置的存取權,只要有至少一個未聲明的介面,瀏覽器就能存取這些裝置。

Windows

Windows 驅動程式模型會引入額外需求。與上述平台不同,即使未載入驅動程式,從使用者應用程式開啟 USB 裝置的功能也不是預設功能。相反地,您必須載入特殊的 WinUSB 驅動程式,才能提供應用程式用來存取裝置的介面。您可以透過在系統上安裝自訂的驅動程式資訊檔案 (INF),或是修改裝置韌體,在枚舉期間提供 Microsoft OS 相容性描述符來完成這項操作。

驅動程式資訊檔案 (INF)

驅動程式資訊檔案會告知 Windows 首次遇到裝置時該如何處理。由於使用者的系統已包含 WinUSB 驅動程式,因此 INF 檔案只需要將供應商和產品 ID 與這項新的安裝規則建立關聯即可。以下檔案為基本範例。將它儲存為副檔名為 .inf 的檔案,變更標示為「X」的部分,然後按一下滑鼠右鍵,並從內容選單中選擇「安裝」。

[Version]
Signature   = "$Windows NT$"
Class       = USBDevice
ClassGUID   = {88BAE032-5A81-49f0-BC3D-A4FF138216D6}
Provider    = %ManufacturerName%
CatalogFile = WinUSBInstallation.cat
DriverVer   = 09/04/2012,13.54.20.543

; ========== Manufacturer/Models sections ===========

[Manufacturer]
%ManufacturerName% = Standard,NTx86,NTia64,NTamd64

[Standard.NTx86]
%USB\MyCustomDevice.DeviceDesc% = USB_Install,USB\VID_XXXX&PID_XXXX

[Standard.NTia64]
%USB\MyCustomDevice.DeviceDesc% = USB_Install,USB\VID_XXXX&PID_XXXX

[Standard.NTamd64]
%USB\MyCustomDevice.DeviceDesc% = USB_Install,USB\VID_XXXX&PID_XXXX

; ========== Class definition ===========

[ClassInstall32]
AddReg = ClassInstall_AddReg

[ClassInstall_AddReg]
HKR,,,,%ClassName%
HKR,,NoInstallClass,,1
HKR,,IconPath,%REG_MULTI_SZ%,"%systemroot%\system32\setupapi.dll,-20"
HKR,,LowerLogoVersion,,5.2

; =================== Installation ===================

[USB_Install]
Include = winusb.inf
Needs   = WINUSB.NT

[USB_Install.Services]
Include = winusb.inf
Needs   = WINUSB.NT.Services

[USB_Install.HW]
AddReg = Dev_AddReg

[Dev_AddReg]
HKR,,DeviceInterfaceGUIDs,0x10000,"{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}"

; =================== Strings ===================

[Strings]
ManufacturerName              = "Your Company Name Here"
ClassName                     = "Your Company Devices"
USB\MyCustomDevice.DeviceDesc = "Your Device Name Here"

[Dev_AddReg] 部分會為裝置設定 DeviceInterfaceGUID 組合。每個裝置介面都必須有 GUID,應用程式才能透過 Windows API 找到並連線至該介面。使用 New-Guid PowerShell 指令碼或線上工具產生隨機 GUID。

為了方便開發,Zadig 工具提供了簡易介面,可將為 USB 介面載入的驅動程式,替換成 WinUSB 驅動程式。

Microsoft OS 相容性描述符

上述 INF 檔案方法相當繁瑣,因為需要事先設定每位使用者的電腦。Windows 8.1 以上版本提供使用自訂 USB 描述符的替代方案。這些描述元會在裝置首次插入時,向 Windows 作業系統提供資訊,這些資訊通常會包含在 INF 檔案中。

設定 WebUSB 描述元後,您也可以輕鬆新增 Microsoft 的 OS 相容性描述元。首先,請使用這個額外的平台功能描述符擴充 BOS 描述符。請務必更新 wTotalLengthbNumDeviceCaps 以納入考量。

欄位 說明
Microsoft OS 2.0 平台能力描述元
0x1C bLength 這個描述元標記的大小
0x10 bDescriptorType 裝置功能描述元
0x05 bDevCapabilityType 平台功能描述元
0x00 bReserved
{0xDF, 0x60, 0xDD, 0xD8, 0x89, 0x45, 0xC7, 0x4C, 0x9C, 0xD2, 0x65, 0x9D, 0x9E, 0x64, 0x8A, 0x9F} PlatformCapablityUUID Microsoft OS 2.0 平台相容性描述元資料 GUID (位元組由小到大格式)
0x06030000 dwWindowsVersion 最低相容的 Windows 版本 (Windows 8.1)
0x00B2 wMSOSDescriptorSetTotalLength 描述元集的總長度
0x02 bMS_VendorCode 用於擷取其他 Microsoft 描述元的 bRequest 值
0x00 bAltEnumCode 裝置不支援其他列舉

與 WebUSB 描述元一樣,您必須選取 bRequest 值,供與這些描述元相關的控制轉移使用。在本例中,我選擇 0x020x07 位於 wIndex 中,是用於從裝置擷取 Microsoft OS 2.0 描述元集的命令。

bmRequestType bRequest wValue wIndex wLength 資料 (回應)
0b11000000 0x02 0x0000 0x0007 * MS OS 2.0 描述元組

USB 裝置可能有多個功能,因此描述元組的首要部分會說明後續屬性與哪個函式相關。以下範例會設定複合裝置的介面 1。描述元提供兩項與此介面相關的重要資訊給作業系統。相容的 ID 描述項會告知 Windows,這個裝置與 WinUSB 驅動程式相容。註冊表屬性描述元函式與上述 INF 範例的 [Dev_AddReg] 部分類似,會設定註冊表屬性,將裝置介面 GUID 指派給這個函式。

欄位 說明
Microsoft OS 2.0 描述元集合標頭
0x000A wLength 這個描述元標記的大小
0x0000 wDescriptorType 描述元集標頭描述元
0x06030000 dwWindowsVersion 最低相容的 Windows 版本 (Windows 8.1)
0x00B2 wTotalLength 描述元集的總長度
Microsoft OS 2.0 設定子集標頭
0x0008 wLength 這個描述元標記的大小
0x0001 wDescriptorType 設定子集標頭說明。
0x00 bConfigurationValue 適用於設定 1 (雖然設定通常從 1 開始編號,但此設定從 0 開始編號)
0x00 bReserved 必須設為 0
0x00A8 wTotalLength 包含此標頭的子集總長度
Microsoft OS 2.0 函式子集標頭
0x0008 wLength 這個描述元標記的大小
0x0002 wDescriptorType 函式子集標頭描述元
0x01 bFirstInterface 函式的第 1 個介面
0x00 bReserved 必須設為 0
0x00A0 wSubsetLength 包含此標頭的子集總長度
Microsoft OS 2.0 相容的 ID 描述元
0x0014 wLength 這個描述元標記的大小
0x0003 wDescriptorType 相容 ID 描述元
"WINUSB\0\0" CompatibileID 以 8 個位元組填滿的 ASCII 字串
"\0\0\0\0\0\0\0\0" SubCompatibleID 以 8 個位元組填滿的 ASCII 字串
Microsoft OS 2.0 登錄檔屬性描述元
0x0084 wLength 這個描述元標記的大小
0x0004 wDescriptorType 登錄檔屬性描述元
0x0007 wPropertyDataType REG_MULTI_SZ
0x002A wPropertyNameLength 屬性名稱的長度
"DeviceInterfaceGUIDs\0" PropertyName 以 UTF-16LE 編碼的屬性名稱,含空值終止符
0x0050 wPropertyDataLength 屬性值的長度
"{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}\0\0" PropertyData GUID 加上兩個以 UTF-16LE 編碼的空字元結尾符號

Windows 只會向裝置查詢一次這項資訊。如果裝置未以有效描述符回應,系統就不會在下次連線時再次詢問。Microsoft 提供USB 裝置登錄項目清單,說明在枚舉裝置時建立的登錄項目。測試時,請刪除為裝置建立的項目,強制 Windows 再次嘗試讀取描述元資料。

如需更多資訊,請參閱 Microsoft 的網誌文章,瞭解如何使用這些描述符。

範例

以下專案提供實作 WebUSB 感知裝置的範例程式碼,其中包含 WebUSB 描述元和 Microsoft OS 描述元: