/* * workflow.c * * Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * See . */ #define WF_CONFIRM_WRITES #include #include "config.h" #include "log.h" #include "mem.h" #include "volcano.h" #include "workflow.h" #ifdef WF_CONFIRM_WRITES #define DO_WHILE(x, y) \ do { \ x; \ } while (y) #else // WF_CONFIRM_WRITES #define DO_WHILE(x, y) x #endif // WF_CONFIRM_WRITES static enum wf_status status = WF_IDLE; static uint16_t wf_i = 0; static uint16_t step = 0; static uint32_t start_t = 0; static uint16_t start_val = 0; static uint16_t curr_val = 0; static void do_step(void) { switch (mem_data()->wf[wf_i].steps[step].op) { case OP_SET_TEMPERATURE: case OP_WAIT_TEMPERATURE: debug("workflow temp %.1f C", mem_data()->wf[wf_i].steps[step].val / 10.0); start_val = volcano_get_current_temp(); DO_WHILE(volcano_set_target_temp(mem_data()->wf[wf_i].steps[step].val), volcano_get_target_temp() != mem_data()->wf[wf_i].steps[step].val); break; case OP_PUMP_TIME: DO_WHILE(volcano_set_pump_state(true), !(volcano_get_state() & VOLCANO_STATE_PUMP)); start_t = to_ms_since_boot(get_absolute_time()); start_val = 0; debug("workflow pump %.3f s", mem_data()->wf[wf_i].steps[step].val / 1000.0); break; case OP_WAIT_TIME: start_t = to_ms_since_boot(get_absolute_time()); start_val = 0; debug("workflow time %.3f s", mem_data()->wf[wf_i].steps[step].val / 1000.0); break; } curr_val = start_val; } uint16_t wf_count(void) { return mem_data()->wf_count; } void wf_move_down(uint16_t index) { if ((index < 1) || (index >= mem_data()->wf_count)) { debug("invalid index %d", index); return; } struct workflow tmp = mem_data()->wf[index - 1]; mem_data()->wf[index - 1] = mem_data()->wf[index]; mem_data()->wf[index] = tmp; } void wf_move_up(uint16_t index) { if (index >= (mem_data()->wf_count - 1)) { debug("invalid index %d", index); return; } struct workflow tmp = mem_data()->wf[index + 1]; mem_data()->wf[index + 1] = mem_data()->wf[index]; mem_data()->wf[index] = tmp; } uint16_t wf_steps(uint16_t index) { if (index >= mem_data()->wf_count) { debug("invalid index %d", index); return 0; } return mem_data()->wf[index].count; } void wf_move_step_down(uint16_t index, uint16_t step_i) { if (index >= mem_data()->wf_count) { debug("invalid index %d", index); return; } if ((step_i < 1) || (step_i >= mem_data()->wf[index].count)) { debug("invalid step %d", step_i); return; } struct wf_step tmp = mem_data()->wf[index].steps[step_i - 1]; mem_data()->wf[index].steps[step_i - 1] = mem_data()->wf[index].steps[step_i]; mem_data()->wf[index].steps[step_i] = tmp; } void wf_move_step_up(uint16_t index, uint16_t step_i) { if (index >= mem_data()->wf_count) { debug("invalid index %d", index); return; } if (step_i >= (mem_data()->wf[index].count - 1)) { debug("invalid step %d", step_i); return; } struct wf_step tmp = mem_data()->wf[index].steps[step_i + 1]; mem_data()->wf[index].steps[step_i + 1] = mem_data()->wf[index].steps[step_i]; mem_data()->wf[index].steps[step_i] = tmp; } struct wf_step *wf_get_step(uint16_t index, uint16_t step_i) { if (index >= mem_data()->wf_count) { debug("invalid index %d", index); return NULL; } return &mem_data()->wf[index].steps[step_i]; } const char *wf_step_str(struct wf_step *step_p) { static char buff[20]; switch (step_p->op) { case OP_SET_TEMPERATURE: snprintf(buff, sizeof(buff), "set temp %.1f C", step_p->val / 10.0f); break; case OP_WAIT_TEMPERATURE: snprintf(buff, sizeof(buff), "wait temp %.1f C", step_p->val / 10.0f); break; case OP_WAIT_TIME: case OP_PUMP_TIME: snprintf(buff, sizeof(buff), "%s time %.1f s", (step_p->op == OP_WAIT_TIME) ? "wait" : "pump", step_p->val / 1000.0f); break; } return buff; } const char *wf_name(uint16_t index) { if (index >= mem_data()->wf_count) { debug("invalid index %d", index); return NULL; } return mem_data()->wf[index].name; } const char *wf_author(uint16_t index) { if (index >= mem_data()->wf_count) { debug("invalid index %d", index); return NULL; } return mem_data()->wf[index].author; } struct wf_state wf_status(void) { struct wf_state s = { .status = status, .index = step, .count = mem_data()->wf[wf_i].count, .step = &mem_data()->wf[wf_i].steps[step], .start_val = start_val, .curr_val = curr_val, }; return s; } void wf_start(uint16_t index) { if (status != WF_IDLE) { debug("workflow already running"); return; } if (index >= mem_data()->wf_count) { debug("invalid index %d", index); return; } status = WF_RUNNING; wf_i = index; step = 0; /* * first turn on heater, then do discovery, to save some time. * this means we heat for some seconds before changing the setpoint. * should not be a problem in practice. */ DO_WHILE(volcano_set_heater_state(true), !(volcano_get_state() & VOLCANO_STATE_HEATER)); volcano_discover_characteristics(true, false); do_step(); } void wf_reset(void) { status = WF_IDLE; } void wf_run(void) { if (status == WF_IDLE) { return; } bool done = false; switch (mem_data()->wf[wf_i].steps[step].op) { case OP_SET_TEMPERATURE: done = true; break; case OP_WAIT_TEMPERATURE: { uint16_t temp = volcano_get_current_temp(); // volcano does not provide a temperature when cold if (start_val == 0) { start_val = temp; } curr_val = temp; done = (temp >= (mem_data()->wf[wf_i].steps[step].val - 5)); break; } case OP_PUMP_TIME: case OP_WAIT_TIME: { uint32_t now = to_ms_since_boot(get_absolute_time()); uint32_t diff = now - start_t; curr_val = diff; done = (diff >= mem_data()->wf[wf_i].steps[step].val); break; } } if (done) { if (mem_data()->wf[wf_i].steps[step].op == OP_PUMP_TIME) { DO_WHILE(volcano_set_pump_state(false), volcano_get_state() & VOLCANO_STATE_PUMP); } step++; if (step >= mem_data()->wf[wf_i].count) { status = WF_IDLE; DO_WHILE(volcano_set_heater_state(false), volcano_get_state() & VOLCANO_STATE_HEATER); debug("workflow finished"); } else { do_step(); } } }