Lesson 2.0 - Angelbots1339/ProgramingLessons GitHub Wiki

Overview


In this lesson you will learn the basics of Command-Based Programming by creating and running simple autonomous routines. All the code will be done in the Auto and AutoFactory classes located at `src\main\java\frc\robot\commands.

Command Based

Command-Based Programming is the design pattern we use to structure our robot code. It is based around two core abstractions: Subsystems, which you learned about in Lesson-1.0, and Commands. Read the WPI Docs to learn more.

Commands represent actions the robot can take. Commands run when scheduled, until they are interrupted or their end condition is met. See Commands for more info.

Subsystems represent independently-controlled collections of robot hardware (such as motor controllers, sensors, pneumatic actuators, etc.) that operate together. They allow for more organized and modular code. Subsystems back the resource-management system of command-based: only one command can use a given subsystem at the same time.

!

The Command Scheduler handles everything to do with running commands, such as starting, ending, and interrupting them. It ensures there are no conflicts, so that every subsystem is only running a single command at a time.

Part 2.1: Creating a Command


Info

There are lots of convenience features and different ways to create and run commands. If you are curious, you can read the WPI command based docs.

The first way of creating commands is having a separate file for each one. Take a look in Auto.java for an example. There are 4 default methods:

// Called when the command is initially scheduled.
@Override
public void initialize() {}

// 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) {} 

// Returns true when the command should end.
@Override
public boolean isFinished() {

return false;

}

which are pretty self-explanatory. Initialize is called once when the command starts, Execute is called repeatedly, End is called when the command is ended, and isFinished can be used to end the command yourself when it's done with its goal.

In the constructor:

private Drive drive; // Make local variable

/** Creates a new Test. */

public Auto(Drive drive) { // Pass in the subsystem from RobotContainer

// Use addRequirements() here to declare subsystem dependencies.

  

this.drive = drive; // Set the local variable

addRequirements(drive); // Tell command scheduler the requirements

}

you notice that we are doing something with the Drive subsystem.

Every subsystem gets instanced in RobotContainer, and those instances must be passed around where they are needed. When a new Auto command is created, you must pass the instances of the subsystems that it requires, and then use addRequirements. This tells the Command Scheduler what subsystems a given command requires so it can resource manage correctly. In this case, the only subsystem that needs to be passed in is Drive.

One apparent issue is that you can't access the subsystem anywhere outside of the constructor, like in the execute() method, where you need to. To get around this, you make a local variable, and then assign the instance to it. That's where you get to the confusing this.drive = drive.

Now, that you can access the Drive subsystem inside all the command's methods, you can make the command do something.

Task

Make a simple auto that drives forward at 1m/s, and then stops the robot once it's moved 5 meters.

Put your code in Auto.java.

Tip

Use the Drive.drive(double forwardVel) method to move, and Drive.getPos().getX() to get the current X value of the robot's position. Keep in mind you are feeding it velocity values for a tank drive.

Test

How to run tests Expected result

!

Solution
// Called when the command is initially scheduled.
@Override
public void initialize() {

  drive.drive(1);
}

// 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) {

  drive.drive(0);
}  

// Returns true when the command should end.
@Override
public boolean isFinished() {

  return drive.getPos().getX() >= 5;
}

Part 2.2: Inline Commands


Info

Making an entire file for a command can be necessary when there's a lot of code, but for many smaller commands like the one you just wrote that do very little, there are better ways.

Inline Commands can make code easier to read and much shorter to write. There are a few types, and you should read more.

As an example, the code from Lesson 2.1 can be shortened to a StartEndCommand. This type of inline command takes 2 methods and the required subsystems as parameters. It runs the first method when the command is scheduled, and the second one when the command is ended. Essentially the initialize() and end() methods inside your Auto command.

StartEndCommand myCommand = new StartEndCommand(() -> {/*Code*/}, mySubsystem1.stopMotors(), mySubsystem1);

If you notice, we are using Lambdas in place of methods () -> {}. These are essentially inline methods, and are common in inline commands to keep them, well, inline. Any code you want to run goes in the curly braces {}, and if you only have one line of code to run, you can forgo the braces and semicolon ;. You can also use Method References instead of Lambdas in certain places, so if you see class::method anywhere in the code, it's basically just a lambda.

The issue with the StartEndCommand, if you notice, is that it will never end on its own, and relies on being stopped by the command scheduler eventually. In this case, you want to stop the robot once it has moved 5 meters, so you need to add on some extra code.

This can be achieved through Command Compositions. In this example, we can use .until() to end the command once a condition is met (once again, we use Lambdas).

myCommand.until(() -> /* Condition */)

Task

Use inline commands to make an auto that drives forward at a velocity of 2m/s until it reaches 10 meters, where it should stop.

Put your code in AutoFactory.java inside the getSimpleInlineAuto method. Create a command, and then make sure the method returns that command.

Test

How to run tests Expected result

!

Solution
public static Command getAuto(Drive drive) {

  Command command = new StartEndCommand(
  	() -> drive.drive(2),
  	() -> drive.drive(0),
  	drive).until(() -> drive.getPos().getX() >= 10);

  return command;
}

Part 2.3: Command Groups


Info

As you should know, autonomous routines can get a lot more complex than simply driving in a straight line. The easiest way to do this is break the auto into several commands, and run those commands one after another.

Sequential Command Groups do exactly this. They allow you to string together several commands that run one after another. When the first command ends, the next will start, and so on to the end. As an example, you can use this to deploy an intake and then start running it, or for placing an object that requires multiple steps. In this case, you might drive forward, turn, and then drive forward again.

// Commands will run in sequence
Command command = new SequentialCommandGroup(
                new StartEndCommand(
                        () -> {},
                        () -> {},
                        mySubsystem1),
                new StartEndCommand(
                        () -> {},
                        () -> {},
                        mySubsystem1)
                    );

Parallel Command Groups allow you to run two commands at the same time (in parallel, as the name suggests). There are 3 different types, and the difference between them is when the entire group ends.

  • Parallel, the command group finishes only when all commands end.
  • Race, the command group finishes when any command ends. All other commands in the group are then interrupted.
  • Deadline, similar to race, but finishes when a specific command ends, and all others are interrupted.

Task

Create an auto that drives forward until it hits 3 meters on X, turns 90º, and then drives and runs the intake at the same time until it hits 3 meters on the Y.

Put your code in AutoFactory.java inside the getSequentialAuto method. Create a command, and then make sure the method returns that command.

Tip

  • Use Intake.runIntake(double speed) to run the intake.
  • Use Drive.drive(double forwardVel, double turnVel) to turn. Remember you are feeding the drive base a velocity. forwardVel is in meters per second, and turnVel is in degrees per second.
  • Use Drive.getPos().getRotation().getDegrees() to get the current rotation of the robot.
  • Turn with a positive velocity to get to 90 deg fastest.
  • You are supposed to use both Sequential and Parallel groups.

Test

How to run tests Expected result

!

Solution
public static Command getSequentialAuto(Drive drive, Intake intake) {

      Command command = new SequentialCommandGroup(
              new StartEndCommand(
                      () -> drive.drive(0.5),
                      () -> drive.drive(0),
                      drive).until(() -> drive.getPos().getX() == 3),
              new StartEndCommand(
                      () -> drive.drive(0, 45),
                      () -> drive.drive(0, 0),
                      drive).until(() -> drive.getPos().getRotation().getDegrees() == 90),
              new ParallelRaceGroup(
                      new StartEndCommand(
                              () -> drive.drive(0.5),
                              () -> drive.drive(0),
                              drive).until(() -> drive.getPos().getY() == 3),
                      new StartEndCommand(
                              () -> intake.runIntake(1),
                              () -> intake.runIntake(0),
                              intake)));

      return command;
  }

Go on to Lesson-3.0.

⚠️ **GitHub.com Fallback** ⚠️