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:40]
iustin.burciu [Code Snippets]
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)
  
-**SnippetSensor reading ​and score calculation** +**Code** 
-```cpp+ 
 +  #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);​ 
 +  float voltaj = adc * (5 / 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("​\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 ==== 
 + 
 +A second Arduino sketch was created to send environmental data to a **Firebase Realtime Database**, allowing remote access ​and monitoring from any internet-connected device. 
 + 
 +This implementation uses the **Firebase ESP Client** library to authenticate,​ connect, and write sensor data securely to the Firebase database. 
 + 
 +**Main responsibilities:​** 
 + 
 +  * Connects the ESP32 to Wi-Fi 
 +  * Authenticates using an email, password, and API key 
 +  * Initializes communication with Firebase 
 +  * Reads temperature and gas levels 
 +  * Calculates the comfort ​score 
 +  ​Uploads all values to Firebase paths every 2 seconds 
 +  ​Activates **LED indicators** (same behavior as the local sketch) 
 + 
 +**Firebase Database Structure:​** 
 + 
 +#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() { float citesteTemperatura() {
   int adc = analogRead(pinLM35);​   int adc = analogRead(pinLM35);​
   float voltaj = adc * (5.0 / 4095.0);   float voltaj = adc * (5.0 / 4095.0);
   return voltaj * 100.0;   return voltaj * 100.0;
 +}
 +
 +int citesteGaz() {
 +  return analogRead(pinMQ4);​
 } }
  
Line 133: Line 440:
   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("​\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.1748482816.txt.gz · Last modified: 2025/05/29 04:40 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