Unit 5 · Lesson 1

Robot Lifecycle Methods

Every line you have written in Units 2 through 4 lives inside a method. This lesson is about which methods WPILib calls, when it calls them, and how long you have to finish. The 20ms loop is not a software preference — it is a hard constraint enforced by the Driver Station. Violate it and the robot disables itself.

By the end of this lesson, you will:

  • Identify every WPILib lifecycle method, when it fires during a match, and how many times
  • Explain the 20ms loop contract and what the watchdog does when it is violated
  • Choose the correct lifecycle method for hardware construction, sensor polling, dashboard updates, PID loops, and autonomous sequences
  • Predict the crash that results from creating hardware objects inside a periodic method
  • Write a structurally correct Robot.java that initializes hardware in robotInit() and drives behavior from periodic methods

From Java Concepts to Physical Robot

Up to this point, your code has only run on your laptop. Unit 5 changes that. When you deploy to a roboRIO, your compiled code runs on a real Linux computer physically connected to motors, sensors, and a battery. The programs you write now have physical consequences.

The first thing to understand is that you do not call your own code. WPILib does. Your Robot.java class extends TimedRobot, and WPILib's main loop calls your lifecycle methods at specific moments during the match. Your job is to understand exactly when each method runs and what to put in it.

The 20ms Loop Contract

WPILib's runtime calls your periodic methods on a 50Hz timer — once every 20 milliseconds. That is the contract. In exchange, your periodic methods must always return within that 20ms window. If a periodic method takes longer, the watchdog fires.

// WPILib's internal loop — simplified while (true) { robotPeriodic(); // your code — must finish fast if (mode == TELEOP) { teleopPeriodic(); } if (mode == AUTO) { autonomousPeriodic(); } if (mode == DISABLED){ disabledPeriodic(); } if (elapsed > 20) { watchdog.alert(); // "Loop time of Xms overrun" } sleep(max(0, 20 - elapsed)); }

20ms is enough to read a controller, run a PID calculation, and command four motors. It is not enough to wait for a sensor to stabilize, construct a hardware object, or run a polling loop. Those block the thread and delay every subsequent cycle.

💡 What the watchdog actually does

The WPILib watchdog prints to the Driver Station console when your loop overruns: "Loop time of 32.4ms overrun". Occasional 1–2ms overruns during JVM warmup at startup are normal. Consistent overruns above ~5ms indicate a problem — usually a blocking call, a slow sensor read, or accidental hardware construction in periodic. Check the Driver Station Messages tab after every test session.

A Match From the Robot's Perspective

Step through the match timeline below. Each step shows which lifecycle method WPILib calls, how many times, and what the robot is expected to do at that moment.

Match Timeline Tracer
Startup
⏸️ Disabled
🤖 Autonomous
🕹️ Teleop
click Next to begin the match timeline
↑ step through to see each lifecycle method fire in sequence
step 0 / 11

Every Lifecycle Method at a Glance

robotPeriodic() is the one method that runs every single loop cycle regardless of mode — use it for things that should always be checked, like dashboard updates and fault logging.

Method
Called
Purpose
robotInit()
once
Program startup — construct all hardware, configure motors, initialize subsystems. Never called again.
robotPeriodic()
every 20ms
Runs in every mode. Dashboard updates, fault scanning, odometry publishing. Runs before mode-specific periodic.
disabledInit()
once
Fires each time the robot enters disabled — at startup, end of auto, end of teleop, E-stop. Stop motors here.
disabledPeriodic()
every 20ms
Dashboard pre-match setup, sensor calibration display, alliance color detection. No motor commands.
autonomousInit()
once
Fires when DS enables auto. Reset odometry to starting pose, select auto routine, prime state machine.
autonomousPeriodic()
every 20ms
Runs the autonomous routine. In TimedRobot: manual state machine. In Command-Based: scheduler handles it.
teleopInit()
once
Fires when teleop begins. Set brake/coast mode, cancel auto commands, prime subsystem initial states.
teleopPeriodic()
every 20ms
Read controllers, compute outputs, command mechanisms. The core of driver-controlled behavior. Must finish in 20ms.
testInit() / testPeriodic()
once / 20ms
Test mode — individual mechanism tests, characterization, sensor validation. Never ships in competition code.
simulationInit() / simulationPeriodic()
once / 20ms
Simulation only. Updates physics models and virtual sensors when running on a laptop, not real hardware.

The Most Important Design Question: Init or Periodic?

Almost every mistake beginners make in TimedRobot code comes from putting the wrong thing in the wrong method. Ask about any line: does this need to run once, or does it need to run every 20ms?

Anything that creates objects, configures hardware, sets initial state, or runs one-time setup belongs in an init() method. These operations are slow relative to the 20ms budget and pointless to repeat.

// ✅ robotInit() — hardware construction exactly once @Override public void robotInit() { driveMotor = new TalonFX(Constants.DRIVE_ID); // ✅ once controller = new XboxController(0); // ✅ once gyro = new Pigeon2(Constants.GYRO_ID); // ✅ once configureMotor(driveMotor); // ✅ configure once } // ✅ autonomousInit() — reset to known state when auto starts @Override public void autonomousInit() { gyro.reset(); odometry.resetPosition(startPose, gyro.getRotation2d()); autoState = AutoState.DRIVE_FORWARD; // ✅ prime the state machine once }

Anything that needs fresh sensor data, responds to live input, or drives ongoing hardware behavior belongs in periodic. These need to run continuously to be useful.

// ✅ teleopPeriodic() — live input → live output, 50x/sec @Override public void teleopPeriodic() { double speed = MathUtil.applyDeadband(controller.getLeftY(), 0.05); driveMotor.set(speed); } // ✅ robotPeriodic() — dashboard always shows current state @Override public void robotPeriodic() { SmartDashboard.putNumber("Battery V", RobotController.getBatteryVoltage()); SmartDashboard.putNumber("Drive Amps", driveMotor.getStatorCurrent().getValueAsDouble()); SmartDashboard.putString("Auto State", autoState.name()); } // ✅ autonomousPeriodic() — state machine advances one step per cycle @Override public void autonomousPeriodic() { switch (autoState) { case DRIVE_FORWARD: driveMotor.set(0.4); if (getDistanceM() >= 1.5) autoState = AutoState.STOP; break; case STOP: driveMotor.set(0.0); break; } }

Some patterns should never appear in any lifecycle method. They block the robot thread, violate the 20ms contract, or corrupt hardware state.

// ❌ Hardware construction in periodic — floods CAN every 20ms @Override public void teleopPeriodic() { TalonFX motor = new TalonFX(5); // ❌ new CAN device every cycle motor.set(controller.getLeftY()); } // ❌ Blocking loop — holds thread until sensor triggers @Override public void teleopPeriodic() { while (!beamBreak.get()) { } // ❌ watchdog fires, robot may disable intakeMotor.set(0.0); } // ❌ Thread.sleep() — actively suspends the robot thread @Override public void autonomousPeriodic() { driveMotor.set(0.5); Thread.sleep(2000); // ❌ robot unresponsive for 2 seconds driveMotor.set(0.0); } // ❌ Resetting odometry in periodic — overwrites good data every 20ms @Override public void teleopPeriodic() { odometry.resetPosition(...); // ❌ position resets 50x/sec — useless driveMotor.set(speed); }

"What Belongs Here?" — Scenario Explorer

Each scenario shows a code snippet and intent. Select it to see which lifecycle method is correct, which is wrong, and why — including the hardware consequence.

What Belongs Here? — click to see the answer
01
intakeMotor = new TalonFX(8);constructing the intake motor object
02
while (!beamBreak.get()) { }waiting for the intake sensor to trigger before stopping motor
03
gyro.reset();resetting the gyro heading to zero for auto start
04
SmartDashboard.putNumber("Amps", motor.getStatorCurrent()...);publishing motor current to the dashboard
05
driveMotor.setNeutralMode(NeutralModeValue.Brake);switching drivetrain to brake mode at the start of teleop
06
double speed = controller.getLeftY();
driveMotor.set(speed);
reading the joystick and commanding the drive motor
❌ Wrong placement

✅ Correct placement

🔍 Event Observation

The most common lifecycle mistake I see in competition robots is hardware construction in a periodic method. It's usually introduced as a quick fix — someone commented out the field declaration and re-added new TalonFX(5) inside teleopPeriodic() to test something, and it got committed. Every 20ms, that code sends a CAN initialization frame to a running motor, clears its configuration, and floods the bus with traffic. The motor stutters. The drive team reports the robot "feels laggy." The programmer spends two hours checking wiring before someone reads the actual code. A single review catch during PR would have prevented it entirely.

robotPeriodic: The Universal Baseline

robotPeriodic() is unique: it runs in every mode — disabled, auto, teleop, test, simulation — every 20ms, always. It runs before the mode-specific periodic. This makes it the right place for anything that should always be happening regardless of match state: dashboard updates, odometry, fault logging.

🔍 LRI Observation

During robot inspections I always ask to see the drive team's dashboard while the robot is disabled. Teams that put dashboard updates only in teleopPeriodic() show stale values until the match starts — they have no way to verify sensor health, battery voltage, or subsystem state in the queue. Teams that put updates in robotPeriodic() can verify everything is functional before they ever enable. Pre-match verification takes sixty seconds and has saved robots from competing with a broken encoder or a dead battery that nobody noticed. Put your diagnostics in robotPeriodic().

⚙️ 🔌 System Check
  • All hardware objects declared as fields and constructed in robotInit(). A field declared but never assigned in robotInit() is null. The first periodic cycle that calls a method on it throws NullPointerException and disables the robot.
  • No new HardwareClass() calls inside any periodic method. Creating hardware objects in periodic floods the CAN bus, resets motor configuration, and causes stutter. This is a match-ending bug if it reaches competition.
  • No blocking calls in periodic: no Thread.sleep(), no polling while() loops. These hold the thread past the 20ms deadline. Use a per-cycle state check instead of a blocking wait.
  • Dashboard calls in robotPeriodic(). This ensures the pit crew can see live sensor data and battery state before enabling — not just during a match.
  • Mode-transition setup in the corresponding init() method. Gyro reset, odometry reset, state machine priming — once at the start of a mode, not every 20ms.
  • Check the Driver Station Messages tab after every test session. Watchdog overrun messages tell you exactly which cycle ran long. Address them before competition — a 35ms loop produces input lag the drive team can feel.

Knowledge Check

A robot program declares private TalonFX motor; as a field but constructs it inside teleopPeriodic(). After a full match, approximately how many times has new TalonFX(5) been called?
  • 1Once — Java reuses the existing object after the first construction
  • 2Three times — once per mode transition
  • 3Approximately 6,750 times — teleop runs at 50Hz for ~135 seconds; each call constructs a new object, floods the CAN bus with an initialization frame, and resets the motor's configuration
  • 4Zero — a compile error prevents hardware construction inside methods
The Driver Station console shows "Loop time of 28ms overrun." What category of code is most likely causing this?
  • 1Dashboard put calls — SmartDashboard is known to be slow
  • 2A blocking operation in a periodic method — a while loop polling a sensor, a Thread.sleep(), a slow CAN request, or hardware construction; any of these can hold the loop thread past the 20ms deadline
  • 3A missing @Override annotation on a lifecycle method
  • 4Too many fields declared in the Robot class
Which lifecycle method is best for publishing battery voltage to SmartDashboard, and why?
  • 1teleopPeriodic() — battery voltage only matters when the driver is in control
  • 2robotInit() — read voltage once at startup and display that value
  • 3robotPeriodic() — it runs in every mode including disabled, so the pit crew and drive team can see live battery voltage in the queue before enabling; voltage in teleopPeriodic only updates during a match
  • 4disabledPeriodic() — the dashboard should only update when it is safe to interact with the robot
💪 Practice Prompt

Your First Structured Robot.java

Build a complete, structurally correct Robot.java without any hardware attached. This is a code structure exercise — focus on putting things in the right methods, not on making mechanisms move.

  1. Declare the fields. Add three private fields: a TalonFX driveMotor, an XboxController controller, and an AutoState autoState enum. Do not assign or construct them yet.
  2. Populate robotInit(). Construct the motor with a CAN ID from Constants.java. Construct the controller on port 0. Apply a current limit using TalonFXConfiguration. This is the only place any constructor is called.
  3. Populate robotPeriodic(). Publish battery voltage, drive motor stator current, and the auto state name to SmartDashboard. Confirm it runs in disabled by testing before enabling.
  4. Populate autonomousInit(). Set autoState = AutoState.DRIVE. Log the selection to the console with System.out.println().
  5. Populate autonomousPeriodic(). Implement a two-state machine using your AutoState enum: DRIVE sets motor to 0.4; after 2 seconds transitions to STOP, which sets motor to 0.0. No Thread.sleep() — use timer logic across cycles.
  6. Populate teleopPeriodic(). Read controller.getLeftY(), apply a 0.05 deadband with MathUtil.applyDeadband(), and command the motor.
  7. Populate disabledInit(). Set the motor to 0.0 and log "Robot disabled."
  8. Bonus: For every line in your completed Robot.java, write which method it lives in, why it belongs there, and what would go wrong if you moved it to a different method.