msoucy.me

Code, games, and sarcasm
Matt Soucy

FIRST Software Architecture: Command Based Programming

in code by Matt Soucy - Comments

This post is a modified and expanded form of a presentation given to my FIRST Robotics Competition team

Introduction

When writing code for a robot, there are a number of ways to organize the code. The form that's become the most prolific, though, is command-based programming.

Command-based programming is a step more abstract than imperative programs such as what might be done by a beginner. This can lead to some confusion, as most individual robot tasks can be viewed linearly:

These ideas seem to fit into the iterative model provided by WPILib, but fall apart when multiple concepts are joined. One method has to keep track of all the logic for multiple subsystems on the robot, how they all interact, etc. This is why the command-based model exists.

The command model breaks robot code into a few different layers: components, subsystems, and commands. The main idea is that a robot is made of several subsystems, each of which has several sensors and manipulators ("components"), and some number of actions that it can perform ("commands"). A command is a process that has a definite beginning, middle, and end, that doesn't care about what's using it. This allows for an autonomous method to easily be written, using existing commands made for teleoperated use.

Components

A "component" is a term for the code interface to a sensor or motor. Any form of input or output that the robot deals with, could be seen as dealing with a component of some kind. Some example components:

There can also be custom components - in the past, the Chop Shop has used a MultiSpeedController, which behaves as a speed controller, but controls several SpeedController instances together. This is useful for locking two motors to the same speed.

Subsystems

A Subsystem is made of components, and adds a layer of abstraction. This is important, because it allows the overall robot code to not have to deal with individual sensors and motors.

Subsystems and Abstraction

Let's say we have an arm on a robot. This arm was designed to use a motor to raise and lower it. So we write the outline of our subsystem:

public class ClawArm extends Subsystem {
    Victor motor;

    public ClawArm()
    {
        motor = new Victor(RobotMap.Pwm.RollerVictor);
    }
}

Now, the subsystem can't do much until we have a way to control it:

public class ClawArm extends Subsystem {
    Victor motor;

    public ClawArm()
    {
        motor = new Victor(RobotMap.Pwm.RollerVictor);
    }

    public void moveForward()
    {
        motor.setSpeed(1);
    }

    public void moveBackward()
    {
        motor.setSpeed(-1);
    }
}

In order for the rest of the robot to use this new functionality, we need to create a Command:

public class RaiseArm extends Command {

    public RaiseArm() {
        requires(Robot.arm);
    }

    protected void initialize() {
        Robot.arm.moveForward();
    }

    protected void execute() { }

    protected boolean isFinished() {
        return true;
    }

    protected void end() { }

    protected void interrupted() { }
}

Now comes the problem - what if the design changes? What direction is "forward"?

Designing For Reusability

After some deliberation, the design for the arm has been changed to use a solenoid instead.

What needs to change?

This can be done by making changes such as:

public class ClawArm extends Subsystem {
    DoubleSolenoid solenoid;

    public ClawArm()
    {
        solenoid = new Victor(RobotMap.Digital.ArmUp,
                              RobotMap.Digital.ArmDown);
    }

    public void moveForward()
    {
        solenoid.set(DoubleSolenoid.Value.kForward);
    }

    public void moveBackward()
    {
        solenoid.set(DoubleSolenoid.Value.kReverse);
    }
}

While not horrible, because the command doesn't have to change, it's still limiting because of the concept of "forward" and "backward".

This can be improved by changing the names:

public class ClawArm extends Subsystem {
    DoubleSolenoid solenoid;

    public ClawArm()
    {
        solenoid = new Victor(RobotMap.Digital.ArmUp,
                              RobotMap.Digital.ArmDown);
    }

    public void raise()
    {
        solenoid.set(DoubleSolenoid.Value.kForward);
    }

    public void lower()
    {
        solenoid.set(DoubleSolenoid.Value.kReverse);
    }
}

Only by changing the names, the resulting command is much clearer:

public class RaiseArm extends Command {

    public RaiseArm() {
        requires(Robot.arm);
    }

    protected void initialize() {
        Robot.arm.raise();
    }

    protected void execute() { }

    protected boolean isFinished() {
        return true;
    }

    protected void end() { }

    protected void interrupted() { }
}

The command itself is still only one line, but it's obvious what it's intended to do.

This would never have had to change if the subsystem had called the method .raise() in the first place!

Lessons Learned for Subsystems

Commands

A command is:

They can be simple, just calling one method of one subsystem:

Or they might be complex, with sequencing and tasks in parallel:

Command Parts

When creating a command, you have to think carefully about its behavior:

public class CustomCommand extends Command {

    public CustomCommand() {
        // Tell the command which subsystem it uses
        requires(Robot.mysubsystem);
    }

    protected void initialize() {
        // Happens once, when the command is first run
    }

    protected void execute() {
        // Happens on loop for as long as the command is active
    }

    protected boolean isFinished() {
        // Tells the command when to finish
        return true;
    }

    protected void end() {
        // Runs after the command is finished
    }

    protected void interrupted() {
        // Runs if another command wants to use its subsystem
    }
}

There's a definitive flow to each command. Each command is created, then initalizes. It runs a chunk of code until it determines that it's finished, and then ends. If it gets interrupted, it knows how to clean up after itself. Almost every command can be reduced to this pattern.

Using Commands

In general, there are three kinds of command:

Depending on what kind of command you have, your command class can look very different.

Instant Commands

An instant command has behavior that happens once, when the command first executes.

public class ShootBall extends Command {

    public ShootBall() {
        requires(Robot.shooter);
    }

    protected void initialize() {
        Robot.shooter.shoot();
    }

    protected void execute() { }

    protected boolean isFinished() {
        return true;
    }

    protected void end() { }

    protected void interrupted() { }
}

The relevant methods are initialize(), isFinished(), and the constructor! The rest can be empty. Returning true from isFinished() just runs initialize() and exits.

This can be made even simpler:

public class ShootBall extends InstantCommand {

    public ShootBall() {
        requires(Robot.shooter);
    }

    protected void initialize() {
        Robot.shooter.shoot();
    }
}

InstantCommand takes care of execute() and isFinished(), and by default end() and interrupted() do nothing.

Continuous Command

A continuous command does one thing, until a condition is met.

public class RaiseElevatorToTop extends Command {

    public RaiseElevatorToTop() {
        requires(Robot.elevator);
    }

    protected void initialize() { }

    protected void execute() {
        Robot.elevator.raise();
    }

    protected boolean isFinished() {
        return Robot.elevator.isAtTop();
    }

    protected void end() { Robot.elevator.halt(); }

    protected void interrupted() { Robot.elevator.halt(); }
}

Some important details to note:

Command Groups

A command group is a way to combine multiple related things, at once or sequentially:

public class AutoScoreHigh extends CommandGroup {

    public AutoScoreHigh() {
        // No requires() call, the subcommands do that
        addParallel(new PrepareBallRollers());
        addSequential(new AlignRobotWithTarget());
        addSequential(new WaitForChildren()); // Wait half a second
        addSequential(new PrintCommand("Fire in the hole!"));
        addSequential(new LaunchBall());
        addSequential(new WaitCommand(0.5)); // Wait half a second
        addSequential(new StopBallRollers());
    }

}

Further development

In my next post, I'll analyze the gains made by switching language from Java to C#. Later on, I'll demonstrate robot code built using what I refer to as "functional command programming", by rewriting the same robot into F#.

blog comments powered by Disqus