blob: 161037ae8989b40b44bf5c1d688b0e814e755ff0 [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
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800164 Args:
Austin Schuh62a4d382016-02-13 20:29:35 -0800165 shooter: Shooter object to use.
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800166 goal: goal state.
167 iterations: Number of timesteps to run the model for.
Austin Schuh62a4d382016-02-13 20:29:35 -0800168 controller_shooter: Shooter object to get K from, or None if we should
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800169 use shooter.
Austin Schuh62a4d382016-02-13 20:29:35 -0800170 observer_shooter: Shooter object to use for the observer, or None if we
171 should use the actual state.
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800172 """
173
174 if controller_shooter is None:
175 controller_shooter = shooter
176
177 vbat = 12.0
178
179 if self.t:
180 initial_t = self.t[-1] + shooter.dt
181 else:
182 initial_t = 0
183
184 for i in xrange(iterations):
185 X_hat = shooter.X
186
187 if observer_shooter is not None:
188 X_hat = observer_shooter.X_hat
189 self.x_hat.append(observer_shooter.X_hat[1, 0])
190
191 ff_U = controller_shooter.Kff * (goal - observer_shooter.A * goal)
192
193 U = controller_shooter.K * (goal - X_hat) + ff_U
194 U[0, 0] = numpy.clip(U[0, 0], -vbat, vbat)
195 self.x.append(shooter.X[0, 0])
196
197
198 if self.v:
199 last_v = self.v[-1]
200 else:
201 last_v = 0
202
203 self.v.append(shooter.X[1, 0])
204 self.a.append((self.v[-1] - last_v) / shooter.dt)
205
206 if observer_shooter is not None:
207 observer_shooter.Y = shooter.Y
208 observer_shooter.CorrectObserver(U)
209 self.offset.append(observer_shooter.X_hat[2, 0])
210
211 applied_U = U.copy()
212 if i > 30:
213 applied_U += 2
214 shooter.Update(applied_U)
215
216 if observer_shooter is not None:
217 observer_shooter.PredictObserver(U)
218
219 self.t.append(initial_t + i * shooter.dt)
220 self.u.append(U[0, 0])
221
222 glog.debug('Time: %f', self.t[-1])
223
224 def Plot(self):
225 pylab.subplot(3, 1, 1)
226 pylab.plot(self.t, self.v, label='x')
227 pylab.plot(self.t, self.x_hat, label='x_hat')
228 pylab.legend()
229
230 pylab.subplot(3, 1, 2)
231 pylab.plot(self.t, self.u, label='u')
232 pylab.plot(self.t, self.offset, label='voltage_offset')
233 pylab.legend()
234
235 pylab.subplot(3, 1, 3)
236 pylab.plot(self.t, self.a, label='a')
237 pylab.legend()
238
239 pylab.show()
Comran Morshed2a97bc82016-01-16 17:27:01 +0000240
241
242def main(argv):
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800243 scenario_plotter = ScenarioPlotter()
244
245 shooter = Shooter()
246 shooter_controller = IntegralShooter()
247 observer_shooter = IntegralShooter()
248
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800249 initial_X = numpy.matrix([[0.0], [0.0]])
250 R = numpy.matrix([[0.0], [100.0], [0.0]])
251 scenario_plotter.run_test(shooter, goal=R, controller_shooter=shooter_controller,
252 observer_shooter=observer_shooter, iterations=200)
253
254 if FLAGS.plot:
255 scenario_plotter.Plot()
256
257 if len(argv) != 5:
258 glog.fatal('Expected .h file name and .cc file name')
Comran Morshed2a97bc82016-01-16 17:27:01 +0000259 else:
260 namespaces = ['y2016', 'control_loops', 'shooter']
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800261 shooter = Shooter('Shooter')
262 loop_writer = control_loop.ControlLoopWriter('Shooter', [shooter],
Comran Morshed2a97bc82016-01-16 17:27:01 +0000263 namespaces=namespaces)
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800264 loop_writer.Write(argv[1], argv[2])
265
266 integral_shooter = IntegralShooter('IntegralShooter')
267 integral_loop_writer = control_loop.ControlLoopWriter(
268 'IntegralShooter', [integral_shooter], namespaces=namespaces)
269 integral_loop_writer.Write(argv[3], argv[4])
Comran Morshed2a97bc82016-01-16 17:27:01 +0000270
271
272if __name__ == '__main__':
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800273 argv = FLAGS(sys.argv)
274 sys.exit(main(argv))