// ============================================================
// C_E_Mega_V7
// Base : V1Temp12 — logique originale ajustée
// Servo : pompe (On=0°, Off=180°)
// V6 : reorganisation des fonctions par categorie
// gererPanne servoOn?, menu EEPROM,
// ============================================================
#include <Wire.h>
#include <OneWire.h>
#include <LiquidCrystal_I2C.h>
#include <DallasTemperature.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <RTClib.h>
#include <Servo.h>
#include <AccelStepper.h>
#include <Keypad.h>
#include <SPI.h>
#include <TFT_eSPI.h>
#include <EEPROM.h>
byte Version = 7;
// ============================================================
// BROCHES
#define POMPE_HOMOG_PIN 3 // PWM, Timer3 — MOSFET IRLZ44N
#define STOP_SWITCH_PIN1 5
#define MS3 6
#define MS2 7
#define MS1 8
#define StepPin 9
#define DirPin 10
#define Sleep 11 // enable/disable stepper driver
#define buzzerPin 12
#define RedLed 13
#define YellowLed 23
#define ServoPin 36
#define ONE_WIRE_BUS 40
#define pinPression A0
#define VOLT_IN_PIN A3
// ============================================================
// ADRESSES EEPROM
#define EEPROM_VALID_MARKER 0xA5
// Adresses EEPROM — disposition séquentielle
#define ADDR_VALID 0 // 1 byte — marqueur validité
#define ADDR_MAX_TODAY 1 // 4 bytes — float
#define ADDR_TODAY_DAY 5 // 1 byte
#define ADDR_TODAY_MONTH 6 // 2 bytes — int
#define ADDR_MAX_PAST 8 // 4 bytes — float
#define ADDR_PAST_DAY 12 // 1 byte
#define ADDR_PAST_MONTH 13 // 1 byte
#define ADDR_MAX_YEAR 14 // 4 bytes — float
#define ADDR_YEAR_DAY 18 // 2 bytes — int
#define ADDR_YEAR_MONTH 20 // 2 bytes — int
#define ADDR_ALARM_ON_H 22 // 1 byte
#define ADDR_ALARM_ON_M 23 // 1 byte
#define ADDR_ALARM_OFF_H 24 // 1 byte
#define ADDR_ALARM_OFF_M 25 // 1 byte
#define ADDR_FROST_POINT 26 // 4 bytes — float
#define ADDR_MARGE_FROID 30 // 4 bytes — float
#define ADDR_TEMP_SERVO 34 // 4 bytes — float
#define ADDR_MOTOR_SPEED 38 // 2 bytes — int
#define ADDR_MOTOR_ACCEL 40 // 2 bytes — int
#define ADDR_MICROSTEP 42 // 1 byte
#define ADDR_FROST_CIRCULE 43 // 4 bytes — float
// Total : 47 bytes — bien dans les 4096 bytes du Mega
// ============================================================
// OBJETS MATERIELS
LiquidCrystal_I2C lcd(0x27, 20, 4);
TFT_eSPI tft = TFT_eSPI();
//#define TFT_ROTATION 0
Adafruit_SSD1306 display(128, 64, &Wire, -1);
RTC_DS3231 rtc;
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
AccelStepper stepper(AccelStepper::DRIVER, StepPin, DirPin);
Servo myServo;
const byte ROWS = 4;
const byte COLS = 3;
char keys[ROWS][COLS] = {
{'1','2','3'},{'4','5','6'},{'7','8','9'},{'*','0','#'}
};
byte rowPins[ROWS] = {28, 29, 30, 31};
byte colPins[COLS] = {32, 33, 34};
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);
// ============================================================
// VARIABLES GLOBALES — AFFICHAGE
bool forceFullRefresh = false; //pour TFT
byte Size = 5; //OLED
// ============================================================
// VARIABLES GLOBALES — RTC
bool rtcFound = false;
byte TimeSet = 0; // ...if time is set = 1
// ============================================================
// VARIABLES GLOBALES — CAPTEURS
const int numSensors = 3;
DeviceAddress sensorAddresses[numSensors];
const byte precision = 12;
float CE = 0, EC = 0, VA = 0;
byte analogValue = 0;
float Mov_avg[numSensors] = {0};
const float BETA = 3950;
float maxToday = -99, maxPast = -1000, maxYear = -1000;
int maxTodayMonth = 0, maxPastMonth = 0, maxYearMonth = 0;
byte maxTodayDay = 0, maxPastDay = 0;
int maxYearDay = 0;
// ============================================================
// VARIABLES GLOBALES — SERVO
const byte On = 0; // servo pompe ON = 0°
const byte Off = 180; // servo pompe OFF = 180°
bool servoAt0 = false;
bool manualOverride = false; // Mode manuel
// ============================================================
// VARIABLES GLOBALES — TENSION / PANNE
float in_voltage = 0;
float ref_voltage = 5.0;
float R1 = 30000.0, R2 = 7500.0;
const float VOLTAGE_LOW_THRESHOLD = 11.5; // pour accepter demande voltage stepper.
const float VOLTAGE_HIGH_THRESHOLD = 12.5; // remontée minimum pour fonctionner.
bool panneActive = false;
bool panneWasActive = false; // détecte le retour voltage
// ============================================================
// VARIABLES GLOBALES — STEPPER
const int StepsPerRevolution = 200;
byte currentMicrostep = 4;
int motorSpeed = 2900; // steps/s — modifiable par menu
int motorAccel = 15000; // steps/s² — modifiable par menu
bool motorBusy = false;
bool seekingStop1 = false;
bool ignoreStopSwitch = false;
bool calibrationPending = false;
int PosNow = 180; // position courante en degrés (180 = home)
int PosNow2 = 0; // delta à parcourir
int DegTurn = 0; // degrés absolus à tourner
const int totalOff = 360; // coupe entrée eau
const int Close = 180; // fermeture sortie (home switch)
const int FrostSafe = 225; // antigel partiel
const int Open = 240; // ouverture normale
const int OpenPlus = 245; // ouverture maximale
byte SemiOff = 50;
// ============================================================
// VARIABLES GLOBALES - Pompe Homogeneisation
const float FrostCircule_DEFAULT = 4.0; // valeur recommandée — entre FrostPoint et FrostPoint+MargeFroid
float FrostCircule = FrostCircule_DEFAULT;
const byte PWM_POMPE_LENT = 120; // vitesse fixe pour l'instant (0-255)
bool pompeHomogOn = false;
// ============================================================
// VARIABLES GLOBALES — SEUILS TEMPERATURE
const float FrostPoint_DEFAULT = 6.0;// Valeur d'origine recommandée
const float MargeFroid_DEFAULT = 2.0;// Valeur d'origine recommandée
const float TempServo_DEFAULT = 6.0;// Valeur d'origine recommandée
float FrostPoint = FrostPoint_DEFAULT;// degre de declenchement - marge froid
float MargeFroid = MargeFroid_DEFAULT;// marge de on/off valve frost
float TempServo = TempServo_DEFAULT;// seuil antigel pompe
float Marge = 0.2;// marge valve open/close
float OverHeat = 444;// surchauffe (inactif)
float OHeatPoint = 0;// Reference
// ============================================================
// VARIABLES GLOBALES — FLAGS LOGIQUE
int Check = 0;
int Done1 = 0, Done2 = 0, Done3 = 0, Done4 = 0;
int Valid1 = 1, Valid2 = 1, Valid3 = 1, Valid4 = 1;
// ============================================================
// VARIABLES GLOBALES — ALARMES / BUZZER
int pitch = 1000;
byte dl = 500;
byte time = 100;
byte BwR = -1;
byte alarmOnHour = 0, alarmOnMinute = 0;// Alarme pour aller à 0°
byte alarmOffHour = 0, alarmOffMinute = 0;// Alarme pour retourner à 180°
bool alarmOn = false;
char lastOnTime[6] = "";
char lastOffTime[6] = "";
bool lastOnActive = false;
bool lastOffActive = false;
bool alertActive = false;// activer/désactiver alerte ovrd
unsigned long lastBlink = 0;// gérer le timing du clignotement
// ============================================================
// VARIABLES GLOBALES — MENUS
byte currentMenu = 0;// 0 = Pas de menu, 1 = Menu 1, 2 = Menu 2, etc.
bool returnToMain = false;
int WaitX = 10000;
// ============================================================
// VARIABLES GLOBALES — CAPTEUR PRESSION
const float tensionMin = 0.5;
const float tensionMax = 4.5;
const float pressionMax = 30.0;
// ============================================================
// VARIABLES GLOBALES — STOP SWITCH
bool stopSwitchError = false; // flag erreur stop switch
//*************************************************************
//
// SECTION 1 — UTILITAIRES DE BASE
// (sans dépendances, utilisées partout)
//
//*************************************************************
void printAddress(DeviceAddress addr) {
for (uint8_t i = 0; i < 8; i++) {
if (addr[i] < 16) Serial.print("0");
Serial.print(addr[i], HEX);
}
Serial.println();
}
void beep() {
tone(buzzerPin, pitch, time); delay(time); noTone(buzzerPin);
}
void beepLow() {
for (int i = 1000; i >= 700; i -= 100) {
tone(buzzerPin, i, time); delay(time); noTone(buzzerPin);
}
}
char waitForKeyWithTimeout(unsigned long timeoutMs) {
unsigned long startWait = millis();
while (millis() - startWait < timeoutMs) {
char key = keypad.getKey();
if (key) { beep(); return key; } // Retourne la touche dès qu'on appuie
// Optionnel : On peut appeler une fonction légère ici
// pour ne pas figer totalement l'affichage si besoin
}
beepLow();
return 0;
}
void lcdMessage(const __FlashStringHelper* msg1, const __FlashStringHelper* msg2 = nullptr) {
lcd.clear();
lcd.print(msg1);
if (msg2 != nullptr) { lcd.setCursor(0, 1); lcd.print(msg2); }
delay(dl * 3);
lcd.clear();
}
long anglesToSteps(float degrees) {
float stepsPerRevMotor = StepsPerRevolution * currentMicrostep;
float stepsPerRevValve = stepsPerRevMotor * 216.0;
return (long)round((degrees * stepsPerRevValve) / 360.0);
}
//*************************************************************
//
// SECTION 2 — MATERIEL DE BASE
// (lecture capteurs, tension, configuration stepper)
//
//*************************************************************
void setMicrostep(int ms) {
switch (ms) {
case 1: digitalWrite(MS1,LOW); digitalWrite(MS2,LOW); digitalWrite(MS3,LOW); break;
case 2: digitalWrite(MS1,HIGH); digitalWrite(MS2,LOW); digitalWrite(MS3,LOW); break;
case 4: digitalWrite(MS1,LOW); digitalWrite(MS2,HIGH); digitalWrite(MS3,LOW); break;
case 8: digitalWrite(MS1,HIGH); digitalWrite(MS2,HIGH); digitalWrite(MS3,LOW); break;
case 16: digitalWrite(MS1,HIGH); digitalWrite(MS2,HIGH); digitalWrite(MS3,HIGH); break;
default: return;
}
currentMicrostep = ms;
}
void updateMicrosteps(int newFactor) {
if (newFactor != currentMicrostep &&
(newFactor==1||newFactor==2||newFactor==4||newFactor==8||newFactor==16)) {
setMicrostep(newFactor);
} Serial.println (F("UpdateMS"));
} // --- Appliquer un nouveau microstepping depuis le menu ou le code ---
float quickVoltage() {
float adc = analogRead(VOLT_IN_PIN);
float calc = (adc * ref_voltage) / 1023.0;
return calc / (R2 / (R1 + R2));
}
float readVoltage() {
const int numSamples = 5;
static float sumAdc = 0;
static int sampleCount = 0;
static unsigned long lastSample = 0;
const unsigned long sampleInterval = 5;
if (millis() - lastSample >= sampleInterval) {
lastSample = millis();
sumAdc += analogRead(VOLT_IN_PIN);
sampleCount++;
if (sampleCount >= numSamples) {
float avgAdc = sumAdc / (float)numSamples;
sumAdc = 0; sampleCount = 0;
float calcVoltage = (avgAdc * ref_voltage) / 1023.0;
in_voltage = calcVoltage / (R2 / (R1 + R2));
}
}
return in_voltage;
}
/*void readSensors() { // Lecture NUMERIQUE
const float alpha = 0.2; // 0.1=très lissé, 0.3=plus réactif — ajuster au besoin
sensors.requestTemperatures();
for (int i = 0; i < numSensors; i++) {
float rawTemp = sensors.getTempC(sensorAddresses[i]);
if (rawTemp > -50 && rawTemp < 85) {
// Lissage exponentiel : nouvelle valeur = alpha*brut + (1-alpha)*ancienne
if (Mov_avg[i] == 0) {
Mov_avg[i] = rawTemp; // initialisation au premier appel
} else {
Mov_avg[i] = alpha * rawTemp + (1.0 - alpha) * Mov_avg[i];
}
// Arrondi 0.1 après lissage
Mov_avg[i] = round(Mov_avg[i] * 10.0) / 10.0;
}
}
}*/
void readSensors() { // Lecture des capteurs ANALOGIQUE
sensors.requestTemperatures();
for (int i = 0; i < 3; i++) {
int analogValue = analogRead(A0 + i);
Mov_avg[i] = 1 / (log(1 / (1023.0 / analogValue - 1)) / BETA + 1.0 / 298.15) - 273.15;
}
}
float lirePressionFiltre() {
float somme = 0;
for (int i = 0; i < 10; i++) {
somme += analogRead(pinPression) * (5.0 / 1023.0);
delay(1);
}
float tension = somme / 10.0;
float pression = (tension - tensionMin) * (pressionMax / (tensionMax - tensionMin));
return pression < 0 ? 0 : pression;
}
//*************************************************************
//
// SECTION 3 — SERVO POMPE ET STEPPER
// (contrôle direct des actionneurs)
//
//*************************************************************
void servoOn() {
delay(dl * 3);
myServo.attach(ServoPin);
myServo.write(On);
servoAt0 = true;
delay(dl * 3);
myServo.detach();
Serial.println(F("Servo ON"));
}
void servoOff() {
delay(dl * 3);
myServo.attach(ServoPin);
myServo.write(Off);
servoAt0 = false;
delay(dl * 3);
myServo.detach();
Serial.println(F("Servo OFF"));
}
void signalSequence(bool opening) {
int cycleDelay = 150;
for (int i = 0; i < 3; i++) {
if (opening) {
tft.fillRect(171, 0, 309, 234, TFT_RED); delay(cycleDelay);
tft.fillRect(171, 0, 309, 234, TFT_YELLOW); delay(cycleDelay);
tft.fillRect(171, 0, 309, 234, TFT_GREEN); delay(cycleDelay);
} else {
tft.fillRect(171, 0, 309, 234, TFT_GREEN); delay(cycleDelay);
tft.fillRect(171, 0, 309, 234, TFT_YELLOW); delay(cycleDelay);
tft.fillRect(171, 0, 309, 234, TFT_RED); delay(cycleDelay);
}
}
delay(200);
forceFullRefresh = true;
}
void controlPompeHomog() {
if (panneActive) {
analogWrite(POMPE_HOMOG_PIN, 0);
pompeHomogOn = false;
return;
}
bool morningWindow = (alarmOn && PosNow == Close);
// Zone tampon : sous FrostCircule mais pas encore dans l'antigel principal (Done4)
bool frostBuffer = (CE <= FrostCircule && CE > FrostPoint - MargeFroid);
bool shouldRun = morningWindow || frostBuffer;
if (shouldRun && !pompeHomogOn) {
analogWrite(POMPE_HOMOG_PIN, PWM_POMPE_LENT);
pompeHomogOn = true;
Serial.println(F("[PompeHomog] ON"));
} else if (!shouldRun && pompeHomogOn) {
analogWrite(POMPE_HOMOG_PIN, 0);
pompeHomogOn = false;
Serial.println(F("[PompeHomog] OFF"));
}
}
void moveMotor(int targetAngle) {
if (motorBusy) return;
int delta = targetAngle - PosNow;
if (delta == 0) return;
PosNow2 = delta;
Serial.print(F("moveMotor -> target=")); Serial.print(targetAngle);
Serial.print(F(" PosNow=")); Serial.print(PosNow);
Serial.print(F(" delta=")); Serial.println(delta);
signalSequence(delta > 0); // true = ouverture (monte), false = fermeture (descend)
motorBusy = true;
digitalWrite(Sleep, HIGH);
delay(50);
long targetStep = anglesToSteps(targetAngle);
long currentStep = stepper.currentPosition();
if (targetAngle == Close) {
// --- Mode recherche STOP1 ---
seekingStop1 = true;
ignoreStopSwitch = false;
long direction = (currentStep < anglesToSteps(180)) ? 1 : -1;
stepper.moveTo(anglesToSteps(180) + direction * anglesToSteps(360));
unsigned long startTime = millis();
while (true) {
if (quickVoltage() < VOLTAGE_LOW_THRESHOLD) {
delay(25);
if (quickVoltage() < VOLTAGE_LOW_THRESHOLD) {
stepper.stop(); digitalWrite(Sleep, LOW); motorBusy = false;
panneActive = true; panneWasActive = true;
display.ssd1306_command(SSD1306_DISPLAYOFF);
lcd.noBacklight(); tft.writecommand(0x28);
digitalWrite(RedLed, HIGH);
Serial.println(F("[PANNE pendant mouvement]"));
return;
}
}
if (!ignoreStopSwitch && digitalRead(STOP_SWITCH_PIN1) == LOW) {
stepper.stop();
stepper.setCurrentPosition(anglesToSteps(180));
PosNow = 180; seekingStop1 = false; ignoreStopSwitch = true;
Serial.println(F("STOP1 -> PosNow=180"));
break;
}
if (millis() - startTime > 60000) {
stepper.stop(); seekingStop1 = false;
ignoreStopSwitch = true; calibrationPending = true;
Serial.println(F("Timeout STOP1"));
break;
}
stepper.run();
}
} else {
// --- Mode normal ---
ignoreStopSwitch = true;
stepper.moveTo(targetStep);
while (stepper.distanceToGo() != 0) {
if (quickVoltage() < VOLTAGE_LOW_THRESHOLD) {
delay(25);
if (quickVoltage() < VOLTAGE_LOW_THRESHOLD) {
stepper.stop(); digitalWrite(Sleep, LOW); motorBusy = false;
panneActive = true; panneWasActive = true;
display.ssd1306_command(SSD1306_DISPLAYOFF);
lcd.noBacklight(); tft.writecommand(0x28);
digitalWrite(RedLed, HIGH);
Serial.println(F("[PANNE pendant mouvement]"));
return;
}
}
stepper.run();
}
PosNow = (int)round((stepper.currentPosition() * 360.0) /
(StepsPerRevolution * currentMicrostep * 216.0));
Serial.print(F("moveMotor FIN PosNow=")); Serial.println(PosNow);
}
digitalWrite(Sleep, LOW);
motorBusy = false;
}
//*************************************************************
//
// SECTION 4 — LOGIQUE SYSTEME
// (surveillance tension, alarmes, conditions temperature)
//
//*************************************************************
void gererPanne() {
if (in_voltage < VOLTAGE_LOW_THRESHOLD && !panneActive) {
panneActive = true; panneWasActive = true;
servoOff(); digitalWrite(Sleep, LOW);
display.ssd1306_command(SSD1306_DISPLAYOFF);
lcd.noBacklight(); tft.writecommand(0x28);
digitalWrite(RedLed, HIGH);
Serial.println(F("[PANNE] Tension basse"));
}
if (in_voltage >= VOLTAGE_HIGH_THRESHOLD && panneActive) {
panneActive = false;
display.ssd1306_command(SSD1306_DISPLAYON);
lcd.backlight(); tft.writecommand(0x29);
digitalWrite(RedLed, LOW);
forceFullRefresh = true; lcd.clear();
Serial.println(F("[RETOUR] Tension retablie"));
// Recalibration stepper
if (digitalRead(STOP_SWITCH_PIN1) == LOW) {
PosNow = 180; stepper.setCurrentPosition(anglesToSteps(180));
calibrationPending = false;
Serial.println(F("[RETOUR] Switch detecte -> PosNow=180"));
} else {
calibrationPending = true;
Serial.println(F("[RETOUR] Switch absent -> moveMotor Close"));
moveMotor(Close);
}
// Relancer servo si on est dans la plage horaire alarme
DateTime now = getNow();
int nowMinutes = now.hour() * 60 + now.minute();
int onMinutes = alarmOnHour * 60 + alarmOnMinute;
int offMinutes = alarmOffHour * 60 + alarmOffMinute;
bool dansPlage = false;
if (onMinutes < offMinutes)
dansPlage = (nowMinutes >= onMinutes && nowMinutes < offMinutes);
else if (onMinutes > offMinutes)
dansPlage = (nowMinutes >= onMinutes || nowMinutes < offMinutes);
if (dansPlage && !(alarmOnHour == 0 && alarmOnMinute == 0)) {
alarmOn = true;
servoOn();
Serial.println(F("[RETOUR] Dans plage alarme -> servo ON"));
}
}
void checkServoAlarm(DateTime now) {
if (panneActive || calibrationPending || motorBusy) return;
static int lastAlarmOnMinute = -1;
static int lastAlarmOffMinute = -1;
// Alarme ON
if (now.hour() == alarmOnHour && now.minute() == alarmOnMinute
&& now.minute() != lastAlarmOnMinute
&& !(alarmOnHour == 0 && alarmOnMinute == 0)) {
beep(); alarmOn = true; lastAlarmOnMinute = now.minute();
servoOn();
Serial.println(F("[Alarme ON] Servo ON"));
}
if (now.minute() != alarmOnMinute) lastAlarmOnMinute = -1;
// Alarme OFF
if (now.hour() == alarmOffHour && now.minute() == alarmOffMinute
&& now.minute() != lastAlarmOffMinute
&& !(alarmOffHour == 0 && alarmOffMinute == 0)) {
beep(); lastAlarmOffMinute = now.minute();
servoOff();
alarmOn = false;
Serial.println(F("[Alarme OFF] Servo OFF"));
}
if (now.minute() != alarmOffMinute) lastAlarmOffMinute = -1;
}
void conditions(DateTime now) {
if (panneActive && CE > FrostPoint - MargeFroid) return;
if (calibrationPending) return;
// Garde : bloquer si alarme dans moins de 20 secondes
int nowSecs = now.hour() * 3600 + now.minute() * 60 + now.second();
int onSecs = alarmOnHour * 3600 + alarmOnMinute * 60;
int offSecs = alarmOffHour * 3600 + alarmOffMinute * 60;
if (abs(nowSecs - onSecs) < 20) return;
if (abs(nowSecs - offSecs) < 20) return;
OHeatPoint = VA + OverHeat;
Check = 0;
// Cond1 : CE <= VA → fermer valve
if (Valid1 > 0 && CE <= VA && CE > FrostPoint) {
Serial.println(F("Cond1: CE<=VA -> Close"));
if (PosNow != Close) {
lcd.setCursor(0, 2); lcd.print(F("Dir:")); lcd.print(Close);
moveMotor(Close);
}
Check = 1;
Done1=1; Done2=0; Done3=0; Done4=0;
Valid1=0; Valid2=1; Valid3=1; Valid4=1;
}
// Cond2 : CE > VA+Marge → ouvrir valve
if (Valid2 > 0 && CE > (VA + Marge) && CE < (OHeatPoint + Marge)) {
Serial.println(F("Cond2: CE>VA+Marge -> Open"));
if (PosNow != Open) {
lcd.setCursor(0, 2); lcd.print(F("Dir:")); lcd.print(Open);
moveMotor(Open);
}
Check = 1;
Done1=0; Done2=1; Done3=0; Done4=0;
Valid1=1; Valid2=0; Valid3=1; Valid4=1;
}
// Cond3 : surchauffe → OpenPlus
if (Valid3 > 0 && CE > (OHeatPoint + Marge)) {
Serial.println(F("Cond3: surchauffe -> OpenPlus"));
if (PosNow != OpenPlus) moveMotor(OpenPlus);
Check = 1;
Done1=0; Done2=0; Done3=1; Done4=0;
Valid1=1; Valid2=1; Valid3=0; Valid4=1;
}
// Cond4 : antigel → FrostSafe + pompe
if (Valid4 > 0 && CE <= FrostPoint - MargeFroid) {
Serial.println(F("Cond4: antigel -> FrostSafe + servoOn"));
if (PosNow != FrostSafe) moveMotor(FrostSafe);
if (!servoAt0) servoOn();
lcd.setCursor(0, 2); lcd.print(F("Dir:")); lcd.print(FrostSafe);
Check = 1;
Done1=0; Done2=0; Done3=0; Done4=1;
Valid1=1; Valid2=1; Valid3=1; Valid4=0;
}
// Remontée après antigel
if (Done4 == 1 && CE > FrostPoint) {
Serial.println(F("Cond4: Remontee temperature"));
if (!alarmOn) {
servoOff();
if (PosNow != Close) moveMotor(Close);
Serial.println(F("Cond4: alarmOff -> servo Off + Close"));
}
Done4=0; Valid1=1; Valid2=1; Valid3=1; Valid4=1;
}
if (Check == 0) {
Serial.print(F("Pas de changement. Pos=")); Serial.println(PosNow);
}
}
void updateDailyMax(DateTime now) {
float currentTemp = Mov_avg[2];
static bool firstRun = true;
if (firstRun) {
if (maxTodayDay == 0) {
maxTodayDay = now.day(); maxTodayMonth = now.month(); maxToday = currentTemp;
}
firstRun = false;
}
// Changement de jour
if (now.day() != maxTodayDay || now.month() != maxTodayMonth) {
if (maxToday > maxPast) {
maxPast = maxToday; maxPastDay = maxTodayDay; maxPastMonth = maxTodayMonth;
}
maxToday = currentTemp; maxTodayDay = now.day(); maxTodayMonth = now.month();
forceFullRefresh = true;
saveEEPROM();
}
if (currentTemp > maxToday) { maxToday = currentTemp; saveEEPROM(); }
if (currentTemp > maxYear) {
maxYear = currentTemp; maxYearDay = now.day(); maxYearMonth = now.month();
saveEEPROM();
}
}
//*************************************************************
//
// SECTION 5 — EEPROM
// (persistance des données)
//
//*************************************************************
void saveEEPROM() {
EEPROM.put(ADDR_VALID, (byte)EEPROM_VALID_MARKER);
EEPROM.put(ADDR_MAX_TODAY, maxToday);
EEPROM.put(ADDR_TODAY_DAY, maxTodayDay);
EEPROM.put(ADDR_TODAY_MONTH, maxTodayMonth);
EEPROM.put(ADDR_MAX_PAST, maxPast);
EEPROM.put(ADDR_PAST_DAY, maxPastDay);
EEPROM.put(ADDR_PAST_MONTH, maxPastMonth);
EEPROM.put(ADDR_MAX_YEAR, maxYear);
EEPROM.put(ADDR_YEAR_DAY, maxYearDay);
EEPROM.put(ADDR_YEAR_MONTH, maxYearMonth);
EEPROM.put(ADDR_ALARM_ON_H, alarmOnHour);
EEPROM.put(ADDR_ALARM_ON_M, alarmOnMinute);
EEPROM.put(ADDR_ALARM_OFF_H, alarmOffHour);
EEPROM.put(ADDR_ALARM_OFF_M, alarmOffMinute);
EEPROM.put(ADDR_FROST_POINT, FrostPoint);
EEPROM.put(ADDR_MARGE_FROID, MargeFroid);
EEPROM.put(ADDR_TEMP_SERVO, TempServo);
EEPROM.put(ADDR_MOTOR_SPEED, motorSpeed);
EEPROM.put(ADDR_MOTOR_ACCEL, motorAccel);
EEPROM.put(ADDR_MICROSTEP, currentMicrostep);
EEPROM.put(ADDR_FROST_CIRCULE, FrostCircule);
}
void loadEEPROM() {
byte marker;
EEPROM.get(ADDR_VALID, marker);
if (marker != EEPROM_VALID_MARKER) {
Serial.println(F("EEPROM vierge — valeurs par defaut")); return;
}
EEPROM.get(ADDR_MAX_TODAY, maxToday);
EEPROM.get(ADDR_TODAY_DAY, maxTodayDay);
EEPROM.get(ADDR_TODAY_MONTH, maxTodayMonth);
EEPROM.get(ADDR_MAX_PAST, maxPast);
EEPROM.get(ADDR_PAST_DAY, maxPastDay);
EEPROM.get(ADDR_PAST_MONTH, maxPastMonth);
EEPROM.get(ADDR_MAX_YEAR, maxYear);
EEPROM.get(ADDR_YEAR_DAY, maxYearDay);
EEPROM.get(ADDR_YEAR_MONTH, maxYearMonth);
EEPROM.get(ADDR_ALARM_ON_H, alarmOnHour);
EEPROM.get(ADDR_ALARM_ON_M, alarmOnMinute);
EEPROM.get(ADDR_ALARM_OFF_H, alarmOffHour);
EEPROM.get(ADDR_ALARM_OFF_M, alarmOffMinute);
EEPROM.get(ADDR_FROST_POINT, FrostPoint);
EEPROM.get(ADDR_MARGE_FROID, MargeFroid);
EEPROM.get(ADDR_TEMP_SERVO, TempServo);
EEPROM.get(ADDR_MOTOR_SPEED, motorSpeed);
EEPROM.get(ADDR_MOTOR_ACCEL, motorAccel);
EEPROM.get(ADDR_MICROSTEP, currentMicrostep);
EEPROM.get(ADDR_FROST_CIRCULE, FrostCircule);
Serial.println(F("EEPROM chargee"));
}
//*************************************************************
//
// SECTION 6 — AFFICHAGE
// (LCD, OLED, TFT)
//
//*************************************************************
void updateLCD() {
lcd.setCursor(0, 0); lcd.print(F("CE ")); lcd.print(CE, 1);
lcd.setCursor(9, 0); lcd.print(F("EC ")); lcd.print(EC, 1);
lcd.setCursor(0, 1); lcd.print(F("VA ")); lcd.print(VA, 1);
lcd.setCursor(9, 1); lcd.print(F("P:")); lcd.print(servoAt0 ? F("ON ") : F("OFF"));
lcd.setCursor(0, 2); lcd.print(F("Pos:")); lcd.print(PosNow); lcd.print(F(" "));
if (rtcFound) {
DateTime now = rtc.now();
lcd.setCursor(0, 3);
char buf[20];
sprintf(buf, "%02d:%02d:%02d %02d/%02d",
now.hour(), now.minute(), now.second(), now.month(), now.day());
lcd.print(buf);
}
}
void updateOLED() {
display.clearDisplay();
display.setTextSize(Size);
display.setTextColor(WHITE);
display.setCursor(0, 0);
display.print(DallasTemperature::toFahrenheit(VA), 1);
display.display();
}
void drawTempBox(const char *label, float value, float Tmin, float Tmax,
int x, int y, int w, int h) {
static float lastVal[3] = {-999, -999, -999};
static uint16_t lastCol[3] = {0, 0, 0};
int idx = (label[0] == 'V') ? 0 : (label[1] == 'E' ? 1 : 2);
uint16_t bgColor, fgColor;
if (value <= Tmin) { bgColor = TFT_BLUE; fgColor = TFT_WHITE; }
else if (value >= Tmax) { bgColor = TFT_RED; fgColor = TFT_WHITE; }
else { bgColor = TFT_YELLOW; fgColor = TFT_BLACK; }
if (bgColor != lastCol[idx] || forceFullRefresh) {
tft.fillRect(x, y, w, h, bgColor);
lastCol[idx] = bgColor; lastVal[idx] = -999;
}
if (fabs(value - lastVal[idx]) >= 0.1 || forceFullRefresh) {
tft.setTextSize(4); tft.setTextColor(fgColor, bgColor);
tft.setCursor(x + 5, y + (h / 2) - 12);
tft.print(label); tft.print(":");
char buf[10]; dtostrf(value, 4, 1, buf); tft.print(buf);
lastVal[idx] = value;
}
}
void drawValue(const char* label, float value, float Tmin, float Tmax,
int x, int y, int fieldWidth, int decimals) {
char buf[20];
dtostrf(value, fieldWidth, decimals, buf); // décimale toujours alignée
uint16_t fgColor = TFT_WHITE, bgColor = TFT_BLACK;
if (!isnan(Tmin) && !isnan(Tmax)) {
if (value >= Tmax) { bgColor = TFT_RED; fgColor = TFT_WHITE; }
else if (value <= Tmin) { bgColor = TFT_BLUE; fgColor = TFT_WHITE; }
else { bgColor = TFT_YELLOW; fgColor = TFT_BLACK; }
} else { tft.setTextColor(fgColor, bgColor);
}
int textPixelWidth = fieldWidth * 20; // 20 px/caractère avec size=4
int textPixelHeight = 40; // hauteur ligne avec size=4
int maxHeight = 235 - y;
//tft.fillRect(x, y, textPixelWidth + 60, min(textPixelHeight, maxHeight), TFT_BLACK);
tft.setCursor(x, y); tft.print(label);
//tft.print(F(" ")); // Espace constant entre label et valeur
tft.print(buf);
}
void print2DigitsTFT(int number) {
if (number < 10) tft.print('0');
tft.print(number);
}
void drawPumpIndicator(bool isOn) {
uint16_t color = isOn ? TFT_GREEN : TFT_RED;
tft.fillRect(214, 280, 32, 32, color);
}
void updateAlarmDisplay(int x, int y, const char* label,
const char* time, uint16_t color,
bool isActive, char* lastTime, bool& lastActive) {
bool timeChanged = strcmp(time, lastTime) != 0;
bool stateChanged = isActive != lastActive;
if (!timeChanged && !stateChanged && !forceFullRefresh) return;
if (isActive) {
tft.fillRect(x, y, 220, 35, color); tft.setTextColor(TFT_BLACK, color);
} else {
tft.fillRect(x, y, 220, 35, TFT_BLACK); tft.setTextColor(color, TFT_BLACK);
}
tft.setCursor(x, y); tft.print(label); tft.print(time);
strcpy(lastTime, time); lastActive = isActive;
}
void updateDisplayTFT(DateTime now) {
static bool firstRun = true;
static int lastPos = -999, lastDep = -999;
static float lastVol = -1.0, lastDiff = -1000, lastAug = -1000;
static float lMT = -1.0, lMP = -1.0, lMY = -1.0;
static int lMTd = -1, lMPd = -1, lMYd = -1;
static int lastS = -1, lastD = -1;
if (forceFullRefresh || firstRun) {
tft.fillScreen(TFT_BLACK);
tft.drawLine(173, 0, 173, 235, TFT_WHITE);
tft.drawLine(0, 235, 480, 235, TFT_WHITE);
tft.drawLine(385, 0, 385, 80, TFT_WHITE);
tft.drawLine(385, 80, 480, 80, TFT_WHITE);
firstRun = false;
lastPos=-999; lastDep=-999; lastVol=-1.0;
lastDiff=-1000; lastAug=-1000;
lMT=-1.0; lMP=-1.0; lMY=-1.0;
lMTd=-1; lMPd=-1; lMYd=-1;
lastS=-1; lastD=-1;
// Version — affiché une seule fois
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setCursor(391, 0); tft.print(F("V."));
tft.setCursor(391, 40); tft.print(Version);
tft.setTextSize(4);
}
// Températures
float T0 = Mov_avg[0], T1 = Mov_avg[1], T2 = Mov_avg[2];
float Tmin = min(T0, min(T1, T2));
float Tmax = max(T0, max(T1, T2));
drawTempBox("VA", T2, Tmin, Tmax, 0, 0, 170, 30);
drawTempBox("CE", T0, Tmin, Tmax, 0, 40, 170, 30);
drawTempBox("EC", T1, Tmin, Tmax, 0, 80, 170, 30);
// Valeurs gauche
if (PosNow != lastPos || forceFullRefresh) {
tft.setTextColor(PosNow == 180 ? TFT_GREEN : TFT_WHITE, TFT_BLACK);
drawValue("Pos", PosNow, NAN, NAN, 0, 120, 4, 0); lastPos = PosNow;
}
if (PosNow2 != lastDep || forceFullRefresh) {
tft.setTextColor(TFT_WHITE, TFT_BLACK);
drawValue("Dep", PosNow2, NAN, NAN, 0, 160, 4, 0); lastDep = PosNow2;
}
if (abs(in_voltage - lastVol) > 0.05 || forceFullRefresh) {
tft.setTextColor(TFT_WHITE, TFT_BLACK);
drawValue("V ", in_voltage, NAN, NAN, 0, 200, 4, 1); lastVol = in_voltage;
}
// Valeurs droite
float diff = round((T0 - T2) * 10.0) / 10.0;
if (abs(diff - lastDiff) > 0.1 || forceFullRefresh) {
tft.setTextColor(TFT_WHITE, TFT_BLACK);
drawValue("Dif", diff, NAN, NAN, 180, 3, 5, 1); lastDiff = diff;
}
float aug = round((T1 - T2) * 10.0) / 10.0;
if (abs(aug - lastAug) > 0.1 || forceFullRefresh) {
tft.setTextColor(TFT_WHITE, TFT_BLACK);
drawValue("Aug", aug, NAN, NAN, 180, 43, 5, 1); lastAug = aug;
}
// Historique max
tft.setTextColor(TFT_WHITE, TFT_BLACK); tft.setTextSize(4);
// Aujourd'hui
if (abs(maxToday - lMT) > 0.1 || maxTodayDay != lMTd || forceFullRefresh) {
tft.setCursor(180, 83); tft.print(F(" "));
tft.setCursor(180, 83);
print2DigitsTFT(maxTodayMonth); tft.print("/"); print2DigitsTFT(maxTodayDay);
tft.print(" "); tft.print(maxToday, 1);
lMT = maxToday; lMTd = maxTodayDay;
}
// Jours Passés
if (abs(maxPast - lMP) > 0.1 || maxPastDay != lMPd || forceFullRefresh) {
tft.setCursor(180, 120); tft.print(F(" "));
tft.setCursor(180, 120);
if (maxPastDay == 0) tft.print(F("--/-- n/a"));
else { print2DigitsTFT(maxPastMonth); tft.print("/"); print2DigitsTFT(maxPastDay);
tft.print(" "); tft.print(maxPast, 1); }
lMP = maxPast; lMPd = maxPastDay;
}
// Année
if (abs(maxYear - lMY) > 0.1 || maxYearDay != lMYd || forceFullRefresh) {
tft.setCursor(180, 160); tft.print(F(" "));
tft.setCursor(180, 160);
if (maxYearDay == 0) tft.print(F("--/-- n/a"));
else { print2DigitsTFT(maxYearMonth); tft.print("/"); print2DigitsTFT(maxYearDay);
tft.print(" "); tft.print(maxYear, 1); }
lMY = maxYear; lMYd = maxYearDay;
}
// Pression / Temp RTC
static unsigned long lastSwitch = 0; static bool showPression = true;
if (millis() - lastSwitch > 3000 || forceFullRefresh) {
showPression = !showPression; lastSwitch = millis();
tft.setCursor(180, 200); tft.setTextColor(TFT_WHITE, TFT_BLACK);
if (showPression) { tft.print(F("Press ")); tft.print(lirePressionFiltre(), 1); }
else { tft.print(F("Temp ")); tft.print(rtc.getTemperature(), 1); }
}
// Date / Heure
if (now.day() != lastD || forceFullRefresh) {
tft.setTextColor(TFT_WHITE, TFT_BLACK); tft.setCursor(0, 240);
char dStr[11]; sprintf(dStr, "%04d/%02d/%02d", now.year(), now.month(), now.day());
tft.print(dStr); lastD = now.day();
}
if (now.second() != lastS || forceFullRefresh) {
tft.setTextColor(TFT_WHITE, TFT_BLACK); tft.setCursor(0, 280);
char tStr[9]; sprintf(tStr, "%02d:%02d:%02d", now.hour(), now.minute(), now.second());
tft.print(tStr); drawPumpIndicator(servoAt0); lastS = now.second();
}
}
void updateDateTimeAndAlarms(DateTime now) {
char alarmOnTime[6], alarmOffTime[6];
sprintf(alarmOnTime, "%02d:%02d", alarmOnHour, alarmOnMinute);
sprintf(alarmOffTime, "%02d:%02d", alarmOffHour, alarmOffMinute);
bool alarmOnActive = alarmOn && !(alarmOnHour == 0 && alarmOnMinute == 0);
bool alarmOffActive = !alarmOn && !(alarmOffHour == 0 && alarmOffMinute == 0);
updateAlarmDisplay(260, 240, "On :", alarmOnTime, TFT_GREEN, alarmOnActive, lastOnTime, lastOnActive);
updateAlarmDisplay(260, 280, "Off:", alarmOffTime, TFT_RED, alarmOffActive, lastOffTime, lastOffActive);
}
//*************************************************************
//
// SECTION 7 — MENUS ET SAISIE
// (interaction utilisateur)
//
//*************************************************************
int getNumberInput(int numDigits) {
int value = 0;
for (int i = 0; i < numDigits; i++) {
char key = 0;
while (!key) key = keypad.getKey();// Attendre que l'utilisateur saisisse un chiffre
if (isdigit(key)) { // Valider que la touche saisie est un chiffre
value = value * 10 + (key - '0'); beep(); lcd.print(key); // Afficher le chiffre saisi
} else { lcd.print(F("?")); beepLow(); }
}
return value;
}
int getValidatedInput(const __FlashStringHelper* prompt, int minV, int maxV, int numDigits) {
int value; bool valid = false;
while (!valid) {
lcd.setCursor(0,2); lcd.print(F(" "));
lcd.setCursor(0,2); lcd.print(prompt);
value = getNumberInput(numDigits);
if (value >= minV && value <= maxV) valid = true;
else { lcd.clear(); lcdMessage(F("Valeur Invalide!"), F("Reessayer...")); beepLow(); delay(dl*3); }
}
return value;
}
void checkRTCSetup(DateTime now) {
if (rtcFound) {
if (now.year() == 2000) { // RTC pas encore réglé
lcd.clear();
lcd.print(F("Regler Date/Heure"));
// tft.fillScreen(TFT_BLACK);
// tft.setTextSize(4);
// tft.setTextColor(TFT_WHITE);
// tft.setCursor(0, 0);
// tft.println(F("Regler Date/Heure"));
beepLow();
delay(dl*3);
setDateTime(); // Demande réglage manuel
}
} else { // Pas de RTC → mode simulation, rien à régler
}
}
void setDateTime() {
lcd.clear(); lcd.print(F("Config. Date/Heure")); delay(dl*3);
int y, m, d, hh, mm, ss;
lcd.setCursor(14,3); lcd.print(F("----"));
y = getValidatedInput(F("Entrer Annee: "), 2000, 2100, 4);
lcd.setCursor(14,3); lcd.print(F("-- "));
m = getValidatedInput(F("Entrer Mois: "), 1, 12, 2);
lcd.setCursor(14,3); lcd.print(F("-- "));
d = getValidatedInput(F("Entrer Jour: "), 1, 31, 2);
lcd.setCursor(14,3); lcd.print(F("-- "));
hh = getValidatedInput(F("Entrer Heure: "), 0, 23, 2);
lcd.setCursor(14,3); lcd.print(F("-- "));
mm = getValidatedInput(F("Entrer Min.: "), 0, 59, 2);
lcd.setCursor(14,3); lcd.print(F("-- "));
ss = getValidatedInput(F("Entrer Sec.: "), 0, 59, 2);
rtc.adjust(DateTime(y, m, d, hh, mm, ss));
TimeSet = 1;
lcdMessage(F("Date/Heure OK !"));
}
void setAlarmTimes() {
lcd.clear();
DateTime now = getNow();
char buffer[12];
lcd.setCursor(0,0); lcd.print(F("Heure:"));
sprintf(buffer, "%02d:%02d:%02d", now.hour(), now.minute(), now.second()); lcd.print(buffer);
lcd.setCursor(11,0); lcd.print(F("On :")); sprintf(buffer,"%02d:%02d",alarmOnHour,alarmOnMinute); lcd.print(buffer);
lcd.setCursor(11,1); lcd.print(F("Off:")); sprintf(buffer,"%02d:%02d",alarmOffHour,alarmOffMinute); lcd.print(buffer);
delay(dl*2);
// Saisie ON
lcd.setCursor(0,2); lcd.print(F("Regler On Hr :--"));
alarmOnHour = getValidatedInput(F("Regler On Hr :"), 0, 23, 2);
lcd.setCursor(0,2); lcd.print(F("Regler On Min :--"));
alarmOnMinute = getValidatedInput(F("Regler On Min :"), 0, 59, 2);
lcd.setCursor(15,0); sprintf(buffer,"%02d:%02d",alarmOnHour,alarmOnMinute); lcd.print(buffer);
lcd.setCursor(0,2); lcd.print(F(" "));
lcd.setCursor(0,3); lcd.print(F(" "));
// Saisie OFF
lcd.setCursor(0,2); lcd.print(F("Regle Off Hr :--"));
alarmOffHour = getValidatedInput(F("Regle Off Hr :"), 0, 23, 2);
lcd.setCursor(0,2); lcd.print(F("Regle Off Min :--"));
alarmOffMinute = getValidatedInput(F("Regle Off Min :"), 0, 59, 2);
lcd.setCursor(15,1); sprintf(buffer,"%02d:%02d",alarmOffHour,alarmOffMinute); lcd.print(buffer);
lcdMessage(F("Alarmes Reglees !")); delay(dl * 3);
now = getNow();
int nowMinutes = now.hour() * 60 + now.minute();
int alarmOnMinutes = alarmOnHour * 60 + alarmOnMinute;
int alarmOffMinutes= alarmOffHour * 60 + alarmOffMinute;
bool dansPlageMaintenant = false;
if (alarmOnMinutes < alarmOffMinutes)
dansPlageMaintenant = (nowMinutes >= alarmOnMinutes && nowMinutes < alarmOffMinutes);
else if (alarmOnMinutes > alarmOffMinutes)
dansPlageMaintenant = (nowMinutes >= alarmOnMinutes || nowMinutes < alarmOffMinutes);
if (dansPlageMaintenant) {
lcd.clear(); lcd.print(F("Heure ON passee"));
lcd.setCursor(0,1); lcd.print(F("Activation immediate"));
beep(); delay(dl*2); alarmOn = true; servoOn();
} else if (alarmOn) {
lcd.clear(); lcd.print(F("Hors plage horaire"));
lcd.setCursor(0,1); lcd.print(F("Pompe arretee"));
delay(dl*2); alarmOn = false; servoOff();
} else {
lcd.clear(); lcd.print(F("Actif a "));
char buf[6]; sprintf(buf,"%02d:%02d",alarmOnHour,alarmOnMinute); lcd.print(buf);
delay(dl*2);
}
saveEEPROM();
}
void Fermeture() {
while (true) {
lcd.clear();
lcd.print(F("Fermer Moteur/Valve"));
lcd.setCursor(0,1); lcd.print(F("1:Fermer."));
if (!servoAt0) { lcd.setCursor(9,2); lcd.print(F("mot -")); }
if (!servoAt0 && PosNow > OpenPlus) { lcd.setCursor(15,2); lcd.print(F("val -")); }
lcd.setCursor(0,2); lcd.print(F("2:Ouvrir."));
if (PosNow <= OpenPlus) { lcd.setCursor(15,1); lcd.print(F("val o")); }
if (servoAt0 && PosNow <= OpenPlus) { lcd.setCursor(9,1); lcd.print(F("mot o")); }
lcd.setCursor(0,3); lcd.print(F("3:Quitter."));
char response = waitForKeyWithTimeout(WaitX);
if (response == 0 || response == '3') {
if (PosNow > OpenPlus || PosNow < SemiOff) {
lcd.clear(); lcd.print(F("Valve doit Ouvrir"));
lcd.setCursor(0,1); lcd.print(F("pour Quitter"));
beepLow(); delay(dl*3); continue;
}
forceFullRefresh = true;
return;
}
if (response == '1') {
if (servoAt0 && (PosNow > OpenPlus)) {
lcdMessage(F("Operation"), F("impossible.")); beepLow(); delay(dl*3);
} else {
beep(); lcd.clear(); lcd.print(F("Fermeture en cours."));
if (servoAt0) { servoOff(); manualOverride = true;
lcd.setCursor(0,1); lcd.print(F("Moteur Ferme.")); delay(dl*2); }
moveMotor(totalOff);
lcd.setCursor(0,2); lcd.print(F("Valve Ferme.")); beep(); delay(dl*3);
}
} else if (response == '2') {
if (servoAt0) {
lcdMessage(F("Operation"), F("impossible.")); beepLow(); delay(dl*3);
} else {
lcd.clear(); lcd.print(F("1:Valve et Pompe"));
lcd.setCursor(0,1); lcd.print(F("2:Ouvrir Valve seul"));
char valpom = waitForKeyWithTimeout(WaitX);
if (valpom == '1') {
moveMotor(Open);
if (PosNow <= OpenPlus) { servoOn(); manualOverride = false; }
lcdMessage(F("Valve/Pompe OK"));
} else if (valpom == '2') {
moveMotor(Open); lcdMessage(F("Valve Ouverte"));
}
}
}
}
}
void Manuel() {
lcd.clear();
lcd.print(F("Backwash/Rince"));
if (servoAt0 == true) {
lcd.setCursor(0, 1);
lcd.println(F("Moteur on. Fermer?"));
}
if (servoAt0 == false) {
lcd.setCursor(0, 1);
lcd.print(F("Continuer?"));
}
lcd.setCursor(0, 2);
lcd.print(F("1 = oui 0 = non"));
char response = 3;
while (response != '1' && response != '0') {
response = keypad.getKey(); // Attendre une réponse
if (response == '1') {
BwR = 0;
manualOverride = true;
bwprintcycles();
lcd.clear();
lcd.print(F("Cycle Backwash fait"));
lcd.setCursor(0, 1);
lcd.print(F("Continuer Rince"));
lcd.setCursor(0, 2);
lcd.print(F("Appuyer * "));
BwR = 1;
char rinse = 0;
while (rinse != '*') {
rinse = keypad.getKey(); // Attendre une réponse
if (rinse == '*') {
bwprintcycles();
lcd.setCursor(0,1);
lcd.print(F("Cycles termine "));delay(dl*3);
lcd.setCursor(0,2);
lcd.print(F("Rallumer Moteur? "));
lcd.setCursor(0,3);
lcd.print(F("1 = oui 0 = non"));
beep();
char start = 0;
while (start != '1' && start != '0') {
start = keypad.getKey(); // Attendre une réponse
if (start == '1') {
servoOn(); // ouvrir — synchrone, servo garanti en position
manualOverride = false;
drawPumpIndicator(servoAt0); // mise à jour TFT immédiate
DPRINT(F("Servo ON: override desactive"));
}
if (start == '0') {
// Servo reste OFF — manualOverride reste true
drawPumpIndicator(servoAt0); // mise à jour TFT immédiate
DPRINT(F("Servo Off: override maintenu"));
}
}
}
}
}
else if (response == '0') {
beep();
lcd.clear();
lcd.print(F("Aucune Action."));
delay(dl*2);
lcd.clear();
manualOverride = false;
}
}
}
void bwprintcycles() {
lcd.clear(); beep();
servoOff(); // Fermer le servo (180°) — synchrone, attend la fin du mouvement
lcd.print(F("Moteur Ferme."));
lcd.setCursor(0, 1);
lcd.print(F("Tourner Poignee"));
lcd.setCursor(0, 2);
if (BwR == 0) {
lcd.print(F("a Backwash et"));
} else {
lcd.print(F("a Rince et"));
}
lcd.setCursor(0, 3);
lcd.print(F("Appuyer *"));
char check = 0;
while (check != '*') {
check = keypad.getKey();
if (check == '*') {
fonctionAvecChrono();
}
}
}
void fonctionAvecChrono() {
lcd.clear();
if (BwR == 0) {
lcd.print(F("Backwash..."));
} else if (BwR == 1) {
lcd.print(F("Rince..."));
}
servoOn(); // Ouvrir — synchrone, servo en position avant de continuer
unsigned long startTime = millis();
while (true) {
unsigned long elapsedTime = millis() - startTime;
if (elapsedTime > 600000) { // Sécurité 10 minutes
servoOff();
moveMotor(Close);
break;
}
int seconds = (elapsedTime / 1000) % 60;
int minutes = (elapsedTime / 60000);
lcd.setCursor(0, 1);
lcd.print(F("Appuyer * terminer"));
lcd.setCursor(0, 2);
lcd.print(F("Temps : "));
lcd.print(minutes);
lcd.print(F(":"));
if (seconds < 10) lcd.print(F("0"));
lcd.print(seconds);
char key = keypad.getKey();
if (key == '*') {
servoOff(); // Fermer — synchrone
beep();
break;
}
}
}
void OvRd() {
lcd.setCursor(17, 0);
lcd.print(F("Ovr"));
lcd.setCursor(17, 1);
lcd.print(F("ON"));
tft.setTextColor(TFT_YELLOW, TFT_BLACK);
tft.setCursor(17*23, 0);
tft.print(F("Ovr"));
tft.setCursor(17*23, 40);
tft.print(F("ON"));
}
void OvRdClr() {// Sinon affichage normal
if (alertActive) {
// Rien,Priorité à l'alerte
} else {
lcd.setCursor(17, 0);
lcd.print(F(" "));
lcd.setCursor(17, 1);
lcd.print(F(" "));
tft.setCursor(17*23, 0);
tft.print(F(" "));
tft.setCursor(17*23, 40);
tft.print(F(" "));
}
}
void handleOverrideDisable() { // Fonction pour gérer la désactivation de l'override via la touche '*'
lcd.clear();
lcd.print(F("Annuler Derive"));
lcd.setCursor(0, 1);
lcd.print(F("1: Immediat"));
lcd.setCursor(0, 2);
lcd.print(F("2: Next Alarme"));
char response = waitForKeyWithTimeout(WaitX);
if (response == '1') {
manualOverride = false; // Désactiver l'override immédiatement
disableOverrideNextAlarm = false;
lcdMessage(F("Derive Desactive."));
} else if (response == '2') {
disableOverrideNextAlarm = true; // Désactiver l'override à la prochaine alarme
lcdMessage(F("Derive"), F("temporaire actif"));
}
else if (response == 0) { // Cas du Timeout (aucune touche pressée)
lcdMessage(F("Temps ecoule..."), F("Retour menu"));
}
}
void displayintermittent(bool isActive) {
if (isActive) {
unsigned long currentMillis = millis();
// Si l'intervalle est dépassé, on change l'état de l'affichage
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
displayState = !displayState; // Basculer l'état d'affichage
if (displayState) {
// Code pour afficher vos données
OvRd();
} else {
// Code pour désactiver l'affichage (écran noir, effacement)
OvRdClr();
}
}
} else {
// Si l'affichage est inactif, assurez-vous de l'éteindre
if (displayState) {
displayState = false;
OvRdClr();// Ajoutez ici le code pour désactiver complètement l'affichage
}
}
}
void alertWithLeds() {// Fonction d'alerte avec LEDs
for (int i = 1; i < 4; i++) {
beep();
digitalWrite(BlueLed, HIGH); delay(250); digitalWrite(BlueLed, LOW);
digitalWrite(YellowLed, HIGH); delay(250); digitalWrite(YellowLed, LOW);
digitalWrite(RedLed, HIGH); delay(250); digitalWrite(RedLed, LOW);
}
}
void blinkLed(int ledPin, int times, int delayMs) {
for (int i = 0; i < times; i++) {
digitalWrite(ledPin, HIGH);
delay(delayMs);
digitalWrite(ledPin, LOW);
delay(delayMs);
}
}
void clignoteLed(int pin, int nb, int tempo) {
// Fonction simple de clignotement
for (int i = 0; i < nb; i++) {
digitalWrite(pin, HIGH);
delay(tempo);
digitalWrite(pin, LOW);
delay(tempo);
}
}
void gestionErreurStopSwitch() {
if (!stopSwitchError) return;
static unsigned long lastBlink = 0;
static bool ledState = false;
DPRINTLN(F("Gestion Erreur Stopswitch"));
// Clignotement LED rouge toutes les 300ms
if (millis() - lastBlink >= 300) {
ledState = !ledState;
digitalWrite(RedLed, ledState);
lastBlink = millis();
}
// Lecture clavier
char key = keypad.getKey();
if (key == '0') {
stopSwitchError = false;
digitalWrite(RedLed, LOW);
lcd.setCursor(0, 2);
lcd.print(F("Erreur acquittee "));
delay(1000);
updateLCDNow = true;
Serial.println(F("Erreur stop switch acquittée"));
}
}
void showMenu1() {
returnToMain = false;
lcd.clear(); lcd.print(F("1:Regler Heure"));
lcd.setCursor(0,1); lcd.print(F("2:Regler Alarmes"));
lcd.setCursor(0,2); lcd.print(F("3:Reset Led"));
lcd.setCursor(0,3); lcd.print(F("4:Fermeture Valve"));
char option = waitForKeyWithTimeout(WaitX);
if (option == '#') { currentMenu = 2; return; }
if (option == 0) { currentMenu = 0; returnToMain = true; return; }
if (option == '1') { setDateTime(); lcd.clear();
lcdMessage(F("Heure mise a jour")); delay(dl * 3); lcd.clear();}
else if (option == '2') { setAlarmTimes(); }
else if (option == '3') { beep(); lcd.clear(); lcd.print(F("Reset Led Rouge"));
delay(dl*2); lcd.clear(); lcd.print(F("Appuyer '*' (3s)"));
char key = waitForKeyWithTimeout(3000);
if (key == '*') {
stopSwitchError = false; digitalWrite(RedLed, LOW); beep(); lcd.clear();
lcdMessage(F("Erreur efface")); delay(dl*2); lcd.clear(); return;
}
}
else if (option == '4') { Fermeture(); }
saveEEPROM();
}
void showMenu2() {
returnToMain = false;
lcd.clear(); lcd.print(F("1:Ferme Moteur BW"));
lcd.setCursor(0, 1); lcd.print(F("2:Ferme Valve/Mot"));
lcd.setCursor(0, 2); lcd.print(F("3:Annule Derive"));
lcd.setCursor(0, 3);
char option = waitForKeyWithTimeout(WaitX);
if (option == '#') { currentMenu = 3; return; } // avancer — priorité absolue
if (option == 0) { currentMenu = 0; returnToMain = true; return; } // timeout
if (option == '1') { Manuel(); }
else if (option == '2') { Fermeture(); }
else if (option == '3') { handleOverrideDisable(); }
else if (option == '4') { }
}
void showMenu3() {
lcd.clear();
lcd.print(F("1:FrostPoint=")); lcd.print(FrostPoint, 1);
lcd.setCursor(0, 1);
lcd.print(F("2:MargeFroid=")); lcd.print(MargeFroid, 1);
lcd.setCursor(0, 2);
lcd.print(F("3:TempServo=")); lcd.print(TempServo, 1);
lcd.setCursor(0, 3);
lcd.print(F("4:FrostCircule=")); lcd.print(FrostCircule, 1);
char option = waitForKeyWithTimeout(WaitX);
if (option == '#') { currentMenu = 4; return; } // avancer — priorité absolue
if (option == 0) { currentMenu = 0; returnToMain = true; return; } // timeout
if (option == '1') {
lcd.clear();
lcd.print(F("FrostPoint:"));
lcd.setCursor(0, 1);
lcd.print(F("Recommande="));
lcd.print(FrostPoint_DEFAULT, 1);
lcd.setCursor(0, 2);
lcd.print(F("Actuel="));
lcd.print(FrostPoint, 1);
lcd.setCursor(0, 3);
lcd.print(F("Entree(0-20):"));
FrostPoint = getValidatedInput(F(""), 0, 20, 2);
// Affiche confirmation sans effacer l'écran
lcd.setCursor(0, 3);
lcd.print(F("FrostPoint="));
lcd.print(FrostPoint, 1);
delay(dl*3);
return;
}
else if (option == '2') {
lcd.clear();
lcd.print(F("MargeFroid:"));
lcd.setCursor(0, 1);
lcd.print(F("Recommande="));
lcd.print(MargeFroid_DEFAULT, 1);
lcd.setCursor(0, 2);
lcd.print(F("Actuel="));
lcd.print(MargeFroid, 1);
// Saisie sur la 4e ligne
lcd.setCursor(0, 3);
lcd.print(F("Entree(0-10): "));
MargeFroid = getValidatedInput(F(""), 0, 10, 1);
lcd.setCursor(0, 3);
lcd.print(F("MargeFroid="));
lcd.print(MargeFroid, 1);
delay(dl*3);
return;
}
else if (option == '3') { // Nouveau : TempServo
lcd.clear();
lcd.print(F("TempServo:"));
lcd.setCursor(0, 1);
lcd.print(F("Recommande="));
lcd.print(TempServo_DEFAULT, 1); // reste indépendant
lcd.setCursor(0, 2);
lcd.print(F("Actuel="));
lcd.print(TempServo, 1); // Variable globale à créer
lcd.setCursor(0, 3);
lcd.print(F("Entree(F>Frost):")); // Exemple plage
float val = getValidatedInput(F(""), FrostPoint, 50, 1); // >= FrostPoint
TempServo = val; // sauvegarde
lcd.setCursor(0, 3);
lcd.print(F("TempServo="));
lcd.print(TempServo, 1);
delay(dl*3);
return;
}
else if (option == '4') { // Nouveau : TempServo
lcd.clear();
lcd.print(F("FrostCircule:"));
lcd.setCursor(0, 1);
lcd.print(F("Recommande="));
lcd.print(FrostCircule_DEFAULT, 1);
lcd.setCursor(0, 2);
lcd.print(F("Actuel="));
lcd.print(FrostCircule, 1);
lcd.setCursor(0, 3);
lcd.print(F("Entree(0-20):"));
FrostCircule = getValidatedInput(F(""), 0, 20, 2);
lcd.setCursor(0, 3);
lcd.print(F("FrostCircule="));
lcd.print(FrostCircule, 1);
delay(dl * 3);
saveEEPROM();
return;
}
}
void showMenu4() {
returnToMain = false;
lcd.clear();
lcd.print(F("1:Vitesse=")); lcd.print(stepper.maxSpeed(), 0);
lcd.setCursor(0, 1);
lcd.print(F("2:Accel=")); lcd.print(stepper.acceleration(), 0);
lcd.setCursor(0, 2);
lcd.print(F("3:Microstep=")); lcd.print(currentMicrostep);
char option = waitForKeyWithTimeout(WaitX);
if (option == '#') { currentMenu = 5; return; } // avancer — priorité absolue
if (option == 0) { currentMenu = 0; returnToMain = true; return; } // timeout
if (option == '1') { // Réglage vitesse max
beep();
lcd.clear();
lcd.print(F("Vitesse actuelle="));
lcd.print(stepper.maxSpeed(), 0);
lcd.setCursor(0, 1);
lcd.print(F("Nouvelle (50-3000):"));
int newSpeed = getValidatedInput(F(""), 50, 3000, 4);
stepper.setMaxSpeed(newSpeed);
lcd.clear();
lcd.print(F("Vitesse="));
lcd.print(newSpeed);
delay(dl*3);
return;
}
else if (option == '2') { // Réglage accélération
lcd.clear();
lcd.print(F("Accel actuelle="));
lcd.print(stepper.acceleration(), 0);
lcd.setCursor(0, 1);
lcd.print(F("Nouvelle (10-2000):"));
int newAccel = getValidatedInput(F(""), 10, 2000, 4);
stepper.setAcceleration(newAccel);
lcd.clear();
lcd.print(F("Accel="));
lcd.print(newAccel);
delay(dl*3);
return;
}
else if (option == '3') { // Réglage microstep
lcd.clear();
lcd.print(F("Microstep actuel="));
lcd.print(currentMicrostep);
lcd.setCursor(0, 1);
lcd.print(F("1,2,4,8,16 valides:"));
int newMicro = getValidatedInput(F(""), 1, 16, 2);
if (newMicro == 1 || newMicro == 2 || newMicro == 4 || newMicro == 8 || newMicro == 16) {
updateMicrosteps(newMicro); // --- Application complète avec compensation
lcd.clear();
lcd.print(F("Microstep="));
lcd.print(currentMicrostep);
lcd.setCursor(0, 1);
lcd.print(F("Facteur corr: "));
lcd.print(String(currentMicrostep) + "/16");
lcd.setCursor(0, 2);
lcd.print(F("Vit: "));
lcd.print(stepper.maxSpeed(), 0);
lcd.setCursor(0, 3);
lcd.print(F("Acc: "));
lcd.print(stepper.acceleration(), 0);
delay(dl*5);
} else {
lcd.clear();
lcd.print(F("Valeur non supportee"));
beepLow();
delay(dl*3);
}
return;
}
}
void showMenu5() {
returnToMain = false;
lcd.clear();
lcd.print(F("1:Reset tout EEPROM"));
lcd.setCursor(0, 1); lcd.print(F("2:Reset Historique"));
lcd.setCursor(0, 2); lcd.print(F("3:Reset Seuils"));
lcd.setCursor(0, 3); lcd.print(F("4:Reset Moteurs"));
char option = waitForKeyWithTimeout(WaitX);
if (option == '#') { currentMenu = 1; return; } // boucle vers Menu1
if (option == 0) { currentMenu = 0; returnToMain = true; return; }
if (option == '1') {
lcd.clear(); lcd.print(F("Confirmer reset?"));
lcd.setCursor(0,1); lcd.print(F("* = Oui"));
char conf = waitForKeyWithTimeout(5000);
if (conf == '*') {
// Historique
maxToday = -99; maxTodayDay = 0; maxTodayMonth = 0;
maxPast = -1000; maxPastDay = 0; maxPastMonth = 0;
maxYear = -1000; maxYearDay = 0; maxYearMonth = 0;
// Seuils
FrostPoint = FrostPoint_DEFAULT;
MargeFroid = MargeFroid_DEFAULT;
TempServo = TempServo_DEFAULT;
FrostCircule = FrostCircule_DEFAULT;
// Moteurs
motorSpeed = 2900; motorAccel = 15000; currentMicrostep = 4;
stepper.setMaxSpeed(motorSpeed);
stepper.setAcceleration(motorAccel);
setMicrostep(currentMicrostep);
saveEEPROM();
lcdMessage(F("Reset complet OK"));
} else { lcdMessage(F("Annule")); }
}
else if (option == '2') {
maxToday = -99; maxTodayDay = 0; maxTodayMonth = 0;
maxPast = -1000; maxPastDay = 0; maxPastMonth = 0;
maxYear = -1000; maxYearDay = 0; maxYearMonth = 0;
saveEEPROM();
lcdMessage(F("Historique efface"));
}
else if (option == '3') {
FrostPoint = FrostPoint_DEFAULT;
MargeFroid = MargeFroid_DEFAULT;
TempServo = TempServo_DEFAULT;
FrostCircule = FrostCircule_DEFAULT;
saveEEPROM();
lcdMessage(F("Seuils reinitialises"));
}
else if (option == '4') {
motorSpeed = 2900; motorAccel = 15000; currentMicrostep = 4;
stepper.setMaxSpeed(motorSpeed);
stepper.setAcceleration(motorAccel);
setMicrostep(currentMicrostep);
saveEEPROM();
lcdMessage(F("Moteurs reinit."));
}
// pas de return ici — le while rappelle showMenu5()
}
void checkKeypad() {
char key = keypad.getKey();
if (key == '#') {
beep(); currentMenu = 1;
while (currentMenu > 0) {
if (currentMenu == 1) showMenu1();
else if (currentMenu == 2) showMenu2();
else if (currentMenu == 3) showMenu3();
else if (currentMenu == 4) showMenu4();
else if (currentMenu == 5) showMenu5();
}
forceFullRefresh = true; lcd.clear();
}
}
//*************************************************************
//
// SECTION 8 — RTC
//
//*************************************************************
DateTime getNow() {
if (rtcFound) return rtc.now();
static unsigned long startMillis = millis();
unsigned long e = (millis() - startMillis) / 1000;
return DateTime(2026, 1, 1 + e/86400, (e/3600)%24, (e/60)%60, e%60);
}
//*************************************************************
//
// SECTION 9 — SETUP ET LOOP
//
//*************************************************************
void setup() {
Serial.begin(115200);
Wire.begin(); Wire.setClock(100000);
pinMode(StepPin, OUTPUT); pinMode(DirPin, OUTPUT); pinMode(Sleep, OUTPUT);
pinMode(MS1, OUTPUT); pinMode(MS2, OUTPUT); pinMode(MS3, OUTPUT);
pinMode(RedLed, OUTPUT); pinMode(YellowLed, OUTPUT);
pinMode(STOP_SWITCH_PIN1, INPUT_PULLUP); pinMode(POMPE_HOMOG_PIN, OUTPUT);
digitalWrite(Sleep, LOW); analogWrite(POMPE_HOMOG_PIN, 0);
stepper.setPinsInverted(true, false, false);
setMicrostep(currentMicrostep);
stepper.setMaxSpeed(motorSpeed);
stepper.setAcceleration(motorAccel);
Serial.print(F("Version ")); Serial.println(Version);
// OLED
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C))
Serial.println(F("OLED non detecte"));
else {
display.clearDisplay(); display.setTextSize(Size);
display.setTextColor(WHITE); display.setCursor(0,0);
display.print(F("00.0")); display.display();
}
// LCD
lcd.init(); lcd.backlight(); lcd.print(F("Init..."));
// TFT
tft.init(); tft.setRotation(1); tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_WHITE); tft.setTextSize(4);
tft.print(F("Init TFT..."));
// RTC
if (rtcFound = rtc.begin()) {
if (rtc.lostPower()) rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
Serial.println(F("RTC OK"));
} else {
Serial.println(F("RTC absent"));
lcd.setCursor(0,1); lcd.print(F("RTC absent"));
}
// EEPROM
loadEEPROM();
// DS18B20
sensors.begin(); sensors.setWaitForConversion(true);
int found = sensors.getDeviceCount();
Serial.print(F("Capteurs: ")); Serial.println(found);
for (int i = 0; i < numSensors; i++) {
if (sensors.getAddress(sensorAddresses[i], i)) {
sensors.setResolution(sensorAddresses[i], precision);
Serial.print(F("Capteur ")); Serial.print(i); Serial.print(F(" : "));
printAddress(sensorAddresses[i]);
}
}
// Servo
myServo.attach(ServoPin); myServo.write(Off);
servoAt0 = false; delay(500); myServo.detach();
Serial.println(F("Servo Off (180)"));
// Position stepper
if (digitalRead(STOP_SWITCH_PIN1) == LOW) {
PosNow = 180; Serial.println(F("STOP1 detecte -> PosNow=180"));
} else {
PosNow = 180; Serial.println(F("STOP1 absent -> PosNow=180 par defaut"));
}
stepper.setCurrentPosition(anglesToSteps(PosNow));
readSensors();
lcd.clear();
Serial.println(F("Setup termine."));
}
void loop() {
DateTime now = getNow();
readVoltage();
gererPanne();
if (panneActive) return;
readSensors();
CE = Mov_avg[0]; EC = Mov_avg[1]; VA = Mov_avg[2];
checkServoAlarm(now);
checkKeypad();
updateOLED();
updateLCD();
updateDailyMax(now);
updateDisplayTFT(now);
updateDateTimeAndAlarms(now);
forceFullRefresh = false;
conditions(now);
controlPompeHomog()
Serial.print(F("CE=")); Serial.print(CE);
Serial.print(F(" EC=")); Serial.print(EC);
Serial.print(F(" VA=")); Serial.print(VA);
Serial.print(F(" V=")); Serial.print(in_voltage, 1);
Serial.print(F(" Pos=")); Serial.print(PosNow);
Serial.print(F(" Srv=")); Serial.println(servoAt0 ? F("ON") : F("OFF"));
}// BwR