diff --git a/config.py b/config.py index dc257b3..c1ac529 100644 --- a/config.py +++ b/config.py @@ -124,6 +124,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 ######################################################################## @@ -155,7 +156,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 = 5 #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 f5efc42..d0d9ebc 100644 --- a/lib/oven.py +++ b/lib/oven.py @@ -362,6 +362,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''' @@ -371,11 +374,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): @@ -524,6 +527,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 = config.sim_speedup_factor # set temps to the temp of the surrounding environment self.t = config.sim_t_env # deg C or F temp of oven @@ -535,6 +539,20 @@ 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 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 @@ -558,9 +576,11 @@ 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) pid = self.pid.compute(self.target, self.board.temp_sensor.temperature() + - config.thermocouple_offset) + config.thermocouple_offset, now_simulator) + heat_on = float(self.time_step * pid) heat_off = float(self.time_step * (1 - pid)) @@ -572,7 +592,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, @@ -599,7 +619,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): @@ -621,8 +641,9 @@ class RealOven(Oven): def heat_then_cool(self): pid = self.pid.compute(self.target, - self.board.temp_sensor.temperature() + - config.thermocouple_offset) + self.board.temp_sensor.temperature + + config.thermocouple_offset, datetime.datetime.now()) + heat_on = float(self.time_step * pid) heat_off = float(self.time_step * (1 - pid)) @@ -726,8 +747,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 3a420e5..681fa54 100644 --- a/lib/ovenWatcher.py +++ b/lib/ovenWatcher.py @@ -33,7 +33,7 @@ class OvenWatcher(threading.Thread): self.recording = False self.notify_all(oven_state) 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"}