MODUL PRAKTIK V.2.0
SMA KRISTEN BARANA

Internet of Things (IoT)
Masterclass

Sistem Smart Home: Monitoring Suhu & Kontrol Relay Web

NodeMCU ESP8266 + Firebase RTDB + Google Apps Script

1 PENDAHULUAN

1.1 Tujuan Pembelajaran

Setelah menyelesaikan modul ini, peserta didik diharapkan mampu:

  1. Memahami konsep dasar arsitektur IoT berbasis Cloud.
  2. Merangkai sensor (DHT11) dan aktuator (Relay) pada NodeMCU ESP8266.
  3. Mengkonfigurasi Firebase Realtime Database sebagai broker dan penyimpan data.
  4. Membuat antarmuka Web Dashboard yang responsif menggunakan layanan gratis Google Apps Script (GAS).
  5. Memprogram NodeMCU untuk mengirim dan menerima data secara real-time.

1.2 Konsep Kerja Sistem

Sistem ini menggunakan Firebase Realtime Database sebagai pusat data.

  • NodeMCU bertugas membaca sensor DHT11 dan mengirimkannya ke Firebase. Di saat bersamaan, ia selalu mengecek status perintah di Firebase. Jika ada perintah "ON", NodeMCU akan menyalakan Relay.
  • Google Apps Script (GAS) digunakan sebagai Web Server gratis untuk menampilkan Dashboard HTML/CSS/JS. Dashboard ini akan mengakses Firebase secara langsung, sehingga pengguna bisa melihat suhu dan menekan tombol relay melalui HP atau Laptop dari mana saja.

2 ALAT DAN BAHAN

A. Hardware

  • NodeMCU ESP8266 (atau ESP32)
  • Sensor Suhu & Kelembapan DHT11
  • Modul Relay 1-Channel (5V)
  • Kabel Jumper (Secukupnya)
  • Kabel Micro USB

B. Software

  • Arduino IDE (versi terbaru)
  • Akun Google (Firebase & GAS)
  • Web Browser (Chrome/Firefox)
  • Koneksi Internet / WiFi

3 PERSIAPAN CLOUD

3.1 Konfigurasi Firebase Realtime Database

  1. Buka console.firebase.google.com dan login dengan akun Google.
  2. Klik Tambahkan Proyek (Add Project), beri nama "IoT Smart Home".
  3. Di menu kiri, pilih Build > Realtime Database > Klik Create Database.
  4. Pilih lokasi server terdekat, lalu pilih Start in Test Mode (Mode pengujian agar perangkat bisa baca-tulis tanpa otentikasi rumit). Klik Enable.
  5. Salin dan simpan URL Database Anda (tanpa https:// dan tanpa / di akhir). Contoh: iot-smarthome-123.firebaseio.com.

3.2 Pembuatan Dashboard Web di GAS

  1. Buka script.google.com dan buat Proyek Baru.
  2. Ganti nama proyek di kiri atas menjadi "Dashboard IoT".
  3. Pada tab Code.gs, hapus semua kode dan masukkan kode Javascript backend ini:
function doGet() {
  return HtmlService.createHtmlOutputFromFile('Index')
      .setTitle('Dashboard IoT Pintar')
      .addMetaTag('viewport', 'width=device-width, initial-scale=1');
}
  1. Buat file baru: Klik ikon + (Tambahkan file) di sebelah kiri > pilih HTML > beri nama Index (Huruf kapital I).
  2. Masukkan kode antarmuka HTML super keren di bawah ini ke dalam file Index.html Anda.

    Langkah Super Penting!

    Cari bagian databaseURL: "https://..." di dalam baris kode di bawah, lalu ganti dengan URL Firebase Anda sendiri.

<!DOCTYPE html>
<html lang="id">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Dashboard IoT Pintar</title>
    <!-- Menggunakan Tailwind CSS untuk desain responsif yang modern -->
    <script src="https://cdn.tailwindcss.com"></script>
    <!-- FontAwesome untuk Ikon -->
    <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
    <style>
        .fade-in { animation: fadeIn 0.5s ease-in-out; }
        @keyframes fadeIn {
            from { opacity: 0; transform: translateY(10px); }
            to { opacity: 1; transform: translateY(0); }
        }
        .toggle-checkbox:checked { right: 0; border-color: #3b82f6; }
        .toggle-checkbox:checked + .toggle-label { background-color: #3b82f6; }
        .toggle-checkbox { right: 0; z-index: 1; border-color: #e5e7eb; transition: all 0.3s; }
        .toggle-label { background-color: #e5e7eb; transition: all 0.3s; }
    </style>
</head>
<body class="bg-slate-100 font-sans min-h-screen text-slate-800">

    <!-- Navbar -->
    <nav class="bg-blue-600 text-white shadow-lg">
        <div class="max-w-6xl mx-auto px-4 py-4 flex justify-between items-center">
            <div class="text-xl font-bold flex items-center gap-2">
                <i class="fa-solid fa-house-signal"></i> Smart Home IoT
            </div>
            <div id="connection-status" class="text-sm bg-blue-700 px-3 py-1 rounded-full flex items-center gap-2">
                <span class="w-2 h-2 rounded-full bg-yellow-400 animate-pulse" id="status-dot"></span>
                <span id="status-text">Menghubungkan...</span>
            </div>
        </div>
    </nav>

    <!-- Main Content -->
    <main class="max-w-6xl mx-auto px-4 py-8">
        <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">

            <!-- Card Suhu -->
            <div class="bg-white rounded-2xl shadow-sm p-6 flex flex-col items-center justify-center fade-in border border-slate-100 hover:shadow-md transition-shadow">
                <div class="w-16 h-16 rounded-full bg-red-100 text-red-500 flex items-center justify-center text-2xl mb-4">
                    <i class="fa-solid fa-temperature-half"></i>
                </div>
                <h2 class="text-slate-500 font-medium mb-1">Suhu Ruangan</h2>
                <div class="text-4xl font-bold text-slate-800 flex items-baseline gap-1">
                    <span id="suhu-value">--</span>
                    <span class="text-xl font-normal text-slate-400">°C</span>
                </div>
            </div>

            <!-- Card Kelembapan -->
            <div class="bg-white rounded-2xl shadow-sm p-6 flex flex-col items-center justify-center fade-in border border-slate-100 hover:shadow-md transition-shadow" style="animation-delay: 0.1s;">
                <div class="w-16 h-16 rounded-full bg-blue-100 text-blue-500 flex items-center justify-center text-2xl mb-4">
                    <i class="fa-solid fa-droplet"></i>
                </div>
                <h2 class="text-slate-500 font-medium mb-1">Kelembapan</h2>
                <div class="text-4xl font-bold text-slate-800 flex items-baseline gap-1">
                    <span id="kelembapan-value">--</span>
                    <span class="text-xl font-normal text-slate-400">%</span>
                </div>
            </div>

            <!-- Card Kontrol Relay -->
            <div class="bg-white rounded-2xl shadow-sm p-6 flex flex-col items-center justify-center fade-in border border-slate-100 hover:shadow-md transition-shadow" style="animation-delay: 0.2s;">
                <div class="w-16 h-16 rounded-full bg-yellow-100 text-yellow-500 flex items-center justify-center text-2xl mb-4" id="relay-icon-bg">
                    <i class="fa-solid fa-lightbulb" id="relay-icon"></i>
                </div>
                <h2 class="text-slate-500 font-medium mb-3">Kontrol Perangkat (Relay)</h2>
                
                <div class="relative inline-block w-16 mr-2 align-middle select-none transition duration-200 ease-in">
                    <input type="checkbox" name="toggle" id="relay-toggle" class="toggle-checkbox absolute block w-8 h-8 rounded-full bg-white border-4 appearance-none cursor-pointer"/>
                    <label for="relay-toggle" class="toggle-label block overflow-hidden h-8 rounded-full bg-gray-300 cursor-pointer"></label>
                </div>
                <div id="relay-status-text" class="mt-2 font-bold text-slate-400">OFF</div>
            </div>

        </div>
    </main>

    <!-- Firebase App (v8 Compat) -->
    <script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-app.js"></script>
    <script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-database.js"></script>

    <script>
        // PENTING: GANTI URL DI BAWAH DENGAN URL FIREBASE ANDA
        const firebaseConfig = {
            databaseURL: "https://GANTI_DENGAN_URL_FIREBASE_ANDA.firebaseio.com" 
        };

        if (!firebase.apps.length) {
            firebase.initializeApp(firebaseConfig);
        }
        const db = firebase.database();

        // DOM Elements
        const statusDot = document.getElementById('status-dot');
        const statusText = document.getElementById('status-text');
        const suhuEl = document.getElementById('suhu-value');
        const kelembapanEl = document.getElementById('kelembapan-value');
        const relayToggle = document.getElementById('relay-toggle');
        const relayStatusText = document.getElementById('relay-status-text');
        const relayIconBg = document.getElementById('relay-icon-bg');

        // Deteksi Koneksi
        firebase.database().ref(".info/connected").on("value", (snap) => {
            if (snap.val() === true) {
                statusDot.className = 'w-2 h-2 rounded-full bg-green-400';
                statusText.innerText = "Terhubung";
            } else {
                statusDot.className = 'w-2 h-2 rounded-full bg-red-500';
                statusText.innerText = "Terputus";
            }
        });

        // Baca Sensor
        db.ref('/sensor/suhu').on('value', (s) => { if(s.val() !== null) suhuEl.innerText = s.val(); });
        db.ref('/sensor/kelembapan').on('value', (s) => { if(s.val() !== null) kelembapanEl.innerText = s.val(); });

        // Kontrol Relay
        let isCodeClick = false;
        db.ref('/kontrol/relay').on('value', (s) => {
            isCodeClick = true; 
            const state = s.val() === "ON";
            relayToggle.checked = state;
            updateUI(state);
            setTimeout(() => { isCodeClick = false; }, 100);
        });

        relayToggle.addEventListener('change', (e) => {
            if (isCodeClick) return;
            updateUI(e.target.checked);
            db.ref('/kontrol/relay').set(e.target.checked ? "ON" : "OFF");
        });

        function updateUI(isOn) {
            if(isOn) {
                relayStatusText.innerText = "ON";
                relayStatusText.className = "mt-2 font-bold text-blue-500";
                relayIconBg.className = "w-16 h-16 rounded-full bg-blue-100 text-blue-500 flex items-center justify-center text-2xl mb-4";
            } else {
                relayStatusText.innerText = "OFF";
                relayStatusText.className = "mt-2 font-bold text-slate-400";
                relayIconBg.className = "w-16 h-16 rounded-full bg-yellow-100 text-yellow-500 flex items-center justify-center text-2xl mb-4";
            }
        }
    </script>
</body>
</html>
  1. Klik tombol Terapkan (Deploy) > Deployment Baru.
  2. Pilih jenis Aplikasi Web. Atur akses ke Siapa saja (Anyone).
  3. Klik Terapkan. Salin URL Web App yang dihasilkan. Buka link tersebut di HP Anda.

4 PERAKITAN HARDWARE

Pastikan kabel USB belum terpasang ke komputer saat merakit!

1. Sensor DHT11 NodeMCU

  • Pin VCC Pin 3V3
  • Pin GND Pin GND
  • Pin DATA Pin D2 (GPIO4)

2. Relay 5V NodeMCU

  • Pin VCC Pin VU / VIN
  • Pin GND Pin GND
  • Pin IN Pin D1 (GPIO5)

5 PEMROGRAMAN NODEMCU

5.1 Instalasi Library

Buka Arduino IDE, masuk ke menu Sketch > Include Library > Manage Libraries. Cari dan instal:

  1. DHT sensor library oleh Adafruit.
  2. Firebase ESP8266 Client oleh Mobizt.

5.2 Upload Kode Program

Salin kode C++ berikut ke Arduino IDE. Jangan lupa ubah pengaturan WiFi dan Firebase.

#include <ESP8266WiFi.h>
#include <FirebaseESP8266.h>
#include "DHT.h"

// 1. PENGATURAN WIFI
#define WIFI_SSID "NAMA_WIFI_ANDA"
#define WIFI_PASSWORD "PASSWORD_WIFI_ANDA"

// 2. PENGATURAN FIREBASE
// Masukkan URL Firebase (Tanpa https:// dan tanpa / di akhir)
#define FIREBASE_HOST "URL_FIREBASE_ANDA.firebaseio.com" 
// (Opsional) Masukkan rahasia database, kosongkan "" jika masih test mode
#define FIREBASE_AUTH "" 

// 3. PENGATURAN HARDWARE
#define DHTPIN 4      // Pin D2
#define DHTTYPE DHT11 
#define RELAY_PIN 5   // Pin D1

DHT dht(DHTPIN, DHTTYPE);
FirebaseData firebaseData;

unsigned long waktuTerakhir = 0;

void setup() {
  Serial.begin(115200);
  
  pinMode(RELAY_PIN, OUTPUT);
  digitalWrite(RELAY_PIN, HIGH); // Relay OFF (Active Low)
  dht.begin();

  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  Serial.print("\nMenghubungkan WiFi");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\nTerhubung ke WiFi!");

  Firebase.begin(FIREBASE_HOST, FIREBASE_AUTH);
  Firebase.reconnectWiFi(true);
}

void loop() {
  unsigned long waktuSekarang = millis();

  // A. MEMBACA PERINTAH DARI WEB (REAL-TIME)
  if (Firebase.getString(firebaseData, "/kontrol/relay")) {
    String status = firebaseData.stringData();
    if (status == "ON") {
      digitalWrite(RELAY_PIN, LOW); // Relay menyala
    } else if (status == "OFF") {
      digitalWrite(RELAY_PIN, HIGH); // Relay mati
    }
  }

  // B. MENGIRIM DATA SENSOR (SETIAP 5 DETIK)
  if (waktuSekarang - waktuTerakhir > 5000) {
    waktuTerakhir = waktuSekarang;
    
    float h = dht.readHumidity();
    float t = dht.readTemperature();

    if (!isnan(h) && !isnan(t)) {
      Firebase.setFloat(firebaseData, "/sensor/suhu", t);
      Firebase.setFloat(firebaseData, "/sensor/kelembapan", h);
      Serial.println("Suhu: " + String(t) + "C, Kelembapan: " + String(h) + "%");
    } else {
      Serial.println("Gagal membaca sensor!");
    }
  }
}

Klik tombol Upload pada Arduino IDE. Buka Serial Monitor (Baudrate 115200) untuk melihat log data.

6 PENGUJIAN DAN EVALUASI

6.1 Langkah Pengujian

  • Verifikasi Hardware: Serial Monitor menampilkan "Terhubung ke WiFi!".
  • Verifikasi Cloud: Buka Firebase. Pastikan folder /sensor dan /kontrol muncul.
  • Verifikasi Web Dashboard: Buka URL Web App di HP Anda. Coba tekan tombol Toggle Relay. Relay fisik harus berbunyi "klik".

6.2 Pemecahan Masalah (Troubleshooting)

  • Status WiFi terus "...": Pastikan WiFi Anda menggunakan frekuensi 2.4 GHz. NodeMCU tidak mendukung 5 GHz.
  • Sensor "NaN": Kabel data DHT terbalik atau kurang rapat.
  • Relay logika terbalik: Jika relay Anda bertipe Active High, ubah LOW menjadi HIGH (dan sebaliknya) pada fungsi digitalWrite di Arduino IDE.

Tantangan Ekstra!

Modifikasi tampilan HTML di GAS! Coba ubah warna tema, atau tambahkan logika otomatis di Arduino: "Jika suhu > 30°C, maka relay kipas menyala otomatis!"