blob: 70af1b0d3695250a222cda98e0f3978e381d5075 [file] [log] [blame]
Austin Schuh085eab92020-11-26 13:54:51 -08001#!/usr/bin/python3
Comran Morshed2a97bc82016-01-16 17:27:01 +00002
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
Tyler Chatow6738c362019-02-16 14:12:30 -080016
Austin Schuh09c2b0b2016-02-13 15:53:16 -080017class VelocityShooter(control_loop.ControlLoop):
Comran Morshed2a97bc82016-01-16 17:27:01 +000018
Tyler Chatow6738c362019-02-16 14:12:30 -080019 def __init__(self, name='VelocityShooter'):
20 super(VelocityShooter, self).__init__(name)
21 # Stall Torque in N m
22 self.stall_torque = 0.71
23 # Stall Current in Amps
24 self.stall_current = 134
25 # Free Speed in RPM
26 self.free_speed = 18730.0
27 # Free Current in Amps
28 self.free_current = 0.7
29 # Moment of inertia of the shooter wheel in kg m^2
30 self.J = 0.00032
31 # Resistance of the motor, divided by 2 to account for the 2 motors
32 self.R = 12.0 / self.stall_current
33 # Motor velocity constant
34 self.Kv = ((self.free_speed / 60.0 * 2.0 * numpy.pi) /
35 (12.0 - self.R * self.free_current))
36 # Torque constant
37 self.Kt = self.stall_torque / self.stall_current
38 # Gear ratio
39 self.G = 12.0 / 18.0
40 # Control loop time step
41 self.dt = 0.005
Austin Schuh09c2b0b2016-02-13 15:53:16 -080042
Tyler Chatow6738c362019-02-16 14:12:30 -080043 # State feedback matrices
44 # [angular velocity]
45 self.A_continuous = numpy.matrix(
46 [[-self.Kt / self.Kv / (self.J * self.G * self.G * self.R)]])
47 self.B_continuous = numpy.matrix(
48 [[self.Kt / (self.J * self.G * self.R)]])
49 self.C = numpy.matrix([[1]])
50 self.D = numpy.matrix([[0]])
Austin Schuh09c2b0b2016-02-13 15:53:16 -080051
Tyler Chatow6738c362019-02-16 14:12:30 -080052 self.A, self.B = self.ContinuousToDiscrete(self.A_continuous,
53 self.B_continuous, self.dt)
Austin Schuh09c2b0b2016-02-13 15:53:16 -080054
Tyler Chatow6738c362019-02-16 14:12:30 -080055 self.PlaceControllerPoles([.87])
Austin Schuh09c2b0b2016-02-13 15:53:16 -080056
Tyler Chatow6738c362019-02-16 14:12:30 -080057 self.PlaceObserverPoles([0.3])
Austin Schuh09c2b0b2016-02-13 15:53:16 -080058
Tyler Chatow6738c362019-02-16 14:12:30 -080059 self.U_max = numpy.matrix([[12.0]])
60 self.U_min = numpy.matrix([[-12.0]])
Austin Schuh09c2b0b2016-02-13 15:53:16 -080061
Tyler Chatow6738c362019-02-16 14:12:30 -080062 qff_vel = 8.0
63 self.Qff = numpy.matrix([[1.0 / (qff_vel**2.0)]])
64
65 self.Kff = controls.TwoStateFeedForwards(self.B, self.Qff)
Austin Schuh09c2b0b2016-02-13 15:53:16 -080066
67
68class Shooter(VelocityShooter):
Austin Schuh09c2b0b2016-02-13 15:53:16 -080069
Tyler Chatow6738c362019-02-16 14:12:30 -080070 def __init__(self, name='Shooter'):
71 super(Shooter, self).__init__(name)
Austin Schuh09c2b0b2016-02-13 15:53:16 -080072
Tyler Chatow6738c362019-02-16 14:12:30 -080073 self.A_continuous_unaugmented = self.A_continuous
74 self.B_continuous_unaugmented = self.B_continuous
Austin Schuh09c2b0b2016-02-13 15:53:16 -080075
Tyler Chatow6738c362019-02-16 14:12:30 -080076 self.A_continuous = numpy.matrix(numpy.zeros((2, 2)))
77 self.A_continuous[1:2, 1:2] = self.A_continuous_unaugmented
78 self.A_continuous[0, 1] = 1
Austin Schuh09c2b0b2016-02-13 15:53:16 -080079
Tyler Chatow6738c362019-02-16 14:12:30 -080080 self.B_continuous = numpy.matrix(numpy.zeros((2, 1)))
81 self.B_continuous[1:2, 0] = self.B_continuous_unaugmented
Comran Morshed2a97bc82016-01-16 17:27:01 +000082
Tyler Chatow6738c362019-02-16 14:12:30 -080083 # State feedback matrices
84 # [position, angular velocity]
85 self.C = numpy.matrix([[1, 0]])
86 self.D = numpy.matrix([[0]])
Comran Morshed2a97bc82016-01-16 17:27:01 +000087
Tyler Chatow6738c362019-02-16 14:12:30 -080088 self.A, self.B = self.ContinuousToDiscrete(self.A_continuous,
89 self.B_continuous, self.dt)
Comran Morshed2a97bc82016-01-16 17:27:01 +000090
Tyler Chatow6738c362019-02-16 14:12:30 -080091 self.rpl = .45
92 self.ipl = 0.07
93 self.PlaceObserverPoles(
94 [self.rpl + 1j * self.ipl, self.rpl - 1j * self.ipl])
Austin Schuh09c2b0b2016-02-13 15:53:16 -080095
Tyler Chatow6738c362019-02-16 14:12:30 -080096 self.K_unaugmented = self.K
97 self.K = numpy.matrix(numpy.zeros((1, 2)))
98 self.K[0, 1:2] = self.K_unaugmented
99 self.Kff_unaugmented = self.Kff
100 self.Kff = numpy.matrix(numpy.zeros((1, 2)))
101 self.Kff[0, 1:2] = self.Kff_unaugmented
102
103 self.InitializeState()
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800104
105
106class IntegralShooter(Shooter):
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800107
Tyler Chatow6738c362019-02-16 14:12:30 -0800108 def __init__(self, name="IntegralShooter"):
109 super(IntegralShooter, self).__init__(name=name)
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800110
Tyler Chatow6738c362019-02-16 14:12:30 -0800111 self.A_continuous_unaugmented = self.A_continuous
112 self.B_continuous_unaugmented = self.B_continuous
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800113
Tyler Chatow6738c362019-02-16 14:12:30 -0800114 self.A_continuous = numpy.matrix(numpy.zeros((3, 3)))
115 self.A_continuous[0:2, 0:2] = self.A_continuous_unaugmented
116 self.A_continuous[0:2, 2] = self.B_continuous_unaugmented
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800117
Tyler Chatow6738c362019-02-16 14:12:30 -0800118 self.B_continuous = numpy.matrix(numpy.zeros((3, 1)))
119 self.B_continuous[0:2, 0] = self.B_continuous_unaugmented
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800120
Tyler Chatow6738c362019-02-16 14:12:30 -0800121 self.C_unaugmented = self.C
122 self.C = numpy.matrix(numpy.zeros((1, 3)))
123 self.C[0:1, 0:2] = self.C_unaugmented
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800124
Tyler Chatow6738c362019-02-16 14:12:30 -0800125 self.A, self.B = self.ContinuousToDiscrete(self.A_continuous,
126 self.B_continuous, self.dt)
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800127
Tyler Chatow6738c362019-02-16 14:12:30 -0800128 q_pos = 0.08
129 q_vel = 4.00
130 q_voltage = 0.3
131 self.Q = numpy.matrix([[(q_pos**2.0), 0.0, 0.0],
132 [0.0, (q_vel**2.0), 0.0],
133 [0.0, 0.0, (q_voltage**2.0)]])
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800134
Tyler Chatow6738c362019-02-16 14:12:30 -0800135 r_pos = 0.05
136 self.R = numpy.matrix([[(r_pos**2.0)]])
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800137
Tyler Chatow6738c362019-02-16 14:12:30 -0800138 self.KalmanGain, self.Q_steady = controls.kalman(
139 A=self.A, B=self.B, C=self.C, Q=self.Q, R=self.R)
140 self.L = self.A * self.KalmanGain
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800141
Tyler Chatow6738c362019-02-16 14:12:30 -0800142 self.K_unaugmented = self.K
143 self.K = numpy.matrix(numpy.zeros((1, 3)))
144 self.K[0, 0:2] = self.K_unaugmented
145 self.K[0, 2] = 1
146 self.Kff_unaugmented = self.Kff
147 self.Kff = numpy.matrix(numpy.zeros((1, 3)))
148 self.Kff[0, 0:2] = self.Kff_unaugmented
149
150 self.InitializeState()
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800151
152
153class ScenarioPlotter(object):
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800154
Tyler Chatow6738c362019-02-16 14:12:30 -0800155 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 = []
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800164
Tyler Chatow6738c362019-02-16 14:12:30 -0800165 def run_test(self,
166 shooter,
167 goal,
168 iterations=200,
169 controller_shooter=None,
170 observer_shooter=None):
171 """Runs the shooter plant with an initial condition and goal.
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800172
Tyler Chatow6738c362019-02-16 14:12:30 -0800173 Args:
174 shooter: Shooter object to use.
175 goal: goal state.
176 iterations: Number of timesteps to run the model for.
177 controller_shooter: Shooter object to get K from, or None if we should
178 use shooter.
179 observer_shooter: Shooter object to use for the observer, or None if we
180 should use the actual state.
181 """
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800182
Tyler Chatow6738c362019-02-16 14:12:30 -0800183 if controller_shooter is None:
184 controller_shooter = shooter
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800185
Tyler Chatow6738c362019-02-16 14:12:30 -0800186 vbat = 12.0
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800187
Tyler Chatow6738c362019-02-16 14:12:30 -0800188 if self.t:
189 initial_t = self.t[-1] + shooter.dt
190 else:
191 initial_t = 0
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800192
Austin Schuh5ea48472021-02-02 20:46:41 -0800193 for i in range(iterations):
Tyler Chatow6738c362019-02-16 14:12:30 -0800194 X_hat = shooter.X
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800195
Tyler Chatow6738c362019-02-16 14:12:30 -0800196 if observer_shooter is not None:
197 X_hat = observer_shooter.X_hat
198 self.x_hat.append(observer_shooter.X_hat[1, 0])
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800199
Tyler Chatow6738c362019-02-16 14:12:30 -0800200 ff_U = controller_shooter.Kff * (goal - observer_shooter.A * goal)
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800201
Tyler Chatow6738c362019-02-16 14:12:30 -0800202 U = controller_shooter.K * (goal - X_hat) + ff_U
203 U[0, 0] = numpy.clip(U[0, 0], -vbat, vbat)
204 self.x.append(shooter.X[0, 0])
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800205
Tyler Chatow6738c362019-02-16 14:12:30 -0800206 if self.v:
207 last_v = self.v[-1]
208 else:
209 last_v = 0
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800210
Tyler Chatow6738c362019-02-16 14:12:30 -0800211 self.v.append(shooter.X[1, 0])
212 self.a.append((self.v[-1] - last_v) / shooter.dt)
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800213
Tyler Chatow6738c362019-02-16 14:12:30 -0800214 if observer_shooter is not None:
215 observer_shooter.Y = shooter.Y
216 observer_shooter.CorrectObserver(U)
217 self.offset.append(observer_shooter.X_hat[2, 0])
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800218
Tyler Chatow6738c362019-02-16 14:12:30 -0800219 applied_U = U.copy()
220 if i > 30:
221 applied_U += 2
222 shooter.Update(applied_U)
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800223
Tyler Chatow6738c362019-02-16 14:12:30 -0800224 if observer_shooter is not None:
225 observer_shooter.PredictObserver(U)
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800226
Tyler Chatow6738c362019-02-16 14:12:30 -0800227 self.t.append(initial_t + i * shooter.dt)
228 self.u.append(U[0, 0])
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800229
Tyler Chatow6738c362019-02-16 14:12:30 -0800230 glog.debug('Time: %f', self.t[-1])
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800231
Tyler Chatow6738c362019-02-16 14:12:30 -0800232 def Plot(self):
233 pylab.subplot(3, 1, 1)
234 pylab.plot(self.t, self.v, label='x')
235 pylab.plot(self.t, self.x_hat, label='x_hat')
236 pylab.legend()
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800237
Tyler Chatow6738c362019-02-16 14:12:30 -0800238 pylab.subplot(3, 1, 2)
239 pylab.plot(self.t, self.u, label='u')
240 pylab.plot(self.t, self.offset, label='voltage_offset')
241 pylab.legend()
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800242
Tyler Chatow6738c362019-02-16 14:12:30 -0800243 pylab.subplot(3, 1, 3)
244 pylab.plot(self.t, self.a, label='a')
245 pylab.legend()
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800246
Tyler Chatow6738c362019-02-16 14:12:30 -0800247 pylab.show()
Comran Morshed2a97bc82016-01-16 17:27:01 +0000248
249
250def main(argv):
Tyler Chatow6738c362019-02-16 14:12:30 -0800251 scenario_plotter = ScenarioPlotter()
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800252
Tyler Chatow6738c362019-02-16 14:12:30 -0800253 shooter = Shooter()
254 shooter_controller = IntegralShooter()
255 observer_shooter = IntegralShooter()
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800256
Tyler Chatow6738c362019-02-16 14:12:30 -0800257 initial_X = numpy.matrix([[0.0], [0.0]])
258 R = numpy.matrix([[0.0], [100.0], [0.0]])
259 scenario_plotter.run_test(
260 shooter,
261 goal=R,
262 controller_shooter=shooter_controller,
263 observer_shooter=observer_shooter,
264 iterations=200)
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800265
Tyler Chatow6738c362019-02-16 14:12:30 -0800266 if FLAGS.plot:
267 scenario_plotter.Plot()
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800268
Tyler Chatow6738c362019-02-16 14:12:30 -0800269 if len(argv) != 5:
270 glog.fatal('Expected .h file name and .cc file name')
271 else:
272 namespaces = ['y2016', 'control_loops', 'shooter']
273 shooter = Shooter('Shooter')
274 loop_writer = control_loop.ControlLoopWriter(
275 'Shooter', [shooter], namespaces=namespaces)
276 loop_writer.Write(argv[1], argv[2])
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800277
Tyler Chatow6738c362019-02-16 14:12:30 -0800278 integral_shooter = IntegralShooter('IntegralShooter')
279 integral_loop_writer = control_loop.ControlLoopWriter(
280 'IntegralShooter', [integral_shooter], namespaces=namespaces)
281 integral_loop_writer.Write(argv[3], argv[4])
Comran Morshed2a97bc82016-01-16 17:27:01 +0000282
283
284if __name__ == '__main__':
Tyler Chatow6738c362019-02-16 14:12:30 -0800285 argv = FLAGS(sys.argv)
286 sys.exit(main(argv))