Nathan Leong | dd72800 | 2024-02-03 15:26:53 -0800 | [diff] [blame] | 1 | #include "frc971/control_loops/catapult/catapult.h" |
| 2 | |
| 3 | namespace frc971::control_loops::catapult { |
| 4 | |
| 5 | const flatbuffers::Offset< |
| 6 | frc971::control_loops::PotAndAbsoluteEncoderProfiledJointStatus> |
| 7 | Catapult::Iterate(const CatapultGoal *catapult_goal, |
| 8 | const PotAndAbsolutePosition *position, |
| 9 | double battery_voltage, double *catapult_voltage, bool fire, |
| 10 | flatbuffers::FlatBufferBuilder *fbb) { |
| 11 | const frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystemGoal |
| 12 | *return_goal = |
| 13 | catapult_goal != nullptr && catapult_goal->has_return_position() |
| 14 | ? catapult_goal->return_position() |
| 15 | : nullptr; |
| 16 | |
| 17 | const bool catapult_disabled = |
| 18 | catapult_.Correct(return_goal, position, catapult_voltage == nullptr); |
| 19 | |
| 20 | if (catapult_disabled) { |
| 21 | catapult_state_ = CatapultState::PROFILE; |
| 22 | } else if (catapult_.running() && catapult_goal != nullptr && fire && |
| 23 | !last_firing_) { |
| 24 | catapult_state_ = CatapultState::FIRING; |
| 25 | latched_shot_position = catapult_goal->shot_position(); |
| 26 | latched_shot_velocity = catapult_goal->shot_velocity(); |
| 27 | } |
| 28 | |
| 29 | // Don't update last_firing_ if the catapult is disabled, so that we actually |
| 30 | // end up firing once it's enabled |
| 31 | if (catapult_.running() && !catapult_disabled) { |
| 32 | last_firing_ = fire; |
| 33 | } |
| 34 | |
| 35 | use_profile_ = true; |
| 36 | |
| 37 | switch (catapult_state_) { |
| 38 | case CatapultState::FIRING: { |
| 39 | // Select the ball controller. We should only be firing if we have a |
| 40 | // ball, or at least should only care about the shot accuracy. |
| 41 | catapult_.set_controller_index(0); |
| 42 | // Ok, so we've now corrected. Next step is to run the MPC. |
| 43 | // |
| 44 | // Since there is a unit delay between when we ask for a U and the |
| 45 | // hardware applies it, we need to run the optimizer for the position at |
| 46 | // the *next* control loop cycle. |
| 47 | |
| 48 | Eigen::Vector3d next_X = catapult_.estimated_state(); |
| 49 | for (int i = catapult_.controller().plant().coefficients().delayed_u; |
| 50 | i > 1; --i) { |
| 51 | next_X = catapult_.controller().plant().A() * next_X + |
| 52 | catapult_.controller().plant().B() * |
| 53 | catapult_.controller().observer().last_U(i - 1); |
| 54 | } |
| 55 | |
| 56 | catapult_mpc_.SetState( |
| 57 | next_X.block<2, 1>(0, 0), |
| 58 | Eigen::Vector2d(latched_shot_position, latched_shot_velocity)); |
| 59 | |
| 60 | const bool solved = catapult_mpc_.Solve(); |
| 61 | current_horizon_ = catapult_mpc_.current_horizon(); |
| 62 | const bool started = catapult_mpc_.started(); |
| 63 | if (solved || started) { |
| 64 | std::optional<double> solution = catapult_mpc_.Next(); |
| 65 | |
| 66 | if (!solution.has_value()) { |
| 67 | CHECK_NOTNULL(catapult_voltage); |
| 68 | *catapult_voltage = 0.0; |
| 69 | if (catapult_mpc_.started()) { |
| 70 | ++shot_count_; |
| 71 | // Finished the catapult, time to fire. |
| 72 | catapult_state_ = CatapultState::RESETTING; |
| 73 | } |
| 74 | } else { |
| 75 | // TODO(austin): Voltage error? |
| 76 | CHECK_NOTNULL(catapult_voltage); |
| 77 | if (current_horizon_ == 1) { |
| 78 | battery_voltage = 12.0; |
| 79 | } |
| 80 | *catapult_voltage = std::max( |
| 81 | 0.0, std::min(12.0, (*solution - 0.0 * next_X(2, 0)) * 12.0 / |
| 82 | std::max(battery_voltage, 8.0))); |
| 83 | use_profile_ = false; |
| 84 | } |
| 85 | } else { |
| 86 | if (!fire) { |
| 87 | // Eh, didn't manage to solve before it was time to fire. Give up. |
| 88 | catapult_state_ = CatapultState::PROFILE; |
| 89 | } |
| 90 | } |
| 91 | |
| 92 | if (!use_profile_) { |
| 93 | catapult_.ForceGoal(catapult_.estimated_position(), |
| 94 | catapult_.estimated_velocity()); |
| 95 | } |
| 96 | } |
| 97 | if (catapult_state_ != CatapultState::RESETTING) { |
| 98 | break; |
| 99 | } else { |
| 100 | [[fallthrough]]; |
| 101 | } |
| 102 | |
| 103 | case CatapultState::RESETTING: |
| 104 | if (catapult_.controller().R(1, 0) > 7.0) { |
Austin Schuh | ea83443 | 2024-02-20 21:23:13 -0800 | [diff] [blame] | 105 | catapult_.mutable_profile()->set_maximum_velocity(7.0); |
| 106 | catapult_.mutable_profile()->set_maximum_acceleration(2000.0); |
Nathan Leong | dd72800 | 2024-02-03 15:26:53 -0800 | [diff] [blame] | 107 | } else if (catapult_.controller().R(1, 0) > 0.0) { |
Austin Schuh | ea83443 | 2024-02-20 21:23:13 -0800 | [diff] [blame] | 108 | catapult_.mutable_profile()->set_maximum_velocity(7.0); |
| 109 | catapult_.mutable_profile()->set_maximum_acceleration(1000.0); |
Nathan Leong | dd72800 | 2024-02-03 15:26:53 -0800 | [diff] [blame] | 110 | } else { |
| 111 | catapult_state_ = CatapultState::PROFILE; |
| 112 | } |
| 113 | [[fallthrough]]; |
| 114 | |
| 115 | case CatapultState::PROFILE: |
| 116 | break; |
| 117 | } |
| 118 | |
| 119 | if (use_profile_) { |
| 120 | if (catapult_state_ != CatapultState::FIRING) { |
| 121 | catapult_mpc_.Reset(); |
| 122 | } |
| 123 | // Select the controller designed for when we have no ball. |
| 124 | catapult_.set_controller_index(1); |
| 125 | |
| 126 | current_horizon_ = 0u; |
| 127 | const double output_voltage = catapult_.UpdateController(catapult_disabled); |
| 128 | if (catapult_voltage != nullptr) { |
| 129 | *catapult_voltage = output_voltage; |
| 130 | } |
| 131 | } |
| 132 | |
| 133 | catapult_.UpdateObserver(catapult_voltage != nullptr |
| 134 | ? (*catapult_voltage * battery_voltage / 12.0) |
| 135 | : 0.0); |
| 136 | |
| 137 | return catapult_.MakeStatus(fbb); |
| 138 | } |
| 139 | |
Austin Schuh | ea83443 | 2024-02-20 21:23:13 -0800 | [diff] [blame] | 140 | } // namespace frc971::control_loops::catapult |