Web Serial API, web sitelerinin seri cihazlarla iletişim kurmasına olanak tanır.
Web Serial API nedir?
Seri bağlantı noktası, verileri bayt bayt gönderip almaya olanak tanıyan iki yönlü bir iletişim arayüzüdür.
Web Serial API, web sitelerinin JavaScript ile seri cihazlardan veri okumasına ve bu cihazlara veri yazmasına olanak tanır. Seri cihazlar, kullanıcının sistemindeki seri bağlantı noktası veya seri bağlantı noktasını taklit eden çıkarılabilir USB ve Bluetooth cihazlar üzerinden bağlanır.
Diğer bir deyişle Web Serial API, web sitelerinin mikrodenetleyiciler ve 3D yazıcılar gibi seri cihazlarla iletişim kurmasına olanak tanıyarak web ile fiziksel dünya arasında köprü oluşturur.
İşletim sistemleri, uygulamaların bazı seri bağlantı noktalarıyla iletişim kurarken düşük düzeyli USB API yerine daha yüksek düzeyli seri API'lerini kullanmasını gerektirdiğinden bu API, WebUSB ile de mükemmel bir uyum sağlar.
Önerilen kullanım alanları
Eğitim, hobi ve endüstri sektörlerinde kullanıcılar bilgisayarlarına çevre birimleri bağlar. Bu cihazlar genellikle özel yazılımlar tarafından kullanılan seri bağlantı üzerinden mikrodenetleyiciler tarafından kontrol edilir. Bu cihazları kontrol etmek için kullanılan bazı özel yazılımlar web teknolojisiyle geliştirilmiştir:
Bazı durumlarda web siteleri, kullanıcıların manuel olarak yüklediği bir aracı uygulaması aracılığıyla cihazla iletişim kurar. Diğerlerinde ise uygulama, Electron gibi bir çerçeve aracılığıyla paketlenmiş bir uygulama olarak yayınlanır. Bazı durumlarda ise kullanıcının, derlenmiş bir uygulamayı USB flash sürücü aracılığıyla cihaza kopyalama gibi ek bir adım uygulaması gerekir.
Tüm bu durumlarda, web sitesi ile kontrol ettiği cihaz arasında doğrudan iletişim sağlanarak kullanıcı deneyimi iyileşir.
Mevcut durum
Step | Durum |
---|---|
1. Açıklayıcı oluşturma | Tamamlandı |
2. Spesifikasyonun ilk taslağını oluşturma | Tamamlandı |
3. Geri bildirim toplayın ve tasarımda iterasyon yapın | Tamamlandı |
4. Kaynak denemesi | Tamamlandı |
5. Lansman | Tamamlandı |
Web Serial API'yi kullanma
Özellik algılama
Web Serial API'nin desteklenip desteklenmediğini kontrol etmek için:
if ("serial" in navigator) {
// The Web Serial API is supported.
}
Seri bağlantı noktası açma
Web Serial API, tasarım gereği asenkrondur. Bu sayede, web sitesi kullanıcı arayüzü, giriş beklerken engellenmez. Seri veriler herhangi bir zamanda alınabileceğinden ve dinlenmesi gerektiğinden bu durum önemlidir.
Seri bağlantı noktasını açmak için önce bir SerialPort
nesnesine erişin. Bunun için, dokunma veya fare tıklaması gibi bir kullanıcı hareketine yanıt olarak navigator.serial.requestPort()
işlevini çağırarak kullanıcıdan tek bir seri bağlantı noktası seçmesini isteyebilir ya da web sitesinin erişmesine izin verilen seri bağlantı noktalarının listesini döndüren navigator.serial.getPorts()
işlevinden birini seçebilirsiniz.
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, filtreleri tanımlayan isteğe bağlı bir nesne değişmezi alır. Bunlar, USB üzerinden bağlı herhangi bir seri cihazı zorunlu USB tedarikçisiyle (usbVendorId
) ve isteğe bağlı USB ürün tanımlayıcılarıyla (usbProductId
) eşleştirmek için kullanılır.
// 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();
requestPort()
çağrısı, kullanıcıdan bir cihaz seçmesini ister ve bir SerialPort
nesnesi döndürür. Bir SerialPort
nesnesi oluşturduktan sonra, port.open()
işlevini istenen baud hızıyla çağırmak seri bağlantı noktasını açar. baudRate
sözlük üyesi, verilerin seri hat üzerinden ne kadar hızlı gönderildiğini belirtir. Saniye başına bit (bps) cinsinden ifade edilir. Bu parametre yanlış belirtilirse gönderdiğiniz ve aldığınız tüm veriler anlamsız olacaktır. Bu nedenle, doğru değeri öğrenmek için cihazınızın belgelerini inceleyin. Seri bağlantı noktasını taklit eden bazı USB ve Bluetooth cihazlarda bu değer, taklit tarafından yok sayıldığı için herhangi bir değere güvenli bir şekilde ayarlanabilir.
// 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 });
Seri bağlantı noktası açarken aşağıdaki seçeneklerden herhangi birini de belirtebilirsiniz. Bu seçenekler isteğe bağlıdır ve kullanışlı varsayılan değerlere sahiptir.
dataBits
: Kare başına veri bit sayısı (7 veya 8).stopBits
: Bir karenin sonunda bulunan durdurma bitlerinin sayısı (1 veya 2).parity
: Eşitlik 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 yönetilir.
Seri bağlantısı kurulduktan sonra SerialPort
nesnesinin readable
ve writable
özellikleri bir ReadableStream ve WritableStream döndürür. Bunlar, seri cihazdan veri almak ve seri cihaza veri göndermek için kullanılır. Her ikisi de veri aktarımı için Uint8Array
örneklerini kullanır.
Seri cihazdan yeni veri geldiğinde port.readable.getReader().read()
, value
ve done
boole türü iki özelliği eşzamansız olarak döndürür. done
doğruysa seri bağlantı noktası kapatılmış veya artık veri gelmiyordur. port.readable.getReader()
çağrısı yapıldığında bir okuyucu oluşturulur ve readable
bu okuyucuya kilitlenir. readable
kilitliyken 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);
}
Arabellek taşması, çerçeveleme hataları veya parite hataları gibi bazı koşullarda, seri bağlantı noktası okumayla ilgili bazı kritik olmayan hatalar oluşabilir. Bunlar istisna olarak atılır ve öncekinin üzerine port.readable
değerini kontrol eden başka bir döngü ekleyerek yakalanabilir. Bu, hatalar ölümcül olmadığı sürece otomatik olarak yeni bir ReadableStream oluşturulduğu için işe yarar. Seri cihazın kaldırılması gibi kritik bir hata oluşursa port.readable
null olur.
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 metin gönderirse port.readable
değerini aşağıda gösterildiği gibi bir TextDecoderStream
üzerinden aktarabilirsiniz. TextDecoderStream
, tüm Uint8Array
parçalarını alıp dizelere dönüştüren bir dönüşüm akışı'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);
}
"Own Buffer" okuyucu kullanarak akıştan veri okurken belleğin nasıl ayrıldığını kontrol edebilirsiniz. ReadableStreamBYOBReader arayüzünü almak için port.readable.getReader({ mode: "byob" })
'ü çağırın ve read()
'i çağırırken kendi ArrayBuffer
'ınızı sağlayın. Web Serial API'nin bu özelliği Chrome 106 veya sonraki sürümlerde 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()...
}
}
value.buffer
içindeki arabelleğin nasıl yeniden kullanılacağına dair bir örnek aşağıda 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`.
}
Seri bağlantı noktasından belirli miktarda verinin nasıl okunacağına dair başka bir örnek aşağıda 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
Seri cihaza veri göndermek için verileri port.writable.getWriter().write()
'e iletin. Seri bağlantı noktasının daha sonra kapatılması için port.writable.getWriter()
üzerinde releaseLock()
çağrısı yapılması 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();
Aşağıda gösterildiği gibi TextEncoderStream
ile port.writable
arasında aktarılan bir boru aracılığıyla cihaza metin gönderin.
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 kilidi açılmışsa seri bağlantı noktasını kapatır. Bu, ilgili okuyucu ve yazar için releaseLock()
çağrıldığı anlamına gelir.
await port.close();
Ancak bir döngü kullanarak seri cihazdan sürekli veri okurken port.readable
, bir hatayla karşılaşana kadar her zaman kilitli kalır. Bu durumda, reader.cancel()
çağrısı reader.read()
'u { value: undefined, done: true }
ile hemen çözmeye zorlar ve böylece döngünün reader.releaseLock()
'ı çağırmasına izin verir.
// 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ıldığında seri bağlantı noktalarını kapatmak daha karmaşıktır. reader.cancel()
'ü eskisi gibi arayın.
Ardından writer.close()
ve port.close()
'u arayın. Bu, dönüştürme akışları aracılığıyla hataları temel seri bağlantı noktasına iletir. Hata yayma işlemi hemen gerçekleşmediğinden, port.readable
ve port.writable
'in kilidinin ne zaman açıldığını algılamak için daha önce oluşturulan readableStreamClosed
ve writableStreamClosed
vaatlerini kullanmanız gerekir. reader
'ü iptal etmek, aktarımın iptal edilmesine neden olur. Bu nedenle, ortaya çıkan hatayı yakalayıp 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ı ve bağlantı kesme seslerini dinleme
USB cihaz tarafından seri bağlantı noktası sağlanıyorsa söz konusu cihaz sistemle bağlı veya sistemden ayrılmış olabilir. Web sitesine seri bağlantı noktasına erişim izni verildiğinde connect
ve disconnect
etkinliklerini izlemelidir.
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ı oluşturduktan sonra, cihaz algılama ve akış kontrolü için seri bağlantı noktası tarafından sunulan sinyalleri açıkça sorgulayabilir ve ayarlayabilirsiniz. Bu sinyaller, boole değerleri olarak tanımlanır. Örneğin, Arduino gibi bazı cihazlar Veri Terminali Hazır (DTR) sinyali etkinleştirilirse programlama moduna girer.
Çıkış sinyallerini ayarlama ve giriş sinyallerini alma işlemleri sırasıyla port.setSignals()
ve port.getSignals()
çağrılarak yapılır. Aşağıdaki kullanım örneklerine bakın.
// 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 verileri bir kerede almayabilirsiniz. İsteğe bağlı olarak parçalara ayrılabilir. Daha fazla bilgi için Streams API kavramları başlıklı makaleyi inceleyin.
Bu sorunu çözmek için TextDecoderStream
gibi bazı yerleşik dönüştürme akışlarını kullanabilir veya gelen akışı ayrıştırmanıza ve ayrıştırılan verileri döndürmenize olanak tanıyan kendi dönüştürme akışınızı oluşturabilirsiniz. Dönüşüm akışı, seri cihaz ile akışı tüketen okuma döngüsü arasında yer alır. Veriler kullanılmadan önce rastgele bir dönüştürme işlemi uygulayabilir. Bunu bir montaj hattı gibi düşünün: Bir widget hattan geçerken hattaki her adım widget'ı değiştirir. Böylece widget, son hedefine ulaştığında tamamen çalışır durumda olur.
Örneğin, bir akışı tüketen ve satır sonlarına göre parçalara ayıran bir dönüştürme akışı sınıfını nasıl oluşturacağınızı düşünün. transform()
yöntemi, akış her yeni veri aldığında çağrılır. Verileri sıraya ekleyebilir veya daha sonra kullanmak üzere kaydedebilir. flush()
yöntemi, akış kapatıldığında çağrılır ve henüz işlenmemiş tüm verileri işler.
Dönüşüm akışı sınıfını kullanmak için gelen bir akışı bu sınıftan geçirmeniz gerekir. Seri bağlantı noktasından okuma bölümündeki üçüncü kod örneğinde, orijinal giriş akışı yalnızca bir TextDecoderStream
üzerinden aktarılmıştır. Bu nedenle, yeni LineBreakTransformer
'imiz üzerinden aktarmak için pipeThrough()
'ı çağırmamız gerekir.
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 seri cihaza gelen veya cihazdan giden akışları bölmek üzere port.readable
sınıfının tee()
yöntemini kullanın. Oluşturulan iki akış bağımsız olarak kullanılabilir. Bu sayede, inceleme için bir akışı konsola yazdırabilirsiniz.
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, SerialPort
örneğinde forget()
'yi çağırarak artık tutmaya istekli olmadığı bir seri bağlantı noktasına erişim izinlerini temizleyebilir. Örneğin, birçok cihazın bulunduğu paylaşılan bir bilgisayarda kullanılan eğitim amaçlı bir web uygulamasında, kullanıcı tarafından oluşturulan çok sayıda izin birikmesi kötü bir kullanıcı deneyimi oluşturur.
// Voluntarily revoke access to this serial port.
await port.forget();
forget()
, Chrome 103 veya sonraki sürümlerde kullanılabildiğinden bu özelliğin aşağıdaki sürümlerde desteklenip desteklenmediğini kontrol edin:
if ("serial" in navigator && "forget" in SerialPort.prototype) {
// forget() is supported.
}
Geliştirici İpuçları
Chrome'da Web Seri API'de hata ayıklama işlemi, seri cihazla ilgili tüm etkinlikleri tek bir yerde görebileceğiniz dahili sayfa about://device-log
ile kolaydır.
Codelab
Google Developer codelab'de, 5x5 LED matrisinde resim göstermek için BBC micro:bit kartıyla etkileşimde bulunmak üzere Web Seri API'yi kullanacaksınız.
Tarayıcı desteği
Web Seri API'si, Chrome 89'da tüm masaüstü platformlarında (ChromeOS, Linux, macOS ve Windows) kullanılabilir.
Çoklu dolgu
Android'de, WebUSB API ve Serial API polyfill kullanılarak USB tabanlı seri bağlantı noktaları desteklenir. Bu polyfill, yerleşik bir cihaz sürücüsü tarafından hak talebinde bulunulmadığı için cihaza WebUSB API üzerinden erişilebilen donanım ve platformlarla sınırlıdır.
Güvenlik ve gizlilik
Spesifikasyon yazarları, Web Serial API'yi tasarlarken ve uygularken kullanıcı kontrolü, şeffaflık ve ergonomi gibi Güçlü Web Platformu Özelliklerine Erişimi Kontrol Etme başlıklı makalede tanımlanan temel ilkeleri temel almıştır. Bu API'nin kullanılabilmesi, temel olarak tek seferde yalnızca tek bir seri cihaza erişim izni veren bir izin modeliyle sınırlandırılmıştır. Kullanıcı istemlerine yanıt olarak kullanıcının belirli bir seri cihazı seçmek için etkin adımlar atması gerekir.
Güvenlikle ilgili avantajları ve dezavantajları anlamak için Web Seri API Açıklayıcısı'nın güvenlik ve gizlilik bölümlerine göz atın.
Geri bildirim
Chrome ekibi, Web Seri API'si hakkındaki düşüncelerinizi ve deneyimlerinizi öğrenmekten memnuniyet duyar.
API tasarımı hakkında bilgi verin
API ile ilgili beklendiği gibi çalışmayan bir şey var mı? Yoksa fikrinizi uygulamak için ihtiyaç duyduğunuz yöntemler veya özellikler eksik mi?
Web Serial API GitHub deposunda spesifikasyon sorunu bildirin veya mevcut bir soruna düşüncelerinizi ekleyin.
Uygulamayla ilgili sorunları bildirme
Chrome'un uygulamasında bir hata mı buldunuz? Yoksa uygulama, spesifikasyondan farklı mı?
https://new.crbug.com adresinden hata kaydı oluşturun. Mümkün olduğunca fazla ayrıntı eklediğinizden, hatayı yeniden oluşturmayla ilgili basit talimatlar sağladığınızdan ve Bileşenler'in Blink>Serial
olarak ayarlandığından emin olun. Glitch, hızlı ve kolay yeniden oluşturma işlemlerini paylaşmak için mükemmel bir araçtır.
Destek gösterme
Web Serial API'yi kullanmayı planlıyor musunuz? Herkese açık desteğiniz, Chrome ekibinin özelliklere öncelik vermesine yardımcı olur ve diğer tarayıcı tedarikçi firmalarına bu özellikleri desteklemenin ne kadar önemli olduğunu gösterir.
#SerialAPI
hashtag'ini kullanarak @ChromiumDev hesabına tweet gönderin ve bu özelliği nerede ve nasıl kullandığınızı bize bildirin.
Faydalı bağlantılar
- Özellik
- İzleme hatası
- ChromeStatus.com girişi
- Blink Bileşeni:
Blink>Serial
Demolar
Teşekkür ederiz
Bu makaleyi inceleyen Reilly Grant ve Joe Medley'e teşekkür ederiz. Birmingham Museums Trust'un Unsplash'ta yer alan uçak fabrikası fotoğrafı.