WebUSB API ทำให้ USB ปลอดภัยและใช้งานได้ง่ายขึ้นด้วยการนำเทคโนโลยีนี้มาใช้กับเว็บ
หากเราพูดว่า "USB" สั้นๆ ง่ายๆ คุณก็คงนึกถึงแป้นพิมพ์ เมาส์ เสียง วิดีโอ และอุปกรณ์จัดเก็บข้อมูลในทันที ถูกต้อง แต่คุณจะเห็นอุปกรณ์ Universal Serial Bus (USB) ประเภทอื่นๆ อยู่
อุปกรณ์ USB ที่ไม่เป็นไปตามมาตรฐานเหล่านี้กำหนดให้ผู้ให้บริการฮาร์ดแวร์ต้องเขียนไดรเวอร์และ SDK สำหรับแพลตฟอร์มที่เฉพาะเจาะจงเพื่อให้คุณ (นักพัฒนาแอป) ใช้ประโยชน์จากอุปกรณ์ได้ แต่ที่ผ่านมารหัสเฉพาะแพลตฟอร์มนี้ทำให้เว็บไม่สามารถใช้อุปกรณ์เหล่านี้ได้ ด้วยเหตุนี้ WebUSB API จึงถือกำเนิดขึ้นเพื่อใช้เป็นช่องทางในการแสดงบริการของอุปกรณ์ USB บนเว็บ API นี้จะช่วยให้ผู้ผลิตฮาร์ดแวร์สามารถสร้าง SDK ของ JavaScript แบบข้ามแพลตฟอร์มสำหรับอุปกรณ์ของตนได้
แต่ที่สำคัญที่สุดคือการดำเนินการนี้จะทำให้ USB ปลอดภัยและใช้งานได้ง่ายขึ้นด้วยการนําไปใช้กับเว็บ
มาดูลักษณะการทำงานที่คุณอาจพบได้เมื่อใช้ WebUSB API
- ซื้ออุปกรณ์ USB
- เสียบเข้ากับคอมพิวเตอร์ การแจ้งเตือนจะปรากฏขึ้นทันทีพร้อมเว็บไซต์ที่ถูกต้องสำหรับอุปกรณ์นี้
- คลิกการแจ้งเตือน เว็บไซต์พร้อมใช้งานแล้ว
- คลิกเพื่อเชื่อมต่อแล้วเครื่องมือเลือกอุปกรณ์ USB จะปรากฏขึ้นใน Chrome ซึ่งคุณจะเลือกอุปกรณ์ได้
เสร็จแล้ว
กระบวนการนี้จะเป็นอย่างไรหากไม่มี WebUSB API
- ติดตั้งแอปพลิเคชันเฉพาะแพลตฟอร์ม
- หากระบบปฏิบัติการของฉันรองรับ โปรดยืนยันว่าฉันดาวน์โหลดแอปที่ถูกต้องแล้ว
- ติดตั้งอุปกรณ์ หากโชคดี คุณจะไม่ได้รับการแจ้งเตือนหรือป๊อปอัปที่น่ากลัวจากระบบปฏิบัติการซึ่งเตือนคุณเกี่ยวกับการติดตั้งไดรเวอร์/แอปพลิเคชันจากอินเทอร์เน็ต หากโชคไม่ดี ไดรเวอร์หรือแอปพลิเคชันที่ติดตั้งอาจทำงานผิดปกติและส่งผลเสียต่อคอมพิวเตอร์ (โปรดทราบว่าเว็บสร้างขึ้นเพื่อมีเว็บไซต์ที่ทำงานผิดปกติ)
- หากคุณใช้ฟีเจอร์นี้เพียงครั้งเดียว รหัสจะยังคงอยู่ในคอมพิวเตอร์จนกว่าคุณจะนึกขึ้นว่าจะนําออก (ในเว็บ ระบบจะเรียกคืนพื้นที่ที่ไม่ได้ใช้ในท้ายที่สุด)
ก่อนจะเริ่มต้น
บทความนี้ถือว่าคุณมีความรู้พื้นฐานเกี่ยวกับวิธีการทำงานของ USB หากไม่ เราขอแนะนำให้อ่านUSB in a NutShell ดูข้อมูลเบื้องต้นเกี่ยวกับ USB ได้ที่ข้อมูลจำเพาะอย่างเป็นทางการของ USB
WebUSB API พร้อมใช้งานใน Chrome 61
พร้อมใช้งานสำหรับช่วงทดลองใช้จากต้นทาง
ก่อนหน้านี้เราได้เพิ่มฟีเจอร์นี้ใน Chrome 54 และ Chrome 57 เป็นช่วงทดลองใช้จากต้นทางเพื่อให้ได้รับความคิดเห็นจากนักพัฒนาซอฟต์แวร์ที่ใช้ WebUSB API ในสนามมากที่สุด
การทดลองใช้ครั้งล่าสุดสิ้นสุดลงเรียบร้อยแล้วเมื่อเดือนกันยายน 2017
ความเป็นส่วนตัวและความปลอดภัย
HTTPS เท่านั้น
ฟีเจอร์นี้จึงใช้ได้เฉพาะในบริบทที่ปลอดภัย ซึ่งหมายความว่าคุณจะต้องสร้างโดยคำนึงถึง TLS
ต้องใช้ท่าทางสัมผัสของผู้ใช้
navigator.usb.requestDevice()
จะเรียกได้ผ่านท่าทางสัมผัสของผู้ใช้เท่านั้น เช่น การสัมผัสหรือการคลิกเมาส์ เพื่อความปลอดภัย
นโยบายสิทธิ์
นโยบายสิทธิ์เป็นกลไกที่ช่วยให้นักพัฒนาแอปเปิดใช้และปิดใช้ฟีเจอร์และ API ต่างๆ ของเบราว์เซอร์ได้ ซึ่งสามารถกําหนดผ่านส่วนหัว HTTP และ/หรือแอตทริบิวต์ "allow" ของ 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 อาศัย Promises ของ JavaScript เป็นอย่างมาก หากไม่คุ้นเคยกับ Promise ให้ดูบทแนะนำ Promise ที่ยอดเยี่ยมนี้ อีกอย่าง () => {}
คือ ฟังก์ชันลูกศรของ ECMAScript 2015
รับสิทธิ์เข้าถึงอุปกรณ์ USB
คุณสามารถแจ้งให้ผู้ใช้เลือกอุปกรณ์ USB ที่เชื่อมต่ออยู่รายการเดียวโดยใช้ navigator.usb.requestDevice()
หรือเรียกใช้ navigator.usb.getDevices()
เพื่อดูรายการอุปกรณ์ USB ที่เชื่อมต่ออยู่ทั้งหมดซึ่งเว็บไซต์ได้รับสิทธิ์เข้าถึง
ฟังก์ชัน navigator.usb.requestDevice()
จะรับออบเจ็กต์ JavaScript ที่ต้องระบุซึ่งกำหนด filters
ตัวกรองเหล่านี้ใช้เพื่อจับคู่อุปกรณ์ USB กับผู้ให้บริการ (vendorId
) ที่ระบุและตัวระบุผลิตภัณฑ์ (productId
) (ไม่บังคับ)
คุณยังกําหนดคีย์ classCode
, protocolCode
, serialNumber
และ subclassCode
ได้ในไฟล์ดังกล่าวด้วย
ตัวอย่างเช่น วิธีเข้าถึงอุปกรณ์ 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); });
ก่อนที่คุณจะถาม เราไม่ได้คิดเลขฐาน 16 0x2341
นี้ขึ้นมาเอง เราเพียงค้นหาคำว่า "Arduino" ในรายการรหัส USB นี้
USB device
ที่แสดงในคำมั่นสัญญาที่ดำเนินการแล้วด้านบนมีข้อมูลพื้นฐานแต่สำคัญบางอย่างเกี่ยวกับอุปกรณ์ เช่น เวอร์ชัน 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
พูดคุยกับบอร์ด Arduino USB
ทีนี้มาดูกันว่าการสื่อสารจากบอร์ด 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 ที่เราใช้เป็นเพียงการใช้โปรโตคอลตัวอย่างโปรโตคอลเดียว (อิงตามโปรโตคอลซีเรียล 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 ของบุคคลที่สามที่ใช้ในโค้ดตัวอย่างด้านบนจะทําสิ่งต่อไปนี้เป็นหลัก
- อุปกรณ์จะทำหน้าที่เป็นอุปกรณ์ 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 ทั้งหมดต่อไปนี้
- การโอน CONTROL ซึ่งใช้ส่งหรือรับพารามิเตอร์การกําหนดค่าหรือคําสั่งไปยังอุปกรณ์ 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 ได้ในที่เดียว
หน้าภายใน about://usb-internals
ยังมีประโยชน์และช่วยให้คุณจำลองการเชื่อมต่อและการยกเลิกการเชื่อมต่ออุปกรณ์ WebUSB เสมือนได้ด้วย
ซึ่งจะเป็นประโยชน์สำหรับการทดสอบ UI โดยไม่ต้องใช้ฮาร์ดแวร์จริง
ในระบบ Linux ส่วนใหญ่ ระบบจะแมปอุปกรณ์ USB ด้วยสิทธิ์อ่านอย่างเดียวโดยค่าเริ่มต้น หากต้องการอนุญาตให้ Chrome เปิดอุปกรณ์ USB คุณจะต้องเพิ่ม udev
rule ใหม่ สร้างไฟล์ที่ /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 ที่ตรวจสอบบทความนี้