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

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