Event-Based Programming With EventLoop

Many operations in robot code are driven by certain conditions; buttons are one common example. Conditions can be polled imperatively with an if statement in a periodic method. As an alternative, WPILib offers an event-based API in the shape of the EventLoop and BooleanEvent classes.

Nota

The example code here is taken from the EventLoop example project (Java/C++).

EventLoop

The EventLoop class is a «container» for pairs of conditions and actions, which can be polled using the poll()/Poll() method. When polled, every condition will be queried and if it returns true the action associated with the condition will be executed.

  private final EventLoop m_loop = new EventLoop();
  @Override
  public void robotPeriodic() {
    // poll all the bindings
    m_loop.poll();
  }

Advertencia

The EventLoop’s poll() method should be called consistently in a *Periodic() method. Failure to do this will result in unintended loop behavior.

BooleanEvent

The BooleanEvent class represents a boolean condition: a BooleanSupplier (Java) / std::function<bool()> (C++).

To bind a callback action to the condition, use ifHigh()/IfHigh():

    BooleanEvent atTargetVelocity =
        new BooleanEvent(m_loop, m_controller::atSetpoint)
            // debounce for more stability
            .debounce(0.2);

    // if we're at the target velocity, kick the ball into the shooter wheel
    atTargetVelocity.ifHigh(() -> m_kicker.set(0.7));

Remember that button binding is declarative: bindings only need to be declared once, ideally some time during robot initialization. The library handles everything else.

Composing Conditions

BooleanEvent objects can be composed to create composite conditions. In C++ this is done using operators when applicable, other cases and all compositions in Java are done using methods.

and() / &&

The and()/&& composes two BooleanEvent conditions into a third condition that returns true only when both of the conditions return true.

    // if the thumb button is held
    intakeButton
        // and there is not a ball at the kicker
        .and(isBallAtKicker.negate())
        // activate the intake
        .ifHigh(() -> m_intake.set(0.5));

or() / ||

The or()/|| composes two BooleanEvent conditions into a third condition that returns true only when either of the conditions return true.

    // if the thumb button is not held
    intakeButton
        // or there is a ball in the kicker
        .or(isBallAtKicker)
        // stop the intake
        .ifHigh(m_intake::stopMotor);

negate() / !

The negate()/! composes one BooleanEvent condition into another condition that returns the opposite of what the original conditional did.

        // and there is not a ball at the kicker
        .and(isBallAtKicker.negate())

debounce() / Debounce()

To avoid rapid repeated activation, conditions (especially those originating from digital inputs) can be debounced with the WPILib Debouncer class using the debounce method:

    BooleanEvent atTargetVelocity =
        new BooleanEvent(m_loop, m_controller::atSetpoint)
            // debounce for more stability
            .debounce(0.2);

rising(), falling()

Often times it is desired to bind an action not to the current state of a condition, but instead to when that state changes. For example, binding an action to when a button is newly pressed as opposed to when it is held. This is what the rising() and falling() decorators do: rising() will return a condition that is true only when the original condition returned true in the current polling and false in the previous polling; falling() returns a condition that returns true only on a transition from true to false.

Advertencia

Due to the «memory» these conditions have, do not use the same instance in multiple places.

    // when we stop being at the target velocity, it means the ball was shot
    atTargetVelocity
        .falling()
        // so stop the kicker
        .ifHigh(m_kicker::stopMotor);

Downcasting BooleanEvent Objects

To convert BooleanEvent objects to other types, most commonly the Trigger subclass used for binding commands to conditions, the generic castTo()/CastTo() decorator exists:

Trigger trigger = booleanEvent.castTo(Trigger::new);

Nota

In Java, the parameter expects a method reference to a constructor accepting an EventLoop instance and a BooleanSupplier. Due to the lack of method references, this parameter is defaulted in C++ as long as a constructor of the form Type(frc::EventLoop*, std::function<bool()>) exists.