Both controllers had nearly identical process_fsm(), process_pid(), and all getters/setters; only Kalman/model setup and process() genuinely differed, and the duplication had already drifted (the Smith variant resets model state on entering HEAT, the plain one didn't). Add TempControllerBase with the shared logic and three small hooks (init_kalman, on_state_entered, post_pid) subclasses use to plug in their own Kalman/model behavior. Each subclass now contains only what makes it different. Also fixes a latent crash: TempController's constructor only accepted (dt, params), but PidFactory/brewpi.py always call it with a third model_params arg, so pid_type "Normal" would have raised TypeError. The shared base's model_params=None default fixes this. Also initializes the Smith controller's trace attributes in __init__ instead of leaving them undefined until the first process() call. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
113 lines
2.7 KiB
Python
113 lines
2.7 KiB
Python
from components import APid
|
|
from components.pid.pid import Pid
|
|
from components.pid.tc_constants import States, THRESH_HOLD_IDLE, THRESH_HOLD_HEAT, \
|
|
THRESH_IDLE_HEAT, THRESH_IDLE_HOLD, THRESH_HEAT_HOLD, THRESH_HEAT_IDLE
|
|
|
|
|
|
class TempControllerBase(APid):
|
|
def __init__(self, dt, params, model_params=None):
|
|
APid.__init__(self)
|
|
self.pid_hold = Pid(dt)
|
|
self.pid_rate = Pid(dt)
|
|
self.theta_ist_set = 0
|
|
self.theta_soll_set = 0
|
|
self.heatrate_ist_set = 0
|
|
self.heatrate_soll_set = 1.0
|
|
self.heatrate_soll = 1.0
|
|
self.theta_ist = 0
|
|
self.heatrate_ist = 0
|
|
self.params = params
|
|
self.model_params = model_params
|
|
self.y = -1
|
|
self.state = States.INIT
|
|
self.use_kalman = True
|
|
self.pid_hold.set_params(params['Hold'])
|
|
self.pid_rate.set_params(params['Heat'])
|
|
self.is_startup = True
|
|
|
|
def init_kalman(self, value):
|
|
raise NotImplementedError
|
|
|
|
def on_state_entered(self, state):
|
|
pass
|
|
|
|
def post_pid(self):
|
|
pass
|
|
|
|
def set_theta_ist(self, value):
|
|
self.theta_ist_set = value
|
|
if self.is_startup:
|
|
self.is_startup = False
|
|
self.init_kalman(value)
|
|
|
|
def get_theta_ist(self):
|
|
return self.theta_ist
|
|
|
|
def set_heatrate_ist(self, value):
|
|
self.heatrate_ist_set = value
|
|
|
|
def get_heatrate_ist(self):
|
|
return self.heatrate_ist
|
|
|
|
def set_theta_soll(self, value):
|
|
self.theta_soll_set = value
|
|
|
|
def get_theta_soll(self):
|
|
return self.theta_soll
|
|
|
|
def get_theta_soll_set(self):
|
|
return self.theta_soll_set
|
|
|
|
def set_heatrate_soll(self, value):
|
|
self.heatrate_soll_set = value
|
|
|
|
def get_heatrate_soll(self):
|
|
return self.heatrate_soll
|
|
|
|
def get_heatrate_soll_set(self):
|
|
return self.heatrate_soll_set
|
|
|
|
def process_fsm(self, diff):
|
|
state_next = self.state
|
|
if self.state == States.INIT:
|
|
if not self.is_startup:
|
|
state_next = States.IDLE
|
|
elif self.state == States.IDLE:
|
|
if diff >= THRESH_IDLE_HEAT:
|
|
state_next = States.HEAT
|
|
self.pid_rate.reset()
|
|
elif diff >= -THRESH_IDLE_HOLD:
|
|
state_next = States.HOLD
|
|
self.pid_rate.reset()
|
|
elif self.state == States.HOLD:
|
|
if diff >= THRESH_HOLD_HEAT:
|
|
state_next = States.HEAT
|
|
self.pid_rate.reset()
|
|
elif diff <= -THRESH_HOLD_IDLE:
|
|
state_next = States.IDLE
|
|
elif self.state == States.HEAT:
|
|
if diff <= -THRESH_HEAT_IDLE:
|
|
state_next = States.IDLE
|
|
elif diff <= THRESH_HEAT_HOLD:
|
|
state_next = States.HOLD
|
|
self.pid_hold.reset()
|
|
|
|
if state_next != self.state:
|
|
self.state = state_next
|
|
print("New state = {}".format(state_next))
|
|
self.on_state_entered(state_next)
|
|
|
|
def process_pid(self, theta_err, heatrate_err):
|
|
self.pid_hold.process(theta_err, -self.theta_ist)
|
|
self.pid_rate.process(heatrate_err, -self.heatrate_ist)
|
|
|
|
if self.state == States.IDLE:
|
|
self.y = 0
|
|
else:
|
|
self.y = self.pid_rate.get_y()
|
|
|
|
self.post_pid()
|
|
|
|
def get_power(self):
|
|
return self.y
|