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 48KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405
  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 "WifiStuff.h"
  22. #include "Statemachine.h"
  23. #include "config.h"
  24. #include "config_pins.h"
  25. Statemachine::DigitBuffer::DigitBuffer(int _size) {
  26. size = _size;
  27. pos = 0;
  28. digits = new int[size];
  29. }
  30. Statemachine::DigitBuffer::~DigitBuffer() {
  31. delete digits;
  32. }
  33. bool Statemachine::DigitBuffer::spaceLeft(void) {
  34. return (pos < size);
  35. }
  36. bool Statemachine::DigitBuffer::hasDigits(void) {
  37. return (pos > 0);
  38. }
  39. int Statemachine::DigitBuffer::countDigits(void) {
  40. return pos;
  41. }
  42. void Statemachine::DigitBuffer::addDigit(int d) {
  43. if (spaceLeft()) {
  44. digits[pos] = d;
  45. pos++;
  46. }
  47. }
  48. void Statemachine::DigitBuffer::removeDigit(void) {
  49. if (hasDigits()) {
  50. pos--;
  51. }
  52. }
  53. void Statemachine::DigitBuffer::clear(void) {
  54. pos = 0;
  55. }
  56. uint32_t Statemachine::DigitBuffer::getNumber(void) {
  57. uint32_t fact = 1;
  58. uint32_t sum = 0;
  59. for (int i = (pos - 1); i >= 0; i--) {
  60. sum += digits[i] * fact;
  61. fact *= 10;
  62. }
  63. return sum;
  64. }
  65. static const char *state_names[] = {
  66. stringify(init),
  67. stringify(door_select),
  68. stringify(menu_a),
  69. stringify(menu_b),
  70. stringify(menu_c),
  71. stringify(auto_mode_a),
  72. stringify(auto_mode_b),
  73. stringify(auto_fert_a),
  74. stringify(auto_fert_b),
  75. stringify(auto_fert_run),
  76. stringify(auto_tank_run),
  77. stringify(auto_stirr_run),
  78. stringify(auto_plant),
  79. stringify(auto_plant_run),
  80. stringify(auto_done),
  81. stringify(fillnwater_plant),
  82. stringify(fillnwater_tank_run),
  83. stringify(fillnwater_plant_run),
  84. stringify(automation_mode),
  85. stringify(menu_pumps),
  86. stringify(menu_pumps_time),
  87. stringify(menu_pumps_go),
  88. stringify(menu_pumps_run),
  89. stringify(menu_pumps_done),
  90. stringify(menu_valves),
  91. stringify(menu_valves_time),
  92. stringify(menu_valves_go),
  93. stringify(menu_valves_run),
  94. stringify(menu_valves_done),
  95. stringify(menu_aux),
  96. stringify(menu_aux_time),
  97. stringify(menu_aux_go),
  98. stringify(menu_aux_run),
  99. stringify(menu_aux_done),
  100. stringify(error)
  101. };
  102. const char *Statemachine::getStateName(void) {
  103. return state_names[state];
  104. }
  105. bool Statemachine::isIdle(void) {
  106. return state == init;
  107. }
  108. Statemachine::Statemachine(print_fn _print, backspace_fn _backspace)
  109. : db(7), selected_plants(plants.countPlants()) {
  110. state = init;
  111. old_state = init;
  112. print = _print;
  113. backspace = _backspace;
  114. selected_id = 0;
  115. selected_time = 0;
  116. start_time = 0;
  117. stop_time = 0;
  118. last_animation_time = 0;
  119. error_condition = "";
  120. into_state_time = 0;
  121. filling_started_empty = false;
  122. watering_started_full = false;
  123. menu_entered_digits = "";
  124. }
  125. void Statemachine::begin(void) {
  126. switch_to(init);
  127. }
  128. void Statemachine::input(int n) {
  129. if (state == init) {
  130. #if (LOCK_COUNT > 0) && defined(DOOR_LOCK_PIN)
  131. if (n == -1) {
  132. if (db.hasDigits()) {
  133. backspace();
  134. db.removeDigit();
  135. if (menu_entered_digits.length() > 0) {
  136. menu_entered_digits.remove(menu_entered_digits.length() - 1);
  137. switch_to(state);
  138. }
  139. } else {
  140. switch_to(menu_a);
  141. }
  142. } else if (n == -2) {
  143. switch_to(menu_a);
  144. } else {
  145. if (db.spaceLeft()) {
  146. db.addDigit(n);
  147. //menu_entered_digits += String(n);
  148. menu_entered_digits += String("*");
  149. switch_to(state);
  150. } else {
  151. backspace();
  152. }
  153. uint32_t n = db.getNumber();
  154. if (n == DOOR_LOCK_PIN) {
  155. db.clear();
  156. menu_entered_digits = "";
  157. selected_plants.clear();
  158. switch_to(door_select);
  159. } else if (db.countDigits() >= DOOR_LOCK_PIN_MAX_DIGITS) {
  160. db.clear();
  161. menu_entered_digits = "";
  162. switch_to(state);
  163. }
  164. }
  165. #else
  166. switch_to(menu_a);
  167. #endif
  168. } else if (state == door_select) {
  169. #if (LOCK_COUNT > 0)
  170. if (n == -1) {
  171. if (db.hasDigits()) {
  172. backspace();
  173. db.removeDigit();
  174. if (menu_entered_digits.length() > 0) {
  175. menu_entered_digits.remove(menu_entered_digits.length() - 1);
  176. switch_to(state);
  177. }
  178. } else {
  179. switch_to(init);
  180. }
  181. } else if (n == -2) {
  182. if (!db.hasDigits()) {
  183. for (int i = 0; i < LOCK_COUNT; i++) {
  184. if (selected_plants.isSet(i)) {
  185. plants.startAux(STIRRER_COUNT + i);
  186. delay(DOOR_LOCK_ON_TIME);
  187. plants.stopAux(STIRRER_COUNT + i);
  188. }
  189. }
  190. plants.stopAllAux();
  191. switch_to(menu_a);
  192. } else {
  193. selected_id = number_input();
  194. if ((selected_id <= 0) || (selected_id > LOCK_COUNT)) {
  195. error_condition = F("Invalid lock ID!");
  196. switch_to(error);
  197. } else {
  198. selected_plants.set(selected_id - 1);
  199. menu_entered_digits = "";
  200. switch_to(state);
  201. }
  202. }
  203. } else {
  204. if (db.spaceLeft()) {
  205. db.addDigit(n);
  206. menu_entered_digits += String(n);
  207. switch_to(state);
  208. } else {
  209. backspace();
  210. }
  211. }
  212. #else
  213. // should never be reached
  214. switch_to(menu_a);
  215. #endif
  216. } else if ((state == menu_a) || (state == menu_b) || (state == menu_c)) {
  217. if (n == 1) {
  218. switch_to(auto_mode_a);
  219. } else if (n == 2) {
  220. switch_to(automation_mode);
  221. } else if (n == 3) {
  222. switch_to(menu_pumps);
  223. } else if (n == 4) {
  224. switch_to(menu_valves);
  225. } else if (n == 5) {
  226. switch_to(menu_aux);
  227. #if (LOCK_COUNT > 0) && !defined(DOOR_LOCK_PIN)
  228. } else if (n == 6) {
  229. switch_to(door_select);
  230. #endif
  231. } else if (n == -1) {
  232. switch_to(init);
  233. } else if (n == -2) {
  234. switch_to((state == menu_a) ? menu_b : ((state == menu_b) ? menu_c : menu_a));
  235. }
  236. } else if (state == automation_mode) {
  237. // TODO
  238. switch_to(menu_a);
  239. } else if ((state == auto_mode_a) || (state == auto_mode_b)) {
  240. if (n == 1) {
  241. switch_to(auto_fert_a);
  242. } else if (n == 2) {
  243. selected_plants.clear();
  244. switch_to(fillnwater_plant);
  245. } else if (n == 3) {
  246. auto wl = plants.getWaterlevel();
  247. if ((wl != Plants::full) && (wl != Plants::invalid)) {
  248. plants.openWaterInlet();
  249. selected_id = plants.countPlants() + 1;
  250. selected_time = MAX_TANK_FILL_TIME;
  251. start_time = millis();
  252. switch_to(auto_tank_run);
  253. } else if (wl == Plants::full) {
  254. stop_time = millis();
  255. switch_to(auto_mode_a);
  256. } else if (wl == Plants::invalid) {
  257. error_condition = F("Invalid sensor state");
  258. state = auto_mode_a;
  259. switch_to(error);
  260. }
  261. } else if (n == 4) {
  262. selected_plants.clear();
  263. switch_to(auto_plant);
  264. } else if (n == -1) {
  265. switch_to(menu_a);
  266. } else if (n == -2) {
  267. switch_to((state == auto_mode_a) ? auto_mode_b : auto_mode_a);
  268. }
  269. } else if ((state == auto_fert_a) || (state == auto_fert_b)) {
  270. if ((n >= 1) && (n <= 3)) {
  271. auto wl = plants.getWaterlevel();
  272. if ((wl != Plants::full) && (wl != Plants::invalid)) {
  273. plants.startFertilizer(n - 1);
  274. selected_id = n;
  275. selected_time = AUTO_PUMP_RUNTIME;
  276. start_time = millis();
  277. switch_to(auto_fert_run);
  278. } else if (wl == Plants::full) {
  279. stop_time = millis();
  280. switch_to(auto_mode_a);
  281. } else if (wl == Plants::invalid) {
  282. error_condition = F("Invalid sensor state");
  283. state = auto_mode_a;
  284. switch_to(error);
  285. }
  286. } else if (n == 4) {
  287. for (int i = 0; i < STIRRER_COUNT; i++) {
  288. plants.startAux(i);
  289. }
  290. selected_id = 1;
  291. selected_time = AUTO_STIRR_RUNTIME;
  292. start_time = millis();
  293. switch_to(auto_stirr_run);
  294. } else if (n == -1) {
  295. switch_to(auto_mode_a);
  296. } else if (n == -2) {
  297. switch_to((state == auto_fert_a) ? auto_fert_b : auto_fert_a);
  298. }
  299. } else if ((state == auto_fert_run) || (state == auto_tank_run)
  300. || (state == auto_stirr_run)) {
  301. plants.abort();
  302. stop_time = millis();
  303. switch_to(auto_done);
  304. } else if (state == auto_plant) {
  305. if (n == -1) {
  306. if (db.hasDigits()) {
  307. backspace();
  308. db.removeDigit();
  309. if (menu_entered_digits.length() > 0) {
  310. menu_entered_digits.remove(menu_entered_digits.length() - 1);
  311. switch_to(state);
  312. }
  313. } else {
  314. switch_to(auto_mode_b);
  315. }
  316. } else if (n == -2) {
  317. if (!db.hasDigits()) {
  318. auto wl = plants.getWaterlevel();
  319. if ((wl != Plants::empty) && (wl != Plants::invalid)) {
  320. for (int i = 0; i < plants.countPlants(); i++) {
  321. if (selected_plants.isSet(i)) {
  322. plants.startPlant(i);
  323. }
  324. }
  325. selected_time = MAX_AUTO_PLANT_RUNTIME;
  326. start_time = millis();
  327. switch_to(auto_plant_run);
  328. } else if (wl == Plants::empty) {
  329. stop_time = millis();
  330. switch_to(auto_mode_b);
  331. } else if (wl == Plants::invalid) {
  332. error_condition = F("Invalid sensor state");
  333. state = auto_mode_b;
  334. switch_to(error);
  335. }
  336. } else {
  337. selected_id = number_input();
  338. if ((selected_id <= 0) || (selected_id > plants.countPlants())) {
  339. error_condition = F("Invalid plant ID!");
  340. switch_to(error);
  341. } else {
  342. selected_plants.set(selected_id - 1);
  343. menu_entered_digits = "";
  344. switch_to(auto_plant);
  345. }
  346. }
  347. } else {
  348. if (db.spaceLeft()) {
  349. db.addDigit(n);
  350. menu_entered_digits += String(n);
  351. switch_to(state);
  352. } else {
  353. backspace();
  354. }
  355. }
  356. } else if (state == auto_plant_run) {
  357. plants.abort();
  358. stop_time = millis();
  359. switch_to(auto_done);
  360. } else if (state == auto_done) {
  361. switch_to(auto_mode_a);
  362. } else if (state == menu_pumps) {
  363. if (n == -1) {
  364. if (db.hasDigits()) {
  365. backspace();
  366. db.removeDigit();
  367. if (menu_entered_digits.length() > 0) {
  368. menu_entered_digits.remove(menu_entered_digits.length() - 1);
  369. switch_to(state);
  370. }
  371. } else {
  372. switch_to(menu_b);
  373. }
  374. } else if (n == -2) {
  375. if (!db.hasDigits()) {
  376. return;
  377. }
  378. selected_id = number_input();
  379. if ((selected_id <= 0) || (selected_id > plants.countFertilizers())) {
  380. error_condition = F("Invalid pump ID!");
  381. switch_to(error);
  382. } else {
  383. switch_to(menu_pumps_time);
  384. }
  385. } else {
  386. if (db.spaceLeft()) {
  387. db.addDigit(n);
  388. menu_entered_digits += String(n);
  389. switch_to(state);
  390. } else {
  391. backspace();
  392. }
  393. }
  394. } else if (state == fillnwater_plant) {
  395. if (n == -1) {
  396. if (db.hasDigits()) {
  397. backspace();
  398. db.removeDigit();
  399. if (menu_entered_digits.length() > 0) {
  400. menu_entered_digits.remove(menu_entered_digits.length() - 1);
  401. switch_to(state);
  402. }
  403. } else {
  404. switch_to(auto_mode_a);
  405. }
  406. } else if (n == -2) {
  407. if (!db.hasDigits()) {
  408. int found = 0;
  409. for (int i = 0; i < plants.countPlants(); i++) {
  410. if (selected_plants.isSet(i)) {
  411. found = 1;
  412. break;
  413. }
  414. }
  415. if (found != 0) {
  416. auto wl = plants.getWaterlevel();
  417. if ((wl != Plants::full) && (wl != Plants::invalid)) {
  418. // if the waterlevel is currently empty, we
  419. // set a flag to record the time to fill
  420. filling_started_empty = (wl == Plants::empty);
  421. plants.openWaterInlet();
  422. selected_id = plants.countPlants() + 1;
  423. selected_time = MAX_TANK_FILL_TIME;
  424. start_time = millis();
  425. switch_to(fillnwater_tank_run);
  426. } else if (wl == Plants::full) {
  427. stop_time = millis();
  428. auto wl = plants.getWaterlevel();
  429. if ((wl != Plants::empty) && (wl != Plants::invalid)) {
  430. for (int i = 0; i < plants.countPlants(); i++) {
  431. if (selected_plants.isSet(i)) {
  432. plants.startPlant(i);
  433. }
  434. }
  435. // for recording the flowrate
  436. watering_started_full = (wl == Plants::full);
  437. selected_time = MAX_AUTO_PLANT_RUNTIME;
  438. start_time = millis();
  439. switch_to(fillnwater_plant_run);
  440. } else if (wl == Plants::empty) {
  441. stop_time = millis();
  442. switch_to(auto_mode_a);
  443. } else if (wl == Plants::invalid) {
  444. error_condition = F("Invalid sensor state");
  445. state = auto_mode_a;
  446. switch_to(error);
  447. }
  448. } else if (wl == Plants::invalid) {
  449. error_condition = F("Invalid sensor state");
  450. state = auto_mode_a;
  451. switch_to(error);
  452. }
  453. }
  454. } else {
  455. selected_id = number_input();
  456. if ((selected_id <= 0) || (selected_id > plants.countPlants())) {
  457. error_condition = F("Invalid plant ID!");
  458. switch_to(error);
  459. } else {
  460. selected_plants.set(selected_id - 1);
  461. menu_entered_digits = "";
  462. switch_to(fillnwater_plant);
  463. }
  464. }
  465. } else {
  466. if (db.spaceLeft()) {
  467. db.addDigit(n);
  468. menu_entered_digits += String(n);
  469. switch_to(state);
  470. } else {
  471. backspace();
  472. }
  473. }
  474. } else if (state == fillnwater_tank_run) {
  475. plants.abort();
  476. stop_time = millis();
  477. auto wl = plants.getWaterlevel();
  478. if ((wl != Plants::empty) && (wl != Plants::invalid)) {
  479. for (int i = 0; i < plants.countPlants(); i++) {
  480. if (selected_plants.isSet(i)) {
  481. plants.startPlant(i);
  482. }
  483. }
  484. watering_started_full = (wl == Plants::full);
  485. selected_time = MAX_AUTO_PLANT_RUNTIME;
  486. start_time = millis();
  487. switch_to(fillnwater_plant_run);
  488. } else if (wl == Plants::empty) {
  489. switch_to(auto_mode_a);
  490. } else if (wl == Plants::invalid) {
  491. error_condition = F("Invalid sensor state");
  492. state = auto_mode_a;
  493. switch_to(error);
  494. }
  495. } else if (state == fillnwater_plant_run) {
  496. plants.abort();
  497. stop_time = millis();
  498. switch_to(auto_done);
  499. } else if (state == menu_pumps_time) {
  500. if (n == -1) {
  501. if (db.hasDigits()) {
  502. backspace();
  503. db.removeDigit();
  504. if (menu_entered_digits.length() > 0) {
  505. menu_entered_digits.remove(menu_entered_digits.length() - 1);
  506. switch_to(state);
  507. }
  508. } else {
  509. switch_to(menu_pumps);
  510. }
  511. } else if (n == -2) {
  512. if (!db.hasDigits()) {
  513. return;
  514. }
  515. selected_time = number_input();
  516. if ((selected_time <= 0) || (selected_time > MAX_PUMP_RUNTIME)) {
  517. error_condition = F("Invalid time range!");
  518. switch_to(error);
  519. } else {
  520. switch_to(menu_pumps_go);
  521. }
  522. } else {
  523. if (db.spaceLeft()) {
  524. db.addDigit(n);
  525. menu_entered_digits += String(n);
  526. switch_to(state);
  527. } else {
  528. backspace();
  529. }
  530. }
  531. } else if (state == menu_pumps_go) {
  532. if (n == -2) {
  533. start_time = millis();
  534. last_animation_time = start_time;
  535. auto wl = plants.getWaterlevel();
  536. if ((wl != Plants::full) && (wl != Plants::invalid)) {
  537. plants.startFertilizer(selected_id - 1);
  538. switch_to(menu_pumps_run);
  539. } else if (wl == Plants::full) {
  540. stop_time = millis();
  541. switch_to(menu_pumps_done);
  542. } else if (wl == Plants::invalid) {
  543. error_condition = F("Invalid sensor state");
  544. state = menu_pumps;
  545. switch_to(error);
  546. }
  547. } else {
  548. switch_to(menu_pumps_time);
  549. }
  550. } else if (state == menu_pumps_run) {
  551. plants.abort();
  552. stop_time = millis();
  553. switch_to(menu_pumps_done);
  554. } else if (state == menu_pumps_done) {
  555. switch_to(menu_b);
  556. } else if (state == menu_valves) {
  557. if (n == -1) {
  558. if (db.hasDigits()) {
  559. backspace();
  560. db.removeDigit();
  561. if (menu_entered_digits.length() > 0) {
  562. menu_entered_digits.remove(menu_entered_digits.length() - 1);
  563. switch_to(state);
  564. }
  565. } else {
  566. switch_to(menu_b);
  567. }
  568. } else if (n == -2) {
  569. if (!db.hasDigits()) {
  570. return;
  571. }
  572. selected_id = number_input();
  573. if ((selected_id <= 0) || (selected_id > (plants.countPlants() + 1))) {
  574. error_condition = F("Invalid valve ID!");
  575. switch_to(error);
  576. } else {
  577. switch_to(menu_valves_time);
  578. }
  579. } else {
  580. if (db.spaceLeft()) {
  581. db.addDigit(n);
  582. menu_entered_digits += String(n);
  583. switch_to(state);
  584. } else {
  585. backspace();
  586. }
  587. }
  588. } else if (state == menu_valves_time) {
  589. if (n == -1) {
  590. if (db.hasDigits()) {
  591. backspace();
  592. db.removeDigit();
  593. if (menu_entered_digits.length() > 0) {
  594. menu_entered_digits.remove(menu_entered_digits.length() - 1);
  595. switch_to(state);
  596. }
  597. } else {
  598. switch_to(menu_valves);
  599. }
  600. } else if (n == -2) {
  601. if (!db.hasDigits()) {
  602. return;
  603. }
  604. selected_time = number_input();
  605. if ((selected_time <= 0) || (selected_time > MAX_VALVE_RUNTIME)) {
  606. error_condition = F("Invalid time range!");
  607. switch_to(error);
  608. } else {
  609. switch_to(menu_valves_go);
  610. }
  611. } else {
  612. if (db.spaceLeft()) {
  613. db.addDigit(n);
  614. menu_entered_digits += String(n);
  615. switch_to(state);
  616. } else {
  617. backspace();
  618. }
  619. }
  620. } else if (state == menu_valves_go) {
  621. if (n == -2) {
  622. start_time = millis();
  623. last_animation_time = start_time;
  624. auto wl = plants.getWaterlevel();
  625. if ((wl != Plants::full) && (wl != Plants::invalid)) {
  626. if (selected_id >= (plants.countPlants() + 1)) {
  627. plants.openWaterInlet();
  628. } else {
  629. plants.startPlant(selected_id - 1);
  630. }
  631. switch_to(menu_valves_run);
  632. } else if (wl == Plants::full) {
  633. stop_time = millis();
  634. switch_to(menu_valves_done);
  635. } else if (wl == Plants::invalid) {
  636. error_condition = F("Invalid sensor state");
  637. state = menu_valves;
  638. switch_to(error);
  639. }
  640. } else {
  641. switch_to(menu_valves_time);
  642. }
  643. } else if (state == menu_valves_run) {
  644. plants.abort();
  645. stop_time = millis();
  646. switch_to(menu_valves_done);
  647. } else if (state == menu_valves_done) {
  648. switch_to(menu_b);
  649. } else if (state == menu_aux) {
  650. if (n == -1) {
  651. if (db.hasDigits()) {
  652. backspace();
  653. db.removeDigit();
  654. if (menu_entered_digits.length() > 0) {
  655. menu_entered_digits.remove(menu_entered_digits.length() - 1);
  656. switch_to(state);
  657. }
  658. } else {
  659. switch_to(menu_c);
  660. }
  661. } else if (n == -2) {
  662. if (!db.hasDigits()) {
  663. return;
  664. }
  665. selected_id = number_input();
  666. if ((selected_id <= 0) || (selected_id > plants.countAux())) {
  667. error_condition = F("Invalid valve ID!");
  668. switch_to(error);
  669. } else {
  670. switch_to(menu_aux_time);
  671. }
  672. } else {
  673. if (db.spaceLeft()) {
  674. db.addDigit(n);
  675. menu_entered_digits += String(n);
  676. switch_to(state);
  677. } else {
  678. backspace();
  679. }
  680. }
  681. } else if (state == menu_aux_time) {
  682. if (n == -1) {
  683. if (db.hasDigits()) {
  684. backspace();
  685. db.removeDigit();
  686. if (menu_entered_digits.length() > 0) {
  687. menu_entered_digits.remove(menu_entered_digits.length() - 1);
  688. switch_to(state);
  689. }
  690. } else {
  691. switch_to(menu_aux);
  692. }
  693. } else if (n == -2) {
  694. if (!db.hasDigits()) {
  695. return;
  696. }
  697. selected_time = number_input();
  698. if ((selected_time <= 0) || (selected_time > MAX_AUX_RUNTIME)) {
  699. error_condition = F("Invalid time range!");
  700. switch_to(error);
  701. } else {
  702. switch_to(menu_aux_go);
  703. }
  704. } else {
  705. if (db.spaceLeft()) {
  706. db.addDigit(n);
  707. menu_entered_digits += String(n);
  708. switch_to(state);
  709. } else {
  710. backspace();
  711. }
  712. }
  713. } else if (state == menu_aux_go) {
  714. if (n == -2) {
  715. start_time = millis();
  716. last_animation_time = start_time;
  717. plants.startAux(selected_id - 1);
  718. switch_to(menu_aux_run);
  719. } else {
  720. switch_to(menu_aux_time);
  721. }
  722. } else if (state == menu_aux_run) {
  723. plants.abort();
  724. stop_time = millis();
  725. switch_to(menu_aux_done);
  726. } else if (state == menu_aux_done) {
  727. switch_to(menu_c);
  728. } else if (state == error) {
  729. if (old_state != error) {
  730. switch_to(old_state);
  731. } else {
  732. switch_to(menu_a);
  733. }
  734. }
  735. }
  736. uint32_t Statemachine::number_input(void) {
  737. for (int i = 0; i < db.countDigits(); i++) {
  738. backspace();
  739. }
  740. uint32_t n = db.getNumber();
  741. db.clear();
  742. debug.print("Whole number input: ");
  743. debug.println(n);
  744. return n;
  745. }
  746. void Statemachine::act(void) {
  747. if ((state == menu_pumps_run) || (state == menu_valves_run)
  748. || (state == menu_aux_run)
  749. || (state == auto_stirr_run)) {
  750. unsigned long runtime = millis() - start_time;
  751. if ((runtime / 1000UL) >= selected_time) {
  752. // stop if timeout has been reached
  753. plants.abort();
  754. stop_time = millis();
  755. switch_to((state == menu_pumps_run) ? menu_pumps_done : ((state == menu_valves_run) ? menu_valves_done : ((state == menu_aux_run) ? menu_aux_done : auto_done)));
  756. } else if ((millis() - last_animation_time) >= 500) {
  757. // update animation if needed
  758. last_animation_time = millis();
  759. switch_to(state);
  760. }
  761. }
  762. #ifdef CHECK_SENSORS_VALVE_PUMP_MENU_FULL
  763. if ((state == menu_pumps_run) || ((state == menu_valves_run) && (selected_id == (plants.countPlants() + 1)))) {
  764. // check water level state
  765. auto wl = plants.getWaterlevel();
  766. if (wl == Plants::full) {
  767. plants.abort();
  768. stop_time = millis();
  769. switch_to((state == menu_pumps_run) ? menu_pumps_done : menu_valves_done);
  770. } else if (wl == Plants::invalid) {
  771. plants.abort();
  772. error_condition = F("Invalid sensor state");
  773. state = (state == menu_pumps_run) ? menu_pumps : menu_valves;
  774. switch_to(error);
  775. }
  776. }
  777. #endif // CHECK_SENSORS_VALVE_PUMP_MENU_FULL
  778. #ifdef CHECK_SENSORS_VALVE_PUMP_MENU_EMPTY
  779. if ((state == menu_valves_run) && (selected_id <= plants.countPlants())) {
  780. // check water level state
  781. auto wl = plants.getWaterlevel();
  782. if (wl == Plants::empty) {
  783. plants.abort();
  784. stop_time = millis();
  785. switch_to(menu_valves_done);
  786. } else if (wl == Plants::invalid) {
  787. plants.abort();
  788. error_condition = F("Invalid sensor state");
  789. state = menu_valves;
  790. switch_to(error);
  791. }
  792. }
  793. #endif // CHECK_SENSORS_VALVE_PUMP_MENU_EMPTY
  794. if ((state == auto_fert_run) || (state == auto_tank_run) || (state == fillnwater_tank_run)) {
  795. unsigned long runtime = millis() - start_time;
  796. if ((runtime / 1000UL) >= selected_time) {
  797. // stop if timeout has been reached
  798. plants.abort();
  799. stop_time = millis();
  800. if (state == fillnwater_tank_run) {
  801. auto wl = plants.getWaterlevel();
  802. if ((wl != Plants::empty) && (wl != Plants::invalid)) {
  803. for (int i = 0; i < plants.countPlants(); i++) {
  804. if (selected_plants.isSet(i)) {
  805. plants.startPlant(i);
  806. }
  807. }
  808. watering_started_full = (wl == Plants::full);
  809. selected_time = MAX_AUTO_PLANT_RUNTIME;
  810. start_time = millis();
  811. switch_to(fillnwater_plant_run);
  812. } else if (wl == Plants::empty) {
  813. stop_time = millis();
  814. switch_to(auto_done);
  815. } else if (wl == Plants::invalid) {
  816. error_condition = F("Invalid sensor state");
  817. state = auto_mode_a;
  818. switch_to(error);
  819. }
  820. } else {
  821. switch_to(auto_done);
  822. }
  823. } else if ((millis() - last_animation_time) >= 500) {
  824. // update animation if needed
  825. last_animation_time = millis();
  826. switch_to(state);
  827. }
  828. // check water level state
  829. auto wl = plants.getWaterlevel();
  830. if (wl == Plants::full) {
  831. plants.abort();
  832. stop_time = millis();
  833. if (state == fillnwater_tank_run) {
  834. // record time to fill here, if we started with
  835. // an empty tank at the start of filling
  836. if (filling_started_empty) {
  837. unsigned long time_to_fill = stop_time - start_time;
  838. debug.print("Filling tank took ");
  839. debug.print(String(time_to_fill));
  840. debug.println("ms");
  841. #if defined(PLATFORM_ESP)
  842. bool success = wifi_write_database(time_to_fill, "calibrated_filling", -1);
  843. if (!success) {
  844. debug.print("Error writing to InfluxDB ");
  845. debug.print(INFLUXDB_HOST);
  846. debug.print(":");
  847. debug.print(INFLUXDB_PORT);
  848. debug.print("/");
  849. debug.print(INFLUXDB_DATABASE);
  850. debug.println("/calibrated_filling");
  851. }
  852. #endif // PLATFORM_ESP
  853. }
  854. auto wl = plants.getWaterlevel();
  855. if ((wl != Plants::empty) && (wl != Plants::invalid)) {
  856. for (int i = 0; i < plants.countPlants(); i++) {
  857. if (selected_plants.isSet(i)) {
  858. plants.startPlant(i);
  859. }
  860. }
  861. watering_started_full = (wl == Plants::full);
  862. selected_time = MAX_AUTO_PLANT_RUNTIME;
  863. start_time = millis();
  864. switch_to(fillnwater_plant_run);
  865. } else if (wl == Plants::empty) {
  866. stop_time = millis();
  867. switch_to(auto_mode_a);
  868. } else if (wl == Plants::invalid) {
  869. error_condition = F("Invalid sensor state");
  870. state = auto_mode_a;
  871. switch_to(error);
  872. }
  873. } else {
  874. switch_to(auto_done);
  875. }
  876. } else if (wl == Plants::invalid) {
  877. plants.abort();
  878. error_condition = F("Invalid sensor state");
  879. state = auto_mode_a;
  880. switch_to(error);
  881. }
  882. }
  883. if ((state == auto_plant_run) || (state == fillnwater_plant_run)) {
  884. unsigned long runtime = millis() - start_time;
  885. if ((runtime / 1000UL) >= selected_time) {
  886. // stop if timeout has been reached
  887. plants.abort();
  888. stop_time = millis();
  889. switch_to(auto_done);
  890. } else if ((millis() - last_animation_time) >= 500) {
  891. // update animation if needed
  892. last_animation_time = millis();
  893. switch_to(state);
  894. }
  895. // check water level state
  896. auto wl = plants.getWaterlevel();
  897. if (wl == Plants::empty) {
  898. plants.abort();
  899. stop_time = millis();
  900. // if we started watering with a full tank
  901. // and then finished watering when it was empty
  902. // and we were only watering a single plant
  903. // look at this as a "calibration run" and record
  904. // the time it took to empty the tank
  905. if ((state == fillnwater_plant_run) && watering_started_full && (selected_plants.countSet() == 1)) {
  906. unsigned long time_to_water = stop_time - start_time;
  907. debug.print("Watering plant ");
  908. debug.print(selected_plants.getFirstSet() + 1);
  909. debug.print(" with the complete tank took ");
  910. debug.print(String(time_to_water));
  911. debug.println("ms");
  912. #if defined(PLATFORM_ESP)
  913. bool success = wifi_write_database(time_to_water, "calibrated_watering", selected_plants.getFirstSet() + 1);
  914. if (!success) {
  915. debug.print("Error writing to InfluxDB ");
  916. debug.print(INFLUXDB_HOST);
  917. debug.print(":");
  918. debug.print(INFLUXDB_PORT);
  919. debug.print("/");
  920. debug.print(INFLUXDB_DATABASE);
  921. debug.println("/calibrated_watering");
  922. }
  923. #endif // PLATFORM_ESP
  924. }
  925. switch_to(auto_done);
  926. } else if (wl == Plants::invalid) {
  927. plants.abort();
  928. error_condition = F("Invalid sensor state");
  929. state = auto_mode_a;
  930. switch_to(error);
  931. }
  932. }
  933. if ((state == menu_a) || (state == menu_b) || (state == menu_c)
  934. || (state == automation_mode) || (state == auto_done)
  935. || (state == auto_mode_a) || (state == auto_mode_b)
  936. || (state == auto_fert_a) || (state == auto_fert_b)
  937. || (state == auto_plant) || (state == fillnwater_plant)
  938. || (state == menu_pumps) || (state == menu_pumps_time)
  939. || (state == menu_pumps_go) || (state == menu_pumps_done)
  940. || (state == menu_valves) || (state == menu_valves_time)
  941. || (state == menu_valves_go) || (state == menu_valves_done)
  942. || (state == menu_aux) || (state == menu_aux_time)
  943. || (state == menu_aux_go) || (state == menu_aux_done)) {
  944. unsigned long runtime = millis() - into_state_time;
  945. if (runtime >= BACK_TO_IDLE_TIMEOUT) {
  946. debug.print("Idle timeout reached in state ");
  947. debug.println(state_names[state]);
  948. switch_to(init);
  949. }
  950. }
  951. }
  952. void Statemachine::switch_to(States s) {
  953. old_state = state;
  954. state = s;
  955. into_state_time = millis();
  956. if (old_state != state) {
  957. // don't spam log with every animation state "change"
  958. debug.print("switch_to ");
  959. debug.print(state_names[old_state]);
  960. debug.print(" --> ");
  961. debug.println(state_names[state]);
  962. menu_entered_digits = "";
  963. }
  964. if (s == init) {
  965. String a = String(F("- Giess-o-mat V")) + FIRMWARE_VERSION + String(F(" -"));
  966. #if (LOCK_COUNT > 0) && defined(DOOR_LOCK_PIN)
  967. String b = String(F("PIN: ")) + menu_entered_digits;
  968. print(a.c_str(),
  969. "* or # to enter menu",
  970. "Enter PIN for locks:",
  971. b.c_str(),
  972. 3);
  973. #else
  974. print(a.c_str(),
  975. "Usage: Enter number",
  976. "* Delete prev. digit",
  977. "# Execute input num.",
  978. -1);
  979. #endif
  980. } else if (s == door_select) {
  981. String a = String("(Input 1 to ") + String(LOCK_COUNT) + String(")");
  982. String b = String(F("Door: ")) + menu_entered_digits;
  983. print("- Select Door Lock -",
  984. "Leave empty if done!",
  985. a.c_str(),
  986. b.c_str(),
  987. 3);
  988. } else if (s == menu_a) {
  989. print("----- Menu 1/3 -----",
  990. "1: Manual Operation",
  991. "2: Automation",
  992. "#: Go to page 2/3...",
  993. -1);
  994. } else if (s == menu_b) {
  995. print("----- Menu 2/3 -----",
  996. "3: Fertilizer pumps",
  997. "4: Outlet valves",
  998. "#: Go to page 3/3...",
  999. -1);
  1000. } else if (s == menu_c) {
  1001. print("----- Menu 3/3 -----",
  1002. "5: Aux. Outputs",
  1003. #if (LOCK_COUNT > 0) && !defined(DOOR_LOCK_PIN)
  1004. "6: Door Locks",
  1005. #else
  1006. "",
  1007. #endif
  1008. "#: Go to page 1/3...",
  1009. -1);
  1010. } else if (state == automation_mode) {
  1011. // TODO
  1012. print("---- Automation ----",
  1013. "TODO NOT IMPLEMENTED",
  1014. "TODO NOT IMPLEMENTED",
  1015. "TODO NOT IMPLEMENTED",
  1016. -1);
  1017. } else if (s == auto_mode_a) {
  1018. print("---- Manual 1/2 ----",
  1019. "1: Add Fertilizer",
  1020. "2: Fill 'n' Water",
  1021. "#: Go to page 2/2...",
  1022. -1);
  1023. } else if (s == auto_mode_b) {
  1024. print("---- Manual 2/2 ----",
  1025. "3: Fill Reservoir",
  1026. "4: Water a plant",
  1027. "#: Go to page 1/2...",
  1028. -1);
  1029. } else if (s == auto_fert_a) {
  1030. print("-- Fertilizer 1/2 --",
  1031. "1: Vegetation Phase",
  1032. "2: Bloom Phase",
  1033. "#: Go to page 2/2...",
  1034. -1);
  1035. } else if (s == auto_fert_b) {
  1036. print("-- Fertilizer 2/2 --",
  1037. "3: Special Fert.",
  1038. "4: Run Stirrer",
  1039. "#: Go to page 1/2...",
  1040. -1);
  1041. } else if (s == auto_fert_run) {
  1042. unsigned long runtime = millis() - start_time;
  1043. String a = String("Time: ") + String(runtime / 1000UL) + String("s / ") + String(selected_time) + String('s');
  1044. unsigned long anim = runtime * 20UL / (selected_time * 1000UL);
  1045. String b;
  1046. for (unsigned long i = 0; i < anim; i++) {
  1047. b += '#';
  1048. }
  1049. print("---- Dispensing ----",
  1050. a.c_str(),
  1051. b.c_str(),
  1052. "Hit any key to stop!",
  1053. -1);
  1054. } else if (s == auto_stirr_run) {
  1055. unsigned long runtime = millis() - start_time;
  1056. String a = String("Time: ") + String(runtime / 1000UL) + String("s / ") + String(selected_time) + String('s');
  1057. unsigned long anim = runtime * 20UL / (selected_time * 1000UL);
  1058. String b;
  1059. for (unsigned long i = 0; i < anim; i++) {
  1060. b += '#';
  1061. }
  1062. print("----- Stirring -----",
  1063. a.c_str(),
  1064. b.c_str(),
  1065. "Hit any key to stop!",
  1066. -1);
  1067. } else if ((s == auto_tank_run) || (s == fillnwater_tank_run)) {
  1068. unsigned long runtime = millis() - start_time;
  1069. String a = String("Time: ") + String(runtime / 1000UL) + String("s / ") + String(selected_time) + String('s');
  1070. unsigned long anim = runtime * 20UL / (selected_time * 1000UL);
  1071. String b;
  1072. for (unsigned long i = 0; i < anim; i++) {
  1073. b += '#';
  1074. }
  1075. print("--- Filling Tank ---",
  1076. a.c_str(),
  1077. b.c_str(),
  1078. "Hit any key to stop!",
  1079. -1);
  1080. } else if ((s == auto_plant) || (s == fillnwater_plant)) {
  1081. String a = String("(Input 1 to ") + String(plants.countPlants()) + String(")");
  1082. String b = String(F("Plant: ")) + menu_entered_digits;
  1083. print("--- Select Plant ---",
  1084. "Leave empty if done!",
  1085. a.c_str(),
  1086. b.c_str(),
  1087. 3);
  1088. } else if ((s == auto_plant_run) || (s == fillnwater_plant_run)) {
  1089. unsigned long runtime = millis() - start_time;
  1090. String a = String("Time: ") + String(runtime / 1000UL) + String("s / ") + String(selected_time) + String('s');
  1091. unsigned long anim = runtime * 20UL / (selected_time * 1000UL);
  1092. String b;
  1093. for (unsigned long i = 0; i < anim; i++) {
  1094. b += '#';
  1095. }
  1096. print("----- Watering -----",
  1097. a.c_str(),
  1098. b.c_str(),
  1099. "Hit any key to stop!",
  1100. -1);
  1101. } else if (s == auto_done) {
  1102. String a = String("after ") + String((stop_time - start_time) / 1000UL) + String("s.");
  1103. print("------- Done -------",
  1104. "Dispensing finished",
  1105. a.c_str(),
  1106. "Hit any key for menu",
  1107. -1);
  1108. #if defined(PLATFORM_ESP)
  1109. unsigned long runtime = stop_time - start_time;
  1110. if ((old_state == auto_plant_run) || (old_state == fillnwater_plant_run)) {
  1111. for (int i = 0; i < plants.countPlants(); i++) {
  1112. if (selected_plants.isSet(i)) {
  1113. bool success = wifi_write_database(runtime / 1000, "plant", i + 1);
  1114. if (!success) {
  1115. debug.print("Error writing to InfluxDB ");
  1116. debug.print(INFLUXDB_HOST);
  1117. debug.print(":");
  1118. debug.print(INFLUXDB_PORT);
  1119. debug.print("/");
  1120. debug.print(INFLUXDB_DATABASE);
  1121. debug.println("/plant");
  1122. }
  1123. }
  1124. }
  1125. } else if (old_state == auto_fert_run) {
  1126. bool success = wifi_write_database(runtime / 1000, "fertilizer", selected_id);
  1127. if (!success) {
  1128. debug.print("Error writing to InfluxDB ");
  1129. debug.print(INFLUXDB_HOST);
  1130. debug.print(":");
  1131. debug.print(INFLUXDB_PORT);
  1132. debug.print("/");
  1133. debug.print(INFLUXDB_DATABASE);
  1134. debug.println("/fertilizer");
  1135. }
  1136. }
  1137. #endif // PLATFORM_ESP
  1138. } else if (s == menu_pumps) {
  1139. String a = String("(Input 1 to ") + String(plants.countFertilizers()) + String(")");
  1140. String b = String(F("Pump: ")) + menu_entered_digits;
  1141. print("------- Pump -------",
  1142. "Please select pump",
  1143. a.c_str(),
  1144. b.c_str(),
  1145. 3);
  1146. } else if (s == menu_pumps_time) {
  1147. String header = String("------ Pump ") + String(selected_id) + String(" ------");
  1148. String b = String(F("Runtime: ")) + menu_entered_digits;
  1149. print(header.c_str(),
  1150. "Please set runtime",
  1151. "(Input in seconds)",
  1152. b.c_str(),
  1153. 3);
  1154. } else if (s == menu_pumps_go) {
  1155. String a = String("Pump No. ") + String(selected_id);
  1156. String b = String("Runtime ") + String(selected_time) + String('s');
  1157. print("----- Confirm? -----",
  1158. a.c_str(),
  1159. b.c_str(),
  1160. " # Confirm",
  1161. -1);
  1162. } else if (s == menu_pumps_run) {
  1163. unsigned long runtime = millis() - start_time;
  1164. String a = String("Time: ") + String(runtime / 1000UL) + String("s / ") + String(selected_time) + String('s');
  1165. unsigned long anim = runtime * 20UL / (selected_time * 1000UL);
  1166. String b;
  1167. for (unsigned long i = 0; i < anim; i++) {
  1168. b += '#';
  1169. }
  1170. print("---- Dispensing ----",
  1171. a.c_str(),
  1172. b.c_str(),
  1173. "Hit any key to stop!",
  1174. -1);
  1175. } else if (s == menu_pumps_done) {
  1176. String a = String("after ") + String((stop_time - start_time) / 1000UL) + String("s.");
  1177. print("------- Done -------",
  1178. "Dispensing finished",
  1179. a.c_str(),
  1180. "Hit any key for menu",
  1181. -1);
  1182. #if defined(PLATFORM_ESP)
  1183. unsigned long runtime = stop_time - start_time;
  1184. bool success = wifi_write_database(runtime / 1000, "fertilizer", selected_id);
  1185. if (!success) {
  1186. debug.print("Error writing to InfluxDB ");
  1187. debug.print(INFLUXDB_HOST);
  1188. debug.print(":");
  1189. debug.print(INFLUXDB_PORT);
  1190. debug.print("/");
  1191. debug.print(INFLUXDB_DATABASE);
  1192. debug.println("/fertilizer");
  1193. }
  1194. #endif // PLATFORM_ESP
  1195. } else if (s == menu_valves) {
  1196. String a = String("(Input 1 to ") + String(plants.countPlants() + 1) + String(")");
  1197. String b = String(F("Valve: ")) + menu_entered_digits;
  1198. print("------ Valves ------",
  1199. "Please select valve",
  1200. a.c_str(),
  1201. b.c_str(),
  1202. 3);
  1203. } else if (s == menu_valves_time) {
  1204. String header = String("----- Valve ") + String(selected_id) + String(" -----");
  1205. String b = String(F("Runtime: ")) + menu_entered_digits;
  1206. print(header.c_str(),
  1207. "Please set runtime",
  1208. "(Input in seconds)",
  1209. b.c_str(),
  1210. 3);
  1211. } else if (s == menu_valves_go) {
  1212. String a = String("Valve No. ") + String(selected_id);
  1213. String b = String("Runtime ") + String(selected_time) + String('s');
  1214. print("----- Confirm? -----",
  1215. a.c_str(),
  1216. b.c_str(),
  1217. " # Confirm",
  1218. -1);
  1219. } else if (s == menu_valves_run) {
  1220. unsigned long runtime = millis() - start_time;
  1221. String a = String("Time: ") + String(runtime / 1000UL) + String("s / ") + String(selected_time) + String('s');
  1222. unsigned long anim = runtime * 20UL / (selected_time * 1000UL);
  1223. String b;
  1224. for (unsigned long i = 0; i <= anim; i++) {
  1225. b += '#';
  1226. }
  1227. print("---- Dispensing ----",
  1228. a.c_str(),
  1229. b.c_str(),
  1230. "Hit any key to stop!",
  1231. -1);
  1232. } else if (s == menu_valves_done) {
  1233. String a = String("after ") + String((stop_time - start_time) / 1000UL) + String("s.");
  1234. print("------- Done -------",
  1235. "Dispensing finished",
  1236. a.c_str(),
  1237. "Hit any key for menu",
  1238. -1);
  1239. #if defined(PLATFORM_ESP)
  1240. unsigned long runtime = stop_time - start_time;
  1241. if (selected_id <= plants.countPlants()) {
  1242. bool success = wifi_write_database(runtime / 1000, "plant", selected_id);
  1243. if (!success) {
  1244. debug.print("Error writing to InfluxDB ");
  1245. debug.print(INFLUXDB_HOST);
  1246. debug.print(":");
  1247. debug.print(INFLUXDB_PORT);
  1248. debug.print("/");
  1249. debug.print(INFLUXDB_DATABASE);
  1250. debug.println("/plant");
  1251. }
  1252. }
  1253. #endif // PLATFORM_ESP
  1254. } else if (s == menu_aux) {
  1255. String a = String("(Input 1 to ") + String(plants.countAux()) + String(")");
  1256. String b = String(F("Aux.: ")) + menu_entered_digits;
  1257. print("------- Aux. -------",
  1258. "Please select aux.",
  1259. a.c_str(),
  1260. b.c_str(),
  1261. 3);
  1262. } else if (s == menu_aux_time) {
  1263. String header = String("------ Aux ") + String(selected_id) + String(" ------");
  1264. String b = String(F("Runtime: ")) + menu_entered_digits;
  1265. print(header.c_str(),
  1266. "Please set runtime",
  1267. "(Input in seconds)",
  1268. b.c_str(),
  1269. 3);
  1270. } else if (s == menu_aux_go) {
  1271. String a = String("Aux No. ") + String(selected_id);
  1272. String b = String("Runtime ") + String(selected_time) + String('s');
  1273. print("----- Confirm? -----",
  1274. a.c_str(),
  1275. b.c_str(),
  1276. " # Confirm",
  1277. -1);
  1278. } else if (s == menu_aux_run) {
  1279. unsigned long runtime = millis() - start_time;
  1280. String a = String("Time: ") + String(runtime / 1000UL) + String("s / ") + String(selected_time) + String('s');
  1281. unsigned long anim = runtime * 20UL / (selected_time * 1000UL);
  1282. String b;
  1283. for (unsigned long i = 0; i <= anim; i++) {
  1284. b += '#';
  1285. }
  1286. print("----- Stirring -----",
  1287. a.c_str(),
  1288. b.c_str(),
  1289. "Hit any key to stop!",
  1290. -1);
  1291. } else if (s == menu_aux_done) {
  1292. String a = String("after ") + String((stop_time - start_time) / 1000UL) + String("s.");
  1293. print("------- Done -------",
  1294. "Aux. run finished",
  1295. a.c_str(),
  1296. "Hit any key for menu",
  1297. -1);
  1298. } else if (s == error) {
  1299. print("------ Error! ------",
  1300. "There is a problem:",
  1301. error_condition.c_str(),
  1302. " Press any key...",
  1303. -1);
  1304. } else {
  1305. debug.print("Invalid state ");
  1306. debug.println(s);
  1307. }
  1308. }