Bir seri bağlantı noktasından okuma ve bu bağlantı noktasına yazma

Web Serial API, web sitelerinin seri cihazlarla iletişim kurmasına olanak tanır.

François Beaufort
François Beaufort

Web Serial API nedir?

Seri bağlantı noktası, bir veri akışına izin veren iki yönlü aldığı anlamına gelir.

Web Serial API, web sitelerinin belirli bir sunucudan okuması ve bir sunucuya yazması için JavaScript yüklü seri cihazda. Seri cihazlar bir kullanıcının sistemindeki seri bağlantı noktası veya çıkarılabilir USB ve Bluetooth cihazlar üzerinden seri bağlantı noktası emüle eder.

Başka bir deyişle Web Serial API, web ile fiziksel dünya arasında Web sitelerinin mikrodenetleyiciler gibi seri cihazlarla iletişim kurmasına olanak tanır. 3D yazıcılardan oluşur.

İşletim sistemlerinin gerektirmesi nedeniyle bu API, WebUSB için de mükemmel bir tamamlayıcıdır. bazı seri bağlantı noktalarıyla iletişim kurmalarına olanak tanır. seri API'sını kullanabilirsiniz.

Önerilen kullanım alanları

Eğitim, hobi amaçlı ve endüstriyel sektörlerde kullanıcılar çevre birimlerini birbirine bağlar. kendi bilgisayarlarına aktarabilir. Bu cihazlar genellikle tarafından kullanılan seri bağlantıyla mikrodenetleyiciler üzerinde çalışır. Biraz özel web teknolojisiyle geliştirilmiştir:

Bazı durumlarda web siteleri bir aracı üzerinden cihazla iletişim kurar kullanıcıların manuel olarak yüklediği uygulamadır. Diğerlerinde ise uygulama Electron gibi bir çerçeve aracılığıyla paketlenmiş bir uygulamada teslim edilir. Bazı durumlarda ise kullanıcının derlenmiş bir uygulamayı, USB flash sürücü aracılığıyla cihaza kopyalama.

Tüm bu durumlarda, kullanıcı deneyimini iyileştirmek için doğrudan kontrol ettiği cihaz arasındaki iletişimi sağlar.

Mevcut durum

Step Durum
1. Açıklayıcı oluşturun Tamamlandı
2. İlk spesifikasyon taslağını oluşturun Tamamlandı
3. Geri bildirim toplama tasarımı yineleyin Tamamlandı
4. Kaynak denemesi Tamamlandı
5. Başlat Tamamlandı

Web Serial API'yi kullanma

Özellik algılama

Web Serial API'nin desteklenip desteklenmediğini kontrol etmek için şunu kullanın:

if ("serial" in navigator) {
  // The Web Serial API is supported.
}

Seri bağlantı noktasını açma

Web Serial API, tasarımı gereği eşzamansızdır. Bu işlem, web sitesinin kullanıcı arayüzünün Bu önemli bir noktadır, çünkü seri verileri çok sayıda dinlemenin bir yol olması gerekir.

Bir seri bağlantı noktasını açmak için önce bir SerialPort nesnesine erişin. Bunun için ya da telefon araması yaparak kullanıcıdan tek bir seri bağlantı noktası seçmesini Dokunma gibi kullanıcı hareketlerine yanıt olarak navigator.serial.requestPort() veya imleçle eşleşen navigator.serial.getPorts() seçeneğinden birini seçin. Web sitesinin erişmesine izin verilen seri bağlantı noktalarının listesi.

document.querySelector('button').addEventListener('click', async () => {
  // Prompt user to select any serial port.
  const port = await navigator.serial.requestPort();
});
// Get all serial ports the user has previously granted the website access to.
const ports = await navigator.serial.getPorts();

navigator.serial.requestPort() işlevi, isteğe bağlı bir nesne değişmez değeri alır bir filtre oluşturabilirsiniz. Bunlar, bağlı herhangi bir seri cihazı eşleştirmek için kullanılır Zorunlu USB tedarikçisi (usbVendorId) ve isteğe bağlı USB ürünü içeren USB tanımlayıcılar (usbProductId).

// Filter on devices with the Arduino Uno USB Vendor/Product IDs.
const filters = [
  { usbVendorId: 0x2341, usbProductId: 0x0043 },
  { usbVendorId: 0x2341, usbProductId: 0x0001 }
];

// Prompt user to select an Arduino Uno device.
const port = await navigator.serial.requestPort({ filters });

const { usbProductId, usbVendorId } = port.getInfo();
Bir web sitesindeki seri bağlantı noktası isteminin ekran görüntüsü
BBC micro:bit seçimi için kullanıcı istemi

requestPort() çağrısı yapıldığında kullanıcıdan cihaz seçmesi istenir ve SerialPort nesne algılandı. port.open() çağrısı yapan bir SerialPort nesneniz olduğunda seri bağlantı noktası açılır. baudRate sözlüğü üye, verilerin bir seri hat üzerinden ne kadar hızlı gönderildiğini belirtir. Bu ifade birimi (bit/sn) cinsinden girin. Cihazınızın dokümanlarında doğru değere sahip. Bu durumda gönderdiğiniz ve aldığınız tüm veriler anlamsız olacaktır yanlış belirtilmiş olabilir. Seri emülasyonu kullanan bazı USB ve Bluetooth cihazlarda göz ardı edildiğinden, güvenli bir şekilde herhangi bir değere ayarlanabilir. emülasyon.

// Prompt user to select any serial port.
const port = await navigator.serial.requestPort();

// Wait for the serial port to open.
await port.open({ baudRate: 9600 });

Ayrıca, bir seri bağlantı noktasını açarken aşağıdaki seçeneklerden herhangi birini de belirtebilirsiniz. Bu seçenekler isteğe bağlıdır ve uygun varsayılan değerlere sahiptir.

  • dataBits: Kare başına veri biti sayısı (7 veya 8).
  • stopBits: Bir karenin sonundaki durak bitlerinin sayısı (1 ya da 2).
  • parity: Eşlik modu ("none", "even" veya "odd").
  • bufferSize: Oluşturulması gereken okuma ve yazma arabelleklerinin boyutu (16 MB'tan küçük olmalıdır).
  • flowControl: Akış denetimi modu ("none" veya "hardware").

Seri bağlantı noktasından okuma

Web Serial API'deki giriş ve çıkış akışları, Streams API tarafından işlenir.

Seri bağlantı noktası bağlantısı kurulduktan sonra readable ve writable SerialPort nesnesindeki özellikler bir ReadableStream ve WritableStream. Bunlar, seri cihaz. Her ikisi de veri aktarımı için Uint8Array örneklerini kullanır.

Seri cihazdan yeni veri geldiğinde, port.readable.getReader().read() eşzamansız olarak iki özellik döndürür: value ve done boole. Eğer done doğru, seri bağlantı noktası kapatılmış veya gelen başka veri yok inç port.readable.getReader() çağrıldığında bir okuyucu oluşturulur ve readable şuna kilitlenir: somut olarak ortaya koyar. readable kilitli durumdayken seri bağlantı noktası kapatılamaz.

const reader = port.readable.getReader();

// Listen to data coming from the serial device.
while (true) {
  const { value, done } = await reader.read();
  if (done) {
    // Allow the serial port to be closed later.
    reader.releaseLock();
    break;
  }
  // value is a Uint8Array.
  console.log(value);
}

Önemli olmayan bazı seri bağlantı noktası okuma hataları, arabellek taşması, çerçeveleme hataları veya parite hataları. Bunlar istisnalar vardır ve bir öncekinin üzerine başka bir döngü eklenerek yakalanabilir. port.readable olup olmadığını kontrol edin. Bu işe yarar çünkü hatalar önemli olmayan bir değer varsa otomatik olarak yeni bir ReadableStream oluşturulur. Önemli bir hata seri cihazın kaldırılması gibi bir gerçekleştiğinde port.readable, null.

while (port.readable) {
  const reader = port.readable.getReader();

  try {
    while (true) {
      const { value, done } = await reader.read();
      if (done) {
        // Allow the serial port to be closed later.
        reader.releaseLock();
        break;
      }
      if (value) {
        console.log(value);
      }
    }
  } catch (error) {
    // TODO: Handle non-fatal read error.
  }
}

Seri cihaz metni geri gönderirse port.readable öğesini bir TextDecoderStream olarak gösterilir. TextDecoderStream, bir dönüşüm akışıdır tüm Uint8Array parçaları yakalayıp dizelere dönüştüren bir komut dosyasıdır.

const textDecoder = new TextDecoderStream();
const readableStreamClosed = port.readable.pipeTo(textDecoder.writable);
const reader = textDecoder.readable.getReader();

// Listen to data coming from the serial device.
while (true) {
  const { value, done } = await reader.read();
  if (done) {
    // Allow the serial port to be closed later.
    reader.releaseLock();
    break;
  }
  // value is a string.
  console.log(value);
}

"Kendi Arabelleğinizi Getirin" işlevini kullanarak akıştan okuma yaptığınızda belleğin nasıl ayrılacağını kontrol edebilirsiniz. yardımcı olur. ReadableStreamBYOBReader arayüzünü almak için port.readable.getReader({ mode: "byob" }) yöntemini arayın ve read() çağrısı yaparken kendi ArrayBuffer öğenizi sağlayın. Chrome 106 veya sonraki sürümlerde Web Serial API'nin bu özelliği desteklediğini unutmayın.

try {
  const reader = port.readable.getReader({ mode: "byob" });
  // Call reader.read() to read data into a buffer...
} catch (error) {
  if (error instanceof TypeError) {
    // BYOB readers are not supported.
    // Fallback to port.readable.getReader()...
  }
}

Aşağıda, value.buffer öğesinden arabelleğin nasıl yeniden kullanılacağına dair bir örnek verilmiştir:

const bufferSize = 1024; // 1kB
let buffer = new ArrayBuffer(bufferSize);

// Set `bufferSize` on open() to at least the size of the buffer.
await port.open({ baudRate: 9600, bufferSize });

const reader = port.readable.getReader({ mode: "byob" });
while (true) {
  const { value, done } = await reader.read(new Uint8Array(buffer));
  if (done) {
    break;
  }
  buffer = value.buffer;
  // Handle `value`.
}

Aşağıda, bir seri bağlantı noktasından belirli miktarda verinin nasıl okunacağına ilişkin başka bir örnek verilmiştir:

async function readInto(reader, buffer) {
  let offset = 0;
  while (offset < buffer.byteLength) {
    const { value, done } = await reader.read(
      new Uint8Array(buffer, offset)
    );
    if (done) {
      break;
    }
    buffer = value.buffer;
    offset += value.byteLength;
  }
  return buffer;
}

const reader = port.readable.getReader({ mode: "byob" });
let buffer = new ArrayBuffer(512);
// Read the first 512 bytes.
buffer = await readInto(reader, buffer);
// Then read the next 512 bytes.
buffer = await readInto(reader, buffer);

Seri bağlantı noktasına yazma

Bir seri cihaza veri göndermek için verileri şuraya aktarın: port.writable.getWriter().write() releaseLock() aranıyor açık Seri bağlantı noktasının daha sonra kapatılması için port.writable.getWriter() gerekir.

const writer = port.writable.getWriter();

const data = new Uint8Array([104, 101, 108, 108, 111]); // hello
await writer.write(data);


// Allow the serial port to be closed later.
writer.releaseLock();

port.writable hizmetine bağlı TextEncoderStream üzerinden cihaza metin gönderin aşağıda gösterildiği gibidir.

const textEncoder = new TextEncoderStream();
const writableStreamClosed = textEncoder.readable.pipeTo(port.writable);

const writer = textEncoder.writable.getWriter();

await writer.write("hello");

Seri bağlantı noktasını kapatma

port.close(), readable ve writable üyeleri varsa seri bağlantı noktasını kapatır kilidi açık. Yani releaseLock(), ilgili ve yazarsınız.

await port.close();

Ancak döngü kullanılarak bir seri cihazdan gelen veriler sürekli okunurken, port.readable, bir hatayla karşılaşana kadar her zaman kilitli kalır. Burada destek kaydı, reader.cancel() çağrıldığında reader.read() zorla çözümlenir { value: undefined, done: true } ile hemen yapıp reader.releaseLock() arama döngüsünü uygulayın.

// Without transform streams.

let keepReading = true;
let reader;

async function readUntilClosed() {
  while (port.readable && keepReading) {
    reader = port.readable.getReader();
    try {
      while (true) {
        const { value, done } = await reader.read();
        if (done) {
          // reader.cancel() has been called.
          break;
        }
        // value is a Uint8Array.
        console.log(value);
      }
    } catch (error) {
      // Handle error...
    } finally {
      // Allow the serial port to be closed later.
      reader.releaseLock();
    }
  }

  await port.close();
}

const closedPromise = readUntilClosed();

document.querySelector('button').addEventListener('click', async () => {
  // User clicked a button to close the serial port.
  keepReading = false;
  // Force reader.read() to resolve immediately and subsequently
  // call reader.releaseLock() in the loop example above.
  reader.cancel();
  await closedPromise;
});

Dönüşüm akışları kullanılırken seri bağlantı noktasının kapatılması daha karmaşık bir işlemdir. reader.cancel() adlı kişiyi eskisi gibi arayın. Ardından writer.close() ve port.close() numaralı telefonu arayın. Bu işlem, hataları yayarak temel seri bağlantı noktasına aktarmanızı sağlar. Çünkü hata yayılımı hemen gerçekleşmezse readableStreamClosed ve kullanmanız gerekir. port.readable adlı zamanın algılanması için daha önce oluşturulmuş writableStreamClosed taahhütleri ve port.writable cihazının kilidi açıldı. reader iptal edildiğinde akışın iptal edilmesi; Bu nedenle, ortaya çıkan hatayı bulup yoksaymanız gerekir.

// With transform streams.

const textDecoder = new TextDecoderStream();
const readableStreamClosed = port.readable.pipeTo(textDecoder.writable);
const reader = textDecoder.readable.getReader();

// Listen to data coming from the serial device.
while (true) {
  const { value, done } = await reader.read();
  if (done) {
    reader.releaseLock();
    break;
  }
  // value is a string.
  console.log(value);
}

const textEncoder = new TextEncoderStream();
const writableStreamClosed = textEncoder.readable.pipeTo(port.writable);

reader.cancel();
await readableStreamClosed.catch(() => { /* Ignore the error */ });

writer.close();
await writableStreamClosed;

await port.close();

Bağlantıyı ve bağlantıyı kesmeyi dinleyin

Bir USB cihazı tarafından seri bağlantı noktası sağlanırsa o cihaz bağlı olabilir veya sistemle bağlantısı kesilebilir. Web sitesine seri bağlantı noktasına eriştiğinde connect ve disconnect etkinliklerini izlemesi gerekir.

navigator.serial.addEventListener("connect", (event) => {
  // TODO: Automatically open event.target or warn user a port is available.
});

navigator.serial.addEventListener("disconnect", (event) => {
  // TODO: Remove |event.target| from the UI.
  // If the serial port was opened, a stream error would be observed as well.
});

Sinyalleri işleme

Seri bağlantı noktası bağlantısını kurduktan sonra, bu bağlantıyı doğrudan cihaz algılama ve akış kontrolü için seri bağlantı noktası tarafından açığa çıkarılan sinyaller. Bu sinyalleri boole değerleri olarak tanımlanır. Örneğin, Arduino gibi bazı cihazlar değeri, Veri Terminali Hazır (DTR) sinyali düğmesi açıldı.

Çıkış sinyallerini ayarlama ve giriş sinyallerini alma işlemleri sırasıyla port.setSignals() ve port.getSignals() aranıyor. Aşağıdaki kullanım örneklerini inceleyin.

// Turn off Serial Break signal.
await port.setSignals({ break: false });

// Turn on Data Terminal Ready (DTR) signal.
await port.setSignals({ dataTerminalReady: true });

// Turn off Request To Send (RTS) signal.
await port.setSignals({ requestToSend: false });
const signals = await port.getSignals();
console.log(`Clear To Send:       ${signals.clearToSend}`);
console.log(`Data Carrier Detect: ${signals.dataCarrierDetect}`);
console.log(`Data Set Ready:      ${signals.dataSetReady}`);
console.log(`Ring Indicator:      ${signals.ringIndicator}`);

Akışları dönüştürme

Seri cihazdan veri aldığınızda, tüm bildirimleri almayabilirsiniz. verileri tek seferde toplayabilirsiniz. Rastgele parçalanabilir. Daha fazla bilgi için bkz. Streams API kavramları.

Bununla başa çıkmak için TextDecoderStream veya kendi dönüşüm akışınızı oluşturun. ayrıştırılır ve ayrıştırılan veriler döndürülür. Dönüşüm akışı seri cihaz ile akışı tüketen okuma döngüsü arasında bağlantı kurar. O da veriler tüketilmeden önce rastgele bir dönüşüm uygular. Bunu bir montaj çizgisi: bir widget çizginin aşağısına doğru ilerledikçe satırdaki her adım Bu şekilde, son hedefine vardığında widget'ın tam olarak işleyen bir widget'a dokunun.

Bir uçak fabrikasının fotoğrafı
2. Dünya Savaşı Kalesi Bromwich Uçak Fabrikası

Örneğin, şu miktarda veri tüketen bir dönüşüm akışı sınıfının nasıl akış şeklinde gösterir ve satır sonlarına göre parçalara ayırır. transform() yöntemi akış tarafından her yeni veri alındığında. Verileri sıraya alabilir ya da daha sonra ilgilenmek üzere saklayabilirsiniz. flush() yöntemi, akış kapatıldığında çağrılır ve henüz işlenmemiş tüm verileri işler.

Dönüştürme akışı sınıfını kullanmak için gelen akışı akış olarak eklemeniz gerekir: somut olarak ortaya koyar. Seri bağlantı noktasından okuma altındaki üçüncü kod örneğinde, orijinal giriş akışı yalnızca bir TextDecoderStream üzerinden sağlanır. Bu nedenle, yeni LineBreakTransformer ile bağlantı kurmak için pipeThrough() adlı kişiyi çağırmamız gerekiyor.

class LineBreakTransformer {
  constructor() {
    // A container for holding stream data until a new line.
    this.chunks = "";
  }

  transform(chunk, controller) {
    // Append new chunks to existing chunks.
    this.chunks += chunk;
    // For each line breaks in chunks, send the parsed lines out.
    const lines = this.chunks.split("\r\n");
    this.chunks = lines.pop();
    lines.forEach((line) => controller.enqueue(line));
  }

  flush(controller) {
    // When the stream is closed, flush any remaining chunks out.
    controller.enqueue(this.chunks);
  }
}
const textDecoder = new TextDecoderStream();
const readableStreamClosed = port.readable.pipeTo(textDecoder.writable);
const reader = textDecoder.readable
  .pipeThrough(new TransformStream(new LineBreakTransformer()))
  .getReader();

Seri cihaz iletişim sorunlarını ayıklamak için şunun tee() yöntemini kullanın: Seri cihaza giden veya seri cihazdan gelen akışları bölmek için port.readable. İkisi oluşturulan akışlar bağımsız olarak tüketilebilir ve bu, tek bir akış yazdırmanıza olanak tanır. konsola gönderilir.

const [appReadable, devReadable] = port.readable.tee();

// You may want to update UI with incoming data from appReadable
// and log incoming data in JS console for inspection from devReadable.

Seri bağlantı noktasına erişimi iptal etme

Web sitesi, artık kullanılmayan bir seri bağlantı noktasına erişim izinlerini temizleyebilir SerialPort örneğinde forget() çağırarak elde tutmayı düşünebilir. Örneğin, Örneğin, birçok kullanıcıyla paylaşılan bir bilgisayarda kullanılan eğitsel bir web uygulaması kullanıcı tarafından oluşturulmuş çok sayıda izin, kötü performansa neden olur. en iyi uygulamaları paylaşacağız.

// Voluntarily revoke access to this serial port.
await port.forget();

forget(), Chrome 103 veya sonraki sürümlerde kullanıma sunulduğundan bu özelliğin şunlar tarafından desteklenir:

if ("serial" in navigator && "forget" in SerialPort.prototype) {
  // forget() is supported.
}

Geliştiriciler İpuçları

Chrome'da Web Serial API'de hata ayıklamak dahili sayfa, Seri cihazla ilgili tüm etkinlikleri tek bir yerde görebileceğiniz about://device-log çok işe yarar.

Web Serial API&#39;de hata ayıklamayla ilgili dahili sayfanın ekran görüntüsü.
Web Serial API'sinde hata ayıklama için Chrome'daki dahili sayfa.

Codelab

Google Developer codelab'de şu etkinliklerle ilgili olarak Web Serial API'yi kullanacaksınız: BBC micro:bit kartı ile bu verilerin 5x5 LED matrisinde gösteriliyor.

Tarayıcı desteği

Web Serial API tüm masaüstü platformlarında (ChromeOS, Linux, macOS, ve Windows) - Chrome 89'da kullanılabilir.

Çoklu Dolgu

Android'de WebUSB API kullanılarak USB tabanlı seri bağlantı noktaları desteklenebilir ve Serial API çoklu dolgusu. Bu çoklu dolgu, donanımla ve platforma erişim sağlanamadığından cihazın WebUSB API üzerinden erişilebilir olduğu yerleşik bir cihaz sürücüsü tarafından hak talebinde bulunulmuş olabilir.

Güvenlik ve gizlilik

Bu spesifikasyonların yazarları, çekirdek API'yi kullanarak Web Serial API'yi Güçlü Web Platformu Özelliklerine Erişimi Kontrol Etme'de açıklanan ilkeleri, dahil olmak üzere tüm bunları kapsar. Bunu kullanma imkanı API, esas olarak yalnızca tek bir kullanıcıya erişim veren bir izin modeliyle denetlenir. seri cihazdan sarkıtılabilir. Kullanıcı istemine yanıt olarak kullanıcının etkin olması gerekir adımları uygulayın.

Güvenlikle ilgili ayrıntıları anlamak için güvenlik ve gizlilik konularına göz atın. bölümlerine göz atın.

Geri bildirim

Chrome ekibi Web Serial API.

Bize API tasarımı hakkında bilgi verin

API'de beklendiği gibi çalışmayan bir şey mi var? Yoksa fikrinizi uygulamak için ihtiyacınız olan yöntem veya özelliklerden yoksun mu?

Web Serial API GitHub deposunda teknik özellik sorunu bildirin veya düşünmeye başlayabilirsiniz.

Uygulamayla ilgili bir sorunu bildirin

Chrome'un uygulanmasıyla ilgili bir hata buldunuz mu? Yoksa ve spesifikasyondan farklı mı?

https://new.crbug.com adresinden hata bildiriminde bulunun. Olabildiğince çok sayıda mümkün olduğunca ayrıntı verin, hatayı yeniden oluşturmak için basit talimatlar sağlayın ve Bileşenler Blink>Serial olarak ayarlandı. Glitch mükemmel bir yöntemdir hızlı ve kolay yenidenler paylaşabilirsiniz.

Desteğinizi gösterin

Web Serial API'yi kullanmayı planlıyor musunuz? Herkese açık desteğiniz Chrome'un ekibinin öncelikle özelliklerin ne kadar önemli olduğunu gösterir ve diğer tarayıcı tedarikçilerine destek olmak.

Hashtag'i kullanarak @ChromiumDev hesabına tweet gönderin #SerialAPI ve nerede ve nasıl kullandığınızı bize bildirin.

Faydalı bağlantılar

Demolar

Teşekkür

Bu makaleyle ilgili yorumları için Reilly Grant ve Joe Medley'ye teşekkür ederiz. Birmingham Museums Trust tarafından Unsplash'teki uçak fabrikası fotoğrafı.