Event-Based Programming With EventLoop

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

Note

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();
  private final Joystick m_joystick = new Joystick(0);
  @Override
  public void robotPeriodic() {
    // poll all the bindings
    m_loop.poll();
  }
  frc::EventLoop m_loop{};
  void RobotPeriodic() override { m_loop.Poll(); }

Avertissement

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));
    frc::BooleanEvent atTargetVelocity =
        frc::BooleanEvent(
            &m_loop,
            [&controller = m_controller] { return controller.AtSetpoint(); })
            // debounce for more stability
            .Debounce(0.2_s);

    // if we're at the target velocity, kick the ball into the shooter wheel
    atTargetVelocity.IfHigh([&kicker = m_kicker] { kicker.Set(0.7); });

N’oubliez pas que la liaison des boutons est déclarative: les liaisons ne doivent être déclarées qu’une seule fois, idéalement pendant l’initialisation du robot. La librarie gère tout le reste.

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));
    // if the thumb button is held
    (intakeButton
     // and there is not a ball at the kicker
     && !isBallAtKicker)
        // activate the intake
        .IfHigh([&intake = m_intake] { 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
        .negate()
        // or there is a ball in the kicker
        .or(isBallAtKicker)
        // stop the intake
        .ifHigh(m_intake::stopMotor);
    // if the thumb button is not held
    (!intakeButton
     // or there is a ball in the kicker
     || isBallAtKicker)
        // stop the intake
        .IfHigh([&intake = m_intake] { intake.Set(0.0); });

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())
     // and there is not a ball at the kicker
     && !isBallAtKicker)

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);
    frc::BooleanEvent atTargetVelocity =
        frc::BooleanEvent(
            &m_loop,
            [&controller = m_controller] { return controller.AtSetpoint(); })
            // debounce for more stability
            .Debounce(0.2_s);

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.

Avertissement

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);
    // when we stop being at the target velocity, it means the ball was shot
    atTargetVelocity
        .Falling()
        // so stop the kicker
        .IfHigh([&kicker = m_kicker] { kicker.Set(0.0); });

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);
frc2::Trigger trigger = booleanEvent.CastTo<frc2::Trigger>();

Note

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.