Tutorial

Cómo crear un monitor serial web

Comunica una página HTML con placas ESP32, Arduino y otros dispositivos mediante la Web Serial API.

Un monitor serial permite observar los mensajes que una placa envía por USB y, cuando hace falta, devolverle comandos desde la computadora. En este tutorial construiremos una versión que funciona directamente en el navegador, donde el usuario selecciona el puerto, elige la velocidad, recibe datos de manera asíncrona y puede enviar texto con distintos finales de línea. El resultado es una herramienta útil para depurar firmware, inspeccionar sensores y controlar prototipos sin instalar una aplicación de escritorio.

Por qué usar un monitor serial en el navegador

Cuando se programa un ESP32, una placa Arduino o cualquier microcontrolador con comunicación serial, la primera ventana hacia el dispositivo suele ser el monitor serial. El firmware puede imprimir estados de inicialización, lecturas de sensores, mensajes de error y resultados de pruebas. Esa salida convierte en observable un programa que, de otro modo, estaría ejecutándose dentro de una placa sin una interfaz visible.

Las herramientas tradicionales, como el monitor de Arduino IDE, PlatformIO o una terminal del sistema, cumplen muy bien esa función. Sin embargo, una interfaz web puede ser conveniente cuando se desea compartir una herramienta, integrarla con documentación o combinar la consola con controles gráficos. Un mismo documento HTML puede mostrar botones, campos de entrada, gráficas y comandos específicos para el experimento.

La página utilizada como referencia para este artículo es la de Serial Studio. Se trata de un monitor bidireccional que muestra lo que llega desde la placa y también permite escribir mensajes hacia ella.

Interfaz de Serial Studio
Interfaz de Serial Studio.

Qué es la Web Serial API

La Web Serial API es una interfaz del navegador que permite leer y escribir bytes en dispositivos que el sistema operativo presenta como puertos seriales. Una placa conectada mediante USB suele aparecer como un puerto serial, aunque internamente utilice un conversor USB-UART o una interfaz USB nativa.

La página no recibe los datos a través de un servidor. El navegador solicita permiso para acceder al puerto local y mantiene la comunicación directamente con el dispositivo conectado a la computadora. Esta arquitectura permite publicar la herramienta como una página estática, siempre que se cumplan las condiciones de seguridad del navegador.

El flujo general tiene cuatro momentos:

  1. El usuario pulsa Conectar y selecciona un puerto.
  2. La página abre el puerto con la velocidad de transmisión elegida.
  3. Un lector asíncrono obtiene los bytes y los convierte en texto.
  4. El usuario puede enviar texto mediante un escritor y cerrar la conexión cuando termina.

Requisitos

Para utilizar el monitor se necesita lo siguiente:

El acceso al puerto siempre requiere una decisión explícita del usuario. La llamada navigator.serial.requestPort() debe ejecutarse como consecuencia de una acción, como pulsar un botón. La página no puede seleccionar silenciosamente cualquier dispositivo conectado.

Un firmware mínimo para probar la conexión

Antes de escribir el monitor, conviene tener un programa pequeño que produzca una salida reconocible y responda a un mensaje. Este ejemplo utiliza funciones disponibles tanto en Arduino como en muchas variantes de ESP32:

El firmware inicia el puerto a 115200 baudios, envía una señal inicial y después produce una línea por segundo. También escucha los datos recibidos y devuelve un eco. El monitor no necesita conocer el significado de cada línea: puede mostrar texto arbitrario y dejar que una capa posterior lo interprete como CSV, JSON o un protocolo propio.

En un proyecto real es importante que cada mensaje tenga un formato definido. Por ejemplo, un sensor podría transmitir IMU,0.012,-0.034,0.997 y una interfaz especializada podría separar sus campos para construir una gráfica o una tabla.

La estructura HTML de la interfaz

La interfaz necesita un selector para los baudios, botones de conexión, un área de consola y controles para enviar información. Los identificadores permiten que JavaScript encuentre cada elemento y modifique su estado.

En la página publicada, los controles de envío se desactivan hasta que existe una conexión. Así se evita intentar escribir en un puerto que todavía no está abierto. La consola utiliza textContent y white-space: pre-wrap para conservar los saltos de línea sin interpretar el texto recibido como HTML.

Detectar compatibilidad y solicitar el puerto

El primer paso consiste en detectar la API. Si no existe, el botón de conexión se desactiva y la consola muestra un mensaje que orienta al usuario. Si existe, la selección del puerto se solicita dentro del evento de clic:

requestPort() devuelve una promesa. Cuando el usuario elige un dispositivo, el resultado es un objeto SerialPort. La llamada open() inicia la comunicación con la velocidad indicada. Si el firmware trabaja a 9600 y la página abre el puerto a 115200, los bytes se interpretarán incorrectamente y la consola mostrará caracteres ilegibles.

Leer el puerto como un flujo asíncrono

La llegada de datos no debe detener la interfaz. Web Serial utiliza Streams API, por lo que el monitor obtiene un lector y espera los datos con await reader.read(). El navegador resuelve esa espera cada vez que llega un nuevo fragmento.

Un detalle importante es que reader.read() no necesariamente devuelve una línea completa. Puede entregar solo una parte de ella o varias líneas juntas. Por eso la implementación mostrada es adecuada para una consola de texto, pero una aplicación que necesite analizar mensajes debe acumular los fragmentos y separar las tramas mediante un delimitador, normalmente \n.

Enviar datos hacia la placa

La comunicación serial es bidireccional. Para escribir se obtiene temporalmente un escritor, se convierte el texto a bytes con TextEncoder y se libera el escritor después de terminar. El final de línea se agrega según la selección del usuario.

El terminador debe coincidir con lo que espera el firmware. Muchas implementaciones basadas en readStringUntil('\n') requieren nueva línea; otras esperan retorno de carro o ambos caracteres. Ofrecer las tres opciones evita tener que modificar el monitor cada vez que cambia el protocolo.

Desconectar y liberar recursos

Cerrar la conexión requiere detener la lectura pendiente, liberar el lector y cerrar el puerto. Si se omite este paso, otra aplicación puede encontrar el dispositivo ocupado hasta que se cierre la pestaña o se desconecte el cable.

reader.cancel() interrumpe la espera de lectura y permite que el bloque finally libere el lector. Después, port.close() cierra la comunicación. La función toggleUI() reactiva el selector de baudios y deshabilita los controles que requieren un puerto abierto.

Conservar una interfaz clara

La lógica de comunicación debe mantenerse separada de la presentación. La consola solo muestra texto; los botones cambian el estado de la conexión; y una función independiente, como appendLog(), centraliza el desplazamiento automático:

La salida se agrega mediante textContent en lugar de innerHTML. Así, si la placa envía símbolos como < o una cadena que parece una etiqueta, el navegador la muestra como texto y no la interpreta como contenido de la página.

Adaptar el monitor a sensores y comandos

Una vez establecida la comunicación, el monitor puede convertirse en la base de una interfaz más específica. Hay dos estrategias frecuentes:

Por ejemplo, si el firmware envía TEMP,24.6, el código puede separar el nombre de la variable y su valor. Para mensajes JSON, JSON.parse() permite convertir cada línea en un objeto. En ambos casos conviene validar el formato antes de actualizar la interfaz y mostrar los errores en la consola.

La misma conexión puede utilizarse para enviar comandos: cambiar el periodo de muestreo, activar un actuador, solicitar una lectura inmediata o modificar parámetros de calibración. El protocolo debe documentar claramente los comandos, sus argumentos y la respuesta esperada.

Problemas frecuentes

Seguridad y límites prácticos

El permiso se concede por sitio y por dispositivo. El usuario debe seleccionar el puerto desde el diálogo del navegador, y la página solo obtiene acceso a los dispositivos autorizados. Aun así, una página con permiso puede enviar datos a la placa, por lo que conviene conectarse únicamente a sitios de confianza y revisar el código antes de conceder acceso.

Web Serial no convierte automáticamente cualquier USB en un puerto serial. La placa debe exponer una interfaz compatible con esta API y el sistema operativo debe reconocerla. Tampoco establece por sí sola un protocolo de mensajes: la velocidad, los terminadores, el formato de las tramas y la interpretación de los datos son decisiones del proyecto.

Conclusión

Un monitor serial web combina una interfaz HTML sencilla con dos ideas esenciales de JavaScript: las promesas para abrir y cerrar el puerto, y los streams para leer datos sin bloquear la página. Con un firmware que envíe líneas bien definidas, esta base puede crecer hasta convertirse en una consola de depuración, un panel de sensores o una interfaz de control para un prototipo.

Referencias