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.mechanisms.swerve; 008 009import java.util.concurrent.locks.ReadWriteLock; 010import java.util.concurrent.locks.ReentrantReadWriteLock; 011import java.util.function.Consumer; 012 013import com.ctre.phoenix6.BaseStatusSignal; 014import com.ctre.phoenix6.CANBus; 015import com.ctre.phoenix6.HootReplay; 016import com.ctre.phoenix6.StatusCode; 017import com.ctre.phoenix6.StatusSignal; 018import com.ctre.phoenix6.Utils; 019import com.ctre.phoenix6.hardware.Pigeon2; 020import com.ctre.phoenix6.jni.PlatformJNI; 021import com.ctre.phoenix6.mechanisms.swerve.LegacySwerveRequest.LegacySwerveControlRequestParameters; 022import com.ctre.phoenix6.signals.NeutralModeValue; 023 024import edu.wpi.first.math.Matrix; 025import edu.wpi.first.math.VecBuilder; 026import edu.wpi.first.math.estimator.SwerveDrivePoseEstimator; 027import edu.wpi.first.math.filter.LinearFilter; 028import edu.wpi.first.math.filter.MedianFilter; 029import edu.wpi.first.math.geometry.Pose2d; 030import edu.wpi.first.math.geometry.Rotation2d; 031import edu.wpi.first.math.geometry.Rotation3d; 032import edu.wpi.first.math.geometry.Translation2d; 033import edu.wpi.first.math.kinematics.ChassisSpeeds; 034import edu.wpi.first.math.kinematics.SwerveDriveKinematics; 035import edu.wpi.first.math.kinematics.SwerveModulePosition; 036import edu.wpi.first.math.kinematics.SwerveModuleState; 037import edu.wpi.first.math.numbers.N1; 038import edu.wpi.first.math.numbers.N3; 039import edu.wpi.first.units.measure.Angle; 040import edu.wpi.first.units.measure.AngularVelocity; 041import edu.wpi.first.wpilibj.Threads; 042import edu.wpi.first.wpilibj.Timer; 043 044/** 045 * Swerve Drive class utilizing CTR Electronics' Phoenix 6 API. 046 * <p> 047 * This class handles the kinematics, configuration, and odometry of a 048 * swerve drive utilizing CTR Electronics devices. We recommend 049 * that users use the Swerve Mechanism Generator in Tuner X to create 050 * a template project that demonstrates how to use this class. 051 * <p> 052 * This class will construct the hardware devices internally, so the user 053 * only specifies the constants (IDs, PID gains, gear ratios, etc). 054 * Getters for these hardware devices are available. 055 * <p> 056 * If using the generator, the order in which modules are constructed is 057 * Front Left, Front Right, Back Left, Back Right. This means if you need 058 * the Back Left module, call {@code getModule(2);} to get the 3rd index 059 * (0-indexed) module, corresponding to the Back Left module. 060 */ 061public class LegacySwerveDrivetrain { 062 protected final boolean IsOnCANFD; 063 protected final CANBus m_canbus; 064 065 protected final double UpdateFrequency; 066 protected final int ModuleCount; 067 protected final LegacySwerveModule[] Modules; 068 069 protected final Pigeon2 m_pigeon2; 070 protected final StatusSignal<Angle> m_yawGetter; 071 protected final StatusSignal<AngularVelocity> m_angularVelocity; 072 073 protected SwerveDriveKinematics m_kinematics; 074 protected SwerveDrivePoseEstimator m_odometry; 075 protected SwerveModulePosition[] m_modulePositions; 076 protected SwerveModuleState[] m_moduleStates; 077 protected Translation2d[] m_moduleLocations; 078 protected OdometryThread m_odometryThread; 079 protected Rotation2d m_fieldRelativeOffset; 080 protected Rotation2d m_operatorForwardDirection; 081 082 protected LegacySwerveRequest m_requestToApply = new LegacySwerveRequest.Idle(); 083 protected LegacySwerveControlRequestParameters m_requestParameters = new LegacySwerveControlRequestParameters(); 084 085 protected final ReadWriteLock m_stateLock = new ReentrantReadWriteLock(); 086 087 protected final LegacySimSwerveDrivetrain m_simDrive; 088 089 /** 090 * Plain-Old-Data class holding the state of the swerve drivetrain. 091 * This encapsulates most data that is relevant for telemetry or 092 * decision-making from the Swerve Drive. 093 */ 094 public static class LegacySwerveDriveState { 095 /** Number of successful data acquisitions */ 096 public int SuccessfulDaqs; 097 /** Number of failed data acquisitions */ 098 public int FailedDaqs; 099 /** The current pose of the robot */ 100 public Pose2d Pose; 101 /** The current velocity of the robot */ 102 public ChassisSpeeds speeds; 103 /** The current module states */ 104 public SwerveModuleState[] ModuleStates; 105 /** The target module states */ 106 public SwerveModuleState[] ModuleTargets; 107 /** The measured odometry update period, in seconds */ 108 public double OdometryPeriod; 109 } 110 111 protected Consumer<LegacySwerveDriveState> m_telemetryFunction = null; 112 protected final LegacySwerveDriveState m_cachedState = new LegacySwerveDriveState(); 113 114 /* Perform swerve module updates in a separate thread to minimize latency */ 115 public class OdometryThread { 116 protected static final int START_THREAD_PRIORITY = 1; // Testing shows 1 (minimum realtime) is sufficient for tighter 117 // odometry loops. 118 // If the odometry period is far away from the desired frequency, 119 // increasing this may help 120 121 protected final Thread m_thread; 122 protected volatile boolean m_running = false; 123 124 protected final BaseStatusSignal[] m_allSignals; 125 126 protected final MedianFilter peakRemover = new MedianFilter(3); 127 protected final LinearFilter lowPass = LinearFilter.movingAverage(50); 128 protected double lastTime = 0; 129 protected double currentTime = 0; 130 protected double averageLoopTime = 0; 131 protected int SuccessfulDaqs = 0; 132 protected int FailedDaqs = 0; 133 134 protected int lastThreadPriority = START_THREAD_PRIORITY; 135 protected volatile int threadPriorityToSet = START_THREAD_PRIORITY; 136 137 public OdometryThread() { 138 m_thread = new Thread(this::run); 139 /* Mark this thread as a "daemon" (background) thread 140 * so it doesn't hold up program shutdown */ 141 m_thread.setDaemon(true); 142 143 /* 4 signals for each module + 2 for Pigeon2 */ 144 m_allSignals = new BaseStatusSignal[(ModuleCount * 4) + 2]; 145 for (int i = 0; i < ModuleCount; ++i) { 146 var signals = Modules[i].getSignals(); 147 m_allSignals[(i * 4) + 0] = signals[0]; 148 m_allSignals[(i * 4) + 1] = signals[1]; 149 m_allSignals[(i * 4) + 2] = signals[2]; 150 m_allSignals[(i * 4) + 3] = signals[3]; 151 } 152 m_allSignals[m_allSignals.length - 2] = m_yawGetter; 153 m_allSignals[m_allSignals.length - 1] = m_angularVelocity; 154 } 155 156 /** 157 * Starts the odometry thread. 158 */ 159 public void start() { 160 m_running = true; 161 m_thread.start(); 162 } 163 164 /** 165 * Stops the odometry thread. 166 */ 167 public void stop() { 168 stop(0); 169 } 170 171 /** 172 * Stops the odometry thread with a timeout. 173 * 174 * @param millis The time to wait in milliseconds 175 */ 176 public void stop(long millis) { 177 m_running = false; 178 try { 179 m_thread.join(millis); 180 } catch (final InterruptedException ex) { 181 Thread.currentThread().interrupt(); 182 } 183 } 184 185 public void run() { 186 /* Make sure all signals update at the correct update frequency */ 187 BaseStatusSignal.setUpdateFrequencyForAll(UpdateFrequency, m_allSignals); 188 Threads.setCurrentThreadPriority(true, START_THREAD_PRIORITY); 189 190 /* Run as fast as possible, our signals will control the timing */ 191 while (m_running) { 192 if (!HootReplay.waitForPlaying(0.100)) { 193 continue; 194 } 195 196 /* Synchronously wait for all signals in drivetrain */ 197 /* Wait up to twice the period of the update frequency */ 198 StatusCode status; 199 if (IsOnCANFD) { 200 status = BaseStatusSignal.waitForAll(2.0 / UpdateFrequency, m_allSignals); 201 } else { 202 /* Wait for the signals to update */ 203 Timer.delay(1.0 / UpdateFrequency); 204 status = BaseStatusSignal.refreshAll(m_allSignals); 205 } 206 207 try { 208 m_stateLock.writeLock().lock(); 209 210 lastTime = currentTime; 211 currentTime = Utils.getCurrentTimeSeconds(); 212 /* We don't care about the peaks, as they correspond to GC events, and we want the period generally low passed */ 213 averageLoopTime = lowPass.calculate(peakRemover.calculate(currentTime - lastTime)); 214 215 /* Get status of first element */ 216 if (status.isOK()) { 217 SuccessfulDaqs++; 218 } else { 219 FailedDaqs++; 220 } 221 222 /* Now update odometry */ 223 /* Keep track of the change in azimuth rotations */ 224 for (int i = 0; i < ModuleCount; ++i) { 225 m_modulePositions[i] = Modules[i].getPosition(false); 226 m_moduleStates[i] = Modules[i].getCurrentState(); 227 } 228 double yawDegrees = BaseStatusSignal.getLatencyCompensatedValueAsDouble( 229 m_yawGetter, m_angularVelocity); 230 231 /* Keep track of previous and current pose to account for the carpet vector */ 232 m_odometry.update(Rotation2d.fromDegrees(yawDegrees), m_modulePositions); 233 234 ChassisSpeeds speeds = m_kinematics.toChassisSpeeds(m_moduleStates); 235 236 /* And now that we've got the new odometry, update the controls */ 237 m_requestParameters.currentPose = m_odometry.getEstimatedPosition() 238 .relativeTo(new Pose2d(0, 0, m_fieldRelativeOffset)); 239 m_requestParameters.kinematics = m_kinematics; 240 m_requestParameters.swervePositions = m_moduleLocations; 241 m_requestParameters.currentChassisSpeed = speeds; 242 m_requestParameters.timestamp = currentTime; 243 m_requestParameters.updatePeriod = 1.0 / UpdateFrequency; 244 m_requestParameters.operatorForwardDirection = m_operatorForwardDirection; 245 246 m_requestToApply.apply(m_requestParameters, Modules); 247 248 /* Update our cached state with the newly updated data */ 249 m_cachedState.FailedDaqs = FailedDaqs; 250 m_cachedState.SuccessfulDaqs = SuccessfulDaqs; 251 m_cachedState.Pose = m_odometry.getEstimatedPosition(); 252 m_cachedState.speeds = speeds; 253 m_cachedState.OdometryPeriod = averageLoopTime; 254 255 if (m_cachedState.ModuleStates == null) { 256 m_cachedState.ModuleStates = new SwerveModuleState[Modules.length]; 257 } 258 if (m_cachedState.ModuleTargets == null) { 259 m_cachedState.ModuleTargets = new SwerveModuleState[Modules.length]; 260 } 261 for (int i = 0; i < Modules.length; ++i) { 262 m_cachedState.ModuleStates[i] = Modules[i].getCurrentState(); 263 m_cachedState.ModuleTargets[i] = Modules[i].getTargetState(); 264 } 265 266 if (m_telemetryFunction != null) { 267 /* Log our state */ 268 m_telemetryFunction.accept(m_cachedState); 269 } 270 } finally { 271 m_stateLock.writeLock().unlock(); 272 } 273 274 /** 275 * This is inherently synchronous, since lastThreadPriority 276 * is only written here and threadPriorityToSet is only read here 277 */ 278 if (threadPriorityToSet != lastThreadPriority) { 279 Threads.setCurrentThreadPriority(true, threadPriorityToSet); 280 lastThreadPriority = threadPriorityToSet; 281 } 282 } 283 } 284 285 public boolean odometryIsValid() { 286 return SuccessfulDaqs > 2; // Wait at least 3 daqs before saying the odometry is valid 287 } 288 289 /** 290 * Sets the DAQ thread priority to a real time priority under the specified priority level 291 * 292 * @param priority Priority level to set the DAQ thread to. 293 * This is a value between 0 and 99, with 99 indicating higher priority and 0 indicating lower priority. 294 */ 295 public void setThreadPriority(int priority) { 296 threadPriorityToSet = priority; 297 } 298 } 299 300 protected boolean checkIsOnCanFD() { 301 return m_canbus.isNetworkFD(); 302 } 303 304 /** 305 * Constructs a CTRSwerveDrivetrain using the specified constants. 306 * <p> 307 * This constructs the underlying hardware devices, so user should not construct 308 * the devices themselves. If they need the devices, they can access them 309 * through 310 * getters in the classes. 311 * 312 * @param driveTrainConstants Drivetrain-wide constants for the swerve drive 313 * @param modules Constants for each specific module 314 */ 315 public LegacySwerveDrivetrain(LegacySwerveDrivetrainConstants driveTrainConstants, LegacySwerveModuleConstants... modules) { 316 this(driveTrainConstants, 0, modules); 317 } 318 /** 319 * Constructs a CTRSwerveDrivetrain using the specified constants. 320 * <p> 321 * This constructs the underlying hardware devices, so user should not construct 322 * the devices themselves. If they need the devices, they can access them 323 * through 324 * getters in the classes. 325 * 326 * @param driveTrainConstants Drivetrain-wide constants for the swerve drive 327 * @param OdometryUpdateFrequency The frequency to run the odometry loop. If 328 * unspecified, this is 250 Hz on CAN FD, and 329 * 100 Hz on CAN 2.0. 330 * @param modules Constants for each specific module 331 */ 332 public LegacySwerveDrivetrain( 333 LegacySwerveDrivetrainConstants driveTrainConstants, double OdometryUpdateFrequency, 334 LegacySwerveModuleConstants... modules) { 335 this(driveTrainConstants, OdometryUpdateFrequency, 336 VecBuilder.fill(0.1, 0.1, 0.1), 337 VecBuilder.fill(0.9, 0.9, 0.9), 338 modules); 339 } 340 341 /** 342 * Constructs a CTRSwerveDrivetrain using the specified constants. 343 * <p> 344 * This constructs the underlying hardware devices, so user should not construct 345 * the devices themselves. If they need the devices, they can access them 346 * through 347 * getters in the classes. 348 * 349 * @param driveTrainConstants Drivetrain-wide constants for the swerve drive 350 * @param OdometryUpdateFrequency The frequency to run the odometry loop. If 351 * unspecified, this is 250 Hz on CAN FD, and 352 * 100 Hz on CAN 2.0. 353 * @param odometryStandardDeviation The standard deviation for odometry calculation 354 * @param visionStandardDeviation The standard deviation for vision calculation 355 * @param modules Constants for each specific module 356 */ 357 public LegacySwerveDrivetrain( 358 LegacySwerveDrivetrainConstants driveTrainConstants, double OdometryUpdateFrequency, 359 Matrix<N3, N1> odometryStandardDeviation, Matrix<N3, N1> visionStandardDeviation, 360 LegacySwerveModuleConstants... modules) { 361 ModuleCount = modules.length; 362 363 m_pigeon2 = new Pigeon2(driveTrainConstants.Pigeon2Id, driveTrainConstants.CANbusName); 364 m_yawGetter = m_pigeon2.getYaw().clone(); 365 m_angularVelocity = m_pigeon2.getAngularVelocityZWorld().clone(); 366 367 if (driveTrainConstants.Pigeon2Configs != null) { 368 StatusCode response = m_pigeon2.getConfigurator().apply(driveTrainConstants.Pigeon2Configs); 369 if (!response.isOK()) { 370 System.out.println( 371 "Pigeon2 ID " + m_pigeon2.getDeviceID() + " failed config with error: " + response.toString()); 372 } 373 } 374 375 Modules = new LegacySwerveModule[ModuleCount]; 376 m_modulePositions = new SwerveModulePosition[ModuleCount]; 377 m_moduleStates = new SwerveModuleState[ModuleCount]; 378 m_moduleLocations = new Translation2d[ModuleCount]; 379 380 int iteration = 0; 381 for (LegacySwerveModuleConstants module : modules) { 382 Modules[iteration] = new LegacySwerveModule(module, driveTrainConstants.CANbusName); 383 m_moduleLocations[iteration] = new Translation2d(module.LocationX, module.LocationY); 384 m_modulePositions[iteration] = Modules[iteration].getPosition(true); 385 m_moduleStates[iteration] = Modules[iteration].getCurrentState(); 386 if (m_requestParameters.maxSpeedMps == 0 || module.SpeedAt12VoltsMps < m_requestParameters.maxSpeedMps) { 387 m_requestParameters.maxSpeedMps = module.SpeedAt12VoltsMps; 388 } 389 390 iteration++; 391 } 392 m_kinematics = new SwerveDriveKinematics(m_moduleLocations); 393 m_odometry = new SwerveDrivePoseEstimator(m_kinematics, new Rotation2d(m_yawGetter.refresh().getValue()), m_modulePositions, new Pose2d(), odometryStandardDeviation, visionStandardDeviation); 394 395 m_fieldRelativeOffset = new Rotation2d(); 396 m_operatorForwardDirection = new Rotation2d(); 397 398 m_simDrive = new LegacySimSwerveDrivetrain(m_moduleLocations, m_pigeon2, driveTrainConstants, modules); 399 400 m_canbus = new CANBus(driveTrainConstants.CANbusName); 401 IsOnCANFD = checkIsOnCanFD(); 402 if (OdometryUpdateFrequency == 0) { 403 UpdateFrequency = IsOnCANFD ? 250 : 100; 404 } else { 405 UpdateFrequency = OdometryUpdateFrequency; 406 } 407 408 m_odometryThread = new OdometryThread(); 409 m_odometryThread.start(); 410 411 PlatformJNI.JNI_ReportLegacySwerve(); 412 } 413 414 /** 415 * Gets a reference to the data acquisition thread. 416 * 417 * @return DAQ thread 418 */ 419 public OdometryThread getDaqThread() { 420 return m_odometryThread; 421 } 422 423 /** 424 * Applies the specified control request to this swerve drivetrain. 425 * 426 * @param request Request to apply 427 */ 428 public void setControl(LegacySwerveRequest request) { 429 try { 430 m_stateLock.writeLock().lock(); 431 m_requestToApply = request; 432 } finally { 433 m_stateLock.writeLock().unlock(); 434 } 435 } 436 437 /** 438 * Configures the neutral mode to use for all modules' drive motors. 439 * 440 * @param neutralMode The drive motor neutral mode 441 * @return Status code of the first failed config call, or OK if all succeeded 442 */ 443 public StatusCode configNeutralMode(NeutralModeValue neutralMode) { 444 var status = StatusCode.OK; 445 for (var module : Modules) { 446 var moduleStatus = module.configNeutralMode(neutralMode); 447 if (status.isOK()) { 448 status = moduleStatus; 449 } 450 } 451 return status; 452 } 453 454 /** 455 * Zero's this swerve drive's odometry entirely. 456 * <p> 457 * This will zero the entire odometry, and place the robot at 0,0 458 */ 459 public void tareEverything() { 460 try { 461 m_stateLock.writeLock().lock(); 462 463 for (int i = 0; i < ModuleCount; ++i) { 464 Modules[i].resetPosition(); 465 m_modulePositions[i] = Modules[i].getPosition(true); 466 } 467 m_odometry.resetPosition(Rotation2d.fromDegrees(m_yawGetter.getValueAsDouble()), m_modulePositions, new Pose2d()); 468 } finally { 469 m_stateLock.writeLock().unlock(); 470 } 471 } 472 473 /** 474 * Takes the current orientation of the robot and makes it X forward for 475 * field-relative 476 * maneuvers. 477 */ 478 public void seedFieldRelative() { 479 try { 480 m_stateLock.writeLock().lock(); 481 m_fieldRelativeOffset = m_cachedState.Pose.getRotation(); 482 } finally { 483 m_stateLock.writeLock().unlock(); 484 } 485 } 486 487 /** 488 * Takes the {@link LegacySwerveRequest.ForwardReference#RedAlliance} perpective direction 489 * and treats it as the forward direction for 490 * {@link LegacySwerveRequest.ForwardReference#OperatorPerspective}. 491 * <p> 492 * If the operator is in the Blue Alliance Station, this should be 0 degrees. 493 * If the operator is in the Red Alliance Station, this should be 180 degrees. 494 * 495 * @param fieldDirection Heading indicating which direction is forward from 496 * the {@link LegacySwerveRequest.ForwardReference#RedAlliance} perspective. 497 */ 498 public void setOperatorPerspectiveForward(Rotation2d fieldDirection) { 499 try { 500 m_stateLock.writeLock().lock(); 501 m_operatorForwardDirection = fieldDirection; 502 } finally { 503 m_stateLock.writeLock().unlock(); 504 } 505 } 506 507 /** 508 * Takes the specified location and makes it the current pose for 509 * field-relative maneuvers 510 * 511 * @param location Pose to make the current pose at. 512 */ 513 public void seedFieldRelative(Pose2d location) { 514 try { 515 m_stateLock.writeLock().lock(); 516 517 m_odometry.resetPosition(Rotation2d.fromDegrees(m_yawGetter.getValueAsDouble()), m_modulePositions, location); 518 /* We need to update our cached pose immediately so that race conditions don't happen */ 519 m_cachedState.Pose = location; 520 } finally { 521 m_stateLock.writeLock().unlock(); 522 } 523 } 524 525 /** 526 * Check if the odometry is currently valid 527 * 528 * @return True if odometry is valid 529 */ 530 public boolean odometryIsValid() { 531 try { 532 m_stateLock.readLock().lock(); 533 return m_odometryThread.odometryIsValid(); 534 } finally { 535 m_stateLock.readLock().unlock(); 536 } 537 } 538 539 /** 540 * Get a reference to the module at the specified index. 541 * The index corresponds to the module described in the constructor 542 * 543 * @param index Which module to get 544 * @return Reference to SwerveModule 545 */ 546 public LegacySwerveModule getModule(int index) { 547 if (index >= Modules.length) 548 return null; 549 return Modules[index]; 550 } 551 552 /** 553 * Gets the current state of the swerve drivetrain. 554 * 555 * @return Current state of the drivetrain 556 */ 557 public LegacySwerveDriveState getState() { 558 try { 559 m_stateLock.readLock().lock(); 560 return m_cachedState; 561 } finally { 562 m_stateLock.readLock().unlock(); 563 } 564 } 565 566 /** 567 * Gets the current orientation of the robot as a {@link Rotation3d} from 568 * the Pigeon 2 quaternion values. 569 * 570 * @return The robot orientation as a {@link Rotation3d} 571 */ 572 public Rotation3d getRotation3d() { 573 return m_pigeon2.getRotation3d(); 574 } 575 576 /** 577 * Adds a vision measurement to the Kalman Filter. This will correct the 578 * odometry pose estimate 579 * while still accounting for measurement noise. 580 * 581 * <p> 582 * This method can be called as infrequently as you want, as long as you are 583 * calling {@link 584 * SwerveDrivePoseEstimator#update} every loop. 585 * 586 * <p> 587 * To promote stability of the pose estimate and make it robust to bad vision 588 * data, we 589 * recommend only adding vision measurements that are already within one meter 590 * or so of the 591 * current pose estimate. 592 * 593 * <p> 594 * Note that the vision measurement standard deviations passed into this method 595 * will continue 596 * to apply to future measurements until a subsequent call to {@link 597 * SwerveDrivePoseEstimator#setVisionMeasurementStdDevs(Matrix)} or this method. 598 * 599 * @param visionRobotPoseMeters The pose of the robot as measured by the 600 * vision camera. 601 * @param timestampSeconds The timestamp of the vision measurement in 602 * seconds. Note that if you 603 * don't use your own time source by calling 604 * {@link 605 * SwerveDrivePoseEstimator#updateWithTime(double,Rotation2d,SwerveModulePosition[])}, 606 * then 607 * you must use a timestamp with an epoch since 608 * FPGA startup (i.e., the epoch of this 609 * timestamp is the same epoch as 610 * {@link edu.wpi.first.wpilibj.Timer#getFPGATimestamp()}). 611 * This means that you should use 612 * {@link edu.wpi.first.wpilibj.Timer#getFPGATimestamp()} 613 * as 614 * your time source in this case. 615 * @param visionMeasurementStdDevs Standard deviations of the vision pose 616 * measurement (x position 617 * in meters, y position in meters, and heading 618 * in radians). Increase these numbers to trust 619 * the vision pose measurement less. 620 */ 621 public void addVisionMeasurement( 622 Pose2d visionRobotPoseMeters, 623 double timestampSeconds, 624 Matrix<N3, N1> visionMeasurementStdDevs) { 625 try { 626 m_stateLock.writeLock().lock(); 627 m_odometry.addVisionMeasurement(visionRobotPoseMeters, timestampSeconds, visionMeasurementStdDevs); 628 } finally { 629 m_stateLock.writeLock().unlock(); 630 } 631 } 632 633 /** 634 * Adds a vision measurement to the Kalman Filter. This will correct the 635 * odometry pose estimate 636 * while still accounting for measurement noise. 637 * 638 * <p> 639 * This method can be called as infrequently as you want, as long as you are 640 * calling {@link 641 * SwerveDrivePoseEstimator#update} every loop. 642 * 643 * <p> 644 * To promote stability of the pose estimate and make it robust to bad vision 645 * data, we 646 * recommend only adding vision measurements that are already within one meter 647 * or so of the 648 * current pose estimate. 649 * 650 * @param visionRobotPoseMeters The pose of the robot as measured by the vision 651 * camera. 652 * @param timestampSeconds The timestamp of the vision measurement in 653 * seconds. Note that if you 654 * don't use your own time source by calling {@link 655 * SwerveDrivePoseEstimator#updateWithTime(double,Rotation2d,SwerveModulePosition[])} 656 * then you 657 * must use a timestamp with an epoch since FPGA 658 * startup (i.e., the epoch of this timestamp is 659 * the same epoch as 660 * {@link edu.wpi.first.wpilibj.Timer#getFPGATimestamp()}.) 661 * This means that 662 * you should use 663 * {@link edu.wpi.first.wpilibj.Timer#getFPGATimestamp()} 664 * as your time source 665 * or sync the epochs. 666 */ 667 public void addVisionMeasurement(Pose2d visionRobotPoseMeters, double timestampSeconds) { 668 try { 669 m_stateLock.writeLock().lock(); 670 m_odometry.addVisionMeasurement(visionRobotPoseMeters, timestampSeconds); 671 } finally { 672 m_stateLock.writeLock().unlock(); 673 } 674 } 675 676 /** 677 * Sets the pose estimator's trust of global measurements. This might be used to 678 * change trust in 679 * vision measurements after the autonomous period, or to change trust as 680 * distance to a vision 681 * target increases. 682 * 683 * @param visionMeasurementStdDevs Standard deviations of the vision 684 * measurements. Increase these 685 * numbers to trust global measurements from 686 * vision less. This matrix is in the form [x, 687 * y, 688 * theta]ᵀ, with units in meters and radians. 689 */ 690 public void setVisionMeasurementStdDevs(Matrix<N3, N1> visionMeasurementStdDevs) { 691 try { 692 m_stateLock.writeLock().lock(); 693 m_odometry.setVisionMeasurementStdDevs(visionMeasurementStdDevs); 694 } finally { 695 m_stateLock.writeLock().unlock(); 696 } 697 } 698 699 /** 700 * Updates all the simulation state variables for this 701 * drivetrain class. User provides the update variables for the simulation. 702 * 703 * @param dtSeconds time since last update call 704 * @param supplyVoltage voltage as seen at the motor controllers 705 */ 706 public void updateSimState(double dtSeconds, double supplyVoltage) { 707 m_simDrive.update(dtSeconds, supplyVoltage, Modules); 708 } 709 710 711 /** 712 * Register the specified lambda to be executed whenever our SwerveDriveState function 713 * is updated in our odometry thread. 714 * <p> 715 * It is imperative that this function is cheap, as it will be executed along with 716 * the odometry call, and if this takes a long time, it may negatively impact 717 * the odometry of this stack. 718 * <p> 719 * This can also be used for logging data if the function performs logging instead of telemetry 720 * 721 * @param telemetryFunction Function to call for telemetry or logging 722 */ 723 public void registerTelemetry(Consumer<LegacySwerveDriveState> telemetryFunction) { 724 try { 725 m_stateLock.writeLock().lock(); 726 m_telemetryFunction = telemetryFunction; 727 } finally { 728 m_stateLock.writeLock().unlock(); 729 } 730 } 731 732 /** 733 * Gets this drivetrain's Pigeon 2 reference. 734 * <p> 735 * This should be used only to access signals and change configurations that the 736 * swerve drivetrain does not configure itself. 737 * 738 * @return This drivetrain's Pigeon 2 reference 739 */ 740 public Pigeon2 getPigeon2() { 741 return m_pigeon2; 742 } 743}