For the complete documentation index, see llms.txt. This page is also available as Markdown.

Changelog

New updates and improvements

Unreleased

These changes are on master and will ship in the next release.

Added

  • Commands v2 SwerveDrive.driveToPose(Pose2d, Distance, Angle), which ends once the robot is within the tolerances, and isNear(Pose2d, Distance, Angle) and stop(). These were only on the Commands v3 SwerveDrive; isNear and stop now live on the shared SwerveDrive.

  • SmartMechanism.getMeasurementSetpoint(): the mechanism's position setpoint as a Distance, converted with the mechanism circumference. It is empty when there is no position setpoint or no circumference is configured.

  • EasyCRT.solve(Angle, Angle) / EasyCRT::Solve(...): solve for travel centered on zero, with a solve(a, b, minAngle, maxAngle) overload for other travel.

  • EasyCRT.getUniqueRange() / EasyCRT::GetUniqueRange(): how far the shared gear can turn before the two encoder readings repeat.

  • A Turret that seeds its motor from EasyCRT at startup, with a unit test, in the BroncBotz_2026 examples for Commands v2 and Commands v3.

Changed

  • Behavior change: the Commands v2 SwerveDrive.driveToPose(Pose2d) now stops the drive when it ends or is interrupted, as the Commands v3 one already did. It used to leave the last commanded speeds in place.

  • Behavior change: withTemperatureCutoff now follows where the closed loop runs, because the cutoff is only checked by the closed loop on the SystemCore (Java and C++). On a SPARK MAX or SPARK Flex it needs an exponential profile or an LQR controller: a trapezoidal profile with a cutoff used to be accepted and silently ignored, and now throws at startup. On a TalonFX or TalonFXS it is now accepted with an LQR controller, where it used to always throw.

  • Breaking: EasyCRT is now built from three tooth counts and solves from two readings (Java and C++). The match tolerance is derived from the tooth counts, and a reading pair that does not agree returns an empty result.

  • Breaking (Java): DifferentialMechanism.set(...) now takes (tilt, twist) like every other method, and mixes them with the same signs as setPosition (left = tilt + twist, right = tilt - twist). It took (twist, tilt) and drove the twist the opposite way. Swap the arguments if you called it as (twist, tilt).

  • EasyCRTConfigGuide.md is replaced by EasyCRTGuide.md, which explains how to choose tooth counts, zero and invert the encoders, handle gear trains, and check your margin yourself.

  • MIGRATION.md now covers the whole 2026 to 2027 upgrade.

Removed

  • Breaking: EasyCRTConfig, including its gearing helpers, offset and inversion setters, match tolerance, and gear recommender, and the EasyCRT status, error and iteration getters.

Fixed

  • ExponentialProfilePIDController.createConstraints(maxVolts, maxVelocity, maxAcceleration) passed kV and kA as state space terms, which gave an unstable profile with a negative maximum velocity. It now builds the same constraints as SmartMotorControllerConfig.withExponentialProfile(Voltage, AngularVelocity, AngularAcceleration).

  • Simulation: Arm, Elevator, Pivot, FlyWheel, DoubleJointedArm and DifferentialMechanism no longer replace the shared BatterySim voltage with one computed from their own current draw alone. The simulated battery now sags from every mechanism's combined draw, and enableDischarge(...) is respected, no matter which mechanism iterates last (Java and C++).

  • simple_robot-2027 examples: the simulated gyro yaw was set in radians instead of degrees, and the elevators were missing the carriage weight and starting position that Elevator requires in simulation.

  • C++ cpptest example: the field-relative SwerveInputStream output is converted to robot-relative before SwerveDrive::Drive.

  • GearBox Javadoc: fromTeeth(...) is a train of meshing gears, so fromTeeth(12, 60, 12, 60) is 5:1, not 25:1.

2026.10.9

Added

  • NovaWrapper (Java yams.core.motorcontrollers.local.NovaWrapper, C++ yams/motorcontrollers/local/NovaWrapper.hpp): a SmartMotorController for the Thrifty Nova, with simulation, telemetry, and alert support. Requires the ThriftyLib 2027 vendordep.

  • SmartMotorController.getRelativeMechanismPosition() / getRelativeMechanismVelocity() (Java) and GetRelativeMechanismPosition() / GetRelativeMechanismVelocity() (C++): read only the relative (motor) encoder, even when an external encoder is configured. getMechanismPosition() / getMechanismVelocity() still prefer the external encoder.

  • C++: SmartMotorController, SmartMotorControllerConfig, simulation, telemetry, SparkWrapper, TalonFXWrapper, and TalonFXSWrapper now match the Java API.

Changed

  • Breaking: getExternalEncoderPosition() / getExternalEncoderVelocity() renamed to getExternalEncoderMechanismPosition() / getExternalEncoderMechanismVelocity() (C++: GetExternalEncoderMechanismPosition() / GetExternalEncoderMechanismVelocity()).

  • Alerts now refer to the SystemCore instead of the RIO.

  • Maven artifacts (Java and C++) and the vendordep JSONs are now served from https://cdn.yassrobotics.com/, so networks that block github.io can still install and update YAMS. Reinstall the vendordep from the new URL:

    Command framework
    Vendordep URL

    Commands v2

    https://cdn.yassrobotics.com/yams_commands2.json

    Commands v3

    https://cdn.yassrobotics.com/yams_commands3.json

2026.10.8

Added

  • SwerveInputStream.withTelemetry(String, TelemetryVerbosity) (Java) / SwerveInputStream::WithTelemetry(...) (C++): publishes the stream's drive mode (LOW), its configuration (MID), and editable copies of the configuration plus a Live Tuning command (HIGH). The stream publishes every time it is read, so there is nothing to call each loop. Access it with getTelemetry() / GetTelemetry().

Call withTelemetry(...) last, after the rest of the stream configuration, so the dashboard starts with the configured values. A clone() of a stream has no telemetry.

2026.10.5

Added

  • GyroAxis enum (yams.core.mechanisms.config.enums: YAW default, ROLL, PITCH) and SwerveDriveConfig.withGyroHeadingAxis(GyroAxis) (Java): use a gyro mounted on its side as the heading source. The gyro attitude is rotated into the robot frame, so the heading keeps its full range and anti tipping reads the robot's tilt.

  • SwerveModule.lock(Rotation2d) and SwerveModuleConfig.getLockedState(...) (Java).

Changed

  • Breaking (Java): SwerveDriveConfig.getGyroAngle() and SwerveDrive.getGyroAngle() are removed. Use getGyroRotation3d(). The heading now wraps at +/-180 degrees instead of counting continuously.

  • Vendordeps now declare requires and conflictsWith, so the Commands v2 and Commands v3 variants of YAMS cannot be installed together.

Fixed

  • Java: SwerveDrive.lockPose() now turns the wheels into the X pattern. Previously the module velocity deadband replaced the zero speed states, so the wheels held their current angle.

  • Java: Simulated SPARKs now close their loop at 1 kHz like a real SPARK (and like simulated Talons), instead of once per 20 ms simulation period. Integral and derivative gains are no longer scaled by the loop period in simulation. Real robot gain conversion is unchanged.

2026.10.3

YAMS now targets WPILib 2027 (2027_alpha7) and the SystemCore. Robot code built against earlier releases needs to be migrated; see Upgrading from 2026 to 2027 in the YAMS documentation.

Added

  • Two vendordeps, one per WPILib command framework. Install exactly one:

    • Commands v2: yams_commands2.json, with command bindings in yams.commands2.*.

    • Commands v3: yams_commands3.json, with command bindings in yams.commands3.* (Java only, since WPILib has no C++ Commands v3).

  • An offline installer, <version>-YAMS-Offline.zip, attached to each GitHub release. Extract it into your WPILib 2027_alpha7 folder, then install the vendordep offline.

  • Example projects for both command frameworks under examples/commands2 and examples/commands3.

Changed

  • Breaking: WPILib 2027 packages (org.wpilib.* in Java, wpi:: in C++), Java 25, Phoenix 6 2027, and REVLib 2027.

  • Breaking (Java): packages split into yams.core.* (mechanism and motor controller state, simulation, telemetry, configs, wrappers, gearing, math, units) and yams.commands2.* / yams.commands3.* (subsystem bound mechanisms, SmartMotorControllerConfig, SwerveDriveConfig, SwerveDrive, SwerveInputStream, and their Command/Trigger factories). Robot code imports mechanisms and those configs from the command framework package.

  • Breaking (Java): the isNear(...) Trigger factory is renamed near(...), matching gte(), lte(), between(), max(), and min(). The boolean isNear(...) check is unchanged.

  • Live tuning registers itself when telemetry is TelemetryVerbosity.HIGH. In other cases, opt in with SmartMotorControllerConfig.setupLiveTuning().

  • Breaking: withIdleMode(MotorMode) is renamed withZeroPower(MotorMode) (C++: WithZeroPower).

2026.9.16

Changed

  • Breaking: SmartMotorControllerConfig.getZeroOffset() is renamed getExternalEncoderZeroOffset(), matching the existing withExternalEncoderZeroOffset(...) setter.

Fixed

  • TalonFXWrapper no longer applies the zero offset to the rotor as well, which double applied the offset.

2026.9.10

Added

  • SmartMotorController.setVelocity(AngularVelocity, Force) / setVelocity(LinearVelocity, Force) and SwerveDrive.setRobotRelativeChassisSpeeds(..., Force[]): pass a feedforward force (for example from a path follower) straight through to the drive motors. SwerveModule gained the matching overloads.

  • SwerveDriveConfig.clone().

  • SmartMotorControllerConfig.withSimulationPeriod(Time) (C++: WithSimulationPeriod).

  • SimSupplier.getStatorCurrent() / getSupplyCurrent().

  • The gyro angular velocity is derived from the heading when no gyro velocity supplier is set, so angular velocity scaling works without one.

Fixed

  • The simulated swerve pose was integrated twice, so it moved robot relative instead of field relative.

2026.9.2

Added

  • Advanced telemetry for SwerveDrive and SwerveModule: SwerveDriveConfig.withTelemetry(String, TelemetryVerbosity) / withTelemetry(String, SwerveDriveTelemetryConfig) and SwerveModuleConfig.withTelemetry(String, SwerveModuleTelemetryConfig), with more live tunable fields.

  • SwerveDrive.setTranslationPID(PIDController), setRotationPID(PIDController), driveToPoseSetpoint(Pose2d), and getModules().

  • SwerveModule.getRawAbsoluteEncoderAngle().

Changed

  • SwerveDrive.resetAzimuthPID() is renamed resetRotationPID().

  • SwerveInputStream takes its maximum velocities from the SwerveDrive.

Removed

  • Breaking: SwerveDriveConfig.withDataLogName(...) and SwerveModuleConfig.withDataLogName(...). Use the withTelemetry(...) overloads above.

  • Breaking: drive to pose in SwerveInputStream, including atTargetPose(double). Use SwerveDrive.driveToPoseSetpoint(Pose2d).

Fixed

  • The absolute encoder supplier is zeroed when an absolute encoder is supplied to SwerveModuleConfig.

2026.8.26

Added

  • SwerveDrive.getSimPose() (Java) / SwerveDrive::GetSimPose() (C++) — a ground-truth simulated pose that assumes every module reached its last-commanded SwerveModuleState perfectly. It's integrated in simIterate()/SimIterate() from the desired (not measured) module states via a Twist2d/frc::Twist2d, independent of the noisy, gyro/odometry-fused pose returned by getPose()/GetPose(). resetOdometry(...)/ResetOdometry(...) also snaps it to the given pose, keeping it in sync with the fused estimate. Intended as a "known truth" source for simulated vision systems.

  • SwerveDrive.getRobotRelativeChassisSpeedsFromState(SwerveModuleState[]) (Java) / SwerveDrive::GetRobotRelativeChassisSpeedsFromState(...) (C++) — converts an arbitrary set of module states back into robot-relative chassis speeds.

  • SwerveDrive.getDesiredModuleStates() (Java) / SwerveDrive::GetDesiredModuleStates() (C++) — returns the last-commanded module states, mirroring the existing getDesiredChassisSpeeds()/GetDesiredChassisSpeeds().

Removed

  • Breaking: SwerveDrive.getPoseEstimator() (Java) / SwerveDrive::GetPoseEstimator() (C++) — direct access to the underlying SwerveDrivePoseEstimator was too easy to misuse (calling update(...)/resetPosition(...)/resetPose(...) on it directly corrupts the pose estimate or desyncs it from the drive's gyro offset). Use getPose(), resetOdometry(Pose2d), and addVisionMeasurement(...) instead.

2026.8.20

Added

  • BatterySim (Java & C++) now models a single, shared robot battery in simulation. Every simulated SmartMotorController — via its SimSupplier (ArmSimSupplier, ElevatorSimSupplier, DCMotorSimSupplier) or hardware wrapper (SparkWrapper, TalonFXWrapper, TalonFXSWrapper) — reports its own current draw into one combined loaded-voltage calculation, instead of each mechanism computing RoboRioSim voltage sag from only its own current draw and overwriting the others.

  • BatterySim.enableDischarge(double, Voltage, Resistance) (Java) / BatterySim::EnableDischarge(double, units::volt_t, units::ohm_t) (C++) — optional realistic battery discharge simulation. Current draw is integrated into amp-hours consumed over time, sagging the open-circuit voltage and raising internal resistance as the battery empties, following a typical FRC sealed lead-acid discharge curve. Also added disableDischarge()/DisableDischarge(), resetDischarge()/ResetDischarge(), and getStateOfCharge()/GetStateOfCharge().

  • BatterySim.replaceSOCInterpolation(InterpolatingDoubleTreeMap) (Java) / BatterySim::ReplaceSOCInterpolation(const std::map<double, double>&) (C++) — swap in a custom state-of-charge → voltage discharge curve, for modeling a well-used competition battery, a different chemistry, or a curve measured from a real battery on a load tester.

Changed

  • C++: Arm, Elevator, Pivot, and FlyWheel no longer call RoboRioSim::SetVInVoltage(...) directly from their own current draw. Voltage now comes from the shared BatterySim, so combined load across multiple mechanisms sags the simulated bus voltage realistically instead of only reflecting whichever mechanism updated last.

Discharge simulation is off by default and only affects simulation — on a real robot, RobotController.getBatteryVoltage()/frc::RobotController::GetBatteryVoltage() always reflects actual hardware.

2026.8.18

Added

  • SwerveDriveConfig.getDiscretization() — returns the discretization time used by optimizeRobotRelativeChassisSpeeds(...), automatically preferring the simulation-specific override from withSimDiscretizationTime(...) when running in simulation.

2026.8.17

Added

  • SmartMotorControllerTelemetryConfig.withCustom(DoubleTelemetryField, boolean) / withCustom(BooleanTelemetryField, boolean) (Java, plus array overloads) — an escape hatch to enable or disable a telemetry field directly when there isn't a dedicated with*() setter for it yet.

Changed

  • Telemetry now only publishes fields that are actually enabled, instead of publishing every registered field regardless of its enabled state.

  • Java & C++: Swerve module telemetry now nests each module under swerve/modules/<name>/... instead of swerve/<name>/..., removing a double / that showed up in some NetworkTables/DataLog paths.

Fixed

  • Java & C++: Cosine compensation in SwerveModuleConfig now reads the azimuth angle once per control cycle and reuses that same value for both state optimization and the cosine scalar, instead of re-querying the absolute encoder separately for each step. Previously those two reads could disagree, which showed up as skewed drive speed or the wheel losing its commanded direction right around a 180° heading error.

2026.8.16

Added

  • MechanismTelemetry is now the primary coordinator of all mechanism telemetry, and gained explicit DataLog support alongside NetworkTables.

  • SwerveDriveConfig.withDataLogName(String)/getDataLogName() — logs the drive's pose, gyro, and chassis speeds/module states to a WPILib DataLog under the given name, in addition to NetworkTables.

  • SwerveModuleConfig.withDataLogName(String)/getDataLogName() — logs that module's absolute encoder angle to a WPILib DataLog under the given name, in addition to NetworkTables.

Changed

  • SwerveModule now shares a single MechanismTelemetry instance (and loop timer) across its drive and azimuth motors instead of each motor re-running its own telemetry setup.

Neither SwerveDriveConfig.withDataLogName(...) nor SwerveModuleConfig.withDataLogName(...) cascades down to a module's drive/azimuth motors. Set a DataLog name on each motor's own SmartMotorControllerConfig/SmartMotorControllerTelemetryConfig for per-motor logging.

2026.7.10

Added

  • C++: SmartMotorControllerConfig::WithFeedforward(...) — a single overloaded setter that takes a WPILib feedforward object directly (frc::ArmFeedforward, frc::ElevatorFeedforward, or frc::SimpleMotorFeedforward<units::turns>), replacing the setters removed below.

Removed

  • Breaking (C++): SmartMotorControllerConfig removed WithArmFeedforward, WithElevatorFeedforward, WithSimpleFeedforward, and the standalone WithKp/WithKi/WithKd/WithKs/WithKv/WithKa/WithKg setters. See the migration note above.

2026.7.09

Added

  • SwerveModule now publishes the azimuth absolute encoder angle to telemetry.

  • Added SwerveModuleConfig.getAbsoluteEncoderSupplier() and getRawAbsoluteEncoderAngle() (Java) to expose the absolute encoder reading directly, including a variant with the configured zero offset stripped out. Swerve telemetry now reports this raw, offset-free angle.

Changed

  • SwerveModule.setSwerveModuleState(SwerveModuleState) now returns the optimized SwerveModuleState instead of void.

Fixed

  • SwerveDrive's desired module states now reflect the actual post-optimization SwerveModuleState returned by setSwerveModuleState(...) above, instead of the pre-optimization input — fixes desired-state telemetry/logging showing the wrong angle after a flip.

2026.7.06

Fixed

  • Fixed SwerveModuleConfig's state optimizer entering an infinite re-optimization loop right at a flip boundary. The flip decision now debounces against the last commanded angle instead of being re-derived from the live absolute encoder every loop.

2026.6.25.1

Added

  • SmartMotorControllerCommandRegistry now throws IllegalStateException when two different Subsystem instances register commands under the same name, and adds removeCommands(Subsystem) to clear a subsystem's registered commands (e.g. between tests).

2026.6.25

Added

  • Added a full C++ port of YAMS: mechanisms (Arm, Elevator, Pivot, FlyWheel, DoubleFlyWheel, DifferentialDrive), motor controller wrappers (Spark, TalonFX, TalonFXS), telemetry, units, and unit tests, mirroring the existing Java API.

  • Added SwerveDrive and SwerveInputStream (new in both Java and C++).

  • Added Doxygen documentation generation for the C++ API.

Removed

  • Breaking: Removed ThriftyNova motor controller support (NovaWrapper) from the Java API; there is no direct replacement.

Changed

  • Adopted wpiformat for consistent Java/C++ formatting across the codebase.

2026.1.17

2026.1.16

  • Fixed non-profiled Closed Loop Control in TalonFXS, TalonFX, and SparkMax.

  • Added Conversion for ThriftyNova encoder readings to account for encoder ticks.

  • Fixed SwerveDrive template.

  • Added Shoot on the move example.

2026.1.4

  • Added Imperial YUnits.PoundSquareInches YUnits.PoundsSquareFeet

  • Deprecated withMOI(double) in favor of withMOI(MomentOfInertia).

  • Added withMOI(MomentOfInertia)

2026.1.2

  • Added SmartMotorControllerConfig().clone() to allow for clonable configs.

  • Added MechanismConfig.withSmartMotorController() for all mechanism and null param constructors

  • Added SensorData.set() overloads

  • 'Trapazoidal' Spelling Error Correction by @akhaled247 in #56

  • REV 2026 with Feedforwards and PIDs by @thenetworkgrinch in #55

  • Added the option to loosely coupled followers. by @thenetworkgrinch in #58

  • Allow for independently created config classes for constants files. by @thenetworkgrinch in #59

  • Changed SwerveInputStream.copy() to SwerveInputStream.clone()

  • Modified SmartMotorControllerConfig to allow for creation without a Subsystem but still require a subsystem eventually.

  • Modified SmartMotorController telemetry to only create Live Tuning and other useful commands if tunable setpoints are not enabled.

  • Added MechanismGearing(double...) overload

  • Added DifferentialDrive example.

2025.12.22

  • Added the ability to use multiple SMC's with Live Tuning in one subsystem

  • Added AdvantageKit example

  • Modified SwerveDrive to support AdvantageKit better.

  • Updated Telemetry Setup for standalone SMC's to be automatic.

  • Added Units to Telemetry Fields.

  • Added DoubleFlyWheel example.

  • Fixed ElevatorConfig comments saying "length" instead of "angle" #54

  • Added support for user moment of inertia in ExponentialProfilePIDController #50

  • Improved Exponential Elevator Examples #46

  • Added ExponentialProfile.

  • Add MapleSim Integration to SwerveDrive by @konnorreynolds in #49

2025.11.05

  • Renamed .withExternalGearing to .withExternalEncoderGearing.

  • Added several new easy commands, like "Up", "Down", and "ZeroEncoder".

  • Moved commands to "SmartDashboard/Mechanisms/MECHNAME/MOTORNAME/Commands/".

  • Fixed immutable SensorData fields.

  • Fixed SparkWrapper.setEncoderPosition to set the sim position correctly.

2025.11.02

  • Renamed SmartMotorControllerConfig.withZeroOffset to SmartMotorControllerConfig.withExternalEncoderZeroOffset

2025.10.31

  • Updated SwerveInputStream to use .with* for shared styling.

  • Added swerve_drive example.

2025.10.29

  • Added setpoint visualization to Arm Mechanism2d

  • Fixed ArmFeedforward calculation on Spark's, and Nova's. Changed velocity calculation from Rotations to Radians. Found by 8114 (The Rocketeers)

2025.10.27

  • Renamed Shooter to FlyWheel

  • Added Loop Time to each mechanism.

2025.10.14

  • Added YUnits which allows you to include more exotic units in your code.

  • Added Sensors which allow you to seamlessly fetch automatically modified sensor data during sim and real data when running IRL.

Last updated

Was this helpful?