Web'deki USB Cihazlara Erişme

WebUSB API, USB'yi web'e getirerek daha güvenli ve kullanımı kolay hale getirir.

François Beaufort
François Beaufort

Açık ve basit bir şekilde "USB" dediysem aklınıza klavye, fare, ses, video ve depolama cihazları aklınıza pek çok şey gelebilir. Doğru yanıt verdiniz ancak başka türlerde Universal Serial Bus (USB) cihazları da bulabilirsiniz.

Bu standart olmayan USB cihazlar, donanım tedarikçilerinin (geliştirici) bunlardan yararlanabilmeniz için platforma özel sürücüler ve SDK'lar yazmasını gerektirir. Ne yazık ki bu platforma özel kod, geçmişte bu cihazların web tarafından kullanılmasını engelledi. Bu yüzden, WebUSB API'nin oluşturulma nedenlerinden biri de budur: USB cihaz hizmetlerini web'de açığa çıkarmanın bir yolunu sağlamak. Donanım üreticileri, bu API'yi kullanarak cihazları için platformlar arası JavaScript SDK'ları oluşturabilir.

Ancak en önemlisi bu, USB'yi web'e taşıyarak daha güvenli ve kullanımı kolay hale getirecek.

WebUSB API'de bekleyebileceğiniz davranışları görelim:

  1. Bir USB cihaz satın alın.
  2. Bilgisayarınıza takın. Hemen, bu cihaz için gidilecek doğru web sitesinin olduğu bir bildirim görünür.
  3. Bildirimi tıklayın. Web sitesi hazır ve kullanıma hazır.
  4. Bağlanmak için tıkladığınızda Chrome'da cihazınızı seçebileceğiniz bir USB cihaz seçici görünür.

İşte oldu!

WebUSB API olmadan bu prosedür nasıl olur?

  1. Platforma özel bir uygulama yükleyin.
  2. Uygulama, işletim sistemimde de destekleniyorsa, doğru dosyayı indirdiğimden emin olun.
  3. Ürünü yükleyin. Şanslıysanız, internetten sürücüleri/uygulamaları yüklemenizle ilgili korkutucu işletim sistemi istemleri veya pop-up'ları almazsınız. Şanssızsanız, yüklü sürücüler veya uygulamalar hatalı çalışır ve bilgisayarınıza zarar verir. (Web'in hatalı web sitelerini içerecek şekilde tasarlandığını unutmayın).
  4. Özelliği sadece bir kez kullanırsanız siz kaldırmayı düşünene kadar kod bilgisayarınızda kalır. (Web'de kullanılmayan alanlar sonunda geri verilir.)

Başlamadan önce

Bu makalede, USB'nin nasıl çalıştığıyla ilgili bazı temel bilgilere sahip olduğunuz varsayılmaktadır. Aksi takdirde, NutShell'de USB okumasını öneririz. USB ile ilgili arka plan bilgileri için resmi USB spesifikasyonlarına göz atın.

WebUSB API, Chrome 61'de mevcuttur.

Kaynak denemelerinde kullanılabilir

Sahada WebUSB API'yi kullanan geliştiricilerden mümkün olduğunca fazla geri bildirim almak için bu özelliği daha önce Chrome 54 ve Chrome 57 sürümlerine kaynak denemesi olarak eklemiştik.

Son deneme süresi Eylül 2017'de başarıyla sona erdi.

Gizlilik ve güvenlik

Yalnızca HTTPS

Bu özellik, gücü nedeniyle yalnızca güvenli bağlamlarda çalışır. Bu nedenle, TLS'yi göz önünde bulundurmanız gerekir.

Kullanıcı hareketi gerekli

navigator.usb.requestDevice(), güvenlik önlemi olarak yalnızca dokunma veya fare tıklaması gibi bir kullanıcı hareketiyle çağrılabilir.

İzin Politikası

İzin Politikası, geliştiricilerin çeşitli tarayıcı özelliklerini ve API'lerini seçerek etkinleştirmelerine ve devre dışı bırakmalarına olanak tanıyan bir mekanizmadır. HTTP üst bilgisi ve/veya iframe "allow" özelliği aracılığıyla tanımlanabilir.

usb özelliğinin Navigator nesnesinde gösterilip gösterilmeyeceğini veya WebUSB'ye izin verip vermeyeceğinizi kontrol eden bir İzin Politikası tanımlayabilirsiniz.

Aşağıda, WebUSB'ye izin verilmeyen bir üstbilgi politikası örneği verilmiştir:

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

Aşağıda, USB'ye izin verilen bir kapsayıcı politikası örneği verilmiştir:

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

Kodlamaya başlayalım

WebUSB API, büyük ölçüde JavaScript Promises'i kullanır. Bunlara aşina değilseniz bu başarılı Promises eğiticisine göz atın. Bir diğer bilgimiz ise () => {}, ECMAScript 2015 Ok işlevleridir.

USB cihazlarına erişim elde etme

Kullanıcının navigator.usb.requestDevice() kullanarak tek bir bağlı USB cihazı seçmesini isteyebilir veya web sitesine erişim izni verilen tüm bağlı USB cihazlarının listesini almak için navigator.usb.getDevices() numaralı telefonu arayabilirsiniz.

navigator.usb.requestDevice() işlevi, filters öğesini tanımlayan zorunlu bir JavaScript nesnesi alır. Bu filtreler, herhangi bir USB cihazı belirtilen satıcı (vendorId) ve isteğe bağlı olarak ürün (productId) tanımlayıcılarıyla eşleştirmek için kullanılır. Burada classCode, protocolCode, serialNumber ve subclassCode anahtarları da tanımlanabilir.

Chrome&#39;daki USB cihazı kullanıcısı isteminin ekran görüntüsü
USB cihazı kullanıcı istemi.

Örneğin, kaynağa izin vermek üzere yapılandırılmış bağlı bir Arduino cihazına nasıl erişileceği aşağıda açıklanmaktadır.

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

Sormadan önce, bu 0x2341 onaltılık sayıyı sihirli bir şekilde tahmin etmedim. Bu USB kimlikleri listesinde "Arduino" kelimesini aradım.

Yukarıdaki vaatte verilen USB device, desteklenen USB sürümü, maksimum paket boyutu, satıcı ve ürün kimlikleri, cihazın sahip olabileceği yapılandırma sayısı gibi cihazla ilgili bazı temel ancak önemli bilgiler içerir. Temel olarak, cihaz USB Açıklayıcı'daki tüm alanları içerir.

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

Bu arada, bir USB cihazı bir açılış sayfası URL'si tanımlamanın yanı sıra WebUSB desteğini de belirtirse Chrome, USB cihaz takılıyken kalıcı bir bildirim gösterir. Bu bildirimi tıkladığınızda açılış sayfası açılır.

Chrome&#39;daki WebUSB bildiriminin ekran görüntüsü
WebUSB bildirimi.

Arduino USB kartıyla konuşma

Tamam, şimdi WebUSB uyumlu bir Arduino kartından USB bağlantı noktası üzerinden iletişim kurmanın ne kadar kolay olduğuna bakalım. Çizimlerinizi WebUSB'de etkinleştirmek için https://github.com/webusb/arduino adresindeki talimatlara göz atın.

Endişelenmeyin, aşağıdaki WebUSB cihaz yöntemlerinin tümünü bu makalenin ilerleyen kısımlarında ele alacağız.

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

Kullandığım WebUSB kitaplığının sadece bir örnek protokol uyguladığını (standart USB seri protokolüne dayanarak) ve üreticilerin diledikleri her türlü uç nokta kümesi oluşturabileceğini unutmayın. Kontrol aktarımları, özellikle veri yolu önceliğine sahip oldukları ve iyi tanımlanmış bir yapıya sahip oldukları için küçük yapılandırma komutları için kullanışlıdır.

Burada da Arduino panosuna yüklenen çizimi görüyorsunuz.

// 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.
}

Yukarıdaki örnek kodda kullanılan üçüncü taraf WebUSB Arduino kitaplığı temel olarak iki şey yapar:

  • Cihaz, Chrome'un açılış sayfası URL'sini okumasını sağlayan bir WebUSB cihazı görevi görür.
  • Varsayılan API'yi geçersiz kılmak için kullanabileceğiniz bir WebUSB Serial API'yi gösterir.

JavaScript kodunu tekrar inceleyin. Kullanıcı tarafından device seçildikten sonra device.open(), USB cihazıyla oturum başlatmak için platforma özel tüm adımları uygular. Ardından, device.selectConfiguration() ile kullanılabilir bir USB Yapılandırması seçmem gerekiyor. Yapılandırmanın cihazın nasıl çalıştırıldığını, maksimum güç tüketimini ve arayüz sayısını belirttiğini unutmayın. Arayüzlerden bahsetmişken, veriler yalnızca arayüz üzerinde hak talebinde bulunulduğunda bir arayüze veya ilişkili uç noktalara aktarılabileceği için device.claimInterface() ile özel erişim isteğinde de bulunmam gerekiyor. Son olarak, Arduino cihazı WebUSB Serial API üzerinden iletişim kurmak amacıyla uygun komutlarla kurmak için device.controlTransferOut() çağrısı yapılması gerekir.

device.transferIn(), buradan ana makinenin toplu veri almaya hazır olduğunu bildirmek için cihaza toplu bir aktarım gerçekleştirir. Ardından söz, uygun şekilde ayrıştırılması gereken bir DataView data içeren result nesnesiyle yerine getirilir.

USB hakkında bilginiz varsa bunların tümü size tanıdık gelecektir.

Daha fazlasını istiyorum

WebUSB API, tüm USB aktarımı/uç noktası türleriyle etkileşim kurmanıza olanak tanır:

  • Bir USB cihazına yapılandırma veya komut parametreleri göndermek ya da almak için kullanılan CONTROL aktarımları, controlTransferIn(setup, length) ve controlTransferOut(setup, data) ile gerçekleştirilir.
  • Az miktarda zaman açısından hassas veriler için kullanılan INTERRUPT aktarımları, transferIn(endpointNumber, length) ve transferOut(endpointNumber, data) ile BULK aktarımlarıyla aynı yöntemlerle işlenir.
  • Video ve ses gibi veri akışları için kullanılan ISOCHRONOUS aktarımları, isochronousTransferIn(endpointNumber, packetLengths) ve isochronousTransferOut(endpointNumber, data, packetLengths) ile işlenir.
  • Zaman açısından hassas olmayan çok miktarda veriyi güvenilir bir şekilde aktarmak için kullanılan BULK aktarımları, transferIn(endpointNumber, length) ve transferOut(endpointNumber, data) ile gerçekleştirilir.

Ayrıca, Mike Tsao'nun WebLight projesine de göz atabilirsiniz. Bu proje, WebUSB API için tasarlanmış, USB kontrollü bir LED cihaz (burada Arduino kullanılarak değil) oluşturmaya yönelik başarılı bir örnek sunar. Donanım, yazılım ve donanım yazılımı bulacaksınız.

USB cihazına erişimi iptal etme

Web sitesi, USBDevice örneğinde forget() yöntemini çağırarak artık ihtiyaç duymadığı bir USB cihaza erişim izinlerini temizleyebilir. Örneğin, çok sayıda cihazın bulunduğu paylaşılan bir bilgisayarda kullanılan eğitici bir web uygulaması söz konusu olduğunda, kullanıcı tarafından oluşturulan izinlerin çok sayıda olması kullanıcı deneyimini olumsuz etkiler.

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

forget(), Chrome 101 veya sonraki sürümlerde kullanılabildiğinden, bu özelliğin aşağıdakilerle desteklenip desteklenmediğini kontrol edin:

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

Aktarım boyutuyla ilgili sınırlar

Bazı işletim sistemleri, bekleyen USB işlemlerinin parçası olabilecek veri miktarına sınırlar uygular. Verilerinizi daha küçük işlemlere bölmek ve tek seferde yalnızca birkaç işlem göndermek bu sınırlamaları önlemeye yardımcı olur. Ayrıca, kullanılan bellek miktarını azaltır ve uygulamanızın, aktarımlar tamamlandıkça ilerlemeyi bildirmesine olanak tanır.

Bir uç noktaya gönderilen birden fazla aktarım her zaman sırayla yürütüldüğünden, USB aktarımları arasındaki gecikmeyi önlemek için sıraya alınmış birden fazla parça göndererek işleme hızını iyileştirmek mümkündür. Bir parçanın tam olarak iletilmesinde, kodunuza aşağıdaki yardımcı işlev örneğinde açıklandığı gibi daha fazla veri sağlaması gerektiği bildirilir.

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

İpuçları

USB cihazlarıyla ilgili tüm etkinlikleri tek bir yerde görebileceğiniz dahili sayfa about://device-log ile Chrome'da USB hataları ayıklamak daha kolaydır.

Chrome&#39;da WebUSB hata ayıklamasına ilişkin cihaz günlüğü sayfasının ekran görüntüsü
WebUSB API'de hata ayıklamak için Chrome'daki cihaz günlüğü sayfası.

about://usb-internals dahili sayfası da kullanışlıdır ve sanal WebUSB cihazlarının bağlantısını ve bağlantısını simüle etmenizi sağlar. Bu, gerçek donanım olmadan kullanıcı arayüzü testleri yapmak için yararlıdır.

Chrome&#39;da WebUSB hata ayıklamasına ilişkin dahili sayfanın ekran görüntüsü
Chrome'da WebUSB API hatalarını ayıklamak için kullanılan dahili sayfa.

Çoğu Linux sisteminde USB cihazları varsayılan olarak salt okuma izinleriyle eşlenir. Chrome'un bir USB cihazı açmasına izin vermek için yeni bir udev kuralı eklemeniz gerekir. /etc/udev/rules.d/50-yourdevicename.rules adresinde aşağıdaki içeriğe sahip bir dosya oluşturun:

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

Bu durumda, örneğin cihazınız bir Arduino ise [yourdevicevendor] değeri 2341 olur. ATTR{idProduct} daha spesifik bir kural için de eklenebilir. user hesabınızın plugdev grubunun üyesi olduğundan emin olun. Ardından cihazınızı yeniden bağlamanız yeterlidir.

Kaynaklar

#WebUSB hashtag'ini kullanarak @ChromiumDev adresine tweet gönderip bu tweet'i nerede ve nasıl kullandığınızı bize bildirin.

Teşekkür

Bu makaleyi incelediği için Joe Medley'ye teşekkür ederiz.