blob: 25e4b2454c73a578abe46c65a81bce93f0316c21 [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
61 self.PlaceObserverPoles([0.3])
62
63 self.U_max = numpy.matrix([[12.0]])
64 self.U_min = numpy.matrix([[-12.0]])
65
66 qff_vel = 8.0
67 self.Qff = numpy.matrix([[1.0 / (qff_vel ** 2.0)]])
68
69 self.Kff = controls.TwoStateFeedForwards(self.B, self.Qff)
70 self.InitializeState()
71
72
73class Shooter(VelocityShooter):
74 def __init__(self, name='Shooter'):
75 super(Shooter, self).__init__(name)
76
77 self.A_continuous_unaugmented = self.A_continuous
78 self.B_continuous_unaugmented = self.B_continuous
79
80 self.A_continuous = numpy.matrix(numpy.zeros((2, 2)))
81 self.A_continuous[1:2, 1:2] = self.A_continuous_unaugmented
82 self.A_continuous[0, 1] = 1
83
84 self.B_continuous = numpy.matrix(numpy.zeros((2, 1)))
85 self.B_continuous[1:2, 0] = self.B_continuous_unaugmented
86
87 # State feedback matrices
88 # [position, angular velocity]
89 self.C = numpy.matrix([[1, 0]])
90 self.D = numpy.matrix([[0]])
91
92 self.A, self.B = self.ContinuousToDiscrete(
93 self.A_continuous, self.B_continuous, self.dt)
94
95 self.rpl = .45
96 self.ipl = 0.07
97 self.PlaceObserverPoles([self.rpl + 1j * self.ipl,
98 self.rpl - 1j * self.ipl])
99
100 self.K_unaugmented = self.K
101 self.K = numpy.matrix(numpy.zeros((1, 2)))
102 self.K[0, 1:2] = self.K_unaugmented
103 self.Kff_unaugmented = self.Kff
104 self.Kff = numpy.matrix(numpy.zeros((1, 2)))
105 self.Kff[0, 1:2] = self.Kff_unaugmented
106
107 self.InitializeState()
108
109
110class IntegralShooter(Shooter):
111 def __init__(self, name='IntegralShooter'):
112 super(IntegralShooter, self).__init__(name=name)
113
114 self.A_continuous_unaugmented = self.A_continuous
115 self.B_continuous_unaugmented = self.B_continuous
116
117 self.A_continuous = numpy.matrix(numpy.zeros((3, 3)))
118 self.A_continuous[0:2, 0:2] = self.A_continuous_unaugmented
119 self.A_continuous[0:2, 2] = self.B_continuous_unaugmented
120
121 self.B_continuous = numpy.matrix(numpy.zeros((3, 1)))
122 self.B_continuous[0:2, 0] = self.B_continuous_unaugmented
123
124 self.C_unaugmented = self.C
125 self.C = numpy.matrix(numpy.zeros((1, 3)))
126 self.C[0:1, 0:2] = self.C_unaugmented
127
128 self.A, self.B = self.ContinuousToDiscrete(
129 self.A_continuous, self.B_continuous, self.dt)
130
131 q_pos = 2.0
132 q_vel = 0.001
133 q_voltage = 10.0
134 self.Q = numpy.matrix([[(q_pos ** 2.0), 0.0, 0.0],
135 [0.0, (q_vel ** 2.0), 0.0],
136 [0.0, 0.0, (q_voltage ** 2.0)]])
137
138 r_pos = 0.001
139 self.R = numpy.matrix([[(r_pos ** 2.0)]])
140
141 self.KalmanGain, self.Q_steady = controls.kalman(
142 A=self.A, B=self.B, C=self.C, Q=self.Q, R=self.R)
143 self.L = self.A * self.KalmanGain
144
145 self.K_unaugmented = self.K
146 self.K = numpy.matrix(numpy.zeros((1, 3)))
147 self.K[0, 0:2] = self.K_unaugmented
148 self.K[0, 2] = 1
149 self.Kff_unaugmented = self.Kff
150 self.Kff = numpy.matrix(numpy.zeros((1, 3)))
151 self.Kff[0, 0:2] = self.Kff_unaugmented
152
153 self.InitializeState()
154
155
156class ScenarioPlotter(object):
157 def __init__(self):
158 # Various lists for graphing things.
159 self.t = []
160 self.x = []
161 self.v = []
162 self.a = []
163 self.x_hat = []
164 self.u = []
165 self.offset = []
166
167 def run_test(self, shooter, goal, iterations=200, controller_shooter=None,
168 observer_shooter=None):
169 """Runs the shooter plant with an initial condition and goal.
170
171 Args:
172 shooter: Shooter object to use.
173 goal: goal state.
174 iterations: Number of timesteps to run the model for.
175 controller_shooter: Shooter object to get K from, or None if we should
176 use shooter.
177 observer_shooter: Shooter object to use for the observer, or None if we
178 should use the actual state.
179 """
180
181 if controller_shooter is None:
182 controller_shooter = shooter
183
184 vbat = 12.0
185
186 if self.t:
187 initial_t = self.t[-1] + shooter.dt
188 else:
189 initial_t = 0
190
191 for i in xrange(iterations):
192 X_hat = shooter.X
193
194 if observer_shooter is not None:
195 X_hat = observer_shooter.X_hat
196 self.x_hat.append(observer_shooter.X_hat[1, 0])
197
198 ff_U = controller_shooter.Kff * (goal - observer_shooter.A * goal)
199
200 U = controller_shooter.K * (goal - X_hat) + ff_U
201 U[0, 0] = numpy.clip(U[0, 0], -vbat, vbat)
202 self.x.append(shooter.X[0, 0])
203
204
205 if self.v:
206 last_v = self.v[-1]
207 else:
208 last_v = 0
209
210 self.v.append(shooter.X[1, 0])
211 self.a.append((self.v[-1] - last_v) / shooter.dt)
212
213 if observer_shooter is not None:
214 observer_shooter.Y = shooter.Y
215 observer_shooter.CorrectObserver(U)
216 self.offset.append(observer_shooter.X_hat[2, 0])
217
218 applied_U = U.copy()
219 if i > 30:
220 applied_U += 2
221 shooter.Update(applied_U)
222
223 if observer_shooter is not None:
224 observer_shooter.PredictObserver(U)
225
226 self.t.append(initial_t + i * shooter.dt)
227 self.u.append(U[0, 0])
228
229 glog.debug('Time: %f', self.t[-1])
230
231 def Plot(self):
232 pylab.subplot(3, 1, 1)
233 pylab.plot(self.t, self.v, label='x')
234 pylab.plot(self.t, self.x_hat, label='x_hat')
235 pylab.legend()
236
237 pylab.subplot(3, 1, 2)
238 pylab.plot(self.t, self.u, label='u')
239 pylab.plot(self.t, self.offset, label='voltage_offset')
240 pylab.legend()
241
242 pylab.subplot(3, 1, 3)
243 pylab.plot(self.t, self.a, label='a')
244 pylab.legend()
245
246 pylab.show()
247
248
249def main(argv):
250 scenario_plotter = ScenarioPlotter()
251
252 shooter = Shooter()
253 shooter_controller = IntegralShooter()
254 observer_shooter = IntegralShooter()
255
256 initial_X = numpy.matrix([[0.0], [0.0]])
257 R = numpy.matrix([[0.0], [100.0], [0.0]])
258 scenario_plotter.run_test(shooter, goal=R, controller_shooter=shooter_controller,
259 observer_shooter=observer_shooter, iterations=200)
260
261 if FLAGS.plot:
262 scenario_plotter.Plot()
263
264 if len(argv) != 5:
265 glog.fatal('Expected .h file name and .cc file name')
266 else:
267 namespaces = ['y2017', 'control_loops', 'superstructure', 'shooter']
268 shooter = Shooter('Shooter')
269 loop_writer = control_loop.ControlLoopWriter('Shooter', [shooter],
270 namespaces=namespaces)
Brian Silverman052e69d2017-02-12 16:19:55 -0800271 loop_writer.AddConstant(control_loop.Constant(
272 'kFreeSpeed', '%f', shooter.free_speed))
273 loop_writer.AddConstant(control_loop.Constant(
274 'kOutputRatio', '%f', shooter.G))
Austin Schuh48d60c12017-02-04 21:58:58 -0800275 loop_writer.Write(argv[1], argv[2])
276
277 integral_shooter = IntegralShooter('IntegralShooter')
278 integral_loop_writer = control_loop.ControlLoopWriter(
279 'IntegralShooter', [integral_shooter], namespaces=namespaces)
280 integral_loop_writer.Write(argv[3], argv[4])
281
282
283if __name__ == '__main__':
284 argv = FLAGS(sys.argv)
285 glog.init()
286 sys.exit(main(argv))