DIY fertilizer mixer and plant watering machine https://www.xythobuz.de/giessomat.html
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Statemachine.cpp 23KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729
  1. /*
  2. * Copyright (c) 2021 Thomas Buck <thomas@xythobuz.de>
  3. *
  4. * This file is part of Giess-o-mat.
  5. *
  6. * Giess-o-mat is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * Giess-o-mat is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with Giess-o-mat. If not, see <https://www.gnu.org/licenses/>.
  18. */
  19. #include "Plants.h"
  20. #include "DebugLog.h"
  21. #include "Statemachine.h"
  22. #include "config.h"
  23. Statemachine::DigitBuffer::DigitBuffer(int _size) {
  24. size = _size;
  25. pos = 0;
  26. digits = new int[size];
  27. }
  28. Statemachine::DigitBuffer::~DigitBuffer() {
  29. delete digits;
  30. }
  31. bool Statemachine::DigitBuffer::spaceLeft(void) {
  32. return (pos < size);
  33. }
  34. bool Statemachine::DigitBuffer::hasDigits(void) {
  35. return (pos > 0);
  36. }
  37. int Statemachine::DigitBuffer::countDigits(void) {
  38. return pos;
  39. }
  40. void Statemachine::DigitBuffer::addDigit(int d) {
  41. if (spaceLeft()) {
  42. digits[pos] = d;
  43. pos++;
  44. }
  45. }
  46. void Statemachine::DigitBuffer::removeDigit(void) {
  47. if (hasDigits()) {
  48. pos--;
  49. }
  50. }
  51. void Statemachine::DigitBuffer::clear(void) {
  52. pos = 0;
  53. }
  54. uint32_t Statemachine::DigitBuffer::getNumber(void) {
  55. uint32_t fact = 1;
  56. uint32_t sum = 0;
  57. for (int i = (pos - 1); i >= 0; i--) {
  58. sum += digits[i] * fact;
  59. fact *= 10;
  60. }
  61. return sum;
  62. }
  63. static const char *state_names[] = {
  64. stringify(init),
  65. stringify(menu),
  66. stringify(auto_mode),
  67. stringify(auto_fert),
  68. stringify(auto_fert_run),
  69. stringify(auto_tank_run),
  70. stringify(auto_plant),
  71. stringify(auto_plant_run),
  72. stringify(auto_done),
  73. stringify(menu_pumps),
  74. stringify(menu_pumps_time),
  75. stringify(menu_pumps_go),
  76. stringify(menu_pumps_run),
  77. stringify(menu_pumps_done),
  78. stringify(menu_valves),
  79. stringify(menu_valves_time),
  80. stringify(menu_valves_go),
  81. stringify(menu_valves_run),
  82. stringify(menu_valves_done),
  83. stringify(error)
  84. };
  85. const char *Statemachine::getStateName(void) {
  86. return state_names[state];
  87. }
  88. Statemachine::Statemachine(print_fn _print, backspace_fn _backspace)
  89. : db(7), selected_plants(plants.countPlants()) {
  90. state = init;
  91. old_state = init;
  92. print = _print;
  93. backspace = _backspace;
  94. selected_id = 0;
  95. selected_time = 0;
  96. start_time = 0;
  97. stop_time = 0;
  98. last_animation_time = 0;
  99. error_condition = "";
  100. }
  101. void Statemachine::begin(void) {
  102. switch_to(init);
  103. }
  104. void Statemachine::input(int n) {
  105. if (state == init) {
  106. switch_to(menu);
  107. } else if (state == menu) {
  108. if (n == 1) {
  109. switch_to(auto_mode);
  110. } else if (n == 2) {
  111. switch_to(menu_pumps);
  112. } else if (n == 3) {
  113. switch_to(menu_valves);
  114. } else if ((n == -1) || (n == -2)) {
  115. switch_to(init);
  116. }
  117. } else if (state == auto_mode) {
  118. if (n == 1) {
  119. switch_to(auto_fert);
  120. } else if (n == 2) {
  121. auto wl = plants.getWaterlevel();
  122. if ((wl != Plants::full) && (wl != Plants::invalid)) {
  123. plants.openWaterInlet();
  124. selected_id = plants.countPlants() + 1;
  125. selected_time = MAX_TANK_FILL_TIME;
  126. start_time = millis();
  127. switch_to(auto_tank_run);
  128. } else if (wl == Plants::full) {
  129. stop_time = millis();
  130. switch_to(auto_mode);
  131. } else if (wl == Plants::invalid) {
  132. error_condition = "Invalid sensor state";
  133. state = auto_mode;
  134. switch_to(error);
  135. }
  136. } else if (n == 3) {
  137. selected_plants.clear();
  138. switch_to(auto_plant);
  139. } else if ((n == -1) || (n == -2)) {
  140. switch_to(menu);
  141. }
  142. } else if (state == auto_fert) {
  143. if ((n >= 1) && (n <= 3)) {
  144. auto wl = plants.getWaterlevel();
  145. if ((wl != Plants::full) && (wl != Plants::invalid)) {
  146. plants.startFertilizer(n - 1);
  147. selected_id = n;
  148. selected_time = AUTO_PUMP_RUNTIME;
  149. start_time = millis();
  150. switch_to(auto_fert_run);
  151. } else if (wl == Plants::full) {
  152. stop_time = millis();
  153. switch_to(auto_mode);
  154. } else if (wl == Plants::invalid) {
  155. error_condition = "Invalid sensor state";
  156. state = auto_mode;
  157. switch_to(error);
  158. }
  159. } else if ((n == -1) || (n == -2)) {
  160. switch_to(auto_mode);
  161. }
  162. } else if (state == auto_fert_run) {
  163. plants.abort();
  164. stop_time = millis();
  165. switch_to(auto_done);
  166. } else if (state == auto_tank_run) {
  167. plants.abort();
  168. stop_time = millis();
  169. switch_to(auto_done);
  170. } else if (state == auto_plant) {
  171. if (n == -1) {
  172. if (db.hasDigits()) {
  173. backspace();
  174. db.removeDigit();
  175. } else {
  176. switch_to(auto_mode);
  177. }
  178. } else if (n == -2) {
  179. if (!db.hasDigits()) {
  180. auto wl = plants.getWaterlevel();
  181. if ((wl != Plants::empty) && (wl != Plants::invalid)) {
  182. for (int i = 0; i < plants.countPlants(); i++) {
  183. if (selected_plants.isSet(i)) {
  184. plants.startPlant(i);
  185. }
  186. }
  187. selected_plants.clear();
  188. selected_time = MAX_AUTO_PLANT_RUNTIME;
  189. start_time = millis();
  190. switch_to(auto_plant_run);
  191. } else if (wl == Plants::empty) {
  192. stop_time = millis();
  193. switch_to(auto_mode);
  194. } else if (wl == Plants::invalid) {
  195. error_condition = "Invalid sensor state";
  196. state = auto_mode;
  197. switch_to(error);
  198. }
  199. } else {
  200. selected_id = number_input();
  201. if ((selected_id <= 0) || (selected_id > plants.countPlants())) {
  202. error_condition = "Invalid plant ID!";
  203. switch_to(error);
  204. } else {
  205. selected_plants.set(selected_id - 1);
  206. switch_to(auto_plant);
  207. }
  208. }
  209. } else {
  210. if (db.spaceLeft()) {
  211. db.addDigit(n);
  212. } else {
  213. backspace();
  214. }
  215. }
  216. } else if (state == auto_plant_run) {
  217. plants.abort();
  218. stop_time = millis();
  219. switch_to(auto_done);
  220. } else if (state == auto_done) {
  221. switch_to(auto_mode);
  222. } else if (state == menu_pumps) {
  223. if (n == -1) {
  224. if (db.hasDigits()) {
  225. backspace();
  226. db.removeDigit();
  227. } else {
  228. switch_to(menu);
  229. }
  230. } else if (n == -2) {
  231. if (!db.hasDigits()) {
  232. return;
  233. }
  234. selected_id = number_input();
  235. if ((selected_id <= 0) || (selected_id > plants.countFertilizers())) {
  236. error_condition = "Invalid pump ID!";
  237. switch_to(error);
  238. } else {
  239. switch_to(menu_pumps_time);
  240. }
  241. } else {
  242. if (db.spaceLeft()) {
  243. db.addDigit(n);
  244. } else {
  245. backspace();
  246. }
  247. }
  248. } else if (state == menu_pumps_time) {
  249. if (n == -1) {
  250. if (db.hasDigits()) {
  251. backspace();
  252. db.removeDigit();
  253. } else {
  254. switch_to(menu_pumps);
  255. }
  256. } else if (n == -2) {
  257. if (!db.hasDigits()) {
  258. return;
  259. }
  260. selected_time = number_input();
  261. if ((selected_time <= 0) || (selected_time > MAX_PUMP_RUNTIME)) {
  262. error_condition = "Invalid time range!";
  263. switch_to(error);
  264. } else {
  265. switch_to(menu_pumps_go);
  266. }
  267. } else {
  268. if (db.spaceLeft()) {
  269. db.addDigit(n);
  270. } else {
  271. backspace();
  272. }
  273. }
  274. } else if (state == menu_pumps_go) {
  275. if (n == -2) {
  276. start_time = millis();
  277. last_animation_time = start_time;
  278. auto wl = plants.getWaterlevel();
  279. if ((wl != Plants::full) && (wl != Plants::invalid)) {
  280. plants.startFertilizer(selected_id - 1);
  281. switch_to(menu_pumps_run);
  282. } else if (wl == Plants::full) {
  283. stop_time = millis();
  284. switch_to(menu_pumps_done);
  285. } else if (wl == Plants::invalid) {
  286. error_condition = "Invalid sensor state";
  287. state = menu_pumps;
  288. switch_to(error);
  289. }
  290. } else {
  291. switch_to(menu_pumps_time);
  292. }
  293. } else if (state == menu_pumps_run) {
  294. plants.abort();
  295. stop_time = millis();
  296. switch_to(menu_pumps_done);
  297. } else if (state == menu_pumps_done) {
  298. switch_to(menu);
  299. } else if (state == menu_valves) {
  300. if (n == -1) {
  301. if (db.hasDigits()) {
  302. backspace();
  303. db.removeDigit();
  304. } else {
  305. switch_to(menu);
  306. }
  307. } else if (n == -2) {
  308. if (!db.hasDigits()) {
  309. return;
  310. }
  311. selected_id = number_input();
  312. if ((selected_id <= 0) || (selected_id > (plants.countPlants() + 1))) {
  313. error_condition = "Invalid valve ID!";
  314. switch_to(error);
  315. } else {
  316. switch_to(menu_valves_time);
  317. }
  318. } else {
  319. if (db.spaceLeft()) {
  320. db.addDigit(n);
  321. } else {
  322. backspace();
  323. }
  324. }
  325. } else if (state == menu_valves_time) {
  326. if (n == -1) {
  327. if (db.hasDigits()) {
  328. backspace();
  329. db.removeDigit();
  330. } else {
  331. switch_to(menu_valves);
  332. }
  333. } else if (n == -2) {
  334. if (!db.hasDigits()) {
  335. return;
  336. }
  337. selected_time = number_input();
  338. if ((selected_time <= 0) || (selected_time > MAX_VALVE_RUNTIME)) {
  339. error_condition = "Invalid time range!";
  340. switch_to(error);
  341. } else {
  342. switch_to(menu_valves_go);
  343. }
  344. } else {
  345. if (db.spaceLeft()) {
  346. db.addDigit(n);
  347. } else {
  348. backspace();
  349. }
  350. }
  351. } else if (state == menu_valves_go) {
  352. if (n == -2) {
  353. start_time = millis();
  354. last_animation_time = start_time;
  355. auto wl = plants.getWaterlevel();
  356. if ((wl != Plants::full) && (wl != Plants::invalid)) {
  357. if (selected_id >= (plants.countPlants() + 1)) {
  358. plants.openWaterInlet();
  359. } else {
  360. plants.startPlant(selected_id - 1);
  361. }
  362. switch_to(menu_valves_run);
  363. } else if (wl == Plants::full) {
  364. stop_time = millis();
  365. switch_to(menu_valves_done);
  366. } else if (wl == Plants::invalid) {
  367. error_condition = "Invalid sensor state";
  368. state = menu_valves;
  369. switch_to(error);
  370. }
  371. } else {
  372. switch_to(menu_valves_time);
  373. }
  374. } else if (state == menu_valves_run) {
  375. plants.abort();
  376. stop_time = millis();
  377. switch_to(menu_valves_done);
  378. } else if (state == menu_valves_done) {
  379. switch_to(menu);
  380. } else if (state == error) {
  381. if (old_state != error) {
  382. switch_to(old_state);
  383. } else {
  384. switch_to(menu);
  385. }
  386. }
  387. }
  388. uint32_t Statemachine::number_input(void) {
  389. for (int i = 0; i < db.countDigits(); i++) {
  390. backspace();
  391. }
  392. uint32_t n = db.getNumber();
  393. db.clear();
  394. debug.print("Whole number input: ");
  395. debug.println(n);
  396. return n;
  397. }
  398. void Statemachine::act(void) {
  399. if ((state == menu_pumps_run) || (state == menu_valves_run)) {
  400. unsigned long runtime = millis() - start_time;
  401. if ((runtime / 1000UL) >= selected_time) {
  402. // stop if timeout has been reached
  403. plants.abort();
  404. stop_time = millis();
  405. switch_to((state == menu_pumps_run) ? menu_pumps_done : menu_valves_done);
  406. } else if ((millis() - last_animation_time) >= 500) {
  407. // update animation if needed
  408. last_animation_time = millis();
  409. switch_to(state);
  410. }
  411. }
  412. #ifdef CHECK_SENSORS_VALVE_PUMP_MENU_FULL
  413. if ((state == menu_pumps_run) || ((state == menu_valves_run) && (selected_id == (plants.countPlants() + 1)))) {
  414. // check water level state
  415. auto wl = plants.getWaterlevel();
  416. if (wl == Plants::full) {
  417. plants.abort();
  418. stop_time = millis();
  419. switch_to((state == menu_pumps_run) ? menu_pumps_done : menu_valves_done);
  420. } else if (wl == Plants::invalid) {
  421. plants.abort();
  422. error_condition = "Invalid sensor state";
  423. state = (state == menu_pumps_run) ? menu_pumps : menu_valves;
  424. switch_to(error);
  425. }
  426. }
  427. #endif // CHECK_SENSORS_VALVE_PUMP_MENU_FULL
  428. #ifdef CHECK_SENSORS_VALVE_PUMP_MENU_EMPTY
  429. if ((state == menu_valves_run) && (selected_id <= plants.countPlants())) {
  430. // check water level state
  431. auto wl = plants.getWaterlevel();
  432. if (wl == Plants::empty) {
  433. plants.abort();
  434. stop_time = millis();
  435. switch_to(menu_valves_done);
  436. } else if (wl == Plants::invalid) {
  437. plants.abort();
  438. error_condition = "Invalid sensor state";
  439. state = menu_valves;
  440. switch_to(error);
  441. }
  442. }
  443. #endif // CHECK_SENSORS_VALVE_PUMP_MENU_EMPTY
  444. if ((state == auto_fert_run) || (state == auto_tank_run)) {
  445. unsigned long runtime = millis() - start_time;
  446. if ((runtime / 1000UL) >= selected_time) {
  447. // stop if timeout has been reached
  448. plants.abort();
  449. stop_time = millis();
  450. switch_to(auto_done);
  451. } else if ((millis() - last_animation_time) >= 500) {
  452. // update animation if needed
  453. last_animation_time = millis();
  454. switch_to(state);
  455. }
  456. // check water level state
  457. auto wl = plants.getWaterlevel();
  458. if (wl == Plants::full) {
  459. plants.abort();
  460. stop_time = millis();
  461. switch_to(auto_done);
  462. } else if (wl == Plants::invalid) {
  463. plants.abort();
  464. error_condition = "Invalid sensor state";
  465. state = auto_mode;
  466. switch_to(error);
  467. }
  468. }
  469. if (state == auto_plant_run) {
  470. unsigned long runtime = millis() - start_time;
  471. if ((runtime / 1000UL) >= selected_time) {
  472. // stop if timeout has been reached
  473. plants.abort();
  474. stop_time = millis();
  475. switch_to(auto_done);
  476. } else if ((millis() - last_animation_time) >= 500) {
  477. // update animation if needed
  478. last_animation_time = millis();
  479. switch_to(state);
  480. }
  481. // check water level state
  482. auto wl = plants.getWaterlevel();
  483. if (wl == Plants::empty) {
  484. plants.abort();
  485. stop_time = millis();
  486. switch_to(auto_done);
  487. } else if (wl == Plants::invalid) {
  488. plants.abort();
  489. error_condition = "Invalid sensor state";
  490. state = auto_mode;
  491. switch_to(error);
  492. }
  493. }
  494. }
  495. void Statemachine::switch_to(States s) {
  496. old_state = state;
  497. state = s;
  498. if (s == init) {
  499. String a = String("- Giess-o-mat V") + FIRMWARE_VERSION + String(" -");
  500. print(a.c_str(),
  501. "Usage: Enter number",
  502. "* Delete prev. digit",
  503. "# Execute input num.",
  504. -1);
  505. } else if (s == menu) {
  506. print("------- Menu -------",
  507. "1: Automatic program",
  508. "2: Fertilizer pumps",
  509. "3: Outlet valves",
  510. -1);
  511. } else if (s == auto_mode) {
  512. print("------- Auto -------",
  513. "1: Add Fertilizer",
  514. "2: Fill Reservoir",
  515. "3: Water a plant",
  516. -1);
  517. } else if (s == auto_fert) {
  518. print("---- Fertilizer ----",
  519. "1: Vegetation Phase",
  520. "2: Bloom Phase",
  521. "3: Special",
  522. -1);
  523. } else if (s == auto_fert_run) {
  524. unsigned long runtime = millis() - start_time;
  525. String a = String("Runtime: ") + String(runtime / 1000UL) + String("s / ") + String(selected_time) + String('s');
  526. unsigned long anim = runtime * 20UL / (selected_time * 1000UL);
  527. String b;
  528. for (unsigned long i = 0; i < anim; i++) {
  529. b += '#';
  530. }
  531. print("---- Dispensing ----",
  532. a.c_str(),
  533. b.c_str(),
  534. "Hit any key to stop!",
  535. -1);
  536. } else if (s == auto_tank_run) {
  537. unsigned long runtime = millis() - start_time;
  538. String a = String("Runtime: ") + String(runtime / 1000UL) + String("s / ") + String(selected_time) + String('s');
  539. unsigned long anim = runtime * 20UL / (selected_time * 1000UL);
  540. String b;
  541. for (unsigned long i = 0; i < anim; i++) {
  542. b += '#';
  543. }
  544. print("--- Filling Tank ---",
  545. a.c_str(),
  546. b.c_str(),
  547. "Hit any key to stop!",
  548. -1);
  549. } else if (s == auto_plant) {
  550. String a = String("(Input 1 to ") + String(plants.countPlants()) + String(")");
  551. print("--- Select Plant ---",
  552. "Leave empty if done!",
  553. a.c_str(),
  554. "Plant: ",
  555. 3);
  556. } else if (s == auto_plant_run) {
  557. unsigned long runtime = millis() - start_time;
  558. String a = String("Runtime: ") + String(runtime / 1000UL) + String("s / ") + String(selected_time) + String('s');
  559. unsigned long anim = runtime * 20UL / (selected_time * 1000UL);
  560. String b;
  561. for (unsigned long i = 0; i < anim; i++) {
  562. b += '#';
  563. }
  564. print("----- Watering -----",
  565. a.c_str(),
  566. b.c_str(),
  567. "Hit any key to stop!",
  568. -1);
  569. } else if (s == auto_done) {
  570. String a = String("after ") + String((stop_time - start_time) / 1000UL) + String("s.");
  571. print("------- Done -------",
  572. "Dispensing finished",
  573. a.c_str(),
  574. "Hit any key for menu",
  575. -1);
  576. } else if (s == menu_pumps) {
  577. String a = String("(Input 1 to ") + String(plants.countFertilizers()) + String(")");
  578. print("------- Pump -------",
  579. "Please select pump",
  580. a.c_str(),
  581. "Pump: ",
  582. 3);
  583. } else if (s == menu_pumps_time) {
  584. String header = String("------ Pump ") + String(selected_id) + String(" ------");
  585. print(header.c_str(),
  586. "Please set runtime",
  587. "(Input in seconds)",
  588. "Runtime: ",
  589. 3);
  590. } else if (s == menu_pumps_go) {
  591. String a = String("Pump No. ") + String(selected_id);
  592. String b = String("Runtime ") + String(selected_time) + String('s');
  593. print("----- Confirm? -----",
  594. a.c_str(),
  595. b.c_str(),
  596. " # Confirm",
  597. -1);
  598. } else if (s == menu_pumps_run) {
  599. unsigned long runtime = millis() - start_time;
  600. String a = String("Runtime: ") + String(runtime / 1000UL) + String("s / ") + String(selected_time) + String('s');
  601. unsigned long anim = runtime * 20UL / (selected_time * 1000UL);
  602. String b;
  603. for (unsigned long i = 0; i < anim; i++) {
  604. b += '#';
  605. }
  606. print("---- Dispensing ----",
  607. a.c_str(),
  608. b.c_str(),
  609. "Hit any key to stop!",
  610. -1);
  611. } else if (s == menu_pumps_done) {
  612. String a = String("after ") + String((stop_time - start_time) / 1000UL) + String("s.");
  613. print("------- Done -------",
  614. "Dispensing finished",
  615. a.c_str(),
  616. "Hit any key for menu",
  617. -1);
  618. } else if (s == menu_valves) {
  619. String a = String("(Input 1 to ") + String(plants.countPlants() + 1) + String(")");
  620. print("------ Valves ------",
  621. "Please select valve",
  622. a.c_str(),
  623. "Valve: ",
  624. 3);
  625. } else if (s == menu_valves_time) {
  626. String header = String("----- Valve ") + String(selected_id) + String(" -----");
  627. print(header.c_str(),
  628. "Please set runtime",
  629. "(Input in seconds)",
  630. "Runtime: ",
  631. 3);
  632. } else if (s == menu_valves_go) {
  633. String a = String("Valve No. ") + String(selected_id);
  634. String b = String("Runtime ") + String(selected_time) + String('s');
  635. print("----- Confirm? -----",
  636. a.c_str(),
  637. b.c_str(),
  638. " # Confirm",
  639. -1);
  640. } else if (s == menu_valves_run) {
  641. unsigned long runtime = millis() - start_time;
  642. String a = String("Runtime: ") + String(runtime / 1000UL) + String("s / ") + String(selected_time) + String('s');
  643. unsigned long anim = runtime * 20UL / (selected_time * 1000UL);
  644. String b;
  645. for (unsigned long i = 0; i <= anim; i++) {
  646. b += '#';
  647. }
  648. print("---- Dispensing ----",
  649. a.c_str(),
  650. b.c_str(),
  651. "Hit any key to stop!",
  652. -1);
  653. } else if (s == menu_valves_done) {
  654. String a = String("after ") + String((stop_time - start_time) / 1000UL) + String("s.");
  655. print("------- Done -------",
  656. "Dispensing finished",
  657. a.c_str(),
  658. "Hit any key for menu",
  659. -1);
  660. } else if (s == error) {
  661. print("------ Error! ------",
  662. "There is a problem:",
  663. error_condition.c_str(),
  664. " Press any key...",
  665. -1);
  666. }
  667. }