// Copyright (c) FIRST and other WPILib contributors. // Open Source Software; you can modify and/or share it under the terms of // the WPILib BSD license file in the root directory of this project. package org.wpilib.command3; import static org.wpilib.util.ErrorMessages.requireNonNullParam; import java.util.Collection; import java.util.Collections; import java.util.Set; import java.util.function.BooleanSupplier; import java.util.function.Consumer; import org.wpilib.annotation.NoDiscard; import org.wpilib.units.measure.Time; /** * Performs some task using one or more {@link Mechanism mechanisms}. Commands are fundamentally * backed by a {@link Coroutine} that can be used to write imperative code that runs asynchronously. * *
Programmers familiar with the earlier versions of the command framework can think of a v3 * command similar to a v1 or v2 command that executes the lifecycle methods in a single method, as * demonstrated in the example below. (Note, however, that more sophisticated code than this is * possible! * *
{@code
* coroutine -> {
* initialize();
* while (!isFinished()) {
* execute();
* coroutine.yield(); // be sure to yield at the end of the loop
* }
* end();
* }
* }
*
* Note: Because coroutines are opt-in collaborate constructs, every * command implementation must call {@link Coroutine#yield()} within any periodic * loop. Failure to do so may result in an unrecoverable infinite loop. * *
Commands require zero or more mechanisms. To prevent conflicting control requests from running * simultaneously (for example, commanding an elevator to both raise and lower at the same time), a * running command has exclusive ownership of all of its required mechanisms. If another * command with an equal or greater {@link #priority()} is scheduled that requires one or more of * those same mechanisms, it will interrupt and cancel the running command. * *
The recommended way to create a command is using {@link Mechanism#run(Consumer)} or a related * factory method to create commands that require a single mechanism (for example, a command that * drives an elevator up and down or rotates an arm). Commands may be composed into {@link * ParallelGroup parallel groups} and {@link SequentialGroup sequences} to build more complex * behavior out of fundamental building blocks. These built-in compositions will require every * mechanism used by every command in them, even if those commands aren't always running, and thus * can leave certain required mechanisms in an uncommanded state: owned, but not used, this * can lead to mechanisms sagging under gravity or running the previous motor control request they * were given. * *
For example, a hypothetical drive-and-score sequence could be coded in two ways: one with a * sequence chain, or one that uses the lower-level coroutine API. Coroutines can be used in an * async/await style that you may be familiar with from languages like JavaScript, Python, or C# * (note that there is no {@code async} keyword; commands are inherently asynchronous). Nested * commands may be forked and awaited, but will not outlive the command that forked them; there is * no analog for something like a {@code ScheduleCommand} from the v2 commands framework. * *
{@code
* class Robot extends TimedRobot {
* private final Drivetrain drivetrain = new Drivetrain();
* private final Elevator elevator = new Elevator();
* private final Gripper gripper = new Gripper();
*
* // Basic sequence builder - owns all 3 mechanisms for the full duration,
* // even when they're not being used
* private Command basicScoringSequence() {
* return drivetrain.driveToScoringLocation()
* .andThen(elevator.moveToScoringHeight())
* .andThen(gripper.release())
* .named("Scoring Sequence (Basic)");
* }
*
* // Advanced sequence with async/await - only owns mechanisms while they're
* // being used, allowing default commands or other user-triggered commands
* // to run when not in use. Interrupting one of the inner commands while it's
* // running will cancel the entire sequence.
* private Command advancedScoringSequence() {
* return Command.noRequirements().executing(coroutine -> {
* coroutine.await(drivetrain.driveToScoringLocation());
* coroutine.await(elevator.moveToScoringHeight());
* coroutine.await(gripper.release());
* }).named("Scoring Sequence (Advanced)");
* }
* }
* }
*/
@NoDiscard("Commands must be used! Did you mean to fork it or bind it to a trigger?")
public interface Command {
/** The default command priority. */
int DEFAULT_PRIORITY = 0;
/**
* The lowest possible command priority. Commands with the lowest priority can be interrupted by
* any other command, including other minimum-priority commands.
*/
int LOWEST_PRIORITY = Integer.MIN_VALUE;
/**
* The highest possible command priority. Commands with the highest priority can only be
* interrupted by other maximum-priority commands.
*/
int HIGHEST_PRIORITY = Integer.MAX_VALUE;
/**
* Runs the command. Commands that need to periodically run until a goal state is reached should
* simply run a while loop like {@code while (!atGoal()) { ... } } and call {@link
* Coroutine#yield()} at the end of the loop.
*
* Warning: any loops in a command must call {@code coroutine.yield()}.
* Failure to do so will prevent anything else in your robot code from running. Commands are
* opt-in collaborative constructs; don't be greedy!
*
* @param coroutine the coroutine backing the command's execution
*/
void run(Coroutine coroutine);
/**
* An optional lifecycle hook that can be implemented to execute some code whenever this command
* is canceled before it naturally completes. Commands should be careful to do a single-shot
* cleanup (for example, setting a motor to zero volts) and not do any complex looping logic here.
*/
default void onCancel() {
// NOP by default
}
/**
* The name of the command.
*
* @return the name of the command
*/
@NoDiscard
String name();
/**
* The mechanisms required by the command. This is used by the scheduler to determine if two
* commands conflict with each other. No mechanism may be required by more than one running
* command at a time.
*
* @return the set of mechanisms required by the command
*/
@NoDiscard
Set More configuration options are needed after calling this function before the command can be
* created. See {@link StagedCommandBuilder} for details.
*
* @return a builder that can be used to configure the resulting command
*/
static NeedsExecutionBuilderStage noRequirements() {
return new StagedCommandBuilder().noRequirements();
}
/**
* Creates a command that requires one or more mechanisms.
*
* More configuration options are needed after calling this function before the command can be
* created. See {@link StagedCommandBuilder} for details.
*
* @param requirement The first required mechanism
* @param rest Any other required mechanisms
* @return A command builder
*/
static NeedsExecutionBuilderStage requiring(Mechanism requirement, Mechanism... rest) {
// parameters will be null checked by the builder
return new StagedCommandBuilder().requiring(requirement, rest);
}
/**
* Creates command that requires some number of mechanisms.
*
* More configuration options are needed after calling this function before the command can be
* created. See {@link StagedCommandBuilder} for details.
*
* @param requirements The required mechanisms. May be empty, but cannot contain null values.
* @return A command builder
*/
static NeedsExecutionBuilderStage requiring(Collection More configuration options are needed after calling this function before the command can be
* created. See {@link ParallelGroupBuilder} for details.
*
* @param commands The commands to run in parallel
* @return A command builder
*/
static ParallelGroupBuilder parallel(Command... commands) {
// parameters will be null checked by the builder
return new ParallelGroupBuilder().requiring(commands);
}
/**
* Starts creating a command that runs a group of multiple commands in parallel. The command will
* complete when any command in the group has completed naturally; all other commands in the group
* will be canceled.
*
* More configuration options are needed after calling this function before the command can be
* created. See {@link ParallelGroupBuilder} for details.
*
* @param commands The commands to run in parallel
* @return A command builder
*/
static ParallelGroupBuilder race(Command... commands) {
// parameters will be null checked by the builder
return new ParallelGroupBuilder().optional(commands);
}
/**
* Starts creating a command that runs a group of multiple commands in sequence. The command will
* complete when the last command in the group has completed naturally. Commands in the group will
* run in the order they're passed to this method.
*
* More configuration options are needed after calling this function before the command can be
* created. See {@link SequentialGroupBuilder} for details.
*
* @param commands The commands to run in sequence.
* @return A command builder
*/
static SequentialGroupBuilder sequence(Command... commands) {
// parameters will be null checked by the builder
return new SequentialGroupBuilder().andThen(commands);
}
/**
* Starts creating a command that simply waits for some condition to be met. The command will not
* require any mechanisms.
*
* More configuration options are needed after calling this function before the command can be
* created. See {@link StagedCommandBuilder} for details.
*
* @param condition The condition to wait for
* @return A command builder
*/
static NeedsNameBuilderStage waitUntil(BooleanSupplier condition) {
requireNonNullParam(condition, "condition", "Command.waitUntil");
return noRequirements().executing(coroutine -> coroutine.waitUntil(condition));
}
/**
* Creates a command that simply waits for a given duration. The command will not require any
* mechanisms.
*
* @param duration How long to wait
* @return A command builder
*/
static NeedsNameBuilderStage waitFor(Time duration) {
requireNonNullParam(duration, "duration", "Command.waitFor");
return noRequirements().executing(coroutine -> coroutine.wait(duration));
}
/**
* Creates a command that runs this one and ends when the end condition is met (if this command
* has not already exited by then).
*
* More configuration options are needed after calling this function before the command can be
* created. See {@link ParallelGroupBuilder} for details.
*
* @param endCondition The end condition to wait for.
* @return The waiting command
*/
default ParallelGroupBuilder until(BooleanSupplier endCondition) {
requireNonNullParam(endCondition, "endCondition", "Command.until");
return new ParallelGroupBuilder()
.optional(this, Command.waitUntil(endCondition).named("Until Condition"));
}
/**
* Creates a command sequence, starting from this command and then running the next one. More
* commands can be added with the builder before naming and creating the sequence.
*
* More configuration options are needed after calling this function before the command can be
* created. See {@link ParallelGroupBuilder} for details.
*
* More configuration options are needed after calling this function before the command can be
* created. See {@link ParallelGroupBuilder} for details.
*
* @param parallel The commands to run in parallel with this one
* @return A parallel group builder
*/
default ParallelGroupBuilder raceWith(Command... parallel) {
return new ParallelGroupBuilder().optional(this).optional(parallel);
}
}
{@code
* Sequence aThenBThenC =
* commandA()
* .andThen(commandB())
* .andThen(commandC())
* .withAutomaticName();
* }
*
* @param next The command to run after this one in the sequence
* @return A sequence builder
*/
default SequentialGroupBuilder andThen(Command next) {
// parameter will be null checked by the builder
return new SequentialGroupBuilder().andThen(this).andThen(next);
}
/**
* Creates a parallel command group, running this command alongside one or more other commands.
* The group will exit once every command has finished.
*
* {@code
* ParallelGroup abc =
* commandA()
* .alongWith(commandB(), commandC())
* .withAutomaticName();
* }
*
* @param parallel The commands to run in parallel with this one
* @return A parallel group builder
*/
default ParallelGroupBuilder alongWith(Command... parallel) {
return new ParallelGroupBuilder().requiring(this).requiring(parallel);
}
/**
* Creates a parallel command group, running this command alongside one or more other commands.
* The group will exit after any command finishes.
*
*