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
bindingManagerfield (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).
}
}
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:
| Phase | When | Called on subsystems / modules |
|---|---|---|
preload() | Start of INIT, once | Your hook — register & install here. |
init() | On INIT, after preload() | Subsystem.init() / Module.init() |
init_loop() | Repeatedly on the init screen | Subsystem.init_loop() / Module.init_loop() |
start() | Once on PLAY | Subsystem.start() / Module.start() |
loop() | Every cycle after PLAY | Subsystem.update() / Module.loop() |
stop() | Once when the OpMode ends | Subsystem.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.