Getting Started

Write your first CDALib OpMode: extend ModularOpMode, register a subsystem, and let the library run the loop.

This page assumes you have already added CDALib to your project. If not, follow Installation first. Here we build a minimal TeleOp so you can see how the pieces connect; each linked page goes deeper.

The shape of an OpMode

Every CDALib OpMode extends ModularOpMode and overrides a single method, preload(). That method is where you build the robot:

  • Register subsystems with registerSubsystem(...) — each one is then driven through its lifecycle automatically.
  • Install modules with installModule(...) for reusable OpMode-level behavior (see Modules).
  • Wire gamepad input through the protected bindingManager field (see Bindings).
  • Optionally change the Lynx bulk-caching mode with setBulkCachingMode(...).

You annotate the class with @TeleOp or @Autonomous so it shows up on the driver station, and optionally with @Photon to let PhotonFTC swap in its optimized motor/servo controllers for lower loop times.

A minimal TeleOp

The example below registers one drive subsystem. Note that preload() is the only method you must implement — ModularOpMode handles init, start, the per-loop update, and stop for you.

package org.firstinspires.ftc.teamcode;

import com.cdarobotics.cdalib.opmodes.ModularOpMode;
import com.outoftheboxrobotics.photoncore.Photon;
import com.qualcomm.robotcore.eventloop.opmode.TeleOp;

@Photon                                    // optional: enable PhotonFTC for lower loop times
@TeleOp(name = "My TeleOp (CDALib)")
public class MyTeleOp extends ModularOpMode {

    private DriveSubsystem drive;

    @Override
    protected void preload() {
        // Build the robot here. hardwareMap is available from the base OpMode.
        drive = new DriveSubsystem(hardwareMap);

        // Register it — CDALib now drives init / init_loop / start / update / stop for you.
        registerSubsystem(drive);

        // Wire gamepad input through the shared binding manager by name. Analog sticks are
        // registered with addAnalog(id, DoubleSupplier); buttons with addBinding(id, BooleanSupplier).
        // Your subsystem reads them later with bindingManager.checkAnalog(id) / checkBinding(id).
        bindingManager.addAnalog("drive",  () -> -gamepad1.left_stick_y);
        bindingManager.addAnalog("strafe", () ->  gamepad1.left_stick_x);
        bindingManager.addAnalog("turn",   () ->  gamepad1.right_stick_x);
        bindingManager.addBinding("slowMode", () -> gamepad1.right_bumper);

        // installModule(...) here to add reusable OpMode-level behavior (e.g. a Pedro TeleOp module).
    }
}
No loop() needed. You never override loop() in a typical CDALib OpMode. Put per-cycle behavior inside your subsystem's update(), or schedule it through the task system. Wiring input in preload() keeps your loop free of button-state bookkeeping — see Bindings for the exact binding methods.

The lifecycle at a glance

Once you register a subsystem or install a module, ModularOpMode calls the following on each of them, in registration order:

PhaseWhenCalled on subsystems / modules
preload()Start of INIT, onceYour hook — register & install here.
init()On INIT, after preload()Subsystem.init() / Module.init()
init_loop()Repeatedly on the init screenSubsystem.init_loop() / Module.init_loop()
start()Once on PLAYSubsystem.start() / Module.start()
loop()Every cycle after PLAYSubsystem.update() / Module.loop()
stop()Once when the OpMode endsSubsystem.stop() / Module.stop()

At the top of each loop(), in the default MANUAL bulk-caching mode, CDALib clears every Lynx hub's bulk cache once — so the loop's first hardware read pulls all sensors and encoders in a single transaction and everything after is served from that snapshot. For the full breakdown, see ModularOpMode.