From a7fafeed65520e471915efd789a1a19cb651a755 Mon Sep 17 00:00:00 2001 From: Andrew de Quincey Date: Fri, 30 Apr 2021 22:23:11 +0100 Subject: [PATCH] add recorder code --- kiln-tuner.py | 83 ++++++++++++++++++++++++++++++++++++++++++++++++ lib/oven.py | 30 +++++++++-------- zieglernicols.py | 3 +- 3 files changed, 101 insertions(+), 15 deletions(-) create mode 100644 kiln-tuner.py diff --git a/kiln-tuner.py b/kiln-tuner.py new file mode 100644 index 0000000..26b6001 --- /dev/null +++ b/kiln-tuner.py @@ -0,0 +1,83 @@ +import os +import sys +import csv +import time +import argparse + + +try: + sys.dont_write_bytecode = True + import config + sys.dont_write_bytecode = False + +except: + print("Could not import config file.") + print("Copy config.py.EXAMPLE to config.py and adapt it for your setup.") + exit(1) + +script_dir = os.path.dirname(os.path.realpath(__file__)) +sys.path.insert(0, script_dir + '/lib/') +profile_path = os.path.join(script_dir, "storage", "profiles") + +from oven import RealOven, SimulatedOven + + +def tune(csvfile, targettemp): + # open the file to log data to + f = open(csvfile, 'w') + csvout = csv.writer(f) + csvout.write(['time', 'temperature']) + + # construct the oven + if config.simulate: + oven = SimulatedOven() + else: + oven = RealOven() + + # Main loop: + # + # * heat the oven to the target temperature at maximum burn. + # * when we reach it turn the heating off completely. + # * wait for it to decay back to the target again. + # * quit + # + # We record the temperature every config.sensor_time_wait + try: + stage = 'heating' + if not config.simulate: + oven.output.heat(1, tuning=True) + + while True: + temp = oven.board.temp_sensor.temperature + \ + config.thermocouple_offset + + csvout.writerow([time.time(), temp]) + csvout.flush() + + if stage == 'heating': + if temp > targettemp: + if not config.simulate: + oven.output.heat(0) + stage = 'decaying' + + elif stage == 'decaying': + if temp < targettemp: + break + + time.sleep(config.sensor_time_wait) + + f.close() + + finally: + # ensure we always shut the oven down! + if not config.simulate: + oven.output.heat(0) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Record data for kiln tuning') + parser.add_argument('csvfile', type=str, help="The CSV file to write to.") + parser.add_argument('--targettemp', type='int', default=400, help="The target temperature to drive the kiln to.") + args = parser.parse_args() + + tune(args.csvfile, args.targettemp) diff --git a/lib/oven.py b/lib/oven.py index 4a1f5b0..869cd3f 100644 --- a/lib/oven.py +++ b/lib/oven.py @@ -26,8 +26,10 @@ class Output(object): log.warning(msg) self.active = False - def heat(self,sleepfor): + def heat(self,sleepfor, tuning=False): self.GPIO.output(config.gpio_heat, self.GPIO.HIGH) + if tuning: + return time.sleep(sleepfor) self.GPIO.output(config.gpio_heat, self.GPIO.LOW) @@ -54,7 +56,7 @@ class Board(object): self.active = True log.info("import %s " % (self.name)) except ImportError: - msg = "max31855 config set, but import failed" + msg = "max31855 config set, but import failed" log.warning(msg) if config.max31856: @@ -64,7 +66,7 @@ class Board(object): self.active = True log.info("import %s " % (self.name)) except ImportError: - msg = "max31856 config set, but import failed" + msg = "max31856 config set, but import failed" log.warning(msg) def create_temp_sensor(self): @@ -76,7 +78,7 @@ class Board(object): class BoardSimulated(object): def __init__(self): self.temp_sensor = TempSensorSimulated() - + class TempSensor(threading.Thread): def __init__(self): threading.Thread.__init__(self) @@ -118,7 +120,7 @@ class TempSensorReal(TempSensor): '''take 5 measurements over each time period and return the average''' while True: - maxtries = 5 + maxtries = 5 sleeptime = self.time_step / float(maxtries) temps = [] for x in range(0,maxtries): @@ -245,10 +247,10 @@ class SimulatedOven(Oven): # set temps to the temp of the surrounding environment self.t = self.t_env # deg C temp of oven self.t_h = self.t_env #deg C temp of heating element - + # call parent init Oven.__init__(self) - + # start thread self.start() log.info("SimulatedOven started") @@ -306,9 +308,9 @@ class SimulatedOven(Oven): self.runtime, self.totaltime, time_left)) - + # we don't actually spend time heating & cooling during - # a simulation, so sleep. + # a simulation, so sleep. time.sleep(self.time_step) @@ -396,7 +398,7 @@ class PID(): self.lastErr = 0 # FIX - this was using a really small window where the PID control - # takes effect from -1 to 1. I changed this to various numbers and + # takes effect from -1 to 1. I changed this to various numbers and # 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. @@ -410,7 +412,7 @@ class PID(): if self.ki > 0: self.iterm += (error * timeDelta * (1/self.ki)) - + dErr = (error - self.lastErr) / timeDelta output = self.kp * error + self.iterm + self.kd * dErr out4logs = output @@ -427,10 +429,10 @@ class PID(): output = float(output / window_size) - if out4logs > 0: + if out4logs > 0: log.info("pid percents pid=%0.2f p=%0.2f i=%0.2f d=%0.2f" % (out4logs, - ((self.kp * error)/out4logs)*100, + ((self.kp * error)/out4logs)*100, (self.iterm/out4logs)*100, - ((self.kd * dErr)/out4logs)*100)) + ((self.kd * dErr)/out4logs)*100)) return output diff --git a/zieglernicols.py b/zieglernicols.py index 5e69f26..913fbea 100644 --- a/zieglernicols.py +++ b/zieglernicols.py @@ -2,7 +2,8 @@ import csv import argparse -# Using the method from https://www.ias.ac.in/article/fulltext/reso/025/10/1385-1397 or https://www.youtube.com/watch?v=nvAQHSe-Ax4 +# Using the method described in "Ziegler–Nichols Tuning Method∗" by Vishakha Vijay Patel +# (https://www.ias.ac.in/article/fulltext/reso/025/10/1385-1397) def line(a, b, x):