/* * coffee.cpp * * Created on: Sep 25, 2017 * Author: sebastian */ #include #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; int sigValue; int brewTime; //Brew time in ms timer brewTimer(&brewTimeHandler); uint64_t totalHeatingTime; //local copies of the corresponding database entries uint16_t brewCounter; bool initalHeating; const char* StateName[] = {"OFF", "HEATING", "INITHEAT", "IDLE", "BREW", "BREWMAN", "CLEAN", "ERROR", "WAITOFF"}; /** * 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, "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); brewTimer.setDivider(4); brewTimer.stop(); brewTime = 0; lastState = STATE_OFF; initalHeating = true; //read the database values if(!(totalHeatingTime = sqlGetConf(CFGHeatingTime))){ logger_error("coffee.cpp: Couldn't read the heating time from the database"); //pthread_exit(EXIT_SUCCESS); exit(EXIT_FAILURE); } if(!(brewCounter = sqlGetConf(CFGbrewcounter))){ logger_error("coffee.cpp: Couldn't read the brew counter from the database"); //pthread_exit(EXIT_SUCCESS); exit(EXIT_FAILURE); } event_subscribe("terminate", &coffeeTerminate); logger(V_BREW, "Determining inital state\n"); //determine inital state if (halGetRelaisState(RELAIS_POWER) && halGetRelaisState(RELAIS_HEAT) && !halGetRelaisState(RELAIS_PUMP)) { //wait for heat relais to switch sleep(1); if (halIsHeating()) { //Heating is on changeState(STATE_INITALHEATING); } else { initalHeating = false; changeState(STATE_IDLE); } } else if (halGetRelaisState(RELAIS_PUMP)) { logger_error("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) { switch (state) { /* * */ case STATE_OFF: halMachineOff(); writeBackCache(); if (SigValueEmpty()) pause(); if (getSigValue() == SigInt0Rls) { //Check waterlevel in gray water tank //turn machine on halMachineOn(); sleep(1); if (halIsHeating()) { //check if System starts to heat when turned on initalHeating = true; changeState(STATE_INITALHEATING); } else { changeState(STATE_IDLE); } if (halProxSensorCovered()) { logger_error("Empty Tank please!\n");//change the state again to go back to the last state afterwards changeState(STATE_FULLTANK); } break; } break; /* * */ case STATE_WAIT_OFF: if (SigValueEmpty()) pause(); switch (getSigValue()) { case SigPressOpn: usleep(100000);//wait so no load will be switched coffeeIncreaseHeatingTime(halgetHeatingTime()); changeState(STATE_OFF); break; case SigInt0Psh: case SigInt1Psh: if (halProxSensorCovered()) { changeState(STATE_FULLTANK); } else if (initalHeating) { changeState(STATE_INITALHEATING); } else { changeState(STATE_HEATING); } break; } break; /* * */ case STATE_INITALHEATING: initalHeating = true; if (SigValueEmpty()) pause(); switch (getSigValue()) { case SigInt1RlsLong: //Turn machine off again coffeeIncreaseHeatingTime(halgetHeatingTime()); changeState(STATE_OFF); break; case SigInt1Rls: changeState(STATE_WAIT_OFF); break; case SigProxCvrd: changeState(STATE_FULLTANK); break; case SigPressOpn: //Inital heating finished initalHeating = false; coffeeIncreaseHeatingTime(halgetHeatingTime()); changeState(STATE_IDLE); break; } break; /* * */ case STATE_HEATING: if (SigValueEmpty()) pause(); switch (getSigValue()) { case SigInt1RlsLong: //Turn machine _immediately_ off again coffeeIncreaseHeatingTime(halgetHeatingTime()); changeState(STATE_OFF); break; case SigInt1Rls: //turn machine off when heating is finished changeState(STATE_WAIT_OFF); break; case SigPressOpn: coffeeIncreaseHeatingTime(halgetHeatingTime()); changeState(STATE_IDLE); break; case SigInt0Psh: //start to brew a delicious coffee changeState(STATE_BREW); break; case SigProxCvrd: changeState(STATE_FULLTANK); break; case SigBrewOn: //someone brews manually changeState(STATE_BREWMANUAL); break; } break; /* * */ case STATE_IDLE: if (SigValueEmpty()) pause(); switch (getSigValue()) { case SigInt1RlsLong: //turn machine _immediately_ off changeState(STATE_OFF); break; case SigInt1Rls: changeState(STATE_OFF); break; case SigPressCls: changeState(STATE_HEATING); break; case SigInt0Psh: changeState(STATE_BREW); break; case SigProxCvrd: changeState(STATE_FULLTANK); break; case SigBrewOn: //someone brews manually changeState(STATE_BREWMANUAL); break; } break; /* * */ case STATE_BREW: //make sure the tank is not full if (halProxSensorCovered()) { changeState(STATE_FULLTANK); logger_error("coffee.cpp: Full tank detection failed..\n"); } else { coffeeBrew(); logger(V_BREW, "Finishing brewing\n"); if (!halProxSensorCovered()) { if (halIsHeating()) { changeState(STATE_HEATING); } else { changeState(STATE_IDLE); } } else { changeState(STATE_FULLTANK); } } break; /* * */ case STATE_BREWMANUAL: if (SigValueEmpty()) pause(); break; /* * */ case STATE_CLEANING: if (SigValueEmpty()) pause(); 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()) pause(); switch (getSigValue()) { case SigInt1Psh: case SigInt0Psh: if(halIsHeating() && initalHeating){ changeState(STATE_INITALHEATING); } else if (halIsHeating() && !initalHeating){ changeState(STATE_HEATING); } else { changeState(STATE_IDLE); } break; } break; /* * */ case STATE_ERROR: if (SigValueEmpty()) pause(); switch (getSigValue()) { case SigInt1RlsLong: case SigInt0RlsLong: if(halIsHeating()){ coffeeIncreaseHeatingTime(halgetHeatingTime()); } 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 %d\n", sigValue); } /** * returns the Signal value from the last received Signal and clears the variable * @return value sent with the last signal */ int getSigValue(void) { int tmp = sigValue; sigValue = 0; return tmp; } 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, "Changing state to %s\n", StateName[newState]); state = newState; event_trigger("statechange", &state, sizeof(state)); } /** * Returns the current state of the FSM */ coffee_status_t getState(void) { return state; } /** * Counter for the brew time * refresh every 200ms */ void brewTimeHandler(void) { brewTime += 200; } /** * handles program termination */ void coffeeTerminate(event_t *event) { logger(V_BREW, "Coffee.cpp: Thread terminating"); //stop brewing halRelaisOff(RELAIS_PUMP); brewTimer.stop(); writeBackCache(); } /** * Function to write back the values of the local copies * brewCounter and totalHeatingTime * */ void writeBackCache(void){ if (sqlSetConf(CFGbrewcounter, brewCounter)) { logger_error("coffee.cpp: Couldn't write brewcounter to database"); return; } if (sqlSetConf(CFGHeatingTime, totalHeatingTime)) { logger_error("coffee.cpp: Couldn't write heating time to database"); return; } } /** * Brewing process */ void coffeeBrew(void) { coffeeIncreaseBrewCounter(); /* * Preinfusion */ logger(V_BREW, "Starting preinfusion...\n"); halResetFlow(); halRelaisOn(RELAIS_PUMP); brewTime = 0; brewTimer.start(); while (halGetFlow() < AMOUNT_PREINFUSION) { usleep(50000); if (getSigValue() == SigInt1Psh) return; } brewTimer.stop(); brewTime = 0; halRelaisOff(RELAIS_PUMP); /* * Wait for coffee to soak in infused water */ brewTimer.start(); while (brewTime < TIME_SOAK) { usleep(100000); if (getSigValue() == SigInt1Psh) return; } brewTimer.stop(); brewTime = 0; halResetFlow(); /* * Brewing the actual espresso */ logger(V_BREW, "Starting infusion...\n"); halRelaisOn(RELAIS_PUMP); brewTimer.start(); while (brewTime < TIME_INFUSION && halGetFlow() < AMOUNT_DBLESPRESSO) { usleep(100000); if (getSigValue() == SigInt1Psh) return; } halRelaisOff(RELAIS_PUMP); halResetFlow(); brewTimer.stop(); brewTime = 0; return; } /** * */ void coffeeIncreaseBrewCounter(void) { brewCounter++; } /** * */ void coffeeIncreaseHeatingTime(uint64_t heatingTime) { totalHeatingTime += heatingTime; }