Sensor

Monitoring Suhu & Kelembaban dengan Sensor DHT11/DHT22 + ESP32

TOKEN

Panduan lengkap dari pengenalan sensor, wiring, program Arduino, kalibrasi, penyimpanan data, hingga proyek MQTT + Web Dashboard

1. Pengenalan Sensor DHT11 & DHT22

Family sensor DHT (Digital Humidity Temperature) merupakan salah satu sensor digital paling populer untuk proyek IoT. Sensor ini mampu mengukur suhu dan kelembaban udara secara bersamaan dalam satu modul kecil yang sangat terjangkau. Dua varian utama yang banyak digunakan adalah DHT11 dan DHT22 (dikenal juga sebagai AM2302).

Sensor DHT bekerja dengan memanfaatkan komponen kapasitif untuk mengukur kelembaban dan termistor NTC untuk mengukur suhu. Data dikirim secara digital melalui satu jalur data (single-wire interface), sehingga sangat hemat pin GPIO pada mikrokontroler seperti ESP32.

Spesifikasi DHT11

Parameter Nilai
Rentang Suhu0ยฐC โ€” 50ยฐC
Akurasi Suhuยฑ2ยฐC
Rentang Kelembaban20% โ€” 80% RH
Akurasi Kelembabanยฑ5% RH
Resolusi1 bit (8 bit per pembacaan)
Tegangan Operasi3.3V โ€” 5.5V DC
Arus Kerja2.5 mA (rata-rata)
Sampling Rate1 Hz (1 pembacaan per detik)
Ukuran Fisik12 ร— 15.5 ร— 5.5 mm
Harga (rata-rata)Rp 10.000 โ€” Rp 20.000

Spesifikasi DHT22 (AM2302)

Parameter Nilai
Rentang Suhu-40ยฐC โ€” 80ยฐC
Akurasi Suhuยฑ0.5ยฐC
Rentang Kelembaban0% โ€” 100% RH
Akurasi Kelembabanยฑ2% RH
Resolusi0.1ยฐC / 0.1% RH (16 bit)
Tegangan Operasi3.3V โ€” 6V DC
Arus Kerja1.5 mA (rata-rata)
Sampling Rate0.5 Hz (1 pembacaan per 2 detik)
Ukuran Fisik15 ร— 25 ร— 7.7 mm
Harga (rata-rata)Rp 25.000 โ€” Rp 45.000
๐Ÿ’ก Kapan Pilih DHT11 vs DHT22?

Pilih DHT11 untuk proyek pemula, sekadar belajar, atau monitoring kasar (misal: indikator ruangan). Pilih DHT22 jika membutuhkan akurasi lebih baik, rentang suhu lebih lebar, atau untuk data logging jangka panjang. Perbedaan harga sekitar Rp 15.000โ€”25.000 sangat worth it untuk presisi yang jauh lebih baik.

2. Perbandingan DHT11 vs DHT22 vs BME280

Jika DHT11 dan DHT22 populer karena harganya yang murah, sensor BME280 (buatan Bosch) hadir sebagai alternatif premium dengan fitur lebih lengkap, termasuk pengukuran tekanan udara (barometrik). Berikut tabel perbandingan lengkap:

๐Ÿ”ต

DHT11

Murah & Mudah
  • Jenis: Digital
  • Suhu: 0โ€“50ยฐC (ยฑ2ยฐC)
  • Kelembaban: 20โ€“80% (ยฑ5%)
  • Tekanan Udara: โŒ
  • Protokol: Single-wire
  • Tegangan: 3.3โ€“5.5V
  • Arus Kerja: 2.5 mA
  • Sampling Rate: 1 Hz
  • Antarmuka: 1 GPIO
  • Kesulitan: โญ Mudah
  • Harga: Rp 10โ€“20rb
  • Cocok Untuk: Pemula, proyek sederhana
โšช

DHT22

Akurat & Andal
  • Jenis: Digital
  • Suhu: -40โ€“80ยฐC (ยฑ0.5ยฐC)
  • Kelembaban: 0โ€“100% (ยฑ2%)
  • Tekanan Udara: โŒ
  • Protokol: Single-wire
  • Tegangan: 3.3โ€“6V
  • Arus Kerja: 1.5 mA
  • Sampling Rate: 0.5 Hz
  • Antarmuka: 1 GPIO
  • Kesulitan: โญ Mudah
  • Harga: Rp 25โ€“45rb
  • Cocok Untuk: Monitoring akurat
๐ŸŸข

BME280

Premium & Lengkap
  • Jenis: Digital (MEMS)
  • Suhu: -40โ€“85ยฐC (ยฑ1ยฐC)
  • Kelembaban: 0โ€“100% (ยฑ3%)
  • Tekanan Udara: โœ… (300โ€“1100 hPa)
  • Protokol: I2C / SPI
  • Tegangan: 1.71โ€“3.6V
  • Arus Kerja: 3.6 ยตA @ 1Hz
  • Sampling Rate: Hingga 100 Hz
  • Antarmuka: 4+ pin (I2C/SPI)
  • Kesulitan: โญโญ Sedang
  • Harga: Rp 40โ€“80rb
  • Cocok Untuk: Weather station, IoT lanjut
โ„น๏ธ Rekomendasi Pilihan

Pemula: Mulai dengan DHT11 (murah, mudah). Sedang: Gunakan DHT22 untuk akurasi lebih baik tanpa kompleksitas wiring. Lanjut: Pilih BME280 jika butuh data tekanan udara untuk aplikasi weather station atau prediksi cuaca. Ketiga sensor kompatibel dengan ESP32 tanpa masalah.

๐Ÿ”ต

DHT11

12 ร— 15.5 mm
1 VCC
2 DATA
3 NC
4 GND
โšช

DHT22

15 ร— 25 mm
1 VCC
2 DATA
3 NC
4 GND
๐ŸŸข

BME280

Modul Kecil (I2C)
VCC
GND
SDA
SCL

3. Wiring & Koneksi ke ESP32

Koneksi sensor DHT11/DHT22 ke ESP32 sangat sederhana karena hanya membutuhkan 3 kabel: VCC (power), DATA (sinyal), dan GND (ground). Pin DATA membutuhkan resistor pull-up 10kฮฉ untuk menjaga stabilitas sinyal komunikasi.

Kebutuhan Komponen

๐Ÿ”Œ Wiring ESP32 + DHT11 / DHT22
ESP32 3.3V
DHT VCC (Pin 1)
ESP32 GPIO4
via 10kฮฉ pull-up ke 3.3V
DHT DATA (Pin 2)
โ€”
Tidak terhubung
DHT NC (Pin 3)
ESP32 GND
DHT GND (Pin 4)
โš ๏ธ Resistor Pull-up 10kฮฉ

Resistor 10kฮฉ dipasang antara pin DATA (2) dan VCC (1) sebagai pull-up resistor. Beberapa modul DHT sudah memiliki resistor internal โ€” periksa modul Anda sebelum memasang.

Wiring Multi-Sensor (DHT11 + DHT22)

Jika ingin menggunakan dua sensor sekaligus untuk komparasi, masing-masing sensor terhubung ke GPIO berbeda:

๐Ÿ”Œ Wiring Dua Sensor DHT pada ESP32
ESP32 3.3V
โ†˜
DHT11 VCC
ESP32 3.3V
โ†—
DHT22 VCC
ESP32 GPIO4
via 10kฮฉ pull-up
DHT11 DATA
ESP32 GPIO5
via 10kฮฉ pull-up
DHT22 DATA
ESP32 GND
โ†˜
DHT11 GND
ESP32 GND
โ†—
DHT22 GND
โš ๏ธ Peringatan Penting
  • ESP32 beroperasi pada tegangan 3.3V. Jangan hubungkan pin GPIO langsung ke tegangan 5V tanpa level shifter.
  • Beberapa modul DHT11/DHT22 sudah memiliki resistor pull-up internal (modul breakout board). Periksa datasheet modul Anda sebelum memasang resistor eksternal.
  • Panjang kabel jumper idealnya kurang dari 20 cm untuk menghindari gangguan noise pada sinyal data.

4. Setup Library Arduino IDE

Untuk menggunakan sensor DHT11/DHT22 di Arduino IDE, kita membutuhkan library dari Adafruit. Adafruit menyediakan library DHT yang sudah sangat stabil dan banyak digunakan komunitas maker di seluruh dunia.

Langkah 1: Instalasi Library DHT

  1. Buka Arduino IDE
  2. Buka menu Sketch โ†’ Include Library โ†’ Manage Libraries
  3. Pada kolom pencarian, ketik "DHT sensor library"
  4. Pilih "DHT sensor library" oleh Adafruit
  5. Klik tombol Install
  6. Jika muncul prompt instalasi dependency, klik "Install All"

Library ini akan otomatis menginstal Adafruit Unified Sensor sebagai dependency, yang merupakan library dasar untuk semua sensor digital Adafruit.

Langkah 2: Verifikasi Instalasi

Setelah instalasi selesai, pastikan library berhasil dimuat dengan cara:

  1. Buka menu Sketch โ†’ Include Library
  2. Scroll ke bawah, cari "DHT sensor library" di bagian Contributed Libraries
  3. Jika sudah muncul, berarti instalasi berhasil

Struktur Include di Kode

C++ โ€” Header yang Diperlukan
// Library utama untuk sensor DHT
#include "DHT.h"

// Definisi pin dan tipe sensor
#define DHT_PIN 4        // GPIO4 terhubung ke pin DATA sensor
#define DHT_TYPE DHT11   // Ganti ke DHT22 jika pakai DHT22

// Inisialisasi objek sensor
DHT sensorSuhu(DHT_PIN, DHT_TYPE);
โ„น๏ธ Ganti Tipe Sensor

Jika menggunakan DHT22, cukup ganti baris #define DHT_TYPE DHT11 menjadi #define DHT_TYPE DHT22. Kode lainnya tetap sama โ€” library akan otomatis menyesuaikan protokol pembacaan.

5. Program Membaca Suhu & Kelembaban

Sekarang kita akan membuat program Arduino untuk membaca data suhu dan kelembaban dari sensor DHT. Program ini termasuk penanganan error jika sensor gagal membaca, serta perhitungan Heat Index (indec panas) yang sangat berguna untuk monitoring kenyamanan ruangan.

Program Dasar Pembacaan DHT

C++ โ€” baca_dht_lengkap.ino
// =============================================
// Program Membaca Suhu & Kelembaban DHT11/DHT22
// BeebaneLabs - https://beebanelabs.pages.dev
// =============================================

#include "DHT.h"

// === Konfigurasi Sensor ===
#define DHT_PIN   4         // GPIO4 terhubung ke pin DATA
#define DHT_TYPE  DHT11     // Ganti ke DHT22 jika diperlukan
#define LED_PIN   2         // LED internal ESP32 (GPIO2)

// === Interval Pembacaan ===
const unsigned long INTERVAL_BACA = 5000;  // Baca setiap 5 detik

// === Objek Sensor ===
DHT sensorDHT(DHT_PIN, DHT_TYPE);

// === Variabel Penyimpanan ===
float suhuTerkini      = 0.0;
float kelembabanTerkini = 0.0;
float heatIndex         = 0.0;
unsigned long totalPembacaan = 0;
unsigned long totalError     = 0;

// === Fungsi Pembacaan dengan Validasi ===
bool bacaSensor() {
  float s = sensorDHT.readTemperature();      // Celsius
  float h = sensorDHT.readHumidity();         // Persen
  float f = sensorDHT.readTemperature(true);  // Fahrenheit

  // Validasi: cek apakah pembacaan valid
  if (isnan(s) || isnan(h)) {
    totalError++;
    Serial.println("[ERROR] Gagal membaca sensor DHT!");
    Serial.println("  Penyebab: koneksi longgar, resistor belum dipasang, atau sensor rusak.");
    return false;
  }

  suhuTerkini      = s;
  kelembabanTerkini = h;
  heatIndex         = sensorDHT.computeHeatIndex(s, h, false);
  totalPembacaan++;
  return true;
}

// === Fungsi Tampilkan Data ke Serial ===
void tampilkanData() {
  Serial.println("โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”");
  Serial.println("โ”‚      Pembacaan Sensor DHT      โ”‚");
  Serial.println("โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค");
  Serial.print("โ”‚ Suhu        : ");
  Serial.print(suhuTerkini, 1);
  Serial.println(" ยฐC        โ”‚");
  Serial.print("โ”‚ Kelembaban  : ");
  Serial.print(kelembabanTerkini, 1);
  Serial.println(" %          โ”‚");
  Serial.print("โ”‚ Heat Index  : ");
  Serial.print(heatIndex, 1);
  Serial.println(" ยฐC        โ”‚");
  Serial.print("โ”‚ Pembacaan # : ");
  Serial.print(totalPembacaan);
  Serial.println("              โ”‚");
  Serial.println("โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜");
}

void setup() {
  Serial.begin(115200);
  pinMode(LED_PIN, OUTPUT);

  Serial.println("โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—");
  Serial.println("โ•‘  Sensor DHT11/DHT22 + ESP32         โ•‘");
  Serial.println("โ•‘  BeebaneLabs - https://beebanelabs.pages.dev          โ•‘");
  Serial.println("โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•");

  // Mulai sensor
  sensorDHT.begin();
  delay(2000);  // Waktu stabilisasi sensor

  Serial.println("Sensor dimulai. Menunggu pembacaan pertama...");
}

void loop() {
  static unsigned long lastBaca = 0;
  unsigned long sekarang = millis();

  if (sekarang - lastBaca >= INTERVAL_BACA) {
    lastBaca = sekarang;

    if (bacaSensor()) {
      tampilkanData();

      // Kedipkan LED saat pembacaan berhasil
      digitalWrite(LED_PIN, HIGH);
      delay(100);
      digitalWrite(LED_PIN, LOW);
    }
  }
}
Output Serial Monitor: โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•— โ•‘ Sensor DHT11/DHT22 + ESP32 โ•‘ โ•‘ BeebaneLabs - https://beebanelabs.pages.dev โ•‘ โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• Sensor dimulai. Menunggu pembacaan pertama... โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ Pembacaan Sensor DHT โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ Suhu : 28.0 ยฐC โ”‚ โ”‚ Kelembaban : 72.5 % โ”‚ โ”‚ Heat Index : 30.1 ยฐC โ”‚ โ”‚ Pembacaan # : 1 โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
๐Ÿ’ก Apa itu Heat Index?

Heat Index (indeks panas) adalah suhu yang "terasa" oleh tubuh manusia, menggabungkan efek suhu aktual dan kelembaban. Semakin tinggi kelembaban, semakin panas suhu terasa. Contoh: suhu 30ยฐC dengan kelembaban 80% terasa seperti 36ยฐC! Heat Index sangat berguna untuk monitoring kenyamanan ruangan atau sistem pendingin otomatis.

6. Kalibrasi & Error Handling

Sensor DHT11/DHT22, seperti semua sensor, memiliki toleransi pembacaan tertentu. Untuk proyek yang membutuhkan akurasi lebih tinggi, diperlukan proses kalibrasi. Selain itu, implementasi error handling yang baik akan memastikan sistem tetap stabil meskipun sensor mengalami gangguan sementara.

Teknik Kalibrasi Dasar

Metode kalibrasi paling sederhana adalah perbandingan dengan referensi. Gunakan termometer digital akurat (misal: termometer laboratorium atau sensor BME280 sebagai referensi) untuk menentukan offset sensor DHT Anda.

C++ โ€” kalibrasi_dht.ino
// =============================================
// Kalibrasi & Error Handling Sensor DHT
// BeebaneLabs - https://beebanelabs.pages.dev
// =============================================

#include "DHT.h"

#define DHT_PIN   4
#define DHT_TYPE  DHT11

DHT sensorDHT(DHT_PIN, DHT_TYPE);

// === Faktor Kalibrasi ===
// Nilai ini didapatkan dari perbandingan dengan sensor referensi
// Contoh: jika DHT11 membaca 30.5ยฐC tapi referensi menunjuk 29.8ยฐC
//          maka offset_suhu = 29.8 - 30.5 = -0.7
const float OFFSET_SUHU         = -0.7;   // Koreksi suhu (ยฐC)
const float OFFSET_KELEMBABAN   = 2.0;    // Koreksi kelembaban (%)
const float BATAS_SUHU_MIN      = -10.0;  // Batas bawah suhu valid
const float BATAS_SUHU_MAX      = 60.0;   // Batas atas suhu valid
const float BATAS_RH_MIN        = 0.0;    // Batas bawah kelembaban
const float BATAS_RH_MAX        = 100.0;  // Batas atas kelembaban
const int   MAX_ERROR_BERUNTUN  = 5;      // Max error berturut sebelum reset

// === Counter Error ===
int errorBeruntun = 0;

// === Fungsi Kalibrasi ===
float kalibrasiSuhu(float raw) {
  return raw + OFFSET_SUHU;
}

float kalibrasiKelembaban(float raw) {
  float calibrated = raw + OFFSET_KELEMBABAN;
  // Clamp ke rentang valid
  if (calibrated < BATAS_RH_MIN) calibrated = BATAS_RH_MIN;
  if (calibrated > BATAS_RH_MAX) calibrated = BATAS_RH_MAX;
  return calibrated;
}

// === Fungsi Pembacaan dengan Error Handling Lanjut ===
bool bacaSensorLanjut(float* suhu, float* kelembaban) {
  float rawSuhu = sensorDHT.readTemperature();
  float rawRH   = sensorDHT.readHumidity();

  // Error 1: Pembacaan NaN
  if (isnan(rawSuhu) || isnan(rawRH)) {
    errorBeruntun++;
    Serial.printf("[ERROR #%d/%d] NaN diterima dari sensor!\n",
                  errorBeruntun, MAX_ERROR_BERUNTUN);

    if (errorBeruntun >= MAX_ERROR_BERUNTUN) {
      Serial.println("[CRITICAL] Terlalu banyak error berturut. Mencoba reset...");
      sensorDHT.begin();
      delay(3000);
      errorBeruntun = 0;
    }
    return false;
  }

  // Error 2: Nilai di luar rentang fisik
  float calibratedSuhu = kalibrasiSuhu(rawSuhu);
  float calibratedRH   = kalibrasiKelembaban(rawRH);

  if (calibratedSuhu < BATAS_SUHU_MIN || calibratedSuhu > BATAS_SUHU_MAX) {
    errorBeruntun++;
    Serial.printf("[WARN] Suhu %.1fยฐC di luar rentang valid (%.0fโ€“%.0fยฐC)\n",
                  calibratedSuhu, BATAS_SUHU_MIN, BATAS_SUHU_MAX);
    return false;
  }

  // Error 3: Kelembaban di luar rentang
  if (calibratedRH < BATAS_RH_MIN || calibratedRH > BATAS_RH_MAX) {
    errorBeruntun++;
    Serial.printf("[WARN] Kelembaban %.1f%% di luar rentang valid\n", calibratedRH);
    return false;
  }

  // Error 4: Perubahan mendadak (sensor terlepas?)
  static float lastSuhu = 0;
  if (lastSuhu != 0 && abs(calibratedSuhu - lastSuhu) > 10.0) {
    Serial.printf("[WARN] Lompatan suhu ekstrem: %.1f โ†’ %.1fยฐC\n",
                  lastSuhu, calibratedSuhu);
    // Tidak return false, tapi catat warning
  }
  lastSuhu = calibratedSuhu;

  // Semua valid
  *suhu       = calibratedSuhu;
  *kelembaban = calibratedRH;
  errorBeruntun = 0;
  return true;
}

void setup() {
  Serial.begin(115200);
  Serial.println("=== Sistem Kalibrasi DHT ===");
  sensorDHT.begin();
  delay(2000);

  Serial.printf("Offset Suhu       : %.1fยฐC\n", OFFSET_SUHU);
  Serial.printf("Offset Kelembaban : %.1f%%\n", OFFSET_KELEMBABAN);
  Serial.printf("Rentang Suhu      : %.0f ~ %.0fยฐC\n", BATAS_SUHU_MIN, BATAS_SUHU_MAX);
}

void loop() {
  static unsigned long lastBaca = 0;
  if (millis() - lastBaca > 5000) {
    lastBaca = millis();

    float suhu, kelembaban;
    if (bacaSensorLanjut(&suhu, &kelembaban)) {
      Serial.printf("[OK] Suhu: %.1fยฐC | RH: %.1f%%\n", suhu, kelembaban);
    }
  }
}

Panduan Kalibrasi Manual

  1. Siapkan referensi: Gunakan termometer akurat dan hygrometer kalibrasi. Tempatkan bersama DHT di lokasi yang sama.
  2. Rekam pembacaan: Catat 10-20 pembacaan dari DHT dan referensi pada kondisi stabil.
  3. Hitung rata-rata selisih: offset = (rata-rata referensi) โˆ’ (rata-rata DHT)
  4. Masukkan offset ke kode: Ubah nilai OFFSET_SUHU dan OFFSET_KELEMBABAN.
  5. Uji ulang: Verifikasi bahwa pembacaan koreksi sesuai referensi dalam toleransi ยฑ0.5ยฐC.

7. Menyimpan Data ke EEPROM/SD Card

ESP32 memiliki memori EEPROM (Electrically Erasable Programmable Read-Only Memory) virtual sebesar 4096 bytes yang bisa digunakan untuk menyimpan data. Namun untuk logging data sensor dalam jumlah besar, SD Card merupakan pilihan yang jauh lebih praktis.

Opsi 1: Menyimpan ke EEPROM (Data Terbatas)

C++ โ€” simpan_eeprom.ino
// =============================================
// Menyimpan Data Sensor ke EEPROM ESP32
// BeebaneLabs - https://beebanelabs.pages.dev
// =============================================

#include "DHT.h"
#include <EEPROM.h>

#define DHT_PIN   4
#define DHT_TYPE  DHT11

DHT sensorDHT(DHT_PIN, DHT_TYPE);

// === Struktur Data untuk Disimpan ===
struct DataSensor {
  float suhu;
  float kelembaban;
  unsigned long timestamp;  // millis() sebagai timestamp
  bool valid;               // Flag validitas data
};

// === Konfigurasi Penyimpanan ===
const int EEPROM_SIZE    = 4096;
const int ALAMAT_AWAL    = 0;    // Alamat awal penyimpanan
const int MAX_RECORDS    = 40;   // Max record (4096 / sizeof(DataSensor))
const int ALAMAT_COUNTER = 4000; // Alamat counter record

int jumlahRecord = 0;

void setup() {
  Serial.begin(115200);
  EEPROM.begin(EEPROM_SIZE);

  Serial.println("=== Data Logging ke EEPROM ===");
  sensorDHT.begin();
  delay(2000);

  // Baca jumlah record yang tersimpan
  jumlahRecord = EEPROM.read(ALAMAT_COUNTER);
  if (jumlahRecord > MAX_RECORDS) {
    Serial.println("[INFO] EEPROM penuh. Menimpa dari awal.");
    jumlahRecord = 0;
  }
  Serial.printf("Record tersimpan: %d / %d\n", jumlahRecord, MAX_RECORDS);
}

// === Simpan Data ke EEPROM ===
void simpanData(float suhu, float kelembaban) {
  if (jumlahRecord >= MAX_RECORDS) {
    Serial.println("[WARN] EEPROM penuh! Data baru tidak disimpan.");
    return;
  }

  DataSensor data;
  data.suhu       = suhu;
  data.kelembaban = kelembaban;
  data.timestamp  = millis();
  data.valid      = true;

  // Hitung alamat penyimpanan
  int alamat = ALAMAT_AWAL + (jumlahRecord * sizeof(DataSensor));

  // Tulis data ke EEPROM
  EEPROM.put(alamat, data);
  EEPROM.write(ALAMAT_COUNTER, jumlahRecord + 1);
  EEPROM.commit();  // Wajib untuk ESP32!

  jumlahRecord++;
  Serial.printf("[EEPROM] Data #%d disimpan di alamat %d\n", jumlahRecord, alamat);
}

// === Baca Data dari EEPROM ===
void bacaSemuaData() {
  Serial.printf("\n=== Total %d Record di EEPROM ===\n", jumlahRecord);
  for (int i = 0; i < jumlahRecord; i++) {
    DataSensor data;
    int alamat = ALAMAT_AWAL + (i * sizeof(DataSensor));
    EEPROM.get(alamat, data);

    if (data.valid) {
      Serial.printf("#%d | %.1fยฐC | %.1f%% | %lu ms\n",
                    i + 1, data.suhu, data.kelembaban, data.timestamp);
    }
  }
}

void loop() {
  static unsigned long lastSimpan = 0;

  if (millis() - lastSimpan > 10000) {  // Simpan setiap 10 detik
    lastSimpan = millis();

    float suhu = sensorDHT.readTemperature();
    float kelembaban = sensorDHT.readHumidity();

    if (!isnan(suhu) && !isnan(kelembaban)) {
      simpanData(suhu, kelembaban);

      // Setiap 5 record, tampilkan semua data
      if (jumlahRecord % 5 == 0) {
        bacaSemuaData();
      }
    }
  }
}
โš ๏ธ Batasan EEPROM ESP32

EEPROM ESP32 hanya 4096 bytes. Setiap DataSensor menempati 12 bytes (2 float + 1 unsigned long + 1 bool), sehingga maksimal sekitar 340 record. Untuk logging data 10 detik sekali, EEPROM hanya cukup ~56 menit. Untuk logging jangka panjang, gunakan SD Card.

Opsi 2: Menyimpan ke SD Card (Data Besar)

๐Ÿ”Œ Wiring ESP32 + SD Card Module (SPI)
ESP32 3.3V
SD VCC
ESP32 GND
SD GND
ESP32 GPIO23
SD MOSI
ESP32 GPIO19
SD MISO
ESP32 GPIO18
SD CLK (SCK)
ESP32 GPIO5
SD CS (Chip Select)
C++ โ€” simpan_sdcard.ino
// =============================================
// Menyimpan Data Sensor ke SD Card sebagai CSV
// BeebaneLabs - https://beebanelabs.pages.dev
// =============================================

#include "DHT.h"
#include <SPI.h>
#include <SD.h>

#define DHT_PIN   4
#define DHT_TYPE  DHT11
#define SD_CS_PIN 5   // GPIO5 sebagai Chip Select

DHT sensorDHT(DHT_PIN, DHT_TYPE);
const char* NAMA_FILE = "/sensor_log.csv";

// === Inisialisasi SD Card ===
bool mulaiSDCard() {
  if (!SD.begin(SD_CS_PIN)) {
    Serial.println("[ERROR] SD Card gagal dimulai!");
    return false;
  }
  Serial.printf("SD Card: Tipe=%d, Kapasitas=%llu bytes\n",
                SD.cardType(), SD.cardSize());
  return true;
}

// === Tulis Header CSV ===
void tulisHeader() {
  File file = SD.open(NAMA_FILE, FILE_APPEND);
  if (file) {
    // Tulis header hanya jika file baru
    if (file.size() == 0) {
      file.println("nomor,suhu_c,kelembaban_persen,heat_index,millis,uptime_detik");
      Serial.println("[SD] Header CSV ditulis.");
    }
    file.close();
  }
}

// === Tulis Data Baru ===
void tulisDataCSV(float suhu, float kelembaban, float heatIndex) {
  File file = SD.open(NAMA_FILE, FILE_APPEND);
  if (file) {
    // Hitung nomor baris berdasarkan ukuran file
    int nomorBaris = file.size() / 50;  // Estimasi kasar

    // Format CSV: nomor,suhu,kelembaban,heat_index,millis,uptime
    file.printf("%d,%.2f,%.2f,%.2f,%lu,%lu\n",
                nomorBaris + 1, suhu, kelembaban, heatIndex,
                millis(), millis() / 1000);
    file.close();

    Serial.printf("[SD] Baris #%d ditulis: %.1fยฐC, %.1f%%\n",
                  nomorBaris + 1, suhu, kelembaban);
  } else {
    Serial.println("[ERROR] Gagal menulis ke SD Card!");
  }
}

void setup() {
  Serial.begin(115200);
  sensorDHT.begin();

  Serial.println("=== Data Logging ke SD Card ===");
  delay(2000);

  if (mulaiSDCard()) {
    tulisHeader();
    Serial.println("[OK] SD Card siap untuk logging.");
  }
}

void loop() {
  static unsigned long lastTulis = 0;

  if (millis() - lastTulis > 15000) {  // Tulis setiap 15 detik
    lastTulis = millis();

    float suhu = sensorDHT.readTemperature();
    float kelembaban = sensorDHT.readHumidity();

    if (!isnan(suhu) && !isnan(kelembaban)) {
      float heatIndex = sensorDHT.computeHeatIndex(suhu, kelembaban, false);
      tulisDataCSV(suhu, kelembaban, heatIndex);
    }
  }
}
๐Ÿ’ก Tips SD Card Logging

Format SD Card sebagai FAT32 atau exFAT sebelum digunakan. Gunakan file CSV supaya data mudah dibuka di Excel atau Google Sheets. Jangan lupa lepaskan SD Card sebelum mencabutnya dari module untuk menghindari kerusakan data. Kapasitas 16GB SD Card cukup untuk menyimpan data logging bertahun-tahun!

8. Proyek: Sensor + MQTT + Web Dashboard

Di bagian ini, kita akan menggabungkan semua materi ke dalam satu proyek utuh: ESP32 membaca sensor DHT, mengirim data ke MQTT broker, dan data ditampilkan secara real-time di web dashboard sederhana yang dibangun dengan HTML/JavaScript.

Arsitektur Sistem

๐Ÿ—๏ธ Arsitektur Proyek MQTT + Web Dashboard
๐ŸŒก๏ธ
DHT11 / DHT22
Sensor Suhu
โ†’ GPIO โ†’
๐Ÿ“ก
ESP32
+ WiFi Module
โ†’ WiFi โ†’
๐Ÿ“ฌ
MQTT Broker
Mosquitto ยท Port 1883
โ† WSS โ†
๐Ÿ–ฅ๏ธ
Web Dashboard
HTML + JS ยท Port 8080
๐Ÿ”„
MQTT WebSock Client
Subscribe ke Broker

Kode ESP32: Publisher MQTT

C++ โ€” dht_mqtt_dashboard.ino
// =============================================
// Proyek: DHT Sensor + MQTT + Web Dashboard
// BeebaneLabs - https://beebanelabs.pages.dev
// =============================================

#include "WiFi.h"
#include "PubSubClient.h"
#include "DHT.h"

// === Konfigurasi WiFi ===
const char* WIFI_SSID = "NamaWiFiAnda";
const char* WIFI_PASS = "PasswordWiFi";

// === Konfigurasi MQTT ===
const char* MQTT_SERVER   = "test.mosquitto.org";
const int   MQTT_PORT     = 1883;
const char* TOPIC_DATA    = "iothub/project/dht/data";
const char* TOPIC_STATUS  = "iothub/project/dht/status";
const char* CLIENT_ID     = "esp32_dht_dashboard_01";

// === Konfigurasi Sensor ===
#define DHT_PIN  4
#define DHT_TYPE DHT11
#define LED_PIN  2

// === Objek ===
DHT sensorDHT(DHT_PIN, DHT_TYPE);
WiFiClient espClient;
PubSubClient mqtt(espClient);

// === Interval ===
const unsigned long INTERVAL_KIRIM = 10000;  // 10 detik
unsigned long lastKirim = 0;
int msgCount = 0;

// === Koneksi WiFi ===
void setupWiFi() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  Serial.print("WiFi: Menghubungkan");
  int attempt = 0;

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
    if (++attempt > 40) {
      Serial.println(" GAGAL! Restart...");
      ESP.restart();
    }
  }

  Serial.printf(" OK! IP: %s\n", WiFi.localIP().toString().c_str());
}

// === Koneksi MQTT ===
void hubungkanMQTT() {
  while (!mqtt.connected()) {
    Serial.print("MQTT: Menghubungkan...");
    if (mqtt.connect(CLIENT_ID)) {
      Serial.println(" OK!");
      mqtt.publish(TOPIC_STATUS, "{\"status\":\"online\"}", true);
    } else {
      Serial.printf(" GAGAL (rc=%d). Coba lagi 3s...\n", mqtt.state());
      delay(3000);
    }
  }
}

// === Kirim Data sebagai JSON ===
void kirimData() {
  float suhu       = sensorDHT.readTemperature();
  float kelembaban = sensorDHT.readHumidity();

  if (isnan(suhu) || isnan(kelembaban)) {
    Serial.println("[ERROR] Sensor gagal membaca!");
    return;
  }

  float heatIndex = sensorDHT.computeHeatIndex(suhu, kelembaban, false);

  // Format JSON
  char payload[256];
  snprintf(payload, sizeof(payload),
    "{"
    "\"suhu\":%.1f,"
    "\"kelembaban\":%.1f,"
    "\"heat_index\":%.1f,"
    "\"rssi\":%d,"
    "\"uptime\":%lu,"
    "\"msg_count\":%d,"
    "\"timestamp\":%lu"
    "}",
    suhu, kelembaban, heatIndex,
    WiFi.RSSI(),
    millis() / 1000,
    msgCount + 1,
    millis()
  );

  if (mqtt.publish(TOPIC_DATA, payload)) {
    msgCount++;
    Serial.printf("[%d] Suhu: %.1fยฐC | RH: %.1f%% | HI: %.1fยฐC\n",
                  msgCount, suhu, kelembaban, heatIndex);
    digitalWrite(LED_PIN, HIGH);
    delay(50);
    digitalWrite(LED_PIN, LOW);
  } else {
    Serial.println("[ERROR] Gagal publish ke MQTT!");
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(LED_PIN, OUTPUT);

  Serial.println("โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—");
  Serial.println("โ•‘  DHT + MQTT + Dashboard v1.0      โ•‘");
  Serial.println("โ•‘  BeebaneLabs - https://beebanelabs.pages.dev        โ•‘");
  Serial.println("โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•");

  sensorDHT.begin();
  delay(2000);

  setupWiFi();
  mqtt.setServer(MQTT_SERVER, MQTT_PORT);
  mqtt.setBufferSize(512);
  hubungkanMQTT();
}

void loop() {
  if (!mqtt.connected()) {
    hubungkanMQTT();
  }
  mqtt.loop();

  if (millis() - lastKirim >= INTERVAL_KIRIM) {
    lastKirim = millis();
    kirimData();
  }
}

Web Dashboard (HTML + JavaScript)

Simpan kode berikut sebagai file dashboard.html dan buka di browser. Dashboard ini menggunakan MQTT over WebSocket untuk menerima data real-time dari broker MQTT.

HTML/JS โ€” dashboard.html
<!DOCTYPE html>
<html lang="id">
<head>
  <meta charset="UTF-8">
  <title>IoT Dashboard - Monitoring Suhu</title>
  <script src="https://unpkg.com/mqtt/dist/mqtt.min.js"></script>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body { font-family: 'Segoe UI', sans-serif; background: #0f172a; color: #e2e8f0; padding: 20px; }
    h1 { text-align: center; margin-bottom: 20px; color: #38bdf8; }
    .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; max-width: 800px; margin: 0 auto; }
    .card { background: #1e293b; border-radius: 12px; padding: 24px; text-align: center; border: 1px solid #334155; }
    .card .label { font-size: 0.85rem; color: #94a3b8; margin-bottom: 8px; }
    .card .value { font-size: 2.5rem; font-weight: 700; }
    .card .unit { font-size: 0.9rem; color: #64748b; }
    .card.suhu .value { color: #f97316; }
    .card.rh .value { color: #38bdf8; }
    .card.hi .value { color: #ef4444; }
    .card.rssi .value { color: #22c55e; font-size: 1.5rem; }
    #status { text-align: center; margin: 16px 0; font-size: 0.9rem; }
    #status.connected { color: #22c55e; }
    #status.disconnected { color: #ef4444; }
    #log { max-width: 800px; margin: 20px auto; background: #1e293b; border-radius: 8px; padding: 16px; font-family: monospace; font-size: 0.8rem; max-height: 200px; overflow-y: auto; }
  </style>
</head>
<body>
  <h1>๐ŸŒก๏ธ IoT Dashboard - Real-time Monitoring</h1>
  <p id="status" class="disconnected">๐Ÿ”ด Menunggu koneksi MQTT...</p>

  <div class="grid">
    <div class="card suhu">
      <div class="label">Suhu</div>
      <div class="value" id="suhu">--</div>
      <div class="unit">ยฐC</div>
    </div>
    <div class="card rh">
      <div class="label">Kelembaban</div>
      <div class="value" id="rh">--</div>
      <div class="unit">%</div>
    </div>
    <div class="card hi">
      <div class="label">Heat Index</div>
      <div class="value" id="hi">--</div>
      <div class="unit">ยฐC</div>
    </div>
    <div class="card rssi">
      <div class="label">WiFi Signal</div>
      <div class="value" id="rssi">--</div>
      <div class="unit">dBm</div>
    </div>
  </div>

  <div id="log"><div>โณ Menunggu data...</div></div>

  <script>
    const broker = "wss://test.mosquitto.org:8081/mqtt";
    const topic  = "iothub/project/dht/data";

    const client = mqtt.connect(broker);
    const logEl  = document.getElementById("log");

    client.on("connect", () => {
      document.getElementById("status").textContent = "๐ŸŸข Terhubung ke MQTT Broker";
      document.getElementById("status").className = "connected";
      client.subscribe(topic);
    });

    client.on("message", (t, msg) => {
      const data = JSON.parse(msg.toString());
      document.getElementById("suhu").textContent = data.suhu.toFixed(1);
      document.getElementById("rh").textContent   = data.kelembaban.toFixed(1);
      document.getElementById("hi").textContent    = data.heat_index.toFixed(1);
      document.getElementById("rssi").textContent  = data.rssi;

      const line = document.createElement("div");
      line.textContent = `[${new Date().toLocaleTimeString()}] Suhu: ${data.suhu}ยฐC | RH: ${data.kelembaban}%`;
      logEl.appendChild(line);
      logEl.scrollTop = logEl.scrollHeight;
    });

    client.on("error", (err) => {
      document.getElementById("status").textContent = "๐Ÿ”ด Error: " + err.message;
      document.getElementById("status").className = "disconnected";
    });
  </script>
</body>
</html>
โ„น๏ธ Cara Menjalankan Dashboard
  1. Simpan kode HTML di atas sebagai file dashboard.html
  2. Upload program ESP32 ke board Anda
  3. Buka file dashboard.html di browser (Chrome/Edge/Firefox)
  4. Pastikan ESP32 dan komputer terhubung ke internet
  5. Dashboard akan menampilkan data suhu secara real-time!

Kustomisasi Lanjutan

9. Quiz: Uji Pemahamanmu!

Setelah membaca tutorial di atas, jawablah 5 pertanyaan berikut untuk menguji pemahamanmu tentang sensor DHT dan ESP32:

Pertanyaan 1: Berapa akurasi suhu sensor DHT11?

a) ยฑ2ยฐC
b) ยฑ0.5ยฐC
c) ยฑ1ยฐC
d) ยฑ0.1ยฐC

Pertanyaan 2: Komponen elektronik apa yang WAJIB dipasang antara pin DATA sensor DHT dan VCC agar pembacaan stabil?

a) Kapasitor 100ยตF
b) LED indicator
c) Transistor NPN
d) Resistor pull-up 10kฮฉ

Pertanyaan 3: Sensor mana yang memiliki fitur pengukuran tekanan udara (barometrik)?

a) DHT11
b) BME280
c) DHT22
d) Semua sensor di atas

Pertanyaan 4: Berapa kapasitas memori EEPROM ESP32 yang tersedia untuk menyimpan data sensor?

a) 512 bytes
b) 1024 bytes
c) 4096 bytes
d) 65536 bytes

Pertanyaan 5: Fungsi utama dari computeHeatIndex() pada library DHT adalah...

a) Menghitung suhu aktual yang lebih akurat dari sensor
b) Menghitung suhu titik embun (dew point)
c) Menghitung suhu yang dirasakan tubuh berdasarkan suhu dan kelembaban
d) Mengkalibrasi ulang pembacaan sensor secara otomatis
โ† Sebelumnya Protokol MQTT untuk IoT Selanjutnya โ†’ Raspberry Pi untuk IoT