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

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