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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091
  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 == fullauto_stirr_run) {
  324. // allow user to stop stirring and continue with fertilizers
  325. plants.abort();
  326. stop_time = millis();
  327. if (selected_ferts.countSet() > 0) {
  328. selected_time = auto_pump_runtime[0];
  329. for (int i = 0; i < plants.countFertilizers(); i++) {
  330. if (auto_pump_runtime[i] > selected_time) {
  331. selected_time = auto_pump_runtime[i];
  332. }
  333. if (selected_ferts.isSet(i)) {
  334. plants.startFertilizer(i);
  335. }
  336. }
  337. start_time = millis();
  338. switch_to(fullauto_fert_run);
  339. } else {
  340. auto wl = plants.getWaterlevel();
  341. if ((wl != Plants::full) && (wl != Plants::invalid)) {
  342. // if the waterlevel is currently empty, we
  343. // set a flag to record the time to fill
  344. filling_started_empty = (wl == Plants::empty);
  345. plants.openWaterInlet();
  346. selected_id = plants.countPlants() + 1;
  347. selected_time = MAX_TANK_FILL_TIME;
  348. start_time = millis();
  349. switch_to(fullauto_tank_run);
  350. } else if (wl == Plants::full) {
  351. // check if kickstart is required for this
  352. bool need_kickstart = false;
  353. for (int i = 0; i < plants.countPlants(); i++) {
  354. if (selected_plants.isSet(i)) {
  355. if (plants.getKickstart()->getPinNumber(i) >= 0) {
  356. need_kickstart = true;
  357. }
  358. }
  359. }
  360. // start kickstart/valve as needed
  361. for (int i = 0; i < plants.countPlants(); i++) {
  362. if (selected_plants.isSet(i)) {
  363. plants.startPlant(i, need_kickstart);
  364. }
  365. }
  366. watering_started_full = (wl == Plants::full);
  367. selected_time = MAX_AUTO_PLANT_RUNTIME;
  368. start_time = millis();
  369. if (need_kickstart) {
  370. switch_to(fullauto_kickstart_run);
  371. } else {
  372. switch_to(fullauto_plant_run);
  373. }
  374. } else if (wl == Plants::invalid) {
  375. plants.abort();
  376. error_condition = F("Invalid sensor state");
  377. state = fullauto_fert;
  378. switch_to(error);
  379. }
  380. }
  381. } else if (state == fullauto_fert_run) {
  382. // allow user to stop fertizilers and continue with tank filling
  383. plants.abort();
  384. stop_time = millis();
  385. auto wl = plants.getWaterlevel();
  386. if ((wl != Plants::full) && (wl != Plants::invalid)) {
  387. // if the waterlevel is currently empty, we
  388. // set a flag to record the time to fill
  389. filling_started_empty = (wl == Plants::empty);
  390. plants.openWaterInlet();
  391. selected_id = plants.countPlants() + 1;
  392. selected_time = MAX_TANK_FILL_TIME;
  393. start_time = millis();
  394. switch_to(fullauto_tank_run);
  395. } else if (wl == Plants::full) {
  396. // check if kickstart is required for this
  397. bool need_kickstart = false;
  398. for (int i = 0; i < plants.countPlants(); i++) {
  399. if (selected_plants.isSet(i)) {
  400. if (plants.getKickstart()->getPinNumber(i) >= 0) {
  401. need_kickstart = true;
  402. }
  403. }
  404. }
  405. // start kickstart/valve as needed
  406. for (int i = 0; i < plants.countPlants(); i++) {
  407. if (selected_plants.isSet(i)) {
  408. plants.startPlant(i, need_kickstart);
  409. }
  410. }
  411. watering_started_full = (wl == Plants::full);
  412. selected_time = MAX_AUTO_PLANT_RUNTIME;
  413. start_time = millis();
  414. if (need_kickstart) {
  415. switch_to(fullauto_kickstart_run);
  416. } else {
  417. switch_to(fullauto_plant_run);
  418. }
  419. } else if (wl == Plants::invalid) {
  420. plants.abort();
  421. error_condition = F("Invalid sensor state");
  422. state = fullauto_fert;
  423. switch_to(error);
  424. }
  425. } else if ((state == fullauto_tank_run)
  426. || (state == fullauto_tank_purge_run)) {
  427. // allow user to stop tank and continue with kickstart
  428. plants.abort();
  429. stop_time = millis();
  430. auto wl = plants.getWaterlevel();
  431. if ((wl != Plants::empty) && (wl != Plants::invalid)) {
  432. // check if kickstart is required for this
  433. bool need_kickstart = false;
  434. for (int i = 0; i < plants.countPlants(); i++) {
  435. if (selected_plants.isSet(i)) {
  436. if (plants.getKickstart()->getPinNumber(i) >= 0) {
  437. need_kickstart = true;
  438. }
  439. }
  440. }
  441. // start kickstart/valve as needed
  442. for (int i = 0; i < plants.countPlants(); i++) {
  443. if (selected_plants.isSet(i)) {
  444. plants.startPlant(i, need_kickstart);
  445. }
  446. }
  447. watering_started_full = (wl == Plants::full);
  448. selected_time = MAX_AUTO_PLANT_RUNTIME;
  449. start_time = millis();
  450. if (need_kickstart) {
  451. if (state == fullauto_tank_run) {
  452. switch_to(fullauto_kickstart_run);
  453. } else {
  454. switch_to(fullauto_kickstart_purge_run);
  455. }
  456. } else {
  457. if (state == fullauto_tank_run) {
  458. switch_to(fullauto_plant_run);
  459. } else {
  460. switch_to(fullauto_plant_purge_run);
  461. }
  462. }
  463. } else if (wl == Plants::empty) {
  464. stop_time = millis();
  465. switch_to(auto_done);
  466. } else if (wl == Plants::invalid) {
  467. error_condition = F("Invalid sensor state");
  468. state = auto_mode_a;
  469. switch_to(error);
  470. }
  471. } else if ((state == fullauto_kickstart_run)
  472. || (state == fullauto_kickstart_purge_run)) {
  473. // allow user to stop kickstarting and continue with plants
  474. plants.abort();
  475. selected_time = MAX_AUTO_PLANT_RUNTIME;
  476. start_time = millis();
  477. // start required valves
  478. for (int i = 0; i < plants.countPlants(); i++) {
  479. if (selected_plants.isSet(i)) {
  480. plants.startPlant(i, false);
  481. }
  482. }
  483. if (state == fullauto_kickstart_run) {
  484. switch_to(fullauto_plant_run);
  485. } else {
  486. switch_to(fullauto_plant_purge_run);
  487. }
  488. } else if ((state == auto_fert_run) || (state == auto_tank_run)
  489. || (state == auto_stirr_run) || (state == auto_plant_run)
  490. || (state == auto_plant_kickstart_run)
  491. || (state == fillnwater_kickstart_run)
  492. || (state == fullauto_plant_run)
  493. || (state == fullauto_plant_overrun)
  494. || (state == fullauto_plant_purge_run)
  495. || (state == fullauto_plant_purge_overrun)) {
  496. plants.abort();
  497. stop_time = millis();
  498. switch_to(auto_done);
  499. } else if (state == auto_plant) {
  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(auto_mode_b);
  510. }
  511. } else if (n == -2) {
  512. if (!db.hasDigits()) {
  513. auto wl = plants.getWaterlevel();
  514. if ((wl != Plants::empty) && (wl != Plants::invalid)) {
  515. // check if kickstart is required for this
  516. bool need_kickstart = false;
  517. for (int i = 0; i < plants.countPlants(); i++) {
  518. if (selected_plants.isSet(i)) {
  519. if (plants.getKickstart()->getPinNumber(i) >= 0) {
  520. need_kickstart = true;
  521. }
  522. }
  523. }
  524. // start kickstart/valve as needed
  525. for (int i = 0; i < plants.countPlants(); i++) {
  526. if (selected_plants.isSet(i)) {
  527. plants.startPlant(i, need_kickstart);
  528. }
  529. }
  530. selected_time = MAX_AUTO_PLANT_RUNTIME;
  531. start_time = millis();
  532. if (need_kickstart) {
  533. switch_to(auto_plant_kickstart_run);
  534. } else {
  535. switch_to(auto_plant_run);
  536. }
  537. } else if (wl == Plants::empty) {
  538. stop_time = millis();
  539. switch_to(auto_mode_b);
  540. } else if (wl == Plants::invalid) {
  541. error_condition = F("Invalid sensor state");
  542. state = auto_mode_b;
  543. switch_to(error);
  544. }
  545. } else {
  546. selected_id = number_input();
  547. if ((selected_id <= 0) || (selected_id > plants.countPlants())) {
  548. error_condition = F("Invalid plant ID!");
  549. switch_to(error);
  550. } else {
  551. selected_plants.set(selected_id - 1);
  552. menu_entered_digits = "";
  553. switch_to(auto_plant);
  554. }
  555. }
  556. } else {
  557. if (db.spaceLeft()) {
  558. db.addDigit(n);
  559. menu_entered_digits += String(n);
  560. switch_to(state);
  561. } else {
  562. backspace();
  563. }
  564. }
  565. } else if (state == fullauto_fert) {
  566. if (n == -1) {
  567. if (db.hasDigits()) {
  568. backspace();
  569. db.removeDigit();
  570. if (menu_entered_digits.length() > 0) {
  571. menu_entered_digits.remove(menu_entered_digits.length() - 1);
  572. switch_to(state);
  573. }
  574. } else {
  575. switch_to(auto_mode_a);
  576. }
  577. } else if (n == -2) {
  578. if (!db.hasDigits()) {
  579. selected_plants.clear();
  580. switch_to(fullauto_plant);
  581. } else {
  582. selected_id = number_input();
  583. if ((selected_id <= 0) || (selected_id > plants.countFertilizers())) {
  584. error_condition = F("Invalid fert. ID!");
  585. switch_to(error);
  586. } else {
  587. selected_ferts.set(selected_id - 1);
  588. menu_entered_digits = "";
  589. switch_to(fullauto_fert);
  590. }
  591. }
  592. } else {
  593. if (db.spaceLeft()) {
  594. db.addDigit(n);
  595. menu_entered_digits += String(n);
  596. switch_to(state);
  597. } else {
  598. backspace();
  599. }
  600. }
  601. } else if (state == fullauto_plant) {
  602. if (n == -1) {
  603. if (db.hasDigits()) {
  604. backspace();
  605. db.removeDigit();
  606. if (menu_entered_digits.length() > 0) {
  607. menu_entered_digits.remove(menu_entered_digits.length() - 1);
  608. switch_to(state);
  609. }
  610. } else {
  611. selected_ferts.clear();
  612. switch_to(fullauto_fert);
  613. }
  614. } else if (n == -2) {
  615. if (!db.hasDigits()) {
  616. if (selected_plants.countSet() <= 0) {
  617. // user has not selected a plant yet...
  618. return;
  619. }
  620. #ifdef FULLAUTO_MIN_PLANT_COUNT
  621. if (selected_plants.countSet() < FULLAUTO_MIN_PLANT_COUNT) {
  622. return;
  623. }
  624. #endif
  625. // check if we need to run fertilizers
  626. if (selected_ferts.countSet() > 0) {
  627. // stirr before pumping fertilizers
  628. for (int i = 0; i < STIRRER_COUNT; i++) {
  629. plants.startAux(i);
  630. }
  631. selected_id = 1;
  632. selected_time = AUTO_STIRR_RUNTIME;
  633. start_time = millis();
  634. switch_to(fullauto_stirr_run);
  635. } else {
  636. // immediately continue with filling tank
  637. auto wl = plants.getWaterlevel();
  638. if ((wl != Plants::full) && (wl != Plants::invalid)) {
  639. // if the waterlevel is currently empty, we
  640. // set a flag to record the time to fill
  641. filling_started_empty = (wl == Plants::empty);
  642. plants.openWaterInlet();
  643. selected_id = plants.countPlants() + 1;
  644. selected_time = MAX_TANK_FILL_TIME;
  645. start_time = millis();
  646. switch_to(fullauto_tank_run);
  647. } else if (wl == Plants::full) {
  648. // check if kickstart is required for this
  649. bool need_kickstart = false;
  650. for (int i = 0; i < plants.countPlants(); i++) {
  651. if (selected_plants.isSet(i)) {
  652. if (plants.getKickstart()->getPinNumber(i) >= 0) {
  653. need_kickstart = true;
  654. }
  655. }
  656. }
  657. // start kickstart/valve as needed
  658. for (int i = 0; i < plants.countPlants(); i++) {
  659. if (selected_plants.isSet(i)) {
  660. plants.startPlant(i, need_kickstart);
  661. }
  662. }
  663. // for recording the flowrate
  664. watering_started_full = (wl == Plants::full);
  665. selected_time = MAX_AUTO_PLANT_RUNTIME;
  666. start_time = millis();
  667. if (need_kickstart) {
  668. switch_to(fullauto_kickstart_run);
  669. } else {
  670. switch_to(fullauto_plant_run);
  671. }
  672. } else if (wl == Plants::invalid) {
  673. error_condition = F("Invalid sensor state");
  674. state = auto_mode_a;
  675. switch_to(error);
  676. }
  677. }
  678. } else {
  679. selected_id = number_input();
  680. if ((selected_id <= 0) || (selected_id > plants.countPlants())) {
  681. error_condition = F("Invalid plant ID!");
  682. switch_to(error);
  683. } else {
  684. selected_plants.set(selected_id - 1);
  685. menu_entered_digits = "";
  686. switch_to(fullauto_plant);
  687. }
  688. }
  689. } else {
  690. if (db.spaceLeft()) {
  691. db.addDigit(n);
  692. menu_entered_digits += String(n);
  693. switch_to(state);
  694. } else {
  695. backspace();
  696. }
  697. }
  698. } else if ((state == auto_done) || (state == fullauto_done)) {
  699. switch_to(auto_mode_a);
  700. } else if (state == menu_pumps) {
  701. if (n == -1) {
  702. if (db.hasDigits()) {
  703. backspace();
  704. db.removeDigit();
  705. if (menu_entered_digits.length() > 0) {
  706. menu_entered_digits.remove(menu_entered_digits.length() - 1);
  707. switch_to(state);
  708. }
  709. } else {
  710. switch_to(menu_b);
  711. }
  712. } else if (n == -2) {
  713. if (!db.hasDigits()) {
  714. return;
  715. }
  716. selected_id = number_input();
  717. if ((selected_id <= 0) || (selected_id > plants.countFertilizers())) {
  718. error_condition = F("Invalid pump ID!");
  719. switch_to(error);
  720. } else {
  721. switch_to(menu_pumps_time);
  722. }
  723. } else {
  724. if (db.spaceLeft()) {
  725. db.addDigit(n);
  726. menu_entered_digits += String(n);
  727. switch_to(state);
  728. } else {
  729. backspace();
  730. }
  731. }
  732. } else if (state == fillnwater_plant) {
  733. if (n == -1) {
  734. if (db.hasDigits()) {
  735. backspace();
  736. db.removeDigit();
  737. if (menu_entered_digits.length() > 0) {
  738. menu_entered_digits.remove(menu_entered_digits.length() - 1);
  739. switch_to(state);
  740. }
  741. } else {
  742. switch_to(auto_mode_a);
  743. }
  744. } else if (n == -2) {
  745. if (!db.hasDigits()) {
  746. if (selected_plants.countSet() <= 0) {
  747. return;
  748. }
  749. auto wl = plants.getWaterlevel();
  750. if ((wl != Plants::full) && (wl != Plants::invalid)) {
  751. // if the waterlevel is currently empty, we
  752. // set a flag to record the time to fill
  753. filling_started_empty = (wl == Plants::empty);
  754. plants.openWaterInlet();
  755. selected_id = plants.countPlants() + 1;
  756. selected_time = MAX_TANK_FILL_TIME;
  757. start_time = millis();
  758. switch_to(fillnwater_tank_run);
  759. } else if (wl == Plants::full) {
  760. // check if kickstart is required for this
  761. bool need_kickstart = false;
  762. for (int i = 0; i < plants.countPlants(); i++) {
  763. if (selected_plants.isSet(i)) {
  764. if (plants.getKickstart()->getPinNumber(i) >= 0) {
  765. need_kickstart = true;
  766. }
  767. }
  768. }
  769. // start kickstart/valve as needed
  770. for (int i = 0; i < plants.countPlants(); i++) {
  771. if (selected_plants.isSet(i)) {
  772. plants.startPlant(i, need_kickstart);
  773. }
  774. }
  775. // for recording the flowrate
  776. watering_started_full = (wl == Plants::full);
  777. selected_time = MAX_AUTO_PLANT_RUNTIME;
  778. start_time = millis();
  779. if (need_kickstart) {
  780. switch_to(fillnwater_kickstart_run);
  781. } else {
  782. switch_to(fillnwater_plant_run);
  783. }
  784. } else if (wl == Plants::invalid) {
  785. error_condition = F("Invalid sensor state");
  786. state = auto_mode_a;
  787. switch_to(error);
  788. }
  789. } else {
  790. selected_id = number_input();
  791. if ((selected_id <= 0) || (selected_id > plants.countPlants())) {
  792. error_condition = F("Invalid plant ID!");
  793. switch_to(error);
  794. } else {
  795. selected_plants.set(selected_id - 1);
  796. menu_entered_digits = "";
  797. switch_to(fillnwater_plant);
  798. }
  799. }
  800. } else {
  801. if (db.spaceLeft()) {
  802. db.addDigit(n);
  803. menu_entered_digits += String(n);
  804. switch_to(state);
  805. } else {
  806. backspace();
  807. }
  808. }
  809. } else if (state == fillnwater_tank_run) {
  810. plants.abort();
  811. stop_time = millis();
  812. auto wl = plants.getWaterlevel();
  813. if ((wl != Plants::empty) && (wl != Plants::invalid)) {
  814. // check if kickstart is required for this
  815. bool need_kickstart = false;
  816. for (int i = 0; i < plants.countPlants(); i++) {
  817. if (selected_plants.isSet(i)) {
  818. if (plants.getKickstart()->getPinNumber(i) >= 0) {
  819. need_kickstart = true;
  820. }
  821. }
  822. }
  823. // start kickstart/valve as needed
  824. for (int i = 0; i < plants.countPlants(); i++) {
  825. if (selected_plants.isSet(i)) {
  826. plants.startPlant(i, need_kickstart);
  827. }
  828. }
  829. watering_started_full = (wl == Plants::full);
  830. selected_time = MAX_AUTO_PLANT_RUNTIME;
  831. start_time = millis();
  832. if (need_kickstart) {
  833. switch_to(fillnwater_kickstart_run);
  834. } else {
  835. switch_to(fillnwater_plant_run);
  836. }
  837. } else if (wl == Plants::empty) {
  838. switch_to(auto_mode_a);
  839. } else if (wl == Plants::invalid) {
  840. error_condition = F("Invalid sensor state");
  841. state = auto_mode_a;
  842. switch_to(error);
  843. }
  844. } else if (state == fillnwater_plant_run) {
  845. plants.abort();
  846. stop_time = millis();
  847. switch_to(auto_done);
  848. } else if (state == menu_pumps_time) {
  849. if (n == -1) {
  850. if (db.hasDigits()) {
  851. backspace();
  852. db.removeDigit();
  853. if (menu_entered_digits.length() > 0) {
  854. menu_entered_digits.remove(menu_entered_digits.length() - 1);
  855. switch_to(state);
  856. }
  857. } else {
  858. switch_to(menu_pumps);
  859. }
  860. } else if (n == -2) {
  861. if (!db.hasDigits()) {
  862. return;
  863. }
  864. selected_time = number_input();
  865. if ((selected_time <= 0) || (selected_time > MAX_PUMP_RUNTIME)) {
  866. error_condition = F("Invalid time range!");
  867. switch_to(error);
  868. } else {
  869. switch_to(menu_pumps_go);
  870. }
  871. } else {
  872. if (db.spaceLeft()) {
  873. db.addDigit(n);
  874. menu_entered_digits += String(n);
  875. switch_to(state);
  876. } else {
  877. backspace();
  878. }
  879. }
  880. } else if (state == menu_pumps_go) {
  881. if (n == -2) {
  882. start_time = millis();
  883. last_animation_time = start_time;
  884. auto wl = plants.getWaterlevel();
  885. if ((wl != Plants::full) && (wl != Plants::invalid)) {
  886. plants.startFertilizer(selected_id - 1);
  887. switch_to(menu_pumps_run);
  888. } else if (wl == Plants::full) {
  889. stop_time = millis();
  890. switch_to(menu_pumps_done);
  891. } else if (wl == Plants::invalid) {
  892. error_condition = F("Invalid sensor state");
  893. state = menu_pumps;
  894. switch_to(error);
  895. }
  896. } else {
  897. switch_to(menu_pumps_time);
  898. }
  899. } else if (state == menu_pumps_run) {
  900. plants.abort();
  901. stop_time = millis();
  902. switch_to(menu_pumps_done);
  903. } else if (state == menu_pumps_done) {
  904. switch_to(menu_b);
  905. } else if (state == menu_valves) {
  906. if (n == -1) {
  907. if (db.hasDigits()) {
  908. backspace();
  909. db.removeDigit();
  910. if (menu_entered_digits.length() > 0) {
  911. menu_entered_digits.remove(menu_entered_digits.length() - 1);
  912. switch_to(state);
  913. }
  914. } else {
  915. switch_to(menu_b);
  916. }
  917. } else if (n == -2) {
  918. if (!db.hasDigits()) {
  919. return;
  920. }
  921. selected_id = number_input();
  922. if ((selected_id <= 0) || (selected_id > (plants.countPlants() + 1))) {
  923. error_condition = F("Invalid valve ID!");
  924. switch_to(error);
  925. } else {
  926. switch_to(menu_valves_time);
  927. }
  928. } else {
  929. if (db.spaceLeft()) {
  930. db.addDigit(n);
  931. menu_entered_digits += String(n);
  932. switch_to(state);
  933. } else {
  934. backspace();
  935. }
  936. }
  937. } else if (state == menu_valves_time) {
  938. if (n == -1) {
  939. if (db.hasDigits()) {
  940. backspace();
  941. db.removeDigit();
  942. if (menu_entered_digits.length() > 0) {
  943. menu_entered_digits.remove(menu_entered_digits.length() - 1);
  944. switch_to(state);
  945. }
  946. } else {
  947. switch_to(menu_valves);
  948. }
  949. } else if (n == -2) {
  950. if (!db.hasDigits()) {
  951. return;
  952. }
  953. selected_time = number_input();
  954. if ((selected_time <= 0) || (selected_time > MAX_VALVE_RUNTIME)) {
  955. error_condition = F("Invalid time range!");
  956. switch_to(error);
  957. } else {
  958. switch_to(menu_valves_go);
  959. }
  960. } else {
  961. if (db.spaceLeft()) {
  962. db.addDigit(n);
  963. menu_entered_digits += String(n);
  964. switch_to(state);
  965. } else {
  966. backspace();
  967. }
  968. }
  969. } else if (state == menu_valves_go) {
  970. if (n == -2) {
  971. start_time = millis();
  972. last_animation_time = start_time;
  973. auto wl = plants.getWaterlevel();
  974. if ((wl != Plants::full) && (wl != Plants::invalid)) {
  975. if (selected_id >= (plants.countPlants() + 1)) {
  976. plants.openWaterInlet();
  977. } else {
  978. // TODO support testing kickstart
  979. plants.startPlant(selected_id - 1, false);
  980. }
  981. switch_to(menu_valves_run);
  982. } else if (wl == Plants::full) {
  983. stop_time = millis();
  984. switch_to(menu_valves_done);
  985. } else if (wl == Plants::invalid) {
  986. error_condition = F("Invalid sensor state");
  987. state = menu_valves;
  988. switch_to(error);
  989. }
  990. } else {
  991. switch_to(menu_valves_time);
  992. }
  993. } else if (state == menu_valves_run) {
  994. plants.abort();
  995. stop_time = millis();
  996. switch_to(menu_valves_done);
  997. } else if (state == menu_valves_done) {
  998. switch_to(menu_b);
  999. } else if (state == menu_aux) {
  1000. if (n == -1) {
  1001. if (db.hasDigits()) {
  1002. backspace();
  1003. db.removeDigit();
  1004. if (menu_entered_digits.length() > 0) {
  1005. menu_entered_digits.remove(menu_entered_digits.length() - 1);
  1006. switch_to(state);
  1007. }
  1008. } else {
  1009. switch_to(menu_c);
  1010. }
  1011. } else if (n == -2) {
  1012. if (!db.hasDigits()) {
  1013. return;
  1014. }
  1015. selected_id = number_input();
  1016. if ((selected_id <= 0) || (selected_id > plants.countAux())) {
  1017. error_condition = F("Invalid valve ID!");
  1018. switch_to(error);
  1019. } else {
  1020. switch_to(menu_aux_time);
  1021. }
  1022. } else {
  1023. if (db.spaceLeft()) {
  1024. db.addDigit(n);
  1025. menu_entered_digits += String(n);
  1026. switch_to(state);
  1027. } else {
  1028. backspace();
  1029. }
  1030. }
  1031. } else if (state == menu_aux_time) {
  1032. if (n == -1) {
  1033. if (db.hasDigits()) {
  1034. backspace();
  1035. db.removeDigit();
  1036. if (menu_entered_digits.length() > 0) {
  1037. menu_entered_digits.remove(menu_entered_digits.length() - 1);
  1038. switch_to(state);
  1039. }
  1040. } else {
  1041. switch_to(menu_aux);
  1042. }
  1043. } else if (n == -2) {
  1044. if (!db.hasDigits()) {
  1045. return;
  1046. }
  1047. selected_time = number_input();
  1048. if ((selected_time <= 0) || (selected_time > MAX_AUX_RUNTIME)) {
  1049. error_condition = F("Invalid time range!");
  1050. switch_to(error);
  1051. } else {
  1052. switch_to(menu_aux_go);
  1053. }
  1054. } else {
  1055. if (db.spaceLeft()) {
  1056. db.addDigit(n);
  1057. menu_entered_digits += String(n);
  1058. switch_to(state);
  1059. } else {
  1060. backspace();
  1061. }
  1062. }
  1063. } else if (state == menu_aux_go) {
  1064. if (n == -2) {
  1065. start_time = millis();
  1066. last_animation_time = start_time;
  1067. plants.startAux(selected_id - 1);
  1068. switch_to(menu_aux_run);
  1069. } else {
  1070. switch_to(menu_aux_time);
  1071. }
  1072. } else if (state == menu_aux_run) {
  1073. plants.abort();
  1074. stop_time = millis();
  1075. switch_to(menu_aux_done);
  1076. } else if (state == menu_aux_done) {
  1077. switch_to(menu_c);
  1078. } else if (state == error) {
  1079. if (old_state != error) {
  1080. switch_to(old_state);
  1081. } else {
  1082. switch_to(menu_a);
  1083. }
  1084. }
  1085. }
  1086. uint32_t Statemachine::number_input(void) {
  1087. for (int i = 0; i < db.countDigits(); i++) {
  1088. backspace();
  1089. }
  1090. uint32_t n = db.getNumber();
  1091. db.clear();
  1092. debug.print("Whole number input: ");
  1093. debug.println(n);
  1094. return n;
  1095. }
  1096. void Statemachine::act(void) {
  1097. if ((state == menu_pumps_run) || (state == menu_valves_run)
  1098. || (state == menu_aux_run) || (state == fullauto_stirr_run)
  1099. || (state == auto_stirr_run)) {
  1100. unsigned long runtime = millis() - start_time;
  1101. if ((runtime / 1000UL) >= selected_time) {
  1102. // stop if timeout has been reached
  1103. plants.abort();
  1104. stop_time = millis();
  1105. if (state == menu_pumps_run) {
  1106. switch_to(menu_pumps_done);
  1107. } else if (state == menu_valves_run) {
  1108. switch_to(menu_valves_done);
  1109. } else if (state == menu_aux_run) {
  1110. switch_to(menu_aux_done);
  1111. } else if (state == fullauto_stirr_run) {
  1112. if (selected_ferts.countSet() > 0) {
  1113. selected_time = auto_pump_runtime[0];
  1114. for (int i = 0; i < plants.countFertilizers(); i++) {
  1115. if (auto_pump_runtime[i] > selected_time) {
  1116. selected_time = auto_pump_runtime[i];
  1117. }
  1118. if (selected_ferts.isSet(i)) {
  1119. plants.startFertilizer(i);
  1120. }
  1121. }
  1122. start_time = millis();
  1123. switch_to(fullauto_fert_run);
  1124. } else {
  1125. auto wl = plants.getWaterlevel();
  1126. if ((wl != Plants::full) && (wl != Plants::invalid)) {
  1127. // if the waterlevel is currently empty, we
  1128. // set a flag to record the time to fill
  1129. filling_started_empty = (wl == Plants::empty);
  1130. plants.openWaterInlet();
  1131. selected_id = plants.countPlants() + 1;
  1132. selected_time = MAX_TANK_FILL_TIME;
  1133. start_time = millis();
  1134. switch_to(fullauto_tank_run);
  1135. } else if (wl == Plants::full) {
  1136. // check if kickstart is required for this
  1137. bool need_kickstart = false;
  1138. for (int i = 0; i < plants.countPlants(); i++) {
  1139. if (selected_plants.isSet(i)) {
  1140. if (plants.getKickstart()->getPinNumber(i) >= 0) {
  1141. need_kickstart = true;
  1142. }
  1143. }
  1144. }
  1145. // start kickstart/valve as needed
  1146. for (int i = 0; i < plants.countPlants(); i++) {
  1147. if (selected_plants.isSet(i)) {
  1148. plants.startPlant(i, need_kickstart);
  1149. }
  1150. }
  1151. watering_started_full = (wl == Plants::full);
  1152. selected_time = MAX_AUTO_PLANT_RUNTIME;
  1153. start_time = millis();
  1154. if (need_kickstart) {
  1155. switch_to(fullauto_kickstart_run);
  1156. } else {
  1157. switch_to(fullauto_plant_run);
  1158. }
  1159. } else if (wl == Plants::invalid) {
  1160. plants.abort();
  1161. error_condition = F("Invalid sensor state");
  1162. state = fullauto_fert;
  1163. switch_to(error);
  1164. }
  1165. }
  1166. } else {
  1167. switch_to(auto_done);
  1168. }
  1169. } else if ((millis() - last_animation_time) >= 500) {
  1170. // update animation if needed
  1171. last_animation_time = millis();
  1172. switch_to(state);
  1173. }
  1174. }
  1175. #ifdef CHECK_SENSORS_VALVE_PUMP_MENU_FULL
  1176. if ((state == menu_pumps_run) || ((state == menu_valves_run) && (selected_id == (plants.countPlants() + 1)))) {
  1177. // check water level state
  1178. auto wl = plants.getWaterlevel();
  1179. if (wl == Plants::full) {
  1180. plants.abort();
  1181. stop_time = millis();
  1182. switch_to((state == menu_pumps_run) ? menu_pumps_done : menu_valves_done);
  1183. } else if (wl == Plants::invalid) {
  1184. plants.abort();
  1185. error_condition = F("Invalid sensor state");
  1186. state = (state == menu_pumps_run) ? menu_pumps : menu_valves;
  1187. switch_to(error);
  1188. }
  1189. }
  1190. #endif // CHECK_SENSORS_VALVE_PUMP_MENU_FULL
  1191. #ifdef CHECK_SENSORS_VALVE_PUMP_MENU_EMPTY
  1192. if ((state == menu_valves_run) && (selected_id <= plants.countPlants())) {
  1193. // check water level state
  1194. auto wl = plants.getWaterlevel();
  1195. if (wl == Plants::empty) {
  1196. plants.abort();
  1197. stop_time = millis();
  1198. switch_to(menu_valves_done);
  1199. } else if (wl == Plants::invalid) {
  1200. plants.abort();
  1201. error_condition = F("Invalid sensor state");
  1202. state = menu_valves;
  1203. switch_to(error);
  1204. }
  1205. }
  1206. #endif // CHECK_SENSORS_VALVE_PUMP_MENU_EMPTY
  1207. // kickstart states
  1208. if ((state == auto_plant_kickstart_run) || (state == fillnwater_kickstart_run)
  1209. || (state == fullauto_kickstart_run) || (state == fullauto_kickstart_purge_run)) {
  1210. unsigned long runtime = millis() - start_time;
  1211. if ((runtime / 1000UL) >= KICKSTART_RUNTIME) {
  1212. // kickstart is done, switch over to valves
  1213. plants.abort();
  1214. selected_time = MAX_AUTO_PLANT_RUNTIME;
  1215. start_time = millis();
  1216. // start required valves
  1217. for (int i = 0; i < plants.countPlants(); i++) {
  1218. if (selected_plants.isSet(i)) {
  1219. plants.startPlant(i, false);
  1220. }
  1221. }
  1222. if (state == auto_plant_kickstart_run) {
  1223. switch_to(auto_plant_run);
  1224. } else if (state == fillnwater_kickstart_run) {
  1225. switch_to(fillnwater_plant_run);
  1226. } else if (state == fullauto_kickstart_run) {
  1227. switch_to(fullauto_plant_run);
  1228. } else {
  1229. switch_to(fullauto_plant_purge_run);
  1230. }
  1231. } else if ((millis() - last_animation_time) >= 500) {
  1232. // update animation if needed
  1233. last_animation_time = millis();
  1234. switch_to(state);
  1235. }
  1236. }
  1237. if (state == fullauto_fert_run) {
  1238. unsigned long runtime = millis() - start_time;
  1239. for (int i = 0; i < plants.countFertilizers(); i++) {
  1240. if (selected_ferts.isSet(i)) {
  1241. if ((runtime / 1000UL) >= auto_pump_runtime[i]) {
  1242. plants.stopFertilizer(i);
  1243. selected_ferts.set(i, false);
  1244. }
  1245. }
  1246. }
  1247. if ((millis() - last_animation_time) >= 500) {
  1248. // update animation if needed
  1249. last_animation_time = millis();
  1250. switch_to(state);
  1251. }
  1252. if (selected_ferts.countSet() <= 0) {
  1253. plants.abort();
  1254. auto wl = plants.getWaterlevel();
  1255. if ((wl != Plants::full) && (wl != Plants::invalid)) {
  1256. // if the waterlevel is currently empty, we
  1257. // set a flag to record the time to fill
  1258. filling_started_empty = (wl == Plants::empty);
  1259. plants.openWaterInlet();
  1260. selected_id = plants.countPlants() + 1;
  1261. selected_time = MAX_TANK_FILL_TIME;
  1262. start_time = millis();
  1263. switch_to(fullauto_tank_run);
  1264. } else if (wl == Plants::full) {
  1265. // check if kickstart is required for this
  1266. bool need_kickstart = false;
  1267. for (int i = 0; i < plants.countPlants(); i++) {
  1268. if (selected_plants.isSet(i)) {
  1269. if (plants.getKickstart()->getPinNumber(i) >= 0) {
  1270. need_kickstart = true;
  1271. }
  1272. }
  1273. }
  1274. // start kickstart/valve as needed
  1275. for (int i = 0; i < plants.countPlants(); i++) {
  1276. if (selected_plants.isSet(i)) {
  1277. plants.startPlant(i, need_kickstart);
  1278. }
  1279. }
  1280. // for recording the flowrate
  1281. watering_started_full = (wl == Plants::full);
  1282. selected_time = MAX_AUTO_PLANT_RUNTIME;
  1283. start_time = millis();
  1284. if (need_kickstart) {
  1285. switch_to(fullauto_kickstart_run);
  1286. } else {
  1287. switch_to(fullauto_plant_run);
  1288. }
  1289. } else if (wl == Plants::invalid) {
  1290. error_condition = F("Invalid sensor state");
  1291. state = auto_mode_a;
  1292. switch_to(error);
  1293. }
  1294. }
  1295. }
  1296. // states that fill the tank up
  1297. if ((state == auto_fert_run) || (state == auto_tank_run)
  1298. || (state == fullauto_tank_run) || (state == fullauto_tank_purge_run)
  1299. || (state == fillnwater_tank_run)) {
  1300. unsigned long runtime = millis() - start_time;
  1301. if ((runtime / 1000UL) >= selected_time) {
  1302. // stop if timeout has been reached
  1303. plants.abort();
  1304. stop_time = millis();
  1305. if ((state == fillnwater_tank_run)
  1306. || (state == fullauto_tank_run)
  1307. || (state == fullauto_tank_purge_run)) {
  1308. auto wl = plants.getWaterlevel();
  1309. if ((wl != Plants::empty) && (wl != Plants::invalid)) {
  1310. // check if kickstart is required for this
  1311. bool need_kickstart = false;
  1312. for (int i = 0; i < plants.countPlants(); i++) {
  1313. if (selected_plants.isSet(i)) {
  1314. if (plants.getKickstart()->getPinNumber(i) >= 0) {
  1315. need_kickstart = true;
  1316. }
  1317. }
  1318. }
  1319. // start kickstart/valve as needed
  1320. for (int i = 0; i < plants.countPlants(); i++) {
  1321. if (selected_plants.isSet(i)) {
  1322. plants.startPlant(i, need_kickstart);
  1323. }
  1324. }
  1325. watering_started_full = (wl == Plants::full);
  1326. selected_time = MAX_AUTO_PLANT_RUNTIME;
  1327. start_time = millis();
  1328. if (need_kickstart) {
  1329. if (state == fillnwater_tank_run) {
  1330. switch_to(fillnwater_kickstart_run);
  1331. } else if (state == fullauto_tank_run) {
  1332. switch_to(fullauto_kickstart_run);
  1333. } else {
  1334. switch_to(fullauto_kickstart_purge_run);
  1335. }
  1336. } else {
  1337. if (state == fillnwater_tank_run) {
  1338. switch_to(fillnwater_plant_run);
  1339. } else if (state == fullauto_tank_run) {
  1340. switch_to(fullauto_plant_run);
  1341. } else {
  1342. switch_to(fullauto_plant_purge_run);
  1343. }
  1344. }
  1345. } else if (wl == Plants::empty) {
  1346. stop_time = millis();
  1347. switch_to(auto_done);
  1348. } else if (wl == Plants::invalid) {
  1349. error_condition = F("Invalid sensor state");
  1350. state = auto_mode_a;
  1351. switch_to(error);
  1352. }
  1353. } else {
  1354. switch_to(auto_done);
  1355. }
  1356. } else if ((millis() - last_animation_time) >= 500) {
  1357. // update animation if needed
  1358. last_animation_time = millis();
  1359. switch_to(state);
  1360. }
  1361. // check water level state
  1362. auto wl = plants.getWaterlevel();
  1363. if (wl == Plants::full) {
  1364. plants.abort();
  1365. stop_time = millis();
  1366. if ((state == fillnwater_tank_run)
  1367. || (state == fullauto_tank_run)
  1368. || (state == fullauto_tank_purge_run)) {
  1369. // record time to fill here, if we started with
  1370. // an empty tank at the start of filling
  1371. if (filling_started_empty) {
  1372. unsigned long time_to_fill = stop_time - start_time;
  1373. debug.print("Filling tank took ");
  1374. debug.print(String(time_to_fill));
  1375. debug.println("ms");
  1376. #if defined(PLATFORM_ESP)
  1377. wifi_write_database(time_to_fill, "calibrated_filling", -1);
  1378. #endif // PLATFORM_ESP
  1379. }
  1380. // check if kickstart is required for this
  1381. bool need_kickstart = false;
  1382. for (int i = 0; i < plants.countPlants(); i++) {
  1383. if (selected_plants.isSet(i)) {
  1384. if (plants.getKickstart()->getPinNumber(i) >= 0) {
  1385. need_kickstart = true;
  1386. }
  1387. }
  1388. }
  1389. // start kickstart/valve as needed
  1390. for (int i = 0; i < plants.countPlants(); i++) {
  1391. if (selected_plants.isSet(i)) {
  1392. plants.startPlant(i, need_kickstart);
  1393. }
  1394. }
  1395. watering_started_full = (wl == Plants::full);
  1396. selected_time = MAX_AUTO_PLANT_RUNTIME;
  1397. start_time = millis();
  1398. if (need_kickstart) {
  1399. if (state == fillnwater_tank_run) {
  1400. switch_to(fillnwater_kickstart_run);
  1401. } else if (state == fullauto_tank_run) {
  1402. switch_to(fullauto_kickstart_run);
  1403. } else {
  1404. switch_to(fullauto_kickstart_purge_run);
  1405. }
  1406. } else {
  1407. if (state == fillnwater_tank_run) {
  1408. switch_to(fillnwater_plant_run);
  1409. } else if (state == fullauto_tank_run) {
  1410. switch_to(fullauto_plant_run);
  1411. } else {
  1412. switch_to(fullauto_plant_purge_run);
  1413. }
  1414. }
  1415. } else {
  1416. switch_to(auto_done);
  1417. }
  1418. } else if (wl == Plants::invalid) {
  1419. plants.abort();
  1420. error_condition = F("Invalid sensor state");
  1421. state = auto_mode_a;
  1422. switch_to(error);
  1423. }
  1424. }
  1425. // states that empty the tank
  1426. if ((state == auto_plant_run) || (state == fillnwater_plant_run)
  1427. || (state == fullauto_plant_run) || (state == fullauto_plant_overrun)
  1428. || (state == fullauto_plant_purge_run)
  1429. || (state == fullauto_plant_purge_overrun)) {
  1430. unsigned long runtime = millis() - start_time;
  1431. if ((runtime / 1000UL) >= selected_time) {
  1432. if (state == fullauto_plant_run) {
  1433. start_time = millis();
  1434. selected_time = OVERRUN_RUNTIME;
  1435. switch_to(fullauto_plant_overrun);
  1436. } else if (state == fullauto_plant_overrun) {
  1437. plants.abort();
  1438. stop_time = millis();
  1439. auto wl = plants.getWaterlevel();
  1440. if ((wl != Plants::full) && (wl != Plants::invalid)) {
  1441. // if the waterlevel is currently empty, we
  1442. // set a flag to record the time to fill
  1443. filling_started_empty = (wl == Plants::empty);
  1444. plants.openWaterInlet();
  1445. selected_id = plants.countPlants() + 1;
  1446. selected_time = PURGE_FILL_RUNTIME;
  1447. start_time = millis();
  1448. switch_to(fullauto_tank_purge_run);
  1449. } else if (wl == Plants::full) {
  1450. // check if kickstart is required for this
  1451. bool need_kickstart = false;
  1452. for (int i = 0; i < plants.countPlants(); i++) {
  1453. if (selected_plants.isSet(i)) {
  1454. if (plants.getKickstart()->getPinNumber(i) >= 0) {
  1455. need_kickstart = true;
  1456. }
  1457. }
  1458. }
  1459. // start kickstart/valve as needed
  1460. for (int i = 0; i < plants.countPlants(); i++) {
  1461. if (selected_plants.isSet(i)) {
  1462. plants.startPlant(i, need_kickstart);
  1463. }
  1464. }
  1465. // for recording the flowrate
  1466. watering_started_full = (wl == Plants::full);
  1467. selected_time = MAX_AUTO_PLANT_RUNTIME;
  1468. start_time = millis();
  1469. if (need_kickstart) {
  1470. switch_to(fullauto_kickstart_purge_run);
  1471. } else {
  1472. switch_to(fullauto_plant_purge_run);
  1473. }
  1474. } else if (wl == Plants::invalid) {
  1475. error_condition = F("Invalid sensor state");
  1476. state = auto_mode_a;
  1477. switch_to(error);
  1478. }
  1479. } else if (state == fullauto_plant_purge_run) {
  1480. start_time = millis();
  1481. selected_time = OVERRUN_RUNTIME;
  1482. switch_to(fullauto_plant_purge_overrun);
  1483. } else if (state == fullauto_plant_purge_overrun) {
  1484. plants.abort();
  1485. stop_time = millis();
  1486. switch_to(fullauto_done);
  1487. } else {
  1488. plants.abort();
  1489. stop_time = millis();
  1490. switch_to(auto_done);
  1491. }
  1492. } else if ((millis() - last_animation_time) >= 500) {
  1493. // update animation if needed
  1494. last_animation_time = millis();
  1495. switch_to(state);
  1496. }
  1497. if ((state == fullauto_plant_overrun) || (state == fullauto_plant_purge_overrun)) {
  1498. // overrun should not exit when sensor returns empty
  1499. return;
  1500. }
  1501. // check water level state
  1502. auto wl = plants.getWaterlevel();
  1503. if (wl == Plants::empty) {
  1504. if ((state == auto_plant_run) || (state == fillnwater_plant_run)) {
  1505. plants.abort();
  1506. stop_time = millis();
  1507. }
  1508. if ((state == auto_plant_run) || (state == fillnwater_plant_run)
  1509. || (state == fullauto_plant_run)) {
  1510. // if we started watering with a full tank
  1511. // and then finished watering when it was empty
  1512. // and we were only watering a single plant
  1513. // look at this as a "calibration run" and record
  1514. // the time it took to empty the tank
  1515. if (watering_started_full && (selected_plants.countSet() == 1)) {
  1516. unsigned long time_to_water = stop_time - start_time;
  1517. debug.print("Watering plant ");
  1518. debug.print(selected_plants.getFirstSet() + 1);
  1519. debug.print(" with the complete tank took ");
  1520. debug.print(String(time_to_water));
  1521. debug.println("ms");
  1522. #if defined(PLATFORM_ESP)
  1523. wifi_write_database(time_to_water, "calibrated_watering", selected_plants.getFirstSet() + 1);
  1524. #endif // PLATFORM_ESP
  1525. }
  1526. }
  1527. if ((state == auto_plant_run) || (state == fillnwater_plant_run)) {
  1528. switch_to(auto_done);
  1529. } else if (state == fullauto_plant_run) {
  1530. start_time = millis();
  1531. selected_time = OVERRUN_RUNTIME;
  1532. switch_to(fullauto_plant_overrun);
  1533. } else if (state == fullauto_plant_purge_run) {
  1534. start_time = millis();
  1535. selected_time = OVERRUN_RUNTIME;
  1536. switch_to(fullauto_plant_purge_overrun);
  1537. }
  1538. } else if (wl == Plants::invalid) {
  1539. plants.abort();
  1540. error_condition = F("Invalid sensor state");
  1541. state = auto_mode_a;
  1542. switch_to(error);
  1543. }
  1544. }
  1545. // timeout in user-input states
  1546. if ((state == menu_a) || (state == menu_b) || (state == menu_c)
  1547. || (state == automation_mode) || (state == auto_done)
  1548. || (state == auto_mode_a) || (state == auto_mode_b)
  1549. || (state == auto_fert_a) || (state == auto_fert_b)
  1550. || (state == auto_plant) || (state == fillnwater_plant)
  1551. || (state == menu_pumps) || (state == menu_pumps_time)
  1552. || (state == menu_pumps_go) || (state == menu_pumps_done)
  1553. || (state == menu_valves) || (state == menu_valves_time)
  1554. || (state == menu_valves_go) || (state == menu_valves_done)
  1555. || (state == menu_aux) || (state == menu_aux_time)
  1556. || (state == menu_aux_go) || (state == menu_aux_done)
  1557. || (state == fullauto_fert) || (state == fullauto_plant)
  1558. || (state == fullauto_done)) {
  1559. unsigned long runtime = millis() - into_state_time;
  1560. if (runtime >= BACK_TO_IDLE_TIMEOUT) {
  1561. debug.print("Idle timeout reached in state ");
  1562. debug.println(state_names[state]);
  1563. switch_to(init);
  1564. }
  1565. }
  1566. }
  1567. void Statemachine::switch_to(States s) {
  1568. old_state = state;
  1569. state = s;
  1570. into_state_time = millis();
  1571. if (old_state != state) {
  1572. // don't spam log with every animation state "change"
  1573. debug.print("switch_to ");
  1574. debug.print(state_names[old_state]);
  1575. debug.print(" --> ");
  1576. debug.println(state_names[state]);
  1577. menu_entered_digits = "";
  1578. }
  1579. if (s == init) {
  1580. String a = String(F("- Giess-o-mat V")) + FIRMWARE_VERSION + String(F(" -"));
  1581. #if (LOCK_COUNT > 0) && defined(DOOR_LOCK_PIN)
  1582. String b = String(F("PIN: ")) + menu_entered_digits;
  1583. print(a.c_str(),
  1584. "* or # to enter menu",
  1585. "Enter PIN for locks:",
  1586. b.c_str(),
  1587. 3);
  1588. #else
  1589. print(a.c_str(),
  1590. "Usage: Enter number",
  1591. "* Delete prev. digit",
  1592. "# Execute input num.",
  1593. -1);
  1594. #endif
  1595. } else if (s == door_select) {
  1596. String a = String(F("(Input 1 to ")) + String(LOCK_COUNT) + String(F(")"));
  1597. String b = String(F("Door: ")) + menu_entered_digits;
  1598. print("- Select Door Lock -",
  1599. "Leave empty if done!",
  1600. a.c_str(),
  1601. b.c_str(),
  1602. 3);
  1603. } else if (s == menu_a) {
  1604. print("----- Menu 1/3 -----",
  1605. "1: Manual Operation",
  1606. "2: Automation",
  1607. "#: Go to page 2/3...",
  1608. -1);
  1609. } else if (s == menu_b) {
  1610. print("----- Menu 2/3 -----",
  1611. "3: Fertilizer pumps",
  1612. "4: Outlet valves",
  1613. "#: Go to page 3/3...",
  1614. -1);
  1615. } else if (s == menu_c) {
  1616. print("----- Menu 3/3 -----",
  1617. "5: Aux. Outputs",
  1618. #if (LOCK_COUNT > 0) && !defined(DOOR_LOCK_PIN)
  1619. "6: Door Locks",
  1620. #else
  1621. "",
  1622. #endif
  1623. "#: Go to page 1/3...",
  1624. -1);
  1625. } else if (state == automation_mode) {
  1626. // TODO
  1627. print("---- Automation ----",
  1628. "TODO NOT IMPLEMENTED",
  1629. "TODO NOT IMPLEMENTED",
  1630. "TODO NOT IMPLEMENTED",
  1631. -1);
  1632. } else if (s == auto_mode_a) {
  1633. print("---- Manual 1/3 ----",
  1634. "1: Full-Auto Mode",
  1635. "2: Add Fertilizer",
  1636. "#: Go to page 2/3...",
  1637. -1);
  1638. } else if (s == auto_mode_b) {
  1639. print("---- Manual 2/3 ----",
  1640. "3: Fill 'n' Water",
  1641. "4: Fill Reservoir",
  1642. "#: Go to page 3/3...",
  1643. -1);
  1644. } else if (s == auto_mode_c) {
  1645. print("---- Manual 3/3 ----",
  1646. "5: Water a plant",
  1647. "",
  1648. "#: Go to page 1/3...",
  1649. -1);
  1650. } else if (s == auto_fert_a) {
  1651. print("-- Fertilizer 1/2 --",
  1652. "1: Vegetation Phase",
  1653. "2: Bloom Phase",
  1654. "#: Go to page 2/2...",
  1655. -1);
  1656. } else if (s == auto_fert_b) {
  1657. print("-- Fertilizer 2/2 --",
  1658. "3: Special Fert.",
  1659. "4: Run Stirrer",
  1660. "#: Go to page 1/2...",
  1661. -1);
  1662. } else if ((s == auto_fert_run) || (s == fullauto_fert_run)) {
  1663. unsigned long runtime = millis() - start_time;
  1664. String a = String(F("Time: ")) + String(runtime / 1000UL) + String(F("s / ")) + String(selected_time) + String('s');
  1665. unsigned long anim = runtime * 20UL / (selected_time * 1000UL);
  1666. String b;
  1667. for (unsigned long i = 0; i < anim; i++) {
  1668. b += '#';
  1669. }
  1670. print("---- Dispensing ----",
  1671. a.c_str(),
  1672. b.c_str(),
  1673. "Hit any key to stop!",
  1674. -1);
  1675. } else if ((s == auto_stirr_run) || (s == fullauto_stirr_run)) {
  1676. unsigned long runtime = millis() - start_time;
  1677. String a = String(F("Time: ")) + String(runtime / 1000UL) + String(F("s / ")) + String(selected_time) + String('s');
  1678. unsigned long anim = runtime * 20UL / (selected_time * 1000UL);
  1679. String b;
  1680. for (unsigned long i = 0; i < anim; i++) {
  1681. b += '#';
  1682. }
  1683. print("----- Stirring -----",
  1684. a.c_str(),
  1685. b.c_str(),
  1686. "Hit any key to stop!",
  1687. -1);
  1688. } else if ((s == auto_tank_run) || (s == fillnwater_tank_run) || (s == fullauto_tank_run) || (s == fullauto_tank_purge_run)) {
  1689. unsigned long runtime = millis() - start_time;
  1690. String a = String(F("Time: ")) + String(runtime / 1000UL) + String(F("s / ")) + String(selected_time) + String('s');
  1691. unsigned long anim = runtime * 20UL / (selected_time * 1000UL);
  1692. String b;
  1693. for (unsigned long i = 0; i < anim; i++) {
  1694. b += '#';
  1695. }
  1696. print("--- Filling Tank ---",
  1697. a.c_str(),
  1698. b.c_str(),
  1699. "Hit any key to stop!",
  1700. -1);
  1701. } else if ((s == auto_plant) || (s == fillnwater_plant) || (s == fullauto_plant)) {
  1702. String a = String(F("(Input 1 to ")) + String(plants.countPlants()) + String(F(")"));
  1703. String b = String(F("Plant: ")) + menu_entered_digits;
  1704. print("--- Select Plant ---",
  1705. "Leave empty if done!",
  1706. a.c_str(),
  1707. b.c_str(),
  1708. 3);
  1709. } else if (s == fullauto_fert) {
  1710. String a = String(F("(Input 1 to ")) + String(plants.countFertilizers()) + String(F(")"));
  1711. String b = String(F("Fert.: ")) + menu_entered_digits;
  1712. print("--- Select Fert. ---",
  1713. "Leave empty if done!",
  1714. a.c_str(),
  1715. b.c_str(),
  1716. 3);
  1717. } else if ((s == auto_plant_kickstart_run) || (s == fillnwater_kickstart_run) || (s == fullauto_kickstart_run) || (s == fullauto_kickstart_purge_run)) {
  1718. unsigned long runtime = millis() - start_time;
  1719. String a = String(F("Time: ")) + String(runtime / 1000UL) + String(F("s / ")) + String(KICKSTART_RUNTIME) + String('s');
  1720. unsigned long anim = runtime * 20UL / (KICKSTART_RUNTIME * 1000UL);
  1721. String b;
  1722. for (unsigned long i = 0; i < anim; i++) {
  1723. b += '#';
  1724. }
  1725. print("---- Kick-Start ----",
  1726. a.c_str(),
  1727. b.c_str(),
  1728. "Hit any key to stop!",
  1729. -1);
  1730. } else if ((s == auto_plant_run) || (s == fillnwater_plant_run) || (s == fullauto_plant_run) || (s == fullauto_plant_purge_run)) {
  1731. unsigned long runtime = millis() - start_time;
  1732. String a = String(F("Time: ")) + String(runtime / 1000UL) + String(F("s / ")) + String(selected_time) + String('s');
  1733. unsigned long anim = runtime * 20UL / (selected_time * 1000UL);
  1734. String b;
  1735. for (unsigned long i = 0; i < anim; i++) {
  1736. b += '#';
  1737. }
  1738. print("----- Watering -----",
  1739. a.c_str(),
  1740. b.c_str(),
  1741. "Hit any key to stop!",
  1742. -1);
  1743. } else if ((s == fullauto_plant_overrun) || (s == fullauto_plant_purge_overrun)) {
  1744. unsigned long runtime = millis() - start_time;
  1745. String a = String(F("Time: ")) + String(runtime / 1000UL) + String(F("s / ")) + String(selected_time) + String('s');
  1746. unsigned long anim = runtime * 20UL / (selected_time * 1000UL);
  1747. String b;
  1748. for (unsigned long i = 0; i < anim; i++) {
  1749. b += '#';
  1750. }
  1751. print("-- Emptying lines --",
  1752. a.c_str(),
  1753. b.c_str(),
  1754. "Hit any key to stop!",
  1755. -1);
  1756. } else if ((s == auto_done) || (s == fullauto_done)) {
  1757. String a = String(F("after ")) + String((stop_time - start_time) / 1000UL) + String(F("s."));
  1758. print("------- Done -------",
  1759. "Dispensing finished",
  1760. a.c_str(),
  1761. "Hit any key for menu",
  1762. -1);
  1763. #if defined(PLATFORM_ESP)
  1764. unsigned long runtime = stop_time - start_time;
  1765. if ((old_state == auto_plant_run) || (old_state == fillnwater_plant_run)) {
  1766. for (int i = 0; i < plants.countPlants(); i++) {
  1767. if (selected_plants.isSet(i)) {
  1768. wifi_write_database(runtime / 1000, "plant", i + 1);
  1769. }
  1770. }
  1771. } else if (old_state == auto_fert_run) {
  1772. wifi_write_database(runtime / 1000, "fertilizer", selected_id);
  1773. }
  1774. #endif // PLATFORM_ESP
  1775. } else if (s == menu_pumps) {
  1776. String a = String(F("(Input 1 to ")) + String(plants.countFertilizers()) + String(F(")"));
  1777. String b = String(F("Pump: ")) + menu_entered_digits;
  1778. print("------- Pump -------",
  1779. "Please select pump",
  1780. a.c_str(),
  1781. b.c_str(),
  1782. 3);
  1783. } else if (s == menu_pumps_time) {
  1784. String header = String(F("------ Pump ")) + String(selected_id) + String(F(" ------"));
  1785. String b = String(F("Runtime: ")) + menu_entered_digits;
  1786. print(header.c_str(),
  1787. "Please set runtime",
  1788. "(Input in seconds)",
  1789. b.c_str(),
  1790. 3);
  1791. } else if (s == menu_pumps_go) {
  1792. String a = String(F("Pump No. ")) + String(selected_id);
  1793. String b = String(F("Runtime ")) + String(selected_time) + String('s');
  1794. print("----- Confirm? -----",
  1795. a.c_str(),
  1796. b.c_str(),
  1797. " # Confirm",
  1798. -1);
  1799. } else if (s == menu_pumps_run) {
  1800. unsigned long runtime = millis() - start_time;
  1801. String a = String(F("Time: ")) + String(runtime / 1000UL) + String(F("s / ")) + String(selected_time) + String('s');
  1802. unsigned long anim = runtime * 20UL / (selected_time * 1000UL);
  1803. String b;
  1804. for (unsigned long i = 0; i < anim; i++) {
  1805. b += '#';
  1806. }
  1807. print("---- Dispensing ----",
  1808. a.c_str(),
  1809. b.c_str(),
  1810. "Hit any key to stop!",
  1811. -1);
  1812. } else if (s == menu_pumps_done) {
  1813. String a = String(F("after ")) + String((stop_time - start_time) / 1000UL) + String(F("s."));
  1814. print("------- Done -------",
  1815. "Dispensing finished",
  1816. a.c_str(),
  1817. "Hit any key for menu",
  1818. -1);
  1819. #if defined(PLATFORM_ESP)
  1820. unsigned long runtime = stop_time - start_time;
  1821. wifi_write_database(runtime / 1000, "fertilizer", selected_id);
  1822. #endif // PLATFORM_ESP
  1823. } else if (s == menu_valves) {
  1824. String a = String(F("(Input 1 to ")) + String(plants.countPlants() + 1) + String(F(")"));
  1825. String b = String(F("Valve: ")) + menu_entered_digits;
  1826. print("------ Valves ------",
  1827. "Please select valve",
  1828. a.c_str(),
  1829. b.c_str(),
  1830. 3);
  1831. } else if (s == menu_valves_time) {
  1832. String header = String(F("----- Valve ")) + String(selected_id) + String(F(" -----"));
  1833. String b = String(F("Runtime: ")) + menu_entered_digits;
  1834. print(header.c_str(),
  1835. "Please set runtime",
  1836. "(Input in seconds)",
  1837. b.c_str(),
  1838. 3);
  1839. } else if (s == menu_valves_go) {
  1840. String a = String(F("Valve No. ")) + String(selected_id);
  1841. String b = String(F("Runtime ")) + String(selected_time) + String('s');
  1842. print("----- Confirm? -----",
  1843. a.c_str(),
  1844. b.c_str(),
  1845. " # Confirm",
  1846. -1);
  1847. } else if (s == menu_valves_run) {
  1848. unsigned long runtime = millis() - start_time;
  1849. String a = String(F("Time: ")) + String(runtime / 1000UL) + String(F("s / ")) + String(selected_time) + String('s');
  1850. unsigned long anim = runtime * 20UL / (selected_time * 1000UL);
  1851. String b;
  1852. for (unsigned long i = 0; i <= anim; i++) {
  1853. b += '#';
  1854. }
  1855. print("---- Dispensing ----",
  1856. a.c_str(),
  1857. b.c_str(),
  1858. "Hit any key to stop!",
  1859. -1);
  1860. } else if (s == menu_valves_done) {
  1861. String a = String(F("after ")) + String((stop_time - start_time) / 1000UL) + String(F("s."));
  1862. print("------- Done -------",
  1863. "Dispensing finished",
  1864. a.c_str(),
  1865. "Hit any key for menu",
  1866. -1);
  1867. #if defined(PLATFORM_ESP)
  1868. unsigned long runtime = stop_time - start_time;
  1869. if (selected_id <= plants.countPlants()) {
  1870. wifi_write_database(runtime / 1000, "plant", selected_id);
  1871. }
  1872. #endif // PLATFORM_ESP
  1873. } else if (s == menu_aux) {
  1874. String a = String(F("(Input 1 to ")) + String(plants.countAux()) + String(F(")"));
  1875. String b = String(F("Aux.: ")) + menu_entered_digits;
  1876. print("------- Aux. -------",
  1877. "Please select aux.",
  1878. a.c_str(),
  1879. b.c_str(),
  1880. 3);
  1881. } else if (s == menu_aux_time) {
  1882. String header = String(F("------ Aux ")) + String(selected_id) + String(F(" ------"));
  1883. String b = String(F("Runtime: ")) + menu_entered_digits;
  1884. print(header.c_str(),
  1885. "Please set runtime",
  1886. "(Input in seconds)",
  1887. b.c_str(),
  1888. 3);
  1889. } else if (s == menu_aux_go) {
  1890. String a = String(F("Aux No. ")) + String(selected_id);
  1891. String b = String(F("Runtime ")) + String(selected_time) + String('s');
  1892. print("----- Confirm? -----",
  1893. a.c_str(),
  1894. b.c_str(),
  1895. " # Confirm",
  1896. -1);
  1897. } else if (s == menu_aux_run) {
  1898. unsigned long runtime = millis() - start_time;
  1899. String a = String(F("Time: ")) + String(runtime / 1000UL) + String(F("s / ")) + String(selected_time) + String('s');
  1900. unsigned long anim = runtime * 20UL / (selected_time * 1000UL);
  1901. String b;
  1902. for (unsigned long i = 0; i <= anim; i++) {
  1903. b += '#';
  1904. }
  1905. print("----- Stirring -----",
  1906. a.c_str(),
  1907. b.c_str(),
  1908. "Hit any key to stop!",
  1909. -1);
  1910. } else if (s == menu_aux_done) {
  1911. String a = String(F("after ")) + String((stop_time - start_time) / 1000UL) + String(F("s."));
  1912. print("------- Done -------",
  1913. "Aux. run finished",
  1914. a.c_str(),
  1915. "Hit any key for menu",
  1916. -1);
  1917. } else if (s == error) {
  1918. print("------ Error! ------",
  1919. "There is a problem:",
  1920. error_condition.c_str(),
  1921. " Press any key...",
  1922. -1);
  1923. } else {
  1924. debug.print("Invalid state ");
  1925. debug.println(s);
  1926. }
  1927. }
  1928. #endif