From 6448843be038a48f07aa19a588f6f40006ad45fb Mon Sep 17 00:00:00 2001 From: suryatho Date: Sat, 13 Jan 2024 17:50:53 -0500 Subject: [PATCH] Added FieldConstants.java Added capability for choreo trajectory following --- .../pathplanner/autos/Example Auto.auto | 2 +- .../pathplanner/paths/Example Path.path | 65 -------- src/main/java/frc/robot/FieldConstants.java | 51 +++++++ src/main/java/frc/robot/RobotContainer.java | 15 +- .../commands/FeedForwardCharacterization.java | 2 +- .../frc/robot/subsystems/drive/Drive.java | 36 +---- .../subsystems/drive/DriveConstants.java | 3 + .../kitbotshooter/KitbotFeederIO.java | 4 +- .../kitbotshooter/KitbotFeederIOSim.java | 2 +- .../kitbotshooter/KitbotFeederIOSparkMax.java | 2 +- .../kitbotshooter/KitbotFlywheelIO.java | 4 +- .../kitbotshooter/KitbotFlywheelIOSim.java | 2 +- .../KitbotFlywheelIOSparkMax.java | 2 +- .../kitbotshooter/KitbotShooter.java | 4 +- .../java/frc/robot/util/AllianceFlipUtil.java | 66 ++++++++ src/main/java/frc/robot/util/GeomUtil.java | 143 ++++++++++++++++++ .../trajectory/ChoreoTrajectoryReader.java | 124 +++++++++++++++ .../trajectory/HolonomicDriveController.java | 52 +++++++ 18 files changed, 460 insertions(+), 119 deletions(-) delete mode 100644 src/main/deploy/pathplanner/paths/Example Path.path create mode 100644 src/main/java/frc/robot/FieldConstants.java create mode 100644 src/main/java/frc/robot/subsystems/drive/DriveConstants.java create mode 100644 src/main/java/frc/robot/util/AllianceFlipUtil.java create mode 100644 src/main/java/frc/robot/util/GeomUtil.java create mode 100644 src/main/java/frc/robot/util/trajectory/ChoreoTrajectoryReader.java create mode 100644 src/main/java/frc/robot/util/trajectory/HolonomicDriveController.java diff --git a/src/main/deploy/pathplanner/autos/Example Auto.auto b/src/main/deploy/pathplanner/autos/Example Auto.auto index 77a8433f..0de36e00 100644 --- a/src/main/deploy/pathplanner/autos/Example Auto.auto +++ b/src/main/deploy/pathplanner/autos/Example Auto.auto @@ -20,7 +20,7 @@ { "type": "path", "data": { - "pathName": "Example Path" + "pathName": "ShootAndTaxi1" } } ] diff --git a/src/main/deploy/pathplanner/paths/Example Path.path b/src/main/deploy/pathplanner/paths/Example Path.path deleted file mode 100644 index 8f3609bb..00000000 --- a/src/main/deploy/pathplanner/paths/Example Path.path +++ /dev/null @@ -1,65 +0,0 @@ -{ - "version": 1.0, - "waypoints": [ - { - "anchor": { - "x": 2.0, - "y": 7.0 - }, - "prevControl": null, - "nextControl": { - "x": 3.013282910175676, - "y": 6.5274984191074985 - }, - "isLocked": false, - "linkedName": null - }, - { - "anchor": { - "x": 5.166769560390973, - "y": 5.0964860911203305 - }, - "prevControl": { - "x": 4.166769560390973, - "y": 6.0964860911203305 - }, - "nextControl": { - "x": 6.166769560390973, - "y": 4.0964860911203305 - }, - "isLocked": false, - "linkedName": null - }, - { - "anchor": { - "x": 7.0, - "y": 1.0 - }, - "prevControl": { - "x": 6.75, - "y": 2.5 - }, - "nextControl": null, - "isLocked": false, - "linkedName": null - } - ], - "rotationTargets": [], - "constraintZones": [], - "eventMarkers": [], - "globalConstraints": { - "maxVelocity": 3.0, - "maxAcceleration": 3.0, - "maxAngularVelocity": 540.0, - "maxAngularAcceleration": 720.0 - }, - "goalEndState": { - "velocity": 0, - "rotation": 0, - "rotateFast": false - }, - "reversed": false, - "folder": null, - "previewStartingState": null, - "useDefaultConstraints": false -} \ No newline at end of file diff --git a/src/main/java/frc/robot/FieldConstants.java b/src/main/java/frc/robot/FieldConstants.java new file mode 100644 index 00000000..631ec0e5 --- /dev/null +++ b/src/main/java/frc/robot/FieldConstants.java @@ -0,0 +1,51 @@ +package frc.robot; + +import edu.wpi.first.math.geometry.Translation2d; +import edu.wpi.first.math.util.Units; + +/** + * Contains various field dimensions and useful reference points. Dimensions are in meters, and sets + * of corners start in the lower left moving clockwise. All units in Meters
+ *
+ * + *

All translations and poses are stored with the origin at the rightmost point on the BLUE + * ALLIANCE wall.
+ *
+ * Length refers to the x direction (as described by wpilib)
+ * Width refers to the y direction (as described by wpilib) + */ +public class FieldConstants { + public static double fieldLength = Units.inchesToMeters(653.22); + public static double fieldWidth = Units.inchesToMeters(323.28); + public static double wingX = Units.inchesToMeters(229.201); + + public static double podiumX = Units.inchesToMeters(121); + public static double startingLineX = Units.inchesToMeters(74.111); + + public static final class StagingLocations { + public static double centerlineX = Units.inchesToMeters(fieldLength / 2); + // need to update + public static double centerlineFirstY = Units.inchesToMeters(-1); + public static double centerlineSeparationY = Units.inchesToMeters(66); + public static double spikeX = Units.inchesToMeters(114); + // need + public static double spikeFirstY = Units.inchesToMeters(-1); + public static double spikeSeparationY = Units.inchesToMeters(57); + + public static Translation2d[] centerlineTranslations = new Translation2d[5]; + public static Translation2d[] spikeTranslations = new Translation2d[3]; + + static { + for (int i = 0; i < centerlineTranslations.length; i++) { + centerlineTranslations[i] = + new Translation2d(centerlineX, centerlineFirstY + (i * centerlineSeparationY)); + } + } + + static { + for (int i = 0; i < spikeTranslations.length; i++) { + spikeTranslations[i] = new Translation2d(spikeX, spikeFirstY + (i * spikeSeparationY)); + } + } + } +} diff --git a/src/main/java/frc/robot/RobotContainer.java b/src/main/java/frc/robot/RobotContainer.java index d72b2e1e..8c9fc807 100644 --- a/src/main/java/frc/robot/RobotContainer.java +++ b/src/main/java/frc/robot/RobotContainer.java @@ -13,7 +13,6 @@ package frc.robot; -import com.pathplanner.lib.auto.AutoBuilder; import edu.wpi.first.math.geometry.Pose2d; import edu.wpi.first.math.geometry.Rotation2d; import edu.wpi.first.wpilibj.GenericHID; @@ -98,14 +97,7 @@ public RobotContainer() { break; } - // Set up auto routines - // NamedCommands.registerCommand( - // "Run Flywheel", - // Commands.startEnd( - // () -> flywheel.runVelocity(flywheelSpeedInput.get()), flywheel::stop, - // flywheel) - // .withTimeout(5.0)); - autoChooser = new LoggedDashboardChooser<>("Auto Choices", AutoBuilder.buildAutoChooser()); + autoChooser = new LoggedDashboardChooser<>("Auto Choices"); // Set up feedforward characterization autoChooser.addOption( @@ -148,6 +140,11 @@ private void configureButtonBindings() { new Pose2d(drive.getPose().getTranslation(), new Rotation2d())), drive) .ignoringDisable(true)); + controller + .a() + .whileTrue( + Commands.run(() -> shooter.runFlywheelVelocity(flywheelSpeedInput.get()), shooter)) + .whileFalse(Commands.run(() -> shooter.runFlywheelVelocity(0.0), shooter)); } /** diff --git a/src/main/java/frc/robot/commands/FeedForwardCharacterization.java b/src/main/java/frc/robot/commands/FeedForwardCharacterization.java index d7ac7a7a..391a3d31 100644 --- a/src/main/java/frc/robot/commands/FeedForwardCharacterization.java +++ b/src/main/java/frc/robot/commands/FeedForwardCharacterization.java @@ -24,7 +24,7 @@ public class FeedForwardCharacterization extends Command { private static final double START_DELAY_SECS = 2.0; - private static final double RAMP_VOLTS_PER_SEC = 0.1; + private static final double RAMP_VOLTS_PER_SEC = 0.4; private FeedForwardCharacterizationData data; private final Consumer voltageConsumer; diff --git a/src/main/java/frc/robot/subsystems/drive/Drive.java b/src/main/java/frc/robot/subsystems/drive/Drive.java index f36eb6ef..ac2f489c 100644 --- a/src/main/java/frc/robot/subsystems/drive/Drive.java +++ b/src/main/java/frc/robot/subsystems/drive/Drive.java @@ -13,11 +13,6 @@ package frc.robot.subsystems.drive; -import com.pathplanner.lib.auto.AutoBuilder; -import com.pathplanner.lib.pathfinding.Pathfinding; -import com.pathplanner.lib.util.HolonomicPathFollowerConfig; -import com.pathplanner.lib.util.PathPlannerLogging; -import com.pathplanner.lib.util.ReplanningConfig; import edu.wpi.first.math.geometry.Pose2d; import edu.wpi.first.math.geometry.Rotation2d; import edu.wpi.first.math.geometry.Translation2d; @@ -28,18 +23,16 @@ import edu.wpi.first.math.kinematics.SwerveModuleState; import edu.wpi.first.math.util.Units; import edu.wpi.first.wpilibj.DriverStation; -import edu.wpi.first.wpilibj.DriverStation.Alliance; import edu.wpi.first.wpilibj2.command.SubsystemBase; -import frc.robot.util.LocalADStarAK; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import org.littletonrobotics.junction.AutoLogOutput; import org.littletonrobotics.junction.Logger; public class Drive extends SubsystemBase { - private static final double MAX_LINEAR_SPEED = Units.feetToMeters(14.5); - private static final double TRACK_WIDTH_X = Units.inchesToMeters(25.0); - private static final double TRACK_WIDTH_Y = Units.inchesToMeters(25.0); + private static final double MAX_LINEAR_SPEED = Units.inchesToMeters(140.0); + private static final double TRACK_WIDTH_X = Units.inchesToMeters(26.0); + private static final double TRACK_WIDTH_Y = Units.inchesToMeters(26.0); private static final double DRIVE_BASE_RADIUS = Math.hypot(TRACK_WIDTH_X / 2.0, TRACK_WIDTH_Y / 2.0); private static final double MAX_ANGULAR_SPEED = MAX_LINEAR_SPEED / DRIVE_BASE_RADIUS; @@ -64,29 +57,6 @@ public Drive( modules[1] = new Module(frModuleIO, 1); modules[2] = new Module(blModuleIO, 2); modules[3] = new Module(brModuleIO, 3); - - // Configure AutoBuilder for PathPlanner - AutoBuilder.configureHolonomic( - this::getPose, - this::setPose, - () -> kinematics.toChassisSpeeds(getModuleStates()), - this::runVelocity, - new HolonomicPathFollowerConfig( - MAX_LINEAR_SPEED, DRIVE_BASE_RADIUS, new ReplanningConfig()), - () -> - DriverStation.getAlliance().isPresent() - && DriverStation.getAlliance().get() == Alliance.Red, - this); - Pathfinding.setPathfinder(new LocalADStarAK()); - PathPlannerLogging.setLogActivePathCallback( - (activePath) -> { - Logger.recordOutput( - "Odometry/Trajectory", activePath.toArray(new Pose2d[activePath.size()])); - }); - PathPlannerLogging.setLogTargetPoseCallback( - (targetPose) -> { - Logger.recordOutput("Odometry/TrajectorySetpoint", targetPose); - }); } public void periodic() { 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 00000000..70e571ef --- /dev/null +++ b/src/main/java/frc/robot/subsystems/drive/DriveConstants.java @@ -0,0 +1,3 @@ +package frc.robot.subsystems.drive; + +public class DriveConstants {} diff --git a/src/main/java/frc/robot/subsystems/kitbotshooter/KitbotFeederIO.java b/src/main/java/frc/robot/subsystems/kitbotshooter/KitbotFeederIO.java index 9f672e13..bb261025 100644 --- a/src/main/java/frc/robot/subsystems/kitbotshooter/KitbotFeederIO.java +++ b/src/main/java/frc/robot/subsystems/kitbotshooter/KitbotFeederIO.java @@ -4,14 +4,14 @@ public interface KitbotFeederIO { @AutoLog - class FeederIOInputs { + class KitbotFeederIOInputs { public double feederPositionRads = 0.0; public double feederVelocityRadPerSec = 0.0; public double feederAppliedVolts = 0.0; public double feederCurrentAmps = 0.0; } - default void updateInputs(FeederIOInputs inputs) {} + default void updateInputs(KitbotFeederIOInputs inputs) {} ; /** run at voltage */ diff --git a/src/main/java/frc/robot/subsystems/kitbotshooter/KitbotFeederIOSim.java b/src/main/java/frc/robot/subsystems/kitbotshooter/KitbotFeederIOSim.java index 27daadf5..d2448920 100644 --- a/src/main/java/frc/robot/subsystems/kitbotshooter/KitbotFeederIOSim.java +++ b/src/main/java/frc/robot/subsystems/kitbotshooter/KitbotFeederIOSim.java @@ -12,7 +12,7 @@ public class KitbotFeederIOSim implements KitbotFeederIO { private double inputVolts = 0.0; @Override - public void updateInputs(FeederIOInputs inputs) { + public void updateInputs(KitbotFeederIOInputs inputs) { positionRads += sim.getAngularVelocityRadPerSec() * (Constants.loopPeriodMs / 1000.0); inputs.feederPositionRads = positionRads; inputs.feederVelocityRadPerSec = sim.getAngularVelocityRadPerSec(); diff --git a/src/main/java/frc/robot/subsystems/kitbotshooter/KitbotFeederIOSparkMax.java b/src/main/java/frc/robot/subsystems/kitbotshooter/KitbotFeederIOSparkMax.java index f284ef2e..b04304ab 100644 --- a/src/main/java/frc/robot/subsystems/kitbotshooter/KitbotFeederIOSparkMax.java +++ b/src/main/java/frc/robot/subsystems/kitbotshooter/KitbotFeederIOSparkMax.java @@ -30,7 +30,7 @@ public KitbotFeederIOSparkMax() { } @Override - public void updateInputs(FeederIOInputs inputs) { + public void updateInputs(KitbotFeederIOInputs inputs) { inputs.feederPositionRads = Units.rotationsToRadians(encoder.getPosition()) / GEARING; inputs.feederVelocityRadPerSec = Units.rotationsToRadians(encoder.getVelocity()) / GEARING; inputs.feederAppliedVolts = motor.getAppliedOutput(); diff --git a/src/main/java/frc/robot/subsystems/kitbotshooter/KitbotFlywheelIO.java b/src/main/java/frc/robot/subsystems/kitbotshooter/KitbotFlywheelIO.java index d35b17d6..4b630ab4 100644 --- a/src/main/java/frc/robot/subsystems/kitbotshooter/KitbotFlywheelIO.java +++ b/src/main/java/frc/robot/subsystems/kitbotshooter/KitbotFlywheelIO.java @@ -4,14 +4,14 @@ public interface KitbotFlywheelIO { @AutoLog - class FlywheelIOInputs { + class KitbotFlywheelIOInputs { public double[] flywheelPositionRads = new double[] {}; public double[] flywheelVelocityRadPerSec = new double[] {}; public double[] flywheelAppliedVolts = new double[] {}; public double[] flywheelCurrentAmps = new double[] {}; } - default void updateInputs(FlywheelIOInputs inputs) {} + default void updateInputs(KitbotFlywheelIOInputs inputs) {} ; /** run to velocity */ diff --git a/src/main/java/frc/robot/subsystems/kitbotshooter/KitbotFlywheelIOSim.java b/src/main/java/frc/robot/subsystems/kitbotshooter/KitbotFlywheelIOSim.java index 6aa87c73..eeb4213a 100644 --- a/src/main/java/frc/robot/subsystems/kitbotshooter/KitbotFlywheelIOSim.java +++ b/src/main/java/frc/robot/subsystems/kitbotshooter/KitbotFlywheelIOSim.java @@ -19,7 +19,7 @@ public class KitbotFlywheelIOSim implements KitbotFlywheelIO { private double inputVolts = 0.0; @Override - public void updateInputs(FlywheelIOInputs inputs) { + public void updateInputs(KitbotFlywheelIOInputs inputs) { positionRads += sim.getAngularVelocityRadPerSec() * (Constants.loopPeriodMs / 1000.0); inputs.flywheelPositionRads = new double[] {positionRads, positionRads}; inputs.flywheelVelocityRadPerSec = new double[2]; diff --git a/src/main/java/frc/robot/subsystems/kitbotshooter/KitbotFlywheelIOSparkMax.java b/src/main/java/frc/robot/subsystems/kitbotshooter/KitbotFlywheelIOSparkMax.java index bf771915..45c76da1 100644 --- a/src/main/java/frc/robot/subsystems/kitbotshooter/KitbotFlywheelIOSparkMax.java +++ b/src/main/java/frc/robot/subsystems/kitbotshooter/KitbotFlywheelIOSparkMax.java @@ -54,7 +54,7 @@ public KitbotFlywheelIOSparkMax() { } @Override - public void updateInputs(FlywheelIOInputs inputs) { + public void updateInputs(KitbotFlywheelIOInputs inputs) { inputs.flywheelPositionRads = getPosition(); inputs.flywheelVelocityRadPerSec = getVelocity(); inputs.flywheelAppliedVolts = diff --git a/src/main/java/frc/robot/subsystems/kitbotshooter/KitbotShooter.java b/src/main/java/frc/robot/subsystems/kitbotshooter/KitbotShooter.java index bd1f03b1..3a7b4bc0 100644 --- a/src/main/java/frc/robot/subsystems/kitbotshooter/KitbotShooter.java +++ b/src/main/java/frc/robot/subsystems/kitbotshooter/KitbotShooter.java @@ -16,8 +16,8 @@ public class KitbotShooter extends SubsystemBase { private KitbotFlywheelIO kitbotFlywheelIO; private KitbotFeederIO kitbotFeederIO; - private FlywheelIOInputsAutoLogged flywheelInputs = new FlywheelIOInputsAutoLogged(); - private FeederIOInputsAutoLogged feederInputs = new FeederIOInputsAutoLogged(); + private KitbotFlywheelIOInputsAutoLogged flywheelInputs = new KitbotFlywheelIOInputsAutoLogged(); + private KitbotFeederIOInputsAutoLogged feederInputs = new KitbotFeederIOInputsAutoLogged(); private Mode currentMode = null; diff --git a/src/main/java/frc/robot/util/AllianceFlipUtil.java b/src/main/java/frc/robot/util/AllianceFlipUtil.java new file mode 100644 index 00000000..c60bc6f1 --- /dev/null +++ b/src/main/java/frc/robot/util/AllianceFlipUtil.java @@ -0,0 +1,66 @@ +package frc.robot.util; + +import edu.wpi.first.math.geometry.Pose2d; +import edu.wpi.first.math.geometry.Rotation2d; +import edu.wpi.first.math.geometry.Translation2d; +import edu.wpi.first.wpilibj.DriverStation; +import edu.wpi.first.wpilibj.DriverStation.Alliance; +import frc.robot.FieldConstants; +import frc.robot.util.trajectory.HolonomicDriveController; + +/** Utility functions for flipping from the blue to red alliance. */ +public class AllianceFlipUtil { + /** Flips an x coordinate to the correct side of the field based on the current alliance color. */ + public static double apply(double xCoordinate) { + if (shouldFlip()) { + return FieldConstants.fieldLength - xCoordinate; + } else { + return xCoordinate; + } + } + + /** Flips a translation to the correct side of the field based on the current alliance color. */ + public static Translation2d apply(Translation2d translation) { + if (shouldFlip()) { + return new Translation2d(apply(translation.getX()), translation.getY()); + } else { + return translation; + } + } + + /** Flips a rotation based on the current alliance color. */ + public static Rotation2d apply(Rotation2d rotation) { + if (shouldFlip()) { + return new Rotation2d(-rotation.getCos(), rotation.getSin()); + } else { + return rotation; + } + } + + /** Flips a pose to the correct side of the field based on the current alliance color. */ + public static Pose2d apply(Pose2d pose) { + if (shouldFlip()) { + return new Pose2d(apply(pose.getTranslation()), apply(pose.getRotation())); + } else { + return pose; + } + } + + /** + * Flips a trajectory state to the correct side of the field based on the current alliance color. + */ + public static HolonomicDriveController.HolonomicDriveState apply( + HolonomicDriveController.HolonomicDriveState state) { + if (shouldFlip()) { + return new HolonomicDriveController.HolonomicDriveState( + apply(state.pose()), -state.velocityX(), state.velocityY(), -state.angularVelocity()); + } else { + return state; + } + } + + public static boolean shouldFlip() { + return DriverStation.getAlliance().isPresent() + && DriverStation.getAlliance().get() == Alliance.Red; + } +} diff --git a/src/main/java/frc/robot/util/GeomUtil.java b/src/main/java/frc/robot/util/GeomUtil.java new file mode 100644 index 00000000..fa87fa4f --- /dev/null +++ b/src/main/java/frc/robot/util/GeomUtil.java @@ -0,0 +1,143 @@ +package frc.robot.util; +// Copyright (c) 2023 FRC 6328 +// http://github.com/Mechanical-Advantage +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file at +// the root directory of this project. + +import edu.wpi.first.math.geometry.Pose2d; +import edu.wpi.first.math.geometry.Pose3d; +import edu.wpi.first.math.geometry.Rotation2d; +import edu.wpi.first.math.geometry.Transform2d; +import edu.wpi.first.math.geometry.Transform3d; +import edu.wpi.first.math.geometry.Translation2d; +import edu.wpi.first.math.geometry.Translation3d; +import edu.wpi.first.math.geometry.Twist2d; + +/** Geometry utilities for working with translations, rotations, transforms, and poses. */ +public class GeomUtil { + /** + * Creates a pure translating transform + * + * @param translation The translation to create the transform with + * @return The resulting transform + */ + public static Transform2d translationToTransform(Translation2d translation) { + return new Transform2d(translation, new Rotation2d()); + } + + /** + * Creates a pure translating transform + * + * @param x The x componenet of the translation + * @param y The y componenet of the translation + * @return The resulting transform + */ + public static Transform2d translationToTransform(double x, double y) { + return new Transform2d(new Translation2d(x, y), new Rotation2d()); + } + + /** + * Creates a pure rotating transform + * + * @param rotation The rotation to create the transform with + * @return The resulting transform + */ + public static Transform2d rotationToTransform(Rotation2d rotation) { + return new Transform2d(new Translation2d(), rotation); + } + + /** + * Converts a Pose2d to a Transform2d to be used in a kinematic chain + * + * @param pose The pose that will represent the transform + * @return The resulting transform + */ + public static Transform2d poseToTransform(Pose2d pose) { + return new Transform2d(pose.getTranslation(), pose.getRotation()); + } + + /** + * Converts a Transform2d to a Pose2d to be used as a position or as the start of a kinematic + * chain + * + * @param transform The transform that will represent the pose + * @return The resulting pose + */ + public static Pose2d transformToPose(Transform2d transform) { + return new Pose2d(transform.getTranslation(), transform.getRotation()); + } + + /** + * Creates a pure translated pose + * + * @param translation The translation to create the pose with + * @return The resulting pose + */ + public static Pose2d translationToPose(Translation2d translation) { + return new Pose2d(translation, new Rotation2d()); + } + + /** + * Creates a pure rotated pose + * + * @param rotation The rotation to create the pose with + * @return The resulting pose + */ + public static Pose2d rotationToPose(Rotation2d rotation) { + return new Pose2d(new Translation2d(), rotation); + } + + /** + * Multiplies a twist by a scaling factor + * + * @param twist The twist to multiply + * @param factor The scaling factor for the twist components + * @return The new twist + */ + public static Twist2d multiplyTwist(Twist2d twist, double factor) { + return new Twist2d(twist.dx * factor, twist.dy * factor, twist.dtheta * factor); + } + + /** + * Converts a Pose3d to a Transform3d to be used in a kinematic chain + * + * @param pose The pose that will represent the transform + * @return The resulting transform + */ + public static Transform3d pose3dToTransform3d(Pose3d pose) { + return new Transform3d(pose.getTranslation(), pose.getRotation()); + } + + /** + * Converts a Transform3d to a Pose3d to be used as a position or as the start of a kinematic + * chain + * + * @param transform The transform that will represent the pose + * @return The resulting pose + */ + public static Pose3d transform3dToPose3d(Transform3d transform) { + return new Pose3d(transform.getTranslation(), transform.getRotation()); + } + + /** + * Converts a Translation3d to a Translation2d by extracting two dimensions (X and Y). chain + * + * @param transform The original translation + * @return The resulting translation + */ + public static Translation2d translation3dTo2dXY(Translation3d translation) { + return new Translation2d(translation.getX(), translation.getY()); + } + + /** + * Converts a Translation3d to a Translation2d by extracting two dimensions (X and Z). chain + * + * @param transform The original translation + * @return The resulting translation + */ + public static Translation2d translation3dTo2dXZ(Translation3d translation) { + return new Translation2d(translation.getX(), translation.getZ()); + } +} diff --git a/src/main/java/frc/robot/util/trajectory/ChoreoTrajectoryReader.java b/src/main/java/frc/robot/util/trajectory/ChoreoTrajectoryReader.java new file mode 100644 index 00000000..59917753 --- /dev/null +++ b/src/main/java/frc/robot/util/trajectory/ChoreoTrajectoryReader.java @@ -0,0 +1,124 @@ +package frc.robot.util.trajectory; + +import static frc.robot.util.trajectory.HolonomicDriveController.HolonomicDriveState; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import edu.wpi.first.math.MathUtil; +import edu.wpi.first.math.geometry.Pose2d; +import edu.wpi.first.math.geometry.Rotation2d; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public final class ChoreoTrajectoryReader { + private static ObjectMapper objectMapper = new ObjectMapper(); + + /** Load Trajectory file (.traj) */ + public Optional generate(File file) { + String type = file.getName().substring(file.getName().lastIndexOf('.')); + // Not generated from code or choreo + if (!type.equals(".traj") || !type.equals(".json")) return Optional.empty(); + + List choreoStates = new ArrayList<>(); + try { + choreoStates = objectMapper.readValue(file, new TypeReference<>() {}); + } catch (IOException e) { + System.out.println("Failed to read states from file"); + return Optional.empty(); + } + + // Generate trajectory object + return Optional.of(generateTrajectoryImplementation(choreoStates)); + } + + private static Trajectory generateTrajectoryImplementation( + final List states) { + return new Trajectory() { + @Override + public double getTotatTimeSeconds() { + return states.get(states.size() - 1).timestamp; + } + + @Override + public Pose2d[] getTrajectoryPoses() { + return states.stream() + .map(state -> new Pose2d(state.x(), state.y(), new Rotation2d(state.heading()))) + .toArray(Pose2d[]::new); + } + + @Override + public HolonomicDriveState sample(double timeSeconds) { + ChoreoTrajectoryState before = null; + ChoreoTrajectoryState after = null; + + for (ChoreoTrajectoryState state : states) { + if (state.timestamp() == timeSeconds) { + return fromChoreoState(state); + } + + if (state.timestamp() < timeSeconds) { + before = state; + } else { + after = state; + break; + } + } + + if (before == null) { + return fromChoreoState(states.get(0)); + } + + if (after == null) { + return fromChoreoState(states.get(states.size() - 1)); + } + + double s = (timeSeconds - before.timestamp()) / (after.timestamp() - before.timestamp()); + + HolonomicDriveState beforeState = fromChoreoState(before); + HolonomicDriveState afterState = fromChoreoState(after); + + Pose2d interpolatedPose = beforeState.pose().interpolate(afterState.pose(), s); + double interpolatedVelocityX = + MathUtil.interpolate(beforeState.velocityX(), afterState.velocityX(), s); + double interpolatedVelocityY = + MathUtil.interpolate(beforeState.velocityY(), afterState.velocityY(), s); + double interpolatedAngularVelocity = + MathUtil.interpolate(beforeState.angularVelocity(), afterState.angularVelocity(), s); + + return new HolonomicDriveState( + interpolatedPose, + interpolatedVelocityX, + interpolatedVelocityY, + interpolatedAngularVelocity); + } + }; + } + + private static HolonomicDriveState fromChoreoState(ChoreoTrajectoryState state) { + return new HolonomicDriveState( + new Pose2d(state.x(), state.y(), new Rotation2d(state.heading())), + state.velocityX(), + state.velocityY(), + state.angularVelocity()); + } + + interface Trajectory { + double getTotatTimeSeconds(); + + Pose2d[] getTrajectoryPoses(); + + HolonomicDriveState sample(double timeSeconds); + } + + record ChoreoTrajectoryState( + double timestamp, + double x, + double y, + double heading, + double velocityX, + double velocityY, + double angularVelocity) {} +} diff --git a/src/main/java/frc/robot/util/trajectory/HolonomicDriveController.java b/src/main/java/frc/robot/util/trajectory/HolonomicDriveController.java new file mode 100644 index 00000000..9c1ca8e0 --- /dev/null +++ b/src/main/java/frc/robot/util/trajectory/HolonomicDriveController.java @@ -0,0 +1,52 @@ +package frc.robot.util.trajectory; + +import edu.wpi.first.math.controller.PIDController; +import edu.wpi.first.math.geometry.Pose2d; +import edu.wpi.first.math.geometry.Rotation2d; +import edu.wpi.first.math.kinematics.ChassisSpeeds; + +public class HolonomicDriveController { + private PIDController linearController, thetaController; + + private Pose2d m_poseError; + private Rotation2d m_rotationError; + + public HolonomicDriveController(PIDController linearController, PIDController thetaController) { + this.linearController = linearController; + this.thetaController = thetaController; + + this.thetaController.enableContinuousInput(-Math.PI, Math.PI); + } + + public ChassisSpeeds calculate(Pose2d currentPose, HolonomicDriveState state) { + Pose2d poseRef = state.pose(); + + m_poseError = poseRef.relativeTo(currentPose); + m_rotationError = poseRef.getRotation().minus(currentPose.getRotation()); + + // Calculate feedback velocities (based on position error). + double xFeedback = linearController.calculate(currentPose.getX(), poseRef.getX()); + double yFeedback = linearController.calculate(currentPose.getY(), poseRef.getY()); + double thetaFeedback = + thetaController.calculate( + currentPose.getRotation().getRadians(), poseRef.getRotation().getRadians()); + + // Return next output. + return ChassisSpeeds.fromFieldRelativeSpeeds( + state.velocityX() + xFeedback, + state.velocityY() + yFeedback, + state.angularVelocity() + thetaFeedback, + currentPose.getRotation()); + } + + public Pose2d getPoseError() { + return m_poseError; + } + + public Rotation2d getRotationError() { + return m_rotationError; + } + + public record HolonomicDriveState( + Pose2d pose, double velocityX, double velocityY, double angularVelocity) {} +}