blob: 79a115e110eba9d206cba7453f9a4597cf743ee4 [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
5from frc971.control_loops.python import polytope
6from y2016.control_loops.python import polydrivetrain
7import numpy
8import sys
9import matplotlib
10from matplotlib import pylab
11import gflags
12import glog
13
14FLAGS = gflags.FLAGS
15
16try:
17 gflags.DEFINE_bool('plot', False, 'If true, plot the loop response.')
18except gflags.DuplicateFlagError:
19 pass
20
21class Wrist(control_loop.ControlLoop):
Austin Schuhf0c0b7f2016-01-31 00:29:13 -080022 def __init__(self, name="Wrist"):
Comran Morshed2ae094e2016-01-23 20:43:20 +000023 super(Wrist, self).__init__(name)
24 # TODO(constants): Update all of these & retune poles.
25 # Stall Torque in N m
Austin Schuhf0c0b7f2016-01-31 00:29:13 -080026 self.stall_torque = 0.71
Comran Morshed2ae094e2016-01-23 20:43:20 +000027 # Stall Current in Amps
Austin Schuhf0c0b7f2016-01-31 00:29:13 -080028 self.stall_current = 134
Comran Morshed2ae094e2016-01-23 20:43:20 +000029 # Free Speed in RPM
Austin Schuhf0c0b7f2016-01-31 00:29:13 -080030 self.free_speed = 18730
Comran Morshed2ae094e2016-01-23 20:43:20 +000031 # Free Current in Amps
Austin Schuhf0c0b7f2016-01-31 00:29:13 -080032 self.free_current = 0.7
Comran Morshed2ae094e2016-01-23 20:43:20 +000033
34 # Resistance of the motor
35 self.R = 12.0 / self.stall_current
36 # Motor velocity constant
37 self.Kv = ((self.free_speed / 60.0 * 2.0 * numpy.pi) /
38 (12.0 - self.R * self.free_current))
39 # Torque constant
40 self.Kt = self.stall_torque / self.stall_current
41 # Gear ratio
Austin Schuhf0c0b7f2016-01-31 00:29:13 -080042 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 +000043
Austin Schuhf0c0b7f2016-01-31 00:29:13 -080044 self.J = 0.15
Comran Morshed2ae094e2016-01-23 20:43:20 +000045
46 # Control loop time step
47 self.dt = 0.005
48
49 # State is [position, velocity]
50 # Input is [Voltage]
51
52 C1 = self.G * self.G * self.Kt / (self.R * self.J * self.Kv)
53 C2 = self.Kt * self.G / (self.J * self.R)
54
55 self.A_continuous = numpy.matrix(
56 [[0, 1],
57 [0, -C1]])
58
59 # Start with the unmodified input
60 self.B_continuous = numpy.matrix(
61 [[0],
62 [C2]])
63
64 self.C = numpy.matrix([[1, 0]])
65 self.D = numpy.matrix([[0]])
66
67 self.A, self.B = self.ContinuousToDiscrete(
68 self.A_continuous, self.B_continuous, self.dt)
69
70 controllability = controls.ctrb(self.A, self.B)
71
Austin Schuhf0c0b7f2016-01-31 00:29:13 -080072 q_pos = 0.20
73 q_vel = 8.0
Comran Morshed2ae094e2016-01-23 20:43:20 +000074 self.Q = numpy.matrix([[(1.0 / (q_pos ** 2.0)), 0.0],
75 [0.0, (1.0 / (q_vel ** 2.0))]])
76
77 self.R = numpy.matrix([[(1.0 / (12.0 ** 2.0))]])
78 self.K = controls.dlqr(self.A, self.B, self.Q, self.R)
79
80 print 'K', self.K
81 print 'Poles are', numpy.linalg.eig(self.A - self.B * self.K)[0]
82
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
94 print 'Kal', self.KalmanGain
95 self.L = self.A * self.KalmanGain
96 print 'KalL is', self.L
97
98 # The box formed by U_min and U_max must encompass all possible values,
99 # or else Austin's code gets angry.
100 self.U_max = numpy.matrix([[12.0]])
101 self.U_min = numpy.matrix([[-12.0]])
102
103 self.InitializeState()
104
105class IntegralWrist(Wrist):
Austin Schuhf0c0b7f2016-01-31 00:29:13 -0800106 def __init__(self, name="IntegralWrist"):
107 super(IntegralWrist, self).__init__(name=name)
Comran Morshed2ae094e2016-01-23 20:43:20 +0000108
109 self.A_continuous_unaugmented = self.A_continuous
110 self.B_continuous_unaugmented = self.B_continuous
111
112 self.A_continuous = numpy.matrix(numpy.zeros((3, 3)))
113 self.A_continuous[0:2, 0:2] = self.A_continuous_unaugmented
114 self.A_continuous[0:2, 2] = self.B_continuous_unaugmented
115
116 self.B_continuous = numpy.matrix(numpy.zeros((3, 1)))
117 self.B_continuous[0:2, 0] = self.B_continuous_unaugmented
118
119 self.C_unaugmented = self.C
120 self.C = numpy.matrix(numpy.zeros((1, 3)))
121 self.C[0:1, 0:2] = self.C_unaugmented
122
123 self.A, self.B = self.ContinuousToDiscrete(self.A_continuous, self.B_continuous, self.dt)
124
125 q_pos = 0.08
126 q_vel = 4.00
Austin Schuhf0c0b7f2016-01-31 00:29:13 -0800127 q_voltage = 1.5
Comran Morshed2ae094e2016-01-23 20:43:20 +0000128 self.Q = numpy.matrix([[(q_pos ** 2.0), 0.0, 0.0],
129 [0.0, (q_vel ** 2.0), 0.0],
130 [0.0, 0.0, (q_voltage ** 2.0)]])
131
132 r_pos = 0.05
133 self.R = numpy.matrix([[(r_pos ** 2.0)]])
134
135 self.KalmanGain, self.Q_steady = controls.kalman(
136 A=self.A, B=self.B, C=self.C, Q=self.Q, R=self.R)
137 self.L = self.A * self.KalmanGain
138
139 self.K_unaugmented = self.K
140 self.K = numpy.matrix(numpy.zeros((1, 3)))
141 self.K[0, 0:2] = self.K_unaugmented
142 self.K[0, 2] = 1
143
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):
240 argv = FLAGS(argv)
241
Comran Morshed2ae094e2016-01-23 20:43:20 +0000242 scenario_plotter = ScenarioPlotter()
243
Austin Schuhf0c0b7f2016-01-31 00:29:13 -0800244 wrist = Wrist()
245 wrist_controller = IntegralWrist()
246 observer_wrist = IntegralWrist()
Comran Morshed2ae094e2016-01-23 20:43:20 +0000247
248 # Test moving the wrist with constant separation.
249 initial_X = numpy.matrix([[0.0], [0.0]])
250 R = numpy.matrix([[1.0], [0.0], [0.0]])
251 scenario_plotter.run_test(wrist, goal=R, controller_wrist=wrist_controller,
252 observer_wrist=observer_wrist, iterations=200)
253
254 if FLAGS.plot:
255 scenario_plotter.Plot()
256
257 # Write the generated constants out to a file.
258 if len(argv) != 5:
259 glog.fatal('Expected .h file name and .cc file name for the wrist and integral wrist.')
260 else:
261 namespaces = ['y2016', 'control_loops', 'superstructure']
262 wrist = Wrist("Wrist")
263 loop_writer = control_loop.ControlLoopWriter('Wrist', [wrist],
264 namespaces=namespaces)
265 loop_writer.Write(argv[1], argv[2])
266
Austin Schuhf0c0b7f2016-01-31 00:29:13 -0800267 integral_wrist = IntegralWrist("IntegralWrist")
Comran Morshed2ae094e2016-01-23 20:43:20 +0000268 integral_loop_writer = control_loop.ControlLoopWriter("IntegralWrist", [integral_wrist],
Austin Schuhf0c0b7f2016-01-31 00:29:13 -0800269 namespaces=namespaces)
Comran Morshed2ae094e2016-01-23 20:43:20 +0000270 integral_loop_writer.Write(argv[3], argv[4])
271
272if __name__ == '__main__':
273 sys.exit(main(sys.argv))