Liaison de commandes à des déclencheurs

Mis à part les commandes autonomes, qui sont planifiées au début de la période autonome, et les commandes par défaut, qui sont automatiquement planifiées chaque fois que leur sous-système n’est pas actuellement en cours d’utilisation, la façon la plus courante d’exécuter une commande est de la lier à un événement déclencheur, comme un bouton appuyé par un opérateur humain. Le paradigme orienté commande rend cela extrêmement facile à faire.

As mentioned earlier, command-based is a declarative programming paradigm. Accordingly, binding buttons to commands is done declaratively; the association of a button and a command is « declared » once, during robot initialization. The library then does all the hard work of checking the button state and scheduling (or canceling) the command as needed, behind-the-scenes. Users only need to worry about designing their desired UI setup - not about implementing it!

Command binding is done through the Trigger class (Java, C++).

Getting a Trigger Instance

To bind commands to conditions, we need a Trigger object. There are three ways to get a Trigger object:

HID Factories

The command-based HID classes contain factory methods returning a Trigger for a given button. CommandGenericHID has an index-based button(int) factory (Java, C++), and its subclasses CommandXboxController (Java, C++), CommandPS4Controller (Java, C++), and CommandJoystick (Java, C++) have named factory methods for each button.

CommandXboxController exampleCommandController = new CommandXboxController(1); // Creates a CommandXboxController on port 1.
Trigger xButton = exampleCommandController.x(); // Creates a new Trigger object for the `X` button on exampleCommandController
frc2::CommandXboxController exampleCommandController{1} // Creates a CommandXboxController on port 1
frc2::Trigger xButton = exampleCommandController.X() // Creates a new Trigger object for the `X` button on exampleCommandController

JoystickButton

Alternatively, the regular HID classes can be used and passed to create an instance of JoystickButton (Java, C++), a constructor-only subclass of Trigger:

XboxController exampleController = new XboxController(2); // Creates an XboxController on port 2.
Trigger yButton = new JoystickButton(exampleController, XboxController.Button.kY.value); // Creates a new JoystickButton object for the `Y` button on exampleController
frc::XboxController exampleController{2} // Creates an XboxController on port 2
frc2::JoystickButton yButton(&exampleStick, frc::XboxController::Button::kY); // Creates a new JoystickButton object for the `Y` button on exampleController

Arbitrary Triggers

While binding to HID buttons is by far the most common use case, users may want to bind commands to arbitrary triggering events. This can be done inline by passing a lambda to the constructor of Trigger:

DigitalInput limitSwitch = new DigitalInput(3); // Limit switch on DIO 3

Trigger exampleTrigger = new Trigger(limitSwitch::get);
frc::DigitalInput limitSwitch{3}; // Limit switch on DIO 3

frc2::Trigger exampleTrigger([&limitSwitch] { return limitSwitch.Get(); });

Trigger Bindings

Note

The C++ command-based library offers two overloads of each button binding method - one that takes an rvalue reference (CommandPtr&&), and one that takes a raw pointer (Command*). The rvalue overload moves ownership to the scheduler, while the raw pointer overload leaves the user responsible for the lifespan of the command object. It is recommended that users preferentially use the rvalue reference overload unless there is a specific need to retain a handle to the command in the calling code.

There are a number of bindings available for the Trigger class. All of these bindings will automatically schedule a command when a certain trigger activation event occurs - however, each binding has different specific behavior.

Trigger objects do not need to survive past the call to a binding method, so the binding methods may be simply called on a temp. Remember that button binding is declarative: bindings only need to be declared once, ideally some time during robot initialization. The library handles everything else.

Note

The Button subclass is deprecated, and usage of its binding methods should be replaced according to the respective deprecation messages in the API docs.

onTrue

This binding schedules a command when a trigger changes from false to true (or, accordingly, when a button changes is initially pressed). The command will be scheduled on the iteration when the state changes, and will not be scheduled again unless the trigger becomes false and then true again (or the button is released and then re-pressed).

52    // Move the arm to 2 radians above horizontal when the 'A' button is pressed.
53    m_driverController.a().onTrue(m_robotArm.setArmGoalCommand(2));
25  // Move the arm to 2 radians above horizontal when the 'A' button is pressed.
26  m_driverController.A().OnTrue(m_arm.SetArmGoalCommand(2_rad));

The onFalse binding is identical, only that it schedules on false instead of on true.

whileTrue

This binding schedules a command when a trigger changes from false to true (or, accordingly, when a button is initially pressed) and cancels it when the trigger becomes false again (or the button is released). The command will not be re-scheduled if it finishes while the trigger is still true. For the command to restart if it finishes while the trigger is true, wrap the command in a RepeatCommand, or use a RunCommand instead of an InstantCommand.

114    // While holding the shoulder button, drive at half speed
115    new JoystickButton(m_driverController, Button.kRightBumper.value)
116        .whileTrue(new HalveDriveSpeed(m_robotDrive));
75  // While holding the shoulder button, drive at half speed
76  frc2::JoystickButton(&m_driverController,
77                       frc::XboxController::Button::kRightBumper)
78      .WhileTrue(HalveDriveSpeed(&m_drive).ToPtr());

The whileFalse binding is identical, only that it schedules on false and cancels on true.

toggleOnTrue

This binding toggles a command, scheduling it when a trigger changes from false to true (or a button is initially pressed), and canceling it under the same condition if the command is currently running. Note that while this functionality is supported, toggles are not a highly-recommended option for user control, as they require the driver to keep track of the robot state. The preferred method is to use two buttons; one to turn on and another to turn off. Using a StartEndCommand or a ConditionalCommand is a good way to specify the commands that you want to be want to be toggled between.

myButton.toggleOnTrue(Commands.startEnd(mySubsystem::onMethod,
    mySubsystem::offMethod,
    mySubsystem));
myButton.ToggleOnTrue(frc2::cmd::StartEnd([&] { mySubsystem.OnMethod(); },
    [&] { mySubsystem.OffMethod(); },
    {&mySubsystem}));

The toggleOnFalse binding is identical, only that it toggles on false instead of on true.

Chaining Calls

It is useful to note that the command binding methods all return the trigger that they were called on, and thus can be chained to bind multiple commands to different states of the same trigger. For example:

exampleButton
    // Binds a FooCommand to be scheduled when the button is pressed
    .onTrue(new FooCommand())
    // Binds a BarCommand to be scheduled when that same button is released
    .onFalse(new BarCommand());
exampleButton
    // Binds a FooCommand to be scheduled when the button is pressed
    .OnTrue(FooCommand().ToPtr())
    // Binds a BarCommand to be scheduled when that same button is released
    .OnFalse(BarCommand().ToPtr());

Composition de déclencheurs

The Trigger class can be composed to create composite triggers through the and(), or(), and negate() methods (or, in C++, the &&, ||, and ! operators). For example:

// Binds an ExampleCommand to be scheduled when both the 'X' and 'Y' buttons of the driver gamepad are pressed
exampleCommandController.x()
    .and(exampleCommandController.y())
    .onTrue(new ExampleCommand());
// Binds an ExampleCommand to be scheduled when both the 'X' and 'Y' buttons of the driver gamepad are pressed
(exampleCommandController.X()
    && exampleCommandController.Y())
    .OnTrue(ExampleCommand().ToPtr());

Déclencheurs anti-rebonds

Pour éviter une activation répétée rapide, les déclencheurs (en particulier ceux provenant d’entrées numériques) peuvent être traités par des filtres anti-rebonds à l’aide de la classe Debouncer de la WPILIb par l’intérmédiaire de sa méthode debounce :

// debounces exampleButton with a 0.1s debounce time, rising edges only
exampleButton.debounce(0.1).onTrue(new ExampleCommand());

// debounces exampleButton with a 0.1s debounce time, both rising and falling edges
exampleButton.debounce(0.1, Debouncer.DebounceType.kBoth).onTrue(new ExampleCommand());
// debounces exampleButton with a 100ms debounce time, rising edges only
exampleButton.Debounce(100_ms).OnTrue(ExampleCommand().ToPtr());

// debounces exampleButton with a 100ms debounce time, both rising and falling edges
exampleButton.Debounce(100_ms, Debouncer::DebounceType::Both).OnTrue(ExampleCommand().ToPtr());