/*
============================================================
PROJECT : 5 Lampu Tangga AC 6W – Arduino Nano
VERSION : 2.2 - Production Massal Ready
DATE : Maret 2026
STATUS : SIAP PRODUKSI MASSAL
FEATURES :
• Fully non-blocking (millis + state machine)
• 2 PIR sensor dengan debounce & warm-up
• Chase naik perlahan, turun & mati cepat
• Timer otomatis extend setiap gerakan baru
• Relay configurable (active HIGH/LOW)
• Sangat stabil & anti-hang untuk produksi ribuan unit
============================================================
*/
#define FIRMWARE_VERSION "2.2-PROD"
// ================== KONFIGURASI PRODUKSI ==================
const uint8_t NUM_LAMPU = 5;
const uint8_t lampuPins[NUM_LAMPU] = {2, 3, 4, 5, 6}; // pin relay
const uint8_t PIR_ATAS = 7; // PIR atas tangga
const uint8_t PIR_BAWAH = 8; // PIR bawah tangga
const bool RELAY_ACTIVE_HIGH = true; // UB AH ke false kalau relay module kamu active LOW (paling umum)
const unsigned long WAKTU_MENYALA = 8000; // waktu lampu menyala (ms) — bisa diubah 5000-15000
const unsigned long STEP_NAIK = 185; // ms antar lampu saat naik (perlahan)
const unsigned long STEP_TURUN = 70; // ms antar lampu saat turun/mati (cepat)
const unsigned long DEBOUNCE_PIR = 130; // anti noise PIR
const unsigned long PIR_WARMUP_TIME = 28000; // 28 detik warm-up PIR pertama kali
// ================== VARIABEL GLOBAL ==================
unsigned long lastStepTime = 0;
unsigned long lastTrigger = 0;
unsigned long timeoutStart = 0;
unsigned long bootTime = 0;
int8_t currentStep = -1;
bool directionUp = true;
bool isAnimating = false;
bool isTurningOff = false;
bool lampsActive = false;
bool prevPIRAtas = false;
bool prevPIRBawah = false;
// Helper relay
uint8_t RELAY_ON = RELAY_ACTIVE_HIGH ? HIGH : LOW;
uint8_t RELAY_OFF = RELAY_ACTIVE_HIGH ? LOW : HIGH;
// ================== SETUP ==================
void setup() {
bootTime = millis();
// SAFETY: matikan semua relay saat boot
for (uint8_t i = 0; i < NUM_LAMPU; i++) {
pinMode(lampuPins[i], OUTPUT);
digitalWrite(lampuPins[i], RELAY_OFF);
}
pinMode(PIR_ATAS, INPUT);
pinMode(PIR_BAWAH, INPUT);
}
// ================== LOOP ==================
void loop() {
unsigned long now = millis();
// Warm-up PIR (hindari false trigger saat pertama nyala)
if (now - bootTime < PIR_WARMUP_TIME) return;
bool pirAtasNow = digitalRead(PIR_ATAS);
bool pirBawahNow = digitalRead(PIR_BAWAH);
// === TRIGGER DARI BAWAH (naik) ===
if (pirBawahNow && !prevPIRBawah && (now - lastTrigger > DEBOUNCE_PIR)) {
directionUp = true;
lastTrigger = now;
timeoutStart = now;
startSequence(true);
}
// === TRIGGER DARI ATAS (turun) ===
if (pirAtasNow && !prevPIRAtas && (now - lastTrigger > DEBOUNCE_PIR)) {
directionUp = false;
lastTrigger = now;
timeoutStart = now;
startSequence(false);
}
prevPIRAtas = pirAtasNow;
prevPIRBawah = pirBawahNow;
// Extend timer kalau masih ada gerakan
if (lampsActive && (pirAtasNow || pirBawahNow)) {
timeoutStart = now;
}
// Jalankan animasi kalau sedang berjalan
if (isAnimating) {
updateSequence();
}
// Auto matikan setelah waktu habis
if (lampsActive && !isAnimating && (now - timeoutStart >= WAKTU_MENYALA)) {
startTurnOffSequence();
}
}
// ================== FUNGSI SEQUENCE ==================
void startSequence(bool naik) {
if (isAnimating) return; // jangan ganggu animasi yang sedang berjalan
directionUp = naik;
currentStep = naik ? 0 : NUM_LAMPU - 1;
isAnimating = true;
isTurningOff = false;
lampsActive = true;
lastStepTime = millis();
digitalWrite(lampuPins[currentStep], RELAY_ON); // nyalakan lampu pertama
}
void startTurnOffSequence() {
isAnimating = true;
isTurningOff = true;
lampsActive = false;
currentStep = directionUp ? 0 : NUM_LAMPU - 1; // mulai mati dari ujung yang sesuai arah
lastStepTime = millis();
digitalWrite(lampuPins[currentStep], RELAY_OFF); // matikan lampu pertama
}
void updateSequence() {
unsigned long interval = (isTurningOff || !directionUp) ? STEP_TURUN : STEP_NAIK;
if (millis() - lastStepTime < interval) return;
lastStepTime = millis();
if (isTurningOff) {
// ================== MATIKAN BERURUTAN ==================
if (directionUp) {
// mati dari bawah ke atas
if (currentStep < NUM_LAMPU - 1) {
currentStep++;
digitalWrite(lampuPins[currentStep], RELAY_OFF);
} else {
isAnimating = false; // selesai mati semua
}
} else {
// mati dari atas ke bawah
if (currentStep > 0) {
currentStep--;
digitalWrite(lampuPins[currentStep], RELAY_OFF);
} else {
isAnimating = false;
}
}
}
else {
// ================== NYALAKAN BERURUTAN ==================
if (directionUp) {
// naik dari bawah ke atas (perlahan)
if (currentStep < NUM_LAMPU - 1) {
currentStep++;
digitalWrite(lampuPins[currentStep], RELAY_ON);
} else {
isAnimating = false; // semua sudah nyala
}
} else {
// turun dari atas ke bawah (cepat)
if (currentStep > 0) {
currentStep--;
digitalWrite(lampuPins[currentStep], RELAY_ON);
} else {
isAnimating = false;
}
}
}
}