blob: 0b2c5f39d604b38c7a5c4f2e0f096dc3186b2d7b [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;
James Kuszmaul851b3962022-02-27 16:42:15 -080012using frc971::control_loops::aiming::RobotState;
Austin Schuh9b2c3342023-02-05 11:31:25 -080013using frc971::control_loops::aiming::ShotConfig;
14using frc971::control_loops::aiming::TurretGoal;
James Kuszmaulb1b2d8e2020-02-21 21:11:46 -080015
James Kuszmaul3b393d72020-02-26 19:43:51 -080016// Shooting-on-the-fly concept:
17// The current way that we manage shooting-on-the fly endeavors to be reasonably
18// simple, until we get a chance to see how the actual dynamics play out.
19// Essentially, we assume that the robot's velocity will represent a constant
20// offset to the ball's velocity over the entire trajectory to the goal and
21// then offset the target that we are pointing at based on that.
22// Let us assume that, if the robot shoots while not moving, regardless of shot
23// distance, the ball's average speed-over-ground to the target will be a
24// constant s_shot (this implies that if the robot is driving straight towards
25// the target, the actual ball speed-over-ground will be greater than s_shot).
26// We will define things in the robot's coordinate frame. We will be shooting
27// at a target that is at position (target_x, target_y) in the robot frame. The
28// robot is travelling at (v_robot_x, v_robot_y). In order to shoot the ball,
29// we need to generate some virtual target (virtual_x, virtual_y) that we will
30// shoot at as if we were standing still. The total time-of-flight to that
31// target will be t_shot = norm2(virtual_x, virtual_y) / s_shot.
32// we will have virtual_x + v_robot_x * t_shot = target_x, and the same
33// for y. This gives us three equations and three unknowns (virtual_x,
34// virtual_y, and t_shot), and given appropriate assumptions, can be solved
35// analytically. However, doing so is obnoxious and given appropriate functions
36// for t_shot may not be feasible. As such, instead of actually solving the
37// equation analytically, we will use an iterative solution where we maintain
38// a current virtual target estimate. We start with this estimate as if the
39// robot is stationary. We then use this estimate to calculate t_shot, and
40// calculate the next value for the virtual target.
41
James Kuszmaulb1b2d8e2020-02-21 21:11:46 -080042namespace {
James Kuszmaula53c3ac2020-02-22 19:36:01 -080043// The overall length and width of the field, in meters.
44constexpr double kFieldLength = 15.983;
45constexpr double kFieldWidth = 8.212;
46// Height of the center of the port(s) above the ground, in meters.
47constexpr double kPortHeight = 2.494;
48
49// Maximum shot angle at which we will attempt to make the shot into the inner
50// port, in radians. Zero would imply that we could only shoot if we were
51// exactly perpendicular to the target. Larger numbers allow us to aim at the
52// inner port more aggressively, at the risk of being more likely to miss the
53// outer port entirely.
Austin Schuhc69fb1a2021-10-24 17:41:55 -070054constexpr double kMaxInnerPortAngle = 15.0 * M_PI / 180.0;
James Kuszmaula53c3ac2020-02-22 19:36:01 -080055
James Kuszmaul519585d2020-03-08 22:32:48 -070056// Distance (in meters) from the edge of the field to the port, with some
57// compensation to ensure that our definition of where the target is matches
58// that reported by the cameras.
James Kuszmaul87c41052021-10-29 14:13:40 -070059constexpr double kEdgeOfFieldToPort = 2.347;
James Kuszmaula53c3ac2020-02-22 19:36:01 -080060
61// The amount (in meters) that the inner port is set back from the outer port.
62constexpr double kInnerPortBackset = 0.743;
63
James Kuszmaul3b393d72020-02-26 19:43:51 -080064// Average speed-over-ground of the ball on its way to the target. Our current
65// model assumes constant ball velocity regardless of shot distance.
66// TODO(james): Is this an appropriate model? For the outer port it should be
67// good enough that it doesn't really matter, but for the inner port it may be
68// more appropriate to do something more dynamic--however, it is not yet clear
69// how we would best estimate speed-over-ground given a hood angle + shooter
70// speed. Assuming a constant average speed over the course of the trajectory
71// should be reasonable, since all we are trying to do here is calculate an
72// overall time-of-flight (we don't actually care about the ball speed itself).
milind-uf7fadbf2021-11-07 14:10:54 -080073constexpr double kBallSpeedOverGround = 17.0; // m/s
James Kuszmaulb83d6e12020-02-22 20:44:48 -080074
James Kuszmaula53c3ac2020-02-22 19:36:01 -080075// Minimum distance that we must be from the inner port in order to attempt the
76// shot--this is to account for the fact that if we are too close to the target,
77// then we won't have a clear shot on the inner port.
milind-uf7fadbf2021-11-07 14:10:54 -080078constexpr double kMinimumInnerPortShotDistance = 1.9;
James Kuszmaula53c3ac2020-02-22 19:36:01 -080079
James Kuszmaulb83d6e12020-02-22 20:44:48 -080080// Amount of buffer, in radians, to leave to help avoid wrapping. I.e., any time
81// that we are in kAvoidEdges mode, we will keep ourselves at least
82// kAntiWrapBuffer radians away from the hardstops.
83constexpr double kAntiWrapBuffer = 0.2;
84
James Kuszmaul64c13b72020-03-01 11:17:31 -080085// If the turret is at zero, then it will be at this angle relative to pointed
86// straight forwards on the robot.
87constexpr double kTurretZeroOffset = M_PI;
88
James Kuszmaulb83d6e12020-02-22 20:44:48 -080089constexpr double kTurretRange = constants::Values::kTurretRange().range();
90static_assert((kTurretRange - 2.0 * kAntiWrapBuffer) > 2.0 * M_PI,
91 "kAntiWrap buffer should be small enough that we still have 360 "
92 "degrees of range.");
93
James Kuszmaula53c3ac2020-02-22 19:36:01 -080094Pose ReverseSideOfField(Pose target) {
95 *target.mutable_pos() *= -1;
96 target.set_theta(aos::math::NormalizeAngle(target.rel_theta() + M_PI));
97 return target;
98}
99
James Kuszmaulb1b2d8e2020-02-21 21:11:46 -0800100flatbuffers::DetachedBuffer MakePrefilledGoal() {
101 flatbuffers::FlatBufferBuilder fbb;
102 fbb.ForceDefaults(true);
103 Aimer::Goal::Builder builder(fbb);
104 builder.add_unsafe_goal(0);
105 builder.add_goal_velocity(0);
106 builder.add_ignore_profile(true);
107 fbb.Finish(builder.Finish());
108 return fbb.Release();
109}
110} // namespace
111
James Kuszmaula53c3ac2020-02-22 19:36:01 -0800112Pose InnerPortPose(aos::Alliance alliance) {
113 const Pose target({kFieldLength / 2 + kInnerPortBackset,
114 -kFieldWidth / 2.0 + kEdgeOfFieldToPort, kPortHeight},
James Kuszmaul519585d2020-03-08 22:32:48 -0700115 M_PI);
James Kuszmaula53c3ac2020-02-22 19:36:01 -0800116 if (alliance == aos::Alliance::kRed) {
117 return ReverseSideOfField(target);
118 }
119 return target;
120}
121
122Pose OuterPortPose(aos::Alliance alliance) {
123 Pose target(
124 {kFieldLength / 2, -kFieldWidth / 2.0 + kEdgeOfFieldToPort, kPortHeight},
James Kuszmaul519585d2020-03-08 22:32:48 -0700125 M_PI);
James Kuszmaula53c3ac2020-02-22 19:36:01 -0800126 if (alliance == aos::Alliance::kRed) {
127 return ReverseSideOfField(target);
128 }
129 return target;
130}
131
Austin Schuh9b2c3342023-02-05 11:31:25 -0800132Aimer::Aimer()
133 : goal_(MakePrefilledGoal()),
134 Tlr_to_la_(drivetrain::GetDrivetrainConfig().Tlr_to_la()) {}
James Kuszmaulb1b2d8e2020-02-21 21:11:46 -0800135
James Kuszmaul3b393d72020-02-26 19:43:51 -0800136void Aimer::Update(const Status *status, aos::Alliance alliance,
137 WrapMode wrap_mode, ShotMode shot_mode) {
James Kuszmaulb1b2d8e2020-02-21 21:11:46 -0800138 const Pose robot_pose({status->x(), status->y(), 0}, status->theta());
James Kuszmaula53c3ac2020-02-22 19:36:01 -0800139 const Pose inner_port = InnerPortPose(alliance);
140 const Pose outer_port = OuterPortPose(alliance);
141 const Pose robot_pose_from_inner_port = robot_pose.Rebase(&inner_port);
James Kuszmaul3b393d72020-02-26 19:43:51 -0800142
James Kuszmaulb1b2d8e2020-02-21 21:11:46 -0800143 // TODO(james): This code should probably just be in the localizer and have
144 // xdot/ydot get populated in the status message directly... that way we don't
145 // keep duplicating this math.
146 // Also, this doesn't currently take into account the lateral velocity of the
147 // robot. All of this would be helped by just doing this work in the Localizer
148 // itself.
149 const Eigen::Vector2d linear_angular =
Austin Schuh9b2c3342023-02-05 11:31:25 -0800150 Tlr_to_la_ * Eigen::Vector2d(status->localizer()->left_velocity(),
151 status->localizer()->right_velocity());
James Kuszmaul3b393d72020-02-26 19:43:51 -0800152 const double xdot = linear_angular(0) * std::cos(status->theta());
153 const double ydot = linear_angular(0) * std::sin(status->theta());
154
James Kuszmaul88c5cef2021-10-23 13:17:36 -0700155 inner_port_angle_ = robot_pose_from_inner_port.heading();
Austin Schuh30e45ff2021-10-16 18:33:53 -0700156 const double inner_port_distance = robot_pose_from_inner_port.rel_pos().x();
James Kuszmaul519585d2020-03-08 22:32:48 -0700157 // Add a bit of hysteresis so that we don't jump between aiming for the inner
158 // and outer ports.
159 const double max_inner_port_angle =
160 aiming_for_inner_port_ ? 1.2 * kMaxInnerPortAngle : kMaxInnerPortAngle;
161 const double min_inner_port_distance =
Austin Schuh30e45ff2021-10-16 18:33:53 -0700162 aiming_for_inner_port_ ? (kMinimumInnerPortShotDistance - 0.3)
James Kuszmaul519585d2020-03-08 22:32:48 -0700163 : kMinimumInnerPortShotDistance;
James Kuszmaul3b393d72020-02-26 19:43:51 -0800164 aiming_for_inner_port_ =
James Kuszmaul88c5cef2021-10-23 13:17:36 -0700165 (std::abs(inner_port_angle_) < max_inner_port_angle) &&
James Kuszmaul519585d2020-03-08 22:32:48 -0700166 (inner_port_distance > min_inner_port_distance);
James Kuszmaul851b3962022-02-27 16:42:15 -0800167 const Pose goal = aiming_for_inner_port_ ? inner_port : outer_port;
James Kuszmaul3b393d72020-02-26 19:43:51 -0800168
James Kuszmaul851b3962022-02-27 16:42:15 -0800169 const struct TurretGoal turret_goal =
170 frc971::control_loops::aiming::AimerGoal(
171 ShotConfig{goal, shot_mode, constants::Values::kTurretRange(),
172 kBallSpeedOverGround,
173 wrap_mode == WrapMode::kAvoidEdges ? kAntiWrapBuffer : 0.0,
174 kTurretZeroOffset},
175 RobotState{robot_pose,
176 {xdot, ydot},
177 linear_angular(1),
178 goal_.message().unsafe_goal()});
James Kuszmaul3b393d72020-02-26 19:43:51 -0800179
James Kuszmaul851b3962022-02-27 16:42:15 -0800180 target_distance_ = turret_goal.target_distance;
181 shot_distance_ = turret_goal.virtual_shot_distance;
James Kuszmaul3b393d72020-02-26 19:43:51 -0800182
James Kuszmaul851b3962022-02-27 16:42:15 -0800183 goal_.mutable_message()->mutate_unsafe_goal(turret_goal.position);
James Kuszmaul519585d2020-03-08 22:32:48 -0700184 goal_.mutable_message()->mutate_goal_velocity(
James Kuszmaul851b3962022-02-27 16:42:15 -0800185 std::clamp(turret_goal.velocity, -2.0, 2.0));
James Kuszmaulb1b2d8e2020-02-21 21:11:46 -0800186}
187
188flatbuffers::Offset<AimerStatus> Aimer::PopulateStatus(
189 flatbuffers::FlatBufferBuilder *fbb) const {
190 AimerStatus::Builder builder(*fbb);
191 builder.add_turret_position(goal_.message().unsafe_goal());
192 builder.add_turret_velocity(goal_.message().goal_velocity());
James Kuszmaula53c3ac2020-02-22 19:36:01 -0800193 builder.add_aiming_for_inner_port(aiming_for_inner_port_);
James Kuszmaul519585d2020-03-08 22:32:48 -0700194 builder.add_target_distance(target_distance_);
James Kuszmaul88c5cef2021-10-23 13:17:36 -0700195 builder.add_inner_port_angle(inner_port_angle_);
James Kuszmaul519585d2020-03-08 22:32:48 -0700196 builder.add_shot_distance(DistanceToGoal());
James Kuszmaulb1b2d8e2020-02-21 21:11:46 -0800197 return builder.Finish();
198}
199
200} // namespace turret
201} // namespace superstructure
202} // namespace control_loops
203} // namespace y2020