Este proyecto tiene como finalidad reconocer si un peso se encuentra dentro o fuera de un rango, ingresando valores de un peso predeterminado y una tolerancia mínima y máxima.
El sistema toma información en tiempo real desde un puerto serial (COM), que envía datos de peso y unidad de medida, los procesa y muestra su estado visualmente en la interfaz.
- Visualización de puestos de balanzas.
- Detección de peso dentro o fuera del rango permitido.
- Apertura y cierre de puertos COM desde la interfaz.
- Detección automática de puertos disponibles.
- Comunicación en tiempo real mediante Socket.IO.
- El trabajo se divide en dos partes principales:
- Servidor – serverv2.js Es el backend del sistema, encargado de manejar la comunicación con la balanza real a través del puerto COM. Desde aquí se detectan los puertos disponibles, se abren o cierran conexiones y se procesan los datos que llegan en tiempo real desde la balanza (peso y unidad de medida).
- Cliente – index.html y script.js El archivo index.html muestra la interfaz visual con los puestos de balanza y sus valores en tiempo real. Por otro lado, script.js se encarga de la comunicación entre el cliente y el servidor, utilizando Socket.IO. En este archivo están las funcionalidades específicas para verificar si el peso está dentro del rango permitido, manejar los eventos de los sockets, actualizar el color de los puestos y controlar la apertura o cierre de puertos desde la interfaz.
- El servidor detecta los puertos COM disponibles usando la librería serialport.
- Cuando el usuario selecciona un puerto y abre la conexión al enviar con esto los datos de peso determinado y la tolerancia, el servidor empieza a leer los datos enviados por la balanza.
- Esos datos (peso y unidad) se envían al cliente mediante un socket.
- En el frontend, se procesa el peso recibido y se compara con el peso objetivo y la tolerancia ingresados.
- Si el valor está dentro del rango permitido → la pantalla se vuelve verde. Vista de interfaz
- Si está fuera → se vuelve roja. Vista de interfaz
En esta pantalla podemos visualizar que se pone verde cuando el peso está dentro del rango de diferencia o suma de la tolerancia permitida
En esta pantalla podemos visualizar que se pone roja cuando el peso está fuera del rango de diferencia o suma de la tolerancia permitida
En script.js, el cliente recibe los valores emitidos por el servidor y cambia el color de fondo según si el peso está dentro o fuera del rango:
socket.emit("abrirPuerto", puertoSeleccionado);
socket.on("peso", (data) => {
console.log("Abierto");
console.log("puestos", puestos);
if (puestos[puestoId.id] && puestos[puestoId.id].puerto === data.puerto) {
const pesoElement = document.getElementById(`peso${puestoId.id}`);
const valor = data.valor;
const unidad = data.unidad;
pesoElement.innerText = valor.toFixed(1) + unidad;
const { defecto, diferencia } = puestos[puestoId.id];
if (valor > defecto - diferencia && valor < defecto + diferencia) {
pesoElement.style.backgroundColor = "#61D864";
pesoElement.style.color = "#449646";
} else {
pesoElement.style.backgroundColor = "#FF625B";
pesoElement.style.color = "#BE4843";
}
}
});Qué hace:
- Llama al evento de abrir el puerto
- Escucha los datos del servidor.
- Muestra el peso actual.
- Cambia el color dependiendo del resultado.
Este fragmento del servidor (serverv2.js) muestra cómo se abre un puerto COM y se procesan los datos recibidos desde la balanza:
function abrirPuerto(puertoCOM) {
const puerto = new SerialPort({ path: puertoCOM, baudRate: 9600 }, (err) => {
if (err) {
console.log(`Error al abrir puerto ${puertoCOM}: ${err.message}`);
io.emit('statusCOM', { puerto: puertoCOM, estado: false });
return;
}
});
const parser = puerto.pipe(new ReadlineParser({ delimiter: '\n' }));
conexiones[puertoCOM] = {
puerto,
parser,
estado: false
};
puerto.on('open', () => {
console.log(`Puerto ${puertoCOM} abierto`);
conexiones[puertoCOM].estado = true;
io.emit('statusCOM', { puerto: puertoCOM, estado: true });
puerto.write("Z\r\n");
puerto.write("CP\r\n");
});
parser.on('data', (data) => {
const receivedData = data.trim();
const match = receivedData.match(/(-?\d+(\.\d+)?)/);
if (match) {
const peso = parseFloat(match[0]);
// Emitimos indicando de qué puerto viene
io.emit('peso', { puerto: puertoCOM, valor: peso, unidad: 'g' });
}
});
}Qué hace:
- Abre el puerto seleccionado.
- Lee los datos enviados por la balanza.
- Los convierte a número y los envía al cliente por Socket.IO.
El sistema detecta automáticamente los puertos disponibles y los actualiza cada segundo:
setInterval(async () => {
const listaPuertos = await listarPuertosCOM();
if (JSON.stringify(listaPuertos) !== JSON.stringify(puertosPrevios)) {
puertosPrevios = listaPuertos;
io.emit('puertosCOM', listaPuertos);
console.log("puertos actualizada:", listaPuertos);
}
}, 1000);Qué hace:
- Verifica constantemente si hay cambios en los puertos conectados por alguna conexión o desconexión en tiempo real.
- Actualiza la lista en el frontend sin necesidad de recargar la página.
Recibe del servidor la lista de puertos COM disponibles y los muestra en el menú desplegable para que el usuario pueda seleccionarlos:
function obtenerPuertos() {
socket.on("puertosCOM", (listaPuertos) => {
const select = document.getElementById("puertoCOMSelect");
select.innerHTML = "";
if (listaPuertos.length === 0) {
select.innerHTML = '<option value="">no hay puertos</option>';
} else {
listaPuertos.forEach(puerto => {
select.innerHTML += `<option value="${puerto}">${puerto}</option>`;
});
}
});
}
obtenerPuertos()Qué hace:
- Muestra los puertos COM disponibles en la interfaz para que el usuario pueda elegir uno.
- Si no hay puertos disponibles, muestra un mensaje indicando “no hay puertos”.
- Si hay puertos disponibles, los agrega como opciones seleccionables en la interfaz.
- Actualiza la lista automáticamente cuando el servidor envía cambios.
El HTML muestra cada “puesto” de balanza, con el valor del peso y el botón de configuración:
<div class="puesto" id="P#">
<div class="titulo">
<button class="btn" onclick="abrir(P#)">
<img src="Assest/Group 4013.png" class="ajustes">
</button>
<span>PUESTO #</span>
</div>
<div class="cuerpo" id="pesoP1">0.0</div>
</div>Qué hace:
- Cada “puesto” representa una balanza conectada.
- Cuando el usuario la configura, se abre una ventana para elegir el puerto, peso predeterminado y tolerancia.
Esta parte del servidor se encarga de manejar el cierre de la conexión con el puerto COM. Hay dos formas en que puede ocurrir el cierre:
- Cierre automático: Se ejecuta cuando el puerto se cierra por causas externas (por ejemplo, si se desconecta la balanza). Actualiza el estado del puerto, avisa al cliente y elimina la conexión.
puerto.on('close', () => {
console.log(`Puerto ${puertoCOM} cerrado`);
conexiones[puertoCOM].estado = false;
io.emit('statusCOM', { puerto: puertoCOM, estado: false });
delete conexiones[puertoCOM];
});- Cierre manual: Permite cerrar el puerto manualmente desde el servidor (por ejemplo, si el usuario lo solicita desde la interfaz).
function cerrarPuerto(puertoCOM) {
if (conexiones[puertoCOM] && conexiones[puertoCOM].puerto.isOpen) {
conexiones[puertoCOM].puerto.close();
console.log(`Puerto ${puertoCOM} cerrado manualmente`);
}
}Qué hace:
- Cierra la conexión entre el servidor y el puerto COM.
- Detiene el envío de datos hacia el cliente.
- Actualiza el estado del puerto para que el sistema sepa que ya no está activo.
- Evita que se sigan recibiendo lecturas de peso de la balanza.
En el frontend, se realiza un cierre manual del puerto COM cuando el usuario decide finalizar la conexión con un puesto (desde el lugar de configuración). Esto detiene la recepción de datos y limpia la interfaz visual correspondiente.
buttoncerrar.addEventListener('click', function() {
if (puestoActivo !== null) {
finalizar(puestoActivo);
puestoActivo = null;
} else {
const error = document.getElementById("error");
error.innerText = `No hay puerto abierto`;
console.log("no hay puesto");
}
});
function finalizar(puestoId) {
if (!puestos[puestoId]) return;
const puerto = puestos[puestoId].puerto;
socket.emit("cerrarPuerto", puerto);
console.log("Puerto cerrado:", puerto);
const pesoElement = document.getElementById(`peso${puestoId}`);
pesoElement.innerText = "0.0";
pesoElement.style.backgroundColor = "#02194A";
pesoElement.style.color = "white";
delete puestos[puestoId];
document.getElementById("caja").style.display = "none";
document.getElementById("opacidad").style.opacity = "1";
}Qué hace:
- Comprueba si hay un puerto abierto
- Llama a la funcion finalizar() en caso que si
- Llama al servidor para cerrar el puerto COM manualmente.
- Detiene la recepción de datos de ese puesto.
- Limpia la interfaz visual del puesto, dejando el valor en 0 y restaurando los colores por defecto.
- Elimina la referencia al puesto del registro interno del cliente (puestos).
En esta parte se manejan las conexiones con el cliente y se envían los datos necesarios para que el frontend pueda interactuar con los puertos COM.
io.on('connection', async (socket) => {
console.log('Cliente conectado:', socket.id);
// Enviar la lista de puertos disponibles
const listaPuertos = await listarPuertosCOM();
socket.emit('puertosCOM', listaPuertos);
// Abrir puerto
socket.on('abrirPuerto', (puertoSeleccionado) => {
abrirPuerto(puertoSeleccionado);
});
// Cerrar puerto
socket.on('cerrarPuerto', (puertoSeleccionado) => {
cerrarPuerto(puertoSeleccionado);
});
// Detectar desconexión del cliente
socket.on('disconnect', () => {
console.log('Cliente desconectado:', socket.id);
});
});¿Qué hace?
- Envía al cliente la lista de puertos COM disponibles.
- Permite al cliente abrir y cerrar puertos desde la interfaz.
- Mantiene actualizada la conexión con cada cliente y detecta cuando se desconecta.
Para convertir el proyecto en una aplicación de escritorio utilizamos Electron. Electron permite empaquetar una aplicación web (HTML, CSS y JavaScript) junto con un servidor Node.js, de forma que el sistema pueda ejecutarse directamente desde una computadora sin necesidad de usar el navegador o instalar dependencias adicionales.
De esta manera, la aplicación puede funcionar como un programa independiente, que se abre con doble clic, manteniendo la comunicación en tiempo real con las balanzas y ofreciendo una interfaz moderna y accesible.
¿Dónde manejamos esto? Main.js
Qué hace:
- Crea una ventana de la aplicación.
- Carga la interfaz web (index.html) dentro de esa ventana.
- Mantiene el servidor corriendo en segundo plano, comunicándose con los puertos y el cliente por Socket.IO.