WebUSB API ช่วยให้ USB ปลอดภัยและใช้งานง่ายขึ้นด้วยการนำไปยังเว็บ
ถ้าผมพูดง่ายๆ ว่า "USB" ก็เป็นไปได้ดีที่คุณจะ นึกถึงแป้นพิมพ์ เมาส์ เสียง วิดีโอ และอุปกรณ์จัดเก็บข้อมูล ใช่แล้ว แต่คุณจะพบอุปกรณ์ Universal Serial Bus (USB) ประเภทอื่นรวมอยู่ด้วย
อุปกรณ์ USB ที่ไม่เป็นมาตรฐานเหล่านี้กำหนดให้ผู้ให้บริการฮาร์ดแวร์ต้องเขียนไดรเวอร์และ SDK เฉพาะแพลตฟอร์มเพื่อให้คุณ (นักพัฒนาซอฟต์แวร์) ได้ใช้ประโยชน์ เราต้องขออภัยที่โค้ดเฉพาะแพลตฟอร์มนี้เคยป้องกันไม่ให้เว็บใช้อุปกรณ์เหล่านี้ นี่จึงเป็นเหตุผลหนึ่งที่เราสร้าง WebUSB API ขึ้นมา เพื่อแสดงวิธีการแสดงบริการอุปกรณ์ USB บนเว็บ API นี้ช่วยให้ผู้ผลิตฮาร์ดแวร์สร้าง JavaScript SDK ข้ามแพลตฟอร์มสำหรับอุปกรณ์ของตนเองได้
แต่สิ่งสำคัญที่สุดคือการดำเนินการนี้จะทำให้ USB ปลอดภัยและใช้งานง่ายขึ้นด้วยการนำ USB มาใช้ในเว็บ
เรามาดูลักษณะการทำงานที่คุณคาดว่าจะได้รับจาก WebUSB API กัน
- ซื้ออุปกรณ์ USB
- เสียบเข้ากับคอมพิวเตอร์ การแจ้งเตือนจะปรากฏขึ้นทันทีพร้อม เว็บไซต์ที่ต้องไปที่สำหรับอุปกรณ์นี้
- คลิกการแจ้งเตือน เว็บไซต์มีอยู่และพร้อมใช้งานแล้ว
- คลิกเพื่อเชื่อมต่อแล้วตัวเลือกอุปกรณ์ USB จะแสดงขึ้นใน Chrome ซึ่งคุณสามารถเลือกอุปกรณ์ได้
นี่ไง!
กระบวนการนี้จะเป็นอย่างไรหากไม่มี WebUSB API
- ติดตั้งแอปพลิเคชันเฉพาะแพลตฟอร์ม
- หากระบบปฏิบัติการของฉันรองรับ ให้ยืนยันว่าฉันได้ดาวน์โหลดสิ่งที่ถูกต้อง
- ติดตั้งสิ่งนี้ ถ้าคุณโชคดี คุณจะไม่ได้รับข้อความแจ้งเกี่ยวกับระบบปฏิบัติการหรือป๊อปอัปที่น่ากลัวเกี่ยวกับการติดตั้งไดรเวอร์/แอปพลิเคชันจากอินเทอร์เน็ต หากไม่โชคดี ไดรเวอร์หรือแอปพลิเคชันที่ติดตั้งทำงานผิดปกติและเป็นอันตรายต่อคอมพิวเตอร์ของคุณ (อย่าลืมว่าเว็บสร้างขึ้นมาเพื่อมีเว็บไซต์ที่ขัดข้อง)
- หากใช้ฟีเจอร์นี้เพียงครั้งเดียว รหัสจะยังอยู่ในคอมพิวเตอร์จนกว่าคุณจะคิดลบออก (บนเว็บ พื้นที่สำหรับไม่���ีการใช้งานจะถูกเรียกคืนในที่สุด)
ก่อนจะเริ่ม
บทความนี้จะถือว่าคุณมีความรู้พื้นฐานเกี่ยวกับวิธีการทำงานของ USB แล้ว หากไม่มี ขอแนะนำให้อ่าน USB ใน NutShell หากต้องการทราบข้อมูลพื้นฐานเกี่ยวกับ USB โปรดอ่านข้อมูลจำเพาะอย่างเป็นทางการของ USB
WebUSB API มีให้บริการใน Chrome 61
พร้อมให้ทดลองใช้จากต้นทาง
ก่อนหน้านี้เราได้เพิ่มฟีเจอร์นี้ใน Chrome 54 และ Chrome 57 เป็นช่วงทดลองใช้จากต้นทางเพื่อรับความคิดเห็นจากนักพัฒนาแอปที่ใช้ WebUSB API ในช่องให้ได้มากที่สุด
การทดลองใช้ล่าสุดได้สิ้นสุดลงแล้วในเดือนกันยายน 2017
ความเป็นส่วนตัวและความปลอดภัย
HTTPS เท่านั้น
เนื่องด้วยความสามารถของฟีเจอร์นี้ ฟีเจอร์นี้จะทำงานได้ในบริบทที่ปลอดภัยเท่านั้น ซึ่งหมายความว่าคุณจะต้องสร้างโดยใช้ TLS
ต้องใช้ท่าทางสัมผัสของผู้ใช้
เพื่อเป็นการป้องกันความปลอดภัย navigator.usb.requestDevice()
อาจเรียกใช้ผ่านท่าทางสัมผัสของผู้ใช้ เช่น การแตะหรือการคลิกเมาส์เท่านั้น
นโยบายสิทธิ์
นโยบายสิทธิ์เป็นกลไกที่ช่วยให้นักพัฒนาซอฟต์แวร์เลือกเปิดและปิดใช้ฟีเจอร์และ API ต่างๆ ของเบราว์เซอร์ได้ โดยสามารถระบุผ่านส่วนหัว HTTP และ/หรือแอตทริบิวต์ "อนุญาต" ของ iframe
คุณสามารถกำหนดนโยบายสิทธิ์ที่ควบคุมว่าแอตทริบิวต์ usb
จะแสดงในออบเจ็กต์ Navigator หรือไม่ หรือพูดง่ายๆ คือหากอนุญาตให้ใช้ WebUSB
ตัวอย่างนโยบายส่วนหัวที่ไม่อนุญาตให้ใช้ WebUSB มีดังนี้
Feature-Policy: fullscreen "*"; usb "none"; payment "self" https://payment.example.com
ด้านล่างเป็นอีกตัวอย่างหนึ่งของนโยบายคอนเทนเนอร์ที่อนุญาตให้ใช้ USB
<iframe allowpaymentrequest allow="usb; fullscreen"></iframe>
มาเริ่มเขียนโค้ดกันเลย
WebUSB API พึ่งพาสัญญา JavaScript เป็นอย่างมาก หากคุณยังไม่คุ้นเคยกับฟีเจอร์นี้ โปรดดูบทแนะนำ Promises ที่ยอดเยี่ยมนี้ อีกเรื่องหนึ่งคือ () => {}
คือฟังก์ชันลูกศรของ ECMAScript 2015
รับสิทธิ์เข้าถึงอุปกรณ์ USB
คุณอาจแจ้งให้ผู้ใช้เลือกอุปกรณ์ USB 1 เครื่องที่เชื่อมต่ออยู่โดยใช้ navigator.usb.requestDevice()
หรือโทรไปที่ navigator.usb.getDevices()
เพื่อดูรายการอุปกรณ์ USB ที่เชื่อมต่ออยู่ทั้งหมดที่เว็บไซต์ได้รับสิทธิ์เข้าถึง
ฟังก์ชัน navigator.usb.requestDevice()
จะใช้ออบเจ็กต์ JavaScript ที่จำเป็นซึ่งกำหนด filters
ตัวกรองเหล่านี้จะใช้เพื่อจับคู่อุปกรณ์ USB กับผู้ให้บริการที่ระ��ุ (vendorId
) และตัวระบุผลิตภัณฑ์ (productId
) (ไม่บังคับ)
นอกจากนี้ยังกำหนดคีย์ classCode
, protocolCode
, serialNumber
และ subclassCode
ในที่ดังกล่าวได้เช่นกัน
![ภาพหน้าจอข้อความแจ้งผู้ใช้อุปกรณ์ USB ใน Chrome](https://cdn.statically.io/img/developer.chrome.com/static/docs/capabilities/usb/image/screenshot-the-usb-devic-25e524dd3ed2b.png?hl=th)
ตัวอย่างเช่น ต่อไปนี้คือวิธีเข้าถึงอุปกรณ์ Arduino ที่เชื่อมต่อซึ่งกำหนดค่าให้อนุญาตต้นทาง
navigator.usb.requestDevice({ filters: [{ vendorId: 0x2341 }] })
.then(device => {
console.log(device.productName); // "Arduino Micro"
console.log(device.manufacturerName); // "Arduino LLC"
})
.catch(error => { console.error(error); });
ก่อนที่จะถาม ฉันหาเลขฐานสิบหก 0x2341
นี้มาน่าจะไม่ถูกต้อง ฉันแค่ค้นหาคำว่า "Arduino" ในรายการของ USB ID
device
USB ที่ส่งคืนในคำสัญญาที่ให้ไว้ข้างต้นมีข้อมูลพื้นฐานบางอย่างที่สำคัญเกี่ยวกับอุปกรณ์ เช่น เวอร์ชัน USB ที่รองรับ ขนาดแพ็กเก็ตสูงสุด ผู้ให้บริการ และรหัสผลิตภัณฑ์ จำนวนการกำหนดค่าที่เป็นไปได้ซึ่งอุปกรณ์อาจมี โดยพื้นฐานแล้วจะมีช่องทั้งหมดใน
ข้อบ่งชี้ USB ของอุปกรณ์
// Get all connected USB devices the website has been granted access to.
navigator.usb.getDevices().then(devices => {
devices.forEach(device => {
console.log(device.productName); // "Arduino Micro"
console.log(device.manufacturerName); // "Arduino LLC"
});
})
ถ้าอุปกรณ์ USB ประกาศการรองรับ WebUSB รวมทั้งกำหนด URL ของหน้า Landing Page ไว้ Chrome จะแสดงการแจ้งเตือนอยู่เรื่อยๆ เมื่อเสียบปลั๊กอุปกรณ์ USB การคลิกการแจ้งเตือนนี้จะเปิดหน้า Landing Page
![ภาพหน้าจอของการแจ้งเตือน WebUSB ใน Chrome](https://cdn.statically.io/img/developer.chrome.com/static/docs/capabilities/usb/image/screenshot-the-webusb-no-3e19033df3385.png?hl=th)
คุยกับกระดาน USB ของ Arduino
คราวนี้มาดูกันว่าการสื่อสารจาก บอร์ด Arduino ที่เข้ากันได้กับ WebUSB ผ่านพอร์ต USB นั้นทำได้ง่ายเพียงใด ��ูวิธีการได้ที่ https://github.com/webusb/arduino เพื่อใช้ภาพร่างใน WebUSB
ไม่ต้องกังวล เราจะพูดถึงวิธีการทั้งหมดของอุปกรณ์ WebUSB ที่ระบุไว้ด้านล่างภายหลังในบทความนี้
let device;
navigator.usb.requestDevice({ filters: [{ vendorId: 0x2341 }] })
.then(selectedDevice => {
device = selectedDevice;
return device.open(); // Begin a session.
})
.then(() => device.selectConfiguration(1)) // Select configuration #1 for the device.
.then(() => device.claimInterface(2)) // Request exclusive control over interface #2.
.then(() => device.controlTransferOut({
requestType: 'class',
recipient: 'interface',
request: 0x22,
value: 0x01,
index: 0x02})) // Ready to receive data
.then(() => device.transferIn(5, 64)) // Waiting for 64 bytes of data from endpoint #5.
.then(result => {
const decoder = new TextDecoder();
console.log('Received: ' + decoder.decode(result.data));
})
.catch(error => { console.error(error); });
โปรดทราบว่าไลบรารี WebUSB ที่ฉันใช้เพียงใช้โปรโตคอลตัวอย่าง 1 รายการ (ตามโปรโตคอลซีเรียล USB มาตรฐาน) และผู้ผลิตสามารถสร้างชุดและปลายทางประเภทใดก็ได้ที่ต้องการ การโอนการควบคุมนั้นเหมาะอย่างยิ่งสำหรับคำสั่งการกำหนดค่าขนาดเล็ก เนื่องจากได้รับลำดับความสำคัญของรถบัสและมีโครงสร้างที่กำหนดไว้เป็นอย่างดี
และนี่คือภาพสเก็ตช์ ที่อัปโหลดไปยังกระดาน Arduino
// Third-party WebUSB Arduino library
#include <WebUSB.h>
WebUSB WebUSBSerial(1 /* https:// */, "webusb.github.io/arduino/demos");
#define Serial WebUSBSerial
void setup() {
Serial.begin(9600);
while (!Serial) {
; // Wait for serial port to connect.
}
Serial.write("WebUSB FTW!");
Serial.flush();
}
void loop() {
// Nothing here for now.
}
ไลบรารี WebUSB Arduino ของบุคคลที่สามที่ใช้ในโค้ดตัวอย่างด้านบนจะดำเนินการ 2 อย่างดังนี้
- อุปกรณ์จะทำหน้าที่เป็นอุปกรณ์ WebUSB ที่ช่วยให้ Chrome อ่าน URL ของหน้า Landing Page ได้
- เพื่อแสดง WebUSB Serial API ที่คุณ��ช้เพื่อลบล้างค่าเริ่มต้นได้
ดูที่โค้ด JavaScript อีกครั้ง เมื่อฉันเลือก device
โดยผู้ใช้ device.open()
จะเรียกใช้ขั้นตอนเฉพาะแพลตฟอร์มทั้งหมดเพื่อเริ่มเซสชันกับอุปกรณ์ USB จากนั้นฉันต้องเลือกการกำหนดค่า USB ที่พร้อมใช้งานด้วย device.selectConfiguration()
โปรดทราบว่าการกำหนดค่าจะระบุวิธีการขับเคลื่อนอุปกรณ์ การใช้พลังงานสูงสุด และจำนวนอินเทอร์เฟซ
เมื่อพูดถึงอินเทอร์เฟซ ฉันยังต้องขอสิทธิ์การเข้าถึงพิเศษกับ device.claimInterface()
ด้วย เนื่องจากข้อมูลจะโอนไปยังอินเทอร์เฟซหรือปลายทางที่เกี่ยวข้องได้ก็ต่อเมื่อมีการอ้างสิทธิ์อินเทอร์เฟซเท่านั้น สุดท้ายคือการโทร
device.controlTransferOut()
เพื่อตั้งค่าอุปกรณ์ Arduino ด้วยคำสั่งที่เหมาะสมเพื่อสื่อสารผ่าน WebUSB Serial API
จากนั้น device.transferIn()
จะดำเนินการถ่ายโอนจำนวนมากไปยังอุปกรณ์เพื่อแจ้งว่าโฮสต์พร้อมรับข้อมูลจำนวนมากแล้ว จากนั้น ก็จะได้รับคำสัญญาดังกล่าวด้วยออบเจ็กต์ result
ที่มี DataView data
ซึ่งต้องมีการแยกวิเคราะห์อย่างเหมาะสม
หากคุณคุ้นเคยกับ USB ทุกอย่างนี้น่าจะคุ้นตากันดี
ฉันต้องการเพิ่ม
WebUSB API ช่วยให้คุณโต้ตอบกับอุปกรณ์ปลายทาง/การโอนผ่าน USB ได้ทุกประเภท ดังนี้
- การโอนการควบคุมที่ใช้เพื่อส่งหรือรับพารามิเตอร์การกำหนดค่าหรือคำสั่งไปยังอุปกรณ์ USB ได้รับการจัดการด้วย
controlTransferIn(setup, length)
และcontrolTransferOut(setup, data)
- การโอนที่แทรกเข้ามาเองซึ่งใช้สำหรับข้อมูลที่ละเอียดอ่อนในช่วงเวลาสั้นๆ จะได้รับการจัดการด้วยวิธีเดียวกับการโอนแบบกลุ่มด้วย
transferIn(endpointNumber, length)
และtransferOut(endpointNumber, data)
- การโอน ISOCHRONOUS ซึ่งใช้สำหรับสตรีมข้อมูล เช่น วิดีโอและเสียง จะจัดการด้วย
isochronousTransferIn(endpointNumber, packetLengths)
และisochronousTransferOut(endpointNumber, data, packetLengths)
- การโอนแบบกลุ่มซึ่งใช้เพื่อโอนข้อมูลที่ไม่ละเอียดอ่อนเวลาจำนวน��ากด้วยวิธีที่เชื่อถือได้จะได้รับการจัดการด้วย
transferIn(endpointNumber, length)
และtransferOut(endpointNumber, data)
คุณอาจดูโปรเจ็กต์ WebLight ของ Mike Tsao ซึ่งให้ตัวอย่างเบื้องต้นในการสร้างอุปกรณ์ LED ที่ควบคุมโดย USB ซึ่งออกแบบ���าสำหรับ WebUSB API (ไม่ได้ใช้ Arduino ที่นี่) คุณจะพบกับฮาร์ดแวร์ ซอฟต์แวร์ และเฟิร์มแวร์
เพิกถอนสิทธิ์เข้าถึงอุปกรณ์ USB
เว็บไซต์จะจัดระเบียบสิทธิ์เข้าถึงอุปกรณ์ USB ที่ไม่ต้องการอีกต่อไปได้ด้วยการเรียกใช้ forget()
ในอินสแตนซ์ USBDevice
ตัวอย่างเช่น สำหรับเว็บแอปพลิเคชันเพื่อการศึกษาที่ใช้บนคอมพิวเตอร์ที่ใช้ร่วมกันกับอุปกรณ์จำนวนมาก สิทธิ์สะสมจำนวนมากที่ผู้ใช้สร้างขึ้นทำให้ผู้ใช้ได้รับประสบการณ์ที่ไม่ดี
// Voluntarily revoke access to this USB device.
await device.forget();
เนื่องจาก forget()
พร้อมใช้งานใน Chrome 101 ขึ้นไป โปรดตรวจสอบว่ามีการรองรับฟีเจอร์นี้หรือไม่
if ("usb" in navigator && "forget" in USBDevice.prototype) {
// forget() is supported.
}
ขีดจำกัดของขนาดการโอน
ระบบปฏิบัติการบางระบบกำหนดขีดจำกัดของปริมาณข้อมูลที่อาจเป็นส่วนหนึ่งของธุรกรรม USB ที่รอดำเนินการ การแบ่งข้อมูลออกเป็นธุรกรรมที่เล็กลงและส่งข้อมูลครั้งละ 2-3 รายการเท่านั้นจะช่วยหลีกเลี่ยงข้อจำกัดเหล่านั้นได้ นอกจากนี้ยังช่วยลดปริมาณหน่วยความจำที่ใช้และทำให้แอปพลิเคชันรายงานความคืบหน้าเมื่อการโอนเสร็จสมบูรณ์ได้อีกด้วย
เนื่องจากการโอนหลายรายการที่ส่งไปยังปลายทางจะดำเนินการตามลำดับเสมอ จึงอาจปรับปรุงอัตราการส่งข้อมูลด้วยการส่งชิ้นส่วนที่เข้าคิวหลายรายการได้เพื่อหลีกเลี่ยงความล่าช้าระหว่างการโอนผ่าน USB ทุกครั้งที่ส่งกลุ่มอย่างเต็มรูปแบ�� ระบบจะแจ้งโค้ดว่าควรให้ข้อมูลเพิ่มเติมตามที่ระบุไว้ในตัวอย่างฟังก์ชันตัวช่วยด้านล่าง
const BULK_TRANSFER_SIZE = 16 * 1024; // 16KB
const MAX_NUMBER_TRANSFERS = 3;
async function sendRawPayload(device, endpointNumber, data) {
let i = 0;
let pendingTransfers = [];
let remainingBytes = data.byteLength;
while (remainingBytes > 0) {
const chunk = data.subarray(
i * BULK_TRANSFER_SIZE,
(i + 1) * BULK_TRANSFER_SIZE
);
// If we've reached max number of transfers, let's wait.
if (pendingTransfers.length == MAX_NUMBER_TRANSFERS) {
await pendingTransfers.shift();
}
// Submit transfers that will be executed in order.
pendingTransfers.push(device.transferOut(endpointNumber, chunk));
remainingBytes -= chunk.byteLength;
i++;
}
// And wait for last remaining transfers to complete.
await Promise.all(pendingTransfers);
}
เคล็ดลับ
แก้ไขข้อบกพร่องของ USB ใน Chrome ได้ง่ายขึ้นด้วยหน้าภายใน about://device-log
ซึ่งคุณจะดูเหตุการณ์ที่เกี่ยวข้องกับอุปกรณ์ USB ทั้งหมดได้ในที่เดียว
![ภาพหน้าจอของหน้าบันทึกอุปกรณ์เพื่อแก้ไขข้อบกพร่องของ WebUSB ใน Chrome](https://cdn.statically.io/img/developer.chrome.com/static/docs/capabilities/usb/image/screenshot-the-device-lo-5e45db14a43c3.png?hl=th)
หน้าภายใน about://usb-internals
ยังมีประโยชน์และช่วยให้คุณจำลองการเชื่อมต่อและการยกเลิกการเชื่อมต่อของอุปกรณ์ WebUSB เสมือนได้
วิธีนี้จะเป็นประโยชน์สำหรับการทดสอบ UI โดยไม่ต้องใช้ฮาร์ดแวร์จริง
![ภาพหน้าจอของหน้าภายในสำหรับแก้ไขข้อบกพร่องของ WebUSB ใน Chrome](https://cdn.statically.io/img/developer.chrome.com/static/docs/capabilities/usb/image/screenshot-the-internal-3f26665cde1c.png?hl=th)
ในระบบ Linux ส่วนใหญ่ อุปกรณ์ USB จะแมปด้วยสิทธิ์อ่านอย่างเดียวโดยค่าเริ่มต้น หากต้องการอนุญาตให้ Chrome เปิดอุปกรณ์ USB คุณจะต้องเพิ่มกฎ udev ใหม่ สร้างไฟล์ที่ /etc/udev/rules.d/50-yourdevicename.rules
โดยมีเนื้อหาต่อไปนี้
SUBSYSTEM=="usb", ATTR{idVendor}=="[yourdevicevendor]", MODE="0664", GROUP="plugdev"
โดยที่ [yourdevicevendor]
คือ 2341
หากอุปกรณ์ของคุณเป็น Arduino
และยังเพิ่ม ATTR{idProduct}
สำหรับกฎที่เจาะจงมากขึ้นได้ด้วย โปรดตรวจสอบว่า user
เป็นสมาชิกของกลุ่ม plugdev
จากนั้นเชื่อมต่ออุปกรณ์อีกครั้ง
แหล่งข้อมูล
- Stack Overflow: https://stackoverflow.com/questions/tagged/webusb
- ข้อกำหนดของ WebUSB API: http://wicg.github.io/webusb/
- สถานะฟีเจอร์ของ Chrome: https://www.chromestatus.com/feature/5651917954875392
- ปัญหาเกี่ยวกับข้อมูลจำเพาะ: https://github.com/WICG/webusb/issues
- ข้อบกพร่องในการใช้งาน: http://crbug.com?q=component:Blink>USB
- WebUSB ❤ ️Arduino: https://github.com/webusb/arduino
- IRC: #webusb บน IRC ของ W3C
- รายชื่ออีเมลของ WICG: https://lists.w3.org/Archives/Public/public-wicg/
- โปรเจ็กต์ WebLight: https://github.com/sowbug/weblight
ส่งทวีตไปที่ @ChromiumDev โดยใช้แฮชแท็ก
#WebUSB
และแจ้งให้เราทราบว่าคุณใช้แฮชแท็กนี้ที่ไหนและอย่างไร
ข้อความแสดงการยอมรับ
ขอขอบคุณ Joe Medley ที่อ่านบทความนี้