Web'deki USB Cihazlara Erişme

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

François Beaufort
François Beaufort

"USB" dediğimde aklınıza hemen klavye, fare, ses, video ve depolama cihazları gelebilir. Haklısın, ama başka tür Universal Serial Bus (USB) cihazları da vardır.

Standart olmayan bu USB cihazlardan yararlanabilmeniz için (geliştirici) donanım tedarikçilerinin platforma özel sürücüler ve SDK'lar yazması gerekir. Maalesef bu platforma özel kod, geçmişte bu cihazların web tarafından kullanılmasını engelliyordu. WebUSB API'nin oluşturulma nedenlerinden biri de USB cihaz hizmetlerini Web'e göstermenin bir yolunu sağlamaktır. Donanım üreticileri bu API ile cihazları için platformlar arası JavaScript SDK'ları oluşturabilir.

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

WebUSB API'de karşılaşabileceğiniz davranışı inceleyelim:

  1. Bir USB cihazı satın alın.
  2. Bilgisayarınıza takın. Bu cihaz için gidilecek doğru web sitesinin bulunduğu bir bildirim hemen gösterilir.
  3. Bildirimi tıklayın. Web sitesi kullanıma hazır.
  4. Bağlantı simgesini tıkladığınızda Chrome'da cihazınızı seçebileceğiniz bir USB cihaz seçici gösterilir.

İşte bu kadar.

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

  1. Platforma özel bir uygulama yükleyin.
  2. İşletim sistemimden destekleniyor olsa bile doğru sürümü indirdiğimden emin olmalıyım.
  3. Cihazı kurun. Şanslıysanız sizi internetten sürücü/uygulama yükleme konusunda uyaran korkutucu işletim sistemi istemleri veya pop-up'lar görmezsiniz. Şanssızsanız yüklü sürücüler veya uygulamalar arızalanır ve bilgisayarınıza zarar verir. (Web'in işlevsel olmayan web sitelerini barındıracak şekilde tasarlandığını unutmayın.)
  4. Özelliği yalnızca bir kez kullanırsanız kodu siz kaldırmayı düşünene kadar bilgisayarınızda kalır. (Web'de, kullanılmayan alan sonunda yeniden alınır.)

Başlamadan önce

Bu makalede, USB'nin işleyiş şekliyle ilgili temel düzeyde bilgi sahibi olduğunuz varsayılmaktadır. Aksi takdirde USB in a NutShell (USB'yi Anlama Kılavuzu) başlıklı makaleyi okumanızı öneririz. USB hakkında arka plan bilgisi için resmi USB özelliklerine bakın.

WebUSB API, Chrome 61'de kullanılabilir.

Kaynak denemelerinde kullanılabilir

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

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

Gizlilik ve güvenlik

Yalnızca HTTPS

Bu özelliğin gücü nedeniyle yalnızca güvenli bağlamlarda çalışır. Bu nedenle, derleme işlemini TLS temelinde yapmanız gerekir.

Kullanıcı hareketi gerekiyor

Güvenlik önlemi olarak navigator.usb.requestDevice() 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ştirmesine ve devre dışı bırakmasına olanak tanıyan bir mekanizmadır. Bir HTTP başlığı ve/veya iframe "allow" özelliği aracılığıyla tanımlanabilir.

usb özelliğinin Navigator nesnesinde gösterilip gösterilmeyeceğini veya başka bir deyişle WebUSB'ye izin verip vermeyeceğinizi kontrol eden bir izin 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 başka 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 Promise'lerine dayanır. Bunlara aşina değilseniz bu harika Promises eğiticisine göz atın. Bir de () => {} basitçe ECMAScript 2015 ok işlevleridir.

USB cihazlara erişim

navigator.usb.requestDevice() kullanarak kullanıcıdan tek bir bağlı USB cihazı seçmesini isteyebilir veya web sitesinin erişimine izin verilen tüm bağlı USB cihazlarının listesini almak için navigator.usb.getDevices() işlevini çağırabilirsiniz.

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

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

Örneğin, kaynağa izin verecek şekilde yapılandırılmış bağlı bir Arduino cihazına nasıl erişeceğiniz aşağıda açıklanmıştı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); });

Siz sormadan önce bu 0x2341 onaltılık sayıyı sihirli bir şekilde düşünmemiştim. Bu USB kimlikleri listesinde "Arduino" kelimesini aradım.

Yukarıdaki karşılanan sözde döndürülen USB device, cihazla ilgili bazı temel ancak önemli bilgiler içerir (ör. desteklenen USB sürümü, maksimum paket boyutu, tedarikçi firma, ürün kimlikleri, cihazın sahip olabileceği olası yapılandırmaların sayısı). Temel olarak cihaz USB tanımlayıcısındaki 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"
  });
})

Bir USB cihazı, WebUSB desteğini duyurmanın yanı sıra bir açılış sayfası URL'si tanımlarsa Chrome, USB cihaz takıldığında 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ı ile konuşun

Şimdi, WebUSB uyumlu bir Arduino kartından USB bağlantı noktası üzerinden iletişim kurmanın ne kadar kolay olduğuna bakalım. Eskizlerinizi WebUSB'de etkinleştirmek için https://github.com/webusb/arduino adresindeki talimatları inceleyin.

Endişelenmeyin, aşağıda bahsedilen tüm WebUSB cihaz yöntemlerini bu makalenin ilerleyen bölümlerinde 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); });

Kullanmakta olduğum WebUSB kitaplığının yalnızca bir örnek protokol (standart USB seri protokolüne göre) uyguladığını ve üreticilerin istedikleri her türlü uç nokta grubunu ve türünü oluşturabileceğini unutmayın. Kontrol aktarımları, otobüs önceliğine sahip oldukları ve iyi tanımlanmış bir yapıya sahip oldukları için özellikle küçük yapılandırma komutları için kullanışlıdır.

Arduino kartına yüklenen taslak da burada gösterilmektedir.

// 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 işlevi yerine getirir:

  • Cihaz, Chrome'un açılış sayfası URL'sini okumasını sağlayan bir WebUSB cihazı gibi çalışır.
  • Varsayılan olanı geçersiz kılmak için kullanabileceğiniz bir WebUSB Seri API'si sağlar.

JavaScript koduna tekrar bakın. Kullanıcı tarafından seçilen device'i aldıktan sonra device.open(), USB cihazla oturum başlatmak için platforma özgü tüm adımları yürütür. Ardından, device.selectConfiguration() ile kullanılabilen bir USB yapılandırması seçmem gerekiyor. Bir yapılandırmada cihazın nasıl güç aldığı, maksimum güç tüketimi ve arayüz sayısı belirtilir. Arayüzden bahsetmişken, veriler yalnızca arayüz için hak talebinde bulunulduğunda bir arayüze veya ilişkili uç noktalara aktarılabileceğinden device.claimInterface() ile özel erişim isteğinde de bulunmam gerekiyor. Son olarak, Arduino cihazını WebUSB Seri API üzerinden iletişim kuracak uygun komutlarla ayarlamak için device.controlTransferOut() çağrısı gerekir.

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

USB'ye aşinasanız tüm bunlar size tanıdık gelecektir.

Daha fazlasını istiyorum

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

  • Bir USB cihaza yapılandırma veya komut parametreleri göndermek ya da almak için kullanılan KONTROL aktarımlarında controlTransferIn(setup, length) ve controlTransferOut(setup, data) kullanılır.
  • Zamana duyarlı küçük miktarda veri için kullanılan KESİNTİ 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 senkronize aktarımlar isochronousTransferIn(endpointNumber, packetLengths) ve isochronousTransferOut(endpointNumber, data, packetLengths) ile yönetilir.
  • Zamana duyarlı olmayan büyük miktarda veriyi güvenilir bir şekilde aktarmak için kullanılan BULK aktarımları, transferIn(endpointNumber, length) ve transferOut(endpointNumber, data) ile işlenir.

WebUSB API için tasarlanmış USB kontrollü bir LED cihaz oluşturma konusunda sıfırdan bir örnek sunan Mike Tsao'nun WebLight projesine de göz atabilirsiniz (burada Arduino kullanılmaz). Donanım, yazılım ve donanım yazılımı bulursunuz.

USB cihaza erişimi iptal etme

Web sitesi, USBDevice örneğinde forget() çağrısını yaparak artık ihtiyaç duymadığı bir USB cihazına erişim izinlerini temizleyebilir. Örneğin, birçok cihazın bulunduğu paylaşılan bir bilgisayarda kullanılan eğitsel bir web uygulamasında, kullanıcı tarafından oluşturulan çok sayıda izin kötü bir kullanıcı deneyimi oluşturur.

// 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ırlamalar

Bazı işletim sistemleri, bekleyen USB işlemlerinin ne kadar veri içerebileceğine dair sınırlar uygular. Verilerinizi daha küçük işlemlere ayırıp bir seferde yalnızca birkaçını göndermek bu sınırlamaları önlemeye yardımcı olur. Ayrıca, kullanılan bellek miktarını azaltır ve aktarımlar tamamlanırken uygulamanızın 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ında gecikmeyi önlemek için sıraya alınmış birden fazla parça göndererek aktarım hızını artırabilirsiniz. Bir parça tamamen iletildiğinde, aşağıdaki yardımcı işlev örneğinde belirtildiği gibi kodunuza daha fazla veri sağlaması gerektiğini bildirir.

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 ile ilgili tüm etkinlikleri tek bir yerde görebileceğiniz dahili about://device-log sayfasıyla Chrome'da USB'de hata ayıklama işlemini kolaylaştırabilirsiniz.

Chrome&#39;da WebUSB&#39;de hata ayıklama için cihaz günlük sayfasının ekran görüntüsü
WebUSB API'de hata ayıklama için Chrome'daki cihaz günlük sayfası.

about://usb-internals adlı dahili sayfa da kullanışlıdır ve sanal WebUSB cihazlarının bağlantısını ve bağlantısını kesme işlemlerini simüle etmenize olanak tanır. Bu, gerçek donanım olmadan kullanıcı arayüzü testi yapmak için yararlı olabilir.

Chrome&#39;da WebUSB&#39;de hata ayıklama için kullanılan dahili sayfanın ekran görüntüsü
WebUSB API'de hata ayıklama için Chrome'daki dahili sayfa.

Çoğu Linux sisteminde USB cihazlar varsayılan olarak salt okuma izinleriyle eşlenir. Chrome'un 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"

Örneğin, cihazınız Arduino ise [yourdevicevendor], 2341 olur. Daha belirli bir kural için ATTR{idProduct} da eklenebilir. user kullanıcısının plugdev grubunun üyesi olduğundan emin olun. Ardından, cihazınızı yeniden bağlayın.

Kaynaklar

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

Teşekkür ederiz

Bu makaleyi inceleyen Joe Medley'e teşekkürler.