blob: cde9c8d714ee4c9fbc70291e52b199512fe09747 [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
Ravago Jones5127ccc2022-07-31 16:32:45 -0700138 self.KalmanGain, self.Q_steady = controls.kalman(A=self.A,
139 B=self.B,
140 C=self.C,
141 Q=self.Q,
142 R=self.R)
Tyler Chatow6738c362019-02-16 14:12:30 -0800143 self.L = self.A * self.KalmanGain
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800144
Tyler Chatow6738c362019-02-16 14:12:30 -0800145 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()
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800154
155
156class ScenarioPlotter(object):
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800157
Tyler Chatow6738c362019-02-16 14:12:30 -0800158 def __init__(self):
159 # Various lists for graphing things.
160 self.t = []
161 self.x = []
162 self.v = []
163 self.a = []
164 self.x_hat = []
165 self.u = []
166 self.offset = []
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800167
Tyler Chatow6738c362019-02-16 14:12:30 -0800168 def run_test(self,
169 shooter,
170 goal,
171 iterations=200,
172 controller_shooter=None,
173 observer_shooter=None):
174 """Runs the shooter plant with an initial condition and goal.
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800175
Tyler Chatow6738c362019-02-16 14:12:30 -0800176 Args:
177 shooter: Shooter object to use.
178 goal: goal state.
179 iterations: Number of timesteps to run the model for.
180 controller_shooter: Shooter object to get K from, or None if we should
181 use shooter.
182 observer_shooter: Shooter object to use for the observer, or None if we
183 should use the actual state.
184 """
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800185
Tyler Chatow6738c362019-02-16 14:12:30 -0800186 if controller_shooter is None:
187 controller_shooter = shooter
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800188
Tyler Chatow6738c362019-02-16 14:12:30 -0800189 vbat = 12.0
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800190
Tyler Chatow6738c362019-02-16 14:12:30 -0800191 if self.t:
192 initial_t = self.t[-1] + shooter.dt
193 else:
194 initial_t = 0
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800195
Austin Schuh5ea48472021-02-02 20:46:41 -0800196 for i in range(iterations):
Tyler Chatow6738c362019-02-16 14:12:30 -0800197 X_hat = shooter.X
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800198
Tyler Chatow6738c362019-02-16 14:12:30 -0800199 if observer_shooter is not None:
200 X_hat = observer_shooter.X_hat
201 self.x_hat.append(observer_shooter.X_hat[1, 0])
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800202
Tyler Chatow6738c362019-02-16 14:12:30 -0800203 ff_U = controller_shooter.Kff * (goal - observer_shooter.A * goal)
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800204
Tyler Chatow6738c362019-02-16 14:12:30 -0800205 U = controller_shooter.K * (goal - X_hat) + ff_U
206 U[0, 0] = numpy.clip(U[0, 0], -vbat, vbat)
207 self.x.append(shooter.X[0, 0])
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800208
Tyler Chatow6738c362019-02-16 14:12:30 -0800209 if self.v:
210 last_v = self.v[-1]
211 else:
212 last_v = 0
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800213
Tyler Chatow6738c362019-02-16 14:12:30 -0800214 self.v.append(shooter.X[1, 0])
215 self.a.append((self.v[-1] - last_v) / shooter.dt)
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800216
Tyler Chatow6738c362019-02-16 14:12:30 -0800217 if observer_shooter is not None:
218 observer_shooter.Y = shooter.Y
219 observer_shooter.CorrectObserver(U)
220 self.offset.append(observer_shooter.X_hat[2, 0])
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800221
Tyler Chatow6738c362019-02-16 14:12:30 -0800222 applied_U = U.copy()
223 if i > 30:
224 applied_U += 2
225 shooter.Update(applied_U)
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800226
Tyler Chatow6738c362019-02-16 14:12:30 -0800227 if observer_shooter is not None:
228 observer_shooter.PredictObserver(U)
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800229
Tyler Chatow6738c362019-02-16 14:12:30 -0800230 self.t.append(initial_t + i * shooter.dt)
231 self.u.append(U[0, 0])
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800232
Tyler Chatow6738c362019-02-16 14:12:30 -0800233 glog.debug('Time: %f', self.t[-1])
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800234
Tyler Chatow6738c362019-02-16 14:12:30 -0800235 def Plot(self):
236 pylab.subplot(3, 1, 1)
237 pylab.plot(self.t, self.v, label='x')
238 pylab.plot(self.t, self.x_hat, label='x_hat')
239 pylab.legend()
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800240
Tyler Chatow6738c362019-02-16 14:12:30 -0800241 pylab.subplot(3, 1, 2)
242 pylab.plot(self.t, self.u, label='u')
243 pylab.plot(self.t, self.offset, label='voltage_offset')
244 pylab.legend()
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800245
Tyler Chatow6738c362019-02-16 14:12:30 -0800246 pylab.subplot(3, 1, 3)
247 pylab.plot(self.t, self.a, label='a')
248 pylab.legend()
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800249
Tyler Chatow6738c362019-02-16 14:12:30 -0800250 pylab.show()
Comran Morshed2a97bc82016-01-16 17:27:01 +0000251
252
253def main(argv):
Tyler Chatow6738c362019-02-16 14:12:30 -0800254 scenario_plotter = ScenarioPlotter()
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800255
Tyler Chatow6738c362019-02-16 14:12:30 -0800256 shooter = Shooter()
257 shooter_controller = IntegralShooter()
258 observer_shooter = IntegralShooter()
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800259
Tyler Chatow6738c362019-02-16 14:12:30 -0800260 initial_X = numpy.matrix([[0.0], [0.0]])
261 R = numpy.matrix([[0.0], [100.0], [0.0]])
Ravago Jones5127ccc2022-07-31 16:32:45 -0700262 scenario_plotter.run_test(shooter,
263 goal=R,
264 controller_shooter=shooter_controller,
265 observer_shooter=observer_shooter,
266 iterations=200)
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800267
Tyler Chatow6738c362019-02-16 14:12:30 -0800268 if FLAGS.plot:
269 scenario_plotter.Plot()
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800270
Tyler Chatow6738c362019-02-16 14:12:30 -0800271 if len(argv) != 5:
272 glog.fatal('Expected .h file name and .cc file name')
273 else:
274 namespaces = ['y2016', 'control_loops', 'shooter']
275 shooter = Shooter('Shooter')
Ravago Jones5127ccc2022-07-31 16:32:45 -0700276 loop_writer = control_loop.ControlLoopWriter('Shooter', [shooter],
277 namespaces=namespaces)
Tyler Chatow6738c362019-02-16 14:12:30 -0800278 loop_writer.Write(argv[1], argv[2])
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800279
Tyler Chatow6738c362019-02-16 14:12:30 -0800280 integral_shooter = IntegralShooter('IntegralShooter')
281 integral_loop_writer = control_loop.ControlLoopWriter(
282 'IntegralShooter', [integral_shooter], namespaces=namespaces)
283 integral_loop_writer.Write(argv[3], argv[4])
Comran Morshed2a97bc82016-01-16 17:27:01 +0000284
285
286if __name__ == '__main__':
Tyler Chatow6738c362019-02-16 14:12:30 -0800287 argv = FLAGS(sys.argv)
288 sys.exit(main(argv))