Commands

Commands are simple state machines that perform high-level robot functions using the methods defined by subsystems. Commands can be either idle, in which they do nothing, or scheduled, in which the scheduler will execute a specific set of the command’s code depending on the state of the command. The CommandScheduler recognizes scheduled commands as being in one of three states: initializing, executing, or ending. Commands specify what is done in each of these states through the initialize(), execute() and end() methods. Commands are represented in the command-based library by the Command interface (Java, C++).

Creating Commands

Nota

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.

Similarly to subsystems, the recommended method for most users to create a command is to 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  }

As before, this contains 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.

Also as before, advanced users seeking more flexibility are free to simply create their own class implementing the Command interface.

The Structure of a Command

While subsystems are fairly freeform, and may generally look like whatever the user wishes them to, commands are quite a bit more constrained. Command code must 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 ends).

Initialization

The initialize() method (Java, C++) is run exactly once per time a command is scheduled, as part of the scheduler’s schedule() method. The scheduler’s run() method does not need to be called for the initialize() method to run. The initialize block should be used to place the command in a known starting state for execution. 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, whenever 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() 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.

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 is un-scheduled. The isFinished() method is called after the execute() method, so the command will execute once on the same iteration that it is un-scheduled.

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 introduced in the previous section:

 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 in fact the command-based library includes code to make commands like this even more succinctly.

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}

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 (and, as can be guessed, the library includes tools to make this kind of command easier to write, too!).