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
2021-10-13 18:35:58 +02:00
2020-11-24 20:42:27 +01:00
2020-12-17 16:28:50 +01:00
2021-08-10 15:05:57 +01:00
2021-10-18 19:15:11 +02:00
2021-10-18 18:08:41 +02:00
2021-10-19 06:32:45 +01:00

BrewPi

A Python-based controller for automating the mash/brewing process of beer: it holds a pot of liquid at target temperatures (or ramps it at a target heating rate) according to a configurable mash schedule, drives a heater and stirrer, and exposes live control/telemetry over a WebSocket so a desktop GUI (or any other client) can monitor and steer the brew.

The project began as a simulation/control-theory playground (Smith-predictor temperature control, pot transport-delay model, see docs/NonLinMPC.pdf) and has grown real-hardware backends for an induction hob and an RTD temperature probe.

Architecture

brewpi/brewpi.py            Server entry point: wires sensor, pot/plant,
                             heater, temperature controller and stirrer
                             together, runs them as asyncio tasks, and
                             serves state/commands over a WebSocket.

client/brewpi_gui.py        PyQt5 desktop client (brewpi.ui) that connects
                             to the server's WebSocket, displays live
                             temperature/power/state, and lets the user set
                             target temperature, heat rate, stirrer speed,
                             and switch the heater/stirrer on or off.

components/                 Pluggable building blocks behind factories:
  pid/                         temperature controllers: plain PID
                                ("Normal") or PID + Smith predictor
                                ("Smith", runs two internal pot models —
                                one with the plant's transport delay, one
                                without — to compensate for the dead time)
  plant/                       pot thermal model (transport-delay line)
                                used in simulation
  sensor/                      temperature sensors: simulated, or a real
                                MAX31865 RTD amplifier over SPI
  actor/                       heater and stirrer drivers: simulated, a
                                Hendi induction hob (serial protocol), or a
                                Pololu 1376 stirrer motor controller
  sud.py                       optional mash-schedule sequencer that steps
                                through a sude/*.json schedule and drives
                                the temperature controller from it

tasks/                       Async tasks (one per component) that poll
                             hardware/sim state at a fixed interval and
                             publish changes through the message dispatcher.

ws/                          Minimal WebSocket pub/sub layer: server
                             (single- and multi-user), client, and a
                             keyed message dispatcher (e.g. "Sensor",
                             "Heater", "TempCtrl", "Stirrer", "Pot", "Sud").

tracer.py                    Logs traced variables to .mat files (for
                             offline analysis/tuning in MATLAB/Octave,
                             see results.m / results_tc.m).

scripts/demos/               Standalone, eyeballed-plot demos (matplotlib)
                             exercising components/* in isolation — pid/,
                             plant/, and a full mash-schedule run in sud/.
                             Not used at runtime; run directly, e.g.
                             `python -m scripts.demos.sud.demo_sud`.

sude/                        Mash schedules ("Sud" = brew/wort), each a
                             JSON list of "heat"/"hold" steps with target
                             temperature/heat rate or hold duration,
                             per-step stirrer timing, and optional pause
                             for user confirmation.

config.json.templ            Configuration template (real hardware).
config.json.sim               Configuration template for simulation mode.

Data flow

The server (brewpi/brewpi.py) loads config.json, builds the configured sensor/heater/stirrer/controller via factories (*Factory.create(name, ...)) based on the Controller section (sensor_name, heater_name, stirrer_name, pid_type, or "sim" for any of them), and connects their outputs to each other's inputs via set_on_changed callbacks, e.g.:

  • sensor temperature → temperature controller's theta_ist
  • temperature controller output y → heater power
  • heater effective power → pot model power (simulation only)

Each component runs inside its own ATask at a configurable interval (Controller.dt, scaled by sim_warp_factor) and pushes state changes onto a keyed WebSocket channel that any connected client can subscribe to and send commands back on (e.g. {"TempCtrl": {"Soll": {"Temp": 65}}}).

Requirements

  • Python 3.8+
  • Server (brewpi/requirements.txt): numpy, scipy, websockets, dpath, and pyserial/spidev if using real hardware backends.
  • Client (client/requirements.txt): PyQt5.

Install with:

pip install -r brewpi/requirements.txt
pip install -r client/requirements.txt

Running

  1. Copy a config template to config.json next to brewpi.py and adjust it. Use config.json.sim to run entirely in simulation (no hardware needed), or config.json.templ as a starting point for real hardware (set the heater/stirrer serial ports and sensor type).

  2. Start the server:

    cd brewpi
    ./brewpi.py
    

    This serves the WebSocket on ws://0.0.0.0:8765.

  3. Start the GUI client and connect to the server's URI:

    cd client
    ./brewpi_gui.py
    

brewpi/brewpi.sh shows how this is wired up to run under a virtualenvwrapper environment ($WORKON_HOME/$BREWPI_HOME) on a Raspberry Pi-style deployment.

Mash schedules

Files under sude/ describe a brew's mash schedule ("Sud"): pot weight, malt/water weights, and a flat schedule list of steps, each either:

  • "type": "heat" — ramp to temp at rate (°C/min), or
  • "type": "hold" — hold the current target for duration seconds (omit for an immediate, zero-duration step).

Every step also carries its own stirrer timing (stirrer_speed, stirrer_time_on/stirrer_time_off in seconds — time_off: 0 means continuous), and may set user_wait_for_continue: true (with an optional user_message) to pause for confirmation once the step completes, e.g. to add malt or check gravity, instead of advancing immediately.

Set the top-level sud config key to a path under sude/ (see config.json.sim/config.json.templ) to have the server step through it automatically: components/sud.py's Sud tracks the current step and tasks/sud.py's SudTask drives the temperature controller's theta_soll/heatrate_soll from "heat" steps (advancing once theta_ist settles close to the target) and counts down "hold" steps' duration, applying each step's stirrer timing as it goes (converting stirrer_time_on/stirrer_time_off into a duty cycle/cycle time). It exposes progress (current step, remaining hold time, state, any user_message) on the "Sud" WebSocket channel and accepts {"Sud": {"Start": true}} to begin the schedule and {"Sud": {"Confirm": true}} to acknowledge a pause and move to the next step. Omit sud from the config to run without schedule automation (manual theta_soll/heatrate_soll control via the GUI, as before).

Logging & analysis

tracer.py (and tasks/tracer.py) periodically dump traced signals (temperatures, heat rates, heater power) to logs/*.mat files, which can be loaded in MATLAB/Octave — see results.m and results_tc.m — to evaluate and tune the controller.

S
Description
No description provided
Readme
14 MiB
Languages
Python 84%
JavaScript 9.4%
HTML 3.1%
CSS 2.4%
M 0.6%
Other 0.5%