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}