打造可充分運用 WebUSB API 的裝置。
本文說明如何建構裝置,以便充分運用 WebUSB API。如需 API 本身的簡介,請參閱「在網路上存取 USB 裝置」。
背景
通用序列匯流排 (USB) 已成為將週邊裝置連接至電腦和行動運算裝置時最常用的實體介面。除了定義公車的電子特性,以及與裝置通訊的一般型號外,USB 規格還包含一組裝置類別規格。這些是裝置製造商可實作的特定裝置類別 (例如儲存空間、音訊、影片、網路等) 的一般模型。這些裝置類別規格的優點是,作業系統廠商可以根據類別規格 (「類別驅動程式」) 實作單一驅動程式,系統就會支援實作該類別的任何裝置。就各製造商需要自行編寫裝置驅動程式的緣故,這項改善措施可帶來莫大助益。
但部分裝置並不屬於這些標準化裝置類別。製造商可能會改為選擇將裝置標示為實作廠商專屬的類別。在這種情況下,作業系統會根據供應商驅動程式套件中提供的資訊,選擇載入的裝置驅動程式。通常是一組已知的供應商與產品 ID,而且這些 ID 是已知實作的特定供應商專屬通訊協定。
USB 的另一項功能是,裝置可能會向其連線的主機提供多個介面。每個介面都可以實作標準化類別,或針對特定供應商導入。當作業系統選擇處理裝置的合適驅動程式時,每個介面都可以由不同的駕駛人領取。舉例來說,USB 網路攝影機通常提供兩個介面,一個實作 USB 影片類別 (適用於攝影機),另一個實作 USB 音訊類別 (用於麥克風)。作業系統不會載入單一的「網路攝影機驅動程式」,而是會載入獨立的影片和音訊類別驅動程式;這些驅動程式會對裝置中的個別功能負責。此介面類別的組合可提供更大的彈性。
API 基本概念
許多標準 USB 類別都有對應的 Web API。舉例來說,網頁可以使用 getUserMedia()
從影片類別裝置擷取影片,或是監聽 KeyboardEvents 或 PointerEvents,或者使用 Gamepad 或 WebHID 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 | 不適用於大量端點 |
設定描述元由多個串連的描述元組成。每個欄位皆以 bLength
和 bDescriptorType
欄位開頭,以便識別。第一個介面是 HID 介面,具備相關聯的 HID 描述元,以及一個用來將輸入事件傳送至作業系統的單一端點。第二個介面是供應商專屬的介面,具有兩個端點,可用於傳送指令至裝置,以及接收傳回回應。
WebUSB 描述元
雖然 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 描述元。請務必更新 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 (採用 Little-endian 格式) |
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。描述元可為 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 描述元: