Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Default Commands and Triggers

We have a mechanism and behaviors. Now connect them to the robot without creating new commands or bindings on every periodic call.

Let the framework run the scheduler

Change your robot base class to commands2.TimedCommandRobot. This framework class runs the command scheduler for you. Do not add a second scheduler call. Construct one Intake in robotInit, then give that same object to Acquire, Release, and the manual behavior. Keep the supplied simulationPeriodic support.

A supplier gives the latest value

ManualIntake receives read_request, a function it can call later. Passing self.read_request without parentheses passes the callable. Passing self.read_request() would call it immediately and pass a number instead. This callable is often called a supplier because it supplies a current value.

During every execute, ManualIntake calls the supplier and sends its result to the subsystem. Its end stops the output. It has no custom isFinished; the base command’s default doesn’t finish on its own.

Assign it with intake.setDefaultCommand(...). A default command runs when that subsystem is free. It is interrupted when another command needs the intake, and can resume afterward. Our supplier returns zero outside enabled teleop so an autonomous timeout cannot resume an old operator request.

Trigger on a changing condition

Trigger watches a boolean supplier. onTrue(command) schedules when the condition changes from false to true. It does not schedule repeatedly just because a button stays true. In our dashboard demonstration, set Acquire false before setting it true again to create another press.

Construct and bind triggers once in robotInit. A physical controller button can provide an equivalent boolean later. For now the dashboard keeps hardware setup separate from learning scheduling.

robot.py
import commands2
from commands2.button import Trigger
import wpilib
from examples.support.intake_io import IntakeSim
from intake_subsystem import Intake
from intake_commands import Acquire, Release, ManualIntake


class Robot(commands2.TimedCommandRobot):
    def robotInit(self):
        if wpilib.RobotBase.isReal():
            raise RuntimeError("This course example is simulation-only")
        self.io = IntakeSim()
        self.intake = Intake(self.io)
        self.acquire = Acquire(self.intake)
        self.release = Release(self.intake)
        self.intake.setDefaultCommand(ManualIntake(self.intake, self.read_request))
        for name in ("Acquire", "Release"):
            wpilib.SmartDashboard.putBoolean(name, False)
        wpilib.SmartDashboard.putBoolean("Piece available", True)
        wpilib.SmartDashboard.putNumber("Intake request", 0.0)
        self.acquire_trigger = Trigger(self.acquire_pressed)
        self.acquire_trigger.onTrue(self.acquire)
        self.release_trigger = Trigger(self.release_pressed)
        self.release_trigger.onTrue(self.release)

    def read_request(self):
        if self.isTeleopEnabled():
            return wpilib.SmartDashboard.getNumber("Intake request", 0.0)
        return 0.0

    def acquire_pressed(self):
        return self.isTeleopEnabled() and wpilib.SmartDashboard.getBoolean("Acquire", False)

    def release_pressed(self):
        return self.isTeleopEnabled() and wpilib.SmartDashboard.getBoolean("Release", False)

    def autonomousInit(self):
        self.acquire.schedule()

    def teleopInit(self):
        self.acquire.cancel()
        self.release.cancel()

    def disabledInit(self):
        commands2.CommandScheduler.getInstance().cancelAll()
        self.intake.stop()

    def simulationPeriodic(self):
        self.io.enabled = self.isEnabled()
        self.io.piece_available = wpilib.SmartDashboard.getBoolean("Piece available", True)
        self.io.step()
        wpilib.SmartDashboard.putBoolean("Has piece", self.intake.has_piece())
        wpilib.SmartDashboard.putNumber("Intake output", self.io.output)
        wpilib.SmartDashboard.putBoolean("Acquire succeeded", self.acquire.succeeded)

Connect your version

Implement ManualIntake and the robot setup in your assignment. Use imports from your own sibling intake_subsystem.py and intake_commands.py, as the example does. The launcher puts the selected assignment on the import path. Run:

python tools/run_robot.py student-work/intake-subsystem

Use zero manual request first. In enabled teleop, set Acquire true; the output should collect then stop. After detecting a piece, set Release true; it should reverse and stop after release. Reset each dashboard button to false before its next use. Watch Acquire succeeded to distinguish sensor success from timeout.

Now hold a nonzero manual request and observe what happens after a command releases the intake. The default can resume the request; that is expected ownership behavior, not a reason to remove cleanup. Return all requests to zero before the next test.

Explain why self.read_request has no parentheses where it is passed to the command, and why trigger bindings belong in initialization. Next, explore how feedback chooses motor effort before the final project.