From 7714a8d2e7b37dbd5fc12d097317310f32265979 Mon Sep 17 00:00:00 2001 From: HAFIZ SALMAN Date: Fri, 6 Jun 2025 04:46:28 +0530 Subject: [PATCH 01/12] created firebase database --- TemPro | 1 + Web/index.html | 4 +- Web/script.js | 185 ++++++++++++++++++++++++++++++++++++++++++------- Web/style.css | 2 +- 4 files changed, 166 insertions(+), 26 deletions(-) create mode 160000 TemPro diff --git a/TemPro b/TemPro new file mode 160000 index 0000000..8467fa9 --- /dev/null +++ b/TemPro @@ -0,0 +1 @@ +Subproject commit 8467fa9b6b02e9e7d4c1319033b14dba05c64e95 diff --git a/Web/index.html b/Web/index.html index 1b5ebff..b8fdeb7 100644 --- a/Web/index.html +++ b/Web/index.html @@ -7,6 +7,8 @@ Tempro - Live Temperature Monitoring + + @@ -25,7 +27,7 @@

🌡️ Live Temperature Monitor

- + \ No newline at end of file diff --git a/Web/script.js b/Web/script.js index 1deec0d..b988b8b 100644 --- a/Web/script.js +++ b/Web/script.js @@ -1,6 +1,48 @@ -const client = mqtt.connect('ws://serverip:9001', { - username: 'user', - password: 'password' +// Check if firebase is defined +if (typeof firebase === 'undefined') { + console.error('Firebase SDK not loaded. Ensure Firebase scripts are included in HTML.'); + Swal.fire({ + theme: 'dark', + icon: 'error', + title: 'Error', + text: 'Firebase SDK failed to load. Please check your internet connection or HTML script tags.', + timer: 5000, + showConfirmButton: false, + timerProgressBar: true + }); +} else { + // Firebase configuration + const firebaseConfig = { + apiKey: "AIzaSyDeNFTMYiKVVtrnTQHZFb_-Uhh307CrEaU", + authDomain: "temprocw.firebaseapp.com", + projectId: "temprocw", + storageBucket: "temprocw.firebasestorage.app", + messagingSenderId: "537290462610", + appId: "1:537290462610:web:0b653f682f4a177e9f1d4b" + }; + + // Initialize Firebase + try { + firebase.initializeApp(firebaseConfig); + } catch (error) { + console.error('Error initializing Firebase:', error); + Swal.fire({ + theme: 'dark', + icon: 'error', + title: 'Error', + text: 'Failed to initialize Firebase. Check your configuration.', + timer: 5000, + showConfirmButton: false, + timerProgressBar: true + }); + } + const db = firebase.firestore(); +} + +// MQTT connection +const client = mqtt.connect('ws://dev.streakon.net:9001', { + username: 'tempro', + password: 'firstfloor' }); const sensorDataMap = new Map(); @@ -8,6 +50,7 @@ const sensorLastSeenMap = new Map(); const sensorOffsetMap = new Map(); // Calibrated Offset Map const ctx = document.getElementById('tempChart').getContext('2d'); const mqttStatus = document.getElementById('mqttStatus'); +const statusText = document.getElementById('statusText'); const chart = new Chart(ctx, { type: 'line', @@ -20,9 +63,9 @@ const chart = new Chart(ctx, { data: [], borderColor: (context) => { const value = context.raw; - if (value > 30) return 'red'; // High danger - if (value > 25) return 'orange'; // Warm - return 'hsl(189, 62.40%, 38.60%)'; // Safe (green) + if (value > 30) return 'red'; + if (value > 25) return 'orange'; + return 'hsl(189, 62.40%, 38.60%)'; }, backgroundColor: 'rgba(59, 130, 246, 0.1)', fill: true, @@ -40,7 +83,7 @@ const chart = new Chart(ctx, { }, { label: 'Calibration Baseline', - data: [], // Will be filled dynamically + data: [], borderColor: '#1d8cf8', borderDash: [5, 5], borderWidth: 2, @@ -53,9 +96,9 @@ const chart = new Chart(ctx, { data: [], borderColor: (context) => { const value = context.raw; - if (value > 30) return 'red'; // High danger - if (value > 25) return 'orange'; // Warm - return 'hsl(115, 62.40%, 38.60%)'; // Safe (green) + if (value > 30) return 'red'; + if (value > 25) return 'orange'; + return 'hsl(115, 62.40%, 38.60%)'; }, backgroundColor: 'rgba(59, 246, 246, 0.1)', fill: true, @@ -105,6 +148,96 @@ const chart = new Chart(ctx, { } }); +// Load offsets and baseline from Firestore on startup +function loadOffsetsFromFirestore() { + if (!firebase.firestore) { + console.error('Firestore not available. Skipping offset load.'); + return; + } + db.collection('sensorOffsets').get() + .then((snapshot) => { + sensorOffsetMap.clear(); + let hasOffsets = false; + snapshot.forEach((doc) => { + if (doc.id !== 'calibrationBaseline') { + sensorOffsetMap.set(doc.id, doc.data().offset); + hasOffsets = true; + } + }); + console.log('Offsets loaded from Firestore:', Array.from(sensorOffsetMap.entries())); + // If offsets exist, show calibrated dataset and hide uncalibrated + if (hasOffsets) { + chart.getDatasetMeta(0).hidden = true; + chart.getDatasetMeta(2).hidden = false; + } + // Load calibration baseline + db.collection('sensorOffsets').doc('calibrationBaseline').get() + .then((doc) => { + if (doc.exists) { + calibrationBaseline = doc.data().baseline; + console.log('Calibration baseline loaded from Firestore:', calibrationBaseline); + } + updateChart(); + }) + .catch((error) => { + console.error('Error loading calibration baseline from Firestore:', error); + }); + }) + .catch((error) => { + console.error('Error loading offsets from Firestore:', error); + Swal.fire({ + theme: 'dark', + icon: 'error', + title: 'Error', + text: 'Failed to load offsets from Firestore.', + timer: 3000, + showConfirmButton: false, + timerProgressBar: true + }); + }); +} + +// Save offsets and baseline to Firestore +function saveOffsetsToFirestore() { + if (!firebase.firestore) { + console.error('Firestore not available. Skipping offset save.'); + return; + } + const promises = []; + for (const [sensor, offset] of sensorOffsetMap.entries()) { + promises.push( + db.collection('sensorOffsets').doc(sensor).set({ + offset: offset, + timestamp: new Date().toISOString() + }) + ); + } + // Save calibration baseline + if (calibrationBaseline !== null) { + promises.push( + db.collection('sensorOffsets').doc('calibrationBaseline').set({ + baseline: calibrationBaseline, + timestamp: new Date().toISOString() + }) + ); + } + Promise.all(promises) + .then(() => { + console.log('Offsets and baseline saved to Firestore:', Array.from(sensorOffsetMap.entries()), calibrationBaseline); + }) + .catch((error) => { + console.error('Error saving offsets to Firestore:', error); + Swal.fire({ + theme: 'dark', + icon: 'error', + title: 'Error', + text: 'Failed to save offsets to Firestore.', + timer: 3000, + showConfirmButton: false, + timerProgressBar: true + }); + }); +} function updateChart() { const now = Date.now(); @@ -153,11 +286,9 @@ function updateChart() { } } - chart.update(); - - chart.update(); } + let calibrationBaseline = null; document.getElementById('calibrateBtn').addEventListener('click', async () => { const { isConfirmed } = await Swal.fire({ @@ -170,7 +301,7 @@ document.getElementById('calibrateBtn').addEventListener('click', async () => { allowEscapeKey: false, }); - if (!isConfirmed) return; // If user cancels, do nothing + if (!isConfirmed) return; Swal.fire({ theme: 'dark', title: 'Calibrating...', @@ -180,6 +311,7 @@ document.getElementById('calibrateBtn').addEventListener('click', async () => { allowOutsideClick: false, allowEscapeKey: false }); + const values = Array.from(sensorDataMap.values()); if (values.length === 0) { @@ -203,11 +335,15 @@ document.getElementById('calibrateBtn').addEventListener('click', async () => { sensorOffsetMap.clear(); for (const [sensor, temp] of sensorDataMap.entries()) { - sensorOffsetMap.set(sensor, Median - temp); // To Save The Offset for every Sensor! + sensorOffsetMap.set(sensor, Median - temp); } calibrationBaseline = Median; chart.getDatasetMeta(0).hidden = true; chart.getDatasetMeta(2).hidden = false; + + // Save offsets to Firestore + saveOffsetsToFirestore(); + const allOffsetsAreZero = Array.from(sensorOffsetMap.values()).every(offset => Math.abs(offset) < 0.1); if (allOffsetsAreZero) { @@ -218,7 +354,7 @@ document.getElementById('calibrateBtn').addEventListener('click', async () => { ` }); } - setTimeout(() => { // simulate a bit of delay + setTimeout(() => { Swal.fire({ theme: 'dark', icon: 'success', @@ -226,6 +362,7 @@ document.getElementById('calibrateBtn').addEventListener('click', async () => { html: `

Baseline set to ${calibrationBaseline.toFixed(2)}°C

Important: Now place all sensors in their appropriate positions as usual.

+

Offsets saved to Firestore.

${allOffsetsAreZero ? `

Fun fact: All your sensors were already reporting nearly identical temperatures! 📟

` : ''} `, confirmButtonText: 'Got it!', @@ -233,13 +370,9 @@ document.getElementById('calibrateBtn').addEventListener('click', async () => { }, 1500); }); - - client.on('connect', () => { mqttStatus.textContent = 'MQTT: Connected'; - mqttStatus.classList.remove('text-red-500'); - mqttStatus.classList.add('text-green-400'); - + mqttStatus.className = 'text-green-400'; for (let i = 1; i <= 10; i++) { client.subscribe(`tempro/sensor${i}`); } @@ -258,8 +391,12 @@ client.on('message', (topic, message) => { client.on('error', () => { mqttStatus.textContent = 'MQTT: Error'; - mqttStatus.classList.remove('text-green-400'); - mqttStatus.classList.add('text-red-500'); + mqttStatus.className = 'text-red-500'; }); -setInterval(updateChart, 5000); +// Load offsets from Firestore on startup +if (firebase.firestore) { + loadOffsetsFromFirestore(); +} + +setInterval(updateChart, 5000); \ No newline at end of file diff --git a/Web/style.css b/Web/style.css index 7361e91..7b59d17 100644 --- a/Web/style.css +++ b/Web/style.css @@ -7,7 +7,7 @@ body { font-family: 'Orbitron', sans-serif; font-weight: 300; letter-spacing: 0.5px; - overflow: hidden; + overflow: auto; height: 100%; display: flex; align-items: center; From 8e1d0dbe76993151f81142ff46ffe062708176ec Mon Sep 17 00:00:00 2001 From: HAFIZ SALMAN Date: Fri, 6 Jun 2025 04:48:10 +0530 Subject: [PATCH 02/12] deleted extra folder --- TemPro | 1 - 1 file changed, 1 deletion(-) delete mode 160000 TemPro diff --git a/TemPro b/TemPro deleted file mode 160000 index 8467fa9..0000000 --- a/TemPro +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8467fa9b6b02e9e7d4c1319033b14dba05c64e95 From f9710773332252db1e60b0c32ae34e7760f5f346 Mon Sep 17 00:00:00 2001 From: HAFIZ SALMAN Date: Fri, 6 Jun 2025 04:50:45 +0530 Subject: [PATCH 03/12] calibration refresh solved --- Web/script.js | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/Web/script.js b/Web/script.js index b988b8b..14f8edf 100644 --- a/Web/script.js +++ b/Web/script.js @@ -1,4 +1,4 @@ -// Check if firebase is defined +// Check if firebase is defined and initialize it if (typeof firebase === 'undefined') { console.error('Firebase SDK not loaded. Ensure Firebase scripts are included in HTML.'); Swal.fire({ @@ -16,14 +16,16 @@ if (typeof firebase === 'undefined') { apiKey: "AIzaSyDeNFTMYiKVVtrnTQHZFb_-Uhh307CrEaU", authDomain: "temprocw.firebaseapp.com", projectId: "temprocw", - storageBucket: "temprocw.firebasestorage.app", + storageBucket: "temprocw.appspot.com", // FIXED bucket domain messagingSenderId: "537290462610", appId: "1:537290462610:web:0b653f682f4a177e9f1d4b" }; - // Initialize Firebase + // Initialize Firebase safely try { - firebase.initializeApp(firebaseConfig); + if (!firebase.apps.length) { + firebase.initializeApp(firebaseConfig); + } } catch (error) { console.error('Error initializing Firebase:', error); Swal.fire({ @@ -36,9 +38,25 @@ if (typeof firebase === 'undefined') { timerProgressBar: true }); } - const db = firebase.firestore(); + + // Safe firestore access + if (!firebase.firestore) { + console.error('Firestore module not loaded.'); + Swal.fire({ + theme: 'dark', + icon: 'error', + title: 'Error', + text: 'Firebase Firestore SDK not loaded.', + timer: 5000, + showConfirmButton: false, + timerProgressBar: true + }); + } } +const db = firebase.firestore(); + + // MQTT connection const client = mqtt.connect('ws://dev.streakon.net:9001', { username: 'tempro', From f4dedb85af5050777628e62e2f6ff109121d19f7 Mon Sep 17 00:00:00 2001 From: HAFIZ SALMAN Date: Fri, 6 Jun 2025 05:52:52 +0530 Subject: [PATCH 04/12] added last seen --- Web/index.html | 49 ++++-- Web/script.js | 427 +++++++++++++++++++------------------------------ 2 files changed, 202 insertions(+), 274 deletions(-) diff --git a/Web/index.html b/Web/index.html index b8fdeb7..bbb33c5 100644 --- a/Web/index.html +++ b/Web/index.html @@ -11,17 +11,48 @@ - -
-
-

🌡️ Live Temperature Monitor

- MQTT: Connecting... + + +
+
+

🌡️ Live Temperature Monitor

+ MQTT: Connecting... +
+ +
+ Status: Normal
-
Status: Normal
-
- + +
+ +
+
+ +
+ +
+ + +
+

📅 Sensor Last Seen

+
+ + + + + + + + + + + +
SensorTempSeen
+
+
-
diff --git a/Web/script.js b/Web/script.js index 14f8edf..d2f2761 100644 --- a/Web/script.js +++ b/Web/script.js @@ -1,74 +1,47 @@ -// Check if firebase is defined and initialize it -if (typeof firebase === 'undefined') { - console.error('Firebase SDK not loaded. Ensure Firebase scripts are included in HTML.'); +// SweetAlert Error Alert +function showErrorAlert(message) { Swal.fire({ - theme: 'dark', + background: '#1e293b', icon: 'error', title: 'Error', - text: 'Firebase SDK failed to load. Please check your internet connection or HTML script tags.', + text: message, timer: 5000, showConfirmButton: false, timerProgressBar: true }); -} else { - // Firebase configuration - const firebaseConfig = { - apiKey: "AIzaSyDeNFTMYiKVVtrnTQHZFb_-Uhh307CrEaU", - authDomain: "temprocw.firebaseapp.com", - projectId: "temprocw", - storageBucket: "temprocw.appspot.com", // FIXED bucket domain - messagingSenderId: "537290462610", - appId: "1:537290462610:web:0b653f682f4a177e9f1d4b" - }; - - // Initialize Firebase safely - try { - if (!firebase.apps.length) { - firebase.initializeApp(firebaseConfig); - } - } catch (error) { - console.error('Error initializing Firebase:', error); - Swal.fire({ - theme: 'dark', - icon: 'error', - title: 'Error', - text: 'Failed to initialize Firebase. Check your configuration.', - timer: 5000, - showConfirmButton: false, - timerProgressBar: true - }); - } +} - // Safe firestore access - if (!firebase.firestore) { - console.error('Firestore module not loaded.'); - Swal.fire({ - theme: 'dark', - icon: 'error', - title: 'Error', - text: 'Firebase Firestore SDK not loaded.', - timer: 5000, - showConfirmButton: false, - timerProgressBar: true - }); - } +// Firebase Configuration +const firebaseConfig = { + apiKey: "AIzaSyDeNFTMYiKVVtrnTQHZFb_-Uhh307CrEaU", + authDomain: "temprocw.firebaseapp.com", + projectId: "temprocw", + storageBucket: "temprocw.appspot.com", + messagingSenderId: "537290462610", + appId: "1:537290462610:web:0b653f682f4a177e9f1d4b" +}; + +try { + if (!firebase.apps.length) firebase.initializeApp(firebaseConfig); +} catch (error) { + console.error('Firebase init error:', error); + showErrorAlert('Failed to initialize Firebase.'); } const db = firebase.firestore(); - - -// MQTT connection const client = mqtt.connect('ws://dev.streakon.net:9001', { username: 'tempro', password: 'firstfloor' }); -const sensorDataMap = new Map(); -const sensorLastSeenMap = new Map(); -const sensorOffsetMap = new Map(); // Calibrated Offset Map -const ctx = document.getElementById('tempChart').getContext('2d'); const mqttStatus = document.getElementById('mqttStatus'); const statusText = document.getElementById('statusText'); +const ctx = document.getElementById('tempChart').getContext('2d'); + +const sensorDataMap = new Map(); +const sensorLastSeenMap = new Map(); +const sensorOffsetMap = new Map(); +let calibrationBaseline = null; const chart = new Chart(ctx, { type: 'line', @@ -77,14 +50,9 @@ const chart = new Chart(ctx, { datasets: [ { label: 'Temperature (°C)', - hidden: false, data: [], - borderColor: (context) => { - const value = context.raw; - if (value > 30) return 'red'; - if (value > 25) return 'orange'; - return 'hsl(189, 62.40%, 38.60%)'; - }, + hidden: false, + borderColor: ({ raw }) => raw > 30 ? 'red' : raw > 25 ? 'orange' : 'hsl(189, 62.40%, 38.60%)', backgroundColor: 'rgba(59, 130, 246, 0.1)', fill: true, tension: 0.4, @@ -92,12 +60,7 @@ const chart = new Chart(ctx, { pointHoverRadius: 8, pointBorderWidth: 2, pointBorderColor: '#ffffff', - pointBackgroundColor: (context) => { - const value = context.raw; - if (value > 30) return 'red'; - if (value > 25) return 'orange'; - return '#4caf50'; - }, + pointBackgroundColor: ({ raw }) => raw > 30 ? 'red' : raw > 25 ? 'orange' : '#4caf50', }, { label: 'Calibration Baseline', @@ -110,14 +73,9 @@ const chart = new Chart(ctx, { }, { label: 'Calibrated Temperature (°C)', - hidden: true, data: [], - borderColor: (context) => { - const value = context.raw; - if (value > 30) return 'red'; - if (value > 25) return 'orange'; - return 'hsl(115, 62.40%, 38.60%)'; - }, + hidden: true, + borderColor: ({ raw }) => raw > 30 ? 'red' : raw > 25 ? 'orange' : 'hsl(115, 62.40%, 38.60%)', backgroundColor: 'rgba(59, 246, 246, 0.1)', fill: true, tension: 0.4, @@ -125,12 +83,7 @@ const chart = new Chart(ctx, { pointHoverRadius: 8, pointBorderWidth: 2, pointBorderColor: '#ffffff', - pointBackgroundColor: (context) => { - const value = context.raw; - if (value > 30) return 'red'; - if (value > 25) return 'orange'; - return '#4caf50'; - }, + pointBackgroundColor: ({ raw }) => raw > 30 ? 'red' : raw > 25 ? 'orange' : '#4caf50', } ] }, @@ -139,122 +92,46 @@ const chart = new Chart(ctx, { maintainAspectRatio: false, scales: { y: { - beginAtZero: false, - title: { - display: true, - text: 'Temperature (°C)', - color: '#ffffff' - }, + title: { display: true, text: 'Temperature (°C)', color: '#ffffff' }, ticks: { color: '#ffffff' }, grid: { color: 'rgba(255, 255, 255, 0.1)' } }, x: { - title: { - display: true, - text: 'Sensor ID', - color: '#ffffff' - }, + title: { display: true, text: 'Sensor ID', color: '#ffffff' }, ticks: { color: '#ffffff' }, grid: { color: 'rgba(255, 255, 255, 0.05)' } } }, plugins: { - legend: { - labels: { color: '#ffffff' } - } + legend: { labels: { color: '#ffffff' } } } } }); -// Load offsets and baseline from Firestore on startup -function loadOffsetsFromFirestore() { - if (!firebase.firestore) { - console.error('Firestore not available. Skipping offset load.'); - return; +// Last Seen Table Update +function updateLastSeenTable() { + const tbody = document.getElementById('lastSeenTableBody'); + if (!tbody) return; + + tbody.innerHTML = ''; + + const entries = [...sensorDataMap.entries()].sort((a, b) => + parseInt(a[0].replace('sensor', '')) - parseInt(b[0].replace('sensor', '')) + ); + + for (const [sensor, temp] of entries) { + const lastSeen = sensorLastSeenMap.get(sensor); + const lastSeenTime = lastSeen ? new Date(lastSeen).toLocaleTimeString() : 'N/A'; + + const row = ` + + ${sensor} + ${temp.toFixed(2)} + ${lastSeenTime} + + `; + tbody.insertAdjacentHTML('beforeend', row); } - db.collection('sensorOffsets').get() - .then((snapshot) => { - sensorOffsetMap.clear(); - let hasOffsets = false; - snapshot.forEach((doc) => { - if (doc.id !== 'calibrationBaseline') { - sensorOffsetMap.set(doc.id, doc.data().offset); - hasOffsets = true; - } - }); - console.log('Offsets loaded from Firestore:', Array.from(sensorOffsetMap.entries())); - // If offsets exist, show calibrated dataset and hide uncalibrated - if (hasOffsets) { - chart.getDatasetMeta(0).hidden = true; - chart.getDatasetMeta(2).hidden = false; - } - // Load calibration baseline - db.collection('sensorOffsets').doc('calibrationBaseline').get() - .then((doc) => { - if (doc.exists) { - calibrationBaseline = doc.data().baseline; - console.log('Calibration baseline loaded from Firestore:', calibrationBaseline); - } - updateChart(); - }) - .catch((error) => { - console.error('Error loading calibration baseline from Firestore:', error); - }); - }) - .catch((error) => { - console.error('Error loading offsets from Firestore:', error); - Swal.fire({ - theme: 'dark', - icon: 'error', - title: 'Error', - text: 'Failed to load offsets from Firestore.', - timer: 3000, - showConfirmButton: false, - timerProgressBar: true - }); - }); -} - -// Save offsets and baseline to Firestore -function saveOffsetsToFirestore() { - if (!firebase.firestore) { - console.error('Firestore not available. Skipping offset save.'); - return; - } - const promises = []; - for (const [sensor, offset] of sensorOffsetMap.entries()) { - promises.push( - db.collection('sensorOffsets').doc(sensor).set({ - offset: offset, - timestamp: new Date().toISOString() - }) - ); - } - // Save calibration baseline - if (calibrationBaseline !== null) { - promises.push( - db.collection('sensorOffsets').doc('calibrationBaseline').set({ - baseline: calibrationBaseline, - timestamp: new Date().toISOString() - }) - ); - } - Promise.all(promises) - .then(() => { - console.log('Offsets and baseline saved to Firestore:', Array.from(sensorOffsetMap.entries()), calibrationBaseline); - }) - .catch((error) => { - console.error('Error saving offsets to Firestore:', error); - Swal.fire({ - theme: 'dark', - icon: 'error', - title: 'Error', - text: 'Failed to save offsets to Firestore.', - timer: 3000, - showConfirmButton: false, - timerProgressBar: true - }); - }); } function updateChart() { @@ -267,143 +144,166 @@ function updateChart() { } } - const entries = Array.from(sensorDataMap.entries()).sort((a, b) => { - const aNum = parseInt(a[0].replace('sensor', '')); - const bNum = parseInt(b[0].replace('sensor', '')); - return aNum - bNum; - }); + const entries = [...sensorDataMap.entries()].sort((a, b) => + parseInt(a[0].replace('sensor', '')) - parseInt(b[0].replace('sensor', '')) + ); const labels = entries.map(([sensor]) => sensor); - const values = entries.map(([sensor, temp]) => temp); - const calibratedValues = entries.map(([sensor, temp]) => temp + (sensorOffsetMap.get(sensor) || 0)); + const values = entries.map(([_, temp]) => temp); + const calibrated = entries.map(([sensor, temp]) => temp + (sensorOffsetMap.get(sensor) || 0)); chart.data.labels = labels; chart.data.datasets[0].data = values; - chart.data.datasets[2].data = calibratedValues; - if (values.length > 0) { - const minTemp = Math.min(...values, ...calibratedValues); - const maxTemp = Math.max(...values, ...calibratedValues); - chart.options.scales.y.min = Math.floor(minTemp - 5); - chart.options.scales.y.max = Math.ceil(maxTemp + 5); - - const highestTemp = Math.max(...calibratedValues); - - if (highestTemp > 30) { - statusText.textContent = 'High'; - statusText.className = 'text-red-500 font-bold'; - } else if (highestTemp > 25) { - statusText.textContent = 'Medium'; - statusText.className = 'text-orange-500 font-bold'; - } else { - statusText.textContent = 'Normal'; - statusText.className = 'text-green-500 font-bold'; - } + chart.data.datasets[2].data = calibrated; + + if (values.length) { + const min = Math.min(...values, ...calibrated); + const max = Math.max(...values, ...calibrated); + chart.options.scales.y.min = Math.floor(min - 5); + chart.options.scales.y.max = Math.ceil(max + 5); + + const maxCalibrated = Math.max(...calibrated); + statusText.textContent = maxCalibrated > 30 ? 'High' : maxCalibrated > 25 ? 'Medium' : 'Normal'; + statusText.className = maxCalibrated > 30 + ? 'text-red-500 font-bold' + : maxCalibrated > 25 + ? 'text-orange-500 font-bold' + : 'text-green-500 font-bold'; if (calibrationBaseline !== null) { - chart.data.datasets[1].data = chart.data.labels.map(() => calibrationBaseline); + chart.data.datasets[1].data = labels.map(() => calibrationBaseline); } } chart.update(); } -let calibrationBaseline = null; +// Firestore Load/Save +function loadOffsetsFromFirestore() { + db.collection('sensorOffsets').onSnapshot(snapshot => { + sensorOffsetMap.clear(); + let hasOffsets = false; + + snapshot.forEach(doc => { + if (doc.id === 'calibrationBaseline') { + calibrationBaseline = doc.data().baseline; + } else { + sensorOffsetMap.set(doc.id, doc.data().offset); + hasOffsets = true; + } + }); + + chart.getDatasetMeta(0).hidden = hasOffsets; + chart.getDatasetMeta(2).hidden = !hasOffsets; + updateChart(); + }, error => { + console.error('Firestore error:', error); + showErrorAlert('Failed to load sensor offsets.'); + }); +} + +function saveOffsetsToFirestore() { + const updates = [...sensorOffsetMap.entries()].map(([sensor, offset]) => + db.collection('sensorOffsets').doc(sensor).set({ offset, timestamp: new Date().toISOString() }) + ); + + if (calibrationBaseline !== null) { + updates.push(db.collection('sensorOffsets').doc('calibrationBaseline').set({ + baseline: calibrationBaseline, + timestamp: new Date().toISOString() + })); + } + + Promise.all(updates) + .then(() => console.log('Calibration data saved.')) + .catch(error => { + console.error('Save error:', error); + showErrorAlert('Failed to save calibration data.'); + }); +} + document.getElementById('calibrateBtn').addEventListener('click', async () => { const { isConfirmed } = await Swal.fire({ - theme: 'dark', + background: '#1e293b', title: 'Preparation', - text: 'Please place all sensors in the same position for calibration.', + text: 'Please place all sensors together.', icon: 'info', confirmButtonText: 'Ready', allowOutsideClick: false, - allowEscapeKey: false, + allowEscapeKey: false }); if (!isConfirmed) return; + Swal.fire({ - theme: 'dark', + background: '#1e293b', title: 'Calibrating...', - didOpen: () => { - Swal.showLoading(); - }, + didOpen: () => Swal.showLoading(), allowOutsideClick: false, allowEscapeKey: false }); - const values = Array.from(sensorDataMap.values()); + const temps = [...sensorDataMap.values()]; + if (!temps.length) return showErrorAlert('No sensor data found.'); - if (values.length === 0) { - Swal.fire({ - theme: 'dark', - icon: 'error', - title: 'Oops...', - text: 'No sensor data available for calibration.', - timer: 3000, - showConfirmButton: false, - timerProgressBar: true - }); - return; - } - - const sorted = [...values].sort((a, b) => a - b); - const middle = Math.floor(sorted.length / 2); - const Median = sorted.length % 2 !== 0 - ? sorted[middle] - : (sorted[middle - 1] + sorted[middle]) / 2; + const sorted = temps.sort((a, b) => a - b); + const median = sorted.length % 2 + ? sorted[Math.floor(sorted.length / 2)] + : (sorted[sorted.length / 2 - 1] + sorted[sorted.length / 2]) / 2; sensorOffsetMap.clear(); for (const [sensor, temp] of sensorDataMap.entries()) { - sensorOffsetMap.set(sensor, Median - temp); + sensorOffsetMap.set(sensor, median - temp); } - calibrationBaseline = Median; + + calibrationBaseline = median; chart.getDatasetMeta(0).hidden = true; chart.getDatasetMeta(2).hidden = false; - - // Save offsets to Firestore saveOffsetsToFirestore(); - const allOffsetsAreZero = Array.from(sensorOffsetMap.values()).every(offset => Math.abs(offset) < 0.1); + const allZero = [...sensorOffsetMap.values()].every(o => Math.abs(o) < 0.1); - if (allOffsetsAreZero) { - Swal.update({ - html: ` -

Analyzing sensor data...

-

Fun Fact: All your sensors are already in great sync! 🎯

- ` - }); - } setTimeout(() => { Swal.fire({ - theme: 'dark', + background: '#1e293b', icon: 'success', title: 'Calibration Complete!', html: ` -

Baseline set to ${calibrationBaseline.toFixed(2)}°C

-

Important: Now place all sensors in their appropriate positions as usual.

-

Offsets saved to Firestore.

- ${allOffsetsAreZero ? `

Fun fact: All your sensors were already reporting nearly identical temperatures! 📟

` : ''} - `, - confirmButtonText: 'Got it!', +

Baseline set to ${median.toFixed(2)}°C

+

Offsets saved to Firestore.

+ ${allZero ? `

Sensors were already aligned 🎯

` : ''} + `, + confirmButtonText: 'Done' }); }, 1500); }); +// MQTT Client Events client.on('connect', () => { mqttStatus.textContent = 'MQTT: Connected'; mqttStatus.className = 'text-green-400'; - for (let i = 1; i <= 10; i++) { - client.subscribe(`tempro/sensor${i}`); - } + for (let i = 1; i <= 10; i++) client.subscribe(`tempro/sensor${i}`); }); client.on('message', (topic, message) => { const sensor = topic.split('/')[1]; const temp = parseFloat(message.toString()); + const timestamp = new Date(); if (!isNaN(temp)) { sensorDataMap.set(sensor, temp); - sensorLastSeenMap.set(sensor, Date.now()); + sensorLastSeenMap.set(sensor, timestamp.getTime()); + updateChart(); + updateLastSeenTable(); // 👈 Added here + + db.collection('sensorLastSeen').doc(sensor).set({ + temperature: temp, + lastSeen: timestamp.toISOString() + }, { merge: true }).catch(error => { + console.error(`Failed to update lastSeen for ${sensor}:`, error); + showErrorAlert(`Firestore error: Could not save last seen for ${sensor}`); + }); } }); @@ -412,9 +312,6 @@ client.on('error', () => { mqttStatus.className = 'text-red-500'; }); -// Load offsets from Firestore on startup -if (firebase.firestore) { - loadOffsetsFromFirestore(); -} - -setInterval(updateChart, 5000); \ No newline at end of file +// Start +loadOffsetsFromFirestore(); +setInterval(updateChart, 5000); From 94d77d2f5b52ac5212e75f6ed0f0c08c8dc67036 Mon Sep 17 00:00:00 2001 From: Hadin AH Date: Fri, 6 Jun 2025 06:08:09 +0530 Subject: [PATCH 05/12] Update script.js --- Web/script.js | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/Web/script.js b/Web/script.js index d2f2761..70c5f20 100644 --- a/Web/script.js +++ b/Web/script.js @@ -178,6 +178,39 @@ function updateChart() { chart.update(); } +async function updateTemperatureLogs() { + if (sensorDataMap.size === 0) return; // No data to save + + // Use current ISO timestamp as doc ID (second precision) + const now = new Date(); + // Cut off milliseconds to reduce duplicates — ISO string to seconds only + const timestampId = now.toISOString().split('.')[0] + 'Z'; // e.g. "2025-06-06T11:30:00Z" + + const docRef = db.collection('temperature_logs').doc(timestampId); + + try { + const doc = await docRef.get(); + if (doc.exists) { + console.log(`Temperature log for ${timestampId} already exists, skipping write.`); + return; // Or you could update if you want + } + + // Prepare readings object from sensorDataMap + const readings = {}; + sensorDataMap.forEach((temp, sensor) => { + readings[sensor] = temp; + }); + + await docRef.set({ readings, timestamp: now.toISOString() }); + + console.log(`Temperature log saved at ${timestampId}`); + + } catch (error) { + console.error('Error saving temperature log:', error); + showErrorAlert('Failed to save temperature log.'); + } +} + // Firestore Load/Save function loadOffsetsFromFirestore() { db.collection('sensorOffsets').onSnapshot(snapshot => { @@ -315,3 +348,4 @@ client.on('error', () => { // Start loadOffsetsFromFirestore(); setInterval(updateChart, 5000); +setInterval(updateTemperatureLogs, 20000); From 19cade75311bc27086110742bdf413548693a4f2 Mon Sep 17 00:00:00 2001 From: Hadin AH Date: Fri, 6 Jun 2025 06:11:46 +0530 Subject: [PATCH 06/12] Update script.js --- Web/script.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Web/script.js b/Web/script.js index 70c5f20..3c05ab7 100644 --- a/Web/script.js +++ b/Web/script.js @@ -201,7 +201,7 @@ async function updateTemperatureLogs() { readings[sensor] = temp; }); - await docRef.set({ readings, timestamp: now.toISOString() }); + await docRef.set({ readings, timestamp: timestampId }); console.log(`Temperature log saved at ${timestampId}`); From 78162e0c3b722a95cbc15f9b39b50e6895bc2102 Mon Sep 17 00:00:00 2001 From: Hadin AH Date: Fri, 6 Jun 2025 06:12:42 +0530 Subject: [PATCH 07/12] Added Temperature Logs Saves history/log every 20 sec --- Web/script.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Web/script.js b/Web/script.js index 3c05ab7..2a47e6a 100644 --- a/Web/script.js +++ b/Web/script.js @@ -186,7 +186,7 @@ async function updateTemperatureLogs() { // Cut off milliseconds to reduce duplicates — ISO string to seconds only const timestampId = now.toISOString().split('.')[0] + 'Z'; // e.g. "2025-06-06T11:30:00Z" - const docRef = db.collection('temperature_logs').doc(timestampId); + const docRef = db.collection('temperatureLogs').doc(timestampId); try { const doc = await docRef.get(); From c50c67cf2602347569dfa395bc9e3fcfafd2af75 Mon Sep 17 00:00:00 2001 From: Hadin AH Date: Fri, 6 Jun 2025 06:39:26 +0530 Subject: [PATCH 08/12] Added table to show logs --- Web/index.html | 29 ++++++++++++++++++++++++++--- Web/script.js | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 3 deletions(-) diff --git a/Web/index.html b/Web/index.html index bbb33c5..1fd1863 100644 --- a/Web/index.html +++ b/Web/index.html @@ -5,9 +5,9 @@ Tempro - Live Temperature Monitoring - + - + @@ -53,6 +53,29 @@

📅 Sensor Last Seen

+ + +
+
+

🗂️ Temperature Logs

+ +
+ +
@@ -61,4 +84,4 @@

📅 Sensor Last Seen

- \ No newline at end of file + diff --git a/Web/script.js b/Web/script.js index 2a47e6a..8ee87ec 100644 --- a/Web/script.js +++ b/Web/script.js @@ -235,6 +235,56 @@ function loadOffsetsFromFirestore() { }); } +document.getElementById('loadLogsBtn').addEventListener('click', async () => { + const logsContainer = document.getElementById('logsContainer'); + const logsTableBody = document.getElementById('logsTableBody'); + + // Toggle visibility + if (!logsContainer.classList.contains('hidden')) { + logsContainer.classList.add('hidden'); + return; + } + + logsTableBody.innerHTML = 'Loading...'; + logsContainer.classList.remove('hidden'); + + try { + const snapshot = await db.collection('temperatureLogs') + .orderBy('timestamp', 'desc') + .limit(10) + .get(); + + if (snapshot.empty) { + logsTableBody.innerHTML = 'No logs found.'; + return; + } + + logsTableBody.innerHTML = ''; + + snapshot.forEach(doc => { + const data = doc.data(); + const timestamp = new Date(data.timestamp).toLocaleString(); + + // Format readings as "sensor: value" pairs joined by commas + const readings = data.readings || {}; + const readingsText = Object.entries(readings) + .map(([sensor, temp]) => `${sensor}: ${temp.toFixed(2)}°C`) + .join(', '); + + const row = ` + + ${timestamp} + ${readingsText} + + `; + logsTableBody.insertAdjacentHTML('beforeend', row); + }); + } catch (error) { + logsTableBody.innerHTML = `Error loading logs.`; + console.error('Error loading temperature logs:', error); + } +}); + function saveOffsetsToFirestore() { const updates = [...sensorOffsetMap.entries()].map(([sensor, offset]) => db.collection('sensorOffsets').doc(sensor).set({ offset, timestamp: new Date().toISOString() }) From fbe5a7fd4194f1e4b0fb80ddf6680b18ad7b4708 Mon Sep 17 00:00:00 2001 From: Hadin AH Date: Fri, 6 Jun 2025 06:57:08 +0530 Subject: [PATCH 09/12] Update index.html Co-Authored-By: HAFIZ SALMAN --- Web/index.html | 1 - 1 file changed, 1 deletion(-) diff --git a/Web/index.html b/Web/index.html index 1fd1863..c11a049 100644 --- a/Web/index.html +++ b/Web/index.html @@ -54,7 +54,6 @@

📅 Sensor Last Seen

-

🗂️ Temperature Logs

From b3d4ee0864d6462d0cc2ddc4826b79a08b03ad54 Mon Sep 17 00:00:00 2001 From: Hadin AH Date: Fri, 6 Jun 2025 21:45:49 +0530 Subject: [PATCH 10/12] Added Temperature Log Graph --- Web/index.html | 12 ++++++---- Web/script.js | 64 ++++++++++++++++++++++++++++++++++++++++++++++---- Web/style.css | 4 +--- 3 files changed, 68 insertions(+), 12 deletions(-) diff --git a/Web/index.html b/Web/index.html index c11a049..215a498 100644 --- a/Web/index.html +++ b/Web/index.html @@ -24,7 +24,6 @@

🌡️ Live Temperature Monitor

-
@@ -34,7 +33,6 @@

🌡️ Live Temperature Monitor

-

📅 Sensor Last Seen

@@ -47,7 +45,6 @@

📅 Sensor Last Seen

-
@@ -70,11 +67,16 @@

🗂️ Temperature Logs

-
+
+

📊 Analytics

+
+ +
+
@@ -83,4 +85,4 @@

🗂️ Temperature Logs

- + \ No newline at end of file diff --git a/Web/script.js b/Web/script.js index 8ee87ec..7d862ce 100644 --- a/Web/script.js +++ b/Web/script.js @@ -108,6 +108,36 @@ const chart = new Chart(ctx, { } }); +const analyticsCtx = document.getElementById('analyticsChart').getContext('2d'); +const sensorColors = ['#4caf50', '#ff9800', '#2196f3', '#f44336', '#9c27b0', '#3f51b5', '#009688', '#795548', '#607d8b', '#00bcd4']; + +const analyticsChart = new Chart(analyticsCtx, { + type: 'line', + data: { + labels: [], + datasets: [] + }, + options: { + responsive: true, + maintainAspectRatio: false, + scales: { + y: { + title: { display: true, text: 'Temperature (°C)', color: '#ffffff' }, + ticks: { color: '#ffffff' }, + grid: { color: 'rgba(255, 255, 255, 0.1)' } + }, + x: { + title: { display: true, text: 'Time', color: '#ffffff' }, + ticks: { color: '#ffffff' }, + grid: { color: 'rgba(255, 255, 255, 0.05)' } + } + }, + plugins: { + legend: { labels: { color: '#ffffff' } } + } + } +}); + // Last Seen Table Update function updateLastSeenTable() { const tbody = document.getElementById('lastSeenTableBody'); @@ -239,7 +269,6 @@ document.getElementById('loadLogsBtn').addEventListener('click', async () => { const logsContainer = document.getElementById('logsContainer'); const logsTableBody = document.getElementById('logsTableBody'); - // Toggle visibility if (!logsContainer.classList.contains('hidden')) { logsContainer.classList.add('hidden'); return; @@ -250,8 +279,8 @@ document.getElementById('loadLogsBtn').addEventListener('click', async () => { try { const snapshot = await db.collection('temperatureLogs') - .orderBy('timestamp', 'desc') - .limit(10) + .orderBy('timestamp', 'asc') + .limit(100) .get(); if (snapshot.empty) { @@ -261,11 +290,15 @@ document.getElementById('loadLogsBtn').addEventListener('click', async () => { logsTableBody.innerHTML = ''; + const sensorSeries = {}; + const timestamps = []; + snapshot.forEach(doc => { const data = doc.data(); const timestamp = new Date(data.timestamp).toLocaleString(); + const timeLabel = new Date(data.timestamp).toLocaleTimeString(); + timestamps.push(timeLabel); - // Format readings as "sensor: value" pairs joined by commas const readings = data.readings || {}; const readingsText = Object.entries(readings) .map(([sensor, temp]) => `${sensor}: ${temp.toFixed(2)}°C`) @@ -278,13 +311,36 @@ document.getElementById('loadLogsBtn').addEventListener('click', async () => { `; logsTableBody.insertAdjacentHTML('beforeend', row); + + for (const [sensor, temp] of Object.entries(readings)) { + if (!sensorSeries[sensor]) sensorSeries[sensor] = []; + sensorSeries[sensor].push(temp); + } }); + + const datasets = Object.entries(sensorSeries).map(([sensor, temps], i) => ({ + label: sensor, + data: temps, + borderColor: sensorColors[i % sensorColors.length], + backgroundColor: 'transparent', + tension: 0.4, + fill: false, + pointRadius: 3, + pointHoverRadius: 6 + })); + + analyticsChart.data.labels = timestamps; + analyticsChart.data.datasets = datasets; + analyticsChart.update(); + } catch (error) { logsTableBody.innerHTML = `Error loading logs.`; console.error('Error loading temperature logs:', error); + showErrorAlert('Could not load logs.'); } }); + function saveOffsetsToFirestore() { const updates = [...sensorOffsetMap.entries()].map(([sensor, offset]) => db.collection('sensorOffsets').doc(sensor).set({ offset, timestamp: new Date().toISOString() }) diff --git a/Web/style.css b/Web/style.css index 7b59d17..ba4aefc 100644 --- a/Web/style.css +++ b/Web/style.css @@ -7,15 +7,13 @@ body { font-family: 'Orbitron', sans-serif; font-weight: 300; letter-spacing: 0.5px; - overflow: auto; - height: 100%; display: flex; align-items: center; justify-content: center; } /* Container styling */ -.main-container { +.main-container .analytic-container{ width: 100%; max-width: 80rem; height: 80vh; From e6ee819b3e445f6dd49873c9ff5850301cb514b4 Mon Sep 17 00:00:00 2001 From: Hadin AH Date: Sun, 8 Jun 2025 16:57:20 +0530 Subject: [PATCH 11/12] Update script.js --- Web/script.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Web/script.js b/Web/script.js index 7d862ce..cf53081 100644 --- a/Web/script.js +++ b/Web/script.js @@ -156,7 +156,7 @@ function updateLastSeenTable() { const row = ` ${sensor} - ${temp.toFixed(2)} + ${(temp+(sensorOffsetMap.get(sensor) || 0)).toFixed(2)} ${lastSeenTime} `; @@ -228,10 +228,10 @@ async function updateTemperatureLogs() { // Prepare readings object from sensorDataMap const readings = {}; sensorDataMap.forEach((temp, sensor) => { - readings[sensor] = temp; + readings[sensor] = temp + (sensorOffsetMap.get(sensor) || 0); }); - await docRef.set({ readings, timestamp: timestampId }); + await docRef.set({ readings, timestamp: now.toISOString() }); console.log(`Temperature log saved at ${timestampId}`); @@ -430,7 +430,7 @@ client.on('message', (topic, message) => { const timestamp = new Date(); if (!isNaN(temp)) { - sensorDataMap.set(sensor, temp); + sensorDataMap.set(sensor, temp + (sensorOffsetMap.get(sensor) || 0)); sensorLastSeenMap.set(sensor, timestamp.getTime()); updateChart(); From 79707c7f0ccbd21a28f0649e301d1817b9d2aac1 Mon Sep 17 00:00:00 2001 From: Hadin AH Date: Tue, 10 Jun 2025 23:02:56 +0530 Subject: [PATCH 12/12] Add Offset Debugging Fixed double offset issue and added some variables to console! --- Web/script.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Web/script.js b/Web/script.js index cf53081..40528ee 100644 --- a/Web/script.js +++ b/Web/script.js @@ -43,6 +43,9 @@ const sensorLastSeenMap = new Map(); const sensorOffsetMap = new Map(); let calibrationBaseline = null; +window.sensorDataMap = sensorDataMap; +window.sensorOffsetMap = sensorOffsetMap; + const chart = new Chart(ctx, { type: 'line', data: { @@ -230,7 +233,6 @@ async function updateTemperatureLogs() { sensorDataMap.forEach((temp, sensor) => { readings[sensor] = temp + (sensorOffsetMap.get(sensor) || 0); }); - await docRef.set({ readings, timestamp: now.toISOString() }); console.log(`Temperature log saved at ${timestampId}`); @@ -430,7 +432,7 @@ client.on('message', (topic, message) => { const timestamp = new Date(); if (!isNaN(temp)) { - sensorDataMap.set(sensor, temp + (sensorOffsetMap.get(sensor) || 0)); + sensorDataMap.set(sensor, temp); sensorLastSeenMap.set(sensor, timestamp.getTime()); updateChart();