blob: 3bb22a9d335a4865587fb5a5eb6fcd739ff81774 [file] [log] [blame]
Comran Morshed2a97bc82016-01-16 17:27:01 +00001#!/usr/bin/python
2
3from frc971.control_loops.python import control_loop
Austin Schuh09c2b0b2016-02-13 15:53:16 -08004from frc971.control_loops.python import controls
Comran Morshed2a97bc82016-01-16 17:27:01 +00005import numpy
6import sys
7from matplotlib import pylab
8
Austin Schuh09c2b0b2016-02-13 15:53:16 -08009import 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)
Comran Morshed2a97bc82016-01-16 17:27:01 +000019 # Stall Torque in N m
Austin Schuhd9e55782016-01-31 00:30:05 -080020 self.stall_torque = 0.71
Comran Morshed2a97bc82016-01-16 17:27:01 +000021 # Stall Current in Amps
Austin Schuhd9e55782016-01-31 00:30:05 -080022 self.stall_current = 134
Comran Morshed2a97bc82016-01-16 17:27:01 +000023 # Free Speed in RPM
Austin Schuhd9e55782016-01-31 00:30:05 -080024 self.free_speed = 18730.0
Comran Morshed2a97bc82016-01-16 17:27:01 +000025 # Free Current in Amps
Austin Schuhd9e55782016-01-31 00:30:05 -080026 self.free_current = 0.7
Comran Morshed2a97bc82016-01-16 17:27:01 +000027 # Moment of inertia of the shooter wheel in kg m^2
Austin Schuhd9e55782016-01-31 00:30:05 -080028 self.J = 0.00032
Comran Morshed2a97bc82016-01-16 17:27:01 +000029 # Resistance of the motor, divided by 2 to account for the 2 motors
Austin Schuhd9e55782016-01-31 00:30:05 -080030 self.R = 12.0 / self.stall_current
Comran Morshed2a97bc82016-01-16 17:27:01 +000031 # Motor velocity constant
32 self.Kv = ((self.free_speed / 60.0 * 2.0 * numpy.pi) /
33 (12.0 - self.R * self.free_current))
34 # Torque constant
35 self.Kt = self.stall_torque / self.stall_current
36 # Gear ratio
Austin Schuhd9e55782016-01-31 00:30:05 -080037 self.G = 12.0 / 18.0
Comran Morshed2a97bc82016-01-16 17:27:01 +000038 # Control loop time step
39 self.dt = 0.005
40
41 # State feedback matrices
Austin Schuh09c2b0b2016-02-13 15:53:16 -080042 # [angular velocity]
Comran Morshed2a97bc82016-01-16 17:27:01 +000043 self.A_continuous = numpy.matrix(
Austin Schuh09c2b0b2016-02-13 15:53:16 -080044 [[-self.Kt / self.Kv / (self.J * self.G * self.G * self.R)]])
Comran Morshed2a97bc82016-01-16 17:27:01 +000045 self.B_continuous = numpy.matrix(
Austin Schuh09c2b0b2016-02-13 15:53:16 -080046 [[self.Kt / (self.J * self.G * self.R)]])
47 self.C = numpy.matrix([[1]])
48 self.D = numpy.matrix([[0]])
49
50 self.A, self.B = self.ContinuousToDiscrete(
51 self.A_continuous, self.B_continuous, self.dt)
52
53 self.PlaceControllerPoles([.87])
54
55 self.PlaceObserverPoles([0.3])
56
57 self.U_max = numpy.matrix([[12.0]])
58 self.U_min = numpy.matrix([[-12.0]])
59
60 qff_vel = 8.0
61 self.Qff = numpy.matrix([[1.0 / (qff_vel ** 2.0)]])
62
63 self.Kff = controls.TwoStateFeedForwards(self.B, self.Qff)
64
65
66class Shooter(VelocityShooter):
67 def __init__(self, name='Shooter'):
68 super(Shooter, self).__init__(name)
69
70 self.A_continuous_unaugmented = self.A_continuous
71 self.B_continuous_unaugmented = self.B_continuous
72
73 self.A_continuous = numpy.matrix(numpy.zeros((2, 2)))
74 self.A_continuous[1:2, 1:2] = self.A_continuous_unaugmented
75 self.A_continuous[0, 1] = 1
76
77 self.B_continuous = numpy.matrix(numpy.zeros((2, 1)))
78 self.B_continuous[1:2, 0] = self.B_continuous_unaugmented
79
80 # State feedback matrices
81 # [position, angular velocity]
Comran Morshed2a97bc82016-01-16 17:27:01 +000082 self.C = numpy.matrix([[1, 0]])
83 self.D = numpy.matrix([[0]])
84
85 self.A, self.B = self.ContinuousToDiscrete(
86 self.A_continuous, self.B_continuous, self.dt)
87
Comran Morshed2a97bc82016-01-16 17:27:01 +000088 self.rpl = .45
89 self.ipl = 0.07
90 self.PlaceObserverPoles([self.rpl + 1j * self.ipl,
91 self.rpl - 1j * self.ipl])
92
Austin Schuh09c2b0b2016-02-13 15:53:16 -080093 self.K_unaugmented = self.K
94 self.K = numpy.matrix(numpy.zeros((1, 2)))
95 self.K[0, 1:2] = self.K_unaugmented
96 self.Kff_unaugmented = self.Kff
97 self.Kff = numpy.matrix(numpy.zeros((1, 2)))
98 self.Kff[0, 1:2] = self.Kff_unaugmented
99
100 self.InitializeState()
101
102
103class IntegralShooter(Shooter):
104 def __init__(self, name="IntegralShooter"):
105 super(IntegralShooter, self).__init__(name=name)
106
107 self.A_continuous_unaugmented = self.A_continuous
108 self.B_continuous_unaugmented = self.B_continuous
109
110 self.A_continuous = numpy.matrix(numpy.zeros((3, 3)))
111 self.A_continuous[0:2, 0:2] = self.A_continuous_unaugmented
112 self.A_continuous[0:2, 2] = self.B_continuous_unaugmented
113
114 self.B_continuous = numpy.matrix(numpy.zeros((3, 1)))
115 self.B_continuous[0:2, 0] = self.B_continuous_unaugmented
116
117 self.C_unaugmented = self.C
118 self.C = numpy.matrix(numpy.zeros((1, 3)))
119 self.C[0:1, 0:2] = self.C_unaugmented
120
121 self.A, self.B = self.ContinuousToDiscrete(
122 self.A_continuous, self.B_continuous, self.dt)
123
124 q_pos = 0.08
125 q_vel = 4.00
126 q_voltage = 0.2
127 self.Q = numpy.matrix([[(q_pos ** 2.0), 0.0, 0.0],
128 [0.0, (q_vel ** 2.0), 0.0],
129 [0.0, 0.0, (q_voltage ** 2.0)]])
130
131 r_pos = 0.05
132 self.R = numpy.matrix([[(r_pos ** 2.0)]])
133
134 self.KalmanGain, self.Q_steady = controls.kalman(
135 A=self.A, B=self.B, C=self.C, Q=self.Q, R=self.R)
136 self.L = self.A * self.KalmanGain
137
138 self.K_unaugmented = self.K
139 self.K = numpy.matrix(numpy.zeros((1, 3)))
140 self.K[0, 0:2] = self.K_unaugmented
141 self.K[0, 2] = 1
142 self.Kff_unaugmented = self.Kff
143 self.Kff = numpy.matrix(numpy.zeros((1, 3)))
144 self.Kff[0, 0:2] = self.Kff_unaugmented
145
146 self.InitializeState()
147
148
149class ScenarioPlotter(object):
150 def __init__(self):
151 # Various lists for graphing things.
152 self.t = []
153 self.x = []
154 self.v = []
155 self.a = []
156 self.x_hat = []
157 self.u = []
158 self.offset = []
159
160 def run_test(self, shooter, goal, iterations=200, controller_shooter=None,
161 observer_shooter=None):
162 """Runs the shooter plant with an initial condition and goal.
163
164 Test for whether the goal has been reached and whether the separation
165 goes outside of the initial and goal values by more than
166 max_separation_error.
167
168 Prints out something for a failure of either condition and returns
169 False if tests fail.
170 Args:
171 shooter: shooter object to use.
172 goal: goal state.
173 iterations: Number of timesteps to run the model for.
174 controller_shooter: Wrist object to get K from, or None if we should
175 use shooter.
176 observer_shooter: Wrist object to use for the observer, or None if we should
177 use the actual state.
178 """
179
180 if controller_shooter is None:
181 controller_shooter = shooter
182
183 vbat = 12.0
184
185 if self.t:
186 initial_t = self.t[-1] + shooter.dt
187 else:
188 initial_t = 0
189
190 for i in xrange(iterations):
191 X_hat = shooter.X
192
193 if observer_shooter is not None:
194 X_hat = observer_shooter.X_hat
195 self.x_hat.append(observer_shooter.X_hat[1, 0])
196
197 ff_U = controller_shooter.Kff * (goal - observer_shooter.A * goal)
198
199 U = controller_shooter.K * (goal - X_hat) + ff_U
200 U[0, 0] = numpy.clip(U[0, 0], -vbat, vbat)
201 self.x.append(shooter.X[0, 0])
202
203
204 if self.v:
205 last_v = self.v[-1]
206 else:
207 last_v = 0
208
209 self.v.append(shooter.X[1, 0])
210 self.a.append((self.v[-1] - last_v) / shooter.dt)
211
212 if observer_shooter is not None:
213 observer_shooter.Y = shooter.Y
214 observer_shooter.CorrectObserver(U)
215 self.offset.append(observer_shooter.X_hat[2, 0])
216
217 applied_U = U.copy()
218 if i > 30:
219 applied_U += 2
220 shooter.Update(applied_U)
221
222 if observer_shooter is not None:
223 observer_shooter.PredictObserver(U)
224
225 self.t.append(initial_t + i * shooter.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.v, 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')
238 pylab.plot(self.t, self.offset, label='voltage_offset')
239 pylab.legend()
240
241 pylab.subplot(3, 1, 3)
242 pylab.plot(self.t, self.a, label='a')
243 pylab.legend()
244
245 pylab.show()
Comran Morshed2a97bc82016-01-16 17:27:01 +0000246
247
248def main(argv):
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800249 scenario_plotter = ScenarioPlotter()
250
251 shooter = Shooter()
252 shooter_controller = IntegralShooter()
253 observer_shooter = IntegralShooter()
254
255 # Test moving the shooter with constant separation.
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')
Comran Morshed2a97bc82016-01-16 17:27:01 +0000266 else:
267 namespaces = ['y2016', 'control_loops', 'shooter']
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800268 shooter = Shooter('Shooter')
269 loop_writer = control_loop.ControlLoopWriter('Shooter', [shooter],
Comran Morshed2a97bc82016-01-16 17:27:01 +0000270 namespaces=namespaces)
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800271 loop_writer.Write(argv[1], argv[2])
272
273 integral_shooter = IntegralShooter('IntegralShooter')
274 integral_loop_writer = control_loop.ControlLoopWriter(
275 'IntegralShooter', [integral_shooter], namespaces=namespaces)
276 integral_loop_writer.Write(argv[3], argv[4])
Comran Morshed2a97bc82016-01-16 17:27:01 +0000277
278
279if __name__ == '__main__':
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800280 argv = FLAGS(sys.argv)
281 sys.exit(main(argv))