#include #include #include #include // Pin Definitions #define DHT_PIN 2 #define DHT_TYPE DHT22 // Relay pins #define RELAY_POMPA_A 3 #define RELAY_POMPA_B 4 #define RELAY_POMPA_PH_UP 5 #define RELAY_POMPA_PH_DOWN 6 #define RELAY_GROW_LIGHT 7 #define RELAY_AERATOR 8 #define RELAY_KIPAS 9 #define RELAY_FOGGER 10 // Sensor pins #define PH_SENSOR_PIN A1 #define TDS_SENSOR_PIN A0 // *** TAMBAHAN UNTUK KOMUNIKASI ESP32 *** #define ESP32_TX_PIN 1 // Pin TX Arduino (D1) -> RX ESP32 (GPIO 16) #define ESP32_RX_PIN 0 // Pin RX Arduino (tidak digunakan dalam kasus ini) // Gunakan Serial hardware untuk komunikasi ke ESP32 // SoftwareSerial esp32Serial(ESP32_RX_PIN, ESP32_TX_PIN); // Alternatif jika butuh software serial // Constants moved to PROGMEM to save RAM const float VOLTAGE_REF = 5.0; // pH Calibration constants (dari kalibrasi 2 titik) const float PH_SLOPE = -4.0787; const float PH_OFFSET = 16.764; // Timing constants const unsigned long SENSOR_INTERVAL = 3000; const unsigned long DHT_INTERVAL = 5000; const unsigned long DISPLAY_INTERVAL = 1000; const unsigned long DEBUG_INTERVAL = 5000; const unsigned long ESP32_SEND_INTERVAL = 2000; // *** TAMBAHAN: Interval kirim data ke ESP32 *** const unsigned long PUMP_DURATION = 5000; const unsigned long GROW_LIGHT_ON_TIME = 43200000UL; const unsigned long GROW_LIGHT_OFF_TIME = 43200000UL; // Initialize components DHT dht(DHT_PIN, DHT_TYPE); LiquidCrystal_I2C lcd(0x27, 16, 2); // Sensor variables float humidity = 0; float temperature = 0; float phValue = 0; float tdsValue = 0; int phRawADC = 0; int tdsRawADC = 0; // Timing variables unsigned long lastSensorRead = 0; unsigned long lastDHTRead = 0; unsigned long lastDisplayUpdate = 0; unsigned long lastDebugOutput = 0; unsigned long lastDHTStatus = 0; unsigned long lastESP32Send = 0; // *** TAMBAHAN: Timer untuk kirim data *** unsigned long pumpStartTime = 0; unsigned long growLightStartTime = 0; // Control flags struct { bool pumpPhUpRunning : 1; bool pumpPhDownRunning : 1; bool growLightState : 1; bool aeratorState : 1; bool debugMode : 1; bool dhtError : 1; bool phError : 1; bool tdsError : 1; bool esp32CommError : 1; // *** TAMBAHAN: Flag error komunikasi ESP32 *** } flags = {false, false, false, true, true, false, false, false, false}; // DHT22 monitoring variables int dhtErrorCount = 0; int dhtSuccessCount = 0; void setup() { Serial.begin(9600); // Untuk komunikasi dengan ESP32 Serial.println(F("=== HYDROPONIC SYSTEM STARTUP ===")); // Initialize LCD Wire.begin(); lcd.init(); lcd.backlight(); lcd.setCursor(0, 0); lcd.print(F("Init LCD...")); delay(1000); // Initialize relay pins pinMode(RELAY_POMPA_A, OUTPUT); pinMode(RELAY_POMPA_B, OUTPUT); pinMode(RELAY_POMPA_PH_UP, OUTPUT); pinMode(RELAY_POMPA_PH_DOWN, OUTPUT); pinMode(RELAY_GROW_LIGHT, OUTPUT); pinMode(RELAY_AERATOR, OUTPUT); pinMode(RELAY_KIPAS, OUTPUT); pinMode(RELAY_FOGGER, OUTPUT); // Set relay initial states (HIGH = OFF untuk relay aktif LOW) digitalWrite(RELAY_POMPA_A, HIGH); digitalWrite(RELAY_POMPA_B, HIGH); digitalWrite(RELAY_POMPA_PH_UP, HIGH); digitalWrite(RELAY_POMPA_PH_DOWN, HIGH); digitalWrite(RELAY_GROW_LIGHT, HIGH); digitalWrite(RELAY_AERATOR, HIGH); digitalWrite(RELAY_KIPAS, HIGH); digitalWrite(RELAY_FOGGER, HIGH); lcd.clear(); lcd.print(F("Init Relays...")); delay(1000); // Initialize DHT sensor lcd.clear(); lcd.print(F("Init DHT22...")); dht.begin(); delay(2000); // Test DHT22 bool dhtOK = false; for (int attempt = 1; attempt <= 5; attempt++) { float testTemp = dht.readTemperature(); float testHum = dht.readHumidity(); if (!isnan(testTemp) && !isnan(testHum) && testTemp > -40 && testTemp < 80 && testHum > 0 && testHum < 100) { dhtOK = true; temperature = testTemp; humidity = testHum; break; } delay(1000); } if (!dhtOK) { flags.dhtError = true; lcd.clear(); lcd.setCursor(0, 0); lcd.print(F("DHT22 ERROR!")); lcd.setCursor(0, 1); lcd.print(F("Check wiring")); delay(3000); } else { flags.dhtError = false; lcd.clear(); lcd.setCursor(0, 0); lcd.print(F("DHT22 OK!")); delay(2000); } // Nyalakan aerator if (!flags.dhtError) { digitalWrite(RELAY_AERATOR, LOW); flags.aeratorState = true; } // Initialize timers growLightStartTime = millis(); lastSensorRead = millis(); lastDHTRead = millis(); lastDisplayUpdate = millis(); lastDebugOutput = millis(); lastDHTStatus = millis(); lastESP32Send = millis(); // *** TAMBAHAN *** lcd.clear(); lcd.setCursor(0, 0); lcd.print(F("System Ready!")); delay(2000); lcd.clear(); // *** TAMBAHAN: Test komunikasi ESP32 *** Serial.println(F("Testing ESP32 communication...")); delay(1000); } void loop() { unsigned long currentTime = millis(); // Read sensors if (currentTime - lastSensorRead >= SENSOR_INTERVAL) { readOtherSensors(); lastSensorRead = currentTime; } if (currentTime - lastDHTRead >= DHT_INTERVAL) { readDHTSensor(); lastDHTRead = currentTime; } // *** TAMBAHAN: Kirim data ke ESP32 *** if (currentTime - lastESP32Send >= ESP32_SEND_INTERVAL) { sendDataToESP32(); lastESP32Send = currentTime; } // Control systems controlHumidity(); controlPH(); controlTDS(); controlGrowLight(); controlAerator(); controlPumps(); // Update display if (currentTime - lastDisplayUpdate >= DISPLAY_INTERVAL) { updateDisplay(); lastDisplayUpdate = currentTime; } // Debug output (comment out jika tidak perlu untuk mengurangi interference) // if (flags.debugMode && (currentTime - lastDebugOutput >= DEBUG_INTERVAL)) { // debugOutput(); // lastDebugOutput = currentTime; // } // Monitor DHT22 health monitorDHT(); delay(50); } // *** FUNGSI BARU: Kirim data ke ESP32 *** void sendDataToESP32() { // Format: temperature,humidity,pH,TDS // Pastikan data valid sebelum dikirim float tempToSend = flags.dhtError ? -999 : temperature; float humToSend = flags.dhtError ? -999 : humidity; float phToSend = flags.phError ? -999 : phValue; float tdsToSend = flags.tdsError ? -999 : tdsValue; // Kirim data dengan format CSV Serial.print(tempToSend, 2); Serial.print(","); Serial.print(humToSend, 2); Serial.print(","); Serial.print(phToSend, 2); Serial.print(","); Serial.print(tdsToSend, 0); Serial.println(); // Newline sebagai delimiter // Optional: Indikator di LCD bahwa data terkirim // lcd.setCursor(15, 0); // lcd.print(">"); } // Fungsi baca DHT22 yang diperbaiki void readDHTSensor() { bool aeratorWasOn = (digitalRead(RELAY_AERATOR) == LOW); bool kipasWasOn = (digitalRead(RELAY_KIPAS) == LOW); if (aeratorWasOn) { digitalWrite(RELAY_AERATOR, HIGH); } if (kipasWasOn) { digitalWrite(RELAY_KIPAS, HIGH); } delay(100); float h = NAN, t = NAN; int retries = 0; const int maxRetries = 3; while (retries < maxRetries) { h = dht.readHumidity(); t = dht.readTemperature(); if (!isnan(h) && !isnan(t) && t > -40 && t < 80 && h > 0 && h < 100) { break; } retries++; if (retries < maxRetries) { delay(500); } } if (aeratorWasOn) { digitalWrite(RELAY_AERATOR, LOW); } if (kipasWasOn) { digitalWrite(RELAY_KIPAS, LOW); } if (retries >= maxRetries || isnan(h) || isnan(t)) { flags.dhtError = true; dhtErrorCount++; } else { flags.dhtError = false; humidity = h; temperature = t; dhtSuccessCount++; dhtErrorCount = 0; } } void readOtherSensors() { phValue = readPH(); flags.phError = (phValue < 0 || phValue > 14); tdsValue = readTDS(); flags.tdsError = (tdsValue < 0); // DEBUG: Tampilkan nilai TDS ke Serial Monitor Serial.print(F("[DEBUG] TDS Value: ")); Serial.print(tdsValue); Serial.println(F(" ppm")); } float getAverageVoltage(int pin, int samples = 20) { long total = 0; for (int i = 0; i < samples; i++) { total += analogRead(pin); delay(5); } float avgADC = total / (float)samples; float voltage = avgADC * (5.0 / 1023.0); return voltage; } float readPH() { float voltage = getAverageVoltage(PH_SENSOR_PIN, 20); if (voltage < 0.1 || voltage > 4.9) { return -1; } float esp32_equivalent_voltage = voltage * (3.3/5.0) * (4095.0/1023.0); float ph = PH_SLOPE * esp32_equivalent_voltage + PH_OFFSET; if (ph < 0) ph = 0; if (ph > 14) ph = 14; return ph; } float readTDS() { int buffer_arr[10]; for (int i = 0; i < 10; i++) { buffer_arr[i] = analogRead(TDS_SENSOR_PIN); delay(30); } // Urutkan nilai buffer for (int i = 0; i < 9; i++) { for (int j = i + 1; j < 10; j++) { if (buffer_arr[i] > buffer_arr[j]) { int temp = buffer_arr[i]; buffer_arr[i] = buffer_arr[j]; buffer_arr[j] = temp; } } } // Hitung rata-rata dari nilai tengah (tanpa outlier) int avgval = 0; for (int i = 2; i < 8; i++) { avgval += buffer_arr[i]; } float voltage = (float)avgval * 5.0 / 1023.0 / 6.0; // Konversi tegangan ke TDS dengan rumus kalibrasi dan faktor float calibrationFactor = 0.71; float tds = (133.42 * pow(voltage, 3) - 255.86 * pow(voltage, 2) + 857.39 * voltage) * 0.5; tds *= calibrationFactor; if (tds < 0 || tds > 2000) { return -1; } return tds; } void controlHumidity() { if (flags.dhtError) { digitalWrite(RELAY_FOGGER, HIGH); digitalWrite(RELAY_KIPAS, HIGH); return; } if (humidity < 85.0) { digitalWrite(RELAY_FOGGER, LOW); digitalWrite(RELAY_KIPAS, HIGH); } else if (humidity > 95.0) { digitalWrite(RELAY_FOGGER, HIGH); digitalWrite(RELAY_KIPAS, LOW); } else { digitalWrite(RELAY_FOGGER, HIGH); digitalWrite(RELAY_KIPAS, HIGH); } } void controlPH() { if (flags.phError) return; if (phValue > 6.5 && !flags.pumpPhDownRunning && !flags.pumpPhUpRunning) { digitalWrite(RELAY_POMPA_PH_DOWN, LOW); flags.pumpPhDownRunning = true; pumpStartTime = millis(); } else if (phValue < 5.0 && !flags.pumpPhUpRunning && !flags.pumpPhDownRunning) { digitalWrite(RELAY_POMPA_PH_UP, LOW); flags.pumpPhUpRunning = true; pumpStartTime = millis(); } } void controlTDS() { if (flags.tdsError) { digitalWrite(RELAY_POMPA_A, HIGH); digitalWrite(RELAY_POMPA_B, HIGH); return; } if (tdsValue < 1000.0) { digitalWrite(RELAY_POMPA_A, LOW); digitalWrite(RELAY_POMPA_B, LOW); } else { digitalWrite(RELAY_POMPA_A, HIGH); digitalWrite(RELAY_POMPA_B, HIGH); } } void controlGrowLight() { unsigned long currentTime = millis(); unsigned long elapsedTime = currentTime - growLightStartTime; if (!flags.growLightState && elapsedTime >= GROW_LIGHT_OFF_TIME) { digitalWrite(RELAY_GROW_LIGHT, LOW); flags.growLightState = true; growLightStartTime = currentTime; } else if (flags.growLightState && elapsedTime >= GROW_LIGHT_ON_TIME) { digitalWrite(RELAY_GROW_LIGHT, HIGH); flags.growLightState = false; growLightStartTime = currentTime; } } void controlAerator() { if (flags.aeratorState && !flags.dhtError) { digitalWrite(RELAY_AERATOR, LOW); } else { digitalWrite(RELAY_AERATOR, HIGH); } } void controlPumps() { unsigned long currentTime = millis(); if (flags.pumpPhUpRunning && (currentTime - pumpStartTime >= PUMP_DURATION)) { digitalWrite(RELAY_POMPA_PH_UP, HIGH); flags.pumpPhUpRunning = false; } if (flags.pumpPhDownRunning && (currentTime - pumpStartTime >= PUMP_DURATION)) { digitalWrite(RELAY_POMPA_PH_DOWN, HIGH); flags.pumpPhDownRunning = false; } } void updateDisplay() { lcd.clear(); lcd.setCursor(0, 0); if (flags.dhtError) { lcd.print(F("T:ERR H:ERR")); } else { lcd.print(F("T:")); lcd.print(temperature, 1); lcd.print(F("C H:")); lcd.print(humidity, 1); lcd.print(F("%")); } lcd.setCursor(0, 1); if (flags.phError) { lcd.print(F("pH:ERR ")); } else { lcd.print(F("pH:")); lcd.print(phValue, 1); lcd.print(F(" ")); } if (flags.tdsError) { lcd.print(F("TDS:ERR")); } else { lcd.print(F("TDS:")); lcd.print(tdsValue, 0); } } void monitorDHT() { static unsigned long lastDHTCheck = 0; if (millis() - lastDHTCheck >= 30000) { if (flags.dhtError && dhtErrorCount >= 5) { dht.begin(); delay(2000); float testT = dht.readTemperature(); float testH = dht.readHumidity(); if (!isnan(testT) && !isnan(testH)) { flags.dhtError = false; dhtErrorCount = 0; temperature = testT; humidity = testH; } } lastDHTCheck = millis(); } }