الوصول إلى أجهزة USB على الويب

تساعد واجهة برمجة تطبيقات WebUSB API على جعل USB أكثر أمانًا وأسهل في الاستخدام من خلال توصيله إلى الويب.

François Beaufort
François Beaufort

إذا قلت ببساطة "USB"، فهناك احتمال كبير أنك فكر على الفور في لوحات المفاتيح وأجهزة الماوس والصوت والفيديو وأجهزة التخزين. انتهى صحيح، لكنك ستجد أنواعًا أخرى من أجهزة الناقل التسلسلي العالمي (USB) هناك.

تتطلب أجهزة USB غير الموحدة هذه من موردي الأجهزة كتابة نظام أساسي برامج التشغيل وحزم SDK حتى تتمكن (المطور) من الاستفادة منها. للأسف، منع هذا الرمز الخاص بالنظام الأساسي من السابق استخدام هذه الأجهزة الويب. وهذا أحد أسباب إنشاء WebUSB API: توفير وسيلة لكشف خدمات أجهزة USB على الويب. باستخدام واجهة برمجة التطبيقات هذه، يستخدم الأجهزة ستتمكّن الشركات المصنّعة من إنشاء حِزم تطوير برامج (SDK) لـ JavaScript من عدّة منصات الأجهزة.

ولكن الأهم من ذلك هو أن هذا يجعل USB أكثر أمانًا وأسهل في الاستخدام عن طريق توفير على الويب.

لنرَ السلوك الذي يمكنك توقّعه عند استخدام WebUSB API:

  1. شراء جهاز USB.
  2. عليك توصيله بجهاز الكمبيوتر. يظهر إشعار على الفور، على الجانب الأيسر موقع الويب الذي تريد الانتقال إليه على هذا الجهاز.
  3. انقر على الإشعار. الموقع متوفر وجاهز للاستخدام!
  4. يمكنك النقر للاتصال وستظهر أداة اختيار أجهزة USB في Chrome حيث يمكنك اختَر جهازك.

مدهش!

كيف سيكون هذا الإجراء بدون WebUSB API؟

  1. تثبيت تطبيق خاص بالنظام الأساسي
  2. إذا كان متوافقًا حتى مع نظام التشغيل لديّ، فتحقّق من أنني نزّلت الشيء الصحيح.
  3. ثبِّت العنصر. إذا حالفك الحظ، لن تتلقّى أي مطالبات أو نوافذ منبثقة مخيفة بشأن نظام التشغيل. يحذّرك من تثبيت برامج تشغيل أو تطبيقات من الإنترنت. في حال حذف فأنت سيئ الحظ، تعطل برامج التشغيل أو التطبيقات المثبتة وتُلحق الضرر جهاز الكمبيوتر. (تجدر الإشارة إلى أنّ الويب مُصمَّم لاحتواء جميع الأعطال المواقع الإلكترونية).
  4. إذا استخدمت الميزة مرة واحدة فقط، سيظل الرمز ظاهرًا على جهاز الكمبيوتر إلى أن فكر في إزالتها. (على الويب، تظل مساحة العناصر غير المستخدمة reclaimed.)

قبل البدء

تفترض هذه المقالة أن لديك بعض المعرفة الأساسية حول آلية عمل USB. إذا لم يكن كذلك، نقترح عليك قراءة USB في NutShell. للحصول على معلومات أساسية حول USB، يمكنك مراجعة مواصفات USB الرسمية.

تتوفَّر WebUSB API في الإصدار Chrome 61.

متاحة لمرحلة التجربة والتقييم

للحصول على أكبر قدر ممكن من الملاحظات من المطوّرين الذين يستخدمون WebUSB في هذا المجال، فقد أضفنا هذه الميزة في السابق إلى Chrome 54 57 على أنّه مرحلة تجريبية للمصدر.

انتهت آخر فترة تجريبية بنجاح في أيلول (سبتمبر) 2017.

الخصوصية والأمان

HTTPS فقط

وبسبب إمكانيات هذه الميزة، فإنّها لا تعمل إلا في السياقات الآمنة. يعني ذلك ستحتاج إلى الإنشاء مع وضع TLS في الاعتبار.

إيماءة المستخدم مطلوبة

كإجراء أمني وقائي، قد يتم السماح لتطبيق navigator.usb.requestDevice() فقط من خلال إيماءة المستخدم مثل اللمس أو النقر بالماوس.

سياسة الأذونات

سياسة الأذونات هي آلية تتيح للمطوّرين تفعيل الميزة بشكل انتقائي وإيقاف الميزات المتنوعة في المتصفّح وواجهات برمجة التطبيقات يمكن تحديدها عن طريق HTTP و/أو إطار iframe "allow" .

يمكنك تحديد سياسة أذونات تتحكّم في ما إذا كانت السمة usb ظاهرة على كائن المستكشف، أو بعبارة أخرى إذا سمحت باستخدام 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 واحد متصل باستخدام navigator.usb.requestDevice() أو الاتصال بالرقم navigator.usb.getDevices() للحصول على قائمة بجميع أجهزة USB المتصلة التي حصل الموقع الإلكتروني على إذن بالوصول إليها.

تستخدم الدالة navigator.usb.requestDevice() كائن JavaScript إلزاميًا التي تعرِّف filters. تُستخدم هذه الفلاتر لمطابقة أي جهاز USB مع معرّف المورّد (vendorId) المحدّد، ويمكنك اختيار معرّف المنتج (productId). يمكن للمفاتيح classCode وprotocolCode وserialNumber وsubclassCode أن هناك أيضًا.

لقطة شاشة لإشعار مستخدم جهاز USB في Chrome
طلب من مستخدم جهاز USB

على سبيل المثال، إليك كيفية الوصول إلى جهاز 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. يؤدي النقر على هذا الإشعار إلى فتح الصفحة المقصودة.

لقطة شاشة لإشعار WebUSB في Chrome
إشعار WebUSB.

التحدّث إلى لوحة مفاتيح USB من Arduino

حسنًا، لنتعرف الآن على مدى سهولة الاتصال من جهاز WebUSB لوحة Arduino فوق منفذ 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.

بعد ذلك، يجري device.transferIn() عملية نقل مجمّعة إلى جهاز لإبلاغه بأنّ المضيف جاهز لتلقّي البيانات المجمّعة. بعد ذلك، يتم تنفيذ الوعد باستخدام عنصر result يحتوي على DataView data الذي تحليله بشكل مناسب.

إذا كنت معتادًا على استخدام المنافذ USB، فمن المفترض أن يبدو كل هذا مألوفًا.

أريد المزيد

تتيح لك واجهة برمجة التطبيقات WebUSB API إمكانية التفاعل مع جميع أنواع نقاط النهاية ونقل USB:

  • Ctrl من عمليات النقل، تُستخدم لإرسال أو استلام التهيئة أو الأمر المعلمات إلى جهاز 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 الذي أنشأه "مايك تساو" مثالاً من أرض الواقع لتصميم جهاز LED يتم التحكم فيه عبر USB ومصمّم لواجهة برمجة تطبيقات WebUSB (وليس باستخدام 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 في مكان واحد.

لقطة شاشة لصفحة سجلّ الجهاز لتصحيح أخطاء WebUSB في Chrome
صفحة سجلّ الجهاز في Chrome لتصحيح أخطاء WebUSB API.

تُعد الصفحة الداخلية about://usb-internals مفيدة أيضًا وتتيح لك لمحاكاة اتصال أجهزة WebUSB الافتراضية وانقطاع الاتصال بها. يكون هذا مفيدًا لإجراء اختبار واجهة المستخدم بدون أجهزة حقيقية.

لقطة شاشة للصفحة الداخلية لتصحيح أخطاء WebUSB في Chrome
صفحة داخلية في Chrome لتصحيح أخطاء WebUSB API

في معظم أنظمة 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. بعد ذلك، ما عليك سوى إعادة توصيل جهازك.

الموارد

إرسال تغريدة إلى @ChromiumDev باستخدام علامة التصنيف #WebUSB عليك إعلامنا بمكان تطبيقك وطريقة استخدامه

شكر وتقدير

شكرًا لـ جو ميدلي على مراجعة هذه المقالة.