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 ID นี้
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 ที่ใช้ร่วมกับ WebUSB ได้นั้นทำได้ง่ายเพียงใด ดูวิธีการที่ 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 ทุกประเภทได้ดังนี้
- การควบคุมการโอนที่ใช้ส่งหรือรับพารามิเตอร์การกําหนดค่าหรือคําสั่งไปยังอุปกรณ์ 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 ที่รอดำเนินการ การแยกข้อมูลของคุณออกเป็นธุรกรรมขนาดเล็กๆ และส่งทีละไม่กี่รายการจะช่วยหลีกเลี่ยงข้อจำกัดเหล่านั้นได้ นอกจากนี้ ยังช่วยลดปริมาณหน่วยความจําที่ใช้และช่วยให้แอปพลิเคชันรายงานความคืบหน้าได้เมื่อการโอนเสร็จสมบูรณ์
เนื่องจากระบบจะดำเนินการโอนหลายรายการที่ส่งไปยังปลายทางตามลำดับเสมอ คุณจึงปรับปรุงประสิทธิภาพการส่งผ่านได้โดยส่งกลุ่มที่รอดำเนินการหลายกลุ่มเพื่อหลีกเลี่ยงเวลาในการตอบสนองระหว่างการโอน 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 ที่ตรวจสอบบทความนี้