I recently retired from mentoring my FIRST Robotics Competition (FRC) team. I started as a student in 2010, and went straight into mentoring upon graduation, so in total I have been writing FRC programs for about 17 years.
Over time though I've become more and more dismayed with how FRC programs are typically written. For decades, I feel that FIRST and WPILib have encouraged students to severely overengineer their robot programs. As time has gone on, I have become more and more frustrated with the state of FRC programming, and our team's code style has drifted further and further away from the norm.
To be clear, I have the utmost respect for the time and effort that FIRST and the WPILib maintainers put in every year, and it is thanks to their efforts that FRC teams have reliable and complete libraries every year. But I think the accepted programming practices in FRC are leading students astray and ought to be corrected.
So as I step away from FIRST, I decided I should write down my philosophy of robot programming in FRC, which I have decided to give the pithy name of Periodic-Oriented Programming.
What is Periodic-Oriented Programming?
Periodic-Oriented Programming is very simple: Do as much as possible in your periodic function. Uses very few abstractions, and write most of your logic in one big function, top to bottom. This is so simple and beginner-like that it seems like it shouldn't work for serious programs. But it does, as long as you carefully establish a few simple boundaries and practices.
For context, the essential structure of an FRC program is typically to use the TimedRobot framework provided by WPILib. To use it, you simply implement periodic functions and the robot framework calls them 50 times per second. For example, here's a complete "tank drive" robot program in Python:
import wpilib
import rev
class MyRobot(wpilib.TimedRobot):
def robotInit(self):
self.gamepad = wpilib.XboxController(0)
self.leftMotor = rev.SparkMax(10, rev.SparkLowLevel.MotorType.kBrushless)
self.rightMotor = rev.SparkMax(11, rev.SparkLowLevel.MotorType.kBrushless)
def teleopPeriodic(self):
self.leftMotor.set(self.gamepad.getLeftY())
self.rightMotor.set(self.gamepad.getRightY())
50 times per second, the left and right motor speeds will be updated to use the left and right gamepad axes. The robot framework and hardware abstraction layers do the rest.
In essence, every FRC program does the following work in periodic:
- Get user inputs (or autonomous state)
- Compute the desired state of the hardware (motor speeds, actuator positions, etc.)
- Enforce safety constraints, smooth signals, or otherwise process the outputs
- Update hardware outputs
In Periodic-Oriented Programming, I basically encourage my students to lean into this pattern. POP code should have few abstractions, and most of the logic should be written top-to-bottom in a single function. And to the greatest extent possible, everything should be run on every periodic—no modes, no commands, no state.
As programs get larger, more structure is needed, but the core philosophy remains the same. The most important piece of structure is subsystems.
Hardware lives in subsystems
The use of "subsystem" classes will be familiar to most FRC teams, but the specifics are what matter here:
- Every subsystem has a periodic method. This is called from robotPeriodic so it runs 100% of the time.
- Hardware is never accessible outside the subsystem. All access to the hardware must be mediated through subsystem methods.
These rules extend the Periodic-Orientation into the subsystem code. For each subsystem, all of its logic should be centralized in one function and readable top to bottom. In most cases, the subsystem periodic is the only place in the entire program that actually touches the hardware. This has many advantages: it ensures by construction that hardware is only touched once per tick, that safety constraints are respected 100% of the time, and that logging and telemetry are run consistently.
Centralizing most logic in periodic also provides a nice platform for both autonomous and teleoperated code. The subsystem's API surface can be comprised of very high-level functions like driveFieldRelative(vx, vy) or goToElevatorPreset(coralOrAlgae, level), which basically just set instance variables to be read later in periodic. These subsystem functions also provide a consistent place to validate inputs (e.g. impossible speeds, angles, or presets), convert units, etc.
By the way, don't be afraid to combine two subsystems if their logic is intertwined. For example, in 2025 our robot had an arm on an elevator, and the safe angles for the arm were dependent on the height of the elevator. Therefore, we made a combined ElevatorAndArm subsystem. (If you've heard advice that classes should be about "one thing", ignore that advice, it is wrong.[1])
Chase the desired state
As I described earlier, every FRC program computes the desired state of the hardware, then updates the outputs to move in that direction. This is most obviously true of closed-loop control (PID, typically), where you simply set a target value (e.g. "elevator at 6 feet") and then compute outputs that will move the hardware toward that target.
Successful Periodic-Oriented programs apply this philosophy to the entire robot program. What hardware state do I want, and how do I get there? This way of thinking applies at the actuator level (e.g. motor speeds), the mechanism level (e.g. arm angles), and the whole-robot level (e.g. safety constraints like "arm can only go out when elevator is down"). It is universal to all robot control.
The question of "What hardware state do I want?" falls naturally into teleopPeriodic and autonomousPeriodic. They naturally define answers to questions like: Where on the field? What velocity? What elevator position? What arm angle? What shooter RPM?
The question of "How do I get there?" then falls naturally into a subsystem periodic. Often it is as simple as elevatorMotor.setReference(elevatorHeight), deferring the actual closed-loop behavior to your motor controllers. But extra behaviors can easily be layered in here too; for example, the whole-robot constraint described earlier can be as simple as:
def periodic(self):
newArmAngle = self.desiredArmAngle
if self.getElevatorHeight() > constants.kMaxElevatorHeightForArm:
newArmAngle = 0 # pull arm in if elevator is up
self.armMotor.setReference(newArmAngle)
Or you could do a "state machine" approach, where attempting to put the arm out will actually drop the elevator to a safe position first:
def periodic(self):
newArmAngle = self.desiredArmAngle
newElevatorHeight = self.desiredElevatorHeight
if self.getElevatorHeight() > constants.kMaxElevatorHeightForArm:
newArmAngle = 0 # pull arm in if elevator is up
if self.desiredArmAngle > 0:
newElevatorHeight = 0 # move elevator down if arm wants to be out
self.armMotor.setReference(newArmAngle)
self.elevatorMotor.setReference(newElevatorHeight)
In a Periodic-Oriented program, complex behaviors like this are easy to debug, robustly uphold safety constraints, never produce conflicting outputs to hardware, and often require less state than other approaches. Speaking of:
Minimize state
Robots have enough state to deal with in the real world. So don't add extra state to your program!
For the beginners in the audience, "state" in programming is data in your program that changes over time. Common sources of state in FRC programs include:
- Physical robot state (e.g. location on field, velocity, mechanism positions)
- User inputs (e.g. joystick axis values and button states)
- Modes (e.g. the ability for operators to toggle between "coral mode" and "algae mode" controls)
- Signal processing state (e.g. for smoothing sensor data or slewing outputs)
- Coprocessor state (e.g. data stored on motor controllers or computer vision devices)
Some of these are inherent, such as the physical robot state (which is the entire point). Others are not, such as modes. It is well-known in programming, across many domains, that more state == more problems, so remove or isolate state wherever you can:
- Avoid modes. They are confusing for both operators and programmers. For example, rather than storing a flag for
armIsExtendedthat is toggled by a single button press, consider having an "out" button and an "in" button, or consider extending the arm only while the button is held. This eliminates ambiguity: the operator knows exactly what state the robot should be in based only on the buttons they are currently pressing.
- Avoid duplicating coprocessor state in your robot code. For example, don't have a variable in your subsystem for
currentArmAngle; read it directly from your motor controller instead. If they can get out of sync, that is an opportunity for bugs.
- Avoid timers. They are a frequent source of bugs because the state of the world is often not what you expect when the timer expires. For example, instead of running your shooter for three seconds, consider running it until a sensor detects that the hopper is empty.
- Avoid autonomous state. For most teams, it is very viable to represent all autonomous outputs as a function of time. In the extreme, this might look like: if 2 <= elapsedTime and elapsedTime <= 5: shooter.shoot() else: shooter.stopShooter() [TODO: BAD CSS HERE] But while this looks silly, this is more or less what tools like Choreo let you do. A Choreo trajectory is just a list of desired positions, velocities, and actions for the robot over time, and executing a Choreo trajectory is literally as simple as looking up the current desired state and applying it to your drivetrain. (This is also true of PathPlanner, but to a lesser extent due to its dependence on commands.) And it need not be fancy: In 2011, when I was a student, my dad (an industrial automation engineer) advised me to author our autonomous routines in spreadsheets. Each autonomous routine that year was literally a CSV file, with one column per actuator and one row per "step" (with start and end times). At any given time, you could simply look up the current row of the table, and look at each column to see what each motor's output should be. It worked well! I don't know why we never did it again.
Avoid autonomous state. For most teams, it is very viable to represent all autonomous outputs as a function of time. In the extreme, this might look like:
if 2 <= elapsedTime and elapsedTime <= 5:
shooter.shoot()
else:
shooter.stopShooter()
But while this looks silly, this is more or less what tools like Choreo let you do. A Choreo trajectory is just a list of desired positions, velocities, and actions for the robot over time, and executing a Choreo trajectory is literally as simple as looking up the current desired state and applying it to your drivetrain. (This is also true of PathPlanner, but to a lesser extent due to its dependence on commands.)
And it need not be fancy: In 2011, when I was a student, my dad (an industrial automation engineer) advised me to author our autonomous routines in spreadsheets. Each autonomous routine that year was literally a CSV file, with one column per actuator and one row per "step" (with start and end times). At any given time, you could simply look up the current row of the table, and look at each column to see what each motor's output should be. It worked well! I don't know why we never did it again.
And there is one final piece of advice that eclipses all others:
Avoid commands at all costs
The WPILib "command-based" programming system is an unmitigated disaster that should never be used under any circumstances.
Harsh? Yes. But after writing and teaching command-based code for ten years, then trying other approaches for another seven, I have concluded that the command-based approach is almost entirely without merit.
At its heart, the problem with the command-based system is that it is woefully, impossibly stateful. When using commands, you don't know which commands are currently active in the scheduler, you don't know what order they execute in, it's difficult to predict when they start, and it's all but impossible to know when they end.
I have witnessed three generations of students (myself included), across multiple teams, debugging bizarre and entirely artificial problems caused entirely by the command-based system. "Why can't we run autonomous twice? Oh, because you can't always call initialize after end? Maybe I need to construct a new command instance? But how can I do that if onTrue takes a Command instead of a () -> Command? Why did the robot stop driving? Oh, something canceled the Drivetrain subsystem's default command? I guess it shouldn't require the subsystem then? Oh, I guess now we're setting the motor speeds twice? Why did that one run before the default command?"
I could keep going. "Why did the robot shoot just then? Wait, there was still a command running from earlier in autonomous? Why didn't it get canceled?" Every one of these questions is a stupid waste of time.
The other problem with commands is that they spread once-simple logic across dozens of files. This is purportedly a benefit of using commands, but the reality is that neither students nor mentors are good at piecing all the logic together in their head again. When you have a bug at competition and need to make a code change now, you need code that is brutally simple, where you can be confident that your change won't have unexpected side effects. Straight-line code is better for this purpose.
The remaining advantages for the command-based model are that they make it easier to sequence and schedule actions in autonomous, and that in principle they prevent multiple pieces of logic from accessing the hardware simultaneously. But as I've already suggested, a stateless, time-driven approach to autonomous works better in most situations, intra-subsystem state can be handled in periodic, and the periodic approach avoids the need for the requirements system by construction. (And if you want to ensure that a subsystem method is called only once per periodic, this can be easily achieved with a flag and a log message in the subsystem method itself.)
So, although it might seem crazy, I strongly encourage everyone to avoid command-based programming.
A final example
Let me conclude with a snippet of real teleopPeriodic code from our 2025 codebase:
def teleopPeriodic(self):
# Get raw speeds from joysticks (to be converted to field/robot relative)
joystickX = constants.kMaxSpeed * wpimath.applyDeadband(self.leftStick.getX(), 0.1)
joystickY = constants.kMaxSpeed * wpimath.applyDeadband(self.leftStick.getY(), 0.1)
# Turn speed is the same regardless of field/robot relative
turnSpeed = wpimath.applyDeadband(-self.rightStick.getX(), 0.1)
# Precision mode
if self.leftStick.getRawButton(1) or self.rightStick.getRawButton(1):
joystickX *= 0.5
joystickY *= 0.5
turnSpeed *= 0.5
# Drive
doRobotRelative = self.leftStick.getRawButton(3) or self.rightStick.getRawButton(3)
if doRobotRelative:
self.drivetrain.drive_robot_relative(-joystickY, -joystickX, turnSpeed)
else:
# Flip to red alliance if necessary
xSpeed = -joystickY
ySpeed = -joystickX
if utils.isRedAlliance():
xSpeed *= -1
ySpeed *= -1
self.drivetrain.drive_field_relative(xSpeed, ySpeed, turnSpeed * constants.kMaxTurnSpeed)
There is no subsystem default command, just a function call. There are no separate commands for field vs. robot relative, just an if statement. There is no separate command or global state for precision controls, just an if statement. There is no binding of commands to buttons, just if statements. Reversed field-relative coordinates for the red alliance? If statement.
Does this feel ugly to you? If so, think of this an acquired taste! This is 25 lines of dead-simple code that completely encapsulates all of our teleop driving logic. The command-based alternative would probably be several classes and 100 to 200 lines of code (when you count all the boilerplate). Here there is no possibility of command cancellation, bad defaults, incorrect execution order, or conflicting writes to the hardware. Instead the code expresses the logic directly and simply.
I suspect that many readers will think this is too simple, like surely this approach will cause problems in practice. But it doesn't! This is our actual 2025 robot code, which supported field-relative and robot-relative swerve drive, odometry and vision, and multiple Choreo-based autonomous routines with different starting points and scoring positions. If you are Orbit and your strategy is 100% autonomous game piece retrieval and placement, then feel free to ignore me. But I believe this approach to FRC programming would be successful for at least 95% of all FRC teams.
[1]The problem with advice like "classes should do one thing, and do it well" is that no one can describe what "one thing" is. This is even acknowledged in books like Clean Code but not clarified in a way I find satisfactory. It is therefore not actionable advice. The decision of where to draw an API boundary should instead be determined by the amount of information that has to cross that boundary. In our ElevatorAndArm example, we could indeed have separated the Elevator and Arm into separate subsystems, but our "coral level 3" preset would then have required calls to two separate subsystems, and both subsystems would have needed references to each other in order to maintain safety constraints. When two classes are deeply aware of each other's internals, you should just combine them.