| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  | # Printer heater support | 
					
						
							|  |  |  | # | 
					
						
							|  |  |  | # Copyright (C) 2016  Kevin O'Connor <kevin@koconnor.net> | 
					
						
							|  |  |  | # | 
					
						
							|  |  |  | # This file may be distributed under the terms of the GNU GPLv3 license. | 
					
						
							|  |  |  | import math, logging, threading | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-06 15:33:21 -05:00
										 |  |  | # Available sensors | 
					
						
							|  |  |  | Sensors = { | 
					
						
							|  |  |  |     # Common thermistors and their Steinhart-Hart coefficients | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |     "EPCOS 100K B57560G104F": ( | 
					
						
							| 
									
										
										
										
											2016-09-15 12:16:08 -04:00
										 |  |  |         0.000722136308968056, 0.000216766566488498, 8.92935804531095e-08), | 
					
						
							|  |  |  |     "ATC Semitec 104GT-2": ( | 
					
						
							| 
									
										
										
										
											2017-03-06 15:19:52 -05:00
										 |  |  |         0.000809651054275124, 0.000211636030735685, 7.07420883993973e-08), | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | SAMPLE_TIME = 0.001 | 
					
						
							|  |  |  | SAMPLE_COUNT = 8 | 
					
						
							|  |  |  | REPORT_TIME = 0.300 | 
					
						
							| 
									
										
										
										
											2017-03-08 20:00:27 -05:00
										 |  |  | PWM_CYCLE_TIME = 0.100 | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  | KELVIN_TO_CELCIUS = -273.15 | 
					
						
							|  |  |  | MAX_HEAT_TIME = 5.0 | 
					
						
							|  |  |  | AMBIENT_TEMP = 25. | 
					
						
							| 
									
										
										
										
											2016-08-25 11:24:18 -04:00
										 |  |  | PID_PARAM_BASE = 255. | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-21 10:48:42 -05:00
										 |  |  | class error(Exception): | 
					
						
							|  |  |  |     pass | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  | class PrinterHeater: | 
					
						
							| 
									
										
										
										
											2017-02-21 10:48:42 -05:00
										 |  |  |     error = error | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |     def __init__(self, printer, config): | 
					
						
							| 
									
										
										
										
											2017-03-12 22:43:05 -04:00
										 |  |  |         self.name = config.section | 
					
						
							| 
									
										
										
										
											2017-03-06 15:33:21 -05:00
										 |  |  |         self.sensor_c = config.getchoice('sensor_type', Sensors) | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |         self.pullup_r = config.getfloat('pullup_resistor', 4700.) | 
					
						
							| 
									
										
										
										
											2016-09-30 19:04:24 -04:00
										 |  |  |         self.min_extrude_temp = config.getfloat('min_extrude_temp', 170.) | 
					
						
							| 
									
										
										
										
											2017-03-12 22:43:05 -04:00
										 |  |  |         self.min_temp = config.getfloat('min_temp') | 
					
						
							|  |  |  |         self.max_temp = config.getfloat('max_temp') | 
					
						
							|  |  |  |         self.max_power = max(0., min(1., config.getfloat('max_power', 1.))) | 
					
						
							|  |  |  |         self.can_extrude = (self.min_extrude_temp <= 0. | 
					
						
							|  |  |  |                             or printer.mcu.is_fileoutput()) | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |         self.lock = threading.Lock() | 
					
						
							|  |  |  |         self.last_temp = 0. | 
					
						
							| 
									
										
										
										
											2016-08-24 15:16:02 -04:00
										 |  |  |         self.last_temp_time = 0. | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |         self.target_temp = 0. | 
					
						
							| 
									
										
										
										
											2017-01-10 18:36:43 -05:00
										 |  |  |         algos = {'watermark': ControlBangBang, 'pid': ControlPID} | 
					
						
							| 
									
										
										
										
											2017-03-12 22:43:05 -04:00
										 |  |  |         algo = config.getchoice('control', algos) | 
					
						
							|  |  |  |         heater_pin = config.get('heater_pin') | 
					
						
							| 
									
										
										
										
											2017-03-06 15:33:21 -05:00
										 |  |  |         sensor_pin = config.get('sensor_pin') | 
					
						
							| 
									
										
										
										
											2016-08-25 11:24:18 -04:00
										 |  |  |         if algo is ControlBangBang and self.max_power == 1.: | 
					
						
							| 
									
										
										
										
											2017-03-12 22:43:05 -04:00
										 |  |  |             self.mcu_pwm = printer.mcu.create_digital_out( | 
					
						
							| 
									
										
										
										
											2017-01-10 18:36:43 -05:00
										 |  |  |                 heater_pin, MAX_HEAT_TIME) | 
					
						
							|  |  |  |         else: | 
					
						
							| 
									
										
										
										
											2017-03-12 22:43:05 -04:00
										 |  |  |             self.mcu_pwm = printer.mcu.create_pwm( | 
					
						
							| 
									
										
										
										
											2017-03-08 20:00:27 -05:00
										 |  |  |                 heater_pin, PWM_CYCLE_TIME, 0, MAX_HEAT_TIME) | 
					
						
							| 
									
										
										
										
											2017-03-06 15:33:21 -05:00
										 |  |  |         self.mcu_adc = printer.mcu.create_adc(sensor_pin) | 
					
						
							| 
									
										
										
										
											2017-03-06 15:19:52 -05:00
										 |  |  |         adc_range = [self.calc_adc(self.min_temp), self.calc_adc(self.max_temp)] | 
					
						
							|  |  |  |         self.mcu_adc.set_minmax(SAMPLE_TIME, SAMPLE_COUNT, | 
					
						
							|  |  |  |                                 minval=min(adc_range), maxval=max(adc_range)) | 
					
						
							| 
									
										
										
										
											2016-11-22 12:37:37 -05:00
										 |  |  |         self.mcu_adc.set_adc_callback(REPORT_TIME, self.adc_callback) | 
					
						
							| 
									
										
										
										
											2017-03-12 22:43:05 -04:00
										 |  |  |         self.control = algo(self, config) | 
					
						
							|  |  |  |         # pwm caching | 
					
						
							|  |  |  |         self.next_pwm_time = 0. | 
					
						
							|  |  |  |         self.last_pwm_value = 0 | 
					
						
							| 
									
										
										
										
											2016-08-24 15:16:02 -04:00
										 |  |  |     def set_pwm(self, read_time, value): | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |         if value: | 
					
						
							|  |  |  |             if self.target_temp <= 0.: | 
					
						
							|  |  |  |                 return | 
					
						
							| 
									
										
										
										
											2016-08-24 15:16:02 -04:00
										 |  |  |             if (read_time < self.next_pwm_time | 
					
						
							| 
									
										
										
										
											2016-08-25 11:24:18 -04:00
										 |  |  |                 and abs(value - self.last_pwm_value) < 0.05): | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |                 return | 
					
						
							| 
									
										
										
										
											2017-01-12 15:04:06 -05:00
										 |  |  |         elif not self.last_pwm_value and ( | 
					
						
							|  |  |  |                 self.target_temp <= 0. or read_time < self.next_pwm_time): | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |             return | 
					
						
							| 
									
										
										
										
											2016-08-24 15:16:02 -04:00
										 |  |  |         pwm_time = read_time + REPORT_TIME + SAMPLE_TIME*SAMPLE_COUNT | 
					
						
							|  |  |  |         self.next_pwm_time = pwm_time + 0.75 * MAX_HEAT_TIME | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |         self.last_pwm_value = value | 
					
						
							| 
									
										
										
										
											2016-08-25 11:24:18 -04:00
										 |  |  |         logging.debug("%s: pwm=%.3f@%.3f (from %.3f@%.3f [%.3f])" % ( | 
					
						
							| 
									
										
										
										
											2017-03-12 22:43:05 -04:00
										 |  |  |             self.name, value, pwm_time, | 
					
						
							| 
									
										
										
										
											2017-01-12 15:04:06 -05:00
										 |  |  |             self.last_temp, self.last_temp_time, self.target_temp)) | 
					
						
							| 
									
										
										
										
											2016-08-24 15:16:02 -04:00
										 |  |  |         self.mcu_pwm.set_pwm(pwm_time, value) | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |     # Temperature calculation | 
					
						
							|  |  |  |     def calc_temp(self, adc): | 
					
						
							|  |  |  |         r = self.pullup_r * adc / (1.0 - adc) | 
					
						
							|  |  |  |         ln_r = math.log(r) | 
					
						
							| 
									
										
										
										
											2017-03-06 15:33:21 -05:00
										 |  |  |         c1, c2, c3 = self.sensor_c | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |         temp_inv = c1 + c2*ln_r + c3*math.pow(ln_r, 3) | 
					
						
							|  |  |  |         return 1.0/temp_inv + KELVIN_TO_CELCIUS | 
					
						
							|  |  |  |     def calc_adc(self, temp): | 
					
						
							|  |  |  |         if temp is None: | 
					
						
							|  |  |  |             return None | 
					
						
							| 
									
										
										
										
											2017-03-06 15:33:21 -05:00
										 |  |  |         c1, c2, c3 = self.sensor_c | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |         temp -= KELVIN_TO_CELCIUS | 
					
						
							|  |  |  |         temp_inv = 1./temp | 
					
						
							|  |  |  |         y = (c1 - temp_inv) / (2*c3) | 
					
						
							|  |  |  |         x = math.sqrt(math.pow(c2 / (3.*c3), 3.) + math.pow(y, 2.)) | 
					
						
							|  |  |  |         r = math.exp(math.pow(x-y, 1./3.) - math.pow(x+y, 1./3.)) | 
					
						
							|  |  |  |         return r / (self.pullup_r + r) | 
					
						
							| 
									
										
										
										
											2016-08-24 15:16:02 -04:00
										 |  |  |     def adc_callback(self, read_time, read_value): | 
					
						
							| 
									
										
										
										
											2016-09-15 12:15:35 -04:00
										 |  |  |         temp = self.calc_temp(read_value) | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |         with self.lock: | 
					
						
							|  |  |  |             self.last_temp = temp | 
					
						
							| 
									
										
										
										
											2016-08-24 15:16:02 -04:00
										 |  |  |             self.last_temp_time = read_time | 
					
						
							| 
									
										
										
										
											2016-11-08 09:22:43 -05:00
										 |  |  |             self.can_extrude = (temp >= self.min_extrude_temp) | 
					
						
							| 
									
										
										
										
											2016-08-24 15:16:02 -04:00
										 |  |  |             self.control.adc_callback(read_time, temp) | 
					
						
							|  |  |  |         #logging.debug("temp: %.3f %f = %f" % (read_time, read_value, temp)) | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |     # External commands | 
					
						
							|  |  |  |     def set_temp(self, print_time, degrees): | 
					
						
							| 
									
										
										
										
											2017-02-21 10:48:42 -05:00
										 |  |  |         if degrees and (degrees < self.min_temp or degrees > self.max_temp): | 
					
						
							|  |  |  |             raise error("Requested temperature (%.1f) out of range (%.1f:%.1f)" | 
					
						
							|  |  |  |                         % (degrees, self.min_temp, self.max_temp)) | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |         with self.lock: | 
					
						
							|  |  |  |             self.target_temp = degrees | 
					
						
							|  |  |  |     def get_temp(self): | 
					
						
							|  |  |  |         with self.lock: | 
					
						
							|  |  |  |             return self.last_temp, self.target_temp | 
					
						
							|  |  |  |     def check_busy(self, eventtime): | 
					
						
							|  |  |  |         with self.lock: | 
					
						
							|  |  |  |             return self.control.check_busy(eventtime) | 
					
						
							|  |  |  |     def start_auto_tune(self, temp): | 
					
						
							|  |  |  |         with self.lock: | 
					
						
							|  |  |  |             self.control = ControlAutoTune(self, self.control, temp) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ###################################################################### | 
					
						
							|  |  |  | # Bang-bang control algo | 
					
						
							|  |  |  | ###################################################################### | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class ControlBangBang: | 
					
						
							|  |  |  |     def __init__(self, heater, config): | 
					
						
							|  |  |  |         self.heater = heater | 
					
						
							|  |  |  |         self.max_delta = config.getfloat('max_delta', 2.0) | 
					
						
							|  |  |  |         self.heating = False | 
					
						
							| 
									
										
										
										
											2016-08-24 15:16:02 -04:00
										 |  |  |     def adc_callback(self, read_time, temp): | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |         if self.heating and temp >= self.heater.target_temp+self.max_delta: | 
					
						
							|  |  |  |             self.heating = False | 
					
						
							|  |  |  |         elif not self.heating and temp <= self.heater.target_temp-self.max_delta: | 
					
						
							|  |  |  |             self.heating = True | 
					
						
							|  |  |  |         if self.heating: | 
					
						
							| 
									
										
										
										
											2016-08-25 11:24:18 -04:00
										 |  |  |             self.heater.set_pwm(read_time, self.heater.max_power) | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |         else: | 
					
						
							| 
									
										
										
										
											2016-08-25 11:24:18 -04:00
										 |  |  |             self.heater.set_pwm(read_time, 0.) | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |     def check_busy(self, eventtime): | 
					
						
							|  |  |  |         return self.heater.last_temp < self.heater.target_temp-self.max_delta | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ###################################################################### | 
					
						
							|  |  |  | # Proportional Integral Derivative (PID) control algo | 
					
						
							|  |  |  | ###################################################################### | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class ControlPID: | 
					
						
							|  |  |  |     def __init__(self, heater, config): | 
					
						
							|  |  |  |         self.heater = heater | 
					
						
							| 
									
										
										
										
											2016-08-25 11:24:18 -04:00
										 |  |  |         self.Kp = config.getfloat('pid_Kp') / PID_PARAM_BASE | 
					
						
							|  |  |  |         self.Ki = config.getfloat('pid_Ki') / PID_PARAM_BASE | 
					
						
							|  |  |  |         self.Kd = config.getfloat('pid_Kd') / PID_PARAM_BASE | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |         self.min_deriv_time = config.getfloat('pid_deriv_time', 2.) | 
					
						
							| 
									
										
										
										
											2016-08-25 11:24:18 -04:00
										 |  |  |         imax = config.getfloat('pid_integral_max', heater.max_power) | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |         self.temp_integ_max = imax / self.Ki | 
					
						
							|  |  |  |         self.prev_temp = AMBIENT_TEMP | 
					
						
							| 
									
										
										
										
											2016-08-24 15:16:02 -04:00
										 |  |  |         self.prev_temp_time = 0. | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |         self.prev_temp_deriv = 0. | 
					
						
							|  |  |  |         self.prev_temp_integ = 0. | 
					
						
							| 
									
										
										
										
											2016-08-24 15:16:02 -04:00
										 |  |  |     def adc_callback(self, read_time, temp): | 
					
						
							|  |  |  |         time_diff = read_time - self.prev_temp_time | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |         # Calculate change of temperature | 
					
						
							|  |  |  |         temp_diff = temp - self.prev_temp | 
					
						
							|  |  |  |         if time_diff >= self.min_deriv_time: | 
					
						
							|  |  |  |             temp_deriv = temp_diff / time_diff | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             temp_deriv = (self.prev_temp_deriv * (self.min_deriv_time-time_diff) | 
					
						
							|  |  |  |                           + temp_diff) / self.min_deriv_time | 
					
						
							|  |  |  |         # Calculate accumulated temperature "error" | 
					
						
							|  |  |  |         temp_err = self.heater.target_temp - temp | 
					
						
							|  |  |  |         temp_integ = self.prev_temp_integ + temp_err * time_diff | 
					
						
							|  |  |  |         temp_integ = max(0., min(self.temp_integ_max, temp_integ)) | 
					
						
							|  |  |  |         # Calculate output | 
					
						
							| 
									
										
										
										
											2016-08-25 11:24:18 -04:00
										 |  |  |         co = self.Kp*temp_err + self.Ki*temp_integ - self.Kd*temp_deriv | 
					
						
							| 
									
										
										
										
											2016-08-24 15:16:02 -04:00
										 |  |  |         #logging.debug("pid: %f@%.3f -> diff=%f deriv=%f err=%f integ=%f co=%d" % ( | 
					
						
							|  |  |  |         #    temp, read_time, temp_diff, temp_deriv, temp_err, temp_integ, co)) | 
					
						
							| 
									
										
										
										
											2016-08-25 11:24:18 -04:00
										 |  |  |         bounded_co = max(0., min(self.heater.max_power, co)) | 
					
						
							| 
									
										
										
										
											2016-08-24 15:16:02 -04:00
										 |  |  |         self.heater.set_pwm(read_time, bounded_co) | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |         # Store state for next measurement | 
					
						
							|  |  |  |         self.prev_temp = temp | 
					
						
							| 
									
										
										
										
											2016-08-24 15:16:02 -04:00
										 |  |  |         self.prev_temp_time = read_time | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |         self.prev_temp_deriv = temp_deriv | 
					
						
							|  |  |  |         if co == bounded_co: | 
					
						
							|  |  |  |             self.prev_temp_integ = temp_integ | 
					
						
							|  |  |  |     def check_busy(self, eventtime): | 
					
						
							|  |  |  |         temp_diff = self.heater.target_temp - self.heater.last_temp | 
					
						
							|  |  |  |         return abs(temp_diff) > 1. or abs(self.prev_temp_deriv) > 0.1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ###################################################################### | 
					
						
							|  |  |  | # Ziegler-Nichols PID autotuning | 
					
						
							|  |  |  | ###################################################################### | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | TUNE_PID_DELTA = 5.0 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class ControlAutoTune: | 
					
						
							|  |  |  |     def __init__(self, heater, old_control, target_temp): | 
					
						
							|  |  |  |         self.heater = heater | 
					
						
							|  |  |  |         self.old_control = old_control | 
					
						
							|  |  |  |         self.target_temp = target_temp | 
					
						
							|  |  |  |         self.heating = False | 
					
						
							|  |  |  |         self.peaks = [] | 
					
						
							|  |  |  |         self.peak = 0. | 
					
						
							| 
									
										
										
										
											2016-08-24 15:16:02 -04:00
										 |  |  |         self.peak_time = 0. | 
					
						
							|  |  |  |     def adc_callback(self, read_time, temp): | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |         if self.heating and temp >= self.target_temp: | 
					
						
							|  |  |  |             self.heating = False | 
					
						
							|  |  |  |             self.check_peaks() | 
					
						
							|  |  |  |         elif not self.heating and temp <= self.target_temp - TUNE_PID_DELTA: | 
					
						
							|  |  |  |             self.heating = True | 
					
						
							|  |  |  |             self.check_peaks() | 
					
						
							|  |  |  |         if self.heating: | 
					
						
							| 
									
										
										
										
											2016-08-25 11:24:18 -04:00
										 |  |  |             self.heater.set_pwm(read_time, self.heater.max_power) | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |             if temp < self.peak: | 
					
						
							|  |  |  |                 self.peak = temp | 
					
						
							| 
									
										
										
										
											2016-08-24 15:16:02 -04:00
										 |  |  |                 self.peak_time = read_time | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |         else: | 
					
						
							| 
									
										
										
										
											2016-08-25 11:24:18 -04:00
										 |  |  |             self.heater.set_pwm(read_time, 0.) | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |             if temp > self.peak: | 
					
						
							|  |  |  |                 self.peak = temp | 
					
						
							| 
									
										
										
										
											2016-08-24 15:16:02 -04:00
										 |  |  |                 self.peak_time = read_time | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |     def check_peaks(self): | 
					
						
							| 
									
										
										
										
											2016-08-24 15:16:02 -04:00
										 |  |  |         self.peaks.append((self.peak, self.peak_time)) | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |         if self.heating: | 
					
						
							|  |  |  |             self.peak = 9999999. | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             self.peak = -9999999. | 
					
						
							|  |  |  |         if len(self.peaks) < 4: | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  |         temp_diff = self.peaks[-1][0] - self.peaks[-2][0] | 
					
						
							| 
									
										
										
										
											2016-08-24 15:16:02 -04:00
										 |  |  |         time_diff = self.peaks[-1][1] - self.peaks[-3][1] | 
					
						
							| 
									
										
										
										
											2016-08-25 11:24:18 -04:00
										 |  |  |         max_power = self.heater.max_power | 
					
						
							|  |  |  |         Ku = 4. * (2. * max_power) / (abs(temp_diff) * math.pi) | 
					
						
							| 
									
										
										
										
											2016-08-24 15:16:02 -04:00
										 |  |  |         Tu = time_diff | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |         Kp = 0.6 * Ku | 
					
						
							|  |  |  |         Ti = 0.5 * Tu | 
					
						
							|  |  |  |         Td = 0.125 * Tu | 
					
						
							|  |  |  |         Ki = Kp / Ti | 
					
						
							|  |  |  |         Kd = Kp * Td | 
					
						
							| 
									
										
										
										
											2016-08-25 11:24:18 -04:00
										 |  |  |         logging.info("Autotune: raw=%f/%f Ku=%f Tu=%f  Kp=%f Ki=%f Kd=%f" % ( | 
					
						
							|  |  |  |             temp_diff, max_power, Ku, Tu, | 
					
						
							|  |  |  |             Kp * PID_PARAM_BASE, Ki * PID_PARAM_BASE, Kd * PID_PARAM_BASE)) | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |     def check_busy(self, eventtime): | 
					
						
							|  |  |  |         if self.heating or len(self.peaks) < 12: | 
					
						
							|  |  |  |             return True | 
					
						
							|  |  |  |         self.heater.control = self.old_control | 
					
						
							|  |  |  |         return False | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ###################################################################### | 
					
						
							|  |  |  | # Tuning information test | 
					
						
							|  |  |  | ###################################################################### | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class ControlBumpTest: | 
					
						
							|  |  |  |     def __init__(self, heater, old_control, target_temp): | 
					
						
							|  |  |  |         self.heater = heater | 
					
						
							|  |  |  |         self.old_control = old_control | 
					
						
							|  |  |  |         self.target_temp = target_temp | 
					
						
							|  |  |  |         self.temp_samples = {} | 
					
						
							|  |  |  |         self.pwm_samples = {} | 
					
						
							|  |  |  |         self.state = 0 | 
					
						
							| 
									
										
										
										
											2016-08-24 15:16:02 -04:00
										 |  |  |     def set_pwm(self, read_time, value): | 
					
						
							|  |  |  |         self.pwm_samples[read_time + 2*REPORT_TIME] = value | 
					
						
							|  |  |  |         self.heater.set_pwm(read_time, value) | 
					
						
							|  |  |  |     def adc_callback(self, read_time, temp): | 
					
						
							|  |  |  |         self.temp_samples[read_time] = temp | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |         if not self.state: | 
					
						
							| 
									
										
										
										
											2016-08-25 11:24:18 -04:00
										 |  |  |             self.set_pwm(read_time, 0.) | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |             if len(self.temp_samples) >= 20: | 
					
						
							|  |  |  |                 self.state += 1 | 
					
						
							|  |  |  |         elif self.state == 1: | 
					
						
							|  |  |  |             if temp < self.target_temp: | 
					
						
							| 
									
										
										
										
											2016-08-25 11:24:18 -04:00
										 |  |  |                 self.set_pwm(read_time, self.heater.max_power) | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |                 return | 
					
						
							| 
									
										
										
										
											2016-08-25 11:24:18 -04:00
										 |  |  |             self.set_pwm(read_time, 0.) | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |             self.state += 1 | 
					
						
							|  |  |  |         elif self.state == 2: | 
					
						
							| 
									
										
										
										
											2016-08-25 11:24:18 -04:00
										 |  |  |             self.set_pwm(read_time, 0.) | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |             if temp <= (self.target_temp + AMBIENT_TEMP) / 2.: | 
					
						
							|  |  |  |                 self.dump_stats() | 
					
						
							|  |  |  |                 self.state += 1 | 
					
						
							|  |  |  |     def dump_stats(self): | 
					
						
							| 
									
										
										
										
											2016-08-24 15:16:02 -04:00
										 |  |  |         out = ["%.3f %.1f %d" % (time, temp, self.pwm_samples.get(time, -1.)) | 
					
						
							|  |  |  |                for time, temp in sorted(self.temp_samples.items())] | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |         f = open("/tmp/heattest.txt", "wb") | 
					
						
							|  |  |  |         f.write('\n'.join(out)) | 
					
						
							|  |  |  |         f.close() | 
					
						
							|  |  |  |     def check_busy(self, eventtime): | 
					
						
							|  |  |  |         if self.state < 3: | 
					
						
							|  |  |  |             return True | 
					
						
							|  |  |  |         self.heater.control = self.old_control | 
					
						
							|  |  |  |         return False |