Files
AutoFilm-ESP32/components/app_machine/app_machine.c

426 lines
10 KiB
C

#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "app_machine.h"
#include "app_process.h"
#include "hal_motor.h"
#include "hal_temp.h"
#include "hal_audio.h"
#include <string.h>
#include <stddef.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 QueueHandle_t s_evtq;
static void emit(ui_evt_t ev)
{
if (s_evtq != NULL) {
xQueueSend(s_evtq, &ev, 0);
}
}
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(void)
{
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_have_deadline = false;
s_resume_pending = true;
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 = true;
if (s_evtq == NULL) {
s_evtq = xQueueCreate(UI_EVT_QUEUE_LEN, sizeof(ui_evt_t));
} else {
xQueueReset(s_evtq);
}
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_evtq == NULL || out == NULL) {
return 0;
}
if (xQueueReceive(s_evtq, out, 0) == pdTRUE) {
return 1;
}
return 0;
}
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();
} 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;
}
}