Web Serial API memungkinkan situs berkomunikasi dengan perangkat serial.
Apa itu Web Serial API?
Porta serial adalah antarmuka komunikasi dua arah yang memungkinkan pengiriman dan menerima data dalam setiap byte.
Web Serial API menyediakan cara bagi situs web untuk membaca dan menulis ke perangkat serial dengan JavaScript. Perangkat serial terhubung melalui port serial pada sistem pengguna atau melalui perangkat USB dan Bluetooth yang dapat dilepas yang mengemulasi porta serial.
Dengan kata lain, Web Serial API menjembatani web dan dunia fisik dengan memungkinkan situs web untuk berkomunikasi dengan perangkat serial, seperti mikrokontroler dan printer 3D.
API ini juga merupakan pendamping yang baik untuk WebUSB karena sistem operasi memerlukan aplikasi untuk berkomunikasi dengan beberapa porta serial menggunakan server level{i> <i}level yang lebih tinggi serial, bukan USB API level rendah.
Kasus penggunaan yang disarankan
Di sektor pendidikan, pehobi, dan industri, pengguna menghubungkan perangkat periferal perangkat seluler mereka ke komputer mereka. Perangkat ini sering dikendalikan oleh {i>microcontroller<i} melalui koneksi serial yang digunakan oleh perangkat lunak khusus. Beberapa kustom perangkat lunak untuk mengontrol perangkat tersebut dibangun dengan teknologi web:
Dalam beberapa kasus, situs berkomunikasi dengan perangkat melalui agen aplikasi yang diinstal pengguna secara manual. Di negara lain, aplikasi yang disajikan dalam aplikasi terpaket melalui kerangka kerja seperti Electron. Dan pada kasus lain, pengguna diharuskan melakukan langkah tambahan seperti menyalin aplikasi yang telah dikompilasi ke perangkat melalui USB {i>flash drive<i}.
Dalam semua kasus ini, pengalaman pengguna akan ditingkatkan dengan memberikan komunikasi antara {i>website<i} dan perangkat yang dikendalikannya.
Status saat ini
Langkah | Status |
---|---|
1. Buat penjelasan | Selesai |
2. Membuat draf awal spesifikasi | Selesai |
3. Kumpulkan masukan & mengulangi desain | Selesai |
4. Uji coba origin | Selesai |
5. Luncurkan | Selesai |
Menggunakan Web Serial API
Deteksi fitur
Untuk memeriksa apakah Web Serial API didukung, gunakan:
if ("serial" in navigator) {
// The Web Serial API is supported.
}
Membuka port serial
Web Serial API dirancang untuk asinkron. Hal ini mencegah UI {i>website<i} dari memblokir ketika menunggu input, yang penting karena data serial dapat diterima kapan saja, sehingga memerlukan cara untuk mendengarkannya.
Untuk membuka port serial, akses objek SerialPort
terlebih dahulu. Untuk itu, Anda dapat
meminta pengguna untuk memilih satu porta serial dengan memanggil
navigator.serial.requestPort()
sebagai respons terhadap gestur pengguna seperti sentuhan
atau klik mouse, atau pilih satu dari navigator.serial.getPorts()
yang menampilkan
daftar porta serial yang
telah diberikan akses ke situs web.
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();
Fungsi navigator.serial.requestPort()
menggunakan literal objek opsional
yang mendefinisikan filter. Keduanya digunakan untuk mencocokkan
perangkat seri apa pun yang terhubung melalui
USB dengan vendor USB wajib (usbVendorId
) dan produk USB opsional
ID (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();
Memanggil requestPort()
akan meminta pengguna memilih perangkat dan menampilkan
Objek SerialPort
. Setelah Anda memiliki objek SerialPort
, memanggil port.open()
dengan kecepatan baud yang diinginkan
akan membuka porta serial. Kamus baudRate
menentukan seberapa cepat data dikirim melalui jalur serial. Hal ini dinyatakan dalam
satuan bit-per-detik (bps). Periksa dokumentasi perangkat Anda untuk menemukan
nilai yang benar karena semua data yang
Anda kirim dan terima akan nonsens jika
ditentukan dengan tidak benar. Untuk beberapa perangkat USB dan Bluetooth yang mengemulasikan
nilai ini dapat disetel dengan aman ke nilai apa pun karena diabaikan oleh
emulasi.
// 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 });
Anda juga dapat menentukan salah satu opsi di bawah saat membuka port serial. Ini opsi bersifat opsional dan memiliki nilai default yang praktis.
dataBits
: Jumlah bit data per frame (7 atau 8).stopBits
: Jumlah bit perhentian di akhir frame (1 atau 2).parity
: Mode paritas (baik"none"
,"even"
, atau"odd"
).bufferSize
: Ukuran buffer baca dan tulis yang harus dibuat (harus kurang dari 16 MB).flowControl
: Mode kontrol alur ("none"
atau"hardware"
).
Membaca dari port serial
Stream input dan output di Web Serial API ditangani oleh Streams API.
Setelah koneksi port serial dibuat, readable
dan writable
properti dari objek SerialPort
menampilkan ReadableStream dan
WritableStream. Itu akan digunakan untuk menerima data dari dan mengirim data ke
perangkat serial. Keduanya menggunakan instance Uint8Array
untuk transfer data.
Saat data baru tiba dari perangkat seri, port.readable.getReader().read()
menampilkan dua properti secara asinkron: value
dan boolean done
. Jika
done
benar, port serial telah ditutup atau tidak ada data lagi yang masuk
inc. Memanggil port.readable.getReader()
akan membuat pembaca dan mengunci readable
ke
anotasi. Saat readable
terkunci, port serial tidak dapat ditutup.
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);
}
Beberapa error pembacaan port serial non-fatal dapat terjadi dalam kondisi tertentu seperti
{i>buffer overflow<i}, kesalahan {i>framing<i}, atau kesalahan paritas. Nilai tersebut ditampilkan sebagai
pengecualian dan dapat ditangkap dengan menambahkan {i>
loop <i}lain di atas loop sebelumnya
yang memeriksa port.readable
. Ini berfungsi karena selama {i>error<i}
non-fatal, ReadableStream baru akan dibuat secara otomatis. Jika error fatal
terjadi, seperti perangkat seri yang dihapus, maka port.readable
menjadi
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.
}
}
Jika perangkat serial mengirimkan teks kembali, Anda dapat menyalurkan port.readable
melalui
TextDecoderStream
seperti yang ditunjukkan di bawah ini. TextDecoderStream
adalah aliran transformasi
yang mengambil semua potongan Uint8Array
dan mengonversinya menjadi string.
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);
}
Anda dapat mengontrol cara memori dialokasikan saat membaca dari streaming menggunakan "Bring Your Own Buffer" pembaca. Panggil port.readable.getReader({ mode: "byob" })
untuk mendapatkan antarmuka ReadableStreamBYOBReader dan memberikan ArrayBuffer
Anda sendiri saat memanggil read()
. Perhatikan bahwa Web Serial API mendukung fitur ini di Chrome 106 atau yang lebih baru.
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()...
}
}
Berikut adalah contoh cara menggunakan kembali buffer dari value.buffer
:
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`.
}
Berikut contoh lain cara membaca data dalam jumlah tertentu dari port serial:
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);
Menulis ke port serial
Untuk mengirim data ke perangkat serial, teruskan data ke
port.writable.getWriter().write()
. Menelepon releaseLock()
aktif
port.writable.getWriter()
diperlukan agar port serial ditutup nanti.
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();
Mengirim teks ke perangkat melalui TextEncoderStream
yang disalurkan ke port.writable
sebagaimana ditunjukkan di bawah ini.
const textEncoder = new TextEncoderStream();
const writableStreamClosed = textEncoder.readable.pipeTo(port.writable);
const writer = textEncoder.writable.getWriter();
await writer.write("hello");
Menutup port serial
port.close()
menutup port serial jika anggota readable
dan writable
-nya
terbuka, yang berarti releaseLock()
telah dipanggil untuk masing-masing
pembaca dan penulis.
await port.close();
Namun, ketika terus membaca data dari
perangkat serial menggunakan loop,
port.readable
akan selalu dikunci hingga mengalami error. Di sini
kasus, memanggil reader.cancel()
akan memaksa reader.read()
untuk menyelesaikan
segera dengan { value: undefined, done: true }
sehingga memungkinkan
melakukan loop untuk memanggil reader.releaseLock()
.
// 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;
});
Menutup port serial lebih rumit saat menggunakan streaming transformasi. Panggil reader.cancel()
seperti sebelumnya.
Lalu, panggil writer.close()
dan port.close()
. {i>Error<i} akan disebarkan melalui
aliran transformasi ke porta serial yang mendasarinya. Karena penyebaran error
tidak terjadi seketika, Anda perlu menggunakan readableStreamClosed
dan
Promise writableStreamClosed
dibuat lebih awal untuk mendeteksi kapan port.readable
dan port.writable
telah dibuka. Membatalkan reader
akan menyebabkan
streaming yang akan dibatalkan; itulah sebabnya Anda harus menangkap dan
mengabaikan {i>error<i} yang dihasilkan.
// 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();
Mendengarkan koneksi dan pemutusan koneksi
Jika port serial disediakan oleh perangkat USB, maka perangkat tersebut mungkin terhubung
atau terputus dari sistem. Ketika situs web telah diberikan izin untuk
mengakses port serial, perangkat akan memantau peristiwa connect
dan disconnect
.
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.
});
Menangani sinyal
Setelah membuat koneksi porta serial, Anda dapat secara eksplisit melakukan kueri dan sinyal yang diekspos oleh port serial untuk deteksi perangkat dan kontrol aliran. Ini sinyal ditentukan sebagai nilai boolean. Misalnya, beberapa perangkat seperti Arduino akan memasuki mode pemrograman jika sinyal {i>Data Terminal Ready<i} (DTR) diaktifkan.
Menetapkan sinyal output dan mendapatkan sinyal input masing-masing dilakukan oleh
memanggil port.setSignals()
dan port.getSignals()
. Lihat contoh penggunaan di bawah.
// 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}`);
Mentransformasi feed
Ketika Anda menerima data dari perangkat serial, Anda tidak selalu mendapatkan semua data sekaligus. Kode ini dapat dipotong secara arbitrer. Untuk informasi selengkapnya, lihat Konsep Streams API.
Untuk mengatasi hal ini, Anda dapat menggunakan
beberapa streaming transformasi bawaan seperti
TextDecoderStream
atau buat aliran transformasi Anda sendiri, sehingga Anda dapat
mengurai aliran yang masuk dan
mengembalikan data yang diuraikan. Aliran transformasi berada
di antara perangkat serial dan loop baca
yang memakai aliran. Teknologi ini dapat
menerapkan transformasi arbitrer sebelum data dikonsumsi. Anggap saja seperti
lini perakitan: saat widget menurun, setiap langkah dalam baris akan berubah
widget, sehingga saat sampai ke tujuan akhir, widget tersebut sepenuhnya
berfungsi.
Misalnya, perhatikan cara membuat class aliran data transformasi yang menggunakan
melakukan streaming dan memotongnya
berdasarkan jeda baris. Metode transform()
dipanggil
setiap kali data baru diterima oleh aliran data. Bisa mengantrekan data atau
menyimpannya untuk nanti. Metode flush()
dipanggil saat streaming ditutup, dan
ia menangani setiap data yang belum diproses.
Untuk menggunakan class aliran data transformasi, Anda perlu menyalurkan aliran masuk melalui
anotasi. Dalam contoh kode ketiga di bagian Read from a serial port,
aliran input asli hanya disalurkan melalui TextDecoderStream
, jadi kami
perlu memanggil pipeThrough()
untuk menyalurkannya melalui LineBreakTransformer
baru kita.
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();
Untuk men-debug masalah komunikasi perangkat serial, gunakan metode tee()
port.readable
untuk membagi streaming yang menuju atau keluar dari perangkat serial. Dua
stream yang dibuat dapat digunakan secara independen dan ini memungkinkan Anda mencetaknya
ke konsol untuk diperiksa.
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.
Mencabut akses ke port serial
Situs dapat menghapus izin untuk mengakses port serial yang tidak lagi diperlukan
yang tertarik untuk mempertahankan dengan memanggil forget()
pada instance SerialPort
. Sebagai
misalnya, untuk aplikasi web pendidikan yang digunakan
di komputer bersama dengan banyak
perangkat, sejumlah besar akumulasi izin
yang dibuat pengguna akan menciptakan
{i>user experience<i}.
// Voluntarily revoke access to this serial port.
await port.forget();
Karena forget()
tersedia di Chrome 103 atau yang lebih baru, periksa apakah fitur ini tersedia
didukung dengan hal berikut:
if ("serial" in navigator && "forget" in SerialPort.prototype) {
// forget() is supported.
}
Tips Developer
{i>Debugging<i} Web Serial API di Chrome mudah dilakukan dengan halaman internal,
about://device-log
tempat Anda dapat melihat semua peristiwa terkait perangkat serial dalam satu
suatu tempat.
Codelab
Di codelab Google Developer, Anda akan menggunakan Web Serial API untuk berinteraksi dengan board BBC micro:bit untuk menampilkan gambar pada matriks LED 5x5.
Dukungan browser
Web Serial API tersedia di semua platform desktop (ChromeOS, Linux, macOS, dan Windows) di Chrome 89.
Polyfill
Di Android, dukungan untuk port serial berbasis USB dapat dilakukan menggunakan WebUSB API dan polyfill Serial API. Polyfill ini terbatas untuk hardware dan platform di mana perangkat dapat diakses melalui WebUSB API karena belum diklaim oleh {i> driver<i} perangkat bawaan.
Keamanan dan privasi
Penulis spesifikasi telah merancang dan mengimplementasikan Web Serial API menggunakan yang ditentukan dalam Mengontrol Akses ke Fitur Platform Web yang Canggih, termasuk kontrol pengguna, transparansi, dan ergonomi. Kemampuan menggunakan API terutama dilindungi oleh model izin yang memberikan akses hanya ke satu perangkat serial pada satu waktu. Sebagai respons terhadap perintah pengguna, pengguna harus mengaktifkan langkah-langkah untuk memilih perangkat serial tertentu.
Untuk memahami konsekuensi dari segi keamanan, lihat keamanan dan privasi dari Web Serial API Explainer.
Masukan
Tim Chrome ingin mengetahui pendapat dan pengalaman Anda dengan Web Serial API.
Beri tahu kami tentang desain API
Apakah ada sesuatu terkait API yang tidak berfungsi seperti yang diharapkan? Atau apakah ada kehilangan metode atau properti yang diperlukan untuk mengimplementasikan ide?
Ajukan masalah spesifikasi di repo GitHub Web Serial API atau tambahkan pendapat Anda terhadap masalah yang ada.
Laporkan masalah terkait penerapan
Apakah Anda menemukan bug pada implementasi Chrome? Ataukah implementasi berbeda dengan spesifikasi?
Laporkan bug di https://new.crbug.com. Pastikan untuk menyertakan sebanyak mungkin
sedetail mungkin, berikan petunjuk sederhana
untuk mereproduksi {i>bug<i}, dan
Komponen ditetapkan ke Blink>Serial
. Glitch sangat cocok untuk
berbagi repro yang
cepat dan mudah.
Tunjukkan dukungan
Apakah Anda berencana menggunakan Web Serial API? Dukungan publik Anda membantu Chrome tim memprioritaskan fitur dan menunjukkan kepada vendor browser lain betapa pentingnya untuk mendukung mereka.
Kirim tweet ke @ChromiumDev menggunakan hashtag
#SerialAPI
dan beri tahu kami tempat serta
cara Anda menggunakannya.
Link bermanfaat
- Spesifikasi
- Bug pelacakan
- Entri ChromeStatus.com
- Komponen Kedipan:
Blink>Serial
Demo
Ucapan terima kasih
Terima kasih kepada Reilly Grant dan Joe Medley atas ulasan mereka dalam artikel ini. Foto pabrik pesawat oleh Birmingham Museums Trust di Unsplash.