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

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942
  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. #ifdef FUNCTION_CONTROL
  26. Statemachine::DigitBuffer::DigitBuffer(int _size) {
  27. size = _size;
  28. pos = 0;
  29. digits = new int[size];
  30. }
  31. Statemachine::DigitBuffer::~DigitBuffer() {
  32. delete digits;
  33. }
  34. bool Statemachine::DigitBuffer::spaceLeft(void) {
  35. return (pos < size);
  36. }
  37. bool Statemachine::DigitBuffer::hasDigits(void) {
  38. return (pos > 0);
  39. }
  40. int Statemachine::DigitBuffer::countDigits(void) {
  41. return pos;
  42. }
  43. void Statemachine::DigitBuffer::addDigit(int d) {
  44. if (spaceLeft()) {
  45. digits[pos] = d;
  46. pos++;
  47. }
  48. }
  49. void Statemachine::DigitBuffer::removeDigit(void) {
  50. if (hasDigits()) {
  51. pos--;
  52. }
  53. }
  54. void Statemachine::DigitBuffer::clear(void) {
  55. pos = 0;
  56. }
  57. uint32_t Statemachine::DigitBuffer::getNumber(void) {
  58. uint32_t fact = 1;
  59. uint32_t sum = 0;
  60. for (int i = (pos - 1); i >= 0; i--) {
  61. sum += digits[i] * fact;
  62. fact *= 10;
  63. }
  64. return sum;
  65. }
  66. static const char *state_names[] = {
  67. stringify(init),
  68. stringify(door_select),
  69. stringify(menu_a),
  70. stringify(menu_b),
  71. stringify(menu_c),
  72. stringify(auto_mode_a),
  73. stringify(auto_mode_b),
  74. stringify(auto_mode_c),
  75. stringify(auto_fert_a),
  76. stringify(auto_fert_b),
  77. stringify(auto_fert_run),
  78. stringify(auto_tank_run),
  79. stringify(auto_stirr_run),
  80. stringify(auto_plant),
  81. stringify(auto_plant_kickstart_run),
  82. stringify(auto_plant_run),
  83. stringify(auto_done),
  84. stringify(fillnwater_plant),
  85. stringify(fillnwater_tank_run),
  86. stringify(fillnwater_kickstart_run),
  87. stringify(fillnwater_plant_run),
  88. stringify(fullauto_fert),
  89. stringify(fullauto_plant),
  90. stringify(fullauto_stirr_run),
  91. stringify(fullauto_fert_run),
  92. stringify(fullauto_tank_run),
  93. stringify(fullauto_kickstart_run),
  94. stringify(fullauto_plant_run),
  95. stringify(fullauto_plant_overrun),
  96. stringify(fullauto_tank_purge_run),
  97. stringify(fullauto_kickstart_purge_run),
  98. stringify(fullauto_plant_purge_run),
  99. stringify(fullauto_plant_purge_overrun),
  100. stringify(fullauto_done),
  101. stringify(automation_mode),
  102. stringify(menu_pumps),
  103. stringify(menu_pumps_time),
  104. stringify(menu_pumps_go),
  105. stringify(menu_pumps_run),
  106. stringify(menu_pumps_done),
  107. stringify(menu_valves),
  108. stringify(menu_valves_time),
  109. stringify(menu_valves_go),
  110. stringify(menu_valves_run),
  111. stringify(menu_valves_done),
  112. stringify(menu_aux),
  113. stringify(menu_aux_time),
  114. stringify(menu_aux_go),
  115. stringify(menu_aux_run),
  116. stringify(menu_aux_done),
  117. stringify(error)
  118. };
  119. static int auto_pump_runtime[PUMP_COUNT] = AUTO_PUMP_RUNTIME;
  120. const char *Statemachine::getStateName(void) {
  121. return state_names[state];
  122. }
  123. bool Statemachine::isIdle(void) {
  124. return state == init;
  125. }
  126. Statemachine::Statemachine(print_fn _print, backspace_fn _backspace)
  127. : db(7), selected_plants(plants.countPlants()),
  128. selected_ferts(plants.countFertilizers()) {
  129. state = init;
  130. old_state = init;
  131. print = _print;
  132. backspace = _backspace;
  133. selected_id = 0;
  134. selected_time = 0;
  135. start_time = 0;
  136. stop_time = 0;
  137. last_animation_time = 0;
  138. error_condition = "";
  139. into_state_time = 0;
  140. filling_started_empty = false;
  141. watering_started_full = false;
  142. menu_entered_digits = "";
  143. }
  144. void Statemachine::begin(void) {
  145. switch_to(init);
  146. }
  147. void Statemachine::input(int n) {
  148. if (state == init) {
  149. #if (LOCK_COUNT > 0) && defined(DOOR_LOCK_PIN)
  150. if (n == -1) {
  151. if (db.hasDigits()) {
  152. backspace();
  153. db.removeDigit();
  154. if (menu_entered_digits.length() > 0) {
  155. menu_entered_digits.remove(menu_entered_digits.length() - 1);
  156. switch_to(state);
  157. }
  158. } else {
  159. switch_to(menu_a);
  160. }
  161. } else if (n == -2) {
  162. switch_to(menu_a);
  163. } else {
  164. if (db.spaceLeft()) {
  165. db.addDigit(n);
  166. //menu_entered_digits += String(n);
  167. menu_entered_digits += String("*");
  168. switch_to(state);
  169. } else {
  170. backspace();
  171. }
  172. uint32_t n = db.getNumber();
  173. if (n == DOOR_LOCK_PIN) {
  174. db.clear();
  175. menu_entered_digits = "";
  176. selected_plants.clear();
  177. switch_to(door_select);
  178. } else if (db.countDigits() >= DOOR_LOCK_PIN_MAX_DIGITS) {
  179. db.clear();
  180. menu_entered_digits = "";
  181. switch_to(state);
  182. }
  183. }
  184. #else
  185. switch_to(menu_a);
  186. #endif
  187. } else if (state == door_select) {
  188. #if (LOCK_COUNT > 0)
  189. if (n == -1) {
  190. if (db.hasDigits()) {
  191. backspace();
  192. db.removeDigit();
  193. if (menu_entered_digits.length() > 0) {
  194. menu_entered_digits.remove(menu_entered_digits.length() - 1);
  195. switch_to(state);
  196. }
  197. } else {
  198. switch_to(init);
  199. }
  200. } else if (n == -2) {
  201. if (!db.hasDigits()) {
  202. for (int i = 0; i < LOCK_COUNT; i++) {
  203. if (selected_plants.isSet(i)) {
  204. plants.startAux(STIRRER_COUNT + i);
  205. delay(DOOR_LOCK_ON_TIME);
  206. plants.stopAux(STIRRER_COUNT + i);
  207. delay(DOOR_LOCK_NEXT_DELAY);
  208. }
  209. }
  210. plants.stopAllAux();
  211. switch_to(menu_a);
  212. } else {
  213. selected_id = number_input();
  214. if ((selected_id <= 0) || (selected_id > LOCK_COUNT)) {
  215. error_condition = F("Invalid lock ID!");
  216. switch_to(error);
  217. } else {
  218. selected_plants.set(selected_id - 1);
  219. menu_entered_digits = "";
  220. switch_to(state);
  221. }
  222. }
  223. } else {
  224. if (db.spaceLeft()) {
  225. db.addDigit(n);
  226. menu_entered_digits += String(n);
  227. switch_to(state);
  228. } else {
  229. backspace();
  230. }
  231. }
  232. #else
  233. // should never be reached
  234. switch_to(menu_a);
  235. #endif
  236. } else if ((state == menu_a) || (state == menu_b) || (state == menu_c)) {
  237. if (n == 1) {
  238. switch_to(auto_mode_a);
  239. } else if (n == 2) {
  240. switch_to(automation_mode);
  241. } else if (n == 3) {
  242. switch_to(menu_pumps);
  243. } else if (n == 4) {
  244. switch_to(menu_valves);
  245. } else if (n == 5) {
  246. switch_to(menu_aux);
  247. #if (LOCK_COUNT > 0) && !defined(DOOR_LOCK_PIN)
  248. } else if (n == 6) {
  249. switch_to(door_select);
  250. #endif
  251. } else if (n == -1) {
  252. switch_to(init);
  253. } else if (n == -2) {
  254. switch_to((state == menu_a) ? menu_b : ((state == menu_b) ? menu_c : menu_a));
  255. }
  256. } else if (state == automation_mode) {
  257. // TODO
  258. switch_to(menu_a);
  259. } else if ((state == auto_mode_a) || (state == auto_mode_b) || (state == auto_mode_c)) {
  260. if (n == 1) {
  261. selected_ferts.clear();
  262. switch_to(fullauto_fert);
  263. } else if (n == 2) {
  264. switch_to(auto_fert_a);
  265. } else if (n == 3) {
  266. selected_plants.clear();
  267. switch_to(fillnwater_plant);
  268. } else if (n == 4) {
  269. auto wl = plants.getWaterlevel();
  270. if ((wl != Plants::full) && (wl != Plants::invalid)) {
  271. plants.openWaterInlet();
  272. selected_id = plants.countPlants() + 1;
  273. selected_time = MAX_TANK_FILL_TIME;
  274. start_time = millis();
  275. switch_to(auto_tank_run);
  276. } else if (wl == Plants::full) {
  277. stop_time = millis();
  278. switch_to(auto_mode_a);
  279. } else if (wl == Plants::invalid) {
  280. error_condition = F("Invalid sensor state");
  281. state = auto_mode_a;
  282. switch_to(error);
  283. }
  284. } else if (n == 5) {
  285. selected_plants.clear();
  286. switch_to(auto_plant);
  287. } else if (n == -1) {
  288. switch_to(menu_a);
  289. } else if (n == -2) {
  290. switch_to((state == auto_mode_a) ? auto_mode_b : ((state == auto_mode_b) ? auto_mode_c : auto_mode_a));
  291. }
  292. } else if ((state == auto_fert_a) || (state == auto_fert_b)) {
  293. // TODO fertilizer number currently "hardcoded" to 3 in UI
  294. if ((n >= 1) && (n <= 3)) {
  295. auto wl = plants.getWaterlevel();
  296. if ((wl != Plants::full) && (wl != Plants::invalid)) {
  297. plants.startFertilizer(n - 1);
  298. selected_id = n;
  299. selected_time = auto_pump_runtime[n - 1];
  300. start_time = millis();
  301. switch_to(auto_fert_run);
  302. } else if (wl == Plants::full) {
  303. stop_time = millis();
  304. switch_to(auto_mode_a);
  305. } else if (wl == Plants::invalid) {
  306. error_condition = F("Invalid sensor state");
  307. state = auto_mode_a;
  308. switch_to(error);
  309. }
  310. } else if (n == 4) {
  311. for (int i = 0; i < STIRRER_COUNT; i++) {
  312. plants.startAux(i);
  313. }
  314. selected_id = 1;
  315. selected_time = AUTO_STIRR_RUNTIME;
  316. start_time = millis();
  317. switch_to(auto_stirr_run);
  318. } else if (n == -1) {
  319. switch_to(auto_mode_a);
  320. } else if (n == -2) {
  321. switch_to((state == auto_fert_a) ? auto_fert_b : auto_fert_a);
  322. }
  323. } else if ((state == auto_fert_run) || (state == auto_tank_run)
  324. || (state == auto_stirr_run) || (state == auto_plant_run)
  325. || (state == auto_plant_kickstart_run)
  326. || (state == fillnwater_kickstart_run)
  327. || (state == fullauto_stirr_run)
  328. || (state == fullauto_fert_run)
  329. || (state == fullauto_tank_run)
  330. || (state == fullauto_kickstart_run)
  331. || (state == fullauto_plant_run)
  332. || (state == fullauto_plant_overrun)
  333. || (state == fullauto_tank_purge_run)
  334. || (state == fullauto_kickstart_purge_run)
  335. || (state == fullauto_plant_purge_run)
  336. || (state == fullauto_plant_purge_overrun)) {
  337. plants.abort();
  338. stop_time = millis();
  339. switch_to(auto_done);
  340. } else if (state == auto_plant) {
  341. if (n == -1) {
  342. if (db.hasDigits()) {
  343. backspace();
  344. db.removeDigit();
  345. if (menu_entered_digits.length() > 0) {
  346. menu_entered_digits.remove(menu_entered_digits.length() - 1);
  347. switch_to(state);
  348. }
  349. } else {
  350. switch_to(auto_mode_b);
  351. }
  352. } else if (n == -2) {
  353. if (!db.hasDigits()) {
  354. auto wl = plants.getWaterlevel();
  355. if ((wl != Plants::empty) && (wl != Plants::invalid)) {
  356. // check if kickstart is required for this
  357. bool need_kickstart = false;
  358. for (int i = 0; i < plants.countPlants(); i++) {
  359. if (selected_plants.isSet(i)) {
  360. if (plants.getKickstart()->getPinNumber(i) >= 0) {
  361. need_kickstart = true;
  362. }
  363. }
  364. }
  365. // start kickstart/valve as needed
  366. for (int i = 0; i < plants.countPlants(); i++) {
  367. if (selected_plants.isSet(i)) {
  368. plants.startPlant(i, need_kickstart);
  369. }
  370. }
  371. selected_time = MAX_AUTO_PLANT_RUNTIME;
  372. start_time = millis();
  373. if (need_kickstart) {
  374. switch_to(auto_plant_kickstart_run);
  375. } else {
  376. switch_to(auto_plant_run);
  377. }
  378. } else if (wl == Plants::empty) {
  379. stop_time = millis();
  380. switch_to(auto_mode_b);
  381. } else if (wl == Plants::invalid) {
  382. error_condition = F("Invalid sensor state");
  383. state = auto_mode_b;
  384. switch_to(error);
  385. }
  386. } else {
  387. selected_id = number_input();
  388. if ((selected_id <= 0) || (selected_id > plants.countPlants())) {
  389. error_condition = F("Invalid plant ID!");
  390. switch_to(error);
  391. } else {
  392. selected_plants.set(selected_id - 1);
  393. menu_entered_digits = "";
  394. switch_to(auto_plant);
  395. }
  396. }
  397. } else {
  398. if (db.spaceLeft()) {
  399. db.addDigit(n);
  400. menu_entered_digits += String(n);
  401. switch_to(state);
  402. } else {
  403. backspace();
  404. }
  405. }
  406. } else if (state == fullauto_fert) {
  407. if (n == -1) {
  408. if (db.hasDigits()) {
  409. backspace();
  410. db.removeDigit();
  411. if (menu_entered_digits.length() > 0) {
  412. menu_entered_digits.remove(menu_entered_digits.length() - 1);
  413. switch_to(state);
  414. }
  415. } else {
  416. switch_to(auto_mode_a);
  417. }
  418. } else if (n == -2) {
  419. if (!db.hasDigits()) {
  420. selected_plants.clear();
  421. switch_to(fullauto_plant);
  422. } else {
  423. selected_id = number_input();
  424. if ((selected_id <= 0) || (selected_id > plants.countFertilizers())) {
  425. error_condition = F("Invalid fert. ID!");
  426. switch_to(error);
  427. } else {
  428. selected_ferts.set(selected_id - 1);
  429. menu_entered_digits = "";
  430. switch_to(fullauto_fert);
  431. }
  432. }
  433. } else {
  434. if (db.spaceLeft()) {
  435. db.addDigit(n);
  436. menu_entered_digits += String(n);
  437. switch_to(state);
  438. } else {
  439. backspace();
  440. }
  441. }
  442. } else if (state == fullauto_plant) {
  443. if (n == -1) {
  444. if (db.hasDigits()) {
  445. backspace();
  446. db.removeDigit();
  447. if (menu_entered_digits.length() > 0) {
  448. menu_entered_digits.remove(menu_entered_digits.length() - 1);
  449. switch_to(state);
  450. }
  451. } else {
  452. selected_ferts.clear();
  453. switch_to(fullauto_fert);
  454. }
  455. } else if (n == -2) {
  456. if (!db.hasDigits()) {
  457. if (selected_plants.countSet() <= 0) {
  458. // user has not selected a plant yet...
  459. return;
  460. }
  461. // check if we need to run fertilizers
  462. if (selected_ferts.countSet() > 0) {
  463. // stirr before pumping fertilizers
  464. for (int i = 0; i < STIRRER_COUNT; i++) {
  465. plants.startAux(i);
  466. }
  467. selected_id = 1;
  468. selected_time = AUTO_STIRR_RUNTIME;
  469. start_time = millis();
  470. switch_to(fullauto_stirr_run);
  471. } else {
  472. // immediately continue with filling tank
  473. auto wl = plants.getWaterlevel();
  474. if ((wl != Plants::full) && (wl != Plants::invalid)) {
  475. // if the waterlevel is currently empty, we
  476. // set a flag to record the time to fill
  477. filling_started_empty = (wl == Plants::empty);
  478. plants.openWaterInlet();
  479. selected_id = plants.countPlants() + 1;
  480. selected_time = MAX_TANK_FILL_TIME;
  481. start_time = millis();
  482. switch_to(fullauto_tank_run);
  483. } else if (wl == Plants::full) {
  484. // check if kickstart is required for this
  485. bool need_kickstart = false;
  486. for (int i = 0; i < plants.countPlants(); i++) {
  487. if (selected_plants.isSet(i)) {
  488. if (plants.getKickstart()->getPinNumber(i) >= 0) {
  489. need_kickstart = true;
  490. }
  491. }
  492. }
  493. // start kickstart/valve as needed
  494. for (int i = 0; i < plants.countPlants(); i++) {
  495. if (selected_plants.isSet(i)) {
  496. plants.startPlant(i, need_kickstart);
  497. }
  498. }
  499. // for recording the flowrate
  500. watering_started_full = (wl == Plants::full);
  501. selected_time = MAX_AUTO_PLANT_RUNTIME;
  502. start_time = millis();
  503. if (need_kickstart) {
  504. switch_to(fullauto_kickstart_run);
  505. } else {
  506. switch_to(fullauto_plant_run);
  507. }
  508. } else if (wl == Plants::invalid) {
  509. error_condition = F("Invalid sensor state");
  510. state = auto_mode_a;
  511. switch_to(error);
  512. }
  513. }
  514. } else {
  515. selected_id = number_input();
  516. if ((selected_id <= 0) || (selected_id > plants.countPlants())) {
  517. error_condition = F("Invalid plant ID!");
  518. switch_to(error);
  519. } else {
  520. selected_plants.set(selected_id - 1);
  521. menu_entered_digits = "";
  522. switch_to(fullauto_plant);
  523. }
  524. }
  525. } else {
  526. if (db.spaceLeft()) {
  527. db.addDigit(n);
  528. menu_entered_digits += String(n);
  529. switch_to(state);
  530. } else {
  531. backspace();
  532. }
  533. }
  534. } else if ((state == auto_done) || (state == fullauto_done)) {
  535. switch_to(auto_mode_a);
  536. } else if (state == menu_pumps) {
  537. if (n == -1) {
  538. if (db.hasDigits()) {
  539. backspace();
  540. db.removeDigit();
  541. if (menu_entered_digits.length() > 0) {
  542. menu_entered_digits.remove(menu_entered_digits.length() - 1);
  543. switch_to(state);
  544. }
  545. } else {
  546. switch_to(menu_b);
  547. }
  548. } else if (n == -2) {
  549. if (!db.hasDigits()) {
  550. return;
  551. }
  552. selected_id = number_input();
  553. if ((selected_id <= 0) || (selected_id > plants.countFertilizers())) {
  554. error_condition = F("Invalid pump ID!");
  555. switch_to(error);
  556. } else {
  557. switch_to(menu_pumps_time);
  558. }
  559. } else {
  560. if (db.spaceLeft()) {
  561. db.addDigit(n);
  562. menu_entered_digits += String(n);
  563. switch_to(state);
  564. } else {
  565. backspace();
  566. }
  567. }
  568. } else if (state == fillnwater_plant) {
  569. if (n == -1) {
  570. if (db.hasDigits()) {
  571. backspace();
  572. db.removeDigit();
  573. if (menu_entered_digits.length() > 0) {
  574. menu_entered_digits.remove(menu_entered_digits.length() - 1);
  575. switch_to(state);
  576. }
  577. } else {
  578. switch_to(auto_mode_a);
  579. }
  580. } else if (n == -2) {
  581. if (!db.hasDigits()) {
  582. if (selected_plants.countSet() <= 0) {
  583. return;
  584. }
  585. auto wl = plants.getWaterlevel();
  586. if ((wl != Plants::full) && (wl != Plants::invalid)) {
  587. // if the waterlevel is currently empty, we
  588. // set a flag to record the time to fill
  589. filling_started_empty = (wl == Plants::empty);
  590. plants.openWaterInlet();
  591. selected_id = plants.countPlants() + 1;
  592. selected_time = MAX_TANK_FILL_TIME;
  593. start_time = millis();
  594. switch_to(fillnwater_tank_run);
  595. } else if (wl == Plants::full) {
  596. // check if kickstart is required for this
  597. bool need_kickstart = false;
  598. for (int i = 0; i < plants.countPlants(); i++) {
  599. if (selected_plants.isSet(i)) {
  600. if (plants.getKickstart()->getPinNumber(i) >= 0) {
  601. need_kickstart = true;
  602. }
  603. }
  604. }
  605. // start kickstart/valve as needed
  606. for (int i = 0; i < plants.countPlants(); i++) {
  607. if (selected_plants.isSet(i)) {
  608. plants.startPlant(i, need_kickstart);
  609. }
  610. }
  611. // for recording the flowrate
  612. watering_started_full = (wl == Plants::full);
  613. selected_time = MAX_AUTO_PLANT_RUNTIME;
  614. start_time = millis();
  615. if (need_kickstart) {
  616. switch_to(fillnwater_kickstart_run);
  617. } else {
  618. switch_to(fillnwater_plant_run);
  619. }
  620. } else if (wl == Plants::invalid) {
  621. error_condition = F("Invalid sensor state");
  622. state = auto_mode_a;
  623. switch_to(error);
  624. }
  625. } else {
  626. selected_id = number_input();
  627. if ((selected_id <= 0) || (selected_id > plants.countPlants())) {
  628. error_condition = F("Invalid plant ID!");
  629. switch_to(error);
  630. } else {
  631. selected_plants.set(selected_id - 1);
  632. menu_entered_digits = "";
  633. switch_to(fillnwater_plant);
  634. }
  635. }
  636. } else {
  637. if (db.spaceLeft()) {
  638. db.addDigit(n);
  639. menu_entered_digits += String(n);
  640. switch_to(state);
  641. } else {
  642. backspace();
  643. }
  644. }
  645. } else if (state == fillnwater_tank_run) {
  646. plants.abort();
  647. stop_time = millis();
  648. auto wl = plants.getWaterlevel();
  649. if ((wl != Plants::empty) && (wl != Plants::invalid)) {
  650. // check if kickstart is required for this
  651. bool need_kickstart = false;
  652. for (int i = 0; i < plants.countPlants(); i++) {
  653. if (selected_plants.isSet(i)) {
  654. if (plants.getKickstart()->getPinNumber(i) >= 0) {
  655. need_kickstart = true;
  656. }
  657. }
  658. }
  659. // start kickstart/valve as needed
  660. for (int i = 0; i < plants.countPlants(); i++) {
  661. if (selected_plants.isSet(i)) {
  662. plants.startPlant(i, need_kickstart);
  663. }
  664. }
  665. watering_started_full = (wl == Plants::full);
  666. selected_time = MAX_AUTO_PLANT_RUNTIME;
  667. start_time = millis();
  668. if (need_kickstart) {
  669. switch_to(fillnwater_kickstart_run);
  670. } else {
  671. switch_to(fillnwater_plant_run);
  672. }
  673. } else if (wl == Plants::empty) {
  674. switch_to(auto_mode_a);
  675. } else if (wl == Plants::invalid) {
  676. error_condition = F("Invalid sensor state");
  677. state = auto_mode_a;
  678. switch_to(error);
  679. }
  680. } else if (state == fillnwater_plant_run) {
  681. plants.abort();
  682. stop_time = millis();
  683. switch_to(auto_done);
  684. } else if (state == menu_pumps_time) {
  685. if (n == -1) {
  686. if (db.hasDigits()) {
  687. backspace();
  688. db.removeDigit();
  689. if (menu_entered_digits.length() > 0) {
  690. menu_entered_digits.remove(menu_entered_digits.length() - 1);
  691. switch_to(state);
  692. }
  693. } else {
  694. switch_to(menu_pumps);
  695. }
  696. } else if (n == -2) {
  697. if (!db.hasDigits()) {
  698. return;
  699. }
  700. selected_time = number_input();
  701. if ((selected_time <= 0) || (selected_time > MAX_PUMP_RUNTIME)) {
  702. error_condition = F("Invalid time range!");
  703. switch_to(error);
  704. } else {
  705. switch_to(menu_pumps_go);
  706. }
  707. } else {
  708. if (db.spaceLeft()) {
  709. db.addDigit(n);
  710. menu_entered_digits += String(n);
  711. switch_to(state);
  712. } else {
  713. backspace();
  714. }
  715. }
  716. } else if (state == menu_pumps_go) {
  717. if (n == -2) {
  718. start_time = millis();
  719. last_animation_time = start_time;
  720. auto wl = plants.getWaterlevel();
  721. if ((wl != Plants::full) && (wl != Plants::invalid)) {
  722. plants.startFertilizer(selected_id - 1);
  723. switch_to(menu_pumps_run);
  724. } else if (wl == Plants::full) {
  725. stop_time = millis();
  726. switch_to(menu_pumps_done);
  727. } else if (wl == Plants::invalid) {
  728. error_condition = F("Invalid sensor state");
  729. state = menu_pumps;
  730. switch_to(error);
  731. }
  732. } else {
  733. switch_to(menu_pumps_time);
  734. }
  735. } else if (state == menu_pumps_run) {
  736. plants.abort();
  737. stop_time = millis();
  738. switch_to(menu_pumps_done);
  739. } else if (state == menu_pumps_done) {
  740. switch_to(menu_b);
  741. } else if (state == menu_valves) {
  742. if (n == -1) {
  743. if (db.hasDigits()) {
  744. backspace();
  745. db.removeDigit();
  746. if (menu_entered_digits.length() > 0) {
  747. menu_entered_digits.remove(menu_entered_digits.length() - 1);
  748. switch_to(state);
  749. }
  750. } else {
  751. switch_to(menu_b);
  752. }
  753. } else if (n == -2) {
  754. if (!db.hasDigits()) {
  755. return;
  756. }
  757. selected_id = number_input();
  758. if ((selected_id <= 0) || (selected_id > (plants.countPlants() + 1))) {
  759. error_condition = F("Invalid valve ID!");
  760. switch_to(error);
  761. } else {
  762. switch_to(menu_valves_time);
  763. }
  764. } else {
  765. if (db.spaceLeft()) {
  766. db.addDigit(n);
  767. menu_entered_digits += String(n);
  768. switch_to(state);
  769. } else {
  770. backspace();
  771. }
  772. }
  773. } else if (state == menu_valves_time) {
  774. if (n == -1) {
  775. if (db.hasDigits()) {
  776. backspace();
  777. db.removeDigit();
  778. if (menu_entered_digits.length() > 0) {
  779. menu_entered_digits.remove(menu_entered_digits.length() - 1);
  780. switch_to(state);
  781. }
  782. } else {
  783. switch_to(menu_valves);
  784. }
  785. } else if (n == -2) {
  786. if (!db.hasDigits()) {
  787. return;
  788. }
  789. selected_time = number_input();
  790. if ((selected_time <= 0) || (selected_time > MAX_VALVE_RUNTIME)) {
  791. error_condition = F("Invalid time range!");
  792. switch_to(error);
  793. } else {
  794. switch_to(menu_valves_go);
  795. }
  796. } else {
  797. if (db.spaceLeft()) {
  798. db.addDigit(n);
  799. menu_entered_digits += String(n);
  800. switch_to(state);
  801. } else {
  802. backspace();
  803. }
  804. }
  805. } else if (state == menu_valves_go) {
  806. if (n == -2) {
  807. start_time = millis();
  808. last_animation_time = start_time;
  809. auto wl = plants.getWaterlevel();
  810. if ((wl != Plants::full) && (wl != Plants::invalid)) {
  811. if (selected_id >= (plants.countPlants() + 1)) {
  812. plants.openWaterInlet();
  813. } else {
  814. // TODO support testing kickstart
  815. plants.startPlant(selected_id - 1, false);
  816. }
  817. switch_to(menu_valves_run);
  818. } else if (wl == Plants::full) {
  819. stop_time = millis();
  820. switch_to(menu_valves_done);
  821. } else if (wl == Plants::invalid) {
  822. error_condition = F("Invalid sensor state");
  823. state = menu_valves;
  824. switch_to(error);
  825. }
  826. } else {
  827. switch_to(menu_valves_time);
  828. }
  829. } else if (state == menu_valves_run) {
  830. plants.abort();
  831. stop_time = millis();
  832. switch_to(menu_valves_done);
  833. } else if (state == menu_valves_done) {
  834. switch_to(menu_b);
  835. } else if (state == menu_aux) {
  836. if (n == -1) {
  837. if (db.hasDigits()) {
  838. backspace();
  839. db.removeDigit();
  840. if (menu_entered_digits.length() > 0) {
  841. menu_entered_digits.remove(menu_entered_digits.length() - 1);
  842. switch_to(state);
  843. }
  844. } else {
  845. switch_to(menu_c);
  846. }
  847. } else if (n == -2) {
  848. if (!db.hasDigits()) {
  849. return;
  850. }
  851. selected_id = number_input();
  852. if ((selected_id <= 0) || (selected_id > plants.countAux())) {
  853. error_condition = F("Invalid valve ID!");
  854. switch_to(error);
  855. } else {
  856. switch_to(menu_aux_time);
  857. }
  858. } else {
  859. if (db.spaceLeft()) {
  860. db.addDigit(n);
  861. menu_entered_digits += String(n);
  862. switch_to(state);
  863. } else {
  864. backspace();
  865. }
  866. }
  867. } else if (state == menu_aux_time) {
  868. if (n == -1) {
  869. if (db.hasDigits()) {
  870. backspace();
  871. db.removeDigit();
  872. if (menu_entered_digits.length() > 0) {
  873. menu_entered_digits.remove(menu_entered_digits.length() - 1);
  874. switch_to(state);
  875. }
  876. } else {
  877. switch_to(menu_aux);
  878. }
  879. } else if (n == -2) {
  880. if (!db.hasDigits()) {
  881. return;
  882. }
  883. selected_time = number_input();
  884. if ((selected_time <= 0) || (selected_time > MAX_AUX_RUNTIME)) {
  885. error_condition = F("Invalid time range!");
  886. switch_to(error);
  887. } else {
  888. switch_to(menu_aux_go);
  889. }
  890. } else {
  891. if (db.spaceLeft()) {
  892. db.addDigit(n);
  893. menu_entered_digits += String(n);
  894. switch_to(state);
  895. } else {
  896. backspace();
  897. }
  898. }
  899. } else if (state == menu_aux_go) {
  900. if (n == -2) {
  901. start_time = millis();
  902. last_animation_time = start_time;
  903. plants.startAux(selected_id - 1);
  904. switch_to(menu_aux_run);
  905. } else {
  906. switch_to(menu_aux_time);
  907. }
  908. } else if (state == menu_aux_run) {
  909. plants.abort();
  910. stop_time = millis();
  911. switch_to(menu_aux_done);
  912. } else if (state == menu_aux_done) {
  913. switch_to(menu_c);
  914. } else if (state == error) {
  915. if (old_state != error) {
  916. switch_to(old_state);
  917. } else {
  918. switch_to(menu_a);
  919. }
  920. }
  921. }
  922. uint32_t Statemachine::number_input(void) {
  923. for (int i = 0; i < db.countDigits(); i++) {
  924. backspace();
  925. }
  926. uint32_t n = db.getNumber();
  927. db.clear();
  928. debug.print("Whole number input: ");
  929. debug.println(n);
  930. return n;
  931. }
  932. void Statemachine::act(void) {
  933. if ((state == menu_pumps_run) || (state == menu_valves_run)
  934. || (state == menu_aux_run) || (state == fullauto_stirr_run)
  935. || (state == auto_stirr_run)) {
  936. unsigned long runtime = millis() - start_time;
  937. if ((runtime / 1000UL) >= selected_time) {
  938. // stop if timeout has been reached
  939. plants.abort();
  940. stop_time = millis();
  941. if (state == menu_pumps_run) {
  942. switch_to(menu_pumps_done);
  943. } else if (state == menu_valves_run) {
  944. switch_to(menu_valves_done);
  945. } else if (state == menu_aux_run) {
  946. switch_to(menu_aux_done);
  947. } else if (state == fullauto_stirr_run) {
  948. if (selected_ferts.countSet() > 0) {
  949. selected_time = auto_pump_runtime[0];
  950. for (int i = 0; i < plants.countFertilizers(); i++) {
  951. if (auto_pump_runtime[i] > selected_time) {
  952. selected_time = auto_pump_runtime[i];
  953. }
  954. if (selected_ferts.isSet(i)) {
  955. plants.startFertilizer(i);
  956. }
  957. }
  958. start_time = millis();
  959. switch_to(fullauto_fert_run);
  960. } else {
  961. auto wl = plants.getWaterlevel();
  962. if ((wl != Plants::full) && (wl != Plants::invalid)) {
  963. // if the waterlevel is currently empty, we
  964. // set a flag to record the time to fill
  965. filling_started_empty = (wl == Plants::empty);
  966. plants.openWaterInlet();
  967. selected_id = plants.countPlants() + 1;
  968. selected_time = MAX_TANK_FILL_TIME;
  969. start_time = millis();
  970. switch_to(fullauto_tank_run);
  971. } else if (wl == Plants::full) {
  972. // check if kickstart is required for this
  973. bool need_kickstart = false;
  974. for (int i = 0; i < plants.countPlants(); i++) {
  975. if (selected_plants.isSet(i)) {
  976. if (plants.getKickstart()->getPinNumber(i) >= 0) {
  977. need_kickstart = true;
  978. }
  979. }
  980. }
  981. // start kickstart/valve as needed
  982. for (int i = 0; i < plants.countPlants(); i++) {
  983. if (selected_plants.isSet(i)) {
  984. plants.startPlant(i, need_kickstart);
  985. }
  986. }
  987. watering_started_full = (wl == Plants::full);
  988. selected_time = MAX_AUTO_PLANT_RUNTIME;
  989. start_time = millis();
  990. if (need_kickstart) {
  991. switch_to(fullauto_kickstart_run);
  992. } else {
  993. switch_to(fullauto_plant_run);
  994. }
  995. } else if (wl == Plants::invalid) {
  996. plants.abort();
  997. error_condition = F("Invalid sensor state");
  998. state = fullauto_fert;
  999. switch_to(error);
  1000. }
  1001. }
  1002. } else {
  1003. switch_to(auto_done);
  1004. }
  1005. } else if ((millis() - last_animation_time) >= 500) {
  1006. // update animation if needed
  1007. last_animation_time = millis();
  1008. switch_to(state);
  1009. }
  1010. }
  1011. #ifdef CHECK_SENSORS_VALVE_PUMP_MENU_FULL
  1012. if ((state == menu_pumps_run) || ((state == menu_valves_run) && (selected_id == (plants.countPlants() + 1)))) {
  1013. // check water level state
  1014. auto wl = plants.getWaterlevel();
  1015. if (wl == Plants::full) {
  1016. plants.abort();
  1017. stop_time = millis();
  1018. switch_to((state == menu_pumps_run) ? menu_pumps_done : menu_valves_done);
  1019. } else if (wl == Plants::invalid) {
  1020. plants.abort();
  1021. error_condition = F("Invalid sensor state");
  1022. state = (state == menu_pumps_run) ? menu_pumps : menu_valves;
  1023. switch_to(error);
  1024. }
  1025. }
  1026. #endif // CHECK_SENSORS_VALVE_PUMP_MENU_FULL
  1027. #ifdef CHECK_SENSORS_VALVE_PUMP_MENU_EMPTY
  1028. if ((state == menu_valves_run) && (selected_id <= plants.countPlants())) {
  1029. // check water level state
  1030. auto wl = plants.getWaterlevel();
  1031. if (wl == Plants::empty) {
  1032. plants.abort();
  1033. stop_time = millis();
  1034. switch_to(menu_valves_done);
  1035. } else if (wl == Plants::invalid) {
  1036. plants.abort();
  1037. error_condition = F("Invalid sensor state");
  1038. state = menu_valves;
  1039. switch_to(error);
  1040. }
  1041. }
  1042. #endif // CHECK_SENSORS_VALVE_PUMP_MENU_EMPTY
  1043. // kickstart states
  1044. if ((state == auto_plant_kickstart_run) || (state == fillnwater_kickstart_run)
  1045. || (state == fullauto_kickstart_run) || (state == fullauto_kickstart_purge_run)) {
  1046. unsigned long runtime = millis() - start_time;
  1047. if ((runtime / 1000UL) >= KICKSTART_RUNTIME) {
  1048. // kickstart is done, switch over to valves
  1049. plants.abort();
  1050. start_time = millis();
  1051. // start required valves
  1052. for (int i = 0; i < plants.countPlants(); i++) {
  1053. if (selected_plants.isSet(i)) {
  1054. plants.startPlant(i, false);
  1055. }
  1056. }
  1057. if (state == auto_plant_kickstart_run) {
  1058. switch_to(auto_plant_run);
  1059. } else if (state == fillnwater_kickstart_run) {
  1060. switch_to(fillnwater_plant_run);
  1061. } else if (state == fullauto_kickstart_run) {
  1062. switch_to(fullauto_plant_run);
  1063. } else {
  1064. switch_to(fullauto_plant_purge_run);
  1065. }
  1066. } else if ((millis() - last_animation_time) >= 500) {
  1067. // update animation if needed
  1068. last_animation_time = millis();
  1069. switch_to(state);
  1070. }
  1071. }
  1072. if (state == fullauto_fert_run) {
  1073. unsigned long runtime = millis() - start_time;
  1074. for (int i = 0; i < plants.countFertilizers(); i++) {
  1075. if (selected_ferts.isSet(i)) {
  1076. if ((runtime / 1000UL) >= auto_pump_runtime[i]) {
  1077. plants.stopFertilizer(i);
  1078. selected_ferts.set(i, false);
  1079. }
  1080. }
  1081. }
  1082. if (selected_ferts.countSet() <= 0) {
  1083. plants.abort();
  1084. auto wl = plants.getWaterlevel();
  1085. if ((wl != Plants::full) && (wl != Plants::invalid)) {
  1086. // if the waterlevel is currently empty, we
  1087. // set a flag to record the time to fill
  1088. filling_started_empty = (wl == Plants::empty);
  1089. plants.openWaterInlet();
  1090. selected_id = plants.countPlants() + 1;
  1091. selected_time = MAX_TANK_FILL_TIME;
  1092. start_time = millis();
  1093. switch_to(fullauto_tank_run);
  1094. } else if (wl == Plants::full) {
  1095. // check if kickstart is required for this
  1096. bool need_kickstart = false;
  1097. for (int i = 0; i < plants.countPlants(); i++) {
  1098. if (selected_plants.isSet(i)) {
  1099. if (plants.getKickstart()->getPinNumber(i) >= 0) {
  1100. need_kickstart = true;
  1101. }
  1102. }
  1103. }
  1104. // start kickstart/valve as needed
  1105. for (int i = 0; i < plants.countPlants(); i++) {
  1106. if (selected_plants.isSet(i)) {
  1107. plants.startPlant(i, need_kickstart);
  1108. }
  1109. }
  1110. // for recording the flowrate
  1111. watering_started_full = (wl == Plants::full);
  1112. selected_time = MAX_AUTO_PLANT_RUNTIME;
  1113. start_time = millis();
  1114. if (need_kickstart) {
  1115. switch_to(fullauto_kickstart_run);
  1116. } else {
  1117. switch_to(fullauto_plant_run);
  1118. }
  1119. } else if (wl == Plants::invalid) {
  1120. error_condition = F("Invalid sensor state");
  1121. state = auto_mode_a;
  1122. switch_to(error);
  1123. }
  1124. }
  1125. }
  1126. // states that fill the tank up
  1127. if ((state == auto_fert_run) || (state == auto_tank_run)
  1128. || (state == fullauto_tank_run) || (state == fullauto_tank_purge_run)
  1129. || (state == fillnwater_tank_run)) {
  1130. unsigned long runtime = millis() - start_time;
  1131. if ((runtime / 1000UL) >= selected_time) {
  1132. // stop if timeout has been reached
  1133. plants.abort();
  1134. stop_time = millis();
  1135. if ((state == fillnwater_tank_run)
  1136. || (state == fullauto_tank_run)
  1137. || (state == fullauto_tank_purge_run)) {
  1138. auto wl = plants.getWaterlevel();
  1139. if ((wl != Plants::empty) && (wl != Plants::invalid)) {
  1140. // check if kickstart is required for this
  1141. bool need_kickstart = false;
  1142. for (int i = 0; i < plants.countPlants(); i++) {
  1143. if (selected_plants.isSet(i)) {
  1144. if (plants.getKickstart()->getPinNumber(i) >= 0) {
  1145. need_kickstart = true;
  1146. }
  1147. }
  1148. }
  1149. // start kickstart/valve as needed
  1150. for (int i = 0; i < plants.countPlants(); i++) {
  1151. if (selected_plants.isSet(i)) {
  1152. plants.startPlant(i, need_kickstart);
  1153. }
  1154. }
  1155. watering_started_full = (wl == Plants::full);
  1156. selected_time = MAX_AUTO_PLANT_RUNTIME;
  1157. start_time = millis();
  1158. if (need_kickstart) {
  1159. if (state == fillnwater_tank_run) {
  1160. switch_to(fillnwater_kickstart_run);
  1161. } else if (state == fullauto_tank_run) {
  1162. switch_to(fullauto_kickstart_run);
  1163. } else {
  1164. switch_to(fullauto_kickstart_purge_run);
  1165. }
  1166. } else {
  1167. if (state == fillnwater_tank_run) {
  1168. switch_to(fillnwater_plant_run);
  1169. } else if (state == fullauto_tank_run) {
  1170. switch_to(fullauto_plant_run);
  1171. } else {
  1172. switch_to(fullauto_plant_purge_run);
  1173. }
  1174. }
  1175. } else if (wl == Plants::empty) {
  1176. stop_time = millis();
  1177. switch_to(auto_done);
  1178. } else if (wl == Plants::invalid) {
  1179. error_condition = F("Invalid sensor state");
  1180. state = auto_mode_a;
  1181. switch_to(error);
  1182. }
  1183. } else {
  1184. switch_to(auto_done);
  1185. }
  1186. } else if ((millis() - last_animation_time) >= 500) {
  1187. // update animation if needed
  1188. last_animation_time = millis();
  1189. switch_to(state);
  1190. }
  1191. // check water level state
  1192. auto wl = plants.getWaterlevel();
  1193. if (wl == Plants::full) {
  1194. plants.abort();
  1195. stop_time = millis();
  1196. if ((state == fillnwater_tank_run)
  1197. || (state == fullauto_tank_run)
  1198. || (state == fullauto_tank_purge_run)) {
  1199. // record time to fill here, if we started with
  1200. // an empty tank at the start of filling
  1201. if (filling_started_empty) {
  1202. unsigned long time_to_fill = stop_time - start_time;
  1203. debug.print("Filling tank took ");
  1204. debug.print(String(time_to_fill));
  1205. debug.println("ms");
  1206. #if defined(PLATFORM_ESP)
  1207. bool success = wifi_write_database(time_to_fill, "calibrated_filling", -1);
  1208. if (!success) {
  1209. debug.print("Error writing to InfluxDB ");
  1210. debug.print(INFLUXDB_HOST);
  1211. debug.print(":");
  1212. debug.print(INFLUXDB_PORT);
  1213. debug.print("/");
  1214. debug.print(INFLUXDB_DATABASE);
  1215. debug.println("/calibrated_filling");
  1216. }
  1217. #endif // PLATFORM_ESP
  1218. }
  1219. // check if kickstart is required for this
  1220. bool need_kickstart = false;
  1221. for (int i = 0; i < plants.countPlants(); i++) {
  1222. if (selected_plants.isSet(i)) {
  1223. if (plants.getKickstart()->getPinNumber(i) >= 0) {
  1224. need_kickstart = true;
  1225. }
  1226. }
  1227. }
  1228. // start kickstart/valve as needed
  1229. for (int i = 0; i < plants.countPlants(); i++) {
  1230. if (selected_plants.isSet(i)) {
  1231. plants.startPlant(i, need_kickstart);
  1232. }
  1233. }
  1234. watering_started_full = (wl == Plants::full);
  1235. selected_time = MAX_AUTO_PLANT_RUNTIME;
  1236. start_time = millis();
  1237. if (need_kickstart) {
  1238. if (state == fillnwater_tank_run) {
  1239. switch_to(fillnwater_kickstart_run);
  1240. } else if (state == fullauto_tank_run) {
  1241. switch_to(fullauto_kickstart_run);
  1242. } else {
  1243. switch_to(fullauto_kickstart_purge_run);
  1244. }
  1245. } else {
  1246. if (state == fillnwater_tank_run) {
  1247. switch_to(fillnwater_plant_run);
  1248. } else if (state == fullauto_tank_run) {
  1249. switch_to(fullauto_plant_run);
  1250. } else {
  1251. switch_to(fullauto_plant_purge_run);
  1252. }
  1253. }
  1254. } else {
  1255. switch_to(auto_done);
  1256. }
  1257. } else if (wl == Plants::invalid) {
  1258. plants.abort();
  1259. error_condition = F("Invalid sensor state");
  1260. state = auto_mode_a;
  1261. switch_to(error);
  1262. }
  1263. }
  1264. // states that empty the tank
  1265. if ((state == auto_plant_run) || (state == fillnwater_plant_run)
  1266. || (state == fullauto_plant_run) || (state == fullauto_plant_overrun)
  1267. || (state == fullauto_plant_purge_run)
  1268. || (state == fullauto_plant_purge_overrun)) {
  1269. unsigned long runtime = millis() - start_time;
  1270. if ((runtime / 1000UL) >= selected_time) {
  1271. if (state == fullauto_plant_run) {
  1272. start_time = millis();
  1273. selected_time = OVERRUN_RUNTIME;
  1274. switch_to(fullauto_plant_overrun);
  1275. } else if (state == fullauto_plant_overrun) {
  1276. plants.abort();
  1277. stop_time = millis();
  1278. auto wl = plants.getWaterlevel();
  1279. if ((wl != Plants::full) && (wl != Plants::invalid)) {
  1280. // if the waterlevel is currently empty, we
  1281. // set a flag to record the time to fill
  1282. filling_started_empty = (wl == Plants::empty);
  1283. plants.openWaterInlet();
  1284. selected_id = plants.countPlants() + 1;
  1285. selected_time = PURGE_FILL_RUNTIME;
  1286. start_time = millis();
  1287. switch_to(fullauto_tank_purge_run);
  1288. } else if (wl == Plants::full) {
  1289. // check if kickstart is required for this
  1290. bool need_kickstart = false;
  1291. for (int i = 0; i < plants.countPlants(); i++) {
  1292. if (selected_plants.isSet(i)) {
  1293. if (plants.getKickstart()->getPinNumber(i) >= 0) {
  1294. need_kickstart = true;
  1295. }
  1296. }
  1297. }
  1298. // start kickstart/valve as needed
  1299. for (int i = 0; i < plants.countPlants(); i++) {
  1300. if (selected_plants.isSet(i)) {
  1301. plants.startPlant(i, need_kickstart);
  1302. }
  1303. }
  1304. // for recording the flowrate
  1305. watering_started_full = (wl == Plants::full);
  1306. selected_time = MAX_AUTO_PLANT_RUNTIME;
  1307. start_time = millis();
  1308. if (need_kickstart) {
  1309. switch_to(fullauto_kickstart_purge_run);
  1310. } else {
  1311. switch_to(fullauto_plant_purge_run);
  1312. }
  1313. } else if (wl == Plants::invalid) {
  1314. error_condition = F("Invalid sensor state");
  1315. state = auto_mode_a;
  1316. switch_to(error);
  1317. }
  1318. } else if (state == fullauto_plant_purge_run) {
  1319. start_time = millis();
  1320. selected_time = OVERRUN_RUNTIME;
  1321. switch_to(fullauto_plant_purge_overrun);
  1322. } else if (state == fullauto_plant_purge_overrun) {
  1323. plants.abort();
  1324. stop_time = millis();
  1325. switch_to(fullauto_done);
  1326. } else {
  1327. plants.abort();
  1328. stop_time = millis();
  1329. switch_to(auto_done);
  1330. }
  1331. } else if ((millis() - last_animation_time) >= 500) {
  1332. // update animation if needed
  1333. last_animation_time = millis();
  1334. switch_to(state);
  1335. }
  1336. if ((state == fullauto_plant_overrun) || (state == fullauto_plant_purge_overrun)) {
  1337. // overrun should not exit when sensor returns empty
  1338. return;
  1339. }
  1340. // check water level state
  1341. auto wl = plants.getWaterlevel();
  1342. if (wl == Plants::empty) {
  1343. plants.abort();
  1344. stop_time = millis();
  1345. // if we started watering with a full tank
  1346. // and then finished watering when it was empty
  1347. // and we were only watering a single plant
  1348. // look at this as a "calibration run" and record
  1349. // the time it took to empty the tank
  1350. if ((state == fillnwater_plant_run) && watering_started_full && (selected_plants.countSet() == 1)) {
  1351. unsigned long time_to_water = stop_time - start_time;
  1352. debug.print("Watering plant ");
  1353. debug.print(selected_plants.getFirstSet() + 1);
  1354. debug.print(" with the complete tank took ");
  1355. debug.print(String(time_to_water));
  1356. debug.println("ms");
  1357. #if defined(PLATFORM_ESP)
  1358. bool success = wifi_write_database(time_to_water, "calibrated_watering", selected_plants.getFirstSet() + 1);
  1359. if (!success) {
  1360. debug.print("Error writing to InfluxDB ");
  1361. debug.print(INFLUXDB_HOST);
  1362. debug.print(":");
  1363. debug.print(INFLUXDB_PORT);
  1364. debug.print("/");
  1365. debug.print(INFLUXDB_DATABASE);
  1366. debug.println("/calibrated_watering");
  1367. }
  1368. #endif // PLATFORM_ESP
  1369. }
  1370. switch_to(auto_done);
  1371. } else if (wl == Plants::invalid) {
  1372. plants.abort();
  1373. error_condition = F("Invalid sensor state");
  1374. state = auto_mode_a;
  1375. switch_to(error);
  1376. }
  1377. }
  1378. // timeout in user-input states
  1379. if ((state == menu_a) || (state == menu_b) || (state == menu_c)
  1380. || (state == automation_mode) || (state == auto_done)
  1381. || (state == auto_mode_a) || (state == auto_mode_b)
  1382. || (state == auto_fert_a) || (state == auto_fert_b)
  1383. || (state == auto_plant) || (state == fillnwater_plant)
  1384. || (state == menu_pumps) || (state == menu_pumps_time)
  1385. || (state == menu_pumps_go) || (state == menu_pumps_done)
  1386. || (state == menu_valves) || (state == menu_valves_time)
  1387. || (state == menu_valves_go) || (state == menu_valves_done)
  1388. || (state == menu_aux) || (state == menu_aux_time)
  1389. || (state == menu_aux_go) || (state == menu_aux_done)
  1390. || (state == fullauto_fert) || (state == fullauto_plant)
  1391. || (state == fullauto_done)) {
  1392. unsigned long runtime = millis() - into_state_time;
  1393. if (runtime >= BACK_TO_IDLE_TIMEOUT) {
  1394. debug.print("Idle timeout reached in state ");
  1395. debug.println(state_names[state]);
  1396. switch_to(init);
  1397. }
  1398. }
  1399. }
  1400. void Statemachine::switch_to(States s) {
  1401. old_state = state;
  1402. state = s;
  1403. into_state_time = millis();
  1404. if (old_state != state) {
  1405. // don't spam log with every animation state "change"
  1406. debug.print("switch_to ");
  1407. debug.print(state_names[old_state]);
  1408. debug.print(" --> ");
  1409. debug.println(state_names[state]);
  1410. menu_entered_digits = "";
  1411. }
  1412. if (s == init) {
  1413. String a = String(F("- Giess-o-mat V")) + FIRMWARE_VERSION + String(F(" -"));
  1414. #if (LOCK_COUNT > 0) && defined(DOOR_LOCK_PIN)
  1415. String b = String(F("PIN: ")) + menu_entered_digits;
  1416. print(a.c_str(),
  1417. "* or # to enter menu",
  1418. "Enter PIN for locks:",
  1419. b.c_str(),
  1420. 3);
  1421. #else
  1422. print(a.c_str(),
  1423. "Usage: Enter number",
  1424. "* Delete prev. digit",
  1425. "# Execute input num.",
  1426. -1);
  1427. #endif
  1428. } else if (s == door_select) {
  1429. String a = String(F("(Input 1 to ")) + String(LOCK_COUNT) + String(F(")"));
  1430. String b = String(F("Door: ")) + menu_entered_digits;
  1431. print("- Select Door Lock -",
  1432. "Leave empty if done!",
  1433. a.c_str(),
  1434. b.c_str(),
  1435. 3);
  1436. } else if (s == menu_a) {
  1437. print("----- Menu 1/3 -----",
  1438. "1: Manual Operation",
  1439. "2: Automation",
  1440. "#: Go to page 2/3...",
  1441. -1);
  1442. } else if (s == menu_b) {
  1443. print("----- Menu 2/3 -----",
  1444. "3: Fertilizer pumps",
  1445. "4: Outlet valves",
  1446. "#: Go to page 3/3...",
  1447. -1);
  1448. } else if (s == menu_c) {
  1449. print("----- Menu 3/3 -----",
  1450. "5: Aux. Outputs",
  1451. #if (LOCK_COUNT > 0) && !defined(DOOR_LOCK_PIN)
  1452. "6: Door Locks",
  1453. #else
  1454. "",
  1455. #endif
  1456. "#: Go to page 1/3...",
  1457. -1);
  1458. } else if (state == automation_mode) {
  1459. // TODO
  1460. print("---- Automation ----",
  1461. "TODO NOT IMPLEMENTED",
  1462. "TODO NOT IMPLEMENTED",
  1463. "TODO NOT IMPLEMENTED",
  1464. -1);
  1465. } else if (s == auto_mode_a) {
  1466. print("---- Manual 1/3 ----",
  1467. "1: Full-Auto Mode",
  1468. "2: Add Fertilizer",
  1469. "#: Go to page 2/3...",
  1470. -1);
  1471. } else if (s == auto_mode_b) {
  1472. print("---- Manual 2/3 ----",
  1473. "3: Fill 'n' Water",
  1474. "4: Fill Reservoir",
  1475. "#: Go to page 3/3...",
  1476. -1);
  1477. } else if (s == auto_mode_c) {
  1478. print("---- Manual 3/3 ----",
  1479. "5: Water a plant",
  1480. "",
  1481. "#: Go to page 1/3...",
  1482. -1);
  1483. } else if (s == auto_fert_a) {
  1484. print("-- Fertilizer 1/2 --",
  1485. "1: Vegetation Phase",
  1486. "2: Bloom Phase",
  1487. "#: Go to page 2/2...",
  1488. -1);
  1489. } else if (s == auto_fert_b) {
  1490. print("-- Fertilizer 2/2 --",
  1491. "3: Special Fert.",
  1492. "4: Run Stirrer",
  1493. "#: Go to page 1/2...",
  1494. -1);
  1495. } else if ((s == auto_fert_run) || (s == fullauto_fert_run)) {
  1496. unsigned long runtime = millis() - start_time;
  1497. String a = String(F("Time: ")) + String(runtime / 1000UL) + String(F("s / ")) + String(selected_time) + String('s');
  1498. unsigned long anim = runtime * 20UL / (selected_time * 1000UL);
  1499. String b;
  1500. for (unsigned long i = 0; i < anim; i++) {
  1501. b += '#';
  1502. }
  1503. print("---- Dispensing ----",
  1504. a.c_str(),
  1505. b.c_str(),
  1506. "Hit any key to stop!",
  1507. -1);
  1508. } else if ((s == auto_stirr_run) || (s == fullauto_stirr_run)) {
  1509. unsigned long runtime = millis() - start_time;
  1510. String a = String(F("Time: ")) + String(runtime / 1000UL) + String(F("s / ")) + String(selected_time) + String('s');
  1511. unsigned long anim = runtime * 20UL / (selected_time * 1000UL);
  1512. String b;
  1513. for (unsigned long i = 0; i < anim; i++) {
  1514. b += '#';
  1515. }
  1516. print("----- Stirring -----",
  1517. a.c_str(),
  1518. b.c_str(),
  1519. "Hit any key to stop!",
  1520. -1);
  1521. } else if ((s == auto_tank_run) || (s == fillnwater_tank_run) || (s == fullauto_tank_run) || (s == fullauto_tank_purge_run)) {
  1522. unsigned long runtime = millis() - start_time;
  1523. String a = String(F("Time: ")) + String(runtime / 1000UL) + String(F("s / ")) + String(selected_time) + String('s');
  1524. unsigned long anim = runtime * 20UL / (selected_time * 1000UL);
  1525. String b;
  1526. for (unsigned long i = 0; i < anim; i++) {
  1527. b += '#';
  1528. }
  1529. print("--- Filling Tank ---",
  1530. a.c_str(),
  1531. b.c_str(),
  1532. "Hit any key to stop!",
  1533. -1);
  1534. } else if ((s == auto_plant) || (s == fillnwater_plant) || (s == fullauto_plant)) {
  1535. String a = String(F("(Input 1 to ")) + String(plants.countPlants()) + String(F(")"));
  1536. String b = String(F("Plant: ")) + menu_entered_digits;
  1537. print("--- Select Plant ---",
  1538. "Leave empty if done!",
  1539. a.c_str(),
  1540. b.c_str(),
  1541. 3);
  1542. } else if (s == fullauto_fert) {
  1543. String a = String(F("(Input 1 to ")) + String(plants.countFertilizers()) + String(F(")"));
  1544. String b = String(F("Fert.: ")) + menu_entered_digits;
  1545. print("--- Select Fert. ---",
  1546. "Leave empty if done!",
  1547. a.c_str(),
  1548. b.c_str(),
  1549. 3);
  1550. } else if ((s == auto_plant_kickstart_run) || (s == fillnwater_kickstart_run) || (s == fullauto_kickstart_run) || (s == fullauto_kickstart_purge_run)) {
  1551. unsigned long runtime = millis() - start_time;
  1552. String a = String(F("Time: ")) + String(runtime / 1000UL) + String(F("s / ")) + String(KICKSTART_RUNTIME) + String('s');
  1553. unsigned long anim = runtime * 20UL / (KICKSTART_RUNTIME * 1000UL);
  1554. String b;
  1555. for (unsigned long i = 0; i < anim; i++) {
  1556. b += '#';
  1557. }
  1558. print("---- Kick-Start ----",
  1559. a.c_str(),
  1560. b.c_str(),
  1561. "Hit any key to stop!",
  1562. -1);
  1563. } else if ((s == auto_plant_run) || (s == fillnwater_plant_run) || (s == fullauto_plant_run) || (s == fullauto_plant_purge_run)) {
  1564. unsigned long runtime = millis() - start_time;
  1565. String a = String(F("Time: ")) + String(runtime / 1000UL) + String(F("s / ")) + String(selected_time) + String('s');
  1566. unsigned long anim = runtime * 20UL / (selected_time * 1000UL);
  1567. String b;
  1568. for (unsigned long i = 0; i < anim; i++) {
  1569. b += '#';
  1570. }
  1571. print("----- Watering -----",
  1572. a.c_str(),
  1573. b.c_str(),
  1574. "Hit any key to stop!",
  1575. -1);
  1576. } else if ((s == fullauto_plant_overrun) || (s == fullauto_plant_purge_overrun)) {
  1577. unsigned long runtime = millis() - start_time;
  1578. String a = String(F("Time: ")) + String(runtime / 1000UL) + String(F("s / ")) + String(selected_time) + String('s');
  1579. unsigned long anim = runtime * 20UL / (selected_time * 1000UL);
  1580. String b;
  1581. for (unsigned long i = 0; i < anim; i++) {
  1582. b += '#';
  1583. }
  1584. print("-- Emptying lines --",
  1585. a.c_str(),
  1586. b.c_str(),
  1587. "Hit any key to stop!",
  1588. -1);
  1589. } else if ((s == auto_done) || (s == fullauto_done)) {
  1590. String a = String(F("after ")) + String((stop_time - start_time) / 1000UL) + String(F("s."));
  1591. print("------- Done -------",
  1592. "Dispensing finished",
  1593. a.c_str(),
  1594. "Hit any key for menu",
  1595. -1);
  1596. #if defined(PLATFORM_ESP)
  1597. unsigned long runtime = stop_time - start_time;
  1598. if ((old_state == auto_plant_run) || (old_state == fillnwater_plant_run)) {
  1599. for (int i = 0; i < plants.countPlants(); i++) {
  1600. if (selected_plants.isSet(i)) {
  1601. bool success = wifi_write_database(runtime / 1000, "plant", i + 1);
  1602. if (!success) {
  1603. debug.print("Error writing to InfluxDB ");
  1604. debug.print(INFLUXDB_HOST);
  1605. debug.print(":");
  1606. debug.print(INFLUXDB_PORT);
  1607. debug.print("/");
  1608. debug.print(INFLUXDB_DATABASE);
  1609. debug.println("/plant");
  1610. }
  1611. }
  1612. }
  1613. } else if (old_state == auto_fert_run) {
  1614. bool success = wifi_write_database(runtime / 1000, "fertilizer", selected_id);
  1615. if (!success) {
  1616. debug.print("Error writing to InfluxDB ");
  1617. debug.print(INFLUXDB_HOST);
  1618. debug.print(":");
  1619. debug.print(INFLUXDB_PORT);
  1620. debug.print("/");
  1621. debug.print(INFLUXDB_DATABASE);
  1622. debug.println("/fertilizer");
  1623. }
  1624. }
  1625. #endif // PLATFORM_ESP
  1626. } else if (s == menu_pumps) {
  1627. String a = String(F("(Input 1 to ")) + String(plants.countFertilizers()) + String(F(")"));
  1628. String b = String(F("Pump: ")) + menu_entered_digits;
  1629. print("------- Pump -------",
  1630. "Please select pump",
  1631. a.c_str(),
  1632. b.c_str(),
  1633. 3);
  1634. } else if (s == menu_pumps_time) {
  1635. String header = String(F("------ Pump ")) + String(selected_id) + String(F(" ------"));
  1636. String b = String(F("Runtime: ")) + menu_entered_digits;
  1637. print(header.c_str(),
  1638. "Please set runtime",
  1639. "(Input in seconds)",
  1640. b.c_str(),
  1641. 3);
  1642. } else if (s == menu_pumps_go) {
  1643. String a = String(F("Pump No. ")) + String(selected_id);
  1644. String b = String(F("Runtime ")) + String(selected_time) + String('s');
  1645. print("----- Confirm? -----",
  1646. a.c_str(),
  1647. b.c_str(),
  1648. " # Confirm",
  1649. -1);
  1650. } else if (s == menu_pumps_run) {
  1651. unsigned long runtime = millis() - start_time;
  1652. String a = String(F("Time: ")) + String(runtime / 1000UL) + String(F("s / ")) + String(selected_time) + String('s');
  1653. unsigned long anim = runtime * 20UL / (selected_time * 1000UL);
  1654. String b;
  1655. for (unsigned long i = 0; i < anim; i++) {
  1656. b += '#';
  1657. }
  1658. print("---- Dispensing ----",
  1659. a.c_str(),
  1660. b.c_str(),
  1661. "Hit any key to stop!",
  1662. -1);
  1663. } else if (s == menu_pumps_done) {
  1664. String a = String(F("after ")) + String((stop_time - start_time) / 1000UL) + String(F("s."));
  1665. print("------- Done -------",
  1666. "Dispensing finished",
  1667. a.c_str(),
  1668. "Hit any key for menu",
  1669. -1);
  1670. #if defined(PLATFORM_ESP)
  1671. unsigned long runtime = stop_time - start_time;
  1672. bool success = wifi_write_database(runtime / 1000, "fertilizer", selected_id);
  1673. if (!success) {
  1674. debug.print("Error writing to InfluxDB ");
  1675. debug.print(INFLUXDB_HOST);
  1676. debug.print(":");
  1677. debug.print(INFLUXDB_PORT);
  1678. debug.print("/");
  1679. debug.print(INFLUXDB_DATABASE);
  1680. debug.println("/fertilizer");
  1681. }
  1682. #endif // PLATFORM_ESP
  1683. } else if (s == menu_valves) {
  1684. String a = String(F("(Input 1 to ")) + String(plants.countPlants() + 1) + String(F(")"));
  1685. String b = String(F("Valve: ")) + menu_entered_digits;
  1686. print("------ Valves ------",
  1687. "Please select valve",
  1688. a.c_str(),
  1689. b.c_str(),
  1690. 3);
  1691. } else if (s == menu_valves_time) {
  1692. String header = String(F("----- Valve ")) + String(selected_id) + String(F(" -----"));
  1693. String b = String(F("Runtime: ")) + menu_entered_digits;
  1694. print(header.c_str(),
  1695. "Please set runtime",
  1696. "(Input in seconds)",
  1697. b.c_str(),
  1698. 3);
  1699. } else if (s == menu_valves_go) {
  1700. String a = String(F("Valve No. ")) + String(selected_id);
  1701. String b = String(F("Runtime ")) + String(selected_time) + String('s');
  1702. print("----- Confirm? -----",
  1703. a.c_str(),
  1704. b.c_str(),
  1705. " # Confirm",
  1706. -1);
  1707. } else if (s == menu_valves_run) {
  1708. unsigned long runtime = millis() - start_time;
  1709. String a = String(F("Time: ")) + String(runtime / 1000UL) + String(F("s / ")) + String(selected_time) + String('s');
  1710. unsigned long anim = runtime * 20UL / (selected_time * 1000UL);
  1711. String b;
  1712. for (unsigned long i = 0; i <= anim; i++) {
  1713. b += '#';
  1714. }
  1715. print("---- Dispensing ----",
  1716. a.c_str(),
  1717. b.c_str(),
  1718. "Hit any key to stop!",
  1719. -1);
  1720. } else if (s == menu_valves_done) {
  1721. String a = String(F("after ")) + String((stop_time - start_time) / 1000UL) + String(F("s."));
  1722. print("------- Done -------",
  1723. "Dispensing finished",
  1724. a.c_str(),
  1725. "Hit any key for menu",
  1726. -1);
  1727. #if defined(PLATFORM_ESP)
  1728. unsigned long runtime = stop_time - start_time;
  1729. if (selected_id <= plants.countPlants()) {
  1730. bool success = wifi_write_database(runtime / 1000, "plant", selected_id);
  1731. if (!success) {
  1732. debug.print("Error writing to InfluxDB ");
  1733. debug.print(INFLUXDB_HOST);
  1734. debug.print(":");
  1735. debug.print(INFLUXDB_PORT);
  1736. debug.print("/");
  1737. debug.print(INFLUXDB_DATABASE);
  1738. debug.println("/plant");
  1739. }
  1740. }
  1741. #endif // PLATFORM_ESP
  1742. } else if (s == menu_aux) {
  1743. String a = String(F("(Input 1 to ")) + String(plants.countAux()) + String(F(")"));
  1744. String b = String(F("Aux.: ")) + menu_entered_digits;
  1745. print("------- Aux. -------",
  1746. "Please select aux.",
  1747. a.c_str(),
  1748. b.c_str(),
  1749. 3);
  1750. } else if (s == menu_aux_time) {
  1751. String header = String(F("------ Aux ")) + String(selected_id) + String(F(" ------"));
  1752. String b = String(F("Runtime: ")) + menu_entered_digits;
  1753. print(header.c_str(),
  1754. "Please set runtime",
  1755. "(Input in seconds)",
  1756. b.c_str(),
  1757. 3);
  1758. } else if (s == menu_aux_go) {
  1759. String a = String(F("Aux No. ")) + String(selected_id);
  1760. String b = String(F("Runtime ")) + String(selected_time) + String('s');
  1761. print("----- Confirm? -----",
  1762. a.c_str(),
  1763. b.c_str(),
  1764. " # Confirm",
  1765. -1);
  1766. } else if (s == menu_aux_run) {
  1767. unsigned long runtime = millis() - start_time;
  1768. String a = String(F("Time: ")) + String(runtime / 1000UL) + String(F("s / ")) + String(selected_time) + String('s');
  1769. unsigned long anim = runtime * 20UL / (selected_time * 1000UL);
  1770. String b;
  1771. for (unsigned long i = 0; i <= anim; i++) {
  1772. b += '#';
  1773. }
  1774. print("----- Stirring -----",
  1775. a.c_str(),
  1776. b.c_str(),
  1777. "Hit any key to stop!",
  1778. -1);
  1779. } else if (s == menu_aux_done) {
  1780. String a = String(F("after ")) + String((stop_time - start_time) / 1000UL) + String(F("s."));
  1781. print("------- Done -------",
  1782. "Aux. run finished",
  1783. a.c_str(),
  1784. "Hit any key for menu",
  1785. -1);
  1786. } else if (s == error) {
  1787. print("------ Error! ------",
  1788. "There is a problem:",
  1789. error_condition.c_str(),
  1790. " Press any key...",
  1791. -1);
  1792. } else {
  1793. debug.print("Invalid state ");
  1794. debug.println(s);
  1795. }
  1796. }
  1797. #endif