Structuring Your Project

While WPILib and the commands v3 framework are flexible and don’t force code to be written in a specific way, we recommend a standard structure to make code easier to understand (both for programmers and CSA volunteers).

  1. Write a single Robot class that owns the mechanisms and driver controls.

  2. Write separate classes that implement Mechanism to represent each mechanism on the robot.

  3. Use OpMode classes to define OpMode-specific behaviors.

  4. Any commands that require a single mechanism should be written as Command methods in that mechanism’s class.

  5. Any commands that require multiple mechanisms should be written as Command methods in a related helper class.

A Single Robot Class

WPILib needs a single entry point to start your program. Use a Robot class that extends from OpModeRobot. Any behavior that should always be active - regardless of OpMode - should be defined here, such as default commands or trigger bindings.

Mechanisms should be declared as public final fields in the robot class and initialized in the field declaration or in the constructor. The former makes the code a little more concise, while the latter allows for flexibility if different mechanism implementations exist.

22@Logged
23public class Robot extends OpModeRobot {
24  public final SwerveDrive swerveDrive = new SwerveDrive();
25  public final Shooter shooter = new Shooter();
26  public final Intake intake = new Intake();
27  public final PoseEstimator poseEstimator = new PoseEstimator();
28  public final CommandGamepad controller = new CommandGamepad(1);
29
30  public final Trigger inNeutralZone = new Trigger(() -> poseEstimator.inZone(NEUTRAL_ZONE));
31
32  // Store the scheduler in a field for Epilogue to pick up and log for us
33  private final Scheduler scheduler = Scheduler.getDefault();
34
35  /** Initializes the robot class and sets safe default commands for all its mechanisms. */
36  public Robot() {
37    swerveDrive.setDefaultCommand(swerveDrive.idle());
38    shooter.setDefaultCommand(shooter.idle());
39    intake.setDefaultCommand(intake.idle());
40  }
41
42  @Override
43  public void robotPeriodic() {
44    // 1. Update odometry so commands will be working with up-to-date information.
45    poseEstimator.odometryUpdate(swerveDrive.getGyroHeading(), swerveDrive.getModulePositions());
46
47    // 2. Run the scheduler to poll triggers and execute our commands.
48    scheduler.run();
49
50    // 3. Update telemetry.
51    Epilogue.update(this);
52  }
53
54  /**
55   * A command that uses all the mechanisms on the robot to aim and shoot at a specific target.
56   *
57   * @param targetPose The target position to shoot at.
58   * @return A command to shoot at a target
59   */
60  public Command shootAt(Pose2d targetPose) {
61    return Command.noRequirements(
62            coroutine -> {
63              coroutine.await(swerveDrive.aimAt(poseEstimator::getEstimatedPose, targetPose));
64
65              coroutine.awaitAll(
66                  intake.agitate(),
67                  shooter.shootAtHub(
68                      () -> {
69                        return Meters.of(
70                            targetPose
71                                .minus(poseEstimator.getEstimatedPose())
72                                .getTranslation()
73                                .getNorm());
74                      }));
75            })
76        .named("Robot.ShootAt[" + targetPose + "]");
77  }
78}

Mechanism Classes

Each physical mechanism on a robot should have a corresponding class in the codebase. These are typically in the first.robot.mechanisms package.

Mechanisms are often a combination of multiple actuators, such as a slapdown intake with one actuator or set of actuators to run intake rollers and a separate actuator that extends and retracts the intake. It’s usually simpler for each independent actuator to have its own Mechanism class; if they’re only used in the context of a larger mechanism, they can still be separate classes, but only used by a single encompassing mechanism.

Mechanism-level Commands

Commands that only interact with a single mechanism should be defined as methods in that mechanism’s class using the run or runRepeatedly builder methods provided by the Mechanism interface. These builder methods make the created command automatically require the mechanism so you can’t forget to set the requirement.

Warning

Command methods are designed to create a Command object that will be run at a later point in the program. Only the code in the lambda function passed to run or runRepeatedly will execute when the command is running.

In the example of a slapdown intake, there could be three classes:

  1. IntakeRoller, for controlling just the rollers

  2. IntakeWrist, for controlling the deployment of the intake

  3. Intake, which combines both the rollers and the wrist

IntakeRoller.java

15@Logged
16public class IntakeRoller implements Mechanism {
17  private final ExampleSmartMotorController motor =
18      new ExampleSmartMotorController(IntakeConstants.ROLLER_MOTOR_ID);
19
20  /**
21   * Stops the roller motor. This command keeps the roller off until interrupted by another command.
22   *
23   * @return Command to stop the roller
24   */
25  public Command stop() {
26    return runRepeatedly(() -> motor.setThrottle(0.0)).named("Intake.Roller.Stop");
27  }
28
29  @Override
30  public Command idle() {
31    return stop();
32  }
33
34  /**
35   * Starts the roller motor in intake mode. This command keeps the roller running until interrupted
36   * by another command.
37   *
38   * @return Command to start the roller in intake mode
39   */
40  public Command intake() {
41    return run(coroutine -> {
42          motor.setSetpoint(
43              ExampleSmartMotorController.PIDMode.VELOCITY,
44              IntakeConstants.ROLLER_INTAKE_SPEED.in(RadiansPerSecond));
45
46          coroutine.park();
47        })
48        .named("Intake.Roller.Intake");
49  }
50
51  /**
52   * Starts the roller motor in expel mode. This command keeps the roller running until interrupted
53   * by another command.
54   *
55   * @return Command to start the roller in expel mode
56   */
57  public Command expel() {
58    return run(coroutine -> {
59          motor.setSetpoint(
60              ExampleSmartMotorController.PIDMode.VELOCITY,
61              IntakeConstants.ROLLER_EXPULSION_SPEED.in(RadiansPerSecond));
62
63          coroutine.park();
64        })
65        .named("Intake.Roller.Expel");
66  }
67}

IntakeWrist.java

17@Logged
18public class IntakeWrist implements Mechanism {
19  private final ExampleSmartMotorController motor =
20      new ExampleSmartMotorController(IntakeConstants.WRIST_MOTOR_ID);
21
22  public Command stop() {
23    return runRepeatedly(() -> motor.setThrottle(0.0)).named("Intake.Wrist.Stop");
24  }
25
26  @Override
27  public Command idle() {
28    return stop();
29  }
30
31  /**
32   * Moves the wrist to a specific angle. The command will exit when the target angle is reached.
33   *
34   * @param target The target angle to move to
35   * @return A command that moves the wrist to the target angle
36   */
37  public Command moveToAngle(Angle target) {
38    double targetRads = target.in(Radians);
39    double tolerance = Radians.convertFrom(0.5, Degrees);
40
41    return run(coroutine -> {
42          motor.setSetpoint(ExampleSmartMotorController.PIDMode.POSITION, targetRads);
43
44          coroutine.waitUntil(() -> Math.abs(motor.getEncoderDistance() - targetRads) <= tolerance);
45        })
46        .named("Intake.Wrist.MoveToAngle[" + target.toLongString() + "]");
47  }
48
49  /**
50   * Moves the wrist to a specific angle. The command will hold the wrist at the target angle until
51   * interrupted.
52   *
53   * @param target The target angle to hold
54   * @return A command that holds the wrist at the target angle
55   */
56  public Command holdAngle(Angle target) {
57    double targetRads = target.in(Radians);
58
59    return run(coroutine -> {
60          motor.setSetpoint(ExampleSmartMotorController.PIDMode.POSITION, targetRads);
61          coroutine.park();
62        })
63        .named("Intake.Wrist.HoldAngle[" + target.toLongString() + "]");
64  }
65
66  /**
67   * Moves the wrist to the stowed position and holds it there until interrupted.
68   *
69   * @return A command that stows the wrist
70   */
71  public Command stow() {
72    return holdAngle(IntakeConstants.WRIST_STOW_ANGLE);
73  }
74
75  /**
76   * Moves the wrist to the down position and holds it there until interrupted.
77   *
78   * @return A command that moves the wrist to the down position
79   */
80  public Command down() {
81    return holdAngle(IntakeConstants.WRIST_DOWN_ANGLE);
82  }
83}

Intake.java

12@Logged
13public class Intake implements Mechanism {
14  private final IntakeWrist wrist = new IntakeWrist();
15  private final IntakeRoller roller = new IntakeRoller();
16
17  /**
18   * Intakes fuel off the ground. This command will run until canceled or interrupted by another
19   * command.
20   *
21   * @return An intake command
22   */
23  public Command intake() {
24    return run(coroutine -> {
25          coroutine.awaitAll(wrist.down(), roller.intake());
26        })
27        .named("Intake.Intake");
28  }
29
30  /**
31   * Stops the intake mechanism. This stops the roller and depowers the wrist, which may sag under
32   * gravity.
33   *
34   * @return A command that stops the intake mechanism
35   */
36  public Command stop() {
37    return run(coroutine -> {
38          coroutine.awaitAll(wrist.stop(), roller.stop());
39        })
40        .withPriority(Command.LOWEST_PRIORITY)
41        .named("Intake.Idle");
42  }
43
44  @Override
45  public Command idle() {
46    return stop();
47  }
48
49  /**
50   * Stows the intake mechanism. This raises the wrist and stops the roller.
51   *
52   * @return A command that stows the intake mechanism
53   */
54  public Command stow() {
55    return run(coroutine -> {
56          coroutine.awaitAll(wrist.stow(), roller.stop());
57        })
58        .named("Intake.Stow");
59  }
60
61  /**
62   * Uses the intake mechanism to agitate fuel inside the hopper. The roller will spin to encourage
63   * fuel to move around and help unblock jams.
64   *
65   * @return A command that agitates the intake mechanism
66   */
67  public Command agitate() {
68    return run(coroutine -> {
69          coroutine.fork(roller.intake());
70
71          while (true) {
72            // await() will yield internally until the wrist has moved to the desired position,
73            // so we don't need to explicitly yield here
74            coroutine.await(wrist.moveToAngle(IntakeConstants.WRIST_AGITATE_UP));
75            coroutine.await(wrist.moveToAngle(IntakeConstants.WRIST_AGITATE_DOWN));
76          }
77        })
78        .named("Intake.Agitate");
79  }
80
81  /**
82   * Uses the intake mechanism to expel fuel from the hopper.
83   *
84   * @return A command that expels fuel from the intake mechanism
85   */
86  public Command outtake() {
87    return run(coroutine -> {
88          coroutine.awaitAll(wrist.down(), roller.expel());
89        })
90        .named("Intake.Outtake");
91  }
92}

Multi-Mechanism Commands

Commands that control multiple mechanisms should coordinate smaller per-mechanism commands. From the intake example above, take note that the main Intake class has no hardware of its own; all hardware control is done in the separate roller and wrist mechanisms, while Intake is only responsible for coordinating the two.

Not every multi-mechanism control scheme needs to use a encapsulating mechanism to coordinate control. The intake example uses that setup because the wrist and roller are part of the intake, but control of disparate mechanisms like an intake and an arm can use a no-requirement command that forks and coordinates mechanism-specific commands.

Complex multi-mechanism control can also be done using state machines.

OpMode Classes

OpMode classes let you group mode-specific logic together in one place without cluttering the main robot class. Because the command scheduler automatically scopes everything you do in the OpMode class to that OpMode, you don’t need to worry about logic specific to that OpMode leaking out and still running when modes change. Commands that are only used in a specific OpMode can be defined in that OpMode class, too.

OpMode constructors are generally all that’s needed. WPILib will automatically call them when that mode is selected on the driver station, passing in the main Robot object if the constructor accepts it.

15@Autonomous
16public class SweepAuto implements OpMode {
17  private final Trigger enabled = new Trigger(RobotState::isEnabled);
18
19  /**
20   * Creates a new autonomous mode that sweeps the left trench and scores on the hub. This
21   * constructor is called automatically by the OpModeRobot framework when the program starts up.
22   *
23   * @param robot The robot instance to control.
24   */
25  public SweepAuto(Robot robot) {
26    // Start the intake stowed
27    robot.intake.setDefaultCommand(robot.intake.stow());
28
29    // Extend the intake once we cross into the neutral zone and leave it extended for the entire
30    // opmode
31    robot.inNeutralZone.onTrue(robot.intake.intake());
32
33    // Once the robot is enabled, start following a sweep path through the left trench,
34    // into the neutral zone, then back over the bump.
35    // When we return to the alliance zone, aim at the hub and start shooting.
36    enabled.onTrue(sweepAndScore(robot));
37  }
38
39  /**
40   * Creates a command that drives the robot in a path that sweeps through the neutral zone to
41   * gather fuel, returns to the alliance zone, and then shoots at the hub until the end of the
42   * autonomous period.
43   *
44   * @param robot The robot instance to control
45   * @return A sweep-and-score command
46   */
47  private Command sweepAndScore(Robot robot) {
48    return Command.noRequirements(
49            coroutine -> {
50              coroutine.await(robot.swerveDrive.followPath("nz-sweep-left-trench"));
51
52              FieldConstants.targetHub()
53                  .ifPresent(
54                      hub -> {
55                        coroutine.await(robot.shootAt(hub));
56                      });
57            })
58        .named("Sweep and Score");
59  }
60}
package first.robot.mechanisms;

import static org.wpilib.units.Units.*;

import module wpilib;
import module wpilib.command3;

public class Arm implements Mechanism {
  // ... hardware definitions, constructors, PID controllers and so on ...

  // Good example:
  public Command stop() {
    return runRepeatedly(() -> motor.stop()).named("Stop Arm");
  }

  public Command incorrectStop() {
    // Incorrect: This will print to console when the command is created, not when it actually runs
    System.out.println("Stopping the arm motor! (but not really)");

    return runRepeatedly(() -> motor.stop()).named("Incorrect Stop Arm");
  }
}

Highly complicated commands with a lot of logic can harm readability of a mechanism class, and can optionally be implemented as standalone class-based commands in the same package as the mechanism. The mechanism class should still have a method to create and return these commands.

As a rule of thumb, commands with more than 15-20 lines of code are good candidates for being moved to class-based commands. Bits of logic can be more easily split into smaller helper methods, but you will need to implement all of the required Command methods yourself.

package first.robot.mechanisms;

import module wpilib.command3;
import first.robot.commands.drivetrain.VeryComplicatedCommand;

public class Drivetrain implements Mechanism {
  // ... hardware definitions, constructors, PID controllers and so on ...

  public Command veryComplicatedCommand() {
    return new VeryComplicatedCommand(this);
  }
}
package first.robot.commands.drivetrain;

import module java.base;
import module wpilib.command3;

public class VeryComplicatedCommand implements Command {
  private final Drivetrain drivetrain;
  private final Set<Mechanism> requirements;

  public FollowPathPartsCommand(Drivetrain drivetrain) {
    this.drivetrain = drivetrain;
    this.requirements = Set.of(drivetrain);
  }

  @Override
  public void run(Coroutine coroutine) {
    // ... lots of complicated logic ...
  }

  @Override
  public Set<Mechanism> requirements() {
    return requirements;
  }

  @Override
  public String name() {
    return "VeryComplicatedCommand";
  }
}