#include #include #include #include #include #include // ================= SUPABASE ================= const char* supabase_url = "https://hpmicdhjyboyeofphgae.supabase.co"; const char* supabase_key = "sb_publishable_OB8e9y1OO0z3Y5xQ828YvA_T3jD16zT"; // ================= WIFI ================= const char* ssid = "Ozie 1"; const char* password = "followkami"; // ================= TIME (NTP) ================= const char* ntpServer = "pool.ntp.org"; const long gmtOffset_sec = 7 * 3600; // WIB (GMT+7) const int daylightOffset_sec = 0; // ================= MQTT ================= const char* mqtt_server = "broker.hivemq.com"; WiFiClient espClient; PubSubClient client(espClient); // ================= DHT ================= #define DHTPIN 4 #define DHTTYPE DHT22 DHT dht(DHTPIN, DHTTYPE); // ================= RELAY ================= #define RELAY1 27 #define RELAY2 26 // Relay aktif LOW #define RELAY_ON LOW #define RELAY_OFF HIGH // ================= THRESHOLD ================= float batasPanas = 70.0; float batasLembab = 90.0; // ================= GLOBAL STATE ================= unsigned long lastMsg = 0; String statusRelay = "INIT"; bool isAutoMode = true; bool manualPump = false; bool manualLight = false; // State Tracking for Upload bool prevPump = false; bool prevLight = false; unsigned long lastPeriodicUpload = 0; // ================= SUPABASE UPLOAD ================= void uploadToSupabase(float hum, String status, String lamp, String pump) { if (WiFi.status() == WL_CONNECTED) { // Ambil waktu saat ini struct tm timeinfo; String timeStr = ""; if (getLocalTime(&timeinfo)) { char timeStringBuff[50]; strftime(timeStringBuff, sizeof(timeStringBuff), "%H:%M:%S", &timeinfo); timeStr = String(timeStringBuff); } WiFiClientSecure clientSecure; clientSecure.setInsecure(); HTTPClient http; String url = String(supabase_url) + "/rest/v1/humidity"; http.begin(clientSecure, url); http.addHeader("Content-Type", "application/json"); http.addHeader("apikey", supabase_key); http.addHeader("Authorization", "Bearer " + String(supabase_key)); http.addHeader("Prefer", "return=minimal"); String json = "{\"kelembapan\":" + String(hum) + (timeStr != "" ? ",\"tanggal_upload\":\"" + timeStr + "\"" : "") + ",\"status\":\"" + status + "\"" + ",\"status_lampu\":\"" + lamp + "\"" + ",\"status_pompa\":\"" + pump + "\"}"; Serial.println("[Supabase] Uploading data..."); int httpCode = http.POST(json); if (httpCode >= 200 && httpCode < 300) { Serial.printf("[Supabase] Berhasil Terkirim! Status: %d\n", httpCode); } else if (httpCode > 0) { String response = http.getString(); Serial.printf("[Supabase] Gagal Mengirim. HTTP Code: %d\n", httpCode); Serial.printf("[Supabase] Alasan: %s\n", response.c_str()); } else { Serial.printf("[Supabase] Gagal Koneksi. Error: %s\n", http.errorToString(httpCode).c_str()); } http.end(); } else { Serial.println("[Supabase] Gagal: WiFi tidak terhubung!"); } } // ================= WIFI ================= void setup_wifi() { delay(10); Serial.println("Connecting to WiFi..."); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println("\nWiFi connected!"); } // ================= MQTT CALLBACK ================= void callback(char* topic, byte* payload, unsigned int length) { String message = ""; for (int i = 0; i < length; i++) { message += (char)payload[i]; } Serial.print("Message arrived ["); Serial.print(topic); Serial.print("] "); Serial.println(message); if (String(topic) == "esp32/statusiqbal/mode") { isAutoMode = (message == "auto"); Serial.println(isAutoMode ? "Mode: AUTO" : "Mode: MANUAL"); } else if (String(topic) == "esp32/statusiqbal/pump") { manualPump = (message == "on"); Serial.println(manualPump ? "Manual Pump: ON" : "Manual Pump: OFF"); } else if (String(topic) == "esp32/statusiqbal/light") { manualLight = (message == "on"); Serial.println(manualLight ? "Manual Light: ON" : "Manual Light: OFF"); } } // ================= MQTT RECONNECT ================= void reconnect() { while (!client.connected()) { Serial.print("Connecting MQTT..."); // Unique client ID using MAC address String clientId = "ESP32MJ-" + WiFi.macAddress(); if (client.connect(clientId.c_str(), "esp32/statusiqbal", 0, true, "offline")) { Serial.println("CONNECTED to MQTT ✅"); client.publish("esp32/statusiqbal", "online", true); // Subscribe to control topics client.subscribe("esp32/statusiqbal/mode"); client.subscribe("esp32/statusiqbal/pump"); client.subscribe("esp32/statusiqbal/light"); } else { Serial.print("FAILED, rc="); Serial.print(client.state()); Serial.println(" trying again in 5s..."); delay(5000); } } } void setup() { Serial.begin(115200); pinMode(RELAY1, OUTPUT); pinMode(RELAY2, OUTPUT); digitalWrite(RELAY1, RELAY_OFF); digitalWrite(RELAY2, RELAY_OFF); dht.begin(); setup_wifi(); // Konfigurasi waktu dari NTP configTime(gmtOffset_sec, daylightOffset_sec, ntpServer); client.setServer(mqtt_server, 1883); client.setCallback(callback); } void loop() { if (!client.connected()) { reconnect(); } client.loop(); unsigned long now = millis(); if (now - lastMsg > 2000) { lastMsg = now; float suhu = dht.readTemperature(); float kelembapan = dht.readHumidity(); if (isnan(suhu) || isnan(kelembapan)) { Serial.println("Gagal baca DHT!"); return; } // ================= CONTROL LOGIC ================= if (isAutoMode) { // Logic: <70% Pump ON, Lamp OFF | >90% Lamp ON, Pump OFF | 70-90% NORMAL (OFF/OFF) if (kelembapan < 70.0) { digitalWrite(RELAY1, RELAY_ON); // Pompa Hidup digitalWrite(RELAY2, RELAY_OFF); // Lampu Mati statusRelay = "KERING / PENYIRAMAN"; } else if (kelembapan > 90.0) { digitalWrite(RELAY1, RELAY_OFF); // Pompa Mati digitalWrite(RELAY2, RELAY_ON); // Lampu Nyala statusRelay = "TERLALU LEMBAB"; } else { digitalWrite(RELAY1, RELAY_OFF); // Pompa Mati digitalWrite(RELAY2, RELAY_OFF); // Lampu Mati statusRelay = "NORMAL"; } } else { // Manual Mode (App Based) digitalWrite(RELAY1, manualPump ? RELAY_ON : RELAY_OFF); digitalWrite(RELAY2, manualLight ? RELAY_ON : RELAY_OFF); statusRelay = "MANUAL"; } // ================= STATUS REPORTING ================= bool currentPump = (digitalRead(RELAY1) == RELAY_ON); bool currentLight = (digitalRead(RELAY2) == RELAY_ON); // Upload logic: Action transition OR Periodic (30 mins) if Normal unsigned long nowMillis = millis(); bool actionDetected = (currentPump != prevPump || currentLight != prevLight); // Interval 30 menit = 1800000 ms bool isPeriodicTime = (nowMillis - lastPeriodicUpload >= 1800000); if (actionDetected) { Serial.println("[Reporting] Deteksi Perubahan Status!"); } if (actionDetected || (statusRelay == "NORMAL" && isPeriodicTime)) { String statusLampuUpload = currentLight ? (isAutoMode ? "MENYALA - OTOMATIS" : "MENYALA - MANUAL") : "MATI"; String statusPompaUpload = currentPump ? (isAutoMode ? "MENYALA - OTOMATIS" : "MENYALA - MANUAL") : "MATI"; Serial.println("[Reporting] Memulai Upload ke Supabase..."); uploadToSupabase(kelembapan, statusRelay, statusLampuUpload, statusPompaUpload); lastPeriodicUpload = nowMillis; } // MQTT Reporting client.publish("esp32/dht/suhu", String(suhu).c_str()); client.publish("esp32/dht/kelembapan", String(kelembapan).c_str()); client.publish("esp32/relay/status", statusRelay.c_str()); client.publish("esp32/statusiqbal/pump", currentPump ? "on" : "off", true); client.publish("esp32/statusiqbal/light", currentLight ? "on" : "off", true); client.publish("esp32/statusiqbal/mode", isAutoMode ? "auto" : "manual", true); // Save states for next comparison prevPump = currentPump; prevLight = currentLight; // Serial Debug Serial.print("Mode: "); Serial.print(isAutoMode ? "AUTO" : "MANUAL"); Serial.print(" | Hum: "); Serial.print(kelembapan); Serial.print("% | Pump: "); Serial.print(currentPump ? "ON" : "OFF"); Serial.print(" | Light: "); Serial.print(currentLight ? "ON" : "OFF"); Serial.print(" | Status: "); Serial.println(statusRelay); } }