Por sorprendente que parezca una web puede controlar un Arduino a través de su puerto serie para ello se puede usar la API WebSerial que por ahora solo funciona correctamente en Chrome y Edge. Para no montar todo desde cero usaremos el framework P5.js y su editor web que permite programar una web de forma muy parecida a programar en Arduino (ambos están inspirados en Processing y su entorno de desarrollo) y la librería p5.webserial. Eso lo podemos hacer incluyendo las siguientes librerías:
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.1.9/p5.min.js"
integrity="sha512-WIklPM6qPCIp6d3fSSr90j+1unQHUOoWDS4sdTiR8gxUTnyZ8S2Mr8e10sKKJ/bhJgpAa/qG068RDkg6fIlNFA=="
crossorigin="anonymous"></script>
<script src="https://unpkg.com/p5-webserial@0.1.1/build/p5.webserial.js"></script>
<script src="sketch.js"></script>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>p5.webserial</title>
</head>
</html>
Vamos a construir un control para mover el brazo robot que ya explique en este otro post. Para ellos crearemos una web con controles que nos permitan mover cada servo, llevarlo a la posición inicial y mandar el comando que queramos al brazo:

Vamos a centrarnos en la parte del código que controla la conexión con Arduino, que es el siguiente:
//Crear el objeto Serial
const serial = new p5.WebSerial();
//enviar datos al puerto serie
function send(cmd){
console.log(cmd);
serial.write(cmd);
}
//leer datos del puerto serie
function serialEvent() {
let readSerialStr = serial.readLine();
trim(readSerialStr);
if (readSerialStr) {
console.log(readSerialStr);
}
}
//incializar la conexion serie
function inicializarSerial() {
if (!navigator.serial) {
alert("WebSerial no esta sorportado en este navegador. Prueba en Chrome o Edge.");
}
serial.getPorts();
serial.on("noport", showPortButton);
serial.on("portavailable", openPort);
serial.on("requesterror", portError);
serial.on("data", serialEvent);
serial.on("close", closePort);
navigator.serial.addEventListener("connect", portConnect);
navigator.serial.addEventListener("disconnect", portDisconnect);
}
// Muestra la ventana de seldccion de puerto
function choosePort() {
showPortButton();
serial.requestPort();
}
//abrir conexion con puerto serie
function openPort() {
console.log("Abriendo puerto serie");
serial.open().then(initiateSerial);
function initiateSerial() {
console.log("Puerto serie abierto");
}
hidePortButton();
}
//Cerrar conexion con puerto serie
function closePort() {
console.log("Puerto serie cerrado");
serial.close();
showPortButton();
}
//Error con el puerto serie
function portError(err) {
alert("Serial port error: " + err);
showPortButton();
}
//Evento puerto serie conectado
function portConnect() {
console.log("Puerto serie conectado");
serial.getPorts();
hidePortButton()
}
//Evento puerto serie desconectado
function portDisconnect() {
serial.close();
console.log("Puerto serie desconectado");
showPortButton();
}
function showPortButton() {
portButton.show();
}
function hidePortButton() {
portButton.hide();
}
Lo primero es inicializar el objeto WebSerial para poder trabajar con él.
inicializarSerial(): Esta función se encarga de inicializar la conexión serie. Primero, verifica si el navegador admite la API Web Serial utilizando el objeto navigator.serial. Si el navegador no admite la API, muestra una alerta. Si la API está disponible, establece varios eventos para manejar la apertura, cierre, errores y desconexiones del puerto serie. Finalmente, agrega eventos para detectar cuándo se conecta o desconecta un puerto serie:
- «noport»: Este evento se activa cuando no se detecta ningún puerto serial disponible en el sistema. En este caso, se llama a la función showPortButton() para mostrar un botón que permita al usuario seleccionar un puerto.
- «portavailable»: Este evento se activa cuando se detecta un puerto serial disponible en el sistema. En este caso, se llama a la función openPort() para abrir la conexión con el puerto.
- «requesterror»: Este evento se activa cuando se produce un error al intentar acceder al puerto serial. En este caso, se llama a la función portError() para mostrar un mensaje de error al usuario.
- «data»: Este evento se activa cuando se recibe un dato del puerto serial. En este caso, se llama a la función serialEvent() para procesar el dato recibido.
- «close»: Este evento se activa cuando se cierra la conexión con el puerto serial. En este caso, se llama a la función closePort() para cerrar la conexión.
- «connect»: Este evento se activa cuando se establece una conexión con un puerto serial. En este caso, se llama a la función portConnect() para detectar los puertos disponibles.
- «disconnect»: Este evento se activa cuando se pierde la conexión con un puerto serial. En este caso, se llama a la función portDisconnect() para cerrar la conexión y mostrar un botón para seleccionar otro puerto.
serialEvent(): Esta función se ejecuta cada vez que se recibe un mensaje del dispositivo conectado a través del puerto serie. Primero, lee la cadena recibida utilizando la función serial.readLine(). Si la cadena leída no está vacía, muestra la cadena en la consola del navegador utilizando console.log(readSerialStr). Lo usaremos para mostrar los datos enviados por el puerto serie.
choosePort(): Esta función muestra una ventana de selección de puerto para que el usuario pueda elegir un puerto serie disponible. La ventana se muestra llamando a la función showPortButton().
openPort(): Esta función se encarga de abrir la conexión con el puerto serie seleccionado. Utiliza la función serial.open() para abrir la conexión serie. Si la conexión se abre con éxito, se llama a la función initiateSerial() para inicializar la conexión serie.
closePort(): Esta función se encarga de cerrar la conexión con el puerto serie actual. Utiliza la función serial.close() para cerrar la conexión serie.
portError(err): Esta función se llama si se produce un error durante la conexión serie. Muestra una alerta con el mensaje de error
portConnect(): Esta función se llama cuando se detecta que un puerto serie se ha conectado. Llama a la función serial.getPorts() para obtener la lista actualizada de puertos serie disponibles.
portDisconnect(): Esta función se llama cuando se detecta que un puerto serie se ha desconectado. Cierra la conexión con el puerto serie actual llamando a la función serial.close().
send(cmd): Esta función toma un argumento cmd
que representa los datos que se deben enviar al dispositivo conectado a través del puerto serie. Envía los datos al puerto serie utilizando la función serial.write(cmd).
Uniendo todas las piezas
Unamos todas las piezas, para ello usaremos la función send(cmd) para enviar los comando que controlan el funcionamiento del brazo (más info aqui) al pulsar cada botón.
Código JS (sketch.js):
//Crear el objeto Serial
const serial = new p5.WebSerial();
let portButton;
let cmdInput;
let posicionBase = 90;
let posicionHombro = 90;
let posicionCodo = 120;
let posicionMano = 10;
function setup() {
createCanvas(400, 400);
portButton = createButton("Elegir puerto");
portButton.position(10, 10);
portButton.mousePressed(choosePort);
inicializarSerial();
// Grupo Base
crearGrupoBotones("Base", 1, 50, 50, () => {return posicionBase+=5;}, () => {return posicionBase-=5;});
// Grupo Hombro
crearGrupoBotones("Hombro", 3, 50, 120, () => {return posicionHombro+=5;}, () => {return posicionHombro-=5;});
// Grupo Codo
crearGrupoBotones("Codo", 2, 50, 190, () => {return posicionCodo+=5;}, () => {return posicionCodo-=5;});
// Grupo Mano
crearGrupoBotones("Mano", 4, 50, 260, () => {return posicionMano+=5;}, () => {return posicionMano-=5;});
let botonApagar = createButton("Pos. Inicial");
botonApagar.position(175, 300);
botonApagar.mousePressed(() => {
send("Q");
posicionBase = 90;
posicionHombro = 90;
posicionCodo = 120;
posicionMano = 10;
});
let inputCmd = createInput();
inputCmd.position(170, 360);
inputCmd.size(60);
botonCmd = createButton('->');
botonCmd.position(240, 360);
botonCmd.mousePressed(() => {send(inputCmd.value())});
}
function draw() {
background(220);
// Mostrar los valores actuales de las posiciones
textSize(20);
text("Base: " + posicionBase, 150, 60);
text("Hombro: " + posicionHombro, 150, 130);
text("Codo: " + posicionCodo, 150, 200);
text("Mano: " + posicionMano, 150, 270);
}
function crearGrupoBotones(nombre, servo, x, y, funcSumar, funcRestar) {
textSize(20);
text(nombre, x, y);
//Sumar
let botonSumar = createButton("+");
botonSumar.position(x + 75, y);
botonSumar.mousePressed(() => {
send("S"+servo+":"+funcSumar());
});
//Restar
let botonRestar = createButton("-");
botonRestar.position(x + 225, y);
botonRestar.mousePressed(() => {
send("S"+servo+":"+funcRestar());
});
}
//enviar datos al puerto serie
function send(cmd){
console.log(cmd);
serial.write(cmd+"\n");
}
//leer datos del puerto serie
function serialEvent() {
let readSerialStr = serial.readLine();
trim(readSerialStr);
if (readSerialStr) {
console.log(readSerialStr);
}
}
//incializar la conexion serie
function inicializarSerial() {
if (!navigator.serial) {
alert("WebSerial no esta sorportado en este navegador. Prueba en Chrome o Edge.");
}
serial.getPorts();
serial.on("noport", showPortButton);
serial.on("portavailable", openPort);
serial.on("requesterror", portError);
serial.on("data", serialEvent);
serial.on("close", closePort);
navigator.serial.addEventListener("connect", portConnect);
navigator.serial.addEventListener("disconnect", portDisconnect);
}
// Muestra la ventana de seldccion de puerto
function choosePort() {
showPortButton();
serial.requestPort();
}
//abrir conexion con puerto serie
function openPort() {
console.log("Abriendo puerto serie");
serial.open().then(initiateSerial);
function initiateSerial() {
console.log("Puerto serie abierto");
}
hidePortButton();
}
//Cerrar conexion con puerto serie
function closePort() {
console.log("Puerto serie cerrado");
serial.close();
showPortButton();
}
//Error con el puerto serie
function portError(err) {
alert("Serial port error: " + err);
showPortButton();
}
//Evento puerto serie conectado
function portConnect() {
console.log("Puerto serie conectado");
serial.getPorts();
hidePortButton()
}
//Evento puerto serie desconectado
function portDisconnect() {
serial.close();
console.log("Puerto serie desconectado");
showPortButton();
}
function showPortButton() {
portButton.show();
}
function hidePortButton() {
portButton.hide();
}
Puedes ver la demostración de este artículo en el siguiente vídeo de mi canal de Youtube:
Y si estas interesado en el tema puedes ver la lista de vídeos.
Pingback: Controlar Arduino con la voz | Construyendo a Chispas