diff --git a/.gitignore b/.gitignore index 5528d4f6..375359c2 100644 --- a/.gitignore +++ b/.gitignore @@ -169,6 +169,8 @@ out/ .fleet # Simulation GUI and other tools window save file +networktables.json +simgui.json *-window.json # Simulation data log directory @@ -176,3 +178,7 @@ logs/ # Folder that has CTRE Phoenix Sim device config storage ctre_sim/ + +# clangd +/.cache +compile_commands.json diff --git a/.wpilib/wpilib_preferences.json b/.wpilib/wpilib_preferences.json index 9a94617b..77a1e114 100644 --- a/.wpilib/wpilib_preferences.json +++ b/.wpilib/wpilib_preferences.json @@ -1,6 +1,6 @@ { "enableCppIntellisense": false, "currentLanguage": "java", - "projectYear": "2024", + "projectYear": "2025beta", "teamNumber": 3481 } \ No newline at end of file diff --git a/IronMaple-License.md b/IronMaple-License.md new file mode 100644 index 00000000..3e1a5b7c --- /dev/null +++ b/IronMaple-License.md @@ -0,0 +1,15 @@ +

+ team logo + project logo +

+ +#### This project uses [maple-sim](https://github.com/Shenzhen-Robotics-Alliance/maple-sim) for simulation. +If you encounter any bugs related to drivetrain physics simulation, please [submit an issue to maple-sim](https://github.com/Shenzhen-Robotics-Alliance/maple-sim/issues/new/choose). + +## MIT License, Copyright (c) 2024 Team5516 "Iron Maple" + +**Permission is hereby granted, free of charge, to any person obtaining a copyof this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:** + +> #### The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +*THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.* \ No newline at end of file diff --git a/WPILib-License.md b/WPILib-License.md index 43b62ec2..645e5425 100644 --- a/WPILib-License.md +++ b/WPILib-License.md @@ -1,4 +1,4 @@ -Copyright (c) 2009-2023 FIRST and other WPILib contributors +Copyright (c) 2009-2024 FIRST and other WPILib contributors All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/build.gradle b/build.gradle index dbfd1afd..7d42139a 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ plugins { id "java" - id "edu.wpi.first.GradleRIO" version "2024.3.2" + id "edu.wpi.first.GradleRIO" version "2025.1.1-beta-2" } java { @@ -45,11 +45,12 @@ def deployArtifact = deploy.targets.roborio.artifacts.frcJava wpi.java.debugJni = false // Set this to true to enable desktop support. -def includeDesktopSupport = true +def includeDesktopSupport = false // Defining my dependencies. In this case, WPILib (+ friends), and vendor libraries. // Also defines JUnit 5. dependencies { + annotationProcessor wpi.java.deps.wpilibAnnotations() implementation wpi.java.deps.wpilib() implementation wpi.java.vendor.java() @@ -98,4 +99,4 @@ wpi.java.configureTestTasks(test) // Configure string concat to always inline compile tasks.withType(JavaCompile) { options.compilerArgs.add '-XDstringConcat=inline' -} +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index d64cd491..a4b76b95 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 5e82d67b..fbacf711 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=permwrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 1aa94a42..f5feea6d 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,8 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/gradlew.bat b/gradlew.bat index 93e3f59f..9d21a218 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail diff --git a/settings.gradle b/settings.gradle index d94f73c6..969c7b09 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,7 +4,7 @@ pluginManagement { repositories { mavenLocal() gradlePluginPortal() - String frcYear = '2024' + String frcYear = '2025' File frcHome if (OperatingSystem.current().isWindows()) { String publicFolder = System.getenv('PUBLIC') diff --git a/simgui.json b/simgui.json index 10b3d8cd..18af23dd 100644 --- a/simgui.json +++ b/simgui.json @@ -60,10 +60,15 @@ "/LiveWindow/Ungrouped/PIDController[1]": "PIDController", "/LiveWindow/Ungrouped/Pigeon 2 [13]": "Gyro", "/LiveWindow/Ungrouped/Scheduler": "Scheduler", + "/SmartDashboard/Encoders": "Alerts", "/SmartDashboard/Field": "Field2d", + "/SmartDashboard/IMU": "Alerts", + "/SmartDashboard/JSON": "Alerts", + "/SmartDashboard/Motors": "Alerts", "/SmartDashboard/Pigeon 2 (v6) [13]": "Gyro", "/SmartDashboard/Pigeon 2 [13]": "Gyro", "/SmartDashboard/SendableChooser[0]": "String Chooser", + "/SmartDashboard/Swerve Drive": "Alerts", "/SmartDashboard/VisionSystemSim-Vision/Sim Field": "Field2d", "/SmartDashboard/navX-Sensor[1]": "Gyro", "/SmartDashboard/navX-Sensor[4]": "Gyro" @@ -75,6 +80,20 @@ } }, "/SmartDashboard/Field": { + "OdometryPose": { + "arrowColor": [ + 255.0, + 255.0, + 0.0, + 255.0 + ], + "color": [ + 224.99996948242188, + 0.0, + 255.0, + 255.0 + ] + }, "XModules": { "arrowColor": [ 0.09611687064170837, diff --git a/src/main/deploy/pathplanner/autos/New Auto.auto b/src/main/deploy/pathplanner/autos/New Auto.auto index 7fc8be27..268147bb 100644 --- a/src/main/deploy/pathplanner/autos/New Auto.auto +++ b/src/main/deploy/pathplanner/autos/New Auto.auto @@ -1,12 +1,5 @@ { - "version": 1.0, - "startingPose": { - "position": { - "x": 2.0, - "y": 7.0 - }, - "rotation": 0 - }, + "version": "2025.0", "command": { "type": "sequential", "data": { @@ -20,6 +13,7 @@ ] } }, + "resetOdom": true, "folder": null, "choreoAuto": false } \ No newline at end of file diff --git a/src/main/deploy/pathplanner/paths/New Path.path b/src/main/deploy/pathplanner/paths/New Path.path index 2012ffc1..708478f2 100644 --- a/src/main/deploy/pathplanner/paths/New Path.path +++ b/src/main/deploy/pathplanner/paths/New Path.path @@ -1,5 +1,5 @@ { - "version": 1.0, + "version": "2025.0", "waypoints": [ { "anchor": { @@ -46,20 +46,25 @@ ], "rotationTargets": [], "constraintZones": [], + "pointTowardsZones": [], "eventMarkers": [], "globalConstraints": { "maxVelocity": 3.0, "maxAcceleration": 3.0, "maxAngularVelocity": 540.0, - "maxAngularAcceleration": 720.0 + "maxAngularAcceleration": 720.0, + "nominalVoltage": 12.0, + "unlimited": false }, "goalEndState": { "velocity": 0, - "rotation": 0, - "rotateFast": false + "rotation": 0.0 }, "reversed": false, "folder": null, - "previewStartingState": null, + "idealStartingState": { + "velocity": 0, + "rotation": 0.0 + }, "useDefaultConstraints": false } \ No newline at end of file diff --git a/src/main/deploy/pathplanner/settings.json b/src/main/deploy/pathplanner/settings.json new file mode 100644 index 00000000..2ca3c4c0 --- /dev/null +++ b/src/main/deploy/pathplanner/settings.json @@ -0,0 +1,32 @@ +{ + "robotWidth": 0.9, + "robotLength": 0.9, + "holonomicMode": true, + "pathFolders": [], + "autoFolders": [], + "defaultMaxVel": 3.0, + "defaultMaxAccel": 3.0, + "defaultMaxAngVel": 540.0, + "defaultMaxAngAccel": 720.0, + "defaultNominalVoltage": 12.0, + "robotMass": 74.088, + "robotMOI": 6.883, + "robotTrackwidth": 0.546, + "driveWheelRadius": 0.048, + "driveGearing": 5.143, + "maxDriveSpeed": 5.45, + "driveMotorType": "NEO", + "driveCurrentLimit": 60.0, + "wheelCOF": 1.2, + "flModuleX": 0.273, + "flModuleY": 0.273, + "frModuleX": 0.273, + "frModuleY": -0.273, + "blModuleX": -0.273, + "blModuleY": 0.273, + "brModuleX": -0.273, + "brModuleY": -0.273, + "bumperOffsetX": 0.0, + "bumperOffsetY": 0.0, + "robotFeatures": [] +} \ No newline at end of file diff --git a/src/main/java/frc/robot/Constants.java b/src/main/java/frc/robot/Constants.java index 0c89bbfc..70ffca24 100644 --- a/src/main/java/frc/robot/Constants.java +++ b/src/main/java/frc/robot/Constants.java @@ -4,7 +4,6 @@ package frc.robot; -import com.pathplanner.lib.util.PIDConstants; import edu.wpi.first.math.geometry.Translation3d; import edu.wpi.first.math.util.Units; import swervelib.math.Matter; @@ -26,12 +25,12 @@ public final class Constants public static final double MAX_SPEED = Units.feetToMeters(14.5); // Maximum speed of the robot in meters per second, used to limit acceleration. - public static final class AutonConstants - { - - public static final PIDConstants TRANSLATION_PID = new PIDConstants(0.7, 0, 0); - public static final PIDConstants ANGLE_PID = new PIDConstants(0.4, 0, 0.01); - } +// public static final class AutonConstants +// { +// +// public static final PIDConstants TRANSLATION_PID = new PIDConstants(0.7, 0, 0); +// public static final PIDConstants ANGLE_PID = new PIDConstants(0.4, 0, 0.01); +// } public static final class DrivebaseConstants { diff --git a/src/main/java/frc/robot/RobotContainer.java b/src/main/java/frc/robot/RobotContainer.java index 2e6fb1d1..ccc30ab6 100644 --- a/src/main/java/frc/robot/RobotContainer.java +++ b/src/main/java/frc/robot/RobotContainer.java @@ -78,6 +78,11 @@ public class RobotContainer () -> MathUtil.applyDeadband(driverXbox.getLeftX(), OperatorConstants.LEFT_X_DEADBAND), () -> driverXbox.getRawAxis(2)); + Command driveSetpointGenSim = drivebase.simDriveCommand( + () -> MathUtil.applyDeadband(driverXbox.getLeftY(), OperatorConstants.LEFT_Y_DEADBAND), + () -> MathUtil.applyDeadband(driverXbox.getLeftX(), OperatorConstants.LEFT_X_DEADBAND), + () -> driverXbox.getRawAxis(2)); + /** * The container for the robot. Contains subsystems, OI devices, and commands. */ @@ -96,6 +101,10 @@ public RobotContainer() */ private void configureBindings() { + if (Robot.isSimulation()) + { + driverXbox.start().onTrue(Commands.runOnce(() -> drivebase.resetOdometry(new Pose2d(3, 3, new Rotation2d())))); + } if (DriverStation.isTest()) { driverXbox.b().whileTrue(drivebase.sysIdDriveMotorCommand()); @@ -112,9 +121,9 @@ private void configureBindings() driverXbox.a().onTrue((Commands.runOnce(drivebase::zeroGyro))); driverXbox.x().onTrue(Commands.runOnce(drivebase::addFakeVisionReading)); driverXbox.b().whileTrue( - Commands.deferredProxy(() -> drivebase.driveToPose( - new Pose2d(new Translation2d(4, 4), Rotation2d.fromDegrees(0))) - )); + drivebase.driveToPose( + new Pose2d(new Translation2d(4, 4), Rotation2d.fromDegrees(0))) + ); driverXbox.y().whileTrue(drivebase.aimAtSpeaker(2)); driverXbox.start().whileTrue(Commands.none()); driverXbox.back().whileTrue(Commands.none()); diff --git a/src/main/java/frc/robot/subsystems/swervedrive/SwerveSubsystem.java b/src/main/java/frc/robot/subsystems/swervedrive/SwerveSubsystem.java index ba3f54dc..e3c90245 100644 --- a/src/main/java/frc/robot/subsystems/swervedrive/SwerveSubsystem.java +++ b/src/main/java/frc/robot/subsystems/swervedrive/SwerveSubsystem.java @@ -4,13 +4,19 @@ package frc.robot.subsystems.swervedrive; +import static edu.wpi.first.units.Units.Meter; + import com.pathplanner.lib.auto.AutoBuilder; import com.pathplanner.lib.commands.PathPlannerAuto; import com.pathplanner.lib.commands.PathfindingCommand; +import com.pathplanner.lib.config.PIDConstants; +import com.pathplanner.lib.config.RobotConfig; +import com.pathplanner.lib.controllers.PPHolonomicDriveController; import com.pathplanner.lib.path.PathConstraints; import com.pathplanner.lib.path.PathPlannerPath; -import com.pathplanner.lib.util.HolonomicPathFollowerConfig; -import com.pathplanner.lib.util.ReplanningConfig; +import com.pathplanner.lib.util.DriveFeedforwards; +import com.pathplanner.lib.util.swerve.SwerveSetpoint; +import com.pathplanner.lib.util.swerve.SwerveSetpointGenerator; import edu.wpi.first.apriltag.AprilTagFieldLayout; import edu.wpi.first.apriltag.AprilTagFields; import edu.wpi.first.math.controller.SimpleMotorFeedforward; @@ -30,11 +36,15 @@ import edu.wpi.first.wpilibj2.command.SubsystemBase; import edu.wpi.first.wpilibj2.command.sysid.SysIdRoutine.Config; import frc.robot.Constants; -import frc.robot.Constants.AutonConstants; +import frc.robot.subsystems.swervedrive.Vision.Cameras; import java.io.File; +import java.io.IOException; import java.util.Arrays; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.DoubleSupplier; -import org.photonvision.PhotonCamera; +import java.util.function.Supplier; +import org.json.simple.parser.ParseException; import org.photonvision.targeting.PhotonPipelineResult; import swervelib.SwerveController; import swervelib.SwerveDrive; @@ -56,15 +66,15 @@ public class SwerveSubsystem extends SubsystemBase /** * AprilTag field layout. */ - private final AprilTagFieldLayout aprilTagFieldLayout = AprilTagFields.k2024Crescendo.loadAprilTagLayoutField(); + private final AprilTagFieldLayout aprilTagFieldLayout = AprilTagFieldLayout.loadField(AprilTagFields.k2024Crescendo); /** * Enable vision odometry updates while driving. */ - private final boolean visionDriveTest = false; + private final boolean visionDriveTest = false; /** * PhotonVision class to keep an accurate odometry. */ - private Vision vision; + private Vision vision; /** * Initialize {@link SwerveDrive} with the directory provided. @@ -91,7 +101,10 @@ public SwerveSubsystem(File directory) SwerveDriveTelemetry.verbosity = TelemetryVerbosity.HIGH; try { - swerveDrive = new SwerveParser(directory).createSwerveDrive(Constants.MAX_SPEED); + swerveDrive = new SwerveParser(directory).createSwerveDrive(Constants.MAX_SPEED, + new Pose2d(new Translation2d(Meter.of(1), + Meter.of(4)), + Rotation2d.fromDegrees(0))); // Alternative method if you don't want to supply the conversion factor via JSON files. // swerveDrive = new SwerveParser(directory).createSwerveDrive(maximumSpeed, angleConversionFactor, driveConversionFactor); } catch (Exception e) @@ -123,7 +136,11 @@ public SwerveSubsystem(File directory) */ public SwerveSubsystem(SwerveDriveConfiguration driveCfg, SwerveControllerConfiguration controllerCfg) { - swerveDrive = new SwerveDrive(driveCfg, controllerCfg, Constants.MAX_SPEED); + swerveDrive = new SwerveDrive(driveCfg, + controllerCfg, + Constants.MAX_SPEED, + new Pose2d(new Translation2d(Meter.of(2), Meter.of(0)), + Rotation2d.fromDegrees(0))); } /** @@ -155,34 +172,55 @@ public void simulationPeriodic() */ public void setupPathPlanner() { - AutoBuilder.configureHolonomic( - this::getPose, // Robot pose supplier - this::resetOdometry, // Method to reset odometry (will be called if your auto has a starting pose) - this::getRobotVelocity, // ChassisSpeeds supplier. MUST BE ROBOT RELATIVE - this::setChassisSpeeds, // Method that will drive the robot given ROBOT RELATIVE ChassisSpeeds - new HolonomicPathFollowerConfig( // HolonomicPathFollowerConfig, this should likely live in your Constants class - AutonConstants.TRANSLATION_PID, - // Translation PID constants - AutonConstants.ANGLE_PID, - // Rotation PID constants - 4.5, - // Max module speed, in m/s - swerveDrive.swerveDriveConfiguration.getDriveBaseRadiusMeters(), - // Drive base radius in meters. Distance from robot center to furthest module. - new ReplanningConfig() - // Default path replanning config. See the API for the options here - ), - () -> { - // Boolean supplier that controls when the path will be mirrored for the red alliance - // This will flip the path being followed to the red side of the field. - // THE ORIGIN WILL REMAIN ON THE BLUE SIDE - var alliance = DriverStation.getAlliance(); - return alliance.isPresent() ? alliance.get() == DriverStation.Alliance.Red : false; - }, - this // Reference to this subsystem to set requirements - ); - - //Preload PathPlanner Path finding + // Load the RobotConfig from the GUI settings. You should probably + // store this in your Constants file + RobotConfig config; + try + { + config = RobotConfig.fromGUISettings(); + + // Configure AutoBuilder last + AutoBuilder.configure( + this::getPose, + // Robot pose supplier + this::resetOdometry, + // Method to reset odometry (will be called if your auto has a starting pose) + this::getRobotVelocity, + // ChassisSpeeds supplier. MUST BE ROBOT RELATIVE + this::setChassisSpeeds, + // Method that will drive the robot given ROBOT RELATIVE ChassisSpeeds. Also optionally outputs individual module feedforwards + new PPHolonomicDriveController( + // PPHolonomicController is the built in path following controller for holonomic drive trains + new PIDConstants(5.0, 0.0, 0.0), + // Translation PID constants + new PIDConstants(5.0, 0.0, 0.0) + // Rotation PID constants + ), + config, + // The robot configuration + () -> { + // Boolean supplier that controls when the path will be mirrored for the red alliance + // This will flip the path being followed to the red side of the field. + // THE ORIGIN WILL REMAIN ON THE BLUE SIDE + + var alliance = DriverStation.getAlliance(); + if (alliance.isPresent()) + { + return alliance.get() == DriverStation.Alliance.Red; + } + return false; + }, + this + // Reference to this subsystem to set requirements + ); + + } catch (Exception e) + { + // Handle exception as needed + e.printStackTrace(); + } + + //Preload PathPlanner Path finding // IF USING CUSTOM PATHFINDER ADD BEFORE THIS LINE PathfindingCommand.warmupCommand().schedule(); } @@ -225,32 +263,34 @@ public Command aimAtSpeaker(double tolerance) SwerveController controller = swerveDrive.getSwerveController(); return run( () -> { - drive(ChassisSpeeds.fromFieldRelativeSpeeds(0, - 0, - controller.headingCalculate(getHeading().getRadians(), - getSpeakerYaw().getRadians()), - getHeading()) - ); + ChassisSpeeds speeds = new ChassisSpeeds(0, 0, + controller.headingCalculate(getHeading().getRadians(), + getSpeakerYaw().getRadians())); + speeds.toRobotRelativeSpeeds(getHeading()); + drive(speeds); }).until(() -> Math.abs(getSpeakerYaw().minus(getHeading()).getDegrees()) < tolerance); } /** * Aim the robot at the target returned by PhotonVision. * - * @param camera {@link PhotonCamera} to communicate with. * @return A {@link Command} which will run the alignment. */ - public Command aimAtTarget(PhotonCamera camera) + public Command aimAtTarget(Cameras camera) { return run(() -> { - PhotonPipelineResult result = camera.getLatestResult(); - if (result.hasTargets()) + Optional resultO = camera.getBestResult(); + if (resultO.isPresent()) { - drive(getTargetSpeeds(0, - 0, - Rotation2d.fromDegrees(result.getBestTarget() - .getYaw()))); // Not sure if this will work, more math may be required. + var result = resultO.get(); + if (result.hasTargets()) + { + drive(getTargetSpeeds(0, + 0, + Rotation2d.fromDegrees(result.getBestTarget() + .getYaw()))); // Not sure if this will work, more math may be required. + } } }); } @@ -284,11 +324,65 @@ public Command driveToPose(Pose2d pose) return AutoBuilder.pathfindToPose( pose, constraints, - 0.0, // Goal end velocity in meters/sec - 0.0 // Rotation delay distance in meters. This is how far the robot should travel before attempting to rotate. + edu.wpi.first.units.Units.MetersPerSecond.of(0) // Goal end velocity in meters/sec ); } + /** + * Drive with {@link SwerveSetpointGenerator} from 254, implemented by PathPlanner. + * + * @param robotRelativeChassisSpeed Robot relative {@link ChassisSpeeds} to achieve. + * @return {@link Command} to run. + * @throws IOException If the PathPlanner GUI settings is invalid + * @throws ParseException If PathPlanner GUI settings is nonexistent. + */ + private Command driveWithSetpointGenerator(Supplier robotRelativeChassisSpeed) + throws IOException, ParseException + { + SwerveSetpointGenerator setpointGenerator = new SwerveSetpointGenerator(RobotConfig.fromGUISettings(), + swerveDrive.getMaximumAngularVelocity()); + AtomicReference prevSetpoint + = new AtomicReference<>(new SwerveSetpoint(swerveDrive.getRobotVelocity(), + swerveDrive.getStates(), + DriveFeedforwards.zeros(swerveDrive.getModules().length))); + double start = Timer.getFPGATimestamp(); + return run(() -> { + SwerveSetpoint newSetpoint = setpointGenerator.generateSetpoint(prevSetpoint.get(), + robotRelativeChassisSpeed.get(), + start - Timer.getFPGATimestamp()); + swerveDrive.drive(newSetpoint.robotRelativeSpeeds(), + newSetpoint.moduleStates(), + newSetpoint.feedforwards().torqueCurrentsAmps()); + // TODO: Convert the amp feedforward to usable generic feedforward. Currently it is ignored. + prevSetpoint.set(newSetpoint); + + + }); + } + + public Command driveWithSetpointGenerator(DoubleSupplier translationX, DoubleSupplier translationY, + DoubleSupplier rotationX) + { + SwerveController swerveController = swerveDrive.getSwerveController(); + try + { + return driveWithSetpointGenerator(() -> { + Translation2d scaledInputs = SwerveMath.scaleTranslation(new Translation2d(translationX.getAsDouble(), + translationY.getAsDouble()), 0.8); + + return new ChassisSpeeds(scaledInputs.getX(), + scaledInputs.getY(), + rotationX.getAsDouble() * swerveDrive.getMaximumAngularVelocity()); + }); + } catch (Exception e) + { + DriverStation.reportError(e.toString(), true); + } + return Commands.none(); + + } + + /** * Command to drive the robot using translative values and heading as a setpoint. * @@ -385,11 +479,9 @@ public Command centerModulesCommand() */ public Command driveToDistanceCommand(double distanceInMeters, double speedInMetersPerSecond) { - return Commands.deferredProxy( - () -> Commands.run(() -> drive(new ChassisSpeeds(speedInMetersPerSecond, 0, 0)), this) - .until(() -> swerveDrive.getPose().getTranslation().getDistance(new Translation2d(0, 0)) > - distanceInMeters) - ); + return run(() -> drive(new ChassisSpeeds(speedInMetersPerSecond, 0, 0))) + .until(() -> swerveDrive.getPose().getTranslation().getDistance(new Translation2d(0, 0)) > + distanceInMeters); } /** diff --git a/src/main/java/frc/robot/subsystems/swervedrive/Vision.java b/src/main/java/frc/robot/subsystems/swervedrive/Vision.java index ae8f67b3..f907751a 100644 --- a/src/main/java/frc/robot/subsystems/swervedrive/Vision.java +++ b/src/main/java/frc/robot/subsystems/swervedrive/Vision.java @@ -1,5 +1,9 @@ package frc.robot.subsystems.swervedrive; +import static edu.wpi.first.units.Units.Microseconds; +import static edu.wpi.first.units.Units.Milliseconds; +import static edu.wpi.first.units.Units.Seconds; + import edu.wpi.first.apriltag.AprilTagFieldLayout; import edu.wpi.first.apriltag.AprilTagFields; import edu.wpi.first.math.Matrix; @@ -14,6 +18,9 @@ import edu.wpi.first.math.numbers.N1; import edu.wpi.first.math.numbers.N3; import edu.wpi.first.math.util.Units; +import edu.wpi.first.networktables.NetworkTablesJNI; +import edu.wpi.first.wpilibj.Alert; +import edu.wpi.first.wpilibj.Alert.AlertType; import edu.wpi.first.wpilibj.smartdashboard.Field2d; import frc.robot.Robot; import java.awt.Desktop; @@ -32,8 +39,6 @@ import org.photonvision.targeting.PhotonPipelineResult; import org.photonvision.targeting.PhotonTrackedTarget; import swervelib.SwerveDrive; -import swervelib.telemetry.Alert; -import swervelib.telemetry.Alert.AlertType; import swervelib.telemetry.SwerveDriveTelemetry; @@ -52,7 +57,7 @@ public class Vision /** * Ambiguity defined as a value between (0,1). Used in {@link Vision#filterPose}. */ - private final double maximumAmbiguity = 0.25; + private final double maximumAmbiguity = 0.25; /** * Photon Vision Simulation */ @@ -124,10 +129,16 @@ public static Pose2d getAprilTagPose(int aprilTag, Transform2d robotOffset) */ public void updatePoseEstimation(SwerveDrive swerveDrive) { - if (SwerveDriveTelemetry.isSimulation) + if (SwerveDriveTelemetry.isSimulation && swerveDrive.getSimulationDriveTrainPose().isPresent()) { - visionSim.update(swerveDrive.getPose()); - + /* + * In the maple-sim, odometry is simulated using encoder values, accounting for factors like skidding and drifting. + * As a result, the odometry may not always be 100% accurate. + * However, the vision system should be able to provide a reasonably accurate pose estimation, even when odometry is incorrect. + * (This is why teams implement vision system to correct odometry.) + * Therefore, we must ensure that the actual robot pose is provided in the simulator when updating the vision simulation during the simulation. + */ + visionSim.update(swerveDrive.getSimulationDriveTrainPose().get()); } for (Cameras camera : Cameras.values()) { @@ -137,7 +148,7 @@ public void updatePoseEstimation(SwerveDrive swerveDrive) var pose = poseEst.get(); swerveDrive.addVisionMeasurement(pose.estimatedPose.toPose2d(), pose.timestampSeconds, - getEstimationStdDevs(camera)); + camera.curStdDevs); } } @@ -154,64 +165,24 @@ public void updatePoseEstimation(SwerveDrive swerveDrive) */ public Optional getEstimatedGlobalPose(Cameras camera) { - Optional poseEst = filterPose(camera.poseEstimator.update()); - // Uncomment to enable outputting of vision targets in sim. - /* - poseEst.ifPresent(estimatedRobotPose -> field2d.getObject(camera + " est pose") - .setPose(estimatedRobotPose.estimatedPose.toPose2d())); - */ - return poseEst; - } - - /** - * The standard deviations of the estimated pose from {@link Vision#getEstimatedGlobalPose(Cameras)}, for use with - * {@link edu.wpi.first.math.estimator.SwerveDrivePoseEstimator SwerveDrivePoseEstimator}. This should only be used - * when there are targets visible. - * - * @param camera Desired camera to get the standard deviation of the estimated pose. - */ - public Matrix getEstimationStdDevs(Cameras camera) - { - var poseEst = getEstimatedGlobalPose(camera); - var estStdDevs = camera.singleTagStdDevs; - var targets = getLatestResult(camera).getTargets(); - int numTags = 0; - double avgDist = 0; - for (var tgt : targets) - { - var tagPose = camera.poseEstimator.getFieldTags().getTagPose(tgt.getFiducialId()); - if (tagPose.isEmpty()) - { - continue; - } - numTags++; - if (poseEst.isPresent()) - { - avgDist += PhotonUtils.getDistanceToPose(poseEst.get().estimatedPose.toPose2d(), tagPose.get().toPose2d()); - } - } - if (numTags == 0) - { - return estStdDevs; - } - avgDist /= numTags; - // Decrease std devs if multiple targets are visible - if (numTags > 1) - { - estStdDevs = camera.multiTagStdDevs; - } - // Increase std devs based on (average) distance - if (numTags == 1 && avgDist > 4) - { - estStdDevs = VecBuilder.fill(Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE); - } else + Optional poseEst = camera.getEstimatedGlobalPose(); + if (Robot.isSimulation()) { - estStdDevs = estStdDevs.times(1 + (avgDist * avgDist / 30)); + Field2d debugField = visionSim.getDebugField(); + // Uncomment to enable outputting of vision targets in sim. + poseEst.ifPresentOrElse( + est -> + debugField + .getObject("VisionEstimation") + .setPose(est.estimatedPose.toPose2d()), + () -> { + debugField.getObject("VisionEstimation").setPoses(); + }); } - - return estStdDevs; + return poseEst; } + /** * Filter pose via the ambiguity and find best estimate between all of the camera's throwing out distances more than * 10m for a short amount of time. @@ -219,6 +190,7 @@ public Matrix getEstimationStdDevs(Cameras camera) * @param pose Estimated robot pose. * @return Could be empty if there isn't a good reading. */ + @Deprecated(since = "2024", forRemoval = true) private Optional filterPose(Optional pose) { if (pose.isPresent()) @@ -257,17 +229,6 @@ private Optional filterPose(Optional pos return Optional.empty(); } - /** - * Get the latest result from a given Camera. - * - * @param camera Given camera to take the result from. - * @return Photon result from sim or a real camera. - */ - public PhotonPipelineResult getLatestResult(Cameras camera) - { - - return Robot.isReal() ? camera.camera.getLatestResult() : camera.cameraSim.getCamera().getLatestResult(); - } /** * Get distance of the robot from the AprilTag pose. @@ -290,19 +251,22 @@ public double getDistanceFromAprilTag(int id) */ public PhotonTrackedTarget getTargetFromId(int id, Cameras camera) { - PhotonTrackedTarget target = null; - PhotonPipelineResult result = getLatestResult(camera); - if (result.hasTargets()) + PhotonTrackedTarget target = null; + for (PhotonPipelineResult result : camera.resultsList) { - for (PhotonTrackedTarget i : result.getTargets()) + if (result.hasTargets()) { - if (i.getFiducialId() == id) + for (PhotonTrackedTarget i : result.getTargets()) { - target = i; + if (i.getFiducialId() == id) + { + return i; + } } } } return target; + } /** @@ -343,9 +307,13 @@ public void updateVisionField() List targets = new ArrayList(); for (Cameras c : Cameras.values()) { - if (getLatestResult(c).hasTargets()) + if (!c.resultsList.isEmpty()) { - targets.addAll(getLatestResult(c).targets); + PhotonPipelineResult latest = c.resultsList.get(0); + if (latest.hasTargets()) + { + targets.addAll(latest.targets); + } } } @@ -398,25 +366,47 @@ enum Cameras /** * Latency alert to use when high latency is detected. */ - public final Alert latencyAlert; + public final Alert latencyAlert; /** * Camera instance for comms. */ - public final PhotonCamera camera; + public final PhotonCamera camera; /** * Pose estimator for camera. */ - public final PhotonPoseEstimator poseEstimator; - public final Matrix singleTagStdDevs; - public final Matrix multiTagStdDevs; + public final PhotonPoseEstimator poseEstimator; + /** + * Standard Deviation for single tag readings for pose estimation. + */ + private final Matrix singleTagStdDevs; + /** + * Standard deviation for multi-tag readings for pose estimation. + */ + private final Matrix multiTagStdDevs; /** * Transform of the camera rotation and translation relative to the center of the robot */ - private final Transform3d robotToCamTransform; + private final Transform3d robotToCamTransform; + /** + * Current standard deviations used. + */ + public Matrix curStdDevs; + /** + * Estimated robot pose. + */ + public Optional estimatedRobotPose; /** * Simulated camera instance which only exists during simulations. */ - public PhotonCameraSim cameraSim; + public PhotonCameraSim cameraSim; + /** + * Results list to be updated periodically and cached to avoid unnecessary queries. + */ + public List resultsList; + /** + * Last read from the camera timestamp to prevent lag due to slow data fetches. + */ + private double lastReadTimestamp = Microseconds.of(NetworkTablesJNI.now()).in(Seconds); /** * Construct a Photon Camera class with help. Standard deviations are fake values, experiment and determine @@ -431,7 +421,7 @@ enum Cameras Cameras(String name, Rotation3d robotToCamRotation, Translation3d robotToCamTranslation, Matrix singleTagStdDevs, Matrix multiTagStdDevsMatrix) { - latencyAlert = new Alert("'" + name + "' Camera is experiencing high latency.", AlertType.WARNING); + latencyAlert = new Alert("'" + name + "' Camera is experiencing high latency.", AlertType.kWarning); camera = new PhotonCamera(name); @@ -440,7 +430,6 @@ enum Cameras poseEstimator = new PhotonPoseEstimator(Vision.fieldLayout, PoseStrategy.MULTI_TAG_PNP_ON_COPROCESSOR, - camera, robotToCamTransform); poseEstimator.setMultiTagFallbackStrategy(PoseStrategy.LOWEST_AMBIGUITY); @@ -475,9 +464,173 @@ public void addToVisionSim(VisionSystemSim systemSim) if (Robot.isSimulation()) { systemSim.addCamera(cameraSim, robotToCamTransform); -// cameraSim.enableDrawWireframe(true); } } + + /** + * Get the result with the least ambiguity from the best tracked target within the Cache. This may not be the most + * recent result! + * + * @return The result in the cache with the least ambiguous best tracked target. This is not the most recent result! + */ + public Optional getBestResult() + { + if (resultsList.isEmpty()) + { + return Optional.empty(); + } + + PhotonPipelineResult bestResult = resultsList.get(0); + double amiguity = bestResult.getBestTarget().getPoseAmbiguity(); + double currentAmbiguity = 0; + for (PhotonPipelineResult result : resultsList) + { + currentAmbiguity = result.getBestTarget().getPoseAmbiguity(); + if (currentAmbiguity < amiguity && currentAmbiguity > 0) + { + bestResult = result; + amiguity = currentAmbiguity; + } + } + return Optional.of(bestResult); + } + + /** + * Get the latest result from the current cache. + * + * @return Empty optional if nothing is found. Latest result if something is there. + */ + public Optional getLatestResult() + { + return resultsList.isEmpty() ? Optional.empty() : Optional.of(resultsList.get(0)); + } + + /** + * Get the estimated robot pose. Updates the current robot pose estimation, standard deviations, and flushes the + * cache of results. + * + * @return Estimated pose. + */ + public Optional getEstimatedGlobalPose() + { + updateUnreadResults(); + return estimatedRobotPose; + } + + /** + * Update the latest results, cached with a maximum refresh rate of 1req/15ms. Sorts the list by timestamp. + */ + private void updateUnreadResults() + { + double mostRecentTimestamp = resultsList.get(0).getTimestampSeconds(); + double currentTimestamp = Microseconds.of(NetworkTablesJNI.now()).in(Seconds); + double debounceTime = Milliseconds.of(15).in(Seconds); + for (PhotonPipelineResult result : resultsList) + { + mostRecentTimestamp = Math.max(mostRecentTimestamp, result.getTimestampSeconds()); + } + if ((resultsList.isEmpty() || (currentTimestamp - mostRecentTimestamp >= debounceTime)) && + (currentTimestamp - lastReadTimestamp) >= debounceTime) + { + resultsList = Robot.isReal() ? camera.getAllUnreadResults() : cameraSim.getCamera().getAllUnreadResults(); + lastReadTimestamp = currentTimestamp; + resultsList.sort((PhotonPipelineResult a, PhotonPipelineResult b) -> { + return a.getTimestampSeconds() >= b.getTimestampSeconds() ? 1 : -1; + }); + if (!resultsList.isEmpty()) + { + updateEstimatedGlobalPose(); + } + } + } + + /** + * The latest estimated robot pose on the field from vision data. This may be empty. This should only be called once + * per loop. + * + *

Also includes updates for the standard deviations, which can (optionally) be retrieved with + * {@link Cameras#updateEstimationStdDevs} + * + * @return An {@link EstimatedRobotPose} with an estimated pose, estimate timestamp, and targets used for + * estimation. + */ + private void updateEstimatedGlobalPose() + { + Optional visionEst = Optional.empty(); + for (var change : camera.getAllUnreadResults()) + { + visionEst = poseEstimator.update(change); + updateEstimationStdDevs(visionEst, change.getTargets()); + } + estimatedRobotPose = visionEst; + } + + /** + * Calculates new standard deviations This algorithm is a heuristic that creates dynamic standard deviations based + * on number of tags, estimation strategy, and distance from the tags. + * + * @param estimatedPose The estimated pose to guess standard deviations for. + * @param targets All targets in this camera frame + */ + private void updateEstimationStdDevs( + Optional estimatedPose, List targets) + { + if (estimatedPose.isEmpty()) + { + // No pose input. Default to single-tag std devs + curStdDevs = singleTagStdDevs; + + } else + { + // Pose present. Start running Heuristic + var estStdDevs = singleTagStdDevs; + int numTags = 0; + double avgDist = 0; + + // Precalculation - see how many tags we found, and calculate an average-distance metric + for (var tgt : targets) + { + var tagPose = poseEstimator.getFieldTags().getTagPose(tgt.getFiducialId()); + if (tagPose.isEmpty()) + { + continue; + } + numTags++; + avgDist += + tagPose + .get() + .toPose2d() + .getTranslation() + .getDistance(estimatedPose.get().estimatedPose.toPose2d().getTranslation()); + } + + if (numTags == 0) + { + // No tags visible. Default to single-tag std devs + curStdDevs = singleTagStdDevs; + } else + { + // One or more tags visible, run the full heuristic. + avgDist /= numTags; + // Decrease std devs if multiple targets are visible + if (numTags > 1) + { + estStdDevs = multiTagStdDevs; + } + // Increase std devs based on (average) distance + if (numTags == 1 && avgDist > 4) + { + estStdDevs = VecBuilder.fill(Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE); + } else + { + estStdDevs = estStdDevs.times(1 + (avgDist * avgDist / 30)); + } + curStdDevs = estStdDevs; + } + } + } + + } } diff --git a/src/main/java/swervelib/SwerveDrive.java b/src/main/java/swervelib/SwerveDrive.java index 7d83e5d0..74ed27f6 100644 --- a/src/main/java/swervelib/SwerveDrive.java +++ b/src/main/java/swervelib/SwerveDrive.java @@ -1,5 +1,16 @@ 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.Amps; +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.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; @@ -18,7 +29,11 @@ import edu.wpi.first.math.numbers.N3; import edu.wpi.first.math.trajectory.Trajectory; import edu.wpi.first.math.util.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.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 +44,10 @@ 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.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 +58,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 +101,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 +124,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 +158,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. */ @@ -175,9 +196,11 @@ public class SwerveDrive * {@link SwerveController}. * @param maxSpeedMPS Maximum speed 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; swerveDriveConfiguration = config; @@ -186,11 +209,48 @@ public SwerveDrive( 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, + Amps.of(config.physicalCharacteristics.driveMotorCurrentLimit), + Amps.of(20), + 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]); + } + + // register the drivetrain simulation + SimulatedArena.getInstance().addDriveTrainSimulation(mapleSimDrive); + + simIMU = new SwerveIMUSimulation(mapleSimDrive.getGyroSimulation()); imuReadingCache = new Cache<>(simIMU::getGyroRotation3d, 5L); } else { @@ -199,16 +259,13 @@ 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); @@ -244,11 +301,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 +361,7 @@ private void checkIfTunerXCompatible() public void setOdometryPeriod(double period) { odometryThread.stop(); + SimulatedArena.overrideSimulationTimings(Seconds.of(period), 1); odometryThread.startPeriodic(period); } @@ -309,6 +371,7 @@ public void setOdometryPeriod(double period) public void stopOdometryThread() { odometryThread.stop(); + SimulatedArena.overrideSimulationTimings(Seconds.of(TimedRobot.kDefaultPeriod), 5); } /** @@ -381,9 +444,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 +455,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 +467,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 +516,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 +544,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()); } @@ -524,15 +585,9 @@ 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 = velocity; } // Calculate required module states via kinematics @@ -634,6 +689,27 @@ 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 feedforwardAmp Feedforward in amperage. (Ignored for now) + */ + public void drive(ChassisSpeeds robotRelativeVelocity, SwerveModuleState[] states, double[] feedforwardAmp) + { + if (SwerveDriveTelemetry.verbosity.ordinal() >= TelemetryVerbosity.LOW.ordinal()) + { + SwerveDriveTelemetry.desiredChassisSpeedsObj = robotRelativeVelocity; + } + for (SwerveModule module : swerveModules) + { + module.setDesiredState(states[module.moduleNumber], false, 0); //feedforwardAmp[module.moduleNumber]); + } + } + /** * Set chassis speeds with closed-loop velocity control. * @@ -646,15 +722,13 @@ public void setChassisSpeeds(ChassisSpeeds chassisSpeeds) autonomousChassisVelocityCorrection, autonomousAngularVelocityCorrection); - SwerveDriveTelemetry.desiredChassisSpeeds[1] = chassisSpeeds.vyMetersPerSecond; - SwerveDriveTelemetry.desiredChassisSpeeds[0] = chassisSpeeds.vxMetersPerSecond; - SwerveDriveTelemetry.desiredChassisSpeeds[2] = Math.toDegrees(chassisSpeeds.omegaRadiansPerSecond); + SwerveDriveTelemetry.desiredChassisSpeedsObj = chassisSpeeds; setRawModuleStates(kinematics.toSwerveModuleStates(chassisSpeeds), chassisSpeeds, 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 +742,26 @@ public Pose2d getPose() } /** - * Gets the current field-relative velocity (x, y and omega) of the robot + * Gets the actual pose of the drivetrain during simulation + * + * @return an optional Pose2d, representing the drivetrain pose during simulation, or an empty optional when running + * on real robot + */ + public Optional getSimulationDriveTrainPose() + { + if (SwerveDriveTelemetry.isSimulation) + { + odometryLock.lock(); + Pose2d simulationPose = mapleSimDrive.getSimulatedDriveTrainPose(); + odometryLock.unlock(); + return Optional.of(simulationPose); + } + + return Optional.empty(); + } + + /** + * Gets the measured field-relative robot velocity (x, y and omega) * * @return A ChassisSpeeds object of the current field-relative velocity */ @@ -678,8 +771,30 @@ public ChassisSpeeds getFieldVelocity() // 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()); + ChassisSpeeds robotRelativeSpeeds = kinematics.toChassisSpeeds(getStates()); + robotRelativeSpeeds.toFieldRelativeSpeeds(getOdometryHeading().unaryMinus()); + return robotRelativeSpeeds; + } + + /** + * Gets the actual field-relative robot velocity (x, y and omega) during simulation + * + * @return An optional ChassisSpeeds representing the actual field-relative velocity of the robot, or an empty + * optional when running on real robot + * @deprecated for testing version of maple-sim only + */ + @Deprecated + public Optional getSimulationFieldVelocity() + { + if (SwerveDriveTelemetry.isSimulation) + { + odometryLock.lock(); + ChassisSpeeds simulationFieldRelativeVelocity = mapleSimDrive.getDriveTrainSimulatedChassisSpeedsFieldRelative(); + odometryLock.unlock(); + return Optional.of(simulationFieldRelativeVelocity); + } + + return Optional.empty(); } /** @@ -692,6 +807,27 @@ public ChassisSpeeds getRobotVelocity() return kinematics.toChassisSpeeds(getStates()); } + /** + * Gets the actual robot-relative robot velocity (x, y and omega) during simulation + * + * @return An optional ChassisSpeeds representing the actual robot-relative velocity of the robot, or an empty + * optional when running on real robot + * @deprecated for testing version of maple-sim only + */ + @Deprecated + public Optional getSimulationRobotVelocity() + { + if (SwerveDriveTelemetry.isSimulation) + { + odometryLock.lock(); + ChassisSpeeds simulationFieldRelativeVelocity = mapleSimDrive.getDriveTrainSimulatedChassisSpeedsRobotRelative(); + odometryLock.unlock(); + return Optional.of(simulationFieldRelativeVelocity); + } + + return Optional.empty(); + } + /** * Resets odometry to the given pose. Gyro angle and module positions do not need to be reset when calling this * method. However, if either gyro angle or module position is reset, this must be called in order for odometry to @@ -703,8 +839,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); + } /** @@ -943,10 +1086,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 +1136,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 +1187,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 +1212,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 +1418,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 +1435,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) @@ -1328,12 +1487,8 @@ public ChassisSpeeds angularVelocitySkewCorrection(ChassisSpeeds velocity) 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)); + velocity.toFieldRelativeSpeeds(getOdometryHeading()); + velocity.toRobotRelativeSpeeds(getOdometryHeading().plus(angularVelocity)); } return velocity; } @@ -1359,10 +1514,25 @@ private ChassisSpeeds movementOptimizations(ChassisSpeeds velocity, boolean uesC // https://www.chiefdelphi.com/t/whitepaper-swerve-drive-skew-and-second-order-kinematics/416964/5 if (uesChassisDiscretize) { - velocity = ChassisSpeeds.discretize(velocity, discretizationdtSeconds); + velocity.discretize(discretizationdtSeconds); } return velocity; } + /** + * Convert a {@link ChassisSpeeds} to {@link SwerveModuleState[]} for use elsewhere. + * + * @param velocity {@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 velocity, boolean optimize) + { + if (optimize) + { + velocity = movementOptimizations(velocity, chassisVelocityCorrection, angularVelocityCorrection); + } + return kinematics.toSwerveModuleStates(velocity); + } } diff --git a/src/main/java/swervelib/SwerveDriveTest.java b/src/main/java/swervelib/SwerveDriveTest.java index a3d71dfe..70405a3e 100644 --- a/src/main/java/swervelib/SwerveDriveTest.java +++ b/src/main/java/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/src/main/java/swervelib/SwerveModule.java b/src/main/java/swervelib/SwerveModule.java index 8b5dec10..e989cdab 100644 --- a/src/main/java/swervelib/SwerveModule.java +++ b/src/main/java/swervelib/SwerveModule.java @@ -1,20 +1,25 @@ package swervelib; -import com.revrobotics.CANSparkMax; -import com.revrobotics.MotorFeedbackSensor; +import static edu.wpi.first.units.Units.MetersPerSecond; + 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.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.simulation.SwerveModuleSimulation; -import swervelib.telemetry.Alert; import swervelib.telemetry.SwerveDriveTelemetry; import swervelib.telemetry.SwerveDriveTelemetry.TelemetryVerbosity; @@ -43,7 +48,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. */ @@ -95,7 +100,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 +116,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; /** @@ -166,13 +171,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 +193,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,8 +201,8 @@ public SwerveModule(int moduleNumber, SwerveModuleConfiguration moduleConfigurat driveMotor.burnFlash(); angleMotor.burnFlash(); - drivePositionCache = new Cache<>(driveMotor::getPosition, 15); - driveVelocityCache = new Cache<>(driveMotor::getVelocity, 15); + drivePositionCache = new Cache<>(driveMotor::getPosition, 20); + driveVelocityCache = new Cache<>(driveMotor::getVelocity, 20); if (SwerveDriveTelemetry.isSimulation) { @@ -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"; @@ -361,7 +371,8 @@ 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) @@ -371,9 +382,26 @@ public void setDesiredState(SwerveModuleState desiredState, boolean isOpenLoop, } // 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) { @@ -381,8 +409,7 @@ public void setDesiredState(SwerveModuleState desiredState, boolean isOpenLoop, 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 +435,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 +456,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 +474,7 @@ private double getCosineCompensatedVelocity(SwerveModuleState desiredState) cosineScalar = 1; } - return desiredState.speedMetersPerSecond * (cosineScalar); + return MetersPerSecond.of(desiredState.speedMetersPerSecond).times(cosineScalar); } /** @@ -518,6 +545,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 +663,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)) @@ -687,9 +721,42 @@ 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) + { + this.simModule.configureSimModule(swerveModuleSimulation); + } } diff --git a/src/main/java/swervelib/encoders/AnalogAbsoluteEncoderSwerve.java b/src/main/java/swervelib/encoders/AnalogAbsoluteEncoderSwerve.java index fbd8214f..11016f65 100644 --- a/src/main/java/swervelib/encoders/AnalogAbsoluteEncoderSwerve.java +++ b/src/main/java/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/src/main/java/swervelib/encoders/CANCoderSwerve.java b/src/main/java/swervelib/encoders/CANCoderSwerve.java index 24ca2013..0477b240 100644 --- a/src/main/java/swervelib/encoders/CANCoderSwerve.java +++ b/src/main/java/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/src/main/java/swervelib/encoders/PWMDutyCycleEncoderSwerve.java b/src/main/java/swervelib/encoders/PWMDutyCycleEncoderSwerve.java index 1b8bb6b8..52270951 100644 --- a/src/main/java/swervelib/encoders/PWMDutyCycleEncoderSwerve.java +++ b/src/main/java/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/src/main/java/swervelib/encoders/SparkMaxAnalogEncoderSwerve.java b/src/main/java/swervelib/encoders/SparkMaxAnalogEncoderSwerve.java index bbf9fb6c..aa88efcf 100644 --- a/src/main/java/swervelib/encoders/SparkMaxAnalogEncoderSwerve.java +++ b/src/main/java/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/src/main/java/swervelib/encoders/SparkMaxEncoderSwerve.java b/src/main/java/swervelib/encoders/SparkMaxEncoderSwerve.java index 6bd68dad..b01a39f1 100644 --- a/src/main/java/swervelib/encoders/SparkMaxEncoderSwerve.java +++ b/src/main/java/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/src/main/java/swervelib/imu/NavXSwerve.java b/src/main/java/swervelib/imu/NavXSwerve.java index 2d6454ef..dd2797d6 100644 --- a/src/main/java/swervelib/imu/NavXSwerve.java +++ b/src/main/java/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/src/main/java/swervelib/imu/Pigeon2Swerve.java b/src/main/java/swervelib/imu/Pigeon2Swerve.java index 574da147..fc0a113c 100644 --- a/src/main/java/swervelib/imu/Pigeon2Swerve.java +++ b/src/main/java/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/src/main/java/swervelib/math/IMULinearMovingAverageFilter.java b/src/main/java/swervelib/math/IMULinearMovingAverageFilter.java index 09509b75..9ac08efb 100644 --- a/src/main/java/swervelib/math/IMULinearMovingAverageFilter.java +++ b/src/main/java/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/src/main/java/swervelib/motors/SparkFlexSwerve.java b/src/main/java/swervelib/motors/SparkFlexSwerve.java index 400de964..7b58f04a 100644 --- a/src/main/java/swervelib/motors/SparkFlexSwerve.java +++ b/src/main/java/swervelib/motors/SparkFlexSwerve.java @@ -1,58 +1,81 @@ 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. @@ -60,7 +83,7 @@ public class SparkFlexSwerve extends SwerveMotor * @param motor The SwerveMotor as a SparkFlex object. * @param isDriveMotor Is the motor being initialized a drive motor? */ - public SparkFlexSwerve(CANSparkFlex motor, boolean isDriveMotor) + public SparkFlexSwerve(SparkFlex motor, boolean isDriveMotor) { this.motor = motor; this.isDriveMotor = isDriveMotor; @@ -68,32 +91,33 @@ 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? */ public SparkFlexSwerve(int id, boolean isDriveMotor) { - this(new CANSparkFlex(id, MotorType.kBrushless), isDriveMotor); + this(new SparkFlex(id, MotorType.kBrushless), isDriveMotor); } /** @@ -109,11 +133,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 +168,8 @@ private void configureSparkFlex(Supplier config) @Override public void setVoltageCompensation(double nominalVoltage) { - configureSparkFlex(() -> motor.enableVoltageCompensation(nominalVoltage)); + cfg.voltageCompensation(nominalVoltage); + cfgUpdated = true; } /** @@ -134,7 +181,9 @@ public void setVoltageCompensation(double nominalVoltage) @Override public void setCurrentLimit(int currentLimit) { - configureSparkFlex(() -> motor.setSmartCurrentLimit(currentLimit)); + + cfg.smartCurrentLimit(currentLimit); + cfgUpdated = true; } /** @@ -145,8 +194,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 +210,17 @@ public Object getMotor() return motor; } + /** + * Get the {@link DCMotor} of the motor class. + * + * @return {@link DCMotor} of this type. + */ + @Override + public DCMotor getSimMotor() + { + return DCMotor.getNeoVortex(1); + } + /** * Queries whether the absolute encoder is directly attached to the motor controller. * @@ -177,11 +238,7 @@ public boolean isAttachedAbsoluteEncoder() @Override public void factoryDefaults() { - if (!factoryDefaultOccurred) - { - configureSparkFlex(motor::restoreFactoryDefaults); - factoryDefaultOccurred = true; - } + // Do nothing } /** @@ -205,12 +262,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 +288,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 +362,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 +377,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 +392,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 +404,8 @@ public void setMotorBrake(boolean isBrakeMode) @Override public void setInverted(boolean inverted) { - configureSparkFlex(() -> { - motor.setInverted(inverted); - return motor.getLastError(); - }); + cfg.inverted(inverted); + cfgUpdated = true; } /** @@ -349,7 +420,8 @@ public void burnFlash() } 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/src/main/java/swervelib/motors/SparkMaxBrushedMotorSwerve.java b/src/main/java/swervelib/motors/SparkMaxBrushedMotorSwerve.java index f384352f..1ce69a82 100644 --- a/src/main/java/swervelib/motors/SparkMaxBrushedMotorSwerve.java +++ b/src/main/java/swervelib/motors/SparkMaxBrushedMotorSwerve.java @@ -1,24 +1,34 @@ 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.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 +36,73 @@ 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 RelativeEncoder encoder; /** * 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. */ - public SparkMaxBrushedMotorSwerve(CANSparkMax motor, boolean isDriveMotor, Type encoderType, int countsPerRevolution, + public SparkMaxBrushedMotorSwerve(SparkMax motor, boolean isDriveMotor, Type encoderType, int countsPerRevolution, boolean useDataPortQuadEncoder) { - 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) @@ -100,36 +124,49 @@ public SparkMaxBrushedMotorSwerve(CANSparkMax motor, boolean isDriveMotor, Type 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 = motor.getAlternateEncoder(); + cfg.alternateEncoder.countsPerRevolution(countsPerRevolution); + + // Configure feedback of the PID controller as the integrated encoder. + cfg.closedLoop.feedbackSensor(FeedbackSensor.kAlternateOrExternalEncoder); + } else + { + this.encoder = motor.getEncoder(); + cfg.encoder.countsPerRevolution(countsPerRevolution); + + // Configure feedback of the PID controller as the integrated encoder. + cfg.closedLoop.feedbackSensor(FeedbackSensor.kPrimaryEncoder); + } + cfgUpdated = true; } + velocity = encoder::getVelocity; + position = encoder::getPosition; // 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. */ public SparkMaxBrushedMotorSwerve(int id, boolean isDriveMotor, Type encoderType, int countsPerRevolution, boolean useDataPortQuadEncoder) { - this(new CANSparkMax(id, MotorType.kBrushed), isDriveMotor, encoderType, countsPerRevolution, + this(new SparkMax(id, MotorType.kBrushed), isDriveMotor, encoderType, countsPerRevolution, useDataPortQuadEncoder); } @@ -146,11 +183,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 +218,8 @@ private void configureSparkMax(Supplier config) @Override public void setVoltageCompensation(double nominalVoltage) { - configureSparkMax(() -> motor.enableVoltageCompensation(nominalVoltage)); + cfg.voltageCompensation(nominalVoltage); + cfgUpdated = true; } /** @@ -171,7 +231,8 @@ public void setVoltageCompensation(double nominalVoltage) @Override public void setCurrentLimit(int currentLimit) { - configureSparkMax(() -> motor.setSmartCurrentLimit(currentLimit)); + cfg.smartCurrentLimit(currentLimit); + cfgUpdated = true; } /** @@ -182,8 +243,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 +259,17 @@ public Object getMotor() return motor; } + /** + * Get the {@link DCMotor} of the motor class. + * + * @return {@link DCMotor} of this type. + */ + @Override + public DCMotor getSimMotor() + { + return DCMotor.getCIM(1); + } + /** * Queries whether the absolute encoder is directly attached to the motor controller. * @@ -214,11 +287,7 @@ public boolean isAttachedAbsoluteEncoder() @Override public void factoryDefaults() { - if (!factoryDefaultOccurred) - { - configureSparkMax(motor::restoreFactoryDefaults); - factoryDefaultOccurred = true; - } + // Do nothing } /** @@ -242,11 +311,25 @@ 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; + + velocity = this.encoder::getVelocity; + position = this.encoder::getPosition; + 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; } if (absoluteEncoder == null && this.encoder == null) { @@ -264,19 +347,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 +430,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 +445,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 +460,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 +472,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 +482,14 @@ public void setInverted(boolean inverted) @Override public void burnFlash() { - configureSparkMax(() -> motor.burnFlash()); + try + { + Thread.sleep(200); + } catch (Exception e) + { + } + motor.configure(cfg, ResetMode.kNoResetSafeParameters, PersistMode.kPersistParameters); + cfgUpdated = false; } /** @@ -388,16 +512,35 @@ 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.setPosition(setpoint); + } + } } /** @@ -454,7 +597,7 @@ public double getAppliedOutput() @Override public double getVelocity() { - return absoluteEncoder == null ? encoder.getVelocity() : absoluteEncoder.getVelocity(); + return velocity.get(); } /** @@ -465,7 +608,7 @@ public double getVelocity() @Override public double getPosition() { - return absoluteEncoder == null ? encoder.getPosition() : absoluteEncoder.getPosition(); + return position.get(); } /** @@ -481,4 +624,20 @@ public void setPosition(double position) configureSparkMax(() -> encoder.setPosition(position)); } } + + /** + * Type for encoder for {@link SparkMax} + */ + public enum Type + { + kNoSensor, + /** + * NO sensor + */ + kHallSensor, + /** + * Hall sensor attached to dataport + */ + kQuadrature, /** Quad encoder attached to alt */ + } } diff --git a/src/main/java/swervelib/motors/SparkMaxSwerve.java b/src/main/java/swervelib/motors/SparkMaxSwerve.java index 14795716..68067a6c 100644 --- a/src/main/java/swervelib/motors/SparkMaxSwerve.java +++ b/src/main/java/swervelib/motors/SparkMaxSwerve.java @@ -1,57 +1,73 @@ 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. @@ -59,7 +75,7 @@ public class SparkMaxSwerve extends SwerveMotor * @param motor The SwerveMotor as a SparkMax object. * @param isDriveMotor Is the motor being initialized a drive motor? */ - public SparkMaxSwerve(CANSparkMax motor, boolean isDriveMotor) + public SparkMaxSwerve(SparkMax motor, boolean isDriveMotor) { this.motor = motor; this.isDriveMotor = isDriveMotor; @@ -67,25 +83,26 @@ public SparkMaxSwerve(CANSparkMax 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; 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? */ public SparkMaxSwerve(int id, boolean isDriveMotor) { - this(new CANSparkMax(id, MotorType.kBrushless), isDriveMotor); + this(new SparkMax(id, MotorType.kBrushless), isDriveMotor); } /** @@ -101,11 +118,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 +153,8 @@ private void configureSparkMax(Supplier config) @Override public void setVoltageCompensation(double nominalVoltage) { - configureSparkMax(() -> motor.enableVoltageCompensation(nominalVoltage)); + cfg.voltageCompensation(nominalVoltage); + cfgUpdated = true; } /** @@ -126,7 +166,9 @@ public void setVoltageCompensation(double nominalVoltage) @Override public void setCurrentLimit(int currentLimit) { - configureSparkMax(() -> motor.setSmartCurrentLimit(currentLimit)); + cfg.smartCurrentLimit(currentLimit); + cfgUpdated = true; + } /** @@ -137,8 +179,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 +196,17 @@ public Object getMotor() return motor; } + /** + * Get the {@link DCMotor} of the motor class. + * + * @return {@link DCMotor} of this type. + */ + @Override + public DCMotor getSimMotor() + { + return DCMotor.getNEO(1); + } + /** * Queries whether the absolute encoder is directly attached to the motor controller. * @@ -169,11 +224,7 @@ public boolean isAttachedAbsoluteEncoder() @Override public void factoryDefaults() { - if (!factoryDefaultOccurred) - { - configureSparkMax(motor::restoreFactoryDefaults); - factoryDefaultOccurred = true; - } + // Do nothing } /** @@ -197,17 +248,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 +279,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 +306,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 +365,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 +381,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 +396,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 +409,8 @@ public void setMotorBrake(boolean isBrakeMode) @Override public void setInverted(boolean inverted) { - configureSparkMax(() -> { - motor.setInverted(inverted); - return motor.getLastError(); - }); + cfg.inverted(inverted); + cfgUpdated = true; } /** @@ -377,7 +425,8 @@ public void burnFlash() } catch (Exception e) { } - configureSparkMax(() -> motor.burnFlash()); + motor.configure(cfg, ResetMode.kNoResetSafeParameters, PersistMode.kPersistParameters); + cfgUpdated = false; } /** @@ -400,11 +449,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 +561,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/src/main/java/swervelib/motors/SwerveMotor.java b/src/main/java/swervelib/motors/SwerveMotor.java index 22b104d4..bae6d2f4 100644 --- a/src/main/java/swervelib/motors/SwerveMotor.java +++ b/src/main/java/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; @@ -172,6 +173,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/src/main/java/swervelib/motors/TalonFXSwerve.java b/src/main/java/swervelib/motors/TalonFXSwerve.java index 233a868f..635df8ad 100644 --- a/src/main/java/swervelib/motors/TalonFXSwerve.java +++ b/src/main/java/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; /** @@ -181,63 +187,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 +240,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 +310,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 +343,7 @@ public double getAppliedOutput() @Override public double getVelocity() { - return motor.getVelocity().getValue(); + return motor.getVelocity().getValue().magnitude(); } /** @@ -401,7 +354,7 @@ public double getVelocity() @Override public double getPosition() { - return motor.getPosition().getValue(); + return motor.getPosition().getValue().magnitude(); } /** @@ -414,8 +367,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 +393,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 +420,17 @@ public Object getMotor() return motor; } + /** + * Get the {@link DCMotor} of the motor class. + * + * @return {@link DCMotor} of this type. + */ + @Override + public DCMotor getSimMotor() + { + return DCMotor.getKrakenX60(1); + } + /** * Queries whether the absolute encoder is directly attached to the motor controller. * diff --git a/src/main/java/swervelib/motors/TalonSRXSwerve.java b/src/main/java/swervelib/motors/TalonSRXSwerve.java index 98cf6e7c..28d2e356 100644 --- a/src/main/java/swervelib/motors/TalonSRXSwerve.java +++ b/src/main/java/swervelib/motors/TalonSRXSwerve.java @@ -7,6 +7,7 @@ import com.ctre.phoenix.motorcontrol.StatusFrameEnhanced; import com.ctre.phoenix.motorcontrol.can.TalonSRXConfiguration; import com.ctre.phoenix.motorcontrol.can.WPI_TalonSRX; +import edu.wpi.first.math.system.plant.DCMotor; import swervelib.encoders.SwerveAbsoluteEncoder; import swervelib.math.SwerveMath; import swervelib.parser.PIDFConfig; @@ -21,31 +22,31 @@ 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. @@ -432,6 +433,17 @@ public Object getMotor() return motor; } + /** + * Get the {@link DCMotor} of the motor class. + * + * @return {@link DCMotor} of this type. + */ + @Override + public DCMotor getSimMotor() + { + return DCMotor.getCIM(1); + } + /** * Queries whether the absolute encoder is directly attached to the motor controller. * diff --git a/src/main/java/swervelib/parser/Cache.java b/src/main/java/swervelib/parser/Cache.java index 830a08c0..830a2731 100644 --- a/src/main/java/swervelib/parser/Cache.java +++ b/src/main/java/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/src/main/java/swervelib/parser/SwerveDriveConfiguration.java b/src/main/java/swervelib/parser/SwerveDriveConfiguration.java index 347b187d..9a6eebb4 100644 --- a/src/main/java/swervelib/parser/SwerveDriveConfiguration.java +++ b/src/main/java/swervelib/parser/SwerveDriveConfiguration.java @@ -2,8 +2,14 @@ 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. @@ -96,4 +102,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/src/main/java/swervelib/parser/SwerveModuleConfiguration.java b/src/main/java/swervelib/parser/SwerveModuleConfiguration.java index 24d06257..8996f448 100644 --- a/src/main/java/swervelib/parser/SwerveModuleConfiguration.java +++ b/src/main/java/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/src/main/java/swervelib/parser/SwerveModulePhysicalCharacteristics.java b/src/main/java/swervelib/parser/SwerveModulePhysicalCharacteristics.java index 99360ce1..a626bbb8 100644 --- a/src/main/java/swervelib/parser/SwerveModulePhysicalCharacteristics.java +++ b/src/main/java/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/src/main/java/swervelib/parser/SwerveParser.java b/src/main/java/swervelib/parser/SwerveParser.java index 573fdd87..aa87c8e8 100644 --- a/src/main/java/swervelib/parser/SwerveParser.java +++ b/src/main/java/swervelib/parser/SwerveParser.java @@ -3,6 +3,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; @@ -138,7 +139,24 @@ public SwerveDrive createSwerveDrive(double maxSpeed) return createSwerveDrive(SwerveMath.createDriveFeedforward(physicalPropertiesJson.optimalVoltage, maxSpeed, physicalPropertiesJson.wheelGripCoefficientOfFriction), - maxSpeed); + maxSpeed, Pose2d.kZero); + } + + /** + * Create {@link SwerveDrive} from JSON configuration directory. + * + * @param maxSpeed Maximum speed of the robot in meters per second, used for both angular acceleration used in + * {@link swervelib.SwerveController} and drive feedforward in + * {@link SwerveMath#createDriveFeedforward(double, double, double)}. + * @param initialPose {@link Pose2d} starting point on the field + * @return {@link SwerveDrive} instance. + */ + public SwerveDrive createSwerveDrive(double maxSpeed, Pose2d initialPose) + { + return createSwerveDrive(SwerveMath.createDriveFeedforward(physicalPropertiesJson.optimalVoltage, + maxSpeed, + physicalPropertiesJson.wheelGripCoefficientOfFriction), + maxSpeed, initialPose); } /** @@ -157,12 +175,12 @@ public SwerveDrive createSwerveDrive(double maxSpeed) */ public SwerveDrive createSwerveDrive(double maxSpeed, double angleMotorConversionFactor, double driveMotorConversion) { - physicalPropertiesJson.conversionFactor.angle = angleMotorConversionFactor; - physicalPropertiesJson.conversionFactor.drive = driveMotorConversion; + physicalPropertiesJson.conversionFactors.angle.factor = angleMotorConversionFactor; + physicalPropertiesJson.conversionFactors.drive.factor = driveMotorConversion; return createSwerveDrive(SwerveMath.createDriveFeedforward(physicalPropertiesJson.optimalVoltage, maxSpeed, physicalPropertiesJson.wheelGripCoefficientOfFriction), - maxSpeed); + maxSpeed, Pose2d.kZero); } /** @@ -184,9 +202,9 @@ public SwerveDrive createSwerveDrive(double maxSpeed, double angleMotorConversio public SwerveDrive createSwerveDrive(SimpleMotorFeedforward driveFeedforward, double maxSpeed, double angleMotorConversionFactor, double driveMotorConversion) { - physicalPropertiesJson.conversionFactor.angle = angleMotorConversionFactor; - physicalPropertiesJson.conversionFactor.drive = driveMotorConversion; - return createSwerveDrive(driveFeedforward, maxSpeed); + physicalPropertiesJson.conversionFactors.angle.factor = angleMotorConversionFactor; + physicalPropertiesJson.conversionFactors.drive.factor = driveMotorConversion; + return createSwerveDrive(driveFeedforward, maxSpeed, Pose2d.kZero); } /** @@ -196,9 +214,10 @@ public SwerveDrive createSwerveDrive(SimpleMotorFeedforward driveFeedforward, do * {@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 initialPose {@link Pose2d} initial pose. * @return {@link SwerveDrive} instance. */ - public SwerveDrive createSwerveDrive(SimpleMotorFeedforward driveFeedforward, double maxSpeed) + public SwerveDrive createSwerveDrive(SimpleMotorFeedforward driveFeedforward, double maxSpeed, Pose2d initialPose) { SwerveModuleConfiguration[] moduleConfigurations = new SwerveModuleConfiguration[moduleJsons.length]; @@ -222,6 +241,8 @@ public SwerveDrive createSwerveDrive(SimpleMotorFeedforward driveFeedforward, do return new SwerveDrive( swerveDriveConfiguration, - controllerPropertiesJson.createControllerConfiguration(swerveDriveConfiguration, maxSpeed), maxSpeed); + controllerPropertiesJson.createControllerConfiguration(swerveDriveConfiguration, maxSpeed), + maxSpeed, + initialPose); } } diff --git a/src/main/java/swervelib/parser/json/DeviceJson.java b/src/main/java/swervelib/parser/json/DeviceJson.java index 7ce6fc3d..ee03ccd1 100644 --- a/src/main/java/swervelib/parser/json/DeviceJson.java +++ b/src/main/java/swervelib/parser/json/DeviceJson.java @@ -4,11 +4,8 @@ import static swervelib.telemetry.SwerveDriveTelemetry.i2cLockupWarning; import static swervelib.telemetry.SwerveDriveTelemetry.serialCommsIssueWarning; -import com.revrobotics.SparkRelativeEncoder.Type; +import com.studica.frc.AHRS.NavXComType; 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; @@ -27,6 +24,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; @@ -122,7 +120,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 +128,17 @@ 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); + throw new RuntimeException("Studica NavX API does not support MXP Serial communication currently."); + +// return new NavXSwerve(Port.kMXP); case "pigeon": return new PigeonSwerve(id); case "pigeon2": diff --git a/src/main/java/swervelib/parser/json/ModuleJson.java b/src/main/java/swervelib/parser/json/ModuleJson.java index 2fa83c89..f26b2e84 100644 --- a/src/main/java/swervelib/parser/json/ModuleJson.java +++ b/src/main/java/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/src/main/java/swervelib/parser/json/PhysicalPropertiesJson.java b/src/main/java/swervelib/parser/json/PhysicalPropertiesJson.java index 60be9d25..eaab95d6 100644 --- a/src/main/java/swervelib/parser/json/PhysicalPropertiesJson.java +++ b/src/main/java/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/src/main/java/swervelib/parser/json/modules/AngleConversionFactorsJson.java b/src/main/java/swervelib/parser/json/modules/AngleConversionFactorsJson.java index a7c8613a..5a3e10f9 100644 --- a/src/main/java/swervelib/parser/json/modules/AngleConversionFactorsJson.java +++ b/src/main/java/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/src/main/java/swervelib/parser/json/modules/ConversionFactorsJson.java b/src/main/java/swervelib/parser/json/modules/ConversionFactorsJson.java index 85ff25f2..546ad65a 100644 --- a/src/main/java/swervelib/parser/json/modules/ConversionFactorsJson.java +++ b/src/main/java/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/src/main/java/swervelib/parser/json/modules/DriveConversionFactorsJson.java b/src/main/java/swervelib/parser/json/modules/DriveConversionFactorsJson.java index d792d36d..937b78a1 100644 --- a/src/main/java/swervelib/parser/json/modules/DriveConversionFactorsJson.java +++ b/src/main/java/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/src/main/java/swervelib/simulation/SwerveIMUSimulation.java b/src/main/java/swervelib/simulation/SwerveIMUSimulation.java index b3f2b62d..f9e3b995 100644 --- a/src/main/java/swervelib/simulation/SwerveIMUSimulation.java +++ b/src/main/java/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,14 @@ 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. */ - public SwerveIMUSimulation() + public SwerveIMUSimulation(GyroSimulation gyroSimulation) { - timer = new Timer(); - timer.start(); - lastTime = timer.get(); + this.gyroSimulation = gyroSimulation; } /** @@ -46,7 +33,7 @@ public SwerveIMUSimulation() */ public Rotation2d getYaw() { - return new Rotation2d(angle); + return gyroSimulation.getGyroReading(); } /** @@ -76,7 +63,7 @@ public Rotation2d getRoll() */ public Rotation3d getGyroRotation3d() { - return new Rotation3d(0, 0, angle); + return new Rotation3d(0, 0, getYaw().getRadians()); } /** @@ -104,9 +91,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 +101,6 @@ public void updateOdometry( */ public void setAngle(double angle) { - this.angle = angle; + this.gyroSimulation.setRotation(Rotation2d.fromRadians(angle)); } } diff --git a/src/main/java/swervelib/simulation/SwerveModuleSimulation.java b/src/main/java/swervelib/simulation/SwerveModuleSimulation.java index 2fb06f8f..97b85ba2 100644 --- a/src/main/java/swervelib/simulation/SwerveModuleSimulation.java +++ b/src/main/java/swervelib/simulation/SwerveModuleSimulation.java @@ -1,54 +1,35 @@ package swervelib.simulation; +import static edu.wpi.first.units.Units.Meters; +import static edu.wpi.first.units.Units.RPM; +import static edu.wpi.first.units.Units.Radian; +import static edu.wpi.first.units.Units.RadiansPerSecond; +import static edu.wpi.first.units.Units.Volts; + 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.motorsims.ControlRequest; /** - * Class to hold simulation data for {@link swervelib.SwerveModule} + * Class that wraps around {@link org.ironmaple.simulation.drivesims.SwerveModuleSimulation} */ public class SwerveModuleSimulation { + private org.ironmaple.simulation.drivesims.SwerveModuleSimulation 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 mapleSimModule the {@link org.ironmaple.simulation.drivesims.SwerveModuleSimulation} object for simulation */ - public SwerveModuleSimulation() + public void configureSimModule(org.ironmaple.simulation.drivesims.SwerveModuleSimulation mapleSimModule) { - timer = new Timer(); - timer.start(); - lastTime = timer.get(); - state = new SwerveModuleState(0, Rotation2d.fromDegrees(0)); - fakeSpeed = 0; - fakePos = 0; - dt = 0; + this.mapleSimModule = mapleSimModule; + mapleSimModule.getDriveMotorConfigs() + .withDefaultFeedForward(Volts.zero()) + .withVelocityVoltageController(Volts.per(RPM).ofNative(7.0 / 3000.0), true); } /** @@ -59,13 +40,10 @@ public SwerveModuleSimulation() */ public void updateStateAndPosition(SwerveModuleState desiredState) { - dt = timer.get() - lastTime; - lastTime = timer.get(); - - state = desiredState; - fakeSpeed = desiredState.speedMetersPerSecond; - fakePos += (fakeSpeed * dt); - + mapleSimModule.requestSteerControl(new ControlRequest.PositionVoltage(desiredState.angle.getMeasure())); + mapleSimModule.requestDriveControl(new ControlRequest.VelocityVoltage( + RadiansPerSecond.of(desiredState.speedMetersPerSecond / mapleSimModule.WHEEL_RADIUS.in(Meters)) + )); } /** @@ -75,8 +53,10 @@ public void updateStateAndPosition(SwerveModuleState desiredState) */ public SwerveModulePosition getPosition() { - - return new SwerveModulePosition(fakePos, state.angle); + return new SwerveModulePosition( + mapleSimModule.getDriveWheelFinalPosition().in(Radian) * mapleSimModule.WHEEL_RADIUS.in(Meters), + mapleSimModule.getSteerAbsoluteFacing() + ); } /** @@ -86,6 +66,12 @@ public SwerveModulePosition getPosition() */ public SwerveModuleState getState() { + if (mapleSimModule == null) + { + return new SwerveModuleState(); + } + SwerveModuleState state = mapleSimModule.getCurrentState(); + state.angle = state.angle.minus(new Rotation2d()); return state; } } diff --git a/src/main/java/swervelib/telemetry/Alert.java b/src/main/java/swervelib/telemetry/Alert.java deleted file mode 100644 index 40f9af78..00000000 --- a/src/main/java/swervelib/telemetry/Alert.java +++ /dev/null @@ -1,237 +0,0 @@ -// Copyright (c) 2023 FRC 6328 -// http://github.com/Mechanical-Advantage -// -// Use of this source code is governed by an MIT-style -// license that can be found below. - -// MIT License -// -// Copyright (c) 2023 FRC 6328 -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -package swervelib.telemetry; - -import edu.wpi.first.util.sendable.Sendable; -import edu.wpi.first.util.sendable.SendableBuilder; -import edu.wpi.first.wpilibj.DriverStation; -import edu.wpi.first.wpilibj.Timer; -import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * Class for managing persistent alerts to be sent over NetworkTables. - */ -public class Alert -{ - - /** - * Group of the alert. - */ - private static Map groups = new HashMap(); - /** - * Type of the Alert to raise. - */ - private final AlertType type; - /** - * Activation state of alert. - */ - private boolean active = false; - /** - * When the alert was raised. - */ - private double activeStartTime = 0.0; - /** - * Text of the alert. - */ - private String text; - - /** - * Creates a new Alert in the default group - "Alerts". If this is the first to be instantiated, the appropriate - * entries will be added to NetworkTables. - * - * @param text Text to be displayed when the alert is active. - * @param type Alert level specifying urgency. - */ - public Alert(String text, AlertType type) - { - this("Alerts", text, type); - } - - /** - * Creates a new Alert. If this is the first to be instantiated in its group, the appropriate entries will be added to - * NetworkTables. - * - * @param group Group identifier, also used as NetworkTables title - * @param text Text to be displayed when the alert is active. - * @param type Alert level specifying urgency. - */ - public Alert(String group, String text, AlertType type) - { - if (!groups.containsKey(group)) - { - groups.put(group, new SendableAlerts()); - SmartDashboard.putData(group, groups.get(group)); - } - - this.text = text; - this.type = type; - groups.get(group).alerts.add(this); - } - - /** - * Sets whether the alert should currently be displayed. When activated, the alert text will also be sent to the - * console. - * - * @param active Set the alert as active and report it to the driver station. - */ - public void set(boolean active) - { - if (active && !this.active) - { - activeStartTime = Timer.getFPGATimestamp(); - printAlert(text); - } - this.active = active; - } - - /** - * Updates current alert text. - * - * @param text The text for the alert. - */ - public void setText(String text) - { - if (active && !text.equals(this.text)) - { - printAlert(text); - } - this.text = text; - } - - - /** - * Print the alert message. - * - * @param text Text to print. - */ - private void printAlert(String text) - { - switch (type) - { - case ERROR: - DriverStation.reportError(text, false); - break; - case ERROR_TRACE: - DriverStation.reportError(text, true); - break; - case WARNING: - DriverStation.reportWarning(text, false); - break; - case WARNING_TRACE: - DriverStation.reportWarning(text, true); - break; - case INFO: - System.out.println(text); - break; - } - } - - /** - * Represents an alert's level of urgency. - */ - public static enum AlertType - { - /** - * High priority alert - displayed first on the dashboard with a red "X" symbol. Use this type for problems which - * will seriously affect the robot's functionality and thus require immediate attention. - */ - ERROR, - /** - * High priority alert - displayed first on the dashboard with a red "X" symbol. Use this type for problems which - * will seriously affect the robot's functionality and thus require immediate attention. Trace printed to driver - * station console. - */ - ERROR_TRACE, - - /** - * Medium priority alert - displayed second on the dashboard with a yellow "!" symbol. Use this type for problems - * which could affect the robot's functionality but do not necessarily require immediate attention. - */ - WARNING, - /** - * Medium priority alert - displayed second on the dashboard with a yellow "!" symbol. Use this type for problems - * which could affect the robot's functionality but do not necessarily require immediate attention. Trace printed to - * driver station console. - */ - WARNING_TRACE, - /** - * Low priority alert - displayed last on the dashboard with a green "i" symbol. Use this type for problems which - * are unlikely to affect the robot's functionality, or any other alerts which do not fall under "ERROR" or - * "WARNING". - */ - INFO - } - - /** - * Sendable alert for advantage scope. - */ - private static class SendableAlerts implements Sendable - { - - /** - * Alert list for sendable. - */ - public final List alerts = new ArrayList<>(); - - /** - * Get alerts based off of type. - * - * @param type Type of alert to fetch. - * @return Active alert strings. - */ - public String[] getStrings(AlertType type) - { - List alertStrings = new ArrayList<>(); - for (Alert alert : alerts) - { - if (alert.type == type && alert.active) - { - alertStrings.add(alert.text); - } - } - // alertStrings.sort((a1, a2) -> (int) (a2.activeStartTime - a1.activeStartTime)); - return alertStrings.toArray(new String[alertStrings.size()]); - } - - @Override - public void initSendable(SendableBuilder builder) - { - builder.setSmartDashboardType("Alerts"); - builder.addStringArrayProperty("errors", () -> getStrings(AlertType.ERROR), null); - builder.addStringArrayProperty("errors", () -> getStrings(AlertType.ERROR_TRACE), null); - builder.addStringArrayProperty("warnings", () -> getStrings(AlertType.WARNING), null); - builder.addStringArrayProperty("warnings", () -> getStrings(AlertType.WARNING_TRACE), null); - builder.addStringArrayProperty("infos", () -> getStrings(AlertType.INFO), null); - } - } -} \ No newline at end of file diff --git a/src/main/java/swervelib/telemetry/SwerveDriveTelemetry.java b/src/main/java/swervelib/telemetry/SwerveDriveTelemetry.java index 3c90fc6f..e4d98380 100644 --- a/src/main/java/swervelib/telemetry/SwerveDriveTelemetry.java +++ b/src/main/java/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/measuredChassisSpeeds", + 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); } /** diff --git a/vendordeps/NavX.json b/vendordeps/NavX.json deleted file mode 100644 index e978a5f7..00000000 --- a/vendordeps/NavX.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "fileName": "NavX.json", - "name": "NavX", - "version": "2024.1.0", - "uuid": "cb311d09-36e9-4143-a032-55bb2b94443b", - "frcYear": "2024", - "mavenUrls": [ - "https://dev.studica.com/maven/release/2024/" - ], - "jsonUrl": "https://dev.studica.com/releases/2024/NavX.json", - "javaDependencies": [ - { - "groupId": "com.kauailabs.navx.frc", - "artifactId": "navx-frc-java", - "version": "2024.1.0" - } - ], - "jniDependencies": [], - "cppDependencies": [ - { - "groupId": "com.kauailabs.navx.frc", - "artifactId": "navx-frc-cpp", - "version": "2024.1.0", - "headerClassifier": "headers", - "sourcesClassifier": "sources", - "sharedLibrary": false, - "libName": "navx_frc", - "skipInvalidPlatforms": true, - "binaryPlatforms": [ - "linuxathena", - "linuxraspbian", - "linuxarm32", - "linuxarm64", - "linuxx86-64", - "osxuniversal", - "windowsx86-64" - ] - } - ] -} \ No newline at end of file diff --git a/vendordeps/PathplannerLib.json b/vendordeps/PathplannerLib-beta.json similarity index 81% rename from vendordeps/PathplannerLib.json rename to vendordeps/PathplannerLib-beta.json index 6dc648db..e79fe1ae 100644 --- a/vendordeps/PathplannerLib.json +++ b/vendordeps/PathplannerLib-beta.json @@ -1,18 +1,18 @@ { - "fileName": "PathplannerLib.json", + "fileName": "PathplannerLib-beta.json", "name": "PathplannerLib", - "version": "2024.2.8", + "version": "2025.0.0-beta-5", "uuid": "1b42324f-17c6-4875-8e77-1c312bc8c786", - "frcYear": "2024", + "frcYear": "2025", "mavenUrls": [ "https://3015rangerrobotics.github.io/pathplannerlib/repo" ], - "jsonUrl": "https://3015rangerrobotics.github.io/pathplannerlib/PathplannerLib.json", + "jsonUrl": "https://3015rangerrobotics.github.io/pathplannerlib/PathplannerLib-beta.json", "javaDependencies": [ { "groupId": "com.pathplanner.lib", "artifactId": "PathplannerLib-java", - "version": "2024.2.8" + "version": "2025.0.0-beta-5" } ], "jniDependencies": [], @@ -20,15 +20,15 @@ { "groupId": "com.pathplanner.lib", "artifactId": "PathplannerLib-cpp", - "version": "2024.2.8", + "version": "2025.0.0-beta-5", "libName": "PathplannerLib", "headerClassifier": "headers", "sharedLibrary": false, "skipInvalidPlatforms": true, "binaryPlatforms": [ "windowsx86-64", - "linuxx86-64", "osxuniversal", + "linuxx86-64", "linuxathena", "linuxarm32", "linuxarm64" diff --git a/vendordeps/Phoenix5.json b/vendordeps/Phoenix5-frc2025-beta-latest.json similarity index 73% rename from vendordeps/Phoenix5.json rename to vendordeps/Phoenix5-frc2025-beta-latest.json index ff7359e1..65dfcff9 100644 --- a/vendordeps/Phoenix5.json +++ b/vendordeps/Phoenix5-frc2025-beta-latest.json @@ -1,43 +1,56 @@ { - "fileName": "Phoenix5.json", + "fileName": "Phoenix5-frc2025-beta-latest.json", "name": "CTRE-Phoenix (v5)", - "version": "5.33.1", - "frcYear": 2024, + "version": "5.34.0-beta-3", + "frcYear": "2025", "uuid": "ab676553-b602-441f-a38d-f1296eff6537", "mavenUrls": [ "https://maven.ctr-electronics.com/release/" ], - "jsonUrl": "https://maven.ctr-electronics.com/release/com/ctre/phoenix/Phoenix5-frc2024-latest.json", + "jsonUrl": "https://maven.ctr-electronics.com/release/com/ctre/phoenix/Phoenix5-frc2025-beta-latest.json", "requires": [ { "uuid": "e995de00-2c64-4df5-8831-c1441420ff19", "errorMessage": "Phoenix 5 requires low-level libraries from Phoenix 6. Please add the Phoenix 6 vendordep before adding Phoenix 5.", - "offlineFileName": "Phoenix6.json", - "onlineUrl": "https://maven.ctr-electronics.com/release/com/ctre/phoenix6/latest/Phoenix6-frc2024-latest.json" + "offlineFileName": "Phoenix6-frc2025-beta-latest.json", + "onlineUrl": "https://maven.ctr-electronics.com/release/com/ctre/phoenix6/latest/Phoenix6-frc2025-beta-latest.json" + } + ], + "conflictsWith": [ + { + "uuid": "e7900d8d-826f-4dca-a1ff-182f658e98af", + "errorMessage": "Users must use the Phoenix 5 replay vendordep when using the Phoenix 6 replay vendordep.", + "offlineFileName": "Phoenix6-replay-frc2025-beta-latest.json" + }, + { + "uuid": "fbc886a4-2cec-40c0-9835-71086a8cc3df", + "errorMessage": "Users cannot have both the replay and regular Phoenix 5 vendordeps in their robot program.", + "offlineFileName": "Phoenix5-replay-frc2025-beta-latest.json" } ], "javaDependencies": [ { "groupId": "com.ctre.phoenix", "artifactId": "api-java", - "version": "5.33.1" + "version": "5.34.0-beta-3" }, { "groupId": "com.ctre.phoenix", "artifactId": "wpiapi-java", - "version": "5.33.1" + "version": "5.34.0-beta-3" } ], "jniDependencies": [ { "groupId": "com.ctre.phoenix", "artifactId": "cci", - "version": "5.33.1", + "version": "5.34.0-beta-3", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "linuxathena" ], "simMode": "hwsim" @@ -45,12 +58,13 @@ { "groupId": "com.ctre.phoenix.sim", "artifactId": "cci-sim", - "version": "5.33.1", + "version": "5.34.0-beta-3", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "osxuniversal" ], "simMode": "swsim" @@ -60,7 +74,7 @@ { "groupId": "com.ctre.phoenix", "artifactId": "wpiapi-cpp", - "version": "5.33.1", + "version": "5.34.0-beta-3", "libName": "CTRE_Phoenix_WPI", "headerClassifier": "headers", "sharedLibrary": true, @@ -68,6 +82,7 @@ "binaryPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "linuxathena" ], "simMode": "hwsim" @@ -75,7 +90,7 @@ { "groupId": "com.ctre.phoenix", "artifactId": "api-cpp", - "version": "5.33.1", + "version": "5.34.0-beta-3", "libName": "CTRE_Phoenix", "headerClassifier": "headers", "sharedLibrary": true, @@ -83,6 +98,7 @@ "binaryPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "linuxathena" ], "simMode": "hwsim" @@ -90,7 +106,7 @@ { "groupId": "com.ctre.phoenix", "artifactId": "cci", - "version": "5.33.1", + "version": "5.34.0-beta-3", "libName": "CTRE_PhoenixCCI", "headerClassifier": "headers", "sharedLibrary": true, @@ -98,6 +114,7 @@ "binaryPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "linuxathena" ], "simMode": "hwsim" @@ -105,7 +122,7 @@ { "groupId": "com.ctre.phoenix.sim", "artifactId": "wpiapi-cpp-sim", - "version": "5.33.1", + "version": "5.34.0-beta-3", "libName": "CTRE_Phoenix_WPISim", "headerClassifier": "headers", "sharedLibrary": true, @@ -113,6 +130,7 @@ "binaryPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "osxuniversal" ], "simMode": "swsim" @@ -120,7 +138,7 @@ { "groupId": "com.ctre.phoenix.sim", "artifactId": "api-cpp-sim", - "version": "5.33.1", + "version": "5.34.0-beta-3", "libName": "CTRE_PhoenixSim", "headerClassifier": "headers", "sharedLibrary": true, @@ -128,6 +146,7 @@ "binaryPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "osxuniversal" ], "simMode": "swsim" @@ -135,7 +154,7 @@ { "groupId": "com.ctre.phoenix.sim", "artifactId": "cci-sim", - "version": "5.33.1", + "version": "5.34.0-beta-3", "libName": "CTRE_PhoenixCCISim", "headerClassifier": "headers", "sharedLibrary": true, @@ -143,6 +162,7 @@ "binaryPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "osxuniversal" ], "simMode": "swsim" diff --git a/vendordeps/Phoenix6.json b/vendordeps/Phoenix6-frc2025-beta-latest.json similarity index 80% rename from vendordeps/Phoenix6.json rename to vendordeps/Phoenix6-frc2025-beta-latest.json index 5eadb206..ff0b8c9e 100644 --- a/vendordeps/Phoenix6.json +++ b/vendordeps/Phoenix6-frc2025-beta-latest.json @@ -1,76 +1,94 @@ { - "fileName": "Phoenix6.json", + "fileName": "Phoenix6-frc2025-beta-latest.json", "name": "CTRE-Phoenix (v6)", - "version": "24.3.0", - "frcYear": 2024, + "version": "25.0.0-beta-3", + "frcYear": "2025", "uuid": "e995de00-2c64-4df5-8831-c1441420ff19", "mavenUrls": [ "https://maven.ctr-electronics.com/release/" ], - "jsonUrl": "https://maven.ctr-electronics.com/release/com/ctre/phoenix6/latest/Phoenix6-frc2024-latest.json", + "jsonUrl": "https://maven.ctr-electronics.com/release/com/ctre/phoenix6/latest/Phoenix6-frc2025-beta-latest.json", "conflictsWith": [ { - "uuid": "3fcf3402-e646-4fa6-971e-18afe8173b1a", - "errorMessage": "The combined Phoenix-6-And-5 vendordep is no longer supported. Please remove the vendordep and instead add both the latest Phoenix 6 vendordep and Phoenix 5 vendordep.", - "offlineFileName": "Phoenix6And5.json" + "uuid": "e7900d8d-826f-4dca-a1ff-182f658e98af", + "errorMessage": "Users can not have both the replay and regular Phoenix 6 vendordeps in their robot program.", + "offlineFileName": "Phoenix6-replay-frc2025-beta-latest.json" } ], "javaDependencies": [ { "groupId": "com.ctre.phoenix6", "artifactId": "wpiapi-java", - "version": "24.3.0" + "version": "25.0.0-beta-3" } ], "jniDependencies": [ + { + "groupId": "com.ctre.phoenix6", + "artifactId": "api-cpp", + "version": "25.0.0-beta-3", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxarm64", + "linuxathena" + ], + "simMode": "hwsim" + }, { "groupId": "com.ctre.phoenix6", "artifactId": "tools", - "version": "24.3.0", + "version": "25.0.0-beta-3", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "linuxathena" ], "simMode": "hwsim" }, { "groupId": "com.ctre.phoenix6.sim", - "artifactId": "tools-sim", - "version": "24.3.0", + "artifactId": "api-cpp-sim", + "version": "25.0.0-beta-3", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "osxuniversal" ], "simMode": "swsim" }, { "groupId": "com.ctre.phoenix6.sim", - "artifactId": "simTalonSRX", - "version": "24.3.0", + "artifactId": "tools-sim", + "version": "25.0.0-beta-3", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "osxuniversal" ], "simMode": "swsim" }, { "groupId": "com.ctre.phoenix6.sim", - "artifactId": "simTalonFX", - "version": "24.3.0", + "artifactId": "simTalonSRX", + "version": "25.0.0-beta-3", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "osxuniversal" ], "simMode": "swsim" @@ -78,12 +96,13 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simVictorSPX", - "version": "24.3.0", + "version": "25.0.0-beta-3", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "osxuniversal" ], "simMode": "swsim" @@ -91,12 +110,13 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simPigeonIMU", - "version": "24.3.0", + "version": "25.0.0-beta-3", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "osxuniversal" ], "simMode": "swsim" @@ -104,12 +124,13 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simCANCoder", - "version": "24.3.0", + "version": "25.0.0-beta-3", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "osxuniversal" ], "simMode": "swsim" @@ -117,12 +138,13 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simProTalonFX", - "version": "24.3.0", + "version": "25.0.0-beta-3", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "osxuniversal" ], "simMode": "swsim" @@ -130,12 +152,13 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simProCANcoder", - "version": "24.3.0", + "version": "25.0.0-beta-3", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "osxuniversal" ], "simMode": "swsim" @@ -143,12 +166,13 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simProPigeon2", - "version": "24.3.0", + "version": "25.0.0-beta-3", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "osxuniversal" ], "simMode": "swsim" @@ -158,7 +182,7 @@ { "groupId": "com.ctre.phoenix6", "artifactId": "wpiapi-cpp", - "version": "24.3.0", + "version": "25.0.0-beta-3", "libName": "CTRE_Phoenix6_WPI", "headerClassifier": "headers", "sharedLibrary": true, @@ -166,6 +190,7 @@ "binaryPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "linuxathena" ], "simMode": "hwsim" @@ -173,7 +198,7 @@ { "groupId": "com.ctre.phoenix6", "artifactId": "tools", - "version": "24.3.0", + "version": "25.0.0-beta-3", "libName": "CTRE_PhoenixTools", "headerClassifier": "headers", "sharedLibrary": true, @@ -181,6 +206,7 @@ "binaryPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "linuxathena" ], "simMode": "hwsim" @@ -188,7 +214,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "wpiapi-cpp-sim", - "version": "24.3.0", + "version": "25.0.0-beta-3", "libName": "CTRE_Phoenix6_WPISim", "headerClassifier": "headers", "sharedLibrary": true, @@ -196,6 +222,7 @@ "binaryPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "osxuniversal" ], "simMode": "swsim" @@ -203,7 +230,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "tools-sim", - "version": "24.3.0", + "version": "25.0.0-beta-3", "libName": "CTRE_PhoenixTools_Sim", "headerClassifier": "headers", "sharedLibrary": true, @@ -211,6 +238,7 @@ "binaryPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "osxuniversal" ], "simMode": "swsim" @@ -218,7 +246,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simTalonSRX", - "version": "24.3.0", + "version": "25.0.0-beta-3", "libName": "CTRE_SimTalonSRX", "headerClassifier": "headers", "sharedLibrary": true, @@ -226,21 +254,7 @@ "binaryPlatforms": [ "windowsx86-64", "linuxx86-64", - "osxuniversal" - ], - "simMode": "swsim" - }, - { - "groupId": "com.ctre.phoenix6.sim", - "artifactId": "simTalonFX", - "version": "24.3.0", - "libName": "CTRE_SimTalonFX", - "headerClassifier": "headers", - "sharedLibrary": true, - "skipInvalidPlatforms": true, - "binaryPlatforms": [ - "windowsx86-64", - "linuxx86-64", + "linuxarm64", "osxuniversal" ], "simMode": "swsim" @@ -248,7 +262,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simVictorSPX", - "version": "24.3.0", + "version": "25.0.0-beta-3", "libName": "CTRE_SimVictorSPX", "headerClassifier": "headers", "sharedLibrary": true, @@ -256,6 +270,7 @@ "binaryPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "osxuniversal" ], "simMode": "swsim" @@ -263,7 +278,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simPigeonIMU", - "version": "24.3.0", + "version": "25.0.0-beta-3", "libName": "CTRE_SimPigeonIMU", "headerClassifier": "headers", "sharedLibrary": true, @@ -271,6 +286,7 @@ "binaryPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "osxuniversal" ], "simMode": "swsim" @@ -278,7 +294,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simCANCoder", - "version": "24.3.0", + "version": "25.0.0-beta-3", "libName": "CTRE_SimCANCoder", "headerClassifier": "headers", "sharedLibrary": true, @@ -286,6 +302,7 @@ "binaryPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "osxuniversal" ], "simMode": "swsim" @@ -293,7 +310,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simProTalonFX", - "version": "24.3.0", + "version": "25.0.0-beta-3", "libName": "CTRE_SimProTalonFX", "headerClassifier": "headers", "sharedLibrary": true, @@ -301,6 +318,7 @@ "binaryPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "osxuniversal" ], "simMode": "swsim" @@ -308,7 +326,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simProCANcoder", - "version": "24.3.0", + "version": "25.0.0-beta-3", "libName": "CTRE_SimProCANcoder", "headerClassifier": "headers", "sharedLibrary": true, @@ -316,6 +334,7 @@ "binaryPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "osxuniversal" ], "simMode": "swsim" @@ -323,7 +342,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simProPigeon2", - "version": "24.3.0", + "version": "25.0.0-beta-3", "libName": "CTRE_SimProPigeon2", "headerClassifier": "headers", "sharedLibrary": true, @@ -331,6 +350,7 @@ "binaryPlatforms": [ "windowsx86-64", "linuxx86-64", + "linuxarm64", "osxuniversal" ], "simMode": "swsim" diff --git a/vendordeps/REVLib.json b/vendordeps/REVLib.json index 731bbbfc..70cdac98 100644 --- a/vendordeps/REVLib.json +++ b/vendordeps/REVLib.json @@ -1,25 +1,25 @@ { "fileName": "REVLib.json", "name": "REVLib", - "version": "2024.2.4", - "frcYear": "2024", + "version": "2025.0.0-beta-3", + "frcYear": "2025", "uuid": "3f48eb8c-50fe-43a6-9cb7-44c86353c4cb", "mavenUrls": [ "https://maven.revrobotics.com/" ], - "jsonUrl": "https://software-metadata.revrobotics.com/REVLib-2024.json", + "jsonUrl": "https://software-metadata.revrobotics.com/REVLib-2025.json", "javaDependencies": [ { "groupId": "com.revrobotics.frc", "artifactId": "REVLib-java", - "version": "2024.2.4" + "version": "2025.0.0-beta-3" } ], "jniDependencies": [ { "groupId": "com.revrobotics.frc", "artifactId": "REVLib-driver", - "version": "2024.2.4", + "version": "2025.0.0-beta-3", "skipInvalidPlatforms": true, "isJar": false, "validPlatforms": [ @@ -37,7 +37,7 @@ { "groupId": "com.revrobotics.frc", "artifactId": "REVLib-cpp", - "version": "2024.2.4", + "version": "2025.0.0-beta-3", "libName": "REVLib", "headerClassifier": "headers", "sharedLibrary": false, @@ -55,7 +55,7 @@ { "groupId": "com.revrobotics.frc", "artifactId": "REVLib-driver", - "version": "2024.2.4", + "version": "2025.0.0-beta-3", "libName": "REVLibDriver", "headerClassifier": "headers", "sharedLibrary": false, diff --git a/vendordeps/ReduxLib_2024.json b/vendordeps/ReduxLib_2025.json similarity index 82% rename from vendordeps/ReduxLib_2024.json rename to vendordeps/ReduxLib_2025.json index f694966d..bd164ce9 100644 --- a/vendordeps/ReduxLib_2024.json +++ b/vendordeps/ReduxLib_2025.json @@ -1,25 +1,25 @@ { - "fileName": "ReduxLib_2024.json", + "fileName": "ReduxLib_2025.json", "name": "ReduxLib", - "version": "2024.3.1", - "frcYear": 2024, + "version": "2025.0.0-beta0", + "frcYear": 2025, "uuid": "151ecca8-670b-4026-8160-cdd2679ef2bd", "mavenUrls": [ "https://maven.reduxrobotics.com/" ], - "jsonUrl": "https://frcsdk.reduxrobotics.com/ReduxLib_2024.json", + "jsonUrl": "https://frcsdk.reduxrobotics.com/ReduxLib_2025.json", "javaDependencies": [ { "groupId": "com.reduxrobotics.frc", "artifactId": "ReduxLib-java", - "version": "2024.3.1" + "version": "2025.0.0-beta0" } ], "jniDependencies": [ { "groupId": "com.reduxrobotics.frc", "artifactId": "ReduxLib-driver", - "version": "2024.3.1", + "version": "2025.0.0-beta0", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ @@ -36,7 +36,7 @@ { "groupId": "com.reduxrobotics.frc", "artifactId": "ReduxLib-cpp", - "version": "2024.3.1", + "version": "2025.0.0-beta0", "libName": "ReduxLib-cpp", "headerClassifier": "headers", "sourcesClassifier": "sources", diff --git a/vendordeps/Studica-2025.1.1-beta-3.json b/vendordeps/Studica-2025.1.1-beta-3.json new file mode 100644 index 00000000..2f64aaf4 --- /dev/null +++ b/vendordeps/Studica-2025.1.1-beta-3.json @@ -0,0 +1,71 @@ +{ + "fileName": "Studica-2025.1.1-beta-3.json", + "name": "Studica", + "version": "2025.1.1-beta-3", + "uuid": "cb311d09-36e9-4143-a032-55bb2b94443b", + "frcYear": "2025", + "mavenUrls": [ + "https://dev.studica.com/maven/release/2025/" + ], + "jsonUrl": "https://dev.studica.com/releases/2025/Studica-2025.1.1-beta-3.json", + "cppDependencies": [ + { + "artifactId": "Studica-cpp", + "binaryPlatforms": [ + "linuxathena", + "linuxarm32", + "linuxarm64", + "linuxx86-64", + "osxuniversal", + "windowsx86-64" + ], + "groupId": "com.studica.frc", + "headerClassifier": "headers", + "libName": "Studica", + "sharedLibrary": false, + "skipInvalidPlatforms": true, + "version": "2025.1.1-beta-3" + }, + { + "artifactId": "Studica-driver", + "binaryPlatforms": [ + "linuxathena", + "linuxarm32", + "linuxarm64", + "linuxx86-64", + "osxuniversal", + "windowsx86-64" + ], + "groupId": "com.studica.frc", + "headerClassifier": "headers", + "libName": "StudicaDriver", + "sharedLibrary": false, + "skipInvalidPlatforms": true, + "version": "2025.1.1-beta-3" + } + ], + "javaDependencies": [ + { + "artifactId": "Studica-java", + "groupId": "com.studica.frc", + "version": "2025.1.1-beta-3" + } + ], + "jniDependencies": [ + { + "artifactId": "Studica-driver", + "groupId": "com.studica.frc", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "linuxathena", + "linuxarm32", + "linuxarm64", + "linuxx86-64", + "osxuniversal", + "windowsx86-64" + ], + "version": "2025.1.1-beta-3" + } + ] +} \ No newline at end of file diff --git a/vendordeps/WPILibNewCommands.json b/vendordeps/WPILibNewCommands.json index 67bf3898..3718e0ac 100644 --- a/vendordeps/WPILibNewCommands.json +++ b/vendordeps/WPILibNewCommands.json @@ -3,7 +3,7 @@ "name": "WPILib-New-Commands", "version": "1.0.0", "uuid": "111e20f7-815e-48f8-9dd6-e675ce75b266", - "frcYear": "2024", + "frcYear": "2025", "mavenUrls": [], "jsonUrl": "", "javaDependencies": [ diff --git a/vendordeps/maple-sim.json b/vendordeps/maple-sim.json new file mode 100644 index 00000000..c0b82958 --- /dev/null +++ b/vendordeps/maple-sim.json @@ -0,0 +1,26 @@ +{ + "fileName": "maple-sim.json", + "name": "maplesim", + "version": "0.2.1", + "frcYear": "2025", + "uuid": "c39481e8-4a63-4a4c-9df6-48d91e4da37b", + "mavenUrls": [ + "https://shenzhen-robotics-alliance.github.io/maple-sim/vendordep/repos/releases", + "https://repo1.maven.org/maven2" + ], + "jsonUrl": "https://shenzhen-robotics-alliance.github.io/maple-sim/vendordep/maple-sim.json", + "javaDependencies": [ + { + "groupId": "org.ironmaple", + "artifactId": "maplesim-java", + "version": "0.2.1" + }, + { + "groupId": "org.dyn4j", + "artifactId": "dyn4j", + "version": "5.0.2" + } + ], + "jniDependencies": [], + "cppDependencies": [] +} \ No newline at end of file diff --git a/vendordeps/photonlib.json b/vendordeps/photonlib.json index 0e80a16c..ad3a530c 100644 --- a/vendordeps/photonlib.json +++ b/vendordeps/photonlib.json @@ -1,20 +1,34 @@ { "fileName": "photonlib.json", "name": "photonlib", - "version": "v2024.3.1", + "version": "v2025.0.0-beta-5", "uuid": "515fe07e-bfc6-11fa-b3de-0242ac130004", - "frcYear": "2024", + "frcYear": "2025", "mavenUrls": [ "https://maven.photonvision.org/repository/internal", "https://maven.photonvision.org/repository/snapshots" ], "jsonUrl": "https://maven.photonvision.org/repository/internal/org/photonvision/photonlib-json/1.0/photonlib-json-1.0.json", - "jniDependencies": [], + "jniDependencies": [ + { + "groupId": "org.photonvision", + "artifactId": "photontargeting-cpp", + "version": "v2025.0.0-beta-5", + "skipInvalidPlatforms": true, + "isJar": false, + "validPlatforms": [ + "windowsx86-64", + "linuxathena", + "linuxx86-64", + "osxuniversal" + ] + } + ], "cppDependencies": [ { "groupId": "org.photonvision", "artifactId": "photonlib-cpp", - "version": "v2024.3.1", + "version": "v2025.0.0-beta-5", "libName": "photonlib", "headerClassifier": "headers", "sharedLibrary": true, @@ -29,7 +43,7 @@ { "groupId": "org.photonvision", "artifactId": "photontargeting-cpp", - "version": "v2024.3.1", + "version": "v2025.0.0-beta-5", "libName": "photontargeting", "headerClassifier": "headers", "sharedLibrary": true, @@ -46,12 +60,12 @@ { "groupId": "org.photonvision", "artifactId": "photonlib-java", - "version": "v2024.3.1" + "version": "v2025.0.0-beta-5" }, { "groupId": "org.photonvision", "artifactId": "photontargeting-java", - "version": "v2024.3.1" + "version": "v2025.0.0-beta-5" } ] } \ No newline at end of file