6.5 KiB
P02 — ui_cmd + app_process + golden tests
STATUS: DONE
DEPENDS: P01
READ: this file, src/devSequence.cpp (values only), docs/megaplans/REFACTOR-MEGAPLAN.md
OUT: FreeRTOS tasks, HAL, display, Arduino, CI yaml, machine states
FORBIDDEN: mutate recipe numbers; String; Arduino headers in new public .h
IN
C components + host tests that lock recipe bytes. No firmware behaviour change yet (main.c may log process count).
FILES
components/ui_cmd/CMakeLists.txt
components/ui_cmd/include/ui_cmd.h
components/app_process/CMakeLists.txt
components/app_process/include/app_process.h
components/app_process/app_process.c
tests/host/CMakeLists.txt
tests/host/test_process.c
tests/host/main.c # if Unity/CTest needs a runner; else add_executable per test
Host CMake must not require IDF_PATH. Link app_process sources directly. Provide esp_err_t typedef if needed:
typedef int esp_err_t;
#define ESP_OK 0
#define ESP_ERR_NOT_FOUND 0x105
#define ESP_ERR_INVALID_ARG 0x102
in tests/host/esp_host_compat.h and include it only in host builds.
ui_cmd.h (lock)
Packed, no pointers in the union payload except none — use ids.
#pragma once
#include <stdint.h>
#include <stdbool.h>
#define UI_CMD_QUEUE_LEN 8
#define UI_EVT_QUEUE_LEN 16
typedef enum {
CMD_SELECT_PROCESS = 1,
CMD_BROWSE_STEP = 2,
CMD_ADJUST_STEP_TIME = 3,
CMD_ARM_STEP = 4,
CMD_START_STEP = 5,
CMD_STOP = 6,
CMD_RESUME = 7,
CMD_RETURN_TO_STEP_SELECT = 8,
CMD_CANCEL_ARMED = 9,
CMD_PUMP_SET = 100,
CMD_VALVE_SET = 101,
CMD_RESERVOIR_SELECT = 102,
CMD_DRAIN_START = 103,
CMD_DRAIN_STOP = 104,
CMD_HEATER_SETPOINT = 105
} ui_cmd_id_t;
typedef enum {
EVT_PROCESS_SELECTED = 1,
EVT_STEP_VIEW = 2,
EVT_STEP_ARMED = 3,
EVT_STEP_STARTED = 4,
EVT_STEP_PROGRESS = 5,
EVT_STEP_COMPLETE = 6,
EVT_STEP_STOPPED = 7,
EVT_STEP_RESUMED = 8,
EVT_PROCESS_IDLE = 9,
EVT_TEMP_UPDATED = 10,
EVT_FAULT = 11
} ui_evt_id_t;
typedef enum { MOTION_IDLE = 0, MOTION_CW = 1, MOTION_CCW = 2 } motion_t;
typedef struct {
ui_cmd_id_t id;
uint8_t process_id;
int8_t step_delta; /* BrowseStep */
int8_t time_delta_s; /* AdjustStepTime, typically ±5 */
uint8_t step_index; /* Arm/Start */
} ui_cmd_t;
typedef struct {
ui_evt_id_t id;
uint8_t process_id;
uint8_t step_index;
uint8_t step_count;
uint16_t time_s;
uint32_t remaining_ms;
float temp_c; /* NAN if disconnected */
bool temp_ok;
float temp_min_c, temp_pref_c, temp_max_c;
float cw_revs, ccw_revs;
motion_t motion;
int16_t fault_code;
} ui_evt_t;
P03+ fill unused fields with 0 / NAN. Do not add char name[n] to the event if it forces copies; UI looks up names via app_process by id+index.
app_process.h (lock)
#pragma once
#include <stdint.h>
#include "esp_err.h" /* or host compat */
#define PROCESS_COUNT 6
#define PROCESS_MAX_STEPS 20
enum {
PROC_C41 = 0,
PROC_E6 = 1,
PROC_ECN2 = 2,
PROC_BW = 3,
PROC_CUSTOM = 4,
PROC_BWREV = 5
};
typedef struct {
const char *name;
uint16_t time_s;
float cw_revs, ccw_revs;
float temp_min_c, temp_pref_c, temp_max_c;
} process_step_t;
typedef struct {
const char *name;
uint8_t step_count;
const process_step_t *steps;
} process_def_t;
void app_process_init(void); /* zero session deltas */
const process_def_t *app_process_get(uint8_t id);
const process_def_t *app_process_find_name(const char *name);
uint16_t app_process_time_s(uint8_t id, uint8_t step); /* default + session delta, min 0 */
esp_err_t app_process_adjust_time(uint8_t id, uint8_t step, int8_t delta_s);
void app_process_reset_session(uint8_t id);
Session overlay: static int16_t time_delta_s[PROCESS_COUNT][PROCESS_MAX_STEPS]. adjust_time adds delta; does not write process_step_t.time_s. Clamp resulting time to [0, 36000].
Golden table (assert exact)
Copy from src/devSequence.cpp. Names strcmp. Floats: compare with fabsf(a-b)<1e-4 except integers as exact.
C41 name="C41" count=7
| i | name | t | cw | ccw | min | pref | max |
|---|---|---|---|---|---|---|---|
| 0 | Prewarm | 180 | 1 | 1 | 37.8 | 38 | 38.2 |
| 1 | Developer | 195 | 5.5 | 5 | 37.8 | 38 | 38.2 |
| 2 | Bleach | 45 | 5.5 | 5 | 32 | 38 | 38.2 |
| 3 | Fix | 180 | 5.5 | 5 | 32 | 38 | 38.2 |
| 4 | Rinse 1 | 60 | 3.5 | 3 | 32 | 38 | 38.2 |
| 5 | Rinse 2 | 60 | 3.5 | 3 | 32 | 38 | 38.2 |
| 6 | Fin Rinse | 30 | 1 | 1 | 32 | 38 | 38.2 |
E6 name="E6" 12: times {180,360,120,120,360,120,360,240,120,120,120,30} names Preheat,FirstDev,Wash 1,Reversal,ColorDev,PreBleach,Bleach,Fixer,Wash 2,Wash 3,Wash 4,Fin Rinse cw {1,5.5,3.5,5,5.5,5.5,5.5,5.5,3.5,3.5,3.5,3.5} ccw {1,5,3,5.5,5,5.5,5.5,5.5,3,3,3,3} min {37.5,37.7,33.0,37.7,37.0,37,37.5,37.5,33.0,33.0,33.0,19.0} pref {38,38,38,38,38,38,38,38,38,38,38,20} max {38.5,38.3,38.0,38.3,39.0,38,38.5,38.5,38.5,38.5,38.5,21.0}
ECN-2 name="ECN-2" 9: t {180,0,210,60,180,150,120,300,120} names Prebath,RemJet,Developer,Stop Bath,Wash,Bleach,Fixer,Wash 2,Fin Rinse cw {1,0,5.5,3.5,3.5,5.5,5.5,3.5,1} ccw {1,0,5,3,3,5,5,3,1} min {27,0,40.8,27,27,27,27,27,27} pref {38,0,41,38,38,38,38,38,38} max {38,0,41.2,38,38,38,38,38,38}
B&W name="B&W" 7: t {510,30,300,60,90,120,30} names Developer,Stop,Fix,Rinse 1,Rinse 2,Rinse 3,Fin Rinse cw {5.5,3.5,3.5,3.5,3.5,3.5,1} ccw {5,3,3,3,3,3,1} min 19 pref 20 max 21 all.
Custom name="Custom" 4: t 10, names Developer,Stop,Fix,Rinse cw {5.5,3.5,3.5,3.5} ccw {5,3,3,3} min 19 pref 20 max 21.
B&WREV name="B&WREV" 12: t {720,300,300,60,120,60,120,360,60,300,60,60} names FirstDev,Wash 1,Bleach,Wash 2,Clearing,Wash 3,Reversal,SecondDev,Wash 4,Fix,Wash 5,Fin Rinse cw {5.5,3.5,5.5,3.5,5.5,3.5,5.5,5.5,3.5,5.5,3.5,3.5} ccw {5,3,5,3,5,3,5,5,3,5,3,3} min {19.5,15.5,19.5,15.5,19.5,15.5,19.5,19.5,15.5,19.5,15.5,15.5} pref 20 all max {20.5,22.5,22.5,22.5,22.5,22.5,22.5,22.5,22.5,22.5,22.5,22.5}
Tests also: find_name("nope")==NULL; adjust_time(C41,1,+5) → 200, const table still 195; reset_session restores 195.
VERIFY
cmake -S tests/host -B build/host && cmake --build build/host && ctest --test-dir build/host --output-on-failure
All golden tests pass.
COMMITS
Add ui_cmd and app_process with frozen recipe tablesAdd host tests for process lookup and session time overlay
DoD
- Headers Arduino-free
- Every cell in golden table asserted
- Overlay does not mutate const steps
- STATUS→DONE