Refactor/esp idf modular ui #5

Merged
gronod merged 4 commits from refactor/esp-idf-modular-ui into develop 2026-09-16 12:40:17 +01:00
13 changed files with 578 additions and 8 deletions
+65
View File
@@ -0,0 +1,65 @@
name: ci
on:
pull_request:
branches: [develop]
push:
branches: [develop, "refactor/**", "feature/**"]
tags: ["v*"]
workflow_dispatch:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Host unit tests
if: hashFiles('tests/host/CMakeLists.txt') != ''
run: |
cmake -S tests/host -B build/host
cmake --build build/host
ctest --test-dir build/host --output-on-failure
firmware:
needs: test
runs-on: ubuntu-latest
container:
image: docker.io/espressif/idf:v5.4
strategy:
fail-fast: false
matrix:
include:
- board: wroom
target: esp32
sdkconfig: sdkconfig.wroom
- board: jc4827w543
target: esp32s3
sdkconfig: sdkconfig.s3
steps:
- uses: actions/checkout@v4
- name: Build
if: hashFiles('CMakeLists.txt') != ''
run: |
git config --global --add safe.directory '*'
idf.py -B build/${{ matrix.board }} set-target ${{ matrix.target }}
idf.py -B build/${{ matrix.board }} \
-D SDKCONFIG_DEFAULTS="sdkconfig.defaults;${{ matrix.sdkconfig }}" \
-D AUTOFILM_BOARD=${{ matrix.board }} \
build
idf.py -B build/${{ matrix.board }} size | tee size-${{ matrix.board }}.txt
python $IDF_PATH/components/esptool_py/esptool/esptool.py \
--chip ${{ matrix.target }} merge_bin \
-o autofilm-${{ matrix.board }}-${{ gitea.sha }}.bin \
@build/${{ matrix.board }}/flash_args
- uses: actions/upload-artifact@v3
if: hashFiles('CMakeLists.txt') != ''
with:
name: firmware-${{ matrix.board }}
path: |
autofilm-${{ matrix.board }}-*.bin
build/${{ matrix.board }}/*.bin
build/${{ matrix.board }}/bootloader/bootloader.bin
build/${{ matrix.board }}/partition_table/partition-table.bin
build/${{ matrix.board }}/flasher_args.json
build/${{ matrix.board }}/sdkconfig
size-${{ matrix.board }}.txt
+3
View File
@@ -0,0 +1,3 @@
idf_component_register(SRCS "app_process.c"
INCLUDE_DIRS "include"
REQUIRES ui_cmd)
+148
View File
@@ -0,0 +1,148 @@
#include "app_process.h"
#include <string.h>
static const process_step_t s_c41_steps[] = {
{"Prewarm", 180, 1.f, 1.f, 37.8f, 38.f, 38.2f},
{"Developer", 195, 5.5f, 5.f, 37.8f, 38.f, 38.2f},
{"Bleach", 45, 5.5f, 5.f, 32.f, 38.f, 38.2f},
{"Fix", 180, 5.5f, 5.f, 32.f, 38.f, 38.2f},
{"Rinse 1", 60, 3.5f, 3.f, 32.f, 38.f, 38.2f},
{"Rinse 2", 60, 3.5f, 3.f, 32.f, 38.f, 38.2f},
{"Fin Rinse", 30, 1.f, 1.f, 32.f, 38.f, 38.2f},
};
static const process_step_t s_e6_steps[] = {
{"Preheat", 180, 1.f, 1.f, 37.5f, 38.f, 38.5f},
{"FirstDev", 360, 5.5f, 5.f, 37.7f, 38.f, 38.3f},
{"Wash 1", 120, 3.5f, 3.f, 33.0f, 38.f, 38.0f},
{"Reversal", 120, 5.f, 5.5f, 37.7f, 38.f, 38.3f},
{"ColorDev", 360, 5.5f, 5.f, 37.0f, 38.f, 39.0f},
{"PreBleach", 120, 5.5f, 5.5f, 37.f, 38.f, 38.f},
{"Bleach", 360, 5.5f, 5.5f, 37.5f, 38.f, 38.5f},
{"Fixer", 240, 5.5f, 5.5f, 37.5f, 38.f, 38.5f},
{"Wash 2", 120, 3.5f, 3.f, 33.0f, 38.f, 38.5f},
{"Wash 3", 120, 3.5f, 3.f, 33.0f, 38.f, 38.5f},
{"Wash 4", 120, 3.5f, 3.f, 33.0f, 38.f, 38.5f},
{"Fin Rinse", 30, 3.5f, 3.f, 19.0f, 20.f, 21.0f},
};
static const process_step_t s_ecn2_steps[] = {
{"Prebath", 180, 1.f, 1.f, 27.f, 38.f, 38.f},
{"RemJet", 0, 0.f, 0.f, 0.f, 0.f, 0.f},
{"Developer", 210, 5.5f, 5.f, 40.8f, 41.f, 41.2f},
{"Stop Bath", 60, 3.5f, 3.f, 27.f, 38.f, 38.f},
{"Wash", 180, 3.5f, 3.f, 27.f, 38.f, 38.f},
{"Bleach", 150, 5.5f, 5.f, 27.f, 38.f, 38.f},
{"Fixer", 120, 5.5f, 5.f, 27.f, 38.f, 38.f},
{"Wash 2", 300, 3.5f, 3.f, 27.f, 38.f, 38.f},
{"Fin Rinse", 120, 1.f, 1.f, 27.f, 38.f, 38.f},
};
static const process_step_t s_bw_steps[] = {
{"Developer", 510, 5.5f, 5.f, 19.f, 20.f, 21.f},
{"Stop", 30, 3.5f, 3.f, 19.f, 20.f, 21.f},
{"Fix", 300, 3.5f, 3.f, 19.f, 20.f, 21.f},
{"Rinse 1", 60, 3.5f, 3.f, 19.f, 20.f, 21.f},
{"Rinse 2", 90, 3.5f, 3.f, 19.f, 20.f, 21.f},
{"Rinse 3", 120, 3.5f, 3.f, 19.f, 20.f, 21.f},
{"Fin Rinse", 30, 1.f, 1.f, 19.f, 20.f, 21.f},
};
static const process_step_t s_custom_steps[] = {
{"Developer", 10, 5.5f, 5.f, 19.f, 20.f, 21.f},
{"Stop", 10, 3.5f, 3.f, 19.f, 20.f, 21.f},
{"Fix", 10, 3.5f, 3.f, 19.f, 20.f, 21.f},
{"Rinse", 10, 3.5f, 3.f, 19.f, 20.f, 21.f},
};
static const process_step_t s_bwrev_steps[] = {
{"FirstDev", 720, 5.5f, 5.f, 19.5f, 20.f, 20.5f},
{"Wash 1", 300, 3.5f, 3.f, 15.5f, 20.f, 22.5f},
{"Bleach", 300, 5.5f, 5.f, 19.5f, 20.f, 22.5f},
{"Wash 2", 60, 3.5f, 3.f, 15.5f, 20.f, 22.5f},
{"Clearing", 120, 5.5f, 5.f, 19.5f, 20.f, 22.5f},
{"Wash 3", 60, 3.5f, 3.f, 15.5f, 20.f, 22.5f},
{"Reversal", 120, 5.5f, 5.f, 19.5f, 20.f, 22.5f},
{"SecondDev", 360, 5.5f, 5.f, 19.5f, 20.f, 22.5f},
{"Wash 4", 60, 3.5f, 3.f, 15.5f, 20.f, 22.5f},
{"Fix", 300, 5.5f, 5.f, 19.5f, 20.f, 22.5f},
{"Wash 5", 60, 3.5f, 3.f, 15.5f, 20.f, 22.5f},
{"Fin Rinse", 60, 3.5f, 3.f, 15.5f, 20.f, 22.5f},
};
static const process_def_t s_processes[PROCESS_COUNT] = {
{"C41", 7, s_c41_steps},
{"E6", 12, s_e6_steps},
{"ECN-2", 9, s_ecn2_steps},
{"B&W", 7, s_bw_steps},
{"Custom", 4, s_custom_steps},
{"B&WREV", 12, s_bwrev_steps},
};
static int16_t s_time_delta_s[PROCESS_COUNT][PROCESS_MAX_STEPS];
void app_process_init(void)
{
memset(s_time_delta_s, 0, sizeof(s_time_delta_s));
}
const process_def_t *app_process_get(uint8_t id)
{
if (id >= PROCESS_COUNT) {
return NULL;
}
return &s_processes[id];
}
const process_def_t *app_process_find_name(const char *name)
{
if (name == NULL) {
return NULL;
}
for (uint8_t i = 0; i < PROCESS_COUNT; i++) {
if (strcmp(s_processes[i].name, name) == 0) {
return &s_processes[i];
}
}
return NULL;
}
uint16_t app_process_time_s(uint8_t id, uint8_t step)
{
if (id >= PROCESS_COUNT) {
return 0;
}
const process_def_t *p = &s_processes[id];
if (step >= p->step_count) {
return 0;
}
int32_t t = (int32_t)p->steps[step].time_s + (int32_t)s_time_delta_s[id][step];
if (t < 0) {
t = 0;
}
if (t > 36000) {
t = 36000;
}
return (uint16_t)t;
}
esp_err_t app_process_adjust_time(uint8_t id, uint8_t step, int8_t delta_s)
{
if (id >= PROCESS_COUNT) {
return ESP_ERR_INVALID_ARG;
}
if (step >= s_processes[id].step_count || step >= PROCESS_MAX_STEPS) {
return ESP_ERR_INVALID_ARG;
}
s_time_delta_s[id][step] = (int16_t)(s_time_delta_s[id][step] + delta_s);
return ESP_OK;
}
void app_process_reset_session(uint8_t id)
{
if (id >= PROCESS_COUNT) {
return;
}
memset(s_time_delta_s[id], 0, sizeof(s_time_delta_s[id]));
}
@@ -0,0 +1,39 @@
#pragma once
#include <stdint.h>
#ifdef AUTOFILM_HOST
#include "esp_host_compat.h"
#else
#include "esp_err.h"
#endif
#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);
+1
View File
@@ -0,0 +1 @@
idf_component_register(INCLUDE_DIRS "include")
+63
View File
@@ -0,0 +1,63 @@
#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;
+4 -2
View File
@@ -34,14 +34,16 @@ 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 | | P00 | `refactor/P00-docs-lock.md` | Architecture docs already on branch | DONE |
| P01 | `refactor/P01-idf-skeleton.md` | Dual-board IDF project compiles | BLOCKED | | 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 | TODO | | 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 | TODO |
| P04 | `refactor/P04-gitea-ci.md` | `.gitea/workflows/ci.yml` live | TODO | | 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 | TODO |
| P06 | `refactor/P06-ui-wroom-cutover.md` | 2004 UI + keypad + Stop/Resume cutover | TODO | | 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 | | P07 | `refactor/P07-board-s3-ui.md` | JC4827W543 display+GT911+STOP | TODO |
| P08 | `refactor/P08-idf-debt.md` | Replace Arduino drivers (RMT etc.) | 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).
## Dependency ## Dependency
``` ```
@@ -1,6 +1,6 @@
# P02 — ui_cmd + app_process + golden tests # P02 — ui_cmd + app_process + golden tests
STATUS: TODO STATUS: DONE
DEPENDS: P01 DEPENDS: P01
READ: this file, `src/devSequence.cpp` (values only), `docs/megaplans/REFACTOR-MEGAPLAN.md` READ: this file, `src/devSequence.cpp` (values only), `docs/megaplans/REFACTOR-MEGAPLAN.md`
OUT: FreeRTOS tasks, HAL, display, Arduino, CI yaml, machine states OUT: FreeRTOS tasks, HAL, display, Arduino, CI yaml, machine states
@@ -191,7 +191,7 @@ All golden tests pass.
## DoD ## DoD
- [ ] Headers Arduino-free - [x] Headers Arduino-free
- [ ] Every cell in golden table asserted - [x] Every cell in golden table asserted
- [ ] Overlay does not mutate const steps - [x] Overlay does not mutate const steps
- [ ] STATUS→DONE - [x] STATUS→DONE
+2 -1
View File
@@ -1,6 +1,7 @@
# P04 — Gitea Actions CI # P04 — Gitea Actions CI
STATUS: TODO STATUS: DONE
Notes: used ${{ gitea.sha }} in merge_bin output name (CICD example); github.sha not substituted. Runner unverified (no Actions run). No required-check flip (P01 BLOCKED; images not confirmed compiling). Release-attach job skipped.
DEPENDS: P01 (required), P02/P03 (tests job becomes real when `tests/host/CMakeLists.txt` exists) DEPENDS: P01 (required), P02/P03 (tests job becomes real when `tests/host/CMakeLists.txt` exists)
READ: this file, `docs/CICD.md` READ: this file, `docs/CICD.md`
OUT: changing firmware sources; enabling branch protection yourself if you lack repo admin — then note BLOCKED for humans OUT: changing firmware sources; enabling branch protection yourself if you lack repo admin — then note BLOCKED for humans
+22
View File
@@ -0,0 +1,22 @@
cmake_minimum_required(VERSION 3.16)
project(autofilm_host_tests C)
enable_testing()
add_executable(test_process
test_process.c
main.c
${CMAKE_CURRENT_SOURCE_DIR}/../../components/app_process/app_process.c
)
target_include_directories(test_process PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/../../components/app_process/include
${CMAKE_CURRENT_SOURCE_DIR}/../../components/ui_cmd/include
)
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)
+6
View File
@@ -0,0 +1,6 @@
#pragma once
typedef int esp_err_t;
#define ESP_OK 0
#define ESP_ERR_NOT_FOUND 0x105
#define ESP_ERR_INVALID_ARG 0x102
+6
View File
@@ -0,0 +1,6 @@
int test_process_run(void);
int main(void)
{
return test_process_run();
}
+214
View File
@@ -0,0 +1,214 @@
#include "app_process.h"
#include <math.h>
#include <stdio.h>
#include <string.h>
static int g_failures;
static void fail(const char *msg)
{
fprintf(stderr, "FAIL: %s\n", msg);
g_failures++;
}
static void expect_ptr_null(const void *p, const char *msg)
{
if (p != NULL) {
fail(msg);
}
}
static void expect_ptr_nonnull(const void *p, const char *msg)
{
if (p == NULL) {
fail(msg);
}
}
static void expect_str(const char *got, const char *want, const char *msg)
{
if (got == NULL || want == NULL || strcmp(got, want) != 0) {
fprintf(stderr, "FAIL: %s got=\"%s\" want=\"%s\"\n",
msg, got ? got : "(null)", want ? want : "(null)");
g_failures++;
}
}
static void expect_u8(uint8_t got, uint8_t want, const char *msg)
{
if (got != want) {
fprintf(stderr, "FAIL: %s got=%u want=%u\n", msg, got, want);
g_failures++;
}
}
static void expect_u16(uint16_t got, uint16_t want, const char *msg)
{
if (got != want) {
fprintf(stderr, "FAIL: %s got=%u want=%u\n", msg, got, want);
g_failures++;
}
}
static void expect_f(float got, float want, const char *msg)
{
if (fabsf(got - want) >= 1e-4f) {
fprintf(stderr, "FAIL: %s got=%f want=%f\n", msg, (double)got, (double)want);
g_failures++;
}
}
static void expect_err(esp_err_t got, esp_err_t want, const char *msg)
{
if (got != want) {
fprintf(stderr, "FAIL: %s got=%d want=%d\n", msg, got, want);
g_failures++;
}
}
static void check_step(const process_def_t *p, uint8_t i,
const char *name, uint16_t t,
float cw, float ccw,
float tmin, float tpref, float tmax)
{
char buf[96];
snprintf(buf, sizeof(buf), "%s[%u].name", p->name, i);
expect_str(p->steps[i].name, name, buf);
snprintf(buf, sizeof(buf), "%s[%u].time_s", p->name, i);
expect_u16(p->steps[i].time_s, t, buf);
snprintf(buf, sizeof(buf), "%s[%u].cw", p->name, i);
expect_f(p->steps[i].cw_revs, cw, buf);
snprintf(buf, sizeof(buf), "%s[%u].ccw", p->name, i);
expect_f(p->steps[i].ccw_revs, ccw, buf);
snprintf(buf, sizeof(buf), "%s[%u].min", p->name, i);
expect_f(p->steps[i].temp_min_c, tmin, buf);
snprintf(buf, sizeof(buf), "%s[%u].pref", p->name, i);
expect_f(p->steps[i].temp_pref_c, tpref, buf);
snprintf(buf, sizeof(buf), "%s[%u].max", p->name, i);
expect_f(p->steps[i].temp_max_c, tmax, buf);
}
int test_process_run(void)
{
app_process_init();
expect_ptr_null(app_process_get(6), "get(6)");
expect_ptr_null(app_process_find_name("nope"), "find_name nope");
expect_ptr_null(app_process_find_name(NULL), "find_name NULL");
const process_def_t *c41 = app_process_get(PROC_C41);
const process_def_t *e6 = app_process_get(PROC_E6);
const process_def_t *ecn = app_process_get(PROC_ECN2);
const process_def_t *bw = app_process_get(PROC_BW);
const process_def_t *custom = app_process_get(PROC_CUSTOM);
const process_def_t *bwrev = app_process_get(PROC_BWREV);
expect_ptr_nonnull(c41, "C41 get");
expect_ptr_nonnull(e6, "E6 get");
expect_ptr_nonnull(ecn, "ECN-2 get");
expect_ptr_nonnull(bw, "B&W get");
expect_ptr_nonnull(custom, "Custom get");
expect_ptr_nonnull(bwrev, "B&WREV get");
expect_str(c41->name, "C41", "C41 name");
expect_u8(c41->step_count, 7, "C41 count");
check_step(c41, 0, "Prewarm", 180, 1.f, 1.f, 37.8f, 38.f, 38.2f);
check_step(c41, 1, "Developer", 195, 5.5f, 5.f, 37.8f, 38.f, 38.2f);
check_step(c41, 2, "Bleach", 45, 5.5f, 5.f, 32.f, 38.f, 38.2f);
check_step(c41, 3, "Fix", 180, 5.5f, 5.f, 32.f, 38.f, 38.2f);
check_step(c41, 4, "Rinse 1", 60, 3.5f, 3.f, 32.f, 38.f, 38.2f);
check_step(c41, 5, "Rinse 2", 60, 3.5f, 3.f, 32.f, 38.f, 38.2f);
check_step(c41, 6, "Fin Rinse", 30, 1.f, 1.f, 32.f, 38.f, 38.2f);
expect_str(e6->name, "E6", "E6 name");
expect_u8(e6->step_count, 12, "E6 count");
check_step(e6, 0, "Preheat", 180, 1.f, 1.f, 37.5f, 38.f, 38.5f);
check_step(e6, 1, "FirstDev", 360, 5.5f, 5.f, 37.7f, 38.f, 38.3f);
check_step(e6, 2, "Wash 1", 120, 3.5f, 3.f, 33.0f, 38.f, 38.0f);
check_step(e6, 3, "Reversal", 120, 5.f, 5.5f, 37.7f, 38.f, 38.3f);
check_step(e6, 4, "ColorDev", 360, 5.5f, 5.f, 37.0f, 38.f, 39.0f);
check_step(e6, 5, "PreBleach", 120, 5.5f, 5.5f, 37.f, 38.f, 38.f);
check_step(e6, 6, "Bleach", 360, 5.5f, 5.5f, 37.5f, 38.f, 38.5f);
check_step(e6, 7, "Fixer", 240, 5.5f, 5.5f, 37.5f, 38.f, 38.5f);
check_step(e6, 8, "Wash 2", 120, 3.5f, 3.f, 33.0f, 38.f, 38.5f);
check_step(e6, 9, "Wash 3", 120, 3.5f, 3.f, 33.0f, 38.f, 38.5f);
check_step(e6, 10, "Wash 4", 120, 3.5f, 3.f, 33.0f, 38.f, 38.5f);
check_step(e6, 11, "Fin Rinse", 30, 3.5f, 3.f, 19.0f, 20.f, 21.0f);
expect_str(ecn->name, "ECN-2", "ECN-2 name");
expect_u8(ecn->step_count, 9, "ECN-2 count");
check_step(ecn, 0, "Prebath", 180, 1.f, 1.f, 27.f, 38.f, 38.f);
check_step(ecn, 1, "RemJet", 0, 0.f, 0.f, 0.f, 0.f, 0.f);
check_step(ecn, 2, "Developer", 210, 5.5f, 5.f, 40.8f, 41.f, 41.2f);
check_step(ecn, 3, "Stop Bath", 60, 3.5f, 3.f, 27.f, 38.f, 38.f);
check_step(ecn, 4, "Wash", 180, 3.5f, 3.f, 27.f, 38.f, 38.f);
check_step(ecn, 5, "Bleach", 150, 5.5f, 5.f, 27.f, 38.f, 38.f);
check_step(ecn, 6, "Fixer", 120, 5.5f, 5.f, 27.f, 38.f, 38.f);
check_step(ecn, 7, "Wash 2", 300, 3.5f, 3.f, 27.f, 38.f, 38.f);
check_step(ecn, 8, "Fin Rinse", 120, 1.f, 1.f, 27.f, 38.f, 38.f);
expect_str(bw->name, "B&W", "B&W name");
expect_u8(bw->step_count, 7, "B&W count");
check_step(bw, 0, "Developer", 510, 5.5f, 5.f, 19.f, 20.f, 21.f);
check_step(bw, 1, "Stop", 30, 3.5f, 3.f, 19.f, 20.f, 21.f);
check_step(bw, 2, "Fix", 300, 3.5f, 3.f, 19.f, 20.f, 21.f);
check_step(bw, 3, "Rinse 1", 60, 3.5f, 3.f, 19.f, 20.f, 21.f);
check_step(bw, 4, "Rinse 2", 90, 3.5f, 3.f, 19.f, 20.f, 21.f);
check_step(bw, 5, "Rinse 3", 120, 3.5f, 3.f, 19.f, 20.f, 21.f);
check_step(bw, 6, "Fin Rinse", 30, 1.f, 1.f, 19.f, 20.f, 21.f);
expect_str(custom->name, "Custom", "Custom name");
expect_u8(custom->step_count, 4, "Custom count");
check_step(custom, 0, "Developer", 10, 5.5f, 5.f, 19.f, 20.f, 21.f);
check_step(custom, 1, "Stop", 10, 3.5f, 3.f, 19.f, 20.f, 21.f);
check_step(custom, 2, "Fix", 10, 3.5f, 3.f, 19.f, 20.f, 21.f);
check_step(custom, 3, "Rinse", 10, 3.5f, 3.f, 19.f, 20.f, 21.f);
expect_str(bwrev->name, "B&WREV", "B&WREV name");
expect_u8(bwrev->step_count, 12, "B&WREV count");
check_step(bwrev, 0, "FirstDev", 720, 5.5f, 5.f, 19.5f, 20.f, 20.5f);
check_step(bwrev, 1, "Wash 1", 300, 3.5f, 3.f, 15.5f, 20.f, 22.5f);
check_step(bwrev, 2, "Bleach", 300, 5.5f, 5.f, 19.5f, 20.f, 22.5f);
check_step(bwrev, 3, "Wash 2", 60, 3.5f, 3.f, 15.5f, 20.f, 22.5f);
check_step(bwrev, 4, "Clearing", 120, 5.5f, 5.f, 19.5f, 20.f, 22.5f);
check_step(bwrev, 5, "Wash 3", 60, 3.5f, 3.f, 15.5f, 20.f, 22.5f);
check_step(bwrev, 6, "Reversal", 120, 5.5f, 5.f, 19.5f, 20.f, 22.5f);
check_step(bwrev, 7, "SecondDev", 360, 5.5f, 5.f, 19.5f, 20.f, 22.5f);
check_step(bwrev, 8, "Wash 4", 60, 3.5f, 3.f, 15.5f, 20.f, 22.5f);
check_step(bwrev, 9, "Fix", 300, 5.5f, 5.f, 19.5f, 20.f, 22.5f);
check_step(bwrev, 10, "Wash 5", 60, 3.5f, 3.f, 15.5f, 20.f, 22.5f);
check_step(bwrev, 11, "Fin Rinse", 60, 3.5f, 3.f, 15.5f, 20.f, 22.5f);
/* Fix broken overlay default check: compare via id, not pointer arithmetic */
expect_u16(app_process_time_s(PROC_C41, 0), 180, "C41[0] time default");
expect_u16(app_process_time_s(PROC_C41, 1), 195, "C41[1] time default");
static const uint8_t ids[PROCESS_COUNT] = {
PROC_C41, PROC_E6, PROC_ECN2, PROC_BW, PROC_CUSTOM, PROC_BWREV
};
for (int i = 0; i < PROCESS_COUNT; i++) {
const process_def_t *p = app_process_get(ids[i]);
const process_def_t *by_name = app_process_find_name(p->name);
if (by_name != p) {
fprintf(stderr, "FAIL: find_name(%s) != get(%u)\n", p->name, ids[i]);
g_failures++;
}
}
expect_err(app_process_adjust_time(PROC_C41, 1, 5), ESP_OK, "adjust C41 step1 +5");
expect_u16(app_process_time_s(PROC_C41, 1), 200, "C41[1] after +5");
expect_u16(c41->steps[1].time_s, 195, "const C41[1].time_s unchanged");
app_process_reset_session(PROC_C41);
expect_u16(app_process_time_s(PROC_C41, 1), 195, "C41[1] after reset");
expect_err(app_process_adjust_time(6, 0, 1), ESP_ERR_INVALID_ARG, "bad id");
expect_err(app_process_adjust_time(PROC_C41, 7, 1), ESP_ERR_INVALID_ARG, "bad step");
if (g_failures == 0) {
printf("test_process: all assertions passed\n");
return 0;
}
fprintf(stderr, "test_process: %d failure(s)\n", g_failures);
return 1;
}