Confusion about WPILib Commands

Hello, I am a little confused about Commands in WPILib.

Here is some pseudocode of a Subsystem. All it does is print things, but in reality, there would be more interesting commands.

public class MySubsystem extends SubsystemBase {

	// This will only print 1-2, but not 1-1
	public Command runMyCommand1() {
		System.out.println("Message 1-1");
		return new PrintCommand("Message 1-2");
	}
	
	// This will only print 2-1, not 2-0 or 2-2
	public Command runMyCommand2() {
		System.out.println("Message 2-0");
		return runOnce(
				() -> {
					System.out.println("Message 2-1");
					new PrintCommand("Message 2-2");
				});
	}
}

and here is a partial RobotContainer:

public class RobotContainer() {
	private final MySubsystem m_subsystem = new MySubsystem();
	private final XboxController m_controller = new XboxController(0);

	private void configureButtonBindings() {
		Trigger xboxButtonX = new Trigger(m_controller::getXButton);
		Trigger xboxButtonY = new Trigger(m_controller::getYButton);
		
		xboxButtonX.onTrue(m_subsystem.runMyCommand1());
		xboxButtonY.onTrue(m_subsystem.runMyCommand2());
	}
}

Output after putting in TeleOp mode and pressing X button and Y buttons on controller

Message 1-2
Message 2-1

Questions

-Why doesn’t Message 1-1 print in runMyCommand1()?
-Why doesn’t Message 2-0 and 2-2 print in runMyCommand2()?

2 Likes

In the first case, the println call occurs precisely once, at the same time the command is created and bound to a trigger. This is because you’ve put in inside the body of your subsystem’s command factory method, which returns a PrintCommand.

In the second case, the print command is instantiated and then thrown away. Creating a print command doesn’t print anything; it has to be scheduled (usually by binding it to a trigger). To make larger commands out of smaller commands, you have to compose them into command groups.

7 Likes

I see – scrolling up further in the output, I do see Message 1-1 and 2-0 being printed when they are first being bound by the Trigger. Thank you for clarifying that point!

I see CommandGroups in the 2022 docs, where it seems like you have to create a subclass of SequentialCommandGroup (is subclassing necessary?), but in the current docs, it seems they are encouraging an inline style using .andThen(), etc.

Let’s say I wanted to use runOnce(), or run(), and andThen(), and until() as in the current docs. How could I pass an existing command (e.g. PrintCommand) so that it is not just instantiated and then thrown away? I assumed that runOnce() would actually schedule the instantiated command. Any specific corrections to runMyCommand2() that would allow the PrintCommand to be scheduled would really be helpful.

All you have to do is pass the print command as the argument of andThen.

As you suggest, simply passing the print command as the argument of andThen() works:

// This works and seems quite natural
.andThen(new PrintCommand("Message 3-1"));

but does not work for run() or runOnce() due to a type mismatch:

// COMPILE ERROR: The method runOnce(Runnable) in the type Subsystem 
// is not applicable for the arguments (PrintCommand)Java(67108979)
.runOnce(new PrintCommand("Message 3-2"));

This is why in my initial post I tried to surround it with a lambda, but as you pointed out, that just instantiates the PrintCommand but never schedules it:

// This compiles, but the PrintCommand is never scheduled and run
.runOnce( () -> new PrintCommand("Message 3-3"));

It seems that for runOnce() and run(), I have to put it into a lambda and explicitly call schedule():

// This actually works
.runOnce( () -> new PrintCommand("Message 3-4").schedule() );

Again, I am not really interested in PrintCommands, these examples were more for debugging/understanding. What I really want is to run something multiple times until a condition is true, something like:

	public Command runMyCommand4() {
		return run( () -> new PrintCommand("Message 4-1").schedule() )
                .until( () -> false )    // normally this would be an actual BooleanSupplier
                                        // here it is an infinite loop
                .andThen(new PrintCommand("Message 4-2"));
	}

Maybe there is a clearer way to write the above (?), because the different syntax for passing Commands to run()/runOnce() vs things like andThen() doesn’t seem like a crisp way to do things.

Inside of the body of the lambda argument you pass to run or runOnce, you should just be calling println. That’s an ordinary function context that runs when the command is scheduled, so you can just do plain imperative coding there as long as you don’t block the main loop.

You should generally never call manually schedule on a command.

Think of PrintCommand(...) as convenient sugar for:

runOnce(() -> { System.out.println(...); })

Remember, calling runOnce does not actually do anything immediately - it defines a command that does something once when it is scheduled, which you can either bind to a trigger or compose into a more-complicated command group.

2 Likes

Thanks for your patience with my questions! However, let’s move the focus away from printing, which is not my actual goal.

What if I want the Trigger to call an existing Command subclass like TurnDegrees(speed, degrees, m_drivetrain) repeatedly until it detects an object.

RobotController::configureButtonBindings() would have something like:

    Trigger xboxButttonX = new Trigger(m_controller::getXButton);
    xboxButttonX.onTrue(m_subsystem.turnToFindObject());

Pretend there is a method in MySubsystem called isObjectDetected() which returns a boolean:

Right now, I have to write a method in MySubsystem like:

    public Command turnToFindObject() {
        double speed = 0.35;
        double degrees = 5.0;
		return run( () -> new TurnDegrees(speed, degrees, m_drivetrain).schedule() )
                .until( this::isObjectDetected );
	}

turnToFindObject() explicitly calls schedule(), which you do not recommend doing. How would I fix this to avoid calling schedule()?

run and runOnce create commands out of ordinary functions. For this case you could just return new TurnDegrees(…).until(…);
The way you have it right now, you’re creating and scheduling a new TurnDegrees command every loop while the command returned from the factory is running.

3 Likes

By the way, if m_controller was a CommandXboxController, you could do m_controller.x().onTrue(m_subsystem.turnToFindObject()), since CommandXboxController automatically builds and retains Triggers for the buttons you would use in this way.

2 Likes

Oh wow, I can’t believe I made it so complicated lol.

I thought I needed to use run() in order to use until() which caused me to go down that entire rabbit hole trying to figure out how to run a Command from run() or runOnce().

Combined with the comments of Oblarg, things make more sense now, and I will try this when I get a chance. Thank you!

2 Likes

Sorry to resurrect this thread already. I don’t have access to my robot to actually run motors and vision right now, so I ran this printing test:

	public Command runMyCommand5() {
		// This should be an infinite loop since until() never returns true?
		return new PrintCommand("Message 5-1").until( () -> false );
    }

Based on Amicus1’s post above, I would have expected Message 5-1 to keep repeating in an infinite loop. However, it only runs once per trigger. This is why I originally thought I needed to have run() prior to .until().

However, by chaining with repeatedly() (which I guess is already obvious to many of you who are more experienced), it finally works:

	public Command runMyCommand6() {
		// This is an infinite loop since until() never returns true
		return new PrintCommand("Message 6-1").repeatedly().until( () -> false );
    }

Thus, my code for the turnToFindObject method should look something like this:

    public Command turnToFindObject() {
        double speed = 0.35;
        double degrees = 5.0;
		return new TurnDegrees(speed, degrees, m_drivetrain)
                .repeatedly()
                .until( this::isObjectDetected );
	}

which I will be able to test once I have the robot. And that seems like a satisfying syntax :slight_smile:

1 Like

It depends on the end condition of the original command. PrintCommand prints once and ends naturally immediately, so the .until never has a chance to interrupt before the command ends. Assuming your TurnDegrees command runs until it has turned 5 degrees, then stops the turning and ends, then the .until has the chance to stop it mid-command.

What’s the reasoning behind turning 5 degrees at a time?

2 Likes

I see! I didn’t realize that until(), when true, interrupts commands mid-command. I thought it was used to decide if a command should be run again.

So I guess instead of calling TurnDegrees() for small amount repeatedly (the 5 degree thing was an arbitrary example), I can call TurnDegrees() once with a large search angle and have until() interrupt it.

I really had a lot of misconceptions (which makes my post aptly titled). Thanks to both Amicus1 and Oblarg for helping me understand this better.

2 Likes

No problem; it turns out that you’ve basically learned the fundamentals of nesting scopes and delayed-execution programming in the span of this thread, and you’ll see the patterns you learned here in many other places if you continue working with software.

2 Likes