# Welcome to YAGSL

Brought to you by Yet Another Generic Swerve Library (YAGSL)

{% hint style="warning" %}
If your swerve drive uses only [Falcon500](https://store.ctr-electronics.com/falcon-500-powered-by-talon-fx/)/[Kraken](https://store.ctr-electronics.com/kraken-x60/)/[TalonFXS](https://store.ctr-electronics.com/products/talon-fxs), [Pigeon2.0](https://store.ctr-electronics.com/pigeon-2/), and [CANCoder](https://store.ctr-electronics.com/cancoder/) from [CTRE](https://pro.docs.ctr-electronics.com/en/latest/index.html) please also consider the [Tuner X Swerve Drive Generator](https://pro.docs.ctr-electronics.com/en/latest/docs/tuner/tuner-swerve/index.html)!
{% endhint %}

<figure><img src="/files/upJUeUFlxj0yVIs52SP7" alt=""><figcaption></figcaption></figure>

## Overview

YAGSL is a Swerve Library developed by current and former BroncBotz mentors for all FRC teams. YAGSL is a JSON configuration parser that builds a [YAMS](https://yams.yamgen.com/) `SwerveDrive` for your robot — describe your hardware once, generate the config at [**config.yagsl.com**](https://config.yagsl.com), and drive. See [What is YAGSL?](/explanation/what-is-yagsl) for how the pieces fit together.

{% embed url="<https://datawrapper.dwcdn.net/ZVxvE/13/>" %}

## This documentation is organized into four sections

* [**Tutorial**](/tutorial/tutorial) — never used YAGSL before? Start here. A single linear walkthrough from a bare WPILib project to a driving swerve robot.
* [**How-to Guides**](/how-to-guides/how-to) — already have a robot running? Task-focused recipes for tuning PIDF, determining inversion, diagnosing drift, and other work you'll come back to.
* [**Reference**](/reference/reference) — precise JSON schema field tables, supported hardware type strings, and where to find the YAMS API docs.
* [**Explanation**](/explanation/explanation) — swerve drive theory and the reasoning behind how YAGSL and YAMS behave.

{% hint style="info" %}
Upgrading an existing robot from before **2026.8.05**? Your `swerve/` config directory uses the old schema — see [Schema Changes](/reference/schema-changes) before you do anything else.
{% endhint %}

## Get started

{% embed url="<https://www.youtube.com/watch?v=4Tcyn_oj_G0&list=PLdhNPDifsCMLLe5pZoyHGXrMMVxNpJOxP>" %}

{% content-ref url="/pages/VUjPRU3oP9Kpxefm9ap7" %}
[Your First Swerve Robot](/tutorial/tutorial)
{% endcontent-ref %}

{% content-ref url="/pages/QcM0FunHvaMONGWx5YYK" %}
[Tune PIDF gains](/how-to-guides/tune-pidf-gains)
{% endcontent-ref %}

{% content-ref url="/pages/VLPyZoQy6yIAwWwW5pmV" %}
[The 8 steps](/how-to-guides/the-8-steps)
{% endcontent-ref %}

{% content-ref url="/pages/kLO6AjaFJTb3Q1dvCozW" %}
[Schema Changes (Migrating from pre-2026.8.05)](/reference/schema-changes)
{% endcontent-ref %}

## YAGSL Online Installation

```
https://yet-another-software-suite.github.io/YAGSL/yagsl/yagsl.json
```

## Our Philosophy

Your program does not revolve around your swerve drive. Your constants file doesn't have to take 10 minutes to find the right option. Different robots should be able to work with the same code — swap the `swerve/` config directory and the same subsystem code drives a different robot.


# Your First Swerve Robot

This tutorial takes a team from a bare WPILib project to a driving swerve robot using YAGSL. Follow the five steps in order — each one builds on the last, and by the end your robot will be driving under field-relative control with a tuned configuration.

{% hint style="warning" %}
Swerve drives are complicated and will not "just work" on the first try. If you've never brought up a swerve drive before, budget more than one sitting for this — gathering accurate information about your robot and calibrating hardware always takes longer than expected. If you've done this before, expect this tutorial to take about 30 minutes plus PID tuning time.
{% endhint %}

## Prerequisites

* A WPILib **Command-Based Robot (Java)** project (new or existing).
* Your swerve modules are physically assembled and wired (motors, absolute encoders, gyroscope all on the robot).
* You know how to deploy code to your robot and open a driver dashboard (Shuffleboard, Elastic, or AdvantageScope).

{% hint style="info" %}
If your swerve drive uses only [Falcon500](https://store.ctr-electronics.com/falcon-500-powered-by-talon-fx/)/[Kraken](https://store.ctr-electronics.com/kraken-x60/)/[TalonFXS](https://store.ctr-electronics.com/products/talon-fxs), [Pigeon2.0](https://store.ctr-electronics.com/pigeon-2/), and [CANCoder](https://store.ctr-electronics.com/cancoder/) from CTRE, you can also consider the [Tuner X Swerve Drive Generator](https://pro.docs.ctr-electronics.com/en/latest/docs/tuner/tuner-swerve/index.html) as an alternative. YAGSL supports a much wider range of hardware and mixes vendors freely.
{% endhint %}

## The five steps

1. [Gather your robot information](/tutorial/01-gather-your-robot-information) — collect the CAN IDs, gear ratios, and module locations you'll need before you touch any software.
2. [Install YAGSL](/tutorial/02-install-yagsl) — add the YAGSL vendordep and any hardware vendordeps your robot needs.
3. [Generate your configuration](/tutorial/03-generate-your-configuration) — use [config.yagsl.com](https://config.yagsl.com) to turn your robot information into a config directory.
4. [Verify and calibrate your hardware](/tutorial/04-verify-and-calibrate-hardware) — confirm every motor, encoder, and gyro reports direction correctly, and capture absolute encoder offsets.
5. [Deploy and drive](/tutorial/05-deploy-and-drive) — wire up `SwerveParser` in a subsystem, bind your controller, and take your robot for its first drive.

Once you're driving, see the **How-to Guides** section for tuning PIDF gains, diagnosing drift, and other tasks you'll come back to as you keep developing.


# Gather your robot information

YAGSL is configured entirely from information about *your* robot — there is no universal constants file that works for everyone. Before opening [config.yagsl.com](https://config.yagsl.com), collect everything in this step. Having it all on hand up front saves you from re-measuring later.

{% hint style="warning" %}
Every swerve drive is different, even between robots using the same COTS modules (SDS MK4, MAXSwerve, etc.). Don't assume your numbers match another team's example.
{% endhint %}

## Hardware types and connections

| Feature                                                       | Notes                                                                                                                                                                                                                                                                   |
| ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Gyroscope model and connection                                | e.g. Pigeon 2.0 on CAN, NavX over USB/SPI/I2C, CANandGyro on CAN. The connection method matters — a NavX over USB is a different config than one on the MXP (SPI).                                                                                                      |
| Drive motor + controller for each module                      | e.g. Kraken X60 on TalonFX, NEO on SparkMAX, etc.                                                                                                                                                                                                                       |
| Angle/steering motor + controller for each module             | Same as above, per module.                                                                                                                                                                                                                                              |
| Absolute encoder model and connection for each module         | e.g. CANcoder on CAN, REV Through Bore on a data port, attached to a SparkMAX analog/duty-cycle port.                                                                                                                                                                   |
| CAN bus name                                                  | `rio` on a roboRIO, unless you're running CTRE devices on a [CANivore](https://store.ctr-electronics.com/canivore/) (use the CANivore's configured name). Starting with the 2027 SystemCore, name whichever of its several native CAN buses each device is actually on. |
| CAN ID / channel of every motor controller, encoder, and gyro | Get this wrong and you'll be controlling the wrong device without realizing it.                                                                                                                                                                                         |

## Physical characteristics

| Feature                   | Relevance                                                                                                                                                |
| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Drive gear ratio          | How many drive motor rotations produce one wheel rotation. Usually published by the module manufacturer.                                                 |
| Steering/angle gear ratio | How many steering motor rotations produce one full module rotation. Also published by the manufacturer.                                                  |
| Wheel diameter            | Needed to convert motor rotations into distance traveled.                                                                                                |
| Module locations          | Distance in inches from the robot's center to each module's center, split into "front" (X) and "left" (Y) components. See below for how to measure this. |

### Measuring module locations

Measure in CAD if you can, for precision — otherwise, use this technique from Team 5010:

1. Lay the robot on its side and rotate the wheels perpendicular to the direction you're measuring.
2. Measure to the *outside* of each wheel and subtract one wheel width to get the center-to-center measurement.
3. Repeat in the other direction.

Record all four module locations (front-left, front-right, back-left, back-right) relative to the robot's center, in inches:

| Module           | X "Front" (in) | Y "Left" (in) |
| ---------------- | -------------- | ------------- |
| Front Left (FL)  | +              | +             |
| Front Right (FR) | +              | −             |
| Back Left (BL)   | −              | +             |
| Back Right (BR)  | −              | −             |

## Inversion and offsets — record placeholders now, fill in later

You won't be able to determine these until your code is deployed (that happens in [step 4](/tutorial/04-verify-and-calibrate-hardware)), but it helps to have a table ready:

| Module           | Drive Inverted? | Angle Inverted? | Absolute Encoder Inverted? | Absolute Encoder Offset (°) |
| ---------------- | --------------- | --------------- | -------------------------- | --------------------------- |
| Front Left (FL)  |                 |                 |                            |                             |
| Front Right (FR) |                 |                 |                            |                             |
| Back Left (BL)   |                 |                 |                            |                             |
| Back Right (BR)  |                 |                 |                            |                             |

Also note whether your gyroscope needs to be inverted — it must report an **increasing** yaw while the robot rotates counterclockwise.

{% hint style="info" %}
A printable version of a bring-up checklist covering all of the above (courtesy of DevilBotz 2876) is available in the repository's asset folder if your team prefers a paper checklist during bring-up.
{% endhint %}

With this information in hand, move on to [installing YAGSL](/tutorial/02-install-yagsl).


# Install YAGSL

## Add the YAGSL vendordep

Installing YAGSL no longer requires typing a URL. Open the **WPILib Vendor Dependencies** panel in the VS Code sidebar (the WPILib extension icon) — YAGSL is listed directly in the catalog alongside the other common FRC vendor libraries (PathplannerLib, photonlib, REVLib, Studica, URCL, etc.). Click **Install** next to it.

<figure><img src="/files/RzptwWT6YJakdPiLCOoG" alt=""><figcaption><p>YAGSL listed directly in the WPILib Vendor Dependencies panel.</p></figcaption></figure>

If your WPILib version doesn't have YAGSL in that catalog yet, install it the manual way instead: open **Manage Vendor Libraries → Install new library (online)** and paste the URL:

```
https://yet-another-software-suite.github.io/YAGSL/yagsl/yagsl.json
```

Run a Gradle build afterward to pull down the library before continuing.

## Add vendordeps for your hardware

YAGSL wraps [YAMS](https://yams.yamgen.com/) and talks to hardware through each vendor's own library. Install a vendordep for every piece of hardware your robot actually uses — same panel as YAGSL, same one-click **Install**:

| Vendor         | Needed for                                 |
| -------------- | ------------------------------------------ |
| CTRE Phoenix 6 | TalonFX, TalonFXS, Pigeon 2.0, CANcoder    |
| CTRE Phoenix 5 | TalonSRX                                   |
| REVLib         | SparkMAX, SparkFlex                        |
| Studica        | NavX / NavX3                               |
| ReduxLib       | Canandgyro, Canandmag, Canandcoder         |
| maple-sim      | Physics simulation (optional, recommended) |

{% hint style="info" %}
If a vendor isn't in the panel's catalog yet on your WPILib version, install it the manual way instead — [installing 3rd-party libraries](https://docs.wpilib.org/en/stable/docs/software/vscode-overview/3rd-party-libraries.html#installing-libraries) covers pasting a URL, which you can get from the vendor's own install page.
{% endhint %}

{% hint style="warning" %}
Your vendor's hardware client (REV Hardware Client, Phoenix Tuner X) is a separate tool from the vendordep — install it too, it's needed for firmware updates and low-level device configuration.
{% endhint %}

Once your build succeeds with all vendordeps installed, move on to [generating your configuration](/tutorial/03-generate-your-configuration).


# Generate your configuration

Since 2026, the supported way to configure YAGSL is [config.yagsl.com](https://config.yagsl.com), a web app that builds your configuration files from a guided form instead of you hand-writing JSON.

{% hint style="info" %}
Hand-editing the JSON files is still possible and fully supported — see the [JSON Schema Reference](/reference/json-schema) if you need to script config generation or tweak a value the generator doesn't expose. Most teams should use the generator, though: it validates your inputs and always produces a schema-correct config.
{% endhint %}

## Fill in the form

The generator is organized into six tabs — work through them using the information you collected in [step 1](/tutorial/01-gather-your-robot-information):

1. **Gyro** — select your gyroscope type and connection (CAN, SPI, etc.), its ID/CAN bus, which axis reports yaw, and whether it needs to be inverted.
2. **Front Left**, **Front Right**, **Back Left**, **Back Right** (one tab each) — for every module, set the drive motor type/ID, angle motor type/ID, absolute encoder type/ID, any needed inversions, and the module's location relative to the robot center.
3. **Properties** — shared physical properties (gearing defaults, current limits) and starting PIDF gains for the drive and angle motors.

You can leave absolute encoder offsets and final PIDF gains at their defaults for now — you'll measure and tune those in the next two steps. The important thing at this stage is getting hardware types, IDs, and module locations correct.

## Download and unzip

When you're done, download the generated ZIP and unzip it into your project's `src/main/deploy` directory so the layout looks like this:

```
src/main/deploy
└── swerve
    └── base
        ├── swervedrive.json
        └── modules
            ├── frontleft.json
            ├── frontright.json
            ├── backleft.json
            ├── backright.json
            ├── physicalproperties.json
            └── pidfproperties.json
```

{% hint style="success" %}
If you already have a config directory from a previous season or a different robot, you can also upload it back into the generator to review or edit it, instead of starting from scratch.
{% endhint %}

Commit these files to your project's version control along with your code — they're part of your robot program, not a build artifact.

Next: [verify and calibrate your hardware](/tutorial/04-verify-and-calibrate-hardware).


# Verify and calibrate your hardware

Before your first drive, confirm every sensor and motor reports direction consistently, and capture each module's absolute encoder offset. Skipping this step is the single most common cause of a swerve drive that "spins out" or drives in the wrong direction.

{% hint style="danger" %}
Make sure every CAN ID is unique across your entire robot. A known failure mode: if a SparkMAX shares a CAN ID with your REV Power Distribution Hub, the SparkMAX simply won't move — with no obvious error.
{% endhint %}

## Physically label everything

Label each motor controller, encoder, and gyroscope with its CAN ID/channel and connection type before you start. This is the easiest step to get wrong and the hardest mistake to spot afterward.

## Check your gyroscope

* **Front is whatever the gyroscope reports as `0`.** Pick a front, and expect that you may need to change it later if it turns out to be inconvenient.
* The gyroscope must report **increasing yaw while the robot rotates counterclockwise**. If it doesn't, invert it.
* If you're using a NavX, recalibrate it once it arrives at your shop — NavX units ship calibrated to the humidity of their factory, not your build space. Recalibrate again periodically, especially at competition.

## Check your motors

Deploy your code with the config from step 3, then — **with the robot disabled** — spin each motor by hand and watch AdvantageScope's `Mechanisms/swerve` NetworkTables tree.

<figure><img src="/files/m3IU5y59zCRLgOKphmHu" alt=""><figcaption><p>The Mechanisms/swerve tree in AdvantageScope — each module's drive/azimuth telemetry and raw absolute encoder are visible per-module.</p></figcaption></figure>

* Rotate the **drive** wheel forward (CCW as viewed from above). That module's `modules/<name>/drive/mechanism/position` should **increase**. If it doesn't, invert the drive motor.
* Rotate the **angle** mechanism CCW (viewed from above). Both `modules/<name>/azimuth/mechanism/position` (relative) and `modules/<name>/encoder` (absolute) should **increase**. If either doesn't, invert that motor or encoder respectively.
* Rotate the entire **robot** CCW. `Mechanisms/swerve/gyro` should **increase**. If not, invert the gyroscope.

See [Determine Inversion](/how-to-guides/determine-inversion) for the full decision procedure if any of these don't behave as expected — or work through [config.yagsl.com/guide](https://config.yagsl.com/guide), which walks through this same alignment process interactively.

{% hint style="warning" %}
If you're using a hardware vendor client to poke at motors/encoders directly, the roboRIO must not be active on the CAN bus at the same time. The most reliable way to do this without disturbing CAN bus termination is to briefly pull the roboRIO's breaker/fuse on the PDP, then power-cycle the robot.
{% endhint %}

## Capture absolute encoder offsets

The absolute encoder offset is what lets your swerve module remember wheel orientation across power cycles — it's essential to a functioning swerve drive.

1. Manually rotate all four wheels so they point in the same direction — bevel gears facing left, as in the diagram below — and forward rotation increases the drive encoder.

<figure><img src="/files/mI3Vo1gWfvt34By0CQQR" alt=""><figcaption><p>The left and right are physical left and right.</p></figcaption></figure>

2. With the robot still disabled, open AdvantageScope and read each module's `Mechanisms/swerve/modules/<name>/encoder` value while it's held in that aligned position.
3. Enter each value as that module's absolute encoder offset — either back in the [config generator](https://config.yagsl.com) (recommended: upload your existing config, update the four offset fields, and re-download) or directly in `absoluteEncoderOffset` in the module's JSON file.

{% hint style="warning" %}
**Special note for MAXSwerve teams:** after aligning the wheels, you may need to add or subtract `90` from the measured offset to get the module pointing truly forward, and set `absoluteEncoderInverted` to `true`. This is a quirk of how MAXSwerve mounts its absolute encoder, not a bug in your measurement.
{% endhint %}

Once every module reports correctly and has a captured offset, move on to [deploying and driving](/tutorial/05-deploy-and-drive).


# Deploy and drive

With hardware verified and offsets captured, wire YAGSL into your robot code. `SwerveParser` reads your `swerve/base` config directory and builds a YAMS `SwerveDrive` for you — you only write the subsystem and control bindings around it.

## Create a swerve subsystem

```java
import edu.wpi.first.math.geometry.Pose2d;
import edu.wpi.first.math.geometry.Rotation2d;
import edu.wpi.first.math.kinematics.ChassisSpeeds;
import edu.wpi.first.wpilibj.Filesystem;
import edu.wpi.first.wpilibj2.command.Command;
import edu.wpi.first.wpilibj2.command.SubsystemBase;
import java.io.File;
import java.util.function.DoubleSupplier;
import swervelib.parser.SwerveParser;
import yams.mechanisms.config.SwerveDriveConfig;
import yams.mechanisms.swerve.SwerveDrive;
import yams.mechanisms.swerve.utility.SwerveInputStream;
import yams.motorcontrollers.SmartMotorControllerConfig.TelemetryVerbosity;

public class SwerveDriveSubsystem extends SubsystemBase
{

  private SwerveDrive drive;

  public SwerveDriveSubsystem()
  {
    var cfg = new SwerveDriveConfig()
        .withStartingPose(new Pose2d(3, 3, Rotation2d.kZero))
        .withSubsystem(this)
        .withTelemetry(TelemetryVerbosity.HIGH);
    try
    {
      drive = new SwerveParser(new File(Filesystem.getDeployDirectory(), "swerve/base"))
          .createSwerveDrive(cfg);
    } catch (Exception e)
    {
      throw new RuntimeException(e);
    }
  }

  public SwerveInputStream getAngularVelocityStream(DoubleSupplier x, DoubleSupplier y,
                                                    DoubleSupplier rot)
  {
    return new SwerveInputStream(drive, x, y, rot);
  }

  public Command drive(SwerveInputStream stream)
  {
    return drive.drive(() -> ChassisSpeeds.fromFieldRelativeSpeeds(stream.get(),
                                                                   new Rotation2d(drive.getGyroAngle())));
  }

  /** Zero the gyro heading. Bind this to a button combo for field recovery. */
  public Command zeroGyro()
  {
    return runOnce(() -> drive.zeroGyro());
  }

  @Override
  public void periodic()
  {
    drive.updateTelemetry();
  }

  @Override
  public void simulationPeriodic()
  {
    drive.simIterate();
  }
}
```

`SwerveParser` reads `swervedrive.json` and every file it references under `modules/`, resolves your hardware through the matching vendor library, and hands back a fully constructed YAMS `SwerveDrive` — there's no constants file to maintain by hand.

`TelemetryVerbosity.HIGH` is useful while bringing your robot up (it's what step 4's dashboard values come from) but adds NetworkTables overhead — turn it down once you've finished tuning.

## Bind your controller

```java
import edu.wpi.first.wpilibj2.command.button.CommandXboxController;
import frc.robot.subsystems.swervedrive.SwerveDriveSubsystem;
import yams.mechanisms.swerve.utility.SwerveInputStream;

public class RobotContainer
{

  final CommandXboxController driverXbox = new CommandXboxController(0);

  private final SwerveDriveSubsystem swerve = new SwerveDriveSubsystem();

  private final SwerveInputStream driveAngularVelocity =
      swerve.getAngularVelocityStream(
                driverXbox::getLeftY,
                driverXbox::getLeftX,
                () -> driverXbox.getRawAxis(2))
            .withAllianceRelativeControl();

  public RobotContainer()
  {
    configureBindings();
  }

  private void configureBindings()
  {
    // Default drive command
    swerve.setDefaultCommand(swerve.drive(driveAngularVelocity));

    // Zero the gyro with Start + Back — use this if the field-relative heading drifts
    driverXbox.start().and(driverXbox.back()).onTrue(swerve.zeroGyro());
  }
}
```

{% hint style="info" %}
**Why bind `zeroGyro()` to a button combo?** Gyros can drift, or power on facing the wrong direction. A button combo (Start + Back, or both bumpers) lets the driver instantly re-align field-relative control without touching the Driver Station. Always bind this — it has saved matches.
{% endhint %}

## First drive

1. Deploy your code.
2. On blocks, enable the robot in Teleop and push the drive stick gently forward. All four wheels should point the same direction and the robot should attempt to move that way.
3. Rotate with the rotation axis. The robot should spin around its center, not drift while spinning.
4. If a module fights the others (spins to the wrong angle, or drives backward relative to the rest), revisit [step 4](/tutorial/04-verify-and-calibrate-hardware) for that module's inversion/offset.
5. Once it drives cleanly on blocks, test on the ground at low speed before opening it up.

Congratulations — you have a driving swerve robot. From here, see the **How-to Guides** section for [tuning PIDF gains](/how-to-guides/tune-pidf-gains), diagnosing drift, and other tasks you'll return to throughout the season.


# What to expect?

Goal-oriented recipes for a robot that's already running YAGSL. If you're setting up your first swerve robot, start with the [Tutorial](/tutorial/tutorial) section instead — these guides assume a working config and address a specific task or problem.

* [How to tune PIDF gains](/how-to-guides/tune-pidf-gains)
* [How to determine inversion](/how-to-guides/determine-inversion)
* [How to verify your module locations](/how-to-guides/verify-module-locations)
* [How to use a custom gyro](/how-to-guides/use-a-custom-gyro) — for hardware YAGSL's parser doesn't build, like a roboRIO-attached Studica AHRS
* [How to drive to a pose](/how-to-guides/drive-to-a-pose) — a single point-to-point PID move, no path planner needed
* [How to set up PathPlanner](/how-to-guides/setup-pathplanner) — register a YAGSL-built `SwerveDrive` with PathPlanner's `AutoBuilder`
* [How to access raw hardware devices](/how-to-guides/access-raw-hardware-devices) — get the actual vendor motor controller/encoder/gyro objects `SwerveParser` built
* [The 8 steps](/how-to-guides/the-8-steps) — the translational-axis/heading coupling debugging procedure
* [How to fix common SparkMAX/SparkFlex problems](/how-to-guides/fix-sparkmax-common-problems)
* [How to set up AdvantageScope](/how-to-guides/set-up-advantagescope)
* [How to view your DataLog in AdvantageScope](/how-to-guides/view-your-datalog-in-advantagescope) — opening a recorded `.wpilog` after the fact, not live telemetry
* [Troubleshooting flowchart](/how-to-guides/troubleshooting-flowchart)


# Tune PIDF gains

Starting points and a tuning procedure for your drive and angle motor gains.

{% hint style="warning" %}
Swerve steering/angle/azimuth motors have PID wrapping enabled at the topmost and bottommost point (e.g. "front" and "back"). When tuning angle PID you may want to test with left or right translation so the module doesn't cross the wrap point.
{% endhint %}

## The basic idea

YAGSL reads PIDF values from [`pidfproperties.json`](/reference/json-schema/pidfproperties-json) in your module folder — generate or edit it with [**config.yagsl.com**](https://config.yagsl.com).

This has been hashed and boiled down to the simplest form like this before:

* **P**: if you're not where you want to be, get there.
* **I**: if you haven't been where you want to be for a long time, get there faster.
* **D**: if you're getting close to where you want to be, slow down.
  * [Video](https://www.youtube.com/watch?v=qKy98Cbcltw) found by 78, found by [this CD post](https://www.chiefdelphi.com/t/finally-i-understand-pid/450811)

<figure><img src="/files/BWjBb2Ts88krC8jXy2aK" alt=""><figcaption></figcaption></figure>

## Tuning procedure

Another good way to describe it, [from CTRE](https://pro.docs.ctr-electronics.com/en/latest/docs/api-reference/device-specific/talonfx/closed-loop-requests.html):

Manual tuning typically follows this process:

1. Set $$P$$, $$I$$, and $$D$$ to zero.
2. Increase $$P$$ until the output **starts** to **oscillate** **around** the setpoint.
3. Increase $$D$$ as much as possible **without** introducing **jittering** to the response.

For the drive motor, also set the feedforward terms (`s`, `v`, `a` — see below) before fine-tuning `P`; a good feedforward means `P` only has to correct small errors.

## WPILib walkthrough

WPILib has lots of great documentation on PID, so it's worth reading directly:

{% embed url="<https://docs.wpilib.org/en/stable/docs/software/advanced-controls/introduction/introduction-to-pid.html>" %}
Basics of PID
{% endembed %}

{% embed url="<https://docs.wpilib.org/en/stable/docs/software/advanced-controls/introduction/tuning-flywheel.html>" %}
Tuning velocity, like the drive motors!
{% endembed %}

{% embed url="<https://docs.wpilib.org/en/stable/docs/software/advanced-controls/introduction/tuning-turret.html>" %}
Tuning position, like the steering/angle/azimuth motors!
{% endembed %}

## The current `pidfproperties.json` shape

{% hint style="info" %}
The schema changed in YAGSL 2026.8.05 — `f` and `iz` (integral zone) and the old `output` clamp are gone. Feedforward is now expressed as `s`/`v`/`a` (`kS`/`kV`/`kA`, matching WPILib's `SimpleMotorFeedforward`). See the [schema changes reference](/reference/schema-changes) if you're migrating an older config by hand.
{% endhint %}

```json
{
  "drive": {
    "p": 0.02,
    "i": 0,
    "d": 0,
    "s": 0,
    "v": 0,
    "a": 0
  },
  "angle": {
    "p": 0.01,
    "i": 0,
    "d": 0,
    "s": 0,
    "v": 0,
    "a": 0
  }
}
```

If you leave drive `v` (kV) at `0`, YAGSL auto-derives it from the drive motor's free speed and your gearing, so a reasonable feedforward exists even before you've tuned anything by hand.

You can also add a `pidfproperties_sim.json` alongside it — YAGSL will load that one instead when running in simulation (`RobotBase.isSimulation()`), so you can use looser gains in sim without touching your competition robot's config.

## Starting points

The right PIDF values vary per-robot, but these are reasonable starting points to iterate from, organized by motor controller type.

{% tabs %}
{% tab title="SparkMax / SparkFlex" %}

```json
{
  "drive": {
    "p": 0.0020645,
    "i": 0,
    "d": 0,
    "s": 0,
    "v": 0,
    "a": 0
  },
  "angle": {
    "p": 0.01,
    "i": 0,
    "d": 0,
    "s": 0,
    "v": 0,
    "a": 0
  }
}
```

{% endtab %}

{% tab title="TalonFX" %}
When the drive motor `type` for every module is `talonfx_*` (or alike — `krakenx60`, `falcon500`) this is a reasonable starting point:

```json
{
  "drive": {
    "p": 1,
    "i": 0,
    "d": 0,
    "s": 0,
    "v": 0,
    "a": 0
  },
  "angle": {
    "p": 50,
    "i": 0,
    "d": 0.32,
    "s": 0,
    "v": 0,
    "a": 0
  }
}
```

{% endtab %}
{% endtabs %}

Once you have a reasonable starting point, use [**config.yagsl.com**](https://config.yagsl.com) to regenerate `pidfproperties.json` with your updated numbers, or hand-edit the file directly — both work, since the parser only cares about the final JSON.


# Determine inversion

Work out which motors, encoders, and the gyro need to be inverted.

Swerve modules and swerve drives require some inversions to get working properly. The goal is to get everything to increase **counter-clockwise positive (CCW+)**.

{% hint style="info" %}
[config.yagsl.com/guide](https://config.yagsl.com/guide) walks through this exact process interactively, with animations, if you'd rather follow along there instead of on this page.
{% endhint %}

{% hint style="warning" %}
If your gears are grinding on the ground but not while on blocks, and your wheels are facing and spinning in the right directions, you may need to [tune PID](/how-to-guides/tune-pidf-gains) instead of inverting anything!
{% endhint %}

{% hint style="warning" %}
If you invert incorrectly, your modules or robot may spin "out of control." If nothing here resolves it, see [The 8 steps](/how-to-guides/the-8-steps).
{% endhint %}

All of the telemetry referenced below lives under `Mechanisms/swerve` in NetworkTables — see [Telemetry, Simulation & Vision](/explanation/telemetry-and-vision) for the full tree.

## 1. Confirm the absolute encoder reads CCW+

<figure><img src="/files/mI3Vo1gWfvt34By0CQQR" alt=""><figcaption><p>Purple shows the way your bevels should be facing (photo by Team 2876)</p></figcaption></figure>

With the robot lifted or on its side so the wheels spin freely, rotate one module by hand **counterclockwise** (viewed from directly above, robot front pointing away from you) and watch that module's `modules/<name>/encoder` value in AdvantageScope.

* If it **increases**, the absolute encoder is already correct — leave `absoluteEncoderInverted` as `false`.
* If it **decreases**, set `absoluteEncoderInverted` to `true` for that module. This is rare — most absolute encoders already read CCW+ by default.

```json
{
  "drive": { "type": "sparkmax_neo", "id": 2, "canbus": "" },
  "angle": { "type": "sparkmax_neo", "id": 1, "canbus": "" },
  "absoluteEncoder": { "type": "cancoder_can", "id": 10, "channel": 0, "canbus": "" },
  "inverted": { "drive": false, "angle": false },
  "absoluteEncoderInverted": true,
  "absoluteEncoderOffset": -50.977,
  "location": { "front": 12, "left": -12 }
}
```

Once every module reads CCW+, rotate each wheel to point straight forward (bevel gear left) and record the `modules/<name>/encoder` value held in that position — that's the module's `absoluteEncoderOffset`. Enter it in the module's JSON, or paste it into [**config.yagsl.com**](https://config.yagsl.com).

## 2. Confirm the gyro reads CCW+

With the robot lifted off the ground, rotate the **entire robot** counterclockwise (viewed from above) and watch `Mechanisms/swerve/gyro`.

* If it **increases**, leave `gyroInvert` alone — there's nothing to change.
* If it **decreases**, set `gyroInvert` to `true` in `swervedrive.json`:

```json
{
  "gyro": { "type": "pigeon2_can", "id": 13, "canbus": "canivore" },
  "gyroAxis": "yaw",
  "gyroInvert": true,
  "modules": ["frontleft.json", "frontright.json", "backleft.json", "backright.json"]
}
```

See the [gyroscope reference](/reference/hardware/gyroscopes) for the full list of supported `gyro.type` values.

## 3. Spin test — catch drive motor inversion and module swaps

Still with the robot lifted off the ground, deploy code and drive with a joystick input that commands pure rotation (e.g. hold the rotation stick fully to one side). Watch the robot against AdvantageScope's Swerve widget (top-down view) — see [Set up AdvantageScope](/how-to-guides/set-up-advantagescope) — and compare:

| Symptom                                                         | Likely cause                                                                                                                                                                                 |
| --------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Modules drift or translate instead of spinning cleanly in place | Wrong CAN IDs for a module, or a wrong `absoluteEncoderOffset`.                                                                                                                              |
| Spins cleanly, but the wrong way (clockwise instead of CCW)     | Inverted drive motor(s); a diagonal module swap (front-left ↔ back-right, front-right ↔ back-left); or an absolute encoder offset captured with the bevel gear facing right instead of left. |

If a single module is spinning the wrong way, invert that module's drive motor:

```json
{
  "drive": { "type": "sparkmax_neo", "id": 2, "canbus": "" },
  "angle": { "type": "sparkmax_neo", "id": 1, "canbus": "" },
  "absoluteEncoder": { "type": "cancoder_can", "id": 10, "channel": 0, "canbus": "" },
  "inverted": { "drive": true, "angle": false },
  "absoluteEncoderInverted": false,
  "absoluteEncoderOffset": -50.977,
  "location": { "front": 12, "left": -12 }
}
```

If every module spins the wrong way at once, double-check for a diagonal module swap in `swervedrive.json`'s `modules` list before you start flipping individual `inverted.drive` fields — it's a more common cause than it sounds.

## 4. Field-orientation check

Once the spin test looks right, put the robot down and drive it forward/back/left/right at its starting heading while watching a 2D/3D Field tab fed from `Mechanisms/swerve/pose`. Then spin in place again, this time watching the field widget's heading instead of the swerve module widget.

* **Wrong at every heading, including the start:** check `gyroInvert`.
* **Only wrong once you rotate away from the starting heading:** an absolute encoder offset that's off, a drive motor inversion mistake, a wrong module `location`, an absolute encoder wired to the wrong motor controller, or a wrong CAN ID. Verify wiring and CAN IDs first — they're the fastest to rule out.
* **The spin test in step 3 looked right, but the field widget's heading spins backwards:** an inverted gyro can flip the field widget even when the module states widget looked correct.

{% hint style="danger" %}

#### Odometry mismatch

If your robot drives backwards in odometry but forwards in real life, while a spin-in-place still shows Counter-Clockwise-Positive movement, apply this patch: add `180` to every `absoluteEncoderOffset` in every module file.
{% endhint %}

{% hint style="info" %}
In older YAGSL configs (pre-2026.8.05) this section was named `imu`/`invertedIMU` instead of `gyro`/`gyroInvert` — see the [schema changes reference](/reference/schema-changes) if you're working from an old config.
{% endhint %}


# Verify your module locations

Your front may not be what you think it is on a robot.

After you have initially configured the robot with YAGSL, test it on blocks to make sure the modules are facing the correct directions while in the air. You may need to [swap the module configurations](/how-to-guides/the-8-steps#how-to-swap-module-configurations) around to ensure they're doing what the robot program thinks they're doing.

## Drive straight

The first test is to command the robot to drive straight. The modules should look like this both in real life and in Elastic or [AdvantageScope](/how-to-guides/set-up-advantagescope).

<figure><img src="/files/mI3Vo1gWfvt34By0CQQR" alt=""><figcaption></figcaption></figure>

If it doesn't look like this in real life, change the offending modules' `location` (and, if needed, CAN IDs) to match this layout.

## Rotate the robot

Your robot should rotate CCW+. For this verification step, as long as the robot appears to be doing either of the following while attempting to rotate, you should be fine.

{% hint style="warning" %}
The depiction on the left requires you to run through [The 8 steps](/how-to-guides/the-8-steps), since your translational axis changes based on your robot's heading.
{% endhint %}

<figure><img src="/files/ssEuCY0WRJ82tOLchCIO" alt=""><figcaption></figcaption></figure>


# Use a custom gyro

Configure a gyro YAGSL's parser doesn't build for you, using a Studica AHRS as the example.

{% hint style="info" %}
If your gyro is one of the [supported types](/reference/hardware/gyroscopes#supported-gyroscope-types) (Pigeon 2, Canandgyro, NavX3-CAN), you don't need this page — just set `gyro.type` normally in `swervedrive.json`.
{% endhint %}

Use a `custom` gyro when your hardware isn't one the parser builds for you. The most common case is the older roboRIO SPI/I2C/USB-serial [Studica AHRS](https://www.studica.com/navx2-micro) (the classic NavX2) — only the CAN-based NavX3 is supported directly, so an AHRS on the MXP/SPI port needs this escape hatch. The same approach works for any other IMU, a simulated/composited heading source, or a heading fused from multiple sensors.

## 1. Set `gyro.type` to `custom`

In `swervedrive.json`, set the gyro's `type` to `custom`. The `id`/`canbus` fields are unused for a custom gyro (leave them at `0`/`""`), and — this is the part that catches people out — `gyroAxis` and `gyroInvert` are **also ignored**. The parser sees `custom` and skips gyro configuration entirely.

```json
{
  "gyro": { "type": "custom", "id": 0, "canbus": "" },
  "gyroAxis": "yaw",
  "gyroInvert": false,
  "modules": ["frontleft.json", "frontright.json", "backleft.json", "backright.json"]
}
```

## 2. Construct the gyro and supply it yourself

Because the parser never calls `SwerveDriveConfig.withGyro()` for a `custom` gyro, you call it yourself on the `SwerveDriveConfig` you build **before** handing it to `SwerveParser.createSwerveDrive(...)`. Everything else — modules, motor controllers, encoders — is still built normally from the rest of the JSON.

```java
import com.studica.frc.AHRS;
import static edu.wpi.first.units.Units.Degrees;

// Onboard MXP SPI port is the common mounting for a roboRIO AHRS.
private final AHRS gyro = new AHRS(AHRS.NavXComType.kMXP_SPI);

public SwerveDriveSubsystem() {
  SwerveDriveConfig cfg = new SwerveDriveConfig()
      .withSubsystem(this)
      .withTelemetry(TelemetryVerbosity.HIGH)
      .withGyro(() -> Degrees.of(gyro.getAngle()));

  drive = new SwerveParser(new File(Filesystem.getDeployDirectory(), "swerve/base"))
      .createSwerveDrive(cfg);
}
```

{% hint style="danger" %}
If you forget to call `withGyro()` before `createSwerveDrive()`, YAMS has no heading supplier to build the drive with — expect a `NoSuchElementException`/`RuntimeException` at startup, not a silent fallback.
{% endhint %}

## 3. Handle inversion yourself

Since `gyroInvert` is ignored, do any inversion directly in the `Supplier<Angle>` you pass to `withGyro()`, then call `withGyroInverted()` yourself to keep it in sync with anything else in YAMS that reads that flag:

```java
.withGyro(() -> Degrees.of(-gyro.getAngle())) // AHRS reports CW+; YAGSL/YAMS expect CCW+
.withGyroInverted(false)
```

## 4. Verify it

Follow the normal [gyro CCW+ check](/how-to-guides/determine-inversion#2-confirm-the-gyro-reads-ccw) — `Mechanisms/swerve/gyro` in NetworkTables should still populate and behave exactly like a parser-built gyro, since from YAMS's perspective a `Supplier<Angle>` is a `Supplier<Angle>` regardless of where it came from. If it increases while the robot rotates counter-clockwise, you're done; if not, negate the supplier as in step 3 rather than flipping `gyroInvert` (which, again, is ignored).

## Optional: angular velocity for skew correction

If you also want gyro-based angular-velocity skew correction (see [Swerve Drive Drift](/reference/swerve-drift-causes)), a `custom` gyro needs its angular velocity supplied the same way the heading was — the parser doesn't build this for you either, custom or not:

```java
.withGyroVelocity(() -> DegreesPerSecond.of(-gyro.getRate()))
.withGyroAngularVelocityScaleFactor(1.0)
```

{% hint style="warning" %}
`AHRS.getRate()` and `getAngle()` follow the same sign convention (both CW+ on Studica hardware) — if you negated one to get CCW+, negate the other too, or your skew correction will fight the heading it's supposed to be correcting.
{% endhint %}


# Drive to a pose

Command the robot to a field-relative Pose2d without PathPlanner.

`SwerveDrive.driveToPose(Pose2d)` drives straight to a field-relative pose using two PID controllers on `SwerveDriveConfig` — no path planning involved, just "go here." It's a good fit for short, single-target moves: aligning to a reef face, a coral station, or a fixed AprilTag-relative setpoint. For multi-point autonomous routines with obstacle-aware paths, see [How to set up PathPlanner](/how-to-guides/setup-pathplanner) instead.

{% hint style="warning" %}
`driveToPose` is **not compatible with AdvantageKit** replay (per its javadoc) — it reads gyro/pose state live inside the command rather than through a logged input.
{% endhint %}

## 1. Give the drive translation and rotation controllers

`SwerveParser` doesn't set these for you — there's no JSON field for them, since they're a robot-code concern, not a per-robot hardware description. Set them on the `SwerveDriveConfig` before calling `createSwerveDrive(...)`:

```java
var cfg = new SwerveDriveConfig()
    .withStartingPose(new Pose2d(3, 3, Rotation2d.kZero))
    .withSubsystem(this)
    .withTelemetry(TelemetryVerbosity.HIGH)
    .withTranslationController(new PIDController(1.0, 0, 0)) // input: meters of position error
    .withRotationController(new PIDController(1.0, 0, 0));   // input: radians of heading error

drive = new SwerveParser(new File(Filesystem.getDeployDirectory(), "swerve/base"))
    .createSwerveDrive(cfg);
```

{% hint style="danger" %}
If you call `driveToPose()` without setting both controllers first, expect a `NoSuchElementException` — `SwerveDriveConfig` throws rather than silently picking a default, since an untuned default would just as likely fight the robot as help it.
{% endhint %}

These are the same controllers `SwerveInputStream`'s heading-hold/heading-lock control uses (see [Heading Correction](/explanation/module-behaviors#heading-correction-heading-snap-control)) — if you've already tuned heading-hold, you have a reasonable starting point for `driveToPose` too, though the two use cases (holding a heading vs. closing a rotation error to a specific target) can want different aggressiveness.

## 2. Wrap it in a command that knows when to stop

`driveToPose` never finishes on its own — it's a `drive(...)` loop that keeps correcting toward the target pose forever, the same way your teleop default command never finishes. Race it against a tolerance check (or a timeout) so it actually ends:

```java
public Command driveToPose(Pose2d pose) {
  return drive.driveToPose(pose)
      .until(() -> drive.getDistanceFromPose(pose).in(Inches) < 1
                && Math.abs(drive.getAngleDifferenceFromPose(pose).in(Degrees)) < 2);
}
```

`SwerveDrive.getDistanceFromPose(Pose2d)` and `getAngleDifferenceFromPose(Pose2d)` give you the current translation/rotation error against any pose, so the tolerance check above works for any target, not just the one you started the command with.

If you'd rather hold at the pose indefinitely (e.g. as a default command while lined up on a game piece), skip `.until(...)` and just interrupt it with whatever takes over next — that's exactly what a bare `drive.driveToPose(pose)` binding does.

## 3. Bind it

```java
driverXbox.a().onTrue(swerve.driveToPose(new Pose2d(3, 3, Rotation2d.fromDegrees(30))));
```

Pass a fixed pose for a known field feature, or a pose computed on the fly (from vision, or the nearest of a set of candidate poses) — `driveToPose` doesn't care where the `Pose2d` came from.

{% hint style="info" %}
The pose is field-relative with the blue-alliance wall as the origin, `0°` facing the red alliance wall — the same convention `SwerveDrive.getPose()`/odometry already use. If you're computing poses per-alliance, flip them the same way `SwerveInputStream.withAllianceRelativeControl()` does rather than hand-rolling it.
{% endhint %}


# Set up PathPlanner

Register a YAGSL-built SwerveDrive with PathPlanner's AutoBuilder.

[PathPlanner](https://pathplanner.dev) is a separate tool from YAGSL — it plans and follows multi-point paths (with obstacle-aware routing, event markers, and an on-field GUI editor) rather than the single point-to-point moves in [driveToPose](/how-to-guides/drive-to-a-pose). This page wires a YAGSL-built `SwerveDrive` into it. It assumes you already have a driving robot from the [Tutorial](/tutorial/tutorial).

{% hint style="info" %}
This page covers the YAGSL/YAMS side of the wiring. For building paths/autos themselves, installing the standalone PathPlanner GUI app, and general PathPlanner concepts, see [PathPlanner's own documentation](https://pathplanner.dev/home.html).
{% endhint %}

## 1. Install PathplannerLib

Open the **WPILib Vendor Dependencies** panel in VS Code (same place you installed YAGSL — see [Install YAGSL](/tutorial/02-install-yagsl)) and install **PathplannerLib** from the catalog.

## 2. Export your robot config from the PathPlanner GUI

In the [PathPlanner GUI app](https://github.com/mjansen4857/pathplanner/releases), fill in your robot's mass, MOI, module offsets, wheel radius, and drive motor specs under its Robot Config tab, then export. This writes `deploy/pathplanner/settings.json`, which `RobotConfig.fromGUISettings()` reads at runtime — you don't hand-write this file or duplicate its values in Java.

{% hint style="warning" %}
This is a **second, separate** description of your robot's physical properties from YAGSL's own `modules/physicalproperties.json` — gear ratio and wheel diameter in particular exist in both places. If you change one (a wheel swap, a gearing change), update the other too, or PathPlanner's paths and YAGSL's own odometry will disagree about how far the robot actually travels. This is a common, JSON-invisible cause of drift — see the [PathPlanner section of Swerve Drive Drift](/reference/swerve-drift-causes#pathplanner).
{% endhint %}

## 3. Register `AutoBuilder` once, right after building the drive

Do this once, in your swerve subsystem's constructor, right after `SwerveParser.createSwerveDrive(...)` returns. After this call, `PathPlannerAuto` commands and GUI-defined paths just work — no extra wiring per auto.

```java
import com.pathplanner.lib.auto.AutoBuilder;
import com.pathplanner.lib.config.PIDConstants;
import com.pathplanner.lib.config.RobotConfig;
import com.pathplanner.lib.controllers.PPHolonomicDriveController;
import edu.wpi.first.wpilibj.DriverStation;
import java.io.IOException;
import org.json.simple.parser.ParseException;

public class SwerveDriveSubsystem extends SubsystemBase {
  private SwerveDrive drive;

  public SwerveDriveSubsystem() {
    var cfg = new SwerveDriveConfig()
        .withStartingPose(new Pose2d(3, 3, Rotation2d.kZero))
        .withSubsystem(this)
        .withTelemetry(TelemetryVerbosity.HIGH);

    drive = new SwerveParser(new File(Filesystem.getDeployDirectory(), "swerve/base"))
        .createSwerveDrive(cfg);

    try {
      setupPathPlanner();
    } catch (IOException | ParseException e) {
      throw new RuntimeException(
          "PathPlanner setup failed -- check deploy/pathplanner/settings.json exists", e);
    }
  }

  private void setupPathPlanner() throws IOException, ParseException {
    AutoBuilder.configure(
        drive::getPose,                  // robot pose supplier
        drive::resetOdometry,             // called if an auto defines a starting pose
        drive::getRobotRelativeSpeed,     // ChassisSpeeds supplier -- MUST be robot-relative
        (speedsRobotRelative, moduleFeedForwards) ->
            drive.setRobotRelativeChassisSpeeds(speedsRobotRelative),
        new PPHolonomicDriveController(
            new PIDConstants(5.0, 0.0, 0.0),  // translation PID
            new PIDConstants(5.0, 0.0, 0.0)   // rotation PID
        ),
        RobotConfig.fromGUISettings(),    // reads deploy/pathplanner/settings.json
        () -> {
          // Field origin is always the blue alliance wall -- flip paths when on red.
          var alliance = DriverStation.getAlliance();
          return alliance.filter(a -> a == DriverStation.Alliance.Red).isPresent();
        },
        this                              // subsystem requirement for the generated commands
    );
  }
}
```

{% hint style="warning" %}
The `PPHolonomicDriveController` PID constants above are **independent** of `SwerveDriveConfig.withTranslationController()`/`withRotationController()` from [driveToPose](/how-to-guides/drive-to-a-pose) — PathPlanner's `AutoBuilder` uses its own controller and never touches YAGSL's. Tune them separately; there's no reason they need matching gains.
{% endhint %}

## 4. Run an auto

```java
public Command getAutonomousCommand() {
  return new PathPlannerAuto("New Auto");
}
```

Build `"New Auto"` in the PathPlanner GUI, deploy, and bind `getAutonomousCommand()` the same way you'd bind any other autonomous command (`RobotContainer`'s autonomous command supplier, or `Robot.autonomousInit()`). Event markers placed in the GUI fire `EventTrigger`s in code:

```java
new EventTrigger("EventMarker").whileTrue(Commands.print("Something"));
```

## Troubleshooting

If a PathPlanner auto drives the wrong distance, curves when it should be straight, or ends up in the wrong spot, see the [PathPlanner checklist in Swerve Drive Drift](/reference/swerve-drift-causes#pathplanner) before re-tuning anything — most of these trace back to the config mismatch in step 2, or PID gains that were never tuned past their defaults.


# Access raw hardware devices

Get the actual vendor motor controller/encoder/gyro objects SwerveParser built, for configuration that isn't exposed through SmartMotorController.

{% hint style="info" %}
Most robot code never needs this page. `SwerveDrive`, `SwerveModule`, and YAMS's `SmartMotorController` cover driving, telemetry, and the vast majority of per-motor configuration. Reach for `createSwerveDriveDevices` when you need to call a vendor-specific method (a CTRE `TalonFXConfigurator` option, a REV `SparkMax` signal, a `CANcoder`-specific setting) that `SmartMotorController` doesn't expose, or when you need real hardware, not a wrapper, for something like MegaTag2 vision fusion (see the example below).
{% endhint %}

`SwerveParser.createSwerveDrive(config)` hands you back a fully-built `SwerveDrive`. Everything underneath it (drive/azimuth motor controllers, absolute encoders, the gyro) is wrapped and effectively private. `SwerveParser.createSwerveDriveDevices(config)` builds the exact same hardware, but also gives you the raw vendor objects it created along the way, alongside the `SwerveDrive` itself.

## 1. Call `createSwerveDriveDevices` instead of `createSwerveDrive`

```java
import swervelib.parser.SwerveParser;
import swervelib.parser.SwerveParser.SwerveDriveDevices;

SwerveParser.parse(new File(Filesystem.getDeployDirectory(), "swerve/base"));
SwerveDriveDevices devices = SwerveParser.createSwerveDriveDevices(cfg);

drive = devices.swerveDrive();
```

{% hint style="danger" %}
Call **either** `createSwerveDrive(...)` **or** `createSwerveDriveDevices(...)` for a given parsed directory, not both. Each call builds the hardware from scratch, so calling both would construct every motor controller, encoder, and gyro twice (two CAN objects fighting over the same ID).
{% endhint %}

## 2. Read the devices back out

`SwerveDriveDevices` is a record with three components:

| Component     | Type                    | Notes                                                                                           |
| ------------- | ----------------------- | ----------------------------------------------------------------------------------------------- |
| `swerveDrive` | `SwerveDrive`           | The same object `createSwerveDrive(...)` would have returned. Use it exactly as normal.         |
| `gyro`        | `Object`                | The raw gyro device (e.g. a CTRE `Pigeon2`), or `null` if `gyro.type` is `"custom"`. See below. |
| `modules`     | `SwerveModuleDevices[]` | One entry per module, in the same order as the `modules` array in `swervedrive.json`.           |

Each `SwerveModuleDevices` is in turn:

| Component         | Type     | Notes                                                                                                                                                                                        |
| ----------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `drive`           | `Object` | Raw drive motor controller (e.g. a REV `SparkMax`/`SparkFlex`, or a CTRE `TalonFX`/`TalonFXS`).                                                                                              |
| `azimuth`         | `Object` | Raw azimuth/angle motor controller.                                                                                                                                                          |
| `absoluteEncoder` | `Object` | Raw absolute encoder device: a separate device (e.g. `CANcoder`, `AnalogEncoder`, `DutyCycleEncoder`), or the same object as `azimuth` if the encoder is attached to the azimuth controller. |

Cast each `Object` to whatever vendor type you know it to be from your `swervedrive.json`/module JSON `type` strings (see [Device Type Strings](/reference/json-schema/device-types)):

```java
import com.ctre.phoenix6.hardware.TalonFX;
import com.ctre.phoenix6.hardware.CANcoder;

SwerveModuleDevices frontLeft = devices.modules()[0];

TalonFX driveMotor = (TalonFX) frontLeft.drive();
CANcoder absoluteEncoder = (CANcoder) frontLeft.absoluteEncoder();

driveMotor.getConfigurator().apply(myCustomTalonConfig);
```

{% hint style="warning" %}
Nothing enforces that `modules[i]` is the type you expect. Mixed-vendor swerve drives (a CTRE drive motor with a REV angle motor, say) are supported by YAGSL, so a blind cast across every module in a loop can throw `ClassCastException` if the vendors aren't actually uniform. Cast per-module against what that module's JSON actually specifies.
{% endhint %}

## 3. Example: full 3-axis gyro data for MegaTag2

`SwerveDrive.getGyroAngle()` only ever gives you yaw, which is genuinely all MegaTag2 requires (see [How to use a custom gyro](/how-to-guides/use-a-custom-gyro#optional-angular-velocity-for-skew-correction) for the same point applied to skew correction). But if your gyro is a full IMU and you'd like to feed a vision coprocessor real roll and pitch too, you need the raw device, not the wrapped one:

```java
import com.ctre.phoenix6.StatusSignal;
import com.ctre.phoenix6.hardware.Pigeon2;
import edu.wpi.first.units.measure.AngularVelocity;

private Pigeon2 gyro;
private StatusSignal<AngularVelocity> gyroX;
private StatusSignal<AngularVelocity> gyroY;
private StatusSignal<AngularVelocity> gyroZ;

public SwerveDriveSubsystem() {
  // ... parse and build cfg as usual ...
  SwerveDriveDevices devices = SwerveParser.createSwerveDriveDevices(cfg);
  drive = devices.swerveDrive();

  gyro = (Pigeon2) devices.gyro();
  gyroX = gyro.getAngularVelocityXDevice();
  gyroY = gyro.getAngularVelocityYDevice();
  gyroZ = gyro.getAngularVelocityZDevice();
}

public Rotation3d getGyroRotation3d() {
  return gyro.getRotation3d(); // full roll/pitch/yaw, not just the yaw SwerveDrive tracks
}
```

Then hand that to your vision coprocessor's orientation input the same way you would the yaw-only version, for example YALL's `LimelightSettings.withRobotOrientation(...)`. Whether the extra roll/pitch actually improves your pose estimate depends on your camera mount and how noisy that IMU data is; it isn't automatically better than yaw-only, just more complete.

{% hint style="info" %}
This pattern is vendor-specific: `getRotation3d()` and per-axis `StatusSignal`s are CTRE Pigeon2 methods. A different gyro's raw device exposes full orientation and angular velocity differently, or not at all, in which case yaw-only (from `SwerveDrive.getGyroAngle()`) is what you have, and that's fine, it's all MegaTag2 needs anyway.
{% endhint %}

## 4. Handle a `custom` gyro

If `swervedrive.json`'s `gyro.type` is `"custom"`, the parser never builds a gyro device at all, see [How to use a custom gyro](/how-to-guides/use-a-custom-gyro), so `devices.gyro()` is `null`. Your own gyro object (the one you constructed to call `SwerveDriveConfig.withGyro(...)`) is already in scope wherever you built it; there's nothing to retrieve here.

## Why `Object`?

`SwerveModuleDevices`/`SwerveDriveDevices` are deliberately vendor-agnostic. YAGSL supports mixing CTRE, REV, ThriftyBot, AndyMark, and Redux hardware across modules (see [Supported Hardware](/reference/hardware)), so there's no single common supertype to return instead of `Object`. The cast is the price of that flexibility.


# The 8 steps

## Something is wrong but it's driving-ish?

When changing inversion states doesn't work and nothing else you know of fixes the issue, debugging can be painful and arduous. Here are the eight steps that are unfortunately necessary to work through it.

Affectionately known as *"Translational Axis change based off of the robot heading"* (the X and Y axis change in field-oriented control based on your robot turning), these steps will resolve it somewhere along the way.

## Prepare your robot code

To run these tests quickly and effectively, use the following (or a variation of it) as your drive code. You may need to port it to your joystick or negate axis data, but this will help you debug faster.

{% code title="RobotContainer.java" %}

```java
SwerveDriveSubsystem drivebase;
CommandXboxController driverXbox;

// Applies deadbands and inverts controls because joysticks
// are back-right positive while robot controls are front-left positive.
// Left stick controls translation, right stick controls the desired
// heading directly (NOT angular rotation).
SwerveInputStream driveDirectAngle =
    new SwerveInputStream(drivebase.getSwerveDrive(),
                          () -> -driverXbox.getLeftY(),
                          () -> -driverXbox.getLeftX(),
                          () -> -driverXbox.getRightX(),
                          () -> -driverXbox.getRightY())
        .withAllianceRelativeControl();

drivebase.setDefaultCommand(drivebase.drive(driveDirectAngle));
```

{% endcode %}

This uses the four-argument `SwerveInputStream(drive, x, y, headingX, headingY)` constructor, which drives directly to a heading computed from the right stick instead of an angular velocity — the same "point the stick where you want the robot to face" behavior the old `SwerveController.getTargetSpeeds()` heading-hold path provided. If you're using the two-argument angular-velocity constructor instead, the steps below are identical; only the exact symptom description changes slightly.

## The eight steps

{% hint style="danger" %}
**The steps are dangerous!**

6 of them will cause your robot to spin out of control.

1 of them will be correct.

1 of them will appear to be correct but have the translational axis change based on the robot's heading.
{% endhint %}

{% hint style="warning" %}
You are not expected to complete all of these steps to get a functioning swerve drive — somewhere along the way your issue should disappear.
{% endhint %}

1. Start by setting `gyroInvert` in `swervedrive.json` to `false` **AND** `drive.inverted` to `false` in every module JSON.
2. THEN set `gyroInvert` to `true`.
3. THEN invert all of the drive motors in the module JSONs by setting `inverted.drive` to `true`.
4. THEN set `gyroInvert` back to `false`.
5. THEN [flip the modules](#user-content-fn-1)[^1].\\

   <figure><img src="/files/ssEuCY0WRJ82tOLchCIO" alt=""><figcaption><p>Depiction of flipping the modules correctly.</p></figcaption></figure>
6. THEN un-invert all of the drive motors (`inverted.drive` back to `false`).
7. THEN set `gyroInvert` to `true`.
8. THEN set your drive motors' `inverted.drive` back to `true`.

{% hint style="danger" %}
IF none of these work, you most likely have an incorrect hardware configuration, something is not working as expected, or something is wired incorrectly.
{% endhint %}

## Verifying behavior

Sometimes the eight steps don't look done when they really are. Here's an example where the robot is actually tuned correctly, but the desired front of the robot is really the back of the robot.

<details>

<summary>How to swap module configurations</summary>

For the examples, we label modules with numbers to reduce confusion; when changing module files around, we assign the numbers to the respective initial module configuration names. For the example above:

1. `frontleft.json`
2. `frontright.json`
3. `backleft.json`
4. `backright.json`

**Swapping `frontleft.json` with `backright.json`**

```json
{
  "drive": { "type": "sparkmax_neo", "id": 4, "canbus": "" },
  "angle": { "type": "sparkmax_neo", "id": 3, "canbus": "" },
  "absoluteEncoder": { "type": "cancoder_can", "id": 9, "channel": 0, "canbus": "" },
  "inverted": { "drive": false, "angle": false },
  "absoluteEncoderOffset": -114.609,
  "location": { "front": 12, "left": 12 }
}
```

```json
{
  "drive": { "type": "sparkmax_neo", "id": 5, "canbus": "" },
  "angle": { "type": "sparkmax_neo", "id": 6, "canbus": "" },
  "absoluteEncoder": { "type": "cancoder_can", "id": 11, "channel": 0, "canbus": "" },
  "inverted": { "drive": false, "angle": false },
  "absoluteEncoderOffset": -18.281,
  "location": { "front": -12, "left": -12 }
}
```

Swap the device IDs and offsets between the two files (keep each file's own `location`) and you've swapped the module configurations correctly.

#### The easy way

1. Change the `location` negations to the desired module side.
2. Rename the files without overwriting each other.

</details>

[^1]: Change the device configuration for the drive AND angle motor controllers AND the absolute encoders in the following way:

    (Front Left -> Back Right) (Front Right -> Back Left) (Back Left -> Front Right) (Back Right -> Front Left)


# Fix common SparkMAX/SparkFlex problems

These usually show up on MAXSwerve robots.

## NEOs are SparkMAX killers!

When a NEO heats up enough from being stalled too long, it will short positive to the sensor cable, sending very high current through the low-current sensor channel and melting the SparkMax PCB. This WILL kill your SparkMAX, and every SparkMAX plugged into your killer NEO.

This can also kill your laptop if you plug into a powered-on SparkMAX, because the short can extend to the USB port.

{% embed url="<https://docs.google.com/presentation/d/1uM_uPD5HjzGwYUOmzZOmmgb1-KANiTTKI3o9aIJlPcc/edit?slide=id.p#slide=id.p>" %}

## SparkMAX absolute encoder boards

The [SparkMAX Absolute Encoder board](https://www.revrobotics.com/rev-11-3326/) should be zip-tied and hot-glued to the SparkMAX for redundancy and to prevent it from coming loose.

### Throughbore connection to the absolute encoder board

The connector between the [SparkMax Absolute Encoder board](https://www.revrobotics.com/rev-11-3326/) and the [REV Throughbore](https://www.revrobotics.com/rev-11-1271/) should be hot-glued down on **BOTH** connection points — these can come loose easily.

The wire should also be tensioned just right. If it's over-tensioned, the wire might not be fully seated in the connector, and the data could be incomplete, corrupted, or unavailable.

## Absolute encoder attached via the SparkMax's duty-cycle port

When you use an absolute encoder wired to the SparkMax through its Absolute Encoder board, YAGSL identifies it as a `sparkmax`-type absolute encoder in the module JSON (see the [absolute encoder reference](/reference/hardware/absolute-encoders) for the exact type string for your device).

```json
{
  "absoluteEncoder": { "type": "revthroughbore_attached", "id": 1, "channel": 0, "canbus": "" }
}
```

{% hint style="success" %}
As of the current parser, when the absolute encoder's vendor matches the azimuth motor controller's vendor (e.g. a REV Throughbore wired into a SparkMax), YAGSL automatically wires it up as the SparkMax's external feedback sensor and applies `absoluteEncoderOffset` for you — there is no manual "set the `factor` to `360`" workaround to remember, and no `useExternalFeedbackSensor()`/`useInternalFeedbackSensor()` calls to make in your own code. Just set `absoluteEncoderOffset` in the module JSON (or via [**config.yagsl.com**](https://config.yagsl.com)) and the parser handles the rest.
{% endhint %}

{% hint style="info" %}
If you're migrating an old config that hand-set `physicalproperties.json`'s `factor` to `360` as a workaround, that field no longer exists — see the [schema changes reference](/reference/schema-changes).
{% endhint %}

## Status frame error

Usually, a status frame error means your connection to the REV Throughbore is incomplete somehow, or the sensor cable from your motor is loose or broken.

If you see this, check **ALL SENSOR CABLES GOING INTO THAT SPARKMAX**.


# Set up AdvantageScope

AdvantageScope is a data visualization tool, courtesy of team 6328 Mechanical Advantage, which can visualize the swerve drive to give you feedback for debugging.

## Opening

Since the 2024 season, [AdvantageScope](https://github.com/Mechanical-Advantage/AdvantageScope) has been included with the WPILib installation. There is no external download required, but with every WPILib update you should re-download the [WPILib installer](https://docs.wpilib.org/en/stable/docs/zero-to-robot/step-2/wpilib-setup.html) to get the latest version of WPILib tools.

## Configuring AdvantageScope

1. Connect your laptop to the robot.
2. Open `AdvantageScope (WPILib)`, or in VS Code open the command palette and type `WPILib: Start Tool`, then click `AdvantageScope`.
3. Click `Help`, then `Show Preferences`.

<figure><img src="/files/XzdnQyTidp5hwL8DDVPh" alt=""><figcaption><p>AdvantageScope's help menu</p></figcaption></figure>

4. Input the roboRIO IP address based on your team number: `10.TE.AM.2`.

<figure><img src="/files/qmZezha2OgSxD1c7gkWI" alt=""><figcaption><p>roboRIO Address field highlighted</p></figcaption></figure>

5. Connect to the robot (or the simulator).

<figure><img src="/files/97OTl7LNgatNsULoYODg" alt=""><figcaption><p>Connect to Robot menu</p></figcaption></figure>

6. Add a new tab by clicking the `+` on the right side of the window.

<figure><img src="/files/v6KfYPMaHOfSeSCsPHr3" alt=""><figcaption><p>Add new tab</p></figcaption></figure>

7. Add a new 🦀 Swerve tab.

<figure><img src="/files/uTEqX4fnooOvFhnBjT4i" alt=""><figcaption><p>Swerve tab</p></figcaption></figure>

8. Drag in each of these `Mechanisms/swerve` fields under the Sources field:
   * `Mechanisms/swerve/states/current`
   * `Mechanisms/swerve/states/desired`
   * `Mechanisms/swerve/chassis/current`
   * `Mechanisms/swerve/chassis/desired`

<figure><img src="/files/tLvQbOTR1Z1NzdZ4NX1a" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
You may need to enable your robot before AdvantageScope shows values for these fields.
{% endhint %}

To see the robot's pose on a 2D/3D field, add a **2D Field** or **3D Field** tab instead and drag in `Mechanisms/swerve/pose`.

## Reading the display

{% hint style="info" %}
The **RED** lines are the measured velocity and position of each swerve module.

The **BLUE** lines are the velocity and position each module was commanded to.
{% endhint %}

Large, persistent gaps between the red and blue lines usually point to a tuning issue — see [how to tune PIDF gains](/how-to-guides/tune-pidf-gains) and [swerve drive drift causes](/reference/swerve-drift-causes).


# View your DataLog in AdvantageScope

Pull the .wpilog off your robot and look at what actually happened during a match or test run.

{% hint style="danger" %}
**This is not "Log Replay."** [AdvantageKit's Log Replay](https://docs.advantagekit.org/getting-started/what-is-advantagekit) re-runs your robot code against a recorded log to deterministically reproduce a match in simulation, and requires the AdvantageKit IO-interface pattern. What this page covers is simpler and works with **every** YAGSL robot, AdvantageKit or not: opening the `.wpilog` file your robot already wrote and looking at the recorded values in AdvantageScope. Nothing re-executes — you're just viewing a file.
{% endhint %}

Every field `SwerveParser` publishes to NetworkTables is also written to a WPILib `DataLog` automatically — no JSON field or code needed to opt in. See [Telemetry, Simulation & Vision](/explanation/telemetry-and-vision) for the live NetworkTables tree; this page is the practical walkthrough for looking at a *recorded* run afterward.

## Step 1: Make sure a DataLog is actually being written

`DataLogManager` owns the actual file on disk. Start it once, early, in `Robot.java`:

```java
@Override
public void robotInit() {
  DataLogManager.start();
  DriverStation.startDataLog(DataLogManager.getLog()); // optional: also logs joystick/DS data
}
```

{% hint style="info" %}
If you never call `DataLogManager.start()` yourself, it still starts lazily the first time `SwerveParser` publishes a field — but calling it explicitly in `robotInit()` guarantees the log begins at power-on (not at the first telemetry update) and lets you fold in DS/joystick data with `DriverStation.startDataLog(...)`.
{% endhint %}

On the real robot, `.wpilog` files are written to a USB drive if one is plugged in, otherwise to `/home/lvuser/logs` on the roboRIO's internal storage. In simulation, they land in your project's `logs/` folder next to `build.gradle`. WPILib initially names the file `FRC_TBD_<random>.wpilog` and renames it to a timestamp (e.g. `FRC_20260817_010558.wpilog`) once it has a real time source.

## Step 2: Nothing to configure — YAGSL logs automatically

Unlike hand-building a `SwerveDriveConfig`/`SwerveModuleConfig` yourself, `SwerveParser` already wires up a `DataLog` name for the drive and every module, at every level, for you:

* `Swerve` — the drive itself (pose, gyro, chassis speeds, module states).
* `Swerve/modules/<name>` — that module's absolute encoder.
* `Swerve/modules/<name>/drive` — that module's drive motor.
* `Swerve/modules/<name>/azimuth` — that module's angle/steering motor.

{% hint style="warning" %}
Note the DataLog tree uses a top-level `Swerve` (capitalized, no `Mechanisms` prefix) — a *different* root than the live NetworkTables tree covered in [Telemetry, Simulation & Vision](/explanation/telemetry-and-vision) (`Mechanisms/swerve`, lowercase). Both describe the same data; AdvantageScope shows them as two separate branches in the field tree (`Swerve` for the DataLog-only entries, `NT` for the live/mirrored NetworkTables ones) — see the sidebar in the screenshots below.
{% endhint %}

There's no granular per-field opt-in and no way to disable this — if you're using `SwerveParser`, you already have a full DataLog recording of your swerve drive every time you run the robot.

## Step 3: Run it and record

Deploy or simulate the robot, enable it, and drive around for a bit — every field above writes to both NetworkTables and the DataLog continuously while enabled.

## Step 4: Pull the file and open it in AdvantageScope

1. Grab the `.wpilog` off the RIO (USB drive, or `scp lvuser@10.TE.AM.2:/home/lvuser/logs/*.wpilog .`) — or straight from your sim project's `logs/` folder.
2. Open **AdvantageScope**, then **File → Open File...** (or drag the `.wpilog` onto the window). Do **not** use "Connect to Robot/Simulator" — that connects live over NetworkTables, which shows *current* values, not the recorded run.

## Step 5: Explore the data

Add a tab to visualize the data:

* **2D Field** tab — drag `Swerve/pose` in to see the robot's recorded path.

<figure><img src="/files/8ybiEMei77VxQtB75BTj" alt=""><figcaption><p>Scrubbing a recorded run's path on the 2D Field tab, fed from Swerve/pose.</p></figcaption></figure>

* **Swerve** tab — AdvantageScope's built-in module visualizer. Drag `Swerve/states/current` (and optionally `Swerve/states/desired` and `Swerve/chassis/current`/`desired`) into the **Sources** list to see wheel vectors animate over the timeline.

<figure><img src="/files/8CuFPZQJ1lZOKalxj3jI" alt=""><figcaption><p>The Swerve tab replaying recorded module states — translating, then spinning in place.</p></figcaption></figure>

* **Line Graph** tab — drag any numeric field to plot it over time. A useful pairing while debugging: each module's `Swerve/modules/<name>/azimuth/mechanism/position` (the angle motor's own relative encoder, from the DataLog) against `Mechanisms/swerve/modules/<name>/encoder` (the raw absolute encoder, from the live NT mirror also present in the file) — if the two drift apart over a run, the relative encoder seeded from the wrong absolute offset.

<figure><img src="/files/3iKtZs7z4oM5d7dReEJe" alt=""><figcaption><p>Dragging each module's azimuth position and absolute encoder fields into the Line Graph tab.</p></figcaption></figure>

Scrub the timeline at the top of the window to step through the run, or hit play to watch it back at normal speed.

{% hint style="success" %}
Because this is just a file, you can open the same `.wpilog` in as many tabs/windows as you want, compare two different matches side by side, or hand the file to a teammate to look at independently — none of that requires the robot, the simulator, or AdvantageKit replay to be running.
{% endhint %}

## Related pages

* [Telemetry, Simulation & Vision](/explanation/telemetry-and-vision)
* [How to set up AdvantageScope](/how-to-guides/set-up-advantagescope)
* [Schema Changes](/reference/schema-changes)


# Troubleshooting flowchart

## Swerve drive diagram

<figure><img src="/files/mI3Vo1gWfvt34By0CQQR" alt=""><figcaption></figcaption></figure>

{% hint style="danger" %}
Older gyroscopes like the Pigeon 1 and NavX 1 drift the longer they're powered on. Consider rebooting or zeroing your gyroscope at the start of your program.
{% endhint %}

## Swerve modules spinning "out of control"

<figure><img src="/files/CySTKRsy5X7hxW1ha2hg" alt=""><figcaption></figcaption></figure>

## Swerve module does not spin correctly

1. Are the CAN IDs, motor types, and absolute encoder types correct? See the [device reference](/reference/hardware) for supported type strings.
2. Are the module `location`s correct?
3. Do you use an absolute magnetic encoder?
   1. Did you loctite the magnet in?
   2. Do you have a good (usually green LED) reading on the magnet?
4. Did you set `absoluteEncoderOffset` while the wheels had the bevel facing left and the wheel pointed straight front-to-back?
5. Did you check [how to determine inversion](/how-to-guides/determine-inversion) for inversion states?
6. Did you [tune your PID](/how-to-guides/tune-pidf-gains)?

If none of the above resolves it, move on to [The 8 steps](/how-to-guides/the-8-steps).

## Swerve drive "spins out of control"

<figure><img src="/files/CWWS4QDyhOtVuhEZpkk6" alt=""><figcaption></figcaption></figure>

See [how to determine inversion](/how-to-guides/determine-inversion) and [the 8 steps](/how-to-guides/the-8-steps).


# Information

Information-oriented lookup material: exact JSON field names/types, supported hardware, and the YAMS API surface YAGSL hands you. Come here when you already know what you're doing and need a precise answer — for step-by-step guidance see [Tutorial](/tutorial/tutorial) or [How-to Guides](/how-to-guides/how-to).

* [JSON Configuration Schema](/reference/json-schema) — every field in `swervedrive.json`, module files, `physicalproperties.json`, and `pidfproperties.json`.
* [Supported Hardware](/reference/hardware) — gyro, motor controller, and absolute encoder type tables plus per-device notes.
* [Standard Conversion Factors](/reference/standard-conversion-factors) — gear ratios/wheel diameters for common COTS swerve modules.
* [Vendordep Installation](/reference/vendordep-installation) — which vendor dependencies you need for your hardware.
* [API Reference](/reference/api-reference) — where to find docs for `SwerveDrive`, `SwerveModule`, and friends (they're YAMS classes).
* [Schema Changes](/reference/schema-changes) — migrating a pre-2026.8.05 `swerve/` config directory.
* [Swerve Drive Drift: Causes and Tuning Order](/reference/swerve-drift-causes) — hardware/software/config causes of drift, and the fixed order to tune them out.


# JSON Configuration Schema

YAGSL builds a YAMS `SwerveDrive` from a directory of JSON files. `swervelib.parser.SwerveParser` reads this directory and constructs the equivalent YAMS `SwerveDriveConfig` / `SwerveModuleConfig` / `SmartMotorControllerConfig` objects for you — you rarely need to hand-edit these files, since [config.yagsl.com](https://config.yagsl.com) generates them for you, but knowing the schema is useful for debugging, scripting, or hand-tweaking a single value.

{% hint style="info" %}
If you're setting up a robot for the first time, use the [Tutorial](/tutorial/tutorial) section instead — this is reference material for looking up exact field names and types.
{% endhint %}

## Directory layout

```
src/main/deploy
└── swerve
    └── base                       <- name is arbitrary, passed to `new SwerveParser(...)`
        ├── swervedrive.json
        └── modules
            ├── frontleft.json
            ├── frontright.json
            ├── backleft.json
            ├── backright.json     <- module filenames are arbitrary, referenced by swervedrive.json
            ├── physicalproperties.json
            ├── pidfproperties.json
            └── pidfproperties_sim.json   (optional)
```

`SwerveParser` reads, in order:

1. `swervedrive.json` — top-level drive config: gyro + list of module files.
2. `modules/physicalproperties.json` — default gearing and current limits shared by every module.
3. `modules/pidfproperties.json` — default PID + feedforward gains shared by every module. If `modules/pidfproperties_sim.json` also exists, it's used instead whenever `RobotBase.isSimulation()` is `true`, so you can tune sim and real gains independently.
4. Each file listed in `swervedrive.json`'s `modules` array — one per swerve module, describing that module's hardware, wiring, and location. A module file may override the gearing from `physicalproperties.json` with its own `gearing` object.

All JSON is parsed with Jackson in lenient mode (`FAIL_ON_UNKNOWN_PROPERTIES = false`), so unknown fields are ignored rather than rejected — useful if you're migrating a file field-by-field.

## Pages in this section

* [swervedrive.json](/reference/json-schema/swervedrive-json)
* [module json (per-module file)](/reference/json-schema/module-json)
* [physicalproperties.json](/reference/json-schema/physicalproperties-json)
* [pidfproperties.json / pidfproperties\_sim.json](/reference/json-schema/pidfproperties-json)
* [Device type strings](/reference/json-schema/device-types) — every valid `type` value for gyros, motor controllers, and absolute encoders.

{% hint style="warning" %}
Coming from a pre-2026.8.05 `swerve/` directory (`imu`, `encoder`, `conversionFactors`, `controllerproperties.json`, PIDF `f`/`iz`)? See [Schema Changes](/reference/schema-changes) for a full migration guide.
{% endhint %}

## Control system and CAN buses

Every device's `canbus` field is just a bus name — `""` selects your control system's **default** CAN bus, and any other string must match a bus your control system actually exposes to WPILib.

{% hint style="info" %}
**2027 season note:** the roboRIO is being replaced by the SystemCore, which exposes several CAN buses natively instead of a single `rio` bus plus an optional CTRE CANivore. `canbus` isn't changing shape for this — it still just names whichever bus a device is on. Today, on a roboRIO, that's `""` for the `rio` bus, or a CANivore's configured name for CTRE devices on a CANivore. Once WPILib/vendor support for SystemCore's buses lands, do the same thing: `""` for the default bus, and the specific bus name for anything else.
{% endhint %}


# swervedrive.json

Top-level drive configuration. Parsed into `swervelib.parser.json.SwerveDriveJson`.

```json
{
  "gyro": {
    "type": "pigeon2_can",
    "id": 0,
    "canbus": ""
  },
  "gyroAxis": "yaw",
  "gyroInvert": false,
  "modules": ["frontleft.json", "frontright.json", "backleft.json", "backright.json"]
}
```

| Field        | Type                                               | Required             | Description                                                                                                                             |
| ------------ | -------------------------------------------------- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `gyro`       | [Device](/reference/json-schema/device-types#gyro) | yes                  | The robot's gyroscope. See [Device type strings](/reference/json-schema/device-types#gyro) for valid `type` values.                     |
| `gyroAxis`   | string enum                                        | no (default `"yaw"`) | Which physical axis of the gyro to use for robot heading: `yaw`, `pitch`, or `roll`.                                                    |
| `gyroInvert` | boolean                                            | yes                  | Invert the gyroscope's heading reading.                                                                                                 |
| `modules`    | string\[]                                          | yes                  | Filenames (relative to the `modules/` directory) of each module's JSON file, minimum 3, listed clockwise from front-left by convention. |

## `gyro` object

| Field    | Type   | Description                                                                                                                                                                                                                        |
| -------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type`   | string | Gyro type string — see [Device type strings](/reference/json-schema/device-types#gyro). Set to `custom` to skip YAGSL's gyro setup and configure the gyro yourself; see [Custom Gyro](/reference/hardware/gyroscopes#custom-gyro). |
| `id`     | number | CAN ID. Set to `0` if the gyro doesn't need one.                                                                                                                                                                                   |
| `canbus` | string | CAN bus name the gyro is on. `""` for the default (rio) bus.                                                                                                                                                                       |

{% hint style="info" %}
When `gyro.type` is `custom`, `gyroAxis` and `gyroInvert` above are also ignored — the parser never calls `SwerveDriveConfig.withGyro()`/`withGyroInverted()`, so you must call them yourself.
{% endhint %}


# Module JSON

One file per swerve module (e.g. `frontleft.json`), referenced by name from `swervedrive.json`'s `modules` array. Parsed into `swervelib.parser.json.ModuleJson`.

```json
{
  "drive": {
    "type": "sparkmax_neo",
    "id": 1,
    "canbus": ""
  },
  "angle": {
    "type": "sparkmax_neo",
    "id": 2,
    "canbus": ""
  },
  "absoluteEncoder": {
    "type": "revthroughbore_attached",
    "id": 0,
    "channel": 0,
    "canbus": ""
  },
  "inverted": {
    "drive": false,
    "angle": false
  },
  "absoluteEncoderOffset": -45.0,
  "absoluteEncoderInverted": false,
  "location": {
    "front": 12.0,
    "left": 12.0
  }
}
```

| Field                     | Type                                                            | Required             | Description                                                                                                                                                                                                               |
| ------------------------- | --------------------------------------------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `drive`                   | [Device](/reference/json-schema/device-types#motor-controllers) | yes                  | Drive motor controller. See [Device type strings](/reference/json-schema/device-types#motor-controllers).                                                                                                                 |
| `angle`                   | [Device](/reference/json-schema/device-types#motor-controllers) | yes                  | Angle/steering/azimuth motor controller.                                                                                                                                                                                  |
| `absoluteEncoder`         | [Device](/reference/json-schema/device-types#absolute-encoders) | yes                  | Module's absolute encoder. See [Device type strings](/reference/json-schema/device-types#absolute-encoders).                                                                                                              |
| `gearing`                 | object                                                          | no                   | Per-module override of drive/angle gearing. If omitted, the module uses the defaults from `physicalproperties.json`. Same shape as [physicalproperties.json's `gearing`](/reference/json-schema/physicalproperties-json). |
| `inverted.drive`          | boolean                                                         | yes                  | Invert the drive motor.                                                                                                                                                                                                   |
| `inverted.angle`          | boolean                                                         | yes                  | Invert the angle motor.                                                                                                                                                                                                   |
| `absoluteEncoderOffset`   | number (degrees)                                                | yes                  | Offset so the wheel reads "forward" when physically pointed forward with the bevel gear to the left. Determined by rotating the wheel to face forward and reading the raw absolute encoder value.                         |
| `absoluteEncoderInverted` | boolean                                                         | no (default `false`) | Invert the absolute encoder's reading.                                                                                                                                                                                    |
| `location.front`          | number (inches)                                                 | yes                  | Distance from the robot's center to the module's center, along the front/back axis.                                                                                                                                       |
| `location.left`           | number (inches)                                                 | yes                  | Distance from the robot's center to the module's center, along the left/right axis.                                                                                                                                       |

## `drive` / `angle` object

| Field    | Type   | Description                                                                                                      |
| -------- | ------ | ---------------------------------------------------------------------------------------------------------------- |
| `type`   | string | Controller+motor type string — see [Device type strings](/reference/json-schema/device-types#motor-controllers). |
| `id`     | number | CAN ID of the motor controller.                                                                                  |
| `canbus` | string | CAN bus the controller is on. `""` for the default bus.                                                          |

## `absoluteEncoder` object

| Field     | Type   | Description                                                                                             |
| --------- | ------ | ------------------------------------------------------------------------------------------------------- |
| `type`    | string | Encoder type string — see [Device type strings](/reference/json-schema/device-types#absolute-encoders). |
| `id`      | number | CAN ID, if the encoder is on CAN. Ignored otherwise.                                                    |
| `channel` | number | AnalogIn channel, if the encoder is analog/duty-cycle. Ignored otherwise.                               |
| `canbus`  | string | CAN bus, if applicable. `""` for the default bus.                                                       |

{% hint style="info" %}
There is no per-module `useCosineCompensator` toggle anymore — cosine compensation is always applied by the parser. See [Module Behaviors](/explanation/module-behaviors) for what it does.
{% endhint %}


# physicalproperties.json

Default gearing and current limits shared by every module, unless a module's own JSON file overrides `gearing`. Parsed into `swervelib.parser.json.PhysicalPropertiesJson`.

```json
{
  "gearing": {
    "drive": {
      "gearRatio": 6.75,
      "diameter": 4.0
    },
    "angle": {
      "gearRatio": 12.8
    }
  },
  "statorCurrentLimit": {
    "drive": 40,
    "angle": 20
  }
}
```

| Field                      | Type            | Required          | Description                                          |
| -------------------------- | --------------- | ----------------- | ---------------------------------------------------- |
| `gearing.drive.gearRatio`  | number          | yes               | Drive motor reduction, as the `X` in `X:1`.          |
| `gearing.drive.diameter`   | number (inches) | yes               | Wheel diameter.                                      |
| `gearing.angle.gearRatio`  | number          | yes               | Angle/steering motor reduction, as the `X` in `X:1`. |
| `statorCurrentLimit.drive` | number (amps)   | no (default `40`) | Drive motor stator current limit.                    |
| `statorCurrentLimit.angle` | number (amps)   | no (default `20`) | Angle motor stator current limit.                    |

See [Standard Conversion Factors](/reference/standard-conversion-factors) for gear ratios/wheel diameters of common COTS swerve modules.

{% hint style="warning" %}
Several fields that existed before 2026.8.05 are gone: `friction`, `steerRotationalInertia`, `robotMass`, `rampRate`, `wheelGripCoefficientOfFriction`, `optimalVoltage`. See [Schema Changes](/reference/schema-changes) for why.
{% endhint %}


# pidfproperties.json

Default closed-loop PID + feedforward gains shared by every module. Parsed into `swervelib.parser.json.PIDFPropertiesJson` (each of `drive`/`angle` is a `swervelib.parser.PIDFConfig`).

```json
{
  "drive": {
    "p": 0.5,
    "i": 0.0,
    "d": 0.01
  },
  "angle": {
    "p": 2.0,
    "i": 0.0,
    "d": 0.1
  }
}
```

| Field | Type   | Required | Description                                                                                                                  |
| ----- | ------ | -------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `p`   | number | yes      | Proportional gain.                                                                                                           |
| `i`   | number | yes      | Integral gain.                                                                                                               |
| `d`   | number | yes      | Derivative gain.                                                                                                             |
| `s`   | number | no       | `kS` — static friction feedforward (`SimpleMotorFeedforward`).                                                               |
| `v`   | number | no       | `kV` — velocity feedforward. If left as `0` for the **drive** motor, the parser auto-derives it from the motor's free speed. |
| `a`   | number | no       | `kA` — acceleration feedforward.                                                                                             |

Both `drive` and `angle` are required top-level keys.

## `pidfproperties_sim.json`

Optional. If present, `SwerveParser` uses it instead of `pidfproperties.json` whenever `RobotBase.isSimulation()` is `true` — same shape as above. Use it to tune simulation gains separately from your real robot's gains, without needing to swap files by hand.

{% hint style="warning" %}
There is no more `f` (feedforward) or `iz` (integral zone) field, and no more `output` min/max clamp object — gains are now expressed as plain PID plus a `SimpleMotorFeedforward`-style `s`/`v`/`a`. See [Schema Changes](/reference/schema-changes).
{% endhint %}

See [How to Tune PIDF Gains](/how-to-guides/tune-pidf-gains) for a tuning procedure and starting-point values.


# Device Type Strings

Every device `type` string YAGSL's parser currently recognizes, grouped by device kind. These are the exact enum values enforced by the JSON Schema files shipped in the vendordep (`vendordep/src/test/resources/schemas/`) and resolved in code by `swervelib.parser.deserializer.ReflectionsManager` and the per-vendor classes under `swervelib.parser.deserializer.reflections`.

{% hint style="info" %}
[config.yagsl.com](https://config.yagsl.com) only lets you pick valid combinations, so you rarely need to type these strings by hand — this page is for reading/debugging existing config files.
{% endhint %}

## Gyro

Used in [swervedrive.json](/reference/json-schema/swervedrive-json)'s `gyro.type`.

| Type string           | Hardware                                                                                        |
| --------------------- | ----------------------------------------------------------------------------------------------- |
| `navx3_can`           | Studica NavX3, over CAN (CAN FD)                                                                |
| `pigeon2_can`         | CTRE Pigeon 2.0, over CAN                                                                       |
| `canandgyro_can`      | Redux Canandgyro, over CAN                                                                      |
| `systemcore_internal` | Internal gyro on the SystemCore control system                                                  |
| `custom`              | Any gyro not built by the parser (e.g. a roboRIO SPI/I2C Studica AHRS) — you supply it yourself |

See [Gyroscopes](/reference/hardware/gyroscopes) for wiring/calibration notes per device.

## Motor Controllers

Used in [module json](/reference/json-schema/module-json)'s `drive.type` and `angle.type`. Format is `<controller>_<motor>`.

| Controller family | Compatible motor suffixes                             |
| ----------------- | ----------------------------------------------------- |
| `talonfx_`        | `krakenx44`, `krakenx60`                              |
| `talonfxs_`       | `neo`, `neo2`, `neo550`, `vortex`, `pulsar`, `minion` |
| `sparkmax_`       | `neo`, `neo2`, `neo550`, `vortex`, `pulsar`, `minion` |
| `sparkflex_`      | `neo`, `neo2`, `neo550`, `vortex`, `pulsar`, `minion` |
| `nova_`           | `neo`, `neo2`, `neo550`, `vortex`, `pulsar`, `minion` |

e.g. `talonfx_krakenx60`, `sparkmax_neo`, `sparkflex_vortex`, `nova_minion`.

Every motor suffix above is a **brushless** motor. SparkMAX itself is still fully supported, but only in brushless mode — there is no `type` value for a brushed motor on any controller family (the old `sparkmax_brushed` and `talonsrx` types are gone). See [Motor Controllers](/reference/hardware/motor-controllers) and [Schema Changes](/reference/schema-changes).

{% hint style="warning" %}
The schema doesn't restrict every combination per-controller (e.g. `talonfx_neo` is syntactically valid JSON) — not every motor is actually usable on every controller family in practice. Stick to combinations [config.yagsl.com](https://config.yagsl.com) offers, or a combination you've confirmed via the controller's own vendor tooling.
{% endhint %}

See [Motor Controllers](/reference/hardware/motor-controllers) for per-controller notes.

## Absolute Encoders

Used in [module json](/reference/json-schema/module-json)'s `absoluteEncoder.type`. Format is roughly `<encoder>_<connection>`.

| Type string                | Hardware                                                      |
| -------------------------- | ------------------------------------------------------------- |
| `revthroughbore_attached`  | REV Through Bore Encoder, plugged into the motor controller   |
| `revthroughbore_dio`       | REV Through Bore Encoder, wired to a roboRIO DIO port         |
| `splineencoder_can`        | Spline encoder, over CAN                                      |
| `cancoder_can`             | CTRE CANcoder, over CAN                                       |
| `canandmag_attached`       | Redux Canandmag, plugged into the motor controller            |
| `canandmag_dio`            | Redux Canandmag, wired to a roboRIO DIO port                  |
| `canandmag_can`            | Redux Canandmag, over CAN                                     |
| `srxmag_attached`          | CTRE Mag Encoder (SRX Mag), plugged into the motor controller |
| `srxmag_analog`            | CTRE Mag Encoder (SRX Mag), wired to a roboRIO analog port    |
| `andymarkhexbore_attached` | AndyMark Hex Bore Encoder, plugged into the motor controller  |
| `andymarkhexbore_dio`      | AndyMark Hex Bore Encoder, wired to a roboRIO DIO port        |
| `andymarkhexbore_analog`   | AndyMark Hex Bore Encoder, wired to a roboRIO analog port     |
| `andymarkhexbore_can`      | AndyMark Hex Bore Encoder, over CAN                           |
| `analog5v_attached`        | Generic 5V analog encoder, plugged into the motor controller  |
| `analog_attached`          | Generic analog encoder, plugged into the motor controller     |
| `dutycycle_attached`       | Generic duty-cycle encoder, plugged into the motor controller |

See [Absolute Encoders](/reference/hardware/absolute-encoders) for per-device notes and calibration tips.


# Supported Hardware

Lookup tables for every gyro, motor controller, and absolute encoder type string YAGSL's parser currently accepts, plus per-device wiring/calibration notes and known issues.

* [Gyroscopes](/reference/hardware/gyroscopes)
* [Motor Controllers](/reference/hardware/motor-controllers)
* [Absolute Encoders](/reference/hardware/absolute-encoders)

For the raw enum values as they appear in JSON, see [Device Type Strings](/reference/json-schema/device-types).


# Gyroscopes

Supported gyroscope/IMU types for swervedrive.json's gyro field

{% hint style="danger" %}
**Hardware support narrowed in the 2026.8.05 rewrite.** YAGSL now only instantiates gyros over **CAN**. The roboRIO SPI/I2C/USB-serial NavX (original NavX2 `navx`/`navx_spi`/`navx_i2c`/ `navx_mxp_serial`/`navx_usb`), the original CTRE **Pigeon** (gen 1), and the analog SPI gyros (**ADXRS450**, **ADIS16448**, **ADIS16470**) are **not supported** by the current parser. If your robot uses one of those devices, see [Schema Changes](/reference/schema-changes) — you'll need to either move to a supported CAN gyro or use the [`custom` gyro type](#custom-gyro) below to keep wiring it up yourself.
{% endhint %}

## Gyroscope Checklist

* [ ] Gyroscope readings increase when rotated counter-clockwise (CCW+).
* [ ] Yaw reading is the robot heading.
* [ ] Gyroscope `0°` is the desired robot "front".

## Configuring the gyro

In `swervedrive.json` the gyro is one object plus two drive-wide settings:

```json
{
  "gyro": {
    "type": "pigeon2_can",
    "id": 13,
    "canbus": "canivore"
  },
  "gyroAxis": "yaw",
  "gyroInvert": true,
  "modules": ["frontleft.json", "frontright.json", "backleft.json", "backright.json"]
}
```

* `gyro.type` — one of the supported types below, formatted `vendor_connection`, or `custom` to configure the gyro yourself (see [Custom Gyro](#custom-gyro)).
* `gyro.id` — CAN ID of the device (ignored where not applicable).
* `gyro.canbus` — CAN bus name. Use `""` for your control system's default bus (`rio` on a roboRIO), or a CANivore name if the device is on one — see [Control system and CAN buses](/reference/json-schema#control-system-and-can-buses).
* `gyroAxis` — which physical axis of the sensor to read as robot heading: `yaw` (default), `pitch`, or `roll`. Only change this if the sensor is mounted on an edge or angle.
* `gyroInvert` — invert the heading reading. If your robot spins out of control with no controller input, invert this.

{% hint style="warning" %}
Only CTRE devices support the `canbus` option for a CANivore. If your device is on your control system's default CAN bus (`rio` on a roboRIO), use `""`. If it's on a CANivore, `canbus` must match the CANivore's configured name.
{% endhint %}

## Supported gyroscope types

| Device                                                                  | `type`                | Communication                                                                                          |
| ----------------------------------------------------------------------- | --------------------- | ------------------------------------------------------------------------------------------------------ |
| [Pigeon 2](#pigeon-2)                                                   | `pigeon2_can`         | CAN; supports CANivore                                                                                 |
| [Canandgyro](https://docs.reduxrobotics.com/canandgyro/getting-started) | `canandgyro_can`      | CAN; default bus only, no CANivore                                                                     |
| [NavX3-CAN](#navx3-can)                                                 | `navx3_can`           | CAN 2.0 / CAN FD                                                                                       |
| SystemCore internal IMU                                                 | `systemcore_internal` | *listed in the config schema but not yet implemented — the parser throws if selected. Do not use yet.* |
| [Custom](#custom-gyro)                                                  | `custom`              | *any* — you construct and supply the gyro yourself                                                     |

If you need a gyro not on this list — including the older roboRIO SPI/I2C/USB-serial NavX (AHRS) devices called out in the hint above, which the CAN-only parser no longer builds — use the `custom` type below.

## Custom Gyro

{% hint style="info" %}
This is the reference summary — for a full walkthrough see [How to use a custom gyro](/how-to-guides/use-a-custom-gyro).
{% endhint %}

Set `gyro.type` to `custom` and YAGSL's parser skips gyro configuration entirely: it never calls `SwerveDriveConfig.withGyro()`/`withGyroInverted()`, and `gyroAxis`/`gyroInvert` in `swervedrive.json` are ignored. You're expected to call `withGyro()`/`withGyroInverted()` yourself on the `SwerveDriveConfig` you pass into `SwerveParser.createSwerveDrive(...)` — everything else in the config (modules, motor controllers, encoders) is still built normally from the JSON.

This is the escape hatch for any gyro the parser doesn't build for you — most commonly the roboRIO SPI-mounted [Studica AHRS](https://www.studica.com/navx2-micro) (the classic NavX2), since only the CAN-based NavX3 is supported directly.

```json
{
  "gyro": { "type": "custom", "id": 0, "canbus": "" },
  "gyroAxis": "yaw",
  "gyroInvert": false,
  "modules": ["frontleft.json", "frontright.json", "backleft.json", "backright.json"]
}
```

```java
import com.studica.frc.AHRS;
import static edu.wpi.first.units.Units.Degrees;

// Onboard MXP SPI port is the common mounting for a roboRIO AHRS.
private final AHRS gyro = new AHRS(AHRS.NavXComType.kMXP_SPI);

public SwerveDriveSubsystem() {
  SwerveDriveConfig cfg = new SwerveDriveConfig()
      .withSubsystem(this)
      .withTelemetry(TelemetryVerbosity.HIGH)
      // AHRS reports CW+; YAGSL/YAMS expect CCW+, so negate it here instead of relying on
      // gyroInvert (which is ignored for a "custom" gyro).
      .withGyro(() -> Degrees.of(-gyro.getAngle()))
      .withGyroInverted(false);

  drive = new SwerveParser(new File(Filesystem.getDeployDirectory(), "swerve/base"))
      .createSwerveDrive(cfg);
}
```

{% hint style="warning" %}
Because `withGyroInverted()` is never called by the parser for a `custom` gyro, do any inversion you need directly in the `Supplier<Angle>` you pass to `withGyro()`, as shown above.
{% endhint %}

## NavX3-CAN

{% hint style="info" %}
NavX3-CAN supports CAN 2.0 and has CAN-FD capability. See Studica's notes for CAN-FD requirements.
{% endhint %}

* [Studica product page](https://www.studica.co/navx3-can-imu)
* [Studica NavX releases, firmware tools, and vendordeps](https://github.com/Studica-Robotics/NavX)

**Checklist**

* [ ] Install the **StudicaLib** vendordep (do not install both `Studica` and `StudicaLib`).
* [ ] Update NavX3-CAN firmware to **5.0.4+** using Studica Hardware Manager.
* [ ] Use Studica Hardware Manager to find the CAN ID (often ships as `0`) and enter it in your config.

NavX3-CAN ships factory-calibrated. For higher accuracy, re-calibrate with Studica Hardware Manager — it auto-detects sensor orientation during calibration. Connect CAN-H/CAN-L to the robot bus and make sure the bus is properly terminated.

## Pigeon 2

* [Product page](https://store.ctr-electronics.com/pigeon-2/)
* [Hardware reference](https://pro.docs.ctr-electronics.com/en/latest/docs/hardware-reference/pigeon2/index.html)
* Upgradeable via [Tuner X](https://v6.docs.ctr-electronics.com/en/latest/docs/tuner/index.html) — pay attention to the LED status code when debugging. Any settings changed in Tuner X are overwritten on startup by YAGSL.

**Checklist**

* [ ] Yaw increments counter-clockwise positive.
* [ ] Pigeon 2 is mounted as close to the robot's center as possible.
* [ ] Updated to the latest firmware, on a unique CAN ID.
* [ ] Calibrated once installed on the robot.

Communicates over CAN and can be paired with a [CANivore](https://store.ctr-electronics.com/canivore/) to keep it off your control system's default CAN bus — [set a CANivore name](https://pro.docs.ctr-electronics.com/en/latest/docs/canivore/canivore-setup.html) and use it as `canbus`.

## Canandgyro

* [Getting started](https://docs.reduxrobotics.com/canandgyro/getting-started)
* CAN only; does not support CANivore.

***

### General gyro tips (still apply regardless of device)

* Mount the gyro as close to the robot's center of rotation as practical — off-center mounting couples translational vibration into the heading reading.
* Always keep the robot still for a few seconds after power-on if your device does an on-the-fly calibration; moving during that window produces a bad heading for the rest of the match.
* If the robot's heading drifts over a match, see [Swerve Drive Drift Causes](/reference/swerve-drift-causes).


# Motor Controllers

Supported motor controller/motor combinations for module drive and angle motors

{% hint style="danger" %}
**TalonSRX and brushed SparkMAX are no longer supported.** The pre-2026.8.05 `talonsrx` and `sparkmax_brushed` types have been removed — YAGSL now only drives brushless motors through YAMS's `SmartMotorController` abstraction. See [Schema Changes](/reference/schema-changes).
{% endhint %}

## Motor Checklist

* [ ] All motors spin counterclockwise positive (CCW+); document any inversions required to achieve that.
* [ ] Motor is updated to the latest firmware.
* [ ] Motor has a unique CAN ID that matches its module JSON.
* [ ] Wheels are aligned forwards with all bevels facing the same way before first enable.
* [ ] PID is tuned — see [Tune PIDF Gains](/how-to-guides/tune-pidf-gains).

## Configuring a motor

Each module JSON (`frontleft.json`, etc.) has a `drive` and an `angle` motor, both `DeviceJson` objects of the form `"<controller>_<motor>"`:

```json
{
  "drive": { "type": "sparkflex_vortex", "id": 5, "canbus": "" },
  "angle":  { "type": "sparkmax_neo550", "id": 6, "canbus": "" }
}
```

The `<motor>` suffix tells YAGSL which `DCMotor` model to use for feedforward and simulation characterization (free speed, stall torque, etc.) — it must match the physical motor attached to that controller.

## Supported controller × motor combinations

| Controller                              | `type` values                                                                                                     |
| --------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| [TalonFX](#talonfx-krakenx60-krakenx44) | `talonfx_krakenx44`, `talonfx_krakenx60`                                                                          |
| [TalonFXS](#talonfxs)                   | `talonfxs_neo`, `talonfxs_neo2`, `talonfxs_neo550`, `talonfxs_vortex`, `talonfxs_pulsar`, `talonfxs_minion`       |
| [SparkMAX](#sparkmax)                   | `sparkmax_neo`, `sparkmax_neo2`, `sparkmax_neo550`, `sparkmax_vortex`, `sparkmax_pulsar`, `sparkmax_minion`       |
| [SparkFlex](#sparkflex)                 | `sparkflex_neo`, `sparkflex_neo2`, `sparkflex_neo550`, `sparkflex_vortex`, `sparkflex_pulsar`, `sparkflex_minion` |
| Nova (ThriftyBot)                       | `nova_neo`, `nova_neo2`, `nova_neo550`, `nova_vortex`, `nova_pulsar`, `nova_minion`                               |

{% hint style="warning" %}
This is the full cross-product the config generator and parser accept for feedforward/simulation purposes — it is **not** a claim that every combination is physically wireable. Respect real hardware constraints: TalonFX only physically drives Kraken X44/X60 motors, SparkMAX/SparkFlex drive REV motors via their own connectors, and Nova drives ThriftyBot motors. Pick the row for your actual controller, and the `<motor>` suffix that matches what's physically bolted to it.
{% endhint %}

Only CTRE controllers (`talonfx_*`, `talonfxs_*`) support the `canbus` field for CANivore use — set it to `""` for your control system's default bus (`rio` on a roboRIO). See [Control system and CAN buses](/reference/json-schema#control-system-and-can-buses).

## TalonFX (Kraken X60 / Kraken X44)

TalonFX is the controller for Kraken X44/X60 and (legacy) Falcon 500 motors. Commonly used as a drive motor for its power and efficiency versus REV NEOs.

* [Kraken product page](https://wcproducts.com/products/kraken)
* Upgradeable/configurable via [Tuner X](https://v6.docs.ctr-electronics.com/en/latest/docs/tuner/index.html) — watch the LED status code when debugging. Settings changed in Tuner X are overwritten on startup by YAGSL.

{% hint style="warning" %}
If running Falcon 500 v2 (effectively discontinued), apply the [Loctite fix](https://content.vexrobotics.com/vexpro/Falcon/217-6515-753-Falcon500-V2-Upgrade.pdf).
{% endhint %}

## TalonFXS

TalonFXS is CTRE's controller for third-party/non-Kraken brushless motors (NEO, NEO Vortex, Minion, Pulsar) wired through CTRE's ecosystem — same Tuner X tooling and CAN/CANivore support as TalonFX.

## SparkMAX

REV SparkMAX, brushless mode (NEO / NEO 550 / NEO Vortex / Minion / Pulsar via the appropriate `<motor>` suffix).

* [Product page](https://www.revrobotics.com/rev-11-2158/)
* Requires the [REV Hardware Client](https://docs.revrobotics.com/rev-hardware-client/gs/install) to test PID, run at a set percentage, and update firmware.

{% hint style="warning" %}
The SparkMAX must be disconnected from the CAN bus **on startup** to be recognized by the REV Hardware Client.
{% endhint %}

To tune PID directly on the controller: open REV Hardware Client → select the SparkMAX → Telemetry tab → set to position control while adjusting gains in the left pane.

**Checklist**

* [ ] Unique CAN ID.
* [ ] Latest firmware.
* [ ] Rotates CCW+ (invert programmatically if not).
* [ ] PID tuned for one drive motor and one angle motor before wiring the rest.

## SparkFlex

REV SparkFlex, brushless mode (same motor suffixes as SparkMAX, primarily used with NEO Vortex).

* [Product page](https://www.revrobotics.com/next-generation-spark-neo/)
* Same [REV Hardware Client](https://docs.revrobotics.com/rev-hardware-client-2) workflow and startup-disconnect caveat as SparkMAX.

***

### General motor tips (apply regardless of controller)

* Confirm CCW+ rotation on the bench before mounting — see [When to Invert](/how-to-guides/determine-inversion).
* Tune one drive motor and one angle motor fully before copying gains to the other three modules; see [Tune PIDF Gains](/how-to-guides/tune-pidf-gains).
* Current limits and gearing live in `physicalproperties.json` (or a per-module `gearing` override) — see [physicalproperties.json Reference](/reference/json-schema/physicalproperties-json).


# Absolute Encoders

Supported absolute encoder types for a module's absoluteEncoder field

YAGSL supports most common FRC absolute encoders. An absolute encoder is required for every module — it's what lets the angle motor know its true position on power-up, without needing to "home" against a hard stop.

The live reading shows up on the dashboard under `Mechanisms/swerve/modules/<name>/encoder` (degrees) — use it to find the value for `absoluteEncoderOffset` in each module's JSON. See [Telemetry, Simulation & Vision](/explanation/telemetry-and-vision) for the full NT tree.

## Absolute Encoder Checklist

* [ ] All absolute encoders read counterclockwise positive (CCW+).
* [ ] Magnetic absolute encoders get a good read on the magnet both at rest and while the robot is moving (check for wiggle/wire strain).
* [ ] Each encoder has a unique CAN ID or analog/DIO channel.
* [ ] Encoder reads the full `0°`–`360°` range.

## Configuring an absolute encoder

`absoluteEncoder` is a `DeviceJson`-shaped object, `"<vendor>_<connection>"`:

```json
{
  "absoluteEncoder": { "type": "cancoder_can", "id": 11, "channel": 0, "canbus": "" },
  "absoluteEncoderOffset": -18.281,
  "absoluteEncoderInverted": false
}
```

* `absoluteEncoderOffset` — degrees to add so the wheel reads `0°` when facing forward with the bevel gear to the left. Read this off `Raw Absolute Encoder` on the dashboard with the wheel manually squared up, then enter the negative of that raw value (config.yagsl.com does this arithmetic for you).
* `absoluteEncoderInverted` — should rarely, if ever, need to be `true`.
* For encoders wired to an analog or DIO channel, set `channel` instead of `id`/`canbus`.

{% hint style="warning" %}
If a module spins continuously without settling, try inverting its angle motor before suspecting the absolute encoder.
{% endhint %}

## Supported absolute encoder types

| Device                                                                                                      | `type`                     | Connection                                                |
| ----------------------------------------------------------------------------------------------------------- | -------------------------- | --------------------------------------------------------- |
| [REV Throughbore](https://www.revrobotics.com/rev-11-1271/)                                                 | `revthroughbore_attached`  | Plugged into the angle motor controller's duty-cycle port |
| REV Throughbore                                                                                             | `revthroughbore_dio`       | roboRIO DIO                                               |
| CTRE [CANcoder](https://pro.docs.ctr-electronics.com/en/latest/docs/hardware-reference/cancoder/index.html) | `cancoder_can`             | CAN; supports CANivore                                    |
| [Canandmag](https://docs.reduxrobotics.com/canandmag/getting-started)                                       | `canandmag_attached`       | Plugged into the angle motor controller's duty-cycle port |
| Canandmag                                                                                                   | `canandmag_dio`            | roboRIO DIO                                               |
| Canandmag                                                                                                   | `canandmag_can`            | CAN                                                       |
| [SRX Mag Encoder](https://store.ctr-electronics.com/srx-mag-encoder/)                                       | `srxmag_attached`          | Plugged into the angle motor controller's duty-cycle port |
| SRX Mag Encoder                                                                                             | `srxmag_analog`            | roboRIO analog input                                      |
| [AndyMark Hex Bore](https://www.andymark.com/)                                                              | `andymarkhexbore_attached` | Plugged into the angle motor controller's duty-cycle port |
| AndyMark Hex Bore                                                                                           | `andymarkhexbore_dio`      | roboRIO DIO                                               |
| AndyMark Hex Bore                                                                                           | `andymarkhexbore_analog`   | roboRIO analog input                                      |
| AndyMark Hex Bore                                                                                           | `andymarkhexbore_can`      | CAN                                                       |
| Spline Encoder                                                                                              | `splineencoder_can`        | CAN (REV)                                                 |
| Generic analog (5V)                                                                                         | `analog5v_attached`        | Plugged into the angle motor controller's 5V analog port  |
| Generic analog                                                                                              | `analog_attached`          | Plugged into the angle motor controller's analog port     |
| Generic duty-cycle                                                                                          | `dutycycle_attached`       | Plugged into the angle motor controller's duty-cycle port |

The `_attached` connection means the encoder is wired directly into the angle motor controller (SparkMAX/SparkFlex duty-cycle or analog port, etc.) rather than a roboRIO or CAN device — YAGSL reads it back through that controller's vendor API. This replaces the old `SwerveDrive.pushOffsetsToEncoders()` mechanism from pre-2026.8.05 releases.

***

### General absolute encoder tips (apply regardless of device)

* Set the offset with the wheel physically squared to the robot, not by eyeballing degrees on the dashboard from an arbitrary position.
* Re-check offsets any time a module is disassembled — even a "put it back the same way" rebuild can shift the magnet by a few degrees, and swerve is unforgiving of small offset errors at low speed.
* If you swap a module between corners, its encoder wiring moves with it — the offset value does not automatically follow. See [Verify Module Locations](/how-to-guides/verify-module-locations).


# Standard Conversion Factors

Known-good gearing values for common commercial-off-the-shelf swerve modules

These are known-good `gearRatio`/`diameter` values for common COTS swerve modules. Set them either as the drivetrain-wide default in `modules/physicalproperties.json`, or as a per-module override in a module's own `gearing` field (module-level `gearing` wins if both are present):

```json
"gearing": {
  "drive": { "gearRatio": 6.75, "diameter": 4 },
  "angle": { "gearRatio": 12.8 }
}
```

* `drive.gearRatio` — motor revolutions per wheel revolution, as `X` where the ratio is `X:1`.
* `drive.diameter` — wheel diameter in **inches**.
* `angle.gearRatio` — motor revolutions per full azimuth revolution, as `X:1`.

The config generator at [config.yagsl.com](https://config.yagsl.com) has these built in as presets — you generally won't need to type these by hand, but they're listed here for reference and for teams hand-editing JSON.

## MAX Swerve

| Pinion | `angle.gearRatio` | `drive.gearRatio` | `drive.diameter` |
| ------ | ----------------- | ----------------- | ---------------- |
| 12T    | 46.42             | 5.50              | 3                |
| 13T    | 46.42             | 5.08              | 3                |
| 14T    | 46.42             | 4.71              | 3                |

## Swerve Drive Specialties (SDS)

| Module  | `angle.gearRatio` | `drive.gearRatio` | `drive.diameter` |
| ------- | ----------------- | ----------------- | ---------------- |
| MK4 L1  | 12.8              | 8.14              | 4                |
| MK4 L2  | 12.8              | 6.75              | 4                |
| MK4 L3  | 12.8              | 6.12              | 4                |
| MK4 L4  | 12.8              | 5.14              | 4                |
| MK4i L1 | 21.4285714286     | 8.14              | 4                |
| MK4i L2 | 21.4285714286     | 6.75              | 4                |
| MK4i L3 | 21.4285714286     | 6.12              | 4                |
| MK5i R1 | 26                | 7.03              | 4                |
| MK5i R2 | 26                | 6.03              | 4                |
| MK5i R3 | 26                | 5.27              | 4                |
| MK4n L1 | 18.75             | 7.13              | 4                |
| MK4n L2 | 18.75             | 5.9               | 4                |
| MK4n L3 | 18.75             | 5.36              | 4                |
| MK5n R1 | 26.09             | 7.03              | 4                |
| MK5n R2 | 26.09             | 6.03              | 4                |
| MK5n R3 | 26.09             | 5.27              | 4                |

## Thrifty Swerve

Steering gear ratio is **25:1** for both configurations below (12T pinion, 3" wheel, NEO-driven). Check the gear-ratio chart on your Thrifty Swerve for other pinion/output combinations.

| Output gear | `angle.gearRatio` | `drive.gearRatio` | `drive.diameter` |
| ----------- | ----------------- | ----------------- | ---------------- |
| 18T         | 25                | 15                | 3                |
| 16T         | 25                | 16.9              | 3                |

## Plummer Industries

| Configuration      | `angle.gearRatio` | `drive.gearRatio` | `drive.diameter` |
| ------------------ | ----------------- | ----------------- | ---------------- |
| Corner, Mid Ratio  | 28                | 4                 | 2.5              |
| Corner, High Ratio | 28                | 3.25              | 2.5              |

## Deriving your own

If your module isn't listed, compute the reduction from the module's datasheet: `gearRatio` is motor revolutions per output revolution (drive → wheel, angle → azimuth), and `diameter` is the wheel diameter in inches, measured on a slightly-worn tread (a fresh, un-worn wheel measures larger than it will after a few matches, and using the worn number keeps odometry accurate longer). Overestimating `diameter` makes the robot think it's traveled less than it has, so odometry-driven autonomous routines undershoot; verify against a real drive-straight test as described in [Verify Module Locations](/how-to-guides/verify-module-locations).


# Vendordep Installation

Vendor dependencies YAGSL requires and how to install them

## Install YAGSL

YAGSL is listed directly in the **WPILib Vendor Dependencies** panel's catalog (VS Code sidebar) — open it and click **Install** next to YAGSL, no URL required.

<figure><img src="/files/RzptwWT6YJakdPiLCOoG" alt=""><figcaption><p>YAGSL listed directly in the WPILib Vendor Dependencies panel.</p></figcaption></figure>

If your WPILib version predates YAGSL being in that catalog, install manually instead: use **Manage Vendor Libraries → Install new library (online)** and paste:

```
https://yet-another-software-suite.github.io/YAGSL/yagsl/yagsl.json
```

See the [WPILib guide to installing 3rd-party libraries](https://docs.wpilib.org/en/stable/docs/software/vscode-overview/3rd-party-libraries.html#installing-libraries) for the general workflow.

## Required vendor libraries

YAGSL's own vendordep manifest currently declares these as hard requirements — Gradle will fail to build without them, even if your robot doesn't use every one of them, because YAGSL is generic across all supported hardware. Install each one from the same WPILib Vendor Dependencies panel as YAGSL:

| Vendor         | Required by YAGSL's manifest | Needed for                                                 |
| -------------- | ---------------------------- | ---------------------------------------------------------- |
| REVLib         | Yes                          | SparkMAX / SparkFlex                                       |
| CTRE Phoenix 6 | Yes                          | TalonFX / TalonFXS / Pigeon 2 / CANcoder                   |
| CTRE Phoenix 5 | Yes                          | (legacy CTRE device support in the wider WPILib ecosystem) |
| StudicaLib     | Yes                          | NavX3-CAN                                                  |
| ThriftyLib     | Yes                          | Nova motor controllers, Thrifty absolute encoders          |

{% hint style="warning" %}
If your robot uses a **Canandgyro**, **Canandmag**, or other Redux Robotics device, add **ReduxLib** yourself from the same panel — it is not currently in YAGSL's auto-required list, even though the parser supports Redux devices.
{% endhint %}

{% hint style="info" %}
If a vendor isn't in the panel's catalog on your WPILib version, fall back to **Manage Vendor Libraries → Install new library (online)** with a URL from the vendor's own install page — see the [WPILib guide to installing 3rd-party libraries](https://docs.wpilib.org/en/stable/docs/software/vscode-overview/3rd-party-libraries.html#installing-libraries).
{% endhint %}

You'll also want the vendor's own tuning/firmware tools installed on your development machine (not a robot-code dependency, but needed to configure/calibrate hardware):

* [REV Hardware Client 2](https://docs.revrobotics.com/rev-hardware-client-2)
* [CTRE Tuner X](https://v6.docs.ctr-electronics.com/en/latest/docs/installation/installation-frc.html)
* Studica Hardware Manager (NavX3-CAN)


# API Reference

YAGSL's own Java surface is intentionally small: `swervelib.parser.SwerveParser` reads your [JSON config](/reference/json-schema) and hands you back a fully-built **YAMS** `SwerveDrive`. From that point on, you're driving and configuring a YAMS object, not a YAGSL-specific one — so the authoritative API reference lives in the YAMS docs, not here.

| Class you get back from `SwerveParser`                 | YAMS API reference                                                                                           |
| ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ |
| `yams.mechanisms.swerve.SwerveDrive`                   | [SwerveDrive](https://yagsl.gitbook.io/yams/api-reference/java-reference/swerve/swerve-drive)                |
| `yams.mechanisms.config.SwerveDriveConfig`             | [SwerveDriveConfig](https://yagsl.gitbook.io/yams/api-reference/java-reference/swerve/swerve-drive-config)   |
| `yams.mechanisms.swerve.SwerveModule` (per-module)     | [SwerveModule](https://yagsl.gitbook.io/yams/api-reference/java-reference/swerve/swerve-module)              |
| Module config (built internally from your module JSON) | [SwerveModuleConfig](https://yagsl.gitbook.io/yams/api-reference/java-reference/swerve/swerve-module-config) |
| `yams.mechanisms.swerve.utility.SwerveInputStream`     | [SwerveInputStream](https://yagsl.gitbook.io/yams/api-reference/java-reference/swerve/swerve-input-stream)   |

The full YAMS API reference (all mechanisms, motor controllers, units, and math helpers — not just swerve) is at [yagsl.gitbook.io/yams](https://yagsl.gitbook.io/yams/api-reference/java-reference).

`SwerveParser.createSwerveDriveDevices(config)` is the one place YAGSL hands back something other than YAMS objects: alongside the `SwerveDrive`, it returns the raw vendor motor controller/encoder/ gyro objects it built, for the rare case where you need to call a vendor-specific method `SmartMotorController` doesn't expose. See [How to access raw hardware devices](/how-to-guides/access-raw-hardware-devices).

{% hint style="info" %}
Only `SwerveParser` and the JSON-file classes under `swervelib.parser.json` are YAGSL-specific. If you find yourself calling a method on `SwerveDrive`, `SwerveModule`, `SwerveDriveConfig`, `SwerveModuleConfig`, or `SwerveInputStream`, you're looking at YAMS API — go to the links above, not this repo's source, for the authoritative docs and Javadoc.
{% endhint %}

## Where to look for what

* **"What JSON fields exist?"** → [JSON Schema Reference](/reference/json-schema) (this repo).
* **"What does `SwerveDrive.drive(...)` do, what are all its methods?"** → [YAMS SwerveDrive reference](https://yagsl.gitbook.io/yams/api-reference/java-reference/swerve/swerve-drive).
* **"How do I build a `SwerveDriveConfig` by hand instead of from JSON?"** → [YAMS SwerveDriveConfig reference](https://yagsl.gitbook.io/yams/api-reference/java-reference/swerve/swerve-drive-config).
* **"How do I wire joystick input into field/robot-relative chassis speeds?"** → [YAMS SwerveInputStream reference](https://yagsl.gitbook.io/yams/api-reference/java-reference/swerve/swerve-input-stream).
* **"What changed in the JSON schema recently?"** → [Schema Changes](/reference/schema-changes).


# Schema Changes (Migrating from pre-2026.8.05)

Between the `2026.4.1` and `2026.8.05` releases, YAGSL was rewritten from its own standalone swerve implementation into a thin JSON parser that builds a **YAMS** `SwerveDrive` (`swervelib.parser.SwerveParser` → `yams.mechanisms.swerve.SwerveDrive`). The JSON configuration schema changed to match what YAMS's config builders need. If your `src/main/deploy/swerve/` directory predates this, none of your old JSON files will parse correctly against the new schema — this page is a field-by-field diff to help you port them by hand. For a fresh config, it's easier to just regenerate everything from [config.yagsl.com](https://config.yagsl.com).

{% hint style="warning" %}
This is a breaking change, not an additive one. The parser silently ignores unknown fields (`FAIL_ON_UNKNOWN_PROPERTIES = false`), so an old file will "parse" without errors but silently drop every renamed/restructured field, leaving you with defaults you didn't intend. Don't assume an old config "still works" just because it loads without an exception — diff it against this page.
{% endhint %}

## `swervedrive.json`

| Old (≤ 2026.4.1)          | New (≥ 2026.8.05)                  | Notes                                                                                             |
| ------------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------- |
| `imu: {type, id, canbus}` | `gyro: {type, id, canbus}`         | Renamed. Old `type` values like `"pigeon2"`, `"pigeon"`, `"navx"` also changed shape — see below. |
| `invertedIMU: boolean`    | `gyroInvert: boolean`              | Renamed.                                                                                          |
| *(none)*                  | `gyroAxis: "yaw"\|"pitch"\|"roll"` | New, optional, defaults to `"yaw"`.                                                               |
| `modules: string[]`       | `modules: string[]`                | Unchanged.                                                                                        |

**Old:**

```json
{
  "imu": { "type": "pigeon2", "id": 13, "canbus": "canivore" },
  "invertedIMU": true,
  "modules": ["frontleft.json", "frontright.json", "backleft.json", "backright.json"]
}
```

**New:**

```json
{
  "gyro": { "type": "pigeon2_can", "id": 13, "canbus": "canivore" },
  "gyroAxis": "yaw",
  "gyroInvert": true,
  "modules": ["frontleft.json", "frontright.json", "backleft.json", "backright.json"]
}
```

Note the `type` string itself changed too — old bare names (`"pigeon2"`, `"pigeon"`, `"navx"`) became explicit `<device>_<connection>` strings (`"pigeon2_can"`). See [Device Type Strings](/reference/json-schema/device-types) for the current, complete list.

## Module JSON (e.g. `frontleft.json`)

| Old (≤ 2026.4.1)                                                    | New (≥ 2026.8.05)                                                                                                                                                                                       | Notes                                                                                                                                                                                                     |
| ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `encoder: {type, id, canbus}`                                       | `absoluteEncoder: {type, id, channel, canbus}`                                                                                                                                                          | Renamed, and gained a `channel` field for analog/DIO-connected encoders. `type` strings changed (`"cancoder"` → `"cancoder_can"`, etc.) — see [Device Type Strings](/reference/json-schema/device-types). |
| `absoluteEncoderOffset: number`                                     | `absoluteEncoderOffset: number`                                                                                                                                                                         | Unchanged (degrees).                                                                                                                                                                                      |
| *(none)*                                                            | `absoluteEncoderInverted: boolean`                                                                                                                                                                      | New, optional, defaults to `false`.                                                                                                                                                                       |
| `drive` / `angle`: `{type, id, canbus}`                             | Same shape, but `type` strings changed to `<controller>_<motor>` (e.g. `"sparkmax_neo"` unchanged, but `"talonfx"` alone became `"talonfx_krakenx60"` etc. — the motor is now part of the type string). | See [Device Type Strings](/reference/json-schema/device-types).                                                                                                                                           |
| *(module-level conversion lived only in `physicalproperties.json`)* | `gearing: {drive: {gearRatio, diameter}, angle: {gearRatio}}` (optional, per-module override)                                                                                                           | New: a module can now override the shared gearing without touching `physicalproperties.json`.                                                                                                             |
| `inverted: {drive, angle}`                                          | `inverted: {drive, angle}`                                                                                                                                                                              | Unchanged.                                                                                                                                                                                                |
| `location: {front, left}`                                           | `location: {front, left}`                                                                                                                                                                               | Unchanged (inches).                                                                                                                                                                                       |

**Old:**

```json
{
  "drive": { "type": "sparkmax_neo", "id": 4, "canbus": null },
  "angle": { "type": "sparkmax_neo", "id": 3, "canbus": null },
  "encoder": { "type": "cancoder", "id": 9, "canbus": null },
  "inverted": { "drive": false, "angle": false },
  "absoluteEncoderOffset": -114.609,
  "location": { "front": 12, "left": 12 }
}
```

**New:**

```json
{
  "drive": { "type": "sparkmax_neo", "id": 4, "canbus": "" },
  "angle": { "type": "sparkmax_neo", "id": 3, "canbus": "" },
  "absoluteEncoder": { "type": "cancoder_can", "id": 9, "channel": 0, "canbus": "" },
  "inverted": { "drive": false, "angle": false },
  "absoluteEncoderOffset": -114.609,
  "absoluteEncoderInverted": false,
  "location": { "front": 12, "left": 12 }
}
```

## `modules/physicalproperties.json`

| Old (≤ 2026.4.1)                                                                        | New (≥ 2026.8.05)                                             | Notes                                                                                                                                   |
| --------------------------------------------------------------------------------------- | ------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `conversionFactors: {drive: {gearRatio, diameter, factor}, angle: {gearRatio, factor}}` | `gearing: {drive: {gearRatio, diameter}, angle: {gearRatio}}` | Renamed and simplified — the precomputed `factor` override is gone; YAMS derives the conversion internally from `gearRatio`/`diameter`. |
| `currentLimit: {drive, angle}`                                                          | `statorCurrentLimit: {drive, angle}`                          | Renamed. Defaults are now `40`/`20` if omitted.                                                                                         |
| `friction: {drive, angle}`                                                              | *(removed)*                                                   | No longer configurable via JSON.                                                                                                        |
| `steerRotationalInertia: number`                                                        | *(removed)*                                                   | No longer configurable via JSON.                                                                                                        |
| `robotMass: number`                                                                     | *(removed)*                                                   | No longer configurable via JSON.                                                                                                        |
| `rampRate: {drive, angle}`                                                              | *(removed)*                                                   | No longer configurable via JSON.                                                                                                        |
| `wheelGripCoefficientOfFriction: number`                                                | *(removed)*                                                   | No longer configurable via JSON.                                                                                                        |
| `optimalVoltage: number`                                                                | *(removed)*                                                   | No longer configurable via JSON.                                                                                                        |

**Old:**

```json
{
  "conversionFactors": {
    "angle": { "gearRatio": 12.8, "factor": 0 },
    "drive": { "gearRatio": 8.14, "diameter": 4, "factor": 0 }
  },
  "currentLimit": { "drive": 40, "angle": 20 },
  "rampRate": { "drive": 0.25, "angle": 0.25 },
  "wheelGripCoefficientOfFriction": 1.19,
  "optimalVoltage": 12
}
```

**New:**

```json
{
  "gearing": {
    "drive": { "gearRatio": 8.14, "diameter": 4.0 },
    "angle": { "gearRatio": 12.8 }
  },
  "statorCurrentLimit": { "drive": 40, "angle": 20 }
}
```

If your old config relied on `rampRate`, `friction`, `robotMass`, `steerRotationalInertia`, or `wheelGripCoefficientOfFriction` for simulation fidelity, that modeling now lives inside YAMS/the motor sim itself rather than being team-configurable per robot via this file.

## `modules/pidfproperties.json`

| Old (≤ 2026.4.1)             | New (≥ 2026.8.05)                    | Notes                                                                                                                         |
| ---------------------------- | ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------- |
| `p`, `i`, `d`                | `p`, `i`, `d`                        | Unchanged.                                                                                                                    |
| `f: number`                  | *(removed, replaced by `s`/`v`/`a`)* | Old `f` was a flat feedforward multiplier; the new fields model a proper `SimpleMotorFeedforward` (`kS`, `kV`, `kA`) instead. |
| `iz: number` (integral zone) | *(removed)*                          | No longer configurable.                                                                                                       |
| `output: {min, max}` (clamp) | *(removed)*                          | No longer configurable via JSON.                                                                                              |
| *(none)*                     | `s`, `v`, `a: number` (all optional) | New feedforward gains. If `drive.v` is left `0`/omitted, the parser auto-derives `kV` from the drive motor's free speed.      |

**Old:**

```json
{
  "drive": { "p": 0.00023, "i": 0.0000002, "d": 1, "f": 0, "iz": 0 },
  "angle": { "p": 0.01, "i": 0, "d": 0, "f": 0, "iz": 0 }
}
```

**New:**

```json
{
  "drive": { "p": 0.5, "i": 0.0, "d": 0.01 },
  "angle": { "p": 2.0, "i": 0.0, "d": 0.1 }
}
```

You'll need to re-tune from scratch rather than porting old `p`/`i`/`d` values directly — the old gains were tuned against a different internal control loop (raw `f`/`iz`) than the new feedforward-based one. See [How to Tune PIDF Gains](/how-to-guides/tune-pidf-gains).

## `controllerproperties.json` — removed entirely

Pre-2026.8.05 YAGSL read a `controllerproperties.json` with an `angleJoystickRadiusDeadband` and a `heading: {p, i, d}` heading-hold PID, used internally by a `swervelib.SwerveController` class to build field-relative driving with heading lock. **Both the file and the class are gone.**

```json
// This file no longer does anything — delete it.
{
  "angleJoystickRadiusDeadband": 0.5,
  "heading": { "p": 0.4, "i": 0, "d": 0.01 }
}
```

Field-relative/heading-based driving is now built in your own robot code using YAMS's `yams.mechanisms.swerve.utility.SwerveInputStream`, not a JSON-configured heading PID. See [SwerveInputStream reference](https://yagsl.gitbook.io/yams/api-reference/java-reference/swerve/swerve-input-stream) and the [tutorial's driving step](/tutorial/05-deploy-and-drive) for the current pattern.

## Hardware support that was dropped, not just renamed

This isn't just a field-rename — some previously-supported gyro and motor-controller hardware has **no equivalent type string at all** in the current parser (`swervelib.parser.deserializer.reflections`). If your old config used one of these, there's no direct migration — you need different hardware or a custom fork:

* **Gyros:** the roboRIO-attached NavX2 (`navx`, `navx_spi`, `navx_i2c`, `navx_mxp_serial`, `navx_usb`), the original Pigeon (gen 1), and analog SPI gyros (`adxrs450`, `adis16448`, `adis16470`) are all gone. Only `pigeon2_can`, `navx3_can`, and `canandgyro_can` are currently supported. (`systemcore_internal` is listed in the schema but currently throws `"Internal gyro not supported yet!"` — don't use it yet.)
* **Motor controllers:** `talonsrx` and brushed `sparkmax_brushed` are gone — only the five brushless controller families (`talonfx`, `talonfxs`, `sparkmax`, `sparkflex`, `nova`) are supported now, each paired with a brushless motor. See [Device Type Strings](/reference/json-schema/device-types).

If you're on old gyro/motor hardware that's no longer supported, budget time to swap hardware, not just JSON — this is a real capability change, not a documentation gap.

## Behavior changes that aren't JSON fields

* **Cosine compensation** used to be toggleable per-module (`useCosineCompensator`, default `true`). It's now always applied by the parser — there's no way to disable it via config.
* **Deploy directory construction** changed from directly instantiating `SwerveDrive`/module classes to `new SwerveParser(dir).createSwerveDrive(new SwerveDriveConfig()...)`. See [Deploy and Drive](/tutorial/05-deploy-and-drive).

## Migration checklist

1. Regenerate from [config.yagsl.com](https://config.yagsl.com) if at all possible — it's faster and less error-prone than hand-porting every field above.
2. If hand-porting: rename `imu`→`gyro`, `invertedIMU`→`gyroInvert` in `swervedrive.json`; add `gyroAxis: "yaw"`.
3. Rename `encoder`→`absoluteEncoder` in every module file, add a `channel` (usually `0` if CAN), add `absoluteEncoderInverted: false`.
4. Update every `type` string to the new `<device>_<connection>` convention — see [Device Type Strings](/reference/json-schema/device-types).
5. Replace `conversionFactors`→`gearing` in `physicalproperties.json`, dropping any `factor` override. Replace `currentLimit`→`statorCurrentLimit`.
6. Delete `controllerproperties.json`; move any heading-lock behavior into robot code via `SwerveInputStream`.
7. Re-tune PIDF from scratch using the new `p`/`i`/`d`/`s`/`v`/`a` shape — old gains don't transfer.
8. Delete any `rampRate`/`friction`/`robotMass`/`steerRotationalInertia`/ `wheelGripCoefficientOfFriction`/`optimalVoltage` fields — they're ignored now.


# Swerve Drive Drift: Causes and Tuning Order

Drift can be caused by anything and everything in a swerve drive...

Swerve drive drift has been a persistent problem for many years, and there have been many discussions as to why it happens. This is a lookup of common causes, followed by the fixed tuning order that removes what's left. For step-by-step tuning instructions, see [How to Tune PIDF Gains](/how-to-guides/tune-pidf-gains).

It's important to keep in mind that SEVERAL of these issues could be present on your robot at once.

## Hardware causes

### Wiring

Incorrect, worn, or loose wiring produces different symptoms depending on which connector is affected.

**Encoder**

* [ ] Telemetry readings are erratic and nonsensical.
* [ ] Motor could spin out of control.
* [ ] Errors appear in the Driver Station console.

**Motor controller**

* [ ] Telemetry may be slow to update.
* [ ] Motors may suddenly stop or spin out of control.
* [ ] Motors may not respond to commands.
* [ ] Errors appear in the Driver Station console.

**Gyroscope**

* [ ] Telemetry may be slow to update.
* [ ] Field-relative robot control may be unusable.
* [ ] Autonomous drifts uncontrollably.
* [ ] Errors appear in the Driver Station console.
* [ ] The gyro is moving/shaking violently, or shifted during movement.

### Physical components

**Magnetic (absolute) encoder**

* [ ] Magnet is not secured to the swerve module and slips while in motion — absolute encoder offsets would need constant readjustment until the magnet is secured (loctite/glue it in).
* [ ] Magnetic encoder itself is not secured to the module.

**Motor**

* [ ] Motor doesn't have a firm connection to the module shaft — it will "twitch" while running if a current limit is set.
* [ ] Motor is dying — it may pull more current to hit the same speeds.

**Swerve module**

* [ ] Wheels are misaligned.
* [ ] Modules behave inconsistently with the same PID gains — grease may need to be applied to the gears.

**Robot**

* [ ] Robot center of gravity is not ideal.

## Software causes

* [ ] Constant `Command Scheduler loop overruns` because of other parts of your code.
* [ ] Maximum physical velocity isn't set correctly — see `SwerveDriveConfig.withMaximumChassisSpeed()`/`withMaximumModuleSpeed()`.
* [ ] If using open-loop control, modules run at different speeds.
* [ ] Controller input filtering.
* [ ] Discretization not compensating for system delay — see `SwerveDriveConfig.withDiscretizationTime()`.
* [ ] Vision measurement standard deviations aren't tuned — see `SwerveDrive.addVisionMeasurement()`/`setVisionMeasurementStdDevs()`.
* [ ] Gyro offset drifted — see `SwerveDrive.zeroGyro()` / `SwerveDriveConfig.withGyroOffset()`.

{% hint style="info" %}
Several runtime tuning knobs from older YAGSL versions (`setCosineCompensator`, `setHeadingCorrection`, `setAntiJitter`, `chassisVelocityCorrection`, `setMaximumSpeeds`, `replaceSwerveModuleFeedforward`, `setOdometryPeriod`, `updateCacheValidityPeriods`) no longer exist as runtime methods on `SwerveDrive`. Cosine compensation is now always enabled internally by the parser, and the rest are configured once, up front, via `SwerveDriveConfig`/`SwerveModuleConfig` (or `pidfproperties.json`'s `s`/`v`/`a` feedforward terms) rather than toggled at runtime. See the [API reference](/reference/api-reference) and [schema changes reference](/reference/schema-changes) for what replaced each.
{% endhint %}

### PathPlanner

* [ ] PathPlanner `AutoBuilder` PIDs aren't tuned well.
* [ ] Maximum module velocity is too high or too low.
* [ ] Test path is curved instead of straight.
* [ ] PathPlanner auto doesn't define a preset starting pose.
* [ ] PathPlanner path doesn't reset odometry on start.

## Configuration causes

* [ ] Module locations aren't defined correctly — remember these are measured from the center of the robot to the center of the module. See [how to verify module locations](/how-to-guides/verify-module-locations).
* [ ] Swerve module JSON doesn't have the correct device definitions for the absolute encoder, angle motor, or drive motor.
* [ ] A module's `gearing` override in its module JSON disagrees with the default in `physicalproperties.json`.
  * A per-module `gearing` override is completely **OPTIONAL** and shouldn't be defined unless you have a specific reason (e.g. one swapped module with a different ratio) — otherwise rely on `physicalproperties.json` to avoid drift between modules.
* [ ] The gear ratio/wheel diameter is not correct.
  * If your swerve drive consistently travels further than expected with PathPlanner, this is a likely cause.
  * If your angle motors never align the module correctly, this can also cause it.
* [ ] Drive and steering/azimuth/angle motor PIDs aren't tuned well enough — see [how to tune PIDF gains](/how-to-guides/tune-pidf-gains).
* [ ] The current limit (`statorCurrentLimit`) is too low.
* [ ] [Translational axis changes with robot orientation](/how-to-guides/the-8-steps).

## Tuning order

Once hardware and configuration issues are ruled out, tune in this order — each step builds on the last, so redoing an earlier one (or replacing hardware like your gyro) means redoing everything after it:

1. **Drive and angle PID** — see [how to tune PIDF gains](/how-to-guides/tune-pidf-gains). Do this first; nothing else is worth tuning until the modules track their setpoints well.
2. **Vision standard deviation**, if you're using vision to help odometry — tune `SwerveDrive.setVisionMeasurementStdDevs()` mostly by guess-and-check, scaling with distance. Some teams get better stability by filtering out vision poses beyond a certain distance.
3. **Discretization**, to compensate for system delay — `SwerveDriveConfig.withDiscretizationTime()`. See [chassis control](/explanation/chassis-control) for what this does and why it matters.
4. **Gyro angular velocity scale factor**, to compensate for gyro system delay — `SwerveDriveConfig.withGyroAngularVelocityScaleFactor()` (less necessary with a Pigeon 2 on a CANivore, which has very low latency).

{% hint style="warning" %}
If you mess with a lower step, all the higher steps will be out of sync — and if you replace a part like your gyro, you may need to redo all of it anyway.
{% endhint %}

The math-heavy explanation of tuning out drift is available here:

{% embed url="<https://github.com/calcmogul/controls-engineering-in-frc>" %}


# Why?

Understanding-oriented background: swerve drive theory, how YAGSL/YAMS behave under the hood, and why. Nothing here is a step-by-step procedure — for that, see [Tutorial](/tutorial/tutorial) or [How-to Guides](/how-to-guides/how-to).

* [What is YAGSL?](/explanation/what-is-yagsl)
* [Swerve Drive Kinematics](/explanation/swerve-drive-kinematics)
* [Swerve Modules](/explanation/swerve-modules)
* [Module Behaviors](/explanation/module-behaviors) — cosine compensation, heading correction, module auto-synchronization, offset offloading
* [Chassis Control](/explanation/chassis-control) — max speed, chassis speed discretization, lock pose
* [Telemetry, Simulation & Vision](/explanation/telemetry-and-vision)
* [Changelog](/explanation/changelog)


# What is YAGSL?

We make your swerve drive run!

## Where does YAGSL fit into my program?

YAGSL used to be a full swerve drive implementation of its own. It no longer is. **YAGSL is now a thin JSON configuration parser that builds a** [**YAMS**](https://yams.yamgen.com/) **`SwerveDrive`.**

YAMS ([Yet Another Mechanism Suite](https://yams.yamgen.com/)) is a general-purpose FRC mechanism library — arms, elevators, flywheels, and swerve drives all share the same underlying `SmartMotorController` abstraction, feedforward/feedback plumbing, simulation support, and telemetry. YAMS's `yams.mechanisms.swerve.SwerveDrive` is a complete, hardware-agnostic swerve implementation in its own right; it does not need YAGSL to function.

What YAGSL adds on top is the part FRC teams actually re-do every season: turning a description of *your* robot's hardware (which motor controllers, which absolute encoders, which gyro, what gear ratios, where each module sits) into the Java objects YAMS needs — `SwerveDriveConfig`, `SwerveModuleConfig`, and `SmartMotorControllerConfig` — without you hand-writing that boilerplate for every robot.

<figure><img src="/files/Km38jp1aPAWPozzeYrEv" alt="created by DeltaDizzy"><figcaption><p>Diagram depicting a drive subsystem and where <code>SwerveDrive</code> fits into one. (created by DeltaDizzy)</p></figcaption></figure>

## How the pieces fit together

1. You describe your robot's hardware as JSON files (`swervedrive.json`, and per-module files under `modules/`) — generated for you by [**config.yagsl.com**](https://config.yagsl.com), or hand-written if you prefer.
2. `swervelib.parser.SwerveParser` reads that JSON, resolves each device string (e.g. `sparkflex_neo`, `cancoder_can`) to the right vendor hardware wrapper, and builds a YAMS `SwerveModuleConfig`/`SmartMotorControllerConfig` per module.
3. `SwerveParser.createSwerveDrive(SwerveDriveConfig)` hands those configs to YAMS, which constructs and returns a ready-to-drive `yams.mechanisms.swerve.SwerveDrive`.
4. From there, everything — kinematics, odometry, telemetry, simulation, vision fusion, driver-input shaping via `SwerveInputStream` — is YAMS's API, documented in the [YAMS API reference](/reference/api-reference). YAGSL only exists at the boundary between "JSON describing my robot" and "a working `SwerveDrive` object."

```java
var cfg = new SwerveDriveConfig()
    .withStartingPose(new Pose2d(3, 3, Rotation2d.kZero))
    .withSubsystem(this)
    .withTelemetry(TelemetryVerbosity.HIGH);

drive = new SwerveParser(new File(Filesystem.getDeployDirectory(), "swerve/base"))
    .createSwerveDrive(cfg);
```

{% hint style="info" %}
If you already know YAMS, or you outgrow what the JSON schema can express, you can always construct `SwerveDriveConfig`/`SwerveModuleConfig` yourself and skip `SwerveParser` entirely — it is a convenience layer, not a requirement.
{% endhint %}

## Our Philosophy

Your program does not revolve around your swerve drive. Your constants file doesn't have to take 10 minutes to find the right option. Different robots should be able to work with the same code — swap the `swerve/` config directory and the same subsystem code drives a different robot.

## Why do we exist?

Most swerve drive code out there is a template that teams are expected to modify and fit to their robot: not generic, and requiring a lot of time and effort to get working. Even after all that effort, bugs can hide in code you copied and never fully understood. There is a better way.

Do you have multiple robots and don't want to change any code to get them to work the same? Create a configuration directory, point `SwerveParser` at it, and go.

## Goals of this documentation

* Teach the fundamentals of a `SwerveDrive` and `SwerveModule` so you can debug and reason about your robot, even though YAMS builds the actual objects for you. See [Swerve Drive Kinematics](/explanation/swerve-drive-kinematics) and [Swerve Modules](/explanation/swerve-modules).
* Walk you through generating a configuration and bringing up your first YAGSL-driven robot — see the [Tutorial](/tutorial/tutorial).
* Give you task-focused recipes for the debugging work every swerve team eventually needs — see the [How-to Guides](/how-to-guides/how-to).
* Document the current JSON schema and hardware support precisely — see the [Reference](/reference/reference) section.


# Swerve Drive Kinematics

How does Swerve Drive work?

<figure><img src="/files/yZNwzSVR0RDjtrnhs8fF" alt=""><figcaption><p>Swerve Drive simulation</p></figcaption></figure>

## Tips while building a Swerve Drive

* Center the gyroscope in the robot, this will help prevent a small drift and ensure more accurate odometry.
* Make sure the magnets (if you're using them) are glued in right!
* Set aside time, assume you will mess up building 1 module or otherwise need a spare during competitions.
* Programming a Swerve Drive is hard, and while YAGSL/YAMS try to make it easier there are many things you must know to fully understand what you are doing!
* Use the right tools for the job! Debugging a Swerve Drive is difficult enough by text only, try out a dashboard like [AdvantageScope](https://github.com/Mechanical-Advantage/AdvantageScope/tree/main) — it has excellent visualization tools that are sure to help you out! See [how to set up AdvantageScope](/how-to-guides/set-up-advantagescope).

## The Basics

Swerve Drives move around by moving each wheel to a specific angle/azimuth and rotating the wheel to go in that direction. Swerve Drives are unique because they can rotate independently of their translational movement, meaning you can move in any direction while facing any direction. As a result you can "turn in-place" and rotate while moving around an area. The rotation of your robot is referred to as the **heading.**

## What is a Swerve Drive?

A Swerve Drive typically consists of 4 Swerve Modules (which are in essence a drive motor, an angle/azimuth motor, and an absolute encoder), and a gyroscope (centered is best). The motors, absolute encoders, and gyroscope do not matter and can all work together with varying degrees of success. As a rule of thumb, if you can stick to one system do it (all REV, all CTRE) — this will give you the best feature set, however they are not required to be the same! For all other use cases YAGSL is the best choice because it (and YAMS underneath it) was built with abstraction in mind, making all sensors and motor controllers functionally equivalent.

#### TL;DR

A swerve drive is composed of

* [ ] Gyroscope
* [ ] Swerve Module
  * [ ] Angle/Azimuth Motor (+ controller)
  * [ ] Drive Motor
  * [ ] Absolute Encoder

#### TL;DR Things that cause issues

* [ ] Bad Center of Gravity
* [ ] Non-centered gyroscope
* [ ] Non-square drive train

This is not a complete list and will grow over time.

## How does a Swerve Drive work in code?

Swerve Drives move each module to a specific angle determined by the direction you want to go and heading you want to face. For FRC we can get these values by hand by calculating the kinematics of the robot, or use [`SwerveDriveKinematics`](https://github.wpilib.org/allwpilib/docs/release/java/edu/wpi/first/math/kinematics/SwerveDriveKinematics.html), which uses the module locations to determine what the rotation and speed of each wheel should be given a [`ChassisSpeeds`](https://github.wpilib.org/allwpilib/docs/release/java/edu/wpi/first/math/kinematics/ChassisSpeeds.html) object, and returns a [`SwerveModuleState`](https://github.wpilib.org/allwpilib/docs/release/java/edu/wpi/first/math/kinematics/SwerveModuleState.html) array. The `SwerveModuleState` can then be used to set the angle/azimuth and speed corresponding to each Swerve Module to go in the desired direction while facing the desired heading.

{% hint style="info" %}
YAMS's `SwerveDrive` builds and manages `SwerveDriveKinematics`, `SwerveModuleState`, and odometry for you internally based on the module locations in your JSON config. The illustration below shows what it's doing under the hood — useful for debugging, not something you need to write yourself.
{% endhint %}

### `SwerveDriveKinematics`

<pre class="language-java" data-title="SwerveDrive.java" data-line-numbers data-full-width="false"><code class="lang-java">// Import relevant classes.
import edu.wpi.first.math.kinematics.SwerveDriveKinematics;
import edu.wpi.first.math.geometry.Translation2d;
import edu.wpi.first.math.util.Units;

// Illustrative example of what YAMS builds internally from your module locations.
public class SwerveDrive {

    // Attributes
<strong>    SwerveDriveKinematics kinematics;
</strong>
    // Constructor
    public SwerveDrive() {
        // Create SwerveDriveKinematics object
        // 12.5in from center of robot to center of wheel.
        // 12.5in is converted to meters to work with object.
        // Translation2d(x,y) == Translation2d(front, left)
<strong>        kinematics = new SwerveDriveKinematics(
</strong><strong>            new Translation2d(Units.inchesToMeters(12.5), Units.inchesToMeters(12.5)), // Front Left
</strong><strong>            new Translation2d(Units.inchesToMeters(12.5), Units.inchesToMeters(-12.5)), // Front Right
</strong><strong>            new Translation2d(Units.inchesToMeters(-12.5), Units.inchesToMeters(12.5)), // Back Left
</strong><strong>            new Translation2d(Units.inchesToMeters(-12.5), Units.inchesToMeters(-12.5))  // Back Right
</strong><strong>        );
</strong>    }
}
</code></pre>

{% hint style="info" %}
The order defines what the output order of module angle/azimuth's and speeds will be! This is exactly why each module JSON file has a `location` field — see the [JSON Schema reference](/reference/json-schema/module-json).
{% endhint %}

### `SwerveModuleState`

`SwerveDriveKinematics` is used to generate the `SwerveModuleState` of each module in the given order — the example below shows how you can do this in a `drive()` function given a `ChassisSpeeds` object.

`SwerveModuleState` has 2 properties reflecting the properties of a Swerve Module: `angle` and `speedMetersPerSecond`. The goal is to set the correct swerve module (based on the order given at construction of the `SwerveDriveKinematics` object) to the angle and speed given in the `SwerveModuleState`.

<pre class="language-java" data-title="SwerveDrive.java" data-line-numbers data-full-width="false"><code class="lang-java">// Simple drive function
public void drive()
{
    // Create test ChassisSpeeds going X = 14in, Y=4in, and spins at 30deg per second.
<strong>    ChassisSpeeds testSpeeds = new ChassisSpeeds(Units.inchesToMeters(14), Units.inchesToMeters(4), Units.degreesToRadians(30));
</strong>
    // Get the SwerveModuleStates for each module given the desired speeds.
<strong>    SwerveModuleState[] swerveModuleStates = kinematics.toSwerveModuleStates(testSpeeds);
</strong><strong>    // Output order is Front-Left, Front-Right, Back-Left, Back-Right
</strong>}
</code></pre>

{% hint style="danger" %}
Swerve Drive code does not work without [`SwerveDriveOdometry`](https://github.wpilib.org/allwpilib/docs/release/java/edu/wpi/first/math/kinematics/SwerveDriveOdometry.html) or [`SwerveDrivePoseEstimator`](https://github.wpilib.org/allwpilib/docs/release/java/edu/wpi/first/math/estimator/SwerveDrivePoseEstimator.html) to keep track of module positions and angles! YAMS's `SwerveDrive` maintains a `SwerveDrivePoseEstimator` for you — see [Telemetry, Simulation & Vision](/explanation/telemetry-and-vision).
{% endhint %}

### `SwerveDriveOdometry`

Life isn't that easy — you have to keep continuous track of the robot's current positioning, specifically the **heading**, **speed**, and **module positions**, collectively known as **odometry**. This is the only way to correctly generate usable `SwerveModuleState`s.

Odometry must be updated every single loop, just like a subsystem's `periodic()`. YAMS's `SwerveDrive` does this for you as part of its own periodic update — you don't need to call an odometry update method yourself, but understanding *why* it must happen every loop is essential for debugging drift and lag.

{% hint style="info" %}
`SwerveDriveOdometry` can be replaced by `SwerveDrivePoseEstimator`, which additionally fuses vision measurements — this is what YAMS uses. See [Telemetry, Simulation & Vision](/explanation/telemetry-and-vision).
{% endhint %}

## Conclusion

There are many more intricacies to Swerve Drives than covered on this page, but this is sufficient for a basic understanding of how a Swerve Drive is programmed. We'd highly encourage the reader to look at as many examples as they can find to understand some of the gotchas further, or continue on to use YAGSL. Good luck!


# Swerve Modules

What is a Swerve Module?

You may see a bunch of different classes in other teams' code which represent a `SwerveModule` and wonder why there isn't a standard class. There is a very good reason for that: every motor, absolute encoder, gear ratio, and installation can be different! YAGSL's JSON configuration exists to describe those differences declaratively instead of in code, and YAMS's `SwerveModule` handles them uniformly underneath.

{% hint style="warning" %}
If you are using a magnetic encoder ensure that the magnet is glued correctly so it does not slip.
{% endhint %}

## What is in a Swerve Module?

* [ ] Drive Gears (ratio must be known)
* [ ] Steering Gears (ratio must be known)
* [ ] Drive Motor (+ controller)
* [ ] Angle/Azimuth/Steering Motor (+ controller)
* [ ] Absolute Encoder

## Review

This may seem out of place, but when debugging swerve drives this comes in handy very quickly!

Smart Motor Controllers typically have the following features:

* [ ] Rotate in either direction.
* [ ] Have sensors on the motor that read in either direction.
* [ ] Burn up when jammed or unable to move, causing very high amperage utilization.
* [ ] Drop the voltage level available when running.
* [ ] Need a minimum amount of voltage to turn against friction.
* [ ] Greased gears attached to the shaft.
* [ ] Ramp up to speed at a configurable rate to avoid using too much power instantly.
* [ ] Have integrated PID loops which can control the output based on a connected sensor's input (typically an encoder).
* [ ] Are connected to a CAN bus — by default the `rio`, but if a [CANivore](https://store.ctr-electronics.com/canivore/) is connected it could be the name of the CANivore as long as the motor controller supports it.
* [ ] Rotate the wheel in more than one shaft rotation, proportional to the gears.

All of these need to be set correctly in order to configure a Swerve Module properly. If one of these is not set correctly you might experience behavior that you won't easily be able to identify.

#### TL;DR

1. Motors can break in many ways and are only expected to operate in one way — refer here while debugging.
2. Swerve Modules contain **drive gears**, **steering gears**, **drive motor**, **steering motor**, and an **absolute encoder**.

## Checklist

* [ ] Steering/Azimuth/Angle motor increases with the absolute encoder value (or is inverted in configuration to match).
* [ ] Steering/Azimuth/Angle motor increases counterclockwise positive.
* [ ] Drive motors increase propelling the robot "forwards".
* [ ] Absolute Encoders are securely seated into the Swerve Module.
* [ ] Gear ratio / conversion is correctly calculated.
* [ ] Absolute encoder offset is configured with the wheel facing the same way on every module.

{% hint style="warning" %}
Wheels should be aligned with the bevels facing the same way to get the absolute encoder offset.
{% endhint %}

## Absolute Encoder Offsets

Unless your mechanical team is working with extreme precision and places the magnet perfectly centered with the swerve module wheel already straight for every single module, you will need to set an offset for each module so the absolute encoder reads the wheel orientation correctly.

{% hint style="warning" %}
The Absolute Encoder offsets determine where the module should point on boot. When modules do not point straight forwards on boot, or after being commanded to go straight, there **IS** an issue with your Absolute Encoder Offsets.

Sometimes if the offset is off just a little bit a module will be dragged, which could **result in penalties**.
{% endhint %}

This offset is the `absoluteEncoderOffset` field in each module's JSON config — see the [JSON Schema reference](/reference/json-schema/module-json) for the exact field, and the [inversion how-to](/how-to-guides/determine-inversion) for the practical procedure to find it.

## Inversion

Depending on your swerve module, your motors and/or absolute encoder may need to be inverted to run as expected.

{% hint style="warning" %}
When the inversion state of your steering/angle/azimuth motor is incorrect, the Swerve Module **WILL** spin out of control when any input is given, and sometimes even at rest.
{% endhint %}

Inversion is configured per-module via the `inverted.drive` / `inverted.angle` and `absoluteEncoderInverted` fields — see [When to Invert?](/how-to-guides/determine-inversion).

## Conversion Factor / Gearing

Math time! Remember dimensional analysis?

{% embed url="<https://youtu.be/hIAdCTNi1S8>" %}
Kahn Academy Explanation of Dimensional Analysis for unit conversions
{% endembed %}

Swerve Modules are given a `SwerveModuleState` object to set the velocity (meters per second) and angle (degrees) of the module. This means native units (rotations, and rotations per minute) must be converted to velocity and angle units, using the gear ratio between the motor and the wheel/steering mechanism.

The steering conversion takes ***rotations*** of the rotor (or absolute encoder, if attached to the motor controller's dataport) and converts them to ***degrees***.

For example, assume the steering gear ratio is `12.8:1` ([SDS MK4 Steering Ratio](https://www.swervedrivespecialties.com/collections/kits/products/mk4-swerve-module)), meaning the rotor spins `12.8` times to complete one mechanism rotation.

$$
SteeringConverionFactor = \frac{1\_{degree}}{1\_{rot}} = \frac{1\_{rot}}{12.8\_{rot}} \* \frac{1\_{rot}}{360\_{deg}}
$$

The drive conversion takes ***rotations*** given by the motor encoder and converts them to ***meters***.

For example, assume the drive gear ratio is `6.75:1` ([SDS MK4 L2 Drive Ratio](https://www.swervedrivespecialties.com/collections/kits/products/mk4-swerve-module)), meaning the rotor spins `6.75` times for the wheel to complete a rotation.

$$
DriveConversionFactor = \frac{\frac{1\_{meter}}{1\_{sec}}}{\frac{1\_{rot}}{1\_{min}}} = \frac{1\_{rot}}{1\_{min}} \* \frac{1\_{rot}}{6.75\_{rot}} \* \frac{60\_{sec}}{1\_{min}} \* \frac{\pi\*d\_{meters}}{1\_{rot}}
$$

All of this is the long way of showing you that math is important, and your gear ratios and wheel diameter are not magic numbers!

{% hint style="warning" %}
Getting this wrong **COULD** cause the motor to spin out of control, or your odometry will always be slightly off, resulting in more exaggerated motion while driving around.
{% endhint %}

Rather than compute a raw conversion factor yourself, the JSON config expresses `gearRatio` and `diameter` directly (per-module override in `modules/<name>.json`, or the shared default in `physicalproperties.json`) and YAMS derives the conversion internally — see the [JSON Schema reference](/reference/json-schema/physicalproperties-json) and [Standard Conversion Factors](/reference/standard-conversion-factors) for common COTS module ratios.

## PID Control

PID stands for Proportional-Integral-Derivative. Swerve Drives should try to use the most up-to-date feedback available, typically the motor controller's on-board PID/closed-loop control.

WPILib has a great guide to learning PIDs — the turret position example is exactly how steering motors are controlled.

{% embed url="<https://docs.wpilib.org/en/stable/docs/software/advanced-controls/introduction/introduction-to-pid.html>" %}

There are 2 PID loops involved in a Swerve Module: one for the drive motor, the other for the steering motor.

#### Drive Motor PID

Tune it as if it were a flywheel — this is documented here:

{% embed url="<https://docs.wpilib.org/en/stable/docs/software/advanced-controls/introduction/tuning-flywheel.html>" %}

The drive motor also benefits from a feedforward, so the PID doesn't have to work against the minimum voltage needed to overcome friction and the robot's weight — see `s`/`v`/`a` in [pidfproperties.json](/reference/json-schema/pidfproperties-json).

#### Steering Motor PID

The Steering Motor PID controls the angle of the wheel in degrees. A few tricks are necessary to do this effectively:

1. PID wrapping ensures the wheel always chooses the shortest path to the destination angle.
2. Grease your gears often!
3. Calculate the correct gear ratio / conversion.
4. Tune quickly and accurately using a hardware client or Tuner X.

WPILib has documentation on a turret position controller, which is the exact same principle:

{% embed url="<https://docs.wpilib.org/en/stable/docs/software/advanced-controls/introduction/tuning-turret.html>" %}

See [How to Tune PIDF Gains](/how-to-guides/tune-pidf-gains) for concrete starting points.

## Current Limiting

You must limit the current of your motors to avoid pulling too much power and browning out — typically 20A for steering motors, and 40A for drive motors (the `statorCurrentLimit` field in [physicalproperties.json](/reference/json-schema/physicalproperties-json)).

{% hint style="warning" %}
SPARK MAX can only apply stator current limits.
{% endhint %}

{% embed url="<https://v6.docs.ctr-electronics.com/en/stable/docs/hardware-reference/talonfx/improving-performance-with-current-limits.html>" %}


# Module Behaviors

Module-level control behaviors and why they exist

YAMS's `SwerveModule`/`SwerveModuleConfig` provide a handful of behaviors beyond "go to this angle at this speed." This page explains what each one does and why it matters. Where a behavior is configurable, it's noted below — several of these are currently hardcoded on by `SwerveParser` and are not yet exposed as JSON fields.

## Cosine Compensation

Cosine compensation scales the speed of your wheel by the cosine of the angle delta between the module's current angle and its commanded angle. While a module is still rotating to its target angle, driving it at full commanded speed wastes power and skids the wheel — scaling speed down by how far off-angle the module still is keeps the wheel's actual velocity vector closer to what was commanded.

{% hint style="warning" %}
Cosine compensation is tuned for real hardware and can behave oddly in simulation, where the physics model doesn't reproduce the same wheel-slip behavior.
{% endhint %}

`SwerveModuleConfig` exposes this as `.withCosineCompensation(boolean)`. Today, `SwerveParser` always builds modules with cosine compensation **enabled** (`SwerveParser.createSwerveModule` hardcodes `.withCosineCompensation(true)`) — there is no JSON field to disable it. If you need it off (e.g. to compare behavior in simulation), you would need to construct the `SwerveModuleConfig` yourself rather than going through `SwerveParser`.

## Heading Correction / Heading-Snap Control

Heading correction keeps the robot facing a fixed heading while translating with no rotation input, or snaps to a heading chosen by a second controller axis. It used to be a JSON-configured PID (`controllerproperties.json`, driven by `SwerveController.headingCalculate`) that ran automatically inside `SwerveDrive.drive()`. **That class and JSON file no longer exist.**

Heading control now lives in your robot code via `yams.mechanisms.swerve.utility.SwerveInputStream`, and the PID gains for it are supplied directly in Java through `SwerveDriveConfig.withRotationController(PIDController)`:

```java
var cfg = new SwerveDriveConfig()
    .withRotationController(new PIDController(1.0, 0, 0))
    .withTranslationController(new PIDController(1.0, 0, 0));

SwerveInputStream headingStream = angularVelocityStream.clone()
    .withControllerHeadingAxis(driver::getRightX, driver::getRightY)
    .withHeadingControl(() -> driver.getRightStickButton());
```

This is a deliberate architectural change: heading control is now composed per-`SwerveInputStream` (you can have multiple drive modes with different heading behavior) rather than a single global toggle on `SwerveDrive`.

## Module Auto-synchronization

Absolute encoders and the motor controller's internal relative encoder can drift apart over a match (belt slip, CAN dropouts, etc). Auto-synchronization periodically re-zeros the internal encoder against the absolute encoder when the module has been at rest and the two disagree by more than a threshold.

This used to be a `SwerveDrive`-level toggle: `SwerveDrive.setModuleEncoderAutoSynchronize(boolean, double)`. It is now configured **per motor controller**, via `SmartMotorControllerConfig.withFeedbackSynchronizationThreshold(Angle)`:

```java
driveConfig.withFeedbackSynchronizationThreshold(Degrees.of(2));
```

Note this is currently only supported on REV SPARK-family controllers in YAMS — CTRE TalonFX/TalonFXS wrappers throw if you set this option, since those controllers handle absolute/relative fusion differently.

## Offset Offloading (External vs. Internal Feedback Sensor)

Offset offloading is where the absolute encoder position (or an attached absolute encoder's offset) is used directly as the motor controller's feedback sensor, instead of the control system (roboRIO today, SystemCore starting in 2027) reading the absolute encoder and re-seeding a separate relative encoder in software. This usually means a faster, more direct control loop, at the cost of a little resilience if that sensor connection drops mid-match.

{% hint style="warning" %}
`SwerveDrive.pushOffsetsToEncoders()` and `SwerveDrive.restoreInternalOffset()` — the old toggle methods — no longer exist. They were deprecated as of YAGSL 2026 in favor of external/internal feedback sensor selection, and have since been removed along with the rest of the pre-JSON-wrapper API.
{% endhint %}

Today, `SwerveDriveConfig.useExternalFeedbackSensor()` is hardcoded to always return `true` in the current YAMS release — external feedback (offset offloading) is always used when an absolute encoder is attached directly to the azimuth motor controller's dataport. There is no JSON field or builder setter to disable this yet.

## Auto-centering Modules

The old YAGSL had an "auto-centering" behavior (`SwerveDrive.setAutoCenteringModules`, `SwerveModule.setAntiJitter`) that snapped idle modules back to `0°` when no drive input was present. **This behavior could not be found anywhere in the current YAMS source** — it does not appear to have been ported to the new architecture. If your team relied on this, treat it as removed rather than renamed; open an issue if you need it back.

## Angular Velocity Compensation

The old YAGSL compensated for a known skew effect: when translating and rotating at the same time, a swerve drive's actual path bows away from the commanded path unless you correct for it (a technique pioneered by Jack-in-the-bot, enabled via `SwerveDrive.setAngularVelocityCompensation(boolean, boolean, double)`).

This has been ported to YAMS under a new name: `SwerveDriveConfig.withGyroAngularVelocityScaleFactor(double scaleFactor)`, with `withSimGyroAngularVelocityScaleFactor(double)` as a simulation-only override. Both take a `[0, 1]` scale factor applied to the gyro's angular velocity for skew correction, and require `SwerveDriveConfig.withGyroVelocity(Supplier<AngularVelocity>)` to be set. See [Swerve Drive Drift: Causes and Tuning Order](/reference/swerve-drift-causes) for where this fits in the tuning order, and note that [Chassis Speed Discretization](/explanation/chassis-control) addresses a related but distinct skew problem.


# Chassis Control

Chassis-level speed limiting, discretization, and lock pose

Beyond individual module behavior, `SwerveDrive` and `SwerveDriveConfig` control a few chassis-wide concerns: how fast the robot is allowed to go, how commanded velocities get corrected for a subtle simulation-of-motion error, and how to make the robot maximally hard to push.

## Max Speed

YAGSL/YAMS store a maximum chassis linear and angular speed and use it for:

* `SwerveDriveKinematics.desaturateWheelSpeeds` — scaling down all module speeds proportionally if any one module would need to exceed the physical maximum.
* Telemetry (`swerve/maxSpeed`).
* Deriving a default drive feedforward from the motor's free speed, when `pidfproperties.json` doesn't set one explicitly.

The maximum speed represents the physical maximum speed of the robot — it is not a "slow mode" throttle (use `SwerveInputStream.withScaleTranslation`/`withScaleRotation`, or scale your controller input directly, for that).

It's set once via `SwerveDriveConfig.withMaximumChassisSpeed(LinearVelocity, AngularVelocity)`:

```java
var cfg = new SwerveDriveConfig()
    .withMaximumChassisSpeed(MetersPerSecond.of(4.5), DegreesPerSecond.of(360));
```

{% hint style="info" %}
This is a constructor-time setting on `SwerveDriveConfig`, not a JSON field — set it in the code that builds your `SwerveDriveConfig` before passing it to `SwerveParser.createSwerveDrive(...)`.
{% endhint %}

## Chassis Speed Discretization

When you command a `ChassisSpeeds` with both translation and rotation, WPILib's `SwerveDriveKinematics` computes module states assuming that velocity is held constant for the whole next timestep. In reality your loop runs at a discrete rate (typically 20ms), and the true path curves slightly within that window — this shows up as unwanted skew/drift, especially at higher rotation rates.

`ChassisSpeeds.discretize(speeds, dt)` corrects for this by working out what constant-velocity command over `dt` would actually produce the desired end pose. YAMS applies this automatically once you set a discretization time:

```java
var cfg = new SwerveDriveConfig()
    .withDiscretizationTime(Seconds.of(0.02))
    .withSimDiscretizationTime(Seconds.of(0.02)); // optionally different for simulation
```

If you don't set a discretization time, no discretization correction is applied. `0.02` (matching the default robot loop period) is a good starting point for most teams.

## Lock Pose

Lock Pose is a special stance where all the wheels point inward into an X formation, making the robot extremely difficult to push. It's most useful for holding position during defense, or at the end of a match.

```java
drive.lockPose();
```

{% hint style="warning" %}
Lock Pose should only be used when no other drive input is given, or you may get undefined/fighting behavior between the lock and your normal drive command.
{% endhint %}

A typical binding calls it repeatedly while a button is held:

```java
driverXbox.x().whileTrue(Commands.runOnce(drive::lockPose, driveSubsystem).repeatedly());
```


# Telemetry, Simulation & Vision

Telemetry verbosity, simulation, and fusing vision pose estimates

## Telemetry

YAMS pushes swerve drive and module data to NetworkTables under `Mechanisms/swerve` (the table name is currently always `swerve`, regardless of your subsystem's name). This is the data most dashboards (Shuffleboard, Elastic, AdvantageScope) read to render swerve widgets — see the [AdvantageScope how-to guide](/how-to-guides/set-up-advantagescope). Every one of these fields is also written to a WPILib DataLog automatically — see [How to view your DataLog in AdvantageScope](/how-to-guides/view-your-datalog-in-advantagescope) to look at a recorded run after the fact.

<figure><img src="/files/m3IU5y59zCRLgOKphmHu" alt=""><figcaption><p>The Mechanisms/swerve NetworkTables tree as seen in AdvantageScope.</p></figcaption></figure>

```
Mechanisms/swerve/
├── gyro                    (degrees)
├── loopTime                (seconds)
├── pose                    (Pose2d struct)
├── chassis/
│   ├── current             (ChassisSpeeds struct, robot-relative, measured)
│   ├── desired              (ChassisSpeeds struct, robot-relative, commanded)
│   └── field                (ChassisSpeeds struct, field-relative)
├── states/
│   ├── current              (SwerveModuleState[] struct array, measured)
│   └── desired               (SwerveModuleState[] struct array, commanded)
└── modules/<name>/
    ├── encoder              (degrees — the module's raw absolute encoder reading)
    ├── drive/                (that module's drive SmartMotorController telemetry)
    │   ├── current/stator
    │   └── mechanism/{position, velocity}
    └── azimuth/              (that module's angle/steering SmartMotorController telemetry)
        ├── current/stator
        └── mechanism/{position, velocity}
```

{% hint style="warning" %}
`modules/<name>/encoder` is the raw absolute encoder reading, in degrees — this is the value you read to determine `absoluteEncoderOffset` for that module. It sits directly under the module's table, as a sibling of `drive` and `azimuth`, not nested inside either of them.
{% endhint %}

How much gets published is controlled by `yams.motorcontrollers.SmartMotorControllerConfig.TelemetryVerbosity`:

```java
public enum TelemetryVerbosity {
  LOW,   // minimal telemetry
  MID,   // moderate telemetry
  HIGH   // full swerve drive + module data
}
```

{% hint style="info" %}
This enum replaces the old `NONE`/`LOW`/`INFO`/`POSE`/`HIGH`/`MACHINE` levels from pre-2026 YAGSL. If you're migrating an older robot project, map your old verbosity choice down to the nearest of `LOW`/`MID`/`HIGH` — see [Schema Changes](/reference/schema-changes).
{% endhint %}

```java
var cfg = new SwerveDriveConfig()
    .withTelemetry(TelemetryVerbosity.HIGH);

drive = new SwerveParser(directory).createSwerveDrive(cfg);
```

{% hint style="warning" %}
Higher telemetry verbosity can induce some lag on the robot and slow down loop cycle times — be deliberate about what you choose, especially in competition.
{% endhint %}

### Reading module telemetry while bringing up a robot

The most useful pair of values while bringing up a new robot are `modules/<name>/encoder` (the raw absolute encoder, in degrees) and `modules/<name>/azimuth/mechanism/position` (the angle motor's own relative encoder, tracking the absolute encoder once seeded):

* If the absolute encoder decreases while the module is rotated counter-clockwise (should be CCW+), set `absoluteEncoderInverted` for that module.
* If the drive or angle motor's telemetry decreases when it should be increasing (or vice versa), invert that motor in `inverted.drive`/`inverted.angle`.

See [Determine Motor/Encoder Inversion](/how-to-guides/determine-inversion) for the full procedure.

## Simulation

YAMS simulates the whole swerve drive using the same vendor simulation models as the real hardware, to varying degrees of fidelity per vendor. All you need to do is call `simIterate()` from your subsystem's `simulationPeriodic()`:

```java
@Override
public void simulationPeriodic()
{
  drive.simIterate();
}
```

{% hint style="info" %}
[Cosine compensation](/explanation/module-behaviors#cosine-compensation) is tuned for real hardware behavior and can look wrong in simulation — this is expected, not a bug in your config.
{% endhint %}

For more on WPILib's simulation framework generally:

{% embed url="<https://docs.wpilib.org/en/stable/docs/software/wpilib-tools/robot-simulation/introduction.html>" %}

## Vision Odometry

YAMS's `SwerveDrive` maintains a `SwerveDrivePoseEstimator` internally, and extends its vision-fusion API directly onto `SwerveDrive` so you don't need to construct or manage your own estimator:

```java
drive.addVisionMeasurement(visionPose, timestampSeconds);

// Or with per-measurement standard deviations:
drive.addVisionMeasurement(visionPose, timestampSeconds, visionStdDevs);

// Or set a persistent default:
drive.setVisionMeasurementStdDevs(visionStdDevs);
```

Feed this from your vision subsystem (PhotonVision, Limelight, etc.) every time a new pose estimate is available. Standard deviations control how much the pose estimator trusts a given vision measurement relative to odometry — tighter (smaller) values pull the estimate toward vision more aggressively; looser (larger) values let odometry dominate. Tune these empirically; see [Swerve Drive Drift Causes](/reference/swerve-drift-causes) if pose estimates drift or jump unexpectedly.


# Changelog

{% hint style="warning" %}
YAGSL was rewritten as a JSON-config parser around YAMS's `SwerveDrive` between versions `2026.4.1` and `2026.8.05`. Every entry below this notice describes the **old**, pre-rewrite architecture (a self-contained `SwerveDrive`/`SwerveModule`/`SwerveController` implementation, `controllerproperties.json`, `imu`/`encoder`-named JSON fields, etc.) — none of it reflects the current API. If you're trying to understand what changed in the rewrite itself, see [Schema Changes](/reference/schema-changes) instead. This history is kept for archaeology: understanding *why* a given behavior exists, and crediting the contributors who built it.
{% endhint %}

## Pull Requests are always reviewed!

I highly encourage anyone who wants to help make YAGSL better to create pull requests with any modifications you have made that increases your quality of life.

{% embed url="<https://github.com/BroncBotz3481/YAGSL-Example/pulls?q=is%3Apr+is%3Aclosed>" %}

{% embed url="<https://github.com/Yet-Another-Software-Suite/YAGSL/pulls?q=is%3Apr+is%3Aclosed>" %}

## Contributing

YAGSL development is done on the `dev` branch of the YAGSL repository here at `yagsl/java/swervelib`.

{% embed url="<https://github.com/BroncBotz3481/YAGSL-Example/tree/dev>" %}
YAGSL-Example dev branch
{% endembed %}

All PR's should be based off of and merged into here. YAGSL is propagated often to the other repositories.

## 2025.8.0

* Fix attached absolute encoders with brushed SparkMAX's

## 2025.7.2

* fix setAbsoluteEncoderOffset() by [@MEisSCAMMER](https://github.com/MEisSCAMMER) in [#338](https://github.com/BroncBotz3481/YAGSL-Example/pull/338)
* Enable Continuous Input for omegaPIDController by [@Sate04](https://github.com/Sate04) in [#337](https://github.com/BroncBotz3481/YAGSL-Example/pull/337)
* Patched crash when starting while enabled.

## 2025.7.1

* Fixed `SwerveInputStream.allianceRelativeControl`
* Prevent crashing midmatch if robot code restarts.
* Added `SwerveInputStream.translationHeadingOffset`
* Removed gyro zeroing from YAGSL startup. You must now call gyro zero on `autonomousInit` or use the `RobotModeTriggers`
* `Added RobotModeTriggers.autonomous().onTrue(Commands.runOnce(this::zeroGyroWithAlliance));`to the example code.

## 2025.7.0

* [ ] Made the old method for conversion factors throw an exception instead of throwing a warning.
* [ ] Added TalonFXS Support.
* [ ] Renamed <kbd>SwerveMotor.isAttachedAbsoluteEncoder</kbd> to <kbd>SwerveMotor.usingExternalFeedbackSensor</kbd>.
* [ ] Deprecated <kbd>SwerveDrive.pushOffsetsToEncoders</kbd> in favor of <kbd>SwerveDrive.useExternalFeedbackSensor</kbd>.
* [ ] Deprecated <kbd>SwerveDrive.restoreInternalOffset</kbd> in favor of <kbd>SwerveDrive.useInternalFeedbackSensor</kbd>.
* [ ] Updated references from Shuffleboard to any dashboard by [@DanPeled](https://github.com/DanPeled)
* [ ] Fixed Absolute Encoders attached to SparkFlex's.
* [ ] Attached absolute encoders are no longer set as the feedback device, must be explicitly called.
* [ ] Added `SwerveInputStream.driveToPose`
* [ ] Add optional optimization of module states in drive function that uses feedforward by [@clrozeboom](https://github.com/clrozeboom) in [#314](https://github.com/BroncBotz3481/YAGSL-Example/pull/314)
* [ ] Make all closable items implement `AutoClosable` by [@kytpbs](https://github.com/kytpbs) in [#317](https://github.com/BroncBotz3481/YAGSL-Example/pull/317)
* [ ] remove useless imports and variables by [@kytpbs](https://github.com/kytpbs) in [#318](https://github.com/BroncBotz3481/YAGSL-Example/pull/318)
* [ ] Set IZone of PIDControllers created from PIDFConfigs by [@CoffeeCoder1](https://github.com/CoffeeCoder1) in [#319](https://github.com/BroncBotz3481/YAGSL-Example/pull/319)
* [ ] Fix typo by [@fletch3555](https://github.com/fletch3555) in [#323](https://github.com/BroncBotz3481/YAGSL-Example/pull/323)
* [ ] Rev Through bore via DIO by [@wackyvert](https://github.com/wackyvert) in [#325](https://github.com/BroncBotz3481/YAGSL-Example/pull/325)
* [ ] stop setMotorBrake from constantly executing by [@rakosi2](https://github.com/rakosi2) in [#327](https://github.com/BroncBotz3481/YAGSL-Example/pull/327)
* [ ] Prevent rotation if controller heading inputs are not past axisDeadband by [@MrFast-js](https://github.com/MrFast-js) in [#328](https://github.com/BroncBotz3481/YAGSL-Example/pull/328)

## 2025.3.0

* [ ] Added `SwerveDrive.setModuleStateOptimization`to allow for easier debugging.
* [ ] Fixed `sparkmax_analog`
* [ ] Null pointer crash when there have been no vision results by [@jwt388](https://github.com/jwt388) in [#307](https://github.com/BroncBotz3481/YAGSL-Example/pull/307)
* [ ] Adding PigeonViaTalonSRX and attached SparkFlex by [@konnorreynolds](https://github.com/konnorreynolds) in [#292](https://github.com/BroncBotz3481/YAGSL-Example/pull/292)
* [ ] Throw an error when module configs are incorrectly set to 0 by [@wackyvert](https://github.com/wackyvert) in [#308](https://github.com/BroncBotz3481/YAGSL-Example/pull/308)
* [ ] Fixed rawAbsoluteEncoder position. by [@konnorreynolds](https://github.com/konnorreynolds) in [#309](https://github.com/BroncBotz3481/YAGSL-Example/pull/309)

## 2025.2.2

* [ ] Fixed `swerve/measuredChassisSpeeds`to report both vx and vy.
* [ ] Fixed `SwerveInputStream.robotRelative` by Team 151
* [ ] Fixed `SwerveDrive.drive` `fieldOriented` parameter to be applied. Bug cuaght by WispySparks from team 2508!
* [ ] Added `SwerveInputStream.headingOffset`

## 2025.2.1

* [ ] Fixed optimization using the relative encoder
* [ ] Fix absolute encoders detection in `SparkMaxSwerve.isAbsoluteEncoderAttached()` by changing null check to `Optional.isPresent()` (Discovered by Team 217!)
* [ ] Changed `absoluteEncoder` to an `Optional` for SparkMax's (by Team 217)
* [ ] Added a way to sysId while spinning in place, changed parameters to `SwerveDriveTest.setDriveSysIdRoutine(new Config(),this, swerveDrive, 12, true)`

## 2025.2.0

* [ ] Disabled kA for the drive motor feedforward by default.
* [ ] Fix getMaxVelocity used in feedforward by [@jwt388](https://github.com/jwt388) in [#286](https://github.com/BroncBotz3481/YAGSL-Example/pull/286)
* [ ] Maximum speed settings do not allow limiting robot to less than full speed by [@clrozeboom](https://github.com/clrozeboom) in [#277](https://github.com/BroncBotz3481/YAGSL-Example/pull/277)
* [ ] Sim modules SysId routines & New maple-sim version by [@catr1xLiu](https://github.com/catr1xLiu) in [#288](https://github.com/BroncBotz3481/YAGSL-Example/pull/288)

## 2025.1.3

* [ ] Fixed `Adjusted IMU Yaw` and set it to publish under `SmartDashboard`
* [ ] Added simulation velocities and readings to module output.
* [ ] Added `srxmag_standalone`absolute encoder type for SRX Mag Encoders attached to a stand alone TalonSRX.

## 2025.1.2

* [ ] Updated to WPILib 2025.1.1
* [ ] Updated CTRE/REV/Studica/Redux vendordeps.
* [ ] Fixed some javadocs failures.
* [ ] Touched up comments by [@yapplejack](https://github.com/yapplejack) in [#278](https://github.com/BroncBotz3481/YAGSL-Example/pull/278)

## 2025.1.1

* [ ] Fixed telemetry publishing problem. Telemetry is now published under the `SmartDashboard` network table.
* [ ] Throw an error when SparkMAX or SparkFlex configuration changes are done while the robot is enabled.
* [ ] Changed example to not set the drive motor idle mode.
* [ ] Added ThirftyNova support.
* [ ] Added `SwerveInputStream.robotRelative`and `SwerveInputStream.allianceRelativeControl`

## 2025.1.0.1

* [ ] Added configuration redundancy for SparkMAX, and SparkFlex.
* [ ] Fix loop time and use field oriented control for driveWithSetpointGenerator (PR [#271 ](https://github.com/BroncBotz3481/YAGSL-Example/pull/271)by @[**jwt388**](https://github.com/jwt388)**)**
* [ ] Added cycle times to telemetry.
* [ ] Optimized telemetry to only post settings when `SwerveDriveTelemetry.updateSettings`is `true`.
* [ ] Reduced retry delay for SparkMAX and SparkFlex to 5ms from 10ms.
* [ ] Reduced on the fly configuration delay to 10ms from 100ms.
* [ ] Added warning for on the fly configuration delay past init.
* [ ] Fixed `SimpleMotorFeedForward.calculate`to use only the velocity. This ignores acceleration unfortunately.
* [ ] Changed `SmartDashboard.put`to NT4 Publishers
* [ ] Removed `IMUVelocity`in favor of using library velocity fetches.
* [ ] Renamed `SwerveIMU.getRate()`-> `SwerveIMU.getYawAngularVelocity()`and made it return an `AngularVelocity`object.
* [ ] Added `SwerveInputStream`object for easy controller conversions.

## 2025.1.0

* [ ] Fixed vision file null-ptr exception.
* [ ] Updated to Maple-Sim 0.2.4; added `SwerveDrive.getMapleSimDrive()` by [@catr1xLiu](https://github.com/catr1xLiu) in [#262](https://github.com/BroncBotz3481/YAGSL-Example/pull/262)
* [ ] Fixed spark max brushed motor controller encoder null-ptr exceptions
* [ ] Fixed `SwerveMath.scaleTranslation`issue
* [ ] Fixed `Vision`update with `resultLists`check by @jwt388 FRC Team 151
* [ ] Changed `SwerveDrive.getMaximumVelocity()`-> `SwerveDrive.getMaximumChassisVelocity()`
* [ ] Changed `SwerveDrive.getMaximumAngularVelocity()` -> `SwerveDrive.getMaximumChassisAngularVelocity()`
* [ ] Used known motor types to calculate the drive motor feedforward.
* [ ] Extended the parser to know the motor type attached to the motor controller. (Except brushed)
* [ ] Separated chassis maximum velocity from module maximum velocity.
* [ ] Added `navx_mxp_serial` back.
* [ ] Added motor specifiers `krakenx60foc`, `krakenx60`, `falcon500foc`, `falcon500`, `sparkmax_neo550`, `sparkmax_neo`, `sparkflex_neo`, `sparkflex_vortex`, `sparkflex_neo550`
* [ ] Create the drive motor feedforward in `SwerveModule` constructor.
* [ ] Added module max speeds to `SwerveModule.maxDriveVelocity` and `SwerveModule.maxAngularVelocity`
* [ ] Updated the web configuration

## 2025.0.0

* [ ] AHRS imu variable is lost after constructor scope ends by [@clrozeboom](https://github.com/clrozeboom) in [#254](https://github.com/BroncBotz3481/YAGSL-Example/pull/254)
* [ ] Add Maple-Sim by [@catr1xLiu](https://github.com/catr1xLiu) in [#251](https://github.com/BroncBotz3481/YAGSL-Example/pull/251)
* [ ] Implement support for kA by [@Etaash-mathamsetty](https://github.com/Etaash-mathamsetty) in [#258](https://github.com/BroncBotz3481/YAGSL-Example/pull/258)
* [ ] Update to WPILIb 2025 Beta-2 by [@thenetworkgrinch](https://github.com/thenetworkgrinch) in [#257](https://github.com/BroncBotz3481/YAGSL-Example/pull/257)
* [ ] Add support to Talon SRX for an integrated absolute encoder by [@WispySparks](https://github.com/WispySparks) in [#208](https://github.com/BroncBotz3481/YAGSL-Example/pull/208)
* [ ] Implemented Path Planner DriveFeedForward using DCMotor modeling by [@catr1xLiu](https://github.com/catr1xLiu) in [#260](https://github.com/BroncBotz3481/YAGSL-Example/pull/260)
* [ ] Fixed NavX inversion.
* [ ] Fixed Absolute Encoder issue on MAXSwerve with help from Team 457 Grease Monkeys

## 2024.7.0 - Last version for 2024 WPILib

* [ ] Added inversion redundancy check to SparkMAX and SparkFlex.
* [ ] Add Warmup for Pathfinding (PR [#241](https://github.com/BroncBotz3481/YAGSL-Example/pull/241) by [TechnologyMan00](https://github.com/Technologyman00))

## 2024.6.1.0

* [ ] Added `Canandgyro` support.
* [ ] A tiny bug fix in the aim-at-speaker command of the example swerve subsystem code by (PR [#239](https://github.com/BroncBotz3481/YAGSL-Example/pull/239) by [catr1xLiu](https://github.com/catr1xLiu))

## 2024.6.0.0

* [ ] Merge Swerve configuration test changes (PR [#228](https://github.com/BroncBotz3481/YAGSL-Example/pull/228) by [**clrozeboom**](https://github.com/clrozeboom)**)**
* [ ] Angular velocity correction (huge update that massively improves YAGSL!) (PR [#231](https://github.com/BroncBotz3481/YAGSL-Example/pull/231) by [yapplejack](https://github.com/yapplejack))
* [ ] Sparkmax optimizations, changes to avg filter etc (PR [#233](https://github.com/BroncBotz3481/YAGSL-Example/pull/233) by [**yapplejack**](https://github.com/yapplejack)**)**
* [ ] Addded `sparkmax_analog5v` as a valid absolute encoder type.
* [ ] Suggestion for desaturateWheelSpeeds() to use desiredChassisSpeeds (PR [#232](https://github.com/BroncBotz3481/YAGSL-Example/pull/232) by [**yapplejack**](https://github.com/yapplejack)**)**
* [ ] Made auto synchronization optional and configurable by `SwerveDrive.setModuleEncoderAutoSynchronize`
* [ ] Fixed configurator problem for `TalonFXSwerve`

## 2024.5.0.4

* [ ] Added PhotonVision `Vision` class to example and integrate it with the example code.
* [ ] Added `getAprilTagPose` method to `Vision` (PR [#226](https://github.com/BroncBotz3481/YAGSL-Example/pull/226) by [**kreidljj**](https://github.com/kreidljj))
* [ ] Update the vision simulation on `Vision.updatePoseEstimation` (PR [#224](https://github.com/BroncBotz3481/YAGSL-Example/pull/224) by [**brandonzx3**](https://github.com/brandonzx3)**)**
* [ ] Add Standard Deviations for YAGSL SwerveDrive Pose Estimator (PR [#222](https://github.com/BroncBotz3481/YAGSL-Example/pull/222) by [**maxikyuu**](https://github.com/maxikyuu))
* [ ] Updating Vendordeps and renaming CanandCoders to CanandMags (PR [#219](https://github.com/BroncBotz3481/YAGSL-Example/pull/219) by [**Turbojax07**](https://github.com/Turbojax07) and YAGSL devs)
* [ ] Fixed documentation issue with `SwerveDriveTelemetry` (Issue [#233 ](https://github.com/BroncBotz3481/YAGSL-Example/issues/223)by [**DanPeled**](https://github.com/DanPeled) )
* [ ] Fixed AbsoluteEncoders attached to the SparkMAX like Throughbores.

## 2024.5.0.3

* [ ] Fixed `SparkMaxAnalogEncoder` wrapper to work. The expected read values are in volts, 3.3v max. (Found and fixed by team Austin from team 2377)
* [ ] Fixed `CanandCoder` to `CanandMag` due to product rename. (Credit to [TurboJax07](https://github.com/Turbojax07))
* [ ] Changed default behavior of setting an attached absolute encoder up without calling `SwerevDrive.pushOffsetsToEncoders` which is now an optional optimization for MAX Swerve modules instead of a requirement. MAX Swerve no longer need to use `360` as the conversion factor IF they do not use `SwerveDrive.pushOffsetsToEncoders`

## 2024.5.0.1

* [ ] Added `SwerveDrive.setVisionMeasurementStdDevs(Matrix<N3, N1> visionMeasurementStdDevs)`
* [ ] Add ability to get the rotation rate from the IMU (PR [#216](https://github.com/BroncBotz3481/YAGSL-Example/pull/216) by [@clrozeboom](https://github.com/clrozeboom))

## 2024.5.0.0

* [ ] Changed input scaling to utilize Polar coordinate magnitude multiplication.
* [ ] Simplify placeInAppropriate0To360Scope (PR [#213](https://github.com/BroncBotz3481/YAGSL-Example/pull/213) by [GoldenStack](https://github.com/GoldenStack))
* [ ] Added auto-centering modules setting.
* [ ] Added back composite conversion factors.

## 2024.4.8.7

* [ ] Increased logging verbosity to introduce logging modes for the field and data. (PR [#185](https://github.com/BroncBotz3481/YAGSL-Example/pull/185) by [5010](https://github.com/5010TigerDynasty))
* [ ] Added `SwerveDrive.setChassisDiscretization` to allow for changing of the discrete value between cycle times which can be tuned to reduce drift. (PR [#194](https://github.com/BroncBotz3481/YAGSL-Example/pull/194) by [TechnologyMan00](https://github.com/Technologyman00))
* [ ] Renamed `SwerveDrive.pushOffsetsToControllers` to `SwerveDrive.pushOffsetsToEncoders`. (PR [#194](https://github.com/BroncBotz3481/YAGSL-Example/pull/194) by [TechnologyMan00](https://github.com/Technologyman00))
* [ ] Changed `Module[...]` to `swerve/modules` in Telemetry.

## 2024.4.8.6

* [ ] Added PIDF helper functions `SwerveModule.setDrivePIDF` and `SwerveModule.setAnglePIDF` alongside `SwerveModule.getDrivePIDF` and `SwerveModule.getAnglePIDF`.
* [ ] Changed feedforward around to use `SwerveModule.setFeedforward` instead of directly modifying `SwerveModule.feedforward`
* [ ] Renamed `SwerveModule.feedforward` to `SwerveModule.driveFeedforward`.
* [ ] Added anti-jitter disabling option via `SwerveModule.setAntiJitter` which also modifies the encoder offsets that are pushed to the motor controllers.
* [ ] Updated vendordeps

## 2024.4.8.5

* [ ] Fixed NavX inversion state not taking any affect on the robot.

## 2024.4.8.4

* [ ] Updated anti jitter to run before cosine compensation.

## 2024.4.8.3

* [ ] Updated `Pigeon2Swerve` to use `imu.getRotation3d()` instead of handling the CAN timeouts.
* [ ] Massively reduced memory footprint required. (with help from [@TheGamer1002](https://github.com/TheGamer1002))

## 2024.4.8.2

* [ ] Made CTRE wait for status times static for user modification. `CANCoderSwerve.STATUS_TIMEOUT_SECONDS`,`TalonFXSwerve.STATUS_TIMEOUT_SECONDS`, and `Pigeon2Swerve.STATUS_TIMEOUT_SECONDS` can be modified for desired use case.

## 2024.4.8.1

* [ ] Added `SwerveDrive.setCosineCompensation` function which can enable and disable cosine compensation as desired, disabled by default in sim because of discrepancies with real robots.

## 2024.4.8

* [ ] Added SysId support. (PR [#160](https://github.com/BroncBotz3481/YAGSL-Example/pull/160) and [#165](https://github.com/BroncBotz3481/YAGSL-Example/pull/165) by [Team 5010](https://github.com/5010TigerDynasty))
* [ ] `waitForStatusUpdate` is used instead of `refresh` with default timeouts of 20ms.
* [ ] Added `Cache` class to cache variables over a `validityPeriod` which will speed up processing and reduce CAN utilization.
* [ ] Added `SwerveDrive.updateCacheValidityPeriods` so the user can change the cache validity period to their hearts desire.
* [ ] Added `SwerveDrive.getModuleMap()` which will fetch all of the `SwerveModule`'s as a HashMap where the key is the module's filename without `.json` so `frontleft.json` would have the key of `frontleft`.

## 2024.4.7

* [ ] Modules will stay in the previous position if the desired velocity is 0.
* [ ] Cosine compensation is now reported through telemetry as expected.

## 2024.4.6.3

* [ ] Updated heading correction to use `getOdometryHeading()` instead of `getYaw()` (PR [#150](https://github.com/BroncBotz3481/YAGSL-Example/pull/150) by [@Blargleflakes](https://github.com/Blargleflakes)) This does impact heading correction PID values in `controllerproperties.json` and may NEED these value's increased by 50x.
* [ ] Added ability to disable the cosine compensator via config files. (PR [#148](https://github.com/BroncBotz3481/YAGSL-Example/pull/148) by [@fovea1959](https://github.com/fovea1959))
* [ ] Added `SwerveDrive.getGyro()` to return `SwerveIMU`.
* [ ] Added `SwerveMotor.setVoltage` and `SwerveMotor.getVoltage` and `SwerveMotor.getAppliledOutput` to the `SwerveMotor` wrapper for future use with SysId.
* [ ] Added functions to test setting voltage of all swerve module motor.
* [ ] Added function to find coupling ratio of all swerve modules.
* [ ] Added function to set steering/azimuth/angle using of all swerve modules.
* [ ] Added function to find kV for drive motors to move.
* [ ] Set the angle motor relative encoder position AFTER changing the conversion factor. (Issue [#155](https://github.com/BroncBotz3481/YAGSL-Example/issues/155))
* [ ] Fixed module open loop control by sending `maxSpeed` to the module. (by @nstrike and [@MarshallTappen](https://github.com/MarshallTappen))
* [ ] Fixed Pigeon2Swerve only using X acceleration (PR [#146](https://github.com/BroncBotz3481/YAGSL-Example/pull/146#event-11577147896) by [@dezash123](https://github.com/dezash123))
* [ ] Prevent drive motors from moving when `absoluteEncoderOffset` is not tuned. (Found by [@fovea1959](https://github.com/fovea1959))
* [ ] Updated javadocs for `SwerveDrive.addVisionMeasurement` so that they reflect the latest changes.

## 2024.4.6.1

* [ ] Fixed `SwerveDrive.resetOdometry` and utilize the pose estimation instead. (PR [#142](https://github.com/BroncBotz3481/YAGSL-Example/pull/142) by [@MarshallTappen](https://github.com/MarshallTappen) and @nstrike [commit](https://github.com/BroncBotz3481/YAGSL-Example/commit/039e5c2867690cfdd5ebd0c1e84eefc6b165adee))
* [ ] Functionalize IMU inversion (PR [#140](https://github.com/BroncBotz3481/YAGSL-Example/pull/140) by [@TechnologyMan00](https://github.com/Technologyman00) and @nstrike)
* [ ] Added `SwerveDrive.getOdometryHeading()`
* [ ] Fixed `SwerveDrive.addVisionMeasurement` with vision standard deviations.
* [ ] Added IMU readings to SmartDashboard via `Raw IMU Yaw` (gyro with invert applied) and `Adjusted IMU Yaw` (pose estimation rotation).
* [ ] Changed `navx_mxp` to `navx_mxp_serial` to notate that it's serial over MXP.
* [ ] Added warning when using `navx_mxp_serial` or `navx_usb`.
* [ ] Added back wheel speed desaturation.

## 2024.4.6

* [ ] Simplified `SwerveMath.calculateDegreesPerRotation` and `SwerveMath.calculateMetersPerRotation` to exclude encoder resolution and add a default.
* [ ] Changed `Module[...] Raw Motor Encoder` to `Module[...] Raw Angle Encoder`.
* [ ] Added `Module[...] Raw Drive Encoder`

## 2024.4.5

* [ ] Fix for TalonFX Angle motor control (by [@bhall-ctre](https://github.com/bhall-ctre), and @Wackyvert 2225 Mentor) .
  1. TalonFX's needed to use conversion factor as gear ratio rather than gear ratio + unit conversion.
  2. Conversion factor needed to be inverted.
  3. setPosition update to reflect current position.
* [ ] Changed `ma3` encoders to be read via analog input. (Discovered by [@CoZm0](https://github.com/CoZ-m0))
* [ ] Support 3 wheel swerve module setups with PathPlanner helper function. (PR #139 by [@TechnologyMan00](https://github.com/Technologyman00))
* [ ] Added `SwerveModule.getAbsoluteEncoder()` `SwerveDrive.getMaximumVelocity()` and `SwerveDrive.getMaximumAngularVelocity()`.
* [ ] Reccommend Tuner X when a compatible Tuner X config is used.
* [ ] Added ability to change heading correction deadband.

## 2024.4.2

* [ ] Added feedforward to SparkMAX's.
* [ ] Added Network Alerts (made by[ @TheGamer1002](https://github.com/TheGamer1002) PR [#136](https://github.com/BroncBotz3481/YAGSL-Example/pull/136))
* [ ] Added feedforward to TalonFX's
* [ ] Fixed TalonFX conversion factor (made by [@jkbo6](https://github.com/jbko6) PR [#128](https://github.com/BroncBotz3481/YAGSL-Example/pull/128))
* [ ] CanAndCoder tweaks (made by [@guineahawk](https://github.com/guineawheek) PR [#134](https://github.com/BroncBotz3481/YAGSL-Example/pull/134))
* [ ] Added missing SparkMAX status frame's (made by [@RoboPenguin7](https://github.com/RoboPenguin7) PR [#130](https://github.com/BroncBotz3481/YAGSL-Example/pull/130))
* [ ] Heading Correction update to make it less sensitive (made by [@balien-12](https://github.com/balien-12) PR [#132](https://github.com/BroncBotz3481/YAGSL-Example/pull/132))
* [ ] Fixed cosine compensator error (made by [@jkbo6](https://github.com/jbko6) PR [#129](https://github.com/BroncBotz3481/YAGSL-Example/pull/129))
* [ ] Updated PathPlanner path based off of alliance (made by [@MarshalTappen](https://github.com/MarshallTappen) PR [#122](https://github.com/BroncBotz3481/YAGSL-Example/pull/122))
* [ ] Fixed Rotation being off, update getDriveBaseMeters (made by [@TechnologyMan00](https://github.com/Technologyman00))


# YASS (Yet Another Software Suite)


# Resources

[YAGSL Discord](https://discord.gg/5pYxSDnymr)

[YAGSL Ambassador Application](https://forms.gle/7fohLEydCXZoWZYZ8)

YAGSL Flyers -

{% file src="/files/LZj9LtguIgt2lFKDhj26" %}


