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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998
  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(auto_mode_a),
  69. stringify(auto_mode_b),
  70. stringify(auto_fert),
  71. stringify(auto_fert_run),
  72. stringify(auto_tank_run),
  73. stringify(auto_plant),
  74. stringify(auto_plant_run),
  75. stringify(auto_done),
  76. stringify(fillnwater_plant),
  77. stringify(fillnwater_tank_run),
  78. stringify(fillnwater_plant_run),
  79. stringify(automation_mode),
  80. stringify(menu_pumps),
  81. stringify(menu_pumps_time),
  82. stringify(menu_pumps_go),
  83. stringify(menu_pumps_run),
  84. stringify(menu_pumps_done),
  85. stringify(menu_valves),
  86. stringify(menu_valves_time),
  87. stringify(menu_valves_go),
  88. stringify(menu_valves_run),
  89. stringify(menu_valves_done),
  90. stringify(error)
  91. };
  92. const char *Statemachine::getStateName(void) {
  93. return state_names[state];
  94. }
  95. bool Statemachine::isIdle(void) {
  96. return state == init;
  97. }
  98. Statemachine::Statemachine(print_fn _print, backspace_fn _backspace)
  99. : db(7), selected_plants(plants.countPlants()) {
  100. state = init;
  101. old_state = init;
  102. print = _print;
  103. backspace = _backspace;
  104. selected_id = 0;
  105. selected_time = 0;
  106. start_time = 0;
  107. stop_time = 0;
  108. last_animation_time = 0;
  109. error_condition = "";
  110. into_state_time = 0;
  111. }
  112. void Statemachine::begin(void) {
  113. switch_to(init);
  114. }
  115. void Statemachine::input(int n) {
  116. if (state == init) {
  117. switch_to(menu_a);
  118. } else if ((state == menu_a) || (state == menu_b)) {
  119. if (n == 1) {
  120. switch_to(auto_mode_a);
  121. } else if (n == 2) {
  122. switch_to(automation_mode);
  123. } else if (n == 3) {
  124. switch_to(menu_pumps);
  125. } else if (n == 4) {
  126. switch_to(menu_valves);
  127. } else if (n == -1) {
  128. switch_to(init);
  129. } else if (n == -2) {
  130. switch_to((state == menu_a) ? menu_b : menu_a);
  131. }
  132. } else if (state == automation_mode) {
  133. // TODO
  134. switch_to(menu_a);
  135. } else if ((state == auto_mode_a) || (state == auto_mode_b)) {
  136. if (n == 1) {
  137. switch_to(auto_fert);
  138. } else if (n == 2) {
  139. selected_plants.clear();
  140. switch_to(fillnwater_plant);
  141. } else if (n == 3) {
  142. auto wl = plants.getWaterlevel();
  143. if ((wl != Plants::full) && (wl != Plants::invalid)) {
  144. plants.openWaterInlet();
  145. selected_id = plants.countPlants() + 1;
  146. selected_time = MAX_TANK_FILL_TIME;
  147. start_time = millis();
  148. switch_to(auto_tank_run);
  149. } else if (wl == Plants::full) {
  150. stop_time = millis();
  151. switch_to(auto_mode_a);
  152. } else if (wl == Plants::invalid) {
  153. error_condition = "Invalid sensor state";
  154. state = auto_mode_a;
  155. switch_to(error);
  156. }
  157. } else if (n == 4) {
  158. selected_plants.clear();
  159. switch_to(auto_plant);
  160. } else if (n == -1) {
  161. switch_to(menu_a);
  162. } else if (n == -2) {
  163. switch_to((state == auto_mode_a) ? auto_mode_b : auto_mode_a);
  164. }
  165. } else if (state == auto_fert) {
  166. if ((n >= 1) && (n <= 3)) {
  167. auto wl = plants.getWaterlevel();
  168. if ((wl != Plants::full) && (wl != Plants::invalid)) {
  169. plants.startFertilizer(n - 1);
  170. selected_id = n;
  171. selected_time = AUTO_PUMP_RUNTIME;
  172. start_time = millis();
  173. switch_to(auto_fert_run);
  174. } else if (wl == Plants::full) {
  175. stop_time = millis();
  176. switch_to(auto_mode_a);
  177. } else if (wl == Plants::invalid) {
  178. error_condition = "Invalid sensor state";
  179. state = auto_mode_a;
  180. switch_to(error);
  181. }
  182. } else if ((n == -1) || (n == -2)) {
  183. switch_to(auto_mode_a);
  184. }
  185. } else if (state == auto_fert_run) {
  186. plants.abort();
  187. stop_time = millis();
  188. switch_to(auto_done);
  189. } else if (state == auto_tank_run) {
  190. plants.abort();
  191. stop_time = millis();
  192. switch_to(auto_done);
  193. } else if (state == auto_plant) {
  194. if (n == -1) {
  195. if (db.hasDigits()) {
  196. backspace();
  197. db.removeDigit();
  198. } else {
  199. switch_to(auto_mode_b);
  200. }
  201. } else if (n == -2) {
  202. if (!db.hasDigits()) {
  203. auto wl = plants.getWaterlevel();
  204. if ((wl != Plants::empty) && (wl != Plants::invalid)) {
  205. for (int i = 0; i < plants.countPlants(); i++) {
  206. if (selected_plants.isSet(i)) {
  207. plants.startPlant(i);
  208. }
  209. }
  210. selected_time = MAX_AUTO_PLANT_RUNTIME;
  211. start_time = millis();
  212. switch_to(auto_plant_run);
  213. } else if (wl == Plants::empty) {
  214. stop_time = millis();
  215. switch_to(auto_mode_b);
  216. } else if (wl == Plants::invalid) {
  217. error_condition = "Invalid sensor state";
  218. state = auto_mode_b;
  219. switch_to(error);
  220. }
  221. } else {
  222. selected_id = number_input();
  223. if ((selected_id <= 0) || (selected_id > plants.countPlants())) {
  224. error_condition = "Invalid plant ID!";
  225. switch_to(error);
  226. } else {
  227. selected_plants.set(selected_id - 1);
  228. switch_to(auto_plant);
  229. }
  230. }
  231. } else {
  232. if (db.spaceLeft()) {
  233. db.addDigit(n);
  234. } else {
  235. backspace();
  236. }
  237. }
  238. } else if (state == auto_plant_run) {
  239. plants.abort();
  240. stop_time = millis();
  241. switch_to(auto_done);
  242. } else if (state == auto_done) {
  243. switch_to(auto_mode_a);
  244. } else if (state == menu_pumps) {
  245. if (n == -1) {
  246. if (db.hasDigits()) {
  247. backspace();
  248. db.removeDigit();
  249. } else {
  250. switch_to(menu_b);
  251. }
  252. } else if (n == -2) {
  253. if (!db.hasDigits()) {
  254. return;
  255. }
  256. selected_id = number_input();
  257. if ((selected_id <= 0) || (selected_id > plants.countFertilizers())) {
  258. error_condition = "Invalid pump ID!";
  259. switch_to(error);
  260. } else {
  261. switch_to(menu_pumps_time);
  262. }
  263. } else {
  264. if (db.spaceLeft()) {
  265. db.addDigit(n);
  266. } else {
  267. backspace();
  268. }
  269. }
  270. } else if (state == fillnwater_plant) {
  271. if (n == -1) {
  272. if (db.hasDigits()) {
  273. backspace();
  274. db.removeDigit();
  275. } else {
  276. switch_to(auto_mode_a);
  277. }
  278. } else if (n == -2) {
  279. if (!db.hasDigits()) {
  280. int found = 0;
  281. for (int i = 0; i < plants.countPlants(); i++) {
  282. if (selected_plants.isSet(i)) {
  283. found = 1;
  284. break;
  285. }
  286. }
  287. if (found != 0) {
  288. auto wl = plants.getWaterlevel();
  289. if ((wl != Plants::full) && (wl != Plants::invalid)) {
  290. plants.openWaterInlet();
  291. selected_id = plants.countPlants() + 1;
  292. selected_time = MAX_TANK_FILL_TIME;
  293. start_time = millis();
  294. switch_to(fillnwater_tank_run);
  295. } else if (wl == Plants::full) {
  296. stop_time = millis();
  297. auto wl = plants.getWaterlevel();
  298. if ((wl != Plants::empty) && (wl != Plants::invalid)) {
  299. for (int i = 0; i < plants.countPlants(); i++) {
  300. if (selected_plants.isSet(i)) {
  301. plants.startPlant(i);
  302. }
  303. }
  304. selected_time = MAX_AUTO_PLANT_RUNTIME;
  305. start_time = millis();
  306. switch_to(fillnwater_plant_run);
  307. } else if (wl == Plants::empty) {
  308. stop_time = millis();
  309. switch_to(auto_mode_a);
  310. } else if (wl == Plants::invalid) {
  311. error_condition = "Invalid sensor state";
  312. state = auto_mode_a;
  313. switch_to(error);
  314. }
  315. } else if (wl == Plants::invalid) {
  316. error_condition = "Invalid sensor state";
  317. state = auto_mode_a;
  318. switch_to(error);
  319. }
  320. }
  321. } else {
  322. selected_id = number_input();
  323. if ((selected_id <= 0) || (selected_id > plants.countPlants())) {
  324. error_condition = "Invalid plant ID!";
  325. switch_to(error);
  326. } else {
  327. selected_plants.set(selected_id - 1);
  328. switch_to(fillnwater_plant);
  329. }
  330. }
  331. } else {
  332. if (db.spaceLeft()) {
  333. db.addDigit(n);
  334. } else {
  335. backspace();
  336. }
  337. }
  338. } else if (state == fillnwater_tank_run) {
  339. plants.abort();
  340. stop_time = millis();
  341. auto wl = plants.getWaterlevel();
  342. if ((wl != Plants::empty) && (wl != Plants::invalid)) {
  343. for (int i = 0; i < plants.countPlants(); i++) {
  344. if (selected_plants.isSet(i)) {
  345. plants.startPlant(i);
  346. }
  347. }
  348. selected_time = MAX_AUTO_PLANT_RUNTIME;
  349. start_time = millis();
  350. switch_to(fillnwater_plant_run);
  351. } else if (wl == Plants::empty) {
  352. switch_to(auto_mode_a);
  353. } else if (wl == Plants::invalid) {
  354. error_condition = "Invalid sensor state";
  355. state = auto_mode_a;
  356. switch_to(error);
  357. }
  358. } else if (state == fillnwater_plant_run) {
  359. plants.abort();
  360. stop_time = millis();
  361. switch_to(auto_done);
  362. } else if (state == menu_pumps_time) {
  363. if (n == -1) {
  364. if (db.hasDigits()) {
  365. backspace();
  366. db.removeDigit();
  367. } else {
  368. switch_to(menu_pumps);
  369. }
  370. } else if (n == -2) {
  371. if (!db.hasDigits()) {
  372. return;
  373. }
  374. selected_time = number_input();
  375. if ((selected_time <= 0) || (selected_time > MAX_PUMP_RUNTIME)) {
  376. error_condition = "Invalid time range!";
  377. switch_to(error);
  378. } else {
  379. switch_to(menu_pumps_go);
  380. }
  381. } else {
  382. if (db.spaceLeft()) {
  383. db.addDigit(n);
  384. } else {
  385. backspace();
  386. }
  387. }
  388. } else if (state == menu_pumps_go) {
  389. if (n == -2) {
  390. start_time = millis();
  391. last_animation_time = start_time;
  392. auto wl = plants.getWaterlevel();
  393. if ((wl != Plants::full) && (wl != Plants::invalid)) {
  394. plants.startFertilizer(selected_id - 1);
  395. switch_to(menu_pumps_run);
  396. } else if (wl == Plants::full) {
  397. stop_time = millis();
  398. switch_to(menu_pumps_done);
  399. } else if (wl == Plants::invalid) {
  400. error_condition = "Invalid sensor state";
  401. state = menu_pumps;
  402. switch_to(error);
  403. }
  404. } else {
  405. switch_to(menu_pumps_time);
  406. }
  407. } else if (state == menu_pumps_run) {
  408. plants.abort();
  409. stop_time = millis();
  410. switch_to(menu_pumps_done);
  411. } else if (state == menu_pumps_done) {
  412. switch_to(menu_b);
  413. } else if (state == menu_valves) {
  414. if (n == -1) {
  415. if (db.hasDigits()) {
  416. backspace();
  417. db.removeDigit();
  418. } else {
  419. switch_to(menu_b);
  420. }
  421. } else if (n == -2) {
  422. if (!db.hasDigits()) {
  423. return;
  424. }
  425. selected_id = number_input();
  426. if ((selected_id <= 0) || (selected_id > (plants.countPlants() + 1))) {
  427. error_condition = "Invalid valve ID!";
  428. switch_to(error);
  429. } else {
  430. switch_to(menu_valves_time);
  431. }
  432. } else {
  433. if (db.spaceLeft()) {
  434. db.addDigit(n);
  435. } else {
  436. backspace();
  437. }
  438. }
  439. } else if (state == menu_valves_time) {
  440. if (n == -1) {
  441. if (db.hasDigits()) {
  442. backspace();
  443. db.removeDigit();
  444. } else {
  445. switch_to(menu_valves);
  446. }
  447. } else if (n == -2) {
  448. if (!db.hasDigits()) {
  449. return;
  450. }
  451. selected_time = number_input();
  452. if ((selected_time <= 0) || (selected_time > MAX_VALVE_RUNTIME)) {
  453. error_condition = "Invalid time range!";
  454. switch_to(error);
  455. } else {
  456. switch_to(menu_valves_go);
  457. }
  458. } else {
  459. if (db.spaceLeft()) {
  460. db.addDigit(n);
  461. } else {
  462. backspace();
  463. }
  464. }
  465. } else if (state == menu_valves_go) {
  466. if (n == -2) {
  467. start_time = millis();
  468. last_animation_time = start_time;
  469. auto wl = plants.getWaterlevel();
  470. if ((wl != Plants::full) && (wl != Plants::invalid)) {
  471. if (selected_id >= (plants.countPlants() + 1)) {
  472. plants.openWaterInlet();
  473. } else {
  474. plants.startPlant(selected_id - 1);
  475. }
  476. switch_to(menu_valves_run);
  477. } else if (wl == Plants::full) {
  478. stop_time = millis();
  479. switch_to(menu_valves_done);
  480. } else if (wl == Plants::invalid) {
  481. error_condition = "Invalid sensor state";
  482. state = menu_valves;
  483. switch_to(error);
  484. }
  485. } else {
  486. switch_to(menu_valves_time);
  487. }
  488. } else if (state == menu_valves_run) {
  489. plants.abort();
  490. stop_time = millis();
  491. switch_to(menu_valves_done);
  492. } else if (state == menu_valves_done) {
  493. switch_to(menu_b);
  494. } else if (state == error) {
  495. if (old_state != error) {
  496. switch_to(old_state);
  497. } else {
  498. switch_to(menu_a);
  499. }
  500. }
  501. }
  502. uint32_t Statemachine::number_input(void) {
  503. for (int i = 0; i < db.countDigits(); i++) {
  504. backspace();
  505. }
  506. uint32_t n = db.getNumber();
  507. db.clear();
  508. debug.print("Whole number input: ");
  509. debug.println(n);
  510. return n;
  511. }
  512. void Statemachine::act(void) {
  513. if ((state == menu_pumps_run) || (state == menu_valves_run)) {
  514. unsigned long runtime = millis() - start_time;
  515. if ((runtime / 1000UL) >= selected_time) {
  516. // stop if timeout has been reached
  517. plants.abort();
  518. stop_time = millis();
  519. switch_to((state == menu_pumps_run) ? menu_pumps_done : menu_valves_done);
  520. } else if ((millis() - last_animation_time) >= 500) {
  521. // update animation if needed
  522. last_animation_time = millis();
  523. switch_to(state);
  524. }
  525. }
  526. #ifdef CHECK_SENSORS_VALVE_PUMP_MENU_FULL
  527. if ((state == menu_pumps_run) || ((state == menu_valves_run) && (selected_id == (plants.countPlants() + 1)))) {
  528. // check water level state
  529. auto wl = plants.getWaterlevel();
  530. if (wl == Plants::full) {
  531. plants.abort();
  532. stop_time = millis();
  533. switch_to((state == menu_pumps_run) ? menu_pumps_done : menu_valves_done);
  534. } else if (wl == Plants::invalid) {
  535. plants.abort();
  536. error_condition = "Invalid sensor state";
  537. state = (state == menu_pumps_run) ? menu_pumps : menu_valves;
  538. switch_to(error);
  539. }
  540. }
  541. #endif // CHECK_SENSORS_VALVE_PUMP_MENU_FULL
  542. #ifdef CHECK_SENSORS_VALVE_PUMP_MENU_EMPTY
  543. if ((state == menu_valves_run) && (selected_id <= plants.countPlants())) {
  544. // check water level state
  545. auto wl = plants.getWaterlevel();
  546. if (wl == Plants::empty) {
  547. plants.abort();
  548. stop_time = millis();
  549. switch_to(menu_valves_done);
  550. } else if (wl == Plants::invalid) {
  551. plants.abort();
  552. error_condition = "Invalid sensor state";
  553. state = menu_valves;
  554. switch_to(error);
  555. }
  556. }
  557. #endif // CHECK_SENSORS_VALVE_PUMP_MENU_EMPTY
  558. if ((state == auto_fert_run) || (state == auto_tank_run) || (state == fillnwater_tank_run)) {
  559. unsigned long runtime = millis() - start_time;
  560. if ((runtime / 1000UL) >= selected_time) {
  561. // stop if timeout has been reached
  562. plants.abort();
  563. stop_time = millis();
  564. if (state == fillnwater_tank_run) {
  565. auto wl = plants.getWaterlevel();
  566. if ((wl != Plants::empty) && (wl != Plants::invalid)) {
  567. for (int i = 0; i < plants.countPlants(); i++) {
  568. if (selected_plants.isSet(i)) {
  569. plants.startPlant(i);
  570. }
  571. }
  572. selected_time = MAX_AUTO_PLANT_RUNTIME;
  573. start_time = millis();
  574. switch_to(fillnwater_plant_run);
  575. } else if (wl == Plants::empty) {
  576. stop_time = millis();
  577. switch_to(auto_done);
  578. } else if (wl == Plants::invalid) {
  579. error_condition = "Invalid sensor state";
  580. state = auto_mode_a;
  581. switch_to(error);
  582. }
  583. } else {
  584. switch_to(auto_done);
  585. }
  586. } else if ((millis() - last_animation_time) >= 500) {
  587. // update animation if needed
  588. last_animation_time = millis();
  589. switch_to(state);
  590. }
  591. // check water level state
  592. auto wl = plants.getWaterlevel();
  593. if (wl == Plants::full) {
  594. plants.abort();
  595. stop_time = millis();
  596. if (state == fillnwater_tank_run) {
  597. auto wl = plants.getWaterlevel();
  598. if ((wl != Plants::empty) && (wl != Plants::invalid)) {
  599. for (int i = 0; i < plants.countPlants(); i++) {
  600. if (selected_plants.isSet(i)) {
  601. plants.startPlant(i);
  602. }
  603. }
  604. selected_time = MAX_AUTO_PLANT_RUNTIME;
  605. start_time = millis();
  606. switch_to(fillnwater_plant_run);
  607. } else if (wl == Plants::empty) {
  608. stop_time = millis();
  609. switch_to(auto_mode_a);
  610. } else if (wl == Plants::invalid) {
  611. error_condition = "Invalid sensor state";
  612. state = auto_mode_a;
  613. switch_to(error);
  614. }
  615. } else {
  616. switch_to(auto_done);
  617. }
  618. } else if (wl == Plants::invalid) {
  619. plants.abort();
  620. error_condition = "Invalid sensor state";
  621. state = auto_mode_a;
  622. switch_to(error);
  623. }
  624. }
  625. if ((state == auto_plant_run) || (state == fillnwater_plant_run)) {
  626. unsigned long runtime = millis() - start_time;
  627. if ((runtime / 1000UL) >= selected_time) {
  628. // stop if timeout has been reached
  629. plants.abort();
  630. stop_time = millis();
  631. switch_to(auto_done);
  632. } else if ((millis() - last_animation_time) >= 500) {
  633. // update animation if needed
  634. last_animation_time = millis();
  635. switch_to(state);
  636. }
  637. // check water level state
  638. auto wl = plants.getWaterlevel();
  639. if (wl == Plants::empty) {
  640. plants.abort();
  641. stop_time = millis();
  642. switch_to(auto_done);
  643. } else if (wl == Plants::invalid) {
  644. plants.abort();
  645. error_condition = "Invalid sensor state";
  646. state = auto_mode_a;
  647. switch_to(error);
  648. }
  649. }
  650. if ((state == menu_a) || (state == menu_b) || (state == automation_mode)
  651. || (state == auto_mode_a) || (state == auto_mode_b)
  652. || (state == auto_fert) || (state == auto_done)
  653. || (state == auto_plant) || (state == fillnwater_plant)
  654. || (state == menu_pumps) || (state == menu_pumps_time)
  655. || (state == menu_pumps_go) || (state == menu_pumps_done)
  656. || (state == menu_valves) || (state == menu_valves_time)
  657. || (state == menu_valves_go) || (state == menu_valves_done)) {
  658. unsigned long runtime = millis() - into_state_time;
  659. if (runtime >= BACK_TO_IDLE_TIMEOUT) {
  660. debug.print("Idle timeout reached in state ");
  661. debug.println(state_names[state]);
  662. switch_to(init);
  663. }
  664. }
  665. }
  666. void Statemachine::switch_to(States s) {
  667. old_state = state;
  668. state = s;
  669. into_state_time = millis();
  670. if (old_state != state) {
  671. // don't spam log with every animation state "change"
  672. debug.print("switch_to ");
  673. debug.print(state_names[old_state]);
  674. debug.print(" --> ");
  675. debug.println(state_names[state]);
  676. }
  677. if (s == init) {
  678. String a = String("- Giess-o-mat V") + FIRMWARE_VERSION + String(" -");
  679. print(a.c_str(),
  680. "Usage: Enter number",
  681. "* Delete prev. digit",
  682. "# Execute input num.",
  683. -1);
  684. } else if (s == menu_a) {
  685. print("----- Menu 1/2 -----",
  686. "1: Manual Operation",
  687. "2: Automation",
  688. "#: Go to page 2/2...",
  689. -1);
  690. } else if (s == menu_b) {
  691. print("----- Menu 2/2 -----",
  692. "3: Fertilizer pumps",
  693. "4: Outlet valves",
  694. "#: Go to page 1/2...",
  695. -1);
  696. } else if (state == automation_mode) {
  697. // TODO
  698. print("---- Automation ----",
  699. "TODO NOT IMPLEMENTED",
  700. "TODO NOT IMPLEMENTED",
  701. "TODO NOT IMPLEMENTED",
  702. -1);
  703. } else if (s == auto_mode_a) {
  704. print("---- Manual 1/2 ----",
  705. "1: Add Fertilizer",
  706. "2: Fill 'n' Water",
  707. "#: Go to page 2/2...",
  708. -1);
  709. } else if (s == auto_mode_b) {
  710. print("---- Manual 2/2 ----",
  711. "3: Fill Reservoir",
  712. "4: Water a plant",
  713. "#: Go to page 1/2...",
  714. -1);
  715. } else if (s == auto_fert) {
  716. print("---- Fertilizer ----",
  717. "1: Vegetation Phase",
  718. "2: Bloom Phase",
  719. "3: Special",
  720. -1);
  721. } else if (s == auto_fert_run) {
  722. unsigned long runtime = millis() - start_time;
  723. String a = String("Time: ") + String(runtime / 1000UL) + String("s / ") + String(selected_time) + String('s');
  724. unsigned long anim = runtime * 20UL / (selected_time * 1000UL);
  725. String b;
  726. for (unsigned long i = 0; i < anim; i++) {
  727. b += '#';
  728. }
  729. print("---- Dispensing ----",
  730. a.c_str(),
  731. b.c_str(),
  732. "Hit any key to stop!",
  733. -1);
  734. } else if ((s == auto_tank_run) || (s == fillnwater_tank_run)) {
  735. unsigned long runtime = millis() - start_time;
  736. String a = String("Time: ") + String(runtime / 1000UL) + String("s / ") + String(selected_time) + String('s');
  737. unsigned long anim = runtime * 20UL / (selected_time * 1000UL);
  738. String b;
  739. for (unsigned long i = 0; i < anim; i++) {
  740. b += '#';
  741. }
  742. print("--- Filling Tank ---",
  743. a.c_str(),
  744. b.c_str(),
  745. "Hit any key to stop!",
  746. -1);
  747. } else if ((s == auto_plant) || (s == fillnwater_plant)) {
  748. String a = String("(Input 1 to ") + String(plants.countPlants()) + String(")");
  749. print("--- Select Plant ---",
  750. "Leave empty if done!",
  751. a.c_str(),
  752. "Plant: ",
  753. 3);
  754. } else if ((s == auto_plant_run) || (s == fillnwater_plant_run)) {
  755. unsigned long runtime = millis() - start_time;
  756. String a = String("Time: ") + String(runtime / 1000UL) + String("s / ") + String(selected_time) + String('s');
  757. unsigned long anim = runtime * 20UL / (selected_time * 1000UL);
  758. String b;
  759. for (unsigned long i = 0; i < anim; i++) {
  760. b += '#';
  761. }
  762. print("----- Watering -----",
  763. a.c_str(),
  764. b.c_str(),
  765. "Hit any key to stop!",
  766. -1);
  767. } else if (s == auto_done) {
  768. String a = String("after ") + String((stop_time - start_time) / 1000UL) + String("s.");
  769. print("------- Done -------",
  770. "Dispensing finished",
  771. a.c_str(),
  772. "Hit any key for menu",
  773. -1);
  774. #if defined(PLATFORM_ESP)
  775. unsigned long runtime = stop_time - start_time;
  776. if ((old_state == auto_plant_run) || (old_state == fillnwater_plant_run)) {
  777. for (int i = 0; i < plants.countPlants(); i++) {
  778. if (selected_plants.isSet(i)) {
  779. bool success = wifi_write_database(runtime / 1000, "plant", i + 1);
  780. if (!success) {
  781. debug.print("Error writing to InfluxDB ");
  782. debug.print(INFLUXDB_HOST);
  783. debug.print(":");
  784. debug.print(INFLUXDB_PORT);
  785. debug.print("/");
  786. debug.print(INFLUXDB_DATABASE);
  787. debug.println("/plant");
  788. }
  789. }
  790. }
  791. } else if (old_state == auto_fert_run) {
  792. bool success = wifi_write_database(runtime / 1000, "fertilizer", selected_id);
  793. if (!success) {
  794. debug.print("Error writing to InfluxDB ");
  795. debug.print(INFLUXDB_HOST);
  796. debug.print(":");
  797. debug.print(INFLUXDB_PORT);
  798. debug.print("/");
  799. debug.print(INFLUXDB_DATABASE);
  800. debug.println("/fertilizer");
  801. }
  802. }
  803. #endif // PLATFORM_ESP
  804. } else if (s == menu_pumps) {
  805. String a = String("(Input 1 to ") + String(plants.countFertilizers()) + String(")");
  806. print("------- Pump -------",
  807. "Please select pump",
  808. a.c_str(),
  809. "Pump: ",
  810. 3);
  811. } else if (s == menu_pumps_time) {
  812. String header = String("------ Pump ") + String(selected_id) + String(" ------");
  813. print(header.c_str(),
  814. "Please set runtime",
  815. "(Input in seconds)",
  816. "Runtime: ",
  817. 3);
  818. } else if (s == menu_pumps_go) {
  819. String a = String("Pump No. ") + String(selected_id);
  820. String b = String("Runtime ") + String(selected_time) + String('s');
  821. print("----- Confirm? -----",
  822. a.c_str(),
  823. b.c_str(),
  824. " # Confirm",
  825. -1);
  826. } else if (s == menu_pumps_run) {
  827. unsigned long runtime = millis() - start_time;
  828. String a = String("Time: ") + String(runtime / 1000UL) + String("s / ") + String(selected_time) + String('s');
  829. unsigned long anim = runtime * 20UL / (selected_time * 1000UL);
  830. String b;
  831. for (unsigned long i = 0; i < anim; i++) {
  832. b += '#';
  833. }
  834. print("---- Dispensing ----",
  835. a.c_str(),
  836. b.c_str(),
  837. "Hit any key to stop!",
  838. -1);
  839. } else if (s == menu_pumps_done) {
  840. String a = String("after ") + String((stop_time - start_time) / 1000UL) + String("s.");
  841. print("------- Done -------",
  842. "Dispensing finished",
  843. a.c_str(),
  844. "Hit any key for menu",
  845. -1);
  846. #if defined(PLATFORM_ESP)
  847. unsigned long runtime = stop_time - start_time;
  848. bool success = wifi_write_database(runtime / 1000, "fertilizer", selected_id);
  849. if (!success) {
  850. debug.print("Error writing to InfluxDB ");
  851. debug.print(INFLUXDB_HOST);
  852. debug.print(":");
  853. debug.print(INFLUXDB_PORT);
  854. debug.print("/");
  855. debug.print(INFLUXDB_DATABASE);
  856. debug.println("/fertilizer");
  857. }
  858. #endif // PLATFORM_ESP
  859. } else if (s == menu_valves) {
  860. String a = String("(Input 1 to ") + String(plants.countPlants() + 1) + String(")");
  861. print("------ Valves ------",
  862. "Please select valve",
  863. a.c_str(),
  864. "Valve: ",
  865. 3);
  866. } else if (s == menu_valves_time) {
  867. String header = String("----- Valve ") + String(selected_id) + String(" -----");
  868. print(header.c_str(),
  869. "Please set runtime",
  870. "(Input in seconds)",
  871. "Runtime: ",
  872. 3);
  873. } else if (s == menu_valves_go) {
  874. String a = String("Valve No. ") + String(selected_id);
  875. String b = String("Runtime ") + String(selected_time) + String('s');
  876. print("----- Confirm? -----",
  877. a.c_str(),
  878. b.c_str(),
  879. " # Confirm",
  880. -1);
  881. } else if (s == menu_valves_run) {
  882. unsigned long runtime = millis() - start_time;
  883. String a = String("Time: ") + String(runtime / 1000UL) + String("s / ") + String(selected_time) + String('s');
  884. unsigned long anim = runtime * 20UL / (selected_time * 1000UL);
  885. String b;
  886. for (unsigned long i = 0; i <= anim; i++) {
  887. b += '#';
  888. }
  889. print("---- Dispensing ----",
  890. a.c_str(),
  891. b.c_str(),
  892. "Hit any key to stop!",
  893. -1);
  894. } else if (s == menu_valves_done) {
  895. String a = String("after ") + String((stop_time - start_time) / 1000UL) + String("s.");
  896. print("------- Done -------",
  897. "Dispensing finished",
  898. a.c_str(),
  899. "Hit any key for menu",
  900. -1);
  901. #if defined(PLATFORM_ESP)
  902. unsigned long runtime = stop_time - start_time;
  903. if (selected_id <= plants.countPlants()) {
  904. bool success = wifi_write_database(runtime / 1000, "plant", selected_id);
  905. if (!success) {
  906. debug.print("Error writing to InfluxDB ");
  907. debug.print(INFLUXDB_HOST);
  908. debug.print(":");
  909. debug.print(INFLUXDB_PORT);
  910. debug.print("/");
  911. debug.print(INFLUXDB_DATABASE);
  912. debug.println("/plant");
  913. }
  914. }
  915. #endif // PLATFORM_ESP
  916. } else if (s == error) {
  917. print("------ Error! ------",
  918. "There is a problem:",
  919. error_condition.c_str(),
  920. " Press any key...",
  921. -1);
  922. } else {
  923. debug.print("Invalid state ");
  924. debug.println(s);
  925. }
  926. }