Skip to content
This repository has been archived by the owner on Mar 31, 2019. It is now read-only.

Command Based Structure

nsun200 edited this page Jan 12, 2013 · 11 revisions

Explanation of Command-based Robot

FRC has done a remarkable job providing the tools to create complex robot programs. However, they have not been so thorough in providing documentation as to how to first pick up these tools and get to work. Through hours of browsing forums of other team’s and contributing to writing my own team’s (Team 1014) Java Command-based robot code, I have developed a much better understanding of the structure and the connections between classes that FRC expects programmers to use. This document explores Command-based Java robot programs, the new paradigm supported as of 2012.

Disclaimer: Although this tutorial is not highly technical, a basic understanding of Java (classes, methods, variables) is assumed. If you do not have enough experience in using Java, there are many tutorials available online that do an excellent job at introducing the basics and getting you up to speed. If any of this tutorial is unclear, poorly worded, or factually incorrect, please email the author at [email protected] for clarification.

Subsystems are fairly simple to understand: they are classes meant to contain groupings of variables that control related components of the robot. One example of a Subsystem could be the shooter system of the robot. A SpeedController (Victor, Jaguar, etc.) used to change the angle of the controller as well as a Pneumatic switch may be declared in this class. To be clear, these classes control groups of related hardware. Like the Shooting system, Drive train, Ball gather, etc. Subsystems should never overlap in their included controller variables. An example of what not to do is as follows:

public class DriveTrain extends Subsystem
{
	Jaguar leftSide, rightSide;
	…
	private DriveTrain()
	{ 
		leftSide = new Jaguar(1);
		rightSide = new Jaguar(2);
	}
}

public class BallGatherer extends Subsystem
{
	Jaguar left, right;
	… 
	private BallGatherer()
	{
		left = new Jaguar(1); //duplicate hardware!
		right = new Jaguar(2); // bad bad bad bad bad!
	}
}

Methods instructing these hardware components how to do basic actions should also be coded for in this Subsystem. For instance, in our shooter Subsystem, potential methods could be shoot(), elevate(double degrees), and getElevationOfShooter(). These are methods that directly interface with the hardware that perform common tasks. These methods should deal exclusively with the hardware initialized in the class, and shouldn’t reach out to other Subsystems for data. That is the purpose of Commands.

One method inherited by Subsystem is public void initDefaultCommand(). This method should contain a call to the method setDefaultCommand(Command c). This line simply sets the first Command that it should use when initialized. Less clear, is the singleton design that Subsystems are meant to implement. Singleton means that there is only one, universal copy of something. So, in the context of Subsystems, it means that only one Subsystem controlling each piece of hardware should exist per program. In order to follow this design scheme, a static method getInstance() should be included in each Subsystem such as the following:

public class Shooter extends Subsystem
{
	public static Shooter instance;

        public static Shooter getInstance()
        {
	   if (instance == null)
		instance = new Subsystem();

	   return instance;
        }

        private Shooter()  //private so no duplicate Subsystem is created 
        { 
              //initializes variables such as SpeedControllers, Pneumatics, etc.
        }
}

CommandBase is a class provided as an example from NetBeans. CommandBase extends Command, but it will be used for a different purpose than most other Commands. CommandBase has a static method init() which initializes a static instance of each Subsystem. The instance of each Subsystem can now be inherited through classes that extend CommandBase. Also, all Subsystems can now be initialized at once in the main class through a single method call to CommandBase.init().

public class CommandBase extends Command
{
	public static Shooter shooter;
        public static void init()
        {
	       shooter = Shooter.getInstance();
	       OI.init();  //we will cover the OI class shortly.
        }
}

Commands are meant to utilize the Subsystems (in our example, Shooter.Java) initialized in the CommandBase like the one we just created. They should also extend CommandBase (and inherently, Command). Most importantly, only one Command can use a given Subsystem at a time. This is vital because it would be disastrous for multiple, contradicting instructions to be given to something like a Jaguar SpeedController. This is why Commands call the requires(Subsystem s) in its constructor. More than one of these methods can be called, allowing a single Command to claim multiple Subsystems. This means that when the requires(Subsystem s) method is called, the program checks Subsystem s to see if it is already claimed by a Command. If it is, then the program invokes Subsystem s’ interrupted() method, by default allowing the new Command to run, and ending the current one. A programmer can code for Subsystem s to take priority in its interrupted() method, however. Some of the methods included in the Command class are void initialize(), void execute(), void end(), boolean isFinished(), and void interrupted().

public class MechanumDrive extends CommandBase
{
    public MechanumDrive()
    {
        requires(driveTrain); //claims the Subsystem driveTrain which was                            
                              //initialized in CommandBase
    }

    // Called just before this Command runs the first time
    protected void initialize() 
    {

    }


  // Called repeatedly when this Command is scheduled to run
    protected void execute() 
    {
        driveTrain.mechanumDrive(); // drive using Mechanum controls
    }

    // Make this return true when this Command no longer needs to run 
    protected boolean isFinished()
    {
        return false; 
    }

    // Called once after isFinished returns true
    protected void end() {}

    // Called when another command which requires one or more of the same
    // subsystems is scheduled to run
    protected void interrupted() { }
}

OI is a class built to initialize and get data from inputs such as Joysticks or other control devices. These initializations are made in its static init() method. Other methods can also be written to get data from the input devices. The following is an example of what an OI class could look like. Having “getter” methods allow for control input to be dynamic, instead of hard-coding control inputs in various classes.

public class OI
{
	Joystick joystick;

        public static init()
        {
	      joystick = new Joystick(1);
        }

        public static double getJoystickX()
        {
	      return joystick.getX();
        }

        public static double getJoystickY()
        {
	      return joystick.getY();
        }
}

RobotMap is a completely optional class used for keeping variability and simplicity across your program. It contains public static final ints, which can be accessed from other classes in order to declare hardware components. This allows a single line to be changed that potentially affects many across the program. For instance, consider the following class declarations.

public class RobotMap
{
    public static final int lFront = 1; //left Front Victor port
    public static final int rFront = 3; 
    public static final int lBack = 2;
    public static final int rBack = 4;
}

//Example on how to call RobotMap
public class DriveTrain extends Subsystem
{
	RobotDrive drive; 
	…	
	/*
	 * Instead of saying ‘new Victor(3)’, a call to RobotMap.rFront will 
         * suffice. This way, you can change RobotMap.rFront to any other port 
         * if the hardware assignments are changed.
	 */
	
        private DriveTrain()
	{
		drive = new RobotDrive(new Victor(RobotMap.lFront), new Victor(RobotMap.rFront),
                                       new Victor(RobotMap.lBack), new Victor(RobotMap.lBack));
	}
	…
	… 
}

Your main class--which will be called RobotTemplate in this example--ties together all these classes, incorporating each class where it is needed, and completing the stratification stressed in the Command-based robot system. Although any class can be made into a main class by extending RobotBase, SimpleRobot, or IterativeRobot, IterativeRobot is the one my team chose to extend. This class has methods that instruct the robot what to do during the autonomous and teleop periods. It also initializes some important classes including CommandBase, OI, and RobotMap. This cleans up the code significantly, allowing for your main class to stay legible and coherent. It also calls methods from a special class, Scheduler, that is integral to the Command-based robot structure. Scheduler organizes what Commands are currently running in each Subsystem, resolving conflicts when needed. It also follows a singleton design pattern, thus requiring Subsystem.getInstance() to be called when it is needed to be handled.

public class RobotTemplate extends IterativeRobot 
{
    Command firstCommand;

    /**
     * This function is run when the robot is first started up and should be
     * used for any initialization code.
     */
    public void robotInit()
    {
        // Initialize all subsystems
        CommandBase.init();
        firstCommand = new MechanumDrive();
    }

    public void autonomousInit()
    {
	//add autonomous Commands to Scheduler
    }

    /*
     * This function is called periodically during autonomous
     */
    public void autonomousPeriodic()
    {
        Scheduler.getInstance().run();
    }

    public void teleopInit()
    {
	// This makes sure that the autonomous stops running when
	// teleop starts running. If you want the autonomous to 
	// continue until interrupted by another command, remove
	// this line or comment it out.
        Scheduler.getInstance().add(firstCommand);
    }

    /**
     * This function is called periodically during operator control
     */
    public void teleopPeriodic() 
    {
        Watchdog.getInstance().feed(); //this makes sure that if the robot 
        //loses comms, it doesn’t go rogue and 
        //burn the city down
        Scheduler.getInstance().run();
    }
}
Clone this wiki locally