Testing the windup-fix rework live against sude/sud_0030.json surfaced a
distinct bug: a HOLD overshoot from grain-fill-in cooling never decayed -
process_pid()'s pid_outer_y floor clamped to exactly 0.0, so pid_inner
fought the pot's own ambient loss to hold the overshot temperature flat
instead of declining back to setpoint (see docs/overshoot2.png).
Replace the hardcoded 0.0 floor with a configurable Outer.y_hold_min
(default 0.0, backward compatible), set to -0.1 in config.json, both
.tpl templates, and the demo scripts - small enough to avoid
reintroducing the bb5af3c limit cycle while letting HOLD request a
gentle decline matching passive ambient cooling.
Adds TestHoldOvershootRecoversToSetpoint and documents the finding in
docs/overshoot_hold_windup.md's Follow-up section and
components/pid/TODO.md. Confirmed against a live sud_0030 re-run, not
just the unit test.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M2ierBoxW3v7nUbDw3M2pE
overshoot_hold_windup.md read like a live plan ("not yet implemented")
even though the fix had already shipped; reworks it into a past-tense
record with corrected line numbers and test descriptions matching what
was actually built. Fixes a stale pid_heat reference in
temp_control_calibration.md.
TODO.md's windup item still claimed test coverage was "outstanding"
from before the test suite was added; corrects that and the "No
automated tests" item above it to reflect current coverage. Both docs
now link docs/fsm_states.png (durable) and the Claude Artifact URL
(session-scoped) for the cascade architecture diagram.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DGQhVQ2Y3yXAQTXhrxVd5u
Renames pid_hold/pid_heat/pid_cool to pid_outer/pid_inner/pid_inner_cool
to match what actually runs when, and splits inner-loop config into
Inner.Heat/Inner.Hold/Inner.Cool so the same PID instance gets a tight
yi_max ceiling only while HOLD drives it, without capping legitimate
1.5 K/min ramps. Fixes the overshoot from docs/overshoot_hold_windup.md
where a cold-water disturbance during HOLD wound up pid_heat's integral
term with no anti-windup engagement, taking ~35s+ to unwind naturally.
Breaking config change: Hold/Heat/Cool -> Outer/Inner.{Heat,Hold,Cool}
in config.json, both .tpl templates, the pid/sud demo scripts, and
replay_sim.py's CLI flags. Adds tests/components/pid/ (stdlib unittest)
covering the Pid clamp/recovery behavior and closed-loop disturbance,
ramp, and HOLD<->HEAT transition cases.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DGQhVQ2Y3yXAQTXhrxVd5u
Supersedes the earlier flat yi_max clamp / FSM-gating ideas: same inner
PID instance switches its active param set (Inner.Heat vs Inner.Hold)
by state instead of freezing, giving bumpless transfer for free and a
tight yi_max that only applies while HOLD is driving it.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KefnwbGDM8CGrhb4sFhVq9
Records the root cause of the cold-water overshoot in docs/overshoot.png
(integral windup in pid_heat with no anti-windup engagement, since y never
saturated) and the refined fix plan: gate pid_heat by FSM state instead of
a flat yi_max clamp, which was rejected after the numbers showed it would
also cripple legitimate high-rate ramps.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KefnwbGDM8CGrhb4sFhVq9
config.json isn't tracked in git, so this records what changed on the
Pi during today's connect/disconnect testing: Heater/Stirrer ports
switched from /dev/ttyUSB0//dev/ttyACM0 to stable /dev/serial/by-id/...
paths after a replug renumbered both devices, plus a reminder to
remove the pre-edit config.json.bak-20260703212821 left on the Pi.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YaPLuRPpyjWcwhMvCvpHCL
Confirmed on hardware: 15s idle while remote-enabled with the heater
on and powered auto-suspends (R? and S? both drop to 0). P? does not
read literal 0 though - it lands on 4095, the raw register's max value
past the calibrated curve's low end, not the digit 0. Any check
expecting P?=="0" to confirm zero power would be wrong on this
firmware; S? is the correct check.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GpePKZiEZWbGo9HrfuML6U
HendiCtrl.cmd() previously raised a bare "Communication error" for any
non-OK response, discarding the firmware's actual reply. It now
includes the raw echo/answer bytes, and --debug prints every
request/response. Using this, the rejected R1 during lockout turned
out to be an explicit "ERR:Invalid state" reply, not a timeout.
Also flashed and retested v1.16: plain reconnect (no --reset) now
recovers from the lockout reliably (4/4 trials), fixing the
intermittent behavior seen on v1.15.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GpePKZiEZWbGo9HrfuML6U
--reset still reliably clears the lockout on v1.15, same as v1.14. A
plain reconnect without --reset now succeeds intermittently (1 of 3
trials) where it always failed before, but that's unreliable enough to
flag as a remaining race condition rather than a fix.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GpePKZiEZWbGo9HrfuML6U
Firmware v1.14 makes --reset (DTR/RTS toggle) reliably clear the
ungraceful-disconnect lockout, unlike v1.12. A plain reconnect without
--reset still fails on both versions, so recovery is specifically tied
to the reset line, not just a fresh connection.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GpePKZiEZWbGo9HrfuML6U
Documents the stirring-induced rate noise problem, its root cause
(post-differentiation filtering), and three mitigation options: pre-filter
before differentiating, sliding-window regression, and model-based rate
from Pot.p_pot (Smith only).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NSo8R6GjoBQdStB3j67zSs