blob: 6c0f2f3623fad68088877d5129d80934b61abd59 [file] [log] [blame]
Austin Schuh48d60c12017-02-04 21:58:58 -08001#!/usr/bin/python
2
3from frc971.control_loops.python import control_loop
4from frc971.control_loops.python import controls
5import numpy
Austin Schuh6c20f202017-02-18 22:31:44 -08006import scipy
Austin Schuh48d60c12017-02-04 21:58:58 -08007import sys
8from matplotlib import pylab
9
10import gflags
11import glog
12
13FLAGS = gflags.FLAGS
14
15gflags.DEFINE_bool('plot', False, 'If true, plot the loop response.')
16
17class VelocityShooter(control_loop.ControlLoop):
18 def __init__(self, name='VelocityShooter'):
19 super(VelocityShooter, self).__init__(name)
20 # Number of motors
21 self.num_motors = 2.0
22 # Stall Torque in N m
23 self.stall_torque = 0.71 * self.num_motors
24 # Stall Current in Amps
25 self.stall_current = 134.0 * self.num_motors
26 # Free Speed in RPM
Brian Silverman052e69d2017-02-12 16:19:55 -080027 self.free_speed_rpm = 18730.0
28 # Free Speed in rotations/second.
29 self.free_speed = self.free_speed_rpm / 60.0
Austin Schuh48d60c12017-02-04 21:58:58 -080030 # Free Current in Amps
31 self.free_current = 0.7 * self.num_motors
32 # Moment of inertia of the shooter wheel in kg m^2
33 # 1400.6 grams/cm^2
34 # 1.407 *1e-4 kg m^2
35 self.J = 0.00080
36 # Resistance of the motor, divided by 2 to account for the 2 motors
37 self.R = 12.0 / self.stall_current
38 # Motor velocity constant
Brian Silverman052e69d2017-02-12 16:19:55 -080039 self.Kv = ((self.free_speed * 2.0 * numpy.pi) /
Austin Schuh48d60c12017-02-04 21:58:58 -080040 (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 = 12.0 / 36.0
45 # Control loop time step
46 self.dt = 0.005
47
48 # State feedback matrices
49 # [angular velocity]
50 self.A_continuous = numpy.matrix(
51 [[-self.Kt / (self.Kv * self.J * self.G * self.G * self.R)]])
52 self.B_continuous = numpy.matrix(
53 [[self.Kt / (self.J * self.G * self.R)]])
54 self.C = numpy.matrix([[1]])
55 self.D = numpy.matrix([[0]])
56
57 self.A, self.B = self.ContinuousToDiscrete(
58 self.A_continuous, self.B_continuous, self.dt)
59
60 self.PlaceControllerPoles([.90])
61
Austin Schuhcd3237a2017-02-18 14:19:26 -080062 glog.debug('K %s', repr(self.K))
63 glog.debug('Poles are %s',
64 repr(numpy.linalg.eig(self.A - self.B * self.K)[0]))
65
Austin Schuh48d60c12017-02-04 21:58:58 -080066 self.PlaceObserverPoles([0.3])
67
68 self.U_max = numpy.matrix([[12.0]])
69 self.U_min = numpy.matrix([[-12.0]])
70
71 qff_vel = 8.0
72 self.Qff = numpy.matrix([[1.0 / (qff_vel ** 2.0)]])
73
74 self.Kff = controls.TwoStateFeedForwards(self.B, self.Qff)
75 self.InitializeState()
76
77
78class Shooter(VelocityShooter):
79 def __init__(self, name='Shooter'):
80 super(Shooter, self).__init__(name)
81
82 self.A_continuous_unaugmented = self.A_continuous
83 self.B_continuous_unaugmented = self.B_continuous
84
85 self.A_continuous = numpy.matrix(numpy.zeros((2, 2)))
86 self.A_continuous[1:2, 1:2] = self.A_continuous_unaugmented
87 self.A_continuous[0, 1] = 1
88
89 self.B_continuous = numpy.matrix(numpy.zeros((2, 1)))
90 self.B_continuous[1:2, 0] = self.B_continuous_unaugmented
91
92 # State feedback matrices
93 # [position, angular velocity]
94 self.C = numpy.matrix([[1, 0]])
95 self.D = numpy.matrix([[0]])
96
97 self.A, self.B = self.ContinuousToDiscrete(
98 self.A_continuous, self.B_continuous, self.dt)
99
100 self.rpl = .45
101 self.ipl = 0.07
102 self.PlaceObserverPoles([self.rpl + 1j * self.ipl,
103 self.rpl - 1j * self.ipl])
104
105 self.K_unaugmented = self.K
106 self.K = numpy.matrix(numpy.zeros((1, 2)))
107 self.K[0, 1:2] = self.K_unaugmented
108 self.Kff_unaugmented = self.Kff
109 self.Kff = numpy.matrix(numpy.zeros((1, 2)))
110 self.Kff[0, 1:2] = self.Kff_unaugmented
111
112 self.InitializeState()
113
114
115class IntegralShooter(Shooter):
116 def __init__(self, name='IntegralShooter'):
117 super(IntegralShooter, self).__init__(name=name)
118
119 self.A_continuous_unaugmented = self.A_continuous
120 self.B_continuous_unaugmented = self.B_continuous
121
122 self.A_continuous = numpy.matrix(numpy.zeros((3, 3)))
123 self.A_continuous[0:2, 0:2] = self.A_continuous_unaugmented
124 self.A_continuous[0:2, 2] = self.B_continuous_unaugmented
125
126 self.B_continuous = numpy.matrix(numpy.zeros((3, 1)))
127 self.B_continuous[0:2, 0] = self.B_continuous_unaugmented
128
129 self.C_unaugmented = self.C
130 self.C = numpy.matrix(numpy.zeros((1, 3)))
131 self.C[0:1, 0:2] = self.C_unaugmented
132
133 self.A, self.B = self.ContinuousToDiscrete(
134 self.A_continuous, self.B_continuous, self.dt)
135
Austin Schuh6c20f202017-02-18 22:31:44 -0800136 glog.debug('A: \n%s', repr(self.A_continuous))
137 glog.debug('eig(A): \n%s', repr(scipy.linalg.eig(self.A_continuous)))
138 glog.debug('schur(A): \n%s', repr(scipy.linalg.schur(self.A_continuous)))
139 glog.debug('A_dt(A): \n%s', repr(self.A))
140
Austin Schuhcd3237a2017-02-18 14:19:26 -0800141 q_pos = 0.01
142 q_vel = 2.0
143 q_voltage = 0.2
Austin Schuh48d60c12017-02-04 21:58:58 -0800144 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.001
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 self.Kff_unaugmented = self.Kff
160 self.Kff = numpy.matrix(numpy.zeros((1, 3)))
161 self.Kff[0, 0:2] = self.Kff_unaugmented
162
163 self.InitializeState()
164
165
166class ScenarioPlotter(object):
167 def __init__(self):
168 # Various lists for graphing things.
169 self.t = []
170 self.x = []
171 self.v = []
172 self.a = []
173 self.x_hat = []
174 self.u = []
175 self.offset = []
176
177 def run_test(self, shooter, goal, iterations=200, controller_shooter=None,
178 observer_shooter=None):
179 """Runs the shooter plant with an initial condition and goal.
180
181 Args:
182 shooter: Shooter object to use.
183 goal: goal state.
184 iterations: Number of timesteps to run the model for.
185 controller_shooter: Shooter object to get K from, or None if we should
186 use shooter.
187 observer_shooter: Shooter object to use for the observer, or None if we
188 should use the actual state.
189 """
190
191 if controller_shooter is None:
192 controller_shooter = shooter
193
194 vbat = 12.0
195
196 if self.t:
197 initial_t = self.t[-1] + shooter.dt
198 else:
199 initial_t = 0
200
201 for i in xrange(iterations):
202 X_hat = shooter.X
203
204 if observer_shooter is not None:
205 X_hat = observer_shooter.X_hat
206 self.x_hat.append(observer_shooter.X_hat[1, 0])
207
208 ff_U = controller_shooter.Kff * (goal - observer_shooter.A * goal)
209
210 U = controller_shooter.K * (goal - X_hat) + ff_U
211 U[0, 0] = numpy.clip(U[0, 0], -vbat, vbat)
212 self.x.append(shooter.X[0, 0])
213
214
215 if self.v:
216 last_v = self.v[-1]
217 else:
218 last_v = 0
219
220 self.v.append(shooter.X[1, 0])
221 self.a.append((self.v[-1] - last_v) / shooter.dt)
222
223 if observer_shooter is not None:
224 observer_shooter.Y = shooter.Y
225 observer_shooter.CorrectObserver(U)
226 self.offset.append(observer_shooter.X_hat[2, 0])
227
228 applied_U = U.copy()
229 if i > 30:
230 applied_U += 2
231 shooter.Update(applied_U)
232
233 if observer_shooter is not None:
234 observer_shooter.PredictObserver(U)
235
236 self.t.append(initial_t + i * shooter.dt)
237 self.u.append(U[0, 0])
238
Austin Schuh48d60c12017-02-04 21:58:58 -0800239 def Plot(self):
240 pylab.subplot(3, 1, 1)
241 pylab.plot(self.t, self.v, label='x')
242 pylab.plot(self.t, self.x_hat, label='x_hat')
243 pylab.legend()
244
245 pylab.subplot(3, 1, 2)
246 pylab.plot(self.t, self.u, label='u')
247 pylab.plot(self.t, self.offset, label='voltage_offset')
248 pylab.legend()
249
250 pylab.subplot(3, 1, 3)
251 pylab.plot(self.t, self.a, label='a')
252 pylab.legend()
253
254 pylab.show()
255
256
257def main(argv):
258 scenario_plotter = ScenarioPlotter()
259
260 shooter = Shooter()
261 shooter_controller = IntegralShooter()
262 observer_shooter = IntegralShooter()
263
264 initial_X = numpy.matrix([[0.0], [0.0]])
265 R = numpy.matrix([[0.0], [100.0], [0.0]])
266 scenario_plotter.run_test(shooter, goal=R, controller_shooter=shooter_controller,
267 observer_shooter=observer_shooter, iterations=200)
268
269 if FLAGS.plot:
270 scenario_plotter.Plot()
271
272 if len(argv) != 5:
273 glog.fatal('Expected .h file name and .cc file name')
274 else:
275 namespaces = ['y2017', 'control_loops', 'superstructure', 'shooter']
276 shooter = Shooter('Shooter')
277 loop_writer = control_loop.ControlLoopWriter('Shooter', [shooter],
278 namespaces=namespaces)
Brian Silverman052e69d2017-02-12 16:19:55 -0800279 loop_writer.AddConstant(control_loop.Constant(
280 'kFreeSpeed', '%f', shooter.free_speed))
281 loop_writer.AddConstant(control_loop.Constant(
282 'kOutputRatio', '%f', shooter.G))
Austin Schuh48d60c12017-02-04 21:58:58 -0800283 loop_writer.Write(argv[1], argv[2])
284
285 integral_shooter = IntegralShooter('IntegralShooter')
286 integral_loop_writer = control_loop.ControlLoopWriter(
287 'IntegralShooter', [integral_shooter], namespaces=namespaces)
288 integral_loop_writer.Write(argv[3], argv[4])
289
290
291if __name__ == '__main__':
292 argv = FLAGS(sys.argv)
293 glog.init()
294 sys.exit(main(argv))