# Temperature controller calibration ## Controller overview `components/pid/temp_controller_smith.py` implements a Smith predictor. Each tick it computes: ``` theta_ist = theta_ist_model + (theta_ist_plant − theta_ist_model_delay) ``` `theta_ist_model` comes from a fast internal `Pot` (transport delay Td = 0); `theta_ist_model_delay` from a second `Pot` with the full configured Td. The correction term `(theta_ist_plant − theta_ist_model_delay)` removes the dead time from the feedback path. The plant model (`components/plant/pot.py`) integrates: ``` temp += (delayed_power_in − L·M·(temp − theta_amb)) / (M·C) · dt ``` Four parameters must be calibrated: | Parameter | Unit | Meaning | |---|---|---| | C | J/(kg·K) | Specific thermal capacity | | M | kg | Mass of pot + water + grain | | L | W/(kg·K) | Heat loss coefficient | | Td | s | Transport (heater-to-sensor) delay | ## 1. Verify model-plant match: replay simulation Feed the **recorded `power_eff`** from a log file into a fresh `Pot` instance with the same parameters and compare the simulated temperature to `temp_ist`. ```python from components.plant.pot import Pot pot = Pot(dt=1.0) pot.set_plant_params({'C': C, 'M': M, 'L': L, 'Td': Td}) pot.set_ambient_temperature(theta_amb) pot.initial(samples[0]['temp_ist']) sim_temp = [] for s in samples: pot.set_power(s['power_eff']) pot.process() sim_temp.append(pot.get_temperature()) ``` Plot `sim_temp` (model) against `temp_ist` (real) and the residual `temp_ist − sim_temp`. What divergence tells you: | Symptom | Likely cause | |---|---| | Ramp slopes differ | C·M wrong | | Phase shift between power step and temperature rise | Td wrong | | Wrong equilibrium temperature during HOLD | L wrong | | Residual grows over a long run | Model drift / L temperature-dependent | ## 2. Parameter identification from log data ### Td — transport delay Cross-correlate `power_eff` with `temp_ist`. The lag at peak correlation is the actual Td. Alternatively, find a sharp power step (start of a ramp) and measure the visible delay before `temp_ist` begins rising. ### L — heat loss coefficient At steady-state HOLD, `d(temp)/dt ≈ 0`, so all input power compensates losses: ``` P_hold = L · M · (T − theta_amb) → L = P_hold / (M · (T − theta_amb)) ``` Repeat across hold phases at different temperatures and fit a line through `(T − theta_amb)` vs `P_hold` for a more robust estimate. ### C — specific heat capacity During a ramp where heat loss is small relative to input power: ``` C ≈ power_eff / (M · rate_ist_K_per_s) ``` `rate_ist` in the log is K/min; divide by 60 to get K/s. Use a mid-ramp window where `power_eff` and `rate_ist` are both stable. ## 3. Controller performance metrics These can be read directly from the log without re-simulation: | Metric | How to compute | |---|---| | Overshoot | `max(temp_ist) − temp_soll` after each setpoint step | | Settling time | First time `|temp_ist − temp_soll| < threshold` is sustained after a step | | Steady-state error | Mean of `temp_soll − temp_ist` during HOLD phase | | Rate tracking error | `rate_ist − rate_soll` during RAMP | ### Smith predictor correction signal The term `theta_ist_plant − theta_ist_model_delay` is the Smith correction; a growing correction signal indicates model drift. This is not currently written to the log. To observe it, log `theta_ist_model` and `theta_ist_model_delay` from `temp_controller_smith.py` alongside the existing fields. ## 4. Suggested addition to analyze_log.py A replay panel added to `utils/analyze_log.py` would show: - `temp_ist` (blue) vs `sim_temp` (red dashed) — model accuracy at a glance - Residual `temp_ist − sim_temp` in a separate subplot — reveals systematic parameter errors vs noise floor