Internet of Things 6 min read Jun 21, 2026 193 Views

Modern ESP32 Environment Monitor Standalone Temp & Humidity

Fiqra Putra Lesmana

Fiqra Putra Lesmana

Author

Sleek, standalone ESP32 web dashboard displaying real-time temp & humidity using dark-mode Apache ECharts. No external backend required.

Modern ESP32 Environment Monitor Standalone Temp & Humidity

Things used in this project

• Hardware components

  1. ESP32

  2. DHT11 Temperature Humidity Sensor

  3. Male/Female Jumper Wires

• Software apps

  1. Arduino IDE

 

Story

A lot of standard microcontroller sensor projects end up displaying data as boring text strings or on very clunky web interfaces. I wanted to build an environment monitoring dashboard that is not only highly functional but also looks like a polished, production-grade digital product.

This project is a standalone local web server running directly off an ESP32. It reads data from a DHT sensor (like a DHT11 or DHT22) and visualizes it using clean, modern Gauge Charts. To pull off this aesthetic, I integrated the Apache ECharts library via CDN directly into the HTML. The UI is built with a strict, eye-friendly Dark Mode and includes a real-time "last updated" timestamp.

The best part? There is no external server or complicated cloud setup involved. The entire web page—HTML, CSS, and the charting JavaScript—is stored entirely in the ESP32's flash memory using PROGMEM (R"rawliteral(...)) inside a single .ino sketch file. The code is extremely lightweight and responsive. I even built a temporary dummy-data generator into the code so I could perfectly fine-tune the web UI/UX before wiring up the actual hardware sensor.

If you need to monitor a bedroom, a server rack, or a greenhouse over your local Wi-Fi, this project gives you an incredibly professional-looking dashboard right out of the box!

 

Schematics

esp32_dht11_wiring.png

Svq0FrEEISgKa6eR5K2NPwsbub7eNfviNQ0JFuxG.png

 

Code

esp32_environment_monitor.ino

#include <WiFi.h>
#include <WebServer.h>
#include "DHT.h"

// --- WIFI CONFIGURATION ---
const char* ssid = "***";
const char* password = "***";

// Adjust to the pin you are using
#define DHTPIN 4      
#define DHTTYPE DHT11 

DHT dht(DHTPIN, DHTTYPE);
WebServer server(80);

// --- MAIN HTML PAGE ---
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ESP32 Local Monitoring</title>
    <!-- ECharts CDN -->
    <script src="https://cdn.jsdelivr.net/npm/echarts@5.5.0/dist/echarts.min.js"></script>
    <style>
        * {
            box-sizing: border-box;
            margin: 0;
            padding: 0;
        }
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background-color: #121212;
            color: #ffffff;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            min-height: 100vh;
            padding: 20px;
        }
        h1 {
            font-weight: 600;
            margin-bottom: 40px;
            text-align: center;
            letter-spacing: 1px;
            text-shadow: 0 2px 4px rgba(0,0,0,0.5);
        }
        .dashboard {
            display: flex;
            flex-wrap: wrap;
            gap: 30px;
            justify-content: center;
            width: 100%;
            max-width: 900px;
        }
        .card {
            background: #1e1e1e;
            border-radius: 16px;
            box-shadow: 0 10px 20px rgba(0,0,0,0.3);
            padding: 30px;
            width: 100%;
            max-width: 400px;
            height: 380px;
            display: flex;
            flex-direction: column;
            align-items: center;
            border: 1px solid #333;
            transition: transform 0.3s ease, box-shadow 0.3s ease;
        }
        .card:hover {
            transform: translateY(-5px);
            box-shadow: 0 15px 30px rgba(0,0,0,0.5);
        }
        .gauge-container {
            width: 100%;
            height: 100%;
        }
        .status {
            margin-top: 20px;
            font-size: 14px;
            color: #888;
        }
        #last-update {
            font-weight: bold;
            color: #bbb;
        }
    </style>
</head>
<body>

    <h1>ESP32 Environment Dashboard</h1>
    
    <div class="dashboard">
        <!-- Temperature Card -->
        <div class="card">
            <div id="tempGauge" class="gauge-container"></div>
        </div>
        <!-- Humidity Card -->
        <div class="card">
            <div id="humGauge" class="gauge-container"></div>
        </div>
    </div>
    
    <div class="status">
        Last updated: <span id="last-update">-</span>
    </div>

    <script>
        function createGaugeOption(name, min, max, unit, colors) {
            return {
                series: [
                    {
                        type: 'gauge',
                        center: ['50%', '60%'],
                        radius: '80%',
                        startAngle: 180,
                        endAngle: 0,
                        min: min,
                        max: max,
                        splitNumber: 4,
                        itemStyle: {
                            color: '#ffffff',
                            shadowColor: 'rgba(0,0,0,0.4)',
                            shadowBlur: 10,
                            shadowOffsetX: 2,
                            shadowOffsetY: 2
                        },
                        progress: {
                            show: false
                        },
                        pointer: {
                            show: true,
                            icon: 'path://M12.8,0.7l12,40.1H0.7L12.8,0.7z',
                            length: '75%',
                            width: 12,
                            offsetCenter: [0, '0%']
                        },
                        axisLine: {
                            lineStyle: {
                                width: 35,
                                color: colors
                            }
                        },
                        axisTick: { 
                            show: false 
                        },
                        splitLine: { 
                            show: true,
                            length: 35,
                            lineStyle: { color: '#1e1e1e', width: 4 }
                        },
                        axisLabel: {
                            distance: -65,
                            color: '#fff',
                            fontSize: 14,
                            fontWeight: 'bold'
                        },
                        title: {
                            show: true,
                            offsetCenter: [0, '55%'],
                            fontSize: 20,
                            color: '#ccc'
                        },
                        detail: {
                            valueAnimation: true,
                            fontSize: 42,
                            fontWeight: 'bold',
                            color: '#fff',
                            offsetCenter: [0, '25%'],
                            formatter: '{value}' + unit
                        },
                        data: [{ value: 0, name: name }]
                    }
                ]
            };
        }

        var tempChart = echarts.init(document.getElementById('tempGauge'));
        var humChart = echarts.init(document.getElementById('humGauge'));

        var tempColors = [
            [0.4, '#58D9F9'],
            [0.6, '#7CFFB2'],
            [0.8, '#FDDD60'],
            [1.0, '#FF6E76']
        ];
        
        var humColors = [
            [0.3, '#FF6E76'],
            [0.7, '#7CFFB2'],
            [1.0, '#58D9F9']
        ];

        tempChart.setOption(createGaugeOption('Temperature', 0, 50, ' °C', tempColors));
        humChart.setOption(createGaugeOption('Humidity', 0, 100, ' %', humColors));

        function fetchData() {
            fetch('/data')
                .then(response => response.json())
                .then(data => {
                    tempChart.setOption({
                        series: [{ data: [{ value: data.temperature, name: 'Temperature' }] }]
                    });
                    humChart.setOption({
                        series: [{ data: [{ value: data.humidity, name: 'Humidity' }] }]
                    });
                    
                    const now = new Date();
                    document.getElementById('last-update').innerText = now.toLocaleTimeString();
                })
                .catch(error => console.error('Failed to fetch data from ESP32:', error));
        }

        fetchData();
        setInterval(fetchData, 3000);
        
        window.addEventListener('resize', function() {
            tempChart.resize();
            humChart.resize();
        });
    </script>
</body>
</html>
)rawliteral";

void handleRoot() {
  server.send(200, "text/html", index_html);
}

void handleData() {
  // Read real sensor data
  float h = dht.readHumidity();
  float t = dht.readTemperature();

  // Check if sensor reading failed
  if (isnan(h) || isnan(t)) {
    Serial.println("Failed to read from DHT sensor!");
    h = 0.0;
    t = 0.0;
  }

  // Create JSON string manually
  String json = "{";
  json += "\"temperature\":";
  json += String(t, 1);
  json += ", \"humidity\":";
  json += String(h, 1);
  json += "}";

  server.sendHeader("Access-Control-Allow-Origin", "*");
  server.send(200, "application/json", json);
}

void setup() {
  Serial.begin(115200);
  
  // Initialize DHT sensor
  dht.begin();
  
  // Start WiFi connection
  WiFi.begin(ssid, password);
  Serial.print("Connecting to WiFi");
  
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  
  Serial.println("\n");
  Serial.println("=== WiFi Connected ===");
  Serial.print("ESP32 IP Address: ");
  Serial.println(WiFi.localIP());
  Serial.println("Open this IP Address in your browser!");
  Serial.println("======================");

  // Endpoint Routing
  server.on("/", HTTP_GET, handleRoot);
  server.on("/data", HTTP_GET, handleData);
  
  // Start the server
  server.begin();
}

void loop() {
  server.handleClient();
}

How do you use it?

  1. Enter your WiFi credentials (ssid and password) into the code.

  2. Upload the code to your ESP32 using the Arduino IDE. Open the Serial Monitor (115200 baud rate) to get the ESP32's local IP Address.

  3. Open that IP address in any browser on a phone or PC connected to the same WiFi network.

 

Fiqra Putra Lesmana

Written by

Fiqra Putra Lesmana

Full-stack developer & hardware enthusiast. I enjoy blending embedded systems, PCB design, and web tech to create functional IoT solutions.

Comments (0)

Leave a Reply

No comments yet. Be the first to share your thoughts!