Binding Commands to Triggers
Triggers: The Declarative Glue
A Trigger wraps any boolean condition — a button, a sensor, an arbitrary check — so a command can be bound to it once, declaratively, during setup. From then on, the scheduler itself checks the condition every loop (step 3 of Lesson 25.3's scheduler loop) and queues or cancels the command as needed — no manual polling code required anywhere else.
// From a controller button
Trigger xButton = driverController.x();
// From an arbitrary condition — any BooleanSupplier (Lesson 19.1) works
Trigger loaded = new Trigger(intake::isLoaded);
In this chapter driverController is a CommandXboxController, from org.wpilib.command3.button: its methods a(), b(), x(), y(), leftBumper(), rightBumper() and so on each return a Trigger for that button. (WPILib 2027 also has a generic CommandGamepad, which names the four face buttons by position — faceDown(), faceRight(), faceLeft(), faceUp() — so the same code fits any gamepad. Those names were changed in May 2026, one sign this API is still alpha.) A Trigger is itself a BooleanSupplier, so it can be passed anywhere a condition is expected.
The Core Bindings
Each binding schedules a command in response to a specific kind of state change:
// Schedules once, the instant the trigger becomes true (e.g. the button is first pressed)
driverController.y().onTrue(intake.retractCommand());
// Schedules while the trigger is true, cancels the instant it becomes false
driverController.b().whileTrue(intake.runIntakeCommand());
// Schedules once, the instant the trigger becomes false — the mirror of onTrue
driverController.a().onFalse(shooter.stopCommand());
// Schedules while the trigger is false, cancels the instant it becomes true
loaded.whileFalse(leds.blinkCommand());
onTrue fires once on the rising edge and doesn't re-fire unless the trigger goes false and true again. whileTrue keeps a command running for as long as the condition holds, and cancels it the moment the condition ends — this is the natural fit for "run the intake while this button is held down." whileFalse is its mirror image.
Composing Triggers
Triggers combine with and(), or(), and negate(), just like boolean expressions (Ch.5):
// Only schedules when BOTH buttons are pressed together
driverController.x().and(driverController.y()).onTrue(climber.deployCommand());
Chaining Multiple Bindings on One Trigger
Binding methods return the same trigger they were called on, so several different bindings can be chained onto one trigger:
driverController.rightBumper()
.onTrue(hatch.grabCommand()) // runs when pressed
.onFalse(hatch.releaseCommand()); // runs when released
Debouncing: Filtering Out Noise
A digital sensor's raw signal can flicker rapidly right at the transition point — a debounce filters that out, requiring the condition to hold steady for a set duration before it's treated as a real change. Like every v3 duration, the time is a Time value (Lesson 25.2):
loaded.debounce(Seconds.of(0.1)).onTrue(leds.flashCommand()); // needs 0.1 s of true first
By default, debounce only filters the rising edge: the condition must stay true for 0.1 s before the trigger turns true, but a drop back to false passes through instantly. A sensor that flickers off and on can therefore still fire onTrue again after each fresh 0.1 s of true. To filter both directions, pass a debounce type from Debouncer.DebounceType (in org.wpilib.math.filter):
loaded.debounce(Seconds.of(0.1), DebounceType.BOTH).onTrue(leds.flashCommand());
More Bindings in Commands v3
toggleOnTrue(command)— each press starts the command if it's stopped, or cancels it if it's running.retryWhileTrue(command)— likewhileTrue, but restarts the command if it ends while the condition is still true.risingEdge()andfallingEdge()— new triggers that are true for exactly one loop when the condition changes; pair them withonTrue.multiPress(2, Seconds.of(0.5))— a new trigger that becomes true on a double-press within half a second.- Command-scoped bindings — a binding made inside a command's body only exists while that command runs, and anything it scheduled is canceled when that command ends. It's a clean way to say "while aiming, fire whenever the turret is on target."
Alpha note: multiPress (May 2026) and ifTrue(command) — which schedules the command on every loop the condition is true (September 2026) — are among the newest v3 additions and may still change.
Common Pitfalls
- Expecting
onTrueto re-fire while a button stays held. It only fires once, on the transition from false to true — for continuous behavior while held, usewhileTrue. - Confusing a
Trigger's boolean condition with the command it's bound to. ATriggeris just a wrapped condition; binding methods (onTrue,whileTrue, etc.) are what actually connect it to a command. - Not debouncing a noisy digital sensor. A sensor near its trip point can rapidly flicker true/false, causing repeated unwanted scheduling —
debounce(...)exists specifically for this. - Assuming the default
debouncefilters flicker in both directions. It only delays the change to true; useDebounceType.BOTHwhen a quick drop to false must also be ignored.
Key Takeaways
- A
Triggerwraps any boolean condition (a button, a sensor, an arbitrary check) so a command can be bound to it once, declaratively; controller buttons come fromCommandXboxControllermethods likea()andrightBumper(). onTrue/onFalsefire once on a transition;whileTrue/whileFalserun a command for as long as the condition holds and cancel it the moment it changes.- Triggers compose with
and(),or(),negate()— same boolean logic as Ch.5. - Binding methods return the same trigger, so multiple bindings can be chained onto one trigger.
debounce(Seconds.of(...))filters out flickering; by default only the rising edge is debounced, andDebounceType.BOTHfilters both.- v3 adds
toggleOnTrue,retryWhileTrue, edge and multi-press triggers, and command-scoped bindings.
Derived from other-reference-repo: wpilib/command-based/05-binding-commands-to-triggers.md (WPILIB 20.5); updated for Commands v3 from allwpilib main (commandsv3/…/command3/Trigger.java, button/CommandXboxController.java, button/CommandGamepad.java, wpimath/…/filter/Debouncer.java)
Deck context: mechacoder-test/src/lessons/java-2.js, slide 12 ("Event Loops & Triggers")