While the previously-described methodologies will work fine for writing command-based robot code, the command-based libraries contain several convenience features for more-advanced users that can greatly reduce the verbosity/complexity of command-based code. It is highly recommended that users familiarize themselves with these features to maximize the value they get out of the command-based libraries.
While users are able to create commands by explicitly writing command classes (either by subclassing CommandBase
or implementing Command
), for many commands (such as those that simply call a single subsystem method) this involves a lot of wasteful boilerplate code. To help alleviate this, many of the prewritten commands included in the command-based library may be inlined - that is, the command body can be defined in a single line of code at command construction.
In order to inline a command definition, users require some way to specify what code the commands will run as constructor parameters. Fortunately, both Java and C++ offer users the ability to pass subroutines as parameters.
In Java, a reference to a subroutine that can be passed as a parameter is called a method reference. The general syntax for a method reference is object::method
. Note that no method parameters are included, since the method itself is the parameter. The method is not being called - it is being passed to another piece of code (in this case, a command) so that that code can call it when needed. For further information on method references, see the official Oracle documentation.
While method references work well for passing a subroutine that has already been written, often it is inconvenient/wasteful to write a subroutine solely for the purpose of sending as a method reference, if that subroutine will never be used elsewhere. To avoid this, Java also supports a feature called "lambda expressions." A lambda expression is an inline method definition - it allows a subroutine to be defined inside of a parameter list. For specifics on how to write Java lambda expressions, see this tutorial.
Warning
Due to complications in C++ semantics, capturing this
in a C++ lambda can cause a null pointer exception if done from a component command of a command group. Whenever possible, C++ users should capture relevant command members explicitly and by value. For more details, see here.
C++ lacks a close equivalent to Java method references - pointers to member functions are generally not directly useable as parameters due to the presence of the implicit this
parameter. However, C++ does offer lambda expressions - in addition, the lambda expressions offered by C++ are in many ways more powerful than those in Java. For specifics on how to write C++ lambda expressions, see cppreference.
So, what does an inlined command definition look like in practice?
The InstantCommand
class provides an example of a type of command that benefits greatly from inlining. Consider the following from the HatchBotInlined example project (Java, C++):
Instead of wastefully writing separate GrabHatch
and ReleaseHatch
commands which call only one method before ending, both can be accomplished with a simple inline definition by passing appropriate subsystem method.
The command-based library includes a variety of pre-written commands for commonly-encountered use cases. Many of these commands are intended to be used "out-of-the-box" via inlining, however they may be subclassed, as well. A list of the included pre-made commands can be found below, along with brief examples of each - for more rigorous documentation, see the API docs (Java, C++).
The ConditionalCommand
class (Java, C++) runs one of two commands when executed, depending on a user-specified true-or-false condition:
java
// Runs either commandOnTrue or commandOnFalse depending on the value of m_limitSwitch.get() new ConditionalCommand(commandOnTrue, commandOnFalse, m_limitSwitch::get)
c++
// Runs either commandOnTrue or commandOnFalse depending on the value of m_limitSwitch.get() frc2::ConditionalCommand(commandOnTrue, commandOnFalse, [&m_limitSwitch] { return m_limitSwitch.Get(); })
Note
While the Java version of SelectCommand simply uses an Object
as a key, the C++ version is templated on the key type.
Note
An alternate version of SelectCommand simply takes a method that supplies the command to be run - this can be very succinct, but makes inferring the command's requirements impossible, and so leaves the user responsible for manually adding the requirements to the SelectCommand.
The SelectCommand
class (Java, C++) is a generalization of the ConditionalCommand
class that runs one of a selection of commands based on the value of a user-specified selector. The following example code is taken from the SelectCommand example project (Java, C++):
The InstantCommand
class (Java, C++) executes a single action on initialization, and then ends immediately:
java
// Actuates the hatch subsystem to grab the hatch new InstantCommand(m_hatchSubsystem::grabHatch, m_hatchSubsystem)
c++
// Actuates the hatch subsystem to grab the hatch frc2::InstantCommand([&m_hatchSubsystem] { m_hatchSubsystem.GrabHatch(); }, {&m_hatchSubsystem})
The RunCommand
class (Java, C++) runs a specified method repeatedly in its execute()
block. It does not have end conditions by default; users can either subclass it, or decorate it to add them.
java
// 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.getY(GenericHID.Hand.kLeft), driverController.getX(GenericHID.Hand.kRight)), m_robotDrive)
c++
// A split-stick arcade command, with forward/backward controlled by the left // hand, and turning controlled by the right. frc2::RunCommand( [this] { m_drive.ArcadeDrive( -m_driverController.GetY(frc::GenericHID::kLeftHand), m_driverController.GetX(frc::GenericHID::kRightHand)); }, {&m_drive}))
The StartEndCommand
class (Java, C++) executes an action when starting, and a second one when ending. It does not have end conditions by default; users can either subclass it, or decorate an inlined command to add them.
java
- new StartEndCommand(
// 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
)
c++
- frc2::StartEndCommand(
// Start a flywheel spinning at 50% power [this] { m_shooter.shooterSpeed(0.5); }, // Stop the flywheel at the end of the command [this] { m_shooter.shooterSpeed(0.0); }, // Requires the shooter subsystem {&m_shooter}
)
The FunctionalCommand
class (Java, C++) allows all four Command
methods to be passed in as method references or lambdas:
java
- 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
)
c++
- frc2::FunctionalCommand(
// Reset encoders on command start [this] { m_drive.ResetEncoders(); }, // Start driving forward at the start of the command [this] { m_drive.ArcadeDrive(ac::kAutoDriveSpeed, 0); }, // Stop driving at the end of the command [this] (bool interrupted) { m_drive.ArcadeDrive(0, 0); }, // End the command when the robot's driven distance exceeds the desired value [this] { return m_drive.GetAverageEncoderDistance() >= kAutoDriveDistanceInches; }, // Requires the drive subsystem {&m_drive}
)
The PrintCommand
class (Java, C++) prints a given string.
java
new PrintCommand("This message will be printed!")
c++
frc2::PrintCommand("This message will be printed!")
The ScheduleCommand
class (Java, C++) schedules a specified command, and ends instantly:
java
// Schedules commandToSchedule when run new ScheduleCommand(commandToSchedule)
c++
// Schedules commandToSchedule when run frc2::ScheduleCommand(&commandToSchedule)
This is often useful for "forking off" from command groups: by default, commands in command groups are run through the command group, and are never themselves seen by the scheduler. Accordingly, their requirements are added to the group's requirements. While this is usually fine, sometimes it is undesirable for the entire command group to gain the requirements of a single command - a good solution is to "fork off" from the command group and schedule that command separately.
The ProxyScheduleCommand
class (Java, C++) schedules a specified command, and does not end until that command ends:
java
// Schedules commandToSchedule when run, does not end until commandToSchedule is no longer scheduled new ProxyScheduleCommand(commandToSchedule)
c++
// Schedules commandToSchedule when run, does not end until commandToSchedule is no longer scheduled frc2::ProxyScheduleCommand(&commandToSchedule)
This is often useful for "forking off" from command groups: by default, commands in command groups are run through the command group, and are never themselves seen by the scheduler. Accordingly, their requirements are added to the group's requirements. While this is usually fine, sometimes it is undesirable for the entire command group to gain the requirements of a single command - a good solution is to "fork off" from the command group and schedule the command separately.
The WaitCommand
class (Java, C++) does nothing, and ends after a specified period of time elapses after its initial scheduling:
java
// Ends 5 seconds after being scheduled new WaitCommand(5)
c++
// Ends 5 seconds after being scheduled frc2::WaitCommand(5.0_s)
This is often useful as a component of a command group.
WaitCommand
can also be subclassed to create a more complicated command that runs for a period of time. If WaitCommand
is used in this method, the user must ensure that the WaitCommand
's Initialize
, End
, and IsFinished
methods are still called in order for the WaitCommand's timer to work.
Warning
The match timer used by WaitUntilCommand does not provide an official match time! While it is fairly accurate, use of this timer can not guarantee the legality of your robot's actions.
The WaitUntilCommand
class (Java, C++) does nothing, and ends once a specified condition becomes true, or until a specified match time passes.
java
// Ends after the 60-second mark of the current match new WaitUntilCommand(60)
// Ends after m_limitSwitch.get() returns true new WaitUntilCommand(m_limitSwitch::get)
c++
// Ends after the 60-second mark of the current match frc2::WaitUntilCommand(60.0_s)
// Ends after m_limitSwitch.Get() returns true frc2::WaitUntilCommand([&m_limitSwitch] { return m_limitSwitch.Get(); })
The PerpetualCommand
class (Java, C++) runs a given command with its end condition removed, so that it runs forever (unless externally interrupted):
java
// Will run commandToRunForever perpetually, even if its isFinished() method returns true new PerpetualCommand(commandToRunForever)
c++
// Will run commandToRunForever perpetually, even if its isFinished() method returns true frc2::PerpetualCommand(commandToRunForever)
The Command
interface contains a number of defaulted "decorator" methods which can be used to add additional functionality to existing commands. A "decorator" method is a method that takes an object (in this case, a command) and returns an object of the same type (i.e. a command) with some additional functionality added to it. A list of the included decorator methods with brief examples is included below - for rigorous documentation, see the API docs (Java, C++).
The withTimeout()
decorator (Java, C++) adds a timeout to a command. The decorated command will be interrupted if the timeout expires:
java
// Will time out 5 seconds after being scheduled, and be interrupted button.whenPressed(command.withTimeout(5));
c++
// Will time out 5 seconds after being scheduled, and be interrupted button.WhenPressed(command.WithTimeout(5.0_s));
The until()
(Java, C++) decorator adds a condition on which the command will be interrupted:
java
// Will be interrupted if m_limitSwitch.get() returns true button.whenPressed(command.until(m_limitSwitch::get));
c++
// Will be interrupted if m_limitSwitch.get() returns true button.WhenPressed(command.Until([&m_limitSwitch] { return m_limitSwitch.Get(); }));
withInterrupt()
is an alias for until()
.
The andThen()
decorator (Java, C++) adds a method to be executed after the command ends:
java
// Will print "hello" after ending button.whenPressed(command.andThen(() -> System.out.println("hello")));
c++
// Will print "hello" after ending button.WhenPressed(command.AndThen([] { std::cout << "hello"; }));
The beforeStarting()
decorator (Java, C++) adds a method to be executed before the command starts:
java
// Will print "hello" before starting button.whenPressed(command.beforeStarting(() -> System.out.println("hello")));
c++
// Will print "hello" before starting button.WhenPressed(command.BeforeStarting([] { std::cout << "hello"; }));
Note
This decorator is not supported in C++ due to technical constraints - users should simply construct a parallel command group the ordinary way instead.
The alongWith()
decorator returns a parallel command group <docs/software/commandbased/command-groups:ParallelCommandGroup>
. All commands will execute at the same time and each will end independently of each other:
// Will be a parallel command group that ends after three seconds with all three commands running their full duration.
button.whenPressed(oneSecCommand.alongWith(twoSecCommand, threeSecCommand));
Note
This decorator is not supported in C++ due to technical constraints - users should simply construct a parallel race group the ordinary way instead.
The raceWith()
decorator returns a parallel race group <docs/software/commandbased/command-groups:ParallelRaceGroup>
that ends as soon as the first command ends. At this point all others are interrupted. It doesn't matter which command is the calling command:
// Will be a parallel race group that ends after one second with the two and three second commands getting interrupted.
button.whenPressed(twoSecCommand.raceWith(oneSecCommand, threeSecCommand));
Note
This decorator is not supported in C++ due to technical constraints - users should simply construct a parallel deadline group the ordinary way instead.
The deadlineWith()
decorator returns a parallel deadline group <docs/software/commandbased/command-groups:ParallelDeadlineGroup>
with the calling command being the deadline. When this deadline command ends it will interrupt any others that are not finished:
// Will be a parallel deadline group that ends after two seconds (the deadline) with the three second command getting interrupted (one second command already finished).
button.whenPressed(twoSecCommand.deadlineWith(oneSecCommand, threeSecCommand));
Note
This decorator is not supported in C++ due to technical constraints - users should set the name of the command inside their command class instead.
The withName()
decorator adds a name to a command. This name will appear on a dashboard when the command is sent via the sendable interface.
// This command will be called "My Command".
var command = new PrintCommand("Hello robot!").withName("My Command");
The perpetually()
decorator (Java, C++) removes the end condition of a command, so that it runs forever.
java
// Will run forever unless externally interrupted, regardless of command.isFinished() button.whenPressed(command.perpetually());
c++
// Will run forever unless externally interrupted, regardless of command.isFinished() button.WhenPressed(command.Perpetually());
Remember that decorators, like all command groups, can be composed! This allows very powerful and concise inline expressions:
// Will run fooCommand, and then a race between barCommand and bazCommand
button.whenPressed(fooCommand.andThen(barCommand.raceWith(bazCommand)));
Note
These factory methods are not included in the C++ command library, as the reduction in verbosity would be minimal - C++ commands should be stack-allocated, removing the need for the new
keyword.
If users do not wish to use the andThen
, alongWith
, raceWith
, and deadlineWith
decorators for declaring command groups, but still wish to reduce verbosity compared to calling the constructors, the CommandGroupBase
class contains four static factory methods for declaring command groups: sequence()
, parallel()
, race()
, and deadline()
. When used from within a command group subclass or in combination with import static
, these become extremely concise and greatly aid in command composition:
public class ExampleSequence extends SequentialCommandGroup {
// Will run a FooCommand, and then a race between a BarCommand and a BazCommand
public ExampleSequence() {
addCommands(
new FooCommand(),
race(
new BarCommand(),
new BazCommand()
)
);
}
}