Menyalakan Lampu AC dengan Gerakan Tangan: ESP32, OpenCV, dan MQTT

Proyek ESP32 OpenCV MQTT ini menggabungkan kecerdasan buatan dan IoT: kamera laptop mendeteksi gerakan tangan dengan OpenCV dan MediaPipe, program Python mengirim perintah ON/OFF lewat MQTT, lalu ESP32 menyalakan atau mematikan lampu AC melalui relay. Tutorial ini sudah diperbarui dengan kode yang teruji (paho-mqtt 2.x, MediaPipe versi yang kompatibel) serta pin relay yang aman untuk ESP32.

⚠️ Peringatan keselamatan listrik: tegangan AC 220 V berbahaya dan dapat mematikan. Semua sambungan AC harus dikerjakan oleh teknisi atau orang dewasa berpengalaman, memakai modul relay opto-isolator, dan ditempatkan di kotak tertutup. Untuk latihan, gunakan LED atau beban DC terlebih dahulu.

Daftar isi:

Cara Kerja Sistem ESP32 OpenCV MQTT

  1. Kamera laptop merekam tangan; MediaPipe Hands mendeteksi 21 titik (landmark) pada tangan.
  2. Program Python menghitung jari yang terbuka: 4 jari terbuka = ON, tangan mengepal = OFF.
  3. Perintah dikirim ke topik MQTT bisaioti/rumah/lampu/ruang_tamu/set — hanya saat status berubah.
  4. ESP32 yang berlangganan topik tersebut menggerakkan relay, lalu melaporkan status ke topik .../state.

Konsep publish/subscribe yang dipakai di sini dijelaskan di panduan protokol MQTT.

Alat dan Bahan

  • ESP32 DevKit dan kabel USB data.
  • Modul relay opto-isolator 1–4 channel (5 V, aktif LOW).
  • Lampu AC + fitting + kabel, atau LED/beban DC untuk latihan aman.
  • Kabel jumper male-female.
  • Laptop/PC dengan webcam, Python 3.10–3.12, dan Arduino IDE.
  • Broker MQTT: broker publik test.mosquitto.org untuk uji coba, atau Mosquitto lokal (disarankan).

Wiring ESP32 ke Modul Relay

Rangkaian ESP32 OpenCV MQTT: ESP32 terhubung ke modul relay 4 channel untuk lampu AC
Rangkaian ESP32 ke modul relay 4 channel dan uji deteksi gerakan tangan — gunakan pin sesuai tabel wiring
ESP32Modul relay
VIN (5 V)VCC
GNDGND
GPIO26IN1
GPIO27IN2
GPIO14IN3
GPIO25IN4

Versi lama tutorial ini memakai GPIO12 untuk IN4. GPIO12 adalah strapping pin yang menentukan tegangan flash saat boot; modul relay yang menarik pin ini ke HIGH bisa membuat ESP32 gagal boot atau gagal upload. Karena itu IN4 dipindah ke GPIO25.

Sisi AC: kabel fase (L) diputus melalui relay — sambungkan ke terminal COM, lalu dari NO (Normally Open) ke lampu. Kabel netral (N) langsung ke lampu. Semua sambungan AC wajib berada di dalam kotak tertutup.

Instalasi Software

Arduino IDE: instal board ESP32 lewat Boards Manager (URL https://espressif.github.io/arduino-esp32/package_esp32_index.json) dan library PubSubClient.

Python: buat virtual environment lalu instal library:

python -m venv venv
# Windows: venv\Scripts\activate   |   Linux/macOS: source venv/bin/activate
pip install opencv-python "mediapipe==0.10.21" "paho-mqtt>=2.0"

Penting: MediaPipe versi 0.10.30 ke atas menghapus API mp.solutions yang dipakai program ini, sehingga muncul error module 'mediapipe' has no attribute 'solutions'. Karena itu versi dikunci ke 0.10.21 (mendukung Python 3.9–3.12). paho-mqtt 2.x juga mewajibkan parameter CallbackAPIVersion saat membuat client.

Program Python OpenCV: Deteksi Gerakan Tangan dan Kirim MQTT ke ESP32

Simpan sebagai hand_control.py. Jika memakai broker publik, ganti topik dengan nama unik agar tidak bentrok dengan pengguna lain.

import time
import uuid

import cv2
import mediapipe as mp
import paho.mqtt.client as mqtt

# ====== KONFIGURASI ======
BROKER    = "test.mosquitto.org"   # atau IP komputer yang menjalankan Mosquitto
PORT      = 1883
TOPIC_SET = "bisaioti/rumah/lampu/ruang_tamu/set"   # buat unik jika memakai broker publik
JEDA_MIN  = 1.0                     # jeda minimal antar perintah (detik)
# =========================

client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2,
                     client_id=f"gesture-pc-{uuid.uuid4().hex[:6]}")
client.connect(BROKER, PORT, keepalive=60)
client.loop_start()

mp_hands = mp.solutions.hands
mp_draw = mp.solutions.drawing_utils


def hitung_jari(hand):
    """Hitung jari telunjuk-kelingking yang terbuka (ujung jari di atas sendi)."""
    lm = hand.landmark
    jari = 0
    for ujung, sendi in [(8, 6), (12, 10), (16, 14), (20, 18)]:
        if lm[ujung].y < lm[sendi].y:
            jari += 1
    return jari


cap = cv2.VideoCapture(0)
state_terakhir, waktu_kirim = None, 0.0

with mp_hands.Hands(max_num_hands=1, min_detection_confidence=0.7,
                    min_tracking_confidence=0.5) as hands:
    while cap.isOpened():
        ok, frame = cap.read()
        if not ok:
            break
        frame = cv2.flip(frame, 1)
        hasil = hands.process(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))

        gesture = None
        if hasil.multi_hand_landmarks:
            hand = hasil.multi_hand_landmarks[0]
            mp_draw.draw_landmarks(frame, hand, mp_hands.HAND_CONNECTIONS)
            jari = hitung_jari(hand)
            if jari >= 4:
                gesture = "ON"      # telapak terbuka
            elif jari == 0:
                gesture = "OFF"     # tangan mengepal

        # kirim hanya jika status berubah, agar broker tidak dibanjiri pesan
        if gesture and gesture != state_terakhir and time.time() - waktu_kirim > JEDA_MIN:
            client.publish(TOPIC_SET, gesture, qos=1, retain=True)
            state_terakhir, waktu_kirim = gesture, time.time()
            print("Kirim perintah:", gesture)

        cv2.putText(frame, f"Lampu: {state_terakhir or '-'}", (10, 40),
                    cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
        cv2.imshow("Kontrol Lampu dengan Gerakan Tangan (tekan q)", frame)
        if cv2.waitKey(1) & 0xFF == ord("q"):
            break

cap.release()
cv2.destroyAllWindows()
client.loop_stop()
client.disconnect()
  • hitung_jari() membandingkan posisi ujung jari dengan sendinya; jika ujung lebih tinggi, jari dianggap terbuka.
  • Perintah hanya dikirim ketika status berubah dan minimal berjarak 1 detik, sehingga lampu tidak berkedip dan broker tidak dibanjiri pesan.
  • Pesan dikirim dengan retain=True, sehingga ESP32 yang baru menyala langsung menerima perintah terakhir.
  • Client ID dibuat acak dengan uuid agar tidak bentrok dengan client lain.

Program ESP32: Menerima Perintah MQTT dari OpenCV dan Mengendalikan Relay

#include <WiFi.h>
#include <PubSubClient.h>

// ====== KONFIGURASI ======
const char* WIFI_SSID     = "NAMA_WIFI";
const char* WIFI_PASSWORD = "PASSWORD_WIFI";
const char* MQTT_SERVER   = "test.mosquitto.org";   // samakan dengan program Python
const int   MQTT_PORT     = 1883;
const char* TOPIC_SET     = "bisaioti/rumah/lampu/ruang_tamu/set";
const char* TOPIC_STATE   = "bisaioti/rumah/lampu/ruang_tamu/state";

// Relay aktif LOW (umum pada modul relay opto-isolator)
const int RELAYS[] = {26, 27, 14, 25};   // hindari GPIO12 (strapping pin)
const int JUMLAH_RELAY = 4;
// =========================

WiFiClient espClient;
PubSubClient mqtt(espClient);
String clientId;
unsigned long lastReconnect = 0;

void setLampu(bool nyala) {
  for (int i = 0; i < JUMLAH_RELAY; i++) digitalWrite(RELAYS[i], nyala ? LOW : HIGH);
  mqtt.publish(TOPIC_STATE, nyala ? "ON" : "OFF", true);   // laporkan status (retained)
  Serial.printf("Lampu %s\n", nyala ? "MENYALA" : "MATI");
}

void callback(char* topic, byte* payload, unsigned int length) {
  String msg;
  for (unsigned int i = 0; i < length; i++) msg += (char)payload[i];
  msg.trim();
  Serial.printf("Perintah masuk: %s\n", msg.c_str());
  if (msg == "ON")  setLampu(true);
  if (msg == "OFF") setLampu(false);
}

void connectMQTT() {
  Serial.print("Menghubungkan ke broker ... ");
  if (mqtt.connect(clientId.c_str())) {
    Serial.println("berhasil");
    mqtt.subscribe(TOPIC_SET, 1);   // perintah terakhir (retained) langsung diterima
  } else {
    Serial.printf("gagal, state=%d\n", mqtt.state());
  }
}

void setup() {
  Serial.begin(115200);
  for (int i = 0; i < JUMLAH_RELAY; i++) {
    pinMode(RELAYS[i], OUTPUT);
    digitalWrite(RELAYS[i], HIGH);   // semua relay OFF saat mulai
  }
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  while (WiFi.status() != WL_CONNECTED) delay(500);
  Serial.println("WiFi terhubung");

  String mac = WiFi.macAddress();
  mac.replace(":", "");
  clientId = "esp32-relay-" + mac.substring(6);

  mqtt.setServer(MQTT_SERVER, MQTT_PORT);
  mqtt.setCallback(callback);
}

void loop() {
  if (!mqtt.connected()) {
    if (lastReconnect == 0 || millis() - lastReconnect > 5000) {
      lastReconnect = millis();
      connectMQTT();
    }
    return;
  }
  mqtt.loop();
}
  • Relay dinyalakan dengan LOW karena modul relay opto-isolator umumnya aktif LOW; semua relay dimatikan saat ESP32 mulai.
  • Subscribe dilakukan sekali setiap kali terhubung, dan reconnect berjalan tanpa memblokir program.
  • Status lampu dilaporkan ke topik .../state sehingga bisa ditampilkan di dashboard. Contoh dashboard ada di tutorial ESP32 DHT11 Node-RED.

Uji Coba Sistem ESP32, OpenCV, dan MQTT

  1. Upload program ke ESP32 dan buka Serial Monitor (115200). Pastikan muncul WiFi terhubung dan Menghubungkan ke broker … berhasil.
  2. Jalankan python hand_control.py. Jendela kamera akan terbuka.
  3. Buka telapak tangan (4 jari terbuka) → lampu menyala. Kepalkan tangan → lampu mati. Tekan q untuk keluar.
  4. Untuk memantau pesan, jalankan mosquitto_sub -h test.mosquitto.org -t "bisaioti/rumah/lampu/#" -v.

Troubleshooting ESP32 OpenCV MQTT

GejalaPenyebabSolusi
module 'mediapipe' has no attribute 'solutions'MediaPipe versi baru (0.10.30+)pip install "mediapipe==0.10.21" dengan Python 3.9–3.12
Error saat mqtt.Client(...)Kode lama paho-mqtt 1.xGunakan mqtt.CallbackAPIVersion.VERSION2 seperti di atas
Kamera tidak terbukaIndeks kamera salah atau dipakai aplikasi lainGanti VideoCapture(0) menjadi 1, tutup aplikasi lain
ESP32 gagal boot/upload setelah relay dipasangRelay terhubung ke strapping pin (GPIO12/GPIO2/GPIO15)Pakai pin 26, 27, 14, 25 seperti tabel wiring
Relay bekerja terbalikModul relay aktif HIGHTukar LOW dan HIGH di fungsi setLampu()
Perintah tidak sampaiTopik atau broker berbedaSamakan topik dan broker di Python dan ESP32

Pengembangan Lanjutan

  • Tambahkan gesture lain (misalnya 1 jari = lampu 1, 2 jari = lampu 2) untuk mengendalikan tiap relay secara terpisah.
  • Gunakan broker pribadi dengan login dan TLS agar orang lain tidak bisa mengirim perintah — lihat tutorial HiveMQ Cloud.
  • Integrasikan dengan Node-RED atau Home Assistant untuk tombol manual dan jadwal otomatis.

📌 Baca juga: Waveshare ESP32-S3-Relay-1CH: pinout, wiring & program

Kesimpulan

Dengan OpenCV, MediaPipe, MQTT, dan ESP32, Anda sudah membangun sistem AI + IoT sederhana: gerakan tangan → perintah MQTT → relay → lampu AC. Pola yang sama bisa dipakai untuk mengendalikan kipas, pompa, atau perangkat lain. Pelajari dasar komunikasinya di tutorial MQTT ESP32 dan tutorial MQTT Python, atau ikuti Training ESP32 Berbasis IoT.

Referensi

Baca juga: ESP32: Panduan Komprehensif untuk Pemula hingga Ahli (IoT, Pemrograman, & Proyek).

Related Articles

Tinggalkan Balasan

Chat Langsung dengan Kami
Staff bisaioti 1 — +62 823-3306-4821 E-Course, Offline Course, Hardware & Teknis IoT/OT Staff bisaioti 2 — +62 823-3306-4821 Training Korporat & Toko Hardware