001// Copyright (c) FIRST and other WPILib contributors.
002// Open Source Software; you can modify and/or share it under the terms of
003// the WPILib BSD license file in the root directory of this project.
004
005package edu.wpi.first.math.kinematics;
006
007import edu.wpi.first.math.MathSharedStore;
008import edu.wpi.first.math.MathUsageId;
009import edu.wpi.first.math.geometry.Pose2d;
010import edu.wpi.first.math.geometry.Rotation2d;
011
012/**
013 * Class for swerve drive odometry. Odometry allows you to track the robot's position on the field
014 * over a course of a match using readings from your swerve drive encoders and swerve azimuth
015 * encoders.
016 *
017 * <p>Teams can use odometry during the autonomous period for complex tasks like path following.
018 * Furthermore, odometry can be used for latency compensation when using computer-vision systems.
019 */
020public class SwerveDriveOdometry {
021  private final SwerveDriveKinematics m_kinematics;
022  private Pose2d m_poseMeters;
023
024  private Rotation2d m_gyroOffset;
025  private Rotation2d m_previousAngle;
026  private final int m_numModules;
027  private SwerveModulePosition[] m_previousModulePositions;
028
029  /**
030   * Constructs a SwerveDriveOdometry object.
031   *
032   * @param kinematics The swerve drive kinematics for your drivetrain.
033   * @param gyroAngle The angle reported by the gyroscope.
034   * @param modulePositions The wheel positions reported by each module.
035   * @param initialPose The starting position of the robot on the field.
036   */
037  public SwerveDriveOdometry(
038      SwerveDriveKinematics kinematics,
039      Rotation2d gyroAngle,
040      SwerveModulePosition[] modulePositions,
041      Pose2d initialPose) {
042    m_kinematics = kinematics;
043    m_poseMeters = initialPose;
044    m_gyroOffset = m_poseMeters.getRotation().minus(gyroAngle);
045    m_previousAngle = initialPose.getRotation();
046    m_numModules = modulePositions.length;
047
048    m_previousModulePositions = new SwerveModulePosition[m_numModules];
049    for (int index = 0; index < m_numModules; index++) {
050      m_previousModulePositions[index] =
051          new SwerveModulePosition(
052              modulePositions[index].distanceMeters, modulePositions[index].angle);
053    }
054
055    MathSharedStore.reportUsage(MathUsageId.kOdometry_SwerveDrive, 1);
056  }
057
058  /**
059   * Constructs a SwerveDriveOdometry object with the default pose at the origin.
060   *
061   * @param kinematics The swerve drive kinematics for your drivetrain.
062   * @param gyroAngle The angle reported by the gyroscope.
063   * @param modulePositions The wheel positions reported by each module.
064   */
065  public SwerveDriveOdometry(
066      SwerveDriveKinematics kinematics,
067      Rotation2d gyroAngle,
068      SwerveModulePosition[] modulePositions) {
069    this(kinematics, gyroAngle, modulePositions, new Pose2d());
070  }
071
072  /**
073   * Resets the robot's position on the field.
074   *
075   * <p>The gyroscope angle does not need to be reset here on the user's robot code. The library
076   * automatically takes care of offsetting the gyro angle.
077   *
078   * <p>Similarly, module positions do not need to be reset in user code.
079   *
080   * @param gyroAngle The angle reported by the gyroscope.
081   * @param modulePositions The wheel positions reported by each module.,
082   * @param pose The position on the field that your robot is at.
083   */
084  public void resetPosition(
085      Rotation2d gyroAngle, SwerveModulePosition[] modulePositions, Pose2d pose) {
086    if (modulePositions.length != m_numModules) {
087      throw new IllegalArgumentException(
088          "Number of modules is not consistent with number of wheel locations provided in "
089              + "constructor");
090    }
091
092    m_poseMeters = pose;
093    m_previousAngle = pose.getRotation();
094    m_gyroOffset = m_poseMeters.getRotation().minus(gyroAngle);
095    for (int index = 0; index < m_numModules; index++) {
096      m_previousModulePositions[index] =
097          new SwerveModulePosition(
098              modulePositions[index].distanceMeters, modulePositions[index].angle);
099    }
100  }
101
102  /**
103   * Returns the position of the robot on the field.
104   *
105   * @return The pose of the robot (x and y are in meters).
106   */
107  public Pose2d getPoseMeters() {
108    return m_poseMeters;
109  }
110
111  /**
112   * Updates the robot's position on the field using forward kinematics and integration of the pose
113   * over time. This method automatically calculates the current time to calculate period
114   * (difference between two timestamps). The period is used to calculate the change in distance
115   * from a velocity. This also takes in an angle parameter which is used instead of the angular
116   * rate that is calculated from forward kinematics.
117   *
118   * @param gyroAngle The angle reported by the gyroscope.
119   * @param modulePositions The current position of all swerve modules. Please provide the positions
120   *     in the same order in which you instantiated your SwerveDriveKinematics.
121   * @return The new pose of the robot.
122   */
123  public Pose2d update(Rotation2d gyroAngle, SwerveModulePosition[] modulePositions) {
124    if (modulePositions.length != m_numModules) {
125      throw new IllegalArgumentException(
126          "Number of modules is not consistent with number of wheel locations provided in "
127              + "constructor");
128    }
129
130    var moduleDeltas = new SwerveModulePosition[m_numModules];
131    for (int index = 0; index < m_numModules; index++) {
132      var current = modulePositions[index];
133      var previous = m_previousModulePositions[index];
134
135      moduleDeltas[index] =
136          new SwerveModulePosition(current.distanceMeters - previous.distanceMeters, current.angle);
137      previous.distanceMeters = current.distanceMeters;
138    }
139
140    var angle = gyroAngle.plus(m_gyroOffset);
141
142    var twist = m_kinematics.toTwist2d(moduleDeltas);
143    twist.dtheta = angle.minus(m_previousAngle).getRadians();
144
145    var newPose = m_poseMeters.exp(twist);
146
147    m_previousAngle = angle;
148    m_poseMeters = new Pose2d(newPose.getTranslation(), angle);
149
150    return m_poseMeters;
151  }
152}