Commit Graph
308 Commits
Author SHA1 Message Date
jens 194156f244 Move Sud step's target temperature from ramp.temp to step.temperature
A step's target temperature applies to the whole step (it's what a
ramp ramps to and what a hold then holds at), not just its ramp phase
- promote it from a nested "ramp": {"temp": ...} to a step-level
"temperature" field, defaulted like descr/grain_mass/etc. Updates all
consumers (Sud._build_step, SudTask, the GUI's forecast estimator,
demo_sud.py) and migrates sude/sud_0010.json and the README.
2026-06-21 09:47:45 +02:00
jens e62863ca93 Fix pid_type "Normal" crashing on startup (part 2)
Found while live-testing the previous fix: brewpi.py unconditionally
wired heater.power_set to tc.set_model_power, but "Normal" has no
internal model and thus no such method - same hasattr guard already
used elsewhere (tasks/sud.py's apply_plant_params) for this.
2026-06-21 09:42:09 +02:00
jens b72977d6cb Fix pid_type "Normal" crashing on startup
PidFactory.create() calls both controllers with the same (dt, params,
model_params, theta_amb=...) signature, but TempController ("Normal")
only accepted (dt, params) - any config using pid_type "Normal" would
crash with a TypeError on startup. It has no internal model, so just
accepts and ignores the extra arguments.
2026-06-21 09:36:40 +02:00
jens 3a19e2a9dc Fix Sud cooldown detection crashing the websocket on a real run
get_theta_ist() can return numpy.float64 (Pot's transport delay line
is a numpy array internally), so the cooldown comparison produced a
numpy.bool_ instead of a plain bool. json.dumps() can't serialize that,
which silently killed the websocket connection's send loop the moment
a ramp step started - found by actually driving the feature through a
live server+GUI run instead of only unit-style tests with a directly
constructed controller.
2026-06-21 09:29:46 +02:00
jens 1170718c3b Force the heater off and show a "Cool down" indicator during a passive cooldown
A ramp step whose target is below the current actual temperature can
only be reached by ambient cooling - the heater can't actively cool.
SudTask now detects this once per ramp phase and calls the controller's
new set_cooling() override (forces y=0, bypassing the FSM) instead of
waiting for its hysteresis-based IDLE transition. The GUI shows a
blinking blue "Cool down" label in the status bar while this is active.
Progression already only advances once theta_ist matches theta_soll
regardless of direction, so no change was needed there.
2026-06-21 09:10:13 +02:00
jens 9bd9889cb0 Allow a Sud step to combine a ramp and a hold
A step can now carry both 'ramp' and 'hold': it ramps to the target
temp, then holds there for the given duration, instead of needing two
separate schedule entries. Sud.temp_reached() switches such a step
from its ramp phase into its hold phase in place (re-firing the
step-changed callback so SudTask/demo_sud re-apply the hold's stirrer
settings); user_wait_for_continue still applies once the whole step -
both phases - is done.

Merges sud_0010.json's three ramp/hold pairs into combined steps.
2026-06-21 09:01:04 +02:00
jens 56de5cf4d6 Show ambient temperature and warp factor in the GUI's status bar
Adds a 'System' channel for static, global info that doesn't belong
to any one task - sent once at startup directly from brewpi.py's
__main__ (a client connecting later still gets it via the
dispatcher's global_state replay on subscribe, same mechanism
SudTask's startup Name/Description already relies on).

Moves warp_factor out of SudTask's startup message and into this new
channel, since it's not actually Sud-specific - it's now available
(and the status bar shows it) even without a Sud configured. The
forecast plot's progress-line scaling switches to the same general
Window.warp_factor field.

Displayed via a permanent status bar widget (not cleared by the
existing transient step/schedule showMessage() calls).
2026-06-21 00:38:31 +02:00
jens 2f7d48b2cb Revert Sud hold "duration" back to minutes
Reverts 19dc7a9 ("Fix Sud hold duration units: seconds, not minutes")
per explicit instruction - duration is minutes again. Re-adds the
*60.0 conversion in Sud._advance() (hold_remaining is ticked in
simulated seconds), the matching conversion in the forecast plot's
_estimate_course(), and flips the README wording back.
2026-06-21 00:32:55 +02:00
jens 189d1e24d2 Scale the Sud forecast progress by the server's actual warp factor
SudTask now computes warp_factor = dt/interval (the same simulated-vs-
wall-clock split Pot/TempController/Stirrer already use internally)
and sends it once at startup as 'WarpFactor'. The GUI uses it to scale
real elapsed time into the forecast's simulated-schedule time axis,
so the progress line lines up with the actual sim speed instead of
assuming real time.

Also fixes SudTask.tick() to decrement hold_remaining by dt (simulated
seconds) instead of interval (wall-clock seconds) - it was the only
place in the sim using the wall-clock interval for something meant to
track simulated time, so hold steps were taking warp_factor times
longer in real time than their declared duration intends. Without
this fix the GUI's new warp-scaled progress line would race ahead of
the real server during holds.
2026-06-21 00:27:14 +02:00
jens e1d0deb8d2 Show user_message as a modal dialog when the Sud waits for confirmation
Adds a thread-safe path from on_sud_changed() (websocket recv thread)
to a modal QMessageBox on the GUI thread via Qt's cross-thread
signal/slot queuing, fired when the State transition lands on
WAIT_USER - which the schedule is already blocked on server-side, so
the dialog just gives it a UI: closing it (OK) sends Confirm.

Deliberately scoped to WAIT_USER transitions, not every UserMessage -
most steps in sude/sud_0010.json inherit the schedule's default
placeholder text ("Put user message here") rather than overriding it,
so popping a dialog for every step would mostly show that placeholder.
2026-06-21 00:15:31 +02:00
jens e171892804 Keep the plant/controller model in sync as the Sud advances
SudTask.on_step_changed() set the controller's target temp/rate on
every step, but never touched the real Pot's or the Smith predictor's
internal model's thermal mass (M/C) - those stayed frozen at
brewpi.py's startup DEFAULT_PLANT_PARAMS for the whole brew, even
though grain_mass/water_mass change per step (malt going in, water
boiling off) and demo_sud.py already recomputes them every step via
derive_plant_params(). Production SudTask just never got the same
treatment.

Adds SudTask.apply_plant_params(), called alongside the existing
theta_soll/heatrate_soll push, mirroring the demo. Needs the real Pot
now, so SudTask takes a `pot` constructor arg; set_model_params() is
only called if the configured controller actually defines one (the
"Normal" pid_type doesn't).
2026-06-21 00:05:55 +02:00
jens 13e507e2b5 Rename Sud's Download/Upload to Save/Load throughout
Renames everywhere the concept appears: Sud.download()/upload() ->
save()/load(), the 'Download'/'Upload' websocket messages ->
'Save'/'Load', the brewpi_gui menu actions, handlers and
sud_download_path -> sud_save_path, and the demo script. Behavior is
unchanged - this is naming only, from the client's perspective (save
to / load from a local file) rather than the server's.
2026-06-20 23:58:21 +02:00
jens 5d884ca3da Wire Pause/Stop to the Sud state machine, with a resumable pause
components/sud.py: adds SudState.PAUSED. pause() freezes RAMPING/
HOLDING (the hold countdown and ramp-reached checks both no-op while
PAUSED, since they're gated on the exact state they froze); start()
now doubles as resume when paused. stop() aborts back to IDLE from
any in-progress state, reusing the same reset logic as __init__/
upload() (factored into _reset_run_state()).

tasks/sud.py: maps 'Pause'/'Stop' messages to the new methods, and
turns the stirrer off on IDLE (not just DONE) so stop() actually
silences it instead of leaving the last step's stirring running.

client/brewpi_gui.py: wires the Pause/Stop toolbar actions, extends
update_sud_actions() so Start doubles as Resume and Stop stays usable
while paused, and freezes the forecast plot's progress line for the
duration of a pause (tracked via accumulated paused time) instead of
letting it drift ahead of actual progress.
2026-06-20 23:48:03 +02:00
jens 4a824d09cc Show the active step in the status bar and progress on the forecast plot
on_sud_changed() now handles 'Step' messages, showing the step's
description (and clearing it once there's no active step), and tracks
IDLE/DONE <-> running transitions to time a red vertical progress line
on the Automatic tab's forecast - elapsed real time since the brew
started, plotted against the forecast's nominal-schedule time axis so
you can see how actual progress compares to the plan.
2026-06-20 23:32:01 +02:00
jens bd658199fe Wire the Start toolbar action to actually start the Sud sequence
Sends {'Start': True} on the Sud channel, which SudTask.recv() already
maps to sud.start(). Only reachable while enabled (loaded and idle),
per update_sud_actions().
2026-06-20 23:23:26 +02:00
jens 2adcca6716 Gate Start/Pause/Stop on whether a Sud is loaded and running
Start is enabled only once a schedule is loaded and idle; Pause/Stop
only while it's actually running (RAMPING/HOLDING/WAIT_USER). All
three stay disabled until something arrives on the Sud channel (which
only happens if a SudTask is actually configured server-side), and
get disabled again on disconnect since nothing reported over a dead
connection can still be trusted.

Also drops actionStop's leftover wiring to disconnect() - now that
Stop is meant to stop a running brew, it shouldn't tear down the
websocket connection instead.
2026-06-20 23:19:01 +02:00
jens 127dd9da51 Don't freeze the GUI thread on disconnect
ws_client.disconnect() blocks until the close handshake completes
(observed ~10s when the server doesn't ack promptly), and was being
called straight from the Connect/Disconnect button's click handler -
joining that wait on the GUI thread, so the click appeared to do
nothing until the timeout passed. Run it off a throwaway thread
instead; on_ws_connect_changed() already flips the button once the
disconnect actually finishes.
2026-06-20 23:09:23 +02:00
jens d1a69f19ce Toggle Connect/Disconnect on the same button once connected
MessageDispatcherSync gains an optional on_connect_changed(bool)
callback, fired from the websocket client's background loop once the
connection is actually live/lost, so the button only flips state on a
real connection change rather than optimistically on click.
2026-06-20 23:04:43 +02:00
jens 9c3ec1dfdf Add a Connect button next to the host field; unwire it from Start
Start/Pause/Stop on the toolbar were never about the websocket
connection - Start now does nothing until that's wired to its own
purpose, and connecting is its own explicit action next to the host
text entry.
2026-06-20 23:01:18 +02:00
jens 62dcd6b541 Default the GUI's websocket URI to localhost instead of brewpi 2026-06-20 22:56:54 +02:00
jens 3289288488 Pre-visualize the estimated Sud course in the Automatic tab
Adds a static forecast plot to the (previously empty) Automatic tab,
showing the planned temperature course derived from a schedule's
declared ramp rates and hold durations alone (no plant/controller
model - just how long the schedule itself says each step takes).

The client now requests the schedule via Download right after
connecting, routing the reply to the forecast instead of a save
dialog since sud_download_path is only set for the user-initiated
download. SudTask also echoes the just-applied document back after a
successful Upload, so editing the schedule refreshes the forecast
without an extra round trip.
2026-06-20 22:54:34 +02:00
jens 0e27c66fc7 Round-trip sud_0010.json through Sud.upload()/download()
Reformats it to json.dump()'s standard 2-space-free, ensure_ascii
output (from exercising the new upload feature against the real
schedule) and adds the matching downloaded copy. No schedule content
changed.
2026-06-20 22:43:17 +02:00
jens caa94c7508 Switch the Plot tab to a bright theme with a refined grid and ticks
Drop dark_background in favor of a plain white figure, matching
demo_sud's blue/red ist/soll color convention. Add faint major/minor
gridlines, drop the top/right spines, and use label_outer() so only
the bottom subplot repeats x tick labels across the shared time axis.
2026-06-20 22:39:38 +02:00
jens 93b5667a64 Add a real-time Plot tab to brewpi_gui, mirroring demo_sud figure 1
Embeds a dark-themed matplotlib canvas (3 stacked strip charts: theta
ist/soll, heatrate ist/soll, heater power_set/power_eff) in a new Plot
tab, sampled once per second from the latest websocket-received values.

HeaterTask now also emits a PowerSet message (the pre-discretization
target power) alongside the existing Power (effective) message, since
the GUI had no way to see the set-point side of figure 1's bottom plot
otherwise.
2026-06-20 22:31:07 +02:00
jens bb94e065f4 Wire Sud upload/download into the brewpi_gui client
Adds File menu actions to save the running Sud's schedule to a local
sud.json and to replace it from one, via the Sud websocket channel's
new Download/Upload messages. File dialogs are opened up front on the
GUI thread; the actual read/write happens off the websocket recv
callback, which runs on a worker thread and must not touch Qt itself.

main_window.py is regenerated from brewpi.ui via pyuic5 5.15.11, which
also picks up an unrelated pre-existing drift (the default URI had
fallen out of sync with the .ui source).
2026-06-20 22:18:49 +02:00
jens db57f3305c Add upload/download of sud.json over the Sud websocket channel
Sud.download() returns the on-disk document; Sud.upload() validates,
persists, and resets the schedule, refusing mid-brew or malformed input
so the current schedule is never left half-applied.
2026-06-20 22:14:27 +02:00
jensandClaude Sonnet 4.6 6fb85415eb Replace config.json's Model/Plant blocks with one shared default in brewpi.py
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
2026-06-20 08:06:31 +02:00
jensandClaude Sonnet 4.6 ac0f13897c Plot the Sud demo's plant M/C over the run
Adds a third figure tracking plant.M/plant.C alongside the existing
temperature/state plots, making the per-step mass changes (malt going
in, water boiling off) visible.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CgR9tPaSzFkAwRAUyeaaCD
2026-06-20 07:56:28 +02:00
jensandClaude Sonnet 4.6 241a796ffa Make grain_mass/water_mass per-step and re-derive plant params on change
grain_mass and water_mass now live on each step (defaulted from
default.step) instead of being fixed for the whole brew, since both
change over a mash (malt going in, water boiling off).
Sud.derive_plant_params() takes them as arguments so it can be
recomputed per step; the demo re-applies the resulting M/C to both the
real plant and the controller's Smith-predictor model on every step
change via the new Pot.set_thermal_params()/
TempController.set_model_params().

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CgR9tPaSzFkAwRAUyeaaCD
2026-06-20 07:45:42 +02:00
jensandClaude Sonnet 4.6 ca0a014ffb Remove leftover debug prints from StirrerSim and TempControllerBase
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CgR9tPaSzFkAwRAUyeaaCD
2026-06-20 07:29:02 +02:00
jensandClaude Sonnet 4.6 33f75c22d2 Print each Sud step's number and description in the demo
Makes it easier to follow which schedule step is currently active
when watching the demo run.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CgR9tPaSzFkAwRAUyeaaCD
2026-06-20 07:28:26 +02:00
jensandClaude Sonnet 4.6 123318ac85 Redesign Sud schedule format: separate ramp/hold keys with defaults
Steps now declare a "ramp" or "hold" key instead of a flat "type", and
unspecified fields (including nested stirrer settings) fall back to a
new top-level default.step structure. Stirrer config is now
interval_time/on_ratio, mapping directly onto AStirrer's cycle
time/duty cycle instead of separate on/off durations. Update
components/sud.py, tasks/sud.py, the Sud demo, sude/sud_0010.json, and
the README to match.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CgR9tPaSzFkAwRAUyeaaCD
2026-06-20 07:20:52 +02:00
jensandClaude Sonnet 4.6 2dcf644bed Document Sud.derive_plant_params() in the README
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 19:36:31 +02:00
jensandClaude Sonnet 4.6 b6551c6433 Derive Pot's M/C from pot/grain/water mass instead of hardcoding them
Add Sud.derive_plant_params(), which combines pot_mass/pot_material/
grain_mass/water_mass into a single lumped (mass, specific heat) pair
using approximate specific-heat constants for water, grain, and (by a
small material lookup table) the pot itself. demo_sud.py now builds
its plant_params from this instead of hardcoded C/M values, keeping
L/Td as the only manually-tuned plant parameters.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 19:34:15 +02:00
jensandClaude Sonnet 4.6 18d9af960f Remove the stirrer duty cycle trace from the Sud demo plot
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 19:26:09 +02:00
jensandClaude Sonnet 4.6 15650b21fb Plot the Sud demo's time axis in seconds instead of minutes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 19:23:38 +02:00
jensandClaude Sonnet 4.6 ace5054983 Update sample hold durations to realistic second values
Now that duration is correctly read as seconds, bump the sample
schedule's hold steps from the placeholder 30-40s to more realistic
200-400s.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 19:17:57 +02:00
jensandClaude Sonnet 4.6 19dc7a95bb Fix Sud hold duration units: seconds, not minutes
_advance() multiplied "duration" by 60, treating it as minutes;
stirrer_speed (percent) and stirrer_time_on/off (seconds) were already
handled correctly with no conversion. Also fix the README's "duration
minutes" wording to match.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 19:17:42 +02:00
jensandClaude Sonnet 4.6 b33f89a569 Adapt Sud/SudTask/demo to the new flat heat/hold schedule schema
sude/sud_0010.json moved from a list of combined ramp+hold+wait
"Rasten" with global stirrer constants to a flat "schedule" list of
explicit {"type": "heat"|"hold", ...} steps, each carrying its own
stirrer_speed/stirrer_time_on/stirrer_time_off and an optional
user_wait_for_continue (+ user_message).

- components/sud.py: Sud now tracks the current `step` instead of
  `rast`; "heat" steps enter RAMPING (advanced externally via
  temp_reached()), "hold" steps enter HOLDING and count down
  `duration` minutes (0 if omitted). Either step type can pause in
  WAIT_USER via user_wait_for_continue, surfaced through the new
  user_message attribute.
- tasks/sud.py: SudTask only pushes theta_soll/heatrate_soll on
  "heat" steps, converts each step's stirrer_time_on/off into a
  duty_cycle/cycle_time on every step change (replacing the old
  state-based RAMPING/HOLDING stirrer switching), and broadcasts
  user_message changes. Stirring is now fully schedule-driven instead
  of implicitly stopped on WAIT_USER/DONE (DONE still stops it, since
  there's no step left to read settings from).
- scripts/demos/sud/demo_sud.py: mirrors the same step-driven wiring.
- README's Mash schedules section rewritten for the new schema.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 19:09:43 +02:00
jensandClaude Sonnet 4.6 2845051350 Refresh design-backlog docs after Pot's gain/theta cleanup
Drop the now-stale "config.json.templ/.sim still carry dead
Model.kn/Plant.kn keys" note (they were deleted), note that Pot now
always starts at theta_amb (no separate initial-temp param) and that
dropping the gain (efficiency) factor entirely is a partial realism
regression, not just a fix. Point the config-validation item at the
gain key that's still dead in config.json.sim. Also update demo_sud.py
to match the Pot params Pot.__init__ actually reads.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 18:37:03 +02:00
jensandClaude Sonnet 4.6 17ca79da1a Simplify Pot: drop gain/theta params, seed initial temp from ambient
[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>
2026-06-19 18:32:28 +02:00
jensandClaude Sonnet 4.6 5ae73cce57 Document scripts/demos/ in the README architecture overview
It existed since the matplotlib-demo relocation but was never added
to the architecture listing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 18:13:55 +02:00
jensandClaude Sonnet 4.6 395eda5eb4 Add a comprehensive mash-schedule demo
Drives a real Smith-predictor TempController, Pot, HeaterSim, and
StirrerSim through sude/sud_0010.json end-to-end (mirroring brewpi.py's
wiring, including the model/plant power-discretization mismatch and
tasks/sud.py's stirrer-switching rules), auto-confirming waitForUser
pauses so it completes unattended. Plots theta/heatrate (actual vs.
target), heater power_set/power_eff, Sud state, rest index, and
stirrer speed/duty cycle over time.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 18:11:13 +02:00
jensandClaude Sonnet 4.6 7e131df4ca Add mash-schedule (Sud) automation: temp/rate sequencing + stirrer switching
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>
2026-06-19 17:54:42 +02:00
jensandClaude Sonnet 4.6 1e89a58370 Replace Pid.scale()'s mutable gain factor with a stateless process() arg
Pid.scale(k) set self.k as persistent state, mutated from outside the
class and never reset in reset(), so a stale scale factor could survive
a state-transition reset. Replace it with a plain scale=1.0 argument on
Pid.process(), and have temp_controller.py/temp_controller_smith.py
compute the heat-rate-overshoot compensation factor themselves and pass
it through process_pid() each tick instead of mutating pid_hold's state.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 17:37:55 +02:00
jensandClaude Sonnet 4.6 519bbd485f Check off theta_amb backlog item now that it's config-driven
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 17:27:47 +02:00
jensandClaude Sonnet 4.6 b3d965fec9 Drop debug prints from TcTask's ist-value change handlers
on_temp_ist_changed/on_rate_ist_changed printed every update in
addition to forwarding it over the WebSocket, spamming stdout once
per controller tick.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 17:17:19 +02:00
jensandClaude Sonnet 4.6 80da55da85 Make ambient temperature configurable and wire it everywhere it was hardcoded
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>
2026-06-19 17:16:57 +02:00
jensandClaude Sonnet 4.6 6711ab2200 Remove leftover debug prints from Pot.process()
The unconditional p_loss/theta_amb/temp prints (left over from the
heat-loss-units fix) ran every tick, spamming stdout in both the
server and every demo script. Drop them and check off the matching
TODO.md item.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 17:03:48 +02:00
jensandClaude Sonnet 4.6 40d57dc68a Refresh README and design-backlog docs against current code
Both TODO.md backlogs and the README's architecture section still
described the Kalman-filter/heat-diffusion design that's since been
replaced by the delay-line Pot model and Kalman-free Smith predictor.
Check off the items that rewrite already fixed (heat-loss units,
transport delay vs. single-pole lag, three-Kalman-tuning), note how
the kn/sensor-noise item was resolved differently than proposed, and
add newly-spotted issues: brewpi.py wiring set_model_power
unconditionally (breaks pid_type "Normal"), kalman.py being dead code
in production, stale Kalman/kn keys in the config templates, and
debug print()s left in Pot.process().

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 17:02:04 +02:00