Applied Command-Based Programming
Now that you understand what commands and subsystems are, you're ready to apply them to make a basic robot program.
Materials
- A Romi robot
- A computer with WiFi
- A USB game controller or joystick(s)
Driving the Romi
First,
create a new project
like you did in 2.2
using the Romi - Command Robot
template for Java.
The template includes a few files that are important to understand:
RobotContainer.java
- This file acts kind of like a "configuration" file for the robot. You'll use it to configure button bindings, to set auto commands, and for anything else which doesn't cleanly fit into a subsystem or command.subsystems/RomiDrivetrain.java
- This is a subsystem abstraction for the Romi's drivetrain. It's a good example of a simple, idiomatic subsystem, and you'll be making use of it in this tutorial.
If you look at RomiDrivetrain.java
,
you'll see that it has a few member variables
which represent the Romi's motors and encoders.
Then, it has methods which can be used
to control the motors and read from the encoders.
You'll be using these methods in the commands you write.
A Drive Command
Now, let's write a command.
Create a new file in the commands
directory called ArcadeDriveCommand.java
.
Then, scaffold a Command
in it:
package frc.robot.commands;
import java.util.Set;
import edu.wpi.first.wpilibj2.command.Command;
import edu.wpi.first.wpilibj2.command.Subsystem;
public class ArcadeDriveCommand implements Command {
public ArcadeDriveCommand() {
System.out.println("Hello, World!");
}
@Override
public Set<Subsystem> getRequirements() {
return null;
}
}
If you run the robot code now, nothing will happen. This is because, although you've created a command, you haven't told the robot to run it anywhere.
To do that,
you'll want to edit
the configureButtonBindings
method in RobotContainer.java
.
This method is called when the robot is initialized,
and is where button bindings are configured
using WPILib's input system.
Since you want the ArcadeDriveCommand
to run most of the time,
you can set it to be the drivetrain's default command
with the following code:
import frc.robot.commands.ArcadeDriveCommand;
...
public void configureButtonBindings() {
m_robotContainer.getDrivetrain().setDefaultCommand(
new ArcadeDriveCommand());
}
Now, if you run the robot code, you should see the message "Hello, World!" printed to the console.
Now that your command is running, you can add some code to it to actually drive the robot.
First, you'll need to add a subsystem to the command.
This is done by creating
a member variable to hold the subsystem,
a constructor parameter to initialize it,
and adding it to the requirements returned by getRequirements
:
public class ArcadeDriveCommand implements Command {
private final RomiDrivetrain drivetrain;
public ArcadeDriveCommand(RomiDrivetrain drivetrain) {
this.drivetrain = drivetrain;
}
@Override
public Set<Subsystem> getRequirements() {
return Set.of(drivetrain);
}
}
Since you need a way to get the speed and rotation values from the controller,
you should add two member variables which "supply" those values.
It would also be possible to read the values directly in the command,
but this spreads out control logic across multiple classes,
which is not ideal.
Instead, the control method will be defined
in the RobotContainer
class when the command is constructed.
commands/ArcadeDriveCommand.java
:
private final FloatSupplier speed;
private final FloatSupplier rotation;
public ArcadeDriveCommand(
RomiDrivetrain drivetrain,
FloatSupplier speed,
FloatSupplier rotation
) {
this.drivetrain = drivetrain;
this.speed = speed;
this.rotation = rotation;
}
RobotContainer.java
:
public class RobotContainer {
private final RomiDrivetrain romiDrivetrain = new RomiDrivetrain();
private final Joystick joystick = new Joystick(0);
public RobotContainer() {
configureButtonBindings();
}
private void configureButtonBindings() {
romiDrivetrain.setDefaultCommand(
new ArcadeDriveCommand(
romiDrivetrain,
() -> (float) joystick.getY(),
() -> (float) joystick.getX()));
}
public Command getAutonomousCommand() {
return null;
}
}
Note: if you're using a different kind of controller, you'll need to use different class(es) and methods to read the relevant values.
Then, you can use the drivetrain's methods
inside the command's execute
method,
which is called repeatedly while the command is running:
@Override
public void execute() {
drivetrain.arcadeDrive(speed.getAsFloat(), rotation.getAsFloat());
}
Now, run the robot and you should be able to drive it around!