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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656
  1. #include "Plants.h"
  2. #include "Statemachine.h"
  3. #include "config.h"
  4. Statemachine::DigitBuffer::DigitBuffer(int _size) {
  5. size = _size;
  6. pos = 0;
  7. digits = new int[size];
  8. }
  9. Statemachine::DigitBuffer::~DigitBuffer() {
  10. delete digits;
  11. }
  12. bool Statemachine::DigitBuffer::spaceLeft(void) {
  13. return (pos < size);
  14. }
  15. bool Statemachine::DigitBuffer::hasDigits(void) {
  16. return (pos > 0);
  17. }
  18. int Statemachine::DigitBuffer::countDigits(void) {
  19. return pos;
  20. }
  21. void Statemachine::DigitBuffer::addDigit(int d) {
  22. if (spaceLeft()) {
  23. digits[pos] = d;
  24. pos++;
  25. }
  26. }
  27. void Statemachine::DigitBuffer::removeDigit(void) {
  28. if (hasDigits()) {
  29. pos--;
  30. }
  31. }
  32. void Statemachine::DigitBuffer::clear(void) {
  33. pos = 0;
  34. }
  35. uint32_t Statemachine::DigitBuffer::getNumber(void) {
  36. uint32_t fact = 1;
  37. uint32_t sum = 0;
  38. for (int i = (pos - 1); i >= 0; i--) {
  39. sum += digits[i] * fact;
  40. fact *= 10;
  41. }
  42. return sum;
  43. }
  44. Statemachine::Statemachine(print_fn _print, backspace_fn _backspace)
  45. : db(7) {
  46. state = init;
  47. old_state = init;
  48. print = _print;
  49. backspace = _backspace;
  50. selected_id = 0;
  51. selected_time = 0;
  52. start_time = 0;
  53. stop_time = 0;
  54. last_animation_time = 0;
  55. error_condition = "";
  56. }
  57. void Statemachine::begin(void) {
  58. switch_to(init);
  59. }
  60. void Statemachine::input(int n) {
  61. if (state == init) {
  62. switch_to(menu);
  63. } else if (state == menu) {
  64. if (n == 1) {
  65. switch_to(menu_auto);
  66. } else if (n == 2) {
  67. switch_to(menu_pumps);
  68. } else if (n == 3) {
  69. switch_to(menu_valves);
  70. } else if ((n == -1) || (n == -2)) {
  71. switch_to(init);
  72. }
  73. } else if (state == menu_auto) {
  74. if (n == 1) {
  75. switch_to(menu_auto_fertilizer);
  76. } else if (n == 2) {
  77. auto wl = plants.getWaterlevel();
  78. if ((wl != Plants::full) && (wl != Plants::invalid)) {
  79. plants.openWaterInlet();
  80. selected_id = plants.countPlants() + 1;
  81. selected_time = MAX_TANK_FILL_TIME;
  82. start_time = millis();
  83. switch_to(menu_auto_reservoir_running);
  84. } else if (wl == Plants::full) {
  85. stop_time = millis();
  86. switch_to(menu_auto);
  87. } else if (wl == Plants::invalid) {
  88. error_condition = "Invalid sensor state";
  89. state = menu_auto;
  90. switch_to(error);
  91. }
  92. } else if (n == 3) {
  93. switch_to(menu_auto_plant);
  94. } else if ((n == -1) || (n == -2)) {
  95. switch_to(menu);
  96. }
  97. } else if (state == menu_auto_fertilizer) {
  98. if ((n >= 1) && (n <= 3)) {
  99. auto wl = plants.getWaterlevel();
  100. if ((wl != Plants::full) && (wl != Plants::invalid)) {
  101. plants.startFertilizer(n - 1);
  102. selected_id = n;
  103. selected_time = AUTO_PUMP_RUNTIME;
  104. start_time = millis();
  105. switch_to(menu_auto_fertilizer_running);
  106. } else if (wl == Plants::full) {
  107. stop_time = millis();
  108. switch_to(menu_auto);
  109. } else if (wl == Plants::invalid) {
  110. error_condition = "Invalid sensor state";
  111. state = menu_auto;
  112. switch_to(error);
  113. }
  114. } else if ((n == -1) || (n == -2)) {
  115. switch_to(menu_auto);
  116. }
  117. } else if (state == menu_auto_fertilizer_running) {
  118. plants.abort();
  119. stop_time = millis();
  120. switch_to(menu_auto_done);
  121. } else if (state == menu_auto_reservoir_running) {
  122. plants.abort();
  123. stop_time = millis();
  124. switch_to(menu_auto_done);
  125. } else if (state == menu_auto_plant) {
  126. if (n == -1) {
  127. if (db.hasDigits()) {
  128. backspace();
  129. db.removeDigit();
  130. } else {
  131. switch_to(menu_auto);
  132. }
  133. } else if (n == -2) {
  134. if (!db.hasDigits()) {
  135. return;
  136. }
  137. selected_id = number_input();
  138. if ((selected_id <= 0) || (selected_id > plants.countPlants())) {
  139. error_condition = "Invalid plant ID!";
  140. switch_to(error);
  141. } else {
  142. auto wl = plants.getWaterlevel();
  143. if ((wl != Plants::empty) && (wl != Plants::invalid)) {
  144. plants.startPlant(selected_id - 1);
  145. selected_time = MAX_AUTO_PLANT_RUNTIME;
  146. start_time = millis();
  147. switch_to(menu_auto_plant_running);
  148. } else if (wl == Plants::empty) {
  149. stop_time = millis();
  150. switch_to(menu_auto);
  151. } else if (wl == Plants::invalid) {
  152. error_condition = "Invalid sensor state";
  153. state = menu_auto;
  154. switch_to(error);
  155. }
  156. }
  157. } else {
  158. if (db.spaceLeft()) {
  159. db.addDigit(n);
  160. } else {
  161. backspace();
  162. }
  163. }
  164. } else if (state == menu_auto_plant_running) {
  165. plants.abort();
  166. stop_time = millis();
  167. switch_to(menu_auto_done);
  168. } else if (state == menu_auto_done) {
  169. switch_to(menu_auto);
  170. } else if (state == menu_pumps) {
  171. if (n == -1) {
  172. if (db.hasDigits()) {
  173. backspace();
  174. db.removeDigit();
  175. } else {
  176. switch_to(menu);
  177. }
  178. } else if (n == -2) {
  179. if (!db.hasDigits()) {
  180. return;
  181. }
  182. selected_id = number_input();
  183. if ((selected_id <= 0) || (selected_id > plants.countFertilizers())) {
  184. error_condition = "Invalid pump ID!";
  185. switch_to(error);
  186. } else {
  187. switch_to(menu_pumps_time);
  188. }
  189. } else {
  190. if (db.spaceLeft()) {
  191. db.addDigit(n);
  192. } else {
  193. backspace();
  194. }
  195. }
  196. } else if (state == menu_pumps_time) {
  197. if (n == -1) {
  198. if (db.hasDigits()) {
  199. backspace();
  200. db.removeDigit();
  201. } else {
  202. switch_to(menu_pumps);
  203. }
  204. } else if (n == -2) {
  205. if (!db.hasDigits()) {
  206. return;
  207. }
  208. selected_time = number_input();
  209. if ((selected_time <= 0) || (selected_time > 120)) {
  210. error_condition = "Invalid time range!";
  211. switch_to(error);
  212. } else {
  213. switch_to(menu_pumps_go);
  214. }
  215. } else {
  216. if (db.spaceLeft()) {
  217. db.addDigit(n);
  218. } else {
  219. backspace();
  220. }
  221. }
  222. } else if (state == menu_pumps_go) {
  223. if (n == -2) {
  224. start_time = millis();
  225. last_animation_time = start_time;
  226. auto wl = plants.getWaterlevel();
  227. if ((wl != Plants::full) && (wl != Plants::invalid)) {
  228. plants.startFertilizer(selected_id - 1);
  229. switch_to(menu_pumps_running);
  230. } else if (wl == Plants::full) {
  231. stop_time = millis();
  232. switch_to(menu_pumps_done);
  233. } else if (wl == Plants::invalid) {
  234. error_condition = "Invalid sensor state";
  235. state = menu_pumps;
  236. switch_to(error);
  237. }
  238. } else {
  239. switch_to(menu_pumps_time);
  240. }
  241. } else if (state == menu_pumps_running) {
  242. plants.abort();
  243. stop_time = millis();
  244. switch_to(menu_pumps_done);
  245. } else if (state == menu_pumps_done) {
  246. switch_to(menu);
  247. } else if (state == menu_valves) {
  248. if (n == -1) {
  249. if (db.hasDigits()) {
  250. backspace();
  251. db.removeDigit();
  252. } else {
  253. switch_to(menu);
  254. }
  255. } else if (n == -2) {
  256. if (!db.hasDigits()) {
  257. return;
  258. }
  259. selected_id = number_input();
  260. if ((selected_id <= 0) || (selected_id > (plants.countPlants() + 1))) {
  261. error_condition = "Invalid valve ID!";
  262. switch_to(error);
  263. } else {
  264. switch_to(menu_valves_time);
  265. }
  266. } else {
  267. if (db.spaceLeft()) {
  268. db.addDigit(n);
  269. } else {
  270. backspace();
  271. }
  272. }
  273. } else if (state == menu_valves_time) {
  274. if (n == -1) {
  275. if (db.hasDigits()) {
  276. backspace();
  277. db.removeDigit();
  278. } else {
  279. switch_to(menu_valves);
  280. }
  281. } else if (n == -2) {
  282. if (!db.hasDigits()) {
  283. return;
  284. }
  285. selected_time = number_input();
  286. if ((selected_time <= 0) || (selected_time > 120)) {
  287. error_condition = "Invalid time range!";
  288. switch_to(error);
  289. } else {
  290. switch_to(menu_valves_go);
  291. }
  292. } else {
  293. if (db.spaceLeft()) {
  294. db.addDigit(n);
  295. } else {
  296. backspace();
  297. }
  298. }
  299. } else if (state == menu_valves_go) {
  300. if (n == -2) {
  301. start_time = millis();
  302. last_animation_time = start_time;
  303. auto wl = plants.getWaterlevel();
  304. if ((wl != Plants::full) && (wl != Plants::invalid)) {
  305. if (selected_id >= (plants.countPlants() + 1)) {
  306. plants.openWaterInlet();
  307. } else {
  308. plants.startPlant(selected_id - 1);
  309. }
  310. switch_to(menu_valves_running);
  311. } else if (wl == Plants::full) {
  312. stop_time = millis();
  313. switch_to(menu_valves_done);
  314. } else if (wl == Plants::invalid) {
  315. error_condition = "Invalid sensor state";
  316. state = menu_valves;
  317. switch_to(error);
  318. }
  319. } else {
  320. switch_to(menu_valves_time);
  321. }
  322. } else if (state == menu_valves_running) {
  323. plants.abort();
  324. stop_time = millis();
  325. switch_to(menu_valves_done);
  326. } else if (state == menu_valves_done) {
  327. switch_to(menu);
  328. } else if (state == error) {
  329. if (old_state != error) {
  330. switch_to(old_state);
  331. } else {
  332. switch_to(menu);
  333. }
  334. }
  335. }
  336. uint32_t Statemachine::number_input(void) {
  337. for (int i = 0; i < db.countDigits(); i++) {
  338. backspace();
  339. }
  340. uint32_t n = db.getNumber();
  341. db.clear();
  342. Serial.print("Whole number input: ");
  343. Serial.println(n);
  344. return n;
  345. }
  346. void Statemachine::act(void) {
  347. if ((state == menu_pumps_running) || (state == menu_valves_running)) {
  348. unsigned long runtime = millis() - start_time;
  349. if ((runtime / 1000UL) >= selected_time) {
  350. // stop if timeout has been reached
  351. plants.abort();
  352. stop_time = millis();
  353. switch_to((state == menu_pumps_running) ? menu_pumps_done : menu_valves_done);
  354. } else if ((millis() - last_animation_time) >= 500) {
  355. // update animation if needed
  356. last_animation_time = millis();
  357. switch_to(state);
  358. }
  359. }
  360. if ((state == menu_pumps_running) || ((state == menu_valves_running) && (selected_id == (plants.countPlants() + 1)))) {
  361. // check water level state
  362. auto wl = plants.getWaterlevel();
  363. if (wl == Plants::full) {
  364. plants.abort();
  365. stop_time = millis();
  366. switch_to((state == menu_pumps_running) ? menu_pumps_done : menu_valves_done);
  367. } else if (wl == Plants::invalid) {
  368. plants.abort();
  369. error_condition = "Invalid sensor state";
  370. state = (state == menu_pumps_running) ? menu_pumps : menu_valves;
  371. switch_to(error);
  372. }
  373. }
  374. if ((state == menu_auto_fertilizer_running) || (state == menu_auto_reservoir_running)) {
  375. unsigned long runtime = millis() - start_time;
  376. if ((runtime / 1000UL) >= selected_time) {
  377. // stop if timeout has been reached
  378. plants.abort();
  379. stop_time = millis();
  380. switch_to(menu_auto_done);
  381. } else if ((millis() - last_animation_time) >= 500) {
  382. // update animation if needed
  383. last_animation_time = millis();
  384. switch_to(state);
  385. }
  386. // check water level state
  387. auto wl = plants.getWaterlevel();
  388. if (wl == Plants::full) {
  389. plants.abort();
  390. stop_time = millis();
  391. switch_to(menu_auto_done);
  392. } else if (wl == Plants::invalid) {
  393. plants.abort();
  394. error_condition = "Invalid sensor state";
  395. state = menu_auto;
  396. switch_to(error);
  397. }
  398. }
  399. if (state == menu_auto_plant_running) {
  400. unsigned long runtime = millis() - start_time;
  401. if ((runtime / 1000UL) >= selected_time) {
  402. // stop if timeout has been reached
  403. plants.abort();
  404. stop_time = millis();
  405. switch_to(menu_auto_done);
  406. } else if ((millis() - last_animation_time) >= 500) {
  407. // update animation if needed
  408. last_animation_time = millis();
  409. switch_to(state);
  410. }
  411. // check water level state
  412. auto wl = plants.getWaterlevel();
  413. if (wl == Plants::empty) {
  414. plants.abort();
  415. stop_time = millis();
  416. switch_to(menu_auto_done);
  417. } else if (wl == Plants::invalid) {
  418. plants.abort();
  419. error_condition = "Invalid sensor state";
  420. state = menu_auto;
  421. switch_to(error);
  422. }
  423. }
  424. }
  425. void Statemachine::switch_to(States s) {
  426. old_state = state;
  427. state = s;
  428. if (s == init) {
  429. String a = String("- Giess-o-mat V") + FIRMWARE_VERSION + String(" -");
  430. print(a.c_str(),
  431. "Usage: Enter number",
  432. "* Delete prev. digit",
  433. "# Execute input num.",
  434. -1);
  435. } else if (s == menu) {
  436. print("------- Menu -------",
  437. "1: Automatic program",
  438. "2: Fertilizer pumps",
  439. "3: Outlet valves",
  440. -1);
  441. } else if (s == menu_auto) {
  442. print("------- Auto -------",
  443. "1: Add Fertilizer",
  444. "2: Fill Reservoir",
  445. "3: Water a plant",
  446. -1);
  447. } else if (s == menu_auto_fertilizer) {
  448. print("---- Fertilizer ----",
  449. "1: Vegetation Phase",
  450. "2: Bloom Phase",
  451. "3: Special",
  452. -1);
  453. } else if (s == menu_auto_fertilizer_running) {
  454. unsigned long runtime = millis() - start_time;
  455. String a = String("Runtime: ") + String(runtime / 1000UL) + String("s / ") + String(selected_time) + String('s');
  456. unsigned long anim = runtime * 20UL / (selected_time * 1000UL);
  457. String b;
  458. for (unsigned long i = 0; i < anim; i++) {
  459. b += '#';
  460. }
  461. print("---- Dispensing ----",
  462. a.c_str(),
  463. b.c_str(),
  464. "Hit any key to stop!",
  465. -1);
  466. } else if (s == menu_auto_reservoir_running) {
  467. unsigned long runtime = millis() - start_time;
  468. String a = String("Runtime: ") + String(runtime / 1000UL) + String("s / ") + String(selected_time) + String('s');
  469. unsigned long anim = runtime * 20UL / (selected_time * 1000UL);
  470. String b;
  471. for (unsigned long i = 0; i < anim; i++) {
  472. b += '#';
  473. }
  474. print("--- Filling Tank ---",
  475. a.c_str(),
  476. b.c_str(),
  477. "Hit any key to stop!",
  478. -1);
  479. } else if (s == menu_auto_plant) {
  480. String a = String("(Input 1 to ") + String(plants.countPlants()) + String(")");
  481. print("--- Select Plant ---",
  482. "Which plant to water",
  483. a.c_str(),
  484. "Plant: ",
  485. 3);
  486. } else if (s == menu_auto_plant_running) {
  487. unsigned long runtime = millis() - start_time;
  488. String a = String("Runtime: ") + String(runtime / 1000UL) + String("s / ") + String(selected_time) + String('s');
  489. unsigned long anim = runtime * 20UL / (selected_time * 1000UL);
  490. String b;
  491. for (unsigned long i = 0; i < anim; i++) {
  492. b += '#';
  493. }
  494. print("----- Watering -----",
  495. a.c_str(),
  496. b.c_str(),
  497. "Hit any key to stop!",
  498. -1);
  499. } else if (s == menu_auto_done) {
  500. String a = String("after ") + String((stop_time - start_time) / 1000UL) + String("s.");
  501. print("------- Done -------",
  502. "Dispensing finished",
  503. a.c_str(),
  504. "Hit any key for menu",
  505. -1);
  506. } else if (s == menu_pumps) {
  507. String a = String("(Input 1 to ") + String(plants.countFertilizers()) + String(")");
  508. print("------- Pump -------",
  509. "Please select pump",
  510. a.c_str(),
  511. "Pump: ",
  512. 3);
  513. } else if (s == menu_pumps_time) {
  514. String header = String("------ Pump ") + String(selected_id) + String(" ------");
  515. print(header.c_str(),
  516. "Please set runtime",
  517. "(Input in seconds)",
  518. "Runtime: ",
  519. 3);
  520. } else if (s == menu_pumps_go) {
  521. String a = String("Pump No. ") + String(selected_id);
  522. String b = String("Runtime ") + String(selected_time) + String('s');
  523. print("----- Confirm? -----",
  524. a.c_str(),
  525. b.c_str(),
  526. " # Confirm",
  527. -1);
  528. } else if (s == menu_pumps_running) {
  529. unsigned long runtime = millis() - start_time;
  530. String a = String("Runtime: ") + String(runtime / 1000UL) + String("s / ") + String(selected_time) + String('s');
  531. unsigned long anim = runtime * 20UL / (selected_time * 1000UL);
  532. String b;
  533. for (unsigned long i = 0; i < anim; i++) {
  534. b += '#';
  535. }
  536. print("---- Dispensing ----",
  537. a.c_str(),
  538. b.c_str(),
  539. "Hit any key to stop!",
  540. -1);
  541. } else if (s == menu_pumps_done) {
  542. String a = String("after ") + String((stop_time - start_time) / 1000UL) + String("s.");
  543. print("------- Done -------",
  544. "Dispensing finished",
  545. a.c_str(),
  546. "Hit any key for menu",
  547. -1);
  548. } else if (s == menu_valves) {
  549. String a = String("(Input 1 to ") + String(plants.countPlants() + 1) + String(")");
  550. print("------ Valves ------",
  551. "Please select valve",
  552. a.c_str(),
  553. "Valve: ",
  554. 3);
  555. } else if (s == menu_valves_time) {
  556. String header = String("----- Valve ") + String(selected_id) + String(" -----");
  557. print(header.c_str(),
  558. "Please set runtime",
  559. "(Input in seconds)",
  560. "Runtime: ",
  561. 3);
  562. } else if (s == menu_valves_go) {
  563. String a = String("Valve No. ") + String(selected_id);
  564. String b = String("Runtime ") + String(selected_time) + String('s');
  565. print("----- Confirm? -----",
  566. a.c_str(),
  567. b.c_str(),
  568. " # Confirm",
  569. -1);
  570. } else if (s == menu_valves_running) {
  571. unsigned long runtime = millis() - start_time;
  572. String a = String("Runtime: ") + String(runtime / 1000UL) + String("s / ") + String(selected_time) + String('s');
  573. unsigned long anim = runtime * 20UL / (selected_time * 1000UL);
  574. String b;
  575. for (unsigned long i = 0; i <= anim; i++) {
  576. b += '#';
  577. }
  578. print("---- Dispensing ----",
  579. a.c_str(),
  580. b.c_str(),
  581. "Hit any key to stop!",
  582. -1);
  583. } else if (s == menu_valves_done) {
  584. String a = String("after ") + String((stop_time - start_time) / 1000UL) + String("s.");
  585. print("------- Done -------",
  586. "Dispensing finished",
  587. a.c_str(),
  588. "Hit any key for menu",
  589. -1);
  590. } else if (s == error) {
  591. print("------ Error! ------",
  592. "There is a problem:",
  593. error_condition.c_str(),
  594. " Press any key...",
  595. -1);
  596. }
  597. }