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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  1. #include "Plants.h"
  2. #include "Statemachine.h"
  3. Statemachine::DigitBuffer::DigitBuffer(int _size) {
  4. size = _size;
  5. pos = 0;
  6. digits = new int[size];
  7. }
  8. Statemachine::DigitBuffer::~DigitBuffer() {
  9. delete digits;
  10. }
  11. bool Statemachine::DigitBuffer::spaceLeft(void) {
  12. return (pos < size);
  13. }
  14. bool Statemachine::DigitBuffer::hasDigits(void) {
  15. return (pos > 0);
  16. }
  17. int Statemachine::DigitBuffer::countDigits(void) {
  18. return pos;
  19. }
  20. void Statemachine::DigitBuffer::addDigit(int d) {
  21. if (spaceLeft()) {
  22. digits[pos] = d;
  23. pos++;
  24. }
  25. }
  26. void Statemachine::DigitBuffer::removeDigit(void) {
  27. if (hasDigits()) {
  28. pos--;
  29. }
  30. }
  31. void Statemachine::DigitBuffer::clear(void) {
  32. pos = 0;
  33. }
  34. uint32_t Statemachine::DigitBuffer::getNumber(void) {
  35. uint32_t fact = 1;
  36. uint32_t sum = 0;
  37. for (int i = (pos - 1); i >= 0; i--) {
  38. sum += digits[i] * fact;
  39. fact *= 10;
  40. }
  41. return sum;
  42. }
  43. Statemachine::Statemachine(print_fn _print, backspace_fn _backspace)
  44. : db(7) {
  45. state = init;
  46. old_state = init;
  47. print = _print;
  48. backspace = _backspace;
  49. selected_id = 0;
  50. selected_time = 0;
  51. start_time = 0;
  52. stop_time = 0;
  53. last_animation_time = 0;
  54. error_condition = "";
  55. }
  56. void Statemachine::begin(void) {
  57. switch_to(init);
  58. }
  59. void Statemachine::input(int n) {
  60. if (state == init) {
  61. switch_to(menu);
  62. } else if (state == menu) {
  63. if (n == 1) {
  64. switch_to(menu_auto);
  65. } else if (n == 2) {
  66. switch_to(menu_pumps);
  67. } else if (n == 3) {
  68. switch_to(menu_valves);
  69. } else if ((n == -1) || (n == -2)) {
  70. switch_to(init);
  71. }
  72. } else if (state == menu_auto) {
  73. switch_to(menu);
  74. } else if (state == menu_auto_mode) {
  75. switch_to(menu);
  76. } else if (state == menu_auto_go) {
  77. switch_to(menu);
  78. } else if (state == menu_auto_done) {
  79. switch_to(menu);
  80. } else if (state == menu_pumps) {
  81. if (n == -1) {
  82. if (db.hasDigits()) {
  83. backspace();
  84. db.removeDigit();
  85. } else {
  86. switch_to(menu);
  87. }
  88. } else if (n == -2) {
  89. if (!db.hasDigits()) {
  90. return;
  91. }
  92. selected_id = number_input();
  93. if ((selected_id <= 0) || (selected_id > plants.countFertilizers())) {
  94. error_condition = "Invalid pump ID!";
  95. switch_to(error);
  96. } else {
  97. switch_to(menu_pumps_time);
  98. }
  99. } else {
  100. if (db.spaceLeft()) {
  101. db.addDigit(n);
  102. } else {
  103. backspace();
  104. }
  105. }
  106. } else if (state == menu_pumps_time) {
  107. if (n == -1) {
  108. if (db.hasDigits()) {
  109. backspace();
  110. db.removeDigit();
  111. } else {
  112. switch_to(menu_pumps);
  113. }
  114. } else if (n == -2) {
  115. if (!db.hasDigits()) {
  116. return;
  117. }
  118. selected_time = number_input();
  119. if ((selected_time <= 0) || (selected_time > 120)) {
  120. error_condition = "Invalid time range!";
  121. switch_to(error);
  122. } else {
  123. switch_to(menu_pumps_go);
  124. }
  125. } else {
  126. if (db.spaceLeft()) {
  127. db.addDigit(n);
  128. } else {
  129. backspace();
  130. }
  131. }
  132. } else if (state == menu_pumps_go) {
  133. if (n == -2) {
  134. start_time = millis();
  135. last_animation_time = start_time;
  136. auto wl = plants.getWaterlevel();
  137. if ((wl != Plants::full) && (wl != Plants::invalid)) {
  138. plants.startFertilizer(selected_id - 1);
  139. switch_to(menu_pumps_running);
  140. } else if (wl == Plants::full) {
  141. stop_time = millis();
  142. switch_to(menu_pumps_done);
  143. } else if (wl == Plants::invalid) {
  144. error_condition = "Invalid sensor state";
  145. state = menu_pumps;
  146. switch_to(error);
  147. }
  148. } else {
  149. switch_to(menu_pumps_time);
  150. }
  151. } else if (state == menu_pumps_running) {
  152. plants.abort();
  153. stop_time = millis();
  154. switch_to(menu_pumps_done);
  155. } else if (state == menu_pumps_done) {
  156. switch_to(menu);
  157. } else if (state == menu_valves) {
  158. if (n == -1) {
  159. if (db.hasDigits()) {
  160. backspace();
  161. db.removeDigit();
  162. } else {
  163. switch_to(menu);
  164. }
  165. } else if (n == -2) {
  166. if (!db.hasDigits()) {
  167. return;
  168. }
  169. selected_id = number_input();
  170. if ((selected_id <= 0) || (selected_id > (plants.countPlants() + 1))) {
  171. error_condition = "Invalid valve ID!";
  172. switch_to(error);
  173. } else {
  174. switch_to(menu_valves_time);
  175. }
  176. } else {
  177. if (db.spaceLeft()) {
  178. db.addDigit(n);
  179. } else {
  180. backspace();
  181. }
  182. }
  183. } else if (state == menu_valves_time) {
  184. if (n == -1) {
  185. if (db.hasDigits()) {
  186. backspace();
  187. db.removeDigit();
  188. } else {
  189. switch_to(menu_valves);
  190. }
  191. } else if (n == -2) {
  192. if (!db.hasDigits()) {
  193. return;
  194. }
  195. selected_time = number_input();
  196. if ((selected_time <= 0) || (selected_time > 120)) {
  197. error_condition = "Invalid time range!";
  198. switch_to(error);
  199. } else {
  200. switch_to(menu_valves_go);
  201. }
  202. } else {
  203. if (db.spaceLeft()) {
  204. db.addDigit(n);
  205. } else {
  206. backspace();
  207. }
  208. }
  209. } else if (state == menu_valves_go) {
  210. if (n == -2) {
  211. start_time = millis();
  212. last_animation_time = start_time;
  213. auto wl = plants.getWaterlevel();
  214. if ((wl != Plants::full) && (wl != Plants::invalid)) {
  215. if (selected_id >= (plants.countPlants() + 1)) {
  216. plants.openWaterInlet();
  217. } else {
  218. plants.startPlant(selected_id - 1);
  219. }
  220. switch_to(menu_valves_running);
  221. } else if (wl == Plants::full) {
  222. stop_time = millis();
  223. switch_to(menu_valves_done);
  224. } else if (wl == Plants::invalid) {
  225. error_condition = "Invalid sensor state";
  226. state = menu_valves;
  227. switch_to(error);
  228. }
  229. } else {
  230. switch_to(menu_valves_time);
  231. }
  232. } else if (state == menu_valves_running) {
  233. plants.abort();
  234. stop_time = millis();
  235. switch_to(menu_valves_done);
  236. } else if (state == menu_valves_done) {
  237. switch_to(menu);
  238. } else if (state == error) {
  239. if (old_state != error) {
  240. switch_to(old_state);
  241. } else {
  242. switch_to(menu);
  243. }
  244. }
  245. }
  246. uint32_t Statemachine::number_input(void) {
  247. for (int i = 0; i < db.countDigits(); i++) {
  248. backspace();
  249. }
  250. uint32_t n = db.getNumber();
  251. db.clear();
  252. Serial.print("Whole number input: ");
  253. Serial.println(n);
  254. return n;
  255. }
  256. void Statemachine::act(void) {
  257. if ((state == menu_pumps_running) || (state == menu_valves_running)) {
  258. unsigned long runtime = millis() - start_time;
  259. if ((runtime / 1000UL) >= selected_time) {
  260. // stop if timeout has been reached
  261. plants.abort();
  262. stop_time = millis();
  263. switch_to((state == menu_pumps_running) ? menu_pumps_done : menu_valves_done);
  264. } else if ((millis() - last_animation_time) >= 500) {
  265. // update animation if needed
  266. last_animation_time = millis();
  267. switch_to(state);
  268. }
  269. }
  270. if ((state == menu_pumps_running) || ((state == menu_valves_running) && (selected_id == (plants.countPlants() + 1)))) {
  271. // check water level state
  272. auto wl = plants.getWaterlevel();
  273. if (wl == Plants::full) {
  274. plants.abort();
  275. stop_time = millis();
  276. switch_to((state == menu_pumps_running) ? menu_pumps_done : menu_valves_done);
  277. } else if (wl == Plants::invalid) {
  278. plants.abort();
  279. error_condition = "Invalid sensor state";
  280. state = (state == menu_pumps_running) ? menu_pumps : menu_valves;
  281. switch_to(error);
  282. }
  283. }
  284. }
  285. void Statemachine::switch_to(States s) {
  286. if (s == error) {
  287. old_state = state;
  288. }
  289. state = s;
  290. if (s == init) {
  291. print("- Giess-o-mat V0.1 -",
  292. "Usage: Enter number",
  293. "* Delete prev. digit",
  294. "# Execute input num.",
  295. -1);
  296. } else if (s == menu) {
  297. print("------- Menu -------",
  298. "1: Automatic program",
  299. "2: Fertilizer pumps",
  300. "3: Outlet valves",
  301. -1);
  302. } else if (s == menu_auto) {
  303. print("------- Auto -------",
  304. "",
  305. "TODO not implemented",
  306. "",
  307. -1);
  308. } else if (s == menu_auto_mode) {
  309. print("menu_auto_mode",
  310. "",
  311. "TODO not implemented",
  312. "",
  313. -1);
  314. } else if (s == menu_auto_go) {
  315. print("menu_auto_go",
  316. "",
  317. "TODO not implemented",
  318. "",
  319. -1);
  320. } else if (s == menu_auto_done) {
  321. print("menu_auto_done",
  322. "",
  323. "TODO not implemented",
  324. "",
  325. -1);
  326. } else if (s == menu_pumps) {
  327. String a = String("(Input 1 to ") + String(plants.countFertilizers()) + String(")");
  328. print("------- Pump -------",
  329. "Please select pump",
  330. a.c_str(),
  331. "Pump: ",
  332. 3);
  333. } else if (s == menu_pumps_time) {
  334. String header = String("------ Pump ") + String(selected_id) + String(" ------");
  335. print(header.c_str(),
  336. "Please set runtime",
  337. "(Input in seconds)",
  338. "Runtime: ",
  339. 3);
  340. } else if (s == menu_pumps_go) {
  341. String a = String("Pump No. ") + String(selected_id);
  342. String b = String("Runtime ") + String(selected_time) + String('s');
  343. print("----- Confirm? -----",
  344. a.c_str(),
  345. b.c_str(),
  346. " # Confirm",
  347. -1);
  348. } else if (s == menu_pumps_running) {
  349. unsigned long runtime = millis() - start_time;
  350. String a = String("Runtime: ") + String(runtime / 1000UL) + String("s / ") + String(selected_time) + String('s');
  351. unsigned long anim = runtime * 20UL / (selected_time * 1000UL);
  352. String b;
  353. for (unsigned long i = 0; i < anim; i++) {
  354. b += '#';
  355. }
  356. print("---- Dispensing ----",
  357. a.c_str(),
  358. b.c_str(),
  359. "Hit any key to stop!",
  360. -1);
  361. } else if (s == menu_pumps_done) {
  362. String a = String("after ") + String((stop_time - start_time) / 1000UL) + String("s.");
  363. print("------- Done -------",
  364. "Dispensing finished",
  365. a.c_str(),
  366. "Hit any key for menu",
  367. -1);
  368. } else if (s == menu_valves) {
  369. String a = String("(Input 1 to ") + String(plants.countPlants() + 1) + String(")");
  370. print("------ Valves ------",
  371. "Please select valve",
  372. a.c_str(),
  373. "Valve: ",
  374. 3);
  375. } else if (s == menu_valves_time) {
  376. String header = String("----- Valve ") + String(selected_id) + String(" -----");
  377. print(header.c_str(),
  378. "Please set runtime",
  379. "(Input in seconds)",
  380. "Runtime: ",
  381. 3);
  382. } else if (s == menu_valves_go) {
  383. String a = String("Valve No. ") + String(selected_id);
  384. String b = String("Runtime ") + String(selected_time) + String('s');
  385. print("----- Confirm? -----",
  386. a.c_str(),
  387. b.c_str(),
  388. " # Confirm",
  389. -1);
  390. } else if (s == menu_valves_running) {
  391. unsigned long runtime = millis() - start_time;
  392. String a = String("Runtime: ") + String(runtime / 1000UL) + String("s / ") + String(selected_time) + String('s');
  393. unsigned long anim = runtime * 20UL / (selected_time * 1000UL);
  394. String b;
  395. for (unsigned long i = 0; i <= anim; i++) {
  396. b += '#';
  397. }
  398. print("---- Dispensing ----",
  399. a.c_str(),
  400. b.c_str(),
  401. "Hit any key to stop!",
  402. -1);
  403. } else if (s == menu_valves_done) {
  404. String a = String("after ") + String((stop_time - start_time) / 1000UL) + String("s.");
  405. print("------- Done -------",
  406. "Dispensing finished",
  407. a.c_str(),
  408. "Hit any key for menu",
  409. -1);
  410. } else if (s == error) {
  411. print("------ Error! ------",
  412. "There is a problem:",
  413. error_condition.c_str(),
  414. " Press any key...",
  415. -1);
  416. }
  417. }