def __init__(self, inputqueue, outputqueue): multiprocessing.Process.__init__(self) self.safetytrigger = False # Use correct communication queues self.inputqueue = inputqueue self.outputqueue = outputqueue # Configure ADC correctly self.i2c_helper = ABEHelpers() self.bus = self.i2c_helper.get_smbus() self.adc = ADCPi(self.bus, 0x68, 0x69, 16) self.adc.set_pga(8) # Setup variables dictionary with initial values self.variabledict = { 'sleeptime': 5, 'kp': 0, 'ki': 0, 'kd': 0, 'setpoint': { '2016-08-12 09:00:00': 20, '2016-08-13 09:00:00': 20 }, 'control_channel': 1, 'control_k1': 885, 'control_k2': 2790, 'control_k3': 0, 'safety_channel': 2, 'safety_k1': 885, 'safety_k2': 2790, 'safety_k3': 0, 'safety_value': 0, 'safety_mode': "off", 'umax': 100, 'umin': 0, 'moutput': "auto", 'ssrduty': 1, 'ssrpin': 27, 'ssrmode': "pwm", 'pwm_frequency': 1, 'relayduty': {'Relay1': 0, 'Relay2': 0, 'Relay3': 0, 'Relay4': 0, 'Relay5': 0}, 'relaypin': {'Relay1': "off", 'Relay2': "off", 'Relay3': "off", 'Relay4': "off", 'Relay5': "off"}, 'terminate': 0, 'autotune_temp': 102, 'autotune_hysteresis': 1, 'autotune_kp': 0, 'autotune_ki': 0, 'autotune_kd': 0, 'autotune_on': 'False', 'autotune_sleeptime': 0.1, 'autotune_maxiterations': 20, 'autotune_iterations': 0, 'autotune_convergence': 0.01, 'autotune_gainsign': 1, 'autotune_dict': { 'time': [], 'temp': [], 'output': [], 'timeperiod': [], 'peaktype': 'max', 'startrange': 0, 'timeperiodstart': 0, 'relayofftime': 0 }, 'autotune_peaks': { 'max': {}, 'min': {} } } # Initialize required variables self.setpoint = 0 self.setpointchanges = 2 self.output = 0 self.maxoutput = 0 self.outputdict = {} self.outputdict['Status'] = 'PID Controller Started'
class PIDController(multiprocessing.Process): def __init__(self, inputqueue, outputqueue): multiprocessing.Process.__init__(self) self.safetytrigger = False # Use correct communication queues self.inputqueue = inputqueue self.outputqueue = outputqueue # Configure ADC correctly self.i2c_helper = ABEHelpers() self.bus = self.i2c_helper.get_smbus() self.adc = ADCPi(self.bus, 0x68, 0x69, 16) self.adc.set_pga(8) # Setup variables dictionary with initial values self.variabledict = { 'sleeptime': 5, 'kp': 0, 'ki': 0, 'kd': 0, 'setpoint': { '2016-08-12 09:00:00': 20, '2016-08-13 09:00:00': 20 }, 'control_channel': 1, 'control_k1': 885, 'control_k2': 2790, 'control_k3': 0, 'safety_channel': 2, 'safety_k1': 885, 'safety_k2': 2790, 'safety_k3': 0, 'safety_value': 0, 'safety_mode': "off", 'umax': 100, 'umin': 0, 'moutput': "auto", 'ssrduty': 1, 'ssrpin': 27, 'ssrmode': "pwm", 'pwm_frequency': 1, 'relayduty': {'Relay1': 0, 'Relay2': 0, 'Relay3': 0, 'Relay4': 0, 'Relay5': 0}, 'relaypin': {'Relay1': "off", 'Relay2': "off", 'Relay3': "off", 'Relay4': "off", 'Relay5': "off"}, 'terminate': 0, 'autotune_temp': 102, 'autotune_hysteresis': 1, 'autotune_kp': 0, 'autotune_ki': 0, 'autotune_kd': 0, 'autotune_on': 'False', 'autotune_sleeptime': 0.1, 'autotune_maxiterations': 20, 'autotune_iterations': 0, 'autotune_convergence': 0.01, 'autotune_gainsign': 1, 'autotune_dict': { 'time': [], 'temp': [], 'output': [], 'timeperiod': [], 'peaktype': 'max', 'startrange': 0, 'timeperiodstart': 0, 'relayofftime': 0 }, 'autotune_peaks': { 'max': {}, 'min': {} } } # Initialize required variables self.setpoint = 0 self.setpointchanges = 2 self.output = 0 self.maxoutput = 0 self.outputdict = {} self.outputdict['Status'] = 'PID Controller Started' # Function for interpolating setpoint def setpoint_interpolate(self): timelist = [] valuelist = [] for time, value in sorted(self.variabledict['setpoint'].items()): timelist.append(time) valuelist.append(value) setpointchanges = len(timelist) - 1 timenow = datetime.datetime.now() if timenow < datetime.datetime.strptime(timelist[0], '%Y-%m-%d %H:%M:%S'): self.setpoint = "off" elif timenow == datetime.datetime.strptime(timelist[0], '%Y-%m-%d %H:%M:%S'): self.setpoint = valuelist[0] elif timenow >= datetime.datetime.strptime(timelist[setpointchanges], '%Y-%m-%d %H:%M:%S'): self.setpoint = "off" else: self.setpoint = "off" for x in range(setpointchanges, -1, -1): # Check for current timeframe and adjust setpoint by interpolation if datetime.datetime.strptime(timelist[x], '%Y-%m-%d %H:%M:%S') < timenow: time1 = datetime.datetime.strptime(timelist[x], '%Y-%m-%d %H:%M:%S') value1 = valuelist[x] time2 = datetime.datetime.strptime(timelist[x+1], '%Y-%m-%d %H:%M:%S') value2 = valuelist[x+1] self.setpoint = ((timenow - time1) / (time2 - time1)) * (value2 - value1) + value1 break # Autotune function def autotune(self): if self.variabledict['autotune_iterations'] == 0: self.variabledict['sleeptime'] = self.variabledict['autotune_sleeptime'] self.variabledict['umin'] = 0 self.variabledict['umax'] = 100 self.variabledict['moutput'] = 0 self.outputdict['Status'] = 'Autotune started' # Read New Measured Variable mvchannel = self.variabledict['control_channel'] v = self.adc.read_voltage(mvchannel) mv = self.variabledict['control_k1'] * v * v + self.variabledict['control_k2'] * v + self.variabledict['control_k3'] # Do assymetric relay output if self.variabledict['autotune_gainsign'] >= 0: if mv <= self.variabledict['autotune_temp'] - self.variabledict['autotune_hysteresis'] and self.variabledict['moutput'] == 0: print('relay on') self.variabledict['moutput'] = 100 self.variabledict['autotune_iterations'] += 1 elif mv >= self.variabledict['autotune_temp'] + self.variabledict['autotune_hysteresis'] and self.variabledict['moutput'] == 100: print('relay off') self.variabledict['moutput'] = 0 self.variabledict['autotune_iterations'] += 1 else: if mv <= self.variabledict['autotune_temp'] - self.variabledict['autotune_hysteresis'] and self.variabledict['moutput'] == 100: print('relay off') self.variabledict['moutput'] = 0 self.variabledict['autotune_iterations'] += 1 elif mv >= self.variabledict['autotune_temp'] + self.variabledict['autotune_hysteresis'] and self.variabledict['moutput'] == 0: print('relay on') self.variabledict['moutput'] = 100 self.variabledict['autotune_iterations'] += 1 self.outputdict['Status'] = 'Autotune in progress - %s of %s' % (self.variabledict['autotune_iterations'], self.variabledict['autotune_maxiterations']) # Update autotunedict self.variabledict['autotune_dict']['time'].append(time.time()) self.variabledict['autotune_dict']['temp'].append(mv) self.variabledict['autotune_dict']['output'].append(self.variabledict['moutput']) if len(self.variabledict['autotune_dict']['output']) == 1: if self.variabledict['autotune_dict']['output'][0] == 100: self.variabledict['autotune_dict']['peaktype'] = 'min' self.variabledict['autotune_dict']['startrange'] = 0 self.variabledict['autotune_dict']['timeperiodstart'] = self.variabledict['autotune_dict']['time'][0] else: if self.variabledict['autotune_dict']['output'][-1] != self.variabledict['autotune_dict']['output'][-2]: endrange = len(self.variabledict['autotune_dict']['output']) startrange = self.variabledict['autotune_dict']['startrange'] # Determine Peak type if self.variabledict['autotune_dict']['output'][-1] == 100: self.variabledict['autotune_dict']['peaktype'] = 'max' peaktemp = max(self.variabledict['autotune_dict']['temp'][startrange:endrange]) peaktempslot = self.variabledict['autotune_dict']['temp'].index(peaktemp) peaktime = self.variabledict['autotune_dict']['time'][peaktempslot] self.variabledict['autotune_peaks']['max'][peaktime] = peaktemp # prepare variables for convergence test periodstart = self.variabledict['autotune_dict']['timeperiodstart'] periodend = self.variabledict['autotune_dict']['time'][-1] periodlength = periodend - periodstart self.variabledict['autotune_dict']['timeperiod'].append(periodlength) self.variabledict['autotune_dict']['timeperiodstart'] = self.variabledict['autotune_dict']['time'][-1] else: self.variabledict['autotune_dict']['peaktype'] = 'min' peaktemp = min(self.variabledict['autotune_dict']['temp'][startrange:endrange]) peaktempslot = self.variabledict['autotune_dict']['temp'].index(peaktemp) peaktime = self.variabledict['autotune_dict']['time'][peaktempslot] self.variabledict['autotune_peaks']['min'][peaktime] = peaktemp self.variabledict['autotune_dict']['relayofftime'] = self.variabledict['autotune_dict']['time'][-1] # Check for convergence if len(self.variabledict['autotune_dict']['timeperiod']) > 1: newlength = self.variabledict['autotune_dict']['timeperiod'][-1] oldlength = self.variabledict['autotune_dict']['timeperiod'][-2] conv = abs((newlength - oldlength) / oldlength) if conv < self.variabledict['autotune_convergence']: # Converged self.variabledict['autotune_on'] = 'False' self.outputdict['Status'] = 'Autotuner converged - Terminating' self.variabledict['terminate'] = 'True' # Write the data to database conn = sqlite3.connect('Autotune.db') dboutput = {} for i in range(len(self.variabledict['autotune_dict']['time'])): dboutput['Time'] = self.variabledict['autotune_dict']['time'][i] dboutput['Temp'] = self.variabledict['autotune_dict']['temp'][i] dboutput['Output'] = self.variabledict['autotune_dict']['output'][i] try: with conn: conn.execute('INSERT INTO autotune(Time, Temp, Output) VALUES' ' (:Time, :Temp, :Output)', dboutput) except sqlite3.IntegrityError: pass # todo Create Autotuner class - Complete the autotune procedure # d1 = ((self.variabledict['autotune_dict']['time'][-1] - self.variabledict['autotune_dict']['relayofftime']) / newlength) * self.maxoutput # d2 = self.maxoutput - d1 # self.outputdict['kp'] = "" # self.outputdict['ki'] = "" # self.outputdict['kd'] = "" if self.variabledict['autotune_iterations'] >= self.variabledict['autotune_maxiterations']: self.variabledict['autotune_on'] = 'False' self.outputdict['Status']= 'Autotuner failed to converge - Terminating' self.outputdict['autotune_on'] = 'False' self.outputdict['terminate'] = 'True' self.variabledict['terminate'] = 'True' # Main looping function of the PID Controller def run(self): # Get updated variables from queue try: while True: updated_variables = self.inputqueue.get_nowait() for variable, value in updated_variables.items(): self.variabledict[variable] = value except queue.Empty: pass # Initialize variables e = 0 e1 = 0 max_relay_output = 0 relaystate = {} relayduty = self.variabledict['relayduty'] for relay in relayduty.keys(): relaystate[relay] = 0 relayoutput = 0 GPIO.setmode(GPIO.BCM) relaypin = self.variabledict['relaypin'] # setup GPIO GPIO.setup(int(self.variabledict['ssrpin']), GPIO.OUT) if self.variabledict['ssrmode'] == 'pwm': pwm = GPIO.PWM(int(self.variabledict['ssrpin']), 1) pwm.start(0) # Set GPIO pins as outputs for relay in sorted(relaypin.keys()): if relaypin[relay] != "off": GPIO.setup(int(relaypin[relay]), GPIO.OUT) # Main control loop while True: # Check for updated variables try: while True: updated_variables = self.inputqueue.get_nowait() for variable, value in updated_variables.items(): self.variabledict[variable] = value except queue.Empty: pass # Check terminate variable if self.variabledict['terminate'] == 'True': break # Update Variables relayduty = self.variabledict['relayduty'] ssrduty = self.variabledict['ssrduty'] # Determine maximum output for relay in sorted(relayduty.keys()): max_relay_output += relayduty[relay] self.maxoutput = self.variabledict['ssrduty'] * .99 + max_relay_output # Get new setpoint based on current date and time self.setpoint_interpolate() # Check for autotune if self.variabledict['autotune_on'] == 'True': self.autotune() # Update control parameters k1 = self.variabledict['kp'] + self.variabledict['ki'] + self.variabledict['kd'] k2 = - self.variabledict['ki'] - 2 * self.variabledict['kd'] k3 = self.variabledict['kd'] sp = self.setpoint u = self.output # Read New Measured Variable mvchannel = int(self.variabledict['control_channel']) v = self.adc.read_voltage(mvchannel) mv = self.variabledict['control_k1'] * v * v + self.variabledict['control_k2'] * v + self.variabledict['control_k3'] # Check if setpoint is active and calculate control output if it is if self.setpoint != "off": # Update error variables e2 = e1 e1 = e e = sp - mv delta_u = k1 * e + k2 * e1 + k3 * e2 u += delta_u else: u = 0 # check for manual mode if self.variabledict['moutput'] != "auto": u = self.variabledict['moutput'] # clamp output to between min and max values if u > self.variabledict['umax']: u = self.variabledict['umax'] elif u < self.variabledict['umin']: u = self.variabledict['umin'] # Check safety variable if self.variabledict['safety_mode'] != "off": svchannel = int(self.variabledict['safety_channel']) sv = self.adc.read_voltage(svchannel) safetytemp = self.variabledict['safety_k1'] * sv * sv + self.variabledict['safety_k2'] * sv + self.variabledict['safety_k3'] if self.variabledict['safety_mode'] == "max" and self.variabledict['safety_value'] > safetytemp: u = 0 self.safetytrigger = True elif self.variabledict['safety_mode'] == "min" and self.variabledict['safety_value'] < safetytemp: u = 0 self.safetytrigger = True else: safetytemp = "off" self.output = u duty = u / self.variabledict['umax'] * 100 # Determine current output for relay in sorted(relayduty.keys()): relayoutput += relaystate[relay] * relayduty[relay] # Determine Required Output output = duty /100 * self.maxoutput # Check which relays should be switched on or off # If at max duty all should be on if duty == 100: for relay in sorted(relayduty.keys()): relaystate[relay] = 1 # Relays produce too much output-switch relays off until relays produce less output than the desired output elif relayoutput > output: for relay in sorted(relayduty.keys(), reverse=True): relayoutput -= relaystate[relay] * relayduty[relay] relaystate[relay] = 0 if relayoutput < output: break # Relays produce too little output-switch relays on until relays produce enough output elif relayoutput + ssrduty < output: for relay in sorted(relayduty.keys()): if relaystate[relay] == 0: relaystate[relay] = 1 relayoutput += relayduty[relay] if relayoutput + ssrduty > output: break # Calculate PWM duty needed from ssr ssroutput = output - relayoutput ssr_pwmduty = (ssroutput * 100) // ssrduty # Activate pins to switch relays for relay in sorted(relaypin.keys()): if relaypin[relay] != "off": GPIO.output(int(relaypin[relay]), relaystate[relay]) # Change ssr PWM if self.variabledict['ssrmode'] == 'pwm': pwm.ChangeFrequency(self.variabledict['pwm_frequency']) pwm.ChangeDutyCycle(ssr_pwmduty) elif self.variabledict['ssrmode'] == 'relay': if ssr_pwmduty > 50: GPIO.output(int(self.variabledict['ssrpin']), 1) else: GPIO.output(int(self.variabledict['ssrpin']), 0) # Update output dictionary self.outputdict['DateTime'] = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') self.outputdict['Temperature'] = mv self.outputdict['Duty'] = duty self.outputdict['Setpoint'] = self.setpoint self.outputdict['SafetyTemp'] = safetytemp self.outputdict['SafetyTrigger'] = self.safetytrigger # Send output to Manager self.outputqueue.put(self.outputdict) # Wait before running loop again time.sleep(self.variabledict['sleeptime']) # Ensure GPIO is cleaned up before exiting loop GPIO.cleanup() print('PID Controller exiting')