New lineEdit_ambient next to the host/connect row - type a value and
press Enter to send {"System": {"AmbientTemp": ...}}. The server now
has a recv handler for the System channel (previously send-only):
updates the real Pot's ambient temperature and, where the controller
has an internal model (Smith), its model Pots too (new
set_ambient_temperature() on TempControllerSmith), then echoes the new
value back so the status bar label and entry itself stay in sync.
70 lines
2.4 KiB
Python
Executable File
70 lines
2.4 KiB
Python
Executable File
from components.plant.pot import Pot
|
|
from components.pid.temp_controller_base import TempControllerBase, States
|
|
|
|
|
|
class TempController(TempControllerBase):
|
|
def __init__(self, dt, params, model_params, theta_amb=20):
|
|
TempControllerBase.__init__(self, dt, params)
|
|
self.dt = dt
|
|
self.last_theta_ist = 20
|
|
self.heatrate_ist = 0
|
|
|
|
# Fast model: same plant model but with zero transport delay, used
|
|
# to predict the current temperature without the dead time.
|
|
self.model = Pot(dt, {**model_params, 'Td': 0}, theta_amb)
|
|
# Delayed model: keeps the plant's assumed transport delay, so it
|
|
# can be compared like-for-like against the real (delayed) measurement.
|
|
self.model_delay = Pot(dt, model_params, theta_amb)
|
|
|
|
self.theta_ist_plant = 0
|
|
self.theta_ist_model = 0
|
|
self.theta_ist_model_delay = 0
|
|
|
|
def set_model_power(self, power):
|
|
self.model.set_power(power)
|
|
self.model_delay.set_power(power)
|
|
|
|
def set_model_params(self, M, C):
|
|
self.model.set_thermal_params(M, C)
|
|
self.model_delay.set_thermal_params(M, C)
|
|
|
|
def set_ambient_temperature(self, theta_amb):
|
|
self.model.set_ambient_temperature(theta_amb)
|
|
self.model_delay.set_ambient_temperature(theta_amb)
|
|
|
|
def on_state_entered(self, state):
|
|
if state == States.HEAT:
|
|
self.model.initial(self.theta_ist)
|
|
self.model_delay.initial(self.theta_ist)
|
|
|
|
def post_pid(self):
|
|
self.model.process()
|
|
self.model_delay.process()
|
|
|
|
def process(self):
|
|
self.theta_ist_plant = self.theta_ist_set
|
|
self.theta_ist_model = self.model.get_temperature()
|
|
self.theta_ist_model_delay = self.model_delay.get_temperature()
|
|
|
|
# Smith predictor: use the fast model's prediction, corrected by the
|
|
# mismatch between the real (delayed) plant and the delayed model,
|
|
# so the dead time drops out of the feedback path.
|
|
self.theta_ist = self.theta_ist_model + (self.theta_ist_plant - self.theta_ist_model_delay)
|
|
|
|
heatrate = (self.theta_ist - self.last_theta_ist)/self.dt*60
|
|
|
|
alpha = 0.1
|
|
self.heatrate_ist = (1-alpha) * self.heatrate_ist + alpha*heatrate
|
|
self.last_theta_ist = self.theta_ist
|
|
|
|
# Compensate for max heat rate to reduce overshoot
|
|
hold_scale = 1.0/self.heatrate_soll_set if self.heatrate_soll_set > 0 else 1.0
|
|
|
|
self.heatrate_soll = self.heatrate_soll_set * self.pid_hold.get_y()
|
|
theta_err = self.theta_soll_set - self.theta_ist
|
|
heatrate_err = self.heatrate_soll - self.heatrate_ist
|
|
|
|
diff = self.theta_soll_set - self.theta_ist
|
|
self.process_fsm(diff)
|
|
self.process_pid(theta_err, heatrate_err, hold_scale)
|