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, anddisabledInit - Trace the startup flow from
Main.javathroughRobot.javatoRobotContainer.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:
- Startup β The robot boots up, creates everything it needs
- Disabled β The robot is on but not moving (waiting for the match to start)
- Autonomous β The robot drives itself for 15 seconds
- Teleop β A human driver takes control
- 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.
Letβs look at each piece in detail.
Step 1: Main.java β The Ignition Key
Open
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 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:
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
- Subsystems are created β Each
new XxxSubsystem()call creates a subsystem object that talks to real hardware (motors, sensors) - Named commands are registered β PathPlanner auto routines can trigger commands by name (like βAutoShootβ or βIntakeβ)
- Auto chooser is set up β A dropdown appears on the dashboard so the drive team can pick an auto routine
- Controller buttons are mapped β Each button on the Xbox controller is wired to a command (X = intake, Y = shoot, etc.)
- 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:
// Each subsystem represents a physical mechanism on the robotpublic 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:
// X BUTTON β toggle intake on/offcontroller.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).
The correct order is:
- (B) Main.java calls
RobotBase.startRobot(Robot::new) - (D) Robot constructor creates RobotContainer
- (A) Subsystems are created (inside RobotContainer constructor)
- (E) Controller buttons are mapped to commands (also inside RobotContainer constructor)
- (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.
@Overridepublic 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 modedisabledPeriodic()runs every 20ms while disabled (in addition torobotPeriodic())
In our code, disabledInit() configures the Limelight cameras for calibration:
@Overridepublic 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 itautonomousPeriodic()runs every 20ms during auto (usually empty because the CommandScheduler handles everything)
@Overridepublic 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 commandteleopPeriodic()runs every 20ms during teleop (usually empty)
@Overridepublic 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.
- autonomousInit() β runs once when the match starts. Schedules the selected auto command.
- autonomousPeriodic() β runs every 20ms during auto (usually empty).
- robotPeriodic() β runs every 20ms throughout auto, executing the auto command via the CommandScheduler.
- teleopInit() β runs once when auto ends and teleop begins. Cancels any leftover auto command.
- teleopPeriodic() β runs every 20ms during teleop (usually empty).
- robotPeriodic() β continues running every 20ms throughout teleop, handling button triggers and commands.
- 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 File | Lifecycle Role |
|---|---|
Triggers the lifecycle by calling startRobot() β runs once, never again | |
Owns the lifecycle methods (robotPeriodic, teleopInit, autonomousInit, etc.) β WPILib calls these automatically | |
Sets up everything during robotInit (via the constructor) β subsystems, bindings, autos. Also provides getAutonomousCommand() for auto mode | |
| Provides configuration values that subsystems read during initialization β CAN IDs, speeds, field measurements |
- teleopInit() β runs once when the Driver Station switches to teleop mode. It cancels any leftover auto command.
- robotPeriodic() β the CommandScheduler inside
robotPeriodic()checks all triggers (including button presses) every 20ms. - Robot() constructor β when the code first starts, the Robot constructor runs and creates the RobotContainer, which sets up everything.
- 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.