Command-Based Programming
An overview of WPILib Command-Based Programming.
Introduction
What you just created is known as a timed robot. This is one of a couple of different paradigms—or ways—to organize your robot code. The other way, which is the way we use on Team 937, is a command-based robot.
Command-based is a declarative paradigm, meaning that we declare the parts of the robot once, then use them later. It is generally made up of commands and subsystems, which we'll explain in a moment.
On Team 937, we've found that command-based code is generally better-organized and easier to extend, so we use it on all of our robots.
Commands
Commands are the files in the robot's code that tell the robot what to do. This has a wide variety of applications, such as telling the robot to raise an arm, launch a catapult, or even just drive the robot.
The Command Scheduler
The Command Scheduler is the class that runs everything in the robot. Every 20 milliseconds, it goes through everything that is running, is initialized to run, or ending. (For those who know how frames in video games work—it is a very similar system). For a detailed look into the Command Scheduler, reference the WPILib documentation:
The four important methods for commands are initialize()
, execute()
, end()
, and isFinished()
. For a detailed look at them, refer to:
Initialize
initialize()
is run once when a command is first scheduled.
Execute
execute()
is run continuously while a command is scheduled. This is the primary method we use in our commands. It is useful for commands the run while a button is pressed down (i.e. spinning a wheel).
End
end()
is run once when a command is finished. This is used the least of the four.
IsFinished
isFinished()
is run as often as execute()
. However, isFinished()
returns a boolean every iteration. When it returns true
, the command ends. If you aren't going to use this method, set it to always return false
.
Here is a simple command as an example, ExtendArm.java
which we used in the 2023 season to extend the arm of Sailor Bison (our 2023 robot). This command uses activation through a button, and therefore does not use the isFinished()
method. This command closely follows the formatting of all commands.
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
/*
* Asimov's Laws:
* The First Law: A robot may not injure a human being or, through inaction, allow a human being to come to harm.
* The Second Law: A robot must obey the orders given it by human beings except where such orders would conflict with the First Law.
* The Third Law: A robot must protect its own existence as long as such protection does not conflict with the First or Second Law.
*/
package frc.robot.commands;
import edu.wpi.first.wpilibj2.command.CommandBase;
import frc.robot.subsystems.arm.ArmExtender;
public class ExtendArm extends CommandBase {
private ArmExtender armExtender;
/** Creates a new RetractArm. */
public ExtendArm(ArmExtender armExtender) {
// Use addRequirements() here to declare subsystem dependencies.
this.armExtender = armExtender;
addRequirements(armExtender);
}
// Called when the command is initially scheduled.
@Override
public void initialize() {
this.armExtender.extend();
}
// Called every time the scheduler runs while the command is scheduled.
@Override
public void execute() {}
// Called once the command ends or is interrupted.
@Override
public void end(boolean interrupted) {
this.armExtender.setArmSpeed(0);
}
// Returns true when the command should end.
@Override
public boolean isFinished() {
return false;
}
}
For a detailed look into commands, refer to the WPILib documentation on commands:
Subsystems
Subsystems are used to communicate with components on the robot directly. They hide operations that can be performed on the hardware that might damage it while exposing methods that make the component easy to use.
The purpose of subsystems is to expose individual actions that the component can do and make it easy to call them, such as retracting the arm or extending it. Subsystems also hide operations that can be harmful to the robot, such as overextending the arm or keeping the arm from moving too fast.
We use subsystems to control groups of motors and hardware that make up one component, such as all the motors for the drivetrain or a motor for a flywheel.
An example subsystem is the ArmIntake
subsystem in robot2023. This subsystem runs the intake on the arm whenever a command tells it to.
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
/*
* Asimov's Laws:
* The First Law: A robot may not injure a human being or, through inaction, allow a human being to come to harm.
* The Second Law: A robot must obey the orders given it by human beings except where such orders would conflict with the First Law.
* The Third Law: A robot must protect its own existence as long as such protection does not conflict with the First or Second Law.
*/
package frc.robot.subsystems.arm;
import edu.wpi.first.wpilibj.motorcontrol.Talon;
import edu.wpi.first.wpilibj2.command.SubsystemBase;
import frc.robot.Constants;
/**
* Subsystem that represents the claw on the arm. Can open the claw or close it to a given pressure.
*/
public class ArmIntake extends SubsystemBase {
private final Talon clawMotor;
/** Creates a new ArmClaw. Should be called once from {@link frc.robot.RobotContainer}. */
public ArmIntake() {
clawMotor = new Talon(Constants.Arm.Intake.ID_TALON_ARM_CLAW);
}
/** Stops the arm intake from spinning */
public void stop() {
clawMotor.set(0);
}
/**
* Sets the speed the intake runs at.
*
* @param setpoint the percent power to run the arm at
*/
public void set(Double setpoint) {
clawMotor.set(setpoint);
}
/**
* Subsystem periodic; called once per scheduler run.
*
* <p>Moves the claw to the current setpoint.
*/
@Override
public void periodic() {}
}
Binding Commands to Triggers & RobotContainer
Now that we know how to create subsystems and commands, let's look into how to actually run the commands we've made.
RobotContainer
All the robot's subsystems, commands, and controllers are instantiated within RobotContainer.java
(recall that instantiation means to create an instance of a class). Once we have them instantiated, we can define events that should cause different commands to run. These events are called triggers, and they're almost always a button being pressed on our controller.
Triggers
There are a number of triggers that you can bind commands to, but for today, we'll just worry about button mappings, since you'll use them primarily. For this, you'll want to make sure you have an XboxController
instantiated at the top of your RobotContainer
.
To create a new button mapping, you'll need to do nameOfXboxControllerObject.a().whileTrue(commandObjectToRun);
. This will run commandObjectToRun
while the a button is held down. It will end the command as soon as the button is released. There are a few other bindings you can use (like running a command when a button is pressed, or stopping a command when a button is pressed) which you can see in the WPILib documentation:
For an example RobotContainer
, see here:
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package frc.robot;
import frc.robot.Constants.OperatorConstants;
import frc.robot.commands.Autos;
import frc.robot.commands.ExampleCommand;
import frc.robot.subsystems.ExampleSubsystem;
import edu.wpi.first.wpilibj2.command.Command;
import edu.wpi.first.wpilibj2.command.button.CommandXboxController;
import edu.wpi.first.wpilibj2.command.button.Trigger;
/**
* This class is where the bulk of the robot should be declared. Since Command-based is a
* "declarative" paradigm, very little robot logic should actually be handled in the {@link Robot}
* periodic methods (other than the scheduler calls). Instead, the structure of the robot (including
* subsystems, commands, and trigger mappings) should be declared here.
*/
public class RobotContainer {
// The robot's subsystems and commands are defined here...
private final ExampleSubsystem m_exampleSubsystem = new ExampleSubsystem();
// Replace with CommandPS4Controller or CommandJoystick if needed
private final CommandXboxController m_driverController =
new CommandXboxController(OperatorConstants.kDriverControllerPort);
/** The container for the robot. Contains subsystems, OI devices, and commands. */
public RobotContainer() {
// Configure the trigger bindings
configureBindings();
}
/**
* Use this method to define your trigger->command mappings. Triggers can be created via the
* {@link Trigger#Trigger(java.util.function.BooleanSupplier)} constructor with an arbitrary
* predicate, or via the named factories in {@link
* edu.wpi.first.wpilibj2.command.button.CommandGenericHID}'s subclasses for {@link
* CommandXboxController Xbox}/{@link edu.wpi.first.wpilibj2.command.button.CommandPS4Controller
* PS4} controllers or {@link edu.wpi.first.wpilibj2.command.button.CommandJoystick Flight
* joysticks}.
*/
private void configureBindings() {
// Schedule `ExampleCommand` when `exampleCondition` changes to `true`
new Trigger(m_exampleSubsystem::exampleCondition)
.onTrue(new ExampleCommand(m_exampleSubsystem));
// Schedule `exampleMethodCommand` when the Xbox controller's B button is pressed,
// cancelling on release.
m_driverController.b().whileTrue(m_exampleSubsystem.exampleMethodCommand());
}
/**
* Use this to pass the autonomous command to the main {@link Robot} class.
*
* @return the command to run in autonomous
*/
public Command getAutonomousCommand() {
// An example command will be run in autonomous
return Autos.exampleAuto(m_exampleSubsystem);
}
}
Applying Our Knowledge
Try to re-write your motor turning project from before in command-based. You'll want to create a separate, brand-new project—don't try to convert your old timed project to command-based.
All the information you need should be in the sections above on this page.
As always, please try it on your own before you check the solution!
Solution
Click the link below for a repository containing the full solution:
Last updated