From 01772b32e993097601f5118327c7bbb63e93314e Mon Sep 17 00:00:00 2001 From: Ghaida Date: Thu, 31 Jul 2025 12:31:17 +0700 Subject: [PATCH] first commit --- sketch_jul31a.ino | 530 ++++++++++++++++++++++++++++++++ sketch_jul31b/sketch_jul31b.ino | 219 +++++++++++++ 2 files changed, 749 insertions(+) create mode 100644 sketch_jul31a.ino create mode 100644 sketch_jul31b/sketch_jul31b.ino diff --git a/sketch_jul31a.ino b/sketch_jul31a.ino new file mode 100644 index 0000000..5744c6d --- /dev/null +++ b/sketch_jul31a.ino @@ -0,0 +1,530 @@ +#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(); + } +} \ No newline at end of file diff --git a/sketch_jul31b/sketch_jul31b.ino b/sketch_jul31b/sketch_jul31b.ino new file mode 100644 index 0000000..819b36a --- /dev/null +++ b/sketch_jul31b/sketch_jul31b.ino @@ -0,0 +1,219 @@ +#include +#include // Gunakan library "Firebase ESP32 Client" by Mobizt + +#define RXD2 16 // RX pin ESP32 +#define TXD2 17 // TX pin ESP32 + +// WiFi credentials +#define WIFI_SSID "hydrop" +#define WIFI_PASSWORD "123123123" + +// Firebase config +#define FIREBASE_HOST "https://hydrop-a1e64-default-rtdb.firebaseio.com" +#define FIREBASE_AUTH "4lJpDHPo9LGh6WCYpjs1aRB6ABJZwHLOXkaCoLAV" + +// Firebase objects +FirebaseData fbdo; +FirebaseAuth auth; +FirebaseConfig config; + +struct SensorData { + float temperature; + float humidity; + float ph; + float tds; + bool tempValid; + bool humValid; + bool phValid; + bool tdsValid; + unsigned long lastUpdate; +} sensorData; + +struct CommStats { + unsigned long totalReceived; + unsigned long validData; + unsigned long errorData; + unsigned long lastReceived; +} commStats = {0, 0, 0, 0}; + +const unsigned long DATA_TIMEOUT = 10000; +const unsigned long STATS_INTERVAL = 30000; + +void setup() { + Serial.begin(115200); + Serial2.begin(9600, SERIAL_8N1, RXD2, TXD2); + + // Koneksi WiFi + WiFi.begin(WIFI_SSID, WIFI_PASSWORD); + Serial.print("Connecting to WiFi"); + while (WiFi.status() != WL_CONNECTED) { + Serial.print("."); + delay(500); + } + Serial.println("\nWiFi connected!"); + + // Konfigurasi Firebase + config.database_url = FIREBASE_HOST; + config.signer.tokens.legacy_token = FIREBASE_AUTH; + + Firebase.begin(&config, &auth); + Firebase.reconnectWiFi(true); + + Serial.println("Firebase initialized."); + + sensorData = {0, 0, 0, 0, false, false, false, false, 0}; + + Serial.println("=== ESP32 Hydroponic Receiver ==="); + Serial.println("Menunggu data dari Arduino..."); +} + +void loop() { + if (Serial2.available()) { + String rawData = Serial2.readStringUntil('\n'); + rawData.trim(); + + commStats.totalReceived++; + commStats.lastReceived = millis(); + + if (parseAndValidateData(rawData)) { + commStats.validData++; + sensorData.lastUpdate = millis(); + + displaySensorData(); + sendToFirebase(); + } else { + commStats.errorData++; + Serial.print("ERROR: Invalid data format: "); + Serial.println(rawData); + } + } + + checkDataTimeout(); + displayStats(); + + delay(100); +} + +bool parseAndValidateData(String data) { + if (data.length() < 5) return false; + + int idx1 = data.indexOf(','); + int idx2 = data.indexOf(',', idx1 + 1); + int idx3 = data.indexOf(',', idx2 + 1); + if (idx1 <= 0 || idx2 <= 0 || idx3 <= 0) return false; + + String tempStr = data.substring(0, idx1); + String humStr = data.substring(idx1 + 1, idx2); + String phStr = data.substring(idx2 + 1, idx3); + String tdsStr = data.substring(idx3 + 1); + + float temp = tempStr.toFloat(); + float hum = humStr.toFloat(); + float ph = phStr.toFloat(); + float tds = tdsStr.toFloat(); + + sensorData.temperature = temp; + sensorData.humidity = hum; + sensorData.ph = ph; + sensorData.tds = tds; + sensorData.tempValid = validateTemperature(temp); + sensorData.humValid = validateHumidity(hum); + sensorData.phValid = validatePH(ph); + sensorData.tdsValid = validateTDS(tds); + + return (sensorData.tempValid || sensorData.humValid || sensorData.phValid || sensorData.tdsValid); +} + +void sendToFirebase() { + if (WiFi.status() != WL_CONNECTED) { + Serial.println("WiFi disconnected, skipping Firebase upload."); + return; + } + + FirebaseJson json; + json.set("temperature", sensorData.tempValid ? sensorData.temperature : -999); + json.set("humidity", sensorData.humValid ? sensorData.humidity : -999); + json.set("ph", sensorData.phValid ? sensorData.ph : -999); + json.set("tds", sensorData.tdsValid ? sensorData.tds : -999); + json.set("timestamp", millis()); + + if (Firebase.updateNode(fbdo, "/sensorData", json)) { + Serial.println("Data sent to Firebase."); + } else { + Serial.print("Firebase failed: "); + Serial.println(fbdo.errorReason()); + } +} + +bool validateTemperature(float temp) { + return temp != -999 && temp >= -10 && temp <= 60; +} + +bool validateHumidity(float hum) { + return hum != -999 && hum >= 0 && hum <= 100; +} + +bool validatePH(float ph) { + return ph != -999 && ph >= 0 && ph <= 14; +} + +bool validateTDS(float tds) { + return tds != -999 && tds >= 0 && tds <= 3000; +} + +void displaySensorData() { + Serial.println("=== SENSOR DATA RECEIVED ==="); + Serial.print("Temperature: "); + Serial.println(sensorData.tempValid ? String(sensorData.temperature, 2) + " °C" : "INVALID"); + + Serial.print("Humidity: "); + Serial.println(sensorData.humValid ? String(sensorData.humidity, 2) + " %" : "INVALID"); + + Serial.print("pH: "); + Serial.println(sensorData.phValid ? String(sensorData.ph, 2) : "INVALID"); + + Serial.print("TDS: "); + Serial.println(sensorData.tdsValid ? String(sensorData.tds, 0) + " ppm" : "INVALID"); + + Serial.println("==========================="); +} + +void checkDataTimeout() { + static unsigned long lastTimeoutCheck = 0; + if (millis() - lastTimeoutCheck >= 5000) { + if (sensorData.lastUpdate > 0 && (millis() - sensorData.lastUpdate) > DATA_TIMEOUT) { + Serial.println("⚠️ WARNING: No valid data for 10 seconds!"); + sensorData.tempValid = false; + sensorData.humValid = false; + sensorData.phValid = false; + sensorData.tdsValid = false; + } + lastTimeoutCheck = millis(); + } +} + +void displayStats() { + static unsigned long lastStatsDisplay = 0; + if (millis() - lastStatsDisplay >= STATS_INTERVAL) { + Serial.println("=== STATISTIK KOMUNIKASI ==="); + Serial.print("Total diterima: "); + Serial.println(commStats.totalReceived); + Serial.print("Valid: "); + Serial.println(commStats.validData); + Serial.print("Error: "); + Serial.println(commStats.errorData); + + if (commStats.totalReceived > 0) { + float success = (float)commStats.validData / commStats.totalReceived * 100; + Serial.print("Success rate: "); + Serial.print(success, 1); + Serial.println(" %"); + } + + Serial.print("Terakhir diterima: "); + Serial.println((millis() - commStats.lastReceived) / 1000); + + Serial.println("============================="); + lastStatsDisplay = millis(); + } +} \ No newline at end of file