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