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