Command Based Programming - quasics/quasics-frc-sw-2015 GitHub Wiki
Command Based Programming
Command Based programming is a method of developing your robot's code with built-in Abstraction Layers, officially supported and designed by FIRST to be easy to set up, develop, and implement. Used by Quasics from the 2016 season, FIRST Stronghold with our robot, Dante, and has been used since.
Overview
The Command based programming structure divides the task of programming the robot into numerous, hierarchical Abstraction Layers.
Subsystem
The Lowest Layer is the Subsystem layer. As the name implies this layer handles the most basic level of communication with the objects on the robot. One standard example of this for almost every FRC Robot is the drive base. A drive base subsystem would handle interaction with the motor controllers in the drive base, as well as any sensors solely attributed to the drive base subsystem, including (but not limited to) encoders, gyroscopes, and accelerometers.
Each subsystem will have its own set of basic functions, called primitives. These are the most basic commands (not to be confused with Commands) that the associated subsystem can handle. Quasics' 2017 season (FIRST Steamworks) robot had the following primitives for its Drive Base Subsystem (code here):
- Read/Reset the Gyroscope
- Set power to the Left/Right drive Motors
- Read Left/Right drive base speed given by the Encoder
- Read Left/Right drive base distance traveled as given by the Encoder
- Read the raw, unprocessed counts given by the Left/Right Encoder
- Reset the Left/Right Encoder Distance/Raw Reading
- Stop both Motors
Note that all of the mentioned primitives serve as the most basic building blocks of any robot movement (setting the robot to move at a speed, understanding where it is pointing, and knowing how far/fast it has gone). Anything more complicated than this most basic level of functionality Is handled at higher levels
Commands
Commands organize the building blocks of the primitives into more complex chains of action. By convention, these commands generally only use one subsystem each. More coordinated action between subsystems is generally accomplished in the next level up, the Command Group.
These commands claim use of their respective subsystem(s) using the Requires tag. For example:
Requires(Robot::driveBase.get());
This would tell the Scheduler that this command uses the subsystem that the driveBase pointer (generally a shared pointer) from the Robot class references. Claiming use of a subsystem in this manner has a two-fold impact. Firstly, it allows the use of that specific subsystem's primitives. Secondly, the command cancels any other command attempting to use the same subsystem, calling the already running command's interrupted function. This second function assures that the programmer can assume the first, and that separate commands don't try to interfere with each other.
Each command is divided into 6 separate functions:
- The Constructor
- Like any constructor this is where you define any specific parameters of the command. For instance, this is where you place the Requires tag, and if you have any parameters for the constructor (e.g. in an autonomous move command, distance and power), this is where you store them as a class based variable.
- Once again, as with any constructor, this function does not have a defined return type, and shares its name with the overall class.
- As of the 2016 season revision to WPI Library, command constructors can take parameters.
void Initialize()
- This function is called any time the command is put into the scheduler (i.e. any time the Start() function is called).
- In this function, one should reset any class variables and vital sensors, as well as setting initial output for any associated output devices
void Execute()
- This is the meat of your command. It is called every time the scheduler runs with this command on deck.
- For a Teleop command, for instance, this is where you would read any associated input devices (joysticks), and set output devices accordingly
- For an autonomous command, this would be where you put any motion profiling code (e.g. slow down when within "X" m of the target) * Since it is called every time the robot's periodic modes are called, this will update at about 50 Hz (~50 calls/sec)
- This is the meat of your command. It is called every time the scheduler runs with this command on deck.
bool IsFinished()
- Does exactly what it says on the tin 🙄. Checks to see if the command should shut itself down. If it returns true, the command will run its End function and will be removed from the schedule.
- For most Teleop commands, this will always return false, because the operator has direct control of the bot, and removing that control in the middle of a match could prove counter-productive and/or dangerous.
- At the end of a period in game play, any currently running commands will be force-stopped, calling the Interrupted function
- For most Autonomous commands, this is where you check to see if you've met or surpassed your objective (e.g. running for time, moving for distance, moving to angle, etc.)
void End()
- This function is called if and when IsFinished returns true
- Generally, this function consists of setting any output devices to a default state (e.g. stopping any motors)
void Interrupted()
- This function is called if and when the command is stopped for any reason besides the IsFinished function shutting returning true (i.e. a higher level invoking the command's Stop() Function, a period ending, the robot being disabled/eStopped, or another command claiming control of the subsystem)
- The contents of this function are much the same as a End function, mostly just stopping the output devices, but provides an option to change how the subsystem reacts to different shutdown methods.
Command Group
Command Groups are, surprisingly enough, groups of commands /sarcasm/. These commands can be run in parallel if using separate subsystems, or in sequence in any case.
Adding commands sequentially (using the command AddSequential(new foo(bar))
or whatever command you want will run the added command after the previous command has run its End function. for this reason, this is most useful in autonomous commands, where you can string separate command elements together into an entire autonomous routine. for instance, a basic command which will move in a square could be:
AddSequential(new MoveForDistance(1,1));
AddSequential(new PointTurnForAngle(90,1));
AddSequential(new MoveForDistance(1,1));
AddSequential(new PointTurnForAngle(90,1));
AddSequential(new MoveForDistance(1,1));
AddSequential(new PointTurnForAngle(90,1));
AddSequential(new MoveForDistance(1,1));
AddSequential(new PointTurnForAngle(90,1));
For the above example to work, you would need to have the commands MoveForDistance and PointTurnForAngle both written and included in the command group file, but it would run each after the last had shut itself down.
Adding commands in parallel uses the function AddParallel(new foo(bar))
, and will run the command foo with parameter bar concurrently with any other commands already set by the command group. Remember, however, that whichever command you add last for a given subsystem by this method will overwrite any others set earlier in the command group. If you had added the commands in the above command group in parallel, as opposed to sequentially, only the final PointTurnForAngle Would run, and every new command would call the Interrupt function of the previous command. This is useful in Autonomous for allowing an attachment to operate on its own while maneuvering on the field of play, for instance, and in Teleop for allowing multiple commands (for instance, driving and accessory commands) to be started with a single command.