Monitoring Suhu Kelembaban Sensor DHT11 Webserver Grafik Realtime dan Datalogger Penampil DMD P10 ESP32 WIFI
Pada kesempatan kali ini saya akan menjelaskan mengenai bagaimana cara membuat sebuah alat yang dapat menampilkan nilai suhu dan kelembaban yang disertai grafik realtime pada webserver. alat ini menggunakan sensor DHT11 dan penampil DMD P10.untuk mikrokontroller yang digunakan yaitu ESP32. untuk lebih jelasnya berikut adalah koding dan skemanya.
1. Skema
2. Interface Webserver
3. Program ESP32
/*
DHT11:
DATA = GPIO 13
P10:
1 PANEL 32x16
Tampilan:
T28.5
H72%
WIFI AP:
SSID = ESP32-MONITOR
PASSWORD = 12345678
WEB:
http://192.168.4.1
FITUR:
- Suhu realtime
- Kelembaban realtime
- Grafik suhu
- Grafik kelembaban
- Tabel 60 data terbaru
- ESP32 Access Point
- DMD scan terus menerus
- Tanpa delay()
- Tanpa hardware timer
- Tanpa FreeRTOS task
*/
#include <WiFi.h>
#include <WebServer.h>
#include <DHT.h>
#include <DMD32.h>
#include "fonts/SystemFont5x7.h"
// =========================================================
// DHT11
// =========================================================
#define DHTPIN 13
#define DHTTYPE DHT11
DHT dht(DHTPIN, DHTTYPE);
// =========================================================
// WIFI
// =========================================================
const char* ssid = "ESP32-MONITOR";
const char* password = "12345678";
WebServer server(80);
// =========================================================
// DMD P10
// =========================================================
#define DISPLAYS_ACROSS 1
#define DISPLAYS_DOWN 1
DMD dmd(DISPLAYS_ACROSS, DISPLAYS_DOWN);
// =========================================================
// SENSOR
// =========================================================
float suhu = 0.0;
float kelembaban = 0.0;
bool sensorOK = false;
// =========================================================
// TIMER SENSOR
// =========================================================
unsigned long sensorMillis = 0;
const unsigned long SENSOR_INTERVAL = 2000;
// =========================================================
// DATA P10
// =========================================================
int displaySuhu10 = -9999;
int displayHum10 = -9999;
int newSuhu10 = -9999;
int newHum10 = -9999;
bool displayUpdate = false;
// =========================================================
// HTML
// =========================================================
const char MAIN_PAGE[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, initial-scale=1">
<title>ESP32 Monitoring</title>
<style>
body {
margin:0;
padding:15px;
font-family:Arial,Helvetica,sans-serif;
background:#eeeeee;
color:#222;
}
.container {
max-width:950px;
margin:auto;
}
h1 {
text-align:center;
}
.status {
background:white;
padding:15px;
border-radius:10px;
text-align:center;
margin-bottom:15px;
box-shadow:0 2px 8px rgba(0,0,0,0.10);
}
.cards {
display:flex;
justify-content:center;
gap:15px;
flex-wrap:wrap;
}
.card {
background:white;
width:220px;
padding:20px;
text-align:center;
border-radius:10px;
box-shadow:0 2px 8px rgba(0,0,0,0.15);
}
.value {
font-size:42px;
font-weight:bold;
}
.unit {
font-size:20px;
}
.box {
background:white;
margin-top:20px;
padding:15px;
border-radius:10px;
box-shadow:0 2px 8px rgba(0,0,0,0.10);
}
canvas {
width:100%;
height:250px;
border:1px solid #dddddd;
display:block;
}
.table-container {
overflow-x:auto;
max-height:500px;
overflow-y:auto;
}
table {
width:100%;
border-collapse:collapse;
min-width:500px;
}
th {
background:#1976d2;
color:white;
position:sticky;
top:0;
z-index:1;
}
th,td {
padding:9px;
border-bottom:1px solid #dddddd;
text-align:center;
}
tr:nth-child(even) {
background:#f5f5f5;
}
tr:hover {
background:#e3f2fd;
}
@media(max-width:600px) {
.card {
width:100%;
}
}
</style>
</head>
<body>
<div class="container">
<h1>
ESP32 MONITORING
</h1>
<div class="status">
<b>ESP32 ACCESS POINT</b>
<br><br>
SSID:
ESP32-MONITOR
<br>
IP:
192.168.4.1
<br>
Update sensor:
2 detik
<br>
Tabel:
60 data terbaru
</div>
<div class="cards">
<div class="card">
<h2>Suhu</h2>
<div class="value">
<span id="suhu">
--
</span>
</div>
<div class="unit">
°C
</div>
</div>
<div class="card">
<h2>Kelembaban</h2>
<div class="value">
<span id="hum">
--
</span>
</div>
<div class="unit">
%
</div>
</div>
</div>
<div class="box">
<h2>
Grafik Suhu
</h2>
<canvas
id="tempCanvas"
width="800"
height="250">
</canvas>
</div>
<div class="box">
<h2>
Grafik Kelembaban
</h2>
<canvas
id="humCanvas"
width="800"
height="250">
</canvas>
</div>
<div class="box">
<h2>
60 Data Terbaru
</h2>
<div class="table-container">
<table>
<thead>
<tr>
<th>No</th>
<th>Waktu</th>
<th>Suhu</th>
<th>Kelembaban</th>
</tr>
</thead>
<tbody id="tableData">
</tbody>
</table>
</div>
</div>
</div>
<script>
let tempData = [];
let humData = [];
let tableData = [];
const MAX_TABLE = 60;
const MAX_GRAPH = 60;
function getTime()
{
let now = new Date();
let h =
String(now.getHours())
.padStart(2,"0");
let m =
String(now.getMinutes())
.padStart(2,"0");
let s =
String(now.getSeconds())
.padStart(2,"0");
return h + ":" + m + ":" + s;
}
const tempCanvas =
document.getElementById("tempCanvas");
const humCanvas =
document.getElementById("humCanvas");
const tempCtx =
tempCanvas.getContext("2d");
const humCtx =
humCanvas.getContext("2d");
function drawChart(ctx, values, title, unit)
{
let width = ctx.canvas.width;
let height = ctx.canvas.height;
ctx.clearRect(
0,
0,
width,
height
);
ctx.fillStyle = "#ffffff";
ctx.fillRect(
0,
0,
width,
height
);
if(values.length == 0)
return;
let min =
Math.min(...values);
let max =
Math.max(...values);
if(max == min)
{
max += 1;
min -= 1;
}
const left = 50;
const right = 15;
const top = 30;
const bottom = 30;
let graphWidth =
width - left - right;
let graphHeight =
height - top - bottom;
ctx.strokeStyle = "#dddddd";
ctx.lineWidth = 1;
for(let i=0;i<=4;i++)
{
let y =
top +
i * graphHeight / 4;
ctx.beginPath();
ctx.moveTo(
left,
y
);
ctx.lineTo(
width-right,
y
);
ctx.stroke();
let value =
max -
i * (max-min) / 4;
ctx.fillStyle = "#555555";
ctx.font = "12px Arial";
ctx.fillText(
value.toFixed(1),
5,
y+4
);
}
ctx.strokeStyle = "#1976d2";
ctx.lineWidth = 2;
ctx.beginPath();
for(let i=0;i<values.length;i++)
{
let x;
if(values.length == 1)
{
x =
left +
graphWidth / 2;
}
else
{
x =
left +
i *
graphWidth /
(values.length-1);
}
let y =
top +
graphHeight -
(
(values[i]-min) /
(max-min)
) *
graphHeight;
if(i == 0)
{
ctx.moveTo(x,y);
}
else
{
ctx.lineTo(x,y);
}
}
ctx.stroke();
ctx.fillStyle = "#1976d2";
for(let i=0;i<values.length;i++)
{
let x;
if(values.length == 1)
{
x =
left +
graphWidth / 2;
}
else
{
x =
left +
i *
graphWidth /
(values.length-1);
}
let y =
top +
graphHeight -
(
(values[i]-min) /
(max-min)
) *
graphHeight;
ctx.beginPath();
ctx.arc(
x,
y,
3,
0,
Math.PI*2
);
ctx.fill();
}
ctx.fillStyle = "#222";
ctx.font =
"bold 15px Arial";
ctx.fillText(
title + " (" + unit + ")",
left,
18
);
}
function updateTable()
{
let tbody =
document.getElementById(
"tableData"
);
tbody.innerHTML = "";
for(
let i=0;
i<tableData.length;
i++
)
{
let data =
tableData[i];
let row =
document.createElement(
"tr"
);
row.innerHTML =
"<td>" +
(i+1) +
"</td>" +
"<td>" +
data.time +
"</td>" +
"<td>" +
data.suhu.toFixed(1) +
" °C</td>" +
"<td>" +
data.hum.toFixed(1) +
" %</td>";
tbody.appendChild(row);
}
}
function updateSensor()
{
fetch("/data")
.then(
response =>
response.json()
)
.then(
data =>
{
document.getElementById(
"suhu"
).innerHTML =
data.suhu.toFixed(1);
document.getElementById(
"hum"
).innerHTML =
data.hum.toFixed(1);
tempData.push(
data.suhu
);
humData.push(
data.hum
);
if(
tempData.length >
MAX_GRAPH
)
{
tempData.shift();
}
if(
humData.length >
MAX_GRAPH
)
{
humData.shift();
}
drawChart(
tempCtx,
tempData,
"SUHU",
"°C"
);
drawChart(
humCtx,
humData,
"KELEMBABAN",
"%"
);
tableData.unshift(
{
time:getTime(),
suhu:data.suhu,
hum:data.hum
});
if(
tableData.length >
MAX_TABLE
)
{
tableData.pop();
}
updateTable();
}
)
.catch(
error =>
console.log(error)
);
}
setInterval(
updateSensor,
2000
);
updateSensor();
</script>
</body>
</html>
)rawliteral";
// =========================================================
// UPDATE BUFFER P10
// =========================================================
void updateP10Buffer()
{
if(!displayUpdate)
return;
displayUpdate = false;
int suhu10 =
newSuhu10;
int hum =
newHum10;
if(
suhu10 == displaySuhu10 &&
hum == displayHum10
)
{
return;
}
displaySuhu10 = suhu10;
displayHum10 = hum;
char textSuhu[12];
char textHum[12];
float suhuDisplay =
((float)suhu10) / 10.0;
sprintf(
textSuhu,
"T%.1f",
suhuDisplay
);
sprintf(
textHum,
"H%d%%",
hum
);
/*
Jangan gunakan clearScreen()
setiap kali update.
Bersihkan buffer satu kali,
kemudian gambar kedua baris.
*/
dmd.clearScreen(true);
dmd.selectFont(
SystemFont5x7
);
dmd.drawString(
0,
0,
textSuhu,
strlen(textSuhu),
GRAPHICS_NORMAL
);
dmd.drawString(
0,
8,
textHum,
strlen(textHum),
GRAPHICS_NORMAL
);
}
// =========================================================
// BACA SENSOR
// =========================================================
void bacaSensor()
{
float h =
dht.readHumidity();
float t =
dht.readTemperature();
if(
isnan(h) ||
isnan(t)
)
{
Serial.println(
"DHT11 ERROR"
);
sensorOK = false;
return;
}
suhu = t;
kelembaban = h;
sensorOK = true;
Serial.print(
"Suhu : "
);
Serial.print(
suhu,
1
);
Serial.println(
" C"
);
Serial.print(
"Hum : "
);
Serial.print(
kelembaban,
1
);
Serial.println(
" %"
);
/*
Jangan langsung menggambar P10
di sini.
Hanya siapkan data baru.
*/
newSuhu10 =
(int)round(
suhu * 10.0
);
newHum10 =
(int)round(
kelembaban
);
displayUpdate = true;
}
// =========================================================
// ROOT
// =========================================================
void handleRoot()
{
server.send_P(
200,
"text/html",
MAIN_PAGE
);
}
// =========================================================
// DATA
// =========================================================
void handleData()
{
String json;
json.reserve(50);
json = "{";
json +=
"\"suhu\":";
json +=
String(
suhu,
1
);
json +=
",";
json +=
"\"hum\":";
json +=
String(
kelembaban,
1
);
json +=
"}";
server.send(
200,
"application/json",
json
);
}
// =========================================================
// SETUP
// =========================================================
void setup()
{
Serial.begin(
115200
);
dht.begin();
// =====================================================
// DMD
// =====================================================
dmd.clearScreen(
true
);
dmd.selectFont(
SystemFont5x7
);
// =====================================================
// WIFI
// =====================================================
WiFi.mode(
WIFI_AP
);
WiFi.setSleep(
false
);
WiFi.softAP(
ssid,
password,
1,
false,
4
);
Serial.println();
Serial.println(
"=============================="
);
Serial.println(
"ESP32 MONITORING ANTI FLICKER"
);
Serial.println(
"=============================="
);
Serial.print(
"SSID : "
);
Serial.println(
ssid
);
Serial.print(
"IP : "
);
Serial.println(
WiFi.softAPIP()
);
// =====================================================
// WEB
// =====================================================
server.on(
"/",
handleRoot
);
server.on(
"/data",
handleData
);
server.begin();
Serial.println(
"Webserver aktif"
);
// =====================================================
// SENSOR PERTAMA
// =====================================================
bacaSensor();
/*
Buat tampilan awal.
*/
updateP10Buffer();
}
// =========================================================
// LOOP
// =========================================================
void loop()
{
/*
=====================================================
PRIORITAS PERTAMA:
SCAN P10
=====================================================
*/
dmd.scanDisplayBySPI();
/*
Update buffer hanya ketika
ada data sensor baru.
*/
if(displayUpdate)
{
updateP10Buffer();
}
/*
Webserver diproses sesudah
scanning P10.
*/
server.handleClient();
/*
Sensor setiap 2 detik.
*/
unsigned long sekarang =
millis();
if(
sekarang -
sensorMillis >=
SENSOR_INTERVAL
)
{
sensorMillis =
sekarang;
bacaSensor();
}
}
4. VIDEO HASILNYA







