Subsystems
Subsystems are the basic unit of robot organization in the command-based paradigm. A subsystem is an abstraction for a collection of robot hardware that operates together as a unit. Subsystems encapsulate this hardware, “hiding” it from the rest of the robot code (e.g. commands) and restricting access to it except through the subsystem’s public methods. Restricting the access in this way provides a single convenient place for code that might otherwise be duplicated in multiple places (such as scaling motor outputs or checking limit switches) if the subsystem internals were exposed. It also allows changes to the specific details of how the subsystem works (the “implementation”) to be isolated from the rest of robot code, making it far easier to make substantial changes if/when the design constraints change.
Subsystems also serve as the backbone of the CommandScheduler
’s resource management system. Commands may declare resource requirements by specifying which subsystems they interact with; the scheduler will never concurrently schedule more than one command that requires a given subsystem. An attempt to schedule a command that requires a subsystem that is already-in-use will either interrupt the currently-running command (if the command has been scheduled as interruptible), or else be ignored.
Subsystems 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. Similar functionality can be achieved in the subsystem’s periodic()
method, which is run once per run of the scheduler; teams should try to be consistent within their codebase about which functionality is achieved through either of these methods. There is also a simulationPeriodic()
method that is similar to periodic()
except that it is only run during Simulation and can be used to update the state of the robot. Subsystems are represented in the command-based library by the Subsystem interface (Java, C++).
Creating a Subsystem
The recommended method to create a subsystem for most users is to subclass the abstract SubsystemBase
class (Java, C++), as seen in the command-based template (Java, C++):
7import edu.wpi.first.wpilibj2.command.SubsystemBase;
8
9public class ExampleSubsystem extends SubsystemBase {
10 /** Creates a new ExampleSubsystem. */
11 public ExampleSubsystem() {}
12
13 @Override
14 public void periodic() {
15 // This method will be called once per scheduler run
16 }
17
18 @Override
19 public void simulationPeriodic() {
20 // This method will be called once per scheduler run during simulation
21 }
22}
5#pragma once
6
7#include <frc2/command/SubsystemBase.h>
8
9class ExampleSubsystem : public frc2::SubsystemBase {
10 public:
11 ExampleSubsystem();
12
13 /**
14 * Will be called periodically whenever the CommandScheduler runs.
15 */
16 void Periodic() override;
17
18 /**
19 * Will be called periodically whenever the CommandScheduler runs during
20 * simulation.
21 */
22 void SimulationPeriodic() override;
23
24 private:
25 // Components (e.g. motor controllers and sensors) should generally be
26 // declared private and exposed only through public methods.
27};
This class contains a few convenience features on top of the basic Subsystem
interface: it automatically calls the register()
method in its constructor to register the subsystem with the scheduler (this is necessary for the periodic()
method to be called when the scheduler runs), and also implements the Sendable
interface so that it can be sent to the dashboard to display/log relevant status information.
Advanced users seeking more flexibility may simply create a class that implements the Subsystem
interface.
Simple Subsystem Example
What might a functional subsystem look like in practice? Below is a simple pneumatically-actuated hatch mechanism from the HatchBot example project (Java, C++):
5package edu.wpi.first.wpilibj.examples.hatchbottraditional.subsystems;
6
7import static edu.wpi.first.wpilibj.DoubleSolenoid.Value.kForward;
8import static edu.wpi.first.wpilibj.DoubleSolenoid.Value.kReverse;
9
10import edu.wpi.first.wpilibj.DoubleSolenoid;
11import edu.wpi.first.wpilibj.PneumaticsModuleType;
12import edu.wpi.first.wpilibj.examples.hatchbottraditional.Constants.HatchConstants;
13import edu.wpi.first.wpilibj2.command.SubsystemBase;
14
15/** A hatch mechanism actuated by a single {@link DoubleSolenoid}. */
16public class HatchSubsystem extends SubsystemBase {
17 private final DoubleSolenoid m_hatchSolenoid =
18 new DoubleSolenoid(
19 PneumaticsModuleType.CTREPCM,
20 HatchConstants.kHatchSolenoidPorts[0],
21 HatchConstants.kHatchSolenoidPorts[1]);
22
23 /** Grabs the hatch. */
24 public void grabHatch() {
25 m_hatchSolenoid.set(kForward);
26 }
27
28 /** Releases the hatch. */
29 public void releaseHatch() {
30 m_hatchSolenoid.set(kReverse);
31 }
32}
5#pragma once
6
7#include <frc/DoubleSolenoid.h>
8#include <frc/PneumaticsControlModule.h>
9#include <frc2/command/SubsystemBase.h>
10
11#include "Constants.h"
12
13class HatchSubsystem : public frc2::SubsystemBase {
14 public:
15 HatchSubsystem();
16
17 // Subsystem methods go here.
18
19 /**
20 * Grabs the hatch.
21 */
22 void GrabHatch();
23
24 /**
25 * Releases the hatch.
26 */
27 void ReleaseHatch();
28
29 private:
30 // Components (e.g. motor controllers and sensors) should generally be
31 // declared private and exposed only through public methods.
32 frc::DoubleSolenoid m_hatchSolenoid;
33};
5#include "subsystems/HatchSubsystem.h"
6
7using namespace HatchConstants;
8
9HatchSubsystem::HatchSubsystem()
10 : m_hatchSolenoid{frc::PneumaticsModuleType::CTREPCM,
11 kHatchSolenoidPorts[0], kHatchSolenoidPorts[1]} {}
12
13void HatchSubsystem::GrabHatch() {
14 m_hatchSolenoid.Set(frc::DoubleSolenoid::kForward);
15}
16
17void HatchSubsystem::ReleaseHatch() {
18 m_hatchSolenoid.Set(frc::DoubleSolenoid::kReverse);
19}
Notice that the subsystem hides the presence of the DoubleSolenoid from outside code (it is declared private
), and instead publicly exposes two higher-level, descriptive robot actions: grabHatch()
and releaseHatch()
. It is extremely important that “implementation details” such as the double solenoid be “hidden” in this manner; this ensures that code outside the subsystem will never cause the solenoid to be in an unexpected state. It also allows the user to change the implementation (for instance, a motor could be used instead of a pneumatic) without any of the code outside of the subsystem having to change with it.
Setting Default Commands
Note
In the C++ command-based library, the CommandScheduler owns the default command objects - accordingly, the object passed to the SetDefaultCommand()
method will be either moved or copied, depending on whether it is an rvalue or an lvalue (rvalue/lvalue explanation). The examples here ensure that move semantics are used by casting to an rvalue with std::move()
.
“Default commands” are commands that run automatically whenever a subsystem is not being used by another command.
Setting a default command for a subsystem is very easy; one simply calls CommandScheduler.getInstance().setDefaultCommand()
, or, more simply, the setDefaultCommand()
method of the Subsystem
interface:
CommandScheduler.getInstance().setDefaultCommand(exampleSubsystem, exampleCommand);
CommandScheduler.GetInstance().SetDefaultCommand(exampleSubsystem, std::move(exampleCommand));
exampleSubsystem.setDefaultCommand(exampleCommand);
exampleSubsystem.SetDefaultCommand(std::move(exampleCommand));
Note
A command that is assigned as the default command for a subsystem must require that subsystem.