blob: 710233bcb990fc4826f4468b019ab76d38fd2ff3 [file] [log] [blame]
Austin Schuh085eab92020-11-26 13:54:51 -08001#!/usr/bin/python3
Brian Silverman17f503e2015-08-02 18:17:18 -07002
Austin Schuha3b42552015-11-27 16:30:12 -08003import gflags
4import glog
Austin Schuh9d4aca82015-11-08 14:41:31 -08005import argparse
Brian Silverman17f503e2015-08-02 18:17:18 -07006import numpy
7import sys
8from matplotlib import pylab
9
Austin Schuha3b42552015-11-27 16:30:12 -080010from frc971.control_loops.python import control_loop
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
Brian Silverman17f503e2015-08-02 18:17:18 -070017class SprungShooter(control_loop.ControlLoop):
Brian Silverman17f503e2015-08-02 18:17:18 -070018
Tyler Chatow6738c362019-02-16 14:12:30 -080019 def __init__(self, name="RawSprungShooter"):
20 super(SprungShooter, self).__init__(name)
21 # Stall Torque in N m
22 self.stall_torque = .4982
23 # Stall Current in Amps
24 self.stall_current = 85
25 # Free Speed in RPM
26 self.free_speed = 19300.0
27 # Free Current in Amps
28 self.free_current = 1.2
29 # Effective mass of the shooter in kg.
30 # This rough estimate should about include the effect of the masses
31 # of the gears. If this number is too low, the eigen values of self.A
32 # will start to become extremely small.
33 self.J = 200
34 # Resistance of the motor, divided by the number of motors.
35 self.R = 12.0 / self.stall_current / 2.0
36 # Motor velocity constant
37 self.Kv = ((self.free_speed / 60.0 * 2.0 * numpy.pi) /
38 (12.0 - self.R * self.free_current))
39 # Torque constant
40 self.Kt = self.stall_torque / self.stall_current
41 # Spring constant for the springs, N/m
42 self.Ks = 2800.0
43 # Maximum extension distance (Distance from the 0 force point on the
44 # spring to the latch position.)
45 self.max_extension = 0.32385
46 # Gear ratio multiplied by radius of final sprocket.
47 self.G = 10.0 / 40.0 * 20.0 / 54.0 * 24.0 / 54.0 * 20.0 / 84.0 * 16.0 * (
48 3.0 / 8.0) / (2.0 * numpy.pi) * 0.0254
Brian Silverman17f503e2015-08-02 18:17:18 -070049
Tyler Chatow6738c362019-02-16 14:12:30 -080050 # Control loop time step
51 self.dt = 0.005
Brian Silverman17f503e2015-08-02 18:17:18 -070052
Tyler Chatow6738c362019-02-16 14:12:30 -080053 # State feedback matrices
54 self.A_continuous = numpy.matrix(
55 [[0, 1],
56 [
57 -self.Ks / self.J,
58 -self.Kt / self.Kv / (self.J * self.G * self.G * self.R)
59 ]])
60 self.B_continuous = numpy.matrix(
61 [[0], [self.Kt / (self.J * self.G * self.R)]])
62 self.C = numpy.matrix([[1, 0]])
63 self.D = numpy.matrix([[0]])
Brian Silverman17f503e2015-08-02 18:17:18 -070064
Tyler Chatow6738c362019-02-16 14:12:30 -080065 self.A, self.B = self.ContinuousToDiscrete(self.A_continuous,
66 self.B_continuous, self.dt)
Brian Silverman17f503e2015-08-02 18:17:18 -070067
Austin Schuh085eab92020-11-26 13:54:51 -080068 self.PlaceControllerPoles([0.4501, 0.4499])
Brian Silverman17f503e2015-08-02 18:17:18 -070069
Austin Schuh085eab92020-11-26 13:54:51 -080070 self.PlaceObserverPoles([0.05001, 0.04999])
Brian Silverman17f503e2015-08-02 18:17:18 -070071
Tyler Chatow6738c362019-02-16 14:12:30 -080072 self.U_max = numpy.matrix([[12.0]])
73 self.U_min = numpy.matrix([[-12.0]])
74
75 self.InitializeState()
Brian Silverman17f503e2015-08-02 18:17:18 -070076
77
78class Shooter(SprungShooter):
Brian Silverman17f503e2015-08-02 18:17:18 -070079
Tyler Chatow6738c362019-02-16 14:12:30 -080080 def __init__(self, name="RawShooter"):
81 super(Shooter, self).__init__(name)
Brian Silverman17f503e2015-08-02 18:17:18 -070082
Tyler Chatow6738c362019-02-16 14:12:30 -080083 # State feedback matrices
84 self.A_continuous = numpy.matrix(
85 [[0, 1],
86 [0, -self.Kt / self.Kv / (self.J * self.G * self.G * self.R)]])
87 self.B_continuous = numpy.matrix(
88 [[0], [self.Kt / (self.J * self.G * self.R)]])
Brian Silverman17f503e2015-08-02 18:17:18 -070089
Tyler Chatow6738c362019-02-16 14:12:30 -080090 self.A, self.B = self.ContinuousToDiscrete(self.A_continuous,
91 self.B_continuous, self.dt)
Brian Silverman17f503e2015-08-02 18:17:18 -070092
Austin Schuh085eab92020-11-26 13:54:51 -080093 self.PlaceControllerPoles([0.45001, 0.44999])
Brian Silverman17f503e2015-08-02 18:17:18 -070094
Austin Schuh085eab92020-11-26 13:54:51 -080095 self.PlaceObserverPoles([0.05001, 0.04999])
Brian Silverman17f503e2015-08-02 18:17:18 -070096
Tyler Chatow6738c362019-02-16 14:12:30 -080097 self.U_max = numpy.matrix([[12.0]])
98 self.U_min = numpy.matrix([[-12.0]])
99
100 self.InitializeState()
Brian Silverman17f503e2015-08-02 18:17:18 -0700101
102
103class SprungShooterDeltaU(SprungShooter):
Brian Silverman17f503e2015-08-02 18:17:18 -0700104
Tyler Chatow6738c362019-02-16 14:12:30 -0800105 def __init__(self, name="SprungShooter"):
106 super(SprungShooterDeltaU, self).__init__(name)
107 A_unaugmented = self.A
108 B_unaugmented = self.B
Austin Schuh6c20f202017-02-18 22:31:44 -0800109
Tyler Chatow6738c362019-02-16 14:12:30 -0800110 A_continuous_unaugmented = self.A_continuous
111 B_continuous_unaugmented = self.B_continuous
Austin Schuh6c20f202017-02-18 22:31:44 -0800112
Tyler Chatow6738c362019-02-16 14:12:30 -0800113 self.A_continuous = numpy.matrix(numpy.zeros((3, 3)))
114 self.A_continuous[0:2, 0:2] = A_continuous_unaugmented
115 self.A_continuous[0:2, 2] = B_continuous_unaugmented
Austin Schuh6c20f202017-02-18 22:31:44 -0800116
Tyler Chatow6738c362019-02-16 14:12:30 -0800117 self.B_continuous = numpy.matrix(numpy.zeros((3, 1)))
118 self.B_continuous[2, 0] = 1.0 / self.dt
Brian Silverman17f503e2015-08-02 18:17:18 -0700119
Tyler Chatow6738c362019-02-16 14:12:30 -0800120 self.A = numpy.matrix([[0.0, 0.0, 0.0], [0.0, 0.0, 0.0],
121 [0.0, 0.0, 1.0]])
122 self.A[0:2, 0:2] = A_unaugmented
123 self.A[0:2, 2] = B_unaugmented
Brian Silverman17f503e2015-08-02 18:17:18 -0700124
Tyler Chatow6738c362019-02-16 14:12:30 -0800125 self.B = numpy.matrix([[0.0], [0.0], [1.0]])
Brian Silverman17f503e2015-08-02 18:17:18 -0700126
Tyler Chatow6738c362019-02-16 14:12:30 -0800127 self.C = numpy.matrix([[1.0, 0.0, 0.0]])
128 self.D = numpy.matrix([[0.0]])
Brian Silverman17f503e2015-08-02 18:17:18 -0700129
Tyler Chatow6738c362019-02-16 14:12:30 -0800130 self.PlaceControllerPoles([0.50, 0.35, 0.80])
Brian Silverman17f503e2015-08-02 18:17:18 -0700131
Tyler Chatow6738c362019-02-16 14:12:30 -0800132 glog.debug('K')
133 glog.debug(str(self.K))
134 glog.debug('Placed controller poles are')
135 glog.debug(str(numpy.linalg.eig(self.A - self.B * self.K)[0]))
Brian Silverman17f503e2015-08-02 18:17:18 -0700136
Tyler Chatow6738c362019-02-16 14:12:30 -0800137 self.rpl = .05
138 self.ipl = 0.008
139 self.PlaceObserverPoles(
140 [self.rpl + 1j * self.ipl, self.rpl - 1j * self.ipl, 0.90])
141 glog.debug('Placed observer poles are')
142 glog.debug(str(numpy.linalg.eig(self.A - self.L * self.C)[0]))
Brian Silverman17f503e2015-08-02 18:17:18 -0700143
Tyler Chatow6738c362019-02-16 14:12:30 -0800144 self.U_max = numpy.matrix([[12.0]])
145 self.U_min = numpy.matrix([[-12.0]])
146
147 self.InitializeState()
Brian Silverman17f503e2015-08-02 18:17:18 -0700148
149
150class ShooterDeltaU(Shooter):
Brian Silverman17f503e2015-08-02 18:17:18 -0700151
Tyler Chatow6738c362019-02-16 14:12:30 -0800152 def __init__(self, name="Shooter"):
153 super(ShooterDeltaU, self).__init__(name)
154 A_unaugmented = self.A
155 B_unaugmented = self.B
Austin Schuh6c20f202017-02-18 22:31:44 -0800156
Tyler Chatow6738c362019-02-16 14:12:30 -0800157 A_continuous_unaugmented = self.A_continuous
158 B_continuous_unaugmented = self.B_continuous
Austin Schuh6c20f202017-02-18 22:31:44 -0800159
Tyler Chatow6738c362019-02-16 14:12:30 -0800160 self.A_continuous = numpy.matrix(numpy.zeros((3, 3)))
161 self.A_continuous[0:2, 0:2] = A_continuous_unaugmented
162 self.A_continuous[0:2, 2] = B_continuous_unaugmented
Austin Schuh6c20f202017-02-18 22:31:44 -0800163
Tyler Chatow6738c362019-02-16 14:12:30 -0800164 self.B_continuous = numpy.matrix(numpy.zeros((3, 1)))
165 self.B_continuous[2, 0] = 1.0 / self.dt
Brian Silverman17f503e2015-08-02 18:17:18 -0700166
Tyler Chatow6738c362019-02-16 14:12:30 -0800167 self.A = numpy.matrix([[0.0, 0.0, 0.0], [0.0, 0.0, 0.0],
168 [0.0, 0.0, 1.0]])
169 self.A[0:2, 0:2] = A_unaugmented
170 self.A[0:2, 2] = B_unaugmented
Brian Silverman17f503e2015-08-02 18:17:18 -0700171
Tyler Chatow6738c362019-02-16 14:12:30 -0800172 self.B = numpy.matrix([[0.0], [0.0], [1.0]])
Brian Silverman17f503e2015-08-02 18:17:18 -0700173
Tyler Chatow6738c362019-02-16 14:12:30 -0800174 self.C = numpy.matrix([[1.0, 0.0, 0.0]])
175 self.D = numpy.matrix([[0.0]])
Brian Silverman17f503e2015-08-02 18:17:18 -0700176
Tyler Chatow6738c362019-02-16 14:12:30 -0800177 self.PlaceControllerPoles([0.55, 0.45, 0.80])
Brian Silverman17f503e2015-08-02 18:17:18 -0700178
Tyler Chatow6738c362019-02-16 14:12:30 -0800179 glog.debug('K')
180 glog.debug(str(self.K))
181 glog.debug('Placed controller poles are')
182 glog.debug(str(numpy.linalg.eig(self.A - self.B * self.K)[0]))
Brian Silverman17f503e2015-08-02 18:17:18 -0700183
Tyler Chatow6738c362019-02-16 14:12:30 -0800184 self.rpl = .05
185 self.ipl = 0.008
186 self.PlaceObserverPoles(
187 [self.rpl + 1j * self.ipl, self.rpl - 1j * self.ipl, 0.90])
188 glog.debug('Placed observer poles are')
189 glog.debug(str(numpy.linalg.eig(self.A - self.L * self.C)[0]))
Brian Silverman17f503e2015-08-02 18:17:18 -0700190
Tyler Chatow6738c362019-02-16 14:12:30 -0800191 self.U_max = numpy.matrix([[12.0]])
192 self.U_min = numpy.matrix([[-12.0]])
193
194 self.InitializeState()
Brian Silverman17f503e2015-08-02 18:17:18 -0700195
196
197def ClipDeltaU(shooter, old_voltage, delta_u):
Tyler Chatow6738c362019-02-16 14:12:30 -0800198 old_u = old_voltage
199 new_u = numpy.clip(old_u + delta_u, shooter.U_min, shooter.U_max)
200 return new_u - old_u
201
Brian Silverman17f503e2015-08-02 18:17:18 -0700202
203def main(argv):
Tyler Chatow6738c362019-02-16 14:12:30 -0800204 argv = FLAGS(argv)
Austin Schuh9d4aca82015-11-08 14:41:31 -0800205
Tyler Chatow6738c362019-02-16 14:12:30 -0800206 # Simulate the response of the system to a goal.
207 sprung_shooter = SprungShooterDeltaU()
208 raw_sprung_shooter = SprungShooter()
209 close_loop_x = []
210 close_loop_u = []
211 goal_position = -0.3
212 R = numpy.matrix(
213 [[goal_position], [0.0],
214 [-sprung_shooter.A[1, 0] / sprung_shooter.A[1, 2] * goal_position]])
215 voltage = numpy.matrix([[0.0]])
Austin Schuh5ea48472021-02-02 20:46:41 -0800216 for _ in range(500):
Tyler Chatow6738c362019-02-16 14:12:30 -0800217 U = sprung_shooter.K * (R - sprung_shooter.X_hat)
218 U = ClipDeltaU(sprung_shooter, voltage, U)
219 sprung_shooter.Y = raw_sprung_shooter.Y + 0.01
220 sprung_shooter.UpdateObserver(U)
221 voltage += U
222 raw_sprung_shooter.Update(voltage)
223 close_loop_x.append(raw_sprung_shooter.X[0, 0] * 10)
224 close_loop_u.append(voltage[0, 0])
Brian Silverman17f503e2015-08-02 18:17:18 -0700225
Tyler Chatow6738c362019-02-16 14:12:30 -0800226 if FLAGS.plot:
227 pylab.plot(range(500), close_loop_x)
228 pylab.plot(range(500), close_loop_u)
229 pylab.show()
Brian Silverman17f503e2015-08-02 18:17:18 -0700230
Tyler Chatow6738c362019-02-16 14:12:30 -0800231 shooter = ShooterDeltaU()
232 raw_shooter = Shooter()
233 close_loop_x = []
234 close_loop_u = []
235 goal_position = -0.3
236 R = numpy.matrix([[goal_position], [0.0],
237 [-shooter.A[1, 0] / shooter.A[1, 2] * goal_position]])
238 voltage = numpy.matrix([[0.0]])
Austin Schuh5ea48472021-02-02 20:46:41 -0800239 for _ in range(500):
Tyler Chatow6738c362019-02-16 14:12:30 -0800240 U = shooter.K * (R - shooter.X_hat)
241 U = ClipDeltaU(shooter, voltage, U)
242 shooter.Y = raw_shooter.Y + 0.01
243 shooter.UpdateObserver(U)
244 voltage += U
245 raw_shooter.Update(voltage)
246 close_loop_x.append(raw_shooter.X[0, 0] * 10)
247 close_loop_u.append(voltage[0, 0])
Brian Silverman17f503e2015-08-02 18:17:18 -0700248
Tyler Chatow6738c362019-02-16 14:12:30 -0800249 if FLAGS.plot:
250 pylab.plot(range(500), close_loop_x)
251 pylab.plot(range(500), close_loop_u)
252 pylab.show()
Brian Silverman17f503e2015-08-02 18:17:18 -0700253
Tyler Chatow6738c362019-02-16 14:12:30 -0800254 # Write the generated constants out to a file.
255 unaug_sprung_shooter = SprungShooter("RawSprungShooter")
256 unaug_shooter = Shooter("RawShooter")
257 namespaces = ['y2014', 'control_loops', 'shooter']
258 unaug_loop_writer = control_loop.ControlLoopWriter(
259 "RawShooter", [unaug_sprung_shooter, unaug_shooter],
260 namespaces=namespaces)
261 unaug_loop_writer.Write(argv[4], argv[3])
Brian Silverman17f503e2015-08-02 18:17:18 -0700262
Tyler Chatow6738c362019-02-16 14:12:30 -0800263 sprung_shooter = SprungShooterDeltaU()
264 shooter = ShooterDeltaU()
265 loop_writer = control_loop.ControlLoopWriter(
266 "Shooter", [sprung_shooter, shooter], namespaces=namespaces)
Brian Silverman17f503e2015-08-02 18:17:18 -0700267
Tyler Chatow6738c362019-02-16 14:12:30 -0800268 loop_writer.AddConstant(
269 control_loop.Constant("kMaxExtension", "%f",
270 sprung_shooter.max_extension))
271 loop_writer.AddConstant(
272 control_loop.Constant("kSpringConstant", "%f", sprung_shooter.Ks))
273 loop_writer.AddConstant(
274 control_loop.Constant("kDt", "%f", sprung_shooter.dt))
275 loop_writer.Write(argv[2], argv[1])
276
Brian Silverman17f503e2015-08-02 18:17:18 -0700277
278if __name__ == '__main__':
Tyler Chatow6738c362019-02-16 14:12:30 -0800279 sys.exit(main(sys.argv))