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 amaçlı ve sanayi sektörlerinde kullanıcılar, çevrebirimi cihazlarını bilgisayarlarına 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şturun | Tamamlandı |
2. İlk spesifikasyon taslağını oluşturun | Tamamlandı |
3. Geri bildirim alma ve tasarım üzerinde yineleme | 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 eşzamansızdır. 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 biti sayısı (7 veya 8).stopBits
: Bir karenin sonunda bulunan durdurma bitlerinin sayısı (1 veya 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 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);
}
Önemli olmayan bazı seri bağlantı noktası okuma hataları; arabellek taşması, çerçeveleme hataları veya parite hataları gibi bazı koşullarda ortaya çıkabilir. 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 önemli 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 döngü kullanarak bir seri cihazdan sürekli olarak veri okurken
port.readable
bir hatayla karşılaşana kadar her zaman kilitli kalır. Bu durumda reader.cancel()
işlevinin çağrılması, reader.read()
ürününün { value: undefined, done: true }
ile hemen çözülmesine zorlanır ve böylece döngünün reader.releaseLock()
çağrısı yapması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 yayılımı hemen gerçekleşmediğinden, port.readable
ve port.writable
kilidinin ne zaman açıldığını tespit etmek için daha önce oluşturulan readableStreamClosed
ve writableStreamClosed
taahhütlerini 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
Bir USB cihazı tarafından seri bağlantı noktası sağlanırsa bu cihaz sisteme bağlanmış veya bağlantısı kesilmiş olabilir. Web sitesine seri bağlantı noktasına erişme 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, Veri Terminali Hazır (DTR) sinyali açılıp açılmazsa Arduino gibi bazı cihazlar 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.
Bununla başa çıkmak için TextDecoderStream
gibi bazı yerleşik dönüşüm akışlarını kullanabilir veya gelen akışı ayrıştırıp ayrıştırılmış veriler döndürmenize olanak tanıyan kendi dönüşüm 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üşünebilirsiniz: Bir widget çizginin ilerisinde olduğunda, satırdaki her adım widget'ı değiştirir. Böylece, son hedefine vardığında widget tam işlevsel hale gelir.
Ö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 alabilir 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ştiriciler İçin İpuçları
Chrome'da Web Serial API'de hata ayıklamak, seri cihazla ilgili tüm etkinlikleri tek bir yerde görebileceğiniz dahili sayfa
about://device-log
sayesinde 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ü platformlarda (ChromeOS, Linux, macOS ve Windows) kullanılabilir.
Çoklu dolgu
Android'de, WebUSB API ve Serial API polyfill aracılığıyla USB tabanlı seri bağlantı noktaları desteklenebilir. 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ı istemine yanıt olarak, kullanıcının belirli bir seri cihazı seçmek için aktif 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 düşüncelerinizi mevcut bir soruna 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'e tweet gönderin ve bu hashtag'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'ın Unsplash'ta yer alan uçak fabrikası fotoğrafı.