-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve structure of command-based framework #22
base: main
Are you sure you want to change the base?
Conversation
@@ -97,3 +98,11 @@ wpi.java.configureTestTasks(test) | |||
tasks.withType(JavaCompile) { | |||
options.compilerArgs.add '-XDstringConcat=inline' | |||
} | |||
|
|||
spotless { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
./gradlew spotlessApply
formats the code consistently.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file just has changes recommend by IDE. You can ignore
public Command followTrajectory(String name, PathConstraints constraints) { | ||
return new FollowTrajectoryCommand(name, odometry, swerveDrive, eventMap, constraints); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Command factory functions like these help reduce duplicate code
return new FollowTrajectoryCommand(name, odometry, swerveDrive, eventMap, constraints); | ||
} | ||
|
||
public Command deliverCone(Constants.ConePosition conePosition) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not putting commands in separate classes makes it quick to move a subroutine into a named variable, which improves readability, and makes it easy to use this subroutine in multiple larger routines
addCommands( | ||
new InstantCommand(() -> grabberSubsystem.grab()), | ||
new WaitCommand(0.2), | ||
new WaitCommand(5) | ||
.deadlineWith( | ||
new WaitUntilCommand(() -> Math.abs(telescopeSubsystem.getTelescopePosition() - ArmConstants.telescopeMiddleSetpoint) < 0.05) | ||
.andThen(new InstantCommand(() -> grabberSubsystem.toggle())) | ||
.deadlineWith( | ||
new RunCommand(() -> pivotSubsystem.setPivotPosition(0.015)), | ||
new WaitUntilCommand(() -> pivotSubsystem.getPivotInRange(0.015, 0.012)) | ||
.andThen(new RunCommand(() -> telescopeSubsystem.setTelescopePosition(ArmConstants.telescopeMiddleSetpoint)))) | ||
.andThen(new WaitCommand(0.5) | ||
.andThen(new ParallelCommandGroup( | ||
new RunCommand(() -> telescopeSubsystem.setTelescopePosition(0.03)), | ||
new WaitUntilCommand(() -> telescopeSubsystem.getTelescopePosition() < 0.2) | ||
.andThen(new RunCommand(() -> pivotSubsystem.setPivotPosition(ArmConstants.elevatorUpPivotDownPosition))))))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Compare this to the new definition of this auto routine:
return grabberSubsystem
.grab()
.andThen(
pivotSubsystem
.moveToTargetContinuously(getArmPositionForCone(conePosition))
.raceWith(
telescopeSubsystem
.moveToTargetAndHold(getTelescopePositionForCone(conePosition))
.andThen(
grabberSubsystem.release(), telescopeSubsystem.moveToTarget(0.03))),
pivotSubsystem.moveToTargetUntilThere(ArmConstants.elevatorUpPivotDownPosition));
public Command release() { | ||
return runOnce(this::openGrabber).andThen(Commands.waitSeconds(0.2)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only commands are exposed publicly. That forces you to only control subsystems through the command scheduler, which can avoid conflicts caused by not setting requirements correctly. It also allows you to add command-based behavior to your publicly facing methods.
Consider this grabber case: You always had a delay after triggering the grabber solenoids to account for the time it takes for the pistons to move.
By making the control of these pistons commands, now the delay is built into this control, and is abstracted away from the user of this subsystem. If you replaced the current pistons with a slower/faster piston, you only need to change the time in these two methods, rather than every place you use them
public boolean isOpen() { | ||
return leftSolenoid.get() == Value.kForward; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Methods that expose the state of the subsystem are useful/necessary for building complex routines that need to know about the state of the subsystem. Remember, if you need to access the state of a subsystem from another, move the commands up to a common scope (like RobotContainer
, but pls not network tables)
color = LedColor.DOCKED; | ||
} | ||
public Command showBalancingColorCommand() { | ||
return runOnce(() -> color = LedColor.BALANCING); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This class is a very simple example of how the commands set the wanted state, and the default command applies the wanted state.
You could 100% remove the default command, and have each of these command factories set a value on the motor, but it's nice to have the physical values all get set in the same place, just to make adding on things easier
|
||
private double targetPos; | ||
|
||
public PivotSubsystem(CANSparkMax pivotMotor, TalonFXSensorCollection telescopeSensor) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe this is cheating. You decide. If you didn't want to pass in sensors from other subsystems, you could have a PivotFeedforwardCalculator
class that aggregated the data and provided an ff value
|
||
@Override | ||
public void periodic() { | ||
ntPivotPosition.setDouble(pivotEncoder.getPosition()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
periodic
is only used for updating internal state (like network tables), not external state (like motor values)
This is not meant to be my way or the highway
I spent the last week looking at the docs for command-based and talking with some of the developers in the frc discord. After lots of learning, I've taken some of the concepts that were recommended and applied them to this code. There are two main sets of changes
Obviously I haven't tested any of this on a real robot, but I think the general principles still come across, even if it's a tad bit buggy hot off the press.
I'd love for you guys to look at these changes and try to understand what it is I've done differently. Think about how you might add additional functionality and if the way I've set up the commands would make that easier / harder. This is a great time to look back on the season and become more skilled for next year.
If you dare, try putting it on the robot and see what bugs there are. One big change I made was I removed the manual control of the arm, as a challenge to get the PID+FF controller working flawlessly. That level of robot control will elevate you to the next level.