أجهزة USB

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

للحصول على معلومات أساسية حول تقنية USB، يُرجى الاطّلاع على مواصفات USB الرسمية. إنّ USB في NutShell هو دورة تدريبية مكثّفة قد تكون مفيدة لك.

متطلبات ملف البيان

تتطلب واجهة برمجة تطبيقات USB الحصول على إذن "usb" في ملف البيان:

"permissions": [
  "usb"
]

بالإضافة إلى ذلك، لمنع الطباعة باستخدام بصمة الإصبع، يجب أن تذكر في ملف البيان جميع أنواع الأجهزة التي تريد الوصول إليها. يتوافق كل نوع من أجهزة USB مع زوج من معرّف المنتج/معرّف المورّد (VID/PID). يمكنك استخدام usb.getDevices لتعداد الأجهزة حسب زوج VID/PID.

يجب أن تذكر في ملف البيان لتطبيقك أزواج VID/PID لكل نوع من الأجهزة التي تريد استخدامها بموجب إذن usbDevices، كما هو موضّح في المثال أدناه:

"permissions": [
  {
    "usbDevices": [
      {
        "vendorId": 123,
        "productId": 456
      }
    ]
  }
]

بدايةً من Chrome 57، يتم تخفيف متطلبات الإعلان عن جميع أنواع الأجهزة في بيان التطبيق بالنسبة إلى التطبيقات التي تعمل على أنها تطبيقات Kiosk لنظام التشغيل ChromeOS. بالنسبة إلى تطبيقات Kiosk، يمكنك استخدام خاصية إذن interfaceClass لطلب إذن بالوصول إلى أجهزة USB التي:

  • تنفيذ واجهة USB لفئة واجهة معيّنة
  • تتضمّن فئة محدَّدة من أجهزة USB

على سبيل المثال، سيمنح الإذن usbDevices التالي التطبيق بالوصول إلى جميع أجهزة USB التي تنفّذ واجهة طابعة (رمز فئة الواجهة 7) وأجهزة موزع USB (رمز فئة الجهاز 9):

"permissions": [
  {
    "usbDevices": [
      {"interfaceClass": 7},
      {"interfaceClass": 9}
    ]
  }
]

للحصول على قائمة قيم interfaceClass المقبولة، يُرجى الاطّلاع على رموز فئات USB.

يمكن دمج السمة interfaceClass مع السمة vendorId للوصول إلى أجهزة USB فقط من مورّد محدّد، كما يوضِّح المثال التالي:

"permissions": [
  {
    "usbDevices": [
      {
        "vendorId": 123,
        "interfaceClass": 7
      }
    ]
  }
]

العثور على جهاز

لتحديد ما إذا كان هناك جهاز محدد واحد أو أكثر متصل بنظام المستخدم، استخدِم طريقة usb.getDevices:

chrome.usb.getDevices(enumerateDevicesOptions, callback);
المعلمة (النوع)الوصف
EnumerateDevicesOptions (كائن)تمثّل هذه السمة كائنًا يحدّد كلاً من vendorId (الطويل) وproductId (الطول) المستخدَم للعثور على النوع الصحيح للجهاز في الحافلة. يجب أن يذكر ملف البيان قسم الأذونات usbDevices الذي يدرج كل أزواج vendorId وdeviceId التي يريد تطبيقك الوصول إليها.
رد الاتصال (الوظيفة)يتم استدعاءه عند انتهاء تعداد الجهاز. سيتم تنفيذ عملية الاستدعاء باستخدام معلَمة واحدة في مصفوفة من عناصر Device بثلاث خصائص: device وvendorId وproductId. سمة الجهاز هي معرّف ثابت لجهاز متصل. ولن يتغير الوضع إلى أن يتم فصل الجهاز عن مصدر الطاقة. وتكون تفاصيل المعرّف مبهمة وقابلة للتغيير. يُرجى عدم الاعتماد على نوعه الحالي.
وإذا لم يتم العثور على أي أجهزة، ستكون المصفوفة فارغة.

مثال:

function onDeviceFound(devices) {
  this.devices=devices;
  if (devices) {
    if (devices.length > 0) {
      console.log("Device(s) found: "+devices.length);
    } else {
      console.log("Device could not be found");
    }
  } else {
    console.log("Permission denied.");
  }
}

chrome.usb.getDevices({"vendorId": vendorId, "productId": productId}, onDeviceFound);

فتح الجهاز

بعد عرض كائنات Device، يمكنك فتح الجهاز باستخدام usb.openDevice للحصول على مؤشر اتصال. لا يمكنك التواصل مع أجهزة USB إلا باستخدام مقابض الاتصال.

الموقعالوصف
جهاز واحدتم استلام الكائن في معاودة الاتصال usb.getDevices.
بيانات (صفيف مؤقت)يحتوي على البيانات التي يرسلها الجهاز إذا كانت عملية النقل واردة.

مثال:

var usbConnection = null;
var onOpenCallback = function(connection) {
  if (connection) {
    usbConnection = connection;
    console.log("Device opened.");
  } else {
    console.log("Device failed to open.");
  }
};

chrome.usb.openDevice(device, onOpenCallback);

لتبسيط عملية الفتح، يمكنك استخدام طريقة usb.findDevices التي تعدّ الأجهزة وتطلب الوصول إليها وتفتحها في مكالمة واحدة:

chrome.usb.findDevices({"vendorId": vendorId, "productId": productId, "interfaceId": interfaceId}, callback);

أي ما يعادل:

chrome.usb.getDevices({"vendorId": vendorId, "productId": productId}, function (devices) {
  if (!devices) {
    console.log("Error enumerating devices.");
    callback();
    return;
  }
  var connections = [], pendingAccessRequests = devices.length;
  devices.forEach(function (device) {
    chrome.usb.requestAccess(interfaceId, function () {
      // No need to check for errors at this point.
      // Nothing can be done if an error occurs anyway. You should always try
      // to open the device.
      chrome.usb.openDevices(device, function (connection) {
        if (connection) connections.push(connection);
        pendingAccessRequests--;
        if (pendingAccessRequests == 0) {
          callback(connections);
        }
      });
    });
  })
});

يتم نقل البيانات من خلال USB واستلامها من جهاز.

يحدد بروتوكول USB أربعة أنواع من عمليات النقل: التحكم والمجمّع ومتساوي المحاذاة والمقاطعة. نوضح في ما يلي عمليات النقل هذه.

ويمكن أن تحدث عمليات النقل في كلا الاتجاهين: من جهاز إلى مضيف (داخلي) و من مضيف إلى جهاز (خارجي). نظرًا لطبيعة بروتوكول USB، يجب أن يبدأ المضيف الرسائل الواردة والصادرة (جهاز الكمبيوتر الذي يشغّل تطبيق Chrome). بالنسبة إلى الرسائل الواردة (من الجهاز إلى المضيف)، يرسل المضيف (الذي تم بدء تشغيله باستخدام رمز JavaScript) رسالة تم وضع علامة عليها باعتبارها "الواردة" إلى الجهاز. تعتمد تفاصيل الرسالة على الجهاز، ولكن عادةً ما سيكون لها بعض تحديد ما تطلبه منها. يستجيب الجهاز بعد ذلك بالبيانات المطلوبة. يعالج Chrome استجابة الجهاز ويتم تسليمها بشكل غير متزامن إلى معاودة الاتصال التي تحدّدها في طريقة النقل. وتكون الرسالة الصادرة (من المضيف إلى الجهاز) مشابهة، لكن الاستجابة لا تحتوي على بيانات تم عرضها من الجهاز.

بالنسبة إلى كل رسالة واردة من الجهاز، يتلقى رد الاتصال المحدد كائن حدث بالسمات التالية:

الموقعالوصف
رمز النتيجة (عدد صحيح)0 هو النجاح؛ والقيم الأخرى تشير إلى الفشل. يمكن قراءة سلسلة خطأ
من chrome.extension.lastError عند الإشارة إلى حدوث خطأ
.
بيانات (صفيف مؤقت)يحتوي على البيانات التي يرسلها الجهاز إذا كانت عملية النقل واردة.

مثال:

var onTransferCallback = function(event) {
   if (event && event.resultCode === 0 && event.data) {
     console.log("got " + event.data.byteLength + " bytes");
   }
};

chrome.usb.bulkTransfer(connectionHandle, transferInfo, onTransferCallback);

التحكم في عمليات النقل

تُستخدم عمليات نقل التحكّم بشكل عام لإرسال أو تلقّي معلَمات الإعدادات أو الأوامر إلى جهاز USB. تُرسِل طريقة التحكم في النقل دائمًا إلى/تقرأ من نقطة النهاية 0، ولا حاجة إلى واجهة المطالبة. الطريقة بسيطة وتتلقى ثلاث معلمات:

chrome.usb.controlTransfer(connectionHandle, transferInfo, transferCallback)
المَعلمة (الأنواع)الوصف
connectionHandleتم استلام الكائن في معاودة الاتصال usb.openDevice.
transferInfoكائن معلَمة مع قيم من الجدول أدناه. راجِع مواصفات بروتوكول جهاز USB للحصول على التفاصيل.
transferCallback()تم تفعيله عند اكتمال عملية النقل.

قيم العنصر transferInfo:

القيمةالوصف
نوع الطلب (سلسلة)"بائع" أو "عادي" أو "فئة" أو "محجوز".
المستلِم (سلسلة)أو "الجهاز" أو "واجهة" أو "نقطة النهاية" أو "غير ذلك".
الاتجاه (سلسلة)"داخل" أو "خارج". يُستخدَم الاتجاه "في" لإبلاغ الجهاز
بأنّه يجب أن يرسل المعلومات إلى المضيف. يبدأ المضيف في جميع الاتصالات على ناقل USB
، لذا استخدِم عملية النقل "في" للسماح للجهاز
بإعادة إرسال المعلومات.
الطلب (عدد صحيح)يتمّ تحديدها من خلال بروتوكول الجهاز.
القيمة (عدد صحيح)يتمّ تحديدها من خلال بروتوكول الجهاز.
الفهرس (عدد صحيح)يتمّ تحديدها من خلال بروتوكول الجهاز.
الطول (عدد صحيح)يُستخدم فقط عندما يكون الاتجاه "في". يعمل هذا الخيار على إبلاغ الجهاز بأنّ هذه هي مقدار البيانات التي يتوقعها المضيف في الاستجابة.
بيانات (صفيف مؤقت)يتم تحديدها بواسطة بروتوكول الجهاز، وهي مطلوبة عندما يكون الاتجاه "خارج".

مثال:

var transferInfo = {
  "requestType": "vendor",
   "recipient": "device",
  "direction": "out",
  "request":  0x31,
  "value": 120,
  "index": 0,
  // Note that the ArrayBuffer, not the TypedArray itself is used.
  "data": new Uint8Array([4, 8, 15, 16, 23, 42]).buffer
};
chrome.usb.controlTransfer(connectionHandle, transferInfo, optionalCallback);

عمليات النقل ISO CHRONOUS

تعتبر عمليات النقل غير المتوازية أكثر أنواع النقل عن طريق USB تعقيدًا. إنها تُستخدم بشكل شائع لتدفقات البيانات، مثل الفيديو والصوت. لبدء عملية نقل متساوية الأوقات (سواءً كانت واردة أو صادرة)، عليك استخدام طريقة usb.isochronousTransfer:

chrome.usb.isochronousTransfer(connectionHandle, isochronousTransferInfo, transferCallback)
المَعلمةالوصف
connectionHandleتم استلام الكائن في معاودة الاتصال usb.openDevice.
isochronousTransferInfoكائن معلَمة يتضمن القيم في الجدول أدناه.
transferCallback()تم تفعيله عند اكتمال عملية النقل.

قيم العنصر isochronousTransferInfo:

القيمةالوصف
TransferInfo (كائن)عنصر يتضمّن السمتَين التاليتَين:
direction (string): "in" أو "out".
نقطة النهاية (عدد صحيح): تم تحديدها بواسطة جهازك. يمكن عادةً معرفة ذلك من خلال استخدام أداة فحص USB، مثل lsusb -v
الطول (عدد صحيح): لا يُستخدم إلا عندما يكون الاتجاه "في". يتم إشعار الجهاز بأنّ هذه هي المقدار الذي يتوقعه المضيف من البيانات.
يجب أن تكون المدة packets × packetLength على الأقل.
البيانات (الصفيف المؤقت): يحدّدها بروتوكول الجهاز، ولا يُستخدم هذا الخيار إلا عندما يكون الاتجاه "خارج".
الحزم (عدد صحيح)إجمالي عدد الحزم المتوقعة في عملية النقل هذه.
طول الحزمة (عدد صحيح)الطول المتوقع لكل حزمة في عملية النقل هذه.

مثال:

var transferInfo = {
  "direction": "in",
  "endpoint": 1,
  "length": 2560
};

var isoTransferInfo = {
  "transferInfo": transferInfo,
  "packets": 20,
  "packetLength": 128
};

chrome.usb.isochronousTransfer(connectionHandle, isoTransferInfo, optionalCallback);

عمليات نقل بشكل مجمّع

تُستخدم عمليات النقل المجمّعة بشكل شائع لنقل كمية كبيرة من البيانات غير الحساسة للوقت بطريقة موثوقة. ويحتوي usb.bulkTransfer على ثلاث معلمات:

chrome.usb.bulkTransfer(connectionHandle, transferInfo, transferCallback);
المَعلمةالوصف
connectionHandleتم استلام الكائن في معاودة الاتصال usb.openDevice.
transferInfoكائن معلَمة يتضمن القيم في الجدول أدناه.
transferCallbackتم تفعيله عند اكتمال عملية النقل.

قيم العنصر transferInfo:

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

مثال:

var transferInfo = {
  "direction": "out",
  "endpoint": 1,
  "data": new Uint8Array([4, 8, 15, 16, 23, 42]).buffer
};

انقطاع عمليات النقل

تُستخدَم عمليات النقل المتقاطعة مع البيانات الحسّاسة للوقت. ونظرًا لأن كل اتصالات USB يتم إجراؤها من قِبل المضيف، عادةً ما يستطلع رمز المضيف الجهاز بشكل دوري، ويرسل هذا الرمز عمليات نقل المقاطعة التي تجعل الجهاز يعيد إرسال البيانات إذا كان هناك أي شيء في قائمة انتظار المقاطعة (يحتفظ به الجهاز). يشتمل الحقل usb.interruptTransfer على ثلاث معلمات:

chrome.usb.interruptTransfer(connectionHandle, transferInfo, transferCallback);
المَعلمةالوصف
connectionHandleتم استلام الكائن في معاودة الاتصال usb.openDevice.
transferInfoكائن معلَمة يتضمن القيم في الجدول أدناه.
transferCallbackتم تفعيله عند اكتمال عملية النقل. يُرجى العلم أنّ معاودة الاتصال هذه لا تتضمّن استجابة الجهاز. والغرض من معاودة الاتصال هو إعلام رمزك بأنه تمت معالجة طلبات النقل غير المتزامنة.

قيم العنصر transferInfo:

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

مثال:

var transferInfo = {
  "direction": "in",
  "endpoint": 1,
  "length": 2
};
chrome.usb.interruptTransfer(connectionHandle, transferInfo, optionalCallback);

المحاذير

لا يمكن الوصول إلى جميع الأجهزة عبر واجهة برمجة تطبيقات USB. بشكل عام، لا يمكن الوصول إلى الأجهزة لأن نواة نظام التشغيل أو برنامج التشغيل الأصلي تمنعها من كود مساحة المستخدم. ومن الأمثلة على ذلك الأجهزة التي تحتوي على ملفات شخصية HID على أنظمة التشغيل OSX، ومحركات أقراص USB.

في معظم أنظمة Linux، يتم ربط أجهزة USB بأذونات القراءة فقط تلقائيًا. لفتح جهاز من خلال واجهة برمجة التطبيقات هذه، سيحتاج المستخدم إلى أن يكون لديه حق الوصول للكتابة إليها أيضًا. الحل البسيط هو تعيين قاعدة udev. إنشاء ملف /etc/udev/rules.d/50-yourdevicename.rules بالمحتوى التالي:

SUBSYSTEM=="usb", ATTR{idVendor}=="[yourdevicevendor]", MODE="0664", GROUP="plugdev"

بعد ذلك، ما عليك سوى إعادة تشغيل البرنامج الخفي لـ udev: service udev restart. يمكنك التحقّق ممّا إذا تم ضبط أذونات الجهاز بشكل صحيح من خلال اتّباع الخطوات التالية:

  • شغِّل تطبيق "lsusb" للعثور على أرقام الحافلات والأجهزة.
  • تشغيل "ls -al /dev/bus/usb/[bus]/[device]" يجب أن يكون هذا الملف مملوكًا للمجموعة "plugdev" وأن يكون لديه أذونات كتابة المجموعة.

لا يمكن لتطبيقك تنفيذ ذلك تلقائيًا لأنّ هذا الإجراء يتطلب إمكانية الوصول إلى الجذر. نقترح عليك تقديم تعليمات للمستخدمين النهائيين وإدراج رابط إلى قسم التحذيرات في هذه الصفحة للحصول على شرح.

على نظام التشغيل ChromeOS، ما عليك سوى طلب usb.requestAccess. يجري وسيط الأذونات هذا الإجراء نيابةً عنك.