Skip to content
IncredibotsAcademy
Incredibots playbook

Understanding Subsystems

Learn what subsystems are in a command-based architecture and how they encapsulate hardware control.

25 minrookie

By the end you can

  • Understand the purpose of a Subsystem in Command-Based programming.
  • Learn how to encapsulate motors and servos within a Subsystem.
  • Understand how Subsystems and Commands work together via the Command Scheduler.

As you start writing code for a robot, you will quickly realize that the robot is made up of many different physical mechanisms. You might have a drivetrain for moving, an intake for picking up elements, and a launcher for shooting them.

In our team's Command-Based Architecture (using FTCLib), we represent these physical mechanisms in our code as Subsystems.

If you want to read the official documentation, check out the FTCLib Subsystem Docs.

What is a Subsystem?

A Subsystem is a Java class that encapsulates a specific physical part of the robot. It acts as the absolute gatekeeper to that hardware.

Instead of letting any part of your code control the intake motors directly, you put all the intake motors inside an IntakeSubsystem. If the robot wants to run the intake, it must ask the IntakeSubsystem to do it.

How Subsystems and Commands Work Together

Subsystems do not decide when they should run. A subsystem simply provides a list of actions it can perform (like runIntakeIn() or stopIntake()).

It is the job of a Command to actually call those methods. The Command Scheduler is the maestro that organizes everything:

  1. The Scheduler looks at which Commands are currently active.
  2. It checks which Subsystems those Commands require.
  3. It prevents two Commands from trying to use the same Subsystem at the same time (e.g., stopping an "Intake In" command if you suddenly trigger an "Intake Out" command).

Why do we use Subsystems?

  1. Organization: If there is a bug with the intake, you know exactly where to look: the IntakeSubsystem.
  2. Safety: Subsystems prevent two different pieces of code from trying to control the same motor at the same time.
  3. Abstraction: A command doesn't need to know how the intake turns on (which motor, which port, what speed). It just calls a simple method like intake.in().

Creating Your First Subsystem

Let's look at how to create a simple IntakeSubsystem. We will assume you have a single motor that spins to suck in a game piece.

  1. In Android Studio, right-click the teamcode folder and navigate to New > Package. Name it subsystems.
  2. Right-click the new subsystems folder and select New > Java Class.
  3. Name the class IntakeSubsystem.

Here is what the code for that subsystem looks like in depth:

package org.firstinspires.ftc.teamcode.subsystems;
 
// We import FTCLib's SubsystemBase, which gives our class special scheduling powers!
import com.arcrobotics.ftclib.command.SubsystemBase;
 
// We import the hardware classes from the FTC SDK so we can talk to the physical robot.
import com.qualcomm.robotcore.hardware.DcMotor;
import com.qualcomm.robotcore.hardware.HardwareMap;
 
// Custom telemetry for sending data to the Driver Station
import com.bylazar.telemetry.TelemetryManager;
 
public class IntakeSubsystem extends SubsystemBase {
    
    // =========================================================
    // 1. DECLARE HARDWARE VARIABLES
    // =========================================================
    // We declare our hardware objects here. 
    // They MUST be 'private' so no other class can accidentally bypass 
    // our methods and command the motor directly.
    private final DcMotor intakeMotor;
    private final TelemetryManager telemetry;
 
    // =========================================================
    // 2. THE CONSTRUCTOR
    // =========================================================
    // The constructor runs exactly ONCE when the robot starts up.
    // It takes in the HardwareMap (which connects software to the physical Hub)
    public IntakeSubsystem(HardwareMap hwMap, TelemetryManager telemetry) {
        this.telemetry = telemetry;
        
        // Grab the physical motor from the HardwareMap. 
        // IMPORTANT: The string "intake_motor" MUST exactly match 
        // what you typed into the Driver Station configuration on the phone!
        intakeMotor = hwMap.get(DcMotor.class, "intake_motor");
        
        // Optional: If your motor spins backwards by default, you can reverse it here!
        // intakeMotor.setDirection(DcMotor.Direction.REVERSE);
        
        // Optional: Tell the motor to brake when power is 0, rather than coasting
        // intakeMotor.setZeroPowerBehavior(DcMotor.ZeroPowerBehavior.BRAKE);
    }
 
    // =========================================================
    // 3. ACTION METHODS
    // =========================================================
    // These methods are public, meaning Commands can call them.
    // They translate simple ideas ("run inward") into exact motor powers (1.0).
    
    public void runIntakeIn() {
        // setPower accepts a value from -1.0 (full reverse) to 1.0 (full forward).
        intakeMotor.setPower(1.0); 
    }
 
    public void runIntakeOut() {
        intakeMotor.setPower(-1.0); 
    }
 
    public void stopIntake() {
        intakeMotor.setPower(0.0); // Stop the motor completely
    }
    
    // =========================================================
    // 4. PERIODIC LOOP (Optional but useful)
    // =========================================================
    @Override
    public void periodic() {
        // Because we extend SubsystemBase, the Command Scheduler will automatically
        // run this method over and over again, many times per second.
        // This is the perfect place to send diagnostic data back to the drivers!
        telemetry.addData("Intake Power", intakeMotor.getPower());
    }
}

Breaking Down the Code

Let's go through the four main parts of a Subsystem:

  1. Hardware Variables: We declare private final DcMotor intakeMotor. Making it private is extremely important! It guarantees that no other class can control the motor directly. They must use the methods we provide.
  2. The Constructor: This is where we grab the physical hardware from the robot's HardwareMap. The string "intake_motor" must exactly match the name you typed into the Driver Station configuration.
  3. Action Methods: We create simple, human-readable methods like runIntakeIn() and stopIntake(). Commands will call these methods later.
  4. Periodic (Optional): Because our class extends SubsystemBase, we can override the periodic() method. The Command Scheduler will run this method automatically every single frame, making it perfect for printing telemetry (debugging text) to the Driver Station screen.

Check Your Understanding

Check yourself

Why is it important to make the hardware variables (like DcMotor) inside a subsystem 'private'?