-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathCommandSwerveDrivetrain.java
423 lines (351 loc) · 16.6 KB
/
CommandSwerveDrivetrain.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
package frc.robot.subsystems.drive;
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;
import com.ctre.phoenix6.mechanisms.swerve.SwerveRequest;
import com.pathplanner.lib.auto.AutoBuilder;
import com.pathplanner.lib.commands.PathPlannerAuto;
import com.pathplanner.lib.pathfinding.LocalADStar;
import com.pathplanner.lib.pathfinding.Pathfinding;
import com.pathplanner.lib.util.HolonomicPathFollowerConfig;
import com.pathplanner.lib.util.PIDConstants;
import com.pathplanner.lib.util.ReplanningConfig;
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.geometry.Translation2d;
import edu.wpi.first.math.kinematics.ChassisSpeeds;
import edu.wpi.first.wpilibj.DriverStation;
import edu.wpi.first.wpilibj.Notifier;
import edu.wpi.first.wpilibj.RobotController;
import edu.wpi.first.wpilibj.Timer;
import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard;
import edu.wpi.first.wpilibj2.command.Command;
import edu.wpi.first.wpilibj2.command.CommandScheduler;
import edu.wpi.first.wpilibj2.command.Subsystem;
import frc.robot.Constants;
import frc.robot.Constants.DriveConstants;
import frc.robot.Constants.ScoringConstants;
import frc.robot.Constants.TunerConstants;
import frc.robot.utils.GeomUtil;
import frc.robot.utils.InterpolateDouble;
import java.util.List;
import java.util.function.Supplier;
import org.littletonrobotics.junction.Logger;
import org.photonvision.PhotonCamera;
import org.photonvision.targeting.TargetCorner;
/**
* 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 double vx, vy, omega = 0.0;
private boolean fieldCentric = true;
private double alignError = 0.0;
private Timer elapsedDriveToNote = new Timer();
private double estimatedTimeToNote = 0;
private boolean drivingToNote = false;
private double allotedErrorTime = 2;
public enum AlignTarget {
NONE,
NOTE,
AMP,
SPEAKER,
SOURCE
}
public enum AlignState {
MANUAL,
ALIGNING,
}
private AlignTarget alignTarget = AlignTarget.NONE;
private AlignState alignState = AlignState.MANUAL;
private PhotonCamera colorCamera = new PhotonCamera("photonvision-orange");
private static InterpolateDouble noteTimeToGoal =
new InterpolateDouble(ScoringConstants.timeToGoalMap(), 0.0, 2.0);
private Supplier<Pose2d> getFieldToRobot = () -> new Pose2d();
private Supplier<Translation2d> getFieldToSpeaker = () -> new Translation2d();
private Supplier<Rotation2d> getFieldToAmp = () -> new Rotation2d();
private Supplier<Rotation2d> getFieldToSource = () -> new Rotation2d();
private Supplier<Translation2d> getRobotVelocity = () -> new Translation2d();
private Supplier<Boolean> hasNote = () -> false;
private PIDController thetaController =
new PIDController(
DriveConstants.alignmentkPMax,
DriveConstants.alignmentkI,
DriveConstants.alignmentkD);
private SwerveRequest.FieldCentric driveFieldCentric = new SwerveRequest.FieldCentric();
private SwerveRequest.RobotCentric driveRobotCentric = new SwerveRequest.RobotCentric();
// private SwerveRequest.SwerveDriveBrake brake = new SwerveRequest.SwerveDriveBrake();
private static final double kSimLoopPeriod = 0.02; // Original: 5 ms
private Notifier simNotifier = null;
private double lastSimTime;
public CommandSwerveDrivetrain(
SwerveDrivetrainConstants driveTrainConstants,
double OdometryUpdateFrequency,
SwerveModuleConstants... modules) {
super(driveTrainConstants, OdometryUpdateFrequency, modules);
configurePathPlanner();
if (Constants.currentMode == Constants.Mode.SIM) {
startSimThread();
}
thetaController.enableContinuousInput(-Math.PI, Math.PI);
/*
* Since we're extending `SwerveDrivetrain`, we can't extend `SubsystemBase`, we can only
* implement `Subsystem`. Because of this, we have to register ourself manaully.
*
* In short, never trust the Command Scheduler.
*/
CommandScheduler.getInstance().registerSubsystem(this);
elapsedDriveToNote.reset();
elapsedDriveToNote.stop();
}
public CommandSwerveDrivetrain(
SwerveDrivetrainConstants driveTrainConstants, SwerveModuleConstants... modules) {
super(driveTrainConstants, modules);
configurePathPlanner();
if (Constants.currentMode == Constants.Mode.SIM) {
startSimThread();
}
thetaController.enableContinuousInput(-Math.PI, Math.PI);
/*
* Since we're extending `SwerveDrivetrain`, we can't extend `SubsystemBase`, we can only
* implement `Subsystem`. Because of this, we have to register ourself manaully.
*
* In short, never trust the Command Scheduler.
*/
CommandScheduler.getInstance().registerSubsystem(this);
}
public void setPoseSupplier(Supplier<Pose2d> getFieldToRobot) {
this.getFieldToRobot = getFieldToRobot;
}
public void setSpeakerSupplier(Supplier<Translation2d> getFieldToSpeaker) {
this.getFieldToSpeaker = getFieldToSpeaker;
}
public void setAmpSupplier(Supplier<Rotation2d> getFieldToAmp) {
this.getFieldToAmp = getFieldToAmp;
}
public void setSourceSupplier(Supplier<Rotation2d> getFieldToSource) {
this.getFieldToSource = getFieldToSource;
}
public void setNoteAcquiredSupplier(Supplier<Boolean> hasNote) {
this.hasNote = hasNote;
}
public void setAlignTarget(AlignTarget alignTarget) {
this.alignTarget = alignTarget;
}
public void setAlignState(AlignState state) {
this.alignState = state;
}
public AlignState getAlignState() {
return alignState;
}
public void setVelocitySupplier(Supplier<Translation2d> getRobotVelocity) {
this.getRobotVelocity = getRobotVelocity;
}
private void configurePathPlanner() {
double driveBaseRadius = 0;
for (var moduleLocation : m_moduleLocations) {
driveBaseRadius = Math.max(driveBaseRadius, moduleLocation.getNorm());
}
Pathfinding.setPathfinder(new LocalADStar());
AutoBuilder.configureHolonomic(
() -> this.getState().Pose, // Supplier of current robot pose
this::seedFieldRelative, // Consumer for seeding pose against auto
this::getCurrentRobotChassisSpeeds,
(speeds) -> {
this.setGoalChassisSpeeds(speeds, false);
this.setAlignState(AlignState.ALIGNING);
this.setAlignTarget(AlignTarget.SPEAKER);
}, // Consumer of ChassisSpeeds to drive the robot
new HolonomicPathFollowerConfig(
new PIDConstants(10, 0, 0),
new PIDConstants(10, 0, 0),
TunerConstants.kSpeedAt12VoltsMps,
driveBaseRadius,
new ReplanningConfig()),
() -> {
// Boolean supplier that controls when the path will be mirrored for the red
// alliance
// This will flip the path being followed to the red side of the field.
// THE ORIGIN WILL REMAIN ON THE BLUE SIDE
var alliance = DriverStation.getAlliance();
if (alliance.isPresent()) {
return alliance.get() == DriverStation.Alliance.Red;
}
return false;
}, // Change this if the path needs to be flipped on red vs blue
this); // Subsystem for requirements
}
public Command getAutoPath(String pathName) {
return new PathPlannerAuto(pathName);
}
public ChassisSpeeds getCurrentRobotChassisSpeeds() {
return m_kinematics.toChassisSpeeds(getState().ModuleStates);
}
private void startSimThread() {
lastSimTime = Utils.getCurrentTimeSeconds();
/* Run simulation at a faster rate so PID gains behave more reasonably */
simNotifier =
new Notifier(
() -> {
final double currentTime = Utils.getCurrentTimeSeconds();
double deltaTime = currentTime - lastSimTime;
lastSimTime = currentTime;
// System.out.println(System.currentTimeMillis());
/* use the measured time delta, get battery voltage from WPILib */
updateSimState(deltaTime, RobotController.getBatteryVoltage());
});
simNotifier.startPeriodic(kSimLoopPeriod);
}
public void setGoalChassisSpeeds(ChassisSpeeds chassisSpeeds, boolean fieldCen) {
vx = chassisSpeeds.vxMetersPerSecond;
vy = chassisSpeeds.vyMetersPerSecond;
omega = chassisSpeeds.omegaRadiansPerSecond;
fieldCentric = fieldCen;
}
public void setGoalChassisSpeeds(ChassisSpeeds chassisSpeeds) {
setGoalChassisSpeeds(chassisSpeeds, true);
}
private void controlDrivetrain() {
Pose2d pose = getFieldToRobot.get();
Rotation2d desiredHeading = pose.getRotation();
if (alignState != AlignState.ALIGNING || alignTarget != AlignTarget.NOTE) {
drivingToNote = false;
elapsedDriveToNote.stop();
elapsedDriveToNote.reset();
}
if (alignState == AlignState.ALIGNING) {
switch (alignTarget) {
/*case NOTE:
var cameraResult = colorCamera.getLatestResult();
if (cameraResult.hasTargets()) {
Pose2d notePose = getNotePoseFromTarget(cameraResult.getBestTarget());
if (!drivingToNote) {
double distance =
Math.sqrt(
Math.pow(notePose.getX(), 2)
+ Math.pow(notePose.getY(), 2));
drivingToNote = true;
estimatedTimeToNote =
distance / DriveConstants.MaxSpeedMetPerSec + allotedErrorTime;
elapsedDriveToNote.reset();
elapsedDriveToNote.start();
setGoalChassisSpeeds(
new ChassisSpeeds(DriveConstants.MaxSpeedMetPerSec, 0, 0),
false);
desiredHeading = notePose.getRotation();
} else if (elapsedDriveToNote.get() > estimatedTimeToNote
|| hasNote.get()) {
setGoalChassisSpeeds(new ChassisSpeeds());
} else {
setGoalChassisSpeeds(
new ChassisSpeeds(DriveConstants.MaxSpeedMetPerSec, 0, 0),
false);
desiredHeading = notePose.getRotation();
}
}
break;*/
case AMP:
desiredHeading = getFieldToAmp.get();
break;
case SPEAKER:
desiredHeading =
calculateDesiredHeading(
pose, new Pose2d(getFieldToSpeaker.get(), new Rotation2d()));
break;
case SOURCE:
desiredHeading = getFieldToSource.get();
break;
case NONE:
break;
default:
break;
}
alignError = thetaController.getPositionError();
double t = Math.abs(alignError) / (Math.PI / 4);
double minkP = DriveConstants.alignmentkPMin;
double maxkP = DriveConstants.alignmentkPMax;
double rotationkP = minkP * (1.0 - t) + t * maxkP;
// double rotationkP1 = (maxkP-minkP)/(Math.PI/4) * (alignError) + 0.1;
if (t > 1) { //
rotationkP = maxkP;
}
Logger.recordOutput("thetaController/rotationkP", rotationkP);
thetaController.setP(rotationkP);
omega =
-thetaController.calculate(
pose.getRotation().getRadians(), desiredHeading.getRadians());
Logger.recordOutput("Drive/omegaCommand", omega);
Logger.recordOutput("Drive/desiredHeading", desiredHeading.getRadians());
Logger.recordOutput("Drive/rotationError", thetaController.getPositionError());
}
Logger.recordOutput("Drive/alignState", alignState);
Logger.recordOutput("Drive/alignTarget", alignTarget);
Logger.recordOutput("Drive/desiredHeading", desiredHeading);
Logger.recordOutput("Drive/fieldToSpeaker", getFieldToSpeaker.get());
Logger.recordOutput("Drive/goalChassisSpeeds", new ChassisSpeeds(vx, vy, omega));
// if (vx == 0 && vy == 0 && omega == 0) {
// setControl(brake);
if (!fieldCentric) {
setControl(
driveRobotCentric
.withVelocityX(vx)
.withVelocityY(vy)
.withRotationalRate(omega)
.withDeadband(0.0)
.withRotationalDeadband(0.0));
} else {
setControl(
driveFieldCentric
.withVelocityX(vx)
.withVelocityY(vy)
.withRotationalRate(omega)
.withDeadband(0.0)
.withRotationalDeadband(0.0));
}
}
private Rotation2d calculateDesiredHeading(Pose2d current, Pose2d target) {
Translation2d robotVelocityAdjusted =
getRobotVelocity.get().times(DriveConstants.anticipationTime);
if (robotVelocityAdjusted.getNorm() < DriveConstants.minimumAnticipationVelocity) {
robotVelocityAdjusted = new Translation2d(0, 0);
}
double robotXAnticipated = current.getX() + robotVelocityAdjusted.getX();
double robotYAnticipated = current.getY() + robotVelocityAdjusted.getY();
Pose2d robotAnticipated =
new Pose2d(robotXAnticipated, robotYAnticipated, current.getRotation());
Pose2d robotToTargetAnticipated = GeomUtil.transformToPose(robotAnticipated.minus(target));
double distanceToTarget =
Math.hypot(
robotToTargetAnticipated.getTranslation().getX(),
robotToTargetAnticipated.getTranslation().getY());
Rotation2d angle =
Rotation2d.fromRadians(
Math.atan2(
robotToTargetAnticipated.getY(), robotToTargetAnticipated.getX()));
angle = angle.plus(Rotation2d.fromDegrees(180));
double timeToGoal = noteTimeToGoal.getValue(distanceToTarget);
double noteVelocity = distanceToTarget / timeToGoal;
// Correction angle accounting for robot velocity
double phi = (Math.PI / 2) - Math.acos(getRobotVelocity.get().getY() / noteVelocity);
return angle.minus(new Rotation2d(phi));
}
public boolean isAligned() {
return Math.abs(alignError) < DriveConstants.alignToleranceRadians;
}
@Override
public void periodic() {
controlDrivetrain();
List<TargetCorner> corners =
colorCamera.getLatestResult().getBestTarget().getDetectedCorners();
double[][] cornersCoordinates = new double[4][2];
/*for (int i = 0; i < 4; i++) {
cornersCoordinates[i] = getCoordinateOfTargetCorner(corners.get(i));
}*/
SmartDashboard.putNumberArray("coordinates 0", cornersCoordinates[0]);
SmartDashboard.putNumberArray("coordinates 1", cornersCoordinates[1]);
SmartDashboard.putNumberArray("coordinates 2", cornersCoordinates[2]);
SmartDashboard.putNumberArray("coordinates 3", cornersCoordinates[3]);
}
}