blob: d236efe6bc0009e039c2430377881852a9277096 [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
40void Arm::Iterate(const uint32_t *unsafe_goal,
41 const control_loops::ArmPosition *position,
42 double *proximal_output, double *distal_output,
43 bool *release_arm_brake, control_loops::ArmStatus *status) {
44 ::Eigen::Matrix<double, 2, 1> Y;
45
46 Y << position->proximal.encoder + proximal_offset_,
47 position->distal.encoder + distal_offset_;
48
49 proximal_zeroing_estimator_.UpdateEstimate(position->proximal);
50 distal_zeroing_estimator_.UpdateEstimate(position->distal);
51
52 if (proximal_output != nullptr) {
53 *proximal_output = 0.0;
54 }
55 if (distal_output != nullptr) {
56 *distal_output = 0.0;
57 }
58
59 arm_ekf_.Correct(Y, kDt());
60
61 if (::std::abs(arm_ekf_.X_hat(0) - follower_.theta(0)) <= 0.05 &&
62 ::std::abs(arm_ekf_.X_hat(2) - follower_.theta(1)) <= 0.05) {
63 close_enough_for_full_power_ = true;
64 }
65 if (::std::abs(arm_ekf_.X_hat(0) - follower_.theta(0)) >= 1.10 ||
66 ::std::abs(arm_ekf_.X_hat(2) - follower_.theta(1)) >= 1.10) {
67 close_enough_for_full_power_ = false;
68 }
69
70 switch (state_) {
71 case State::UNINITIALIZED:
72 // Wait in the uninitialized state until the intake is initialized.
73 LOG(DEBUG, "Uninitialized, waiting for intake\n");
74 state_ = State::ZEROING;
75 proximal_zeroing_estimator_.Reset();
76 distal_zeroing_estimator_.Reset();
77 // TODO(austin): Go to the nearest node. For now, we are always going to
78 // go to node 0.
79 break;
80
81 case State::ZEROING:
82 // Zero by not moving.
83 if (proximal_zeroing_estimator_.zeroed() &&
84 distal_zeroing_estimator_.zeroed()) {
85 state_ = State::RUNNING;
86
87 proximal_offset_ = proximal_zeroing_estimator_.offset();
88 distal_offset_ = distal_zeroing_estimator_.offset();
89
90 Y << position->proximal.encoder + proximal_offset_,
91 position->distal.encoder + distal_offset_;
92
93 // TODO(austin): Offset ekf rather than reset it. Since we aren't
94 // moving at this point, it's pretty safe to do this.
95 ::Eigen::Matrix<double, 4, 1> X;
96 X << Y(0), 0.0, Y(1), 0.0;
97 arm_ekf_.Reset(X);
98 } else {
99 break;
100 }
101
102 case State::RUNNING:
103 // ESTOP if we hit the hard limits.
104 // TODO(austin): Pick some sane limits.
105 if (proximal_zeroing_estimator_.error() ||
106 distal_zeroing_estimator_.error()) {
107 LOG(ERROR, "Zeroing error ESTOP\n");
108 state_ = State::ESTOP;
109 } else if (unsafe_goal != nullptr) {
110 if (!follower_.has_path()) {
Austin Schuh7dfccf62018-03-03 21:28:14 -0800111 // TODO(austin): Nearest point at the end of Initialize.
112 // So, get a vector of all the points, loop through them, and find the
113 // closest one.
114
Austin Schuhcb091712018-02-21 20:01:55 -0800115 // If we don't have a path and are far from the goal, don't update the
116 // path.
117 // TODO(austin): Once we get close to our first setpoint, crank the
118 // power back up. Otherwise we'll be weak at startup.
119 LOG(INFO, "No path.\n");
120 if (!close_enough_for_full_power_) {
121 break;
122 }
123 }
124 if (current_node_ != *unsafe_goal) {
125 LOG(INFO, "Goal is different\n");
126 if (*unsafe_goal >= search_graph_.num_vertexes()) {
127 LOG(ERROR, "goal out of range ESTOP\n");
128 state_ = State::ESTOP;
129 break;
130 }
131
132 if (follower_.path_distance_to_go() > 1e-3) {
133 LOG(INFO, " Distance to go %f\n", follower_.path_distance_to_go());
134 // Still on the old path segment. Can't change yet.
135 break;
136 }
137
138 search_graph_.SetGoal(*unsafe_goal);
139
140 size_t min_edge = 0;
141 double min_cost = -1.0;
142 for (const SearchGraph::HalfEdge &edge :
143 search_graph_.Neighbors(current_node_)) {
144 const double cost = search_graph_.GetCostToGoal(edge.dest);
145 if (min_cost == -1 || cost < min_cost) {
146 min_edge = edge.edge_id;
147 min_cost = cost;
148 }
149 }
150 // Ok, now we know which edge we are on. Figure out the path and
151 // trajectory.
152 const SearchGraph::Edge &next_edge = search_graph_.edges()[min_edge];
Austin Schuh7dfccf62018-03-03 21:28:14 -0800153 LOG(INFO, "Switching from node %d to %d along edge %d\n",
154 static_cast<int>(current_node_), static_cast<int>(next_edge.end),
155 static_cast<int>(min_edge));
156 follower_.SwitchTrajectory(&trajectories_[min_edge]);
157 current_node_ = next_edge.end;
Austin Schuhcb091712018-02-21 20:01:55 -0800158 }
159 }
160 break;
161
162 case State::ESTOP:
163 LOG(ERROR, "Estop\n");
164 break;
165 }
166
167 const bool disable =
168 ((proximal_output == nullptr) || (distal_output == nullptr) ||
169 (release_arm_brake == nullptr)) ||
170 state_ != State::RUNNING;
171 if (disable) {
172 close_enough_for_full_power_ = false;
173 }
174
175 follower_.Update(
176 arm_ekf_.X_hat(), disable, kDt(), kVMax(),
177 close_enough_for_full_power_ ? kOperatingVoltage() : kPathlessVMax());
178 LOG(INFO, "Max voltage: %f\n",
179 close_enough_for_full_power_ ? kOperatingVoltage() : kPathlessVMax());
180 status->goal_theta0 = follower_.theta(0);
181 status->goal_theta1 = follower_.theta(1);
182 status->goal_omega0 = follower_.omega(0);
183 status->goal_omega1 = follower_.omega(1);
184
185 status->theta0 = arm_ekf_.X_hat(0);
186 status->theta1 = arm_ekf_.X_hat(2);
187 status->omega0 = arm_ekf_.X_hat(1);
188 status->omega1 = arm_ekf_.X_hat(3);
189 status->voltage_error0 = arm_ekf_.X_hat(4);
190 status->voltage_error1 = arm_ekf_.X_hat(5);
191
192 if (!disable) {
193 *proximal_output = ::std::max(
194 -kOperatingVoltage(), ::std::min(kOperatingVoltage(), follower_.U(0)));
195 *distal_output = ::std::max(
196 -kOperatingVoltage(), ::std::min(kOperatingVoltage(), follower_.U(1)));
197 *release_arm_brake = true;
198 }
199
200 status->proximal_estimator_state =
201 proximal_zeroing_estimator_.GetEstimatorState();
202 status->distal_estimator_state =
203 distal_zeroing_estimator_.GetEstimatorState();
204
205 status->path_distance_to_go = follower_.path_distance_to_go();
206 status->current_node = current_node_;
207
208 status->zeroed = (proximal_zeroing_estimator_.zeroed() &&
209 distal_zeroing_estimator_.zeroed());
210 status->estopped = (state_ == State::ESTOP);
211 status->state = static_cast<int32_t>(state_);
212 status->failed_solutions = follower_.failed_solutions();
213
214 arm_ekf_.Predict(follower_.U(), kDt());
215}
216
217} // namespace arm
218} // namespace superstructure
219} // namespace control_loops
220} // namespace y2018