打造支援 WebUSB 的裝置

打造可充分運用 WebUSB API 的裝置。

Reilly Grant
Reilly Grant

本文說明如何建構裝置,以便充分運用 WebUSB API。如需 API 本身的簡介,請參閱「在網路上存取 USB 裝置」。

背景

通用序列匯流排 (USB) 已成為將週邊裝置連接至電腦和行動運算裝置時最常用的實體介面。除了定義公車的電子特性,以及與裝置通訊的一般型號外,USB 規格還包含一組裝置類別規格。這些是裝置製造商可實作的特定裝置類別 (例如儲存空間、音訊、影片、網路等) 的一般模型。這些裝置類別規格的優點是,作業系統廠商可以根據類別規格 (「類別驅動程式」) 實作單一驅動程式,系統就會支援實作該類別的任何裝置。就各製造商需要自行編寫裝置驅動程式的緣故,這項改善措施可帶來莫大助益。

但部分裝置並不屬於這些標準化裝置類別。製造商可能會改為選擇將裝置標示為實作廠商專屬的類別。在這種情況下,作業系統會根據供應商驅動程式套件中提供的資訊,選擇載入的裝置驅動程式。通常是一組已知的供應商與產品 ID,而且這些 ID 是已知實作的特定供應商專屬通訊協定。

USB 的另一項功能是,裝置可能會向其連線的主機提供多個介面。每個介面都可以實作標準化類別,或針對特定供應商導入。當作業系統選擇處理裝置的合適驅動程式時,每個介面都可以由不同的駕駛人領取。舉例來說,USB 網路攝影機通常提供兩個介面,一個實作 USB 影片類別 (適用於攝影機),另一個實作 USB 音訊類別 (用於麥克風)。作業系統不會載入單一的「網路攝影機驅動程式」,而是會載入獨立的影片和音訊類別驅動程式;這些驅動程式會對裝置中的個別功能負責。此介面類別的組合可提供更大的彈性。

API 基本概念

許多標準 USB 類別都有對應的 Web API。舉例來說,網頁可以使用 getUserMedia() 從影片類別裝置擷取影片,或是監聽 KeyboardEventsPointerEvents,或者使用 GamepadWebHID API,從人體介面 (HID) 類別裝置接收輸入事件。如同並非所有裝置都實作標準化類別定義,並非所有裝置都會實作與現有網路平台 API 對應的功能。在這種情況下,WebUSB API 可讓網站聲明供應商專屬介面的擁有權,並直接在頁面中實作該介面的支援,藉此填補這個缺口。

由於作業系統管理 USB 裝置的方式不同,裝置可透過 WebUSB 存取裝置的具體需求略有不同,但基本規定是,裝置不應已有頁面要控制的介面的驅動程式。這些驅動程式可以是 OS 供應商提供的一般類別驅動程式,或是供應商提供的裝置驅動程式。由於 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 報表描述元總長度
端點描述元
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 描述元中加入下列平台功能描述元:

欄位 說明
二進位裝置物件存放區描述元
0x05 bLength 這個描述元的大小
0x0F bDescriptorType 二進位裝置物件存放區描述元
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 欄位設為非零值,瀏覽器就會發出控制轉移作業,並將 bRequest 設為 bVendorCode 值 (從平台功能描述元設為 bVendorCode 值),並將 wValue 設為 iLandingPage 值,藉此執行 WebUSB 專用的 GET_URL 要求。GET_URL (0x02) 的要求代碼位於 wIndex

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

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

平台專屬注意事項

雖然 WebUSB API 會嘗試提供一致的 USB 裝置存取介面,開發人員仍應瞭解應用程式為了存取裝置而必須遵守的規定 (例如網路瀏覽器規定)。

macOS

macOS 不需要特殊功能。使用 WebUSB 的網站可以連線至裝置,並對任何未由核心驅動程式或其他應用程式聲明的介面聲明擁有權。

Linux

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

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

XXXX 換成裝置的十六進位廠商 ID 和產品 ID,例如 ATTR{idVendor}=="18d1", ATTR{idProduct}=="4e11" 會比對 Nexus One 手機。撰寫時必須不使用一般的「0x」前置字串,且必須全部小寫,才能正確識別。如要找出裝置的 ID,請執行指令列工具 lsusb

這項規則應放在 /etc/udev/rules.d 目錄中的檔案,並在裝置接上電源後立即生效。因此不必重新啟動 udev。

Android

Android 平台以 Linux 為基礎,但不需要修改系統設定。根據預設,瀏覽器只要沒有搭載作業系統的驅動程式,就能使用裝置。不過,開發人員應瞭解使用者會在連線至裝置時遇到額外步驟。當使用者選取裝置以回應 requestDevice() 呼叫後,Android 會顯示提示,詢問是否允許 Chrome 存取該裝置。如果使用者返回的網站已取得裝置連線權限,且網站呼叫了 open(),系統也會再次顯示這則提示。

此外,比起電腦版 Linux,Android 上的存取裝置數量也更多,因為預設隨附的驅動程式較少。值得注意的是,其中一個省略的部分就是 USB 對序列轉接器經常實作的 USB CDC-ACM 類別,因為 Android SDK 中沒有可用來與序列裝置通訊的 API。

ChromeOS

此外,以 Linux 為基礎的 ChromeOS,也不需要修改系統設定。權限_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 尋找並連結 GUID。使用 New-Guid PowerShell cmdlet 或線上工具產生隨機 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 (採用 Little-endian 格式)
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。描述元可為 OS 提供有關此介面的兩項重要資訊。相容的 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 (從 0 為索引,但一般會從 1 建立索引的設定)
0x00 bReserved 必須設為 0
0x00A8 wTotalLength 內含此標頭的子集總長度
Microsoft OS 2.0 函式子集標頭
0x0008 wLength 這個描述元的大小
0x0002 wDescriptorType 函式子集標頭描述元
0x01 bFirstInterface 函式的第一個介面
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 描述元: