ModularOpMode
The base OpMode you subclass to assemble a robot out of reusable parts. Register
subsystems and modules once, and ModularOpMode drives their whole lifecycle for you —
init, start, loop, and stop — while managing Lynx hub bulk caching so every cycle reads all your
sensors in a single transaction.
ModularOpMode extends the FTC SDK's iterative OpMode. Instead of
overriding init()/loop() directly and wiring hardware by hand, you
override a single hook, preload(), to declare what your robot is made of. From there
the base class forwards each SDK lifecycle phase to every registered
subsystem and installed module, in the
order you registered them.
public abstract class ModularOpMode extends OpMode {
protected final BindingManager bindingManager = new BindingManager();
protected abstract void preload();
protected void registerSubsystem(Subsystem subsystem);
protected void installModule(Module module);
protected void setBulkCachingMode(LynxModule.BulkCachingMode mode);
}
Lifecycle
Each phase of the SDK OpMode lifecycle is forwarded to your parts. Subsystems always run before modules within a phase, and both run in registration order.
| Phase | What ModularOpMode does |
|---|---|
init() |
Runs your preload(); discovers every LynxModule from the
hardware map and applies the bulk-caching mode to each; then calls subsystem.init()
on every subsystem followed by module.init() on every module. |
init_loop() |
Calls subsystem.init_loop() on each subsystem, then
module.init_loop() on each module. Repeats while the driver waits on the init
screen. |
start() |
Calls subsystem.start() on each subsystem, then module.start()
on each module, then polls taskMaster.update() once. |
loop() |
In MANUAL mode, clears every hub's bulk cache once; then calls
subsystem.update() on each subsystem, module.loop() on each module,
and finally polls taskMaster.update(). |
stop() |
Calls subsystem.stop() on each subsystem, then module.stop()
on each module, so they can safe or release hardware. |
update() for the main loop, while
modules expose loop(). The TaskMaster is polled at the end of both
start() and loop(), so queued tasks advance
every cycle. See Subsystems and Modules
for the full contract of each.Registration
You never call the lifecycle methods yourself. Instead you declare your robot in
preload(), which runs once at the very top of init() before anything is
initialized.
| Member | Purpose |
|---|---|
protected abstract void preload() |
The one method you must implement. Build the robot here: register subsystems, install
modules, and optionally change the bulk-caching mode. Called once at the start of
init(). |
void registerSubsystem(Subsystem subsystem) |
Adds a subsystem to be driven every phase, in registration order. Call from
preload(). |
void installModule(Module module) |
Adds a module to be driven every phase, in installation order. Call from
preload(). |
protected final BindingManager bindingManager |
The shared BindingManager for this OpMode. Modules such as
BindingModule and TeleOpPedroModule register and query gamepad
bindings against it; it is exposed to subclasses so you can wire
your own bindings too. |
@TeleOp(name = "My TeleOp")
public class MyTeleOp extends ModularOpMode {
private DriveSubsystem drive;
@Override
protected void preload() {
drive = new DriveSubsystem(hardwareMap);
registerSubsystem(drive);
registerSubsystem(new ThreadedIMU(hardwareMap, "imu"));
// Modules typically wire gamepad bindings against bindingManager.
installModule(new TeleOpPedroModule(/* ... */));
}
}
Lynx bulk caching
Every Control Hub and Expansion Hub (a "Lynx" module) can batch its sensor and encoder reads.
ModularOpMode sets the caching mode on every hub in init(); the mode
defaults to MANUAL and can be changed from preload():
protected void setBulkCachingMode(LynxModule.BulkCachingMode mode)
In MANUAL mode the cache is cleared exactly once, at the top of each
loop(). The first hardware read after that clear pulls every sensor and
encoder off the hub in a single bulk transaction; every subsequent read that loop is served from
that in-memory snapshot instead of hitting the bus. Because CDALib's devices
read hardware in their update(), this means a full loop's worth of sensor reads costs
one round-trip to each hub rather than one per sensor.
update(). Do not clear the cache
yourself mid-loop.I2C devices (such as the universal IMU or a REV Color Sensor V3) are not covered by Lynx bulk caching. CDALib handles those separately: see ThreadedIMU and the RevColorSensorV3Device, both of which minimize per-loop I2C cost.
PhotonFTC
For teams that want even lower loop times, annotate your concrete OpMode subclass with
@Photon. PhotonCore then swaps in its optimized motor and servo controllers under the
hood.
@Photon
@TeleOp(name = "My TeleOp")
public class MyTeleOp extends ModularOpMode {
// ...
}
Photon pairs naturally with the default MANUAL bulk-caching mode used by
ModularOpMode — one cache clear per loop(). Leave the caching mode on
MANUAL when Photon is enabled; the two optimizations compound rather than conflict.