Author SHA1 Message Date
gronod ce9d729027 Tick A04 DoD checkbox after CI green
ci / test (push) Successful in 1m11s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Successful in 3m51s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Successful in 3m53s
2026-09-17 10:08:33 +01:00
gronod 06acd84928 Add esp_timer to hal_temp requires
ci / test (push) Successful in 2m6s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Successful in 3m42s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Successful in 3m44s
2026-09-17 09:58:29 +01:00
gronod cfd047a0ac Mark A04 status DONE
ci / test (push) Successful in 1m24s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Failing after 7m23s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Failing after 7m24s
2026-09-17 09:47:01 +01:00
gronod a3830a58cb Make DS18B20 conversion non-blocking in hal_temp 2026-09-17 09:46:32 +01:00
gronod 3785f07189 Mark A03 status DONE
ci / test (push) Successful in 1m6s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Successful in 3m55s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Successful in 4m0s
2026-09-17 07:58:32 +01:00
gronod 37b772218b Make task watchdog setup explicit in app_main
ci / test (push) Successful in 1m5s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Successful in 4m36s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Successful in 4m40s
2026-09-17 07:49:20 +01:00
gronod 8f7db974fa Mark A02 status DONE
ci / test (push) Successful in 1m10s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Successful in 3m32s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Successful in 3m42s
2026-09-17 07:30:48 +01:00
gronod 02d226b949 Update machine tests for auto-advance 2026-09-17 07:30:19 +01:00
gronod d71e28c85d Restore auto-advance to arm next step after step complete 2026-09-17 07:30:19 +01:00
gronod adff1d963b Mark A01 status DONE
ci / test (push) Successful in 1m7s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Successful in 4m0s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Successful in 4m8s
2026-09-17 07:24:09 +01:00
gronod d84116c45b Replace machine event ring with FreeRTOS queue 2026-09-17 07:23:23 +01:00
gronod 1ade64b2b2 Fix step deadline to derive from real uptime 2026-09-17 07:22:57 +01:00
gronod 20535ceb14 Add FreeRTOS queue shim for host tests 2026-09-17 07:22:17 +01:00
gronod 76ef75da29 Add audit remediation megaplan
ci / test (push) Successful in 1m35s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Successful in 7m31s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Successful in 7m32s
2026-09-17 06:43:54 +01:00
19 changed files with 543 additions and 62 deletions
+1
View File
@@ -4,6 +4,7 @@
.vscode/launch.json .vscode/launch.json
.vscode/ipch .vscode/ipch
build/ build/
build_host/
sdkconfig sdkconfig
sdkconfig.old sdkconfig.old
managed_components/ managed_components/
+1 -1
View File
@@ -1,4 +1,4 @@
idf_component_register(SRCS "app_machine.c" idf_component_register(SRCS "app_machine.c"
INCLUDE_DIRS "include" INCLUDE_DIRS "include"
REQUIRES ui_cmd app_process REQUIRES ui_cmd app_process
PRIV_REQUIRES hal_motor hal_temp hal_audio) PRIV_REQUIRES hal_motor hal_temp hal_audio freertos)
+23 -23
View File
@@ -1,3 +1,6 @@
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "app_machine.h" #include "app_machine.h"
#include "app_process.h" #include "app_process.h"
#include "hal_motor.h" #include "hal_motor.h"
@@ -5,6 +8,7 @@
#include "hal_audio.h" #include "hal_audio.h"
#include <string.h> #include <string.h>
#include <stddef.h>
#define AGITATE_RPM 60u #define AGITATE_RPM 60u
@@ -16,20 +20,13 @@ static uint32_t s_deadline_ms;
static bool s_have_deadline; static bool s_have_deadline;
static bool s_resume_pending; static bool s_resume_pending;
static bool s_auto_advance; static bool s_auto_advance;
static ui_evt_t s_q[UI_EVT_QUEUE_LEN]; static QueueHandle_t s_evtq;
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) static void emit(ui_evt_t ev)
{ {
if (s_q_count == UI_EVT_QUEUE_LEN) { if (s_evtq != NULL) {
s_q_head = (uint8_t)((s_q_head + 1u) % UI_EVT_QUEUE_LEN); xQueueSend(s_evtq, &ev, 0);
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) static void fill_common(ui_evt_t *ev)
@@ -122,14 +119,13 @@ static void enter_armed(void)
emit_id(EVT_STEP_ARMED); emit_id(EVT_STEP_ARMED);
} }
static void start_running(uint32_t now_ms) static void start_running(void)
{ {
const process_def_t *p = app_process_get(s_proc); const process_def_t *p = app_process_get(s_proc);
uint16_t t_s = app_process_time_s(s_proc, s_step); uint16_t t_s = app_process_time_s(s_proc, s_step);
s_remaining_ms = (uint32_t)t_s * 1000u; s_remaining_ms = (uint32_t)t_s * 1000u;
s_deadline_ms = now_ms + s_remaining_ms; s_have_deadline = false;
s_have_deadline = true; s_resume_pending = true;
s_resume_pending = false;
if (t_s == 0 || p == NULL) { if (t_s == 0 || p == NULL) {
hal_motor_enable(false); hal_motor_enable(false);
@@ -198,10 +194,14 @@ void app_machine_init(void)
s_deadline_ms = 0; s_deadline_ms = 0;
s_have_deadline = false; s_have_deadline = false;
s_resume_pending = false; s_resume_pending = false;
s_auto_advance = false; s_auto_advance = true;
s_q_head = 0;
s_q_tail = 0; if (s_evtq == NULL) {
s_q_count = 0; s_evtq = xQueueCreate(UI_EVT_QUEUE_LEN, sizeof(ui_evt_t));
} else {
xQueueReset(s_evtq);
}
app_process_init(); app_process_init();
hal_motor_init(); hal_motor_init();
hal_temp_init(); hal_temp_init();
@@ -230,14 +230,14 @@ uint32_t app_machine_remaining_ms(void)
int app_machine_last_event(ui_evt_t *out) int app_machine_last_event(ui_evt_t *out)
{ {
if (s_q_count == 0 || out == NULL) { if (s_evtq == NULL || out == NULL) {
return 0; return 0;
} }
*out = s_q[s_q_head]; if (xQueueReceive(s_evtq, out, 0) == pdTRUE) {
s_q_head = (uint8_t)((s_q_head + 1u) % UI_EVT_QUEUE_LEN);
s_q_count--;
return 1; return 1;
} }
return 0;
}
void app_machine_on_temp(float c, bool ok) void app_machine_on_temp(float c, bool ok)
{ {
@@ -336,7 +336,7 @@ void app_machine_handle_cmd(const ui_cmd_t *cmd)
enter_armed(); enter_armed();
} else if (s_state == ST_ARMED) { } else if (s_state == ST_ARMED) {
apply_step_index(cmd->step_index); apply_step_index(cmd->step_index);
start_running(0); start_running();
} else if (s_state == ST_COMPLETE) { } else if (s_state == ST_COMPLETE) {
arm_or_next_from_complete(); arm_or_next_from_complete();
} }
+1 -1
View File
@@ -1,6 +1,6 @@
set(srcs) set(srcs)
set(priv_inc) set(priv_inc)
set(priv_req driver) set(priv_req driver esp_timer)
if(IDF_TARGET STREQUAL "esp32") if(IDF_TARGET STREQUAL "esp32")
list(APPEND srcs hal_temp.c) list(APPEND srcs hal_temp.c)
+36 -6
View File
@@ -4,8 +4,7 @@
#include "esp_err.h" #include "esp_err.h"
#include "esp_log.h" #include "esp_log.h"
#include "freertos/FreeRTOS.h" #include "esp_timer.h"
#include "freertos/task.h"
#include "onewire_bus.h" #include "onewire_bus.h"
#include "board.h" #include "board.h"
@@ -18,10 +17,24 @@
static const char *TAG = "temp"; static const char *TAG = "temp";
#define TEMP_CONV_MS 750
typedef enum {
T_START,
T_WAIT,
} temp_state_t;
static onewire_bus_handle_t s_bus; static onewire_bus_handle_t s_bus;
static bool s_inited; static bool s_inited;
static float s_last_c; static float s_last_c;
static bool s_last_ok; static bool s_last_ok;
static temp_state_t s_state = T_START;
static uint32_t s_deadline_ms;
static uint32_t now_ms(void)
{
return (uint32_t)(esp_timer_get_time() / 1000ULL);
}
static bool scratch_valid(const uint8_t *sp) static bool scratch_valid(const uint8_t *sp)
{ {
@@ -45,38 +58,55 @@ void hal_temp_tick(void)
{ {
if (s_bus == NULL) { if (s_bus == NULL) {
s_last_ok = false; s_last_ok = false;
vTaskDelay(pdMS_TO_TICKS(750)); return;
}
uint32_t now = now_ms();
if (s_state == T_START) {
if (now < s_deadline_ms) {
return; return;
} }
if (onewire_bus_reset(s_bus) != ESP_OK) { if (onewire_bus_reset(s_bus) != ESP_OK) {
s_last_ok = false; s_last_ok = false;
vTaskDelay(pdMS_TO_TICKS(750)); s_deadline_ms = now + TEMP_CONV_MS;
return; return;
} }
uint8_t conv[2] = {CMD_SKIP_ROM, CMD_CONVERT_T}; uint8_t conv[2] = {CMD_SKIP_ROM, CMD_CONVERT_T};
if (onewire_bus_write_bytes(s_bus, conv, 2) != ESP_OK) { if (onewire_bus_write_bytes(s_bus, conv, 2) != ESP_OK) {
s_last_ok = false; s_last_ok = false;
vTaskDelay(pdMS_TO_TICKS(750)); s_deadline_ms = now + TEMP_CONV_MS;
return; return;
} }
vTaskDelay(pdMS_TO_TICKS(750)); s_deadline_ms = now + TEMP_CONV_MS;
s_state = T_WAIT;
return;
}
/* T_WAIT */
if (now < s_deadline_ms) {
return;
}
s_state = T_START;
s_deadline_ms = now;
if (onewire_bus_reset(s_bus) != ESP_OK) { if (onewire_bus_reset(s_bus) != ESP_OK) {
s_last_ok = false; s_last_ok = false;
s_deadline_ms = now + TEMP_CONV_MS;
return; return;
} }
uint8_t rd[2] = {CMD_SKIP_ROM, CMD_READ_SCRATCH}; uint8_t rd[2] = {CMD_SKIP_ROM, CMD_READ_SCRATCH};
if (onewire_bus_write_bytes(s_bus, rd, 2) != ESP_OK) { if (onewire_bus_write_bytes(s_bus, rd, 2) != ESP_OK) {
s_last_ok = false; s_last_ok = false;
s_deadline_ms = now + TEMP_CONV_MS;
return; return;
} }
uint8_t sp[9]; uint8_t sp[9];
memset(sp, 0, sizeof(sp)); memset(sp, 0, sizeof(sp));
if (onewire_bus_read_bytes(s_bus, sp, sizeof(sp)) != ESP_OK) { if (onewire_bus_read_bytes(s_bus, sp, sizeof(sp)) != ESP_OK) {
s_last_ok = false; s_last_ok = false;
s_deadline_ms = now + TEMP_CONV_MS;
return; return;
} }
if (!scratch_valid(sp)) { if (!scratch_valid(sp)) {
s_last_ok = false; s_last_ok = false;
s_deadline_ms = now + TEMP_CONV_MS;
return; return;
} }
int16_t raw = (int16_t)((uint16_t)sp[0] | ((uint16_t)sp[1] << 8)); int16_t raw = (int16_t)((uint16_t)sp[0] | ((uint16_t)sp[1] << 8));
+38
View File
@@ -0,0 +1,38 @@
# Codebase Audit & Remediation Plan (develop branch)
After a thorough audit of the `develop` branch focusing on the recent modular ESP-IDF port, I've identified several critical defects primarily centered around FreeRTOS concurrency, system timers, and legacy feature regressions.
Here is the step-by-step remediation plan to address these issues. **No code has been changed yet.**
## 1. Fix Critical Step Timing Bug in `app_machine.c`
**Defect:** `CMD_START_STEP` triggers `start_running(0)`, passing a hardcoded `0` for `now_ms`. The task then calculates `s_deadline_ms = 0 + remaining_ms`. When `app_machine_tick()` runs moments later, it reads the *real* system uptime (e.g., 200,000 ms). If the uptime is larger than the step duration, the step instantly completes.
**Remediation Steps:**
- Modify `start_running()` to remove the `now_ms` argument.
- Instead of calculating `s_deadline_ms` immediately, configure the state machine to defer calculation: set `s_have_deadline = false;` and `s_resume_pending = true;`.
- This safely delegates the deadline calculation to the next `app_machine_tick()` cycle, which naturally computes it using the true system `now_ms`.
- Update `app_machine_handle_cmd()` to call `start_running()` without arguments.
## 2. Fix Event Queue Concurrency in `app_machine.c`
**Defect:** The system uses a raw array `s_q` (with `s_q_head`, `s_q_tail`, and `s_q_count++`) to pass events from the machine to the UI. However, `emit()` is called by both the `machine_task` and the `temp_task` (via `app_machine_on_temp`), while `app_machine_last_event()` is read by the `ui_task`. These are non-atomic read-modify-write operations across three threads, which will inevitably corrupt the queue and crash the UI.
**Remediation Steps:**
- Replace the raw ring buffer variables (`s_q`, `s_q_head`, `s_q_tail`, `s_q_count`) with a standard FreeRTOS `QueueHandle_t s_evtq`.
- In `app_machine_init()`, initialize the queue: `s_evtq = xQueueCreate(UI_EVT_QUEUE_LEN, sizeof(ui_evt_t));`.
- Update `emit()` to use `xQueueSend(s_evtq, &ev, 0);`.
- Update `app_machine_last_event()` to use `xQueueReceive(s_evtq, out, 0) == pdTRUE`.
## 3. Restore Auto-Advance Functionality in `app_machine.c`
**Defect:** The legacy Arduino loop automatically chained processing steps together (`run == 1`). The new state machine includes a `maybe_auto_advance()` function guarded by `s_auto_advance`, but `s_auto_advance` is permanently hardcoded to `false` and never toggled. As a result, the machine halts after every single step.
**Remediation Steps:**
- Initialize `s_auto_advance = true` to match the legacy behavior of chaining steps automatically.
- (Optional) Wire up a UI command (`CMD_TOGGLE_AUTO_ADVANCE`) to allow users to turn this off if manual pausing between steps is desired.
## 4. Fix Task Watchdog Initialization in `main.c`
**Defect:** `ui_task`, `machine_task`, `input_task`, and `temp_task` all invoke `esp_task_wdt_add(NULL)`. However, the Task Watchdog Timer (TWDT) is never initialized in `app_main()`. Depending on the ESP-IDF version and `sdkconfig` defaults, this can cause silent failures or panic at boot.
**Remediation Steps:**
- In `app_main()`, invoke `esp_task_wdt_init(&wdt_config)` *before* creating the FreeRTOS tasks.
- Ensure the WDT timeout is set generously (e.g., 3-5 seconds) to accommodate the 1.5-second blocking time in `hal_temp_tick()`.
## 5. Clean up `hal_temp_tick()` Timing (Low Priority)
**Defect:** `hal_temp_tick()` blocks `temp_task` with multiple `vTaskDelay(pdMS_TO_TICKS(750))` calls for the DS18B20 conversion. While safely isolated from the UI, it forces the watchdog timeout to be artificially large and limits responsiveness if multiple sensors were ever added.
**Remediation Steps:**
- Refactor the 1-Wire sequence into a non-blocking state machine within `temp_task`, or simply keep it as-is but acknowledge the WDT requirement constraint outlined in step 4.
+63
View File
@@ -0,0 +1,63 @@
# AUDIT MEGAPLAN — develop-branch defect remediation
Audience: coding agent. One phase = one session. Do not start the next phase in the same session.
## Source
Implements `docs/audit_remediation_plan.md` — five defects found in the post-refactor `develop` tree: step deadline computed from a fake `now`, non-thread-safe UI event ring, auto-advance regression, implicit task-watchdog setup, and a blocking DS18B20 conversion in `hal_temp_tick`.
## Protocol (every session)
1. `git checkout feature/audit-remediation && git pull --ff-only`
2. Read only: `AGENTS.md`, this file (status table), **the assigned phase file**, and the audit doc items it cites. Open `docs/CURRENT_STATE.md` / `docs/TARGET_ARCHITECTURE.md` / `docs/CICD.md` only if the phase `READ:` list says so.
3. Execute `IN` only. Honour `OUT` and `FORBIDDEN`.
4. Run `VERIFY` exactly. Do not push if any verify item fails. Local verify = host ctest; firmware builds run in Gitea CI on push (`feature/**` is a push trigger — see `.gitea/workflows/ci.yml`).
5. Commits: messages listed in the phase. Imperative. One concern per commit.
6. Push `origin feature/audit-remediation`. PR target is `develop`, not `main`.
7. Set phase `STATUS:` to `DONE` in the phase file **and** this table. One-line `Notes` if you diverged (API name only — do not silently change behaviour).
If blocked: stop, commit nothing broken, write `BLOCKED:` at top of the phase file with the exact error.
## Frozen constraints (never reinterpret)
- Recipe **values** (times s, CW, CCW, temp min/pref/max) for C41 E6 ECN-2 B&W Custom B&WREV = `components/app_process/app_process.c` / `docs/CURRENT_STATE.md`. Changing values requires explicit user order + doc update in the same commit.
- Stop while running: Esc `'X'` (keypad r4c4) and touch STOP on S3 → immediate motor **EN disabled**, then Resume or ReturnToStepSelect. `hal_motor_request_stop` runs before any other work on the stop path.
- UI never blocks on motor, OneWire, or audio.
- No Arduino types in `ui_cmd`, `app_process`, `app_machine` public headers.
- Watchdog stays **on** — A03 makes setup explicit, it does not disable anything. No `vTaskDelete` of long-lived workers.
- Session time edits ±5 s are RAM overlays only. No NVS profiles. No pumps/valves/heaters.
- **Auto-advance**: REFACTOR-MEGAPLAN froze `auto_advance` default `false` "unless a later phase says so" — **A02 is that phase** (audit item 3, owner-approved). Semantics = legacy `run==1` chain: auto-**ARM** the next step, never auto-run; the operator still presses Start per step.
- Event queue semantics change (accepted): a full event queue now drops the **newest** event (`xQueueSend` timeout 0); the old ring overwrote the oldest. UI drains at 40 Hz vs ≤~1.3 Hz producers — overflow is not expected.
## Branch
`feature/audit-remediation` cut from `develop` → PRs into `develop`. (`feature/**` is required for the CI push trigger.)
## Working-tree note
The branch was cut with uncommitted `components/app_machine/app_machine.c` changes present: they implement audit items 1–2 **plus** the item-3 flag flip. A01 lands items 1–2 with `s_auto_advance` reverted to `false`; A02 lands the flag. Do not commit the whole dirty file in one go — split per concern (`git add -p` or re-apply in order).
## Status
| ID | File | Session goal | STATUS |
| --- | --- | --- | --- |
| A00 | `audit/A00-docs.md` | Land audit doc + megaplan + phase files | DONE |
| A01 | `audit/A01-machine-timing-queue.md` | Deadline fix + FreeRTOS event queue + host queue shim | DONE |
| A02 | `audit/A02-auto-advance.md` | Restore auto-arm after step complete + test rework | DONE |
| A03 | `audit/A03-task-wdt.md` | Explicit TWDT init/reconfigure + `add()` failure logging | DONE |
| A04 | `audit/A04-temp-nonblocking.md` | OPTIONAL: non-blocking DS18B20 conversion | DONE |
## Dependency
```
A00 → A01 → A02 (same file; A02 builds on A01's queue + tests)
A03 is independent of A01/A02 (main.c only) — run any time after A00
A04 runs after A03 (shares temp_task in main.c); OPTIONAL — skipping is acceptable
```
## Do not
- Expand scope: no `CMD_TOGGLE_AUTO_ADVANCE` (rejected by owner), no UI changes, no NVS, no pumps/heater, no recipe edits.
- Commit `sdkconfig` (generated), `build/`, `build_host/`, tokens.
- Batch unrelated fixes into a phase.
- Reduce the TWDT timeout below the blocking bound while `hal_temp_tick` still blocks (~750 ms); A04 removes that constraint.
+2
View File
@@ -52,6 +52,8 @@ P07 Notes: custom NV3041A QSPI + GT911; shared app_ui text grid + colour softkey
P08 Notes: LEDC audio; RMT STEP + GPIO DIR/EN; onewire_bus DS18B20 +0.4; GPIO keypad; IDF I2C 2004. Arduino-as-component removed. third_party trees unlinked. host ctest green. idf.py not run here (no IDF_PATH). hw unflashed. P08 Notes: LEDC audio; RMT STEP + GPIO DIR/EN; onewire_bus DS18B20 +0.4; GPIO keypad; IDF I2C 2004. Arduino-as-component removed. third_party trees unlinked. host ctest green. idf.py not run here (no IDF_PATH). hw unflashed.
AUDIT A02 Notes: supersedes the frozen `auto_advance` default-false constraint — `s_auto_advance = true` restores the legacy auto-ARM chain (audit item 3, owner-approved); auto-ARM only, never auto-run.
## Dependency ## Dependency
``` ```
+24
View File
@@ -0,0 +1,24 @@
# A00 — land audit docs on the remediation branch
STATUS: DONE
DEPENDS: —
Notes: executed by the session that authored the megaplan.
**READ:** `AGENTS.md`, `docs/megaplans/AUDIT-MEGAPLAN.md`
**IN:**
1. Create `feature/audit-remediation` from `develop`.
2. Commit `docs/audit_remediation_plan.md`, `docs/megaplans/AUDIT-MEGAPLAN.md`, `docs/megaplans/audit/*.md`, and the `build_host/` `.gitignore` line.
3. Do **not** commit the `components/app_machine/app_machine.c` working-tree changes (A01/A02 own them) or `build_host/` artifacts.
**OUT:** all code changes.
**VERIFY:** `git log -1` shows the docs commit; `git status` shows only `app_machine.c` modified.
**COMMITS:**
1. `Add audit remediation megaplan`
**DoD checkboxes:**
- [x] Branch `feature/audit-remediation` exists, cut from `develop`.
- [x] Audit doc + megaplan + phase files committed.
- [x] `app_machine.c` left uncommitted for A01/A02.
@@ -0,0 +1,44 @@
# A01 — app_machine deadline fix + FreeRTOS event queue + host queue shim
STATUS: DONE
DEPENDS: A00
**READ:** this file, `docs/megaplans/AUDIT-MEGAPLAN.md`, `docs/audit_remediation_plan.md` items 1–2, `components/app_machine/app_machine.c`, `tests/host/CMakeLists.txt`
**Context:** the working tree already carries uncommitted fixes for items 1–2 **and** the item-3 `s_auto_advance = true` flip. This phase lands items 1–2 only; the flag stays `false` until A02. The queue change adds `#include "freertos/queue.h"` to `app_machine.c`, which the host test build cannot resolve — a shim is required.
**IN:**
1. `app_machine.c` deadline fix (keep from working tree): `start_running(void)` takes no `now_ms`; sets `s_remaining_ms = time_s * 1000`, `s_have_deadline = false`, `s_resume_pending = true`. First `app_machine_tick` in `ST_RUNNING` computes `s_deadline_ms = now_ms + s_remaining_ms` from real uptime. `app_machine_handle_cmd` calls `start_running()` with no args.
2. `app_machine.c` cleanup: revert `s_auto_advance` to `false`; restore `#define AGITATE_RPM 60u`; **remove** the duplicate `#define UI_EVT_QUEUE_LEN 16` (already defined via `ui_cmd.h`).
3. `app_machine.c` event queue (keep from working tree): `static QueueHandle_t s_evtq`; `emit()` does `xQueueSend(s_evtq, &ev, 0)` when non-NULL; `app_machine_last_event()` does `xQueueReceive(s_evtq, out, 0) == pdTRUE`. In `app_machine_init`: `if (s_evtq == NULL) { s_evtq = xQueueCreate(UI_EVT_QUEUE_LEN, sizeof(ui_evt_t)); } else { xQueueReset(s_evtq); }` — host tests re-init repeatedly and stale events must not leak between inits.
4. `components/app_machine/CMakeLists.txt`: add `freertos` to `PRIV_REQUIRES` (explicit dependency for `freertos/queue.h`).
5. New host shim so `test_machine` keeps compiling the same `app_machine.c`:
- `tests/host/freertos/FreeRTOS.h` — `BaseType_t`, `UBaseType_t`, `TickType_t`, `pdTRUE`, `pdFALSE`, `pdPASS`, `pdMS_TO_TICKS`.
- `tests/host/freertos/queue.h` — `QueueHandle_t` + prototypes for `xQueueCreate`, `xQueueSend`, `xQueueReceive`, `xQueueReset`.
- `tests/host/freertos/queue.c` — malloc'd ring buffer; `xQueueSend` returns `pdFALSE` (drop) when full; `xQueueReceive` returns `pdFALSE` when empty; `xQueueReset` clears.
- `tests/host/CMakeLists.txt` — add `freertos/queue.c` to the `test_machine` sources (`tests/host` is already on its include path, so `"freertos/FreeRTOS.h"` resolves to the shim).
6. Behaviour must be identical for host tests: all existing `test_machine.c` cases pass **unchanged** (auto_advance still `false`).
**OUT:** `s_auto_advance = true` (A02), TWDT setup (A03), `hal_temp` internals (A04), any UI change.
**FORBIDDEN:** committing the auto-advance flag; changing recipe values; Arduino types; deleting or weakening host test assertions.
**VERIFY:**
```
cmake -S tests/host -B build/host && cmake --build build/host && ctest --test-dir build/host --output-on-failure
```
All green. Firmware builds run in CI on push.
**COMMITS** (order matters — every commit must compile on host):
1. `Add FreeRTOS queue shim for host tests` (shim alone; old code doesn't include the headers yet — harmless)
2. `Fix step deadline to derive from real uptime`
3. `Replace machine event ring with FreeRTOS queue`
Stage hunks per concern (`git add -p` or re-apply in order) — the dirty file combines all three changes plus the A02 flag.
**DoD checkboxes:**
- [x] `start_running` takes no `now_ms`; deadline set on the first `RUNNING` tick.
- [x] `emit`/`app_machine_last_event` go through `s_evtq`; `app_machine_init` creates or resets the queue.
- [x] `s_auto_advance` still `false`.
- [x] Host ctest green with zero test changes.
- [x] STATUS → DONE here and in the megaplan table.
+43
View File
@@ -0,0 +1,43 @@
# A02 — restore auto-advance (auto-arm next step after complete)
STATUS: DONE
DEPENDS: A01
Notes: `stop_disables_motor_and_resume` final assertion updated to `ST_ARMED` — the "unchanged" list cannot hold once auto-advance is on (resume-to-complete auto-arms step 1).
**READ:** this file, `docs/megaplans/AUDIT-MEGAPLAN.md`, `docs/audit_remediation_plan.md` item 3, `docs/CURRENT_STATE.md` §Runtime behaviour, `components/app_machine/app_machine.c`, `tests/host/test_machine.c`
**Semantics (implement exactly):** legacy `run==1` in `src/menu.cpp` chained `startProcessing()`, which displayed `Ent:start Esc:quit` for the next step — i.e. **auto-ARM, never auto-run**. `maybe_auto_advance()` already implements this: on the completing tick it moves to `ST_ARMED` for `s_step+1` (emitting `EVT_STEP_ARMED`), or `ST_IDLE` + `EVT_PROCESS_IDLE` after the last step. `ST_COMPLETE` becomes transient inside the completing tick — the alarm (`hal_audio_alarm_complete`, ~7.5 s async, self-terminating) plays while the next step sits armed. That is acceptable and intentional: do **not** add `alarm_cancel` to `start_running` and do not block on the melody.
**IN:**
1. `app_machine.c`: set `s_auto_advance = true` in `app_machine_init`. Nothing else in the state machine changes.
2. Keep the `ST_COMPLETE` + `CMD_STOP` alarm-cancel branch in `app_machine_handle_cmd` — it becomes unreachable while auto-advance is on, but stays as a defensive path.
3. Rework `tests/host/test_machine.c` for transient COMPLETE (Custom has 4 steps, ECN-2 RemJet is step 1, C41 Prewarm is step 0):
- `arm_start_complete_custom_10s`: after `tick(10000)` expect `ST_ARMED` and `step_index == 1` (auto-armed next step); drop the `tick(10001)` "stays complete" assertion.
- Replace `stop_during_complete_cancels_alarm` with `auto_advance_last_step_goes_idle`: select Custom, `cmd_arm(3)`, `cmd_start(3)`, `tick(0)`, `tick(10000)` → `ST_IDLE`; drain events and assert `EVT_PROCESS_IDLE` present.
- `ecn2_remjet_zero_time`: after `tick(0)` expect `ST_ARMED` at step index 2.
- `c41_clock`: after `tick(180000)` expect `ST_ARMED` at step index 1.
- All other cases unchanged (`select_c41_step_view`, `adjust_does_not_mutate_const`, `stop_disables_motor_and_resume`, `return_from_stopped`, `stop_ignored_meaningless_in_idle`).
- Note: `complete_step` still emits `EVT_STEP_COMPLETE` before `EVT_STEP_ARMED`/`EVT_PROCESS_IDLE`; tests may assert that ordering via `pop_ids` if convenient.
4. `docs/megaplans/REFACTOR-MEGAPLAN.md`: add a one-line `Notes` entry recording that the `auto_advance` default-false freeze is superseded by AUDIT A02 (owner-approved via audit item 3). Do not rewrite the frozen-constraint text itself.
**OUT:** `CMD_TOGGLE_AUTO_ADVANCE` (rejected by owner), any UI/input changes, NVS, recipe values.
**FORBIDDEN:** auto-starting the next step's motor; removing the `ST_COMPLETE` STOP branch; changing recipe tables; editing the frozen-constraint lines of REFACTOR-MEGAPLAN.
**VERIFY:**
```
cmake -S tests/host -B build/host && cmake --build build/host && ctest --test-dir build/host --output-on-failure
```
All green, including the new `auto_advance_last_step_goes_idle`. Firmware builds run in CI on push.
**COMMITS:**
1. `Restore auto-advance to arm next step after step complete`
2. `Update machine tests for auto-advance`
**DoD checkboxes:**
- [x] `s_auto_advance = true`; auto-ARM only, never auto-run.
- [x] Last step completes → `ST_IDLE` + `EVT_PROCESS_IDLE`.
- [x] `ST_COMPLETE` + `CMD_STOP` branch retained.
- [x] REFACTOR-MEGAPLAN supersede note added.
- [x] Host ctest green.
- [x] STATUS → DONE here and in the megaplan table.
+60
View File
@@ -0,0 +1,60 @@
# A03 — explicit task watchdog setup in app_main
STATUS: DONE
DEPENDS: A00 (independent of A01/A02 — `main/main.c` only)
**READ:** this file, `docs/megaplans/AUDIT-MEGAPLAN.md`, `docs/audit_remediation_plan.md` item 4, `main/main.c`, `sdkconfig.defaults`
**Context (audit correction):** the audit's "TWDT never initialized → panic at boot" is unlikely under the current config — `sdkconfig.defaults` sets `CONFIG_ESP_TASK_WDT_EN=y` + `CONFIG_ESP_TASK_WDT_TIMEOUT_S=10` + `CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=y`, and IDF v5.4 auto-initialises the TWDT before `app_main`, so `esp_task_wdt_add(NULL)` in the tasks already succeeds. The real gaps: setup is implicit (breaks silently if the Kconfig default ever changes), and every `esp_task_wdt_add` return value is ignored. Make setup explicit and failures observable. **Keep the effective config identical** — 10 s already bounds the ~750 ms `hal_temp_tick` block; do not adopt the audit's 3–5 s suggestion.
**IN:**
1. `main/main.c`, in `app_main` before the `xTaskCreate` block, under `#if CONFIG_ESP_TASK_WDT_EN`:
```c
esp_task_wdt_config_t wdt_cfg = {
.timeout_ms = CONFIG_ESP_TASK_WDT_TIMEOUT_S * 1000,
.idle_core_mask =
#if CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0
(1u << 0)
#else
0
#endif
#if CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1
| (1u << 1)
#endif
,
#if CONFIG_ESP_TASK_WDT_PANIC
.trigger_panic = true,
#else
.trigger_panic = false,
#endif
};
esp_err_t wdt_err = esp_task_wdt_init(&wdt_cfg);
if (wdt_err == ESP_ERR_INVALID_STATE) {
wdt_err = esp_task_wdt_reconfigure(&wdt_cfg); /* already auto-initialised */
}
if (wdt_err != ESP_OK) {
ESP_LOGW(TAG, "task wdt setup: %s", esp_err_to_name(wdt_err));
}
```
Verify field names/signatures against `esp_task_wdt.h` in the pinned IDF (v5.4) and adjust the sketch only as needed to compile.
2. In each of `input_task`, `machine_task`, `ui_task`, `temp_task`: capture `esp_err_t werr = esp_task_wdt_add(NULL);` and `ESP_LOGW(TAG, "... wdt add failed: %s", esp_err_to_name(werr))` on non-OK; continue regardless — the unconditional `esp_task_wdt_reset()` no-ops harmlessly if the task is not subscribed.
**OUT:** `app_machine`, `hal_temp` internals, UI code, `sdkconfig*` edits.
**FORBIDDEN:** disabling the TWDT or idle-task watch; `vTaskDelete`; shrinking the timeout; committing a generated `sdkconfig`.
**VERIFY:**
```
cmake -S tests/host -B build/host && cmake --build build/host && ctest --test-dir build/host --output-on-failure
```
(unchanged — `main.c` is not host-compiled; run anyway to catch regressions). Firmware builds for both `esp32` and `esp32s3` run in CI on push — confirm both go green.
**COMMITS:**
1. `Make task watchdog setup explicit in app_main`
**DoD checkboxes:**
- [x] init-or-reconfigure runs before task creation.
- [x] `esp_task_wdt_add` failures are logged in all four tasks.
- [x] Effective config unchanged: 10 s timeout, CPU0 idle watched, no panic.
- [x] Both CI firmware builds green.
- [x] STATUS → DONE here and in the megaplan table.
@@ -0,0 +1,37 @@
# A04 — (OPTIONAL) non-blocking DS18B20 conversion in hal_temp
STATUS: DONE
DEPENDS: A03 (shares `temp_task` in `main/main.c`)
**READ:** this file, `docs/megaplans/AUDIT-MEGAPLAN.md`, `docs/audit_remediation_plan.md` item 5, `components/hal_temp/hal_temp.c`, `main/main.c`
**Context:** `hal_temp_tick()` currently blocks `temp_task` for ~750 ms per conversion (plus 750 ms on each error path). It is safely isolated from the UI, so this is a robustness/latency improvement only — the audit itself allows keeping the block if the WDT constraint is documented (A03 does that). Implement only if the phase session chooses to spend it.
**IN:**
1. `components/hal_temp/hal_temp.c`: turn `hal_temp_tick()` into a two-state machine driven by a deadline (`esp_timer_get_time()` / 1000, or `xTaskGetTickCount()`):
- `T_START`: `onewire_bus_reset` + `CMD_SKIP_ROM`/`CMD_CONVERT_T`; on success record `s_deadline_ms = now + 750` and move to `T_WAIT`. On any bus error: `s_last_ok = false`, set a ~750 ms retry deadline, stay in `T_START` (preserves the current retry cadence).
- `T_WAIT`: if `now < s_deadline_ms` return immediately; else `onewire_bus_reset` + `CMD_SKIP_ROM`/`CMD_READ_SCRATCH`, read 9 bytes, CRC-check via existing `scratch_valid`, update `s_last_c`/`s_last_ok`, move back to `T_START` (next conversion starts on the next tick).
- The `s_bus == NULL` path must also be non-blocking (`s_last_ok = false`, return).
- Keep `TEMP_OFFSET`, `scratch_valid`, `hal_temp_init`, and `hal_temp_read_c` semantics unchanged.
2. `main/main.c` `temp_task`: `hal_temp_tick` no longer blocks, so add `vTaskDelay(pdMS_TO_TICKS(100))` at the end of the loop (~10 Hz poll) so the task always sleeps. Calling `app_machine_on_temp` every loop is acceptable (16-deep event queue drained at 40 Hz by `ui_task`); keep it unconditional to keep the diff small.
3. Host stub `components/hal_temp/stub/hal_temp.c` / `tests/host/stubs/hal_temp.c`: no signature change — leave as-is unless the implementation forces otherwise.
**OUT:** additional sensors, temperature alarms, heater control, `hal_temp_read_c` contract changes.
**FORBIDDEN:** changing `TEMP_OFFSET` or the CRC; any `vTaskDelay` ≥ 750 ms left inside `hal_temp_tick`; blocking the UI task; removing the watchdog.
**VERIFY:**
```
cmake -S tests/host -B build/host && cmake --build build/host && ctest --test-dir build/host --output-on-failure
```
plus CI firmware builds for both targets.
**COMMITS:**
1. `Make DS18B20 conversion non-blocking in hal_temp`
**DoD checkboxes:**
- [x] No `vTaskDelay(750)` inside `hal_temp_tick`; conversion waits via deadline.
- [x] `temp_task` always blocks (explicit 100 ms delay).
- [x] `TEMP_OFFSET`, CRC, and read semantics unchanged.
- [x] Host ctest green; both CI firmware builds green.
- [x] STATUS → DONE here and in the megaplan table (or noted `SKIPPED` with reason).
+45 -4
View File
@@ -97,7 +97,10 @@ static void map_and_post_key(char key)
static void input_task(void *arg) static void input_task(void *arg)
{ {
(void)arg; (void)arg;
esp_task_wdt_add(NULL); esp_err_t werr = esp_task_wdt_add(NULL);
if (werr != ESP_OK) {
ESP_LOGW(TAG, "input wdt add failed: %s", esp_err_to_name(werr));
}
const TickType_t period = pdMS_TO_TICKS(40); /* 25 Hz */ const TickType_t period = pdMS_TO_TICKS(40); /* 25 Hz */
for (;;) { for (;;) {
ui_raw_key_t raw; ui_raw_key_t raw;
@@ -119,7 +122,10 @@ static void input_task(void *arg)
static void machine_task(void *arg) static void machine_task(void *arg)
{ {
(void)arg; (void)arg;
esp_task_wdt_add(NULL); esp_err_t werr = esp_task_wdt_add(NULL);
if (werr != ESP_OK) {
ESP_LOGW(TAG, "machine wdt add failed: %s", esp_err_to_name(werr));
}
const TickType_t period = pdMS_TO_TICKS(20); /* 50 Hz */ const TickType_t period = pdMS_TO_TICKS(20); /* 50 Hz */
for (;;) { for (;;) {
ui_cmd_t cmd; ui_cmd_t cmd;
@@ -136,7 +142,10 @@ static void machine_task(void *arg)
static void ui_task(void *arg) static void ui_task(void *arg)
{ {
(void)arg; (void)arg;
esp_task_wdt_add(NULL); esp_err_t werr = esp_task_wdt_add(NULL);
if (werr != ESP_OK) {
ESP_LOGW(TAG, "ui wdt add failed: %s", esp_err_to_name(werr));
}
hal_display_clear(); hal_display_clear();
hal_display_text(0, 0, "AUTOFILM"); hal_display_text(0, 0, "AUTOFILM");
@@ -164,13 +173,17 @@ static void ui_task(void *arg)
static void temp_task(void *arg) static void temp_task(void *arg)
{ {
(void)arg; (void)arg;
esp_task_wdt_add(NULL); esp_err_t werr = esp_task_wdt_add(NULL);
if (werr != ESP_OK) {
ESP_LOGW(TAG, "temp wdt add failed: %s", esp_err_to_name(werr));
}
for (;;) { for (;;) {
hal_temp_tick(); hal_temp_tick();
float c = 0.0f; float c = 0.0f;
bool ok = (hal_temp_read_c(&c) == ESP_OK); bool ok = (hal_temp_read_c(&c) == ESP_OK);
app_machine_on_temp(ok ? c : 0.0f, ok); app_machine_on_temp(ok ? c : 0.0f, ok);
esp_task_wdt_reset(); esp_task_wdt_reset();
vTaskDelay(pdMS_TO_TICKS(100));
} }
} }
#endif #endif
@@ -205,6 +218,34 @@ void app_main(void)
s_cmdq = xQueueCreate(UI_CMD_QUEUE_LEN, sizeof(ui_cmd_t)); s_cmdq = xQueueCreate(UI_CMD_QUEUE_LEN, sizeof(ui_cmd_t));
#if CONFIG_ESP_TASK_WDT_EN
esp_task_wdt_config_t wdt_cfg = {
.timeout_ms = CONFIG_ESP_TASK_WDT_TIMEOUT_S * 1000,
.idle_core_mask =
#if CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0
(1u << 0)
#else
0
#endif
#if CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1
| (1u << 1)
#endif
,
#if CONFIG_ESP_TASK_WDT_PANIC
.trigger_panic = true,
#else
.trigger_panic = false,
#endif
};
esp_err_t wdt_err = esp_task_wdt_init(&wdt_cfg);
if (wdt_err == ESP_ERR_INVALID_STATE) {
wdt_err = esp_task_wdt_reconfigure(&wdt_cfg); /* already auto-initialised */
}
if (wdt_err != ESP_OK) {
ESP_LOGW(TAG, "task wdt setup: %s", esp_err_to_name(wdt_err));
}
#endif
xTaskCreate(temp_task, "temp", 4096, NULL, 2, NULL); xTaskCreate(temp_task, "temp", 4096, NULL, 2, NULL);
xTaskCreate(input_task, "input", 3072, NULL, 8, NULL); xTaskCreate(input_task, "input", 3072, NULL, 8, NULL);
xTaskCreate(machine_task, "machine", 4096, NULL, 6, NULL); xTaskCreate(machine_task, "machine", 4096, NULL, 6, NULL);
+1
View File
@@ -37,6 +37,7 @@ add_executable(test_machine
stubs/hal_motor.c stubs/hal_motor.c
stubs/hal_temp.c stubs/hal_temp.c
stubs/hal_audio.c stubs/hal_audio.c
freertos/queue.c
) )
target_include_directories(test_machine PRIVATE ${HOST_INCLUDES}) target_include_directories(test_machine PRIVATE ${HOST_INCLUDES})
+14
View File
@@ -0,0 +1,14 @@
#pragma once
#include <stdint.h>
typedef long BaseType_t;
typedef unsigned long UBaseType_t;
typedef uint32_t TickType_t;
#define pdFALSE ((BaseType_t)0)
#define pdTRUE ((BaseType_t)1)
#define pdPASS pdTRUE
#define pdFAIL pdFALSE
#define pdMS_TO_TICKS(xTimeInMs) ((TickType_t)(xTimeInMs))
+72
View File
@@ -0,0 +1,72 @@
#include "freertos/queue.h"
#include <stdlib.h>
#include <string.h>
struct QueueDefinition {
UBaseType_t len;
UBaseType_t item_size;
UBaseType_t head;
UBaseType_t tail;
UBaseType_t count;
uint8_t *storage;
};
QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength, UBaseType_t uxItemSize)
{
struct QueueDefinition *q = calloc(1, sizeof(*q));
if (q == NULL) {
return NULL;
}
q->storage = malloc((size_t)uxQueueLength * uxItemSize);
if (q->storage == NULL) {
free(q);
return NULL;
}
q->len = uxQueueLength;
q->item_size = uxItemSize;
return q;
}
BaseType_t xQueueSend(QueueHandle_t xQueue, const void *pvItemToQueue, TickType_t xTicksToWait)
{
(void)xTicksToWait;
if (xQueue == NULL || pvItemToQueue == NULL) {
return pdFALSE;
}
if (xQueue->count == xQueue->len) {
return pdFALSE;
}
memcpy(xQueue->storage + (size_t)xQueue->tail * xQueue->item_size,
pvItemToQueue, xQueue->item_size);
xQueue->tail = (xQueue->tail + 1u) % xQueue->len;
xQueue->count++;
return pdTRUE;
}
BaseType_t xQueueReceive(QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait)
{
(void)xTicksToWait;
if (xQueue == NULL || pvBuffer == NULL) {
return pdFALSE;
}
if (xQueue->count == 0u) {
return pdFALSE;
}
memcpy(pvBuffer, xQueue->storage + (size_t)xQueue->head * xQueue->item_size,
xQueue->item_size);
xQueue->head = (xQueue->head + 1u) % xQueue->len;
xQueue->count--;
return pdTRUE;
}
BaseType_t xQueueReset(QueueHandle_t xQueue)
{
if (xQueue == NULL) {
return pdFALSE;
}
xQueue->head = 0;
xQueue->tail = 0;
xQueue->count = 0;
return pdTRUE;
}
+11
View File
@@ -0,0 +1,11 @@
#pragma once
#include "freertos/FreeRTOS.h"
struct QueueDefinition;
typedef struct QueueDefinition *QueueHandle_t;
QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength, UBaseType_t uxItemSize);
BaseType_t xQueueSend(QueueHandle_t xQueue, const void *pvItemToQueue, TickType_t xTicksToWait);
BaseType_t xQueueReceive(QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait);
BaseType_t xQueueReset(QueueHandle_t xQueue);
+17 -17
View File
@@ -145,12 +145,10 @@ static void arm_start_complete_custom_10s(void)
app_machine_tick(9999); app_machine_tick(9999);
expect_int((int)app_machine_state(), ST_RUNNING, "custom tick9999"); expect_int((int)app_machine_state(), ST_RUNNING, "custom tick9999");
app_machine_tick(10000); app_machine_tick(10000);
expect_int((int)app_machine_state(), ST_COMPLETE, "custom complete"); expect_int((int)app_machine_state(), ST_ARMED, "custom auto-armed next");
expect_int((int)app_machine_step_index(), 1, "custom auto-advance step");
expect_int(stub_motor_enabled ? 1 : 0, 0, "custom motor off complete"); expect_int(stub_motor_enabled ? 1 : 0, 0, "custom motor off complete");
expect_int(stub_alarm_count >= 1 ? 1 : 0, 1, "custom alarm"); 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) static void stop_disables_motor_and_resume(void)
@@ -182,7 +180,7 @@ static void stop_disables_motor_and_resume(void)
expect_int(stub_beep_count >= beeps + 1 ? 1 : 0, 1, "resume beep"); expect_int(stub_beep_count >= beeps + 1 ? 1 : 0, 1, "resume beep");
app_machine_tick(1000); app_machine_tick(1000);
app_machine_tick(1000 + rem); app_machine_tick(1000 + rem);
expect_int((int)app_machine_state(), ST_COMPLETE, "resume then complete"); expect_int((int)app_machine_state(), ST_ARMED, "resume then auto-armed");
} }
static void return_from_stopped(void) static void return_from_stopped(void)
@@ -208,23 +206,23 @@ static void return_from_stopped(void)
expect_int((int)app_process_get(PROC_CUSTOM)->steps[0].time_s, 10, "const still 10"); expect_int((int)app_process_get(PROC_CUSTOM)->steps[0].time_s, 10, "const still 10");
} }
static void stop_during_complete_cancels_alarm(void) static void auto_advance_last_step_goes_idle(void)
{ {
app_machine_init(); app_machine_init();
ui_cmd_t c = cmd_select(PROC_CUSTOM); ui_cmd_t c = cmd_select(PROC_CUSTOM);
app_machine_handle_cmd(&c); app_machine_handle_cmd(&c);
ui_cmd_t arm = cmd_arm(0); ui_cmd_t arm = cmd_arm(3);
app_machine_handle_cmd(&arm); app_machine_handle_cmd(&arm);
ui_cmd_t start = cmd_start(0); ui_cmd_t start = cmd_start(3);
app_machine_handle_cmd(&start); app_machine_handle_cmd(&start);
app_machine_tick(0); app_machine_tick(0);
app_machine_tick(10000); app_machine_tick(10000);
expect_int((int)app_machine_state(), ST_COMPLETE, "complete before stop"); expect_int((int)app_machine_state(), ST_IDLE, "last step goes idle");
int cancels = stub_alarm_cancel_count; ui_evt_id_t ids[8];
ui_cmd_t stop = cmd_id(CMD_STOP); int n = pop_ids(ids, 8);
app_machine_handle_cmd(&stop); if (!has_id(ids, n, EVT_PROCESS_IDLE)) {
expect_int((int)app_machine_state(), ST_STEP_SELECT, "stop from complete"); fail("last step idle event");
expect_int(stub_alarm_cancel_count >= cancels + 1 ? 1 : 0, 1, "alarm cancel"); }
} }
static void ecn2_remjet_zero_time(void) static void ecn2_remjet_zero_time(void)
@@ -246,7 +244,8 @@ static void ecn2_remjet_zero_time(void)
expect_int(stub_motor_enabled ? 1 : 0, 0, "remjet no enable"); expect_int(stub_motor_enabled ? 1 : 0, 0, "remjet no enable");
expect_int(stub_agitate_start_count, starts, "remjet no agitate"); expect_int(stub_agitate_start_count, starts, "remjet no agitate");
app_machine_tick(0); app_machine_tick(0);
expect_int((int)app_machine_state(), ST_COMPLETE, "remjet complete next tick"); expect_int((int)app_machine_state(), ST_ARMED, "remjet auto-armed next");
expect_int((int)app_machine_step_index(), 2, "remjet auto-advance step");
} }
static void stop_ignored_meaningless_in_idle(void) static void stop_ignored_meaningless_in_idle(void)
@@ -275,7 +274,8 @@ static void c41_clock(void)
app_machine_tick(179999); app_machine_tick(179999);
expect_int((int)app_machine_state(), ST_RUNNING, "c41 tick179999"); expect_int((int)app_machine_state(), ST_RUNNING, "c41 tick179999");
app_machine_tick(180000); app_machine_tick(180000);
expect_int((int)app_machine_state(), ST_COMPLETE, "c41 tick180000"); expect_int((int)app_machine_state(), ST_ARMED, "c41 auto-armed next");
expect_int((int)app_machine_step_index(), 1, "c41 auto-advance step");
} }
int main(void) int main(void)
@@ -285,7 +285,7 @@ int main(void)
arm_start_complete_custom_10s(); arm_start_complete_custom_10s();
stop_disables_motor_and_resume(); stop_disables_motor_and_resume();
return_from_stopped(); return_from_stopped();
stop_during_complete_cancels_alarm(); auto_advance_last_step_goes_idle();
ecn2_remjet_zero_time(); ecn2_remjet_zero_time();
stop_ignored_meaningless_in_idle(); stop_ignored_meaningless_in_idle();
c41_clock(); c41_clock();