#include #include #include #include LiquidCrystal lcd(12, 11, 2, 3, 4, 5); #define MAX_PROGRAMS 10 #define MAX_WIDTH 20 #define MAX_TIME_STRING_SIZE MAX_WIDTH+1 #define DELAY_VEILLE 60 time_t beginTimes[MAX_PROGRAMS]; time_t endTimes[MAX_PROGRAMS]; time_t beginForcedMode; int progOffset; int progSelect; int melody1[] = { 440, 0, -1 }; int melodyDuration1[] = { 4, 2 }; int melody2[] = { 262, 262, 262, 294, 330, 294, 262, 330, 294, 294, 262, 0, -1 }; int melodyDuration2[] = { 2, 2, 2, 2, 4, 4, 2, 2, 2, 2, 4, 8 }; int melody3[] = { 294, 330, 370, 440, 392, 392, 494, 440, 440, 587, 554, 587, 440, 370, 294, 330, 370, 392, 440, 494, 440, 392, 370, 330, 370, 294, 277, 294, 330, 220, 294, 330, 392, 370, 330, 370, 294, 330, 370, 440, 392, 392, 494, 440, 440, 587, 554, 587, 440, 370, 294, 330, 370, 440, 392, 370, 330, 294, 247, 220, 294, 277, 294, 0, -1 }; int melodyDuration3[] = { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, }; int melody4[] = { 294, 294, 392, 392, 392, 440, 494, 392, 494, 494, 494, 523, 494, 440, 440, 370, 294, 370, 440, 494, 523, 523, 587, 659, 587, 523, 494, 440, 392, 0, -1 }; int melodyDuration4[] = { 2, 2, 2, 2, 2, 2, 6, 2, 2, 2, 2, 2, 2, 6, 2, 2, 2, 2, 2, 2, 6, 2, 2, 2, 2, 2, 2, 2, 6, 2 }; int *melodies[] = { melody1, melody2, melody3, melody4 }; int *melodiesDurations[] = { melodyDuration1, melodyDuration2, melodyDuration3, melodyDuration4 }; char *melodiesNames[] = { "Simple bip", "Au clair de la lune", "Que ma joie demeure", "A life on the ocean wave" }; int melodyPos = 0; int noteDuration = 0; int melodyToUse = 0; int nbMelodies = sizeof(melodies)/sizeof(int*); /*********************** * Programs management * ***********************/ void initPrograms() { for(int i = 0; i < MAX_PROGRAMS; ++i) { beginTimes[i] = 0; endTimes[i] = 0; } } void cleanPrograms() { time_t timeNow = now(); int j = 0; for(int i = 0; i < MAX_PROGRAMS; ++i) { if(endTimes[i] >= timeNow) { beginTimes[j] = beginTimes[i]; endTimes[j] = endTimes[i]; j++; } } for(;j < MAX_PROGRAMS; ++j) { beginTimes[j] = 0; endTimes[j] = 0; } } void addProgram(time_t tm, int duration) { for(int i = 0; i < MAX_PROGRAMS; ++i) { if(beginTimes[i] == 0 && endTimes[i] == 0) { beginTimes[i] = tm; endTimes[i] = tm + duration * 60; break; } } } void addProgram(int year, int month, int day, int hour, int minutes, int duration) { tmElements_t t; t.Second = 0; t.Minute = minutes; t.Hour = hour; t.Day = day; t.Month = month; t.Year = year - 1970; time_t tm = makeTime(t); addProgram(tm, duration); } /********************** * Display management * **********************/ char timeString[MAX_TIME_STRING_SIZE]; char tmpString[MAX_TIME_STRING_SIZE]; char* dateAndTimeAsString(time_t t) { snprintf(timeString, MAX_TIME_STRING_SIZE, "%02u/%02u/%04u %02u:%02u:%02u", day(t), month(t), year(t), hour(t), minute(t), second(t)); return timeString; } char* shortDateAndTimeAsString(time_t t) { snprintf(timeString, MAX_TIME_STRING_SIZE, "%02u/%02u/%02u %02u:%02u", day(t), month(t), year(t) - 2000, hour(t), minute(t)); return timeString; } char* timeAsString(time_t t) { snprintf(timeString, MAX_TIME_STRING_SIZE, "%02u:%02u:%02u", hour(t), minute(t), second(t)); return timeString; } char* dateAsString(time_t t) { snprintf(timeString, MAX_TIME_STRING_SIZE, "%02u/%02u/%04u", day(t), month(t), year(t)); return timeString; } char* elapsedAsString(time_t beginTime, time_t nowTime) { int secondsElapsed = nowTime - beginTime; int minutesElapsed = secondsElapsed / 60; secondsElapsed = secondsElapsed % 60; snprintf(timeString, MAX_TIME_STRING_SIZE, "%u:%02u", minutesElapsed, secondsElapsed); return timeString; } char* elapsedRemainingAsString(time_t beginTime, time_t endTime, time_t nowTime) { int secondsElapsed = nowTime - beginTime; int minutesElapsed = secondsElapsed / 60; secondsElapsed = secondsElapsed % 60; int secondsRemaining = endTime - nowTime; if(secondsRemaining < 0) secondsRemaining = 0; int minutesRemaining = secondsRemaining / 60; secondsRemaining = secondsRemaining % 60; snprintf(timeString, MAX_TIME_STRING_SIZE, "%u:%02u - %u:%02u", minutesElapsed, secondsElapsed, minutesRemaining, secondsRemaining); return timeString; } char* centered(char *outString, char* string) { int string_length = min(strlen(string), MAX_WIDTH); int spaces = (MAX_WIDTH - string_length) / 2; int i; for(i = 0; i < spaces; ++i) outString[i] = ' '; snprintf(outString + i, MAX_WIDTH + 1, string); i += string_length; while(i < MAX_WIDTH) outString[i++] = ' '; return outString; } char* menuElt(char* string, bool premier, bool dernier) { int string_length = min(strlen(string), MAX_WIDTH - 4); int spaces = (MAX_WIDTH - string_length - 4) / 2; int i; for(i = 0; i < spaces; ++i) timeString[i] = ' '; timeString[i++] = premier ? ' ' : 0x7f; timeString[i++] = ' '; snprintf(timeString + i, MAX_WIDTH - 3, string); i += string_length; timeString[i++] = ' '; timeString[i++] = dernier ? ' ' : 0x7e; while(i < MAX_WIDTH) timeString[i++] = ' '; return timeString; } /******************* * Oven management * *******************/ enum etats { VEILLE, HEURE, MENU_PROG_COURANT, MENU_MARCHE_FORCEE, MENU_PROG_RAPIDE, MENU_PROGRAMME, MENU_LISTE_PROG, MENU_CHANGER_HEURE, MENU_CHANGER_SONNERIE, PROG_COURANT, MARCHE_FORCEE, PROG_RAPIDE, PROGRAMME, LISTE_PROG, CHANGER_HEURE, CHANGER_SONNERIE } currentState = VEILLE; int alarm = 0; int warmIfNeeded(time_t timeNow) { // Decide if the hoven is on or off boolean warm = false; // Return value: program number that warms, -1 if forced mode, -2 if not warming int program = -2; static int prevProgram = -2; if(currentState == MARCHE_FORCEE) { warm = true; program = -1; } for(int i = 0; i < MAX_PROGRAMS; ++i) { if(timeNow >= beginTimes[i] && timeNow <= endTimes[i]) { warm = true; program = i; prevProgram = i; } } if(warm) { if(alarm == 0 && currentState != MARCHE_FORCEE) { currentState = PROG_COURANT; } if(currentState != MARCHE_FORCEE) { alarm = 1; } digitalWrite(13, HIGH); } else { if(alarm == 1) { alarm = 2; // Only remove the program when OK clicked after the alarm ring //cleanPrograms(); } if(alarm == 2) { if(noteDuration < melodiesDurations[melodyToUse][melodyPos]) { int note = melodies[melodyToUse][melodyPos]; if(note > 0) tone(8, note); else noTone(8); noteDuration++; } else { noTone(8); noteDuration = 0; melodyPos++; if(melodies[melodyToUse][melodyPos] == -1) { melodyPos = 0; } } } if(alarm == 3) { alarm = 0; prevProgram = -2; } digitalWrite(13, LOW); } return max(program,prevProgram); } /**************** * Core program * ****************/ time_t lastAction = 0; int duration_prog = 0; int pg_time[5]; int pg_max[5] = { 2050, 12, 31, 23, 59 }; int pg_min[5] = { 2013, 1, 1, 0, 0 }; int pg_step; enum buttons { OK = 0, MINUS = 1, PLUS = 2 }; bool buttons_pushed[3] = { false, false, false }; bool buttons_curr[3] = { false, false, false }; bool buttons_prev[3] = { false, false, false }; bool buttons[3] = { false, false, false }; void updateButtons() { for(int i = 0; i < 3; ++i) { buttons_prev[i] = buttons_curr[i]; buttons_curr[i] = false; } int buttonsState = analogRead(A0); //Serial.println(buttonsState); if(buttonsState > 1000) { buttons_curr[OK] = true; buttons[OK] = true; } if(buttonsState > 400 && buttonsState < 800) { buttons_curr[PLUS] = true; buttons[PLUS] = true; } if(buttonsState > 200 && buttonsState < 400) { buttons_curr[MINUS] = true; buttons[MINUS] = true; } for(int i = 0; i < 3; ++i) { buttons_pushed[i] = ((buttons_curr[i] == true) && (buttons_prev[i] == false)); if(buttons_pushed[i]) { Serial.print("Buttons pushed: "); Serial.println(i); } } } void setup() { Serial.begin(9600); setSyncProvider(RTC.get); while(timeStatus() == timeNotSet); pinMode(6, OUTPUT); pinMode(8, OUTPUT); pinMode(13, OUTPUT); lcd.begin(20, 4); lcd.clear(); initPrograms(); } void displayProgs() { if(progSelect > progOffset + 2) progOffset = progSelect - 2; if(progSelect < progOffset) progOffset = progSelect; lcd.clear(); lcd.setCursor(0, 0); lcd.print("Supprimer programme:"); for(int i = 0; i < 3; ++i) { lcd.setCursor(0, i+1); if(progSelect == progOffset + i) lcd.print(">"); if(beginTimes[progOffset+i] != 0) { lcd.setCursor(2, i+1); lcd.print(shortDateAndTimeAsString(beginTimes[progOffset+i])); } else { lcd.setCursor(2, i+1); lcd.print(" Retour menu"); break; } } } void displaySonneries() { if(progSelect > progOffset + 2) progOffset = progSelect - 2; if(progSelect < progOffset) progOffset = progSelect; lcd.clear(); lcd.setCursor(0, 0); lcd.print("Choisir sonnerie:"); for(int i = 0; i < 3; ++i) { lcd.setCursor(0, i+1); if(progSelect == progOffset + i) lcd.print(">"); if(progOffset+i < nbMelodies) { lcd.setCursor(2, i+1); char melodyName[MAX_WIDTH]; snprintf(melodyName, MAX_WIDTH - 1, melodiesNames[progOffset+i]); lcd.print(melodyName); } } } void loop() { time_t timeNow = now(); int warm; warm = warmIfNeeded(timeNow); if(warm == -2 && currentState != VEILLE && lastAction + DELAY_VEILLE < timeNow) { currentState = VEILLE; } updateButtons(); // State machine if(buttons_pushed[OK]) { if(alarm == 2) { noTone(8); warm = -2; alarm = 3; melodyPos = 0; cleanPrograms(); if(currentState == PROG_COURANT || currentState == MENU_PROG_COURANT) currentState = MENU_MARCHE_FORCEE; } else if(currentState == VEILLE) currentState = HEURE; else if(currentState == HEURE) currentState = MENU_MARCHE_FORCEE; else if(currentState == MENU_PROG_COURANT) { currentState = PROG_COURANT; } else if(currentState == PROG_COURANT) { currentState = MENU_PROG_COURANT; } else if(currentState == MENU_MARCHE_FORCEE) { currentState = MARCHE_FORCEE; beginForcedMode = timeNow; } else if(currentState == MARCHE_FORCEE) { currentState = MENU_MARCHE_FORCEE; } else if(currentState == MENU_PROG_RAPIDE) { currentState = PROG_RAPIDE; duration_prog = 30; } else if(currentState == PROG_RAPIDE) { currentState = MENU_PROG_RAPIDE; addProgram(timeNow, duration_prog); } else if(currentState == MENU_PROGRAMME) { currentState = PROGRAMME; pg_time[0] = year(timeNow); pg_time[1] = month(timeNow); pg_time[2] = day(timeNow); pg_time[3] = hour(timeNow); pg_time[4] = minute(timeNow); pg_step = 0; duration_prog = 30; } else if(currentState == PROGRAMME) { if (pg_step < 5) pg_step++; else { currentState = MENU_PROGRAMME; addProgram(pg_time[0], pg_time[1], pg_time[2], pg_time[3], pg_time[4], duration_prog); } } else if(currentState == MENU_CHANGER_HEURE) { currentState = CHANGER_HEURE; pg_time[0] = year(timeNow); pg_time[1] = month(timeNow); pg_time[2] = day(timeNow); pg_time[3] = hour(timeNow); pg_time[4] = minute(timeNow); pg_step = 0; } else if(currentState == CHANGER_HEURE) { if (pg_step < 4) pg_step++; else { currentState = MENU_CHANGER_HEURE; tmElements_t t; t.Second = 0; t.Minute = pg_time[4]; t.Hour = pg_time[3]; t.Day = pg_time[2]; t.Month = pg_time[1]; t.Year = pg_time[0] - 1970; time_t tm = makeTime(t); RTC.set(tm); setSyncProvider(RTC.get); while(timeStatus() == timeNotSet); } } else if(currentState == MENU_CHANGER_SONNERIE) { progOffset = 0; progSelect = melodyToUse; currentState = CHANGER_SONNERIE; } else if(currentState == CHANGER_SONNERIE) { if(progSelect <= nbMelodies) melodyToUse = progSelect; melodyPos = 0; currentState = MENU_CHANGER_SONNERIE; } else if(currentState == MENU_LISTE_PROG) { currentState = LISTE_PROG; progOffset = 0; progSelect = 0; cleanPrograms(); } else if(currentState == LISTE_PROG) { if(beginTimes[progSelect] == 0) { currentState = MENU_LISTE_PROG; } else { endTimes[progSelect] = 0; cleanPrograms(); } } lastAction = timeNow; } if(buttons_curr[PLUS]) { if(currentState == PROG_RAPIDE) duration_prog = duration_prog > 998 ? 999 : duration_prog + 1; else if(currentState == PROG_COURANT && warm >= 0) { endTimes[warm] += 60; if(endTimes[warm] > timeNow) { alarm = 1; noTone(8); } } else if(currentState == PROGRAMME || currentState == CHANGER_HEURE) { if(pg_step < 5) pg_time[pg_step] = pg_time[pg_step] >= pg_max[pg_step] ? pg_min[pg_step] : pg_time[pg_step] + 1; else duration_prog = duration_prog > 998 ? 999 : duration_prog + 1; } lastAction = timeNow; } if(buttons_pushed[PLUS]) { if(currentState == MENU_PROG_COURANT) currentState = MENU_MARCHE_FORCEE; else if(currentState == MENU_MARCHE_FORCEE) currentState = MENU_PROG_RAPIDE; else if(currentState == MENU_PROG_RAPIDE) currentState = MENU_PROGRAMME; else if(currentState == MENU_PROGRAMME) currentState = MENU_LISTE_PROG; else if(currentState == MENU_LISTE_PROG) currentState = MENU_CHANGER_HEURE; else if(currentState == MENU_CHANGER_HEURE) currentState = MENU_CHANGER_SONNERIE; // Testing beginTimes[progSelect] because if progSelect is greater than last prog, it means we select "back to menu" else if(currentState == LISTE_PROG && progSelect + 1 < MAX_PROGRAMS && beginTimes[progSelect] != 0) progSelect++; else if(currentState == CHANGER_SONNERIE && progSelect + 1 < nbMelodies) progSelect++; lastAction = timeNow; } if(buttons_curr[MINUS]) { if(currentState == PROG_RAPIDE) duration_prog = duration_prog < 1 ? 0 : duration_prog - 1; else if(currentState == PROG_COURANT && warm >= 0 && endTimes[warm] > timeNow) endTimes[warm] -= 60; else if(currentState == PROGRAMME || currentState == CHANGER_HEURE) { if(pg_step < 5) pg_time[pg_step] = pg_time[pg_step] <= pg_min[pg_step] ? pg_max[pg_step] : pg_time[pg_step] - 1; else duration_prog = duration_prog < 1 ? 0 : duration_prog - 1; } lastAction = timeNow; } if(buttons_pushed[MINUS]) { if(currentState == MENU_CHANGER_SONNERIE) currentState = MENU_CHANGER_HEURE; else if(currentState == MENU_CHANGER_HEURE) currentState = MENU_LISTE_PROG; else if(currentState == MENU_LISTE_PROG) currentState = MENU_PROGRAMME; else if(currentState == MENU_PROGRAMME) currentState = MENU_PROG_RAPIDE; else if(currentState == MENU_PROG_RAPIDE) currentState = MENU_MARCHE_FORCEE; else if(currentState == MENU_MARCHE_FORCEE && warm >= 0) currentState = MENU_PROG_COURANT; else if(currentState == LISTE_PROG && progSelect > 0) progSelect--; else if(currentState == CHANGER_SONNERIE && progSelect > 0) progSelect--; lastAction = timeNow; } if(currentState == VEILLE || currentState == HEURE) { digitalWrite(6, currentState == HEURE ? HIGH : LOW); lcd.clear(); lcd.setCursor(5, 1); lcd.print(dateAsString(timeNow)); lcd.setCursor(6, 2); lcd.print(timeAsString(timeNow)); } else if(currentState == LISTE_PROG) { displayProgs(); } else if(currentState == CHANGER_SONNERIE) { displaySonneries(); } else { digitalWrite(6, HIGH); lcd.clear(); lcd.setCursor(0, 0); lcd.print(centered(tmpString, shortDateAndTimeAsString(timeNow))); /* lcd.print(dateAndTimeAsString(timeNow)); lcd.setCursor(5, 0); lcd.print(dateAsString(timeNow)); lcd.setCursor(6, 1); lcd.print(timeAsString(timeNow)); */ lcd.setCursor(0, 1); if(warm >= 0) { lcd.print(centered(tmpString, elapsedRemainingAsString(beginTimes[warm], endTimes[warm], timeNow))); } else if(warm == -1) { lcd.print(centered(tmpString, elapsedAsString(beginForcedMode, timeNow))); } if(currentState == CHANGER_HEURE) { char buffer[20]; snprintf(buffer, 20, "%s%02u%s-%s%02u%s-%s%02u%s %s%02u%s:%s%02u%s", (pg_step == 0 ? "<" : ""), pg_time[0], (pg_step == 0 ? ">" : ""), (pg_step == 1 ? "<" : ""), pg_time[1], (pg_step == 1 ? ">" : ""), (pg_step == 2 ? "<" : ""), pg_time[2], (pg_step == 2 ? ">" : ""), (pg_step == 3 ? "<" : ""), pg_time[3], (pg_step == 3 ? ">" : ""), (pg_step == 4 ? "<" : ""), pg_time[4], (pg_step == 4 ? ">" : "")); lcd.print(centered(timeString, buffer)); } else if(currentState == PROG_RAPIDE) { char buffer[8]; snprintf(buffer, 8, "%u mn", duration_prog); lcd.print(centered(timeString, buffer)); } else if(currentState == PROGRAMME) { char buffer[20]; if(pg_step < 5) { snprintf(buffer, 20, "%s%02u%s-%s%02u%s-%s%02u%s %s%02u%s:%s%02u%s", (pg_step == 0 ? "<" : ""), pg_time[0], (pg_step == 0 ? ">" : ""), (pg_step == 1 ? "<" : ""), pg_time[1], (pg_step == 1 ? ">" : ""), (pg_step == 2 ? "<" : ""), pg_time[2], (pg_step == 2 ? ">" : ""), (pg_step == 3 ? "<" : ""), pg_time[3], (pg_step == 3 ? ">" : ""), (pg_step == 4 ? "<" : ""), pg_time[4], (pg_step == 4 ? ">" : "")); } else { snprintf(buffer, 8, "%u mn", duration_prog); } lcd.print(centered(timeString, buffer)); } lcd.setCursor(0, 3); if(currentState == MENU_PROG_COURANT) lcd.print(menuElt("Prog. en cours", true, false)); else if(currentState == MENU_MARCHE_FORCEE) lcd.print(menuElt("Marche forcee", (warm < 0), false)); else if(currentState == MENU_PROG_RAPIDE) lcd.print(menuElt("Programme rapide", false, false)); else if(currentState == MENU_PROGRAMME) lcd.print(menuElt("Programme", false, false)); else if(currentState == MENU_LISTE_PROG) lcd.print(menuElt("Liste programmes", false, false)); else if(currentState == MENU_CHANGER_HEURE) lcd.print(menuElt("Changer heures", false, false)); else if(currentState == MENU_CHANGER_SONNERIE) lcd.print(menuElt("Changer sonnerie", false, true)); else if(currentState == MARCHE_FORCEE || currentState == PROG_COURANT) lcd.print(menuElt("Chauffe Marcel !", true, true)); } delay(100); }