/*
* =====================================================================
* Nove cerpani vody - ridici jednotka cerpadla
* =====================================================================
* Platforma : Arduino Nano (ATmega328P)
* Zadani : "Nove cerpani vody - predpis pro naprogramovani" (Pepa)
* Podklady : voda/souhrn-nove-cerpani-vody.md
* voda/Odpoved_Otazky-Nove-cerpani-vody.doc
*
* Zapojeni:
* A0 - pot.: max. rozdil hladiny pro poruchu 2 (0V=100 l, 5V=500 l)
* A1 - pot.: delka plovouciho casoveho okna (0V=30 min, 5V=120 min)
* A2 - pot.: kalibrace litru na cm, +-10 % (2,5V = 0 %)
* A3 - pot.: doladeni prahu indikace 100 % hladiny, +-10 %
* A4 - I2C SDA - displej LCD 20x4 (adresa 0x27)
* A5 - I2C SCL - displej
* A7 - cidlo hladiny: 0,094V = 0 cm (pracovni nula), 5V = 800 cm
* D2 - SW6: ulozeni 100% hladiny do EEPROM (aktivni log.1)
* D3 - SW5: ulozeni kalibrace do EEPROM (aktivni log.1)
* D5 - SW4: prepinac AUT (log.0) / MAN (log.1)
* D7 - SW2: Reset (aktivni log.1)
* D8 - SW1: Start - realizuje se 3 s po pusteni (aktivni log.1)
* D9 - SW3: Zalevani 30/60/90/120 min, 5. stisk = zrusit
* D6 - vystup: rele stykace cerpadla (log.1 = seple)
* D10 - vystup: indikace poruchy (log.1 = porucha)
* D4, D11, D12, D13 - rezervovano (externi SPI pamet se NEpouziva,
* dle odpovedi Q5 staci interni EEPROM)
*
* Poruchy:
* 1 - nizka hladina (A7 < 0,094 V) -> rele VYP, D10=1, latch
* 2 - pokles hladiny > limit A0 v okne A1 -> rele VYP, D10=1, latch
* 3 - dosazeni max. hladiny (100 %) -> jen indikace D10=1
* Latch poruchy 1/2 se rusi POUZE tlacitkem Reset (po odezneni priciny).
*
* Knihovny: Wire (soucast IDE), EEPROM (soucast IDE),
* LiquidCrystal_I2C (Frank de Brabander) - instalace pres
* Library Manager: "LiquidCrystal I2C"
* =====================================================================
*/
#include <Wire.h>
#include <EEPROM.h>
#include <LiquidCrystal_I2C.h>
/* ===================== KONFIGURACE (upravitelne) ================== */
// Kalibracni konstanta: kolik litru vody odpovida 1 cm vysky hladiny.
// Predbezna hodnota dle Q4 - upravit podle skutecne geometrie nadrze!
const float LITERS_PER_CM = 10.0;
// Vychozi 100% vyska hladiny [cm], nez ji uzivatel ulozi tlacitkem SW6.
const float DEFAULT_LEVEL100_CM = 700.0;
// Prevod cidla hladiny (odpoved Q7): 0,094 V = 0 cm, 5 V = 800 cm.
const int ADC_ZERO_RAW = 19; // 0,094 V na 10bit ADC (5V ref.)
const int ADC_FULL_RAW = 1023; // 5 V
const float LEVEL_FULL_CM = 800.0; // vyska hladiny pri 5 V
// Hystereze poruchy 1 (odpoved Q7): porucha odezni az +2 cm nad nulou.
const float FAULT1_CLEAR_CM = 2.0;
// Start: cerpadlo se rozbehne 3 s po pusteni tlacitka Start.
const unsigned long START_DELAY_MS = 3000UL;
// Zalevani (odpoved Q8): potvrzeni poctu stisku po 2 s bez stisku.
const unsigned long WATER_CONFIRM_MS = 2000UL;
const unsigned int WATER_MINUTES[4] = {30, 60, 90, 120};
// Debounce tlacitek (odpoved Q9).
const unsigned long DEBOUNCE_MS = 50UL;
// Porucha 2 - vzorkovani hladiny do klouzaveho okna (odpoved Q1).
const unsigned long F2_SAMPLE_MS = 60000UL; // 1 vzorek za minutu
const int F2_BUF_SIZE = 121; // max. okno 120 min + rezerva
// Displej: LCD 20x4 pres I2C (predpoklad Q6).
const uint8_t LCD_ADDR = 0x27;
const uint8_t LCD_COLS = 20;
const uint8_t LCD_ROWS = 4;
const unsigned long LCD_REFRESH_MS = 400UL; // perioda prekresleni
// Obrazovka kalibrace: mizi 3 s po poslednim pohybu potenciometru A2.
const unsigned long CAL_SCREEN_MS = 3000UL;
const int POT_DEADBAND = 8; // mrtve pasmo detekce pohybu
// EEPROM - ulozene hodnoty (odpoved Q5: staci interni pamet).
const int EE_ADDR = 0;
const uint16_t EE_MAGIC = 0xC0DA; // "voda" - platnost dat
/* ========================= PINY =================================== */
const uint8_t PIN_POT_MAXDIFF = A0;
const uint8_t PIN_POT_WINDOW = A1;
const uint8_t PIN_POT_CAL = A2;
const uint8_t PIN_POT_IND100 = A3;
const uint8_t PIN_SENSOR = A7;
const uint8_t PIN_BTN_SAVE100 = 2; // SW6
const uint8_t PIN_BTN_SAVECAL = 3; // SW5
const uint8_t PIN_SW_AUTMAN = 5; // SW4 (0=AUT, 1=MAN)
const uint8_t PIN_BTN_RESET = 7; // SW2
const uint8_t PIN_BTN_START = 8; // SW1
const uint8_t PIN_BTN_WATER = 9; // SW3
const uint8_t PIN_RELAY = 6; // rele cerpadla
const uint8_t PIN_FAULT = 10; // indikace poruchy
/* ====================== DATOVE TYPY =============================== */
// Stavovy automat rizeni cerpadla.
enum State : uint8_t {
ST_STOPPED, // po Resetu / poruse - rele vypnuto, ceka na Start
ST_START_WAIT, // Start stisknut a pusten - bezi 3s odpocet
ST_RUNNING, // normalni automaticky provoz - rele seplo
ST_WATERING // rezim zalevani - rele seplo na navoleny cas
};
// Data ukladana do interni EEPROM (s kontrolnim souctem - viz Q5).
struct Persist {
uint16_t magic; // EE_MAGIC = data platna
float level100Cm; // ulozena 100% vyska hladiny [cm]
int8_t calPct; // ulozena kalibracni korekce [-10..+10 %]
uint8_t crc; // XOR kontrolni soucet
};
// Stav jednoho tlacitka s debouncingem (aktivni v log.1, pull-down).
struct Button {
uint8_t pin;
bool stable; // ustaleny stav
bool lastRead; // posledni ctena uroven
unsigned long lastChangeMs;
bool pressedEvent; // hrana 0->1 (stisk)
bool releasedEvent; // hrana 1->0 (pusteni)
};
/* ==================== GLOBALNI PROMENNE =========================== */
LiquidCrystal_I2C lcd(LCD_ADDR, LCD_COLS, LCD_ROWS);
State state = ST_STOPPED;
// Mereni hladiny
float levelCm = 0.0; // aktualni vyska hladiny [cm] (filtr. hodnota)
float liters = 0.0; // prepocet na litry
float percent = 0.0; // % naplneni vuci 100% hladine
int sensorRaw = 0; // syrova hodnota ADC z A7
// Perzistentni nastaveni
float level100Cm = DEFAULT_LEVEL100_CM;
int8_t calPctSaved = 0; // ulozena korekce litru/cm [%]
// Poruchy
bool fault1 = false; // nizka hladina (latch)
bool fault2 = false; // prekroceni rozdilu hladiny (latch)
bool fault3 = false; // max. hladina (jen indikace, bez latche)
// Start s 3s prodlevou
unsigned long startWaitBeganMs = 0;
// Zalevani
uint8_t waterPressCount = 0; // pocet stisku SW3 (0 = neaktivni)
unsigned long waterLastPressMs = 0; // cas posledniho stisku
bool waterSelecting = false; // probiha volba poctu stisku
unsigned long waterEndMs = 0; // konec zalevani (millis)
unsigned int waterMinutes = 0; // navolena doba [min]
// Porucha 2 - kruhovy buffer minutovych vzorku hladiny [l]
int16_t f2Buf[F2_BUF_SIZE];
uint8_t f2Head = 0; // index nejnovejsiho vzorku
uint8_t f2Count = 0; // pocet platnych vzorku
unsigned long f2LastSampleMs = 0;
// Tlacitka
Button btnSave100 = {PIN_BTN_SAVE100, false, false, 0, false, false};
Button btnSaveCal = {PIN_BTN_SAVECAL, false, false, 0, false, false};
Button btnReset = {PIN_BTN_RESET, false, false, 0, false, false};
Button btnStart = {PIN_BTN_START, false, false, 0, false, false};
Button btnWater = {PIN_BTN_WATER, false, false, 0, false, false};
// Displej
unsigned long lcdLastRefreshMs = 0;
unsigned long calPotLastMoveMs = 0; // posledni pohyb A2 -> obrazovka 4
int calPotLastRaw = -1000; // pro detekci pohybu A2
unsigned long a0LastMoveMs = 0; // pohyb A0 -> zobrazit hodnotu
unsigned long a1LastMoveMs = 0; // pohyb A1 -> zobrazit hodnotu
int a0LastRaw = -1000;
int a1LastRaw = -1000;
/* ======================== SETUP =================================== */
void setup() {
// Tlacitka maji externi pull-down (odpoved Q9) -> prosty INPUT.
pinMode(PIN_BTN_SAVE100, INPUT);
pinMode(PIN_BTN_SAVECAL, INPUT);
pinMode(PIN_SW_AUTMAN, INPUT);
pinMode(PIN_BTN_RESET, INPUT);
pinMode(PIN_BTN_START, INPUT);
pinMode(PIN_BTN_WATER, INPUT);
pinMode(PIN_RELAY, OUTPUT);
pinMode(PIN_FAULT, OUTPUT);
digitalWrite(PIN_RELAY, LOW); // po zapnuti rele rozeple
digitalWrite(PIN_FAULT, LOW);
loadPersist(); // nacteni EEPROM (100 %, kalibrace)
lcd.init();
lcd.backlight();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(F("Cerpani vody v1.0"));
lcd.setCursor(0, 1);
lcd.print(F("Start systemu..."));
// Inicializacni zmereni hladiny (naplneni filtru realnou hodnotou).
sensorRaw = readSensorRaw();
levelCm = rawToCm(sensorRaw);
liters = levelCm * litersPerCmEff(); // aby f2Reset ulozil realny stav
f2Reset();
delay(1000); // jedina blokujici prodleva - uvodni obrazovka
lcd.clear();
}
/* ========================= LOOP =================================== */
void loop() {
const unsigned long now = millis();
readInputs(now); // tlacitka + potenciometry + cidlo
updateFaults(now); // vyhodnoceni poruch 1/2/3
handleButtons(now); // Reset / Start / Zalevani / ulozeni hodnot
updateStateMachine(now);// prechody stavu vc. 3s startu a konce zalevani
driveOutputs(); // rele D6 + porucha D10
updateDisplay(now); // prekresleni LCD
}
/* ============== CTENI VSTUPU + PREVODY HLADINY ==================== */
// Precte cidlo hladiny s prumerovanim (potlaceni sumu ADC).
int readSensorRaw() {
long sum = 0;
for (uint8_t i = 0; i < 8; i++) {
sum += analogRead(PIN_SENSOR);
}
return (int)(sum / 8);
}
// Prevod ADC -> vyska hladiny [cm]. 0,094 V = 0 cm (pracovni nula, Q7).
float rawToCm(int raw) {
if (raw <= ADC_ZERO_RAW) return 0.0;
return (float)(raw - ADC_ZERO_RAW) * LEVEL_FULL_CM
/ (float)(ADC_FULL_RAW - ADC_ZERO_RAW);
}
// Efektivni pocet litru na cm = konstanta * ulozena korekce z A2.
float litersPerCmEff() {
return LITERS_PER_CM * (1.0 + (float)calPctSaved / 100.0);
}
// Prah 100 % hladiny [cm] = ulozena hodnota + doladeni potenciometrem A3
// (odpoved Q2: A3 posouva prah indikace v rozsahu +-10 %).
float level100ThresholdCm() {
int a3 = analogRead(PIN_POT_IND100);
float offsetPct = ((float)a3 / 1023.0) * 20.0 - 10.0; // -10..+10 %
return level100Cm * (1.0 + offsetPct / 100.0);
}
// Limit poruchy 2 [l] z potenciometru A0 (0V=100 l, 5V=500 l).
float f2LimitLiters() {
int a0 = analogRead(PIN_POT_MAXDIFF);
return 100.0 + ((float)a0 / 1023.0) * 400.0;
}
// Delka klouzaveho okna [min] z potenciometru A1 (0V=30, 5V=120 min).
unsigned int f2WindowMinutes() {
int a1 = analogRead(PIN_POT_WINDOW);
return (unsigned int)(30.0 + ((float)a1 / 1023.0) * 90.0 + 0.5);
}
// Aktualni poloha kalibracniho potenciometru A2 [-10..+10 %].
int8_t calPotPct() {
int a2 = analogRead(PIN_POT_CAL);
return (int8_t)(((float)a2 / 1023.0) * 20.0 - 10.0 + 0.5);
}
// Nacte vsechny vstupy, aktualizuje hladinu/litry/procenta a detekuje
// pohyb potenciometru (kvuli docasnym obrazovkam nastaveni).
void readInputs(unsigned long now) {
// --- cidlo hladiny s jednoduchym IIR filtrem ---
sensorRaw = readSensorRaw();
float cmNow = rawToCm(sensorRaw);
levelCm = levelCm * 0.8 + cmNow * 0.2; // vyhlazeni
liters = levelCm * litersPerCmEff();
float liters100 = level100Cm * litersPerCmEff();
percent = (liters100 > 1.0) ? (liters / liters100) * 100.0 : 0.0;
// --- tlacitka (debounce) ---
updateButton(btnSave100, now);
updateButton(btnSaveCal, now);
updateButton(btnReset, now);
updateButton(btnStart, now);
updateButton(btnWater, now);
// --- detekce pohybu potenciometru A2 (obrazovka kalibrace, Q6) ---
int a2 = analogRead(PIN_POT_CAL);
if (calPotLastRaw < -999) calPotLastRaw = a2; // prvni pruchod
if (abs(a2 - calPotLastRaw) > POT_DEADBAND) {
calPotLastRaw = a2;
calPotLastMoveMs = now;
}
// --- detekce pohybu A0 / A1 (zobrazeni nastavovane hodnoty) ---
int a0 = analogRead(PIN_POT_MAXDIFF);
if (a0LastRaw < -999) a0LastRaw = a0;
if (abs(a0 - a0LastRaw) > POT_DEADBAND) { a0LastRaw = a0; a0LastMoveMs = now; }
int a1 = analogRead(PIN_POT_WINDOW);
if (a1LastRaw < -999) a1LastRaw = a1;
if (abs(a1 - a1LastRaw) > POT_DEADBAND) { a1LastRaw = a1; a1LastMoveMs = now; }
}
// Debounce jednoho tlacitka; generuje udalosti stisk/pusteni.
void updateButton(Button &b, unsigned long now) {
b.pressedEvent = false;
b.releasedEvent = false;
bool raw = (digitalRead(b.pin) == HIGH);
if (raw != b.lastRead) {
b.lastRead = raw;
b.lastChangeMs = now;
}
if ((now - b.lastChangeMs) >= DEBOUNCE_MS && raw != b.stable) {
b.stable = raw;
if (raw) b.pressedEvent = true;
else b.releasedEvent = true;
}
}
// Vraci true, je-li prepinac SW4 v poloze MAN (log.1).
bool isManualMode() {
return digitalRead(PIN_SW_AUTMAN) == HIGH;
}
/* ==================== PORUCHY 1 / 2 / 3 =========================== */
// Reset klouzaveho okna poruchy 2 (naplni buffer aktualni hodnotou).
void f2Reset() {
int16_t cur = (int16_t)liters;
for (int i = 0; i < F2_BUF_SIZE; i++) f2Buf[i] = cur;
f2Head = 0;
f2Count = 1;
f2LastSampleMs = millis();
}
// Prida minutovy vzorek litru do kruhoveho bufferu (jen za behu cerpadla).
void f2AddSample(unsigned long now) {
if (now - f2LastSampleMs < F2_SAMPLE_MS) return;
f2LastSampleMs = now;
f2Head = (f2Head + 1) % F2_BUF_SIZE;
f2Buf[f2Head] = (int16_t)liters;
if (f2Count < F2_BUF_SIZE) f2Count++;
}
// Porucha 2 (odpoved Q1): pokles objemu v okne delky dle A1 (30-120 min)
// vetsi nez limit dle A0 (100-500 l) -> porucha (unik/prasknute potrubi).
bool f2Check() {
unsigned int windowMin = f2WindowMinutes();
if (f2Count < 2) return false;
uint8_t depth = min((uint8_t)windowMin, (uint8_t)(f2Count - 1));
// Nejvyssi hodnota v okne minus aktualni = maximalni pokles.
int16_t maxInWindow = f2Buf[f2Head];
for (uint8_t i = 1; i <= depth; i++) {
uint8_t idx = (f2Head + F2_BUF_SIZE - i) % F2_BUF_SIZE;
if (f2Buf[idx] > maxInWindow) maxInWindow = f2Buf[idx];
}
float drop = (float)maxInWindow - liters;
return drop > f2LimitLiters();
}
// Vyhodnoceni vsech poruch. Poruchy 1 a 2 jsou latch - drzi az do Resetu
// (odpoved Q3: zadny autostart po odezneni priciny).
void updateFaults(unsigned long now) {
// Porucha 1 - nizka hladina; vyhlasit pod nulou, latch drzi do Resetu.
if (sensorRaw < ADC_ZERO_RAW) {
fault1 = true;
}
// Porucha 2 - vyhodnocuje se jen za behu cerpadla (RUNNING/WATERING).
if (state == ST_RUNNING || state == ST_WATERING) {
f2AddSample(now);
if (f2Check()) fault2 = true;
}
// Porucha 3 - max. hladina; jen indikace, sama odezni (bez latche).
fault3 = (levelCm >= level100ThresholdCm());
}
// Podminka pro zruseni latchu poruchy 1 pri Resetu: hladina musi byt
// alespon FAULT1_CLEAR_CM nad pracovni nulou (hystereze, odpoved Q7).
bool fault1ConditionCleared() {
return levelCm >= FAULT1_CLEAR_CM;
}
/* =============== OBSLUHA TLACITEK A REZIMU ======================== */
void handleButtons(unsigned long now) {
// ---- Reset (SW2): vypnout rele; smazat poruchy, pokud pricina ustala ----
if (btnReset.pressedEvent) {
state = ST_STOPPED;
waterSelecting = false;
waterPressCount = 0;
waterMinutes = 0;
if (fault1 && fault1ConditionCleared()) fault1 = false;
if (fault2) { fault2 = false; } // pricina pominula zastavenim cerpadla
f2Reset(); // vynulovani mericiho okna
}
// ---- Start (SW1): jen v AUT rezimu, jen bez trvajici poruchy 1/2 ----
// Start se realizuje 3 s PO PUSTENI tlacitka (prechod 1 -> 0).
if (btnStart.releasedEvent) {
if (!isManualMode() && !fault1 && !fault2 && state == ST_STOPPED) {
state = ST_START_WAIT;
startWaitBeganMs = now;
}
}
// ---- Zalevani (SW3): pocet stisku voli cas, 5. stisk = zrusit ----
// Lze spustit kdykoli, i po Resetu (odpoved Q8).
if (btnWater.pressedEvent && !fault1 && !fault2) {
waterSelecting = true;
waterLastPressMs = now;
waterPressCount++;
if (waterPressCount >= 5) {
// 5. stisk = zruseni funkce -> navrat dle prepinace D5
waterSelecting = false;
waterPressCount = 0;
if (state == ST_WATERING) state = ST_STOPPED;
}
}
// Potvrzeni volby zalevani po 2 s bez dalsiho stisku (odpoved Q8).
if (waterSelecting && (now - waterLastPressMs >= WATER_CONFIRM_MS)) {
waterSelecting = false;
if (waterPressCount >= 1 && waterPressCount <= 4) {
waterMinutes = WATER_MINUTES[waterPressCount - 1];
waterEndMs = now + (unsigned long)waterMinutes * 60000UL;
state = ST_WATERING;
f2Reset();
}
waterPressCount = 0;
}
// ---- SW6 (D2): ulozeni aktualni vysky hladiny jako 100 % ----
if (btnSave100.pressedEvent) {
level100Cm = levelCm;
savePersist();
}
// ---- SW5 (D3): ulozeni kalibracni korekce z potenciometru A2 ----
if (btnSaveCal.pressedEvent) {
calPctSaved = calPotPct();
savePersist();
}
}
/* ==================== STAVOVY AUTOMAT ============================= */
void updateStateMachine(unsigned long now) {
// Trvajici porucha 1/2 okamzite zastavi cerpadlo (latch drzi do Resetu).
if (fault1 || fault2) {
if (state != ST_STOPPED) state = ST_STOPPED;
return;
}
switch (state) {
case ST_START_WAIT:
// MAN rezim behem cekani rusi start (Start funguje jen v AUT, Q3).
if (isManualMode()) {
state = ST_STOPPED;
} else if (now - startWaitBeganMs >= START_DELAY_MS) {
state = ST_RUNNING;
f2Reset();
}
break;
case ST_RUNNING:
// Prepnuti do MAN za behu -> zastavit automatiku (v MAN jen indikace).
if (isManualMode()) state = ST_STOPPED;
break;
case ST_WATERING:
// Konec navolene doby -> navrat do stavu dle prepinace D5:
// AUT -> STOPPED (ceka na novy Start), MAN -> STOPPED (jen indikace).
if ((long)(now - waterEndMs) >= 0) {
state = ST_STOPPED;
waterMinutes = 0;
}
break;
case ST_STOPPED:
default:
break;
}
}
/* ======================= VYSTUPY ================================== */
void driveOutputs() {
// Rele D6: seple pri RUNNING nebo WATERING (porucha 3 ho nevypina).
bool relayOn = (state == ST_RUNNING || state == ST_WATERING)
&& !fault1 && !fault2;
digitalWrite(PIN_RELAY, relayOn ? HIGH : LOW);
// Porucha D10: log.1 pri jakekoli poruse 1/2/3.
digitalWrite(PIN_FAULT, (fault1 || fault2 || fault3) ? HIGH : LOW);
}
/* ================ EEPROM (interni pamet, Q5) ====================== */
// XOR kontrolni soucet struktury Persist (bez pole crc).
uint8_t persistCrc(const Persist &p) {
const uint8_t *b = (const uint8_t *)&p;
uint8_t crc = 0x5A;
for (size_t i = 0; i < sizeof(Persist) - sizeof(p.crc); i++) crc ^= b[i];
return crc;
}
// Nacte ulozene hodnoty; pri nevalidnich datech pouzije vychozi.
void loadPersist() {
Persist p;
EEPROM.get(EE_ADDR, p);
if (p.magic == EE_MAGIC && p.crc == persistCrc(p)
&& p.level100Cm > 0.0 && p.level100Cm <= LEVEL_FULL_CM
&& p.calPct >= -10 && p.calPct <= 10) {
level100Cm = p.level100Cm;
calPctSaved = p.calPct;
} else {
level100Cm = DEFAULT_LEVEL100_CM;
calPctSaved = 0;
}
}
// Ulozi hodnoty do EEPROM. EEPROM.put pouziva update() -> zapisuje se
// jen pri zmene, tim se setri zivotnost pameti (~100 000 cyklu).
void savePersist() {
Persist p;
p.magic = EE_MAGIC;
p.level100Cm = level100Cm;
p.calPct = calPctSaved;
p.crc = persistCrc(p);
EEPROM.put(EE_ADDR, p);
}
/* ======================== DISPLEJ ================================= */
/*
* Provizorni rozvrzeni (finalni layout doda zadavatel pozdeji).
* 4 obrazovky dle zadani:
* 1 - automaticky provoz 2 - manualni provoz
* 3 - zalevani 4 - kalibrace cidla (pohyb A2, mizi po 3 s)
* Na kazde obrazovce: litry + % (misto % docasne hodnota A0/A1 pri pohybu).
*/
// Vypis radku doplneny mezerami na sirku LCD (prepis bez blikani).
void lcdPrintLine(uint8_t row, const char *text) {
lcd.setCursor(0, row);
uint8_t i = 0;
for (; text[i] != '\0' && i < LCD_COLS; i++) lcd.print(text[i]);
for (; i < LCD_COLS; i++) lcd.print(' ');
}
void updateDisplay(unsigned long now) {
if (now - lcdLastRefreshMs < LCD_REFRESH_MS) return;
lcdLastRefreshMs = now;
char line[21];
// ---- Obrazovka 4: kalibrace cidla (pri pohybu A2, mizi po 3 s) ----
if (calPotLastMoveMs != 0 && (now - calPotLastMoveMs) < CAL_SCREEN_MS) {
int8_t pct = calPotPct();
lcdPrintLine(0, "Kalibrace cidla");
// Konstanta = efektivni litry/cm pri aktualni poloze potenciometru
float kNow = LITERS_PER_CM * (1.0 + (float)pct / 100.0);
snprintf(line, sizeof(line), "Konstanta %d.%d l/cm",
(int)kNow, (int)(kNow * 10) % 10);
lcdPrintLine(1, line);
snprintf(line, sizeof(line), "Zmena %+d %%", pct);
lcdPrintLine(2, line);
lcdPrintLine(3, "Ulozit: tl. SW5");
return;
}
// ---- Radek 0: rezim/stav ----
if (fault1) lcdPrintLine(0, "PORUCHA 1: min.hlad.");
else if (fault2) lcdPrintLine(0, "PORUCHA 2: unik vody");
else if (state == ST_WATERING) {
// Obrazovka 3 - zalevani: zbyvajici cas v minutach
unsigned long remainMin = (waterEndMs > now)
? (waterEndMs - now + 59999UL) / 60000UL : 0;
snprintf(line, sizeof(line), "ZALEVANI zbyva %lum", remainMin);
lcdPrintLine(0, line);
}
else if (waterSelecting) {
// Volba doby zalevani (pocet stisku SW3)
if (waterPressCount >= 1 && waterPressCount <= 4)
snprintf(line, sizeof(line), "Zalevani: %u min ?",
WATER_MINUTES[waterPressCount - 1]);
else
snprintf(line, sizeof(line), "Zalevani: zrusit ?");
lcdPrintLine(0, line);
}
else if (isManualMode()) lcdPrintLine(0, "MANUALNI PROVOZ"); // obr. 2
else if (state == ST_RUNNING) lcdPrintLine(0, "AUTOMAT: BEZI"); // obr. 1
else if (state == ST_START_WAIT)lcdPrintLine(0, "AUTOMAT: start za 3s");
else lcdPrintLine(0, "AUTOMAT: zastaveno");
// ---- Radek 1: hladina v litrech ----
snprintf(line, sizeof(line), "Hladina: %ld l", (long)liters);
lcdPrintLine(1, line);
// ---- Radek 2: procenta / nastavovana hodnota A0 nebo A1 ----
// Pri pohybu potenciometru A0/A1 se misto procent zobrazi nastavovana
// hodnota (dle zadani - obrazovka 1).
if (a0LastMoveMs != 0 && (now - a0LastMoveMs) < CAL_SCREEN_MS) {
snprintf(line, sizeof(line), "Max.rozdil: %ld l", (long)f2LimitLiters());
} else if (a1LastMoveMs != 0 && (now - a1LastMoveMs) < CAL_SCREEN_MS) {
snprintf(line, sizeof(line), "Plov.cas: %u min", f2WindowMinutes());
} else {
int pctInt = (int)(percent + 0.5);
if (pctInt > 999) pctInt = 999;
snprintf(line, sizeof(line), "Naplneni: %d %%", pctInt);
}
lcdPrintLine(2, line);
// ---- Radek 3: indikace max. hladiny / rele ----
if (fault3)
lcdPrintLine(3, "MAX. HLADINA !");
else
lcdPrintLine(3, (digitalRead(PIN_RELAY) == HIGH)
? "Cerpadlo: ZAP" : "Cerpadlo: VYP");
}
Garaz
Min Mez
Hystereze