blob: 660e577cb029a707184890f97f7a91cb5f21da35 [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
7import matplotlib
8from 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 Wrist(control_loop.ControlLoop):
Austin Schuhf0c0b7f2016-01-31 00:29:13 -080020 def __init__(self, name="Wrist"):
Comran Morshed2ae094e2016-01-23 20:43:20 +000021 super(Wrist, self).__init__(name)
22 # TODO(constants): Update all of these & retune poles.
23 # Stall Torque in N m
Austin Schuhf0c0b7f2016-01-31 00:29:13 -080024 self.stall_torque = 0.71
Comran Morshed2ae094e2016-01-23 20:43:20 +000025 # Stall Current in Amps
Austin Schuhf0c0b7f2016-01-31 00:29:13 -080026 self.stall_current = 134
Comran Morshed2ae094e2016-01-23 20:43:20 +000027 # Free Speed in RPM
Austin Schuhf0c0b7f2016-01-31 00:29:13 -080028 self.free_speed = 18730
Comran Morshed2ae094e2016-01-23 20:43:20 +000029 # Free Current in Amps
Austin Schuhf0c0b7f2016-01-31 00:29:13 -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
Austin Schuhf0c0b7f2016-01-31 00:29:13 -080040 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 +000041
Austin Schuhf59b6bc2016-03-11 21:26:19 -080042 self.J = 0.20
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 Schuhf0c0b7f2016-01-31 00:29:13 -080070 q_pos = 0.20
71 q_vel = 8.0
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 glog.debug('Poles are %s for %s',
79 repr(numpy.linalg.eig(self.A - self.B * self.K)[0]), self._name)
Comran Morshed2ae094e2016-01-23 20:43:20 +000080
Comran Morshed2ae094e2016-01-23 20:43:20 +000081 q_pos = 0.05
82 q_vel = 2.65
83 self.Q = numpy.matrix([[(q_pos ** 2.0), 0.0],
84 [0.0, (q_vel ** 2.0)]])
85
86 r_volts = 0.025
87 self.R = numpy.matrix([[(r_volts ** 2.0)]])
88
89 self.KalmanGain, self.Q_steady = controls.kalman(
90 A=self.A, B=self.B, C=self.C, Q=self.Q, R=self.R)
91
Comran Morshed2ae094e2016-01-23 20:43:20 +000092 self.L = self.A * self.KalmanGain
Comran Morshed2ae094e2016-01-23 20:43:20 +000093
94 # The box formed by U_min and U_max must encompass all possible values,
95 # or else Austin's code gets angry.
96 self.U_max = numpy.matrix([[12.0]])
97 self.U_min = numpy.matrix([[-12.0]])
98
Austin Schuha88c4072016-02-06 14:31:03 -080099 self.Kff = controls.TwoStateFeedForwards(self.B, self.Q)
100
Comran Morshed2ae094e2016-01-23 20:43:20 +0000101 self.InitializeState()
102
103class IntegralWrist(Wrist):
Austin Schuhf0c0b7f2016-01-31 00:29:13 -0800104 def __init__(self, name="IntegralWrist"):
105 super(IntegralWrist, 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
Austin Schuhf0c0b7f2016-01-31 00:29:13 -0800125 q_voltage = 1.5
Comran Morshed2ae094e2016-01-23 20:43:20 +0000126 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 Schuhf0c0b7f2016-01-31 00:29:13 -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 Schuhf0c0b7f2016-01-31 00:29:13 -0800156 self.offset = []
Comran Morshed2ae094e2016-01-23 20:43:20 +0000157
158 def run_test(self, wrist, goal, iterations=200, controller_wrist=None,
159 observer_wrist=None):
160 """Runs the wrist 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 wrist: wrist object to use.
170 goal: goal state.
171 iterations: Number of timesteps to run the model for.
172 controller_wrist: Wrist object to get K from, or None if we should
173 use wrist.
174 observer_wrist: Wrist object to use for the observer, or None if we should
175 use the actual state.
176 """
177
178 if controller_wrist is None:
179 controller_wrist = wrist
180
181 vbat = 12.0
182
183 if self.t:
184 initial_t = self.t[-1] + wrist.dt
185 else:
186 initial_t = 0
187
188 for i in xrange(iterations):
189 X_hat = wrist.X
190
191 if observer_wrist is not None:
192 X_hat = observer_wrist.X_hat
193 self.x_hat.append(observer_wrist.X_hat[0, 0])
194
195 U = controller_wrist.K * (goal - X_hat)
196 U[0, 0] = numpy.clip(U[0, 0], -vbat, vbat)
197 self.x.append(wrist.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(wrist.X[1, 0])
205 self.a.append((self.v[-1] - last_v) / wrist.dt)
206
207 if observer_wrist is not None:
208 observer_wrist.Y = wrist.Y
209 observer_wrist.CorrectObserver(U)
Austin Schuhf0c0b7f2016-01-31 00:29:13 -0800210 self.offset.append(observer_wrist.X_hat[2, 0])
Comran Morshed2ae094e2016-01-23 20:43:20 +0000211
Austin Schuhf0c0b7f2016-01-31 00:29:13 -0800212 wrist.Update(U + 2.0)
Comran Morshed2ae094e2016-01-23 20:43:20 +0000213
214 if observer_wrist is not None:
215 observer_wrist.PredictObserver(U)
216
217 self.t.append(initial_t + i * wrist.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 Schuhf0c0b7f2016-01-31 00:29:13 -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 Schuhf0c0b7f2016-01-31 00:29:13 -0800236
Comran Morshed2ae094e2016-01-23 20:43:20 +0000237 pylab.show()
238
239
240def main(argv):
Comran Morshed2ae094e2016-01-23 20:43:20 +0000241 scenario_plotter = ScenarioPlotter()
242
Austin Schuhf0c0b7f2016-01-31 00:29:13 -0800243 wrist = Wrist()
244 wrist_controller = IntegralWrist()
245 observer_wrist = IntegralWrist()
Comran Morshed2ae094e2016-01-23 20:43:20 +0000246
247 # Test moving the wrist with constant separation.
248 initial_X = numpy.matrix([[0.0], [0.0]])
249 R = numpy.matrix([[1.0], [0.0], [0.0]])
250 scenario_plotter.run_test(wrist, goal=R, controller_wrist=wrist_controller,
251 observer_wrist=observer_wrist, iterations=200)
252
253 if FLAGS.plot:
254 scenario_plotter.Plot()
255
256 # Write the generated constants out to a file.
257 if len(argv) != 5:
258 glog.fatal('Expected .h file name and .cc file name for the wrist and integral wrist.')
259 else:
260 namespaces = ['y2016', 'control_loops', 'superstructure']
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800261 wrist = Wrist('Wrist')
Austin Schuha88c4072016-02-06 14:31:03 -0800262 loop_writer = control_loop.ControlLoopWriter(
263 'Wrist', [wrist], namespaces=namespaces)
Comran Morshed2ae094e2016-01-23 20:43:20 +0000264 loop_writer.Write(argv[1], argv[2])
265
Austin Schuha88c4072016-02-06 14:31:03 -0800266 integral_wrist = IntegralWrist('IntegralWrist')
267 integral_loop_writer = control_loop.ControlLoopWriter(
268 'IntegralWrist', [integral_wrist], namespaces=namespaces)
Comran Morshed2ae094e2016-01-23 20:43:20 +0000269 integral_loop_writer.Write(argv[3], argv[4])
270
271if __name__ == '__main__':
Austin Schuh09c2b0b2016-02-13 15:53:16 -0800272 argv = FLAGS(sys.argv)
273 sys.exit(main(argv))