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

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