class Controller(object): def __init__(self, pid_x, pid_y, pid_z, pid_theta, bounding_box=True): ''' @param: pid_x is a tuple such that (kp, ki, kd, integrator, derivator, set_point) @param: pid_y is a tuple such that (kp, ki, kd, integrator, derivator, set_point) @param: pid_z is a tuple such that (kp, ki, kd, integrator, derivator, set_point) @param: pid_theta is a tuple such that (kp, ki, kd, integrator, derivator, set_point) @param: bounding_box is a boolean that will initially turn the bounding box on (True) or off (False). Default is True ''' self.pid_x = PID() self.pid_y = PID() self.pid_z = PID() self.pid_theta = PID() # Set gains, integrators, derivators, and set points self.pid_x.setKp(pid_x[0]) self.pid_x.setKi(pid_x[1]) self.pid_x.setKd(pid_x[2]) self.pid_x.setIntegrator(pid_x[3]) self.pid_x.setDerivator(pid_x[4]) self.pid_x.setPoint(pid_x[5]) self.pid_y.setKp(pid_y[0]) self.pid_y.setKi(pid_y[1]) self.pid_y.setKd(pid_y[2]) self.pid_y.setIntegrator(pid_y[3]) self.pid_y.setDerivator(pid_y[4]) self.pid_y.setPoint(pid_y[5]) self.pid_z.setKp(pid_z[0]) self.pid_z.setKi(pid_z[1]) self.pid_z.setKd(pid_z[2]) self.pid_z.setIntegrator(pid_z[3]) self.pid_z.setDerivator(pid_z[4]) self.pid_z.setPoint(pid_z[5]) self.pid_theta.setKp(pid_theta[0]) self.pid_theta.setKi(pid_theta[1]) self.pid_theta.setKd(pid_theta[2]) self.pid_theta.setIntegrator(pid_theta[3]) self.pid_theta.setDerivator(pid_theta[4]) self.pid_theta.setPoint(pid_theta[5]) # Should we use the bounding box? self.use_bounding_box = bounding_box def update(self, values): ''' This updates each controller and returns the updated values as a tuple @param: values is a tuple with the current value for each degree of freedom ''' x_update = self.pid_x.update(values[0]) y_update = self.pid_y.update(values[0]) z_update = self.pid_z.update(values[0]) theta_update = self.pid_theta.update(values[0]) return (x_update, y_update, z_update, theta_update)
class PID_simulator: #PID simulator with tkinter & matplotlib. Code might have been taken here and there from stackoverflow & github def __init__(self,pid = None): self.closed = False self.pid = PID(1.2,1,0.001) if pid is not None: self.pid = pid #how many inputs are in the graph at a time self.x_interval = 30 self.output = 0 self.x = [] self.y = [] self.start = 0 self.line = None self.root = Tk.Tk() self.root.title("Brought to you by Your Mom and co.") fig = plt.Figure() #bunch of labels & their position on the grid. play around to figure it out self.error_label = Tk.Label(self.root,text= "Your Mom") self.error_label.grid(column = 5, row = 0) p_label = Tk.Label(self.root,text= "P Constant").grid(column = 0, row = 0) i_label = Tk.Label(self.root,text= "I Constant").grid(column = 1, row = 0) d_label = Tk.Label(self.root,text= "D Constant").grid(column = 2, row = 0) pid_label = Tk.Label(self.root,text= "PID Setpoint").grid(column = 3, row = 0) #we only care about the text in the box. All other elements work on their own with Tkinter self.p_constant = Tk.Text(self.root,height = 2, width = 10, bg = "light yellow") self.p_constant.grid(column = 0, row = 1) self.i_constant = Tk.Text(self.root,height = 2, width = 10, bg = "light yellow") self.i_constant.grid(column = 1, row = 1) self.d_constant = Tk.Text(self.root,height = 2, width = 10, bg = "light yellow") self.d_constant.grid(column = 2, row = 1) self.sp = Tk.Text(self.root,height = 2, width = 10, bg = "light yellow") self.sp.grid(column = 3, row = 1) changePID = Tk.Button(self.root,text = "Change PID value", command = self.change_PID).grid(column = 1, row = 2) self.canvas = FigureCanvasTkAgg(fig,master = self.root) self.canvas.get_tk_widget().grid(column = 4, row = 3) #create a plot and put it into matplot's figure. 111 divides into 1 row & 1 column of plot #refers to the online doc. But yeah, we only need 1 plot. ax = fig.add_subplot(111) #set x and y axis. This can be put into variable in future sprint. ax.set_ylim([-180,180]) ax.set_xlim([0,self.x_interval]) #matplot automatically draw the line from a list of x and y values. line need to be redraw again and again self.line, = ax.plot(self.x, self.y) def on_closing(): self.closed = True self.root.destroy() self.root.protocol("WM_DELETE_WINDOW", on_closing) #threading bad =( #set an animation with delay of 500 mili. #ani = FuncAnimation(fig, # self.func_animate,interval=0, blit=False) #Tk.mainloop() #callback function for the button. Triggered when button pressed. #Every element is changed when button pressed. Bunch of try except in case of dumb/blank input def change_PID(self): pconst = self.p_constant.get("1.0",'end-1c') try: p_c = float(pconst) self.pid.setKp(p_c) except: pass iconst = self.i_constant.get("1.0",'end-1c') try: i_c = float(iconst) self.pid.setKi = i_c except: pass dconst = self.d_constant.get("1.0",'end-1c') try: d_c = float(dconst) pid.setKd = d_c except: pass setpoint = self.sp.get("1.0",'end-1c') try: s_p = float(setpoint) self.pid.SetPoint = s_p except: pass #callback function for the FuncAnimation. I have no idea what i does (interval maybe) def func_animate(self,feedback): if(len(self.y)<self.x_interval): self.x.append(self.start) self.start += 1 self.output = self.pid.update(feedback) #uncomment this line when start getting feedback from actual env #self.feedback += self.output self.y.append(feedback) else: self.x = [] self.y = [] self.start = 0 self.x.append(0) self.y.append(feedback) if not self.closed: #self.error_label.config(text = '%.3g'%(feedback-self.pid.SetPoint)) self.line.set_data(self.x, self.y) self.canvas.draw() self.root.update() #update feedback here def get_output(self): return self.output def update_feedback(self,feedback): self.func_animate(feedback) return self.output