From d1f8a4761b5171c4f34d909990b5b0593adae340 Mon Sep 17 00:00:00 2001 From: topquark12 Date: Thu, 31 Dec 2020 15:10:11 +0800 Subject: [PATCH 01/12] pytec: Provides stable PID auto tune parameters --- pytec/autotune.py | 282 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 282 insertions(+) create mode 100644 pytec/autotune.py diff --git a/pytec/autotune.py b/pytec/autotune.py new file mode 100644 index 0000000..fb4f59b --- /dev/null +++ b/pytec/autotune.py @@ -0,0 +1,282 @@ +import math +import logging +from time import time +from collections import deque, namedtuple +from pytec.client import Client + +# Based on hirshmann pid-autotune libiary +# See https://github.com/hirschmann/pid-autotune +# Which is in turn based on a fork of Arduino PID AutoTune Library +# See https://github.com/t0mpr1c3/Arduino-PID-AutoTune-Library + +# Auto tune parameters +# Thermostat channel +channel = 0 +# Target temperature of the autotune routine, celcius +target_temperature = 30 +# Value by which output will be increased/decreased from zero, amps +output_step = 1 +# Reference period for local minima/maxima, seconds +lookback = 3 +# Determines by how much the input value must overshoot/undershoot the setpoint, celcius +noiseband = 1.5 + +class PIDAutotune(object): + + PIDParams = namedtuple('PIDParams', ['Kp', 'Ki', 'Kd']) + + PEAK_AMPLITUDE_TOLERANCE = 0.05 + STATE_OFF = 'off' + STATE_RELAY_STEP_UP = 'relay step up' + STATE_RELAY_STEP_DOWN = 'relay step down' + STATE_SUCCEEDED = 'succeeded' + STATE_FAILED = 'failed' + + _tuning_rules = { + "ziegler-nichols": [0.6, 1.2, 0.075], + "tyreus-luyben": [0.4545, 0.2066, 0.07214], + "ciancone-marlin": [0.303, 0.1364, 0.0481], + "pessen-integral": [0.7, 1.75, 0.105], + "some-overshoot": [0.333, 0.667, 0.111], + "no-overshoot": [0.2, 0.4, 0.0667] + } + + def __init__(self, setpoint, out_step=10, lookback=60, noiseband=0.5, sampletime=1.2): + if setpoint is None: + raise ValueError('setpoint must be specified') + # if out_step < 1: + # raise ValueError('out_step must be greater or equal to 1') + # if sampletime < 1: + # raise ValueError('sampletime must be greater or equal to 1') + # if lookback < sampletime: + # raise ValueError('lookback must be greater or equal to sampletime') + # if out_min >= out_max: + # raise ValueError('out_min must be less than out_max') + + # self._time = time + # logging = logging.getLogger(type(self).__name__) + self._inputs = deque(maxlen=round(lookback / sampletime)) + # self._sampletime = sampletime * 1000 + self._setpoint = setpoint + self._outputstep = out_step + self._noiseband = noiseband + self._out_min = -out_step + self._out_max = out_step + self._state = PIDAutotune.STATE_OFF + self._peak_timestamps = deque(maxlen=5) + self._peaks = deque(maxlen=5) + self._output = 0 + self._last_run_timestamp = 0 + self._peak_type = 0 + self._peak_count = 0 + self._initial_output = 0 + self._induced_amplitude = 0 + self._Ku = 0 + self._Pu = 0 + + + def state(self): + """Get the current state.""" + return self._state + + + def output(self): + """Get the last output value.""" + return self._output + + # @property + def tuning_rules(self): + """Get a list of all available tuning rules.""" + return self._tuning_rules.keys() + + def get_pid_parameters(self, tuning_rule='ziegler-nichols'): + """Get PID parameters. + + Args: + tuning_rule (str): Sets the rule which should be used to calculate + the parameters. + """ + divisors = self._tuning_rules[tuning_rule] + kp = self._Ku * divisors[0] + # ki = kp / (self._Pu / divisors[1]) + ki = divisors[1] * self._Ku / self._Pu + # kd = kp * (self._Pu / divisors[2]) + kd = divisors[2] * self._Ku * self._Pu + return PIDAutotune.PIDParams(kp, ki, kd) + + def run(self, input_val, time_input): + """To autotune a system, this method must be called periodically. + + Args: + input_val (float): The input value. + + Returns: + `true` if tuning is finished, otherwise `false`. + """ + now = time_input * 1000 + + if (self._state == PIDAutotune.STATE_OFF + or self._state == PIDAutotune.STATE_SUCCEEDED + or self._state == PIDAutotune.STATE_FAILED): + self._initTuner(input_val, now) + + self._last_run_timestamp = now + # print("temp : ", input_val) + + # check input and change relay state if necessary + if (self._state == PIDAutotune.STATE_RELAY_STEP_UP + and input_val > self._setpoint + self._noiseband): + self._state = PIDAutotune.STATE_RELAY_STEP_DOWN + logging.debug('switched state: {0}'.format(self._state)) + logging.debug('input: {0}'.format(input_val)) + elif (self._state == PIDAutotune.STATE_RELAY_STEP_DOWN + and input_val < self._setpoint - self._noiseband): + self._state = PIDAutotune.STATE_RELAY_STEP_UP + logging.debug('switched state: {0}'.format(self._state)) + logging.debug('input: {0}'.format(input_val)) + + # set output + if (self._state == PIDAutotune.STATE_RELAY_STEP_UP): + self._output = self._initial_output + self._outputstep + elif self._state == PIDAutotune.STATE_RELAY_STEP_DOWN: + self._output = self._initial_output - self._outputstep + + # respect output limits + self._output = min(self._output, self._out_max) + self._output = max(self._output, self._out_min) + + # identify peaks + is_max = True + is_min = True + + for val in self._inputs: + is_max = is_max and (input_val >= val) + is_min = is_min and (input_val <= val) + + self._inputs.append(input_val) + + # we don't want to trust the maxes or mins until the input array is full + if len(self._inputs) < self._inputs.maxlen: + return False + + # increment peak count and record peak time for maxima and minima + inflection = False + + # peak types: + # -1: minimum + # +1: maximum + if is_max: + if self._peak_type == -1: + inflection = True + self._peak_type = 1 + elif is_min: + if self._peak_type == 1: + inflection = True + self._peak_type = -1 + + # update peak times and values + if inflection: + self._peak_count += 1 + self._peaks.append(input_val) + self._peak_timestamps.append(now) + logging.debug('found peak: {0}'.format(input_val)) + logging.debug('peak count: {0}'.format(self._peak_count)) + + # check for convergence of induced oscillation + # convergence of amplitude assessed on last 4 peaks (1.5 cycles) + self._induced_amplitude = 0 + + if inflection and (self._peak_count > 4): + abs_max = self._peaks[-2] + abs_min = self._peaks[-2] + for i in range(0, len(self._peaks) - 2): + self._induced_amplitude += abs(self._peaks[i] - self._peaks[i+1]) + abs_max = max(self._peaks[i], abs_max) + abs_min = min(self._peaks[i], abs_min) + + self._induced_amplitude /= 6.0 + + # check convergence criterion for amplitude of induced oscillation + amplitude_dev = ((0.5 * (abs_max - abs_min) - self._induced_amplitude) + / self._induced_amplitude) + + logging.debug('amplitude: {0}'.format(self._induced_amplitude)) + logging.debug('amplitude deviation: {0}'.format(amplitude_dev)) + + if amplitude_dev < PIDAutotune.PEAK_AMPLITUDE_TOLERANCE: + self._state = PIDAutotune.STATE_SUCCEEDED + # logging.debug('peak finding succeeded') + + # if the autotune has not already converged + # terminate after 10 cycles + if self._peak_count >= 20: + self._output = 0 + self._state = PIDAutotune.STATE_FAILED + return True + + if self._state == PIDAutotune.STATE_SUCCEEDED: + self._output = 0 + logging.debug('peak finding successful') + + # calculate ultimate gain + self._Ku = 4.0 * self._outputstep / (self._induced_amplitude * math.pi) + print('Ku: {0}'.format(self._Ku)) + + # calculate ultimate period in seconds + period1 = self._peak_timestamps[3] - self._peak_timestamps[1] + period2 = self._peak_timestamps[4] - self._peak_timestamps[2] + self._Pu = 0.5 * (period1 + period2) / 1000.0 + print('Pu: {0}'.format(self._Pu)) + + for rule in self._tuning_rules: + params = self.get_pid_parameters(rule) + print('rule: {0}'.format(rule)) + print('Kp: {0}'.format(params.Kp)) + print('Ki: {0}'.format(params.Ki)) + print('Kd: {0}'.format(params.Kd)) + + + return True + return False + + def _initTuner(self, inputValue, timestamp): + self._peak_type = 0 + self._peak_count = 0 + self._output = 0 + self._initial_output = 0 + self._Ku = 0 + self._Pu = 0 + self._inputs.clear() + self._peaks.clear() + self._peak_timestamps.clear() + self._peak_timestamps.append(timestamp) + self._state = PIDAutotune.STATE_RELAY_STEP_UP + +# logging.basicConfig(level=logging.DEBUG) + +tec = Client() #(host="localhost", port=6667) + +data = next(tec.report_mode()) +ch = data[channel] + +tuner = PIDAutotune(target_temperature, output_step, lookback, noiseband, ch['interval']) + +for data in tec.report_mode(): + + try: + ch = data[channel] + + temperature = ch['temperature'] + + if (tuner.run(temperature, ch['time'])): + # logging.debug('true') + break + + tunerOut = tuner.output() + + tec.set_param("pwm", channel, "i_set" , tunerOut) + + except: + pass + +tec.set_param("pwm", channel, "i_set" , channel) \ No newline at end of file -- 2.44.1 From ab305d5fa57aa7616be988555b4a7f05a75dfb48 Mon Sep 17 00:00:00 2001 From: topquark12 Date: Mon, 4 Jan 2021 10:54:04 +0800 Subject: [PATCH 02/12] pytec: __name__ check, examples does not run when pytec module is imported, change autotune state management to use enum --- pytec/autotune.py | 99 +++++++++++---------- pytec/example.py | 22 ++--- pytec/plot.py | 218 +++++++++++++++++++++++----------------------- 3 files changed, 175 insertions(+), 164 deletions(-) diff --git a/pytec/autotune.py b/pytec/autotune.py index fb4f59b..cbd79ac 100644 --- a/pytec/autotune.py +++ b/pytec/autotune.py @@ -3,35 +3,41 @@ import logging from time import time from collections import deque, namedtuple from pytec.client import Client +from enum import Enum # Based on hirshmann pid-autotune libiary # See https://github.com/hirschmann/pid-autotune # Which is in turn based on a fork of Arduino PID AutoTune Library # See https://github.com/t0mpr1c3/Arduino-PID-AutoTune-Library -# Auto tune parameters -# Thermostat channel -channel = 0 -# Target temperature of the autotune routine, celcius -target_temperature = 30 -# Value by which output will be increased/decreased from zero, amps -output_step = 1 -# Reference period for local minima/maxima, seconds -lookback = 3 -# Determines by how much the input value must overshoot/undershoot the setpoint, celcius -noiseband = 1.5 - -class PIDAutotune(object): +if __name__ == "__main__": - PIDParams = namedtuple('PIDParams', ['Kp', 'Ki', 'Kd']) + # Auto tune parameters + # Thermostat channel + channel = 0 + # Target temperature of the autotune routine, celcius + target_temperature = 30 + # Value by which output will be increased/decreased from zero, amps + output_step = 1 + # Reference period for local minima/maxima, seconds + lookback = 3 + # Determines by how much the input value must overshoot/undershoot the setpoint, celcius + noiseband = 1.5 - PEAK_AMPLITUDE_TOLERANCE = 0.05 +class PIDAutotuneState(Enum): STATE_OFF = 'off' STATE_RELAY_STEP_UP = 'relay step up' STATE_RELAY_STEP_DOWN = 'relay step down' STATE_SUCCEEDED = 'succeeded' STATE_FAILED = 'failed' +class PIDAutotune(): + + PIDParams = namedtuple('PIDParams', ['Kp', 'Ki', 'Kd']) + + PEAK_AMPLITUDE_TOLERANCE = 0.05 + + _tuning_rules = { "ziegler-nichols": [0.6, 1.2, 0.075], "tyreus-luyben": [0.4545, 0.2066, 0.07214], @@ -62,7 +68,7 @@ class PIDAutotune(object): self._noiseband = noiseband self._out_min = -out_step self._out_max = out_step - self._state = PIDAutotune.STATE_OFF + self._state = PIDAutotuneState.STATE_OFF self._peak_timestamps = deque(maxlen=5) self._peaks = deque(maxlen=5) self._output = 0 @@ -115,30 +121,30 @@ class PIDAutotune(object): """ now = time_input * 1000 - if (self._state == PIDAutotune.STATE_OFF - or self._state == PIDAutotune.STATE_SUCCEEDED - or self._state == PIDAutotune.STATE_FAILED): + if (self._state == PIDAutotuneState.STATE_OFF + or self._state == PIDAutotuneState.STATE_SUCCEEDED + or self._state == PIDAutotuneState.STATE_FAILED): self._initTuner(input_val, now) self._last_run_timestamp = now # print("temp : ", input_val) # check input and change relay state if necessary - if (self._state == PIDAutotune.STATE_RELAY_STEP_UP + if (self._state == PIDAutotuneState.STATE_RELAY_STEP_UP and input_val > self._setpoint + self._noiseband): - self._state = PIDAutotune.STATE_RELAY_STEP_DOWN + self._state = PIDAutotuneState.STATE_RELAY_STEP_DOWN logging.debug('switched state: {0}'.format(self._state)) logging.debug('input: {0}'.format(input_val)) - elif (self._state == PIDAutotune.STATE_RELAY_STEP_DOWN + elif (self._state == PIDAutotuneState.STATE_RELAY_STEP_DOWN and input_val < self._setpoint - self._noiseband): - self._state = PIDAutotune.STATE_RELAY_STEP_UP + self._state = PIDAutotuneState.STATE_RELAY_STEP_UP logging.debug('switched state: {0}'.format(self._state)) logging.debug('input: {0}'.format(input_val)) # set output - if (self._state == PIDAutotune.STATE_RELAY_STEP_UP): + if (self._state == PIDAutotuneState.STATE_RELAY_STEP_UP): self._output = self._initial_output + self._outputstep - elif self._state == PIDAutotune.STATE_RELAY_STEP_DOWN: + elif self._state == PIDAutotuneState.STATE_RELAY_STEP_DOWN: self._output = self._initial_output - self._outputstep # respect output limits @@ -204,17 +210,17 @@ class PIDAutotune(object): logging.debug('amplitude deviation: {0}'.format(amplitude_dev)) if amplitude_dev < PIDAutotune.PEAK_AMPLITUDE_TOLERANCE: - self._state = PIDAutotune.STATE_SUCCEEDED + self._state = PIDAutotuneState.STATE_SUCCEEDED # logging.debug('peak finding succeeded') # if the autotune has not already converged # terminate after 10 cycles if self._peak_count >= 20: self._output = 0 - self._state = PIDAutotune.STATE_FAILED + self._state = PIDAutotuneState.STATE_FAILED return True - if self._state == PIDAutotune.STATE_SUCCEEDED: + if self._state == PIDAutotuneState.STATE_SUCCEEDED: self._output = 0 logging.debug('peak finding successful') @@ -250,33 +256,34 @@ class PIDAutotune(object): self._peaks.clear() self._peak_timestamps.clear() self._peak_timestamps.append(timestamp) - self._state = PIDAutotune.STATE_RELAY_STEP_UP + self._state = PIDAutotuneState.STATE_RELAY_STEP_UP -# logging.basicConfig(level=logging.DEBUG) +if __name__ == "__main__": -tec = Client() #(host="localhost", port=6667) + # logging.basicConfig(level=logging.DEBUG) -data = next(tec.report_mode()) -ch = data[channel] + tec = Client() #(host="localhost", port=6667) -tuner = PIDAutotune(target_temperature, output_step, lookback, noiseband, ch['interval']) + data = next(tec.report_mode()) + ch = data[channel] -for data in tec.report_mode(): + tuner = PIDAutotune(target_temperature, output_step, lookback, noiseband, ch['interval']) - try: - ch = data[channel] + for data in tec.report_mode(): - temperature = ch['temperature'] + try: + ch = data[channel] - if (tuner.run(temperature, ch['time'])): - # logging.debug('true') - break + temperature = ch['temperature'] - tunerOut = tuner.output() + if (tuner.run(temperature, ch['time'])): + break - tec.set_param("pwm", channel, "i_set" , tunerOut) + tunerOut = tuner.output() - except: - pass + tec.set_param("pwm", channel, "i_set" , tunerOut) -tec.set_param("pwm", channel, "i_set" , channel) \ No newline at end of file + except: + pass + + tec.set_param("pwm", channel, "i_set" , channel) \ No newline at end of file diff --git a/pytec/example.py b/pytec/example.py index 001ea49..fdecb5f 100644 --- a/pytec/example.py +++ b/pytec/example.py @@ -1,11 +1,13 @@ -from pytec.client import Client +if __name__ == "__main__": -tec = Client() #(host="localhost", port=6667) -tec.set_param("s-h", 1, "t0", 20) -print(tec.get_pwm()) -print(tec.get_pid()) -print(tec.get_pwm()) -print(tec.get_postfilter()) -print(tec.get_steinhart_hart()) -for data in tec.report_mode(): - print(data) + from pytec.client import Client + + tec = Client() #(host="localhost", port=6667) + tec.set_param("s-h", 1, "t0", 20) + print(tec.get_pwm()) + print(tec.get_pid()) + print(tec.get_pwm()) + print(tec.get_postfilter()) + print(tec.get_steinhart_hart()) + for data in tec.report_mode(): + print(data) diff --git a/pytec/plot.py b/pytec/plot.py index 77fd048..0b2fbb2 100644 --- a/pytec/plot.py +++ b/pytec/plot.py @@ -4,125 +4,127 @@ import matplotlib.animation as animation from threading import Thread, Lock from pytec.client import Client -TIME_WINDOW = 300.0 +if __name__ == "__main__": -tec = Client() -target_temperature = tec.get_pid()[0]['target'] -print("Channel 0 target temperature: {:.3f}".format(target_temperature)) + TIME_WINDOW = 300.0 -class Series: - def __init__(self, conv=lambda x: x): - self.conv = conv - self.x_data = [] - self.y_data = [] + tec = Client() + target_temperature = tec.get_pid()[0]['target'] + print("Channel 0 target temperature: {:.3f}".format(target_temperature)) - def append(self, x, y): - self.x_data.append(x) - self.y_data.append(self.conv(y)) + class Series: + def __init__(self, conv=lambda x: x): + self.conv = conv + self.x_data = [] + self.y_data = [] - def clip(self, min_x): - drop = 0 - while drop < len(self.x_data) and self.x_data[drop] < min_x: - drop += 1 - self.x_data = self.x_data[drop:] - self.y_data = self.y_data[drop:] + def append(self, x, y): + self.x_data.append(x) + self.y_data.append(self.conv(y)) + + def clip(self, min_x): + drop = 0 + while drop < len(self.x_data) and self.x_data[drop] < min_x: + drop += 1 + self.x_data = self.x_data[drop:] + self.y_data = self.y_data[drop:] + + series = { + 'adc': Series(), + 'sens': Series(lambda x: x * 0.0001), + 'temperature': Series(), + 'i_set': Series(), + 'pid_output': Series(), + 'vref': Series(), + 'dac_value': Series(), + 'dac_feedback': Series(), + 'i_tec': Series(), + 'tec_i': Series(), + 'tec_u_meas': Series(), + 'interval': Series(), + } + series_lock = Lock() + + quit = False + + def recv_data(tec): + global last_packet_time + for data in tec.report_mode(): + ch0 = data[0] + series_lock.acquire() + try: + for k, s in series.items(): + if k in ch0: + v = ch0[k] + if type(v) is float: + s.append(ch0['time'], v) + finally: + series_lock.release() + + if quit: + break + + thread = Thread(target=recv_data, args=(tec,)) + thread.start() + + fig, ax = plt.subplots() + + for k, s in series.items(): + s.plot, = ax.plot([], [], label=k) + legend = ax.legend() + + def animate(i): + min_x, max_x, min_y, max_y = None, None, None, None -series = { - 'adc': Series(), - 'sens': Series(lambda x: x * 0.0001), - 'temperature': Series(lambda t: t - target_temperature), - 'i_set': Series(), - 'pid_output': Series(), - 'vref': Series(), - 'dac_value': Series(), - 'dac_feedback': Series(), - 'i_tec': Series(), - 'tec_i': Series(), - 'tec_u_meas': Series(), - 'interval': Series(), -} -series_lock = Lock() - -quit = False - -def recv_data(tec): - global last_packet_time - for data in tec.report_mode(): - ch0 = data[0] series_lock.acquire() try: for k, s in series.items(): - if k in ch0: - v = ch0[k] - if type(v) is float: - s.append(ch0['time'], v) + s.plot.set_data(s.x_data, s.y_data) + if len(s.y_data) > 0: + s.plot.set_label("{}: {:.3f}".format(k, s.y_data[-1])) + + if len(s.x_data) > 0: + min_x_ = min(s.x_data) + if min_x is None: + min_x = min_x_ + else: + min_x = min(min_x, min_x_) + max_x_ = max(s.x_data) + if max_x is None: + max_x = max_x_ + else: + max_x = max(max_x, max_x_) + if len(s.y_data) > 0: + min_y_ = min(s.y_data) + if min_y is None: + min_y = min_y_ + else: + min_y = min(min_y, min_y_) + max_y_ = max(s.y_data) + if max_y is None: + max_y = max_y_ + else: + max_y = max(max_y, max_y_) + + if min_x and max_x - TIME_WINDOW > min_x: + for s in series.values(): + s.clip(max_x - TIME_WINDOW) finally: series_lock.release() - if quit: - break + if min_x != max_x: + ax.set_xlim(min_x, max_x) + if min_y != max_y: + margin_y = 0.01 * (max_y - min_y) + ax.set_ylim(min_y - margin_y, max_y + margin_y) -thread = Thread(target=recv_data, args=(tec,)) -thread.start() + global legend + legend.remove() + legend = ax.legend() -fig, ax = plt.subplots() + ani = animation.FuncAnimation( + fig, animate, interval=1, blit=False, save_count=50) -for k, s in series.items(): - s.plot, = ax.plot([], [], label=k) -legend = ax.legend() - -def animate(i): - min_x, max_x, min_y, max_y = None, None, None, None - - series_lock.acquire() - try: - for k, s in series.items(): - s.plot.set_data(s.x_data, s.y_data) - if len(s.y_data) > 0: - s.plot.set_label("{}: {:.3f}".format(k, s.y_data[-1])) - - if len(s.x_data) > 0: - min_x_ = min(s.x_data) - if min_x is None: - min_x = min_x_ - else: - min_x = min(min_x, min_x_) - max_x_ = max(s.x_data) - if max_x is None: - max_x = max_x_ - else: - max_x = max(max_x, max_x_) - if len(s.y_data) > 0: - min_y_ = min(s.y_data) - if min_y is None: - min_y = min_y_ - else: - min_y = min(min_y, min_y_) - max_y_ = max(s.y_data) - if max_y is None: - max_y = max_y_ - else: - max_y = max(max_y, max_y_) - - if min_x and max_x - TIME_WINDOW > min_x: - for s in series.values(): - s.clip(max_x - TIME_WINDOW) - finally: - series_lock.release() - - if min_x != max_x: - ax.set_xlim(min_x, max_x) - if min_y != max_y: - margin_y = 0.01 * (max_y - min_y) - ax.set_ylim(min_y - margin_y, max_y + margin_y) - - global legend - legend.remove() - legend = ax.legend() - -ani = animation.FuncAnimation( - fig, animate, interval=1, blit=False, save_count=50) - -plt.show() -quit = True -thread.join() + plt.show() + quit = True + thread.join() -- 2.44.1 From aab0d6d25c7c98a2207879cfa121c248d22208d3 Mon Sep 17 00:00:00 2001 From: topquark12 Date: Mon, 4 Jan 2021 12:07:45 +0800 Subject: [PATCH 03/12] pytec: redo autotune __main__ check --- pytec/autotune.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/pytec/autotune.py b/pytec/autotune.py index cbd79ac..42dee6f 100644 --- a/pytec/autotune.py +++ b/pytec/autotune.py @@ -10,20 +10,6 @@ from enum import Enum # Which is in turn based on a fork of Arduino PID AutoTune Library # See https://github.com/t0mpr1c3/Arduino-PID-AutoTune-Library -if __name__ == "__main__": - - # Auto tune parameters - # Thermostat channel - channel = 0 - # Target temperature of the autotune routine, celcius - target_temperature = 30 - # Value by which output will be increased/decreased from zero, amps - output_step = 1 - # Reference period for local minima/maxima, seconds - lookback = 3 - # Determines by how much the input value must overshoot/undershoot the setpoint, celcius - noiseband = 1.5 - class PIDAutotuneState(Enum): STATE_OFF = 'off' STATE_RELAY_STEP_UP = 'relay step up' @@ -258,7 +244,19 @@ class PIDAutotune(): self._peak_timestamps.append(timestamp) self._state = PIDAutotuneState.STATE_RELAY_STEP_UP -if __name__ == "__main__": +def main(): + + # Auto tune parameters + # Thermostat channel + channel = 0 + # Target temperature of the autotune routine, celcius + target_temperature = 30 + # Value by which output will be increased/decreased from zero, amps + output_step = 1 + # Reference period for local minima/maxima, seconds + lookback = 3 + # Determines by how much the input value must overshoot/undershoot the setpoint, celcius + noiseband = 1.5 # logging.basicConfig(level=logging.DEBUG) @@ -286,4 +284,6 @@ if __name__ == "__main__": except: pass - tec.set_param("pwm", channel, "i_set" , channel) \ No newline at end of file + tec.set_param("pwm", channel, "i_set" , channel) + +if __name__ == "__main__": main() \ No newline at end of file -- 2.44.1 From 9f872c0113b6a8fa4021b08a70d843e61c814bf8 Mon Sep 17 00:00:00 2001 From: topquark12 Date: Mon, 4 Jan 2021 14:13:01 +0800 Subject: [PATCH 04/12] pytec: autotune formatting --- pytec/autotune.py | 42 ++++++++++-------------------------------- 1 file changed, 10 insertions(+), 32 deletions(-) diff --git a/pytec/autotune.py b/pytec/autotune.py index 42dee6f..b261c26 100644 --- a/pytec/autotune.py +++ b/pytec/autotune.py @@ -2,8 +2,8 @@ import math import logging from time import time from collections import deque, namedtuple -from pytec.client import Client from enum import Enum +from pytec.client import Client # Based on hirshmann pid-autotune libiary # See https://github.com/hirschmann/pid-autotune @@ -17,12 +17,10 @@ class PIDAutotuneState(Enum): STATE_SUCCEEDED = 'succeeded' STATE_FAILED = 'failed' -class PIDAutotune(): - +class PIDAutotune(): PIDParams = namedtuple('PIDParams', ['Kp', 'Ki', 'Kd']) - PEAK_AMPLITUDE_TOLERANCE = 0.05 - + PEAK_AMPLITUDE_TOLERANCE = 0.05 _tuning_rules = { "ziegler-nichols": [0.6, 1.2, 0.075], @@ -36,19 +34,8 @@ class PIDAutotune(): def __init__(self, setpoint, out_step=10, lookback=60, noiseband=0.5, sampletime=1.2): if setpoint is None: raise ValueError('setpoint must be specified') - # if out_step < 1: - # raise ValueError('out_step must be greater or equal to 1') - # if sampletime < 1: - # raise ValueError('sampletime must be greater or equal to 1') - # if lookback < sampletime: - # raise ValueError('lookback must be greater or equal to sampletime') - # if out_min >= out_max: - # raise ValueError('out_min must be less than out_max') - - # self._time = time - # logging = logging.getLogger(type(self).__name__) + self._inputs = deque(maxlen=round(lookback / sampletime)) - # self._sampletime = sampletime * 1000 self._setpoint = setpoint self._outputstep = out_step self._noiseband = noiseband @@ -65,18 +52,15 @@ class PIDAutotune(): self._induced_amplitude = 0 self._Ku = 0 self._Pu = 0 - def state(self): """Get the current state.""" return self._state - def output(self): """Get the last output value.""" return self._output - # @property def tuning_rules(self): """Get a list of all available tuning rules.""" return self._tuning_rules.keys() @@ -90,9 +74,7 @@ class PIDAutotune(): """ divisors = self._tuning_rules[tuning_rule] kp = self._Ku * divisors[0] - # ki = kp / (self._Pu / divisors[1]) ki = divisors[1] * self._Ku / self._Pu - # kd = kp * (self._Pu / divisors[2]) kd = divisors[2] * self._Ku * self._Pu return PIDAutotune.PIDParams(kp, ki, kd) @@ -100,7 +82,8 @@ class PIDAutotune(): """To autotune a system, this method must be called periodically. Args: - input_val (float): The input value. + input_val (float): The temperature input value. + time_input (float): Current time in seconds. Returns: `true` if tuning is finished, otherwise `false`. @@ -110,10 +93,9 @@ class PIDAutotune(): if (self._state == PIDAutotuneState.STATE_OFF or self._state == PIDAutotuneState.STATE_SUCCEEDED or self._state == PIDAutotuneState.STATE_FAILED): - self._initTuner(input_val, now) + self.init_tuner(input_val, now) self._last_run_timestamp = now - # print("temp : ", input_val) # check input and change relay state if necessary if (self._state == PIDAutotuneState.STATE_RELAY_STEP_UP @@ -197,7 +179,6 @@ class PIDAutotune(): if amplitude_dev < PIDAutotune.PEAK_AMPLITUDE_TOLERANCE: self._state = PIDAutotuneState.STATE_SUCCEEDED - # logging.debug('peak finding succeeded') # if the autotune has not already converged # terminate after 10 cycles @@ -225,13 +206,12 @@ class PIDAutotune(): print('rule: {0}'.format(rule)) print('Kp: {0}'.format(params.Kp)) print('Ki: {0}'.format(params.Ki)) - print('Kd: {0}'.format(params.Kd)) - + print('Kd: {0}'.format(params.Kd)) return True return False - def _initTuner(self, inputValue, timestamp): + def init_tuner(self, inputValue, timestamp): self._peak_type = 0 self._peak_count = 0 self._output = 0 @@ -245,7 +225,6 @@ class PIDAutotune(): self._state = PIDAutotuneState.STATE_RELAY_STEP_UP def main(): - # Auto tune parameters # Thermostat channel channel = 0 @@ -260,7 +239,7 @@ def main(): # logging.basicConfig(level=logging.DEBUG) - tec = Client() #(host="localhost", port=6667) + tec = Client() data = next(tec.report_mode()) ch = data[channel] @@ -268,7 +247,6 @@ def main(): tuner = PIDAutotune(target_temperature, output_step, lookback, noiseband, ch['interval']) for data in tec.report_mode(): - try: ch = data[channel] -- 2.44.1 From 14a027c0f02ef0b44ea78ddaffd22ff79a2a9cc1 Mon Sep 17 00:00:00 2001 From: topquark12 Date: Tue, 5 Jan 2021 12:42:50 +0800 Subject: [PATCH 05/12] pytec: revert changes to example.py and plot.py, save changes for other PR --- pytec/example.py | 22 +++-- pytec/plot.py | 218 +++++++++++++++++++++++------------------------ 2 files changed, 118 insertions(+), 122 deletions(-) diff --git a/pytec/example.py b/pytec/example.py index fdecb5f..001ea49 100644 --- a/pytec/example.py +++ b/pytec/example.py @@ -1,13 +1,11 @@ -if __name__ == "__main__": +from pytec.client import Client - from pytec.client import Client - - tec = Client() #(host="localhost", port=6667) - tec.set_param("s-h", 1, "t0", 20) - print(tec.get_pwm()) - print(tec.get_pid()) - print(tec.get_pwm()) - print(tec.get_postfilter()) - print(tec.get_steinhart_hart()) - for data in tec.report_mode(): - print(data) +tec = Client() #(host="localhost", port=6667) +tec.set_param("s-h", 1, "t0", 20) +print(tec.get_pwm()) +print(tec.get_pid()) +print(tec.get_pwm()) +print(tec.get_postfilter()) +print(tec.get_steinhart_hart()) +for data in tec.report_mode(): + print(data) diff --git a/pytec/plot.py b/pytec/plot.py index 0b2fbb2..77fd048 100644 --- a/pytec/plot.py +++ b/pytec/plot.py @@ -4,127 +4,125 @@ import matplotlib.animation as animation from threading import Thread, Lock from pytec.client import Client -if __name__ == "__main__": +TIME_WINDOW = 300.0 - TIME_WINDOW = 300.0 +tec = Client() +target_temperature = tec.get_pid()[0]['target'] +print("Channel 0 target temperature: {:.3f}".format(target_temperature)) - tec = Client() - target_temperature = tec.get_pid()[0]['target'] - print("Channel 0 target temperature: {:.3f}".format(target_temperature)) +class Series: + def __init__(self, conv=lambda x: x): + self.conv = conv + self.x_data = [] + self.y_data = [] - class Series: - def __init__(self, conv=lambda x: x): - self.conv = conv - self.x_data = [] - self.y_data = [] + def append(self, x, y): + self.x_data.append(x) + self.y_data.append(self.conv(y)) - def append(self, x, y): - self.x_data.append(x) - self.y_data.append(self.conv(y)) - - def clip(self, min_x): - drop = 0 - while drop < len(self.x_data) and self.x_data[drop] < min_x: - drop += 1 - self.x_data = self.x_data[drop:] - self.y_data = self.y_data[drop:] - - series = { - 'adc': Series(), - 'sens': Series(lambda x: x * 0.0001), - 'temperature': Series(), - 'i_set': Series(), - 'pid_output': Series(), - 'vref': Series(), - 'dac_value': Series(), - 'dac_feedback': Series(), - 'i_tec': Series(), - 'tec_i': Series(), - 'tec_u_meas': Series(), - 'interval': Series(), - } - series_lock = Lock() - - quit = False - - def recv_data(tec): - global last_packet_time - for data in tec.report_mode(): - ch0 = data[0] - series_lock.acquire() - try: - for k, s in series.items(): - if k in ch0: - v = ch0[k] - if type(v) is float: - s.append(ch0['time'], v) - finally: - series_lock.release() - - if quit: - break - - thread = Thread(target=recv_data, args=(tec,)) - thread.start() - - fig, ax = plt.subplots() - - for k, s in series.items(): - s.plot, = ax.plot([], [], label=k) - legend = ax.legend() - - def animate(i): - min_x, max_x, min_y, max_y = None, None, None, None + def clip(self, min_x): + drop = 0 + while drop < len(self.x_data) and self.x_data[drop] < min_x: + drop += 1 + self.x_data = self.x_data[drop:] + self.y_data = self.y_data[drop:] +series = { + 'adc': Series(), + 'sens': Series(lambda x: x * 0.0001), + 'temperature': Series(lambda t: t - target_temperature), + 'i_set': Series(), + 'pid_output': Series(), + 'vref': Series(), + 'dac_value': Series(), + 'dac_feedback': Series(), + 'i_tec': Series(), + 'tec_i': Series(), + 'tec_u_meas': Series(), + 'interval': Series(), +} +series_lock = Lock() + +quit = False + +def recv_data(tec): + global last_packet_time + for data in tec.report_mode(): + ch0 = data[0] series_lock.acquire() try: for k, s in series.items(): - s.plot.set_data(s.x_data, s.y_data) - if len(s.y_data) > 0: - s.plot.set_label("{}: {:.3f}".format(k, s.y_data[-1])) - - if len(s.x_data) > 0: - min_x_ = min(s.x_data) - if min_x is None: - min_x = min_x_ - else: - min_x = min(min_x, min_x_) - max_x_ = max(s.x_data) - if max_x is None: - max_x = max_x_ - else: - max_x = max(max_x, max_x_) - if len(s.y_data) > 0: - min_y_ = min(s.y_data) - if min_y is None: - min_y = min_y_ - else: - min_y = min(min_y, min_y_) - max_y_ = max(s.y_data) - if max_y is None: - max_y = max_y_ - else: - max_y = max(max_y, max_y_) - - if min_x and max_x - TIME_WINDOW > min_x: - for s in series.values(): - s.clip(max_x - TIME_WINDOW) + if k in ch0: + v = ch0[k] + if type(v) is float: + s.append(ch0['time'], v) finally: series_lock.release() - if min_x != max_x: - ax.set_xlim(min_x, max_x) - if min_y != max_y: - margin_y = 0.01 * (max_y - min_y) - ax.set_ylim(min_y - margin_y, max_y + margin_y) + if quit: + break - global legend - legend.remove() - legend = ax.legend() +thread = Thread(target=recv_data, args=(tec,)) +thread.start() - ani = animation.FuncAnimation( - fig, animate, interval=1, blit=False, save_count=50) +fig, ax = plt.subplots() - plt.show() - quit = True - thread.join() +for k, s in series.items(): + s.plot, = ax.plot([], [], label=k) +legend = ax.legend() + +def animate(i): + min_x, max_x, min_y, max_y = None, None, None, None + + series_lock.acquire() + try: + for k, s in series.items(): + s.plot.set_data(s.x_data, s.y_data) + if len(s.y_data) > 0: + s.plot.set_label("{}: {:.3f}".format(k, s.y_data[-1])) + + if len(s.x_data) > 0: + min_x_ = min(s.x_data) + if min_x is None: + min_x = min_x_ + else: + min_x = min(min_x, min_x_) + max_x_ = max(s.x_data) + if max_x is None: + max_x = max_x_ + else: + max_x = max(max_x, max_x_) + if len(s.y_data) > 0: + min_y_ = min(s.y_data) + if min_y is None: + min_y = min_y_ + else: + min_y = min(min_y, min_y_) + max_y_ = max(s.y_data) + if max_y is None: + max_y = max_y_ + else: + max_y = max(max_y, max_y_) + + if min_x and max_x - TIME_WINDOW > min_x: + for s in series.values(): + s.clip(max_x - TIME_WINDOW) + finally: + series_lock.release() + + if min_x != max_x: + ax.set_xlim(min_x, max_x) + if min_y != max_y: + margin_y = 0.01 * (max_y - min_y) + ax.set_ylim(min_y - margin_y, max_y + margin_y) + + global legend + legend.remove() + legend = ax.legend() + +ani = animation.FuncAnimation( + fig, animate, interval=1, blit=False, save_count=50) + +plt.show() +quit = True +thread.join() -- 2.44.1 From 6ea41d5cc820dde4b6e149d75bbb35513e147cb8 Mon Sep 17 00:00:00 2001 From: topquark12 Date: Tue, 5 Jan 2021 17:15:07 +0800 Subject: [PATCH 06/12] pytec: clean up styling issues --- pytec/autotune.py | 55 ++++++++++++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/pytec/autotune.py b/pytec/autotune.py index b261c26..2aa4520 100644 --- a/pytec/autotune.py +++ b/pytec/autotune.py @@ -1,8 +1,8 @@ import math import logging -from time import time from collections import deque, namedtuple from enum import Enum + from pytec.client import Client # Based on hirshmann pid-autotune libiary @@ -10,6 +10,7 @@ from pytec.client import Client # Which is in turn based on a fork of Arduino PID AutoTune Library # See https://github.com/t0mpr1c3/Arduino-PID-AutoTune-Library + class PIDAutotuneState(Enum): STATE_OFF = 'off' STATE_RELAY_STEP_UP = 'relay step up' @@ -17,10 +18,11 @@ class PIDAutotuneState(Enum): STATE_SUCCEEDED = 'succeeded' STATE_FAILED = 'failed' -class PIDAutotune(): + +class PIDAutotune: PIDParams = namedtuple('PIDParams', ['Kp', 'Ki', 'Kd']) - PEAK_AMPLITUDE_TOLERANCE = 0.05 + PEAK_AMPLITUDE_TOLERANCE = 0.05 _tuning_rules = { "ziegler-nichols": [0.6, 1.2, 0.075], @@ -31,10 +33,11 @@ class PIDAutotune(): "no-overshoot": [0.2, 0.4, 0.0667] } - def __init__(self, setpoint, out_step=10, lookback=60, noiseband=0.5, sampletime=1.2): + def __init__(self, setpoint, out_step=10, lookback=60, + noiseband=0.5, sampletime=1.2): if setpoint is None: raise ValueError('setpoint must be specified') - + self._inputs = deque(maxlen=round(lookback / sampletime)) self._setpoint = setpoint self._outputstep = out_step @@ -52,11 +55,11 @@ class PIDAutotune(): self._induced_amplitude = 0 self._Ku = 0 self._Pu = 0 - + def state(self): """Get the current state.""" return self._state - + def output(self): """Get the last output value.""" return self._output @@ -129,7 +132,7 @@ class PIDAutotune(): self._inputs.append(input_val) - # we don't want to trust the maxes or mins until the input array is full + # we don't trust the maxes or mins until the input array is full if len(self._inputs) < self._inputs.maxlen: return False @@ -164,14 +167,16 @@ class PIDAutotune(): abs_max = self._peaks[-2] abs_min = self._peaks[-2] for i in range(0, len(self._peaks) - 2): - self._induced_amplitude += abs(self._peaks[i] - self._peaks[i+1]) + self._induced_amplitude += abs(self._peaks[i] + - self._peaks[i+1]) abs_max = max(self._peaks[i], abs_max) abs_min = min(self._peaks[i], abs_min) self._induced_amplitude /= 6.0 # check convergence criterion for amplitude of induced oscillation - amplitude_dev = ((0.5 * (abs_max - abs_min) - self._induced_amplitude) + amplitude_dev = ((0.5 * (abs_max - abs_min) + - self._induced_amplitude) / self._induced_amplitude) logging.debug('amplitude: {0}'.format(self._induced_amplitude)) @@ -179,7 +184,7 @@ class PIDAutotune(): if amplitude_dev < PIDAutotune.PEAK_AMPLITUDE_TOLERANCE: self._state = PIDAutotuneState.STATE_SUCCEEDED - + # if the autotune has not already converged # terminate after 10 cycles if self._peak_count >= 20: @@ -192,7 +197,8 @@ class PIDAutotune(): logging.debug('peak finding successful') # calculate ultimate gain - self._Ku = 4.0 * self._outputstep / (self._induced_amplitude * math.pi) + self._Ku = 4.0 * self._outputstep / \ + (self._induced_amplitude * math.pi) print('Ku: {0}'.format(self._Ku)) # calculate ultimate period in seconds @@ -206,12 +212,12 @@ class PIDAutotune(): print('rule: {0}'.format(rule)) print('Kp: {0}'.format(params.Kp)) print('Ki: {0}'.format(params.Ki)) - print('Kd: {0}'.format(params.Kd)) + print('Kd: {0}'.format(params.Kd)) return True return False - def init_tuner(self, inputValue, timestamp): + def init_tuner(self, input_value, timestamp): self._peak_type = 0 self._peak_count = 0 self._output = 0 @@ -224,6 +230,7 @@ class PIDAutotune(): self._peak_timestamps.append(timestamp) self._state = PIDAutotuneState.STATE_RELAY_STEP_UP + def main(): # Auto tune parameters # Thermostat channel @@ -234,17 +241,19 @@ def main(): output_step = 1 # Reference period for local minima/maxima, seconds lookback = 3 - # Determines by how much the input value must overshoot/undershoot the setpoint, celcius + # Determines by how much the input value must + # overshoot/undershoot the setpoint, celcius noiseband = 1.5 # logging.basicConfig(level=logging.DEBUG) - tec = Client() + tec = Client() data = next(tec.report_mode()) ch = data[channel] - tuner = PIDAutotune(target_temperature, output_step, lookback, noiseband, ch['interval']) + tuner = PIDAutotune(target_temperature, output_step, + lookback, noiseband, ch['interval']) for data in tec.report_mode(): try: @@ -255,13 +264,15 @@ def main(): if (tuner.run(temperature, ch['time'])): break - tunerOut = tuner.output() + tuner_out = tuner.output() - tec.set_param("pwm", channel, "i_set" , tunerOut) + tec.set_param("pwm", channel, "i_set", tuner_out) - except: + except KeyError: pass - tec.set_param("pwm", channel, "i_set" , channel) + tec.set_param("pwm", channel, "i_set", 0) -if __name__ == "__main__": main() \ No newline at end of file + +if __name__ == "__main__": + main() -- 2.44.1 From 29b8a4a6f43543cfea06b9d4c6de652bc99db3f8 Mon Sep 17 00:00:00 2001 From: topquark12 Date: Tue, 5 Jan 2021 17:19:06 +0800 Subject: [PATCH 07/12] pytec: remove inputValue in init_tuner --- pytec/autotune.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/pytec/autotune.py b/pytec/autotune.py index 2aa4520..de51347 100644 --- a/pytec/autotune.py +++ b/pytec/autotune.py @@ -56,6 +56,19 @@ class PIDAutotune: self._Ku = 0 self._Pu = 0 + def init_tuner(self, timestamp): + self._peak_type = 0 + self._peak_count = 0 + self._output = 0 + self._initial_output = 0 + self._Ku = 0 + self._Pu = 0 + self._inputs.clear() + self._peaks.clear() + self._peak_timestamps.clear() + self._peak_timestamps.append(timestamp) + self._state = PIDAutotuneState.STATE_RELAY_STEP_UP + def state(self): """Get the current state.""" return self._state @@ -96,7 +109,7 @@ class PIDAutotune: if (self._state == PIDAutotuneState.STATE_OFF or self._state == PIDAutotuneState.STATE_SUCCEEDED or self._state == PIDAutotuneState.STATE_FAILED): - self.init_tuner(input_val, now) + self.init_tuner(now) self._last_run_timestamp = now @@ -217,19 +230,6 @@ class PIDAutotune: return True return False - def init_tuner(self, input_value, timestamp): - self._peak_type = 0 - self._peak_count = 0 - self._output = 0 - self._initial_output = 0 - self._Ku = 0 - self._Pu = 0 - self._inputs.clear() - self._peaks.clear() - self._peak_timestamps.clear() - self._peak_timestamps.append(timestamp) - self._state = PIDAutotuneState.STATE_RELAY_STEP_UP - def main(): # Auto tune parameters -- 2.44.1 From bfcb91d2c043989c1595058102aca0020fc56686 Mon Sep 17 00:00:00 2001 From: topquark12 Date: Wed, 6 Jan 2021 09:43:10 +0800 Subject: [PATCH 08/12] pytec: remove autotune reset function, autotuner can only be run once --- pytec/autotune.py | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/pytec/autotune.py b/pytec/autotune.py index de51347..b8b8b3d 100644 --- a/pytec/autotune.py +++ b/pytec/autotune.py @@ -56,19 +56,6 @@ class PIDAutotune: self._Ku = 0 self._Pu = 0 - def init_tuner(self, timestamp): - self._peak_type = 0 - self._peak_count = 0 - self._output = 0 - self._initial_output = 0 - self._Ku = 0 - self._Pu = 0 - self._inputs.clear() - self._peaks.clear() - self._peak_timestamps.clear() - self._peak_timestamps.append(timestamp) - self._state = PIDAutotuneState.STATE_RELAY_STEP_UP - def state(self): """Get the current state.""" return self._state @@ -109,7 +96,7 @@ class PIDAutotune: if (self._state == PIDAutotuneState.STATE_OFF or self._state == PIDAutotuneState.STATE_SUCCEEDED or self._state == PIDAutotuneState.STATE_FAILED): - self.init_tuner(now) + self._state = PIDAutotuneState.STATE_RELAY_STEP_UP self._last_run_timestamp = now -- 2.44.1 From 9e215830aa034b13606d861bb6f384839efc8f05 Mon Sep 17 00:00:00 2001 From: topquark12 Date: Wed, 6 Jan 2021 10:25:44 +0800 Subject: [PATCH 09/12] pytec: narrow down exception catching range --- pytec/autotune.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/pytec/autotune.py b/pytec/autotune.py index b8b8b3d..8dd96dd 100644 --- a/pytec/autotune.py +++ b/pytec/autotune.py @@ -246,18 +246,19 @@ def main(): try: ch = data[channel] - temperature = ch['temperature'] - - if (tuner.run(temperature, ch['time'])): - break - - tuner_out = tuner.output() - - tec.set_param("pwm", channel, "i_set", tuner_out) - + # Sometimes report_mode may yeild empty object except KeyError: pass + temperature = ch['temperature'] + + if (tuner.run(temperature, ch['time'])): + break + + tuner_out = tuner.output() + + tec.set_param("pwm", channel, "i_set", tuner_out) + tec.set_param("pwm", channel, "i_set", 0) -- 2.44.1 From 089f8a0dead5c5d0880b36bd0a45b74a488ec197 Mon Sep 17 00:00:00 2001 From: topquark12 Date: Wed, 6 Jan 2021 10:33:50 +0800 Subject: [PATCH 10/12] pytec: prevent uninitialized ch from crashing the code below --- pytec/autotune.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytec/autotune.py b/pytec/autotune.py index 8dd96dd..1d98e68 100644 --- a/pytec/autotune.py +++ b/pytec/autotune.py @@ -248,7 +248,7 @@ def main(): # Sometimes report_mode may yeild empty object except KeyError: - pass + continue temperature = ch['temperature'] -- 2.44.1 From 6fe2f151ff8925773d3271254bb7f0d5d89fda80 Mon Sep 17 00:00:00 2001 From: topquark12 Date: Wed, 6 Jan 2021 10:36:43 +0800 Subject: [PATCH 11/12] remove blank line --- pytec/autotune.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pytec/autotune.py b/pytec/autotune.py index 1d98e68..1aa8833 100644 --- a/pytec/autotune.py +++ b/pytec/autotune.py @@ -245,7 +245,6 @@ def main(): for data in tec.report_mode(): try: ch = data[channel] - # Sometimes report_mode may yeild empty object except KeyError: continue -- 2.44.1 From 3b6ce68a626a2e4de114710981d6c2b78ca44926 Mon Sep 17 00:00:00 2001 From: topquark12 Date: Wed, 6 Jan 2021 11:00:28 +0800 Subject: [PATCH 12/12] pytec: comment updated to indicate workaround --- pytec/autotune.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytec/autotune.py b/pytec/autotune.py index 1aa8833..f2e5de5 100644 --- a/pytec/autotune.py +++ b/pytec/autotune.py @@ -245,7 +245,7 @@ def main(): for data in tec.report_mode(): try: ch = data[channel] - # Sometimes report_mode may yeild empty object + # Workaround for report_mode may yeild empty object except KeyError: continue -- 2.44.1