426 lines
10 KiB
C
426 lines
10 KiB
C
#include "app_machine.h"
|
|
#include "app_process.h"
|
|
#include "hal_motor.h"
|
|
#include "hal_temp.h"
|
|
#include "hal_audio.h"
|
|
|
|
#include <string.h>
|
|
|
|
#define AGITATE_RPM 60u
|
|
|
|
static machine_state_t s_state;
|
|
static uint8_t s_proc;
|
|
static uint8_t s_step;
|
|
static uint32_t s_remaining_ms;
|
|
static uint32_t s_deadline_ms;
|
|
static bool s_have_deadline;
|
|
static bool s_resume_pending;
|
|
static bool s_auto_advance;
|
|
static ui_evt_t s_q[UI_EVT_QUEUE_LEN];
|
|
static uint8_t s_q_head;
|
|
static uint8_t s_q_tail;
|
|
static uint8_t s_q_count;
|
|
|
|
static void emit(ui_evt_t ev)
|
|
{
|
|
if (s_q_count == UI_EVT_QUEUE_LEN) {
|
|
s_q_head = (uint8_t)((s_q_head + 1u) % UI_EVT_QUEUE_LEN);
|
|
s_q_count--;
|
|
}
|
|
s_q[s_q_tail] = ev;
|
|
s_q_tail = (uint8_t)((s_q_tail + 1u) % UI_EVT_QUEUE_LEN);
|
|
s_q_count++;
|
|
}
|
|
|
|
static void fill_common(ui_evt_t *ev)
|
|
{
|
|
const process_def_t *p = app_process_get(s_proc);
|
|
memset(ev, 0, sizeof(*ev));
|
|
ev->process_id = s_proc;
|
|
ev->step_index = s_step;
|
|
ev->step_count = (p != NULL) ? p->step_count : 0;
|
|
ev->time_s = app_process_time_s(s_proc, s_step);
|
|
ev->remaining_ms = s_remaining_ms;
|
|
if (p != NULL && s_step < p->step_count) {
|
|
ev->cw_revs = p->steps[s_step].cw_revs;
|
|
ev->ccw_revs = p->steps[s_step].ccw_revs;
|
|
ev->temp_min_c = p->steps[s_step].temp_min_c;
|
|
ev->temp_pref_c = p->steps[s_step].temp_pref_c;
|
|
ev->temp_max_c = p->steps[s_step].temp_max_c;
|
|
}
|
|
}
|
|
|
|
static void emit_id(ui_evt_id_t id)
|
|
{
|
|
ui_evt_t ev;
|
|
fill_common(&ev);
|
|
ev.id = id;
|
|
emit(ev);
|
|
}
|
|
|
|
static void emit_fault(int16_t code)
|
|
{
|
|
ui_evt_t ev;
|
|
fill_common(&ev);
|
|
ev.id = EVT_FAULT;
|
|
ev.fault_code = code;
|
|
emit(ev);
|
|
}
|
|
|
|
static void apply_step_index(uint8_t step)
|
|
{
|
|
const process_def_t *p = app_process_get(s_proc);
|
|
if (p == NULL || p->step_count == 0) {
|
|
return;
|
|
}
|
|
if (step >= p->step_count) {
|
|
step = (uint8_t)(p->step_count - 1u);
|
|
}
|
|
s_step = step;
|
|
}
|
|
|
|
static void replace_session_time_s(uint16_t time_s)
|
|
{
|
|
uint16_t cur = app_process_time_s(s_proc, s_step);
|
|
int32_t left = (int32_t)time_s - (int32_t)cur;
|
|
while (left != 0) {
|
|
int8_t chunk = (left > 127) ? 127 : (left < -128) ? -128 : (int8_t)left;
|
|
if (app_process_adjust_time(s_proc, s_step, chunk) != ESP_OK) {
|
|
break;
|
|
}
|
|
uint16_t now = app_process_time_s(s_proc, s_step);
|
|
int32_t next_left = (int32_t)time_s - (int32_t)now;
|
|
if (next_left == left) {
|
|
break;
|
|
}
|
|
left = next_left;
|
|
}
|
|
}
|
|
|
|
static void go_step_select(void)
|
|
{
|
|
s_state = ST_STEP_SELECT;
|
|
s_have_deadline = false;
|
|
s_resume_pending = false;
|
|
emit_id(EVT_STEP_VIEW);
|
|
}
|
|
|
|
static void go_idle(void)
|
|
{
|
|
s_state = ST_IDLE;
|
|
s_have_deadline = false;
|
|
s_resume_pending = false;
|
|
s_remaining_ms = 0;
|
|
emit_id(EVT_PROCESS_IDLE);
|
|
}
|
|
|
|
static void enter_armed(void)
|
|
{
|
|
s_state = ST_ARMED;
|
|
s_have_deadline = false;
|
|
s_resume_pending = false;
|
|
emit_id(EVT_STEP_ARMED);
|
|
}
|
|
|
|
static void start_running(uint32_t now_ms)
|
|
{
|
|
const process_def_t *p = app_process_get(s_proc);
|
|
uint16_t t_s = app_process_time_s(s_proc, s_step);
|
|
s_remaining_ms = (uint32_t)t_s * 1000u;
|
|
s_deadline_ms = now_ms + s_remaining_ms;
|
|
s_have_deadline = true;
|
|
s_resume_pending = false;
|
|
|
|
if (t_s == 0 || p == NULL) {
|
|
hal_motor_enable(false);
|
|
} else {
|
|
(void)hal_motor_agitate_start(p->steps[s_step].cw_revs,
|
|
p->steps[s_step].ccw_revs,
|
|
AGITATE_RPM);
|
|
hal_motor_enable(true);
|
|
}
|
|
s_state = ST_RUNNING;
|
|
emit_id(EVT_STEP_STARTED);
|
|
}
|
|
|
|
static void complete_step(void)
|
|
{
|
|
hal_motor_agitate_stop();
|
|
hal_motor_enable(false);
|
|
s_remaining_ms = 0;
|
|
s_have_deadline = false;
|
|
s_resume_pending = false;
|
|
s_state = ST_COMPLETE;
|
|
hal_audio_alarm_complete();
|
|
emit_id(EVT_STEP_COMPLETE);
|
|
}
|
|
|
|
static void maybe_auto_advance(uint32_t now_ms)
|
|
{
|
|
(void)now_ms;
|
|
if (!s_auto_advance) {
|
|
return;
|
|
}
|
|
const process_def_t *p = app_process_get(s_proc);
|
|
if (p == NULL) {
|
|
go_idle();
|
|
return;
|
|
}
|
|
if ((uint16_t)s_step + 1u < p->step_count) {
|
|
s_step++;
|
|
enter_armed();
|
|
} else {
|
|
go_idle();
|
|
}
|
|
}
|
|
|
|
static void arm_or_next_from_complete(void)
|
|
{
|
|
const process_def_t *p = app_process_get(s_proc);
|
|
if (p == NULL) {
|
|
go_idle();
|
|
return;
|
|
}
|
|
if ((uint16_t)s_step + 1u < p->step_count) {
|
|
s_step++;
|
|
enter_armed();
|
|
} else {
|
|
go_idle();
|
|
}
|
|
}
|
|
|
|
void app_machine_init(void)
|
|
{
|
|
s_state = ST_IDLE;
|
|
s_proc = 0;
|
|
s_step = 0;
|
|
s_remaining_ms = 0;
|
|
s_deadline_ms = 0;
|
|
s_have_deadline = false;
|
|
s_resume_pending = false;
|
|
s_auto_advance = false;
|
|
s_q_head = 0;
|
|
s_q_tail = 0;
|
|
s_q_count = 0;
|
|
app_process_init();
|
|
hal_motor_init();
|
|
hal_temp_init();
|
|
hal_audio_init();
|
|
}
|
|
|
|
machine_state_t app_machine_state(void)
|
|
{
|
|
return s_state;
|
|
}
|
|
|
|
uint8_t app_machine_process_id(void)
|
|
{
|
|
return s_proc;
|
|
}
|
|
|
|
uint8_t app_machine_step_index(void)
|
|
{
|
|
return s_step;
|
|
}
|
|
|
|
uint32_t app_machine_remaining_ms(void)
|
|
{
|
|
return s_remaining_ms;
|
|
}
|
|
|
|
int app_machine_last_event(ui_evt_t *out)
|
|
{
|
|
if (s_q_count == 0 || out == NULL) {
|
|
return 0;
|
|
}
|
|
*out = s_q[s_q_head];
|
|
s_q_head = (uint8_t)((s_q_head + 1u) % UI_EVT_QUEUE_LEN);
|
|
s_q_count--;
|
|
return 1;
|
|
}
|
|
|
|
void app_machine_on_temp(float c, bool ok)
|
|
{
|
|
ui_evt_t ev;
|
|
fill_common(&ev);
|
|
ev.id = EVT_TEMP_UPDATED;
|
|
ev.temp_c = c;
|
|
ev.temp_ok = ok;
|
|
emit(ev);
|
|
}
|
|
|
|
void app_machine_tick(uint32_t now_ms)
|
|
{
|
|
if (s_state == ST_RUNNING && s_resume_pending) {
|
|
s_deadline_ms = now_ms + s_remaining_ms;
|
|
s_have_deadline = true;
|
|
s_resume_pending = false;
|
|
}
|
|
|
|
if (s_state == ST_RUNNING && s_have_deadline) {
|
|
if (now_ms >= s_deadline_ms) {
|
|
s_remaining_ms = 0;
|
|
complete_step();
|
|
maybe_auto_advance(now_ms);
|
|
return;
|
|
}
|
|
s_remaining_ms = s_deadline_ms - now_ms;
|
|
emit_id(EVT_STEP_PROGRESS);
|
|
}
|
|
}
|
|
|
|
void app_machine_handle_cmd(const ui_cmd_t *cmd)
|
|
{
|
|
if (cmd == NULL) {
|
|
return;
|
|
}
|
|
|
|
switch (cmd->id) {
|
|
case CMD_SELECT_PROCESS:
|
|
if (app_process_get(cmd->process_id) == NULL) {
|
|
emit_fault(1);
|
|
return;
|
|
}
|
|
s_proc = cmd->process_id;
|
|
s_step = 0;
|
|
s_remaining_ms = 0;
|
|
s_state = ST_STEP_SELECT;
|
|
emit_id(EVT_PROCESS_SELECTED);
|
|
emit_id(EVT_STEP_VIEW);
|
|
return;
|
|
|
|
case CMD_BROWSE_STEP:
|
|
if (s_state != ST_STEP_SELECT) {
|
|
return;
|
|
}
|
|
{
|
|
const process_def_t *p = app_process_get(s_proc);
|
|
if (p == NULL || p->step_count == 0) {
|
|
return;
|
|
}
|
|
int32_t next = (int32_t)s_step + (int32_t)cmd->step_delta;
|
|
if (next < 0) {
|
|
next = 0;
|
|
}
|
|
if (next >= (int32_t)p->step_count) {
|
|
next = (int32_t)p->step_count - 1;
|
|
}
|
|
s_step = (uint8_t)next;
|
|
emit_id(EVT_STEP_VIEW);
|
|
}
|
|
return;
|
|
|
|
case CMD_ADJUST_STEP_TIME:
|
|
if (s_state != ST_STEP_SELECT) {
|
|
return;
|
|
}
|
|
if (app_process_adjust_time(s_proc, s_step, cmd->time_delta_s) == ESP_OK) {
|
|
emit_id(EVT_STEP_VIEW);
|
|
} else {
|
|
emit_fault(1);
|
|
}
|
|
return;
|
|
|
|
case CMD_ARM_STEP:
|
|
if (s_state == ST_STEP_SELECT) {
|
|
apply_step_index(cmd->step_index);
|
|
enter_armed();
|
|
} else if (s_state == ST_COMPLETE) {
|
|
arm_or_next_from_complete();
|
|
}
|
|
return;
|
|
|
|
case CMD_START_STEP:
|
|
if (s_state == ST_STEP_SELECT) {
|
|
apply_step_index(cmd->step_index);
|
|
enter_armed();
|
|
} else if (s_state == ST_ARMED) {
|
|
apply_step_index(cmd->step_index);
|
|
start_running(0);
|
|
} else if (s_state == ST_COMPLETE) {
|
|
arm_or_next_from_complete();
|
|
}
|
|
return;
|
|
|
|
case CMD_CANCEL_ARMED:
|
|
if (s_state == ST_ARMED) {
|
|
hal_motor_enable(false);
|
|
go_step_select();
|
|
}
|
|
return;
|
|
|
|
case CMD_STOP:
|
|
if (s_state == ST_IDLE) {
|
|
return;
|
|
}
|
|
if (s_state == ST_RUNNING) {
|
|
/* request_stop before any other work */
|
|
hal_motor_request_stop();
|
|
hal_motor_enable(false);
|
|
if (s_have_deadline && !s_resume_pending) {
|
|
/* remaining already updated by last tick; if never ticked, keep start remaining */
|
|
}
|
|
hal_audio_beep_short();
|
|
s_state = ST_STOPPED;
|
|
s_have_deadline = false;
|
|
s_resume_pending = false;
|
|
emit_id(EVT_STEP_STOPPED);
|
|
return;
|
|
}
|
|
if (s_state == ST_ARMED) {
|
|
hal_motor_enable(false);
|
|
go_step_select();
|
|
return;
|
|
}
|
|
if (s_state == ST_STOPPED) {
|
|
{
|
|
uint16_t secs = (uint16_t)((s_remaining_ms + 999u) / 1000u);
|
|
replace_session_time_s(secs);
|
|
}
|
|
hal_motor_enable(false);
|
|
go_step_select();
|
|
return;
|
|
}
|
|
if (s_state == ST_COMPLETE) {
|
|
hal_audio_alarm_cancel();
|
|
hal_audio_beep_short();
|
|
go_step_select();
|
|
return;
|
|
}
|
|
return;
|
|
|
|
case CMD_RESUME:
|
|
if (s_state == ST_STOPPED) {
|
|
const process_def_t *p = app_process_get(s_proc);
|
|
hal_audio_beep_short();
|
|
s_resume_pending = true;
|
|
s_have_deadline = false;
|
|
if (p != NULL && s_remaining_ms > 0) {
|
|
(void)hal_motor_agitate_start(p->steps[s_step].cw_revs,
|
|
p->steps[s_step].ccw_revs,
|
|
AGITATE_RPM);
|
|
hal_motor_enable(true);
|
|
} else {
|
|
hal_motor_enable(false);
|
|
}
|
|
s_state = ST_RUNNING;
|
|
emit_id(EVT_STEP_RESUMED);
|
|
}
|
|
return;
|
|
|
|
case CMD_RETURN_TO_STEP_SELECT:
|
|
if (s_state == ST_STOPPED) {
|
|
uint16_t secs = (uint16_t)((s_remaining_ms + 999u) / 1000u);
|
|
replace_session_time_s(secs);
|
|
hal_motor_enable(false);
|
|
go_step_select();
|
|
} else if (s_state == ST_COMPLETE) {
|
|
go_step_select();
|
|
}
|
|
return;
|
|
|
|
default:
|
|
return;
|
|
}
|
|
}
|