55import com .ctre .phoenix6 .swerve .SwerveModule ;
66import com .ctre .phoenix6 .swerve .SwerveRequest ;
77import edu .wpi .first .epilogue .Logged ;
8+ import edu .wpi .first .math .controller .ProfiledPIDController ;
89import edu .wpi .first .math .geometry .Rotation2d ;
10+ import edu .wpi .first .math .geometry .Translation2d ;
11+ import edu .wpi .first .math .kinematics .ChassisSpeeds ;
912import edu .wpi .first .math .kinematics .SwerveModuleState ;
13+ import edu .wpi .first .math .trajectory .TrapezoidProfile ;
1014import edu .wpi .first .wpilibj2 .command .Command ;
1115import frc .robot .config .TunerConstants ;
1216import frc .robot .helpers .ApplyModuleStates ;
17+ import frc .robot .helpers .ShotCalculator ;
1318import frc .robot .subsystems .CommandSwerveDrivetrain ;
1419import java .util .function .DoubleSupplier ;
1520
@@ -18,14 +23,16 @@ public class DrivetrainCommand extends Command {
1823 public static enum Position {
1924 TELEOP ,
2025 STILL_SHOT ,
21- SOTM
26+ SOTM ,
27+ AUTO_ALIGN_HUB
2228 }
2329
2430 private CommandSwerveDrivetrain subsystem ;
2531 private DrivetrainCommand .Position pose ;
2632 private DoubleSupplier leftY ;
2733 private DoubleSupplier leftX ;
2834 private DoubleSupplier rightX ;
35+ public static String drivetrainState = "Stopped" ;
2936
3037 private final double MaxSpeed =
3138 TunerConstants .kSpeedAt12Volts .in (MetersPerSecond ); // kSpeedAt12Volts desired top speed
@@ -48,6 +55,9 @@ public static enum Position {
4855 };
4956 private final ApplyModuleStates applyRequest = new ApplyModuleStates ();
5057 private final SwerveRequest .SwerveDriveBrake brakeRequest = new SwerveRequest .SwerveDriveBrake ();
58+ private final ProfiledPIDController autoAlignPidController ;
59+ private Rotation2d m_targetAngle = Rotation2d .kZero ;
60+ private static final double FUEL_EXIT_VELOCITY = 10.0 ; // This needs to be tuned
5161
5262 public DrivetrainCommand (
5363 CommandSwerveDrivetrain subsystem ,
@@ -60,10 +70,38 @@ public DrivetrainCommand(
6070 this .leftX = leftX ;
6171 this .leftY = leftY ;
6272 this .rightX = rightX ;
73+ double alignP = 85 ;
74+ double alignI = 0 ;
75+ double alignD = 0 ;
76+ this .autoAlignPidController =
77+ new ProfiledPIDController (
78+ alignP ,
79+ alignI ,
80+ alignD ,
81+ new TrapezoidProfile .Constraints (MaxAngularRate , MaxAngularRate / 0.2 ));
82+ this .autoAlignPidController .enableContinuousInput (
83+ -Math .PI , Math .PI ); // Swerve angles are continuous (-180 to 180 deg)
84+ // Don't compute hub-facing target in the constructor (DriverStation alliance may be
85+ // unavailable during initialization). Initialize to current heading; execute() will
86+ // recompute the actual hub-facing target each loop.
87+ this .m_targetAngle = subsystem .getState ().Pose .getRotation ();
6388
6489 addRequirements (subsystem );
6590 }
6691
92+ public double getAutoAlignRotationalOutput () {
93+ Rotation2d currentAngle = subsystem .getState ().Pose .getRotation ();
94+ double current = currentAngle .getRadians ();
95+ double target = m_targetAngle .getRadians ();
96+ double output = autoAlignPidController .calculate (current , target );
97+ if (output > MaxAngularRate ) {
98+ output = MaxAngularRate ;
99+ } else if (output < -MaxAngularRate ) {
100+ output = -MaxAngularRate ;
101+ }
102+ return output ;
103+ }
104+
67105 @ Override
68106 public void execute () {
69107 switch (pose ) {
@@ -78,20 +116,71 @@ public void execute() {
78116 .withRotationalRate (
79117 -rightX .getAsDouble ()
80118 * MaxAngularRate )); // Drive counterclockwise with negative X
119+ drivetrainState = "Teleop Drive" ;
81120 break ;
82121
83122 // (Needs check) Mode for when shots are from a still position
84123 case STILL_SHOT :
85124 // make X with wheels, set wheels to brake mode
86125 applyRequest .ModuleStates = states ;
87126 subsystem .setControl (applyRequest );
88- System . out . println ( "Drivetrain: Still Shot Configuration" ) ;
127+ drivetrainState = " Still Shot" ;
89128 break ;
90129
91130 // (Incomplete) Mode for moving shots
92131 case SOTM :
93- // shoot on the move, reference Mechanical Advantage build log
94- System .out .println ("Drivetrain: SOTM Drive" );
132+ drivetrainState = "Shoot on the Move Drive" ;
133+ double vx = -leftY .getAsDouble () * MaxSpeed ;
134+ double vy = -leftX .getAsDouble () * MaxSpeed ;
135+ Translation2d robotPosition = subsystem .getState ().Pose .getTranslation ();
136+ ChassisSpeeds speeds = subsystem .getState ().Speeds ;
137+ Translation2d robotVelocity =
138+ new Translation2d (speeds .vxMetersPerSecond , speeds .vyMetersPerSecond );
139+ Translation2d goal = ShotCalculator .calculateHubPosition ();
140+ double distance = robotPosition .getDistance (goal );
141+ double flightTime = distance / FUEL_EXIT_VELOCITY ;
142+ Translation2d toGoal = goal .minus (robotPosition );
143+ Translation2d toGoalDir =
144+ toGoal .getNorm () > 1e-6 ? toGoal .div (toGoal .getNorm ()) : new Translation2d ();
145+ Translation2d lateralVelocity =
146+ robotVelocity .minus (toGoalDir .times (robotVelocity .dot (toGoalDir )));
147+ Translation2d virtualGoal = goal .minus (lateralVelocity .times (flightTime ));
148+ Rotation2d targetAngle = virtualGoal .minus (robotPosition ).getAngle ();
149+ double currentHeading = subsystem .getState ().Pose .getRotation ().getRadians ();
150+ double targetHeading = targetAngle .getRadians ();
151+ double error = targetHeading - currentHeading ;
152+ error = Math .atan2 (Math .sin (error ), Math .cos (error ));
153+ double kP = 50 ; // tune
154+ double omega = error * kP ;
155+ omega = Math .max (-MaxAngularRate , Math .min (MaxAngularRate , omega ));
156+ subsystem .setControl (drive .withVelocityX (vx ).withVelocityY (vy ).withRotationalRate (omega ));
157+
158+ break ;
159+
160+ case AUTO_ALIGN_HUB :
161+ // Recompute the desired heading toward the hub each loop
162+ m_targetAngle =
163+ ShotCalculator .getRotationTowardsHub (
164+ ShotCalculator .calculateHubPosition (), subsystem .getState ().Pose .getTranslation ());
165+ double currentRad = subsystem .getState ().Pose .getRotation ().getRadians ();
166+ double targetRad = m_targetAngle .getRadians ();
167+ // Normalize error1 to [-pi, pi]
168+ double error1 =
169+ Math .atan2 (Math .sin (targetRad - currentRad ), Math .cos (targetRad - currentRad ));
170+ double absError = Math .abs (error1 );
171+ double angleTolerance = Math .toRadians (0.5 ); // stop within 1 degree
172+
173+ double rotOutput = getAutoAlignRotationalOutput ();
174+
175+ if (absError < angleTolerance ) {
176+ // Aligned: stop rotating (and hold position)
177+ subsystem .setControl (drive .withVelocityX (0.0 ).withVelocityY (0.0 ).withRotationalRate (0.0 ));
178+ } else {
179+ // Not aligned: apply rotational output
180+ subsystem .setControl (
181+ drive .withVelocityX (0.0 ).withVelocityY (0.0 ).withRotationalRate (rotOutput ));
182+ }
183+ drivetrainState = "Auto Align" ;
95184 break ;
96185
97186 default :
@@ -104,6 +193,22 @@ public void end(boolean interrupted) {}
104193
105194 @ Override
106195 public boolean isFinished () {
107- return false ;
196+ if (pose != Position .AUTO_ALIGN_HUB ) {
197+ return false ;
198+ }
199+
200+ Translation2d hubPos = ShotCalculator .calculateHubPosition ();
201+
202+ Translation2d currentTrans = subsystem .getState ().Pose .getTranslation ();
203+ if (currentTrans == null ) {
204+ return false ;
205+ }
206+
207+ Rotation2d target = ShotCalculator .getRotationTowardsHub (hubPos , currentTrans );
208+ double current = subsystem .getState ().Pose .getRotation ().getRadians ();
209+ double targetRad = target .getRadians ();
210+ double err = Math .atan2 (Math .sin (targetRad - current ), Math .cos (targetRad - current ));
211+ double angleTolerance = Math .toRadians (1.0 ); // 1 degree
212+ return Math .abs (err ) < angleTolerance ;
108213 }
109214}
0 commit comments