blob: 6de8551a45aaaac884b7b38ca402c69bc00a8a4c [file] [log] [blame]
Comran Morshed2ae094e2016-01-23 20:43:20 +00001#!/usr/bin/python
2
3from frc971.control_loops.python import control_loop
4from frc971.control_loops.python import controls
Comran Morshed2ae094e2016-01-23 20:43:20 +00005import numpy
6import sys
Comran Morshed2ae094e2016-01-23 20:43:20 +00007from matplotlib import pylab
8import gflags
9import glog
10
11FLAGS = gflags.FLAGS
12
13try:
14 gflags.DEFINE_bool('plot', False, 'If true, plot the loop response.')
15except gflags.DuplicateFlagError:
16 pass
17
18class Wrist(control_loop.ControlLoop):
Austin Schuhf0c0b7f2016-01-31 00:29:13 -080019 def __init__(self, name="Wrist"):
Comran Morshed2ae094e2016-01-23 20:43:20 +000020 super(Wrist, self).__init__(name)
21 # TODO(constants): Update all of these & retune poles.
22 # Stall Torque in N m
Austin Schuhf0c0b7f2016-01-31 00:29:13 -080023 self.stall_torque = 0.71
Comran Morshed2ae094e2016-01-23 20:43:20 +000024 # Stall Current in Amps
Austin Schuhf0c0b7f2016-01-31 00:29:13 -080025 self.stall_current = 134
Comran Morshed2ae094e2016-01-23 20:43:20 +000026 # Free Speed in RPM
Austin Schuhf0c0b7f2016-01-31 00:29:13 -080027 self.free_speed = 18730
Comran Morshed2ae094e2016-01-23 20:43:20 +000028 # Free Current in Amps
Austin Schuhf0c0b7f2016-01-31 00:29:13 -080029 self.free_current = 0.7
Comran Morshed2ae094e2016-01-23 20:43:20 +000030
31 # Resistance of the motor
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
Austin Schuhf0c0b7f2016-01-31 00:29:13 -080039 self.G = (56.0 / 12.0) * (54.0 / 14.0) * (64.0 / 18.0) * (48.0 / 16.0)
Comran Morshed2ae094e2016-01-23 20:43:20 +000040
Austin Schuh2fc178f2016-04-24 19:09:26 -070041 self.J = 0.35
Comran Morshed2ae094e2016-01-23 20:43:20 +000042
43 # Control loop time step
44 self.dt = 0.005
45
46 # State is [position, velocity]
47 # Input is [Voltage]
48
49 C1 = self.G * self.G * self.Kt / (self.R * self.J * self.Kv)
50 C2 = self.Kt * self.G / (self.J * self.R)
51
52 self.A_continuous = numpy.matrix(
53 [[0, 1],
54 [0, -C1]])
55
56 # Start with the unmodified input
57 self.B_continuous = numpy.matrix(
58 [[0],
59 [C2]])
60
61 self.C = numpy.matrix([[1, 0]])
62 self.D = numpy.matrix([[0]])
63
64 self.A, self.B = self.ContinuousToDiscrete(
65 self.A_continuous, self.B_continuous, self.dt)
66
67 controllability = controls.ctrb(self.A, self.B)
68
Austin Schuhf0c0b7f2016-01-31 00:29:13 -080069 q_pos = 0.20
70 q_vel = 8.0
Comran Morshed2ae094e2016-01-23 20:43:20 +000071 self.Q = numpy.matrix([[(1.0 / (q_pos ** 2.0)), 0.0],
72 [0.0, (1.0 / (q_vel ** 2.0))]])
73
74 self.R = numpy.matrix([[(1.0 / (12.0 ** 2.0))]])
75 self.K = controls.dlqr(self.A, self.B, self.Q, self.R)
76
Austin Schuha88c4072016-02-06 14:31:03 -080077 glog.debug('Poles are %s for %s',
78 repr(numpy.linalg.eig(self.A - self.B * self.K)[0]), self._name)
Comran Morshed2ae094e2016-01-23 20:43:20 +000079
Comran Morshed2ae094e2016-01-23 20:43:20 +000080 q_pos = 0.05
81 q_vel = 2.65
82 self.Q = numpy.matrix([[(q_pos ** 2.0), 0.0],
83 [0.0, (q_vel ** 2.0)]])
84
85 r_volts = 0.025
86 self.R = numpy.matrix([[(r_volts ** 2.0)]])
87
88 self.KalmanGain, self.Q_steady = controls.kalman(
89 A=self.A, B=self.B, C=self.C, Q=self.Q, R=self.R)
90
Comran Morshed2ae094e2016-01-23 20:43:20 +000091 self.L = self.A * self.KalmanGain
Comran Morshed2ae094e2016-01-23 20:43:20 +000092
93 # The box formed by U_min and U_max must encompass all possible values,
94 # or else Austin's code gets angry.
95 self.U_max = numpy.matrix([[12.0]])
96 self.U_min = numpy.matrix([[-12.0]])
97
Austin Schuha88c4072016-02-06 14:31:03 -080098 self.Kff = controls.TwoStateFeedForwards(self.B, self.Q)
99
Comran Morshed2ae094e2016-01-23 20:43:20 +0000100 self.InitializeState()
101
102class IntegralWrist(Wrist):
Austin Schuhf0c0b7f2016-01-31 00:29:13 -0800103 def __init__(self, name="IntegralWrist"):
104 super(IntegralWrist, self).__init__(name=name)
Comran Morshed2ae094e2016-01-23 20:43:20 +0000105
106 self.A_continuous_unaugmented = self.A_continuous
107 self.B_continuous_unaugmented = self.B_continuous
108
109 self.A_continuous = numpy.matrix(numpy.zeros((3, 3)))
110 self.A_continuous[0:2, 0:2] = self.A_continuous_unaugmented
111 self.A_continuous[0:2, 2] = self.B_continuous_unaugmented
112
113 self.B_continuous = numpy.matrix(numpy.zeros((3, 1)))
114 self.B_continuous[0:2, 0] = self.B_continuous_unaugmented
115
116 self.C_unaugmented = self.C
117 self.C = numpy.matrix(numpy.zeros((1, 3)))
118 self.C[0:1, 0:2] = self.C_unaugmented
119
120 self.A, self.B = self.ContinuousToDiscrete(self.A_continuous, self.B_continuous, self.dt)
121
122 q_pos = 0.08
123 q_vel = 4.00
Austin Schuhf0c0b7f2016-01-31 00:29:13 -0800124 q_voltage = 1.5
Comran Morshed2ae094e2016-01-23 20:43:20 +0000125 self.Q = numpy.matrix([[(q_pos ** 2.0), 0.0, 0.0],
126 [0.0, (q_vel ** 2.0), 0.0],
127 [0.0, 0.0, (q_voltage ** 2.0)]])
128
129 r_pos = 0.05
130 self.R = numpy.matrix([[(r_pos ** 2.0)]])
131
132 self.KalmanGain, self.Q_steady = controls.kalman(
133 A=self.A, B=self.B, C=self.C, Q=self.Q, R=self.R)
134 self.L = self.A * self.KalmanGain
135
136 self.K_unaugmented = self.K
137 self.K = numpy.matrix(numpy.zeros((1, 3)))
138 self.K[0, 0:2] = self.K_unaugmented
139 self.K[0, 2] = 1
Austin Schuha88c4072016-02-06 14:31:03 -0800140 self.Kff_unaugmented = self.Kff
141 self.Kff = numpy.matrix(numpy.zeros((1, 3)))
142 self.Kff[0, 0:2] = self.Kff_unaugmented
Comran Morshed2ae094e2016-01-23 20:43:20 +0000143
144 self.InitializeState()
Austin Schuhf0c0b7f2016-01-31 00:29:13 -0800145
Comran Morshed2ae094e2016-01-23 20:43:20 +0000146class ScenarioPlotter(object):
147 def __init__(self):
148 # Various lists for graphing things.
149 self.t = []
150 self.x = []
151 self.v = []
152 self.a = []
153 self.x_hat = []
154 self.u = []
Austin Schuhf0c0b7f2016-01-31 00:29:13 -0800155 self.offset = []
Comran Morshed2ae094e2016-01-23 20:43:20 +0000156
157 def run_test(self, wrist, goal, iterations=200, controller_wrist=None,
158 observer_wrist=None):
159 """Runs the wrist plant with an initial condition and goal.
160
161 Test for whether the goal has been reached and whether the separation
162 goes outside of the initial and goal values by more than
163 max_separation_error.
164
165 Prints out something for a failure of either condition and returns
166 False if tests fail.
167 Args:
168 wrist: wrist object to use.
169 goal: goal state.
170 iterations: Number of timesteps to run the model for.
171 controller_wrist: Wrist object to get K from, or None if we should
172 use wrist.
173 observer_wrist: Wrist object to use for the observer, or None if we should
174 use the actual state.
175 """
176
177 if controller_wrist is None:
178 controller_wrist = wrist
179
180 vbat = 12.0
181
182 if self.t:
183 initial_t = self.t[-1] + wrist.dt
184 else:
185 initial_t = 0
186
187 for i in xrange(iterations):
188 X_hat = wrist.X
189
190 if observer_wrist is not None:
191 X_hat = observer_wrist.X_hat
192 self.x_hat.append(observer_wrist.X_hat[0, 0])
193
194 U = controller_wrist.K * (goal - X_hat)
195 U[0, 0] = numpy.clip(U[0, 0], -vbat, vbat)
196 self.x.append(wrist.X[0, 0])
197
198 if self.v:
199 last_v = self.v[-1]
200 else:
201 last_v = 0
202
203 self.v.append(wrist.X[1, 0])
204 self.a.append((self.v[-1] - last_v) / wrist.dt)
205
206 if observer_wrist is not None:
207 observer_wrist.Y = wrist.Y
208 observer_wrist.CorrectObserver(U)
Austin Schuhf0c0b7f2016-01-31 00:29:13 -0800209 self.offset.append(observer_wrist.X_hat[2, 0])
Comran Morshed2ae094e2016-01-23 20:43:20 +0000210
Austin Schuhf0c0b7f2016-01-31 00:29:13 -0800211 wrist.Update(U + 2.0)
Comran Morshed2ae094e2016-01-23 20:43:20 +0000212
213 if observer_wrist is not None:
214 observer_wrist.PredictObserver(U)
215
216 self.t.append(initial_t + i * wrist.dt)
217 self.u.append(U[0, 0])
218
219 glog.debug('Time: %f', self.t[-1])
220
221 def Plot(self):
222 pylab.subplot(3, 1, 1)
223 pylab.plot(self.t, self.x, label='x')
224 pylab.plot(self.t, self.x_hat, label='x_hat')
225 pylab.legend()
226
227 pylab.subplot(3, 1, 2)
228 pylab.plot(self.t, self.u, label='u')
Austin Schuhf0c0b7f2016-01-31 00:29:13 -0800229 pylab.plot(self.t, self.offset, label='voltage_offset')
230 pylab.legend()
Comran Morshed2ae094e2016-01-23 20:43:20 +0000231
232 pylab.subplot(3, 1, 3)
233 pylab.plot(self.t, self.a, label='a')
Comran Morshed2ae094e2016-01-23 20:43:20 +0000234 pylab.legend()
Austin Schuhf0c0b7f2016-01-31 00:29:13 -0800235
Comran Morshed2ae094e2016-01-23 20:43:20 +0000236 pylab.show()
237
238
239def main(argv):
Comran Morshed2ae094e2016-01-23 20:43:20 +0000240 scenario_plotter = ScenarioPlotter()
241
Austin Schuhf0c0b7f2016-01-31 00:29:13 -0800242 wrist = Wrist()
243 wrist_controller = IntegralWrist()
244 observer_wrist = IntegralWrist()
Comran Morshed2ae094e2016-01-23 20:43:20 +0000245
246 # Test moving the wrist with constant separation.
247 initial_X = numpy.matrix([[0.0], [0.0]])
248 R = numpy.matrix([[1.0], [0.0], [0.0]])
249 scenario_plotter.run_test(wrist, goal=R, controller_wrist=wrist_controller,
250 observer_wrist=observer_wrist, iterations=200)
251
252 if FLAGS.plot:
253 scenario_plotter.Plot()
254
255 # Write the generated constants out to a file.
256 if len(argv) != 5:
257 glog.fatal('Expected .h file name and .cc file name for the wrist and integral wrist.')
258 else:
259 namespaces = ['y2016', 'control_loops', 'superstructure']
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800260 wrist = Wrist('Wrist')
Austin Schuha88c4072016-02-06 14:31:03 -0800261 loop_writer = control_loop.ControlLoopWriter(
262 'Wrist', [wrist], namespaces=namespaces)
Comran Morshed2ae094e2016-01-23 20:43:20 +0000263 loop_writer.Write(argv[1], argv[2])
264
Austin Schuha88c4072016-02-06 14:31:03 -0800265 integral_wrist = IntegralWrist('IntegralWrist')
266 integral_loop_writer = control_loop.ControlLoopWriter(
267 'IntegralWrist', [integral_wrist], namespaces=namespaces)
Comran Morshed2ae094e2016-01-23 20:43:20 +0000268 integral_loop_writer.Write(argv[3], argv[4])
269
270if __name__ == '__main__':
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800271 argv = FLAGS(sys.argv)
272 sys.exit(main(argv))