/* * display2.cpp * * Created on: Sep 26, 2017 * Updated: * Author: Philipp Hinz/Sebastian Vendt */ #include #include #include #include #include #include "display2.h" #include "global.h" #include "logger.h" #include "database.h" #include "timer.h" #include "lcd.h" #include "coffee.h" #include "hal.h" #include "events.h" #include "DS18B20.h" #define CURRENT 0 #define NEXT 1 //timeouts for the transitions in ms #define TIMEOUT_PREBREW 1000 #define TIMEOUT_POSTBREW 9000 display_lang_t displayLang; timer displayTimer(displayTimerHandler); int lcd = 0; // currently only the history of states is of interest coffee_status_t coffeeState[] = {STATE_OFF, STATE_NULL}; // current - next state coffee_mode_t coffeeMode[] = {MODE_STATE, MODE_NULL}; coffee_menuPage_t coffeePage[] = {PAGE_SOFTOFF, PAGE_NULL}; bool coffeeDescaling = false; refreshRate_t currentRefreshRate = refresh_std; uint16_t holdNext[3] = {0, 0, 0}; //holdtime of the new {state, Mode, Page} in ms uint16_t nextWaitTime[3] = {0, 0, 0}; //actual waiting time of the new {state, Mode, Page} typedef enum { state_idx = 0, mode_idx = 1, page_idx = 2 } idx_t; void switchToNextPage(coffee_menuPage_t* history); void switchToNextMode(coffee_mode_t* history); void switchToNextState(coffee_status_t* history); void setRefreshRate(refreshRate_t rate); void setSwitchToNextTimeout(idx_t idx, uint16_t millis); void displayPrintTemp(int line); void displayPrintStats(int line); void displayPrintStats2(int line); void displayPrintNextDesc(int line); void displayPrintPostBrew(int line); void displayPrintBrewManual(int line); void displayPrintFlow(int line); void displayPrintCleanCycle(int line); void displayStateUpdated(event_t *event); void displayModeUpdated(event_t *event); void displayPageUpdated(event_t *event); void displayDescaling(event_t *event); void displayPrintLogo(void); void displayPrintNum(int line, uint16_t num, char* unitstr); void displayClearLine(int line); void displayTerminate(event_t *e); void track(idx_t idx); /** * Prints out the current time in a centered position * @param line Target line in display */ void displayPrintTime(int line) { time_t rawtime; struct tm * timeinfo; if (line > DISPLAY_ROWS) line = 0; time(&rawtime); timeinfo = localtime(&rawtime); lcdPosition(lcd, 0, line); lcdPrintf(lcd, " %.2d:%.2d:%.2d ", timeinfo->tm_hour, timeinfo->tm_min, timeinfo->tm_sec); } /** * Prints out the current temperature in a centered position * @param line Target line in display */ void displayPrintTemp(int line) { //TODO: the temperature is wrong(old value) the first time the page is opened //after the first refresh the right temperature is displayed //no idea where this comes from, datasheet says nothing about dummy readouts. //no old values are used, conversion is started before every readout... //Quick fix: DS18B20_readTemp(); if (line > DISPLAY_ROWS) line = 0; lcdPosition(lcd, 0, line); lcdPrintf(lcd, " %d C ", DS18B20_readTemp()); } /** * Prints the total epsressi brewed since reset */ void displayPrintStats(int line) { char buffer[17]; if (line > DISPLAY_ROWS) line = 0; lcdPosition(lcd, 0, line); sprintf(buffer, "%7d espressi", getBrewCounter()); lcdPrintf(lcd, buffer); } /** * Prints the total heating time in kWh */ void displayPrintStats2(int line) { char buffer[17]; if (line > DISPLAY_ROWS) line = 0; uint64_t totalkWh = 2 * halGetTotalHeatingTime()/(60*60); sprintf(buffer, "%lld kWh", totalkWh); displayPrintLn(line, buffer, true); } /** * Prints the remaining time or the remaining cups until the next descaling is necessary */ void displayPrintNextDesc(int line) { char buffer[17]; if (line > DISPLAY_ROWS) line = 0; sprintf(buffer, "%d d or %d C", checkDirtyTime(), checkDirtyEspresso()); displayPrintLn(line, buffer, true); } /** * */ void displayPrintPostBrew(int line) { char buffer[17]; if (line > DISPLAY_ROWS) line = 0; float brewTime = (float)halGetLastFlowTime(); sprintf(buffer, "%.1f ml / %.1f s", halGetLastFlow(), brewTime / 1000); displayPrintLn(line, buffer, true); } void displayPrintBrewManual(int line) { char buffer[17]; if (line > DISPLAY_ROWS) line = 0; float brewTime = (float)halGetFlowTime(); sprintf(buffer, "%.1f ml / %.1f s", halGetFlow(), brewTime / 1000); displayPrintLn(line, buffer, true); } /** * Prints out the total volume flow * @param line Target line in display */ void displayPrintFlow(int line) { float flow = halGetFlow(); lcdPosition(lcd, 0, line); lcdPrintf(lcd, "%s: %.0f ml ", displayGetString(str_flow), flow); } /** * Prints the current cycle of the cleaning process */ void displayPrintCleanCycle(int line) { char buffer[17]; if (line > DISPLAY_ROWS) line = 0; sprintf(buffer, "%2d of %2d", getCurrentCleanCycle(), CLEANING_CYCLES); displayPrintLn(line, buffer, true); } /** * Prints a string to a specific line, optionally centered. * This function also fills out the remaining row of the display with spaces, * to ensure there is no old data left. * @param line Target line in display * @param *str String to print * @param centered Print centered or not */ void displayPrintLn(int line, const char* str, bool centered) { char buf[DISPLAY_COLS + 1]; int len = strlen(str); int i = 0; int spaces = 0; if (len > DISPLAY_COLS) len = DISPLAY_COLS; if (line > DISPLAY_ROWS) line = 0; if (centered) { spaces = (DISPLAY_COLS - len) / 2; if (spaces) { for (i = 0; i < spaces; i++) { buf[i] = ' '; } } } for (i = 0; i < len; i++) { buf[spaces + i] = str[i]; } if ((len + spaces) < DISPLAY_COLS) { // fill remaining space for (i = i + spaces; i < DISPLAY_COLS; i++) { buf[i] = ' '; } } //draw the descaling star if(coffeeDescaling && line == 0){ buf[15] = '*'; } lcdPosition(lcd, 0, line); buf[DISPLAY_COLS] = '\0'; lcdPrintf(lcd, buf); } /** * Updates the display state to the matching coffee state * @param event Event data */ void displayStateUpdated(event_t *event) { if (event->len != sizeof(coffee_status_t)) { logger_error("display2.cpp: Invalid use of event %s\n", event->event); return; } coffee_status_t state = *(coffee_status_t*) event->data; coffeeState[NEXT] = state; displayTimer.call(); } /** * Updates the display state to the matching coffee display mode * @param event Event data */ void displayModeUpdated(event_t *event) { if (event->len != sizeof(coffee_mode_t)) { logger_error("display2.cpp: Invalid use of event %s\n", event->event); return; } coffee_mode_t mode = *(coffee_mode_t*) event->data; coffeeMode[NEXT] = mode; displayTimer.call(); } /** * Updates the display state to the matching coffee display page * The new state is put on hold (next) and the display can decided if it immediately makes the * new state to the current one or if a timeout is specified in which the new state is put on hold and the * state transition is processed. * One initial asynchronous call * @param event Event data */ void displayPageUpdated(event_t *event) { if (event->len != sizeof(coffee_menuPage_t)) { logger_error("display2.cpp: Invalid use of event %s\n", event->event); return; } coffee_menuPage_t page = *(coffee_menuPage_t*) event->data; coffeePage[NEXT] = page; displayTimer.call(); } /** * Updates the current descaling status of the machine */ void displayDescaling(event_t *event) { if (event->len != sizeof(bool)) { logger_error("display2.cpp: Invalid use of event %s\n", event->event); return; } coffeeDescaling = *(bool*) event->data; } /** * Prints the logo (CoffeePi) and also the temperature with the logo (CoffeePi@72C) if the machine is on */ void displayPrintLogo(void) { char buffer[17]; switch (coffeeState[CURRENT]) { case STATE_HEATING: case STATE_INITALHEATING: case STATE_IDLE: sprintf(buffer, "CoffeePi @ %dC", DS18B20_readTemp()); break; default: sprintf(buffer, "CoffeePi"); break; } displayPrintLn(0, buffer, true); } /** * Prints a integer number on the display * @param line: The line number where the number shall be printed * @param num: The number to be printed * @param unitstr: The unit string, e.g. ms */ void displayPrintNum(int line, uint16_t num, const char* unitstr) { displayClearLine(line); lcdPosition(lcd, 0, line); lcdPrintf(lcd, " %d %s", num, unitstr); } /** * Write spaces to the display line to clear previous content * @param num: The line number */ void displayClearLine(int line) { lcdPosition(lcd, 0, line); lcdPrintf(lcd, " "); } /** * Handles cleanup before program termination */ void displayTerminate(event_t *e) { logger(V_BASIC, "display.cpp: Terminating\n"); displayPrintLn(0, "CoffeePi", true); displayPrintLn(1, displayGetString(str_bye), true); displayTimer.stop(); } /** * Main thread to handle display data * @param *threadid Thread ID */ void* displayThread(void* threadid) { //sleep(1); // Wait for other components to initialize displayTimer.start(); logger(V_BASIC, "display.cpp: Initialized display Thread. Timer state: %d \n", displayTimer.isActive()); while (1) { pause(); if (1) { displayTimer.call(); } } pthread_exit(EXIT_SUCCESS); } /** * Timer handler for display update * @param *threadid Thread ID */ void* displayTimerHandler(void* threadid) { //track time of the new state in hold and switch if necessary track(state_idx); track(page_idx); track(mode_idx); displayRefresh(); pthread_exit(EXIT_SUCCESS); } /** * Tracks the elapsed time in ms of the new state waiting to become the current one if a hold time is specified */ void track(idx_t idx){ if (holdNext[idx] != 0) { nextWaitTime[idx] += 1000 / currentRefreshRate; if (holdNext[idx] <= nextWaitTime[idx]) { switch(idx){ case state_idx: switchToNextState(coffeeState); break; case page_idx: switchToNextPage(coffeePage); break; case mode_idx: switchToNextMode(coffeeMode); break; } } } } /** * Initializes display */ void displayInit(void) { lcd = lcdInit(); if (lcd < 0) logger_error("display2.cpp: Error: unable to init LCD (%d)\n", lcd); lcdClear(lcd); lcdHome(lcd); displayPrintLn(0, (char*) "CoffeePi", true); displayPrintLn(1, (char*) "booting...", true); //lcdPrintf(lcd, " CoffeePi booting..."); //setRefreshRate(refresh_std); currentRefreshRate = refresh_std; displayTimer.setDivider(20 / refresh_std); /**The following block comes from void* displayThread(void* threadid) * The idea is that the initialization functions get the component ready to react on external events * once the threads start, events might be triggered and every component can process them * This should fix the TO_DO where a descaling event was triggered during startup of the coffeethread and * the displaythread did not react to it since it hadn't subscribed to the event yet */ int tmp = sqlGetConf(CFGdisplaylang); if (!tmp || tmp >= lang_last) tmp = DEFAULT_LANG; displaySetLang((display_lang_t) tmp); event_subscribe("statechange", &displayStateUpdated, "display.cpp"); event_subscribe("modechange", &displayModeUpdated, "display.cpp"); event_subscribe("pagechange", &displayPageUpdated, "display.cpp"); event_subscribe("terminate", &displayTerminate, "display.cpp"); event_subscribe("descaling", &displayDescaling, "display.cpp"); logger(V_BASIC, "display2.cpp Initialized display with a refresh rate of %d Hz\n", refresh_std); } /** * Sets the language of the display text * @param lang New language */ void displaySetLang(display_lang_t lang) { displayLang = lang; } /* * */ void switchToNextPage(coffee_menuPage_t* history){ if(history[NEXT] != PAGE_NULL) { history[CURRENT] = history[NEXT]; history[NEXT] = PAGE_NULL; holdNext[page_idx] = 0; nextWaitTime[page_idx] = 0; } } /* * */ void switchToNextMode(coffee_mode_t* history){ if(history[NEXT] != MODE_NULL) { history[CURRENT] = history[NEXT]; history[NEXT] = MODE_NULL; holdNext[mode_idx] = 0; nextWaitTime[mode_idx] = 0; } } /* * */ void switchToNextState(coffee_status_t* history){ if(history[NEXT] != STATE_NULL) { history[CURRENT] = history[NEXT]; history[NEXT] = STATE_NULL; holdNext[state_idx] = 0; nextWaitTime[state_idx] = 0; } } /** * sets the refresh rate of the the display * to one of the predefined refresh rates. */ void setRefreshRate(refreshRate_t rate) { if (currentRefreshRate != rate) { currentRefreshRate = rate; displayTimer.setDivider(20 / rate); } } /** * specifies a timeout at which the new state/mode/page will be made to the current one * This function is enables for special state/mode/page transitions which hold the new state while * the current transition is displayed. These cases set a timeout specifying how long the state transition should be paused. * After the time specified in the timeout the timerhandler of the display will switch the states and makes the new one to the current one. */ void setSwitchToNextTimeout(idx_t idx, uint16_t millis) { holdNext[idx] = millis; } /** * The core description of what will be displayed in what displaystate */ void displayRefresh(void) { //handle mode trainsitions switchToNextMode(coffeeMode); //FSM of the display if (coffeeMode[CURRENT] == MODE_STATE) { //handle state transitions if (coffeeState[NEXT] == STATE_BREW) { //Pre brew //check how long the new state is already in hold ... and either switch or process transition setSwitchToNextTimeout(state_idx, TIMEOUT_PREBREW); setRefreshRate(refresh_fast); displayPrintLogo(); displayPrintLn(1, displayGetString(str_brewing), true); return; } else if ((coffeeState[CURRENT] == STATE_BREW || coffeeState[CURRENT] == STATE_BREWMANUAL) && coffeeState[NEXT] != STATE_NULL) { //Post brew setSwitchToNextTimeout(state_idx, TIMEOUT_POSTBREW); setRefreshRate(refresh_std); displayPrintLn(0, displayGetString(str_postBrew), true); displayPrintPostBrew(1); return; } else { //no special state transition -> make new state to the current one switchToNextState(coffeeState); } switch (coffeeState[CURRENT]) { //coffeeGetState() case STATE_IDLE: displayPrintLogo(); displayPrintLn(1, displayGetString(str_ready), true); break; case STATE_INITALHEATING: displayPrintLogo(); displayPrintLn(1, displayGetString(str_heating), true); break; case STATE_HEATING: displayPrintLogo(); displayPrintLn(1, displayGetString(str_heatingready), true); break; case STATE_BREW: displayPrintLn(0, displayGetString(str_brewing), true); displayPrintFlow(1); break; case STATE_BREWMANUAL: setRefreshRate(refresh_fast); displayPrintLn(0, displayGetString(str_brewing), true); //displayPrintFlow(1); displayPrintBrewManual(1); break; case STATE_CLEANING: displayPrintLn(0, displayGetString(str_cleaning), true); displayPrintCleanCycle(1); break; case STATE_ERROR: displayPrintLogo(); displayPrintLn(1, displayGetString(str_error), true); break; case STATE_WAIT_OFF: displayPrintLogo(); displayPrintLn(1, displayGetString(str_waitoff), true); break; case STATE_OFF: displayPrintLogo(); displayPrintTime(1); break; case STATE_FULLTANK: displayPrintLn(0, displayGetString(str_fullTank), true); displayClearLine(1); break; default: displayPrintLn(0, displayGetString(str_error), true); displayPrintLn(0, "???", true); break; } } else if (coffeeMode[CURRENT] == MODE_MENU) { //handle page transitions switchToNextPage(coffeePage); switch (coffeePage[CURRENT]) { case PAGE_SOFTOFF: displayPrintLn(0, displayGetString(str_menu), true); displayPrintLn(1, displayGetString(str_menu_softoff), true); break; case PAGE_KILL: displayPrintLn(0, displayGetString(str_menu), true); displayPrintLn(1, displayGetString(str_menu_kill), true); break; case PAGE_SINGLEESPRESSO: displayPrintLn(0, displayGetString(str_menu), true); displayPrintLn(1, displayGetString(str_menu_singleEspresso), true); break; case PAGE_STATS: displayPrintLn(0, displayGetString(str_menu_stats), true); displayPrintStats(1); break; case PAGE_STATS2: displayPrintLn(0, displayGetString(str_menu_stats2), true); displayPrintStats2(1); break; case PAGE_DESCALING: displayPrintLn(0, displayGetString(str_menu_nextdesc), true); displayPrintNextDesc(1); break; case PAGE_TEMP: displayPrintLn(0, displayGetString(str_menu_temp), true); displayPrintTemp(1); break; case PAGE_CLEAN: displayPrintLn(0, displayGetString(str_menu), true); displayPrintLn(1, displayGetString(str_menu_clean), true); break; case PAGE_REBOOT: displayPrintLn(0, displayGetString(str_menu), true); displayPrintLn(1, displayGetString(str_menu_reboot), true); break; case PAGE_EXIT: displayPrintLn(0, displayGetString(str_menu), true); displayPrintLn(1, displayGetString(str_menu_exit), true); break; case PAGE_SETTINGS: displayPrintLn(0, displayGetString(str_menu), true); displayPrintLn(1, displayGetString(str_menu_settings), true); break; case PAGE_RESETKWH: displayPrintLn(0, displayGetString(str_menu_settings), true); displayPrintLn(1, displayGetString(str_menu_resetkwh), true); break; case PAGE_RESETKWH_VAL: //confirmation dialog displayPrintLn(0, displayGetString(str_menu_resetkwh_val), true); displayClearLine(1); break; case PAGE_CHANGEPREINF: displayPrintLn(0, displayGetString(str_menu_settings), true); displayPrintLn(1, displayGetString(str_menu_changepreinf), true); break; case PAGE_CHANGEPREINF_VAL: displayPrintLn(0, displayGetString(str_menu_changepreinf), true); displayPrintNum(1, getCoffeeCfg()->amount_preinfusion, "ml"); break; case PAGE_CHANGEBREWTIME: displayPrintLn(0, displayGetString(str_menu_settings), true); displayPrintLn(1, displayGetString(str_menu_changebrewtime), true); break; case PAGE_CHANGEBREWTIME_VAL: displayPrintLn(0, displayGetString(str_menu_changebrewtime), true); displayPrintNum(1, getCoffeeCfg()->time_infusion, "ms"); break; case PAGE_CHANGEBREWML: displayPrintLn(0, displayGetString(str_menu_settings), true); displayPrintLn(1, displayGetString(str_menu_changebrewml), true); break; case PAGE_CHANGEBREWML_VAL: displayPrintLn(0, displayGetString(str_menu_changebrewml), true); displayPrintNum(1, getCoffeeCfg()->amount_espresso, "ml"); break; case PAGE_CHANGESOAKTIME: displayPrintLn(0, displayGetString(str_menu_settings), true); displayPrintLn(1, displayGetString(str_menu_changesoaktime), true); break; case PAGE_CHANGESOAKTIME_VAL: displayPrintLn(0, displayGetString(str_menu_changesoaktime), true); displayPrintNum(1, getCoffeeCfg()->time_soak, "ms"); break; case PAGE_CHANGEAUTORESET: displayPrintLn(0, displayGetString(str_menu_settings), true); displayPrintLn(1, displayGetString(str_menu_changeAutoReset), true); break; case PAGE_CHANGEAUTORESET_VAL: displayPrintLn(0, displayGetString(str_menu_changeAutoReset), true); displayPrintNum(1, halGetFlowResetTime(), "ms"); break; case PAGE_LEAVESETTINGS: displayPrintLn(0, displayGetString(str_menu_settings), true); displayPrintLn(1, displayGetString(str_menu_leavesettings), true); break; default: displayPrintLn(0, displayGetString(str_menu), true); displayPrintLn(1, "???", true); break; } } } /** * Returns the matching translation of a string * @param string Requested string * @return Translated string */ const char* displayGetString(display_strings_t string) { if (displayLang >= lang_last) displayLang = DEFAULT_LANG; return display_strings[string].text[displayLang]; }