/* * coffee.cpp * * Created on: Sep 25, 2017 * Author: Sebastian Vendt */ #include #include #include #include #include #include #include #include #include #include #include #include "coffee.h" #include "hal.h" #include "logger.h" #include "timer.h" #include "database.h" coffee_status_t state; coffee_menuPage_t page; coffee_mode_t mode; int sigValue; int menuTimeout; timer menuTimer(&menuTimerHandler); timer brewTimer(&brewTimerHandler); uint16_t brewCounter; uint16_t brewTime; uint16_t currentCleanCycle; bool initalHeating; bool descaling; //flag to indicate descaling and cleaning uint16_t descBrewcount; time_t descRawTimestamp; uint16_t oldCoffeeCFGVal; //temporary variable to save any old config value to restore when aborting the config change const char* PageName[] = { "SOFTOFF", "KILL", "SINGLEESPRESSO", "STATS", "STATS2", "DESCALING", "TEMP", "CLEAN", "REBOOT", "SETTINGS", "RESETKWH", "RESETKWH_VAL", "CHANGEPREINF", "CHANGEPREINF_VAL", "CHANGEBREWTIME", "CHANGEBREWTIME_VAL", "CHANGEBREWML", "CHANGEBREWML_VAL", "CHANGESOAKTIME", "CHANGESOAKTIME_VAL", "CHANGEAUTORESET", "CHANGEAUTORESET_VAL", "LEAVESETTINGS", "EXIT", "PAGE_NULL" }; const char* StateName[] = { "OFF", "HEATING", "INITHEAT", "IDLE", "BREW", "BREWMAN", "CLEAN", "ERROR", "WAITOFF", "FULLTANK", "STATE_NULL", }; //the reference configuration for brewing coffees coffeeConfig coffeeCFG = {AMOUNT_PREINFUSION, TIME_SOAK, TIME_INFUSION, AMOUNT_DBLESPRESSO}; bool brewSingleEspresso = false; //flag to brew a single espresso only /** * Thread for the finite state machine * It represents the current state of the machine and handles signals coming from * the pressure control, buttons, the brew switch and the proximity sensor * @param threadid the ID of the thread */ void *coffeeThread(void *threadid) { logger(V_BASIC, "coffee.cpp: Initializing coffee thread...\n"); //installing new Signal handler for coffeethread struct sigaction action; sigemptyset(&action.sa_mask); action.sa_flags = SA_SIGINFO; action.sa_sigaction = coffeeHandler; sigaction(SIGUSR2, &action, NULL); currentCleanCycle = 0; menuTimer.setDivider(ms2Divider(200)); menuTimer.stop(); menuTimeout = 0; brewTimer.setDivider(ms2Divider(200)); brewTimer.stop(); brewTime = 0; initalHeating = true; mode = MODE_STATE; //Unless we enter the menu we start in state mode page = PAGE_SOFTOFF; descaling = false; if (!(brewCounter = sqlGetConf(CFGbrewcounter))) { logger_error("coffee.cpp: Couldn't read the brew counter from the database\n"); //pthread_exit(EXIT_SUCCESS); exit(EXIT_FAILURE); } if (!(descRawTimestamp = (time_t) sqlGetConf(CFGDescTimestamp))) { logger_error("coffee.cpp: Couldn't read the descaling time from the database\n"); //pthread_exit(EXIT_SUCCESS); exit(EXIT_FAILURE); } if (!(descBrewcount = sqlGetConf(CFGDescBrewCount))) { logger_error("coffee.cpp: Couldn't read the descaling brewcount time from the database\n"); //pthread_exit(EXIT_SUCCESS); exit(EXIT_FAILURE); } if (!(coffeeCFG.amount_espresso = sqlGetConf(CFGAmountDblEspresso))) { coffeeCFG.amount_espresso = AMOUNT_DBLESPRESSO; logger_error("coffee.cpp: Couldn't read config CFGAmountDblEspresso from database\nFalling back to default!\n"); } if (!(coffeeCFG.amount_preinfusion = sqlGetConf(CFGAmountPreinfusion))) { coffeeCFG.amount_preinfusion = AMOUNT_PREINFUSION; logger_error("coffee.cpp: Couldn't read config CFGAmountPreinfusion from database\nFalling back to default!\n"); } if (!(coffeeCFG.time_infusion = sqlGetConf(CFGTimeInfusion))) { coffeeCFG.time_infusion = TIME_INFUSION; logger_error("coffee.cpp: Couldn't read config CFGTimeInfusion from database\nFalling back to default!\n"); } if (!(coffeeCFG.time_soak = sqlGetConf(CFGTimeSoak))) { coffeeCFG.time_soak = TIME_SOAK; logger_error("coffee.cpp: Couldn't read config CFGTimeSoak from database\nFalling back to default!\n"); } checkDescaling(); event_subscribe("terminate", &coffeeTerminate, "coffee.cpp"); logger(V_BREW, "Determining inital state\n"); //determine initial state if (halGetRelaisState(RELAIS_POWER) && halGetRelaisState(RELAIS_HEAT) && !halGetRelaisState(RELAIS_PUMP)) { //wait for heat relais to switch coffeeNap(1,0); if (halIsHeating()) { //Heating is on changeState(STATE_INITALHEATING); } else { initalHeating = false; changeState(STATE_IDLE); } } else if (halGetRelaisState(RELAIS_PUMP)) { logger_error("coffee.cpp: Whoops, why is the pump running...\n"); changeState(STATE_ERROR); } else { changeState(STATE_OFF); } logger(V_BREW, "Starting Coffee FSM\n"); //begin FSM while (1) { /* * Menu FSM */ switch (page) { case PAGE_NULL: break; case PAGE_SOFTOFF: //this page is only available when the machine is on if (SigValueEmpty() && mode == MODE_MENU) pause(); switch (getSigValue(MODE_MENU)) { case SigInt0Rls: if(halIsHeating()){ changeState(STATE_WAIT_OFF); } else { changeState(STATE_OFF); } leaveMenu(); break; case SigRotCW: changePage(PAGE_KILL); break; case SigRotCCW: changePage(PAGE_EXIT); break; } break; case PAGE_KILL: //this page is only available when the machine is on if (SigValueEmpty() && mode == MODE_MENU) pause(); switch (getSigValue(MODE_MENU)) { case SigInt0Rls: changeState(STATE_OFF); leaveMenu(); break; case SigRotCW: if (state == STATE_HEATING || state == STATE_IDLE) { changePage(PAGE_SINGLEESPRESSO); } else { changePage(PAGE_REBOOT); } break; case SigRotCCW: changePage(PAGE_SOFTOFF); break; } break; case PAGE_SINGLEESPRESSO: //this page is only available when the machine is hot if (SigValueEmpty() && mode == MODE_MENU) pause(); switch (getSigValue(MODE_MENU)) { case SigInt0Rls: brewSingleEspresso = true; changeMode(MODE_STATE); changeState(STATE_BREW); leaveMenu(); break; case SigRotCW: changePage(PAGE_CLEAN); break; case SigRotCCW: changePage(PAGE_KILL); break; } break; case PAGE_CLEAN: //this page is only be available when the machine is hot if (SigValueEmpty() && mode == MODE_MENU) pause(); switch (getSigValue(MODE_MENU)) { case SigInt0Rls: changeMode(MODE_STATE); changeState(STATE_CLEANING); leaveMenu(); break; case SigRotCW: changePage(PAGE_REBOOT); break; case SigRotCCW: changePage(PAGE_SINGLEESPRESSO); break; } break; case PAGE_REBOOT: if (SigValueEmpty() && mode == MODE_MENU) pause(); switch (getSigValue(MODE_MENU)) { case SigInt0Rls: event_trigger("terminate"); coffeeNap(4, 0); system("reboot"); //prevent any further user action coffeeNap(30, 0); break; case SigRotCW: changePage(PAGE_TEMP); break; case SigRotCCW: if(state == STATE_IDLE || state == STATE_HEATING){ changePage(PAGE_CLEAN); } else if (state == STATE_ERROR || state == STATE_INITALHEATING) { changePage(PAGE_KILL); } else { changePage(PAGE_EXIT); } break; } break; case PAGE_TEMP: if (SigValueEmpty() && mode == MODE_MENU) pause(); switch (getSigValue(MODE_MENU)) { case SigRotCW: changePage(PAGE_STATS); break; case SigRotCCW: changePage(PAGE_REBOOT); break; } break; case PAGE_STATS: if (SigValueEmpty() && mode == MODE_MENU) pause(); switch (getSigValue(MODE_MENU)) { case SigRotCW: changePage(PAGE_STATS2); break; case SigRotCCW: changePage(PAGE_TEMP); break; } break; case PAGE_STATS2: if (SigValueEmpty() && mode == MODE_MENU) pause(); switch (getSigValue(MODE_MENU)) { case SigRotCW: changePage(PAGE_DESCALING); break; case SigRotCCW: changePage(PAGE_STATS); break; } break; case PAGE_DESCALING: if (SigValueEmpty() && mode == MODE_MENU) pause(); switch (getSigValue(MODE_MENU)) { case SigRotCW: changePage(PAGE_SETTINGS); break; case SigRotCCW: changePage(PAGE_STATS2); break; } break; case PAGE_SETTINGS: if (SigValueEmpty() && mode == MODE_MENU) pause(); switch (getSigValue(MODE_MENU)) { case SigInt0Rls: changePage(PAGE_RESETKWH); break; case SigRotCW: changePage(PAGE_EXIT); break; case SigRotCCW: changePage(PAGE_DESCALING); break; } break; case PAGE_RESETKWH: if (SigValueEmpty() && mode == MODE_MENU) pause(); switch (getSigValue(MODE_MENU)) { case SigInt0Rls: changePage(PAGE_RESETKWH_VAL); break; case SigRotCW: changePage(PAGE_CHANGEPREINF); break; case SigRotCCW: changePage(PAGE_LEAVESETTINGS); break; } break; case PAGE_RESETKWH_VAL: if (SigValueEmpty() && mode == MODE_MENU) pause(); switch (getSigValue(MODE_MENU)) { case SigInt0Rls: //Reset the KWH counter halSetTotalHeatingTime(0); halWriteBackCache(); changePage(PAGE_RESETKWH); break; case SigInt1Rls: //Abort reseting the KWH counter changePage(PAGE_RESETKWH); break; case SigRotCW: break; case SigRotCCW: break; } break; case PAGE_CHANGEPREINF: if (SigValueEmpty() && mode == MODE_MENU) pause(); switch (getSigValue(MODE_MENU)) { case SigInt0Rls: //temporary store the old value of the config to restore when the change process is aborted oldCoffeeCFGVal = coffeeCFG.amount_preinfusion; changePage(PAGE_CHANGEPREINF_VAL); break; case SigRotCW: changePage(PAGE_CHANGEBREWTIME); break; case SigRotCCW: changePage(PAGE_RESETKWH); break; } break; case PAGE_CHANGEPREINF_VAL: if (SigValueEmpty() && mode == MODE_MENU) pause(); switch (getSigValue(MODE_MENU)) { case SigInt0Rls: writeBackCache(); oldCoffeeCFGVal = 0; changePage(PAGE_CHANGEPREINF); break; case SigInt1Rls: //Abort the change of the preinfusion //restore the previous value coffeeCFG.amount_preinfusion = oldCoffeeCFGVal; oldCoffeeCFGVal = 0; changePage(PAGE_CHANGEPREINF); break; case SigRotCW: coffeeCFG.amount_preinfusion += 1; break; case SigRotCCW: if (coffeeCFG.amount_preinfusion >= 1) coffeeCFG.amount_preinfusion -= 1; break; } break; case PAGE_CHANGEBREWTIME: if (SigValueEmpty() && mode == MODE_MENU) pause(); switch (getSigValue(MODE_MENU)) { case SigInt0Rls: oldCoffeeCFGVal = coffeeCFG.time_infusion; changePage(PAGE_CHANGEBREWTIME_VAL); break; case SigRotCW: changePage(PAGE_CHANGEBREWML); break; case SigRotCCW: changePage(PAGE_CHANGEPREINF); break; } break; case PAGE_CHANGEBREWTIME_VAL: if (SigValueEmpty() && mode == MODE_MENU) pause(); switch (getSigValue(MODE_MENU)) { case SigInt0Rls: writeBackCache(); oldCoffeeCFGVal = 0; changePage(PAGE_CHANGEBREWTIME); break; case SigInt1Rls: //abort the change of the brew time coffeeCFG.time_infusion = oldCoffeeCFGVal; oldCoffeeCFGVal = 0; changePage(PAGE_CHANGEBREWTIME); break; case SigRotCW: coffeeCFG.time_infusion += 1000; break; case SigRotCCW: if (coffeeCFG.time_infusion >= 1000) coffeeCFG.time_infusion -= 1000; break; } break; case PAGE_CHANGEBREWML: if (SigValueEmpty() && mode == MODE_MENU) pause(); switch (getSigValue(MODE_MENU)) { case SigInt0Rls: oldCoffeeCFGVal = coffeeCFG.amount_espresso; changePage(PAGE_CHANGEBREWML_VAL); break; case SigRotCW: changePage(PAGE_CHANGESOAKTIME); break; case SigRotCCW: changePage(PAGE_CHANGEBREWTIME); break; } break; case PAGE_CHANGEBREWML_VAL: if (SigValueEmpty() && mode == MODE_MENU) pause(); switch (getSigValue(MODE_MENU)) { case SigInt0Rls: writeBackCache(); oldCoffeeCFGVal = 0; changePage(PAGE_CHANGEBREWML); break; case SigInt1Rls: //abort the change of the brew amout coffeeCFG.amount_espresso = oldCoffeeCFGVal; oldCoffeeCFGVal = 0; changePage(PAGE_CHANGEBREWML); break; case SigRotCW: coffeeCFG.amount_espresso += 1; break; case SigRotCCW: if (coffeeCFG.amount_espresso >= 1) coffeeCFG.amount_espresso -= 1; break; } break; case PAGE_CHANGESOAKTIME: if (SigValueEmpty() && mode == MODE_MENU) pause(); switch (getSigValue(MODE_MENU)) { case SigInt0Rls: oldCoffeeCFGVal = coffeeCFG.time_soak; changePage(PAGE_CHANGESOAKTIME_VAL); break; case SigRotCW: changePage(PAGE_CHANGEAUTORESET); break; case SigRotCCW: changePage(PAGE_CHANGEBREWML); break; } break; case PAGE_CHANGESOAKTIME_VAL: if (SigValueEmpty() && mode == MODE_MENU) pause(); switch (getSigValue(MODE_MENU)) { case SigInt0Rls: writeBackCache(); oldCoffeeCFGVal = 0; changePage(PAGE_CHANGESOAKTIME); break; case SigInt1Rls: //abort the change of the soak time coffeeCFG.time_soak = oldCoffeeCFGVal; oldCoffeeCFGVal = 0; changePage(PAGE_CHANGESOAKTIME); break; case SigRotCW: coffeeCFG.time_soak += 100; break; case SigRotCCW: if (coffeeCFG.time_soak >= 100) coffeeCFG.time_soak -= 100; break; } break; case PAGE_CHANGEAUTORESET: if (SigValueEmpty() && mode == MODE_MENU) pause(); switch (getSigValue(MODE_MENU)) { case SigInt0Rls: oldCoffeeCFGVal = halGetFlowResetTime(); changePage(PAGE_CHANGEAUTORESET_VAL); break; case SigRotCW: changePage(PAGE_LEAVESETTINGS); break; case SigRotCCW: changePage(PAGE_CHANGESOAKTIME); break; } break; case PAGE_CHANGEAUTORESET_VAL: if (SigValueEmpty() && mode == MODE_MENU) pause(); switch (getSigValue(MODE_MENU)) { case SigInt0Rls: halWriteBackCache(); oldCoffeeCFGVal = 0; changePage(PAGE_CHANGEAUTORESET); break; case SigInt1Rls: //abort the change of the auto reset halSetFlowResetTime(oldCoffeeCFGVal); oldCoffeeCFGVal = 0; changePage(PAGE_CHANGEAUTORESET); break; case SigRotCW: halSetFlowResetTime(halGetFlowResetTime() + 100); break; case SigRotCCW: if (halGetFlowResetTime() >= 100) halSetFlowResetTime(halGetFlowResetTime() - 100); break; } break; case PAGE_LEAVESETTINGS: if (SigValueEmpty() && mode == MODE_MENU) pause(); switch (getSigValue(MODE_MENU)) { case SigInt0Rls: changePage(PAGE_SETTINGS); break; case SigRotCW: changePage(PAGE_RESETKWH); break; case SigRotCCW: changePage(PAGE_CHANGESOAKTIME); break; } break; case PAGE_EXIT: if (SigValueEmpty() && mode == MODE_MENU) pause(); switch (getSigValue(MODE_MENU)) { case SigInt0Rls: leaveMenu(); break; case SigRotCW: if (state == STATE_HEATING || state == STATE_ERROR || state == STATE_IDLE || state == STATE_INITALHEATING) { changePage(PAGE_SOFTOFF); } else { changePage(PAGE_REBOOT); } break; case SigRotCCW: changePage(PAGE_SETTINGS); break; } break; } //end switch (page) /* * Hardware FSM */ switch (state) { case STATE_NULL: break; /* * */ case STATE_OFF: if (mode == MODE_STATE) { halMachineOff(); writeBackCache(); // his might be a bit confusing to change the page here even if the menu isn't actually displayed changePage(PAGE_REBOOT); if (SigValueEmpty()) pause(); } switch (getSigValue(MODE_STATE)) { case SigInt0Rls: case SigPowerUp: //Check waterlevel in gray water tank //turn machine on halMachineOn(); coffeeNap(1,500000000); if (halIsHeating() && !halProxSensorCovered()) { //check if System starts to heat when turned on changeState(STATE_INITALHEATING); } else if (!halIsHeating() && !halProxSensorCovered()) { changeState(STATE_IDLE); } else if (halProxSensorCovered()) { logger_error("coffee.cpp: Full graywater tank detected\n"); changeState(STATE_FULLTANK); } if (page != PAGE_SOFTOFF) changePage(PAGE_SOFTOFF); //the machine is on, the menu starts with the turning off page break; case SigRotCCW: case SigRotCW: case SigInt1Rls: //Enter the menu enterMenu(); break; } break; /* * */ case STATE_WAIT_OFF: if (SigValueEmpty() && mode == MODE_STATE) pause(); switch (getSigValue(MODE_STATE)) { case SigPressOpn: coffeeNap(1,0); //wait so no load will be switched changeState(STATE_OFF); break; case SigInt0Rls: case SigInt1Rls: case SigPowerUp: if (halProxSensorCovered()) { changeState(STATE_FULLTANK); } else if (initalHeating) { changeState(STATE_INITALHEATING); } else { changeState(STATE_HEATING); } break; } break; /* * */ case STATE_INITALHEATING: initalHeating = true; if (SigValueEmpty() && mode == MODE_STATE) pause(); switch (getSigValue(MODE_STATE)) { case SigPressOpn: //Inital heating finished initalHeating = false; changeState(STATE_IDLE); break; case SigPowerDown: changeState(STATE_WAIT_OFF); break; case SigRotCCW: case SigRotCW: case SigInt1Rls: enterMenu(); break; } break; /* * */ case STATE_HEATING: if (SigValueEmpty() && mode == MODE_STATE) pause(); switch (getSigValue(MODE_STATE)) { case SigPressOpn: changeState(STATE_IDLE); break; case SigInt0Rls: //start to brew a delicious coffee changeState(STATE_BREW); break; case SigBrewOn: //someone brews manually coffeeManualBrewStart(); changeState(STATE_BREWMANUAL); break; case SigPowerDown: changeState(STATE_WAIT_OFF); break; case SigRotCCW: case SigRotCW: case SigInt1Rls: //Enter the menu enterMenu(); break; } break; /* * */ case STATE_IDLE: if (SigValueEmpty() && mode == MODE_STATE) pause(); switch (getSigValue(MODE_STATE)) { case SigPressCls: changeState(STATE_HEATING); break; case SigInt0Rls: changeState(STATE_BREW); break; case SigBrewOn: //someone brews manually coffeeManualBrewStart(); changeState(STATE_BREWMANUAL); break; case SigPowerDown: changeState(STATE_OFF); break; case SigRotCCW: case SigRotCW: case SigInt1Rls: //Enter the menu enterMenu(); break; } break; /* * */ case STATE_BREW: //make sure the tank is not full if (halProxSensorCovered()) { changeState(STATE_FULLTANK); } else { if (brewSingleEspresso) { coffeeBrew(coffeeCFG.amount_preinfusion, coffeeCFG.time_soak, coffeeCFG.time_infusion, coffeeCFG.amount_espresso / 2); brewSingleEspresso = false; } else { coffeeBrew(coffeeCFG.amount_preinfusion, coffeeCFG.time_soak, coffeeCFG.time_infusion, coffeeCFG.amount_espresso); } checkDescaling(); logger(V_BREW, "coffee.cpp: Finished brewing\n"); if (halProxSensorCovered()) { changeState(STATE_FULLTANK); } else if (halIsHeating()) { changeState(STATE_HEATING); } else { changeState(STATE_IDLE); } } break; /* * */ case STATE_BREWMANUAL: if (SigValueEmpty() && mode == MODE_STATE) pause(); switch (getSigValue(MODE_STATE)) { case SigBrewOff: if (halProxSensorCovered()) { changeState(STATE_FULLTANK); } else if (halIsHeating()) { changeState(STATE_HEATING); } else { changeState(STATE_IDLE); } break; } break; /* * */ case STATE_CLEANING: //this can only be executed once the machine is hot! if (!halProxSensorCovered()) { //execute the cleaning procedure coffeeClean(); if (halProxSensorCovered()) { changeState(STATE_FULLTANK); } else if (halIsHeating()) { changeState(STATE_HEATING); } else { changeState(STATE_IDLE); } } else { changeState(STATE_FULLTANK); } break; /* * Full tank is detected at the beginning and the end of a brewing process, during * idle times, initial heating and heating */ case STATE_FULLTANK: if (SigValueEmpty() && mode == MODE_STATE) pause(); switch (getSigValue(MODE_STATE)) { case SigInt0RlsLong: if (!halProxSensorCovered()) { if (halIsHeating() && initalHeating) { changeState(STATE_INITALHEATING); } else if (halIsHeating() && !initalHeating) { changeState(STATE_HEATING); } else { changeState(STATE_IDLE); } } break; } break; /* * */ case STATE_ERROR: if (SigValueEmpty() && mode == MODE_STATE) pause(); switch (getSigValue(MODE_STATE)) { case SigInt0RlsLong: changeState(STATE_OFF); break; } } } pthread_exit(EXIT_SUCCESS); } /** * Handler for the Signal send to this thread * It saves the type of signal received and tracks the time between a push and release event of up to 4 signals * The time is stored in the HalEvent variable when a release event is received * @param signum * @param siginfo * @param context */ void coffeeHandler(int signum, siginfo_t *siginfo, void *context) { sigval_t sigVal = (siginfo->si_value); sigValue = sigVal.sival_int; logger(V_BREW, "coffee.cpp: CoffeeHandler called with Signal %s\n", SigName[sigValue]); } /** * returns the Signal value from the last received Signal for the given mode and clears the variable * @return value sent with the last signal */ int getSigValue(coffee_mode_t mode) { int tmp = sigValue; if (mode == MODE_MENU) { switch (sigValue) { case SigInt0Psh: case SigInt0Rls: case SigInt0RlsLong: case SigInt1Psh: case SigInt1Rls: case SigRotCCW: case SigRotCW: sigValue = 0; menuTimeout = 0; return tmp; break; default: break; } } else { //State Mode sigValue = 0; return tmp; } //int tmp = sigValue; //sigValue = 0; //return tmp; return 0; } bool SigValueEmpty(void) { if (sigValue == 0) return true; else return false; } /** * Changes the state of the machine to newState * prints the change to the logger * @param newState */ void changeState(coffee_status_t newState) { logger(V_BREW, "coffee.cpp: Changing state to %s\n", StateName[newState]); state = newState; event_trigger("statechange", &state, sizeof(state)); } /* * Change Page to new menu page */ void changePage(coffee_menuPage_t newPage) { logger(V_BREW, "coffee.cpp: Change Page to %s\n", PageName[newPage]); event_trigger("pagechange", &newPage, sizeof(newPage)); page = newPage; } /* * Changes the mode of the machine to the given mode */ void changeMode(coffee_mode_t newMode) { if (newMode == MODE_MENU) logger(V_BREW, "coffee.cpp: Changing to menu mode\n"); else logger(V_BREW, "coffee.cpp: Changing to state mode\n"); event_trigger("modechange", &newMode, sizeof(newMode)); mode = newMode; } /* * leaving the menu * sets the start page for the next menu call to softoff */ void leaveMenu(void) { logger(V_BREW, "coffee.cpp: Leaving the menu again\n"); //leave the menu again changeMode(MODE_STATE); //change page to initial page changePage(PAGE_SOFTOFF); //stop the timeout counter menuTimeout = 0; menuTimer.stop(); } /** * entering the menu * starts the timeoutcounter and changes the mode to the Menu Mode */ void enterMenu(void) { logger(V_BREW, "coffee.cpp: Entering the menu\n"); changeMode(MODE_MENU); menuTimeout = 0; menuTimer.start(); } /** * Returns the current cleaning cycle */ uint16_t getCurrentCleanCycle(void) { return currentCleanCycle; } /** * Returns the current state of the FSM */ coffee_status_t getState(void) { return state; } /** * Returns the local up-to-date brewcounter */ uint16_t getBrewCounter(void) { return brewCounter; } /** * Returns the value of the brewcounter when the last descaling happened */ uint16_t getDescBrewCounter (void) { return descBrewcount; } /** * Returns the raw time stamp when the last descaling happened */ time_t * getDescTimestamp (void) { return &descRawTimestamp; } /** * Returns a pointer to the current coffee config */ coffeeConfig* getCoffeeCfg (void) { return &coffeeCFG; } /** * Counter for the menu timeout * When no input is coming from the user the machine leaves the menu automatically after MENUTIMEOUT seconds */ void menuTimerHandler(void){ menuTimeout += 200; if((menuTimeout/1000) >= MENUTIMEOUT) { leaveMenu(); } } /** * handles program termination */ void coffeeTerminate(event_t *event) { logger(V_BREW, "Coffee.cpp: Terminating\n"); //stop brewing halRelaisOff(RELAIS_PUMP); writeBackCache(); } /** * coffeeNap does almost the same as sleep() but resumes to sleep after a signal got delivered * sec: seconds to sleep * nanosec: nanoseconds to sleep */ void coffeeNap (uint64_t sec, uint64_t nanosec){ struct timespec sleepTime; clock_gettime(CLOCK_REALTIME, &sleepTime); sleepTime.tv_sec += sec; sleepTime.tv_nsec += nanosec; //overflow if(sleepTime.tv_nsec >= 1000000000) { sleepTime.tv_nsec -= 1000000000; sleepTime.tv_sec++; } int errval = 0; do{ errval = clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &sleepTime, NULL); } while(errval == EINTR); if(errval) logger_error("coffee.cpp: Suspending thread failed with error %d\n", errval); } /** * Function to write back the values of the local copies * brewCounter and totalHeatingTime * */ void writeBackCache(void) { logger(V_BREW, "Writing back cache...\n"); logger(V_BREW, "Writing back brewCounter: %d\n", brewCounter); if (sqlSetConf(CFGbrewcounter, brewCounter)) { logger_error("coffee.cpp: Couldn't write brewcounter to database!\n"); } logger(V_BREW, "Writing back descaling brew counter: %d\n", descBrewcount); if (sqlSetConf(CFGDescBrewCount, descBrewcount)) { logger_error("coffee.cpp: Couldn't write descaling brewcount to database!\n"); } struct tm * timeinfo; timeinfo = localtime(&descRawTimestamp); logger(V_BREW, "Writing back descaling timestamp %d-%d-%d %d:%d\n", timeinfo->tm_mday, timeinfo->tm_mon, timeinfo->tm_year+1900, timeinfo->tm_hour, timeinfo->tm_min); if (sqlSetConf(CFGDescTimestamp, (uint64_t)descRawTimestamp)) { logger_error("coffee.cpp: Couldn't write descaling timestamp to database!\n"); } logger(V_BREW, "Writing back CFGAmountPreinfusion: %d\n", coffeeCFG.amount_preinfusion); if (sqlSetConf(CFGAmountPreinfusion, coffeeCFG.amount_preinfusion)) { logger_error("coffee.cpp: Couldn't write CFGAmountPreinfusion to database!\n"); } logger(V_BREW, "Writing back CFGTimeSoak: %d\n", coffeeCFG.time_soak); if (sqlSetConf(CFGTimeSoak, coffeeCFG.time_soak)) { logger_error("coffee.cpp: Couldn't write CFGTimeSoak to database!\n"); } logger(V_BREW, "Writing back CFGTimeInfusion: %d\n", coffeeCFG.time_infusion); if (sqlSetConf(CFGTimeInfusion, coffeeCFG.time_infusion)) { logger_error("coffee.cpp: Couldn't write CFGTimeInfusion to database!\n"); } logger(V_BREW, "Writing back CFGAmountDblEspresso: %d\n", coffeeCFG.amount_espresso); if (sqlSetConf(CFGAmountDblEspresso, coffeeCFG.amount_espresso)) { logger_error("coffee.cpp: Couldn't write CFGAmountDblEspresso to database!\n"); } } /* * Procedure for cleaning the machine */ void coffeeClean(void) { logger(V_BREW, "coffee.cpp: Cleaning...\n"); for (int i = 0; i < CLEANING_CYCLES; i++) { currentCleanCycle++; halPumpOn(); coffeeNap(3,0); halPumpOff(); coffeeNap(15,0); } updateDescaling(); descaling = false; currentCleanCycle = 0; event_trigger("descaling", &descaling, sizeof(bool)); } /* * */ void coffeeManualBrewStart (void) { logger(V_BREW, "coffee.cpp: Starting manual brewing...\n"); coffeeIncreaseBrewCounter(); } /** * Brewing process */ void coffeeBrew(uint16_t amount_preinfusion, uint16_t time_soak, uint16_t time_infusion, uint16_t amount_espresso) { coffeeIncreaseBrewCounter(); /* * Preinfusion */ logger(V_BREW, "coffee.cpp: Starting preinfusion...\n"); halPumpOn(); while (halGetFlow() < amount_preinfusion && halGetFlowTime() < TIME_PREINFUSION) { //Don't use coffeeNap here since we don't want to resume to sleep after a signal got caught... //coffeeNap(0, 50000000); usleep(50000); if (getSigValue(MODE_STATE) == SigInt0Rls) { halPumpOff(); return; } } halPumpOff(); /* * Wait for coffee to soak in infused water */ brewTimer.start(); while (brewTime < time_soak) { //coffeeNap(1, 100000000); usleep(100000); if (getSigValue(MODE_STATE) == SigInt0Rls) { brewTimer.stop(); brewTime = 0; return; } } brewTimer.stop(); brewTime = 0; /* * Brewing the actual espresso */ logger(V_BREW, "coffee.cpp: Starting infusion...\n"); halPumpOn(); brewTimer.start(); while (brewTime < time_infusion && halGetFlow() < amount_espresso) { //coffeeNap(1, 100000000); usleep(100000); if (getSigValue(MODE_STATE) == SigInt0Rls){ halPumpOff(); return; } } halPumpOff(); brewTimer.stop(); brewTime = 0; return; } void brewTimerHandler(){ brewTime += 200; } /** * */ void coffeeIncreaseBrewCounter(void) { brewCounter++; } /** * Checks if the descaling is necessary * uses descBrewcount and descTimestamp */ void checkDescaling(){ int16_t dirtyEspresso = checkDirtyEspresso (); int16_t dirtyTime = checkDirtyTime (); if(dirtyEspresso <= 0) { logger(V_BREW, "coffee.cpp: Descaling necessary due to quantity: %d\n", dirtyEspresso); descaling = true; event_trigger("descaling", &descaling, sizeof(bool)); } if(dirtyTime <= 0) { logger(V_BREW, "coffee.cpp: Descaling necessary due to time in days: %d\n", dirtyTime); descaling = true; event_trigger("descaling", &descaling, sizeof(bool)); } } /** * this function returns the remaining espressi to be brewed before the descaling event is fired * returns a positive integer when there are cups remaining * and a negative when the number of cups are exceeded * Number of cups after a descaling is defined with DIRTY_ESPRESSO */ int16_t checkDirtyEspresso (void) { return descBrewcount + DIRTY_ESPRESSO - brewCounter; } /** * Returns the remaining days before the next descaling event is fired * returns a positive integer if there is time left and a negative one if the descaling time was exceeded */ int16_t checkDirtyTime (void) { time_t rawtime; time(&rawtime); double diffseconds = difftime(rawtime, descRawTimestamp); diffseconds /= 24*60*60; //calculate the days return DIRTY_TIME - diffseconds; } /** * updates the corresponding variables after a descaling process */ void updateDescaling(){ descBrewcount = brewCounter; time_t newDesTimestamp; time(&newDesTimestamp); if(newDesTimestamp == -1){ logger(V_BREW, "coffee.cpp: Whoops, couldn't retrieve new descaling timestamp\n"); } else { descRawTimestamp = newDesTimestamp; } }