Commands
Commands have special behavior that attempts to handle scheduling that can
make more complex robots unnecessarily complicated. The only use case for commands should be interfacing with buttons (and autos) to
ask the robot for a state. There should be little to no logic within the command. Robot behavior belongs in the subsystem state machines
and, when it uses more than one subsystem, in the robot's super state machine in RobotContainer. See Requesting Robot States.
Command Structure
Commands are classes that extend the Command class. They have a few key methods that are used to define their
behavior:
initialize(): Called once when the command is initially scheduled.execute(): Called repeatedly while the command is scheduled.isFinished(): Returns true when the command should end.end(interrupted): Called once when the command ends, either because it finished or was interrupted.
These methods execute like a state machine, with initialize() running once, followed by repeated calls to execute()
until isFinished() returns true, at which point end() is called. Below is an example of a command as a state machine:
The execution of commands is managed by the Command Scheduler, which is responsible for scheduling, running, and ending commands. It will call the appropriate methods on the command at the right times based on its state at a frequency of 50Hz (20ms intervals).
Instant Command Types
There are several types of commands that provide different behaviors. All commands ultimately extend the base Command class. They all implement the same methods, but some have additional features or constraints.
The types listed below have auto implementations for some methods to simplify common use cases. These are all ways to implement simple commands without needing to create a full class or object for each one.
We can return instant commands to define simple behaviors of a subsystem without needing to create a full command class for each action.
This can prevent creating multiple instant commands each time we want to run a simple action on a subsystem and keeps the interaction consistent.
In the examples below, the state diagrams show the execution flow of each command type. States that are highlighted in green indicate where user-defined behavior occurs, while states in red represent states with no user-defined behavior.
RunOnce Command
A command that runs a given function once and then finishes. The definition requires only the function to run. It will run the function and then finish immediately after.
Here is an example that sets a solenoid to the forward position once:
// Sets the hatch solenoid to the forward position once.
Commands.runOnce(() -> m_hatchSolenoid.set(kForward));
Run Command
A command that runs a given function repeatedly until it is interrupted.
The definition requires only the function to run. It will run the function in execute() and never finish on its own.
Here is an example that runs the drive trains while the command is active:
// A split-stick arcade command, with forward/backward controlled by the left
// hand, and turning controlled by the right.
Commands.run(() -> m_robotDrive.arcadeDrive(
-driverController.getLeftY(),
driverController.getRightX())
);
StartEnd Command
A command that runs one function when it starts and another when it ends.
The definition requires the two functions. It will run the start function in initialize() and the end function in end(), never finishing on its own. It will not run anything in execute().
Here is an example that starts a shooter when the command starts and stops it when the command ends:
Commands.startEnd(
// Start a flywheel spinning at 50% power
() -> m_shooter.shooterSpeed(0.5),
// Stop the flywheel at the end of the command
() -> m_shooter.shooterSpeed(0.0)
);
Wait Command
A command that does nothing for a specified duration. The definition requires only the duration. It will wait in execute() until the time has elapsed, but performs no actions during that time.
Here is an example that waits for 2 seconds:
Commands.waitSeconds(2.0);
Functional Command
A command that allows you to define custom behavior for each method using functions.
The definition requires functions for each of the command methods: initialize(), execute(), isFinished(), and end().
This can be useful for more complex commands that still do not require a full class definition.
Here is an example of a command that dr ives the robot forward for a specified distance:
new FunctionalCommand(
// Reset encoders on command start
m_robotDrive::resetEncoders,
// Start driving forward at the start of the command
() -> m_robotDrive.arcadeDrive(kAutoDriveSpeed, 0),
// Stop driving at the end of the command
interrupted -> m_robotDrive.arcadeDrive(0, 0),
// End the command when the robot's driven distance exceeds the desired value
() -> m_robotDrive.getAverageEncoderDistance() >= kAutoDriveDistanceInches
)
Full Custom Commands
For more complex behaviors, you can create a full command by extending the Command class and implementing the necessary methods.
These require their own class file and can encapsulate more complex logic. They function similarly to the Functional Command and allow for complete control over the command's behavior.
Below is a template for creating a custom command that inherits from the Command class. We almost never write one: buttons use the inline commands above, and autonomous routines are behavior trees, not commands. It is here so you can read WPILib code and the occasional dashboard utility.
public class ExampleCommand extends Command {
/** Creates a new ExampleCommand. */
public ExampleCommand() {}
// Called when the command is initially scheduled.
@Override
public void initialize() {}
// Called every time the scheduler runs while the command is scheduled.
@Override
public void execute() {}
// Called once the command ends or is interrupted.
@Override
public void end(boolean interrupted) {}
// Returns true when the command should end.
@Override
public boolean isFinished() {
return false;
}
}
Command Groups
Commands can be combined into groups to create more complex behaviors. There are several types of command groups available:
- SequentialCommandGroup: Runs commands in sequence, one after the other.
- ParallelCommandGroup: Runs commands in parallel, all at the same time.
- ParallelRaceGroup: Runs commands in parallel, but ends when the first command finishes.
- ParallelDeadlineGroup: Runs commands in parallel, but ends when a specified "deadline" command finishes.
These command groups allow you to create complex command sequences and behaviors by combining simpler commands.
new SequentialCommandGroup(
new FirstCommand(),
new SecondCommand(),
new ThirdCommand()
);
We do not write command groups for autos. Our autonomous routines are behavior trees: the
same sequencing (Sequence, ParallelDeadline, Timeout), built in Arbor with the robot's states as the steps,
editable without a code deploy. A multi-step action the driver needs is a robot state with step states in RobotContainer,
not a group. If a tree ever needs an existing WPILib command, registerCommand(...) wraps it as a node.
Command Decorators
Commands can be modified using decorators to change their behavior without altering the original command's code. These decorators can add functionality such as timeouts, delays, or conditional execution.
There are lots of available decorators in the Command class that can be seen in the WPILib Command documentation
or the API docs. So of the more popular ones are listed below.
withTimeout(double seconds)- Ends the command after the specified time has elapsed. (This is useful for safety to prevent a command from running indefinitely especially in Autonmous)
ignoreDisable(true/false)- Allows the command to run even when the robot is disabled. (By default, commands do not run when the robot is disabled for safety reasons)
until(Supplier<Boolean> condition)- Ends the command when the specified condition returns true. (This is useful for ending a command based on a specific event or state)
unless(Supplier<Boolean> condition)- Prevent the command from starting if the specified condition returns true. (This is useful for preventing a command from running under certain conditions)
onlyIf(Supplier<Boolean> condition)- Allows the command to run only if the specified condition returns true. (This is useful for ensuring a command only runs when certain criteria are met) They can be chained together to create complex behaviors. Here is an example of using decorators to modify a command:
Command myCommand = new ExampleCommand()
.withTimeout(5.0) // Ends after 5 seconds
.ignoreDisable(true) // Does run when disabled
.until(() -> someConditionIsMet()); // Ends when a specific condition is met
We can also create command sequences with decarators
andThen(Command next)- Schedules the specified command to run after the current command ends. (This is useful for creating command sequences without needing to create a full
SequentialCommandGroup)
- Schedules the specified command to run after the current command ends. (This is useful for creating command sequences without needing to create a full
beforeStarting(Command previous)- Schedules the specified command to run before the current command starts. (This is useful for setting up preconditions for a command without needing to create a full
SequentialCommandGroup) Here is an example of using these decorators to create a command sequence:
- Schedules the specified command to run before the current command starts. (This is useful for setting up preconditions for a command without needing to create a full
Command myCommandSequence = new ExampleCommand()
.andThen(new NextCommand()) // Runs after ExampleCommand ends
.beforeStarting(new SetupCommand()); // Runs before ExampleCommand starts
You can use these decorators on instant commands as well to create quick command sequences without needing to create full command classes.
Requesting Robot States
We do not write command files for subsystems. Anything that uses more than one subsystem is a robot state in the
super state machine in RobotContainer, and buttons and autos only
ask the robot for a state with setWantedState(...), the same method you use on a subsystem. The commands that do this are
small inline lambdas in OI:
RobotContainer robot = RobotContainer.getInstance();
// Hold: the robot wants SHOOT. Let go: back to DRIVE.
driver_controller_.rightTrigger().whileTrue(
Commands.startEnd(
() -> robot.setWantedState(RobotStates.SHOOT),
() -> robot.setWantedState(RobotStates.DRIVE)));
// Press: ask once.
driver_controller_.x().onTrue(Commands.runOnce(() -> robot.setWantedState(RobotStates.BRAKE)));
If two held buttons ask for different states, letting go of the first one should not cancel the second. Only go back to
DRIVE on release if your state is still the wanted one:
() -> {
if (robot.getWantedState() == RobotStates.AIM) {
robot.setWantedState(RobotStates.DRIVE);
}
}