blob: 07a67f68bbbc4076d476ffb194336b6208f8e657 [file] [log] [blame]
Comran Morshed2ae094e2016-01-23 20:43:20 +00001#!/usr/bin/python
2
Comran Morshed2ae094e2016-01-23 20:43:20 +00003import numpy
4import sys
Austin Schuha88c4072016-02-06 14:31:03 -08005
6from frc971.control_loops.python import control_loop
7from frc971.control_loops.python import controls
Comran Morshed2ae094e2016-01-23 20:43:20 +00008from matplotlib import pylab
9import gflags
10import glog
11
12FLAGS = gflags.FLAGS
13
14try:
15 gflags.DEFINE_bool('plot', False, 'If true, plot the loop response.')
16except gflags.DuplicateFlagError:
17 pass
18
19class Shoulder(control_loop.ControlLoop):
20 def __init__(self, name="Shoulder", mass=None):
21 super(Shoulder, self).__init__(name)
22 # TODO(constants): Update all of these & retune poles.
23 # Stall Torque in N m
Austin Schuha2dd8672016-01-31 12:18:40 -080024 self.stall_torque = 0.71
Comran Morshed2ae094e2016-01-23 20:43:20 +000025 # Stall Current in Amps
Austin Schuha2dd8672016-01-31 12:18:40 -080026 self.stall_current = 134
Comran Morshed2ae094e2016-01-23 20:43:20 +000027 # Free Speed in RPM
Austin Schuha2dd8672016-01-31 12:18:40 -080028 self.free_speed = 18730
Comran Morshed2ae094e2016-01-23 20:43:20 +000029 # Free Current in Amps
Austin Schuha2dd8672016-01-31 12:18:40 -080030 self.free_current = 0.7
Comran Morshed2ae094e2016-01-23 20:43:20 +000031
32 # Resistance of the motor
33 self.R = 12.0 / self.stall_current
34 # Motor velocity constant
35 self.Kv = ((self.free_speed / 60.0 * 2.0 * numpy.pi) /
36 (12.0 - self.R * self.free_current))
37 # Torque constant
38 self.Kt = self.stall_torque / self.stall_current
39 # Gear ratio
Comran Morshed367d8e52016-03-05 14:45:47 +000040 self.G = (56.0 / 12.0) * (64.0 / 14.0) * (72.0 / 18.0) * (42.0 / 12.0)
Comran Morshed2ae094e2016-01-23 20:43:20 +000041
Austin Schuha2dd8672016-01-31 12:18:40 -080042 self.J = 3.0
Comran Morshed2ae094e2016-01-23 20:43:20 +000043
44 # Control loop time step
45 self.dt = 0.005
46
47 # State is [position, velocity]
48 # Input is [Voltage]
49
50 C1 = self.G * self.G * self.Kt / (self.R * self.J * self.Kv)
51 C2 = self.Kt * self.G / (self.J * self.R)
52
53 self.A_continuous = numpy.matrix(
54 [[0, 1],
55 [0, -C1]])
56
57 # Start with the unmodified input
58 self.B_continuous = numpy.matrix(
59 [[0],
60 [C2]])
61
62 self.C = numpy.matrix([[1, 0]])
63 self.D = numpy.matrix([[0]])
64
65 self.A, self.B = self.ContinuousToDiscrete(
66 self.A_continuous, self.B_continuous, self.dt)
67
68 controllability = controls.ctrb(self.A, self.B)
69
Austin Schuha2dd8672016-01-31 12:18:40 -080070 q_pos = 0.14
71 q_vel = 4.5
Comran Morshed2ae094e2016-01-23 20:43:20 +000072 self.Q = numpy.matrix([[(1.0 / (q_pos ** 2.0)), 0.0],
73 [0.0, (1.0 / (q_vel ** 2.0))]])
74
75 self.R = numpy.matrix([[(1.0 / (12.0 ** 2.0))]])
76 self.K = controls.dlqr(self.A, self.B, self.Q, self.R)
77
Austin Schuha88c4072016-02-06 14:31:03 -080078 self.Kff = controls.TwoStateFeedForwards(self.B, self.Q)
79
80 glog.debug('Poles are %s for %s',
81 repr(numpy.linalg.eig(self.A - self.B * self.K)[0]), self._name)
Comran Morshed2ae094e2016-01-23 20:43:20 +000082
Comran Morshed2ae094e2016-01-23 20:43:20 +000083 q_pos = 0.05
84 q_vel = 2.65
85 self.Q = numpy.matrix([[(q_pos ** 2.0), 0.0],
86 [0.0, (q_vel ** 2.0)]])
87
88 r_volts = 0.025
89 self.R = numpy.matrix([[(r_volts ** 2.0)]])
90
91 self.KalmanGain, self.Q_steady = controls.kalman(
92 A=self.A, B=self.B, C=self.C, Q=self.Q, R=self.R)
93
Comran Morshed2ae094e2016-01-23 20:43:20 +000094 self.L = self.A * self.KalmanGain
Comran Morshed2ae094e2016-01-23 20:43:20 +000095
96 # The box formed by U_min and U_max must encompass all possible values,
97 # or else Austin's code gets angry.
98 self.U_max = numpy.matrix([[12.0]])
99 self.U_min = numpy.matrix([[-12.0]])
100
101 self.InitializeState()
102
103class IntegralShoulder(Shoulder):
Austin Schuha2dd8672016-01-31 12:18:40 -0800104 def __init__(self, name="IntegralShoulder"):
105 super(IntegralShoulder, self).__init__(name=name)
Comran Morshed2ae094e2016-01-23 20:43:20 +0000106
107 self.A_continuous_unaugmented = self.A_continuous
108 self.B_continuous_unaugmented = self.B_continuous
109
110 self.A_continuous = numpy.matrix(numpy.zeros((3, 3)))
111 self.A_continuous[0:2, 0:2] = self.A_continuous_unaugmented
112 self.A_continuous[0:2, 2] = self.B_continuous_unaugmented
113
114 self.B_continuous = numpy.matrix(numpy.zeros((3, 1)))
115 self.B_continuous[0:2, 0] = self.B_continuous_unaugmented
116
117 self.C_unaugmented = self.C
118 self.C = numpy.matrix(numpy.zeros((1, 3)))
119 self.C[0:1, 0:2] = self.C_unaugmented
120
121 self.A, self.B = self.ContinuousToDiscrete(self.A_continuous, self.B_continuous, self.dt)
122
123 q_pos = 0.08
124 q_vel = 4.00
125 q_voltage = 6.0
126 self.Q = numpy.matrix([[(q_pos ** 2.0), 0.0, 0.0],
127 [0.0, (q_vel ** 2.0), 0.0],
128 [0.0, 0.0, (q_voltage ** 2.0)]])
129
130 r_pos = 0.05
131 self.R = numpy.matrix([[(r_pos ** 2.0)]])
132
133 self.KalmanGain, self.Q_steady = controls.kalman(
134 A=self.A, B=self.B, C=self.C, Q=self.Q, R=self.R)
135 self.L = self.A * self.KalmanGain
136
137 self.K_unaugmented = self.K
138 self.K = numpy.matrix(numpy.zeros((1, 3)))
139 self.K[0, 0:2] = self.K_unaugmented
140 self.K[0, 2] = 1
Austin Schuha88c4072016-02-06 14:31:03 -0800141 self.Kff_unaugmented = self.Kff
142 self.Kff = numpy.matrix(numpy.zeros((1, 3)))
143 self.Kff[0, 0:2] = self.Kff_unaugmented
Comran Morshed2ae094e2016-01-23 20:43:20 +0000144
145 self.InitializeState()
Austin Schuha2dd8672016-01-31 12:18:40 -0800146
Comran Morshed2ae094e2016-01-23 20:43:20 +0000147class ScenarioPlotter(object):
148 def __init__(self):
149 # Various lists for graphing things.
150 self.t = []
151 self.x = []
152 self.v = []
153 self.a = []
154 self.x_hat = []
155 self.u = []
Austin Schuha2dd8672016-01-31 12:18:40 -0800156 self.offset = []
Comran Morshed2ae094e2016-01-23 20:43:20 +0000157
158 def run_test(self, shoulder, goal, iterations=200, controller_shoulder=None,
159 observer_shoulder=None):
160 """Runs the shoulder plant with an initial condition and goal.
161
162 Test for whether the goal has been reached and whether the separation
163 goes outside of the initial and goal values by more than
164 max_separation_error.
165
166 Prints out something for a failure of either condition and returns
167 False if tests fail.
168 Args:
169 shoulder: shoulder object to use.
170 goal: goal state.
171 iterations: Number of timesteps to run the model for.
172 controller_shoulder: Shoulder object to get K from, or None if we should
173 use shoulder.
174 observer_shoulder: Shoulder object to use for the observer, or None if we should
175 use the actual state.
176 """
177
178 if controller_shoulder is None:
179 controller_shoulder = shoulder
180
181 vbat = 12.0
182
183 if self.t:
184 initial_t = self.t[-1] + shoulder.dt
185 else:
186 initial_t = 0
187
188 for i in xrange(iterations):
189 X_hat = shoulder.X
190
191 if observer_shoulder is not None:
192 X_hat = observer_shoulder.X_hat
193 self.x_hat.append(observer_shoulder.X_hat[0, 0])
194
195 U = controller_shoulder.K * (goal - X_hat)
196 U[0, 0] = numpy.clip(U[0, 0], -vbat, vbat)
197 self.x.append(shoulder.X[0, 0])
198
199 if self.v:
200 last_v = self.v[-1]
201 else:
202 last_v = 0
203
204 self.v.append(shoulder.X[1, 0])
205 self.a.append((self.v[-1] - last_v) / shoulder.dt)
206
207 if observer_shoulder is not None:
208 observer_shoulder.Y = shoulder.Y
209 observer_shoulder.CorrectObserver(U)
Austin Schuha2dd8672016-01-31 12:18:40 -0800210 self.offset.append(observer_shoulder.X_hat[2, 0])
Comran Morshed2ae094e2016-01-23 20:43:20 +0000211
Austin Schuha2dd8672016-01-31 12:18:40 -0800212 shoulder.Update(U + 2.0)
Comran Morshed2ae094e2016-01-23 20:43:20 +0000213
214 if observer_shoulder is not None:
215 observer_shoulder.PredictObserver(U)
216
217 self.t.append(initial_t + i * shoulder.dt)
218 self.u.append(U[0, 0])
219
220 glog.debug('Time: %f', self.t[-1])
221
222 def Plot(self):
223 pylab.subplot(3, 1, 1)
224 pylab.plot(self.t, self.x, label='x')
225 pylab.plot(self.t, self.x_hat, label='x_hat')
226 pylab.legend()
227
228 pylab.subplot(3, 1, 2)
229 pylab.plot(self.t, self.u, label='u')
Austin Schuha2dd8672016-01-31 12:18:40 -0800230 pylab.plot(self.t, self.offset, label='voltage_offset')
231 pylab.legend()
Comran Morshed2ae094e2016-01-23 20:43:20 +0000232
233 pylab.subplot(3, 1, 3)
234 pylab.plot(self.t, self.a, label='a')
Comran Morshed2ae094e2016-01-23 20:43:20 +0000235 pylab.legend()
Austin Schuha2dd8672016-01-31 12:18:40 -0800236
Comran Morshed2ae094e2016-01-23 20:43:20 +0000237 pylab.show()
238
239
240def main(argv):
241 argv = FLAGS(argv)
242
Comran Morshed2ae094e2016-01-23 20:43:20 +0000243 scenario_plotter = ScenarioPlotter()
244
Austin Schuha2dd8672016-01-31 12:18:40 -0800245 shoulder = Shoulder()
246 shoulder_controller = IntegralShoulder()
247 observer_shoulder = IntegralShoulder()
Comran Morshed2ae094e2016-01-23 20:43:20 +0000248
249 # Test moving the shoulder with constant separation.
250 initial_X = numpy.matrix([[0.0], [0.0]])
Austin Schuha2dd8672016-01-31 12:18:40 -0800251 R = numpy.matrix([[numpy.pi / 2.0], [0.0], [0.0]])
Comran Morshed2ae094e2016-01-23 20:43:20 +0000252 scenario_plotter.run_test(shoulder, goal=R, controller_shoulder=shoulder_controller,
253 observer_shoulder=observer_shoulder, iterations=200)
254
255 if FLAGS.plot:
256 scenario_plotter.Plot()
257
258 # Write the generated constants out to a file.
259 if len(argv) != 5:
260 glog.fatal('Expected .h file name and .cc file name for the shoulder and integral shoulder.')
261 else:
262 namespaces = ['y2016', 'control_loops', 'superstructure']
263 shoulder = Shoulder("Shoulder")
264 loop_writer = control_loop.ControlLoopWriter('Shoulder', [shoulder],
265 namespaces=namespaces)
266 loop_writer.Write(argv[1], argv[2])
267
Austin Schuha2dd8672016-01-31 12:18:40 -0800268 integral_shoulder = IntegralShoulder("IntegralShoulder")
Comran Morshed2ae094e2016-01-23 20:43:20 +0000269 integral_loop_writer = control_loop.ControlLoopWriter("IntegralShoulder", [integral_shoulder],
Austin Schuha2dd8672016-01-31 12:18:40 -0800270 namespaces=namespaces)
Comran Morshed2ae094e2016-01-23 20:43:20 +0000271 integral_loop_writer.Write(argv[3], argv[4])
272
273if __name__ == '__main__':
274 sys.exit(main(sys.argv))