WPILibC++ 2023.4.3
SwerveDrivePoseEstimator.h
Go to the documentation of this file.
1// Copyright (c) FIRST and other WPILib contributors.
2// Open Source Software; you can modify and/or share it under the terms of
3// the WPILib BSD license file in the root directory of this project.
4
5#pragma once
6
7#include <cmath>
8
9#include <fmt/format.h>
10#include <wpi/SymbolExports.h>
11#include <wpi/array.h>
12#include <wpi/timestamp.h>
13
14#include "frc/EigenCore.h"
15#include "frc/geometry/Pose2d.h"
20#include "units/time.h"
21#include "wpimath/MathShared.h"
22
23namespace frc {
24
25/**
26 * This class wraps Swerve Drive Odometry to fuse latency-compensated
27 * vision measurements with swerve drive encoder distance measurements. It is
28 * intended to be a drop-in for SwerveDriveOdometry.
29 *
30 * Update() should be called every robot loop.
31 *
32 * AddVisionMeasurement() can be called as infrequently as you want; if you
33 * never call it, then this class will behave as regular encoder
34 * odometry.
35 */
36template <size_t NumModules>
38 public:
39 /**
40 * Constructs a SwerveDrivePoseEstimator with default standard deviations
41 * for the model and vision measurements.
42 *
43 * The default standard deviations of the model states are
44 * 0.1 meters for x, 0.1 meters for y, and 0.1 radians for heading.
45 * The default standard deviations of the vision measurements are
46 * 0.9 meters for x, 0.9 meters for y, and 0.9 radians for heading.
47 *
48 * @param kinematics A correctly-configured kinematics object for your
49 * drivetrain.
50 * @param gyroAngle The current gyro angle.
51 * @param modulePositions The current distance and rotation measurements of
52 * the swerve modules.
53 * @param initialPose The starting pose estimate.
54 */
57 const Rotation2d& gyroAngle,
59 const Pose2d& initialPose)
60 : SwerveDrivePoseEstimator{kinematics, gyroAngle,
61 modulePositions, initialPose,
62 {0.1, 0.1, 0.1}, {0.9, 0.9, 0.9}} {}
63
64 /**
65 * Constructs a SwerveDrivePoseEstimator.
66 *
67 * @param kinematics A correctly-configured kinematics object for your
68 * drivetrain.
69 * @param gyroAngle The current gyro angle.
70 * @param modulePositions The current distance and rotation measurements of
71 * the swerve modules.
72 * @param initialPose The starting pose estimate.
73 * @param stateStdDevs Standard deviations of the pose estimate (x position in
74 * meters, y position in meters, and heading in radians). Increase these
75 * numbers to trust your state estimate less.
76 * @param visionMeasurementStdDevs Standard deviations of the vision pose
77 * measurement (x position in meters, y position in meters, and heading in
78 * radians). Increase these numbers to trust the vision pose measurement
79 * less.
80 */
83 const Rotation2d& gyroAngle,
85 const Pose2d& initialPose, const wpi::array<double, 3>& stateStdDevs,
86 const wpi::array<double, 3>& visionMeasurementStdDevs)
87 : m_kinematics{kinematics},
88 m_odometry{kinematics, gyroAngle, modulePositions, initialPose} {
89 for (size_t i = 0; i < 3; ++i) {
90 m_q[i] = stateStdDevs[i] * stateStdDevs[i];
91 }
92
93 SetVisionMeasurementStdDevs(visionMeasurementStdDevs);
94 }
95
96 /**
97 * Resets the robot's position on the field.
98 *
99 * The gyroscope angle does not need to be reset in the user's robot code.
100 * The library automatically takes care of offsetting the gyro angle.
101 *
102 * @param gyroAngle The angle reported by the gyroscope.
103 * @param modulePositions The current distance and rotation measurements of
104 * the swerve modules.
105 * @param pose The position on the field that your robot is at.
106 */
108 const Rotation2d& gyroAngle,
110 const Pose2d& pose) {
111 // Reset state estimate and error covariance
112 m_odometry.ResetPosition(gyroAngle, modulePositions, pose);
113 m_poseBuffer.Clear();
114 }
115
116 /**
117 * Gets the estimated robot pose.
118 *
119 * @return The estimated robot pose in meters.
120 */
121 Pose2d GetEstimatedPosition() const { return m_odometry.GetPose(); }
122
123 /**
124 * Sets the pose estimator's trust in vision measurements. This might be used
125 * to change trust in vision measurements after the autonomous period, or to
126 * change trust as distance to a vision target increases.
127 *
128 * @param visionMeasurementStdDevs Standard deviations of the vision pose
129 * measurement (x position in meters, y position in meters, and heading in
130 * radians). Increase these numbers to trust the vision pose measurement
131 * less.
132 */
134 const wpi::array<double, 3>& visionMeasurementStdDevs) {
136 for (size_t i = 0; i < 3; ++i) {
137 r[i] = visionMeasurementStdDevs[i] * visionMeasurementStdDevs[i];
138 }
139
140 // Solve for closed form Kalman gain for continuous Kalman filter with A = 0
141 // and C = I. See wpimath/algorithms.md.
142 for (size_t row = 0; row < 3; ++row) {
143 if (m_q[row] == 0.0) {
144 m_visionK(row, row) = 0.0;
145 } else {
146 m_visionK(row, row) =
147 m_q[row] / (m_q[row] + std::sqrt(m_q[row] * r[row]));
148 }
149 }
150 }
151
152 /**
153 * Adds a vision measurement to the Kalman Filter. This will correct the
154 * odometry pose estimate while still accounting for measurement noise.
155 *
156 * This method can be called as infrequently as you want, as long as you are
157 * calling Update() every loop.
158 *
159 * To promote stability of the pose estimate and make it robust to bad vision
160 * data, we recommend only adding vision measurements that are already within
161 * one meter or so of the current pose estimate.
162 *
163 * @param visionRobotPose The pose of the robot as measured by the vision
164 * camera.
165 * @param timestamp The timestamp of the vision measurement in seconds. Note
166 * that if you don't use your own time source by calling UpdateWithTime()
167 * then you must use a timestamp with an epoch since FPGA startup (i.e.,
168 * the epoch of this timestamp is the same epoch as
169 * frc::Timer::GetFPGATimestamp().) This means that you should use
170 * frc::Timer::GetFPGATimestamp() as your time source or sync the epochs.
171 */
172 void AddVisionMeasurement(const Pose2d& visionRobotPose,
173 units::second_t timestamp) {
174 // Step 0: If this measurement is old enough to be outside the pose buffer's
175 // timespan, skip.
176 if (!m_poseBuffer.GetInternalBuffer().empty() &&
177 m_poseBuffer.GetInternalBuffer().front().first - kBufferDuration >
178 timestamp) {
179 return;
180 }
181
182 // Step 1: Get the estimated pose from when the vision measurement was made.
183 auto sample = m_poseBuffer.Sample(timestamp);
184
185 if (!sample.has_value()) {
186 return;
187 }
188
189 // Step 2: Measure the twist between the odometry pose and the vision pose
190 auto twist = sample.value().pose.Log(visionRobotPose);
191
192 // Step 3: We should not trust the twist entirely, so instead we scale this
193 // twist by a Kalman gain matrix representing how much we trust vision
194 // measurements compared to our current pose.
195 frc::Vectord<3> k_times_twist =
196 m_visionK * frc::Vectord<3>{twist.dx.value(), twist.dy.value(),
197 twist.dtheta.value()};
198
199 // Step 4: Convert back to Twist2d
200 Twist2d scaledTwist{units::meter_t{k_times_twist(0)},
201 units::meter_t{k_times_twist(1)},
202 units::radian_t{k_times_twist(2)}};
203
204 // Step 5: Reset Odometry to state at sample with vision adjustment.
205 m_odometry.ResetPosition(sample.value().gyroAngle,
206 sample.value().modulePositions,
207 sample.value().pose.Exp(scaledTwist));
208
209 // Step 6: Record the current pose to allow multiple measurements from the
210 // same timestamp
211 m_poseBuffer.AddSample(timestamp,
212 {GetEstimatedPosition(), sample.value().gyroAngle,
213 sample.value().modulePositions});
214
215 // Step 7: Replay odometry inputs between sample time and latest recorded
216 // sample to update the pose buffer and correct odometry.
217 auto internal_buf = m_poseBuffer.GetInternalBuffer();
218
219 auto upper_bound = std::lower_bound(
220 internal_buf.begin(), internal_buf.end(), timestamp,
221 [](const auto& pair, auto t) { return t > pair.first; });
222
223 for (auto entry = upper_bound; entry != internal_buf.end(); entry++) {
224 UpdateWithTime(entry->first, entry->second.gyroAngle,
225 entry->second.modulePositions);
226 }
227 }
228
229 /**
230 * Adds a vision measurement to the Kalman Filter. This will correct the
231 * odometry pose estimate while still accounting for measurement noise.
232 *
233 * This method can be called as infrequently as you want, as long as you are
234 * calling Update() every loop.
235 *
236 * To promote stability of the pose estimate and make it robust to bad vision
237 * data, we recommend only adding vision measurements that are already within
238 * one meter or so of the current pose estimate.
239 *
240 * Note that the vision measurement standard deviations passed into this
241 * method will continue to apply to future measurements until a subsequent
242 * call to SetVisionMeasurementStdDevs() or this method.
243 *
244 * @param visionRobotPose The pose of the robot as measured by the vision
245 * camera.
246 * @param timestamp The timestamp of the vision measurement in seconds. Note
247 * that if you don't use your own time source by calling UpdateWithTime(),
248 * then you must use a timestamp with an epoch since FPGA startup (i.e.,
249 * the epoch of this timestamp is the same epoch as
250 * frc::Timer::GetFPGATimestamp(). This means that you should use
251 * frc::Timer::GetFPGATimestamp() as your time source in this case.
252 * @param visionMeasurementStdDevs Standard deviations of the vision pose
253 * measurement (x position in meters, y position in meters, and heading in
254 * radians). Increase these numbers to trust the vision pose measurement
255 * less.
256 */
258 const Pose2d& visionRobotPose, units::second_t timestamp,
259 const wpi::array<double, 3>& visionMeasurementStdDevs) {
260 SetVisionMeasurementStdDevs(visionMeasurementStdDevs);
261 AddVisionMeasurement(visionRobotPose, timestamp);
262 }
263
264 /**
265 * Updates the pose estimator with wheel encoder and gyro information. This
266 * should be called every loop.
267 *
268 * @param gyroAngle The current gyro angle.
269 * @param modulePositions The current distance and rotation measurements of
270 * the swerve modules.
271 * @return The estimated robot pose in meters.
272 */
274 const Rotation2d& gyroAngle,
275 const wpi::array<SwerveModulePosition, NumModules>& modulePositions) {
276 return UpdateWithTime(wpi::math::MathSharedStore::GetTimestamp(), gyroAngle,
277 modulePositions);
278 }
279
280 /**
281 * Updates the pose estimator with wheel encoder and gyro information. This
282 * should be called every loop.
283 *
284 * @param currentTime Time at which this method was called, in seconds.
285 * @param gyroAngle The current gyro angle.
286 * @param modulePositions The current distance traveled and rotations of
287 * the swerve modules.
288 * @return The estimated robot pose in meters.
289 */
291 units::second_t currentTime, const Rotation2d& gyroAngle,
292 const wpi::array<SwerveModulePosition, NumModules>& modulePositions) {
293 m_odometry.Update(gyroAngle, modulePositions);
294
295 wpi::array<SwerveModulePosition, NumModules> internalModulePositions{
297
298 for (size_t i = 0; i < NumModules; i++) {
299 internalModulePositions[i].distance = modulePositions[i].distance;
300 internalModulePositions[i].angle = modulePositions[i].angle;
301 }
302
303 m_poseBuffer.AddSample(currentTime, {GetEstimatedPosition(), gyroAngle,
304 internalModulePositions});
305
306 return GetEstimatedPosition();
307 }
308
309 private:
310 struct InterpolationRecord {
311 // The pose observed given the current sensor inputs and the previous pose.
312 Pose2d pose;
313
314 // The current gyroscope angle.
315 Rotation2d gyroAngle;
316
317 // The distances traveled and rotations measured at each module.
319
320 /**
321 * Checks equality between this InterpolationRecord and another object.
322 *
323 * @param other The other object.
324 * @return Whether the two objects are equal.
325 */
326 bool operator==(const InterpolationRecord& other) const = default;
327
328 /**
329 * Checks inequality between this InterpolationRecord and another object.
330 *
331 * @param other The other object.
332 * @return Whether the two objects are not equal.
333 */
334 bool operator!=(const InterpolationRecord& other) const = default;
335
336 /**
337 * Interpolates between two InterpolationRecords.
338 *
339 * @param endValue The end value for the interpolation.
340 * @param i The interpolant (fraction).
341 *
342 * @return The interpolated state.
343 */
344 InterpolationRecord Interpolate(
346 InterpolationRecord endValue, double i) const {
347 if (i < 0) {
348 return *this;
349 } else if (i > 1) {
350 return endValue;
351 } else {
352 // Find the new module distances.
355 // Find the distance between this measurement and the
356 // interpolated measurement.
359
360 for (size_t i = 0; i < NumModules; i++) {
361 modulePositions[i].distance =
362 wpi::Lerp(this->modulePositions[i].distance,
363 endValue.modulePositions[i].distance, i);
364 modulePositions[i].angle =
365 wpi::Lerp(this->modulePositions[i].angle,
366 endValue.modulePositions[i].angle, i);
367
368 modulesDelta[i].distance =
369 modulePositions[i].distance - this->modulePositions[i].distance;
370 modulesDelta[i].angle = modulePositions[i].angle;
371 }
372
373 // Find the new gyro angle.
374 auto gyro = wpi::Lerp(this->gyroAngle, endValue.gyroAngle, i);
375
376 // Create a twist to represent this changed based on the interpolated
377 // sensor inputs.
378 auto twist = kinematics.ToTwist2d(modulesDelta);
379 twist.dtheta = (gyro - gyroAngle).Radians();
380
381 return {pose.Exp(twist), gyro, modulePositions};
382 }
383 }
384 };
385
386 static constexpr units::second_t kBufferDuration = 1.5_s;
387
388 SwerveDriveKinematics<NumModules>& m_kinematics;
389 SwerveDriveOdometry<NumModules> m_odometry;
391 Eigen::Matrix3d m_visionK = Eigen::Matrix3d::Zero();
392
393 TimeInterpolatableBuffer<InterpolationRecord> m_poseBuffer{
394 kBufferDuration, [this](const InterpolationRecord& start,
395 const InterpolationRecord& end, double t) {
396 return start.Interpolate(this->m_kinematics, end, t);
397 }};
398};
399
400extern template class EXPORT_TEMPLATE_DECLARE(WPILIB_DLLEXPORT)
401 SwerveDrivePoseEstimator<4>;
402
403} // namespace frc
EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE RowXpr row(Index i)
This is the const version of row(). *‍/.
Definition: BlockMethods.h:1118
#define EXPORT_TEMPLATE_DECLARE(export)
Definition: SymbolExports.h:94
#define WPILIB_DLLEXPORT
Definition: SymbolExports.h:36
Represents a 2D pose containing translational and rotational elements.
Definition: Pose2d.h:25
Pose2d Exp(const Twist2d &twist) const
Obtain a new Pose2d from a (constant curvature) velocity.
A rotation in a 2D coordinate frame represented by a point on the unit circle (cosine and sine).
Definition: Rotation2d.h:26
Helper class that converts a chassis velocity (dx, dy, and dtheta components) into individual module ...
Definition: SwerveDriveKinematics.h:47
Twist2d ToTwist2d(ModuleDeltas &&... wheelDeltas) const
Performs forward kinematics to return the resulting Twist2d from the given module position deltas.
Definition: SwerveDriveKinematics.inc:101
This class wraps Swerve Drive Odometry to fuse latency-compensated vision measurements with swerve dr...
Definition: SwerveDrivePoseEstimator.h:37
void AddVisionMeasurement(const Pose2d &visionRobotPose, units::second_t timestamp, const wpi::array< double, 3 > &visionMeasurementStdDevs)
Adds a vision measurement to the Kalman Filter.
Definition: SwerveDrivePoseEstimator.h:257
Pose2d Update(const Rotation2d &gyroAngle, const wpi::array< SwerveModulePosition, NumModules > &modulePositions)
Updates the pose estimator with wheel encoder and gyro information.
Definition: SwerveDrivePoseEstimator.h:273
void ResetPosition(const Rotation2d &gyroAngle, const wpi::array< SwerveModulePosition, NumModules > &modulePositions, const Pose2d &pose)
Resets the robot's position on the field.
Definition: SwerveDrivePoseEstimator.h:107
SwerveDrivePoseEstimator(SwerveDriveKinematics< NumModules > &kinematics, const Rotation2d &gyroAngle, const wpi::array< SwerveModulePosition, NumModules > &modulePositions, const Pose2d &initialPose, const wpi::array< double, 3 > &stateStdDevs, const wpi::array< double, 3 > &visionMeasurementStdDevs)
Constructs a SwerveDrivePoseEstimator.
Definition: SwerveDrivePoseEstimator.h:81
void SetVisionMeasurementStdDevs(const wpi::array< double, 3 > &visionMeasurementStdDevs)
Sets the pose estimator's trust in vision measurements.
Definition: SwerveDrivePoseEstimator.h:133
Pose2d UpdateWithTime(units::second_t currentTime, const Rotation2d &gyroAngle, const wpi::array< SwerveModulePosition, NumModules > &modulePositions)
Updates the pose estimator with wheel encoder and gyro information.
Definition: SwerveDrivePoseEstimator.h:290
SwerveDrivePoseEstimator(SwerveDriveKinematics< NumModules > &kinematics, const Rotation2d &gyroAngle, const wpi::array< SwerveModulePosition, NumModules > &modulePositions, const Pose2d &initialPose)
Constructs a SwerveDrivePoseEstimator with default standard deviations for the model and vision measu...
Definition: SwerveDrivePoseEstimator.h:55
void AddVisionMeasurement(const Pose2d &visionRobotPose, units::second_t timestamp)
Adds a vision measurement to the Kalman Filter.
Definition: SwerveDrivePoseEstimator.h:172
Pose2d GetEstimatedPosition() const
Gets the estimated robot pose.
Definition: SwerveDrivePoseEstimator.h:121
This class is a wrapper around std::array that does compile time size checking.
Definition: array.h:25
static units::second_t GetTimestamp()
Definition: MathShared.h:77
auto sqrt(const UnitType &value) noexcept -> unit_t< square_root< typename units::traits::unit_t_traits< UnitType >::unit_type >, typename units::traits::unit_t_traits< UnitType >::underlying_type, linear_scale >
computes the square root of value
Definition: math.h:483
static EIGEN_DEPRECATED const end_t end
Definition: IndexedViewHelper.h:181
Definition: AprilTagFieldLayout.h:22
Eigen::Vector< double, Size > Vectord
Definition: EigenCore.h:12
bool operator==(const Value &lhs, const Value &rhs)
constexpr bool operator!=(const UNIT_LIB_DEFAULT_TYPE lhs, const Units &rhs) noexcept
Definition: base.h:2716
constexpr T Lerp(const T &startValue, const T &endValue, double t)
Linearly interpolates between two values.
Definition: MathExtras.h:950
constexpr empty_array_t empty_array
Definition: array.h:15
A change in distance along a 2D arc since the last pose update.
Definition: Twist2d.h:21
units::radian_t dtheta
Angular "dtheta" component (radians)
Definition: Twist2d.h:35