From 4bc6978a20dc2a036b3f562bf3c23c46f6da9361 Mon Sep 17 00:00:00 2001 From: thenetworkgrinch Date: Mon, 9 Dec 2024 23:26:04 +0000 Subject: [PATCH] Upgrading to 2025.1.0 --- swervelib/SwerveController.java | 2 +- swervelib/SwerveDrive.java | 477 ++++++++++++------ swervelib/SwerveDriveTest.java | 35 +- swervelib/SwerveModule.java | 208 ++++++-- .../encoders/AnalogAbsoluteEncoderSwerve.java | 7 +- swervelib/encoders/CANCoderSwerve.java | 25 +- .../encoders/PWMDutyCycleEncoderSwerve.java | 5 +- .../encoders/SparkMaxAnalogEncoderSwerve.java | 50 +- swervelib/encoders/SparkMaxEncoderSwerve.java | 68 ++- swervelib/encoders/TalonSRXEncoderSwerve.java | 90 ++++ swervelib/imu/NavXSwerve.java | 59 +-- swervelib/imu/Pigeon2Swerve.java | 36 +- .../math/IMULinearMovingAverageFilter.java | 2 +- swervelib/math/SwerveMath.java | 4 + swervelib/motors/SparkFlexSwerve.java | 327 +++++++----- .../motors/SparkMaxBrushedMotorSwerve.java | 395 +++++++++++---- swervelib/motors/SparkMaxSwerve.java | 315 +++++++----- swervelib/motors/SwerveMotor.java | 14 + swervelib/motors/TalonFXSwerve.java | 129 ++--- swervelib/motors/TalonSRXSwerve.java | 56 +- swervelib/parser/Cache.java | 3 +- .../parser/SwerveControllerConfiguration.java | 2 +- .../parser/SwerveDriveConfiguration.java | 83 ++- .../parser/SwerveModuleConfiguration.java | 8 +- .../SwerveModulePhysicalCharacteristics.java | 46 +- swervelib/parser/SwerveParser.java | 54 +- swervelib/parser/json/DeviceJson.java | 72 ++- swervelib/parser/json/ModuleJson.java | 67 +-- .../parser/json/PhysicalPropertiesJson.java | 57 ++- .../modules/AngleConversionFactorsJson.java | 12 +- .../json/modules/ConversionFactorsJson.java | 17 +- .../modules/DriveConversionFactorsJson.java | 14 +- swervelib/simulation/SwerveIMUSimulation.java | 32 +- .../simulation/SwerveModuleSimulation.java | 68 +-- swervelib/telemetry/SwerveDriveTelemetry.java | 159 +++++- 35 files changed, 1889 insertions(+), 1109 deletions(-) create mode 100644 swervelib/encoders/TalonSRXEncoderSwerve.java diff --git a/swervelib/SwerveController.java b/swervelib/SwerveController.java index 057ba46a..80116de6 100644 --- a/swervelib/SwerveController.java +++ b/swervelib/SwerveController.java @@ -227,7 +227,7 @@ public double headingCalculate(double currentHeadingAngleRadians, double targetH * * @param angularVelocity Angular velocity in radians per second. */ - public void setMaximumAngularVelocity(double angularVelocity) + public void setMaximumChassisAngularVelocity(double angularVelocity) { config.maxAngularVelocity = angularVelocity; } diff --git a/swervelib/SwerveDrive.java b/swervelib/SwerveDrive.java index 7d83e5d0..f0acfc6f 100644 --- a/swervelib/SwerveDrive.java +++ b/swervelib/SwerveDrive.java @@ -1,5 +1,17 @@ package swervelib; +import static edu.wpi.first.hal.FRCNetComm.tInstances.kRobotDriveSwerve_YAGSL; +import static edu.wpi.first.hal.FRCNetComm.tResourceType.kResourceType_RobotDrive; +import static edu.wpi.first.units.Units.Inches; +import static edu.wpi.first.units.Units.KilogramSquareMeters; +import static edu.wpi.first.units.Units.Kilograms; +import static edu.wpi.first.units.Units.Meters; +import static edu.wpi.first.units.Units.MetersPerSecond; +import static edu.wpi.first.units.Units.Newtons; +import static edu.wpi.first.units.Units.Seconds; +import static edu.wpi.first.units.Units.Volts; + +import edu.wpi.first.hal.HAL; import edu.wpi.first.math.Matrix; import edu.wpi.first.math.controller.SimpleMotorFeedforward; import edu.wpi.first.math.estimator.SwerveDrivePoseEstimator; @@ -16,9 +28,17 @@ import edu.wpi.first.math.kinematics.SwerveModuleState; import edu.wpi.first.math.numbers.N1; import edu.wpi.first.math.numbers.N3; +import edu.wpi.first.math.system.plant.DCMotor; import edu.wpi.first.math.trajectory.Trajectory; import edu.wpi.first.math.util.Units; +import edu.wpi.first.units.measure.AngularVelocity; +import edu.wpi.first.units.measure.Force; +import edu.wpi.first.units.measure.LinearVelocity; +import edu.wpi.first.wpilibj.Alert; +import edu.wpi.first.wpilibj.Alert.AlertType; +import edu.wpi.first.wpilibj.DriverStation; import edu.wpi.first.wpilibj.Notifier; +import edu.wpi.first.wpilibj.TimedRobot; import edu.wpi.first.wpilibj.Timer; import edu.wpi.first.wpilibj.smartdashboard.Field2d; import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard; @@ -29,6 +49,11 @@ import java.util.Optional; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +import org.ironmaple.simulation.SimulatedArena; +import org.ironmaple.simulation.drivesims.AbstractDriveTrainSimulation; +import org.ironmaple.simulation.drivesims.SwerveDriveSimulation; +import org.ironmaple.simulation.drivesims.SwerveModuleSimulation; +import org.ironmaple.simulation.drivesims.configs.DriveTrainSimulationConfig; import swervelib.encoders.CANCoderSwerve; import swervelib.imu.IMUVelocity; import swervelib.imu.Pigeon2Swerve; @@ -39,8 +64,6 @@ import swervelib.parser.SwerveControllerConfiguration; import swervelib.parser.SwerveDriveConfiguration; import swervelib.simulation.SwerveIMUSimulation; -import swervelib.telemetry.Alert; -import swervelib.telemetry.Alert.AlertType; import swervelib.telemetry.SwerveDriveTelemetry; import swervelib.telemetry.SwerveDriveTelemetry.TelemetryVerbosity; @@ -84,7 +107,7 @@ public class SwerveDrive private final Alert tunerXRecommendation = new Alert("Swerve Drive", "Your Swerve Drive is compatible with Tuner X swerve generator, please consider using that instead of YAGSL. More information here!\n" + "https://pro.docs.ctr-electronics.com/en/latest/docs/tuner/tuner-swerve/index.html", - AlertType.WARNING); + AlertType.kWarning); /** * Field object. */ @@ -107,20 +130,24 @@ public class SwerveDrive * Correct for skew that scales with angular velocity in * {@link SwerveDrive#drive(Translation2d, double, boolean, boolean)} */ - public boolean angularVelocityCorrection = false; + public boolean angularVelocityCorrection = false; /** * Correct for skew that scales with angular velocity in * {@link SwerveDrive#setChassisSpeeds(ChassisSpeeds chassisSpeeds)} during auto. */ - public boolean autonomousAngularVelocityCorrection = false; + public boolean autonomousAngularVelocityCorrection = false; /** * Angular Velocity Correction Coefficent (expected values between -0.15 and 0.15). */ - public double angularVelocityCoefficient = 0; + public double angularVelocityCoefficient = 0; /** * Whether to correct heading when driving translationally. Set to true to enable. */ public boolean headingCorrection = false; + /** + * MapleSim SwerveDrive. + */ + private SwerveDriveSimulation mapleSimDrive; /** * Amount of seconds the duration of the timestep the speeds should be applied for. */ @@ -137,7 +164,7 @@ public class SwerveDrive * Class that calculates robot's yaw velocity using IMU measurements. Used for angularVelocityCorrection in * {@link SwerveDrive#drive(Translation2d, double, boolean, boolean)}. */ - private IMUVelocity imuVelocity; + private IMUVelocity imuVelocity; /** * Simulation of the swerve drive. */ @@ -161,7 +188,7 @@ public class SwerveDrive /** * Maximum speed of the robot in meters per second. */ - private double maxSpeedMPS; + private double maxChassisSpeedMPS; /** * Creates a new swerve drivebase subsystem. Robot is controlled via the {@link SwerveDrive#drive} method, or via the @@ -173,24 +200,61 @@ public class SwerveDrive * @param config The {@link SwerveDriveConfiguration} configuration to base the swerve drive off of. * @param controllerConfig The {@link SwerveControllerConfiguration} to use when creating the * {@link SwerveController}. - * @param maxSpeedMPS Maximum speed in meters per second, remember to use {@link Units#feetToMeters(double)} if - * you have feet per second! + * @param maxSpeedMPS Maximum speed of the robot in meters per second, remember to use + * {@link Units#feetToMeters(double)} if you have feet per second! + * @param startingPose Starting {@link Pose2d} on the field. */ public SwerveDrive( - SwerveDriveConfiguration config, SwerveControllerConfiguration controllerConfig, double maxSpeedMPS) + SwerveDriveConfiguration config, SwerveControllerConfiguration controllerConfig, double maxSpeedMPS, + Pose2d startingPose) { - this.maxSpeedMPS = maxSpeedMPS; + this.maxChassisSpeedMPS = maxSpeedMPS; swerveDriveConfiguration = config; swerveController = new SwerveController(controllerConfig); // Create Kinematics from swerve module locations. kinematics = new SwerveDriveKinematics(config.moduleLocationsMeters); odometryThread = new Notifier(this::updateOdometry); + this.swerveModules = config.modules; + // Create an integrator for angle if the robot is being simulated to emulate an IMU // If the robot is real, instantiate the IMU instead. if (SwerveDriveTelemetry.isSimulation) { - simIMU = new SwerveIMUSimulation(); + DriveTrainSimulationConfig simulationConfig = DriveTrainSimulationConfig.Default() + .withBumperSize( + Meters.of(config.getTracklength()) + .plus(Inches.of(5)), + Meters.of(config.getTrackwidth()) + .plus(Inches.of(5))) + .withRobotMass(Kilograms.of(config.physicalCharacteristics.robotMassKg)) + .withCustomModuleTranslations(config.moduleLocationsMeters) + .withGyro(config.getGyroSim()) + .withSwerveModule(() -> new SwerveModuleSimulation( + config.getDriveMotorSim(), + config.getAngleMotorSim(), + config.physicalCharacteristics.conversionFactor.drive.gearRatio, + config.physicalCharacteristics.conversionFactor.angle.gearRatio, + Volts.of(config.physicalCharacteristics.driveFrictionVoltage), + Volts.of(config.physicalCharacteristics.angleFrictionVoltage), + Inches.of( + config.physicalCharacteristics.conversionFactor.drive.diameter / + 2), + KilogramSquareMeters.of(0.02), + config.physicalCharacteristics.wheelGripCoefficientOfFriction)); + + mapleSimDrive = new SwerveDriveSimulation(simulationConfig, startingPose); + + // feed module simulation instances to modules + for (int i = 0; i < swerveModules.length; i++) + { + this.swerveModules[i].configureModuleSimulation(mapleSimDrive.getModules()[i], config.physicalCharacteristics); + } + + // register the drivetrain simulation + SimulatedArena.getInstance().addDriveTrainSimulation(mapleSimDrive); + + simIMU = new SwerveIMUSimulation(mapleSimDrive.getGyroSimulation()); imuReadingCache = new Cache<>(simIMU::getGyroRotation3d, 5L); } else { @@ -199,19 +263,15 @@ public SwerveDrive( imuReadingCache = new Cache<>(imu::getRotation3d, 5L); } - this.swerveModules = config.modules; - // odometry = new SwerveDriveOdometry(kinematics, getYaw(), getModulePositions()); swerveDrivePoseEstimator = new SwerveDrivePoseEstimator( kinematics, getYaw(), getModulePositions(), - new Pose2d(new Translation2d(0, 0), - Rotation2d.fromDegrees(0))); // x,y,heading in radians; Vision measurement std dev, higher=less weight + startingPose); // x,y,heading in radians; Vision measurement std dev, higher=less weight zeroGyro(); - setMaximumSpeed(maxSpeedMPS); // Initialize Telemetry if (SwerveDriveTelemetry.verbosity.ordinal() >= TelemetryVerbosity.POSE.ordinal()) @@ -244,11 +304,15 @@ public SwerveDrive( } SwerveDriveTelemetry.measuredStates = new double[SwerveDriveTelemetry.moduleCount * 2]; SwerveDriveTelemetry.desiredStates = new double[SwerveDriveTelemetry.moduleCount * 2]; + SwerveDriveTelemetry.desiredStatesObj = new SwerveModuleState[SwerveDriveTelemetry.moduleCount]; + SwerveDriveTelemetry.measuredStatesObj = new SwerveModuleState[SwerveDriveTelemetry.moduleCount]; } - odometryThread.startPeriodic(SwerveDriveTelemetry.isSimulation ? 0.01 : 0.02); + setOdometryPeriod(SwerveDriveTelemetry.isSimulation ? 0.004 : 0.02); checkIfTunerXCompatible(); + + HAL.report(kResourceType_RobotDrive, kRobotDriveSwerve_YAGSL); } /** @@ -300,6 +364,7 @@ private void checkIfTunerXCompatible() public void setOdometryPeriod(double period) { odometryThread.stop(); + SimulatedArena.overrideSimulationTimings(Seconds.of(period), 1); odometryThread.startPeriodic(period); } @@ -309,6 +374,7 @@ public void setOdometryPeriod(double period) public void stopOdometryThread() { odometryThread.stop(); + SimulatedArena.overrideSimulationTimings(Seconds.of(TimedRobot.kDefaultPeriod), 5); } /** @@ -381,9 +447,8 @@ public void setHeadingCorrection(boolean state, double deadband) public void driveFieldOrientedandRobotOriented(ChassisSpeeds fieldOrientedVelocity, ChassisSpeeds robotOrientedVelocity) { - ChassisSpeeds TotalVelocties = ChassisSpeeds.fromFieldRelativeSpeeds(fieldOrientedVelocity, getOdometryHeading()) - .plus(robotOrientedVelocity); - drive(TotalVelocties); + fieldOrientedVelocity.toRobotRelativeSpeeds(getOdometryHeading()); + drive(fieldOrientedVelocity.plus(robotOrientedVelocity)); } /** @@ -393,8 +458,8 @@ public void driveFieldOrientedandRobotOriented(ChassisSpeeds fieldOrientedVeloci */ public void driveFieldOriented(ChassisSpeeds velocity) { - ChassisSpeeds fieldOrientedVelocity = ChassisSpeeds.fromFieldRelativeSpeeds(velocity, getOdometryHeading()); - drive(fieldOrientedVelocity); + velocity.toRobotRelativeSpeeds(getOdometryHeading()); + drive(velocity); } /** @@ -405,8 +470,8 @@ public void driveFieldOriented(ChassisSpeeds velocity) */ public void driveFieldOriented(ChassisSpeeds velocity, Translation2d centerOfRotationMeters) { - ChassisSpeeds fieldOrientedVelocity = ChassisSpeeds.fromFieldRelativeSpeeds(velocity, getOdometryHeading()); - drive(fieldOrientedVelocity, centerOfRotationMeters); + velocity.toRobotRelativeSpeeds(getOdometryHeading()); + drive(velocity, centerOfRotationMeters); } /** @@ -454,12 +519,11 @@ public void drive( { // Creates a robot-relative ChassisSpeeds object, converting from field-relative speeds if // necessary. - ChassisSpeeds velocity = - fieldRelative - ? ChassisSpeeds.fromFieldRelativeSpeeds( - translation.getX(), translation.getY(), rotation, getOdometryHeading()) - : new ChassisSpeeds(translation.getX(), translation.getY(), rotation); - + ChassisSpeeds velocity = new ChassisSpeeds(translation.getX(), translation.getY(), rotation); + if (fieldRelative) + { + velocity.toRobotRelativeSpeeds(getOdometryHeading()); + } drive(velocity, isOpenLoop, centerOfRotationMeters); } @@ -483,12 +547,12 @@ public void drive( { // Creates a robot-relative ChassisSpeeds object, converting from field-relative speeds if // necessary. - ChassisSpeeds velocity = - fieldRelative - ? ChassisSpeeds.fromFieldRelativeSpeeds( - translation.getX(), translation.getY(), rotation, getOdometryHeading()) - : new ChassisSpeeds(translation.getX(), translation.getY(), rotation); + ChassisSpeeds velocity = new ChassisSpeeds(translation.getX(), translation.getY(), rotation); + if (fieldRelative) + { + velocity.toRobotRelativeSpeeds(getOdometryHeading()); + } drive(velocity, isOpenLoop, new Translation2d()); } @@ -497,25 +561,27 @@ public void drive( * states accordingly. Can use either open-loop or closed-loop velocity control for the wheel velocities. Applies * heading correction if enabled and necessary. * - * @param velocity The chassis speeds to set the robot to achieve. + * @param robotRelativeVelocity The chassis speeds to set the robot to achieve. * @param isOpenLoop Whether to use closed-loop velocity control. Set to true to disable closed-loop. * @param centerOfRotationMeters The center of rotation in meters, 0 is the center of the robot. */ - public void drive(ChassisSpeeds velocity, boolean isOpenLoop, Translation2d centerOfRotationMeters) + public void drive(ChassisSpeeds robotRelativeVelocity, boolean isOpenLoop, Translation2d centerOfRotationMeters) { - velocity = movementOptimizations(velocity, chassisVelocityCorrection, angularVelocityCorrection); + robotRelativeVelocity = movementOptimizations(robotRelativeVelocity, + chassisVelocityCorrection, + angularVelocityCorrection); // Heading Angular Velocity Deadband, might make a configuration option later. // Originally made by Team 1466 Webb Robotics. // Modified by Team 7525 Pioneers and BoiledBurntBagel of 6036 if (headingCorrection) { - if (Math.abs(velocity.omegaRadiansPerSecond) < HEADING_CORRECTION_DEADBAND - && (Math.abs(velocity.vxMetersPerSecond) > HEADING_CORRECTION_DEADBAND - || Math.abs(velocity.vyMetersPerSecond) > HEADING_CORRECTION_DEADBAND)) + if (Math.abs(robotRelativeVelocity.omegaRadiansPerSecond) < HEADING_CORRECTION_DEADBAND + && (Math.abs(robotRelativeVelocity.vxMetersPerSecond) > HEADING_CORRECTION_DEADBAND + || Math.abs(robotRelativeVelocity.vyMetersPerSecond) > HEADING_CORRECTION_DEADBAND)) { - velocity.omegaRadiansPerSecond = + robotRelativeVelocity.omegaRadiansPerSecond = swerveController.headingCalculate(getOdometryHeading().getRadians(), lastHeadingRadians); } else { @@ -524,39 +590,30 @@ public void drive(ChassisSpeeds velocity, boolean isOpenLoop, Translation2d cent } // Display commanded speed for testing - if (SwerveDriveTelemetry.verbosity == TelemetryVerbosity.INFO) - { - SmartDashboard.putString("RobotVelocity", velocity.toString()); - } if (SwerveDriveTelemetry.verbosity.ordinal() >= TelemetryVerbosity.LOW.ordinal()) { - SwerveDriveTelemetry.desiredChassisSpeeds[1] = velocity.vyMetersPerSecond; - SwerveDriveTelemetry.desiredChassisSpeeds[0] = velocity.vxMetersPerSecond; - SwerveDriveTelemetry.desiredChassisSpeeds[2] = Math.toDegrees(velocity.omegaRadiansPerSecond); + SwerveDriveTelemetry.desiredChassisSpeedsObj = robotRelativeVelocity; } // Calculate required module states via kinematics - SwerveModuleState[] swerveModuleStates = kinematics.toSwerveModuleStates(velocity, centerOfRotationMeters); + SwerveModuleState[] swerveModuleStates = kinematics.toSwerveModuleStates(robotRelativeVelocity, + centerOfRotationMeters); - setRawModuleStates(swerveModuleStates, velocity, isOpenLoop); + setRawModuleStates(swerveModuleStates, robotRelativeVelocity, isOpenLoop); } /** * Set the maximum speeds for desaturation. * - * @param attainableMaxModuleSpeedMetersPerSecond The absolute max speed that a module can reach in meters per - * second. * @param attainableMaxTranslationalSpeedMetersPerSecond The absolute max speed that your robot can reach while * translating in meters per second. * @param attainableMaxRotationalVelocityRadiansPerSecond The absolute max speed the robot can reach while rotating in * radians per second. */ public void setMaximumSpeeds( - double attainableMaxModuleSpeedMetersPerSecond, double attainableMaxTranslationalSpeedMetersPerSecond, double attainableMaxRotationalVelocityRadiansPerSecond) { - setMaximumSpeed(attainableMaxModuleSpeedMetersPerSecond); this.attainableMaxTranslationalSpeedMetersPerSecond = attainableMaxTranslationalSpeedMetersPerSecond; this.attainableMaxRotationalVelocityRadiansPerSecond = attainableMaxRotationalVelocityRadiansPerSecond; this.swerveController.config.maxAngularVelocity = attainableMaxRotationalVelocityRadiansPerSecond; @@ -564,13 +621,33 @@ public void setMaximumSpeeds( /** * Get the maximum velocity from {@link SwerveDrive#attainableMaxTranslationalSpeedMetersPerSecond} or - * {@link SwerveDrive#maxSpeedMPS} whichever is higher. + * {@link SwerveDrive#maxChassisSpeedMPS} whichever is higher. * * @return Maximum speed in meters/second. */ - public double getMaximumVelocity() + public double getMaximumChassisVelocity() + { + return Math.max(this.attainableMaxTranslationalSpeedMetersPerSecond, maxChassisSpeedMPS); + } + + /** + * Get the maximum drive velocity of a module as a {@link LinearVelocity}. + * + * @return {@link LinearVelocity} representing the maximum drive speed of a module. + */ + public LinearVelocity getMaximumModuleDriveVelocity() { - return Math.max(this.attainableMaxTranslationalSpeedMetersPerSecond, maxSpeedMPS); + return swerveModules[0].getMaxVelocity(); + } + + /** + * Get the maximum angular velocity of an azimuth/angle motor in the swerve module. + * + * @return {@link AngularVelocity} of the maximum azimuth/angle motor. + */ + public AngularVelocity getMaximumModuleAngleVelocity() + { + return swerveModules[0].getMaxAngularVelocity(); } /** @@ -579,7 +656,7 @@ public double getMaximumVelocity() * * @return Maximum angular velocity in radians per second. */ - public double getMaximumAngularVelocity() + public double getMaximumChassisAngularVelocity() { return Math.max(this.attainableMaxRotationalVelocityRadiansPerSecond, swerveController.config.maxAngularVelocity); } @@ -595,15 +672,16 @@ private void setRawModuleStates(SwerveModuleState[] desiredStates, ChassisSpeeds boolean isOpenLoop) { // Desaturates wheel speeds + double maxModuleSpeedMPS = getMaximumModuleDriveVelocity().in(MetersPerSecond); if (attainableMaxTranslationalSpeedMetersPerSecond != 0 || attainableMaxRotationalVelocityRadiansPerSecond != 0) { SwerveDriveKinematics.desaturateWheelSpeeds(desiredStates, desiredChassisSpeed, - maxSpeedMPS, + maxModuleSpeedMPS, attainableMaxTranslationalSpeedMetersPerSecond, attainableMaxRotationalVelocityRadiansPerSecond); } else { - SwerveDriveKinematics.desaturateWheelSpeeds(desiredStates, maxSpeedMPS); + SwerveDriveKinematics.desaturateWheelSpeeds(desiredStates, maxModuleSpeedMPS); } // Sets states @@ -615,17 +693,18 @@ private void setRawModuleStates(SwerveModuleState[] desiredStates, ChassisSpeeds /** * Set the module states (azimuth and velocity) directly. Used primarily for auto paths. Does not allow for usage of - * desaturateWheelSpeeds(SwerveModuleState[] moduleStates, ChassisSpeeds desiredChassisSpeed, double - * attainableMaxModuleSpeedMetersPerSecond, double attainableMaxTranslationalSpeedMetersPerSecond, double - * attainableMaxRotationalVelocityRadiansPerSecond) + * {@link SwerveDriveKinematics#desaturateWheelSpeeds(SwerveModuleState[] moduleStates, ChassisSpeeds + * desiredChassisSpeed, double attainableMaxModuleSpeedMetersPerSecond, double + * attainableMaxTranslationalSpeedMetersPerSecond, double attainableMaxRotationalVelocityRadiansPerSecond)} * * @param desiredStates A list of SwerveModuleStates to send to the modules. * @param isOpenLoop Whether to use closed-loop velocity control. Set to true to disable closed-loop. */ public void setModuleStates(SwerveModuleState[] desiredStates, boolean isOpenLoop) { + double maxModuleSpeedMPS = getMaximumModuleDriveVelocity().in(MetersPerSecond); desiredStates = kinematics.toSwerveModuleStates(kinematics.toChassisSpeeds(desiredStates)); - SwerveDriveKinematics.desaturateWheelSpeeds(desiredStates, maxSpeedMPS); + SwerveDriveKinematics.desaturateWheelSpeeds(desiredStates, maxModuleSpeedMPS); // Sets states for (SwerveModule module : swerveModules) @@ -634,27 +713,68 @@ public void setModuleStates(SwerveModuleState[] desiredStates, boolean isOpenLoo } } + /** + * Drive the robot using the {@link SwerveModuleState}, it is recommended to have + * {@link SwerveDrive#setCosineCompensator(boolean)} set to false for this.
+ *

+ * + * @param robotRelativeVelocity Robot relative {@link ChassisSpeeds} + * @param states Corresponding {@link SwerveModuleState} to use (not checked against the + * {@param robotRelativeVelocity}). + * @param feedforwardForces Feedforward forces generated by set-point generator + */ + public void drive(ChassisSpeeds robotRelativeVelocity, SwerveModuleState[] states, Force[] feedforwardForces) + { + if (SwerveDriveTelemetry.verbosity.ordinal() >= TelemetryVerbosity.LOW.ordinal()) + { + SwerveDriveTelemetry.desiredChassisSpeedsObj = robotRelativeVelocity; + } + for (SwerveModule module : swerveModules) + { + // from the module configuration, obtain necessary information to calculate feed-forward + // Warning: Will not work well if motor is not what we are expecting. + // Warning: Should replace module.getDriveMotor().simMotor with expected motor type first. + DCMotor driveMotorModel = module.configuration.driveMotor.getSimMotor(); + double driveGearRatio = module.configuration.conversionFactors.drive.gearRatio; + double wheelRadiusMeters = Units.inchesToMeters(module.configuration.conversionFactors.drive.diameter) / 2; + + // calculation: + double desiredGroundSpeedMPS = states[module.moduleNumber].speedMetersPerSecond; + double feedforwardVoltage = driveMotorModel.getVoltage( + // Since: (1) torque = force * momentOfForce; (2) torque (on wheel) = torque (on motor) * gearRatio + // torque (on motor) = force * wheelRadius / gearRatio + feedforwardForces[module.moduleNumber].in(Newtons) * wheelRadiusMeters / driveGearRatio, + // Since: (1) linear velocity = angularVelocity * wheelRadius; (2) wheelVelocity = motorVelocity / gearRatio + // motorAngularVelocity = linearVelocity / wheelRadius * gearRatio + desiredGroundSpeedMPS / wheelRadiusMeters * driveGearRatio + ); + module.setDesiredState( + states[module.moduleNumber], + false, + feedforwardVoltage + ); + } + } + /** * Set chassis speeds with closed-loop velocity control. * - * @param chassisSpeeds Chassis speeds to set. + * @param robotRelativeSpeeds Chassis speeds to set. */ - public void setChassisSpeeds(ChassisSpeeds chassisSpeeds) + public void setChassisSpeeds(ChassisSpeeds robotRelativeSpeeds) { - chassisSpeeds = movementOptimizations(chassisSpeeds, - autonomousChassisVelocityCorrection, - autonomousAngularVelocityCorrection); + robotRelativeSpeeds = movementOptimizations(robotRelativeSpeeds, + autonomousChassisVelocityCorrection, + autonomousAngularVelocityCorrection); - SwerveDriveTelemetry.desiredChassisSpeeds[1] = chassisSpeeds.vyMetersPerSecond; - SwerveDriveTelemetry.desiredChassisSpeeds[0] = chassisSpeeds.vxMetersPerSecond; - SwerveDriveTelemetry.desiredChassisSpeeds[2] = Math.toDegrees(chassisSpeeds.omegaRadiansPerSecond); + SwerveDriveTelemetry.desiredChassisSpeedsObj = robotRelativeSpeeds; - setRawModuleStates(kinematics.toSwerveModuleStates(chassisSpeeds), chassisSpeeds, false); + setRawModuleStates(kinematics.toSwerveModuleStates(robotRelativeSpeeds), robotRelativeSpeeds, false); } /** - * Gets the current pose (position and rotation) of the robot, as reported by odometry. + * Gets the measured pose (position and rotation) of the robot, as reported by odometry. * * @return The robot's pose */ @@ -668,7 +788,38 @@ public Pose2d getPose() } /** - * Gets the current field-relative velocity (x, y and omega) of the robot + * Gets the maple-sim drivetrain simulation instance This is used to add intake simulation / launch game pieces from + * the robot + * + * @return an optional maple-sim {@link SwerveDriveSimulation} object, or {@link Optional#empty()} when calling from a + * real robot + */ + public Optional getMapleSimDrive() + { + if (SwerveDriveTelemetry.isSimulation) + { + return Optional.of(mapleSimDrive); + } + + return Optional.empty(); + } + + /** + * Gets the actual pose of the drivetrain during simulation + * + * @return an {@link Optional} {@link Pose2d}, representing the drivetrain pose during simulation, or an empty + * optional when running on real robot + */ + public Optional getSimulationDriveTrainPose() + { + odometryLock.lock(); + Optional simulationPose = getMapleSimDrive().map(AbstractDriveTrainSimulation::getSimulatedDriveTrainPose); + odometryLock.unlock(); + return simulationPose; + } + + /** + * Gets the measured field-relative robot velocity (x, y and omega) * * @return A ChassisSpeeds object of the current field-relative velocity */ @@ -676,10 +827,10 @@ public ChassisSpeeds getFieldVelocity() { // ChassisSpeeds has a method to convert from field-relative to robot-relative speeds, // but not the reverse. However, because this transform is a simple rotation, negating the - // angle - // given as the robot angle reverses the direction of rotation, and the conversion is reversed. - return ChassisSpeeds.fromFieldRelativeSpeeds( - kinematics.toChassisSpeeds(getStates()), getOdometryHeading().unaryMinus()); + // angle given as the robot angle reverses the direction of rotation, and the conversion is reversed. + ChassisSpeeds robotRelativeSpeeds = kinematics.toChassisSpeeds(getStates()); + robotRelativeSpeeds.toFieldRelativeSpeeds(getOdometryHeading());//.unaryMinus()); + return robotRelativeSpeeds; } /** @@ -703,8 +854,15 @@ public void resetOdometry(Pose2d pose) { odometryLock.lock(); swerveDrivePoseEstimator.resetPosition(getYaw(), getModulePositions(), pose); + if (SwerveDriveTelemetry.isSimulation) + { + mapleSimDrive.setSimulationWorldPose(pose); + } odometryLock.unlock(); - kinematics.toSwerveModuleStates(ChassisSpeeds.fromFieldRelativeSpeeds(0, 0, 0, getYaw())); + ChassisSpeeds robotRelativeSpeeds = new ChassisSpeeds(); + robotRelativeSpeeds.toFieldRelativeSpeeds(getYaw()); + kinematics.toSwerveModuleStates(robotRelativeSpeeds); + } /** @@ -888,48 +1046,6 @@ public void setModuleEncoderAutoSynchronize(boolean enabled, double deadband) } - /** - * Set the maximum speed of the drive motors, modified {@link SwerveDrive#maxSpeedMPS} which is used for the - * {@link SwerveDrive#setRawModuleStates(SwerveModuleState[], ChassisSpeeds, boolean)} function and - * {@link SwerveController#getTargetSpeeds(double, double, double, double, double)} functions. This function overrides - * what was placed in the JSON and could damage your motor/robot if set too high or unachievable rates. - * - * @param maximumSpeed Maximum speed for the drive motors in meters / second. - * @param updateModuleFeedforward Update the swerve module feedforward to account for the new maximum speed. This - * should be true unless you have replaced the drive motor feedforward with - * {@link SwerveDrive#replaceSwerveModuleFeedforward(SimpleMotorFeedforward)} - * @param optimalVoltage Optimal voltage to use for the feedforward. - */ - public void setMaximumSpeed(double maximumSpeed, boolean updateModuleFeedforward, double optimalVoltage) - { - maxSpeedMPS = maximumSpeed; - swerveDriveConfiguration.physicalCharacteristics.optimalVoltage = optimalVoltage; - for (SwerveModule module : swerveModules) - { - module.maxSpeed = maximumSpeed; - if (updateModuleFeedforward) - { - module.setFeedforward(SwerveMath.createDriveFeedforward(optimalVoltage, - maximumSpeed, - swerveDriveConfiguration.physicalCharacteristics.wheelGripCoefficientOfFriction)); - } - } - } - - /** - * Set the maximum speed of the drive motors, modified {@link SwerveDrive#maxSpeedMPS} which is used for the - * {@link SwerveDrive#setRawModuleStates(SwerveModuleState[], ChassisSpeeds, boolean)} function and - * {@link SwerveController#getTargetSpeeds(double, double, double, double, double)} functions. This function overrides - * what was placed in the JSON and could damage your motor/robot if set too high or unachievable rates. Overwrites the - * {@link SwerveModule#setFeedforward(SimpleMotorFeedforward)}. - * - * @param maximumSpeed Maximum speed for the drive motors in meters / second. - */ - public void setMaximumSpeed(double maximumSpeed) - { - setMaximumSpeed(maximumSpeed, true, swerveDriveConfiguration.physicalCharacteristics.optimalVoltage); - } - /** * Point all modules toward the robot center, thus making the robot very difficult to move. Forcing the robot to keep * the current pose. @@ -943,10 +1059,7 @@ public void lockPose() new SwerveModuleState(0, swerveModule.configuration.moduleLocation.getAngle()); if (SwerveDriveTelemetry.verbosity.ordinal() >= TelemetryVerbosity.INFO.ordinal()) { - SwerveDriveTelemetry.desiredStates[swerveModule.moduleNumber * 2] = - desiredState.angle.getDegrees(); - SwerveDriveTelemetry.desiredStates[(swerveModule.moduleNumber * 2) + 1] = - desiredState.speedMetersPerSecond; + SwerveDriveTelemetry.desiredStatesObj[swerveModule.moduleNumber] = desiredState; } swerveModule.setDesiredState(desiredState, false, true); @@ -996,34 +1109,42 @@ public void replaceSwerveModuleFeedforward(SimpleMotorFeedforward driveFeedforwa public void updateOdometry() { odometryLock.lock(); + invalidateCache(); try { // Update odometry swerveDrivePoseEstimator.update(getYaw(), getModulePositions()); - // Update angle accumulator if the robot is simulated - if (SwerveDriveTelemetry.verbosity.ordinal() >= TelemetryVerbosity.INFO.ordinal()) + if (SwerveDriveTelemetry.isSimulation) { - Pose2d[] modulePoses = getSwerveModulePoses(swerveDrivePoseEstimator.getEstimatedPosition()); - if (SwerveDriveTelemetry.isSimulation) + try + { + SimulatedArena.getInstance().simulationPeriodic(); + } catch (Exception e) { - simIMU.updateOdometry( - kinematics, - getStates(), - modulePoses, - field); + DriverStation.reportError("MapleSim error", false); } + } - ChassisSpeeds measuredChassisSpeeds = getRobotVelocity(); - SwerveDriveTelemetry.measuredChassisSpeeds[1] = measuredChassisSpeeds.vyMetersPerSecond; - SwerveDriveTelemetry.measuredChassisSpeeds[0] = measuredChassisSpeeds.vxMetersPerSecond; - SwerveDriveTelemetry.measuredChassisSpeeds[2] = Math.toDegrees(measuredChassisSpeeds.omegaRadiansPerSecond); - SwerveDriveTelemetry.robotRotation = getOdometryHeading().getDegrees(); + // Update angle accumulator if the robot is simulated + if (SwerveDriveTelemetry.verbosity.ordinal() >= TelemetryVerbosity.INFO.ordinal()) + { + SwerveDriveTelemetry.measuredChassisSpeedsObj = getRobotVelocity(); + SwerveDriveTelemetry.robotRotationObj = getOdometryHeading(); } if (SwerveDriveTelemetry.verbosity.ordinal() >= TelemetryVerbosity.POSE.ordinal()) { - field.setRobotPose(swerveDrivePoseEstimator.getEstimatedPosition()); + if (SwerveDriveTelemetry.isSimulation) + { + field.setRobotPose(mapleSimDrive.getSimulatedDriveTrainPose()); + field.getObject("OdometryPose").setPose(swerveDrivePoseEstimator.getEstimatedPosition()); + field.getObject("XModules").setPoses(getSwerveModulePoses(mapleSimDrive.getSimulatedDriveTrainPose())); + + } else + { + field.setRobotPose(swerveDrivePoseEstimator.getEstimatedPosition()); + } } double sumVelocity = 0; @@ -1039,8 +1160,7 @@ public void updateOdometry() } if (SwerveDriveTelemetry.verbosity.ordinal() >= TelemetryVerbosity.INFO.ordinal()) { - SwerveDriveTelemetry.measuredStates[module.moduleNumber * 2] = moduleState.angle.getDegrees(); - SwerveDriveTelemetry.measuredStates[(module.moduleNumber * 2) + 1] = moduleState.speedMetersPerSecond; + SwerveDriveTelemetry.measuredStatesObj[module.moduleNumber] = moduleState; } } @@ -1065,6 +1185,18 @@ public void updateOdometry() odometryLock.unlock(); } + /** + * Invalidate all {@link Cache} object used by the {@link SwerveDrive} + */ + public void invalidateCache() + { + imuReadingCache.update(); + for (SwerveModule module : swerveModules) + { + module.invalidateCache(); + } + } + /** * Synchronize angle motor integrated encoders with data from absolute encoders. */ @@ -1259,8 +1391,8 @@ public void setCosineCompensator(boolean enabled) /** * Sets the Chassis discretization seconds as well as enableing/disabling the Chassis velocity correction in teleop * - * @param enable Enable chassis velocity correction, which will use - * {@link ChassisSpeeds#discretize(ChassisSpeeds, double)} with the following. + * @param enable Enable chassis velocity correction, which will use {@link ChassisSpeeds#discretize(double)} with + * the following. * @param dtSeconds The duration of the timestep the speeds should be applied for. */ public void setChassisDiscretization(boolean enable, double dtSeconds) @@ -1276,10 +1408,10 @@ public void setChassisDiscretization(boolean enable, double dtSeconds) * Sets the Chassis discretization seconds as well as enableing/disabling the Chassis velocity correction in teleop * and/or auto * - * @param useInTeleop Enable chassis velocity correction, which will use - * {@link ChassisSpeeds#discretize(ChassisSpeeds, double)} with the following in teleop. - * @param useInAuto Enable chassis velocity correction, which will use - * {@link ChassisSpeeds#discretize(ChassisSpeeds, double)} with the following in auto. + * @param useInTeleop Enable chassis velocity correction, which will use {@link ChassisSpeeds#discretize(double)} with + * the following in teleop. + * @param useInAuto Enable chassis velocity correction, which will use {@link ChassisSpeeds#discretize(double)} with + * the following in auto. * @param dtSeconds The duration of the timestep the speeds should be applied for. */ public void setChassisDiscretization(boolean useInTeleop, boolean useInAuto, double dtSeconds) @@ -1320,49 +1452,62 @@ public void setAngularVelocityCompensation(boolean useInTeleop, boolean useInAut /** * Correct for skew that worsens as angular velocity increases * - * @param velocity The chassis speeds to set the robot to achieve. + * @param robotRelativeVelocity The chassis speeds to set the robot to achieve. * @return {@link ChassisSpeeds} of the robot after angular velocity skew correction. */ - public ChassisSpeeds angularVelocitySkewCorrection(ChassisSpeeds velocity) + public ChassisSpeeds angularVelocitySkewCorrection(ChassisSpeeds robotRelativeVelocity) { var angularVelocity = new Rotation2d(imuVelocity.getVelocity() * angularVelocityCoefficient); if (angularVelocity.getRadians() != 0.0) { - velocity = ChassisSpeeds.fromRobotRelativeSpeeds( - velocity.vxMetersPerSecond, - velocity.vyMetersPerSecond, - velocity.omegaRadiansPerSecond, - getOdometryHeading()); - velocity = ChassisSpeeds.fromFieldRelativeSpeeds(velocity, getOdometryHeading().plus(angularVelocity)); + robotRelativeVelocity.toFieldRelativeSpeeds(getOdometryHeading()); + robotRelativeVelocity.toRobotRelativeSpeeds(getOdometryHeading().plus(angularVelocity)); } - return velocity; + return robotRelativeVelocity; } /** * Enable desired drive corrections * - * @param velocity The chassis speeds to set the robot to achieve. + * @param robotRelativeVelocity The chassis speeds to set the robot to achieve. * @param uesChassisDiscretize Correct chassis velocity using 254's correction. * @param useAngularVelocitySkewCorrection Use the robot's angular velocity to correct for skew. * @return The chassis speeds after optimizations. */ - private ChassisSpeeds movementOptimizations(ChassisSpeeds velocity, boolean uesChassisDiscretize, + private ChassisSpeeds movementOptimizations(ChassisSpeeds robotRelativeVelocity, boolean uesChassisDiscretize, boolean useAngularVelocitySkewCorrection) { if (useAngularVelocitySkewCorrection) { - velocity = angularVelocitySkewCorrection(velocity); + robotRelativeVelocity = angularVelocitySkewCorrection(robotRelativeVelocity); } // Thank you to Jared Russell FRC254 for Open Loop Compensation Code // https://www.chiefdelphi.com/t/whitepaper-swerve-drive-skew-and-second-order-kinematics/416964/5 if (uesChassisDiscretize) { - velocity = ChassisSpeeds.discretize(velocity, discretizationdtSeconds); + robotRelativeVelocity.discretize(discretizationdtSeconds); } - return velocity; + return robotRelativeVelocity; } + /** + * Convert a {@link ChassisSpeeds} to {@link SwerveModuleState[]} for use elsewhere. + * + * @param robotRelativeVelocity {@link ChassisSpeeds} velocity to use. + * @param optimize Perform chassis velocity correction or angular velocity correction. + * @return {@link SwerveModuleState[]} for use elsewhere. + */ + public SwerveModuleState[] toServeModuleStates(ChassisSpeeds robotRelativeVelocity, boolean optimize) + { + if (optimize) + { + robotRelativeVelocity = movementOptimizations(robotRelativeVelocity, + chassisVelocityCorrection, + angularVelocityCorrection); + } + return kinematics.toSwerveModuleStates(robotRelativeVelocity); + } } diff --git a/swervelib/SwerveDriveTest.java b/swervelib/SwerveDriveTest.java index a3d71dfe..70405a3e 100644 --- a/swervelib/SwerveDriveTest.java +++ b/swervelib/SwerveDriveTest.java @@ -1,20 +1,20 @@ package swervelib; -import static edu.wpi.first.units.MutableMeasure.mutable; import static edu.wpi.first.units.Units.Degrees; import static edu.wpi.first.units.Units.DegreesPerSecond; +import static edu.wpi.first.units.Units.Meter; import static edu.wpi.first.units.Units.Meters; import static edu.wpi.first.units.Units.MetersPerSecond; import static edu.wpi.first.units.Units.Seconds; import static edu.wpi.first.units.Units.Volts; import edu.wpi.first.math.geometry.Rotation2d; -import edu.wpi.first.units.Angle; -import edu.wpi.first.units.Distance; -import edu.wpi.first.units.Measure; -import edu.wpi.first.units.MutableMeasure; -import edu.wpi.first.units.Velocity; -import edu.wpi.first.units.Voltage; +import edu.wpi.first.units.measure.MutAngle; +import edu.wpi.first.units.measure.MutAngularVelocity; +import edu.wpi.first.units.measure.MutDistance; +import edu.wpi.first.units.measure.MutLinearVelocity; +import edu.wpi.first.units.measure.MutVoltage; +import edu.wpi.first.units.measure.Voltage; import edu.wpi.first.wpilibj.DriverStation; import edu.wpi.first.wpilibj.RobotController; import edu.wpi.first.wpilibj.Timer; @@ -37,23 +37,23 @@ public class SwerveDriveTest /** * Tracks the voltage being applied to a motor */ - private static final MutableMeasure m_appliedVoltage = mutable(Volts.of(0)); + private static final MutVoltage m_appliedVoltage = new MutVoltage(0, 0, Volts); /** * Tracks the distance travelled of a position motor */ - private static final MutableMeasure m_distance = mutable(Meters.of(0)); + private static final MutDistance m_distance = new MutDistance(0, 0, Meter); /** * Tracks the velocity of a positional motor */ - private static final MutableMeasure> m_velocity = mutable(MetersPerSecond.of(0)); + private static final MutLinearVelocity m_velocity = new MutLinearVelocity(0, 9, MetersPerSecond); /** * Tracks the rotations of an angular motor */ - private static final MutableMeasure m_anglePosition = mutable(Degrees.of(0)); + private static final MutAngle m_anglePosition = new MutAngle(0, 0, Degrees); /** * Tracks the velocity of an angular motor */ - private static final MutableMeasure> m_angVelocity = mutable(DegreesPerSecond.of(0)); + private static final MutAngularVelocity m_angVelocity = new MutAngularVelocity(0, 0, DegreesPerSecond); /** * Set the angle of the modules to a given {@link Rotation2d} @@ -214,7 +214,7 @@ public static double findCouplingRatio(SwerveDrive swerveDrive, double volts, bo Timer.delay(1); Rotation2d startingAbsoluteEncoderPosition = Rotation2d.fromDegrees(absoluteEncoder.getAbsolutePosition()); double driveEncoderPositionRotations = module.getDriveMotor().getPosition() / - module.configuration.conversionFactors.drive; + module.configuration.conversionFactors.drive.factor; if (automatic) { module.getAngleMotor().setVoltage(volts); @@ -230,8 +230,9 @@ public static double findCouplingRatio(SwerveDrive swerveDrive, double volts, bo false); Timer.delay(60); } - double couplingRatio = (module.getDriveMotor().getPosition() / module.configuration.conversionFactors.drive) - - driveEncoderPositionRotations; + double couplingRatio = + (module.getDriveMotor().getPosition() / module.configuration.conversionFactors.drive.factor) - + driveEncoderPositionRotations; DriverStation.reportWarning(module.configuration.name + " Coupling Ratio: " + couplingRatio, false); couplingRatioSum += couplingRatio; } @@ -308,7 +309,7 @@ public static SysIdRoutine setDriveSysIdRoutine(Config config, SubsystemBase swe SwerveDrive swerveDrive, double maxVolts) { return new SysIdRoutine(config, new SysIdRoutine.Mechanism( - (Measure voltage) -> { + (Voltage voltage) -> { SwerveDriveTest.centerModules(swerveDrive); SwerveDriveTest.powerDriveMotorsVoltage(swerveDrive, Math.min(voltage.in(Volts), maxVolts)); }, @@ -380,7 +381,7 @@ public static SysIdRoutine setAngleSysIdRoutine(Config config, SubsystemBase swe SwerveDrive swerveDrive) { return new SysIdRoutine(config, new SysIdRoutine.Mechanism( - (Measure voltage) -> { + (Voltage voltage) -> { SwerveDriveTest.powerAngleMotorsVoltage(swerveDrive, voltage.in(Volts)); SwerveDriveTest.powerDriveMotorsVoltage(swerveDrive, 0); }, diff --git a/swervelib/SwerveModule.java b/swervelib/SwerveModule.java index 8b5dec10..97967f24 100644 --- a/swervelib/SwerveModule.java +++ b/swervelib/SwerveModule.java @@ -1,20 +1,29 @@ package swervelib; -import com.revrobotics.CANSparkMax; -import com.revrobotics.MotorFeedbackSensor; +import static edu.wpi.first.units.Units.MetersPerSecond; +import static edu.wpi.first.units.Units.RadiansPerSecond; +import static edu.wpi.first.units.Units.RotationsPerSecond; + import edu.wpi.first.math.controller.SimpleMotorFeedforward; import edu.wpi.first.math.geometry.Rotation2d; import edu.wpi.first.math.kinematics.SwerveModulePosition; import edu.wpi.first.math.kinematics.SwerveModuleState; +import edu.wpi.first.units.measure.AngularVelocity; +import edu.wpi.first.units.measure.LinearVelocity; +import edu.wpi.first.wpilibj.Alert; +import edu.wpi.first.wpilibj.Alert.AlertType; import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard; +import swervelib.encoders.SparkMaxEncoderSwerve; import swervelib.encoders.SwerveAbsoluteEncoder; import swervelib.math.SwerveMath; +import swervelib.motors.SparkMaxBrushedMotorSwerve; +import swervelib.motors.SparkMaxSwerve; import swervelib.motors.SwerveMotor; import swervelib.parser.Cache; import swervelib.parser.PIDFConfig; import swervelib.parser.SwerveModuleConfiguration; +import swervelib.parser.SwerveModulePhysicalCharacteristics; import swervelib.simulation.SwerveModuleSimulation; -import swervelib.telemetry.Alert; import swervelib.telemetry.SwerveDriveTelemetry; import swervelib.telemetry.SwerveDriveTelemetry.TelemetryVerbosity; @@ -43,7 +52,7 @@ public class SwerveModule /** * Module number for kinematics, usually 0 to 3. front left -> front right -> back left -> back right. */ - public final int moduleNumber; + public final int moduleNumber; /** * Swerve Motors. */ @@ -85,9 +94,13 @@ public class SwerveModule */ private final String rawDriveVelName; /** - * Maximum speed of the drive motors in meters per second. + * Maximum {@link LinearVelocity} for the drive motor of the swerve module. + */ + private LinearVelocity maxDriveVelocity; + /** + * Maximum {@link AngularVelocity} for the azimuth/angle motor of the swerve module. */ - public double maxSpeed; + private AngularVelocity maxAngularVelocity; /** * Feedforward for the drive motor during closed loop control. */ @@ -95,7 +108,7 @@ public class SwerveModule /** * Anti-Jitter AKA auto-centering disabled. */ - private boolean antiJitterEnabled = true; + private boolean antiJitterEnabled = true; /** * Last swerve module state applied. */ @@ -111,15 +124,15 @@ public class SwerveModule /** * Encoder synchronization queued. */ - private boolean synchronizeEncoderQueued = false; + private boolean synchronizeEncoderQueued = false; /** * Encoder, Absolute encoder synchronization enabled. */ - private boolean synchronizeEncoderEnabled = false; + private boolean synchronizeEncoderEnabled = false; /** * Encoder synchronization deadband in degrees. */ - private double synchronizeEncoderDeadband = 3; + private double synchronizeEncoderDeadband = 3; /** @@ -127,11 +140,8 @@ public class SwerveModule * * @param moduleNumber Module number for kinematics. * @param moduleConfiguration Module constants containing CAN ID's and offsets. - * @param driveFeedforward Drive motor feedforward created by - * {@link SwerveMath#createDriveFeedforward(double, double, double)}. */ - public SwerveModule(int moduleNumber, SwerveModuleConfiguration moduleConfiguration, - SimpleMotorFeedforward driveFeedforward) + public SwerveModule(int moduleNumber, SwerveModuleConfiguration moduleConfiguration) { // angle = 0; // speed = 0; @@ -141,15 +151,15 @@ public SwerveModule(int moduleNumber, SwerveModuleConfiguration moduleConfigurat configuration = moduleConfiguration; angleOffset = moduleConfiguration.angleOffset; - // Initialize Feedforwards. - driveMotorFeedforward = driveFeedforward; - // Create motors from configuration and reset them to defaults. angleMotor = moduleConfiguration.angleMotor; driveMotor = moduleConfiguration.driveMotor; angleMotor.factoryDefaults(); driveMotor.factoryDefaults(); + // Initialize Feedforwards. + driveMotorFeedforward = getDefaultFeedforward(); + // Configure voltage comp, current limit, and ramp rate. angleMotor.setVoltageCompensation(configuration.physicalCharacteristics.optimalVoltage); driveMotor.setVoltageCompensation(configuration.physicalCharacteristics.optimalVoltage); @@ -166,13 +176,18 @@ public SwerveModule(int moduleNumber, SwerveModuleConfiguration moduleConfigurat absoluteEncoder.configure(moduleConfiguration.absoluteEncoderInverted); } + if (SwerveDriveTelemetry.isSimulation) + { + simModule = new SwerveModuleSimulation(); + } + // Setup the cache for the absolute encoder position. - absolutePositionCache = new Cache<>(this::getRawAbsolutePosition, 15); + absolutePositionCache = new Cache<>(this::getRawAbsolutePosition, 20); // Config angle motor/controller - angleMotor.configureIntegratedEncoder(moduleConfiguration.conversionFactors.angle); + angleMotor.configureIntegratedEncoder(moduleConfiguration.conversionFactors.angle.factor); angleMotor.configurePIDF(moduleConfiguration.anglePIDF); - angleMotor.configurePIDWrapping(0, 180); + angleMotor.configurePIDWrapping(0, 360); angleMotor.setInverted(moduleConfiguration.angleMotorInverted); angleMotor.setMotorBrake(false); @@ -183,7 +198,7 @@ public SwerveModule(int moduleNumber, SwerveModuleConfiguration moduleConfigurat } // Config drive motor/controller - driveMotor.configureIntegratedEncoder(moduleConfiguration.conversionFactors.drive); + driveMotor.configureIntegratedEncoder(moduleConfiguration.conversionFactors.drive.factor); driveMotor.configurePIDF(moduleConfiguration.velocityPIDF); driveMotor.setInverted(moduleConfiguration.driveMotorInverted); driveMotor.setMotorBrake(true); @@ -191,13 +206,8 @@ public SwerveModule(int moduleNumber, SwerveModuleConfiguration moduleConfigurat driveMotor.burnFlash(); angleMotor.burnFlash(); - drivePositionCache = new Cache<>(driveMotor::getPosition, 15); - driveVelocityCache = new Cache<>(driveMotor::getVelocity, 15); - - if (SwerveDriveTelemetry.isSimulation) - { - simModule = new SwerveModuleSimulation(); - } + drivePositionCache = new Cache<>(driveMotor::getPosition, 20); + driveVelocityCache = new Cache<>(driveMotor::getVelocity, 20); // Force a cache update on init. driveVelocityCache.update(); @@ -210,11 +220,11 @@ public SwerveModule(int moduleNumber, SwerveModuleConfiguration moduleConfigurat noEncoderWarning = new Alert("Motors", "There is no Absolute Encoder on module #" + moduleNumber, - Alert.AlertType.WARNING); + AlertType.kWarning); encoderOffsetWarning = new Alert("Motors", "Pushing the Absolute Encoder offset to the encoder failed on module #" + moduleNumber, - Alert.AlertType.WARNING); + AlertType.kWarning); rawAbsoluteAngleName = "swerve/modules/" + configuration.name + "/Raw Absolute Encoder"; adjAbsoluteAngleName = "swerve/modules/" + configuration.name + "/Adjusted Absolute Encoder"; @@ -224,6 +234,20 @@ public SwerveModule(int moduleNumber, SwerveModuleConfiguration moduleConfigurat rawDriveVelName = "swerve/modules/" + configuration.name + "/Raw Drive Velocity"; } + /** + * Get the default {@link SimpleMotorFeedforward} for the swerve module drive motor. + * + * @return {@link SimpleMotorFeedforward} using motor details. + */ + public SimpleMotorFeedforward getDefaultFeedforward() + { + double nominalVoltage = driveMotor.getSimMotor().nominalVoltageVolts; + double maxDriveSpeedMPS = getMaxVelocity().in(MetersPerSecond); + return SwerveMath.createDriveFeedforward(nominalVoltage, + maxDriveSpeedMPS, + configuration.physicalCharacteristics.wheelGripCoefficientOfFriction); + } + /** * Set the voltage compensation for the swerve module motor. * @@ -361,28 +385,45 @@ public void setAnglePIDF(PIDFConfig config) */ public void setDesiredState(SwerveModuleState desiredState, boolean isOpenLoop, boolean force) { - desiredState = SwerveModuleState.optimize(desiredState, Rotation2d.fromDegrees(getAbsolutePosition())); + + desiredState.optimize(Rotation2d.fromDegrees(getAbsolutePosition())); // If we are forcing the angle if (!force && antiJitterEnabled) { // Prevents module rotation if speed is less than 1% - SwerveMath.antiJitter(desiredState, lastState, Math.min(maxSpeed, 4)); + SwerveMath.antiJitter(desiredState, lastState, Math.min(maxDriveVelocity.in(MetersPerSecond), 4)); } // Cosine compensation. - double velocity = configuration.useCosineCompensator - ? getCosineCompensatedVelocity(desiredState) - : desiredState.speedMetersPerSecond; + LinearVelocity nextVelocity = configuration.useCosineCompensator + ? getCosineCompensatedVelocity(desiredState) + : MetersPerSecond.of(desiredState.speedMetersPerSecond); + LinearVelocity curVelocity = MetersPerSecond.of(lastState.speedMetersPerSecond); + desiredState.speedMetersPerSecond = nextVelocity.magnitude(); + + setDesiredState(desiredState, isOpenLoop, driveMotorFeedforward.calculate(curVelocity, nextVelocity).magnitude()); + } + + /** + * Set the desired state of the swerve module.
WARNING: If you are not using one of the functions from + * {@link SwerveDrive} you may screw up {@link SwerveDrive#kinematics} + * + * @param desiredState Desired swerve module state. + * @param isOpenLoop Whether to use open loop (direct percent) or direct velocity control. + * @param driveFeedforwardVoltage Drive motor controller feedforward as a voltage. + */ + public void setDesiredState(SwerveModuleState desiredState, boolean isOpenLoop, + double driveFeedforwardVoltage) + { if (isOpenLoop) { - double percentOutput = desiredState.speedMetersPerSecond / maxSpeed; + double percentOutput = desiredState.speedMetersPerSecond / maxDriveVelocity.in(MetersPerSecond); driveMotor.set(percentOutput); } else { - driveMotor.setReference(velocity, driveMotorFeedforward.calculate(velocity)); - desiredState.speedMetersPerSecond = velocity; + driveMotor.setReference(desiredState.speedMetersPerSecond, driveFeedforwardVoltage); } // Prevent module rotation if angle is the same as the previous angle. @@ -408,10 +449,10 @@ public void setDesiredState(SwerveModuleState desiredState, boolean isOpenLoop, simModule.updateStateAndPosition(desiredState); } + // TODO: Change and move to SwerveDriveTelemetry if (SwerveDriveTelemetry.verbosity.ordinal() >= TelemetryVerbosity.INFO.ordinal()) { - SwerveDriveTelemetry.desiredStates[moduleNumber * 2] = desiredState.angle.getDegrees(); - SwerveDriveTelemetry.desiredStates[(moduleNumber * 2) + 1] = velocity; + SwerveDriveTelemetry.desiredStatesObj[moduleNumber] = desiredState; } if (SwerveDriveTelemetry.verbosity == TelemetryVerbosity.HIGH) @@ -429,7 +470,7 @@ public void setDesiredState(SwerveModuleState desiredState, boolean isOpenLoop, * @param desiredState Desired {@link SwerveModuleState} to use. * @return Cosine compensated velocity in meters/second. */ - private double getCosineCompensatedVelocity(SwerveModuleState desiredState) + private LinearVelocity getCosineCompensatedVelocity(SwerveModuleState desiredState) { double cosineScalar = 1.0; // Taken from the CTRE SwerveModule class. @@ -447,7 +488,7 @@ private double getCosineCompensatedVelocity(SwerveModuleState desiredState) cosineScalar = 1; } - return desiredState.speedMetersPerSecond * (cosineScalar); + return MetersPerSecond.of(desiredState.speedMetersPerSecond).times(cosineScalar); } /** @@ -518,6 +559,13 @@ public double getAbsolutePosition() */ public double getRawAbsolutePosition() { + /* During simulation, when no absolute encoders are available, we return the state from the simulation module instead. */ + if (SwerveDriveTelemetry.isSimulation) + { + Rotation2d absolutePosition = simModule.getState().angle; + return absolutePosition.getDegrees(); + } + double angle; if (absoluteEncoder != null) { @@ -629,9 +677,9 @@ public void pushOffsetsToEncoders() if (absoluteEncoder != null && angleOffset == configuration.angleOffset) { // If the absolute encoder is attached. - if (angleMotor.getMotor() instanceof CANSparkMax) + if (angleMotor instanceof SparkMaxSwerve || angleMotor instanceof SparkMaxBrushedMotorSwerve) { - if (absoluteEncoder.getAbsoluteEncoder() instanceof MotorFeedbackSensor) + if (absoluteEncoder instanceof SparkMaxEncoderSwerve) { angleMotor.setAbsoluteEncoder(absoluteEncoder); if (absoluteEncoder.setAbsoluteEncoderOffset(angleOffset)) @@ -677,6 +725,40 @@ public boolean getAbsoluteEncoderReadIssue() } } + + /** + * Get the maximum module velocity as a {@link LinearVelocity} based on the RPM and gear ratio. + * + * @return {@link LinearVelocity} max velocity of the drive wheel. + */ + public LinearVelocity getMaxVelocity() + { + if (maxDriveVelocity == null) + { + maxDriveVelocity = MetersPerSecond.of( + (RadiansPerSecond.of(driveMotor.getSimMotor().freeSpeedRadPerSec).in(RotationsPerSecond) / + configuration.conversionFactors.drive.gearRatio) * + configuration.conversionFactors.drive.diameter); + } + return maxDriveVelocity; + } + + /** + * Get the maximum module angular velocity as a {@link AngularVelocity} based on the RPM and gear ratio. + * + * @return {@link AngularVelocity} max velocity of the angle/azimuth. + */ + public AngularVelocity getMaxAngularVelocity() + { + if (maxAngularVelocity == null) + { + maxAngularVelocity = RotationsPerSecond.of( + RadiansPerSecond.of(angleMotor.getSimMotor().freeSpeedRadPerSec).in(RotationsPerSecond) * + configuration.conversionFactors.angle.gearRatio); + } + return maxAngularVelocity; + } + /** * Update data sent to {@link SmartDashboard}. */ @@ -687,9 +769,43 @@ public void updateTelemetry() SmartDashboard.putNumber(rawAbsoluteAngleName, absoluteEncoder.getAbsolutePosition()); } SmartDashboard.putNumber(rawAngleName, angleMotor.getPosition()); - SmartDashboard.putNumber(rawDriveName, driveMotor.getPosition()); - SmartDashboard.putNumber(rawDriveVelName, driveMotor.getVelocity()); + SmartDashboard.putNumber(rawDriveName, drivePositionCache.getValue()); + SmartDashboard.putNumber(rawDriveVelName, driveVelocityCache.getValue()); SmartDashboard.putNumber(adjAbsoluteAngleName, getAbsolutePosition()); SmartDashboard.putNumber(absoluteEncoderIssueName, getAbsoluteEncoderReadIssue() ? 1 : 0); } + + /** + * Invalidate the {@link Cache} objects used by {@link SwerveModule}. + */ + public void invalidateCache() + { + absolutePositionCache.update(); + drivePositionCache.update(); + driveVelocityCache.update(); + } + + /** + * Obtains the {@link SwerveModuleSimulation} used in simulation. + * + * @return the module simulation, null if this method is called on a real robot + */ + public SwerveModuleSimulation getSimModule() + { + return simModule; + } + + /** + * Configure the {@link SwerveModule#simModule} with the MapleSim + * {@link org.ironmaple.simulation.drivesims.SwerveModuleSimulation} + * + * @param swerveModuleSimulation MapleSim {@link org.ironmaple.simulation.drivesims.SwerveModuleSimulation} to + * configure with. + */ + public void configureModuleSimulation( + org.ironmaple.simulation.drivesims.SwerveModuleSimulation swerveModuleSimulation, + SwerveModulePhysicalCharacteristics physicalCharacteristics) + { + this.simModule.configureSimModule(swerveModuleSimulation, physicalCharacteristics); + } } diff --git a/swervelib/encoders/AnalogAbsoluteEncoderSwerve.java b/swervelib/encoders/AnalogAbsoluteEncoderSwerve.java index fbd8214f..11016f65 100644 --- a/swervelib/encoders/AnalogAbsoluteEncoderSwerve.java +++ b/swervelib/encoders/AnalogAbsoluteEncoderSwerve.java @@ -1,8 +1,9 @@ package swervelib.encoders; +import edu.wpi.first.wpilibj.Alert; +import edu.wpi.first.wpilibj.Alert.AlertType; import edu.wpi.first.wpilibj.AnalogInput; import edu.wpi.first.wpilibj.RobotController; -import swervelib.telemetry.Alert; /** * Swerve Absolute Encoder for Thrifty Encoders and other analog encoders. @@ -39,11 +40,11 @@ public AnalogAbsoluteEncoderSwerve(AnalogInput encoder) cannotSetOffset = new Alert( "Encoders", "Cannot Set Absolute Encoder Offset of Analog Encoders Channel #" + encoder.getChannel(), - Alert.AlertType.WARNING); + AlertType.kWarning); inaccurateVelocities = new Alert( "Encoders", "The Analog Absolute encoder may not report accurate velocities!", - Alert.AlertType.WARNING_TRACE); + AlertType.kWarning); } /** diff --git a/swervelib/encoders/CANCoderSwerve.java b/swervelib/encoders/CANCoderSwerve.java index 24ca2013..0477b240 100644 --- a/swervelib/encoders/CANCoderSwerve.java +++ b/swervelib/encoders/CANCoderSwerve.java @@ -1,15 +1,20 @@ package swervelib.encoders; +import static edu.wpi.first.units.Units.Degrees; +import static edu.wpi.first.units.Units.DegreesPerSecond; +import static edu.wpi.first.units.Units.Rotations; + import com.ctre.phoenix6.StatusCode; import com.ctre.phoenix6.StatusSignal; import com.ctre.phoenix6.configs.CANcoderConfiguration; import com.ctre.phoenix6.configs.CANcoderConfigurator; import com.ctre.phoenix6.configs.MagnetSensorConfigs; import com.ctre.phoenix6.hardware.CANcoder; -import com.ctre.phoenix6.signals.AbsoluteSensorRangeValue; import com.ctre.phoenix6.signals.MagnetHealthValue; import com.ctre.phoenix6.signals.SensorDirectionValue; -import swervelib.telemetry.Alert; +import edu.wpi.first.units.measure.Angle; +import edu.wpi.first.wpilibj.Alert; +import edu.wpi.first.wpilibj.Alert.AlertType; /** * Swerve Absolute Encoder for CTRE CANCoders. @@ -65,21 +70,21 @@ public CANCoderSwerve(int id, String canbus) magnetFieldLessThanIdeal = new Alert( "Encoders", "CANCoder " + encoder.getDeviceID() + " magnetic field is less than ideal.", - Alert.AlertType.WARNING); + AlertType.kWarning); readingFaulty = new Alert( "Encoders", "CANCoder " + encoder.getDeviceID() + " reading was faulty.", - Alert.AlertType.WARNING); + AlertType.kWarning); readingIgnored = new Alert( "Encoders", "CANCoder " + encoder.getDeviceID() + " reading was faulty, ignoring.", - Alert.AlertType.WARNING); + AlertType.kWarning); cannotSetOffset = new Alert( "Encoders", "Failure to set CANCoder " + encoder.getDeviceID() + " Absolute Encoder Offset", - Alert.AlertType.WARNING); + AlertType.kWarning); } /** @@ -112,7 +117,7 @@ public void configure(boolean inverted) MagnetSensorConfigs magnetSensorConfiguration = new MagnetSensorConfigs(); cfg.refresh(magnetSensorConfiguration); cfg.apply(magnetSensorConfiguration - .withAbsoluteSensorRange(AbsoluteSensorRangeValue.Unsigned_0To1) + .withAbsoluteSensorDiscontinuityPoint(Rotations.of(1)) .withSensorDirection(inverted ? SensorDirectionValue.Clockwise_Positive : SensorDirectionValue.CounterClockwise_Positive)); } @@ -139,7 +144,7 @@ public double getAbsolutePosition() readingFaulty.set(false); } - StatusSignal angle = encoder.getAbsolutePosition(); + StatusSignal angle = encoder.getAbsolutePosition(); // Taken from democat's library. // Source: https://github.com/democat3457/swerve-lib/blob/7c03126b8c22f23a501b2c2742f9d173a5bcbc40/src/main/java/com/swervedrivespecialties/swervelib/ctre/CanCoderFactoryBuilder.java#L51-L74 @@ -160,7 +165,7 @@ public double getAbsolutePosition() readingIgnored.set(false); } - return angle.getValue() * 360; + return angle.getValue().in(Degrees); } /** @@ -213,6 +218,6 @@ public boolean setAbsoluteEncoderOffset(double offset) @Override public double getVelocity() { - return encoder.getVelocity().getValue() * 360; + return encoder.getVelocity().getValue().in(DegreesPerSecond); } } diff --git a/swervelib/encoders/PWMDutyCycleEncoderSwerve.java b/swervelib/encoders/PWMDutyCycleEncoderSwerve.java index 1b8bb6b8..52270951 100644 --- a/swervelib/encoders/PWMDutyCycleEncoderSwerve.java +++ b/swervelib/encoders/PWMDutyCycleEncoderSwerve.java @@ -1,7 +1,8 @@ package swervelib.encoders; +import edu.wpi.first.wpilibj.Alert; +import edu.wpi.first.wpilibj.Alert.AlertType; import edu.wpi.first.wpilibj.DutyCycleEncoder; -import swervelib.telemetry.Alert; /** * DutyCycle encoders such as "US Digital MA3 with PWM Output, the CTRE Mag Encoder, the Rev Hex Encoder, and the AM Mag @@ -42,7 +43,7 @@ public PWMDutyCycleEncoderSwerve(int pin) inaccurateVelocities = new Alert( "Encoders", "The PWM Duty Cycle encoder may not report accurate velocities!", - Alert.AlertType.WARNING_TRACE); + AlertType.kWarning); } diff --git a/swervelib/encoders/SparkMaxAnalogEncoderSwerve.java b/swervelib/encoders/SparkMaxAnalogEncoderSwerve.java index bbf9fb6c..aa88efcf 100644 --- a/swervelib/encoders/SparkMaxAnalogEncoderSwerve.java +++ b/swervelib/encoders/SparkMaxAnalogEncoderSwerve.java @@ -1,12 +1,15 @@ package swervelib.encoders; -import com.revrobotics.CANSparkMax; import com.revrobotics.REVLibError; -import com.revrobotics.SparkAnalogSensor; -import com.revrobotics.SparkAnalogSensor.Mode; +import com.revrobotics.spark.SparkAnalogSensor; +import com.revrobotics.spark.SparkMax; +import com.revrobotics.spark.config.SparkMaxConfig; +import edu.wpi.first.wpilibj.Alert; +import edu.wpi.first.wpilibj.Alert.AlertType; import java.util.function.Supplier; +import swervelib.motors.SparkMaxBrushedMotorSwerve; +import swervelib.motors.SparkMaxSwerve; import swervelib.motors.SwerveMotor; -import swervelib.telemetry.Alert; /** * SparkMax absolute encoder, attached through the data port analog pin. @@ -14,33 +17,38 @@ public class SparkMaxAnalogEncoderSwerve extends SwerveAbsoluteEncoder { + /** + * {@link swervelib.motors.SparkMaxSwerve} or {@link swervelib.motors.SparkMaxBrushedMotorSwerve} object. + */ + private final SwerveMotor sparkMax; /** * The {@link SparkAnalogSensor} representing the duty cycle encoder attached to the SparkMax analog port. */ - public SparkAnalogSensor encoder; + public SparkAnalogSensor encoder; /** * An {@link Alert} for if there is a failure configuring the encoder. */ - private Alert failureConfiguring; + private Alert failureConfiguring; /** * An {@link Alert} for if the absolute encoder does not support integrated offsets. */ - private Alert doesNotSupportIntegratedOffsets; - + private Alert doesNotSupportIntegratedOffsets; /** - * Create the {@link SparkMaxAnalogEncoderSwerve} object as a analog sensor from the {@link CANSparkMax} motor data - * port analog pin. + * Create the {@link SparkMaxAnalogEncoderSwerve} object as a analog sensor from the {@link SparkMax} motor data port + * analog pin. * * @param motor Motor to create the encoder from. * @param maxVoltage Maximum voltage for analog input reading. */ public SparkMaxAnalogEncoderSwerve(SwerveMotor motor, double maxVoltage) { - if (motor.getMotor() instanceof CANSparkMax) + if (motor.getMotor() instanceof SparkMax) { - encoder = ((CANSparkMax) motor.getMotor()).getAnalog(Mode.kAbsolute); - encoder.setPositionConversionFactor(360 / maxVoltage); + sparkMax = motor; + encoder = ((SparkMax) motor.getMotor()).getAnalog(); + motor.setAbsoluteEncoder(this); + sparkMax.configureIntegratedEncoder(360 / maxVoltage); } else { throw new RuntimeException("Motor given to instantiate SparkMaxEncoder is not a CANSparkMax"); @@ -48,11 +56,11 @@ public SparkMaxAnalogEncoderSwerve(SwerveMotor motor, double maxVoltage) failureConfiguring = new Alert( "Encoders", "Failure configuring SparkMax Analog Encoder", - Alert.AlertType.WARNING_TRACE); + AlertType.kWarning); doesNotSupportIntegratedOffsets = new Alert( "Encoders", "SparkMax Analog Sensors do not support integrated offsets", - Alert.AlertType.WARNING_TRACE); + AlertType.kWarning); } @@ -99,7 +107,17 @@ public void clearStickyFaults() @Override public void configure(boolean inverted) { - encoder.setInverted(inverted); + if (sparkMax instanceof SparkMaxSwerve) + { + SparkMaxConfig cfg = ((SparkMaxSwerve) sparkMax).getConfig(); + cfg.analogSensor.inverted(true); + ((SparkMaxSwerve) sparkMax).updateConfig(cfg); + } else if (sparkMax instanceof SparkMaxBrushedMotorSwerve) + { + SparkMaxConfig cfg = ((SparkMaxBrushedMotorSwerve) sparkMax).getConfig(); + cfg.analogSensor.inverted(true); + ((SparkMaxBrushedMotorSwerve) sparkMax).updateConfig(cfg); + } } /** diff --git a/swervelib/encoders/SparkMaxEncoderSwerve.java b/swervelib/encoders/SparkMaxEncoderSwerve.java index 6bd68dad..b01a39f1 100644 --- a/swervelib/encoders/SparkMaxEncoderSwerve.java +++ b/swervelib/encoders/SparkMaxEncoderSwerve.java @@ -1,12 +1,16 @@ package swervelib.encoders; import com.revrobotics.AbsoluteEncoder; -import com.revrobotics.CANSparkMax; import com.revrobotics.REVLibError; -import com.revrobotics.SparkAbsoluteEncoder.Type; +import com.revrobotics.spark.SparkAbsoluteEncoder; +import com.revrobotics.spark.SparkMax; +import com.revrobotics.spark.config.SparkMaxConfig; +import edu.wpi.first.wpilibj.Alert; +import edu.wpi.first.wpilibj.Alert.AlertType; import java.util.function.Supplier; +import swervelib.motors.SparkMaxBrushedMotorSwerve; +import swervelib.motors.SparkMaxSwerve; import swervelib.motors.SwerveMotor; -import swervelib.telemetry.Alert; /** * SparkMax absolute encoder, attached through the data port. @@ -17,18 +21,23 @@ public class SparkMaxEncoderSwerve extends SwerveAbsoluteEncoder /** * The {@link AbsoluteEncoder} representing the duty cycle encoder attached to the SparkMax. */ - public AbsoluteEncoder encoder; + public SparkAbsoluteEncoder encoder; /** * An {@link Alert} for if there is a failure configuring the encoder. */ - private Alert failureConfiguring; + private Alert failureConfiguring; /** * An {@link Alert} for if there is a failure configuring the encoder offset. */ - private Alert offsetFailure; + private Alert offsetFailure; + /** + * {@link SparkMaxBrushedMotorSwerve} or {@link SparkMaxSwerve} instance. + */ + private SwerveMotor sparkMax; /** - * Create the {@link SparkMaxEncoderSwerve} object as a duty cycle from the {@link CANSparkMax} motor. + * Create the {@link SparkMaxEncoderSwerve} object as a duty cycle from the {@link com.revrobotics.spark.SparkMax} + * motor. * * @param motor Motor to create the encoder from. * @param conversionFactor The conversion factor to set if the output is not from 0 to 360. @@ -38,16 +47,17 @@ public SparkMaxEncoderSwerve(SwerveMotor motor, int conversionFactor) failureConfiguring = new Alert( "Encoders", "Failure configuring SparkMax Analog Encoder", - Alert.AlertType.WARNING_TRACE); + AlertType.kWarning); offsetFailure = new Alert( "Encoders", "Failure to set Absolute Encoder Offset", - Alert.AlertType.WARNING_TRACE); - if (motor.getMotor() instanceof CANSparkMax) + AlertType.kWarning); + if (motor.getMotor() instanceof SparkMax) { - encoder = ((CANSparkMax) motor.getMotor()).getAbsoluteEncoder(Type.kDutyCycle); - configureSparkMax(() -> encoder.setVelocityConversionFactor(conversionFactor)); - configureSparkMax(() -> encoder.setPositionConversionFactor(conversionFactor)); + sparkMax = motor; + encoder = ((SparkMax) motor.getMotor()).getAbsoluteEncoder(); + motor.setAbsoluteEncoder(this); + motor.configureIntegratedEncoder(conversionFactor); } else { throw new RuntimeException("Motor given to instantiate SparkMaxEncoder is not a CANSparkMax"); @@ -97,7 +107,17 @@ public void clearStickyFaults() @Override public void configure(boolean inverted) { - encoder.setInverted(inverted); + if (sparkMax instanceof SparkMaxSwerve) + { + SparkMaxConfig cfg = ((SparkMaxSwerve) sparkMax).getConfig(); + cfg.analogSensor.inverted(true); + ((SparkMaxSwerve) sparkMax).updateConfig(cfg); + } else if (sparkMax instanceof SparkMaxBrushedMotorSwerve) + { + SparkMaxConfig cfg = ((SparkMaxBrushedMotorSwerve) sparkMax).getConfig(); + cfg.analogSensor.inverted(true); + ((SparkMaxBrushedMotorSwerve) sparkMax).updateConfig(cfg); + } } /** @@ -131,17 +151,19 @@ public Object getAbsoluteEncoder() @Override public boolean setAbsoluteEncoderOffset(double offset) { - REVLibError error = null; - for (int i = 0; i < maximumRetries; i++) + if (sparkMax instanceof SparkMaxSwerve) { - error = encoder.setZeroOffset(offset); - if (error == REVLibError.kOk) - { - return true; - } + SparkMaxConfig cfg = ((SparkMaxSwerve) sparkMax).getConfig(); + cfg.absoluteEncoder.zeroOffset(offset); + ((SparkMaxSwerve) sparkMax).updateConfig(cfg); + return true; + } else if (sparkMax instanceof SparkMaxBrushedMotorSwerve) + { + SparkMaxConfig cfg = ((SparkMaxBrushedMotorSwerve) sparkMax).getConfig(); + cfg.absoluteEncoder.zeroOffset(offset); + ((SparkMaxBrushedMotorSwerve) sparkMax).updateConfig(cfg); + return true; } - offsetFailure.setText("Failure to set Absolute Encoder Offset Error: " + error); - offsetFailure.set(true); return false; } diff --git a/swervelib/encoders/TalonSRXEncoderSwerve.java b/swervelib/encoders/TalonSRXEncoderSwerve.java new file mode 100644 index 00000000..d2449ed7 --- /dev/null +++ b/swervelib/encoders/TalonSRXEncoderSwerve.java @@ -0,0 +1,90 @@ +package swervelib.encoders; + +import com.ctre.phoenix.motorcontrol.FeedbackDevice; +import com.ctre.phoenix.motorcontrol.can.WPI_TalonSRX; +import swervelib.motors.SwerveMotor; +import swervelib.motors.TalonSRXSwerve; + +/** + * Talon SRX attached absolute encoder. + */ +public class TalonSRXEncoderSwerve extends SwerveAbsoluteEncoder +{ + + /** + * Multiplying by this converts native Talon SRX units into degrees. + */ + private final double degreesPerSensorUnit; + /** + * Reference to a Talon SRX for polling its attached absolute encoder. + */ + private final WPI_TalonSRX talon; + + /** + * Creates a {@link TalonSRXEncoderSwerve}. + * + * @param motor motor to poll the sensor from. + * @param feedbackDevice the feedback device the sensor uses e.g. PWM or Analog. + */ + public TalonSRXEncoderSwerve(SwerveMotor motor, FeedbackDevice feedbackDevice) + { + if (motor instanceof TalonSRXSwerve talonSRXSwerve) + { + talonSRXSwerve.setSelectedFeedbackDevice(feedbackDevice); + this.talon = (WPI_TalonSRX) talonSRXSwerve.getMotor(); + // https://v5.docs.ctr-electronics.com/en/stable/ch14_MCSensor.html#sensor-resolution + degreesPerSensorUnit = switch (feedbackDevice) + { + case Analog -> 360.0 / 1024.0; + default -> 360.0 / 4096.0; + }; + } else + { + throw new RuntimeException("Motor given to instantiate TalonSRXEncoder is not a WPI_TalonSRX"); + } + } + + @Override + public void factoryDefault() + { + // Handled in TalonSRXSwerve + } + + @Override + public void clearStickyFaults() + { + // Handled in TalonSRXSwerve + } + + @Override + public void configure(boolean inverted) + { + talon.setSensorPhase(inverted); + } + + @Override + public double getAbsolutePosition() + { + return (talon.getSelectedSensorPosition() * degreesPerSensorUnit) % 360; + } + + @Override + public Object getAbsoluteEncoder() + { + return talon; + } + + @Override + public boolean setAbsoluteEncoderOffset(double offset) + { + talon.setSelectedSensorPosition(talon.getSelectedSensorPosition() + offset / degreesPerSensorUnit); + return true; + } + + @Override + public double getVelocity() + { + return talon.getSelectedSensorVelocity() * 10 * degreesPerSensorUnit; + } + +} diff --git a/swervelib/imu/NavXSwerve.java b/swervelib/imu/NavXSwerve.java index 2d6454ef..dd2797d6 100644 --- a/swervelib/imu/NavXSwerve.java +++ b/swervelib/imu/NavXSwerve.java @@ -1,14 +1,12 @@ package swervelib.imu; -import com.kauailabs.navx.frc.AHRS; +import com.studica.frc.AHRS; +import com.studica.frc.AHRS.NavXComType; import edu.wpi.first.math.geometry.Rotation3d; import edu.wpi.first.math.geometry.Translation3d; -import edu.wpi.first.wpilibj.I2C; -import edu.wpi.first.wpilibj.SPI; -import edu.wpi.first.wpilibj.SerialPort; -import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard; +import edu.wpi.first.wpilibj.Alert; +import edu.wpi.first.wpilibj.Alert.AlertType; import java.util.Optional; -import swervelib.telemetry.Alert; /** * Communicates with the NavX({@link AHRS}) as the IMU. @@ -38,9 +36,9 @@ public class NavXSwerve extends SwerveIMU * * @param port Serial Port to connect to. */ - public NavXSwerve(SerialPort.Port port) + public NavXSwerve(NavXComType port) { - navXError = new Alert("IMU", "Error instantiating NavX.", Alert.AlertType.ERROR_TRACE); + navXError = new Alert("IMU", "Error instantiating NavX.", AlertType.kError); try { /* Communicate w/navX-MXP via the MXP SPI Bus. */ @@ -48,7 +46,6 @@ public NavXSwerve(SerialPort.Port port) /* See http://navx-mxp.kauailabs.com/guidance/selecting-an-interface/ for details. */ imu = new AHRS(port); factoryDefault(); - SmartDashboard.putData(imu); } catch (RuntimeException ex) { navXError.setText("Error instantiating NavX: " + ex.getMessage()); @@ -56,49 +53,6 @@ public NavXSwerve(SerialPort.Port port) } } - /** - * Constructor for the NavX({@link AHRS}) swerve. - * - * @param port SPI Port to connect to. - */ - public NavXSwerve(SPI.Port port) - { - try - { - /* Communicate w/navX-MXP via the MXP SPI Bus. */ - /* Alternatively: I2C.Port.kMXP, SerialPort.Port.kMXP or SerialPort.Port.kUSB */ - /* See http://navx-mxp.kauailabs.com/guidance/selecting-an-interface/ for details. */ - imu = new AHRS(port); - factoryDefault(); - SmartDashboard.putData(imu); - } catch (RuntimeException ex) - { - navXError.setText("Error instantiating NavX: " + ex.getMessage()); - navXError.set(true); - } - } - - /** - * Constructor for the NavX({@link AHRS}) swerve. - * - * @param port I2C Port to connect to. - */ - public NavXSwerve(I2C.Port port) - { - try - { - /* Communicate w/navX-MXP via the MXP SPI Bus. */ - /* Alternatively: I2C.Port.kMXP, SerialPort.Port.kMXP or SerialPort.Port.kUSB */ - /* See http://navx-mxp.kauailabs.com/guidance/selecting-an-interface/ for details. */ - imu = new AHRS(port); - factoryDefault(); - SmartDashboard.putData(imu); - } catch (RuntimeException ex) - { - navXError.setText("Error instantiating NavX: " + ex.getMessage()); - navXError.set(true); - } - } /** * Reset offset to current gyro reading. Does not call NavX({@link AHRS#reset()}) because it has been reported to be @@ -137,6 +91,7 @@ public void setOffset(Rotation3d offset) public void setInverted(boolean invertIMU) { invertedIMU = invertIMU; + setOffset(getRawRotation3d()); } /** diff --git a/swervelib/imu/Pigeon2Swerve.java b/swervelib/imu/Pigeon2Swerve.java index 574da147..fc0a113c 100644 --- a/swervelib/imu/Pigeon2Swerve.java +++ b/swervelib/imu/Pigeon2Swerve.java @@ -1,13 +1,17 @@ package swervelib.imu; +import static edu.wpi.first.units.Units.DegreesPerSecond; + import com.ctre.phoenix6.StatusSignal; import com.ctre.phoenix6.configs.Pigeon2Configuration; import com.ctre.phoenix6.configs.Pigeon2Configurator; import com.ctre.phoenix6.hardware.Pigeon2; import edu.wpi.first.math.geometry.Rotation3d; import edu.wpi.first.math.geometry.Translation3d; +import edu.wpi.first.units.measure.LinearAcceleration; import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard; import java.util.Optional; +import java.util.function.Supplier; /** * SwerveIMU interface for the {@link Pigeon2} @@ -36,6 +40,19 @@ public class Pigeon2Swerve extends SwerveIMU */ private Pigeon2Configurator cfg; + /** + * X Acceleration supplier + */ + private Supplier> xAcc; + /** + * Y Accelleration supplier. + */ + private Supplier> yAcc; + /** + * Z Acceleration supplier. + */ + private Supplier> zAcc; + /** * Generate the SwerveIMU for {@link Pigeon2}. * @@ -46,6 +63,9 @@ public Pigeon2Swerve(int canid, String canbus) { imu = new Pigeon2(canid, canbus); this.cfg = imu.getConfigurator(); + xAcc = imu::getAccelerationX; + yAcc = imu::getAccelerationY; + zAcc = imu::getAccelerationZ; SmartDashboard.putData(imu); } @@ -123,6 +143,7 @@ public Rotation3d getRotation3d() return getRawRotation3d().minus(offset); } + /** * Fetch the acceleration [x, y, z] from the IMU in meters per second squared. If acceleration isn't supported returns * empty. @@ -132,24 +153,19 @@ public Rotation3d getRotation3d() @Override public Optional getAccel() { - // TODO: Switch to suppliers. - StatusSignal xAcc = imu.getAccelerationX(); - StatusSignal yAcc = imu.getAccelerationY(); - StatusSignal zAcc = imu.getAccelerationZ(); - - return Optional.of(new Translation3d(xAcc.waitForUpdate(STATUS_TIMEOUT_SECONDS).getValue(), - yAcc.waitForUpdate(STATUS_TIMEOUT_SECONDS).getValue(), - zAcc.waitForUpdate(STATUS_TIMEOUT_SECONDS).getValue()).times(9.81 / 16384.0)); + // TODO: Implement later. + + return Optional.empty(); } /** * Fetch the rotation rate from the IMU in degrees per second. If rotation rate isn't supported returns empty. * - * @return {@link Double} of the rotation rate as an {@link Optional}. + * @return Rotation rate in DegreesPerSecond. */ public double getRate() { - return imu.getRate(); + return imu.getAngularVelocityZWorld().waitForUpdate(STATUS_TIMEOUT_SECONDS).getValue().in(DegreesPerSecond); } /** diff --git a/swervelib/math/IMULinearMovingAverageFilter.java b/swervelib/math/IMULinearMovingAverageFilter.java index 09509b75..9ac08efb 100644 --- a/swervelib/math/IMULinearMovingAverageFilter.java +++ b/swervelib/math/IMULinearMovingAverageFilter.java @@ -15,7 +15,7 @@ public class IMULinearMovingAverageFilter /** * Gain on each reading. */ - private final double m_inputGain; + private final double m_inputGain; /** * Construct a linear moving average fitler diff --git a/swervelib/math/SwerveMath.java b/swervelib/math/SwerveMath.java index b77838c0..eea10e40 100644 --- a/swervelib/math/SwerveMath.java +++ b/swervelib/math/SwerveMath.java @@ -411,6 +411,10 @@ public static Translation2d cubeTranslation(Translation2d translation) */ public static Translation2d scaleTranslation(Translation2d translation, double scalar) { + if (Math.hypot(translation.getX(), translation.getY()) <= 1.0E-6) + { + return translation; + } return new Translation2d(translation.getNorm() * scalar, translation.getAngle()); } } diff --git a/swervelib/motors/SparkFlexSwerve.java b/swervelib/motors/SparkFlexSwerve.java index 400de964..e9f4b846 100644 --- a/swervelib/motors/SparkFlexSwerve.java +++ b/swervelib/motors/SparkFlexSwerve.java @@ -1,66 +1,90 @@ package swervelib.motors; +import static edu.wpi.first.units.Units.Seconds; + import com.revrobotics.AbsoluteEncoder; -import com.revrobotics.CANSparkBase.ControlType; -import com.revrobotics.CANSparkBase.IdleMode; -import com.revrobotics.CANSparkFlex; -import com.revrobotics.CANSparkLowLevel.MotorType; -import com.revrobotics.CANSparkLowLevel.PeriodicFrame; -import com.revrobotics.CANSparkMax; -import com.revrobotics.MotorFeedbackSensor; import com.revrobotics.REVLibError; import com.revrobotics.RelativeEncoder; -import com.revrobotics.SparkAnalogSensor; -import com.revrobotics.SparkPIDController; +import com.revrobotics.spark.SparkBase.ControlType; +import com.revrobotics.spark.SparkBase.PersistMode; +import com.revrobotics.spark.SparkBase.ResetMode; +import com.revrobotics.spark.SparkClosedLoopController; +import com.revrobotics.spark.SparkFlex; +import com.revrobotics.spark.SparkLowLevel.MotorType; +import com.revrobotics.spark.config.ClosedLoopConfig.FeedbackSensor; +import com.revrobotics.spark.config.SparkBaseConfig.IdleMode; +import com.revrobotics.spark.config.SparkFlexConfig; +import com.revrobotics.spark.config.SparkMaxConfig; +import edu.wpi.first.math.system.plant.DCMotor; +import edu.wpi.first.units.Units; +import edu.wpi.first.wpilibj.Alert; +import edu.wpi.first.wpilibj.Alert.AlertType; import edu.wpi.first.wpilibj.Timer; import java.util.function.Supplier; import swervelib.encoders.SwerveAbsoluteEncoder; import swervelib.parser.PIDFConfig; -import swervelib.telemetry.Alert; import swervelib.telemetry.SwerveDriveTelemetry; /** - * An implementation of {@link CANSparkFlex} as a {@link SwerveMotor}. + * An implementation of {@link SparkFlex} as a {@link SwerveMotor}. */ public class SparkFlexSwerve extends SwerveMotor { /** - * {@link CANSparkFlex} Instance. + * {@link SparkFlex} Instance. */ - private final CANSparkFlex motor; + private final SparkFlex motor; /** * Integrated encoder. */ - public RelativeEncoder encoder; + public RelativeEncoder encoder; /** * Absolute encoder attached to the SparkMax (if exists) */ - public SwerveAbsoluteEncoder absoluteEncoder; + public SwerveAbsoluteEncoder absoluteEncoder; /** * Closed-loop PID controller. */ - public SparkPIDController pid; + public SparkClosedLoopController pid; + /** + * Supplier for the velocity of the motor controller. + */ + private Supplier velocity; + /** + * Supplier for the position of the motor controller. + */ + private Supplier position; /** * Factory default already occurred. */ - private boolean factoryDefaultOccurred = false; + private boolean factoryDefaultOccurred = false; /** * An {@link Alert} for if there is an error configuring the motor. */ - private Alert failureConfiguring; + private Alert failureConfiguring; /** * An {@link Alert} for if the absolute encoder's offset is set in the json instead of the hardware client. */ - private Alert absoluteEncoderOffsetWarning; + private Alert absoluteEncoderOffsetWarning; + /** + * Configuration object for {@link SparkFlex} motor. + */ + private SparkFlexConfig cfg = new SparkFlexConfig(); + /** + * Tracker for changes that need to be pushed. + */ + private boolean cfgUpdated = false; + /** * Initialize the swerve motor. * * @param motor The SwerveMotor as a SparkFlex object. * @param isDriveMotor Is the motor being initialized a drive motor? + * @param motorType {@link DCMotor} which the {@link SparkFlex} is attached to. */ - public SparkFlexSwerve(CANSparkFlex motor, boolean isDriveMotor) + public SparkFlexSwerve(SparkFlex motor, boolean isDriveMotor, DCMotor motorType) { this.motor = motor; this.isDriveMotor = isDriveMotor; @@ -68,32 +92,34 @@ public SparkFlexSwerve(CANSparkFlex motor, boolean isDriveMotor) clearStickyFaults(); encoder = motor.getEncoder(); - pid = motor.getPIDController(); - pid.setFeedbackDevice( - encoder); // Configure feedback of the PID controller as the integrated encoder. + pid = motor.getClosedLoopController(); + cfg.closedLoop.feedbackSensor(FeedbackSensor.kPrimaryEncoder); // Configure feedback of the PID controller as the integrated encoder. + cfgUpdated = true; // Spin off configurations in a different thread. // configureSparkMax(() -> motor.setCANTimeout(0)); // Commented out because it prevents feedback. failureConfiguring = new Alert("Motors", "Failure configuring motor " + motor.getDeviceId(), - Alert.AlertType.WARNING_TRACE); + AlertType.kWarning); absoluteEncoderOffsetWarning = new Alert("Motors", "IF possible configure the duty cycle encoder offset in the REV Hardware Client instead of using the " + "absoluteEncoderOffset in the Swerve Module JSON!", - Alert.AlertType.WARNING); - + AlertType.kWarning); + velocity = encoder::getVelocity; + position = encoder::getPosition; } /** - * Initialize the {@link SwerveMotor} as a {@link CANSparkMax} connected to a Brushless Motor. + * Initialize the {@link SwerveMotor} as a {@link SparkFlex} connected to a Brushless Motor. * * @param id CAN ID of the SparkMax. * @param isDriveMotor Is the motor being initialized a drive motor? + * @param motorType {@link DCMotor} which the {@link SparkFlex} is attached to. */ - public SparkFlexSwerve(int id, boolean isDriveMotor) + public SparkFlexSwerve(int id, boolean isDriveMotor, DCMotor motorType) { - this(new CANSparkFlex(id, MotorType.kBrushless), isDriveMotor); + this(new SparkFlex(id, MotorType.kBrushless), isDriveMotor, motorType); } /** @@ -109,11 +135,33 @@ private void configureSparkFlex(Supplier config) { return; } - Timer.delay(0.01); + Timer.delay(Units.Milliseconds.of(10).in(Seconds)); } failureConfiguring.set(true); } + /** + * Get the current configuration of the {@link SparkFlex} + * + * @return {@link SparkMaxConfig} + */ + public SparkFlexConfig getConfig() + { + return cfg; + } + + /** + * Update the config for the {@link SparkFlex} + * + * @param cfgGiven Given {@link SparkFlexConfig} which should have minimal modifications. + */ + public void updateConfig(SparkFlexConfig cfgGiven) + { + cfg.apply(cfgGiven); + configureSparkFlex(() -> motor.configure(cfg, ResetMode.kNoResetSafeParameters, PersistMode.kPersistParameters)); + cfgUpdated = false; + } + /** * Set the voltage compensation for the swerve module motor. * @@ -122,7 +170,8 @@ private void configureSparkFlex(Supplier config) @Override public void setVoltageCompensation(double nominalVoltage) { - configureSparkFlex(() -> motor.enableVoltageCompensation(nominalVoltage)); + cfg.voltageCompensation(nominalVoltage); + cfgUpdated = true; } /** @@ -134,7 +183,9 @@ public void setVoltageCompensation(double nominalVoltage) @Override public void setCurrentLimit(int currentLimit) { - configureSparkFlex(() -> motor.setSmartCurrentLimit(currentLimit)); + + cfg.smartCurrentLimit(currentLimit); + cfgUpdated = true; } /** @@ -145,8 +196,9 @@ public void setCurrentLimit(int currentLimit) @Override public void setLoopRampRate(double rampRate) { - configureSparkFlex(() -> motor.setOpenLoopRampRate(rampRate)); - configureSparkFlex(() -> motor.setClosedLoopRampRate(rampRate)); + cfg.closedLoopRampRate(rampRate) + .openLoopRampRate(rampRate); + cfgUpdated = true; } /** @@ -160,6 +212,21 @@ public Object getMotor() return motor; } + /** + * Get the {@link DCMotor} of the motor class. + * + * @return {@link DCMotor} of this type. + */ + @Override + public DCMotor getSimMotor() + { + if (simMotor == null) + { + simMotor = DCMotor.getNeoVortex(1); + } + return simMotor; + } + /** * Queries whether the absolute encoder is directly attached to the motor controller. * @@ -177,11 +244,7 @@ public boolean isAttachedAbsoluteEncoder() @Override public void factoryDefaults() { - if (!factoryDefaultOccurred) - { - configureSparkFlex(motor::restoreFactoryDefaults); - factoryDefaultOccurred = true; - } + // Do nothing } /** @@ -205,12 +268,20 @@ public SwerveMotor setAbsoluteEncoder(SwerveAbsoluteEncoder encoder) if (encoder == null) { absoluteEncoder = null; - configureSparkFlex(() -> pid.setFeedbackDevice(this.encoder)); - } else if (encoder.getAbsoluteEncoder() instanceof MotorFeedbackSensor) + cfg.closedLoop.feedbackSensor(FeedbackSensor.kPrimaryEncoder); + cfgUpdated = true; + + velocity = this.encoder::getVelocity; + position = this.encoder::getPosition; + } else if (encoder.getAbsoluteEncoder() instanceof AbsoluteEncoder) { + cfg.closedLoop.feedbackSensor(FeedbackSensor.kAbsoluteEncoder); + cfgUpdated = true; absoluteEncoderOffsetWarning.set(true); absoluteEncoder = encoder; - configureSparkFlex(() -> pid.setFeedbackDevice((MotorFeedbackSensor) absoluteEncoder.getAbsoluteEncoder())); + + velocity = absoluteEncoder::getVelocity; + position = absoluteEncoder::getAbsolutePosition; } return this; } @@ -223,39 +294,70 @@ public SwerveMotor setAbsoluteEncoder(SwerveAbsoluteEncoder encoder) @Override public void configureIntegratedEncoder(double positionConversionFactor) { + cfg.signals + .absoluteEncoderPositionAlwaysOn(false) + .absoluteEncoderVelocityAlwaysOn(false) + .analogPositionAlwaysOn(false) + .analogVelocityAlwaysOn(false) + .analogVoltageAlwaysOn(false) + .externalOrAltEncoderPositionAlwaysOn(false) + .externalOrAltEncoderVelocityAlwaysOn(false) + .primaryEncoderPositionAlwaysOn(false) + .primaryEncoderVelocityAlwaysOn(false) + .iAccumulationAlwaysOn(false) + .appliedOutputPeriodMs(10) + .faultsPeriodMs(20) + ; if (absoluteEncoder == null) { - configureSparkFlex(() -> encoder.setPositionConversionFactor(positionConversionFactor)); - configureSparkFlex(() -> encoder.setVelocityConversionFactor(positionConversionFactor / 60)); + cfg.closedLoop.feedbackSensor(FeedbackSensor.kPrimaryEncoder); + + cfg.encoder + .positionConversionFactor(positionConversionFactor) + .velocityConversionFactor(positionConversionFactor / 60); + // Changes the measurement period and number of samples used to calculate the velocity for the intergrated motor controller + // Notability this changes the returned velocity and the velocity used for the onboard velocity PID loop (TODO: triple check the PID portion of this statement) + // Default settings of 32ms and 8 taps introduce ~100ms of measurement lag + // https://www.chiefdelphi.com/t/shooter-encoder/400211/11 + // This value was taken from: + // https://github.com/Mechanical-Advantage/RobotCode2023/blob/9884d13b2220b76d430e82248fd837adbc4a10bc/src/main/java/org/littletonrobotics/frc2023/subsystems/drive/ModuleIOSparkMax.java#L132-L133 + // and tested on 9176 for YAGSL, notably 3005 uses 16ms instead of 10 but 10 is more common based on github searches + cfg.encoder + .quadratureMeasurementPeriod(10) + .quadratureAverageDepth(2); // Taken from - // https://github.com/frc3512/SwerveBot-2022/blob/9d31afd05df6c630d5acb4ec2cf5d734c9093bf8/src/main/java/frc/lib/util/CANSparkMaxUtil.java#L67 - configureCANStatusFrames(10, 20, 20, 500, 500); + // https://github.com/frc3512/SwerveBot-2022/blob/9d31afd05df6c630d5acb4ec2cf5d734c9093bf8/src/main/java/frc/lib/util/SparkMaxUtil.java#L67 + // Unused frames can be set to 65535 to decrease CAN ultilization. + cfg.signals + .primaryEncoderVelocityAlwaysOn(isDriveMotor) // Disable velocity reporting for angle motors. + .primaryEncoderPositionAlwaysOn(true) + .primaryEncoderPositionPeriodMs(20); + } else { - configureSparkFlex(() -> { - if (absoluteEncoder.getAbsoluteEncoder() instanceof AbsoluteEncoder) - { - return ((AbsoluteEncoder) absoluteEncoder.getAbsoluteEncoder()).setPositionConversionFactor( - positionConversionFactor); - } else - { - return ((SparkAnalogSensor) absoluteEncoder.getAbsoluteEncoder()).setPositionConversionFactor( - positionConversionFactor); - } - }); - configureSparkFlex(() -> { - if (absoluteEncoder.getAbsoluteEncoder() instanceof AbsoluteEncoder) - { - return ((AbsoluteEncoder) absoluteEncoder.getAbsoluteEncoder()).setVelocityConversionFactor( - positionConversionFactor / 60); - } else - { - return ((SparkAnalogSensor) absoluteEncoder.getAbsoluteEncoder()).setVelocityConversionFactor( - positionConversionFactor / 60); - } - }); + // By default the SparkMax relays the info from the duty cycle encoder to the roborio every 200ms on CAN frame 5 + // This needs to be set to 20ms or under to properly update the swerve module position for odometry + // Configuration taken from 3005, the team who helped develop the Max Swerve: + // https://github.com/FRC3005/Charged-Up-2023-Public/blob/2b6a7c695e23edebafa27a76cf639a00f6e8a3a6/src/main/java/frc/robot/subsystems/drive/REVSwerveModule.java#L227-L244 + // Some of the frames can probably be adjusted to decrease CAN utilization, with 65535 being the max. + // From testing, 20ms on frame 5 sometimes returns the same value while constantly powering the azimuth but 8ms may be overkill, + // with limited testing 19ms did not return the same value while the module was constatntly rotating. + if (absoluteEncoder.getAbsoluteEncoder() instanceof AbsoluteEncoder) + { + cfg.closedLoop.feedbackSensor(FeedbackSensor.kAbsoluteEncoder); + + cfg.signals + .absoluteEncoderPositionAlwaysOn(true) + .absoluteEncoderPositionPeriodMs(20); + + cfg.absoluteEncoder + .positionConversionFactor(positionConversionFactor) + .velocityConversionFactor(positionConversionFactor / 60); + } } + cfgUpdated = true; + } /** @@ -266,15 +368,10 @@ public void configureIntegratedEncoder(double positionConversionFactor) @Override public void configurePIDF(PIDFConfig config) { -// int pidSlot = -// isDriveMotor ? SparkMAX_slotIdx.Velocity.ordinal() : SparkMAX_slotIdx.Position.ordinal(); - int pidSlot = 0; - configureSparkFlex(() -> pid.setP(config.p, pidSlot)); - configureSparkFlex(() -> pid.setI(config.i, pidSlot)); - configureSparkFlex(() -> pid.setD(config.d, pidSlot)); - configureSparkFlex(() -> pid.setFF(config.f, pidSlot)); - configureSparkFlex(() -> pid.setIZone(config.iz, pidSlot)); - configureSparkFlex(() -> pid.setOutputRange(config.output.min, config.output.max, pidSlot)); + cfg.closedLoop.pidf(config.p, config.i, config.d, config.f) + .iZone(config.iz) + .outputRange(config.output.min, config.output.max); + cfgUpdated = true; } /** @@ -286,30 +383,11 @@ public void configurePIDF(PIDFConfig config) @Override public void configurePIDWrapping(double minInput, double maxInput) { - configureSparkFlex(() -> pid.setPositionPIDWrappingEnabled(true)); - configureSparkFlex(() -> pid.setPositionPIDWrappingMinInput(minInput)); - configureSparkFlex(() -> pid.setPositionPIDWrappingMaxInput(maxInput)); - } + cfg.closedLoop + .positionWrappingEnabled(true) + .positionWrappingInputRange(minInput, maxInput); + cfgUpdated = true; - /** - * Set the CAN status frames. - * - * @param CANStatus0 Applied Output, Faults, Sticky Faults, Is Follower - * @param CANStatus1 Motor Velocity, Motor Temperature, Motor Voltage, Motor Current - * @param CANStatus2 Motor Position - * @param CANStatus3 Analog Sensor Voltage, Analog Sensor Velocity, Analog Sensor Position - * @param CANStatus4 Alternate Encoder Velocity, Alternate Encoder Position - */ - public void configureCANStatusFrames( - int CANStatus0, int CANStatus1, int CANStatus2, int CANStatus3, int CANStatus4) - { - configureSparkFlex(() -> motor.setPeriodicFramePeriod(PeriodicFrame.kStatus0, CANStatus0)); - configureSparkFlex(() -> motor.setPeriodicFramePeriod(PeriodicFrame.kStatus1, CANStatus1)); - configureSparkFlex(() -> motor.setPeriodicFramePeriod(PeriodicFrame.kStatus2, CANStatus2)); - configureSparkFlex(() -> motor.setPeriodicFramePeriod(PeriodicFrame.kStatus3, CANStatus3)); - configureSparkFlex(() -> motor.setPeriodicFramePeriod(PeriodicFrame.kStatus4, CANStatus4)); - // TODO: Configure Status Frame 5 and 6 if necessary - // https://docs.revrobotics.com/sparkmax/operating-modes/control-interfaces } /** @@ -320,7 +398,8 @@ public void configureCANStatusFrames( @Override public void setMotorBrake(boolean isBrakeMode) { - configureSparkFlex(() -> motor.setIdleMode(isBrakeMode ? IdleMode.kBrake : IdleMode.kCoast)); + cfg.idleMode(isBrakeMode ? IdleMode.kBrake : IdleMode.kCoast); + cfgUpdated = true; } /** @@ -331,10 +410,8 @@ public void setMotorBrake(boolean isBrakeMode) @Override public void setInverted(boolean inverted) { - configureSparkFlex(() -> { - motor.setInverted(inverted); - return motor.getLastError(); - }); + cfg.inverted(inverted); + cfgUpdated = true; } /** @@ -343,13 +420,8 @@ public void setInverted(boolean inverted) @Override public void burnFlash() { - try - { - Thread.sleep(200); - } catch (Exception e) - { - } - configureSparkFlex(() -> motor.burnFlash()); + motor.configure(cfg, ResetMode.kNoResetSafeParameters, PersistMode.kPersistParameters); + cfgUpdated = false; } /** @@ -372,11 +444,14 @@ public void set(double percentOutput) @Override public void setReference(double setpoint, double feedforward) { - boolean possibleBurnOutIssue = true; -// int pidSlot = -// isDriveMotor ? SparkMAX_slotIdx.Velocity.ordinal() : SparkMAX_slotIdx.Position.ordinal(); int pidSlot = 0; + if (cfgUpdated) + { + burnFlash(); + Timer.delay(0.1); // Give 100ms to apply changes + } + if (isDriveMotor) { configureSparkFlex(() -> @@ -454,7 +529,7 @@ public double getAppliedOutput() @Override public double getVelocity() { - return absoluteEncoder == null ? encoder.getVelocity() : absoluteEncoder.getVelocity(); + return velocity.get(); } /** @@ -465,7 +540,7 @@ public double getVelocity() @Override public double getPosition() { - return absoluteEncoder == null ? encoder.getPosition() : absoluteEncoder.getAbsolutePosition(); + return position.get(); } /** @@ -482,22 +557,4 @@ public void setPosition(double position) } } - /** - * REV Slots for PID configuration. - */ - enum SparkMAX_slotIdx - { - /** - * Slot 1, used for position PID's. - */ - Position, - /** - * Slot 2, used for velocity PID's. - */ - Velocity, - /** - * Slot 3, used arbitrarily. (Documentation show simulations). - */ - Simulation - } } diff --git a/swervelib/motors/SparkMaxBrushedMotorSwerve.java b/swervelib/motors/SparkMaxBrushedMotorSwerve.java index f384352f..0502da4a 100644 --- a/swervelib/motors/SparkMaxBrushedMotorSwerve.java +++ b/swervelib/motors/SparkMaxBrushedMotorSwerve.java @@ -1,24 +1,35 @@ package swervelib.motors; +import static edu.wpi.first.units.Units.Seconds; + import com.revrobotics.AbsoluteEncoder; -import com.revrobotics.CANSparkBase.ControlType; -import com.revrobotics.CANSparkBase.IdleMode; -import com.revrobotics.CANSparkLowLevel.MotorType; -import com.revrobotics.CANSparkLowLevel.PeriodicFrame; -import com.revrobotics.CANSparkMax; import com.revrobotics.REVLibError; import com.revrobotics.RelativeEncoder; -import com.revrobotics.SparkMaxAlternateEncoder; -import com.revrobotics.SparkPIDController; -import com.revrobotics.SparkRelativeEncoder.Type; +import com.revrobotics.spark.SparkBase.ControlType; +import com.revrobotics.spark.SparkBase.PersistMode; +import com.revrobotics.spark.SparkBase.ResetMode; +import com.revrobotics.spark.SparkClosedLoopController; +import com.revrobotics.spark.SparkLowLevel.MotorType; +import com.revrobotics.spark.SparkMax; +import com.revrobotics.spark.config.ClosedLoopConfig.FeedbackSensor; +import com.revrobotics.spark.config.SparkBaseConfig.IdleMode; +import com.revrobotics.spark.config.SparkMaxConfig; +import edu.wpi.first.math.system.plant.DCMotor; +import edu.wpi.first.units.Units; +import edu.wpi.first.wpilibj.Alert; +import edu.wpi.first.wpilibj.Alert.AlertType; +import edu.wpi.first.wpilibj.DriverStation; import edu.wpi.first.wpilibj.Timer; +import java.util.Optional; import java.util.function.Supplier; +import swervelib.encoders.SparkMaxAnalogEncoderSwerve; +import swervelib.encoders.SparkMaxEncoderSwerve; import swervelib.encoders.SwerveAbsoluteEncoder; import swervelib.parser.PIDFConfig; -import swervelib.telemetry.Alert; +import swervelib.telemetry.SwerveDriveTelemetry; /** - * Brushed motor control with {@link CANSparkMax}. + * Brushed motor control with {@link SparkMax}. */ public class SparkMaxBrushedMotorSwerve extends SwerveMotor { @@ -26,59 +37,74 @@ public class SparkMaxBrushedMotorSwerve extends SwerveMotor /** * SparkMAX Instance. */ - private final CANSparkMax motor; - + private final SparkMax motor; /** * Absolute encoder attached to the SparkMax (if exists) */ - public AbsoluteEncoder absoluteEncoder; + public SwerveAbsoluteEncoder absoluteEncoder; /** * Integrated encoder. */ - public RelativeEncoder encoder; + public Optional encoder = Optional.empty(); /** * Closed-loop PID controller. */ - public SparkPIDController pid; + public SparkClosedLoopController pid; + /** + * Supplier for the velocity of the motor controller. + */ + private Supplier velocity; + /** + * Supplier for the position of the motor controller. + */ + private Supplier position; /** * Factory default already occurred. */ - private boolean factoryDefaultOccurred = false; + private boolean factoryDefaultOccurred = false; /** * An {@link Alert} for if the motor has no encoder. */ - private Alert noEncoderAlert; + private Alert noEncoderAlert; /** * An {@link Alert} for if there is an error configuring the motor. */ - private Alert failureConfiguringAlert; + private Alert failureConfiguringAlert; /** * An {@link Alert} for if the motor has no encoder defined. */ - private Alert noEncoderDefinedAlert; + private Alert noEncoderDefinedAlert; + /** + * Configuration object for {@link SparkMax} motor. + */ + private SparkMaxConfig cfg = new SparkMaxConfig(); + /** + * Tracker for changes that need to be pushed. + */ + private boolean cfgUpdated = false; /** * Initialize the swerve motor. * * @param motor The SwerveMotor as a SparkMax object. * @param isDriveMotor Is the motor being initialized a drive motor? - * @param encoderType {@link Type} of encoder to use for the {@link CANSparkMax} device. + * @param encoderType {@link Type} of encoder to use for the {@link SparkMax} device. * @param countsPerRevolution The number of encoder pulses for the {@link Type} encoder per revolution. * @param useDataPortQuadEncoder Use the encoder attached to the data port of the spark max for a quadrature encoder. + * @param motorType {@link DCMotor} which the {@link SparkMax} is attached to. */ - public SparkMaxBrushedMotorSwerve(CANSparkMax motor, boolean isDriveMotor, Type encoderType, int countsPerRevolution, - boolean useDataPortQuadEncoder) + public SparkMaxBrushedMotorSwerve(SparkMax motor, boolean isDriveMotor, Type encoderType, int countsPerRevolution, + boolean useDataPortQuadEncoder, DCMotor motorType) { - noEncoderAlert = new Alert("Motors", "Cannot use motor without encoder.", - Alert.AlertType.ERROR_TRACE); + AlertType.kError); failureConfiguringAlert = new Alert("Motors", "Failure configuring motor " + motor.getDeviceId(), - Alert.AlertType.WARNING_TRACE); + AlertType.kWarning); noEncoderDefinedAlert = new Alert("Motors", "An encoder MUST be defined to work with a SparkMAX", - Alert.AlertType.ERROR_TRACE); + AlertType.kError); // Drive motors **MUST** have an encoder attached. if (isDriveMotor && encoderType == Type.kNoSensor) @@ -95,42 +121,61 @@ public SparkMaxBrushedMotorSwerve(CANSparkMax motor, boolean isDriveMotor, Type this.motor = motor; this.isDriveMotor = isDriveMotor; + this.simMotor = motorType; factoryDefaults(); clearStickyFaults(); // Get the onboard PID controller. - pid = motor.getPIDController(); + pid = motor.getClosedLoopController(); // If there is a sensor attached to the data port or encoder port set the relative encoder. if (isDriveMotor || (encoderType != Type.kNoSensor || useDataPortQuadEncoder)) { - this.encoder = useDataPortQuadEncoder ? - motor.getAlternateEncoder(SparkMaxAlternateEncoder.Type.kQuadrature, countsPerRevolution) : - motor.getEncoder(encoderType, countsPerRevolution); - // Configure feedback of the PID controller as the integrated encoder. - configureSparkMax(() -> pid.setFeedbackDevice(encoder)); + if (useDataPortQuadEncoder) + { + this.encoder = Optional.of(motor.getAlternateEncoder()); + cfg.alternateEncoder.countsPerRevolution(countsPerRevolution); + + // Configure feedback of the PID controller as the integrated encoder. + cfg.closedLoop.feedbackSensor(FeedbackSensor.kAlternateOrExternalEncoder); + } else + { + this.encoder = Optional.of(motor.getEncoder()); + cfg.encoder.countsPerRevolution(countsPerRevolution); + + // Configure feedback of the PID controller as the integrated encoder. + cfg.closedLoop.feedbackSensor(FeedbackSensor.kPrimaryEncoder); + } + cfgUpdated = true; } + encoder.ifPresentOrElse((RelativeEncoder enc) -> { + velocity = enc::getVelocity; + position = enc::getPosition; + }, () -> { + noEncoderDefinedAlert.set(true); + }); // Spin off configurations in a different thread. // configureSparkMax(() -> motor.setCANTimeout(0)); // Commented it out because it prevents feedback. } /** - * Initialize the {@link SwerveMotor} as a {@link CANSparkMax} connected to a Brushless Motor. + * Initialize the {@link SwerveMotor} as a {@link SparkMax} connected to a Brushless Motor. * * @param id CAN ID of the SparkMax. * @param isDriveMotor Is the motor being initialized a drive motor? - * @param encoderType {@link Type} of encoder to use for the {@link CANSparkMax} device. + * @param encoderType {@link Type} of encoder to use for the {@link SparkMax} device. * @param countsPerRevolution The number of encoder pulses for the {@link Type} encoder per revolution. * @param useDataPortQuadEncoder Use the encoder attached to the data port of the spark max for a quadrature encoder. + * @param motorType Motor type controlled by the {@link SparkMax} motor controller. */ public SparkMaxBrushedMotorSwerve(int id, boolean isDriveMotor, Type encoderType, int countsPerRevolution, - boolean useDataPortQuadEncoder) + boolean useDataPortQuadEncoder, DCMotor motorType) { - this(new CANSparkMax(id, MotorType.kBrushed), isDriveMotor, encoderType, countsPerRevolution, - useDataPortQuadEncoder); + this(new SparkMax(id, MotorType.kBrushed), isDriveMotor, encoderType, countsPerRevolution, + useDataPortQuadEncoder, motorType); } /** @@ -146,11 +191,33 @@ private void configureSparkMax(Supplier config) { return; } - Timer.delay(0.01); + Timer.delay(Units.Milliseconds.of(10).in(Seconds)); } failureConfiguringAlert.set(true); } + /** + * Get the current configuration of the {@link SparkMax} + * + * @return {@link SparkMaxConfig} + */ + public SparkMaxConfig getConfig() + { + return cfg; + } + + /** + * Update the config for the {@link SparkMax} + * + * @param cfgGiven Given {@link SparkMaxConfig} which should have minimal modifications. + */ + public void updateConfig(SparkMaxConfig cfgGiven) + { + cfg.apply(cfgGiven); + configureSparkMax(() -> motor.configure(cfg, ResetMode.kNoResetSafeParameters, PersistMode.kPersistParameters)); + cfgUpdated = false; + } + /** * Set the voltage compensation for the swerve module motor. * @@ -159,7 +226,8 @@ private void configureSparkMax(Supplier config) @Override public void setVoltageCompensation(double nominalVoltage) { - configureSparkMax(() -> motor.enableVoltageCompensation(nominalVoltage)); + cfg.voltageCompensation(nominalVoltage); + cfgUpdated = true; } /** @@ -171,7 +239,8 @@ public void setVoltageCompensation(double nominalVoltage) @Override public void setCurrentLimit(int currentLimit) { - configureSparkMax(() -> motor.setSmartCurrentLimit(currentLimit)); + cfg.smartCurrentLimit(currentLimit); + cfgUpdated = true; } /** @@ -182,8 +251,9 @@ public void setCurrentLimit(int currentLimit) @Override public void setLoopRampRate(double rampRate) { - configureSparkMax(() -> motor.setOpenLoopRampRate(rampRate)); - configureSparkMax(() -> motor.setClosedLoopRampRate(rampRate)); + cfg.closedLoopRampRate(rampRate) + .openLoopRampRate(rampRate); + cfgUpdated = true; } /** @@ -197,6 +267,21 @@ public Object getMotor() return motor; } + /** + * Get the {@link DCMotor} of the motor class. + * + * @return {@link DCMotor} of this type. + */ + @Override + public DCMotor getSimMotor() + { + if (simMotor == null) + { + simMotor = DCMotor.getCIM(1); + } + return simMotor; + } + /** * Queries whether the absolute encoder is directly attached to the motor controller. * @@ -214,11 +299,7 @@ public boolean isAttachedAbsoluteEncoder() @Override public void factoryDefaults() { - if (!factoryDefaultOccurred) - { - configureSparkMax(motor::restoreFactoryDefaults); - factoryDefaultOccurred = true; - } + // Do nothing } /** @@ -242,13 +323,32 @@ public SwerveMotor setAbsoluteEncoder(SwerveAbsoluteEncoder encoder) if (encoder == null) { absoluteEncoder = null; - configureSparkMax(() -> pid.setFeedbackDevice(this.encoder)); - } else if (encoder.getAbsoluteEncoder() instanceof AbsoluteEncoder) + cfg.closedLoop.feedbackSensor(FeedbackSensor.kPrimaryEncoder); + cfgUpdated = true; + + this.encoder.ifPresentOrElse((RelativeEncoder enc) -> { + velocity = enc::getVelocity; + position = enc::getPosition; + }, () -> { + noEncoderDefinedAlert.set(true); + }); + burnFlash(); + } else if (encoder instanceof SparkMaxAnalogEncoderSwerve || encoder instanceof SparkMaxEncoderSwerve) { - absoluteEncoder = (AbsoluteEncoder) encoder.getAbsoluteEncoder(); - configureSparkMax(() -> pid.setFeedbackDevice(absoluteEncoder)); + cfg.closedLoop.feedbackSensor(encoder instanceof SparkMaxAnalogEncoderSwerve + ? FeedbackSensor.kAnalogSensor : FeedbackSensor.kAbsoluteEncoder); + cfgUpdated = true; + + DriverStation.reportWarning( + "IF possible configure the encoder offset in the REV Hardware Client instead of using the" + + " absoluteEncoderOffset in the Swerve Module JSON!", + false); + absoluteEncoder = encoder; + velocity = absoluteEncoder::getVelocity; + position = absoluteEncoder::getAbsolutePosition; + noEncoderDefinedAlert.set(false); } - if (absoluteEncoder == null && this.encoder == null) + if (absoluteEncoder == null && this.encoder.isEmpty()) { noEncoderDefinedAlert.set(true); throw new RuntimeException("An encoder MUST be defined to work with a SparkMAX"); @@ -264,19 +364,79 @@ public SwerveMotor setAbsoluteEncoder(SwerveAbsoluteEncoder encoder) @Override public void configureIntegratedEncoder(double positionConversionFactor) { + cfg.signals + .absoluteEncoderPositionAlwaysOn(false) + .absoluteEncoderVelocityAlwaysOn(false) + .analogPositionAlwaysOn(false) + .analogVelocityAlwaysOn(false) + .analogVoltageAlwaysOn(false) + .externalOrAltEncoderPositionAlwaysOn(false) + .externalOrAltEncoderVelocityAlwaysOn(false) + .primaryEncoderPositionAlwaysOn(false) + .primaryEncoderVelocityAlwaysOn(false) + .iAccumulationAlwaysOn(false) + .appliedOutputPeriodMs(10) + .faultsPeriodMs(20); if (absoluteEncoder == null) { - configureSparkMax(() -> encoder.setPositionConversionFactor(positionConversionFactor)); - configureSparkMax(() -> encoder.setVelocityConversionFactor(positionConversionFactor / 60)); + cfg.closedLoop.feedbackSensor(FeedbackSensor.kPrimaryEncoder); + cfg.encoder + .positionConversionFactor(positionConversionFactor) + .velocityConversionFactor(positionConversionFactor / 60); + // Changes the measurement period and number of samples used to calculate the velocity for the intergrated motor controller + // Notability this changes the returned velocity and the velocity used for the onboard velocity PID loop (TODO: triple check the PID portion of this statement) + // Default settings of 32ms and 8 taps introduce ~100ms of measurement lag + // https://www.chiefdelphi.com/t/shooter-encoder/400211/11 + // This value was taken from: + // https://github.com/Mechanical-Advantage/RobotCode2023/blob/9884d13b2220b76d430e82248fd837adbc4a10bc/src/main/java/org/littletonrobotics/frc2023/subsystems/drive/ModuleIOSparkMax.java#L132-L133 + // and tested on 9176 for YAGSL, notably 3005 uses 16ms instead of 10 but 10 is more common based on github searches + cfg.encoder + .quadratureMeasurementPeriod(10) + .quadratureAverageDepth(2); // Taken from - // https://github.com/frc3512/SwerveBot-2022/blob/9d31afd05df6c630d5acb4ec2cf5d734c9093bf8/src/main/java/frc/lib/util/CANSparkMaxUtil.java#L67 - configureCANStatusFrames(10, 20, 20, 500, 500); + // https://github.com/frc3512/SwerveBot-2022/blob/9d31afd05df6c630d5acb4ec2cf5d734c9093bf8/src/main/java/frc/lib/util/SparkMaxUtil.java#L67 + // Unused frames can be set to 65535 to decrease CAN ultilization. + cfg.signals + .primaryEncoderVelocityAlwaysOn(isDriveMotor) // Disable velocity reporting for angle motors. + .primaryEncoderPositionAlwaysOn(true) + .primaryEncoderPositionPeriodMs(20); } else { - configureSparkMax(() -> absoluteEncoder.setPositionConversionFactor(positionConversionFactor)); - configureSparkMax(() -> absoluteEncoder.setVelocityConversionFactor(positionConversionFactor / 60)); + // By default the SparkMax relays the info from the duty cycle encoder to the roborio every 200ms on CAN frame 5 + // This needs to be set to 20ms or under to properly update the swerve module position for odometry + // Configuration taken from 3005, the team who helped develop the Max Swerve: + // https://github.com/FRC3005/Charged-Up-2023-Public/blob/2b6a7c695e23edebafa27a76cf639a00f6e8a3a6/src/main/java/frc/robot/subsystems/drive/REVSwerveModule.java#L227-L244 + // Some of the frames can probably be adjusted to decrease CAN utilization, with 65535 being the max. + // From testing, 20ms on frame 5 sometimes returns the same value while constantly powering the azimuth but 8ms may be overkill, + // with limited testing 19ms did not return the same value while the module was constatntly rotating. + if (absoluteEncoder.getAbsoluteEncoder() instanceof AbsoluteEncoder) + { + cfg.closedLoop.feedbackSensor(FeedbackSensor.kAbsoluteEncoder); + + cfg.signals + .absoluteEncoderPositionAlwaysOn(true) + .absoluteEncoderPositionPeriodMs(20); + + cfg.absoluteEncoder + .positionConversionFactor(positionConversionFactor) + .velocityConversionFactor(positionConversionFactor / 60); + } else + { + cfg.closedLoop.feedbackSensor(FeedbackSensor.kAnalogSensor); + + cfg.signals + .analogVoltageAlwaysOn(true) + .analogPositionAlwaysOn(true) + .analogVoltagePeriodMs(20) + .analogPositionPeriodMs(20); + + cfg.analogSensor + .positionConversionFactor(positionConversionFactor) + .velocityConversionFactor(positionConversionFactor / 60); + } } + cfgUpdated = true; } /** @@ -287,16 +447,10 @@ public void configureIntegratedEncoder(double positionConversionFactor) @Override public void configurePIDF(PIDFConfig config) { -// int pidSlot = -// isDriveMotor ? SparkMAX_slotIdx.Velocity.ordinal() : SparkMAX_slotIdx.Position.ordinal(); - int pidSlot = 0; - - configureSparkMax(() -> pid.setP(config.p, pidSlot)); - configureSparkMax(() -> pid.setI(config.i, pidSlot)); - configureSparkMax(() -> pid.setD(config.d, pidSlot)); - configureSparkMax(() -> pid.setFF(config.f, pidSlot)); - configureSparkMax(() -> pid.setIZone(config.iz, pidSlot)); - configureSparkMax(() -> pid.setOutputRange(config.output.min, config.output.max, pidSlot)); + cfg.closedLoop.pidf(config.p, config.i, config.d, config.f) + .iZone(config.iz) + .outputRange(config.output.min, config.output.max); + cfgUpdated = true; } /** @@ -308,30 +462,11 @@ public void configurePIDF(PIDFConfig config) @Override public void configurePIDWrapping(double minInput, double maxInput) { - configureSparkMax(() -> pid.setPositionPIDWrappingEnabled(true)); - configureSparkMax(() -> pid.setPositionPIDWrappingMinInput(minInput)); - configureSparkMax(() -> pid.setPositionPIDWrappingMaxInput(maxInput)); - } + cfg.closedLoop + .positionWrappingEnabled(true) + .positionWrappingInputRange(minInput, maxInput); + cfgUpdated = true; - /** - * Set the CAN status frames. - * - * @param CANStatus0 Applied Output, Faults, Sticky Faults, Is Follower - * @param CANStatus1 Motor Velocity, Motor Temperature, Motor Voltage, Motor Current - * @param CANStatus2 Motor Position - * @param CANStatus3 Analog Sensor Voltage, Analog Sensor Velocity, Analog Sensor Position - * @param CANStatus4 Alternate Encoder Velocity, Alternate Encoder Position - */ - public void configureCANStatusFrames( - int CANStatus0, int CANStatus1, int CANStatus2, int CANStatus3, int CANStatus4) - { - configureSparkMax(() -> motor.setPeriodicFramePeriod(PeriodicFrame.kStatus0, CANStatus0)); - configureSparkMax(() -> motor.setPeriodicFramePeriod(PeriodicFrame.kStatus1, CANStatus1)); - configureSparkMax(() -> motor.setPeriodicFramePeriod(PeriodicFrame.kStatus2, CANStatus2)); - configureSparkMax(() -> motor.setPeriodicFramePeriod(PeriodicFrame.kStatus3, CANStatus3)); - configureSparkMax(() -> motor.setPeriodicFramePeriod(PeriodicFrame.kStatus4, CANStatus4)); - // TODO: Configure Status Frame 5 and 6 if necessary - // https://docs.revrobotics.com/sparkmax/operating-modes/control-interfaces } /** @@ -342,7 +477,8 @@ public void configureCANStatusFrames( @Override public void setMotorBrake(boolean isBrakeMode) { - configureSparkMax(() -> motor.setIdleMode(isBrakeMode ? IdleMode.kBrake : IdleMode.kCoast)); + cfg.idleMode(isBrakeMode ? IdleMode.kBrake : IdleMode.kCoast); + cfgUpdated = true; } /** @@ -353,10 +489,8 @@ public void setMotorBrake(boolean isBrakeMode) @Override public void setInverted(boolean inverted) { - configureSparkMax(() -> { - motor.setInverted(inverted); - return motor.getLastError(); - }); + cfg.inverted(inverted); + cfgUpdated = true; } /** @@ -365,7 +499,8 @@ public void setInverted(boolean inverted) @Override public void burnFlash() { - configureSparkMax(() -> motor.burnFlash()); + motor.configure(cfg, ResetMode.kNoResetSafeParameters, PersistMode.kPersistParameters); + cfgUpdated = false; } /** @@ -388,16 +523,37 @@ public void set(double percentOutput) @Override public void setReference(double setpoint, double feedforward) { -// int pidSlot = -// isDriveMotor ? SparkMAX_slotIdx.Velocity.ordinal() : SparkMAX_slotIdx.Position.ordinal(); int pidSlot = 0; - configureSparkMax(() -> - pid.setReference( - setpoint, - isDriveMotor ? ControlType.kVelocity : ControlType.kPosition, - pidSlot, - feedforward) - ); + + if (cfgUpdated) + { + burnFlash(); + Timer.delay(0.1); // Give 100ms to apply changes + } + + if (isDriveMotor) + { + configureSparkMax(() -> + pid.setReference( + setpoint, + ControlType.kVelocity, + pidSlot, + feedforward)); + } else + { + configureSparkMax(() -> + pid.setReference( + setpoint, + ControlType.kPosition, + pidSlot, + feedforward)); + if (SwerveDriveTelemetry.isSimulation) + { + encoder.ifPresent((RelativeEncoder enc) -> { + enc.setPosition(setpoint); + }); + } + } } /** @@ -454,7 +610,7 @@ public double getAppliedOutput() @Override public double getVelocity() { - return absoluteEncoder == null ? encoder.getVelocity() : absoluteEncoder.getVelocity(); + return velocity.get(); } /** @@ -465,7 +621,7 @@ public double getVelocity() @Override public double getPosition() { - return absoluteEncoder == null ? encoder.getPosition() : absoluteEncoder.getPosition(); + return position.get(); } /** @@ -478,7 +634,28 @@ public void setPosition(double position) { if (absoluteEncoder == null) { - configureSparkMax(() -> encoder.setPosition(position)); + encoder.ifPresent((RelativeEncoder enc) -> { + configureSparkMax(() -> enc.setPosition(position)); + }); } } + + /** + * Type for encoder for {@link SparkMax} + */ + public enum Type + { + /** + * NO sensor + */ + kNoSensor, + /** + * Hall sensor attached to dataport + */ + kHallSensor, + /** + * Quad encoder attached to alt + */ + kQuadrature, + } } diff --git a/swervelib/motors/SparkMaxSwerve.java b/swervelib/motors/SparkMaxSwerve.java index 14795716..b36da814 100644 --- a/swervelib/motors/SparkMaxSwerve.java +++ b/swervelib/motors/SparkMaxSwerve.java @@ -1,91 +1,112 @@ package swervelib.motors; +import static edu.wpi.first.units.Units.Seconds; + import com.revrobotics.AbsoluteEncoder; -import com.revrobotics.CANSparkBase.ControlType; -import com.revrobotics.CANSparkBase.IdleMode; -import com.revrobotics.CANSparkLowLevel.MotorType; -import com.revrobotics.CANSparkLowLevel.PeriodicFrame; -import com.revrobotics.CANSparkMax; -import com.revrobotics.MotorFeedbackSensor; import com.revrobotics.REVLibError; import com.revrobotics.RelativeEncoder; -import com.revrobotics.SparkAnalogSensor; -import com.revrobotics.SparkPIDController; +import com.revrobotics.spark.SparkBase.ControlType; +import com.revrobotics.spark.SparkBase.PersistMode; +import com.revrobotics.spark.SparkBase.ResetMode; +import com.revrobotics.spark.SparkClosedLoopController; +import com.revrobotics.spark.SparkLowLevel.MotorType; +import com.revrobotics.spark.SparkMax; +import com.revrobotics.spark.config.ClosedLoopConfig.FeedbackSensor; +import com.revrobotics.spark.config.SparkBaseConfig.IdleMode; +import com.revrobotics.spark.config.SparkMaxConfig; +import edu.wpi.first.math.system.plant.DCMotor; +import edu.wpi.first.units.Units; import edu.wpi.first.wpilibj.DriverStation; import edu.wpi.first.wpilibj.Timer; import java.util.function.Supplier; +import swervelib.encoders.SparkMaxAnalogEncoderSwerve; +import swervelib.encoders.SparkMaxEncoderSwerve; import swervelib.encoders.SwerveAbsoluteEncoder; import swervelib.parser.PIDFConfig; import swervelib.telemetry.SwerveDriveTelemetry; /** - * An implementation of {@link CANSparkMax} as a {@link SwerveMotor}. + * An implementation of {@link com.revrobotics.spark.SparkMax} as a {@link SwerveMotor}. */ public class SparkMaxSwerve extends SwerveMotor { /** - * {@link CANSparkMax} Instance. + * {@link SparkMax} Instance. */ - private final CANSparkMax motor; + private final SparkMax motor; /** * Integrated encoder. */ - public RelativeEncoder encoder; + public RelativeEncoder encoder; /** * Absolute encoder attached to the SparkMax (if exists) */ - public SwerveAbsoluteEncoder absoluteEncoder; + public SwerveAbsoluteEncoder absoluteEncoder; /** * Closed-loop PID controller. */ - public SparkPIDController pid; + public SparkClosedLoopController pid; /** * Factory default already occurred. */ - private boolean factoryDefaultOccurred = false; + private boolean factoryDefaultOccurred = false; /** * Supplier for the velocity of the motor controller. */ - private Supplier velocity; + private Supplier velocity; /** * Supplier for the position of the motor controller. */ - private Supplier position; + private Supplier position; + /** + * Configuration object for {@link SparkMax} motor. + */ + private SparkMaxConfig cfg = new SparkMaxConfig(); + /** + * Tracker for changes that need to be pushed. + */ + private boolean cfgUpdated = false; + /** * Initialize the swerve motor. * * @param motor The SwerveMotor as a SparkMax object. * @param isDriveMotor Is the motor being initialized a drive motor? + * @param motorType Motor type controlled by the {@link SparkMax} motor controller. */ - public SparkMaxSwerve(CANSparkMax motor, boolean isDriveMotor) + public SparkMaxSwerve(SparkMax motor, boolean isDriveMotor, DCMotor motorType) { this.motor = motor; this.isDriveMotor = isDriveMotor; + this.simMotor = motorType; factoryDefaults(); clearStickyFaults(); encoder = motor.getEncoder(); - pid = motor.getPIDController(); - pid.setFeedbackDevice( - encoder); // Configure feedback of the PID controller as the integrated encoder. + pid = motor.getClosedLoopController(); + cfg.closedLoop.feedbackSensor(FeedbackSensor.kPrimaryEncoder); // Configure feedback of the PID controller as the integrated encoder. + cfgUpdated = true; velocity = encoder::getVelocity; position = encoder::getPosition; + // Spin off configurations in a different thread. // configureSparkMax(() -> motor.setCANTimeout(0)); // Commented out because it prevents feedback. } + /** - * Initialize the {@link SwerveMotor} as a {@link CANSparkMax} connected to a Brushless Motor. + * Initialize the {@link SwerveMotor} as a {@link SparkMax} connected to a Brushless Motor. * * @param id CAN ID of the SparkMax. * @param isDriveMotor Is the motor being initialized a drive motor? + * @param motorType Motor type controlled by the {@link SparkMax} motor controller. */ - public SparkMaxSwerve(int id, boolean isDriveMotor) + public SparkMaxSwerve(int id, boolean isDriveMotor, DCMotor motorType) { - this(new CANSparkMax(id, MotorType.kBrushless), isDriveMotor); + this(new SparkMax(id, MotorType.kBrushless), isDriveMotor, motorType); } /** @@ -101,11 +122,33 @@ private void configureSparkMax(Supplier config) { return; } - Timer.delay(0.01); + Timer.delay(Units.Milliseconds.of(10).in(Seconds)); } DriverStation.reportWarning("Failure configuring motor " + motor.getDeviceId(), true); } + /** + * Get the current configuration of the {@link SparkMax} + * + * @return {@link SparkMaxConfig} + */ + public SparkMaxConfig getConfig() + { + return cfg; + } + + /** + * Update the config for the {@link SparkMax} + * + * @param cfgGiven Given {@link SparkMaxConfig} which should have minimal modifications. + */ + public void updateConfig(SparkMaxConfig cfgGiven) + { + cfg.apply(cfgGiven); + configureSparkMax(() -> motor.configure(cfg, ResetMode.kNoResetSafeParameters, PersistMode.kPersistParameters)); + cfgUpdated = false; + } + /** * Set the voltage compensation for the swerve module motor. * @@ -114,7 +157,8 @@ private void configureSparkMax(Supplier config) @Override public void setVoltageCompensation(double nominalVoltage) { - configureSparkMax(() -> motor.enableVoltageCompensation(nominalVoltage)); + cfg.voltageCompensation(nominalVoltage); + cfgUpdated = true; } /** @@ -126,7 +170,9 @@ public void setVoltageCompensation(double nominalVoltage) @Override public void setCurrentLimit(int currentLimit) { - configureSparkMax(() -> motor.setSmartCurrentLimit(currentLimit)); + cfg.smartCurrentLimit(currentLimit); + cfgUpdated = true; + } /** @@ -137,8 +183,10 @@ public void setCurrentLimit(int currentLimit) @Override public void setLoopRampRate(double rampRate) { - configureSparkMax(() -> motor.setOpenLoopRampRate(rampRate)); - configureSparkMax(() -> motor.setClosedLoopRampRate(rampRate)); + cfg.closedLoopRampRate(rampRate) + .openLoopRampRate(rampRate); + cfgUpdated = true; + } /** @@ -152,6 +200,21 @@ public Object getMotor() return motor; } + /** + * Get the {@link DCMotor} of the motor class. + * + * @return {@link DCMotor} of this type. + */ + @Override + public DCMotor getSimMotor() + { + if (simMotor == null) + { + simMotor = DCMotor.getNEO(1); + } + return simMotor; + } + /** * Queries whether the absolute encoder is directly attached to the motor controller. * @@ -169,11 +232,7 @@ public boolean isAttachedAbsoluteEncoder() @Override public void factoryDefaults() { - if (!factoryDefaultOccurred) - { - configureSparkMax(motor::restoreFactoryDefaults); - factoryDefaultOccurred = true; - } + // Do nothing } /** @@ -197,17 +256,23 @@ public SwerveMotor setAbsoluteEncoder(SwerveAbsoluteEncoder encoder) if (encoder == null) { absoluteEncoder = null; - configureSparkMax(() -> pid.setFeedbackDevice(this.encoder)); + cfg.closedLoop.feedbackSensor(FeedbackSensor.kPrimaryEncoder); + cfgUpdated = true; + velocity = this.encoder::getVelocity; position = this.encoder::getPosition; - } else if (encoder.getAbsoluteEncoder() instanceof MotorFeedbackSensor) + burnFlash(); + } else if (encoder instanceof SparkMaxAnalogEncoderSwerve || encoder instanceof SparkMaxEncoderSwerve) { + cfg.closedLoop.feedbackSensor(encoder instanceof SparkMaxAnalogEncoderSwerve + ? FeedbackSensor.kAnalogSensor : FeedbackSensor.kAbsoluteEncoder); + cfgUpdated = true; + DriverStation.reportWarning( "IF possible configure the encoder offset in the REV Hardware Client instead of using the" + " absoluteEncoderOffset in the Swerve Module JSON!", false); absoluteEncoder = encoder; - configureSparkMax(() -> pid.setFeedbackDevice((MotorFeedbackSensor) absoluteEncoder.getAbsoluteEncoder())); velocity = absoluteEncoder::getVelocity; position = absoluteEncoder::getAbsolutePosition; } @@ -222,10 +287,26 @@ public SwerveMotor setAbsoluteEncoder(SwerveAbsoluteEncoder encoder) @Override public void configureIntegratedEncoder(double positionConversionFactor) { + cfg.signals + .absoluteEncoderPositionAlwaysOn(false) + .absoluteEncoderVelocityAlwaysOn(false) + .analogPositionAlwaysOn(false) + .analogVelocityAlwaysOn(false) + .analogVoltageAlwaysOn(false) + .externalOrAltEncoderPositionAlwaysOn(false) + .externalOrAltEncoderVelocityAlwaysOn(false) + .primaryEncoderPositionAlwaysOn(false) + .primaryEncoderVelocityAlwaysOn(false) + .iAccumulationAlwaysOn(false) + .appliedOutputPeriodMs(10) + .faultsPeriodMs(20); + if (absoluteEncoder == null) { - configureSparkMax(() -> encoder.setPositionConversionFactor(positionConversionFactor)); - configureSparkMax(() -> encoder.setVelocityConversionFactor(positionConversionFactor / 60)); + cfg.closedLoop.feedbackSensor(FeedbackSensor.kPrimaryEncoder); + cfg.encoder + .positionConversionFactor(positionConversionFactor) + .velocityConversionFactor(positionConversionFactor / 60); // Changes the measurement period and number of samples used to calculate the velocity for the intergrated motor controller // Notability this changes the returned velocity and the velocity used for the onboard velocity PID loop (TODO: triple check the PID portion of this statement) // Default settings of 32ms and 8 taps introduce ~100ms of measurement lag @@ -233,54 +314,55 @@ public void configureIntegratedEncoder(double positionConversionFactor) // This value was taken from: // https://github.com/Mechanical-Advantage/RobotCode2023/blob/9884d13b2220b76d430e82248fd837adbc4a10bc/src/main/java/org/littletonrobotics/frc2023/subsystems/drive/ModuleIOSparkMax.java#L132-L133 // and tested on 9176 for YAGSL, notably 3005 uses 16ms instead of 10 but 10 is more common based on github searches - configureSparkMax(() -> encoder.setMeasurementPeriod(10)); - configureSparkMax(() -> encoder.setAverageDepth(2)); + cfg.encoder + .quadratureMeasurementPeriod(10) + .quadratureAverageDepth(2); // Taken from - // https://github.com/frc3512/SwerveBot-2022/blob/9d31afd05df6c630d5acb4ec2cf5d734c9093bf8/src/main/java/frc/lib/util/CANSparkMaxUtil.java#L67 + // https://github.com/frc3512/SwerveBot-2022/blob/9d31afd05df6c630d5acb4ec2cf5d734c9093bf8/src/main/java/frc/lib/util/SparkMaxUtil.java#L67 // Unused frames can be set to 65535 to decrease CAN ultilization. - configureCANStatusFrames(10, 20, 20, 500, 500, 200, 200); + cfg.signals + .primaryEncoderVelocityAlwaysOn(isDriveMotor) // Disable velocity reporting for angle motors. + .primaryEncoderPositionAlwaysOn(true) + .primaryEncoderPositionPeriodMs(20); + } else { // By default the SparkMax relays the info from the duty cycle encoder to the roborio every 200ms on CAN frame 5 // This needs to be set to 20ms or under to properly update the swerve module position for odometry // Configuration taken from 3005, the team who helped develop the Max Swerve: // https://github.com/FRC3005/Charged-Up-2023-Public/blob/2b6a7c695e23edebafa27a76cf639a00f6e8a3a6/src/main/java/frc/robot/subsystems/drive/REVSwerveModule.java#L227-L244 - // Some of the frames can probably be adjusted to decrease CAN utilization, with 65535 being the max. + // Some of the frames can probably be adjusted to decrease CAN utilization, with 65535 being the max. // From testing, 20ms on frame 5 sometimes returns the same value while constantly powering the azimuth but 8ms may be overkill, // with limited testing 19ms did not return the same value while the module was constatntly rotating. if (absoluteEncoder.getAbsoluteEncoder() instanceof AbsoluteEncoder) { - configureCANStatusFrames(100, 20, 20, 200, 20, 8, 50); - } - // Need to test on analog encoders but the same concept should apply for them, worst thing that could happen is slightly more can use - else if (absoluteEncoder.getAbsoluteEncoder() instanceof SparkAnalogSensor) + cfg.closedLoop.feedbackSensor(FeedbackSensor.kAbsoluteEncoder); + + cfg.signals + .absoluteEncoderPositionAlwaysOn(true) + .absoluteEncoderPositionPeriodMs(20); + + cfg.absoluteEncoder + .positionConversionFactor(positionConversionFactor) + .velocityConversionFactor(positionConversionFactor / 60); + } else { - configureCANStatusFrames(100, 20, 20, 19, 200, 200, 200); + cfg.closedLoop.feedbackSensor(FeedbackSensor.kAnalogSensor); + + cfg.signals + .analogVoltageAlwaysOn(true) + .analogPositionAlwaysOn(true) + .analogVoltagePeriodMs(20) + .analogPositionPeriodMs(20); + + cfg.analogSensor + .positionConversionFactor(positionConversionFactor) + .velocityConversionFactor(positionConversionFactor / 60); } - configureSparkMax(() -> { - if (absoluteEncoder.getAbsoluteEncoder() instanceof AbsoluteEncoder) - { - return ((AbsoluteEncoder) absoluteEncoder.getAbsoluteEncoder()).setPositionConversionFactor( - positionConversionFactor); - } else - { - return ((SparkAnalogSensor) absoluteEncoder.getAbsoluteEncoder()).setPositionConversionFactor( - positionConversionFactor); - } - }); - configureSparkMax(() -> { - if (absoluteEncoder.getAbsoluteEncoder() instanceof AbsoluteEncoder) - { - return ((AbsoluteEncoder) absoluteEncoder.getAbsoluteEncoder()).setVelocityConversionFactor( - positionConversionFactor / 60); - } else - { - return ((SparkAnalogSensor) absoluteEncoder.getAbsoluteEncoder()).setVelocityConversionFactor( - positionConversionFactor / 60); - } - }); } + cfgUpdated = true; + } /** @@ -291,15 +373,11 @@ else if (absoluteEncoder.getAbsoluteEncoder() instanceof SparkAnalogSensor) @Override public void configurePIDF(PIDFConfig config) { -// int pidSlot = -// isDriveMotor ? SparkMAX_slotIdx.Velocity.ordinal() : SparkMAX_slotIdx.Position.ordinal(); - int pidSlot = 0; - configureSparkMax(() -> pid.setP(config.p)); - configureSparkMax(() -> pid.setI(config.i)); - configureSparkMax(() -> pid.setD(config.d)); - configureSparkMax(() -> pid.setFF(config.f)); - configureSparkMax(() -> pid.setIZone(config.iz)); - configureSparkMax(() -> pid.setOutputRange(config.output.min, config.output.max)); + cfg.closedLoop.pidf(config.p, config.i, config.d, config.f) + .iZone(config.iz) + .outputRange(config.output.min, config.output.max); + cfgUpdated = true; + } /** @@ -311,33 +389,11 @@ public void configurePIDF(PIDFConfig config) @Override public void configurePIDWrapping(double minInput, double maxInput) { - configureSparkMax(() -> pid.setPositionPIDWrappingEnabled(true)); - configureSparkMax(() -> pid.setPositionPIDWrappingMinInput(minInput)); - configureSparkMax(() -> pid.setPositionPIDWrappingMaxInput(maxInput)); - } + cfg.closedLoop + .positionWrappingEnabled(true) + .positionWrappingInputRange(minInput, maxInput); + cfgUpdated = true; - /** - * Set the CAN status frames. - * - * @param CANStatus0 Applied Output, Faults, Sticky Faults, Is Follower - * @param CANStatus1 Motor Velocity, Motor Temperature, Motor Voltage, Motor Current - * @param CANStatus2 Motor Position - * @param CANStatus3 Analog Sensor Voltage, Analog Sensor Velocity, Analog Sensor Position - * @param CANStatus4 Alternate Encoder Velocity, Alternate Encoder Position - * @param CANStatus5 Duty Cycle Absolute Encoder Position, Duty Cycle Absolute Encoder Absolute Angle - * @param CANStatus6 Duty Cycle Absolute Encoder Velocity, Duty Cycle Absolute Encoder Frequency - */ - public void configureCANStatusFrames( - int CANStatus0, int CANStatus1, int CANStatus2, int CANStatus3, int CANStatus4, int CANStatus5, int CANStatus6) - { - configureSparkMax(() -> motor.setPeriodicFramePeriod(PeriodicFrame.kStatus0, CANStatus0)); - configureSparkMax(() -> motor.setPeriodicFramePeriod(PeriodicFrame.kStatus1, CANStatus1)); - configureSparkMax(() -> motor.setPeriodicFramePeriod(PeriodicFrame.kStatus2, CANStatus2)); - configureSparkMax(() -> motor.setPeriodicFramePeriod(PeriodicFrame.kStatus3, CANStatus3)); - configureSparkMax(() -> motor.setPeriodicFramePeriod(PeriodicFrame.kStatus4, CANStatus4)); - configureSparkMax(() -> motor.setPeriodicFramePeriod(PeriodicFrame.kStatus5, CANStatus5)); - configureSparkMax(() -> motor.setPeriodicFramePeriod(PeriodicFrame.kStatus6, CANStatus6)); - // https://docs.revrobotics.com/sparkmax/operating-modes/control-interfaces } /** @@ -348,7 +404,9 @@ public void configureCANStatusFrames( @Override public void setMotorBrake(boolean isBrakeMode) { - configureSparkMax(() -> motor.setIdleMode(isBrakeMode ? IdleMode.kBrake : IdleMode.kCoast)); + cfg.idleMode(isBrakeMode ? IdleMode.kBrake : IdleMode.kCoast); + cfgUpdated = true; + } /** @@ -359,10 +417,8 @@ public void setMotorBrake(boolean isBrakeMode) @Override public void setInverted(boolean inverted) { - configureSparkMax(() -> { - motor.setInverted(inverted); - return motor.getLastError(); - }); + cfg.inverted(inverted); + cfgUpdated = true; } /** @@ -371,13 +427,8 @@ public void setInverted(boolean inverted) @Override public void burnFlash() { - try - { - Thread.sleep(200); - } catch (Exception e) - { - } - configureSparkMax(() -> motor.burnFlash()); + motor.configure(cfg, ResetMode.kNoResetSafeParameters, PersistMode.kPersistParameters); + cfgUpdated = false; } /** @@ -400,11 +451,14 @@ public void set(double percentOutput) @Override public void setReference(double setpoint, double feedforward) { - boolean possibleBurnOutIssue = true; -// int pidSlot = -// isDriveMotor ? SparkMAX_slotIdx.Velocity.ordinal() : SparkMAX_slotIdx.Position.ordinal(); int pidSlot = 0; + if (cfgUpdated) + { + burnFlash(); + Timer.delay(0.1); // Give 100ms to apply changes + } + if (isDriveMotor) { configureSparkMax(() -> @@ -509,23 +563,4 @@ public void setPosition(double position) configureSparkMax(() -> encoder.setPosition(position)); } } - - /** - * REV Slots for PID configuration. - */ - enum SparkMAX_slotIdx - { - /** - * Slot 1, used for position PID's. - */ - Position, - /** - * Slot 2, used for velocity PID's. - */ - Velocity, - /** - * Slot 3, used arbitrarily. (Documentation show simulations). - */ - Simulation - } } diff --git a/swervelib/motors/SwerveMotor.java b/swervelib/motors/SwerveMotor.java index 22b104d4..06940c6b 100644 --- a/swervelib/motors/SwerveMotor.java +++ b/swervelib/motors/SwerveMotor.java @@ -1,5 +1,6 @@ package swervelib.motors; +import edu.wpi.first.math.system.plant.DCMotor; import swervelib.encoders.SwerveAbsoluteEncoder; import swervelib.parser.PIDFConfig; @@ -13,6 +14,12 @@ public abstract class SwerveMotor * The maximum amount of times the swerve motor will attempt to configure a motor if failures occur. */ public final int maximumRetries = 5; + /** + * Sim motor to use, defaulted in {@link SwerveMotor#getSimMotor()}, but can be overridden here.
NOTE: This will + * not change the simulation motor type! It is intended for use only if you are utilizing Feedforwards from + * PathPlanner. + */ + public DCMotor simMotor; /** * Whether the swerve motor is a drive motor. */ @@ -172,6 +179,13 @@ public abstract class SwerveMotor */ public abstract Object getMotor(); + /** + * Get the {@link DCMotor} of the motor class. + * + * @return {@link DCMotor} of this type. + */ + public abstract DCMotor getSimMotor(); + /** * Queries whether the absolute encoder is directly attached to the motor controller. * diff --git a/swervelib/motors/TalonFXSwerve.java b/swervelib/motors/TalonFXSwerve.java index 233a868f..73393ce8 100644 --- a/swervelib/motors/TalonFXSwerve.java +++ b/swervelib/motors/TalonFXSwerve.java @@ -1,12 +1,18 @@ package swervelib.motors; +import static edu.wpi.first.units.Units.Degrees; +import static edu.wpi.first.units.Units.Rotations; +import static edu.wpi.first.units.Units.Volts; + import com.ctre.phoenix6.configs.TalonFXConfiguration; import com.ctre.phoenix6.configs.TalonFXConfigurator; import com.ctre.phoenix6.controls.MotionMagicVoltage; import com.ctre.phoenix6.controls.VelocityVoltage; import com.ctre.phoenix6.hardware.TalonFX; import com.ctre.phoenix6.signals.FeedbackSensorSourceValue; +import com.ctre.phoenix6.signals.InvertedValue; import com.ctre.phoenix6.signals.NeutralModeValue; +import edu.wpi.first.math.system.plant.DCMotor; import swervelib.encoders.SwerveAbsoluteEncoder; import swervelib.parser.PIDFConfig; import swervelib.telemetry.SwerveDriveTelemetry; @@ -20,39 +26,39 @@ public class TalonFXSwerve extends SwerveMotor /** * Wait time for status frames to show up. */ - public static double STATUS_TIMEOUT_SECONDS = 0.02; + public static double STATUS_TIMEOUT_SECONDS = 0.02; /** * Factory default already occurred. */ - private final boolean factoryDefaultOccurred = false; + private final boolean factoryDefaultOccurred = false; /** * Whether the absolute encoder is integrated. */ - private final boolean absoluteEncoder = false; + private final boolean absoluteEncoder = false; /** * Motion magic angle voltage setter. */ - private final MotionMagicVoltage m_angleVoltageSetter = new MotionMagicVoltage(0); + private final MotionMagicVoltage m_angleVoltageSetter = new MotionMagicVoltage(0); /** * Velocity voltage setter for controlling drive motor. */ - private final VelocityVoltage m_velocityVoltageSetter = new VelocityVoltage(0); + private final VelocityVoltage m_velocityVoltageSetter = new VelocityVoltage(0); /** * TalonFX motor controller. */ - private final TalonFX motor; + private final TalonFX motor; /** * Conversion factor for the motor. */ - private double conversionFactor; + private double conversionFactor; /** * Current TalonFX configuration. */ - private TalonFXConfiguration configuration = new TalonFXConfiguration(); + private TalonFXConfiguration configuration = new TalonFXConfiguration(); /** * Current TalonFX Configurator. */ - private TalonFXConfigurator cfg; + private TalonFXConfigurator cfg; /** @@ -60,12 +66,14 @@ public class TalonFXSwerve extends SwerveMotor * * @param motor Motor to use. * @param isDriveMotor Whether this motor is a drive motor. + * @param motorType {@link DCMotor} which the {@link TalonFX} is attached to. */ - public TalonFXSwerve(TalonFX motor, boolean isDriveMotor) + public TalonFXSwerve(TalonFX motor, boolean isDriveMotor, DCMotor motorType) { this.isDriveMotor = isDriveMotor; this.motor = motor; this.cfg = motor.getConfigurator(); + this.simMotor = motorType; factoryDefaults(); clearStickyFaults(); @@ -82,10 +90,11 @@ public TalonFXSwerve(TalonFX motor, boolean isDriveMotor) * @param id ID of the TalonFX on the CANBus. * @param canbus CANBus on which the TalonFX is on. * @param isDriveMotor Whether the motor is a drive or steering motor. + * @param motorType {@link DCMotor} which the {@link TalonFX} is attached to. */ - public TalonFXSwerve(int id, String canbus, boolean isDriveMotor) + public TalonFXSwerve(int id, String canbus, boolean isDriveMotor, DCMotor motorType) { - this(new TalonFX(id, canbus), isDriveMotor); + this(new TalonFX(id, canbus), isDriveMotor, motorType); } /** @@ -93,10 +102,11 @@ public TalonFXSwerve(int id, String canbus, boolean isDriveMotor) * * @param id ID of the TalonFX on the canbus. * @param isDriveMotor Whether the motor is a drive or steering motor. + * @param motorType {@link DCMotor} which the {@link TalonFX} is attached to. */ - public TalonFXSwerve(int id, boolean isDriveMotor) + public TalonFXSwerve(int id, boolean isDriveMotor, DCMotor motorType) { - this(new TalonFX(id), isDriveMotor); + this(new TalonFX(id), isDriveMotor, motorType); } /** @@ -181,63 +191,7 @@ public void configureIntegratedEncoder(double positionConversionFactor) cfg.apply(configuration); // Taken from democat's library. // https://github.com/democat3457/swerve-lib/blob/7c03126b8c22f23a501b2c2742f9d173a5bcbc40/src/main/java/com/swervedrivespecialties/swervelib/ctre/Falcon500DriveControllerFactoryBuilder.java#L16 - configureCANStatusFrames(250); - } - - /** - * Set the CAN status frames. - * - * @param CANStatus1 Applied Motor Output, Fault Information, Limit Switch Information - */ - public void configureCANStatusFrames(int CANStatus1) - { - // motor.setStatusFramePeriod(StatusFrameEnhanced.Status_1_General, CANStatus1); - } - - /** - * Set the CAN status frames. - * - * @param CANStatus1 Applied Motor Output, Fault Information, Limit Switch Information - * @param CANStatus2 Selected Sensor Position (PID 0), Selected Sensor Velocity (PID 0), Brushed Supply Current - * Measurement, Sticky Fault Information - * @param CANStatus3 Quadrature Information - * @param CANStatus4 Analog Input, Supply Battery Voltage, Controller Temperature - * @param CANStatus8 Pulse Width Information - * @param CANStatus10 Motion Profiling/Motion Magic Information - * @param CANStatus12 Selected Sensor Position (Aux PID 1), Selected Sensor Velocity (Aux PID 1) - * @param CANStatus13 PID0 (Primary PID) Information - * @param CANStatus14 PID1 (Auxiliary PID) Information - * @param CANStatus21 Integrated Sensor Position (Talon FX), Integrated Sensor Velocity (Talon FX) - * @param CANStatusCurrent Brushless Supply Current Measurement, Brushless Stator Current Measurement - */ - public void configureCANStatusFrames( - int CANStatus1, - int CANStatus2, - int CANStatus3, - int CANStatus4, - int CANStatus8, - int CANStatus10, - int CANStatus12, - int CANStatus13, - int CANStatus14, - int CANStatus21, - int CANStatusCurrent) - { - // motor.setStatusFramePeriod(StatusFrameEnhanced.Status_1_General, CANStatus1); - // motor.setStatusFramePeriod(StatusFrameEnhanced.Status_2_Feedback0, CANStatus2); - // motor.setStatusFramePeriod(StatusFrameEnhanced.Status_3_Quadrature, CANStatus3); - // motor.setStatusFramePeriod(StatusFrameEnhanced.Status_4_AinTempVbat, CANStatus4); - // motor.setStatusFramePeriod(StatusFrameEnhanced.Status_8_PulseWidth, CANStatus8); - // motor.setStatusFramePeriod(StatusFrameEnhanced.Status_10_Targets, CANStatus10); - // motor.setStatusFramePeriod(StatusFrameEnhanced.Status_12_Feedback1, CANStatus12); - // motor.setStatusFramePeriod(StatusFrameEnhanced.Status_13_Base_PIDF0, CANStatus13); - // motor.setStatusFramePeriod(StatusFrameEnhanced.Status_14_Turn_PIDF1, CANStatus14); - // motor.setStatusFramePeriod(StatusFrameEnhanced.Status_21_FeedbackIntegrated, CANStatus21); - // motor.setStatusFramePeriod(StatusFrameEnhanced.Status_Brushless_Current, - // CANStatusCurrent); - - // TODO: Configure Status Frame 2 thru 21 if necessary - // https://v5.docs.ctr-electronics.com/en/stable/ch18_CommonAPI.html#setting-status-frame-periods + // configureCANStatusFrames(250); } /** @@ -290,7 +244,10 @@ public void setMotorBrake(boolean isBrakeMode) public void setInverted(boolean inverted) { // Timer.delay(1); - motor.setInverted(inverted); + cfg.refresh(configuration.MotorOutput); + configuration.MotorOutput.withInverted( + inverted ? InvertedValue.CounterClockwise_Positive : InvertedValue.Clockwise_Positive); + cfg.apply(configuration.MotorOutput); } /** @@ -357,7 +314,7 @@ public void setReference(double setpoint, double feedforward, double position) @Override public double getVoltage() { - return motor.getMotorVoltage().waitForUpdate(STATUS_TIMEOUT_SECONDS).getValue(); + return motor.getMotorVoltage().waitForUpdate(STATUS_TIMEOUT_SECONDS).getValue().in(Volts); } /** @@ -390,7 +347,7 @@ public double getAppliedOutput() @Override public double getVelocity() { - return motor.getVelocity().getValue(); + return motor.getVelocity().getValue().magnitude(); } /** @@ -401,7 +358,7 @@ public double getVelocity() @Override public double getPosition() { - return motor.getPosition().getValue(); + return motor.getPosition().getValue().magnitude(); } /** @@ -414,8 +371,7 @@ public void setPosition(double position) { if (!absoluteEncoder && !SwerveDriveTelemetry.isSimulation) { - position = position < 0 ? (position % 360) + 360 : position; - cfg.setPosition(position / 360); + cfg.setPosition(Degrees.of(position).in(Rotations)); } } @@ -441,8 +397,8 @@ public void setCurrentLimit(int currentLimit) { cfg.refresh(configuration.CurrentLimits); cfg.apply( - configuration.CurrentLimits.withStatorCurrentLimit(currentLimit) - .withStatorCurrentLimitEnable(true)); + configuration.CurrentLimits.withSupplyCurrentLimit(currentLimit) + .withSupplyCurrentLimitEnable(true)); } /** @@ -468,6 +424,21 @@ public Object getMotor() return motor; } + /** + * Get the {@link DCMotor} of the motor class. + * + * @return {@link DCMotor} of this type. + */ + @Override + public DCMotor getSimMotor() + { + if (simMotor == null) + { + simMotor = DCMotor.getKrakenX60(1); + } + return simMotor; + } + /** * Queries whether the absolute encoder is directly attached to the motor controller. * diff --git a/swervelib/motors/TalonSRXSwerve.java b/swervelib/motors/TalonSRXSwerve.java index 98cf6e7c..5c8f690f 100644 --- a/swervelib/motors/TalonSRXSwerve.java +++ b/swervelib/motors/TalonSRXSwerve.java @@ -7,6 +7,8 @@ import com.ctre.phoenix.motorcontrol.StatusFrameEnhanced; import com.ctre.phoenix.motorcontrol.can.TalonSRXConfiguration; import com.ctre.phoenix.motorcontrol.can.WPI_TalonSRX; +import com.ctre.phoenix6.hardware.TalonFX; +import edu.wpi.first.math.system.plant.DCMotor; import swervelib.encoders.SwerveAbsoluteEncoder; import swervelib.math.SwerveMath; import swervelib.parser.PIDFConfig; @@ -21,42 +23,44 @@ public class TalonSRXSwerve extends SwerveMotor /** * Factory default already occurred. */ - private final boolean factoryDefaultOccurred = false; + private final boolean factoryDefaultOccurred = false; /** * Current TalonFX configuration. */ - private final TalonSRXConfiguration configuration = new TalonSRXConfiguration(); + private final TalonSRXConfiguration configuration = new TalonSRXConfiguration(); /** * Whether the absolute encoder is integrated. */ - private final boolean absoluteEncoder = false; + private final boolean absoluteEncoder = false; /** * TalonSRX motor controller. */ - private final WPI_TalonSRX motor; + private final WPI_TalonSRX motor; /** * The position conversion factor to convert raw sensor units to Meters Per 100ms, or Ticks to Degrees. */ - private double positionConversionFactor = 1; + private double positionConversionFactor = 1; /** * If the TalonFX configuration has changed. */ - private boolean configChanged = true; + private boolean configChanged = true; /** * Nominal voltage default to use with feedforward. */ - private double nominalVoltage = 12.0; + private double nominalVoltage = 12.0; /** * Constructor for TalonSRX swerve motor. * * @param motor Motor to use. * @param isDriveMotor Whether this motor is a drive motor. + * @param motorType {@link DCMotor} which the {@link WPI_TalonSRX} is attached to. */ - public TalonSRXSwerve(WPI_TalonSRX motor, boolean isDriveMotor) + public TalonSRXSwerve(WPI_TalonSRX motor, boolean isDriveMotor, DCMotor motorType) { this.isDriveMotor = isDriveMotor; this.motor = motor; + this.simMotor = motorType; motor.configSelectedFeedbackSensor(FeedbackDevice.QuadEncoder); factoryDefaults(); @@ -69,10 +73,11 @@ public TalonSRXSwerve(WPI_TalonSRX motor, boolean isDriveMotor) * * @param id ID of the TalonSRX on the canbus. * @param isDriveMotor Whether the motor is a drive or steering motor. + * @param motorType {@link DCMotor} which the {@link TalonFX} is attached to. */ - public TalonSRXSwerve(int id, boolean isDriveMotor) + public TalonSRXSwerve(int id, boolean isDriveMotor, DCMotor motorType) { - this(new WPI_TalonSRX(id), isDriveMotor); + this(new WPI_TalonSRX(id), isDriveMotor, motorType); } /** @@ -358,11 +363,6 @@ public double getPosition() } else { var pos = motor.getSelectedSensorPosition() * positionConversionFactor; - pos %= 360; - if (pos < 360) - { - pos += 360; - } return pos; } } @@ -421,6 +421,17 @@ public void setLoopRampRate(double rampRate) configChanged = true; } + /** + * Set the selected feedback device for the TalonSRX. + * + * @param feedbackDevice Feedback device to select. + */ + public void setSelectedFeedbackDevice(FeedbackDevice feedbackDevice) + { + configuration.primaryPID.selectedFeedbackSensor = feedbackDevice; + configChanged = true; + } + /** * Get the motor object from the module. * @@ -432,6 +443,21 @@ public Object getMotor() return motor; } + /** + * Get the {@link DCMotor} of the motor class. + * + * @return {@link DCMotor} of this type. + */ + @Override + public DCMotor getSimMotor() + { + if (simMotor == null) + { + simMotor = DCMotor.getCIM(1); + } + return simMotor; + } + /** * Queries whether the absolute encoder is directly attached to the motor controller. * diff --git a/swervelib/parser/Cache.java b/swervelib/parser/Cache.java index 830a08c0..830a2731 100644 --- a/swervelib/parser/Cache.java +++ b/swervelib/parser/Cache.java @@ -1,5 +1,6 @@ package swervelib.parser; +import edu.wpi.first.wpilibj.RobotBase; import edu.wpi.first.wpilibj.RobotController; import java.util.function.Supplier; @@ -95,7 +96,7 @@ public Cache updateValidityPeriod(long validityPeriod) */ public T getValue() { - if (isStale()) + if (isStale() || RobotBase.isSimulation()) { update(); } diff --git a/swervelib/parser/SwerveControllerConfiguration.java b/swervelib/parser/SwerveControllerConfiguration.java index 7f1d9671..9fd438eb 100644 --- a/swervelib/parser/SwerveControllerConfiguration.java +++ b/swervelib/parser/SwerveControllerConfiguration.java @@ -18,7 +18,7 @@ public class SwerveControllerConfiguration public final double angleJoyStickRadiusDeadband; // Deadband for the minimum hypot for the heading joystick. /** - * Maximum angular velocity in rad/s + * Maximum chassis angular velocity in rad/s */ public double maxAngularVelocity; diff --git a/swervelib/parser/SwerveDriveConfiguration.java b/swervelib/parser/SwerveDriveConfiguration.java index 347b187d..6c62581a 100644 --- a/swervelib/parser/SwerveDriveConfiguration.java +++ b/swervelib/parser/SwerveDriveConfiguration.java @@ -1,9 +1,14 @@ package swervelib.parser; -import edu.wpi.first.math.controller.SimpleMotorFeedforward; import edu.wpi.first.math.geometry.Translation2d; +import edu.wpi.first.math.system.plant.DCMotor; +import java.util.function.Supplier; +import org.ironmaple.simulation.drivesims.GyroSimulation; import swervelib.SwerveModule; +import swervelib.imu.NavXSwerve; +import swervelib.imu.Pigeon2Swerve; import swervelib.imu.SwerveIMU; +import swervelib.math.SwerveMath; /** * Swerve drive configurations used during SwerveDrive construction. @@ -38,20 +43,18 @@ public class SwerveDriveConfiguration * @param moduleConfigs Module configuration. * @param swerveIMU Swerve IMU. * @param invertedIMU Invert the IMU. - * @param driveFeedforward The drive motor feedforward to use for the {@link SwerveModule}. * @param physicalCharacteristics {@link SwerveModulePhysicalCharacteristics} to store in association with self. */ public SwerveDriveConfiguration( SwerveModuleConfiguration[] moduleConfigs, SwerveIMU swerveIMU, boolean invertedIMU, - SimpleMotorFeedforward driveFeedforward, SwerveModulePhysicalCharacteristics physicalCharacteristics) { this.moduleCount = moduleConfigs.length; this.imu = swerveIMU; swerveIMU.setInverted(invertedIMU); - this.modules = createModules(moduleConfigs, driveFeedforward); + this.modules = createModules(moduleConfigs); this.moduleLocationsMeters = new Translation2d[moduleConfigs.length]; for (SwerveModule module : modules) { @@ -63,17 +66,15 @@ public SwerveDriveConfiguration( /** * Create modules based off of the SwerveModuleConfiguration. * - * @param swerves Swerve constants. - * @param driveFeedforward Drive feedforward created using - * {@link swervelib.math.SwerveMath#createDriveFeedforward(double, double, double)}. + * @param swerves Swerve constants. * @return Swerve Modules. */ - public SwerveModule[] createModules(SwerveModuleConfiguration[] swerves, SimpleMotorFeedforward driveFeedforward) + public SwerveModule[] createModules(SwerveModuleConfiguration[] swerves) { SwerveModule[] modArr = new SwerveModule[swerves.length]; for (int i = 0; i < swerves.length; i++) { - modArr[i] = new SwerveModule(i, swerves[i], driveFeedforward); + modArr[i] = new SwerveModule(i, swerves[i]); } return modArr; } @@ -96,4 +97,68 @@ public double getDriveBaseRadiusMeters() //Return Largest Radius return centerOfModules.getDistance(moduleLocationsMeters[0]); } + + /** + * Get the trackwidth of the swerve modules. + * + * @return Effective trackwdtih in Meters + */ + public double getTrackwidth() + { + SwerveModuleConfiguration fr = SwerveMath.getSwerveModule(modules, true, false); + SwerveModuleConfiguration fl = SwerveMath.getSwerveModule(modules, true, true); + return fr.moduleLocation.getDistance(fl.moduleLocation); + } + + /** + * Get the tracklength of the swerve modules. + * + * @return Effective tracklength in Meters + */ + public double getTracklength() + { + SwerveModuleConfiguration br = SwerveMath.getSwerveModule(modules, false, false); + SwerveModuleConfiguration bl = SwerveMath.getSwerveModule(modules, false, true); + return br.moduleLocation.getDistance(bl.moduleLocation); + } + + /** + * Get the {@link DCMotor} corresponding to the first module's configuration. + * + * @return {@link DCMotor} of the drive motor. + */ + public DCMotor getDriveMotorSim() + { + SwerveModuleConfiguration fl = SwerveMath.getSwerveModule(modules, true, true); + return fl.driveMotor.getSimMotor(); + } + + /** + * Get the {@link DCMotor} corresponding to the first module configuration. + * + * @return {@link DCMotor} of the angle motor. + */ + public DCMotor getAngleMotorSim() + { + SwerveModuleConfiguration fl = SwerveMath.getSwerveModule(modules, true, true); + return fl.angleMotor.getSimMotor(); + } + + /** + * Get the gyro simulation for the robot. + * + * @return {@link GyroSimulation} gyro simulation. + */ + public Supplier getGyroSim() + { + if (imu instanceof Pigeon2Swerve) + { + return GyroSimulation.getPigeon2(); + } else if (imu instanceof NavXSwerve) + { + return GyroSimulation.getNav2X(); + } + return GyroSimulation.getGeneric(); + } + } diff --git a/swervelib/parser/SwerveModuleConfiguration.java b/swervelib/parser/SwerveModuleConfiguration.java index 24d06257..8996f448 100644 --- a/swervelib/parser/SwerveModuleConfiguration.java +++ b/swervelib/parser/SwerveModuleConfiguration.java @@ -3,7 +3,7 @@ import edu.wpi.first.math.geometry.Translation2d; import swervelib.encoders.SwerveAbsoluteEncoder; import swervelib.motors.SwerveMotor; -import swervelib.parser.json.MotorConfigDouble; +import swervelib.parser.json.modules.ConversionFactorsJson; /** * Swerve Module configuration class which is used to configure {@link swervelib.SwerveModule}. @@ -17,7 +17,7 @@ public class SwerveModuleConfiguration * {@link swervelib.math.SwerveMath#calculateDegreesPerSteeringRotation(double, double)} respectively to calculate the * conversion factors. */ - public final MotorConfigDouble conversionFactors; + public final ConversionFactorsJson conversionFactors; /** * Angle offset in degrees for the Swerve Module. */ @@ -89,7 +89,7 @@ public class SwerveModuleConfiguration public SwerveModuleConfiguration( SwerveMotor driveMotor, SwerveMotor angleMotor, - MotorConfigDouble conversionFactors, + ConversionFactorsJson conversionFactors, SwerveAbsoluteEncoder absoluteEncoder, double angleOffset, double xMeters, @@ -139,7 +139,7 @@ public SwerveModuleConfiguration( public SwerveModuleConfiguration( SwerveMotor driveMotor, SwerveMotor angleMotor, - MotorConfigDouble conversionFactors, + ConversionFactorsJson conversionFactors, SwerveAbsoluteEncoder absoluteEncoder, double angleOffset, double xMeters, diff --git a/swervelib/parser/SwerveModulePhysicalCharacteristics.java b/swervelib/parser/SwerveModulePhysicalCharacteristics.java index 99360ce1..a626bbb8 100644 --- a/swervelib/parser/SwerveModulePhysicalCharacteristics.java +++ b/swervelib/parser/SwerveModulePhysicalCharacteristics.java @@ -1,6 +1,6 @@ package swervelib.parser; -import swervelib.parser.json.MotorConfigDouble; +import swervelib.parser.json.modules.ConversionFactorsJson; /** * Configuration class which stores physical characteristics shared between every swerve module. @@ -16,20 +16,32 @@ public class SwerveModulePhysicalCharacteristics * The time it takes for the motor to go from 0 to full throttle in seconds. */ public final double driveMotorRampRate, angleMotorRampRate; + /** + * The minimum voltage to spin the module or wheel. + */ + public final double driveFrictionVoltage, angleFrictionVoltage; /** * Wheel grip tape coefficient of friction on carpet, as described by the vendor. */ - public final double wheelGripCoefficientOfFriction; + public final double wheelGripCoefficientOfFriction; + /** + * Steer rotational inertia in (KilogramSquareMeters) kg/m_sq. + */ + public final double steerRotationalInertia; + /** + * Robot mass in Kilograms. + */ + public final double robotMassKg; /** * The voltage to use for the smart motor voltage compensation. */ - public double optimalVoltage; + public double optimalVoltage; /** * The conversion factors for the drive and angle motors, created by * {@link swervelib.math.SwerveMath#calculateMetersPerRotation(double, double, double)} and * {@link swervelib.math.SwerveMath#calculateDegreesPerSteeringRotation(double, double)}. */ - public MotorConfigDouble conversionFactor; + public ConversionFactorsJson conversionFactor; /** * Construct the swerve module physical characteristics. @@ -47,15 +59,23 @@ public class SwerveModulePhysicalCharacteristics * over drawing power from battery) * @param angleMotorRampRate The time in seconds to go from 0 to full throttle on the motor. (Prevents * overdrawing power and power loss). + * @param angleFrictionVoltage Angle motor minimum voltage. + * @param driveFrictionVoltage Drive motor minimum voltage. + * @param steerRotationalInertia Steering rotational inertia in KilogramSquareMeters. + * @param robotMassKg Robot mass in kG. */ public SwerveModulePhysicalCharacteristics( - MotorConfigDouble conversionFactors, + ConversionFactorsJson conversionFactors, double wheelGripCoefficientOfFriction, double optimalVoltage, int driveMotorCurrentLimit, int angleMotorCurrentLimit, double driveMotorRampRate, - double angleMotorRampRate) + double angleMotorRampRate, + double driveFrictionVoltage, + double angleFrictionVoltage, + double steerRotationalInertia, + double robotMassKg) { this.wheelGripCoefficientOfFriction = wheelGripCoefficientOfFriction; this.optimalVoltage = optimalVoltage; @@ -64,7 +84,7 @@ public SwerveModulePhysicalCharacteristics( // Set the conversion factors to null if they are both 0. if (conversionFactors != null) { - if (conversionFactors.angle == 0 && conversionFactors.drive == 0) + if (conversionFactors.isAngleEmpty() && conversionFactors.isDriveEmpty()) { this.conversionFactor = null; } @@ -74,6 +94,10 @@ public SwerveModulePhysicalCharacteristics( this.angleMotorCurrentLimit = angleMotorCurrentLimit; this.driveMotorRampRate = driveMotorRampRate; this.angleMotorRampRate = angleMotorRampRate; + this.driveFrictionVoltage = driveFrictionVoltage; + this.angleFrictionVoltage = angleFrictionVoltage; + this.steerRotationalInertia = steerRotationalInertia; + this.robotMassKg = robotMassKg; } /** @@ -90,7 +114,7 @@ public SwerveModulePhysicalCharacteristics( * power and power loss). */ public SwerveModulePhysicalCharacteristics( - MotorConfigDouble conversionFactors, + ConversionFactorsJson conversionFactors, double driveMotorRampRate, double angleMotorRampRate) { @@ -101,6 +125,10 @@ public SwerveModulePhysicalCharacteristics( 40, 20, driveMotorRampRate, - angleMotorRampRate); + angleMotorRampRate, + 0.2, + 0.3, + 0.03, + 50); } } diff --git a/swervelib/parser/SwerveParser.java b/swervelib/parser/SwerveParser.java index 573fdd87..afa53544 100644 --- a/swervelib/parser/SwerveParser.java +++ b/swervelib/parser/SwerveParser.java @@ -2,7 +2,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import edu.wpi.first.math.controller.SimpleMotorFeedforward; +import edu.wpi.first.math.geometry.Pose2d; import java.io.File; import java.io.IOException; import java.util.HashMap; @@ -135,10 +135,7 @@ private void checkDirectory(File directory) */ public SwerveDrive createSwerveDrive(double maxSpeed) { - return createSwerveDrive(SwerveMath.createDriveFeedforward(physicalPropertiesJson.optimalVoltage, - maxSpeed, - physicalPropertiesJson.wheelGripCoefficientOfFriction), - maxSpeed); + return createSwerveDrive(maxSpeed, Pose2d.kZero); } /** @@ -157,48 +154,20 @@ public SwerveDrive createSwerveDrive(double maxSpeed) */ public SwerveDrive createSwerveDrive(double maxSpeed, double angleMotorConversionFactor, double driveMotorConversion) { - physicalPropertiesJson.conversionFactor.angle = angleMotorConversionFactor; - physicalPropertiesJson.conversionFactor.drive = driveMotorConversion; - return createSwerveDrive(SwerveMath.createDriveFeedforward(physicalPropertiesJson.optimalVoltage, - maxSpeed, - physicalPropertiesJson.wheelGripCoefficientOfFriction), - maxSpeed); + physicalPropertiesJson.conversionFactors.angle.factor = angleMotorConversionFactor; + physicalPropertiesJson.conversionFactors.drive.factor = driveMotorConversion; + return createSwerveDrive(maxSpeed, Pose2d.kZero); } /** * Create {@link SwerveDrive} from JSON configuration directory. * - * @param driveFeedforward Drive feedforward to use for swerve modules, should be created using - * {@link swervelib.math.SwerveMath#createDriveFeedforward(double, double, - * double)}. - * @param maxSpeed Maximum speed of the robot in meters per second for normal+angular acceleration - * in {@link swervelib.SwerveController} of the robot. - * @param angleMotorConversionFactor Angle (AKA azimuth) motor conversion factor to convert motor controller PID loop - * units to degrees, usually created using - * {@link SwerveMath#calculateDegreesPerSteeringRotation(double, double)}. - * @param driveMotorConversion Drive motor conversion factor to convert motor controller PID loop units to - * meters per rotation, usually created using - * {@link SwerveMath#calculateMetersPerRotation(double, double, double)}. - * @return {@link SwerveDrive} instance. - */ - public SwerveDrive createSwerveDrive(SimpleMotorFeedforward driveFeedforward, double maxSpeed, - double angleMotorConversionFactor, double driveMotorConversion) - { - physicalPropertiesJson.conversionFactor.angle = angleMotorConversionFactor; - physicalPropertiesJson.conversionFactor.drive = driveMotorConversion; - return createSwerveDrive(driveFeedforward, maxSpeed); - } - - /** - * Create {@link SwerveDrive} from JSON configuration directory. - * - * @param driveFeedforward Drive feedforward to use for swerve modules, should be created using - * {@link swervelib.math.SwerveMath#createDriveFeedforward(double, double, double)}. - * @param maxSpeed Maximum speed of the robot in meters per second for normal+angular acceleration in - * {@link swervelib.SwerveController} of the robot + * @param maxSpeed Maximum speed of the robot in meters per second for normal+angular acceleration in + * {@link swervelib.SwerveController} of the robot + * @param initialPose {@link Pose2d} initial pose. * @return {@link SwerveDrive} instance. */ - public SwerveDrive createSwerveDrive(SimpleMotorFeedforward driveFeedforward, double maxSpeed) + public SwerveDrive createSwerveDrive(double maxSpeed, Pose2d initialPose) { SwerveModuleConfiguration[] moduleConfigurations = new SwerveModuleConfiguration[moduleJsons.length]; @@ -217,11 +186,12 @@ public SwerveDrive createSwerveDrive(SimpleMotorFeedforward driveFeedforward, do moduleConfigurations, swerveDriveJson.imu.createIMU(), swerveDriveJson.invertedIMU, - driveFeedforward, physicalPropertiesJson.createPhysicalProperties()); return new SwerveDrive( swerveDriveConfiguration, - controllerPropertiesJson.createControllerConfiguration(swerveDriveConfiguration, maxSpeed), maxSpeed); + controllerPropertiesJson.createControllerConfiguration(swerveDriveConfiguration, maxSpeed), + maxSpeed, + initialPose); } } diff --git a/swervelib/parser/json/DeviceJson.java b/swervelib/parser/json/DeviceJson.java index 7ce6fc3d..71a11a44 100644 --- a/swervelib/parser/json/DeviceJson.java +++ b/swervelib/parser/json/DeviceJson.java @@ -4,11 +4,10 @@ import static swervelib.telemetry.SwerveDriveTelemetry.i2cLockupWarning; import static swervelib.telemetry.SwerveDriveTelemetry.serialCommsIssueWarning; -import com.revrobotics.SparkRelativeEncoder.Type; +import com.ctre.phoenix.motorcontrol.FeedbackDevice; +import com.studica.frc.AHRS.NavXComType; +import edu.wpi.first.math.system.plant.DCMotor; import edu.wpi.first.wpilibj.DriverStation; -import edu.wpi.first.wpilibj.I2C; -import edu.wpi.first.wpilibj.SPI; -import edu.wpi.first.wpilibj.SerialPort.Port; import swervelib.encoders.AnalogAbsoluteEncoderSwerve; import swervelib.encoders.CANCoderSwerve; import swervelib.encoders.CanAndMagSwerve; @@ -16,6 +15,7 @@ import swervelib.encoders.SparkMaxAnalogEncoderSwerve; import swervelib.encoders.SparkMaxEncoderSwerve; import swervelib.encoders.SwerveAbsoluteEncoder; +import swervelib.encoders.TalonSRXEncoderSwerve; import swervelib.imu.ADIS16448Swerve; import swervelib.imu.ADIS16470Swerve; import swervelib.imu.ADXRS450Swerve; @@ -27,6 +27,7 @@ import swervelib.imu.SwerveIMU; import swervelib.motors.SparkFlexSwerve; import swervelib.motors.SparkMaxBrushedMotorSwerve; +import swervelib.motors.SparkMaxBrushedMotorSwerve.Type; import swervelib.motors.SparkMaxSwerve; import swervelib.motors.SwerveMotor; import swervelib.motors.TalonFXSwerve; @@ -92,6 +93,10 @@ public SwerveAbsoluteEncoder createEncoder(SwerveMotor motor) return new AnalogAbsoluteEncoderSwerve(id); case "cancoder": return new CANCoderSwerve(id, canbus != null ? canbus : ""); + case "talonsrx_pwm": + return new TalonSRXEncoderSwerve(motor, FeedbackDevice.PulseWidthEncodedPosition); + case "talonsrx_analog": + return new TalonSRXEncoderSwerve(motor, FeedbackDevice.Analog); default: throw new RuntimeException(type + " is not a recognized absolute encoder type."); } @@ -122,7 +127,7 @@ public SwerveIMU createIMU() return new CanandgyroSwerve(id); case "navx": case "navx_spi": - return new NavXSwerve(SPI.Port.kMXP); + return new NavXSwerve(NavXComType.kMXP_SPI); case "navx_i2c": DriverStation.reportWarning( "WARNING: There exists an I2C lockup issue on the roboRIO that could occur, more information here: " + @@ -130,15 +135,15 @@ public SwerveIMU createIMU() ".html#onboard-i2c-causing-system-lockups", false); i2cLockupWarning.set(true); - return new NavXSwerve(I2C.Port.kMXP); + return new NavXSwerve(NavXComType.kI2C); case "navx_usb": DriverStation.reportWarning("WARNING: There is issues when using USB camera's and the NavX like this!\n" + "https://pdocs.kauailabs.com/navx-mxp/guidance/selecting-an-interface/", false); serialCommsIssueWarning.set(true); - return new NavXSwerve(Port.kUSB); + return new NavXSwerve(NavXComType.kUSB1); case "navx_mxp_serial": serialCommsIssueWarning.set(true); - return new NavXSwerve(Port.kMXP); + return new NavXSwerve(NavXComType.kMXP_UART); case "pigeon": return new PigeonSwerve(id); case "pigeon2": @@ -162,39 +167,56 @@ public SwerveMotor createMotor(boolean isDriveMotor) } switch (type) { + case "sparkmax_neo": + case "neo": + case "sparkmax": + return new SparkMaxSwerve(id, isDriveMotor, DCMotor.getNEO(1)); + case "sparkmax_neo550": + case "neo550": + return new SparkMaxSwerve(id, isDriveMotor, DCMotor.getNeo550(1)); + case "sparkflex_vortex": + case "vortex": + case "sparkflex": + return new SparkFlexSwerve(id, isDriveMotor, DCMotor.getNeoVortex(1)); + case "sparkflex_neo": + return new SparkFlexSwerve(id, isDriveMotor, DCMotor.getNEO(1)); + case "sparkflex_neo550": + return new SparkFlexSwerve(id, isDriveMotor, DCMotor.getNeo550(1)); + case "falcon500": + case "falcon": + return new TalonFXSwerve(id, canbus != null ? canbus : "", isDriveMotor, DCMotor.getFalcon500(1)); + case "falcon500foc": + return new TalonFXSwerve(id, canbus != null ? canbus : "", isDriveMotor, DCMotor.getFalcon500Foc(1)); + case "krakenx60": + case "talonfx": + return new TalonFXSwerve(id, canbus != null ? canbus : "", isDriveMotor, DCMotor.getKrakenX60(1)); + case "krakenx60foc": + return new TalonFXSwerve(id, canbus != null ? canbus : "", isDriveMotor, DCMotor.getKrakenX60Foc(1)); + case "talonsrx": + return new TalonSRXSwerve(id, isDriveMotor, DCMotor.getCIM(1)); case "sparkmax_brushed": switch (canbus) { case "greyhill_63r256": - return new SparkMaxBrushedMotorSwerve(id, isDriveMotor, Type.kQuadrature, 1024, false); + return new SparkMaxBrushedMotorSwerve(id, isDriveMotor, Type.kQuadrature, 1024, false, DCMotor.getCIM(1)); case "srx_mag_encoder": - return new SparkMaxBrushedMotorSwerve(id, isDriveMotor, Type.kQuadrature, 4096, false); + return new SparkMaxBrushedMotorSwerve(id, isDriveMotor, Type.kQuadrature, 4096, false, DCMotor.getCIM(1)); case "throughbore": - return new SparkMaxBrushedMotorSwerve(id, isDriveMotor, Type.kQuadrature, 8192, false); + return new SparkMaxBrushedMotorSwerve(id, isDriveMotor, Type.kQuadrature, 8192, false, DCMotor.getCIM(1)); case "throughbore_dataport": - return new SparkMaxBrushedMotorSwerve(id, isDriveMotor, Type.kNoSensor, 8192, true); + return new SparkMaxBrushedMotorSwerve(id, isDriveMotor, Type.kNoSensor, 8192, true, DCMotor.getCIM(1)); case "greyhill_63r256_dataport": - return new SparkMaxBrushedMotorSwerve(id, isDriveMotor, Type.kQuadrature, 1024, true); + return new SparkMaxBrushedMotorSwerve(id, isDriveMotor, Type.kQuadrature, 1024, true, DCMotor.getCIM(1)); case "srx_mag_encoder_dataport": - return new SparkMaxBrushedMotorSwerve(id, isDriveMotor, Type.kQuadrature, 4096, true); + return new SparkMaxBrushedMotorSwerve(id, isDriveMotor, Type.kQuadrature, 4096, true, DCMotor.getCIM(1)); default: if (isDriveMotor) { throw new RuntimeException("Spark MAX " + id + " MUST have a encoder attached to the motor controller."); } // We are creating a motor for an angle motor which will use the absolute encoder attached to the data port. - return new SparkMaxBrushedMotorSwerve(id, isDriveMotor, Type.kNoSensor, 0, false); + return new SparkMaxBrushedMotorSwerve(id, isDriveMotor, Type.kNoSensor, 0, false, DCMotor.getCIM(1)); } - case "neo": - case "sparkmax": - return new SparkMaxSwerve(id, isDriveMotor); - case "sparkflex": - return new SparkFlexSwerve(id, isDriveMotor); - case "falcon": - case "talonfx": - return new TalonFXSwerve(id, canbus != null ? canbus : "", isDriveMotor); - case "talonsrx": - return new TalonSRXSwerve(id, isDriveMotor); default: throw new RuntimeException(type + " is not a recognized motor type."); } diff --git a/swervelib/parser/json/ModuleJson.java b/swervelib/parser/json/ModuleJson.java index 2fa83c89..f26b2e84 100644 --- a/swervelib/parser/json/ModuleJson.java +++ b/swervelib/parser/json/ModuleJson.java @@ -1,8 +1,8 @@ package swervelib.parser.json; -import com.revrobotics.CANSparkMax; -import com.revrobotics.MotorFeedbackSensor; +import com.revrobotics.spark.SparkMax; import edu.wpi.first.math.util.Units; +import swervelib.encoders.SparkMaxEncoderSwerve; import swervelib.encoders.SwerveAbsoluteEncoder; import swervelib.motors.SwerveMotor; import swervelib.parser.PIDFConfig; @@ -26,14 +26,6 @@ public class ModuleJson * Angle motor device configuration. */ public DeviceJson angle; - /** - * Conversion factor for the module, if different from the one in swervedrive.json - *

- * Conversion factor applied to the motor controllers PID loops. Can be calculated with - * {@link swervelib.math.SwerveMath#calculateDegreesPerSteeringRotation(double, double)} for angle motors or - * {@link swervelib.math.SwerveMath#calculateMetersPerRotation(double, double, double)} for drive motors. - */ - public MotorConfigDouble conversionFactor = new MotorConfigDouble(0, 0); /** * Conversion Factors composition. Auto-calculates the conversion factors. */ @@ -81,37 +73,8 @@ public SwerveModuleConfiguration createModuleConfiguration( SwerveMotor angleMotor = angle.createMotor(false); SwerveAbsoluteEncoder absEncoder = encoder.createEncoder(angleMotor); - // Setup deprecation notice. -// if (this.conversionFactor.drive != 0 && this.conversionFactor.angle != 0) -// { -// new Alert("Configuration", -// "\n'conversionFactor': {'drive': " + conversionFactor.drive + ", 'angle': " + conversionFactor.angle + -// "} \nis deprecated, please use\n" + -// "'conversionFactors': {'drive': {'factor': " + conversionFactor.drive + "}, 'angle': {'factor': " + -// conversionFactor.angle + "} }", -// AlertType.WARNING).set(true); -// } - - // Override with composite conversion factor. - if (!conversionFactors.isAngleEmpty()) - { - conversionFactor.angle = conversionFactors.angle.calculate(); - } - if (!conversionFactors.isDriveEmpty()) - { - conversionFactor.drive = conversionFactors.drive.calculate(); - } - // Set the conversion factors to null if they are both 0. - if (this.conversionFactor != null) - { - if (this.conversionFactor.angle == 0 && this.conversionFactor.drive == 0) - { - this.conversionFactor = null; - } - } - - if (this.conversionFactor == null && physicalCharacteristics.conversionFactor == null) + if (!conversionFactors.works() && physicalCharacteristics.conversionFactor == null) { throw new RuntimeException("No Conversion Factor configured! Please create SwerveDrive using \n" + "SwerveParser.createSwerveDrive(driveFeedforward, maxSpeed, angleMotorConversionFactor, driveMotorConversion)\n" + @@ -120,27 +83,27 @@ public SwerveModuleConfiguration createModuleConfiguration( "OR\n" + "Set the conversion factor in physicalproperties.json OR the module JSON file." + "REMEMBER: You can calculate the conversion factors using SwerveMath.calculateMetersPerRotation AND SwerveMath.calculateDegreesPerSteeringRotation\n"); - } else if (physicalCharacteristics.conversionFactor != null && this.conversionFactor == null) + } else if (physicalCharacteristics.conversionFactor.works() && !conversionFactors.works()) { - this.conversionFactor = physicalCharacteristics.conversionFactor; - } else if (physicalCharacteristics.conversionFactor != - null) // If both are defined, override 0 with the physical characterstics input. + conversionFactors = physicalCharacteristics.conversionFactor; + } else if (physicalCharacteristics.conversionFactor.works()) + // If both are defined, override 0 with the physical characterstics input. { - this.conversionFactor.angle = this.conversionFactor.angle == 0 ? physicalCharacteristics.conversionFactor.angle - : this.conversionFactor.angle; - this.conversionFactor.drive = this.conversionFactor.drive == 0 ? physicalCharacteristics.conversionFactor.drive - : this.conversionFactor.drive; + conversionFactors.angle = conversionFactors.isAngleEmpty() ? physicalCharacteristics.conversionFactor.angle + : conversionFactors.angle; + conversionFactors.drive = conversionFactors.isDriveEmpty() ? physicalCharacteristics.conversionFactor.drive + : conversionFactors.drive; } - if (this.conversionFactor.drive == 0 || this.conversionFactor.angle == 0) + if (conversionFactors.isDriveEmpty() || conversionFactors.isAngleEmpty()) { throw new RuntimeException( "Conversion factors cannot be 0, please configure conversion factors in physicalproperties.json or the module JSON files."); } // Backwards compatibility, auto-optimization. - if (conversionFactor.angle == 360 && absEncoder != null && - absEncoder.getAbsoluteEncoder() instanceof MotorFeedbackSensor && angleMotor.getMotor() instanceof CANSparkMax) + if (conversionFactors.angle.factor == 360 && absEncoder != null && + absEncoder instanceof SparkMaxEncoderSwerve && angleMotor.getMotor() instanceof SparkMax) { angleMotor.setAbsoluteEncoder(absEncoder); } @@ -148,7 +111,7 @@ public SwerveModuleConfiguration createModuleConfiguration( return new SwerveModuleConfiguration( drive.createMotor(true), angleMotor, - conversionFactor, + conversionFactors, absEncoder, absoluteEncoderOffset, Units.inchesToMeters(Math.round(location.x) == 0 ? location.front : location.x), diff --git a/swervelib/parser/json/PhysicalPropertiesJson.java b/swervelib/parser/json/PhysicalPropertiesJson.java index 60be9d25..eaab95d6 100644 --- a/swervelib/parser/json/PhysicalPropertiesJson.java +++ b/swervelib/parser/json/PhysicalPropertiesJson.java @@ -1,5 +1,8 @@ package swervelib.parser.json; +import edu.wpi.first.units.Units; +import edu.wpi.first.wpilibj.Alert; +import edu.wpi.first.wpilibj.Alert.AlertType; import swervelib.parser.SwerveModulePhysicalCharacteristics; import swervelib.parser.json.modules.ConversionFactorsJson; @@ -9,13 +12,23 @@ public class PhysicalPropertiesJson { - /** - * Conversion factor applied to the motor controllers PID loops. Can be calculated with - * {@link swervelib.math.SwerveMath#calculateDegreesPerSteeringRotation(double, double)} for angle motors or - * {@link swervelib.math.SwerveMath#calculateMetersPerRotation(double, double, double)} for drive motors. + * DEPRECATED! Use {@link PhysicalPropertiesJson#conversionFactors} instead. + */ + @Deprecated(since = "2025", forRemoval = true) + public MotorConfigDouble conversionFactor = new MotorConfigDouble(); + /** + * Minimum voltage to spin the module or wheel. */ - public MotorConfigDouble conversionFactor = new MotorConfigDouble(0, 0); + public MotorConfigDouble friction = new MotorConfigDouble(0.3, 0.2); + /** + * Steer rotational inertia in KilogramMetersSquare. + */ + public double steerRotationalInertia = 0.03; + /** + * Robot mass in lb (pounds) + */ + public double robotMass = 110.2311; /** * Conversion Factors composition. Auto-calculates the conversion factors. */ @@ -45,35 +58,29 @@ public class PhysicalPropertiesJson public SwerveModulePhysicalCharacteristics createPhysicalProperties() { // Setup deprecation notice. -// if (conversionFactor.drive != 0 && conversionFactor.angle != 0 && conversionFactors.isDriveEmpty() && -// conversionFactors.isAngleEmpty()) -// { -// new Alert("Configuration", -// "\n'conversionFactor': {'drive': " + conversionFactor.drive + ", 'angle': " + conversionFactor.angle + -// "} \nis deprecated, please use\n" + -// "'conversionFactors': {'drive': {'factor': " + conversionFactor.drive + "}, 'angle': {'factor': " + -// conversionFactor.angle + "} }", -// AlertType.ERROR_TRACE).set(true); -// } - - if (!conversionFactors.isAngleEmpty()) - { - conversionFactor.angle = conversionFactors.angle.calculate(); - } - - if (!conversionFactors.isDriveEmpty()) + if (conversionFactor.drive != 0 && conversionFactor.angle != 0 && conversionFactors.isDriveEmpty() && + conversionFactors.isAngleEmpty()) { - conversionFactor.drive = conversionFactors.drive.calculate(); + new Alert("Configuration", + "\n'conversionFactor': {'drive': " + conversionFactor.drive + ", 'angle': " + conversionFactor.angle + + "} \nis deprecated, please use\n" + + "'conversionFactors': {'drive': {'factor': " + conversionFactor.drive + "}, 'angle': {'factor': " + + conversionFactor.angle + "} }", + AlertType.kError).set(true); } return new SwerveModulePhysicalCharacteristics( - conversionFactor, + conversionFactors, wheelGripCoefficientOfFriction, optimalVoltage, currentLimit.drive, currentLimit.angle, rampRate.drive, - rampRate.angle); + rampRate.angle, + friction.drive, + friction.angle, + steerRotationalInertia, + Units.Pounds.of(robotMass).in(Units.Kilogram)); } } diff --git a/swervelib/parser/json/modules/AngleConversionFactorsJson.java b/swervelib/parser/json/modules/AngleConversionFactorsJson.java index a7c8613a..5a3e10f9 100644 --- a/swervelib/parser/json/modules/AngleConversionFactorsJson.java +++ b/swervelib/parser/json/modules/AngleConversionFactorsJson.java @@ -1,8 +1,6 @@ package swervelib.parser.json.modules; import swervelib.math.SwerveMath; -import swervelib.telemetry.Alert; -import swervelib.telemetry.Alert.AlertType; /** * Angle motor conversion factors composite JSON parse class. @@ -13,11 +11,11 @@ public class AngleConversionFactorsJson /** * Gear ratio for the angle/steering/azimuth motor on the Swerve Module. Motor rotations to 1 wheel rotation. */ - public double gearRatio = 0; + public double gearRatio; /** * Calculated or given conversion factor. */ - public double factor = 0; + public double factor = 0; /** * Calculate the drive conversion factor. @@ -26,12 +24,6 @@ public class AngleConversionFactorsJson */ public double calculate() { - if (factor != 0 && gearRatio != 0) - { - new Alert("Configuration", - "The given angle conversion factor takes precedence over the composite conversion factor, please remove 'factor' if you want to use the composite factor instead.", - AlertType.WARNING).set(true); - } if (factor == 0) { factor = SwerveMath.calculateDegreesPerSteeringRotation(gearRatio); diff --git a/swervelib/parser/json/modules/ConversionFactorsJson.java b/swervelib/parser/json/modules/ConversionFactorsJson.java index 85ff25f2..546ad65a 100644 --- a/swervelib/parser/json/modules/ConversionFactorsJson.java +++ b/swervelib/parser/json/modules/ConversionFactorsJson.java @@ -22,7 +22,8 @@ public class ConversionFactorsJson */ public boolean isDriveEmpty() { - return drive.factor == 0 && drive.diameter == 0 && drive.gearRatio == 0; + drive.calculate(); + return drive.factor == 0; } /** @@ -32,6 +33,18 @@ public boolean isDriveEmpty() */ public boolean isAngleEmpty() { - return angle.factor == 0 && angle.gearRatio == 0; + angle.calculate(); + return angle.factor == 0; + } + + /** + * Check if the conversion factor can be found. + * + * @return If the conversion factors can be found. + */ + public boolean works() + { + return (angle.factor != 0 && drive.factor != 0) || + ((drive.gearRatio != 0 && drive.diameter != 0)) && (angle.gearRatio != 0); } } diff --git a/swervelib/parser/json/modules/DriveConversionFactorsJson.java b/swervelib/parser/json/modules/DriveConversionFactorsJson.java index d792d36d..937b78a1 100644 --- a/swervelib/parser/json/modules/DriveConversionFactorsJson.java +++ b/swervelib/parser/json/modules/DriveConversionFactorsJson.java @@ -2,8 +2,6 @@ import edu.wpi.first.math.util.Units; import swervelib.math.SwerveMath; -import swervelib.telemetry.Alert; -import swervelib.telemetry.Alert.AlertType; /** * Drive motor composite JSON parse class. @@ -14,15 +12,15 @@ public class DriveConversionFactorsJson /** * Gear ratio for the drive motor rotations to turn the wheel 1 complete rotation. */ - public double gearRatio = 0; + public double gearRatio; /** * Diameter of the wheel in inches. */ - public double diameter = 0; + public double diameter; /** * Calculated conversion factor. */ - public double factor = 0; + public double factor = 0; /** * Calculate the drive conversion factor. @@ -31,12 +29,6 @@ public class DriveConversionFactorsJson */ public double calculate() { - if (factor != 0 && (diameter != 0 || gearRatio != 0)) - { - new Alert("Configuration", - "The given drive conversion factor takes precedence over the composite conversion factor, please remove 'factor' if you want to use the composite factor instead.", - AlertType.WARNING).set(true); - } if (factor == 0) { factor = SwerveMath.calculateMetersPerRotation(Units.inchesToMeters(this.diameter), this.gearRatio); diff --git a/swervelib/simulation/SwerveIMUSimulation.java b/swervelib/simulation/SwerveIMUSimulation.java index b3f2b62d..aa92c7e8 100644 --- a/swervelib/simulation/SwerveIMUSimulation.java +++ b/swervelib/simulation/SwerveIMUSimulation.java @@ -6,9 +6,9 @@ import edu.wpi.first.math.geometry.Translation3d; import edu.wpi.first.math.kinematics.SwerveDriveKinematics; import edu.wpi.first.math.kinematics.SwerveModuleState; -import edu.wpi.first.wpilibj.Timer; import edu.wpi.first.wpilibj.smartdashboard.Field2d; import java.util.Optional; +import org.ironmaple.simulation.drivesims.GyroSimulation; /** * Simulation for {@link swervelib.SwerveDrive} IMU. @@ -16,27 +16,16 @@ public class SwerveIMUSimulation { - /** - * Main timer to control movement estimations. - */ - private final Timer timer; - /** - * The last time the timer was read, used to determine position changes. - */ - private double lastTime; - /** - * Heading of the robot. - */ - private double angle; + private final GyroSimulation gyroSimulation; /** * Create the swerve drive IMU simulation. + * + * @param gyroSimulation Gyro simulation from MapleSim. */ - public SwerveIMUSimulation() + public SwerveIMUSimulation(GyroSimulation gyroSimulation) { - timer = new Timer(); - timer.start(); - lastTime = timer.get(); + this.gyroSimulation = gyroSimulation; } /** @@ -46,7 +35,7 @@ public SwerveIMUSimulation() */ public Rotation2d getYaw() { - return new Rotation2d(angle); + return gyroSimulation.getGyroReading(); } /** @@ -76,7 +65,7 @@ public Rotation2d getRoll() */ public Rotation3d getGyroRotation3d() { - return new Rotation3d(0, 0, angle); + return new Rotation3d(0, 0, getYaw().getRadians()); } /** @@ -104,9 +93,6 @@ public void updateOdometry( Pose2d[] modulePoses, Field2d field) { - - angle += kinematics.toChassisSpeeds(states).omegaRadiansPerSecond * (timer.get() - lastTime); - lastTime = timer.get(); field.getObject("XModules").setPoses(modulePoses); } @@ -117,6 +103,6 @@ public void updateOdometry( */ public void setAngle(double angle) { - this.angle = angle; + this.gyroSimulation.setRotation(Rotation2d.fromRadians(angle)); } } diff --git a/swervelib/simulation/SwerveModuleSimulation.java b/swervelib/simulation/SwerveModuleSimulation.java index 2fb06f8f..76b15ec0 100644 --- a/swervelib/simulation/SwerveModuleSimulation.java +++ b/swervelib/simulation/SwerveModuleSimulation.java @@ -1,54 +1,33 @@ package swervelib.simulation; +import static edu.wpi.first.units.Units.Amps; + import edu.wpi.first.math.geometry.Rotation2d; import edu.wpi.first.math.kinematics.SwerveModulePosition; import edu.wpi.first.math.kinematics.SwerveModuleState; -import edu.wpi.first.wpilibj.Timer; +import org.ironmaple.simulation.drivesims.SelfControlledSwerveDriveSimulation; +import swervelib.parser.SwerveModulePhysicalCharacteristics; /** - * Class to hold simulation data for {@link swervelib.SwerveModule} + * Class that wraps around {@link org.ironmaple.simulation.drivesims.SwerveModuleSimulation} */ public class SwerveModuleSimulation { + private SelfControlledSwerveDriveSimulation.SelfControlledModuleSimulation mapleSimModule = null; /** - * Main timer to simulate the passage of time. - */ - private final Timer timer; - /** - * Time delta since last update - */ - private double dt; - /** - * Fake motor position. - */ - private double fakePos; - /** - * The fake speed of the previous state, used to calculate {@link SwerveModuleSimulation#fakePos}. - */ - private double fakeSpeed; - /** - * Last time queried. - */ - private double lastTime; - /** - * Current simulated swerve module state. - */ - private SwerveModuleState state; - - /** - * Create simulation class and initialize module at 0. + * Configure the maple sim module + * + * @param simModule the {@link org.ironmaple.simulation.drivesims.SwerveModuleSimulation} object for simulation */ - public SwerveModuleSimulation() + public void configureSimModule(org.ironmaple.simulation.drivesims.SwerveModuleSimulation simModule, + SwerveModulePhysicalCharacteristics physicalCharacteristics) { - timer = new Timer(); - timer.start(); - lastTime = timer.get(); - state = new SwerveModuleState(0, Rotation2d.fromDegrees(0)); - fakeSpeed = 0; - fakePos = 0; - dt = 0; + this.mapleSimModule = new SelfControlledSwerveDriveSimulation.SelfControlledModuleSimulation(simModule); + this.mapleSimModule.withCurrentLimits( + Amps.of(physicalCharacteristics.driveMotorCurrentLimit), + Amps.of(physicalCharacteristics.angleMotorCurrentLimit)); } /** @@ -59,13 +38,7 @@ public SwerveModuleSimulation() */ public void updateStateAndPosition(SwerveModuleState desiredState) { - dt = timer.get() - lastTime; - lastTime = timer.get(); - - state = desiredState; - fakeSpeed = desiredState.speedMetersPerSecond; - fakePos += (fakeSpeed * dt); - + mapleSimModule.runModuleState(desiredState); } /** @@ -75,8 +48,7 @@ public void updateStateAndPosition(SwerveModuleState desiredState) */ public SwerveModulePosition getPosition() { - - return new SwerveModulePosition(fakePos, state.angle); + return mapleSimModule.getModulePosition(); } /** @@ -86,6 +58,12 @@ public SwerveModulePosition getPosition() */ public SwerveModuleState getState() { + if (mapleSimModule == null) + { + return new SwerveModuleState(); + } + SwerveModuleState state = mapleSimModule.getMeasuredState(); + state.angle = state.angle.minus(Rotation2d.kZero); return state; } } diff --git a/swervelib/telemetry/SwerveDriveTelemetry.java b/swervelib/telemetry/SwerveDriveTelemetry.java index 3c90fc6f..07d984bc 100644 --- a/swervelib/telemetry/SwerveDriveTelemetry.java +++ b/swervelib/telemetry/SwerveDriveTelemetry.java @@ -1,8 +1,15 @@ package swervelib.telemetry; +import edu.wpi.first.math.geometry.Rotation2d; +import edu.wpi.first.math.kinematics.ChassisSpeeds; +import edu.wpi.first.math.kinematics.SwerveModuleState; +import edu.wpi.first.networktables.NetworkTableInstance; +import edu.wpi.first.networktables.StructArrayPublisher; +import edu.wpi.first.networktables.StructPublisher; +import edu.wpi.first.wpilibj.Alert; +import edu.wpi.first.wpilibj.Alert.AlertType; import edu.wpi.first.wpilibj.RobotBase; import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard; -import swervelib.telemetry.Alert.AlertType; /** * Telemetry to describe the {@link swervelib.SwerveDrive} following frc-web-components. (Which follows AdvantageKit) @@ -13,90 +20,186 @@ public class SwerveDriveTelemetry /** * An {@link Alert} for if the CAN ID is greater than 40. */ - public static final Alert canIdWarning = new Alert("JSON", - "CAN IDs greater than 40 can cause undefined behaviour, please use a CAN ID below 40!", - Alert.AlertType.WARNING); + public static final Alert canIdWarning = new Alert("JSON", + "CAN IDs greater than 40 can cause undefined behaviour, please use a CAN ID below 40!", + AlertType.kWarning); /** * An {@link Alert} for if there is an I2C lockup issue on the roboRIO. */ - public static final Alert i2cLockupWarning = new Alert("IMU", - "I2C lockup issue detected on roboRIO. Check console for more information.", - Alert.AlertType.WARNING); + public static final Alert i2cLockupWarning = new Alert("IMU", + "I2C lockup issue detected on roboRIO. Check console for more information.", + AlertType.kWarning); /** * NavX serial comm issue. */ - public static final Alert serialCommsIssueWarning = new Alert("IMU", - "Serial comms is interrupted with USB and other serial traffic and causes intermittent connected/disconnection issues. Please consider another protocol or be mindful of this.", - AlertType.WARNING); + public static final Alert serialCommsIssueWarning = new Alert("IMU", + "Serial comms is interrupted with USB and other serial traffic and causes intermittent connected/disconnection issues. Please consider another protocol or be mindful of this.", + AlertType.kWarning); + /** + * Measured swerve module states object. + */ + public static SwerveModuleState[] measuredStatesObj = new SwerveModuleState[4]; + /** + * Desired swerve module states object + */ + public static SwerveModuleState[] desiredStatesObj = new SwerveModuleState[4]; + /** + * The maximum achievable angular velocity of the robot. This is used to visualize the angular velocity from the + * chassis speeds properties. + */ + public static ChassisSpeeds measuredChassisSpeedsObj = new ChassisSpeeds(); + /** + * Describes the desired forward, sideways and angular velocity of the robot. + */ + public static ChassisSpeeds desiredChassisSpeedsObj = new ChassisSpeeds(); + /** + * The robot's current rotation based on odometry or gyro readings + */ + public static Rotation2d robotRotationObj = new Rotation2d(); /** * The current telemetry verbosity level. */ - public static TelemetryVerbosity verbosity = TelemetryVerbosity.MACHINE; + public static TelemetryVerbosity verbosity = TelemetryVerbosity.MACHINE; /** * State of simulation of the Robot, used to optimize retrieval. */ - public static boolean isSimulation = RobotBase.isSimulation(); + public static boolean isSimulation = RobotBase.isSimulation(); /** * The number of swerve modules */ - public static int moduleCount; + public static int moduleCount; /** * The Locations of the swerve drive wheels. */ - public static double[] wheelLocations; + public static double[] wheelLocations; /** * An array of rotation and velocity values describing the measured state of each swerve module */ - public static double[] measuredStates; + public static double[] measuredStates; /** * An array of rotation and velocity values describing the desired state of each swerve module */ - public static double[] desiredStates; + public static double[] desiredStates; /** * The robot's current rotation based on odometry or gyro readings */ - public static double robotRotation = 0; + public static double robotRotation = 0; /** * The maximum achievable speed of the modules, used to adjust the size of the vectors. */ - public static double maxSpeed; + public static double maxSpeed; /** * The units of the module rotations and robot rotation */ - public static String rotationUnit = "degrees"; + public static String rotationUnit = "degrees"; /** * The distance between the left and right modules. */ - public static double sizeLeftRight; + public static double sizeLeftRight; /** * The distance between the front and back modules. */ - public static double sizeFrontBack; + public static double sizeFrontBack; /** * The direction the robot should be facing when the "Robot Rotation" is zero or blank. This option is often useful to * align with odometry data or match videos. 'up', 'right', 'down' or 'left' */ - public static String forwardDirection = "up"; + public static String forwardDirection = "up"; /** * The maximum achievable angular velocity of the robot. This is used to visualize the angular velocity from the * chassis speeds properties. */ - public static double maxAngularVelocity; + public static double maxAngularVelocity; /** * The maximum achievable angular velocity of the robot. This is used to visualize the angular velocity from the * chassis speeds properties. */ - public static double[] measuredChassisSpeeds = new double[3]; + public static double[] measuredChassisSpeeds = new double[3]; /** * Describes the desired forward, sideways and angular velocity of the robot. */ - public static double[] desiredChassisSpeeds = new double[3]; + public static double[] desiredChassisSpeeds = new double[3]; + /** + * Struct publisher for AdvantageScope swerve widgets. + */ + private static StructArrayPublisher measuredStatesStruct + = NetworkTableInstance.getDefault() + .getStructArrayTopic( + "swerve/advantagescope/currentStates", + SwerveModuleState.struct) + .publish(); + /** + * Struct publisher for AdvantageScope swerve widgets. + */ + private static StructArrayPublisher desiredStatesStruct + = NetworkTableInstance.getDefault() + .getStructArrayTopic( + "swerve/advantagescope/desiredStates", + SwerveModuleState.struct) + .publish(); + /** + * Measured {@link ChassisSpeeds} for NT4 AdvantageScope swerve widgets. + */ + private static StructPublisher measuredChassisSpeedsStruct + = NetworkTableInstance.getDefault() + .getStructTopic( + "swerve/advantagescope/measuredChassisSpeeds", + ChassisSpeeds.struct) + .publish(); + /** + * Desired {@link ChassisSpeeds} for NT4 AdvantageScope swerve widgets. + */ + private static StructPublisher desiredChassisSpeedsStruct + = NetworkTableInstance.getDefault() + .getStructTopic( + "swerve/advantagescope/desiredChassisSpeeds", + ChassisSpeeds.struct) + .publish(); + /** + * Robot {@link Rotation2d} for AdvantageScope swerve widgets. + */ + private static StructPublisher robotRotationStruct + = NetworkTableInstance.getDefault() + .getStructTopic( + "swerve/advantagescope/robotRotation", + Rotation2d.struct) + .publish(); /** * Upload data to smartdashboard */ public static void updateData() { + measuredChassisSpeeds[0] = measuredChassisSpeedsObj.vxMetersPerSecond; + measuredChassisSpeeds[1] = measuredChassisSpeedsObj.vxMetersPerSecond; + measuredChassisSpeeds[2] = Math.toDegrees(measuredChassisSpeedsObj.omegaRadiansPerSecond); + + desiredChassisSpeeds[0] = desiredChassisSpeedsObj.vxMetersPerSecond; + desiredChassisSpeeds[1] = desiredChassisSpeedsObj.vyMetersPerSecond; + desiredChassisSpeeds[2] = Math.toDegrees(desiredChassisSpeedsObj.omegaRadiansPerSecond); + + robotRotation = robotRotationObj.getDegrees(); + + for (int i = 0; i < measuredStatesObj.length; i++) + { + SwerveModuleState state = measuredStatesObj[i]; + if (state != null) + { + measuredStates[i * 2] = state.angle.getDegrees(); + measuredStates[i * 2 + 1] = state.speedMetersPerSecond; + } + } + + for (int i = 0; i < desiredStatesObj.length; i++) + { + SwerveModuleState state = desiredStatesObj[i]; + if (state != null) + { + desiredStates[i * 2] = state.angle.getDegrees(); + desiredStates[i * 2 + 1] = state.speedMetersPerSecond; + } + } + SmartDashboard.putNumber("swerve/moduleCount", moduleCount); SmartDashboard.putNumberArray("swerve/wheelLocations", wheelLocations); SmartDashboard.putNumberArray("swerve/measuredStates", measuredStates); @@ -110,6 +213,12 @@ public static void updateData() SmartDashboard.putNumber("swerve/maxAngularVelocity", maxAngularVelocity); SmartDashboard.putNumberArray("swerve/measuredChassisSpeeds", measuredChassisSpeeds); SmartDashboard.putNumberArray("swerve/desiredChassisSpeeds", desiredChassisSpeeds); + + desiredStatesStruct.set(desiredStatesObj); + measuredStatesStruct.set(measuredStatesObj); + desiredChassisSpeedsStruct.set(desiredChassisSpeedsObj); + measuredChassisSpeedsStruct.set(measuredChassisSpeedsObj); + robotRotationStruct.set(robotRotationObj); } /**