001/************************* PROJECT RON *************************/
002/* Copyright (c) 2026 StuyPulse Robotics. All rights reserved. */
003/* Use of this source code is governed by an MIT-style license */
004/* that can be found in the repository LICENSE file.           */
005/***************************************************************/
006package com.stuypulse.robot.subsystems.swerve;
007
008import static edu.wpi.first.units.Units.Second;
009import static edu.wpi.first.units.Units.Seconds;
010import static edu.wpi.first.units.Units.Volts;
011
012import org.ironmaple.simulation.drivesims.SwerveDriveSimulation;
013
014import com.ctre.phoenix6.SignalLogger;
015import com.ctre.phoenix6.Utils;
016import com.ctre.phoenix6.swerve.SwerveDrivetrainConstants;
017import com.ctre.phoenix6.swerve.SwerveModule.DriveRequestType;
018import com.ctre.phoenix6.swerve.SwerveModuleConstants;
019import com.ctre.phoenix6.swerve.SwerveRequest;
020import com.pathplanner.lib.auto.AutoBuilder;
021import com.pathplanner.lib.config.RobotConfig;
022import com.pathplanner.lib.controllers.PPHolonomicDriveController;
023import com.pathplanner.lib.path.PathPlannerPath;
024import com.pathplanner.lib.util.PathPlannerLogging;
025import com.stuypulse.robot.Robot;
026import com.stuypulse.robot.constants.Field;
027import com.stuypulse.robot.constants.Gains;
028import com.stuypulse.robot.constants.Settings;
029import com.stuypulse.robot.subsystems.swerve.TunerConstants.TunerSwerveDrivetrain;
030import com.stuypulse.robot.util.simulation.MapleSimSwerveDrivetrain;
031import com.stuypulse.robot.util.simulation.SimulationConstants;
032
033import dev.doglog.DogLog;
034import edu.wpi.first.math.Matrix;
035import edu.wpi.first.math.geometry.Pose2d;
036import edu.wpi.first.math.geometry.Rotation2d;
037import edu.wpi.first.math.geometry.Translation2d;
038import edu.wpi.first.math.geometry.Twist2d;
039import edu.wpi.first.math.kinematics.ChassisSpeeds;
040import edu.wpi.first.math.kinematics.SwerveModuleState;
041import edu.wpi.first.math.numbers.N1;
042import edu.wpi.first.math.numbers.N3;
043import edu.wpi.first.math.system.plant.DCMotor;
044import edu.wpi.first.networktables.NetworkTableInstance;
045import edu.wpi.first.networktables.StructArrayPublisher;
046import edu.wpi.first.networktables.StructPublisher;
047import edu.wpi.first.wpilibj.Notifier;
048import edu.wpi.first.wpilibj.Timer;
049import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard;
050import edu.wpi.first.wpilibj2.command.Command;
051import edu.wpi.first.wpilibj2.command.Subsystem;
052import edu.wpi.first.wpilibj2.command.sysid.SysIdRoutine;
053
054/**
055 * Class that extends the Phoenix 6 SwerveDrivetrain class and implements
056 * Subsystem so it can easily
057 * be used in command-based projects.
058 */
059public class CommandSwerveDrivetrain extends TunerSwerveDrivetrain implements Subsystem {
060
061    private static final CommandSwerveDrivetrain instance;
062
063    static {
064        instance = TunerConstants.createDrivetrain();
065    }
066
067    public static CommandSwerveDrivetrain getInstance() {
068        return instance;
069    }
070
071    // 5 ms
072    private static final double kSimLoopPeriod = 0.005;
073
074    private Notifier m_simNotifier = null;
075
076    private double m_lastSimTime;
077
078    /* Swerve requests to apply during SysId characterization */
079    private final SwerveRequest.SysIdSwerveTranslation m_moduleTranslationCharacterization = new SwerveRequest.SysIdSwerveTranslation();
080
081    private final SwerveRequest.SysIdSwerveSteerGains m_steerCharacterization = new SwerveRequest.SysIdSwerveSteerGains();
082
083    private final SwerveRequest.SysIdSwerveRotation m_rotationCharacterization = new SwerveRequest.SysIdSwerveRotation();
084
085    private final SwerveRequest.FieldCentric fieldCentricRequest = new SwerveRequest.FieldCentric()
086            .withDriveRequestType(DriveRequestType.OpenLoopVoltage)
087            .withDeadband(Settings.Swerve.MODULE_VELOCITY_DEADBAND_M_PER_S)
088            .withRotationalDeadband(Settings.Swerve.ROTATIONAL_DEADBAND_RAD_PER_S)
089            .withDesaturateWheelSpeeds(true);
090
091    private final SwerveRequest.RobotCentric robotCentricRequest = new SwerveRequest.RobotCentric()
092            .withDriveRequestType(DriveRequestType.OpenLoopVoltage)
093            .withDeadband(Settings.Swerve.MODULE_VELOCITY_DEADBAND_M_PER_S)
094            .withRotationalDeadband(Settings.Swerve.ROTATIONAL_DEADBAND_RAD_PER_S)
095            .withDesaturateWheelSpeeds(true);
096
097    public SwerveRequest.FieldCentric getFieldCentricSwerveRequest() {
098        return this.fieldCentricRequest;
099    }
100
101    public SwerveRequest.RobotCentric getRobotCentricSwerveRequest() {
102        return this.robotCentricRequest;
103    }
104
105    /*
106     * SysId routine for characterizing module translation. This is used to find PID
107     * gains for the drive motors.
108     */
109    private final SysIdRoutine m_sysIdRoutineModuleTranslation = new SysIdRoutine(
110            new SysIdRoutine.Config( // Use default ramp rate (1 V/s)
111                    null, // Reduce dynamic step voltage to 4 V to prevent brownout
112                    Volts.of(4), // Use default timeout (10 s)
113                    null, // Log state with SignalLogger class
114                    state -> SignalLogger.writeString("SysIdModuleTranslation_State",
115                            state.toString())),
116            new SysIdRoutine.Mechanism(
117                    output -> setControl(m_moduleTranslationCharacterization.withVolts(output)),
118                    null,
119                    this));
120
121    /*
122     * SysId routine for characterizing chassis translation. This is used to find
123     * PID gains PID to pose.
124     */
125    private final SysIdRoutine m_sysIdRoutineChassisTranslation = new SysIdRoutine(
126            new SysIdRoutine.Config(
127                    /* This is in meters per second², but SysId only supports "volts per second" */
128                    Volts.of(1)
129                            .per(Second), /*
130                                           * This is in meters per second, but SysId only
131                                           * supports "volts"
132                                           */
133                    Volts.of(
134                            Settings.Swerve.Constraints.MAX_VELOCITY_M_PER_S), // Use
135                                                                               // default
136                                                                               // timeout
137                                                                               // (10 s)
138                    null, // Log state with SignalLogger class
139                    state -> SignalLogger.writeString("SysIdChassisTranslation_State",
140                            state.toString())),
141            new SysIdRoutine.Mechanism(
142                    output -> {
143                        /*
144                         * output is actually meters per second, but SysId only supports "volts"
145                         */
146                        setControl(
147                                getFieldCentricSwerveRequest()
148                                        .withVelocityX(output.in(Volts))
149                                        .withVelocityY(0)
150                                        .withRotationalRate(0));
151                        /* also log the requested output for SysId */
152                        SignalLogger.writeDouble("Target X Velocity ('voltage')",
153                                output.in(Volts));
154                        SignalLogger.writeDouble("X Position", getPose().getX());
155                        SignalLogger.writeDouble(
156                                "X Velocity",
157                                getChassisSpeeds().vxMetersPerSecond
158                                        * getPose().getRotation().getCos());
159                    },
160                    null,
161                    this));
162
163    /*
164     * SysId routine for characterizing steer. This is used to find PID gains for
165     * the steer motors.
166     */
167    private final SysIdRoutine m_sysIdRoutineSteer = new SysIdRoutine(
168            new SysIdRoutine.Config( // Use default ramp rate (1 V/s)
169                    null, // Use dynamic voltage of 7 V
170                    Volts.of(7), // Use default timeout (10 s)
171                    null, // Log state with SignalLogger class
172                    state -> SignalLogger.writeString("SysIdSteer_State", state.toString())),
173            new SysIdRoutine.Mechanism(
174                    volts -> setControl(m_steerCharacterization.withVolts(volts)), null, this));
175
176    /*
177     * SysId routine for characterizing rotation.
178     * This is used to find PID gains for the FieldCentricFacingAngle
179     * HeadingController.
180     * See the documentation of SwerveRequest.SysIdSwerveRotation for info on
181     * importing the log to SysId.
182     */
183    private final SysIdRoutine m_sysIdRoutineRotation = new SysIdRoutine(
184            new SysIdRoutine.Config(
185                    /* This is in radians per second², but SysId only supports "volts per second" */
186                    Volts.of(Math.PI / 6)
187                            .per(Second), /*
188                                           * This is in radians per second, but SysId only
189                                           * supports "volts"
190                                           */
191                    Volts.of(Math.PI), // Use default timeout (10 s)
192                    null, // Log state with SignalLogger class
193                    state -> SignalLogger.writeString("SysIdRotation_State", state.toString())),
194            new SysIdRoutine.Mechanism(
195                    output -> {
196                        /*
197                         * output is actually radians per second, but SysId only supports
198                         * "volts"
199                         */
200                        setControl(m_rotationCharacterization
201                                .withRotationalRate(output.in(Volts)));
202                        /* also log the requested output for SysId */
203                        SignalLogger.writeDouble("Rotational_Target_Rate ('voltage')",
204                                output.in(Volts));
205                        SignalLogger.writeDouble(
206                                "Rotational Position",
207                                getPose().getRotation().getRadians());
208                        SignalLogger.writeDouble(
209                                "Rotational_Velocity",
210                                getState().Speeds.omegaRadiansPerSecond);
211                    },
212                    null,
213                    this));
214
215    /* The SysId routine to test */
216    private SysIdRoutine m_sysIdRoutineToApply = m_sysIdRoutineModuleTranslation;
217
218    /**
219     * Constructs a CTRE SwerveDrivetrain using the specified constants.
220     *
221     * <p>
222     * This constructs the underlying hardware devices, so users should not
223     * construct the devices
224     * themselves. If they need the devices, they can access them through getters in
225     * the classes.
226     *
227     * @param drivetrainConstants Drivetrain-wide constants for the swerve drive
228     * @param modules             Constants for each specific module
229     */
230    // With all constructors the maple sim module constant regulation is not done if
231    // the robot is not
232    // real
233    protected CommandSwerveDrivetrain(
234            SwerveDrivetrainConstants drivetrainConstants, SwerveModuleConstants<?, ?, ?>... modules) {
235        super(
236                drivetrainConstants,
237                MapleSimSwerveDrivetrain.regulateModuleConstantsForSimulation(modules));
238        if (Utils.isSimulation()) {
239            startSimThread();
240        }
241    }
242
243    /**
244     * Constructs a CTRE SwerveDrivetrain using the specified constants.
245     *
246     * <p>
247     * This constructs the underlying hardware devices, so users should not
248     * construct the devices
249     * themselves. If they need the devices, they can access them through getters in
250     * the classes.
251     *
252     * @param drivetrainConstants     Drivetrain-wide constants for the swerve drive
253     * @param odometryUpdateFrequency The frequency to run the odometry loop. If
254     *                                unspecified or set to
255     *                                0 Hz, this is 250 Hz on CAN FD, and 100 Hz on
256     *                                CAN 2.0.
257     * @param modules                 Constants for each specific module
258     */
259    private CommandSwerveDrivetrain(
260            SwerveDrivetrainConstants drivetrainConstants,
261            double odometryUpdateFrequency,
262            SwerveModuleConstants<?, ?, ?>... modules) {
263        super(
264                drivetrainConstants,
265                odometryUpdateFrequency,
266                MapleSimSwerveDrivetrain.regulateModuleConstantsForSimulation(modules));
267        if (Utils.isSimulation()) {
268            startSimThread();
269        }
270    }
271
272    /**
273     * Constructs a CTRE SwerveDrivetrain using the specified constants.
274     *
275     * <p>
276     * This constructs the underlying hardware devices, so users should not
277     * construct the devices
278     * themselves. If they need the devices, they can access them through getters in
279     * the classes.
280     *
281     * @param drivetrainConstants       Drivetrain-wide constants for the swerve
282     *                                  drive
283     * @param odometryUpdateFrequency   The frequency to run the odometry loop. If
284     *                                  unspecified or set to
285     *                                  0 Hz, this is 250 Hz on CAN FD, and 100 Hz
286     *                                  on CAN 2.0.
287     * @param odometryStandardDeviation The standard deviation for odometry
288     *                                  calculation in the form
289     *                                  [x, y, theta]ᵀ, with units in meters and
290     *                                  radians
291     * @param visionStandardDeviation   The standard deviation for vision
292     *                                  calculation in the form [x, y,
293     *                                  theta]ᵀ, with units in meters and radians
294     * @param modules                   Constants for each specific module
295     */
296    private CommandSwerveDrivetrain(
297            SwerveDrivetrainConstants drivetrainConstants,
298            double odometryUpdateFrequency,
299            Matrix<N3, N1> odometryStandardDeviation,
300            Matrix<N3, N1> visionStandardDeviation,
301            SwerveModuleConstants<?, ?, ?>... modules) {
302        super(
303                drivetrainConstants,
304                odometryUpdateFrequency,
305                odometryStandardDeviation,
306                visionStandardDeviation,
307                MapleSimSwerveDrivetrain.regulateModuleConstantsForSimulation(modules));
308        if (Utils.isSimulation()) {
309            startSimThread();
310        }
311    }
312
313    @Override
314    public void setControl(SwerveRequest request) {
315        if (Settings.EnabledSubsystems.SWERVE.get()) {
316            super.setControl(request);
317        } else {
318            super.setControl(new SwerveRequest.Idle());
319        }
320    }
321
322    /**
323     * Runs the SysId Quasistatic test in the given direction for the routine
324     * specified by {@link
325     * #m_sysIdRoutineToApply}.
326     *
327     * @param direction Direction of the SysId Quasistatic test
328     * @return Command to run
329     */
330    public Command sysIdQuasistatic(SysIdRoutine.Direction direction) {
331        return m_sysIdRoutineToApply.quasistatic(direction);
332    }
333
334    /**
335     * Runs the SysId Dynamic test in the given direction for the routine specified
336     * by {@link
337     * #m_sysIdRoutineToApply}.
338     *
339     * @param direction Direction of the SysId Dynamic test
340     * @return Command to run
341     */
342    public Command sysIdDynamic(SysIdRoutine.Direction direction) {
343        return m_sysIdRoutineToApply.dynamic(direction);
344    }
345
346    public Command sysidRotationDynamic(SysIdRoutine.Direction direction) {
347        return m_sysIdRoutineRotation.dynamic(direction);
348    }
349
350    public Command sysidRotationQuasiStatic(SysIdRoutine.Direction direction) {
351        return m_sysIdRoutineRotation.quasistatic(direction);
352    }
353
354    public Command sysidSteerDynamic(SysIdRoutine.Direction direction) {
355        return m_sysIdRoutineSteer.dynamic(direction);
356    }
357
358    public Command sysidSteerQuasistatic(SysIdRoutine.Direction direction) {
359        return m_sysIdRoutineSteer.quasistatic(direction);
360    }
361
362    private MapleSimSwerveDrivetrain mapleSimSwerveDrivetrain = null;
363
364    @SuppressWarnings("unchecked")
365    private void startSimThread() {
366        mapleSimSwerveDrivetrain = new MapleSimSwerveDrivetrain(
367                Seconds.of(kSimLoopPeriod),
368                SimulationConstants.Drivetrain.TOTAL_WEIGHT.get(),
369                SimulationConstants.Drivetrain.LENGTH,
370                SimulationConstants.Drivetrain.WIDTH,
371                DCMotor.getKrakenX60(1),
372                DCMotor.getKrakenX60(1),
373                SimulationConstants.Drivetrain.WHEEL_COF,
374                getModuleLocations(),
375                getPigeon2(),
376                getModules(),
377                TunerConstants.FrontLeft,
378                TunerConstants.FrontRight,
379                TunerConstants.BackLeft,
380                TunerConstants.BackRight);
381        m_simNotifier = new Notifier(mapleSimSwerveDrivetrain::update);
382        m_simNotifier.startPeriodic(kSimLoopPeriod);
383    }
384
385    public SwerveDriveSimulation getMapleSimDrive() {
386        return mapleSimSwerveDrivetrain.mapleSimDrive;
387    }
388
389    /**
390     * Adds a vision measurement to the Kalman Filter. This will correct the
391     * odometry pose estimate
392     * while still accounting for measurement noise.
393     *
394     * @param visionRobotPoseMeters The pose of the robot as measured by the vision
395     *                              camera.
396     * @param timestampSeconds      The timestamp of the vision measurement in
397     *                              seconds.
398     */
399    @Override
400    public void addVisionMeasurement(Pose2d visionRobotPoseMeters, double timestampSeconds) {
401        super.addVisionMeasurement(visionRobotPoseMeters, Utils.fpgaToCurrentTime(timestampSeconds));
402    }
403
404    /**
405     * Adds a vision measurement to the Kalman Filter. This will correct the
406     * odometry pose estimate
407     * while still accounting for measurement noise.
408     *
409     * <p>
410     * Note that the vision measurement standard deviations passed into this method
411     * will continue
412     * to apply to future measurements until a subsequent call to {@link
413     * #setVisionMeasurementStdDevs(Matrgix)} or this method.
414     *
415     * @param visionRobotPoseMeters    The pose of the robot as measured by the
416     *                                 vision camera.
417     * @param timestampSeconds         The timestamp of the vision measurement in
418     *                                 seconds.
419     * @param visionMeasurementStdDevs Standard deviations of the vision pose
420     *                                 measurement in the form
421     *                                 [x, y, theta]ᵀ, with units in meters and
422     *                                 radians.
423     */
424    @Override
425    public void addVisionMeasurement(
426            Pose2d visionRobotPoseMeters,
427            double timestampSeconds,
428            Matrix<N3, N1> visionMeasurementStdDevs) {
429        super.addVisionMeasurement(
430                visionRobotPoseMeters, Utils.fpgaToCurrentTime(timestampSeconds),
431                visionMeasurementStdDevs);
432    }
433
434    public Pose2d getPose() {
435        return getState().Pose;
436    }
437
438    public boolean isAlignedToTarget(Pose2d target) {
439        Pose2d currentPose = getPose();
440        Rotation2d targetAngle = new Rotation2d(
441                Math.atan2(target.getY() - currentPose.getY(), target.getX() - currentPose.getX()));
442        return currentPose.getRotation().minus(targetAngle)
443                .getDegrees() < Settings.Swerve.Alignment.Tolerances.THETA_TOLERANCE.getDegrees();
444    }
445
446    public Pose2d getShooterPose() {
447        // offset is negative because the shooter is behind the robot center
448        return SimulationConstants.Shooter.OFFSETS.applyToPose2d(
449                mapleSimSwerveDrivetrain == null
450                        ? getPose()
451                        : getMapleSimDrive().getSimulatedDriveTrainPose());
452    }
453
454    @Override
455    public void resetPose(Pose2d pose) {
456        if (this.mapleSimSwerveDrivetrain != null)
457            mapleSimSwerveDrivetrain.mapleSimDrive.setSimulationWorldPose(pose);
458        // Wait for simulation to update
459        Timer.delay(0.05);
460        super.resetPose(pose);
461    }
462
463    public void configureAutoBuilder() {
464        try {
465            AutoBuilder.configure(
466                    this::getPose,
467                    this::resetPose,
468                    this::getChassisSpeeds,
469                    this::setChassisSpeeds,
470                    new PPHolonomicDriveController(Gains.Swerve.Alignment.XY,
471                            Gains.Swerve.Alignment.THETA),
472                    RobotConfig.fromGUISettings(),
473                    () -> false,
474                    instance);
475            PathPlannerLogging.setLogActivePathCallback(
476                    (poses) -> {
477                        if (Robot.isBlue()) {
478                            Field.FIELD2D.getObject("path").setPoses(poses);
479                        } else {
480                            Field.FIELD2D.getObject("path").setPoses(
481                                    Field.transformToOppositeAlliance(poses));
482                        }
483                    });
484        } catch (Exception e) {
485            e.printStackTrace();
486        }
487    }
488
489    public Command followPathCommand(String pathName) {
490        try {
491            return followPathCommand(PathPlannerPath.fromPathFile(pathName));
492        } catch (Exception e) {
493            throw new IllegalArgumentException(pathName + " does not exist");
494        }
495    }
496
497    public Command followPathCommand(PathPlannerPath path) {
498        return AutoBuilder.followPath(path);
499    }
500
501    public SwerveModuleState[] getModuleStates() {
502        SwerveModuleState[] moduleStates = new SwerveModuleState[4];
503        for (int i = 0; i < 4; i++) {
504            moduleStates[i] = getModule(i).getCurrentState();
505        }
506        return moduleStates;
507    }
508
509    public ChassisSpeeds getChassisSpeeds() {
510        return getKinematics().toChassisSpeeds(getModuleStates());
511    }
512
513    public Translation2d getFieldRelativeSpeeds() {
514        return new Translation2d(getChassisSpeeds().vxMetersPerSecond, getChassisSpeeds().vyMetersPerSecond)
515                .rotateBy(getPose().getRotation());
516    }
517
518    private void setChassisSpeeds(ChassisSpeeds robotSpeeds) {
519        setControl(
520                new SwerveRequest.RobotCentric()
521                        .withVelocityX(robotSpeeds.vxMetersPerSecond)
522                        .withVelocityY(robotSpeeds.vyMetersPerSecond)
523                        .withRotationalRate(robotSpeeds.omegaRadiansPerSecond));
524    }
525
526    public void drive(Translation2d velocity, double rotation) {
527        ChassisSpeeds speeds = ChassisSpeeds.fromFieldRelativeSpeeds(
528                Robot.isBlue() ? velocity.getY() : -velocity.getY(),
529                Robot.isBlue() ? -velocity.getX() : velocity.getX(),
530                -rotation,
531                getPose().getRotation());
532        Pose2d robotVel = new Pose2d(
533                Settings.DT.in(Seconds) * speeds.vxMetersPerSecond,
534                Settings.DT.in(Seconds) * speeds.vyMetersPerSecond,
535                Rotation2d.fromRadians(Settings.DT.in(Seconds) * speeds.omegaRadiansPerSecond));
536        Twist2d twistVel = new Pose2d().log(robotVel);
537        setChassisSpeeds(
538                new ChassisSpeeds(
539                        twistVel.dx / Settings.DT.in(Seconds),
540                        twistVel.dy / Settings.DT.in(Seconds),
541                        twistVel.dtheta / Settings.DT.in(Seconds)));
542    }
543
544    private final StructPublisher<Pose2d> posePublisher = NetworkTableInstance.getDefault()
545            .getStructTopic("AdvScope/DTPose", Pose2d.struct).publish();
546
547    private final StructPublisher<ChassisSpeeds> chassisPublisher = NetworkTableInstance.getDefault()
548            .getStructTopic("AdvScope/ChassisSpeeds", ChassisSpeeds.struct)
549            .publish();
550
551    private final StructArrayPublisher<SwerveModuleState> modulePublisher = NetworkTableInstance.getDefault()
552            .getStructArrayTopic("AdvScope/SwerveStates", SwerveModuleState.struct)
553            .publish();
554
555    @Override
556    public void periodic() {
557        final Pose2d pose = mapleSimSwerveDrivetrain == null
558                ? getPose()
559                : mapleSimSwerveDrivetrain.mapleSimDrive.getSimulatedDriveTrainPose();
560        if (Settings.DEBUG_MODE) {
561            // under debug flag to avoid log clutter
562            posePublisher.set(pose);
563            chassisPublisher.set(getChassisSpeeds());
564            modulePublisher.set(getModuleStates());
565        }
566        SmartDashboard.putNumber("Swerve/Pose X", getPose().getX());
567        SmartDashboard.putNumber("Swerve/Pose Y", getPose().getY());
568        SmartDashboard.putNumber("Swerve/Pose Theta", getPose().getRotation().getDegrees());
569        DogLog.log("Swerve/Pose/X", getPose().getX());
570        DogLog.log("Swerve/Pose/Y", getPose().getY());
571        DogLog.log("Swerve/Pose/Theta", getPose().getRotation().getDegrees());
572        DogLog.log("Swerve/Pose2D", Robot.isBlue() ? pose : Field.transformToOppositeAlliance(pose));
573        for (int i = 0; i < 4; i++) {
574            DogLog.log(
575                    "Swerve/Modules/Module " + i + "/Speed (m per s)",
576                    getModule(i).getCurrentState().speedMetersPerSecond);
577            DogLog.log(
578                    "Swerve/Modules/Module " + i + "/Target Speed (m per s)",
579                    getModule(i).getTargetState().speedMetersPerSecond);
580            DogLog.log(
581                    "Swerve/Modules/Module " + i + "/Angle (deg)",
582                    getModule(i).getCurrentState().angle.getDegrees() % 360);
583            DogLog.log(
584                    "Swerve/Modules/Module " + i + "/Target Angle (deg)",
585                    getModule(i).getTargetState().angle.getDegrees() % 360);
586        }
587        Field.FIELD2D
588                .getRobotObject()
589                .setPose(Robot.isBlue() ? pose : Field.transformToOppositeAlliance(pose));
590        if (Settings.DEBUG_MODE) {
591            for (int i = 0; i < 4; i++) {
592                DogLog.log(
593                        "Swerve/Modules/Module " + i + "/Stator Current",
594                        getModule(i).getDriveMotor().getStatorCurrent().getValueAsDouble());
595                DogLog.log(
596                        "Swerve/Modules/Module " + i + "/Supply Current",
597                        getModule(i).getDriveMotor().getSupplyCurrent().getValueAsDouble());
598            }
599            DogLog.log(
600                    "Swerve/Velocity Robot Relative X (m per s)",
601                    getChassisSpeeds().vxMetersPerSecond);
602            DogLog.log(
603                    "Swerve/Velocity Robot Relative Y (m per s)",
604                    getChassisSpeeds().vyMetersPerSecond);
605            DogLog.log("Swerve/Velocity Field Relative X (m per s)", getFieldRelativeSpeeds().getX());
606            DogLog.log("Swerve/Field Relative Rotation", getPose().getRotation().getDegrees());
607            DogLog.log("Swerve/Velocity Field Relative Y (m per s)", getFieldRelativeSpeeds().getY());
608            DogLog.log("Swerve/Angular Velocity (rad per s)", getChassisSpeeds().omegaRadiansPerSecond);
609
610            // Distance in meters
611            DogLog.forceNt.log("Swerve/Distance From Hub",
612                    pose.getTranslation().getDistance(Field.getHubPose().getTranslation()));
613            DogLog.forceNt.log("Swerve/Distance From Ferry Zone",
614                    pose.getTranslation().getDistance(Field.getFerryZonePose(pose.getTranslation()).getTranslation()));
615        }
616    }
617}