display2.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600
  1. /*
  2. * display2.cpp
  3. *
  4. * Created on: Sep 26, 2017
  5. * Updated:
  6. * Author: Philipp Hinz/Sebastian Vendt
  7. */
  8. #include <stdlib.h>
  9. #include <pthread.h>
  10. #include <time.h>
  11. #include <unistd.h>
  12. #include <string.h>
  13. #include "display2.h"
  14. #include "global.h"
  15. #include "logger.h"
  16. #include "database.h"
  17. #include "timer.h"
  18. #include "lcd.h"
  19. #include "coffee.h"
  20. #include "hal.h"
  21. #include "events.h"
  22. #include "DS18B20.h"
  23. #define CURRENT 0
  24. #define NEXT 1
  25. //timeouts for the transitions in ms
  26. #define TIMEOUT_PREBREW 1000
  27. #define TIMEOUT_POSTBREW 9000
  28. display_lang_t displayLang;
  29. timer displayTimer(displayTimerHandler);
  30. int lcd = 0;
  31. // currently only the history of states is of interest
  32. coffee_status_t coffeeState[] = {STATE_OFF, STATE_NULL}; // current - next state
  33. coffee_mode_t coffeeMode[] = {MODE_STATE, MODE_NULL};
  34. coffee_menuPage_t coffeePage[] = {PAGE_SOFTOFF, PAGE_NULL};
  35. bool coffeeDescaling = false;
  36. refreshRate_t currentRefreshRate = refresh_std;
  37. uint16_t holdNext[3] = {0, 0, 0}; //holdtime of the new {state, Mode, Page} in ms
  38. uint16_t nextWaitTime[3] = {0, 0, 0}; //actual waiting time of the new {state, Mode, Page}
  39. typedef enum {
  40. state_idx = 0,
  41. mode_idx = 1,
  42. page_idx = 2
  43. } idx_t;
  44. void track(idx_t idx);
  45. void switchToNextPage(coffee_menuPage_t* history);
  46. void switchToNextMode(coffee_mode_t* history);
  47. void switchToNextState(coffee_status_t* history);
  48. void setRefreshRate(refreshRate_t rate);
  49. void setSwitchToNextTimeout(idx_t idx, uint16_t millis);
  50. /**
  51. * Prints out the current time in a centered position
  52. * @param line Target line in display
  53. */
  54. void displayPrintTime(int line) {
  55. time_t rawtime;
  56. struct tm * timeinfo;
  57. if (line > DISPLAY_ROWS)
  58. line = 0;
  59. time(&rawtime);
  60. timeinfo = localtime(&rawtime);
  61. lcdPosition(lcd, 0, line);
  62. lcdPrintf(lcd, " %.2d:%.2d:%.2d ", timeinfo->tm_hour,
  63. timeinfo->tm_min, timeinfo->tm_sec);
  64. }
  65. /**
  66. * Prints out the current temperature in a centered position
  67. * @param line Target line in display
  68. */
  69. void displayPrintTemp(int line) {
  70. //TODO: the temperature is wrong(old value) the first time the page is opened
  71. //after the first refresh the right temperature is displayed
  72. //no idea where this comes from, datasheet says nothing about dummy readouts.
  73. //no old values are used, conversion is started before every readout...
  74. //Quick fix:
  75. DS18B20_readTemp();
  76. if (line > DISPLAY_ROWS)
  77. line = 0;
  78. lcdPosition(lcd, 0, line);
  79. lcdPrintf(lcd, " %d C ", DS18B20_readTemp());
  80. }
  81. /**
  82. * Prints the total epsressi brewed since reset
  83. */
  84. void displayPrintStats(int line) {
  85. char buffer[17];
  86. if (line > DISPLAY_ROWS)
  87. line = 0;
  88. lcdPosition(lcd, 0, line);
  89. sprintf(buffer, "%7d espressi", getBrewCounter());
  90. lcdPrintf(lcd, buffer);
  91. }
  92. /**
  93. * Prints the total heating time in kWh
  94. */
  95. void displayPrintStats2(int line) {
  96. char buffer[17];
  97. if (line > DISPLAY_ROWS)
  98. line = 0;
  99. uint64_t totalkWh = 2 * getTotalHeatingTime()/(60*60);
  100. sprintf(buffer, "%lld kWh", totalkWh);
  101. displayPrintLn(line, buffer, true);
  102. }
  103. /**
  104. * Prints the remaining time or the remaining cups until the next descaling is necessary
  105. */
  106. void displayPrintNextDesc(int line) {
  107. char buffer[17];
  108. if (line > DISPLAY_ROWS) line = 0;
  109. sprintf(buffer, "%d d or %d C", checkDirtyTime(), checkDirtyEspresso());
  110. displayPrintLn(line, buffer, true);
  111. }
  112. /**
  113. *
  114. */
  115. void displayPrintPostBrew(int line){
  116. char buffer[17];
  117. if (line > DISPLAY_ROWS) line = 0;
  118. float brewTime = (float)halGetLastFlowTime();
  119. sprintf(buffer, "%.1f ml / %.1f s", halGetLastFlow(), brewTime / 1000);
  120. displayPrintLn(line, buffer, true);
  121. }
  122. void displayPrintBrewManual(int line) {
  123. char buffer[17];
  124. if (line > DISPLAY_ROWS) line = 0;
  125. float brewTime = (float)halGetFlowTime();
  126. sprintf(buffer, "%.1f ml / %.1f s", halGetFlow(), brewTime / 1000);
  127. displayPrintLn(line, buffer, true);
  128. }
  129. /**
  130. * Prints out the total volume flow
  131. * @param line Target line in display
  132. */
  133. void displayPrintFlow(int line) {
  134. float flow = halGetFlow();
  135. lcdPosition(lcd, 0, line);
  136. lcdPrintf(lcd, "%s: %.0f ml ", displayGetString(str_flow), flow);
  137. }
  138. /**
  139. * Prints the current cycle of the cleaning process
  140. */
  141. void displayPrintCleanCycle(int line) {
  142. char buffer[17];
  143. if (line > DISPLAY_ROWS) line = 0;
  144. sprintf(buffer, "%2d of %2d", getCurrentCleanCycle(), CLEANING_CYCLES);
  145. displayPrintLn(line, buffer, true);
  146. }
  147. /**
  148. * Prints a string to a specific line, optionally centered.
  149. * This function also fills out the remaining row of the display with spaces,
  150. * to ensure there is no old data left.
  151. * @param line Target line in display
  152. * @param *str String to print
  153. * @param centered Print centered or not
  154. */
  155. void displayPrintLn(int line, const char* str, bool centered) {
  156. char buf[DISPLAY_COLS + 1];
  157. int len = strlen(str);
  158. int i = 0;
  159. int spaces = 0;
  160. if (len > DISPLAY_COLS)
  161. len = DISPLAY_COLS;
  162. if (line > DISPLAY_ROWS)
  163. line = 0;
  164. if (centered) {
  165. spaces = (DISPLAY_COLS - len) / 2;
  166. if (spaces) {
  167. for (i = 0; i < spaces; i++) {
  168. buf[i] = ' ';
  169. }
  170. }
  171. }
  172. for (i = 0; i < len; i++) {
  173. buf[spaces + i] = str[i];
  174. }
  175. if ((len + spaces) < DISPLAY_COLS) { // fill remaining space
  176. for (i = i + spaces; i < DISPLAY_COLS; i++) {
  177. buf[i] = ' ';
  178. }
  179. }
  180. //draw the descaling star
  181. if(coffeeDescaling && line == 0){
  182. buf[15] = '*';
  183. }
  184. lcdPosition(lcd, 0, line);
  185. buf[DISPLAY_COLS] = '\0';
  186. lcdPrintf(lcd, buf);
  187. //logger(V_HAL, "Printed out on display: \"%s\"\n", buf);
  188. }
  189. /**
  190. * Updates the display state to the matching coffee state
  191. * @param event Event data
  192. */
  193. void displayStateUpdated(event_t *event) {
  194. if (event->len != sizeof(coffee_status_t)) {
  195. logger_error("Invalid use of event %s\n", event->event);
  196. return;
  197. }
  198. coffee_status_t state = *(coffee_status_t*) event->data;
  199. coffeeState[NEXT] = state;
  200. displayTimer.call();
  201. }
  202. /**
  203. * Updates the display state to the matching coffee display mode
  204. * @param event Event data
  205. */
  206. void displayModeUpdated(event_t *event) {
  207. if (event->len != sizeof(coffee_mode_t)) {
  208. logger_error("Invalid use of event %s\n", event->event);
  209. return;
  210. }
  211. coffee_mode_t mode = *(coffee_mode_t*) event->data;
  212. coffeeMode[NEXT] = mode;
  213. displayTimer.call();
  214. }
  215. /**
  216. * Updates the display state to the matching coffee display page
  217. * The new state is put on hold (next) and the display can decided if it immediately makes the
  218. * new state to the current one or if a timeout is specified in which the new state is put on hold and the
  219. * state transition is processed.
  220. * One initial asynchronous call
  221. * @param event Event data
  222. */
  223. void displayPageUpdated(event_t *event) {
  224. if (event->len != sizeof(coffee_menuPage_t)) {
  225. logger_error("Invalid use of event %s\n", event->event);
  226. return;
  227. }
  228. coffee_menuPage_t page = *(coffee_menuPage_t*) event->data;
  229. coffeePage[NEXT] = page;
  230. displayTimer.call();
  231. }
  232. /**
  233. * Updates the current descaling status of the machine
  234. */
  235. void displayDescaling(event_t *event) {
  236. if (event->len != sizeof(bool)) {
  237. logger_error("Invalid use of event %s\n", event->event);
  238. return;
  239. }
  240. coffeeDescaling = *(bool*) event->data;
  241. }
  242. /**
  243. * Prints the logo (CoffeePi) and also the temperature with the logo (CoffeePi@72C) if the machine is on
  244. */
  245. void displayPrintLogo(void) {
  246. char buffer[17];
  247. switch (coffeeState[CURRENT]) {
  248. case STATE_HEATING:
  249. case STATE_INITALHEATING:
  250. case STATE_IDLE:
  251. sprintf(buffer, "CoffeePi @ %dC", DS18B20_readTemp());
  252. break;
  253. default:
  254. sprintf(buffer, "CoffeePi");
  255. break;
  256. }
  257. displayPrintLn(0, buffer, true);
  258. }
  259. /**
  260. * Handles cleanup before program termination
  261. */
  262. void displayTerminate(event_t *e) {
  263. logger(V_BASIC, "display.cpp: Terminating\n");
  264. displayPrintLn(0, "CoffeePi", true);
  265. displayPrintLn(1, displayGetString(str_bye), true);
  266. displayTimer.stop();
  267. }
  268. /**
  269. * Main thread to handle display data
  270. * @param *threadid Thread ID
  271. */
  272. void* displayThread(void* threadid) {
  273. //sleep(1); // Wait for other components to initialize
  274. displayTimer.start();
  275. logger(V_BASIC, "Initialized display Thread. Timer state: %d \n", displayTimer.isActive());
  276. while (1) {
  277. pause();
  278. if (1) {
  279. displayTimer.call();
  280. }
  281. }
  282. pthread_exit(EXIT_SUCCESS);
  283. }
  284. /**
  285. * Timer handler for display update
  286. * @param *threadid Thread ID
  287. */
  288. void* displayTimerHandler(void* threadid) {
  289. //track time of the new state in hold and switch if necessary
  290. track(state_idx);
  291. track(page_idx);
  292. track(mode_idx);
  293. displayRefresh();
  294. pthread_exit(EXIT_SUCCESS);
  295. }
  296. /**
  297. * Tracks the elapsed time in ms of the new state waiting to become the current one if a hold time is specified
  298. */
  299. void track(idx_t idx){
  300. if (holdNext[idx] != 0) {
  301. nextWaitTime[idx] += 1000 / currentRefreshRate;
  302. if (holdNext[idx] <= nextWaitTime[idx]) {
  303. switch(idx){
  304. case state_idx:
  305. switchToNextState(coffeeState);
  306. break;
  307. case page_idx:
  308. switchToNextPage(coffeePage);
  309. break;
  310. case mode_idx:
  311. switchToNextMode(coffeeMode);
  312. break;
  313. }
  314. }
  315. }
  316. }
  317. /**
  318. * Initializes display
  319. */
  320. void displayInit(void) {
  321. lcd = lcdInit();
  322. if (lcd < 0)
  323. logger_error("Error: unable to init LCD (%d)\n", lcd);
  324. lcdClear(lcd);
  325. lcdHome(lcd);
  326. displayPrintLn(0, (char*) "CoffeePi", true);
  327. displayPrintLn(1, (char*) "booting...", true);
  328. //lcdPrintf(lcd, " CoffeePi booting...");
  329. //setRefreshRate(refresh_std);
  330. currentRefreshRate = refresh_std;
  331. displayTimer.setDivider(20 / refresh_std);
  332. /**The following block comes from void* displayThread(void* threadid)
  333. * The idea is that the initialization functions get the component ready to react on external events
  334. * once the threads start, events might be triggered and every component can process them
  335. * This should fix the TO_DO where a descaling event was triggered during startup of the coffeethread and
  336. * the displaythread did not react to it since it hadn't subscribed to the event yet
  337. */
  338. int tmp = sqlGetConf(CFGdisplaylang);
  339. if (!tmp || tmp >= lang_last)
  340. tmp = DEFAULT_LANG;
  341. displaySetLang((display_lang_t) tmp);
  342. event_subscribe("statechange", &displayStateUpdated, "display.cpp");
  343. event_subscribe("modechange", &displayModeUpdated, "display.cpp");
  344. event_subscribe("pagechange", &displayPageUpdated, "display.cpp");
  345. event_subscribe("terminate", &displayTerminate, "display.cpp");
  346. event_subscribe("descaling", &displayDescaling, "display.cpp");
  347. logger(V_BASIC, "display2.cpp Initialized display with a refresh rate of %d Hz\n",
  348. refresh_std);
  349. }
  350. /**
  351. * Sets the language of the display text
  352. * @param lang New language
  353. */
  354. void displaySetLang(display_lang_t lang) {
  355. displayLang = lang;
  356. }
  357. /*
  358. *
  359. */
  360. void switchToNextPage(coffee_menuPage_t* history){
  361. if(history[NEXT] != PAGE_NULL) {
  362. history[CURRENT] = history[NEXT];
  363. history[NEXT] = PAGE_NULL;
  364. holdNext[page_idx] = 0;
  365. nextWaitTime[page_idx] = 0;
  366. }
  367. }
  368. /*
  369. *
  370. */
  371. void switchToNextMode(coffee_mode_t* history){
  372. if(history[NEXT] != MODE_NULL) {
  373. history[CURRENT] = history[NEXT];
  374. history[NEXT] = MODE_NULL;
  375. holdNext[mode_idx] = 0;
  376. nextWaitTime[mode_idx] = 0;
  377. }
  378. }
  379. /*
  380. *
  381. */
  382. void switchToNextState(coffee_status_t* history){
  383. if(history[NEXT] != STATE_NULL) {
  384. history[CURRENT] = history[NEXT];
  385. history[NEXT] = STATE_NULL;
  386. holdNext[state_idx] = 0;
  387. nextWaitTime[state_idx] = 0;
  388. }
  389. }
  390. /**
  391. * sets the refresh rate of the the display
  392. * to one of the predefined refresh rates.
  393. */
  394. void setRefreshRate(refreshRate_t rate) {
  395. if (currentRefreshRate != rate) {
  396. currentRefreshRate = rate;
  397. displayTimer.setDivider(20 / rate);
  398. }
  399. }
  400. /**
  401. * specifies a timeout at which the new state/mode/page will be made to the current one
  402. * This function is enables for special state/mode/page transitions which hold the new state while
  403. * the current transition is displayed. These cases set a timeout specifying how long the state transition should be paused.
  404. * 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.
  405. */
  406. void setSwitchToNextTimeout(idx_t idx, uint16_t millis) {
  407. holdNext[idx] = millis;
  408. }
  409. /**
  410. * The core description of what will be displayed in what displaystate
  411. */
  412. void displayRefresh(void) {
  413. //handle mode trainsitions
  414. switchToNextMode(coffeeMode);
  415. //FSM of the display
  416. if (coffeeMode[CURRENT] == MODE_STATE) {
  417. //handle state transitions
  418. if (coffeeState[NEXT] == STATE_BREW) { //Pre brew
  419. //check how long the new state is already in hold ... and either switch or process transition
  420. setSwitchToNextTimeout(state_idx, TIMEOUT_PREBREW);
  421. setRefreshRate(refresh_fast);
  422. displayPrintLogo();
  423. displayPrintLn(1, displayGetString(str_brewing), true);
  424. return;
  425. } else if ((coffeeState[CURRENT] == STATE_BREW || coffeeState[CURRENT] == STATE_BREWMANUAL) && coffeeState[NEXT] != STATE_NULL) { //Post brew
  426. setSwitchToNextTimeout(state_idx, TIMEOUT_POSTBREW);
  427. setRefreshRate(refresh_std);
  428. displayPrintLn(0, displayGetString(str_postBrew), true);
  429. displayPrintPostBrew(1);
  430. return;
  431. } else { //no special state transition -> make new state to the current one
  432. switchToNextState(coffeeState);
  433. }
  434. switch (coffeeState[CURRENT]) { //coffeeGetState()
  435. case STATE_IDLE:
  436. displayPrintLogo();
  437. displayPrintLn(1, displayGetString(str_ready), true);
  438. break;
  439. case STATE_INITALHEATING:
  440. displayPrintLogo();
  441. displayPrintLn(1, displayGetString(str_heating), true);
  442. break;
  443. case STATE_HEATING:
  444. displayPrintLogo();
  445. displayPrintLn(1, displayGetString(str_heatingready), true);
  446. break;
  447. case STATE_BREW:
  448. displayPrintLn(0, displayGetString(str_brewing), true);
  449. displayPrintFlow(1);
  450. break;
  451. case STATE_BREWMANUAL:
  452. setRefreshRate(refresh_fast);
  453. displayPrintLn(0, displayGetString(str_brewing), true);
  454. //displayPrintFlow(1);
  455. displayPrintBrewManual(1);
  456. break;
  457. case STATE_CLEANING:
  458. displayPrintLn(0, displayGetString(str_cleaning), true);
  459. displayPrintCleanCycle(1);
  460. break;
  461. case STATE_ERROR:
  462. displayPrintLogo();
  463. displayPrintLn(1, displayGetString(str_error), true);
  464. break;
  465. case STATE_WAIT_OFF:
  466. displayPrintLogo();
  467. displayPrintLn(1, displayGetString(str_waitoff), true);
  468. break;
  469. case STATE_OFF:
  470. default:
  471. displayPrintLogo();
  472. displayPrintTime(1);
  473. break;
  474. }
  475. } else if (coffeeMode[CURRENT] == MODE_MENU) {
  476. //handle page transitions
  477. switchToNextPage(coffeePage);
  478. switch (coffeePage[CURRENT]) {
  479. case PAGE_SOFTOFF:
  480. displayPrintLn(0, displayGetString(str_menu), true);
  481. displayPrintLn(1, displayGetString(str_menu_softoff), true);
  482. break;
  483. case PAGE_KILL:
  484. displayPrintLn(0, displayGetString(str_menu), true);
  485. displayPrintLn(1, displayGetString(str_menu_kill), true);
  486. break;
  487. case PAGE_STATS:
  488. displayPrintLn(0, displayGetString(str_menu_stats), true);
  489. displayPrintStats(1);
  490. break;
  491. case PAGE_STATS2:
  492. displayPrintLn(0, displayGetString(str_menu_stats2), true);
  493. displayPrintStats2(1);
  494. break;
  495. case PAGE_DESCALING:
  496. displayPrintLn(0, displayGetString(str_menu_nextdesc), true);
  497. displayPrintNextDesc(1);
  498. break;
  499. case PAGE_TEMP:
  500. displayPrintLn(0, displayGetString(str_menu_temp), true);
  501. displayPrintTemp(1);
  502. break;
  503. case PAGE_CLEAN:
  504. displayPrintLn(0, displayGetString(str_menu), true);
  505. displayPrintLn(1, displayGetString(str_menu_clean), true);
  506. break;
  507. case PAGE_RESTART:
  508. displayPrintLn(0, displayGetString(str_menu), true);
  509. displayPrintLn(1, displayGetString(str_menu_restart), true);
  510. break;
  511. case PAGE_EXIT:
  512. displayPrintLn(0, displayGetString(str_menu), true);
  513. displayPrintLn(1, displayGetString(str_menu_exit), true);
  514. break;
  515. default:
  516. displayPrintLn(0, displayGetString(str_menu), true);
  517. displayPrintLn(1, "???", true);
  518. break;
  519. }
  520. }
  521. }
  522. /**
  523. * Returns the matching translation of a string
  524. * @param string Requested string
  525. * @return Translated string
  526. */
  527. const char* displayGetString(display_strings_t string) {
  528. if (displayLang >= lang_last)
  529. displayLang = DEFAULT_LANG;
  530. return display_strings[string].text[displayLang];
  531. }