mirror of
https://github.com/adrigongv23/G26---Telemetry-Software.git
synced 2026-05-25 12:31:27 +02:00
Página Telemetria que se va a usar (.html)
This commit is contained in:
parent
74b764c9d8
commit
5ca44845c8
1 changed files with 254 additions and 0 deletions
254
index.html
Normal file
254
index.html
Normal file
|
|
@ -0,0 +1,254 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>Telemetría IoT - Sesiones</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<style>
|
||||||
|
* { box-sizing: border-box; }
|
||||||
|
body { margin:0; font-family: Arial, sans-serif; background: linear-gradient(135deg,#667eea,#764ba2); min-height:100vh; display:flex; align-items:center; justify-content:center; padding:20px; }
|
||||||
|
#app { background:#fff; width:100%; max-width:980px; border-radius:16px; padding:28px; box-shadow:0 20px 60px rgba(0,0,0,.25); }
|
||||||
|
h1 { margin:0 0 8px; color:#333; text-align:center; }
|
||||||
|
#estado { text-align:center; font-weight:700; padding:10px; border-radius:8px; margin:8px 0 14px; }
|
||||||
|
.activa { color:#155724; background:#d4edda; }
|
||||||
|
.inactiva { color:#721c24; background:#f8d7da; }
|
||||||
|
#peso { text-align:center; font-size:3.2rem; font-weight:800; color:#007bff; margin:6px 0 16px; letter-spacing:-1px; }
|
||||||
|
.grid { display:grid; grid-template-columns: repeat(auto-fit,minmax(180px,1fr)); gap:10px; margin-bottom:16px; }
|
||||||
|
.card { background:#f7f7fb; border-radius:10px; padding:12px; text-align:center; }
|
||||||
|
.label { font-size:.85rem; color:#6c757d; }
|
||||||
|
.value { font-size:1.4rem; font-weight:700; color:#333; }
|
||||||
|
.controls { display:flex; gap:10px; flex-wrap:wrap; justify-content:center; margin-top:8px; }
|
||||||
|
button { background:#007bff; color:#fff; border:0; padding:12px 18px; border-radius:8px; font-weight:700; cursor:pointer; transition:.2s; }
|
||||||
|
button.stop { background:#dc3545; }
|
||||||
|
button.download { background:#28a745; }
|
||||||
|
button:hover { filter:brightness(.95); transform: translateY(-1px); }
|
||||||
|
#log { margin-top:16px; background:#f8f9fa; padding:12px; border-radius:8px; max-height:260px; overflow:auto; font-family: Consolas, monospace; font-size:.9rem; color:#495057; }
|
||||||
|
#conn { text-align:center; margin-top:6px; font-size:.9rem; color:#6c757d; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app">
|
||||||
|
<h1>🔬 Telemetría IoT</h1>
|
||||||
|
<div id="estado" class="inactiva">❌ Sesión Inactiva</div>
|
||||||
|
<div id="peso">--- kg</div>
|
||||||
|
<div id="conn">Socket: desconocido</div>
|
||||||
|
|
||||||
|
<div class="grid">
|
||||||
|
<div class="card"><div class="label">Ventanas recibidas</div><div class="value" id="ventanas-count">0</div></div>
|
||||||
|
<div class="card"><div class="label">Muestras totales</div><div class="value" id="muestras-count">0</div></div>
|
||||||
|
<div class="card"><div class="label">Última actualización</div><div class="value" id="ultima-actualizacion">---</div></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="controls">
|
||||||
|
<button onclick="iniciarSesion()">▶️ Iniciar sesión</button>
|
||||||
|
<button class="stop" onclick="finalizarSesion()">⏸️ Finalizar sesión</button>
|
||||||
|
<button class="download" onclick="descargarCSV()">📥 Descargar CSV</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="log"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://www.gstatic.com/firebasejs/8.10.0/firebase-app.js"></script>
|
||||||
|
<script src="https://www.gstatic.com/firebasejs/8.10.0/firebase-database.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const firebaseConfig = {
|
||||||
|
apiKey: "AIzaSyB0JaH3ZPXdj-fw2LmKq1DGCEjriJ8hmgc",
|
||||||
|
authDomain: "iot-formula-gades.firebaseapp.com",
|
||||||
|
databaseURL: "https://iot-formula-gades-default-rtdb.europe-west1.firebasedatabase.app",
|
||||||
|
projectId: "iot-formula-gades",
|
||||||
|
storageBucket: "iot-formula-gades.firebasestorage.app",
|
||||||
|
messagingSenderId: "930129619937",
|
||||||
|
appId: "1:930129619937:web:473b802794abe593da40a0",
|
||||||
|
measurementId: "G-CWXQWQQE3R"
|
||||||
|
};
|
||||||
|
firebase.initializeApp(firebaseConfig);
|
||||||
|
const db = firebase.database();
|
||||||
|
|
||||||
|
const controlRef = db.ref('/control/sesion_activa');
|
||||||
|
const sesionesRef = db.ref('/sesiones');
|
||||||
|
const infoConnRef = db.ref('.info/connected'); // [web:263]
|
||||||
|
|
||||||
|
const estadoEl = document.getElementById('estado');
|
||||||
|
const connEl = document.getElementById('conn');
|
||||||
|
const pesoEl = document.getElementById('peso');
|
||||||
|
const ventanasCountEl = document.getElementById('ventanas-count');
|
||||||
|
const muestrasCountEl = document.getElementById('muestras-count');
|
||||||
|
const ultimaActualizacionEl = document.getElementById('ultima-actualizacion');
|
||||||
|
const logEl = document.getElementById('log');
|
||||||
|
|
||||||
|
let ultimaSesionID = null; // clave actual de sesión
|
||||||
|
let windowsRef = null; // referencia sin query
|
||||||
|
let windowsQuery = null; // query activa
|
||||||
|
let lastWindowId = null;
|
||||||
|
let prevLogId = null;
|
||||||
|
let prevSampleTs = null;
|
||||||
|
let ventanasRecibidas = 0;
|
||||||
|
let muestrasTotales = 0;
|
||||||
|
let todasLasMuestras = [];
|
||||||
|
|
||||||
|
function log(msg) {
|
||||||
|
const line = document.createElement('div');
|
||||||
|
line.textContent = `[${new Date().toLocaleTimeString()}] ${msg}`;
|
||||||
|
logEl.prepend(line);
|
||||||
|
while (logEl.children.length > 300) logEl.removeChild(logEl.lastChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
infoConnRef.on('value', s => {
|
||||||
|
connEl.textContent = s.val() ? 'Socket: conectado' : 'Socket: desconectado';
|
||||||
|
});
|
||||||
|
|
||||||
|
function iniciarSesion() { controlRef.set(true); log('▶️ Iniciar sesión'); }
|
||||||
|
function finalizarSesion(){ controlRef.set(false); log('⏸️ Finalizar sesión'); }
|
||||||
|
|
||||||
|
function iniciarSesionUI() {
|
||||||
|
estadoEl.className = 'activa';
|
||||||
|
estadoEl.textContent = '✅ Sesión Activa';
|
||||||
|
pesoEl.style.color = '#28a745';
|
||||||
|
actualizarMetricas();
|
||||||
|
}
|
||||||
|
function finalizarSesionUI() {
|
||||||
|
estadoEl.className = 'inactiva';
|
||||||
|
estadoEl.textContent = '❌ Sesión Inactiva';
|
||||||
|
pesoEl.style.color = '#6c757d';
|
||||||
|
pesoEl.textContent = '--- kg';
|
||||||
|
actualizarMetricas();
|
||||||
|
}
|
||||||
|
function actualizarMetricas() {
|
||||||
|
ventanasCountEl.textContent = ventanasRecibidas;
|
||||||
|
muestrasCountEl.textContent = muestrasTotales;
|
||||||
|
ultimaActualizacionEl.textContent = new Date().toLocaleTimeString();
|
||||||
|
}
|
||||||
|
function resetEstadoSesion() {
|
||||||
|
ventanasRecibidas = 0; muestrasTotales = 0; todasLasMuestras = [];
|
||||||
|
lastWindowId = null; prevLogId = null; prevSampleTs = null;
|
||||||
|
actualizarMetricas();
|
||||||
|
}
|
||||||
|
|
||||||
|
// UI de sesión (no afecta a tracking)
|
||||||
|
controlRef.on('value', snap => {
|
||||||
|
const activo = !!snap.val();
|
||||||
|
if (activo) iniciarSesionUI(); else finalizarSesionUI();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 1) Al cargar, engancharse a la última
|
||||||
|
sesionesRef.orderByKey().limitToLast(1).on('value', snap => {
|
||||||
|
let key = null; snap.forEach(ch => key = ch.key);
|
||||||
|
if (!key) return;
|
||||||
|
const keyNum = Number(key);
|
||||||
|
const curNum = ultimaSesionID ? Number(ultimaSesionID) : -1;
|
||||||
|
if (keyNum > curNum) attachToSession(key);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2) Y además detectar nuevas que aparezcan después
|
||||||
|
sesionesRef.on('child_added', snap => {
|
||||||
|
const key = snap.key;
|
||||||
|
const keyNum = Number(key);
|
||||||
|
const curNum = ultimaSesionID ? Number(ultimaSesionID) : -1;
|
||||||
|
if (keyNum > curNum) attachToSession(key);
|
||||||
|
});
|
||||||
|
|
||||||
|
function detachWindows() {
|
||||||
|
if (windowsQuery) { windowsQuery.off(); windowsQuery = null; }
|
||||||
|
if (windowsRef) { windowsRef.off(); windowsRef = null; }
|
||||||
|
resetEstadoSesion();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjunta una sesión: hace un prefetch inicial y luego activa child_added
|
||||||
|
function attachToSession(sid) {
|
||||||
|
// Detach y reset
|
||||||
|
detachWindows();
|
||||||
|
ultimaSesionID = sid;
|
||||||
|
log(`📂 Sesión activa: ${sid}`);
|
||||||
|
|
||||||
|
windowsRef = db.ref(`/sesiones/${sid}/windows`);
|
||||||
|
|
||||||
|
// Prefetch: traer lo que ya exista ahora mismo (por si se escribió antes de adjuntar)
|
||||||
|
windowsRef.orderByKey().once('value', (snap) => {
|
||||||
|
const batch = [];
|
||||||
|
snap.forEach(ch => {
|
||||||
|
batch.push({ id: Number(ch.key), v: ch.val() });
|
||||||
|
});
|
||||||
|
batch.sort((a,b) => a.id - b.id);
|
||||||
|
for (const it of batch) {
|
||||||
|
if (lastWindowId === null || it.id > lastWindowId) {
|
||||||
|
processVentana(it.v, it.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ahora activar child_added para lo que venga a partir de aquí
|
||||||
|
windowsQuery = windowsRef.orderByKey().startAt(String((lastWindowId || 0) + 1)); // [web:263]
|
||||||
|
windowsQuery.on('child_added', (snap2) => {
|
||||||
|
const id = Number(snap2.key);
|
||||||
|
if (Number.isFinite(lastWindowId) && id <= lastWindowId) return;
|
||||||
|
processVentana(snap2.val(), id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function processVentana(w, id) {
|
||||||
|
if (!w || !Array.isArray(w.muestras)) return;
|
||||||
|
|
||||||
|
if (prevLogId !== null && id > prevLogId + 1) {
|
||||||
|
log(`⚠️ Faltan ${id - prevLogId - 1} ventanas entre ${prevLogId} y ${id}`);
|
||||||
|
}
|
||||||
|
prevLogId = id;
|
||||||
|
|
||||||
|
const t0 = typeof w.t0_ms === 'number' ? w.t0_ms : 0;
|
||||||
|
const dt = typeof w.dt_ms === 'number' ? w.dt_ms : 500;
|
||||||
|
const numMuestras = w.muestras.length;
|
||||||
|
|
||||||
|
w.muestras.forEach((m, i) => {
|
||||||
|
const ts_ms = (typeof m.ts_ms === 'number') ? m.ts_ms : (t0 + i*dt);
|
||||||
|
const iso = new Date(ts_ms).toISOString();
|
||||||
|
const peso = (typeof m.peso === 'number') ? m.peso : parseFloat(m.peso);
|
||||||
|
todasLasMuestras.push([iso, ts_ms, id, peso]);
|
||||||
|
|
||||||
|
if (prevSampleTs !== null && ts_ms - prevSampleTs > 2000) {
|
||||||
|
const gap = ((ts_ms - prevSampleTs)/1000).toFixed(1);
|
||||||
|
log(`⚠️ Hueco temporal de ${gap}s antes de ventana ${id}`);
|
||||||
|
}
|
||||||
|
prevSampleTs = ts_ms;
|
||||||
|
});
|
||||||
|
|
||||||
|
// UI
|
||||||
|
const last = w.muestras[numMuestras - 1];
|
||||||
|
if (last && last.peso != null) {
|
||||||
|
const v = parseFloat(last.peso);
|
||||||
|
if (!Number.isNaN(v)) pesoEl.textContent = v.toFixed(3) + ' kg';
|
||||||
|
}
|
||||||
|
ventanasRecibidas++;
|
||||||
|
muestrasTotales = todasLasMuestras.length;
|
||||||
|
actualizarMetricas();
|
||||||
|
|
||||||
|
const lastTs = (typeof last?.ts_ms === 'number') ? last.ts_ms : (t0 + (numMuestras-1)*dt);
|
||||||
|
log(`📦 Ventana ${id} (+${numMuestras} muestras) @ ${new Date(lastTs).toISOString()}`);
|
||||||
|
|
||||||
|
lastWindowId = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
function descargarCSV() {
|
||||||
|
if (!ultimaSesionID) { alert('⚠️ No hay sesión.'); return; }
|
||||||
|
if (todasLasMuestras.length === 0) { alert('⚠️ Sin datos.'); return; }
|
||||||
|
todasLasMuestras.sort((a,b) => a[1] - b[1]);
|
||||||
|
const BOM = "\uFEFF";
|
||||||
|
let csv = "sep=,\r\n";
|
||||||
|
csv += "Timestamp,Timestamp_ms,Window_ID,Peso_kg\r\n";
|
||||||
|
for (const r of todasLasMuestras) {
|
||||||
|
csv += [r[0], String(r[1]), String(r[2]), String(r[3])].join(',') + "\r\n";
|
||||||
|
}
|
||||||
|
const blob = new Blob([BOM + csv], { type: "text/csv;charset=utf-8;" });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url; a.download = `sesion_${ultimaSesionID}.csv`;
|
||||||
|
document.body.appendChild(a); a.click(); document.body.removeChild(a);
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
log(`📥 CSV exportado (${todasLasMuestras.length} filas)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.iniciarSesion = iniciarSesion;
|
||||||
|
window.finalizarSesion = finalizarSesion;
|
||||||
|
window.descargarCSV = descargarCSV;
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue