Files
bascula-autonoma-totem/uibuilder/totem/src/index.js
T
yamaray 370ae5c414 Debido a nuevo requerimiento se introduce la opcion (configurable) de tener una camara de contexto en la bascula. Esta camara en caso de estar
habilitada en la configuracion tomara una imagen en el momento en que el camion este completamente en la bascula, para ello hemos introducido un codigo en el nodo
"estable" que es donde se hace la deteccion del camion completamente en la bascula. Posteriormente en el nodo "Analisis del modo de Operacion" es
donde verificamos que la imagen fue tomada y la colocamos en el registro que se esta haciendo para generar el movimiento correspondiente. El Grupo
"CAMARA CONTEXTO" posee los nodos de solicitud de imagen y de recepcion de la misma. La configuracion de IP de la camara se hace en la pantalla
de configuracion al igual que su habilitacion en el sistema.
- Como herramienta de ayuda para saber si el Totem esta en capacidad de solicitar y de recibir la imagen de contexto enviada por la camara
se ha incluido una funcion en el menu de configuracion -> Pruebas del sistema -> Pedir Snapshot de contexto de forma que el usuario pueda visualizar
la imagen actual que la camara esta enviando y pueda de esa forma confirmar el correcto funcionamiento de la conexión y de los equipos.
2026-03-25 17:18:29 +01:00

495 lines
18 KiB
JavaScript

/* global uibuilder */
uibuilder.start();
// Navegación entre pantallas
function mostrarPantalla(id) {
document.querySelectorAll('.pantalla').forEach(div =>
div.classList.toggle('visible', div.id === id)
);
}
let currentTopic = "";
let result = [];
//****************************************************RECEPCION DE MENSAJES DESDE NODE_RED ***************************************************************** */
uibuilder.onChange('msg', msg => {
if (msg.payload && msg.payload.pantalla) {
mostrarPantalla('pantalla-' + msg.payload.pantalla);
}
if (msg.semaforo) {
document.getElementById('estado').style.backgroundColor = msg.semaforo;
}
if (msg.payload && msg.payload.pantalla === 'snapshot') {
// Limpiar datos visibles
document.getElementById('camara').textContent = '---';
document.getElementById('placa').textContent = '----';
document.getElementById('fecha').textContent = '----';
// Mostrar imagen vacía
document.getElementById('foto').src = "";
}
if (msg.payload && msg.payload.pantalla === 'contexto') {
// Limpiar datos visibles
document.getElementById('camara_contexto').textContent = '---';
document.getElementById('fecha_contexto').textContent = '----';
// Mostrar imagen vacía
document.getElementById('foto_contexto').src = "";
}
if (msg.payload && msg.payload.pantalla === 'escuchar' && msg.buttons != true) {
// Limpiar datos visibles
document.getElementById('camara_escuchar').textContent = '---';
document.getElementById('placa_escuchar').textContent = '----';
document.getElementById('fecha_escuchar').textContent = '----';
document.getElementById('contador').textContent = `0 / 0`;
// Mostrar imagen vacía
document.getElementById('foto_escuchar').src = "";
}
// nuevo paara camara de contexto
if (msg.hasOwnProperty("picture_contexto")) {
if (msg.context_cam) {
document.getElementById('foto_contexto').src = msg.picture_contexto;
document.getElementById('fecha_contexto').textContent = msg.fecha_contexto || '';
document.getElementById('camara_contexto').textContent = "Contexto" || '---';
}
}
if (msg.hasOwnProperty("picture")) {
//uibuilder.send({ payload: msg.payload.Picture.Plate.PlateNumber, topic: msg.payload.Picture.SnapInfo.DeviceID });
if (msg.snapshot_cam) {
document.getElementById('foto').src = msg.picture;
document.getElementById('placa').textContent = msg.plate || '----';
document.getElementById('fecha').textContent = msg.plate_time || '';
document.getElementById('camara').textContent = msg.camera_id || '---';
}
else if (msg.escuchar_cam){
document.getElementById('foto_escuchar').src = msg.picture;
document.getElementById('placa_escuchar').textContent = msg.plate || '----';
document.getElementById('fecha_escuchar').textContent = msg.plate_time || '';
document.getElementById('camara_escuchar').textContent = msg.camera_id || '---';
const total = msg.snapshot_indexmax || 0;
const actual = (msg.snapshot_readindex || 0) + 1;
document.getElementById('contador').textContent = `${actual} / ${total}`;
}
}
else if (msg.payload?.pantalla === 'tipos') {
mostrarPantalla('pantalla-tipos');
if (msg.payload.tiposDatos) {
tiposDatos = msg.payload.tiposDatos;
}
}
if (msg.barrera) {
mostrarBarreraAbierta()
}
else{
mostrarBarreraCerrada()
}
// tabla dinamica
if (msg.topic == "general" || msg.topic == "conexion" || msg.topic == "camara" || msg.topic == "sensores" || msg.topic == "mensajes" || msg.topic == "tiempos"){
if (!msg.tabla || !Array.isArray(msg.tabla)) return;
const tbody = document.getElementById('config-table-body');
tbody.innerHTML = '';
currentTopic = msg.topic || 'general';
msg.tabla.forEach(item => { //generamos dinamixamente la tabla que vera el usuario en el dispositivo (PC o smartphone).
// notese que la celda 2 de cada fila es del tipo editable.
const row = document.createElement('tr');
const cell1 = document.createElement('td');
cell1.textContent = item.parametro;
const cell2 = document.createElement('td');
cell2.contentEditable = true;
cell2.textContent = item.valor;
row.appendChild(cell1);
row.appendChild(cell2);
tbody.appendChild(row); // enviamos la tabla al elemento html (config-table-body)
});
}
// configuracion del pop up de notificaciones
if (msg.modalText) {
document.getElementById('modal-text').textContent = msg.modalText;
const btnSi = document.getElementById('btn-modal-si');
const btnNo = document.getElementById('btn-modal-no');
if (msg.modalType === "notificacion") {
btnSi.textContent = 'OK';
btnNo.style.display = 'none';
window.modalCallback = null;
} else {
btnSi.textContent = 'Sí';
btnNo.style.display = 'inline-block';
window.modalCallback = msg.modalCallback || null;
}
document.getElementById('confirm-modal').style.display = 'block';
}
// ======================================== Datos para la Pantalla Principal ========================================================================
if (msg.hasOwnProperty("peso")) document.getElementById('peso').textContent = msg.peso || 0;
//if (msg.semaforo_color) document.getElementById('semaforo-color').style.backgroundColor = msg.semaforo_color;
const led = document.getElementById('semaforo-color');
if (msg.semaforo_color) {
led.style.backgroundColor = msg.semaforo_color;
led.classList.add('led');
} else {
led.style.backgroundColor = '#ccc';
led.classList.remove('led');
}
if (msg.hasOwnProperty("matricula")) document.getElementById('matricula').textContent = msg.matricula;
if (msg.hasOwnProperty("peso_estable_flag")) document.getElementById('peso-estable').textContent = msg.peso_estable_flag ? 'SI' : 'NO';
if (msg.hasOwnProperty("vial")) document.getElementById('vial').textContent = msg.vial;
if (msg.hasOwnProperty("totem_id")) document.getElementById('totem-id').textContent = msg.totem_id;
if (msg.hasOwnProperty("iplan")) document.getElementById('iplan').textContent = msg.iplan;
if (msg.hasOwnProperty("ipsupervisor")) document.getElementById('ipsupervisor').textContent = msg.ipsupervisor;
// ======== Sensor de distancia añadido por david========
if (msg.hasOwnProperty("distance")) {
const dist = msg.distance;
const dmin = msg.distance_min;
const maxRange = 4000;
document.getElementById('sensor-dist').textContent =
(dist !== null && dist !== undefined) ? dist + ' mm' : '--- mm';
if (dmin !== null && dmin !== undefined) {
document.getElementById('sensor-dmin').textContent = dmin + ' mm';
}
if (dist !== null && dist !== undefined) {
const fillPct = Math.min((dist / maxRange) * 100, 100);
const fill = document.getElementById('sensor-fill');
fill.style.width = fillPct + '%';
}
}
});
//****************************************************************************************************************************************************** */
// Detectar clics en botones
document.addEventListener('click', ev => {
if (ev.target.matches('button[data-action]')) {
const action = ev.target.getAttribute('data-action');
console.log('⏺ Acción botón:', action);
uibuilder.send({ payload: { seccion: action } });
}
});
// Mostrar pantalla tipos
document.addEventListener('click', ev => {
if (ev.target.matches('button[data-action="tipos"]')) {
mostrarPantalla('pantalla-tipos');
}
});
document.getElementById('btn-add-param').addEventListener('click', agregarParametro);
document.getElementById('btn-guardar-tipo').addEventListener('click', () => {
window.modalCallback = guardarTipo;
document.getElementById('modal-text').textContent = `¿Deseas guardar los cambios de Tipo ${tipoActual}?`;
document.getElementById('confirm-modal').style.display = 'block';
});
document.getElementById('btn-cancelar-tipo').addEventListener('click', () => {
uibuilder.send({ payload: { accion: "cancelar_tipo", tipo: tipoActual } });
volverATipos();
});
document.getElementById('btn-volver-tipos').addEventListener('click', () => {
volverATipos(); // esto limpia la tabla con los parametros del Tipo en el que estemos
uibuilder.send({ payload: { seccion: "volver_tipos" } });
});
let tipoActual = null;
let tiposDatos = {}; // aquí se guarda la configuración temporal
//******************************************* FUNCIONES ******************************************************** */
// Acciones a partir de botones en html
function sendRow() {// Aqui llega cuando se hace click en el boton Actualizar de las tablas dinamicas
// recoge el contenido de la tabla en ese instante y saca Notificacion de guardar con opcion Si o No
const rows = document.querySelectorAll('#config-table-body tr');
result = []; // inicializamos result para poner exactamente la tabla actual
/*
rows.forEach(row => {
const parametro = row.cells[0].textContent.trim();
let valor = row.cells[1].textContent.trim();
// normaliza a booleano si corresponde
if (valor === "true") valor = true;
if (valor === "false") valor = false;
result.push({ parametro, valor });
});
*/
//Modificacion de la lectura de la tabla para asegurar que los numeros son mantenidos como numeros y no como texto
// al igual que los valores booleanos.
rows.forEach(row => {
const parametro = row.cells[0].textContent.trim();
let valor = row.cells[1].textContent.trim();
if (/^(true|false)$/i.test(valor)) {
valor = valor.toLowerCase() === "true";
} else if (/^-?\d+$/.test(valor)) {
valor = parseInt(valor, 10);
} else if (/^-?\d+\.\d+$/.test(valor)) {
valor = parseFloat(valor);
}
result.push({ parametro, valor });
});
// Mostramos directamente la confirmación y definimos la callback. "topic" contiene el nombre de la tabla donde se guardan los valores que vienen en "result"
mostrarConfirmacion("¿Deseas guardar los cambios?", () => {
uibuilder.send({
payload: { table: result, seccion: "respuesta" },
topic: currentTopic,
origen: "actualizar"
});
});
}
function sendCancel() { // boton Cancelar de las tablas dinamicas
uibuilder.send({ payload: {seccion:'cancel-table'}, topic: currentTopic });
}
function confirmGuardar() { // Respuesta Afirmativa del Modal de tabla dinamica
document.getElementById('confirm-modal').style.display = 'none';
if (typeof window.modalCallback === 'function') {
const cb = window.modalCallback;
window.modalCallback = null; // limpia para la siguiente vez
cb(); // ejecuta
} else if (typeof window.modalCallback === 'string') {
uibuilder.send({ payload: { table: result }, modalCallback: window.modalCallback, origen: "modal" });
}
}
/*
function confirmGuardar() { // Respuesta Afirmativa del Modal de tabla dinamica
uibuilder.send({ payload: { table: result }, topic: currentTopic, origen: "modal-tabla" });
document.getElementById('confirm-modal').style.display = 'none';
}
*/
function cancelarGuardar() { // Respuesta Negativa del Modal de tabla dinamica
result = [];
document.getElementById('confirm-modal').style.display = 'none';
}
// Funciones para icono barrera
function mostrarBarreraAbierta() {
const icono = document.getElementById('icono-barrera');
icono.src = "/barrera_abierta.png";
/*
setTimeout(() => {
mostrarBarreraCerrada();
}, 1000); // 1 segundo
*/
}
function mostrarBarreraCerrada() {
const icono = document.getElementById('icono-barrera');
icono.src = "/barrera_cerrada.png";
}
//************************* FUNCIONES DE TIPOS DE CONFIGURACION *****************************/
function seleccionarTipo(n) {
tipoActual = n;
document.getElementById('titulo-tipo').textContent = `TIPO ${n}`;
document.getElementById('tipo-configuracion').style.display = 'block';
const contenedor = document.getElementById('parametros');
contenedor.innerHTML = '';
const datos = tiposDatos[n] || []; // ya es un array
datos.forEach(d => {
const row = crearFilaParametro(d.parametro, d.valor);
contenedor.appendChild(row);
});
}
function agregarParametro() {
const contenedor = document.getElementById('parametros');
if (contenedor.children.length >= 8) return;
const row = crearFilaParametro('', '');
contenedor.appendChild(row);
}
function crearFilaParametro(parametro, valor) {
const div = document.createElement('div');
div.className = 'parametro-row';
// Input para el nombre del parámetro
const inputParam = document.createElement('input');
inputParam.placeholder = 'Parámetro';
inputParam.value = parametro;
// Input para el valor: checkbox o texto
let inputValor;
if (valor === true || valor === false || valor === "true" || valor === "false") {
inputValor = document.createElement('input');
inputValor.type = 'checkbox';
inputValor.checked = (valor === true || valor === "true");
} else {
inputValor = document.createElement('input');
inputValor.placeholder = 'Valor';
inputValor.value = valor;
}
inputParam.classList.add('parametro');
inputValor.classList.add('valor');
// Botón para eliminar la fila
const btnDelete = document.createElement('button');
btnDelete.textContent = '-';
btnDelete.className = 'btn rojo';
btnDelete.style.flex = '0 0 40px';
btnDelete.style.width = '40px';
btnDelete.style.height = '40px';
btnDelete.style.marginLeft = '4px';
btnDelete.style.zIndex = '1';
btnDelete.addEventListener('click', (e) => {
e.stopPropagation();
e.preventDefault();
window.modalCallback = () => div.remove();
document.getElementById('modal-text').textContent = '¿Deseas eliminar este parámetro?';
document.getElementById('confirm-modal').style.display = 'block';
return false;
});
// Añadir los elementos a la fila
div.appendChild(inputParam);
div.appendChild(inputValor);
div.appendChild(btnDelete);
return div;
}
function crearFilaParametro_viejo(parametro, valor) { // Crea un nuevo elemento en la tabla del Tipo que estemos trabajando con dos campos y un boton de borrado
const div = document.createElement('div');
div.className = 'parametro-row';
const inputParam = document.createElement('input');
inputParam.placeholder = 'Parámetro';
inputParam.value = parametro;
const inputValor = document.createElement('input');
inputValor.placeholder = 'Valor';
inputValor.value = valor;
const btnDelete = document.createElement('button');
btnDelete.textContent = '-';
btnDelete.className = 'btn rojo';
btnDelete.style.width = '40px';
btnDelete.style.height = '40px';
btnDelete.onclick = () => { // gestion de la alerta para el borrado, haciendo uso del modal que tenemos en html
window.modalCallback = () => div.remove();
document.getElementById('modal-text').textContent = '¿Deseas eliminar este parámetro?';
document.getElementById('confirm-modal').style.display = 'block';
};
div.appendChild(inputParam);
div.appendChild(inputValor);
div.appendChild(btnDelete);
return div;
}
function guardarTipo() {
const contenedor = document.getElementById('parametros');
const filas = contenedor.querySelectorAll('.parametro-row');
const datos = [];
filas.forEach(fila => {
const [param, val] = fila.querySelectorAll('input');
let valor;
if (val.type === 'checkbox') {
valor = val.checked;
} else {
valor = val.value.trim();
}
if (param.value.trim()) {
datos.push({ parametro: param.value.trim(), valor });
}
});
/*
filas.forEach(fila => {
const [param, val] = fila.querySelectorAll('input');
if (param.value.trim()) {
datos.push({ parametro: param.value.trim(), valor: val.value.trim() });
}
});
*/
tiposDatos[tipoActual] = datos;
uibuilder.send({ payload: { tipo: tipoActual, datos }, topic: "tipoconfig" }); // el topic nos sirve para seleccionar en el proximo nodo la funcion de guardar los datos
volverATipos(); // 🔷 salimos de la pantalla del tipo actual
}
function volverATipos() {
document.getElementById('tipo-configuracion').style.display = 'none';
tipoActual = null;
}
//==========================================FUNCIONES PARA MANEJAR LAS NOTIFICACIONES ======================================================
function mostrarConfirmacion(texto, callback) {
document.getElementById('modal-text').textContent = texto;
// Restablece estado normal del modal
const btnSi = document.getElementById('btn-modal-si');
const btnNo = document.getElementById('btn-modal-no');
btnSi.textContent = 'Sí';
btnNo.style.display = 'inline-block';
window.modalCallback = callback;
document.getElementById('confirm-modal').style.display = 'block';
}
function mostrarNotificacion(texto) {
document.getElementById('modal-text').textContent = texto;
const btnSi = document.getElementById('btn-modal-si');
const btnNo = document.getElementById('btn-modal-no');
btnSi.textContent = 'OK';
btnNo.style.display = 'none';
window.modalCallback = null;
document.getElementById('confirm-modal').style.display = 'block';
}
/*
Cuando necesitemos un modal clásico (Sí/No), por ejemplo para confirmar actualización:
mostrarConfirmacion("¿Deseas guardar los cambios?", () => {
uibuilder.send({ payload: { table: result, seccion:"respuesta" }, topic: currentTopic, origen: "actualizar" });
});
Cuando necesitemos simplemente notificar un error:
mostrarNotificacion("La IP introducida es inválida, por favor corrígela.");
*/