blob: 484202ee3c35fbdebcad202e120057d1d94ea73d [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"
11
12namespace y2018 {
13namespace control_loops {
14namespace superstructure {
15namespace arm {
16
17namespace chrono = ::std::chrono;
18using ::aos::monotonic_clock;
19
20SearchGraph::Edge MakeEdge(::std::vector<TrajectoryPair> *trajectories,
21 size_t start, size_t end, ::std::unique_ptr<Path> forwards_path,
22 ::std::unique_ptr<Path> backwards_path) {
23 trajectories->emplace_back(::std::move(forwards_path),
24 ::std::move(backwards_path), 0.005);
25
26 return SearchGraph::Edge{start, end,
27 trajectories->back().forwards.path().length()};
28}
29
30SearchGraph MakeSearchGraph(::std::vector<TrajectoryPair> *trajectories) {
31 ::std::vector<SearchGraph::Edge> edges;
32
33 // TODO(austin): Add more trajectories here.
34 edges.push_back(
35 MakeEdge(trajectories, 0, 1, MakeReversedDemoPath(), MakeDemoPath()));
36
37 return SearchGraph(2, ::std::move(edges));
38}
39
40Arm::Arm()
41 : proximal_zeroing_estimator_(constants::GetValues().arm_proximal.zeroing),
42 distal_zeroing_estimator_(constants::GetValues().arm_distal.zeroing),
43 alpha_unitizer_((::Eigen::Matrix<double, 2, 2>() << 1.0 / kAlpha0Max(),
44 0.0, 0.0, 1.0 / kAlpha1Max())
45 .finished()),
46 search_graph_(MakeSearchGraph(&trajectories_)),
47 // Go to the start of the first trajectory.
48 follower_(trajectories_[0].forwards.path().Theta(0)) {
49 // TODO(austin): Stop talking about indeces in the list and start talking
50 // about points/search for the nearest point.
51 for (TrajectoryPair &trajectory_pair : trajectories_) {
52 trajectory_pair.forwards.OptimizeTrajectory(alpha_unitizer_, kVMax());
53 trajectory_pair.backwards.OptimizeTrajectory(alpha_unitizer_, kVMax());
54 }
55}
56
57void Arm::Reset() { state_ = State::UNINITIALIZED; }
58
59void Arm::Iterate(const uint32_t *unsafe_goal,
60 const control_loops::ArmPosition *position,
61 double *proximal_output, double *distal_output,
62 bool *release_arm_brake, control_loops::ArmStatus *status) {
63 ::Eigen::Matrix<double, 2, 1> Y;
64
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();
96 // TODO(austin): Go to the nearest node. For now, we are always going to
97 // go to node 0.
98 break;
99
100 case State::ZEROING:
101 // Zero by not moving.
102 if (proximal_zeroing_estimator_.zeroed() &&
103 distal_zeroing_estimator_.zeroed()) {
104 state_ = State::RUNNING;
105
106 proximal_offset_ = proximal_zeroing_estimator_.offset();
107 distal_offset_ = distal_zeroing_estimator_.offset();
108
109 Y << position->proximal.encoder + proximal_offset_,
110 position->distal.encoder + distal_offset_;
111
112 // TODO(austin): Offset ekf rather than reset it. Since we aren't
113 // moving at this point, it's pretty safe to do this.
114 ::Eigen::Matrix<double, 4, 1> X;
115 X << Y(0), 0.0, Y(1), 0.0;
116 arm_ekf_.Reset(X);
117 } else {
118 break;
119 }
120
121 case State::RUNNING:
122 // ESTOP if we hit the hard limits.
123 // TODO(austin): Pick some sane limits.
124 if (proximal_zeroing_estimator_.error() ||
125 distal_zeroing_estimator_.error()) {
126 LOG(ERROR, "Zeroing error ESTOP\n");
127 state_ = State::ESTOP;
128 } else if (unsafe_goal != nullptr) {
129 if (!follower_.has_path()) {
130 // If we don't have a path and are far from the goal, don't update the
131 // path.
132 // TODO(austin): Once we get close to our first setpoint, crank the
133 // power back up. Otherwise we'll be weak at startup.
134 LOG(INFO, "No path.\n");
135 if (!close_enough_for_full_power_) {
136 break;
137 }
138 }
139 if (current_node_ != *unsafe_goal) {
140 LOG(INFO, "Goal is different\n");
141 if (*unsafe_goal >= search_graph_.num_vertexes()) {
142 LOG(ERROR, "goal out of range ESTOP\n");
143 state_ = State::ESTOP;
144 break;
145 }
146
147 if (follower_.path_distance_to_go() > 1e-3) {
148 LOG(INFO, " Distance to go %f\n", follower_.path_distance_to_go());
149 // Still on the old path segment. Can't change yet.
150 break;
151 }
152
153 search_graph_.SetGoal(*unsafe_goal);
154
155 size_t min_edge = 0;
156 double min_cost = -1.0;
157 for (const SearchGraph::HalfEdge &edge :
158 search_graph_.Neighbors(current_node_)) {
159 const double cost = search_graph_.GetCostToGoal(edge.dest);
160 if (min_cost == -1 || cost < min_cost) {
161 min_edge = edge.edge_id;
162 min_cost = cost;
163 }
164 }
165 // Ok, now we know which edge we are on. Figure out the path and
166 // trajectory.
167 const SearchGraph::Edge &next_edge = search_graph_.edges()[min_edge];
168 if (next_edge.start == current_node_) {
169 follower_.SwitchTrajectory(&trajectories_[min_edge].forwards);
170 current_node_ = next_edge.end;
171 } else {
172 follower_.SwitchTrajectory(&trajectories_[min_edge].backwards);
173 current_node_ = next_edge.start;
174 }
175 }
176 }
177 break;
178
179 case State::ESTOP:
180 LOG(ERROR, "Estop\n");
181 break;
182 }
183
184 const bool disable =
185 ((proximal_output == nullptr) || (distal_output == nullptr) ||
186 (release_arm_brake == nullptr)) ||
187 state_ != State::RUNNING;
188 if (disable) {
189 close_enough_for_full_power_ = false;
190 }
191
192 follower_.Update(
193 arm_ekf_.X_hat(), disable, kDt(), kVMax(),
194 close_enough_for_full_power_ ? kOperatingVoltage() : kPathlessVMax());
195 LOG(INFO, "Max voltage: %f\n",
196 close_enough_for_full_power_ ? kOperatingVoltage() : kPathlessVMax());
197 status->goal_theta0 = follower_.theta(0);
198 status->goal_theta1 = follower_.theta(1);
199 status->goal_omega0 = follower_.omega(0);
200 status->goal_omega1 = follower_.omega(1);
201
202 status->theta0 = arm_ekf_.X_hat(0);
203 status->theta1 = arm_ekf_.X_hat(2);
204 status->omega0 = arm_ekf_.X_hat(1);
205 status->omega1 = arm_ekf_.X_hat(3);
206 status->voltage_error0 = arm_ekf_.X_hat(4);
207 status->voltage_error1 = arm_ekf_.X_hat(5);
208
209 if (!disable) {
210 *proximal_output = ::std::max(
211 -kOperatingVoltage(), ::std::min(kOperatingVoltage(), follower_.U(0)));
212 *distal_output = ::std::max(
213 -kOperatingVoltage(), ::std::min(kOperatingVoltage(), follower_.U(1)));
214 *release_arm_brake = true;
215 }
216
217 status->proximal_estimator_state =
218 proximal_zeroing_estimator_.GetEstimatorState();
219 status->distal_estimator_state =
220 distal_zeroing_estimator_.GetEstimatorState();
221
222 status->path_distance_to_go = follower_.path_distance_to_go();
223 status->current_node = current_node_;
224
225 status->zeroed = (proximal_zeroing_estimator_.zeroed() &&
226 distal_zeroing_estimator_.zeroed());
227 status->estopped = (state_ == State::ESTOP);
228 status->state = static_cast<int32_t>(state_);
229 status->failed_solutions = follower_.failed_solutions();
230
231 arm_ekf_.Predict(follower_.U(), kDt());
232}
233
234} // namespace arm
235} // namespace superstructure
236} // namespace control_loops
237} // namespace y2018