// ============================================
// Sketch: Button menu test (AUTO/MAN/SET + Stopwatch)
// ============================================
#include <Arduino.h>
#include <LiquidCrystal_I2C.h>
#include "variables.h"
// ================== BUTTONY ==================
#define BTN_SET 2
#define BTN_UP 3
#define BTN_DOWN 4
#define BUZZ 5
#define BTN_SERVICE 8 // nové tlačítko
// LCD objekt je definován v lcd.ino
extern LiquidCrystal_I2C lcd;
// LCD funkce (lcd.ino)
void lcdInit();
void lcdAutoMode();
void lcdManMode();
void homeScreen();
void loading(uint8_t col, uint8_t row);
void lcdBlinkHourglass();
void lcdStopwatchHoldScreen();
String formatStopwatch(int minutes);
// Prototyp beep
void beep();
// ================== MENU / STAV ==================
uint8_t menuLevel = 0; // 0 = home, 1 = list, 2 = edit
uint8_t menuGroup = 0; // 0 = AUTO, 1 = MAN, 2 = SET
uint8_t menuIndex = 0;
// ================== EDIT STAV ==================
enum EditContext {
EDIT_NONE = 0,
EDIT_POOL_TEMP,
EDIT_STOPWATCH,
EDIT_DATETIME,
EDIT_DAYLIGHT,
EDIT_WIFI
};
EditContext editCtx = EDIT_NONE;
// ================== MENU TYPY ==================
enum MenuItemType {
MI_ACTION = 0,
MI_TOGGLE_BOOL,
MI_EDIT_SCREEN,
MI_EXIT
};
typedef void (*EditFunction)();
struct MenuItem {
const char* label;
MenuItemType type;
EditFunction onEnter;
bool* togglePtr; // používá se pro MI_TOGGLE_BOOL
};
// Předdeklarace obrazovek a akcí
void showAnyMenu();
void showManMenu();
void showSetMenu();
void startEdit(EditContext ctx);
void showEditScreen();
void handleEditUp();
void handleEditDown();
void handleEditSet();
void enterPoolTempEdit();
void enterStopwatchEdit();
void enterDateTimeEdit();
void enterDaylightEdit();
void enterWifiEdit();
void actionStopAll();
void actionNoop();
void enterSetMenu();
void aboutSunreg();
// Blink pro datum/čas edit
bool blinkState = true;
unsigned long lastBlink = 0;
const unsigned long blinkInterval = 400;
// STOPWATCH hold screen
bool showStopwatchHold = false;
// STOPWATCH odpočet (v minutách)
unsigned long lastStopwatchTick = 0;
// ---------- TIME / DATE ----------
int editHour = 12;
int editMinute = 30;
int editDay = 15;
int editMonth = 11;
int editYear = 2025;
// Globální proměnná pro označení pole v DATETIME
uint8_t dtFieldIndex = 0;
// ================== AUTO MENU ==================
const MenuItem autoMenu[] = {
{ "Pool temp", MI_EDIT_SCREEN, enterPoolTempEdit, nullptr },
{ "Filtration", MI_ACTION, actionNoop, nullptr },
{ "Exit MENU", MI_EXIT, nullptr, nullptr }
};
const uint8_t autoMenuSize = sizeof(autoMenu) / sizeof(autoMenu[0]);
// ================== MAN MENU ==================
const MenuItem manMenu[] = {
{ "Pump", MI_TOGGLE_BOOL, nullptr, &Vars.pump },
{ "Heating", MI_TOGGLE_BOOL, nullptr, &Vars.heatValve },
{ "Filtration", MI_TOGGLE_BOOL, nullptr, &Vars.filtrValve },
{ "Stopwatch", MI_EDIT_SCREEN, enterStopwatchEdit, nullptr },
{ "Stpw action", MI_TOGGLE_BOOL, nullptr, &Vars.stopWatchAction },
{ "STOP all", MI_ACTION, actionStopAll, nullptr },
{ "Settings", MI_ACTION, enterSetMenu, nullptr },
{ "Exit MENU", MI_EXIT, nullptr, nullptr }
};
const uint8_t manMenuSize = sizeof(manMenu) / sizeof(manMenu[0]);
// ================== SET MENU ==================
const MenuItem setMenu[] = {
{ "Wi-Fi", MI_EDIT_SCREEN, enterWifiEdit, &Vars.stavWifi },
{ "Date/Time", MI_EDIT_SCREEN, enterDateTimeEdit, nullptr },
{ "Daylight", MI_EDIT_SCREEN, enterDaylightEdit, Vars.daylightValue },
{ "Heat circ", MI_ACTION, actionNoop, nullptr },
{ "Pool circ", MI_ACTION, actionNoop, nullptr },
{ "Disp sleep", MI_ACTION, actionNoop, nullptr },
{ "Filt duration",MI_ACTION, actionNoop, nullptr },
{ "RCHK time", MI_ACTION, actionNoop, nullptr },
{ "Firmwr upgr", MI_TOGGLE_BOOL, nullptr, &Vars.firmwareUpgr },
{ "About SUNREG", MI_ACTION, aboutSunreg, nullptr },
{ "Exit MENU", MI_EXIT, nullptr, nullptr }
};
const uint8_t setMenuSize = sizeof(setMenu) / sizeof(setMenu[0]);
// ================== AKTUÁLNÍ MENU POINTER ==================
const MenuItem* currentMenu = nullptr;
uint8_t currentMenuSize = 0;
// ================== OSTATNÍ ==================
unsigned long lastPress = 0;
const unsigned long debounceMs = 180;
// ----------------------------------------------------------
// Pomocné funkce
// ----------------------------------------------------------
void beep() {
digitalWrite(BUZZ, HIGH);
delay(50);
digitalWrite(BUZZ, LOW);
}
// Uložení konfigurace (placeholder)
void saveConfig() {
// sem přijde EEPROM / SPIFFS atd.
}
// ----------------------------------------------------------
// MENU – SET
// ----------------------------------------------------------
void showSetMenu() {
if (!currentMenu || currentMenuSize == 0) return;
lcd.clear();
const MenuItem &item = currentMenu[menuIndex];
lcd.setCursor(0, 0);
lcd.print(item.label);
// ON/OFF pro bool položky
if (item.type == MI_TOGGLE_BOOL && item.togglePtr != nullptr) {
lcd.setCursor(13, 0);
lcd.print(*(item.togglePtr) ? " on" : "off");
}
// spodní řádek
lcd.setCursor(0, 1);
if (menuIndex < currentMenuSize - 1) {
const char* nextLabel = currentMenu[menuIndex + 1].label;
if (strcmp(nextLabel, "Exit MENU") != 0) {
lcd.write(7); // next symbol
} else {
lcd.print(' ');
}
lcd.print(nextLabel);
} else {
lcd.print(" ");
}
}
// ----------------------------------------------------------
// MENU – AUTO – univerzální výpis
// ----------------------------------------------------------
void showAnyMenu() {
if (!currentMenu || currentMenuSize == 0) return;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(currentMenu[menuIndex].label);
lcd.setCursor(0, 1);
if (menuIndex < currentMenuSize - 1) {
const char* nextLabel = currentMenu[menuIndex + 1].label;
if (strcmp(nextLabel, "Exit MENU") != 0) {
lcd.write(7);
} else {
lcd.print(' ');
}
lcd.print(nextLabel);
} else {
lcd.print(" ");
}
}
// ----------------------------------------------------------
// MENU – MAN: speciální výpis s ON/OFF/MAN/AUTO/Stopwatch
// ----------------------------------------------------------
void showManMenu() {
if (!currentMenu || currentMenuSize == 0) return;
lcd.clear();
const MenuItem &item = currentMenu[menuIndex];
lcd.setCursor(0, 0);
lcd.print(item.label);
// Univerzální ON/OFF pro toggle položky
if (item.type == MI_TOGGLE_BOOL && item.togglePtr != nullptr) {
// speciální případ: Stpw action → MAN/AUTO
if (item.togglePtr == &Vars.stopWatchAction) {
lcd.setCursor(12, 0);
lcd.print(Vars.stopWatchAction ? "MAN" : "AUTO");
} else {
lcd.setCursor(13, 0);
lcd.print(*(item.togglePtr) ? " on" : "off");
}
}
// speciální položka Stopwatch – zobraz zbývající čas vpravo
if (&item == &manMenu[3]) {
String t = formatStopwatch(Vars.stopWatch);
int pad = 16 - t.length();
if (pad < 0) pad = 0;
lcd.setCursor(pad, 0);
lcd.print(t);
}
// spodní řádek
lcd.setCursor(0, 1);
if (menuIndex < currentMenuSize - 1) {
const char* nextLabel = currentMenu[menuIndex + 1].label;
if (strcmp(nextLabel, "Exit MENU") != 0) {
lcd.write(7);
} else {
lcd.print(' ');
}
lcd.print(nextLabel);
} else {
lcd.print(" ");
}
}
// ----------------------------------------------------------
// EDIT – START + OBECNÝ RENDERER
// ----------------------------------------------------------
void startEdit(EditContext ctx) {
editCtx = ctx;
menuLevel = 2;
showEditScreen();
}
void showEditScreen() {
lcd.clear();
switch (editCtx) {
case EDIT_POOL_TEMP:
lcd.setCursor(0,0);
lcd.print("Pool temp");
lcd.setCursor(0,1);
// zarovnání doprava
char buf[6];
sprintf(buf, "%2d", Vars.poolTemp);
{
int pad = 16 - strlen(buf);
for(int i = 0; i < pad; i++) lcd.print(' ');
}
lcd.print(buf);
lcd.write(2);
break;
case EDIT_STOPWATCH: {
lcd.setCursor(0, 0);
lcd.print("Stopwatch");
lcd.setCursor(0, 1);
if (Vars.stopWatch == 0) {
lcd.print("(0-12hod) off");
} else {
int h = Vars.stopWatch / 60;
int m = Vars.stopWatch % 60;
char buf[6];
sprintf(buf, "%02d:%02d", h, m);
int padding = 16 - strlen(buf);
if (padding < 0) padding = 0;
for (int i = 0; i < padding; i++) lcd.print(' ');
lcd.print(buf);
}
break;
}
case EDIT_WIFI:
lcd.setCursor(0, 0);
lcd.print("Wi-Fi setup");
lcd.setCursor(0, 1);
lcd.print("Not implemented");
break;
case EDIT_DATETIME:
lcd.setCursor(0, 0);
if (editHour < 10) lcd.print("0");
lcd.print(editHour);
lcd.print(":");
if (editMinute < 10) lcd.print("0");
lcd.print(editMinute);
lcd.setCursor(0, 1);
if (editDay < 10) lcd.print("0");
lcd.print(editDay);
lcd.print(".");
if (editMonth < 10) lcd.print("0");
lcd.print(editMonth);
lcd.print(".");
lcd.print(editYear);
break;
case EDIT_DAYLIGHT:
lcd.setCursor(0, 0);
lcd.print("Daylight");
lcd.setCursor(12, 0);
//hodnota z cidla, vypisovat z prava cele cislo
lcd.print("80%");
lcd.setCursor(0, 1);
lcd.print("Start level: ");
lcd.print(Vars.daylightValue);
lcd.print("%");
break;
default:
lcd.setCursor(0, 0);
lcd.print("Edit mode");
lcd.setCursor(0, 1);
lcd.print("No handler");
break;
}
}
// ----------------------------------------------------------
// EDIT – UP / DOWN / SET
// ----------------------------------------------------------
void handleEditUp() {
switch (editCtx) {
case EDIT_POOL_TEMP:
if (Vars.poolTemp < 39) Vars.poolTemp++;
else beep();
break;
case EDIT_STOPWATCH:
if (Vars.stopWatch < 720) {
Vars.stopWatch += 15;
if (Vars.stopWatch > 720) Vars.stopWatch = 720;
}
break;
case EDIT_DATETIME:
switch (dtFieldIndex) {
case 0: if (editHour < 23) editHour++; else editHour = 0; break;
case 1: if (editMinute < 59) editMinute++; else editMinute = 0; break;
case 2: if (editDay < 31) editDay++; else editDay = 1; break;
case 3: if (editMonth < 12) editMonth++; else editMonth = 1; break;
case 4: if (editYear < 2099) editYear++; break;
}
break;
case EDIT_DAYLIGHT:
//5-99
Serial.println("Daylight UP");
if (Vars.daylightValue < 99) Vars.daylightValue++;
break;
default:
break;
}
showEditScreen();
}
void handleEditDown() {
switch (editCtx) {
case EDIT_POOL_TEMP:
if (Vars.poolTemp > 15) Vars.poolTemp--;
else beep();
break;
case EDIT_STOPWATCH:
if (Vars.stopWatch >= 15) {
Vars.stopWatch -= 15;
} else {
Vars.stopWatch = 0;
beep();
}
break;
case EDIT_DATETIME:
switch (dtFieldIndex) {
case 0: if (editHour > 0) editHour--; else editHour = 23; break;
case 1: if (editMinute > 0) editMinute--; else editMinute = 59; break;
case 2: if (editDay > 1) editDay--; else editDay = 31; break;
case 3: if (editMonth > 1) editMonth--; else editMonth = 12; break;
case 4: if (editYear > 2020) editYear--; else beep(); break;
}
break;
case EDIT_DAYLIGHT:
if (Vars.daylightValue > 0) Vars.daylightValue--;
else beep();
break;
default:
break;
}
showEditScreen();
}
void handleEditSet() {
// ---- DATETIME ----
if (editCtx == EDIT_DATETIME) {
dtFieldIndex++;
if (dtFieldIndex > 4) {
dtFieldIndex = 0;
editCtx = EDIT_NONE;
menuLevel = 1;
// návrat
if (menuGroup == 2) showSetMenu();
else if (menuGroup == 1) showManMenu();
else showAnyMenu();
} else {
showEditScreen();
}
return;
}
// ---- STOPWATCH ----
if (editCtx == EDIT_STOPWATCH) {
editCtx = EDIT_NONE;
menuLevel = 1;
if (menuGroup == 1) showManMenu();
else showAnyMenu();
return;
}
// ---- POOL TEMP ----
if (editCtx == EDIT_POOL_TEMP) {
editCtx = EDIT_NONE;
menuLevel = 1;
if (menuGroup == 1) showManMenu();
else showAnyMenu();
return;
}
// ---- DAYLIGHT + WIFI ----
if (editCtx == EDIT_DAYLIGHT || editCtx == EDIT_WIFI) {
editCtx = EDIT_NONE;
menuLevel = 1;
if (menuGroup == 1) showManMenu();
else showAnyMenu();
return;
}
}
// ----------------------------------------------------------
// KONKRÉTNÍ EDIT FUNKCE
// ----------------------------------------------------------
void enterPoolTempEdit() { startEdit(EDIT_POOL_TEMP); }
void enterStopwatchEdit() { startEdit(EDIT_STOPWATCH); }
void enterDaylightEdit() { startEdit(EDIT_DAYLIGHT); }
void enterWifiEdit() {
startEdit(EDIT_WIFI);
loading(15, 1);
}
void enterDateTimeEdit() {
dtFieldIndex = 0;
editCtx = EDIT_DATETIME;
menuLevel = 2;
blinkState = true;
lastBlink = millis();
showEditScreen(); // vykreslí HH:MM / DD.MM.YYYY
}
// ----------------------------------------------------------
// AKCE (ACTION položky)
// ----------------------------------------------------------
void actionStopAll() {
Vars.pump = false;
Vars.heatValve = false;
Vars.filtrValve = false;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("All stopped");
delay(1000);
if (menuGroup == 1) showManMenu();
else showAnyMenu();
}
void actionNoop() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Not implemented");
delay(600);
if (menuGroup == 1) showManMenu();
else showAnyMenu();
}
void aboutSunreg() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("SUNREG v1.0");
lcd.setCursor(0, 1);
lcd.print("(c) 2025 Yours");
delay(1500);
if (menuGroup == 1) showManMenu();
else if (menuGroup == 2) showSetMenu();
else showAnyMenu();
}
// ----------------------------------------------------------
// VSTUP DO MENU PODLE REŽIMU
// ----------------------------------------------------------
void enterMenuByWorkMode() {
menuLevel = 1;
menuIndex = 0;
if (Vars.workMode == 0) { // AUTO
menuGroup = 0;
currentMenu = autoMenu;
currentMenuSize = autoMenuSize;
showAnyMenu();
} else { // MAN
menuGroup = 1;
currentMenu = manMenu;
currentMenuSize = manMenuSize;
showManMenu();
}
}
void enterSetMenu() {
menuLevel = 1;
menuGroup = 2;
menuIndex = 0;
currentMenu = setMenu;
currentMenuSize = setMenuSize;
showSetMenu();
}
// ----------------------------------------------------------
// STOPWATCH – ukončení a aplikace režimu
// ----------------------------------------------------------
void stopStopwatchAndApplyMode() {
Vars.pump = false;
Vars.heatValve = false;
Vars.filtrValve = false;
Vars.stopWatch = 0;
// false = AUTO, true = MAN
Vars.workMode = Vars.stopWatchAction ? 1 : 0;
saveConfig();
homeScreen();
}
// Stopky běží v minutách, odpočet 1x za 60 s
void updateStopwatch() {
if (Vars.workMode != 1) return; // jen MAN
if (menuLevel != 0) return; // jen home
if (Vars.stopWatch <= 0) return;
if (millis() - lastStopwatchTick >= 60000UL) {
lastStopwatchTick = millis();
if (Vars.stopWatch > 0) Vars.stopWatch--;
if (Vars.stopWatch <= 0) {
stopStopwatchAndApplyMode();
}
}
}
// ----------------------------------------------------------
// BUTTON HANDLER
// ----------------------------------------------------------
void buttons() {
if (millis() - lastPress < debounceMs) return;
//Temporary setting menu
// vstp do service Menu
if (!digitalRead(BTN_SERVICE)) {
enterSetMenu();
return; // nepokračovat do normálního režimu
}
// ---------------- SET ----------------
if (!digitalRead(BTN_SET)) {
lastPress = millis();
// HOME → vstup do menu dle režimu
if (menuLevel == 0) {
enterMenuByWorkMode();
return;
}
// EDIT → potvrzení a návrat
if (menuLevel == 2) {
handleEditSet();
return;
}
// LIST → rozhodnutí
if (menuLevel == 1 && currentMenu != nullptr) {
const MenuItem &item = currentMenu[menuIndex];
if (item.type == MI_EXIT) {
menuLevel = 0;
homeScreen();
return;
}
if (item.type == MI_TOGGLE_BOOL && item.togglePtr != nullptr) {
*(item.togglePtr) = !(*(item.togglePtr));
if (menuGroup == 1) showManMenu();
else if (menuGroup == 2) showSetMenu();
else showAnyMenu();
return;
}
if (item.type == MI_EDIT_SCREEN && item.onEnter != nullptr) {
item.onEnter();
return;
}
if (item.type == MI_ACTION) {
if (item.onEnter != nullptr) item.onEnter();
else actionNoop();
return;
}
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Unknown item");
delay(500);
if (menuGroup == 1) showManMenu();
else if (menuGroup == 2) showSetMenu();
else showAnyMenu();
return;
}
}
// ---------------- UP ----------------
if (!digitalRead(BTN_UP)) {
lastPress = millis();
// EDIT režim – UP mění hodnotu
if (menuLevel == 2) {
handleEditUp();
return;
}
// HOME – přepnutí do AUTO
if (menuLevel == 0) {
if (Vars.workMode != 0) {
// MAN → AUTO: vše vypnout a vynulovat stopky
Vars.pump = false;
Vars.heatValve = false;
Vars.filtrValve = false;
Vars.stopWatch = 0;
Vars.stopWatchAction = false;
saveConfig();
Vars.workMode = 0;
homeScreen();
}
return;
}
// LIST – posun v menu nahoru
if (menuLevel == 1 && currentMenu != nullptr) {
if (menuIndex > 0) menuIndex--;
if (menuGroup == 2) showSetMenu();
else if (menuGroup == 1) showManMenu();
else showAnyMenu();
}
}
// ---------------- DOWN ----------------
if (!digitalRead(BTN_DOWN)) {
lastPress = millis();
// HOLD obrazovka stopek na MAN home
if (menuLevel == 0 && Vars.workMode == 1 && Vars.stopWatch > 0) {
if (!showStopwatchHold) {
showStopwatchHold = true;
lcdStopwatchHoldScreen();
}
lastPress = millis();
return;
}
// EDIT režim – DOWN mění hodnotu
if (menuLevel == 2) {
handleEditDown();
return;
}
// HOME – přepnutí do MAN
if (menuLevel == 0) {
if (Vars.workMode != 1) {
Vars.workMode = 1;
homeScreen();
}
return;
}
// LIST – posun v menu dolů
if (menuLevel == 1 && currentMenu != nullptr) {
if (menuIndex < currentMenuSize - 1) menuIndex++;
if (menuGroup == 2) showSetMenu();
else if (menuGroup == 1) showManMenu();
else showAnyMenu();
}
}
}
void blinkDateTimeField() {
if (millis() - lastBlink < blinkInterval) return;
lastBlink = millis();
blinkState = !blinkState;
switch (dtFieldIndex) {
case 0: // HH
lcd.setCursor(0,0);
if (!blinkState) lcd.print(" ");
else {
if (editHour < 10) lcd.print("0");
lcd.print(editHour);
}
break;
case 1: // MM
lcd.setCursor(3,0);
if (!blinkState) lcd.print(" ");
else {
if (editMinute < 10) lcd.print("0");
lcd.print(editMinute);
}
break;
case 2: // DD
lcd.setCursor(0,1);
if (!blinkState) lcd.print(" ");
else {
if (editDay < 10) lcd.print("0");
lcd.print(editDay);
}
break;
case 3: // MM (měsíc)
lcd.setCursor(3,1);
if (!blinkState) lcd.print(" ");
else {
if (editMonth < 10) lcd.print("0");
lcd.print(editMonth);
}
break;
case 4: // YYYY
lcd.setCursor(6,1);
if (!blinkState) lcd.print(" ");
else lcd.print(editYear);
break;
}
}
// ----------------------------------------------------------
// SETUP & LOOP
// ----------------------------------------------------------
void setup() {
Serial.begin(115200);
delay(50);
Vars.begin();
lcdInit();
pinMode(BTN_SET, INPUT_PULLUP);
pinMode(BTN_UP, INPUT_PULLUP);
pinMode(BTN_DOWN, INPUT_PULLUP);
pinMode(BUZZ, OUTPUT);
pinMode(BTN_SERVICE, INPUT_PULLUP);
homeScreen();
}
void loop() {
buttons(); // hlavní obsluha tlačítek
lcdBlinkHourglass(); // blikající přesýpací hodiny při běžících stopkách
updateStopwatch(); // odpočet stopek (v minutách)
// návrat z HOLD obrazovky po uvolnění BTN_DOWN
if (showStopwatchHold) {
if (digitalRead(BTN_DOWN)) {
showStopwatchHold = false;
homeScreen();
}
}
// případné blikání při editaci času (pokud nechceš blokující enterDateTimeEdit)
if (menuLevel == 2 && editCtx == EDIT_DATETIME) {
blinkDateTimeField();
}
}