diff --git a/swervelib/SwerveDrive.java b/swervelib/SwerveDrive.java index ad09420..c5c4ff5 100644 --- a/swervelib/SwerveDrive.java +++ b/swervelib/SwerveDrive.java @@ -52,11 +52,14 @@ import java.util.Map; 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 org.ironmaple.simulation.drivesims.configs.SwerveModuleSimulationConfig; + import swervelib.encoders.CANCoderSwerve; import swervelib.imu.Pigeon2Swerve; import swervelib.imu.SwerveIMU; @@ -243,7 +246,7 @@ public class SwerveDrive .withRobotMass(Kilograms.of(config.physicalCharacteristics.robotMassKg)) .withCustomModuleTranslations(config.moduleLocationsMeters) .withGyro(config.getGyroSim()) - .withSwerveModule(() -> new SwerveModuleSimulation( + .withSwerveModule(new SwerveModuleSimulationConfig( config.getDriveMotorSim(), config.getAngleMotorSim(), config.physicalCharacteristics.conversionFactor.drive.gearRatio, @@ -254,7 +257,8 @@ public class SwerveDrive config.physicalCharacteristics.conversionFactor.drive.diameter / 2), KilogramSquareMeters.of(0.02), - config.physicalCharacteristics.wheelGripCoefficientOfFriction)); + config.physicalCharacteristics.wheelGripCoefficientOfFriction) + ); mapleSimDrive = new SwerveDriveSimulation(simulationConfig, startingPose); @@ -452,39 +456,38 @@ public class SwerveDrive /** * Tertiary method of controlling the drive base given velocity in both field oriented and robot oriented at the same - * time. The inputs are added together so this is not intneded to be used to give the driver both methods of control. + * time. The inputs are added together so this is not intended to be used to give the driver both methods of control. * * @param fieldOrientedVelocity The field oriented velocties to use * @param robotOrientedVelocity The robot oriented velocties to use */ - public void driveFieldOrientedandRobotOriented(ChassisSpeeds fieldOrientedVelocity, + public void driveFieldOrientedAndRobotOriented(ChassisSpeeds fieldOrientedVelocity, ChassisSpeeds robotOrientedVelocity) { - fieldOrientedVelocity.toRobotRelativeSpeeds(getOdometryHeading()); - drive(fieldOrientedVelocity.plus(robotOrientedVelocity)); + + drive(ChassisSpeeds.fromFieldRelativeSpeeds(fieldOrientedVelocity, getOdometryHeading()) + .plus(robotOrientedVelocity)); } /** * Secondary method of controlling the drive base given velocity and adjusting it for field oriented use. * - * @param velocity Velocity of the robot desired. + * @param fieldRelativeSpeeds Velocity of the robot desired. */ - public void driveFieldOriented(ChassisSpeeds velocity) + public void driveFieldOriented(ChassisSpeeds fieldRelativeSpeeds) { - velocity.toRobotRelativeSpeeds(getOdometryHeading()); - drive(velocity); + drive(ChassisSpeeds.fromFieldRelativeSpeeds(fieldRelativeSpeeds, getOdometryHeading())); } /** * Secondary method of controlling the drive base given velocity and adjusting it for field oriented use. * - * @param velocity Velocity of the robot desired. + * @param fieldRelativeSpeeds Velocity of the robot desired. * @param centerOfRotationMeters The center of rotation in meters, 0 is the center of the robot. */ - public void driveFieldOriented(ChassisSpeeds velocity, Translation2d centerOfRotationMeters) + public void driveFieldOriented(ChassisSpeeds fieldRelativeSpeeds, Translation2d centerOfRotationMeters) { - velocity.toRobotRelativeSpeeds(getOdometryHeading()); - drive(velocity, centerOfRotationMeters); + drive(ChassisSpeeds.fromFieldRelativeSpeeds(fieldRelativeSpeeds, getOdometryHeading()), centerOfRotationMeters); } /** @@ -535,7 +538,7 @@ public class SwerveDrive ChassisSpeeds velocity = new ChassisSpeeds(translation.getX(), translation.getY(), rotation); if (fieldRelative) { - velocity.toRobotRelativeSpeeds(getOdometryHeading()); + velocity = ChassisSpeeds.fromFieldRelativeSpeeds(velocity, getOdometryHeading()); } drive(velocity, isOpenLoop, centerOfRotationMeters); } @@ -564,7 +567,7 @@ public class SwerveDrive if (fieldRelative) { - velocity.toRobotRelativeSpeeds(getOdometryHeading()); + ChassisSpeeds.fromFieldRelativeSpeeds(velocity, getOdometryHeading()); } drive(velocity, isOpenLoop, new Translation2d()); } @@ -730,7 +733,6 @@ public class SwerveDrive /** * 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 @@ -844,8 +846,10 @@ public class SwerveDrive // 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. ChassisSpeeds robotRelativeSpeeds = kinematics.toChassisSpeeds(getStates()); - robotRelativeSpeeds.toFieldRelativeSpeeds(getOdometryHeading());//.unaryMinus()); - return robotRelativeSpeeds; + return ChassisSpeeds.fromRobotRelativeSpeeds(robotRelativeSpeeds, getOdometryHeading()); + // Might need to be this instead + //return ChassisSpeeds.fromFieldRelativeSpeeds( + // kinematics.toChassisSpeeds(getStates()), getOdometryHeading().unaryMinus()); } /** @@ -874,8 +878,7 @@ public class SwerveDrive mapleSimDrive.setSimulationWorldPose(pose); } odometryLock.unlock(); - ChassisSpeeds robotRelativeSpeeds = new ChassisSpeeds(); - robotRelativeSpeeds.toFieldRelativeSpeeds(getYaw()); + ChassisSpeeds robotRelativeSpeeds = ChassisSpeeds.fromRobotRelativeSpeeds(new ChassisSpeeds(0, 0, 0), getYaw()); kinematics.toSwerveModuleStates(robotRelativeSpeeds); } @@ -1408,8 +1411,8 @@ public class SwerveDrive /** * 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(double)} with - * the following. + * @param enable Enable chassis velocity correction, which will use + * {@link ChassisSpeeds#discretize(ChassisSpeeds, double)}} with the following. * @param dtSeconds The duration of the timestep the speeds should be applied for. */ public void setChassisDiscretization(boolean enable, double dtSeconds) @@ -1425,10 +1428,10 @@ public class SwerveDrive * 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(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 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 dtSeconds The duration of the timestep the speeds should be applied for. */ public void setChassisDiscretization(boolean useInTeleop, boolean useInAuto, double dtSeconds) @@ -1476,8 +1479,10 @@ public class SwerveDrive var angularVelocity = new Rotation2d(imu.getYawAngularVelocity().in(RadiansPerSecond) * angularVelocityCoefficient); if (angularVelocity.getRadians() != 0.0) { - robotRelativeVelocity.toFieldRelativeSpeeds(getOdometryHeading()); - robotRelativeVelocity.toRobotRelativeSpeeds(getOdometryHeading().plus(angularVelocity)); + ChassisSpeeds fieldRelativeVelocity = ChassisSpeeds.fromRobotRelativeSpeeds(robotRelativeVelocity, + getOdometryHeading()); + robotRelativeVelocity = ChassisSpeeds.fromFieldRelativeSpeeds(fieldRelativeVelocity, + getOdometryHeading().plus(angularVelocity)); } return robotRelativeVelocity; } @@ -1503,7 +1508,7 @@ public class SwerveDrive // https://www.chiefdelphi.com/t/whitepaper-swerve-drive-skew-and-second-order-kinematics/416964/5 if (uesChassisDiscretize) { - robotRelativeVelocity.discretize(discretizationdtSeconds); + robotRelativeVelocity = ChassisSpeeds.discretize(robotRelativeVelocity, discretizationdtSeconds); } return robotRelativeVelocity; diff --git a/swervelib/SwerveInputStream.java b/swervelib/SwerveInputStream.java index 4a8748d..0222bf0 100644 --- a/swervelib/SwerveInputStream.java +++ b/swervelib/SwerveInputStream.java @@ -6,6 +6,8 @@ import edu.wpi.first.math.geometry.Rotation2d; import edu.wpi.first.math.geometry.Translation2d; import edu.wpi.first.math.kinematics.ChassisSpeeds; import edu.wpi.first.wpilibj.DriverStation; +import edu.wpi.first.wpilibj.DriverStation.Alliance; +import edu.wpi.first.wpilibj.XboxController; import java.util.Optional; import java.util.function.BooleanSupplier; import java.util.function.DoubleSupplier; @@ -13,11 +15,28 @@ import java.util.function.Supplier; import swervelib.math.SwerveMath; /** - * Helper class to easily transform Controller inputs into workable Chassis speeds.
Inspired by SciBorgs. - * https://github.com/SciBorgs/Crescendo-2024/blob/main/src/main/java/org/sciborgs1155/lib/InputStream.java + * Helper class to easily transform Controller inputs into workable Chassis speeds. Intended to easily create an + * interface that generates {@link ChassisSpeeds} from {@link XboxController} *

- * Intended to easily create an interface that generates {@link ChassisSpeeds} from - * {@link edu.wpi.first.wpilibj.XboxController} + *
Inspired by SciBorgs FRC 1155.
Example: + *

+ * {@code
+ *   XboxController driverXbox = new XboxController(0);
+ *
+ *   SwerveInputStream driveAngularVelocity = SwerveInputStream.of(drivebase.getSwerveDrive(),
+ *                                                                 () -> driverXbox.getLeftY() * -1,
+ *                                                                 () -> driverXbox.getLeftX() * -1) // Axis which give the desired translational angle and speed.
+ *                                                             .withControllerRotationAxis(driverXbox::getRightX) // Axis which give the desired angular velocity.
+ *                                                             .deadband(0.01)                  // Controller deadband
+ *                                                             .scaleTranslation(0.8)           // Scaled controller translation axis
+ *                                                             .allianceRelativeControl(true);  // Alliance relative controls.
+ *
+ *   SwerveInputStream driveDirectAngle = driveAngularVelocity.copy()  // Copy the stream so further changes do not affect driveAngularVelocity
+ *                                                            .withControllerHeadingAxis(driverXbox::getRightX,
+ *                                                                                       driverXbox::getRightY) // Axis which give the desired heading angle using trigonometry.
+ *                                                            .headingWhile(true); // Enable heading based control.
+ * }
+ * 
*/ public class SwerveInputStream implements Supplier { @@ -37,7 +56,7 @@ public class SwerveInputStream implements Supplier /** * Rotation supplier as angular velocity. */ - private Optional controllerOmega = Optional.empty(); + private Optional controllerOmega = Optional.empty(); /** * Controller supplier as heading. */ @@ -49,19 +68,19 @@ public class SwerveInputStream implements Supplier /** * Axis deadband for the controller. */ - private Optional axisDeadband = Optional.empty(); + private Optional axisDeadband = Optional.empty(); /** * Translational axis scalar value, should be between (0, 1]. */ - private Optional translationAxisScale = Optional.empty(); + private Optional translationAxisScale = Optional.empty(); /** * Angular velocity axis scalar value, should be between (0, 1] */ - private Optional omegaAxisScale = Optional.empty(); + private Optional omegaAxisScale = Optional.empty(); /** * Target to aim at. */ - private Optional aimTarget = Optional.empty(); + private Optional aimTarget = Optional.empty(); /** * Output {@link ChassisSpeeds} based on heading while this is True. */ @@ -78,6 +97,22 @@ public class SwerveInputStream implements Supplier * Maintain current heading and drive without rotating, ideally. */ private Optional translationOnlyEnabled = Optional.empty(); + /** + * Cube the translation magnitude from the controller. + */ + private Optional translationCube = Optional.empty(); + /** + * Cube the angular velocity axis from the controller. + */ + private Optional omegaCube = Optional.empty(); + /** + * Robot relative oriented output expected. + */ + private Optional robotRelative = Optional.empty(); + /** + * Field oriented chassis output is relative to your current alliance. + */ + private Optional allianceRelative = Optional.empty(); /** * {@link SwerveController} for simple control over heading. */ @@ -88,53 +123,6 @@ public class SwerveInputStream implements Supplier private SwerveInputMode currentMode = SwerveInputMode.ANGULAR_VELOCITY; - /** - * Drive modes to keep track of. - */ - enum SwerveInputMode - { - /** - * Translation only mode, does not allow for rotation and maintains current heading. - */ - TRANSLATION_ONLY, - /** - * Output based off angular velocity - */ - ANGULAR_VELOCITY, - /** - * Output based off of heading. - */ - HEADING, - /** - * Output based off of targeting. - */ - AIM - } - - /** - * Copy the {@link SwerveInputStream} object. - * - * @return Clone of current {@link SwerveInputStream} - */ - public SwerveInputStream copy() - { - SwerveInputStream newStream = new SwerveInputStream(swerveDrive, controllerTranslationX, controllerTranslationY); - newStream.controllerOmega = controllerOmega; - newStream.controllerHeadingX = controllerHeadingX; - newStream.controllerHeadingY = controllerHeadingY; - newStream.axisDeadband = axisDeadband; - newStream.translationAxisScale = translationAxisScale; - newStream.omegaAxisScale = omegaAxisScale; - newStream.aimTarget = aimTarget; - newStream.headingEnabled = headingEnabled; - newStream.aimEnabled = aimEnabled; - newStream.currentMode = currentMode; - newStream.translationOnlyEnabled = translationOnlyEnabled; - newStream.lockedHeading = lockedHeading; - newStream.swerveController = swerveController; - return newStream; - } - /** * Create a {@link SwerveInputStream} for an easy way to generate {@link ChassisSpeeds} from a driver controller. * @@ -193,6 +181,130 @@ public class SwerveInputStream implements Supplier return new SwerveInputStream(drive, x, y); } + /** + * Copy the {@link SwerveInputStream} object. + * + * @return Clone of current {@link SwerveInputStream} + */ + public SwerveInputStream copy() + { + SwerveInputStream newStream = new SwerveInputStream(swerveDrive, controllerTranslationX, controllerTranslationY); + newStream.controllerOmega = controllerOmega; + newStream.controllerHeadingX = controllerHeadingX; + newStream.controllerHeadingY = controllerHeadingY; + newStream.axisDeadband = axisDeadband; + newStream.translationAxisScale = translationAxisScale; + newStream.omegaAxisScale = omegaAxisScale; + newStream.aimTarget = aimTarget; + newStream.headingEnabled = headingEnabled; + newStream.aimEnabled = aimEnabled; + newStream.currentMode = currentMode; + newStream.translationOnlyEnabled = translationOnlyEnabled; + newStream.lockedHeading = lockedHeading; + newStream.swerveController = swerveController; + newStream.omegaCube = omegaCube; + newStream.translationCube = translationCube; + newStream.robotRelative = robotRelative; + newStream.allianceRelative = allianceRelative; + return newStream; + } + + /** + * Set the stream to output robot relative {@link ChassisSpeeds} + * + * @param enabled Robot-Relative {@link ChassisSpeeds} output. + * @return self + */ + public SwerveInputStream robotRelative(BooleanSupplier enabled) + { + robotRelative = Optional.of(enabled); + return this; + } + + /** + * Set the stream to output robot relative {@link ChassisSpeeds} + * + * @param enabled Robot-Relative {@link ChassisSpeeds} output. + * @return self + */ + public SwerveInputStream robotRelative(boolean enabled) + { + robotRelative = enabled ? Optional.of(() -> enabled) : Optional.empty(); + return this; + } + + /** + * Modify the output {@link ChassisSpeeds} so that it is always relative to your alliance. + * + * @param enabled Alliance aware {@link ChassisSpeeds} output. + * @return self + */ + public SwerveInputStream allianceRelativeControl(BooleanSupplier enabled) + { + allianceRelative = Optional.of(enabled); + return this; + } + + /** + * Modify the output {@link ChassisSpeeds} so that it is always relative to your alliance. + * + * @param enabled Alliance aware {@link ChassisSpeeds} output. + * @return self + */ + public SwerveInputStream allianceRelativeControl(boolean enabled) + { + allianceRelative = enabled ? Optional.of(() -> enabled) : Optional.empty(); + return this; + } + + /** + * Cube the angular velocity controller axis for a non-linear controls scheme. + * + * @param enabled Enabled state for the stream. + * @return self. + */ + public SwerveInputStream cubeRotationControllerAxis(BooleanSupplier enabled) + { + omegaCube = Optional.of(enabled); + return this; + } + + /** + * Cube the angular velocity controller axis for a non-linear controls scheme. + * + * @param enabled Enabled state for the stream. + * @return self. + */ + public SwerveInputStream cubeRotationControllerAxis(boolean enabled) + { + omegaCube = Optional.of(() -> enabled); + return this; + } + + /** + * Cube the translation axis magnitude for a non-linear control scheme. + * + * @param enabled Enabled state for the stream + * @return self + */ + public SwerveInputStream cubeTranslationControllerAxis(BooleanSupplier enabled) + { + translationOnlyEnabled = Optional.of(enabled); + return this; + } + + /** + * Cube the translation axis magnitude for a non-linear control scheme + * + * @param enabled Enabled state for the stream + * @return self + */ + public SwerveInputStream cubeTranslationControllerAxis(boolean enabled) + { + translationCube = enabled ? Optional.of(() -> enabled) : Optional.empty(); + return this; + } + /** * Add a rotation axis for Angular Velocity control * @@ -255,7 +367,6 @@ public class SwerveInputStream implements Supplier return this; } - /** * Output {@link ChassisSpeeds} based on heading while the supplier is True. * @@ -510,9 +621,98 @@ public class SwerveInputStream implements Supplier } /** - * Gets a field-oriented {@link ChassisSpeeds} + * Apply the cube transformation on the given {@link Translation2d} * - * @return field-oriented {@link ChassisSpeeds} + * @param translation {@link Translation2d} representing controller input + * @return Cubed {@link Translation2d} if the {@link SwerveInputStream#translationCube} is present. + */ + private Translation2d applyTranslationCube(Translation2d translation) + { + if (translationCube.isPresent() && translationCube.get().getAsBoolean()) + { + return SwerveMath.cubeTranslation(translation); + } + return translation; + } + + /** + * Apply the cube transformation on the given rotation controller axis + * + * @param rotationAxis Rotation controller axis to cube. + * @return Cubed axis value if the {@link SwerveInputStream#omegaCube} is present. + */ + private double applyOmegaCube(double rotationAxis) + { + if (omegaCube.isPresent() && omegaCube.get().getAsBoolean()) + { + return Math.pow(rotationAxis, 3); + } + return rotationAxis; + } + + /** + * Change {@link ChassisSpeeds} to robot relative. + * + * @param fieldRelativeSpeeds Field relative speeds to translate into robot-relative speeds. + * @return Robot relative {@link ChassisSpeeds}. + */ + private ChassisSpeeds applyRobotRelativeTranslation(ChassisSpeeds fieldRelativeSpeeds) + { + if (robotRelative.isPresent() && robotRelative.get().getAsBoolean()) + { + return ChassisSpeeds.fromFieldRelativeSpeeds(fieldRelativeSpeeds, swerveDrive.getOdometryHeading()); + } + return fieldRelativeSpeeds; + } + + /** + * Apply alliance aware translation which flips the {@link Translation2d} if the robot is on the Blue alliance. + * + * @param fieldRelativeTranslation Field-relative {@link Translation2d} to flip. + * @return Alliance-oriented {@link Translation2d} + */ + private Translation2d applyAllianceAwareTranslation(Translation2d fieldRelativeTranslation) + { + if (allianceRelative.isPresent() && allianceRelative.get().getAsBoolean()) + { + if (robotRelative.isPresent() && robotRelative.get().getAsBoolean()) + { + throw new RuntimeException("Cannot use robot oriented control with Alliance aware movement!"); + } + if (DriverStation.getAlliance().isPresent() && DriverStation.getAlliance().get() == Alliance.Red) + { + return fieldRelativeTranslation.rotateBy(Rotation2d.k180deg); + } + } + return fieldRelativeTranslation; + } + + /** + * Apply alliance aware translation which flips the {@link Rotation2d} if the robot is on the Blue alliance. + * + * @param fieldRelativeRotation Field-relative {@link Rotation2d} to flip. + * @return Alliance-oriented {@link Rotation2d} + */ + private Rotation2d applyAllianceAwareRotation(Rotation2d fieldRelativeRotation) + { + if (allianceRelative.isPresent() && allianceRelative.get().getAsBoolean()) + { + if (robotRelative.isPresent() && robotRelative.get().getAsBoolean()) + { + throw new RuntimeException("Cannot use robot oriented control with Alliance aware movement!"); + } + if (DriverStation.getAlliance().isPresent() && DriverStation.getAlliance().get() == Alliance.Red) + { + return fieldRelativeRotation.rotateBy(Rotation2d.k180deg); + } + } + return fieldRelativeRotation; + } + + /** + * Gets a {@link ChassisSpeeds} + * + * @return {@link ChassisSpeeds} */ @Override public ChassisSpeeds get() @@ -520,10 +720,13 @@ public class SwerveInputStream implements Supplier double maximumChassisVelocity = swerveDrive.getMaximumChassisVelocity(); Translation2d scaledTranslation = applyTranslationScalar(applyDeadband(controllerTranslationX.getAsDouble()), applyDeadband(controllerTranslationY.getAsDouble())); + scaledTranslation = applyTranslationCube(scaledTranslation); + scaledTranslation = applyAllianceAwareTranslation(scaledTranslation); - double vxMetersPerSecond = scaledTranslation.getX() * maximumChassisVelocity; - double vyMetersPerSecond = scaledTranslation.getY() * maximumChassisVelocity; - double omegaRadiansPerSecond = 0; + double vxMetersPerSecond = scaledTranslation.getX() * maximumChassisVelocity; + double vyMetersPerSecond = scaledTranslation.getY() * maximumChassisVelocity; + double omegaRadiansPerSecond = 0; + ChassisSpeeds speeds = new ChassisSpeeds(); SwerveInputMode newMode = findMode(); // Handle transitions here. @@ -542,21 +745,27 @@ public class SwerveInputStream implements Supplier { omegaRadiansPerSecond = swerveController.headingCalculate(swerveDrive.getOdometryHeading().getRadians(), lockedHeading.get().getRadians()); + speeds = new ChassisSpeeds(vxMetersPerSecond, vyMetersPerSecond, omegaRadiansPerSecond); break; } case ANGULAR_VELOCITY -> { - omegaRadiansPerSecond = applyRotationalScalar(applyDeadband(controllerOmega.get().getAsDouble())) * + omegaRadiansPerSecond = applyOmegaCube(applyRotationalScalar(applyDeadband(controllerOmega.get() + .getAsDouble()))) * swerveDrive.getMaximumChassisAngularVelocity(); + speeds = new ChassisSpeeds(vxMetersPerSecond, vyMetersPerSecond, omegaRadiansPerSecond); break; } case HEADING -> { omegaRadiansPerSecond = swerveController.headingCalculate(swerveDrive.getOdometryHeading().getRadians(), - swerveController.getJoystickAngle(controllerHeadingX.get() - .getAsDouble(), - controllerHeadingY.get() - .getAsDouble())); + applyAllianceAwareRotation(Rotation2d.fromRadians( + swerveController.getJoystickAngle( + controllerHeadingX.get() + .getAsDouble(), + controllerHeadingY.get() + .getAsDouble()))).getRadians()); + speeds = new ChassisSpeeds(vxMetersPerSecond, vyMetersPerSecond, omegaRadiansPerSecond); break; } case AIM -> @@ -565,12 +774,36 @@ public class SwerveInputStream implements Supplier Translation2d relativeTrl = aimTarget.get().relativeTo(swerveDrive.getPose()).getTranslation(); Rotation2d target = new Rotation2d(relativeTrl.getX(), relativeTrl.getY()).plus(currentHeading); omegaRadiansPerSecond = swerveController.headingCalculate(currentHeading.getRadians(), target.getRadians()); + speeds = new ChassisSpeeds(vxMetersPerSecond, vyMetersPerSecond, omegaRadiansPerSecond); break; } } currentMode = newMode; - return new ChassisSpeeds(vxMetersPerSecond, vyMetersPerSecond, omegaRadiansPerSecond); + return applyRobotRelativeTranslation(speeds); + } + + /** + * Drive modes to keep track of. + */ + enum SwerveInputMode + { + /** + * Translation only mode, does not allow for rotation and maintains current heading. + */ + TRANSLATION_ONLY, + /** + * Output based off angular velocity + */ + ANGULAR_VELOCITY, + /** + * Output based off of heading. + */ + HEADING, + /** + * Output based off of targeting. + */ + AIM } } diff --git a/swervelib/SwerveModule.java b/swervelib/SwerveModule.java index ddd694d..81bb658 100644 --- a/swervelib/SwerveModule.java +++ b/swervelib/SwerveModule.java @@ -237,21 +237,21 @@ public class SwerveModule moduleNumber, AlertType.kWarning); - rawAbsoluteAnglePublisher = NetworkTableInstance.getDefault().getDoubleTopic( + rawAbsoluteAnglePublisher = NetworkTableInstance.getDefault().getTable("SmartDashboard").getDoubleTopic( "swerve/modules/" + configuration.name + "/Raw Absolute Encoder").publish(); - adjAbsoluteAnglePublisher = NetworkTableInstance.getDefault().getDoubleTopic( + adjAbsoluteAnglePublisher = NetworkTableInstance.getDefault().getTable("SmartDashboard").getDoubleTopic( "swerve/modules/" + configuration.name + "/Adjusted Absolute Encoder").publish(); - absoluteEncoderIssuePublisher = NetworkTableInstance.getDefault().getBooleanTopic( + absoluteEncoderIssuePublisher = NetworkTableInstance.getDefault().getTable("SmartDashboard").getBooleanTopic( "swerve/modules/" + configuration.name + "/Absolute Encoder Read Issue").publish(); - rawAnglePublisher = NetworkTableInstance.getDefault().getDoubleTopic( + rawAnglePublisher = NetworkTableInstance.getDefault().getTable("SmartDashboard").getDoubleTopic( "swerve/modules/" + configuration.name + "/Raw Angle Encoder").publish(); - rawDriveEncoderPublisher = NetworkTableInstance.getDefault().getDoubleTopic( + rawDriveEncoderPublisher = NetworkTableInstance.getDefault().getTable("SmartDashboard").getDoubleTopic( "swerve/modules/" + configuration.name + "/Raw Drive Encoder").publish(); - rawDriveVelocityPublisher = NetworkTableInstance.getDefault().getDoubleTopic( + rawDriveVelocityPublisher = NetworkTableInstance.getDefault().getTable("SmartDashboard").getDoubleTopic( "swerve/modules/" + configuration.name + "/Raw Drive Velocity").publish(); - speedSetpointPublisher = NetworkTableInstance.getDefault().getDoubleTopic( + speedSetpointPublisher = NetworkTableInstance.getDefault().getTable("SmartDashboard").getDoubleTopic( "swerve/modules/" + configuration.name + "/Speed Setpoint").publish(); - angleSetpointPublisher = NetworkTableInstance.getDefault().getDoubleTopic( + angleSetpointPublisher = NetworkTableInstance.getDefault().getTable("SmartDashboard").getDoubleTopic( "swerve/modules/" + configuration.name + "/Angle Setpoint").publish(); } @@ -423,7 +423,10 @@ public class SwerveModule LinearVelocity curVelocity = MetersPerSecond.of(lastState.speedMetersPerSecond); desiredState.speedMetersPerSecond = nextVelocity.magnitude(); - setDesiredState(desiredState, isOpenLoop, driveMotorFeedforward.calculate(nextVelocity).magnitude()); + setDesiredState(desiredState, + isOpenLoop, + driveMotorFeedforward.calculateWithVelocities(curVelocity.in(MetersPerSecond), + nextVelocity.in(MetersPerSecond))); } /** @@ -441,7 +444,7 @@ public class SwerveModule if (isOpenLoop) { double percentOutput = desiredState.speedMetersPerSecond / maxDriveVelocity.in(MetersPerSecond); - driveMotor.set(percentOutput); + driveMotor.setVoltage(percentOutput * 12); } else { driveMotor.setReference(desiredState.speedMetersPerSecond, driveFeedforwardVoltage); @@ -823,8 +826,9 @@ public class SwerveModule * 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. + * @param swerveModuleSimulation MapleSim {@link org.ironmaple.simulation.drivesims.SwerveModuleSimulation} to + * configure with. + * @param physicalCharacteristics {@link SwerveModulePhysicalCharacteristics} that represent the swerve drive. */ public void configureModuleSimulation( org.ironmaple.simulation.drivesims.SwerveModuleSimulation swerveModuleSimulation, diff --git a/swervelib/encoders/CANCoderSwerve.java b/swervelib/encoders/CANCoderSwerve.java index 3789dc3..ba21689 100644 --- a/swervelib/encoders/CANCoderSwerve.java +++ b/swervelib/encoders/CANCoderSwerve.java @@ -13,6 +13,7 @@ import com.ctre.phoenix6.configs.CANcoderConfigurator; import com.ctre.phoenix6.hardware.CANcoder; import com.ctre.phoenix6.signals.MagnetHealthValue; import com.ctre.phoenix6.signals.SensorDirectionValue; + import edu.wpi.first.units.measure.Angle; import edu.wpi.first.units.measure.AngularVelocity; import edu.wpi.first.wpilibj.Alert; @@ -27,11 +28,7 @@ public class CANCoderSwerve extends SwerveAbsoluteEncoder /** * Wait time for status frames to show up. */ - public static double STATUS_TIMEOUT_SECONDS = Milliseconds.of(10).in(Seconds); - /** - * CANCoder with WPILib sendable and support. - */ - public CANcoder encoder; + public static double STATUS_TIMEOUT_SECONDS = Milliseconds.of(10).in(Seconds); /** * An {@link Alert} for if the CANCoder magnet field is less than ideal. */ @@ -60,6 +57,10 @@ public class CANCoderSwerve extends SwerveAbsoluteEncoder * Angular velocity of the {@link CANcoder}. */ private final StatusSignal velocity; + /** + * CANCoder with WPILib sendable and support. + */ + public CANcoder encoder; /** * {@link CANcoder} Configurator objet for this class. */ diff --git a/swervelib/encoders/CanAndMagSwerve.java b/swervelib/encoders/CanAndMagSwerve.java index 91000c3..4297158 100644 --- a/swervelib/encoders/CanAndMagSwerve.java +++ b/swervelib/encoders/CanAndMagSwerve.java @@ -1,6 +1,7 @@ package swervelib.encoders; import com.reduxrobotics.sensors.canandmag.Canandmag; +import com.reduxrobotics.sensors.canandmag.CanandmagSettings; /** * HELIUM {@link Canandmag} from ReduxRobotics absolute encoder, attached through the CAN bus. @@ -12,6 +13,10 @@ public class CanAndMagSwerve extends SwerveAbsoluteEncoder * The {@link Canandmag} representing the CANandMag on the CAN bus. */ public Canandmag encoder; + /** + * The {@link Canandmag} settings object to use. + */ + public CanandmagSettings settings; /** * Create the {@link Canandmag} @@ -21,6 +26,7 @@ public class CanAndMagSwerve extends SwerveAbsoluteEncoder public CanAndMagSwerve(int canid) { encoder = new Canandmag(canid); + settings = encoder.getSettings(); } /** @@ -51,7 +57,8 @@ public class CanAndMagSwerve extends SwerveAbsoluteEncoder @Override public void configure(boolean inverted) { - encoder.setSettings(new Canandmag.Settings().setInvertDirection(inverted)); + settings.setInvertDirection(inverted); + encoder.setSettings(settings); } /** @@ -85,7 +92,8 @@ public class CanAndMagSwerve extends SwerveAbsoluteEncoder @Override public boolean setAbsoluteEncoderOffset(double offset) { - return encoder.setSettings(new Canandmag.Settings().setZeroOffset(offset)); + settings.setZeroOffset(offset); + return encoder.setSettings(settings); } /** diff --git a/swervelib/encoders/ThriftyNovaEncoderSwerve.java b/swervelib/encoders/ThriftyNovaEncoderSwerve.java new file mode 100644 index 0000000..36292a8 --- /dev/null +++ b/swervelib/encoders/ThriftyNovaEncoderSwerve.java @@ -0,0 +1,94 @@ +package swervelib.encoders; + +import swervelib.motors.SwerveMotor; +import swervelib.motors.ThriftyNovaSwerve; + +/** + * Thrifty Nova absolute encoder, attached through the data port. + */ +public class ThriftyNovaEncoderSwerve extends SwerveAbsoluteEncoder { + /** The absolute encoder is directly interfaced through the Thrifty Nova motor. */ + protected ThriftyNovaSwerve motor; + /** + * Inversion state of the attached encoder. + */ + protected boolean inverted = false; + /** + * Offset of the absolute encoder. + */ + protected double offset = 0.0; + + /** + * Create the {@link ThriftyNovaEncoderSwerve} object as an absolute encoder from the {@link ThriftyNovaSwerve} motor. + * + * @param motor {@link SwerveMotor} through which to interface with the attached encoder . + */ + public ThriftyNovaEncoderSwerve(SwerveMotor motor) { + this.motor = (ThriftyNovaSwerve)motor; + motor.setAbsoluteEncoder(null); + } + + /** + * Set factory default. + */ + @Override + public void factoryDefault() { + } + + /** + * Clear sticky faults. + */ + @Override + public void clearStickyFaults() { + } + + /** + * Configure the absolute encoder. + * + * @param inverted Whether the encoder is inverted. + */ + @Override + public void configure(boolean inverted) { + this.inverted = inverted; + } + + /** + * Get the absolute position of the encoder. + * + * @return Absolute position in degrees from [0, 360). + */ + @Override + public double getAbsolutePosition() { + return (motor.getPosition() + offset) * (inverted ? -1.0 : 1.0); + } + + /** + * Get the instantiated absolute encoder Object. + */ + @Override + public Object getAbsoluteEncoder() { + return null; + } + + /** + * Set the absolute encoder offset. + * + * @param offset offset in degrees from [0, 360). + * @return true if successful. + */ + @Override + public boolean setAbsoluteEncoderOffset(double offset) { + this.offset = offset; + return true; + } + + /** + * Get the absolute encoder velocity. + * WARNING: Angular velocity is generally not measurable at high speeds. + * @return Velocity in degrees per second. + */ + @Override + public double getVelocity() { + return motor.getVelocity(); + } +} diff --git a/swervelib/imu/ADIS16448Swerve.java b/swervelib/imu/ADIS16448Swerve.java index 8447fc6..fc5f636 100644 --- a/swervelib/imu/ADIS16448Swerve.java +++ b/swervelib/imu/ADIS16448Swerve.java @@ -4,7 +4,6 @@ import static edu.wpi.first.units.Units.DegreesPerSecond; import edu.wpi.first.math.geometry.Rotation3d; import edu.wpi.first.math.geometry.Translation3d; -import edu.wpi.first.units.measure.AngularVelocity; import edu.wpi.first.units.measure.MutAngularVelocity; import edu.wpi.first.wpilibj.ADIS16448_IMU; import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard; diff --git a/swervelib/imu/ADIS16470Swerve.java b/swervelib/imu/ADIS16470Swerve.java index 620710d..79603e0 100644 --- a/swervelib/imu/ADIS16470Swerve.java +++ b/swervelib/imu/ADIS16470Swerve.java @@ -4,7 +4,6 @@ import static edu.wpi.first.units.Units.DegreesPerSecond; import edu.wpi.first.math.geometry.Rotation3d; import edu.wpi.first.math.geometry.Translation3d; -import edu.wpi.first.units.measure.AngularVelocity; import edu.wpi.first.units.measure.MutAngularVelocity; import edu.wpi.first.wpilibj.ADIS16470_IMU; import edu.wpi.first.wpilibj.ADIS16470_IMU.IMUAxis; diff --git a/swervelib/imu/ADXRS450Swerve.java b/swervelib/imu/ADXRS450Swerve.java index 033ad3e..d2be03f 100644 --- a/swervelib/imu/ADXRS450Swerve.java +++ b/swervelib/imu/ADXRS450Swerve.java @@ -4,7 +4,6 @@ import static edu.wpi.first.units.Units.DegreesPerSecond; import edu.wpi.first.math.geometry.Rotation3d; import edu.wpi.first.math.geometry.Translation3d; -import edu.wpi.first.units.measure.AngularVelocity; import edu.wpi.first.units.measure.MutAngularVelocity; import edu.wpi.first.wpilibj.ADXRS450_Gyro; import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard; diff --git a/swervelib/imu/AnalogGyroSwerve.java b/swervelib/imu/AnalogGyroSwerve.java index a3afaf1..168c365 100644 --- a/swervelib/imu/AnalogGyroSwerve.java +++ b/swervelib/imu/AnalogGyroSwerve.java @@ -4,7 +4,6 @@ import static edu.wpi.first.units.Units.DegreesPerSecond; import edu.wpi.first.math.geometry.Rotation3d; import edu.wpi.first.math.geometry.Translation3d; -import edu.wpi.first.units.measure.AngularVelocity; import edu.wpi.first.units.measure.MutAngularVelocity; import edu.wpi.first.wpilibj.AnalogGyro; import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard; diff --git a/swervelib/imu/CanandgyroSwerve.java b/swervelib/imu/CanandgyroSwerve.java index 5d5f67a..357dc0c 100644 --- a/swervelib/imu/CanandgyroSwerve.java +++ b/swervelib/imu/CanandgyroSwerve.java @@ -5,8 +5,8 @@ import static edu.wpi.first.units.Units.RotationsPerSecond; import com.reduxrobotics.sensors.canandgyro.Canandgyro; import edu.wpi.first.math.geometry.Rotation3d; import edu.wpi.first.math.geometry.Translation3d; -import edu.wpi.first.units.measure.AngularVelocity; import edu.wpi.first.units.measure.MutAngularVelocity; + import java.util.Optional; /** diff --git a/swervelib/imu/NavXSwerve.java b/swervelib/imu/NavXSwerve.java index a99450e..64f97d0 100644 --- a/swervelib/imu/NavXSwerve.java +++ b/swervelib/imu/NavXSwerve.java @@ -6,7 +6,6 @@ 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.units.measure.AngularVelocity; import edu.wpi.first.units.measure.MutAngularVelocity; import edu.wpi.first.wpilibj.Alert; import edu.wpi.first.wpilibj.Alert.AlertType; diff --git a/swervelib/imu/Pigeon2Swerve.java b/swervelib/imu/Pigeon2Swerve.java index c471776..752a3b9 100644 --- a/swervelib/imu/Pigeon2Swerve.java +++ b/swervelib/imu/Pigeon2Swerve.java @@ -8,7 +8,6 @@ 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.AngularVelocity; import edu.wpi.first.units.measure.LinearAcceleration; import edu.wpi.first.units.measure.MutAngularVelocity; import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard; diff --git a/swervelib/imu/PigeonSwerve.java b/swervelib/imu/PigeonSwerve.java index 12ccf78..81eb2da 100644 --- a/swervelib/imu/PigeonSwerve.java +++ b/swervelib/imu/PigeonSwerve.java @@ -6,7 +6,6 @@ import com.ctre.phoenix.sensors.WPI_PigeonIMU; import edu.wpi.first.math.geometry.Quaternion; import edu.wpi.first.math.geometry.Rotation3d; import edu.wpi.first.math.geometry.Translation3d; -import edu.wpi.first.units.measure.AngularVelocity; import edu.wpi.first.units.measure.MutAngularVelocity; import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard; import java.util.Optional; diff --git a/swervelib/imu/SwerveIMU.java b/swervelib/imu/SwerveIMU.java index 2b23036..3d58d7d 100644 --- a/swervelib/imu/SwerveIMU.java +++ b/swervelib/imu/SwerveIMU.java @@ -2,8 +2,8 @@ package swervelib.imu; import edu.wpi.first.math.geometry.Rotation3d; import edu.wpi.first.math.geometry.Translation3d; -import edu.wpi.first.units.measure.AngularVelocity; import edu.wpi.first.units.measure.MutAngularVelocity; + import java.util.Optional; /** @@ -59,9 +59,9 @@ public abstract class SwerveIMU public abstract Optional getAccel(); /** - * Fetch the rotation rate from the IMU as {@link AngularVelocity} + * Fetch the rotation rate from the IMU as {@link MutAngularVelocity} * - * @return {@link AngularVelocity} of the rotation rate. + * @return {@link MutAngularVelocity} of the rotation rate. */ public abstract MutAngularVelocity getYawAngularVelocity(); diff --git a/swervelib/math/SwerveMath.java b/swervelib/math/SwerveMath.java index eea10e4..7fa8f71 100644 --- a/swervelib/math/SwerveMath.java +++ b/swervelib/math/SwerveMath.java @@ -399,6 +399,10 @@ public class SwerveMath */ public static Translation2d cubeTranslation(Translation2d translation) { + if (Math.hypot(translation.getX(), translation.getY()) <= 1.0E-6) + { + return translation; + } return new Translation2d(Math.pow(translation.getNorm(), 3), translation.getAngle()); } diff --git a/swervelib/motors/SparkFlexSwerve.java b/swervelib/motors/SparkFlexSwerve.java index ecbcf26..eab749f 100644 --- a/swervelib/motors/SparkFlexSwerve.java +++ b/swervelib/motors/SparkFlexSwerve.java @@ -1,5 +1,6 @@ package swervelib.motors; +import static edu.wpi.first.units.Units.Milliseconds; import static edu.wpi.first.units.Units.Seconds; import com.revrobotics.AbsoluteEncoder; @@ -8,6 +9,7 @@ import com.revrobotics.RelativeEncoder; import com.revrobotics.spark.SparkBase.ControlType; import com.revrobotics.spark.SparkBase.PersistMode; import com.revrobotics.spark.SparkBase.ResetMode; +import com.revrobotics.spark.ClosedLoopSlot; import com.revrobotics.spark.SparkClosedLoopController; import com.revrobotics.spark.SparkFlex; import com.revrobotics.spark.SparkLowLevel.MotorType; @@ -16,7 +18,6 @@ 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.DriverStation; @@ -41,7 +42,7 @@ public class SparkFlexSwerve extends SwerveMotor */ public RelativeEncoder encoder; /** - * Absolute encoder attached to the SparkMax (if exists) + * Absolute encoder attached to the SparkFlex (if exists) */ public SwerveAbsoluteEncoder absoluteEncoder; /** @@ -72,14 +73,6 @@ public class SparkFlexSwerve extends SwerveMotor * Configuration object for {@link SparkFlex} motor. */ private SparkFlexConfig cfg = new SparkFlexConfig(); - /** - * Tracker for changes that need to be pushed. - */ - private boolean cfgUpdated = false; - /** - * After the first post-module config update there will be an error thrown to alert to a possible issue. - */ - private boolean startupInitialized = false; /** @@ -99,10 +92,9 @@ public class SparkFlexSwerve extends SwerveMotor encoder = motor.getEncoder(); 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. + // configureSparkFlex(() -> motor.setCANTimeout(0)); // Commented out because it prevents feedback. failureConfiguring = new Alert("Motors", "Failure configuring motor " + motor.getDeviceId(), @@ -118,7 +110,7 @@ public class SparkFlexSwerve extends SwerveMotor /** * Initialize the {@link SwerveMotor} as a {@link SparkFlex} connected to a Brushless Motor. * - * @param id CAN ID of the SparkMax. + * @param id CAN ID of the SparkFlex. * @param isDriveMotor Is the motor being initialized a drive motor? * @param motorType {@link DCMotor} which the {@link SparkFlex} is attached to. */ @@ -140,7 +132,7 @@ public class SparkFlexSwerve extends SwerveMotor { return; } - Timer.delay(Units.Milliseconds.of(5).in(Seconds)); + Timer.delay(Milliseconds.of(5).in(Seconds)); } failureConfiguring.set(true); } @@ -148,7 +140,7 @@ public class SparkFlexSwerve extends SwerveMotor /** * Get the current configuration of the {@link SparkFlex} * - * @return {@link SparkMaxConfig} + * @return {@link SparkFlexConfig} */ public SparkFlexConfig getConfig() { @@ -162,9 +154,12 @@ public class SparkFlexSwerve extends SwerveMotor */ public void updateConfig(SparkFlexConfig cfgGiven) { + if (!DriverStation.isDisabled()) + { + throw new RuntimeException("Configuration changes cannot be applied while the robot is enabled."); + } cfg.apply(cfgGiven); configureSparkFlex(() -> motor.configure(cfg, ResetMode.kNoResetSafeParameters, PersistMode.kPersistParameters)); - cfgUpdated = false; } /** @@ -176,7 +171,6 @@ public class SparkFlexSwerve extends SwerveMotor public void setVoltageCompensation(double nominalVoltage) { cfg.voltageCompensation(nominalVoltage); - cfgUpdated = true; } /** @@ -190,7 +184,6 @@ public class SparkFlexSwerve extends SwerveMotor { cfg.smartCurrentLimit(currentLimit); - cfgUpdated = true; } /** @@ -203,7 +196,6 @@ public class SparkFlexSwerve extends SwerveMotor { cfg.closedLoopRampRate(rampRate) .openLoopRampRate(rampRate); - cfgUpdated = true; } /** @@ -274,14 +266,12 @@ public class SparkFlexSwerve extends SwerveMotor { absoluteEncoder = null; 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; @@ -361,7 +351,6 @@ public class SparkFlexSwerve extends SwerveMotor .velocityConversionFactor(positionConversionFactor / 60); } } - cfgUpdated = true; } @@ -376,7 +365,6 @@ public class SparkFlexSwerve extends SwerveMotor cfg.closedLoop.pidf(config.p, config.i, config.d, config.f) .iZone(config.iz) .outputRange(config.output.min, config.output.max); - cfgUpdated = true; } /** @@ -391,7 +379,6 @@ public class SparkFlexSwerve extends SwerveMotor cfg.closedLoop .positionWrappingEnabled(true) .positionWrappingInputRange(minInput, maxInput); - cfgUpdated = true; } @@ -404,7 +391,6 @@ public class SparkFlexSwerve extends SwerveMotor public void setMotorBrake(boolean isBrakeMode) { cfg.idleMode(isBrakeMode ? IdleMode.kBrake : IdleMode.kCoast); - cfgUpdated = true; } /** @@ -416,7 +402,6 @@ public class SparkFlexSwerve extends SwerveMotor public void setInverted(boolean inverted) { cfg.inverted(inverted); - cfgUpdated = true; } /** @@ -425,10 +410,13 @@ public class SparkFlexSwerve extends SwerveMotor @Override public void burnFlash() { + if (!DriverStation.isDisabled()) + { + throw new RuntimeException("Config updates cannot be applied while the robot is Enabled!"); + } configureSparkFlex(() -> { return motor.configure(cfg, ResetMode.kNoResetSafeParameters, PersistMode.kPersistParameters); }); - cfgUpdated = false; } /** @@ -451,20 +439,6 @@ public class SparkFlexSwerve extends SwerveMotor @Override public void setReference(double setpoint, double feedforward) { - int pidSlot = 0; - - if (cfgUpdated) - { - burnFlash(); - Timer.delay(0.01); // Give 10ms to apply changes - if (startupInitialized) - { - DriverStation.reportWarning("Applying changes mid-execution not recommended.", true); - } else - { - startupInitialized = true; - } - } if (isDriveMotor) { @@ -472,7 +446,7 @@ public class SparkFlexSwerve extends SwerveMotor pid.setReference( setpoint, ControlType.kVelocity, - pidSlot, + ClosedLoopSlot.kSlot0, feedforward)); } else { @@ -480,7 +454,7 @@ public class SparkFlexSwerve extends SwerveMotor pid.setReference( setpoint, ControlType.kPosition, - pidSlot, + ClosedLoopSlot.kSlot0, feedforward)); if (SwerveDriveTelemetry.isSimulation) { diff --git a/swervelib/motors/SparkMaxBrushedMotorSwerve.java b/swervelib/motors/SparkMaxBrushedMotorSwerve.java index 26fb76c..db3ea10 100644 --- a/swervelib/motors/SparkMaxBrushedMotorSwerve.java +++ b/swervelib/motors/SparkMaxBrushedMotorSwerve.java @@ -1,5 +1,6 @@ package swervelib.motors; +import static edu.wpi.first.units.Units.Milliseconds; import static edu.wpi.first.units.Units.Seconds; import com.revrobotics.AbsoluteEncoder; @@ -8,6 +9,7 @@ import com.revrobotics.RelativeEncoder; import com.revrobotics.spark.SparkBase.ControlType; import com.revrobotics.spark.SparkBase.PersistMode; import com.revrobotics.spark.SparkBase.ResetMode; +import com.revrobotics.spark.ClosedLoopSlot; import com.revrobotics.spark.SparkClosedLoopController; import com.revrobotics.spark.SparkLowLevel.MotorType; import com.revrobotics.spark.SparkMax; @@ -15,7 +17,6 @@ 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; @@ -78,14 +79,6 @@ public class SparkMaxBrushedMotorSwerve extends SwerveMotor * Configuration object for {@link SparkMax} motor. */ private SparkMaxConfig cfg = new SparkMaxConfig(); - /** - * Tracker for changes that need to be pushed. - */ - private boolean cfgUpdated = false; - /** - * After the first post-module config update there will be an error thrown to alert to a possible issue. - */ - private boolean startupInitialized = false; /** * Initialize the swerve motor. @@ -152,7 +145,6 @@ public class SparkMaxBrushedMotorSwerve extends SwerveMotor // Configure feedback of the PID controller as the integrated encoder. cfg.closedLoop.feedbackSensor(FeedbackSensor.kPrimaryEncoder); } - cfgUpdated = true; } encoder.ifPresentOrElse((RelativeEncoder enc) -> { velocity = enc::getVelocity; @@ -195,7 +187,7 @@ public class SparkMaxBrushedMotorSwerve extends SwerveMotor { return; } - Timer.delay(Units.Milliseconds.of(5).in(Seconds)); + Timer.delay(Milliseconds.of(5).in(Seconds)); } failureConfiguringAlert.set(true); } @@ -217,9 +209,12 @@ public class SparkMaxBrushedMotorSwerve extends SwerveMotor */ public void updateConfig(SparkMaxConfig cfgGiven) { + if (!DriverStation.isDisabled()) + { + throw new RuntimeException("Configuration changes cannot be applied while the robot is enabled."); + } cfg.apply(cfgGiven); configureSparkMax(() -> motor.configure(cfg, ResetMode.kNoResetSafeParameters, PersistMode.kPersistParameters)); - cfgUpdated = false; } /** @@ -231,7 +226,6 @@ public class SparkMaxBrushedMotorSwerve extends SwerveMotor public void setVoltageCompensation(double nominalVoltage) { cfg.voltageCompensation(nominalVoltage); - cfgUpdated = true; } /** @@ -244,7 +238,6 @@ public class SparkMaxBrushedMotorSwerve extends SwerveMotor public void setCurrentLimit(int currentLimit) { cfg.smartCurrentLimit(currentLimit); - cfgUpdated = true; } /** @@ -257,7 +250,6 @@ public class SparkMaxBrushedMotorSwerve extends SwerveMotor { cfg.closedLoopRampRate(rampRate) .openLoopRampRate(rampRate); - cfgUpdated = true; } /** @@ -328,7 +320,6 @@ public class SparkMaxBrushedMotorSwerve extends SwerveMotor { absoluteEncoder = null; cfg.closedLoop.feedbackSensor(FeedbackSensor.kPrimaryEncoder); - cfgUpdated = true; this.encoder.ifPresentOrElse((RelativeEncoder enc) -> { velocity = enc::getVelocity; @@ -341,7 +332,6 @@ public class SparkMaxBrushedMotorSwerve extends SwerveMotor { 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" + @@ -440,7 +430,6 @@ public class SparkMaxBrushedMotorSwerve extends SwerveMotor .velocityConversionFactor(positionConversionFactor / 60); } } - cfgUpdated = true; } /** @@ -454,7 +443,6 @@ public class SparkMaxBrushedMotorSwerve extends SwerveMotor cfg.closedLoop.pidf(config.p, config.i, config.d, config.f) .iZone(config.iz) .outputRange(config.output.min, config.output.max); - cfgUpdated = true; } /** @@ -469,7 +457,6 @@ public class SparkMaxBrushedMotorSwerve extends SwerveMotor cfg.closedLoop .positionWrappingEnabled(true) .positionWrappingInputRange(minInput, maxInput); - cfgUpdated = true; } @@ -482,7 +469,6 @@ public class SparkMaxBrushedMotorSwerve extends SwerveMotor public void setMotorBrake(boolean isBrakeMode) { cfg.idleMode(isBrakeMode ? IdleMode.kBrake : IdleMode.kCoast); - cfgUpdated = true; } /** @@ -494,7 +480,6 @@ public class SparkMaxBrushedMotorSwerve extends SwerveMotor public void setInverted(boolean inverted) { cfg.inverted(inverted); - cfgUpdated = true; } /** @@ -503,10 +488,13 @@ public class SparkMaxBrushedMotorSwerve extends SwerveMotor @Override public void burnFlash() { + if (!DriverStation.isDisabled()) + { + throw new RuntimeException("Config updates cannot be applied while the robot is Enabled!"); + } configureSparkMax(() -> { return motor.configure(cfg, ResetMode.kNoResetSafeParameters, PersistMode.kPersistParameters); }); - cfgUpdated = false; } /** @@ -531,26 +519,13 @@ public class SparkMaxBrushedMotorSwerve extends SwerveMotor { int pidSlot = 0; - if (cfgUpdated) - { - burnFlash(); - Timer.delay(0.01); // Give 10ms to apply changes - if (startupInitialized) - { - DriverStation.reportWarning("Applying changes mid-execution not recommended.", true); - } else - { - startupInitialized = true; - } - } - if (isDriveMotor) { configureSparkMax(() -> pid.setReference( setpoint, ControlType.kVelocity, - pidSlot, + ClosedLoopSlot.kSlot0, feedforward)); } else { @@ -558,7 +533,7 @@ public class SparkMaxBrushedMotorSwerve extends SwerveMotor pid.setReference( setpoint, ControlType.kPosition, - pidSlot, + ClosedLoopSlot.kSlot0, feedforward)); if (SwerveDriveTelemetry.isSimulation) { diff --git a/swervelib/motors/SparkMaxSwerve.java b/swervelib/motors/SparkMaxSwerve.java index 4b2955b..5cf8515 100644 --- a/swervelib/motors/SparkMaxSwerve.java +++ b/swervelib/motors/SparkMaxSwerve.java @@ -1,10 +1,12 @@ package swervelib.motors; +import static edu.wpi.first.units.Units.Milliseconds; import static edu.wpi.first.units.Units.Seconds; import com.revrobotics.AbsoluteEncoder; import com.revrobotics.REVLibError; import com.revrobotics.RelativeEncoder; +import com.revrobotics.spark.ClosedLoopSlot; import com.revrobotics.spark.SparkBase.ControlType; import com.revrobotics.spark.SparkBase.PersistMode; import com.revrobotics.spark.SparkBase.ResetMode; @@ -15,7 +17,6 @@ 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; @@ -63,14 +64,6 @@ public class SparkMaxSwerve extends SwerveMotor * Configuration object for {@link SparkMax} motor. */ private SparkMaxConfig cfg = new SparkMaxConfig(); - /** - * Tracker for changes that need to be pushed. - */ - private boolean cfgUpdated = false; - /** - * After the first post-module config update there will be an error thrown to alert to a possible issue. - */ - private boolean startupInitialized = false; /** @@ -92,7 +85,6 @@ public class SparkMaxSwerve extends SwerveMotor 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; @@ -126,7 +118,7 @@ public class SparkMaxSwerve extends SwerveMotor { return; } - Timer.delay(Units.Milliseconds.of(5).in(Seconds)); + Timer.delay(Milliseconds.of(5).in(Seconds)); } DriverStation.reportWarning("Failure configuring motor " + motor.getDeviceId(), true); } @@ -148,9 +140,12 @@ public class SparkMaxSwerve extends SwerveMotor */ public void updateConfig(SparkMaxConfig cfgGiven) { + if (!DriverStation.isDisabled()) + { + throw new RuntimeException("Configuration changes cannot be applied while the robot is enabled."); + } cfg.apply(cfgGiven); configureSparkMax(() -> motor.configure(cfg, ResetMode.kNoResetSafeParameters, PersistMode.kPersistParameters)); - cfgUpdated = false; } /** @@ -162,7 +157,6 @@ public class SparkMaxSwerve extends SwerveMotor public void setVoltageCompensation(double nominalVoltage) { cfg.voltageCompensation(nominalVoltage); - cfgUpdated = true; } /** @@ -175,7 +169,6 @@ public class SparkMaxSwerve extends SwerveMotor public void setCurrentLimit(int currentLimit) { cfg.smartCurrentLimit(currentLimit); - cfgUpdated = true; } @@ -189,7 +182,6 @@ public class SparkMaxSwerve extends SwerveMotor { cfg.closedLoopRampRate(rampRate) .openLoopRampRate(rampRate); - cfgUpdated = true; } @@ -261,7 +253,6 @@ public class SparkMaxSwerve extends SwerveMotor { absoluteEncoder = null; cfg.closedLoop.feedbackSensor(FeedbackSensor.kPrimaryEncoder); - cfgUpdated = true; velocity = this.encoder::getVelocity; position = this.encoder::getPosition; @@ -270,7 +261,6 @@ public class SparkMaxSwerve extends SwerveMotor { 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" + @@ -365,7 +355,6 @@ public class SparkMaxSwerve extends SwerveMotor .velocityConversionFactor(positionConversionFactor / 60); } } - cfgUpdated = true; } @@ -380,7 +369,6 @@ public class SparkMaxSwerve extends SwerveMotor cfg.closedLoop.pidf(config.p, config.i, config.d, config.f) .iZone(config.iz) .outputRange(config.output.min, config.output.max); - cfgUpdated = true; } @@ -396,7 +384,6 @@ public class SparkMaxSwerve extends SwerveMotor cfg.closedLoop .positionWrappingEnabled(true) .positionWrappingInputRange(minInput, maxInput); - cfgUpdated = true; } @@ -409,7 +396,6 @@ public class SparkMaxSwerve extends SwerveMotor public void setMotorBrake(boolean isBrakeMode) { cfg.idleMode(isBrakeMode ? IdleMode.kBrake : IdleMode.kCoast); - cfgUpdated = true; } @@ -422,7 +408,6 @@ public class SparkMaxSwerve extends SwerveMotor public void setInverted(boolean inverted) { cfg.inverted(inverted); - cfgUpdated = true; } /** @@ -431,10 +416,13 @@ public class SparkMaxSwerve extends SwerveMotor @Override public void burnFlash() { + if (!DriverStation.isDisabled()) + { + throw new RuntimeException("Config updates cannot be applied while the robot is Enabled!"); + } configureSparkMax(() -> { return motor.configure(cfg, ResetMode.kNoResetSafeParameters, PersistMode.kPersistParameters); }); - cfgUpdated = false; } /** @@ -459,26 +447,13 @@ public class SparkMaxSwerve extends SwerveMotor { int pidSlot = 0; - if (cfgUpdated) - { - burnFlash(); - Timer.delay(0.01); // Give 10ms to apply changes - if (startupInitialized) - { - DriverStation.reportWarning("Applying changes mid-execution not recommended.", true); - } else - { - startupInitialized = true; - } - } - if (isDriveMotor) { configureSparkMax(() -> pid.setReference( setpoint, ControlType.kVelocity, - pidSlot, + ClosedLoopSlot.kSlot0, feedforward)); } else { @@ -486,7 +461,7 @@ public class SparkMaxSwerve extends SwerveMotor pid.setReference( setpoint, ControlType.kPosition, - pidSlot, + ClosedLoopSlot.kSlot0, feedforward)); if (SwerveDriveTelemetry.isSimulation) { diff --git a/swervelib/motors/TalonFXSwerve.java b/swervelib/motors/TalonFXSwerve.java index 73393ce..7d3b98a 100644 --- a/swervelib/motors/TalonFXSwerve.java +++ b/swervelib/motors/TalonFXSwerve.java @@ -189,9 +189,6 @@ public class TalonFXSwerve extends SwerveMotor .withSensorToMechanismRatio(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); } /** diff --git a/swervelib/motors/TalonSRXSwerve.java b/swervelib/motors/TalonSRXSwerve.java index 5c8f690..00958e8 100644 --- a/swervelib/motors/TalonSRXSwerve.java +++ b/swervelib/motors/TalonSRXSwerve.java @@ -7,11 +7,11 @@ import com.ctre.phoenix.motorcontrol.NeutralMode; 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; +import swervelib.parser.json.modules.ConversionFactorsJson; import swervelib.telemetry.SwerveDriveTelemetry; /** @@ -25,7 +25,7 @@ public class TalonSRXSwerve extends SwerveMotor */ private final boolean factoryDefaultOccurred = false; /** - * Current TalonFX configuration. + * Current TalonSRX configuration. */ private final TalonSRXConfiguration configuration = new TalonSRXConfiguration(); /** @@ -41,7 +41,11 @@ public class TalonSRXSwerve extends SwerveMotor */ private double positionConversionFactor = 1; /** - * If the TalonFX configuration has changed. + * Module Conversion factors to use. + */ + private ConversionFactorsJson moduleConversionFactors; + /** + * If the TalonSRX configuration has changed. */ private boolean configChanged = true; /** @@ -73,7 +77,7 @@ public class TalonSRXSwerve extends SwerveMotor * * @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. + * @param motorType {@link DCMotor} which the {@link WPI_TalonSRX} is attached to. */ public TalonSRXSwerve(int id, boolean isDriveMotor, DCMotor motorType) { @@ -468,4 +472,4 @@ public class TalonSRXSwerve extends SwerveMotor { return absoluteEncoder; } -} +} \ No newline at end of file diff --git a/swervelib/motors/ThriftyNovaSwerve.java b/swervelib/motors/ThriftyNovaSwerve.java new file mode 100644 index 0000000..17b6a75 --- /dev/null +++ b/swervelib/motors/ThriftyNovaSwerve.java @@ -0,0 +1,448 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +package swervelib.motors; + +import com.thethriftybot.Conversion; +import com.thethriftybot.Conversion.PositionUnit; +import com.thethriftybot.Conversion.VelocityUnit; +import com.thethriftybot.ThriftyNova; +import com.thethriftybot.ThriftyNova.CurrentType; +import com.thethriftybot.ThriftyNova.EncoderType; +import com.thethriftybot.ThriftyNova.PIDSlot; +import edu.wpi.first.math.controller.PIDController; +import edu.wpi.first.math.system.plant.DCMotor; +import edu.wpi.first.wpilibj.DataLogManager; +import edu.wpi.first.wpilibj.DriverStation; +import edu.wpi.first.wpilibj.RobotController; +import java.util.List; +import swervelib.encoders.SwerveAbsoluteEncoder; +import swervelib.parser.PIDFConfig; + +/** + * An implementation of {@link ThriftyNova} as a {@link SwerveMotor}. + */ +public class ThriftyNovaSwerve extends SwerveMotor +{ + + /** + * ThriftyNova Instance. + */ + private ThriftyNova motor; + /** + * The Encoder type being used + */ + private EncoderType encoderType; + /** + * Closed-loop PID controller. + */ + public PIDController pid; + /** + * Factory default already occurred. + */ + private boolean factoryDefaultOccurred = false; + /** + * Position conversion object for the motor encoder + */ + private Conversion positionConversion; + /** + * Velocity conversion object for the motor encoder + */ + private Conversion velocityConversion; + /** + * The position conversion factor for the encoder + */ + private double positionConversionFactor = 1.0; + /** + * The position conversion factor for the encoder + */ + private double velocityConversionFactor = 1.0 / 60.0; + /** + * {@link DCMotor} for simulation and calculations. + */ + private final DCMotor simMotor; + + /** + * Initialize the swerve motor. + * + * @param motor The SwerveMotor as a ThriftyNova object. + * @param isDriveMotor Is the motor being initialized a drive motor? + * @param motorType {@link DCMotor} controlled by the {@link ThriftyNova} + */ + public ThriftyNovaSwerve(ThriftyNova motor, boolean isDriveMotor, DCMotor motorType) + { + this.motor = motor; + this.isDriveMotor = isDriveMotor; + this.simMotor = motorType; + factoryDefaults(); + clearStickyFaults(); + + motor.usePIDSlot(PIDSlot.SLOT0); + pid = new PIDController(0, 0, 0); + motor.pid0.setPID(pid); + + if (isDriveMotor) + { + positionConversion = new Conversion(PositionUnit.ROTATIONS, EncoderType.INTERNAL); + velocityConversion = new Conversion(VelocityUnit.ROTATIONS_PER_SEC, EncoderType.INTERNAL); + } else + { + positionConversion = new Conversion(PositionUnit.DEGREES, EncoderType.INTERNAL); + velocityConversion = new Conversion(VelocityUnit.DEGREES_PER_SEC, EncoderType.INTERNAL); + } + } + + /** + * Initialize the {@link SwerveMotor} as a {@link ThriftyNova} connected to a Brushless Motor. + * + * @param id CAN ID of the ThriftyNova. + * @param isDriveMotor Is the motor being initialized a drive motor? + * @param motor {@link DCMotor} controlled by the {@link ThriftyNova} + */ + public ThriftyNovaSwerve(int id, boolean isDriveMotor, DCMotor motor) + { + this(new ThriftyNova(id), isDriveMotor, motor); + } + + /** + * Set factory defaults on the motor controller. + */ + @Override + public void factoryDefaults() + { + // Factory defaults from https://docs.thethriftybot.com/thrifty-nova/gqCPUYXcVoOZ4KW3DqIr/software-resources/configure-controller-settings/factory-default + if (!factoryDefaultOccurred) + { + motor.setInverted(false); + motor.setBrakeMode(false); + setCurrentLimit(40); + motor.setEncoderPosition(0); + motor.setMaxOutput(1.0); + motor.setRampDown(100); + motor.setRampUp(100); + configureCANStatusFrames(0.25, 0.1, 0.25, 0.5, 0.50); + motor.setSoftLimits(0, 0); + configurePIDF(new PIDFConfig()); + motor.pid1.setP(0) + .setI(0) + .setD(0) + .setFF(0.0); + DriverStation.reportWarning("Factory defaults not implemented for ThriftyNovaSwerve", true); + factoryDefaultOccurred = true; + } + } + + /** + * Clear the sticky faults on the motor controller. + */ + @Override + public void clearStickyFaults() + { + motor.clearErrors(); + } + + /** + * Set the absolute encoder to be a compatible absolute encoder. + * + * @param encoder The encoder to use. + * @return The {@link SwerveMotor} for easy instantiation. + */ + @Override + public SwerveMotor setAbsoluteEncoder(SwerveAbsoluteEncoder encoder) + { + if (isDriveMotor) + { + positionConversion = new Conversion(PositionUnit.ROTATIONS, EncoderType.ABS); + velocityConversion = new Conversion(VelocityUnit.ROTATIONS_PER_SEC, EncoderType.ABS); + } else + { + positionConversion = new Conversion(PositionUnit.DEGREES, EncoderType.ABS); + velocityConversion = new Conversion(VelocityUnit.DEGREES_PER_SEC, EncoderType.ABS); + } + motor.useEncoderType(EncoderType.ABS); + return this; + } + + @Override + public void configureIntegratedEncoder(double positionConversionFactor) + { + motor.useEncoderType(EncoderType.INTERNAL); + if (isDriveMotor) + { + positionConversion = new Conversion(PositionUnit.ROTATIONS, EncoderType.INTERNAL); + velocityConversion = new Conversion(VelocityUnit.ROTATIONS_PER_SEC, EncoderType.INTERNAL); + } else + { + positionConversion = new Conversion(PositionUnit.DEGREES, EncoderType.INTERNAL); + velocityConversion = new Conversion(VelocityUnit.DEGREES_PER_SEC, EncoderType.INTERNAL); + } + this.positionConversionFactor = positionConversionFactor; + this.velocityConversionFactor = positionConversionFactor / 60.0; + configureCANStatusFrames(0.25, 0.01, 0.01, 0.02, 0.20); + } + + /** + * Set the CAN status frames. + * + * @param fault Fault transmission rate + * @param sensor Sensor transmission rate + * @param quadSensor External quad encoder transmission rate + * @param control Control frame transmission rate + * @param current Current feedback transmission rate + */ + public void configureCANStatusFrames( + double fault, double sensor, double quadSensor, double control, double current) + { + motor.canFreq.setFault(fault); + motor.canFreq.setSensor(sensor); + motor.canFreq.setQuadSensor(quadSensor); + motor.canFreq.setControl(control); + motor.canFreq.setCurrent(current); + checkErrors("Configuring CAN status frames failed: "); + } + + /** + * Configure the PIDF values for the closed loop controller. 0 is disabled or off. + * + * @param config Configuration class holding the PIDF values. + */ + @Override + public void configurePIDF(PIDFConfig config) + { + motor.pid0.setP(config.p).setI(config.i).setD(config.d); + checkErrors("Configuring PIDF failed: "); + } + + /** + * Configure the PID wrapping for the position closed loop controller. + * + * @param minInput Minimum PID input. + * @param maxInput Maximum PID input. + */ + @Override + public void configurePIDWrapping(double minInput, double maxInput) + { + // Do nothing + } + + /** + * Set the idle mode. + * + * @param isBrakeMode Set the brake mode. + */ + @Override + public void setMotorBrake(boolean isBrakeMode) + { + motor.setBrakeMode(isBrakeMode); + checkErrors("Setting motor brake mode failed: "); + } + + /** + * Set the motor to be inverted. + * + * @param inverted State of inversion. + */ + @Override + public void setInverted(boolean inverted) + { + motor.setInverted(inverted); + checkErrors("Setting motor inversion failed: "); + } + + /** + * Save the configurations from flash to EEPROM. + */ + @Override + public void burnFlash() + { + // Do nothing + } + + /** + * Set the percentage output. + * + * @param percentOutput percent out for the motor controller. + */ + @Override + public void set(double percentOutput) + { + motor.setPercent(percentOutput); + } + + /** + * Set the closed loop PID controller reference point. + * + * @param setpoint Setpoint in MPS or Angle in degrees. + * @param feedforward Feedforward in volt-meter-per-second or kV. + */ + @Override + public void setReference(double setpoint, double feedforward) + { + setReference(setpoint, feedforward, getPosition()); + } + + /** + * Set the closed loop PID controller reference point. + * + * @param setpoint Setpoint in meters per second or angle in degrees. + * @param feedforward Feedforward in volt-meter-per-second or kV. + * @param position Only used on the angle motor, the position of the motor in degrees. + */ + @Override + public void setReference(double setpoint, double feedforward, double position) + { + if (isDriveMotor) + { + motor.pid0.setFF(feedforward); + motor.setVelocity(setpoint); + } else + { + motor.setPosition(setpoint / 360.0); + } + } + + /** + * Get the voltage output of the motor controller. + * + * @return Voltage output. + */ + @Override + public double getVoltage() + { + throw new UnsupportedOperationException("Unimplemented method 'getVoltage'"); + } + + /** + * Set the voltage of the motor. + * + * @param voltage Voltage to set. + */ + @Override + public void setVoltage(double voltage) + { + motor.setPercent(voltage / RobotController.getBatteryVoltage()); + } + + /** + * Get the voltage output of the motor controller. + * + * @return Voltage output. + */ + @Override + public double getAppliedOutput() + { + return motor.getStatorCurrent(); + } + + /** + * Get the velocity of the integrated encoder. + * + * @return velocity in Meters Per Second, or Degrees per Second. + */ + @Override + public double getVelocity() + { + return velocityConversion.fromMotor(motor.getVelocity()) * velocityConversionFactor; + } + + /** + * Get the position of the integrated encoder. + * + * @return Position in Meters or Degrees. + */ + @Override + public double getPosition() + { + return positionConversion.fromMotor(motor.getPosition()) * positionConversionFactor; + } + + /** + * Set the integrated encoder position. + * + * @param position Integrated encoder position. Should be angle in degrees or meters. + */ + @Override + public void setPosition(double position) + { + motor.setEncoderPosition(positionConversion.toMotor(position)); + } + + @Override + public void setVoltageCompensation(double nominalVoltage) + { + } + + /** + * Set the current limit for the swerve drive motor, remember this may cause jumping if used in conjunction with + * voltage compensation. This is useful to protect the motor from current spikes. + * + * @param currentLimit Current limit in AMPS at free speed. + */ + @Override + public void setCurrentLimit(int currentLimit) + { + motor.setMaxCurrent(CurrentType.STATOR, currentLimit); + checkErrors("Setting current limit failed: "); + } + + /** + * Set the maximum rate the open/closed loop output can change by. + * + * @param rampRate Time in seconds to go from 0 to full throttle. + */ + @Override + public void setLoopRampRate(double rampRate) + { + motor.setRampUp(rampRate); + motor.setRampDown(rampRate); + checkErrors("Setting loop ramp rate failed: "); + } + + /** + * Get the motor object from the module. + * + * @return Motor object. + */ + @Override + public Object getMotor() + { + return motor; + } + + /** + * Queries whether the absolute encoder is directly attached to the motor controller. + * + * @return connected absolute encoder state. + */ + @Override + public boolean isAttachedAbsoluteEncoder() + { + return EncoderType.ABS == encoderType; + } + + /** + * Checks for errors in the motor and logs them if any are found. + * + * @param message the message to prepend to the log and print statement + */ + private void checkErrors(String message) + { + List errors = motor.getErrors(); + if (errors.size() > 0) + { + for (ThriftyNova.Error error : errors) + { + DataLogManager.log(this.getClass().getSimpleName() + ": " + message + error.toString()); + System.out.println(this.getClass().getSimpleName() + ": " + message + error.toString()); + } + } + + } + + @Override + public DCMotor getSimMotor() + { + return simMotor; + } +} diff --git a/swervelib/parser/json/DeviceJson.java b/swervelib/parser/json/DeviceJson.java index 1d6f339..7c10b6b 100644 --- a/swervelib/parser/json/DeviceJson.java +++ b/swervelib/parser/json/DeviceJson.java @@ -6,6 +6,7 @@ import static swervelib.telemetry.SwerveDriveTelemetry.serialCommsIssueWarning; 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 swervelib.encoders.AnalogAbsoluteEncoderSwerve; @@ -16,6 +17,7 @@ import swervelib.encoders.SparkMaxAnalogEncoderSwerve; import swervelib.encoders.SparkMaxEncoderSwerve; import swervelib.encoders.SwerveAbsoluteEncoder; import swervelib.encoders.TalonSRXEncoderSwerve; +import swervelib.encoders.ThriftyNovaEncoderSwerve; import swervelib.imu.ADIS16448Swerve; import swervelib.imu.ADIS16470Swerve; import swervelib.imu.ADXRS450Swerve; @@ -32,6 +34,7 @@ import swervelib.motors.SparkMaxSwerve; import swervelib.motors.SwerveMotor; import swervelib.motors.TalonFXSwerve; import swervelib.motors.TalonSRXSwerve; +import swervelib.motors.ThriftyNovaSwerve; /** * Device JSON parsed class. Used to access the JSON data. @@ -97,6 +100,8 @@ public class DeviceJson return new TalonSRXEncoderSwerve(motor, FeedbackDevice.PulseWidthEncodedPosition); case "talonsrx_analog": return new TalonSRXEncoderSwerve(motor, FeedbackDevice.Analog); + case "thrifty_nova": + return new ThriftyNovaEncoderSwerve(motor); default: throw new RuntimeException(type + " is not a recognized absolute encoder type."); } @@ -221,6 +226,10 @@ public class DeviceJson // 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, DCMotor.getCIM(1)); } + case "nova_neo": + return new ThriftyNovaSwerve(id, isDriveMotor, DCMotor.getNEO(1)); + case "nova_neo550": + return new ThriftyNovaSwerve(id, isDriveMotor, DCMotor.getNeo550(1)); 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 f26b2e8..7cb8821 100644 --- a/swervelib/parser/json/ModuleJson.java +++ b/swervelib/parser/json/ModuleJson.java @@ -4,7 +4,9 @@ import com.revrobotics.spark.SparkMax; import edu.wpi.first.math.util.Units; import swervelib.encoders.SparkMaxEncoderSwerve; import swervelib.encoders.SwerveAbsoluteEncoder; +import swervelib.encoders.ThriftyNovaEncoderSwerve; import swervelib.motors.SwerveMotor; +import swervelib.motors.ThriftyNovaSwerve; import swervelib.parser.PIDFConfig; import swervelib.parser.SwerveModuleConfiguration; import swervelib.parser.SwerveModulePhysicalCharacteristics; @@ -103,7 +105,10 @@ public class ModuleJson // Backwards compatibility, auto-optimization. if (conversionFactors.angle.factor == 360 && absEncoder != null && - absEncoder instanceof SparkMaxEncoderSwerve && angleMotor.getMotor() instanceof SparkMax) + (absEncoder instanceof SparkMaxEncoderSwerve && angleMotor.getMotor() instanceof SparkMax)) + { + angleMotor.setAbsoluteEncoder(absEncoder); + } else if ((absEncoder instanceof ThriftyNovaEncoderSwerve && angleMotor instanceof ThriftyNovaSwerve)) { angleMotor.setAbsoluteEncoder(absEncoder); } diff --git a/swervelib/parser/json/PhysicalPropertiesJson.java b/swervelib/parser/json/PhysicalPropertiesJson.java index eaab95d..758675d 100644 --- a/swervelib/parser/json/PhysicalPropertiesJson.java +++ b/swervelib/parser/json/PhysicalPropertiesJson.java @@ -1,6 +1,8 @@ package swervelib.parser.json; -import edu.wpi.first.units.Units; +import static edu.wpi.first.units.Units.Kilogram; +import static edu.wpi.first.units.Units.Pounds; + import edu.wpi.first.wpilibj.Alert; import edu.wpi.first.wpilibj.Alert.AlertType; import swervelib.parser.SwerveModulePhysicalCharacteristics; @@ -80,7 +82,7 @@ public class PhysicalPropertiesJson friction.drive, friction.angle, steerRotationalInertia, - Units.Pounds.of(robotMass).in(Units.Kilogram)); + Pounds.of(robotMass).in(Kilogram)); } } diff --git a/swervelib/simulation/SwerveModuleSimulation.java b/swervelib/simulation/SwerveModuleSimulation.java index 76b15ec..96fe383 100644 --- a/swervelib/simulation/SwerveModuleSimulation.java +++ b/swervelib/simulation/SwerveModuleSimulation.java @@ -20,6 +20,7 @@ public class SwerveModuleSimulation * Configure the maple sim module * * @param simModule the {@link org.ironmaple.simulation.drivesims.SwerveModuleSimulation} object for simulation + * @param physicalCharacteristics Physical characteristics of the swerve drive from the JSON or built. */ public void configureSimModule(org.ironmaple.simulation.drivesims.SwerveModuleSimulation simModule, SwerveModulePhysicalCharacteristics physicalCharacteristics) diff --git a/swervelib/telemetry/SwerveDriveTelemetry.java b/swervelib/telemetry/SwerveDriveTelemetry.java index 5a7c382..8bd5964 100644 --- a/swervelib/telemetry/SwerveDriveTelemetry.java +++ b/swervelib/telemetry/SwerveDriveTelemetry.java @@ -45,6 +45,8 @@ public class SwerveDriveTelemetry */ private static final DoublePublisher moduleCountPublisher = NetworkTableInstance.getDefault() + .getTable( + "SmartDashboard") .getDoubleTopic( "swerve/moduleCount") .publish(); @@ -53,6 +55,8 @@ public class SwerveDriveTelemetry */ private static final DoubleArrayPublisher measuredStatesArrayPublisher = NetworkTableInstance.getDefault() + .getTable( + "SmartDashboard") .getDoubleArrayTopic( "swerve/measuredStates") .publish(); @@ -61,6 +65,8 @@ public class SwerveDriveTelemetry */ private static final DoubleArrayPublisher desiredStatesArrayPublisher = NetworkTableInstance.getDefault() + .getTable( + "SmartDashboard") .getDoubleArrayTopic( "swerve/desiredStates") .publish(); @@ -69,6 +75,8 @@ public class SwerveDriveTelemetry */ private static final DoubleArrayPublisher measuredChassisSpeedsArrayPublisher = NetworkTableInstance.getDefault() + .getTable( + "SmartDashboard") .getDoubleArrayTopic( "swerve/measuredChassisSpeeds") .publish(); @@ -77,6 +85,8 @@ public class SwerveDriveTelemetry */ private static final DoubleArrayPublisher desiredChassisSpeedsArrayPublisher = NetworkTableInstance.getDefault() + .getTable( + "SmartDashboard") .getDoubleArrayTopic( "swerve/desiredChassisSpeeds") .publish(); @@ -85,6 +95,8 @@ public class SwerveDriveTelemetry */ private static final DoublePublisher robotRotationPublisher = NetworkTableInstance.getDefault() + .getTable( + "SmartDashboard") .getDoubleTopic( "swerve/robotRotation") .publish(); @@ -93,6 +105,8 @@ public class SwerveDriveTelemetry */ private static final DoublePublisher maxAngularVelocityPublisher = NetworkTableInstance.getDefault() + .getTable( + "SmartDashboard") .getDoubleTopic( "swerve/maxAngularVelocity") .publish(); @@ -101,6 +115,8 @@ public class SwerveDriveTelemetry */ private static final StructArrayPublisher measuredStatesStruct = NetworkTableInstance.getDefault() + .getTable( + "SmartDashboard") .getStructArrayTopic( "swerve/advantagescope/currentStates", SwerveModuleState.struct) @@ -110,6 +126,8 @@ public class SwerveDriveTelemetry */ private static final StructArrayPublisher desiredStatesStruct = NetworkTableInstance.getDefault() + .getTable( + "SmartDashboard") .getStructArrayTopic( "swerve/advantagescope/desiredStates", SwerveModuleState.struct) @@ -119,6 +137,8 @@ public class SwerveDriveTelemetry */ private static final StructPublisher measuredChassisSpeedsStruct = NetworkTableInstance.getDefault() + .getTable( + "SmartDashboard") .getStructTopic( "swerve/advantagescope/measuredChassisSpeeds", ChassisSpeeds.struct) @@ -128,6 +148,8 @@ public class SwerveDriveTelemetry */ private static final StructPublisher desiredChassisSpeedsStruct = NetworkTableInstance.getDefault() + .getTable( + "SmartDashboard") .getStructTopic( "swerve/advantagescope/desiredChassisSpeeds", ChassisSpeeds.struct) @@ -137,6 +159,8 @@ public class SwerveDriveTelemetry */ private static final StructPublisher robotRotationStruct = NetworkTableInstance.getDefault() + .getTable( + "SmartDashboard") .getStructTopic( "swerve/advantagescope/robotRotation", Rotation2d.struct) @@ -144,103 +168,125 @@ public class SwerveDriveTelemetry /** * Wheel locations array publisher for NT4. */ - private static final DoubleArrayPublisher wheelLocationsArrayPublisher = NetworkTableInstance.getDefault() - .getDoubleArrayTopic( - "swerve/wheelLocation") - .publish(); + private static final DoubleArrayPublisher wheelLocationsArrayPublisher + = NetworkTableInstance.getDefault() + .getTable( + "SmartDashboard") + .getDoubleArrayTopic( + "swerve/wheelLocation") + .publish(); /** * Max speed publisher for NT4. */ - private static final DoublePublisher maxSpeedPublisher = NetworkTableInstance.getDefault() - .getDoubleTopic( - "swerve/maxSpeed") - .publish(); + private static final DoublePublisher maxSpeedPublisher + = NetworkTableInstance.getDefault() + .getTable( + "SmartDashboard") + .getDoubleTopic( + "swerve/maxSpeed") + .publish(); /** * Rotation unit for NT4. */ - private static final StringPublisher rotationUnitPublisher = NetworkTableInstance.getDefault() - .getStringTopic( - "swerve/rotationUnit") - .publish(); + private static final StringPublisher rotationUnitPublisher + = NetworkTableInstance.getDefault() + .getTable( + "SmartDashboard") + .getStringTopic( + "swerve/rotationUnit") + .publish(); /** * Chassis width publisher */ - private static final DoublePublisher sizeLeftRightPublisher = NetworkTableInstance.getDefault() - .getDoubleTopic( - "swerve/sizeLeftRight") - .publish(); + private static final DoublePublisher sizeLeftRightPublisher + = NetworkTableInstance.getDefault() + .getTable( + "SmartDashboard") + .getDoubleTopic( + "swerve/sizeLeftRight") + .publish(); /** * Chassis Length publisher. */ - private static final DoublePublisher sizeFrontBackPublisher = NetworkTableInstance.getDefault() - .getDoubleTopic( - "swerve/sizeFrontBack") - .publish(); + private static final DoublePublisher sizeFrontBackPublisher + = NetworkTableInstance.getDefault() + .getTable( + "SmartDashboard") + .getDoubleTopic( + "swerve/sizeFrontBack") + .publish(); /** * Chassis direction widget publisher. */ - private static final StringPublisher forwardDirectionPublisher = NetworkTableInstance.getDefault() - .getStringTopic( - "swerve/forwardDirection") - .publish(); + private static final StringPublisher forwardDirectionPublisher + = NetworkTableInstance.getDefault() + .getTable( + "SmartDashboard") + .getStringTopic( + "swerve/forwardDirection") + .publish(); /** * Odometry cycle time, updated whenever {@link SwerveDrive#updateOdometry()} is called. */ private static final DoublePublisher odomCycleTime - = NetworkTableInstance.getDefault() - .getDoubleTopic( - "swerve/odomCycleMS") - .publish(); + = NetworkTableInstance.getDefault() + .getTable( + "SmartDashboard") + .getDoubleTopic( + "swerve/odomCycleMS") + .publish(); /** * Control cycle time, updated whenever * {@link swervelib.SwerveModule#setDesiredState(SwerveModuleState, boolean, double)} is called for the last module. */ private static final DoublePublisher ctrlCycleTime - = NetworkTableInstance.getDefault() - .getDoubleTopic( - "swerve/controlCycleMS") - .publish(); + = NetworkTableInstance.getDefault() + .getTable( + "SmartDashboard") + .getDoubleTopic( + "swerve/controlCycleMS") + .publish(); /** * Odometry timer to track cycle times. */ - private static final Timer odomTimer = new Timer(); + private static final Timer odomTimer = new Timer(); /** * Control timer to track cycle times. */ - private static final Timer ctrlTimer = new Timer(); + private static final Timer ctrlTimer = new Timer(); /** * Measured swerve module states object. */ public static SwerveModuleState[] measuredStatesObj - = new SwerveModuleState[4]; + = new SwerveModuleState[4]; /** * Desired swerve module states object */ public static SwerveModuleState[] desiredStatesObj - = new SwerveModuleState[4]; + = 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(); + public static ChassisSpeeds measuredChassisSpeedsObj = new ChassisSpeeds(); /** * Describes the desired forward, sideways and angular velocity of the robot. */ - public static ChassisSpeeds desiredChassisSpeedsObj = new ChassisSpeeds(); + public static ChassisSpeeds desiredChassisSpeedsObj = new ChassisSpeeds(); /** * The robot's current rotation based on odometry or gyro readings */ - public static Rotation2d robotRotationObj = new Rotation2d(); + public static Rotation2d robotRotationObj = new Rotation2d(); /** * The current telemetry verbosity level. */ public static TelemetryVerbosity verbosity - = TelemetryVerbosity.MACHINE; + = TelemetryVerbosity.MACHINE; /** * State of simulation of the Robot, used to optimize retrieval. */ public static boolean isSimulation - = RobotBase.isSimulation(); + = RobotBase.isSimulation(); /** * The number of swerve modules */ @@ -260,7 +306,7 @@ public class SwerveDriveTelemetry /** * 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. */ @@ -268,7 +314,7 @@ public class SwerveDriveTelemetry /** * 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. */ @@ -281,7 +327,7 @@ public class SwerveDriveTelemetry * 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. @@ -291,15 +337,15 @@ public class SwerveDriveTelemetry * 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]; /** * Update the telemetry settings that infrequently change. */ - public static boolean updateSettings = true; + public static boolean updateSettings = true; /** * Start the ctrl timer to measure cycle time, independent of periodic loops.