From 2f78d8867cb2a2e2ed798d0a8e1819258d908633 Mon Sep 17 00:00:00 2001 From: Fansyah301 Date: Mon, 4 Aug 2025 14:51:48 +0700 Subject: [PATCH] first commit --- ARDUINO/ARDUINO.ino | 137 +++++++++++++++ ARDUINO/ARDUINO_POT/ARDUINO_POT.ino | 137 +++++++++++++++ ARDUINO/KALIBRASI/KALIBRASI.ino | 18 ++ ESP8266/ESP8266.ino | 255 ++++++++++++++++++++++++++++ NODERED/flows.json | 50 ++++++ 5 files changed, 597 insertions(+) create mode 100644 ARDUINO/ARDUINO.ino create mode 100644 ARDUINO/ARDUINO_POT/ARDUINO_POT.ino create mode 100644 ARDUINO/KALIBRASI/KALIBRASI.ino create mode 100644 ESP8266/ESP8266.ino create mode 100644 NODERED/flows.json diff --git a/ARDUINO/ARDUINO.ino b/ARDUINO/ARDUINO.ino new file mode 100644 index 0000000..a5c0599 --- /dev/null +++ b/ARDUINO/ARDUINO.ino @@ -0,0 +1,137 @@ +#include + +// Konfigurasi LCD +LiquidCrystal_I2C lcd(0x27, 20, 4); // LCD 20x4 + +// Pin sensor +const int pHpin = A0; + +// Kalibrasi berdasarkan data aktual (pH = -6.90 * V + 28.15) +const float FIXED_SLOPE = -6.90; +const float FIXED_INTERCEPT = 28.15; + +#define UKURAN_FILTER 10 +float pembacaanpH[UKURAN_FILTER]; +int indeksPembacaan = 0; + +unsigned long waktuBacaTerakhir = 0; +const int intervalBaca = 1000; +unsigned long waktuKirimTerakhir = 0; +const int intervalKirim = 2000; + +void setup() { + Serial.begin(9600); + lcd.init(); + lcd.backlight(); + lcd.clear(); + lcd.setCursor(0, 0); + lcd.print("Monitoring pH Meter"); + lcd.setCursor(0, 1); + lcd.print(" Feeder Automatic "); + delay(2000); + lcd.clear(); +} + +void loop() { + float tegangan = dapatkanTeganganRataRata(); + float nilaipH = dapatkanpHStabil(tegangan); + + if (millis() - waktuBacaTerakhir >= intervalBaca) { + waktuBacaTerakhir = millis(); + tampilkanHasil(nilaipH); + } + + if (millis() - waktuKirimTerakhir >= intervalKirim) { + waktuKirimTerakhir = millis(); + kirimKeESP(nilaipH, tegangan); + } +} + +float dapatkanTeganganRataRata() { + float total = 0; + for (int i = 0; i < 10; i++) { + total += analogRead(pHpin); + delay(10); + } + float tegangan = (total / 10.0) * (5.0 / 1023.0); // Sesuaikan dengan Vref 5V + return constrain(tegangan, 0, 5.0); +} + +float dapatkanpHStabil(float tegangan) { + float pH = (FIXED_SLOPE * tegangan) + FIXED_INTERCEPT; + pembacaanpH[indeksPembacaan] = pH; + indeksPembacaan = (indeksPembacaan + 1) % UKURAN_FILTER; + + float total = 0; + int jumlahValid = 0; + for (int i = 0; i < UKURAN_FILTER; i++) { + if (!isnan(pembacaanpH[i])) { + total += pembacaanpH[i]; + jumlahValid++; + } + } + + if (jumlahValid == 0) return pH; + float rataRatapH = total / jumlahValid; + if (isnan(rataRatapH) || rataRatapH < 0 || rataRatapH > 14) return pH; + return rataRatapH; +} + +void tampilkanHasil(float pH) { + // Baris 1: Nilai pH + lcd.setCursor(0, 0); + lcd.print("pH: "); + if (pH <= 0 || pH >= 14) { + lcd.print("Error "); + } else { + lcd.print(pH, 2); lcd.print(" "); + } + + // Baris 2: Status Pompa + lcd.setCursor(0, 1); + if (pH < 6.5) { + lcd.print("Pump1:ON Pump2:OFF"); + } else if (pH > 7.5) { + lcd.print("Pump1:OFF Pump2:ON "); + } else { + lcd.print("Pump1:OFF Pump2:OFF"); + } + + // Baris 3: Rentang pH + lcd.setCursor(0, 2); + if (pH <= 0 || pH >= 14) { + lcd.print("Rentang: -- "); + } else if (pH < 3.0) { + lcd.print("Rentang: 0.0 - 2.9"); + } else if (pH < 6.5) { + lcd.print("Rentang: 3.0 - 6.4"); + } else if (pH < 7.5) { + lcd.print("Rentang: 6.5 - 7.4"); + } else if (pH < 9.0) { + lcd.print("Rentang: 7.5 - 8.9"); + } else { + lcd.print("Rentang: 9.0 - 14.0"); + } + + // Baris 4: Kategori pH + lcd.setCursor(0, 3); + if (pH <= 0 || pH >= 14) { + lcd.print("Kategori:Tidak Valid "); + } else if (pH < 3.0) { + lcd.print("Kategori:Sangat Asam "); + } else if (pH < 6.5) { + lcd.print("Kategori:Asam "); + } else if (pH < 7.5) { + lcd.print("Kategori:Netral "); + } else if (pH < 9.0) { + lcd.print("Kategori:Basa "); + } else { + lcd.print("Kategori:Sangat Basa "); + } +} + +void kirimKeESP(float pH, float volt) { + Serial.print(pH, 2); + Serial.print(","); + Serial.println(volt, 3); +} diff --git a/ARDUINO/ARDUINO_POT/ARDUINO_POT.ino b/ARDUINO/ARDUINO_POT/ARDUINO_POT.ino new file mode 100644 index 0000000..b5e575d --- /dev/null +++ b/ARDUINO/ARDUINO_POT/ARDUINO_POT.ino @@ -0,0 +1,137 @@ +#include + +// Konfigurasi LCD +LiquidCrystal_I2C lcd(0x27, 20, 4); // LCD 20x4 + +// Pin sensor +const int pHpin = A1; + +// Kalibrasi sensor pH +const float FIXED_SLOPE = 1.80; +const float FIXED_INTERCEPT = 1.00; + +#define UKURAN_FILTER 10 +float pembacaanpH[UKURAN_FILTER]; +int indeksPembacaan = 0; + +unsigned long waktuBacaTerakhir = 0; +const int intervalBaca = 1000; +unsigned long waktuKirimTerakhir = 0; +const int intervalKirim = 2000; + +void setup() { + Serial.begin(9600); + lcd.init(); + lcd.backlight(); + lcd.clear(); + lcd.setCursor(0, 0); + lcd.print("Monitoring pH Meter"); + lcd.setCursor(0, 1); + lcd.print(" Feeder Automatic "); + delay(2000); + lcd.clear(); +} + +void loop() { + float tegangan = dapatkanTeganganRataRata(); + float nilaipH = dapatkanpHStabil(tegangan); + + if (millis() - waktuBacaTerakhir >= intervalBaca) { + waktuBacaTerakhir = millis(); + tampilkanHasil(nilaipH); + } + + if (millis() - waktuKirimTerakhir >= intervalKirim) { + waktuKirimTerakhir = millis(); + kirimKeESP(nilaipH, tegangan); + } +} + +float dapatkanTeganganRataRata() { + float total = 0; + for (int i = 0; i < 10; i++) { + total += analogRead(pHpin); + delay(10); + } + float tegangan = (total / 10.0) * (5.0 / 1023.0); + return constrain(tegangan, 0, 5.0); +} + +float dapatkanpHStabil(float tegangan) { + float pH = (FIXED_SLOPE * tegangan) + FIXED_INTERCEPT; + pembacaanpH[indeksPembacaan] = pH; + indeksPembacaan = (indeksPembacaan + 1) % UKURAN_FILTER; + + float total = 0; + int jumlahValid = 0; + for (int i = 0; i < UKURAN_FILTER; i++) { + if (!isnan(pembacaanpH[i])) { + total += pembacaanpH[i]; + jumlahValid++; + } + } + + if (jumlahValid == 0) return pH; + float rataRatapH = total / jumlahValid; + if (isnan(rataRatapH) || rataRatapH < 0 || rataRatapH > 14) return pH; + return rataRatapH; +} + +void tampilkanHasil(float pH) { + // Baris 1: Nilai pH + lcd.setCursor(0, 0); + lcd.print("pH: "); + if (pH <= 0 || pH >= 14) { + lcd.print("Error "); + } else { + lcd.print(pH, 2); lcd.print(" "); + } + + // Baris 2: Status Pompa + lcd.setCursor(0, 1); + if (pH < 6.5) { + lcd.print("Pump1:ON Pump2:OFF "); + } else if (pH > 7.5) { + lcd.print("Pump1:OFF Pump2:ON "); + } else { + lcd.print("Pump1:OFF Pump2:OFF "); + } + + // Baris 3: Rentang pH + lcd.setCursor(0, 2); + if (pH <= 0 || pH >= 14) { + lcd.print("Rentang: -- "); + } else if (pH < 3.0) { + lcd.print("Rentang: 0.0 - 2.9"); + } else if (pH < 6.5) { + lcd.print("Rentang: 3.0 - 6.4"); + } else if (pH < 7.5) { + lcd.print("Rentang: 6.5 - 7.4"); + } else if (pH < 9.0) { + lcd.print("Rentang: 7.5 - 8.9"); + } else { + lcd.print("Rentang: 9.0 - 14.0"); + } + + // Baris 4: Kategori pH + lcd.setCursor(0, 3); + if (pH <= 0 || pH >= 14) { + lcd.print("Kategori:Tidak Valid"); + } else if (pH < 3.0) { + lcd.print("Kategori:Sangat Asam"); + } else if (pH < 6.5) { + lcd.print("Kategori:Asam "); + } else if (pH < 7.5) { + lcd.print("Kategori:Netral "); + } else if (pH < 9.0) { + lcd.print("Kategori:Basa "); + } else { + lcd.print("Kategori:Sangat Basa"); + } +} + +void kirimKeESP(float pH, float volt) { + Serial.print(pH, 2); + Serial.print(","); + Serial.println(volt, 3); +} diff --git a/ARDUINO/KALIBRASI/KALIBRASI.ino b/ARDUINO/KALIBRASI/KALIBRASI.ino new file mode 100644 index 0000000..7303bab --- /dev/null +++ b/ARDUINO/KALIBRASI/KALIBRASI.ino @@ -0,0 +1,18 @@ +const int pHpin = A0; + +void setup() { + Serial.begin(9600); +} + +void loop() { + int adcValue = analogRead(pHpin); + float voltage = adcValue * 5.0 / 1023.0; + + Serial.print("ADC: "); + Serial.print(adcValue); + Serial.print(" | Tegangan: "); + Serial.print(voltage, 3); + Serial.println(" V"); + + delay(1000); +} diff --git a/ESP8266/ESP8266.ino b/ESP8266/ESP8266.ino new file mode 100644 index 0000000..8c9230a --- /dev/null +++ b/ESP8266/ESP8266.ino @@ -0,0 +1,255 @@ +#include +#include +#include +#include +#include + +// === WiFi & MQTT === +const char* ssid = "Fansyah"; +const char* password = "12345678"; +const char* mqtt_server = "203.194.113.47"; + +WiFiClient espClient; +PubSubClient client(espClient); + +// === NTP (UTC+7) === +WiFiUDP ntpUDP; +NTPClient timeClient(ntpUDP, "pool.ntp.org", 25200); + +// === Pin === +#define RELAY1 D0 +#define RELAY2 D4 +#define SERVO_PIN D5 +Servo myServo; + +// === Jadwal Pakan === +int morningHour = 6, morningMinute = 0; +int eveningHour = 17, eveningMinute = 30; +bool morningFed = false, eveningFed = false, modeOtomatis = true; + +// === Servo Timer === +bool servoActive = false; +unsigned long servoStartTime = 0; +const unsigned long SERVO_DURATION = 60000; + +// === Data pH & Tegangan === +float pHValue = 0.0; +float voltageValue = 0.0; +unsigned long lastDataReceivedTime = 0; + +// === Threshold Relay === +const float pH_THRESHOLD_LOW = 6.5; +const float pH_THRESHOLD_HIGH = 7.5; + +// === Mode Pompa === +bool modePompaOtomatis = true; // default otomatis +String perintahManual = "netral"; // default netral + +void setup() { + Serial.begin(9600); + pinMode(RELAY1, OUTPUT); + pinMode(RELAY2, OUTPUT); + digitalWrite(RELAY1, HIGH); + digitalWrite(RELAY2, HIGH); + + myServo.attach(SERVO_PIN); + myServo.write(0); // Posisi awal servo + + WiFi.begin(ssid, password); + while (WiFi.status() != WL_CONNECTED) { + delay(500); Serial.print("."); + } + Serial.println("\nWiFi Terhubung: " + WiFi.localIP().toString()); + + client.setServer(mqtt_server, 1883); + client.setCallback(callback); + timeClient.begin(); + + Serial.println("Menunggu data pH dari Arduino..."); +} + +void loop() { + if (!client.connected()) reconnectMQTT(); + client.loop(); + timeClient.update(); + + checkSchedule(); + readSerialData(); + checkServo(); + sendCurrentTimeToMQTT(); + checkpHLevel(); // Periksa dan kontrol relay +} + +void reconnectMQTT() { + while (!client.connected()) { + Serial.print("Menghubungkan ke MQTT..."); + if (client.connect("ESPFeederClient")) { + Serial.println(" Terhubung!"); + client.subscribe("feeder/jadwal/siang"); + client.subscribe("feeder/jadwal/sore"); + client.subscribe("feeder/mode"); + client.subscribe("feeder/modepompa"); + client.subscribe("feeder/manual/kontrol"); + client.publish("feeder/status", "ESP Terhubung"); + } else { + Serial.print(" Gagal ("); Serial.print(client.state()); + Serial.println("). Coba lagi 5 detik..."); + delay(5000); + } + } +} + +void callback(char* topic, byte* payload, unsigned int length) { + String msg; + for (int i = 0; i < length; i++) msg += (char)payload[i]; + msg.trim(); + + if (String(topic) == "feeder/jadwal/siang") { + int sep = msg.indexOf(':'); + morningHour = msg.substring(0, sep).toInt(); + morningMinute = msg.substring(sep + 1).toInt(); + Serial.println("[MQTT] Jadwal Siang: " + msg); + } else if (String(topic) == "feeder/jadwal/sore") { + int sep = msg.indexOf(':'); + eveningHour = msg.substring(0, sep).toInt(); + eveningMinute = msg.substring(sep + 1).toInt(); + Serial.println("[MQTT] Jadwal Sore: " + msg); + } else if (String(topic) == "feeder/mode") { + modeOtomatis = (msg == "otomatis"); + Serial.println("[MQTT] Mode: " + msg); + } else if (String(topic) == "feeder/modepompa") { + modePompaOtomatis = (msg == "otomatis"); + Serial.println("[MQTT] Mode Pompa: " + msg); + } else if (String(topic) == "feeder/manual/kontrol") { + perintahManual = msg; + Serial.println("[MQTT] Perintah Manual Pompa: " + msg); + } +} + +void checkSchedule() { + int h = timeClient.getHours(); + int m = timeClient.getMinutes(); + + if (modeOtomatis) { + if (h == morningHour && m == morningMinute && !morningFed) { + triggerFeeding("Pagi"); + morningFed = true; + eveningFed = false; + } + + if (h == eveningHour && m == eveningMinute && !eveningFed) { + triggerFeeding("Sore"); + eveningFed = true; + morningFed = false; + } + + if (h != morningHour || m != morningMinute) morningFed = false; + if (h != eveningHour || m != eveningMinute) eveningFed = false; + } +} + +void triggerFeeding(String waktu) { + Serial.println(">> MEMBERI PAKAN " + waktu + " (" + timeClient.getFormattedTime() + ")"); + myServo.write(90); // Aktifkan servo + servoStartTime = millis(); + servoActive = true; + + String notif = "Pakan diberikan - " + waktu + " | " + timeClient.getFormattedTime(); + client.publish("feeder/notifikasi", notif.c_str()); + Serial.println("[MQTT] " + notif); +} + +void checkServo() { + if (servoActive && millis() - servoStartTime >= SERVO_DURATION) { + myServo.write(0); // Kembalikan ke posisi awal + servoActive = false; + Serial.println("[INFO] Servo dimatikan"); + } +} + +void readSerialData() { + if (Serial.available()) { + String in = Serial.readStringUntil('\n'); + in.trim(); + int sep = in.indexOf(','); + + if (sep > 0) { + float pH = in.substring(0, sep).toFloat(); + float volt = in.substring(sep + 1).toFloat(); + String t = timeClient.getFormattedTime(); + + String ph_payload = String(pH, 2) + " | " + t; + String volt_payload = String(volt, 3) + " V | " + t; + client.publish("feeder/pH", ph_payload.c_str()); + client.publish("feeder/tegangan", volt_payload.c_str()); + + Serial.printf("[DATA] pH: %.2f | V: %.3f | %s\n", pH, volt, t.c_str()); + lastDataReceivedTime = millis(); + pHValue = pH; + voltageValue = volt; + } + } + + if (millis() - lastDataReceivedTime > 10000 && lastDataReceivedTime != 0) { + String t = timeClient.getFormattedTime(); + client.publish("feeder/pH", ("0 | " + t).c_str()); + client.publish("feeder/tegangan", ("0 V | " + t).c_str()); + Serial.println("[WARNING] Timeout data pH"); + lastDataReceivedTime = 0; + } +} + +void kirimStatusPompa(String status) { + client.publish("feeder/statuspompa", status.c_str()); + Serial.println("[MQTT] Status Pompa: " + status); +} + +void checkpHLevel() { + String status = "Pompa OFF"; + + if (modePompaOtomatis) { + Serial.print(">> [Otomatis] Pemeriksaan pH: "); Serial.println(pHValue, 2); + + if (pHValue < pH_THRESHOLD_LOW) { + digitalWrite(RELAY1, LOW); + digitalWrite(RELAY2, HIGH); + status = "Pompa 1 Aktif (Otomatis - pH rendah)"; + } else if (pHValue > pH_THRESHOLD_HIGH) { + digitalWrite(RELAY1, HIGH); + digitalWrite(RELAY2, LOW); + status = "Pompa 2 Aktif (Otomatis - pH tinggi)"; + } else { + digitalWrite(RELAY1, HIGH); + digitalWrite(RELAY2, HIGH); + status = "Tidak ada Pompa Aktif (Otomatis - pH normal)"; + } + } else { + Serial.print(">> [Manual] Perintah: "); Serial.println(perintahManual); + + if (perintahManual == "ph_rendah") { + digitalWrite(RELAY1, LOW); + digitalWrite(RELAY2, HIGH); + status = "Pompa 1 Aktif (Manual - pH rendah)"; + } else if (perintahManual == "ph_tinggi") { + digitalWrite(RELAY1, HIGH); + digitalWrite(RELAY2, LOW); + status = "Pompa 2 Aktif (Manual - pH tinggi)"; + } else { + digitalWrite(RELAY1, HIGH); + digitalWrite(RELAY2, HIGH); + status = "Tidak ada Pompa Aktif (Manual - Netral)"; + } + } + + kirimStatusPompa(status); +} + +void sendCurrentTimeToMQTT() { + static unsigned long last = 0; + if (millis() - last >= 1000) { + last = millis(); + String t = timeClient.getFormattedTime(); + client.publish("feeder/waktu", t.c_str()); + Serial.println("[MQTT] Waktu dikirim: " + t); + } +} diff --git a/NODERED/flows.json b/NODERED/flows.json new file mode 100644 index 0000000..bcb8db9 --- /dev/null +++ b/NODERED/flows.json @@ -0,0 +1,50 @@ +[ + { + "id": "2e92a845f1f50f3f", + "type": "ui_gauge", + "z": "29be4b82ed975b38", + "name": "Volt Sensor", + "group": "6052452935d9b5f7", + "order": 2, + "width": 3, + "height": 3, + "gtype": "donut", + "title": "Tegangan", + "label": "", + "format": "{{value}} V", + "min": "0", + "max": "5", + "colors": [ + "#1d91c0", + "#ffe600", + "#ca3838" + ], + "seg1": "", + "seg2": "", + "diff": false, + "className": "", + "x": 340, + "y": 120, + "wires": [] + }, + { + "id": "6052452935d9b5f7", + "type": "ui_group", + "name": "Monitoring PH Meter", + "tab": "03a33f3b692afcd8", + "order": 1, + "disp": true, + "width": 6, + "collapse": false, + "className": "" + }, + { + "id": "03a33f3b692afcd8", + "type": "ui_tab", + "name": "Fedder Dashboard", + "icon": "dashboard", + "order": 1, + "disabled": false, + "hidden": false + } +] \ No newline at end of file