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

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