blob: b5e61c102dff5f248986ff2266ebd863f0f25972 [file] [log] [blame]
Comran Morshed2ae094e2016-01-23 20:43:20 +00001#!/usr/bin/python
2
3from frc971.control_loops.python import control_loop
4from frc971.control_loops.python import controls
5from frc971.control_loops.python import polytope
6from y2016.control_loops.python import polydrivetrain
7import numpy
8import sys
9import matplotlib
10from matplotlib import pylab
11import gflags
12import glog
13
14FLAGS = gflags.FLAGS
15
16try:
17 gflags.DEFINE_bool('plot', False, 'If true, plot the loop response.')
18except gflags.DuplicateFlagError:
19 pass
20
21class Intake(control_loop.ControlLoop):
22 def __init__(self, name="Intake", mass=None):
23 super(Intake, self).__init__(name)
24 # TODO(constants): Update all of these & retune poles.
25 # Stall Torque in N m
26 self.stall_torque = 0.476
27 # Stall Current in Amps
28 self.stall_current = 80.730
29 # Free Speed in RPM
30 self.free_speed = 13906.0
31 # Free Current in Amps
32 self.free_current = 5.820
33 # Mass of the intake
34 if mass is None:
35 self.mass = 5.0
36 else:
37 self.mass = mass
38
39 # Resistance of the motor
40 self.R = 12.0 / self.stall_current
41 # Motor velocity constant
42 self.Kv = ((self.free_speed / 60.0 * 2.0 * numpy.pi) /
43 (12.0 - self.R * self.free_current))
44 # Torque constant
45 self.Kt = self.stall_torque / self.stall_current
46 # Gear ratio
47 self.G = (56.0 / 12.0) * (54.0 / 14.0) * (64.0 / 14.0) * (72.0 / 18.0)
48 # Intake length
49 self.r = 18 * 0.0254
50
51 self.J = self.r * self.mass
52
53 # Control loop time step
54 self.dt = 0.005
55
56 # State is [position, velocity]
57 # Input is [Voltage]
58
59 C1 = self.G * self.G * self.Kt / (self.R * self.J * self.Kv)
60 C2 = self.Kt * self.G / (self.J * self.R)
61
62 self.A_continuous = numpy.matrix(
63 [[0, 1],
64 [0, -C1]])
65
66 # Start with the unmodified input
67 self.B_continuous = numpy.matrix(
68 [[0],
69 [C2]])
70
71 self.C = numpy.matrix([[1, 0]])
72 self.D = numpy.matrix([[0]])
73
74 self.A, self.B = self.ContinuousToDiscrete(
75 self.A_continuous, self.B_continuous, self.dt)
76
77 controllability = controls.ctrb(self.A, self.B)
78
79 print "Free speed is", self.free_speed * numpy.pi * 2.0 / 60.0 / self.G
80
81 q_pos = 0.15
82 q_vel = 2.5
83 self.Q = numpy.matrix([[(1.0 / (q_pos ** 2.0)), 0.0],
84 [0.0, (1.0 / (q_vel ** 2.0))]])
85
86 self.R = numpy.matrix([[(1.0 / (12.0 ** 2.0))]])
87 self.K = controls.dlqr(self.A, self.B, self.Q, self.R)
88
89 print 'K', self.K
90 print 'Poles are', numpy.linalg.eig(self.A - self.B * self.K)[0]
91
92 self.rpl = 0.30
93 self.ipl = 0.10
94 self.PlaceObserverPoles([self.rpl + 1j * self.ipl,
95 self.rpl - 1j * self.ipl])
96
97 print 'L is', self.L
98
99 q_pos = 0.05
100 q_vel = 2.65
101 self.Q = numpy.matrix([[(q_pos ** 2.0), 0.0],
102 [0.0, (q_vel ** 2.0)]])
103
104 r_volts = 0.025
105 self.R = numpy.matrix([[(r_volts ** 2.0)]])
106
107 self.KalmanGain, self.Q_steady = controls.kalman(
108 A=self.A, B=self.B, C=self.C, Q=self.Q, R=self.R)
109
110 print 'Kal', self.KalmanGain
111 self.L = self.A * self.KalmanGain
112 print 'KalL is', self.L
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]])
117 self.U_min = numpy.matrix([[-12.0]])
118
119 self.InitializeState()
120
121class IntegralIntake(Intake):
122 def __init__(self, name="IntegralIntake", mass=None):
123 super(IntegralIntake, self).__init__(name=name, mass=mass)
124
125 self.A_continuous_unaugmented = self.A_continuous
126 self.B_continuous_unaugmented = self.B_continuous
127
128 self.A_continuous = numpy.matrix(numpy.zeros((3, 3)))
129 self.A_continuous[0:2, 0:2] = self.A_continuous_unaugmented
130 self.A_continuous[0:2, 2] = self.B_continuous_unaugmented
131
132 self.B_continuous = numpy.matrix(numpy.zeros((3, 1)))
133 self.B_continuous[0:2, 0] = self.B_continuous_unaugmented
134
135 self.C_unaugmented = self.C
136 self.C = numpy.matrix(numpy.zeros((1, 3)))
137 self.C[0:1, 0:2] = self.C_unaugmented
138
139 self.A, self.B = self.ContinuousToDiscrete(self.A_continuous, self.B_continuous, self.dt)
140
141 q_pos = 0.08
142 q_vel = 4.00
143 q_voltage = 6.0
144 self.Q = numpy.matrix([[(q_pos ** 2.0), 0.0, 0.0],
145 [0.0, (q_vel ** 2.0), 0.0],
146 [0.0, 0.0, (q_voltage ** 2.0)]])
147
148 r_pos = 0.05
149 self.R = numpy.matrix([[(r_pos ** 2.0)]])
150
151 self.KalmanGain, self.Q_steady = controls.kalman(
152 A=self.A, B=self.B, C=self.C, Q=self.Q, R=self.R)
153 self.L = self.A * self.KalmanGain
154
155 self.K_unaugmented = self.K
156 self.K = numpy.matrix(numpy.zeros((1, 3)))
157 self.K[0, 0:2] = self.K_unaugmented
158 self.K[0, 2] = 1
159
160 self.InitializeState()
161class ScenarioPlotter(object):
162 def __init__(self):
163 # Various lists for graphing things.
164 self.t = []
165 self.x = []
166 self.v = []
167 self.a = []
168 self.x_hat = []
169 self.u = []
170
171 def run_test(self, intake, goal, iterations=200, controller_intake=None,
172 observer_intake=None):
173 """Runs the intake plant with an initial condition and goal.
174
175 Test for whether the goal has been reached and whether the separation
176 goes outside of the initial and goal values by more than
177 max_separation_error.
178
179 Prints out something for a failure of either condition and returns
180 False if tests fail.
181 Args:
182 intake: intake object to use.
183 goal: goal state.
184 iterations: Number of timesteps to run the model for.
185 controller_intake: Intake object to get K from, or None if we should
186 use intake.
187 observer_intake: Intake object to use for the observer, or None if we should
188 use the actual state.
189 """
190
191 if controller_intake is None:
192 controller_intake = intake
193
194 vbat = 12.0
195
196 if self.t:
197 initial_t = self.t[-1] + intake.dt
198 else:
199 initial_t = 0
200
201 for i in xrange(iterations):
202 X_hat = intake.X
203
204 if observer_intake is not None:
205 X_hat = observer_intake.X_hat
206 self.x_hat.append(observer_intake.X_hat[0, 0])
207
208 U = controller_intake.K * (goal - X_hat)
209 U[0, 0] = numpy.clip(U[0, 0], -vbat, vbat)
210 self.x.append(intake.X[0, 0])
211
212 if self.v:
213 last_v = self.v[-1]
214 else:
215 last_v = 0
216
217 self.v.append(intake.X[1, 0])
218 self.a.append((self.v[-1] - last_v) / intake.dt)
219
220 if observer_intake is not None:
221 observer_intake.Y = intake.Y
222 observer_intake.CorrectObserver(U)
223
224 intake.Update(U)
225
226 if observer_intake is not None:
227 observer_intake.PredictObserver(U)
228
229 self.t.append(initial_t + i * intake.dt)
230 self.u.append(U[0, 0])
231
232 glog.debug('Time: %f', self.t[-1])
233
234 def Plot(self):
235 pylab.subplot(3, 1, 1)
236 pylab.plot(self.t, self.x, label='x')
237 pylab.plot(self.t, self.x_hat, label='x_hat')
238 pylab.legend()
239
240 pylab.subplot(3, 1, 2)
241 pylab.plot(self.t, self.u, label='u')
242
243 pylab.subplot(3, 1, 3)
244 pylab.plot(self.t, self.a, label='a')
245
246 pylab.legend()
247 pylab.show()
248
249
250def main(argv):
251 argv = FLAGS(argv)
252
253 base_mass = 4
254 load_mass = 0
255
256 scenario_plotter = ScenarioPlotter()
257
258 intake = Intake(mass=base_mass + load_mass)
259 intake_controller = IntegralIntake(mass=base_mass + load_mass)
260 observer_intake = IntegralIntake(mass=base_mass + load_mass)
261
262 # Test moving the intake with constant separation.
263 initial_X = numpy.matrix([[0.0], [0.0]])
264 R = numpy.matrix([[1.0], [0.0], [0.0]])
265 scenario_plotter.run_test(intake, goal=R, controller_intake=intake_controller,
266 observer_intake=observer_intake, iterations=200)
267
268 if FLAGS.plot:
269 scenario_plotter.Plot()
270
271 # Write the generated constants out to a file.
272 if len(argv) != 5:
273 glog.fatal('Expected .h file name and .cc file name for the intake and integral intake.')
274 else:
275 namespaces = ['y2016', 'control_loops', 'superstructure']
276 intake = Intake("Intake")
277 loop_writer = control_loop.ControlLoopWriter('Intake', [intake],
278 namespaces=namespaces)
279 loop_writer.Write(argv[1], argv[2])
280
281 integral_intake = IntegralIntake("IntegralIntake", mass=base_mass + load_mass)
282 integral_loop_writer = control_loop.ControlLoopWriter("IntegralIntake", [integral_intake],
283 namespaces=['y2016', 'control_loops', 'superstructure'])
284 integral_loop_writer.Write(argv[3], argv[4])
285
286if __name__ == '__main__':
287 sys.exit(main(sys.argv))