diff --git a/README.md b/README.md index 95258d4..28a6702 100644 --- a/README.md +++ b/README.md @@ -91,8 +91,10 @@ sude/ Mash schedules ("Sud" = brew/wort), each a 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. +config.json.templ Configuration template - the "sim" component + names (sensor/heater/stirrer/plant) already + default to simulation; point Heater/Stirrer + at real serial ports to switch to hardware. ``` ### Data flow @@ -168,10 +170,11 @@ 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). +1. Copy `config.json.templ` to `config.json` next to `brewpi.py` - it + already runs entirely in simulation (no hardware needed) as-is; switch + any of the `sensor_name`/`heater_name`/`stirrer_name`/`plant_name` + `"sim"` values to a real backend, and set the corresponding serial + port, to use real hardware. 2. Start the server: ```bash @@ -311,6 +314,17 @@ re-anchoring itself to match whatever's actually happening would no longer be a meaningful baseline to compare reality against. It's computed once, up front, and left alone. +`SudTask._send_forecast()` thins the curve to at most `MAX_FORECAST_POINTS` +(1000) before broadcasting it - a fine enough `dt` over a multi-hour brew +can otherwise produce a single `Forecast` message of several MB, large +enough to exceed the `websockets` library's default 1 MiB `max_size` and +get the connection closed outright (code 1009). `SudTask.forecast_t`/ +`forecast_theta` themselves stay at full simulated resolution (used for +the exact-match truncation in `_continue_forecast_after_confirm()` below, +and for `SudLogTask`'s full-fidelity `logs/forecast_*.json` - see "Logging +& analysis") - only the copy actually sent to clients is thinned, which +the GUI's few-hundred-pixel-wide plot can't show the difference from anyway. + The one wrinkle is a step with `user_wait_for_continue`: a human's response time genuinely can't be forecast. Rather than stall the whole estimate there, `SudForecastEstimator.estimate()` models it as a zero-delay diff --git a/components/pid/TODO.md b/components/pid/TODO.md index 6bb19a3..bc62804 100644 --- a/components/pid/TODO.md +++ b/components/pid/TODO.md @@ -58,10 +58,10 @@ git history for `temp_controller.py`/`temp_controller_smith.py`). schema/dataclass validation layer at config-load would surface errors immediately instead of mid-`__init__` — and would have caught the dead `TempCtrl.Kalman` / `Model.kn` / `Plant.kn` keys that used - to sit unused in `config.json.templ`/`.sim` (since deleted) and the - `Model.gain`/`Plant.gain` keys still doing the same in - `config.json.sim` right now — `Pot` dropped `gain` entirely, see - `components/plant/TODO.md`. + to sit unused in `config.json.templ`/`.sim` (since deleted), and the + `Model.gain`/`Plant.gain` keys that did the same in `config.json.sim` + before that file was removed entirely — `Pot` dropped `gain` + entirely, see `components/plant/TODO.md`. - [ ] **`kalman.py` is now dead code in production.** Neither `temp_controller.py` nor `temp_controller_smith.py` uses `Kalman` diff --git a/components/plant/TODO.md b/components/plant/TODO.md index 6058040..a4b8075 100644 --- a/components/plant/TODO.md +++ b/components/plant/TODO.md @@ -46,9 +46,10 @@ the current `delay`-line rewrite of `Pot`. and partially regressed: `Pot` also dropped its `gain` (heater efficiency) factor entirely — `process()` now applies 100% of commanded power with no loss, whereas before `params['gain']` could - model e.g. an 80% efficient transfer. `Model.gain`/`Plant.gain` are - now dead keys in `config.json.sim` (already absent from `.templ`) — - see `components/pid/TODO.md`'s config-validation item. Still no + model e.g. an 80% efficient transfer. `Model.gain`/`Plant.gain` were + dead keys in `config.json.sim` (already absent from `.templ`) before + that file was removed entirely — see `components/pid/TODO.md`'s + config-validation item. Still no heater power saturation enforced inside `Pot` itself (handled ad hoc in demo harnesses), no evaporation/lid heat loss, and no varying thermal mass `M` as volume changes (e.g. boil-off, adding diff --git a/config.json.sim b/config.json.sim deleted file mode 100755 index 5774f4c..0000000 --- a/config.json.sim +++ /dev/null @@ -1,41 +0,0 @@ -{ - "ambient_temperature": 20, - "Controller" : { - "dt": 0.1, - "sim_warp_factor": 10.0, - "stirrer_name": "sim", - "heater_name": "sim", - "sensor_name": "sim", - "plant_name": "sim", - "pid_type": "Smith" - }, - "TempCtrl": { - "Hold": { - "kp": 0.25, - "ki": 0.0, - "kd": 0.0, - "kt": 0.0 - }, - "Heat": { - "kp": 0.05, - "ki": 0.01, - "kd": 0.0, - "kt": 1.5 - }, - "Cool": { - "kp": 0.05, - "ki": 0.01, - "kd": 0.0, - "kt": 1.5 - } - }, - "Heater": { - "port": "/dev/ttyUSB0", - "speed": "115200" - }, - "Stirrer": { - "port": "/dev/ttyACM0", - "speed": "115200" - } -} - diff --git a/config.json.templ b/config.json.templ index 8167304..2ff95df 100644 --- a/config.json.templ +++ b/config.json.templ @@ -2,7 +2,7 @@ "ambient_temperature": 20, "Controller" : { "dt": 1, - "sim_warp_factor": 10.0, + "sim_warp_factor": 50.0, "stirrer_name": "sim", "heater_name": "sim", "sensor_name": "sim", @@ -17,14 +17,14 @@ "kt": 0.0 }, "Heat": { - "kp": 0.05, - "ki": 0.01, + "kp": 0.08, + "ki": 0.02, "kd": 0.0, "kt": 1.5 }, "Cool": { - "kp": 0.05, - "ki": 0.01, + "kp": 0.08, + "ki": 0.02, "kd": 0.0, "kt": 1.5 }, diff --git a/server/brewpi.py b/server/brewpi.py index 9014c76..49da7f3 100755 --- a/server/brewpi.py +++ b/server/brewpi.py @@ -37,20 +37,19 @@ class Tee: if __name__ == '__main__': - os.makedirs("logs", exist_ok=True) - log_path = "logs/brewpi.{}.log".format(time.strftime("%Y%m%d%H%M%S", time.localtime())) + parser = ap.ArgumentParser() + parser.add_argument("-c", "--config", default="config.json") + parser.add_argument("-m", "--logdir", default="logs") + + args = parser.parse_args() + + os.makedirs(args.logdir, exist_ok=True) + log_path = f"{args.logdir}/brewpi.{time.strftime("%Y%m%d%H%M%S", time.localtime())}.log" log_file = open(log_path, "a", buffering=1) sys.stdout = Tee(sys.stdout, log_file) sys.stderr = Tee(sys.stderr, log_file) print("Logging to {}".format(log_path)) - parser = ap.ArgumentParser() - parser.add_argument("-d", "--debug", action="store_true") - parser.add_argument("-c", "--config", default="config.json") - parser.add_argument("-m", "--model", default="model.json") - parser.add_argument("-s", "--sim", action="store_true") - - args = parser.parse_args() config = json.load(open(args.config)) dispatcher = MessageDispatcher() server = WsServerMultiUser(listener=dispatcher)