Differences

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

Link to this comparison view

iothings:proiecte:2025sric:esp32environmentalcomfortmonitoringsystemweb [2025/05/29 04:50]
iustin.burciu [Firebase Integration Sketch]
iothings:proiecte:2025sric:esp32environmentalcomfortmonitoringsystemweb [2025/05/29 05:41] (current)
iustin.burciu [Local Web Server Sketch]
Line 41: Line 41:
 | LED Blue  | GPIO25 ​    | Lights up at comfort score 100 | | LED Blue  | GPIO25 ​    | Lights up at comfort score 100 |
 | LED Red   | GPIO26 ​    | Lights up if gas > 30 | | LED Red   | GPIO26 ​    | Lights up if gas > 30 |
 +
 +{{ iothings:​proiecte:​2025sric:​img_1163.jpeg?​600 |Front wiring view }}
 +{{ iothings:​proiecte:​2025sric:​img_1164.jpeg?​600 |Top view of the breadboard }}
 +{{ iothings:​proiecte:​2025sric:​img_1165.jpeg?​600 |Connection to ESP32 }}
 +{{ iothings:​proiecte:​2025sric:​img_1166.jpeg?​600 |Complete physical assembly }}
 +
 +
  
 ===== System Diagram ===== ===== System Diagram =====
Line 116: Line 123:
     * Virtual LEDs (colored indicators)     * Virtual LEDs (colored indicators)
  
-**Snippet: Sensor reading and score calculation**+**Code**
  
-  float citesteTemperatura() {+  ​#include <​WiFi.h>​ 
 + 
 +const int pinLM35 = 34; 
 +const int pinMQ4 = 32; 
 + 
 +const char* ssid = "​myWifi";​ 
 +const char* password = "";​ 
 + 
 +WiFiServer server(80);​ 
 + 
 +float citesteTemperatura() {
   int adc = analogRead(pinLM35);​   int adc = analogRead(pinLM35);​
-  float voltaj = adc * (5.0 / 4095.0);+  float voltaj = adc * (5 / 4095.0);
   return voltaj * 100.0;   return voltaj * 100.0;
-  ​}+
 + 
 +int citesteGaz() { 
 +  return analogRead(pinMQ4);​ 
 +}
  
-  ​int calculeazaScor(float temp, int gaz) {+int calculeazaScor(float temp, int gaz) {
   int scor = 100;   int scor = 100;
   if (temp < 19 || temp > 25) scor -= 30;   if (temp < 19 || temp > 25) scor -= 30;
Line 132: Line 153:
   if (gaz > 100) scor -= 30;   if (gaz > 100) scor -= 30;
   return max(scor, 0);   return max(scor, 0);
 +}
 +
 +void setup() {
 +  Serial.begin(115200);​
 +  WiFi.begin(ssid,​ password);
 +  Serial.print("​Conectare la WiFi"​);​
 +  while (WiFi.status() != WL_CONNECTED) {
 +    delay(500);
 +    Serial.print("​."​);​
 +  }
 +  Serial.println("​\nConectat!"​);​
 +  Serial.println(WiFi.localIP());​
 +  server.begin();​
 +  pinMode(25, OUTPUT); // LED albastru - scor 100
 +  pinMode(26, OUTPUT); // LED rosu - gaz > 30
 +
 +}
 +
 +void loop() {
 +  WiFiClient client = server.available();​
 +  if (client) {
 +    String cerere = client.readStringUntil('​\r'​);​
 +    client.read();​
 +
 +    if (cerere.indexOf("/​data"​) >= 0) {
 +      float temp = citesteTemperatura();​
 +      int gaz = citesteGaz();​
 +      int scor = calculeazaScor(temp,​ gaz);
 +      String raspuns = String(temp,​ 1) + ";"​ + String(gaz) + ";"​ + String(scor);​
 +      // LED rosu - gaz periculos
 +      digitalWrite(26,​ (gaz > 30) ? HIGH : LOW);
 +      // LED albastru - scor maxim
 +      digitalWrite(25,​ (scor >= 100) ? HIGH : LOW);
 +      client.println("​HTTP/​1.1 200 OK");
 +      client.println("​Content-Type:​ text/​plain"​);​
 +      client.println("​Connection:​ close"​);​
 +      client.println();​
 +      client.println(raspuns);​
 +    } else {
 +      String pagina = R"​rawliteral(
 +  <​!DOCTYPE html><​html>​
 +  <​head>​
 +    <​title>​Monitor de evaluare al confortului ambiental</​title>​
 +    <​style>​
 +      body {
 +        font-family:​ Arial, sans-serif;
 +        background: #f0f0f0;
 +        text-align: center;
 +        padding: 20px;
 +      }
 +      .container {
 +        background: #fff;
 +        border-radius:​ 12px;
 +        padding: 20px;
 +        margin: auto;
 +        width: 90%;
 +        max-width: 700px;
 +        box-shadow: 0 0 10px rgba(0,​0,​0,​0.2);​
 +      }
 +      h2 { color: #0077cc; }
 +      p { font-size: 18px; margin: 10px 0; }
 +      span { font-weight:​ bold; }
 +      canvas { margin: 20px 0; border: 1px solid #ccc; }
 +      #alerta { color: red; font-weight:​ bold; }
 +      ul { padding-left:​ 0; list-style: none; }
 +      li { font-size: 14px; margin: 5px 0; }
 +      .led-box {
 +        display: flex;
 +        justify-content:​ center;
 +        gap: 30px;
 +        margin-top: 10px;
 +      }
 +      .led {
 +        width: 25px;
 +        height: 25px;
 +        border-radius:​ 50%;
 +        background: gray;
 +        margin: auto;
 +      }
 +    </​style>​
 +    <​script>​
 +      let tempData = [], gazData = [], labels = [];
 +      let ultimaAlerta = "";​
 +
 +      function updateData() {
 +        fetch('/​data'​).then(r => r.text()).then(d => {
 +          let [t, g, s] = d.trim().split(';'​);​
 +          document.getElementById("​temp"​).innerText = t + " \u00B0C";​
 +          document.getElementById("​gaz"​).innerText = g;
 +          document.getElementById("​scor"​).innerText = s + " / 100";
 +
 +          const timestamp = new Date().toLocaleTimeString();​
 +          const alertaCurenta = g + "​-"​ + t + "​-"​ + s;
 +
 +          // LED virtual gaz
 +          document.getElementById("​ledGaz"​).style.background = (parseInt(g) > 30) ? "​red"​ : "​gray";​
 +          // LED virtual scor
 +          document.getElementById("​ledScor"​).style.background = (parseInt(s) === 100) ? "​blue"​ : "​gray";​
 +
 +          // Alerta gaz
 +          if (parseInt(g) > 30) {
 +            document.getElementById("​alerta"​).innerText = "​ATENTIE:​ gazul a depasit 30, iar calitatea aerului a scazut!";​
 +            if (alertaCurenta !== ultimaAlerta) {
 +              ultimaAlerta = alertaCurenta;​
 +              const li = document.createElement("​li"​);​
 +              li.textContent = timestamp + " - Gaz: " + g + ", Temp: " + t + " °C, Scor: " + s + "/​100";​
 +              li.style.color = "​red";​
 +              document.getElementById("​istoric"​).appendChild(li);​
 +            }
 +          } else if (parseInt(s) === 100 && alertaCurenta !== ultimaAlerta) {
 +            ultimaAlerta = alertaCurenta;​
 +            const li = document.createElement("​li"​);​
 +            li.textContent = timestamp + " - Scorul de confort a atins valoarea maxima (100)";​
 +            li.style.color = "​blue";​
 +            document.getElementById("​istoric"​).appendChild(li);​
 +            document.getElementById("​alerta"​).innerText = "";​
 +          } else {
 +            document.getElementById("​alerta"​).innerText = "";​
 +          }
 +
 +          let ts = new Date().toLocaleTimeString();​
 +          if (labels.length >= 20) {
 +            labels.shift();​ tempData.shift();​ gazData.shift();​
 +          }
 +          labels.push(ts);​
 +          tempData.push(parseFloat(t));​
 +          gazData.push(parseInt(g));​
 +
 +          drawGraph("​tempChart",​ labels, tempData, "​Temperatura",​ "​\u00B0C"​);​
 +          drawGraph("​gazChart",​ labels, gazData, "Nivel Gaz", "​ppm"​);​
 +        });
 +      }
 +
 +      function drawGraph(id,​ labels, data, title, unit) {
 +        const canvas = document.getElementById(id);​
 +        const ctx = canvas.getContext("​2d"​);​
 +        ctx.clearRect(0,​ 0, canvas.width,​ canvas.height);​
 +
 +        ctx.beginPath();​
 +        ctx.moveTo(40,​ 10);
 +        ctx.lineTo(40,​ 190);
 +        ctx.lineTo(490,​ 190);
 +        ctx.strokeStyle = "#​aaa";​
 +        ctx.stroke();​
 +
 +        ctx.font = "14px Arial";​
 +        ctx.fillStyle = "#​000";​
 +        ctx.fillText(title,​ 200, 20);
 +
 +        const max = Math.max(...data) * 1.1 || 1;
 +        ctx.fillText(max.toFixed(1) + " " + unit, 5, 20);
 +        ctx.fillText("​0 " + unit, 10, 190);
 +
 +        ctx.beginPath();​
 +        ctx.strokeStyle = "#​0077cc";​
 +        data.forEach((val,​ i) => {
 +          const x = 40 + (i * 22);
 +          const y = 190 - (val / max) * 160;
 +          if (i === 0) ctx.moveTo(x,​ y);
 +          else ctx.lineTo(x,​ y);
 +        });
 +        ctx.stroke();​
 +
 +        ctx.fillStyle = "#​0077cc";​
 +        ctx.font = "12px Arial";​
 +        data.forEach((val,​ i) => {
 +          const x = 40 + (i * 22);
 +          const y = 190 - (val / max) * 160;
 +          ctx.beginPath();​
 +          ctx.arc(x, y, 3, 0, 2 * Math.PI);
 +          ctx.fill();
 +          ctx.fillText(val.toFixed(1),​ x - 10, y - 10);
 +        });
 +      }
 +
 +      setInterval(updateData,​ 2000);
 +    </​script>​
 +  </​head>​
 +  <​body>​
 +    <div class="​container">​
 +      <​h2>​Monitorizare Mediu</​h2>​
 +      <​p>​Temperatura:​ <span id="​temp">​--</​span></​p>​
 +      <​p>​Gaz:​ <span id="​gaz">​--</​span></​p>​
 +      <​p>​Scor Confort: <span id="​scor">​--</​span></​p>​
 +
 +      <!-- LED-uri virtuale -->
 +      <div class="​led-box">​
 +        <div>
 +          <div id="​ledGaz"​ class="​led"></​div>​
 +          <​p>​Gaz Periculos</​p>​
 +        </​div>​
 +        <div>
 +          <div id="​ledScor"​ class="​led"></​div>​
 +          <​p>​Scor Maxim</​p>​
 +        </​div>​
 +      </​div>​
 +
 +      <p id="​alerta"></​p>​
 +      <canvas id="​tempChart"​ width="​500"​ height="​200"></​canvas>​
 +      <canvas id="​gazChart"​ width="​500"​ height="​200"></​canvas>​
 +      <div style="​margin-top:​20px;​ text-align:​left;">​
 +        <​h3>​Istoric Evenimente</​h3>​
 +        <ul id="​istoric"></​ul>​
 +      </​div>​
 +    </​div>​
 +  </​body></​html>​
 +)rawliteral";​
 +
 +      client.println("​HTTP/​1.1 200 OK");
 +      client.println("​Content-Type:​ text/html; charset=UTF-8"​);​
 +      client.println("​Connection:​ close"​);​
 +      client.println();​
 +      client.print(pagina);​
 +    }
 +    delay(1);
 +    client.stop();​
   }   }
 +}
  
 +{{ iothings:​proiecte:​2025sric:​alertaocw.png?​700 |}}
 +{{ iothings:​proiecte:​2025sric:​calitateocw.png?​700 |}}
  
 ==== Firebase Integration Sketch ==== ==== Firebase Integration Sketch ====
Line 153: Line 393:
 **Firebase Database Structure:​** **Firebase Database Structure:​**
  
-{{iothings:​proiecte:​2025sric:​firebaseimg.png?​1000 |}}+#include <​WiFi.h>​ 
 +#include <​Firebase_ESP_Client.h>​ 
 +#include "​addons/​TokenHelper.h"​ 
 +#include "​addons/​RTDBHelper.h"​ 
 + 
 +// WiFi 
 +const char* ssid = "​mywifi";​ 
 +const char* password = "";​ 
 + 
 +// Firebase 
 + 
 +#define API_KEY "​AIzaSyCpb_wClxGJonhdSk6B3dnMPWU4KgGPsRE"​ 
 + 
 +#define DATABASE_URL "​https://​esp32-monitor-d753c-default-rtdb.europe-west1.firebasedatabase.app/"​ 
 + 
 +#define USER_EMAIL "​esp32@test.com"​ 
 + 
 +#define USER_PASSWORD "​parola123"​ 
 + 
 +FirebaseData fbdo; 
 +FirebaseAuth auth; 
 +FirebaseConfig config; 
 + 
 +unsigned long lastSend = 0; 
 + 
 +const int pinLM35 = 34; 
 +const int pinMQ4 = 32; 
 + 
 +float citesteTemperatura() { 
 +  int adc = analogRead(pinLM35);​ 
 +  float voltaj = adc * (5.0 / 4095.0); 
 +  return voltaj * 100.0; 
 +
 + 
 +int citesteGaz() { 
 +  return analogRead(pinMQ4);​ 
 +
 + 
 +int calculeazaScor(float temp, int gaz) { 
 +  int scor = 100; 
 +  if (temp < 19 || temp > 25) scor -= 30; 
 +  if (temp < 16 || temp > 28) scor -= 30; 
 +  if (gaz > 30) scor -= 10; 
 +  if (gaz > 50) scor -= 20; 
 +  if (gaz > 100) scor -= 30; 
 +  return max(scor, 0); 
 +
 + 
 +void setup() { 
 +  Serial.begin(115200);​ 
 +  WiFi.begin(ssid,​ password);​ 
 +  Serial.print("​Conectare la WiFi"​);​ 
 +  while (WiFi.status() != WL_CONNECTED) { 
 +    delay(500);​ 
 +    Serial.print("​."​);​ 
 +  } 
 +  Serial.println("​\nWiFi conectat"​);​ 
 + 
 +  // Configurare Firebase 
 +  config.api_key = API_KEY; 
 +  auth.user.email = USER_EMAIL;​ 
 +  auth.user.password = USER_PASSWORD;​ 
 +  config.database_url = DATABASE_URL;​ 
 + 
 +  Firebase.begin(&​config,​ &​auth);​ 
 +  Firebase.reconnectWiFi(true);​ 
 + 
 +  pinMode(25, OUTPUT); // LED albastru - scor 100 
 +  pinMode(26, OUTPUT); // LED rosu - gaz > 30 
 +
 + 
 +void loop() { 
 +  if (Firebase.ready() && (millis() - lastSend > 2000)) { 
 +    lastSend = millis(); 
 + 
 +    float temperatura = citesteTemperatura();​ 
 +    int gaz = citesteGaz();​ 
 +    int scor = calculeazaScor(temperatura,​ gaz); 
 + 
 +    // Trimitere catre Firebase 
 +    Firebase.RTDB.setFloat(&​fbdo,​ "/​monitor/​temperatura",​ temperatura);​ 
 +    Firebase.RTDB.setInt(&​fbdo,​ "/​monitor/​gaz",​ gaz); 
 +    Firebase.RTDB.setInt(&​fbdo,​ "/​monitor/​scor",​ scor); 
 + 
 +    // Control LED-uri 
 +    digitalWrite(26,​ (gaz > 30) ? HIGH : LOW); 
 +    digitalWrite(25,​ (scor >= 100) ? HIGH : LOW); 
 + 
 +    Serial.printf("​Trimis:​ Temp=%.2f, Gaz=%d, Scor=%d\n",​ temperatura,​ gaz, scor); 
 +  } 
 +
 + 
 +{{iothings:​proiecte:​2025sric:​firebaseimg.png?​800 |}} 
 + 
 +===== Challenges ===== 
 + 
 +During the development of the Environmental Comfort Monitoring System, several challenges were encountered:​ 
 + 
 +  * **Voltage Compatibility for Sensors:​** ​  
 +    The MQ-4 gas sensor operates at 5V, while the ESP32’s GPIO pins are not 5V tolerant. Careful wiring and understanding of voltage levels was essential to avoid damaging the board. 
 + 
 +  * **Character Encoding Issues in Web Dashboard:​** ​  
 +    When displaying special characters like the degree symbol (°), improper encoding (such as `Â`) was initially shown. This was resolved by setting the correct `Content-Type:​ text/html; charset=UTF-8` header. 
 + 
 +  * **Chart Scaling and Clarity:​** ​  
 +    The visual graphs needed consistent scaling despite changing data. Implementing a dynamic Y-axis that adapts to maximum values while remaining readable was a key UX challenge. 
 + 
 +  * **Web Interface Responsiveness:​** ​  
 +    Creating a layout that auto-adjusts for both mobile and desktop screens, while updating in real time without full page reloads, required a combination of efficient JavaScript and CSS techniques. 
 + 
 +  * **Firebase Authentication & Realtime Sync:​** ​  
 +    Integrating Firebase required understanding its asynchronous behavior, authentication token refresh mechanisms, and ensuring secure access with proper API key and database rules. 
 + 
 +  * **Separation of Concerns:​** ​  
 +    Maintaining two distinct sketches — one for the local server and one for Firebase integration — introduced complexity but ensured better modularity and reduced code overhead on the ESP32. 
 + 
 +  * **Data Accuracy and Sensor Calibration:​** ​  
 +    Environmental factors such as temperature drift and sensor warm-up times needed to be considered to ensure accurate readings and valid scoring logic. 
 + 
 +===== References ===== 
 + 
 +Below are the key resources used throughout the development process of this project: 
 + 
 +  * [Lab 1: Getting Started](https://​ocw.cs.pub.ro/​courses/​iothings/​laboratoare/​2022/​lab1) 
 +  * [Lab 2: Sensors & SPIFFS](https://​ocw.cs.pub.ro/​courses/​iothings/​laboratoare/​2022/​lab2) 
 +  * [Lab 3: Web servers](https://​ocw.cs.pub.ro/​courses/​iothings/​laboratoare/​2022/​lab3) 
 +  * [Lab 4: Databases for IoT - Firebase](https://​ocw.cs.pub.ro/​courses/​iothings/​laboratoare/​2022/​lab4) 
 +  * [Lab 5: Firebase (part 2)](https://​ocw.cs.pub.ro/​courses/​iothings/​laboratoare/​2022/​lab5) 
 +  * [Lab 6: InfluxDB](https://​ocw.cs.pub.ro/​courses/​iothings/​laboratoare/​2022/​lab6) 
  
iothings/proiecte/2025sric/esp32environmentalcomfortmonitoringsystemweb.1748483409.txt.gz · Last modified: 2025/05/29 04:50 by iustin.burciu
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