Two root causes for the visible power dip at every ramp-to-ramp
step boundary:
1. One-tick cascade delay: heatrate_soll was computed from
pid_hold.get_y() before pid_hold.process() ran in that tick,
so the first tick of a new ramp used the stale hold-phase
output (≈0) as the rate setpoint, driving pid_heat to near
zero. Fix: move heatrate_soll computation inside process_pid(),
after pid_hold.process().
2. Cold-start reset: pid_heat.reset() at HOLD→HEAT discarded
the hold-phase integral, so pid_heat had to ramp up from
zero on every new step. Fix: remove the reset (bumpless
transfer) — the hold-phase integral is a warm starting point
that carries the baseline hold power into the new ramp.
Together these eliminate the ~1s dip to near-zero and replace
the slow 20–30s integral ramp-up with an immediate start at
roughly hold-level power, climbing monotonically to the target.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
States, DEFAULT_THRESHOLDS, the three PIDs, FSM state vars, process_fsm(),
on_state_entered(), set_enabled(), and is_holding() move to a new
TempControllerFsm class in temp_controller_fsm.py. TempControllerBase
inherits from both APid and TempControllerFsm; States is re-exported so
existing imports in temp_controller_smith.py are unaffected.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tqxrk8uj4M3w3d3eXm3xK8