530 lines
13 KiB
C++
530 lines
13 KiB
C++
#include <DHT.h>
|
|
#include <LiquidCrystal_I2C.h>
|
|
#include <Wire.h>
|
|
#include <SoftwareSerial.h>
|
|
|
|
// 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();
|
|
}
|
|
} |