blob: d522034e32dab5e403f453ac77eef3912d648f5f [file] [log] [blame]
Austin Schuh1a387962015-01-31 16:36:20 -08001#!/usr/bin/python
2
Austin Schuh88af0852016-12-04 20:31:32 -08003from frc971.control_loops.python import control_loop
4from frc971.control_loops.python import controls
5from frc971.control_loops.python import polytope
Austin Schuh1a387962015-01-31 16:36:20 -08006import numpy
7import sys
8import matplotlib
9from matplotlib import pylab
10
Austin Schuh6c20f202017-02-18 22:31:44 -080011import gflags
12import glog
13
14FLAGS = gflags.FLAGS
15
16gflags.DEFINE_bool('plot', False, 'If true, plot the loop response.')
17
18
Austin Schuh1a387962015-01-31 16:36:20 -080019class Elevator(control_loop.ControlLoop):
20 def __init__(self, name="Elevator", mass=None):
21 super(Elevator, self).__init__(name)
22 # Stall Torque in N m
23 self.stall_torque = 0.476
24 # Stall Current in Amps
25 self.stall_current = 80.730
26 # Free Speed in RPM
27 self.free_speed = 13906.0
28 # Free Current in Amps
29 self.free_current = 5.820
30 # Mass of the elevator
31 if mass is None:
32 self.mass = 13.0
33 else:
34 self.mass = mass
35
36 # Resistance of the motor
37 self.R = 12.0 / self.stall_current
38 # Motor velocity constant
39 self.Kv = ((self.free_speed / 60.0 * 2.0 * numpy.pi) /
40 (12.0 - self.R * self.free_current))
41 # Torque constant
42 self.Kt = self.stall_torque / self.stall_current
43 # Gear ratio
44 self.G = (56.0 / 12.0) * (84.0 / 14.0)
45 # Pulley diameter
46 self.r = 32 * 0.005 / numpy.pi / 2.0
47 # Control loop time step
48 self.dt = 0.005
49
50 # Elevator left/right spring constant (N/m)
Austin Schuhbfb8b242015-02-16 15:45:22 -080051 self.spring = 800.0
Austin Schuh1a387962015-01-31 16:36:20 -080052
53 # State is [average position, average velocity,
54 # position difference/2, velocity difference/2]
Austin Schuh703b8d42015-02-01 14:56:34 -080055 # Input is [V_left, V_right]
Austin Schuh1a387962015-01-31 16:36:20 -080056
57 C1 = self.spring / (self.mass * 0.5)
58 C2 = self.Kt * self.G / (self.mass * 0.5 * self.r * self.R)
59 C3 = self.G * self.G * self.Kt / (
60 self.R * self.r * self.r * self.mass * 0.5 * self.Kv)
61
62 self.A_continuous = numpy.matrix(
63 [[0, 1, 0, 0],
64 [0, -C3, 0, 0],
65 [0, 0, 0, 1],
66 [0, 0, -C1 * 2.0, -C3]])
67
Austin Schuh6c20f202017-02-18 22:31:44 -080068 glog.debug('Full speed is', C2 / C3 * 12.0)
Austin Schuh1a387962015-01-31 16:36:20 -080069
70 # Start with the unmodified input
71 self.B_continuous = numpy.matrix(
72 [[0, 0],
73 [C2 / 2.0, C2 / 2.0],
74 [0, 0],
75 [C2 / 2.0, -C2 / 2.0]])
76
77 self.C = numpy.matrix([[1, 0, 1, 0],
78 [1, 0, -1, 0]])
79 self.D = numpy.matrix([[0, 0],
80 [0, 0]])
81
82 self.A, self.B = self.ContinuousToDiscrete(
83 self.A_continuous, self.B_continuous, self.dt)
84
Austin Schuh6c20f202017-02-18 22:31:44 -080085 glog.debug(repr(self.A))
Austin Schuh1a387962015-01-31 16:36:20 -080086
Brian Silvermane18cf502015-11-28 23:04:43 +000087 controllability = controls.ctrb(self.A, self.B)
Austin Schuh6c20f202017-02-18 22:31:44 -080088 glog.debug('Rank of augmented controllability matrix: %d',
89 numpy.linalg.matrix_rank(controllability))
Austin Schuh1a387962015-01-31 16:36:20 -080090
91 q_pos = 0.02
92 q_vel = 0.400
93 q_pos_diff = 0.01
94 q_vel_diff = 0.45
95 self.Q = numpy.matrix([[(1.0 / (q_pos ** 2.0)), 0.0, 0.0, 0.0],
96 [0.0, (1.0 / (q_vel ** 2.0)), 0.0, 0.0],
97 [0.0, 0.0, (1.0 / (q_pos_diff ** 2.0)), 0.0],
98 [0.0, 0.0, 0.0, (1.0 / (q_vel_diff ** 2.0))]])
99
100 self.R = numpy.matrix([[(1.0 / (12.0 ** 2.0)), 0.0],
101 [0.0, 1.0 / (12.0 ** 2.0)]])
102 self.K = controls.dlqr(self.A, self.B, self.Q, self.R)
Austin Schuh6c20f202017-02-18 22:31:44 -0800103 glog.debug(repr(self.K))
Austin Schuh1a387962015-01-31 16:36:20 -0800104
Austin Schuh6c20f202017-02-18 22:31:44 -0800105 glog.debug(repr(numpy.linalg.eig(self.A - self.B * self.K)[0]))
Austin Schuh1a387962015-01-31 16:36:20 -0800106
107 self.rpl = 0.20
108 self.ipl = 0.05
109 self.PlaceObserverPoles([self.rpl + 1j * self.ipl,
110 self.rpl + 1j * self.ipl,
111 self.rpl - 1j * self.ipl,
112 self.rpl - 1j * self.ipl])
113
114 # The box formed by U_min and U_max must encompass all possible values,
115 # or else Austin's code gets angry.
116 self.U_max = numpy.matrix([[12.0], [12.0]])
117 self.U_min = numpy.matrix([[-12.0], [-12.0]])
118
119 self.InitializeState()
120
121
122def CapU(U):
123 if U[0, 0] - U[1, 0] > 24:
124 return numpy.matrix([[12], [-12]])
125 elif U[0, 0] - U[1, 0] < -24:
126 return numpy.matrix([[-12], [12]])
127 else:
128 max_u = max(U[0, 0], U[1, 0])
129 min_u = min(U[0, 0], U[1, 0])
130 if max_u > 12:
131 return U - (max_u - 12)
132 if min_u < -12:
133 return U - (min_u + 12)
134 return U
135
136
137def run_test(elevator, initial_X, goal, max_separation_error=0.01,
Austin Schuh88af0852016-12-04 20:31:32 -0800138 show_graph=False, iterations=200, controller_elevator=None,
Austin Schuh1a387962015-01-31 16:36:20 -0800139 observer_elevator=None):
140 """Runs the elevator plant with an initial condition and goal.
141
142 The tests themselves are not terribly sophisticated; I just test for
143 whether the goal has been reached and whether the separation goes
144 outside of the initial and goal values by more than max_separation_error.
145 Prints out something for a failure of either condition and returns
146 False if tests fail.
147 Args:
148 elevator: elevator object to use.
149 initial_X: starting state.
150 goal: goal state.
151 show_graph: Whether or not to display a graph showing the changing
152 states and voltages.
153 iterations: Number of timesteps to run the model for.
154 controller_elevator: elevator object to get K from, or None if we should
155 use elevator.
156 observer_elevator: elevator object to use for the observer, or None if we
157 should use the actual state.
158 """
159
160 elevator.X = initial_X
161
162 if controller_elevator is None:
163 controller_elevator = elevator
164
165 if observer_elevator is not None:
166 observer_elevator.X_hat = initial_X + 0.01
167 observer_elevator.X_hat = initial_X
168
169 # Various lists for graphing things.
170 t = []
171 x_avg = []
172 x_sep = []
173 x_hat_avg = []
174 x_hat_sep = []
175 v_avg = []
176 v_sep = []
177 u_left = []
178 u_right = []
179
180 sep_plot_gain = 100.0
181
182 for i in xrange(iterations):
183 X_hat = elevator.X
184 if observer_elevator is not None:
185 X_hat = observer_elevator.X_hat
186 x_hat_avg.append(observer_elevator.X_hat[0, 0])
187 x_hat_sep.append(observer_elevator.X_hat[2, 0] * sep_plot_gain)
188 U = controller_elevator.K * (goal - X_hat)
189 U = CapU(U)
190 x_avg.append(elevator.X[0, 0])
191 v_avg.append(elevator.X[1, 0])
192 x_sep.append(elevator.X[2, 0] * sep_plot_gain)
193 v_sep.append(elevator.X[3, 0])
194 if observer_elevator is not None:
195 observer_elevator.PredictObserver(U)
196 elevator.Update(U)
197 if observer_elevator is not None:
198 observer_elevator.Y = elevator.Y
199 observer_elevator.CorrectObserver(U)
200
201 t.append(i * elevator.dt)
202 u_left.append(U[0, 0])
203 u_right.append(U[1, 0])
204
Austin Schuh6c20f202017-02-18 22:31:44 -0800205 glog.debug(repr(numpy.linalg.inv(elevator.A)))
206 glog.debug('delta time is %f', elevator.dt)
207 glog.debug('Velocity at t=0 is %f %f %f %f', x_avg[0], v_avg[0], x_sep[0], v_sep[0])
208 glog.debug('Velocity at t=1+dt is %f %f %f %f', x_avg[1], v_avg[1], x_sep[1], v_sep[1])
Austin Schuh1a387962015-01-31 16:36:20 -0800209
210 if show_graph:
211 pylab.subplot(2, 1, 1)
212 pylab.plot(t, x_avg, label='x avg')
213 pylab.plot(t, x_sep, label='x sep')
214 if observer_elevator is not None:
215 pylab.plot(t, x_hat_avg, label='x_hat avg')
216 pylab.plot(t, x_hat_sep, label='x_hat sep')
217 pylab.legend()
218
219 pylab.subplot(2, 1, 2)
220 pylab.plot(t, u_left, label='u left')
221 pylab.plot(t, u_right, label='u right')
222 pylab.legend()
223 pylab.show()
224
225
226def main(argv):
227 loaded_mass = 25
228 #loaded_mass = 0
229 elevator = Elevator(mass=13 + loaded_mass)
230 elevator_controller = Elevator(mass=13 + 15)
231 observer_elevator = Elevator(mass=13 + 15)
232 #observer_elevator = None
233
234 # Test moving the elevator with constant separation.
235 initial_X = numpy.matrix([[0.0], [0.0], [0.01], [0.0]])
236 #initial_X = numpy.matrix([[0.0], [0.0], [0.00], [0.0]])
237 R = numpy.matrix([[1.0], [0.0], [0.0], [0.0]])
238 run_test(elevator, initial_X, R, controller_elevator=elevator_controller,
239 observer_elevator=observer_elevator)
240
241 # Write the generated constants out to a file.
242 if len(argv) != 3:
Austin Schuh6c20f202017-02-18 22:31:44 -0800243 glog.fatal('Expected .h file name and .cc file name for the elevator.')
Austin Schuh1a387962015-01-31 16:36:20 -0800244 else:
Austin Schuh88af0852016-12-04 20:31:32 -0800245 namespaces = ['y2015', 'control_loops', 'fridge']
Austin Schuh1a387962015-01-31 16:36:20 -0800246 elevator = Elevator("Elevator")
Austin Schuh88af0852016-12-04 20:31:32 -0800247 loop_writer = control_loop.ControlLoopWriter("Elevator", [elevator],
248 namespaces=namespaces)
249 if argv[1][-3:] == '.cc':
250 loop_writer.Write(argv[2], argv[1])
251 else:
252 loop_writer.Write(argv[1], argv[2])
Austin Schuh1a387962015-01-31 16:36:20 -0800253
254if __name__ == '__main__':
Austin Schuh6c20f202017-02-18 22:31:44 -0800255 argv = FLAGS(sys.argv)
256 glog.init()
257 sys.exit(main(argv))