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

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