Commit Graph
11 Commits
Author SHA1 Message Date
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 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 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 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 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 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 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 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