James Kuszmaul | b1b2d8e | 2020-02-21 21:11:46 -0800 | [diff] [blame^] | 1 | #include "y2020/control_loops/superstructure/turret/aiming.h" |
| 2 | |
| 3 | #include "frc971/control_loops/pose.h" |
| 4 | #include "y2020/control_loops/drivetrain/drivetrain_base.h" |
| 5 | |
| 6 | namespace y2020 { |
| 7 | namespace control_loops { |
| 8 | namespace superstructure { |
| 9 | namespace turret { |
| 10 | |
| 11 | using frc971::control_loops::Pose; |
| 12 | |
| 13 | namespace { |
| 14 | flatbuffers::DetachedBuffer MakePrefilledGoal() { |
| 15 | flatbuffers::FlatBufferBuilder fbb; |
| 16 | fbb.ForceDefaults(true); |
| 17 | Aimer::Goal::Builder builder(fbb); |
| 18 | builder.add_unsafe_goal(0); |
| 19 | builder.add_goal_velocity(0); |
| 20 | builder.add_ignore_profile(true); |
| 21 | fbb.Finish(builder.Finish()); |
| 22 | return fbb.Release(); |
| 23 | } |
| 24 | } // namespace |
| 25 | |
| 26 | Aimer::Aimer() : goal_(MakePrefilledGoal()) {} |
| 27 | |
| 28 | void Aimer::Update(const Status *status) { |
| 29 | // For now, just do enough to keep the turret pointed straight towards (0, 0). |
| 30 | // Don't worry about properly handling shooting on the fly--just try to keep |
| 31 | // the turret pointed straight towards one target. |
| 32 | // This also doesn't do anything intelligent with wrapping--it just produces a |
| 33 | // result in the range (-pi, pi] rather than taking advantage of the turret's |
| 34 | // full range. |
| 35 | Pose goal({0, 0, 0}, 0); |
| 36 | const Pose robot_pose({status->x(), status->y(), 0}, status->theta()); |
| 37 | goal = goal.Rebase(&robot_pose); |
| 38 | const double heading_to_goal = goal.heading(); |
| 39 | CHECK(status->has_localizer()); |
| 40 | // TODO(james): This code should probably just be in the localizer and have |
| 41 | // xdot/ydot get populated in the status message directly... that way we don't |
| 42 | // keep duplicating this math. |
| 43 | // Also, this doesn't currently take into account the lateral velocity of the |
| 44 | // robot. All of this would be helped by just doing this work in the Localizer |
| 45 | // itself. |
| 46 | const Eigen::Vector2d linear_angular = |
| 47 | drivetrain::GetDrivetrainConfig().Tlr_to_la() * |
| 48 | Eigen::Vector2d(status->localizer()->left_velocity(), |
| 49 | status->localizer()->right_velocity()); |
| 50 | // X and Y dot are negated because we are interested in the derivative of |
| 51 | // (target_pos - robot_pos). |
| 52 | const double xdot = -linear_angular(0) * std::cos(status->theta()); |
| 53 | const double ydot = -linear_angular(0) * std::sin(status->theta()); |
| 54 | const double rel_x = goal.rel_pos().x(); |
| 55 | const double rel_y = goal.rel_pos().y(); |
| 56 | const double squared_norm = rel_x * rel_x + rel_y * rel_y; |
| 57 | // If squared_norm gets to be too close to zero, just zero out the relevant |
| 58 | // term to prevent NaNs. Note that this doesn't address the chattering that |
| 59 | // would likely occur if we were to get excessively close to the target. |
| 60 | const double atan_diff = (squared_norm < 1e-3) |
| 61 | ? 0.0 |
| 62 | : (rel_x * ydot - rel_y * xdot) / squared_norm; |
| 63 | // heading = atan2(relative_y, relative_x) - robot_theta |
| 64 | // dheading / dt = (rel_x * rel_y' - rel_y * rel_x') / (rel_x^2 + rel_y^2) - dtheta / dt |
| 65 | const double dheading_dt = atan_diff - linear_angular(1); |
| 66 | |
| 67 | goal_.mutable_message()->mutate_unsafe_goal(heading_to_goal); |
| 68 | goal_.mutable_message()->mutate_goal_velocity(dheading_dt); |
| 69 | } |
| 70 | |
| 71 | flatbuffers::Offset<AimerStatus> Aimer::PopulateStatus( |
| 72 | flatbuffers::FlatBufferBuilder *fbb) const { |
| 73 | AimerStatus::Builder builder(*fbb); |
| 74 | builder.add_turret_position(goal_.message().unsafe_goal()); |
| 75 | builder.add_turret_velocity(goal_.message().goal_velocity()); |
| 76 | return builder.Finish(); |
| 77 | } |
| 78 | |
| 79 | } // namespace turret |
| 80 | } // namespace superstructure |
| 81 | } // namespace control_loops |
| 82 | } // namespace y2020 |