建構裝置,充分發揮 WebUSB API 的優勢。
本文將說明如何建構裝置,充分發揮 WebUSB API 的優勢。如要簡要瞭解 API 本身,請參閱「在網路上存取 USB 裝置」。
背景
通用序列匯流排 (USB) 已成為連接周邊裝置與電腦和行動運算裝置最常見的實體介面。除了定義匯流排的電氣特性和與裝置通訊的一般模型,USB 規格也包含一組裝置類別規格。這些是裝置製造商可實作的一般裝置類別模型,例如儲存空間、音訊、視訊、網路等。這些裝置類別規格的優點在於,作業系統供應商可以根據類別規格實作單一驅動程式 (「類別驅動程式」),並支援實作該類別的任何裝置。這項功能大幅改善了製造商必須自行編寫裝置驅動程式的情況。
不過,有些裝置不屬於這些標準化裝置類別。製造商可以選擇將裝置標示為實作供應商專屬類別。在這種情況下,作業系統會根據供應商的驅動程式套件提供的資訊,選擇要載入哪個裝置驅動程式,通常是已知可實作特定供應商專屬通訊協定的供應商和產品 ID 組合。
USB 的另一項功能是,裝置可能會為所連結的主機提供多個介面。每個介面都可以實作標準化類別,或為特定供應商專用。當作業系統選擇正確的驅動程式來處理裝置時,每個介面都可以由不同的驅動程式聲明。舉例來說,USB 網路攝影機通常會提供兩個介面,一個是實作 USB 視訊類別 (相機),另一個是實作 USB 音訊類別 (麥克風)。作業系統不會載入單一「網路攝影機驅動程式」,而是載入獨立的視訊和音訊類別驅動程式,負責裝置的個別功能。這種介面類別組合可提供更大的彈性。
API 基本資訊
許多標準 USB 類別都有對應的 Web API。舉例來說,網頁可以使用 getUserMedia()
從影片類別裝置擷取影片,也可以透過監聽 KeyboardEvents 或 PointerEvents,或使用 Gamepad 或 WebHID 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 | 不適用於大量端點 |
設定描述元件由多個連接在一起的描述元件組成。每個欄位的開頭都會以 bLength
和 bDescriptorType
欄位開始,方便識別。第一個介面是 HID 介面,其中包含相關聯的 HID 描述元和用於將輸入事件傳送至作業系統的單一端點。第二個介面是供應商專屬介面,其中包含兩個端點,可用於向裝置傳送指令,並接收回應。
WebUSB 描述元
雖然 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 描述符。請務必更新 wTotalLength
和 bNumDeviceCaps
以納入考量。
值 | 欄位 | 說明 |
---|---|---|
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
值,供與這些描述元相關的控制轉移使用。在本例中,我選擇 0x02
。0x07
位於 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 描述元: