blob: 4912359606e6acfe075fbdb0fac8451c6811a224 [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 Schuhb874fd32018-03-05 00:27:10 -080029 follower_(ReadyAboveBoxPoint()),
30 points_(PointList()) {
Austin Schuh7dfccf62018-03-03 21:28:14 -080031 int i = 0;
32 for (const auto &trajectory : trajectories_) {
33 LOG(INFO, "trajectory length for edge node %d: %f\n", i,
34 trajectory.path().length());
35 ++i;
Austin Schuhcb091712018-02-21 20:01:55 -080036 }
37}
38
39void Arm::Reset() { state_ = State::UNINITIALIZED; }
40
Austin Schuh96341532018-03-09 21:17:24 -080041void Arm::Iterate(const uint32_t *unsafe_goal, bool grab_box, bool open_claw,
Austin Schuhcb091712018-02-21 20:01:55 -080042 const control_loops::ArmPosition *position,
Austin Schuh96341532018-03-09 21:17:24 -080043 const bool claw_beambreak_triggered,
44 const bool box_back_beambreak_triggered,
45 const bool intake_clear_of_box, double *proximal_output,
46 double *distal_output, bool *release_arm_brake,
Austin Schuh17e484e2018-03-11 01:11:36 -080047 bool *claw_closed, control_loops::ArmStatus *status,
48 bool suicide) {
Austin Schuhcb091712018-02-21 20:01:55 -080049 ::Eigen::Matrix<double, 2, 1> Y;
Austin Schuh96341532018-03-09 21:17:24 -080050 const bool outputs_disabled =
51 ((proximal_output == nullptr) || (distal_output == nullptr) ||
52 (release_arm_brake == nullptr) || (claw_closed == nullptr));
53
54 uint32_t filtered_goal = 0;
55 if (unsafe_goal != nullptr) {
56 filtered_goal = *unsafe_goal;
57 }
58
59 if (open_claw) {
60 claw_closed_ = false;
61 }
62 if (outputs_disabled) {
63 claw_closed_ = true;
64 }
Austin Schuhcb091712018-02-21 20:01:55 -080065
66 Y << position->proximal.encoder + proximal_offset_,
67 position->distal.encoder + distal_offset_;
68
69 proximal_zeroing_estimator_.UpdateEstimate(position->proximal);
70 distal_zeroing_estimator_.UpdateEstimate(position->distal);
71
72 if (proximal_output != nullptr) {
73 *proximal_output = 0.0;
74 }
75 if (distal_output != nullptr) {
76 *distal_output = 0.0;
77 }
78
79 arm_ekf_.Correct(Y, kDt());
80
81 if (::std::abs(arm_ekf_.X_hat(0) - follower_.theta(0)) <= 0.05 &&
82 ::std::abs(arm_ekf_.X_hat(2) - follower_.theta(1)) <= 0.05) {
83 close_enough_for_full_power_ = true;
84 }
85 if (::std::abs(arm_ekf_.X_hat(0) - follower_.theta(0)) >= 1.10 ||
86 ::std::abs(arm_ekf_.X_hat(2) - follower_.theta(1)) >= 1.10) {
87 close_enough_for_full_power_ = false;
88 }
89
90 switch (state_) {
91 case State::UNINITIALIZED:
92 // Wait in the uninitialized state until the intake is initialized.
93 LOG(DEBUG, "Uninitialized, waiting for intake\n");
94 state_ = State::ZEROING;
95 proximal_zeroing_estimator_.Reset();
96 distal_zeroing_estimator_.Reset();
Austin Schuhcb091712018-02-21 20:01:55 -080097 break;
98
99 case State::ZEROING:
100 // Zero by not moving.
101 if (proximal_zeroing_estimator_.zeroed() &&
102 distal_zeroing_estimator_.zeroed()) {
Austin Schuh96341532018-03-09 21:17:24 -0800103 state_ = State::DISABLED;
Austin Schuhcb091712018-02-21 20:01:55 -0800104
105 proximal_offset_ = proximal_zeroing_estimator_.offset();
106 distal_offset_ = distal_zeroing_estimator_.offset();
107
108 Y << position->proximal.encoder + proximal_offset_,
109 position->distal.encoder + distal_offset_;
110
111 // TODO(austin): Offset ekf rather than reset it. Since we aren't
112 // moving at this point, it's pretty safe to do this.
113 ::Eigen::Matrix<double, 4, 1> X;
114 X << Y(0), 0.0, Y(1), 0.0;
115 arm_ekf_.Reset(X);
116 } else {
117 break;
118 }
119
Austin Schuhb874fd32018-03-05 00:27:10 -0800120 case State::DISABLED: {
121 follower_.SwitchTrajectory(nullptr);
122 close_enough_for_full_power_ = false;
Austin Schuh96341532018-03-09 21:17:24 -0800123
Austin Schuhb874fd32018-03-05 00:27:10 -0800124 const ::Eigen::Matrix<double, 2, 1> current_theta =
125 (::Eigen::Matrix<double, 2, 1>() << arm_ekf_.X_hat(0),
126 arm_ekf_.X_hat(2))
127 .finished();
128 uint32_t best_index = 0;
129 double best_distance = (points_[0] - current_theta).norm();
130 uint32_t current_index = 0;
131 for (const ::Eigen::Matrix<double, 2, 1> &point : points_) {
132 const double new_distance = (point - current_theta).norm();
133 if (new_distance < best_distance) {
134 best_distance = new_distance;
135 best_index = current_index;
136 }
137 ++current_index;
138 }
139 follower_.set_theta(points_[best_index]);
140 current_node_ = best_index;
141
142 if (!outputs_disabled) {
Austin Schuh96341532018-03-09 21:17:24 -0800143 state_ = State::GOTO_PATH;
144 } else {
145 break;
146 }
Austin Schuhb874fd32018-03-05 00:27:10 -0800147 }
Austin Schuh96341532018-03-09 21:17:24 -0800148
149 case State::GOTO_PATH:
150 if (outputs_disabled) {
151 state_ = State::DISABLED;
152 } else if (close_enough_for_full_power_) {
153 state_ = State::RUNNING;
154 grab_state_ = GrabState::NORMAL;
155 }
156 break;
157
Austin Schuhcb091712018-02-21 20:01:55 -0800158 case State::RUNNING:
159 // ESTOP if we hit the hard limits.
160 // TODO(austin): Pick some sane limits.
161 if (proximal_zeroing_estimator_.error() ||
162 distal_zeroing_estimator_.error()) {
163 LOG(ERROR, "Zeroing error ESTOP\n");
164 state_ = State::ESTOP;
Austin Schuh96341532018-03-09 21:17:24 -0800165 } else if (outputs_disabled) {
166 state_ = State::DISABLED;
Austin Schuh17e484e2018-03-11 01:11:36 -0800167 } else if (suicide) {
168 state_ = State::PREP_CLIMB;
169 climb_count_ = 50;
170 }
171 break;
172
173 case State::PREP_CLIMB:
174 --climb_count_;
175 if (climb_count_ <= 0) {
176 state_ = State::ESTOP;
177 } else if (!suicide) {
178 state_ = State::RUNNING;
Austin Schuhcb091712018-02-21 20:01:55 -0800179 }
180 break;
181
182 case State::ESTOP:
183 LOG(ERROR, "Estop\n");
184 break;
185 }
186
Austin Schuh17e484e2018-03-11 01:11:36 -0800187 const bool disable = outputs_disabled || (state_ != State::RUNNING &&
188 state_ != State::GOTO_PATH &&
189 state_ != State::PREP_CLIMB);
Austin Schuhcb091712018-02-21 20:01:55 -0800190 if (disable) {
191 close_enough_for_full_power_ = false;
192 }
193
Austin Schuhb874fd32018-03-05 00:27:10 -0800194 // TODO(austin): Do we need to debounce box_back_beambreak_triggered ?
Austin Schuh96341532018-03-09 21:17:24 -0800195 if (claw_closed_) {
196 if ((filtered_goal == ReadyAboveBoxIndex()) ||
197 (filtered_goal == TallBoxGrabIndex()) ||
198 (filtered_goal == ShortBoxGrabIndex())) {
199 filtered_goal = NeutralIndex();
200 }
201 }
202
203 // TODO(austin): Do we need to debounce box_back_beambreak_triggered ?
204 switch (grab_state_) {
205 case GrabState::NORMAL:
206 if (grab_box && !claw_closed_) {
207 grab_state_ = GrabState::WAIT_FOR_BOX;
208 } else {
209 break;
210 }
211 case GrabState::WAIT_FOR_BOX:
212 if (!grab_box) {
213 grab_state_ = GrabState::NORMAL;
214 } else {
215 if (AtState(ReadyAboveBoxIndex()) && NearEnd()) {
216 // We are being asked to grab the box, and the claw is near the box.
217 if (box_back_beambreak_triggered) {
218 // And we now see the box! Try for a tall box.
219 grab_state_ = GrabState::TALL_BOX;
220 }
221 }
222 }
223 break;
224 case GrabState::TALL_BOX:
225 if (!grab_box) {
226 grab_state_ = GrabState::NORMAL;
Austin Schuh96341532018-03-09 21:17:24 -0800227 } else if (AtState(TallBoxGrabIndex()) && NearEnd()) {
228 // We are being asked to grab the box, and the claw is near the box.
229 if (claw_beambreak_triggered) {
230 grab_state_ = GrabState::CLAW_CLOSE;
231 // Snap time for the delay here.
232 claw_close_start_time_ = ::aos::monotonic_clock::now();
233 } else {
234 grab_state_ = GrabState::SHORT_BOX;
235 }
236 }
237 break;
238 case GrabState::SHORT_BOX:
239 if (!grab_box) {
240 grab_state_ = GrabState::NORMAL;
Austin Schuh96341532018-03-09 21:17:24 -0800241 } else if (AtState(ShortBoxGrabIndex()) && NearEnd()) {
242 // We are being asked to grab the box, and the claw is near the box.
243 if (claw_beambreak_triggered) {
244 grab_state_ = GrabState::CLAW_CLOSE;
245 // Snap time for the delay here.
246 claw_close_start_time_ = ::aos::monotonic_clock::now();
247 } else {
248 grab_state_ = GrabState::WAIT_FOR_BOX;
249 }
250 }
251 break;
252 case GrabState::CLAW_CLOSE:
253 if (::aos::monotonic_clock::now() >
Austin Schuhb874fd32018-03-05 00:27:10 -0800254 claw_close_start_time_ + ::std::chrono::milliseconds(300)) {
Austin Schuh96341532018-03-09 21:17:24 -0800255 grab_state_ = GrabState::OPEN_INTAKE;
256 }
257 break;
258 case GrabState::OPEN_INTAKE:
259 if (intake_clear_of_box) {
260 grab_state_ = GrabState::NORMAL;
261 }
262 break;
263 }
264
265 // Now, based out our current state, go to the right state.
266 switch (grab_state_) {
267 case GrabState::NORMAL:
268 // Don't let the intake close fully with the claw closed.
269 // TODO(austin): If we want to transfer the box from the claw to the
270 // intake, we'll need to change this.
271 if (claw_closed_) {
272 max_intake_override_ = -0.5;
273 } else {
274 max_intake_override_ = 1000.0;
275 }
276 break;
277 case GrabState::WAIT_FOR_BOX:
278 filtered_goal = ReadyAboveBoxIndex();
279 claw_closed_ = false;
280 max_intake_override_ = 1000.0;
281 break;
282 case GrabState::TALL_BOX:
283 filtered_goal = TallBoxGrabIndex();
284 claw_closed_ = false;
285 max_intake_override_ = 1000.0;
286 break;
287 case GrabState::SHORT_BOX:
288 filtered_goal = ShortBoxGrabIndex();
289 claw_closed_ = false;
290 max_intake_override_ = 1000.0;
291 break;
292 case GrabState::CLAW_CLOSE:
293 // Don't move.
294 filtered_goal = current_node_;
295 claw_closed_ = true;
296 max_intake_override_ = 1000.0;
297 break;
298 case GrabState::OPEN_INTAKE:
299 // Don't move.
300 filtered_goal = current_node_;
301 claw_closed_ = true;
302 max_intake_override_ = -0.5;
303 break;
304 }
305
306 if (state_ == State::RUNNING && unsafe_goal != nullptr) {
307 if (current_node_ != filtered_goal) {
308 LOG(INFO, "Goal is different\n");
309 if (filtered_goal >= search_graph_.num_vertexes()) {
310 LOG(ERROR, "goal node out of range ESTOP\n");
311 state_ = State::ESTOP;
312 } else if (follower_.path_distance_to_go() > 1e-3) {
313 // Still on the old path segment. Can't change yet.
314 } else {
315 search_graph_.SetGoal(filtered_goal);
316
317 size_t min_edge = 0;
318 double min_cost = ::std::numeric_limits<double>::infinity();
319 for (const SearchGraph::HalfEdge &edge :
320 search_graph_.Neighbors(current_node_)) {
321 const double cost = search_graph_.GetCostToGoal(edge.dest);
322 if (cost < min_cost) {
323 min_edge = edge.edge_id;
324 min_cost = cost;
325 }
326 }
327 // Ok, now we know which edge we are on. Figure out the path and
328 // trajectory.
329 const SearchGraph::Edge &next_edge = search_graph_.edges()[min_edge];
330 LOG(INFO, "Switching from node %d to %d along edge %d\n",
331 static_cast<int>(current_node_), static_cast<int>(next_edge.end),
332 static_cast<int>(min_edge));
333 follower_.SwitchTrajectory(&trajectories_[min_edge]);
334 current_node_ = next_edge.end;
335 }
336 }
337 }
338
Austin Schuh345a3732018-03-21 20:49:32 -0700339 const double max_operating_voltage =
340 close_enough_for_full_power_
341 ? kOperatingVoltage()
342 : (state_ == State::GOTO_PATH ? kGotoPathVMax() : kPathlessVMax());
343 follower_.Update(arm_ekf_.X_hat(), disable, kDt(), kVMax(),
344 max_operating_voltage);
345 LOG(INFO, "Max voltage: %f\n", max_operating_voltage);
Austin Schuhcb091712018-02-21 20:01:55 -0800346 status->goal_theta0 = follower_.theta(0);
347 status->goal_theta1 = follower_.theta(1);
348 status->goal_omega0 = follower_.omega(0);
349 status->goal_omega1 = follower_.omega(1);
350
351 status->theta0 = arm_ekf_.X_hat(0);
352 status->theta1 = arm_ekf_.X_hat(2);
353 status->omega0 = arm_ekf_.X_hat(1);
354 status->omega1 = arm_ekf_.X_hat(3);
355 status->voltage_error0 = arm_ekf_.X_hat(4);
356 status->voltage_error1 = arm_ekf_.X_hat(5);
357
358 if (!disable) {
359 *proximal_output = ::std::max(
360 -kOperatingVoltage(), ::std::min(kOperatingVoltage(), follower_.U(0)));
361 *distal_output = ::std::max(
362 -kOperatingVoltage(), ::std::min(kOperatingVoltage(), follower_.U(1)));
Austin Schuh17e484e2018-03-11 01:11:36 -0800363 if (state_ != State::PREP_CLIMB) {
364 *release_arm_brake = true;
365 } else {
366 *release_arm_brake = false;
367 }
Austin Schuh96341532018-03-09 21:17:24 -0800368 *claw_closed = claw_closed_;
Austin Schuhcb091712018-02-21 20:01:55 -0800369 }
370
371 status->proximal_estimator_state =
372 proximal_zeroing_estimator_.GetEstimatorState();
373 status->distal_estimator_state =
374 distal_zeroing_estimator_.GetEstimatorState();
375
376 status->path_distance_to_go = follower_.path_distance_to_go();
377 status->current_node = current_node_;
378
379 status->zeroed = (proximal_zeroing_estimator_.zeroed() &&
380 distal_zeroing_estimator_.zeroed());
381 status->estopped = (state_ == State::ESTOP);
382 status->state = static_cast<int32_t>(state_);
Austin Schuh96341532018-03-09 21:17:24 -0800383 status->grab_state = static_cast<int32_t>(grab_state_);
Austin Schuhcb091712018-02-21 20:01:55 -0800384 status->failed_solutions = follower_.failed_solutions();
385
386 arm_ekf_.Predict(follower_.U(), kDt());
387}
388
389} // namespace arm
390} // namespace superstructure
391} // namespace control_loops
392} // namespace y2018