blob: c283f196c6d56219bb5bf64d6eb57b726648d158 [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,
47 bool *claw_closed, control_loops::ArmStatus *status) {
Austin Schuhcb091712018-02-21 20:01:55 -080048 ::Eigen::Matrix<double, 2, 1> Y;
Austin Schuh96341532018-03-09 21:17:24 -080049 const bool outputs_disabled =
50 ((proximal_output == nullptr) || (distal_output == nullptr) ||
51 (release_arm_brake == nullptr) || (claw_closed == nullptr));
52
53 uint32_t filtered_goal = 0;
54 if (unsafe_goal != nullptr) {
55 filtered_goal = *unsafe_goal;
56 }
57
58 if (open_claw) {
59 claw_closed_ = false;
60 }
61 if (outputs_disabled) {
62 claw_closed_ = true;
63 }
Austin Schuhcb091712018-02-21 20:01:55 -080064
65 Y << position->proximal.encoder + proximal_offset_,
66 position->distal.encoder + distal_offset_;
67
68 proximal_zeroing_estimator_.UpdateEstimate(position->proximal);
69 distal_zeroing_estimator_.UpdateEstimate(position->distal);
70
71 if (proximal_output != nullptr) {
72 *proximal_output = 0.0;
73 }
74 if (distal_output != nullptr) {
75 *distal_output = 0.0;
76 }
77
78 arm_ekf_.Correct(Y, kDt());
79
80 if (::std::abs(arm_ekf_.X_hat(0) - follower_.theta(0)) <= 0.05 &&
81 ::std::abs(arm_ekf_.X_hat(2) - follower_.theta(1)) <= 0.05) {
82 close_enough_for_full_power_ = true;
83 }
84 if (::std::abs(arm_ekf_.X_hat(0) - follower_.theta(0)) >= 1.10 ||
85 ::std::abs(arm_ekf_.X_hat(2) - follower_.theta(1)) >= 1.10) {
86 close_enough_for_full_power_ = false;
87 }
88
89 switch (state_) {
90 case State::UNINITIALIZED:
91 // Wait in the uninitialized state until the intake is initialized.
92 LOG(DEBUG, "Uninitialized, waiting for intake\n");
93 state_ = State::ZEROING;
94 proximal_zeroing_estimator_.Reset();
95 distal_zeroing_estimator_.Reset();
Austin Schuhcb091712018-02-21 20:01:55 -080096 break;
97
98 case State::ZEROING:
99 // Zero by not moving.
100 if (proximal_zeroing_estimator_.zeroed() &&
101 distal_zeroing_estimator_.zeroed()) {
Austin Schuh96341532018-03-09 21:17:24 -0800102 state_ = State::DISABLED;
Austin Schuhcb091712018-02-21 20:01:55 -0800103
104 proximal_offset_ = proximal_zeroing_estimator_.offset();
105 distal_offset_ = distal_zeroing_estimator_.offset();
106
107 Y << position->proximal.encoder + proximal_offset_,
108 position->distal.encoder + distal_offset_;
109
110 // TODO(austin): Offset ekf rather than reset it. Since we aren't
111 // moving at this point, it's pretty safe to do this.
112 ::Eigen::Matrix<double, 4, 1> X;
113 X << Y(0), 0.0, Y(1), 0.0;
114 arm_ekf_.Reset(X);
115 } else {
116 break;
117 }
118
Austin Schuhb874fd32018-03-05 00:27:10 -0800119 case State::DISABLED: {
120 follower_.SwitchTrajectory(nullptr);
121 close_enough_for_full_power_ = false;
Austin Schuh96341532018-03-09 21:17:24 -0800122
Austin Schuhb874fd32018-03-05 00:27:10 -0800123 const ::Eigen::Matrix<double, 2, 1> current_theta =
124 (::Eigen::Matrix<double, 2, 1>() << arm_ekf_.X_hat(0),
125 arm_ekf_.X_hat(2))
126 .finished();
127 uint32_t best_index = 0;
128 double best_distance = (points_[0] - current_theta).norm();
129 uint32_t current_index = 0;
130 for (const ::Eigen::Matrix<double, 2, 1> &point : points_) {
131 const double new_distance = (point - current_theta).norm();
132 if (new_distance < best_distance) {
133 best_distance = new_distance;
134 best_index = current_index;
135 }
136 ++current_index;
137 }
138 follower_.set_theta(points_[best_index]);
139 current_node_ = best_index;
140
141 if (!outputs_disabled) {
Austin Schuh96341532018-03-09 21:17:24 -0800142 state_ = State::GOTO_PATH;
143 } else {
144 break;
145 }
Austin Schuhb874fd32018-03-05 00:27:10 -0800146 }
Austin Schuh96341532018-03-09 21:17:24 -0800147
148 case State::GOTO_PATH:
149 if (outputs_disabled) {
150 state_ = State::DISABLED;
151 } else if (close_enough_for_full_power_) {
152 state_ = State::RUNNING;
153 grab_state_ = GrabState::NORMAL;
154 }
155 break;
156
Austin Schuhcb091712018-02-21 20:01:55 -0800157 case State::RUNNING:
158 // ESTOP if we hit the hard limits.
159 // TODO(austin): Pick some sane limits.
160 if (proximal_zeroing_estimator_.error() ||
161 distal_zeroing_estimator_.error()) {
162 LOG(ERROR, "Zeroing error ESTOP\n");
163 state_ = State::ESTOP;
Austin Schuh96341532018-03-09 21:17:24 -0800164 } else if (outputs_disabled) {
165 state_ = State::DISABLED;
Austin Schuhcb091712018-02-21 20:01:55 -0800166 }
167 break;
168
169 case State::ESTOP:
170 LOG(ERROR, "Estop\n");
171 break;
172 }
173
Austin Schuh96341532018-03-09 21:17:24 -0800174 const bool disable = outputs_disabled ||
175 (state_ != State::RUNNING && state_ != State::GOTO_PATH);
Austin Schuhcb091712018-02-21 20:01:55 -0800176 if (disable) {
177 close_enough_for_full_power_ = false;
178 }
179
Austin Schuhb874fd32018-03-05 00:27:10 -0800180 // TODO(austin): Do we need to debounce box_back_beambreak_triggered ?
Austin Schuh96341532018-03-09 21:17:24 -0800181 if (claw_closed_) {
182 if ((filtered_goal == ReadyAboveBoxIndex()) ||
183 (filtered_goal == TallBoxGrabIndex()) ||
184 (filtered_goal == ShortBoxGrabIndex())) {
185 filtered_goal = NeutralIndex();
186 }
187 }
188
189 // TODO(austin): Do we need to debounce box_back_beambreak_triggered ?
190 switch (grab_state_) {
191 case GrabState::NORMAL:
192 if (grab_box && !claw_closed_) {
193 grab_state_ = GrabState::WAIT_FOR_BOX;
194 } else {
195 break;
196 }
197 case GrabState::WAIT_FOR_BOX:
198 if (!grab_box) {
199 grab_state_ = GrabState::NORMAL;
200 } else {
201 if (AtState(ReadyAboveBoxIndex()) && NearEnd()) {
202 // We are being asked to grab the box, and the claw is near the box.
203 if (box_back_beambreak_triggered) {
204 // And we now see the box! Try for a tall box.
205 grab_state_ = GrabState::TALL_BOX;
206 }
207 }
208 }
209 break;
210 case GrabState::TALL_BOX:
211 if (!grab_box) {
212 grab_state_ = GrabState::NORMAL;
Austin Schuh96341532018-03-09 21:17:24 -0800213 } else if (AtState(TallBoxGrabIndex()) && NearEnd()) {
214 // We are being asked to grab the box, and the claw is near the box.
215 if (claw_beambreak_triggered) {
216 grab_state_ = GrabState::CLAW_CLOSE;
217 // Snap time for the delay here.
218 claw_close_start_time_ = ::aos::monotonic_clock::now();
219 } else {
220 grab_state_ = GrabState::SHORT_BOX;
221 }
222 }
223 break;
224 case GrabState::SHORT_BOX:
225 if (!grab_box) {
226 grab_state_ = GrabState::NORMAL;
Austin Schuh96341532018-03-09 21:17:24 -0800227 } else if (AtState(ShortBoxGrabIndex()) && 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::WAIT_FOR_BOX;
235 }
236 }
237 break;
238 case GrabState::CLAW_CLOSE:
239 if (::aos::monotonic_clock::now() >
Austin Schuhb874fd32018-03-05 00:27:10 -0800240 claw_close_start_time_ + ::std::chrono::milliseconds(300)) {
Austin Schuh96341532018-03-09 21:17:24 -0800241 grab_state_ = GrabState::OPEN_INTAKE;
242 }
243 break;
244 case GrabState::OPEN_INTAKE:
245 if (intake_clear_of_box) {
246 grab_state_ = GrabState::NORMAL;
247 }
248 break;
249 }
250
251 // Now, based out our current state, go to the right state.
252 switch (grab_state_) {
253 case GrabState::NORMAL:
254 // Don't let the intake close fully with the claw closed.
255 // TODO(austin): If we want to transfer the box from the claw to the
256 // intake, we'll need to change this.
257 if (claw_closed_) {
258 max_intake_override_ = -0.5;
259 } else {
260 max_intake_override_ = 1000.0;
261 }
262 break;
263 case GrabState::WAIT_FOR_BOX:
264 filtered_goal = ReadyAboveBoxIndex();
265 claw_closed_ = false;
266 max_intake_override_ = 1000.0;
267 break;
268 case GrabState::TALL_BOX:
269 filtered_goal = TallBoxGrabIndex();
270 claw_closed_ = false;
271 max_intake_override_ = 1000.0;
272 break;
273 case GrabState::SHORT_BOX:
274 filtered_goal = ShortBoxGrabIndex();
275 claw_closed_ = false;
276 max_intake_override_ = 1000.0;
277 break;
278 case GrabState::CLAW_CLOSE:
279 // Don't move.
280 filtered_goal = current_node_;
281 claw_closed_ = true;
282 max_intake_override_ = 1000.0;
283 break;
284 case GrabState::OPEN_INTAKE:
285 // Don't move.
286 filtered_goal = current_node_;
287 claw_closed_ = true;
288 max_intake_override_ = -0.5;
289 break;
290 }
291
292 if (state_ == State::RUNNING && unsafe_goal != nullptr) {
293 if (current_node_ != filtered_goal) {
294 LOG(INFO, "Goal is different\n");
295 if (filtered_goal >= search_graph_.num_vertexes()) {
296 LOG(ERROR, "goal node out of range ESTOP\n");
297 state_ = State::ESTOP;
298 } else if (follower_.path_distance_to_go() > 1e-3) {
299 // Still on the old path segment. Can't change yet.
300 } else {
301 search_graph_.SetGoal(filtered_goal);
302
303 size_t min_edge = 0;
304 double min_cost = ::std::numeric_limits<double>::infinity();
305 for (const SearchGraph::HalfEdge &edge :
306 search_graph_.Neighbors(current_node_)) {
307 const double cost = search_graph_.GetCostToGoal(edge.dest);
308 if (cost < min_cost) {
309 min_edge = edge.edge_id;
310 min_cost = cost;
311 }
312 }
313 // Ok, now we know which edge we are on. Figure out the path and
314 // trajectory.
315 const SearchGraph::Edge &next_edge = search_graph_.edges()[min_edge];
316 LOG(INFO, "Switching from node %d to %d along edge %d\n",
317 static_cast<int>(current_node_), static_cast<int>(next_edge.end),
318 static_cast<int>(min_edge));
319 follower_.SwitchTrajectory(&trajectories_[min_edge]);
320 current_node_ = next_edge.end;
321 }
322 }
323 }
324
Austin Schuhcb091712018-02-21 20:01:55 -0800325 follower_.Update(
326 arm_ekf_.X_hat(), disable, kDt(), kVMax(),
327 close_enough_for_full_power_ ? kOperatingVoltage() : kPathlessVMax());
328 LOG(INFO, "Max voltage: %f\n",
329 close_enough_for_full_power_ ? kOperatingVoltage() : kPathlessVMax());
330 status->goal_theta0 = follower_.theta(0);
331 status->goal_theta1 = follower_.theta(1);
332 status->goal_omega0 = follower_.omega(0);
333 status->goal_omega1 = follower_.omega(1);
334
335 status->theta0 = arm_ekf_.X_hat(0);
336 status->theta1 = arm_ekf_.X_hat(2);
337 status->omega0 = arm_ekf_.X_hat(1);
338 status->omega1 = arm_ekf_.X_hat(3);
339 status->voltage_error0 = arm_ekf_.X_hat(4);
340 status->voltage_error1 = arm_ekf_.X_hat(5);
341
342 if (!disable) {
343 *proximal_output = ::std::max(
344 -kOperatingVoltage(), ::std::min(kOperatingVoltage(), follower_.U(0)));
345 *distal_output = ::std::max(
346 -kOperatingVoltage(), ::std::min(kOperatingVoltage(), follower_.U(1)));
347 *release_arm_brake = true;
Austin Schuh96341532018-03-09 21:17:24 -0800348 *claw_closed = claw_closed_;
Austin Schuhcb091712018-02-21 20:01:55 -0800349 }
350
351 status->proximal_estimator_state =
352 proximal_zeroing_estimator_.GetEstimatorState();
353 status->distal_estimator_state =
354 distal_zeroing_estimator_.GetEstimatorState();
355
356 status->path_distance_to_go = follower_.path_distance_to_go();
357 status->current_node = current_node_;
358
359 status->zeroed = (proximal_zeroing_estimator_.zeroed() &&
360 distal_zeroing_estimator_.zeroed());
361 status->estopped = (state_ == State::ESTOP);
362 status->state = static_cast<int32_t>(state_);
Austin Schuh96341532018-03-09 21:17:24 -0800363 status->grab_state = static_cast<int32_t>(grab_state_);
Austin Schuhcb091712018-02-21 20:01:55 -0800364 status->failed_solutions = follower_.failed_solutions();
365
366 arm_ekf_.Predict(follower_.U(), kDt());
367}
368
369} // namespace arm
370} // namespace superstructure
371} // namespace control_loops
372} // namespace y2018