Skip to content

Commit

Permalink
code format/lint (#64)
Browse files Browse the repository at this point in the history
  • Loading branch information
rogerlz authored Oct 10, 2023
1 parent 0b03c2b commit 6e7730a
Showing 1 changed file with 77 additions and 50 deletions.
127 changes: 77 additions & 50 deletions klippy/extras/pid_calibrate.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def cmd_PID_CALIBRATE(self, gcmd):
heater_name = gcmd.get("HEATER")
target = gcmd.get_float("TARGET")
write_file = gcmd.get_int("WRITE_FILE", 0)
tolerance = gcmd.get_float('TOLERANCE', TUNE_PID_TOL, above=0.)
tolerance = gcmd.get_float("TOLERANCE", TUNE_PID_TOL, above=0.0)
pheaters = self.printer.lookup_object("heaters")
try:
heater = pheaters.lookup_heater(heater_name)
Expand Down Expand Up @@ -58,17 +58,19 @@ def cmd_PID_CALIBRATE(self, gcmd):
configfile.set(heater_name, "pid_Ki", "%.3f" % (Ki,))
configfile.set(heater_name, "pid_Kd", "%.3f" % (Kd,))


TUNE_PID_DELTA = 5.0
TUNE_PID_TOL = 0.02
TUNE_PID_SAMPLES = 3
TUNE_PID_MAX_PEAKS = 60


class ControlAutoTune:
def __init__(self, heater, target, tolerance):
self.heater = heater
self.heater_max_power = heater.get_max_power()
# store the reference so we can push messages if needed
self.gcode = heater.printer.lookup_object('gcode')
self.gcode = heater.printer.lookup_object("gcode")
# holds the various max power settings used during the test.
self.powers = [self.heater_max_power]
# holds the times the power setting was changed.
Expand All @@ -79,9 +81,9 @@ def __init__(self, heater, target, tolerance):
# acceptable level
self.tolerance = tolerance
# the the temp that determines when to turn the heater off
self.temp_high = target + TUNE_PID_DELTA/2.
self.temp_high = target + TUNE_PID_DELTA / 2.0
# the the temp that determines when to turn the heater on
self.temp_low = target - TUNE_PID_DELTA/2.
self.temp_low = target - TUNE_PID_DELTA / 2.0
# is the heater on
self.heating = False
# the current potential peak value
Expand All @@ -106,8 +108,9 @@ def temperature_update(self, read_time, temp, target_temp):
if self.done:
return
# store test data
self.data.append((read_time, temp, self.heater.last_pwm_value,
self.target))
self.data.append(
(read_time, temp, self.heater.last_pwm_value, self.target)
)
# ensure the starting temp is low enough to run the test.
if not self.started and temp >= self.temp_low:
self.errored = True
Expand All @@ -123,12 +126,12 @@ def temperature_update(self, read_time, temp, target_temp):
self.gcode.respond_info("calibration did not finish in time")
return
# indicate that the target temp has been crossed at-least once
if temp > self.target and self.target_crossed == False:
if temp > self.target and self.target_crossed is False:
self.target_crossed = True
# only do work if the target temp has been crossed at-least once
if self.target_crossed:
# check for a new peak value
if temp > self.temp_high or temp < self.temp_low :
if temp > self.temp_high or temp < self.temp_low:
self.check_peak(read_time, temp)
# it's time to calculate and store a high peak
if self.peak > self.temp_high and temp < self.target:
Expand All @@ -137,9 +140,9 @@ def temperature_update(self, read_time, temp, target_temp):
if self.peak < self.temp_low and temp > self.target:
self.store_peak()
# check if the conditions are right to evaluate a new sample
peaks = float(len(self.peaks)) - 1.
peaks = float(len(self.peaks)) - 1.0
powers = float(len(self.powers))
if (peaks % 2.) == 0. and (powers * 2.) == peaks:
if (peaks % 2.0) == 0.0 and (powers * 2.0) == peaks:
self.log_info()
# check for convergence
if self.converged():
Expand All @@ -161,6 +164,7 @@ def temperature_update(self, read_time, temp, target_temp):
self.heater.set_pwm(read_time, self.powers[-1])
else:
self.heater.set_pwm(read_time, 0)

def check_peak(self, time, temp):
# deal with duplicate temps
if temp == self.peak:
Expand All @@ -173,16 +177,18 @@ def check_peak(self, time, temp):
if temp < self.target and temp < self.peak:
self.peak = temp
self.peak_times = [time]

def store_peak(self):
time = sum(self.peak_times)/float(len(self.peak_times))
time = sum(self.peak_times) / float(len(self.peak_times))
self.peaks.append((time, self.peak))
self.peak = self.target
self.peak_times = []

def log_info(self):
# provide some useful info to the user
sample = len(self.powers)
pwm = self.powers[-1]
asymmetry = (self.peaks[-2][1] + self.peaks[-1][1])/2. - self.target
asymmetry = (self.peaks[-2][1] + self.peaks[-1][1]) / 2.0 - self.target
tolerance = self.get_sample_tolerance()
if tolerance is False:
fmt = "sample:%d pwm:%.4f asymmetry:%.4f tolerance:n/a\n"
Expand All @@ -192,86 +198,106 @@ def log_info(self):
fmt = "sample:%d pwm:%.4f asymmetry:%.4f tolerance:%.4f\n"
data = (sample, pwm, asymmetry, tolerance)
self.gcode.respond_info(fmt % data)

def get_sample_tolerance(self):
powers = len(self.powers)
if powers < TUNE_PID_SAMPLES + 1:
return False
powers = self.powers[-1*(TUNE_PID_SAMPLES+1):]
return max(powers)-min(powers)
powers = self.powers[-1 * (TUNE_PID_SAMPLES + 1) :]
return max(powers) - min(powers)

def converged(self):
tolerance = self.get_sample_tolerance()
if tolerance is False:
return False
return False
if tolerance <= self.tolerance:
return True
return False

def set_power(self):
peak_low = self.peaks[-2][1]
peak_high = self.peaks[-1][1]
power = self.powers[-1]
mid = power * ((self.target - peak_low)/(peak_high - peak_low))
if mid * 2. > self.heater_max_power:
mid = power * ((self.target - peak_low) / (peak_high - peak_low))
if mid * 2.0 > self.heater_max_power:
# the new power is to high so just return max power
self.powers.append(self.heater_max_power)
return
self.powers.append(mid * 2.)
self.powers.append(mid * 2.0)

def finish(self, time):
self.heater.set_pwm(time, 0)
self.heater.alter_target(0)
self.done = True
self.heating = False
self.heater.set_pwm(time, 0)
self.heater.alter_target(0)
self.done = True
self.heating = False

def check_busy(self, eventtime, smoothed_temp, target_temp):
if eventtime == 0. and smoothed_temp == 0. and target_temp == 0.:
if eventtime == 0.0 and smoothed_temp == 0.0 and target_temp == 0.0:
if self.errored:
return True
return False
if self.done:
return False
return True

def write_file(self, filename):
f = open(filename, "w")
f.write('time, temp, pwm, target\n')
data = ["%.5f, %.5f, %.5f, %.5f" % (time, temp, pwm, target)
for time, temp, pwm, target in self.data]
f.write('\n'.join(data))
f.write("time, temp, pwm, target\n")
data = [
"%.5f, %.5f, %.5f, %.5f" % (time, temp, pwm, target)
for time, temp, pwm, target in self.data
]
f.write("\n".join(data))
peaks = self.peaks[:]
powers = self.powers[:]
# pop off the
peaks.pop(0)
samples = []
for i in range(len(powers)):
samples.append((i, peaks[i*2][0], peaks[i*2][1], peaks[i*2+1][0],
peaks[i*2+1][1], powers[i]))
f.write('\nsample, low time, low, high time, high, max power\n')
data = ["%.5f, %.5f, %.5f, %.5f, %.5f, %.5f" % (sample, low_time,
low, high_time, high, max_power) for sample, low_time, low,
high_time, high, max_power in samples]
f.write('\n'.join(data))
samples.append(
(
i,
peaks[i * 2][0],
peaks[i * 2][1],
peaks[i * 2 + 1][0],
peaks[i * 2 + 1][1],
powers[i],
)
)
f.write("\nsample, low time, low, high time, high, max power\n")
data = [
"%.5f, %.5f, %.5f, %.5f, %.5f, %.5f"
% (sample, low_time, low, high_time, high, max_power)
for sample, low_time, low, high_time, high, max_power in samples
]
f.write("\n".join(data))
f.close()

def calc_pid(self):
temp_diff = 0.
time_diff = 0.
theta = 0.
temp_diff = 0.0
time_diff = 0.0
theta = 0.0
for i in range(1, TUNE_PID_SAMPLES * 2, 2):
temp_diff = temp_diff + self.peaks[-i][1] - self.peaks[-i-1][1]
time_diff = time_diff + self.peaks[-i][0] - self.peaks[-i-2][0]
temp_diff = temp_diff + self.peaks[-i][1] - self.peaks[-i - 1][1]
time_diff = time_diff + self.peaks[-i][0] - self.peaks[-i - 2][0]
theta = theta + self.peaks[-i][0] - self.times[-i]
temp_diff = temp_diff/float(TUNE_PID_SAMPLES)
time_diff = time_diff/float(TUNE_PID_SAMPLES)
theta = theta/float(TUNE_PID_SAMPLES)
temp_diff = temp_diff / float(TUNE_PID_SAMPLES)
time_diff = time_diff / float(TUNE_PID_SAMPLES)
theta = theta / float(TUNE_PID_SAMPLES)
amplitude = 0.5 * abs(temp_diff)
power = self.powers[-1*(TUNE_PID_SAMPLES):]
power = sum(power)/float(len(power))
power = self.powers[-1 * (TUNE_PID_SAMPLES) :]
power = sum(power) / float(len(power))
# calculate the various parameters
Ku = 4. * power / (math.pi * amplitude)
Ku = 4.0 * power / (math.pi * amplitude)
Tu = time_diff
Wu = (2. * math.pi)/Tu
tau = math.tan(math.pi - theta *Wu)/Wu
Km = -math.sqrt(tau**2 * Wu**2 + 1.)/Ku
Wu = (2.0 * math.pi) / Tu
tau = math.tan(math.pi - theta * Wu) / Wu
Km = -math.sqrt(tau**2 * Wu**2 + 1.0) / Ku
# log the extra details
logging.info("Ziegler-Nichols constants: Ku=%f Tu=%f", Ku, Tu)
logging.info("Cohen-Coon constants: Km=%f Theta=%f Tau=%f", Km,
theta, tau)
logging.info(
"Cohen-Coon constants: Km=%f Theta=%f Tau=%f", Km, theta, tau
)
# Use Ziegler-Nichols method to generate PID parameters
Ti = 0.5 * Tu
Td = 0.125 * Tu
Expand All @@ -280,5 +306,6 @@ def calc_pid(self):
Kd = Kp * Td
return Kp, Ki, Kd


def load_config(config):
return PIDCalibrate(config)

0 comments on commit 6e7730a

Please sign in to comment.