blob: 73781d2d54dd7470fd2077d9df3097975050fc6f [file] [log] [blame]
milind-u37385182023-02-20 15:07:28 -08001#include "y2023/control_loops/superstructure/arm/arm.h"
2
3namespace y2023 {
4namespace control_loops {
5namespace superstructure {
6namespace arm {
7namespace {
8
9namespace chrono = ::std::chrono;
10using ::aos::monotonic_clock;
11
12constexpr int kMaxBrownoutCount = 4;
13
14} // namespace
15
16Arm::Arm(std::shared_ptr<const constants::Values> values)
17 : values_(values),
18 state_(ArmState::UNINITIALIZED),
19 proximal_zeroing_estimator_(values_->arm_proximal.zeroing),
20 distal_zeroing_estimator_(values_->arm_distal.zeroing),
21 proximal_offset_(0.0),
22 distal_offset_(0.0),
23 max_intake_override_(1000.0),
24 alpha_unitizer_((::Eigen::Matrix<double, 2, 2>() << 1.0 / kAlpha0Max(),
25 0.0, 0.0, 1.0 / kAlpha1Max())
26 .finished()),
27 dynamics_(kArmConstants),
28 search_graph_(MakeSearchGraph(&dynamics_, &trajectories_, alpha_unitizer_,
29 kVMax())),
30 close_enough_for_full_power_(false),
31 brownout_count_(0),
32 arm_ekf_(&dynamics_),
33 // Go to the start of the first trajectory.
34 follower_(&dynamics_, NeutralPosPoint()),
35 points_(PointList()),
36 current_node_(0) {
37 int i = 0;
38 for (const auto &trajectory : trajectories_) {
39 AOS_LOG(INFO, "trajectory length for edge node %d: %f\n", i,
40 trajectory.trajectory.path().length());
41 ++i;
42 }
43}
44
45void Arm::Reset() { state_ = ArmState::UNINITIALIZED; }
46
47flatbuffers::Offset<superstructure::ArmStatus> Arm::Iterate(
48 const ::aos::monotonic_clock::time_point /*monotonic_now*/,
49 const uint32_t *unsafe_goal, const superstructure::ArmPosition *position,
50 bool trajectory_override, double *proximal_output, double *distal_output,
51 bool /*intake*/, bool /*spit*/, flatbuffers::FlatBufferBuilder *fbb) {
52 ::Eigen::Matrix<double, 2, 1> Y;
53 const bool outputs_disabled =
54 ((proximal_output == nullptr) || (distal_output == nullptr));
55 if (outputs_disabled) {
56 ++brownout_count_;
57 } else {
58 brownout_count_ = 0;
59 }
60
61 uint32_t filtered_goal = 0;
62 if (unsafe_goal != nullptr) {
63 filtered_goal = *unsafe_goal;
64 }
65
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 ArmState::UNINITIALIZED:
92 // Wait in the uninitialized state until the intake is initialized.
93 AOS_LOG(DEBUG, "Uninitialized, waiting for intake\n");
94 state_ = ArmState::ZEROING;
95 proximal_zeroing_estimator_.Reset();
96 distal_zeroing_estimator_.Reset();
97 break;
98
99 case ArmState::ZEROING:
100 // Zero by not moving.
101 if (proximal_zeroing_estimator_.zeroed() &&
102 distal_zeroing_estimator_.zeroed()) {
103 state_ = ArmState::DISABLED;
104
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 [[fallthrough]];
120
121 case ArmState::DISABLED: {
122 follower_.SwitchTrajectory(nullptr);
123 close_enough_for_full_power_ = false;
124
125 const ::Eigen::Matrix<double, 2, 1> current_theta =
126 (::Eigen::Matrix<double, 2, 1>() << arm_ekf_.X_hat(0),
127 arm_ekf_.X_hat(2))
128 .finished();
129 uint32_t best_index = 0;
130 double best_distance = (points_[0] - current_theta).norm();
131 uint32_t current_index = 0;
132 for (const ::Eigen::Matrix<double, 2, 1> &point : points_) {
133 const double new_distance = (point - current_theta).norm();
134 if (new_distance < best_distance) {
135 best_distance = new_distance;
136 best_index = current_index;
137 }
138 ++current_index;
139 }
140 follower_.set_theta(points_[best_index]);
141 current_node_ = best_index;
142
143 if (!outputs_disabled) {
144 state_ = ArmState::GOTO_PATH;
145 } else {
146 break;
147 }
148 }
149 [[fallthrough]];
150
151 case ArmState::GOTO_PATH:
152 if (outputs_disabled) {
153 state_ = ArmState::DISABLED;
154 } else if (trajectory_override) {
155 follower_.SwitchTrajectory(nullptr);
156 current_node_ = filtered_goal;
157 follower_.set_theta(points_[current_node_]);
158 state_ = ArmState::GOTO_PATH;
159 } else if (close_enough_for_full_power_) {
160 state_ = ArmState::RUNNING;
161 }
162 break;
163
164 case ArmState::RUNNING:
165 // ESTOP if we hit the hard limits.
166 // TODO(austin): Pick some sane limits.
167 if (proximal_zeroing_estimator_.error() ||
168 distal_zeroing_estimator_.error()) {
169 AOS_LOG(ERROR, "Zeroing error ESTOP\n");
170 state_ = ArmState::ESTOP;
171 } else if (outputs_disabled && brownout_count_ > kMaxBrownoutCount) {
172 state_ = ArmState::DISABLED;
173 } else if (trajectory_override) {
174 follower_.SwitchTrajectory(nullptr);
175 current_node_ = filtered_goal;
176 follower_.set_theta(points_[current_node_]);
177 state_ = ArmState::GOTO_PATH;
178 }
179 break;
180
181 case ArmState::ESTOP:
182 AOS_LOG(ERROR, "Estop\n");
183 break;
184 }
185
186 const bool disable = outputs_disabled || (state_ != ArmState::RUNNING &&
187 state_ != ArmState::GOTO_PATH);
188 if (disable) {
189 close_enough_for_full_power_ = false;
190 }
191
192 if (state_ == ArmState::RUNNING && unsafe_goal != nullptr) {
193 if (current_node_ != filtered_goal) {
194 AOS_LOG(INFO, "Goal is different\n");
195 if (filtered_goal >= search_graph_.num_vertexes()) {
196 AOS_LOG(ERROR, "goal node out of range ESTOP\n");
197 state_ = ArmState::ESTOP;
198 } else if (follower_.path_distance_to_go() > 1e-3) {
199 // Still on the old path segment. Can't change yet.
200 } else {
201 search_graph_.SetGoal(filtered_goal);
202
203 size_t min_edge = 0;
204 double min_cost = ::std::numeric_limits<double>::infinity();
205 for (const SearchGraph::HalfEdge &edge :
206 search_graph_.Neighbors(current_node_)) {
207 const double cost = search_graph_.GetCostToGoal(edge.dest);
208 if (cost < min_cost) {
209 min_edge = edge.edge_id;
210 min_cost = cost;
211 }
212 }
213 // Ok, now we know which edge we are on. Figure out the path and
214 // trajectory.
215 const SearchGraph::Edge &next_edge = search_graph_.edges()[min_edge];
216 AOS_LOG(INFO, "Switching from node %d to %d along edge %d\n",
217 static_cast<int>(current_node_),
218 static_cast<int>(next_edge.end), static_cast<int>(min_edge));
219 vmax_ = trajectories_[min_edge].vmax;
220 follower_.SwitchTrajectory(&trajectories_[min_edge].trajectory);
221 current_node_ = next_edge.end;
222 }
223 }
224 }
225
226 const double max_operating_voltage =
227 close_enough_for_full_power_
228 ? kOperatingVoltage()
229 : (state_ == ArmState::GOTO_PATH ? kGotoPathVMax() : kPathlessVMax());
230 follower_.Update(arm_ekf_.X_hat(), disable, kDt(), vmax_,
231 max_operating_voltage);
232 AOS_LOG(INFO, "Max voltage: %f\n", max_operating_voltage);
233
234 flatbuffers::Offset<frc971::PotAndAbsoluteEncoderEstimatorState>
235 proximal_estimator_state_offset =
236 proximal_zeroing_estimator_.GetEstimatorState(fbb);
237 flatbuffers::Offset<frc971::PotAndAbsoluteEncoderEstimatorState>
238 distal_estimator_state_offset =
239 distal_zeroing_estimator_.GetEstimatorState(fbb);
240
241 superstructure::ArmStatus::Builder status_builder(*fbb);
242 status_builder.add_proximal_estimator_state(proximal_estimator_state_offset);
243 status_builder.add_distal_estimator_state(distal_estimator_state_offset);
244
245 status_builder.add_goal_theta0(follower_.theta(0));
246 status_builder.add_goal_theta1(follower_.theta(1));
247 status_builder.add_goal_omega0(follower_.omega(0));
248 status_builder.add_goal_omega1(follower_.omega(1));
249
250 status_builder.add_theta0(arm_ekf_.X_hat(0));
251 status_builder.add_theta1(arm_ekf_.X_hat(2));
252 status_builder.add_omega0(arm_ekf_.X_hat(1));
253 status_builder.add_omega1(arm_ekf_.X_hat(3));
254 status_builder.add_voltage_error0(arm_ekf_.X_hat(4));
255 status_builder.add_voltage_error1(arm_ekf_.X_hat(5));
256
257 if (!disable) {
258 *proximal_output = ::std::max(
259 -kOperatingVoltage(), ::std::min(kOperatingVoltage(), follower_.U(0)));
260 *distal_output = ::std::max(
261 -kOperatingVoltage(), ::std::min(kOperatingVoltage(), follower_.U(1)));
262 }
263
264 status_builder.add_path_distance_to_go(follower_.path_distance_to_go());
265 status_builder.add_current_node(current_node_);
266
267 status_builder.add_zeroed(zeroed());
268 status_builder.add_estopped(estopped());
269 status_builder.add_state(state_);
270 status_builder.add_failed_solutions(follower_.failed_solutions());
271
272 arm_ekf_.Predict(follower_.U(), kDt());
273 return status_builder.Finish();
274}
275
276} // namespace arm
277} // namespace superstructure
278} // namespace control_loops
279} // namespace y2023