L'API Web Serial consente ai siti web di comunicare con i dispositivi seriali.
Che cos'è l'API Web Serial?
Una porta seriale è un'interfaccia di comunicazione bidirezionale che consente l'invio e la ricezione di dati byte per byte.
L'API Web Serial consente ai siti web di leggere e scrivere su un dispositivo seriale con JavaScript. I dispositivi seriali sono collegati tramite una porta seriale sul sistema dell'utente o tramite dispositivi USB e Bluetooth rimovibili che emulano una porta seriale.
In altre parole, l'API Web Serial fa da ponte tra il web e il mondo fisico consentendo ai siti web di comunicare con dispositivi seriali, come microcontrollori e stampanti 3D.
Questa API è anche un ottimo complemento di WebUSB, in quanto i sistemi operativi richiedono alle applicazioni di comunicare con alcune porte seriali utilizzando la propria API seriale di livello superiore anziché l'API USB di basso livello.
Casi d'uso suggeriti
Nei settori dell'istruzione, degli hobbisti e dell'industria, gli utenti collegano dispositivi periferici ai propri computer. Questi dispositivi sono spesso controllati da microcontrollori tramite una connessione seriale utilizzata da software personalizzati. Alcuni software personalizzati per controllare questi dispositivi sono realizzati con tecnologia web:
In alcuni casi, i siti web comunicano con il dispositivo tramite un'applicazione agente che gli utenti hanno installato manualmente. In altri casi, l'applicazione viene distribuita in un'applicazione pacchettizzata tramite un framework come Electron. In altri casi, l'utente deve eseguire un passaggio aggiuntivo, ad esempio copiare un'applicazione compilata sul dispositivo tramite un'unità flash USB.
In tutti questi casi, l'esperienza utente verrà migliorata fornendo una comunicazione diretta tra il sito web e il dispositivo che controlla.
Stato attuale
Passaggio | Stato |
---|---|
1. Creare una spiegazione | Completato |
2. Crea la bozza iniziale delle specifiche | Completato |
3. Raccogli feedback e itera il design | Completato |
4. Prova dell'origine | Completato |
5. Avvia | Completato |
Utilizzo dell'API Web Serial
Rilevamento delle funzionalità
Per verificare se l'API Web Serial è supportata, utilizza:
if ("serial" in navigator) {
// The Web Serial API is supported.
}
Apri una porta seriale
L'API Web Serial è asincrona per progettazione. In questo modo, l'interfaccia utente del sito web non si blocca in attesa dell'input, il che è importante perché i dati seriali possono essere ricevuti in qualsiasi momento, richiedendo un modo per ascoltarli.
Per aprire una porta seriale, accedi prima a un oggetto SerialPort
. A questo scopo, puoi
chiedere all'utente di selezionare una singola porta seriale chiamando
navigator.serial.requestPort()
in risposta a un gesto dell'utente, ad esempio un tocco
o un clic del mouse, oppure sceglierne una da navigator.serial.getPorts()
, che restituisce
un elenco di porte seriali a cui è stato concesso l'accesso al sito 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();
La funzione navigator.serial.requestPort()
accetta un oggetto letterale facoltativo
che definisce i filtri. Questi vengono utilizzati per abbinare qualsiasi dispositivo seriale connesso tramite
USB con un fornitore USB obbligatorio (usbVendorId
) e identificatori di prodotto USB
facoltativi (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();

La chiamata a requestPort()
chiede all'utente di selezionare un dispositivo e restituisce un
oggetto SerialPort
. Una volta ottenuto un oggetto SerialPort
, la chiamata a port.open()
con la velocità di trasmissione desiderata aprirà la porta seriale. Il membro del dizionario baudRate
specifica la velocità di invio dei dati su una linea seriale. È espressa in
unità di bit al secondo (bps). Consulta la documentazione del dispositivo per
il valore corretto, poiché tutti i dati che invii e ricevi saranno incomprensibili se questo
valore non è specificato correttamente. Per alcuni dispositivi USB e Bluetooth che emulano una porta seriale, questo valore può essere impostato in modo sicuro su qualsiasi valore, in quanto viene ignorato dall'emulazione.
// 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 });
Puoi anche specificare una delle opzioni riportate di seguito quando apri una porta seriale. Queste opzioni sono facoltative e hanno comodi valori predefiniti.
dataBits
: il numero di bit di dati per frame (7 o 8).stopBits
: il numero di bit di stop alla fine di un frame (1 o 2).parity
: La modalità di parità ("none"
,"even"
o"odd"
).bufferSize
: le dimensioni dei buffer di lettura e scrittura da creare (deve essere inferiore a 16 MB).flowControl
: La modalità di controllo del flusso ("none"
o"hardware"
).
Leggere da una porta seriale
I flussi di input e output nell'API Web Serial vengono gestiti dall'API Streams.
Una volta stabilita la connessione alla porta seriale, le proprietà readable
e writable
dell'oggetto SerialPort
restituiscono uno ReadableStream e un
WritableStream. Verranno utilizzati per ricevere dati dal dispositivo seriale e inviarli a quest'ultimo. Entrambi utilizzano istanze Uint8Array
per il trasferimento dei dati.
Quando arrivano nuovi dati dal dispositivo seriale, port.readable.getReader().read()
restituisce in modo asincrono due proprietà: value
e un valore booleano done
. Se
done
è true, la porta seriale è stata chiusa o non sono più in arrivo dati. La chiamata port.readable.getReader()
crea un lettore e lo blocca su readable
. Mentre readable
è bloccato, la porta seriale non può essere chiusa.
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);
}
In alcune condizioni, ad esempio overflow del buffer, errori di framing o errori di parità, possono verificarsi alcuni errori di lettura della porta seriale non fatali. Questi vengono generati come
eccezioni e possono essere rilevati aggiungendo un altro ciclo sopra quello precedente
che controlla port.readable
. Ciò funziona perché, finché gli errori non sono
irreparabili, viene creato automaticamente un nuovo ReadableStream. Se si verifica un errore irreversibile, ad esempio la rimozione del dispositivo seriale, port.readable
diventa
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.
}
}
Se il dispositivo seriale restituisce testo, puoi inserire port.readable
tramite un
TextDecoderStream
come mostrato di seguito. Un TextDecoderStream
è un flusso di trasformazione
che acquisisce tutti i blocchi Uint8Array
e li converte in stringhe.
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);
}
Puoi controllare la modalità di allocazione della memoria quando leggi dallo stream utilizzando un lettore "Bring Your Own Buffer". Chiama port.readable.getReader({ mode: "byob" })
per ottenere l'interfaccia ReadableStreamBYOBReader e fornisci il tuo ArrayBuffer
quando chiami read()
. Tieni presente che l'API Web Serial supporta questa funzionalità in Chrome 106 o versioni successive.
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()...
}
}
Ecco un esempio di come riutilizzare il buffer di 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`.
}
Ecco un altro esempio di come leggere una quantità specifica di dati da una porta seriale:
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);
Scrivere su una porta seriale
Per inviare dati a un dispositivo seriale, passa i dati a
port.writable.getWriter().write()
. La chiamata a releaseLock()
su
port.writable.getWriter()
è necessaria per chiudere la porta seriale in un secondo momento.
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();
Invia il testo al dispositivo tramite TextEncoderStream
reindirizzato a port.writable
come mostrato di seguito.
const textEncoder = new TextEncoderStream();
const writableStreamClosed = textEncoder.readable.pipeTo(port.writable);
const writer = textEncoder.writable.getWriter();
await writer.write("hello");
Chiudere una porta seriale
port.close()
chiude la porta seriale se i relativi membri readable
e writable
sono sbloccati, il che significa che releaseLock()
è stato chiamato per il rispettivo
lettore e writer.
await port.close();
Tuttavia, quando si leggono continuamente i dati da un dispositivo seriale utilizzando un ciclo,
port.readable
verrà sempre bloccato finché non si verifica un errore. In questo
caso, la chiamata di reader.cancel()
forzerà reader.read()
a risolvere
immediatamente con { value: undefined, done: true }
, consentendo così al
ciclo di chiamare 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;
});
La chiusura di una porta seriale è più complicata quando si utilizzano flussi di trasformazione. Chiama reader.cancel()
come prima.
Poi chiama writer.close()
e port.close()
. Questo propaga gli errori attraverso
i flussi di trasformazione alla porta seriale sottostante. Poiché la propagazione degli errori
non avviene immediatamente, devi utilizzare le promesse readableStreamClosed
e
writableStreamClosed
create in precedenza per rilevare quando port.readable
e port.writable
sono state sbloccate. L'annullamento di reader
causa l'interruzione
del flusso, motivo per cui devi rilevare e ignorare l'errore risultante.
// 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();
Ascoltare la connessione e la disconnessione
Se una porta seriale è fornita da un dispositivo USB, questo dispositivo può essere connesso
o disconnesso dal sistema. Quando al sito web è stata concessa l'autorizzazione
per accedere a una porta seriale, deve monitorare gli eventi connect
e 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.
});
Indicatori del manubrio
Dopo aver stabilito la connessione alla porta seriale, puoi eseguire query e impostare in modo esplicito i segnali esposti dalla porta seriale per il rilevamento dei dispositivi e il controllo del flusso. Questi indicatori sono definiti come valori booleani. Ad esempio, alcuni dispositivi come Arduino entreranno in modalità di programmazione se il segnale Data Terminal Ready (DTR) viene attivato/disattivato.
L'impostazione dei segnali di output e l'ottenimento dei segnali di input vengono eseguiti rispettivamente
chiamando port.setSignals()
e port.getSignals()
. Vedi gli esempi di utilizzo riportati di seguito.
// 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}`);
Trasformare gli stream
Quando ricevi dati dal dispositivo seriale, non necessariamente riceverai tutti i dati contemporaneamente. Potrebbe essere suddiviso in blocchi arbitrari. Per saperne di più, consulta Concetti dell'API Streams.
Per risolvere questo problema, puoi utilizzare alcuni stream di trasformazione integrati, ad esempio
TextDecoderStream
, o creare il tuo stream di trasformazione che ti consenta di
analizzare lo stream in entrata e restituire i dati analizzati. Lo stream di trasformazione si trova
tra il dispositivo seriale e il ciclo di lettura che utilizza lo stream. Può
applicare una trasformazione arbitraria prima che i dati vengano utilizzati. Immagina una
catena di montaggio: man mano che un widget scende lungo la linea, ogni passaggio della linea lo modifica, in modo che quando arriva alla destinazione finale, sia un widget
completamente funzionante.

Ad esempio, considera come creare una classe di stream di trasformazione che consuma uno stream e lo suddivide in blocchi in base agli interruzioni di riga. Il relativo metodo transform()
viene chiamato
ogni volta che il flusso riceve nuovi dati. Può mettere in coda i dati o
salvarli per un secondo momento. Il metodo flush()
viene chiamato quando lo stream viene chiuso e
gestisce tutti i dati che non sono ancora stati elaborati.
Per utilizzare la classe di stream di trasformazione, devi convogliare uno stream in entrata
al suo interno. Nel terzo esempio di codice in Read from a serial port (Leggi da una porta seriale),
il flusso di input originale è stato inviato solo tramite TextDecoderStream
, quindi
dobbiamo chiamare pipeThrough()
per inviarlo tramite il nostro nuovo LineBreakTransformer
.
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();
Per eseguire il debug dei problemi di comunicazione con i dispositivi seriali, utilizza il metodo tee()
di
port.readable
per dividere i flussi in entrata o in uscita dal dispositivo seriale. I due
stream creati possono essere utilizzati in modo indipendente e ciò ti consente di stamparne uno
nella console per l'ispezione.
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.
Revocare l'accesso a una porta seriale
Il sito web può pulire le autorizzazioni di accesso a una porta seriale che non gli interessa più mantenere chiamando forget()
sull'istanza SerialPort
. Ad esempio, per un'applicazione web didattica utilizzata su un computer condiviso con molti dispositivi, un numero elevato di autorizzazioni generate dagli utenti accumulate crea un'esperienza utente scadente.
// Voluntarily revoke access to this serial port.
await port.forget();
Poiché forget()
è disponibile in Chrome 103 o versioni successive, verifica se questa funzionalità è
supportata con quanto segue:
if ("serial" in navigator && "forget" in SerialPort.prototype) {
// forget() is supported.
}
Suggerimenti per gli sviluppatori
Il debug dell'API Web Serial in Chrome è semplice con la pagina interna
about://device-log
, dove puoi visualizzare tutti gli eventi correlati ai dispositivi seriali in un unico
posto.

Codelab
Nel codelab per sviluppatori Google, utilizzerai l'API Web Serial per interagire con una scheda BBC micro:bit per mostrare immagini sulla sua matrice LED 5x5.
Supporto browser
L'API Web Serial è disponibile su tutte le piattaforme desktop (ChromeOS, Linux, macOS e Windows) in Chrome 89.
Polyfill
Su Android, il supporto delle porte seriali basate su USB è possibile utilizzando l'API WebUSB e il polyfill dell'API Serial. Questo polyfill è limitato all'hardware e alle piattaforme in cui il dispositivo è accessibile tramite l'API WebUSB perché non è stato rivendicato da un driver di dispositivo integrato.
Sicurezza e privacy
Gli autori delle specifiche hanno progettato e implementato l'API Web Serial utilizzando i principi fondamentali definiti in Controlling Access to Powerful Web Platform Features, tra cui controllo utente, trasparenza ed ergonomia. La possibilità di utilizzare questa API è principalmente regolata da un modello di autorizzazioni che concede l'accesso a un solo dispositivo seriale alla volta. In risposta a un prompt dell'utente, quest'ultimo deve eseguire passaggi attivi per selezionare un dispositivo seriale specifico.
Per comprendere i compromessi in termini di sicurezza, consulta le sezioni sicurezza e privacy della spiegazione dell'API Web Serial.
Feedback
Il team di Chrome vorrebbe conoscere la tua opinione e la tua esperienza con l'API Web Serial.
Descrivi la progettazione dell'API
C'è qualcosa nell'API che non funziona come previsto? Oppure mancano metodi o proprietà che ti servono per implementare la tua idea?
Invia una segnalazione relativa alle specifiche nel repository GitHub dell'API Web Serial o aggiungi i tuoi commenti a una segnalazione esistente.
Segnalare un problema relativo all'implementazione
Hai trovato un bug nell'implementazione di Chrome? O l'implementazione è diversa dalla specifica?
Invia una segnalazione di bug all'indirizzo https://new.crbug.com. Assicurati di includere il maggior numero di dettagli possibile, fornisci istruzioni semplici per riprodurre il bug e imposta Componenti su Blink>Serial
.
Mostrare il proprio sostegno
Intendi utilizzare l'API Web Serial? Il tuo supporto pubblico aiuta il team di Chrome a dare la priorità alle funzionalità e mostra ad altri fornitori di browser quanto sia fondamentale supportarle.
Invia un tweet a @ChromiumDev utilizzando l'hashtag
#SerialAPI
e facci sapere dove e come lo utilizzi.
Link utili
- Specifiche
- Bug di monitoraggio
- Voce di ChromeStatus.com
- Componente Blink:
Blink>Serial
Demo
Ringraziamenti
Grazie a Reilly Grant e Joe Medley per le loro recensioni di questo articolo. Foto della fabbrica di aeroplani di Birmingham Museums Trust su Unsplash.