2024-12-17 18:49:55 +00:00
package swervelib ;
import edu.wpi.first.math.MathUtil ;
2025-03-19 03:45:47 +00:00
import edu.wpi.first.math.Nat ;
import edu.wpi.first.math.Vector ;
2025-02-22 06:15:56 +00:00
import edu.wpi.first.math.controller.ProfiledPIDController ;
2024-12-17 18:49:55 +00:00
import edu.wpi.first.math.geometry.Pose2d ;
import edu.wpi.first.math.geometry.Rotation2d ;
2025-03-19 03:45:47 +00:00
import edu.wpi.first.math.geometry.Transform2d ;
2024-12-17 18:49:55 +00:00
import edu.wpi.first.math.geometry.Translation2d ;
import edu.wpi.first.math.kinematics.ChassisSpeeds ;
2025-03-19 03:45:47 +00:00
import edu.wpi.first.math.numbers.N2 ;
import edu.wpi.first.math.trajectory.TrapezoidProfile.State ;
2024-12-17 18:49:55 +00:00
import edu.wpi.first.wpilibj.DriverStation ;
2025-01-06 15:44:15 +00:00
import edu.wpi.first.wpilibj.DriverStation.Alliance ;
import edu.wpi.first.wpilibj.XboxController ;
2024-12-17 18:49:55 +00:00
import java.util.Optional ;
import java.util.function.BooleanSupplier ;
import java.util.function.DoubleSupplier ;
import java.util.function.Supplier ;
import swervelib.math.SwerveMath ;
/ * *
2025-01-06 15:44:15 +00:00
* Helper class to easily transform Controller inputs into workable Chassis speeds . Intended to easily create an
* interface that generates { @link ChassisSpeeds } from { @link XboxController }
2024-12-17 18:49:55 +00:00
* < p >
2025-01-06 15:44:15 +00:00
* < br / > Inspired by SciBorgs FRC 1155 . < br / > Example :
* < pre >
* { @code
* XboxController driverXbox = new XboxController ( 0 ) ;
*
* SwerveInputStream driveAngularVelocity = SwerveInputStream . of ( drivebase . getSwerveDrive ( ) ,
* ( ) - > driverXbox . getLeftY ( ) * - 1 ,
* ( ) - > driverXbox . getLeftX ( ) * - 1 ) // Axis which give the desired translational angle and speed.
* . withControllerRotationAxis ( driverXbox : : getRightX ) // Axis which give the desired angular velocity.
* . deadband ( 0 . 01 ) // Controller deadband
* . scaleTranslation ( 0 . 8 ) // Scaled controller translation axis
* . allianceRelativeControl ( true ) ; // Alliance relative controls.
*
* SwerveInputStream driveDirectAngle = driveAngularVelocity . copy ( ) // Copy the stream so further changes do not affect driveAngularVelocity
* . withControllerHeadingAxis ( driverXbox : : getRightX ,
* driverXbox : : getRightY ) // Axis which give the desired heading angle using trigonometry.
* . headingWhile ( true ) ; // Enable heading based control.
* }
* < / pre >
2024-12-17 18:49:55 +00:00
* /
public class SwerveInputStream implements Supplier < ChassisSpeeds >
{
/ * *
* Translation suppliers .
* /
2025-02-22 06:15:56 +00:00
private final DoubleSupplier controllerTranslationX ;
2024-12-17 18:49:55 +00:00
/ * *
* Translational supplier .
* /
2025-02-22 06:15:56 +00:00
private final DoubleSupplier controllerTranslationY ;
2024-12-17 18:49:55 +00:00
/ * *
* { @link SwerveDrive } object for transformations .
* /
2025-02-22 06:15:56 +00:00
private final SwerveDrive swerveDrive ;
2024-12-17 18:49:55 +00:00
/ * *
* Rotation supplier as angular velocity .
* /
2025-03-19 03:45:47 +00:00
private Optional < DoubleSupplier > controllerOmega = Optional . empty ( ) ;
2024-12-17 18:49:55 +00:00
/ * *
* Controller supplier as heading .
* /
2025-03-19 03:45:47 +00:00
private Optional < DoubleSupplier > controllerHeadingX = Optional . empty ( ) ;
2024-12-17 18:49:55 +00:00
/ * *
* Controller supplier as heading .
* /
2025-03-19 03:45:47 +00:00
private Optional < DoubleSupplier > controllerHeadingY = Optional . empty ( ) ;
2024-12-17 18:49:55 +00:00
/ * *
* Axis deadband for the controller .
* /
2025-03-19 03:45:47 +00:00
private Optional < Double > axisDeadband = Optional . empty ( ) ;
2024-12-17 18:49:55 +00:00
/ * *
* Translational axis scalar value , should be between ( 0 , 1 ] .
* /
2025-03-19 03:45:47 +00:00
private Optional < Double > translationAxisScale = Optional . empty ( ) ;
2024-12-17 18:49:55 +00:00
/ * *
* Angular velocity axis scalar value , should be between ( 0 , 1 ]
* /
2025-03-19 03:45:47 +00:00
private Optional < Double > omegaAxisScale = Optional . empty ( ) ;
2024-12-17 18:49:55 +00:00
/ * *
* Target to aim at .
* /
2025-03-19 03:45:47 +00:00
private Optional < Pose2d > aimTarget = Optional . empty ( ) ;
2025-02-22 06:15:56 +00:00
/ * *
* Target { @link Supplier < Pose2d > } to drive towards when driveToPose is enabled .
* /
2025-03-19 03:45:47 +00:00
private Optional < Supplier < Pose2d > > driveToPose = Optional . empty ( ) ;
2025-02-22 06:15:56 +00:00
/ * *
2025-03-19 03:45:47 +00:00
* { @link ProfiledPIDController } for the translation while driving to a pose . Units are m / s
2025-02-22 06:15:56 +00:00
* /
2025-03-19 03:45:47 +00:00
private Optional < ProfiledPIDController > driveToPoseTranslationPIDController = Optional . empty ( ) ;
2025-02-22 06:15:56 +00:00
/ * *
* { @link ProfiledPIDController } for the Rotational axis while driving to a pose . Units are m / s
* /
2025-03-19 03:45:47 +00:00
private Optional < ProfiledPIDController > driveToPoseOmegaPIDController = Optional . empty ( ) ;
2024-12-17 18:49:55 +00:00
/ * *
* Output { @link ChassisSpeeds } based on heading while this is True .
* /
2025-03-19 03:45:47 +00:00
private Optional < BooleanSupplier > headingEnabled = Optional . empty ( ) ;
2024-12-17 18:49:55 +00:00
/ * *
* Locked heading for { @link SwerveInputMode # TRANSLATION_ONLY }
* /
2025-03-19 03:45:47 +00:00
private Optional < Rotation2d > lockedHeading = Optional . empty ( ) ;
2024-12-17 18:49:55 +00:00
/ * *
* Output { @link ChassisSpeeds } based on aim while this is True .
* /
2025-03-19 03:45:47 +00:00
private Optional < BooleanSupplier > aimEnabled = Optional . empty ( ) ;
2025-02-22 06:15:56 +00:00
/ * *
* Output { @link ChassisSpeeds } to move to a specific { @link Pose2d } .
* /
2025-03-19 03:45:47 +00:00
private Optional < BooleanSupplier > driveToPoseEnabled = Optional . empty ( ) ;
2024-12-17 18:49:55 +00:00
/ * *
* Maintain current heading and drive without rotating , ideally .
* /
2025-03-19 03:45:47 +00:00
private Optional < BooleanSupplier > translationOnlyEnabled = Optional . empty ( ) ;
2025-01-06 15:44:15 +00:00
/ * *
* Cube the translation magnitude from the controller .
* /
2025-03-19 03:45:47 +00:00
private Optional < BooleanSupplier > translationCube = Optional . empty ( ) ;
2025-01-06 15:44:15 +00:00
/ * *
* Cube the angular velocity axis from the controller .
* /
2025-03-19 03:45:47 +00:00
private Optional < BooleanSupplier > omegaCube = Optional . empty ( ) ;
2025-01-06 15:44:15 +00:00
/ * *
* Robot relative oriented output expected .
* /
2025-03-19 03:45:47 +00:00
private Optional < BooleanSupplier > robotRelative = Optional . empty ( ) ;
2025-01-06 15:44:15 +00:00
/ * *
* Field oriented chassis output is relative to your current alliance .
* /
2025-03-19 03:45:47 +00:00
private Optional < BooleanSupplier > allianceRelative = Optional . empty ( ) ;
2025-02-22 06:15:56 +00:00
/ * *
* Heading offset enable state .
* /
2025-03-19 03:45:47 +00:00
private Optional < BooleanSupplier > translationHeadingOffsetEnabled = Optional . empty ( ) ;
2025-02-22 06:15:56 +00:00
/ * *
* Heading offset to apply during heading based control .
* /
2025-03-19 03:45:47 +00:00
private Optional < Rotation2d > translationHeadingOffset = Optional . empty ( ) ;
2024-12-17 18:49:55 +00:00
/ * *
* { @link SwerveController } for simple control over heading .
* /
2025-03-19 03:45:47 +00:00
private SwerveController swerveController = null ;
2024-12-17 18:49:55 +00:00
/ * *
* Current { @link SwerveInputMode } to use .
* /
2025-03-19 03:45:47 +00:00
private SwerveInputMode currentMode = SwerveInputMode . ANGULAR_VELOCITY ;
2024-12-17 18:49:55 +00:00
/ * *
* Create a { @link SwerveInputStream } for an easy way to generate { @link ChassisSpeeds } from a driver controller .
*
* @param drive { @link SwerveDrive } object for transformation .
* @param x Translation X input in range of [ - 1 , 1 ]
* @param y Translation Y input in range of [ - 1 , 1 ]
* /
private SwerveInputStream ( SwerveDrive drive , DoubleSupplier x , DoubleSupplier y )
{
controllerTranslationX = x ;
controllerTranslationY = y ;
swerveDrive = drive ;
}
/ * *
* Create a { @link SwerveInputStream } for an easy way to generate { @link ChassisSpeeds } from a driver controller .
*
* @param drive { @link SwerveDrive } object for transformation .
* @param x Translation X input in range of [ - 1 , 1 ]
* @param y Translation Y input in range of [ - 1 , 1 ]
* @param rot Rotation input in range of [ - 1 , 1 ]
* /
public SwerveInputStream ( SwerveDrive drive , DoubleSupplier x , DoubleSupplier y , DoubleSupplier rot )
{
this ( drive , x , y ) ;
controllerOmega = Optional . of ( rot ) ;
}
/ * *
* Create a { @link SwerveInputStream } for an easy way to generate { @link ChassisSpeeds } from a driver controller .
*
* @param drive { @link SwerveDrive } object for transformation .
* @param x Translation X input in range of [ - 1 , 1 ]
* @param y Translation Y input in range of [ - 1 , 1 ]
* @param headingX Heading X input in range of [ - 1 , 1 ]
* @param headingY Heading Y input in range of [ - 1 , 1 ]
* /
public SwerveInputStream ( SwerveDrive drive , DoubleSupplier x , DoubleSupplier y , DoubleSupplier headingX ,
DoubleSupplier headingY )
{
this ( drive , x , y ) ;
controllerHeadingX = Optional . of ( headingX ) ;
controllerHeadingY = Optional . of ( headingY ) ;
}
/ * *
* Create basic { @link SwerveInputStream } without any rotation components .
*
* @param drive { @link SwerveDrive } object for transformation .
* @param x { @link DoubleSupplier } of the translation X axis of the controller joystick to use .
* @param y { @link DoubleSupplier } of the translation X axis of the controller joystick to use .
* @return { @link SwerveInputStream } to use as you see fit .
* /
public static SwerveInputStream of ( SwerveDrive drive , DoubleSupplier x , DoubleSupplier y )
{
return new SwerveInputStream ( drive , x , y ) ;
}
2025-01-06 15:44:15 +00:00
/ * *
* Copy the { @link SwerveInputStream } object .
*
* @return Clone of current { @link SwerveInputStream }
* /
public SwerveInputStream copy ( )
{
SwerveInputStream newStream = new SwerveInputStream ( swerveDrive , controllerTranslationX , controllerTranslationY ) ;
newStream . controllerOmega = controllerOmega ;
newStream . controllerHeadingX = controllerHeadingX ;
newStream . controllerHeadingY = controllerHeadingY ;
newStream . axisDeadband = axisDeadband ;
newStream . translationAxisScale = translationAxisScale ;
newStream . omegaAxisScale = omegaAxisScale ;
2025-02-22 06:15:56 +00:00
newStream . driveToPose = driveToPose ;
2025-03-19 03:45:47 +00:00
newStream . driveToPoseTranslationPIDController = driveToPoseTranslationPIDController ;
2025-02-22 06:15:56 +00:00
newStream . driveToPoseOmegaPIDController = driveToPoseOmegaPIDController ;
2025-01-06 15:44:15 +00:00
newStream . aimTarget = aimTarget ;
newStream . headingEnabled = headingEnabled ;
newStream . aimEnabled = aimEnabled ;
2025-02-22 06:15:56 +00:00
newStream . driveToPoseEnabled = driveToPoseEnabled ;
2025-01-06 15:44:15 +00:00
newStream . currentMode = currentMode ;
newStream . translationOnlyEnabled = translationOnlyEnabled ;
newStream . lockedHeading = lockedHeading ;
newStream . swerveController = swerveController ;
newStream . omegaCube = omegaCube ;
newStream . translationCube = translationCube ;
newStream . robotRelative = robotRelative ;
newStream . allianceRelative = allianceRelative ;
2025-03-19 03:45:47 +00:00
newStream . translationHeadingOffsetEnabled = translationHeadingOffsetEnabled ;
newStream . translationHeadingOffset = translationHeadingOffset ;
2025-01-06 15:44:15 +00:00
return newStream ;
}
/ * *
* Set the stream to output robot relative { @link ChassisSpeeds }
*
* @param enabled Robot - Relative { @link ChassisSpeeds } output .
* @return self
* /
public SwerveInputStream robotRelative ( BooleanSupplier enabled )
{
robotRelative = Optional . of ( enabled ) ;
return this ;
}
/ * *
* Set the stream to output robot relative { @link ChassisSpeeds }
*
* @param enabled Robot - Relative { @link ChassisSpeeds } output .
* @return self
* /
public SwerveInputStream robotRelative ( boolean enabled )
{
robotRelative = enabled ? Optional . of ( ( ) - > enabled ) : Optional . empty ( ) ;
return this ;
}
2025-02-22 06:15:56 +00:00
/ * *
* Drive to a given pose with the provided { @link ProfiledPIDController } s
*
* @param pose { @link Supplier < Pose2d > } for ease of use .
2025-03-19 03:45:47 +00:00
* @param xPIDController PID controller for the translational axis , units are m / s .
2025-02-22 06:15:56 +00:00
* @param omegaPIDController PID Controller for rotational axis , units are rad / s .
* @return self
* /
public SwerveInputStream driveToPose ( Supplier < Pose2d > pose , ProfiledPIDController xPIDController ,
2025-03-19 03:45:47 +00:00
ProfiledPIDController omegaPIDController )
2025-02-22 06:15:56 +00:00
{
2025-03-19 03:45:47 +00:00
omegaPIDController . reset ( swerveDrive . getPose ( ) . getRotation ( ) . getRadians ( ) ) ;
xPIDController . reset ( swerveDrive . getPose ( ) . getTranslation ( ) . getDistance ( pose . get ( ) . getTranslation ( ) ) ) ;
omegaPIDController . enableContinuousInput ( - Math . PI , Math . PI ) ;
xPIDController . setGoal ( new State ( 0 , 0 ) ) ;
2025-02-22 06:15:56 +00:00
driveToPose = Optional . of ( pose ) ;
2025-03-19 03:45:47 +00:00
driveToPoseTranslationPIDController = Optional . of ( xPIDController ) ;
2025-02-22 06:15:56 +00:00
driveToPoseOmegaPIDController = Optional . of ( omegaPIDController ) ;
return this ;
}
/ * *
* Enable driving to the target pose .
*
* @param enabled Enable state of drive to pose .
* @return self .
* /
public SwerveInputStream driveToPoseEnabled ( BooleanSupplier enabled )
{
driveToPoseEnabled = Optional . of ( enabled ) ;
return this ;
}
/ * *
* Enable driving to the target pose .
*
* @param enabled Enable state of drive to pose .
* @return self .
* /
public SwerveInputStream driveToPoseEnabled ( boolean enabled )
{
driveToPoseEnabled = enabled ? Optional . of ( ( ) - > enabled ) : Optional . empty ( ) ;
2025-03-19 03:45:47 +00:00
Pose2d swervePose = swerveDrive . getPose ( ) ;
// driveToPoseXPIDController.ifPresent(profiledPIDController -> profiledPIDController.reset(swervePose.getX()));
// driveToPoseYPIDController.ifPresent(profiledPIDController -> profiledPIDController.reset(swervePose.getY()));
// driveToPoseOmegaPIDController.ifPresent(profiledPIDController -> profiledPIDController.reset(swervePose.getRotation()
// .getRadians()));
2025-02-22 06:15:56 +00:00
return this ;
}
/ * *
* Heading offset enabled boolean supplier .
*
* @param enabled Enable state
* @return self
* /
2025-03-19 03:45:47 +00:00
public SwerveInputStream translationHeadingOffset ( BooleanSupplier enabled )
2025-02-22 06:15:56 +00:00
{
2025-03-19 03:45:47 +00:00
translationHeadingOffsetEnabled = Optional . of ( enabled ) ;
2025-02-22 06:15:56 +00:00
return this ;
}
/ * *
* Heading offset enable
*
* @param enabled Enable state
* @return self
* /
2025-03-19 03:45:47 +00:00
public SwerveInputStream translationHeadingOffset ( boolean enabled )
2025-02-22 06:15:56 +00:00
{
2025-03-19 03:45:47 +00:00
translationHeadingOffsetEnabled = enabled ? Optional . of ( ( ) - > enabled ) : Optional . empty ( ) ;
2025-02-22 06:15:56 +00:00
return this ;
}
/ * *
* Set the heading offset angle .
*
* @param angle { @link Rotation2d } offset to apply
* @return self
* /
2025-03-19 03:45:47 +00:00
public SwerveInputStream translationHeadingOffset ( Rotation2d angle )
2025-02-22 06:15:56 +00:00
{
2025-03-19 03:45:47 +00:00
translationHeadingOffset = Optional . of ( angle ) ;
2025-02-22 06:15:56 +00:00
return this ;
}
2025-01-06 15:44:15 +00:00
/ * *
* Modify the output { @link ChassisSpeeds } so that it is always relative to your alliance .
*
* @param enabled Alliance aware { @link ChassisSpeeds } output .
* @return self
* /
public SwerveInputStream allianceRelativeControl ( BooleanSupplier enabled )
{
allianceRelative = Optional . of ( enabled ) ;
return this ;
}
/ * *
* Modify the output { @link ChassisSpeeds } so that it is always relative to your alliance .
*
* @param enabled Alliance aware { @link ChassisSpeeds } output .
* @return self
* /
public SwerveInputStream allianceRelativeControl ( boolean enabled )
{
allianceRelative = enabled ? Optional . of ( ( ) - > enabled ) : Optional . empty ( ) ;
return this ;
}
/ * *
* Cube the angular velocity controller axis for a non - linear controls scheme .
*
* @param enabled Enabled state for the stream .
* @return self .
* /
public SwerveInputStream cubeRotationControllerAxis ( BooleanSupplier enabled )
{
omegaCube = Optional . of ( enabled ) ;
return this ;
}
/ * *
* Cube the angular velocity controller axis for a non - linear controls scheme .
*
* @param enabled Enabled state for the stream .
* @return self .
* /
public SwerveInputStream cubeRotationControllerAxis ( boolean enabled )
{
omegaCube = Optional . of ( ( ) - > enabled ) ;
return this ;
}
/ * *
* Cube the translation axis magnitude for a non - linear control scheme .
*
* @param enabled Enabled state for the stream
* @return self
* /
public SwerveInputStream cubeTranslationControllerAxis ( BooleanSupplier enabled )
{
translationOnlyEnabled = Optional . of ( enabled ) ;
return this ;
}
/ * *
* Cube the translation axis magnitude for a non - linear control scheme
*
* @param enabled Enabled state for the stream
* @return self
* /
public SwerveInputStream cubeTranslationControllerAxis ( boolean enabled )
{
translationCube = enabled ? Optional . of ( ( ) - > enabled ) : Optional . empty ( ) ;
return this ;
}
2024-12-17 18:49:55 +00:00
/ * *
* Add a rotation axis for Angular Velocity control
*
* @param rot Rotation axis with values from [ - 1 , 1 ]
* @return self
* /
public SwerveInputStream withControllerRotationAxis ( DoubleSupplier rot )
{
controllerOmega = Optional . of ( rot ) ;
return this ;
}
/ * *
* Add heading axis for Heading based control .
*
* @param headingX Heading X axis with values from [ - 1 , 1 ]
* @param headingY Heading Y axis with values from [ - 1 , 1 ]
* @return self
* /
public SwerveInputStream withControllerHeadingAxis ( DoubleSupplier headingX , DoubleSupplier headingY )
{
controllerHeadingX = Optional . of ( headingX ) ;
controllerHeadingY = Optional . of ( headingY ) ;
return this ;
}
/ * *
* Set a deadband for all controller axis .
*
* @param deadband Deadband to set , should be between [ 0 , 1 )
* @return self
* /
public SwerveInputStream deadband ( double deadband )
{
axisDeadband = deadband = = 0 ? Optional . empty ( ) : Optional . of ( deadband ) ;
return this ;
}
/ * *
* Scale the translation axis for { @link SwerveInputStream } by a constant scalar value .
*
* @param scaleTranslation Translation axis scalar value . ( 0 , 1 ]
* @return this
* /
public SwerveInputStream scaleTranslation ( double scaleTranslation )
{
translationAxisScale = scaleTranslation = = 0 ? Optional . empty ( ) : Optional . of ( scaleTranslation ) ;
return this ;
}
/ * *
* Scale the rotation axis input for { @link SwerveInputStream } to reduce the range in which they operate .
*
* @param scaleRotation Angular velocity axis scalar value . ( 0 , 1 ]
* @return this
* /
public SwerveInputStream scaleRotation ( double scaleRotation )
{
omegaAxisScale = scaleRotation = = 0 ? Optional . empty ( ) : Optional . of ( scaleRotation ) ;
return this ;
}
/ * *
* Output { @link ChassisSpeeds } based on heading while the supplier is True .
*
* @param trigger Supplier to use .
* @return this .
* /
public SwerveInputStream headingWhile ( BooleanSupplier trigger )
{
headingEnabled = Optional . of ( trigger ) ;
return this ;
}
/ * *
* Set the heading enable state .
*
* @param headingState Heading enabled state .
* @return this
* /
public SwerveInputStream headingWhile ( boolean headingState )
{
if ( headingState )
{
headingEnabled = Optional . of ( ( ) - > true ) ;
} else
{
headingEnabled = Optional . empty ( ) ;
}
return this ;
}
/ * *
* Aim the { @link SwerveDrive } at this pose while driving .
*
* @param aimTarget { @link Pose2d } to point at .
* @return this
* /
public SwerveInputStream aim ( Pose2d aimTarget )
{
this . aimTarget = aimTarget . equals ( Pose2d . kZero ) ? Optional . empty ( ) : Optional . of ( aimTarget ) ;
return this ;
}
/ * *
* Enable aiming while the trigger is true .
*
* @param trigger When True will enable aiming at the current target .
* @return this .
* /
public SwerveInputStream aimWhile ( BooleanSupplier trigger )
{
aimEnabled = Optional . of ( trigger ) ;
return this ;
}
/ * *
* Enable aiming while the trigger is true .
*
* @param trigger When True will enable aiming at the current target .
* @return this .
* /
public SwerveInputStream aimWhile ( boolean trigger )
{
if ( trigger )
{
aimEnabled = Optional . of ( ( ) - > true ) ;
} else
{
aimEnabled = Optional . empty ( ) ;
}
return this ;
}
/ * *
* Enable locking of rotation and only translating , overrides everything .
*
* @param trigger Translation only while returns true .
* @return this
* /
public SwerveInputStream translationOnlyWhile ( BooleanSupplier trigger )
{
translationOnlyEnabled = Optional . of ( trigger ) ;
return this ;
}
/ * *
* Enable locking of rotation and only translating , overrides everything .
*
* @param translationState Translation only if true .
* @return this
* /
public SwerveInputStream translationOnlyWhile ( boolean translationState )
{
if ( translationState )
{
translationOnlyEnabled = Optional . of ( ( ) - > true ) ;
} else
{
translationOnlyEnabled = Optional . empty ( ) ;
}
return this ;
}
/ * *
* Find { @link SwerveInputMode } based off existing parameters of the { @link SwerveInputStream }
*
* @return The calculated { @link SwerveInputMode } , defaults to { @link SwerveInputMode # ANGULAR_VELOCITY } .
* /
private SwerveInputMode findMode ( )
{
2025-02-22 06:15:56 +00:00
if ( driveToPoseEnabled . isPresent ( ) & & driveToPoseEnabled . get ( ) . getAsBoolean ( ) )
{
if ( driveToPose . isPresent ( ) )
{
2025-03-19 03:45:47 +00:00
if ( driveToPoseOmegaPIDController . isPresent ( ) & & driveToPoseTranslationPIDController . isPresent ( ) )
2025-02-22 06:15:56 +00:00
{
return SwerveInputMode . DRIVE_TO_POSE ;
}
2025-03-19 03:45:47 +00:00
System . out . println ( " Drive to pose present " ) ;
2025-02-22 06:15:56 +00:00
DriverStation . reportError ( " Drive to pose not supplied with pid controllers. " , false ) ;
}
DriverStation . reportError ( " Drive to pose enabled without supplier present. " , false ) ;
} else if ( translationOnlyEnabled . isPresent ( ) & & translationOnlyEnabled . get ( ) . getAsBoolean ( ) )
2024-12-17 18:49:55 +00:00
{
return SwerveInputMode . TRANSLATION_ONLY ;
} else if ( aimEnabled . isPresent ( ) & & aimEnabled . get ( ) . getAsBoolean ( ) )
{
if ( aimTarget . isPresent ( ) )
{
return SwerveInputMode . AIM ;
} else
{
DriverStation . reportError (
" Attempting to enter AIM mode without target, please use SwerveInputStream.aim() to select a target first! " ,
false ) ;
}
} else if ( headingEnabled . isPresent ( ) & & headingEnabled . get ( ) . getAsBoolean ( ) )
{
if ( controllerHeadingX . isPresent ( ) & & controllerHeadingY . isPresent ( ) )
{
return SwerveInputMode . HEADING ;
} else
{
DriverStation . reportError (
" Attempting to enter HEADING mode without heading axis, please use SwerveInputStream.withControllerHeadingAxis to add heading axis! " ,
false ) ;
}
} else if ( controllerOmega . isEmpty ( ) )
{
DriverStation . reportError (
" Attempting to enter ANGULAR_VELOCITY mode without a rotation axis, please use SwerveInputStream.withControllerRotationAxis to add angular velocity axis! " ,
false ) ;
return SwerveInputMode . TRANSLATION_ONLY ;
}
return SwerveInputMode . ANGULAR_VELOCITY ;
}
/ * *
* Transition smoothly from one mode to another .
*
* @param newMode New mode to transition too .
* /
private void transitionMode ( SwerveInputMode newMode )
{
// Handle removing of current mode.
switch ( currentMode )
{
case TRANSLATION_ONLY - >
{
lockedHeading = Optional . empty ( ) ;
break ;
}
2025-03-19 03:45:47 +00:00
case ANGULAR_VELOCITY , HEADING , AIM - >
2024-12-17 18:49:55 +00:00
{
// Do nothing
break ;
}
2025-03-19 03:45:47 +00:00
case DRIVE_TO_POSE - >
{
break ;
}
2024-12-17 18:49:55 +00:00
}
// Transitioning to new mode
switch ( newMode )
{
case TRANSLATION_ONLY - >
{
lockedHeading = Optional . of ( swerveDrive . getOdometryHeading ( ) ) ;
break ;
}
2025-03-19 03:45:47 +00:00
case ANGULAR_VELOCITY - >
2024-12-17 18:49:55 +00:00
{
if ( swerveDrive . headingCorrection )
{
swerveDrive . setHeadingCorrection ( false ) ;
}
break ;
}
2025-02-22 06:15:56 +00:00
case HEADING , AIM - >
2024-12-17 18:49:55 +00:00
{
// Do nothing
break ;
}
2025-03-19 03:45:47 +00:00
case DRIVE_TO_POSE - >
{
if ( swerveDrive . headingCorrection )
{
swerveDrive . setHeadingCorrection ( false ) ;
}
}
2024-12-17 18:49:55 +00:00
}
}
/ * *
* Apply the deadband if it exists .
*
* @param axisValue Axis value to apply the deadband too .
* @return axis value with deadband , else axis value straight .
* /
private double applyDeadband ( double axisValue )
{
if ( axisDeadband . isPresent ( ) )
{
return MathUtil . applyDeadband ( axisValue , axisDeadband . get ( ) ) ;
}
return axisValue ;
}
/ * *
* Apply the scalar value if it exists .
*
* @param axisValue Axis value to apply teh scalar too .
* @return Axis value scaled by scalar value .
* /
private double applyRotationalScalar ( double axisValue )
{
if ( omegaAxisScale . isPresent ( ) )
{
return axisValue * omegaAxisScale . get ( ) ;
}
return axisValue ;
}
/ * *
* Scale the translational axis by the { @link SwerveInputStream # translationAxisScale } if it exists .
*
* @param xAxis X axis to scale .
* @param yAxis Y axis to scale .
* @return Scaled { @link Translation2d }
* /
private Translation2d applyTranslationScalar ( double xAxis , double yAxis )
{
if ( translationAxisScale . isPresent ( ) )
{
return SwerveMath . scaleTranslation ( new Translation2d ( xAxis , yAxis ) ,
translationAxisScale . get ( ) ) ;
}
return new Translation2d ( xAxis , yAxis ) ;
}
/ * *
2025-01-06 15:44:15 +00:00
* Apply the cube transformation on the given { @link Translation2d }
*
* @param translation { @link Translation2d } representing controller input
* @return Cubed { @link Translation2d } if the { @link SwerveInputStream # translationCube } is present .
* /
private Translation2d applyTranslationCube ( Translation2d translation )
{
if ( translationCube . isPresent ( ) & & translationCube . get ( ) . getAsBoolean ( ) )
{
return SwerveMath . cubeTranslation ( translation ) ;
}
return translation ;
}
/ * *
* Apply the cube transformation on the given rotation controller axis
*
* @param rotationAxis Rotation controller axis to cube .
* @return Cubed axis value if the { @link SwerveInputStream # omegaCube } is present .
* /
private double applyOmegaCube ( double rotationAxis )
{
if ( omegaCube . isPresent ( ) & & omegaCube . get ( ) . getAsBoolean ( ) )
{
return Math . pow ( rotationAxis , 3 ) ;
}
return rotationAxis ;
}
/ * *
2025-02-22 06:15:56 +00:00
* Change { @link ChassisSpeeds } from robot relative if enabled .
2025-01-06 15:44:15 +00:00
*
2025-02-22 06:15:56 +00:00
* @param fieldRelativeSpeeds Field or robot relative speeds to translate into robot - relative speeds .
* @return Field relative { @link ChassisSpeeds } .
2025-01-06 15:44:15 +00:00
* /
private ChassisSpeeds applyRobotRelativeTranslation ( ChassisSpeeds fieldRelativeSpeeds )
{
if ( robotRelative . isPresent ( ) & & robotRelative . get ( ) . getAsBoolean ( ) )
{
2025-02-22 06:15:56 +00:00
return ChassisSpeeds . fromRobotRelativeSpeeds ( fieldRelativeSpeeds , swerveDrive . getOdometryHeading ( ) ) ;
2025-01-06 15:44:15 +00:00
}
return fieldRelativeSpeeds ;
}
/ * *
* Apply alliance aware translation which flips the { @link Translation2d } if the robot is on the Blue alliance .
2024-12-17 18:49:55 +00:00
*
2025-01-06 15:44:15 +00:00
* @param fieldRelativeTranslation Field - relative { @link Translation2d } to flip .
* @return Alliance - oriented { @link Translation2d }
* /
private Translation2d applyAllianceAwareTranslation ( Translation2d fieldRelativeTranslation )
{
if ( allianceRelative . isPresent ( ) & & allianceRelative . get ( ) . getAsBoolean ( ) )
{
if ( robotRelative . isPresent ( ) & & robotRelative . get ( ) . getAsBoolean ( ) )
{
2025-03-19 03:45:47 +00:00
if ( driveToPoseEnabled . isPresent ( ) & & driveToPoseEnabled . get ( ) . getAsBoolean ( ) )
{
return fieldRelativeTranslation ;
}
2025-01-06 15:44:15 +00:00
throw new RuntimeException ( " Cannot use robot oriented control with Alliance aware movement! " ) ;
}
if ( DriverStation . getAlliance ( ) . isPresent ( ) & & DriverStation . getAlliance ( ) . get ( ) = = Alliance . Red )
{
return fieldRelativeTranslation . rotateBy ( Rotation2d . k180deg ) ;
}
}
return fieldRelativeTranslation ;
}
/ * *
2025-03-19 03:45:47 +00:00
* Adds offset to translation if one is set .
2025-01-06 15:44:15 +00:00
*
2025-03-19 03:45:47 +00:00
* @param speeds { @link ChassisSpeeds } to offset
* @return Offsetted { @link ChassisSpeeds }
2025-01-06 15:44:15 +00:00
* /
2025-03-19 03:45:47 +00:00
private ChassisSpeeds applyTranslationHeadingOffset ( ChassisSpeeds speeds )
2025-01-06 15:44:15 +00:00
{
2025-03-19 03:45:47 +00:00
if ( translationHeadingOffsetEnabled . isPresent ( ) & & translationHeadingOffsetEnabled . get ( ) . getAsBoolean ( ) )
2025-01-06 15:44:15 +00:00
{
2025-03-19 03:45:47 +00:00
if ( translationHeadingOffset . isPresent ( ) )
2025-01-06 15:44:15 +00:00
{
2025-03-19 03:45:47 +00:00
Translation2d speedsTranslation = new Translation2d ( speeds . vxMetersPerSecond ,
speeds . vyMetersPerSecond ) . rotateBy ( translationHeadingOffset . get ( ) ) ;
return new ChassisSpeeds ( speedsTranslation . getX ( ) , speedsTranslation . getY ( ) , speeds . omegaRadiansPerSecond ) ;
2025-01-06 15:44:15 +00:00
}
}
2025-03-19 03:45:47 +00:00
return speeds ;
2025-01-06 15:44:15 +00:00
}
2025-02-22 06:15:56 +00:00
/ * *
2025-03-19 03:45:47 +00:00
* When the { @link SwerveInputStream } is in { @link SwerveInputMode # DRIVE_TO_POSE } this function will return if the
* robot is at the desired pose within the defined tolerance .
2025-02-22 06:15:56 +00:00
*
2025-03-19 03:45:47 +00:00
* @param toleranceMeters Tolerance in meters .
* @return At target pose , true if current mode is not { @link SwerveInputMode # DRIVE_TO_POSE } and no pose supplier has
* been given .
2025-02-22 06:15:56 +00:00
* /
2025-03-19 03:45:47 +00:00
public boolean atTargetPose ( double toleranceMeters )
2025-02-22 06:15:56 +00:00
{
2025-03-19 03:45:47 +00:00
if ( currentMode ! = SwerveInputMode . DRIVE_TO_POSE )
2025-02-22 06:15:56 +00:00
{
2025-03-19 03:45:47 +00:00
DriverStation . reportError ( " SwerveInputStream.atTargetPose called while not set to DriveToPose. " , false ) ;
if ( ! driveToPose . isPresent ( ) )
2025-02-22 06:15:56 +00:00
{
2025-03-19 03:45:47 +00:00
return true ;
2025-02-22 06:15:56 +00:00
}
}
2025-03-19 03:45:47 +00:00
if ( driveToPose . isPresent ( ) )
{
Pose2d targetPose = driveToPose . get ( ) . get ( ) ;
return swerveDrive . getPose ( ) . getTranslation ( ) . getDistance ( targetPose . getTranslation ( ) ) < = toleranceMeters ;
}
return true ;
2025-02-22 06:15:56 +00:00
}
2025-01-06 15:44:15 +00:00
/ * *
* Gets a { @link ChassisSpeeds }
*
* @return { @link ChassisSpeeds }
2024-12-17 18:49:55 +00:00
* /
@Override
public ChassisSpeeds get ( )
{
double maximumChassisVelocity = swerveDrive . getMaximumChassisVelocity ( ) ;
Translation2d scaledTranslation = applyTranslationScalar ( applyDeadband ( controllerTranslationX . getAsDouble ( ) ) ,
applyDeadband ( controllerTranslationY . getAsDouble ( ) ) ) ;
2025-01-06 15:44:15 +00:00
scaledTranslation = applyTranslationCube ( scaledTranslation ) ;
scaledTranslation = applyAllianceAwareTranslation ( scaledTranslation ) ;
2024-12-17 18:49:55 +00:00
2025-01-06 15:44:15 +00:00
double vxMetersPerSecond = scaledTranslation . getX ( ) * maximumChassisVelocity ;
double vyMetersPerSecond = scaledTranslation . getY ( ) * maximumChassisVelocity ;
double omegaRadiansPerSecond = 0 ;
ChassisSpeeds speeds = new ChassisSpeeds ( ) ;
2024-12-17 18:49:55 +00:00
SwerveInputMode newMode = findMode ( ) ;
// Handle transitions here.
if ( currentMode ! = newMode )
{
transitionMode ( newMode ) ;
}
if ( swerveController = = null )
{
swerveController = swerveDrive . getSwerveController ( ) ;
}
switch ( newMode )
{
case TRANSLATION_ONLY - >
{
omegaRadiansPerSecond = swerveController . headingCalculate ( swerveDrive . getOdometryHeading ( ) . getRadians ( ) ,
lockedHeading . get ( ) . getRadians ( ) ) ;
2025-01-06 15:44:15 +00:00
speeds = new ChassisSpeeds ( vxMetersPerSecond , vyMetersPerSecond , omegaRadiansPerSecond ) ;
2024-12-17 18:49:55 +00:00
break ;
}
case ANGULAR_VELOCITY - >
{
2025-01-06 15:44:15 +00:00
omegaRadiansPerSecond = applyOmegaCube ( applyRotationalScalar ( applyDeadband ( controllerOmega . get ( )
. getAsDouble ( ) ) ) ) *
2024-12-17 18:49:55 +00:00
swerveDrive . getMaximumChassisAngularVelocity ( ) ;
2025-01-06 15:44:15 +00:00
speeds = new ChassisSpeeds ( vxMetersPerSecond , vyMetersPerSecond , omegaRadiansPerSecond ) ;
2024-12-17 18:49:55 +00:00
break ;
}
case HEADING - >
{
omegaRadiansPerSecond = swerveController . headingCalculate ( swerveDrive . getOdometryHeading ( ) . getRadians ( ) ,
2025-03-19 03:45:47 +00:00
Rotation2d . fromRadians (
swerveController . getJoystickAngle (
controllerHeadingX . get ( )
. getAsDouble ( ) ,
controllerHeadingY . get ( )
. getAsDouble ( ) ) )
. getRadians ( ) ) ;
2025-02-22 06:15:56 +00:00
// Prevent rotation if controller heading inputs are not past axisDeadband
if ( Math . abs ( controllerHeadingX . get ( ) . getAsDouble ( ) ) + Math . abs ( controllerHeadingY . get ( ) . getAsDouble ( ) ) <
axisDeadband . get ( ) )
{
omegaRadiansPerSecond = 0 ;
}
2025-01-06 15:44:15 +00:00
speeds = new ChassisSpeeds ( vxMetersPerSecond , vyMetersPerSecond , omegaRadiansPerSecond ) ;
2024-12-17 18:49:55 +00:00
break ;
}
case AIM - >
{
Rotation2d currentHeading = swerveDrive . getOdometryHeading ( ) ;
Translation2d relativeTrl = aimTarget . get ( ) . relativeTo ( swerveDrive . getPose ( ) ) . getTranslation ( ) ;
Rotation2d target = new Rotation2d ( relativeTrl . getX ( ) , relativeTrl . getY ( ) ) . plus ( currentHeading ) ;
omegaRadiansPerSecond = swerveController . headingCalculate ( currentHeading . getRadians ( ) , target . getRadians ( ) ) ;
2025-01-06 15:44:15 +00:00
speeds = new ChassisSpeeds ( vxMetersPerSecond , vyMetersPerSecond , omegaRadiansPerSecond ) ;
2024-12-17 18:49:55 +00:00
break ;
}
2025-02-22 06:15:56 +00:00
case DRIVE_TO_POSE - >
{
2025-03-19 03:45:47 +00:00
// Written by team 8865!
ProfiledPIDController translationPIDController = driveToPoseTranslationPIDController . get ( ) ;
ProfiledPIDController rotationPIDController = driveToPoseOmegaPIDController . get ( ) ;
Pose2d swervePoseSetpoint = driveToPose . get ( ) . get ( ) ;
Pose2d robotPose = swerveDrive . getPose ( ) ;
Vector < N2 > robotVec = robotPose . getTranslation ( ) . toVector ( ) ;
Vector < N2 > targetPoseRelativeToRobotPose = swervePoseSetpoint . getTranslation ( ) . toVector ( ) . minus (
robotVec ) ;
double distanceFromTarget = targetPoseRelativeToRobotPose . norm ( ) ;
Vector < N2 > traversalVector = new Vector ( Nat . N2 ( ) ) ;
traversalVector . set ( 0 , 0 , targetPoseRelativeToRobotPose . get ( 0 , 0 ) ) ;
traversalVector . set ( 1 , 0 , targetPoseRelativeToRobotPose . get ( 1 , 0 ) ) ;
traversalVector = traversalVector . unit ( )
. times ( - translationPIDController . calculate ( distanceFromTarget , 0 ) ) ;
Vector < N2 > robotForwardVec = robotPose . transformBy ( new Transform2d ( 1 , 0 , new Rotation2d ( ) ) ) . getTranslation ( )
. toVector ( ) . minus ( robotVec ) ;
Vector < N2 > robotLateralVec = robotPose . transformBy ( new Transform2d ( 0 , 1 , new Rotation2d ( ) ) ) . getTranslation ( )
. toVector ( ) . minus ( robotVec ) ;
currentMode = newMode ;
speeds = ChassisSpeeds . fromRobotRelativeSpeeds ( new ChassisSpeeds (
robotForwardVec . norm ( ) * traversalVector . dot ( robotForwardVec ) ,
robotLateralVec . norm ( ) * traversalVector . dot ( robotLateralVec ) ,
rotationPIDController . calculate ( robotPose . getRotation ( ) . getRadians ( ) ,
swervePoseSetpoint . getRotation ( ) . getRadians ( ) ) ) ,
swerveDrive . getOdometryHeading ( ) ) ;
double lerpDistance = robotPose . getTranslation ( ) . plus ( new Translation2d ( speeds . vxMetersPerSecond ,
vyMetersPerSecond ) . times ( 0 . 02 ) )
. getDistance ( swervePoseSetpoint . getTranslation ( ) ) ;
// Filter out incorrect ChassisSpeeds.
if ( lerpDistance > distanceFromTarget )
{
speeds = new ChassisSpeeds ( 0 , 0 , 0 ) ;
}
return speeds ;
2025-02-22 06:15:56 +00:00
}
2024-12-17 18:49:55 +00:00
}
currentMode = newMode ;
2025-03-19 03:45:47 +00:00
return applyTranslationHeadingOffset ( applyRobotRelativeTranslation ( speeds ) ) ;
2025-01-06 15:44:15 +00:00
}
/ * *
* Drive modes to keep track of .
* /
enum SwerveInputMode
{
/ * *
* Translation only mode , does not allow for rotation and maintains current heading .
* /
TRANSLATION_ONLY ,
/ * *
* Output based off angular velocity
* /
ANGULAR_VELOCITY ,
/ * *
* Output based off of heading .
* /
HEADING ,
/ * *
* Output based off of targeting .
* /
2025-02-22 06:15:56 +00:00
AIM ,
/ * *
* Drive to a target pose .
* /
DRIVE_TO_POSE
2024-12-17 18:49:55 +00:00
}
}