From 6e6c56d3a409fa5a5b66428ce087545efa9908ad Mon Sep 17 00:00:00 2001 From: jbruce12000 Date: Tue, 10 May 2022 12:21:55 -0400 Subject: [PATCH] adding pid_control_window parameter, removing stop_integral_windup parameter, fixed api bug --- config.py | 34 ++++++++++++++++------------------ lib/oven.py | 48 ++++++++++++++++++++++++++++++------------------ 2 files changed, 46 insertions(+), 36 deletions(-) diff --git a/config.py b/config.py index 6e4b5fd..16d03b1 100644 --- a/config.py +++ b/config.py @@ -13,7 +13,7 @@ log_format = '%(asctime)s %(levelname)s %(name)s: %(message)s' ### Server listening_ip = "0.0.0.0" -listening_port = 8081 +listening_port = 8082 ### Cost Estimate kwh_rate = 0.1319 # Rate in currency_type to calculate cost to run job @@ -73,11 +73,8 @@ pid_kd = 200 # Derivative # # Initial heating and Integral Windup # -# During initial heating, if the temperature is constantly under the -# setpoint,large amounts of Integral can accumulate. This accumulation -# causes the kiln to run above the setpoint for potentially a long -# period of time. These settings allow integral accumulation only when -# the temperature is close to the setpoint. This applies only to the integral. +# this setting is deprecated and is no longer used. this happens by +# default and is the expected behavior. stop_integral_windup = True ######################################################################## @@ -88,7 +85,7 @@ sim_t_env = 60.0 # deg C sim_c_heat = 100.0 # J/K heat capacity of heat element sim_c_oven = 5000.0 # J/K heat capacity of oven sim_p_heat = 5450.0 # W heating power of oven -sim_R_o_nocool = 0.1 # K/W thermal resistance oven -> environment +sim_R_o_nocool = 0.1 # K/W thermal resistance oven -> environment sim_R_o_cool = 0.05 # K/W " with cooling sim_R_ho_noair = 0.1 # K/W thermal resistance heat element -> oven sim_R_ho_air = 0.05 # K/W " with internal air circulation @@ -112,18 +109,19 @@ time_scale_profile = "m" # s = Seconds | m = Minutes | h = Hours - Enter and vi # this should not replace you watching your kiln or use of a kiln-sitter emergency_shutoff_temp = 2264 #cone 7 -# If the kiln cannot heat or cool fast enough and is off by more than -# kiln_must_catch_up_max_error the entire schedule is shifted until -# the desired temperature is reached. If your kiln cannot attain the -# wanted temperature, the schedule will run forever. This is often used -# for heating as fast as possible in a section of a kiln schedule/profile. -# NOTE: every time the kiln goes outside the window around the set point -# [by default 5 degrees above and five degrees below] the accumulated -# intergral will be zeroed out. If this were not done, there could be -# large build-ups of integral that makes it appear the system is not -# operating. +# If the current temperature is outside the pid control window, +# delay the schedule until it does back inside. This allows for heating +# and cooling as fast as possible and not continuing until temp is reached. kiln_must_catch_up = True -kiln_must_catch_up_max_error = 5 #degrees + +# This setting is required. +# This setting defines the window within which PID control occurs. +# Outside this window (N degrees below or above the current target) +# the elements are either 100% on because the kiln is too cold +# or 100% off because the kiln is too hot. No integral builds up +# outside the window. The bigger you make the window, the more +# integral you will accumulate. +pid_control_window = 10 #degrees # thermocouple offset # If you put your thermocouple in ice water and it reads 36F, you can diff --git a/lib/oven.py b/lib/oven.py index e495bce..a8f6260 100644 --- a/lib/oven.py +++ b/lib/oven.py @@ -197,12 +197,13 @@ class Oven(threading.Thread): log.info("Refusing to start profile - thermocouple unknown error") return - log.info("Running schedule %s" % profile.name) + self.startat = startat * 60 + self.runtime = self.startat + self.start_time = datetime.datetime.now() - datetime.timedelta(seconds=self.startat) self.profile = profile self.totaltime = profile.get_duration() self.state = "RUNNING" - self.start_time = datetime.datetime.now() - self.startat = startat * 60 + log.info("Running schedule %s starting at %d minutes" % (profile.name,startat)) log.info("Starting") def abort_run(self): @@ -215,25 +216,23 @@ class Oven(threading.Thread): temp = self.board.temp_sensor.temperature + \ config.thermocouple_offset # kiln too cold, wait for it to heat up - if self.target - temp > config.kiln_must_catch_up_max_error: + if self.target - temp > config.pid_control_window: log.info("kiln must catch up, too cold, shifting schedule") self.start_time = datetime.datetime.now() - datetime.timedelta(milliseconds = self.runtime * 1000) self.pid.iterm = 0 # kiln too hot, wait for it to cool down - if temp - self.target > config.kiln_must_catch_up_max_error: + if temp - self.target > config.pid_control_window: log.info("kiln must catch up, too hot, shifting schedule") self.start_time = datetime.datetime.now() - datetime.timedelta(milliseconds = self.runtime * 1000) self.pid.iterm = 0 def update_runtime(self): + runtime_delta = datetime.datetime.now() - self.start_time if runtime_delta.total_seconds() < 0: runtime_delta = datetime.timedelta(0) - if self.startat > 0: - self.runtime = self.startat + runtime_delta.total_seconds() - else: - self.runtime = runtime_delta.total_seconds() + self.runtime = runtime_delta.total_seconds() def update_target_temp(self): self.target = self.profile.get_target_temperature(self.runtime) @@ -481,13 +480,24 @@ class PID(): window_size = 100 error = float(setpoint - ispoint) - - if self.ki > 0: - if config.stop_integral_windup == True: - if abs(self.kp * error) < window_size: - self.iterm += (error * timeDelta * (1/self.ki)) - else: - self.iterm += (error * timeDelta * (1/self.ki)) + + # this removes the need for config.stop_integral_windup + # it turns the controller into a binary on/off switch + # any time it's outside the window defined by + # config.pid_control_window + if error < (-1 * config.pid_control_window): + log.info("kiln outside pid control window, max cooling") + self.lastErr = error + self.lastNow = now + return 0 + if error > (1 * config.pid_control_window): + log.info("kiln outside pid control window, max heating") + self.lastErr = error + self.lastNow = now + return 1 + + icomp = (error * timeDelta * (1/self.ki)) + self.iterm += (error * timeDelta * (1/self.ki)) dErr = (error - self.lastErr) / timeDelta output = self.kp * error + self.iterm + self.kd * dErr @@ -524,9 +534,11 @@ class PID(): # ((self.kp * error)/out4logs)*100, # (self.iterm/out4logs)*100, # ((self.kd * dErr)/out4logs)*100)) - log.info("pid actuals pid=%0.2f p=%0.2f i=%0.2f d=%0.2f" % (out4logs, + log.info("pid actuals pid=%0.2f p=%0.2f i=%0.2f d=%0.2f icomp=%0.2f error=%0.2f" % (out4logs, self.kp * error, self.iterm, - self.kd * dErr)) + self.kd * dErr, + icomp, + error)) return output