Commands

Commands represent actions the robot can take. Commands run when scheduled, until they are interrupted or their end condition is met. Commands are represented in the command-based library by the Command interface (Java, C++).

The Structure of a Command

Commands specify what the command will do in each of its possible states. This is done by overriding the initialize(), execute(), and end() methods. Additionally, a command must be able to tell the scheduler when (if ever) it has finished execution - this is done by overriding the isFinished() method. All of these methods are defaulted to reduce clutter in user code: initialize(), execute(), and end() are defaulted to simply do nothing, while isFinished() is defaulted to return false (resulting in a command that never finishes naturally, and will run until interrupted).

Initialization

The initialize() method (Java, C++) marks the command start, and is called exactly once per time a command is scheduled. The initialize() method should be used to place the command in a known starting state for execution. Command objects may be reused and scheduled multiple times, so any state or resources needed for the command’s functionality should be initialized or opened in initialize (which will be called at the start of each use) rather than the constructor (which is invoked only once on object allocation). It is also useful for performing tasks that only need to be performed once per time scheduled, such as setting motors to run at a constant speed or setting the state of a solenoid actuator.

Execution

The execute() method (Java, C++) is called repeatedly while the command is scheduled; this is when the scheduler’s run() method is called (this is generally done in the main robot periodic method, which runs every 20ms by default). The execute block should be used for any task that needs to be done continually while the command is scheduled, such as updating motor outputs to match joystick inputs, or using the output of a control loop.

Ending

The end(bool interrupted) method (Java, C++) is called once when the command ends, whether it finishes normally (i.e. isFinished() returned true) or it was interrupted (either by another command or by being explicitly canceled). The method argument specifies the manner in which the command ended; users can use this to differentiate the behavior of their command end accordingly. The end block should be used to “wrap up” command state in a neat way, such as setting motors back to zero or reverting a solenoid actuator to a “default” state. Any state or resources initialized in initialize() should be closed in end().

Specifying end conditions

The isFinished() method (Java, C++) is called repeatedly while the command is scheduled, whenever the scheduler’s run() method is called. As soon as it returns true, the command’s end() method is called and it ends. The isFinished() method is called after the execute() method, so the command will execute once on the same iteration that it ends.

Command Properties

In addition to the four lifecycle methods described above, each Command also has three properties, defined by getter methods that should always return the same value with no side affects.

getRequirements

Each command should declare any subsystems it controls as requirements. This backs the scheduler’s resource management mechanism, ensuring that no more than one command requires a given subsystem at the same time. This prevents situations such as two different pieces of code attempting to set the same motor controller to different output values.

Declaring requirements is done by overriding the getRequirements() method in the relevant command class, by calling addRequirements(), or by using the requirements vararg (Java) / initializer list (C++) parameter at the end of the parameter list of most command constructors and factories in the library:

Commands.run(intake::activate, intake);

As a rule, command compositions require all subsystems their components require.

runsWhenDisabled

The runsWhenDisabled() method (Java, C++) returns a boolean/bool specifying whether the command may run when the robot is disabled. With the default of returning false, the command will be canceled when the robot is disabled and attempts to schedule it will do nothing. Returning true will allow the command to run and be scheduled when the robot is disabled.

Important

When the robot is disabled, PWM outputs are disabled and CAN motor controllers may not apply voltage, regardless of runsWhenDisabled!

This property can be set either by overriding the runsWhenDisabled() method in the relevant command class, or by using the ignoringDisable decorator (Java, C++):

CommandBase mayRunDuringDisabled = Commands.run(() -> updateTelemetry()).ignoringDisable(true);

As a rule, command compositions may run when disabled if all their component commands set runsWhenDisabled as true.

getInterruptionBehavior

The getInterruptionBehavior() method (Java, C++) defines what happens if another command sharing a requirement is scheduled while this one is running. In the default behavior, kCancelSelf, the current command will be canceled and the incoming command will be scheduled successfully. If kCancelIncoming is returned, the incoming command’s scheduling will be aborted and this command will continue running. Note that getInterruptionBehavior only affects resolution of requirement conflicts: all commands can be canceled, regardless of getInterruptionBehavior.

Note

This was previously controlled by the interruptible parameter passed when scheduling a command, and is now a property of the command object.

This property can be set either by overriding the getInterruptionBehavior method in the relevant command class, or by using the withInterruptBehavior() decorator (Java, C++):

CommandBase noninteruptible = Commands.run(intake::activate, intake).withInterruptBehavior(Command.InterruptBehavior.kCancelIncoming);

As a rule, command compositions are kCancelIncoming if all their components are kCancelIncoming as well.

Included Command Types

The command-based library includes many pre-written command types. Through the use of lambdas, these commands can cover almost all use cases and teams should rarely need to write custom command classes. Many of these commands are provided via static factory functions in the Commands utility class (Java) or in the frc2::cmd namespace defined in the Commands.h header (C++). Classes inheriting from Subsystem also have instance methods that implicitly require this.

Running Actions

The most basic commands are actions the robot takes: setting voltage to a motor, changing a solenoid’s direction, etc. For these commands, which typically consist of a method call or two, the command-based library offers several factories to be construct commands inline with one or more lambdas to be executed.

The runOnce factory, backed by the InstantCommand (Java, C++) class, creates a command that calls a lambda once, and then finishes.

24  /** Grabs the hatch. */
25  public CommandBase grabHatchCommand() {
26    // implicitly require `this`
27    return this.runOnce(() -> m_hatchSolenoid.set(kForward));
28  }
29
30  /** Releases the hatch. */
31  public CommandBase releaseHatchCommand() {
32    // implicitly require `this`
33    return this.runOnce(() -> m_hatchSolenoid.set(kReverse));
34  }

The run factory, backed by the RunCommand (Java, C++) class, creates a command that calls a lambda repeatedly, until interrupted.

// A split-stick arcade command, with forward/backward controlled by the left
// hand, and turning controlled by the right.
new RunCommand(() -> m_robotDrive.arcadeDrive(
    -driverController.getLeftY(),
    driverController.getRightX()),
    m_robotDrive)

The startEnd factory, backed by the StartEndCommand (Java, C++) class, calls one lambda when scheduled, and then a second lambda when interrupted.

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),
    // Requires the shooter subsystem
    m_shooter
)

FunctionalCommand (Java, C++) accepts four lambdas that constitute the four command lifecycle methods: a Runnable/std::function<void()> for each of initialize() and execute(), a BooleanConsumer/std::function<void(bool)> for end(), and a BooleanSupplier/std::function<bool()> for isFinished().

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,
    // Require the drive subsystem
    m_robotDrive
)

To print a string and ending immediately, the library offers the Commands.print(String)/frc2::cmd::Print(std::string_view) factory, backed by the PrintCommand (Java, C++) subclass of InstantCommand.

Waiting

Waiting for a certain condition to happen or adding a delay can be useful to synchronize between different commands in a command composition or between other robot actions.

To wait and end after a specified period of time elapses, the library offers the Commands.waitSeconds(double)/frc2::cmd::Wait(units::second_t) factory, backed by the WaitCommand (Java, C++) class.

// Ends 5 seconds after being scheduled
new WaitCommand(5.0)

To wait until a certain condition becomes true, the library offers the Commands.waitUntil(BooleanSupplier)/frc2::cmd::WaitUntil(std::function<bool()>) factory, backed by the WaitUntilCommand class (Java, C++).

// Ends after m_limitSwitch.get() returns true
new WaitUntilCommand(m_limitSwitch::get)

Control Algorithm Commands

There are commands for various control setups:

  • PIDCommand uses a PID controller. For more info, see PIDCommand.

  • TrapezoidProfileCommand tracks a trapezoid motion profile. For more info, see TrapezoidProfileCommand.

  • ProfiledPIDCommand combines PID control with trapezoid motion profiles. For more info, see ProfiledPIDCommand.

  • MecanumControllerCommand (Java, C++) is useful for controlling mecanum drivetrains. See API docs and the MecanumControllerCommand (Java, C++) example project for more info.

  • SwerveControllerCommand (Java, C++) is useful for controlling swerve drivetrains. See API docs and the SwerveControllerCommand (Java, C++) example project for more info.

  • RamseteCommand (Java, C++) is useful for path following with differential drivetrains (“tank drive”). See API docs and the Trajectory Tutorial for more info.

Custom Command Classes

Users may also write custom command classes. As this is significantly more verbose, it’s recommended to use the more concise factories mentioned above.

Note

In the C++ API, a CRTP is used to allow certain Command methods to work with the object ownership model. Users should always extend the CommandHelper class when defining their own command classes, as is shown below.

To write a custom command class, subclass the abstract CommandBase class (Java, C++), as seen in the command-based template (Java, C++):

 7import edu.wpi.first.wpilibj.templates.commandbased.subsystems.ExampleSubsystem;
 8import edu.wpi.first.wpilibj2.command.CommandBase;
 9
10/** An example command that uses an example subsystem. */
11public class ExampleCommand extends CommandBase {
12  @SuppressWarnings({"PMD.UnusedPrivateField", "PMD.SingularField"})
13  private final ExampleSubsystem m_subsystem;
14
15  /**
16   * Creates a new ExampleCommand.
17   *
18   * @param subsystem The subsystem used by this command.
19   */
20  public ExampleCommand(ExampleSubsystem subsystem) {
21    m_subsystem = subsystem;
22    // Use addRequirements() here to declare subsystem dependencies.
23    addRequirements(subsystem);
24  }

Inheriting from CommandBase rather than Command provides several convenience features. It automatically overrides the getRequirements() method for users, returning a list of requirements that is empty by default, but can be added to with the addRequirements() method. It also implements the Sendable interface, and so can be sent to the dashboard - this provides a handy way for scheduling commands for testing (via a button on the dashboard) without needing to bind them to buttons on a controller.

Simple Command Example

What might a functional command look like in practice? As before, below is a simple command from the HatchBot example project (Java, C++) that uses the HatchSubsystem:

 5package edu.wpi.first.wpilibj.examples.hatchbottraditional.commands;
 6
 7import edu.wpi.first.wpilibj.examples.hatchbottraditional.subsystems.HatchSubsystem;
 8import edu.wpi.first.wpilibj2.command.CommandBase;
 9
10/**
11 * A simple command that grabs a hatch with the {@link HatchSubsystem}. Written explicitly for
12 * pedagogical purposes. Actual code should inline a command this simple with {@link
13 * edu.wpi.first.wpilibj2.command.InstantCommand}.
14 */
15public class GrabHatch extends CommandBase {
16  // The subsystem the command runs on
17  private final HatchSubsystem m_hatchSubsystem;
18
19  public GrabHatch(HatchSubsystem subsystem) {
20    m_hatchSubsystem = subsystem;
21    addRequirements(m_hatchSubsystem);
22  }
23
24  @Override
25  public void initialize() {
26    m_hatchSubsystem.grabHatch();
27  }
28
29  @Override
30  public boolean isFinished() {
31    return true;
32  }
33}

Notice that the hatch subsystem used by the command is passed into the command through the command’s constructor. This is a pattern called dependency injection, and allows users to avoid declaring their subsystems as global variables. This is widely accepted as a best-practice - the reasoning behind this is discussed in a later section.

Notice also that the above command calls the subsystem method once from initialize, and then immediately ends (as isFinished() simply returns true). This is typical for commands that toggle the states of subsystems, and as such it would be more succinct to write this command using the factories described above.

What about a more complicated case? Below is a drive command, from the same example project:

 5package edu.wpi.first.wpilibj.examples.hatchbottraditional.commands;
 6
 7import edu.wpi.first.wpilibj.examples.hatchbottraditional.subsystems.DriveSubsystem;
 8import edu.wpi.first.wpilibj2.command.CommandBase;
 9import java.util.function.DoubleSupplier;
10
11/**
12 * A command to drive the robot with joystick input (passed in as {@link DoubleSupplier}s). Written
13 * explicitly for pedagogical purposes - actual code should inline a command this simple with {@link
14 * edu.wpi.first.wpilibj2.command.RunCommand}.
15 */
16public class DefaultDrive extends CommandBase {
17  private final DriveSubsystem m_drive;
18  private final DoubleSupplier m_forward;
19  private final DoubleSupplier m_rotation;
20
21  /**
22   * Creates a new DefaultDrive.
23   *
24   * @param subsystem The drive subsystem this command wil run on.
25   * @param forward The control input for driving forwards/backwards
26   * @param rotation The control input for turning
27   */
28  public DefaultDrive(DriveSubsystem subsystem, DoubleSupplier forward, DoubleSupplier rotation) {
29    m_drive = subsystem;
30    m_forward = forward;
31    m_rotation = rotation;
32    addRequirements(m_drive);
33  }
34
35  @Override
36  public void execute() {
37    m_drive.arcadeDrive(m_forward.getAsDouble(), m_rotation.getAsDouble());
38  }
39}

And then usage:

57    // Configure default commands
58    // Set the default drive command to split-stick arcade drive
59    m_robotDrive.setDefaultCommand(
60        // A split-stick arcade command, with forward/backward controlled by the left
61        // hand, and turning controlled by the right.
62        new DefaultDrive(
63            m_robotDrive,
64            () -> -m_driverController.getLeftY(),
65            () -> -m_driverController.getRightX()));

Notice that this command does not override isFinished(), and thus will never end; this is the norm for commands that are intended to be used as default commands. Once more, this command is rather simple and calls the subsystem method only from one place, and as such, could be more concisely written using factories:

49    // Configure default commands
50    // Set the default drive command to split-stick arcade drive
51    m_robotDrive.setDefaultCommand(
52        // A split-stick arcade command, with forward/backward controlled by the left
53        // hand, and turning controlled by the right.
54        Commands.run(
55            () ->
56                m_robotDrive.arcadeDrive(
57                    -m_driverController.getLeftY(), -m_driverController.getRightX()),
58            m_robotDrive));