/* * display.cpp * * Created on: Sep 26, 2017 * Author: Philipp Hinz */ #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" display_lang_t displayLang; timer displayTimer(displayTimerHandler); int lcd = 0; volatile int timerScaler = 0; volatile int elapsedCnt = 0; coffee_status_t coffeeState = STATE_OFF; coffee_mode_t coffeeMode = MODE_STATE; coffee_menuPage_t coffeePage = PAGE_SOFTOFF; bool coffeeDescaling = false; /** * 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... 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 * getTotalHeatingTime()/(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); } /** * 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 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); //logger(V_HAL, "Printed out on display: \"%s\"\n", 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("Invalid use of event %s\n", event->event); return; } coffee_status_t state = *(coffee_status_t*) event->data; if (state != coffeeState) { coffeeState = state; if (timerScaler) timerScaler--; elapsedCnt = 0; 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("Invalid use of event %s\n", event->event); return; } coffee_mode_t mode = *(coffee_mode_t*) event->data; if (mode != coffeeMode) { coffeeMode = mode; if (timerScaler) timerScaler--; elapsedCnt = 0; displayTimer.call(); } } /** * Updates the display state to the matching coffee display page * @param event Event data */ void displayPageUpdated(event_t *event) { if (event->len != sizeof(coffee_menuPage_t)) { logger_error("Invalid use of event %s\n", event->event); return; } coffee_menuPage_t page = *(coffee_menuPage_t*) event->data; if (page != coffeePage) { coffeePage = page; timerScaler = REFRESH_RATE; elapsedCnt = 0; displayTimer.call(); } } /** * Updates the current descaling status of the machine */ void displayDescaling(event_t *event) { if (event->len != sizeof(bool)) { logger_error("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) { 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); } /** * 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); } /** * Main thread to handle display data * @param *threadid Thread ID */ void* displayThread(void* threadid) { //sleep(1); // Wait for other components to initialize displayTimer.start(); while (1) { pause(); if (1) { timerScaler = REFRESH_RATE; displayTimer.call(); } } pthread_exit(EXIT_SUCCESS); } /** * Timer handler for display update * @param *threadid Thread ID */ void* displayTimerHandler(void* threadid) { int scale1Hz = 0; if (timerScaler++ >= REFRESH_RATE) { // 1s elapsed, reset timerScaler = 0; scale1Hz = 1; } if (scale1Hz) { if (coffeeMode == MODE_STATE) { switch (coffeeState) { case STATE_OFF: case STATE_HEATING: case STATE_INITALHEATING: case STATE_IDLE: case STATE_CLEANING: case STATE_ERROR: case STATE_WAIT_OFF: displayRefresh(); break; default: break; } } else if (coffeeMode == MODE_MENU) { displayRefresh(); } } switch (coffeeState) { case STATE_BREW: case STATE_BREWMANUAL: displayRefresh(); break; default: break; } if (elapsedCnt < (5 * REFRESH_RATE)) // Don't let it grow too large elapsedCnt++; pthread_exit(EXIT_SUCCESS); } /** * Initializes display */ void displayInit(void) { lcd = lcdInit(); if (lcd < 0) logger_error("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..."); timerScaler = 0; displayTimer.setDivider((1000000 / TIMER_DELAY_US) / REFRESH_RATE); logger(V_BASIC, "Initialized display with a refresh rate of %d Hz\n", REFRESH_RATE); /**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); event_subscribe("modechange", &displayModeUpdated); event_subscribe("pagechange", &displayPageUpdated); event_subscribe("terminate", &displayTerminate); event_subscribe("descaling", &displayDescaling); } /** * Sets the language of the display text * @param lang New language */ void displaySetLang(display_lang_t lang) { displayLang = lang; } /** * Refreshed the display content and outputs it */ void displayRefresh(void) { if (coffeeMode == MODE_STATE) { switch (coffeeState) { //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: if (elapsedCnt <= 2 * REFRESH_RATE) { displayPrintLogo(); displayPrintLn(1, displayGetString(str_brewing), true); } else { displayPrintLn(0, displayGetString(str_brewing), true); displayPrintFlow(1); } break; case STATE_BREWMANUAL: displayPrintLn(0, displayGetString(str_brewing), true); displayPrintFlow(1); break; case STATE_CLEANING: displayPrintLogo(); displayPrintLn(1, displayGetString(str_cleaning), true); 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: default: displayPrintLogo(); displayPrintTime(1); break; } } else if (coffeeMode == MODE_MENU) { switch (coffeePage) { 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_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_DEMO: displayPrintLn(0, displayGetString(str_menu), true); displayPrintLn(1, displayGetString(str_menu_demo), true); break; case PAGE_EXIT: displayPrintLn(0, displayGetString(str_menu), true); displayPrintLn(1, displayGetString(str_menu_exit), 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]; }