Differences

This shows you the differences between two versions of the page.

Link to this comparison view

iothings:laboratoare:2025:lab9 [2025/11/23 12:52]
dan.tudose [4.2 index.html]
iothings:laboratoare:2025:lab9 [2025/11/23 13:43] (current)
dan.tudose [3. ESP32-C6 Sparrow Firmware (HTTPS + BME680 + NeoPixel)]
Line 189: Line 189:
  
   * ''​WiFiClientSecure''​ for HTTPS.   * ''​WiFiClientSecure''​ for HTTPS.
-  * For simplicity we will call ''​setInsecure()''​ (it still uses TLS but skips certificate ​validation).  ​ +  * We have embedded a proper CA certificate. 
-    ​*For productionembed proper CA certificates.*+  ​* ​The certificate is the full Google TLS root (GTS Root R1cross-signed by GlobalSign) captured from a live identitytoolkit.googleapis.com chain and reverted to setCACert using that PEM
  
  
Line 240: Line 240:
     index.html     index.html
     app.js     app.js
 +    chart.umd.min.js
 </​code>​ </​code>​
  
Line 246: Line 247:
 ==== 4.2 index.html ==== ==== 4.2 index.html ====
  
-Create ''​dashboard/​public/​index.html''​ with:+Create ''​dashboard/​public/​index.html''​ with the code from [[iothings:laboratoare:​2025_code:​lab9_2|here]].
  
  
 ==== 4.3 app.js ==== ==== 4.3 app.js ====
  
-Create ''​dashboard/​public/​app.js''​ and paste this.  ​ +Create ''​dashboard/​public/​app.js''​ and paste [[iothings:​laboratoare:​2025_code:​lab9_3|this]].  ​
-**Replace** the ''​firebaseConfig''​ values with those from your Firebase console:+
  
-<code javascript>​ +**Replace** the ''​firebaseConfig''​ values with those from your Firebase console.
-// ===== 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}`;​ +
-      }); +
-  }); +
-}); +
- +
-</​code>​+
  
 +==== 4.4 chart.umd.min.js ====
  
 +Create ''​dashboard/​public/​chart.umd.min.js''​. You need this for rendering the charts locally. ​
  
 +The code is [[iothings:​laboratoare:​2025_code:​lab9_4|here]].
 ===== 5. Deploy Dashboard with Firebase Hosting ===== ===== 5. Deploy Dashboard with Firebase Hosting =====
  
iothings/laboratoare/2025/lab9.1763895147.txt.gz · Last modified: 2025/11/23 12:52 by dan.tudose
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0