blob: 1280b3613b902ce5c88a16fa3984e793ede5fd55 [file] [log] [blame]
Sabina Davisedf89472020-02-17 15:27:37 -08001from frc971.control_loops.python import control_loop
2from frc971.control_loops.python import controls
3import numpy
Maxwell Henderson34242992024-01-07 12:39:11 -08004
5import matplotlib
6import matplotlib.pyplot as plt
Sabina Davisedf89472020-02-17 15:27:37 -08007
8import glog
9
10
11class FlywheelParams(object):
Ravago Jones5127ccc2022-07-31 16:32:45 -070012
Sabina Davisedf89472020-02-17 15:27:37 -080013 def __init__(self,
14 name,
15 motor,
16 G,
17 J,
18 q_pos,
19 q_vel,
20 q_voltage,
21 r_pos,
22 controller_poles,
23 dt=0.00505):
24 self.name = name
25 self.motor = motor
26 self.G = G
27 self.J = J
28 self.q_pos = q_pos
29 self.q_vel = q_vel
30 self.q_voltage = q_voltage
31 self.r_pos = r_pos
32 self.dt = dt
33 self.controller_poles = controller_poles
34
35
Austin Schuh43b9ae92020-02-29 23:08:38 -080036class VelocityFlywheel(control_loop.HybridControlLoop):
Ravago Jones5127ccc2022-07-31 16:32:45 -070037
Sabina Davisedf89472020-02-17 15:27:37 -080038 def __init__(self, params, name="Flywheel"):
39 super(VelocityFlywheel, self).__init__(name=name)
40 self.params = params
41 # Set Motor
42 self.motor = self.params.motor
43 # Moment of inertia of the flywheel wheel in kg m^2
44 self.J = self.params.J
45 # Gear ratio
46 self.G = self.params.G
47 # Control loop time step
48 self.dt = self.params.dt
49
50 # State feedback matrices
51 # [angular velocity]
52 self.A_continuous = numpy.matrix([[
53 -self.motor.Kt / self.motor.Kv /
54 (self.J * self.G * self.G * self.motor.resistance)
55 ]])
56 self.B_continuous = numpy.matrix(
57 [[self.motor.Kt / (self.J * self.G * self.motor.resistance)]])
58 self.C = numpy.matrix([[1]])
59 self.D = numpy.matrix([[0]])
60
61 self.A, self.B = self.ContinuousToDiscrete(self.A_continuous,
62 self.B_continuous, self.dt)
63
64 self.PlaceControllerPoles(self.params.controller_poles)
65
66 # Generated controller not used.
67 self.PlaceObserverPoles([0.3])
68
69 self.U_max = numpy.matrix([[12.0]])
70 self.U_min = numpy.matrix([[-12.0]])
71
72 qff_vel = 8.0
73 self.Qff = numpy.matrix([[1.0 / (qff_vel**2.0)]])
74
75 self.Kff = controls.TwoStateFeedForwards(self.B, self.Qff)
76
Austin Schuheb240f62021-11-07 19:57:06 -080077 glog.debug('K: %s', str(self.K))
78 glog.debug('Poles: %s',
79 str(numpy.linalg.eig(self.A - self.B * self.K)[0]))
80
Sabina Davisedf89472020-02-17 15:27:37 -080081
82class Flywheel(VelocityFlywheel):
Ravago Jones5127ccc2022-07-31 16:32:45 -070083
Sabina Davisedf89472020-02-17 15:27:37 -080084 def __init__(self, params, name="Flywheel"):
85 super(Flywheel, self).__init__(params, name=name)
86
87 self.A_continuous_unaugmented = self.A_continuous
88 self.B_continuous_unaugmented = self.B_continuous
89
90 self.A_continuous = numpy.matrix(numpy.zeros((2, 2)))
91 self.A_continuous[1:2, 1:2] = self.A_continuous_unaugmented
92 self.A_continuous[0, 1] = 1
93
94 self.B_continuous = numpy.matrix(numpy.zeros((2, 1)))
95 self.B_continuous[1:2, 0] = self.B_continuous_unaugmented
96
97 # State feedback matrices
98 # [position, angular velocity]
99 self.C = numpy.matrix([[1, 0]])
100 self.D = numpy.matrix([[0]])
101
102 self.A, self.B = self.ContinuousToDiscrete(self.A_continuous,
103 self.B_continuous, self.dt)
104
105 rpl = 0.45
106 ipl = 0.07
107 self.PlaceObserverPoles([rpl + 1j * ipl, rpl - 1j * ipl])
108
109 self.K_unaugmented = self.K
110 self.K = numpy.matrix(numpy.zeros((1, 2)))
111 self.K[0, 1:2] = self.K_unaugmented
112 self.Kff_unaugmented = self.Kff
113 self.Kff = numpy.matrix(numpy.zeros((1, 2)))
114 self.Kff[0, 1:2] = self.Kff_unaugmented
115
116 self.InitializeState()
117
118
119class IntegralFlywheel(Flywheel):
Ravago Jones5127ccc2022-07-31 16:32:45 -0700120
Sabina Davisedf89472020-02-17 15:27:37 -0800121 def __init__(self, params, name="IntegralFlywheel"):
122 super(IntegralFlywheel, self).__init__(params, name=name)
123
124 self.A_continuous_unaugmented = self.A_continuous
125 self.B_continuous_unaugmented = self.B_continuous
126
127 self.A_continuous = numpy.matrix(numpy.zeros((3, 3)))
128 self.A_continuous[0:2, 0:2] = self.A_continuous_unaugmented
129 self.A_continuous[0:2, 2] = self.B_continuous_unaugmented
130
131 self.B_continuous = numpy.matrix(numpy.zeros((3, 1)))
132 self.B_continuous[0:2, 0] = self.B_continuous_unaugmented
133
134 # states
135 # [position, velocity, voltage_error]
136 self.C_unaugmented = self.C
137 self.C = numpy.matrix(numpy.zeros((1, 3)))
138 self.C[0:1, 0:2] = self.C_unaugmented
139
Austin Schuh43b9ae92020-02-29 23:08:38 -0800140 glog.debug('A_continuous %s' % str(self.A_continuous))
141 glog.debug('B_continuous %s' % str(self.B_continuous))
142 glog.debug('C %s' % str(self.C))
143
Sabina Davisedf89472020-02-17 15:27:37 -0800144 self.A, self.B = self.ContinuousToDiscrete(self.A_continuous,
145 self.B_continuous, self.dt)
146
Austin Schuh43b9ae92020-02-29 23:08:38 -0800147 glog.debug('A %s' % str(self.A))
148 glog.debug('B %s' % str(self.B))
149
Sabina Davisedf89472020-02-17 15:27:37 -0800150 q_pos = self.params.q_pos
151 q_vel = self.params.q_vel
152 q_voltage = self.params.q_voltage
Austin Schuh43b9ae92020-02-29 23:08:38 -0800153 self.Q_continuous = numpy.matrix([[(q_pos**2.0), 0.0, 0.0],
Ravago Jones5127ccc2022-07-31 16:32:45 -0700154 [0.0, (q_vel**2.0), 0.0],
155 [0.0, 0.0, (q_voltage**2.0)]])
Sabina Davisedf89472020-02-17 15:27:37 -0800156
157 r_pos = self.params.r_pos
Austin Schuh43b9ae92020-02-29 23:08:38 -0800158 self.R_continuous = numpy.matrix([[(r_pos**2.0)]])
Sabina Davisedf89472020-02-17 15:27:37 -0800159
Ravago Jones5127ccc2022-07-31 16:32:45 -0700160 _, _, self.Q, self.R = controls.kalmd(A_continuous=self.A_continuous,
161 B_continuous=self.B_continuous,
162 Q_continuous=self.Q_continuous,
163 R_continuous=self.R_continuous,
164 dt=self.dt)
Austin Schuh43b9ae92020-02-29 23:08:38 -0800165
166 glog.debug('Q_discrete %s' % (str(self.Q)))
167 glog.debug('R_discrete %s' % (str(self.R)))
168
Ravago Jones5127ccc2022-07-31 16:32:45 -0700169 self.KalmanGain, self.P_steady_state = controls.kalman(A=self.A,
170 B=self.B,
171 C=self.C,
172 Q=self.Q,
173 R=self.R)
Sabina Davisedf89472020-02-17 15:27:37 -0800174 self.L = self.A * self.KalmanGain
175
176 self.K_unaugmented = self.K
177 self.K = numpy.matrix(numpy.zeros((1, 3)))
178 self.K[0, 0:2] = self.K_unaugmented
179 self.K[0, 2] = 1
180 self.Kff_unaugmented = self.Kff
181 self.Kff = numpy.matrix(numpy.zeros((1, 3)))
182 self.Kff[0, 0:2] = self.Kff_unaugmented
183
184 self.InitializeState()
185
186
Austin Schuh43b9ae92020-02-29 23:08:38 -0800187def PlotSpinup(params, goal, iterations=400):
Sabina Davisedf89472020-02-17 15:27:37 -0800188 """Runs the flywheel plant with an initial condition and goal.
189
190 Args:
191 flywheel: Flywheel object to use.
192 goal: goal state.
193 iterations: Number of timesteps to run the model for.
194 controller_flywheel: Flywheel object to get K from, or None if we should
195 use flywheel.
196 observer_flywheel: Flywheel object to use for the observer, or None if we
197 should use the actual state.
198 """
199
200 # Various lists for graphing things.
201 t = []
202 x = []
203 v = []
204 a = []
205 x_hat = []
206 u = []
207 offset = []
208
209 flywheel = Flywheel(params, params.name)
210 controller_flywheel = IntegralFlywheel(params, params.name)
211 observer_flywheel = IntegralFlywheel(params, params.name)
212 vbat = 12.0
213
214 if t:
215 initial_t = t[-1] + flywheel.dt
216 else:
217 initial_t = 0
218
Austin Schuh5ea48472021-02-02 20:46:41 -0800219 for i in range(iterations):
Sabina Davisedf89472020-02-17 15:27:37 -0800220 X_hat = flywheel.X
221
222 if observer_flywheel is not None:
223 X_hat = observer_flywheel.X_hat
224 x_hat.append(observer_flywheel.X_hat[1, 0])
225
226 ff_U = controller_flywheel.Kff * (goal - observer_flywheel.A * goal)
227
228 U = controller_flywheel.K * (goal - X_hat) + ff_U
229 U[0, 0] = numpy.clip(U[0, 0], -vbat, vbat)
230 x.append(flywheel.X[0, 0])
231
232 if v:
233 last_v = v[-1]
234 else:
235 last_v = 0
236
237 v.append(flywheel.X[1, 0])
238 a.append((v[-1] - last_v) / flywheel.dt)
239
240 if observer_flywheel is not None:
241 observer_flywheel.Y = flywheel.Y
Austin Schuh43b9ae92020-02-29 23:08:38 -0800242 observer_flywheel.CorrectHybridObserver(U)
Sabina Davisedf89472020-02-17 15:27:37 -0800243 offset.append(observer_flywheel.X_hat[2, 0])
244
245 applied_U = U.copy()
Austin Schuh43b9ae92020-02-29 23:08:38 -0800246 if i > 200:
Sabina Davisedf89472020-02-17 15:27:37 -0800247 applied_U += 2
248 flywheel.Update(applied_U)
249
250 if observer_flywheel is not None:
Austin Schuh43b9ae92020-02-29 23:08:38 -0800251 observer_flywheel.PredictHybridObserver(U, flywheel.dt)
Sabina Davisedf89472020-02-17 15:27:37 -0800252
253 t.append(initial_t + i * flywheel.dt)
254 u.append(U[0, 0])
255
Maxwell Henderson34242992024-01-07 12:39:11 -0800256 matplotlib.use("GTK3Agg")
Sabina Davisedf89472020-02-17 15:27:37 -0800257
Maxwell Henderson34242992024-01-07 12:39:11 -0800258 plt.subplot(3, 1, 1)
259 plt.plot(t, v, label='x')
260 plt.plot(t, x_hat, label='x_hat')
261 plt.legend()
Sabina Davisedf89472020-02-17 15:27:37 -0800262
Maxwell Henderson34242992024-01-07 12:39:11 -0800263 plt.subplot(3, 1, 2)
264 plt.plot(t, u, label='u')
265 plt.plot(t, offset, label='voltage_offset')
266 plt.legend()
Sabina Davisedf89472020-02-17 15:27:37 -0800267
Maxwell Henderson34242992024-01-07 12:39:11 -0800268 plt.subplot(3, 1, 3)
269 plt.plot(t, a, label='a')
270 plt.legend()
271
272 plt.show()
Sabina Davisedf89472020-02-17 15:27:37 -0800273
274
275def WriteFlywheel(params, plant_files, controller_files, namespace):
276 """Writes out the constants for a flywheel to a file.
277
278 Args:
279 params: list of Flywheel Params, the
280 parameters defining the system.
281 plant_files: list of strings, the cc and h files for the plant.
282 controller_files: list of strings, the cc and h files for the integral
283 controller.
284 namespaces: list of strings, the namespace list to use.
285 """
286 # Write the generated constants out to a file.
287 flywheels = []
288 integral_flywheels = []
289
290 if type(params) is list:
291 name = params[0].name
292 for index, param in enumerate(params):
293 flywheels.append(Flywheel(param, name=param.name + str(index)))
294 integral_flywheels.append(
Ravago Jones5127ccc2022-07-31 16:32:45 -0700295 IntegralFlywheel(param,
296 name='Integral' + param.name + str(index)))
Sabina Davisedf89472020-02-17 15:27:37 -0800297 else:
298 name = params.name
299 flywheels.append(Flywheel(params, params.name))
300 integral_flywheels.append(
301 IntegralFlywheel(params, name='Integral' + params.name))
302
Ravago Jones5127ccc2022-07-31 16:32:45 -0700303 loop_writer = control_loop.ControlLoopWriter(name,
304 flywheels,
305 namespaces=namespace)
Sabina Davisedf89472020-02-17 15:27:37 -0800306 loop_writer.AddConstant(
Ravago Jones5127ccc2022-07-31 16:32:45 -0700307 control_loop.Constant('kOutputRatio', '%f', flywheels[0].G))
Sabina Davisedf89472020-02-17 15:27:37 -0800308 loop_writer.AddConstant(
Austin Schuh9dcd5202020-02-20 20:06:04 -0800309 control_loop.Constant('kFreeSpeed', '%f',
Sabina Davisedf89472020-02-17 15:27:37 -0800310 flywheels[0].motor.free_speed))
Austin Schuhe8ca06a2020-03-07 22:27:39 -0800311 loop_writer.AddConstant(
Ravago Jones5127ccc2022-07-31 16:32:45 -0700312 control_loop.Constant('kBemf',
313 '%f',
314 flywheels[0].motor.Kv * flywheels[0].G,
315 comment="// Radians/sec / volt"))
Austin Schuhe8ca06a2020-03-07 22:27:39 -0800316 loop_writer.AddConstant(
Ravago Jones5127ccc2022-07-31 16:32:45 -0700317 control_loop.Constant('kResistance',
318 '%f',
319 flywheels[0].motor.resistance,
320 comment="// Ohms"))
Sabina Davisedf89472020-02-17 15:27:37 -0800321 loop_writer.Write(plant_files[0], plant_files[1])
322
323 integral_loop_writer = control_loop.ControlLoopWriter(
Austin Schuh43b9ae92020-02-29 23:08:38 -0800324 'Integral' + name,
325 integral_flywheels,
326 namespaces=namespace,
327 plant_type='StateFeedbackHybridPlant',
328 observer_type='HybridKalman')
Sabina Davisedf89472020-02-17 15:27:37 -0800329 integral_loop_writer.Write(controller_files[0], controller_files[1])