blob: 97331f37b80e5d9f484a69f1753ca69a7e6c2cb7 [file] [log] [blame]
James Kuszmaulb1b2d8e2020-02-21 21:11:46 -08001#include "y2020/control_loops/superstructure/turret/aiming.h"
2
James Kuszmaulb83d6e12020-02-22 20:44:48 -08003#include "y2020/constants.h"
James Kuszmaulb1b2d8e2020-02-21 21:11:46 -08004#include "y2020/control_loops/drivetrain/drivetrain_base.h"
5
6namespace y2020 {
7namespace control_loops {
8namespace superstructure {
9namespace turret {
10
11using frc971::control_loops::Pose;
12
13namespace {
James Kuszmaula53c3ac2020-02-22 19:36:01 -080014// The overall length and width of the field, in meters.
15constexpr double kFieldLength = 15.983;
16constexpr double kFieldWidth = 8.212;
17// Height of the center of the port(s) above the ground, in meters.
18constexpr double kPortHeight = 2.494;
19
20// Maximum shot angle at which we will attempt to make the shot into the inner
21// port, in radians. Zero would imply that we could only shoot if we were
22// exactly perpendicular to the target. Larger numbers allow us to aim at the
23// inner port more aggressively, at the risk of being more likely to miss the
24// outer port entirely.
25constexpr double kMaxInnerPortAngle = 20.0 * M_PI / 180.0;
26
27// Distance (in meters) from the edge of the field to the port.
28constexpr double kEdgeOfFieldToPort = 2.404;
29
30// The amount (in meters) that the inner port is set back from the outer port.
31constexpr double kInnerPortBackset = 0.743;
32
James Kuszmaulb83d6e12020-02-22 20:44:48 -080033
James Kuszmaula53c3ac2020-02-22 19:36:01 -080034// Minimum distance that we must be from the inner port in order to attempt the
35// shot--this is to account for the fact that if we are too close to the target,
36// then we won't have a clear shot on the inner port.
37constexpr double kMinimumInnerPortShotDistance = 4.0;
38
James Kuszmaulb83d6e12020-02-22 20:44:48 -080039// Amount of buffer, in radians, to leave to help avoid wrapping. I.e., any time
40// that we are in kAvoidEdges mode, we will keep ourselves at least
41// kAntiWrapBuffer radians away from the hardstops.
42constexpr double kAntiWrapBuffer = 0.2;
43
44constexpr double kTurretRange = constants::Values::kTurretRange().range();
45static_assert((kTurretRange - 2.0 * kAntiWrapBuffer) > 2.0 * M_PI,
46 "kAntiWrap buffer should be small enough that we still have 360 "
47 "degrees of range.");
48
James Kuszmaula53c3ac2020-02-22 19:36:01 -080049Pose ReverseSideOfField(Pose target) {
50 *target.mutable_pos() *= -1;
51 target.set_theta(aos::math::NormalizeAngle(target.rel_theta() + M_PI));
52 return target;
53}
54
James Kuszmaulb1b2d8e2020-02-21 21:11:46 -080055flatbuffers::DetachedBuffer MakePrefilledGoal() {
56 flatbuffers::FlatBufferBuilder fbb;
57 fbb.ForceDefaults(true);
58 Aimer::Goal::Builder builder(fbb);
59 builder.add_unsafe_goal(0);
60 builder.add_goal_velocity(0);
61 builder.add_ignore_profile(true);
62 fbb.Finish(builder.Finish());
63 return fbb.Release();
64}
65} // namespace
66
James Kuszmaula53c3ac2020-02-22 19:36:01 -080067Pose InnerPortPose(aos::Alliance alliance) {
68 const Pose target({kFieldLength / 2 + kInnerPortBackset,
69 -kFieldWidth / 2.0 + kEdgeOfFieldToPort, kPortHeight},
70 0.0);
71 if (alliance == aos::Alliance::kRed) {
72 return ReverseSideOfField(target);
73 }
74 return target;
75}
76
77Pose OuterPortPose(aos::Alliance alliance) {
78 Pose target(
79 {kFieldLength / 2, -kFieldWidth / 2.0 + kEdgeOfFieldToPort, kPortHeight},
80 0.0);
81 if (alliance == aos::Alliance::kRed) {
82 return ReverseSideOfField(target);
83 }
84 return target;
85}
86
James Kuszmaulb1b2d8e2020-02-21 21:11:46 -080087Aimer::Aimer() : goal_(MakePrefilledGoal()) {}
88
James Kuszmaulb83d6e12020-02-22 20:44:48 -080089void Aimer::Update(const Status *status, aos::Alliance alliance, Mode mode) {
James Kuszmaulb1b2d8e2020-02-21 21:11:46 -080090 const Pose robot_pose({status->x(), status->y(), 0}, status->theta());
James Kuszmaula53c3ac2020-02-22 19:36:01 -080091 const Pose inner_port = InnerPortPose(alliance);
92 const Pose outer_port = OuterPortPose(alliance);
93 const Pose robot_pose_from_inner_port = robot_pose.Rebase(&inner_port);
94 const double inner_port_angle = robot_pose_from_inner_port.heading();
95 const double inner_port_distance = robot_pose_from_inner_port.xy_norm();
96 aiming_for_inner_port_ =
97 (std::abs(inner_port_angle) < kMaxInnerPortAngle) &&
98 (inner_port_distance > kMinimumInnerPortShotDistance);
99 const Pose goal =
100 (aiming_for_inner_port_ ? inner_port : outer_port).Rebase(&robot_pose);
James Kuszmaulb1b2d8e2020-02-21 21:11:46 -0800101 const double heading_to_goal = goal.heading();
102 CHECK(status->has_localizer());
James Kuszmaula53c3ac2020-02-22 19:36:01 -0800103 distance_ = goal.xy_norm();
James Kuszmaulb1b2d8e2020-02-21 21:11:46 -0800104 // TODO(james): This code should probably just be in the localizer and have
105 // xdot/ydot get populated in the status message directly... that way we don't
106 // keep duplicating this math.
107 // Also, this doesn't currently take into account the lateral velocity of the
108 // robot. All of this would be helped by just doing this work in the Localizer
109 // itself.
110 const Eigen::Vector2d linear_angular =
111 drivetrain::GetDrivetrainConfig().Tlr_to_la() *
112 Eigen::Vector2d(status->localizer()->left_velocity(),
113 status->localizer()->right_velocity());
114 // X and Y dot are negated because we are interested in the derivative of
115 // (target_pos - robot_pos).
116 const double xdot = -linear_angular(0) * std::cos(status->theta());
117 const double ydot = -linear_angular(0) * std::sin(status->theta());
118 const double rel_x = goal.rel_pos().x();
119 const double rel_y = goal.rel_pos().y();
120 const double squared_norm = rel_x * rel_x + rel_y * rel_y;
121 // If squared_norm gets to be too close to zero, just zero out the relevant
122 // term to prevent NaNs. Note that this doesn't address the chattering that
123 // would likely occur if we were to get excessively close to the target.
124 const double atan_diff = (squared_norm < 1e-3)
125 ? 0.0
126 : (rel_x * ydot - rel_y * xdot) / squared_norm;
127 // heading = atan2(relative_y, relative_x) - robot_theta
128 // dheading / dt = (rel_x * rel_y' - rel_y * rel_x') / (rel_x^2 + rel_y^2) - dtheta / dt
129 const double dheading_dt = atan_diff - linear_angular(1);
130
James Kuszmaulb83d6e12020-02-22 20:44:48 -0800131 double range = kTurretRange;
132 if (mode == Mode::kAvoidEdges) {
133 range -= 2.0 * kAntiWrapBuffer;
134 }
135 // Calculate a goal turret heading such that it is within +/- pi of the
136 // current position (i.e., a goal that would minimize the amount the turret
137 // would have to travel).
138 // We then check if this goal would bring us out of range of the valid angles,
139 // and if it would, we reset to be within +/- pi of zero.
140 double turret_heading = goal_.message().unsafe_goal() +
141 aos::math::NormalizeAngle(
142 heading_to_goal - goal_.message().unsafe_goal());
143 if (std::abs(turret_heading - constants::Values::kTurretRange().middle()) >
144 range / 2.0) {
145 turret_heading = aos::math::NormalizeAngle(turret_heading);
146 }
147
148 goal_.mutable_message()->mutate_unsafe_goal(turret_heading);
James Kuszmaulb1b2d8e2020-02-21 21:11:46 -0800149 goal_.mutable_message()->mutate_goal_velocity(dheading_dt);
150}
151
152flatbuffers::Offset<AimerStatus> Aimer::PopulateStatus(
153 flatbuffers::FlatBufferBuilder *fbb) const {
154 AimerStatus::Builder builder(*fbb);
155 builder.add_turret_position(goal_.message().unsafe_goal());
156 builder.add_turret_velocity(goal_.message().goal_velocity());
James Kuszmaula53c3ac2020-02-22 19:36:01 -0800157 builder.add_aiming_for_inner_port(aiming_for_inner_port_);
James Kuszmaulb1b2d8e2020-02-21 21:11:46 -0800158 return builder.Finish();
159}
160
161} // namespace turret
162} // namespace superstructure
163} // namespace control_loops
164} // namespace y2020