#include #include #include #include #include #include "HX711.h" // ===================================================== // WIFI // ===================================================== const char* ssid = "Bemby 1"; const char* password = "BISMILLAH88"; // ===================================================== // MQTT // ===================================================== const char* mqtt_server = "broker.hivemq.com"; const int mqtt_port = 1883; WiFiClient espClient; PubSubClient client(espClient); // ===================================================== // MQTT TOPIC (Sesuai dengan Backend Railway) // ===================================================== const char* topicData = "novil/pengering/data"; const char* topicStatus = "novil/pengering/status"; const char* topicControl = "novil/pengering/control"; const char* topicButton = "novil/pengering/button"; // ===================================================== // LCD // ===================================================== LiquidCrystal_PCF8574 lcd(0x27); // ===================================================== // BUTTON // ===================================================== #define BUTTON_PIN 18 bool lastButtonState = HIGH; // ===================================================== // DHT22 // ===================================================== #define DHTPIN 4 #define DHTTYPE DHT22 DHT dht(DHTPIN, DHTTYPE); // ===================================================== // HX711 // ===================================================== #define DT1 25 #define SCK1 26 #define DT2 33 // Sesuai dengan kode test yang working #define SCK2 32 // Sesuai dengan kode test yang working HX711 scale1; HX711 scale2; // ===================================================== // RELAY // ===================================================== #define RELAY_HEATER1 13 // RELAY1 - Heater 1 #define RELAY_HEATER2 12 // RELAY2 - Heater 2 #define RELAY_FAN 14 // RELAY3 - Fan #define RELAY_EXHAUST 27 // RELAY4 - Exhaust // ===================================================== // KALIBRASI LOADCELL // ===================================================== float calibration_factor1 = 709.0; // Sesuai dengan kode test yang working float calibration_factor2 = 709.0; // Sesuai dengan kode test yang working // ===================================================== // VARIABEL // ===================================================== float berat_awal = 0; float berat_trigger = 0; bool modePengeringan = false; bool pengeringanSelesai = false; bool beratTersimpan = false; unsigned long waktuMulaiBerat = 0; // ===================================================== // FUNCTION // ===================================================== void tampilReady(); void tampilSelesai(); void kirimStatusRelay(float suhu, float berat); void kirimDataMQTT(float suhu, float berat); // ===================================================== // WIFI CONNECT // ===================================================== void setup_wifi() { Serial.println(); Serial.println("Starting WiFi connection..."); // Tampilkan status connecting di LCD lcd.clear(); delay(50); lcd.setCursor(0, 0); lcd.print("Connecting WiFi"); lcd.setCursor(0, 1); lcd.print("Please wait"); Serial.print("Connecting to: "); Serial.println(ssid); WiFi.begin(ssid, password); int attempts = 0; int maxAttempts = 20; // Loop dengan update LCD setiap detik untuk mencegah stuck while (WiFi.status() != WL_CONNECTED && attempts < maxAttempts) { delay(500); Serial.print("."); // Update LCD setiap 2 attempts (1 detik) if (attempts % 2 == 0) { lcd.setCursor(0, 1); lcd.print(" "); // Clear line lcd.setCursor(0, 1); lcd.print("Wait "); lcd.print(attempts / 2); lcd.print("s/"); lcd.print(maxAttempts / 2); lcd.print("s"); } attempts++; } Serial.println(); // New line after dots if (WiFi.status() == WL_CONNECTED) { Serial.println("WiFi Connected"); Serial.print("IP Address : "); Serial.println(WiFi.localIP()); // Tampilkan WiFi connected di LCD lcd.clear(); delay(50); lcd.setCursor(0, 0); lcd.print("WiFi Connected!"); lcd.setCursor(0, 1); lcd.print(WiFi.localIP()); delay(2000); } else { Serial.println("WiFi Connection Failed!"); // Tampilkan error di LCD lcd.clear(); delay(50); lcd.setCursor(0, 0); lcd.print("WiFi Failed!"); lcd.setCursor(0, 1); lcd.print("Check settings"); delay(3000); } } // ===================================================== // 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("MQTT Message ["); Serial.print(topic); Serial.print("] : "); Serial.println(message); if (String(topic) == topicControl) { // ================================================= // HEATER 1 // ================================================= if (message == "HEATER_ON") { digitalWrite(RELAY_HEATER1, LOW); digitalWrite(RELAY_HEATER2, LOW); Serial.println("HEATER 1 & 2 ON"); } else if (message == "HEATER_OFF") { digitalWrite(RELAY_HEATER1, HIGH); digitalWrite(RELAY_HEATER2, HIGH); Serial.println("HEATER 1 & 2 OFF"); } // ================================================= // FAN // ================================================= else if (message == "FAN_ON") { digitalWrite(RELAY_FAN, LOW); Serial.println("FAN ON"); } else if (message == "FAN_OFF") { digitalWrite(RELAY_FAN, HIGH); Serial.println("FAN OFF"); } // ================================================= // LAMP (tidak digunakan, untuk kompatibilitas) // ================================================= else if (message == "LAMP_ON") { Serial.println("LAMP ON (tidak tersedia)"); } else if (message == "LAMP_OFF") { Serial.println("LAMP OFF (tidak tersedia)"); } // ================================================= // EXHAUST // ================================================= else if (message == "EXHAUST_ON") { digitalWrite(RELAY_EXHAUST, LOW); Serial.println("EXHAUST ON"); } else if (message == "EXHAUST_OFF") { digitalWrite(RELAY_EXHAUST, HIGH); Serial.println("EXHAUST OFF"); } // ================================================= // START MQTT (dari Flutter via Backend) // ================================================= else if (message == "START") { if (modePengeringan || pengeringanSelesai) { Serial.println("START DIABAIKAN - Sistem sedang berjalan atau selesai"); client.publish(topicStatus, "TIDAK BISA START SEKARANG"); return; } Serial.println("START MQTT (dari aplikasi)"); // Tare HX711 saat mulai pengeringan dengan timeout Serial.println("Taring scales..."); lcd.clear(); lcd.setCursor(0, 0); lcd.print("Calibrating..."); lcd.setCursor(0, 1); lcd.print("Please wait"); // Cek apakah scale ready sebelum tare bool scale1Ready = scale1.is_ready(); bool scale2Ready = scale2.is_ready(); Serial.print("Scale 1 ready: "); Serial.println(scale1Ready ? "YES" : "NO"); Serial.print("Scale 2 ready: "); Serial.println(scale2Ready ? "YES" : "NO"); if (scale1Ready && scale2Ready) { // Tare dengan tracking waktu unsigned long tareStart = millis(); scale1.tare(); unsigned long tare1Time = millis() - tareStart; Serial.print("Scale 1 tared in "); Serial.print(tare1Time); Serial.println("ms"); delay(100); tareStart = millis(); scale2.tare(); unsigned long tare2Time = millis() - tareStart; Serial.print("Scale 2 tared in "); Serial.print(tare2Time); Serial.println("ms"); delay(100); Serial.println("Scales tare complete"); } else { Serial.println("Scale(s) NOT READY - skipping tare"); if (!scale1Ready) Serial.println("Scale 1 not responding"); if (!scale2Ready) Serial.println("Scale 2 not responding"); client.publish(topicStatus, "WARNING: Scale not ready"); } modePengeringan = true; pengeringanSelesai = false; beratTersimpan = false; berat_awal = 0; berat_trigger = 0; waktuMulaiBerat = 0; lcd.clear(); client.publish(topicStatus, "PENGERINGAN DIMULAI"); } // ================================================= // RESET MQTT (dari Flutter via Backend) // ================================================= else if (message == "RESET") { Serial.println("RESET MQTT (dari aplikasi)"); modePengeringan = false; pengeringanSelesai = false; beratTersimpan = false; berat_awal = 0; berat_trigger = 0; waktuMulaiBerat = 0; // Matikan semua relay digitalWrite(RELAY_HEATER1, HIGH); digitalWrite(RELAY_HEATER2, HIGH); digitalWrite(RELAY_FAN, HIGH); digitalWrite(RELAY_EXHAUST, HIGH); lcd.clear(); tampilReady(); client.publish(topicStatus, "PENGERINGAN SIAP"); } // ================================================= // TARE COMMAND (Kalibrasi ulang load cell ke 0) // ================================================= else if (message == "TARE") { Serial.println("TARE COMMAND received"); lcd.clear(); lcd.setCursor(0, 0); lcd.print("Taring..."); lcd.setCursor(0, 1); lcd.print("Remove all load"); delay(2000); bool scale1Ready = scale1.wait_ready_timeout(1000); bool scale2Ready = scale2.wait_ready_timeout(1000); if (scale1Ready && scale2Ready) { scale1.tare(10); delay(100); scale2.tare(10); delay(100); Serial.println("Manual tare successful"); client.publish(topicStatus, "TARE COMPLETED"); lcd.setCursor(0, 1); lcd.print("Done! "); delay(1000); } else { Serial.println("TARE FAILED - scales not ready"); client.publish(topicStatus, "TARE FAILED"); lcd.setCursor(0, 1); lcd.print("Failed! "); delay(2000); } lcd.clear(); tampilReady(); } } } // ===================================================== // MQTT CONNECT // ===================================================== void reconnect() { int attempts = 0; int maxAttempts = 3; while (!client.connected() && attempts < maxAttempts) { Serial.print("Connecting MQTT (attempt "); Serial.print(attempts + 1); Serial.print("/"); Serial.print(maxAttempts); Serial.println(")..."); // Tampilkan status di LCD dengan update setiap attempt if (modePengeringan == false && pengeringanSelesai == false) { lcd.clear(); lcd.setCursor(0, 0); lcd.print("Connecting MQTT"); lcd.setCursor(0, 1); lcd.print("Attempt "); lcd.print(attempts + 1); lcd.print("/"); lcd.print(maxAttempts); } // Generate unique client ID String clientId = "ESP32_PENGERING_"; clientId += String(random(0xffff), HEX); if (client.connect(clientId.c_str())) { Serial.println("MQTT Connected"); Serial.print("Client ID: "); Serial.println(clientId); // Subscribe ke topic control client.subscribe(topicControl); Serial.print("Subscribed to: "); Serial.println(topicControl); // Publish status connected client.publish(topicStatus, "ESP32 CONNECTED"); // Tampilkan MQTT connected di LCD if (modePengeringan == false && pengeringanSelesai == false) { lcd.clear(); lcd.setCursor(0, 0); lcd.print("MQTT Connected!"); lcd.setCursor(0, 1); lcd.print("System Ready"); delay(2000); } return; // Keluar dari fungsi setelah berhasil connect } else { Serial.print("MQTT Failed, rc="); Serial.print(client.state()); Serial.println(" retrying..."); // Update LCD menunjukkan gagal if (modePengeringan == false && pengeringanSelesai == false) { lcd.setCursor(0, 1); lcd.print("Failed! Retry..."); } attempts++; if (attempts < maxAttempts) { delay(2000); // Tunggu sebelum retry } } } // Jika gagal setelah semua attempts if (!client.connected()) { Serial.println("MQTT Connection failed after all attempts"); if (modePengeringan == false && pengeringanSelesai == false) { lcd.clear(); lcd.setCursor(0, 0); lcd.print("MQTT Failed!"); lcd.setCursor(0, 1); lcd.print("Check network"); delay(3000); } } } // ===================================================== // SETUP // ===================================================== void setup() { Serial.begin(115200); delay(1000); // Beri waktu serial monitor untuk ready Serial.println(); Serial.println("================================="); Serial.println("ESP32 Pengering Ikan - Railway"); Serial.println("================================="); // Setup I2C untuk LCD Serial.println("Initializing I2C..."); Wire.begin(21, 22); delay(100); // Setup LCD dengan error handling Serial.println("Initializing LCD..."); lcd.begin(16, 2); delay(100); lcd.setBacklight(255); delay(100); lcd.clear(); delay(100); // Test LCD - tampilkan pesan booting lcd.setCursor(0, 0); lcd.print("ESP32 Pengering"); lcd.setCursor(0, 1); lcd.print("Initializing..."); delay(2000); Serial.println("LCD initialized successfully"); // Setup WiFi setup_wifi(); // Setup MQTT client.setServer(mqtt_server, mqtt_port); client.setCallback(callback); client.setKeepAlive(60); // Setup Button pinMode(BUTTON_PIN, INPUT_PULLUP); // Setup DHT22 Serial.println("Initializing DHT22..."); dht.begin(); delay(100); // Setup HX711 Serial.println("Initializing HX711..."); // Inisialisasi HX711 (style kode test yang working) scale1.begin(DT1, SCK1); Serial.println("HX711 #1 begin OK"); scale2.begin(DT2, SCK2); Serial.println("HX711 #2 begin OK"); scale1.set_scale(calibration_factor1); scale2.set_scale(calibration_factor2); Serial.println("Calibration factor applied"); // Update LCD lcd.clear(); lcd.setCursor(0, 0); lcd.print("Checking scales"); lcd.setCursor(0, 1); lcd.print("Please wait..."); // Menunggu HX711 siap dengan timeout 10 detik (style kode test) Serial.println("Menunggu HX711 siap..."); unsigned long startTime = millis(); bool scale1Ready = false; bool scale2Ready = false; while ((!scale1.is_ready() || !scale2.is_ready()) && millis() - startTime < 10000) { scale1Ready = scale1.is_ready(); scale2Ready = scale2.is_ready(); Serial.print("HX1: "); Serial.print(scale1Ready ? "1" : "0"); Serial.print(" | HX2: "); Serial.println(scale2Ready ? "1" : "0"); // Update LCD dengan status lcd.setCursor(0, 1); lcd.print("HX1:"); lcd.print(scale1Ready ? "OK" : ".."); lcd.print(" HX2:"); lcd.print(scale2Ready ? "OK" : ".."); lcd.print(" "); delay(500); // Jika keduanya sudah ready, keluar dari loop if (scale1Ready && scale2Ready) break; } Serial.println(); // Check final status scale1Ready = scale1.is_ready(); scale2Ready = scale2.is_ready(); if (!scale1Ready) { Serial.println("ERROR: HX711 #1 tidak terdeteksi!"); } else { Serial.println("HX711 #1 siap"); } if (!scale2Ready) { Serial.println("ERROR: HX711 #2 tidak terdeteksi!"); } else { Serial.println("HX711 #2 siap"); } // Tare hanya jika siap (style kode test) if (scale1Ready) { Serial.println("Tare HX711 #1..."); lcd.setCursor(0, 1); lcd.print("Taring scale 1.."); scale1.tare(); delay(100); } if (scale2Ready) { Serial.println("Tare HX711 #2..."); lcd.setCursor(0, 1); lcd.print("Taring scale 2.."); scale2.tare(); delay(100); } if (scale1Ready || scale2Ready) { lcd.setCursor(0, 1); lcd.print("Tare complete! "); delay(1000); } else { lcd.clear(); lcd.setCursor(0, 0); lcd.print("Scale Warning!"); lcd.setCursor(0, 1); lcd.print("Check wiring"); delay(3000); } Serial.println("HX711 Setup selesai"); // Setup Relay (Active LOW) pinMode(RELAY_HEATER1, OUTPUT); pinMode(RELAY_HEATER2, OUTPUT); pinMode(RELAY_FAN, OUTPUT); pinMode(RELAY_EXHAUST, OUTPUT); // Matikan semua relay digitalWrite(RELAY_HEATER1, HIGH); digitalWrite(RELAY_HEATER2, HIGH); digitalWrite(RELAY_FAN, HIGH); digitalWrite(RELAY_EXHAUST, HIGH); // Tampilkan ready lcd.clear(); delay(100); tampilReady(); delay(500); Serial.println("Setup Complete!"); Serial.println("================================="); } // ===================================================== // LOOP // ===================================================== void loop() { // ===================================================== // MQTT Connection Check // ===================================================== if (!client.connected()) { reconnect(); } client.loop(); // ===================================================== // BUTTON // ===================================================== bool buttonState = digitalRead(BUTTON_PIN); if (lastButtonState == HIGH && buttonState == LOW) { Serial.println("BUTTON PRESSED"); client.publish(topicButton, "BUTTON PRESSED"); // ================================================= // STATE: READY TO DRY // Tekan button → mulai pengeringan // ================================================= if (!modePengeringan && !pengeringanSelesai) { Serial.println("START PENGERINGAN"); // Tare HX711 saat mulai pengeringan (style kode test) Serial.println("Taring scales..."); lcd.clear(); lcd.setCursor(0, 0); lcd.print("Zeroing..."); lcd.setCursor(0, 1); lcd.print("Remove all load"); delay(2000); // Kasih waktu user untuk pastikan rak kosong // Cek apakah scale ready sebelum tare bool scale1Ready = scale1.is_ready(); bool scale2Ready = scale2.is_ready(); Serial.print("Scale 1 ready: "); Serial.println(scale1Ready ? "YES" : "NO"); Serial.print("Scale 2 ready: "); Serial.println(scale2Ready ? "YES" : "NO"); bool tareSuccess = false; if (scale1Ready && scale2Ready) { lcd.setCursor(0, 1); lcd.print("Calibrating... "); scale1.tare(); Serial.println("Scale 1 tared"); delay(100); scale2.tare(); Serial.println("Scale 2 tared"); delay(100); lcd.setCursor(0, 1); lcd.print("Done! Put fish "); delay(1000); tareSuccess = true; } else { if (!scale1Ready) Serial.println("Scale 1 not ready!"); if (!scale2Ready) Serial.println("Scale 2 not ready!"); } if (!tareSuccess) { Serial.println("WARNING: Tare FAILED"); lcd.setCursor(0, 1); lcd.print("Scale Error! "); delay(2000); // Batal start jika scale tidak ready lcd.clear(); lcd.setCursor(0, 0); lcd.print("Cannot Start!"); lcd.setCursor(0, 1); lcd.print("Check scales"); delay(3000); lcd.clear(); tampilReady(); lastButtonState = buttonState; return; // Keluar dari loop, jangan lanjut start } Serial.println("Scales tare complete"); // Publish ke topicButton dan topicControl client.publish(topicButton, "START_BUTTON"); client.publish(topicControl, "START"); modePengeringan = true; pengeringanSelesai = false; beratTersimpan = false; berat_awal = 0; berat_trigger = 0; waktuMulaiBerat = 0; lcd.clear(); client.publish(topicStatus, "PENGERINGAN DIMULAI"); } delay(300); } lastButtonState = buttonState; // ===================================================== // MODE READY — belum ada button press // ===================================================== if (!modePengeringan) { tampilReady(); delay(500); return; } // ===================================================== // MODE PENGERINGAN // ===================================================== // ===================================================== // DHT22 // ===================================================== float suhu = dht.readTemperature(); if (isnan(suhu)) { lcd.clear(); lcd.setCursor(0, 0); lcd.print("DHT ERROR"); Serial.println("DHT ERROR"); client.publish(topicStatus, "DHT ERROR"); delay(1000); return; } // ===================================================== // HX711 // ===================================================== float berat1 = 0; float berat2 = 0; bool scale1Valid = false; bool scale2Valid = false; // Cek dan baca scale 1 (style kode test) if (scale1.is_ready()) { berat1 = scale1.get_units(5); scale1Valid = true; } else { Serial.println("WARNING: Scale 1 not ready!"); } // Cek dan baca scale 2 (style kode test) if (scale2.is_ready()) { berat2 = scale2.get_units(5); scale2Valid = true; } else { Serial.println("WARNING: Scale 2 not ready!"); } // Jika kedua scale tidak ready, skip loop ini if (!scale1Valid && !scale2Valid) { Serial.println("ERROR: Kedua load cell tidak ready!"); lcd.clear(); lcd.setCursor(0, 0); lcd.print("Scale Error!"); lcd.setCursor(0, 1); lcd.print("Check wiring"); client.publish(topicStatus, "LOAD CELL ERROR"); delay(2000); return; } // ===================================================== // ANTI NOISE // ===================================================== if (abs(berat1) < 0.5) berat1 = 0; if (abs(berat2) < 0.5) berat2 = 0; // ===================================================== // TOTAL BERAT (Jumlah dari kedua load cell) // ===================================================== float berat = berat1 + berat2; // ===================================================== // DETEKSI IKAN // ===================================================== if (!beratTersimpan && berat > 50 && waktuMulaiBerat == 0) { waktuMulaiBerat = millis(); Serial.println("IKAN TERDETEKSI"); client.publish(topicStatus, "SCAN BERAT..."); } // ===================================================== // SIMPAN BERAT AWAL // ===================================================== if (!beratTersimpan && waktuMulaiBerat > 0 && millis() - waktuMulaiBerat >= 5000) { berat_awal = berat; float berat_daging = berat_awal * 0.3; float berat_air_awal = berat_awal * 0.7; float sisa_air = berat_air_awal * 0.4; berat_trigger = berat_daging + sisa_air; beratTersimpan = true; Serial.println("=================================="); Serial.println("BERAT AWAL TERSIMPAN"); Serial.print("Berat Awal : "); Serial.print(berat_awal, 2); Serial.println(" g"); Serial.print("Berat Daging : "); Serial.print(berat_daging, 2); Serial.println(" g (30%)"); Serial.print("Berat Air Awal : "); Serial.print(berat_air_awal, 2); Serial.println(" g (70%)"); Serial.print("Sisa Air Target : "); Serial.print(sisa_air, 2); Serial.println(" g (40% dari air)"); Serial.print("Target Akhir : "); Serial.print(berat_trigger, 2); Serial.println(" g"); Serial.println("=================================="); String msg = "PENGERINGAN BERJALAN - Awal:"; msg += String(berat_awal, 0); msg += "g Target:"; msg += String(berat_trigger, 0); msg += "g"; client.publish(topicStatus, msg.c_str()); } // ===================================================== // TARGET TERCAPAI → SELESAI // ===================================================== if (beratTersimpan && berat <= berat_trigger) { static unsigned long targetReachedTime = 0; static bool targetReachedFlag = false; // Pertama kali target tercapai if (!targetReachedFlag) { targetReachedTime = millis(); targetReachedFlag = true; Serial.println("=================================="); Serial.println("TARGET TERCAPAI - Verifikasi 10 detik..."); Serial.print("Berat Sekarang: "); Serial.print(berat, 2); Serial.println(" g"); Serial.print("Target: "); Serial.print(berat_trigger, 2); Serial.println(" g"); Serial.println("=================================="); client.publish(topicStatus, "Verifikasi target..."); } // Tunggu 10 detik untuk memastikan berat stabil di bawah target if (millis() - targetReachedTime >= 10000) { // Setelah 10 detik masih di bawah target, baru selesai modePengeringan = false; pengeringanSelesai = true; targetReachedFlag = false; // Reset flag // Matikan heater 1, 2 dan fan, nyalakan exhaust untuk cooling digitalWrite(RELAY_HEATER1, HIGH); digitalWrite(RELAY_HEATER2, HIGH); digitalWrite(RELAY_FAN, HIGH); digitalWrite(RELAY_EXHAUST, LOW); // EXHAUST ON Serial.println("=================================="); Serial.println("PENGERINGAN SELESAI"); Serial.print("Berat Awal : "); Serial.print(berat_awal, 2); Serial.println(" g"); Serial.print("Berat Akhir : "); Serial.print(berat, 2); Serial.println(" g"); Serial.print("Target : "); Serial.print(berat_trigger, 2); Serial.println(" g"); Serial.print("Susut : "); Serial.print(berat_awal - berat, 2); Serial.println(" g"); Serial.print("Persen Susut: "); Serial.print(((berat_awal - berat) / berat_awal) * 100, 1); Serial.println(" %"); Serial.println("=================================="); client.publish(topicStatus, "PENGERINGAN SELESAI"); // Kirim data dengan status COMPLETED untuk trigger notifikasi kirimStatusRelay(suhu, berat); // Tampilkan selesai selama 5 detik lcd.clear(); lcd.setCursor(0, 0); lcd.print("PENGERINGAN"); lcd.setCursor(0, 1); lcd.print("SELESAI"); delay(5000); // Exhaust jalan 20 detik untuk buang panas lcd.clear(); lcd.setCursor(0, 0); lcd.print("Cooling down..."); lcd.setCursor(0, 1); lcd.print("20 seconds"); // Countdown 20 detik for (int i = 20; i > 0; i--) { lcd.setCursor(0, 1); lcd.print(" "); lcd.setCursor(0, 1); lcd.print(String(i) + " seconds left"); delay(1000); // Tetap kirim MQTT status relay saat cooling kirimStatusRelay(suhu, berat); client.loop(); } // Matikan exhaust dan kembali ke ready digitalWrite(RELAY_EXHAUST, HIGH); // EXHAUST OFF // Reset variabel pengeringanSelesai = false; beratTersimpan = false; berat_awal = 0; berat_trigger = 0; waktuMulaiBerat = 0; lcd.clear(); tampilReady(); client.publish(topicStatus, "PENGERINGAN SIAP"); // Kirim status relay setelah reset kirimStatusRelay(suhu, berat); Serial.println("Sistem kembali ke READY"); delay(500); return; } } else { // Berat naik lagi atau di atas target, reset flag verifikasi static bool targetReachedFlag = false; targetReachedFlag = false; } // ===================================================== // RELAY CONTROL berdasarkan status dan suhu // Relay HANYA ON setelah berat_awal & target tersimpan // ===================================================== if (beratTersimpan) { // Debug: Print status berat setiap 5 detik static unsigned long lastDebug = 0; if (millis() - lastDebug > 5000) { Serial.println("--- STATUS PENGERINGAN ---"); Serial.print("Berat Sekarang : "); Serial.print(berat, 2); Serial.println(" g"); Serial.print("Target : "); Serial.print(berat_trigger, 2); Serial.println(" g"); Serial.print("Selisih : "); Serial.print(berat - berat_trigger, 2); Serial.println(" g"); Serial.print("Kondisi : "); if (berat <= berat_trigger) { Serial.println("TARGET TERCAPAI ✓"); } else { Serial.println("MASIH MENGERINGKAN..."); } Serial.println("-------------------------"); lastDebug = millis(); } // Berat sudah tersimpan, mulai pengeringan // FAN selalu ON saat pengeringan digitalWrite(RELAY_FAN, LOW); // FAN ON // Kontrol HEATER dan EXHAUST berdasarkan suhu if (suhu >= 55.0) { // Suhu sudah tinggi (≥55°C) digitalWrite(RELAY_HEATER1, HIGH); // HEATER 1 OFF digitalWrite(RELAY_HEATER2, HIGH); // HEATER 2 OFF digitalWrite(RELAY_EXHAUST, LOW); // EXHAUST ON untuk buang panas // Serial.println("Suhu >= 55C: HEATER OFF, EXHAUST ON"); } else { // Suhu masih rendah (<55°C) digitalWrite(RELAY_HEATER1, LOW); // HEATER 1 ON digitalWrite(RELAY_HEATER2, LOW); // HEATER 2 ON digitalWrite(RELAY_EXHAUST, HIGH); // EXHAUST OFF // Serial.println("Suhu < 55C: HEATER ON, EXHAUST OFF"); } } else { // Masih scanning berat, relay tetap OFF digitalWrite(RELAY_HEATER1, HIGH); // HEATER 1 OFF digitalWrite(RELAY_HEATER2, HIGH); // HEATER 2 OFF digitalWrite(RELAY_FAN, HIGH); // FAN OFF digitalWrite(RELAY_EXHAUST, HIGH); // EXHAUST OFF } // ===================================================== // LCD BARIS 1 // ===================================================== lcd.setCursor(0, 0); lcd.print(" "); lcd.setCursor(0, 0); lcd.print("S:"); lcd.print(suhu, 1); lcd.print("C"); lcd.setCursor(9, 0); lcd.print("B:"); lcd.print(berat, 0); lcd.print("g"); // ===================================================== // LCD BARIS 2 // ===================================================== lcd.setCursor(0, 1); lcd.print(" "); lcd.setCursor(0, 1); if (!beratTersimpan) { lcd.print("Scan Berat..."); } else { lcd.print("T:"); lcd.print(berat_trigger, 0); lcd.print("g"); } // ===================================================== // MQTT DATA (kirim ke Backend Railway) // ===================================================== kirimStatusRelay(suhu, berat); delay(1000); } // ===================================================== // MQTT DATA dengan Status Relay (Format JSON untuk Backend Railway) // ===================================================== void kirimStatusRelay(float suhu, float berat) { // Baca status relay saat ini bool heater1_on = (digitalRead(RELAY_HEATER1) == LOW); bool heater2_on = (digitalRead(RELAY_HEATER2) == LOW); bool fan_on = (digitalRead(RELAY_FAN) == LOW); bool exhaust_on = (digitalRead(RELAY_EXHAUST) == LOW); // Tentukan status string String statusString = "UNKNOWN"; if (pengeringanSelesai) { statusString = "SELESAI"; } else if (modePengeringan && beratTersimpan) { statusString = "BERJALAN"; } else if (modePengeringan && !beratTersimpan) { statusString = "SCANNING"; } else { statusString = "READY"; } // Format JSON dengan status relay DAN status String payload = "{"; payload += "\"suhu\":" + String(suhu, 1) + ","; payload += "\"berat\":" + String(berat, 0) + ","; payload += "\"target\":" + String(berat_trigger, 0) + ","; payload += "\"relay1\":" + String(heater1_on ? "true" : "false") + ","; payload += "\"relay2\":" + String(heater2_on ? "true" : "false") + ","; payload += "\"relay3\":" + String(fan_on ? "true" : "false") + ","; payload += "\"relay4\":" + String(exhaust_on ? "true" : "false") + ","; payload += "\"status\":\"" + statusString + "\""; payload += "}"; // Publish ke topic data bool success = client.publish(topicData, payload.c_str()); if (success) { Serial.print("MQTT Data Sent: "); Serial.println(payload); } else { Serial.println("MQTT Data Failed to Send"); } } // ===================================================== // MQTT DATA (Backward compatibility - deprecated) // ===================================================== void kirimDataMQTT(float suhu, float berat) { kirimStatusRelay(suhu, berat); } // ===================================================== // READY LCD // ===================================================== void tampilReady() { lcd.setCursor(0, 0); lcd.print(" "); lcd.setCursor(0, 0); lcd.print("Ready to Dry"); lcd.setCursor(0, 1); lcd.print(" "); lcd.setCursor(0, 1); lcd.print("Press Button"); } // ===================================================== // SELESAI LCD // ===================================================== void tampilSelesai() { lcd.setCursor(0, 0); lcd.print(" "); lcd.setCursor(0, 0); lcd.print("PENGERINGAN"); lcd.setCursor(0, 1); lcd.print(" "); lcd.setCursor(0, 1); lcd.print("SELESAI"); }