Comandos

Los comandos son máquinas de estado simples que realizan funciones de robot de alto nivel utilizando los métodos definidos por los subsistemas. Los comandos pueden estar inactivos, en los que no hacen nada, o programados, en los que el programador ejecutará un conjunto específico del código del comando dependiendo del estado del comando. El CommandScheduler reconoce los comandos programados como si estuvieran en uno de tres estados: inicializando, ejecutando o finalizando. Los comandos especifican lo que se hace en cada uno de estos estados a través de los métodos initialize(), execute() and end(). Los comandos se representan en la biblioteca basada en comandos mediante la interfaz ``Command``(Java, C++).

Crear comandos

Nota

En la API de C ++, se utiliza un CRTP <https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern> __ para permitir que ciertos métodos de comando funcionen con el modelo de propiedad de objetos. Los usuarios deben * siempre * extender la clase CommandHelper https://github.com/wpilibsuite/allwpilib/blob/main/wpilibNewCommands/src/main/native/include/frc2/command/CommandHelper.h cuando definen sus propias clases de comando, como se muestra a continuación.

De manera similar a los subsistemas, el método recomendado para que la mayoría de los usuarios creen un comando es subclasificar la clase abstracta CommandBase (Java <https://first.wpi.edu/wpilib/allwpilib/docs/release/java/edu/wpi/first/wpilibj2/command/CommandBase.html> __, C ++ <https://first.wpi.edu/wpilib/allwpilib/docs/release/cpp/classfrc2_1_1CommandBase.html> __), como se ve en la plantilla basada en comandos (Java <https://github.com/wpilibsuite/allwpilib/blob/main/wpilibjExamples/src/main/java/edu/wpi/first/wpilibj/templates/commandbased/commands/ExampleCommand.java> `__, C ++ <https://github.com/wpilibsuite/allwpilib/blob/main/wpilibcExamples/src/main/cpp/templates/commandbased/include/commands/ExampleCommand.h> `__):

10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import edu.wpi.first.wpilibj.templates.commandbased.subsystems.ExampleSubsystem;
import edu.wpi.first.wpilibj2.command.CommandBase;

/**
 * An example command that uses an example subsystem.
 */
public class ExampleCommand extends CommandBase {
  @SuppressWarnings({"PMD.UnusedPrivateField", "PMD.SingularField"})
  private final ExampleSubsystem m_subsystem;

  /**
   * Creates a new ExampleCommand.
   *
   * @param subsystem The subsystem used by this command.
   */
  public ExampleCommand(ExampleSubsystem subsystem) {
    m_subsystem = subsystem;
    // Use addRequirements() here to declare subsystem dependencies.
    addRequirements(subsystem);
  }

Como antes, contiene varias funciones de conveniencia. Anula automáticamente el método getRequirements() para los usuarios, devolviendo una lista de requisitos que está vacía de forma predeterminada, pero que se puede agregar con el método addRequirements(). También implementa la interfaz Sendable y, por lo tanto, se puede enviar a la dashboard; esto proporciona una forma práctica de programar comandos para pruebas (a través de un botón en la dashboard) sin necesidad de vincularlos a botones de un controlador.

Además, como antes, los usuarios avanzados que buscan más flexibilidad son libres de simplemente crear su propia clase implementando la interfaz de Comando.

La estructura de un comando

Si bien los subsistemas son bastante libres y, en general, pueden verse como lo que el usuario desee, los comandos son un poco más restringidos. El código de comando debe especificar qué hará el comando en cada uno de sus posibles estados. Esto se hace anulando los métodos initialize(), execute() y end(). Además, un comando debe poder decirle al programador cuándo (si alguna vez) ha finalizado la ejecución; esto se hace anulando el método isFinished(). Todos estos métodos están predeterminados para reducir el desorden en el código de usuario: initialize(), execute() y end() están predeterminados para simplemente no hacer nada, mientras que isFinished() está predeterminado para devolver falso (lo que da como resultado un comando que nunca termina).

Inicialización

El método initialize() (Java, C++) se ejecuta exactamente una vez que un comando es programado, como parte del método schedule() del programador de comandos. El método run() del programador no necesita ser llamado para que el método initialize() se ejecute. El bloque de inicializar debería ser usado para acomodar el comando en un estado de inicio conocido para su ejecución. También es útil para realizar tareas que solo necesitan ser realizadas una vez por el numero de veces programada, como configurar motores a funcionar a una velocidad constante o establecer el estado de un actuador solenoide.

Ejecución

El método execute() (Java, C++) se llama repetidamente mientras el comando está programado, siempre que se llama al método run() del programador (esto generalmente se hace en el método periódico del robot principal, que se ejecuta cada 20 ms por defecto). El bloque de ejecución debe usarse para cualquier tarea que deba realizarse continuamente mientras se programa el comando, como actualizar las salidas del motor para que coincidan con las entradas del joystick o usar la salida de un bucle de control.

Finalizando

El método end() (Java, C++) se llama una vez cuando finaliza el comando, ya sea que termine normalmente (es decir,  isFinished() devolvió verdadero) o fue interrumpido (ya sea por otro comando o por ser cancelado explícitamente). El argumento del método especifica la manera en que terminó el comando; los usuarios pueden usar esto para diferenciar el comportamiento de su comando en consecuencia. El bloque final debe usarse para «concluir» el estado de comando de una manera ordenada, como volver a poner los motores a cero o revertir un actuador de solenoide a un estado «predeterminado».

Especificar condiciones finales

El método isFinished() (Java <https://first.wpi.edu/wpilib/allwpilib/docs/release/java/edu/wpi/first/wpilibj2/command/Command.html#end(boolean)> __, C ++ <https://first.wpi.edu/wpilib/allwpilib/docs/release/cpp/classfrc2_1_1Command.html#af5e8c12152d195a4f3c06789366aac88> __) se llama repetidamente mientras se programa el comando, siempre que se llama al método run() del programador. Tan pronto como devuelve verdadero, se llama al método end() del comando y no está programado. El método isFinished() se llama después del método execute (), por lo que el comando se ejecutará una vez en la misma iteración que no está programado.

Ejemplo de comando simple

¿Cómo sería un comando funcional en la práctica? Como antes, a continuación se muestra un comando simple del proyecto de ejemplo HatchBot (Java <https://github.com/wpilibsuite/allwpilib/tree/main/wpilibjExamples/src/main/java/edu/wpi/first/wpilibj/examples/hatchbottraditional> __, C ++ <https://github.com/wpilibsuite/allwpilib/tree/main/wpilibcExamples/src/main/cpp/examples/HatchbotTraditional> __) que usa el HatchSubsystem presentado en la sección anterior:

 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package edu.wpi.first.wpilibj.examples.hatchbottraditional.commands;

import edu.wpi.first.wpilibj2.command.CommandBase;

import edu.wpi.first.wpilibj.examples.hatchbottraditional.subsystems.HatchSubsystem;

/**
 * A simple command that grabs a hatch with the {@link HatchSubsystem}.  Written explicitly for
 * pedagogical purposes.  Actual code should inline a command this simple with {@link
 * edu.wpi.first.wpilibj2.command.InstantCommand}.
 */
public class GrabHatch extends CommandBase {
  // The subsystem the command runs on
  private final HatchSubsystem m_hatchSubsystem;

  public GrabHatch(HatchSubsystem subsystem) {
    m_hatchSubsystem = subsystem;
    addRequirements(m_hatchSubsystem);
  }

  @Override
  public void initialize() {
    m_hatchSubsystem.grabHatch();
  }

  @Override
  public boolean isFinished() {
    return true;
  }
}

Observe que el subsistema hatch utilizado por el comando se pasa al comando a través del constructor del comando. Este es un patrón llamado inyección de dependencia <https://en.wikipedia.org/wiki/Dependency_injection> __, y permite a los usuarios evitar declarar sus subsistemas como variables globales. Esto es ampliamente aceptado como una mejor práctica - el razonamiento detrás de esto se discute en: doc: later section <structuring-command-based-project>.

Observe también que el comando anterior llama al método del subsistema una vez desde la inicialización, y luego finaliza inmediatamente (ya que isFinished() simplemente devuelve verdadero). Esto es típico de los comandos que alternan los estados de los subsistemas y, de hecho, la biblioteca basada en comandos incluye código para hacer :ref: comandos como este <docs/software/commandbased/convenience-features:InstantCommand> aún más sucintamente.

¿Qué tal un caso más complicado? A continuación se muestra un comando de unidad, del mismo proyecto de ejemplo:

 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package edu.wpi.first.wpilibj.examples.hatchbottraditional.commands;

import java.util.function.DoubleSupplier;

import edu.wpi.first.wpilibj2.command.CommandBase;

import edu.wpi.first.wpilibj.examples.hatchbottraditional.subsystems.DriveSubsystem;

/**
 * A command to drive the robot with joystick input (passed in as {@link DoubleSupplier}s). Written
 * explicitly for pedagogical purposes - actual code should inline a command this simple with {@link
 * edu.wpi.first.wpilibj2.command.RunCommand}.
 */
public class DefaultDrive extends CommandBase {
  private final DriveSubsystem m_drive;
  private final DoubleSupplier m_forward;
  private final DoubleSupplier m_rotation;

  /**
   * Creates a new DefaultDrive.
   *
   * @param subsystem The drive subsystem this command wil run on.
   * @param forward The control input for driving forwards/backwards
   * @param rotation The control input for turning
   */
  public DefaultDrive(DriveSubsystem subsystem, DoubleSupplier forward, DoubleSupplier rotation) {
    m_drive = subsystem;
    m_forward = forward;
    m_rotation = rotation;
    addRequirements(m_drive);
  }

  @Override
  public void execute() {
    m_drive.arcadeDrive(m_forward.getAsDouble(), m_rotation.getAsDouble());
  }
}

Tenga en cuenta que este comando no anula isFinished() y, por lo tanto, nunca terminará; esta es la norma para los comandos que están destinados a ser utilizados como comandos predeterminados (y, como se puede adivinar, ¡la biblioteca incluye herramientas para hacer :ref: este tipo de comando <docs/software/commandbased/convenience-features:RunCommand> más fácil de escribir también!).