// ======= IMPORT LIBRARY & DEFINISI PIN ======= #include #include #include #include #define DHTPIN 4 #define DHTTYPE DHT22 #define FAN_PIN_2 26 #define SERVO_PIN 14 #define LIMIT_SWITCH_PIN 27 #define I2C_SDA_PIN_0 21 #define I2C_SCL_PIN_0 22 // ======= DEKLARASI OBJEK ======= DHT dht(DHTPIN, DHTTYPE); Servo myServo; RTC_DS3231 rtc; // ======= VARIABEL GLOBAL ======= float suhu = NAN; float kelembapan = NAN; bool isAutoMode = false; bool isIncubationStarted = false; int currentDay = 1; int currentServoPos = 40; int servoAngleByDay = 40; unsigned long lastDHTRead = 0; const unsigned long DHT_INTERVAL = 10000; DateTime tanggalMulai; int overrideDay = 0; unsigned long lastStatusPrint = 0; const unsigned long STATUS_PRINT_INTERVAL = 2000; // ======= ARRAY UNTUK FILTER SENSOR DHT ======= const int NUM_DHT_READINGS = 10; float dhtTempReadings[NUM_DHT_READINGS]; float dhtHumReadings[NUM_DHT_READINGS]; int dhtReadingIndex = 0; bool dhtReadingsInitialized = false; // ======= ARRAY UNTUK HISTORI HARIAN ======= float dailyTemperatures[21]; float dailyHumidities[21]; int dailyVentAngles[21]; // ======= VARIABEL TAMBAHAN SISTEM ======= int angleReadingIndex = 0; bool angleReadingsInitialized = false; uint32_t lastTickUpdate = 0; bool detachServoFlag = false; uint32_t detachServoTick = 0; uint32_t skipTick = 0; // ======= FUNGSI PERHITUNGAN HARI INKUBASI ======= void hitungHariInkubasi() {   if (overrideDay > 0) {   } else if (!isIncubationStarted) {   } else {     DateTime now = rtc.now();     TimeSpan durasi = now - tanggalMulai;     currentDay = durasi.days() + 1;     if (currentDay < 1) currentDay = 1;     if (currentDay > 21) currentDay = 21;   } } // ======= FUNGSI SET TANGGAL MULAI INKUBASI MANUAL ======= void setTanggalMulaiManualHariKe(int hariKe) {   DateTime now = rtc.now();   if (hariKe < 1) hariKe = 1;   if (hariKe > 21) hariKe = 21;   DateTime newStart = now - TimeSpan(hariKe - 1, 0, 0, 0);   tanggalMulai = newStart;   overrideDay = 0;   currentDay = hariKe;   isAutoMode = true;   isIncubationStarted = true;   tampilkanStatus();   Serial.printf("Tgl mulai diset ke %02d-%02d-%04d (Hari %d), mode AUTO aktif.\n",                 tanggalMulai.day(), tanggalMulai.month(), tanggalMulai.year(), hariKe); } // ======= FUNGSI BACA SUHU TERFILTER ======= 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;   }   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;   return sum / count; } // ======= FUNGSI BACA KELEMBAPAN TERFILTER ======= 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;   }   dhtHumReadings[dhtReadingIndex] = rawHum;   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;   return sum / count; } // ======= FUNGSI BACA SENSOR DHT SECARA PERIODIK ======= void bacaSensor() {   suhu = readFilteredTemperature();   kelembapan = readFilteredHumidity();   Serial.printf("DHT: Suhu=%.2fC, Kelembapan=%.2f%%\n", suhu, kelembapan); } // ======= FUNGSI SIMPAN HISTORI HARIAN KE ARRAY ======= void saveDailyHistory(int dayIndex, float temp, float hum, int ventAngle) {   if (dayIndex >= 0 && dayIndex < 21) {     dailyTemperatures[dayIndex] = temp;     dailyHumidities[dayIndex] = hum;     dailyVentAngles[dayIndex] = ventAngle;     Serial.printf("Riwayat Hari %d tersimpan: S=%.2f, K=%.2f, Vent=%d\n",                   dayIndex + 1, temp, hum, ventAngle);   } else {     Serial.println("Indeks hari tidak valid.");   } } // ======= FUNGSI RESET HISTORI HARIAN ======= void loadDailyHistory() {   Serial.println("Inisialisasi riwayat harian...");   for (int i = 0; i < 21; i++) {     dailyTemperatures[i] = 0.0;     dailyHumidities[i] = 0.0;     dailyVentAngles[i] = 0;   }   Serial.println("Riwayat harian terinisialisasi."); } void clearDailyHistory() {   Serial.println("Mengosongkan riwayat harian (RAM)...");   for (int i = 0; i < 21; i++) {     dailyTemperatures[i] = 0.0;     dailyHumidities[i] = 0.0;     dailyVentAngles[i] = 0;   }   Serial.println("Riwayat harian terkuras (RAM)."); } // ======= FUNGSI SETUP AWAL SISTEM ======= void setup() {   delay(2000);   Serial.begin(115200);   loadDailyHistory();   Wire.begin(I2C_SDA_PIN_0, I2C_SCL_PIN_0);   Serial.printf("I2C Bus 0 aktif: SDA=%d, SCL=%d\n", I2C_SDA_PIN_0, I2C_SCL_PIN_0);   Serial.println("Inkubator ON");   Serial.println("Memuat...");   delay(2000);   angleReadingsInitialized = true;   angleReadingIndex = 0;   dht.begin();   Serial.println("Sensor DHT22 aktif.");   pinMode(FAN_PIN_2, OUTPUT);   pinMode(LIMIT_SWITCH_PIN, INPUT_PULLUP);   digitalWrite(FAN_PIN_2, LOW);   myServo.attach(SERVO_PIN);   myServo.write(currentServoPos);   if (!rtc.begin(&Wire)) {     Serial.println("RTC tidak terdeteksi/gagal!");     tanggalMulai = DateTime(F(__DATE__), F(__TIME__));     isIncubationStarted = false;     currentDay = 1;   } else {     Serial.println("RTC berhasil aktif.");     if (rtc.lostPower()) {       rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));     }     isIncubationStarted = false;     currentDay = 1;     tanggalMulai = rtc.now();   }   if (rtc.now() < tanggalMulai) {     tanggalMulai = rtc.now();   }   isAutoMode = false;   currentDay = 1;   Serial.println("Sistem ON. Mode MANUAL aktif."); } // ======= FUNGSI LOOP UTAMA ======= void loop() {   unsigned long nowMillis = millis();   // Filter agar loop tidak terlalu sering jalan   if (nowMillis - skipTick >= 50) {     skipTick = nowMillis;   } else {     return;   }   // ======= BAGIAN PEMROSESAN PERINTAH DARI SERIAL =======   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);         currentDay = 1;         isAutoMode = true;         overrideDay = 0;         isIncubationStarted = true;         clearDailyHistory();         Serial.printf("Inkubasi mulai: %02d-%02d-%04d (Hari %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);     } else if (input.equalsIgnoreCase("auto")) {       if (isIncubationStarted) {         isAutoMode = true;         Serial.println("Mode: AUTO aktif.");       } else {         Serial.println("Inkubasi belum dimulai. Gunakan 'start DD-MM-YYYY'.");       }     } else if (input.equalsIgnoreCase("manual")) {       if (!isIncubationStarted) {         isAutoMode = false;         Serial.println("Mode: MANUAL aktif.");       } else {         Serial.println("Inkubasi sedang berjalan. Mode MANUAL tidak diizinkan.");         isAutoMode = true;       }     } else if (input.equalsIgnoreCase("stop")) {       isIncubationStarted = false;       isAutoMode = false;       currentDay = 1;       tanggalMulai = rtc.now();       clearDailyHistory();       matikanSemua();       Serial.println("Inkubasi berhenti. Aktuator OFF. Mode MANUAL aktif.");     }   }   // ======= BAGIAN PERHITUNGAN PERGANTIAN HARI INKUBASI OTOMATIS =======   if (isIncubationStarted && overrideDay == 0) {     DateTime now = rtc.now();     TimeSpan durasi = now - tanggalMulai;     int calculatedDay = durasi.days() + 1;     if (calculatedDay != currentDay && calculatedDay >= 1 && calculatedDay <= 21) {       if (currentDay >= 1 && currentDay <= 21) {         saveDailyHistory(currentDay - 1, suhu, kelembapan, currentServoPos);       }       currentDay = calculatedDay;       Serial.printf("Hari berubah ke Hari %d. (%s)\n", currentDay, now.timestamp().c_str());     }   }   // ======= BAGIAN PEMBACAAN SENSOR DHT PERIODIK =======   if (nowMillis - lastDHTRead >= DHT_INTERVAL) {     bacaSensor();     lastDHTRead = nowMillis;   }   // ======= BAGIAN MODE OTOMATIS KONTROL KIPAS DAN SERVO =======   if (isAutoMode) {     bool fan2State = false;     if (suhu > 38.0 || kelembapan > 70) {       digitalWrite(FAN_PIN_2, HIGH);       fan2State = true;     } else if (suhu < 37.6 && kelembapan < 68) {       digitalWrite(FAN_PIN_2, LOW);       fan2State = false;     }     digitalWrite(FAN_PIN_2, fan2State ? HIGH : LOW);     // ======= BAGIAN PENYESUAIAN SUDUT SERVO BERDASARKAN HARI INKUBASI =======     switch (currentDay) {       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 ... 17:         servoAngleByDay = 135;         break;       case 18: case 19: case 20: case 21:         servoAngleByDay = 40;         break;       default:         servoAngleByDay = 40;         break;     }     // ======= BAGIAN GERAKKAN SERVO VENTILASI JIKA PERLU =======     if (currentServoPos != servoAngleByDay) {       if (bolehGerakKe(servoAngleByDay)) {         myServo.write(servoAngleByDay);         Serial.printf("✅ Servo ke %d°\n", servoAngleByDay);         delay(200);         currentServoPos = servoAngleByDay;         detachServoFlag = true;         detachServoTick = millis();       } else {         Serial.println("🚫 Servo tertahan (limit switch/sudut tidak valid).");       }     } else {       if (detachServoFlag) {         if (millis() - detachServoTick >= 500) {           detachServoFlag = false;           myServo.detach();           Serial.println("Servo terlepas");         }       }     }   } else {     // ======= MODE MANUAL: KIPAS & SERVO KE KONDISI DEFAULT =======     digitalWrite(FAN_PIN_2, LOW);     myServo.write(40);     currentServoPos = 40;   }   // ======= TAMPILKAN STATUS KE SERIAL SETIAP INTERVAL =======   if (nowMillis - lastStatusPrint >= STATUS_PRINT_INTERVAL) {     tampilkanStatus();     lastStatusPrint = nowMillis;   }   // ======= PERHITUNGAN ULANG HARI INKUBASI TIAP 1 DETIK (REDUNDAN) =======   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(currentDay - 1, suhu, kelembapan, currentServoPos);         currentDay = calculatedDay;         Serial.printf("Hari berubah ke Hari %d.\n", currentDay);       }     }     lastTickUpdate = nowMillis;   } } // ======= FUNGSI MATIKAN SEMUA AKTUATOR ======= void matikanSemua() {   digitalWrite(FAN_PIN_2, LOW);   Serial.println("Kipas OFF.");   myServo.write(40);   currentServoPos = 40;   Serial.println("Servo ventilasi ke 40°.");   Serial.println("Semua aktuator OFF."); } // ======= FUNGSI TAMPILKAN STATUS SISTEM ======= void tampilkanStatus() {   Serial.printf("Status: S=%.2fC, K=%.2f%%, Hari=%d, Vent=%d°\n",                 suhu, kelembapan, currentDay, currentServoPos); } // ======= FUNGSI CEK APAKAH SERVO BOLEH BERGERAK KE SUDUT TERTENTU ======= bool bolehGerakKe(int targetAngle) {   if (digitalRead(LIMIT_SWITCH_PIN) == LOW) {     Serial.println("Limit switch aktif, servo diblokir.");     return false;   }   if (targetAngle < 40 || targetAngle > 135) {     Serial.printf("Sudut target %d° di luar jangkauan (40-135°).\n", targetAngle);     return false;   }   return true; }