Skip to content

Intro to CBP_Overview

FreshOlives edited this page Oct 29, 2022 · 5 revisions

Disclaimer: The majority of content on this page is pulled almost verbatim from WPILib documentation, with light edits, and the examples reworked to correspond to their Taproot equivalents. Some content was also borrowed from the Taproot Wiki for the section on comprised commands.

What Is “Command-Based” Programming?

Taproot supports a robot programming methodology called “command-based” programming. In general, “command-based” can refer both the general programming paradigm, and to the set of Taproot library resources included to facilitate it.

“Command-based” programming is an example of what is known as a design pattern. It is a general way of organizing one’s robot code that is well-suited to a particular problem-space. It is not the only way to write a robot program, but it is a very effective one; command-based robot code tends to be clean, extensible, and easy to re-use from year to year.

The command-based paradigm is also an example of what is known as declarative programming. In declarative programming, the emphasis is placed on what the program ought to do, rather than how the program ought to do it. Thus, the command-based libraries allow users to define desired robot behaviors while minimizing the amount of iteration-by-iteration robot logic that they must write. For example, in a command-based program, a user can specify that “the robot should perform an action when a switch is up":

HoldCommandMapping feedFeeder(drivers(), {&feederForward}, RemoteMapState(Remote::Switch::RIGHT_SWITCH, Remote::SwitchState::UP));

While this may look intimidating at first, let's break it down:

  • HoldCommandMapping - Indicates that we want a "Hold" command mapping type, which activates the command when the indicated button is pressed, and ends it when the button is released.

  • feedFeeder - The name of the command mapping object being created

  • &feederForward - Points towards the command that defines the action that should be done.

  • RemoteMapState(Remote::Switch::RIGHT_SWITCH, Remote::SwitchState::UP)) - Indicates which button state the command should be mapped to. In our case, the right switch of the remote being flipped up.

In other words, we are telling the robot "Move the feeder forward as long as the right switch is up."

In contrast, in an ordinary imperative program, the user would need to check the switch state every iteration, and perform the appropriate action based on the state of the switch.

if(rightSwitch.Get() == UP) {
  if(!pressed) {
    feeder.forward();
    pressed = true;
  }
} else {
  feeder.stop();
  pressed = false;
}

Subsystems and Commands

The command-based pattern is based around two core abstractions: commands, and subsystems.

Subsystems are the basic unit of robot organization in the design-based paradigm. Subsystems encapsulate lower-level robot hardware (such as motor controllers, sensors, and/or pneumatic actuators), and define the interfaces through which that hardware can be accessed by the rest of the robot code. Subsystems allow users to “hide” the internal complexity of their actual hardware from the rest of their code - this both simplifies the rest of the robot code, and allows changes to the internal details of a subsystem without also changing the rest of the robot code. Subsystems implement the Subsystem interface.

Commands define high-level robot actions or behaviors that utilize the methods defined by the subsystems. A command is a simple state machine that is either initializing, executing, ending, or idle. Users write code specifying which action should be taken in each state. Simple commands can be composed into “command groups” to accomplish more-complicated tasks. Commands, including command groups, implement the Command interface.

How Commands Are Run

Commands are run by the CommandScheduler, a class that is at the core of the command-based library. The CommandScheduler is in charge of polling buttons for new commands to schedule, checking the resources required by those commands to avoid conflicts, executing currently-scheduled commands, and removing commands that have finished or been interrupted. The scheduler’s run() method is called every iteration of the robots main loop.

Multiple commands can run concurrently, as long as they do not require the same resources on the robot. Resource management is handled on a per-subsystem basis: commands may specify which subsystems they interact with, and the scheduler will never schedule more than one command requiring a given subsystem at a time. This ensures that, for example, users will not end up with two different pieces of code attempting to set the same motor controller to different output values. If a new command is scheduled that requires a subsystem that is already in use, it will either interrupt the currently-running command that requires that subsystem, or else it will not be scheduled.

Subsystems also can be associated with “default commands” that will be automatically scheduled when no other command is currently using the subsystem. This is useful for continuous “background” actions such as controlling the robot drive, or keeping an arm held at a setpoint.

When a command is scheduled, its initialize() method is called once. Its execute() method is then called once per call to drivers->commandScheduler.run(). A command is un-scheduled and has its end(bool interrupted) method called when either its isFinished() method returns true, or else it is interrupted (either by another command with which it shares a required subsystem, or by being canceled).

Comprised Commands (a.k.a. Command Groups)

It is often desirable to build complex commands from simple pieces. This is achievable by composing commands into “comprised commands.” A comprised command is a command that contains multiple commands within it, which run either in parallel or in sequence.

The ComprisedCommand is a layer built on top of the Command class. Interacting with multiple commands can be done easily because each comprised command has access to its own unique command scheduler that it may use to add/remove instances of the commands that it uses.

Next Page: Subsystems