WPILib Swerve Classes
You've been using these classes since Lesson 3. This lesson makes you fluent in all of them — not just the methods you've already called, but the full API surface of ChassisSpeeds, SwerveDriveKinematics, SwerveModuleState, Pose2d, Rotation2d, and Translation2d. Knowing the full API means fewer trips to the documentation and faster debugging.
By the end of this lesson, you will:
- Use all major factory methods on
Rotation2dandTranslation2dand explain when to prefer each - Describe the difference between
Pose2d.plus(Transform2d),Pose2d.relativeTo(), andPose2d.transformBy() - Call
ChassisSpeeds.fromFieldRelativeSpeeds()and explain its geometry - Call
SwerveDriveKinematics.toChassisSpeeds()(forward kinematics) and state when it's used - Explain the difference between
SwerveModuleStateandSwerveModulePositionand which is used where - Use
Pose2d.toString()and geometry math for SmartDashboard debugging
Rotation2d — The Angle Wrapper
Rotation2d is WPILib's type-safe angle object. It stores an angle internally in radians but provides factory methods in whatever unit you prefer. Using it instead of raw doubles prevents unit confusion and enables geometry operations.
SwerveModuleState or Pose2d.Math.sin(), Math.cos(), or any standard Java math function.PositionVoltage or comparing to CANcoder values.Rotation2d.fromDegrees(350).plus(Rotation2d.fromDegrees(20)) returns 10° — the wrap is handled automatically.Rotation2d.fromDegrees(pigeon.getYaw().getValueAsDouble()).unaryMinus()..plus() but semantically clearer when composing 2D transforms.Translation2d and Pose2d — Position on the Field
| Class | What it represents | Common usage in swerve |
|---|---|---|
| Translation2d(x, y) | A 2D point or displacement in meters. No heading. | Module positions in kinematics constructor; vision target positions; scoring location waypoints |
| Pose2d(x, y, rotation) | A position + heading. The robot's full state on the field. | Odometry output; auto starting positions; PathPlanner waypoints; AprilTag pose |
| Transform2d(translation, rotation) | A relative change in position and heading. | Moving from camera pose to robot pose; predicting robot position after a known motion |
Pose2d robotPose = getPose(); // e.g. (3.0, 2.0, 45°)
// Where is the target relative to the robot?
Pose2d targetField = new Pose2d(5.0, 2.0, new Rotation2d());
Pose2d targetRelative = targetField.relativeTo(robotPose);
// targetRelative.getX() = forward distance to target
// targetRelative.getY() = lateral offset to target
// Where will the robot be after a known transform?
Transform2d motion = new Transform2d(
new Translation2d(1.0, 0.0), new Rotation2d()); // 1m forward
Pose2d predicted = robotPose.plus(motion);
// Distance from robot to target (for approach logic)
double dist = robotPose.getTranslation()
.getDistance(targetField.getTranslation());
// ── ChassisSpeeds API ─────────────────────────────
// Constructor (robot-oriented)
ChassisSpeeds robotSpeeds = new ChassisSpeeds(2.0, 0.5, 0.3);
// fromFieldRelativeSpeeds (field-oriented — Lesson 9)
ChassisSpeeds fieldSpeeds = ChassisSpeeds.fromFieldRelativeSpeeds(
2.0, 0.5, 0.3, getHeading());
// toChassisSpeeds (forward kinematics)
ChassisSpeeds actual = m_kinematics.toChassisSpeeds(
m_fl.getState(), m_fr.getState(),
m_bl.getState(), m_br.getState());
// ── State vs Position distinction ─────────────────
SwerveModuleState state = m_fl.getState(); // velocity + angle
SwerveModulePosition pos = m_fl.getPosition(); // distance + angle
The State vs. Position Distinction — A Common Confusion
Both SwerveModuleState and SwerveModulePosition contain an angle. But the other field is completely different, and using the wrong one in the wrong context produces subtle bugs with no compiler error.
| Class | Speed/Distance field | Angle field | Used by | Analogous to |
|---|---|---|---|---|
| SwerveModuleState | speedMetersPerSecond — instantaneous velocity |
angle (Rotation2d) |
setDesiredState(), toChassisSpeeds(), SmartDashboard |
Speedometer reading + compass |
| SwerveModulePosition | distanceMeters — accumulated travel since reset |
angle (Rotation2d) |
SwerveDriveOdometry.update(), resetPosition() |
Odometer reading + compass |
If you accidentally pass a SwerveModuleState array where the odometry expects a SwerveModulePosition array (or vice versa), Java's type system will catch it at compile time — they are different types. But if you pass position data from before a resetPosition() call, the odometry receives stale data and starts from the wrong baseline. Always call getModulePositions() immediately before every update() and every resetPosition() call — never cache the positions array across multiple loop cycles.
Complete Class Reference
A quick-reference card for every WPILib swerve-related class. Bookmark this mentally — these are the methods you'll reach for repeatedly throughout the season.
toSwerveModuleStates().toSwerveModuleStates().setDesiredState(). Mutates a copy — doesn't modify the original.state.speedMetersPerSecond.distanceMeters increases as the wheel rolls forward, decreases rolling backward.periodic() loop. Returns updated Pose2d.autonomousInit() and by PathPlanner at path start.Knowledge Check
1. You need to know if the robot is within 0.5 meters of a scoring target at (8.2, 4.1) on the field. The robot's current pose is (7.8, 3.8). Which method call gives you the distance?
2. SwerveModuleState.optimize() takes (desired, current) as arguments. If you accidentally swap them and pass (current, desired), what happens?
3. Rotation2d.fromDegrees(350).plus(Rotation2d.fromDegrees(30)) — what does this return?
Explore the Full API in SmartDashboard
- In your drivetrain's
periodic(), add these four lines: publishgetPose().getX(),getPose().getY(),getPose().getRotation().getDegrees(), and the forward-kinematics result fromgetChassisSpeeds().vxMetersPerSecond. Drive the robot and confirm each updates correctly. The forward-kinematics vx should approximately match what you command indrive(). - Add a test that computes the distance from the robot's current pose to a fixed point (e.g., the center of the field at (8.23, 4.0)). Publish this distance to SmartDashboard as "Drive/DistanceToCenter". Drive around and watch it change. This is the building block of proximity-based autonomous triggers.
- Verify
ChassisSpeeds.discretize()is in yourdrive()method. Remove it temporarily and drive a slow forward+rotation arc — you should see slight curvature. Re-add it and repeat. The path should straighten. If you can't perceive the difference at your test speed, try a very slow speed (0.1 m/s) with high rotation (1.0 rad/s). - Stretch goal: Write a unit test (or a console check in teleopInit) that verifies:
Rotation2d.fromDegrees(90).plus(Rotation2d.fromDegrees(200))equals approximatelyRotation2d.fromDegrees(-70). Then verifyRotation2d.fromDegrees(-90).unaryMinus()equals 90°. These tests confirm your angle arithmetic understanding before you need it in field-oriented control.