// =========================================================================================
// AUTO FEED DENGAN EEPROM DI RTC ARDUINO MEGA
// =========================================================================================
// Ini di gunakan sebagai master di mana menangani max 31 slave menggunakan komunikasi RS258
// RS485 dapat menangani max 32 layanan dimana satu layanan di gunakan oleh master itu sendiri
// Slave 1 - 30 di gunakan oleh slave servi
// Slave 31 digunakan oleh modul (PH Meter, TDS, Suhu, Aerator, keran)
#include <RTClib.h>
#include <Servo.h>
#include <LiquidCrystal_I2C.h>
#include <SoftwareSerial.h>
#include <Wire.h>
//===========================================================================================
//inisiasi pin
//===========================================================================================
#define btn_down_pin 2
#define btn_down digitalRead(btn_down_pin) //action button down
#define btn_up_pin 3
#define btn_up digitalRead(btn_up_pin) //Action button up
#define btn_ok_pin 4
#define btn_ok digitalRead(btn_ok_pin) //Action button up
#define buz_pin 5 //pin buzzer
#define DERE 8 //DE dan RE di gabung jadi satu
#define EEPROM_I2C_ADDRESS 0x50
SoftwareSerial RS485Serial(10, 7);
LiquidCrystal_I2C lcd(0x27, 20, 4);
//============================================================================================
const byte eepromJamPak[4] = { 10, 11, 12, 13 }; //almat di eeprom untuk jam pakan 1-4 kali dalam sehari
const byte eepromMntPak[4] = { 14, 15, 16, 17 }; //alamat di eeprom untuk menit pakan dalam sehari
const byte eepromDtkPak[4] = { 18, 19, 20, 21 }; //alamat di eeprom untuk detik pakan dalam sehari
const byte eepromEnableJamPakan[4] = { 25, 26, 27, 28 }; //alamatdi eeprom untuk hidup dan mati jam pakan 4x sehari
const byte eepromEnableKeranAir = 29; //alamat di eeprom utnuk hidup dan mati relay untuk valve keran
const byte eepromEnablePhMeter = 30; //alamat di eeprom untuk hidup dan mai ph meter
const byte eepromKeranAirPerHari = 31; //alamat di eeporm untuk seting relay valve akan hidup dalam berapa kali sehari jika diseting 0 maka akan hidup sehari sekali
const byte eepromJamRelay = 32; //alamat di eeprom untuk jam relay menyala
const byte eepromMntRelay = 33; //alamat di eeprom untuk mentit relay menyala
const byte eepromDtkRelay = 34; //alamat dieeporm untuk detik relay menyala
const byte eepromFistLoad = 35; //alamat penyimpanan pertama kali load
const byte eepromEnableBuzzer = 36; //alaamt dieeprom untuk enbable buzzer
const byte eepromEnableRelayAerator = 37; //alamat di eeprom untuk aerator
const byte eepromDurasiRelayAeratorMati = 38; //alamat eeprom untuk durasi otomatis hidup lagi aerator
const byte eepromEnableRelayUV = 41; //alamat eeprom lampuUV
const byte eJamUv[2] = { 42, 43 }; //alamat jadwal jam uv
const byte eMntUv[2] = { 44, 45 }; //alamat jadwal menit uv
const byte eDtkUv[2] = { 46, 47 }; //alamat jadwal detik uv
const byte eJamAer[2] = { 48, 49 }; //alamat jadwal jam aerator
const byte eMntAer[2] = { 50, 51 }; //alamat jadwal menit aerator
const byte eDtkAer[2] = { 52, 53 };
const byte eEnableJadwalAer = 54; //alamat jadwal mati hidup aerator
const byte eepromDurasiKeranAirPerHari = 70; //alamat di eeprom untuk seting lama durasi keran valve hidup yang konstan atau tetap tampa pengurangan
const byte eepromDurasiKeranAirHidup = 80; //alamat dieeporm untuk seting durasi valve hidup yang akan terus berkurang sampai 0
const byte eepromTotalJmlPakan = 90; //alamat di eeporm untuk menyimpan totaljumlah pakan yang telah di berikan
const int eepromEnableServo = 94; //alamat eeprom untuk hidup mati servo 91-555 dengn boole 1 byte
const int eepromAngleServo = 559; //alamat di eeprom untuk derajat/angle dari servo 556-1485 dengn int16_t
const int eepromJmlPakan = 1489; //alamatdi eeprom untuk jumlah pakan dalam sekali diberi pakan berapa kali servo bergerak dari 1486 - 1950 dengan int8_t
//============================================================================================
//Menu navigasi yang di ambil dari enum di bawah
//============================================================================================
char menu;
enum PROGMEM {
m1_dis, // menu display
//Servo, keran, aerator, setting
m1_uta, //menu utama
m1_kem, //kembali
m12_ser, //servo
m123_ser,
m12_ka, //keran air
m12_aer,
m12_phm,
m12_buz,
m12_uv,
//jadwal pakan, servo, jumlah pakan
//keran air, ph meter, buzzer
//tanggal, waktu, reset, kembali
m12_set, //setting
//jadwal paka
m123_sjdp, //seting jadwal pakan
////servo 1, servo 2, servo 3, servo 4, servo 4, kembali
m12_sse, //seting servo slave
m123_sse, //seting servo
m123_vss, //view seting servo
m123_sss, //save setting servo
m123_sjp, //setting jumlah pakan
m123_ska, //setting keran air
m123_sae, //setting aerator
m123_suv, //setting uv
m123_stg, //setting tanggal
m1_swk, //setting waktu
m1234_sdkam, //setting durasi keran air mati
m1234_svtg, //save tanggal
m1234_sdka, //setting durasi keran air
m1234_skaph, //setting keran air per hari
m1234_sjka, //setting jam relay valve
m1_kon, //konfirmasi
m12_svwk //save waktu
};
enum PROGMEM {//Jenis dari waktu yang akan di set atau di save yang di isi dari enum dibawah
n,
w,
wp0, //Waktu Pakan 0
wp1, //Waktu Pakan 1
wp2, //Waktu Pakan 2
wp3 //Waktu Pakan 3
};
//============================================================================================
//icon
//============================================================================================
byte icon[8] = { 0b00000, 0b11110, 0b00000, 0b11110, 0b00000, 0b11110, 0b00000, 0b00000 };
byte I10[8] = { 0b00000, 0b10000, 0b00000, 0b01001, 0b00011, 0b10111, 0b01101, 0b01111 };
byte I20[8] = { 0b00000, 0b00000, 0b00000, 0b11100, 0b11110, 0b11111, 0b11111, 0b11111 };
byte I30[8] = { 0b00000, 0b00000, 0b00000, 0b00000, 0b00100, 0b01100, 0b11100, 0b11100 };
byte I40[8] = { 0b10000, 0b00100, 0b00000, 0b10001, 0b00011, 0b00110, 0b00011, 0b00001 };
byte I11[8] = { 0b00111, 0b00011, 0b00001, 0b00000, 0b00000, 0b00010, 0b00011, 0b00111 };
byte I21[8] = { 0b11111, 0b11110, 0b11100, 0b00000, 0b10000, 0b00000, 0b00010, 0b00001 };
byte I31[8] = { 0b01100, 0b00100, 0b00000, 0b01011, 0b00111, 0b01110, 0b00111, 0b00011 };
byte I41[8] = { 0b00000, 0b00000, 0b00000, 0b10000, 0b11010, 0b11110, 0b11010, 0b10000 };
byte ICal[8] = { B01010, B11111, B10001, B11111, B10001, B10001, B11111, B00000 };
byte IClo[8] = { B01110, B10001, B10111, B10101, B10101, B10001, B01110, B00000 };
byte IBuz[8] = { B00100, B01110, B01010, B01010, B11111, B00000, B00100, B00000 };
byte ITemp[8] = { B01110, B01011, B01010, B01011, B01110, B11111, B11111, B01110 };
byte IPh[8] = { B11100, B10100, B11100, B10000, B10100, B10100, B10111, B10101 };
byte ITds[8] = { B01110, B00100, B00100, B11011, B10110, B10111, B10101, B11011 };
byte IUv[8] = { B10100, B10100, B11100, B00000, B01010, B01010, B00100, B00000 };
byte IAer[8] = { B00100, B11100, B11101, B00100, B00001, B00010, B00001, B00000 };
byte IOff[8] = { B00000, B11111, B10001, B10001, B10001, B10001, B11111, B00000 };
byte IOn[8] = { B00000, B11111, B11111, B11111, B11111, B11111, B11111, B00000 };
char charMsg = n;
int8_t int8Msg = 0;
int16_t int16Msg = 0;
bool backlightLCD = 1;
//RTC (Real Time Clock)
RTC_DS1307 rtc;
//Angle Servo -> derajat perputaran servo
int8_t baris;
int8_t kolom;
int16_t angleServo[31][15];
bool enableServo[31][15];
int8_t jmlPakan[31][15]; //jumlah pakan yang diberikan dalam sekali sessi
//Tanggal default saat setting harus type data int
int16_t tanggal[3] = { 0, 0, 0 },
durasiKeranAirPerHari = 0,
buf[10],
temp,
dktNoBacklight = 1000, //durasi untuk matikan backlight lcd
durasiRelayAeratorMati = 120,
durasiBuzzer = 0,
TIMEOUT_MS = 0, // Pengaturan batas waktu tunggu respon (dalam milidetik) rs485
totalJmlPakan = 0, //pencatatan total semua pakan yang diberikan //durasi buzzer
durasiKeranAirHidup = 60, //durasi relay valve hidupnya berapa detik // hidup mati relay valve
avgValue; //penyimpanan average valuedari feedback sensor
int8_t idx_pos = 0,
pos_waktu[5] = { 19, 7, 10, 13, 12 }, //Posisi setting waktu 8 untuk jam 11 untuk menit 14 untuk detik 15 untuk back atau exit
pos_tanggal[4] = { 19, 6, 9, 14 }, //Posisi setting tanggal 6 untuk hari 9 untuk bulan 14 untuk tahun 15 untuk back atau exit
pos_konfirmasi[3] = { 2, 9 }, //posisi cursor saat konfirmasi ya dan tidak
pos_keranAir[7] = { 19, 19, 19, 19, 7, 10, 13 }, //posisi relay balve atau keran air
pos_matiHidup[3] = { 19, 7, 15 }, //posisi ph meter
pos_servo[4] = { 19, 16, 10, 15 }, //posisi servo
pos_setAerator[9] = { 19, 19, 19, 7, 10, 13, 7, 10, 13 }, //posisi set aerator
p_setUv[8] = { 19, 19, 7, 10, 13, 7, 10, 13 }, //posisi cursor set uv
enableJamPakan[4] = { 1, 1, 1, 1 }, //enable dan disable 4 jadwal waktu pakan
jamUv[2] = { 6, 18 }, //jam un
mntUv[2] = { 0, 0 }, //menit uv
dtkUv[2] = { 0, 0 }, //detik uv
jamAer[2] = { 6, 18 }, //jam Aerator
mntAer[2] = { 0, 0 }, //menit Aerator
dtkAer[2] = { 0, 0 }, //detik Aerator
waktu[3] = { 0, 0, 0 }, //Waktu default saat setting harus type data int
jamPak[4] = { 3, 9, 15, 21 }, //waktu jam paka
mntPak[4] = { 0, 0, 0, 0 }, //menit pakan
dtkPak[4] = { 0, 0, 0, 0 }; //detik pakan
bool statusSlave[31] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
int8_t jmlEnablePakan = 0, //jumlah jadwal yang di enable kan dari jadwal 1-4
jRVHidup = 9, // jam relay valve hidup
mRVHidup = 0, // menit relay valve hidup
dRVHidup = 0, // detik relay valve hidup
keranAirPerHari = 0, //relay vaalve setiap berapa hari seali
jamRelayAerator = 0, // jam aerator
menitRelayAerator = 0, // menit aerator
detikRelayAerator = 0, // detik aerator
jamKeranAir = 0, //jam relay
menitKeranAir = 0, //menit relay
detikKeranAir = 0; // detik relay
//posisi kursor pilihan
const char cJam[5] = "Jam:";
const char cMenit[5] = "Mnt:";
const char cDetik[5] = "Dtk:";
const char cHari[5] = "Hri:";
const char cBulan[5] = "Bln:";
const char cTahun[5] = "Thn:";
String urutan_waktu[3] = { cJam, cMenit, cDetik }, //untuk menampung data posisis index harus type data
urutan_tanggal[3] = { cHari, cBulan, cTahun }; //untuk menampung data posisis index harus type data
String msgKonfirmasi;
bool gantianRBuzzer = 0,
enableBuzzer = 1,
firstLoad = true, //memberikan flag saat pertama kali arduino di jalankan
enableRelayAerator = 0,
enablePhMeter = 0,
eJadwalAer = 0,
enableKeranAir = 0, //Untuk mengaktifkan dan nonaktif otomatisasi buka tutup keran air
sudahTerprosesJadwalPakan = false, //memberikan flag agar dalam beberap waktu hanya terproses sekali
enableRelayUV = 0; // hidup mati jadwal uv
uint8_t wktSebelumnya[2] = { 0, 0 }; //repet buzzer
int8_t posBack = int8Msg;
float calibration_value = 21.34 + 0.7, //ph meter
phValue = 0;
char menuKonfirmasi;
const char cMati[7] PROGMEM = "(M)ati";
const char cHidup[8] PROGMEM = "(H)idup";
const char kembali[8] PROGMEM = "Kembali";
//sensor suhu
//OneWire ds(suhu_pin); // Pin data di D4
//float suhu = 0;
void setup() {
Serial.begin(9600); // Untuk memantau lewat laptop (opsional)
Serial1.begin(9600); // Jalur komunikasi ke Bluetooth JDY-31
//RS485
RS485Serial.begin(9600);
pinMode(DERE, OUTPUT);
digitalWrite(DERE, LOW);
//Mematikan led di papan arduino
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, LOW);
//inisiasi LCD
lcd.init();
lcd.backlight();
lcd.createChar(0, I10);
lcd.setCursor(3, 1);
lcd.write(byte(0));
lcd.createChar(1, I20);
lcd.setCursor(4, 1);
lcd.write(byte(1));
lcd.createChar(2, I30);
lcd.setCursor(5, 1);
lcd.write(byte(2));
lcd.createChar(3, I40);
lcd.setCursor(6, 1);
lcd.write(byte(3));
lcd.createChar(4, I11);
lcd.setCursor(3, 2);
lcd.write(byte(4));
lcd.createChar(5, I21);
lcd.setCursor(4, 2);
lcd.write(byte(5));
lcd.createChar(6, I31);
lcd.setCursor(5, 2);
lcd.write(byte(6));
lcd.createChar(7, I41);
lcd.setCursor(6, 2);
lcd.write(byte(7));
lcd.setCursor(7, 1);
lcd.write(byte(7));
lcd.setCursor(11, 1);
lcd.print(F("FEED"));
lcd.setCursor(8, 2);
lcd.print(F("AUTOMATIC"));
delay(4000);
lcd.clear();
//SETUP RTC MODULE
if (!rtc.begin()) {
lcd.setCursor(0, 1);
lcd.print(F("TIDAK ADA RTC"));
Serial.println("TIDAK ADA RTC");
Serial.flush();
while (1)
;
}
firstLoad = readEEPROM(eepromFistLoad);
if (firstLoad) {
setDefault();
firstLoad = false;
writeEEPROM(eepromFistLoad, firstLoad);
} else {
//jadwal jam pakan
for (int8_t i = 0; i < sizeof(eepromJamPak); i++) {
jamPak[i] = readEEPROM(eepromJamPak[i]);
}
for (int8_t i = 0; i < sizeof(eepromMntPak); i++) {
mntPak[i] = readEEPROM(eepromMntPak[i]);
}
for (int8_t i = 0; i < sizeof(eepromDtkPak); i++) {
dtkPak[i] = readEEPROM(eepromDtkPak[i]);
}
for (int8_t i = 0; i < sizeof(enableJamPakan); i++) {
enableJamPakan[i] = readEEPROM(eepromEnableJamPakan[i]);
if (enableJamPakan[i]) jmlEnablePakan++;
}
//buzzer
enableBuzzer = readEEPROM(eepromEnableBuzzer);
//Ph Meter
enablePhMeter = readEEPROM(eepromEnablePhMeter);
enableKeranAir = readEEPROM(eepromEnableKeranAir);
keranAirPerHari = readEEPROM(eepromKeranAirPerHari);
jRVHidup = readEEPROM(eepromJamRelay);
mRVHidup = readEEPROM(eepromMntRelay);
dRVHidup = readEEPROM(eepromDtkRelay);
//durasi relay valve per hari
durasiKeranAirPerHari = long(readLongFromEEPROM(eepromDurasiKeranAirPerHari));
//durasi relay valve
durasiKeranAirHidup = long(readLongFromEEPROM(eepromDurasiKeranAirHidup));
//total jumlah pakan menggunakan readlong
totalJmlPakan = long(readLongFromEEPROM(eepromTotalJmlPakan));
//Aerator
enableRelayAerator = readEEPROM(eepromEnableRelayAerator);
durasiRelayAeratorMati = readEEPROM(eepromDurasiRelayAeratorMati);
eJadwalAer = readEEPROM(eEnableJadwalAer);
enableRelayUV = readEEPROM(eepromEnableRelayUV);
for (int8_t i = 0; i < sizeof(jamUv); i++) {
jamUv[i] = readEEPROM(eJamUv[i]);
}
for (int8_t i = 0; i < sizeof(mntUv); i++) {
mntUv[i] = readEEPROM(eMntUv[i]);
}
for (int8_t i = 0; i < sizeof(dtkUv); i++) {
dtkUv[i] = readEEPROM(eDtkUv[i]);
}
for (int8_t i = 0; i < sizeof(jamAer); i++) {
jamAer[i] = readEEPROM(eJamAer[i]);
}
for (int8_t i = 0; i < sizeof(mntAer); i++) {
mntAer[i] = readEEPROM(eMntAer[i]);
}
for (int8_t i = 0; i < sizeof(dtkAer); i++) {
dtkAer[i] = readEEPROM(eDtkAer[i]);
}
}
//initialize the LED pin as an output:
//untuk memberika aksi INPUT_PULLUP ketika di tekan itu low
pinMode(btn_down_pin, INPUT_PULLUP);
pinMode(btn_up_pin, INPUT_PULLUP);
pinMode(btn_ok_pin, INPUT_PULLUP);
//inisiasi buzzer
pinMode(buz_pin, OUTPUT);
digitalWrite(buz_pin, LOW);
//relay valve inisiasi relay untuk selenoid valve
tutupKeranAir();
//inisiasi
//pinMode(relay_aerator_pin, OUTPUT);
tutupRelayAerator();
//uv
//pinMode(relay_uv_pin, OUTPUT);
matiUv();
//menu
lcd.clear();
menu = m1_dis;
}
void loop() {
//Debuging
if (Serial.available()) {
String dataToSend = Serial.readStringUntil('\n');
digitalWrite(DERE, HIGH);
RS485Serial.println(dataToSend);
Serial.println(dataToSend);
digitalWrite(DERE, LOW);
}
//RS485
receiveCommandRS485();
//
receiveCommandJD31();
//RTC
DateTime now = rtc.now();
String date = duaDigit(now.day()) + F("/") + duaDigit(now.month()) + F("/") + duaDigit(now.year());
String time = duaDigit(now.hour()) + F(":") + duaDigit(now.minute()) + F(":") + duaDigit(now.second()) + F(" ");
//run valve
if (now.hour() == jamKeranAir && now.minute() == menitKeranAir && now.second() == detikKeranAir) {
tutupKeranAir();
}
//hidup aertor
if (now.hour() == jamRelayAerator && now.minute() == menitRelayAerator && now.second() == detikRelayAerator && enableRelayAerator) {
bukaRelayAerator();
}
//relay valve terjadwal setiap jam 9 pagi
if (enableKeranAir) {
if (durasiKeranAirPerHari == 0 && now.hour() == jRVHidup && now.minute() == mRVHidup && now.second() == dRVHidup) {
bukaKeranAir(m1_dis);
durasiKeranAirPerHari = keranAirPerHari;
} else if (now.hour() == jRVHidup && now.minute() == mRVHidup && now.second() == dRVHidup) {
keranAirPerHari--;
writeEEPROM(eepromDurasiKeranAirPerHari, durasiKeranAirPerHari);
} else if (durasiKeranAirPerHari < 0) durasiKeranAirPerHari = keranAirPerHari;
}
//mati hidup ultraviolet
if (now.hour() == jamUv[1] && now.minute() == mntUv[1] && now.second() == dtkUv[1]) matiUv();
else if (now.hour() == jamUv[0] && now.minute() == mntUv[0] && now.second() == dtkUv[0]) hidupUv();
//mati hidup aerator
if (eJadwalAer && now.hour() == jamAer[1] && now.minute() == mntAer[1] && now.second() == dtkAer[1]) tutupRelayAerator();
else if (eJadwalAer && now.hour() == jamAer[0] && now.minute() == mntAer[0] && now.second() == dtkAer[0]) bukaRelayAerator();
//reepet buzzer
repetBuzzer();
repetDurasi(now.second());
// jadwal pakan dengan servo
for (int8_t t = 0; t < 4; t++) { // Jumlah jadwal pasti = 4
// Cek apakah waktu cocok DAN pastikan BELUM pernah terkirim di detik ini
if (enableJamPakan[t] && now.hour() == jamPak[t] && now.minute() == mntPak[t] && now.second() == dtkPak[t]) {
if (!sudahTerprosesJadwalPakan) {
sudahTerprosesJadwalPakan = true; // KUNCI! Agar tidak berkali-kali dalam 1 detik
// --- KODE LOOP SLAVE & SERVO ANDA DI SINI ---
for (int16_t i = 0; i < 31; i++) { // Iterasi Slave 0 sampai 30
String dataSlaveIni = "";
for (int16_t j = 0; j < 15; j++) { // Iterasi Servo 0 sampai 14
if (getDataEnableServo(i, j)) {
int16_t angle = getDataAngleServo(i, j);
int8_t pakan = getDataJmlPakan(i, j);
// PERBAIKAN: Format langsung diakhiri dengan '_' dan ditutup dengan '|' per perintah
dataSlaveIni += String(i) + "_S_" + String(j) + "_" + String(angle) + "_" + String(pakan) + "|";
}
}
// Jika ada instruksi yang terkumpul untuk Slave 'i' ini, kirimkan
if (dataSlaveIni.length() > 0) {
sendComand(dataSlaveIni);
delay(100); // Beri jeda antar Slave agar tidak terjadi tabrakan data di bus RS485
}
}
// --- AKHIR KODE LOOP SLAVE & SERVO ---
}
}
}
// BUKA KUNCI secara otomatis jika detik aktual sudah tidak sama dengan detik jadwal pakan
// Ini memastikan sistem siap untuk jadwal pakan berikutnya
if (now.second() != 0) {
sudahTerprosesJadwalPakan = false;
}
//automatic mati lampu layar
if (dktNoBacklight == 0) {
if (backlightLCD) {
pindahMenu(m1_dis, 0);
lcd.noBacklight();
backlightLCD = 0;
}
if (btnUp() || btnDown()) backlight();
} else {
//pengurangan detik untuk menonaktifkan backlight
dktNoBacklight--;
}
switch (menu) {
//Menu Display
case m1_dis:
if (idx_pos > 1) idx_pos = 0;
else if (idx_pos < 0) idx_pos = 1;
if (idx_pos == 0) {
//baris pertama
lcd.createChar(0, ICal);
lcd.setCursor(4, 0);
lcd.write(byte(0));
lcd.setCursor(5, 0);
lcd.print(date);
//baris kedua
lcd.createChar(1, IClo);
lcd.setCursor(5, 1);
lcd.write(byte(1));
lcd.setCursor(6, 1);
lcd.print(time);
//baris ke tiga
lcd.createChar(2, IUv);
lcd.setCursor(0, 2);
lcd.write(byte(2));
lcd.setCursor(1, 2);
lcd.print("M");
lcd.createChar(3, IAer);
lcd.setCursor(3, 2);
lcd.write(byte(3));
lcd.setCursor(4, 2);
hidupMatiChar(readEEPROM(eepromEnableKeranAir));
lcd.createChar(5, IPh);
lcd.setCursor(6, 2);
lcd.write(byte(5));
lcd.setCursor(7, 2);
lcd.print(getPH());
lcd.createChar(4, ITemp);
lcd.setCursor(9, 2);
lcd.write(byte(4));
lcd.setCursor(10, 2);
lcd.print("100\xDF");
lcd.createChar(6, ITds);
lcd.setCursor(15, 2);
lcd.write(byte(6));
lcd.setCursor(16, 2);
lcd.print("1500");
} else {
lcd.setCursor(0, 0);
lcd.print(F("Durasi Keran:"));
lcd.setCursor(13, 0);
lcd.print(String(durasiBuzzer) + " ");
lcd.setCursor(0, 1);
lcd.print(F("Durasi Valve:"));
lcd.setCursor(13, 1);
lcd.print(String(readEEPROM(eepromKeranAirPerHari)) + "day");
lcd.setCursor(0, 2);
lcd.print("Total Pakan:" + String(totalJmlPakan));
lcd.setCursor(0, 3);
lcd.print(F("Waktu Pakan Active:"));
lcd.setCursor(19, 3);
lcd.print(String(jmlEnablePakan));
}
updown(0);
if (btnOk()) {
backlight();
if (idx_pos == 0) pindahMenu(m1_uta, 0);
else {
idx_pos = 0;
delay(200);
lcd.clear();
}
}
break;
//Menu Utama
case m1_uta:
//pendefinisian icon memu
lcd.createChar(0, icon);
if (idx_pos > 7) idx_pos = 0;
else if (idx_pos < 0) idx_pos = 7;
if (idx_pos == 0 || idx_pos == 1 || idx_pos == 2 || idx_pos == 3) setMenu("Servo", "Keran Air", "Aerator", "pH Meter");
else if (idx_pos == 4 || idx_pos == 5 || idx_pos == 6 || idx_pos == 7) setMenu("Buzzer", "Ultraviolet", "Setting", "Kembali");
lcd.setCursor(19, idx_pos % 4);
updown(1);
if (btnOk()) {
if (idx_pos == 0) pindahMenu(m12_ser, 0);
else if (idx_pos == 1) pindahMenu(m12_ka, 0);
else if (idx_pos == 2) pindahMenu(m12_aer, 0);
else if (idx_pos == 3) pindahMenu(m12_phm, 0);
else if (idx_pos == 4) pindahMenu(m12_buz, 0);
else if (idx_pos == 5) pindahMenu(m12_uv, 0);
else if (idx_pos == 6) pindahMenu(m12_set, 0);
else pindahMenu(m1_dis, 0);
}
break;
//Servo
case m12_ser:
if (idx_pos > 32) idx_pos = 0;
else if (idx_pos < 0) idx_pos = 32;
if (idx_pos == 32) {
setMenu(F("Kembali"));
} else if (idx_pos == 0 || idx_pos == 1 || idx_pos == 2 || idx_pos == 3) {
for (int8_t i = 1; i <= 3; i++) {
if (statusSlave[i - 1]) lcd.createChar(1, IOn);
else lcd.createChar(1, IOff);
lcd.setCursor(16, i);
lcd.write(byte(1));
}
setMenu(F("Semua Servo"), F("Servo Slave 0"), F("Servo Slave 1"), F("Servo Slave 2"));
} else if (idx_pos == 28 || idx_pos == 29 || idx_pos == 30 || idx_pos == 31) {
for (int8_t i = 0; i <= 3; i++) {
if (statusSlave[i + 27]) lcd.createChar(1, IOn);
else lcd.createChar(1, IOff);
lcd.setCursor(16, i);
lcd.write(byte(1));
}
setMenu(F("Servo Slave 27"), F("Servo Slave 28"), F("Servo Slave 29"), F("Servo Slave 30"));
} else if (idx_pos % 4 == 0 && idx_pos > 3) {
for (int8_t i = 0; i <= 3; i++) {
if (statusSlave[idx_pos - 1 + i]) lcd.createChar(1, IOn);
else lcd.createChar(1, IOff);
lcd.setCursor(16, i);
lcd.write(byte(1));
}
setMenu("Servo Slave " + String(idx_pos - 1), "Servo Slave " + String(idx_pos), "Servo Slave " + String(idx_pos + 1), "Servo Slave " + String(idx_pos + 2));
} else if (idx_pos % 4 == 1 && idx_pos > 3) {
for (int8_t i = 0; i <= 3; i++) {
if (statusSlave[idx_pos - 2 + i]) lcd.createChar(1, IOn);
else lcd.createChar(1, IOff);
lcd.setCursor(16, i);
lcd.write(byte(1));
}
setMenu("Servo Slave " + String(idx_pos - 2), "Servo Slave " + String(idx_pos - 1), "Servo Slave " + String(idx_pos), "Servo Slave " + String(idx_pos + 1));
} else if (idx_pos % 4 == 2 && idx_pos > 3) {
for (int8_t i = 0; i <= 3; i++) {
if (statusSlave[idx_pos - 3 + i]) lcd.createChar(1, IOn);
else lcd.createChar(1, IOff);
lcd.setCursor(16, i);
lcd.write(byte(1));
}
setMenu("Servo Slave " + String(idx_pos - 3), "Servo Slave " + String(idx_pos - 2), "Servo Slave " + String(idx_pos - 1), "Servo Slave " + String(idx_pos));
} else if (idx_pos % 4 == 3 && idx_pos > 3) {
for (int8_t i = 0; i <= 3; i++) {
if (statusSlave[idx_pos - 4 + i]) lcd.createChar(1, IOn);
else lcd.createChar(1, IOff);
lcd.setCursor(16, i);
lcd.write(byte(1));
}
setMenu("Servo Slave " + String(idx_pos - 4), "Servo Slave " + String(idx_pos - 3), "Servo Slave " + String(idx_pos - 2), "Servo Slave " + String(idx_pos - 1));
}
lcd.setCursor(19, idx_pos % 4);
updown(1);
if (btnOk()) {
if (idx_pos >= 32) pindahMenu(m1_uta, 0);
else if (idx_pos == 0) {
int8Msg = -1; //jik minus 1 berarti nyalakan semua servo
pindahMenuKonfirmasi(m12_ser, F("Hidupkan Semua?"), 0);
} else {
int8Msg = idx_pos - 1;
posBack = idx_pos;
pindahMenu(m123_ser, 0);
}
}
break;
//Servo menu
case m123_ser:
if (idx_pos > 16) idx_pos = 0;
else if (idx_pos < 0) idx_pos = 16;
if (idx_pos == 16) setMenu(F("Kembali"));
else if (idx_pos == 0 || idx_pos == 1 || idx_pos == 2 || idx_pos == 3) {
setMenu(F("Semua Servo"), "Servo Slave " + String(posBack - 1) + ".0", "Servo Slave " + String(posBack - 1) + ".1", "Servo Slave " + String(posBack - 1) + ".2");
} else if (idx_pos % 4 == 0 && idx_pos > 3) {
setMenu("Servo Slave " + String(posBack - 1) + "." + String(idx_pos - 1), "Servo Slave " + String(posBack - 1) + "." + String(idx_pos), "Servo Slave " + String(posBack - 1) + "." + String(idx_pos + 1), "Servo Slave " + String(posBack - 1) + "." + String(idx_pos + 2));
} else if (idx_pos % 4 == 1 && idx_pos > 3) {
setMenu("Servo Slave " + String(posBack - 1) + "." + String(idx_pos - 2), "Servo Slave " + String(posBack - 1) + "." + String(idx_pos - 1), "Servo Slave " + String(posBack - 1) + "." + String(idx_pos), "Servo Slave " + String(posBack - 1) + "." + String(idx_pos + 1));
} else if (idx_pos % 4 == 2 && idx_pos > 3) {
setMenu("Servo Slave " + String(posBack - 1) + "." + String(idx_pos - 3), "Servo Slave " + String(posBack - 1) + "." + String(idx_pos - 2), "Servo Slave " + String(posBack - 1) + "." + String(idx_pos - 1), "Servo Slave " + String(posBack - 1) + "." + String(idx_pos));
} else if (idx_pos % 4 == 3 && idx_pos > 3) {
setMenu("Servo Slave " + String(posBack - 1) + "." + String(idx_pos - 4), "Servo Slave " + String(posBack - 1) + "." + String(idx_pos - 3), "Servo Slave " + String(posBack - 1) + "." + String(idx_pos - 2), "Servo Slave " + String(posBack - 1) + "." + String(idx_pos - 1));
}
lcd.setCursor(19, idx_pos % 4);
updown(1);
if (btnOk()) {
if (idx_pos == 16) pindahMenu(m12_ser, posBack);
else if (idx_pos == 0) {
int8Msg = -1; //jik minus 1 berarti nyalakan semua servo
pindahMenuKonfirmasi(m123_ser, F("Semua Servo?"), 0);
} else {
int8Msg = idx_pos - 1;
pindahMenuKonfirmasi(m123_ser, "Hidup Slave " + String(posBack - 1) + "." + String(idx_pos - 1) + "?", 0);
}
}
break;
//Keran Air
case m12_ka:
if (idx_pos > 2) idx_pos = 0;
else if (idx_pos < 0) idx_pos = 2;
lcd.setCursor(0, 0);
lcd.write(byte(0));
lcd.print(F("Keran Air"));
lcd.setCursor(18, 0);
lcd.print(F("X"));
lcd.setCursor(0, 1);
lcd.print(F("Status:Tutup"));
lcd.setCursor(0, 3);
lcd.print((__FlashStringHelper*)cHidup);
lcd.setCursor(9, 3);
lcd.print((__FlashStringHelper*)cMati);
if (idx_pos == 0) lcd.setCursor(pos_matiHidup[idx_pos], 0);
else lcd.setCursor(pos_matiHidup[idx_pos], 3);
updown(1);
if (btnOk()) {
if (idx_pos == 0) pindahMenu(m1_uta, 1);
else if (idx_pos == 1) pindahMenuKonfirmasi(m12_ka, F("Keran Dibuka?"), 0);
else pindahMenuKonfirmasi(m12_ka, F("Keran Ditutup?"), 0);
delay(200);
lcd.clear();
}
break;
//Aerator
case m12_aer:
if (idx_pos > 2) idx_pos = 0;
else if (idx_pos < 0) idx_pos = 2;
lcd.setCursor(0, 0);
lcd.write(byte(0));
lcd.print(F("Aerator Air"));
lcd.setCursor(18, 0);
lcd.print(F("X"));
lcd.setCursor(0, 1);
lcd.print(F("Status:OFF"));
lcd.setCursor(0, 3);
lcd.print((__FlashStringHelper*)cHidup);
lcd.setCursor(9, 3);
lcd.print((__FlashStringHelper*)cMati);
if (idx_pos == 0) lcd.setCursor(pos_matiHidup[idx_pos], 0);
else lcd.setCursor(pos_matiHidup[idx_pos], 3);
updown(1);
if (btnOk()) {
if (idx_pos == 0) pindahMenu(m1_uta, 2);
else if (idx_pos == 1) pindahMenuKonfirmasi(m12_aer, F("Aerator Hidup?"), 0);
else pindahMenuKonfirmasi(m12_aer, F("Aerator Mati?"), 0);
delay(200);
lcd.clear();
}
break;
//PH Meter
case m12_phm:
if (idx_pos > 2) idx_pos = 0;
else if (idx_pos < 0) idx_pos = 2;
lcd.setCursor(0, 0);
lcd.write(byte(0));
lcd.print(F("pH Meter"));
lcd.setCursor(18, 0);
lcd.print(F("X"));
lcd.setCursor(0, 1);
if (enablePhMeter) lcd.print(F("Status:Hidup"));
else lcd.print(F("Status:Mati"));
lcd.setCursor(0, 3);
lcd.print((__FlashStringHelper*)cHidup);
lcd.setCursor(9, 3);
lcd.print((__FlashStringHelper*)cMati);
if (idx_pos == 0) lcd.setCursor(pos_matiHidup[idx_pos], 0);
else lcd.setCursor(pos_matiHidup[idx_pos], 3);
updown(1);
if (btnOk()) {
if (idx_pos == 0) pindahMenu(m1_uta, 3);
else if (idx_pos == 1) pindahMenuKonfirmasi(m12_phm, F("pH meter Hidup?"), 0);
else if (idx_pos == 2) pindahMenuKonfirmasi(m12_phm, F("pH meter Mati?"), 0);
delay(200);
lcd.clear();
}
break;
//Menu Buzzer
case m12_buz:
if (idx_pos > 2) idx_pos = 0;
else if (idx_pos < 0) idx_pos = 2;
lcd.setCursor(0, 0);
lcd.write(byte(0));
lcd.print(F("Buzzer"));
lcd.setCursor(18, 0);
lcd.print(F("X"));
lcd.setCursor(0, 1);
if (enableBuzzer) lcd.print(F("Status:Hidup"));
else lcd.print(F("Status:Mati"));
lcd.setCursor(0, 3);
lcd.print((__FlashStringHelper*)cHidup);
lcd.setCursor(9, 3);
lcd.print((__FlashStringHelper*)cMati);
if (idx_pos == 0) lcd.setCursor(pos_matiHidup[idx_pos], 0);
else lcd.setCursor(pos_matiHidup[idx_pos], 3);
updown(1);
if (btnOk()) {
if (idx_pos == 0) pindahMenu(m1_uta, 4);
else if (idx_pos == 1) pindahMenuKonfirmasi(m12_buz, F("Buzzer Hidup?"), 0);
else pindahMenuKonfirmasi(m12_buz, F("Buzzer Mati?"), 0);
delay(200);
lcd.clear();
}
break;
//Menu Ultra Violet
case m12_uv:
if (idx_pos > 2) idx_pos = 0;
else if (idx_pos < 0) idx_pos = 2;
lcd.setCursor(0, 0);
lcd.write(byte(0));
lcd.print(F("Ultra Violet"));
lcd.setCursor(18, 0);
lcd.print(F("X"));
lcd.setCursor(0, 1);
if (enableRelayUV) lcd.print(F("Status:Hidup"));
else lcd.print(F("Status:Mati"));
lcd.setCursor(0, 3);
lcd.print((__FlashStringHelper*)cHidup);
lcd.setCursor(9, 3);
lcd.print((__FlashStringHelper*)cMati);
if (idx_pos == 0) lcd.setCursor(pos_matiHidup[idx_pos], 0);
else lcd.setCursor(pos_matiHidup[idx_pos], 3);
updown(1);
if (btnOk()) {
if (idx_pos == 0) pindahMenu(m1_uta, 5);
else if (idx_pos == 1) pindahMenuKonfirmasi(m12_uv, F("UV Hidup?"), 0);
else pindahMenuKonfirmasi(m12_uv, F("UV Mati?"), 0);
delay(200);
lcd.clear();
}
break;
//Setup
case m12_set:
if (idx_pos > 8) idx_pos = 0;
else if (idx_pos < 0) idx_pos = 8;
if (idx_pos == 0 || idx_pos == 1 || idx_pos == 2 || idx_pos == 3) setMenu(F("Jadwal Pakan"), F("Servo Slave"), F("Keran Air"), F("Aerator"));
else if (idx_pos == 4 || idx_pos == 5 || idx_pos == 6 || idx_pos == 7) setMenu(F("Ultraviolet"), F("Tanggal"), F("Waktu"), F("Reset"));
else if (idx_pos == 8) setMenu(F("Kembali"));
lcd.setCursor(19, idx_pos % 4);
updown(1);
if (btnOk()) {
if (idx_pos == 0) pindahMenu(m123_sjdp, 0);
else if (idx_pos == 1) pindahMenu(m12_sse, 0);
else if (idx_pos == 2) pindahMenu(m123_ska, 0);
else if (idx_pos == 3) pindahMenu(m123_sae, 0);
else if (idx_pos == 4) pindahMenu(m123_suv, 0);
else if (idx_pos == 5) pindahMenu(m123_stg, 0);
else if (idx_pos == 6) {
charMsg = w;
pindahMenu(m1_swk, 0);
} else if (idx_pos == 7) pindahMenuKonfirmasi(m12_set, F("Reset Default?"), 9);
else if (idx_pos == 8) pindahMenu(m1_uta, 6);
}
break;
case m123_sjdp:
if (idx_pos > 4) idx_pos = 0;
else if (idx_pos < 0) idx_pos = 4;
if (idx_pos == 0 || idx_pos == 1 || idx_pos == 2 || idx_pos == 3) setMenu(F("Waktu Pakan 1"), F("Waktu Pakan 2"), F("Waktu Pakan 3"), F("Waktu Pakan 4"));
else {
lcd.setCursor(0, 0);
lcd.write(byte(0));
lcd.print(F("Kembali"));
}
lcd.setCursor(19, idx_pos % 4);
updown(1);
if (btnOk()) {
if (idx_pos == 0) {
charMsg = wp0;
pindahMenu(m1_swk, 0);
} else if (idx_pos == 1) {
charMsg = wp1;
pindahMenu(m1_swk, 0);
} else if (idx_pos == 2) {
charMsg = wp2;
pindahMenu(m1_swk, 0);
} else if (idx_pos == 3) {
charMsg = wp3;
pindahMenu(m1_swk, 0);
} else pindahMenu(m12_set, 0);
}
break;
//Setting Servo Slave
case m12_sse:
if (idx_pos > 31) idx_pos = 0;
else if (idx_pos < 0) idx_pos = 31;
if (idx_pos > 27) setMenu(F("Servo Slave 28"), F("Servo Slave 29"), F("Servo Slave 30"), F("Kembali"));
else if (idx_pos % 4 == 0) {
setMenu("Servo Slave " + String(idx_pos), "Servo Slave " + String(idx_pos + 1), "Servo Slave " + String(idx_pos + 2), "Servo Slave " + String(idx_pos + 3));
} else if (idx_pos % 4 == 1) {
setMenu("Servo Slave " + String(idx_pos - 1), "Servo Slave " + String(idx_pos), "Servo Slave " + String(idx_pos + 1), "Servo Slave " + String(idx_pos + 2));
} else if (idx_pos % 4 == 2) {
setMenu("Servo Slave " + String(idx_pos - 2), "Servo Slave " + String(idx_pos - 1), "Servo Slave " + String(idx_pos), "Servo Slave " + String(idx_pos + 1));
} else if (idx_pos % 4 == 3) {
setMenu("Servo Slave " + String(idx_pos - 3), "Servo Slave " + String(idx_pos - 2), "Servo Slave " + String(idx_pos - 1), "Servo Slave " + String(idx_pos));
}
lcd.setCursor(19, idx_pos % 4);
updown(1);
if (btnOk()) {
if (idx_pos == 31) pindahMenu(m12_set, 1);
else {
int8Msg = idx_pos;
pindahMenu(m123_sse, 0);
}
}
break;
//Setting Servo Slave
case m123_sse:
if (idx_pos > 15) idx_pos = 0;
else if (idx_pos < 0) idx_pos = 15;
if (idx_pos > 11) setMenu("Servo Slave " + String(int8Msg) + ".12", "Servo Slave " + String(int8Msg) + ".13", "Servo Slave " + String(int8Msg) + ".14", "Kembali");
else if (idx_pos % 4 == 0) {
setMenu("Servo Slave " + String(int8Msg) + "." + String(idx_pos), "Servo Slave " + String(int8Msg) + "." + String(idx_pos + 1), "Servo Slave " + String(int8Msg) + "." + String(idx_pos + 2), "Servo Slave " + String(int8Msg) + "." + String(idx_pos + 3));
} else if (idx_pos % 4 == 1) {
setMenu("Servo Slave " + String(int8Msg) + "." + String(idx_pos - 1), "Servo Slave " + String(int8Msg) + "." + String(idx_pos), "Servo Slave " + String(int8Msg) + "." + String(idx_pos + 1), "Servo Slave " + String(int8Msg) + "." + String(idx_pos + 2));
} else if (idx_pos % 4 == 2) {
setMenu("Servo Slave " + String(int8Msg) + "." + String(idx_pos - 2), "Servo Slave " + String(int8Msg) + "." + String(idx_pos - 1), "Servo Slave " + String(int8Msg) + "." + String(idx_pos), "Servo Slave " + String(int8Msg) + "." + String(idx_pos + 1));
} else if (idx_pos % 4 == 3) {
setMenu("Servo Slave " + String(int8Msg) + "." + String(idx_pos - 3), "Servo Slave " + String(int8Msg) + "." + String(idx_pos - 2), "Servo Slave " + String(int8Msg) + "." + String(idx_pos - 1), "Servo Slave " + String(int8Msg) + "." + String(idx_pos));
}
lcd.setCursor(19, idx_pos % 4);
updown(1);
if (btnOk()) {
if (idx_pos == 15) pindahMenu(m12_sse, int8Msg);
else {
baris = int8Msg;
kolom = idx_pos;
pindahMenu(m123_vss, 0);
}
}
break;
//view seting servo
case m123_vss:
if (idx_pos > 3) idx_pos = 0;
else if (idx_pos < 0) idx_pos = 3;
lcd.setCursor(0, 0);
lcd.write(byte(0));
lcd.print(F("Servo Slave "));
lcd.setCursor(13, 0);
lcd.print(String(baris) + "." + String(kolom));
lcd.setCursor(18, 0);
lcd.print(F("X"));
lcd.setCursor(0, 1);
lcd.print(F("Jumlah Pakan:"));
lcd.setCursor(13, 1);
lcd.print(String(getDataJmlPakan(baris, kolom)) + "x");
lcd.setCursor(0, 2);
lcd.print(F("Angle:"));
lcd.print(getDataAngleServo(baris, kolom) + String((char)223));
lcd.setCursor(0, 3);
lcd.print(F("Automatic:"));
lcd.setCursor(10, 3);
hidupMatiString(getDataEnableServo(baris, kolom));
if (idx_pos == 0) lcd.setCursor(pos_servo[idx_pos], 0);
else if (idx_pos == 1) lcd.setCursor(pos_servo[idx_pos], 1);
else if (idx_pos == 2) lcd.setCursor(pos_servo[idx_pos], 2);
else lcd.setCursor(pos_servo[idx_pos], 3);
updown(1);
if (btnOk()) {
if (idx_pos == 0) pindahMenu(m123_sse, kolom);
else if (idx_pos == 1) {
int8Msg = getDataJmlPakan(baris, kolom);
pindahMenu(m123_sjp, 0);
} else if (idx_pos == 2) {
int16Msg = getDataAngleServo(baris, kolom);
pindahMenu(m123_sss, kolom);
} else {
updateDanSimpanEnableServo(baris, kolom, !getDataEnableServo(baris, kolom));
}
}
break;
//Setup Seting Servo
case m123_sss:
if (int16Msg > 180) int16Msg = 0;
else if (int16Msg < 0) int16Msg = 180;
lcd.setCursor(0, 0);
lcd.print(F("Angle (0-180)"));
lcd.setCursor(0, 1);
lcd.print("Set: " + String(int16Msg));
if (btnUp()) {
int16Msg++;
delay(200);
lcd.clear();
} else if (btnDown()) {
int16Msg--;
delay(200);
lcd.clear();
}
if (btnOk()) {
updateDanSimpanAngleServo(baris, kolom, int16Msg);
pindahMenu(m123_vss, 1);
}
break;
//Seting Jumlah Pakan
case m123_sjp:
if (int8Msg > 25) int8Msg = 1;
else if (int8Msg < 1) int8Msg = 25;
lcd.setCursor(0, 0);
lcd.write(byte(0));
lcd.print(F("Jumlah Pakan"));
lcd.setCursor(0, 1);
lcd.print("Set: " + String(int8Msg));
if (btnUp()) {
int8Msg++;
delay(200);
lcd.clear();
} else if (btnDown()) {
int8Msg--;
delay(200);
lcd.clear();
}
if (btnOk()) {
updateDanSimpanJmlPakan(baris, kolom, int8Msg);
pindahMenu(m123_vss, 1);
}
break;
//Seting Keran Air
case m123_ska:
if (idx_pos > 6) idx_pos = 0;
else if (idx_pos < 0) idx_pos = 6;
if (idx_pos >= 0 && idx_pos <= 3) {
lcd.setCursor(0, 0);
lcd.write(byte(0));
lcd.print(F("Keran Air"));
lcd.setCursor(18, 0);
lcd.print(F("X"));
lcd.setCursor(0, 1);
lcd.print(F("Status:"));
hidupMatiString(enableKeranAir);
lcd.setCursor(0, 2);
lcd.print("Durasi:" + String(keranAirPerHari) + "hari");
lcd.setCursor(0, 3);
lcd.print(F("Lama:"));
lcd.print(String(durasiKeranAirHidup) + "Detik");
} else {
lcd.setCursor(0, 0);
lcd.write(byte(0));
lcd.print(F("Keran Air Hidup"));
lcd.setCursor(0, 1);
lcd.print(F("Set:"));
waktu[0] = jRVHidup;
waktu[1] = mRVHidup;
waktu[2] = dRVHidup;
lcd.setCursor(5, 1);
lcd.print(String(waktu[0]));
lcd.setCursor(8, 1);
lcd.print(String(waktu[1]));
lcd.setCursor(11, 1);
lcd.print(String(waktu[2]));
lcd.setCursor(0, 2);
lcd.print(F("Posisi Slave:"));
lcd.setCursor(13, 2);
lcd.print(F("0"));
}
if (idx_pos == 0) lcd.setCursor(pos_keranAir[idx_pos], 0);
else if (idx_pos == 1) lcd.setCursor(pos_keranAir[idx_pos], 1);
else if (idx_pos == 2) lcd.setCursor(pos_keranAir[idx_pos], 2);
else if (idx_pos == 3) lcd.setCursor(pos_keranAir[idx_pos], 3);
else lcd.setCursor(pos_keranAir[idx_pos], 1);
updown(1);
if (btnOk()) {
if (idx_pos == 0) pindahMenu(m12_set, 3);
else if (idx_pos == 1) pindahMenu(m1234_skaph, 0);
else if (idx_pos == 2) pindahMenu(m1234_sdka, 0);
else if (idx_pos == 3) {
if (enableKeranAir) {
enableKeranAir = false;
writeEEPROM(eepromEnableKeranAir, enableKeranAir);
pesan(m123_ska, 3, F("Jadwal Keran Air"), (__FlashStringHelper*)cMati);
} else {
enableKeranAir = true;
writeEEPROM(eepromEnableKeranAir, enableKeranAir);
pesan(m123_ska, 3, F("Jadwal Keran Air"), (__FlashStringHelper*)cHidup);
}
delay(200);
lcd.clear();
} else if (idx_pos == 4) {
charMsg = m123_ska;
pindahMenu(m12_svwk, 0);
} else if (idx_pos == 5) {
charMsg = m123_ska;
pindahMenu(m12_svwk, 1);
} else if (idx_pos == 6) {
charMsg = m123_ska;
pindahMenu(m12_svwk, 2);
}
}
break;
//setup aerator
case m123_sae:
if (idx_pos > 8) idx_pos = 0;
else if (idx_pos < 0) idx_pos = 8;
if (idx_pos < 3) {
lcd.setCursor(0, 0);
lcd.write(byte(0));
lcd.print(F("Aerator"));
lcd.setCursor(18, 0);
lcd.print(F("X"));
lcd.setCursor(0, 1);
lcd.print(F("Status:"));
lcd.setCursor(7, 1);
hidupMatiString(eJadwalAer);
lcd.setCursor(0, 2);
lcd.print(F("Durasi Mati:"));
lcd.setCursor(12, 2);
lcd.print(String(durasiRelayAeratorMati));
lcd.setCursor(0, 3);
lcd.print(F("Posisi Slave:"));
lcd.setCursor(13, 3);
lcd.print(F("0"));
} else if (idx_pos > 2) {
lcd.setCursor(0, 0);
lcd.write(byte(0));
lcd.print(F("Aerator Mati"));
lcd.setCursor(0, 1);
lcd.print(F("Set: "));
waktu[0] = jamAer[0];
waktu[1] = mntAer[0];
waktu[2] = dtkAer[0];
lcd.setCursor(5, 1);
lcd.print(String(waktu[0]));
lcd.setCursor(8, 1);
lcd.print(String(waktu[1]));
lcd.setCursor(11, 1);
lcd.print(String(waktu[2]));
lcd.setCursor(0, 2);
lcd.write(byte(0));
lcd.print(F("Aerator Hidup"));
lcd.setCursor(0, 3);
lcd.print(F("Set: "));
waktu[0] = jamAer[0];
waktu[1] = mntAer[0];
waktu[2] = dtkAer[0];
lcd.setCursor(5, 3);
lcd.print(String(waktu[0]));
lcd.setCursor(8, 3);
lcd.print(String(waktu[1]));
lcd.setCursor(11, 3);
lcd.print(String(waktu[2]));
}
if (idx_pos == 0) lcd.setCursor(pos_setAerator[idx_pos], 0);
else if (idx_pos == 1) lcd.setCursor(pos_setAerator[idx_pos], 1);
else if (idx_pos == 2) lcd.setCursor(pos_setAerator[idx_pos], 2);
else if (idx_pos > 2 && idx_pos < 6) lcd.setCursor(pos_setAerator[idx_pos], 1);
else lcd.setCursor(pos_setAerator[idx_pos], 3);
updown(1);
if (btnOk()) {
if (idx_pos == 0) {
pindahMenu(m12_set, 4);
} else if (idx_pos == 1) {
pindahMenu(m1234_sdkam, 0);
} else if (idx_pos == 2) {
if (eJadwalAer) {
eJadwalAer = false;
writeEEPROM(eEnableJadwalAer, eJadwalAer);
pesan(m123_sae, 2, F("Jadwal Aerator"), (__FlashStringHelper*)cMati);
} else {
eJadwalAer = true;
writeEEPROM(eEnableJadwalAer, eJadwalAer);
pesan(m123_sae, 2, F("Jadwal Aerator"), (__FlashStringHelper*)cHidup);
}
delay(200);
lcd.clear();
} else if (idx_pos == 3) {
charMsg = m123_sae;
int8Msg = 0;
pindahMenu(m12_svwk, 0);
} else if (idx_pos == 4) {
charMsg = m123_sae;
int8Msg = 0;
pindahMenu(m12_svwk, 1);
} else if (idx_pos == 5) {
charMsg = m123_sae;
int8Msg = 0;
pindahMenu(m12_svwk, 2);
} else if (idx_pos == 6) {
charMsg = m123_sae;
int8Msg = 1;
pindahMenu(m12_svwk, 0);
} else if (idx_pos == 7) {
charMsg = m123_sae;
int8Msg = 1;
pindahMenu(m12_svwk, 1);
} else if (idx_pos == 8) {
charMsg = m123_sae;
int8Msg = 1;
pindahMenu(m12_svwk, 2);
}
delay(200);
lcd.clear();
}
break;
//Setup Ultraviolet
case m123_suv:
if (idx_pos > 7) idx_pos = 0;
else if (idx_pos < 0) idx_pos = 7;
if (idx_pos >= 0 && idx_pos < 5) {
lcd.setCursor(0, 0);
lcd.write(byte(0));
lcd.print(F("Ultrviolt"));
lcd.setCursor(18, 0);
lcd.print(F("X"));
lcd.setCursor(0, 1);
lcd.print(F("Status:"));
hidupMatiString(enableKeranAir);
lcd.setCursor(0, 2);
lcd.write(byte(0));
lcd.print(F("Ultrviolt Hidup"));
lcd.setCursor(0, 3);
lcd.print(F("Set: "));
waktu[0] = jamUv[0];
waktu[1] = mntUv[0];
waktu[2] = dtkUv[0];
lcd.setCursor(5, 3);
lcd.print(String(waktu[0]));
lcd.setCursor(8, 3);
lcd.print(String(waktu[1]));
lcd.setCursor(11, 3);
lcd.print(String(waktu[2]));
} else {
lcd.setCursor(0, 0);
lcd.write(byte(0));
lcd.print(F("Ultrviolt Mati"));
lcd.setCursor(0, 1);
lcd.print(F("Set: "));
waktu[0] = jamUv[1];
waktu[1] = mntUv[1];
waktu[2] = dtkUv[1];
lcd.setCursor(5, 1);
lcd.print(String(waktu[0]));
lcd.setCursor(8, 1);
lcd.print(String(waktu[1]));
lcd.setCursor(11, 1);
lcd.print(String(waktu[2]));
lcd.setCursor(0, 2);
lcd.print(F("Posisi Slave:"));
lcd.setCursor(13, 2);
lcd.print(F("0"));
}
if (idx_pos == 0) lcd.setCursor(p_setUv[idx_pos], 0);
else if (idx_pos == 1 || idx_pos > 4) lcd.setCursor(p_setUv[idx_pos], 1);
else lcd.setCursor(p_setUv[idx_pos], 3);
updown(1);
if (btnOk()) {
if (idx_pos == 0) pindahMenu(m12_set, 5);
else if (idx_pos == 1) {
if (enableRelayUV) {
enableRelayUV = false;
writeEEPROM(eepromEnableRelayUV, enableRelayUV);
pesan(m123_suv, 1, F("Jadwal UV"), (__FlashStringHelper*)cMati);
} else {
enableRelayUV = true;
writeEEPROM(eepromEnableRelayUV, enableRelayUV);
pesan(m123_suv, 1, F("Jadwal UV"), (__FlashStringHelper*)cHidup);
}
delay(200);
lcd.clear();
} else if (idx_pos == 2) {
charMsg = m123_suv;
int8Msg = 0;
pindahMenu(m12_svwk, 0);
} else if (idx_pos == 3) {
charMsg = m123_suv;
int8Msg = 0;
pindahMenu(m12_svwk, 1);
} else if (idx_pos == 4) {
charMsg = m123_suv;
int8Msg = 0;
pindahMenu(m12_svwk, 2);
} else if (idx_pos == 5) {
charMsg = m123_suv;
int8Msg = 1;
pindahMenu(m12_svwk, 0);
} else if (idx_pos == 6) {
charMsg = m123_suv;
int8Msg = 1;
pindahMenu(m12_svwk, 1);
} else if (idx_pos == 7) {
charMsg = m123_suv;
int8Msg = 1;
pindahMenu(m12_svwk, 2);
}
}
break;
case m123_stg:
if (idx_pos > 3) idx_pos = 0;
else if (idx_pos < 0) idx_pos = 3;
tanggal[0] = now.day();
tanggal[1] = now.month();
tanggal[2] = now.year();
lcd.setCursor(0, 0);
lcd.write(byte(0));
lcd.print(F("Tanggal"));
lcd.setCursor(18, 0);
lcd.print(F("X"));
lcd.setCursor(0, 1);
lcd.print(F("Set:"));
lcd.setCursor(4, 1);
lcd.print(duaDigit(tanggal[0]));
lcd.setCursor(7, 1);
lcd.print(duaDigit(tanggal[1]));
lcd.setCursor(10, 1);
lcd.print(duaDigit(tanggal[2]));
if (idx_pos == 0) lcd.setCursor(pos_tanggal[idx_pos], 0);
else lcd.setCursor(pos_tanggal[idx_pos], 1);
updown(1);
if (btnOk()) {
if (idx_pos == 0) pindahMenu(m12_set, 6);
else if (idx_pos == 1) pindahMenu(m1234_svtg, 0);
else if (idx_pos == 2) pindahMenu(m1234_svtg, 1);
else if (idx_pos == 3) pindahMenu(m1234_svtg, 2);
delay(200);
lcd.clear();
}
break;
case m1_swk:
if (idx_pos > 3 && charMsg == w) idx_pos = 0;
else if (idx_pos < 0 && charMsg == w) idx_pos = 3;
if (idx_pos > 4) idx_pos = 0;
else if (idx_pos < 0) idx_pos = 4;
lcd.setCursor(18, 0);
lcd.print(F("X"));
if (charMsg == w) lcd.setCursor(0, 1);
else lcd.setCursor(0, 2);
lcd.print(F("Set: "));
switch (charMsg) {
case w:
lcd.setCursor(0, 0);
lcd.write(byte(0));
lcd.print(F("Waktu"));
waktu[0] = now.hour();
waktu[1] = now.minute();
waktu[2] = now.second();
break;
case wp0:
lcd.setCursor(0, 0);
lcd.write(byte(0));
lcd.print(F("Jam Pakan 1"));
lcd.setCursor(0, 1);
lcd.print(F("Status:"));
lcd.setCursor(7, 1);
hidupMatiString(enableJamPakan[0]);
waktu[0] = jamPak[0];
waktu[1] = mntPak[0];
waktu[2] = dtkPak[0];
break;
case wp1:
lcd.setCursor(0, 0);
lcd.write(byte(0));
lcd.print(F("Jam Pakan 2"));
lcd.setCursor(0, 1);
lcd.print(F("Status:"));
lcd.setCursor(7, 1);
hidupMatiString(enableJamPakan[1]);
waktu[0] = jamPak[1];
waktu[1] = mntPak[1];
waktu[2] = dtkPak[1];
break;
case wp2:
lcd.setCursor(0, 0);
lcd.write(byte(0));
lcd.print(F("Jam Pakan 3"));
lcd.setCursor(0, 1);
lcd.print(F("Status:"));
lcd.setCursor(7, 1);
hidupMatiString(enableJamPakan[2]);
waktu[0] = jamPak[2];
waktu[1] = mntPak[2];
waktu[2] = dtkPak[2];
break;
case wp3:
lcd.setCursor(0, 0);
lcd.write(byte(0));
lcd.print(F("Jam Pakan 4"));
lcd.setCursor(0, 1);
lcd.print(F("Status:"));
lcd.setCursor(7, 1);
hidupMatiString(enableJamPakan[3]);
waktu[0] = jamPak[3];
waktu[1] = mntPak[3];
waktu[2] = dtkPak[3];
break;
}
if (charMsg == w) lcd.setCursor(5, 1);
else lcd.setCursor(5, 2);
lcd.print(duaDigit(waktu[0]));
if (charMsg == w) lcd.setCursor(8, 1);
else lcd.setCursor(8, 2);
lcd.print(duaDigit(waktu[1]));
if (charMsg == w) lcd.setCursor(11, 1);
else lcd.setCursor(11, 2);
lcd.print(duaDigit(waktu[2]));
if (idx_pos == 0) lcd.setCursor(pos_waktu[idx_pos], 0);
else if (idx_pos == 4) lcd.setCursor(pos_waktu[idx_pos], 1);
else if (charMsg == w) lcd.setCursor(pos_waktu[idx_pos], 1);
else lcd.setCursor(pos_waktu[idx_pos], 2);
updown(1);
if (btnOk()) {
if (idx_pos == 0) {
switch (charMsg) {
case w:
pindahMenu(m12_set, 7);
break;
case wp0:
pindahMenu(m123_sjdp, 0);
break;
case wp1:
pindahMenu(m123_sjdp, 1);
break;
case wp2:
pindahMenu(m123_sjdp, 2);
break;
case wp3:
pindahMenu(m123_sjdp, 3);
break;
}
} else if (idx_pos > 0 && idx_pos < 4) {
pindahMenu(m12_svwk, idx_pos - 1);
} else {
switch (charMsg) {
case wp0:
if (enableJamPakan[0]) {
enableJamPakan[0] = false;
pesan(m1_swk, 4, F("Jadwal Pakan 1"), (__FlashStringHelper*)cMati);
} else {
enableJamPakan[0] = true;
pesan(m1_swk, 4, F("Jadwal Pakan 1"), (__FlashStringHelper*)cHidup);
}
Serial.println(String(enableJamPakan[0]));
writeEEPROM(eepromEnableJamPakan[0], enableJamPakan[0]);
break;
case wp1:
if (enableJamPakan[1]) {
enableJamPakan[1] = false;
jmlEnablePakan--;
pesan(m1_swk, 4, F("Jadwal Pakan 2"), (__FlashStringHelper*)cMati);
} else {
enableJamPakan[1] = true;
jmlEnablePakan++;
pesan(m1_swk, 4, F("Jadwal Pakan 2"), (__FlashStringHelper*)cHidup);
}
writeEEPROM(eepromEnableJamPakan[1], enableJamPakan[1]);
break;
case wp2:
if (enableJamPakan[2]) {
enableJamPakan[2] = false;
jmlEnablePakan--;
pesan(m1_swk, 4, F("Jadwal Pakan 3"), (__FlashStringHelper*)cMati);
} else {
enableJamPakan[2] = true;
jmlEnablePakan++;
pesan(m1_swk, 4, F("Jadwal Pakan 3"), (__FlashStringHelper*)cHidup);
}
writeEEPROM(eepromEnableJamPakan[2], enableJamPakan[2]);
break;
case wp3:
if (enableJamPakan[3]) {
enableJamPakan[3] = false;
jmlEnablePakan--;
pesan(m1_swk, 4, F("Jadwal Pakan 4"), (__FlashStringHelper*)cMati);
} else {
enableJamPakan[3] = true;
jmlEnablePakan++;
pesan(m1_swk, 4, F("Jadwal Pakan 4"), (__FlashStringHelper*)cHidup);
}
writeEEPROM(eepromEnableJamPakan[3], enableJamPakan[3]);
break;
}
}
delay(200);
lcd.clear();
}
break;
case m1234_sdkam:
if (durasiRelayAeratorMati > 3600) durasiRelayAeratorMati = 10;
else if (durasiRelayAeratorMati < 10) durasiRelayAeratorMati = 3600;
lcd.setCursor(0, 0);
lcd.print(F("Durasi (0-3600)"));
lcd.setCursor(0, 1);
lcd.print(F("Set: "));
lcd.setCursor(5, 1);
lcd.print(String(durasiRelayAeratorMati));
if (btnUp()) {
durasiRelayAeratorMati = durasiRelayAeratorMati + 10;
delay(200);
lcd.clear();
} else if (btnDown()) {
durasiRelayAeratorMati = durasiRelayAeratorMati - 10;
delay(200);
lcd.clear();
}
if (btnOk()) {
writeLongIntoEEPROM(eepromDurasiRelayAeratorMati, durasiRelayAeratorMati);
pindahMenu(m123_sae, 1);
delay(200);
lcd.clear();
}
break;
case m1234_skaph:
if (keranAirPerHari > 99) keranAirPerHari = 0;
else if (keranAirPerHari < 0) keranAirPerHari = 99;
lcd.setCursor(0, 0);
lcd.print(F("(0-99) perhari"));
lcd.setCursor(0, 1);
lcd.print(F("Set: "));
lcd.setCursor(5, 1);
lcd.print(keranAirPerHari);
if (btnUp()) {
keranAirPerHari++;
delay(200);
lcd.clear();
} else if (btnDown()) {
keranAirPerHari--;
delay(200);
lcd.clear();
}
if (btnOk()) {
writeLongIntoEEPROM(eepromKeranAirPerHari, keranAirPerHari);
pindahMenu(m123_ska, 1);
delay(200);
lcd.clear();
}
break;
case m1234_sdka:
if (durasiKeranAirHidup > 3600) durasiKeranAirHidup = 10;
else if (durasiKeranAirHidup < 10) durasiKeranAirHidup = 3600;
lcd.setCursor(0, 0);
lcd.print(F("Durasi (0-3600)"));
lcd.setCursor(0, 1);
lcd.print(F("Set: "));
lcd.setCursor(5, 1);
lcd.print(String(durasiKeranAirHidup));
if (btnUp()) {
durasiKeranAirHidup = durasiKeranAirHidup + 10;
delay(200);
lcd.clear();
} else if (btnDown()) {
durasiKeranAirHidup = durasiKeranAirHidup - 10;
delay(200);
lcd.clear();
}
if (btnOk()) {
writeLongIntoEEPROM(eepromDurasiKeranAirHidup, durasiKeranAirHidup);
pindahMenu(m123_ska, 2);
delay(200);
lcd.clear();
}
break;
case m1234_svtg:
lcd.setCursor(0, 0);
lcd.print(date);
lcd.setCursor(0, 1);
lcd.print(F("Set "));
lcd.setCursor(4, 1);
lcd.print(urutan_tanggal[idx_pos]);
lcd.setCursor(9, 1);
lcd.print(duaDigit(tanggal[idx_pos]));
if (btnUp()) {
tanggal[idx_pos]++;
delay(200);
lcd.clear();
} else if (btnDown()) {
tanggal[idx_pos]--;
delay(200);
lcd.clear();
}
if (idx_pos == 0) {
if (tanggal[idx_pos] > 31) {
tanggal[idx_pos] = 1;
} else if (tanggal[idx_pos] < 1) {
tanggal[idx_pos] = 31;
}
} else if (idx_pos == 1) {
if (tanggal[idx_pos] > 12) {
tanggal[idx_pos] = 1;
} else if (tanggal[idx_pos] < 1) {
tanggal[idx_pos] = 12;
}
} else {
if (tanggal[idx_pos] < 0) {
tanggal[idx_pos] = 0;
}
}
if (btnOk()) {
rtc.adjust(DateTime(tanggal[2], tanggal[1], tanggal[0], now.hour(), now.minute(), now.second()));
if (idx_pos == 0) pindahMenu(m123_stg, 1);
else if (idx_pos == 1) pindahMenu(m123_stg, 2);
else if (idx_pos == 2) pindahMenu(m123_stg, 3);
}
break;
case m1_kon:
if (idx_pos > 1) idx_pos = 0;
else if (idx_pos < 0) idx_pos = 1;
lcd.setCursor(0, 0);
lcd.print(msgKonfirmasi);
lcd.setCursor(0, 1);
lcd.print(F("Ya"));
lcd.setCursor(4, 1);
lcd.print(F("Tidak"));
lcd.setCursor(pos_konfirmasi[idx_pos], 1);
updown(1);
if (btnOk()) {
if (idx_pos == 0) {
if (menuKonfirmasi == m12_set) {
setDefault();
pindahMenu(menuKonfirmasi, 8);
} else if (menuKonfirmasi == m12_ka) {
if (msgKonfirmasi.equals(F("Keran Dibuka?"))) {
bukaKeranAir(m12_ka);
} else {
tutupKeranAir(m12_ka);
}
} else if (menuKonfirmasi == m12_aer) {
if (msgKonfirmasi.equals(F("Aerator Hidup?"))) {
bukaRelayAerator(menuKonfirmasi, 1);
} else {
tutupRelayAerator(menuKonfirmasi, 2);
}
} else if (menuKonfirmasi == m12_ser) {
if (int8Msg == -1) {
// --- KODE LOOP SLAVE & SERVO ANDA DI SINI ---
int8_t comTerkirim = 0;
for (int16_t i = 0; i < 31; i++) { // Iterasi Slave 0 sampai 30
String dataSlaveIni = "";
for (int16_t j = 0; j < 15; j++) { // Iterasi Servo 0 sampai 14
if (getDataEnableServo(i, j)) {
int16_t angle = getDataAngleServo(i, j);
int8_t pakan = getDataJmlPakan(i, j);
// PERBAIKAN: Format langsung diakhiri dengan '_' dan ditutup dengan '|' per perintah
dataSlaveIni += String(i) + "_S_" + String(j) + "_" + String(angle) + "_" + String(pakan) + "|";
}
}
// Jika ada instruksi yang terkumpul untuk Slave 'i' ini, kirimkan
if (dataSlaveIni.length() > 0) {
// Catatan: println() di dalam fungsi sendComand otomatis menambahkan "\n" di ujung data
sendComand(dataSlaveIni);
comTerkirim++;
delay(100); // Beri jeda antar Slave agar tidak terjadi tabrakan data di bus RS485
}
}
if (comTerkirim == 0) pesan(menu, 0, F("WARNING"), F("Enable servo dahulu!"));
// --- AKHIR KODE LOOP SLAVE & SERVO ---
pindahMenu(menuKonfirmasi, int8Msg + 1);
}
} else if (menuKonfirmasi == m123_ser) {
if (int8Msg == -1) {
String dataSlaveIni = "";
for (int16_t j = 0; j < 15; j++) { // Iterasi Servo 0 sampai 14
if (getDataEnableServo(posBack - 1, j)) {
int16_t angle = getDataAngleServo(posBack - 1, j);
int8_t pakan = getDataJmlPakan(posBack - 1, j);
// PERBAIKAN: Format langsung diakhiri dengan '_' dan ditutup dengan '|' per perintah
dataSlaveIni += String(posBack - 1) + "_S_" + String(j) + "_" + String(angle) + "_" + String(pakan) + "|";
}
}
// Jika ada instruksi yang terkumpul untuk Slave 'i' ini, kirimkan
if (dataSlaveIni.length() > 0) {
// Catatan: println() di dalam fungsi sendComand otomatis menambahkan "\n" di ujung data
sendComand(dataSlaveIni);
} else pesan(menu, 0, F("WARNING"), F("Enable servo dahulu!"));
pindahMenu(menuKonfirmasi, int8Msg + 1);
} else {
sendComand(posBack - 1, "S", int8Msg, getDataAngleServo(posBack - 1, int8Msg), getDataJmlPakan(posBack - 1, int8Msg));
pindahMenu(menuKonfirmasi, int8Msg);
}
} else if (menuKonfirmasi == m12_phm) {
if (msgKonfirmasi.equals(F("pH Meter Hidup?"))) {
enablePhMeter = true;
writeEEPROM(eepromEnablePhMeter, enablePhMeter);
pesan(menuKonfirmasi, 1, F("Setatus pH Meter"), (__FlashStringHelper*)cHidup);
} else {
enablePhMeter = false;
writeEEPROM(eepromEnablePhMeter, enablePhMeter);
pesan(menuKonfirmasi, 2, F("Setatus pH Meter"), (__FlashStringHelper*)cMati);
}
} else if (menuKonfirmasi == m12_buz) {
if (msgKonfirmasi.equals(F("Buzzer Hidup?"))) {
enableBuzzer = true;
writeEEPROM(eepromEnableBuzzer, enableBuzzer);
pesan(menuKonfirmasi, 1, F("Setatus Buzzer"), (__FlashStringHelper*)cHidup);
} else {
enableBuzzer = false;
writeEEPROM(eepromEnableBuzzer, enableBuzzer);
pesan(menuKonfirmasi, 2, F("Setatus Buzzer"), (__FlashStringHelper*)cMati);
}
} else if (menuKonfirmasi == m12_uv) {
if (msgKonfirmasi.equals(F("UV Hidup?"))) {
hidupUv(menuKonfirmasi, 1);
} else {
matiUv(menuKonfirmasi, 2);
}
}
} else {
if (menuKonfirmasi == m12_set) pindahMenu(menuKonfirmasi, 8);
else if (menuKonfirmasi == m123_ser) pindahMenu(menuKonfirmasi, int8Msg + 1);
else pindahMenu(menuKonfirmasi, 0);
}
delay(200);
lcd.clear();
}
break;
case m12_svwk:
if (idx_pos == 0) {
if (waktu[idx_pos] > 23) {
waktu[idx_pos] = 0;
} else if (waktu[idx_pos] < 0) {
waktu[idx_pos] = 23;
}
} else {
if (waktu[idx_pos] > 59) {
waktu[idx_pos] = 0;
} else if (waktu[idx_pos] < 0) {
waktu[idx_pos] = 59;
}
}
lcd.setCursor(0, 0);
lcd.print(time);
lcd.setCursor(0, 1);
lcd.print(F("Set "));
lcd.setCursor(4, 1);
lcd.print(urutan_waktu[idx_pos]);
lcd.setCursor(9, 1);
lcd.print(duaDigit(waktu[idx_pos]));
if (btnUp()) {
waktu[idx_pos]++;
delay(200);
lcd.clear();
} else if (btnDown()) {
waktu[idx_pos]--;
delay(200);
lcd.clear();
}
if (btnOk()) {
switch (charMsg) {
case w:
rtc.adjust(DateTime(now.year(), now.month(), now.day(), waktu[0], waktu[1], waktu[2]));
pindahMenu(m1_swk, 0);
break;
case wp0:
jamPak[0] = waktu[0];
writeEEPROM(eepromJamPak[0], waktu[0]);
mntPak[0] = waktu[1];
writeEEPROM(eepromMntPak[0], waktu[1]);
dtkPak[0] = waktu[2];
writeEEPROM(eepromDtkPak[0], waktu[2]);
if (idx_pos == 0) pindahMenu(m1_swk, 1);
else if (idx_pos == 1) pindahMenu(m1_swk, 2);
else pindahMenu(m1_swk, 3);
break;
case wp1:
jamPak[1] = waktu[0];
writeEEPROM(eepromJamPak[1], waktu[0]);
mntPak[1] = waktu[1];
writeEEPROM(eepromMntPak[1], waktu[1]);
dtkPak[1] = waktu[2];
writeEEPROM(eepromDtkPak[1], waktu[2]);
if (idx_pos == 0) pindahMenu(m1_swk, 1);
else if (idx_pos == 1) pindahMenu(m1_swk, 2);
else pindahMenu(m1_swk, 3);
break;
case wp2:
jamPak[2] = waktu[0];
writeEEPROM(eepromJamPak[2], waktu[0]);
mntPak[2] = waktu[1];
writeEEPROM(eepromMntPak[2], waktu[1]);
dtkPak[2] = waktu[2];
writeEEPROM(eepromDtkPak[2], waktu[2]);
if (idx_pos == 0) pindahMenu(m1_swk, 1);
else if (idx_pos == 1) pindahMenu(m1_swk, 2);
else pindahMenu(m1_swk, 3);
break;
case wp3:
jamPak[3] = waktu[0];
writeEEPROM(eepromJamPak[3], waktu[0]);
mntPak[3] = waktu[1];
writeEEPROM(eepromMntPak[3], waktu[1]);
dtkPak[3] = waktu[2];
writeEEPROM(eepromDtkPak[3], waktu[2]);
if (idx_pos == 0) pindahMenu(m1_swk, 1);
else if (idx_pos == 1) pindahMenu(m1_swk, 2);
else pindahMenu(m1_swk, 3);
break;
case m123_ska:
jRVHidup = waktu[0];
writeEEPROM(eepromJamRelay, jRVHidup);
mRVHidup = waktu[1];
writeEEPROM(eepromMntRelay, mRVHidup);
dRVHidup = waktu[2];
writeEEPROM(eepromDtkRelay, dRVHidup);
if (idx_pos == 0) pindahMenu(m123_ska, 4);
else if (idx_pos == 1) pindahMenu(m123_ska, 5);
else pindahMenu(m123_ska, 6);
break;
case m123_suv:
jamUv[int8Msg] = waktu[0];
writeEEPROM(eJamUv[int8Msg], jamUv[int8Msg]);
mntUv[int8Msg] = waktu[1];
writeEEPROM(eMntUv[int8Msg], mntUv[int8Msg]);
dtkUv[int8Msg] = waktu[2];
writeEEPROM(eDtkUv[int8Msg], dtkUv[int8Msg]);
if (idx_pos == 0 && int8Msg == 0) pindahMenu(m123_suv, 1);
else if (idx_pos == 1 && int8Msg == 0) pindahMenu(m123_suv, 2);
else if (idx_pos == 2 && int8Msg == 0) pindahMenu(m123_suv, 3);
else if (idx_pos == 0 && int8Msg == 1) pindahMenu(m123_suv, 4);
else if (idx_pos == 1 && int8Msg == 1) pindahMenu(m123_suv, 5);
else pindahMenu(m123_suv, 6);
break;
case m123_sae:
jamAer[int8Msg] = waktu[0];
writeEEPROM(eJamAer[int8Msg], jamAer[int8Msg]);
mntAer[int8Msg] = waktu[1];
writeEEPROM(eMntAer[int8Msg], mntAer[int8Msg]);
dtkAer[int8Msg] = waktu[2];
writeEEPROM(eDtkAer[int8Msg], dtkAer[int8Msg]);
if (idx_pos == 0 && int8Msg == 0) pindahMenu(m123_sae, 3);
else if (idx_pos == 1 && int8Msg == 0) pindahMenu(m123_sae, 4);
else if (idx_pos == 2 && int8Msg == 0) pindahMenu(m123_sae, 5);
else if (idx_pos == 0 && int8Msg == 1) pindahMenu(m123_sae, 6);
else if (idx_pos == 1 && int8Msg == 1) pindahMenu(m123_sae, 7);
else pindahMenu(m123_sae, 8);
break;
}
}
break;
}
}
//fungsi untuk ganti menu
void pindahMenu(char newMenu, int8_t index) {
idx_pos = index;
backlight();
menu = newMenu;
lcd.clear();
delay(200);
}
void backlight() {
dktNoBacklight = 1000;
backlightLCD = 1;
lcd.backlight();
}
// ==========================================
// FUNGSI UTAMA BACA/TULIS TIPE DATA LONG (4 BYTE)
// ==========================================
void writeLongIntoEEPROM(unsigned int address, long value) {
byte four = (value & 0xFF);
byte three = ((value >> 8) & 0xFF);
byte two = ((value >> 16) & 0xFF);
byte one = ((value >> 24) & 0xFF);
// Menggunakan fungsi writeEEPROM milik Anda yang hemat umur chip
writeEEPROM(address, four);
writeEEPROM(address + 1, three);
writeEEPROM(address + 2, two);
writeEEPROM(address + 3, one);
}
long readLongFromEEPROM(unsigned int address) {
// Paksa tipe data menjadi unsigned long saat dibaca agar pergeseran bit aman
unsigned long four = readEEPROM(address);
unsigned long three = readEEPROM(address + 1);
unsigned long two = readEEPROM(address + 2);
unsigned long one = readEEPROM(address + 3);
// Gabungkan menggunakan unsigned long, lalu kembalikan sebagai data long biasa
return (long)((four) | (three << 8) | (two << 16) | (one << 24));
}
//fungsi mengktifkn rely dengn dursi dlm detik
void bukaKeranAir(char menu) {
DateTime now = rtc.now();
if (durasiKeranAirHidup >= 10) {
int8_t j, m, d;
m = durasiKeranAirHidup / 60; //60
d = durasiKeranAirHidup % 60; //0
j = m / 60; //1
m = m % 60;
jamKeranAir = now.hour() + j;
menitKeranAir = now.minute() + m;
detikKeranAir = now.second() + d;
//normalisasi
if (detikKeranAir >= 60) {
menitKeranAir += detikKeranAir / 60;
detikKeranAir %= 60;
}
if (menitKeranAir >= 60) {
jamKeranAir += menitKeranAir / 60;
menitKeranAir %= 60;
}
bukaKeranAir();
durasiBuzzer = durasiKeranAirHidup;
pesan(menu, 1, F("Setatus Keran"), (__FlashStringHelper*)cHidup);
}
}
void bukaKeranAir() {
//digitalWrite(relay_valve_pin, HIGH);
delay(200);
}
void tutupKeranAir() {
//digitalWrite(relay_valve_pin, LOW);
durasiBuzzer = 0;
}
void tutupKeranAir(char menu) {
tutupKeranAir();
pesan(menu, 2, F("Setatus Keran"), (__FlashStringHelper*)cMati);
}
void bukaRelayAerator() {
//digitalWrite(relay_aerator_pin, HIGH);
delay(200);
}
void bukaRelayAerator(char menu, int8_t pos) {
//digitalWrite(relay_aerator_pin, HIGH);
enableRelayAerator = 1;
writeEEPROM(eepromEnableRelayAerator, enableRelayAerator);
pesan(menu, pos, F("Setatus Aerator"), (__FlashStringHelper*)cHidup);
delay(200);
}
void tutupRelayAerator() {
//digitalWrite(relay_aerator_pin, LOW);
delay(200);
}
void tutupRelayAerator(char menu, int8_t pos) {
//digitalWrite(relay_aerator_pin, LOW);
enableRelayAerator = 0;
writeEEPROM(eepromEnableRelayAerator, enableRelayAerator);
pesan(menu, pos, F("Setatus Aerator"), (__FlashStringHelper*)cMati);
delay(200);
}
void matiUv() {
//digitalWrite(relay_uv_pin, LOW);
delay(200);
}
void matiUv(char menu, int8_t pos) {
//digitalWrite(relay_uv_pin, LOW);
writeEEPROM(eepromEnableRelayUV, enableRelayUV);
pesan(menuKonfirmasi, pos, F("Setatus UV"), (__FlashStringHelper*)cMati);
delay(200);
}
void hidupUv() {
//digitalWrite(relay_uv_pin, HIGH);
delay(200);
}
void hidupUv(char menu, int8_t pos) {
//digitalWrite(relay_uv_pin, HIGH);
writeEEPROM(eepromEnableRelayUV, enableRelayUV);
pesan(menuKonfirmasi, pos, F("Setatus UV"), (__FlashStringHelper*)cHidup);
delay(200);
}
void tutupRelayAeratorDurasi() {
DateTime now = rtc.now();
int8_t j, m, d;
m = durasiRelayAeratorMati / 60; //60
d = durasiRelayAeratorMati % 60; //0
j = m / 60; //1
m = m % 60;
jamRelayAerator = now.hour() + j;
menitRelayAerator = now.minute() + m;
detikRelayAerator = now.second() + d;
//normalisasi
if (detikRelayAerator >= 60) {
menitRelayAerator += detikRelayAerator / 60;
detikRelayAerator %= 60;
}
if (menitRelayAerator >= 60) {
jamRelayAerator += menitRelayAerator / 60;
menitRelayAerator %= 60;
}
tutupRelayAerator();
pesan(menu, 0, F("Setatus Aerator"), (__FlashStringHelper*)cMati);
delay(200);
}
//fungsi untuk reepet buzzer on off bergiliran sebagai
//warning kita bahwa kran masih menyala
void repetBuzzer() {
unsigned long wktSekarang = millis();
if (wktSekarang - wktSebelumnya[0] >= 50000) {
wktSebelumnya[0] = wktSekarang;
if (durasiBuzzer > 0 && enableBuzzer) {
if (gantianRBuzzer) {
gantianRBuzzer = 0;
digitalWrite(buz_pin, LOW);
} else {
gantianRBuzzer = 1;
digitalWrite(buz_pin, HIGH);
}
durasiBuzzer--;
} else {
digitalWrite(buz_pin, LOW);
durasiBuzzer = 0;
}
}
}
//countdown yang di ambil detik dari RTC real time
void repetDurasi(int8_t detik) {
//ambil data PH air
if (int8_t(wktSebelumnya[1]) != detik) {
wktSebelumnya[1] = detik;
//screen saver
if (!backlightLCD && detik % 6 == 0 && menu == m1_dis) {
idx_pos++;
lcd.clear();
}
//ph meter
for (int8_t i = 0; i < 10; i++) { //Get 10 sample value from the sensor for smooth the value
//buf[i] = analogRead(ph_pin);
}
for (int8_t i = 0; i < 9; i++) { //sort the analog from small to large
for (int8_t j = i + 1; j < 10; j++) {
if (buf[i] > buf[j]) {
temp = buf[i];
buf[i] = buf[j];
buf[j] = temp;
}
}
}
avgValue = 0;
for (int8_t i = 2; i < 8; i++) avgValue += buf[i]; //take the average value of 6 center sample
phValue = (float)avgValue * 5.0 / 1024 / 6; //convert the analog into millivolt
phValue = -5.70 * phValue + calibration_value; //convert the millivolt into pH value
//automatic nyala valve ketika dibawah ph 7
// if (phValue < 7 && enablePhMeter) {
// tutupKeranAir();
// durasiBuzzer = durasiKeranAirHidup;
// } else if (phValue >= 7 && enablePhMeter) {
// bukaKeranAir();
// durasiBuzzer = 0;
// }
//durasi buzzer mengikuti waktu keran
// if (durasiBuzzer >= 1) {
// digitalWrite(buz_pin, HIGH);
// durasiBuzzer--;
// } else if (durasiBuzzer < 1) {
// //tutupKeranAir();
// durasiBuzzer = 0;
// digitalWrite(buz_pin, LOW);
// }
//cek koneksi rs485
if (TIMEOUT_MS >= 1) {
TIMEOUT_MS--;
} else {
for (int8_t i = 0; i <= 31; i++) {
sendComand(i, "C", 0, 0, 0);
}
memset(statusSlave, false, sizeof(statusSlave));
TIMEOUT_MS = 60;
}
//suhu
// byte data[2];
// ds.reset();
// ds.write(0xCC); // Skip ROM (hanya jika 1 sensor)
// ds.write(0x44); // Mulai konversi
// //delay(750); // Tunggu konversi selesai
// ds.reset();
// ds.write(0xCC);
// ds.write(0xBE); // Read Scratchpad
// data[0] = ds.read();
// data[1] = ds.read();
// // Rumus konversi suhu sederhana
// int16_t raw = (data[1] << 8) | data[0];
// suhu = (float)raw / 16.0;
}
}
String getPH() {
if (enablePhMeter) String(phValue);
else return F("M");
}
void setDefault() {
lcd.clear();
lcd.setCursor(4, 1);
lcd.print(F("Mohon Tunggu"));
for (int i = 0; i < 31; i++) {
for (int j = 0; j < 15; j++) {
angleServo[i][j] = 100; // Atau diisi angka 1 (artinya servo aktif)
// KARENA INI MATRIKS, WAJIB HITUNG ALAMAT FISIKNYA DULU
unsigned int alamatFisik = eepromAngleServo + (((i * 15) + j) * 2);
writeEEPROM_int16(alamatFisik, angleServo[i][j]);
}
}
// ==========================================
// 1. MENYIMPAN MATRIKS enableServo (bool)
// ==========================================
for (int i = 0; i < 31; i++) {
for (int j = 0; j < 15; j++) {
enableServo[i][j] = false; // Atau diisi angka 1 (artinya servo aktif)
// HITUNG ALAMAT FISIK (Melompat 1 byte karena tipe bool berukuran 1 byte)
unsigned int alamatFisik = eepromEnableServo + ((i * 15) + j);
// Tulis nilai dari matriks ke EEPROM fisik (Nilai true otomatis diubah jadi byte 1)
writeEEPROM(alamatFisik, enableServo[i][j]);
}
}
// ==========================================
// 2. MENYIMPAN MATRIKS jmlPakan (int8_t / byte)
// ==========================================
for (int i = 0; i < 31; i++) {
for (int j = 0; j < 15; j++) {
jmlPakan[i][j] = 1;
// HITUNG ALAMAT FISIK (Melompat 1 byte karena tipe int8_t berukuran 1 byte)
unsigned int alamatFisik = eepromJmlPakan + ((i * 15) + j);
// Tulis nilai dari matriks ke EEPROM fisik
writeEEPROM(alamatFisik, jmlPakan[i][j]);
}
}
//jumlah total pakan
totalJmlPakan = 0;
writeLongIntoEEPROM(eepromTotalJmlPakan, totalJmlPakan);
//Cek Jadwal jam pakan di eeporom
jamPak[0] = 7;
jamPak[1] = 11;
jamPak[2] = 15;
jamPak[3] = 19;
for (int8_t i = 0; i < sizeof(eepromJamPak); i++) {
writeEEPROM(eepromJamPak[i], jamPak[i]);
}
for (int8_t i = 0; i < sizeof(eepromMntPak); i++) {
mntPak[i] = 0;
writeEEPROM(eepromMntPak[i], mntPak[i]);
}
for (int8_t i = 0; i < sizeof(eepromDtkPak); i++) {
dtkPak[i] = 0;
writeEEPROM(eepromDtkPak[i], dtkPak[i]);
}
//read enable disable Jam Pakan pada eeprom default pertama kali
for (int8_t i = 0; i < sizeof(enableJamPakan); i++) {
enableJamPakan[i] = true;
writeEEPROM(eepromEnableJamPakan[i], enableJamPakan[i]);
}
//buzzer
enableBuzzer = 1;
writeEEPROM(eepromEnableBuzzer, enableBuzzer);
jmlEnablePakan = 4;
//Ph Meter
enablePhMeter = false;
writeEEPROM(eepromEnablePhMeter, enablePhMeter);
enableKeranAir = false;
writeEEPROM(eepromEnableKeranAir, enableKeranAir);
keranAirPerHari = 0;
writeEEPROM(eepromKeranAirPerHari, keranAirPerHari);
durasiKeranAirPerHari = 0;
jRVHidup = 9;
writeEEPROM(eepromJamRelay, jRVHidup);
mRVHidup = 0;
writeEEPROM(eepromMntRelay, mRVHidup);
dRVHidup = 0;
writeEEPROM(eepromDtkRelay, dRVHidup);
writeLongIntoEEPROM(eepromDurasiKeranAirPerHari, durasiKeranAirPerHari);
//durasi relay valve
durasiKeranAirHidup = 60;
writeLongIntoEEPROM(eepromDurasiKeranAirHidup, durasiKeranAirHidup);
//aerator
enableRelayAerator = 0;
writeEEPROM(eepromEnableRelayAerator, enableRelayAerator);
durasiRelayAeratorMati = 120;
writeEEPROM(eepromDurasiRelayAeratorMati, durasiRelayAeratorMati);
eJadwalAer = 0;
writeEEPROM(eEnableJadwalAer, eJadwalAer);
enableRelayUV = 0;
writeEEPROM(eepromEnableRelayUV, enableRelayUV);
jamUv[0] = 6;
writeEEPROM(eJamUv[0], jamUv[0]);
jamUv[1] = 18;
writeEEPROM(eJamUv[1], jamUv[1]);
for (int8_t i = 0; i < sizeof(mntUv); i++) {
mntUv[i] = 0;
writeEEPROM(eMntUv[i], mntUv[i]);
}
for (int8_t i = 0; i < sizeof(dtkUv); i++) {
dtkUv[i] = 0;
writeEEPROM(eDtkUv[i], dtkUv[i]);
}
writeEEPROM(eJamAer[0], jamAer[0]);
jamAer[1] = 18;
writeEEPROM(eJamAer[1], jamAer[1]);
for (int8_t i = 0; i < sizeof(mntAer); i++) {
mntAer[i] = 0;
writeEEPROM(eMntAer[i], mntAer[i]);
}
for (int8_t i = 0; i < sizeof(dtkAer); i++) {
dtkAer[i] = 0;
writeEEPROM(eDtkAer[i], dtkAer[i]);
}
}
void pesan(char menu, int8_t pos, const __FlashStringHelper* msg1, const __FlashStringHelper* msg2) {
// Gunakan reinterpret_cast<const char*> agar dikenali oleh strlen_P
byte posMsg1 = (20 - strlen_P(reinterpret_cast<const char*>(msg1))) / 2;
byte posMsg2 = (20 - strlen_P(reinterpret_cast<const char*>(msg2))) / 2;
lcd.clear();
lcd.setCursor(posMsg1, 1); // Biasanya baris pertama LCD itu indeks 0
lcd.print(msg1);
lcd.setCursor(posMsg2, 2); // Biasanya baris kedua LCD itu indeks 1
lcd.print(msg2);
delay(2500);
pindahMenu(menu, pos);
}
void pesan(char menu, int8_t pos, const char* msg1, const __FlashStringHelper* msg2) {
byte posMsg1 = (20 - strlen(msg1)) / 2;
byte posMsg2 = (20 - strlen_P(reinterpret_cast<const char*>(msg2))) / 2;
lcd.clear();
lcd.setCursor(posMsg1, 1);
lcd.print(msg1); // Mencetak string dari RAM
lcd.setCursor(posMsg2, 2);
lcd.print(msg2); // Mencetak string dari Flash
delay(2500);
pindahMenu(menu, pos);
}
void updown(bool kursor) {
if (kursor) lcd.print(F("<"));
if (btnUp()) {
backlight();
idx_pos++;
delay(200);
lcd.clear();
} else if (btnDown()) {
backlight();
idx_pos--;
delay(200);
lcd.clear();
}
}
void setMenu(char menu0, char menu1) {
lcd.setCursor(0, 0);
lcd.write(byte(0));
lcd.print(menu0);
lcd.setCursor(0, 1);
lcd.write(byte(0));
lcd.print(menu1);
}
void setMenu(char menu0, char menu1, char menu2, char menu3) {
lcd.setCursor(0, 0);
lcd.write(byte(0));
lcd.print(menu0);
lcd.setCursor(0, 1);
lcd.write(byte(0));
lcd.print(menu1);
lcd.setCursor(0, 2);
lcd.write(byte(0));
lcd.print(menu2);
lcd.setCursor(0, 3);
lcd.write(byte(0));
lcd.print(menu3);
}
void setMenu(String menu0) {
lcd.setCursor(0, 0);
lcd.write(byte(0));
lcd.print(menu0);
}
void setMenu(String menu0, String menu1) {
lcd.setCursor(0, 0);
lcd.write(byte(0));
lcd.print(menu0);
lcd.setCursor(0, 1);
lcd.write(byte(0));
lcd.print(menu1);
}
void setMenu(String menu0, String menu1, String menu2, String menu3) {
lcd.setCursor(0, 0);
lcd.write(byte(0));
lcd.print(menu0);
lcd.setCursor(0, 1);
lcd.write(byte(0));
lcd.print(menu1);
lcd.setCursor(0, 2);
lcd.write(byte(0));
lcd.print(menu2);
lcd.setCursor(0, 3);
lcd.write(byte(0));
lcd.print(menu3);
}
void pindahMenuKonfirmasi(char menu, String msg, int8_t index) {
menuKonfirmasi = menu;
msgKonfirmasi = msg;
pindahMenu(m1_kon, index);
}
void hidupMatiString(bool kondisi) {
if (kondisi) lcd.print(F("Hidup"));
else lcd.print(F("Mati "));
}
void hidupMatiChar(bool kondisi) {
if (kondisi) lcd.print(F("H"));
else lcd.print(F("M"));
}
String duaDigit(int16_t nilai) {
if (nilai > 9) return String(nilai, DEC);
else return "0" + String(nilai);
}
bool btnUp() {
if (!btn_up && btn_down) return true;
else return false;
}
bool btnDown() {
if (!btn_down && btn_up) return true;
else return false;
}
bool btnOk() {
if (!btn_ok) return true;
//if (!btn_up && !btn_down) return true;
else return false;
}
//fungsi untuk memgirimkan perintah ke slave
// S -> code com untuk servo single
// A -> code untuk servo all
// U -> Ultra violet
// R -> Aerator
// P -> Ph
// T -> Tds
// U -> Suhu
// C -> untuk cek koneksi slave
void sendComand(byte cSlave, String com, byte servo, int16_t angle, uint8_t jmlPakan) {
//-- -PENGIRIMAN PERINTAH-- -
if (com == "S") durasiBuzzer = jmlPakan;
String sendData = String(cSlave) + "_" + String(com) + "_" + String(servo) + "_" + String(angle) + "_" + String(jmlPakan) + "_|\n";
digitalWrite(DERE, HIGH);
RS485Serial.write(sendData.c_str());
RS485Serial.flush();
digitalWrite(DERE, LOW);
Serial.print("Send Data => " + sendData);
}
// --- FUNGSI TAMBAHAN KHUSUS UNTUK MENGIRIM DATA MASSAL (BULK GABUNGAN) ---
void sendComand(String dataGabungan) {
durasiBuzzer = 10;
dataGabungan = dataGabungan + "\n";
// Nyalakan pin komunikasi RS485 (Transmit Mode)
digitalWrite(DERE, HIGH);
// Kirimkan teks gabungan panjang beserta '\n' di ujungnya ke bus RS485
RS485Serial.write(dataGabungan.c_str());
RS485Serial.flush();
// Matikan pin komunikasi RS485 (Receive Mode)
digitalWrite(DERE, LOW);
// Tampilkan ke Serial Monitor Laptop untuk memantau data yang keluar
Serial.print("Send Bulk Data => " + dataGabungan);
}
//fungsi untuk menerima perintah dari slave
void receiveCommandRS485() {
//-- -MENUNGGU PERINTAH-- -
if (RS485Serial.available()) {
String receivedData = RS485Serial.readStringUntil('\n');
receivedData.trim(); // Menghapus spasi atau karakter tak terlihat di ujung teks
//Serial.println(receivedData);
if (receivedData.length() > 0) {
// ======================================================================
// PROTEKSI 1: VALIDASI JUMLAH DELIMITER '_'
// Format wajib: codeSlave_com_cservo_status (Harus ada minimal 3 karakter '_')
// ======================================================================
int countDelimiter = 0;
for (int i = 0; i < receivedData.length(); i++) {
if (receivedData[i] == '_') {
countDelimiter++;
}
}
// Jika karakter '_' kurang dari 3, data tidak lengkap/rusak. Langsung keluar dari fungsi.
if (countDelimiter < 3) {
Serial.println("RS485 ERROR: Data kurang / format salah! Diabaikan -> " + receivedData);
return; // Menggunakan 'return' karena ini struktur 'if', bukan perulangan 'while'
}
// ======================================================================
// PROSES PARSING YANG AMAN
// ======================================================================
String strSlave = getValue(receivedData, '_', 0);
String com = getValue(receivedData, '_', 1);
String strServo = getValue(receivedData, '_', 2);
String strStatus = getValue(receivedData, '_', 3);
// PROTEKSI 2: Validasi hasil parsing sebelum diubah ke angka
// Menggunakan nilai default -1 untuk mendeteksi data angka yang gagal/kosong
int8_t codeSlave = (strSlave.length() > 0) ? strSlave.toInt() : -1;
int8_t cservo = (strServo.length() > 0) ? strServo.toInt() : -1;
int8_t status = (strStatus.length() > 0) ? strStatus.toInt() : 0;
// DEBUG
Serial.println(receivedData + " <= Code Slave:" + String(codeSlave) + " command:" + com + " servo:" + String(cservo) + " status:" + String(status));
// ======================================================================
// EKSEKUSI PERINTAH
// ======================================================================
// Pastikan perintah hanya berjalan jika proses parsing codeSlave berhasil (tidak -1)
if (codeSlave >= 0) {
if (com == "C") {
sendComand(codeSlave, "C", 0, 0, 1);
statusSlave[codeSlave] = true;
} else if (com == "S") {
// Validasi tambahan: pastikan nomor servo juga valid sebelum menampilkan pesan
if (cservo >= 0) {
pesan(menu, 0, ("Servo Slave " + String(codeSlave) + "." + String(cservo) + " ").c_str(), (__FlashStringHelper*)cHidup);
} else {
Serial.println("RS485 ERROR: Nomor servo tidak valid!");
}
}
} else {
Serial.println("RS485 ERROR: Code Slave tidak valid!");
}
}
}
}
//Fungsi untuk membaca data di terima dari bluetooth JD-31
void receiveCommandJD31() {
// --- MENUNGGU PERINTAH ---
while (Serial1.available()) {
String receivedData = Serial1.readStringUntil('\n');
receivedData.trim(); // Menghapus spasi atau \r di ujung teks
if (receivedData.length() > 0) {
// ======================================================================
// PROTEKSI 1: VALIDASI JUMLAH DELIMITER '_'
// Format wajib: cSlave_com_cServo_status (Harus ada minimal 3 karakter '_')
// ======================================================================
int countDelimiter = 0;
for (int i = 0; i < receivedData.length(); i++) {
if (receivedData[i] == '_') {
countDelimiter++;
}
}
// Jika karakter '_' kurang dari 3, DATA CACAT! Langsung buang.
if (countDelimiter < 3) {
Serial.println("JD31 ERROR: Data kurang / format salah! Diabaikan -> " + receivedData);
continue; // Langsung lompat ke data Serial berikutnya, tidak lanjut ke bawah
}
// ======================================================================
// PROSES PARSING (Aman karena jumlah '_' sudah dipastikan pas)
// ======================================================================
String strSlave = getValue(receivedData, '_', 0);
String com = getValue(receivedData, '_', 1);
String strServo = getValue(receivedData, '_', 2);
String strStatus = getValue(receivedData, '_', 3);
// PROTEKSI 2: Validasi isi String hasil parsing sebelum diconvert ke angka
byte cSlave = (strSlave.length() > 0) ? strSlave.toInt() : 0;
int8_t cServo = (strServo.length() > 0) ? strServo.toInt() : -1; // -1 jika gagal parsing
int8_t status = (strStatus.length() > 0) ? strStatus.toInt() : 0;
// DEBUG MONITOR
Serial.println("DATA OK -> " + receivedData + " | Slave:" + String(cSlave) + " Cmd:" + com + " Servo:" + String(cServo) + " Stat:" + String(status));
// ======================================================================
// KSEKUSI PERINTAH
// ======================================================================
if (com == "S") {
// Proteksi tambahan: pastikan nomor servo masuk akal (tidak -1)
if (cServo >= 0) {
sendComand(cSlave, com, cServo, getDataAngleServo(cSlave, cServo), getDataJmlPakan(cSlave, cServo));
} else {
Serial.println("JD31 ERROR: Nomor servo hasil parsing tidak valid!");
}
}
}
}
}
//fungsi untuk memecah string sesuai sparator
String getValue(String data, char separator, int index) {
int found = 0;
int strIndex[] = { 0, -1 };
int maxIndex = data.length() - 1; // Aman meskipun panjang teks lebih dari 127 karakter
for (int i = 0; i <= maxIndex && found <= index; i++) {
if (data.charAt(i) == separator || i == maxIndex) {
found++;
strIndex[0] = strIndex[1] + 1;
strIndex[1] = (i == maxIndex) ? i + 1 : i;
}
}
// Jika index ditemukan, potong teksnya. Jika tidak, kembalikan String kosong.
if (found > index) {
String result = data.substring(strIndex[0], strIndex[1]);
result.trim(); // Memastikan tidak ada spasi/karakter tak terlihat yang ikut terpotong
return result;
}
return "";
}
void updateDanSimpanEnableServo(int b, int k, bool nilaiBaru) {
// Validasi koordinat agar aman (tidak melebih batas array)
if (b >= 31 || k >= 15 || b < 0 || k < 0) {
Serial.println("Error: Koordinat di luar batas array 31x15!");
return;
}
// Ubah data yang ada di RAM (Array 2D)
enableServo[b][k] = nilaiBaru;
// Hitung posisi alamat EEPROM spesifik untuk koordinat ini
// sizeof(int) bernilai 2 byte karena tipe datanya 'int'
int alamatSpesifik = eepromEnableServo + ((b * 15) + k) * sizeof(bool);
// Tulis hanya ke alamat spesifik tersebut menggunakan EEPROM.put()
// EEPROM.put secara otomatis mendeteksi jika nilainya sama, tidak akan ditulis ulang (hemat umur EEPROM)
writeEEPROM(alamatSpesifik, nilaiBaru);
}
bool getDataEnableServo(int b, int k) {
// Validasi batas koordinat (Baris 0-30, Kolom 0-14)
if (b >= 31 || k >= 15 || b < 0 || k < 0) {
Serial.println("Error: Koordinat ambil data di luar batas!");
return false;
}
// Hitung alamat spesifik (tipe bool berukuran 1 byte)
int alamatSpesifik = eepromEnableServo + ((b * 15) + k) * sizeof(bool);
// Ambil data murni dari EEPROM
bool hasilData = readEEPROM(alamatSpesifik);
return hasilData;
}
void updateDanSimpanAngleServo(int b, int k, int16_t nilaiBaru) {
// Validasi koordinat agar aman (tidak melebih batas array)
if (b >= 31 || k >= 15 || b < 0 || k < 0) {
Serial.println(F("Error: Koordinat di luar batas array 31x15!"));
return;
}
// Ubah data yang ada di RAM (Array 2D)
angleServo[b][k] = nilaiBaru;
// Hitung posisi alamat EEPROM spesifik untuk koordinat ini
int alamatSpesifik = eepromAngleServo + (((b * 15) + k) * sizeof(int16_t));
// PERBAIKAN: Tulis data 2 byte secara utuh menggunakan fungsi khusus int16
writeEEPROM_int16(alamatSpesifik, nilaiBaru);
}
int16_t getDataAngleServo(int b, int k) {
// Validasi batas koordinat (Baris 0-30, Kolom 0-14)
if (b >= 31 || k >= 15 || b < 0 || k < 0) {
Serial.println(F("Error: Koordinat ambil data di luar batas!"));
return -1;
}
// PERBAIKAN KURUNG: Pastikan rumus dihitung matang sebelum dijumlahkan
unsigned int alamatSpesifik = eepromAngleServo + (((b * 15) + k) * 2);
// Ambil data murni 2 byte dari EEPROM
int16_t hasilData = readEEPROM_int16(alamatSpesifik);
return hasilData;
}
void updateDanSimpanJmlPakan(int b, int k, int8_t nilaiBaru) {
// Validasi koordinat agar aman (tidak melebih batas array)
if (b >= 31 || k >= 15 || b < 0 || k < 0) {
Serial.println("Error: Koordinat di luar batas array 31x15!");
return;
}
// Ubah data yang ada di RAM (Array 2D)
jmlPakan[b][k] = nilaiBaru;
// Hitung posisi alamat EEPROM spesifik untuk koordinat ini
// sizeof(int) bernilai 2 byte karena tipe datanya 'int'
int alamatSpesifik = eepromJmlPakan + ((b * 15) + k) * sizeof(int8_t);
// Tulis hanya ke alamat spesifik tersebut menggunakan EEPROM.put()
// EEPROM.put secara otomatis mendeteksi jika nilainya sama, tidak akan ditulis ulang (hemat umur EEPROM)
writeEEPROM(alamatSpesifik, nilaiBaru);
}
int8_t getDataJmlPakan(int b, int k) {
// Validasi batas koordinat (Baris 0-30, Kolom 0-14)
if (b >= 31 || k >= 15 || b < 0 || k < 0) {
Serial.println("Error: Koordinat ambil data di luar batas!");
return false;
}
// Hitung alamat spesifik (tipe bool berukuran 1 byte)
int alamatSpesifik = eepromJmlPakan + ((b * 15) + k) * sizeof(int8_t);
// Ambil data murni dari EEPROM
int8_t hasilData = readEEPROM(alamatSpesifik);
return hasilData;
}
// Fungsi penghemat umur EEPROM (Hanya menulis jika data berbeda)
void writeEEPROM(unsigned int memAddress, byte dataBaru) {
// Baca data yang saat ini tersimpan di alamat tersebut
byte dataLama = readEEPROM(memAddress);
// Bandingkan data lama dengan data baru
if (dataLama != dataBaru) {
// Lakukan penulisan fisik ke EEPROM jika data berbeda
Wire.beginTransmission(EEPROM_I2C_ADDRESS);
Wire.write((int)(memAddress >> 8));
Wire.write((int)(memAddress & 0xFF));
Wire.write(dataBaru);
Wire.endTransmission();
delay(10); // Jeda wajib setelah menulis fisik
}
}
// Fungsi dasar membaca dari EEPROM
byte readEEPROM(unsigned int memAddress) {
byte rData = 0xFF;
Wire.beginTransmission(EEPROM_I2C_ADDRESS);
Wire.write((int)(memAddress >> 8));
Wire.write((int)(memAddress & 0xFF));
Wire.endTransmission();
Wire.requestFrom(EEPROM_I2C_ADDRESS, 1);
if (Wire.available()) {
rData = Wire.read();
}
return rData;
}
// Fungsi menulis data tipe int16_t (2 Byte) ke EEPROM I2C
void writeEEPROM_int16(unsigned int memAddress, int16_t dataBaru) {
// Pecah int16_t menjadi 2 buah byte
byte high = (byte)(dataBaru >> 8);
byte low = (byte)(dataBaru & 0xFF);
// Tulis byte pertama (High) dan byte kedua (Low) secara berurutan
writeEEPROM(memAddress, high);
writeEEPROM(memAddress + 1, low);
}
// Ganti fungsi lama Anda dengan ini:
int16_t readEEPROM_int16(unsigned int memAddress) {
// Pastikan dibaca sebagai tipe unsigned byte (0-255) murni
uint8_t high = (uint8_t)readEEPROM(memAddress);
uint8_t low = (uint8_t)readEEPROM(memAddress + 1);
// Gabungkan dengan casting int16_t yang tegas
return (int16_t)((high << 8) | low);
}