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.

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