#include <avr/wdt.h>
#include <EEPROM.h>
#include <Wire.h>
#include <SSD1306Ascii.h>
#include <SSD1306AsciiWire.h>
// ======================================================
// DEBUG LOGGING
// ======================================================
#define LOG_LEVEL 2 // 0=OFF, 1=ERROR, 2=INFO, 3=DEBUG
#define LOG_ERROR(...) logPrint(1, __VA_ARGS__)
#define LOG_INFO(...) logPrint(2, __VA_ARGS__)
#define LOG_DEBUG(...) logPrint(3, __VA_ARGS__)
unsigned long lastLogTime = 0;
const unsigned long LOG_THROTTLE = 800;
void logPrint(byte level, const char* format, ...) {
if (level > LOG_LEVEL) return;
unsigned long now = millis();
if (now - lastLogTime < LOG_THROTTLE) return;
lastLogTime = now;
Serial.print(F("["));
Serial.print(now / 1000);
Serial.print(F("s] "));
if (level == 1) Serial.print(F("ERROR: "));
else if (level == 2) Serial.print(F("INFO: "));
else if (level == 3) Serial.print(F("DEBUG: "));
char buf[128];
va_list args;
va_start(args, format);
vsnprintf(buf, sizeof(buf), format, args);
va_end(args);
Serial.println(buf);
}
// ======================================================
// PINOUT
// ======================================================
const byte PIN_FLOAT = 2;
const byte PIN_PRESSURE = 3;
const byte PIN_RESET = 4;
const byte PIN_ACS712 = A0;
const byte PIN_PUMP = 10;
const byte PIN_ALARM = 11;
const byte PIN_LED_STATUS = 5;
const byte PIN_LED_ALARM = 6;
// DIP Switch
const byte PIN_DIP_DRYRUN = 7;
const byte PIN_DIP_MAX1 = 8;
const byte PIN_DIP_MAX2 = 9;
const byte PIN_DIP_RESERVE = 12;
// ======================================================
// EEPROM
// ======================================================
const int ADDR_SIGNATURE = 20;
const byte EEPROM_SIGNATURE = 0x5A;
const int ADDR_TRIP = 0;
const int ADDR_FAULT = 1;
const int ADDR_DRY_ENABLE = 10;
const int ADDR_DRY_THRESHOLD = 11;
const int ADDR_MAX_RUNTIME_IDX = 12;
const int ADDR_CHECKSUM = 13;
// ======================================================
// CONSTANTS
// ======================================================
const unsigned long T_DEBOUNCE_FLOAT = 1500;
const unsigned long T_DEBOUNCE_PRESS = 500;
const unsigned long T_PRIMING_TIMEOUT = 15000;
const unsigned long T_CURRENT_GRACE = 5000;
const unsigned long T_CURRENT_TRIP = 3500;
const unsigned long T_WELDED_VAL_TIME = 3000;
const unsigned long T_MIN_OFF_TIME = 7000;
const unsigned long T_MIN_ON_TIME = 3000;
const unsigned long T_CALIB_INTERVAL = 86400000UL;
const unsigned long T_STARTUP_INHIBIT = 15000;
const unsigned long T_DIP_READ_INTERVAL = 5000;
const unsigned long T_ACS_FAULT_PERSIST = 250;
const unsigned long T_PRESS_STABLE = 2500;
const unsigned long T_POST_TRIP_LOCK = 10000;
const unsigned long T_CALIB_RETRY = 600000;
const unsigned long T_AUTO_RETRY = 300000;
const unsigned long T_STABLE_RUN = 15000;
const unsigned long T_SENSOR_WATCHDOG = 180000;
const float WELD_CURRENT_THRESHOLD = 1.28f;
const float DRY_RUN_RATIO = 0.45f;
const float I_DRY_RUN_THRESHOLD_DEFAULT = 0.45f;
const float MIN_NORMAL_CURRENT = 0.35f;
unsigned long T_MAX_RUNTIME = 3600000UL;
// ======================================================
// GLOBAL STATE
// ======================================================
float adcOffset = 512.0f;
const float ADC_PER_AMP = 20.48f;
float normalCurrentRef = 0.0f;
bool tripState = false;
byte faultCode = 0;
byte lastDisplayedFault = 255;
float rmsCurrent = 0.0f;
float emaCurrent = 0.0f;
bool pumpState = false;
bool floatFiltered = false;
bool pressFiltered = false;
unsigned long pumpStartTime = 0;
unsigned long lastPumpOffTime = 0;
unsigned long noPressureTime = 0;
unsigned long lowCurrentTime = 0;
unsigned long weldedVerifyTime = 0;
unsigned long lastAutoCalibTime = 0;
unsigned long continuousRunStartTime = 0;
unsigned long startupTime = 0;
unsigned long lastDIPReadTime = 0;
unsigned long acsFaultStartTime = 0;
unsigned long pressStableTime = 0;
unsigned long lastTripTime = 0;
unsigned long lastCalibAttempt = 0;
unsigned long stableRunStartTime = 0;
unsigned long weldStableTime = 0;
unsigned long sensorWatchdogTimer = 0;
unsigned long rmsWindowStart = 0;
float sqAccumulator = 0.0f;
uint16_t sampleCount = 0;
unsigned long lastSampleTime = 0;
// Calibration
enum CalState { CAL_IDLE, CAL_SETTLING, CAL_SAMPLING };
CalState calState = CAL_IDLE;
bool calibrationRequest = false;
unsigned long calTimer = 0;
long calSum = 0;
float calSqSum = 0.0f;
int calCount = 0;
byte calFailCount = 0;
// Auto Retry
byte retryCount = 0;
const byte MAX_RETRY = 3;
bool dryRunEnabled = true;
float I_DRY_RUN_THRESHOLD = I_DRY_RUN_THRESHOLD_DEFAULT;
// OLED
SSD1306AsciiWire oled;
#define OLED_ADDR 0x3C
// ======================================================
// OLED
// ======================================================
void drawMainScreen(unsigned long now) {
static char prevStatus[12] = "";
static char prevCurrent[12] = "";
static char prevFloat[12] = "";
static char prevPress[12] = "";
static char prevTime[12] = "";
static char prevFooter[20] = "";
static unsigned long prevSec = 999999;
char buf[20];
snprintf(buf, sizeof(buf), "%s", pumpState ? "RUNNING" : "STANDBY");
if (strcmp(buf, prevStatus) != 0) {
oled.set2X();
oled.setCursor(0, 0);
oled.print(F(" "));
oled.setCursor(0, 0);
oled.print(buf);
strcpy(prevStatus, buf);
oled.set1X();
}
float displayI = (emaCurrent < 0.1f) ? 0.0f : emaCurrent;
int current_x100 = (int)(displayI * 100 + 0.5f);
snprintf(buf, sizeof(buf), "I:%d.%02d A", current_x100 / 100, current_x100 % 100);
if (strcmp(buf, prevCurrent) != 0) {
oled.setCursor(0, 2);
oled.clearToEOL();
oled.print(buf);
strcpy(prevCurrent, buf);
}
snprintf(buf, sizeof(buf), "FLOAT:%s", floatFiltered ? "OK " : "LOW");
if (strcmp(buf, prevFloat) != 0) {
oled.setCursor(0, 3);
oled.clearToEOL();
oled.print(buf);
strcpy(prevFloat, buf);
}
snprintf(buf, sizeof(buf), "PRESS:%s", pressFiltered ? "OK " : "LOW");
if (strcmp(buf, prevPress) != 0) {
oled.setCursor(0, 4);
oled.clearToEOL();
oled.print(buf);
strcpy(prevPress, buf);
}
unsigned long sec = (pumpState && pumpStartTime > 0) ? (now - pumpStartTime) / 1000 : 999998;
if (sec != prevSec) {
prevSec = sec;
if (pumpState && pumpStartTime > 0) {
snprintf(buf, sizeof(buf), "TIME:%02lu:%02lu", sec/60, sec%60);
} else {
snprintf(buf, sizeof(buf), "TIME:--:--");
}
oled.setCursor(0, 5);
oled.clearToEOL();
oled.print(buf);
strcpy(prevTime, buf);
}
snprintf(buf, sizeof(buf), "%s", (calState != CAL_IDLE) ? "CALIBRATING..." : "SYSTEM NORMAL");
if (strcmp(buf, prevFooter) != 0) {
oled.setCursor(0, 6);
oled.clearToEOL();
oled.print(buf);
strcpy(prevFooter, buf);
}
}
void drawFaultScreen() {
if (faultCode == lastDisplayedFault) return;
lastDisplayedFault = faultCode;
oled.clear();
oled.setFont(Adafruit5x7);
oled.set2X();
oled.setCursor(0, 0);
oled.println(F("FAULT"));
oled.set1X();
oled.setCursor(0, 2);
oled.print(F("CODE: "));
oled.println(faultCode);
oled.print(F("ERR : "));
switch (faultCode) {
case 1: oled.println(F("NO PRESSURE")); break;
case 2: oled.println(F("DRY RUN")); break;
case 3: oled.println(F("SENSOR ERR")); break;
case 4: oled.println(F("RELAY ERR")); break;
case 5: oled.println(F("TIMEOUT")); break;
default: oled.println(F("UNKNOWN")); break;
}
oled.println();
oled.print(F("HOLD 10s TO RESET"));
}
// ======================================================
// VISUAL & BUTTON
// ======================================================
void updateVisualIndicators(unsigned long now) {
bool blinkRun = ((now % 500) < 250);
bool blinkFault = false;
if (tripState && faultCode >= 1 && faultCode <= 5) {
unsigned long period = 600, onTime = 200;
switch (faultCode) {
case 1: period = 800; onTime = 400; break;
case 2: period = 400; onTime = 200; break;
case 3: period = 300; onTime = 100; break;
case 4: blinkFault = ((now % 1000 < 150) || (now % 1000 >= 250 && now % 1000 < 400)); break;
case 5: period = 1200; onTime = 600; break;
}
if (faultCode != 4) blinkFault = ((now % period) < onTime);
}
if (tripState) {
digitalWrite(PIN_LED_STATUS, LOW);
digitalWrite(PIN_LED_ALARM, blinkFault);
digitalWrite(PIN_ALARM, blinkFault);
} else if (pumpState) {
digitalWrite(PIN_LED_STATUS, blinkRun);
digitalWrite(PIN_LED_ALARM, LOW);
digitalWrite(PIN_ALARM, LOW);
} else {
digitalWrite(PIN_LED_STATUS, HIGH);
digitalWrite(PIN_LED_ALARM, LOW);
digitalWrite(PIN_ALARM, LOW);
}
}
void handleButton(unsigned long now) {
static bool lastRaw = HIGH;
static unsigned long debounceTime = 0;
static unsigned long buttonPressTime = 0;
static bool buttonActive = false;
bool raw = (digitalRead(PIN_RESET) == LOW);
if (raw != lastRaw) {
debounceTime = now;
lastRaw = raw;
}
if (now - debounceTime < 30) return;
bool pressed = raw;
if (pressed && !buttonActive) {
buttonActive = true;
buttonPressTime = now;
}
else if (!pressed && buttonActive) {
unsigned long hold = now - buttonPressTime;
buttonActive = false;
if (hold > 10000) {
tripState = false;
faultCode = 0;
lastDisplayedFault = 255;
oled.clear();
EEPROM.update(ADDR_TRIP, 0);
EEPROM.update(ADDR_FAULT, 0);
fullProtectionReset();
calibrationRequest = true;
}
else if (hold > 3000) {
tripState = false;
faultCode = 0;
lastDisplayedFault = 255;
oled.clear();
EEPROM.update(ADDR_TRIP, 0);
EEPROM.update(ADDR_FAULT, 0);
fullProtectionReset();
calibrationRequest = true;
}
buttonPressTime = 0;
}
}
// ======================================================
// CORE
// ======================================================
void updateSensors(unsigned long now) {
static byte badADC = 0;
if (now - lastSampleTime >= 1) {
lastSampleTime = now;
int raw = analogRead(PIN_ACS712);
bool isStarting = pumpState && (emaCurrent < 0.25f || now - pumpStartTime < 3000);
if (!isStarting) {
if (raw < 10 || raw > 1010) {
if (++badADC > 20) triggerTrip(3, now);
} else {
badADC = 0;
}
} else {
badADC = 0;
}
float current = (raw - adcOffset) / ADC_PER_AMP;
sqAccumulator += current * current;
sampleCount++;
if (calState == CAL_SAMPLING) {
calSum += raw;
calSqSum += (float)raw * raw;
calCount++;
}
if (now - rmsWindowStart >= 200) {
rmsCurrent = sampleCount ? sqrt(sqAccumulator / sampleCount) : 0.0f;
sqAccumulator = 0; sampleCount = 0;
rmsWindowStart = now;
float alpha = (rmsCurrent > emaCurrent) ? 0.45f : 0.18f;
emaCurrent = emaCurrent * (1.0f - alpha) + rmsCurrent * alpha;
}
}
static bool fRaw = false, pRaw = false;
static unsigned long fC = 0, pC = 0;
bool fIn = (digitalRead(PIN_FLOAT) == LOW);
bool pIn = (digitalRead(PIN_PRESSURE) == LOW);
if (fIn != fRaw) { fRaw = fIn; fC = now; }
if (now - fC >= T_DEBOUNCE_FLOAT) floatFiltered = fRaw;
if (pIn != pRaw) { pRaw = pIn; pC = now; }
if (now - pC >= T_DEBOUNCE_PRESS) pressFiltered = pIn;
}
void processProtection(unsigned long now) {
if (tripState || calState != CAL_IDLE) return;
// Sensor Watchdog
if (pumpState) {
if (sensorWatchdogTimer == 0) sensorWatchdogTimer = now;
if (now - sensorWatchdogTimer > T_SENSOR_WATCHDOG) {
LOG_ERROR("Sensor watchdog timeout");
triggerTrip(3, now);
}
} else {
sensorWatchdogTimer = 0;
}
// Welded Relay
if (!pumpState && (now - lastPumpOffTime > 8000)) {
if (emaCurrent > WELD_CURRENT_THRESHOLD && emaCurrent > 0.3f &&
(normalCurrentRef < 0.3f || emaCurrent > normalCurrentRef * 0.4f)) {
if (weldStableTime == 0) weldStableTime = now;
if (now - weldStableTime > 1500) {
if (weldedVerifyTime == 0) weldedVerifyTime = now;
if (now - weldedVerifyTime >= T_WELDED_VAL_TIME) triggerTrip(4, now);
}
} else {
weldStableTime = 0;
weldedVerifyTime = 0;
}
}
if (!pumpState) {
noPressureTime = lowCurrentTime = 0;
stableRunStartTime = 0;
return;
}
if (continuousRunStartTime == 0) continuousRunStartTime = now;
if (T_MAX_RUNTIME && now - continuousRunStartTime >= T_MAX_RUNTIME) triggerTrip(5, now);
// Pressure
if (!pressFiltered) {
pressStableTime = 0;
if (noPressureTime == 0) noPressureTime = now;
if (now - noPressureTime >= T_PRIMING_TIMEOUT) triggerTrip(1, now);
} else {
if (pressStableTime == 0) pressStableTime = now;
noPressureTime = 0;
}
// Dry Run
float effectiveDry = (normalCurrentRef > 0.3f) ? max(0.25f, normalCurrentRef * DRY_RUN_RATIO) : I_DRY_RUN_THRESHOLD;
if (dryRunEnabled && (now - pumpStartTime >= T_CURRENT_GRACE)) {
if (emaCurrent < effectiveDry) {
if (lowCurrentTime == 0) lowCurrentTime = now;
if (now - lowCurrentTime >= T_CURRENT_TRIP) triggerTrip(2, now);
} else lowCurrentTime = 0;
}
// Normal Current Ref
if (pumpState && pressFiltered && emaCurrent > 0.4f && (now - pumpStartTime > 8000)) {
if (emaCurrent > normalCurrentRef * 1.3f) {
normalCurrentRef = normalCurrentRef * 0.7f + emaCurrent * 0.3f;
} else if (emaCurrent > normalCurrentRef) {
normalCurrentRef = normalCurrentRef * 0.9f + emaCurrent * 0.1f;
}
normalCurrentRef = max(normalCurrentRef, MIN_NORMAL_CURRENT);
}
// Stable run
if (pumpState && pressFiltered && emaCurrent > 0.3f) {
if (stableRunStartTime == 0) stableRunStartTime = now;
if (now - stableRunStartTime > T_STABLE_RUN) retryCount = 0;
} else {
stableRunStartTime = 0;
}
}
void updateOutputs(unsigned long now) {
if (tripState || now - lastTripTime < T_POST_TRIP_LOCK) return;
bool canStart = (now - lastPumpOffTime >= T_MIN_OFF_TIME) &&
(now >= startupTime + T_STARTUP_INHIBIT) &&
(!pumpState || (now - pumpStartTime >= T_MIN_ON_TIME));
bool pressureStable = (pressFiltered && pressStableTime && (now - pressStableTime >= T_PRESS_STABLE));
bool command = floatFiltered && !pressureStable && !tripState && canStart;
if (command && !pumpState) {
pumpState = true;
pumpStartTime = continuousRunStartTime = now;
} else if ((!command || pressureStable) && pumpState) {
pumpState = false;
lastPumpOffTime = now;
continuousRunStartTime = 0;
}
digitalWrite(PIN_PUMP, pumpState ? LOW : HIGH);
}
void autoRetryCheck(unsigned long now) {
if (!tripState || faultCode == 0 || retryCount >= MAX_RETRY) return;
if (now - lastTripTime < T_AUTO_RETRY) return;
bool conditionRecovered = (faultCode == 1 && pressFiltered) ||
(faultCode == 2 && emaCurrent > MIN_NORMAL_CURRENT * 1.1f);
if ((faultCode == 1 || faultCode == 2) && floatFiltered && conditionRecovered) {
retryCount++;
tripState = false;
faultCode = 0;
lastDisplayedFault = 255;
EEPROM.update(ADDR_TRIP, 0);
EEPROM.update(ADDR_FAULT, 0);
fullProtectionReset();
}
}
void triggerTrip(byte code, unsigned long now) {
if (tripState) return;
fullProtectionReset();
lastTripTime = now;
tripState = true;
faultCode = code;
lastDisplayedFault = 255;
EEPROM.update(ADDR_TRIP, 1);
EEPROM.update(ADDR_FAULT, faultCode);
pumpState = false;
digitalWrite(PIN_PUMP, HIGH);
lastPumpOffTime = now;
LOG_ERROR("Trip triggered! Code: %d", code);
}
void fullProtectionReset() {
noPressureTime = lowCurrentTime = weldedVerifyTime = continuousRunStartTime =
pumpStartTime = acsFaultStartTime = pressStableTime = stableRunStartTime =
weldStableTime = sensorWatchdogTimer = 0;
}
// ======================================================
// SETTINGS & EEPROM
// ======================================================
byte calculateChecksum() {
byte sum = 0xA5;
for (int i = ADDR_DRY_ENABLE; i <= ADDR_MAX_RUNTIME_IDX; i++) {
byte b = EEPROM.read(i);
sum ^= b;
sum = (sum << 1) | (sum >> 7);
}
return sum;
}
void initEEPROMifNeeded() {
if (EEPROM.read(ADDR_SIGNATURE) != EEPROM_SIGNATURE || EEPROM.read(ADDR_CHECKSUM) != calculateChecksum()) {
saveSettings();
EEPROM.update(ADDR_SIGNATURE, EEPROM_SIGNATURE);
EEPROM.update(ADDR_TRIP, 0);
EEPROM.update(ADDR_FAULT, 0);
}
}
void saveSettings() {
EEPROM.update(ADDR_DRY_ENABLE, dryRunEnabled ? 1 : 0);
EEPROM.update(ADDR_DRY_THRESHOLD, (byte)(I_DRY_RUN_THRESHOLD * 100));
byte idx = (T_MAX_RUNTIME == 1800000UL) ? 0 : (T_MAX_RUNTIME == 3600000UL) ? 1 : (T_MAX_RUNTIME == 5400000UL) ? 2 : 3;
EEPROM.update(ADDR_MAX_RUNTIME_IDX, idx);
EEPROM.update(ADDR_CHECKSUM, calculateChecksum());
}
void loadSettings() {
if (EEPROM.read(ADDR_CHECKSUM) != calculateChecksum()) {
dryRunEnabled = true;
I_DRY_RUN_THRESHOLD = I_DRY_RUN_THRESHOLD_DEFAULT;
T_MAX_RUNTIME = 3600000UL;
saveSettings();
return;
}
dryRunEnabled = (EEPROM.read(ADDR_DRY_ENABLE) == 1);
byte th = EEPROM.read(ADDR_DRY_THRESHOLD);
if (th >= 25 && th <= 150) I_DRY_RUN_THRESHOLD = th / 100.0f;
byte idx = EEPROM.read(ADDR_MAX_RUNTIME_IDX);
T_MAX_RUNTIME = (idx == 0) ? 1800000UL : (idx == 1) ? 3600000UL : (idx == 2) ? 5400000UL : 0UL;
}
void readDIPSwitch() {
dryRunEnabled = (digitalRead(PIN_DIP_DRYRUN) == LOW);
bool dip2 = (digitalRead(PIN_DIP_MAX1) == LOW);
bool dip3 = (digitalRead(PIN_DIP_MAX2) == LOW);
if (dip2 && dip3) T_MAX_RUNTIME = 0UL;
else if (dip2 && !dip3) T_MAX_RUNTIME = 1800000UL;
else if (!dip2 && dip3) T_MAX_RUNTIME = 3600000UL;
else T_MAX_RUNTIME = 5400000UL;
}
void readDIPSwitchPeriodic(unsigned long now) {
if (now - lastDIPReadTime >= T_DIP_READ_INTERVAL) {
lastDIPReadTime = now;
readDIPSwitch();
}
}
// ======================================================
// CALIBRATION
// ======================================================
void processCalibration(unsigned long now) {
if (tripState || pumpState) {
calState = CAL_IDLE;
calibrationRequest = false;
return;
}
switch (calState) {
case CAL_IDLE:
if (calibrationRequest && emaCurrent < 0.08f) {
calibrationRequest = false;
calTimer = now;
calState = CAL_SETTLING;
calFailCount = 0;
}
break;
case CAL_SETTLING:
if (now - calTimer >= 2000) {
calSum = 0; calSqSum = 0.0f; calCount = 0;
calTimer = now;
calState = CAL_SAMPLING;
}
break;
case CAL_SAMPLING:
if (now - calTimer >= 3500) {
if (calCount > 20) {
float avgRaw = (float)calSum / calCount;
float variance = (calSqSum / calCount) - (avgRaw * avgRaw);
if (avgRaw >= 485 && avgRaw <= 540 && variance < 10.0f && emaCurrent < 0.08f) {
adcOffset = avgRaw;
lastAutoCalibTime = now;
calFailCount = 0;
} else {
calFailCount++;
if (calFailCount >= 3) triggerTrip(3, now);
}
}
calState = CAL_IDLE;
}
break;
}
}
// ======================================================
// SETUP & LOOP
// ======================================================
void setup() {
Serial.begin(115200);
LOG_INFO("Pump Controller v12 started");
pinMode(PIN_FLOAT, INPUT_PULLUP);
pinMode(PIN_PRESSURE, INPUT_PULLUP);
pinMode(PIN_RESET, INPUT_PULLUP);
pinMode(PIN_PUMP, OUTPUT);
pinMode(PIN_ALARM, OUTPUT);
pinMode(PIN_LED_STATUS, OUTPUT);
pinMode(PIN_LED_ALARM, OUTPUT);
pinMode(PIN_DIP_DRYRUN, INPUT_PULLUP);
pinMode(PIN_DIP_MAX1, INPUT_PULLUP);
pinMode(PIN_DIP_MAX2, INPUT_PULLUP);
pinMode(PIN_DIP_RESERVE, INPUT_PULLUP);
digitalWrite(PIN_PUMP, HIGH);
digitalWrite(PIN_ALARM, LOW);
digitalWrite(PIN_LED_STATUS, LOW);
digitalWrite(PIN_LED_ALARM, LOW);
Wire.begin();
Wire.setClock(400000L);
Wire.setWireTimeout(3000, true);
oled.begin(&Adafruit128x64, OLED_ADDR);
oled.setFont(Adafruit5x7);
oled.clear();
oled.set2X();
oled.println(F("PUMP"));
oled.println(F("CTRL v12"));
oled.set1X();
delay(2000);
initEEPROMifNeeded();
loadSettings();
readDIPSwitch();
if (EEPROM.read(ADDR_TRIP) == 1) {
tripState = true;
faultCode = EEPROM.read(ADDR_FAULT);
if (faultCode < 1 || faultCode > 5) {
tripState = false;
faultCode = 0;
}
}
unsigned long now = millis();
rmsWindowStart = lastSampleTime = lastAutoCalibTime = lastDIPReadTime =
lastCalibAttempt = startupTime = now;
adcOffset = 512.0f;
wdt_enable(WDTO_4S);
LOG_INFO("System Ready");
}
void loop() {
unsigned long now = millis();
updateSensors(now);
handleButton(now);
readDIPSwitchPeriodic(now);
if (tripState) {
if (now - lastTripTime < T_POST_TRIP_LOCK) {
updateVisualIndicators(now);
wdt_reset();
return;
}
autoRetryCheck(now);
}
if (!tripState) {
bool calibTime = (now - lastAutoCalibTime >= T_CALIB_INTERVAL) ||
(calFailCount > 0 && now - lastCalibAttempt >= T_CALIB_RETRY);
if (!pumpState && now >= startupTime + T_STARTUP_INHIBIT &&
(now - lastPumpOffTime >= T_MIN_OFF_TIME) && calibTime) {
calibrationRequest = true;
lastCalibAttempt = now;
}
}
processCalibration(now);
processProtection(now);
updateOutputs(now);
updateVisualIndicators(now);
static unsigned long lastDisplay = 0;
if (now - lastDisplay >= (tripState ? 700 : 500)) {
tripState ? drawFaultScreen() : drawMainScreen(now);
lastDisplay = now;
}
wdt_reset();
}