blob: 07d54ec58c8954b54f4ce5277e096a4f02b07186 [file] [log] [blame]
milind-u37385182023-02-20 15:07:28 -08001#include "y2023/control_loops/superstructure/arm/arm.h"
2
milind-u18a901d2023-02-17 21:51:55 -08003#include "y2023/control_loops/superstructure/roll/integral_hybrid_roll_plant.h"
4#include "y2023/control_loops/superstructure/roll/integral_roll_plant.h"
5
milind-u37385182023-02-20 15:07:28 -08006namespace y2023 {
7namespace control_loops {
8namespace superstructure {
9namespace arm {
10namespace {
11
12namespace chrono = ::std::chrono;
13using ::aos::monotonic_clock;
14
15constexpr int kMaxBrownoutCount = 4;
16
17} // namespace
18
19Arm::Arm(std::shared_ptr<const constants::Values> values)
20 : values_(values),
21 state_(ArmState::UNINITIALIZED),
22 proximal_zeroing_estimator_(values_->arm_proximal.zeroing),
23 distal_zeroing_estimator_(values_->arm_distal.zeroing),
milind-u18a901d2023-02-17 21:51:55 -080024 roll_joint_zeroing_estimator_(values_->roll_joint.zeroing),
milind-u37385182023-02-20 15:07:28 -080025 proximal_offset_(0.0),
26 distal_offset_(0.0),
milind-u18a901d2023-02-17 21:51:55 -080027 roll_joint_offset_(0.0),
28 alpha_unitizer_((::Eigen::DiagonalMatrix<double, 3>().diagonal()
29 << (1.0 / kAlpha0Max()),
30 (1.0 / kAlpha1Max()), (1.0 / kAlpha2Max()))
milind-u37385182023-02-20 15:07:28 -080031 .finished()),
32 dynamics_(kArmConstants),
milind-u37385182023-02-20 15:07:28 -080033 close_enough_for_full_power_(false),
34 brownout_count_(0),
milind-u18a901d2023-02-17 21:51:55 -080035 roll_joint_loop_(roll::MakeIntegralRollLoop()),
36 hybrid_roll_joint_loop_(roll::MakeIntegralHybridRollLoop()),
milind-u37385182023-02-20 15:07:28 -080037 arm_ekf_(&dynamics_),
milind-u18a901d2023-02-17 21:51:55 -080038 search_graph_(MakeSearchGraph(&dynamics_, &trajectories_, alpha_unitizer_,
39 kVMax(), &hybrid_roll_joint_loop_)),
milind-u37385182023-02-20 15:07:28 -080040 // Go to the start of the first trajectory.
milind-u18a901d2023-02-17 21:51:55 -080041 follower_(&dynamics_, &hybrid_roll_joint_loop_, NeutralPosPoint()),
milind-u37385182023-02-20 15:07:28 -080042 points_(PointList()),
43 current_node_(0) {
44 int i = 0;
45 for (const auto &trajectory : trajectories_) {
46 AOS_LOG(INFO, "trajectory length for edge node %d: %f\n", i,
47 trajectory.trajectory.path().length());
48 ++i;
49 }
50}
51
52void Arm::Reset() { state_ = ArmState::UNINITIALIZED; }
53
54flatbuffers::Offset<superstructure::ArmStatus> Arm::Iterate(
55 const ::aos::monotonic_clock::time_point /*monotonic_now*/,
56 const uint32_t *unsafe_goal, const superstructure::ArmPosition *position,
57 bool trajectory_override, double *proximal_output, double *distal_output,
Maxwell Henderson5938a832023-02-23 09:33:15 -080058 double *roll_joint_output, flatbuffers::FlatBufferBuilder *fbb) {
59 ::Eigen::Matrix<double, 2, 1> Y;
milind-u37385182023-02-20 15:07:28 -080060 const bool outputs_disabled =
milind-u18a901d2023-02-17 21:51:55 -080061 ((proximal_output == nullptr) || (distal_output == nullptr) ||
62 (roll_joint_output == nullptr));
milind-u37385182023-02-20 15:07:28 -080063 if (outputs_disabled) {
64 ++brownout_count_;
65 } else {
66 brownout_count_ = 0;
67 }
68
milind-u18a901d2023-02-17 21:51:55 -080069 // TODO(milind): should we default to the closest position?
70 uint32_t filtered_goal = arm::NeutralPosIndex();
milind-u37385182023-02-20 15:07:28 -080071 if (unsafe_goal != nullptr) {
72 filtered_goal = *unsafe_goal;
73 }
74
milind-u18a901d2023-02-17 21:51:55 -080075 ::Eigen::Matrix<double, 2, 1> Y_arm;
76 Y_arm << position->proximal()->encoder() + proximal_offset_,
milind-u37385182023-02-20 15:07:28 -080077 position->distal()->encoder() + distal_offset_;
milind-u18a901d2023-02-17 21:51:55 -080078 ::Eigen::Matrix<double, 1, 1> Y_roll_joint;
79 Y_roll_joint << position->roll_joint()->encoder() + roll_joint_offset_;
milind-u37385182023-02-20 15:07:28 -080080
81 proximal_zeroing_estimator_.UpdateEstimate(*position->proximal());
82 distal_zeroing_estimator_.UpdateEstimate(*position->distal());
milind-u18a901d2023-02-17 21:51:55 -080083 roll_joint_zeroing_estimator_.UpdateEstimate(*position->roll_joint());
milind-u37385182023-02-20 15:07:28 -080084
85 if (proximal_output != nullptr) {
86 *proximal_output = 0.0;
87 }
88 if (distal_output != nullptr) {
89 *distal_output = 0.0;
90 }
milind-u18a901d2023-02-17 21:51:55 -080091 if (roll_joint_output != nullptr) {
92 *roll_joint_output = 0.0;
93 }
milind-u37385182023-02-20 15:07:28 -080094
milind-u18a901d2023-02-17 21:51:55 -080095 arm_ekf_.Correct(Y_arm, kDt());
96 roll_joint_loop_.Correct(Y_roll_joint);
milind-u37385182023-02-20 15:07:28 -080097
98 if (::std::abs(arm_ekf_.X_hat(0) - follower_.theta(0)) <= 0.05 &&
milind-u18a901d2023-02-17 21:51:55 -080099 ::std::abs(arm_ekf_.X_hat(2) - follower_.theta(1)) <= 0.05 &&
100 ::std::abs(roll_joint_loop_.X_hat(0) - follower_.theta(2)) <= 0.05) {
milind-u37385182023-02-20 15:07:28 -0800101 close_enough_for_full_power_ = true;
102 }
103 if (::std::abs(arm_ekf_.X_hat(0) - follower_.theta(0)) >= 1.10 ||
milind-u18a901d2023-02-17 21:51:55 -0800104 ::std::abs(arm_ekf_.X_hat(2) - follower_.theta(1)) >= 1.10 ||
105 ::std::abs(roll_joint_loop_.X_hat(0) - follower_.theta(2)) >= 0.50) {
milind-u37385182023-02-20 15:07:28 -0800106 close_enough_for_full_power_ = false;
107 }
108
109 switch (state_) {
110 case ArmState::UNINITIALIZED:
111 // Wait in the uninitialized state until the intake is initialized.
112 AOS_LOG(DEBUG, "Uninitialized, waiting for intake\n");
113 state_ = ArmState::ZEROING;
114 proximal_zeroing_estimator_.Reset();
115 distal_zeroing_estimator_.Reset();
milind-u18a901d2023-02-17 21:51:55 -0800116 roll_joint_zeroing_estimator_.Reset();
milind-u37385182023-02-20 15:07:28 -0800117 break;
118
119 case ArmState::ZEROING:
120 // Zero by not moving.
milind-u18a901d2023-02-17 21:51:55 -0800121 if (zeroed()) {
milind-u37385182023-02-20 15:07:28 -0800122 state_ = ArmState::DISABLED;
123
124 proximal_offset_ = proximal_zeroing_estimator_.offset();
125 distal_offset_ = distal_zeroing_estimator_.offset();
milind-u18a901d2023-02-17 21:51:55 -0800126 roll_joint_offset_ = roll_joint_zeroing_estimator_.offset();
milind-u37385182023-02-20 15:07:28 -0800127
milind-u18a901d2023-02-17 21:51:55 -0800128 Y_arm << position->proximal()->encoder() + proximal_offset_,
milind-u37385182023-02-20 15:07:28 -0800129 position->distal()->encoder() + distal_offset_;
milind-u18a901d2023-02-17 21:51:55 -0800130 Y_roll_joint << position->roll_joint()->encoder() + roll_joint_offset_;
milind-u37385182023-02-20 15:07:28 -0800131
132 // TODO(austin): Offset ekf rather than reset it. Since we aren't
133 // moving at this point, it's pretty safe to do this.
milind-u18a901d2023-02-17 21:51:55 -0800134 ::Eigen::Matrix<double, 4, 1> X_arm;
135 X_arm << Y_arm(0), 0.0, Y_arm(1), 0.0;
136 arm_ekf_.Reset(X_arm);
137
138 ::Eigen::Matrix<double, 3, 1> X_roll_joint;
139 X_roll_joint << Y_roll_joint(0), 0.0, 0.0;
140 roll_joint_loop_.mutable_X_hat() = X_roll_joint;
milind-u37385182023-02-20 15:07:28 -0800141 } else {
142 break;
143 }
144 [[fallthrough]];
145
146 case ArmState::DISABLED: {
147 follower_.SwitchTrajectory(nullptr);
148 close_enough_for_full_power_ = false;
149
milind-u18a901d2023-02-17 21:51:55 -0800150 const ::Eigen::Matrix<double, 3, 1> current_theta =
151 (::Eigen::Matrix<double, 3, 1>() << arm_ekf_.X_hat(0),
152 arm_ekf_.X_hat(2), roll_joint_loop_.X_hat(0))
milind-u37385182023-02-20 15:07:28 -0800153 .finished();
154 uint32_t best_index = 0;
155 double best_distance = (points_[0] - current_theta).norm();
156 uint32_t current_index = 0;
milind-u18a901d2023-02-17 21:51:55 -0800157 for (const ::Eigen::Matrix<double, 3, 1> &point : points_) {
milind-u37385182023-02-20 15:07:28 -0800158 const double new_distance = (point - current_theta).norm();
159 if (new_distance < best_distance) {
160 best_distance = new_distance;
161 best_index = current_index;
162 }
163 ++current_index;
164 }
165 follower_.set_theta(points_[best_index]);
166 current_node_ = best_index;
167
168 if (!outputs_disabled) {
169 state_ = ArmState::GOTO_PATH;
170 } else {
171 break;
172 }
173 }
174 [[fallthrough]];
175
176 case ArmState::GOTO_PATH:
177 if (outputs_disabled) {
178 state_ = ArmState::DISABLED;
179 } else if (trajectory_override) {
180 follower_.SwitchTrajectory(nullptr);
181 current_node_ = filtered_goal;
182 follower_.set_theta(points_[current_node_]);
183 state_ = ArmState::GOTO_PATH;
184 } else if (close_enough_for_full_power_) {
185 state_ = ArmState::RUNNING;
186 }
187 break;
188
189 case ArmState::RUNNING:
190 // ESTOP if we hit the hard limits.
191 // TODO(austin): Pick some sane limits.
192 if (proximal_zeroing_estimator_.error() ||
milind-u18a901d2023-02-17 21:51:55 -0800193 distal_zeroing_estimator_.error() ||
194 roll_joint_zeroing_estimator_.error()) {
milind-u37385182023-02-20 15:07:28 -0800195 AOS_LOG(ERROR, "Zeroing error ESTOP\n");
196 state_ = ArmState::ESTOP;
197 } else if (outputs_disabled && brownout_count_ > kMaxBrownoutCount) {
198 state_ = ArmState::DISABLED;
199 } else if (trajectory_override) {
200 follower_.SwitchTrajectory(nullptr);
201 current_node_ = filtered_goal;
202 follower_.set_theta(points_[current_node_]);
203 state_ = ArmState::GOTO_PATH;
204 }
205 break;
206
207 case ArmState::ESTOP:
208 AOS_LOG(ERROR, "Estop\n");
209 break;
210 }
211
212 const bool disable = outputs_disabled || (state_ != ArmState::RUNNING &&
213 state_ != ArmState::GOTO_PATH);
214 if (disable) {
215 close_enough_for_full_power_ = false;
216 }
217
218 if (state_ == ArmState::RUNNING && unsafe_goal != nullptr) {
219 if (current_node_ != filtered_goal) {
220 AOS_LOG(INFO, "Goal is different\n");
221 if (filtered_goal >= search_graph_.num_vertexes()) {
222 AOS_LOG(ERROR, "goal node out of range ESTOP\n");
223 state_ = ArmState::ESTOP;
224 } else if (follower_.path_distance_to_go() > 1e-3) {
225 // Still on the old path segment. Can't change yet.
226 } else {
227 search_graph_.SetGoal(filtered_goal);
228
229 size_t min_edge = 0;
230 double min_cost = ::std::numeric_limits<double>::infinity();
231 for (const SearchGraph::HalfEdge &edge :
232 search_graph_.Neighbors(current_node_)) {
233 const double cost = search_graph_.GetCostToGoal(edge.dest);
234 if (cost < min_cost) {
235 min_edge = edge.edge_id;
236 min_cost = cost;
237 }
238 }
239 // Ok, now we know which edge we are on. Figure out the path and
240 // trajectory.
241 const SearchGraph::Edge &next_edge = search_graph_.edges()[min_edge];
242 AOS_LOG(INFO, "Switching from node %d to %d along edge %d\n",
243 static_cast<int>(current_node_),
244 static_cast<int>(next_edge.end), static_cast<int>(min_edge));
245 vmax_ = trajectories_[min_edge].vmax;
246 follower_.SwitchTrajectory(&trajectories_[min_edge].trajectory);
247 current_node_ = next_edge.end;
248 }
249 }
250 }
251
252 const double max_operating_voltage =
253 close_enough_for_full_power_
254 ? kOperatingVoltage()
255 : (state_ == ArmState::GOTO_PATH ? kGotoPathVMax() : kPathlessVMax());
milind-u18a901d2023-02-17 21:51:55 -0800256 ::Eigen::Matrix<double, 9, 1> X_hat;
257 X_hat.block<6, 1>(0, 0) = arm_ekf_.X_hat();
258 X_hat.block<3, 1>(6, 0) = roll_joint_loop_.X_hat();
259
260 follower_.Update(X_hat, disable, kDt(), vmax_, max_operating_voltage);
milind-u37385182023-02-20 15:07:28 -0800261 AOS_LOG(INFO, "Max voltage: %f\n", max_operating_voltage);
262
milind-u18a901d2023-02-17 21:51:55 -0800263 arm_ekf_.Predict(follower_.U().head<2>(), kDt());
264 roll_joint_loop_.UpdateObserver(follower_.U().tail<1>(), kDtDuration());
265
milind-u37385182023-02-20 15:07:28 -0800266 flatbuffers::Offset<frc971::PotAndAbsoluteEncoderEstimatorState>
267 proximal_estimator_state_offset =
268 proximal_zeroing_estimator_.GetEstimatorState(fbb);
269 flatbuffers::Offset<frc971::PotAndAbsoluteEncoderEstimatorState>
270 distal_estimator_state_offset =
271 distal_zeroing_estimator_.GetEstimatorState(fbb);
milind-u18a901d2023-02-17 21:51:55 -0800272 flatbuffers::Offset<frc971::PotAndAbsoluteEncoderEstimatorState>
273 roll_joint_estimator_state_offset =
274 roll_joint_zeroing_estimator_.GetEstimatorState(fbb);
milind-u37385182023-02-20 15:07:28 -0800275
276 superstructure::ArmStatus::Builder status_builder(*fbb);
277 status_builder.add_proximal_estimator_state(proximal_estimator_state_offset);
278 status_builder.add_distal_estimator_state(distal_estimator_state_offset);
milind-u18a901d2023-02-17 21:51:55 -0800279 status_builder.add_roll_joint_estimator_state(
280 roll_joint_estimator_state_offset);
milind-u37385182023-02-20 15:07:28 -0800281
282 status_builder.add_goal_theta0(follower_.theta(0));
283 status_builder.add_goal_theta1(follower_.theta(1));
milind-u18a901d2023-02-17 21:51:55 -0800284 status_builder.add_goal_theta2(follower_.theta(2));
milind-u37385182023-02-20 15:07:28 -0800285 status_builder.add_goal_omega0(follower_.omega(0));
286 status_builder.add_goal_omega1(follower_.omega(1));
milind-u18a901d2023-02-17 21:51:55 -0800287 status_builder.add_goal_omega2(follower_.omega(2));
milind-u37385182023-02-20 15:07:28 -0800288
289 status_builder.add_theta0(arm_ekf_.X_hat(0));
290 status_builder.add_theta1(arm_ekf_.X_hat(2));
milind-u18a901d2023-02-17 21:51:55 -0800291 status_builder.add_theta2(roll_joint_loop_.X_hat(0));
milind-u37385182023-02-20 15:07:28 -0800292 status_builder.add_omega0(arm_ekf_.X_hat(1));
293 status_builder.add_omega1(arm_ekf_.X_hat(3));
milind-u18a901d2023-02-17 21:51:55 -0800294 status_builder.add_omega2(roll_joint_loop_.X_hat(1));
milind-u37385182023-02-20 15:07:28 -0800295 status_builder.add_voltage_error0(arm_ekf_.X_hat(4));
296 status_builder.add_voltage_error1(arm_ekf_.X_hat(5));
milind-u18a901d2023-02-17 21:51:55 -0800297 status_builder.add_voltage_error2(roll_joint_loop_.X_hat(2));
milind-u37385182023-02-20 15:07:28 -0800298
299 if (!disable) {
300 *proximal_output = ::std::max(
301 -kOperatingVoltage(), ::std::min(kOperatingVoltage(), follower_.U(0)));
302 *distal_output = ::std::max(
303 -kOperatingVoltage(), ::std::min(kOperatingVoltage(), follower_.U(1)));
milind-u18a901d2023-02-17 21:51:55 -0800304 *roll_joint_output = ::std::max(
305 -kOperatingVoltage(), ::std::min(kOperatingVoltage(), follower_.U(2)));
milind-u37385182023-02-20 15:07:28 -0800306 }
307
308 status_builder.add_path_distance_to_go(follower_.path_distance_to_go());
309 status_builder.add_current_node(current_node_);
310
311 status_builder.add_zeroed(zeroed());
312 status_builder.add_estopped(estopped());
313 status_builder.add_state(state_);
314 status_builder.add_failed_solutions(follower_.failed_solutions());
315
milind-u37385182023-02-20 15:07:28 -0800316 return status_builder.Finish();
317}
318
319} // namespace arm
320} // namespace superstructure
321} // namespace control_loops
322} // namespace y2023