display2.cpp 15 KB

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