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

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