Web Serial API memungkinkan situs berkomunikasi dengan perangkat serial.
Apa itu Web Serial API?
Port serial adalah antarmuka komunikasi dua arah yang memungkinkan pengiriman dan penerimaan data byte per byte.
Web Serial API menyediakan cara bagi situs 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 port serial.
Dengan kata lain, Web Serial API menjembatani web dan dunia fisik dengan memungkinkan situs berkomunikasi dengan perangkat serial, seperti pengontrol mikro dan printer 3D.
API ini juga merupakan pendamping yang baik untuk WebUSB karena sistem operasi memerlukan aplikasi untuk berkomunikasi dengan beberapa port serial menggunakan API seri level yang lebih tinggi, bukan USB API level rendah.
Kasus penggunaan yang disarankan
Di sektor pendidikan, penghobi, dan industri, pengguna menghubungkan perangkat periferal ke komputer mereka. Perangkat ini sering kali dikendalikan oleh mikrokontroler melalui koneksi serial yang digunakan oleh perangkat lunak khusus. Beberapa software kustom untuk mengontrol perangkat ini dibuat dengan teknologi web:
Dalam beberapa kasus, situs berkomunikasi dengan perangkat melalui aplikasi agen yang diinstal pengguna secara manual. Sementara itu, aplikasi dikirimkan dalam aplikasi terpaket melalui framework seperti Electron. Di sisi lain, pengguna harus melakukan langkah tambahan seperti menyalin aplikasi yang dikompilasi ke perangkat melalui flash drive USB.
Dalam semua kasus ini, pengalaman pengguna akan ditingkatkan dengan menyediakan komunikasi langsung antara situs dan perangkat yang dikontrolnya.
Status saat ini
Langkah | Status |
---|---|
1. Buat pesan penjelasan | Selesai |
2. Membuat draf awal spesifikasi | Selesai |
3. Mengumpulkan 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 memiliki desain yang asinkron. Hal ini mencegah UI situs memblokir saat menunggu input, yang merupakan hal penting karena data serial dapat diterima kapan saja, sehingga memerlukan cara untuk memprosesnya.
Untuk membuka port serial, akses objek SerialPort
terlebih dahulu. Untuk melakukannya, Anda dapat
meminta pengguna untuk memilih satu port serial dengan memanggil
navigator.serial.requestPort()
sebagai respons terhadap gestur pengguna seperti
sentuhan atau klik mouse, atau memilih satu dari navigator.serial.getPorts()
yang menampilkan
daftar port serial yang aksesnya diberikan kepada situs.
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 menentukan filter. ID tersebut digunakan untuk mencocokkan perangkat serial apa pun yang terhubung melalui
USB dengan vendor USB wajib (usbVendorId
) dan ID produk USB
opsional (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();
![Screenshot perintah port serial di situs](https://developer.chrome.com/static/docs/capabilities/serial/image/screenshot-a-serial-port-a8c295a2d99b6.jpg?authuser=19&hl=id)
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 port serial. Anggota kamus baudRate
menentukan seberapa cepat data dikirim melalui saluran serial. Nilai ini dinyatakan dalam
satuan bit per detik (bps). Periksa dokumentasi perangkat untuk
mendapatkan nilai yang benar, karena semua data yang Anda kirim dan terima akan berupa nonsens jika
ditetapkan dengan tidak benar. Untuk beberapa perangkat USB dan Bluetooth yang mengemulasi port
seri, nilai ini dapat ditetapkan 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 opsi di bawah saat membuka port serial. Opsi ini bersifat opsional dan memiliki nilai default yang mudah.
dataBits
: Jumlah bit data per frame (7 atau 8).stopBits
: Jumlah bit perhentian di akhir frame (1 atau 2).parity
: Mode paritas ("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
Streaming input dan output di Web Serial API ditangani oleh Streams API.
Setelah koneksi port serial dibuat, properti readable
dan writable
dari objek SerialPort
akan menampilkan ReadableStream dan
WritableStream. Metode tersebut akan digunakan untuk menerima data dari dan mengirim data ke
perangkat serial. Keduanya menggunakan instance Uint8Array
untuk transfer data.
Jika data baru tiba dari perangkat serial, port.readable.getReader().read()
akan menampilkan dua properti secara asinkron: boolean value
dan done
. Jika
done
bernilai benar, port serial telah ditutup atau tidak ada lagi data yang
masuk. Memanggil port.readable.getReader()
akan membuat pembaca dan mengunci readable
ke
pembaca. Saat readable
dikunci, 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 beberapa kondisi seperti
buffer overflow, error framing, atau error paritas. Hal tersebut ditampilkan sebagai pengecualian dan dapat ditangkap dengan menambahkan loop lain di atas loop sebelumnya yang memeriksa port.readable
. Cara ini berfungsi karena selama error bersifat
non-fatal, ReadableStream baru akan dibuat secara otomatis. Jika terjadi error fatal, seperti perangkat serial yang dihapus, port.readable
akan 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 mengirim teks kembali, Anda dapat menyalurkan port.readable
melalui
TextDecoderStream
seperti yang ditunjukkan di bawah. TextDecoderStream
adalah aliran transformasi
yang mengambil semua bagian 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 stream menggunakan pembaca "Bring Your Own Buffer". Panggil port.readable.getReader({ mode: "byob" })
untuk mendapatkan antarmuka ReadableStreamBYOBReader dan menyediakan 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 ini contoh lain cara membaca jumlah data 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()
. Memanggil releaseLock()
di
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();
Kirim teks ke perangkat melalui TextEncoderStream
yang disalurkan ke port.writable
seperti yang 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
pembaca dan penulis masing-masing.
await port.close();
Namun, saat terus membaca data dari perangkat serial menggunakan loop,
port.readable
akan selalu dikunci hingga mengalami error. Dalam hal ini, memanggil reader.cancel()
akan memaksa reader.read()
untuk segera di-resolve dengan { value: undefined, done: true }
, sehingga memungkinkan loop 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 jika menggunakan aliran transformasi. Panggil reader.cancel()
seperti sebelumnya.
Lalu, panggil writer.close()
dan port.close()
. Hal ini menyebarkan error melalui
aliran transformasi ke port serial yang mendasarinya. Karena propagasi error
tidak terjadi dengan segera, Anda harus menggunakan promise readableStreamClosed
dan
writableStreamClosed
yang dibuat sebelumnya untuk mendeteksi kapan port.readable
dan port.writable
telah dibuka. Membatalkan reader
akan menyebabkan
streaming dibatalkan. Itulah sebabnya Anda harus menangkap dan mengabaikan error 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, perangkat tersebut dapat dihubungkan
atau terputus dari sistem. Jika situs telah diberi izin untuk mengakses port serial, situs tersebut harus 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 port serial, Anda dapat secara eksplisit membuat kueri dan menetapkan sinyal yang diekspos oleh port serial untuk deteksi perangkat dan kontrol alur. Sinyal ini ditentukan sebagai nilai boolean. Misalnya, beberapa perangkat seperti Arduino akan memasuki mode pemrograman jika sinyal Data Terminal Ready (DTR) dialihkan.
Menetapkan sinyal output dan mendapatkan sinyal input masing-masing dilakukan dengan
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}`);
Mengubah streaming
Saat menerima data dari perangkat serial, Anda belum tentu mendapatkan semua data sekaligus. Data tersebut dapat dipotong secara acak. Untuk mengetahui informasi selengkapnya, lihat Konsep Streams API.
Untuk mengatasinya, Anda dapat menggunakan beberapa aliran transformasi bawaan seperti
TextDecoderStream
, atau membuat aliran transformasi sendiri yang memungkinkan Anda
mengurai aliran yang masuk dan menampilkan data yang diurai. Aliran transformasi berada
di antara perangkat serial dan loop baca yang menggunakan streaming tersebut. Alat ini dapat
menerapkan transformasi arbitrer sebelum data digunakan. Anggap saja seperti
jalur perakitan: saat widget menurun, setiap langkah dalam garis tersebut mengubah
widget, sehingga pada saat mencapai tujuan akhir, widget tersebut adalah
widget yang berfungsi penuh.
![Foto pabrik pesawat](https://developer.chrome.com/static/docs/capabilities/serial/image/photo-an-aeroplane-facto-b003fc6025223.jpg?authuser=19&hl=id)
Misalnya, pertimbangkan cara membuat class stream transformasi yang menggunakan
streaming dan memilahnya berdasarkan jeda baris. Metode transform()
dipanggil
setiap kali data baru diterima oleh aliran data. Itu dapat mengantrekan data atau
menyimpannya untuk nanti. Metode flush()
dipanggil saat aliran data ditutup, dan
menangani data yang belum diproses.
Untuk menggunakan class stream transformasi, Anda perlu menyalurkan aliran masuk melalui
ini. Pada contoh kode ketiga di bagian Read from a serial port,
aliran input asli hanya disalurkan melalui TextDecoderStream
, sehingga kita
perlu memanggil pipeThrough()
untuk menyalurkannya melalui LineBreakTransformer
baru.
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()
dari
port.readable
untuk memisahkan streaming ke atau dari perangkat serial. Kedua
stream yang dibuat dapat digunakan secara independen dan dengan demikian Anda dapat 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 membersihkan izin untuk mengakses port serial yang tidak lagi
perlu dipertahankan dengan memanggil forget()
pada instance SerialPort
. Misalnya, untuk aplikasi web pendidikan yang digunakan di komputer bersama dengan banyak
perangkat, akumulasi izin yang dibuat pengguna dalam jumlah besar akan memberikan pengalaman
pengguna yang buruk.
// Voluntarily revoke access to this serial port.
await port.forget();
Karena forget()
tersedia di Chrome 103 atau yang lebih baru, periksa apakah fitur ini
didukung dengan hal berikut:
if ("serial" in navigator && "forget" in SerialPort.prototype) {
// forget() is supported.
}
Tips Developer
Proses debug Web Serial API di Chrome mudah dilakukan dengan halaman internal,
about://device-log
tempat Anda dapat melihat semua peristiwa terkait perangkat serial di
satu tempat.
![Screenshot halaman internal untuk men-debug Web Serial API.](https://developer.chrome.com/static/docs/capabilities/serial/image/screenshot-the-internal-d465b2f3272a8.jpg?authuser=19&hl=id)
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) pada Chrome 89.
Isi Ulang
Di Android, dukungan untuk port serial berbasis USB dimungkinkan menggunakan WebUSB API dan polyfill Serial API. Polyfill ini terbatas untuk hardware dan platform tempat perangkat dapat diakses melalui WebUSB API karena belum diklaim oleh driver perangkat bawaan.
Keamanan dan privasi
Penulis spesifikasi telah mendesain dan menerapkan Web Serial API menggunakan prinsip inti yang ditentukan dalam Mengontrol Akses ke Fitur Platform Web yang Canggih, termasuk kontrol pengguna, transparansi, dan ergonomi. Kemampuan untuk menggunakan API ini terutama dibatasi oleh model izin yang memberikan akses hanya ke satu perangkat serial pada satu waktu. Sebagai respons atas perintah pengguna, pengguna harus melakukan langkah-langkah aktif untuk memilih perangkat serial tertentu.
Untuk memahami konsekuensi keamanan, lihat bagian keamanan dan privasi di Web Serial API Explainer.
Masukan
Tim Chrome ingin mengetahui pendapat dan pengalaman Anda saat menggunakan Web Serial API.
Beri tahu kami tentang desain API
Apakah ada sesuatu pada API yang tidak berfungsi seperti yang diharapkan? Atau apakah ada metode atau properti yang hilang yang Anda perlukan untuk menerapkan ide Anda?
Laporkan masalah spesifikasi di repo GitHub Web Serial API atau tambahkan ide Anda ke masalah yang sudah ada.
Melaporkan masalah terkait penerapan
Apakah Anda menemukan bug pada implementasi Chrome? Atau apakah implementasinya berbeda dengan spesifikasi?
Laporkan bug di https://new.crbug.com. Pastikan Anda menyertakan
detail sebanyak mungkin, memberikan petunjuk sederhana untuk mereproduksi bug, dan menyetel
Komponen ke Blink>Serial
. Glitch berfungsi dengan baik untuk
berbagi repro dengan cepat dan mudah.
Tampilkan dukungan
Apakah Anda berencana menggunakan Web Serial API? Dukungan publik Anda membantu tim Chrome memprioritaskan fitur dan menunjukkan kepada vendor browser lain betapa pentingnya mendukung mereka.
Kirim tweet ke @ChromiumDev menggunakan hashtag
#SerialAPI
dan beri tahu kami di mana dan bagaimana Anda menggunakannya.
Link bermanfaat
- Spesifikasi
- Bug pelacakan
- Entri ChromeStatus.com
- Komponen Blink:
Blink>Serial
Demo
Ucapan terima kasih
Terima kasih kepada Reilly Grant dan Joe Medley atas ulasan mereka tentang artikel ini. Foto pabrik pesawat oleh Birmingham Museums Trust di Unsplash.