432 lines
14 KiB
C++
432 lines
14 KiB
C++
// ======= IMPORT LIBRARY & DEFINISI PIN =======
|
||
#include <DHT.h>
|
||
#include <ESP32Servo.h>
|
||
#include <Wire.h>
|
||
#include <RTClib.h>
|
||
|
||
#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;
|
||
}
|