first commit

This commit is contained in:
Fansyah301 2025-08-04 14:51:48 +07:00
commit 2f78d8867c
5 changed files with 597 additions and 0 deletions

137
ARDUINO/ARDUINO.ino Normal file
View File

@ -0,0 +1,137 @@
#include <LiquidCrystal_I2C.h>
// 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);
}

View File

@ -0,0 +1,137 @@
#include <LiquidCrystal_I2C.h>
// 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);
}

View File

@ -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);
}

255
ESP8266/ESP8266.ino Normal file
View File

@ -0,0 +1,255 @@
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <Servo.h>
#include <NTPClient.h>
#include <WiFiUdp.h>
// === 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);
}
}

50
NODERED/flows.json Normal file
View File

@ -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
}
]