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 (Lesson 25.3's Step 2) and schedules 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);
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());
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."
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(new ExampleCommand());
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:
exampleButton
.onTrue(new FooCommand()) // runs when pressed
.onFalse(new BarCommand()); // 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:
exampleButton.debounce(0.1).onTrue(new ExampleCommand()); // ignores flickers under 0.1s
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.
Key Takeaways
- A
Triggerwraps any boolean condition (a button, a sensor, an arbitrary check) so a command can be bound to it once, declaratively. onTrue/onFalsefire once on a transition;whileTrue/whileFalseschedule for as long as the condition holds and cancel 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(...)filters out rapid flickering near a condition's trip point.
Derived from other-reference-repo: wpilib/command-based/05-binding-commands-to-triggers.md (WPILIB 20.5)
Deck context: mechacoder-test/src/lessons/java-2.js, slide 12 ("Event Loops & Triggers")