Modules

A Module is an OpMode-shaped unit of behavior. It has the same lifecycle hooks as an OpMode and is installed into a ModularOpMode, which drives it for you — a clean way to package cross-cutting robot behavior and reuse it across OpModes.

What a Module is

The abstract Module class mirrors the FTC OpMode lifecycle exactly, with five hooks:

HookCalled
void init()once, when the OpMode initializes.
void init_loop()repeatedly during init, after init() and before start(), while the driver is on the init screen.
void start()once, when the OpMode starts (play pressed).
void loop()every loop.
void stop()once, when the OpMode ends.

You install a module into a ModularOpMode with installModule(module), and from then on the OpMode calls each of the module's hooks at the matching point in its own lifecycle. You never invoke the hooks yourself.

Modules vs. Subsystems. Both are driven by the OpMode, but they answer different questions. A Subsystem wraps a single mechanism (an arm, an intake) and exposes commands for it. A Module is a whole OpMode-shaped behavior — typically something cross-cutting, like the entire TeleOp drive loop or a binding-triggered action — rather than one piece of hardware. Reach for a Subsystem to model a mechanism; reach for a Module to package program logic you want to drop into several OpModes.

Built-in modules

abstract

Module

The base class. Extend it and implement the five lifecycle hooks to write your own reusable behavior.

no-op

NullModule

A module whose hooks all do nothing. A safe placeholder or default where a module is required but no behavior is wanted.

BindingModule

Runs an action whenever a binding fires, edge-wise — once each time the binding transitions from inactive to active. The constructor takes a BindingManager, a Binding, and a Runnable; it registers the binding with the manager immediately on construction (via bindingManager.replaceBinding(binding)) and remembers the binding's id. Every loop() it reads bindingManager.checkBinding(id) and, on each rising edge, runs the action once.

installModule(new BindingModule(
    bindingManager,
    new Binding("shoot", gamepad1::a),
    () -> shooter.fire()));   // runs once each time A is newly pressed

LambdaModule

Builds a module from four lambdas — init, start, loop, stop — for quick inline behavior without declaring a dedicated class. (init_loop is a no-op for this module.)

installModule(new LambdaModule(
    () -> telemetry.addLine("init"),   // init
    () -> timer.reset(),               // start
    () -> telemetry.update(),          // loop
    () -> {}));                        // stop

Pedro 3.0 TeleOpPedroModule

A ready-made driving module that runs a full field/robot TeleOp drive loop through a PedroPathing Follower. It reads four BindingManager inputs — the analog ids "drive", "strafe", and "turn", plus the boolean "slowMode" — and drives the follower every loop.

Constructors take a Follower, a starting Pose, a BindingManager, and an optional slow-mode multiplier (default 1). Each constructor calls follower.setPose(startingPose) to seed the robot's pose.

TeleOpPedroModule(Follower follower, Pose startingPose, BindingManager bindingManager)
TeleOpPedroModule(Follower follower, Pose startingPose, BindingManager bindingManager,
                  double slowModeMultiplier)

Wire up the inputs with setBindings(...), which registers the four ids for you:

void setBindings(DoubleSupplier drive, DoubleSupplier strafe,
                 DoubleSupplier turn, BooleanSupplier slowMode)

Behavior across the lifecycle:

  • init() calls follower.update() once.
  • loop() calls follower.manual(drive, strafe, turn) each loop; when the "slowMode" binding is active, drive and strafe are scaled by the slow-mode multiplier (turn is left unscaled).
Pedro 3 driving. There is no separate startTeleopDrive() call — invoking follower.manual(...) each loop puts the follower into MANUAL mode on its own. The drive powers are applied by follower.update(), which must run every loop (the OpMode's follower update covers this).

Installing a drive module with bindings

Install the drive module alongside your drivetrain and wire the required bindings:

public class TeleOp extends ModularOpMode {
    @Override
    public void initialize() {
        Follower follower = Constants.createFollower(hardwareMap);
        Pose start = new Pose(0, 0, 0);

        TeleOpPedroModule drive =
            new TeleOpPedroModule(follower, start, bindingManager, 0.4); // 40% in slow mode

        drive.setBindings(
            () -> -gamepad1.left_stick_y,   // "drive"
            () ->  gamepad1.left_stick_x,   // "strafe"
            () ->  gamepad1.right_stick_x,  // "turn"
            () ->  gamepad1.right_bumper);  // "slowMode"

        installModule(drive);
    }
}

Holding the right bumper scales drive and strafe to 40% for fine positioning; releasing it returns to full speed.

Pedro 3.0 API. TeleOpPedroModule uses com.pedropathing.follower.Follower, com.pedropathing.math.Pose, follower.setPose(...), follower.manual(...), and follower.update(). These replace the pre-3.0 TeleOp-drive APIs and are a breaking change from older PedroPathing versions.