blob: 45d0e451db88ccfc3d686a0924ab6b020f07f9e3 [file] [log] [blame]
Austin Schuhcb091712018-02-21 20:01:55 -08001#include "y2018/control_loops/superstructure/arm/arm.h"
2
3#include <chrono>
4#include <iostream>
5
6#include "aos/common/logging/logging.h"
7#include "aos/common/logging/queue_logging.h"
8#include "y2018/constants.h"
9#include "y2018/control_loops/superstructure/arm/demo_path.h"
10#include "y2018/control_loops/superstructure/arm/dynamics.h"
Austin Schuh7dfccf62018-03-03 21:28:14 -080011#include "y2018/control_loops/superstructure/arm/generated_graph.h"
Austin Schuhcb091712018-02-21 20:01:55 -080012
13namespace y2018 {
14namespace control_loops {
15namespace superstructure {
16namespace arm {
17
18namespace chrono = ::std::chrono;
19using ::aos::monotonic_clock;
20
Austin Schuhcb091712018-02-21 20:01:55 -080021Arm::Arm()
22 : proximal_zeroing_estimator_(constants::GetValues().arm_proximal.zeroing),
23 distal_zeroing_estimator_(constants::GetValues().arm_distal.zeroing),
24 alpha_unitizer_((::Eigen::Matrix<double, 2, 2>() << 1.0 / kAlpha0Max(),
25 0.0, 0.0, 1.0 / kAlpha1Max())
26 .finished()),
Austin Schuh7dfccf62018-03-03 21:28:14 -080027 search_graph_(MakeSearchGraph(&trajectories_, alpha_unitizer_, kVMax())),
Austin Schuhcb091712018-02-21 20:01:55 -080028 // Go to the start of the first trajectory.
Austin Schuh7dfccf62018-03-03 21:28:14 -080029 follower_(ReadyAboveBoxPoint()) {
30 int i = 0;
31 for (const auto &trajectory : trajectories_) {
32 LOG(INFO, "trajectory length for edge node %d: %f\n", i,
33 trajectory.path().length());
34 ++i;
Austin Schuhcb091712018-02-21 20:01:55 -080035 }
36}
37
38void Arm::Reset() { state_ = State::UNINITIALIZED; }
39
Austin Schuh96341532018-03-09 21:17:24 -080040void Arm::Iterate(const uint32_t *unsafe_goal, bool grab_box, bool open_claw,
Austin Schuhcb091712018-02-21 20:01:55 -080041 const control_loops::ArmPosition *position,
Austin Schuh96341532018-03-09 21:17:24 -080042 const bool claw_beambreak_triggered,
43 const bool box_back_beambreak_triggered,
44 const bool intake_clear_of_box, double *proximal_output,
45 double *distal_output, bool *release_arm_brake,
46 bool *claw_closed, control_loops::ArmStatus *status) {
Austin Schuhcb091712018-02-21 20:01:55 -080047 ::Eigen::Matrix<double, 2, 1> Y;
Austin Schuh96341532018-03-09 21:17:24 -080048 const bool outputs_disabled =
49 ((proximal_output == nullptr) || (distal_output == nullptr) ||
50 (release_arm_brake == nullptr) || (claw_closed == nullptr));
51
52 uint32_t filtered_goal = 0;
53 if (unsafe_goal != nullptr) {
54 filtered_goal = *unsafe_goal;
55 }
56
57 if (open_claw) {
58 claw_closed_ = false;
59 }
60 if (outputs_disabled) {
61 claw_closed_ = true;
62 }
Austin Schuhcb091712018-02-21 20:01:55 -080063
64 Y << position->proximal.encoder + proximal_offset_,
65 position->distal.encoder + distal_offset_;
66
67 proximal_zeroing_estimator_.UpdateEstimate(position->proximal);
68 distal_zeroing_estimator_.UpdateEstimate(position->distal);
69
70 if (proximal_output != nullptr) {
71 *proximal_output = 0.0;
72 }
73 if (distal_output != nullptr) {
74 *distal_output = 0.0;
75 }
76
77 arm_ekf_.Correct(Y, kDt());
78
79 if (::std::abs(arm_ekf_.X_hat(0) - follower_.theta(0)) <= 0.05 &&
80 ::std::abs(arm_ekf_.X_hat(2) - follower_.theta(1)) <= 0.05) {
81 close_enough_for_full_power_ = true;
82 }
83 if (::std::abs(arm_ekf_.X_hat(0) - follower_.theta(0)) >= 1.10 ||
84 ::std::abs(arm_ekf_.X_hat(2) - follower_.theta(1)) >= 1.10) {
85 close_enough_for_full_power_ = false;
86 }
87
88 switch (state_) {
89 case State::UNINITIALIZED:
90 // Wait in the uninitialized state until the intake is initialized.
91 LOG(DEBUG, "Uninitialized, waiting for intake\n");
92 state_ = State::ZEROING;
93 proximal_zeroing_estimator_.Reset();
94 distal_zeroing_estimator_.Reset();
Austin Schuhcb091712018-02-21 20:01:55 -080095 break;
96
97 case State::ZEROING:
98 // Zero by not moving.
99 if (proximal_zeroing_estimator_.zeroed() &&
100 distal_zeroing_estimator_.zeroed()) {
Austin Schuh96341532018-03-09 21:17:24 -0800101 state_ = State::DISABLED;
Austin Schuhcb091712018-02-21 20:01:55 -0800102
103 proximal_offset_ = proximal_zeroing_estimator_.offset();
104 distal_offset_ = distal_zeroing_estimator_.offset();
105
106 Y << position->proximal.encoder + proximal_offset_,
107 position->distal.encoder + distal_offset_;
108
109 // TODO(austin): Offset ekf rather than reset it. Since we aren't
110 // moving at this point, it's pretty safe to do this.
111 ::Eigen::Matrix<double, 4, 1> X;
112 X << Y(0), 0.0, Y(1), 0.0;
113 arm_ekf_.Reset(X);
114 } else {
115 break;
116 }
117
Austin Schuh96341532018-03-09 21:17:24 -0800118 case State::DISABLED:
119 if (!outputs_disabled) {
120 follower_.SwitchTrajectory(nullptr);
121 close_enough_for_full_power_ = false;
122 // TODO(austin): Nearest point at the end of Initialize.
123 // So, get a vector of all the points, loop through them, and find the
124 // closest one.
125 follower_.set_theta(ReadyAboveBoxPoint());
126 current_node_ = ReadyAboveBoxIndex();
127
128 state_ = State::GOTO_PATH;
129 } else {
130 break;
131 }
132
133 case State::GOTO_PATH:
134 if (outputs_disabled) {
135 state_ = State::DISABLED;
136 } else if (close_enough_for_full_power_) {
137 state_ = State::RUNNING;
138 grab_state_ = GrabState::NORMAL;
139 }
140 break;
141
Austin Schuhcb091712018-02-21 20:01:55 -0800142 case State::RUNNING:
143 // ESTOP if we hit the hard limits.
144 // TODO(austin): Pick some sane limits.
145 if (proximal_zeroing_estimator_.error() ||
146 distal_zeroing_estimator_.error()) {
147 LOG(ERROR, "Zeroing error ESTOP\n");
148 state_ = State::ESTOP;
Austin Schuh96341532018-03-09 21:17:24 -0800149 } else if (outputs_disabled) {
150 state_ = State::DISABLED;
Austin Schuhcb091712018-02-21 20:01:55 -0800151 }
152 break;
153
154 case State::ESTOP:
155 LOG(ERROR, "Estop\n");
156 break;
157 }
158
Austin Schuh96341532018-03-09 21:17:24 -0800159 const bool disable = outputs_disabled ||
160 (state_ != State::RUNNING && state_ != State::GOTO_PATH);
Austin Schuhcb091712018-02-21 20:01:55 -0800161 if (disable) {
162 close_enough_for_full_power_ = false;
163 }
164
Austin Schuh96341532018-03-09 21:17:24 -0800165 if (claw_closed_) {
166 if ((filtered_goal == ReadyAboveBoxIndex()) ||
167 (filtered_goal == TallBoxGrabIndex()) ||
168 (filtered_goal == ShortBoxGrabIndex())) {
169 filtered_goal = NeutralIndex();
170 }
171 }
172
173 // TODO(austin): Do we need to debounce box_back_beambreak_triggered ?
174 switch (grab_state_) {
175 case GrabState::NORMAL:
176 if (grab_box && !claw_closed_) {
177 grab_state_ = GrabState::WAIT_FOR_BOX;
178 } else {
179 break;
180 }
181 case GrabState::WAIT_FOR_BOX:
182 if (!grab_box) {
183 grab_state_ = GrabState::NORMAL;
184 } else {
185 if (AtState(ReadyAboveBoxIndex()) && NearEnd()) {
186 // We are being asked to grab the box, and the claw is near the box.
187 if (box_back_beambreak_triggered) {
188 // And we now see the box! Try for a tall box.
189 grab_state_ = GrabState::TALL_BOX;
190 }
191 }
192 }
193 break;
194 case GrabState::TALL_BOX:
195 if (!grab_box) {
196 grab_state_ = GrabState::NORMAL;
197 } else if (!box_back_beambreak_triggered) {
198 // Lost the box, go back to wait for it.
199 grab_state_ = GrabState::WAIT_FOR_BOX;
200 } else if (AtState(TallBoxGrabIndex()) && NearEnd()) {
201 // We are being asked to grab the box, and the claw is near the box.
202 if (claw_beambreak_triggered) {
203 grab_state_ = GrabState::CLAW_CLOSE;
204 // Snap time for the delay here.
205 claw_close_start_time_ = ::aos::monotonic_clock::now();
206 } else {
207 grab_state_ = GrabState::SHORT_BOX;
208 }
209 }
210 break;
211 case GrabState::SHORT_BOX:
212 if (!grab_box) {
213 grab_state_ = GrabState::NORMAL;
214 } else if (!box_back_beambreak_triggered) {
215 // Lost the box, go back to wait for it.
216 grab_state_ = GrabState::WAIT_FOR_BOX;
217 } else if (AtState(ShortBoxGrabIndex()) && NearEnd()) {
218 // We are being asked to grab the box, and the claw is near the box.
219 if (claw_beambreak_triggered) {
220 grab_state_ = GrabState::CLAW_CLOSE;
221 // Snap time for the delay here.
222 claw_close_start_time_ = ::aos::monotonic_clock::now();
223 } else {
224 grab_state_ = GrabState::WAIT_FOR_BOX;
225 }
226 }
227 break;
228 case GrabState::CLAW_CLOSE:
229 if (::aos::monotonic_clock::now() >
230 claw_close_start_time_ + ::std::chrono::milliseconds(500)) {
231 grab_state_ = GrabState::OPEN_INTAKE;
232 }
233 break;
234 case GrabState::OPEN_INTAKE:
235 if (intake_clear_of_box) {
236 grab_state_ = GrabState::NORMAL;
237 }
238 break;
239 }
240
241 // Now, based out our current state, go to the right state.
242 switch (grab_state_) {
243 case GrabState::NORMAL:
244 // Don't let the intake close fully with the claw closed.
245 // TODO(austin): If we want to transfer the box from the claw to the
246 // intake, we'll need to change this.
247 if (claw_closed_) {
248 max_intake_override_ = -0.5;
249 } else {
250 max_intake_override_ = 1000.0;
251 }
252 break;
253 case GrabState::WAIT_FOR_BOX:
254 filtered_goal = ReadyAboveBoxIndex();
255 claw_closed_ = false;
256 max_intake_override_ = 1000.0;
257 break;
258 case GrabState::TALL_BOX:
259 filtered_goal = TallBoxGrabIndex();
260 claw_closed_ = false;
261 max_intake_override_ = 1000.0;
262 break;
263 case GrabState::SHORT_BOX:
264 filtered_goal = ShortBoxGrabIndex();
265 claw_closed_ = false;
266 max_intake_override_ = 1000.0;
267 break;
268 case GrabState::CLAW_CLOSE:
269 // Don't move.
270 filtered_goal = current_node_;
271 claw_closed_ = true;
272 max_intake_override_ = 1000.0;
273 break;
274 case GrabState::OPEN_INTAKE:
275 // Don't move.
276 filtered_goal = current_node_;
277 claw_closed_ = true;
278 max_intake_override_ = -0.5;
279 break;
280 }
281
282 if (state_ == State::RUNNING && unsafe_goal != nullptr) {
283 if (current_node_ != filtered_goal) {
284 LOG(INFO, "Goal is different\n");
285 if (filtered_goal >= search_graph_.num_vertexes()) {
286 LOG(ERROR, "goal node out of range ESTOP\n");
287 state_ = State::ESTOP;
288 } else if (follower_.path_distance_to_go() > 1e-3) {
289 // Still on the old path segment. Can't change yet.
290 } else {
291 search_graph_.SetGoal(filtered_goal);
292
293 size_t min_edge = 0;
294 double min_cost = ::std::numeric_limits<double>::infinity();
295 for (const SearchGraph::HalfEdge &edge :
296 search_graph_.Neighbors(current_node_)) {
297 const double cost = search_graph_.GetCostToGoal(edge.dest);
298 if (cost < min_cost) {
299 min_edge = edge.edge_id;
300 min_cost = cost;
301 }
302 }
303 // Ok, now we know which edge we are on. Figure out the path and
304 // trajectory.
305 const SearchGraph::Edge &next_edge = search_graph_.edges()[min_edge];
306 LOG(INFO, "Switching from node %d to %d along edge %d\n",
307 static_cast<int>(current_node_), static_cast<int>(next_edge.end),
308 static_cast<int>(min_edge));
309 follower_.SwitchTrajectory(&trajectories_[min_edge]);
310 current_node_ = next_edge.end;
311 }
312 }
313 }
314
Austin Schuhcb091712018-02-21 20:01:55 -0800315 follower_.Update(
316 arm_ekf_.X_hat(), disable, kDt(), kVMax(),
317 close_enough_for_full_power_ ? kOperatingVoltage() : kPathlessVMax());
318 LOG(INFO, "Max voltage: %f\n",
319 close_enough_for_full_power_ ? kOperatingVoltage() : kPathlessVMax());
320 status->goal_theta0 = follower_.theta(0);
321 status->goal_theta1 = follower_.theta(1);
322 status->goal_omega0 = follower_.omega(0);
323 status->goal_omega1 = follower_.omega(1);
324
325 status->theta0 = arm_ekf_.X_hat(0);
326 status->theta1 = arm_ekf_.X_hat(2);
327 status->omega0 = arm_ekf_.X_hat(1);
328 status->omega1 = arm_ekf_.X_hat(3);
329 status->voltage_error0 = arm_ekf_.X_hat(4);
330 status->voltage_error1 = arm_ekf_.X_hat(5);
331
332 if (!disable) {
333 *proximal_output = ::std::max(
334 -kOperatingVoltage(), ::std::min(kOperatingVoltage(), follower_.U(0)));
335 *distal_output = ::std::max(
336 -kOperatingVoltage(), ::std::min(kOperatingVoltage(), follower_.U(1)));
337 *release_arm_brake = true;
Austin Schuh96341532018-03-09 21:17:24 -0800338 *claw_closed = claw_closed_;
Austin Schuhcb091712018-02-21 20:01:55 -0800339 }
340
341 status->proximal_estimator_state =
342 proximal_zeroing_estimator_.GetEstimatorState();
343 status->distal_estimator_state =
344 distal_zeroing_estimator_.GetEstimatorState();
345
346 status->path_distance_to_go = follower_.path_distance_to_go();
347 status->current_node = current_node_;
348
349 status->zeroed = (proximal_zeroing_estimator_.zeroed() &&
350 distal_zeroing_estimator_.zeroed());
351 status->estopped = (state_ == State::ESTOP);
352 status->state = static_cast<int32_t>(state_);
Austin Schuh96341532018-03-09 21:17:24 -0800353 status->grab_state = static_cast<int32_t>(grab_state_);
Austin Schuhcb091712018-02-21 20:01:55 -0800354 status->failed_solutions = follower_.failed_solutions();
355
356 arm_ekf_.Predict(follower_.U(), kDt());
357}
358
359} // namespace arm
360} // namespace superstructure
361} // namespace control_loops
362} // namespace y2018