Mark P05 BLOCKED pending P03 HAL and app_machine #6

Merged
gronod merged 4 commits from refactor/esp-idf-modular-ui into develop 2026-09-16 13:30:25 +01:00
15 changed files with 935 additions and 9 deletions
+3
View File
@@ -0,0 +1,3 @@
idf_component_register(SRCS "app_machine.c"
INCLUDE_DIRS "include"
REQUIRES ui_cmd app_process)
+425
View File
@@ -0,0 +1,425 @@
#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;
}
}
@@ -0,0 +1,24 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include "ui_cmd.h"
typedef enum {
ST_IDLE = 0,
ST_STEP_SELECT,
ST_ARMED,
ST_RUNNING,
ST_STOPPED,
ST_COMPLETE
} machine_state_t;
void app_machine_init(void);
machine_state_t app_machine_state(void);
void app_machine_handle_cmd(const ui_cmd_t *cmd);
void app_machine_tick(uint32_t now_ms); /* drive timers; tests call this */
void app_machine_on_temp(float c, bool ok);
int app_machine_last_event(ui_evt_t *out); /* pop 1 event; 0 empty, 1 ok */
uint8_t app_machine_process_id(void);
uint8_t app_machine_step_index(void);
uint32_t app_machine_remaining_ms(void);
+6
View File
@@ -0,0 +1,6 @@
#pragma once
void hal_audio_init(void);
void hal_audio_beep_short(void);
void hal_audio_alarm_complete(void);
void hal_audio_alarm_cancel(void);
+16
View File
@@ -0,0 +1,16 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#ifdef AUTOFILM_HOST
#include "esp_host_compat.h"
#else
#include "esp_err.h"
#endif
void hal_motor_init(void);
void hal_motor_enable(bool on); /* on=false → EN disabled (HIGH on wroom) */
void hal_motor_request_stop(void); /* must be safe to call anytime; sets flag + enable false */
esp_err_t hal_motor_agitate_start(float cw_revs, float ccw_revs, uint32_t rpm);
void hal_motor_agitate_stop(void); /* leave disabled */
bool hal_motor_is_enabled(void);
+10
View File
@@ -0,0 +1,10 @@
#pragma once
#ifdef AUTOFILM_HOST
#include "esp_host_compat.h"
#else
#include "esp_err.h"
#endif
void hal_temp_init(void);
esp_err_t hal_temp_read_c(float *out); /* ESP_FAIL → disconnected */
+4 -2
View File
@@ -35,15 +35,17 @@ If blocked: stop, commit nothing broken, write `BLOCKED:` at top of the phase fi
| P00 | `refactor/P00-docs-lock.md` | Architecture docs already on branch | DONE |
| P01 | `refactor/P01-idf-skeleton.md` | Dual-board IDF project compiles | BLOCKED |
| P02 | `refactor/P02-ui-cmd-process.md` | `ui_cmd` + `app_process` + golden tests | DONE |
| P03 | `refactor/P03-machine-host.md` | `app_machine` + stub HAL + SM tests | TODO |
| P03 | `refactor/P03-machine-host.md` | `app_machine` + stub HAL + SM tests | DONE |
| P04 | `refactor/P04-gitea-ci.md` | `.gitea/workflows/ci.yml` live | DONE |
| P05 | `refactor/P05-hal-wroom-motion.md` | WROOM motor/temp/audio HAL | TODO |
| P05 | `refactor/P05-hal-wroom-motion.md` | WROOM motor/temp/audio HAL | BLOCKED |
| P06 | `refactor/P06-ui-wroom-cutover.md` | 2004 UI + keypad + Stop/Resume cutover | TODO |
| P07 | `refactor/P07-board-s3-ui.md` | JC4827W543 display+GT911+STOP | TODO |
| P08 | `refactor/P08-idf-debt.md` | Replace Arduino drivers (RMT etc.) | TODO |
P04 Notes: merge_bin uses `${{ gitea.sha }}` (not `github.sha`); runner unverified (no Actions run).
P05 Notes: BLOCKED — no app_machine + HAL headers from P03 (branch @ 415c13a). Not implemented.
## Dependency
```
+5 -5
View File
@@ -1,6 +1,6 @@
# P03 — app_machine + stub HAL + state tests
STATUS: TODO
STATUS: DONE
DEPENDS: P02
READ: this file, `docs/megaplans/REFACTOR-MEGAPLAN.md`, `components/ui_cmd/include/ui_cmd.h`
OUT: real GPIO, Arduino, LCD, Gitea yaml, deleting `src/menu.cpp`
@@ -130,7 +130,7 @@ Required cases in `test_machine.c` names:
## DoD
- [ ] STOP path calls `hal_motor_request_stop` before any other work
- [ ] default auto_advance false
- [ ] host tests cover list above
- [ ] STATUS→DONE
- [x] STOP path calls `hal_motor_request_stop` before any other work
- [x] default auto_advance false
- [x] host tests cover list above
- [x] STATUS→DONE
@@ -1,6 +1,18 @@
BLOCKED: P03 artifacts missing on branch refactor/esp-idf-modular-ui @ 415c13a.
Required and absent:
- components/app_machine/ (include/app_machine.h, app_machine.c)
- components/hal_motor/include/hal_motor.h
- components/hal_temp/include/hal_temp.h
- components/hal_audio/include/hal_audio.h
Megaplan table still lists P03 STATUS=TODO. Protocol: P05 requires P03 APIs.
Do not invent or change public HAL/app_machine APIs. Do not implement P01–P04 in this session.
No device HAL, tasks, Arduino-as-component, or main.c cutover landed.
# P05 — WROOM HAL motor / temp / audio
STATUS: TODO
STATUS: BLOCKED
DEPENDS: P03
READ: this file, `include/config.h`, `src/motor.cpp`, `src/temperature.cpp`, `src/sound.cpp`, `components/hal_*/include/*.h`
OUT: rewriting `app_ui` / deleting menus; S3 motor pins; disabling WDT; `vTaskDelete` motor
+25 -1
View File
@@ -3,6 +3,16 @@ project(autofilm_host_tests C)
enable_testing()
set(HOST_INCLUDES
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/../../components/app_process/include
${CMAKE_CURRENT_SOURCE_DIR}/../../components/app_machine/include
${CMAKE_CURRENT_SOURCE_DIR}/../../components/ui_cmd/include
${CMAKE_CURRENT_SOURCE_DIR}/../../components/hal_motor/include
${CMAKE_CURRENT_SOURCE_DIR}/../../components/hal_temp/include
${CMAKE_CURRENT_SOURCE_DIR}/../../components/hal_audio/include
)
add_executable(test_process
test_process.c
main.c
@@ -18,5 +28,19 @@ target_include_directories(test_process PRIVATE
target_compile_definitions(test_process PRIVATE AUTOFILM_HOST=1)
target_compile_features(test_process PRIVATE c_std_11)
target_link_libraries(test_process PRIVATE m)
add_test(NAME test_process COMMAND test_process)
add_executable(test_machine
test_machine.c
${CMAKE_CURRENT_SOURCE_DIR}/../../components/app_process/app_process.c
${CMAKE_CURRENT_SOURCE_DIR}/../../components/app_machine/app_machine.c
stubs/hal_motor.c
stubs/hal_temp.c
stubs/hal_audio.c
)
target_include_directories(test_machine PRIVATE ${HOST_INCLUDES})
target_compile_definitions(test_machine PRIVATE AUTOFILM_HOST=1)
target_compile_features(test_machine PRIVATE c_std_11)
target_link_libraries(test_machine PRIVATE m)
add_test(NAME test_machine COMMAND test_machine)
+1
View File
@@ -2,5 +2,6 @@
typedef int esp_err_t;
#define ESP_OK 0
#define ESP_FAIL -1
#define ESP_ERR_NOT_FOUND 0x105
#define ESP_ERR_INVALID_ARG 0x102
+27
View File
@@ -0,0 +1,27 @@
#include "hal_audio.h"
int stub_beep_count;
int stub_alarm_count;
int stub_alarm_cancel_count;
void hal_audio_init(void)
{
stub_beep_count = 0;
stub_alarm_count = 0;
stub_alarm_cancel_count = 0;
}
void hal_audio_beep_short(void)
{
stub_beep_count++;
}
void hal_audio_alarm_complete(void)
{
stub_alarm_count++;
}
void hal_audio_alarm_cancel(void)
{
stub_alarm_cancel_count++;
}
+55
View File
@@ -0,0 +1,55 @@
#include "hal_motor.h"
bool stub_motor_enabled;
int stub_stop_count;
int stub_agitate_start_count;
int stub_agitate_stop_count;
int stub_enable_count;
float stub_last_cw;
float stub_last_ccw;
uint32_t stub_last_rpm;
void hal_motor_init(void)
{
stub_motor_enabled = false;
stub_stop_count = 0;
stub_agitate_start_count = 0;
stub_agitate_stop_count = 0;
stub_enable_count = 0;
stub_last_cw = 0.f;
stub_last_ccw = 0.f;
stub_last_rpm = 0;
}
void hal_motor_enable(bool on)
{
stub_enable_count++;
stub_motor_enabled = on;
}
void hal_motor_request_stop(void)
{
stub_stop_count++;
stub_motor_enabled = false;
}
esp_err_t hal_motor_agitate_start(float cw_revs, float ccw_revs, uint32_t rpm)
{
stub_agitate_start_count++;
stub_last_cw = cw_revs;
stub_last_ccw = ccw_revs;
stub_last_rpm = rpm;
stub_motor_enabled = true;
return ESP_OK;
}
void hal_motor_agitate_stop(void)
{
stub_agitate_stop_count++;
stub_motor_enabled = false;
}
bool hal_motor_is_enabled(void)
{
return stub_motor_enabled;
}
+22
View File
@@ -0,0 +1,22 @@
#include "hal_temp.h"
#include <stddef.h>
int stub_temp_fail;
float stub_temp_c = 20.0f;
void hal_temp_init(void)
{
stub_temp_fail = 0;
stub_temp_c = 20.0f;
}
esp_err_t hal_temp_read_c(float *out)
{
if (stub_temp_fail) {
return ESP_FAIL;
}
if (out != NULL) {
*out = stub_temp_c;
}
return ESP_OK;
}
+299
View File
@@ -0,0 +1,299 @@
#include "app_machine.h"
#include "app_process.h"
#include "hal_motor.h"
#include <stdio.h>
#include <string.h>
extern bool stub_motor_enabled;
extern int stub_stop_count;
extern int stub_agitate_start_count;
extern int stub_beep_count;
extern int stub_alarm_count;
extern int stub_alarm_cancel_count;
static int g_failures;
static void fail(const char *msg)
{
fprintf(stderr, "FAIL: %s\n", msg);
g_failures++;
}
static void expect_int(int got, int want, const char *msg)
{
if (got != want) {
fprintf(stderr, "FAIL: %s got=%d want=%d\n", msg, got, want);
g_failures++;
}
}
static void expect_u32(uint32_t got, uint32_t want, const char *msg)
{
if (got != want) {
fprintf(stderr, "FAIL: %s got=%u want=%u\n", msg, got, want);
g_failures++;
}
}
static void drain_events(void)
{
ui_evt_t ev;
while (app_machine_last_event(&ev) == 1) {
}
}
static int pop_ids(ui_evt_id_t *ids, int max)
{
int n = 0;
ui_evt_t ev;
while (n < max && app_machine_last_event(&ev) == 1) {
ids[n++] = ev.id;
}
return n;
}
static bool has_id(const ui_evt_id_t *ids, int n, ui_evt_id_t id)
{
for (int i = 0; i < n; i++) {
if (ids[i] == id) {
return true;
}
}
return false;
}
static ui_cmd_t cmd_select(uint8_t proc)
{
ui_cmd_t c;
memset(&c, 0, sizeof(c));
c.id = CMD_SELECT_PROCESS;
c.process_id = proc;
return c;
}
static ui_cmd_t cmd_arm(uint8_t step)
{
ui_cmd_t c;
memset(&c, 0, sizeof(c));
c.id = CMD_ARM_STEP;
c.step_index = step;
return c;
}
static ui_cmd_t cmd_start(uint8_t step)
{
ui_cmd_t c;
memset(&c, 0, sizeof(c));
c.id = CMD_START_STEP;
c.step_index = step;
return c;
}
static ui_cmd_t cmd_id(ui_cmd_id_t id)
{
ui_cmd_t c;
memset(&c, 0, sizeof(c));
c.id = id;
return c;
}
static void select_c41_step_view(void)
{
app_machine_init();
ui_cmd_t c = cmd_select(PROC_C41);
app_machine_handle_cmd(&c);
expect_int((int)app_machine_state(), ST_STEP_SELECT, "select_c41_step_view state");
expect_int((int)app_machine_process_id(), PROC_C41, "select_c41_step_view proc");
expect_int((int)app_machine_step_index(), 0, "select_c41_step_view step");
ui_evt_id_t ids[8];
int n = pop_ids(ids, 8);
if (!has_id(ids, n, EVT_PROCESS_SELECTED) || !has_id(ids, n, EVT_STEP_VIEW)) {
fail("select_c41_step_view events");
}
}
static void adjust_does_not_mutate_const(void)
{
app_machine_init();
ui_cmd_t c = cmd_select(PROC_C41);
app_machine_handle_cmd(&c);
ui_cmd_t adj;
memset(&adj, 0, sizeof(adj));
adj.id = CMD_ADJUST_STEP_TIME;
adj.time_delta_s = 5;
app_machine_handle_cmd(&adj);
const process_def_t *p = app_process_get(PROC_C41);
expect_int((int)p->steps[0].time_s, 180, "adjust_does_not_mutate_const const time");
expect_int((int)app_process_time_s(PROC_C41, 0), 185, "adjust_does_not_mutate_const overlay");
}
static void arm_start_complete_custom_10s(void)
{
app_machine_init();
ui_cmd_t c = cmd_select(PROC_CUSTOM);
app_machine_handle_cmd(&c);
ui_cmd_t arm = cmd_arm(0);
app_machine_handle_cmd(&arm);
expect_int((int)app_machine_state(), ST_ARMED, "custom arm state");
ui_cmd_t start = cmd_start(0);
app_machine_handle_cmd(&start);
expect_int((int)app_machine_state(), ST_RUNNING, "custom start state");
expect_int(stub_motor_enabled ? 1 : 0, 1, "custom motor on");
app_machine_tick(0);
expect_int((int)app_machine_state(), ST_RUNNING, "custom tick0");
app_machine_tick(9999);
expect_int((int)app_machine_state(), ST_RUNNING, "custom tick9999");
app_machine_tick(10000);
expect_int((int)app_machine_state(), ST_COMPLETE, "custom complete");
expect_int(stub_motor_enabled ? 1 : 0, 0, "custom motor off complete");
expect_int(stub_alarm_count >= 1 ? 1 : 0, 1, "custom alarm");
/* default auto_advance false */
app_machine_tick(10001);
expect_int((int)app_machine_state(), ST_COMPLETE, "auto_advance stays complete");
}
static void stop_disables_motor_and_resume(void)
{
app_machine_init();
ui_cmd_t c = cmd_select(PROC_CUSTOM);
app_machine_handle_cmd(&c);
ui_cmd_t arm = cmd_arm(0);
app_machine_handle_cmd(&arm);
ui_cmd_t start = cmd_start(0);
app_machine_handle_cmd(&start);
app_machine_tick(0);
app_machine_tick(1000);
int stops_before = stub_stop_count;
ui_cmd_t stop = cmd_id(CMD_STOP);
app_machine_handle_cmd(&stop);
expect_int((int)app_machine_state(), ST_STOPPED, "stop state");
expect_int(stub_motor_enabled ? 1 : 0, 0, "stop motor disabled");
expect_int(stub_stop_count >= stops_before + 1 ? 1 : 0, 1, "stop_count");
uint32_t rem = app_machine_remaining_ms();
if (rem < 8000 || rem > 10000) {
fprintf(stderr, "FAIL: remaining after stop got=%u\n", rem);
g_failures++;
}
int beeps = stub_beep_count;
ui_cmd_t resume = cmd_id(CMD_RESUME);
app_machine_handle_cmd(&resume);
expect_int((int)app_machine_state(), ST_RUNNING, "resume state");
expect_int(stub_beep_count >= beeps + 1 ? 1 : 0, 1, "resume beep");
app_machine_tick(1000);
app_machine_tick(1000 + rem);
expect_int((int)app_machine_state(), ST_COMPLETE, "resume then complete");
}
static void return_from_stopped(void)
{
app_machine_init();
ui_cmd_t c = cmd_select(PROC_CUSTOM);
app_machine_handle_cmd(&c);
ui_cmd_t arm = cmd_arm(0);
app_machine_handle_cmd(&arm);
ui_cmd_t start = cmd_start(0);
app_machine_handle_cmd(&start);
app_machine_tick(0);
app_machine_tick(1000);
ui_cmd_t stop = cmd_id(CMD_STOP);
app_machine_handle_cmd(&stop);
uint32_t rem = app_machine_remaining_ms();
ui_cmd_t ret = cmd_id(CMD_RETURN_TO_STEP_SELECT);
app_machine_handle_cmd(&ret);
expect_int((int)app_machine_state(), ST_STEP_SELECT, "return_from_stopped state");
uint16_t overlay = app_process_time_s(PROC_CUSTOM, 0);
uint16_t expect_s = (uint16_t)((rem + 999u) / 1000u);
expect_int((int)overlay, (int)expect_s, "return_from_stopped overlay");
expect_int((int)app_process_get(PROC_CUSTOM)->steps[0].time_s, 10, "const still 10");
}
static void stop_during_complete_cancels_alarm(void)
{
app_machine_init();
ui_cmd_t c = cmd_select(PROC_CUSTOM);
app_machine_handle_cmd(&c);
ui_cmd_t arm = cmd_arm(0);
app_machine_handle_cmd(&arm);
ui_cmd_t start = cmd_start(0);
app_machine_handle_cmd(&start);
app_machine_tick(0);
app_machine_tick(10000);
expect_int((int)app_machine_state(), ST_COMPLETE, "complete before stop");
int cancels = stub_alarm_cancel_count;
ui_cmd_t stop = cmd_id(CMD_STOP);
app_machine_handle_cmd(&stop);
expect_int((int)app_machine_state(), ST_STEP_SELECT, "stop from complete");
expect_int(stub_alarm_cancel_count >= cancels + 1 ? 1 : 0, 1, "alarm cancel");
}
static void ecn2_remjet_zero_time(void)
{
app_machine_init();
ui_cmd_t c = cmd_select(PROC_ECN2);
app_machine_handle_cmd(&c);
ui_cmd_t browse;
memset(&browse, 0, sizeof(browse));
browse.id = CMD_BROWSE_STEP;
browse.step_delta = 1;
app_machine_handle_cmd(&browse);
expect_int((int)app_machine_step_index(), 1, "remjet index");
ui_cmd_t arm = cmd_arm(1);
app_machine_handle_cmd(&arm);
int starts = stub_agitate_start_count;
ui_cmd_t start = cmd_start(1);
app_machine_handle_cmd(&start);
expect_int(stub_motor_enabled ? 1 : 0, 0, "remjet no enable");
expect_int(stub_agitate_start_count, starts, "remjet no agitate");
app_machine_tick(0);
expect_int((int)app_machine_state(), ST_COMPLETE, "remjet complete next tick");
}
static void stop_ignored_meaningless_in_idle(void)
{
app_machine_init();
int stops = stub_stop_count;
int enables = stub_motor_enabled ? 1 : 0;
ui_cmd_t stop = cmd_id(CMD_STOP);
app_machine_handle_cmd(&stop);
expect_int((int)app_machine_state(), ST_IDLE, "idle stays idle");
expect_int(stub_stop_count, stops, "idle no request_stop required");
(void)enables;
}
static void c41_clock(void)
{
app_machine_init();
ui_cmd_t c = cmd_select(PROC_C41);
app_machine_handle_cmd(&c);
ui_cmd_t arm = cmd_arm(0);
app_machine_handle_cmd(&arm);
ui_cmd_t start = cmd_start(0);
app_machine_handle_cmd(&start);
app_machine_tick(0);
expect_int((int)app_machine_state(), ST_RUNNING, "c41 tick0");
app_machine_tick(179999);
expect_int((int)app_machine_state(), ST_RUNNING, "c41 tick179999");
app_machine_tick(180000);
expect_int((int)app_machine_state(), ST_COMPLETE, "c41 tick180000");
}
int main(void)
{
select_c41_step_view();
adjust_does_not_mutate_const();
arm_start_complete_custom_10s();
stop_disables_motor_and_resume();
return_from_stopped();
stop_during_complete_cancels_alarm();
ecn2_remjet_zero_time();
stop_ignored_meaningless_in_idle();
c41_clock();
if (g_failures == 0) {
printf("test_machine: all assertions passed\n");
return 0;
}
fprintf(stderr, "test_machine: %d failure(s)\n", g_failures);
return 1;
}