Gráficas variables ajustables y borrado de datos csv

main
EMOTIONS-HUNTER 6 days ago
parent 2f33e59fe8
commit 0ba3e59fb6

@ -184,6 +184,23 @@ app.post('/api/sensors/:type/command', async (req, res) => {
});
});
// --- RUTAS DE GESTIÓN DE ALMACENAMIENTO ---
// Variable global para simular la configuración de escritura del demonio C++
let loggingRateSeconds = 1;
app.post('/api/config/logging', (req, res) => {
loggingRateSeconds = req.body.rate || 1;
console.log(`[SISTEMA] Demonio de escritura configurado a: 1 registro cada ${loggingRateSeconds}s`);
res.json({ success: true, rate: loggingRateSeconds });
});
app.post('/api/history/clear', (req, res) => {
console.log(`[SISTEMA] Purgado de base de datos histórico solicitado por el usuario.`);
// En el hardware real, aquí se ejecutaría 'fs.unlink()' o se truncarían los archivos CSV en /logs/
res.json({ success: true, message: "Archivos de registro truncados correctamente." });
});
app.listen(PORT, () => {
console.log(`[MOCK SERVER] Backend Node.js corriendo en http://localhost:${PORT}`);
});

@ -1,8 +1,11 @@
const POLLING_INTERVAL_MS = 1000;
const HISTORY_POLLING_INTERVAL_MS = 10000;
const ALARM_CONFIG_FILE = "../config/alarms.json";
const WARNING_MARGIN_RATIO = 0.1;
// Variables dinámicas para el control de históricos
let currentHistoryInterval = 10000;
let historyIntervalId = null;
// Diccionario para traducir estados en la UI sin romper las clases CSS
const stateTranslations = {
"NORMAL": "NORMAL",
@ -586,11 +589,75 @@ async function updateHistoricalTrends() {
});
}
updateDashboard();
setInterval(updateDashboard, POLLING_INTERVAL_MS);
// --- MOTOR DE INTERVALOS DINÁMICOS ---
function startHistoryPolling() {
if (historyIntervalId) {
clearInterval(historyIntervalId);
}
historyIntervalId = setInterval(updateHistoricalTrends, currentHistoryInterval);
}
// Inicialización de renderizado
updateHistoricalTrends();
setInterval(updateHistoricalTrends, HISTORY_POLLING_INTERVAL_MS);
startHistoryPolling();
// --- FUNCIONES DE CONTROL DE ALMACENAMIENTO ---
window.updateChartRefreshRate = function () {
const select = document.getElementById("chart-refresh-rate");
currentHistoryInterval = parseInt(select.value, 10);
const intervalText = select.options[select.selectedIndex].text.replace("Cada ", "").toLowerCase();
document.getElementById("history-interval").textContent = intervalText;
startHistoryPolling();
};
window.updateLoggingRate = async function () {
const select = document.getElementById("data-logging-rate");
const rate = parseInt(select.value, 10);
try {
const response = await fetch('/api/config/logging', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ rate })
});
if (!response.ok) throw new Error("Fallo en la sincronización con el servidor");
alert(`Configuración de hardware actualizada:\nLos demonios C++ guardarán 1 registro cada ${rate} segundos.`);
} catch (error) {
console.error("Error configurando la tasa de registro:", error);
}
};
window.clearHistoricalData = async function () {
const confirmacion = confirm("ADVERTENCIA CRÍTICA:\n\n¿Confirma el purgado total de los archivos históricos CSV? Esta acción no se puede deshacer y los datos de tendencias se perderán de forma permanente.");
if (!confirmacion) return;
try {
const response = await fetch('/api/history/clear', { method: 'POST' });
if (!response.ok) throw new Error("Fallo en el purgado de archivos");
// Simulación visual de purgado en la interfaz (Destrucción de datos del Canvas)
historicalCharts.forEach(config => {
const existingChart = chartInstances.get(config.id);
if (existingChart) {
existingChart.data.labels = [];
existingChart.data.datasets.forEach(dataset => dataset.data = []);
existingChart.update("none");
}
setChartAvailability(config, false);
});
alert("Purgado de memoria completado exitosamente.");
} catch (error) {
console.error("Error ejecutando el borrado:", error);
alert("Error de sistema al intentar purgar los archivos.");
}
};
const chartCards = {
temperature: document.getElementById("chart-card-temperature"),
@ -611,13 +678,13 @@ document.querySelectorAll(".chart-filter").forEach((button) => {
if (selected === "all") {
Object.values(chartCards).forEach((card) => {
if(card) card.style.display = "";
if (card) card.style.display = "";
});
return;
}
Object.entries(chartCards).forEach(([key, card]) => {
if(card) card.style.display = key === selected ? "" : "none";
if (card) card.style.display = key === selected ? "" : "none";
});
});
});

@ -132,9 +132,43 @@
<section class="trends-panel" aria-label="Tendencias históricas">
<div class="panel-heading">
<h2>Tendencias Históricas</h2>
<p>Las gráficas se actualizan cada <span id="history-interval">10 segundos</span></p>
</div>
<div class="card config-card" style="margin-bottom: 20px; padding: 20px; border-left: 4px solid #9c27b0;">
<h3>Configuración de Almacenamiento</h3>
<div style="display: flex; gap: 20px; align-items: center; flex-wrap: wrap;">
<div>
<label style="display: block; margin-bottom: 5px; font-size: 0.9em; color: #ccc;">Actualización
de Gráficas (UI):</label>
<select id="chart-refresh-rate" onchange="updateChartRefreshRate()"
style="padding: 8px; background-color: #222; color: white; border: 1px solid #444; border-radius: 4px;">
<option value="5000">Cada 5 segundos</option>
<option value="10000" selected>Cada 10 segundos</option>
<option value="30000">Cada 30 segundos</option>
<option value="60000">Cada 1 minuto</option>
</select>
</div>
<div>
<label style="display: block; margin-bottom: 5px; font-size: 0.9em; color: #ccc;">Frecuencia de
Guardado (Log):</label>
<select id="data-logging-rate" onchange="updateLoggingRate()"
style="padding: 8px; background-color: #222; color: white; border: 1px solid #444; border-radius: 4px;">
<option value="1">1 lectura / segundo</option>
<option value="5">1 lectura / 5 segundos</option>
<option value="10">1 lectura / 10 segundos</option>
<option value="60">1 lectura / 1 minuto</option>
</select>
</div>
<div style="margin-top: auto;">
<button class="btn-command" onclick="clearHistoricalData()"
style="padding: 8px 15px; background-color: #ff4444; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;">
Borrar Historial de Datos
</button>
</div>
</div>
</div>
<div class="card export-card">
<h3>Exportar Historial (CSV / Excel)</h3>
<div class="button-group">
@ -199,68 +233,78 @@
<div class="card terminal-card" style="margin-top: 20px; padding: 20px; border-left: 4px solid #00ffcc;">
<div class="card terminal-card" style="margin-top: 20px; padding: 20px; border-left: 4px solid #00ffcc;">
<h3>Consola de Hardware (Atlas Scientific EZO)</h3>
<div style="display: flex; gap: 10px; margin-bottom: 15px; align-items: center; flex-wrap: wrap;">
<select id="terminal-sensor-select" style="padding: 5px;">
<option value="rtd">Temperatura (RTD)</option>
<option value="ph">Sensor de pH</option>
<option value="do">Oxígeno Disuelto (DO)</option>
<option value="ec">Conductividad (EC)</option>
</select>
<button class="btn-command" onclick="sendCommand('i')"
style="padding: 5px 15px; cursor: pointer;">Info (i)</button>
<button class="btn-command" onclick="sendCommand('status')"
style="padding: 5px 15px; cursor: pointer;">Estado</button>
<button class="btn-command" onclick="sendCommand('r')"
style="padding: 5px 15px; background-color: #4f46e5; color: white; border: none; border-radius: 4px; cursor: pointer;">Leer (r)</button>
<span style="color: #666; margin: 0 10px;">|</span>
<input type="text" id="custom-command" placeholder="Ej: factory, t,25.0, led,1"
style="padding: 5px; width: 200px; background-color: #222; color: #00ffcc; border: 1px solid #444;">
<button class="btn-command" onclick="sendCustomCommand()"
style="padding: 5px 15px; background-color: #00ffcc; color: black; font-weight: bold; border: none; border-radius: 4px; cursor: pointer;">Enviar ↵</button>
</div>
<h3>Consola de Hardware (Atlas Scientific EZO)</h3>
<div id="terminal-output"
style="background-color: #1e1e1e; color: #00ff00; padding: 15px; height: 150px; overflow-y: auto; font-family: 'Courier New', Courier, monospace; border-radius: 4px;">
<div style="color: #888;">[SISTEMA] Consola I2C iniciada. Esperando comandos...</div>
</div>
</div>
<div class="card calibration-card" style="margin-top: 20px; padding: 20px; border-left: 4px solid #ffcc00;">
<h3>Panel de Calibración</h3>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px;">
<div>
<label style="display: block; margin-bottom: 5px;">Seleccionar Sensor a Calibrar:</label>
<select id="cal-sensor-select" style="padding: 8px; width: 100%; margin-bottom: 15px;">
<div style="display: flex; gap: 10px; margin-bottom: 15px; align-items: center; flex-wrap: wrap;">
<select id="terminal-sensor-select" style="padding: 5px;">
<option value="rtd">Temperatura (RTD)</option>
<option value="ph">Sensor de pH</option>
<option value="do">Oxígeno Disuelto (DO)</option>
<option value="ec">Conductividad (EC)</option>
</select>
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px;">Comandos Globales de Calibración:</label>
<button class="btn-command" onclick="sendCalibrationCmd('cal,?')" style="padding: 5px 10px; margin-right: 5px; cursor: pointer;">Verificar Estado (?)</button>
<button class="btn-command" onclick="sendCalibrationCmd('cal,clear')" style="padding: 5px 10px; background-color: #ff4444; color: white; border: none; border-radius: 4px; cursor: pointer;">Borrar Memoria (Clear)</button>
</div>
<button class="btn-command" onclick="sendCommand('i')"
style="padding: 5px 15px; cursor: pointer;">Info (i)</button>
<button class="btn-command" onclick="sendCommand('status')"
style="padding: 5px 15px; cursor: pointer;">Estado</button>
<button class="btn-command" onclick="sendCommand('r')"
style="padding: 5px 15px; background-color: #4f46e5; color: white; border: none; border-radius: 4px; cursor: pointer;">Leer
(r)</button>
<span style="color: #666; margin: 0 10px;">|</span>
<input type="text" id="custom-command" placeholder="Ej: factory, t,25.0, led,1"
style="padding: 5px; width: 200px; background-color: #222; color: #00ffcc; border: 1px solid #444;">
<button class="btn-command" onclick="sendCustomCommand()"
style="padding: 5px 15px; background-color: #00ffcc; color: black; font-weight: bold; border: none; border-radius: 4px; cursor: pointer;">Enviar
</button>
</div>
<div style="background-color: #f5f5f5; padding: 15px; border-radius: 4px;">
<h4 style="margin-top: 0; color: #333;">Calibración Multipunto</h4>
<p style="font-size: 0.9em; color: #666;">Ingresa el valor de referencia físico (ej. solución pH, o temperatura del agua).</p>
<div id="terminal-output"
style="background-color: #1e1e1e; color: #00ff00; padding: 15px; height: 150px; overflow-y: auto; font-family: 'Courier New', Courier, monospace; border-radius: 4px;">
<div style="color: #888;">[SISTEMA] Consola I2C iniciada. Esperando comandos...</div>
</div>
</div>
<div class="card calibration-card" style="margin-top: 20px; padding: 20px; border-left: 4px solid #ffcc00;">
<h3>Panel de Calibración</h3>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px;">
<div>
<label style="display: block; margin-bottom: 5px;">Seleccionar Sensor a Calibrar:</label>
<select id="cal-sensor-select" style="padding: 8px; width: 100%; margin-bottom: 15px;">
<option value="rtd">Temperatura (RTD)</option>
<option value="ph">Sensor de pH</option>
<option value="do">Oxígeno Disuelto (DO)</option>
<option value="ec">Conductividad (EC)</option>
</select>
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px;">Comandos Globales de Calibración:</label>
<button class="btn-command" onclick="sendCalibrationCmd('cal,?')"
style="padding: 5px 10px; margin-right: 5px; cursor: pointer;">Verificar Estado
(?)</button>
<button class="btn-command" onclick="sendCalibrationCmd('cal,clear')"
style="padding: 5px 10px; background-color: #ff4444; color: white; border: none; border-radius: 4px; cursor: pointer;">Borrar
Memoria (Clear)</button>
</div>
</div>
<div style="display: flex; gap: 10px; align-items: center;">
<input type="number" id="cal-value" placeholder="Ej: 7.00" step="0.01" style="padding: 8px; width: 100px; border: 1px solid #ccc; border-radius: 4px;">
<button class="btn-command" onclick="calibratePoint()" style="padding: 8px 15px; background-color: #00ffcc; color: black; font-weight: bold; border: none; border-radius: 4px; cursor: pointer;">Calibrar Punto</button>
<div style="background-color: #f5f5f5; padding: 15px; border-radius: 4px;">
<h4 style="margin-top: 0; color: #333;">Calibración Multipunto</h4>
<p style="font-size: 0.9em; color: #666;">Ingresa el valor de referencia físico (ej. solución
pH, o temperatura del agua).</p>
<div style="display: flex; gap: 10px; align-items: center;">
<input type="number" id="cal-value" placeholder="Ej: 7.00" step="0.01"
style="padding: 8px; width: 100px; border: 1px solid #ccc; border-radius: 4px;">
<button class="btn-command" onclick="calibratePoint()"
style="padding: 8px 15px; background-color: #00ffcc; color: black; font-weight: bold; border: none; border-radius: 4px; cursor: pointer;">Calibrar
Punto</button>
</div>
</div>
</div>
</div>
</div>
</main>

Loading…
Cancel
Save