התקני USB

מסמך זה מתאר איך להשתמש ב-USB API כדי לתקשר עם התקני USB. יש מכשירים שאי אפשר לגשת אליהם דרך USB API (פרטים נוספים מופיעים בקטע אזהרות שבהמשך). אפליקציות Chrome יכולות גם להתחבר למכשירים serial ו-Bluetooth.

למידע רקע על USB, ניתן לעיין במפרט הרשמי של USB. USB ב-NutShell הוא קורס קריסה סביר שיכול להועיל לך.

דרישה למניפסט

ל-USB API נדרשת ההרשאה 'usb' בקובץ המניפסט:

"permissions": [
  "usb"
]

בנוסף, כדי למנוע הדפסה של טביעות אצבע, צריך להצהיר על כל סוגי המכשירים שרוצים לגשת אליהם בקובץ המניפסט. כל סוג של התקן USB מתאים לצמד של מזהה ספק/מזהה מוצר (VID/PID). אפשר להשתמש בפונקציה usb.getDevices כדי לספור מכשירים לפי צמד VID/PID שלהם.

עליכם להצהיר על צמדי VID/PID לכל סוג מכשיר שבו אתם רוצים להשתמש, במסגרת ההרשאה usbDevices, בקובץ המניפסט של האפליקציה, כפי שמוצג בדוגמה הבאה:

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

החל מ-Chrome 57, הדרישה להצהרה על כל סוגי המכשירים בקובץ המניפסט של האפליקציה לא תקפה לאפליקציות שפועלות כאפליקציות קיוסק ב-ChromeOS. לאפליקציות "קיוסק", אפשר להשתמש במאפיין ההרשאה 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 רק באמצעות נקודות אחיזה לחיבור.

נכסהתיאור
deviceהאובייקט התקבל בקריאה חוזרת (callback) usb.getDevices.
נתונים (arraybuffer)מכילה את הנתונים שנשלחו על ידי המכשיר אם ההעברה נכנסת.

דוגמה:

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 ונשלחת באופן אסינכרוני אל הקריאה החוזרת (callback) שציינתם בשיטת ההעברה. הודעה יוצאת (ממארח למכשיר) דומה, אבל התגובה לא מכילה נתונים שהוחזרו מהמכשיר.

לכל הודעה מהמכשיר, הקריאה החוזרת שצוינה תקבל אובייקט אירוע עם המאפיינים הבאים:

נכסהתיאור
resultCode (מספר שלם)הערך 0 הוא הצלחה; ערכים אחרים מציינים כישלון. ניתן
לקרוא מחרוזת שגיאה מ-chrome.extension.lastError אם מצוין
כשל.
נתונים (arraybuffer)מכילה את הנתונים שנשלחו על ידי המכשיר אם ההעברה נכנסת.

דוגמה:

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

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

העברות של Control

בדרך כלל, העברות בקרה משמשות לשליחה או לקבלה של פרמטרים של הגדרות או של פקודות להתקן USB. שיטת ControlTransfer תמיד שולחת או קוראת מנקודת הקצה 0, ולא צריך להשתמש ב-ClaimInterface. השיטה פשוטה וכוללת שלושה פרמטרים:

chrome.usb.controlTransfer(connectionHandle, transferInfo, transferCallback)
פרמטר (סוגים)התיאור
connectionHandleהאובייקט התקבל בקריאה חוזרת (callback) של usb.openDevice.
transferInfoאובייקט פרמטר עם ערכים מהטבלה שלמטה. לפרטים, יש לעיין במפרט הפרוטוקול של התקן ה-USB.
transferCallback()מופעלת כשההעברה הושלמה.

ערכים עבור אובייקט transferInfo:

Valueהתיאור
requestType (מחרוזת)"vendor", "standard", "class" או "reserve".
נמען (מחרוזת)'device', 'interface', 'endpoint' או 'אחר'.
כיוון (מחרוזת)'ב' או 'החוצה'. ההנחיה 'כניסה' משמשת כדי לשלוח למכשיר התראה
שעליו לשלוח מידע למארח. כל התקשורת באוטובוס
USB מתבצעת ביוזמת המארח, לכן יש להשתמש בהעברה בנקאית (in) כדי לאפשר למכשיר
לשלוח מידע בחזרה.
request (מספר שלם)מוגדר על ידי הפרוטוקול של המכשיר שלך.
ערך (מספר שלם)מוגדר על ידי הפרוטוקול של המכשיר שלך.
אינדקס (מספר שלם)מוגדר על ידי הפרוטוקול של המכשיר שלך.
אורך (מספר שלם)בשימוש רק כשהכיוון הוא 'ב-'. הצגת התראה למכשיר שזו כמות הנתונים שהמארח מצפה לקבל בתגובה.
נתונים (arraybuffer)מוגדר על ידי הפרוטוקול של המכשיר שלך, נדרש כשהכיוון הוא 'out'.

דוגמה:

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);

העברות באמצעות ISOCHRONOUS

העברות ישו-כרוניות הן הסוג המורכב ביותר של העברת USB. בדרך כלל הם משמשים לסטרימינג של נתונים, כמו וידאו וקול. כדי להתחיל העברה איזוכרונית (נכנסת או יוצאת), צריך להשתמש בשיטה usb.isochronousTransfer:

chrome.usb.isochronousTransfer(connectionHandle, isochronousTransferInfo, transferCallback)
פרמטרהתיאור
connectionHandleהאובייקט התקבל בקריאה חוזרת (callback) של usb.openDevice.
isochronousTransferInfoאובייקט פרמטר עם הערכים בטבלה שלמטה.
transferCallback()מופעלת כשההעברה הושלמה.

ערכים עבור אובייקט isochronousTransferInfo:

Valueהתיאור
TransferInfo (אובייקט)אובייקט עם המאפיינים הבאים:
כיוון (מחרוזת): בפנים או בחוץ.
נקודת קצה (מספר שלם): מוגדר על ידי המכשיר. בדרך כלל אפשר לאתר את המכשיר באמצעות כלי לבדיקת USB, כמו lsusb -v
אורך (מספר שלם): בשימוש רק כשהכיוון הוא 'in'. התראה למכשיר שזו כמות הנתונים שהמארח מצפה לקבל בתגובה.
הגודל צריך להיות לפחות packets × packetLength.
נתונים (arraybuffer): מוגדר על ידי הפרוטוקול של המכשיר, בשימוש רק כשהכיוון הוא 'out'.
חבילות (מספר שלם)המספר הכולל של החבילות הצפויות בהעברה הזו.
אורך המנה (מספר שלם)האורך הצפוי של כל חבילה בהעברה הזו.

דוגמה:

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האובייקט התקבל בקריאה חוזרת (callback) של usb.openDevice.
transferInfoאובייקט פרמטר עם הערכים בטבלה שלמטה.
transferCallbackמופעלת כשההעברה הושלמה.

ערכים עבור אובייקט transferInfo:

Valueהתיאור
כיוון (מחרוזת)'ב' או 'החוצה'.
נקודת קצה (מספר שלם)מוגדר על ידי הפרוטוקול של המכשיר שלך.
אורך (מספר שלם)בשימוש רק כשהכיוון הוא 'ב-'. הצגת התראה למכשיר שזו כמות הנתונים שהמארח מצפה לקבל בתגובה.
נתונים (ArrayBuffer)מוגדר על ידי הפרוטוקול של המכשיר שלך, בשימוש רק כאשר הכיוון הוא 'out'.

דוגמה:

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האובייקט התקבל בקריאה חוזרת (callback) של usb.openDevice.
transferInfoאובייקט פרמטר עם הערכים בטבלה שלמטה.
transferCallbackמופעלת כשההעברה הושלמה. לתשומת ליבך, הקריאה החוזרת הזו לא כוללת את תגובת המכשיר. מטרת הקריאה החוזרת היא פשוט ליידע את הקוד שלך שבקשות ההעברה האסינכרוניות עובדו.

ערכים עבור אובייקט transferInfo:

Valueהתיאור
כיוון (מחרוזת)'ב' או 'החוצה'.
נקודת קצה (מספר שלם)מוגדר על ידי הפרוטוקול של המכשיר שלך.
אורך (מספר שלם)בשימוש רק כשהכיוון הוא 'ב-'. הצגת התראה למכשיר שזו כמות הנתונים שהמארח מצפה לקבל בתגובה.
נתונים (ArrayBuffer)מוגדר על ידי הפרוטוקול של המכשיר שלך, בשימוש רק כאשר הכיוון הוא 'out'.

דוגמה:

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

נקודות שצריך לשים לב אליהן:

לא ניתן לגשת לכל המכשירים באמצעות ממשק ה-API של USB. באופן כללי, אי אפשר לגשת למכשירים כי ליבת מערכת ההפעלה או מנהל התקן מקומי מונעים אותם מקוד המרחב של המשתמש. חלק מהדוגמאות הן מכשירים עם פרופיל HID במערכות OSX וכונני עט בחיבור USB.

ברוב מערכות Linux, התקני USB ממופים עם הרשאות לקריאה בלבד כברירת מחדל. כדי לפתוח מכשיר באמצעות ה-API הזה, צריכה להיות לו גם הרשאת כתיבה. פתרון פשוט הוא להגדיר כלל udev. יוצרים קובץ /etc/udev/rules.d/50-yourdevicename.rules עם התוכן הבא:

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

לאחר מכן, פשוט מפעילים מחדש את ה-udev daemon: service udev restart. כדי לבדוק אם הרשאות המכשיר מוגדרות כראוי:

  • מריצים את lsusb כדי למצוא את מספרי האוטובוס והמכשירים.
  • מריצים את ls -al /dev/bus/usb/[bus]/[device]. הקובץ הזה צריך להיות בבעלות הקבוצה "Plugdev" ולהיות בעל הרשאות כתיבה קבוצתיות.

האפליקציה לא יכולה לעשות זאת באופן אוטומטי כי כדי לבצע את התהליך הזה נדרשת גישה לרמה הבסיסית. אנחנו ממליצים לספק הוראות למשתמשי קצה ולקשר לקטע אזהרות בדף הזה כדי לקבל הסבר.

ב-ChromeOS, פשוט קוראים ל-usb.requestAccess. מתווך ההרשאות עושה זאת עבורכם.