1003 lines
26 KiB
C++
1003 lines
26 KiB
C++
#include <Arduino.h>
|
|
#include <WiFi.h>
|
|
#include <FirebaseESP32.h>
|
|
#include <WiFiManager.h>
|
|
#include <Wire.h>
|
|
#include "RTClib.h"
|
|
#include "time.h"
|
|
|
|
// ================= FIREBASE =================
|
|
#define FIREBASE_HOST "telurku-fa78c-default-rtdb.asia-southeast1.firebasedatabase.app"
|
|
#define FIREBASE_AUTH "3zofmC4xXE1g80NTQtB8X7JZp6eirMzUAwZC6iM8"
|
|
|
|
// ================= PIN =================
|
|
const int pinENA = 13;
|
|
const int pinIN1 = 12;
|
|
const int pinIN2 = 14;
|
|
const int pinIR1 = 34;
|
|
const int pinIR2 = 35;
|
|
const int pinIR3 = 32;
|
|
const int pinSDA = 21;
|
|
const int pinSCL = 22;
|
|
|
|
// ================= OBJEK =================
|
|
RTC_DS3231 rtc;
|
|
FirebaseData fbdo;
|
|
FirebaseAuth auth;
|
|
FirebaseConfig config;
|
|
|
|
// ================= VARIABEL SISTEM =================
|
|
// Counter telur dari kandang
|
|
int jumlahTelur1 = 0;
|
|
int jumlahTelur2 = 0;
|
|
|
|
// ===== CHECKPOINT UNTUK DELTA CALCULATION =====
|
|
// Ini TIDAK akan di-reset saat reset counter, hanya reset saat panen berhasil
|
|
int ir1CheckpointPanen = 0;
|
|
int ir2CheckpointPanen = 0;
|
|
int jumlahTelurMasuk = 0;
|
|
int totalTelurHarusKeBak = 0;
|
|
|
|
// Total telur yang sudah dipanen sebelumnya
|
|
int totalTelurSudahDipanen = 0;
|
|
|
|
// Target panen saat conveyor berjalan
|
|
int targetPanen = 0;
|
|
|
|
// Status sensor sebelumnya
|
|
int lastStatusIR1 = HIGH;
|
|
int lastStatusIR2 = HIGH;
|
|
int lastStatusIR3 = HIGH;
|
|
|
|
// Status sistem
|
|
bool motorBerjalan = false;
|
|
bool modePanenAktif = false;
|
|
|
|
bool dataTersinkron = false;
|
|
bool firebaseInitialized = false;
|
|
|
|
// Timer motor
|
|
unsigned long waktuMulaiMotor = 0;
|
|
unsigned long waktuMulaiFailsafe = 0;
|
|
|
|
// Durasi dari Firebase
|
|
int durasiTargetDetik = 0;
|
|
|
|
// Durasi motor yang dapat diperpanjang
|
|
unsigned long durasiMotorAkhir = 0;
|
|
|
|
// Waktu telur terakhir masuk ke bak (untuk cek durasi tanpa telur)
|
|
unsigned long waktuTelurTerakhirMasuk = 0;
|
|
|
|
// Flag untuk mencegah penambahan durasi berkali-kali
|
|
bool durasiSudahDitambahkan = false;
|
|
|
|
// Waktu ketika target telur tercapai di IR3
|
|
unsigned long waktuTargetTercapai = 0;
|
|
|
|
// Timeout setelah target tercapai sebelum motor OFF (10 detik)
|
|
const unsigned long timeoutMotorAfterTarget = 10000;
|
|
|
|
// Flag untuk motor yang sedang dalam proses "extra push" mengangkut sisa telur
|
|
bool motorSedangPushSisa = false;
|
|
|
|
// Timeout darurat 3 menit
|
|
const unsigned long timeoutFailsafe = 180000;
|
|
|
|
// ===== RESET COUNTER PENDING (event-based, bukan time-based) =====
|
|
bool resetCounterPending = false; // Flag: diminta untuk reset setelah panen selesai
|
|
|
|
// ================= TIMER TASK =================
|
|
|
|
unsigned long lastTaskSensor = 0;
|
|
unsigned long lastTaskFirebase = 0;
|
|
unsigned long lastTaskJadwal = 0;
|
|
unsigned long lastTaskResetWiFi = 0;
|
|
|
|
unsigned long lastReconnectAttempt = 0;
|
|
unsigned long lastWiFiStatusPrint = 0;
|
|
|
|
const unsigned long intervalSensor = 50;
|
|
const unsigned long intervalFirebase = 5000;
|
|
const unsigned long intervalJadwal = 15000;
|
|
const unsigned long intervalResetWiFi = 2000;
|
|
const unsigned long intervalReconnect = 10000;
|
|
|
|
// ================= MOTOR =================
|
|
|
|
void motorON()
|
|
{
|
|
// Hitung telur BARU yang masuk sejak panen terakhir selesai
|
|
// Menggunakan checkpoint panen yang TIDAK di-reset dengan counter
|
|
int telurBaruIR1 = jumlahTelur1 - ir1CheckpointPanen;
|
|
int telurBaruIR2 = jumlahTelur2 - ir2CheckpointPanen;
|
|
|
|
totalTelurHarusKeBak = telurBaruIR1 + telurBaruIR2;
|
|
targetPanen = totalTelurHarusKeBak;
|
|
|
|
if (targetPanen <= 0)
|
|
{
|
|
Serial.println(
|
|
"[PANEN] Tidak ada telur baru"
|
|
);
|
|
return;
|
|
}
|
|
|
|
jumlahTelurMasuk = 0;
|
|
motorSedangPushSisa = false;
|
|
waktuTargetTercapai = 0;
|
|
|
|
modePanenAktif = true;
|
|
|
|
digitalWrite(pinIN1, HIGH);
|
|
digitalWrite(pinIN2, LOW);
|
|
|
|
ledcWrite(0, 180);
|
|
|
|
motorBerjalan = true;
|
|
waktuMulaiMotor = millis();
|
|
waktuMulaiFailsafe = millis();
|
|
waktuTelurTerakhirMasuk = millis();
|
|
durasiSudahDitambahkan = false;
|
|
durasiMotorAkhir = (unsigned long)durasiTargetDetik * 1000;
|
|
|
|
if (Firebase.ready())
|
|
{
|
|
Firebase.setBool(
|
|
fbdo,
|
|
"/aktuator/motor",
|
|
true
|
|
);
|
|
}
|
|
|
|
Serial.printf(
|
|
"[PANEN] Target telur = %d (IR1_delta: %d + IR2_delta: %d)\n",
|
|
targetPanen,
|
|
telurBaruIR1,
|
|
telurBaruIR2
|
|
);
|
|
|
|
Serial.println(
|
|
"[MOTOR] ON"
|
|
);
|
|
}
|
|
|
|
void motorOFF()
|
|
{
|
|
digitalWrite(pinIN1, LOW);
|
|
digitalWrite(pinIN2, LOW);
|
|
|
|
ledcWrite(0, 0);
|
|
|
|
motorBerjalan = false;
|
|
motorSedangPushSisa = false;
|
|
|
|
if (Firebase.ready())
|
|
{
|
|
Firebase.setBool(
|
|
fbdo,
|
|
"/aktuator/motor",
|
|
false
|
|
);
|
|
}
|
|
|
|
Serial.println(
|
|
"[MOTOR] OFF"
|
|
);
|
|
}
|
|
|
|
// ================= FIREBASE INIT =================
|
|
|
|
void initFirebase()
|
|
{
|
|
if (!firebaseInitialized)
|
|
{
|
|
config.host = FIREBASE_HOST;
|
|
|
|
config.signer.tokens.legacy_token =
|
|
FIREBASE_AUTH;
|
|
|
|
Firebase.begin(
|
|
&config,
|
|
&auth
|
|
);
|
|
|
|
Firebase.reconnectWiFi(true);
|
|
|
|
firebaseInitialized = true;
|
|
|
|
Serial.println(
|
|
"[Firebase] Initialized"
|
|
);
|
|
}
|
|
}
|
|
|
|
// ================= AMBIL DATA AWAL =================
|
|
|
|
void fetchInitialData()
|
|
{
|
|
if (!Firebase.ready())
|
|
return;
|
|
|
|
Serial.println(
|
|
"[Firebase] Mengambil data terakhir..."
|
|
);
|
|
|
|
if (
|
|
Firebase.getInt(
|
|
fbdo,
|
|
"/data/infra1"
|
|
)
|
|
)
|
|
{
|
|
jumlahTelur1 =
|
|
fbdo.intData();
|
|
|
|
Serial.printf(
|
|
"IR1 terakhir: %d\n",
|
|
jumlahTelur1
|
|
);
|
|
}
|
|
|
|
if (
|
|
Firebase.getInt(
|
|
fbdo,
|
|
"/data/infra2"
|
|
)
|
|
)
|
|
{
|
|
jumlahTelur2 =
|
|
fbdo.intData();
|
|
|
|
Serial.printf(
|
|
"IR2 terakhir: %d\n",
|
|
jumlahTelur2
|
|
);
|
|
}
|
|
|
|
if (
|
|
Firebase.getInt(
|
|
fbdo,
|
|
"/data/sudah_dipanen"
|
|
)
|
|
)
|
|
{
|
|
totalTelurSudahDipanen =
|
|
fbdo.intData();
|
|
|
|
Serial.printf(
|
|
"Sudah dipanen: %d\n",
|
|
totalTelurSudahDipanen
|
|
);
|
|
}
|
|
|
|
ir1CheckpointPanen = jumlahTelur1;
|
|
ir2CheckpointPanen = jumlahTelur2;
|
|
|
|
dataTersinkron = true;
|
|
|
|
Serial.println(
|
|
"[Firebase] Sinkronisasi awal berhasil"
|
|
);
|
|
}
|
|
|
|
// ================= NTP KE RTC =================
|
|
|
|
void sinkronisasiWaktuNTP()
|
|
{
|
|
if (
|
|
WiFi.status() !=
|
|
WL_CONNECTED
|
|
)
|
|
{
|
|
return;
|
|
}
|
|
|
|
configTime(
|
|
7 * 3600,
|
|
0,
|
|
"id.pool.ntp.org",
|
|
"time.nist.gov"
|
|
);
|
|
|
|
struct tm timeinfo;
|
|
if (getLocalTime(&timeinfo))
|
|
{
|
|
rtc.adjust(
|
|
DateTime(
|
|
timeinfo.tm_year + 1900,
|
|
timeinfo.tm_mon + 1,
|
|
timeinfo.tm_mday,
|
|
timeinfo.tm_hour,
|
|
timeinfo.tm_min,
|
|
timeinfo.tm_sec
|
|
)
|
|
);
|
|
|
|
Serial.println(
|
|
"[RTC] Sinkronisasi NTP berhasil"
|
|
);
|
|
}
|
|
else
|
|
{
|
|
Serial.println(
|
|
"[RTC] Gagal sinkronisasi NTP"
|
|
);
|
|
}
|
|
}
|
|
|
|
// ================= CEK KONEKSI =================
|
|
void cekKoneksiInternet()
|
|
{
|
|
unsigned long nowMillis =
|
|
millis();
|
|
if (
|
|
WiFi.status() !=
|
|
WL_CONNECTED
|
|
)
|
|
{
|
|
if (
|
|
nowMillis -
|
|
lastWiFiStatusPrint >=
|
|
5000
|
|
)
|
|
{
|
|
lastWiFiStatusPrint =
|
|
nowMillis;
|
|
Serial.println(
|
|
"[WiFi] Terputus, mencoba reconnect..."
|
|
);
|
|
}
|
|
if (
|
|
nowMillis -
|
|
lastReconnectAttempt >=
|
|
intervalReconnect
|
|
)
|
|
{
|
|
lastReconnectAttempt =
|
|
nowMillis;
|
|
WiFi.disconnect(false);
|
|
WiFi.reconnect();
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (!firebaseInitialized)
|
|
{
|
|
initFirebase();
|
|
}
|
|
|
|
if (
|
|
!dataTersinkron &&
|
|
Firebase.ready()
|
|
)
|
|
{
|
|
fetchInitialData();
|
|
}
|
|
}
|
|
|
|
// ================= SETUP =================
|
|
void setup()
|
|
{
|
|
Serial.begin(115200);
|
|
Wire.begin(
|
|
pinSDA,
|
|
pinSCL
|
|
);
|
|
if (!rtc.begin())
|
|
{
|
|
Serial.println(
|
|
"[RTC] Tidak terdeteksi!"
|
|
);
|
|
|
|
while (1);
|
|
}
|
|
pinMode(pinIN1, OUTPUT);
|
|
pinMode(pinIN2, OUTPUT);
|
|
pinMode(pinIR1, INPUT);
|
|
pinMode(pinIR2, INPUT);
|
|
pinMode(pinIR3, INPUT);
|
|
digitalWrite(pinIN1, LOW);
|
|
digitalWrite(pinIN2, LOW);
|
|
|
|
ledcSetup(
|
|
0,
|
|
30000,
|
|
8
|
|
);
|
|
|
|
ledcAttachPin(
|
|
pinENA,
|
|
0
|
|
);
|
|
|
|
ledcWrite(
|
|
0,
|
|
0
|
|
);
|
|
|
|
WiFi.mode(WIFI_STA);
|
|
WiFiManager wm;
|
|
wm.setConnectTimeout(20);
|
|
wm.setConfigPortalTimeout(180);
|
|
bool wifiConnected =
|
|
wm.autoConnect(
|
|
"ESP32_Telurku"
|
|
);
|
|
|
|
if (wifiConnected)
|
|
{
|
|
Serial.println(
|
|
"[WiFi] Terhubung"
|
|
);
|
|
sinkronisasiWaktuNTP();
|
|
initFirebase();
|
|
fetchInitialData();
|
|
}
|
|
else
|
|
{
|
|
Serial.println(
|
|
"[WiFi] Gagal, sistem berjalan offline"
|
|
);
|
|
}
|
|
}
|
|
|
|
// ================= LOOP =================
|
|
|
|
void loop()
|
|
{
|
|
unsigned long currentMillis =
|
|
millis();
|
|
cekKoneksiInternet();
|
|
// ================= TASK 1 SENSOR =================
|
|
if (
|
|
currentMillis -
|
|
lastTaskSensor >=
|
|
intervalSensor
|
|
)
|
|
{
|
|
lastTaskSensor =
|
|
currentMillis;
|
|
int s1 =
|
|
digitalRead(pinIR1);
|
|
int s2 =
|
|
digitalRead(pinIR2);
|
|
int s3 =
|
|
digitalRead(pinIR3);
|
|
//IR1
|
|
if (
|
|
lastStatusIR1 == HIGH &&
|
|
s1 == LOW
|
|
)
|
|
{
|
|
jumlahTelur1++;
|
|
|
|
Serial.printf(
|
|
"[IR1] Total: %d\n",
|
|
jumlahTelur1
|
|
);
|
|
}
|
|
//IR2
|
|
if (
|
|
lastStatusIR2 == HIGH &&
|
|
s2 == LOW
|
|
)
|
|
{
|
|
jumlahTelur2++;
|
|
|
|
Serial.printf(
|
|
"[IR2] Total: %d\n",
|
|
jumlahTelur2
|
|
);
|
|
}
|
|
//IR3 saat panen aktif
|
|
if (
|
|
modePanenAktif &&
|
|
lastStatusIR3 == HIGH &&
|
|
s3 == LOW
|
|
)
|
|
{
|
|
jumlahTelurMasuk++;
|
|
|
|
// Catat waktu telur terakhir masuk
|
|
waktuTelurTerakhirMasuk = millis();
|
|
|
|
Serial.printf(
|
|
"[IR3] %d/%d\n",
|
|
jumlahTelurMasuk,
|
|
targetPanen
|
|
);
|
|
}
|
|
|
|
lastStatusIR1 = s1;
|
|
lastStatusIR2 = s2;
|
|
lastStatusIR3 = s3;
|
|
}
|
|
|
|
// ================= CEK TARGET PANEN =================
|
|
if (
|
|
motorBerjalan &&
|
|
modePanenAktif &&
|
|
jumlahTelurMasuk >=
|
|
targetPanen &&
|
|
targetPanen > 0 &&
|
|
waktuTargetTercapai == 0
|
|
)
|
|
{
|
|
Serial.printf(
|
|
"[PANEN] Target tercapai %d/%d\n",
|
|
jumlahTelurMasuk,
|
|
targetPanen
|
|
);
|
|
|
|
// Catat waktu target tercapai HANYA SEKALI
|
|
waktuTargetTercapai = millis();
|
|
}
|
|
|
|
// ================= TASK 2 UPDATE FIREBASE =================
|
|
if (
|
|
currentMillis -
|
|
lastTaskFirebase >=
|
|
intervalFirebase
|
|
)
|
|
{
|
|
lastTaskFirebase =
|
|
currentMillis;
|
|
if (Firebase.ready())
|
|
{
|
|
// RTC hanya dibaca saat dibutuhkan (task ini & task jadwal),
|
|
// bukan setiap iterasi loop, supaya I2C tidak dibebani terus-menerus.
|
|
DateTime now =
|
|
rtc.now();
|
|
FirebaseJson update;
|
|
update.set(
|
|
"infra1",
|
|
jumlahTelur1
|
|
);
|
|
update.set(
|
|
"infra2",
|
|
jumlahTelur2
|
|
);
|
|
update.set(
|
|
"infra1_checkpoint_panen",
|
|
ir1CheckpointPanen
|
|
);
|
|
update.set(
|
|
"infra2_checkpoint_panen",
|
|
ir2CheckpointPanen
|
|
);
|
|
update.set(
|
|
"infra3",
|
|
jumlahTelurMasuk
|
|
);
|
|
update.set(
|
|
"target_panen",
|
|
targetPanen
|
|
);
|
|
update.set(
|
|
"total_harus_kebak",
|
|
totalTelurHarusKeBak
|
|
);
|
|
update.set(
|
|
"sudah_dipanen",
|
|
totalTelurSudahDipanen
|
|
);
|
|
update.set(
|
|
"sisa_panen",
|
|
targetPanen -
|
|
jumlahTelurMasuk
|
|
);
|
|
update.set(
|
|
"rtc_unix",
|
|
(long long)
|
|
now.unixtime()
|
|
);
|
|
update.set(
|
|
"reset_counter_pending",
|
|
resetCounterPending ? 1 : 0
|
|
);
|
|
update.set(
|
|
"last_update",
|
|
now.timestamp()
|
|
);
|
|
if (
|
|
Firebase.updateNode(
|
|
fbdo,
|
|
"/data",
|
|
update
|
|
)
|
|
)
|
|
{
|
|
Serial.println(
|
|
"[Firebase] Data berhasil dikirim"
|
|
);
|
|
}
|
|
else
|
|
{
|
|
Serial.print(
|
|
"[Firebase] Gagal update: "
|
|
);
|
|
|
|
Serial.println(
|
|
fbdo.errorReason()
|
|
);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Serial.println(
|
|
"[Firebase] Offline"
|
|
);
|
|
}
|
|
}
|
|
|
|
// ================= TASK 3 RESET WIFI =================
|
|
if (
|
|
currentMillis -
|
|
lastTaskResetWiFi >=
|
|
intervalResetWiFi
|
|
)
|
|
{
|
|
lastTaskResetWiFi =
|
|
currentMillis;
|
|
|
|
// RESET WIFI
|
|
if (
|
|
Firebase.ready() &&
|
|
Firebase.getBool(
|
|
fbdo,
|
|
"/kontrol/reset_wifi"
|
|
)
|
|
)
|
|
{
|
|
if (fbdo.boolData())
|
|
{
|
|
Firebase.setBool(
|
|
fbdo,
|
|
"/kontrol/reset_wifi",
|
|
false
|
|
);
|
|
delay(500);
|
|
|
|
WiFiManager wm;
|
|
wm.resetSettings();
|
|
Serial.println(
|
|
"[WiFi] Reset konfigurasi"
|
|
);
|
|
ESP.restart();
|
|
}
|
|
}
|
|
|
|
// RESET COUNTER
|
|
if (
|
|
Firebase.ready() &&
|
|
Firebase.getBool(
|
|
fbdo,
|
|
"/kontrol/reset_counter"
|
|
)
|
|
)
|
|
{
|
|
if (fbdo.boolData())
|
|
{
|
|
// Set flag: reset akan dilakukan setelah panen selesai
|
|
// Ini memastikan panen sore bisa berjalan dengan checkpoint yang benar
|
|
resetCounterPending = true;
|
|
|
|
Firebase.setBool(
|
|
fbdo,
|
|
"/kontrol/reset_counter",
|
|
false
|
|
);
|
|
Serial.println(
|
|
"[RESET] Menunggu panen selesai untuk melakukan reset counter..."
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ================= TASK 4 PENJADWALAN =================
|
|
if (
|
|
!motorBerjalan &&
|
|
currentMillis -
|
|
lastTaskJadwal >=
|
|
intervalJadwal
|
|
)
|
|
{
|
|
lastTaskJadwal =
|
|
currentMillis;
|
|
if (
|
|
Firebase.ready() &&
|
|
Firebase.getJSON(
|
|
fbdo,
|
|
"/kontrol/penjadwalan"
|
|
)
|
|
)
|
|
{
|
|
DateTime now =
|
|
rtc.now();
|
|
char jamSekarang[6];
|
|
sprintf(
|
|
jamSekarang,
|
|
"%02d:%02d",
|
|
now.hour(),
|
|
now.minute()
|
|
);
|
|
FirebaseJson &json =
|
|
fbdo.jsonObject();
|
|
size_t len =
|
|
json.iteratorBegin();
|
|
String key;
|
|
String value;
|
|
int type;
|
|
for (
|
|
size_t i = 0;
|
|
i < len;
|
|
i++
|
|
)
|
|
{
|
|
json.iteratorGet(
|
|
i,
|
|
type,
|
|
key,
|
|
value
|
|
);
|
|
if (
|
|
type ==
|
|
FirebaseJson::JSON_OBJECT
|
|
)
|
|
{
|
|
FirebaseJsonData d;
|
|
bool aktif =
|
|
false;
|
|
String jamJadwal =
|
|
"";
|
|
String durStr =
|
|
"";
|
|
// ===== PATH TANPA OPERATOR + =====
|
|
String pathAktif = key;
|
|
pathAktif.concat("/aktif");
|
|
String pathJam = key;
|
|
pathJam.concat("/jam");
|
|
String pathDurasi = key;
|
|
pathDurasi.concat("/durasi");
|
|
// ===============================
|
|
if (json.get(d, pathAktif.c_str()))
|
|
aktif = d.boolValue;
|
|
|
|
if (json.get(d, pathJam.c_str()))
|
|
jamJadwal = d.stringValue;
|
|
|
|
if (json.get(d, pathDurasi.c_str()))
|
|
durStr = d.stringValue;
|
|
|
|
if (aktif && jamJadwal == String(jamSekarang))
|
|
{
|
|
// proses selanjutnya
|
|
}
|
|
{
|
|
durStr.trim();
|
|
durStr.toLowerCase();
|
|
|
|
if (durStr.indexOf("detik") != -1)
|
|
{
|
|
durasiTargetDetik =
|
|
durStr.substring(0, durStr.indexOf("detik")).toInt();
|
|
}
|
|
else if (durStr.indexOf("menit") != -1)
|
|
{
|
|
durasiTargetDetik =
|
|
durStr.substring(0, durStr.indexOf("menit")).toFloat() * 60;
|
|
}
|
|
else
|
|
{
|
|
// Jika hanya angka, dianggap menit (agar kompatibel dengan data lama)
|
|
durasiTargetDetik =
|
|
durStr.toFloat() * 60;
|
|
}
|
|
|
|
if (durasiTargetDetik <= 0)
|
|
{
|
|
durasiTargetDetik = 60;
|
|
}
|
|
|
|
Serial.printf(
|
|
"[JADWAL] Durasi Firebase: %s -> %d detik\n",
|
|
durStr.c_str(),
|
|
durasiTargetDetik
|
|
);
|
|
Serial.printf(
|
|
"[JADWAL] Motor ON %d detik\n",
|
|
durasiTargetDetik
|
|
);
|
|
|
|
motorON();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
json.iteratorEnd();
|
|
}
|
|
}
|
|
|
|
// ================= TASK 5 MONITOR MOTOR =================
|
|
|
|
if (motorBerjalan)
|
|
{
|
|
unsigned long waktuBerjalan =
|
|
millis() -
|
|
waktuMulaiMotor;
|
|
bool durasiHabis =
|
|
waktuBerjalan >=
|
|
durasiMotorAkhir;
|
|
bool failsafeHabis =
|
|
millis() -
|
|
waktuMulaiFailsafe >=
|
|
timeoutFailsafe;
|
|
|
|
// ===== KONDISI 1: Target sudah tercapai =====
|
|
// Motor OFF 10 detik setelah target tercapai (bukan mengikuti durasi)
|
|
if (waktuTargetTercapai != 0)
|
|
{
|
|
unsigned long waktuSetelahTarget =
|
|
millis() -
|
|
waktuTargetTercapai;
|
|
|
|
if (waktuSetelahTarget >= timeoutMotorAfterTarget)
|
|
{
|
|
Serial.printf(
|
|
"[PANEN] Semua telur masuk bak (%d/%d)\n",
|
|
jumlahTelurMasuk,
|
|
targetPanen
|
|
);
|
|
|
|
totalTelurSudahDipanen +=
|
|
jumlahTelurMasuk;
|
|
// Update checkpoint panen saat panen berhasil
|
|
// Ini akan dipakai untuk panen berikutnya (panen sore)
|
|
ir1CheckpointPanen = jumlahTelur1;
|
|
ir2CheckpointPanen = jumlahTelur2;
|
|
|
|
Serial.printf(
|
|
"[PANEN] Checkpoint updated: IR1=%d, IR2=%d (siap untuk panen berikutnya)\n",
|
|
ir1CheckpointPanen,
|
|
ir2CheckpointPanen
|
|
);
|
|
|
|
modePanenAktif = false;
|
|
waktuTargetTercapai = 0;
|
|
motorOFF();
|
|
|
|
// ===== RESET COUNTER JIKA PENDING =====
|
|
// Reset dilakukan EVENT-BASED setelah panen selesai, bukan time-based
|
|
if (resetCounterPending)
|
|
{
|
|
jumlahTelur1 = 0;
|
|
jumlahTelur2 = 0;
|
|
ir1CheckpointPanen = 0;
|
|
ir2CheckpointPanen = 0;
|
|
jumlahTelurMasuk = 0;
|
|
totalTelurSudahDipanen = 0;
|
|
targetPanen = 0;
|
|
durasiSudahDitambahkan = false;
|
|
durasiMotorAkhir = 0;
|
|
waktuTelurTerakhirMasuk = 0;
|
|
motorSedangPushSisa = false;
|
|
resetCounterPending = false;
|
|
Serial.println(
|
|
"[RESET] Counter berhasil direset setelah panen selesai"
|
|
);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Hanya print countdown setiap detik (saat countdown berubah)
|
|
static unsigned long lastCountdownPrint = 0;
|
|
unsigned long currentCountdown = (timeoutMotorAfterTarget - waktuSetelahTarget) / 1000;
|
|
|
|
if (lastCountdownPrint != currentCountdown)
|
|
{
|
|
lastCountdownPrint = currentCountdown;
|
|
|
|
Serial.printf(
|
|
"[PANEN] Menunggu OFF: %lu detik lagi\n",
|
|
currentCountdown
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ===== KONDISI 2: Durasi habis tapi target belum tercapai =====
|
|
// Ada telur tertinggal di konveyor, perpanjang durasi untuk push
|
|
|
|
else if (
|
|
durasiHabis &&
|
|
jumlahTelurMasuk <
|
|
targetPanen
|
|
)
|
|
{
|
|
// Cek apakah ada telur yang masuk dalam durasi
|
|
// Jika tidak ada telur sejak durasi habis dan belum pernah ditambah
|
|
if (
|
|
!durasiSudahDitambahkan &&
|
|
millis() -
|
|
waktuTelurTerakhirMasuk >=
|
|
durasiMotorAkhir
|
|
)
|
|
{
|
|
// Perpanjang durasi motor sebesar 2x dari durasi awal
|
|
unsigned long durasiTambahan =
|
|
(unsigned long)durasiTargetDetik * 1000 * 2;
|
|
durasiMotorAkhir +=
|
|
durasiTambahan;
|
|
durasiSudahDitambahkan =
|
|
true;
|
|
motorSedangPushSisa =
|
|
true;
|
|
Serial.printf(
|
|
"[PANEN] Ada telur tertinggal! Durasi perpanjang 2x: %lu detik\n",
|
|
durasiMotorAkhir / 1000
|
|
);
|
|
|
|
Serial.printf(
|
|
"[PANEN] Menunggu telur masuk: %d/%d\n",
|
|
jumlahTelurMasuk,
|
|
targetPanen
|
|
);
|
|
}
|
|
// Motor tetap jalan, menunggu sisa telur masuk bak
|
|
// atau sampai failsafe 5 menit tercapai
|
|
else
|
|
{
|
|
Serial.printf(
|
|
"[PANEN] Menunggu telur masuk: %d/%d\n",
|
|
jumlahTelurMasuk,
|
|
targetPanen
|
|
);
|
|
}
|
|
}
|
|
|
|
// ===== KONDISI 3: Failsafe 5 menit =====
|
|
// Motor OFF paksa jika timeout 5 menit
|
|
|
|
if (failsafeHabis)
|
|
{
|
|
Serial.println(
|
|
"[FAILSAFE] Timeout 5 menit"
|
|
);
|
|
totalTelurSudahDipanen +=
|
|
jumlahTelurMasuk;
|
|
// Update checkpoint panen saat failsafe
|
|
ir1CheckpointPanen = jumlahTelur1;
|
|
ir2CheckpointPanen = jumlahTelur2;
|
|
Serial.printf(
|
|
"[FAILSAFE] Checkpoint updated: IR1=%d, IR2=%d\n",
|
|
ir1CheckpointPanen,
|
|
ir2CheckpointPanen
|
|
);
|
|
modePanenAktif = false;
|
|
waktuTargetTercapai = 0;
|
|
motorOFF();
|
|
// ===== RESET COUNTER JIKA PENDING (FAILSAFE) =====
|
|
// Jika failsafe terjadi dan reset pending, jalankan reset
|
|
if (resetCounterPending)
|
|
{
|
|
jumlahTelur1 = 0;
|
|
jumlahTelur2 = 0;
|
|
ir1CheckpointPanen = 0;
|
|
ir2CheckpointPanen = 0;
|
|
jumlahTelurMasuk = 0;
|
|
totalTelurSudahDipanen = 0;
|
|
targetPanen = 0;
|
|
durasiSudahDitambahkan = false;
|
|
durasiMotorAkhir = 0;
|
|
waktuTelurTerakhirMasuk = 0;
|
|
motorSedangPushSisa = false;
|
|
resetCounterPending = false;
|
|
Serial.println(
|
|
"[RESET] Counter direset setelah failsafe"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
} |