Skip to content

Lesson 1.2: Robot Lifecycle

🎯 What You’ll Learn

By the end of this lesson you will be able to:

  • Describe the sequence of methods WPILib calls when the robot starts up
  • Explain what happens during robotInit, teleopInit, teleopPeriodic, autonomousInit, and disabledInit
  • Trace the startup flow from Main.java through Robot.java to RobotContainer.java
  • Identify which Core Files are responsible for each phase of the robot’s life

What Is the Robot Lifecycle?

When you flip the switch on your robot, the code doesn’t just β€œrun.” WPILib calls a specific sequence of methods on your Robot class in a precise order. This sequence is called the Robot Lifecycle.

Think of it like a play with acts:

  1. Startup β€” The robot boots up, creates everything it needs
  2. Disabled β€” The robot is on but not moving (waiting for the match to start)
  3. Autonomous β€” The robot drives itself for 15 seconds
  4. Teleop β€” A human driver takes control
  5. Back to Disabled β€” Match ends, robot stops

Each act has an init method (runs once when entering that mode) and a periodic method (runs every 20 milliseconds while in that mode). That’s 50 times per second!


The Startup Sequence: From Power-On to Ready

Let’s trace exactly what happens when the robot code starts. This is the most important sequence to understand because it sets up everything.

Robot Startup Sequence
🟒 Robot is powered on and code deploys
Main.java main() method
The JVM calls main(), which runs RobotBase.startRobot(Robot::new). This tells WPILib to create a new Robot instance and start the framework.
Robot.java Robot() constructor
The Robot constructor runs. It creates a new RobotContainer β€” this is where ALL the setup happens. Subsystems are created, buttons are bound, auto routines are registered.
RobotContainer.java RobotContainer() constructor
Subsystems are instantiated (drivetrain, shooter, turret, intake, climber). Named commands are registered for PathPlanner. Controller buttons are mapped to commands. Default commands are set.
Robot.java robotPeriodic()
Once setup is complete, WPILib starts calling robotPeriodic() every 20ms. This runs the CommandScheduler, which checks triggers and executes active commands.
Robot.java disabledInit()
The robot starts in disabled mode. disabledInit() runs once β€” in our code, it configures the Limelight cameras for calibration while we wait for the match.
πŸ”΅ Robot is initialized, all subsystems ready, waiting in disabled mode for the match to start

Let’s look at each piece in detail.


Step 1: Main.java β€” The Ignition Key

Open Main.java and you’ll see the simplest file in the entire project:

Main.java
public final class Main {
private Main() {}
public static void main(String... args) {
RobotBase.startRobot(Robot::new);
}
}

That’s it. One line of real code. RobotBase.startRobot(Robot::new) tells WPILib: β€œCreate a Robot object and start running the lifecycle on it.”

You will never edit this file. It’s the same in every FRC project.

How many lines of real code does Main.java contain?


Step 2: Robot.java β€” The Engine

Robot.java is where the lifecycle lives. Your Robot class extends TimedRobot, which is a WPILib class that calls your methods at the right time.

Here’s the constructor β€” the first thing that runs:

Robot.java β€” Constructor
public Robot() {
// Create the RobotContainer β€” this is where all the setup happens
m_robotContainer = new RobotContainer();
System.out.println(">>> Robot code started β€” build v31 <<<");
}

The constructor does one critical thing: it creates a RobotContainer. That single line triggers a cascade of setup β€” every subsystem gets created, every button gets wired, every auto routine gets registered.


Step 3: RobotContainer.java β€” The Wiring Diagram

When new RobotContainer() runs, a lot happens inside the constructor of RobotContainer.java. Here’s the order:

  1. Subsystems are created β€” Each new XxxSubsystem() call creates a subsystem object that talks to real hardware (motors, sensors)
  2. Named commands are registered β€” PathPlanner auto routines can trigger commands by name (like β€œAutoShoot” or β€œIntake”)
  3. Auto chooser is set up β€” A dropdown appears on the dashboard so the drive team can pick an auto routine
  4. Controller buttons are mapped β€” Each button on the Xbox controller is wired to a command (X = intake, Y = shoot, etc.)
  5. Default commands are set β€” These run whenever no other command is using a subsystem (like the turret always auto-aiming)

Here’s a simplified look at the subsystem creation:

RobotContainer.java β€” Subsystem Creation
// Each subsystem represents a physical mechanism on the robot
public final CommandSwerveDrivetrain drivetrain = TunerConstants.createDrivetrain();
private final TurretSubsystem turretSubsystem = new TurretSubsystem();
private final ShooterSubsystem shooterSubsystem = new ShooterSubsystem();
private final IntakeSubsystem intakeSubsystem = new IntakeSubsystem();
private final ClimberSubsystem climber = new ClimberSubsystem();

And here’s how a button gets wired to a command:

RobotContainer.java β€” Button Binding Example
// X BUTTON β€” toggle intake on/off
controller.x()
.toggleOnTrue(
new SequentialCommandGroup(
new InstantCommand(() -> intakeSubsystem.deployOut(), intakeSubsystem),
new WaitCommand(0.3),
new InstantCommand(() -> intakeSubsystem.stopDeploy()),
new RunCommand(() -> intakeSubsystem.runIntake(0.5), intakeSubsystem)
)
);

Don’t worry about understanding every detail of that binding yet β€” you’ll dig into commands and bindings in Lessons 2.2 and 2.3. For now, just know that RobotContainer is where inputs (buttons) get connected to outputs (motor actions).


βœ…Checkpoint: Startup Sequence
Put these events in the correct order: (A) Subsystems are created, (B) Main.java calls startRobot(), (C) robotPeriodic() starts running, (D) Robot constructor creates RobotContainer, (E) Controller buttons are mapped to commands

The correct order is:

  1. (B) Main.java calls RobotBase.startRobot(Robot::new)
  2. (D) Robot constructor creates RobotContainer
  3. (A) Subsystems are created (inside RobotContainer constructor)
  4. (E) Controller buttons are mapped to commands (also inside RobotContainer constructor)
  5. (C) robotPeriodic() starts running every 20ms

The key insight: steps A and E both happen inside step D. Creating the RobotContainer triggers all the subsystem and button setup.


The Lifecycle Methods

Once the robot is initialized, WPILib calls different methods depending on what mode the Driver Station is in. Here’s the complete picture:

robotPeriodic() β€” The Heartbeat

This method runs every 20 milliseconds, no matter what mode the robot is in β€” disabled, autonomous, teleop, or test. It’s the robot’s heartbeat.

Robot.java β€” robotPeriodic()
@Override
public void robotPeriodic() {
// The command scheduler runs all active commands and checks triggers
CommandScheduler.getInstance().run();
// Fuse Limelight vision data into our position estimate
updateVisionPoseEstimate();
// Clamp pose to field bounds
clampPoseToField();
// Update calibration dashboard values
m_robotContainer.updateCalibrationTelemetry();
}

The most important line is CommandScheduler.getInstance().run(). This is the engine that makes the entire command-based framework work. Every 20ms it:

  • Checks all triggers (button presses, sensor thresholds)
  • Runs the execute() method of every active command
  • Runs the periodic() method of every subsystem
  • Cleans up commands that have finished

disabledInit() and disabledPeriodic()

When the robot is on but the Driver Station says β€œDisabled”:

  • disabledInit() runs once when entering disabled mode
  • disabledPeriodic() runs every 20ms while disabled (in addition to robotPeriodic())

In our code, disabledInit() configures the Limelight cameras for calibration:

Robot.java β€” disabledInit()
@Override
public void disabledInit() {}

Our team’s disabledInit() is empty β€” the Limelight setup happens in disabledPeriodic() instead, where it continuously seeds the camera IMUs with the Pigeon heading while we wait for the match.

autonomousInit() and autonomousPeriodic()

When the match starts and the Driver Station switches to Autonomous:

  • autonomousInit() runs once β€” it grabs the selected auto routine and starts it
  • autonomousPeriodic() runs every 20ms during auto (usually empty because the CommandScheduler handles everything)
Robot.java β€” autonomousInit()
@Override
public void autonomousInit() {
// Configure Limelight cameras for best accuracy
LimelightHelpers.SetIMUMode("limelight-front", 4);
LimelightHelpers.SetIMUMode("limelight-back", 0);
// Get the selected auto routine from the dashboard chooser
m_autonomousCommand = m_robotContainer.getAutonomousCommand();
if (m_autonomousCommand != null) {
CommandScheduler.getInstance().schedule(m_autonomousCommand);
}
}

Notice how autonomousInit() doesn’t run the auto β€” it just schedules it. The CommandScheduler (running inside robotPeriodic()) actually executes the auto command step by step.

teleopInit() and teleopPeriodic()

When the Driver Station switches to Teleop (driver control):

  • teleopInit() runs once β€” it cancels any leftover auto command
  • teleopPeriodic() runs every 20ms during teleop (usually empty)
Robot.java β€” teleopInit()
@Override
public void teleopInit() {
// Cancel auto command if it's still running when teleop starts
if (m_autonomousCommand != null) {
CommandScheduler.getInstance().cancel(m_autonomousCommand);
}
// Configure Limelight cameras
LimelightHelpers.SetIMUMode("limelight-front", 4);
LimelightHelpers.SetIMUMode("limelight-back", 0);
}

Why cancel the auto command? Because autonomous and teleop are different phases. If the auto routine is still running when the driver takes over, it would fight with the driver’s joystick inputs. Cancelling it cleanly hands control to the driver.

What would happen if teleopInit() did NOT cancel the autonomous command?


The Full Lifecycle Flow

Here’s the complete picture of a typical FRC match:

Power On
└─→ Main.java: main() calls startRobot()
└─→ Robot constructor: creates RobotContainer
└─→ RobotContainer: creates subsystems, binds buttons, sets defaults
β”Œβ”€β”€β”€ robotPeriodic() runs every 20ms (ALWAYS) ───┐
β”‚ CommandScheduler.run() β”‚
β”‚ Vision updates β”‚
β”‚ Telemetry updates β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
DISABLED ──→ disabledInit() once, disabledPeriodic() every 20ms
β”‚
β–Ό
AUTONOMOUS ──→ autonomousInit() once (schedules auto command)
β”‚ autonomousPeriodic() every 20ms
β–Ό
TELEOP ──→ teleopInit() once (cancels auto command)
β”‚ teleopPeriodic() every 20ms
β–Ό
DISABLED ──→ disabledInit() again (match over)

The key takeaway: robotPeriodic() is always running. The mode-specific methods just set things up β€” the CommandScheduler inside robotPeriodic() does the actual work.

βœ…Checkpoint: Match Flow
Walk through what happens during a typical FRC match from the robot's perspective. Name the lifecycle methods that run (in order) from the moment the match starts in autonomous through the end of teleop.
  1. autonomousInit() β€” runs once when the match starts. Schedules the selected auto command.
  2. autonomousPeriodic() β€” runs every 20ms during auto (usually empty).
  3. robotPeriodic() β€” runs every 20ms throughout auto, executing the auto command via the CommandScheduler.
  4. teleopInit() β€” runs once when auto ends and teleop begins. Cancels any leftover auto command.
  5. teleopPeriodic() β€” runs every 20ms during teleop (usually empty).
  6. robotPeriodic() β€” continues running every 20ms throughout teleop, handling button triggers and commands.
  7. disabledInit() β€” runs once when the match ends and the robot is disabled.

The key insight: robotPeriodic() runs the entire time, across all modes. The mode-specific init methods just set up or tear down commands.


Which method runs the CommandScheduler that executes all active commands?


The Role of Each Core File in the Lifecycle

Now that you understand the lifecycle, let’s connect it back to the four Core Files:

Core FileLifecycle Role
Main.javaTriggers the lifecycle by calling startRobot() β€” runs once, never again
Robot.javaOwns the lifecycle methods (robotPeriodic, teleopInit, autonomousInit, etc.) β€” WPILib calls these automatically
RobotContainer.javaSets up everything during robotInit (via the constructor) β€” subsystems, bindings, autos. Also provides getAutonomousCommand() for auto mode
Constants.javaProvides configuration values that subsystems read during initialization β€” CAN IDs, speeds, field measurements

βœ…Checkpoint: Lifecycle Methods
For each scenario, name the lifecycle method that runs: (1) The match switches from auto to teleop. (2) The robot needs to check if a button was pressed. (3) The robot code first starts up. (4) The match ends and the robot is disabled.
  1. teleopInit() β€” runs once when the Driver Station switches to teleop mode. It cancels any leftover auto command.
  2. robotPeriodic() β€” the CommandScheduler inside robotPeriodic() checks all triggers (including button presses) every 20ms.
  3. Robot() constructor β€” when the code first starts, the Robot constructor runs and creates the RobotContainer, which sets up everything.
  4. disabledInit() β€” runs once when the robot enters disabled mode (match over or e-stop).

Why This Matters

Understanding the lifecycle is the foundation for everything else in this course:

  • Lesson 2.1 (Subsystems) β€” You’ll see how subsystems are created during initialization and how their periodic() methods run every loop
  • Lesson 2.2 (Commands) β€” You’ll learn how commands get scheduled and executed by the CommandScheduler
  • Lesson 2.3 (RobotContainer) β€” You’ll trace button presses from the controller through the binding to the command to the motor output
  • Activity 2.4 (Trace a Button) β€” You’ll follow the complete path from a button press to a motor spinning

Every time you read robot code, ask yourself: β€œWhen does this code run?” The answer is always one of the lifecycle methods you learned today.


What’s Next?

In Activity 1.3: Open & Build, you’ll open the robot project in your IDE and run your first Gradle build. Then in Lesson 2.1: Subsystems, you’ll dive into the subsystem files and learn how they control real hardware.