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 edu.wpi.first.units.Units.Newtons;
010
011import java.util.HashMap;
012import java.util.concurrent.locks.Lock;
013import java.util.concurrent.locks.ReentrantLock;
014
015import com.ctre.phoenix6.StatusCode;
016import com.ctre.phoenix6.configs.CANcoderConfiguration;
017import com.ctre.phoenix6.configs.CANdiConfiguration;
018import com.ctre.phoenix6.configs.TalonFXConfiguration;
019import com.ctre.phoenix6.configs.TalonFXSConfiguration;
020import com.ctre.phoenix6.controls.ControlRequest;
021import com.ctre.phoenix6.hardware.CANcoder;
022import com.ctre.phoenix6.hardware.CANdi;
023import com.ctre.phoenix6.hardware.ParentDevice;
024import com.ctre.phoenix6.hardware.TalonFX;
025import com.ctre.phoenix6.hardware.TalonFXS;
026import com.ctre.phoenix6.hardware.traits.CommonTalon;
027import com.ctre.phoenix6.signals.BrushedMotorWiringValue;
028import com.ctre.phoenix6.signals.ExternalFeedbackSensorSourceValue;
029import com.ctre.phoenix6.signals.FeedbackSensorSourceValue;
030import com.ctre.phoenix6.signals.InvertedValue;
031import com.ctre.phoenix6.signals.MotorArrangementValue;
032import com.ctre.phoenix6.signals.NeutralModeValue;
033import com.ctre.phoenix6.signals.SensorDirectionValue;
034import com.ctre.phoenix6.signals.SensorPhaseValue;
035import com.ctre.phoenix6.swerve.SwerveDrivetrain.DeviceConstructor;
036import com.ctre.phoenix6.swerve.SwerveModuleConstants.ClosedLoopOutputType;
037import com.ctre.phoenix6.swerve.SwerveModuleConstants.DriveMotorArrangement;
038import com.ctre.phoenix6.swerve.SwerveModuleConstants.SteerMotorArrangement;
039import com.ctre.phoenix6.swerve.jni.SwerveJNI;
040
041import edu.wpi.first.math.geometry.Rotation2d;
042import edu.wpi.first.math.kinematics.SwerveModulePosition;
043import edu.wpi.first.math.kinematics.SwerveModuleState;
044import edu.wpi.first.units.measure.Force;
045import edu.wpi.first.wpilibj.DriverStation;
046
047/**
048 * Swerve Module class that encapsulates a swerve module powered by CTR
049 * Electronics devices.
050 * <p>
051 * This class handles the hardware devices and configures them for
052 * swerve module operation using the Phoenix 6 API.
053 * <p>
054 * This class constructs hardware devices internally, so the user
055 * only specifies the constants (IDs, PID gains, gear ratios, etc).
056 * Getters for these hardware devices are available.
057 */
058public class SwerveModule<
059    DriveMotorT extends CommonTalon,
060    SteerMotorT extends CommonTalon,
061    EncoderT extends ParentDevice
062> {
063    /**
064     * All possible control requests for the module steer motor.
065     */
066    public enum SteerRequestType {
067        /**
068         * Control the drive motor using a Motion Magic® Expo request.
069         * The control output type is determined by {@link SwerveModuleConstants#SteerMotorClosedLoopOutput}
070         */
071        MotionMagicExpo(0),
072        /**
073         * Control the drive motor using an unprofiled position request.
074         * The control output type is determined by {@link SwerveModuleConstants#SteerMotorClosedLoopOutput}
075         */
076        Position(1);
077
078        public final int value;
079
080        SteerRequestType(int initValue) {
081            this.value = initValue;
082        }
083
084        private static HashMap<Integer, SteerRequestType> _map = null;
085        static {
086            _map = new HashMap<Integer, SteerRequestType>();
087            for (SteerRequestType type : SteerRequestType.values()) {
088                _map.put(type.value, type);
089            }
090        }
091
092        /**
093         * Gets SteerRequestType from specified value
094         *
095         * @param value Value of SteerRequestType
096         * @return SteerRequestType of specified value
097         */
098        public static SteerRequestType valueOf(int value) {
099            SteerRequestType retval = _map.get(value);
100            if (retval != null)
101                return retval;
102            return SteerRequestType.values()[0];
103        }
104    }
105
106    /**
107     * All possible control requests for the module drive motor.
108     */
109    public enum DriveRequestType {
110        /**
111         * Control the drive motor using an open-loop voltage request.
112         */
113        OpenLoopVoltage(0),
114        /**
115         * Control the drive motor using a velocity closed-loop request.
116         * The control output type is determined by {@link SwerveModuleConstants#DriveMotorClosedLoopOutput}
117         */
118        Velocity(1);
119
120        public final int value;
121
122        DriveRequestType(int initValue) {
123            this.value = initValue;
124        }
125
126        private static HashMap<Integer, DriveRequestType> _map = null;
127        static {
128            _map = new HashMap<Integer, DriveRequestType>();
129            for (DriveRequestType type : DriveRequestType.values()) {
130                _map.put(type.value, type);
131            }
132        }
133
134        /**
135         * Gets DriveRequestType from specified value
136         *
137         * @param value Value of DriveRequestType
138         * @return DriveRequestType of specified value
139         */
140        public static DriveRequestType valueOf(int value) {
141            DriveRequestType retval = _map.get(value);
142            if (retval != null)
143                return retval;
144            return DriveRequestType.values()[0];
145        }
146    }
147
148    /**
149     * Contains everything the swerve module needs to apply a request.
150     */
151    public static class ModuleRequest {
152        /**
153         * Unoptimized speed and direction the module should target.
154         */
155        public SwerveModuleState State = new SwerveModuleState();
156
157        /**
158         * Robot-centric wheel force feedforward to apply in the
159         * X direction. X is defined as forward according to WPILib
160         * convention, so this determines the forward force to apply.
161         * <p>
162         * This force should include friction.
163         */
164        public double WheelForceFeedforwardX = 0.0;
165        /**
166         * Robot-centric wheel force feedforward to apply in the
167         * Y direction. Y is defined as to the left according to WPILib
168         * convention, so this determines the force to apply to the left.
169         * <p>
170         * This force should include friction.
171         */
172        public double WheelForceFeedforwardY = 0.0;
173
174        /**
175         * The type of control request to use for the drive motor.
176         */
177        public DriveRequestType DriveRequest = DriveRequestType.OpenLoopVoltage;
178        /**
179         * The type of control request to use for the steer motor.
180         */
181        public SteerRequestType SteerRequest = SteerRequestType.Position;
182
183        /**
184         * The update period of the module request. Setting this to a
185         * non-zero value adds a velocity feedforward to the steer motor.
186         */
187        public double UpdatePeriod = 0.0;
188
189        /**
190         * When using Voltage-based control, set to true (default) to use FOC commutation
191         * (requires Phoenix Pro), which increases peak power by ~15%. Set to false to
192         * use trapezoidal commutation. This is ignored when using Torque-based control,
193         * which always uses FOC.
194         * <p>
195         * FOC improves motor performance by leveraging torque (current) control. 
196         * However, this may be inconvenient for applications that require specifying
197         * duty cycle or voltage.  CTR-Electronics has developed a hybrid method that
198         * combines the performances gains of FOC while still allowing applications to
199         * provide duty cycle or voltage demand.  This not to be confused with simple
200         * sinusoidal control or phase voltage control which lacks the performance
201         * gains.
202         */
203        public boolean EnableFOC = true;
204
205        /**
206         * Modifies the State parameter and returns itself.
207         * <p>
208         * Unoptimized speed and direction the module should target.
209         *
210         * @param newState Parameter to modify
211         * @return Itself
212         */
213        public ModuleRequest withState(SwerveModuleState newState) {
214            this.State = newState;
215            return this;
216        }
217
218        /**
219         * Modifies the WheelForceFeedforwardX parameter and returns itself.
220         * <p>
221         * Robot-centric wheel force feedforward to apply in the
222         * X direction. X is defined as forward according to WPILib
223         * convention, so this determines the forward force to apply.
224         * <p>
225         * This force should include friction applied to the ground.
226         *
227         * @param newWheelForceFeedforwardX Parameter to modify
228         * @return Itself
229         */
230        public ModuleRequest withWheelForceFeedforwardX(double newWheelForceFeedforwardX) {
231            this.WheelForceFeedforwardX = newWheelForceFeedforwardX;
232            return this;
233        }
234        /**
235         * Modifies the WheelForceFeedforwardX parameter and returns itself.
236         * <p>
237         * Robot-centric wheel force feedforward to apply in the
238         * X direction. X is defined as forward according to WPILib
239         * convention, so this determines the forward force to apply.
240         * <p>
241         * This force should include friction applied to the ground.
242         *
243         * @param newWheelForceFeedforwardX Parameter to modify
244         * @return Itself
245         */
246        public ModuleRequest withWheelForceFeedforwardX(Force newWheelForceFeedforwardX) {
247            this.WheelForceFeedforwardX = newWheelForceFeedforwardX.in(Newtons);
248            return this;
249        }
250        /**
251         * Helper method to get the WheelForceFeedforwardX parameter
252         * as a unit type. If not using the Java units library,
253         * {@link #WheelForceFeedforwardX} can be accessed directly instead.
254         * <p>
255         * Robot-centric wheel force feedforward to apply in the
256         * X direction. X is defined as forward according to WPILib
257         * convention, so this determines the forward force to apply.
258         * <p>
259         * This force should include friction applied to the ground.
260         *
261         * @return WheelForceFeedforwardX
262         */
263        public Force getWheelForceFeedforwardXMeasure() {
264            return Newtons.of(WheelForceFeedforwardX);
265        }
266
267        /**
268         * Modifies the WheelForceFeedforwardY parameter and returns itself.
269         * <p>
270         * Robot-centric wheel force feedforward to apply in the
271         * Y direction. Y is defined as to the left according to WPILib
272         * convention, so this determines the force to apply to the left.
273         * <p>
274         * This force should include friction applied to the ground.
275         *
276         * @param newWheelForceFeedforwardY Parameter to modify
277         * @return Itself
278         */
279        public ModuleRequest withWheelForceFeedforwardY(double newWheelForceFeedforwardY) {
280            this.WheelForceFeedforwardY = newWheelForceFeedforwardY;
281            return this;
282        }
283        /**
284         * Modifies the WheelForceFeedforwardY parameter and returns itself.
285         * <p>
286         * Robot-centric wheel force feedforward to apply in the
287         * Y direction. Y is defined as to the left according to WPILib
288         * convention, so this determines the force to apply to the left.
289         * <p>
290         * This force should include friction applied to the ground.
291         *
292         * @param newWheelForceFeedforwardY Parameter to modify
293         * @return Itself
294         */
295        public ModuleRequest withWheelForceFeedforwardY(Force newWheelForceFeedforwardY) {
296            this.WheelForceFeedforwardY = newWheelForceFeedforwardY.in(Newtons);
297            return this;
298        }
299        /**
300         * Helper method to get the WheelForceFeedforwardY parameter
301         * as a unit type. If not using the Java units library,
302         * {@link #WheelForceFeedforwardY} can be accessed directly instead.
303         * <p>
304         * Robot-centric wheel force feedforward to apply in the
305         * Y direction. Y is defined as to the left according to WPILib
306         * convention, so this determines the force to apply to the left.
307         * <p>
308         * This force should include friction applied to the ground.
309         *
310         * @return WheelForceFeedforwardY
311         */
312        public Force getWheelForceFeedforwardYMeasure() {
313            return Newtons.of(WheelForceFeedforwardY);
314        }
315
316        /**
317         * Modifies the DriveRequest parameter and returns itself.
318         * <p>
319         * The type of control request to use for the drive motor.
320         *
321         * @param newDriveRequest Parameter to modify
322         * @return Itself
323         */
324        public ModuleRequest withDriveRequest(DriveRequestType newDriveRequest) {
325            this.DriveRequest = newDriveRequest;
326            return this;
327        }
328
329        /**
330         * Modifies the SteerRequest parameter and returns itself.
331         * <p>
332         * The type of control request to use for the steer motor.
333         *
334         * @param newSteerRequest Parameter to modify
335         * @return Itself
336         */
337        public ModuleRequest withSteerRequest(SteerRequestType newSteerRequest) {
338            this.SteerRequest = newSteerRequest;
339            return this;
340        }
341
342        /**
343         * Modifies the UpdatePeriod parameter and returns itself.
344         * <p>
345         * The update period of the module request. Setting this to a
346         * non-zero value adds a velocity feedforward to the steer motor.
347         *
348         * @param newUpdatePeriod Parameter to modify
349         * @return Itself
350         */
351        public ModuleRequest withUpdatePeriod(double newUpdatePeriod) {
352            this.UpdatePeriod = newUpdatePeriod;
353            return this;
354        }
355
356        /**
357         * Modifies the EnableFOC parameter and returns itself.
358         * <p>
359         * When using Voltage-based control, set to true (default) to use FOC commutation
360         * (requires Phoenix Pro), which increases peak power by ~15%. Set to false to
361         * use trapezoidal commutation. This is ignored when using Torque-based control,
362         * which always uses FOC.
363         * <p>
364         * FOC improves motor performance by leveraging torque (current) control. 
365         * However, this may be inconvenient for applications that require specifying
366         * duty cycle or voltage.  CTR-Electronics has developed a hybrid method that
367         * combines the performances gains of FOC while still allowing applications to
368         * provide duty cycle or voltage demand.  This not to be confused with simple
369         * sinusoidal control or phase voltage control which lacks the performance
370         * gains.
371         *
372         * @param newEnableFOC Parameter to modify
373         * @return Itself
374         */
375        public ModuleRequest withEnableFOC(boolean newEnableFOC) {
376            this.EnableFOC = newEnableFOC;
377            return this;
378        }
379    }
380
381    /** Number of times to attempt config applies. */
382    protected static final int kNumConfigAttempts = 2;
383
384    /** ID of the native drivetrain instance, used for JNI calls. */
385    protected final int m_drivetrainId;
386    /** Index of this module in the native drivetrain, used for JNI calls. */
387    protected final int m_moduleIdx;
388    /** JNI instance to use for non-static JNI calls. This object is not thread-safe. */
389    protected final SwerveJNI m_jni = new SwerveJNI();
390
391    private final DriveMotorT m_driveMotor;
392    private final SteerMotorT m_steerMotor;
393    private final EncoderT m_encoder;
394
395    private final ClosedLoopOutputType m_driveClosedLoopOutput;
396    private final ClosedLoopOutputType m_steerClosedLoopOutput;
397
398    private final SwerveModulePosition m_currentPosition = new SwerveModulePosition();
399    private final SwerveModuleState m_currentState = new SwerveModuleState();
400    private final SwerveModuleState m_targetState = new SwerveModuleState();
401    /** Lock protecting the swerve module state. */
402    private final Lock m_stateLock = new ReentrantLock();
403
404    boolean m_isValid = true;
405
406    /**
407     * Construct a SwerveModule with the specified constants.
408     *
409     * @param driveMotorConstructor Constructor for the drive motor, such as {@code TalonFX::new}
410     * @param steerMotorConstructor Constructor for the steer motor, such as {@code TalonFX::new}
411     * @param encoderConstructor    Constructor for the azimuth encoder, such as {@code CANcoder::new}
412     * @param constants             Constants used to construct the module
413     * @param canbusName            The name of the CAN bus this module is on
414     * @param drivetrainId          ID of the swerve drivetrain
415     * @param index                 Index of this swerve module
416     */
417    public SwerveModule(
418        DeviceConstructor<DriveMotorT> driveMotorConstructor,
419        DeviceConstructor<SteerMotorT> steerMotorConstructor,
420        DeviceConstructor<EncoderT> encoderConstructor,
421        SwerveModuleConstants<?, ?, ?> constants,
422        String canbusName,
423        int drivetrainId,
424        int index
425    ) {
426        m_drivetrainId = drivetrainId;
427        m_moduleIdx = index;
428
429        m_driveMotor = driveMotorConstructor.create(constants.DriveMotorId, canbusName);
430        m_steerMotor = steerMotorConstructor.create(constants.SteerMotorId, canbusName);
431        m_encoder = encoderConstructor.create(constants.EncoderId, canbusName);
432
433        m_driveClosedLoopOutput = constants.DriveMotorClosedLoopOutput;
434        m_steerClosedLoopOutput = constants.SteerMotorClosedLoopOutput;
435
436        StatusCode response = StatusCode.OK;
437
438        if (
439            m_driveMotor instanceof TalonFX driveMotor &&
440            (constants.DriveMotorInitialConfigs == null || constants.DriveMotorInitialConfigs instanceof TalonFXConfiguration)
441        ) {
442            TalonFXConfiguration driveConfigs = (TalonFXConfiguration)constants.DriveMotorInitialConfigs;
443            if (driveConfigs == null) {
444                driveConfigs = new TalonFXConfiguration();
445            }
446            
447            driveConfigs.MotorOutput.NeutralMode = NeutralModeValue.Brake;
448
449            driveConfigs.Slot0 = constants.DriveMotorGains;
450            driveConfigs.TorqueCurrent.PeakForwardTorqueCurrent = constants.SlipCurrent;
451            driveConfigs.TorqueCurrent.PeakReverseTorqueCurrent = -constants.SlipCurrent;
452            driveConfigs.CurrentLimits.StatorCurrentLimit = constants.SlipCurrent;
453            driveConfigs.CurrentLimits.StatorCurrentLimitEnable = true;
454
455            if (constants.DriveMotorType != DriveMotorArrangement.TalonFX_Integrated) {
456                System.out.println(
457                    "Drive motor Talon FX ID " + driveMotor.getDeviceID() + " only supports TalonFX_Integrated."
458                );
459            }
460
461            driveConfigs.MotorOutput.Inverted = constants.DriveMotorInverted
462                    ? InvertedValue.Clockwise_Positive
463                    : InvertedValue.CounterClockwise_Positive;
464            for (int i = 0; i < kNumConfigAttempts; ++i) {
465                response = driveMotor.getConfigurator().apply(driveConfigs);
466                if (response.isOK()) break;
467            }
468            if (!response.isOK()) {
469                System.out.println(
470                    "Talon ID " + driveMotor.getDeviceID() + " failed config with error " + response.toString()
471                );
472            }
473        } else if (
474            m_driveMotor instanceof TalonFXS driveMotor &&
475            (constants.DriveMotorInitialConfigs == null || constants.DriveMotorInitialConfigs instanceof TalonFXSConfiguration)
476        ) {
477            TalonFXSConfiguration driveConfigs = (TalonFXSConfiguration)constants.DriveMotorInitialConfigs;
478            if (driveConfigs == null) {
479                driveConfigs = new TalonFXSConfiguration();
480            }
481            
482            driveConfigs.MotorOutput.NeutralMode = NeutralModeValue.Brake;
483
484            driveConfigs.Slot0 = constants.DriveMotorGains;
485            driveConfigs.CurrentLimits.StatorCurrentLimit = constants.SlipCurrent;
486            driveConfigs.CurrentLimits.StatorCurrentLimitEnable = true;
487
488            switch (constants.DriveMotorType) {
489                case TalonFX_Integrated:
490                    System.out.println(
491                        "Cannot use TalonFX_Integrated drive motor type on Talon FXS ID " + driveMotor.getDeviceID() + ". TalonFX_Integrated is only supported on Talon FX."
492                    );
493                    break;
494
495                case TalonFXS_NEO_JST:
496                    driveConfigs.Commutation.MotorArrangement = MotorArrangementValue.NEO_JST;
497                    break;
498                case TalonFXS_VORTEX_JST:
499                    driveConfigs.Commutation.MotorArrangement = MotorArrangementValue.VORTEX_JST;
500                    break;
501            }
502
503            driveConfigs.MotorOutput.Inverted = constants.DriveMotorInverted
504                    ? InvertedValue.Clockwise_Positive
505                    : InvertedValue.CounterClockwise_Positive;
506            for (int i = 0; i < kNumConfigAttempts; ++i) {
507                response = driveMotor.getConfigurator().apply(driveConfigs);
508                if (response.isOK()) break;
509            }
510            if (!response.isOK()) {
511                System.out.println(
512                    "Talon ID " + driveMotor.getDeviceID() + " failed config with error " + response.toString()
513                );
514            }
515        } else {
516            DriverStation.reportError(
517                "[phoenix] SwerveDrivetrain drive motor type does not match the SwerveModuleConstants drive initial configs type. Ensure that the SwerveDrivetrain and SwerveModuleConstants instances are both constructed with the same drive motor type (TalonFX/TalonFXConfiguration, TalonFXS/TalonFXSConfiguration, etc.).",
518                true
519            );
520            m_isValid = false;
521        }
522
523        if (
524            m_steerMotor instanceof TalonFX steerMotor &&
525            (constants.SteerMotorInitialConfigs == null || constants.SteerMotorInitialConfigs instanceof TalonFXConfiguration)
526        ) {
527            TalonFXConfiguration steerConfigs = (TalonFXConfiguration)constants.SteerMotorInitialConfigs;
528            if (steerConfigs == null) {
529                steerConfigs = new TalonFXConfiguration();
530            }
531
532            steerConfigs.MotorOutput.NeutralMode = NeutralModeValue.Brake;
533
534            steerConfigs.Slot0 = constants.SteerMotorGains;
535
536            if (constants.SteerMotorType != SteerMotorArrangement.TalonFX_Integrated) {
537                System.out.println(
538                    "Steer motor Talon FX ID " + steerMotor.getDeviceID() + " only supports TalonFX_Integrated."
539                );
540            }
541
542            /* Modify configuration to use remote encoder setting */
543            steerConfigs.Feedback.FeedbackRemoteSensorID = constants.EncoderId;
544            switch (constants.FeedbackSource) {
545                case FusedCANcoder:
546                    steerConfigs.Feedback.FeedbackSensorSource = FeedbackSensorSourceValue.FusedCANcoder;
547                    break;
548                case SyncCANcoder:
549                    steerConfigs.Feedback.FeedbackSensorSource = FeedbackSensorSourceValue.SyncCANcoder;
550                    break;
551                case RemoteCANcoder:
552                    steerConfigs.Feedback.FeedbackSensorSource = FeedbackSensorSourceValue.RemoteCANcoder;
553                    break;
554
555                case FusedCANdiPWM1:
556                    steerConfigs.Feedback.FeedbackSensorSource = FeedbackSensorSourceValue.FusedCANdiPWM1;
557                    break;
558                case FusedCANdiPWM2:
559                    steerConfigs.Feedback.FeedbackSensorSource = FeedbackSensorSourceValue.FusedCANdiPWM2;
560                    break;
561                case SyncCANdiPWM1:
562                    steerConfigs.Feedback.FeedbackSensorSource = FeedbackSensorSourceValue.SyncCANdiPWM1;
563                    break;
564                case SyncCANdiPWM2:
565                    steerConfigs.Feedback.FeedbackSensorSource = FeedbackSensorSourceValue.SyncCANdiPWM2;
566                    break;
567                case RemoteCANdiPWM1:
568                    steerConfigs.Feedback.FeedbackSensorSource = FeedbackSensorSourceValue.RemoteCANdiPWM1;
569                    break;
570                case RemoteCANdiPWM2:
571                    steerConfigs.Feedback.FeedbackSensorSource = FeedbackSensorSourceValue.RemoteCANdiPWM2;
572                    break;
573
574                case TalonFXS_PulseWidth:
575                    System.out.println(
576                        "Cannot use Pulse Width steer feedback type on Talon FX ID " + steerMotor.getDeviceID() + ". Pulse Width is only supported on Talon FXS."
577                    );
578                    break;
579            }
580            steerConfigs.Feedback.RotorToSensorRatio = constants.SteerMotorGearRatio;
581    
582            steerConfigs.MotionMagic.MotionMagicExpo_kV = 0.12 * constants.SteerMotorGearRatio;
583            steerConfigs.MotionMagic.MotionMagicExpo_kA = 0.8 / constants.SteerMotorGearRatio;
584    
585            steerConfigs.ClosedLoopGeneral.ContinuousWrap = true; // Enable continuous wrap for swerve modules
586    
587            steerConfigs.MotorOutput.Inverted = constants.SteerMotorInverted
588                    ? InvertedValue.Clockwise_Positive
589                    : InvertedValue.CounterClockwise_Positive;
590            for (int i = 0; i < kNumConfigAttempts; ++i) {
591                response = steerMotor.getConfigurator().apply(steerConfigs);
592                if (response.isOK()) break;
593            }
594            if (!response.isOK()) {
595                System.out.println(
596                    "Talon ID " + steerMotor.getDeviceID() + " failed config with error: " + response.toString()
597                );
598            }
599        } else if (
600            m_steerMotor instanceof TalonFXS steerMotor &&
601            (constants.SteerMotorInitialConfigs == null || constants.SteerMotorInitialConfigs instanceof TalonFXSConfiguration)
602        ) {
603            TalonFXSConfiguration steerConfigs = (TalonFXSConfiguration)constants.SteerMotorInitialConfigs;
604            if (steerConfigs == null) {
605                steerConfigs = new TalonFXSConfiguration();
606            }
607
608            steerConfigs.MotorOutput.NeutralMode = NeutralModeValue.Brake;
609
610            steerConfigs.Slot0 = constants.SteerMotorGains;
611
612            switch (constants.SteerMotorType) {
613                case TalonFX_Integrated:
614                    System.out.println(
615                        "Cannot use TalonFX_Integrated steer motor type on Talon FXS ID " + steerMotor.getDeviceID() + ". TalonFX_Integrated is only supported on Talon FX."
616                    );
617                    break;
618
619                case TalonFXS_Minion_JST:
620                    steerConfigs.Commutation.MotorArrangement = MotorArrangementValue.Minion_JST;
621                    break;
622                case TalonFXS_NEO_JST:
623                    steerConfigs.Commutation.MotorArrangement = MotorArrangementValue.NEO_JST;
624                    break;
625                case TalonFXS_VORTEX_JST:
626                    steerConfigs.Commutation.MotorArrangement = MotorArrangementValue.VORTEX_JST;
627                    break;
628                case TalonFXS_NEO550_JST:
629                    steerConfigs.Commutation.MotorArrangement = MotorArrangementValue.NEO550_JST;
630                    break;
631                case TalonFXS_Brushed_AB:
632                    steerConfigs.Commutation.MotorArrangement = MotorArrangementValue.Brushed_DC;
633                    steerConfigs.Commutation.BrushedMotorWiring = BrushedMotorWiringValue.Leads_A_and_B;
634                    break;
635                case TalonFXS_Brushed_AC:
636                    steerConfigs.Commutation.MotorArrangement = MotorArrangementValue.Brushed_DC;
637                    steerConfigs.Commutation.BrushedMotorWiring = BrushedMotorWiringValue.Leads_A_and_C;
638                    break;
639                case TalonFXS_Brushed_BC:
640                    steerConfigs.Commutation.MotorArrangement = MotorArrangementValue.Brushed_DC;
641                    steerConfigs.Commutation.BrushedMotorWiring = BrushedMotorWiringValue.Leads_B_and_C;
642                    break;
643            }
644
645            /* Modify configuration to use remote encoder setting */
646            steerConfigs.ExternalFeedback.FeedbackRemoteSensorID = constants.EncoderId;
647            switch (constants.FeedbackSource) {
648                case FusedCANcoder:
649                    steerConfigs.ExternalFeedback.ExternalFeedbackSensorSource = ExternalFeedbackSensorSourceValue.FusedCANcoder;
650                    break;
651                case SyncCANcoder:
652                    steerConfigs.ExternalFeedback.ExternalFeedbackSensorSource = ExternalFeedbackSensorSourceValue.SyncCANcoder;
653                    break;
654                case RemoteCANcoder:
655                    steerConfigs.ExternalFeedback.ExternalFeedbackSensorSource = ExternalFeedbackSensorSourceValue.RemoteCANcoder;
656                    break;
657
658                case FusedCANdiPWM1:
659                    steerConfigs.ExternalFeedback.ExternalFeedbackSensorSource = ExternalFeedbackSensorSourceValue.FusedCANdiPWM1;
660                    break;
661                case FusedCANdiPWM2:
662                    steerConfigs.ExternalFeedback.ExternalFeedbackSensorSource = ExternalFeedbackSensorSourceValue.FusedCANdiPWM2;
663                    break;
664                case SyncCANdiPWM1:
665                    steerConfigs.ExternalFeedback.ExternalFeedbackSensorSource = ExternalFeedbackSensorSourceValue.SyncCANdiPWM1;
666                    break;
667                case SyncCANdiPWM2:
668                    steerConfigs.ExternalFeedback.ExternalFeedbackSensorSource = ExternalFeedbackSensorSourceValue.SyncCANdiPWM2;
669                    break;
670                case RemoteCANdiPWM1:
671                    steerConfigs.ExternalFeedback.ExternalFeedbackSensorSource = ExternalFeedbackSensorSourceValue.RemoteCANdiPWM1;
672                    break;
673                case RemoteCANdiPWM2:
674                    steerConfigs.ExternalFeedback.ExternalFeedbackSensorSource = ExternalFeedbackSensorSourceValue.RemoteCANdiPWM2;
675                    break;
676
677                case TalonFXS_PulseWidth:
678                    steerConfigs.ExternalFeedback.ExternalFeedbackSensorSource = ExternalFeedbackSensorSourceValue.PulseWidth;
679                    steerConfigs.ExternalFeedback.AbsoluteSensorOffset = constants.EncoderOffset;
680                    steerConfigs.ExternalFeedback.SensorPhase = constants.EncoderInverted
681                        ? SensorPhaseValue.Opposed
682                        : SensorPhaseValue.Aligned;
683                    break;
684            }
685            steerConfigs.ExternalFeedback.RotorToSensorRatio = constants.SteerMotorGearRatio;
686    
687            steerConfigs.MotionMagic.MotionMagicExpo_kV = 0.12 * constants.SteerMotorGearRatio;
688            steerConfigs.MotionMagic.MotionMagicExpo_kA = 0.8 / constants.SteerMotorGearRatio;
689    
690            steerConfigs.ClosedLoopGeneral.ContinuousWrap = true; // Enable continuous wrap for swerve modules
691    
692            steerConfigs.MotorOutput.Inverted = constants.SteerMotorInverted
693                    ? InvertedValue.Clockwise_Positive
694                    : InvertedValue.CounterClockwise_Positive;
695            for (int i = 0; i < kNumConfigAttempts; ++i) {
696                response = steerMotor.getConfigurator().apply(steerConfigs);
697                if (response.isOK()) break;
698            }
699            if (!response.isOK()) {
700                System.out.println(
701                    "Talon ID " + steerMotor.getDeviceID() + " failed config with error: " + response.toString()
702                );
703            }
704        } else {
705            DriverStation.reportError(
706                "[phoenix] SwerveDrivetrain steer motor type does not match the SwerveModuleConstants steer initial configs type. Ensure that the SwerveDrivetrain and SwerveModuleConstants instances are both constructed with the same steer motor type (TalonFX/TalonFXConfiguration, TalonFXS/TalonFXSConfiguration, etc.).",
707                true
708            );
709            m_isValid = false;
710        }
711
712        if (
713            m_encoder instanceof CANcoder encoder &&
714            (constants.EncoderInitialConfigs == null || constants.EncoderInitialConfigs instanceof CANcoderConfiguration)
715        ) {
716            CANcoderConfiguration encoderConfigs = (CANcoderConfiguration)constants.EncoderInitialConfigs;
717            if (encoderConfigs == null) {
718                encoderConfigs = new CANcoderConfiguration();
719            }
720
721            encoderConfigs.MagnetSensor.MagnetOffset = constants.EncoderOffset;
722            encoderConfigs.MagnetSensor.SensorDirection = constants.EncoderInverted
723                    ? SensorDirectionValue.Clockwise_Positive
724                    : SensorDirectionValue.CounterClockwise_Positive;
725            for (int i = 0; i < kNumConfigAttempts; ++i) {
726                response = encoder.getConfigurator().apply(encoderConfigs);
727                if (response.isOK()) break;
728            }
729            if (!response.isOK()) {
730                System.out.println(
731                    "Encoder ID " + encoder.getDeviceID() + " failed config with error: " + response.toString()
732                );
733            }
734        } else if (
735            m_encoder instanceof CANdi encoder &&
736            (constants.EncoderInitialConfigs == null || constants.EncoderInitialConfigs instanceof CANdiConfiguration)
737        ) {
738            CANdiConfiguration encoderConfigs = (CANdiConfiguration)constants.EncoderInitialConfigs;
739            if (encoderConfigs == null) {
740                encoderConfigs = new CANdiConfiguration();
741            }
742
743            for (int i = 0; i < kNumConfigAttempts; ++i) {
744                response = encoder.getConfigurator().apply(encoderConfigs.DigitalInputs);
745                if (response.isOK()) break;
746            }
747            if (!response.isOK()) {
748                System.out.println(
749                    "Encoder ID " + encoder.getDeviceID() + " failed config with error: " + response.toString()
750                );
751            }
752
753            for (int i = 0; i < kNumConfigAttempts; ++i) {
754                response = encoder.getConfigurator().apply(encoderConfigs.CustomParams);
755                if (response.isOK()) break;
756            }
757            if (!response.isOK()) {
758                System.out.println(
759                    "Encoder ID " + encoder.getDeviceID() + " failed config with error: " + response.toString()
760                );
761            }
762
763            switch (constants.FeedbackSource) {
764                case FusedCANdiPWM1:
765                case SyncCANdiPWM1:
766                case RemoteCANdiPWM1:
767                    encoderConfigs.PWM1.AbsoluteSensorOffset = constants.EncoderOffset;
768                    encoderConfigs.PWM1.SensorDirection = constants.EncoderInverted;
769
770                    for (int i = 0; i < kNumConfigAttempts; ++i) {
771                        response = encoder.getConfigurator().apply(encoderConfigs.PWM1);
772                        if (response.isOK()) break;
773                    }
774                    if (!response.isOK()) {
775                        System.out.println(
776                            "Encoder ID " + encoder.getDeviceID() + " failed config with error: " + response.toString()
777                        );
778                    }
779                    break;
780
781                case FusedCANdiPWM2:
782                case SyncCANdiPWM2:
783                case RemoteCANdiPWM2:
784                    encoderConfigs.PWM2.AbsoluteSensorOffset = constants.EncoderOffset;
785                    encoderConfigs.PWM2.SensorDirection = constants.EncoderInverted;
786
787                    for (int i = 0; i < kNumConfigAttempts; ++i) {
788                        response = encoder.getConfigurator().apply(encoderConfigs.PWM2);
789                        if (response.isOK()) break;
790                    }
791                    if (!response.isOK()) {
792                        System.out.println(
793                            "Encoder ID " + encoder.getDeviceID() + " failed config with error: " + response.toString()
794                        );
795                    }
796                    break;
797
798                default:
799                    break;
800            }
801        } else if (
802            m_encoder instanceof TalonFXS &&
803            (constants.EncoderInitialConfigs == null || constants.EncoderInitialConfigs instanceof TalonFXSConfiguration)
804        ) {
805            /* do nothing if using encoder on a Talon FXS, configs are applied when initializing the steer motor */
806        } else {
807            DriverStation.reportError(
808                "[phoenix] SwerveDrivetrain encoder type does not match the SwerveModuleConstants encoder initial configs type. Ensure that the SwerveDrivetrain and SwerveModuleConstants instances are both constructed with the same encoder type (CANcoder/CANcoderConfiguration, CANdi/CANdiConfiguration, etc.).",
809                true
810            );
811            m_isValid = false;
812        }
813    }
814
815    /**
816     * Applies the desired SwerveModuleState to this module.
817     *
818     * @param moduleRequest The request to apply to this module
819     */
820    public void apply(ModuleRequest moduleRequest) {
821        m_jni.moduleApplyParams.state.speed = moduleRequest.State.speedMetersPerSecond;
822        m_jni.moduleApplyParams.state.angle = moduleRequest.State.angle.getRadians();
823        m_jni.moduleApplyParams.wheelForceFeedforwardX = moduleRequest.WheelForceFeedforwardX;
824        m_jni.moduleApplyParams.wheelForceFeedforwardY = moduleRequest.WheelForceFeedforwardY;
825        m_jni.moduleApplyParams.driveRequest = moduleRequest.DriveRequest.value;
826        m_jni.moduleApplyParams.steerRequest = moduleRequest.SteerRequest.value;
827        m_jni.moduleApplyParams.updatePeriod = moduleRequest.UpdatePeriod;
828        m_jni.moduleApplyParams.enableFOC = moduleRequest.EnableFOC;
829
830        m_jni.JNI_Module_Apply(m_drivetrainId, m_moduleIdx);
831    }
832
833    /**
834     * Controls this module using the specified drive and steer control requests.
835     *
836     * This is intended only to be used for characterization of the robot; do not use this for normal use.
837     *
838     * @param driveRequest The control request to apply to the drive motor
839     * @param steerRequest The control request to apply to the steer motor
840     */
841    public void apply(ControlRequest driveRequest, ControlRequest steerRequest) {
842        m_driveMotor.setControl(driveRequest.withUpdateFreqHz(0));
843        m_steerMotor.setControl(steerRequest.withUpdateFreqHz(0));
844    }
845
846    /**
847     * Gets the state of this module and passes it back as a
848     * SwerveModulePosition object with latency compensated values.
849     * <p>
850     * This function is blocking when it performs a refresh.
851     *
852     * @param refresh True if the signals should be refreshed
853     * @return SwerveModulePosition containing this module's state
854     */
855    public final SwerveModulePosition getPosition(boolean refresh) {
856        try {
857            m_stateLock.lock();
858
859            m_jni.JNI_Module_GetPosition(m_drivetrainId, m_moduleIdx, refresh);
860
861            m_currentPosition.distanceMeters = m_jni.modulePosition.distance;
862            if (m_currentPosition.angle.getRadians() != m_jni.modulePosition.angle) {
863                m_currentPosition.angle = Rotation2d.fromRadians(m_jni.modulePosition.angle);
864            }
865            return m_currentPosition;
866        } finally {
867            m_stateLock.unlock();
868        }
869    }
870
871    /**
872     * Gets the last cached swerve module position.
873     * This differs from {@link getPosition} in that it will not
874     * perform any latency compensation or refresh the signals.
875     *
876     * @return Last cached SwerveModulePosition
877     */
878    public final SwerveModulePosition getCachedPosition() {
879        try {
880            m_stateLock.lock();
881
882            m_jni.JNI_Module_GetCachedPosition(m_drivetrainId, m_moduleIdx);
883
884            m_currentPosition.distanceMeters = m_jni.modulePosition.distance;
885            if (m_currentPosition.angle.getRadians() != m_jni.modulePosition.angle) {
886                m_currentPosition.angle = Rotation2d.fromRadians(m_jni.modulePosition.angle);
887            }
888            return m_currentPosition;
889        } finally {
890            m_stateLock.unlock();
891        }
892    }
893
894    /**
895     * Get the current state of the module.
896     * <p>
897     * This is typically used for telemetry, as the SwerveModulePosition
898     * is used for odometry.
899     *
900     * @return Current state of the module
901     */
902    public final SwerveModuleState getCurrentState() {
903        try {
904            m_stateLock.lock();
905
906            m_jni.JNI_Module_GetCurrentState(m_drivetrainId, m_moduleIdx);
907
908            m_currentState.speedMetersPerSecond = m_jni.moduleState.speed;
909            if (m_currentState.angle.getRadians() != m_jni.moduleState.angle) {
910                m_currentState.angle = Rotation2d.fromRadians(m_jni.moduleState.angle);
911            }
912            return m_currentState;
913        } finally {
914            m_stateLock.unlock();
915        }
916    }
917
918    /**
919     * Get the target state of the module.
920     * <p>
921     * This is typically used for telemetry.
922     *
923     * @return Target state of the module
924     */
925    public final SwerveModuleState getTargetState() {
926        try {
927            m_stateLock.lock();
928
929            m_jni.JNI_Module_GetTargetState(m_drivetrainId, m_moduleIdx);
930
931            m_targetState.speedMetersPerSecond = m_jni.moduleState.speed;
932            if (m_targetState.angle.getRadians() != m_jni.moduleState.angle) {
933                m_targetState.angle = Rotation2d.fromRadians(m_jni.moduleState.angle);
934            }
935            return m_targetState;
936        } finally {
937            m_stateLock.unlock();
938        }
939    }
940
941    /**
942     * Resets this module's drive motor position to 0 rotations.
943     */
944    public void resetPosition() {
945        SwerveJNI.JNI_Module_ResetPosition(m_drivetrainId, m_moduleIdx);
946    }
947
948    /**
949     * Gets the closed-loop output type to use for the drive motor.
950     *
951     * @return Drive motor closed-loop output type
952     */
953    public final ClosedLoopOutputType getDriveClosedLoopOutputType() {
954        return m_driveClosedLoopOutput;
955    }
956
957    /**
958     * Gets the closed-loop output type to use for the steer motor.
959     *
960     * @return Steer motor closed-loop output type
961     */
962    public final ClosedLoopOutputType getSteerClosedLoopOutputType() {
963        return m_steerClosedLoopOutput;
964    }
965
966    /**
967     * Gets this module's Drive Motor reference.
968     * <p>
969     * This should be used only to access signals and change configurations that the
970     * swerve drivetrain does not configure itself.
971     *
972     * @return This module's Drive Motor reference
973     */
974    public final DriveMotorT getDriveMotor() {
975        return m_driveMotor;
976    }
977
978    /**
979     * Gets this module's Steer Motor reference.
980     * <p>
981     * This should be used only to access signals and change configurations that the
982     * swerve drivetrain does not configure itself.
983     *
984     * @return This module's Steer Motor reference
985     */
986    public final SteerMotorT getSteerMotor() {
987        return m_steerMotor;
988    }
989
990    /**
991     * Gets this module's azimuth encoder reference.
992     * <p>
993     * This should be used only to access signals and change configurations that the
994     * swerve drivetrain does not configure itself.
995     *
996     * @return This module's azimuth encoder reference
997     */
998    public final EncoderT getEncoder() {
999        return m_encoder;
1000    }
1001}