From bfde2e04210e9f8c8e470801ec6cdc668063db59 Mon Sep 17 00:00:00 2001 From: jbruce Date: Tue, 15 Nov 2022 10:46:00 -0500 Subject: [PATCH 01/13] fix ticks to be even hours/mins/secs on graph --- config.py | 2 +- public/assets/js/picoreflow.js | 70 +++++++++++++++++++++++++++------- 2 files changed, 57 insertions(+), 15 deletions(-) diff --git a/config.py b/config.py index 77d3736..3909e03 100644 --- a/config.py +++ b/config.py @@ -107,7 +107,7 @@ sim_R_ho_air = 0.05 # K/W " with internal air circulation temp_scale = "f" # c = Celsius | f = Fahrenheit - Unit to display time_scale_slope = "h" # s = Seconds | m = Minutes | h = Hours - Slope displayed in temp_scale per time_scale_slope -time_scale_profile = "m" # s = Seconds | m = Minutes | h = Hours - Enter and view target time in time_scale_profile +time_scale_profile = "h" # s = Seconds | m = Minutes | h = Hours - Enter and view target time in time_scale_profile # emergency shutoff the profile if this temp is reached or exceeded. # This just shuts off the profile. If your SSR is working, your kiln will diff --git a/public/assets/js/picoreflow.js b/public/assets/js/picoreflow.js index 95c6ede..3666579 100644 --- a/public/assets/js/picoreflow.js +++ b/public/assets/js/picoreflow.js @@ -188,23 +188,51 @@ function hazardTemp(){ } } -function timeTickFormatter(val) +function timeTickFormatter(val,axis) { - if (val < 1800) - { - return val; - } - else - { - var hours = Math.floor(val / (3600)); - var div_min = val % (3600); - var minutes = Math.floor(div_min / 60); +console.log(val) - if (hours < 10) {hours = "0"+hours;} - if (minutes < 10) {minutes = "0"+minutes;} +// hours +if(axis.max>3600) { + //var hours = Math.floor(val / (3600)); + //return hours; + return Math.floor(val/3600); + } - return hours+":"+minutes; - } +// minutes +if(axis.max<=3600) { + return Math.floor(val/60); + } + +// seconds +if(axis.max<=60) { + return val; + } + + +//var div_min = val % (3600); +//var minutes = Math.floor(div_min / 60); + + +//if(val < 60) { return "0:00:" + ("00"+val).slice(-3);} +//if(val > 60 and val < 3600) { return "0: + +// if (val < 1800) +// { +// return val; +// } +// else +// { +// var hours = Math.floor(val / (3600)); +// var div_min = val % (3600); +// var minutes = Math.floor(div_min / 60); + +// if (hours < 10) {hours = "0"+hours;} +// if (minutes < 10) {minutes = "0"+minutes;} + + //return hours+":"+minutes; +// return hours+":00"; +// } } function runTask() @@ -366,6 +394,19 @@ function saveProfile() leaveEditMode(); } +function get_tick_size() { +switch(time_scale_profile){ + case "s": + return 1; + case "m": + return 60; + case "h": + return 3600; + } +return 3600; +} + + function getOptions() { @@ -395,6 +436,7 @@ function getOptions() min: 0, tickColor: 'rgba(216, 211, 197, 0.2)', tickFormatter: timeTickFormatter, + tickSize: get_tick_size(), font: { size: 14, From 9b78ea005c9aa7bc48dc68adf694b82dd16e9f9b Mon Sep 17 00:00:00 2001 From: jbruce Date: Tue, 15 Nov 2022 11:02:21 -0500 Subject: [PATCH 02/13] clean up my mess --- public/assets/js/picoreflow.js | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/public/assets/js/picoreflow.js b/public/assets/js/picoreflow.js index 3666579..7ceb50e 100644 --- a/public/assets/js/picoreflow.js +++ b/public/assets/js/picoreflow.js @@ -190,8 +190,6 @@ function hazardTemp(){ function timeTickFormatter(val,axis) { -console.log(val) - // hours if(axis.max>3600) { //var hours = Math.floor(val / (3600)); @@ -208,31 +206,6 @@ if(axis.max<=3600) { if(axis.max<=60) { return val; } - - -//var div_min = val % (3600); -//var minutes = Math.floor(div_min / 60); - - -//if(val < 60) { return "0:00:" + ("00"+val).slice(-3);} -//if(val > 60 and val < 3600) { return "0: - -// if (val < 1800) -// { -// return val; -// } -// else -// { -// var hours = Math.floor(val / (3600)); -// var div_min = val % (3600); -// var minutes = Math.floor(div_min / 60); - -// if (hours < 10) {hours = "0"+hours;} -// if (minutes < 10) {minutes = "0"+minutes;} - - //return hours+":"+minutes; -// return hours+":00"; -// } } function runTask() @@ -406,7 +379,6 @@ switch(time_scale_profile){ return 3600; } - function getOptions() { From f5336ec2a11bfad2fc3ca4df7cfaf65349e0564d Mon Sep 17 00:00:00 2001 From: James Kirikland Garner Date: Wed, 14 Dec 2022 16:32:10 -0800 Subject: [PATCH 03/13] Speeding up simulator. --- lib/oven.py | 22 +++++++++++++++++++--- lib/ovenWatcher.py | 9 ++++++++- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/lib/oven.py b/lib/oven.py index 7503fac..45fd05e 100644 --- a/lib/oven.py +++ b/lib/oven.py @@ -243,6 +243,9 @@ class Oven(threading.Thread): self.reset() self.save_automatic_restart_state() + def get_start_time(self): + return datetime.datetime.now() - datetime.timedelta(milliseconds = self.runtime * 1000) + def kiln_must_catch_up(self): '''shift the whole schedule forward in time by one time_step to wait for the kiln to catch up''' @@ -252,11 +255,11 @@ class Oven(threading.Thread): # kiln too cold, wait for it to heat up 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.start_time = self.get_start_time() # kiln too hot, wait for it to cool down 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.start_time = self.get_start_time() def update_runtime(self): @@ -414,6 +417,7 @@ class SimulatedOven(Oven): self.R_o_nocool = config.sim_R_o_nocool self.R_ho_noair = config.sim_R_ho_noair self.R_ho = self.R_ho_noair + self.speedup_factor = 10 # set temps to the temp of the surrounding environment self.t = self.t_env # deg C temp of oven @@ -425,6 +429,18 @@ class SimulatedOven(Oven): self.start() log.info("SimulatedOven started") + # runtime is in sped up time, start_time is actual time of day + def get_start_time(self): + return datetime.datetime.now() - datetime.timedelta(milliseconds = self.runtime * 1000 / self.speedup_factor) + + def update_runtime(self): + + runtime_delta = datetime.datetime.now() - self.start_time + if runtime_delta.total_seconds() < 0: + runtime_delta = datetime.timedelta(0) + + self.runtime = runtime_delta.total_seconds() * self.speedup_factor + def heating_energy(self,pid): # using pid here simulates the element being on for # only part of the time_step @@ -489,7 +505,7 @@ class SimulatedOven(Oven): # we don't actually spend time heating & cooling during # a simulation, so sleep. - time.sleep(self.time_step) + time.sleep(self.time_step / self.speedup_factor) class RealOven(Oven): diff --git a/lib/ovenWatcher.py b/lib/ovenWatcher.py index 3e47e4f..19dd2ad 100644 --- a/lib/ovenWatcher.py +++ b/lib/ovenWatcher.py @@ -1,4 +1,6 @@ import threading,logging,json,time,datetime + +import config from oven import Oven log = logging.getLogger(__name__) @@ -32,7 +34,12 @@ class OvenWatcher(threading.Thread): else: self.recording = False self.notify_all(oven_state) - time.sleep(self.oven.time_step) + + if config.simulate: + time.sleep(self.oven.time_step / self.oven.speedup_factor) + else: + time.sleep(self.oven.time_step) + def lastlog_subset(self,maxpts=50): '''send about maxpts from lastlog by skipping unwanted data''' From 945fcf4187d173ed263b23fb24f6ca3cb38b4566 Mon Sep 17 00:00:00 2001 From: James Kirikland Garner Date: Wed, 14 Dec 2022 19:22:42 -0800 Subject: [PATCH 04/13] Works at 1000 times speed, a little messy. --- .gitignore | 1 + config.py | 2 +- lib/oven.py | 12 +++++++----- lib/ovenWatcher.py | 2 +- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 35300ae..d17affe 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ thumbs.db #storage/profiles #config.py .idea/* +state.json diff --git a/config.py b/config.py index 3909e03..2dbed17 100644 --- a/config.py +++ b/config.py @@ -175,7 +175,7 @@ ignore_tc_short_errors = False # cleaned up (deleted) by the OS on boot. # The state file is written to disk every sensor_time_wait seconds (2s by default) # and is written in the same directory as config.py. -automatic_restarts = True +automatic_restarts = False automatic_restart_window = 15 # max minutes since power outage automatic_restart_state_file = os.path.abspath(os.path.join(os.path.dirname( __file__ ),'state.json')) diff --git a/lib/oven.py b/lib/oven.py index 45fd05e..8a08b9c 100644 --- a/lib/oven.py +++ b/lib/oven.py @@ -417,7 +417,7 @@ class SimulatedOven(Oven): self.R_o_nocool = config.sim_R_o_nocool self.R_ho_noair = config.sim_R_ho_noair self.R_ho = self.R_ho_noair - self.speedup_factor = 10 + self.speedup_factor = 1000 # set temps to the temp of the surrounding environment self.t = self.t_env # deg C temp of oven @@ -441,6 +441,9 @@ class SimulatedOven(Oven): self.runtime = runtime_delta.total_seconds() * self.speedup_factor + def update_target_temp(self): + self.target = self.profile.get_target_temperature(self.runtime) + def heating_energy(self,pid): # using pid here simulates the element being on for # only part of the time_step @@ -466,7 +469,7 @@ class SimulatedOven(Oven): def heat_then_cool(self): pid = self.pid.compute(self.target, self.board.temp_sensor.temperature + - config.thermocouple_offset) + config.thermocouple_offset, self.start_time + datetime.timedelta(milliseconds = self.runtime * 1000)) heat_on = float(self.time_step * pid) heat_off = float(self.time_step * (1 - pid)) @@ -528,7 +531,7 @@ class RealOven(Oven): def heat_then_cool(self): pid = self.pid.compute(self.target, self.board.temp_sensor.temperature + - config.thermocouple_offset) + config.thermocouple_offset, datetime.datetime.now()) heat_on = float(self.time_step * pid) heat_off = float(self.time_step * (1 - pid)) @@ -610,8 +613,7 @@ class PID(): # settled on -50 to 50 and then divide by 50 at the end. This results # in a larger PID control window and much more accurate control... # instead of what used to be binary on/off control. - def compute(self, setpoint, ispoint): - now = datetime.datetime.now() + def compute(self, setpoint, ispoint, now): timeDelta = (now - self.lastNow).total_seconds() window_size = 100 diff --git a/lib/ovenWatcher.py b/lib/ovenWatcher.py index 19dd2ad..be13146 100644 --- a/lib/ovenWatcher.py +++ b/lib/ovenWatcher.py @@ -36,7 +36,7 @@ class OvenWatcher(threading.Thread): self.notify_all(oven_state) if config.simulate: - time.sleep(self.oven.time_step / self.oven.speedup_factor) + time.sleep(self.oven.time_step) else: time.sleep(self.oven.time_step) From 83ce8fb5f512397a4ff7639ba281855b0e4b5418 Mon Sep 17 00:00:00 2001 From: James Kirikland Garner Date: Thu, 15 Dec 2022 09:22:58 -0800 Subject: [PATCH 05/13] fix now bug --- lib/oven.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/oven.py b/lib/oven.py index dffc7c0..22f017d 100644 --- a/lib/oven.py +++ b/lib/oven.py @@ -558,10 +558,10 @@ class SimulatedOven(Oven): self.board.temp_sensor.simulated_temperature = self.t def heat_then_cool(self): - now_simulator = self.start_time + datetime.timedelta(milliseconds = self.runtime * 1000) + # now_simulator = self.start_time + datetime.timedelta(milliseconds = self.runtime * 1000) pid = self.pid.compute(self.target, self.board.temp_sensor.temperature + - config.thermocouple_offset, now_simulator) + config.thermocouple_offset, self.start_time + datetime.timedelta(milliseconds = self.runtime * 1000)) heat_on = float(self.time_step * pid) heat_off = float(self.time_step * (1 - pid)) From c9ee92c93d0f8f1038d104fde5bec1f07e07aff1 Mon Sep 17 00:00:00 2001 From: James Kirikland Garner Date: Thu, 15 Dec 2022 09:38:14 -0800 Subject: [PATCH 06/13] now bug --- lib/oven.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/oven.py b/lib/oven.py index 8a08b9c..c389057 100644 --- a/lib/oven.py +++ b/lib/oven.py @@ -207,7 +207,7 @@ class Oven(threading.Thread): self.cost = 0 self.state = "IDLE" self.profile = None - self.start_time = 0 + self.start_time = datetime.datetime.now() self.runtime = 0 self.totaltime = 0 self.target = 0 @@ -434,7 +434,6 @@ class SimulatedOven(Oven): return datetime.datetime.now() - datetime.timedelta(milliseconds = self.runtime * 1000 / self.speedup_factor) def update_runtime(self): - runtime_delta = datetime.datetime.now() - self.start_time if runtime_delta.total_seconds() < 0: runtime_delta = datetime.timedelta(0) @@ -467,9 +466,10 @@ class SimulatedOven(Oven): self.board.temp_sensor.temperature = self.t def heat_then_cool(self): + now_simulator = self.start_time + datetime.timedelta(milliseconds = self.runtime * 1000) pid = self.pid.compute(self.target, self.board.temp_sensor.temperature + - config.thermocouple_offset, self.start_time + datetime.timedelta(milliseconds = self.runtime * 1000)) + config.thermocouple_offset, now_simulator) heat_on = float(self.time_step * pid) heat_off = float(self.time_step * (1 - pid)) From 8d770b3086c5a19daa8eead677d14772f680738e Mon Sep 17 00:00:00 2001 From: James Kirikland Garner Date: Thu, 15 Dec 2022 10:23:08 -0800 Subject: [PATCH 07/13] Blinka merge, playing with config settings. --- config.py | 5 +++-- lib/oven.py | 2 +- lib/ovenWatcher.py | 6 ++---- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/config.py b/config.py index e6abc80..a3ac87c 100644 --- a/config.py +++ b/config.py @@ -82,7 +82,7 @@ max31856 = 0 # Every N seconds a decision is made about switching the relay[s] # on & off and for how long. The thermocouple is read # temperature_average_samples times during and the average value is used. -sensor_time_wait = 2 +sensor_time_wait = 0.2 ######################################################################## @@ -117,6 +117,7 @@ sim_R_o_nocool = 0.5 # 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 +sim_speedup_factor = 1000 ######################################################################## @@ -148,7 +149,7 @@ kiln_must_catch_up = True # 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. This should be a positive integer. -pid_control_window = 5 #degrees +pid_control_window = 50 #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 8a947d6..82c7bc7 100644 --- a/lib/oven.py +++ b/lib/oven.py @@ -508,7 +508,7 @@ class SimulatedOven(Oven): self.R_o_nocool = config.sim_R_o_nocool self.R_ho_noair = config.sim_R_ho_noair self.R_ho = self.R_ho_noair - self.speedup_factor = 1000 + self.speedup_factor = config.sim_speedup_factor # set temps to the temp of the surrounding environment self.t = self.t_env # deg C temp of oven diff --git a/lib/ovenWatcher.py b/lib/ovenWatcher.py index be13146..cd0b6ea 100644 --- a/lib/ovenWatcher.py +++ b/lib/ovenWatcher.py @@ -35,10 +35,8 @@ class OvenWatcher(threading.Thread): self.recording = False self.notify_all(oven_state) - if config.simulate: - time.sleep(self.oven.time_step) - else: - time.sleep(self.oven.time_step) + + time.sleep(self.oven.time_step) def lastlog_subset(self,maxpts=50): From 9900bb442153a27e2ceca0c8a188fe3c8d456b1f Mon Sep 17 00:00:00 2001 From: James Kirikland Garner Date: Thu, 15 Dec 2022 14:49:32 -0800 Subject: [PATCH 08/13] Oven watcher time hard coded, added test json --- config.py | 2 +- lib/oven.py | 2 +- lib/ovenWatcher.py | 4 +--- storage/profiles/test-fast.json | 1 + 4 files changed, 4 insertions(+), 5 deletions(-) create mode 100644 storage/profiles/test-fast.json diff --git a/config.py b/config.py index a3ac87c..1e3cd4c 100644 --- a/config.py +++ b/config.py @@ -82,7 +82,7 @@ max31856 = 0 # Every N seconds a decision is made about switching the relay[s] # on & off and for how long. The thermocouple is read # temperature_average_samples times during and the average value is used. -sensor_time_wait = 0.2 +sensor_time_wait = 2 ######################################################################## diff --git a/lib/oven.py b/lib/oven.py index 82c7bc7..411db71 100644 --- a/lib/oven.py +++ b/lib/oven.py @@ -573,7 +573,7 @@ class SimulatedOven(Oven): if heat_on > 0: self.heat = heat_on - log.info("simulation: -> %dW heater: %.0f -> %dW oven: %.0f -> %dW env" % (int(self.p_heat * pid), + log.info("simulation: -> %dW heater: %.0f -> %dW oven: %.0f -> %dW env" % (int(self.p_heat * pid), self.t_h, int(self.p_ho), self.t, diff --git a/lib/ovenWatcher.py b/lib/ovenWatcher.py index cd0b6ea..157a3be 100644 --- a/lib/ovenWatcher.py +++ b/lib/ovenWatcher.py @@ -35,10 +35,8 @@ class OvenWatcher(threading.Thread): self.recording = False self.notify_all(oven_state) + time.sleep(0.5) - time.sleep(self.oven.time_step) - - def lastlog_subset(self,maxpts=50): '''send about maxpts from lastlog by skipping unwanted data''' totalpts = len(self.last_log) diff --git a/storage/profiles/test-fast.json b/storage/profiles/test-fast.json new file mode 100644 index 0000000..b863af2 --- /dev/null +++ b/storage/profiles/test-fast.json @@ -0,0 +1 @@ +{"data": [[0, 200], [3600, 200], [10800, 2000], [14400, 2250], [16400, 2250], [19400, 70]], "type": "profile", "name": "test-fast"} From d8c1f7cb00fa42120e9fc6931166dcb7fa314882 Mon Sep 17 00:00:00 2001 From: James Kirikland Garner Date: Fri, 16 Dec 2022 19:24:03 -0800 Subject: [PATCH 09/13] fussing --- Test/test_Profile.py | 49 +++++++++++++++++++++++++++++++++ config.py | 12 ++------ lib/oven.py | 26 +++++++++++++---- storage/profiles/test-fast.json | 2 +- 4 files changed, 73 insertions(+), 16 deletions(-) create mode 100644 Test/test_Profile.py diff --git a/Test/test_Profile.py b/Test/test_Profile.py new file mode 100644 index 0000000..7c579b2 --- /dev/null +++ b/Test/test_Profile.py @@ -0,0 +1,49 @@ +from lib.oven import Profile +import os +import json + +def get_profile(): + profile_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'storage', 'profiles', "test-fast.json")) + print(profile_path) + with open(profile_path) as infile: + profile_json = json.dumps(json.load(infile)) + profile = Profile(profile_json) + + return profile + + +def test_get_target_temperature(): + profile = get_profile() + + temperature = profile.get_target_temperature(3000) + assert int(temperature) == 178 + + temperature = profile.get_target_temperature(6004) + assert temperature == 801.0 + + +def test_shift_remaining_segments(): + profile = get_profile() + + now = 6000 + shift_seconds = 100 + profile.shift_remaining_segments(now, shift_seconds) + + assert profile.data[0][0] == 0 + assert profile.data[1][0] == 3600 + assert profile.data[2][0] == 10900 + assert profile.data[3][0] == 14500 + assert profile.data[4][0] == 16500 + assert profile.data[5][0] == 19500 + + +def test_get_next_point(): + profile = get_profile() + + now = 6000 + segment = profile.get_next_point(now) + assert segment == 2 + + now = 14405 + segment = profile.get_next_point(now) + assert segment == 4 diff --git a/config.py b/config.py index 415d64a..4efc69e 100644 --- a/config.py +++ b/config.py @@ -97,21 +97,13 @@ pid_kp = 25 # Proportional 25,200,200 pid_ki = 10 # Integral pid_kd = 200 # Derivative -######################################################################## -# -# Initial heating and Integral Windup -# -# this setting is deprecated and is no longer used. this happens by -# default and is the expected behavior. -stop_integral_windup = True - ######################################################################## # # Simulation parameters simulate = True sim_t_env = 60.0 # deg C sim_c_heat = 500.0 # J/K heat capacity of heat element -sim_c_oven = 5000.0 # J/K heat capacity of oven +sim_c_oven = 10000.0 # J/K heat capacity of oven sim_p_heat = 5450.0 # W heating power of oven sim_R_o_nocool = 0.5 # K/W thermal resistance oven -> environment sim_R_o_cool = 0.05 # K/W " with cooling @@ -135,7 +127,7 @@ time_scale_profile = "m" # s = Seconds | m = Minutes | h = Hours - Enter and vi # naturally cool off. If your SSR has failed/shorted/closed circuit, this # means your kiln receives full power until your house burns down. # this should not replace you watching your kiln or use of a kiln-sitter -emergency_shutoff_temp = 2264 #cone 7 +emergency_shutoff_temp = 2350 #2264 #cone 7 # If the current temperature is outside the pid control window, # delay the schedule until it does back inside. This allows for heating diff --git a/lib/oven.py b/lib/oven.py index 411db71..7abad72 100644 --- a/lib/oven.py +++ b/lib/oven.py @@ -314,7 +314,7 @@ class Oven(threading.Thread): def __init__(self): threading.Thread.__init__(self) self.daemon = True - self.temperature = 0 + self.temperature = 70 self.time_step = config.sensor_time_wait self.reset() @@ -356,11 +356,12 @@ class Oven(threading.Thread): # kiln too cold, wait for it to heat up if self.target - temp > config.pid_control_window: log.info("kiln must catch up, too cold, shifting schedule") - self.start_time = self.get_start_time() + self.profile.shift_remaining_segments(self.runtime, 10) + self.totaltime = self.profile.get_duration() # kiln too hot, wait for it to cool down if temp - self.target > config.pid_control_window: - log.info("kiln must catch up, too hot, shifting schedule") - self.start_time = self.get_start_time() + self.profile.shift_remaining_segments(self.runtime, 10) + self.totaltime = self.profile.get_duration() def update_runtime(self): @@ -679,6 +680,21 @@ class Profile(): return (prev_point, next_point) + def get_next_point(self, now): + next_point = None # Handle error if nothing found + for i in range(len(self.data)): + if now < self.data[i][0]: + next_point = i + break + + return next_point + + def shift_remaining_segments(self, now, shift_seconds): + next_point = self.get_next_point(now) + for i in range(len(self.data)): + if i >= next_point: + self.data[i][0] += shift_seconds + def get_target_temperature(self, time): if time > self.get_duration(): return 0 @@ -733,7 +749,7 @@ class PID(): 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 + output = self.kp * error #+ self.iterm + self.kd * dErr output = sorted([-1 * window_size, output, window_size])[1] out4logs = output output = float(output / window_size) diff --git a/storage/profiles/test-fast.json b/storage/profiles/test-fast.json index b863af2..67a3f1d 100644 --- a/storage/profiles/test-fast.json +++ b/storage/profiles/test-fast.json @@ -1 +1 @@ -{"data": [[0, 200], [3600, 200], [10800, 2000], [14400, 2250], [16400, 2250], [19400, 70]], "type": "profile", "name": "test-fast"} +{"data": [[0, 70], [3600, 200], [10800, 2000], [14400, 2250], [16400, 2250], [19400, 1000]], "type": "profile", "name": "test-fast"} From 3a097e5098f25652e691b5acb877fd0382d9d035 Mon Sep 17 00:00:00 2001 From: James Kirikland Garner Date: Sat, 17 Dec 2022 19:56:01 -0800 Subject: [PATCH 10/13] revert to 5df0bc5 as in pull request --- config.py | 12 ++++++++++-- lib/oven.py | 26 +++++--------------------- storage/profiles/test-fast.json | 2 +- 3 files changed, 16 insertions(+), 24 deletions(-) diff --git a/config.py b/config.py index 4efc69e..415d64a 100644 --- a/config.py +++ b/config.py @@ -97,13 +97,21 @@ pid_kp = 25 # Proportional 25,200,200 pid_ki = 10 # Integral pid_kd = 200 # Derivative +######################################################################## +# +# Initial heating and Integral Windup +# +# this setting is deprecated and is no longer used. this happens by +# default and is the expected behavior. +stop_integral_windup = True + ######################################################################## # # Simulation parameters simulate = True sim_t_env = 60.0 # deg C sim_c_heat = 500.0 # J/K heat capacity of heat element -sim_c_oven = 10000.0 # J/K heat capacity of oven +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.5 # K/W thermal resistance oven -> environment sim_R_o_cool = 0.05 # K/W " with cooling @@ -127,7 +135,7 @@ time_scale_profile = "m" # s = Seconds | m = Minutes | h = Hours - Enter and vi # naturally cool off. If your SSR has failed/shorted/closed circuit, this # means your kiln receives full power until your house burns down. # this should not replace you watching your kiln or use of a kiln-sitter -emergency_shutoff_temp = 2350 #2264 #cone 7 +emergency_shutoff_temp = 2264 #cone 7 # If the current temperature is outside the pid control window, # delay the schedule until it does back inside. This allows for heating diff --git a/lib/oven.py b/lib/oven.py index 7abad72..411db71 100644 --- a/lib/oven.py +++ b/lib/oven.py @@ -314,7 +314,7 @@ class Oven(threading.Thread): def __init__(self): threading.Thread.__init__(self) self.daemon = True - self.temperature = 70 + self.temperature = 0 self.time_step = config.sensor_time_wait self.reset() @@ -356,12 +356,11 @@ class Oven(threading.Thread): # kiln too cold, wait for it to heat up if self.target - temp > config.pid_control_window: log.info("kiln must catch up, too cold, shifting schedule") - self.profile.shift_remaining_segments(self.runtime, 10) - self.totaltime = self.profile.get_duration() + self.start_time = self.get_start_time() # kiln too hot, wait for it to cool down if temp - self.target > config.pid_control_window: - self.profile.shift_remaining_segments(self.runtime, 10) - self.totaltime = self.profile.get_duration() + log.info("kiln must catch up, too hot, shifting schedule") + self.start_time = self.get_start_time() def update_runtime(self): @@ -680,21 +679,6 @@ class Profile(): return (prev_point, next_point) - def get_next_point(self, now): - next_point = None # Handle error if nothing found - for i in range(len(self.data)): - if now < self.data[i][0]: - next_point = i - break - - return next_point - - def shift_remaining_segments(self, now, shift_seconds): - next_point = self.get_next_point(now) - for i in range(len(self.data)): - if i >= next_point: - self.data[i][0] += shift_seconds - def get_target_temperature(self, time): if time > self.get_duration(): return 0 @@ -749,7 +733,7 @@ class PID(): 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 + output = self.kp * error + self.iterm + self.kd * dErr output = sorted([-1 * window_size, output, window_size])[1] out4logs = output output = float(output / window_size) diff --git a/storage/profiles/test-fast.json b/storage/profiles/test-fast.json index 67a3f1d..b863af2 100644 --- a/storage/profiles/test-fast.json +++ b/storage/profiles/test-fast.json @@ -1 +1 @@ -{"data": [[0, 70], [3600, 200], [10800, 2000], [14400, 2250], [16400, 2250], [19400, 1000]], "type": "profile", "name": "test-fast"} +{"data": [[0, 200], [3600, 200], [10800, 2000], [14400, 2250], [16400, 2250], [19400, 70]], "type": "profile", "name": "test-fast"} From c43770ace513c7451057b058373c62d21379b1ba Mon Sep 17 00:00:00 2001 From: James Kirikland Garner Date: Sun, 18 Dec 2022 10:30:07 -0800 Subject: [PATCH 11/13] Removed changes not needed for PR --- Test/test_Profile.py | 49 -------------------------------------------- config.py | 4 ++-- lib/oven.py | 2 +- lib/ovenWatcher.py | 1 - 4 files changed, 3 insertions(+), 53 deletions(-) delete mode 100644 Test/test_Profile.py diff --git a/Test/test_Profile.py b/Test/test_Profile.py deleted file mode 100644 index 7c579b2..0000000 --- a/Test/test_Profile.py +++ /dev/null @@ -1,49 +0,0 @@ -from lib.oven import Profile -import os -import json - -def get_profile(): - profile_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'storage', 'profiles', "test-fast.json")) - print(profile_path) - with open(profile_path) as infile: - profile_json = json.dumps(json.load(infile)) - profile = Profile(profile_json) - - return profile - - -def test_get_target_temperature(): - profile = get_profile() - - temperature = profile.get_target_temperature(3000) - assert int(temperature) == 178 - - temperature = profile.get_target_temperature(6004) - assert temperature == 801.0 - - -def test_shift_remaining_segments(): - profile = get_profile() - - now = 6000 - shift_seconds = 100 - profile.shift_remaining_segments(now, shift_seconds) - - assert profile.data[0][0] == 0 - assert profile.data[1][0] == 3600 - assert profile.data[2][0] == 10900 - assert profile.data[3][0] == 14500 - assert profile.data[4][0] == 16500 - assert profile.data[5][0] == 19500 - - -def test_get_next_point(): - profile = get_profile() - - now = 6000 - segment = profile.get_next_point(now) - assert segment == 2 - - now = 14405 - segment = profile.get_next_point(now) - assert segment == 4 diff --git a/config.py b/config.py index 415d64a..472dfb0 100644 --- a/config.py +++ b/config.py @@ -149,7 +149,7 @@ kiln_must_catch_up = True # 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. This should be a positive integer. -pid_control_window = 50 #degrees +pid_control_window = 5 #degrees # thermocouple offset # If you put your thermocouple in ice water and it reads 36F, you can @@ -205,7 +205,7 @@ ignore_tc_too_many_errors = False # cleaned up (deleted) by the OS on boot. # The state file is written to disk every sensor_time_wait seconds (2s by default) # and is written in the same directory as config.py. -automatic_restarts = False +automatic_restarts = True automatic_restart_window = 15 # max minutes since power outage automatic_restart_state_file = os.path.abspath(os.path.join(os.path.dirname( __file__ ),'state.json')) diff --git a/lib/oven.py b/lib/oven.py index 411db71..117b543 100644 --- a/lib/oven.py +++ b/lib/oven.py @@ -322,7 +322,7 @@ class Oven(threading.Thread): self.cost = 0 self.state = "IDLE" self.profile = None - self.start_time = datetime.datetime.now() + self.start_time = 0 self.runtime = 0 self.totaltime = 0 self.target = 0 diff --git a/lib/ovenWatcher.py b/lib/ovenWatcher.py index 157a3be..462e811 100644 --- a/lib/ovenWatcher.py +++ b/lib/ovenWatcher.py @@ -1,6 +1,5 @@ import threading,logging,json,time,datetime -import config from oven import Oven log = logging.getLogger(__name__) From d5af5bcf7d8db91a298891aab9eedeb3ac745d97 Mon Sep 17 00:00:00 2001 From: James Kirikland Garner Date: Sun, 18 Dec 2022 10:50:46 -0800 Subject: [PATCH 12/13] Put back as it was --- lib/ovenWatcher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ovenWatcher.py b/lib/ovenWatcher.py index 462e811..796f849 100644 --- a/lib/ovenWatcher.py +++ b/lib/ovenWatcher.py @@ -34,7 +34,7 @@ class OvenWatcher(threading.Thread): self.recording = False self.notify_all(oven_state) - time.sleep(0.5) + time.sleep(self.oven.time_step) def lastlog_subset(self,maxpts=50): '''send about maxpts from lastlog by skipping unwanted data''' From 21b2655867a7cd44891d29b13ef031d1e25a2fd7 Mon Sep 17 00:00:00 2001 From: James Kirikland Garner Date: Sun, 18 Dec 2022 10:54:35 -0800 Subject: [PATCH 13/13] Really put back ovenWatcher I hope; --- lib/ovenWatcher.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/ovenWatcher.py b/lib/ovenWatcher.py index 796f849..5d07b64 100644 --- a/lib/ovenWatcher.py +++ b/lib/ovenWatcher.py @@ -1,5 +1,4 @@ import threading,logging,json,time,datetime - from oven import Oven log = logging.getLogger(__name__) @@ -33,7 +32,6 @@ class OvenWatcher(threading.Thread): else: self.recording = False self.notify_all(oven_state) - time.sleep(self.oven.time_step) def lastlog_subset(self,maxpts=50):