blob: 9153bc6f4b04b727541a0c34f193da07e8678b8b [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
4from matplotlib import pylab
5
6import glog
7
8
9class FlywheelParams(object):
Ravago Jones5127ccc2022-07-31 16:32:45 -070010
Sabina Davisedf89472020-02-17 15:27:37 -080011 def __init__(self,
12 name,
13 motor,
14 G,
15 J,
16 q_pos,
17 q_vel,
18 q_voltage,
19 r_pos,
20 controller_poles,
21 dt=0.00505):
22 self.name = name
23 self.motor = motor
24 self.G = G
25 self.J = J
26 self.q_pos = q_pos
27 self.q_vel = q_vel
28 self.q_voltage = q_voltage
29 self.r_pos = r_pos
30 self.dt = dt
31 self.controller_poles = controller_poles
32
33
Austin Schuh43b9ae92020-02-29 23:08:38 -080034class VelocityFlywheel(control_loop.HybridControlLoop):
Ravago Jones5127ccc2022-07-31 16:32:45 -070035
Sabina Davisedf89472020-02-17 15:27:37 -080036 def __init__(self, params, name="Flywheel"):
37 super(VelocityFlywheel, self).__init__(name=name)
38 self.params = params
39 # Set Motor
40 self.motor = self.params.motor
41 # Moment of inertia of the flywheel wheel in kg m^2
42 self.J = self.params.J
43 # Gear ratio
44 self.G = self.params.G
45 # Control loop time step
46 self.dt = self.params.dt
47
48 # State feedback matrices
49 # [angular velocity]
50 self.A_continuous = numpy.matrix([[
51 -self.motor.Kt / self.motor.Kv /
52 (self.J * self.G * self.G * self.motor.resistance)
53 ]])
54 self.B_continuous = numpy.matrix(
55 [[self.motor.Kt / (self.J * self.G * self.motor.resistance)]])
56 self.C = numpy.matrix([[1]])
57 self.D = numpy.matrix([[0]])
58
59 self.A, self.B = self.ContinuousToDiscrete(self.A_continuous,
60 self.B_continuous, self.dt)
61
62 self.PlaceControllerPoles(self.params.controller_poles)
63
64 # Generated controller not used.
65 self.PlaceObserverPoles([0.3])
66
67 self.U_max = numpy.matrix([[12.0]])
68 self.U_min = numpy.matrix([[-12.0]])
69
70 qff_vel = 8.0
71 self.Qff = numpy.matrix([[1.0 / (qff_vel**2.0)]])
72
73 self.Kff = controls.TwoStateFeedForwards(self.B, self.Qff)
74
Austin Schuheb240f62021-11-07 19:57:06 -080075 glog.debug('K: %s', str(self.K))
76 glog.debug('Poles: %s',
77 str(numpy.linalg.eig(self.A - self.B * self.K)[0]))
78
Sabina Davisedf89472020-02-17 15:27:37 -080079
80class Flywheel(VelocityFlywheel):
Ravago Jones5127ccc2022-07-31 16:32:45 -070081
Sabina Davisedf89472020-02-17 15:27:37 -080082 def __init__(self, params, name="Flywheel"):
83 super(Flywheel, self).__init__(params, name=name)
84
85 self.A_continuous_unaugmented = self.A_continuous
86 self.B_continuous_unaugmented = self.B_continuous
87
88 self.A_continuous = numpy.matrix(numpy.zeros((2, 2)))
89 self.A_continuous[1:2, 1:2] = self.A_continuous_unaugmented
90 self.A_continuous[0, 1] = 1
91
92 self.B_continuous = numpy.matrix(numpy.zeros((2, 1)))
93 self.B_continuous[1:2, 0] = self.B_continuous_unaugmented
94
95 # State feedback matrices
96 # [position, angular velocity]
97 self.C = numpy.matrix([[1, 0]])
98 self.D = numpy.matrix([[0]])
99
100 self.A, self.B = self.ContinuousToDiscrete(self.A_continuous,
101 self.B_continuous, self.dt)
102
103 rpl = 0.45
104 ipl = 0.07
105 self.PlaceObserverPoles([rpl + 1j * ipl, rpl - 1j * ipl])
106
107 self.K_unaugmented = self.K
108 self.K = numpy.matrix(numpy.zeros((1, 2)))
109 self.K[0, 1:2] = self.K_unaugmented
110 self.Kff_unaugmented = self.Kff
111 self.Kff = numpy.matrix(numpy.zeros((1, 2)))
112 self.Kff[0, 1:2] = self.Kff_unaugmented
113
114 self.InitializeState()
115
116
117class IntegralFlywheel(Flywheel):
Ravago Jones5127ccc2022-07-31 16:32:45 -0700118
Sabina Davisedf89472020-02-17 15:27:37 -0800119 def __init__(self, params, name="IntegralFlywheel"):
120 super(IntegralFlywheel, self).__init__(params, name=name)
121
122 self.A_continuous_unaugmented = self.A_continuous
123 self.B_continuous_unaugmented = self.B_continuous
124
125 self.A_continuous = numpy.matrix(numpy.zeros((3, 3)))
126 self.A_continuous[0:2, 0:2] = self.A_continuous_unaugmented
127 self.A_continuous[0:2, 2] = self.B_continuous_unaugmented
128
129 self.B_continuous = numpy.matrix(numpy.zeros((3, 1)))
130 self.B_continuous[0:2, 0] = self.B_continuous_unaugmented
131
132 # states
133 # [position, velocity, voltage_error]
134 self.C_unaugmented = self.C
135 self.C = numpy.matrix(numpy.zeros((1, 3)))
136 self.C[0:1, 0:2] = self.C_unaugmented
137
Austin Schuh43b9ae92020-02-29 23:08:38 -0800138 glog.debug('A_continuous %s' % str(self.A_continuous))
139 glog.debug('B_continuous %s' % str(self.B_continuous))
140 glog.debug('C %s' % str(self.C))
141
Sabina Davisedf89472020-02-17 15:27:37 -0800142 self.A, self.B = self.ContinuousToDiscrete(self.A_continuous,
143 self.B_continuous, self.dt)
144
Austin Schuh43b9ae92020-02-29 23:08:38 -0800145 glog.debug('A %s' % str(self.A))
146 glog.debug('B %s' % str(self.B))
147
Sabina Davisedf89472020-02-17 15:27:37 -0800148 q_pos = self.params.q_pos
149 q_vel = self.params.q_vel
150 q_voltage = self.params.q_voltage
Austin Schuh43b9ae92020-02-29 23:08:38 -0800151 self.Q_continuous = numpy.matrix([[(q_pos**2.0), 0.0, 0.0],
Ravago Jones5127ccc2022-07-31 16:32:45 -0700152 [0.0, (q_vel**2.0), 0.0],
153 [0.0, 0.0, (q_voltage**2.0)]])
Sabina Davisedf89472020-02-17 15:27:37 -0800154
155 r_pos = self.params.r_pos
Austin Schuh43b9ae92020-02-29 23:08:38 -0800156 self.R_continuous = numpy.matrix([[(r_pos**2.0)]])
Sabina Davisedf89472020-02-17 15:27:37 -0800157
Ravago Jones5127ccc2022-07-31 16:32:45 -0700158 _, _, self.Q, self.R = controls.kalmd(A_continuous=self.A_continuous,
159 B_continuous=self.B_continuous,
160 Q_continuous=self.Q_continuous,
161 R_continuous=self.R_continuous,
162 dt=self.dt)
Austin Schuh43b9ae92020-02-29 23:08:38 -0800163
164 glog.debug('Q_discrete %s' % (str(self.Q)))
165 glog.debug('R_discrete %s' % (str(self.R)))
166
Ravago Jones5127ccc2022-07-31 16:32:45 -0700167 self.KalmanGain, self.P_steady_state = controls.kalman(A=self.A,
168 B=self.B,
169 C=self.C,
170 Q=self.Q,
171 R=self.R)
Sabina Davisedf89472020-02-17 15:27:37 -0800172 self.L = self.A * self.KalmanGain
173
174 self.K_unaugmented = self.K
175 self.K = numpy.matrix(numpy.zeros((1, 3)))
176 self.K[0, 0:2] = self.K_unaugmented
177 self.K[0, 2] = 1
178 self.Kff_unaugmented = self.Kff
179 self.Kff = numpy.matrix(numpy.zeros((1, 3)))
180 self.Kff[0, 0:2] = self.Kff_unaugmented
181
182 self.InitializeState()
183
184
Austin Schuh43b9ae92020-02-29 23:08:38 -0800185def PlotSpinup(params, goal, iterations=400):
Sabina Davisedf89472020-02-17 15:27:37 -0800186 """Runs the flywheel plant with an initial condition and goal.
187
188 Args:
189 flywheel: Flywheel object to use.
190 goal: goal state.
191 iterations: Number of timesteps to run the model for.
192 controller_flywheel: Flywheel object to get K from, or None if we should
193 use flywheel.
194 observer_flywheel: Flywheel object to use for the observer, or None if we
195 should use the actual state.
196 """
197
198 # Various lists for graphing things.
199 t = []
200 x = []
201 v = []
202 a = []
203 x_hat = []
204 u = []
205 offset = []
206
207 flywheel = Flywheel(params, params.name)
208 controller_flywheel = IntegralFlywheel(params, params.name)
209 observer_flywheel = IntegralFlywheel(params, params.name)
210 vbat = 12.0
211
212 if t:
213 initial_t = t[-1] + flywheel.dt
214 else:
215 initial_t = 0
216
Austin Schuh5ea48472021-02-02 20:46:41 -0800217 for i in range(iterations):
Sabina Davisedf89472020-02-17 15:27:37 -0800218 X_hat = flywheel.X
219
220 if observer_flywheel is not None:
221 X_hat = observer_flywheel.X_hat
222 x_hat.append(observer_flywheel.X_hat[1, 0])
223
224 ff_U = controller_flywheel.Kff * (goal - observer_flywheel.A * goal)
225
226 U = controller_flywheel.K * (goal - X_hat) + ff_U
227 U[0, 0] = numpy.clip(U[0, 0], -vbat, vbat)
228 x.append(flywheel.X[0, 0])
229
230 if v:
231 last_v = v[-1]
232 else:
233 last_v = 0
234
235 v.append(flywheel.X[1, 0])
236 a.append((v[-1] - last_v) / flywheel.dt)
237
238 if observer_flywheel is not None:
239 observer_flywheel.Y = flywheel.Y
Austin Schuh43b9ae92020-02-29 23:08:38 -0800240 observer_flywheel.CorrectHybridObserver(U)
Sabina Davisedf89472020-02-17 15:27:37 -0800241 offset.append(observer_flywheel.X_hat[2, 0])
242
243 applied_U = U.copy()
Austin Schuh43b9ae92020-02-29 23:08:38 -0800244 if i > 200:
Sabina Davisedf89472020-02-17 15:27:37 -0800245 applied_U += 2
246 flywheel.Update(applied_U)
247
248 if observer_flywheel is not None:
Austin Schuh43b9ae92020-02-29 23:08:38 -0800249 observer_flywheel.PredictHybridObserver(U, flywheel.dt)
Sabina Davisedf89472020-02-17 15:27:37 -0800250
251 t.append(initial_t + i * flywheel.dt)
252 u.append(U[0, 0])
253
Sabina Davisedf89472020-02-17 15:27:37 -0800254 pylab.subplot(3, 1, 1)
255 pylab.plot(t, v, label='x')
256 pylab.plot(t, x_hat, label='x_hat')
257 pylab.legend()
258
259 pylab.subplot(3, 1, 2)
260 pylab.plot(t, u, label='u')
261 pylab.plot(t, offset, label='voltage_offset')
262 pylab.legend()
263
264 pylab.subplot(3, 1, 3)
265 pylab.plot(t, a, label='a')
266 pylab.legend()
267
268 pylab.show()
269
270
271def WriteFlywheel(params, plant_files, controller_files, namespace):
272 """Writes out the constants for a flywheel to a file.
273
274 Args:
275 params: list of Flywheel Params, the
276 parameters defining the system.
277 plant_files: list of strings, the cc and h files for the plant.
278 controller_files: list of strings, the cc and h files for the integral
279 controller.
280 namespaces: list of strings, the namespace list to use.
281 """
282 # Write the generated constants out to a file.
283 flywheels = []
284 integral_flywheels = []
285
286 if type(params) is list:
287 name = params[0].name
288 for index, param in enumerate(params):
289 flywheels.append(Flywheel(param, name=param.name + str(index)))
290 integral_flywheels.append(
Ravago Jones5127ccc2022-07-31 16:32:45 -0700291 IntegralFlywheel(param,
292 name='Integral' + param.name + str(index)))
Sabina Davisedf89472020-02-17 15:27:37 -0800293 else:
294 name = params.name
295 flywheels.append(Flywheel(params, params.name))
296 integral_flywheels.append(
297 IntegralFlywheel(params, name='Integral' + params.name))
298
Ravago Jones5127ccc2022-07-31 16:32:45 -0700299 loop_writer = control_loop.ControlLoopWriter(name,
300 flywheels,
301 namespaces=namespace)
Sabina Davisedf89472020-02-17 15:27:37 -0800302 loop_writer.AddConstant(
Ravago Jones5127ccc2022-07-31 16:32:45 -0700303 control_loop.Constant('kOutputRatio', '%f', flywheels[0].G))
Sabina Davisedf89472020-02-17 15:27:37 -0800304 loop_writer.AddConstant(
Austin Schuh9dcd5202020-02-20 20:06:04 -0800305 control_loop.Constant('kFreeSpeed', '%f',
Sabina Davisedf89472020-02-17 15:27:37 -0800306 flywheels[0].motor.free_speed))
Austin Schuhe8ca06a2020-03-07 22:27:39 -0800307 loop_writer.AddConstant(
Ravago Jones5127ccc2022-07-31 16:32:45 -0700308 control_loop.Constant('kBemf',
309 '%f',
310 flywheels[0].motor.Kv * flywheels[0].G,
311 comment="// Radians/sec / volt"))
Austin Schuhe8ca06a2020-03-07 22:27:39 -0800312 loop_writer.AddConstant(
Ravago Jones5127ccc2022-07-31 16:32:45 -0700313 control_loop.Constant('kResistance',
314 '%f',
315 flywheels[0].motor.resistance,
316 comment="// Ohms"))
Sabina Davisedf89472020-02-17 15:27:37 -0800317 loop_writer.Write(plant_files[0], plant_files[1])
318
319 integral_loop_writer = control_loop.ControlLoopWriter(
Austin Schuh43b9ae92020-02-29 23:08:38 -0800320 'Integral' + name,
321 integral_flywheels,
322 namespaces=namespace,
323 plant_type='StateFeedbackHybridPlant',
324 observer_type='HybridKalman')
Sabina Davisedf89472020-02-17 15:27:37 -0800325 integral_loop_writer.Write(controller_files[0], controller_files[1])