// ===== Firebase config (from Firebase console web app) ===== const firebaseConfig = { apiKey: "YOUR_API_KEY", authDomain: "your-project-id.firebaseapp.com", databaseURL: "https://your-project-id-default-rtdb.YOUR_REGION.firebasedatabase.app", projectId: "your-project-id", }; const DEVICE_ID = "sparrow-01"; const MAX_CHART_POINTS = 50; const CHART_LOCAL_SRC = "chart.umd.min.js?v=1"; const CHART_CDN_SRC = "https://cdn.jsdelivr.net/npm/chart.js@4.4.3/dist/chart.umd.min.js"; // Wait for Firebase scripts (loaded via defer) window.addEventListener("DOMContentLoaded", () => { firebase.initializeApp(firebaseConfig); const auth = firebase.auth(); const db = firebase.database(); const loginScreen = document.getElementById("login-screen"); const dashboard = document.getElementById("dashboard"); const loginEmail = document.getElementById("login-email"); const loginPassword = document.getElementById("login-password"); const loginBtn = document.getElementById("login-btn"); const loginError = document.getElementById("login-error"); const logoutBtn = document.getElementById("logout-btn"); const userEmail = document.getElementById("user-email"); const latestDiv = document.getElementById("latest-reading"); const historyBody = document.getElementById("history-body"); const chartTempEl = document.getElementById("chart-temp"); const chartHumEl = document.getElementById("chart-hum"); const chartPressEl= document.getElementById("chart-press"); const chartStatus = document.getElementById("chart-status"); const ledOnCheckbox = document.getElementById("led-on"); const ledColorInput = document.getElementById("led-color"); const ledApplyBtn = document.getElementById("led-apply-btn"); const ledStatus = document.getElementById("led-status"); let charts = null; const chartState = { labels: [], temp: [], hum: [], press: [], }; function loadScript(src) { return new Promise((resolve, reject) => { const s = document.createElement("script"); s.src = src; s.onload = resolve; s.onerror = reject; document.head.appendChild(s); }); } async function ensureChartReady() { if (window.Chart) { if (chartStatus) chartStatus.textContent = `Charts ready (Chart.js ${window.Chart.version || ""})`; return true; } if (chartStatus) chartStatus.textContent = "Loading charts…"; try { await loadScript(CHART_LOCAL_SRC); } catch (e) { console.warn("Local Chart.js failed, trying CDN", e); try { await loadScript(CHART_CDN_SRC); } catch (e2) { console.error("Chart.js failed to load from all sources", e2); return false; } } const ok = !!window.Chart; if (chartStatus) chartStatus.textContent = ok ? `Charts ready (Chart.js ${window.Chart.version || ""})` : "Charts unavailable"; return ok; } function resetCharts() { chartState.labels = []; chartState.temp = []; chartState.hum = []; chartState.press = []; if (charts) { charts.temp.data.labels = []; charts.temp.data.datasets[0].data = []; charts.hum.data.labels = []; charts.hum.data.datasets[0].data = []; charts.press.data.labels = []; charts.press.data.datasets[0].data = []; charts.temp.update("none"); charts.hum.update("none"); charts.press.update("none"); } } function initCharts() { if (!window.Chart) { console.warn("Chart.js not available"); return; } const baseOptions = { responsive: true, animation: false, plugins: { legend: { display: false } }, scales: { x: { ticks: { color: "#aaa" }, grid: { color: "rgba(255,255,255,0.05)" } }, y: { ticks: { color: "#aaa" }, grid: { color: "rgba(255,255,255,0.05)" } }, }, }; charts = { temp: new Chart(chartTempEl.getContext("2d"), { type: "line", data: { labels: [], datasets: [{ label: "Temp (°C)", data: [], borderColor: "#ff6b6b", backgroundColor: "rgba(255,107,107,0.15)", tension: 0.2, fill: true, pointRadius: 0 }] }, options: baseOptions, }), hum: new Chart(chartHumEl.getContext("2d"), { type: "line", data: { labels: [], datasets: [{ label: "Hum (%)", data: [], borderColor: "#4fd1c5", backgroundColor: "rgba(79,209,197,0.15)", tension: 0.2, fill: true, pointRadius: 0 }] }, options: baseOptions, }), press: new Chart(chartPressEl.getContext("2d"), { type: "line", data: { labels: [], datasets: [{ label: "Press (hPa)", data: [], borderColor: "#9f7aea", backgroundColor: "rgba(159,122,234,0.15)", tension: 0.2, fill: true, pointRadius: 0 }] }, options: baseOptions, }), }; } function updateCharts(ts, t, h, p) { if (!charts) return; const label = new Date(ts).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" }); chartState.labels.push(label); chartState.temp.push(t); chartState.hum.push(h); chartState.press.push(p); if (chartState.labels.length > MAX_CHART_POINTS) { chartState.labels.shift(); chartState.temp.shift(); chartState.hum.shift(); chartState.press.shift(); } charts.temp.data.labels = chartState.labels.slice(); charts.hum.data.labels = chartState.labels.slice(); charts.press.data.labels = chartState.labels.slice(); charts.temp.data.datasets[0].data = chartState.temp.slice(); charts.hum.data.datasets[0].data = chartState.hum.slice(); charts.press.data.datasets[0].data= chartState.press.slice(); charts.temp.update("none"); charts.hum.update("none"); charts.press.update("none"); if (chartStatus) chartStatus.textContent = `Charts live — last update ${label}`; } // ===== Auth UI handling ===== auth.onAuthStateChanged((user) => { if (user) { loginScreen.style.display = "none"; dashboard.style.display = "block"; userEmail.textContent = user.email || "(no email)"; resetCharts(); ensureChartReady().then((ok) => { if (ok) { initCharts(); } else { console.warn("Charts disabled: Chart.js not available"); if (chartStatus) chartStatus.textContent = "Charts disabled: Chart.js not available (check console / cache)"; } startDataListeners(); }); } else { dashboard.style.display = "none"; loginScreen.style.display = "block"; userEmail.textContent = ""; historyBody.innerHTML = ""; latestDiv.innerHTML = "<p>No data yet…</p>"; resetCharts(); if (chartStatus) chartStatus.textContent = "Charts require login to load."; } }); loginBtn.addEventListener("click", () => { loginError.textContent = ""; const email = loginEmail.value.trim(); const pass = loginPassword.value.trim(); auth.signInWithEmailAndPassword(email, pass) .catch(err => { console.error(err); loginError.textContent = err.message; }); }); logoutBtn.addEventListener("click", () => { auth.signOut(); }); // ===== Telemetry listeners ===== function addHistoryRow(ts, t, h, p) { const tr = document.createElement("tr"); const date = new Date(ts); tr.innerHTML = ` <td>${date.toLocaleString()}</td> <td>${t.toFixed(1)}</td> <td>${h.toFixed(1)}</td> <td>${p.toFixed(1)}</td> `; historyBody.prepend(tr); // newest on top // Keep at most 20 rows while (historyBody.rows.length > 20) { historyBody.deleteRow(historyBody.rows.length - 1); } } function updateLatest(ts, t, h, p) { const date = new Date(ts); latestDiv.innerHTML = ` <p><strong>${date.toLocaleString()}</strong></p> <p>Temperature: <strong>${t.toFixed(1)} °C</strong></p> <p>Humidity: <strong>${h.toFixed(1)} %</strong></p> <p>Pressure: <strong>${p.toFixed(1)} hPa</strong></p> `; } function startDataListeners() { // Listen for last 20 telemetry entries const telemRef = db.ref(`devices/${DEVICE_ID}/telemetry`).limitToLast(20); telemRef.off(); // avoid duplicates on re-login telemRef.on("child_added", (snap) => { const val = snap.val(); if (!val) return; const ts = val.timestamp || Date.now(); const t = val.temperature || 0; const h = val.humidity || 0; const p = val.pressure || 0; updateLatest(ts, t, h, p); addHistoryRow(ts, t, h, p); updateCharts(ts, t, h, p); }); // Keep dashboard LED controls in sync with DB const ledRef = db.ref(`devices/${DEVICE_ID}/led`); ledRef.off(); ledRef.on("value", (snap) => { const val = snap.val(); if (!val) return; const state = val.state || "off"; const color = val.color || "#ffffff"; ledOnCheckbox.checked = state === "on"; ledColorInput.value = color; ledStatus.textContent = `Current: ${state.toUpperCase()} ${color}`; }); } // ===== LED control write ===== ledApplyBtn.addEventListener("click", () => { const state = ledOnCheckbox.checked ? "on" : "off"; const color = ledColorInput.value || "#ffffff"; const ledRef = db.ref(`devices/${DEVICE_ID}/led`); ledRef.set({ state, color }) .then(() => { ledStatus.textContent = `Set: ${state.toUpperCase()} ${color}`; }) .catch(err => { console.error(err); ledStatus.textContent = `Error: ${err.message}`; }); }); });