#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <RTClib.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <EEPROM.h>
#include <string.h>
#include <ArduinoJson.h>
const int PIN_RELAIS_GRANULES = 11;
const int PIN_RELAIS_VIBRATEUR = 3;
const int PIN_BUZZER = 12;
const int PIN_JOY_VERT = A1;
const int PIN_JOY_HORZ = A0;
const int PIN_JOY_SEL = 2;
const int SEUIL_PRESSION = LOW;
const int SEUIL_CENTRE_BAS = 300;
const int SEUIL_CENTRE_HAUT = 700;
const unsigned long DELAI_TRANSITION_JOY = 150;
const unsigned long DELAI_TRANSITION_SERIE = 120;
LiquidCrystal_I2C lcd_Menus(0x27,20,4);
LiquidCrystal_I2C lcd_Rapports(0x26,20,4);
LiquidCrystal_I2C lcd_Infos(0x20,20,4);
RTC_DS1307 rtc;
OneWire oneWire1(A2), oneWire2(A3), oneWire3(A4), oneWire4(A5);
DallasTemperature sensors1(&oneWire1), sensors2(&oneWire2), sensors3(&oneWire3), sensors4(&oneWire4);
char lcd_Menus_Buffer[4][21];
char lcd_Rapports_Buffer[4][21];
char lcd_Infos_Buffer[4][21];
char lcd_Menus_Previous[4][21];
char lcd_Rapports_Previous[4][21];
char lcd_Infos_Previous[4][21];
unsigned long dernierEnvoiJSON = 0;
const unsigned long INTERVALLE_JSON = 100;
unsigned long granules_lastSwitchTime = 0;
unsigned long vibreur_lastSwitchTime = 0;
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;
const float BASE_CONSO_ENERGIE_PER_DAY = 311.04f;
unsigned long derniereLectureTemp = 0;
const long INTERVALLE_LECTURE_TEMP = 5000;
unsigned long derniereLectureRTC = 0;
const long INTERVALLE_LECTURE_RTC = 1000;
unsigned long tempsDebutConversion = 0;
const long TEMPS_CONVERSION_MAX = 800;
long Granules_Time_ON = 60;
long Granules_Time_OFF = 1;
long Vibreur_Time_ON = 5;
long Vibreur_Time_OFF = 10;
byte tempUnit = 0;
byte granuleQuality = 0;
byte consoPeriodMode = 0;
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"};
char menuOptions[4][20] = {
"1. System OFF",
"2. Test Actionneurs",
"3. Minuteurs",
"4. Parametres"
};
int menuSelection = 0;
const char* PARAMETRES_LABELS[] = {
"1. Date/Heure",
"2. Unite Temp.",
"3. Qualite Granules",
"4. Conso/Unit",
"5. Reglage T.Cibles"
};
int parametresSelection = 0;
const char* minuteursOptions_V8[] = {"1. Gr T-ON","2. Gr T-OFF","3. Vib T-ON","4. Vib T-OFF"};
int minuteursSelection = 0;
char actionneursOptions[3][20] = {
"1. Granules : OFF",
"2. Vibreur : OFF",
"3. Buzzer : OFF"
};
int actionneursSelection = 0;
bool isSystemRunning = false;
bool granules_isON = false;
bool vibreur_isON = false;
bool test_granules_state=false;
bool test_vibreur_state=false;
bool test_buzzer_state=false;
byte rapportMode = 0;
byte ancienRapportMode = 255;
enum Direction { NONE, UP, DOWN, LEFT, RIGHT, SELECT };
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;
enum RTCField { RTC_YEAR, RTC_MONTH, RTC_DAY, RTC_HOUR, RTC_MINUTE, RTC_SECOND, RTC_FIELD_COUNT };
RTCField rtcSelection = RTC_YEAR;
// --------- Buffer JSON entrant (Processing) ----------
String serialIn = "";
void clearBuffer(char buffer[4][21]) {
for (int i=0;i<4;i++){
memset(buffer[i],' ',20);
buffer[i][20]='\0';
}
}
void printCenteredBuffer(char buffer[4][21], int line, const char* msg){
int len=strlen(msg);
int pos=(20-len)/2;
if (pos<0) pos=0;
memset(buffer[line],' ',20);
strncpy(buffer[line]+pos, msg, (len > 20-pos ? 20-pos : len));
buffer[line][20]='\0';
}
void updateLCDline(LiquidCrystal_I2C &lcd, int line, char buf[21], char prev[21]) {
if (strncmp(buf, prev, 20) != 0) {
memcpy(prev, buf, 20);
prev[20] = '\0';
lcd.setCursor(0, line);
lcd.print(prev);
}
}
void syncLCD(LiquidCrystal_I2C &lcd, char buf[4][21], char prev[4][21]) {
for (int i=0;i<4;i++) updateLCDline(lcd, i, buf[i], prev[i]);
}
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,"%s", message);
syncLCD(lcd,buffer,lcd_Menus_Previous);
delay(400);
lcd.clear();
clearBuffer(buffer);
}
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;
memset(lcd_Rapports_Previous,0,sizeof(lcd_Rapports_Previous));
}
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]);
}
syncLCD(lcd_Rapports,lcd_Rapports_Buffer,lcd_Rapports_Previous);
}
}
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 >= TEMPS_CONVERSION_MAX){
tempsDebutConversion = now + TEMPS_CONVERSION_MAX;
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);
syncLCD(lcd_Infos,lcd_Infos_Buffer,lcd_Infos_Previous);
}
}
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<4){
char p=(idx==menuSelection)?'>':' ';
snprintf(lcd_Menus_Buffer[i],21,"%c %s",p,menuOptions[idx]);
}
}
syncLCD(lcd_Menus,lcd_Menus_Buffer,lcd_Menus_Previous);
}
void afficherReglagesMinuteurs(){
clearBuffer(lcd_Menus_Buffer);
for(int i=0;i<4;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);
}
syncLCD(lcd_Menus,lcd_Menus_Buffer,lcd_Menus_Previous);
}
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<3;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");
syncLCD(lcd_Menus,lcd_Menus_Buffer,lcd_Menus_Previous);
}
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>");
syncLCD(lcd_Menus,lcd_Menus_Buffer,lcd_Menus_Previous);
}
void afficherParametres(){
clearBuffer(lcd_Menus_Buffer);
bool edit=(currentState==EDITING_PARAMETRE);
int debut=(parametresSelection>2)?parametresSelection-2:0;
for(int i=0;i<4;i++){
int idx=debut+i;
if(idx<5){
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 && edit)
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);
}
}
}
syncLCD(lcd_Menus,lcd_Menus_Buffer,lcd_Menus_Previous);
}
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> ");
syncLCD(lcd_Menus,lcd_Menus_Buffer,lcd_Menus_Previous);
}
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> ");
syncLCD(lcd_Menus,lcd_Menus_Buffer,lcd_Menus_Previous);
}
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 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;
case 1: pf=1.0f/24.0f; break;
case 2: pf=1.0f; break;
case 3: pf=7.0f; break;
case 4: pf=30.4167f; break;
}
consoGranules = dG*pf;
consoEnergie = dE*pf;
}
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 saveSettings(){
EEPROM.put(0, Granules_Time_ON);
EEPROM.put(4, Granules_Time_OFF);
EEPROM.put(8, Vibreur_Time_ON);
EEPROM.put(12, Vibreur_Time_OFF);
EEPROM.put(17, tempUnit);
EEPROM.put(18, granuleQuality);
EEPROM.put(19, consoPeriodMode);
EEPROM.write(16, 0xAA);
}
void loadSettings(){
byte marker;
EEPROM.get(16, marker);
if (marker!=0xAA){
Granules_Time_ON = 60;
Granules_Time_OFF= 1;
Vibreur_Time_ON = 5;
Vibreur_Time_OFF = 10;
saveSettings();
} else {
EEPROM.get(0, Granules_Time_ON);
EEPROM.get(4, Granules_Time_OFF);
EEPROM.get(8, Vibreur_Time_ON);
EEPROM.get(12, Vibreur_Time_OFF);
EEPROM.get(17,tempUnit);
EEPROM.get(18,granuleQuality);
EEPROM.get(19,consoPeriodMode);
if (tempUnit>1) tempUnit=0;
if (granuleQuality>=3) granuleQuality=0;
if (consoPeriodMode>=5) consoPeriodMode=0;
}
}
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;
}
/*
static void beep(){
digitalWrite(PIN_BUZZER,HIGH);
delay(18);
digitalWrite(PIN_BUZZER,LOW);
}
*/
void executerOptionMenu(int idx){
switch(idx){
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;
}
}
void gererMenuPrincipalNavigation(Direction c){
if (c==UP){
if(menuSelection>0){
menuSelection--;
afficherMenuPrincipal();
}
}
else if (c==DOWN){
if(menuSelection<3){
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 gererReglagesTCiblesNavigation(Direction c){
if(c==LEFT||c==SELECT){
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){
if(c==UP||c==DOWN){
int nv=consoPeriodMode + ((c==UP)?1:-1);
if(nv<0) nv=4;
if(nv>4) nv=0;
consoPeriodMode=nv;
afficherReglagesConsoUnit();
}
if(c==SELECT||c==LEFT){
saveSettings();
currentState=SUB_MENU_CONSO_UNIT;
afficherReglagesConsoUnit();
}
}
}
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();
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){
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;
}
rtc.adjust(DateTime(a,m,j,h,mn,s));
afficherReglagesRTC();
}
else if(c==SELECT){
currentState=SUB_MENU_PARAMETRES;
afficherParametres();
}
}
void gererParametresNavigation(Direction c){
if (currentState==SUB_MENU_PARAMETRES){
if(c==UP){
if(parametresSelection>0){
parametresSelection--;
afficherParametres();
}
}
else if(c==DOWN){
if(parametresSelection<4){
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;
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){
if(parametresSelection==1){
if(c==UP||c==DOWN){
tempUnit=(tempUnit==0)?1:0;
afficherParametres();
}
}
else if(parametresSelection==2){
if(c==UP||c==DOWN){
int nv=granuleQuality + ((c==UP)?1:-1);
if(nv<0) nv=2;
if(nv>2) nv=0;
granuleQuality=nv;
afficherParametres();
}
}
if(c==SELECT||c==LEFT){
saveSettings();
currentState=SUB_MENU_PARAMETRES;
afficherParametres();
}
}
}
void gererTestActionneursNavigation(Direction c){
if(c==UP){
if(actionneursSelection>0){
actionneursSelection--;
afficherTestactionneurs();
}
}
else if(c==DOWN){
if(actionneursSelection<2){
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 if(actionneursSelection==2){
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 gererMinuteursNavigation(Direction c){
if (currentState==SUB_MENU_MINUTEURS){
if(c==UP){
if(minuteursSelection>0){
minuteursSelection--;
afficherReglagesMinuteurs();
}
}
else if(c==DOWN){
if(minuteursSelection<3){
minuteursSelection++;
afficherReglagesMinuteurs();
}
}
else if(c==SELECT){
currentState=EDITING_MINUTERIE;
afficherReglagesMinuteurs();
}
else if(c==LEFT){
currentState=MAIN_MENU;
afficherMenuPrincipal();
}
}
else if (currentState==EDITING_MINUTERIE){
long* val;
switch(minuteursSelection){
case 0: val=&Granules_Time_ON; break;
case 1: val=&Granules_Time_OFF; break;
case 2: val=&Vibreur_Time_ON; break;
case 3: val=&Vibreur_Time_OFF; break;
default: return;
}
if(c==UP||c==RIGHT){
(*val)++;
if(*val>9999) *val=1;
afficherReglagesMinuteurs();
}
else if(c==DOWN||c==LEFT){
(*val)--;
if(*val<1) *val=9999;
afficherReglagesMinuteurs();
}
else if(c==SELECT){
saveSettings();
currentState=SUB_MENU_MINUTEURS;
afficherReglagesMinuteurs();
}
}
}
void gererNavigation(Direction c){
if(c==NONE) return;
switch(currentState){
case MAIN_MENU:
gererMenuPrincipalNavigation(c);
break;
case SUB_MENU_ACTIONNEURS:
gererTestActionneursNavigation(c);
break;
case SUB_MENU_MINUTEURS:
case EDITING_MINUTERIE:
gererMinuteursNavigation(c);
break;
case SUB_MENU_PARAMETRES:
case EDITING_PARAMETRE:
gererParametresNavigation(c);
break;
case SUB_MENU_RTC:
gererReglagesRTCNavigation(c);
break;
case SUB_MENU_CONSO_UNIT:
case EDITING_CONSO_UNIT:
gererReglagesConsoUnitNavigation(c);
break;
case SUB_MENU_TCIBLES:
gererReglagesTCiblesNavigation(c);
break;
default: break;
}
}
void serialEvent() {
while (Serial.available()) {
char inChar = (char)Serial.read();
serialIn += inChar;
if (inChar == '\n') {
StaticJsonDocument<300> doc;
DeserializationError err = deserializeJson(doc, serialIn);
if (!err) {
const char* cmd = doc["cmd"];
// ---------------------------
// 1) RÉGLAGE DES MINUTEURS
// ---------------------------
if (strcmp(cmd, "setMinuteurs") == 0) {
Granules_Time_ON = (long)doc["gOn"];
Granules_Time_OFF = (long)doc["gOff"];
Vibreur_Time_ON = (long)doc["vOn"];
Vibreur_Time_OFF = (long)doc["vOff"];
saveSettings(); // EEPROM
envoyerJSON(); // renvoyer vers Processing
}
// ---------------------------
// 2) AUTRES RÉGLAGES
// ---------------------------
if (strcmp(cmd, "setSettings") == 0) {
tempUnit = doc["unit"];
granuleQuality = doc["quality"];
consoPeriodMode = doc["mode"];
saveSettings();
envoyerJSON();
}
}
serialIn = ""; // reset du buffer JSON
}
}
}
void envoyerJSON() {
StaticJsonDocument<700> doc;
// SYSTEME
JsonObject sys = doc.createNestedObject("system");
sys["running"] = isSystemRunning ? 1 : 0;
sys["granules"] = granules_isON ? 1 : 0;
sys["vibreur"] = vibreur_isON ? 1 : 0;
// LCD MENUS
JsonArray jm = doc.createNestedArray("menus");
for (int i = 0; i < 4; i++) jm.add(lcd_Menus_Buffer[i]);
// LCD RAPPORTS
JsonArray jr = doc.createNestedArray("rapports");
for (int i = 0; i < 4; i++) jr.add(lcd_Rapports_Buffer[i]);
// LCD INFOS
JsonArray ji = doc.createNestedArray("infos");
for (int i = 0; i < 4; i++) ji.add(lcd_Infos_Buffer[i]);
// MINUTEURS
JsonObject min = doc.createNestedObject("minuteurs");
min["gOn"] = Granules_Time_ON;
min["gOff"] = Granules_Time_OFF;
min["vOn"] = Vibreur_Time_ON;
min["vOff"] = Vibreur_Time_OFF;
// SETTINGS
JsonObject st = doc.createNestedObject("settings");
st["unit"] = tempUnit;
st["quality"] = granuleQuality;
st["mode"] = consoPeriodMode;
serializeJson(doc, Serial);
Serial.println();
}
void setup() {
Serial.begin(9600);
Serial.println(F("SYSTEME DE CONTROLE DEMARRE"));
pinMode(PIN_RELAIS_GRANULES, OUTPUT);
pinMode(PIN_RELAIS_VIBRATEUR, OUTPUT);
pinMode(PIN_BUZZER, OUTPUT);
pinMode(PIN_JOY_SEL, INPUT_PULLUP);
digitalWrite(PIN_RELAIS_GRANULES, LOW);
digitalWrite(PIN_RELAIS_VIBRATEUR, LOW);
noTone(PIN_BUZZER);
loadSettings();
calculerConsommation(); // Calcul initial
init_lcd(lcd_Menus, lcd_Menus_Buffer, "LCD Menus OK", 0x27);
init_lcd(lcd_Rapports, lcd_Rapports_Buffer, "LCD Rapports OK", 0x26);
init_lcd(lcd_Infos, lcd_Infos_Buffer, "LCD Infos OK", 0x20);
// Initialisation RTC
if (!rtc.begin()) {
Serial.println(F("Erreur RTC non trouvee."));
// rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); // Pour ajuster à la compilation si nécessaire
}
// Initialisation capteurs de temperature
sensors1.begin();
sensors2.begin();
sensors3.begin();
sensors4.begin();
// Affichage initial
afficherMenuPrincipal();
}
void loop() {
// 1. Gestion des E/S
Direction joy = lireJoystickAnalogique();
static unsigned long lastJoyTime = 0;
if (joy != NONE && millis() - lastJoyTime > DELAI_TRANSITION_JOY) {
gererNavigation(joy);
lastJoyTime = millis();
}
// 2. Gestion des cycles d'actionneurs (uniquement si le systeme est ON)
gererCycleGranules();
gererCycleVibreur();
// 3. Mise à jour des affichages
miseAJourAfficheRapports();
miseAJourAfficheInfos();
// 4. Gestion de la consommation (recalcul pour être à jour si les minuteries ont changé)
calculerConsommation();
}Relais
Vibreur
Relais
Granules
Lcd_Menu
Lcd_Rapport
Lcd_Infos