It is possible to write simple robot programs in a single Robot class, using the simple TimedRobot template. However, as your program grows, the complexity can snowball. You’re better off if your overall program is designed to be scalable. Scalability is an engineering concept where we observe how problems and solutions change as they grow. You want your program to scale gracefully as it adds functions. You may also want to be able to scale up the number of programmers, so multiple people can work comfortably on separate parts of the code.
A recommended pattern for organizing robot programs for FIRST is the command-based framework.
Command Based Framework
When creating a command-based robot program, we define three types of software:
- Subsystem classes represent major physical parts of your robot, such as a drive train, shooter, game-piece collector, or climber.
- Command classes represent major actions that subsystems can do, such as driving, shooting, acquiring, or climbing.
- Operator interface objects start commands executing on the subsystems. Think of joystick buttons, or custom trigger objects.
For a command-based program these bits of software will work within a standardized software project. Within this framework there is another object called the
CommandScheduler, which will handle the lifecycle of commands. The
CommandScheduler monitors buttons and triggers. It starts and manages the commands.
At the top level of your project will be three important class files:
Main, which starts the whole program. Don’t make any changes to this file.
RobotContainer, which is in charge of creating the objects from the subsystem and command classes, and connecting them with operator interfaces.
Robot, which handles the lifecycle timing. It will initiate robot initialization calls into the
RobotContainerclass. It also handles transitions in and out of teleop and autonomous modes, and causes the
Separating robot functions out into these three classes is an example of Separation of Concerns, a software engineering principle in which we divide different functions into different areas.
Subsystems represent physical parts of your robot. Think about the physical components that you may manipulate from your program. A subsystem might contain:
- Motors, and their associated motor controllers and sometimes encoders
- Pneumatic pistons, and their associated solenoids
- Relays or Spikes
- Gyros or Inertial Management Units
- Sensors, including ultrasonic sensors, limit switches or potentiometers
Each subsystem class will contain software objects connecting to those physical objects. For instance, if a drive train subsystem has two physical motors, then the
DriveTrainSubsystem class will contain two motor controller objects. Typically we make those objects private, meaning that they may not be accessed directly from the outside.
To allow access to the internal components, we next define functions in the subsystem that allow component access. For instance, we might add a function that sets the speed of a shooter motor or another function that causes pneumatic solenoids to push a ball into the shooter. One important quality of these functions is that each one must be quick, only taking a tiny amount of time.
The practice of making internals private but then exposing high-level actions through functions is another software engineering principal called Ecapsulation. Encapsulation hides internal complexity, allowing the overall program to be less complex.
Commands execute actions on subsystems. Most commands operate only on a single subsystem, but it is possible to run a command on multiple subsystems. When a command operates on a subsystem, we say that the command requires that subsystem. There are three important rules for commands:
- A subsystem can only run one command at a time.
- If a subsystem is running one command but then another command starts on that subsystem, the first command will be interrupted. We call this action interrupting, but the first command will be completely stopped.
For instance, suppose the robot is turning clockwise, but then you initiate a command to turn counter-clockwise. It is impossible to do both at the same time, and the second command is what you really want. The clockwise command will be stopped and the counter-clockwise command will start up.
- A subsystem may have a default command that runs whenever no other commands are scheduled.
For instance, a drivetrain subsystem typically has a default driving command that allows joysticks to drive the robot. If we initiate a command to rotate the robot ninety degrees clockwise, then the driving command will be interrupted until the rotate command finishes. The default command will start up again as soon as the turn command is finished.
Some commands will be “instant commands”, meaning that they initiate some simple action and then end immediately. For instance, a command to set the speed on a shooter wheel will do just that, and then end.
Other commands have a lifecycle that can span a much longer period of time. These command have an initialization function when they start, and then an execution function that is run repeatedly until the command is finished, and then a finalization function to run when the command is finished. Each of these functions must be quick and time a tiny amount of time, but the overall life of the command may take many seconds.
Note that you can also combine commands into command groups. One command group can cause multiple commands to run in sequence or in parallel.
The most common way of initiating a command is to attach it to a joystick button. Pressing that button schedules the command and it will start running. You can also cause a command to start when the button is released, or to execute only while the button is held down. You can allow a button to toggle on or off. You can also define combinations of buttons, so commands start when you press multiple buttons. There is a lot of flexibility available.
You can also define custom trigger objects that initiate commands. You could define triggers that start commands whenever certain conditions become true, such as when an ultrasonic sensor detects a wall or when voltage drops on an analog input or when a certain time is reached.
Note that autonomous routines are typically created as group commands. The autonomous command will be scheduled whenever the robot initiates autonomous mode.
Designing Subsystems and Commands
When it is time to divide your robot into subsystems, the choices might seem obvious, but there are a couple of principles to think about. Remember that a subsystem can only run one command at a time. If it seems that you will need to run multiple commands on one area, consider splitting those parts into multiple subsystems. For instance, if you have a subsystem that acquires balls, stores them, and then shoots them, you might want to break that part into two or three separate subsystems. On the other hand, if it seems like two subsystems are always doing the same thing at the same time, then maybe they should be combined. For instance, it might not be optimal to create separate subsystems for the left and right sides of your drive train.
The number of subsystems is usually fixed, but you can have a great many commands. You can keep adding commands as you think of new functions. Don’t worry if you create a lot of commands that aren’t ultimately used in the final robot program. Commands are an area for innovation.
After you’ve learned how basic commands work, read the documentation on all specialized command types, such as the
CommandGroup, etc. After you’ve mastered these heavy-weight commands, you learn light-weight lambda based commands.
Most programming languages develop standards on how things should be named. In Java, the long established practice is:
- Class names start with a capital letter, but then separate words with capital letters. This is called camel case.
- Variables start with a lower case letter, and then proceed with camel case.
- Constants, which are variables that are never changed, are all capital letters with words separated by underscores.
When choosing names, don’t be afraid to make long multi-word names that make meaning more clear. For robot programs, you might adopt the following conventions:
- Subsystem classes should end in the word “Subsystem”. For instance
- Commands classes should end in the word “Command” and begin with the name of the command’s main subystem. For instance,