diff --git a/.gitignore b/.gitignore index 9535c83..a8d1911 100644 --- a/.gitignore +++ b/.gitignore @@ -158,5 +158,15 @@ gradle-app.setting .settings/ bin/ +# IntelliJ +*.iml +*.ipr +*.iws +.idea/ +out/ + +# Fleet +.fleet + # Simulation GUI and other tools window save file *-window.json diff --git a/.wpilib/wpilib_preferences.json b/.wpilib/wpilib_preferences.json index ad66d9e..18fa281 100644 --- a/.wpilib/wpilib_preferences.json +++ b/.wpilib/wpilib_preferences.json @@ -1,6 +1,6 @@ { "enableCppIntellisense": false, "currentLanguage": "java", - "projectYear": "2023", + "projectYear": "2024beta", "teamNumber": 1114 } \ No newline at end of file diff --git a/README.md b/README.md index 4b007fb..6aeca4c 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,12 @@ [![Build](https://github.com/Simbotics/Simbot-Base/actions/workflows/build.yml/badge.svg)](https://github.com/Simbotics/Simbot-Base/actions/workflows/build.yml) -# DISCLAIMER -All code at this point in time is a WIP and has NOT been tested or run on any robot and we can NOT confirm that the code will work as it is intended. - -# Simbot Base -A base robot with the most up-to-date utilities and starting components to get started on robot code as fast as possible. - -## Contributing -Interested in contributing to the project? Be sure to read [the contributing information](https://github.com/Simbotics/Simbot-Base/blob/main/CONTRIBUTING.md) for more information on how to get started with contributing to the codebase. +# Simbot-Base +A base robot with the most up-to-date utilities and starting components to get started on robot code as fast as possible ## Contributors and Credits Thank you to the following who has contributed, supplied the tools to make this project even possible, or has supported this project along the way. - [CTR Electronics](https://github.com/CrossTheRoadElec) - Provided an example and starter [repository](https://github.com/CrossTheRoadElec/Phoenix6-Examples/tree/main/java/SwerveWithPathPlanner) to build from +- [Ian Tapply](https://github.com/IanTapply22) - Main student contributor - [Kaleb Dodd](https://github.com/kaleb-dodd) - Project coordinator - [Cole MacPhail](https://github.com/colemacphail) - Project coordinator -- [Ian Tapply](https://github.com/IanTapply22) - Main student contributor -- [Hoodie Rocks/Cobblestone](https://github.com/HoodieRocks) - Student programmer diff --git a/WPILib-License.md b/WPILib-License.md index 3d5a824..43b62ec 100644 --- a/WPILib-License.md +++ b/WPILib-License.md @@ -1,4 +1,4 @@ -Copyright (c) 2009-2021 FIRST and other WPILib contributors +Copyright (c) 2009-2023 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 b233ef1..10ee205 100644 --- a/build.gradle +++ b/build.gradle @@ -1,10 +1,12 @@ plugins { id "java" - id "edu.wpi.first.GradleRIO" version "2023.4.3" + id "edu.wpi.first.GradleRIO" version "2024.1.1-beta-3" } -sourceCompatibility = JavaVersion.VERSION_11 -targetCompatibility = JavaVersion.VERSION_11 +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 +} def ROBOT_MAIN_CLASS = "frc.robot.Main" @@ -84,6 +86,7 @@ wpi.sim.addDriverstation() // knows where to look for our Robot Class. jar { from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } } + from sourceSets.main.allSource manifest edu.wpi.first.gradlerio.GradleRIOPlugin.javaManifest(ROBOT_MAIN_CLASS) duplicatesStrategy = DuplicatesStrategy.INCLUDE } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 249e583..7f93135 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 c23a1b3..1058752 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=permwrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=permwrapper/dists diff --git a/gradlew b/gradlew index a69d9cb..1aa94a4 100755 --- a/gradlew +++ b/gradlew @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -80,13 +80,11 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# 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 # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,22 +131,29 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -193,11 +198,15 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/gradlew.bat b/gradlew.bat index f127cfd..93e3f59 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -26,6 +26,7 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% diff --git a/settings.gradle b/settings.gradle index 48c039e..d94f73c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,7 +4,7 @@ pluginManagement { repositories { mavenLocal() gradlePluginPortal() - String frcYear = '2023' + String frcYear = '2024' File frcHome if (OperatingSystem.current().isWindows()) { String publicFolder = System.getenv('PUBLIC') @@ -25,3 +25,6 @@ pluginManagement { } } } + +Properties props = System.getProperties(); +props.setProperty("org.gradle.internal.native.headers.unresolved.dependencies.ignore", "true"); diff --git a/src/main/java/frc/robot/CommandController.java b/src/main/java/frc/robot/CommandController.java new file mode 100644 index 0000000..da7dbb1 --- /dev/null +++ b/src/main/java/frc/robot/CommandController.java @@ -0,0 +1,228 @@ +package frc.robot; + +import edu.wpi.first.wpilibj.GenericHID.RumbleType; +import edu.wpi.first.wpilibj2.command.button.CommandXboxController; +import edu.wpi.first.wpilibj2.command.button.Trigger; + +/** + * The {@code CommandController} class acts as a wrapper around an existing + * {@code CommandXboxController}, providing developers with the ability to rename its + * methods for improved clarity on the functionality of each button. + * + *

By extending this class, developers can use more intuitive method names + * that align with the specific actions associated with each button on the + * controller. This aims to enhance code readability and understanding, making it + * easier for developers to work with the controller in a more expressive manner.

+ * + *

Example usage:

+ *
+ * {@code
+ * CommandController driver = new CommandController(0);
+ * driver.greenButton().onTrue(() -> {}); // insert command
+ * + * @author Simbotics + * + * @see CommandXboxController + */ +public class CommandController { + CommandXboxController joystick; + + public CommandController(int port) { + this.joystick = new CommandXboxController(port); + } + + /** + * Gets the left joystick X axis. + * + * @return The left joystick X axis. + */ + public double getLeftX() { + return this.joystick.getLeftX(); + } + + /** + * Gets the left joystick Y axis. + * + * @return The left joystick Y axis. + */ + public double getLeftY() { + return -this.joystick.getLeftY(); + } + + /** + * Gets the pressed state of the left trigger. + * + * @return The pressed state of the left trigger as a trigger. + */ + public Trigger leftTrigger() { + return this.joystick.rightTrigger(); + } + + /** + * Gets the pressed state of the right trigger. + * + * @return The pressed state of the right trigger as a trigger. + */ + public Trigger rightTrigger() { + return this.joystick.rightTrigger(); + } + + /** + * Gets the right joystick X axis. + * + * @return The right joystick X axis. + */ + public double getRightX() { + return this.joystick.getRightX(); + } + + /** + * Gets the right joystick Y axis. + * + * @return The right joystick Y axis. + */ + public double getRightY() { + return -this.joystick.getRightY(); + } + + /** + * Gets the green buttons trigger. + * + * @return the trigger for the green button + */ + public Trigger greenButton() { + return this.joystick.a(); + } + + /** + * Gets the blue buttons trigger. + * + * @return the trigger for the blue button + */ + public Trigger blueButton() { + return this.joystick.x(); + } + + /** + * Gets the red buttons trigger. + * + * @return the trigger for the red button + */ + public Trigger redButton() { + return this.joystick.b(); + } + + /** + * Gets the yellow buttons trigger. + * + * @return the trigger for the yellow button + */ + public Trigger yellowButton() { + return this.joystick.y(); + } + + /** + * Gets the back button trigger + * + * @return the trigger for the back button + */ + public Trigger backButton() { + return this.joystick.back(); + } + + /** + * Gets the back button trigger + * + * @return the trigger for the back button + */ + public Trigger startButton() { + return this.joystick.start(); + } + + /** + * Gets the pressed state of the top left back button. + * + * @return The value of the pressed state as a trigger. + */ + public Trigger leftBumper() { + return this.joystick.leftBumper(); + } + + /** + * Gets the pressed state of the top right back button. + * + * @return The value of the pressed state as a trigger. + */ + public Trigger rightBumper() { + return this.joystick.rightBumper(); + } + + /** + * Gets the pressed state of the left joystick button. + * + * @return The state of the left joystick button as a trigger. + */ + public Trigger leftStickClick() { + return this.joystick.button(9); + } + + /** + * Gets the pressed state of the right joystick button. + * + * @return The state of the of the right joystick button as a boolean. + */ + public Trigger rightStickClick() { + return this.joystick.button(10); + } + + /** POV methods. */ + public int getPOVVal() { + return this.joystick.getHID().getPOV(0); + } + + /** + * Get trigger for when POV angle is 180 + * + * @return trigger for when POV is 180 + */ + public Trigger POVDown() { + return this.joystick.povDown(); + } + + /** + * Get trigger for when POV angle is 90 + * + * @return trigger for when POV is 90 + */ + public Trigger POVRight() { + return this.joystick.povRight(); + } + + /** + * Get trigger for when POV angle is 0 + * + * @return trigger for when POV is 0 + */ + public Trigger POVUp() { + return this.joystick.povUp(); + } + + /** + * Get trigger for when POV angle is 270 + * + * @return trigger for when POV is 270 + */ + public Trigger POVLeft() { + return this.joystick.povLeft(); + } + + /** + * Sets the amount of rumble for supported controllers. + * + * @param rumble Amount of rumble; Either 0 or 1. + */ + public void setRumble(double rumble) { + this.joystick.getHID().setRumble(RumbleType.kLeftRumble, rumble); + this.joystick.getHID().setRumble(RumbleType.kRightRumble, rumble); + } +} diff --git a/src/main/java/frc/robot/Robot.java b/src/main/java/frc/robot/Robot.java index 8bafc7b..f9fc442 100644 --- a/src/main/java/frc/robot/Robot.java +++ b/src/main/java/frc/robot/Robot.java @@ -4,8 +4,6 @@ package frc.robot; -import com.ctre.phoenix6.SignalLogger; - import edu.wpi.first.math.geometry.Pose2d; import edu.wpi.first.wpilibj.TimedRobot; import edu.wpi.first.wpilibj.Timer; @@ -24,8 +22,6 @@ public void robotInit() { m_robotContainer = new RobotContainer(); m_robotContainer.drivetrain.getDaqThread().setThreadPriority(99); - - SignalLogger.start(); } @Override diff --git a/src/main/java/frc/robot/RobotContainer.java b/src/main/java/frc/robot/RobotContainer.java index 4d4825f..36551bc 100644 --- a/src/main/java/frc/robot/RobotContainer.java +++ b/src/main/java/frc/robot/RobotContainer.java @@ -4,90 +4,85 @@ package frc.robot; +import com.ctre.phoenix6.mechanisms.swerve.SwerveModule.DriveRequestType; import com.ctre.phoenix6.mechanisms.swerve.SwerveRequest; -import edu.wpi.first.math.geometry.Pose2d; -import edu.wpi.first.math.geometry.Rotation2d; +import edu.wpi.first.wpilibj.PowerDistribution; import edu.wpi.first.wpilibj2.command.Command; import edu.wpi.first.wpilibj2.command.InstantCommand; -import edu.wpi.first.wpilibj2.command.button.CommandXboxController; -import frc.robot.generated.TunerConstants; +import frc.robot.subsystems.drive.DriveConstants; +import frc.robot.subsystems.drive.DriveSubsystem; +import frc.robot.subsystems.intake.IntakeSubsystem; +import frc.robot.subsystems.intake.enums.IntakeGamepiece; +import frc.robot.subsystems.intake.states.scoring.cone.HighCone; +import frc.robot.subsystems.led.LEDSubsystem; public class RobotContainer { - final double MaxSpeed = 6; // 6 meters per second desired top speed - final double MaxAngularRate = Math.PI; // Half a rotation per second max angular velocity + private class Controller { + public static final CommandController driver = new CommandController(0); + public static final CommandController operator = new CommandController(1); + } + + private final LEDSubsystem ledSubsystem; + private final IntakeSubsystem intakeSubsystem; + + private final PowerDistribution pdp = new PowerDistribution(); - /* Setting up bindings for necessary control of the swerve drive platform */ - CommandXboxController joystick = new CommandXboxController(0); // My joystick - CommandSwerveDrivetrain drivetrain = TunerConstants.DriveTrain; // My drivetrain - SwerveRequest.FieldCentric drive = + // Set up the base for the drive and drivetrain + final DriveSubsystem drivetrain = DriveConstants.DriveTrain; + private final SwerveRequest.FieldCentric drive = new SwerveRequest.FieldCentric() - .withIsOpenLoop(true) - .withDeadband(MaxSpeed * 0.1) - .withRotationalDeadband(MaxAngularRate * 0.1); // I want field-centric - // driving in open loop - SwerveRequest.SwerveDriveBrake brake = new SwerveRequest.SwerveDriveBrake(); - SwerveRequest.RobotCentric forwardStraight = - new SwerveRequest.RobotCentric().withIsOpenLoop(true); - SwerveRequest.PointWheelsAt point = new SwerveRequest.PointWheelsAt(); + .withDeadband(DriveConstants.kMaxAngularRate * 0.1) + .withRotationalDeadband(DriveConstants.kMaxAngularRate * 0.1) + .withDriveRequestType(DriveRequestType.OpenLoopVoltage); /* Path follower */ - // Command runAuto = drivetrain.getAutoPath("Tests"); + private Command runAuto = drivetrain.getAutoPath("Tests"); - Telemetry logger = new Telemetry(MaxSpeed); - - Pose2d odomStart = new Pose2d(0, 0, new Rotation2d(0, 0)); + private final Telemetry logger = new Telemetry(DriveConstants.kMaxSpeed); private void configureBindings() { - drivetrain.setDefaultCommand( // Drivetrain will execute this command periodically + ledSubsystem.setDefaultCommand(new InstantCommand(() -> ledSubsystem.periodic(), ledSubsystem)); + drivetrain.registerTelemetry(logger::telemeterize); + + drivetrain.setDefaultCommand( drivetrain .applyRequest( () -> drive - .withVelocityX(-joystick.getLeftY() * MaxSpeed) // Drive forward with + .withVelocityX( + -Controller.driver.getLeftY() + * DriveConstants.kMaxSpeed) // Drive forward with // negative Y (forward) .withVelocityY( - -joystick.getLeftX() * MaxSpeed) // Drive left with negative X (left) + -Controller.driver.getLeftX() + * DriveConstants.kMaxSpeed) // Drive left with negative X (left) .withRotationalRate( - -joystick.getRightX() - * MaxAngularRate) // Drive counterclockwise with negative X (left) + -Controller.driver.getRightX() + * DriveConstants + .kMaxAngularRate) // Drive counterclockwise with negative X + // (left) ) .ignoringDisable(true)); - joystick.a().whileTrue(drivetrain.applyRequest(() -> brake)); - joystick - .b() - .whileTrue( - drivetrain.applyRequest( - () -> - point.withModuleDirection( - new Rotation2d(-joystick.getLeftY(), -joystick.getLeftX())))); - - // if (Utils.isSimulation()) { - // drivetrain.seedFieldRelative(new Pose2d(new Translation2d(), Rotation2d.fromDegrees(90))); - // } - drivetrain.registerTelemetry(logger::telemeterize); - - joystick - .pov(0) - .whileTrue( - drivetrain.applyRequest(() -> forwardStraight.withVelocityX(0.5).withVelocityY(0))); - joystick - .pov(180) - .whileTrue( - drivetrain.applyRequest(() -> forwardStraight.withVelocityX(-0.5).withVelocityY(0))); + Controller.operator + .yellowButton() + .toggleOnTrue(intakeSubsystem.intakeHoldCommand(IntakeGamepiece.CONE)); + Controller.operator + .blueButton() + .toggleOnTrue(intakeSubsystem.intakeHoldCommand(IntakeGamepiece.CUBE)); + Controller.operator + .greenButton() + .toggleOnTrue(intakeSubsystem.intakeScoreCommand(new HighCone(), IntakeGamepiece.CONE)); } public RobotContainer() { configureBindings(); + ledSubsystem = new LEDSubsystem(); + intakeSubsystem = new IntakeSubsystem(pdp); } public Command getAutonomousCommand() { - /* First put the drivetrain into auto run mode, then run the auto */ - return new InstantCommand(() -> {}); - } - - public boolean seedPoseButtonDown() { - return joystick.leftBumper().getAsBoolean(); + return runAuto; } } diff --git a/src/main/java/frc/robot/Telemetry.java b/src/main/java/frc/robot/Telemetry.java index 16e9089..bb0861a 100644 --- a/src/main/java/frc/robot/Telemetry.java +++ b/src/main/java/frc/robot/Telemetry.java @@ -1,5 +1,6 @@ package frc.robot; +import com.ctre.phoenix6.SignalLogger; import com.ctre.phoenix6.Utils; import com.ctre.phoenix6.mechanisms.swerve.SwerveDrivetrain.SwerveDriveState; @@ -10,8 +11,6 @@ import edu.wpi.first.networktables.NetworkTable; import edu.wpi.first.networktables.NetworkTableInstance; import edu.wpi.first.networktables.StringPublisher; -import edu.wpi.first.wpilibj.DataLogManager; -import edu.wpi.first.wpilibj.Timer; import edu.wpi.first.wpilibj.smartdashboard.Mechanism2d; import edu.wpi.first.wpilibj.smartdashboard.MechanismLigament2d; import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard; @@ -20,8 +19,6 @@ public class Telemetry { private final double MaxSpeed; - private int logEntry; - private int odomEntry; /** * Construct a telemetry object, with the specified max speed of the robot @@ -30,36 +27,36 @@ public class Telemetry { */ public Telemetry(double maxSpeed) { MaxSpeed = maxSpeed; - logEntry = DataLogManager.getLog().start("odometry", "double[]"); - odomEntry = DataLogManager.getLog().start("odom period", "double"); + SignalLogger.start(); } /* What to publish over networktables for telemetry */ - NetworkTableInstance inst = NetworkTableInstance.getDefault(); + private final NetworkTableInstance inst = NetworkTableInstance.getDefault(); /* Robot pose for field positioning */ - NetworkTable table = inst.getTable("Pose"); - DoubleArrayPublisher fieldPub = table.getDoubleArrayTopic("robotPose").publish(); - StringPublisher fieldTypePub = table.getStringTopic(".type").publish(); + private final NetworkTable table = inst.getTable("Pose"); + private final DoubleArrayPublisher fieldPub = table.getDoubleArrayTopic("robotPose").publish(); + private final StringPublisher fieldTypePub = table.getStringTopic(".type").publish(); /* Robot speeds for general checking */ - NetworkTable driveStats = inst.getTable("Drive"); - DoublePublisher velocityX = driveStats.getDoubleTopic("Velocity X").publish(); - DoublePublisher velocityY = driveStats.getDoubleTopic("Velocity Y").publish(); - DoublePublisher speed = driveStats.getDoubleTopic("Speed").publish(); - DoublePublisher odomPeriod = driveStats.getDoubleTopic("Odometry Period").publish(); + private final NetworkTable driveStats = inst.getTable("Drive"); + private final DoublePublisher velocityX = driveStats.getDoubleTopic("Velocity X").publish(); + private final DoublePublisher velocityY = driveStats.getDoubleTopic("Velocity Y").publish(); + private final DoublePublisher speed = driveStats.getDoubleTopic("Speed").publish(); + private final DoublePublisher odomFreq = + driveStats.getDoubleTopic("Odometry Frequency").publish(); /* Keep a reference of the last pose to calculate the speeds */ - Pose2d m_lastPose = new Pose2d(); - double lastTime = Utils.getCurrentTimeSeconds(); + private Pose2d m_lastPose = new Pose2d(); + private double lastTime = Utils.getCurrentTimeSeconds(); /* Mechanisms to represent the swerve module states */ - Mechanism2d[] m_moduleMechanisms = + private final Mechanism2d[] m_moduleMechanisms = new Mechanism2d[] { new Mechanism2d(1, 1), new Mechanism2d(1, 1), new Mechanism2d(1, 1), new Mechanism2d(1, 1), }; /* A direction and length changing ligament for speed representation */ - MechanismLigament2d[] m_moduleSpeeds = + private final MechanismLigament2d[] m_moduleSpeeds = new MechanismLigament2d[] { m_moduleMechanisms[0] .getRoot("RootSpeed", 0.5, 0.5) @@ -75,7 +72,7 @@ public Telemetry(double maxSpeed) { .append(new MechanismLigament2d("Speed", 0.5, 0)), }; /* A direction changing and length constant ligament for module direction */ - MechanismLigament2d[] m_moduleDirections = + private final MechanismLigament2d[] m_moduleDirections = new MechanismLigament2d[] { m_moduleMechanisms[0] .getRoot("RootDirection", 0.5, 0.5) @@ -110,7 +107,7 @@ public void telemeterize(SwerveDriveState state) { speed.set(velocities.getNorm()); velocityX.set(velocities.getX()); velocityY.set(velocities.getY()); - odomPeriod.set(1.0 / state.OdometryPeriod); + odomFreq.set(1.0 / state.OdometryPeriod); /* Telemeterize the module's states */ for (int i = 0; i < 4; ++i) { @@ -121,12 +118,8 @@ public void telemeterize(SwerveDriveState state) { SmartDashboard.putData("Module " + i, m_moduleMechanisms[i]); } - DataLogManager.getLog() - .appendDoubleArray( - logEntry, - new double[] {pose.getX(), pose.getY(), pose.getRotation().getDegrees()}, - (long) (Timer.getFPGATimestamp() * 1000000)); - DataLogManager.getLog() - .appendDouble(odomEntry, state.OdometryPeriod, (long) (Timer.getFPGATimestamp() * 1000000)); + SignalLogger.writeDoubleArray( + "odometry", new double[] {pose.getX(), pose.getY(), pose.getRotation().getDegrees()}); + SignalLogger.writeDouble("odom period", state.OdometryPeriod, "seconds"); } } diff --git a/src/main/java/frc/robot/generated/TunerConstants.java b/src/main/java/frc/robot/generated/TunerConstants.java deleted file mode 100644 index a66c7d8..0000000 --- a/src/main/java/frc/robot/generated/TunerConstants.java +++ /dev/null @@ -1,133 +0,0 @@ -package frc.robot.generated; - -import com.ctre.phoenix6.configs.Slot0Configs; -import com.ctre.phoenix6.mechanisms.swerve.SwerveModuleConstantsFactory; -import com.ctre.phoenix6.mechanisms.swerve.SwerveModuleConstants.SwerveModuleSteerFeedbackType; -import com.ctre.phoenix6.mechanisms.swerve.SwerveDrivetrainConstants; -import com.ctre.phoenix6.mechanisms.swerve.SwerveModuleConstants; - -import edu.wpi.first.math.util.Units; -import frc.robot.CommandSwerveDrivetrain; - -public class TunerConstants { - static class CustomSlotGains extends Slot0Configs { - public CustomSlotGains(double kP, double kI, double kD, double kV, double kS) { - this.kP = kP; - this.kI = kI; - this.kD = kD; - this.kV = kV; - this.kS = kS; - } - } - - private static final CustomSlotGains steerGains = new CustomSlotGains(100, 0, 0.05, 0, 0); - private static final CustomSlotGains driveGains = new CustomSlotGains(3, 0, 0, 0, 0); - - private static final double kCoupleRatio = 3.5; - - private static final double kDriveGearRatio = 7.363636364; - private static final double kSteerGearRatio = 15.42857143; - private static final double kWheelRadiusInches = - 2.167; // Estimated at first, then fudge-factored to make odom match record - private static final int kPigeonId = 1; - private static final boolean kSteerMotorReversed = true; - private static final String kCANbusName = "rio"; - private static final boolean kInvertLeftSide = false; - private static final boolean kInvertRightSide = true; - - private static double kSteerInertia = 0.00001; - private static double kDriveInertia = 0.001; - - private static final SwerveDrivetrainConstants DrivetrainConstants = - new SwerveDrivetrainConstants() - .withPigeon2Id(kPigeonId) - .withCANbusName(kCANbusName) - .withSupportsPro(true); - - private static final SwerveModuleConstantsFactory ConstantCreator = - new SwerveModuleConstantsFactory() - .withDriveMotorGearRatio(kDriveGearRatio) - .withSteerMotorGearRatio(kSteerGearRatio) - .withWheelRadius(kWheelRadiusInches) - .withSlipCurrent(30) - .withSteerMotorGains(steerGains) - .withDriveMotorGains(driveGains) - .withSpeedAt12VoltsMps( - 6) // Theoretical free speed is 10 meters per second at 12v applied output - .withSteerInertia(kSteerInertia) - .withDriveInertia(kDriveInertia) - .withFeedbackSource(SwerveModuleSteerFeedbackType.FusedCANcoder) - .withCouplingGearRatio( - kCoupleRatio) // Every 1 rotation of the azimuth results in couple ratio drive turns - .withSteerMotorInverted(kSteerMotorReversed); - - private static final int kFrontLeftDriveMotorId = 5; - private static final int kFrontLeftSteerMotorId = 4; - private static final int kFrontLeftEncoderId = 2; - private static final double kFrontLeftEncoderOffset = -0.83544921875; - - private static final double kFrontLeftXPosInches = 10.5; - private static final double kFrontLeftYPosInches = 10.5; - private static final int kFrontRightDriveMotorId = 7; - private static final int kFrontRightSteerMotorId = 6; - private static final int kFrontRightEncoderId = 3; - private static final double kFrontRightEncoderOffset = -0.15234375; - - private static final double kFrontRightXPosInches = 10.5; - private static final double kFrontRightYPosInches = -10.5; - private static final int kBackLeftDriveMotorId = 1; - private static final int kBackLeftSteerMotorId = 0; - private static final int kBackLeftEncoderId = 0; - private static final double kBackLeftEncoderOffset = -0.4794921875; - - private static final double kBackLeftXPosInches = -10.5; - private static final double kBackLeftYPosInches = 10.5; - private static final int kBackRightDriveMotorId = 3; - private static final int kBackRightSteerMotorId = 2; - private static final int kBackRightEncoderId = 1; - private static final double kBackRightEncoderOffset = -0.84130859375; - - private static final double kBackRightXPosInches = -10.5; - private static final double kBackRightYPosInches = -10.5; - - private static final SwerveModuleConstants FrontLeft = - ConstantCreator.createModuleConstants( - kFrontLeftSteerMotorId, - kFrontLeftDriveMotorId, - kFrontLeftEncoderId, - kFrontLeftEncoderOffset, - Units.inchesToMeters(kFrontLeftXPosInches), - Units.inchesToMeters(kFrontLeftYPosInches), - kInvertLeftSide); - private static final SwerveModuleConstants FrontRight = - ConstantCreator.createModuleConstants( - kFrontRightSteerMotorId, - kFrontRightDriveMotorId, - kFrontRightEncoderId, - kFrontRightEncoderOffset, - Units.inchesToMeters(kFrontRightXPosInches), - Units.inchesToMeters(kFrontRightYPosInches), - kInvertRightSide); - private static final SwerveModuleConstants BackLeft = - ConstantCreator.createModuleConstants( - kBackLeftSteerMotorId, - kBackLeftDriveMotorId, - kBackLeftEncoderId, - kBackLeftEncoderOffset, - Units.inchesToMeters(kBackLeftXPosInches), - Units.inchesToMeters(kBackLeftYPosInches), - kInvertLeftSide); - private static final SwerveModuleConstants BackRight = - ConstantCreator.createModuleConstants( - kBackRightSteerMotorId, - kBackRightDriveMotorId, - kBackRightEncoderId, - kBackRightEncoderOffset, - Units.inchesToMeters(kBackRightXPosInches), - Units.inchesToMeters(kBackRightYPosInches), - kInvertRightSide); - - public static final CommandSwerveDrivetrain DriveTrain = - new CommandSwerveDrivetrain( - DrivetrainConstants, 250, FrontLeft, FrontRight, BackLeft, BackRight); -} diff --git a/src/main/java/frc/robot/subsystems/drive/DriveConstants.java b/src/main/java/frc/robot/subsystems/drive/DriveConstants.java new file mode 100644 index 0000000..f0c8855 --- /dev/null +++ b/src/main/java/frc/robot/subsystems/drive/DriveConstants.java @@ -0,0 +1,142 @@ +package frc.robot.subsystems.drive; + +import com.ctre.phoenix6.mechanisms.swerve.SwerveDrivetrainConstants; +import com.ctre.phoenix6.mechanisms.swerve.SwerveModuleConstantsFactory; +import com.ctre.phoenix6.mechanisms.swerve.SwerveModuleConstants.SteerFeedbackType; + +import frc.robot.subsystems.drive.swerve.SwerveModuleLocation; +import frc.robot.subsystems.drive.swerve.SwerveModules; + +public class DriveConstants { + /** Only touch these if you know what you're doing */ + private static final DriveSlotGains steerGains = new DriveSlotGains(100.0, 0, 0.05, 0, 0); + + private static final DriveSlotGains driveGains = new DriveSlotGains(3.0, 0, 0, 0, 0); + + public static final double kMaxSpeed = 6.0; // 6 meters per second desired top speed + public static final double kMaxAngularRate = + Math.PI; // Half a rotation per second max angular velocity + + private static final double kCoupleRatio = 3.5; + + private static final double kWheelRadiusInches = + 2.167; // Estimated at first, then fudge-factored to make odom + // match record + + private static final String kCANbusName = "rio"; + private static final int kPigeonId = 1; + + public static final double kSpeedAt12VoltsMps = 5.0; + public static final double kSimLoopPeriod = 0.005; // 5 ms + + private static final boolean kSteerMotorReversed = true; + public static final boolean kInvertLeftSide = false; + public static final boolean kInvertRightSide = true; + + /** The ratios of the drive and steer gears in the swerve modules */ + private class GearRatio { + private static final double kDriveGearRatio = 7.363636364; + private static final double kSteerGearRatio = 15.42857143; + } + + /** + * The simulated voltage necessary to overcome friction for swerve. This is only used for + * simulation + */ + private class FrictionVoltage { + private static final double kSteerFrictionVoltage = 0.25; + private static final double kDriveFrictionVoltage = 0.25; + } + + /** + * The inertia of the drive and steer motors in the swerve modules. These should be left to + * default + */ + private class Intertia { + private static final double kSteerInertia = 0.00001; + private static final double kDriveInertia = 0.001; + } + + /** + * The CAN IDs of the drive motors in the swerve modules NOTE: Be sure to change these to the + * correct values at the start of the season + */ + public class DriveMotorId { + public static final int kFrontLeftDriveMotorId = 5; + public static final int kFrontRightDriveMotorId = 7; + public static final int kBackLeftDriveMotorId = 1; + public static final int kBackRightDriveMotorId = 3; + } + + /** + * The CAN IDs of the steer motors in the swerve modules NOTE: Be sure to change these to the + * correct values at the start of the season + */ + public class SteerMotorId { + public static final int kFrontLeftSteerMotorId = 4; + public static final int kFrontRightSteerMotorId = 6; + public static final int kBackLeftSteerMotorId = 0; + public static final int kBackRightSteerMotorId = 2; + } + + /** + * The CAN IDs of the encoders in the swerve modules NOTE: Be sure to change these to the correct + * values at the start of the season + */ + public class EncoderId { + public static final int kFrontLeftEncoderId = 2; + public static final int kFrontRightEncoderId = 3; + public static final int kBackLeftEncoderId = 0; + public static final int kBackRightEncoderId = 1; + } + + public class EncoderOffset { + public static final double kFrontLeftEncoderOffset = -0.83544921875; + public static final double kFrontRightEncoderOffset = -0.15234375; + public static final double kBackLeftEncoderOffset = -0.4794921875; + public static final double kBackRightEncoderOffset = -0.84130859375; + } + + /** + * The X and Y positions of the swerve modules relative to the center of the robot in inches NOTE: + * Be sure to change these to the correct values at the start of the season + */ + public class SwerveModulePosition { + public static final SwerveModuleLocation kFrontLeft = new SwerveModuleLocation(10.5, 10.5); + public static final SwerveModuleLocation kFrontRight = new SwerveModuleLocation(10.5, -10.5); + public static final SwerveModuleLocation kBackLeft = new SwerveModuleLocation(-10.5, 10.5); + public static final SwerveModuleLocation kBackRight = new SwerveModuleLocation(-10.5, -10.5); + } + + private static final SwerveDrivetrainConstants DrivetrainConstants = + new SwerveDrivetrainConstants().withPigeon2Id(kPigeonId).withCANbusName(kCANbusName); + + public static final SwerveModuleConstantsFactory ConstantCreator = + new SwerveModuleConstantsFactory() + .withDriveMotorGearRatio(GearRatio.kDriveGearRatio) + .withSteerMotorGearRatio(GearRatio.kSteerGearRatio) + .withWheelRadius(kWheelRadiusInches) + .withSlipCurrent(30) + .withSteerMotorGains(steerGains) + .withDriveMotorGains(driveGains) + .withSpeedAt12VoltsMps( + kSpeedAt12VoltsMps) // Theoretical free speed is 10 meters per second at 12v applied + // output + .withSteerInertia(Intertia.kSteerInertia) + .withDriveInertia(Intertia.kDriveInertia) + .withFeedbackSource(SteerFeedbackType.FusedCANcoder) + .withSteerFrictionVoltage(FrictionVoltage.kSteerFrictionVoltage) + .withDriveFrictionVoltage(FrictionVoltage.kDriveFrictionVoltage) + .withCouplingGearRatio( + kCoupleRatio) // Every 1 rotation of the azimuth results in couple ratio drive turns + .withSteerMotorInverted(kSteerMotorReversed); + + /** Construct the final drivetrain object that we interact with */ + public static final DriveSubsystem DriveTrain = + new DriveSubsystem( + DrivetrainConstants, + SwerveModules.FrontLeft, + SwerveModules.FrontRight, + SwerveModules.BackLeft, + SwerveModules.BackRight); +} diff --git a/src/main/java/frc/robot/subsystems/drive/DriveSlotGains.java b/src/main/java/frc/robot/subsystems/drive/DriveSlotGains.java new file mode 100644 index 0000000..2879a83 --- /dev/null +++ b/src/main/java/frc/robot/subsystems/drive/DriveSlotGains.java @@ -0,0 +1,13 @@ +package frc.robot.subsystems.drive; + +import com.ctre.phoenix6.configs.Slot0Configs; + +public class DriveSlotGains extends Slot0Configs { + public DriveSlotGains(double kP, double kI, double kD, double kV, double kS) { + this.kP = kP; + this.kI = kI; + this.kD = kD; + this.kV = kV; + this.kS = kS; + } +} diff --git a/src/main/java/frc/robot/CommandSwerveDrivetrain.java b/src/main/java/frc/robot/subsystems/drive/DriveSubsystem.java similarity index 51% rename from src/main/java/frc/robot/CommandSwerveDrivetrain.java rename to src/main/java/frc/robot/subsystems/drive/DriveSubsystem.java index 5211b54..33e576e 100644 --- a/src/main/java/frc/robot/CommandSwerveDrivetrain.java +++ b/src/main/java/frc/robot/subsystems/drive/DriveSubsystem.java @@ -1,7 +1,8 @@ -package frc.robot; +package frc.robot.subsystems.drive; import java.util.function.Supplier; +import com.ctre.phoenix6.Utils; import com.ctre.phoenix6.mechanisms.swerve.SwerveDrivetrain; import com.ctre.phoenix6.mechanisms.swerve.SwerveDrivetrainConstants; import com.ctre.phoenix6.mechanisms.swerve.SwerveModuleConstants; @@ -15,32 +16,33 @@ import edu.wpi.first.math.geometry.Pose2d; import edu.wpi.first.math.geometry.Rotation2d; import edu.wpi.first.math.kinematics.ChassisSpeeds; +import edu.wpi.first.wpilibj.Notifier; +import edu.wpi.first.wpilibj.RobotController; import edu.wpi.first.wpilibj2.command.Command; import edu.wpi.first.wpilibj2.command.RunCommand; import edu.wpi.first.wpilibj2.command.Subsystem; -/** - * Class that extends the Phoenix SwerveDrivetrain class and implements subsystem so it can be used - * in command-based projects easily. - */ -public class CommandSwerveDrivetrain extends SwerveDrivetrain implements Subsystem { - private SwerveRequest.ApplyChassisSpeeds autoRequest = new SwerveRequest.ApplyChassisSpeeds(); +public class DriveSubsystem extends SwerveDrivetrain implements Subsystem { + private Notifier m_simNotifier = null; + private double m_lastSimTime; - public CommandSwerveDrivetrain( - SwerveDrivetrainConstants driveTrainConstants, - double OdometryUpdateFrequency, - SwerveModuleConstants... modules) { - super(driveTrainConstants, OdometryUpdateFrequency, modules); - configurePathPlanner(); - } + private SwerveRequest.ApplyChassisSpeeds autoRequest = new SwerveRequest.ApplyChassisSpeeds(); - public CommandSwerveDrivetrain( - SwerveDrivetrainConstants driveTrainConstants, SwerveModuleConstants... modules) { - super(driveTrainConstants, modules); - configurePathPlanner(); + @Override + public void simulationPeriodic() { + updateSimState(0.02, 12); } + /** + * Configures path planner to have a holonomic path follower so it can move in any direction. This + * is meant for holonomic drivetrains AND swerve + */ private void configurePathPlanner() { + double driveBaseRadius = 0; + for (var moduleLocation : m_moduleLocations) { + driveBaseRadius = Math.max(driveBaseRadius, moduleLocation.getNorm()); + } + AutoBuilder.configureHolonomic( () -> this.getState().Pose, // Supplier of current robot pose this::seedFieldRelative, // Consumer for seeding pose against auto @@ -51,13 +53,18 @@ private void configurePathPlanner() { new HolonomicPathFollowerConfig( new PIDConstants(10, 0, 0), new PIDConstants(10, 0, 0), - 1, - 1, - new ReplanningConfig(), - 0.004), - this); // Subsystem for requirements + DriveConstants.kSpeedAt12VoltsMps, + driveBaseRadius, + new ReplanningConfig()), + this); } + /** + * Applies and runs a command on the swerve drivetrain + * + * @param requestSupplier The supplier of the swerve request to apply (the request) + * @return A command which runs the specified request + */ public Command applyRequest(Supplier requestSupplier) { return new RunCommand( () -> { @@ -66,16 +73,16 @@ public Command applyRequest(Supplier requestSupplier) { this); } + /** + * Returns a command that will run the specified pathplanner path + * + * @param pathName The name of the path created in pathplanner + * @return A command which runs the specified path + */ public Command getAutoPath(String pathName) { return new PathPlannerAuto(pathName); } - @Override - public void simulationPeriodic() { - /* Assume */ - updateSimState(0.02, 12); - } - /** * Takes the specified location and makes it the current pose for field-relative maneuvers * @@ -93,7 +100,64 @@ public void seedFieldRelative(Pose2d location) { } } + /** + * Returns the current robot chassis speeds + * + * @return The current robot chassis speeds as a Twist2d object + */ public ChassisSpeeds getCurrentRobotChassisSpeeds() { return m_kinematics.toChassisSpeeds(getState().ModuleStates); } + + /** + * Updates the state of the drivetrain for simulation + * + * @param deltaTime The time since the last update + * @param batteryVoltage The current battery voltage + */ + private void startSimThread() { + m_lastSimTime = Utils.getCurrentTimeSeconds(); + + /* Run simulation at a faster rate so PID gains behave more reasonably */ + m_simNotifier = + new Notifier( + () -> { + final double currentTime = Utils.getCurrentTimeSeconds(); + double deltaTime = currentTime - m_lastSimTime; + m_lastSimTime = currentTime; + + /* use the measured time delta, get battery voltage from WPILib */ + updateSimState(deltaTime, RobotController.getBatteryVoltage()); + }); + m_simNotifier.startPeriodic(DriveConstants.kSimLoopPeriod); + } + + /** Add any methods you want to call when the drive subsystem is initialized and called */ + public void initialize() { + configurePathPlanner(); + } + + /** Initialize the drive subsystem when we create an instance of it and configure it * */ + public DriveSubsystem( + SwerveDrivetrainConstants driveTrainConstants, + double OdometryUpdateFrequency, + SwerveModuleConstants... modules) { + super(driveTrainConstants, OdometryUpdateFrequency, modules); + this.initialize(); + + if (Utils.isSimulation()) { + startSimThread(); + } + } + + /** Initialize the drive subsystem when we create an instance of it and configure it * */ + public DriveSubsystem( + SwerveDrivetrainConstants driveTrainConstants, SwerveModuleConstants... modules) { + super(driveTrainConstants, modules); + this.initialize(); + + if (Utils.isSimulation()) { + startSimThread(); + } + } } diff --git a/src/main/java/frc/robot/subsystems/drive/swerve/SwerveModuleLocation.java b/src/main/java/frc/robot/subsystems/drive/swerve/SwerveModuleLocation.java new file mode 100644 index 0000000..cd689e7 --- /dev/null +++ b/src/main/java/frc/robot/subsystems/drive/swerve/SwerveModuleLocation.java @@ -0,0 +1,54 @@ +package frc.robot.subsystems.drive.swerve; + +import edu.wpi.first.math.util.Units; + +public class SwerveModuleLocation { + private double xPosition, yPosition; + + /** + * Stores the x and y position of a swerve module + * + * @param xPosition The X position of the module relative to the center of the robot + * @param yPosition The Y position of the module relative to the center of the robot + */ + public SwerveModuleLocation(double xPosition, double yPosition) { + this.xPosition = xPosition; + this.yPosition = yPosition; + } + + /** + * Returns the X position of the module relative to the center of the robot in inches + * + * @return The raw X position of the module in inches + */ + public double getXPositionRaw() { + return this.xPosition; + } + + /** + * Returns the Y position of the module relative to the center of the robot in inches + * + * @return The raw Y position of the module in inches + */ + public double getYPositionRaw() { + return this.yPosition; + } + + /** + * Returns the X position of the module relative to the center of the robot in meters + * + * @return The calculated X position of the module in meters + */ + public double getXPositionCalculated() { + return Units.inchesToMeters(this.xPosition); + } + + /** + * Returns the Y position of the module relative to the center of the robot in meters + * + * @return The calculated Y position of the module in meters + */ + public double getYPositionCalculated() { + return Units.inchesToMeters(this.yPosition); + } +} diff --git a/src/main/java/frc/robot/subsystems/drive/swerve/SwerveModules.java b/src/main/java/frc/robot/subsystems/drive/swerve/SwerveModules.java new file mode 100644 index 0000000..9afff70 --- /dev/null +++ b/src/main/java/frc/robot/subsystems/drive/swerve/SwerveModules.java @@ -0,0 +1,53 @@ +package frc.robot.subsystems.drive.swerve; + +import com.ctre.phoenix6.mechanisms.swerve.SwerveModuleConstants; + +import frc.robot.subsystems.drive.DriveConstants; +import frc.robot.subsystems.drive.DriveConstants.DriveMotorId; +import frc.robot.subsystems.drive.DriveConstants.EncoderId; +import frc.robot.subsystems.drive.DriveConstants.EncoderOffset; +import frc.robot.subsystems.drive.DriveConstants.SteerMotorId; +import frc.robot.subsystems.drive.DriveConstants.SwerveModulePosition; + +/** This class houses the constants and assembled swerve module configurations for the robot */ +public class SwerveModules { + public static final SwerveModuleConstants FrontLeft = + DriveConstants.ConstantCreator.createModuleConstants( + SteerMotorId.kFrontLeftSteerMotorId, + DriveMotorId.kFrontLeftDriveMotorId, + EncoderId.kFrontLeftEncoderId, + EncoderOffset.kFrontLeftEncoderOffset, + SwerveModulePosition.kFrontLeft.getXPositionCalculated(), + SwerveModulePosition.kFrontLeft.getXPositionCalculated(), + DriveConstants.kInvertLeftSide); + + public static final SwerveModuleConstants FrontRight = + DriveConstants.ConstantCreator.createModuleConstants( + SteerMotorId.kFrontRightSteerMotorId, + DriveMotorId.kFrontRightDriveMotorId, + EncoderId.kFrontRightEncoderId, + EncoderOffset.kFrontRightEncoderOffset, + SwerveModulePosition.kFrontRight.getXPositionCalculated(), + SwerveModulePosition.kFrontRight.getXPositionCalculated(), + DriveConstants.kInvertRightSide); + + public static final SwerveModuleConstants BackLeft = + DriveConstants.ConstantCreator.createModuleConstants( + SteerMotorId.kBackLeftSteerMotorId, + DriveMotorId.kBackLeftDriveMotorId, + EncoderId.kBackLeftEncoderId, + EncoderOffset.kBackLeftEncoderOffset, + SwerveModulePosition.kBackLeft.getXPositionCalculated(), + SwerveModulePosition.kBackLeft.getXPositionCalculated(), + DriveConstants.kInvertLeftSide); + + public static final SwerveModuleConstants BackRight = + DriveConstants.ConstantCreator.createModuleConstants( + SteerMotorId.kBackRightSteerMotorId, + DriveMotorId.kBackRightDriveMotorId, + EncoderId.kBackRightEncoderId, + EncoderOffset.kBackRightEncoderOffset, + SwerveModulePosition.kBackRight.getXPositionCalculated(), + SwerveModulePosition.kBackRight.getXPositionCalculated(), + DriveConstants.kInvertRightSide); +} diff --git a/src/main/java/frc/robot/subsystems/intake/IntakeConstants.java b/src/main/java/frc/robot/subsystems/intake/IntakeConstants.java new file mode 100644 index 0000000..1e00685 --- /dev/null +++ b/src/main/java/frc/robot/subsystems/intake/IntakeConstants.java @@ -0,0 +1,34 @@ +package frc.robot.subsystems.intake; + +public class IntakeConstants { + + /** Used when scoring/outtaking */ + public class OuttakeSpeeds { + public static final double LOW_CUBE = 0.3; + public static final double LOW_CONE = 0.6; + public static final double LOW_CUBE_AUTO = 0.3; + public static final double LOW_CONE_AUTO = 1.0; + + public static final double MID_CONE = 0.1; + public static final double MID_CONE_TIPPED = 0.8; + public static final double MID_CUBE = 0.25; + public static final double MID_CUBE_AUTO = 0.2; + + public static final double HIGH_CONE = 0.15; + public static final double HIGH_CONE_AUTO = 0.15; + public static final double HIGH_CUBE = 0.55; + public static final double HIGH_CUBE_AUTO = 0.6; + } + + public static final double INTAKING_SPEED = 0.95; + + public static final int INTAKE_MOTOR_CHANNEL = 6; + public static final double INTAKE_AMP_THRESHOLD = 16; + + // 20ms per periodic cycle * number of periodic cycles / 1000 to get as milliseconds + public static final double INTAKE_CUBE_DELAY = (20 * 15) / 1000; + public static final double INTAKE_CONE_DELAY = (20 * 18) / 1000; + + public static final double HOLD_CONE_SPEED = 0.18; + public static final double HOLD_CUBE_SPEED = 0.15; +} diff --git a/src/main/java/frc/robot/subsystems/intake/IntakeSubsystem.java b/src/main/java/frc/robot/subsystems/intake/IntakeSubsystem.java new file mode 100644 index 0000000..ad21f11 --- /dev/null +++ b/src/main/java/frc/robot/subsystems/intake/IntakeSubsystem.java @@ -0,0 +1,128 @@ +package frc.robot.subsystems.intake; + +import edu.wpi.first.wpilibj.PowerDistribution; +import edu.wpi.first.wpilibj.motorcontrol.VictorSP; +import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard; +import edu.wpi.first.wpilibj2.command.Command; +import edu.wpi.first.wpilibj2.command.Commands; +import edu.wpi.first.wpilibj2.command.SubsystemBase; + +import frc.robot.subsystems.intake.enums.IntakeGamepiece; +import frc.robot.subsystems.intake.states.ScoringState; + +/** + * This an example implementation of our intake subsystem from 2023. + * + *

Each subsystem represents a different purpose of the robot such as arms, LEDs, drivetrains, + * shooter, etc. + */ +public class IntakeSubsystem extends SubsystemBase { + + private VictorSP intakeMotor = new VictorSP(IntakeConstants.INTAKE_MOTOR_CHANNEL); + private PowerDistribution pdp; + + public IntakeSubsystem(PowerDistribution pdp) { + this.pdp = pdp; + } + + /** + * This method runs once every 20ms. + * + *

This is useful for updating subsystem-specific state that you don't want to offload to a + * Command. + * + *

Teams should try to be consistent within their own codebases about which responsibilities + * will be handled by Commands, and which will be handled here. + */ + @Override + public void periodic() { + SmartDashboard.putNumber( + "INTAKE CURRENT", this.pdp.getCurrent(IntakeConstants.INTAKE_MOTOR_CHANNEL)); + SmartDashboard.putNumber("INTAKE SPEED", this.intakeMotor.get()); + } + + /** + * Runs a command that stops the intake + * + * @return a command that stops the intake + */ + public Command stopIntakeCommand() { + return runOnce(() -> this.intakeMotor.set(0)).ignoringDisable(true); + } + + /** + * Runs a command to score a gamepiece. + * + * @param scoringState the state to run the intake in + * @param expectedPiece the type of gamepiece to expect when scoring + * @return a command that scores a gamepiece + */ + public Command intakeScoreCommand(ScoringState scoringState, IntakeGamepiece expectedGamepiece) { + + SmartDashboard.putString("INTAKE STATE", scoringState.getStateName()); + + return run(() -> { + Commands.runOnce( + () -> outtakeCommand(scoringState.getOuttakeSpeed(expectedGamepiece)), this) + .andThen(Commands.waitSeconds(0.5)) + .andThen(stopIntakeCommand()); + }) + .andThen(stopIntakeCommand()); + } + + /** + * Runs a command that intakes and holds a gamepiece + * + * @param gamepiece the type of gamepiece to expect + * @return a command that forces the intake to hold the specified gamepiece + */ + public Command intakeHoldCommand(IntakeGamepiece gamepiece) { + return run(() -> { + this.intakeMotor.set(IntakeConstants.INTAKING_SPEED); + + // this will keep the motor running as long as the current is low enough + // this results in something similar to an if statement + Commands.waitUntil( + () -> + this.pdp.getCurrent(IntakeConstants.INTAKE_MOTOR_CHANNEL) + < IntakeConstants.INTAKE_AMP_THRESHOLD); + + if (gamepiece.equals(IntakeGamepiece.CUBE)) { + // wait a short amount of time so the gamepiece gets pulled in + Commands.waitSeconds(IntakeConstants.INTAKE_CUBE_DELAY); + this.intakeMotor.set(IntakeConstants.HOLD_CUBE_SPEED); + } + if (gamepiece.equals(IntakeGamepiece.CONE)) { + // we have a cone, so run the motor at a higher speed + Commands.waitSeconds(IntakeConstants.INTAKE_CONE_DELAY); + this.intakeMotor.set(IntakeConstants.HOLD_CONE_SPEED); + } + }) + .finallyDo(() -> this.intakeMotor.set(0)) + .ignoringDisable(false); + } + + /** + * Runs a command that spits out its gamepiece + * + * @param speed speed to run the motor at, this is pre-inverted + * @return a command that forces the intake to spit out its gamepiece + */ + public Command outtakeCommand(double speed) { + return runOnce(() -> this.intakeMotor.set(-speed)); + } + + /** + * This method is similar to periodic, but only runs in simulation mode. + * + *

Useful for updating subsystem-specific state that needs to be maintained for simulations, + * such as for updating edu.wpi.first.wpilibj.simulation classes and setting simulated sensor + * readings. + */ + @Override + public void simulationPeriodic() { + SmartDashboard.putNumber( + "INTAKE CURRENT", this.pdp.getCurrent(IntakeConstants.INTAKE_MOTOR_CHANNEL)); + SmartDashboard.putNumber("INTAKE SPEED", this.intakeMotor.get()); + } +} diff --git a/src/main/java/frc/robot/subsystems/intake/enums/IntakeGamepiece.java b/src/main/java/frc/robot/subsystems/intake/enums/IntakeGamepiece.java new file mode 100644 index 0000000..4fccfd5 --- /dev/null +++ b/src/main/java/frc/robot/subsystems/intake/enums/IntakeGamepiece.java @@ -0,0 +1,6 @@ +package frc.robot.subsystems.intake.enums; + +public enum IntakeGamepiece { + CONE, + CUBE +} diff --git a/src/main/java/frc/robot/subsystems/intake/states/ScoringState.java b/src/main/java/frc/robot/subsystems/intake/states/ScoringState.java new file mode 100644 index 0000000..0326901 --- /dev/null +++ b/src/main/java/frc/robot/subsystems/intake/states/ScoringState.java @@ -0,0 +1,68 @@ +package frc.robot.subsystems.intake.states; + +import frc.robot.subsystems.intake.enums.IntakeGamepiece; + +public abstract class ScoringState { + double outtakeSpeed; + double cubeOuttakeSpeed; + double coneOuttakeSpeed; + + String stateName; + + /** + * Creates a scoring state with the same outtake speed for cubes and cones + * + * @param outtakeSpeed The speed that should be used to outtake the gamepiece + * @param stateName The name of the scoring state that should be used for logging + */ + protected ScoringState(double outtakeSpeed, String stateName) { + this.outtakeSpeed = outtakeSpeed; + this.stateName = stateName; + } + + /** + * Creates a scoring state with different outtake speeds for cubes and cones + * + * @param cubeOuttakeSpeed The speed that should be used to outtake the cube gamepiece + * @param coneOuttakeSpeed The speed that should be used to outtake the cone gamepiece + * @param stateName The name of the scoring state that should be used for logging + */ + protected ScoringState(double cubeOuttakeSpeed, double coneOuttakeSpeed, String stateName) { + this.cubeOuttakeSpeed = cubeOuttakeSpeed; + this.coneOuttakeSpeed = coneOuttakeSpeed; + this.stateName = stateName; + } + + /** + * Gets the name of the scoring state + * + * @return The name of the scoring state + */ + public String getStateName() { + return this.stateName; + } + + /** + * Gets the outtake speed for the scoring state depending on the gamepiece that is expected to be + * scored + * + * @param expectedGamepiece The gamepiece that is expected to be scored + * @return The outtake speed for the scoring state + */ + public double getOuttakeSpeed(IntakeGamepiece expectedGamepiece) { + + if (expectedGamepiece == null) { + return this.outtakeSpeed; + } + + // Based on the gamepiece that is expected to be scored, return the appropriate outtake speed + switch (expectedGamepiece) { + case CUBE: + return this.cubeOuttakeSpeed; + case CONE: + return this.coneOuttakeSpeed; + default: + return 0.0; + } + } +} diff --git a/src/main/java/frc/robot/subsystems/intake/states/scoring/cone/HighCone.java b/src/main/java/frc/robot/subsystems/intake/states/scoring/cone/HighCone.java new file mode 100644 index 0000000..b8a2032 --- /dev/null +++ b/src/main/java/frc/robot/subsystems/intake/states/scoring/cone/HighCone.java @@ -0,0 +1,11 @@ +package frc.robot.subsystems.intake.states.scoring.cone; + +import frc.robot.subsystems.intake.IntakeConstants; +import frc.robot.subsystems.intake.states.ScoringState; + +public class HighCone extends ScoringState { + + public HighCone() { + super(IntakeConstants.OuttakeSpeeds.HIGH_CONE, "High Cone"); + } +} diff --git a/src/main/java/frc/robot/subsystems/intake/states/scoring/cone/HighConeAuto.java b/src/main/java/frc/robot/subsystems/intake/states/scoring/cone/HighConeAuto.java new file mode 100644 index 0000000..95c87c5 --- /dev/null +++ b/src/main/java/frc/robot/subsystems/intake/states/scoring/cone/HighConeAuto.java @@ -0,0 +1,11 @@ +package frc.robot.subsystems.intake.states.scoring.cone; + +import frc.robot.subsystems.intake.IntakeConstants; +import frc.robot.subsystems.intake.states.ScoringState; + +public class HighConeAuto extends ScoringState { + + public HighConeAuto() { + super(IntakeConstants.OuttakeSpeeds.HIGH_CONE_AUTO, "High Cone Auto"); + } +} diff --git a/src/main/java/frc/robot/subsystems/intake/states/scoring/cone/MidCone.java b/src/main/java/frc/robot/subsystems/intake/states/scoring/cone/MidCone.java new file mode 100644 index 0000000..4bc00f9 --- /dev/null +++ b/src/main/java/frc/robot/subsystems/intake/states/scoring/cone/MidCone.java @@ -0,0 +1,11 @@ +package frc.robot.subsystems.intake.states.scoring.cone; + +import frc.robot.subsystems.intake.IntakeConstants; +import frc.robot.subsystems.intake.states.ScoringState; + +public class MidCone extends ScoringState { + + public MidCone() { + super(IntakeConstants.OuttakeSpeeds.MID_CONE, "Mid Cone"); + } +} diff --git a/src/main/java/frc/robot/subsystems/intake/states/scoring/cone/MidConeTipped.java b/src/main/java/frc/robot/subsystems/intake/states/scoring/cone/MidConeTipped.java new file mode 100644 index 0000000..6be2495 --- /dev/null +++ b/src/main/java/frc/robot/subsystems/intake/states/scoring/cone/MidConeTipped.java @@ -0,0 +1,11 @@ +package frc.robot.subsystems.intake.states.scoring.cone; + +import frc.robot.subsystems.intake.IntakeConstants; +import frc.robot.subsystems.intake.states.ScoringState; + +public class MidConeTipped extends ScoringState { + + public MidConeTipped() { + super(IntakeConstants.OuttakeSpeeds.MID_CONE_TIPPED, "Mid Cone Tipped"); + } +} diff --git a/src/main/java/frc/robot/subsystems/intake/states/scoring/cube/HighCube.java b/src/main/java/frc/robot/subsystems/intake/states/scoring/cube/HighCube.java new file mode 100644 index 0000000..b260fd8 --- /dev/null +++ b/src/main/java/frc/robot/subsystems/intake/states/scoring/cube/HighCube.java @@ -0,0 +1,11 @@ +package frc.robot.subsystems.intake.states.scoring.cube; + +import frc.robot.subsystems.intake.IntakeConstants; +import frc.robot.subsystems.intake.states.ScoringState; + +public class HighCube extends ScoringState { + + public HighCube() { + super(IntakeConstants.OuttakeSpeeds.HIGH_CUBE, "High Cube"); + } +} diff --git a/src/main/java/frc/robot/subsystems/intake/states/scoring/cube/HighCubeAuto.java b/src/main/java/frc/robot/subsystems/intake/states/scoring/cube/HighCubeAuto.java new file mode 100644 index 0000000..65d5964 --- /dev/null +++ b/src/main/java/frc/robot/subsystems/intake/states/scoring/cube/HighCubeAuto.java @@ -0,0 +1,11 @@ +package frc.robot.subsystems.intake.states.scoring.cube; + +import frc.robot.subsystems.intake.IntakeConstants; +import frc.robot.subsystems.intake.states.ScoringState; + +public class HighCubeAuto extends ScoringState { + + public HighCubeAuto() { + super(IntakeConstants.OuttakeSpeeds.HIGH_CUBE, "High Cube Auto"); + } +} diff --git a/src/main/java/frc/robot/subsystems/intake/states/scoring/cube/MidCube.java b/src/main/java/frc/robot/subsystems/intake/states/scoring/cube/MidCube.java new file mode 100644 index 0000000..dea9eac --- /dev/null +++ b/src/main/java/frc/robot/subsystems/intake/states/scoring/cube/MidCube.java @@ -0,0 +1,11 @@ +package frc.robot.subsystems.intake.states.scoring.cube; + +import frc.robot.subsystems.intake.IntakeConstants; +import frc.robot.subsystems.intake.states.ScoringState; + +public class MidCube extends ScoringState { + + public MidCube() { + super(IntakeConstants.OuttakeSpeeds.MID_CUBE, "Mid Cube"); + } +} diff --git a/src/main/java/frc/robot/subsystems/intake/states/scoring/cube/MidCubeAuto.java b/src/main/java/frc/robot/subsystems/intake/states/scoring/cube/MidCubeAuto.java new file mode 100644 index 0000000..75cc262 --- /dev/null +++ b/src/main/java/frc/robot/subsystems/intake/states/scoring/cube/MidCubeAuto.java @@ -0,0 +1,11 @@ +package frc.robot.subsystems.intake.states.scoring.cube; + +import frc.robot.subsystems.intake.IntakeConstants; +import frc.robot.subsystems.intake.states.ScoringState; + +public class MidCubeAuto extends ScoringState { + + public MidCubeAuto() { + super(IntakeConstants.OuttakeSpeeds.MID_CUBE, "Mid Cube Auto"); + } +} diff --git a/src/main/java/frc/robot/subsystems/intake/states/scoring/level/Low.java b/src/main/java/frc/robot/subsystems/intake/states/scoring/level/Low.java new file mode 100644 index 0000000..de19cf1 --- /dev/null +++ b/src/main/java/frc/robot/subsystems/intake/states/scoring/level/Low.java @@ -0,0 +1,11 @@ +package frc.robot.subsystems.intake.states.scoring.level; + +import frc.robot.subsystems.intake.IntakeConstants; +import frc.robot.subsystems.intake.states.ScoringState; + +public class Low extends ScoringState { + + public Low() { + super(IntakeConstants.OuttakeSpeeds.LOW_CUBE, IntakeConstants.OuttakeSpeeds.LOW_CONE, "Low"); + } +} diff --git a/src/main/java/frc/robot/subsystems/intake/states/scoring/level/LowAuto.java b/src/main/java/frc/robot/subsystems/intake/states/scoring/level/LowAuto.java new file mode 100644 index 0000000..cf1cc1f --- /dev/null +++ b/src/main/java/frc/robot/subsystems/intake/states/scoring/level/LowAuto.java @@ -0,0 +1,14 @@ +package frc.robot.subsystems.intake.states.scoring.level; + +import frc.robot.subsystems.intake.IntakeConstants; +import frc.robot.subsystems.intake.states.ScoringState; + +public class LowAuto extends ScoringState { + + public LowAuto() { + super( + IntakeConstants.OuttakeSpeeds.LOW_CUBE_AUTO, + IntakeConstants.OuttakeSpeeds.LOW_CONE_AUTO, + "Low Auto"); + } +} diff --git a/src/main/java/frc/robot/subsystems/led/LEDColour.java b/src/main/java/frc/robot/subsystems/led/LEDColour.java new file mode 100644 index 0000000..c908bb2 --- /dev/null +++ b/src/main/java/frc/robot/subsystems/led/LEDColour.java @@ -0,0 +1,89 @@ +package frc.robot.subsystems.led; + +/** + * This is only used for setting solid colour states. Please see the LEDModes.java class for special + * LED modes + */ +public class LEDColour { + private double red, green, blue; + + public LEDColour(int red, int green, int blue) { + this.red = red; + this.green = green; + this.blue = blue; + } + + public LEDColour(double red, double green, double blue) { + this.red = red; + this.green = green; + this.blue = blue; + } + + /** + * Copies the RGB values from an LEDState object to the current one + * + * @param ledState The LEDState to copy the RGB values from + * @return The current instane of LEDColour with the new RGB values + */ + public LEDColour copy(LEDColour ledState) { + this.red = ledState.red; + this.green = ledState.green; + this.blue = ledState.blue; + + return this; + } + + /** + * Gets the red value of an LED state object + * + * @return The double value of the red value in the RGB sequence + */ + public double getRed() { + return this.red; + } + + /** + * Gets the red value of an LED state object as an integer + * + * @return The integer value of the red value in the RGB sequence + */ + public int getRedInt() { + return (int) this.red; + } + + /** + * Gets the green value of an LED state object + * + * @return The double value of the green value in the RGB sequence + */ + public double getGreen() { + return this.green; + } + + /** + * Gets the green value of an LED state object as an integer + * + * @return The integer value of the green value in the RGB sequence + */ + public int getGreenInt() { + return (int) this.green; + } + + /** + * Gets the blue value of an LED state object + * + * @return The integer value of the blue value in the RGB sequence + */ + public double getBlue() { + return this.blue; + } + + /** + * Gets the blue value of an LED state object as an integer + * + * @return The integer value of the blue value in the RGB sequence + */ + public int getBlueInt() { + return (int) this.blue; + } +} diff --git a/src/main/java/frc/robot/subsystems/led/LEDConstants.java b/src/main/java/frc/robot/subsystems/led/LEDConstants.java new file mode 100644 index 0000000..970777a --- /dev/null +++ b/src/main/java/frc/robot/subsystems/led/LEDConstants.java @@ -0,0 +1,7 @@ +package frc.robot.subsystems.led; + +public class LEDConstants { + + public static final int ledsPerSegment = + 16; // The number LEDs (actual diodes) there are on each segment +} diff --git a/src/main/java/frc/robot/subsystems/led/LEDSegment.java b/src/main/java/frc/robot/subsystems/led/LEDSegment.java new file mode 100644 index 0000000..9c6e9c7 --- /dev/null +++ b/src/main/java/frc/robot/subsystems/led/LEDSegment.java @@ -0,0 +1,76 @@ +package frc.robot.subsystems.led; + +import frc.robot.subsystems.led.exceptions.InvalidLEDSegmentException; +import frc.robot.subsystems.led.modes.Breathing; +import frc.robot.subsystems.led.modes.LEDMode; +import frc.robot.subsystems.led.modes.Solid; + +/** + * We are going to attach a name to each index in the LED modes array here. Remember that all + * indexes start at 0 and NOT 1 + */ +public enum LEDSegment { + + // Add all aliases for segments below + FrontLeft(0, new Solid(new LEDColour(255, 0, 0))), // Set the LEDs to solid red + BackLeft(1, new Breathing(new LEDColour(0, 255, 0))), // Create a green breathing effect + BackRight(2, new Breathing(new LEDColour(0, 255, 0))), // Create a green breathing effect + FrontRight(3, new Solid(new LEDColour(255, 0, 0))); // Set the LEDs to solid red + + public final int segmentNumber; // The segment number of the LED strip (starts at 1 and goes up) + public LEDMode ledMode; // The mode of the LED strip + + private LEDSegment(int segmentNumber, LEDMode defaultLedMode) { + this.segmentNumber = segmentNumber; + this.ledMode = defaultLedMode; + } + + /** + * Checks if the LED segment is a valid segment based off of the number of segments in + * LEDConstants.java + * + * @return True if it is within the index bounds, false if it isn't + */ + private boolean isValid() { + // Return a boolean based on if the segment number is greater than number of + // segments + return (this.getSegmentIdentifier() < LEDSubsystem.ledSegments.size()); + } + + /** + * Sets the mode of the current segment + * + * @param ledMode The mode to set the LEDs to + */ + public void setLedMode(LEDMode ledMode) { + // Guard clause to check if the segment is within the bounds of the number of + // available segments + if (!this.isValid()) { + throw new InvalidLEDSegmentException( + String.format( + "Invalid LED segment: %d. Number of segments: %d", + this.getSegmentIdentifier(), + LEDSubsystem.ledSegments.size())); // Throw an exception with the segment information + } + + this.ledMode = ledMode; + } + + /** + * Retrieves the true segment number of an LED segment + * + * @return The segment number of the LED segment + */ + public int getSegmentIdentifier() { + return this.segmentNumber; + } + + /** + * Gets the LED mode for this segment + * + * @return The LED mode of the current segment as an LEDMode object + */ + public LEDMode getLedMode() { + return this.ledMode; + } +} diff --git a/src/main/java/frc/robot/subsystems/led/LEDSubsystem.java b/src/main/java/frc/robot/subsystems/led/LEDSubsystem.java new file mode 100644 index 0000000..e0242d8 --- /dev/null +++ b/src/main/java/frc/robot/subsystems/led/LEDSubsystem.java @@ -0,0 +1,60 @@ +package frc.robot.subsystems.led; + +import java.util.ArrayList; +import java.util.List; + +import edu.wpi.first.wpilibj.AddressableLED; +import edu.wpi.first.wpilibj.AddressableLEDBuffer; +import edu.wpi.first.wpilibj2.command.SubsystemBase; +import frc.robot.subsystems.led.modes.LEDMode; + +public class LEDSubsystem extends SubsystemBase { + + public static List ledSegments = new ArrayList<>(); + + public static AddressableLED leds = + new AddressableLED(0); // The PWM port the LEDs are plugged into + public static AddressableLEDBuffer ledBuffer = + new AddressableLEDBuffer( + (ledSegments.size() * LEDConstants.ledsPerSegment)); // The buffer that holds the LED data + + @Override + public void periodic() { + // For every segment that is registered, run the periodic function + for (LEDSegment ledSegment : ledSegments) { + ledSegment.getLedMode().periodic(ledSegment.getSegmentIdentifier()); + } + } + + /** Does the basic initialization process of setting the length of the LEDs */ + public void initialize() { + // Register all LED segments into the array + ledSegments.add(LEDSegment.FrontLeft); + ledSegments.add(LEDSegment.BackLeft); + ledSegments.add(LEDSegment.BackRight); + ledSegments.add(LEDSegment.FrontRight); + + leds.setLength( + (ledSegments.size() * LEDConstants.ledsPerSegment)); // Set the length of the LED strip + + leds.start(); // Start the LED strip + } + + /** + * Sets the mode for all segments available + * + * @param ledMode The mode to set them all as. Please see the modes directory for all available + * modes + */ + public void setAllSegmentModesCommand(LEDMode ledMode) { + // For every segment we can set the mode of, set the mode as the one provided + for (LEDSegment ledSegment : ledSegments) { + ledSegment.setLedMode(ledMode); + } + } + + /** Initialize the LED subsystem when we create an instance of it */ + public LEDSubsystem() { + this.initialize(); + } +} diff --git a/src/main/java/frc/robot/subsystems/led/README.md b/src/main/java/frc/robot/subsystems/led/README.md new file mode 100644 index 0000000..eaea1a3 --- /dev/null +++ b/src/main/java/frc/robot/subsystems/led/README.md @@ -0,0 +1,36 @@ +## How do I create a new segment of LEDs? +Creating a new segment could not be easier. Simply just do the following: +1. Open the `LEDSegment` class in this subsystem directory. +2. Add a new segment with a proper name at the top of the class with the rest of the segments. +3. Set the segment number to what number of segment it is. This number will start at 1 from the first segment and will go up by one for each segment you create. +4. Set the default mode to be something like `SolidRed` or to another mode you've created. +5. Add the following block of code to the initialize function in the `LEDSubsystem` class, be sure to replace segment name with the name of your segment as specified previously : +``` +LEDConstants.ledSegments.add(LEDSegment.); +``` + +You're all set now! You've successfully created a new segment of LEDs for your robot. + +## How do I create a new LED mode? +We've made it simple and easy to create LED modes with this subsystem. Just follow the following steps: +1. Create a new Java class in the `modes` directory. +2. Extend that class from the `LEDMode` class. +3. Add any code you want to repeat every 20ms to the periodic section! This should update your LED's constantly when they're on that mode. +4. Add any code you want to run initially into the initialize section. + +You're done! Our goal was to make this as simple and easy to do as a beginner and with limited Java knowledge. + +## I'm getting an "Invalid LED segment" error, how do I fix it? +It seems that the index for the segment is higher than the number of segments specified in the `LEDConstants` class. Be aware that an index is different than the actual number of segments, it will always be one less than what the segment number is. + +Be sure to either change the value of the number of segments, or update the index of your segment. + +## Why is my LED segment not turning on? +This could be for several reason. Please see the following for possible causes: +- Does your console have any errors in it regarding LEDs? +- Is the segment registered and added to the ledSegments array in the initialize function in `LEDSubsystem` class? +- Is the segment created in the `LEDSegment` class and have a valid index and LED mode? +- Is the number of LEDs per segment the actual number on the physical LED strip? +- Is the number of segments the correct number? + +Hopefully this gets the main points of failure. If for some reason the issue is due to our codebase, feel free to open an issue via our [issue tracker](https://github.com/Simbotics/Simbot-Base/issues). Be sure to follow our issue template when opening an issue. \ No newline at end of file diff --git a/src/main/java/frc/robot/subsystems/led/exceptions/InvalidLEDSegmentException.java b/src/main/java/frc/robot/subsystems/led/exceptions/InvalidLEDSegmentException.java new file mode 100644 index 0000000..d4d82f1 --- /dev/null +++ b/src/main/java/frc/robot/subsystems/led/exceptions/InvalidLEDSegmentException.java @@ -0,0 +1,7 @@ +package frc.robot.subsystems.led.exceptions; + +public class InvalidLEDSegmentException extends RuntimeException { + public InvalidLEDSegmentException(String message) { + super(message); + } +} diff --git a/src/main/java/frc/robot/subsystems/led/modes/Breathing.java b/src/main/java/frc/robot/subsystems/led/modes/Breathing.java new file mode 100644 index 0000000..35cafb1 --- /dev/null +++ b/src/main/java/frc/robot/subsystems/led/modes/Breathing.java @@ -0,0 +1,41 @@ +package frc.robot.subsystems.led.modes; + +import edu.wpi.first.wpilibj.util.Color; +import frc.robot.subsystems.led.LEDColour; +import frc.robot.subsystems.led.LEDConstants; +import frc.robot.subsystems.led.LEDSubsystem; + +public class Breathing extends LEDMode { + private int cycle; + private LEDColour ledColor; + + public Breathing(LEDColour ledColour) { + this.ledColor = ledColour; + } + + @Override + public void initialize() { + this.cycle = 1; + System.out.println("Starting the Breathing mode"); + } + + @Override + public void periodic(int segmentIndex) { + int minSegWindow = segmentIndex * LEDConstants.ledsPerSegment; + int maxSegWindow = minSegWindow + LEDConstants.ledsPerSegment; + + for (int i = minSegWindow; i < maxSegWindow; i++) { + LEDSubsystem.ledBuffer.setLED(i, calculateBreathingColor()); + } + + this.cycle++; + } + + private Color calculateBreathingColor() { + double breathingValue = (Math.sin(Math.PI * this.cycle / 80.0) + 1.0) / 2.0; + return new Color( + this.ledColor.getRed() * breathingValue, + this.ledColor.getGreen() * breathingValue, + this.ledColor.getBlue() * breathingValue); + } +} diff --git a/src/main/java/frc/robot/subsystems/led/modes/LEDMode.java b/src/main/java/frc/robot/subsystems/led/modes/LEDMode.java new file mode 100644 index 0000000..1037284 --- /dev/null +++ b/src/main/java/frc/robot/subsystems/led/modes/LEDMode.java @@ -0,0 +1,10 @@ +package frc.robot.subsystems.led.modes; + +public abstract class LEDMode { + + /** Runs when the mode is first called. This can be used to set constants/variabled */ + public abstract void initialize(); + + /** Runs constantly every 20ms, this is where you want to calculate what your LEDs do */ + public abstract void periodic(int segmentIndex); +} diff --git a/src/main/java/frc/robot/subsystems/led/modes/Solid.java b/src/main/java/frc/robot/subsystems/led/modes/Solid.java new file mode 100644 index 0000000..60587db --- /dev/null +++ b/src/main/java/frc/robot/subsystems/led/modes/Solid.java @@ -0,0 +1,35 @@ +package frc.robot.subsystems.led.modes; + +import frc.robot.subsystems.led.LEDColour; +import frc.robot.subsystems.led.LEDConstants; +import frc.robot.subsystems.led.LEDSubsystem; + +public class Solid extends LEDMode { + private LEDColour ledColour; + + public Solid(LEDColour ledColour) { + this.ledColour = ledColour; + } + + @Override + public void initialize() { + System.out.println("Starting the Solid LED mode"); + } + + @Override + public void periodic(int segmentIndex) { + int minSegWindow = + segmentIndex + * LEDConstants.ledsPerSegment; // Set the start of the segment to display the LEDs from + int maxSegWindow = + minSegWindow + + LEDConstants + .ledsPerSegment; // Set the end of the segment so we know where to stop displaying + // LEDs + // For every LED in the segment window, we are going to set its colour + for (int i = minSegWindow; i < maxSegWindow; i++) { + LEDSubsystem.ledBuffer.setRGB( + i, ledColour.getRedInt(), ledColour.getGreenInt(), ledColour.getBlueInt()); + } + } +} diff --git a/src/main/java/frc/robot/subsystems/led/modes/special/Off.java b/src/main/java/frc/robot/subsystems/led/modes/special/Off.java new file mode 100644 index 0000000..072db3c --- /dev/null +++ b/src/main/java/frc/robot/subsystems/led/modes/special/Off.java @@ -0,0 +1,35 @@ +package frc.robot.subsystems.led.modes.special; + +import frc.robot.subsystems.led.LEDColour; +import frc.robot.subsystems.led.LEDConstants; +import frc.robot.subsystems.led.LEDSubsystem; +import frc.robot.subsystems.led.modes.LEDMode; + +public class Off extends LEDMode { + + @Override + public void initialize() { + System.out.println("Starting the Off LED mode"); + } + + @Override + public void periodic(int segmentIndex) { + int minSegWindow = + segmentIndex + * LEDConstants.ledsPerSegment; // Set the start of the segment to display the LEDs from + int maxSegWindow = + minSegWindow + + LEDConstants + .ledsPerSegment; // Set the end of the segment so we know where to stop displaying + // LEDs + + LEDColour offLedColour = + new LEDColour(0, 0, 0); // Create a new RGB sequence with all values set to 0 + + // For every LED in the segment window, we are going to set its colour + for (int i = minSegWindow; i < maxSegWindow; i++) { + LEDSubsystem.ledBuffer.setRGB( + i, offLedColour.getRedInt(), offLedColour.getGreenInt(), offLedColour.getBlueInt()); + } + } +} diff --git a/src/main/java/frc/robot/subsystems/led/modes/special/PrideBi.java b/src/main/java/frc/robot/subsystems/led/modes/special/PrideBi.java new file mode 100644 index 0000000..5f8fb7e --- /dev/null +++ b/src/main/java/frc/robot/subsystems/led/modes/special/PrideBi.java @@ -0,0 +1,58 @@ +package frc.robot.subsystems.led.modes.special; + +import frc.robot.subsystems.led.LEDColour; +import frc.robot.subsystems.led.LEDConstants; +import frc.robot.subsystems.led.LEDSubsystem; +import frc.robot.subsystems.led.modes.LEDMode; + +public class PrideBi extends LEDMode { + + @Override + public void initialize() { + System.out.println("Starting the PrideBi LED mode"); + } + + @Override + public void periodic(int segmentIndex) { + int minSegWindow = + segmentIndex + * LEDConstants.ledsPerSegment; // Set the start of the segment to display the LEDs from + int maxSegWindow = + minSegWindow + + LEDConstants + .ledsPerSegment; // Set the end of the segment so we know where to stop displaying + // LEDs + + // Divide the segment into three equal parts + int segmentSize = LEDConstants.ledsPerSegment / 3; + + // Set the color for each part + LEDColour magentaLedColour = new LEDColour(214, 2, 112); + LEDColour purpleLedColour = new LEDColour(155, 79, 150); + LEDColour royalBlueLedColour = new LEDColour(0, 56, 168); + + // For every LED in the segment window, set its color based on the part of the segment it + // belongs to + for (int i = minSegWindow; i < maxSegWindow; i++) { + if (i < minSegWindow + segmentSize) { + LEDSubsystem.ledBuffer.setRGB( + i, + magentaLedColour.getRedInt(), + magentaLedColour.getGreenInt(), + magentaLedColour.getBlueInt()); + } else if (i < minSegWindow + 2 * segmentSize) { + LEDSubsystem.ledBuffer.setRGB( + i, + purpleLedColour.getRedInt(), + purpleLedColour.getGreenInt(), + purpleLedColour.getBlueInt()); + } else { + LEDSubsystem.ledBuffer.setRGB( + i, + royalBlueLedColour.getRedInt(), + royalBlueLedColour.getGreenInt(), + royalBlueLedColour.getBlueInt()); + } + } + } +} diff --git a/src/main/java/frc/robot/subsystems/led/modes/special/RGBSplit.java b/src/main/java/frc/robot/subsystems/led/modes/special/RGBSplit.java new file mode 100644 index 0000000..d19fd22 --- /dev/null +++ b/src/main/java/frc/robot/subsystems/led/modes/special/RGBSplit.java @@ -0,0 +1,52 @@ +package frc.robot.subsystems.led.modes.special; + +import frc.robot.subsystems.led.LEDColour; +import frc.robot.subsystems.led.LEDConstants; +import frc.robot.subsystems.led.LEDSubsystem; +import frc.robot.subsystems.led.modes.LEDMode; + +public class RGBSplit extends LEDMode { + + @Override + public void initialize() { + System.out.println("Starting the RGBSplit LED mode"); + } + + @Override + public void periodic(int segmentIndex) { + int minSegWindow = + segmentIndex + * LEDConstants.ledsPerSegment; // Set the start of the segment to display the LEDs from + int maxSegWindow = + minSegWindow + + LEDConstants + .ledsPerSegment; // Set the end of the segment so we know where to stop displaying + // LEDs + + // Divide the segment into three equal parts + int segmentSize = LEDConstants.ledsPerSegment / 3; + + // Set the color for each part + LEDColour redLedColour = new LEDColour(255, 0, 0); + LEDColour greenLedColour = new LEDColour(0, 255, 0); + LEDColour blueLedColour = new LEDColour(0, 0, 255); + + // For every LED in the segment window, set its color based on the part of the segment it + // belongs to + for (int i = minSegWindow; i < maxSegWindow; i++) { + if (i < minSegWindow + segmentSize) { + LEDSubsystem.ledBuffer.setRGB( + i, redLedColour.getRedInt(), redLedColour.getGreenInt(), redLedColour.getBlueInt()); + } else if (i < minSegWindow + 2 * segmentSize) { + LEDSubsystem.ledBuffer.setRGB( + i, + greenLedColour.getRedInt(), + greenLedColour.getGreenInt(), + greenLedColour.getBlueInt()); + } else { + LEDSubsystem.ledBuffer.setRGB( + i, blueLedColour.getRedInt(), blueLedColour.getGreenInt(), blueLedColour.getBlueInt()); + } + } + } +} diff --git a/src/main/java/frc/robot/subsystems/led/modes/special/Rainbow.java b/src/main/java/frc/robot/subsystems/led/modes/special/Rainbow.java new file mode 100644 index 0000000..b4ebafa --- /dev/null +++ b/src/main/java/frc/robot/subsystems/led/modes/special/Rainbow.java @@ -0,0 +1,35 @@ +package frc.robot.subsystems.led.modes.special; + +import frc.robot.subsystems.led.LEDConstants; +import frc.robot.subsystems.led.LEDSubsystem; +import frc.robot.subsystems.led.modes.LEDMode; + +public class Rainbow extends LEDMode { + + @Override + public void initialize() { + System.out.println("Starting the Rainbow LED mode"); + } + + @Override + public void periodic(int segmentIndex) { + int minSegWindow = + segmentIndex + * LEDConstants.ledsPerSegment; // Set the start of the segment to display the LEDs from + int maxSegWindow = + minSegWindow + + LEDConstants + .ledsPerSegment; // Set the end of the segment so we know where to stop displaying + // LEDs + + double rainbowSpeed = + 15.0; // Set the speed of the rainbow, the higher the number, the faster it is + double hue = + System.currentTimeMillis() + / rainbowSpeed; // Calculate the hue based off the speed and the current system time + + for (int i = minSegWindow; i < maxSegWindow; i++) { + LEDSubsystem.ledBuffer.setHSV(i, (int) ((hue + i * 5) % 180), 255, 255); + } + } +} diff --git a/src/main/java/frc/robot/subsystems/led/modes/special/StaticPride.java b/src/main/java/frc/robot/subsystems/led/modes/special/StaticPride.java new file mode 100644 index 0000000..2e8f49c --- /dev/null +++ b/src/main/java/frc/robot/subsystems/led/modes/special/StaticPride.java @@ -0,0 +1,29 @@ +package frc.robot.subsystems.led.modes.special; + +import frc.robot.subsystems.led.LEDConstants; +import frc.robot.subsystems.led.LEDSubsystem; +import frc.robot.subsystems.led.modes.LEDMode; + +public class StaticPride extends LEDMode { + + @Override + public void initialize() { + System.out.println("Starting the StaticPride LED mode"); + } + + @Override + public void periodic(int segmentIndex) { + int minSegWindow = + segmentIndex + * LEDConstants.ledsPerSegment; // Set the start of the segment to display the LEDs from + int maxSegWindow = + minSegWindow + + LEDConstants + .ledsPerSegment; // Set the end of the segment so we know where to stop displaying + // LEDs + + for (int i = minSegWindow; i < maxSegWindow; i++) { + LEDSubsystem.ledBuffer.setHSV(i, (int) ((i * 5) % 180), 255, 255); + } + } +} diff --git a/src/main/java/frc/robot/subsystems/limelight/Limelight.java b/src/main/java/frc/robot/subsystems/limelight/Limelight.java new file mode 100644 index 0000000..25af1c6 --- /dev/null +++ b/src/main/java/frc/robot/subsystems/limelight/Limelight.java @@ -0,0 +1,142 @@ +package frc.robot.subsystems.limelight; + +import edu.wpi.first.math.geometry.Pose2d; +import edu.wpi.first.math.geometry.Pose3d; +import edu.wpi.first.networktables.NetworkTable; +import edu.wpi.first.networktables.NetworkTableEntry; +import edu.wpi.first.networktables.NetworkTableInstance; +import edu.wpi.first.wpilibj.DriverStation; +import edu.wpi.first.wpilibj.Timer; +import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard; +import frc.robot.subsystems.limelight.enums.LimelightLEDMode; +import frc.robot.subsystems.limelight.enums.LimelightStream; + +public class Limelight implements LimelightIO { + private String limelightName; + private NetworkTable networkTable; + private LimelightPoseData limelightPoseData; + + /** + * Creates a new limelight + * + * @param limelightName The name of the limelight + */ + public Limelight(String limelightName) { + this.limelightName = limelightName; + + // Initializes and sets camera and pipeline of the limelight + this.networkTable = NetworkTableInstance.getDefault().getTable(this.limelightName); + this.networkTable.getEntry(LimelightConstants.TableConstants.CAMERA).setNumber(0); + this.networkTable.getEntry(LimelightConstants.TableConstants.PIPELINE).setNumber(0); + } + + @Override + public void updateData(LimelightIOData limelightIOData) { + NetworkTableEntry robotPoseEntry; + + // Creates a new limelight pose data instance if it doesn't exist + if (this.limelightPoseData == null) { + this.limelightPoseData = new LimelightPoseData(new double[7]); + } + + // Sets the pose of the limelight based on what alliance we are on + if (DriverStation.getAlliance().get() == DriverStation.Alliance.Blue) { + robotPoseEntry = this.networkTable.getEntry(LimelightConstants.TableConstants.BOT_POSE_BLUE); + } else if (DriverStation.getAlliance().get() == DriverStation.Alliance.Red) { + robotPoseEntry = this.networkTable.getEntry(LimelightConstants.TableConstants.BOT_POSE_RED); + } else { + robotPoseEntry = this.networkTable.getEntry(LimelightConstants.TableConstants.BOT_POSE); + } + + // Update the limelight pose data + this.limelightPoseData.update(robotPoseEntry.getDoubleArray(new double[7])); + + // Create a 3d pose from data from the limelight + Pose3d limelightPose = limelightPoseData.toPose3d(); + + // Set if the limelight is locked onto a target + if (this.networkTable.getEntry(LimelightConstants.TableConstants.VALID_TARGET).getDouble(0) + == 1) { + limelightIOData.lockedOnTarget = true; + } else { + limelightIOData.lockedOnTarget = false; + } + + if (limelightIOData.lockedOnTarget) { + // Set the time that the limelight was updated + limelightIOData.limelightLastUpdated = + Timer.getFPGATimestamp() - limelightPoseData.getTotalLatency(); + + limelightIOData.isNewPose = true; + + // Set the target x and y + limelightIOData.targetX = + this.networkTable.getEntry(LimelightConstants.TableConstants.TARGET_X).getDouble(0); + limelightIOData.targetY = + this.networkTable.getEntry(LimelightConstants.TableConstants.TARGET_Y).getDouble(0); + + // Set the pose of the limelight (x, y, rotation) + Pose2d pose2d = limelightPose.toPose2d(); + limelightIOData.limelightX = pose2d.getX(); + limelightIOData.limelightY = pose2d.getY(); + limelightIOData.limelightRotation = pose2d.getRotation().getRadians(); + limelightIOData.limelightPose = pose2d; + + } else { + limelightIOData.isNewPose = false; + + // Set the target x and y to 0 because we don't have a target + limelightIOData.targetX = 0; + limelightIOData.targetY = 0; + } + } + + /** Gets the name of a limelight that's registered into the network table */ + @Override + public String getName() { + return this.limelightName; + } + + /** + * Sets the led mode of the limelight + * + * @param limelightLEDMode The led mode to set the limelight to (see LimelightLEDMode.java for + * options) + */ + public void setLEDMode(LimelightLEDMode limelightLEDMode) { + this.networkTable + .getEntry(LimelightConstants.TableConstants.LED_MODE) + .setNumber(limelightLEDMode.getNetworkTableValue()); + } + + /** + * Sets the streaming mode of the limelight + * + * @param stream The streaming mode to set the limelight to as a limelight stream type + */ + public void setStream(LimelightStream limelightStream) { + this.networkTable + .getEntry(LimelightConstants.TableConstants.STREAM) + .setNumber(limelightStream.getNetworkTableValue()); + } + + /** + * Sets the pipeline of the limelight based on our presets for it + * + * @param pipeline The pipeline to set the limelight to as an integer + */ + public void setPipeline(int pipeline) { + this.networkTable.getEntry(LimelightConstants.TableConstants.PIPELINE).setNumber(pipeline); + } + + /** + * Displays the pose values of the limelight on the smart dashboard + * + * @param pose The pose to retrieve and display the values from + */ + public void displayLimelightPoseValues(Pose2d pose) { + SmartDashboard.putNumber(this.limelightName + " x", pose.getX()); + SmartDashboard.putNumber(this.limelightName + " y", pose.getY()); + SmartDashboard.putNumber(this.limelightName + " angleDegrees", pose.getRotation().getDegrees()); + } +} diff --git a/src/main/java/frc/robot/subsystems/limelight/LimelightConstants.java b/src/main/java/frc/robot/subsystems/limelight/LimelightConstants.java new file mode 100644 index 0000000..96c7797 --- /dev/null +++ b/src/main/java/frc/robot/subsystems/limelight/LimelightConstants.java @@ -0,0 +1,29 @@ +package frc.robot.subsystems.limelight; + +import edu.wpi.first.math.util.Units; + +public class LimelightConstants { + public static final double LOWEST_DISTANCE = Units.feetToMeters(10.0); + + static class TableConstants { + /** + * All tables for limelights can be found here + * https://docs.limelightvision.io/docs/docs-limelight/apis/complete-networktables-api + */ + public static final String CAMERA = "camera"; + + public static final String PIPELINE = "pipeline"; + public static final String LED_MODE = "ledMode"; + public static final String STREAM = "stream"; + + // Driverstation/botposes + public static final String BOT_POSE_BLUE = "botpose_wpiblue"; + public static final String BOT_POSE_RED = "botpose_wpired"; + public static final String BOT_POSE = "botpose"; + + // Targeting related + public static final String VALID_TARGET = "tv"; + public static final String TARGET_X = "tx"; + public static final String TARGET_Y = "ty"; + } +} diff --git a/src/main/java/frc/robot/subsystems/limelight/LimelightIO.java b/src/main/java/frc/robot/subsystems/limelight/LimelightIO.java new file mode 100644 index 0000000..903f4bb --- /dev/null +++ b/src/main/java/frc/robot/subsystems/limelight/LimelightIO.java @@ -0,0 +1,52 @@ +package frc.robot.subsystems.limelight; + +import edu.wpi.first.math.geometry.Pose2d; + +public interface LimelightIO { + + /** Data that the limelight will collect and track */ + class LimelightIOData { + public double limelightX; + public double limelightY; + public double limelightRotation; + + public double targetX; + public double targetY; + + public boolean isNewPose; + public Pose2d limelightPose; + public double limelightLastUpdated; + + public double maxDistance; + public double minDistance; + + public boolean lockedOnTarget = false; + public int singleIDUsed; + + public double translationToTargetX; + public double translationToTargetY; + } + + /** + * Updates the data of the limelight + * + * @param data + */ + default void updateData(LimelightIOData data) {} + + /** + * Gets, or sets the name of the limelight + * + * @return + */ + default String getName() { + return ""; + } + + /** + * Sets the refrence pose of the limelight + * + * @param pose + */ + default void setRefrencePose(Pose2d pose) {} +} diff --git a/src/main/java/frc/robot/subsystems/limelight/LimelightPoseAndTimestamp.java b/src/main/java/frc/robot/subsystems/limelight/LimelightPoseAndTimestamp.java new file mode 100644 index 0000000..3b22ad8 --- /dev/null +++ b/src/main/java/frc/robot/subsystems/limelight/LimelightPoseAndTimestamp.java @@ -0,0 +1,21 @@ +package frc.robot.subsystems.limelight; + +import edu.wpi.first.math.geometry.Pose2d; + +public class LimelightPoseAndTimestamp { + Pose2d limelightPose; + double lastUpdatedTimestamp; + + public LimelightPoseAndTimestamp(Pose2d limelightPose, double lastUpdatedTimestamp) { + this.limelightPose = limelightPose; + this.lastUpdatedTimestamp = lastUpdatedTimestamp; + } + + public Pose2d getLimelightPose() { + return this.limelightPose; + } + + public double getLastUpdatedTimestamp() { + return this.lastUpdatedTimestamp; + } +} diff --git a/src/main/java/frc/robot/subsystems/limelight/LimelightPoseData.java b/src/main/java/frc/robot/subsystems/limelight/LimelightPoseData.java new file mode 100644 index 0000000..29d2cc3 --- /dev/null +++ b/src/main/java/frc/robot/subsystems/limelight/LimelightPoseData.java @@ -0,0 +1,116 @@ +package frc.robot.subsystems.limelight; + +import edu.wpi.first.math.geometry.Pose3d; +import edu.wpi.first.math.geometry.Rotation3d; + +public class LimelightPoseData { + private double x; + private double y; + private double z; + private double roll; + private double pitch; + private double yaw; + private double totalLatency; + + public LimelightPoseData(double[] data) { + if (data.length != 7) { + throw new IllegalArgumentException("Data array must have 7 elements"); + } + + this.x = data[0]; + this.y = data[1]; + this.z = data[2]; + this.roll = data[3]; + this.pitch = data[4]; + this.yaw = data[5]; + this.totalLatency = data[6]; + } + + /** + * Updates the pose data without creating a new instance of limelight pose data + * + * @param data The data to update the pose data with + */ + public void update(double[] data) { + if (data.length != 7) { + throw new IllegalArgumentException("Data array must have 7 elements"); + } + + this.x = data[0]; + this.y = data[1]; + this.z = data[2]; + this.roll = data[3]; + this.pitch = data[4]; + this.yaw = data[5]; + this.totalLatency = data[6]; + } + + /** + * Gets the x position of the limelight + * + * @return The x position of the limelight + */ + public double getX() { + return this.x; + } + + /** + * Gets the y position of the limelight + * + * @return The y position of the limelight + */ + public double getY() { + return this.y; + } + + /** + * Gets the z position of the limelight + * + * @return The z position of the limelight + */ + public double getZ() { + return this.z; + } + + /** + * Gets a 3d rotation position of the limelight + * + * @return The 3d rotation position of the limelight + */ + public Rotation3d getRotation() { + return new Rotation3d( + Math.toRadians(this.roll), Math.toRadians(this.pitch), Math.toRadians(this.yaw)); + } + + /** + * Gets the raw latency of the limelight straight from the network table + * + * @return The raw latency of the limelight straight from the network table + */ + public double getRawTotalLatency() { + return this.totalLatency; + } + + /** + * Gets the total latency of the limelight in seconds + * + * @return The total latency of the limelight in seconds + */ + public double getTotalLatency() { + return this.totalLatency / 1000; + } + + /** + * Converts the limelight pose data to a 3d pose + * + * @return A 3d pose of the limelight pose data + */ + public Pose3d toPose3d() { + return new Pose3d( + this.x, + this.y, + this.z, + new Rotation3d( + Math.toRadians(this.roll), Math.toRadians(this.pitch), Math.toRadians(this.yaw))); + } +} diff --git a/src/main/java/frc/robot/subsystems/limelight/LimelightSubsystem.java b/src/main/java/frc/robot/subsystems/limelight/LimelightSubsystem.java new file mode 100644 index 0000000..82a2696 --- /dev/null +++ b/src/main/java/frc/robot/subsystems/limelight/LimelightSubsystem.java @@ -0,0 +1,101 @@ +package frc.robot.subsystems.limelight; + +import java.util.ArrayList; +import java.util.List; + +import edu.wpi.first.math.geometry.Pose2d; +import edu.wpi.first.math.geometry.Rotation2d; +import edu.wpi.first.wpilibj.DriverStation; +import edu.wpi.first.wpilibj2.command.SubsystemBase; +import frc.robot.subsystems.limelight.LimelightIO.LimelightIOData; + +public class LimelightSubsystem extends SubsystemBase { + private final Limelight[] limelights; + private final LimelightIOData[] limelightData; + + private final List results = new ArrayList<>(); + + private int acceptableTagID; + private boolean useSingleTag = false; + + /** + * Initializes cameras and input loggers + * + * @param cameras Array of cameras being used + */ + public LimelightSubsystem(Limelight[] limelights) { + this.limelights = limelights; + this.limelightData = new LimelightIOData[this.limelights.length]; + + for (int i = 0; i < this.limelights.length; i++) { + this.limelightData[i] = new LimelightIOData(); + } + } + + @Override + public void periodic() { + // Clear results from last periodic + this.results.clear(); + + for (int i = 0; i < this.limelightData.length; i++) { + // update and process new inputs + this.limelights[i].updateData(limelightData[i]); + + if (this.limelightData[i].lockedOnTarget + && this.limelightData[i].isNewPose + && !DriverStation.isAutonomous() + && this.limelightData[i].maxDistance < LimelightConstants.LOWEST_DISTANCE) { + if (this.useSingleTag) { + if (this.limelightData[i].singleIDUsed == this.acceptableTagID) { + this.processLimelight(i); + } + } else { + this.processLimelight(i); + } + } + } + } + + /** + * Processes the limelight data and adds the pose to the list of poses + * + * @param limelightNumber The numerical ID of the limelight + */ + public void processLimelight(int limelightNumber) { + // Create a new pose based off the new limelight data + Pose2d currentPose = + new Pose2d( + this.limelightData[limelightNumber].limelightX, + this.limelightData[limelightNumber].limelightY, + new Rotation2d(this.limelightData[limelightNumber].limelightRotation)); + + // Add the new pose to the list of poses + this.results.add( + new LimelightPoseAndTimestamp( + currentPose, limelightData[limelightNumber].limelightLastUpdated)); + } + + /** Returns the last recorded pose */ + public List getLimelightOdometry() { + return this.results; + } + + public void setUseSingleTag(boolean useSingleTag) { + this.setUseSingleTag(useSingleTag, 0); + } + + public void setUseSingleTag(boolean useSingleTag, int acceptableTagID) { + this.useSingleTag = useSingleTag; + this.acceptableTagID = acceptableTagID; + } + + public void setReferencePose(Pose2d pose) { + for (Limelight limelight : this.limelights) { + limelight.setRefrencePose(pose); + } + } + + public double getMinDistance(int camera) { + return this.limelightData[camera].minDistance; + } +} diff --git a/src/main/java/frc/robot/subsystems/limelight/enums/LimelightLEDMode.java b/src/main/java/frc/robot/subsystems/limelight/enums/LimelightLEDMode.java new file mode 100644 index 0000000..db542a1 --- /dev/null +++ b/src/main/java/frc/robot/subsystems/limelight/enums/LimelightLEDMode.java @@ -0,0 +1,32 @@ +package frc.robot.subsystems.limelight.enums; + +import java.util.HashMap; +import java.util.Map; + +public enum LimelightLEDMode { + PIPELINE_DEFAULT(0), + OFF(1), + BLINK(2), + ON(3); + + private int networkTableValue; + private static Map ledModeMap = new HashMap<>(); + + private LimelightLEDMode(int networkTableValue) { + this.networkTableValue = networkTableValue; + } + + static { + for (LimelightLEDMode limelightLEDMode : LimelightLEDMode.values()) { + ledModeMap.put(limelightLEDMode.networkTableValue, limelightLEDMode); + } + } + + public static LimelightLEDMode valueOf(int limelightNetworkTableValue) { + return ledModeMap.get(limelightNetworkTableValue); + } + + public int getNetworkTableValue() { + return this.networkTableValue; + } +} diff --git a/src/main/java/frc/robot/subsystems/limelight/enums/LimelightStream.java b/src/main/java/frc/robot/subsystems/limelight/enums/LimelightStream.java new file mode 100644 index 0000000..e21a936 --- /dev/null +++ b/src/main/java/frc/robot/subsystems/limelight/enums/LimelightStream.java @@ -0,0 +1,33 @@ +package frc.robot.subsystems.limelight.enums; + +import java.util.HashMap; +import java.util.Map; + +public enum LimelightStream { + STANDARD(0), // side-by-side streams if a webcam is attached to Limelight + PIP_MAIN(1), // secondary camera stream in the lower-right corner of the + // primary camera stream + PIP_SECONDARY(2); // primary camera stream in the lower-right corner of the + // secondary camera stream + + private int networkTableValue; + private static Map streamMap = new HashMap<>(); + + private LimelightStream(int networkTableValue) { + this.networkTableValue = networkTableValue; + } + + static { + for (LimelightStream limelightStream : LimelightStream.values()) { + streamMap.put(limelightStream.networkTableValue, limelightStream); + } + } + + public static LimelightStream valueOf(int limelightNetworkTableValue) { + return streamMap.get(limelightNetworkTableValue); + } + + public int getNetworkTableValue() { + return this.networkTableValue; + } +} diff --git a/vendordeps/PathplannerLib.json b/vendordeps/PathplannerLib.json index aeaa3ba..a92663b 100644 --- a/vendordeps/PathplannerLib.json +++ b/vendordeps/PathplannerLib.json @@ -1,7 +1,7 @@ { "fileName": "PathplannerLib.json", "name": "PathplannerLib", - "version": "2024.0.0-beta-1", + "version": "2024.0.0-beta-6.2", "uuid": "1b42324f-17c6-4875-8e77-1c312bc8c786", "frcYear": "2024", "mavenUrls": [ @@ -12,7 +12,7 @@ { "groupId": "com.pathplanner.lib", "artifactId": "PathplannerLib-java", - "version": "2024.0.0-beta-1" + "version": "2024.0.0-beta-6.2" } ], "jniDependencies": [], @@ -20,7 +20,7 @@ { "groupId": "com.pathplanner.lib", "artifactId": "PathplannerLib-cpp", - "version": "2024.0.0-beta-1", + "version": "2024.0.0-beta-6.2", "libName": "PathplannerLib", "headerClassifier": "headers", "sharedLibrary": false, diff --git a/vendordeps/Phoenix6.json b/vendordeps/Phoenix6.json index 1f9f441..622e9c7 100644 --- a/vendordeps/Phoenix6.json +++ b/vendordeps/Phoenix6.json @@ -1,7 +1,7 @@ { "fileName": "Phoenix6.json", "name": "CTRE-Phoenix (v6)", - "version": "24.0.0-beta-1", + "version": "24.0.0-beta-7", "frcYear": 2024, "uuid": "e995de00-2c64-4df5-8831-c1441420ff19", "mavenUrls": [ @@ -19,14 +19,14 @@ { "groupId": "com.ctre.phoenix6", "artifactId": "wpiapi-java", - "version": "24.0.0-beta-1" + "version": "24.0.0-beta-7" } ], "jniDependencies": [ { "groupId": "com.ctre.phoenix6", "artifactId": "tools", - "version": "24.0.0-beta-1", + "version": "24.0.0-beta-7", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ @@ -39,7 +39,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "tools-sim", - "version": "24.0.0-beta-1", + "version": "24.0.0-beta-7", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ @@ -52,7 +52,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simTalonSRX", - "version": "24.0.0-beta-1", + "version": "24.0.0-beta-7", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ @@ -65,7 +65,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simTalonFX", - "version": "24.0.0-beta-1", + "version": "24.0.0-beta-7", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ @@ -78,7 +78,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simVictorSPX", - "version": "24.0.0-beta-1", + "version": "24.0.0-beta-7", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ @@ -91,7 +91,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simPigeonIMU", - "version": "24.0.0-beta-1", + "version": "24.0.0-beta-7", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ @@ -104,7 +104,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simCANCoder", - "version": "24.0.0-beta-1", + "version": "24.0.0-beta-7", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ @@ -117,7 +117,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simProTalonFX", - "version": "24.0.0-beta-1", + "version": "24.0.0-beta-7", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ @@ -130,7 +130,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simProCANcoder", - "version": "24.0.0-beta-1", + "version": "24.0.0-beta-7", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ @@ -143,7 +143,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simProPigeon2", - "version": "24.0.0-beta-1", + "version": "24.0.0-beta-7", "isJar": false, "skipInvalidPlatforms": true, "validPlatforms": [ @@ -158,7 +158,7 @@ { "groupId": "com.ctre.phoenix6", "artifactId": "wpiapi-cpp", - "version": "24.0.0-beta-1", + "version": "24.0.0-beta-7", "libName": "CTRE_Phoenix6_WPI", "headerClassifier": "headers", "sharedLibrary": true, @@ -173,7 +173,7 @@ { "groupId": "com.ctre.phoenix6", "artifactId": "tools", - "version": "24.0.0-beta-1", + "version": "24.0.0-beta-7", "libName": "CTRE_PhoenixTools", "headerClassifier": "headers", "sharedLibrary": true, @@ -188,7 +188,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "wpiapi-cpp-sim", - "version": "24.0.0-beta-1", + "version": "24.0.0-beta-7", "libName": "CTRE_Phoenix6_WPISim", "headerClassifier": "headers", "sharedLibrary": true, @@ -203,7 +203,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "tools-sim", - "version": "24.0.0-beta-1", + "version": "24.0.0-beta-7", "libName": "CTRE_PhoenixTools_Sim", "headerClassifier": "headers", "sharedLibrary": true, @@ -218,7 +218,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simTalonSRX", - "version": "24.0.0-beta-1", + "version": "24.0.0-beta-7", "libName": "CTRE_SimTalonSRX", "headerClassifier": "headers", "sharedLibrary": true, @@ -233,7 +233,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simTalonFX", - "version": "24.0.0-beta-1", + "version": "24.0.0-beta-7", "libName": "CTRE_SimTalonFX", "headerClassifier": "headers", "sharedLibrary": true, @@ -248,7 +248,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simVictorSPX", - "version": "24.0.0-beta-1", + "version": "24.0.0-beta-7", "libName": "CTRE_SimVictorSPX", "headerClassifier": "headers", "sharedLibrary": true, @@ -263,7 +263,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simPigeonIMU", - "version": "24.0.0-beta-1", + "version": "24.0.0-beta-7", "libName": "CTRE_SimPigeonIMU", "headerClassifier": "headers", "sharedLibrary": true, @@ -278,7 +278,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simCANCoder", - "version": "24.0.0-beta-1", + "version": "24.0.0-beta-7", "libName": "CTRE_SimCANCoder", "headerClassifier": "headers", "sharedLibrary": true, @@ -293,7 +293,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simProTalonFX", - "version": "24.0.0-beta-1", + "version": "24.0.0-beta-7", "libName": "CTRE_SimProTalonFX", "headerClassifier": "headers", "sharedLibrary": true, @@ -308,7 +308,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simProCANcoder", - "version": "24.0.0-beta-1", + "version": "24.0.0-beta-7", "libName": "CTRE_SimProCANcoder", "headerClassifier": "headers", "sharedLibrary": true, @@ -323,7 +323,7 @@ { "groupId": "com.ctre.phoenix6.sim", "artifactId": "simProPigeon2", - "version": "24.0.0-beta-1", + "version": "24.0.0-beta-7", "libName": "CTRE_SimProPigeon2", "headerClassifier": "headers", "sharedLibrary": true, diff --git a/vendordeps/WPILibNewCommands.json b/vendordeps/WPILibNewCommands.json index bd535bf..67bf389 100644 --- a/vendordeps/WPILibNewCommands.json +++ b/vendordeps/WPILibNewCommands.json @@ -3,6 +3,7 @@ "name": "WPILib-New-Commands", "version": "1.0.0", "uuid": "111e20f7-815e-48f8-9dd6-e675ce75b266", + "frcYear": "2024", "mavenUrls": [], "jsonUrl": "", "javaDependencies": [