From 3d732a996580d77254945166f8e860929e8ff16d Mon Sep 17 00:00:00 2001 From: farhanputra1 <143571979+farhanputra1@users.noreply.github.com> Date: Mon, 7 Jul 2025 15:33:19 +0700 Subject: [PATCH] Add files via upload --- inkubator ayam.ino | 1396 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1396 insertions(+) create mode 100644 inkubator ayam.ino diff --git a/inkubator ayam.ino b/inkubator ayam.ino new file mode 100644 index 0000000..c217579 --- /dev/null +++ b/inkubator ayam.ino @@ -0,0 +1,1396 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // WARNING: Library ini mungkin tidak sepenuhnya kompatibel dengan ESP32, namun seringkali berfungsi. + +// --- PIN DEFINITIONS --- +#define DHTPIN 4 +#define DHTTYPE DHT22 +#define HEATER_PIN 19 +#define FAN_PIN 18 +#define FAN_PIN_2 26 +#define MISTMAKER_PIN 12 +#define SERVO_PIN 14 +#define LIMIT_SWITCH_PIN 27 +#define TRIAC_PIN 13 + +#define HEATER_PWM_CHANNEL 10 +#define PWM_FREQUENCY 510 +#define PWM_RESOLUTION 8 +#define MAX_PWM_DUTY ((1 << PWM_RESOLUTION) - 1) + +#define I2C_SDA_PIN_0 21 +#define I2C_SCL_PIN_0 22 + +#define I2C_SDA_PIN_1 32 +#define I2C_SCL_PIN_1 33 + + +// --- EEPROM ADDRESSES --- +#define EEPROM_ADDR_DATE 0 // 3 bytes for Day, Month, Year +#define EEPROM_ADDR_INCUBATION_STARTED 3 // 1 byte for boolean status (0 or 1) +#define EEPROM_ADDR_CENTER_RAW 4 // 2 bytes for int (AS5600 center raw value) +#define EEPROM_ADDR_LAST_DAY 6 // 1 byte for last saved currentDay (max 21) + + +#define EEPROM_ADDR_TEMP_HISTORY (EEPROM_ADDR_LAST_DAY + 1) // Mulai setelah lastSavedDay +#define EEPROM_ADDR_HUM_HISTORY (EEPROM_ADDR_TEMP_HISTORY + (sizeof(float) * 21)) +#define EEPROM_ADDR_RACK_ANGLE_HISTORY (EEPROM_ADDR_HUM_HISTORY + (sizeof(float) * 21)) +#define EEPROM_ADDR_VENT_ANGLE_HISTORY (EEPROM_ADDR_RACK_ANGLE_HISTORY + (sizeof(int) * 21)) + + +// Tambahkan definisi EEPROM_SIZE ini di bagian atas file Anda +const int EEPROM_SIZE = EEPROM_ADDR_VENT_ANGLE_HISTORY + (sizeof(int) * 21); + +// Total EEPROM size needed: Alamat tertinggi (6) + ukuran variabel (1 byte) = 7 byte. +// EEPROM.begin() akan disesuaikan secara otomatis saat kompilasi berdasarkan alamat tertinggi + 1. + +// --- GLOBAL OBJECTS --- +DHT dht(DHTPIN, DHTTYPE); +Servo myServo; +AS5600 encoder(&Wire1); // AS5600 menggunakan Wire1 (I2C Bus 1) +RTC_DS3231 rtc; // RTC menggunakan Wire (I2C Bus 0) +AsyncWebServer server(80); +LiquidCrystal_I2C lcd(0x27, 16, 2); // LCD menggunakan Wire (I2C Bus 0) + +// --- WIFI CREDENTIALS --- +const char* ssid = "MINDIL"; +const char* password = "mewah12345"; + +// --- GLOBAL VARIABLES --- +float suhu = NAN; +float kelembapan = NAN; +bool heaterState = false; +bool mistState = false; +bool isAutoMode = false; +bool isHeaterOn = false; // Status heater ON/OFF +bool isMistMakerOn = false; // Status mist maker ON/OFF +bool isIncubationStarted = false; // Variabel global untuk melacak status inkubasi +bool isRackMotorMoving = false; +int currentDay = 1; // Hari inkubasi saat ini +int lastSavedDay = 1; // Hari inkubasi terakhir yang disimpan di EEPROM +bool isWiFiConnected = false; +bool webServerStarted = false; + +bool manualRackRotationActive = false; // Status apakah motor sedang berputar karena perintah manual +unsigned long lastManualRotateMillis = 0; // Waktu terakhir putar manual dimulai +const unsigned long MANUAL_ROTATE_DURATION = 7000; // Durasi putar manual dalam ms (sesuaikan dengan RotateByTimer) + + +int currentServoPos = 40; +int servoAngleByDay = 40; + +unsigned long lastDHTRead = 0; +const unsigned long DHT_INTERVAL = 10000; // 10 detik + +int targetPwmDuty = MAX_PWM_DUTY; +int currentPwmDuty = MAX_PWM_DUTY; +const int PWM_STEP = 10; // Langkah perubahan PWM +unsigned long lastPwmUpdateTime = 0; +const unsigned long PWM_UPDATE_INTERVAL = 235; // Update PWM setiap 20ms untuk transisi halus + +DateTime tanggalMulai; +int overrideDay = 0; // Digunakan untuk override hari inkubasi secara manual + +unsigned long lastStatusPrint = 0; +const unsigned long STATUS_PRINT_INTERVAL = 2000; // Print status ke Serial setiap 2 detik + +const int NUM_DHT_READINGS = 10; // Jumlah pembacaan untuk filter DHT +float dhtTempReadings[NUM_DHT_READINGS]; +float dhtHumReadings[NUM_DHT_READINGS]; +int dhtReadingIndex = 0; +bool dhtReadingsInitialized = false; + +//int rawDiffReadings[NUM_ANGLE_READINGS]; +int angleReadingIndex = 0; +bool angleReadingsInitialized = false; + +unsigned long lastWiFiReconnectAttempt = 0; +const unsigned long WIFI_RECONNECT_INTERVAL = 30000; // Coba reconnect WiFi setiap 30 detik + +unsigned long lastLcdUpdate = 0; +const unsigned long LCD_UPDATE_INTERVAL = 1000; // Update LCD setiap 5 detik + +// --- NEW GLOBAL ARRAYS FOR DAILY HISTORY --- +float dailyTemperatures[21]; // Array untuk menyimpan suhu harian +float dailyHumidities[21]; // Array untuk menyimpan kelembaban harian + +int dailyVentAngles[21]; // Array untuk menyimpan sudut ventilasi harian + +// --- FUNCTION DECLARATIONS --- +void matikanSemua(); +void tampilkanStatus(); +bool bolehGerakKe(int targetAngle); +void connectToWiFi(); + + + +float readFilteredTemperature(); +float readFilteredHumidity(); + +void simpanStatusInkubasi(bool started); +bool bacaStatusInkubasi(); +void simpanTanggalMulai(DateTime dt); +DateTime bacaTanggalMulai(); +void simpanHariTerakhir(int day); +int bacaHariTerakhir(); +void hitungHariInkubasi(); // Logika di dalamnya akan disesuaikan +void setTanggalMulaiManualHariKe(int hariKe); +void scanI2CBus(TwoWire *wireObj, const char* busName, int sdaPin, int sclPin); + +// NEW FUNCTION DECLARATIONS +void saveDailyHistory(int dayIndex, float temp, float hum, int ventAngle); +void loadDailyHistory(); +void clearDailyHistory(); // Fungsi untuk mereset data historis (saat inkubasi berhenti) + + +//UNTUK RAK TELUR +#define HALL_DO_PIN 23 +#define TRIAC_PIN 13 +bool motorNyala = false; +unsigned long lastStartTime = 0; +#define ROT_NONE 0 +#define ROT_ZERO 1 +#define ROT_SKEW 2 +static volatile int rotateState = ROT_NONE; +static volatile int rotateStateTarget = ROT_NONE; +int sudutZero = 0; +float sudutZeroDerajat = 0; +float sudutSekarang = 0; +float sudutSekarangDerajat = 0; +bool rotateFindZero = false; +uint32_t tRotate = 0; +uint32_t jedaPutarRak = 14400000;//5000;//14400000; //14400000 = 4 jam + +bool putarRakByTimer = true; //kalau false coba pakai sensor sudut. kalau true, dibatasi sama sudut dan timer +uint32_t tStopPutar = 0; + +uint32_t lastTickUpdate = 0; + +bool detachServoFlag = false; +uint32_t detachServoTick = 0; +uint32_t skipTick = 0; + +// --- FUNCTION IMPLEMENTATIONS --- + +void scanI2CBus(TwoWire *wireObj, const char* busName, int sdaPin, int sclPin) { + byte error, address; + int nDevices = 0; + Serial.printf("\n--- Scanning I2C Bus %s (SDA=%d, SCL=%d) ---\n", busName, sdaPin, sclPin); + Serial.println(" 0 1 2 3 4 5 6 7 8 9 A B C D E F"); + + for (int i = 0; i < 128; i += 16) { + Serial.printf("%02x ", i); + for (address = 0; address < 16; address++) { + if (i + address < 8) { // Alamat 0-7 dicadangkan, tidak perlu scan + Serial.print(" "); + continue; + } + wireObj->beginTransmission(i + address); + error = wireObj->endTransmission(); + + if (error == 0) { + Serial.printf("%02x ", i + address); + nDevices++; + } else if (error == 4) { + Serial.print("?? "); // Unknown error + } else { + Serial.print("-- "); // No device found + } + } + Serial.println(); + } + + if (nDevices == 0) { + Serial.printf("\nNo I2C devices found on Bus %s.\n", busName); + } else { + Serial.printf("\nDone scanning I2C Bus %s. Found %d device(s).\n", busName, nDevices); + } + Serial.println("------------------------------------------"); +} + +void simpanTanggalMulai(DateTime dt) { + EEPROM.write(EEPROM_ADDR_DATE, dt.day()); + EEPROM.write(EEPROM_ADDR_DATE + 1, dt.month()); + EEPROM.write(EEPROM_ADDR_DATE + 2, dt.year() - 2000); + EEPROM.commit(); + Serial.printf("Tanggal mulai inkubasi disimpan: %02d-%02d-%04d\n", dt.day(), dt.month(), dt.year()); +} + +DateTime bacaTanggalMulai() { + int d = EEPROM.read(EEPROM_ADDR_DATE); + int m = EEPROM.read(EEPROM_ADDR_DATE + 1); + int y = EEPROM.read(EEPROM_ADDR_DATE + 2) + 2000; + // Cek validitas data dasar (contoh: tahun 2000-2099) + if (d < 1 || d > 31 || m < 1 || m > 12 || y < 2000 || y > 2099) { + Serial.println("Tanggal mulai di EEPROM tidak valid, menggunakan tanggal default."); + return DateTime(2025, 5, 20); // Contoh default tanggal + } + Serial.printf("Tanggal mulai dibaca dari EEPROM: %02d-%02d-%04d\n", d, m, y); + return DateTime(y, m, d); +} + +void simpanStatusInkubasi(bool started) { + EEPROM.write(EEPROM_ADDR_INCUBATION_STARTED, started ? 1 : 0); + EEPROM.commit(); + isIncubationStarted = started; // Pastikan variabel global juga terupdate + Serial.printf("Status inkubasi disimpan: %s\n", started ? "DIMULAI" : "BELUM DIMULAI"); +} + +bool bacaStatusInkubasi() { + byte status = EEPROM.read(EEPROM_ADDR_INCUBATION_STARTED); + // Jika nilai yang dibaca tidak valid (misal, EEPROM belum pernah ditulis), anggap belum dimulai + if (status != 0 && status != 1) { + Serial.println("Status inkubasi di EEPROM tidak valid, dianggap BELUM DIMULAI."); + return false; + } + isIncubationStarted = (status == 1); // Perbarui variabel global + Serial.printf("Status inkubasi dibaca dari EEPROM: %s\n", isIncubationStarted ? "DIMULAI" : "BELUM DIMULAI"); + return isIncubationStarted; +} + +void simpanHariTerakhir(int day) { + if (day < 1) day = 1; // Pastikan hari tidak kurang dari 1 + if (day > 21) day = 21; // Pastikan hari tidak lebih dari 21 + EEPROM.write(EEPROM_ADDR_LAST_DAY, day); + EEPROM.commit(); + lastSavedDay = day; // Update variabel global + Serial.printf("Hari terakhir inkubasi disimpan ke EEPROM: %d\n", day); +} + +int bacaHariTerakhir() { + int day = EEPROM.read(EEPROM_ADDR_LAST_DAY); + // Jika nilai tidak valid (misal, EEPROM belum pernah ditulis), anggap hari 1 + if (day < 1 || day > 21) { + Serial.println("Hari terakhir di EEPROM tidak valid, dianggap hari ke-1."); + return 1; + } + lastSavedDay = day; // Update variabel global + Serial.printf("Hari terakhir dibaca dari EEPROM: %d\n", lastSavedDay); + return lastSavedDay; +} + +void hitungHariInkubasi() { + // Fungsi ini sekarang lebih fokus pada perhitungan real-time untuk tujuan display/debug jika diperlukan, + // atau untuk set tanggalMulai saat start baru. + // currentDay aktual untuk logika utama akan diatur oleh lastSavedDay atau setTanggalMulaiManualHariKe. + if (overrideDay > 0) { + // Jika ada override, currentDay diset langsung di setTanggalMulaiManualHariKe + } else if (!isIncubationStarted) { + // Hanya hitung dari RTC jika inkubasi belum dimulai. + // Ini untuk setting awal tanggalMulai atau saat pertama kali siklus dijalankan. + DateTime now = rtc.now(); + TimeSpan durasi = now - tanggalMulai; + currentDay = durasi.days() + 1; + if (currentDay < 1) currentDay = 1; + if (currentDay > 21) currentDay = 21; + } + // Jika isIncubationStarted TRUE dan tidak ada overrideDay, currentDay akan mempertahankan + // nilai dari lastSavedDay yang dimuat di setup() atau yang terakhir disimpan secara eksplisit. + // Logika untuk maju hari otomatis ada di loop(). +} + +void setTanggalMulaiManualHariKe(int hariKe) { + DateTime now = rtc.now(); + if (hariKe < 1) hariKe = 1; + if (hariKe > 21) hariKe = 21; + // Sesuaikan tanggalMulai agar hari saat ini menjadi hariKe + DateTime newStart = now - TimeSpan(hariKe - 1, 0, 0, 0); + tanggalMulai = newStart; + simpanTanggalMulai(tanggalMulai); + + overrideDay = 0; // Membatalkan override hari + currentDay = hariKe; // Set currentDay langsung ke hari yang diminta + simpanHariTerakhir(currentDay); // <--- SIMPAN HARI KE EEPROM + isAutoMode = true; // Langsung setel ke mode otomatis + simpanStatusInkubasi(true); // <--- TANDAI INKUBASI SUDAH DIMULAI + tampilkanStatus(); + Serial.printf("Tanggal mulai diset ke %02d-%02d-%04d agar hari sekarang adalah hari ke-%d, mode AUTO aktif.\n", + tanggalMulai.day(), tanggalMulai.month(), tanggalMulai.year(), hariKe); +} + +float readFilteredTemperature() { + float rawTemp = dht.readTemperature(); + if (isnan(rawTemp)) { + if (dhtReadingsInitialized || dhtReadingIndex > 0) { + float sum = 0; + int count = 0; + if (dhtReadingsInitialized) { + for (int i = 0; i < NUM_DHT_READINGS; i++) { + sum += dhtTempReadings[i]; + } + count = NUM_DHT_READINGS; + } else { + for (int i = 0; i < dhtReadingIndex; i++) { + sum += dhtTempReadings[i]; + } + count = dhtReadingIndex; + } + if (count > 0) return sum / count; + } + return suhu; // Mengembalikan nilai suhu terakhir yang valid jika pembacaan gagal + } + + dhtTempReadings[dhtReadingIndex] = rawTemp; + + dhtReadingIndex = (dhtReadingIndex + 1) % NUM_DHT_READINGS; + + if (dhtReadingIndex == 0) { + dhtReadingsInitialized = true; + } + + float sum = 0; + int count = 0; + if (dhtReadingsInitialized) { + for (int i = 0; i < NUM_DHT_READINGS; i++) { + sum += dhtTempReadings[i]; + } + count = NUM_DHT_READINGS; + } else { + for (int i = 0; i < dhtReadingIndex; i++) { + sum += dhtTempReadings[i]; + } + count = dhtReadingIndex; + } + + if (count == 0) return rawTemp; // Seharusnya tidak terjadi jika rawTemp valid + return sum / count; +} + +float readFilteredHumidity() { + float rawHum = dht.readHumidity(); + if (isnan(rawHum)) { + if (dhtReadingsInitialized || dhtReadingIndex > 0) { + float sum = 0; + int count = 0; + if (dhtReadingsInitialized) { + for (int i = 0; i < NUM_DHT_READINGS; i++) { + sum += dhtHumReadings[i]; + } + count = NUM_DHT_READINGS; + } else { + for (int i = 0; i < dhtReadingIndex; i++) { + sum += dhtHumReadings[i]; + } + count = dhtReadingIndex; + } + if (count > 0) return sum / count; + } + return kelembapan; // Mengembalikan nilai kelembapan terakhir yang valid jika pembacaan gagal + } + + dhtHumReadings[dhtReadingIndex] = rawHum; + + // dhtReadingIndex sudah diupdate di readFilteredTemperature, jadi ini akan digunakan untuk keduanya + // dhtReadingIndex = (dhtReadingIndex + 1) % NUM_DHT_READINGS; // Tidak perlu di sini lagi, karena sudah di readFilteredTemperature + + float sum = 0; + int count = 0; + if (dhtReadingsInitialized) { + for (int i = 0; i < NUM_DHT_READINGS; i++) { + sum += dhtHumReadings[i]; + } + count = NUM_DHT_READINGS; + } else { + for (int i = 0; i < dhtReadingIndex; i++) { + sum += dhtHumReadings[i]; + } + count = dhtReadingIndex; + } + + if (count == 0) return rawHum; // Seharusnya tidak terjadi jika rawHum valid + return sum / count; +} + +void bacaSensor() { + suhu = readFilteredTemperature(); + kelembapan = readFilteredHumidity(); + Serial.printf("Pembacaan DHT (Filtered): Suhu=%.2f, Kelembapan=%.2f\n", suhu, kelembapan); +} + +void updateHeaterPWM() { + unsigned long now = millis(); + if (now - lastPwmUpdateTime >= PWM_UPDATE_INTERVAL) { + if (suhu < 37.3){ + targetPwmDuty += PWM_STEP; + }else if (suhu > 37.7){ + targetPwmDuty -= 1; + } + if (targetPwmDuty<0) targetPwmDuty = 0; + if (targetPwmDuty>MAX_PWM_DUTY) targetPwmDuty = MAX_PWM_DUTY; + if (isAutoMode){ + currentPwmDuty = targetPwmDuty; + }else{ + currentPwmDuty = 0; + } + ledcWrite(HEATER_PWM_CHANNEL, currentPwmDuty); + lastPwmUpdateTime = now; + } +} + +void connectToWiFi() { + if (WiFi.status() == WL_CONNECTED) { + if (!isWiFiConnected) { + isWiFiConnected = true; + Serial.print("WiFi Terhubung! Alamat IP: "); + Serial.println(WiFi.localIP()); + lcd.clear(); + lcd.print("WiFi Connected!"); + lcd.setCursor(0, 1); + lcd.print(WiFi.localIP()); + //delay(2000); + //lcd.clear(); + } + return; + } + + if (WiFi.status() != WL_CONNECTED) { + if (isWiFiConnected) { + isWiFiConnected = false; + Serial.println("WiFi Terputus. Mencoba menyambung kembali..."); + lcd.clear(); + lcd.print("WiFi Disconnected"); + lcd.setCursor(0, 1); + lcd.print("Retrying..."); + } + + // Hindari mencoba menyambung terlalu sering jika sudah dalam proses + if (millis() - lastWiFiReconnectAttempt >= WIFI_RECONNECT_INTERVAL || lastWiFiReconnectAttempt == 0) { + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + Serial.println("Mencoba menyambungkan WiFi..."); + lastWiFiReconnectAttempt = millis(); + } + } +} + +// --- NEW FUNCTION IMPLEMENTATIONS FOR DAILY HISTORY --- + +// Menyimpan satu titik data harian ke EEPROM +void saveDailyHistory(int dayIndex, float temp, float hum, int ventAngle) { + if (dayIndex >= 0 && dayIndex < 21) { + // Simpan ke array global (cache) + dailyTemperatures[dayIndex] = temp; + dailyHumidities[dayIndex] = hum; + dailyVentAngles[dayIndex] = ventAngle; + + // Simpan ke EEPROM + EEPROM.put(EEPROM_ADDR_TEMP_HISTORY + (dayIndex * sizeof(float)), temp); + EEPROM.put(EEPROM_ADDR_HUM_HISTORY + (dayIndex * sizeof(float)), hum); + EEPROM.put(EEPROM_ADDR_VENT_ANGLE_HISTORY + (dayIndex * sizeof(int)), ventAngle); + EEPROM.commit(); + Serial.printf("Data historis Hari %d disimpan: Suhu=%.2f, RH=%.2f, Rak=%d, Vent=%d\n", + dayIndex + 1, temp, hum, ventAngle); + } else { + Serial.println("Indeks hari untuk penyimpanan histori tidak valid."); + } +} + +// Memuat semua data historis dari EEPROM ke array global +void loadDailyHistory() { + Serial.println("Memuat data historis harian dari EEPROM..."); + for (int i = 0; i < 21; i++) { + EEPROM.get(EEPROM_ADDR_TEMP_HISTORY + (i * sizeof(float)), dailyTemperatures[i]); + EEPROM.get(EEPROM_ADDR_HUM_HISTORY + (i * sizeof(float)), dailyHumidities[i]); + EEPROM.get(EEPROM_ADDR_VENT_ANGLE_HISTORY + (i * sizeof(int)), dailyVentAngles[i]); + // Validasi data yang dibaca (misal, jika EEPROM kosong awalnya) + if (isnan(dailyTemperatures[i]) || dailyTemperatures[i] < 0 || dailyTemperatures[i] > 100) dailyTemperatures[i] = 0.0; + if (isnan(dailyHumidities[i]) || dailyHumidities[i] < 0 || dailyHumidities[i] > 100) dailyHumidities[i] = 0.0; + if (dailyVentAngles[i] < 0 || dailyVentAngles[i] > 180) dailyVentAngles[i] = 0; + } + Serial.println("Data historis harian selesai dimuat."); +} + +// Mengosongkan semua data historis di EEPROM dan RAM +void clearDailyHistory() { + Serial.println("Mengosongkan data historis harian..."); + for (int i = 0; i < 21; i++) { + dailyTemperatures[i] = 0.0; + dailyHumidities[i] = 0.0; + dailyVentAngles[i] = 0; + // Opsional: Langsung tulis 0 ke EEPROM untuk clear total + EEPROM.put(EEPROM_ADDR_TEMP_HISTORY + (i * sizeof(float)), 0.0f); + EEPROM.put(EEPROM_ADDR_HUM_HISTORY + (i * sizeof(float)), 0.0f); + EEPROM.put(EEPROM_ADDR_VENT_ANGLE_HISTORY + (i * sizeof(int)), 0); + } + EEPROM.commit(); // Pastikan perubahan disimpan + Serial.println("Data historis harian berhasil dikosongkan."); +} + +void setup() { + delay(2000); + Serial.begin(115200); + // Ukuran EEPROM.begin() harus mencakup alamat tertinggi yang digunakan + ukuran variabelnya + //EEPROM.begin(EEPROM_ADDR_LAST_DAY + 1); // Cukup 1 byte untuk hari (1-21) + EEPROM.begin(EEPROM_SIZE); + loadDailyHistory(); + + if (!SPIFFS.begin(true)) { + Serial.println("Gagal mount SPIFFS!"); + while (true) delay(1000); + } + Serial.println("SPIFFS mounted successfully."); + + Wire.begin(I2C_SDA_PIN_0, I2C_SCL_PIN_0); + Serial.printf("I2C Bus 0 (RTC & LCD) diinisialisasi: SDA=%d, SCL=%d\n", I2C_SDA_PIN_0, I2C_SCL_PIN_0); + + lcd.init(); + lcd.backlight(); + lcd.print("Inkubator Nyala"); + lcd.setCursor(0, 1); + lcd.print("Loading..."); + delay(2000); + + Wire1.begin(I2C_SDA_PIN_1, I2C_SCL_PIN_1); + Serial.printf("I2C Bus 1 (AS5600) diinisialisasi: SDA=%d, SCL=%d\n", I2C_SDA_PIN_1, I2C_SCL_PIN_1); + + angleReadingsInitialized = true; + angleReadingIndex = 0; + + dht.begin(); + Serial.println("DHT22 sensor diinisialisasi."); + + ledcSetup(HEATER_PWM_CHANNEL, PWM_FREQUENCY, PWM_RESOLUTION); + ledcAttachPin(HEATER_PIN, HEATER_PWM_CHANNEL); + ledcWrite(HEATER_PWM_CHANNEL, 0); + + pinMode(FAN_PIN, OUTPUT); + pinMode(FAN_PIN_2, OUTPUT); + pinMode(MISTMAKER_PIN, OUTPUT); + pinMode(TRIAC_PIN, OUTPUT); + pinMode(LIMIT_SWITCH_PIN, INPUT_PULLUP); + + digitalWrite(FAN_PIN, LOW); + digitalWrite(FAN_PIN_2, LOW); + digitalWrite(MISTMAKER_PIN, LOW); + digitalWrite(TRIAC_PIN, LOW); + + myServo.attach(SERVO_PIN); + myServo.write(currentServoPos); + + // --- PEMBACAAN DAN LOGIKA INISIALISASI RTC & EEPROM --- + if (!rtc.begin(&Wire)) { + Serial.println("RTC tidak terdeteksi atau gagal diinisialisasi!"); + Serial.println("Periksa koneksi RTC (SDA=21, SCL=22) atau batere."); + // Jika RTC tidak ada, kita *tidak akan* mereset hari inkubasi ke 1 secara paksa, + // melainkan mengandalkan `lastSavedDay` jika inkubasi sudah dimulai. + // `tanggalMulai` akan diset ke waktu kompilasi sebagai fallback minimal. + tanggalMulai = DateTime(F(__DATE__), F(__TIME__)); + simpanTanggalMulai(tanggalMulai); // Simpan tanggal kompilasi ini ke EEPROM + bacaStatusInkubasi(); // Baca status inkubasi dari EEPROM + bacaHariTerakhir(); // Baca hari terakhir dari EEPROM + // currentDay akan diatur berdasarkan lastSavedDay di bagian bawah setup() + Serial.println("RTC tidak aktif, mengandalkan data EEPROM dan waktu kompilasi sebagai fallback."); + } else { + Serial.println("RTC berhasil inisialisasi."); + bacaStatusInkubasi(); // Baca status inkubasi terlebih dahulu + bacaHariTerakhir(); // Baca hari terakhir terlebih dahulu + + if (rtc.lostPower()) { + Serial.println("RTC kehilangan daya, waktu perlu disetel ulang!"); + rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); // Setel RTC ke waktu kompilasi + Serial.println("RTC telah disetel ulang ke waktu kompilasi."); + // PENTING: Kita TIDAK mereset isIncubationStarted atau currentDay di sini + // agar bisa melanjutkan dari data EEPROM jika inkubasi sudah berjalan. + // tanggalMulai akan disesuaikan di bawah jika 'now < tanggalMulai' + } + + // Baca tanggalMulai dari EEPROM. Ini adalah tanggal yang kita simpan saat siklus dimulai. + tanggalMulai = bacaTanggalMulai(); + } + + DateTime now = rtc.now(); + if (now < tanggalMulai) { + Serial.println("Peringatan: Waktu RTC saat ini lebih awal dari tanggal mulai yang dibaca dari EEPROM."); + Serial.println("Ini mungkin indikasi RTC disetel mundur atau masalah sinkronisasi."); + // PENTING: Kita TIDAK mereset isIncubationStarted atau currentDay di sini. + // Hanya sesuaikan tanggalMulai agar tidak ada error perhitungan durasi negatif. + tanggalMulai = now; + simpanTanggalMulai(tanggalMulai); // Simpan tanggalMulai yang baru (now) + // currentDay akan diatur berdasarkan lastSavedDay di bagian bawah setup() + } + + // Penentuan mode awal saat boot + if (isIncubationStarted) { + // Jika inkubasi sudah dimulai (dari EEPROM), paksa ke mode otomatis + isAutoMode = true; + // Kita telah membaca lastSavedDay sebelumnya. Gunakan nilai itu! + currentDay = lastSavedDay; + Serial.printf("Sistem dinyalakan, inkubasi sudah berjalan. Mode OTOMATIS aktif, melanjutkan dari Hari ke-%d.\n", currentDay); + } else { + // Jika inkubasi belum dimulai, default ke mode manual + isAutoMode = false; + currentDay = 1; // Hari ke-1 jika siklus belum dimulai + simpanHariTerakhir(currentDay); // Pastikan hari ke-1 disimpan juga + Serial.println("Sistem dinyalakan, inkubasi belum dimulai. Mode MANUAL aktif secara default."); + } + // --- AKHIR LOGIKA INISIALISASI RTC & EEPROM --- + + connectToWiFi(); // Coba koneksi WiFi + + // --- WEB SERVER HANDLERS --- + server.serveStatic("/", SPIFFS, "/").setDefaultFile("login1.html"); + + server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) { + request->redirect("/login1.html"); + }); + + server.on("/data", HTTP_GET, [](AsyncWebServerRequest * request) { + bacaSensor(); + // hitungHariInkubasi(); // Tidak perlu di sini, currentDay sudah diatur oleh logika di loop/setup + + String json = "{"; + json += "\"temp\":" + String(suhu, 2) + ","; + json += "\"hum\":" + String(kelembapan, 2) + ","; + json += "\"day\":" + String(currentDay) + ","; + json += "\"sudutVentilasi\":" + String(currentServoPos) + ","; + + String rackAngleString = ""; + + if (posisiRakMiring()) { + rackAngleString = "Miring"; + } else if (posisiRakTegak()) { + rackAngleString = "Tegak"; + } else { + rackAngleString = "Belum Inisiasi"; + } + json += "\"sudutRak\":\"" + rackAngleString + "\","; + + int pwmDisplayValue = (currentPwmDuty*100) / MAX_PWM_DUTY; + + json += "\"pwmHeater\":" + String(pwmDisplayValue) + ","; + json += "\"wifiStatus\":\"" + String(isWiFiConnected ? "CONNECTED" : "DISCONNECTED") + "\","; + json += "\"isAutoMode\":" + String(isAutoMode ? "true" : "false") + ","; // Tambahkan status mode + json += "\"isIncubationStarted\":" + String(isIncubationStarted ? "true" : "false"); // Tambahkan status siklus + + json += ",\"isHeaterOn\":" + String(isHeaterOn ? "true" : "false"); + json += ",\"isMistMakerOn\":" + String(isMistMakerOn ? "true" : "false"); + json += ",\"isRackMotorMoving\":" + String(motorNyala ? "true" : "false"); + + json += ","; // Tambahkan koma sebelum menambahkan item baru + json += "\"currentRtcTime\":" + String(rtc.now().unixtime() * 1000); + json += ",\"startDate\":\""; // Tambahkan koma sebelum startDate + if (isIncubationStarted) { + char dateBuffer[11]; // YYYY-MM-DD\0 + dateBuffer[10] = 0; + sprintf(dateBuffer, "%04d-%02d-%02d", tanggalMulai.year(), tanggalMulai.month(), tanggalMulai.day()); + json += String(dateBuffer); + } else { + json += ""; // Kosong jika inkubasi belum dimulai + } + json += "\""; + json += "}"; + + request->send(200, "application/json", json); + }); + + + server.on("/setday", HTTP_POST, + [](AsyncWebServerRequest * request) { + request->send(200, "application/json", "{\"message\":\"OK\"}"); + }, + NULL, + [](AsyncWebServerRequest * request, uint8_t *data, size_t len, size_t index, size_t total) { + static String body; + if (index == 0) body = ""; + for (size_t i = 0; i < len; i++) { + body += (char)data[i]; + } + if (index + len == total) { + DynamicJsonDocument doc(256); + auto error = deserializeJson(doc, body); + if (!error) { + int day = doc["day"]; + if (day >= 1 && day <= 21) { + //set pwm ke max saat pertama kali aktif + targetPwmDuty = MAX_PWM_DUTY; + currentPwmDuty = MAX_PWM_DUTY; + overrideDay = day; // overrideDay akan digunakan oleh setTanggalMulaiManualHariKe + // Panggil fungsi setTanggalMulaiManualHariKe untuk mengatur hari dan menyimpannya + setTanggalMulaiManualHariKe(day); // Ini akan meng-update currentDay dan menyimpannya ke EEPROM + Serial.printf("Hari inkubasi di-set manual ke %d dari web, mode AUTO aktif\n", day); + } else { + Serial.println("Nilai hari inkubasi tidak valid dari web."); + } + } else { + Serial.println("JSON invalid diterima di /setday dari web"); + Serial.println(error.c_str()); + } + } + }); + + + server.on("/stop", HTTP_POST, [](AsyncWebServerRequest * request) { + isAutoMode = false; + overrideDay = 0; + currentDay = 1; // Reset hari + tanggalMulai = rtc.now(); // Reset tanggal mulai ke sekarang + simpanTanggalMulai(tanggalMulai); + simpanStatusInkubasi(false); + isAutoMode = false; + Serial.println("Sistem diatur ke Mode MANUAL."); // Debug + simpanHariTerakhir(1); + clearDailyHistory(); // <--- PENTING: Kosongkan histori saat stop! + matikanSemua(); + Serial.println("🛑 Inkubasi dihentikan manual dari dashboard"); + request->send(200, "text/plain", "Inkubasi dihentikan"); + }); + + server.on("/toggleRack", HTTP_POST, [](AsyncWebServerRequest * request) { + // Izinkan putaran rak manual HANYA jika inkubasi TIDAK berjalan + if (!isIncubationStarted) { // <--- KONDISI BARU: Cek isIncubationStarted + if (posisiRakMiring()) { + RotateZero(); + request->send(200, "application/json", "{\"message\":\"Memulai putaran rak ke tegak.\"}"); + } else if (posisiRakTegak()) { + RotateAgain(); + request->send(200, "application/json", "{\"message\":\"Memulai putaran rak ke miring.\"}"); + } else { + RotateZero(); + request->send(200, "application/json", "{\"message\":\"Memulai putaran rak ke tegak.\"}"); + } + } else { // Jika inkubasi sedang berjalan + Serial.println("🚫 Gerakan rak manual ditolak: Inkubasi sedang berjalan."); + request->send(403, "application/json", "{\"message\":\"Rak tidak dapat diputar secara manual saat inkubasi berjalan.\"}"); + } + }); + + server.on("/setmode/auto", HTTP_POST, [](AsyncWebServerRequest * request) { + isAutoMode = true; + Serial.println("Mode: OTOMATIS aktif (via web)."); + request->send(200, "text/plain", "Mode OTOMATIS aktif."); + }); + + server.on("/setmode/manual", HTTP_POST, [](AsyncWebServerRequest * request) { + if (!isIncubationStarted) { // Hanya izinkan manual jika inkubasi belum dimulai + isAutoMode = false; + + Serial.println("Mode: MANUAL aktif (via web)."); + request->send(200, "text/plain", "Mode MANUAL aktif."); + } else { + isAutoMode = true; // Paksa kembali ke otomatis jika sudah dimulai + Serial.println("Inkubasi sudah dimulai. Mode MANUAL tidak diizinkan (via web)."); + request->send(403, "text/plain", "Mode MANUAL tidak diizinkan setelah inkubasi dimulai."); + } + }); + + server.onNotFound([](AsyncWebServerRequest * request) { + request->send(404, "text/plain", "File Not Found"); + }); + + server.begin(); + webServerStarted = true; + Serial.println("Web server dimulai."); + + //buat rak telur + setupRakTelur(); + //delay(2000); + +} + +void loop() { + unsigned long nowMillis = millis(); + + //limit stack call + if (nowMillis-skipTick>=50){ + skipTick=nowMillis; + }else{ + return; + } + + if (Serial.available()) { + String input = Serial.readStringUntil('\n'); + input.trim(); + if (input.startsWith("start ")) { + int d, m, y; + if (sscanf(input.c_str(), "start %d-%d-%d", &d, &m, &y) == 3) { + tanggalMulai = DateTime(y, m, d); + simpanTanggalMulai(tanggalMulai); + + currentDay = 1; + simpanHariTerakhir(currentDay); + + isAutoMode = true; + overrideDay = 0; + simpanStatusInkubasi(true); + clearDailyHistory(); // <--- TAMBAHKAN BARIS INI + Serial.printf("Inkubasi dimulai dengan tanggal: %02d-%02d-%04d, hari ke-%d.\n", d, m, y, currentDay); + } else { + Serial.println("Format salah! Gunakan: start DD-MM-YYYY"); + } + } else if (input.startsWith("setday ")) { + int h = input.substring(7).toInt(); + setTanggalMulaiManualHariKe(h); // Fungsi ini sudah memanggil simpanStatusInkubasi(true) dan simpanHariTerakhir(currentDay) + } else if (input.equalsIgnoreCase("auto")) { + isAutoMode = true; + Serial.println("Mode: OTOMATIS aktif (termasuk motor)."); + } else if (input.equalsIgnoreCase("manual")) { + if (!isIncubationStarted) { // Hanya izinkan manual jika inkubasi belum dimulai + isAutoMode = false; + Serial.println("Mode: MANUAL aktif (termasuk motor)."); + + } else { + Serial.println("Inkubasi sudah dimulai. Mode MANUAL tidak diizinkan."); + isAutoMode = true; // Paksa kembali ke otomatis jika sudah dimulai + } + } else if (input.equalsIgnoreCase("calibrate")) { + Serial.println("Perintah kalibrasi AS5600 manual tidak didukung lagi."); + Serial.println("Titik 0 derajat AS5600 otomatis disetel saat boot."); + } + } + + ////Serial.println("Debug point: 1"); + + // --- LOGIKA UNTUK MAJU HARI SECARA OTOMATIS SAAT INKUBASI BERJALAN --- + // Ini akan memastikan currentDay maju sesuai waktu kalender jika ESP tidak mati. + // Jika ESP mati dan menyala kembali, currentDay akan melanjutkan dari lastSavedDay yang dimuat di setup. + if (isIncubationStarted && overrideDay == 0) { // Hanya saat inkubasi otomatis dan tidak di-override + DateTime now = rtc.now(); + TimeSpan durasi = now - tanggalMulai; + int calculatedDay = durasi.days() + 1; // Hitung hari berdasarkan RTC dan tanggalMulai + + // Jika hari yang dihitung berbeda dari currentDay yang sedang berjalan, update dan simpan + // Ini memastikan currentDay tidak melompat maju jika ada pemadaman singkat + if (calculatedDay != currentDay && calculatedDay >= 1 && calculatedDay <= 21) { + currentDay = calculatedDay; + simpanHariTerakhir(currentDay); // <--- SIMPAN PERUBAHAN HARI OTOMATIS + Serial.printf("Hari inkubasi otomatis berubah ke Hari ke-%d. (%s)\n", currentDay, now.timestamp().c_str()); + } + } + // Jika tidak di isIncubationStarted, atau ada overrideDay, currentDay akan diatur secara eksplisit. + // Maka currentDay tidak akan maju otomatis disini. + + //Serial.println("Debug point: 2"); + + if (nowMillis - lastDHTRead >= DHT_INTERVAL) { + bacaSensor(); + lastDHTRead = nowMillis; + } + + //Serial.println("Debug point: 3"); + + if (isAutoMode) { + //heaterState = suhu < 37.3; + + //if (heaterState) { + // targetPwmDuty = MAX_PWM_DUTY; + //} else { + // if (suhu >= 37.7) { + // targetPwmDuty = 0; + // } else { + // + // } + //} + + updateHeaterPWM(); + + isHeaterOn = (targetPwmDuty > 0); + + digitalWrite(FAN_PIN, isHeaterOn ? HIGH : LOW); // Fan 1 nyala saat heater nyala + + bool fan2State = false; + if (suhu > 38.0 || kelembapan > 70 ) { + fan2State = true; + } + else if (suhu < 37.6 && kelembapan < 68) { + fan2State = false; // Set kipas mati + } + digitalWrite(FAN_PIN_2, fan2State ? HIGH : LOW); + + float targetRH = (currentDay <= 18) ? 55 : 68; // Target RH 55% untuk hari 1-18, 68% untuk hari 19-21 + float turnOnRH; + if (targetRH == 55) { + turnOnRH = 53; // Nyala di 53% jika target 55% + } else { // targetRH == 68 + turnOnRH = 65; // Nyala di 65% jika target 68% + } + + if (kelembapan < turnOnRH) { + mistState = true; + } + else if (kelembapan >= targetRH) { + mistState = false; + } + digitalWrite(MISTMAKER_PIN, mistState ? HIGH : LOW); + isMistMakerOn = mistState; + + switch (currentDay) { // currentDay sudah diatur di setup atau diperbarui di loop + case 1: case 2: case 3: + servoAngleByDay = 40; + break; + case 4: + servoAngleByDay = 60; + break; + case 5: + servoAngleByDay = 85; + break; + case 6: + servoAngleByDay = 110; + break; + case 7: case 8: case 9: case 10: case 11: case 12: case 13: case 14: case 15: case 16: case 17: + servoAngleByDay = 135; + break; + case 18: case 19: case 20: case 21: + servoAngleByDay = 40; // Kembali ke 40 derajat saat periode penetasan + break; + default: + servoAngleByDay = 40; // Default untuk hari di luar range + break; + } + + //Serial.println("Debug point: 4"); + + if (currentServoPos != servoAngleByDay) { // Cek apakah posisi target berbeda dari posisi saat ini + if (bolehGerakKe(servoAngleByDay)) { // Cek apakah servo diizinkan untuk bergerak + if (!myServo.attached()) { // Pastikan servo terhubung (attached) + myServo.attach(SERVO_PIN); + Serial.println("Servo di-ATTACH"); // Debugging: Konfirmasi attach + } + myServo.write(servoAngleByDay); + //Serial.printf("✅ Servo ventilasi digerakkan ke %d°\n", servoAngleByDay); // Konfirmasi perintah gerakan + Serial.print("✅ Servo ventilasi digerakkan ke "); // Konfirmasi perintah gerakan + Serial.println(servoAngleByDay); + delay(200); + myServo.detach(); + Serial.println("Servo di-DETACH"); // Debugging: Konfirmasi detach + currentServoPos = servoAngleByDay; // Perbarui variabel posisi servo saat ini + detachServoFlag = true; + detachServoTick = millis(); + } else { + Serial.println("🚫 Gerakan servo ditolak karena limit switch atau sudut tidak valid."); + } + }else{ + if (detachServoFlag){ + if (millis()-detachServoTick>=500){ + detachServoFlag = false; + myServo.detach(); + Serial.println("Servo di-DETACH"); // Debugging: Konfirmasi detach + } + } + } + } else { // Mode MANUAL + targetPwmDuty = 0; + updateHeaterPWM(); + isHeaterOn = false; + digitalWrite(FAN_PIN, LOW); + digitalWrite(FAN_PIN_2, LOW); + digitalWrite(MISTMAKER_PIN, LOW); + isMistMakerOn = false; + myServo.write(40); // Setel servo ke posisi default di mode manual + currentServoPos = 40; + } + + //Serial.println("Debug point: 5"); + + if (nowMillis - lastStatusPrint >= STATUS_PRINT_INTERVAL) { + //Serial.println("Debug point: 5.1"); + tampilkanStatus(); + //Serial.println("Debug point: 5.2"); + lastStatusPrint = nowMillis; + } + + //Serial.println("Debug point: 5.10"); + + if (nowMillis - lastLcdUpdate >= LCD_UPDATE_INTERVAL) { + //if (Wire.endTransmission()){ + lcd.clear(); + lcd.setCursor(0, 0); + lcd.printf("S: %.1fC K:%.0f%%", suhu, kelembapan); + lcd.setCursor(0, 1); + + if (isWiFiConnected) { + lcd.printf("Hari: %d | WiFi OK", currentDay); + } else { + lcd.printf("Hari: %d | No WiFi", currentDay); + } + + if (nowMillis - lastWiFiReconnectAttempt >= WIFI_RECONNECT_INTERVAL) { + connectToWiFi(); + lastWiFiReconnectAttempt = nowMillis; + } + //} + Wire.endTransmission(); + + lastLcdUpdate = nowMillis; + } + + //Serial.println("Debug point: 6"); + + if (nowMillis - lastTickUpdate >= 1000){ + if (isIncubationStarted && overrideDay == 0) { + DateTime now = rtc.now(); + TimeSpan durasi = now - tanggalMulai; + int calculatedDay = durasi.days() + 1; + + if (calculatedDay != currentDay && calculatedDay >= 1 && calculatedDay <= 21) { + saveDailyHistory(calculatedDay - 1, suhu, kelembapan, currentServoPos); + + currentDay = calculatedDay; + simpanHariTerakhir(currentDay); // <--- SIMPAN PERUBAHAN HARI OTOMATIS + //Serial.printf("Hari inkubasi otomatis berubah ke Hari ke-%d. (%s)\n", currentDay, now.timestamp().c_str()); + String s = "Hari inkubasi otomatis berubah ke Hari ke-" + String(currentDay); + Serial.println(s); + } + } + lastTickUpdate = nowMillis; + } + + //Serial.println("Debug point: 7"); + + RotateRoutine(); + + //Serial.println("Debug point: 8"); +} + +void matikanSemua() { + targetPwmDuty = MAX_PWM_DUTY; //set to max again after off + currentPwmDuty = 0; + ledcWrite(HEATER_PWM_CHANNEL, 0); + Serial.println("Heater dimatikan."); + + digitalWrite(FAN_PIN, LOW); + digitalWrite(FAN_PIN_2, LOW); + Serial.println("Fan dimatikan."); + digitalWrite(MISTMAKER_PIN, LOW); + Serial.println("Mistmaker dimatikan."); + myServo.write(40); // Setel servo ke posisi default + currentServoPos = 40; + Serial.println("Servo ventilasi disetel ke 40°."); + + RotateZero(); + Serial.println("Rak telur tegak"); + + Serial.println("Semua aktuator dimatikan."); +} + +void tampilkanStatus() { + //Serial.println("Debug point -> tampilkanStatus: 1"); + + String rackAngleString = ""; + rackAngleString = "Belum inisiasi"; + if (posisiRakMiring()) rackAngleString = "Miring"; + if (posisiRakTegak()) rackAngleString = "Tegak"; + int pwmDisplayValue = (currentPwmDuty*100) / MAX_PWM_DUTY; + + // Serial.printf("Suhu: %.2f°C, RH: %.2f%%, Hari inkubasi: %d, Sudut ventilasi: %d°, Heater PWM: %d, Fan: %s, Mistmaker: %s, Motor: %s | Mode Sistem: %s | Inkubasi: %s | WiFi: %s\n", + // suhu, kelembapan, currentDay, currentServoPos, pwmDisplayValue, + // heaterState ? "ON" : "OFF", + // mistState ? "ON" : "OFF", + // isAutoMode ? "OTOMATIS" : "MANUAL", + // isIncubationStarted ? "DIMULAI" : "BELUM DIMULAI", // Tambahkan status inkubasi + // isWiFiConnected ? "TERHUBUNG" : "TERPUTUS"); + Serial.print("Suhu: " + String(suhu, 2) + "°C, "); + Serial.print("RH: " + String(kelembapan, 2) + "%, "); + Serial.print("Hari inkubasi: " + String(currentDay) + ", "); + Serial.print("Sudut ventilasi: " + String(currentServoPos) + ", "); + Serial.print("Heater PWM: " + String(pwmDisplayValue) + ", "); + Serial.print("posisi rak: " + rackAngleString + ", "); + Serial.print("Motor Rak: " + String(motorNyala ? "ON" : "OFF") + ", "); + + Serial.println(); + //Serial.println("Debug point -> tampilkanStatus: 2"); +} + +bool bolehGerakKe(int targetAngle) { + if (digitalRead(LIMIT_SWITCH_PIN) == LOW) { + Serial.println("Limit switch tertekan, gerakan servo dicegah."); + return false; + } + // Batasi rentang gerak servo + if (targetAngle < 40 || targetAngle > 135) { + Serial.printf("Sudut target %d° di luar rentang yang diizinkan (40-135°).\n", targetAngle); + return false; + } + return true; +} + + +//urusan rak telur +void setupRakTelur() { + Serial.println("Melakukan Setup Rak Telur..."); + pinMode(HALL_DO_PIN, INPUT); + pinMode(TRIAC_PIN, OUTPUT); + //Wire.begin(32, 33); + encoder.begin(); + lastStartTime = millis(); + + if (isIncubationStarted) { + if (currentDay >= 19) { + RotateZero(); + } else { + RotateAgain(); + } + } else { + RotateZero(); + Serial.println("Menegakkan rak telur."); + } + tRotate = millis(); + Serial.println("Setup Rak Telur OK."); +} + +void SetMotor(bool state) { + if (motorNyala != state) { + motorNyala = state; + digitalWrite(TRIAC_PIN, state ? HIGH : LOW); + Serial.println("Motor: " + (String) (state ? "ON" : "OFF")); + + } +} + +bool RotateZero() { + // int raw = encoder.rawAngle(); + // uint8_t i2cError = Wire1.lastError(); + // if (i2cError != 0) { + // SetMotor(false); + // rotateState = ROT_NONE; + // rotateState = rotateStateTarget; + // Serial.println("Gagal membaca sensor sudut!"); + // return false; + // } + + if (rotateStateTarget != ROT_ZERO) { + rotateStateTarget = ROT_ZERO; + SetMotor(true); + } + + //tunggu sampai kena hall sensor + if (rotateStateTarget != rotateState) { + bool hall = !((bool)digitalRead(HALL_DO_PIN)); + if (hall) { + rotateState = rotateStateTarget; + SetMotor(false); + sudutZero = encoder.rawAngle(); + sudutZeroDerajat = (sudutZero * 360) / 4096; + sudutSekarang = 0; + sudutSekarangDerajat = 0; + encoder.setOffset(0); + encoder.resetPosition(0); + Serial.print("Sudut tengah: raw "); + Serial.print(sudutZero); + Serial.print(", derajat "); + Serial.println(sudutZeroDerajat); + //tRotate = millis(); + return true; + } + } + + return false; +} + +bool RotateAgain() { + if (rotateStateTarget == ROT_SKEW) { + if (rotateState == ROT_SKEW) { + rotateState = ROT_NONE; + rotateStateTarget = ROT_NONE; + if (putarRakByTimer) { + RotateByTimer(); + } else { + Rotate(); + } + Serial.println("Memutar rak..."); + return true; + } + } else if ((rotateState == ROT_NONE && rotateStateTarget == ROT_NONE) || (rotateState == ROT_ZERO && rotateStateTarget == ROT_ZERO) ) { + if (putarRakByTimer) { + RotateByTimer(); + } else { + Rotate(); + } + Serial.println("Memutar rak..."); + return true; + } + Serial.println("Gagal memutar rak!"); + Serial.println("rotateState: " + String(rotateState)); + Serial.println("rotateStateTarget: " + String(rotateStateTarget)); + return false; +} + +bool RotateByTimer() { + if (rotateStateTarget != ROT_SKEW) { + rotateStateTarget = ROT_SKEW; + bool hall = !((bool)digitalRead(HALL_DO_PIN)); + if (hall) { + sudutZero = encoder.rawAngle(); + sudutZeroDerajat = (sudutZero * 360) / 4096; + rotateFindZero = false; + encoder.setOffset(0); + encoder.resetPosition(0); + Serial.print("Sudut tengah: raw "); + Serial.print(sudutZero); + Serial.print(", derajat "); + Serial.println(sudutZeroDerajat); + } else { + rotateFindZero = true; + Serial.println("Cari sudut tengah dulu..."); + } + tStopPutar = millis(); + SetMotor(true); + } + + bool hall = false; + if (rotateStateTarget != rotateState) { + if (rotateFindZero) { + hall = !((bool)digitalRead(HALL_DO_PIN)); + if (hall) { + rotateFindZero = false; + sudutZero = encoder.rawAngle(); + sudutZeroDerajat = (sudutZero * 360) / 4096; + encoder.setOffset(0); + encoder.resetPosition(0); + Serial.print("Update Sudut tengah: raw "); + Serial.print(sudutZero); + Serial.print(", derajat "); + Serial.println(sudutZeroDerajat); + tStopPutar = millis(); + } + } else { + hall = !((bool)digitalRead(HALL_DO_PIN)); + if (hall) { + rotateFindZero = false; + sudutZero = encoder.rawAngle(); + sudutZeroDerajat = (sudutZero * 360) / 4096; + encoder.setOffset(0); + encoder.resetPosition(0); + tStopPutar = millis(); + } + sudutSekarang = encoder.rawAngle(); + sudutSekarangDerajat = abs(((sudutSekarang * 360) / 4096) - sudutZeroDerajat); + float angular = encoder.getAngularSpeed(); + Serial.print("Sudut: "); + Serial.print(sudutSekarangDerajat); + Serial.print(", angular speed "); + Serial.println(angular); + if ((sudutSekarangDerajat >= 45) || (millis() - tStopPutar >= 7000)) { + rotateState = rotateStateTarget; + SetMotor(false); + tRotate = millis(); + Serial.println("Berhenti."); + return true; + } + } + } + + return false; +} + +bool Rotate() { + // int raw = encoder.rawAngle(); + // uint8_t i2cError = Wire1.lastError(); + // if (i2cError != 0) { + // SetMotor(false); + // rotateState = ROT_NONE; + // rotateState = rotateStateTarget; + // Serial.println("Gagal membaca sensor sudut!"); + // return false; + // } + + if (rotateStateTarget != ROT_SKEW) { + rotateStateTarget = ROT_SKEW; + bool hall = !((bool)digitalRead(HALL_DO_PIN)); + if (hall) { + sudutZero = encoder.rawAngle(); + sudutZeroDerajat = (sudutZero * 360) / 4096; + rotateFindZero = false; + encoder.setOffset(0); + encoder.resetPosition(0); + Serial.print("Sudut tengah: raw "); + Serial.print(sudutZero); + Serial.print(", derajat "); + Serial.println(sudutZeroDerajat); + } else { + rotateFindZero = true; + Serial.println("Cari sudut tengah dulu..."); + } + SetMotor(true); + } + + bool hall = false; + if (rotateStateTarget != rotateState) { + if (rotateFindZero) { + hall = !((bool)digitalRead(HALL_DO_PIN)); + if (hall) { + rotateFindZero = false; + sudutZero = encoder.rawAngle(); + sudutZeroDerajat = (sudutZero * 360) / 4096; + encoder.setOffset(0); + encoder.resetPosition(0); + Serial.print("Update Sudut tengah: raw "); + Serial.print(sudutZero); + Serial.print(", derajat "); + Serial.println(sudutZeroDerajat); + } + } else { + hall = !((bool)digitalRead(HALL_DO_PIN)); + if (hall) { + rotateFindZero = false; + sudutZero = encoder.rawAngle(); + sudutZeroDerajat = (sudutZero * 360) / 4096; + encoder.setOffset(0); + encoder.resetPosition(0); + } + sudutSekarang = encoder.rawAngle(); + sudutSekarangDerajat = abs(((sudutSekarang * 360) / 4096) - sudutZeroDerajat); + float angular = encoder.getAngularSpeed(); + Serial.print("Sudut: "); + Serial.print(sudutSekarangDerajat); + Serial.print(", angular speed "); + Serial.println(angular); + if (sudutSekarangDerajat >= 45) { + rotateState = rotateStateTarget; + SetMotor(false); + tRotate = millis(); + Serial.println("Berhenti."); + return true; + } + } + } + + return false; +} + +void RotateRoutine() { + if (isAutoMode && isIncubationStarted) { + //if (true) { + if (!motorNyala) { + if (currentDay <= 19) { + if (((millis() - tRotate) >= jedaPutarRak) || posisiRakTegak()) { + //Serial.println("debug r3"); + tRotate = millis(); + RotateAgain(); + Serial.println("Memutar rak telur..."); + } + } else { + RotateZero(); + } + } + } + + if (rotateStateTarget != rotateState) { + if (rotateStateTarget == ROT_ZERO) { + RotateZero(); + } else if (rotateStateTarget == ROT_SKEW) { + if (putarRakByTimer) { + RotateByTimer(); + } else { + Rotate(); + } + } + } +} + +bool posisiRakMiring() { + return (rotateState == ROT_SKEW); +} + +bool posisiRakTegak() { + return (rotateState == ROT_ZERO); +}