James Kuszmaul | 851b396 | 2022-02-27 16:42:15 -0800 | [diff] [blame] | 1 | #include "frc971/control_loops/aiming/aiming.h" |
| 2 | |
James Kuszmaul | 851b396 | 2022-02-27 16:42:15 -0800 | [diff] [blame] | 3 | #include "gtest/gtest.h" |
Philipp Schrader | 790cb54 | 2023-07-05 21:06:52 -0700 | [diff] [blame] | 4 | |
James Kuszmaul | 851b396 | 2022-02-27 16:42:15 -0800 | [diff] [blame] | 5 | #include "frc971/constants.h" |
Philipp Schrader | 790cb54 | 2023-07-05 21:06:52 -0700 | [diff] [blame] | 6 | #include "frc971/control_loops/pose.h" |
James Kuszmaul | 851b396 | 2022-02-27 16:42:15 -0800 | [diff] [blame] | 7 | |
| 8 | namespace frc971::control_loops::aiming::testing { |
| 9 | |
| 10 | TEST(AimerTest, StandingStill) { |
| 11 | const Pose target({0.0, 0.0, 0.0}, 0.0); |
| 12 | Pose robot_pose({1.0, 0.0, 0.0}, 0.0); |
| 13 | const constants::Range range{-4.5, 4.5, -4.0, 4.0}; |
| 14 | const double kBallSpeed = 10.0; |
| 15 | // Robot is ahead of target, should have to turret to 180 deg to shoot. |
| 16 | TurretGoal goal = AimerGoal( |
| 17 | ShotConfig{target, ShotMode::kShootOnTheFly, range, kBallSpeed, 0.0, 0.0}, |
| 18 | RobotState{robot_pose, {0.0, 0.0}, 0.0, 0.0}); |
| 19 | EXPECT_FLOAT_EQ(M_PI, goal.position); |
| 20 | EXPECT_FLOAT_EQ(0.0, goal.velocity); |
| 21 | EXPECT_FLOAT_EQ(1.0, goal.virtual_shot_distance); |
| 22 | EXPECT_FLOAT_EQ(1.0, goal.target_distance); |
| 23 | |
| 24 | // If there is a turret offset, it should get compensated out. |
| 25 | goal = AimerGoal(ShotConfig{target, ShotMode::kShootOnTheFly, range, |
| 26 | kBallSpeed, 0.0, M_PI}, |
| 27 | RobotState{robot_pose, {0.0, 0.0}, 0.0, 0.0}); |
| 28 | EXPECT_FLOAT_EQ(0.0, goal.position); |
| 29 | |
| 30 | robot_pose = Pose({-1.0, 0.0, 0.0}, 1.0); |
| 31 | goal = AimerGoal( |
| 32 | ShotConfig{target, ShotMode::kShootOnTheFly, range, kBallSpeed, 0.0, 0.0}, |
| 33 | RobotState{robot_pose, {0.0, 0.0}, 0.0, 0.0}); |
| 34 | EXPECT_FLOAT_EQ(-1.0, goal.position); |
| 35 | EXPECT_FLOAT_EQ(0.0, goal.velocity); |
| 36 | EXPECT_FLOAT_EQ(1.0, goal.virtual_shot_distance); |
| 37 | EXPECT_FLOAT_EQ(1.0, goal.target_distance); |
| 38 | |
| 39 | // Test that we handle the case that where we are right on top of the target. |
| 40 | robot_pose = Pose({0.0, 0.0, 0.0}, 0.0); |
| 41 | goal = AimerGoal( |
| 42 | ShotConfig{target, ShotMode::kShootOnTheFly, range, kBallSpeed, 0.0, 0.0}, |
| 43 | RobotState{robot_pose, {0.0, 0.0}, 0.0, 0.0}); |
| 44 | EXPECT_FLOAT_EQ(0.0, goal.position); |
| 45 | EXPECT_FLOAT_EQ(0.0, goal.velocity); |
| 46 | EXPECT_FLOAT_EQ(0.0, goal.virtual_shot_distance); |
| 47 | EXPECT_FLOAT_EQ(0.0, goal.target_distance); |
| 48 | } |
| 49 | |
| 50 | // Test that spinning in place results in correct velocity goals. |
| 51 | TEST(AimerTest, SpinningRobot) { |
| 52 | const Pose target({0.0, 0.0, 0.0}, 0.0); |
| 53 | Pose robot_pose({-1.0, 0.0, 0.0}, 0.0); |
| 54 | const constants::Range range{-4.5, 4.5, -4.0, 4.0}; |
| 55 | const double kBallSpeed = 10.0; |
| 56 | TurretGoal goal = AimerGoal( |
| 57 | ShotConfig{target, ShotMode::kShootOnTheFly, range, kBallSpeed, 0.0, 0.0}, |
| 58 | RobotState{robot_pose, {0.0, 0.0}, 971.0, 0.0}); |
| 59 | EXPECT_FLOAT_EQ(0.0, goal.position); |
| 60 | EXPECT_FLOAT_EQ(-971.0, goal.velocity); |
| 61 | } |
| 62 | |
| 63 | // Tests that when we drive straight away from the target we don't have to spin |
| 64 | // the turret. |
| 65 | TEST(AimerTest, DrivingAwayFromTarget) { |
| 66 | const Pose target({0.0, 0.0, 0.0}, 0.0); |
| 67 | Pose robot_pose({-1.0, 0.0, 0.0}, 0.0); |
| 68 | const constants::Range range{-4.5, 4.5, -4.0, 4.0}; |
| 69 | const double kBallSpeed = 10.0; |
| 70 | TurretGoal goal = AimerGoal( |
| 71 | ShotConfig{target, ShotMode::kStatic, range, kBallSpeed, 0.0, 0.0}, |
| 72 | RobotState{robot_pose, {-1.0, 0.0}, 0.0, 0.0}); |
| 73 | EXPECT_FLOAT_EQ(0.0, goal.position); |
| 74 | EXPECT_FLOAT_EQ(0.0, goal.velocity); |
| 75 | EXPECT_FLOAT_EQ(1.0, goal.virtual_shot_distance); |
| 76 | EXPECT_FLOAT_EQ(1.0, goal.target_distance); |
| 77 | // Next, try with shooting-on-the-fly enabled--because we are driving straight |
| 78 | // away from the target, only the goal distance should be impacted. |
| 79 | goal = AimerGoal( |
| 80 | ShotConfig{target, ShotMode::kShootOnTheFly, range, kBallSpeed, 0.0, 0.0}, |
| 81 | RobotState{robot_pose, {-1.0, 0.0}, 0.0, 0.0}); |
| 82 | EXPECT_FLOAT_EQ(0.0, goal.position); |
| 83 | EXPECT_FLOAT_EQ(0.0, goal.velocity); |
| 84 | EXPECT_FLOAT_EQ(1.111, goal.virtual_shot_distance); |
| 85 | EXPECT_FLOAT_EQ(1.0, goal.target_distance); |
| 86 | } |
| 87 | |
| 88 | // Tests that when we drive perpendicular to the target, we do have to spin. |
| 89 | TEST(AimerTest, DrivingLateralToTarget) { |
| 90 | const Pose target({0.0, 0.0, 0.0}, 0.0); |
| 91 | Pose robot_pose({0.0, -1.0, 0.0}, 0.0); |
| 92 | const constants::Range range{-4.5, 4.5, -4.0, 4.0}; |
| 93 | const double kBallSpeed = 10.0; |
| 94 | TurretGoal goal = AimerGoal( |
| 95 | ShotConfig{target, ShotMode::kStatic, range, kBallSpeed, 0.0, 0.0}, |
| 96 | RobotState{robot_pose, {1.0, 0.0}, 0.0, 0.0}); |
| 97 | EXPECT_FLOAT_EQ(M_PI_2, goal.position); |
| 98 | EXPECT_FLOAT_EQ(1.0, goal.velocity); |
| 99 | EXPECT_FLOAT_EQ(1.0, goal.virtual_shot_distance); |
| 100 | EXPECT_FLOAT_EQ(1.0, goal.target_distance); |
| 101 | // Next, test with shooting-on-the-fly enabled, The goal numbers should all be |
| 102 | // slightly offset due to the robot velocity. |
| 103 | goal = AimerGoal( |
| 104 | ShotConfig{target, ShotMode::kShootOnTheFly, range, kBallSpeed, 0.0, 0.0}, |
| 105 | RobotState{robot_pose, {1.0, 0.0}, 0.0, 0.0}); |
| 106 | // Confirm that the turret heading goal is a bit more than pi / 2, but not by |
| 107 | // too much. |
| 108 | EXPECT_LT(M_PI_2 + 0.01, goal.position); |
| 109 | EXPECT_GT(M_PI_2 + 0.5, goal.position); |
| 110 | // Similarly, the turret velocity goal should be a bit less than 1.0, |
| 111 | // since the turret is no longer at exactly a right angle. |
| 112 | EXPECT_LT(0.9, goal.velocity); |
| 113 | EXPECT_GT(0.999, goal.velocity); |
| 114 | // And the distance to the goal should be a bit greater than 1.0. |
| 115 | EXPECT_LT(1.00001, goal.virtual_shot_distance); |
| 116 | EXPECT_GT(1.1, goal.virtual_shot_distance); |
| 117 | EXPECT_FLOAT_EQ(1.0, goal.target_distance); |
| 118 | } |
| 119 | |
| 120 | // Confirms that when we move the turret heading so that it would be entirely |
| 121 | // out of the normal range of motion that we send a valid (in-range) goal. |
| 122 | // I.e., test that we have some hysteresis, but that it doesn't take us |
| 123 | // out-of-range. |
| 124 | TEST(AimerTest, WrapWhenOutOfRange) { |
| 125 | // Start ourselves needing a turret angle of 0.0. |
| 126 | const Pose target({0.0, 0.0, 0.0}, 0.0); |
| 127 | Pose robot_pose({-1.0, 0.0, 0.0}, 0.0); |
| 128 | const constants::Range range{-5.5, 5.5, -5.0, 5.0}; |
| 129 | const double kBallSpeed = 10.0; |
| 130 | TurretGoal goal = AimerGoal( |
| 131 | ShotConfig{target, ShotMode::kShootOnTheFly, range, kBallSpeed, 0.0, 0.0}, |
| 132 | RobotState{robot_pose, {0.0, 0.0}, 0.0, 0.0}); |
| 133 | EXPECT_FLOAT_EQ(0.0, goal.position); |
| 134 | EXPECT_FLOAT_EQ(0.0, goal.velocity); |
| 135 | |
| 136 | // Rotate a bit... |
| 137 | robot_pose = Pose({-1.0, 0.0, 0.0}, 2.0); |
| 138 | goal = AimerGoal( |
| 139 | ShotConfig{target, ShotMode::kShootOnTheFly, range, kBallSpeed, 0.0, 0.0}, |
| 140 | RobotState{robot_pose, {0.0, 0.0}, 0.0, goal.position}); |
| 141 | EXPECT_FLOAT_EQ(-2.0, goal.position); |
| 142 | EXPECT_FLOAT_EQ(0.0, goal.velocity); |
| 143 | |
| 144 | // Rotate to the soft stop. |
| 145 | robot_pose = Pose({-1.0, 0.0, 0.0}, 4.0); |
| 146 | goal = AimerGoal( |
| 147 | ShotConfig{target, ShotMode::kShootOnTheFly, range, kBallSpeed, 0.0, 0.0}, |
| 148 | RobotState{robot_pose, {0.0, 0.0}, 0.0, goal.position}); |
| 149 | EXPECT_FLOAT_EQ(-4.0, goal.position); |
| 150 | EXPECT_FLOAT_EQ(0.0, goal.velocity); |
| 151 | |
| 152 | // Rotate past the hard stop. |
| 153 | robot_pose = Pose({-1.0, 0.0, 0.0}, 0.0); |
| 154 | goal = AimerGoal( |
| 155 | ShotConfig{target, ShotMode::kShootOnTheFly, range, kBallSpeed, 0.0, 0.0}, |
| 156 | RobotState{robot_pose, {0.0, 0.0}, 0.0, goal.position}); |
| 157 | EXPECT_FLOAT_EQ(0.0, goal.position); |
| 158 | EXPECT_FLOAT_EQ(0.0, goal.velocity); |
| 159 | } |
| 160 | |
Stephan Pleines | 8ef61ed | 2024-02-19 20:13:06 -0800 | [diff] [blame] | 161 | // Tests that we fail if the ballspeed is zero. Can't hit anything if the ball |
| 162 | // isn't moving. |
| 163 | TEST(AimerDeathTest, ZeroBallSpeed) { |
| 164 | const Pose target({0.0, 0.0, 0.0}, 0.0); |
| 165 | Pose robot_pose({1.0, 1.0, 1.0}, 0.0); |
| 166 | const constants::Range range{-2.36, 2.36, -2.16, 2.16}; |
| 167 | const double kBallSpeed = 0.0; |
| 168 | EXPECT_DEATH(AimerGoal(ShotConfig{target, ShotMode::kShootOnTheFly, range, |
| 169 | kBallSpeed, 0.0, 0.0}, |
| 170 | RobotState{robot_pose, {0.0, 0.0}, 0.0, 0.0}), |
| 171 | "Ball speed must be positive."); |
| 172 | } |
| 173 | |
James Kuszmaul | 851b396 | 2022-02-27 16:42:15 -0800 | [diff] [blame] | 174 | } // namespace frc971::control_loops::aiming::testing |