Mengakses Perangkat USB di Web

WebUSB API membuat USB lebih aman dan mudah digunakan dengan menghadirkannya ke Web.

François Beaufort
François Beaufort

Jika saya mengatakan "USB" dengan lugas dan sederhana, ada kemungkinan besar Anda akan langsung memikirkan {i>keyboard<i}, {i>mouse<i}, audio, video, dan perangkat penyimpanan. Anda benar, tetapi Anda akan menemukan jenis perangkat {i>Universal Serial Bus<i} (USB) lainnya di luar sana.

Perangkat USB yang tidak standar ini mengharuskan vendor hardware untuk menulis driver dan SDK khusus platform agar Anda (developer) dapat memanfaatkannya. Sayangnya, kode khusus platform ini secara historis mencegah perangkat ini digunakan oleh Web. Dan itulah salah satu alasan WebUSB API dibuat: untuk memberikan cara mengekspos layanan perangkat USB ke Web. Dengan API ini, produsen hardware akan dapat mem-build JavaScript SDK lintas platform untuk perangkat mereka.

Namun, yang paling penting, hal ini akan membuat USB lebih aman dan mudah digunakan dengan menghadirkannya ke Web.

Mari kita lihat perilaku yang dapat Anda harapkan dengan WebUSB API:

  1. Beli perangkat USB.
  2. Colokkan ke komputer. Notifikasi akan langsung muncul, dengan situs yang tepat dibuka untuk perangkat ini.
  3. Klik notifikasi tersebut. Situs webnya ada dan siap digunakan!
  4. Klik untuk menghubungkan dan pemilih perangkat USB akan muncul di Chrome tempat Anda dapat memilih perangkat.

Tada!

Seperti apa prosedur ini tanpa WebUSB API?

  1. Instal aplikasi khusus platform.
  2. Jika didukung di sistem operasi saya, pastikan saya telah mengunduh hal yang benar.
  3. Instal objek tersebut. Jika beruntung, Anda tidak akan mendapat perintah atau pop-up OS yang menyeramkan, yang memperingatkan Anda tentang menginstal driver/aplikasi dari internet. Jika Anda tidak beruntung, driver atau aplikasi yang terinstal tidak berfungsi dan membahayakan komputer Anda. (Ingat, web dibuat untuk berisi situs yang tidak berfungsi).
  4. Jika Anda hanya menggunakan fitur ini sekali, kode tetap berada di komputer sampai Anda berpikir untuk menghapusnya. (Di Web, ruang untuk yang tidak digunakan pada akhirnya diklaim kembali.)

Sebelum saya mulai

Artikel ini mengasumsikan bahwa Anda memiliki pengetahuan dasar tentang cara kerja USB. Jika tidak, sebaiknya baca USB di NutShell. Untuk informasi latar belakang tentang USB, lihat spesifikasi USB resmi.

WebUSB API tersedia di Chrome 61.

Tersedia untuk uji coba origin

Untuk mendapatkan masukan sebanyak mungkin dari developer yang menggunakan WebUSB API di lapangan, sebelumnya kami telah menambahkan fitur ini di Chrome 54 dan Chrome 57 sebagai uji coba origin.

Uji coba terbaru telah berhasil berakhir pada September 2017.

Privasi dan keamanan

Khusus HTTPS

Karena kecanggihan fitur ini, fitur ini hanya berfungsi pada konteks yang aman. Artinya, Anda harus membuat aplikasi dengan mempertimbangkan TLS.

Gestur pengguna diperlukan

Sebagai tindakan pengamanan, navigator.usb.requestDevice() hanya dapat dipanggil melalui gestur pengguna seperti sentuhan atau klik mouse.

Kebijakan Izin

Kebijakan Izin adalah mekanisme yang memungkinkan developer untuk mengaktifkan dan menonaktifkan berbagai fitur browser dan API secara selektif. Ini dapat ditentukan melalui header HTTP dan/atau atribut "allow" iframe.

Anda dapat menentukan Kebijakan Izin yang mengontrol apakah atribut usb ditampilkan pada objek Navigator, atau dengan kata lain jika Anda mengizinkan WebUSB.

Berikut adalah contoh kebijakan header yang tidak mengizinkan WebUSB:

Feature-Policy: fullscreen "*"; usb "none"; payment "self" https://payment.example.com

Berikut adalah contoh lain dari kebijakan container yang mengizinkan USB:

<iframe allowpaymentrequest allow="usb; fullscreen"></iframe>

Mari kita mulai coding

WebUSB API sangat bergantung pada Promise JavaScript. Jika Anda tidak terbiasa dengannya, lihat tutorial Promise yang sangat bermanfaat ini. Satu hal lagi, () => {} hanyalah fungsi Panah ECMAScript 2015.

Mendapatkan akses ke perangkat USB

Anda dapat meminta pengguna memilih satu perangkat USB yang terhubung menggunakan navigator.usb.requestDevice() atau memanggil navigator.usb.getDevices() untuk mendapatkan daftar semua perangkat USB yang terhubung yang dapat diakses oleh situs.

Fungsi navigator.usb.requestDevice() mengambil objek JavaScript wajib yang menentukan filters. Filter ini digunakan untuk mencocokkan perangkat USB apa pun dengan vendor (vendorId) dan, secara opsional, ID produk (productId). Kunci classCode, protocolCode, serialNumber, dan subclassCode juga dapat ditentukan di sana.

Screenshot perintah pengguna perangkat USB di Chrome
Perintah pengguna perangkat USB.

Misalnya, berikut adalah cara mendapatkan akses ke perangkat Arduino terhubung yang dikonfigurasi untuk mengizinkan asal.

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

Sebelumnya, saya tidak memberikan angka heksadesimal 0x2341 ini. Saya baru saja menelusuri kata "Arduino" di Daftar ID USB ini.

device USB yang ditampilkan dalam promise yang terpenuhi di atas memiliki beberapa informasi dasar yang penting tentang perangkat seperti versi USB yang didukung, ukuran paket maksimum, vendor, dan ID produk, jumlah kemungkinan konfigurasi yang dapat dimiliki perangkat. Pada dasarnya, file ini berisi semua kolom dalam Deskripsi USB perangkat.

// 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"
  });
})

Ngomong-ngomong, jika perangkat USB mengumumkan dukungannya untuk WebUSB, serta menentukan URL halaman landing, Chrome akan menampilkan notifikasi persisten saat perangkat USB dicolokkan. Mengklik notifikasi ini akan membuka halaman landing.

Screenshot notifikasi WebUSB di Chrome
Notifikasi WebUSB.

Berbicara dengan board USB Arduino

Oke, sekarang mari kita lihat betapa mudahnya berkomunikasi dari board Arduino yang kompatibel dengan WebUSB melalui porta USB. Lihat petunjuk di https://github.com/webusb/arduino untuk mengaktifkan sketsa Anda dengan WebUSB.

Jangan khawatir, saya akan membahas semua metode perangkat WebUSB yang disebutkan di bawah ini nanti dalam artikel ini.

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

Perlu diingat bahwa library WebUSB yang saya gunakan hanya menerapkan satu protokol contoh (berdasarkan protokol serial USB standar) dan produsen dapat membuat kumpulan dan jenis endpoint apa pun yang mereka inginkan. Transfer kontrol sangat cocok untuk perintah konfigurasi kecil karena mendapatkan prioritas bus dan memiliki struktur yang jelas.

Dan inilah sketsa yang telah diunggah ke board 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.
}

Library Arduino WebUSB pihak ketiga yang digunakan dalam kode contoh di atas pada dasarnya melakukan dua hal:

  • Perangkat ini berfungsi sebagai perangkat WebUSB yang memungkinkan Chrome membaca URL halaman landing.
  • File ini mengekspos WebUSB Serial API yang dapat Anda gunakan untuk mengganti API default.

Lihat kode JavaScript lagi. Setelah device dipilih oleh pengguna, device.open() akan menjalankan semua langkah khusus platform untuk memulai sesi dengan perangkat USB. Kemudian, saya harus memilih Konfigurasi USB yang tersedia dengan device.selectConfiguration(). Ingat bahwa konfigurasi menentukan cara perangkat diberi daya, konsumsi daya maksimum, dan jumlah antarmuka. Terkait antarmuka, saya juga perlu meminta akses eksklusif dengan device.claimInterface() karena data hanya dapat ditransfer ke antarmuka atau endpoint terkait saat antarmuka diklaim. Terakhir, pemanggilan device.controlTransferOut() diperlukan untuk menyiapkan perangkat Arduino dengan perintah yang sesuai untuk berkomunikasi melalui WebUSB Serial API.

Dari sana, device.transferIn() akan melakukan transfer massal ke perangkat untuk memberi tahunya bahwa host siap menerima data massal. Kemudian, promise dipenuhi dengan objek result yang berisi data DataView yang harus diurai dengan tepat.

Jika Anda akrab dengan USB, semua ini seharusnya terlihat cukup familier.

Saya ingin lebih banyak

WebUSB API memungkinkan Anda berinteraksi dengan semua jenis transfer/endpoint USB:

  • Transfer CONTROL, yang digunakan untuk mengirim atau menerima parameter konfigurasi atau perintah ke perangkat USB, ditangani dengan controlTransferIn(setup, length) dan controlTransferOut(setup, data).
  • Transfer INTERRUPT, yang digunakan untuk data sensitif dalam jumlah kecil, ditangani dengan metode yang sama seperti transfer BULK dengan transferIn(endpointNumber, length) dan transferOut(endpointNumber, data).
  • Transfer ISOCHRONOUS, yang digunakan untuk streaming data seperti video dan suara, ditangani dengan isochronousTransferIn(endpointNumber, packetLengths) dan isochronousTransferOut(endpointNumber, data, packetLengths).
  • Transfer BULK, yang digunakan untuk mentransfer data dalam jumlah besar yang tidak mendesak dengan cara yang andal, ditangani dengan transferIn(endpointNumber, length) dan transferOut(endpointNumber, data).

Sebaiknya Anda juga melihat project WebLight Mike Tsao yang memberikan contoh dari bawah ke atas tentang cara membangun perangkat LED yang dikontrol USB dan dirancang untuk WebUSB API (di sini tidak menggunakan Arduino). Anda akan menemukan perangkat keras, perangkat lunak, dan {i>firmware<i}.

Mencabut akses ke perangkat USB

Situs dapat menghapus izin untuk mengakses perangkat USB yang tidak lagi diperlukannya dengan memanggil forget() pada instance USBDevice. Misalnya, untuk aplikasi web pendidikan yang digunakan di komputer bersama dengan banyak perangkat, akumulasi izin yang dibuat pengguna dalam jumlah besar akan menyebabkan pengalaman pengguna yang buruk.

// Voluntarily revoke access to this USB device.
await device.forget();

Karena forget() tersedia di Chrome 101 atau yang lebih baru, periksa apakah fitur ini didukung dengan hal berikut:

if ("usb" in navigator && "forget" in USBDevice.prototype) {
  // forget() is supported.
}

Batas ukuran transfer

Beberapa sistem operasi memberlakukan batas pada jumlah data yang dapat menjadi bagian dari transaksi USB yang tertunda. Membagi data menjadi transaksi yang lebih kecil dan hanya mengirimkannya sedikit dalam satu waktu akan membantu menghindari batasan tersebut. Hal ini juga mengurangi jumlah memori yang digunakan dan memungkinkan aplikasi Anda melaporkan progres saat transfer selesai.

Karena beberapa transfer yang dikirim ke endpoint selalu dijalankan secara berurutan, throughput dapat ditingkatkan dengan mengirimkan beberapa potongan dalam antrean untuk menghindari latensi antartransfer USB. Setiap kali dikirimkan sepenuhnya, potongan akan memberi tahu kode Anda bahwa potongan tersebut harus menyediakan lebih banyak data seperti yang didokumentasikan dalam contoh fungsi helper di bawah ini.

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

Tips

Proses debug USB di Chrome lebih mudah dengan halaman internal about://device-log tempat Anda dapat melihat semua peristiwa terkait perangkat USB di satu tempat.

Screenshot halaman log perangkat untuk men-debug WebUSB di Chrome
Halaman log perangkat di Chrome untuk men-debug WebUSB API.

Halaman internal about://usb-internals juga berguna dan memungkinkan Anda menyimulasikan koneksi dan pemutusan koneksi perangkat WebUSB virtual. Hal ini berguna untuk melakukan pengujian UI tanpa perangkat keras sungguhan.

Screenshot halaman internal untuk men-debug WebUSB di Chrome
Halaman internal di Chrome untuk men-debug WebUSB API.

Pada sebagian besar sistem Linux, perangkat USB dipetakan dengan izin hanya baca secara default. Agar Chrome dapat membuka perangkat USB, Anda perlu menambahkan aturan udev baru. Buat file di /etc/udev/rules.d/50-yourdevicename.rules dengan konten berikut:

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

dengan [yourdevicevendor] adalah 2341 jika perangkat Anda adalah Arduino. ATTR{idProduct} juga dapat ditambahkan untuk aturan yang lebih spesifik. Pastikan user Anda adalah anggota grup plugdev. Lalu, cukup sambungkan kembali perangkat Anda.

Referensi

Kirim tweet ke @ChromiumDev menggunakan hashtag #WebUSB dan beri tahu kami di mana dan bagaimana Anda menggunakannya.

Ucapan terima kasih

Terima kasih kepada Joe Medley telah meninjau artikel ini.