指令

命令是简单的状态机,使用子系统定义的方法执行高级机器人功能。命令可以是空闲的(不执行任何操作),也可以是已调度的(在调度中,调度程序将根据命令的状态执行一组特定的命令代码)。 CommandScheduler``将调度的命令识别为处于以下三种状态之一:初始化,执行或结束。命令通过``initialize()’’,``execute()’’和``end()’’方法指定在每种状态下要执行的操作。命令在基于命令的库中由“命令”接口(Java <https://first.wpi.edu/wpilib/allwpilib/docs/release/java/edu/wpi/first/wpilibj2/command/Command.html>` __,C ++)表示。

创建指令

与子系统类似,建议大多数用户创建命令的方法是将抽象的CommandBase类(Java <https://first.wpi.edu/wpilib/allwpilib/docs/release/java/edu/wpi/first/wpilibj2/command/CommandBase.html>`__,C ++)子类化,如在基于命令的模板(Java)中所见。 <https://github.com/wpilibsuite/allwpilib/blob/main/wpilibjExamples/src/main/java/edu/wpi/first/wpilibj/templates/commandbased/commands/ExampleCommand.java>`__,C ++):

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

和以前一样,它包含一些便利功能。它会自动为用户覆盖“getRequirements()”方法,返回默认情况下为空的需求列表,但可以使用“addRequirements()”方法添加到其中。它还实现了“Sendable”接口,因此可以发送到仪表板——这提供了一种方便的方式来安调度排测试指令(通过仪表板上的按钮),而无需将其绑定到控制器上的按钮。

同样,像以前一样,寻求更高灵活性的高级用户可以自由地创建自己的类来实现“Command”接口。

指令的结构

尽管子系统是相当自由的形式,并且通常看起来像用户希望它们具有的功能,但是指令的约束要多得多。指令的代码必须指定指令在每种可能的状态下将执行的操作。这是通过覆盖“initialize()”,“execute()”和“end()”方法来完成的。另外,指令必须能够告诉调度程序何时(如果有)完成执行——通过重写“isFinished()”方法来完成。所有这些方法默认情况下都是为了减少用户代码中的混乱:“initialize()”,“execute()”和“end()”默认情况下只是不执行任何操作,而“isFinished()” 默认返回false(导致一个永不停止的命令)。

初始化

每次调度命令时,initialize()方法(Java <https://first.wpi.edu/wpilib/allwpilib/docs/release/java/edu/wpi/first/wpilibj2/command/Command.html#initialize()>`__,C ++)仅运行一次,这是调度程序schedule()方法的一部分。无需调用调度程序的run()方法即可运行initialize()方法。应该使用initialize块将命令置于已知的开始状态以执行。这对于执行每个计划时间仅需要执行一次的任务(例如,将电动机设置为以恒定速度运行或设置螺线管致动器的状态)也很有用。

执行

每次调用调度程序的run()方法时,都会在调度命令时反复调用execute()方法(Java <https://first.wpi.edu/wpilib/allwpilib/docs/release/java/edu/wpi/first/wpilibj2/command/Command.html#execute()> __,C ++ <https://first.wpi.edu/wpilib/allwpilib/docs/release/cpp/classfrc2_1_1Command.html#a7d7ea1271f7dcc65c0ba3221d179b510>` __)(通常在主机器人周期性方法,默认情况下每20ms运行一次)。执行块应用于在计划命令时需要连续完成的任何任务,例如更新电动机输出以匹配操纵杆输入,或使用控制回路的输出。

结束

命令结束后,无论是否正常完成(例如isFinished()返回true)或被中断,都会调用一次end()方法(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#a134eda3756f00c667bb5415b23ee920c>`__) (通过另一个命令或被显式取消)。 method参数指定命令结束的方式。用户可以使用它来相应地区分其命令端的行为。端块应以整洁的方式“整理”命令状态,例如将电动机设置为零或将螺线管执行器恢复为“默认”状态。

指定结束条件

每当调用调度程序的run()方法时,就会在调度命令时重复调用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> __)。一旦返回true,就会调用命令的end()方法,并且该方法是非计划的。 isFinished()方法在execute()方法之后被调用,因此命令将在未计划的同一迭代中执行一次。

简单的指令示例

实际上,功能命令是什么样的?和以前一样,下面是HatchBot示例项目(Java <https://github.com/wpilibsuite/allwpilib/tree/main/wpilibjExamples/src/main/java/edu/wpi/first/wpilibj/examples/hatchbottraditional>`__,C ++)中的简单命令,该命令使用了上一节中介绍的“ HatchSubsystem”:

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

请注意,该指令所使用的图案填充子系统是通过指令的构造函数传递到指令中的。这种模式称为`dependency injection <https://en.wikipedia.org/wiki/Dependency_injection>`__,并且允许用户避免将其子系统声明为全局变量。最佳做法已被广泛接受——其背后的论证在:doc:`later section <structuring-command-based-project>`中讨论。

还要注意,以上指令从初始化调用一次子系统方法,然后立即结束(因为“isFinished()”仅返回true)。这对于切换子系统状态的命令来说是典型的,实际上基于命令的库包含使诸如此类:ref:commands like this <docs/software/commandbased/convenience-features:InstantCommand> 指令的代码更加简洁的代码。

那更复杂的情况呢?以下是来自同一示例项目的驱动指令:

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

注意,该指令不会覆盖“isFinished()”,因此它永远不会结束;这是旨在用作默认指令的指令的规范(而且,可以猜到,该库包含一些工具,这些工具也使:ref:this kind of command <docs/software/commandbased/convenience-features:RunCommand> 更加易于编写!)。