001/* 002 * Copyright (C) Cross The Road Electronics. All rights reserved. 003 * License information can be found in CTRE_LICENSE.txt 004 * For support and suggestions contact support@ctr-electronics.com or file 005 * an issue tracker at https://github.com/CrossTheRoadElec/Phoenix-Releases 006 */ 007package com.ctre.phoenix6.swerve; 008 009import static org.wpilib.units.Units.*; 010 011import java.util.Optional; 012import java.util.concurrent.locks.Lock; 013import java.util.concurrent.locks.ReentrantLock; 014import java.util.function.Consumer; 015import java.util.function.IntSupplier; 016 017import com.ctre.phoenix6.CANBus; 018import com.ctre.phoenix6.StatusCode; 019import com.ctre.phoenix6.Utils; 020import com.ctre.phoenix6.hardware.ParentDevice; 021import com.ctre.phoenix6.hardware.Pigeon2; 022import com.ctre.phoenix6.hardware.traits.CommonDevice; 023import com.ctre.phoenix6.hardware.traits.CommonTalon; 024import com.ctre.phoenix6.signals.NeutralModeValue; 025import com.ctre.phoenix6.swerve.SwerveRequest.NativeSwerveRequest; 026import com.ctre.phoenix6.swerve.jni.SwerveJNI; 027 028import org.wpilib.driverstation.DriverStationErrors; 029import org.wpilib.math.geometry.Pose2d; 030import org.wpilib.math.geometry.Rotation2d; 031import org.wpilib.math.geometry.Rotation3d; 032import org.wpilib.math.geometry.Translation2d; 033import org.wpilib.math.kinematics.ChassisVelocities; 034import org.wpilib.math.kinematics.SwerveDriveKinematics; 035import org.wpilib.math.kinematics.SwerveModulePosition; 036import org.wpilib.math.kinematics.SwerveModuleVelocity; 037import org.wpilib.math.linalg.Matrix; 038import org.wpilib.math.numbers.N1; 039import org.wpilib.math.numbers.N3; 040import org.wpilib.units.measure.*; 041 042/** 043 * Swerve Drive class utilizing CTR Electronics' Phoenix 6 API. 044 * <p> 045 * This class handles the kinematics, configuration, and odometry of a 046 * swerve drive utilizing CTR Electronics devices. We recommend using 047 * the Swerve Project Generator in Tuner X to create a template project 048 * that demonstrates how to use this class. 049 * <p> 050 * This class will construct the hardware devices internally, so the user 051 * only specifies the constants (IDs, PID gains, gear ratios, etc). 052 * Getters for these hardware devices are available. 053 * <p> 054 * This class performs pose estimation internally using a separate odometry 055 * thread. Vision measurements can be added using {@link #addVisionMeasurement}. 056 * Other odometry APIs such as {@link #resetPose} are also available. The 057 * resulting pose estimate can be retrieved along with module states and 058 * other information using {@link #getState}. Additionally, the odometry 059 * thread synchronously provides all new state updates to a telemetry function 060 * registered with {@link #registerTelemetry}. 061 * <p> 062 * If using the generator, the order in which modules are constructed is 063 * Front Left, Front Right, Back Left, Back Right. This means if you need 064 * the Back Left module, call {@code getModule(2);} to get the third 065 * (0-indexed) module. 066 */ 067public class SwerveDrivetrain< 068 DriveMotorT extends CommonTalon, 069 SteerMotorT extends CommonTalon, 070 EncoderT extends ParentDevice 071> implements AutoCloseable { 072 /** 073 * Functional interface for device constructors. 074 */ 075 @FunctionalInterface 076 public static interface DeviceConstructor<DeviceT> { 077 DeviceT create(int id, CANBus canbus); 078 } 079 080 /** 081 * Plain-Old-Data class holding the state of the swerve drivetrain. 082 * This encapsulates most data that is relevant for telemetry or 083 * decision-making from the Swerve Drive. 084 */ 085 public static class SwerveDriveState implements Cloneable { 086 /** The current pose of the robot */ 087 public Pose2d Pose = new Pose2d(); 088 /** The current robot-centric velocity */ 089 public ChassisVelocities Velocity = new ChassisVelocities(); 090 /** The current module positions */ 091 public SwerveModulePosition[] ModulePositions; 092 /** The current module velocities */ 093 public SwerveModuleVelocity[] ModuleVelocities; 094 /** The target module velocities */ 095 public SwerveModuleVelocity[] ModuleTargets; 096 /** The raw heading of the robot, unaffected by vision updates and odometry resets */ 097 public Rotation2d RawHeading = new Rotation2d(); 098 /** The timestamp of the state capture, in the timebase of {@link Utils#getCurrentTimeSeconds()} */ 099 public double Timestamp; 100 /** The measured odometry update period, in seconds */ 101 public double OdometryPeriod; 102 /** Number of successful data acquisitions */ 103 public int SuccessfulDaqs; 104 /** Number of failed data acquisitions */ 105 public int FailedDaqs; 106 107 /** 108 * Creates a deep copy of this state object. 109 * This API is not thread-safe. 110 */ 111 @Override 112 public SwerveDriveState clone() { 113 final var toReturn = new SwerveDriveState(); 114 toReturn.Pose = Pose; 115 toReturn.Velocity = Velocity; 116 toReturn.ModulePositions = ModulePositions.clone(); 117 toReturn.ModuleVelocities = ModuleVelocities.clone(); 118 toReturn.ModuleTargets = ModuleTargets.clone(); 119 toReturn.RawHeading = RawHeading; 120 toReturn.Timestamp = Timestamp; 121 toReturn.OdometryPeriod = OdometryPeriod; 122 toReturn.SuccessfulDaqs = SuccessfulDaqs; 123 toReturn.FailedDaqs = FailedDaqs; 124 return toReturn; 125 } 126 127 protected void updateFromJni(SwerveJNI.DriveState driveState) { 128 if (Pose.getX() != driveState.PoseX || 129 Pose.getY() != driveState.PoseY || 130 Pose.getRotation().getRadians() != driveState.PoseTheta) 131 { 132 Pose = new Pose2d(driveState.PoseX, driveState.PoseY, Rotation2d.fromRadians(driveState.PoseTheta)); 133 } 134 135 if (Velocity.vx != driveState.VelocityVx || 136 Velocity.vy != driveState.VelocityVy || 137 Velocity.omega != driveState.VelocityOmega) 138 { 139 Velocity = new ChassisVelocities(driveState.VelocityVx, driveState.VelocityVy, driveState.VelocityOmega); 140 } 141 142 for (int i = 0; i < ModulePositions.length; ++i) { 143 if (ModulePositions[i].distance != driveState.ModulePositions[i].distance || 144 ModulePositions[i].angle.getRadians() != driveState.ModulePositions[i].angle) 145 { 146 ModulePositions[i] = new SwerveModulePosition(driveState.ModulePositions[i].distance, Rotation2d.fromRadians(driveState.ModulePositions[i].angle)); 147 } 148 } 149 for (int i = 0; i < ModuleVelocities.length; ++i) { 150 if (ModuleVelocities[i].velocity != driveState.ModuleVelocities[i].velocity || 151 ModuleVelocities[i].angle.getRadians() != driveState.ModuleVelocities[i].angle) 152 { 153 ModuleVelocities[i] = new SwerveModuleVelocity(driveState.ModuleVelocities[i].velocity, Rotation2d.fromRadians(driveState.ModuleVelocities[i].angle)); 154 } 155 } 156 for (int i = 0; i < ModuleTargets.length; ++i) { 157 if (ModuleTargets[i].velocity != driveState.ModuleTargets[i].velocity || 158 ModuleTargets[i].angle.getRadians() != driveState.ModuleTargets[i].angle) 159 { 160 ModuleTargets[i] = new SwerveModuleVelocity(driveState.ModuleTargets[i].velocity, Rotation2d.fromRadians(driveState.ModuleTargets[i].angle)); 161 } 162 } 163 164 if (RawHeading.getRadians() != driveState.RawHeading) { 165 RawHeading = Rotation2d.fromRadians(driveState.RawHeading); 166 } 167 168 Timestamp = driveState.Timestamp; 169 OdometryPeriod = driveState.OdometryPeriod; 170 SuccessfulDaqs = driveState.SuccessfulDaqs; 171 FailedDaqs = driveState.FailedDaqs; 172 } 173 } 174 175 /** 176 * Contains everything the control requests need to calculate the module state. 177 */ 178 public static class SwerveControlParameters { 179 /** ID of the native drivetrain instance, used for JNI calls */ 180 public int drivetrainId; 181 182 /** The kinematics object used for control */ 183 public SwerveDriveKinematics kinematics; 184 /** The locations of the swerve modules */ 185 public Translation2d[] moduleLocations; 186 /** The max speed of the robot at 12 V output, in m/s */ 187 public double kMaxSpeedMps; 188 189 /** The forward direction from the operator perspective */ 190 public Rotation2d operatorForwardDirection = new Rotation2d(); 191 /** The current robot-centric chassis velocity */ 192 public ChassisVelocities currentChassisVelocity = new ChassisVelocities(); 193 /** The current pose of the robot */ 194 public Pose2d currentPose = new Pose2d(); 195 /** The timestamp of the current control apply, in the timebase of {@link Utils#getCurrentTimeSeconds()} */ 196 public double timestamp; 197 /** The update period of control apply */ 198 public double updatePeriod; 199 200 protected void updateFromJni(SwerveJNI.ControlParams controlParams) { 201 kMaxSpeedMps = controlParams.kMaxSpeedMps; 202 if (operatorForwardDirection.getRadians() != controlParams.operatorForwardDirection) { 203 operatorForwardDirection = Rotation2d.fromRadians(controlParams.operatorForwardDirection); 204 } 205 currentChassisVelocity.vx = controlParams.currentChassisVelocityVx; 206 currentChassisVelocity.vy = controlParams.currentChassisVelocityVy; 207 currentChassisVelocity.omega = controlParams.currentChassisVelocityOmega; 208 if (currentPose.getX() != controlParams.currentPoseX || 209 currentPose.getY() != controlParams.currentPoseY || 210 currentPose.getRotation().getRadians() != controlParams.currentPoseTheta) 211 { 212 currentPose = new Pose2d( 213 controlParams.currentPoseX, 214 controlParams.currentPoseY, 215 Rotation2d.fromRadians(controlParams.currentPoseTheta) 216 ); 217 } 218 timestamp = controlParams.timestamp; 219 updatePeriod = controlParams.updatePeriod; 220 } 221 } 222 223 /** Number of times to attempt config applies. */ 224 protected static final int kNumConfigAttempts = 2; 225 226 /** ID of the native drivetrain instance, used for JNI calls. */ 227 protected final int m_drivetrainId; 228 /** JNI instance to use for non-static JNI calls. This object is not thread-safe. */ 229 protected final SwerveJNI m_jni = new SwerveJNI(); 230 231 private final SwerveModule<DriveMotorT, SteerMotorT, EncoderT>[] m_modules; 232 private final Translation2d[] m_moduleLocations; 233 234 private final SwerveDriveKinematics m_kinematics; 235 236 private final Pigeon2 m_pigeon2; 237 private final SimSwerveDrivetrain m_simDrive; 238 239 /** The control parameters supplied to a non-native SwerveRequest. */ 240 private final SwerveControlParameters m_controlParams = new SwerveControlParameters(); 241 /** The swerve request currently applied. */ 242 private SwerveRequest m_swerveRequest = new SwerveRequest.Idle(); 243 /** Handle to the native object used to apply a non-native SwerveRequest. */ 244 private long m_controlHandle = 0; 245 246 /** JNI instance to use for telemetry JNI calls. This object is not thread-safe. */ 247 protected final SwerveJNI m_telemetryJNI; 248 /** The telemetry function currently applied. */ 249 private Consumer<SwerveDriveState> m_telemetryFunction = null; 250 /** Handle to the native object used to call the telemetry function. */ 251 private long m_telemetryHandle = 0; 252 253 /** Lock protecting the swerve drive state. */ 254 private final Lock m_stateLock = new ReentrantLock(); 255 /** The last received swerve drive state. */ 256 private final SwerveDriveState m_cachedState = new SwerveDriveState(); 257 /** The current operator forward direction. */ 258 private Rotation2d m_operatorForwardDirection = new Rotation2d(); 259 260 /** Performs swerve module updates in a separate thread to minimize latency. */ 261 public class OdometryThread { 262 /** 263 * Starts the odometry thread. 264 */ 265 public final void start() { 266 SwerveJNI.JNI_Odom_Start(m_drivetrainId); 267 } 268 269 /** 270 * Stops the odometry thread. 271 */ 272 public final void stop() { 273 SwerveJNI.JNI_Odom_Stop(m_drivetrainId); 274 } 275 276 /** 277 * Check if the odometry is currently valid. 278 * 279 * @return True if odometry is valid 280 */ 281 public boolean isOdometryValid() { 282 return SwerveJNI.JNI_IsOdometryValid(m_drivetrainId); 283 } 284 285 /** 286 * Sets the odometry thread priority to a real time priority under the specified priority level 287 * 288 * @param priority Priority level to set the odometry thread to. 289 * This is a value between 0 and 99, with 99 indicating higher priority and 0 indicating lower priority. 290 */ 291 public final void setThreadPriority(int priority) { 292 SwerveJNI.JNI_Odom_SetThreadPriority(m_drivetrainId, priority); 293 } 294 } 295 296 private final OdometryThread m_odometryThread = new OdometryThread(); 297 298 /** Thread responsible for disposing the native drivetrain on shutdown. */ 299 private Thread m_shutdownHook; 300 301 private double m_lastErrorSeconds = 0.0; 302 303 /** 304 * Constructs a CTRE SwerveDrivetrain using the specified constants. 305 * <p> 306 * This constructs the underlying hardware devices, so users should not construct 307 * the devices themselves. If they need the devices, they can access them through 308 * getters in the classes. 309 * 310 * @param driveMotorConstructor Constructor for the drive motor, such as {@code TalonFX::new} 311 * @param steerMotorConstructor Constructor for the steer motor, such as {@code TalonFX::new} 312 * @param encoderConstructor Constructor for the azimuth encoder, such as {@code CANcoder::new} 313 * @param drivetrainConstants Drivetrain-wide constants for the swerve drive 314 * @param modules Constants for each specific module 315 */ 316 public SwerveDrivetrain( 317 DeviceConstructor<DriveMotorT> driveMotorConstructor, 318 DeviceConstructor<SteerMotorT> steerMotorConstructor, 319 DeviceConstructor<EncoderT> encoderConstructor, 320 SwerveDrivetrainConstants drivetrainConstants, 321 SwerveModuleConstants<?, ?, ?>... modules 322 ) { 323 this(() -> { 324 long nativeDriveConstants = drivetrainConstants.createNativeInstance(); 325 long nativeModuleConstants = SwerveModuleConstants.createNativeInstance(modules); 326 327 var drivetrain = SwerveJNI.JNI_CreateDrivetrain(nativeDriveConstants, nativeModuleConstants, modules.length); 328 329 SwerveJNI.JNI_DestroyConstants(nativeDriveConstants); 330 SwerveJNI.JNI_DestroyConstants(nativeModuleConstants); 331 return drivetrain; 332 }, 333 driveMotorConstructor, steerMotorConstructor, encoderConstructor, 334 drivetrainConstants, modules 335 ); 336 } 337 338 /** 339 * Constructs a CTRE SwerveDrivetrain using the specified constants. 340 * <p> 341 * This constructs the underlying hardware devices, so users should not construct 342 * the devices themselves. If they need the devices, they can access them through 343 * getters in the classes. 344 * 345 * @param driveMotorConstructor Constructor for the drive motor, such as {@code TalonFX::new} 346 * @param steerMotorConstructor Constructor for the steer motor, such as {@code TalonFX::new} 347 * @param encoderConstructor Constructor for the azimuth encoder, such as {@code CANcoder::new} 348 * @param drivetrainConstants Drivetrain-wide constants for the swerve drive 349 * @param odometryUpdateFrequency The frequency to run the odometry loop. If 350 * unspecified or set to 0 Hz, this is 250 Hz on 351 * CAN FD, and 100 Hz on CAN 2.0. 352 * @param modules Constants for each specific module 353 */ 354 public SwerveDrivetrain( 355 DeviceConstructor<DriveMotorT> driveMotorConstructor, 356 DeviceConstructor<SteerMotorT> steerMotorConstructor, 357 DeviceConstructor<EncoderT> encoderConstructor, 358 SwerveDrivetrainConstants drivetrainConstants, 359 double odometryUpdateFrequency, 360 SwerveModuleConstants<?, ?, ?>... modules 361 ) { 362 this(() -> { 363 long nativeDriveConstants = drivetrainConstants.createNativeInstance(); 364 long nativeModuleConstants = SwerveModuleConstants.createNativeInstance(modules); 365 366 var drivetrain = SwerveJNI.JNI_CreateDrivetrainWithFreq(nativeDriveConstants, odometryUpdateFrequency, 367 nativeModuleConstants, modules.length); 368 369 SwerveJNI.JNI_DestroyConstants(nativeDriveConstants); 370 SwerveJNI.JNI_DestroyConstants(nativeModuleConstants); 371 return drivetrain; 372 }, 373 driveMotorConstructor, steerMotorConstructor, encoderConstructor, 374 drivetrainConstants, modules 375 ); 376 } 377 378 /** 379 * Constructs a CTRE SwerveDrivetrain using the specified constants. 380 * <p> 381 * This constructs the underlying hardware devices, so users should not construct 382 * the devices themselves. If they need the devices, they can access them through 383 * getters in the classes. 384 * 385 * @param driveMotorConstructor Constructor for the drive motor, such as {@code TalonFX::new} 386 * @param steerMotorConstructor Constructor for the steer motor, such as {@code TalonFX::new} 387 * @param encoderConstructor Constructor for the azimuth encoder, such as {@code CANcoder::new} 388 * @param drivetrainConstants Drivetrain-wide constants for the swerve drive 389 * @param odometryUpdateFrequency The frequency to run the odometry loop. If 390 * unspecified or set to 0 Hz, this is 250 Hz on 391 * CAN FD, and 100 Hz on CAN 2.0. 392 * @param odometryStandardDeviation The standard deviation for odometry calculation 393 * in the form [x, y, theta]ᵀ, with units in meters 394 * and radians 395 * @param visionStandardDeviation The standard deviation for vision calculation 396 * in the form [x, y, theta]ᵀ, with units in meters 397 * and radians 398 * @param modules Constants for each specific module 399 */ 400 public SwerveDrivetrain( 401 DeviceConstructor<DriveMotorT> driveMotorConstructor, 402 DeviceConstructor<SteerMotorT> steerMotorConstructor, 403 DeviceConstructor<EncoderT> encoderConstructor, 404 SwerveDrivetrainConstants drivetrainConstants, 405 double odometryUpdateFrequency, 406 Matrix<N3, N1> odometryStandardDeviation, 407 Matrix<N3, N1> visionStandardDeviation, 408 SwerveModuleConstants<?, ?, ?>... modules 409 ) { 410 this(() -> { 411 long nativeDriveConstants = drivetrainConstants.createNativeInstance(); 412 long nativeModuleConstants = SwerveModuleConstants.createNativeInstance(modules); 413 414 var drivetrain = SwerveJNI.JNI_CreateDrivetrainWithStddev(nativeDriveConstants, odometryUpdateFrequency, 415 odometryStandardDeviation.getData(), visionStandardDeviation.getData(), 416 nativeModuleConstants, modules.length); 417 418 SwerveJNI.JNI_DestroyConstants(nativeDriveConstants); 419 SwerveJNI.JNI_DestroyConstants(nativeModuleConstants); 420 return drivetrain; 421 }, 422 driveMotorConstructor, steerMotorConstructor, encoderConstructor, 423 drivetrainConstants, modules 424 ); 425 } 426 427 @SuppressWarnings("this-escape") 428 private SwerveDrivetrain( 429 IntSupplier createNativeInst, 430 DeviceConstructor<DriveMotorT> driveMotorConstructor, 431 DeviceConstructor<SteerMotorT> steerMotorConstructor, 432 DeviceConstructor<EncoderT> encoderConstructor, 433 SwerveDrivetrainConstants drivetrainConstants, 434 SwerveModuleConstants<?, ?, ?>... modules 435 ) { 436 m_drivetrainId = createNativeInst.getAsInt(); 437 438 m_cachedState.ModulePositions = new SwerveModulePosition[modules.length]; 439 m_cachedState.ModuleVelocities = new SwerveModuleVelocity[modules.length]; 440 m_cachedState.ModuleTargets = new SwerveModuleVelocity[modules.length]; 441 m_jni.driveState.ModulePositions = new SwerveJNI.ModulePosition[modules.length]; 442 m_jni.driveState.ModuleVelocities = new SwerveJNI.ModuleVelocity[modules.length]; 443 m_jni.driveState.ModuleTargets = new SwerveJNI.ModuleVelocity[modules.length]; 444 for (int i = 0; i < modules.length; ++i) { 445 m_cachedState.ModulePositions[i] = new SwerveModulePosition(); 446 m_cachedState.ModuleVelocities[i] = new SwerveModuleVelocity(); 447 m_cachedState.ModuleTargets[i] = new SwerveModuleVelocity(); 448 m_jni.driveState.ModulePositions[i] = new SwerveJNI.ModulePosition(); 449 m_jni.driveState.ModuleVelocities[i] = new SwerveJNI.ModuleVelocity(); 450 m_jni.driveState.ModuleTargets[i] = new SwerveJNI.ModuleVelocity(); 451 } 452 453 m_telemetryJNI = m_jni.clone(); 454 455 final var canbus = new CANBus(drivetrainConstants.CANBusName); 456 @SuppressWarnings("unchecked") 457 final var tmpModules = (SwerveModule<DriveMotorT, SteerMotorT, EncoderT>[])new SwerveModule<?, ?, ?>[modules.length]; 458 459 m_modules = tmpModules; 460 m_moduleLocations = new Translation2d[modules.length]; 461 for (int i = 0; i < modules.length; ++i) { 462 m_modules[i] = new SwerveModule<DriveMotorT, SteerMotorT, EncoderT>( 463 driveMotorConstructor, 464 steerMotorConstructor, 465 encoderConstructor, 466 modules[i], 467 canbus, 468 m_drivetrainId, i 469 ); 470 m_moduleLocations[i] = new Translation2d(modules[i].LocationX, modules[i].LocationY); 471 } 472 473 m_kinematics = new SwerveDriveKinematics(m_moduleLocations); 474 475 m_controlParams.drivetrainId = m_drivetrainId; 476 m_controlParams.kinematics = m_kinematics; 477 m_controlParams.moduleLocations = m_moduleLocations; 478 479 m_pigeon2 = new Pigeon2(drivetrainConstants.Pigeon2Id, canbus); 480 m_simDrive = new SimSwerveDrivetrain(m_moduleLocations, m_pigeon2.getSimState(), modules); 481 482 if (drivetrainConstants.Pigeon2Configs != null) { 483 StatusCode retval = StatusCode.OK; 484 for (int i = 0; i < kNumConfigAttempts; ++i) { 485 retval = getPigeon2().getConfigurator().apply(drivetrainConstants.Pigeon2Configs); 486 if (retval.isOK()) break; 487 } 488 if (!retval.isOK()) { 489 System.out.println("Pigeon2 ID " + getPigeon2().getDeviceID() + " failed config with error: " + retval); 490 } 491 } 492 /* do not start thread until after applying Pigeon 2 configs */ 493 m_odometryThread.start(); 494 495 m_shutdownHook = new Thread(() -> { 496 m_shutdownHook = null; 497 close(); 498 }); 499 Runtime.getRuntime().addShutdownHook(m_shutdownHook); 500 } 501 502 @Override 503 public void close() { 504 SwerveJNI.JNI_DestroyDrivetrain(m_drivetrainId); 505 if (m_controlHandle != 0) { 506 SwerveJNI.JNI_DestroyControl(m_controlHandle); 507 m_controlHandle = 0; 508 } 509 if (m_telemetryHandle != 0) { 510 SwerveJNI.JNI_DestroyTelemetry(m_telemetryHandle); 511 m_telemetryHandle = 0; 512 } 513 514 if (m_shutdownHook != null) { 515 Runtime.getRuntime().removeShutdownHook(m_shutdownHook); 516 m_shutdownHook = null; 517 } 518 } 519 520 /** 521 * Updates all the simulation state variables for this 522 * drivetrain class. User provides the update variables for the simulation. 523 * 524 * @param dtSeconds time since last update call 525 * @param supplyVoltage voltage as seen at the motor controllers 526 */ 527 public void updateSimState(double dtSeconds, double supplyVoltage) { 528 m_simDrive.update(dtSeconds, supplyVoltage, m_modules); 529 } 530 531 /** 532 * Optimizes the bus utilization of all devices in the swerve drivetrain by reducing 533 * the update frequencies of their status signals. All signals necessary for drivetrain 534 * functionality will remain enabled. 535 * <p> 536 * This is equivalent to calling {@link ParentDevice#optimizeBusUtilizationForAll} 537 * with all devices in the drivetrain. 538 * <p> 539 * This will wait up to 0.100 seconds (100ms) for each status frame. 540 * 541 * @return Status code of the first failed optimize call, or OK if all succeeded 542 */ 543 public StatusCode optimizeBusUtilization() { 544 final var devices = new CommonDevice[m_modules.length * 3 + 1]; 545 devices[0] = getPigeon2(); 546 for (int i = 0; i < m_modules.length; ++i) { 547 devices[i * 3 + 1] = m_modules[i].getDriveMotor(); 548 devices[i * 3 + 2] = m_modules[i].getSteerMotor(); 549 devices[i * 3 + 3] = m_modules[i].getEncoder(); 550 } 551 return ParentDevice.optimizeBusUtilizationForAll(devices); 552 } 553 554 /** 555 * Optimizes the bus utilization of all devices in the swerve drivetrain by reducing 556 * the update frequencies of their status signals. All signals necessary for drivetrain 557 * functionality will remain enabled. 558 * <p> 559 * This is equivalent to calling {@link ParentDevice#optimizeBusUtilizationForAll} 560 * with all devices in the drivetrain. 561 * <p> 562 * This will wait up to 0.100 seconds (100ms) for each status frame. 563 * 564 * @param optimizedFreqHz The update frequency to apply to the optimized status signals. A frequency 565 * of 0 Hz will turn off the signals. Otherwise, the minimum supported signal 566 * frequency is 4 Hz. 567 * @return Status code of the first failed optimize call, or OK if all succeeded 568 */ 569 public StatusCode optimizeBusUtilization(double optimizedFreqHz) { 570 final var devices = new CommonDevice[m_modules.length * 3 + 1]; 571 devices[0] = getPigeon2(); 572 for (int i = 0; i < m_modules.length; ++i) { 573 devices[i * 3 + 1] = m_modules[i].getDriveMotor(); 574 devices[i * 3 + 2] = m_modules[i].getSteerMotor(); 575 devices[i * 3 + 3] = m_modules[i].getEncoder(); 576 } 577 return ParentDevice.optimizeBusUtilizationForAll(optimizedFreqHz, devices); 578 } 579 580 /** 581 * Optimizes the bus utilization of all devices in the swerve drivetrain by reducing 582 * the update frequencies of their status signals. All signals necessary for drivetrain 583 * functionality will remain enabled. 584 * <p> 585 * This is equivalent to calling {@link ParentDevice#optimizeBusUtilizationForAll} 586 * with all devices in the drivetrain. 587 * <p> 588 * This will wait up to 0.100 seconds (100ms) for each status frame. 589 * 590 * @param optimizedFreq The update frequency to apply to the optimized status signals. A frequency 591 * of 0 Hz will turn off the signals. Otherwise, the minimum supported signal 592 * frequency is 4 Hz. 593 * @return Status code of the first failed optimize call, or OK if all succeeded 594 */ 595 public StatusCode optimizeBusUtilization(Frequency optimizedFreq) { 596 return optimizeBusUtilization(optimizedFreq.in(Hertz)); 597 } 598 599 /** 600 * Gets whether the drivetrain is on a CAN FD bus. 601 * 602 * @return true if on CAN FD 603 */ 604 public final boolean isOnCANFD() { 605 return SwerveJNI.JNI_IsOnCANFD(m_drivetrainId); 606 } 607 608 /** 609 * Gets the target odometry update frequency in Hz. 610 * 611 * @return Target odometry update frequency 612 */ 613 public final double getOdometryFrequency() { 614 return SwerveJNI.JNI_GetOdometryFrequency(m_drivetrainId); 615 } 616 617 /** 618 * Gets the target odometry update frequency. 619 * 620 * @return Target odometry update frequency 621 */ 622 public final Frequency getOdometryFrequencyMeasure() { 623 return Hertz.of(getOdometryFrequency()); 624 } 625 626 /** 627 * Gets a reference to the odometry thread. 628 * 629 * @return Odometry thread 630 */ 631 public final OdometryThread getOdometryThread() { 632 return m_odometryThread; 633 } 634 635 /** 636 * Check if the odometry is currently valid. 637 * 638 * @return True if odometry is valid 639 */ 640 public boolean isOdometryValid() { 641 return SwerveJNI.JNI_IsOdometryValid(m_drivetrainId); 642 } 643 644 /** 645 * Gets a reference to the kinematics used for the drivetrain. 646 * 647 * @return Swerve kinematics 648 */ 649 public final SwerveDriveKinematics getKinematics() { 650 return m_kinematics; 651 } 652 653 /** 654 * Applies the specified control request to this swerve drivetrain. 655 * 656 * @param request Request to apply 657 */ 658 public void setControl(SwerveRequest request) { 659 if (!m_modules[0].m_isValid) { 660 double now = Utils.getCurrentTimeSeconds(); 661 if (now - m_lastErrorSeconds > 3.0) { 662 m_lastErrorSeconds = now; 663 DriverStationErrors.reportError( 664 "[phoenix] SwerveDrivetrain configuration invalid, drive is disabled. Ensure that the SwerveDrivetrain and SwerveModuleConstants instances are constructed with matching motor types (TalonFX/TalonFXConfiguration, TalonFXS/TalonFXSConfiguration, etc.).", 665 true 666 ); 667 } 668 return; 669 } 670 671 if (m_swerveRequest != request) { 672 m_swerveRequest = request; 673 674 var prevControlHandle = m_controlHandle; 675 676 if (request == null) { 677 m_controlHandle = m_jni.JNI_SetControl(m_drivetrainId, null); 678 } else if (request instanceof NativeSwerveRequest req) { 679 req.applyNative(m_drivetrainId); 680 m_controlHandle = 0; 681 } else { 682 m_controlHandle = m_jni.JNI_SetControl(m_drivetrainId, () -> { 683 m_controlParams.updateFromJni(m_jni.controlParams); 684 return request.apply(m_controlParams, m_modules).value; 685 }); 686 } 687 688 if (prevControlHandle != 0) { 689 SwerveJNI.JNI_DestroyControl(prevControlHandle); 690 } 691 } else if (request instanceof NativeSwerveRequest req) { 692 /* update the native object */ 693 req.applyNative(m_drivetrainId); 694 } 695 } 696 697 /** 698 * Gets the current state of the swerve drivetrain. 699 * This includes information such as the pose estimate, 700 * module states, and chassis velocity. 701 * 702 * @return Current state of the drivetrain 703 */ 704 public final SwerveDriveState getState() { 705 m_jni.JNI_GetState(m_drivetrainId); 706 try { 707 m_stateLock.lock(); 708 m_cachedState.updateFromJni(m_jni.driveState); 709 return m_cachedState; 710 } finally { 711 m_stateLock.unlock(); 712 } 713 } 714 715 /** 716 * Gets a copy of the current state of the swerve drivetrain. 717 * This includes information such as the pose estimate, 718 * module states, and chassis velocity. 719 * <p> 720 * This can be used to get a thread-safe copy of the state object. 721 * 722 * @return Copy of the current state of the drivetrain 723 */ 724 public final SwerveDriveState getStateCopy() { 725 m_jni.JNI_GetState(m_drivetrainId); 726 try { 727 m_stateLock.lock(); 728 m_cachedState.updateFromJni(m_jni.driveState); 729 return m_cachedState.clone(); 730 } finally { 731 m_stateLock.unlock(); 732 } 733 } 734 735 /** 736 * Register the specified lambda to be executed whenever our SwerveDriveState function 737 * is updated in our odometry thread. 738 * <p> 739 * It is imperative that this function is cheap, as it will be executed along with 740 * the odometry call, and if this takes a long time, it may negatively impact the 741 * odometry of this stack. 742 * <p> 743 * This can also be used for logging data if the function performs logging instead of telemetry. 744 * Additionally, the SwerveDriveState object can be cloned and stored for later processing. 745 * 746 * @param telemetryFunction Function to call for telemetry or logging 747 */ 748 public void registerTelemetry(Consumer<SwerveDriveState> telemetryFunction) { 749 if (m_telemetryFunction != telemetryFunction) { 750 m_telemetryFunction = telemetryFunction; 751 752 var prevTelemetryHandle = m_telemetryHandle; 753 754 if (telemetryFunction == null) { 755 m_telemetryHandle = m_telemetryJNI.JNI_RegisterTelemetry(m_drivetrainId, null); 756 } else { 757 m_telemetryHandle = m_telemetryJNI.JNI_RegisterTelemetry(m_drivetrainId, () -> { 758 try { 759 m_stateLock.lock(); 760 m_cachedState.updateFromJni(m_telemetryJNI.driveState); 761 telemetryFunction.accept(m_cachedState); 762 } finally { 763 m_stateLock.unlock(); 764 } 765 }); 766 } 767 768 if (prevTelemetryHandle != 0) { 769 SwerveJNI.JNI_DestroyTelemetry(prevTelemetryHandle); 770 } 771 } 772 } 773 774 /** 775 * Configures the neutral mode to use for all modules' drive motors. 776 * <p> 777 * This will wait up to 0.100 seconds (100ms) by default. 778 * 779 * @param neutralMode The drive motor neutral mode 780 * @return Status code of the first failed config call, or OK if all succeeded 781 */ 782 public StatusCode configNeutralMode(NeutralModeValue neutralMode) { 783 return StatusCode.valueOf(SwerveJNI.JNI_ConfigNeutralMode(m_drivetrainId, neutralMode.value)); 784 } 785 786 /** 787 * Configures the neutral mode to use for all modules' drive motors. 788 * 789 * @param neutralMode The drive motor neutral mode 790 * @param timeoutSeconds Maximum amount of time to wait when performing each configuration 791 * @return Status code of the first failed config call, or OK if all succeeded 792 */ 793 public StatusCode configNeutralMode(NeutralModeValue neutralMode, double timeoutSeconds) { 794 return StatusCode.valueOf(SwerveJNI.JNI_ConfigNeutralModeWithTimeout(m_drivetrainId, neutralMode.value, timeoutSeconds)); 795 } 796 797 /** 798 * Zero's this swerve drive's odometry entirely. 799 * <p> 800 * This will zero the entire odometry, and place the robot at 0,0 801 */ 802 public void tareEverything() { 803 SwerveJNI.JNI_TareEverything(m_drivetrainId); 804 } 805 806 /** 807 * Resets the rotation of the robot pose to 0 from the 808 * {@link SwerveRequest.ForwardPerspectiveValue#OperatorPerspective} 809 * perspective. This makes the current orientation of the robot the 810 * X forward for field-centric maneuvers. 811 * <p> 812 * This is equivalent to calling {@link #resetRotation} with the operator 813 * perspective rotation. 814 */ 815 public void seedFieldCentric() { 816 seedFieldCentric(Rotation2d.kZero); 817 } 818 819 /** 820 * Resets the rotation of the robot pose to the given value from the 821 * {@link SwerveRequest.ForwardPerspectiveValue#OperatorPerspective} 822 * perspective. This makes the current orientation of the robot minus 823 * {@code rotation} the X forward for field-centric maneuvers. 824 * <p> 825 * This is equivalent to calling {@link #resetRotation} with 826 * {@code rotation.plus(getOperatorForwardDirection())}. 827 * 828 * @param rotation Rotation to make the current rotation 829 */ 830 public void seedFieldCentric(Rotation2d rotation) { 831 SwerveJNI.JNI_SeedFieldCentric(m_drivetrainId, rotation.getRadians()); 832 } 833 834 /** 835 * Resets the pose of the robot. The pose should be from the 836 * {@link SwerveRequest.ForwardPerspectiveValue#BlueAlliance} perspective. 837 * 838 * @param pose Pose to make the current pose 839 */ 840 public void resetPose(Pose2d pose) { 841 SwerveJNI.JNI_ResetPose(m_drivetrainId, pose.getX(), pose.getY(), pose.getRotation().getRadians()); 842 } 843 844 /** 845 * Resets the translation of the robot pose without affecting rotation. 846 * The translation should be from the {@link SwerveRequest.ForwardPerspectiveValue#BlueAlliance} 847 * perspective. 848 * 849 * @param translation Translation to make the current translation 850 */ 851 public void resetTranslation(Translation2d translation) { 852 SwerveJNI.JNI_ResetTranslation(m_drivetrainId, translation.getX(), translation.getY()); 853 } 854 855 /** 856 * Resets the rotation of the robot pose without affecting translation. 857 * The rotation should be from the {@link SwerveRequest.ForwardPerspectiveValue#BlueAlliance} 858 * perspective. 859 * 860 * @param rotation Rotation to make the current rotation 861 */ 862 public void resetRotation(Rotation2d rotation) { 863 SwerveJNI.JNI_ResetRotation(m_drivetrainId, rotation.getRadians()); 864 } 865 866 /** 867 * Takes the {@link SwerveRequest.ForwardPerspectiveValue#BlueAlliance} perpective direction 868 * and treats it as the forward direction for 869 * {@link SwerveRequest.ForwardPerspectiveValue#OperatorPerspective}. 870 * <p> 871 * If the operator is in the Blue Alliance Station, this should be 0 degrees. 872 * If the operator is in the Red Alliance Station, this should be 180 degrees. 873 * <p> 874 * This does not change the robot pose, which is in the 875 * {@link SwerveRequest.ForwardPerspectiveValue#BlueAlliance} perspective. 876 * As a result, the robot pose may need to be reset using {@link #resetPose}. 877 * 878 * @param fieldDirection Heading indicating which direction is forward from 879 * the {@link SwerveRequest.ForwardPerspectiveValue#BlueAlliance} perspective 880 */ 881 public void setOperatorPerspectiveForward(Rotation2d fieldDirection) { 882 SwerveJNI.JNI_SetOperatorPerspectiveForward(m_drivetrainId, fieldDirection.getRadians()); 883 } 884 885 /** 886 * Returns the {@link SwerveRequest.ForwardPerspectiveValue#BlueAlliance} perpective 887 * direction that is treated as the forward direction for 888 * {@link SwerveRequest.ForwardPerspectiveValue#OperatorPerspective}. 889 * <p> 890 * If the operator is in the Blue Alliance Station, this should be 0 degrees. 891 * If the operator is in the Red Alliance Station, this should be 180 degrees. 892 * 893 * @return Heading indicating which direction is forward from 894 * the {@link SwerveRequest.ForwardPerspectiveValue#BlueAlliance} perspective 895 */ 896 public final Rotation2d getOperatorForwardDirection() { 897 double operatorForwardDirectionRad = SwerveJNI.JNI_GetOperatorForwardDirection(m_drivetrainId); 898 try { 899 m_stateLock.lock(); 900 if (m_operatorForwardDirection.getRadians() != operatorForwardDirectionRad) { 901 m_operatorForwardDirection = Rotation2d.fromRadians(operatorForwardDirectionRad); 902 } 903 return m_operatorForwardDirection; 904 } finally { 905 m_stateLock.unlock(); 906 } 907 } 908 909 /** 910 * Adds a vision measurement to the Kalman Filter. This will correct the 911 * odometry pose estimate while still accounting for measurement noise. 912 * <p> 913 * This method can be called as infrequently as you want. 914 * <p> 915 * To promote stability of the pose estimate and make it robust to bad vision 916 * data, we recommend only adding vision measurements that are already within 917 * one meter or so of the current pose estimate. 918 * 919 * @param visionRobotPoseMeters The pose of the robot as measured by the vision 920 * camera. 921 * @param timestampSeconds The timestamp of the vision measurement in 922 * seconds. Note that you must use a timestamp with an 923 * epoch since system startup (i.e., the epoch of this 924 * timestamp is the same epoch as 925 * {@link Utils#getCurrentTimeSeconds}). 926 * This means that you should use 927 * {@link Utils#getCurrentTimeSeconds} 928 * as your time source or sync the epochs. 929 */ 930 public void addVisionMeasurement(Pose2d visionRobotPoseMeters, double timestampSeconds) { 931 SwerveJNI.JNI_AddVisionMeasurement(m_drivetrainId, visionRobotPoseMeters.getX(), visionRobotPoseMeters.getY(), 932 visionRobotPoseMeters.getRotation().getRadians(), timestampSeconds); 933 } 934 935 /** 936 * Adds a vision measurement to the Kalman Filter. This will correct the 937 * odometry pose estimate while still accounting for measurement noise. 938 * <p> 939 * This method can be called as infrequently as you want. 940 * <p> 941 * To promote stability of the pose estimate and make it robust to bad vision 942 * data, we recommend only adding vision measurements that are already within 943 * one meter or so of the current pose estimate. 944 * <p> 945 * Note that the vision measurement standard deviations passed into this method 946 * will continue to apply to future measurements until a subsequent call to 947 * {@link #setVisionMeasurementStdDevs(Matrix)} or this method. 948 * 949 * @param visionRobotPoseMeters The pose of the robot as measured by the 950 * vision camera. 951 * @param timestampSeconds The timestamp of the vision measurement in 952 * seconds. Note that you must use a timestamp with an 953 * epoch since system startup (i.e., the epoch of this 954 * timestamp is the same epoch as 955 * {@link Utils#getCurrentTimeSeconds}). 956 * This means that you should use 957 * {@link Utils#getCurrentTimeSeconds} 958 * as your time source or sync the epochs. 959 * @param visionMeasurementStdDevs Standard deviations of the vision pose 960 * measurement (x position 961 * in meters, y position in meters, and heading 962 * in radians). Increase these numbers to trust 963 * the vision pose measurement less. 964 */ 965 public void addVisionMeasurement( 966 Pose2d visionRobotPoseMeters, 967 double timestampSeconds, 968 Matrix<N3, N1> visionMeasurementStdDevs) 969 { 970 SwerveJNI.JNI_AddVisionMeasurementWithStdDev(m_drivetrainId, visionRobotPoseMeters.getX(), visionRobotPoseMeters.getY(), 971 visionRobotPoseMeters.getRotation().getRadians(), timestampSeconds, visionMeasurementStdDevs.getData()); 972 } 973 974 /** 975 * Sets the pose estimator's trust of global measurements. This might be used to 976 * change trust in vision measurements after the autonomous period, or to change 977 * trust as distance to a vision target increases. 978 * 979 * @param visionMeasurementStdDevs Standard deviations of the vision 980 * measurements. Increase these numbers to 981 * trust global measurements from vision less. 982 * This matrix is in the form [x, y, theta]ᵀ, 983 * with units in meters and radians. 984 */ 985 public void setVisionMeasurementStdDevs(Matrix<N3, N1> visionMeasurementStdDevs) { 986 SwerveJNI.JNI_SetVisionMeasurementStdDevs(m_drivetrainId, visionMeasurementStdDevs.getData()); 987 } 988 989 /** 990 * Sets the pose estimator's trust in robot odometry. This might be used to change 991 * trust in odometry after an impact with the wall or traversing a bump. 992 * 993 * @param stateStdDevs Standard deviations of the pose estimate. Increase these 994 * numbers to trust your state estimate less. This matrix is 995 * in the form [x, y, theta]ᵀ, with units in meters and radians. 996 */ 997 public void setStateStdDevs(Matrix<N3, N1> stateStdDevs) { 998 SwerveJNI.JNI_SetStateStdDevs(m_drivetrainId, stateStdDevs.getData()); 999 } 1000 1001 /** 1002 * Return the pose at a given timestamp, if the buffer is not empty. 1003 * 1004 * @param timestampSeconds The pose's timestamp. Note that you must use a timestamp 1005 * with an epoch since system startup (i.e., the epoch of 1006 * this timestamp is the same epoch as 1007 * {@link Utils#getCurrentTimeSeconds}). 1008 * This means that you should use 1009 * {@link Utils#getCurrentTimeSeconds} 1010 * as your time source in this case. 1011 * @return The pose at the given timestamp (or Optional.empty() if the buffer is 1012 * empty). 1013 */ 1014 public Optional<Pose2d> samplePoseAt(double timestampSeconds) { 1015 double[] retval = SwerveJNI.JNI_SamplePoseAt(m_drivetrainId, timestampSeconds); 1016 if (retval == null) { 1017 return Optional.empty(); 1018 } 1019 return Optional.of(new Pose2d(retval[0], retval[1], Rotation2d.fromRadians(retval[2]))); 1020 } 1021 1022 /** 1023 * Get a reference to the module at the specified index. 1024 * The index corresponds to the module described in the constructor. 1025 * 1026 * @param index Which module to get 1027 * @return Reference to SwerveModule 1028 */ 1029 public final SwerveModule<DriveMotorT, SteerMotorT, EncoderT> getModule(int index) { 1030 if (index >= m_modules.length) { 1031 return null; 1032 } 1033 return m_modules[index]; 1034 } 1035 1036 /** 1037 * Get a reference to the full array of modules. 1038 * The indexes correspond to the module described in the constructor. 1039 * 1040 * @return Reference to the SwerveModule array 1041 */ 1042 public final SwerveModule<DriveMotorT, SteerMotorT, EncoderT>[] getModules() { 1043 return m_modules; 1044 } 1045 1046 /** 1047 * Gets the locations of the swerve modules. 1048 * 1049 * @return Reference to the array of swerve module locations 1050 */ 1051 public final Translation2d[] getModuleLocations() { 1052 return m_moduleLocations; 1053 } 1054 1055 /** 1056 * Gets the current orientation of the robot as a {@link Rotation3d} from 1057 * the Pigeon 2 quaternion values. 1058 * 1059 * @return The robot orientation as a {@link Rotation3d} 1060 */ 1061 public Rotation3d getRotation3d() { 1062 return m_pigeon2.getRotation3d(); 1063 } 1064 1065 /** 1066 * Gets this drivetrain's Pigeon 2 reference. 1067 * <p> 1068 * This should be used only to access signals and change configurations that the 1069 * swerve drivetrain does not configure itself. 1070 * 1071 * @return This drivetrain's Pigeon 2 reference 1072 */ 1073 public final Pigeon2 getPigeon2() { 1074 return m_pigeon2; 1075 } 1076}