S&B Volcano vaporizer remote control with Pi Pico W
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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. /*
  2. * workflow.c
  3. *
  4. * Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
  5. *
  6. * This program 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. * This program 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. * See <http://www.gnu.org/licenses/>.
  17. */
  18. #define WF_CONFIRM_WRITES
  19. #include <stdio.h>
  20. #include "config.h"
  21. #include "log.h"
  22. #include "mem.h"
  23. #include "volcano.h"
  24. #include "workflow.h"
  25. #ifdef WF_CONFIRM_WRITES
  26. #define DO_WHILE(x, y) \
  27. do { \
  28. x; \
  29. } while (y)
  30. #else // WF_CONFIRM_WRITES
  31. #define DO_WHILE(x, y) x
  32. #endif // WF_CONFIRM_WRITES
  33. static enum wf_status status = WF_IDLE;
  34. static uint16_t wf_i = 0;
  35. static uint16_t step = 0;
  36. static uint32_t start_t = 0;
  37. static uint16_t start_val = 0;
  38. static uint16_t curr_val = 0;
  39. static void do_step(void) {
  40. switch (mem_data()->wf[wf_i].steps[step].op) {
  41. case OP_SET_TEMPERATURE:
  42. case OP_WAIT_TEMPERATURE:
  43. debug("workflow temp %.1f C", mem_data()->wf[wf_i].steps[step].val / 10.0);
  44. start_val = volcano_get_current_temp();
  45. DO_WHILE(volcano_set_target_temp(mem_data()->wf[wf_i].steps[step].val),
  46. volcano_get_target_temp() != mem_data()->wf[wf_i].steps[step].val);
  47. break;
  48. case OP_PUMP_TIME:
  49. DO_WHILE(volcano_set_pump_state(true),
  50. !(volcano_get_state() & VOLCANO_STATE_PUMP));
  51. start_t = to_ms_since_boot(get_absolute_time());
  52. start_val = 0;
  53. debug("workflow pump %.3f s", mem_data()->wf[wf_i].steps[step].val / 1000.0);
  54. break;
  55. case OP_WAIT_TIME:
  56. start_t = to_ms_since_boot(get_absolute_time());
  57. start_val = 0;
  58. debug("workflow time %.3f s", mem_data()->wf[wf_i].steps[step].val / 1000.0);
  59. break;
  60. }
  61. curr_val = start_val;
  62. }
  63. uint16_t wf_count(void) {
  64. return mem_data()->wf_count;
  65. }
  66. void wf_move_down(uint16_t index) {
  67. if ((index < 1) || (index >= mem_data()->wf_count)) {
  68. debug("invalid index %d", index);
  69. return;
  70. }
  71. struct workflow tmp = mem_data()->wf[index - 1];
  72. mem_data()->wf[index - 1] = mem_data()->wf[index];
  73. mem_data()->wf[index] = tmp;
  74. }
  75. void wf_move_up(uint16_t index) {
  76. if (index >= (mem_data()->wf_count - 1)) {
  77. debug("invalid index %d", index);
  78. return;
  79. }
  80. struct workflow tmp = mem_data()->wf[index + 1];
  81. mem_data()->wf[index + 1] = mem_data()->wf[index];
  82. mem_data()->wf[index] = tmp;
  83. }
  84. uint16_t wf_steps(uint16_t index) {
  85. if (index >= mem_data()->wf_count) {
  86. debug("invalid index %d", index);
  87. return 0;
  88. }
  89. return mem_data()->wf[index].count;
  90. }
  91. void wf_move_step_down(uint16_t index, uint16_t step_i) {
  92. if (index >= mem_data()->wf_count) {
  93. debug("invalid index %d", index);
  94. return;
  95. }
  96. if ((step_i < 1) || (step_i >= mem_data()->wf[index].count)) {
  97. debug("invalid step %d", step_i);
  98. return;
  99. }
  100. struct wf_step tmp = mem_data()->wf[index].steps[step_i - 1];
  101. mem_data()->wf[index].steps[step_i - 1] = mem_data()->wf[index].steps[step_i];
  102. mem_data()->wf[index].steps[step_i] = tmp;
  103. }
  104. void wf_move_step_up(uint16_t index, uint16_t step_i) {
  105. if (index >= mem_data()->wf_count) {
  106. debug("invalid index %d", index);
  107. return;
  108. }
  109. if (step_i >= (mem_data()->wf[index].count - 1)) {
  110. debug("invalid step %d", step_i);
  111. return;
  112. }
  113. struct wf_step tmp = mem_data()->wf[index].steps[step_i + 1];
  114. mem_data()->wf[index].steps[step_i + 1] = mem_data()->wf[index].steps[step_i];
  115. mem_data()->wf[index].steps[step_i] = tmp;
  116. }
  117. struct wf_step *wf_get_step(uint16_t index, uint16_t step_i) {
  118. if (index >= mem_data()->wf_count) {
  119. debug("invalid index %d", index);
  120. return NULL;
  121. }
  122. return &mem_data()->wf[index].steps[step_i];
  123. }
  124. const char *wf_step_str(struct wf_step *step_p) {
  125. static char buff[20];
  126. switch (step_p->op) {
  127. case OP_SET_TEMPERATURE:
  128. snprintf(buff, sizeof(buff),
  129. "set temp %.1f C", step_p->val / 10.0f);
  130. break;
  131. case OP_WAIT_TEMPERATURE:
  132. snprintf(buff, sizeof(buff),
  133. "wait temp %.1f C", step_p->val / 10.0f);
  134. break;
  135. case OP_WAIT_TIME:
  136. case OP_PUMP_TIME:
  137. snprintf(buff, sizeof(buff),
  138. "%s time %.1f s",
  139. (step_p->op == OP_WAIT_TIME) ? "wait" : "pump",
  140. step_p->val / 1000.0f);
  141. break;
  142. }
  143. return buff;
  144. }
  145. const char *wf_name(uint16_t index) {
  146. if (index >= mem_data()->wf_count) {
  147. debug("invalid index %d", index);
  148. return NULL;
  149. }
  150. return mem_data()->wf[index].name;
  151. }
  152. const char *wf_author(uint16_t index) {
  153. if (index >= mem_data()->wf_count) {
  154. debug("invalid index %d", index);
  155. return NULL;
  156. }
  157. return mem_data()->wf[index].author;
  158. }
  159. struct wf_state wf_status(void) {
  160. struct wf_state s = {
  161. .status = status,
  162. .index = step,
  163. .count = mem_data()->wf[wf_i].count,
  164. .step = &mem_data()->wf[wf_i].steps[step],
  165. .start_val = start_val,
  166. .curr_val = curr_val,
  167. };
  168. return s;
  169. }
  170. void wf_start(uint16_t index) {
  171. if (status != WF_IDLE) {
  172. debug("workflow already running");
  173. return;
  174. }
  175. if (index >= mem_data()->wf_count) {
  176. debug("invalid index %d", index);
  177. return;
  178. }
  179. status = WF_RUNNING;
  180. wf_i = index;
  181. step = 0;
  182. /*
  183. * first turn on heater, then do discovery, to save some time.
  184. * this means we heat for some seconds before changing the setpoint.
  185. * should not be a problem in practice.
  186. */
  187. DO_WHILE(volcano_set_heater_state(true),
  188. !(volcano_get_state() & VOLCANO_STATE_HEATER));
  189. volcano_discover_characteristics(true, false);
  190. do_step();
  191. }
  192. void wf_reset(void) {
  193. status = WF_IDLE;
  194. }
  195. void wf_run(void) {
  196. if (status == WF_IDLE) {
  197. return;
  198. }
  199. bool done = false;
  200. switch (mem_data()->wf[wf_i].steps[step].op) {
  201. case OP_SET_TEMPERATURE:
  202. done = true;
  203. break;
  204. case OP_WAIT_TEMPERATURE: {
  205. uint16_t temp = volcano_get_current_temp();
  206. // volcano does not provide a temperature when cold
  207. if (start_val == 0) {
  208. start_val = temp;
  209. }
  210. curr_val = temp;
  211. done = (temp >= (mem_data()->wf[wf_i].steps[step].val - 5));
  212. break;
  213. }
  214. case OP_PUMP_TIME:
  215. case OP_WAIT_TIME: {
  216. uint32_t now = to_ms_since_boot(get_absolute_time());
  217. uint32_t diff = now - start_t;
  218. curr_val = diff;
  219. done = (diff >= mem_data()->wf[wf_i].steps[step].val);
  220. break;
  221. }
  222. }
  223. if (done) {
  224. if (mem_data()->wf[wf_i].steps[step].op == OP_PUMP_TIME) {
  225. DO_WHILE(volcano_set_pump_state(false),
  226. volcano_get_state() & VOLCANO_STATE_PUMP);
  227. }
  228. step++;
  229. if (step >= mem_data()->wf[wf_i].count) {
  230. status = WF_IDLE;
  231. DO_WHILE(volcano_set_heater_state(false),
  232. volcano_get_state() & VOLCANO_STATE_HEATER);
  233. debug("workflow finished");
  234. } else {
  235. do_step();
  236. }
  237. }
  238. }