diff --git a/README.md b/README.md new file mode 100644 index 0000000..ee8b75d --- /dev/null +++ b/README.md @@ -0,0 +1,128 @@ +# 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 (Kalman filter + +Smith-predictor temperature control, pot heat-diffusion model, see +[`docs/NonLinMPC.pdf`](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 (PID, Smith + predictor + Kalman filter) and the math + model used for prediction + plant/ pot heat-diffusion model 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 + +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"). + +tracer.py Logs traced variables to .mat files (for + offline analysis/tuning in MATLAB/Octave, + see results.m / results_tc.m). + +sude/ Mash schedules ("Sud" = brew/wort), each a + JSON list of temperature rests ("Rasten") + with target temperature, heat rate, 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` (and `PyQt5.Qwt` for plotting). + +Install with: + +```bash +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: + + ```bash + 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: + + ```bash + 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: pot weight, malt/water +weights, stirrer speed/duty, and a list of temperature rests, each with a +target temperature, heating rate, and whether to pause for user confirmation +before continuing (e.g. to add malt or check gravity). + +## 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. diff --git a/__init__.py b/brewpi/__init__.py similarity index 100% rename from __init__.py rename to brewpi/__init__.py diff --git a/brewpi.py b/brewpi/brewpi.py similarity index 100% rename from brewpi.py rename to brewpi/brewpi.py diff --git a/brewpi.sh b/brewpi/brewpi.sh similarity index 100% rename from brewpi.sh rename to brewpi/brewpi.sh diff --git a/brewpi/requirements.txt b/brewpi/requirements.txt new file mode 100644 index 0000000..e69de29 diff --git a/client/__init__.py b/client/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/brewpi.ui b/client/brewpi.ui similarity index 100% rename from brewpi.ui rename to client/brewpi.ui diff --git a/brewpi_gui.py b/client/brewpi_gui.py similarity index 100% rename from brewpi_gui.py rename to client/brewpi_gui.py diff --git a/brewpi_gui.sh b/client/brewpi_gui.sh similarity index 100% rename from brewpi_gui.sh rename to client/brewpi_gui.sh diff --git a/brewpi_win.py b/client/brewpi_win.py similarity index 100% rename from brewpi_win.py rename to client/brewpi_win.py diff --git a/client/requirements.txt b/client/requirements.txt new file mode 100644 index 0000000..067539d --- /dev/null +++ b/client/requirements.txt @@ -0,0 +1 @@ +pyqt5 \ No newline at end of file diff --git a/ws_client_gui.py b/client/ws_client_gui.py similarity index 100% rename from ws_client_gui.py rename to client/ws_client_gui.py diff --git a/ws/__init__.py b/ws/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ws/client/__init__.py b/ws/client/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ws/server/__init__.py b/ws/server/__init__.py new file mode 100644 index 0000000..e69de29