Both blocks were nearly identical (config.json.sim's differing M and
unused gain key being the only divergence), so there's no need for
per-config tuning here. DEFAULT_PLANT_PARAMS in brewpi.py now feeds
both the real Pot plant and the temperature controller's
Smith-predictor model. No demo scripts read these config keys, so
nothing else needed updating.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CgR9tPaSzFkAwRAUyeaaCD
[Pot] - removed the gain (efficiency) factor and the separate
"theta" initial-temperature param; Pot now starts at theta_amb and
set_power()/get_power() operate on p_in directly. Added
set_ambient_temperature() for live ambient updates. Dropped the
now-unused "theta"/"gain" keys from Model/Plant in config.json.sim
and config.json.templ (also fixes a JSON syntax error in
config.json.templ: trailing commas left over after removing "gain"
made Model/Plant fail to parse).
[Sensor] - fix missing space in TempSensorSim.temperature()'s
offset/variance expression (cosmetic, no behavior change).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
brewpi.py had a dead -m/--model CLI arg and sude/*.json schedules were
pure data with no code to drive them, despite the README documenting
them as if they were already wired up.
Add components/sud.py's Sud, which loads a sude/*.json schedule and
steps through its Rasten (ramp -> hold -> optional wait-for-user ->
next rest), and tasks/sud.py's SudTask, which drives the temperature
controller's theta_soll/heatrate_soll from the current rest and
switches the stirrer between continuous (ramping) and intermittent
(holding, via stirrSpeedRast/stirrDutyRast/stirrCycleTime) operation.
Exposes progress on a new "Sud" WebSocket channel and accepts
Start/Confirm commands. Enabled via a new optional top-level "sud"
config key (see config.json.templ/.sim).
"Reached target" is detected via abs(theta_ist - theta_soll_set) <
tolerance rather than tc.state == HOLD, since the latter can still
read HOLD left over from the previous rest for one tick after a new
target is pushed (tc.state only updates on the controller's own,
independently-scheduled process() tick).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
brewpi.py passed a literal 20 as theta_amb to Pot, and
temp_controller_smith.py's two internal Pot models (and every demo's
Pot/TempController instantiation) silently relied on Pot's default of
20 instead. Add a top-level ambient_temperature key to
config.json(.templ/.sim), thread it through brewpi.py into both the
real plant and the Smith predictor's models via a new theta_amb param
on TempController, and make the demos pass it explicitly too.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
THRESH_HOLD_IDLE/HOLD_HEAT/IDLE_HEAT/IDLE_HOLD/HEAT_HOLD/HEAT_IDLE in
tc_constants.py were hardcoded module-level constants shared by every
controller instance, so tuning the IDLE/HOLD/HEAT hysteresis required
a code change.
Replace them with DEFAULT_THRESHOLDS plus an optional
TempCtrl.Thresholds config section, merged at construction time in
TempControllerBase.__init__ so configs that omit the section keep
behaving exactly as before. Documented the new section in
config.json.templ; left config.json.sim without it to exercise the
default-fallback path.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>