// 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. * *

Requirements

* *

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. * *

Advanced Usage

* *

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 requirements(); /** * The priority of the command. If a command is scheduled that conflicts with another running or * pending command, their priority values are compared to determine which should run. If the * scheduled command is lower priority than the running command, then it will not be scheduled and * the running command will continue to run. If it is the same or higher priority, then the * running command will be canceled and the scheduled command will start to run. * * @return the priority of the command */ @NoDiscard default int priority() { return DEFAULT_PRIORITY; } /** * Checks if this command has a lower {@link #priority() priority} than another command. * * @param other the command to compare with * @return true if this command has a lower priority than the other one, false otherwise */ @NoDiscard default boolean isLowerPriorityThan(Command other) { requireNonNullParam(other, "other", "Command.isLowerPriorityThan"); return priority() < other.priority(); } /** * Checks if this command requires a particular mechanism. * * @param mechanism the mechanism to check * @return true if the mechanism is required, false if not */ @NoDiscard default boolean requires(Mechanism mechanism) { return requirements().contains(mechanism); } /** * Checks if this command conflicts with another command. * * @param other the commands to check against * @return true if both commands require at least one of the same mechanism, false if both * commands have completely different requirements */ @NoDiscard default boolean conflictsWith(Command other) { requireNonNullParam(other, "other", "Command.conflictsWith"); return !Collections.disjoint(requirements(), other.requirements()); } /** * Creates a new command that runs this one for a maximum duration, after which it is forcibly * canceled. This is particularly useful for autonomous routines where you want to prevent your * entire autonomous period spent stuck on a single action because a mechanism doesn't quite reach * its setpoint (for example, spinning up a flywheel or driving to a particular location on the * field). The resulting command will have the same name as this one, with the timeout period * appended. * * @param timeout the maximum duration that the command is permitted to run. Negative or zero * values will result in the command running only once before being canceled. * @return the timed out command. */ default Command withTimeout(Time timeout) { requireNonNullParam(timeout, "timeout", "Command.withTimeout"); return race(this, waitFor(timeout).named("Timeout: " + timeout.toLongString())) .named(name() + " [" + timeout.toLongString() + " timeout]"); } /** * Creates a command that does not require any hardware; that is, it does not affect the state of * any physical objects. This is useful for commands that do some cleanup or state management, * such as resetting odometry or sensors, that you don't want to interrupt a command that's * controlling the mechanisms it affects. * *

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 requirements) { // parameters will be null checked by the builder return new StagedCommandBuilder().requiring(requirements); } /** * Starts creating a command that runs a group of multiple commands in parallel. The command will * complete when every command in the group has completed naturally. * *

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. * *

{@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. * *

More configuration options are needed after calling this function before the command can be * created. See {@link ParallelGroupBuilder} for details. * *

{@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. * *

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); } }