blob: ae57730009260fc2801b45ca22ad1b2f02236916 [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):
Austin Schuh07cb5852016-01-31 00:58:46 -080022 def __init__(self, name="Intake"):
Comran Morshed2ae094e2016-01-23 20:43:20 +000023 super(Intake, self).__init__(name)
24 # TODO(constants): Update all of these & retune poles.
25 # Stall Torque in N m
Austin Schuh07cb5852016-01-31 00:58:46 -080026 self.stall_torque = 0.71
Comran Morshed2ae094e2016-01-23 20:43:20 +000027 # Stall Current in Amps
Austin Schuh07cb5852016-01-31 00:58:46 -080028 self.stall_current = 134
Comran Morshed2ae094e2016-01-23 20:43:20 +000029 # Free Speed in RPM
Austin Schuh07cb5852016-01-31 00:58:46 -080030 self.free_speed = 18730
Comran Morshed2ae094e2016-01-23 20:43:20 +000031 # Free Current in Amps
Austin Schuh07cb5852016-01-31 00:58:46 -080032 self.free_current = 0.7
Comran Morshed2ae094e2016-01-23 20:43:20 +000033
34 # Resistance of the motor
35 self.R = 12.0 / self.stall_current
36 # Motor velocity constant
37 self.Kv = ((self.free_speed / 60.0 * 2.0 * numpy.pi) /
38 (12.0 - self.R * self.free_current))
39 # Torque constant
40 self.Kt = self.stall_torque / self.stall_current
41 # Gear ratio
Austin Schuh07cb5852016-01-31 00:58:46 -080042 self.G = (56.0 / 12.0) * (54.0 / 14.0) * (64.0 / 18.0) * (48.0 / 16.0)
Comran Morshed2ae094e2016-01-23 20:43:20 +000043
Austin Schuh07cb5852016-01-31 00:58:46 -080044 self.J = 0.9
Comran Morshed2ae094e2016-01-23 20:43:20 +000045
46 # Control loop time step
47 self.dt = 0.005
48
49 # State is [position, velocity]
50 # Input is [Voltage]
51
52 C1 = self.G * self.G * self.Kt / (self.R * self.J * self.Kv)
53 C2 = self.Kt * self.G / (self.J * self.R)
54
55 self.A_continuous = numpy.matrix(
56 [[0, 1],
57 [0, -C1]])
58
59 # Start with the unmodified input
60 self.B_continuous = numpy.matrix(
61 [[0],
62 [C2]])
63
64 self.C = numpy.matrix([[1, 0]])
65 self.D = numpy.matrix([[0]])
66
67 self.A, self.B = self.ContinuousToDiscrete(
68 self.A_continuous, self.B_continuous, self.dt)
69
70 controllability = controls.ctrb(self.A, self.B)
71
72 print "Free speed is", self.free_speed * numpy.pi * 2.0 / 60.0 / self.G
73
Austin Schuh07cb5852016-01-31 00:58:46 -080074 q_pos = 0.20
75 q_vel = 5.5
Comran Morshed2ae094e2016-01-23 20:43:20 +000076 self.Q = numpy.matrix([[(1.0 / (q_pos ** 2.0)), 0.0],
77 [0.0, (1.0 / (q_vel ** 2.0))]])
78
79 self.R = numpy.matrix([[(1.0 / (12.0 ** 2.0))]])
80 self.K = controls.dlqr(self.A, self.B, self.Q, self.R)
81
82 print 'K', self.K
83 print 'Poles are', numpy.linalg.eig(self.A - self.B * self.K)[0]
84
85 self.rpl = 0.30
86 self.ipl = 0.10
87 self.PlaceObserverPoles([self.rpl + 1j * self.ipl,
88 self.rpl - 1j * self.ipl])
89
90 print 'L is', self.L
91
92 q_pos = 0.05
93 q_vel = 2.65
94 self.Q = numpy.matrix([[(q_pos ** 2.0), 0.0],
95 [0.0, (q_vel ** 2.0)]])
96
97 r_volts = 0.025
98 self.R = numpy.matrix([[(r_volts ** 2.0)]])
99
100 self.KalmanGain, self.Q_steady = controls.kalman(
101 A=self.A, B=self.B, C=self.C, Q=self.Q, R=self.R)
102
103 print 'Kal', self.KalmanGain
104 self.L = self.A * self.KalmanGain
105 print 'KalL is', self.L
106
107 # The box formed by U_min and U_max must encompass all possible values,
108 # or else Austin's code gets angry.
109 self.U_max = numpy.matrix([[12.0]])
110 self.U_min = numpy.matrix([[-12.0]])
111
112 self.InitializeState()
113
114class IntegralIntake(Intake):
Austin Schuh07cb5852016-01-31 00:58:46 -0800115 def __init__(self, name="IntegralIntake"):
116 super(IntegralIntake, self).__init__(name=name)
Comran Morshed2ae094e2016-01-23 20:43:20 +0000117
118 self.A_continuous_unaugmented = self.A_continuous
119 self.B_continuous_unaugmented = self.B_continuous
120
121 self.A_continuous = numpy.matrix(numpy.zeros((3, 3)))
122 self.A_continuous[0:2, 0:2] = self.A_continuous_unaugmented
123 self.A_continuous[0:2, 2] = self.B_continuous_unaugmented
124
125 self.B_continuous = numpy.matrix(numpy.zeros((3, 1)))
126 self.B_continuous[0:2, 0] = self.B_continuous_unaugmented
127
128 self.C_unaugmented = self.C
129 self.C = numpy.matrix(numpy.zeros((1, 3)))
130 self.C[0:1, 0:2] = self.C_unaugmented
131
132 self.A, self.B = self.ContinuousToDiscrete(self.A_continuous, self.B_continuous, self.dt)
133
134 q_pos = 0.08
135 q_vel = 4.00
Austin Schuh07cb5852016-01-31 00:58:46 -0800136 q_voltage = 3.0
Comran Morshed2ae094e2016-01-23 20:43:20 +0000137 self.Q = numpy.matrix([[(q_pos ** 2.0), 0.0, 0.0],
138 [0.0, (q_vel ** 2.0), 0.0],
139 [0.0, 0.0, (q_voltage ** 2.0)]])
140
141 r_pos = 0.05
142 self.R = numpy.matrix([[(r_pos ** 2.0)]])
143
144 self.KalmanGain, self.Q_steady = controls.kalman(
145 A=self.A, B=self.B, C=self.C, Q=self.Q, R=self.R)
146 self.L = self.A * self.KalmanGain
147
148 self.K_unaugmented = self.K
149 self.K = numpy.matrix(numpy.zeros((1, 3)))
150 self.K[0, 0:2] = self.K_unaugmented
151 self.K[0, 2] = 1
152
153 self.InitializeState()
Austin Schuh07cb5852016-01-31 00:58:46 -0800154
Comran Morshed2ae094e2016-01-23 20:43:20 +0000155class ScenarioPlotter(object):
156 def __init__(self):
157 # Various lists for graphing things.
158 self.t = []
159 self.x = []
160 self.v = []
161 self.a = []
162 self.x_hat = []
163 self.u = []
Austin Schuh07cb5852016-01-31 00:58:46 -0800164 self.offset = []
Comran Morshed2ae094e2016-01-23 20:43:20 +0000165
166 def run_test(self, intake, goal, iterations=200, controller_intake=None,
167 observer_intake=None):
168 """Runs the intake plant with an initial condition and goal.
169
170 Test for whether the goal has been reached and whether the separation
171 goes outside of the initial and goal values by more than
172 max_separation_error.
173
174 Prints out something for a failure of either condition and returns
175 False if tests fail.
176 Args:
177 intake: intake object to use.
178 goal: goal state.
179 iterations: Number of timesteps to run the model for.
180 controller_intake: Intake object to get K from, or None if we should
181 use intake.
182 observer_intake: Intake object to use for the observer, or None if we should
183 use the actual state.
184 """
185
186 if controller_intake is None:
187 controller_intake = intake
188
189 vbat = 12.0
190
191 if self.t:
192 initial_t = self.t[-1] + intake.dt
193 else:
194 initial_t = 0
195
196 for i in xrange(iterations):
197 X_hat = intake.X
198
199 if observer_intake is not None:
200 X_hat = observer_intake.X_hat
201 self.x_hat.append(observer_intake.X_hat[0, 0])
202
203 U = controller_intake.K * (goal - X_hat)
204 U[0, 0] = numpy.clip(U[0, 0], -vbat, vbat)
205 self.x.append(intake.X[0, 0])
206
207 if self.v:
208 last_v = self.v[-1]
209 else:
210 last_v = 0
211
212 self.v.append(intake.X[1, 0])
213 self.a.append((self.v[-1] - last_v) / intake.dt)
214
215 if observer_intake is not None:
216 observer_intake.Y = intake.Y
217 observer_intake.CorrectObserver(U)
Austin Schuh07cb5852016-01-31 00:58:46 -0800218 self.offset.append(observer_intake.X_hat[2, 0])
Comran Morshed2ae094e2016-01-23 20:43:20 +0000219
Austin Schuh07cb5852016-01-31 00:58:46 -0800220 intake.Update(U + 2.0)
Comran Morshed2ae094e2016-01-23 20:43:20 +0000221
222 if observer_intake is not None:
223 observer_intake.PredictObserver(U)
224
225 self.t.append(initial_t + i * intake.dt)
226 self.u.append(U[0, 0])
227
228 glog.debug('Time: %f', self.t[-1])
229
230 def Plot(self):
231 pylab.subplot(3, 1, 1)
232 pylab.plot(self.t, self.x, label='x')
233 pylab.plot(self.t, self.x_hat, label='x_hat')
234 pylab.legend()
235
236 pylab.subplot(3, 1, 2)
237 pylab.plot(self.t, self.u, label='u')
Austin Schuh07cb5852016-01-31 00:58:46 -0800238 pylab.plot(self.t, self.offset, label='voltage_offset')
239 pylab.legend()
Comran Morshed2ae094e2016-01-23 20:43:20 +0000240
241 pylab.subplot(3, 1, 3)
242 pylab.plot(self.t, self.a, label='a')
Comran Morshed2ae094e2016-01-23 20:43:20 +0000243 pylab.legend()
Austin Schuh07cb5852016-01-31 00:58:46 -0800244
Comran Morshed2ae094e2016-01-23 20:43:20 +0000245 pylab.show()
246
247
248def main(argv):
249 argv = FLAGS(argv)
250
Comran Morshed2ae094e2016-01-23 20:43:20 +0000251 scenario_plotter = ScenarioPlotter()
252
Austin Schuh07cb5852016-01-31 00:58:46 -0800253 intake = Intake()
254 intake_controller = IntegralIntake()
255 observer_intake = IntegralIntake()
Comran Morshed2ae094e2016-01-23 20:43:20 +0000256
257 # Test moving the intake with constant separation.
258 initial_X = numpy.matrix([[0.0], [0.0]])
Austin Schuh07cb5852016-01-31 00:58:46 -0800259 R = numpy.matrix([[numpy.pi/2.0], [0.0], [0.0]])
Comran Morshed2ae094e2016-01-23 20:43:20 +0000260 scenario_plotter.run_test(intake, goal=R, controller_intake=intake_controller,
261 observer_intake=observer_intake, iterations=200)
262
263 if FLAGS.plot:
264 scenario_plotter.Plot()
265
266 # Write the generated constants out to a file.
267 if len(argv) != 5:
268 glog.fatal('Expected .h file name and .cc file name for the intake and integral intake.')
269 else:
270 namespaces = ['y2016', 'control_loops', 'superstructure']
271 intake = Intake("Intake")
272 loop_writer = control_loop.ControlLoopWriter('Intake', [intake],
273 namespaces=namespaces)
274 loop_writer.Write(argv[1], argv[2])
275
Austin Schuh07cb5852016-01-31 00:58:46 -0800276 integral_intake = IntegralIntake("IntegralIntake")
Comran Morshed2ae094e2016-01-23 20:43:20 +0000277 integral_loop_writer = control_loop.ControlLoopWriter("IntegralIntake", [integral_intake],
Austin Schuh07cb5852016-01-31 00:58:46 -0800278 namespaces=namespaces)
Comran Morshed2ae094e2016-01-23 20:43:20 +0000279 integral_loop_writer.Write(argv[3], argv[4])
280
281if __name__ == '__main__':
282 sys.exit(main(sys.argv))