#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <EEPROM.h>
// ====================== SETTINGS INDEX DEFINITIONS ======================
#define SETTING_WASH_AG_TIME 0 // "wash Ag"
#define SETTING_24H_AG_PREE_SOAK_TIME 1 // "24H Ag on time"
#define SETTING_24H_AG_DL_TIME 2 // "24H Ag wait time"
#define SETTING_24H_AG_ONp_TIME 3 // "24H Ag ON pulse time"
#define SETTING_RINCE_AG_TIME 4 // "rince Ag"
// Wash drain timers (8 settings) -
#define SETTING_WASH_DRAIN_TIME1 5 // "wash drain 1"
#define SETTING_WASH_DRAIN_TIME2 6 // "wash drain 2"
#define SETTING_WASH_DRAIN_TIME3 7 // "wash drain 3"
#define SETTING_WASH_DRAIN_TIME4 8 // "wash drain 4"
#define SETTING_WASH_DRAIN_TIME5 9 // "wash drain 5"
#define SETTING_WASH_DRAIN_TIME6 10 // "wash drain 6"
#define SETTING_WASH_DRAIN_TIME7 11 // "wash drain 7"
#define SETTING_WASH_DRAIN_TIME8 12 // "wash drain 8"
// Cold wash timers (8 settings) -
#define SETTING_COLD_WASH1_TIME 13 // "cold wash 1"
#define SETTING_COLD_WASH2_TIME 14 // "cold wash 2"
#define SETTING_COLD_WASH3_TIME 15 // "cold wash 3"
#define SETTING_COLD_WASH4_TIME 16 // "cold wash 4"
#define SETTING_COLD_WASH5_TIME 17 // "cold wash 5"
#define SETTING_COLD_WASH6_TIME 18 // "cold wash 6"
#define SETTING_COLD_WASH7_TIME 19 // "cold wash 7"
#define SETTING_COLD_WASH8_TIME 20 // "cold wash 8"
// Cooking timers (8 settings) -
#define SETTING_COOKING1_TIME 21 // "cooking1"
#define SETTING_COOKING2_TIME 22 // "cooking2"
#define SETTING_COOKING3_TIME 23 // "cooking3"
#define SETTING_COOKING4_TIME 24 // "cooking4"
#define SETTING_COOKING5_TIME 25 // "cooking5"
#define SETTING_COOKING6_TIME 26 // "cooking6"
#define SETTING_COOKING7_TIME 27 // "cooking7"
#define SETTING_COOKING8_TIME 28 // "cooking8"
// Hot rinse with agitator (8 settings) -
#define SETTING_HOT_RINCE1_TIME 29 // "hot rince 1"
#define SETTING_HOT_RINCE2_TIME 30 // "hot rince 2"
#define SETTING_HOT_RINCE3_TIME 31 // "hot rince 3"
#define SETTING_HOT_RINCE4_TIME 32 // "hot rince 4"
#define SETTING_HOT_RINCE5_TIME 33 // "hot rince 5"
#define SETTING_HOT_RINCE6_TIME 34 // "hot rince 6"
#define SETTING_HOT_RINCE7_TIME 35 // "hot rince 7"
#define SETTING_HOT_RINCE8_TIME 36 // "hot rince 8"
// Hot rinse with air (8 settings) -
#define SETTING_AIR1_TIME 37 // "air1"
#define SETTING_AIR2_TIME 38 // "air2"
#define SETTING_AIR3_TIME 39 // "air3"
#define SETTING_AIR4_TIME 40 // "air4"
#define SETTING_AIR5_TIME 41 // "air5"
#define SETTING_AIR6_TIME 42 // "air6"
#define SETTING_AIR7_TIME 43 // "air7"
#define SETTING_AIR8_TIME 44 // "air8"
// Temperature & bypass settings -
#define SETTING_BYPASS 45 // "bypass"
#define SETTING_COOK_SUB_AG1 46 // "cook Sub Ag 1"
#define SETTING_COOK_SUB_AG2 47 // "cook Sub Ag 2"
// ====================== SYSTEM SETTINGS ======================
struct Setting {
const char* name;
unsigned long value;
unsigned long min;
unsigned long max;
bool isBoolean;
int step;
};
Setting settings[] = {
[SETTING_WASH_AG_TIME] = {"wash Ag", 1, 1000, 300000, false, 1},
[SETTING_24H_AG_PREE_SOAK_TIME] = {"pre soak AG", 1000, 1000, 180000, false, 1},
[SETTING_24H_AG_DL_TIME] = {"24H Ag wait", 1000, 1000, 600000, false, 1},
[SETTING_24H_AG_ONp_TIME] = {"24H Ag ON pulse", 1000, 1000, 180000, false, 1},
[SETTING_RINCE_AG_TIME] = {"cold rince Ag", 1, 1000, 600000, false, 1},
// Wash drain timers (8 settings) -
[SETTING_WASH_DRAIN_TIME1] = {"wash drain 1", 8000, 1000, 600000, false, 1},
[SETTING_WASH_DRAIN_TIME2] = {"wash drain 2", 8000, 1000, 600000, false, 1},
[SETTING_WASH_DRAIN_TIME3] = {"wash drain 3", 8000, 1000, 600000, false, 1},
[SETTING_WASH_DRAIN_TIME4] = {"wash drain 4", 8000, 1000, 600000, false, 1},
[SETTING_WASH_DRAIN_TIME5] = {"wash drain 5", 8000, 1000, 600000, false, 1},
[SETTING_WASH_DRAIN_TIME6] = {"wash drain 6", 8000, 1000, 600000, false, 1},
[SETTING_WASH_DRAIN_TIME7] = {"wash drain 7", 8000, 1000, 600000, false, 1},
[SETTING_WASH_DRAIN_TIME8] = {"wash drain 8", 8000, 1000, 600000, false, 1},
// Cold wash timers (8 settings) -
[SETTING_COLD_WASH1_TIME] = {"cold wash 1", 30000, 1000, 600000, false, 1},
[SETTING_COLD_WASH2_TIME] = {"cold wash 2", 30000, 1000, 600000, false, 1},
[SETTING_COLD_WASH3_TIME] = {"cold wash 3", 30000, 1000, 600000, false, 1},
[SETTING_COLD_WASH4_TIME] = {"cold wash 4", 30000, 1000, 600000, false, 1},
[SETTING_COLD_WASH5_TIME] = {"cold wash 5", 30000, 1000, 600000, false, 1},
[SETTING_COLD_WASH6_TIME] = {"cold wash 6", 30000, 1000, 600000, false, 1},
[SETTING_COLD_WASH7_TIME] = {"cold wash 7", 30000, 1000, 600000, false, 1},
[SETTING_COLD_WASH8_TIME] = {"cold wash 8", 30000, 1000, 600000, false, 1},
// Cooking timers (8 settings) -
[SETTING_COOKING1_TIME] = {"cooking1", 30000, 1000, 600000, false, 1},
[SETTING_COOKING2_TIME] = {"cooking2", 30000, 1000, 600000, false, 1},
[SETTING_COOKING3_TIME] = {"cooking3", 30000, 1000, 600000, false, 1},
[SETTING_COOKING4_TIME] = {"cooking4", 30000, 1000, 600000, false, 1},
[SETTING_COOKING5_TIME] = {"cooking5", 30000, 1000, 600000, false, 1},
[SETTING_COOKING6_TIME] = {"cooking6", 30000, 1000, 600000, false, 1},
[SETTING_COOKING7_TIME] = {"cooking7", 30000, 1000, 600000, false, 1},
[SETTING_COOKING8_TIME] = {"cooking8", 30000, 1000, 600000, false, 1},
// Hot rinse with agitator (8 settings) -
[SETTING_HOT_RINCE1_TIME] = {"hot rince 1", 30000, 1000, 600000, false, 1},
[SETTING_HOT_RINCE2_TIME] = {"hot rince 2", 30000, 1000, 600000, false, 1},
[SETTING_HOT_RINCE3_TIME] = {"hot rince 3", 30000, 1000, 600000, false, 1},
[SETTING_HOT_RINCE4_TIME] = {"hot rince 4", 30000, 1000, 600000, false, 1},
[SETTING_HOT_RINCE5_TIME] = {"hot rince 5", 30000, 1000, 600000, false, 1},
[SETTING_HOT_RINCE6_TIME] = {"hot rince 6", 30000, 1000, 600000, false, 1},
[SETTING_HOT_RINCE7_TIME] = {"hot rince 7", 30000, 1000, 600000, false, 1},
[SETTING_HOT_RINCE8_TIME] = {"hot rince 8", 30000, 1000, 600000, false, 1},
// Hot rinse with air (8 settings) -
[SETTING_AIR1_TIME] = {"air1", 30000, 1000, 600000, false, 1},
[SETTING_AIR2_TIME] = {"air2", 30000, 1000, 600000, false, 1},
[SETTING_AIR3_TIME] = {"air3", 30000, 1000, 600000, false, 1},
[SETTING_AIR4_TIME] = {"air4", 30000, 1000, 600000, false, 1},
[SETTING_AIR5_TIME] = {"air5", 30000, 1000, 600000, false, 1},
[SETTING_AIR6_TIME] = {"air6", 30000, 1000, 600000, false, 1},
[SETTING_AIR7_TIME] = {"air7", 30000, 1000, 600000, false, 1},
[SETTING_AIR8_TIME] = {"air8", 30000, 1000, 600000, false, 1},
[SETTING_BYPASS] = {"bypass", 0, 0, 1, true, 1},
[SETTING_COOK_SUB_AG1] = {"cook Sub Ag 1", 5000, 1000, 600000, false, 1},
[SETTING_COOK_SUB_AG2] = {"cook Sub Ag 2", 5000, 1000, 600000, false, 1}
};
const int numSettings = sizeof(settings) / sizeof(settings[0]);
// ====================== PIN DEFINITIONS ======================
// Button Pins (consistent naming)
const int BTN_UP = 13;
const int BTN_DOWN = 12;
const int BTN_SELECT = 11;
const int BTN_BACK = 10;
// Other Input Pins
const int irSen1Pin = 8; //flame water
const int irSen2Pin = 9; //flame tank
const int dosingSwitchPin = 6; //dosing switch
const int tiltLockPin = 14; //locked
const int fillPin = 16; //fill switch-good
const int OPENdrainSwitchPin = 3; //drain
const int CLOSEDdrainSwitchPin = 2; //drain
// Output Pins
const int powerON = 53; //220v
const int waterAgitator = 51;
const int waterFill = 49;
const int drain = 47;
const int gasMainTank = 45;
const int gasWaterHeat = 43;
//space
const int air = 39; //12
const int dosing = 37 ; //12v
const int motor = 35 ; //220v
const int WaterBY = 29; //220v
const int Elight = 33; //220v
const int sparkPin = 31; //220v
//space
//========temperature=========
// Clock monitoring settings
#define CLOCK_CHECK_INTERVAL 1200 // Check clock every 1.2 seconds
#define WAIT_RESET_TIME 2000 // A3 low for 2 seconds during reset
#define WAIT_AFTER_RESET_TIME 1000 // Wait 1 second after reset
// Pin definitions for clock monitoring
#define CLOCK_PIN A1 // Clock input - INPUT_PULLUP
#define POWER_PIN A3 // Power control output - OUTPUT
#define WATER_LO_PIN A4 // Water low input - INPUT_PULLUP
#define WATER_HI_PIN A5 // Water high input - INPUT_PULLUP
#define TANK_LO_PIN A6 // Tank low input - INPUT_PULLUP
#define TANK_HI_PIN A7 // Tank high input - INPUT_PULLUP
#define LCD_UPDATE_TIME 500 // LCD update interval in milliseconds
// Clock monitoring variables
bool clockState = false;
bool lastClockState = false;
unsigned long lastClockChange = 0;
bool clockRunning = true;
bool systemReset = false;
unsigned long resetStartTime = 0;
// Input states
bool waterLoState = false;
bool waterHiState = false;
bool tankLoState = false;
bool tankHiState = false;
// Display states (keep last values if clock stops)
bool displayWaterLo = false;
bool displayWaterHi = false;
bool displayTankLo = false;
bool displayTankHi = false;
bool displayClockGood = true;
// Timing variables
unsigned long previousLCDMillis = 0;
unsigned long previousClockCheckMillis = 0;
// System Constants
const int maxIgnitionAttempts = 5;
#define EEPROM_START_ADDR 0
// ====================== MENU SYSTEM ======================
enum MenuState {
MAIN_MENU,
SETTINGS_MENU,
RUN_MODE,
SAVE_MENU,
DIAG_MENU
};
// LCD and Temp Sensors
LiquidCrystal_I2C lcd(0x27, 16, 2);
// Menu Variables
MenuState currentMenu = MAIN_MENU;
int menuSelection = 0;
int diagSelection = 0;
bool inSubMenu = false;
bool editing = false;
int currentSetting = 0;
int firstrun = 0;
int runNumer = 0;
int RUNpos = 0;
int drainmove = 0;
int IRtester = 0;
int ErraNO = 0;
//==== gas===
// Add these with your other global variables
bool gasTankState = false;
bool gasWaterState = false;
// Button hold variables
unsigned long buttonHoldStartTime = 0;
const unsigned long holdExitTime = 2000;
bool exitHoldActive = false;
// Menu Items
const char* mainMenuItems[] = {
"Diagnostics", "Settings", "Run mode1", "Run mode2", "Run mode3",
"Run mode4", "Run mode5", "Run mode6", "Run mode7", "Run mode8",
"Save Settings", "Restart"
};
const int numMainMenuItems = sizeof(mainMenuItems) / sizeof(mainMenuItems[0]);
const char* diagMenuItems[] = {
"Drain TEST", // 0
"Input TEST SW", // 1
"Input TEST IR", // 2
"Temperature", // 3
"Button TEST", // 4
"Output TEST", // 5
"Output GAS", // 6
"Start Position" // 7
};
const int numDiagMenuItems = sizeof(diagMenuItems) / sizeof(diagMenuItems[0]);
// ====================== FUNCTION PROTOTYPES ======================
// Display Functions
void displayMainMenu();
void displayDiagnosticsMenu();
void displayDrainTest(); // 0 - Drain TEST
void displaySWTest(); // 1 - Input TEST SW
void displayIRTest(); // 2 - Input TEST IR
void displayTempTest(); // 3 - Temperature
void displayButtonTest(); // 4 - Button TEST
void displayOutputTest(); // 5 - Output TEST
void displayGasTest(); // 6 - Output GAS
void displayStartPosition(); // 7 - Start Position
void displayRunMode();
void displaySettings();
void displaySetting();
// Menu Handlers
void handleMainMenu();
void handleDiagnosticsMenu();
void handleSettingsMenu();
void handleRunMode();
void handleSaveSettings();
// Utility Functions
void loadSettingsFromEEPROM();
void saveSettingsToEEPROM();
float readTemperature(int pin);
void executeCaseDirectly(int caseNumber);
// Software reset function
void(* resetFunc) (void) = 0; // Declare reset function at address 0
// ====================== SETUP FUNCTION ======================
void setup() {
// Initialize LCD
lcd.init();
lcd.backlight();
// Set pin modes for buttons (using consistent names)
pinMode(BTN_UP, INPUT_PULLUP);
pinMode(BTN_DOWN, INPUT_PULLUP);
pinMode(BTN_SELECT, INPUT_PULLUP);
pinMode(BTN_BACK, INPUT_PULLUP);
// Set pin modes for other inputs
pinMode(dosingSwitchPin, INPUT_PULLUP); //
pinMode(OPENdrainSwitchPin, INPUT_PULLUP); //
pinMode(CLOSEDdrainSwitchPin, INPUT_PULLUP); //
pinMode(irSen1Pin, INPUT_PULLUP);
pinMode(irSen2Pin, INPUT_PULLUP);
pinMode(fillPin, INPUT_PULLUP); //
pinMode(tiltLockPin, INPUT_PULLUP); //
//temps
// Pin definitions for clock monitoring
pinMode(CLOCK_PIN, INPUT_PULLUP);
pinMode(POWER_PIN, OUTPUT);
pinMode(WATER_LO_PIN, INPUT_PULLUP);
pinMode(WATER_HI_PIN, INPUT_PULLUP);
pinMode(TANK_LO_PIN, INPUT_PULLUP);
pinMode(TANK_HI_PIN, INPUT_PULLUP);
// Set pin modes for outputs
pinMode(drain, OUTPUT);
pinMode(waterFill, OUTPUT);
pinMode(waterAgitator, OUTPUT);
pinMode(motor, OUTPUT);
pinMode(gasMainTank, OUTPUT);
pinMode(gasWaterHeat, OUTPUT);
pinMode(air, OUTPUT);
pinMode(dosing, OUTPUT);
pinMode(sparkPin, OUTPUT);
pinMode(Elight, OUTPUT);
pinMode(WaterBY, OUTPUT);
pinMode(powerON, OUTPUT);
// Initialize all outputs to OFF
digitalWrite(drain, LOW);
digitalWrite(waterFill, LOW);
digitalWrite(waterAgitator, LOW);
digitalWrite(motor, LOW);
digitalWrite(gasMainTank, LOW);
digitalWrite(gasWaterHeat, LOW);
digitalWrite(air, LOW);
digitalWrite(dosing, LOW);
digitalWrite(sparkPin, LOW);
digitalWrite(Elight, LOW);
// Load settings from EEPROM
loadSettingsFromEEPROM();
// Display main menu on startup
displayMainMenu();
}
// ====================== MAIN LOOP ======================
void loop() {
// Handle the current menu state
switch (currentMenu) {
case MAIN_MENU:
handleMainMenu();
break;
case DIAG_MENU:
handleDiagnosticsMenu();
break;
case SETTINGS_MENU:
handleSettingsMenu();
break;
case RUN_MODE:
handleRunMode();
break;
case SAVE_MENU:
handleSaveSettings();
break;
}
}
// ====================== EEPROM FUNCTIONS ======================
void loadSettingsFromEEPROM() {
int addr = EEPROM_START_ADDR;
for (int i = 0; i < numSettings; i++) {
EEPROM.get(addr, settings[i].value);
addr += sizeof(float); // Changed from sizeof(unsigned long)
settings[i].value = constrain(settings[i].value, settings[i].min, settings[i].max);
}
}
void saveSettingsToEEPROM() {
int addr = EEPROM_START_ADDR;
for (int i = 0; i < numSettings; i++) {
EEPROM.put(addr, settings[i].value);
addr += sizeof(float); // Changed from sizeof(unsigned long)
}
}
// ====================== DISPLAY FUNCTIONS ======================
/**
Display the main menu
*/
void displayMainMenu() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Main Menu");
lcd.setCursor(0, 1);
lcd.print(">");
lcd.print(mainMenuItems[menuSelection]);
}
/**
Display the diagnostics menu
*/
void displayDiagnosticsMenu() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Diagnostics Menu");
lcd.setCursor(0, 1);
lcd.print(">");
lcd.print(diagMenuItems[diagSelection]);
}
/**
Display switch test screen
*/
void displaySWTest() {
lcd.setCursor(0, 0);
lcd.print("dose=");
lcd.print(digitalRead(dosingSwitchPin));
lcd.print(" ");
lcd.setCursor(0, 1);
lcd.print("water=");
lcd.print(digitalRead(fillPin));
lcd.print(" Lock=");
lcd.print(digitalRead(tiltLockPin));
lcd.print(" ");
}
/**
Display IR sensor test screen
*/
void displayIRTest() {
lcd.setCursor(0, 0);
lcd.print("IR sen1-tank =");
lcd.print(digitalRead(irSen1Pin));
lcd.print(" ");
lcd.setCursor(0, 1);
lcd.print("IR sen2-water=");
lcd.print(digitalRead(irSen2Pin));
lcd.print(" ");
}
/**
Read temperature from specified sensor
@param pin The temperature sensor pin to read
@return Temperature in Celsius, or -127.0 on error
*/
// Display temperature test screen
void displayTempTest() {
lcd.setCursor(0, 0);
lcd.print("Water LO=");
lcd.print(digitalRead(TANK_LO_PIN));
lcd.print(" :HI");
lcd.print(digitalRead(TANK_HI_PIN));
lcd.print(" ");
lcd.setCursor(0, 1);
lcd.print("tank LO=");
lcd.print(digitalRead(WATER_LO_PIN));
lcd.print(" :HI");
lcd.print(digitalRead(WATER_HI_PIN));
lcd.print(" ");
if (digitalRead(CLOCK_PIN) == 0) {////////////////////////////////////
lcd.print("*");
}
else {
lcd.print(" ");
}
}
/**
Display button test screen
*/
void displayButtonTest() {
lcd.setCursor(0, 0);
lcd.print("exit=");
lcd.print(digitalRead(BTN_BACK));
lcd.print(" UP=");
lcd.print(digitalRead(BTN_UP));
lcd.print(" ");
lcd.setCursor(0, 1);
lcd.print("entr=");
lcd.print(digitalRead(BTN_SELECT));
lcd.print(" DW=");
lcd.print(digitalRead(BTN_DOWN));
if (exitHoldActive) {
unsigned long holdDuration = millis() - buttonHoldStartTime;
float progress = (float)holdDuration / holdExitTime;
// Progress bar could be displayed here if needed
} else {
lcd.print(" ");
}
}
/**
Display drain test screen
*/
void displayDrainTest() {
// static bool openingInProgress = false;
lcd.setCursor(0, 0);
lcd.print("Drain: CL=");
lcd.print(digitalRead(CLOSEDdrainSwitchPin));
lcd.print(" OP=");
lcd.print (digitalRead(OPENdrainSwitchPin));
//lcd.setCursor(12, 1);
//lcd.print(drainmove);
//close - now Opening...
if (digitalRead(BTN_UP) == LOW && digitalRead(OPENdrainSwitchPin) == LOW) {
digitalWrite(drain, HIGH);
lcd.setCursor(0, 1);
lcd.print("Opening... ");
drainmove = 2;
}
if (drainmove == 2 && digitalRead(OPENdrainSwitchPin) == HIGH ) {
digitalWrite(drain, LOW);
lcd.setCursor(0, 1);
lcd.print("Open ");
drainmove = 0;
}
//open - now Closing...
if (digitalRead(BTN_DOWN) == LOW && digitalRead(CLOSEDdrainSwitchPin) == LOW ) {
digitalWrite(drain, HIGH);
lcd.setCursor(0, 1);
lcd.print("Closing... ");
drainmove = 1;
}
if (drainmove == 1 && digitalRead(CLOSEDdrainSwitchPin) == HIGH ) {
digitalWrite(drain, LOW);
lcd.setCursor(0, 1);
lcd.print("close ");
drainmove = 0;
}
}
/**
Display output test screen
*/
/**
Display output test screen - NON-BLOCKING VERSION
*/
/**
Display output test screen - NON-BLOCKING VERSION
*/
void displayOutputTest() {
struct OutputConfig {
const char* name;
int pin;
bool state;
};
OutputConfig outputs[] = {
{"Water Fill", waterFill, false},//0
{"Water Agit", waterAgitator, false},//1
{"Main Motor", motor, false},//2
{"Air Valve", air, false},//3
{"Spark", sparkPin, false},//4
{"Dosing", dosing, false},//5
{"Bypass Water", WaterBY, false},//6
{"E-light", Elight, false}, //7
{"power on", powerON, false} //8
};
const int numOutputs = sizeof(outputs) / sizeof(outputs[0]);
static int outputSelection = 0;
static bool outputSelected = false;
static bool selectPressed = false;
static bool upPressed = false;
static bool downPressed = false;
static unsigned long lastUpdate = 0;
static unsigned long lastButtonCheck = 0;
const unsigned long updateInterval = 200;
// Only update display periodically
if (millis() - lastUpdate > updateInterval) {
if (!outputSelected) {
// Selection mode
lcd.setCursor(0, 0);
lcd.print("Select Output: ");
lcd.setCursor(0, 1);
lcd.print(">");
String displayName = outputs[outputSelection].name;
if (displayName.length() > 11) displayName = displayName.substring(0, 11);
lcd.print(displayName);
lcd.print(":");
lcd.print(digitalRead(outputs[outputSelection].pin) ? "ON " : "OFF");
lcd.print(" ");
} else {
// Control mode
lcd.setCursor(0, 0);
lcd.print("Control Output: ");
lcd.setCursor(0, 1);
lcd.print(">");
String displayName = outputs[outputSelection].name;
if (displayName.length() > 11) displayName = displayName.substring(0, 11);
lcd.print(displayName);
lcd.print(":");
lcd.print(digitalRead(outputs[outputSelection].pin) ? "ON " : "OFF");
lcd.print(" ");
}
lastUpdate = millis();
}
// Button handling with debounce
if (millis() - lastButtonCheck > 50) {
if (!outputSelected) {
// Navigation in selection mode
if (digitalRead(BTN_UP) == LOW && !upPressed) {
upPressed = true;
outputSelection = (outputSelection - 1 + numOutputs) % numOutputs;
selectPressed = false;
lastButtonCheck = millis();
} else if (digitalRead(BTN_DOWN) == LOW && !downPressed) {
downPressed = true;
outputSelection = (outputSelection + 1) % numOutputs;
selectPressed = false;
lastButtonCheck = millis();
} else if (digitalRead(BTN_SELECT) == LOW && !selectPressed) {
selectPressed = true;
outputSelected = true;
lastButtonCheck = millis();
} else if (digitalRead(BTN_BACK) == LOW) {
// Turn all outputs off when exiting
for (int i = 0; i < numOutputs; i++) {
digitalWrite(outputs[i].pin, LOW);
}
currentMenu = DIAG_MENU;
outputSelected = false;
selectPressed = false;
lastButtonCheck = millis();
return;
}
} else {
// Control in edit mode
if (digitalRead(BTN_UP) == LOW && !upPressed) {
upPressed = true;
digitalWrite(outputs[outputSelection].pin, HIGH);
lastButtonCheck = millis();
} else if (digitalRead(BTN_DOWN) == LOW && !downPressed) {
downPressed = true;
digitalWrite(outputs[outputSelection].pin, LOW);
lastButtonCheck = millis();
} else if (digitalRead(BTN_SELECT) == LOW && !selectPressed) {
selectPressed = true;
outputSelected = false; // Go back to selection mode
lastButtonCheck = millis();
} else if (digitalRead(BTN_BACK) == LOW) {
// Exit completely
for (int i = 0; i < numOutputs; i++) {
digitalWrite(outputs[i].pin, LOW);
}
currentMenu = DIAG_MENU;
outputSelected = false;
selectPressed = false;
lastButtonCheck = millis();
return;
}
}
// Reset button flags when buttons are released
if (digitalRead(BTN_SELECT) == HIGH) {
selectPressed = false;
}
if (digitalRead(BTN_UP) == HIGH) {
upPressed = false;
}
if (digitalRead(BTN_DOWN) == HIGH) {
downPressed = false;
}
lastButtonCheck = millis();
}
}
/**
Display gas output test screen
*/
void displayGasTest() {
typedef struct {
const char* name;
bool* target;
bool enabled;
} OutputConfig;
OutputConfig GasTest[] = {
{"gas tank", & gasTankState, false},
{"gas Water", &gasWaterState, false}
};
const int numGasTests = sizeof(GasTest) / sizeof(GasTest[0]);
static int gasSelection = 0;
static bool gasSelected = false;
static bool selectPressed = false;
static bool inGasTest = true;
while (inGasTest) {
if (!gasSelected) {
// Selection mode
lcd.setCursor(0, 0);
lcd.print("Select Gas Test: ");
if (digitalRead(BTN_SELECT) == HIGH) {
selectPressed = false;
}
lcd.setCursor(0, 1);
lcd.print(">");
String displayName = GasTest[gasSelection].name;
if (displayName.length() > 11) displayName = displayName.substring(0, 11);
lcd.print(displayName);
lcd.print(":");
lcd.print(*GasTest[gasSelection].target ? "ON" : "OFF");
lcd.print(" ");
// Handle navigation
if (digitalRead(BTN_UP) == LOW) {
digitalWrite(sparkPin, HIGH);
gasSelection = (gasSelection - 1 + numGasTests) % numGasTests;
while (digitalRead(BTN_UP) == LOW);
delay(100);
}
else if (digitalRead(BTN_DOWN) == LOW) {
gasSelection = (gasSelection + 1) % numGasTests;
while (digitalRead(BTN_DOWN) == LOW);
delay(200);
}
else if (digitalRead(BTN_SELECT) == LOW && !selectPressed) {
selectPressed = true;
gasSelected = true;
while (digitalRead(BTN_SELECT) == LOW);
}
else if (digitalRead(BTN_BACK) == LOW) {
digitalWrite(gasMainTank, LOW);
digitalWrite(gasWaterHeat, LOW);
digitalWrite(sparkPin, LOW);
gasTankState = false;
gasWaterState = false;
inGasTest = false;
currentMenu = DIAG_MENU;
return;
}
}
else {
// Control mode
lcd.setCursor(0, 0);
lcd.print("Control: ");
// Add IR sensor status display
if (gasSelection == 0) {
lcd.print("IR1: ");
lcd.print(digitalRead(irSen1Pin));
lcd.print(" ");
} else {
lcd.print("IR2: ");
lcd.print(digitalRead(irSen2Pin));
lcd.print(" ");
}
lcd.setCursor(0, 1);
lcd.print(">");
String displayName = GasTest[gasSelection].name;
if (displayName.length() > 8) displayName = displayName.substring(0, 6);
lcd.print(displayName);
lcd.print(":");
lcd.print(*GasTest[gasSelection].target ? "ON" : "OFF");
lcd.print(" ");
// Handle control
if (digitalRead(BTN_UP) == LOW) {
digitalWrite(sparkPin, HIGH);
*GasTest[gasSelection].target = true;
digitalWrite(gasSelection == 0 ? gasMainTank : gasWaterHeat, HIGH);
while (digitalRead(BTN_UP) == LOW);
delay(100);
}
else if (digitalRead(BTN_DOWN) == LOW) {
*GasTest[gasSelection].target = false;
digitalWrite(gasSelection == 0 ? gasMainTank : gasWaterHeat, LOW);
digitalWrite(sparkPin, LOW);
while (digitalRead(BTN_DOWN) == LOW);
delay(200);
}
else if (digitalRead(BTN_BACK) == LOW) {
gasSelected = false;
selectPressed = false;
digitalWrite(gasMainTank, LOW);
digitalWrite(gasWaterHeat, LOW);
digitalWrite(sparkPin, LOW);
gasTankState = false;
gasWaterState = false;
while (digitalRead(BTN_BACK) == LOW);
}
}
delay(50);
}
}
/**
Display start position test screen
*/
/**
Display ADC water level test screen
*/
void displayStartPosition() {
// Navigation states
static int navState = 0; // 0=choose, 1=edit case, 2=edit time
static int selection = 0; // 0=case, 1=time, 2=run
static int btnState = 0;
// Values
static int selectedCase = 1;
static int selectedTime = 1;
// Button state tracking
static int upPressed = 0;
static int downPressed = 0;
static int selectPressed = 0;
static int backPressed = 0;
// Display current state
switch (navState) {
case 0: // Choose mode
lcd.setCursor(0, 0);
lcd.print("Choose :");
lcd.setCursor(0, 1);
if (selection == 0) {
lcd.print(">Case ");
}
if (selection == 1) {
lcd.print(">Time ");
}
if (selection == 2) {
lcd.print(">Run ");
}
// Handle navigation
if (btnState == 0 && digitalRead(BTN_SELECT) == HIGH && digitalRead(BTN_BACK) == HIGH) {
btnState = 1;
}
if (digitalRead(BTN_UP) == LOW && btnState == 1) {
selection = (selection - 1 + 3) % 3;
}
if (digitalRead(BTN_DOWN) == LOW && btnState == 1) {
selection = (selection + 1) % 3;
}
if (digitalRead(BTN_SELECT) == LOW && btnState == 1) {
btnState = 3;
if (selection == 2) {
// Execute run functionality here
runNumer = selectedCase;
RUNpos = selectedTime;
currentMenu = RUN_MODE; // Change to run mode
return; // Exit to let the main loop handle the run mode
} else {
navState = selection + 1;
}
return;
}
if (digitalRead(BTN_BACK) == LOW && btnState == 2) {
backPressed = 1;
currentMenu = DIAG_MENU;
return;
}
return;
case 1: // Edit case mode
lcd.setCursor(0, 0);
lcd.print("Edit Case: ");
lcd.setCursor(0, 1);
lcd.print("Value: ");
lcd.print(selectedCase);
lcd.print(" ");
// Handle button presses
if (btnState == 3 && digitalRead(BTN_SELECT) == HIGH && digitalRead(BTN_BACK) == HIGH) {
btnState = 4;
}
if (digitalRead(BTN_UP) == LOW && btnState == 4) {
selectedCase = min(selectedCase + 1, 61);
}
if (digitalRead(BTN_DOWN) == LOW && btnState == 4) {
selectedCase = max(selectedCase - 1, 1);
}
if (digitalRead(BTN_SELECT) == LOW && btnState == 4) {
btnState = 0;
runNumer = selectedCase;
RUNpos = selectedTime;
navState = 0; // Return to choose mode
return;
}
if (digitalRead(BTN_BACK) == LOW && btnState == 4) {
btnState = 0;
navState = 0; // Return to choose mode
return;
}
return;
case 2: // Edit time mode
lcd.setCursor(0, 0);
lcd.print("Edit Time:");
lcd.setCursor(0, 1);
lcd.print("Value: ");
lcd.print(selectedTime);
lcd.print(" ");
// Handle button presses
if (btnState == 3 && digitalRead(BTN_SELECT) == HIGH && digitalRead(BTN_BACK) == HIGH) {
btnState = 4;
}
if (digitalRead(BTN_UP) == LOW && btnState == 4) {
selectedTime = min(selectedTime + 1, 8);
}
if (digitalRead(BTN_DOWN) == LOW && btnState == 4) {
selectedTime = max(selectedTime - 1, 1);
}
if (digitalRead(BTN_SELECT) == LOW && btnState == 4) {
btnState = 0;
runNumer = selectedCase;
RUNpos = selectedTime;
navState = 0; // Return to choose mode
return;
}
if (digitalRead(BTN_BACK) == LOW && btnState == 4) {
btnState = 0;
navState = 0; // Return to choose mode
return;
}
return;
}
}
/**
Execute a specific case directly from diagnostics
@param caseNumber The case number to execute
*/
void executeCaseDirectly(int caseNumber) {
runNumer = caseNumber;
currentMenu = RUN_MODE;
lcd.clear();
lcd.print("Executing Case");
lcd.setCursor(0, 1);
lcd.print(caseNumber);
delay(1000);
}
/**
Display run mode screen
*/
void displayRunMode() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Run Mode ");
lcd.print(menuSelection - 1);
lcd.setCursor(0, 1);
lcd.print("Not implemented");
}
/**
Display settings menu
*/
void displaySettings() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Settings");
lcd.setCursor(0, 1);
lcd.print(settings[currentSetting].name);
}
/**
Display current setting being edited
*/
void displaySetting() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(settings[currentSetting].name);
lcd.setCursor(15, 0);
lcd.print(editing ? "*" : " ");
lcd.setCursor(0, 1);
// Special handling for bypass setting
if (strcmp(settings[currentSetting].name, "bypass") == 0) {
lcd.print(settings[currentSetting].value ? "NO" : "YES");//defalt to 1 for no
return;
}
// Special display for boolean settings
if (settings[currentSetting].isBoolean) {
lcd.print(settings[currentSetting].value ? "ON" : "OFF");
}
// For time-based settings, show minutes:seconds
else {
unsigned long totalSeconds = settings[currentSetting].value / 1000;
unsigned long minutes = totalSeconds / 60;
unsigned long seconds = totalSeconds % 60;
lcd.print(minutes);
lcd.print("m ");
lcd.print(seconds);
lcd.print("s");
// Add ON/OFF time indicators for sub-agitation settings
if (strcmp(settings[currentSetting].name, "cook Sub Ag 1") == 0) {
lcd.setCursor(10, 1);
lcd.print("ON T ");
} else if (strcmp(settings[currentSetting].name, "cook Sub Ag 2") == 0) {
lcd.setCursor(10, 1);
lcd.print("OFF T");
}
}
}
// ====================== MENU HANDLERS ======================
/**
Handle main menu navigation
*/
void handleMainMenu() {
// Handle UP button - move selection up (wraps around)
if (digitalRead(BTN_DOWN) == LOW) {
menuSelection = (menuSelection - 1 + numMainMenuItems) % numMainMenuItems;
displayMainMenu();
delay(200); // Simple debounce delay
}
// Handle DOWN button - move selection down (wraps around)
if (digitalRead(BTN_UP) == LOW) {
menuSelection = (menuSelection + 1) % numMainMenuItems;
displayMainMenu();
delay(200); // Simple debounce delay
}
// Handle SELECT button
if (digitalRead(BTN_SELECT) == LOW) {
inSubMenu = true;
switch (menuSelection) {
case 0: // Diagnostics
currentMenu = DIAG_MENU;
diagSelection = 0;
displayDiagnosticsMenu();
break;
case 1: // Settings
currentMenu = SETTINGS_MENU;
currentSetting = 0;
editing = false;
displaySettings();
break;
case 10: // Save Settings
currentMenu = SAVE_MENU;
handleSaveSettings();
break;
case 11: // Restart
lcd.clear();
lcd.print("Restarting...");
delay(1000);
resetFunc(); // Call the reset function
break;
default: // Run modes (2-9)
// Map menu selection to run number (2-9 becomes 1-8)
runNumer = menuSelection - 1;
currentMenu = RUN_MODE;
startRun(); // Call function to start the run
break;
}
delay(200); // Simple debounce delay
}
}
//=====================handleDiagnosticsMenu====================================
void handleDiagnosticsMenu() {
static bool testScreenActive = false;
static unsigned long lastButtonCheck = 0;
const unsigned long buttonCheckInterval = 100;
if (millis() - lastButtonCheck > buttonCheckInterval) {
if (!testScreenActive) {
// Handle menu navigation
if (digitalRead(BTN_UP) == LOW) {
diagSelection = (diagSelection + 1) % numDiagMenuItems;
displayDiagnosticsMenu();
} else if (digitalRead(BTN_DOWN) == LOW) {
diagSelection = (diagSelection - 1 + numDiagMenuItems) % numDiagMenuItems;
displayDiagnosticsMenu();
} else if (digitalRead(BTN_SELECT) == LOW) {
testScreenActive = true;
exitHoldActive = false;
// Clear display when entering test screen
lcd.clear();
} else if (digitalRead(BTN_BACK) == LOW) {
currentMenu = MAIN_MENU;
inSubMenu = false;
displayMainMenu();
return;
}
} else {
// Handle test screen exit
if (digitalRead(BTN_BACK) == LOW) {
testScreenActive = false;
displayDiagnosticsMenu();
}
}
lastButtonCheck = millis();
}
// Update test screens if active
if (testScreenActive) {
static unsigned long lastTestUpdate = 0;
if (millis() - lastTestUpdate > 300) { // Update test screens less frequently
switch (diagSelection) {
case 0: displayDrainTest(); break;
case 1: displaySWTest(); break;
case 2: displayIRTest(); break;
case 3: displayTempTest(); break;
case 4: displayButtonTest(); break;
case 5: displayOutputTest(); break;
case 6: displayGasTest(); break;
case 7: displayStartPosition(); break;
}
lastTestUpdate = millis();
}
}
}
// Handle diagnostics menu navigation
//==================handleSaveSettings====================================
/**
Handle save settings confirmation
*/
void handleSaveSettings() {
static bool saveConfirmed = false;
static bool selectReady = false; // Tracks if SELECT is ready for new press
lcd.setCursor(0, 0);
lcd.print("Save Settings? ");
lcd.setCursor(0, 1);
lcd.print(saveConfirmed ? "[Y] Save " : "[N] Save ");
lcd.print(" ");
// Toggle confirmation with UP/DOWN
if (digitalRead(BTN_UP) == LOW || digitalRead(BTN_DOWN) == LOW) {
saveConfirmed = !saveConfirmed;
lcd.setCursor(0, 1);
lcd.print(saveConfirmed ? "[Y] Save " : "[N] Save ");
delay(200); // Simple delay for button press
}
// SELECT button handling - your requested format
if (digitalRead(BTN_SELECT) == LOW && selectReady) {
selectReady = false; // Prevent repeat triggers
if (saveConfirmed) {
saveSettingsToEEPROM();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Settings Saved!");
} else {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Save Canceled!");
}
delay(1000);
currentMenu = MAIN_MENU;
inSubMenu = false;
displayMainMenu();
return;
}
else if (digitalRead(BTN_SELECT) == HIGH) {
selectReady = true; // Reset when button released
}
// BACK button handling
if (digitalRead(BTN_BACK) == LOW) {
currentMenu = MAIN_MENU;
inSubMenu = false;
displayMainMenu();
return;
}
}
/**
Handle settings menu navigation and editing
*/
void handleSettingsMenu() {
static bool settingSelected = false;
static bool enterPressedForTankTemp = false;
static unsigned long lastButtonPress = 0;
static bool selectReady = false; // Tracks if SELECT is ready for new press
const unsigned long debounceDelay = 200;
if (!settingSelected) {
// Navigation between settings
if (digitalRead(BTN_UP) == LOW && millis() - lastButtonPress > debounceDelay) {
currentSetting = (currentSetting + 1) % numSettings;
displaySettings();
enterPressedForTankTemp = false;
lastButtonPress = millis();
}
if (digitalRead(BTN_DOWN) == LOW && millis() - lastButtonPress > debounceDelay) {
currentSetting = (currentSetting - 1 + numSettings) % numSettings;
displaySettings();
enterPressedForTankTemp = false;
lastButtonPress = millis();
}
if (digitalRead(BTN_SELECT) == LOW && selectReady) {
selectReady = false;
settingSelected = true;
editing = true;
displaySetting();
lastButtonPress = millis();
}
else if (digitalRead(BTN_SELECT) == HIGH) {
selectReady = true;
}
if (digitalRead(BTN_BACK) == LOW && millis() - lastButtonPress > debounceDelay) {
currentMenu = MAIN_MENU;
inSubMenu = false;
displayMainMenu();
lastButtonPress = millis();
}
}
else {
if (editing) {
// Handle editing of the setting value
if (strcmp(settings[currentSetting].name, "bypass") == 0) {
// Special handling for bypass (boolean toggle)
if ((digitalRead(BTN_UP) == LOW || digitalRead(BTN_DOWN) == LOW) &&
millis() - lastButtonPress > debounceDelay) {
settings[currentSetting].value = !settings[currentSetting].value;
displaySetting();
lastButtonPress = millis();
}
}
else {
// Normal numeric setting handling
if (digitalRead(BTN_UP) == LOW && millis() - lastButtonPress > debounceDelay) {
// Handle increment
if (strcmp(settings[currentSetting].name, "Water Temp") == 0 ||
strcmp(settings[currentSetting].name, "Tank Temp") == 0) {
settings[currentSetting].value += settings[currentSetting].step;
if (settings[currentSetting].value > settings[currentSetting].max) {
settings[currentSetting].value = settings[currentSetting].max;
}
} else {
if (settings[currentSetting].value + 1000 > settings[currentSetting].max) {
settings[currentSetting].value = settings[currentSetting].min;
} else {
settings[currentSetting].value += 1000;
}
}
displaySetting();
lastButtonPress = millis();
}
if (digitalRead(BTN_DOWN) == LOW && millis() - lastButtonPress > debounceDelay) {
// Handle decrement
if (strcmp(settings[currentSetting].name, "Water Temp") == 0 ||
strcmp(settings[currentSetting].name, "Tank Temp") == 0) {
settings[currentSetting].value -= settings[currentSetting].step;
if (settings[currentSetting].value < settings[currentSetting].min) {
settings[currentSetting].value = settings[currentSetting].min;
}
} else {
if (settings[currentSetting].value - 1000 < settings[currentSetting].min) {
settings[currentSetting].value = settings[currentSetting].max;
} else {
settings[currentSetting].value -= 1000;
}
}
displaySetting();
lastButtonPress = millis();
}
}
// Exit editing mode
if (digitalRead(BTN_SELECT) == LOW && selectReady) {
selectReady = false;
editing = false;
settingSelected = false;
lastButtonPress = millis();
}
else if (digitalRead(BTN_SELECT) == HIGH) {
selectReady = true;
}
if (digitalRead(BTN_BACK) == LOW && millis() - lastButtonPress > debounceDelay) {
editing = false;
settingSelected = false;
displaySettings();
lastButtonPress = millis();
}
}
}
}
void startRun() {
currentMenu == RUN_MODE; // Or whatever your run state variable is called
// Add any run initialization code here
}
// ====================== RUN MODE HANDLER ======================
/**
Handle the run mode state machine
*/
void handleRunMode() {
static unsigned long startTime = 0;
static int currentStep = 0;
static bool running = false;
static unsigned long caseStartTime = 0;
// Variables used in various cases
unsigned long drainTime;
unsigned long drainStartTime;
unsigned long drainCloseStart;
unsigned long washTime;
unsigned long agitationStartTime;
unsigned long remaining;
unsigned long minutes;
unsigned long seconds;
unsigned long drainOpenStart;
// Back button exits run mode if not running
if (digitalRead(BTN_BACK) == LOW && !running) {
currentMenu = MAIN_MENU;
inSubMenu = false;
displayMainMenu();
delay(200);
return;
}
// Start the run sequence if not already running
if (!running) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Run Mode ");
lcd.print(runNumer);
lcd.setCursor(0, 1);
lcd.print("Starting...");
delay(1000);
startTime = millis();
caseStartTime = millis();
running = true;
return;
}
// Main run mode state machine
if (running) {
unsigned long currentTime = millis();
unsigned long elapsed = currentTime - startTime;
unsigned long caseElapsed = currentTime - caseStartTime;
switch (runNumer) {
// Cases 1-8 are basic run modes that set up for specific sequences
case 1:
digitalWrite(powerON, HIGH);
lcd.setCursor(0, 0);
lcd.print("Running Case 1");
RUNpos = 1;
runNumer = 9;
delay(1500);
break;
case 2:
digitalWrite(powerON, HIGH);
lcd.setCursor(0, 0);
lcd.print("Running Case 2");
RUNpos = 2;
runNumer = 9;
delay(1500);
break;
case 3:
digitalWrite(powerON, HIGH);
lcd.setCursor(0, 0);
lcd.print("Running Case 3");
runNumer = 9;
RUNpos = 3;
delay(1500);
break;
case 4:
digitalWrite(powerON, HIGH);
lcd.setCursor(0, 0);
lcd.print("Running Case 4");
runNumer = 9;
RUNpos = 4;
delay(1500);
break;
case 5:
digitalWrite(powerON, HIGH);
lcd.setCursor(0, 0);
lcd.print("Running Case 5");
runNumer = 9;
RUNpos = 5;
delay(1500);
break;
case 6:
digitalWrite(powerON, HIGH);
lcd.setCursor(0, 0);
lcd.print("Running Case 6");
runNumer = 9;
RUNpos = 6;
delay(1500);
break;
case 7:
digitalWrite(powerON, HIGH);
lcd.setCursor(0, 0);
lcd.print("Running Case 7");
runNumer = 9;
RUNpos = 7;
delay(1500);
break;
case 8:
digitalWrite(powerON, HIGH);
lcd.setCursor(0, 0);
lcd.print("Running Case 8");
runNumer = 9;
RUNpos = 8;
delay(1500);
break;
//Cold Wash: 9 → 19
// Case 9: Cold wash cycle- closing Drain
case 9: {
digitalWrite(powerON, HIGH);
lcd.setCursor(0, 0);
lcd.print("cold wash-RUN: ");
// If drain needs closing
if (digitalRead(CLOSEDdrainSwitchPin) == LOW) {
digitalWrite(drain, HIGH);
lcd.setCursor(0, 1);
lcd.print("Closing... ");
drainmove = 2;
runNumer = 9; // Stay in this case
}
// If drain is already closing
if (drainmove == 2 && digitalRead(CLOSEDdrainSwitchPin) == HIGH) {
digitalWrite(drain, LOW);
lcd.setCursor(0, 1);
lcd.print("CLOSED ");
drainmove = 0;
}
// If drain is already closed
if (digitalRead(CLOSEDdrainSwitchPin) == HIGH) {
lcd.setCursor(0, 1);
lcd.print("CLOSED ");
delay(1000);
if (digitalRead(CLOSEDdrainSwitchPin) == HIGH) {
runNumer = 10; // Move to next case
} else {
runNumer = 9;
}
}
break;
}
// Case 10: Cold wash cycle - Tilt lock check
case 10: {
digitalWrite(powerON, HIGH);
if (digitalRead(dosingSwitchPin) == HIGH) {
lcd.setCursor(0, 0);
lcd.print("ERROR: ");
lcd.setCursor(0, 1);
lcd.print(" dosing empty ");
delay(1500);
ErraNO = runNumer;
runNumer = 99;
}
if (digitalRead(tiltLockPin) == HIGH) {
lcd.setCursor(0, 0);
lcd.print("ERROR: ");
lcd.setCursor(0, 1);
lcd.print(" tilt Lock ");
delay(1500);
ErraNO = runNumer;
runNumer = 99;
}
if ((digitalRead(tiltLockPin) == LOW) && (digitalRead(dosingSwitchPin) == LOW)) {
lcd.setCursor(0, 0);
lcd.print("tilt Lock ");
lcd.setCursor(0, 1);
lcd.print("good ");
delay(1500);
runNumer = 11;
}
break;
}
case 11: {
if (digitalRead(fillPin) == LOW) {
runNumer = 12;
} else {
lcd.setCursor(0, 0);
lcd.print("tank level ");
lcd.setCursor(0, 1);
lcd.print("fix ");
runNumer = 11;
}
break;
}
case 12: {
digitalWrite(powerON, HIGH);
static unsigned long lastUpdate = 0;
if (digitalRead(fillPin) == LOW) {
digitalWrite(waterFill, HIGH);
digitalWrite(WaterBY, HIGH);
runNumer = 12;
} else {
digitalWrite(waterFill, LOW);
digitalWrite(WaterBY, LOW);
runNumer = 13;
caseStartTime = millis();
lcd.clear(); // Only clear when done filling
}
// Non-blocking LCD update
if (millis() - lastUpdate > 500) {
lcd.setCursor(0, 0);
lcd.print("Filling Water...");
lcd.setCursor(0, 1);
lcd.print("Water SW=");
lcd.print(digitalRead(fillPin)) ;
lastUpdate = millis();
}
break;
}
case 13: {
digitalWrite(powerON, HIGH);
static bool agitationStarted = false;
static unsigned long agitationStartTime = 0;
if (!agitationStarted) {
// Initialize agitation
lcd.setCursor(0, 0);
lcd.print("Wash Cycle ");
washTime = settings[SETTING_WASH_AG_TIME].value;
agitationStartTime = millis();
delay(50);
digitalWrite(motor, HIGH);
delay(100);
agitationStarted = true;
}
// Calculate remaining time
unsigned long remaining = washTime - (millis() - agitationStartTime);
if (remaining > washTime) remaining = 0; // Prevent overflow
unsigned long minutes = remaining / 60000;
unsigned long seconds = (remaining % 60000) / 1000;
// Update display
lcd.setCursor(0, 1);
lcd.print("Agitating ");
if (minutes < 10) lcd.print(" ");
lcd.print(minutes);
lcd.print("m ");
if (seconds < 10) lcd.print("0");
lcd.print(seconds);
lcd.print("s ");
// Check for completion
if (millis() - agitationStartTime >= washTime) {
digitalWrite(motor, LOW);
delay(50);
lcd.clear();
drainmove = 0;
runNumer = 14; // Move to next case
agitationStarted = false;
break; // Exit this case
}
// Check for cancel button
if (digitalRead(BTN_BACK) == LOW) {
digitalWrite(motor, LOW);
lcd.clear();
lcd.print("Wash Stopped!");
delay(2000);
ErraNO = runNumer;
runNumer = 99;
agitationStarted = false;
break;
}
// NO runNumer = 12 needed here!
// The case structure itself keeps us in case 12
break; // Important: break to prevent fall-through until we're ready
}
case 14: // Wash Cycle Drain Phase - Drain is closed, now opening
{
digitalWrite(powerON, HIGH);
// Check for cancel first
if (digitalRead(BTN_BACK) == LOW) {
lcd.clear();
lcd.print("Drain Stopped!");
delay(1000);
ErraNO = runNumer;
runNumer = 99;
break; // Important: exit the case after setting runNumer
}
// If drain is not yet open and we haven't started opening
if ((digitalRead(OPENdrainSwitchPin) == LOW) && (drainmove != 2)) {
lcd.setCursor(0, 1);
lcd.print("Opening... ");
digitalWrite(drain, HIGH); // Start opening
drainmove = 2; // Mark that we're in opening process
}
// If drain is opening but not yet fully open
else if (drainmove == 2 && digitalRead(OPENdrainSwitchPin) == LOW) {
lcd.setCursor(0, 1);
lcd.print("Opening... ");
// Keep drain HIGH until switch detects it's open
}
// If drain is now fully open
else if (digitalRead(OPENdrainSwitchPin) == HIGH) {
digitalWrite(drain, LOW); // Stop the motor
lcd.setCursor(0, 1);
lcd.print("Open ");
// Add a small delay to ensure stable detection
delay(300);
// Double-check that drain is still open
if (digitalRead(OPENdrainSwitchPin) == HIGH) {
lcd.clear();
drainmove = 0; // Reset for next operation
runNumer = 15; // Move to next phase
}
}
// Safety: if somehow we think drain is open but switch says otherwise
//else {
// lcd.setCursor(0, 1);
// lcd.print("Error: Check Drain");
// You might want to add error handling here
// }
break;
}
case 15: { // Wash drain timer - empty the tank
digitalWrite(powerON, HIGH);
static unsigned long drainStartTime = 0;
static bool drainTimerStarted = false;
// Use wash drain timer setting based on RUNpos
unsigned long DRAIN_TIME = settings[SETTING_WASH_DRAIN_TIME1 + RUNpos - 1].value;
if (!drainTimerStarted) {
drainStartTime = millis();
drainTimerStarted = true;
lcd.clear();
}
unsigned long elapsed = millis() - drainStartTime;
unsigned long remaining = (elapsed < DRAIN_TIME) ? (DRAIN_TIME - elapsed) : 0;
unsigned long minutes = remaining / 60000;
unsigned long seconds = (remaining % 60000) / 1000;
// Display status
lcd.setCursor(0, 0);
lcd.print("Draining Tank... ");
lcd.setCursor(0, 1);
lcd.print("Time: ");
if (minutes < 10) lcd.print("0");
lcd.print(minutes);
lcd.print(":");
if (seconds < 10) lcd.print("0");
lcd.print(seconds);
lcd.print("s ");
// Check for completion
if (elapsed >= DRAIN_TIME) {
drainTimerStarted = false;
lcd.clear();
lcd.print("Drain Complete!");
delay(1000);
currentSetting = 0;
firstrun = 0;
runNumer = 16; // Move to next case
}
// Check for cancel
if (digitalRead(BTN_BACK) == LOW) {
drainTimerStarted = false;
lcd.clear();
lcd.print("Drain Stopped!");
delay(1000);
ErraNO = runNumer;
runNumer = 99;
}
break;
}
case 16: {
digitalWrite(powerON, HIGH);
static bool fillAgitationStarted = false;
static unsigned long fillStartTime = 0;
static bool waterTurnedOff = false;
// Use cold wash timer setting based on RUNpos instead of fixed time
unsigned long FILL_AGITATION_TIME = settings[SETTING_COLD_WASH1_TIME + RUNpos - 1].value;
if (!fillAgitationStarted) {
// Initialize motor and water fill
digitalWrite(motor, HIGH); // Turn on motor
delay(50);
digitalWrite(waterAgitator, HIGH); // Turn on AJ fill
delay(50);
digitalWrite(WaterBY, HIGH); // Turn on bypass
delay(50);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Spray & Agitate");
fillStartTime = millis();
fillAgitationStarted = true;
waterTurnedOff = false;
}
// Calculate elapsed time
unsigned long elapsed = millis() - fillStartTime;
// Turn off water after 60% of time has passed
if (!waterTurnedOff && elapsed >= (FILL_AGITATION_TIME * 0.6)) {
digitalWrite(waterAgitator, LOW); // Turn off AJ fill
delay(300);
digitalWrite(WaterBY, LOW);
delay(200);
digitalWrite(air, HIGH); //Turn on air
if (currentSetting == 0) {
lcd.clear();
currentSetting = 1;
}
lcd.setCursor(0, 0);
lcd.print("Air & Agitate ");
waterTurnedOff = true;
} else {
lcd.setCursor(0, 0);
lcd.print("Spray & Agitate");
}
// Calculate remaining time
unsigned long remaining = (elapsed < FILL_AGITATION_TIME) ? (FILL_AGITATION_TIME - elapsed) : 0;
unsigned long minutes = remaining / 60000;
unsigned long seconds = (remaining % 60000) / 1000;
// Update display
lcd.setCursor(0, 1);
lcd.print("Time: ");
if (minutes < 10) lcd.print("0");
lcd.print(minutes);
lcd.print(":");
if (seconds < 10) lcd.print("0");
lcd.print(seconds);
lcd.print(" ");
if ( firstrun == 0) {
lcd.clear();
firstrun = 1;
}
// Check for completion
if (elapsed >= FILL_AGITATION_TIME) {
digitalWrite(motor, LOW); // Turn off motor
digitalWrite(waterAgitator, LOW); // Ensure AJ fill is off
digitalWrite(WaterBY, LOW); // Turn off bypass
digitalWrite(air, LOW); //Turn off air
delay(50);
lcd.clear();
runNumer = 17; // Move to next case
currentSetting = 0;
firstrun = 0;
fillAgitationStarted = false; // Reset state
break;
}
// Check for cancel button
if (digitalRead(BTN_BACK) == LOW) {
digitalWrite(motor, LOW);
digitalWrite(waterAgitator, LOW);
digitalWrite(WaterBY, LOW);
digitalWrite(air, LOW);
lcd.clear();
lcd.print("Fill Stopped!");
delay(2000);
ErraNO = runNumer;
runNumer = 99;
fillAgitationStarted = false;
break;
}
break;
}
case 17: {
digitalWrite(powerON, HIGH);
static bool waitStarted = false;
static unsigned long waitStartTime = 0;
const unsigned long WAIT_TIME = 120000; // 2 minutes = 120000 ms
lcd.setCursor(0, 0);
lcd.print("Please Wait...");
// if (!waitStarted) {
// waitStartTime = millis();
// waitStarted = true;
// }
// Calculate remaining time
unsigned long elapsed = millis() - waitStartTime;
unsigned long remaining = (elapsed < WAIT_TIME) ? (WAIT_TIME - elapsed) : 0;
unsigned long minutes = remaining / 60000;
unsigned long seconds = (remaining % 60000) / 1000;
// Update countdown display
lcd.setCursor(0, 1);
lcd.print("time: ");
if (minutes < 10) lcd.print("0");
lcd.print(minutes);
lcd.print(":");
if (seconds < 10) lcd.print("0");
lcd.print(seconds);
lcd.print(" ");
// Check for completion
if (elapsed >= WAIT_TIME) {
lcd.clear();
lcd.print("Wait Complete!");
delay(1000);
runNumer = 18;
firstrun = 0;
currentSetting = 0;
waitStarted = false;
break;
}
// Check for cancel button
if (digitalRead(BTN_BACK) == LOW) {
lcd.clear();
lcd.print("Process Canceled!");
delay(2000);
ErraNO = runNumer;
runNumer = 99;
waitStarted = false;
break;
}
if (firstrun == 0) {
delay(100);
lcd.clear();
firstrun = 1;
}
break;
}
case 18: // Wash Cycle - wait to push enter
{
static unsigned long lastActivityTime = 0;
static bool elightOffByButton = false;
static bool firstRun = true;
// Initialize on first run
if (firstRun) {
digitalWrite(powerON, HIGH);
digitalWrite(Elight, HIGH);
lastActivityTime = millis();
firstRun = false;
}
lcd.setCursor(0, 0);
lcd.print("Wash Cycle-wait");
lcd.setCursor(0, 1);
lcd.print("HOLD enter");
// Check if 10 minutes have passed since E-light was turned off
if ( millis() - lastActivityTime > 600000) { // 10 minutes = 600000 ms
digitalWrite(Elight, HIGH); // Turn E-light back on
elightOffByButton = false;
lastActivityTime = millis(); // Reset timer
}
// Check for ENTER button press
if (digitalRead(BTN_SELECT) == LOW) { // Button pressed
digitalWrite(Elight, LOW); // Immediately turn off E-light
elightOffByButton = true;
lastActivityTime = millis(); // Update last activity time
unsigned long buttonPressTime = millis();
while (digitalRead(BTN_SELECT) == LOW) { // Wait while button is held
if (millis() - buttonPressTime > 1000) { // Held for 1 second
lcd.clear();
lcd.print("Starting wash...");
delay(1000);
runNumer = 20; // Proceed to next case
firstRun = true; // Reset for next time
break;
}
}
delay(50); // Simple debounce
}
break;
}
case 19: // Wait for user
{ // Your user wait implementation here
runNumer = 20; // Move to next case
break;
}
//24H Soak: 20 → 29
// Case 20: 24H soak preparation-Closing Drain
case 20: {
digitalWrite(powerON, HIGH);
lcd.setCursor(0, 0);
lcd.print("cold wash-RUN: ");
// If drain needs closing
if (digitalRead(CLOSEDdrainSwitchPin) == LOW) {
digitalWrite(drain, HIGH);
lcd.setCursor(0, 1);
lcd.print("Closing... ");
drainmove = 2;
runNumer = 20; // Stay in this case
}
// If drain is already closing
if (drainmove == 2 && digitalRead(CLOSEDdrainSwitchPin) == HIGH) {
digitalWrite(drain, LOW);
lcd.setCursor(0, 1);
lcd.print("CLOSED ");
drainmove = 0;
}
// If drain is already closed
if (digitalRead(CLOSEDdrainSwitchPin) == HIGH) {
lcd.setCursor(0, 1);
lcd.print("CLOSED ");
delay(1000);
if (digitalRead(CLOSEDdrainSwitchPin) == HIGH) {
runNumer = 21; // Move to next case
} else {
runNumer = 20;
}
}
break;
}
case 21: {
if (digitalRead(fillPin) == LOW) {
runNumer = 22;
currentSetting = 0;
} else {
lcd.setCursor(0, 0);
lcd.print("tank level ");
lcd.setCursor(0, 1);
lcd.print("fix ");
runNumer = 21;
}
break;
}
/////////////////////////////////////////hot water=WATER_LO_PIN =20c
case 22: {
digitalWrite(powerON, HIGH);
// Clock monitoring variables for this case
static unsigned long lastClockCheck = 0;
static unsigned long clockErrorStartTime = 0;
static bool clockErrorActive = false;
static bool gasWasOnBeforeClockError = false;
const unsigned long MAX_CLOCK_ERROR_TIME = 300000; // 5 minutes max clock error
// Get target temperature from settings
// Constants
const unsigned long sparkDuration = 2500;
const unsigned long sparkOFFtime = 1500;
const byte maxIgnitionAttempts = 4;
const unsigned long maintainTime = 30000; // 1 minute maintain time
const unsigned long gasCheckTimeout = 10000; // 10 seconds to detect gas issues
// State variables
static byte ignitionAttempts = 0;
static unsigned long lastSparkTime = 0;
static bool sparkActive = false;
static unsigned long timerStartTime = 0;
static bool timerRunning = false;
static bool waterFillingStarted = false;
static unsigned long lastFlameDetectedTime = 0;
static bool gasIssueDetected = false;
static bool initialIgnitionStarted = false;
static unsigned long ignitionStartTime = 0;
// CLOCK MONITORING - Check clock status periodically
if (millis() - lastClockCheck >= CLOCK_CHECK_INTERVAL) {
lastClockCheck = millis();
bool currentClockState = digitalRead(CLOCK_PIN);
// Detect clock state change
if (currentClockState != lastClockState) {
lastClockChange = millis();
lastClockState = currentClockState;
}
// Check if clock is running (should change state periodically)
clockRunning = (millis() - lastClockChange < 5000); // Consider clock running if changed in last 5 seconds
if (!clockRunning && !clockErrorActive) {
// Clock just stopped - store gas state and start error timer
clockErrorActive = true;
clockErrorStartTime = millis();
gasWasOnBeforeClockError = digitalRead(gasWaterHeat);
// Turn off gas immediately for safety
digitalWrite(gasWaterHeat, LOW);
digitalWrite(sparkPin, LOW);
sparkActive = false;
lcd.clear();
lcd.print("CLOCK ERROR!");
lcd.setCursor(0, 1);
lcd.print("Attempting reset...");
// Attempt clock reset
digitalWrite(POWER_PIN, LOW);
delay(WAIT_RESET_TIME);
digitalWrite(POWER_PIN, HIGH);
delay(WAIT_AFTER_RESET_TIME);
}
// Check if we've been in clock error too long
if (clockErrorActive && (millis() - clockErrorStartTime > MAX_CLOCK_ERROR_TIME)) {
lcd.clear();
lcd.print("CLOCK FAILURE!");
lcd.setCursor(0, 1);
lcd.print("Gas OFF - Safe mode");
ErraNO = runNumer;
runNumer = 99;
return;
}
// Check if clock recovered
if (clockErrorActive && clockRunning) {
clockErrorActive = false;
lcd.clear();
lcd.print("Clock recovered!");
delay(1000);
// Restore gas state if it was on before error
if (gasWasOnBeforeClockError && !waterFillingStarted) {
// Will be handled in normal ignition logic below
}
}
}
// If clock error is active, display status and wait
if (clockErrorActive) {
unsigned long errorTime = millis() - clockErrorStartTime;
unsigned long remainingErrorTime = MAX_CLOCK_ERROR_TIME - errorTime;
unsigned long errorMinutes = remainingErrorTime / 60000;
unsigned long errorSeconds = (remainingErrorTime % 60000) / 1000;
lcd.setCursor(0, 0);
lcd.print("Clock Error! ");
lcd.setCursor(0, 1);
lcd.print("Time left: ");
if (errorMinutes < 10) lcd.print("0");
lcd.print(errorMinutes);
lcd.print(":");
if (errorSeconds < 10) lcd.print("0");
lcd.print(errorSeconds);
// Keep gas off during clock error
digitalWrite(gasWaterHeat, LOW);
digitalWrite(sparkPin, LOW);
sparkActive = false;
return; // Don't proceed with normal operation during clock error
}
// NORMAL OPERATION (only if clock is good)
if ((digitalRead(irSen1Pin) == HIGH) && (currentSetting == 1)) {
lcd.clear();
currentSetting = 0;
}
// Check water level sensor
if (digitalRead(fillPin) == HIGH) { // Water level high is full= high
digitalWrite(waterFill, LOW);
digitalWrite(WaterBY, LOW);
digitalWrite(gasWaterHeat, LOW);
digitalWrite(sparkPin, LOW);
waterFillingStarted = false;
lcd.clear();
lcd.print("Fill Complete");
delay(1000);
lcd.clear();
runNumer = 23; // Proceed to next phase
return;
}
// Update flame detection time - low = flame
if (digitalRead(irSen1Pin) == LOW) {
lastFlameDetectedTime = millis();
gasIssueDetected = false; // Reset gas issue if flame is detected
}
// Start ignition immediately when entering case 23 if not already started
if (!timerRunning && !waterFillingStarted && !initialIgnitionStarted) {
initialIgnitionStarted = true;
ignitionAttempts = 0;
ignitionStartTime = millis();
}
// Initial ignition phase (before timer starts)
if (initialIgnitionStarted && !timerRunning && !waterFillingStarted) {
lcd.setCursor(0, 0);
lcd.print("Starting Heater");
// Check if we have a flame (successful ignition) low = flame
if (digitalRead(irSen1Pin) == LOW) {
// Flame detected - start timer
timerStartTime = millis();
timerRunning = true;
initialIgnitionStarted = false;
digitalWrite(sparkPin, LOW);
sparkActive = false;
lcd.clear();
}
else if (ignitionAttempts < maxIgnitionAttempts) {
// Attempt ignition
if (currentSetting == 0) {
lcd.clear();
}
lcd.setCursor(0, 1);
lcd.print("Igniting ");
lcd.print(ignitionAttempts + 1);
lcd.print("/4 ");
currentSetting = 1;
// Start new ignition cycle
if (!sparkActive && (millis() - lastSparkTime > sparkOFFtime)) {
digitalWrite(gasWaterHeat, HIGH); // Open gas valve
digitalWrite(sparkPin, HIGH); // Start spark
sparkActive = true;
lastSparkTime = millis();
ignitionAttempts++;
}
// Turn off spark after duration
if (sparkActive && (millis() - lastSparkTime >= sparkDuration)) {
digitalWrite(sparkPin, LOW);
sparkActive = false;
// Wait a moment to see if ignition was successful-high = on flame
if (digitalRead(irSen1Pin) == HIGH) {
// Ignition failed, wait before next attempt
delay(1000);
}
}
}
else {
// Failed all 4 attempts
digitalWrite(gasWaterHeat, LOW);
digitalWrite(sparkPin, LOW);
lcd.setCursor(0, 1);
lcd.print("Ignition Failed!");
ErraNO = runNumer;
runNumer = 99; // Go to error state
delay(3000);
initialIgnitionStarted = false;
ignitionAttempts = 0;
break;
}
}
// Check if we should start the timer (temp high OR flame detected)
if (!timerRunning && !waterFillingStarted && digitalRead(irSen1Pin) == HIGH) {
timerStartTime = millis();
timerRunning = true;
ignitionAttempts = 0; // Reset attempts when timer starts
initialIgnitionStarted = false;
}
if (timerRunning) {
// Calculate remaining time
unsigned long elapsed = millis() - timerStartTime;
unsigned long remaining = (elapsed < maintainTime) ? (maintainTime - elapsed) : 0;
unsigned long seconds = remaining / 1000;
// Display timer status
if ((digitalRead(irSen1Pin) == HIGH) && (currentSetting == 1)) {
lcd.clear();
currentSetting = 0;
}
lcd.setCursor(0, 0);
lcd.print("Heating ");
lcd.print(digitalRead(WATER_LO_PIN));
lcd.setCursor(0, 1);
if (seconds < 10) lcd.print(" ");
lcd.print(seconds);
lcd.print("s ");
// Maintain temperature during timer
if (digitalRead(WATER_LO_PIN) == 1) {
// Need to heat - check if flame is present-low = flame
if (digitalRead(irSen1Pin) == LOW) {
// Flame is present, just open gas valve
digitalWrite(gasWaterHeat, HIGH);
digitalWrite(sparkPin, LOW);
sparkActive = false;
ignitionAttempts = 0; // Reset attempts when flame is detected
} else {
// No flame - attempt ignition
if (ignitionAttempts < maxIgnitionAttempts) {
// Start new ignition cycle
if (!sparkActive && (millis() - lastSparkTime > sparkOFFtime)) {
digitalWrite(gasWaterHeat, HIGH); // Open gas valve
digitalWrite(sparkPin, HIGH); // Start spark
sparkActive = true;
lastSparkTime = millis();
ignitionAttempts++;
if (currentSetting == 0) {
lcd.clear();
currentSetting = 1;
}
lcd.setCursor(0, 1);
lcd.print("Re-igniting ");
lcd.print(ignitionAttempts);
lcd.print("/4 ");
delay(300);
}
// Turn off spark after duration
if (sparkActive && (millis() - lastSparkTime >= sparkDuration)) {
digitalWrite(sparkPin, LOW);
sparkActive = false;
}
} else {
// Failed after max attempts
digitalWrite(gasWaterHeat, LOW);
digitalWrite(sparkPin, LOW);
lcd.setCursor(0, 1);
lcd.print("Ignition Failed!");
ErraNO = runNumer;
runNumer = 99; // Go to error state
delay(3000);
timerRunning = false;
ignitionAttempts = 0;
break;
}
}
} else {
// Temperature is at or above target, turn off heating
digitalWrite(gasWaterHeat, LOW);
digitalWrite(sparkPin, LOW);
sparkActive = false;
}
// Check if timer is complete
if (elapsed >= maintainTime) {
timerRunning = false;
waterFillingStarted = true;
digitalWrite(waterFill, HIGH); // Start filling with water
digitalWrite(WaterBY, LOW);//no by pass
ignitionAttempts = 0; // Reset attempts for filling phase
lcd.clear();
lcd.print("Starting Fill");
delay(1000);
lcd.clear();
}
}
else if (waterFillingStarted) {
// Water filling phase after timer completes
lcd.setCursor(0, 0);
lcd.print("Heating ");
lcd.print(digitalRead(WATER_LO_PIN));
// Display current temperature with "fill" indicator
// Maintain temperature while filling
if ( digitalRead(WATER_LO_PIN) == 1) {
// Need to heat - check if flame is present-low = flame
if (digitalRead(irSen1Pin) == LOW) {
// Flame is present, just open gas valve
digitalWrite(gasWaterHeat, HIGH);
digitalWrite(sparkPin, LOW);
sparkActive = false;
ignitionAttempts = 0; // Reset attempts when flame is detected
} else {
// No flame - attempt ignition
if (ignitionAttempts < maxIgnitionAttempts) {
// Start new ignition cycle
if (!sparkActive && (millis() - lastSparkTime > sparkOFFtime)) {
digitalWrite(gasWaterHeat, HIGH); // Open gas valve
digitalWrite(sparkPin, HIGH); // Start spark
sparkActive = true;
lastSparkTime = millis();
ignitionAttempts++;
lcd.setCursor(0, 1);
lcd.print("Igniting ");
lcd.print(ignitionAttempts);
lcd.print("/4 ");
delay(200);
}
// Turn off spark after duration
if (sparkActive && (millis() - lastSparkTime >= sparkDuration)) {
digitalWrite(sparkPin, LOW);
sparkActive = false;
}
} else {
// Failed after max attempts
digitalWrite(gasWaterHeat, LOW);
digitalWrite(sparkPin, LOW);
lcd.setCursor(0, 1);
lcd.print("Ignition Failed!");
ErraNO = runNumer;
runNumer = 99; // Go to error state
delay(3000);
waterFillingStarted = false;
ignitionAttempts = 0;
break;
}
}
} else {
// Temperature is at or above target, turn off heating
digitalWrite(gasWaterHeat, LOW);
digitalWrite(sparkPin, LOW);
sparkActive = false;
}
}
// Emergency stop check
if (digitalRead(BTN_BACK) == LOW) {
digitalWrite(gasWaterHeat, LOW);
digitalWrite(sparkPin, LOW);
digitalWrite(waterFill, LOW);
digitalWrite(WaterBY, LOW);
ErraNO = runNumer;
runNumer = 99;
timerRunning = false;
waterFillingStarted = false;
initialIgnitionStarted = false;
ignitionAttempts = 0;
}
break;
}
// 24H
// Case 23: ` for 24H soak- dosing
case 23: {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("24H Soak - Dose ");
digitalWrite(powerON, HIGH);
digitalWrite(gasWaterHeat, LOW);
digitalWrite(sparkPin, LOW);
digitalWrite(waterFill, LOW);
digitalWrite(WaterBY, LOW);
delay(100);
digitalWrite(dosing, HIGH);
digitalWrite(sparkPin, LOW);
delay(1500);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("24H Soak - Dose ");
lcd.setCursor(0, 1);
lcd.print("DOSE done ");
digitalWrite(dosing, LOW);
digitalWrite(sparkPin, LOW);
firstrun = 0;
currentSetting = 0;
delay(150);
delay(1500);
lcd.clear();
runNumer = 24;
break;
}
// Case 24: Agitation during 24H soak -pre soak
case 24: {
digitalWrite(powerON, HIGH);// digitalWrite(digitalWrite(powerON, HIGH);, HIGH);
lcd.setCursor(0, 0);
lcd.print("24H Ag-motor ");
washTime = settings[SETTING_24H_AG_PREE_SOAK_TIME].value;
agitationStartTime = millis();
digitalWrite(motor, HIGH);
delay(200);
if ( firstrun == 0) {
lcd.clear();
firstrun = 1;
}
lcd.setCursor(0, 0);
lcd.print("24H Ag-motor ");
lcd.setCursor(0, 1);
lcd.print("Time:");
remaining = washTime;
minutes = remaining / 60000;
seconds = (remaining % 60000) / 1000;
lcd.print(minutes);
lcd.print("m ");
lcd.print(seconds);
lcd.print("s ");
while (millis() - agitationStartTime < washTime) {
remaining = washTime - (millis() - agitationStartTime);
minutes = remaining / 60000;
seconds = (remaining % 60000) / 1000;
lcd.setCursor(0, 1);
lcd.print("Time:");
if (minutes < 10) lcd.print(" ");
lcd.print(minutes);
lcd.print("m ");
if (seconds < 10) lcd.print("0");
lcd.print(seconds);
lcd.print("s ");
if (digitalRead(BTN_BACK) == LOW) {
digitalWrite(motor, LOW);
lcd.clear();
lcd.print("Wash Stopped!");
delay(2000);
ErraNO = runNumer;
runNumer = 99;
return;
}
delay(200);
}
delay(1000);
lcd.clear();
// delay(1000);
runNumer = 25;
currentSetting = 0;
firstrun = 0;
break;
}
//===case 25===
case 25: {
digitalWrite(powerON, HIGH);
// Clock monitoring variables for this case
static unsigned long lastClockCheck = 0;
static unsigned long clockErrorStartTime = 0;
static bool clockErrorActive = false;
static bool gasWasOnBeforeClockError = false;
const unsigned long MAX_CLOCK_ERROR_TIME = 300000; // 5 minutes max clock error
static unsigned long soakStartTime = 0;
const unsigned long SOAK_DURATION = 180000; //86400000; // 24 hours
// Flame control variables
static byte ignitionAttempts = 0;
static unsigned long lastSparkTime = 0;
static bool sparkActive = false;
static unsigned long lastFlameDetectedTime = 0;
static bool initialIgnitionStarted = false;
static unsigned long ignitionStartTime = 0;
// Constants
const unsigned long sparkDuration = 2500;
const unsigned long sparkOFFtime = 1500;
const byte maxIgnitionAttempts = 4;
// CLOCK MONITORING - Check clock status periodically
if (millis() - lastClockCheck >= CLOCK_CHECK_INTERVAL) {
lastClockCheck = millis();
bool currentClockState = digitalRead(CLOCK_PIN);
// Detect clock state change
if (currentClockState != lastClockState) {
lastClockChange = millis();
lastClockState = currentClockState;
}
// Check if clock is running (should change state periodically)
clockRunning = (millis() - lastClockChange < 5000); // Consider clock running if changed in last 5 seconds
if (!clockRunning && !clockErrorActive) {
// Clock just stopped - store gas state and start error timer
clockErrorActive = true;
clockErrorStartTime = millis();
gasWasOnBeforeClockError = digitalRead(gasMainTank);
// Turn off gas immediately for safety
digitalWrite(gasMainTank, LOW);
digitalWrite(sparkPin, LOW);
sparkActive = false;
digitalWrite(motor, LOW);
lcd.clear();
lcd.print("CLOCK ERROR!");
lcd.setCursor(0, 1);
lcd.print("Attempting reset...");
// Attempt clock reset
digitalWrite(POWER_PIN, LOW);
delay(WAIT_RESET_TIME);
digitalWrite(POWER_PIN, HIGH);
delay(WAIT_AFTER_RESET_TIME);
}
// Check if we've been in clock error too long
if (clockErrorActive && (millis() - clockErrorStartTime > MAX_CLOCK_ERROR_TIME)) {
lcd.clear();
lcd.print("CLOCK FAILURE!");
lcd.setCursor(0, 1);
lcd.print("Gas OFF - Safe mode");
ErraNO = runNumer;
runNumer = 99;
return;
}
// Check if clock recovered
if (clockErrorActive && clockRunning) {
clockErrorActive = false;
lcd.clear();
lcd.print("Clock recovered!");
delay(1000);
// Restore gas state if it was on before error
if (gasWasOnBeforeClockError) {
// Will be handled in normal ignition logic below
initialIgnitionStarted = true; // Restart ignition process
}
}
}
// If clock error is active, display status and wait
if (clockErrorActive) {
unsigned long errorTime = millis() - clockErrorStartTime;
unsigned long remainingErrorTime = MAX_CLOCK_ERROR_TIME - errorTime;
unsigned long errorMinutes = remainingErrorTime / 60000;
unsigned long errorSeconds = (remainingErrorTime % 60000) / 1000;
lcd.setCursor(0, 0);
lcd.print("Clock Error! ");
lcd.setCursor(0, 1);
lcd.print("Time left: ");
if (errorMinutes < 10) lcd.print("0");
lcd.print(errorMinutes);
lcd.print(":");
if (errorSeconds < 10) lcd.print("0");
lcd.print(errorSeconds);
// Keep gas off during clock error
digitalWrite(gasMainTank, LOW);
digitalWrite(sparkPin, LOW);
digitalWrite(motor, LOW);
sparkActive = false;
return; // Don't proceed with normal operation during clock error
}
// NORMAL OPERATION (only if clock is good)
// Calculate remaining time
unsigned long elapsed = (soakStartTime > 0) ? (millis() - soakStartTime) : 0;
unsigned long remaining = (elapsed < SOAK_DURATION) ? (SOAK_DURATION - elapsed) : 0;
unsigned long hours = remaining / 3600000;
unsigned long minutes = (remaining % 3600000) / 60000;
unsigned long seconds = (remaining % 60000) / 1000;
// Initialize soak timer on first entry
if (soakStartTime == 0) {
soakStartTime = millis();
initialIgnitionStarted = true;
ignitionAttempts = 0;
ignitionStartTime = millis();
lcd.clear();
}
// CHANGED: irSen1Pin → irSen2Pin
if (digitalRead(irSen2Pin) == HIGH) { // high no flame
lcd.setCursor(15, 0);
lcd.print("n"); // no flame
} else {
lcd.setCursor(15, 0);
lcd.print("F"); // flame
}
// Motor control based on temperature (only when flame is present)
// CHANGED: TANK_LO_PIN used for temperature control
if (digitalRead(TANK_LO_PIN) == 1) {
// CHANGED: irSen1Pin → irSen2Pin
if (digitalRead(irSen2Pin) == LOW) { // tank flame low = flame on
digitalWrite(motor, HIGH);
delay(50);
} else {
// LCD clear condition (preserved as requested)
digitalWrite(motor, LOW);
delay(50);
lcd.setCursor(0, 1);
lcd.print("TANK Temp lo: ");
lcd.print(digitalRead(TANK_LO_PIN)); // CHANGED: TANK_LO_PIN
}
} else {
digitalWrite(motor, LOW);
delay(200);
if (currentSetting == 1) {
lcd.clear();
currentSetting = 0;
}
}
// Initial ignition phase
if (initialIgnitionStarted) {
lcd.setCursor(0, 0);
lcd.print("Starting Heater");
// Check if we have a flame (successful ignition)
// CHANGED: irSen1Pin → irSen2Pin
if (digitalRead(irSen2Pin) == LOW) {
// Flame detected - successful ignition
digitalWrite(sparkPin, LOW);
sparkActive = false;
initialIgnitionStarted = false;
ignitionAttempts = 0;
lcd.clear();
}
else if (ignitionAttempts < maxIgnitionAttempts) {
// Attempt ignition
if (currentSetting == 0) {
lcd.clear();
currentSetting = 1;
}
lcd.setCursor(0, 1);
lcd.print("Igniting ");
lcd.print(ignitionAttempts + 1);
lcd.print("/4 ");
delay(300);
// Start new ignition cycle
if (!sparkActive && (millis() - lastSparkTime > sparkOFFtime)) {
digitalWrite(gasMainTank, HIGH); // Open gas valve
delay(200);
digitalWrite(sparkPin, HIGH); // Start spark
delay(200);
sparkActive = true;
lastSparkTime = millis();
ignitionAttempts++;
}
// Turn off spark after duration
if (sparkActive && (millis() - lastSparkTime >= sparkDuration)) {
digitalWrite(sparkPin, LOW);
delay(200);
sparkActive = false;
// Wait a moment to see if ignition was successful
// CHANGED: irSen1Pin → irSen2Pin
if (digitalRead(irSen2Pin) == HIGH) { // tank high = no flame
// Ignition failed, wait before next attempt
delay(1000);
}
}
}
else {
// Failed all 4 attempts
digitalWrite(gasMainTank, LOW);
digitalWrite(sparkPin, LOW);
digitalWrite(motor, LOW);
delay(100);
lcd.clear();
lcd.setCursor(0, 1);
lcd.print("Ignition Failed!");
ErraNO = runNumer;
runNumer = 99; // Go to error state
delay(3000);
initialIgnitionStarted = false;
ignitionAttempts = 0;
break;
}
}
else {
// Normal operation after successful ignition
// CHANGED: TANK_LO_PIN used for temperature control
if (digitalRead(TANK_LO_PIN) == 1) {
// Need to heat - check if flame is present
// CHANGED: irSen1Pin → irSen2Pin
if (digitalRead(irSen2Pin) == LOW) {
// Flame is present, just open gas valve
digitalWrite(gasMainTank, HIGH);
delay(200);
digitalWrite(sparkPin, LOW);
// Show temperature status
lcd.setCursor(0, 1);
lcd.print("TANK Temp lo: ");
lcd.print(digitalRead(TANK_LO_PIN));
lcd.print(" ");
delay(200);
sparkActive = false;
ignitionAttempts = 0; // Reset attempts when flame is detected
} else {
// No flame - attempt re-ignition
if (ignitionAttempts < maxIgnitionAttempts) {
// Start new ignition cycle
if (!sparkActive && (millis() - lastSparkTime > sparkOFFtime)) {
digitalWrite(gasMainTank, HIGH); // Open gas valve
delay(200);
digitalWrite(sparkPin, HIGH); // Start spark
delay(200);
sparkActive = true;
lastSparkTime = millis();
ignitionAttempts++;
if (currentSetting == 0) {
currentSetting = 1;
}
lcd.setCursor(0, 1);
lcd.print("Re-igniting ");
lcd.print(ignitionAttempts);
lcd.print("/4 ");
delay(400);
}
// Turn off spark after duration
if (sparkActive && (millis() - lastSparkTime >= sparkDuration)) {
digitalWrite(sparkPin, LOW);
delay(200);
sparkActive = false;
}
} else {
// Failed after max attempts
digitalWrite(gasMainTank, LOW);
delay(200);
digitalWrite(sparkPin, LOW);
delay(200);
lcd.setCursor(0, 1);
lcd.print("Ignition Failed!");
ErraNO = runNumer;
runNumer = 99; // Go to error state
delay(3000);
ignitionAttempts = 0;
break;
}
}
} else {
// Temperature is at or above target, turn off heating
digitalWrite(gasMainTank, LOW);
delay(200);
digitalWrite(sparkPin, LOW);
delay(200);
sparkActive = false;
}
}
// Check if soak duration is complete
if (elapsed >= SOAK_DURATION) {
digitalWrite(gasMainTank, LOW);
delay(200);
digitalWrite(sparkPin, LOW);
delay(200);
sparkActive = false;
soakStartTime = 0;
initialIgnitionStarted = false;
runNumer = 26;
firstrun = 0;
currentSetting = 0;
lcd.setCursor(0, 1);
lcd.print("Soak Complete! ");
currentSetting = 0;
delay(2000);
}
// Emergency stop check
if (digitalRead(BTN_BACK) == LOW) {
digitalWrite(gasMainTank, LOW);
digitalWrite(sparkPin, LOW);
sparkActive = false;
ErraNO = runNumer;
runNumer = 99;
soakStartTime = 0;
initialIgnitionStarted = false;
currentSetting = 0;
}
// Update display every second
lcd.setCursor(0, 0);
lcd.print("24H: ");
if (hours < 10) lcd.print("0");
lcd.print(hours);
lcd.print("h");
if (minutes < 10) lcd.print("0");
lcd.print(minutes);
lcd.print("m");
if (seconds < 10) lcd.print("0");
lcd.print(seconds);
lcd.print("s ");
// CHANGED: irSen1Pin → irSen2Pin
if (digitalRead(irSen2Pin) == LOW) {
// Show temperature status
lcd.setCursor(0, 1);
lcd.print("TANK Temp lo: ");
lcd.print(digitalRead(TANK_LO_PIN)); // CHANGED: TANK_LO_PIN
}
if (firstrun == 0) {
firstrun = 1;
lcd.clear();
}
delay(20);
break;
}
case 26:
{
digitalWrite(powerON, HIGH);
// Check for cancel first
if (digitalRead(BTN_BACK) == LOW) {
lcd.clear();
lcd.print("Drain Stopped!");
delay(1000);
ErraNO = runNumer;
runNumer = 99;
break; // Important: exit the case after setting runNumer
}
// If drain is not yet open and we haven't started opening
if ((digitalRead(OPENdrainSwitchPin) == LOW) && (drainmove != 2)) {
lcd.setCursor(0, 1);
lcd.print("Opening... ");
digitalWrite(drain, HIGH); // Start opening
drainmove = 2; // Mark that we're in opening process
}
// If drain is opening but not yet fully open
else if (drainmove == 2 && digitalRead(OPENdrainSwitchPin) == LOW) {
lcd.setCursor(0, 1);
lcd.print("Opening... ");
// Keep drain HIGH until switch detects it's open
}
// If drain is now fully open
else if (digitalRead(OPENdrainSwitchPin) == HIGH) {
digitalWrite(drain, LOW); // Stop the motor
lcd.setCursor(0, 1);
lcd.print("Open ");
// Add a small delay to ensure stable detection
delay(300);
// Double-check that drain is still open
if (digitalRead(OPENdrainSwitchPin) == HIGH) {
lcd.clear();
drainmove = 0; // Reset for next operation
firstrun = 0;
currentSetting = 0;
runNumer = 27; // Move to next phase
delay(200);
}
}
break;
}
case 27:// drain wait
{ // Wash drain timer - empty the tank
digitalWrite(powerON, HIGH);
static unsigned long drainStartTime = 0;
static bool drainTimerStarted = false;
// Use wash drain timer setting based on RUNpos
unsigned long DRAIN_TIME = settings[SETTING_WASH_DRAIN_TIME1 + RUNpos - 1].value;
if (!drainTimerStarted) {
drainStartTime = millis();
drainTimerStarted = true;
lcd.clear();
}
unsigned long elapsed = millis() - drainStartTime;
unsigned long remaining = (elapsed < DRAIN_TIME) ? (DRAIN_TIME - elapsed) : 0;
unsigned long minutes = remaining / 60000;
unsigned long seconds = (remaining % 60000) / 1000;
// Display status
lcd.setCursor(0, 0);
lcd.print("Draining Tank... ");
lcd.setCursor(0, 1);
lcd.print("Time: ");
if (minutes < 10) lcd.print("0");
lcd.print(minutes);
lcd.print(":");
if (seconds < 10) lcd.print("0");
lcd.print(seconds);
lcd.print("s ");
// Check for completion
if (elapsed >= DRAIN_TIME) {
drainTimerStarted = false;
lcd.clear();
lcd.print("Drain Complete!");
delay(1000);
firstrun = 0;
currentSetting = 0;
runNumer = 28; // Move to next case
}
// Check for cancel
if (digitalRead(BTN_BACK) == LOW) {
drainTimerStarted = false;
lcd.clear();
lcd.print("Drain Stopped!");
delay(1000);
ErraNO = runNumer;
runNumer = 99;
}
break;
}
//24H soak
// Case 28: Bypass check only
case 28: {
digitalWrite(powerON, HIGH);
// Check if bypass is enabled (value 0)
if (settings[SETTING_BYPASS].value == 0) {
lcd.setCursor(0, 0);
lcd.print("Bypass Enabled");
lcd.setCursor(0, 1);
lcd.print("Skipping ");
delay(2000);
firstrun = 0;
currentSetting = 0;
runNumer = 29;
} else {
lcd.setCursor(0, 0);
lcd.print("24H Soak Drain");
lcd.setCursor(0, 1);
lcd.print("Hold ENTER for 2s");
digitalWrite(Elight, HIGH);
// Check for ENTER button hold
if (digitalRead(BTN_SELECT) == LOW) {
unsigned long buttonPressTime = millis();
while (digitalRead(BTN_SELECT) == LOW) {
if (millis() - buttonPressTime > 2000) {
lcd.clear();
lcd.print("Manual Skip");
lcd.setCursor(0, 1);
lcd.print("Skipping ");
digitalWrite(Elight, LOW);
delay(1000);
runNumer = 29;
break;
}
}
}
}
break;
}
// Case 29: Drain completion and user wait
case 29: {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Drain Complete");
digitalWrite(Elight, LOW);
firstrun = 0;
currentSetting = 0;
runNumer = 30; // Move to next case
break;
}
//Cold Rinse: 30 → 39
// Case 30: Cold rinse - Drain shold be open
case 30: {
digitalWrite(powerON, HIGH);
digitalWrite(gasMainTank, LOW);
digitalWrite(gasWaterHeat, LOW);
digitalWrite(sparkPin, LOW);
currentSetting = 0;
lcd.clear();
if (digitalRead(fillPin) == HIGH) {
runNumer = 31;
} else {
lcd.setCursor(0, 0);
lcd.print("tank level ");
lcd.setCursor(0, 1);
lcd.print("fix ");
runNumer = 30;
firstrun = 0;
currentSetting = 0;
}
break;
}
// Case 31: Cold rinse - agitation motor and cold water
case 31: {// tank is oprn
digitalWrite(powerON, HIGH);
washTime = settings[SETTING_RINCE_AG_TIME].value;
agitationStartTime = millis();
digitalWrite(motor, HIGH);
delay(200);
digitalWrite(waterFill, HIGH);
delay(200);
digitalWrite(WaterBY, HIGH);
delay(200);
if (currentSetting == 1) {
lcd.clear();
currentSetting = 0;
}
lcd.setCursor(0, 0);
lcd.print("cold rinse");
lcd.setCursor(0, 1);
lcd.print("Agitating ");
remaining = washTime;
minutes = remaining / 60000;
seconds = (remaining % 60000) / 1000;
lcd.print(minutes);
lcd.print("m ");
lcd.print(seconds);
lcd.print("s");
while (millis() - agitationStartTime < washTime) {
remaining = washTime - (millis() - agitationStartTime);
minutes = remaining / 60000;
seconds = (remaining % 60000) / 1000;
lcd.setCursor(10, 1);
if (minutes < 10) lcd.print(" ");
lcd.print(minutes);
lcd.print("m ");
if (seconds < 10) lcd.print("0");
lcd.print(seconds);
lcd.print("s ");
if (digitalRead(BTN_BACK) == LOW) {
digitalWrite(motor, LOW);
lcd.clear();
lcd.print("Wash Stopped!");
delay(2000);
ErraNO = runNumer;
runNumer = 99;
return;
}
delay(200);
}
lcd.clear();
runNumer = 32;
firstrun = 0;
currentSetting = 0;
break;
}
// Case 32: Cold rinse - Stop water fill and motor
case 32: {
digitalWrite(powerON, HIGH);
digitalWrite(motor, LOW);
digitalWrite(waterFill, LOW);
digitalWrite(WaterBY, LOW);
lcd.clear();
currentSetting = 0;
runNumer = 33;
firstrun = 0;
currentSetting = 0;
break;
}
// Case 33: Cold rinse - Closing drain
// Closing drain
case 33: {
digitalWrite(powerON, HIGH);
lcd.setCursor(0, 0);
lcd.print("Cold Rinse- drain Closing ");
delay(200);
if (digitalRead(CLOSEDdrainSwitchPin) == LOW) {
digitalWrite(drain, HIGH);
lcd.setCursor(0, 1);
lcd.print("Closing... ");
drainmove = 2;
runNumer = 33;
} else {
lcd.setCursor(0, 1);
lcd.print("CLOSED ");
digitalWrite(drain, LOW);
drainmove == 2;
}
if (drainmove == 2 && digitalRead(CLOSEDdrainSwitchPin) == HIGH) {
digitalWrite(drain, LOW);
lcd.setCursor(0, 1);
lcd.print("CLOSED ");
delay(500);
drainmove = 0;
if ( digitalRead(CLOSEDdrainSwitchPin) == HIGH) {
currentSetting = 0;
lcd.clear();
delay(50);
firstrun = 0;
currentSetting = 0;
runNumer = 40;
}
}
break;
}
case 34: // Wait for user
{ // Your user wait implementation here
runNumer = 35; // Move to next case
break;
}
case 35: // Wait for user
{ // Your user wait implementation here
runNumer = 36; // Move to next case
break;
}
case 37: // Wait for user
{ // Your user wait implementation here
runNumer = 38; // Move to next case
break;
}
case 38: // Wait for user
{ // Your user wait implementation here
runNumer = 39; // Move to next case
break;
}
case 39: // Wait for user
{ // Your user wait implementation here
currentSetting = 0;
runNumer = 40; // Move to next case
break;
}
//Cooking: 40 → 49
// Case 40: Cooking - Water heating and ignition
case 40: {//WATER_HI_PIN hot water
digitalWrite(powerON, HIGH);
// Get target temperature from settings
// const float targetTemp = settings[SETTING_WATER_TEMP].value;
// float currentTemp = readTemperature(temp2SenPin);
// Constants
const unsigned long sparkDuration = 2500;
const unsigned long sparkOFFtime = 1500;
const byte maxIgnitionAttempts = 4;
const unsigned long maintainTime = 60000; // 1 minute maintain time
const unsigned long gasCheckTimeout = 10000; // 10 seconds to detect gas issues
// State variables
static byte ignitionAttempts = 0;
static unsigned long lastSparkTime = 0;
static bool sparkActive = false;
static unsigned long timerStartTime = 0;
static bool timerRunning = false;
static bool waterFillingStarted = false;
static unsigned long lastFlameDetectedTime = 0;
static bool gasIssueDetected = false;
static bool initialIgnitionStarted = false;
static unsigned long ignitionStartTime = 0;
// Update flame detection time-low = flame
if (digitalRead(irSen2Pin) == LOW) {
lastFlameDetectedTime = millis();
gasIssueDetected = false; // Reset gas issue if flame is detected
}
// Start ignition immediately when entering case 40 if not already started
if (!timerRunning && !waterFillingStarted && !initialIgnitionStarted) {
initialIgnitionStarted = true;
ignitionAttempts = 0;
ignitionStartTime = millis();
lcd.clear();
}
// Initial ignition phase (before timer starts)
if (initialIgnitionStarted && !timerRunning && !waterFillingStarted) {
lcd.setCursor(0, 0);
lcd.print("Starting Heater");
// Check if we have a flame (successful ignition)-low = flame
if (digitalRead(irSen2Pin) == LOW) {
// Flame detected - start timer
timerStartTime = millis();
timerRunning = true;
initialIgnitionStarted = false;
digitalWrite(sparkPin, LOW);
sparkActive = false;
lcd.clear();
}
else if (ignitionAttempts < maxIgnitionAttempts) {
// Attempt ignition
if (currentSetting == 0) {
lcd.clear();
currentSetting = 1;
}
lcd.setCursor(0, 1);
lcd.print("Igniting ");
lcd.print(ignitionAttempts + 1);
lcd.print("/4 ");
// Start new ignition cycle
if (!sparkActive && (millis() - lastSparkTime > sparkOFFtime)) {
digitalWrite(gasWaterHeat, HIGH); // Open gas valve
digitalWrite(sparkPin, HIGH); // Start spark
sparkActive = true;
lastSparkTime = millis();
ignitionAttempts++;
}
// Turn off spark after duration
if (sparkActive && (millis() - lastSparkTime >= sparkDuration)) {
digitalWrite(sparkPin, LOW);
sparkActive = false;
// Wait a moment to see if ignition was successful-high = no flame
if (digitalRead(irSen2Pin) == HIGH) {
// Ignition failed, wait before next attempt
delay(1000);
}
}
}
else {
// Failed all 4 attempts
digitalWrite(gasWaterHeat, LOW);
digitalWrite(sparkPin, LOW);
lcd.setCursor(0, 1);
lcd.print("Ignition Failed!");
ErraNO = runNumer;
runNumer = 99; // Go to error state
delay(3000);
initialIgnitionStarted = false;
ignitionAttempts = 0;
break;
}
}
// Check if we should start the timer (temp high OR flame detected)
if (!timerRunning && !waterFillingStarted && digitalRead(irSen2Pin) == HIGH) {
timerStartTime = millis();
timerRunning = true;
ignitionAttempts = 0; // Reset attempts when timer starts
initialIgnitionStarted = false;
}
if (timerRunning) {
// Calculate remaining time
unsigned long elapsed = millis() - timerStartTime;
unsigned long remaining = (elapsed < maintainTime) ? (maintainTime - elapsed) : 0;
unsigned long seconds = remaining / 1000;
if (currentSetting == 1) {
lcd.clear();
currentSetting = 0;
}
// Display timer status
lcd.setCursor(0, 0);
lcd.print("Heating ");
lcd.print(WATER_HI_PIN);
lcd.setCursor(0, 1);
if (seconds < 10) lcd.print(" ");
lcd.print(seconds);
lcd.print("s ");
// Maintain temperature during timer
if (WATER_HI_PIN == 1) {
// Need to heat - check if flame is present-low = flame
if (digitalRead(irSen2Pin) == LOW) {
// Flame is present, just open gas valve
digitalWrite(gasWaterHeat, HIGH);
digitalWrite(sparkPin, LOW);
sparkActive = false;
ignitionAttempts = 0; // Reset attempts when flame is detected
} else {
// No flame - attempt ignition
if (ignitionAttempts < maxIgnitionAttempts) {
// Start new ignition cycle
if (!sparkActive && (millis() - lastSparkTime > sparkOFFtime)) {
digitalWrite(gasWaterHeat, HIGH); // Open gas valve
digitalWrite(sparkPin, HIGH); // Start spark
sparkActive = true;
lastSparkTime = millis();
ignitionAttempts++;
lcd.setCursor(0, 1);
lcd.print("Re-igniting ");
lcd.print(ignitionAttempts);
lcd.print("/4 ");
delay(300);
}
// Turn off spark after duration
if (sparkActive && (millis() - lastSparkTime >= sparkDuration)) {
digitalWrite(sparkPin, LOW);
sparkActive = false;
}
} else {
// Failed after max attempts
digitalWrite(gasWaterHeat, LOW);
digitalWrite(sparkPin, LOW);
lcd.setCursor(0, 1);
lcd.print("Ignition Failed!");
ErraNO = runNumer;
runNumer = 99; // Go to error state
delay(3000);
timerRunning = false;
ignitionAttempts = 0;
break;
}
}
} else {
// Temperature is at or above target, turn off heating
digitalWrite(gasWaterHeat, LOW);
digitalWrite(sparkPin, LOW);
sparkActive = false;
}
// Check if timer is complete
if (elapsed >= maintainTime) {
timerRunning = false;
waterFillingStarted = true;
digitalWrite(waterFill, HIGH); // Start filling with water
digitalWrite(WaterBY, LOW);//no by pass
ignitionAttempts = 0; // Reset attempts for filling phase
lcd.clear();
lcd.print("Starting Fill");
delay(1000);
lcd.clear();
}
}
else if (waterFillingStarted) {
// Water filling phase after timer completes
lcd.setCursor(0, 0);
lcd.print("Heating ");
lcd.print(WATER_HI_PIN);
// Display current temperature with "fill" indicator
// Maintain temperature while filling
if (WATER_HI_PIN == 1) {
// Need to heat - check if flame is present-low = flame
if (digitalRead(irSen2Pin) == LOW) {
// Flame is present, just open gas valve
digitalWrite(gasWaterHeat, HIGH);
digitalWrite(sparkPin, LOW);
sparkActive = false;
ignitionAttempts = 0; // Reset attempts when flame is detected
} else {
// No flame - attempt ignition
if (ignitionAttempts < maxIgnitionAttempts) {
// Start new ignition cycle
if (!sparkActive && (millis() - lastSparkTime > sparkOFFtime)) {
digitalWrite(gasWaterHeat, HIGH); // Open gas valve
digitalWrite(sparkPin, HIGH); // Start spark
sparkActive = true;
lastSparkTime = millis();
ignitionAttempts++;
lcd.setCursor(0, 1);
lcd.print("Igniting ");
lcd.print(ignitionAttempts);
lcd.print("/4 ");
delay(200);
}
// Turn off spark after duration
if (sparkActive && (millis() - lastSparkTime >= sparkDuration)) {
digitalWrite(sparkPin, LOW);
sparkActive = false;
}
} else {
// Failed after max attempts
digitalWrite(gasWaterHeat, LOW);
digitalWrite(sparkPin, LOW);
lcd.setCursor(0, 1);
lcd.print("Ignition Failed!");
ErraNO = runNumer;
runNumer = 99; // Go to error state
delay(3000);
waterFillingStarted = false;
ignitionAttempts = 0;
break;
}
}
} else {
// Temperature is at or above target, turn off heating
digitalWrite(gasWaterHeat, LOW);
digitalWrite(sparkPin, LOW);
sparkActive = false;
}
// Check water level sensor-(fillPin) ==low)= filling
if (digitalRead(fillPin) == LOW) { // Water level high
digitalWrite(waterFill, LOW);
digitalWrite(WaterBY, LOW);
digitalWrite(gasWaterHeat, LOW);
digitalWrite(sparkPin, LOW);
waterFillingStarted = false;
lcd.clear();
lcd.print("Fill Complete");
delay(1000);
lcd.clear();
runNumer = 41; // Proceed to next phase
} else {
runNumer = 40;
}
}
// Emergency stop check
if (digitalRead(BTN_BACK) == LOW) {
digitalWrite(gasWaterHeat, LOW);
digitalWrite(sparkPin, LOW);
digitalWrite(waterFill, LOW);
digitalWrite(WaterBY, LOW);
ErraNO = runNumer;
runNumer = 99;
timerRunning = false;
waterFillingStarted = false;
initialIgnitionStarted = false;
ignitionAttempts = 0;
}
break;
}
// Case 41: Cooking - Water filling and temperature maintenance
case 41: {
digitalWrite(powerON, HIGH);
digitalWrite(waterFill, LOW);
digitalWrite(WaterBY, LOW);
digitalWrite(gasWaterHeat, LOW);
digitalWrite(sparkPin, LOW);
// Display status
lcd.setCursor(0, 0);
lcd.print("COOK ");
lcd.setCursor(0, 1);
lcd.print("Fill Complete ");
runNumer = 42; // Proceed to main cooking
firstrun = 0;
currentSetting = 0;
delay(1000);
lcd.clear();
break;
}
// Case 42: Main cooking cycle with agitation
case 42: { //Cooking *
digitalWrite(powerON, HIGH);
// const float targetTemp = settings[SETTING_TANK_TEMP].value;
// float currentTemp = readTemperature(temp1SenPin);
unsigned long cookTime = settings[SETTING_COOKING1_TIME + RUNpos - 1].value;
// Constants for ignition control
const unsigned long sparkDuration = 2500;
const unsigned long sparkOFFtime = 1500;
const byte maxIgnitionAttempts = 4;
const unsigned long flameCheckTimeout = 2000; // 5 seconds to detect flame loss
// State variables
static unsigned long cookStartTime = millis();
static byte ignitionAttempts = 0;
static unsigned long lastSparkTime = 0;
static bool sparkActive = false;
static unsigned long lastFlameDetectedTime = millis();
static bool flameLost = false;
unsigned long elapsed = millis() - cookStartTime;
unsigned long remaining = cookTime - elapsed;
if (digitalRead(TANK_HI_PIN) == 1) {
} else {
if (currentSetting == 1) {
lcd.clear();
delay(100);
currentSetting = 0;
}
}
// Check for cancel
if (digitalRead(BTN_BACK) == LOW) {
lcd.clear();
lcd.print("Cooking stopped!");
delay(1000);
ErraNO = runNumer;
runNumer = 99;
}
// Update flame detection time
if (digitalRead(irSen1Pin) == LOW) {
lastFlameDetectedTime = millis();
flameLost = false;
ignitionAttempts = 0;
}
// Check for flame loss during operation
if (digitalRead(gasMainTank) == HIGH &&
digitalRead(irSen1Pin) == LOW &&
millis() - lastFlameDetectedTime > flameCheckTimeout) {
// Flame lost during operation
flameLost = true;
// digitalWrite(gasMainTank, LOW); // Close gas valve for safety
digitalWrite(sparkPin, LOW);
sparkActive = false;
}
// Display status
lcd.setCursor(0, 0);
lcd.print("Cooking ");
lcd.print(digitalRead(TANK_HI_PIN));
if (flameLost) {
// Show flame loss warning
lcd.print("Flame Lost! ");
lcd.print(ignitionAttempts);
lcd.print("/4");
delay(400);
} else {
lcd.setCursor(0, 1);
lcd.print("Time");
unsigned long minutes = remaining / 60000;
unsigned long seconds = (remaining % 60000) / 1000;
if (minutes < 10) lcd.print(" ");
lcd.print(minutes);
lcd.print(":");
if (seconds < 10) lcd.print("0");
lcd.print(seconds);
lcd.print("s");
if (digitalRead(irSen1Pin) == HIGH) {
delay(10);
lcd.setCursor(0, 1);
lcd.print("Time");
unsigned long minutes = remaining / 60000;
unsigned long seconds = (remaining % 60000) / 1000;
if (minutes < 10) lcd.print(" ");
lcd.print(minutes);
lcd.print(":");
if (seconds < 10) lcd.print("0");
lcd.print(seconds);
lcd.print("s");
lcd.setCursor(11, 1);
lcd.print("TEMP ");
lcd.print(digitalRead(TANK_HI_PIN));
}
}
// Temperature control with flame safety
if (digitalRead(TANK_HI_PIN) == 1) {
if (flameLost) {
// Attempt to reignite
if (ignitionAttempts < maxIgnitionAttempts) {
// Start new ignition cycle
if (!sparkActive && (millis() - lastSparkTime > sparkOFFtime)) {
digitalWrite(gasMainTank, HIGH); // Open gas valve
delay(200);
digitalWrite(sparkPin, HIGH); // Start spark
delay(200);
sparkActive = true;
lastSparkTime = millis();
ignitionAttempts = ignitionAttempts + 1;
if (currentSetting == 0) {
lcd.clear();
currentSetting = 1;
}
lcd.setCursor(0, 1);
lcd.print("Reigniting ");
lcd.print(ignitionAttempts);
lcd.print("/4 ");
delay(400);
}
// Turn off spark after duration
if (sparkActive && (millis() - lastSparkTime >= sparkDuration)) {
digitalWrite(sparkPin, LOW);
sparkActive = false;
}
} else {
// Failed all reignition attempts
digitalWrite(gasMainTank, LOW);
digitalWrite(sparkPin, LOW);
lcd.setCursor(0, 1);
lcd.print("Reignite Failed!");
ErraNO = runNumer;
runNumer = 99; // Go to error state
delay(3000);
break;
}
} else {
// Normal operation - flame is present
digitalWrite(gasMainTank, HIGH);
digitalWrite(motor, HIGH); // Agitation
digitalWrite(sparkPin, LOW); // Ensure spark is off
sparkActive = false;
}
} else if (digitalRead(TANK_HI_PIN) == 1) {
// Temperature reached target
digitalWrite(gasMainTank, LOW);
digitalWrite(sparkPin, LOW);
sparkActive = false;
}
// Check for completion
if (elapsed >= cookTime) {
digitalWrite(gasMainTank, LOW);
digitalWrite(motor, LOW);
digitalWrite(sparkPin, LOW);
runNumer = 43; // Proceed to drain
firstrun = 0;
currentSetting = 0;
cookStartTime = 0; // Reset timer
lcd.clear();
ignitionAttempts = 0;
flameLost = false;
}
// Emergency stop
if (digitalRead(BTN_BACK) == LOW) {
digitalWrite(gasMainTank, LOW);
digitalWrite(motor, LOW);
digitalWrite(sparkPin, LOW);
ErraNO = runNumer;
runNumer = 99;
ignitionAttempts = 0;
flameLost = false;
}
break;
}
// Case 43: Post-cooking drain opening
case 43: {
// Wash Cycle Drain Phase -
//drain is closed - opening drain
//close - now Opening...
digitalWrite(powerON, HIGH);
if (digitalRead(OPENdrainSwitchPin) == LOW) {
digitalWrite(drain, HIGH);
lcd.setCursor(0, 1);
lcd.print("Opening... ");
drainmove = 2;
runNumer = 43;
} else {
lcd.setCursor(0, 1);
lcd.print("Open ");
digitalWrite(drain, LOW);
delay(1000);
if (digitalRead(OPENdrainSwitchPin) == HIGH ) {
drainmove = 2;
}
}
if (drainmove == 2 && digitalRead(OPENdrainSwitchPin) == HIGH ) {
digitalWrite(drain, LOW);
lcd.setCursor(0, 1);
lcd.print("Open ");
delay(500);
if (digitalRead(OPENdrainSwitchPin) == HIGH ) {
runNumer = 50;
firstrun = 0;
currentSetting = 0;
lcd.clear();
drainmove = 0;
}
}
break;
}
case 44: // Wait for user
{ // Your user wait implementation here
runNumer = 45; // Move to next case
break;
}
case 45: // Wait for user
{ // Your user wait implementation here
runNumer = 46; // Move to next case
break;
}
case 46: // Wait for user
{ // Your user wait implementation here
runNumer = 47; // Move to next case
break;
}
case 47: // Wait for user
{ // Your user wait implementation here
runNumer = 48; // Move to next case
break;
}
case 48: // Wait for user
{ // Your user wait implementation here
runNumer = 49; // Move to next case
break;
}
case 49: // Wait for user
{ // Your user wait implementation here
runNumer = 50; // Move to next case
break;
}
//Hot Rinse: 50→ 59
// Case 50: Hot rinse heating phase
case 50: // fill water
{
digitalWrite(powerON, HIGH);
// Get target temperature from settings
// const float targetTemp = settings[SETTING_WATER_TEMP].value;
// float currentTemp = readTemperature(temp2SenPin);
// Constants
const unsigned long sparkDuration = 2500;
const unsigned long sparkOFFtime = 1500;
const byte maxIgnitionAttempts = 4;
const unsigned long maintainTime = 60000; // 1 minute maintain time
const unsigned long gasCheckTimeout = 10000; // 10 seconds to detect gas issues
// State variables
static byte ignitionAttempts = 0;
static unsigned long lastSparkTime = 0;
static bool sparkActive = false;
static unsigned long timerStartTime = 0;
static bool timerRunning = false;
static bool waterFillingStarted = false;
static unsigned long lastFlameDetectedTime = 0;
static bool gasIssueDetected = false;
static bool initialIgnitionStarted = false;
static unsigned long ignitionStartTime = 0;
// Update flame detection time-low = flame
if (digitalRead(irSen2Pin) == LOW) {
lastFlameDetectedTime = millis();
gasIssueDetected = false; // Reset gas issue if flame is detected
}
// Start ignition immediately when entering case 40 if not already started
if (!timerRunning && !waterFillingStarted && !initialIgnitionStarted) {
initialIgnitionStarted = true;
ignitionAttempts = 0;
ignitionStartTime = millis();
lcd.clear();
}
// Initial ignition phase (before timer starts)
if (initialIgnitionStarted && !timerRunning && !waterFillingStarted) {
lcd.setCursor(0, 0);
lcd.print("Hot rinse");
// Check if we have a flame (successful ignition)-low = flame
if (digitalRead(irSen2Pin) == LOW) {
// Flame detected - start timer
timerStartTime = millis();
timerRunning = true;
initialIgnitionStarted = false;
digitalWrite(sparkPin, LOW);
sparkActive = false;
lcd.clear();
}
else if (ignitionAttempts < maxIgnitionAttempts) {
// Attempt ignition
lcd.setCursor(0, 1);
lcd.print("Igniting ");
lcd.print(ignitionAttempts + 1);
lcd.print("/4 ");
// Start new ignition cycle
if (!sparkActive && (millis() - lastSparkTime > sparkOFFtime)) {
digitalWrite(gasWaterHeat, HIGH); // Open gas valve
digitalWrite(sparkPin, HIGH); // Start spark
sparkActive = true;
lastSparkTime = millis();
ignitionAttempts++;
}
// Turn off spark after duration
if (sparkActive && (millis() - lastSparkTime >= sparkDuration)) {
digitalWrite(sparkPin, LOW);
sparkActive = false;
// Wait a moment to see if ignition was successful-high = no flame
if (digitalRead(irSen2Pin) == HIGH) {
// Ignition failed, wait before next attempt
delay(1000);
}
}
}
else {
// Failed all 4 attempts
digitalWrite(gasWaterHeat, LOW);
digitalWrite(sparkPin, LOW);
lcd.setCursor(0, 1);
lcd.print("Ignition Failed!");
ErraNO = runNumer;
runNumer = 99; // Go to error state
delay(3000);
initialIgnitionStarted = false;
ignitionAttempts = 0;
break;
}
}
// Check if we should start the timer (temp high OR flame detected)
if (!timerRunning && !waterFillingStarted && digitalRead(irSen2Pin) == HIGH) {
timerStartTime = millis();
timerRunning = true;
ignitionAttempts = 0; // Reset attempts when timer starts
initialIgnitionStarted = false;
}
if (timerRunning) {
// Calculate remaining time
unsigned long elapsed = millis() - timerStartTime;
unsigned long remaining = (elapsed < maintainTime) ? (maintainTime - elapsed) : 0;
unsigned long seconds = remaining / 1000;
// Display timer status
lcd.setCursor(0, 0);
lcd.print("Hot rinse ");
lcd.print(digitalRead(WATER_HI_PIN));//water hot
lcd.setCursor(0, 1);
if (seconds < 10) lcd.print(" ");
lcd.print(seconds);
lcd.print("s ");
// Maintain temperature during timer
if (WATER_HI_PIN == 1) {
// Need to heat - check if flame is present-low = flame
if (digitalRead(irSen2Pin) == LOW) {
// Flame is present, just open gas valve
digitalWrite(gasWaterHeat, HIGH);
digitalWrite(sparkPin, LOW);
sparkActive = false;
ignitionAttempts = 0; // Reset attempts when flame is detected
} else {
// No flame - attempt ignition
if (ignitionAttempts < maxIgnitionAttempts) {
// Start new ignition cycle
if (!sparkActive && (millis() - lastSparkTime > sparkOFFtime)) {
digitalWrite(gasWaterHeat, HIGH); // Open gas valve
digitalWrite(sparkPin, HIGH); // Start spark
sparkActive = true;
lastSparkTime = millis();
ignitionAttempts++;
lcd.setCursor(0, 1);
lcd.print("Re-igniting ");
lcd.print(ignitionAttempts);
lcd.print("/4 ");
delay(300);
}
// Turn off spark after duration
if (sparkActive && (millis() - lastSparkTime >= sparkDuration)) {
digitalWrite(sparkPin, LOW);
sparkActive = false;
}
} else {
// Failed after max attempts
digitalWrite(gasWaterHeat, LOW);
digitalWrite(sparkPin, LOW);
lcd.setCursor(0, 1);
lcd.print("Ignition Failed!");
ErraNO = runNumer;
runNumer = 99; // Go to error state
delay(3000);
timerRunning = false;
ignitionAttempts = 0;
break;
}
}
} else {
// Temperature is at or above target, turn off heating
digitalWrite(gasWaterHeat, LOW);
digitalWrite(sparkPin, LOW);
sparkActive = false;
}
// Check if timer is complete
if (elapsed >= maintainTime) {
timerRunning = false;
waterFillingStarted = true;
digitalWrite(waterAgitator, HIGH); // Start filling with water
ignitionAttempts = 0; // Reset attempts for filling phase
lcd.clear();
lcd.print("Starting Fill");
delay(1000);
lcd.clear();
firstrun = 0;
currentSetting = 0;
runNumer = 51;
}
}
else if (waterFillingStarted) {
// Water filling phase after timer completes
lcd.setCursor(0, 0);
lcd.print("Hot rinse ");
// lcd.print(targetTemp, 0);
lcd.print((char)223);
lcd.print("C ");
// Display current temperature with "fill" indicator
lcd.setCursor(0, 1);
lcd.print("Temp ");
lcd.print(digitalRead(WATER_HI_PIN));
lcd.print((char)223);
lcd.print("C fill ");
delay(1000);
firstrun = 0;
currentSetting = 0;
runNumer = 51;
// break;
// Maintain temperature while filling
if (digitalRead(TANK_HI_PIN) == 1) {
// Need to heat - check if flame is present-low = flame
if (digitalRead(irSen2Pin) == LOW) {
// Flame is present, just open gas valve
digitalWrite(gasWaterHeat, HIGH);
digitalWrite(sparkPin, LOW);
sparkActive = false;
ignitionAttempts = 0; // Reset attempts when flame is detected
} else {
// No flame - attempt ignition
if (ignitionAttempts < maxIgnitionAttempts) {
// Start new ignition cycle
if (!sparkActive && (millis() - lastSparkTime > sparkOFFtime)) {
digitalWrite(gasWaterHeat, HIGH); // Open gas valve
digitalWrite(sparkPin, HIGH); // Start spark
sparkActive = true;
lastSparkTime = millis();
ignitionAttempts++;
lcd.setCursor(0, 1);
lcd.print("Igniting ");
lcd.print(ignitionAttempts);
lcd.print("/4 ");
delay(200);
}
// Turn off spark after duration
if (sparkActive && (millis() - lastSparkTime >= sparkDuration)) {
digitalWrite(sparkPin, LOW);
sparkActive = false;
}
} else {
// Failed after max attempts
digitalWrite(gasWaterHeat, LOW);
digitalWrite(sparkPin, LOW);
lcd.setCursor(0, 1);
lcd.print("Ignition Failed!");
ErraNO = runNumer;
runNumer = 99; // Go to error state
delay(3000);
waterFillingStarted = false;
ignitionAttempts = 0;
break;
}
}
} else {
// Temperature is at or above target, turn off heating
digitalWrite(gasWaterHeat, LOW);
digitalWrite(sparkPin, LOW);
sparkActive = false;
}
}
// Emergency stop check
if (digitalRead(BTN_BACK) == LOW) {
digitalWrite(gasWaterHeat, LOW);
digitalWrite(sparkPin, LOW);
digitalWrite(waterFill, LOW);
digitalWrite(WaterBY, LOW);
ErraNO = runNumer;
runNumer = 99;
timerRunning = false;
waterFillingStarted = false;
initialIgnitionStarted = false;
ignitionAttempts = 0;
}
break;
}
// Case 51: Hot rinse agitation phase // heat tank
case 51: {
digitalWrite(powerON, HIGH);
static unsigned long rinseStartTime = 0;
const unsigned long RINSE_DURATION = settings[SETTING_HOT_RINCE1_TIME + RUNpos - 1].value;
// Get target temperature from settings
// const float targetTemp = settings[SETTING_WATER_TEMP].value;
// waterTempSensor.requestTemperatures();
// float currentTemp = waterTempSensor.getTempCByIndex(0);
// Flame control variables
static byte ignitionAttempts = 0;
static unsigned long lastSparkTime = 0;
static bool sparkActive = false;
static unsigned long lastFlameDetectedTime = 0;
static bool initialIgnitionStarted = false;
static unsigned long ignitionStartTime = 0;
// Constants
const unsigned long sparkDuration = 2500;
const unsigned long sparkOFFtime = 1500;
const byte maxIgnitionAttempts = 4;
// Calculate remaining time
unsigned long elapsed = (rinseStartTime > 0) ? (millis() - rinseStartTime) : 0;
unsigned long remaining = (elapsed < RINSE_DURATION) ? (RINSE_DURATION - elapsed) : 0;
unsigned long minutes = remaining / 60000;
unsigned long seconds = (remaining % 60000) / 1000;
// Initialize rinse timer on first entry
if (rinseStartTime == 0) {
rinseStartTime = millis();
initialIgnitionStarted = true;
ignitionAttempts = 0;
ignitionStartTime = millis();
digitalWrite(waterAgitator, HIGH);
lcd.clear();
}
if (digitalRead(irSen2Pin) == HIGH) {//high no flame
lcd.setCursor(15, 0);
lcd.print("n");//no flame
} else {
lcd.setCursor(15, 0);
lcd.print("F"); // flame
}
// Motor control based on temperature (only when flame is present)
if (digitalRead(WATER_LO_PIN) == 1) {
if (digitalRead(irSen2Pin) == LOW) { // water flame low = flame on
digitalWrite(motor, HIGH);
delay(50);
} else {
// LCD clear condition (preserved as requested)
digitalWrite(motor, LOW);
delay(50);
lcd.setCursor(0, 1);
lcd.print("Temp: ");
lcd.print(digitalRead(TANK_HI_PIN));
}
} else {
digitalWrite(motor, LOW);
delay(200);
if (currentSetting == 1) {
lcd.clear();
currentSetting = 0;
}
lcd.setCursor(0, 1);
lcd.print("Temp: ");
lcd.print(digitalRead(TANK_HI_PIN));
}
// Initial ignition phase (like case 25)
if (initialIgnitionStarted) {
lcd.setCursor(0, 0);
lcd.print("Hot Rinse ");
// Check if we have a flame (successful ignition)
if (digitalRead(irSen2Pin) == LOW) {
// Flame detected - successful ignition
digitalWrite(sparkPin, LOW);
sparkActive = false;
initialIgnitionStarted = false;
ignitionAttempts = 0;
lcd.clear();
}
else if (ignitionAttempts < maxIgnitionAttempts) {
// Attempt ignition
// LCD clear condition (preserved as requested)
if (currentSetting == 0) {
lcd.clear();
currentSetting = 1;
}
lcd.setCursor(0, 1);
lcd.print("Igniting ");
lcd.print(ignitionAttempts + 1);
lcd.print("/4 ");
delay(300);
// Start new ignition cycle
if (!sparkActive && (millis() - lastSparkTime > sparkOFFtime)) {
digitalWrite(gasWaterHeat, HIGH); // Open gas valve
delay(200);
digitalWrite(sparkPin, HIGH); // Start spark
delay(200);
sparkActive = true;
lastSparkTime = millis();
ignitionAttempts++;
}
// Turn off spark after duration
if (sparkActive && (millis() - lastSparkTime >= sparkDuration)) {
digitalWrite(sparkPin, LOW);
delay(200);
sparkActive = false;
// Wait a moment to see if ignition was successful
if (digitalRead(irSen2Pin) == HIGH) { // water high = no flame
// Ignition failed, wait before next attempt
delay(1000);
}
}
}
else {
// Failed all 4 attempts
digitalWrite(gasWaterHeat, LOW);
digitalWrite(sparkPin, LOW);
digitalWrite(motor, LOW);
digitalWrite(waterAgitator, LOW);
delay(100);
lcd.clear();
lcd.setCursor(0, 1);
lcd.print("Ignition Failed!");
ErraNO = runNumer;
runNumer = 99; // Go to error state
delay(3000);
initialIgnitionStarted = false;
ignitionAttempts = 0;
break;
}
}
else {
// Normal operation after successful ignition
if (digitalRead(TANK_HI_PIN) == 1) {
// Need to heat - check if flame is present
if (digitalRead(irSen2Pin) == LOW) {
// Flame is present, just open gas valve
digitalWrite(gasWaterHeat, HIGH);
delay(200);
digitalWrite(sparkPin, LOW);
// Show temperature status
lcd.setCursor(0, 1);
lcd.print("Temp: ");
lcd.print(digitalRead(WATER_LO_PIN));
delay(200);
sparkActive = false;
ignitionAttempts = 0; // Reset attempts when flame is detected
} else {
// No flame - attempt re-ignition
if (ignitionAttempts < maxIgnitionAttempts) {
// Start new ignition cycle
if (!sparkActive && (millis() - lastSparkTime > sparkOFFtime)) {
digitalWrite(gasWaterHeat, HIGH); // Open gas valve
delay(200);
digitalWrite(sparkPin, HIGH); // Start spark
delay(200);
sparkActive = true;
lastSparkTime = millis();
ignitionAttempts++;
if (currentSetting == 0) {
currentSetting = 1;
}
lcd.setCursor(0, 1);
lcd.print("Re-igniting ");
lcd.print(ignitionAttempts);
lcd.print("/4 ");
delay(400);
}
// Turn off spark after duration
if (sparkActive && (millis() - lastSparkTime >= sparkDuration)) {
digitalWrite(sparkPin, LOW);
delay(200);
sparkActive = false;
}
} else {
// Failed after max attempts
digitalWrite(gasWaterHeat, LOW);
delay(200);
digitalWrite(sparkPin, LOW);
delay(200);
lcd.setCursor(0, 1);
lcd.print("Ignition Failed!");
ErraNO = runNumer;
runNumer = 99; // Go to error state
delay(3000);
ignitionAttempts = 0;
break;
}
}
} else {
// Temperature is at or above target, turn off heating
digitalWrite(gasWaterHeat, LOW);
delay(200);
digitalWrite(sparkPin, LOW);
delay(200);
sparkActive = false;
}
}
// Control water agitator (always on during rinse)
digitalWrite(waterAgitator, HIGH);
// Check if rinse duration is complete
if (elapsed >= RINSE_DURATION) {
digitalWrite(gasWaterHeat, LOW);
delay(200);
digitalWrite(sparkPin, LOW);
delay(200);
digitalWrite(waterAgitator, LOW);
sparkActive = false;
rinseStartTime = 0;
initialIgnitionStarted = false;
runNumer = 52;
firstrun = 0;
currentSetting = 0;
lcd.setCursor(0, 1);
lcd.print("Rinse Complete! ");
currentSetting = 0;
delay(2000);
}
// Emergency stop check
if (digitalRead(BTN_BACK) == LOW) {
digitalWrite(gasWaterHeat, LOW);
digitalWrite(sparkPin, LOW);
digitalWrite(waterAgitator, LOW);
sparkActive = false;
ErraNO = runNumer;
runNumer = 99;
rinseStartTime = 0;
initialIgnitionStarted = false;
currentSetting = 0;
}
// Update display every second
lcd.setCursor(0, 0);
lcd.print("Rinse: ");
if (minutes < 10) lcd.print("0");
lcd.print(minutes);
lcd.print("m");
if (seconds < 10) lcd.print("0");
lcd.print(seconds);
lcd.print("s ");
if (digitalRead(irSen2Pin) == LOW) {
// Show temperature status
lcd.setCursor(0, 1);
lcd.print("Temp: ");
}
if (firstrun == 0) {
firstrun = 1;
lcd.clear();
}
delay(20);
break;
}
// Case 52: Air drying phase
case 52: {
digitalWrite(powerON, HIGH);
digitalWrite(gasMainTank, LOW);
digitalWrite(gasWaterHeat, LOW);
digitalWrite(sparkPin, LOW);
digitalWrite(waterFill, LOW);
digitalWrite(waterAgitator, LOW);
unsigned long airTime = settings[SETTING_AIR1_TIME + RUNpos - 1].value; // Air time setting
static unsigned long airStart = millis();
lcd.setCursor(0, 0);
lcd.print("Air Drying ");
// Calculate remaining time in minutes and seconds
unsigned long remaining = airTime - (millis() - airStart);
unsigned long minutes = remaining / 60000;
unsigned long seconds = (remaining % 60000) / 1000;
lcd.setCursor(0, 1);
lcd.print("Time Left: ");
if (minutes < 10) lcd.print("0");
lcd.print(minutes);
lcd.print(":");
if (seconds < 10) lcd.print("0");
lcd.print(seconds);
lcd.print(" ");
digitalWrite(air, HIGH); // Turn on air
digitalWrite(motor, HIGH); // Agitation
if (millis() - airStart >= airTime) {
digitalWrite(air, LOW);
digitalWrite(motor, LOW); // Agitation
runNumer = 61; // Proceed to final check
airStart = 0;
}
break;
}
case 53: // Wait for user
{ // Your user wait implementation here
runNumer = 54; // Move to next case
break;
}
case 54: // Wait for user
{ // Your user wait implementation here
runNumer = 55; // Move to next case
break;
}
case 55: // Wait for user
{ // Your user wait implementation here
runNumer = 56; // Move to next case
break;
}
case 56: // Wait for user
{ // Your user wait implementation here
runNumer = 57; // Move to next case
break;
}
case 57: // Wait for user
{ // Your user wait implementation here
runNumer = 58; // Move to next case
break;
}
case 58: // Wait for user
{ // Your user wait implementation here
runNumer = 59; // Move to next case
break;
}
case 59: // Wait for user
{ // Your user wait implementation here
runNumer = 60; // Move to next case
break;
}
//Compleat: 60→ 61
// Case 60: Final water level check
case 60: {
digitalWrite(powerON, HIGH);
lcd.setCursor(0, 0);
lcd.print("Final Check ");
if (fillPin == 0) {
lcd.setCursor(0, 1);
lcd.print("Water Level OK ");
runNumer = 61; // Complete
delay(2000);
lcd.clear();
} else {
lcd.clear();
runNumer = 61;
}
break;
}
// Case 61: Complete
case 61: {
digitalWrite(powerON, LOW);
lcd.setCursor(0, 0);
lcd.print("PROCESS COMPLETE");
digitalWrite(Elight, HIGH); // Indicate done
digitalWrite(powerON, HIGH);
// Turn off all outputs
digitalWrite(waterAgitator, LOW);
digitalWrite(air, LOW);
digitalWrite(motor, LOW);
digitalWrite(gasMainTank, LOW);
digitalWrite(gasWaterHeat, LOW);
digitalWrite(sparkPin, LOW);
digitalWrite(waterFill, LOW);
digitalWrite(waterAgitator, LOW);
digitalWrite(drain, LOW);
digitalWrite(dosing, LOW);
runNumer = 61;
if (digitalRead(BTN_BACK) == LOW) {
digitalWrite(Elight, LOW);
digitalWrite(powerON, LOW);
running = false;
currentMenu = MAIN_MENU;
}
break;
}
// Case 99: Emergency shutdown-Emergency Stop
case 99: {
// Emergency shutdown - turn off all outputs
lcd.setCursor(13, 0);
lcd.print("C");
lcd.print(ErraNO);
digitalWrite(waterAgitator, LOW);
digitalWrite(air, LOW);
digitalWrite(motor, LOW);
digitalWrite(gasMainTank, LOW);
digitalWrite(gasWaterHeat, LOW);
digitalWrite(sparkPin, LOW);
digitalWrite(waterFill, LOW);
digitalWrite(powerON, LOW);
digitalWrite(Elight, HIGH);
runNumer = 99;
if (digitalRead(BTN_BACK) == LOW) {
running = false;
digitalWrite(powerON, LOW);
digitalWrite(air, LOW);
digitalWrite(motor, LOW);
digitalWrite(gasMainTank, LOW);
digitalWrite(gasWaterHeat, LOW);
digitalWrite(sparkPin, LOW);
digitalWrite(waterFill, LOW);
digitalWrite(powerON, LOW);
digitalWrite(Elight, LOW);
lcd.clear();
lcd.print("Run Cancelled!");
delay(2000);
currentMenu = MAIN_MENU;
inSubMenu = false;
displayMainMenu();
} else {
runNumer = 99;
}
// running = false;
break;
}
// Default case for invalid run numbers
default:
lcd.clear();
lcd.print("Invalid case!");
lcd.setCursor(0, 1);
// lcd.print(runNumer);
running = false;
delay(2000);
currentMenu = MAIN_MENU;
break;
}
// Check for back button to cancel run
if (digitalRead(BTN_BACK) == LOW) {
running = false;
lcd.clear();
digitalWrite(powerON, LOW);
digitalWrite(waterAgitator, LOW);
digitalWrite(air, LOW);
digitalWrite(motor, LOW);
digitalWrite(gasMainTank, LOW);
digitalWrite(gasWaterHeat, LOW);
digitalWrite(sparkPin, LOW);
digitalWrite(waterFill, LOW);
digitalWrite(Elight, LOW);
lcd.print("Run Cancelled!");
delay(2000);
lcd.clear();
currentMenu = MAIN_MENU;
inSubMenu = false;
displayMainMenu();
}
}
}10 outputs 11 wires 15 pin plug
5 sen inputs 7 wire 10 pin plug
4 input buttons
tank - - - water fill
i2c lcd
fill - down fill
drain -up closed
drain -down open
fill tank
drain motor
fill aj
air
main gas tank
gas water
3 ph motor
dopping
E-LIGHT
Spark
lock
dope
1 tank
2 water heat
FILL RT
FILLING LF