OpMode Framework¶
What is an OpMode?¶
An opmode is an operator-selectable program that defines what the robot does during a particular mode of operation (autonomous, teleoperated, or utility).
Common use cases include:
Multiple autonomous routines: follow different paths or perform different actions depending on match strategy
Multiple teleoperated behaviors: switch between drive styles (e.g. tank vs. arcade), different button mappings, or restricted controls for robot demonstrations or guest drivers
Testing and diagnostics: test the whole robot, an individual subsystem, a motor, or a sensor without modifying match code
You can select an autonomous, teleop, or utility opmode directly on the DS, or use match mode to select both an autonomous and teleop opmode with match timing.
Here’s an example of what opmode selection looks like on the Driver Station:

The Robot Class¶
In an opmode project the Robot class extends OpModeRobot (Java, C++). Hardware objects, subsystems, and any state shared across all opmodes are declared as members here.
5package org.wpilib.templates.opmode;
6
7import org.wpilib.framework.OpModeRobot;
8
9/**
10 * The methods in this class are called automatically as described in the OpModeRobot documentation.
11 * OpMode classes anywhere in the package (or sub-packages) where this class is located are
12 * automatically registered to display in the Driver Station. If you change the name of this class
13 * or the package after creating this project, you must also update the Main.java file in the
14 * project.
15 */
16public class Robot extends OpModeRobot {
17 /**
18 * This function is run when the robot is first started up and should be used for any
19 * initialization code.
20 */
21 public Robot() {}
22
23 /** This function is called exactly once when the DS first connects. */
24 @Override
25 public void driverStationConnected() {}
26
27 /**
28 * This function is called periodically anytime when no opmode is selected, including when the
29 * Driver Station is disconnected.
30 */
31 @Override
32 public void nonePeriodic() {}
33}
5#pragma once
6
7#include "wpi/framework/OpModeRobot.hpp"
8
9class Robot : public wpi::OpModeRobot<Robot> {
10 public:
11 Robot();
12 void DriverStationConnected() override;
13 void NonePeriodic() override;
14};
5#include "Robot.hpp"
6
7#include "opmode/MyAuto.hpp"
8#include "opmode/MyTeleop.hpp"
9
10Robot::Robot() {
11 // Add opmodes to the robot here.
12 AddOpMode<MyTeleop>(wpi::RobotMode::TELEOPERATED, "My Teleop", "",
13 "An example teleop opmode");
14 AddOpMode<MyAuto>(wpi::RobotMode::AUTONOMOUS, "My Auto", "");
15 PublishOpModes();
16}
17
18/** This function is called exactly once when the DS first connects. */
19void Robot::DriverStationConnected() {}
20
21/**
22 * This function is called periodically anytime when no opmode is selected,
23 * including when the Driver Station is disconnected.
24 */
25void Robot::NonePeriodic() {}
The following methods in OpModeRobot run no matter the selected opmode:
driverStationConnected(): called once when the Driver Station first connectsrobotPeriodic(): called every loop iteration regardless of enabled state or selected opmodedisabledInit()/disabledPeriodic()/disabledExit(): called when entering, during, and exiting the disabled state. The robot is disabled whenever the DS has not enabled it or communication is lost; while disabled, actuators (motors, solenoids, etc.) cannot be commanded.nonePeriodic(): called periodically when no opmode is selected (including when the DS is disconnected)simulationInit()/simulationPeriodic(): called during construction and every loop if simulation is running
Creating OpModes¶
Individual opmodes extend PeriodicOpMode (Java, C++) and implement whichever lifecycle methods they need.
In Java, opmodes are registered by annotating the class with @Autonomous, @Teleop, or @Utility. OpModeRobot automatically scans the project package at startup and publishes the list to the Driver Station.
5package org.wpilib.templates.opmode.opmode;
6
7import org.wpilib.opmode.PeriodicOpMode;
8import org.wpilib.opmode.Teleop;
9import org.wpilib.templates.opmode.Robot;
10
11@Teleop
12public class MyTeleop extends PeriodicOpMode {
13 private final Robot robot;
14
15 /** The Robot instance is passed into the opmode via the constructor. */
16 public MyTeleop(Robot robot) {
17 this.robot = robot;
18 }
19
20 @Override
21 public void disabledPeriodic() {
22 /* Called periodically (on every DS packet) while the robot is disabled. */
23 }
24
25 @Override
26 public void start() {
27 /* Called once when the robot is enabled. */
28 }
29
30 @Override
31 public void periodic() {
32 /* Called periodically (set time interval) while the robot is enabled. */
33 }
34
35 @Override
36 public void end() {
37 /* Called when the robot is disabled (after previously being enabled). */
38 }
39
40 @Override
41 public void close() {
42 /* Called when the opmode is de-selected / no additional methods will be called. */
43 }
44}
5package org.wpilib.templates.opmode.opmode;
6
7import org.wpilib.opmode.Autonomous;
8import org.wpilib.opmode.PeriodicOpMode;
9import org.wpilib.templates.opmode.Robot;
10
11@Autonomous(name = "My Auto", group = "Group 1")
12public class MyAuto extends PeriodicOpMode {
13 private final Robot robot;
14
15 /** The Robot instance is passed into the opmode via the constructor. */
16 public MyAuto(Robot robot) {
17 this.robot = robot;
18 }
19
20 /*
21 * This method runs periodically, using the same period as the Robot instance.
22 *
23 * Additional periodic methods may be configured with addPeriodic(),
24 * which can have periods that differ from the main Robot instance.
25 */
26 @Override
27 public void periodic() {
28 // Put custom auto code here
29 }
30}
All annotation attributes are optional:
Attribute |
Description |
Default |
|---|---|---|
|
Name shown in the DS drop-down |
Class simple name |
|
Group label for organizing the drop-down |
(ungrouped) |
|
Extended description |
(none) |
|
Text color in the DS (CSS color string) |
(default) |
|
Background color in the DS (CSS color string) |
(default) |
C++ has no annotation support. Opmodes are registered explicitly in the Robot constructor using AddOpMode<T>(mode, name, group, description), followed by a single PublishOpModes() call.
5#include "Robot.hpp"
6
7#include "opmode/MyAuto.hpp"
8#include "opmode/MyTeleop.hpp"
9
10Robot::Robot() {
11 // Add opmodes to the robot here.
12 AddOpMode<MyTeleop>(wpi::RobotMode::TELEOPERATED, "My Teleop", "",
13 "An example teleop opmode");
14 AddOpMode<MyAuto>(wpi::RobotMode::AUTONOMOUS, "My Auto", "");
15 PublishOpModes();
16}
17
18/** This function is called exactly once when the DS first connects. */
19void Robot::DriverStationConnected() {}
20
21/**
22 * This function is called periodically anytime when no opmode is selected,
23 * including when the Driver Station is disconnected.
24 */
25void Robot::NonePeriodic() {}
5#pragma once
6
7#include "wpi/opmode/PeriodicOpMode.hpp"
8
9class Robot;
10
11class MyTeleop : public wpi::PeriodicOpMode {
12 public:
13 /** The Robot instance is passed into the opmode via the constructor. */
14 explicit MyTeleop(Robot& robot);
15 ~MyTeleop() override;
16 void Start() override;
17 void Periodic() override;
18 void End() override;
19
20 private:
21 [[maybe_unused]]
22 Robot& robot;
23};
5#include "opmode/MyTeleop.hpp"
6
7#include "Robot.hpp"
8
9/** The Robot instance is passed into the opmode via the constructor. */
10MyTeleop::MyTeleop(Robot& robot) : robot{robot} {}
11
12MyTeleop::~MyTeleop() {
13 /* Called when the opmode is de-selected. */
14}
15
16void MyTeleop::Start() {
17 /* Called once when the robot is first enabled. */
18}
19
20void MyTeleop::Periodic() {
21 /* Called periodically (set time interval) while the robot is enabled. */
22}
23
24void MyTeleop::End() {
25 /* Called when the robot is disabled (after previously being enabled). */
26}
5#pragma once
6
7#include "wpi/opmode/PeriodicOpMode.hpp"
8
9class Robot;
10
11class MyAuto : public wpi::PeriodicOpMode {
12 public:
13 /** The Robot instance is passed into the opmode via the constructor. */
14 explicit MyAuto(Robot& robot);
15 ~MyAuto() override;
16 void Start() override;
17 void Periodic() override;
18 void End() override;
19
20 private:
21 [[maybe_unused]]
22 Robot& robot;
23};
5#include "opmode/MyAuto.hpp"
6
7#include "Robot.hpp"
8
9/** The Robot instance is passed into the opmode via the constructor. */
10MyAuto::MyAuto(Robot& robot) : robot{robot} {
11 /*
12 * Can call the base class constructor with the period to set a different
13 * periodic time interval.
14 *
15 * Additional periodic methods may be configured with AddPeriodic().
16 */
17}
18
19MyAuto::~MyAuto() {
20 /* Called when the opmode is de-selected. */
21}
22
23void MyAuto::Start() {
24 /* Called once when the robot is first enabled. */
25}
26
27void MyAuto::Periodic() {
28 /* Called periodically (set time interval) while the robot is enabled. */
29}
30
31void MyAuto::End() {
32 /* Called when the robot is disabled (after previously being enabled). */
33}
OpMode Lifecycle¶
When the operator selects an opmode on the Driver Station, a new instance of the opmode class is constructed. There is no separate initialization function; any necessary state and hardware setup can happen in the constructor directly.
While an opmode is selected but the robot is disabled, disabledPeriodic() is called periodically at OpModeRobot#getPeriod() (default 20 ms). This is useful for updating dashboard displays, reading sensors, or previewing what the opmode is about to do. The library guarantees that disabledPeriodic() will be called at least once before the robot transitions to enabled, so any initialization logic placed here is guaranteed to run.
Note
Both OpMode and OpModeRobot have disabledPeriodic() methods that can be overridden. The OpModeRobot#disabledPeriodic() method is called when the robot is disabled no matter which OpMode is selected, and the OpMode#disabledPeriodic() method is called when the robot is selected and that specific opmode is selected.
When the robot transitions from disabled to enabled, start() is called exactly once. Use it to start timers, reset accumulators, or prepare anything that needs to be fresh at the start of each enable. start() should return quickly and not have any blocking actions; ongoing work belongs in periodic().
While the robot is enabled, periodic() is called repeatedly at OpModeRobot#getPeriod() (default 20 ms / 50 Hz). This is where most robot logic runs: reading sensors, computing outputs, and commanding actuators. Additional callbacks registered with addPeriodic() run at their own configured rates.
When the robot disables, end() is called first. Use it to stop motors, retract mechanisms, or send any final state updates. Then close() is called (Java) or the object is destroyed (C++/Python); use close() to release resources like open file handles. The object is never reused after this point.
Note
Selecting a different opmode while the robot is enabled automatically disables the robot first, so end() is always called before the switch.
If a different opmode is selected while the robot is already disabled, only close() is called, as the opmode was never started.
Immediately after the old opmode is destroyed, a fresh instance is constructed based on the current DS selection. If the same opmode is still selected, the same class is instantiated again from scratch, so its constructor and disabledPeriodic() run again before the next enable. In match mode (when selected manually on the DS or when FMS-connected), only the selected autonomous opmode is constructed initially; once autonomous completes, the selected teleop opmode is then constructed. Only one opmode object is ever alive at a time.
Accessing Robot Hardware¶
Opmodes receive the Robot instance through their constructor. Declare a field to store it and assign it in the constructor so all methods can use it.
@Teleop
public class MyTeleop extends PeriodicOpMode {
private final Robot robot;
public MyTeleop(Robot robot) { // Robot is injected automatically
this.robot = robot;
}
@Override
public void periodic() {
robot.drive.arcadeDrive(robot.joystick.getY(), robot.joystick.getX());
}
}
class MyTeleop : public wpi::PeriodicOpMode {
public:
explicit MyTeleop(Robot& robot); // Robot is injected automatically
void Periodic() override;
private:
Robot& robot;
};
MyTeleop::MyTeleop(Robot& robot) : robot{robot} {}
void MyTeleop::Periodic() {
robot.drive.ArcadeDrive(robot.joystick.GetY(), robot.joystick.GetX());
}
Multiple OpModes and DS Selection¶
Any number of classes can be annotated with the same type. All of them appear in the Driver Station’s drop-down for that mode, organized alphabetically within their groups. Classes can also have more than one annotation, so a utility opmode can temporarily also be labeled as teleop so it can run when connected to FMS.
@Autonomous(name = "Drive Straight", group = "Drive")
public class DriveStraight extends PeriodicOpMode { ... }
@Autonomous(name = "Score Cone", group = "Score")
public class ScoreCone extends PeriodicOpMode { ... }
@Autonomous(group = "Score")
public class ScoreCube extends PeriodicOpMode { ... }
@Utility(name = "Test Arm")
public class TestArm extends PeriodicOpMode { ... }
The operator selects the desired OpMode in the DS before enabling. In match mode (selected manually in the DS, or when connected to the FMS), the operator selects both an autonomous and a teleop OpMode before the match; the DS transitions between them automatically.
Custom Periodic Callbacks¶
PeriodicOpMode has an additional method, addPeriodic(), for running callbacks at rates other than the main loop period. This is useful when a task needs to run more frequently than 20 ms, such as high-rate odometry integration or sensor polling. The optional offset parameter staggers the callback relative to the start of the main loop, which prevents it from executing at the exact same moment as periodic() and ensures the most recent sensor data is available when periodic() runs:
public class MyAuto extends PeriodicOpMode {
public MyAuto(Robot robot) {
// Run an odometry update at 5 ms, offset 1 ms from the main loop
addPeriodic(robot.odometry::update, 0.005, 0.001);
}
}
MyAuto::MyAuto(Robot& robot) : robot{robot} {
// Run an odometry update at 5 ms, offset 1 ms from the main loop
AddPeriodic([&] { robot.odometry.Update(); }, 5_ms, 1_ms);
}
Callbacks are registered immediately at opmode construction and run even while the robot is disabled.
Warning
Callbacks run regardless of enabled state. Any actuator commands inside a callback must be guarded with an isEnabled() check, or they will send commands that fail while the robot is disabled.
Migration from TimedRobot¶
To switch to the OpMode framework from TimedRobot, replace per-mode methods in Robot (autonomousInit, teleopPeriodic, utilityInit, utilityPeriodic etc.) with separate @Autonomous, @Teleop, and @Utility opmode classes. Multiple opmodes of the same type replace SendableChooser.
Note
TimedRobot remains fully supported. Migration is not required.