تجعل WebUSB API منفذ USB أكثر أمانًا وسهولة في الاستخدام من خلال توفيره على الويب.
إذا قلت "USB" بشكل واضح وبسيط، هناك احتمال كبير أن تفكر فورًا في لوحات المفاتيح وأجهزة الماوس وأجهزة الصوت والفيديو وأجهزة التخزين. أنت على صواب، ولكن يمكنك العثور على أنواع أخرى من أجهزة Universal Serial Bus (USB) في السوق.
تتطلب أجهزة USB غير الموحدة هذه من موردي الأجهزة كتابة برامج تشغيل وحزم SDK خاصة بالنظام الأساسي حتى يتسنى لك (كمطوّر) الاستفادة منها. في السابق، كان هذا الرمز المخصّص للنظام الأساسي يمنع الويب من استخدام هذه الأجهزة. وهذا هو أحد أسباب إنشاء WebUSB API: لتوفير طريقة لعرض خدمات أجهزة USB على الويب. باستخدام واجهة برمجة التطبيقات هذه، سيتمكن مصنّعو الأجهزة من إنشاء حزم تطوير برامج (SDK) لـ JavaScript من عدّة أنظمة أساسية لأجهزتهم.
ولكن الأهم من ذلك هو أن هذا يجعل USB أكثر أمانًا وأسهل في الاستخدام عن طريق توصيله إلى الويب.
لنلقِ نظرة على السلوك المتوقّع لواجهة برمجة التطبيقات WebUSB API:
- شراء جهاز USB.
- عليك توصيله بجهاز الكمبيوتر. يظهر إشعار على الفور يتضمن الموقع الإلكتروني المناسب للانتقال إليه على هذا الجهاز.
- انقر على الإشعار. أصبح الموقع الإلكتروني متوفّرًا وجاهزًا للاستخدام.
- انقر للاتصال وسيظهر منتقي جهاز USB في Chrome حيث يمكنك اختيار جهازك.
مدهش!
كيف سيكون هذا الإجراء بدون WebUSB API؟
- ثبِّت تطبيقًا خاصًا بمنصّة معيّنة.
- إذا كان التطبيق متوافقًا مع نظام التشغيل، تأكَّد من أنّك نزّلت التطبيق الصحيح.
- ثبِّت العنصر. إذا كنت محظوظًا، لن تظهر لك أي رسائل أو نوافذ منبثقة مخيفة من نظام التشغيل تُحذّرك بشأن تثبيت برامج تشغيل أو تطبيقات من الإنترنت. فإذا لم يحالفك الحظ، تتعطل برامج التشغيل أو التطبيقات المثبتة وتضر جهاز الكمبيوتر. (تذكّر أن الهدف من إنشاء الويب هو احتواء المواقع الإلكترونية المُعطَّلة).
- إذا استخدمت الميزة مرة واحدة فقط، سيبقى الرمز على جهاز الكمبيوتر إلى أن تقرر إزالته. (على الويب، تتم في نهاية المطاف استعادة المساحة غير المستخدَمة).
قبل البدء
تفترض هذه المقالة أنّ لديك بعض المعرفة الأساسية حول آلية عمل USB. إذا لم يكن الأمر كذلك، ننصحك بالاطّلاع على USB in a NutShell. للحصول على معلومات أساسية عن USB، اطّلِع على مواصفات USB الرسمية.
تتوفّر WebUSB API في الإصدار 61 من Chrome.
متاحة لمرحلة التجربة والتقييم
للحصول على أكبر قدر ممكن من التعليقات من المطوّرين الذين يستخدمون WebUSB API في المجال، أضفنا هذه الميزة سابقًا في Chrome 54 وChrome 57 باعتبارها مرحلة تجريبية للمصدر.
انتهت آخر فترة تجريبية بنجاح في أيلول (سبتمبر) 2017.
الخصوصية والأمان
HTTPS فقط
وبسبب إمكانيات هذه الميزة، فإنّها لا تعمل إلا في السياقات الآمنة. وهذا يعني أنّك ستحتاج إلى إنشاء التطبيق مع مراعاة TLS.
إيماءة المستخدم مطلوبة
كإجراء احترازي للأمان، لا يمكن
استدعاء navigator.usb.requestDevice()
إلا من خلال إيماءة المستخدم، مثل لمسة أو نقرة بالماوس.
سياسة الأذونات
سياسة الأذونات هي آلية تتيح للمطوّرين تفعيل إيقاف ميزات المتصفّح وواجهات برمجة التطبيقات المختلفة بشكل انتقائي. ويمكن تحديده من خلال عنوان HTTP و/أو سمة iframe "allow".
يمكنك تحديد سياسة أذونات تتحكّم في ما إذا كانت سمة 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. إذا لم تكن على دراية
بهذه الأنواع من الوظائف، يمكنك الاطّلاع على هذا الدليل التعليمي الرائع حول الوعود. هناك شيء آخر، وهو أنّ () => {}
هي ببساطة دوالّ الأسهم في 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); });
قبل أن تسأل، لم أتوصّل إلى هذا الرقم الثنائي 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 للصفحة المقصودة، سيعرض Chrome إشعارًا مستمرًا عند توصيل جهاز USB. يؤدي النقر على هذا الإشعار إلى فتح الصفحة المقصودة.
التحدّث إلى لوحة 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 للصفحة المقصودة.
- وتعرض واجهة برمجة التطبيقات 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)
. - يتم نقل البيانات بشكل متزامن باستخدام
isochronousTransferIn(endpointNumber, packetLengths)
وisochronousTransferOut(endpointNumber, data, packetLengths)
، وذلك لبث البيانات مثل الفيديو والصوت. - إنّ عمليات النقل المجمّعة، التي تُستخدَم لنقل كمية كبيرة من البيانات غير الحسّاسة للوقت بطريقة
موثوق بها، تتم من خلال
transferIn(endpointNumber, length)
وtransferOut(endpointNumber, data)
.
يمكنك أيضًا الاطّلاع على مشروع WebLight الذي أعدّه "مايك تساو"، والذي يقدّم مثالاً أساسيًا على إنشاء جهاز مزوّد بمصابيح LED يتم التحكّم فيه عبر USB ومصمّم لواجهة برمجة التطبيقات WebUSB API (لا يتم استخدام Arduino هنا). ويمكنك العثور على الأجهزة والبرامج والبرامج الثابتة.
إبطال إذن الوصول إلى جهاز USB
يمكن للموقع الإلكتروني إزالة الأذونات للوصول إلى جهاز USB لم يعُد بحاجة إليه من خلال طلب البيانات من forget()
على مثيل USBDevice
. على سبيل المثال، بالنسبة إلى
تطبيق ويب تعليمي يتم استخدامه على كمبيوتر مشترَك مع العديد من الأجهزة، يؤدي العدد الكبير
من الأذونات المتراكمة التي ينشئها المستخدم إلى تقديم تجربة سيئة للمستخدم.
// Voluntarily revoke access to this USB device.
await device.forget();
بما أنّ forget()
متاح في الإصدار 101 من Chrome أو الإصدارات الأحدث، تحقّق مما إذا كانت هذه الميزة متوفرة
مع ما يلي:
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 الافتراضية وإيقافها.
يكون ذلك مفيدًا لإجراء اختبار واجهة المستخدم بدون استخدام أجهزة فعلية.
في معظم أنظمة 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 على مراجعة هذه المقالة.