The API serial permite que los sitios Web se comuniquen con dispositivos seriales.
Updated
What is the serial API?
A serial port is a bidirectional communication interface that allows you to send and receive data byte by byte.
La API serial proporciona una forma para que los sitios web lean y escriban en un dispositivo serial con JavaScript. Los dispositivos serie se conectan a través de un puerto serie en el sistema del Username o mediante dispositivos USB y Bluetooth extraíbles que emulan un puerto serie.
In other words, the serial API bridges the web and the physical world by allowing websites to communicate with serial devices, such as microcontrollers and 3D printers.
This API is also a great companion for WebUSB as operating systems require applications to communicate with some serial ports using their native top-level serial API instead of the low-level USB API.
This article reflects the Serial API implemented in Chrome 86 and later. Some property names have changed from previous versions.
Suggested use cases
En los sectores educativo, aficionado e industrial, los usuarios conectan dispositivos periféricos a sus computadoras. Estos dispositivos a menudo son controlados por microcontroladores a través de una conexión en serie utilizada por software personalizado. Algún software personalizado para controlar estos dispositivos está construido con tecnología web:
En algunos casos, los sitios web se comunican con el dispositivo a través de una aplicación de agente nativo que los usuarios instalaron manualmente. En otros, la aplicación se entrega en una aplicación nativa empaquetada a través de un marco como Electron. Y en otros, el usuario debe realizar un paso adicional, como copiar una aplicación compilada al dispositivo a través de una unidad flash USB.
In all these cases, the user experience will be enhanced by providing direct communication between the website and the device it is controlling.
Actual state
Using the serial API
Enable support during the proof of origin phase
The Serial API is available on all desktop platforms (Chrome OS, Linux, macOS, and Windows) as a proof of origin on Chrome 80. The proof of origin is expected to finish just before Chrome 89 is set in February 2021 The API can also be enabled by a flag.
Origin testing allows you to test new features and provide feedback on their usability, practicality, and effectiveness to the web standards community. For more information, see the Origin testing guide for web developers. To enroll in this or any other proof of origin, visit the registration page.
Register for proof of origin
- Request a token by your origin.
- Add the token to your pages. There are two ways to do it:
Enabling via chrome: // flags
To experiment with the serial API locally on all desktop platforms, without a source test token, enable the #experimental-web-platform-features
flag on
chrome://flags
.
Feature detection
To check if the serial API is supported, use:
if ("serial" in navigator) {
}
Open a serial port
La API serial es asincrónica por diseño. Esto evita que la user interface del sitio web se bloquee cuando se espera una entrada, lo cual es importante porque los datos en serie se pueden recibir en cualquier momento, lo que requiere una forma de escucharlos.
To open a serial port, first access a SerialPort
object. To do this, you can ask the user to select a single serial port by calling
navigator.serial.requestPort ()
or choose one of navigator.serial.getPorts ()
which returns a list of serial ports that the website has previously accessed.
const port = await navigator.serial.requestPort();
const ports = await navigator.serial.getPorts();
the navigator.serial.requestPort ()
La función toma un literal de objeto opcional que define filters. Se utilizan para hacer coincidir cualquier dispositivo serie conectado a través de USB con un proveedor USB obligatorio (usbVendorId
) and optional USB product identifiers (usbProductId
).
const filters = [
{ usbVendorId: 0x2341, usbProductId: 0x0043 },
{ usbVendorId: 0x2341, usbProductId: 0x0001 }
];
const port = await navigator.serial.requestPort({ filters });
const { usbProductId, usbVendorId } = port.getInfo();
Vocation requestPort ()
prompts the user to select a device and returns a
SerialPort
object. Once you have a SerialPort
object calling port.open ()
with the desired baud rate the serial port will open. the baudRate
The dictionary member specifies how fast data is sent over a serial line. It is expressed in units of bits per second (bps). Check your device documentation for the correct value as all data you send and receive will be gibberish if this is specified incorrectly. For some USB and Bluetooth devices that emulate a serial port, this value can be safely set to any value as it is ignored by the emulation.
const port = await navigator.serial.requestPort();
await port.open({ baudRate: 9600 });
You can also specify any of the following options when opening a serial port. These options are optional and have predetermined values.
dataBits
: The number of data bits per frame (7 or 8).stopBits
: The number of stop bits at the end of a frame (1 or 2).parity
: El modo de paridad (ya be"none"
,"even"
or"odd"
).bufferSize
: The size of the read and write buffers to create (must be less than 16 MB).flowControl
: The flow control mode (either"none"
or"hardware"
).
Read from a serial port
The input and output streams in the serial API are handled by the Streams API.
If the streams are new to you, see Streams API concepts. This article just touches the surface of streams and their management.
Once the serial port connection is established, readable
and writable
properties of SerialPort
object returns a ReadableStream and a
WritableStream. They will be used to receive data and send data to the serial device. They both use Uint8Array
instances for data transfer.
When new data arrives from the serial device, port.readable.getReader (). read ()
returns two properties asynchronously: the value
and a done
boolean. Yes
done
it's true, the serial port has been closed or no more data is coming in. Calling port.readable.getReader ()
create a reader and block readable
it. While readable
it is locked, the serial port cannot be closed.
const reader = port.readable.getReader();
while (true) {
const { value, done } = await reader.read();
if (done) {
reader.releaseLock();
break;
}
console.log(value);
}
Some non-fatal serial port read errors can occur under some conditions, such as buffer overflow, framing errors, or parity errors. Those are thrown as exceptions and can be caught by adding another loop on top of the previous one that checks port.readable
. This works because as long as the errors are not fatal, a new ReadableStream it is created automatically. If a fatal error such as serial device removal occurs, port.readable
becomes null.
while (port.readable) {
const reader = port.readable.getReader();
try {
while (true) {
const { value, done } = await reader.read();
if (done) {
reader.releaseLock();
break;
}
if (value) {
console.log(value);
}
}
} catch (error) {
}
}
If the serial device sends text back, you can pipe port.readable
through a
TextDecoderStream
As shown below. A TextDecoderStream
is a transform current
that grabs everything Uint8Array
fragments and turns them into strings.
const textDecoder = new TextDecoderStream();
const readableStreamClosed = port.readable.pipeTo(textDecoder.writable);
const reader = textDecoder.readable.getReader();
while (true) {
const { value, done } = await reader.read();
if (done) {
reader.releaseLock();
break;
}
console.log(value);
}
Write to a serial port
To send data to a serial device, pass the data to
port.writable.getWriter (). write ()
. Vocation releaseLock ()
in
port.writable.getWriter ()
it is necessary for the serial port to be closed later.
const writer = port.writable.getWriter();const data = new Uint8Array([104, 101, 108, 108, 111]);
await writer.write(data);
writer.releaseLock();
Send text to the device through a TextEncoderStream
channeled to port.writable
As shown below.
const textEncoder = new TextEncoderStream();
const writableStreamClosed = textEncoder.readable.pipeTo(port.writable);const writer = textEncoder.writable.getWriter();
await writer.write("hello");
Close a serial port
port.close ()
close the serial port if your readable
and writable
The members are unlocked, sense releaseLock ()
It has been summoned by its respective reader and writer.
await port.close();
However, when continuously reading data from a serial device using a loop,
port.readable
it will always be blocked until it encounters an error. In this case, calling reader.cancel ()
force to reader.read ()
to solve immediately with {value: undefined, done: true}
and thus allowing the loop to call reader.releaseLock ()
.
const reader = port.readable.getReader();
while (true) {
const { value, done } = await reader.read();
if (done) {
reader.releaseLock();
break;
}
console.log(value);
}
await reader.cancel();
await port.close();
Closing a serial port is more complicated when using it transform streams (I like it
TextDecoderStream
and TextEncoderStream
). Call reader.cancel ()
like before. Then call writer.close ()
and port.close ()
. This propagates the errors through the transform streams to the underlying serial port. Because error propagation does not happen immediately, you should use the readableStreamClosed
and
writableStreamClosed
previously created promises to detect when port.readable
and port.writable
have been unlocked. Cancel the reader
causes the sequence to be aborted; that's why you have to catch and ignore the resulting error.
const textDecoder = new TextDecoderStream();
const readableStreamClosed = port.readable.pipeTo(textDecoder.writable);
const reader = textDecoder.readable.getReader();
while (true) {
const { value, done } = await reader.read();
if (done) {
reader.releaseLock();
break;
}
console.log(value);
}
const textEncoder = new TextEncoderStream();
const writableStreamClosed = textEncoder.readable.pipeTo(port.writable);
reader.cancel();
await readableStreamClosed.catch(() => { });
writer.close();
await writableStreamClosed;
await port.close();
Hear the connection and disconnection
If a USB device provides a serial port, then that device can be connected to or disconnected from the system. When the website has been granted permission to access a serial port, it should monitor the connect
and disconnect
events.
navigator.serial.addEventListener("connect", (event) => {
});navigator.serial.addEventListener("disconnect", (event) => {
});
Handle signals
Después de establecer la conexión del puerto serie, puede consultar y configurar explícitamente las señales expuestas por el puerto serie para la detección de dispositivos y el control de flujo. Estas señales se definen como valores booleanos. Por ejemplo, algunos dispositivos como Arduino entrarán en un modo de programming si se activa la señal Data Terminal Ready (DTR).
Adjustment exit signs and get input signals are made respectively by calling port.setSignals ()
and port.getSignals ()
. See the usage examples below.
await port.setSignals({ break: false });
await port.setSignals({ dataTerminalReady: true });
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}`);
Transforming streams
When you receive data from the serial device, you will not necessarily get all the data at once. It can be arbitrarily fragmented. For more information, see
Streams API concepts.
To deal with this, you can use some built-in transform streams, like
TextDecoderStream
or create your own transformation flow that allows you to analyze the incoming flow and return analyzed data. The transform stream is between the serial device and the read loop that is consuming the stream. You can apply an arbitrary transformation before the data is consumed. Think of it like an assembly line - as a widget moves down the line, each step on the line modifies the widget so that when it reaches its final destination, it is a fully functioning widget.
For example, consider how to create a transform stream class that consumes a stream and chunks it based on line breaks. Their transform ()
the method is called each time the flow receives new data. You can queue the data or save it for later. the flush ()
The method is called when the stream is closed and handles any data that has not yet been processed.
To use the transform stream class, you must pipe an incoming stream through it. In the third code example in Read from a serial port, the original input stream was only piped through a TextDecoderStream
then we have to call pipeThrough ()
to channel it through our new LineBreakTransformer
.
class LineBreakTransformer {
constructor() {
Este.chunks = "";
}transform(chunk, controller) {
Este.chunks += chunk;
const lines = Este.chunks.split("rn");
Este.chunks = lines.pop();
lines.forEach((line) => controller.enqueue(line));
}
flush(controller) {
controller.enqueue(Este.chunks);
}
}
const textDecoder = new TextDecoderStream();
const readableStreamClosed = port.readable.pipeTo(textDecoder.writable);
const reader = textDecoder.readable
.pipeThrough(new TransformStream(new LineBreakTransformer()))
.getReader();
To debug serial device communication problems, use the tee()
method of
port.readable
to split the transmissions going to or from the serial device. The two created flows can be consumed independently and this allows you to print one on the console for inspection.
const [appReadable, devReadable] = port.readable.tee();
Codelab
At Google Developer Code Lab, you will use the serial API to interact with a
BBC micro: bit board to display images on its 5 × 5 LED matrix.
Polyfill
On Android, USB-based serial ports can be supported through the WebUSB API and Polyfill API series. Este polyfill está limitado a hardware y plataformas donde se puede acceder al dispositivo a través de la API WebUSB porque no ha sido reclamado por un controlador de dispositivo integrado.
Security and privacy
The specification authors have designed and implemented the serial API using the basic principles defined in Control access to powerful features of the web platform, including user control, transparency and ergonomics. The ability to use this API is primarily controlled by a permissions model that grants access to only one serial device at a time. In response to a user request, the user must take active steps to select a particular serial device.
To understand the security tradeoffs, see the security and privacy
Serial API Explainer sections.
Feedback
The Chrome team would love to hear your thoughts and experiences with the Serial API.
Tell us about the API design
Is there something in the API that is not working as expected? Or are you missing any methods or properties you need to implement your idea?
File a spec issue on the Serial API GitHub repository or add your thoughts to an existing problem.
Report a problem with the deployment
Found a bug with the Chrome implementation? Or is the implementation different from the specification?
File a bug in https://new.crbug.com. Be sure to include as much detail as you can, provide simple instructions for reproducing the bug, and
Components adjusted to Blink> Serial
. Failure works great for quick and easy sharing of reps.
Show support
¿Está planeando utilizar la API serial? Su apoyo público ayuda al equipo de Chrome a priorizar funciones y muestra a otros proveedores de browsers lo importante que es brindarles soporte.
Send a tweet to @Cromodev with the hashtag
#SerialAPI
and let us know where and how you are using it.
Helpful Links
Population:
Thanks
Thanks to Reilly Scholarship and Joe medley for their reviews of this article. Aircraft factory photo by Birmingham Museums Trust in Unsplash.