Captures the ramp/hold/user-confirm phase order and a couple of non-obvious clarifications from a design discussion (ramp is gated purely by the schedule's 'ramp' key, not actual-vs-target temp; WAIT_USER is a distinct phase, not an extended hold), plus one open gap: pure-hold steps never re-set the controller's target temperature. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LhiQe64F74uHV8jzuoSa5K
59 lines
2.7 KiB
Markdown
59 lines
2.7 KiB
Markdown
# components/sud.py step lifecycle spec
|
|
|
|
Confirmed spec for a `Sud` schedule step's internal phase order
|
|
(`components/sud.py`), from a design discussion clarifying how `ramp`/
|
|
`hold`/user-confirm interact.
|
|
|
|
## Phase order
|
|
|
|
`ramp` -> `hold` -> user-confirm. Each step independently opts into
|
|
`ramp` and/or `hold` by including that key (`_build_step()`,
|
|
`sud.py:35-45`); the other phase is skipped entirely if its key is
|
|
absent. The `'ramp'` key's presence is the *only* trigger for ramping
|
|
(`_advance()`, `sud.py:192-210`) - there is no diff-based decision
|
|
(`T_actual` vs. `T_target`) anywhere that decides whether to ramp, and
|
|
no internal setpoint-trajectory limiter in the temperature controller
|
|
(`set_theta_soll()` is an instant, unconditional assignment,
|
|
`components/pid/temp_controller_base.py:81-82`). The `ramp` key both
|
|
gates ramping *and* carries its settings (`rate`); it does not merely
|
|
override settings on top of an always-on ramp.
|
|
|
|
## Duration `0`
|
|
|
|
Supported for hold: `hold_remaining = duration * 60.0` can be `0`;
|
|
`tick()` (`sud.py:186-190`) finishes the step on the next tick once
|
|
`hold_remaining <= 0`.
|
|
|
|
## End-of-step branching
|
|
|
|
`_finish_step()` (`sud.py:212-216`): no `user_wait_for_continue` ->
|
|
advances immediately to the next step; `user_wait_for_continue: true`
|
|
-> enters `WAIT_USER`, resumed only by `confirm()`.
|
|
|
|
`WAIT_USER` is not "hold, prolonged": `HOLDING` and `WAIT_USER` are
|
|
distinct `SudState` values. The hold countdown runs to completion
|
|
*before* `WAIT_USER` begins - `tick()` only decrements
|
|
`hold_remaining` while `state == HOLDING`, so the timer is frozen
|
|
(not extended) during the wait. It delays the next step, but it's a
|
|
separate third phase, not a longer hold.
|
|
|
|
The gating field is `user_wait_for_continue`, not "presence of a user
|
|
message": `user_message` (display text) and `user_wait_for_continue`
|
|
(the actual block) are independent fields - a step can show a message
|
|
without blocking, or block silently.
|
|
|
|
## Open items
|
|
|
|
- [ ] **Pure-hold steps never (re-)set the controller's target
|
|
temperature.** For a step with only `hold` (no `ramp`),
|
|
`tasks/sud.py`'s `on_step_changed` never calls `tc.set_theta_soll()`
|
|
- the guard at line 73 requires `ramp is not None`. The
|
|
controller's target temperature for a pure-hold step is whatever
|
|
was left from the previous step's ramp; there's no explicit
|
|
fallback to the hold step's own `temperature` field. `README.md`
|
|
(Mash schedules section) documents this as designed behavior
|
|
("`SudTask` drives ... `theta_soll`/`heatrate_soll` from ramp
|
|
steps ... counts down hold steps' duration"), so it's intentional,
|
|
not a bug - but it means a schedule can't express a temperature
|
|
jump straight into a hold without a `ramp` block.
|