blob: d35bec9607747b3911e142c1b1b0ca4153fffe6e [file] [log] [blame]
James Kuszmaulb1b2d8e2020-02-21 21:11:46 -08001#include "y2020/control_loops/superstructure/turret/aiming.h"
2
James Kuszmaulb1b2d8e2020-02-21 21:11:46 -08003#include "y2020/control_loops/drivetrain/drivetrain_base.h"
4
5namespace y2020 {
6namespace control_loops {
7namespace superstructure {
8namespace turret {
9
10using frc971::control_loops::Pose;
11
12namespace {
James Kuszmaula53c3ac2020-02-22 19:36:01 -080013// The overall length and width of the field, in meters.
14constexpr double kFieldLength = 15.983;
15constexpr double kFieldWidth = 8.212;
16// Height of the center of the port(s) above the ground, in meters.
17constexpr double kPortHeight = 2.494;
18
19// Maximum shot angle at which we will attempt to make the shot into the inner
20// port, in radians. Zero would imply that we could only shoot if we were
21// exactly perpendicular to the target. Larger numbers allow us to aim at the
22// inner port more aggressively, at the risk of being more likely to miss the
23// outer port entirely.
24constexpr double kMaxInnerPortAngle = 20.0 * M_PI / 180.0;
25
26// Distance (in meters) from the edge of the field to the port.
27constexpr double kEdgeOfFieldToPort = 2.404;
28
29// The amount (in meters) that the inner port is set back from the outer port.
30constexpr double kInnerPortBackset = 0.743;
31
32// Minimum distance that we must be from the inner port in order to attempt the
33// shot--this is to account for the fact that if we are too close to the target,
34// then we won't have a clear shot on the inner port.
35constexpr double kMinimumInnerPortShotDistance = 4.0;
36
37Pose ReverseSideOfField(Pose target) {
38 *target.mutable_pos() *= -1;
39 target.set_theta(aos::math::NormalizeAngle(target.rel_theta() + M_PI));
40 return target;
41}
42
James Kuszmaulb1b2d8e2020-02-21 21:11:46 -080043flatbuffers::DetachedBuffer MakePrefilledGoal() {
44 flatbuffers::FlatBufferBuilder fbb;
45 fbb.ForceDefaults(true);
46 Aimer::Goal::Builder builder(fbb);
47 builder.add_unsafe_goal(0);
48 builder.add_goal_velocity(0);
49 builder.add_ignore_profile(true);
50 fbb.Finish(builder.Finish());
51 return fbb.Release();
52}
53} // namespace
54
James Kuszmaula53c3ac2020-02-22 19:36:01 -080055Pose InnerPortPose(aos::Alliance alliance) {
56 const Pose target({kFieldLength / 2 + kInnerPortBackset,
57 -kFieldWidth / 2.0 + kEdgeOfFieldToPort, kPortHeight},
58 0.0);
59 if (alliance == aos::Alliance::kRed) {
60 return ReverseSideOfField(target);
61 }
62 return target;
63}
64
65Pose OuterPortPose(aos::Alliance alliance) {
66 Pose target(
67 {kFieldLength / 2, -kFieldWidth / 2.0 + kEdgeOfFieldToPort, kPortHeight},
68 0.0);
69 if (alliance == aos::Alliance::kRed) {
70 return ReverseSideOfField(target);
71 }
72 return target;
73}
74
James Kuszmaulb1b2d8e2020-02-21 21:11:46 -080075Aimer::Aimer() : goal_(MakePrefilledGoal()) {}
76
James Kuszmaula53c3ac2020-02-22 19:36:01 -080077void Aimer::Update(const Status *status, aos::Alliance alliance) {
78 // This doesn't do anything intelligent with wrapping--it just produces a
James Kuszmaulb1b2d8e2020-02-21 21:11:46 -080079 // result in the range (-pi, pi] rather than taking advantage of the turret's
80 // full range.
James Kuszmaulb1b2d8e2020-02-21 21:11:46 -080081 const Pose robot_pose({status->x(), status->y(), 0}, status->theta());
James Kuszmaula53c3ac2020-02-22 19:36:01 -080082 const Pose inner_port = InnerPortPose(alliance);
83 const Pose outer_port = OuterPortPose(alliance);
84 const Pose robot_pose_from_inner_port = robot_pose.Rebase(&inner_port);
85 const double inner_port_angle = robot_pose_from_inner_port.heading();
86 const double inner_port_distance = robot_pose_from_inner_port.xy_norm();
87 aiming_for_inner_port_ =
88 (std::abs(inner_port_angle) < kMaxInnerPortAngle) &&
89 (inner_port_distance > kMinimumInnerPortShotDistance);
90 const Pose goal =
91 (aiming_for_inner_port_ ? inner_port : outer_port).Rebase(&robot_pose);
James Kuszmaulb1b2d8e2020-02-21 21:11:46 -080092 const double heading_to_goal = goal.heading();
93 CHECK(status->has_localizer());
James Kuszmaula53c3ac2020-02-22 19:36:01 -080094 distance_ = goal.xy_norm();
James Kuszmaulb1b2d8e2020-02-21 21:11:46 -080095 // TODO(james): This code should probably just be in the localizer and have
96 // xdot/ydot get populated in the status message directly... that way we don't
97 // keep duplicating this math.
98 // Also, this doesn't currently take into account the lateral velocity of the
99 // robot. All of this would be helped by just doing this work in the Localizer
100 // itself.
101 const Eigen::Vector2d linear_angular =
102 drivetrain::GetDrivetrainConfig().Tlr_to_la() *
103 Eigen::Vector2d(status->localizer()->left_velocity(),
104 status->localizer()->right_velocity());
105 // X and Y dot are negated because we are interested in the derivative of
106 // (target_pos - robot_pos).
107 const double xdot = -linear_angular(0) * std::cos(status->theta());
108 const double ydot = -linear_angular(0) * std::sin(status->theta());
109 const double rel_x = goal.rel_pos().x();
110 const double rel_y = goal.rel_pos().y();
111 const double squared_norm = rel_x * rel_x + rel_y * rel_y;
112 // If squared_norm gets to be too close to zero, just zero out the relevant
113 // term to prevent NaNs. Note that this doesn't address the chattering that
114 // would likely occur if we were to get excessively close to the target.
115 const double atan_diff = (squared_norm < 1e-3)
116 ? 0.0
117 : (rel_x * ydot - rel_y * xdot) / squared_norm;
118 // heading = atan2(relative_y, relative_x) - robot_theta
119 // dheading / dt = (rel_x * rel_y' - rel_y * rel_x') / (rel_x^2 + rel_y^2) - dtheta / dt
120 const double dheading_dt = atan_diff - linear_angular(1);
121
122 goal_.mutable_message()->mutate_unsafe_goal(heading_to_goal);
123 goal_.mutable_message()->mutate_goal_velocity(dheading_dt);
124}
125
126flatbuffers::Offset<AimerStatus> Aimer::PopulateStatus(
127 flatbuffers::FlatBufferBuilder *fbb) const {
128 AimerStatus::Builder builder(*fbb);
129 builder.add_turret_position(goal_.message().unsafe_goal());
130 builder.add_turret_velocity(goal_.message().goal_velocity());
James Kuszmaula53c3ac2020-02-22 19:36:01 -0800131 builder.add_aiming_for_inner_port(aiming_for_inner_port_);
James Kuszmaulb1b2d8e2020-02-21 21:11:46 -0800132 return builder.Finish();
133}
134
135} // namespace turret
136} // namespace superstructure
137} // namespace control_loops
138} // namespace y2020