2020-12-26 14:12:05 -08:00
|
|
|
// Copyright (c) FIRST and other WPILib contributors.
|
|
|
|
|
// Open Source Software; you can modify and/or share it under the terms of
|
|
|
|
|
// the WPILib BSD license file in the root directory of this project.
|
2020-11-28 17:35:35 -05:00
|
|
|
|
|
|
|
|
#include <limits>
|
2022-12-02 11:36:10 -05:00
|
|
|
#include <numbers>
|
2020-11-28 17:35:35 -05:00
|
|
|
#include <random>
|
2022-12-02 11:36:10 -05:00
|
|
|
#include <tuple>
|
|
|
|
|
|
|
|
|
|
#include <fmt/format.h>
|
2023-08-28 15:13:34 -07:00
|
|
|
#include <gtest/gtest.h>
|
2024-05-12 06:25:42 -07:00
|
|
|
#include <wpi/print.h>
|
2023-01-12 02:04:30 -05:00
|
|
|
#include <wpi/timestamp.h>
|
2020-11-28 17:35:35 -05:00
|
|
|
|
|
|
|
|
#include "frc/estimator/SwerveDrivePoseEstimator.h"
|
|
|
|
|
#include "frc/geometry/Pose2d.h"
|
|
|
|
|
#include "frc/kinematics/SwerveDriveKinematics.h"
|
|
|
|
|
#include "frc/trajectory/TrajectoryGenerator.h"
|
|
|
|
|
|
2022-12-02 11:36:10 -05:00
|
|
|
void testFollowTrajectory(
|
|
|
|
|
const frc::SwerveDriveKinematics<4>& kinematics,
|
|
|
|
|
frc::SwerveDrivePoseEstimator<4>& estimator,
|
|
|
|
|
const frc::Trajectory& trajectory,
|
|
|
|
|
std::function<frc::ChassisSpeeds(frc::Trajectory::State&)>
|
|
|
|
|
chassisSpeedsGenerator,
|
|
|
|
|
std::function<frc::Pose2d(frc::Trajectory::State&)>
|
|
|
|
|
visionMeasurementGenerator,
|
|
|
|
|
const frc::Pose2d& startingPose, const frc::Pose2d& endingPose,
|
|
|
|
|
const units::second_t dt, const units::second_t kVisionUpdateRate,
|
|
|
|
|
const units::second_t kVisionUpdateDelay, const bool checkError,
|
|
|
|
|
const bool debug) {
|
|
|
|
|
wpi::array<frc::SwerveModulePosition, 4> positions{wpi::empty_array};
|
|
|
|
|
|
|
|
|
|
estimator.ResetPosition(frc::Rotation2d{}, positions, startingPose);
|
2020-11-28 17:35:35 -05:00
|
|
|
|
|
|
|
|
std::default_random_engine generator;
|
|
|
|
|
std::normal_distribution<double> distribution(0.0, 1.0);
|
|
|
|
|
|
|
|
|
|
units::second_t t = 0_s;
|
|
|
|
|
|
2022-12-02 11:36:10 -05:00
|
|
|
std::vector<std::pair<units::second_t, frc::Pose2d>> visionPoses;
|
|
|
|
|
std::vector<std::tuple<units::second_t, units::second_t, frc::Pose2d>>
|
|
|
|
|
visionLog;
|
2020-11-28 17:35:35 -05:00
|
|
|
|
|
|
|
|
double maxError = -std::numeric_limits<double>::max();
|
|
|
|
|
double errorSum = 0;
|
|
|
|
|
|
2022-12-02 11:36:10 -05:00
|
|
|
if (debug) {
|
2024-05-12 06:25:42 -07:00
|
|
|
wpi::print("time, est_x, est_y, est_theta, true_x, true_y, true_theta\n");
|
2022-12-02 11:36:10 -05:00
|
|
|
}
|
|
|
|
|
|
2020-11-28 17:35:35 -05:00
|
|
|
while (t < trajectory.TotalTime()) {
|
|
|
|
|
frc::Trajectory::State groundTruthState = trajectory.Sample(t);
|
|
|
|
|
|
2022-12-02 11:36:10 -05:00
|
|
|
// We are due for a new vision measurement if it's been `visionUpdateRate`
|
|
|
|
|
// seconds since the last vision measurement
|
|
|
|
|
if (visionPoses.empty() ||
|
|
|
|
|
visionPoses.back().first + kVisionUpdateRate < t) {
|
|
|
|
|
auto visionPose =
|
|
|
|
|
visionMeasurementGenerator(groundTruthState) +
|
2022-10-25 15:28:36 -04:00
|
|
|
frc::Transform2d{frc::Translation2d{distribution(generator) * 0.1_m,
|
|
|
|
|
distribution(generator) * 0.1_m},
|
2022-12-02 11:36:10 -05:00
|
|
|
frc::Rotation2d{distribution(generator) * 0.05_rad}};
|
|
|
|
|
visionPoses.push_back({t, visionPose});
|
2020-11-28 17:35:35 -05:00
|
|
|
}
|
|
|
|
|
|
2022-12-02 11:36:10 -05:00
|
|
|
// We should apply the oldest vision measurement if it has been
|
|
|
|
|
// `visionUpdateDelay` seconds since it was measured
|
|
|
|
|
if (!visionPoses.empty() &&
|
|
|
|
|
visionPoses.front().first + kVisionUpdateDelay < t) {
|
|
|
|
|
auto visionEntry = visionPoses.front();
|
|
|
|
|
estimator.AddVisionMeasurement(visionEntry.second, visionEntry.first);
|
|
|
|
|
visionPoses.erase(visionPoses.begin());
|
|
|
|
|
visionLog.push_back({t, visionEntry.first, visionEntry.second});
|
|
|
|
|
}
|
2020-11-28 17:35:35 -05:00
|
|
|
|
2022-12-02 11:36:10 -05:00
|
|
|
auto chassisSpeeds = chassisSpeedsGenerator(groundTruthState);
|
2022-10-25 15:28:36 -04:00
|
|
|
|
2022-12-02 11:36:10 -05:00
|
|
|
auto moduleStates = kinematics.ToSwerveModuleStates(chassisSpeeds);
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < 4; i++) {
|
|
|
|
|
positions[i].distance += moduleStates[i].speed * dt;
|
|
|
|
|
positions[i].angle = moduleStates[i].angle;
|
|
|
|
|
}
|
2022-10-25 15:28:36 -04:00
|
|
|
|
2020-11-28 17:35:35 -05:00
|
|
|
auto xhat = estimator.UpdateWithTime(
|
|
|
|
|
t,
|
|
|
|
|
groundTruthState.pose.Rotation() +
|
2022-12-02 11:36:10 -05:00
|
|
|
frc::Rotation2d{distribution(generator) * 0.05_rad} -
|
|
|
|
|
trajectory.InitialPose().Rotation(),
|
|
|
|
|
positions);
|
|
|
|
|
|
|
|
|
|
if (debug) {
|
2024-05-12 06:25:42 -07:00
|
|
|
wpi::print("{}, {}, {}, {}, {}, {}, {}\n", t.value(), xhat.X().value(),
|
2022-12-02 11:36:10 -05:00
|
|
|
xhat.Y().value(), xhat.Rotation().Radians().value(),
|
|
|
|
|
groundTruthState.pose.X().value(),
|
|
|
|
|
groundTruthState.pose.Y().value(),
|
|
|
|
|
groundTruthState.pose.Rotation().Radians().value());
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-25 15:28:36 -04:00
|
|
|
double error = groundTruthState.pose.Translation()
|
|
|
|
|
.Distance(xhat.Translation())
|
|
|
|
|
.value();
|
|
|
|
|
|
|
|
|
|
if (error > maxError) {
|
|
|
|
|
maxError = error;
|
|
|
|
|
}
|
|
|
|
|
errorSum += error;
|
|
|
|
|
|
|
|
|
|
t += dt;
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-02 11:36:10 -05:00
|
|
|
if (debug) {
|
2024-05-12 06:25:42 -07:00
|
|
|
wpi::print("apply_time, measured_time, vision_x, vision_y, vision_theta\n");
|
2022-12-02 11:36:10 -05:00
|
|
|
|
|
|
|
|
units::second_t apply_time;
|
|
|
|
|
units::second_t measure_time;
|
|
|
|
|
frc::Pose2d vision_pose;
|
|
|
|
|
for (auto record : visionLog) {
|
|
|
|
|
std::tie(apply_time, measure_time, vision_pose) = record;
|
2024-05-12 06:25:42 -07:00
|
|
|
wpi::print("{}, {}, {}, {}, {}\n", apply_time.value(),
|
2022-12-02 11:36:10 -05:00
|
|
|
measure_time.value(), vision_pose.X().value(),
|
|
|
|
|
vision_pose.Y().value(),
|
|
|
|
|
vision_pose.Rotation().Radians().value());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
EXPECT_NEAR(endingPose.X().value(),
|
|
|
|
|
estimator.GetEstimatedPosition().X().value(), 0.08);
|
|
|
|
|
EXPECT_NEAR(endingPose.Y().value(),
|
|
|
|
|
estimator.GetEstimatedPosition().Y().value(), 0.08);
|
|
|
|
|
EXPECT_NEAR(endingPose.Rotation().Radians().value(),
|
|
|
|
|
estimator.GetEstimatedPosition().Rotation().Radians().value(),
|
|
|
|
|
0.15);
|
|
|
|
|
|
|
|
|
|
if (checkError) {
|
2023-08-28 15:13:34 -07:00
|
|
|
// NOLINTNEXTLINE(bugprone-integer-division)
|
2022-12-02 11:36:10 -05:00
|
|
|
EXPECT_LT(errorSum / (trajectory.TotalTime() / dt), 0.058);
|
|
|
|
|
EXPECT_LT(maxError, 0.2);
|
|
|
|
|
}
|
2022-10-25 15:28:36 -04:00
|
|
|
}
|
|
|
|
|
|
2022-12-02 11:36:10 -05:00
|
|
|
TEST(SwerveDrivePoseEstimatorTest, AccuracyFacingTrajectory) {
|
2022-10-25 15:28:36 -04:00
|
|
|
frc::SwerveDriveKinematics<4> kinematics{
|
|
|
|
|
frc::Translation2d{1_m, 1_m}, frc::Translation2d{1_m, -1_m},
|
|
|
|
|
frc::Translation2d{-1_m, -1_m}, frc::Translation2d{-1_m, 1_m}};
|
|
|
|
|
|
|
|
|
|
frc::SwerveModulePosition fl;
|
|
|
|
|
frc::SwerveModulePosition fr;
|
|
|
|
|
frc::SwerveModulePosition bl;
|
|
|
|
|
frc::SwerveModulePosition br;
|
|
|
|
|
|
|
|
|
|
frc::SwerveDrivePoseEstimator<4> estimator{
|
2022-12-02 11:36:10 -05:00
|
|
|
kinematics, frc::Rotation2d{}, {fl, fr, bl, br},
|
|
|
|
|
frc::Pose2d{}, {0.1, 0.1, 0.1}, {0.45, 0.45, 0.45}};
|
2022-10-25 15:28:36 -04:00
|
|
|
|
|
|
|
|
frc::Trajectory trajectory = frc::TrajectoryGenerator::GenerateTrajectory(
|
|
|
|
|
std::vector{frc::Pose2d{0_m, 0_m, 45_deg}, frc::Pose2d{3_m, 0_m, -90_deg},
|
|
|
|
|
frc::Pose2d{0_m, 0_m, 135_deg},
|
|
|
|
|
frc::Pose2d{-3_m, 0_m, -90_deg},
|
|
|
|
|
frc::Pose2d{0_m, 0_m, 45_deg}},
|
2022-12-02 11:36:10 -05:00
|
|
|
frc::TrajectoryConfig(2_mps, 2.0_mps_sq));
|
|
|
|
|
|
|
|
|
|
testFollowTrajectory(
|
|
|
|
|
kinematics, estimator, trajectory,
|
|
|
|
|
[&](frc::Trajectory::State& state) {
|
|
|
|
|
return frc::ChassisSpeeds{state.velocity, 0_mps,
|
|
|
|
|
state.velocity * state.curvature};
|
|
|
|
|
},
|
|
|
|
|
[&](frc::Trajectory::State& state) { return state.pose; },
|
|
|
|
|
{0_m, 0_m, frc::Rotation2d{45_deg}}, {0_m, 0_m, frc::Rotation2d{45_deg}},
|
2024-08-14 10:44:00 -07:00
|
|
|
20_ms, 100_ms, 250_ms, true, false);
|
2022-12-02 11:36:10 -05:00
|
|
|
}
|
2022-10-25 15:28:36 -04:00
|
|
|
|
2022-12-02 11:36:10 -05:00
|
|
|
TEST(SwerveDrivePoseEstimatorTest, BadInitialPose) {
|
|
|
|
|
frc::SwerveDriveKinematics<4> kinematics{
|
|
|
|
|
frc::Translation2d{1_m, 1_m}, frc::Translation2d{1_m, -1_m},
|
|
|
|
|
frc::Translation2d{-1_m, -1_m}, frc::Translation2d{-1_m, 1_m}};
|
2022-10-25 15:28:36 -04:00
|
|
|
|
2022-12-02 11:36:10 -05:00
|
|
|
frc::SwerveModulePosition fl;
|
|
|
|
|
frc::SwerveModulePosition fr;
|
|
|
|
|
frc::SwerveModulePosition bl;
|
|
|
|
|
frc::SwerveModulePosition br;
|
2022-10-25 15:28:36 -04:00
|
|
|
|
2022-12-02 11:36:10 -05:00
|
|
|
frc::SwerveDrivePoseEstimator<4> estimator{
|
|
|
|
|
kinematics, frc::Rotation2d{}, {fl, fr, bl, br},
|
|
|
|
|
frc::Pose2d{}, {0.1, 0.1, 0.1}, {0.9, 0.9, 0.9}};
|
2020-11-28 17:35:35 -05:00
|
|
|
|
2022-12-02 11:36:10 -05:00
|
|
|
frc::Trajectory trajectory = frc::TrajectoryGenerator::GenerateTrajectory(
|
|
|
|
|
std::vector{frc::Pose2d{0_m, 0_m, 45_deg}, frc::Pose2d{3_m, 0_m, -90_deg},
|
|
|
|
|
frc::Pose2d{0_m, 0_m, 135_deg},
|
|
|
|
|
frc::Pose2d{-3_m, 0_m, -90_deg},
|
|
|
|
|
frc::Pose2d{0_m, 0_m, 45_deg}},
|
|
|
|
|
frc::TrajectoryConfig(2_mps, 2.0_mps_sq));
|
|
|
|
|
|
|
|
|
|
for (units::degree_t offset_direction_degs = 0_deg;
|
|
|
|
|
offset_direction_degs < 360_deg; offset_direction_degs += 45_deg) {
|
|
|
|
|
for (units::degree_t offset_heading_degs = 0_deg;
|
|
|
|
|
offset_heading_degs < 360_deg; offset_heading_degs += 45_deg) {
|
|
|
|
|
auto pose_offset = frc::Rotation2d{offset_direction_degs};
|
|
|
|
|
auto heading_offset = frc::Rotation2d{offset_heading_degs};
|
|
|
|
|
|
|
|
|
|
auto initial_pose =
|
|
|
|
|
trajectory.InitialPose() +
|
|
|
|
|
frc::Transform2d{frc::Translation2d{pose_offset.Cos() * 1_m,
|
|
|
|
|
pose_offset.Sin() * 1_m},
|
|
|
|
|
heading_offset};
|
|
|
|
|
|
|
|
|
|
testFollowTrajectory(
|
|
|
|
|
kinematics, estimator, trajectory,
|
|
|
|
|
[&](frc::Trajectory::State& state) {
|
|
|
|
|
return frc::ChassisSpeeds{state.velocity, 0_mps,
|
|
|
|
|
state.velocity * state.curvature};
|
|
|
|
|
},
|
|
|
|
|
[&](frc::Trajectory::State& state) { return state.pose; },
|
2024-08-14 10:44:00 -07:00
|
|
|
initial_pose, {0_m, 0_m, frc::Rotation2d{45_deg}}, 20_ms, 100_ms,
|
|
|
|
|
250_ms, false, false);
|
2020-11-28 17:35:35 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-01-12 02:04:30 -05:00
|
|
|
|
|
|
|
|
TEST(SwerveDrivePoseEstimatorTest, SimultaneousVisionMeasurements) {
|
2024-08-17 10:44:34 -04:00
|
|
|
// This tests for multiple vision measurements applied at the same time.
|
2023-01-12 02:04:30 -05:00
|
|
|
// The expected behavior is that all measurements affect the estimated pose.
|
|
|
|
|
// The alternative result is that only one vision measurement affects the
|
|
|
|
|
// outcome. If that were the case, after 1000 measurements, the estimated
|
|
|
|
|
// pose would converge to that measurement.
|
|
|
|
|
frc::SwerveDriveKinematics<4> kinematics{
|
|
|
|
|
frc::Translation2d{1_m, 1_m}, frc::Translation2d{1_m, -1_m},
|
|
|
|
|
frc::Translation2d{-1_m, -1_m}, frc::Translation2d{-1_m, 1_m}};
|
|
|
|
|
|
|
|
|
|
frc::SwerveModulePosition fl;
|
|
|
|
|
frc::SwerveModulePosition fr;
|
|
|
|
|
frc::SwerveModulePosition bl;
|
|
|
|
|
frc::SwerveModulePosition br;
|
|
|
|
|
|
|
|
|
|
frc::SwerveDrivePoseEstimator<4> estimator{
|
|
|
|
|
kinematics, frc::Rotation2d{},
|
|
|
|
|
{fl, fr, bl, br}, frc::Pose2d{1_m, 2_m, frc::Rotation2d{270_deg}},
|
|
|
|
|
{0.1, 0.1, 0.1}, {0.45, 0.45, 0.45}};
|
|
|
|
|
|
|
|
|
|
estimator.UpdateWithTime(0_s, frc::Rotation2d{}, {fl, fr, bl, br});
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < 1000; i++) {
|
|
|
|
|
estimator.AddVisionMeasurement(
|
|
|
|
|
frc::Pose2d{0_m, 0_m, frc::Rotation2d{0_deg}}, 0_s);
|
|
|
|
|
estimator.AddVisionMeasurement(
|
|
|
|
|
frc::Pose2d{3_m, 1_m, frc::Rotation2d{90_deg}}, 0_s);
|
|
|
|
|
estimator.AddVisionMeasurement(
|
|
|
|
|
frc::Pose2d{2_m, 4_m, frc::Rotation2d{180_deg}}, 0_s);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
auto dx = units::math::abs(estimator.GetEstimatedPosition().X() - 0_m);
|
|
|
|
|
auto dy = units::math::abs(estimator.GetEstimatedPosition().Y() - 0_m);
|
|
|
|
|
auto dtheta = units::math::abs(
|
|
|
|
|
estimator.GetEstimatedPosition().Rotation().Radians() - 0_deg);
|
|
|
|
|
|
|
|
|
|
EXPECT_TRUE(dx > 0.08_m || dy > 0.08_m || dtheta > 0.08_rad);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
auto dx = units::math::abs(estimator.GetEstimatedPosition().X() - 3_m);
|
|
|
|
|
auto dy = units::math::abs(estimator.GetEstimatedPosition().Y() - 1_m);
|
|
|
|
|
auto dtheta = units::math::abs(
|
|
|
|
|
estimator.GetEstimatedPosition().Rotation().Radians() - 90_deg);
|
|
|
|
|
|
|
|
|
|
EXPECT_TRUE(dx > 0.08_m || dy > 0.08_m || dtheta > 0.08_rad);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
auto dx = units::math::abs(estimator.GetEstimatedPosition().X() - 2_m);
|
|
|
|
|
auto dy = units::math::abs(estimator.GetEstimatedPosition().Y() - 4_m);
|
|
|
|
|
auto dtheta = units::math::abs(
|
|
|
|
|
estimator.GetEstimatedPosition().Rotation().Radians() - 180_deg);
|
|
|
|
|
|
|
|
|
|
EXPECT_TRUE(dx > 0.08_m || dy > 0.08_m || dtheta > 0.08_rad);
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-02-04 01:04:30 -05:00
|
|
|
|
|
|
|
|
TEST(SwerveDrivePoseEstimatorTest, TestDiscardStaleVisionMeasurements) {
|
|
|
|
|
frc::SwerveDriveKinematics<4> kinematics{
|
|
|
|
|
frc::Translation2d{1_m, 1_m}, frc::Translation2d{1_m, -1_m},
|
|
|
|
|
frc::Translation2d{-1_m, -1_m}, frc::Translation2d{-1_m, 1_m}};
|
|
|
|
|
|
|
|
|
|
frc::SwerveModulePosition fl;
|
|
|
|
|
frc::SwerveModulePosition fr;
|
|
|
|
|
frc::SwerveModulePosition bl;
|
|
|
|
|
frc::SwerveModulePosition br;
|
|
|
|
|
|
|
|
|
|
frc::SwerveDrivePoseEstimator<4> estimator{
|
|
|
|
|
kinematics, frc::Rotation2d{}, {fl, fr, bl, br},
|
|
|
|
|
frc::Pose2d{}, {0.1, 0.1, 0.1}, {0.45, 0.45, 0.45}};
|
|
|
|
|
|
2023-02-26 15:06:37 -08:00
|
|
|
// Add enough measurements to fill up the buffer
|
2024-08-14 10:44:00 -07:00
|
|
|
for (auto time = 0_s; time < 4_s; time += 20_ms) {
|
2023-02-04 01:04:30 -05:00
|
|
|
estimator.UpdateWithTime(time, frc::Rotation2d{}, {fl, fr, bl, br});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto odometryPose = estimator.GetEstimatedPosition();
|
|
|
|
|
|
|
|
|
|
// Apply a vision measurement from 3 seconds ago
|
|
|
|
|
estimator.AddVisionMeasurement(
|
|
|
|
|
frc::Pose2d{frc::Translation2d{10_m, 10_m}, frc::Rotation2d{0.1_rad}},
|
|
|
|
|
1_s, {0.1, 0.1, 0.1});
|
|
|
|
|
|
|
|
|
|
EXPECT_NEAR(odometryPose.X().value(),
|
|
|
|
|
estimator.GetEstimatedPosition().X().value(), 1e-6);
|
|
|
|
|
EXPECT_NEAR(odometryPose.Y().value(),
|
|
|
|
|
estimator.GetEstimatedPosition().Y().value(), 1e-6);
|
|
|
|
|
EXPECT_NEAR(odometryPose.Rotation().Radians().value(),
|
|
|
|
|
estimator.GetEstimatedPosition().Rotation().Radians().value(),
|
|
|
|
|
1e-6);
|
|
|
|
|
}
|
2024-04-27 21:03:37 -07:00
|
|
|
|
|
|
|
|
TEST(SwerveDrivePoseEstimatorTest, TestSampleAt) {
|
|
|
|
|
frc::SwerveDriveKinematics<4> kinematics{
|
|
|
|
|
frc::Translation2d{1_m, 1_m}, frc::Translation2d{1_m, -1_m},
|
|
|
|
|
frc::Translation2d{-1_m, -1_m}, frc::Translation2d{-1_m, 1_m}};
|
|
|
|
|
frc::SwerveDrivePoseEstimator estimator{
|
|
|
|
|
kinematics,
|
|
|
|
|
frc::Rotation2d{},
|
|
|
|
|
{frc::SwerveModulePosition{}, frc::SwerveModulePosition{},
|
|
|
|
|
frc::SwerveModulePosition{}, frc::SwerveModulePosition{}},
|
|
|
|
|
frc::Pose2d{},
|
|
|
|
|
{1.0, 1.0, 1.0},
|
|
|
|
|
{1.0, 1.0, 1.0}};
|
|
|
|
|
|
|
|
|
|
// Returns empty when null
|
|
|
|
|
EXPECT_EQ(std::nullopt, estimator.SampleAt(1_s));
|
|
|
|
|
|
|
|
|
|
// Add odometry measurements, but don't fill up the buffer
|
|
|
|
|
// Add a tiny tolerance for the upper bound because of floating point rounding
|
|
|
|
|
// error
|
|
|
|
|
for (double time = 1; time <= 2 + 1e-9; time += 0.02) {
|
|
|
|
|
wpi::array<frc::SwerveModulePosition, 4> wheelPositions{
|
|
|
|
|
{frc::SwerveModulePosition{units::meter_t{time}, frc::Rotation2d{}},
|
|
|
|
|
frc::SwerveModulePosition{units::meter_t{time}, frc::Rotation2d{}},
|
|
|
|
|
frc::SwerveModulePosition{units::meter_t{time}, frc::Rotation2d{}},
|
|
|
|
|
frc::SwerveModulePosition{units::meter_t{time}, frc::Rotation2d{}}}};
|
|
|
|
|
estimator.UpdateWithTime(units::second_t{time}, frc::Rotation2d{},
|
|
|
|
|
wheelPositions);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Sample at an added time
|
|
|
|
|
EXPECT_EQ(std::optional(frc::Pose2d{1.02_m, 0_m, frc::Rotation2d{}}),
|
|
|
|
|
estimator.SampleAt(1.02_s));
|
|
|
|
|
// Sample between updates (test interpolation)
|
|
|
|
|
EXPECT_EQ(std::optional(frc::Pose2d{1.01_m, 0_m, frc::Rotation2d{}}),
|
|
|
|
|
estimator.SampleAt(1.01_s));
|
|
|
|
|
// Sampling before the oldest value returns the oldest value
|
|
|
|
|
EXPECT_EQ(std::optional(frc::Pose2d{1_m, 0_m, frc::Rotation2d{}}),
|
|
|
|
|
estimator.SampleAt(0.5_s));
|
|
|
|
|
// Sampling after the newest value returns the newest value
|
|
|
|
|
EXPECT_EQ(std::optional(frc::Pose2d{2_m, 0_m, frc::Rotation2d{}}),
|
|
|
|
|
estimator.SampleAt(2.5_s));
|
|
|
|
|
|
|
|
|
|
// Add a vision measurement after the odometry measurements (while keeping all
|
|
|
|
|
// of the old odometry measurements)
|
|
|
|
|
estimator.AddVisionMeasurement(frc::Pose2d{2_m, 0_m, frc::Rotation2d{1_rad}},
|
|
|
|
|
2.2_s);
|
|
|
|
|
|
|
|
|
|
// Make sure nothing changed (except the newest value)
|
|
|
|
|
EXPECT_EQ(std::optional(frc::Pose2d{1.02_m, 0_m, frc::Rotation2d{}}),
|
|
|
|
|
estimator.SampleAt(1.02_s));
|
|
|
|
|
EXPECT_EQ(std::optional(frc::Pose2d{1.01_m, 0_m, frc::Rotation2d{}}),
|
|
|
|
|
estimator.SampleAt(1.01_s));
|
|
|
|
|
EXPECT_EQ(std::optional(frc::Pose2d{1_m, 0_m, frc::Rotation2d{}}),
|
|
|
|
|
estimator.SampleAt(0.5_s));
|
|
|
|
|
|
|
|
|
|
// Add a vision measurement before the odometry measurements that's still in
|
|
|
|
|
// the buffer
|
|
|
|
|
estimator.AddVisionMeasurement(frc::Pose2d{1_m, 0.2_m, frc::Rotation2d{}},
|
|
|
|
|
0.9_s);
|
|
|
|
|
|
|
|
|
|
// Everything should be the same except Y is 0.1 (halfway between 0 and 0.2)
|
|
|
|
|
EXPECT_EQ(std::optional(frc::Pose2d{1.02_m, 0.1_m, frc::Rotation2d{}}),
|
|
|
|
|
estimator.SampleAt(1.02_s));
|
|
|
|
|
EXPECT_EQ(std::optional(frc::Pose2d{1.01_m, 0.1_m, frc::Rotation2d{}}),
|
|
|
|
|
estimator.SampleAt(1.01_s));
|
|
|
|
|
EXPECT_EQ(std::optional(frc::Pose2d{1_m, 0.1_m, frc::Rotation2d{}}),
|
|
|
|
|
estimator.SampleAt(0.5_s));
|
|
|
|
|
EXPECT_EQ(std::optional(frc::Pose2d{2_m, 0.1_m, frc::Rotation2d{}}),
|
|
|
|
|
estimator.SampleAt(2.5_s));
|
|
|
|
|
}
|