/**
* PROJECT: OEM-Grade Pump Controller V4.4 – Arduino Uno FINAL FIXED
* Perbaikan compile error:
* - Inisialisasi struct SensorFilter dilakukan manual (AVR-GCC compatible)
* - Semua fitur review terakhir sudah ada (sensor stuck hanya saat ON, reset clear timer, turbulence filter, dll)
*/
#include <Wire.h>
#include <LiquidCrystal_I2C.h> // Library: LiquidCrystal_I2C by Frank de Brabander
#include <avr/wdt.h> // Watchdog
// ==========================================
// KONFIGURASI PIN (Arduino Uno)
// ==========================================
#define SENSOR_ACTIVE_LOW(x) (!(x)) // Sensor aktif LOW (INPUT_PULLUP)
const uint8_t IN_S1 = 7; // Tank full
const uint8_t IN_S2 = 8; // No water / dry run
const uint8_t IN_F1 = 6; // Fuse OK
const uint8_t IN_RESET = A0; // Tombol reset (long press)
const uint8_t OUT_K1 = 2; // Relay pompa
const uint8_t OUT_H1 = 9; // LED Hijau
const uint8_t OUT_H2 = 10; // LED Kuning
const uint8_t OUT_H3 = 11; // LED Merah
const uint8_t BUZZER = 5; // Buzzer
#define RELAY_ON LOW
#define RELAY_OFF HIGH
// ==========================================
// KONSTANTA TIMER
// ==========================================
const uint32_t RECOVERY_TIME = 30UL * 60 * 1000UL; // 30 menit
const uint32_t MIN_RUN_TIME = 10 * 1000UL;
const uint32_t MIN_OFF_TIME = 5 * 1000UL;
const uint32_t SENSOR_STUCK_TIME = 60 * 1000UL; // hanya saat pompa ON
const uint32_t RESET_LONG_PRESS_MS = 3 * 1000UL;
const uint32_t SCAN_INTERVAL = 100UL; // polling rate
const uint16_t LCD_UPDATE_INTERVAL = 600UL; // anti-flicker
const uint8_t MAX_RETRY_LIMIT = 3;
const uint8_t LEVEL_FILTER_COUNT = 5; // anti turbulence
// LCD I2C
LiquidCrystal_I2C lcd(0x27, 16, 2); // Ganti ke 0x3F jika perlu
// ==========================================
// STRUCT & FILTER
// ==========================================
struct SensorFilter {
uint8_t pin;
bool lastRaw;
bool debounced;
unsigned long lastChange;
unsigned long delayMs;
uint8_t stableCount;
};
SensorFilter fsS1;
SensorFilter fsS2;
SensorFilter fsF1;
SensorFilter fsReset;
// ==========================================
// GLOBAL VARIABLES
// ==========================================
enum SystemState { ST_INIT, ST_RUNNING, ST_RECOVERY, ST_FAULT, ST_LOCKOUT };
SystemState currentState = ST_INIT;
bool S1, S2, F1, btnReset;
bool K1_req = false;
uint8_t dryRunCounter = 0;
unsigned long totalRuntime = 0;
unsigned long lastScan = 0;
unsigned long stateTimer = 0;
unsigned long motorTimer = 0;
unsigned long s2StuckTimer = 0;
unsigned long resetPressStart = 0;
unsigned long lastLCDupdate = 0;
char prevLine1[17] = "";
char prevLine2[17] = "";
// ==========================================
// INISIALISASI FILTER (manual untuk AVR compatibility)
// ==========================================
void initFilters() {
fsS1.pin = IN_S1;
fsS1.lastRaw = HIGH;
fsS1.debounced = HIGH;
fsS1.lastChange = 0;
fsS1.delayMs = 35;
fsS1.stableCount = 0;
fsS2.pin = IN_S2;
fsS2.lastRaw = HIGH;
fsS2.debounced = HIGH;
fsS2.lastChange = 0;
fsS2.delayMs = 35;
fsS2.stableCount = 0;
fsF1.pin = IN_F1;
fsF1.lastRaw = HIGH;
fsF1.debounced = HIGH;
fsF1.lastChange = 0;
fsF1.delayMs = 35;
fsF1.stableCount = 0;
fsReset.pin = IN_RESET;
fsReset.lastRaw = HIGH;
fsReset.debounced = HIGH;
fsReset.lastChange = 0;
fsReset.delayMs = 50;
fsReset.stableCount = 0;
}
bool readFiltered(SensorFilter &fs) {
bool raw = digitalRead(fs.pin);
if (raw != fs.lastRaw) {
fs.lastChange = millis();
fs.lastRaw = raw;
fs.stableCount = 0;
}
if (millis() - fs.lastChange >= fs.delayMs) {
if (raw == fs.debounced) {
fs.stableCount = LEVEL_FILTER_COUNT;
} else {
fs.stableCount++;
if (fs.stableCount >= LEVEL_FILTER_COUNT) {
fs.debounced = raw;
}
}
}
return fs.debounced;
}
void readInputs() {
S1 = SENSOR_ACTIVE_LOW(readFiltered(fsS1));
S2 = SENSOR_ACTIVE_LOW(readFiltered(fsS2));
F1 = SENSOR_ACTIVE_LOW(readFiltered(fsF1));
btnReset = SENSOR_ACTIVE_LOW(readFiltered(fsReset));
}
// ==========================================
// LOGGING MINIMALIS
// ==========================================
#define LOGI(msg) Serial.println(F("[INFO] " msg))
#define LOGW(msg) Serial.println(F("[WARN] " msg))
#define LOGE(msg) Serial.println(F("[ERROR] " msg))
// ==========================================
// FSM & LOGIKA UTAMA
// ==========================================
void processFSM() {
// Fuse prioritas tertinggi
if (!F1) {
if (currentState != ST_FAULT) {
currentState = ST_FAULT;
K1_req = false;
LOGE("Fuse fault detected");
}
return;
}
// Sensor S2 stuck HANYA saat pompa ON
if (S2 && K1_req) {
if (s2StuckTimer == 0) s2StuckTimer = millis();
if (millis() - s2StuckTimer >= SENSOR_STUCK_TIME) {
currentState = ST_FAULT;
K1_req = false;
LOGE("Sensor S2 stuck during pump ON");
return;
}
} else {
s2StuckTimer = 0;
}
switch (currentState) {
case ST_INIT:
currentState = ST_RUNNING;
LOGI("System init complete");
break;
case ST_RUNNING:
if (S2 && K1_req) {
dryRunCounter++;
LOGW("Dry-run detected");
if (dryRunCounter >= MAX_RETRY_LIMIT) {
currentState = ST_LOCKOUT;
LOGE("Lockout activated");
} else {
currentState = ST_RECOVERY;
stateTimer = millis();
LOGI("Entering recovery");
}
K1_req = false;
motorTimer = millis();
return;
}
if (S1 && !K1_req && millis() - motorTimer >= MIN_OFF_TIME) {
K1_req = true;
motorTimer = millis();
dryRunCounter = 0;
LOGI("Pump START");
}
else if (!S1 && K1_req && millis() - motorTimer >= MIN_RUN_TIME) {
K1_req = false;
motorTimer = millis();
LOGI("Pump STOP");
}
break;
case ST_RECOVERY:
if (S2) stateTimer = millis();
if (millis() - stateTimer >= RECOVERY_TIME) {
currentState = ST_RUNNING;
LOGI("Recovery finished");
}
break;
case ST_FAULT:
case ST_LOCKOUT:
K1_req = false;
if (btnReset) {
if (resetPressStart == 0) resetPressStart = millis();
if (millis() - resetPressStart >= RESET_LONG_PRESS_MS) {
dryRunCounter = 0;
stateTimer = 0;
s2StuckTimer = 0;
motorTimer = millis();
currentState = ST_INIT;
LOGI("Reset accepted - system cleared");
resetPressStart = 0;
}
} else {
resetPressStart = 0;
}
break;
}
}
void writeOutputs() {
digitalWrite(OUT_K1, K1_req ? RELAY_ON : RELAY_OFF);
digitalWrite(OUT_H1, (currentState == ST_RUNNING && K1_req));
digitalWrite(OUT_H2, (currentState == ST_RECOVERY) ? (millis() % 1000 < 500) : (currentState == ST_RUNNING && !K1_req));
digitalWrite(OUT_H3, (currentState == ST_LOCKOUT) ? (millis() % 400 < 200) : (currentState == ST_FAULT));
digitalWrite(BUZZER, (currentState == ST_FAULT || currentState == ST_LOCKOUT) && (millis() % 4000 < 800));
}
void trackRuntime() {
static unsigned long lastTick = 0;
if (K1_req) {
while (millis() - lastTick >= 1000) {
lastTick += 1000;
totalRuntime++;
}
} else {
lastTick = millis();
}
}
void updateLCD() {
unsigned long now = millis();
if (now - lastLCDupdate < LCD_UPDATE_INTERVAL) return;
lastLCDupdate = now;
char l1[17], l2[17];
const char* st = "INIT ";
if (currentState == ST_RUNNING) st = K1_req ? "POMPA NYALA" : "STANDBY ";
if (currentState == ST_RECOVERY) st = "RECOVERY ";
if (currentState == ST_FAULT) st = "FAULT! ";
if (currentState == ST_LOCKOUT) st = "LOCKOUT! ";
snprintf(l1, sizeof(l1), "%s", st);
snprintf(l2, sizeof(l2), "Run:%lu jam", totalRuntime / 3600);
if (strcmp(l1, prevLine1) != 0) {
lcd.setCursor(0, 0);
lcd.print(l1);
strcpy(prevLine1, l1);
}
if (strcmp(l2, prevLine2) != 0) {
lcd.setCursor(0, 1);
lcd.print(l2);
strcpy(prevLine2, l2);
}
}
// ==========================================
// SETUP & LOOP
// ==========================================
void setup() {
Serial.begin(9600);
delay(200);
// Watchdog 4 detik
wdt_enable(WDTO_4S);
// Inisialisasi filter sensor
initFilters();
lcd.init();
lcd.backlight();
lcd.clear();
lcd.print("Pump Ctrl Uno");
lcd.setCursor(0,1);
lcd.print("Starting...");
delay(1500);
lcd.clear();
pinMode(IN_S1, INPUT_PULLUP);
pinMode(IN_S2, INPUT_PULLUP);
pinMode(IN_F1, INPUT_PULLUP);
pinMode(IN_RESET, INPUT_PULLUP);
pinMode(OUT_K1, OUTPUT); digitalWrite(OUT_K1, RELAY_OFF);
pinMode(OUT_H1, OUTPUT); digitalWrite(OUT_H1, LOW);
pinMode(OUT_H2, OUTPUT); digitalWrite(OUT_H2, LOW);
pinMode(OUT_H3, OUTPUT); digitalWrite(OUT_H3, LOW);
pinMode(BUZZER, OUTPUT); digitalWrite(BUZZER, LOW);
LOGI("Pump Controller Uno started");
}
void loop() {
wdt_reset();
if (millis() - lastScan >= SCAN_INTERVAL) {
lastScan = millis();
readInputs();
processFSM();
writeOutputs();
trackRuntime();
updateLCD();
}
}