#include <Arduino.h>
#include <IRremote.hpp>
#include <Wire.h>
#include "RTClib.h"
// --- KONFIGURASI PIN ---
const int PIN_IR_RECEIVER = 2, PIN_LS_OPEN = 5, PIN_LS_CLOSE = 6, PIN_RAIN = 7;
const int PIN_MOTOR_L = 8, PIN_MOTOR_R = 9, PIN_PWM_EN = 10, PIN_CUR_SENSE = A0, PIN_BUZZER = 12;
// --- PARAMETER ---
const int PWM_TARGET = 220;
const int CURRENT_LIMIT = 600;
const long MOVE_TIMEOUT = 25000;
const long RAIN_CONFIRM_TIME = 3000;
const int RAMP_STEP_MS = 15;
enum State { CLOSED, OPENING, OPEN, CLOSING, STOPPED, FAULT };
State currentState = STOPPED;
RTC_DS3231 rtc;
unsigned long moveStartTime = 0, rainStartTime = 0, lastRampMillis = 0;
int currentPWM = 0;
bool rainLock = false;
// --- 0. LOGGING HELPER ---
void logEvent(String msg) {
DateTime now = rtc.now();
Serial.print("[");
if(now.hour() < 10) Serial.print('0'); Serial.print(now.hour()); Serial.print(':');
if(now.minute() < 10) Serial.print('0'); Serial.print(now.minute()); Serial.print(':');
if(now.second() < 10) Serial.print('0'); Serial.print(now.second());
Serial.print("] ");
Serial.println(msg);
}
// --- 1. LOW LEVEL ACTUATOR ---
void applyHardware(State s, int pwm) {
if (s == OPENING) {
digitalWrite(PIN_MOTOR_L, HIGH); digitalWrite(PIN_MOTOR_R, LOW);
} else if (s == CLOSING) {
digitalWrite(PIN_MOTOR_L, LOW); digitalWrite(PIN_MOTOR_R, HIGH);
} else {
digitalWrite(PIN_MOTOR_L, LOW); digitalWrite(PIN_MOTOR_R, LOW);
pwm = 0;
}
analogWrite(PIN_PWM_EN, pwm);
}
// --- 2. STATE TRANSITION ---
void requestState(State newState) {
if (currentState == FAULT && newState != STOPPED) return;
if (newState == OPENING && digitalRead(PIN_LS_OPEN) == LOW) {
logEvent("ERR: Gagal Buka (Limit Atas Tertekan)");
return;
}
if (newState == CLOSING && digitalRead(PIN_LS_CLOSE) == LOW) {
logEvent("ERR: Gagal Tutup (Limit Bawah Tertekan)");
return;
}
if ((currentState == OPENING && newState == CLOSING) || (currentState == CLOSING && newState == OPENING)) {
logEvent("WAR: Arah Berlawanan! Cooldown...");
applyHardware(STOPPED, 0);
delay(400);
}
// Log Perubahan State
String stateNames[] = {"CLOSED", "OPENING", "OPEN", "CLOSING", "STOPPED", "FAULT"};
logEvent("STATE: " + stateNames[currentState] + " -> " + stateNames[newState]);
currentState = newState;
currentPWM = 0;
if (currentState == OPENING || currentState == CLOSING) moveStartTime = millis();
}
// --- 3. SYSTEM ENGINE ---
void runEngine() {
static uint8_t overCurrentCnt = 0;
if (currentState == OPENING || currentState == CLOSING) {
if (currentPWM < PWM_TARGET && millis() - lastRampMillis > RAMP_STEP_MS) {
currentPWM += 5;
lastRampMillis = millis();
}
applyHardware(currentState, currentPWM);
if (currentState == OPENING && digitalRead(PIN_LS_OPEN) == LOW) {
logEvent("INF: Limit Atas Tercapai");
requestState(OPEN);
}
if (currentState == CLOSING && digitalRead(PIN_LS_CLOSE) == LOW) {
logEvent("INF: Limit Bawah Tercapai");
requestState(CLOSED);
}
if (analogRead(PIN_CUR_SENSE) > CURRENT_LIMIT) {
if (++overCurrentCnt > 10) {
logEvent("ALM: OVERCURRENT! Arus motor terlalu tinggi.");
requestState(FAULT);
}
} else { overCurrentCnt = 0; }
if (millis() - moveStartTime > MOVE_TIMEOUT) {
logEvent("ALM: TIMEOUT! Motor jalan terlalu lama.");
requestState(FAULT);
}
} else {
applyHardware(currentState, 0);
}
}
void setup() {
Serial.begin(9600);
IrReceiver.begin(PIN_IR_RECEIVER, ENABLE_LED_FEEDBACK);
if (!rtc.begin()) Serial.println("CRITICAL: RTC tidak terbaca!");
pinMode(PIN_LS_OPEN, INPUT_PULLUP); pinMode(PIN_LS_CLOSE, INPUT_PULLUP);
pinMode(PIN_RAIN, INPUT_PULLUP);
pinMode(PIN_MOTOR_L, OUTPUT); pinMode(PIN_MOTOR_R, OUTPUT);
pinMode(PIN_PWM_EN, OUTPUT); pinMode(PIN_BUZZER, OUTPUT);
logEvent("SYSTEM: Rebooted & Ready");
requestState(STOPPED);
}
void loop() {
if (IrReceiver.decode()) {
uint8_t cmd = IrReceiver.decodedIRData.command;
Serial.print("IR: CMD 0x"); Serial.println(cmd, HEX);
if (cmd == 0xE0) requestState(OPENING);
else if (cmd == 0x90) requestState(CLOSING);
else if (cmd == 0x68) requestState(STOPPED);
IrReceiver.resume();
}
if (digitalRead(PIN_RAIN) == LOW) {
if (rainStartTime == 0) rainStartTime = millis();
if (millis() - rainStartTime > RAIN_CONFIRM_TIME && !rainLock) {
logEvent("ENV: Hujan Terdeteksi (Confirmed)");
rainLock = true; requestState(CLOSING);
}
} else {
rainStartTime = 0;
if (currentState == CLOSED && rainLock) {
logEvent("ENV: Sensor Kering, Lock Dilepas");
rainLock = false;
}
}
DateTime now = rtc.now();
static int lastMin = -1;
if (now.minute() != lastMin) {
if (now.hour() == 6 && now.minute() == 0 && currentState == CLOSED && !rainLock) {
logEvent("SCHED: Jadwal Buka Pagi");
requestState(OPENING);
}
lastMin = now.minute();
}
runEngine();
if (currentState == FAULT) digitalWrite(PIN_BUZZER, (millis() % 200 < 100));
}