/* * hal.cpp * * Created on: Aug 3, 2016 * Author: Philipp Hinz, Sebastian Vendt */ #include #include #include #include #include #include #include #include #include #include "hal.h" #include "global.h" #include "logger.h" #include "timer.h" #include "database.h" const char* SigName[] = {"SigInt0Psh", "SigInt0Rls", "SigInt0RlsLong", "SigInt1Psh", "SigInt1Rls", "SigInt1RlsLong", "SigPressCls", "SigPressOpn", "SigProxOpn", "SigProxCvrd", "SigBrewOn", "SigBrewOff", "SigPowerUp", "SigPowerDown", "SigRotCW", "SigRotCCW"}; typedef struct timespec timespec; volatile int flowcnt = 0; int lastFlowcnt = 0; int flowResetValue = -1; bool brewmanual = false; int Int0Time, Int1Time; int idleCounter; bool idle; bool flagIgnoreRlsInt0, flagIgnoreRlsInt1; //storage for the last state of the buttons, the proximity sensor and the pressure sensor int pinState[4] = {1, 1, 1, 0}; //state of the rotary encoder //0: rotary1 state at t-1 //1: rotary2 state at t-1 //2: rotary1 state at t //3: rotary2 state at t int rotaryState[4] = {1, 0, 0, 1}; //sweep counter to log every brew uint16_t logcycle = 1; timer Int0Timer(&halInt0TimerHandler); timer Int1Timer(&halInt1TimerHandler); timer idleTimer(&halIdleTimerHandler); timer flowResetTimer (&flowResetTimerHandler); time_t heatingCycle[] = {0, 0}; timespec flowTimestep[] = {{0,0},{0,0}}; uint8_t flowIndex = 0; int16_t tickCounter = 0; timespec pumpCycle[] = {{0,0},{0,0}}; //delay of the debounce in milliseconds #define DELAY_DEBOUNCE 50 #define DELAY_MICRODEB 2 //display turn off after idle time in min //minimal time is 2min #define IDLE_TIME 10 /** * Initializes HAL */ void halInit(void) { pinMode(RELAIS_HEAT, OUTPUT); pinMode(RELAIS_PUMP, OUTPUT); pinMode(RELAIS_POWER, OUTPUT); pinMode(PIN_PRESSURE_CTRL, INPUT); pinMode(PIN_PROXIMITY_SENSOR, INPUT); pinMode(PIN_INT0, INPUT); pinMode(PIN_INT1, INPUT); pinMode(PIN_FLOW, INPUT); pinMode(PIN_DISP, OUTPUT); pinMode(PIN_ROTARY1, INPUT); pinMode(PIN_ROTARY2, INPUT); idleTimer.setDivider(1200); //1 min idleCounter = 0; idle = false; clock_gettime(CLOCK_REALTIME, &flowTimestep[0]); clock_gettime(CLOCK_REALTIME, &flowTimestep[1]); halDisplayOn(); if (optPower) { halMachineOn(); } else { halMachineOff(); } sleep(1); //wait till the machine eventually turned on when optPower pinState[3] = halIsHeating(); Int0Timer.setDivider(4); //200ms Int1Timer.setDivider(4); Int0Time = 0; Int1Time = 0; flowResetTimer.setDivider(20); //1000ms flagIgnoreRlsInt0 = false; flagIgnoreRlsInt1 = false; event_subscribe("terminate", &halTerminate); if (wiringPiISR(PIN_INT0, INT_EDGE_BOTH, &halInt0) < 0) { logger_error("Unable to setup ISR0: %s\n", strerror(errno)); return; } if (wiringPiISR(PIN_INT1, INT_EDGE_BOTH, &halInt1) < 0) { logger_error("Unable to setup ISR1: %s\n", strerror(errno)); return; } if (wiringPiISR(PIN_FLOW, INT_EDGE_FALLING, &halIntFlow) < 0) { logger_error("Unable to setup ISRFLOW: %s\n", strerror(errno)); return; } if (wiringPiISR(PIN_PRESSURE_CTRL, INT_EDGE_BOTH, &halIntPressure) < 0) { logger_error("Unable to setup ISRPressure: %s\n", strerror(errno)); return; } if (wiringPiISR(PIN_PROXIMITY_SENSOR, INT_EDGE_BOTH, &halIntProximity) < 0) { logger_error("Unable to setup ISRProximity: %s\n", strerror(errno)); return; } if (wiringPiISR(PIN_ROTARY1, INT_EDGE_BOTH, &halIntRotary) < 0) { logger_error("Unable to setup ISRRotary2: %s\n", strerror(errno)); return; } //TODO when machine is turned off above the logcycle 1 is written back from cache... //TODO logcycle is not used atm if (!(logcycle = sqlGetConf(CFGSweepCounter))) { logger_error("hal.cpp: Couldn't read the logcycle counter from the database\n"); //pthread_exit(EXIT_SUCCESS); exit(EXIT_FAILURE); } } /** * Switches relais on * @param relais Relais ID */ void halRelaisOn(int relais) { halRelaisSet(relais, LOW); } /** * Turn the display off */ void halDisplayOff(){ digitalWrite(PIN_DISP, LOW); } /** * Turn the display on */ void halDisplayOn(){ digitalWrite(PIN_DISP, HIGH); } /** * Switches relais off * @param relais Relais ID */ void halRelaisOff(int relais) { halRelaisSet(relais, HIGH); } /** * Switches relais to state * @param relais Relais ID * @param state LOW(0) or HIGH(1) */ void halRelaisSet(int relais, int state) { if (state != HIGH && state != LOW) return; switch (relais) { case RELAIS_POWER: case RELAIS_HEAT: case RELAIS_PUMP: digitalWrite(relais, state); break; } } /** * Returns the state of the relais relais * Returns HIGH when Relais is ON * @param relais Relais ID */ int halGetRelaisState(int relais) { switch (relais) { case RELAIS_POWER: case RELAIS_HEAT: case RELAIS_PUMP: return !digitalRead(relais); break; } return -1; } /** * */ void halIntRotary(void) { //delay(DELAY_MICRODEB); rotaryState[2] = digitalRead(PIN_ROTARY1); rotaryState[3] = digitalRead(PIN_ROTARY2); if (rotaryState[0] != rotaryState[2]) { //check for the status of the other pin if (rotaryState[1] != rotaryState[3]) { if ((rotaryState[2] == HIGH && rotaryState[3] == LOW) || (rotaryState[2] == LOW && rotaryState[3] == HIGH)) { //clockwise rotation tickCounter++; if (!(abs(tickCounter) % ROTARY_STEPSIZE)) { //logger(V_HAL, "rotary encoder CW \n"); halSendSignal(SigRotCW); tickCounter = 0; } } else if ((rotaryState[2] == HIGH && rotaryState[3] == HIGH) || (rotaryState[2] == LOW && rotaryState[3] == LOW)) { //counterclockwise rotation tickCounter--; if (!(abs(tickCounter) % ROTARY_STEPSIZE)) { //logger(V_HAL, "rotary encoder CCW \n"); halSendSignal(SigRotCCW); tickCounter = 0; } } rotaryState[1] = rotaryState[3]; } rotaryState[0] = rotaryState[2]; } } /** * Interrupt routine for Int0 (Top button) */ void halInt0(void) { //wait for a debounce delay(DELAY_DEBOUNCE); if (halGetInt0() && !pinState[0]) { //released logger(V_HAL, "Int0 released\n"); pinState[0] = 1; if (flagIgnoreRlsInt0) { flagIgnoreRlsInt0 = false; } else { Int0Time = 0; Int0Timer.stop(); halSendSignal(SigInt0Rls); } } else if(!halGetInt0() && pinState[0]) { //pressed logger(V_HAL, "Int0 pushed\n"); pinState[0] = 0; halSendSignal(SigInt0Psh); Int0Time = 0; Int0Timer.start(); } } /** * */ void halInt0TimerHandler(void) { Int0Time += 200; if (Int0Time >= (TIME_BUTTONLONGPRESS * 1000)) { halSendSignal(SigInt0RlsLong); flagIgnoreRlsInt0 = true; Int0Time = 0; Int0Timer.stop(); } } /** * */ void halIdleTimerHandler(void) { //TODO the idle counter is once resetted when a button is pressed. From this moment the machine //will enter the idle state no matter what the user does -> reset Idle counter on every input! if(++idleCounter == IDLE_TIME){ halEnterIdle(); } } /** * Interrupt routine for Int1 (Bottom button) */ void halInt1(void) { delay(DELAY_DEBOUNCE); if (halGetInt1() && !pinState[1]) { logger(V_HAL, "Int1 released\n"); pinState[1] = 1; if (flagIgnoreRlsInt1) { flagIgnoreRlsInt1 = false; } else { Int1Time = 0; Int1Timer.stop(); halSendSignal(SigInt1Rls); } } else if(!halGetInt1() && pinState[1]) { logger(V_HAL, "Int1 pushed\n"); pinState[1] = 0; halSendSignal(SigInt1Psh); Int1Time = 0; Int1Timer.start(); } } /* * */ void halInt1TimerHandler(void) { Int1Time += 200; if (Int1Time >= (TIME_BUTTONLONGPRESS * 1000)) { halSendSignal(SigInt1RlsLong); flagIgnoreRlsInt1 = true; Int1Time = 0; Int1Timer.stop(); } } /** * Timer handler to auto-reset the flow counter. * The timer is started when the flow interrupt is triggered. * It compares the last value (flowResetValue) with the current flow value (flowcnt) * If they match (e.g no flow within 1000ms) the flow counter is reseted. * Setting the last value to -1 ensures that when the timer gets called immediately after it is started the comparison will fail. * This implementation is read-only and so it doesn't need semaphores. */ void flowResetTimerHandler() { if(flowResetValue == flowcnt) { halResetFlow(); return; } flowResetValue = flowcnt; } /** * Interrupt routine for the flow sensor * It counts the edges and stores the value in flowcnt */ void halIntFlow(void) { //halRelaisOff(RELAIS_POWER); if(!flowResetTimer.isActive()) flowResetTimer.start(); logger(V_HAL, "IntFlow triggered #%d total: %.2fml\n", flowcnt, halGetFlow()); flowcnt++; if(flowcnt >= BREW_MANUAL_TRIGGER) { halSendSignal(SigBrewOn); brewmanual = true; } //detect a manual brewing process //TODO to be able to detect a manual brewing process we need to have the following //autoclear the flowcnt after certain idle time //detect when the the flow is triggered but the pump is not started from software //here we need to suppress the false alarms when the pump is turned off and slowly running out //subroutine to log the flow to the database /*timespec deltaT; clock_gettime(CLOCK_REALTIME, &flowTimestep[flowIndex]); timespec_diff(&flowTimestep[((flowIndex + 1) % 2)], &flowTimestep[flowIndex], &deltaT); if (sqlLogFlow(logcycle, halGetFlow()*1000, deltaT.tv_sec * 1000 + deltaT.tv_nsec/1000000)) { logger_error("hal.cpp: could not log flow to database!"); return; } flowIndex = (flowIndex + 1) % 2;*/ } /** * Interrupt routine for the pressure control * It captures the time at closing point and opening point * Reading heating time via the getHeatingTime function */ void halIntPressure(void) { logger(V_HAL, "IntPressure Control triggered\n"); if (halIsHeating() && !pinState[3]) { pinState[3] = 1; time(&heatingCycle[0]); halSendSignal(SigPressCls); } else if(!halIsHeating() && pinState[3]) { pinState[3] = 0; time(&heatingCycle[1]); halSendSignal(SigPressOpn); } } /** * Function to read the heating time in sec * If called during a heating process, it returns the time elapsed since the heating started * If called after a heating process, it returns the total time elapsed during the heating cycle */ double halgetHeatingTime(void){ //TODO check return value on negative times //TODO move the tracking of the heating time into hal.cpp and fix issue of wrong tracking -> inital pressure close is not //recognized and so heatingCycle has no time if (halIsHeating()) { logger(V_HAL, "Hot Heating Time: %f\n", difftime(time(NULL), heatingCycle[0])); return difftime(time(0), heatingCycle[0]); } else { logger(V_HAL, "Heating time: %f\n", difftime(heatingCycle[1], heatingCycle[0])); return difftime(heatingCycle[1], heatingCycle[0]); } } /** * Method to handle toggle of the proximity sensor */ void halIntProximity(void) { delay(DELAY_DEBOUNCE); if (halProxSensorCovered() && !pinState[2]) { logger(V_HAL, "IntProximity triggered\n"); pinState[2] = 1; halSendSignal(SigProxCvrd); } else if(!halProxSensorCovered() && pinState[2]){ logger(V_HAL, "IntProximity triggered\n"); pinState[2] = 0; halSendSignal(SigProxOpn); } } /** * Returns total flow through sensor in ml */ float halGetFlow(void) { return flowcnt * FLOW_ML_PULSE; } /* * Returns the last total flow through the sensor in ml after reset */ float halGetLastFlow(void) { return lastFlowcnt * FLOW_ML_PULSE; } /** * Resets the Flow counter */ void halResetFlow(void) { logger(V_HAL, "Flow counter reset, amount so far: %.2f ml\n", halGetFlow()); lastFlowcnt = flowcnt; flowcnt = 0; flowResetTimer.stop(); flowResetValue = -1; if(brewmanual) { brewmanual = false; halSendSignal(SigBrewOff); } } /** * Reads the status of the Pressure Control * @return 1 (true) for closed Pressure Control(heating) and 0 (false) for open */ bool halIsHeating(void) { if (digitalRead(PIN_PRESSURE_CTRL) == 0) { return true; } else { return false; } } /** * Returns status of the proximity switch * @return 1 if the proximity switch is covered and 0 if uncovered */ bool halProxSensorCovered(void) { if(digitalRead(PIN_PROXIMITY_SENSOR) == 0){ return false; } else { return true; } } /** * Returns the value of the top button Int0 (low active) * @return LOW or HIGH */ int halGetInt0(void) { return digitalRead(PIN_INT0); } /** * Returns the value of the bottom button Int1 (low active) * @return LOW or HIGH */ int halGetInt1(void) { return digitalRead(PIN_INT1); } /** * send Signal to coffee thread * @param val Integer value assigned to signal */ void halSendSignal(HalSig val) { //reboot pi when lower button is pressed long if (val == SigInt1RlsLong) { event_trigger("terminate"); sleep(3); system("reboot"); } //catch if machine is idle and drop button event switch (val) { case SigInt0Psh: case SigInt1Psh: if (idle) { return; } break; case SigInt0Rls: case SigInt0RlsLong: case SigInt1Rls: case SigRotCCW: case SigRotCW: if (idle) { halLeaveIdle(); return; } break; default: break; } sigval value = { 0 }; value.sival_int = (int) val; try { if (pthread_sigqueue(thread[THREAD_COFFEE], SIGUSR2, value)) { logger_error("hal.cpp: Failed to queue signal %d %s\n", val, strerror(errno)); //No Signals reach the state machine anymore... exit(EXIT_FAILURE); } } catch (int e) { logger_error("Whoops.. %d\n", e); } } /** * Turn machine on */ void halMachineOn(void) { halRelaisOn(RELAIS_HEAT); halRelaisOff(RELAIS_PUMP); halRelaisOn(RELAIS_POWER); idleTimer.stop(); logger(V_HAL, "Turning machine on\n"); } /** * Turn machine off */ void halMachineOff(void) { halRelaisOff(RELAIS_HEAT); halRelaisOff(RELAIS_PUMP); halRelaisOff(RELAIS_POWER); idleCounter = 0; idleTimer.start(); halWriteBackCache(); logger(V_HAL, "Turning machine off\n"); } /** * */ void halEnterIdle(void){ logger(V_HAL, "Entering Idle Mode\n"); idleTimer.stop(); halDisplayOff(); idle = true; } /** * */ void halLeaveIdle(void){ idleCounter = 0; logger(V_HAL, "Leaving Idle Mode\n"); halDisplayOn(); idleTimer.start(); idle = false; } /** * Wrapper function to turn the pump on * and to measure how long the pump is running * @param cycle the number of the sweep in the database */ void halPumpOn(){ halRelaisOn(RELAIS_PUMP); clock_gettime(CLOCK_REALTIME, &pumpCycle[0]); } /** * */ void halIncreaseLogCycleCounter(void){ logcycle = logcycle + 1; } /** * Wrapper function to turn the pump off * and to measure how long the pump is running */ void halPumpOff(void){ halRelaisOff(RELAIS_PUMP); clock_gettime(CLOCK_REALTIME, &pumpCycle[1]); } /** * Function to get the elapsed time the pump is running in ms * when the pump is on, this function returns the time between turning the pump on and the call * when the pump is off, this function returns the time elapsed in the last pump cycle */ double halGetPumpTime(void){ timespec now; timespec diff = {0,0}; if(halGetRelaisState(RELAIS_PUMP) == HIGH){//pump is on clock_gettime(CLOCK_REALTIME, &now); timespec_diff(&pumpCycle[0], &now, &diff); } else { timespec_diff(&pumpCycle[0], &pumpCycle[1], &diff); } return diff.tv_sec * 1000 + diff.tv_nsec/1000000; } /** * Function to calculate the difference between two timespecs */ void timespec_diff(timespec *start, timespec *stop, timespec *result) { long int secDiff = stop->tv_sec - start->tv_sec; long int nsecDiff = stop->tv_nsec - start->tv_nsec; if (secDiff > 0) { if (nsecDiff >= 0) { result->tv_sec = secDiff; result->tv_nsec = nsecDiff; } else if (nsecDiff < 0) { result->tv_sec = --secDiff; result->tv_nsec = 1000000000 + nsecDiff; } } else if (secDiff < 0) { if (nsecDiff >= 0) { result->tv_sec = ++secDiff; result->tv_nsec = -(1000000000 - nsecDiff); } else if (nsecDiff < 0) { result->tv_sec = secDiff; result->tv_nsec = nsecDiff; } } else if (secDiff == 0){ result->tv_sec = secDiff; result->tv_nsec = nsecDiff; } return; } /* * Handler for Termination of the hal */ void halTerminate(event_t *event){ halWriteBackCache(); } /* * writing back non volatile variables of the hal to the database: SweepCounter */ void halWriteBackCache(){ if (sqlSetConf(CFGSweepCounter, logcycle)) { logger_error("hal.cpp: Couldn't write logcycle to database"); return; } logger(V_BREW, "Writing back logcycle %d\n", logcycle); }