// ============================================================================
// PROJET CHAUFFE-EAU SPA — Sketch_V15_1 (basé sur Sketch_V15)
// Ajouts : Envoi de l'état des actionneurs (relais + buzzer) sur le port série
// Corrections : Joystick stabilisé (edge-trigger), anti-rebond, bip feedback,
// animation de démarrage. Menus/EEPROM/Capteurs/RTC conservés.
// Carte : Arduino Mega 2560
// ============================================================================
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <RTClib.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <EEPROM.h>
#include <string.h>
// -----------------------------------------------------------------------------
// I. DÉFINITIONS MATÉRIELLES
// -----------------------------------------------------------------------------
// 1) Relais et Buzzer
const int PIN_RELAIS_GRANULES = 11;
const int PIN_RELAIS_VIBRATEUR = 3;
const int PIN_BUZZER = 12;
// 2) Joystick
const int PIN_JOY_VERT = A1;
const int PIN_JOY_HORZ = A0;
const int PIN_JOY_SEL = 2;
const int SEUIL_PRESSION = LOW; // bouton en INPUT_PULLUP
// Seuils analogiques et délais (stabilisation)
const int SEUIL_CENTRE_BAS = 300;
const int SEUIL_CENTRE_HAUT = 700;
const unsigned long DELAI_TRANSITION_JOY = 150; // ms (physique)
const unsigned long DELAI_TRANSITION_SERIE = 120; // ms (virtuel Processing)
// 3) LCD (3× 20x4)
LiquidCrystal_I2C lcd_Menus(0x27, 20, 4); // Menus
LiquidCrystal_I2C lcd_Rapports(0x26, 20, 4); // Rapports
LiquidCrystal_I2C lcd_Infos(0x20, 20, 4); // Infos capteurs
// 4) RTC et capteurs
RTC_DS1307 rtc;
OneWire oneWire1(A2), oneWire2(A3), oneWire3(A4), oneWire4(A5);
DallasTemperature sensors1(&oneWire1), sensors2(&oneWire2), sensors3(&oneWire3), sensors4(&oneWire4);
// -----------------------------------------------------------------------------
// II. EEPROM (adresses conservées)
// -----------------------------------------------------------------------------
const int EEPROM_ADDR_GRANULES_TON = 0;
const int EEPROM_ADDR_GRANULES_TOFF = 4;
const int EEPROM_ADDR_VIBREUR_TON = 8;
const int EEPROM_ADDR_VIBREUR_TOFF = 12;
const int EEPROM_ADDR_MARKER = 16;
const byte EEPROM_MARKER_VALUE = 0xAA;
const int EEPROM_ADDR_TEMP_UNIT = 17; // 0=C, 1=F
const int EEPROM_ADDR_GRANULE_QUALITY= 18; // 0..2
const int EEPROM_ADDR_CONSO_PERIOD = 19; // 0..4
// -----------------------------------------------------------------------------
// III. VARIABLES GLOBALES & LIBELLÉS (inchangés fonctionnellement)
// -----------------------------------------------------------------------------
// Buffers d’affichage (RAM) + cadence d’envoi série (vers Processing)
char lcd_Menus_Buffer[4][21];
char lcd_Rapports_Buffer[4][21];
char lcd_Infos_Buffer[4][21];
unsigned long dernierEnvoiSerie = 0;
const long INTERVALLE_ENVOI_SERIE = 100; // 10 Hz
// Mesures/RTC/Conso
float temp_1=0, temp_2=0, temp_3=0, temp_4=0;
DateTime maintenant;
float consoGranules = 0.0f;
float consoEnergie = 0.0f;
float puissanceMoyenneKW = 0.0f;
const float BASE_CONSO_GRANULES_PER_DAY = 64800.00f; // g/jour (exemple)
const float BASE_CONSO_ENERGIE_PER_DAY = 311.04f; // kWh/jour (exemple)
// Timers non-bloquants
unsigned long derniereLectureTemp = 0; // DS18B20 scheduling
const long INTERVALLE_LECTURE_TEMP = 5000; // ms
unsigned long derniereLectureRTC = 0;
const long INTERVALLE_LECTURE_RTC = 1000; // ms
unsigned long dernierMouvement = 0; // (retiré de l’usage direct, remplacé par edge)
const long TEMPS_CONVERSION_MAX = 800; // ms DS18B20
unsigned long tempsDebutConversion = 0;
// Minuteries système
long Granules_Time_ON = 60;
long Granules_Time_OFF = 1;
long Vibreur_Time_ON = 5;
long Vibreur_Time_OFF = 10;
// Paramètres utilisateur
byte tempUnit = 0; // 0=C, 1=F
byte granuleQuality = 0; // 0..2
byte consoPeriodMode = 0; // 0..4
// Libellés
const char* TEMP_UNIT_LABELS_FULL[] = {"CELSIUS","FAHRENHEIT"};
const char* TEMP_UNIT_LABELS_SHORT[] = {"C","F"};
const char* QUALITE_GRANULES_LABELS[]= {"Supreme","Moyen","Commun"};
const char* CONSO_PERIOD_LABELS[] = {"par minute","Par heure","Par jour","Par semaine","Par mois"};
const int NOMBRE_QUALITE_GRANULES = 3;
const int NOMBRE_OPTIONS_PERIOD = 5;
// Menus
char menuOptions[4][20] = {
"1. System OFF",
"2. Test Actionneurs",
"3. Minuteurs",
"4. Parametres"
};
const int NOMBRE_OPTIONS_MENU = 4;
int menuSelection = 0;
const char* PARAMETRES_LABELS[] = {
"1. Date/Heure",
"2. Unite Temp.",
"3. Qualite Granules",
"4. Conso/Unit",
"5. Reglage T.Cibles"
};
const int NOMBRE_OPTIONS_PARAMETRES = 5;
int parametresSelection = 0;
int consoUnitSelection = 0;
const char* minuteursOptions_V8[] = {"1. Gr T-ON","2. Gr T-OFF","3. Vib T-ON","4. Vib T-OFF"};
const int NOMBRE_OPTIONS_MINUT = 4;
int minuteursSelection = 0;
char actionneursOptions[3][20] = {
"1. Granules : OFF",
"2. Vibreur : OFF",
"3. Buzzer : OFF"
};
const int NOMBRE_OPTIONS_ACTIONNEURS = 3;
int actionneursSelection = 0;
// États
bool isSystemRunning = false;
bool granules_isON = false;
bool vibreur_isON = false;
unsigned long granules_lastSwitchTime = 0;
unsigned long vibreur_lastSwitchTime = 0;
bool test_granules_state=false;
bool test_vibreur_state=false;
bool test_buzzer_state=false;
// Rapports
byte rapportMode = 0;
byte ancienRapportMode = 255;
// RTC editor
enum RTCField { RTC_YEAR, RTC_MONTH, RTC_DAY, RTC_HOUR, RTC_MINUTE, RTC_SECOND, RTC_FIELD_COUNT };
RTCField rtcSelection = RTC_YEAR;
// Navigation générale
enum Direction { NONE, UP, DOWN, LEFT, RIGHT, SELECT };
// App State
enum AppState {
MAIN_MENU,
SUB_MENU_RTC,
SUB_MENU_TCIBLES,
SUB_MENU_ACTIONNEURS,
SUB_MENU_MINUTEURS,
EDITING_MINUTERIE,
SUB_MENU_PARAMETRES,
EDITING_PARAMETRE,
SUB_MENU_CONSO_UNIT,
EDITING_CONSO_UNIT
};
AppState currentState = MAIN_MENU;
// -----------------------------------------------------------------------------
// IV. UTILITAIRES D’AFFICHAGE
// -----------------------------------------------------------------------------
void clearBuffer(char buffer[4][21]) {
for (int i=0;i<4;i++){
memset(buffer[i],' ',20);
buffer[i][20]='\0';
}
}
void syncBufferToPhysicalLCD(LiquidCrystal_I2C& lcd, char buffer[4][21]) {
lcd.clear();
for (int i=0;i<4;i++){
lcd.setCursor(0,i);
char tmp[21];
strncpy(tmp, buffer[i], 20);
tmp[20]='\0';
lcd.print(tmp);
}
}
void init_lcd(LiquidCrystal_I2C& lcd, char buffer[4][21], const char* message, uint8_t addr){
lcd.init();
lcd.backlight();
clearBuffer(buffer);
snprintf(buffer[0],21,"Init OK - Adresse: ");
snprintf(buffer[1],21,"0x%02X ", addr);
snprintf(buffer[2],21,"%-20s", message);
snprintf(buffer[3],21," ");
syncBufferToPhysicalLCD(lcd, buffer);
delay(400);
lcd.clear();
clearBuffer(buffer);
}
void printCenteredBuffer(char buffer[4][21], int line, const char* msg){
int len = (int)strlen(msg);
int pos = (20-len)/2;
if(pos<0) pos=0;
memset(buffer[line],' ',20);
strncpy(buffer[line]+pos, msg, (size_t)min(len, 20-pos));
buffer[line][20]='\0';
}
// Effets de scroll sur Rapports (avec redessin)
void miseAJourAfficheRapports();
void scrollLeftRapports(){
for(int i=0;i<4;i++){
lcd_Rapports.scrollDisplayLeft();
delay(75);
}
miseAJourAfficheRapports();
}
void scrollRightRapports(){
for(int i=0;i<4;i++){
lcd_Rapports.scrollDisplayRight();
delay(75);
}
miseAJourAfficheRapports();
}
// -----------------------------------------------------------------------------
// V. AFFICHAGES SPÉCIFIQUES
// -----------------------------------------------------------------------------
void miseAJourAfficheRapports(){
if (currentState==SUB_MENU_RTC || currentState==SUB_MENU_TCIBLES ||
currentState==SUB_MENU_CONSO_UNIT || currentState==EDITING_CONSO_UNIT) return;
if (rapportMode!=ancienRapportMode || millis()-derniereLectureRTC>=INTERVALLE_LECTURE_RTC){
if (millis()-derniereLectureRTC>=INTERVALLE_LECTURE_RTC){
derniereLectureRTC=millis();
maintenant=rtc.now();
}
if (rapportMode!=ancienRapportMode){
ancienRapportMode=rapportMode;
lcd_Rapports.clear();
}
clearBuffer(lcd_Rapports_Buffer);
char fb[12];
if (rapportMode==0){
char dt[21];
sprintf(dt, "%02d/%02d/%d %02d:%02d",
maintenant.day(), maintenant.month(), maintenant.year(),
maintenant.hour(), maintenant.minute());
printCenteredBuffer(lcd_Rapports_Buffer,0,dt);
dtostrf(consoGranules,7,2,fb);
snprintf(lcd_Rapports_Buffer[1],21,"Conso: %s g/%s", fb, CONSO_PERIOD_LABELS[consoPeriodMode]);
dtostrf(consoEnergie ,7,4,fb);
snprintf(lcd_Rapports_Buffer[2],21,"Energie: %s kWh/%s", fb, CONSO_PERIOD_LABELS[consoPeriodMode]);
dtostrf(puissanceMoyenneKW,5,2,fb);
snprintf(lcd_Rapports_Buffer[3],21,"P. Moyenne: %s kW", fb);
} else if (rapportMode==1){
snprintf(lcd_Rapports_Buffer[0],21,"System:%s | Qualite:%s",
isSystemRunning?" ON":"OFF",
QUALITE_GRANULES_LABELS[granuleQuality]);
snprintf(lcd_Rapports_Buffer[1],21,"Gr: %s (%ld/%ld s)",
granules_isON?"ON":"OFF", Granules_Time_ON, Granules_Time_OFF);
snprintf(lcd_Rapports_Buffer[2],21,"Vib: %s (%ld/%ld s)",
vibreur_isON?"ON":"OFF", Vibreur_Time_ON, Vibreur_Time_OFF);
snprintf(lcd_Rapports_Buffer[3],21," <L/R: Nav> ");
} else {
printCenteredBuffer(lcd_Rapports_Buffer,0,"Infos System");
snprintf(lcd_Rapports_Buffer[1],21,"Qualite: %s",
QUALITE_GRANULES_LABELS[granuleQuality]);
snprintf(lcd_Rapports_Buffer[2],21,"Conso: %s",
CONSO_PERIOD_LABELS[consoPeriodMode]);
snprintf(lcd_Rapports_Buffer[3],21,"Unite: %s",
TEMP_UNIT_LABELS_SHORT[tempUnit]);
}
syncBufferToPhysicalLCD(lcd_Rapports, lcd_Rapports_Buffer);
}
}
void miseAJourAfficheInfos(){
unsigned long now = millis();
if (now-derniereLectureTemp >= INTERVALLE_LECTURE_TEMP){
sensors1.requestTemperatures();
sensors2.requestTemperatures();
sensors3.requestTemperatures();
sensors4.requestTemperatures();
tempsDebutConversion = now;
derniereLectureTemp = now;
return;
}
if (now - tempsDebutConversion >= (unsigned long)TEMPS_CONVERSION_MAX){
tempsDebutConversion = now + TEMPS_CONVERSION_MAX; // verrou jusqu’à la prochaine demande
float t1c=sensors1.getTempCByIndex(0),
t2c=sensors2.getTempCByIndex(0),
t3c=sensors3.getTempCByIndex(0),
t4c=sensors4.getTempCByIndex(0);
float t1=(tempUnit==1)? (t1c*1.8f+32.0f):t1c;
float t2=(tempUnit==1)? (t2c*1.8f+32.0f):t2c;
float t3=(tempUnit==1)? (t3c*1.8f+32.0f):t3c;
float t4=(tempUnit==1)? (t4c*1.8f+32.0f):t4c;
const char* u = (tempUnit==1)?"F":"C";
clearBuffer(lcd_Infos_Buffer);
snprintf(lcd_Infos_Buffer[0],21,"T1: %.1f%s", t1,u);
snprintf(lcd_Infos_Buffer[1],21,"T2: %.1f%s", t2,u);
snprintf(lcd_Infos_Buffer[2],21,"T3: %.1f%s", t3,u);
snprintf(lcd_Infos_Buffer[3],21,"T4: %.1f%s", t4,u);
syncBufferToPhysicalLCD(lcd_Infos, lcd_Infos_Buffer);
}
}
void afficherMenuPrincipal(){
clearBuffer(lcd_Menus_Buffer);
int debut = (menuSelection>2)? menuSelection-2:0;
for(int i=0;i<4;i++){
int idx = debut+i;
if(idx<NOMBRE_OPTIONS_MENU){
char p=(idx==menuSelection)?'>':' ';
snprintf(lcd_Menus_Buffer[i],21,"%c %s",p,menuOptions[idx]);
}
}
syncBufferToPhysicalLCD(lcd_Menus, lcd_Menus_Buffer);
}
void afficherReglagesMinuteurs(){
clearBuffer(lcd_Menus_Buffer);
for(int i=0;i<NOMBRE_OPTIONS_MINUT;i++){
char p = (i==minuteursSelection)?
((currentState==EDITING_MINUTERIE)?'E':'>') : ' ';
long v = (i==0)?Granules_Time_ON :
(i==1)?Granules_Time_OFF :
(i==2)?Vibreur_Time_ON :
Vibreur_Time_OFF;
char vb[6];
sprintf(vb, "%04ld", v);
snprintf(lcd_Menus_Buffer[i],21, "%c %s : %s",
p, minuteursOptions_V8[i], vb);
}
syncBufferToPhysicalLCD(lcd_Menus, lcd_Menus_Buffer);
}
void afficherTestactionneurs(){
clearBuffer(lcd_Menus_Buffer);
sprintf(actionneursOptions[0], "1. Granules : %s", test_granules_state?"ON ":"OFF");
sprintf(actionneursOptions[1], "2. Vibreur : %s", test_vibreur_state?"ON ":"OFF");
sprintf(actionneursOptions[2], "3. Buzzer : %s", test_buzzer_state?"ON ":"OFF");
for(int i=0;i<NOMBRE_OPTIONS_ACTIONNEURS;i++){
char p=(i==actionneursSelection)?'>':' ';
snprintf(lcd_Menus_Buffer[i],21, "%c %s", p, actionneursOptions[i]);
}
snprintf(lcd_Menus_Buffer[3],21, "Etat System: %s",
isSystemRunning?"ON":"OFF");
syncBufferToPhysicalLCD(lcd_Menus, lcd_Menus_Buffer);
}
void afficherReglagesRTC(){
clearBuffer(lcd_Menus_Buffer);
DateTime n = rtc.now();
snprintf(lcd_Menus_Buffer[0],21,
"%c%04d%c/%c%02d%c/%c%02d%c",
(rtcSelection==RTC_YEAR)?'[':' ', n.year(), (rtcSelection==RTC_YEAR)?']':' ',
(rtcSelection==RTC_MONTH)?'[':' ',n.month(), (rtcSelection==RTC_MONTH)?']':' ',
(rtcSelection==RTC_DAY)?'[':' ', n.day(), (rtcSelection==RTC_DAY)?']':' '
);
snprintf(lcd_Menus_Buffer[1],21,
" %c%02d%c:%c%02d%c:%c%02d%c",
(rtcSelection==RTC_HOUR)?'[':' ', n.hour(), (rtcSelection==RTC_HOUR)?']':' ',
(rtcSelection==RTC_MINUTE)?'[':' ', n.minute(), (rtcSelection==RTC_MINUTE)?']':' ',
(rtcSelection==RTC_SECOND)?'[':' ', n.second(), (rtcSelection==RTC_SECOND)?']':' '
);
snprintf(lcd_Menus_Buffer[3],21,
" <LEFT:Retour> <RIGHT:Suivant>");
syncBufferToPhysicalLCD(lcd_Menus, lcd_Menus_Buffer);
}
void afficherParametres(){
clearBuffer(lcd_Menus_Buffer);
bool isEditing = (currentState==EDITING_PARAMETRE);
int debut = (parametresSelection>2)? parametresSelection-2:0;
for(int i=0;i<4;i++){
int idx = debut+i;
if(idx<NOMBRE_OPTIONS_PARAMETRES){
char p=(idx==parametresSelection)?'>':' ';
if (idx==0 || idx==3 || idx==4){
snprintf(lcd_Menus_Buffer[i],21, "%c%s", p, PARAMETRES_LABELS[idx]);
} else {
const char* v = (idx==1)? TEMP_UNIT_LABELS_FULL[tempUnit]
: QUALITE_GRANULES_LABELS[granuleQuality];
const char* lab = (idx==1)? "Unite:" : "Qualite:";
if (idx==parametresSelection && isEditing)
snprintf(lcd_Menus_Buffer[i],21,
"%c%d. %s [%s]", p, idx+1, lab, v);
else
snprintf(lcd_Menus_Buffer[i],21,
"%c%d. %s %s", p, idx+1, lab, v);
}
}
}
syncBufferToPhysicalLCD(lcd_Menus, lcd_Menus_Buffer);
}
void afficherReglagesConsoUnit(){
clearBuffer(lcd_Menus_Buffer);
printCenteredBuffer(lcd_Menus_Buffer,0,"CONSO/UNIT");
char p = (currentState==EDITING_CONSO_UNIT)?'E':'>';
snprintf(lcd_Menus_Buffer[1],21, "%c Periode: %s",
p, CONSO_PERIOD_LABELS[consoPeriodMode]);
snprintf(lcd_Menus_Buffer[3],21, " <LEFT: Retour> ");
syncBufferToPhysicalLCD(lcd_Menus, lcd_Menus_Buffer);
}
void afficherReglagesTCibles(){
clearBuffer(lcd_Menus_Buffer);
printCenteredBuffer(lcd_Menus_Buffer,0,"REGLAGE T.CIBLES");
printCenteredBuffer(lcd_Menus_Buffer,1,"(A IMPLEMENTER)");
snprintf(lcd_Menus_Buffer[3],21, " <LEFT: Retour> ");
syncBufferToPhysicalLCD(lcd_Menus, lcd_Menus_Buffer);
}
// -----------------------------------------------------------------------------
// VI. COMMUNICATION SÉRIE VERS PROCESSING
// -----------------------------------------------------------------------------
void envoyerContenuLCDs(){
if (!Serial) return;
Serial.print("[M]");
for(int i=0;i<4;i++){
Serial.print(lcd_Menus_Buffer[i]);
if(i<3) Serial.print("|");
}
Serial.print("[R]");
for(int i=0;i<4;i++){
Serial.print(lcd_Rapports_Buffer[i]);
if(i<3) Serial.print("|");
}
Serial.print("[I]");
for(int i=0;i<4;i++){
Serial.print(lcd_Infos_Buffer[i]);
if(i<3) Serial.print("|");
}
Serial.println();
}
// NOUVEAU : état des actionneurs & système vers Processing
void envoyerEtatActionneurs() {
// On lit directement l'état électrique des broches de sortie,
// pour refléter exactement ce qui est envoyé aux relais/buzzer.
int etatVibreur = digitalRead(PIN_RELAIS_VIBRATEUR);
int etatGranules = digitalRead(PIN_RELAIS_GRANULES);
int etatBuzzer = digitalRead(PIN_BUZZER);
Serial.println("<STATE>");
Serial.print("VIBREUR="); Serial.println(etatVibreur);
Serial.print("GRANULES="); Serial.println(etatGranules);
Serial.print("BUZZER="); Serial.println(etatBuzzer);
Serial.print("SYSTEM="); Serial.println(isSystemRunning ? 1 : 0);
Serial.println("</STATE>");
}
// -----------------------------------------------------------------------------
// VII. LOGIQUE (CONSOMMATION / CYCLES / MENUS)
// -----------------------------------------------------------------------------
void calculerConsommation(){
float cycle = (float)Granules_Time_ON + (float)Granules_Time_OFF;
float duty = (cycle>0)? ((float)Granules_Time_ON / cycle) : 0.0f;
float qf = (granuleQuality==0)?1.0f:
(granuleQuality==1)?0.95f:0.90f;
float dG = BASE_CONSO_GRANULES_PER_DAY * duty;
float dE = (BASE_CONSO_ENERGIE_PER_DAY*qf) * duty;
puissanceMoyenneKW = dE / 24.0f;
float pf=1.0f;
switch(consoPeriodMode){
case 0: pf=1.0f/(24.0f*60.0f); break; // minute
case 1: pf=1.0f/24.0f; break; // heure
case 2: pf=1.0f; break; // jour
case 3: pf=7.0f; break; // semaine
case 4: pf=30.4167f; break; // mois moyen
}
consoGranules = dG*pf;
consoEnergie = dE*pf;
}
void gererAffichageRapports(){ miseAJourAfficheRapports(); }
void gererLecturesTemperature(){ miseAJourAfficheInfos(); }
Direction lireJoystickAnalogique(){
if (digitalRead(PIN_JOY_SEL)==SEUIL_PRESSION) return SELECT;
int v=analogRead(PIN_JOY_VERT);
int h=analogRead(PIN_JOY_HORZ);
if (v<SEUIL_CENTRE_BAS) return UP;
if (v>SEUIL_CENTRE_HAUT) return DOWN;
if (h<SEUIL_CENTRE_BAS) return RIGHT;
if (h>SEUIL_CENTRE_HAUT) return LEFT;
return NONE;
}
void gererCycleGranules(){
if(!isSystemRunning) return;
unsigned long now=millis();
unsigned long interval;
if (granules_isON){
interval=(unsigned long)Granules_Time_ON*1000UL;
if(now-granules_lastSwitchTime>=interval){
digitalWrite(PIN_RELAIS_GRANULES,LOW);
granules_isON=false;
granules_lastSwitchTime=now;
}
} else {
interval=(unsigned long)Granules_Time_OFF*1000UL;
if(now-granules_lastSwitchTime>=interval){
digitalWrite(PIN_RELAIS_GRANULES,HIGH);
granules_isON=true;
granules_lastSwitchTime=now;
}
}
}
void gererCycleVibreur(){
if(!isSystemRunning) return;
unsigned long now=millis();
unsigned long interval;
if (vibreur_isON){
interval=(unsigned long)Vibreur_Time_ON*1000UL;
if(now-vibreur_lastSwitchTime>=interval){
digitalWrite(PIN_RELAIS_VIBRATEUR,LOW);
vibreur_isON=false;
vibreur_lastSwitchTime=now;
}
} else {
interval=(unsigned long)Vibreur_Time_OFF*1000UL;
if(now-vibreur_lastSwitchTime>=interval){
digitalWrite(PIN_RELAIS_VIBRATEUR,HIGH);
vibreur_isON=true;
vibreur_lastSwitchTime=now;
}
}
}
void gererMenuPrincipalNavigation(Direction c){
if (c==UP){
if(menuSelection>0){
menuSelection--;
afficherMenuPrincipal();
}
}
else if (c==DOWN){
if(menuSelection<NOMBRE_OPTIONS_MENU-1){
menuSelection++;
afficherMenuPrincipal();
}
}
else if (c==SELECT){
if (menuSelection==0){
isSystemRunning=!isSystemRunning;
if(isSystemRunning){
granules_lastSwitchTime=millis();
granules_isON=true;
digitalWrite(PIN_RELAIS_GRANULES,HIGH);
vibreur_lastSwitchTime=millis();
vibreur_isON=true;
digitalWrite(PIN_RELAIS_VIBRATEUR,HIGH);
strcpy(menuOptions[0],"1. System ON");
} else {
digitalWrite(PIN_RELAIS_GRANULES,LOW);
digitalWrite(PIN_RELAIS_VIBRATEUR,LOW);
strcpy(menuOptions[0],"1. System OFF");
}
afficherMenuPrincipal();
} else {
executerOptionMenu(menuSelection);
}
}
}
void saveSettings(){
EEPROM.put(EEPROM_ADDR_GRANULES_TON, Granules_Time_ON);
EEPROM.put(EEPROM_ADDR_GRANULES_TOFF, Granules_Time_OFF);
EEPROM.put(EEPROM_ADDR_VIBREUR_TON, Vibreur_Time_ON);
EEPROM.put(EEPROM_ADDR_VIBREUR_TOFF, Vibreur_Time_OFF);
EEPROM.put(EEPROM_ADDR_TEMP_UNIT, tempUnit);
EEPROM.put(EEPROM_ADDR_GRANULE_QUALITY, granuleQuality);
EEPROM.put(EEPROM_ADDR_CONSO_PERIOD, consoPeriodMode);
EEPROM.write(EEPROM_ADDR_MARKER, EEPROM_MARKER_VALUE);
}
void loadSettings(){
byte marker;
EEPROM.get(EEPROM_ADDR_MARKER, marker);
if (marker!=EEPROM_MARKER_VALUE){
Granules_Time_ON = 60;
Granules_Time_OFF= 1;
Vibreur_Time_ON = 5;
Vibreur_Time_OFF = 10;
saveSettings();
} else {
EEPROM.get(EEPROM_ADDR_GRANULES_TON, Granules_Time_ON);
EEPROM.get(EEPROM_ADDR_GRANULES_TOFF, Granules_Time_OFF);
EEPROM.get(EEPROM_ADDR_VIBREUR_TON, Vibreur_Time_ON);
EEPROM.get(EEPROM_ADDR_VIBREUR_TOFF, Vibreur_Time_OFF);
EEPROM.get(EEPROM_ADDR_TEMP_UNIT, tempUnit);
EEPROM.get(EEPROM_ADDR_GRANULE_QUALITY, granuleQuality);
EEPROM.get(EEPROM_ADDR_CONSO_PERIOD, consoPeriodMode);
if (tempUnit>1) tempUnit=0;
if (granuleQuality>=NOMBRE_QUALITE_GRANULES) granuleQuality=0;
if (consoPeriodMode>=NOMBRE_OPTIONS_PERIOD) consoPeriodMode=0;
}
}
void gererMinuteursNavigation(Direction c){
long* v=nullptr;
if(minuteursSelection==0) v=&Granules_Time_ON;
else if(minuteursSelection==1) v=&Granules_Time_OFF;
else if(minuteursSelection==2) v=&Vibreur_Time_ON;
else v=&Vibreur_Time_OFF;
if (currentState==SUB_MENU_MINUTEURS){
if(c==UP){
if(minuteursSelection>0){
minuteursSelection--;
afficherReglagesMinuteurs();
}
}
else if(c==DOWN){
if(minuteursSelection<NOMBRE_OPTIONS_MINUT-1){
minuteursSelection++;
afficherReglagesMinuteurs();
}
}
else if(c==SELECT){
currentState=EDITING_MINUTERIE;
afficherReglagesMinuteurs();
}
else if(c==LEFT){
currentState=MAIN_MENU;
afficherMenuPrincipal();
}
} else if (currentState==EDITING_MINUTERIE){
if(c==UP){
if(*v<9999) (*v)++;
afficherReglagesMinuteurs();
}
else if(c==DOWN){
if(*v>1) (*v)--;
afficherReglagesMinuteurs();
}
else if(c==RIGHT){
if(*v+10<=9999) *v+=10;
else *v=9999;
afficherReglagesMinuteurs();
}
else if(c==SELECT || c==LEFT){
saveSettings();
currentState=SUB_MENU_MINUTEURS;
afficherReglagesMinuteurs();
}
}
}
void gererParametresNavigation(Direction c){
if (currentState==SUB_MENU_PARAMETRES){
if(c==UP){
if(parametresSelection>0){
parametresSelection--;
afficherParametres();
}
}
else if(c==DOWN){
if(parametresSelection<NOMBRE_OPTIONS_PARAMETRES-1){
parametresSelection++;
afficherParametres();
}
}
else if(c==SELECT){
if(parametresSelection==0){
currentState=SUB_MENU_RTC;
rtcSelection=RTC_YEAR;
afficherReglagesRTC();
return;
}
if(parametresSelection==3){
currentState=SUB_MENU_CONSO_UNIT;
consoUnitSelection=0;
afficherReglagesConsoUnit();
return;
}
if(parametresSelection==4){
currentState=SUB_MENU_TCIBLES;
afficherReglagesTCibles();
return;
}
currentState=EDITING_PARAMETRE;
afficherParametres();
}
else if(c==LEFT){
currentState=MAIN_MENU;
afficherMenuPrincipal();
}
} else if (currentState==EDITING_PARAMETRE){
bool changed=false;
if(parametresSelection==1 || parametresSelection==2){
if(c==UP||c==DOWN){
if(parametresSelection==1){
tempUnit=(tempUnit==0)?1:0;
} else {
int nv=granuleQuality + ((c==UP)?1:-1);
if(nv<0) nv=NOMBRE_QUALITE_GRANULES-1;
if(nv>=NOMBRE_QUALITE_GRANULES) nv=0;
granuleQuality=nv;
}
changed=true;
}
}
if(changed) afficherParametres();
if(c==SELECT||c==LEFT){
saveSettings();
currentState=SUB_MENU_PARAMETRES;
afficherParametres();
}
}
}
void gererReglagesConsoUnitNavigation(Direction c){
if (currentState==SUB_MENU_CONSO_UNIT){
if(c==SELECT){
currentState=EDITING_CONSO_UNIT;
afficherReglagesConsoUnit();
}
else if(c==LEFT){
currentState=SUB_MENU_PARAMETRES;
afficherParametres();
}
} else if (currentState==EDITING_CONSO_UNIT){
bool ch=false;
if(c==UP||c==DOWN){
int nv=consoPeriodMode + ((c==UP)?1:-1);
if(nv<0) nv=NOMBRE_OPTIONS_PERIOD-1;
if(nv>=NOMBRE_OPTIONS_PERIOD) nv=0;
consoPeriodMode=nv;
ch=true;
}
if(ch) afficherReglagesConsoUnit();
if(c==SELECT||c==LEFT){
saveSettings();
currentState=SUB_MENU_CONSO_UNIT;
afficherReglagesConsoUnit();
}
}
}
void gererReglagesTCiblesNavigation(Direction c){
if(c==LEFT||c==SELECT){
currentState=SUB_MENU_PARAMETRES;
afficherParametres();
}
}
void desactiverTousActionneurs(){
digitalWrite(PIN_RELAIS_GRANULES,LOW);
digitalWrite(PIN_RELAIS_VIBRATEUR,LOW);
noTone(PIN_BUZZER);
test_granules_state=false;
test_vibreur_state=false;
test_buzzer_state=false;
}
void gererTestActionneursNavigation(Direction c){
if(c==UP){
if(actionneursSelection>0){
actionneursSelection--;
afficherTestactionneurs();
}
}
else if(c==DOWN){
if(actionneursSelection<NOMBRE_OPTIONS_ACTIONNEURS-1){
actionneursSelection++;
afficherTestactionneurs();
}
}
else if(c==SELECT){
if(actionneursSelection==0){
test_granules_state=!test_granules_state;
digitalWrite(PIN_RELAIS_GRANULES, test_granules_state?HIGH:LOW);
}
else if(actionneursSelection==1){
test_vibreur_state=!test_vibreur_state;
digitalWrite(PIN_RELAIS_VIBRATEUR, test_vibreur_state?HIGH:LOW);
}
else {
test_buzzer_state=!test_buzzer_state;
if(test_buzzer_state) tone(PIN_BUZZER,1000);
else noTone(PIN_BUZZER);
}
afficherTestactionneurs();
}
else if(c==LEFT){
desactiverTousActionneurs();
currentState=MAIN_MENU;
afficherMenuPrincipal();
}
}
void gererReglagesRTCNavigation(Direction c){
DateTime n=rtc.now();
int a=n.year(), m=n.month(), j=n.day(),
h=n.hour(), mn=n.minute(), s=n.second();
bool ch=false;
if(c==RIGHT){
rtcSelection=(RTCField)((rtcSelection+1)%RTC_FIELD_COUNT);
afficherReglagesRTC();
}
else if(c==LEFT){
if(rtcSelection==RTC_YEAR){
currentState=SUB_MENU_PARAMETRES;
afficherParametres();
return;
}
rtcSelection=(RTCField)((rtcSelection-1+RTC_FIELD_COUNT)%RTC_FIELD_COUNT);
afficherReglagesRTC();
}
else if(c==UP||c==DOWN){
ch=true;
int d=(c==UP)?1:-1;
switch(rtcSelection){
case RTC_YEAR: a+=d; if(a<2024) a=2099; if(a>2099) a=2024; break;
case RTC_MONTH: m+=d; if(m<1) m=12; if(m>12) m=1; break;
case RTC_DAY: j+=d; if(j<1) j=31; if(j>31) j=1; break;
case RTC_HOUR: h+=d; if(h<0) h=23; if(h>23) h=0; break;
case RTC_MINUTE: mn+=d; if(mn<0) mn=59; if(mn>59) mn=0; break;
case RTC_SECOND: s=0; break;
}
if(ch){
rtc.adjust(DateTime(a,m,j,h,mn,s));
afficherReglagesRTC();
}
}
else if(c==SELECT){
currentState=SUB_MENU_PARAMETRES;
afficherParametres();
}
}
void executerOptionMenu(int idx){
lcd_Menus.clear();
switch(idx){
case 0:
currentState=MAIN_MENU;
afficherMenuPrincipal();
break;
case 1:
digitalWrite(PIN_RELAIS_GRANULES,LOW);
digitalWrite(PIN_RELAIS_VIBRATEUR,LOW);
currentState=SUB_MENU_ACTIONNEURS;
actionneursSelection=0;
afficherTestactionneurs();
break;
case 2:
currentState=SUB_MENU_MINUTEURS;
minuteursSelection=0;
afficherReglagesMinuteurs();
break;
case 3:
currentState=SUB_MENU_PARAMETRES;
parametresSelection=0;
afficherParametres();
break;
}
}
// -----------------------------------------------------------------------------
// VIII. JOYSTICK (STABILISÉ + VIRTUEL)
// -----------------------------------------------------------------------------
static void beep(){
digitalWrite(PIN_BUZZER,HIGH);
delay(18);
digitalWrite(PIN_BUZZER,LOW);
}
void traiterCommande(Direction c, bool estPhysique){
if(currentState==MAIN_MENU){
if(estPhysique){
if(c==RIGHT){
scrollLeftRapports();
rapportMode=(rapportMode+1)%3;
}
else if(c==LEFT){
scrollRightRapports();
rapportMode=(rapportMode+2)%3;
}
}
if(c==UP||c==DOWN||c==SELECT){
gererMenuPrincipalNavigation(c);
}
}
else if(currentState==SUB_MENU_MINUTEURS||currentState==EDITING_MINUTERIE){
gererMinuteursNavigation(c);
}
else if(currentState==SUB_MENU_ACTIONNEURS){
gererTestActionneursNavigation(c);
}
else if(currentState==SUB_MENU_RTC){
gererReglagesRTCNavigation(c);
}
else if(currentState==SUB_MENU_TCIBLES){
gererReglagesTCiblesNavigation(c);
}
else if(currentState==SUB_MENU_PARAMETRES||currentState==EDITING_PARAMETRE){
gererParametresNavigation(c);
}
else if(currentState==SUB_MENU_CONSO_UNIT||currentState==EDITING_CONSO_UNIT){
gererReglagesConsoUnitNavigation(c);
}
}
void gererJoystick(){
static Direction lastPhys=NONE, lastVirt=NONE;
static unsigned long tPhys=0, tVirt=0;
unsigned long now=millis();
// Virtuel (Processing)
if(Serial.available()>0){
char c=Serial.read();
Direction d=NONE;
if(c=='U') d=UP;
else if(c=='D') d=DOWN;
else if(c=='L') d=LEFT;
else if(c=='R') d=RIGHT;
else if(c=='J') d=SELECT;
else if(c=='B') d=NONE;
if(d!=NONE && d!=lastVirt && (now-tVirt)>=DELAI_TRANSITION_SERIE){
tVirt=now;
lastVirt=d;
beep();
traiterCommande(d,false);
return;
}
if(d==NONE) lastVirt=NONE;
}
// Physique
Direction cur=lireJoystickAnalogique();
if(cur!=lastPhys){
if(cur!=NONE && (now-tPhys)>=DELAI_TRANSITION_JOY){
tPhys=now;
lastPhys=cur;
beep();
traiterCommande(cur,true);
} else if(cur==NONE){
lastPhys=NONE;
}
}
}
// -----------------------------------------------------------------------------
// IX. SETUP & LOOP
// -----------------------------------------------------------------------------
void animationDemarrage(){
clearBuffer(lcd_Menus_Buffer);
printCenteredBuffer(lcd_Menus_Buffer,1,"Systeme Chauffe-Eau");
printCenteredBuffer(lcd_Menus_Buffer,2,"Initialisation...");
syncBufferToPhysicalLCD(lcd_Menus, lcd_Menus_Buffer);
// Barre de progression sur lcd_Rapports
clearBuffer(lcd_Rapports_Buffer);
printCenteredBuffer(lcd_Rapports_Buffer,1,"Verification Modules");
syncBufferToPhysicalLCD(lcd_Rapports, lcd_Rapports_Buffer);
lcd_Rapports.setCursor(0,3);
for(int i=0;i<20;i++){
lcd_Rapports.print("#");
delay(50);
}
}
void setup(){
Serial.begin(115200);
init_lcd(lcd_Menus, lcd_Menus_Buffer, "Ecran Menus (0x27)", 0x27);
init_lcd(lcd_Rapports, lcd_Rapports_Buffer,"Ecran Rapports (0x26)",0x26);
init_lcd(lcd_Infos, lcd_Infos_Buffer, "Ecran Infos (0x20)", 0x20);
pinMode(PIN_RELAIS_GRANULES,OUTPUT);
digitalWrite(PIN_RELAIS_GRANULES,LOW);
pinMode(PIN_RELAIS_VIBRATEUR,OUTPUT);
digitalWrite(PIN_RELAIS_VIBRATEUR,LOW);
pinMode(PIN_BUZZER,OUTPUT);
noTone(PIN_BUZZER);
pinMode(PIN_JOY_SEL,INPUT_PULLUP);
loadSettings();
if(!rtc.begin()){
lcd_Menus.clear();
lcd_Menus.print("ERREUR: RTC NON TROUVE");
while(1);
}
sensors1.begin();
sensors2.begin();
sensors3.begin();
sensors4.begin();
sensors1.setWaitForConversion(false);
sensors2.setWaitForConversion(false);
sensors3.setWaitForConversion(false);
sensors4.setWaitForConversion(false);
// Animation de démarrage
animationDemarrage();
// Bips
beep();
delay(100);
beep();
strcpy(menuOptions[0],"1. System OFF");
afficherMenuPrincipal();
}
void loop(){
calculerConsommation();
gererAffichageRapports();
gererLecturesTemperature();
gererJoystick();
if(currentState!=SUB_MENU_ACTIONNEURS){
gererCycleGranules();
gererCycleVibreur();
}
// Envoi périodique vers Processing : LCDs + états actionneurs
if (millis() - dernierEnvoiSerie >= INTERVALLE_ENVOI_SERIE) {
dernierEnvoiSerie = millis();
envoyerContenuLCDs();
envoyerEtatActionneurs();
}
}
Relais
Vibreur
Relais
Granules
Lcd_Menu
Lcd_Rapport
Lcd_Infos