Met de Web Serial API kunnen websites communiceren met seriële apparaten.
Wat is de Web Serial API?
Een seriële poort is een bidirectionele communicatie-interface waarmee gegevens byte voor byte kunnen worden verzonden en ontvangen.
De Web Serial API biedt websites de mogelijkheid om met JavaScript te lezen van en te schrijven naar een serieel apparaat. Seriële apparaten worden aangesloten via een seriële poort op het systeem van de gebruiker of via verwisselbare USB- en Bluetooth-apparaten die een seriële poort emuleren.
Met andere woorden: de Web Serial API slaat een brug tussen het web en de fysieke wereld door websites te laten communiceren met seriële apparaten, zoals microcontrollers en 3D-printers.
Deze API is ook een goede aanvulling op WebUSB , omdat besturingssystemen vereisen dat applicaties met bepaalde seriële poorten communiceren via hun seriële API op een hoger niveau in plaats van via de USB API op een lager niveau.
Voorgestelde gebruiksscenario's
In het onderwijs, de hobbywereld en de industriële sector sluiten gebruikers randapparatuur aan op hun computers. Deze apparaten worden vaak aangestuurd door microcontrollers via een seriële verbinding die wordt gebruikt door software op maat. Sommige software op maat voor de aansturing van deze apparaten is gebouwd met webtechnologie:
In sommige gevallen communiceren websites met het apparaat via een agentapplicatie die gebruikers handmatig hebben geïnstalleerd. In andere gevallen wordt de applicatie geleverd als een pakketapplicatie via een framework zoals Electron. En in andere gevallen moet de gebruiker een extra stap uitvoeren, zoals het kopiëren van een gecompileerde applicatie naar het apparaat via een USB-stick.
In al deze gevallen wordt de gebruikerservaring verbeterd door directe communicatie tussen de website en het apparaat dat door de website wordt beheerd.
Huidige status
Stap | Status |
---|---|
1. Maak een uitleg | Compleet |
2. Maak een eerste ontwerp van de specificatie | Compleet |
3. Verzamel feedback en herhaal het ontwerp | Compleet |
4. Oorsprongsproef | Compleet |
5. Lancering | Compleet |
De Web Serial API gebruiken
Functiedetectie
Om te controleren of de Web Serial API wordt ondersteund, gebruikt u:
if ("serial" in navigator) {
// The Web Serial API is supported.
}
Open een seriële poort
De Web Serial API is asynchroon ontworpen. Dit voorkomt dat de gebruikersinterface van de website blokkeert tijdens het wachten op invoer. Dit is belangrijk omdat seriële data op elk moment kan worden ontvangen, wat een manier vereist om ernaar te luisteren.
Om een seriële poort te openen, moet u eerst een SerialPort
object openen. Hiervoor kunt u de gebruiker vragen één seriële poort te selecteren door navigator.serial.requestPort()
aan te roepen als reactie op een gebruikersgebaar, zoals een aanraking of muisklik, of er een kiezen via navigator.serial.getPorts()
, dat een lijst retourneert met seriële poorten waartoe de website toegang heeft gekregen.
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();
De functie navigator.serial.requestPort()
accepteert een optionele objectliteral die filters definieert. Deze worden gebruikt om elk serieel apparaat dat via USB is aangesloten te koppelen aan een verplichte USB-leverancier ( usbVendorId
) en optionele USB-product-ID's ( 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();

Door requestPort()
aan te roepen, wordt de gebruiker gevraagd een apparaat te selecteren en wordt een SerialPort
object geretourneerd. Zodra u een SerialPort
-object hebt, opent u de seriële poort port.open()
aan te roepen met de gewenste baudrate. Het baudRate
woordenboeklid specificeert hoe snel gegevens over een seriële lijn worden verzonden. Dit wordt uitgedrukt in bits per seconde (bps). Raadpleeg de documentatie van uw apparaat voor de juiste waarde, want alle gegevens die u verzendt en ontvangt, zullen onzin zijn als deze onjuist is opgegeven. Voor sommige USB- en Bluetooth-apparaten die een seriële poort emuleren, kan deze waarde veilig op elke gewenste waarde worden ingesteld, omdat deze door de emulatie wordt genegeerd.
// 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 });
U kunt ook een van de onderstaande opties opgeven bij het openen van een seriële poort. Deze opties zijn optioneel en hebben handige standaardwaarden .
-
dataBits
: Het aantal databits per frame (7 of 8). -
stopBits
: Het aantal stopbits aan het einde van een frame (1 of 2). -
parity
: De pariteitsmodus ("none"
,"even"
of"odd"
). -
bufferSize
: De grootte van de lees- en schrijfbuffers die moeten worden gemaakt (moet kleiner zijn dan 16 MB). -
flowControl
: De stroomregelmodus ("none"
of"hardware"
).
Lezen van een seriële poort
Invoer- en uitvoerstromen in de Web Serial API worden afgehandeld door de Streams API.
Nadat de seriële poortverbinding tot stand is gebracht, retourneren de readable
en writable
eigenschappen van het SerialPort
object een ReadableStream en een WritableStream . Deze worden gebruikt om gegevens te ontvangen van en te verzenden naar het seriële apparaat. Beide gebruiken Uint8Array
instanties voor gegevensoverdracht.
Wanneer er nieuwe gegevens van het seriële apparaat binnenkomen, retourneert port.readable.getReader().read()
asynchroon twee eigenschappen: de value
en een boolean done
. Als done
true is, is de seriële poort gesloten of komen er geen gegevens meer binnen. Door port.readable.getReader()
aan te roepen, wordt een lezer aangemaakt en wordt readable
hieraan gekoppeld. Zolang readable
vergrendeld is, kan de seriële poort niet worden gesloten.
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);
}
Sommige niet-fatale leesfouten in seriële poorten kunnen onder bepaalde omstandigheden optreden, zoals bufferoverloop, framingfouten of pariteitsfouten. Deze worden gegenereerd als uitzonderingen en kunnen worden opgevangen door een extra lus toe te voegen bovenop de vorige die port.readable
controleert. Dit werkt omdat er automatisch een nieuwe ReadableStream wordt aangemaakt, zolang de fouten niet-fataal zijn. Als er een fatale fout optreedt, bijvoorbeeld wanneer het seriële apparaat wordt verwijderd, wordt port.readable
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.
}
}
Als het seriële apparaat tekst terugstuurt, kunt u port.readable
via een TextDecoderStream
sturen, zoals hieronder weergegeven. Een TextDecoderStream
is een transformatiestroom die alle Uint8Array
fragmenten pakt en omzet in strings.
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);
}
Met een "Bring Your Own Buffer"-lezer kunt u bepalen hoe geheugen wordt toegewezen wanneer u uit de stream leest. Roep port.readable.getReader({ mode: "byob" })
aan om de ReadableStreamBYOBReader -interface te verkrijgen en uw eigen ArrayBuffer
op te geven wanneer read()
aanroept. De Web Serial API ondersteunt deze functie in Chrome 106 of hoger.
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()...
}
}
Hier is een voorbeeld van hoe u de buffer uit value.buffer
kunt hergebruiken:
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`.
}
Hier is nog een voorbeeld van hoe u een specifieke hoeveelheid gegevens van een seriële poort kunt lezen:
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);
Schrijf naar een seriële poort
Om gegevens naar een serieel apparaat te sturen, stuurt u de gegevens door naar port.writable.getWriter().write()
. Het aanroepen releaseLock()
op port.writable.getWriter()
is vereist om de seriële poort later te sluiten.
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();
Stuur tekst naar het apparaat via een TextEncoderStream
die is gekoppeld aan port.writable
, zoals hieronder weergegeven.
const textEncoder = new TextEncoderStream();
const writableStreamClosed = textEncoder.readable.pipeTo(port.writable);
const writer = textEncoder.writable.getWriter();
await writer.write("hello");
Sluit een seriële poort
port.close()
sluit de seriële poort als de readable
en writable
leden ontgrendeld zijn, wat betekent dat releaseLock()
is aangeroepen voor hun respectievelijke lezer en schrijver.
await port.close();
Bij het continu lezen van gegevens van een serieel apparaat met behulp van een lus, wordt port.readable
echter altijd vergrendeld totdat er een fout optreedt. In dit geval zorgt het aanroepen reader.cancel()
ervoor dat reader.read()
onmiddellijk wordt omgezet in { value: undefined, done: true }
, waardoor de lus reader.releaseLock()
kan aanroepen.
// 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;
});
Het sluiten van een seriële poort is ingewikkelder bij het gebruik van transform streams . Roep reader.cancel()
aan zoals eerder. Roep vervolgens writer.close()
en port.close()
. Dit propageert fouten via de transform streams naar de onderliggende seriële poort. Omdat foutpropagatie niet direct plaatsvindt, moet u de eerder gemaakte promises readableStreamClosed
en writableStreamClosed
gebruiken om te detecteren wanneer port.readable
en port.writable
ontgrendeld zijn. Het annuleren van de reader
zorgt ervoor dat de stream wordt afgebroken; daarom moet u de resulterende fout opvangen en negeren.
// 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();
Luister naar verbinding en ontkoppeling
Als een seriële poort wordt aangestuurd door een USB-apparaat, kan dat apparaat worden aangesloten op of losgekoppeld van het systeem. Wanneer de website toestemming heeft gekregen om toegang te krijgen tot een seriële poort, moet deze de connect
en disconnect
bewaken.
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.
});
Omgaan met signalen
Nadat u de seriële poortverbinding tot stand hebt gebracht, kunt u de signalen die door de seriële poort worden weergegeven, expliciet opvragen en instellen voor apparaatdetectie en datastroomregeling. Deze signalen worden gedefinieerd als Booleaanse waarden. Sommige apparaten, zoals Arduino, gaan bijvoorbeeld naar de programmeermodus als het Data Terminal Ready (DTR)-signaal wordt ingeschakeld.
Het instellen van uitgangssignalen en het ophalen van ingangssignalen gebeurt respectievelijk door port.setSignals()
en port.getSignals()
aan te roepen. Zie onderstaande gebruiksvoorbeelden.
// 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}`);
Het transformeren van stromen
Wanneer u gegevens van het seriële apparaat ontvangt, ontvangt u niet noodzakelijkerwijs alle gegevens in één keer. Deze kunnen willekeurig verdeeld zijn. Zie Streams API-concepten voor meer informatie.
Om dit aan te pakken, kunt u ingebouwde transformatiestromen gebruiken, zoals TextDecoderStream
, of uw eigen transformatiestroom creëren waarmee u de inkomende stroom kunt parseren en geparseerde gegevens kunt retourneren. De transformatiestroom bevindt zich tussen het seriële apparaat en de leeslus die de stroom gebruikt. Deze kan een willekeurige transformatie toepassen voordat de gegevens worden verbruikt. Zie het als een lopende band: terwijl een widget langs de productielijn komt, wijzigt elke stap in de productielijn de widget, zodat deze tegen de tijd dat deze zijn eindbestemming bereikt, een volledig functionerende widget is.

Denk bijvoorbeeld aan het maken van een transform stream-klasse die een stream gebruikt en deze opsplitst op basis van regeleinden. De transform()
-methode wordt aangeroepen telkens wanneer er nieuwe gegevens door de stream worden ontvangen. Deze kan de gegevens in de wachtrij plaatsen of opslaan voor later. De flush()
-methode wordt aangeroepen wanneer de stream wordt gesloten en verwerkt alle gegevens die nog niet zijn verwerkt.
Om de Transform Stream-klasse te gebruiken, moet je een inkomende stream erdoorheen leiden. In het derde codevoorbeeld onder 'Lezen van een seriële poort' werd de oorspronkelijke invoerstream alleen via een TextDecoderStream
geleid, dus moeten we pipeThrough()
aanroepen om deze via onze nieuwe LineBreakTransformer
te leiden.
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();
Voor het debuggen van communicatieproblemen met seriële apparaten gebruikt u de tee()
-methode van port.readable
om de streams die van of naar het seriële apparaat gaan te splitsen. De twee gegenereerde streams kunnen onafhankelijk van elkaar worden gebruikt, zodat u er één naar de console kunt sturen ter controle.
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.
Toegang tot een seriële poort intrekken
De website kan de rechten voor toegang tot een seriële poort die niet langer behouden moet worden, opschonen door forget()
aan te roepen op de SerialPort
instantie. Bijvoorbeeld, voor een educatieve webapplicatie die gebruikt wordt op een gedeelde computer met veel apparaten, zorgt een groot aantal verzamelde, door de gebruiker gegenereerde rechten voor een slechte gebruikerservaring.
// Voluntarily revoke access to this serial port.
await port.forget();
Omdat forget()
beschikbaar is in Chrome 103 of later, controleer of deze functie wordt ondersteund met het volgende:
if ("serial" in navigator && "forget" in SerialPort.prototype) {
// forget() is supported.
}
Ontwikkelaarstips
U kunt de Web Serial API in Chrome eenvoudig debuggen met behulp van de interne pagina about://device-log
Daar ziet u alle gebeurtenissen met betrekking tot seriële apparaten op één plek.

Codelab
In de Google Developer-codelab gebruikt u de Web Serial API om te communiceren met een BBC micro:bit- bord om afbeeldingen weer te geven op de 5x5 LED-matrix.
Browserondersteuning
De Web Serial API is beschikbaar op alle desktopplatforms (ChromeOS, Linux, macOS en Windows) in Chrome 89.
Polyfill
Op Android is ondersteuning voor USB-gebaseerde seriële poorten mogelijk via de WebUSB API en de Serial API polyfill . Deze polyfill is beperkt tot hardware en platforms waarop het apparaat toegankelijk is via de WebUSB API, omdat deze niet is geclaimd door een ingebouwde apparaatdriver.
Beveiliging en privacy
De auteurs van de specificaties hebben de Web Serial API ontworpen en geïmplementeerd met behulp van de kernprincipes die zijn gedefinieerd in 'Toegang tot krachtige webplatformfuncties beheren' , waaronder gebruikerscontrole, transparantie en ergonomie. De mogelijkheid om deze API te gebruiken, wordt primair beperkt door een toestemmingsmodel dat toegang verleent tot slechts één serieel apparaat tegelijk. Naar aanleiding van een prompt van de gebruiker moet de gebruiker actieve stappen ondernemen om een specifiek serieel apparaat te selecteren.
Als u de beveiligingsaspecten wilt begrijpen, raadpleeg dan de secties over beveiliging en privacy in de Web Serial API Explainer.
Feedback
Het Chrome-team hoort graag uw mening en ervaringen met de Web Serial API.
Vertel ons over het API-ontwerp
Werkt er iets in de API niet zoals verwacht? Of ontbreken er methoden of eigenschappen die u nodig hebt om uw idee te implementeren?
Dien een spec-probleem in op de Web Serial API GitHub-repository of voeg uw mening toe over een bestaand probleem.
Meld een probleem met de implementatie
Heb je een bug gevonden in de implementatie van Chrome? Of wijkt de implementatie af van de specificatie?
Meld een bug op https://new.crbug.com . Zorg ervoor dat u zoveel mogelijk details verstrekt, eenvoudige instructies geeft voor het reproduceren van de bug en dat Components is ingesteld op Blink>Serial
.
Toon steun
Bent u van plan de Web Serial API te gebruiken? Uw publieke steun helpt het Chrome-team bij het prioriteren van functies en laat andere browserleveranciers zien hoe belangrijk het is om deze te ondersteunen.
Stuur een tweet naar @ChromiumDev met de hashtag #SerialAPI
en laat ons weten waar en hoe je het gebruikt.
Nuttige links
- Specificatie
- Tracking-bug
- ChromeStatus.com-vermelding
- Knippercomponent:
Blink>Serial
Demo's
Dankbetuigingen
Met dank aan Reilly Grant en Joe Medley voor hun recensies van dit artikel. Foto van de vliegtuigfabriek door Birmingham Museums Trust op Unsplash .