Question about Command Based Programming

Hello all,

I’m starting on learning command-based programming for my team this fall. My underlining confusion at this point is that I’m not sure how command scheduling works.

For example: At the moment I have 2 subsystems and 2 commands: Drivetrain and Hopper as subsystems, and Swerve Drive and Manual Hopper as the commands.

In theory, I want the execute() in swerve drive to be running non-stop during teleop, and the manual hopper execute() to be running while I’m holding down the x button on the manipulator controller. My understanding is that the swerve drive command should inherently be running due to the fact that it is a command, but doesn’t that mean the manual hopper one is also? Or if I want the swerve drive command to be running during autonomous but not the hopper, how do I remove the hopper command from the scheduler during auto and put it back in teleop?

Is the solution to this to put an if statement for the x button value inside the manual hopper execute and have it run during the whole match? I don’t think this is right because I see tutorials online scheduling commands in RobotContainer, but these are usually just instant commands, and not ones that happen for a span of time like all of teleop, or while a button is being pressed.

Here is my file structure if anyone is interested:
image

I can obviously add a picture of my code later someone thinks it would help, I just don’t want to add 500 lines to what I think should be a simple explanation.

Thanks a lot!

I suspect others will chime in here as well giving very detailed descriptions and links to docs.wpilib.org and I suggest you take a visit there and look at the command base documents.

I also like to suggest you open one of the examples that are bundled in the FRC VSCode installer. Simply create new project and select the example vs template option. Try GearsBot :wink:

What your looking for to start with your two subsystem will be in the RobotContainer. Here you will create your subsystems, Define default commands to the subsystem if they have one. (Your Drivetrain does!) and finally create your joystick objects and link buttons to commands that intern interact with a subsystem.

I usually start with my new student using the Robotbuilder Tool also installed with the FRC VSCode installer. My Jumpstart Video is HERE I like to start here because it gets all the base code organized well and will make the connections for the programer. After the first day or two we abandon RobotBuilder and Continue to build off what was created.

PS The other option to posting code and it will be suggested anyhow is to link the code to a git repository (Github is commonly used) and push it there. Here are a few notes on git in VSCode I put together

2 Likes

Nope. Is a car always running because it is inherently a car, or do you have to start it? You have to schedule the command to run. Be it as a Subsystem’s Default Command (What you want for Swerve where the command will always run if no other Command is scheduled for that given subsystem), triggered by a gamepad via a Button object, or any other condition via a Trigger object.

Like @cpapplefamily said, I highly recommend reading through WPILIB’s documentation as well as the Command-Based examples they provide.

A basic overview though is that you create Susystem and Command Classes to define the low-level behaviors like reading sensors, writing outputs like motors and pneumatics, and implementing your calculations and algorithms that make the hardware operate. Then you combine them, along with Joystick/XBoxController/ect. and Button/Trigger Objects in the RobotContainer to define your high-level behaviors mainly when specific actions should occur.

So your RobotContainer may look like the following:

public class RobotContainer {
  // The robot's subsystems and commands are defined here...
  private final DriveTrain m_driveTrain = new DriveTrain();
  private final Hopper m_hopper = new Hopper();

  XboxController m_driverController = new XboxController(Constants.DriverController);

  /*
   * The container for the robot. Contains subsystems, OI devices, and commands.
   */
  public RobotContainer() {
    // Configure the button bindings
    configureButtonBindings();


    driveTrain.setDefaultCommand(new SwerveDrive(driveTrain, () -> m_driverController.getX(Hand.kLeft), () -> m_driverController.getY(Hand.kLeft), () -> m_driverController.getX(Hand.kRight)));
  }

  /*
   * Use this method to define your button->command mappings. Buttons can be
   * created by instantiating a {@link GenericHID} or one of its subclasses
   * ({@link edu.wpi.first.wpilibj.Joystick} or {@link XboxController}), and then
   * passing it to a {@link edu.wpi.first.wpilibj2.command.button.JoystickButton}.
   */
  private void configureButtonBindings() {
    // Driver Controller Buttons
    JoystickButton hopperButton = new JoystickButton(m_driverController, Constants.HopperButton);

    // Hopper
    hopperButton.whenPressed(new ManualHopper(hopper));

  }

  /**
   * Use this to pass the autonomous command to the main {@link Robot} class.
   *
   * @return the command to run in autonomous
   */
  public Command getAutonomousCommand() {
    // An ExampleCommand will run in autonomous
    return null;
  }
}

Of course variations exist. Some people define their Commands at the top with the Subsystems instead of initializing them directly in the configureButtonBindings method. Some also define their defaults in the configureButtonBindings instead of the RobotContainer Constructor. Some others also define their Joysticks in the configureButtonBindings along with their buttons and triggers. All those decisions tend to be more stylistic and depend on how you think your code will make the most sense.

3 Likes

Thanks for the speedy response!

Am I correct in saying that every subsystem has a command that can be running, and robotContainer is what determines what that command is?

In the case of the hopper, would you recommend not giving it a default command, but instead just mapping a button to each of its different functions? Because a lot of the time it is inactive. Also for ending the command, would you recommend returning true in isFinished() of the command if the button’s value is no longer true? Or would it be something like using whilePressed() in robot container?


Thanks again.

Pretty much right. If you read the What Is “Command-Based” Programming? — FIRST Robotics Competition documentation under How are Commands Run, it talks about how you setup your commands to require a subsystem. This way the scheduler knows what subsystems are required by what running commands and make sure two commands that require the same subsystem aren’t running simultaneously.

No, and Yes. I personally recommend having a default command for all subsystems, and that said default should usually be a “stopped” command. A command that just makes sure that all the motors are stopped (or holding steady if required… but you should always try to make it not required…) and whatever else to make sure that if another command fails to cleanup after itself properly when it is finished, the default command will intervene.

If you take a look at 3468’s code from 2020, you can see this in action:

With the stop or other “fail-safe” command as the default, you then define the conditions under which the subsystem SHOULD do something. Of course finding ways to combine sensors or other inputs to reduce and automate the number of things the driver needs to keep track of is highly recommended. That is where Trigger objects can come into play.

In the following code-segment you can see we have several Trigger objects that are reading sensors from our subsystems, and then using those sensors along with button inputs to decide when to run our intake, conveyor, and launcher subsystems. The intake and conveyor ones being particularly cool to me because they automatically turned off and on to index the power cells into our conveyor such that they greatly reduced the risk of jamming all with a single button!

So, I’m sure you’ve figured out the answer from my thoughts on the topic of default commands, but if you want the hopper to run as long as you are holding the button, you’d want to use the whileHeld or WhenHeld methods of the https://first.wpi.edu/wpilib/allwpilib/docs/release/java/edu/wpi/first/wpilibj2/command/button/Button.html class. Or whileActiveContinuous​ and whileActiveOnce​ for the https://first.wpi.edu/wpilib/allwpilib/docs/release/java/edu/wpi/first/wpilibj2/command/button/Trigger.html class (Button is actually a subclass of Trigger, so all the options generally have their equivalents, mostly with replacing “pressed” with “active”)

+1 to having a “stall” command.

Our line of thinking often says that each subsystem should be in one given state at a time. Different commands can swap between the states, and there is one default one. Here is an example of our Hopper subsystem from our design spec:


Hopper

The hopper’s job is to store balls collected from the intake, then quickly push them into the shooter one by one.

States

Idle Hopper (default)

In this state, the dejammer wheels and the feeder wheels stop spinning.

Feed

When entering the feed state, the dejammer wheels and the feeder wheels both spin inwards. This forces the balls into the shooter.

Dejam

The feeder wheel stops spinning and the dejammer wheels spin outwards. This removes jams. All jellies, perservatives, and marmolades remain in the robot.

Hardware

  • 1 VictorSP for feeding balls
  • 1 VictorSP (connected to 2 bag motors) to dejam balls

(Some people say a spec is over kill. We find it to be just the right amount of kill)

Full Subsystem Spec

Drivetrain

The drivetrain’s job is to get the robot from point A to B, whether that be by driver input or trajectory following. It uses curvature drive and motion-profiling with quintic spline generation.

Marketing Note: Motion profiling + Curvature drive are very advance topics, please make sure include highlight them in any media about the drivetrain.

States

Open Loop (default)

In this state, the driver is in full control of movement. Joystick input will be fed directly in as power inputs to the motors (after being cubed in order to give more control to the drivers)

Path Follower

In this state, the motion is controlled by a trajectory. This trajectory is fed in as a parameter. It sets the speed of the drivetrain based on an internal timer. This state automatically ends when the timer reaches or exceeds the total time of the trajectory.

Hardware

  • 4 FalconFX’s for driving

Shooter

The shooter’s job is to automatically track the goal and shoot balls when told to by the operator. It automatically tracks with the use of vision processing and gyro-based control.

Techincal Note: The shooter and hopper are very closely intertwined in function. We considered combining into one subsystem. It was decided that they each had their own states and functions, so they should be keep seperate. In the case that a command group needs information about a subsystem to determine the state of another, a “status” value will be passed over network tables.

States

Idle Shooter

All motors stop rotating.

Lazy Tracking (default)

In this state, the turret uses the NavX Gyro to approximate the goal target. It uses the angle to point the turrent down field. This makes it so the turret is almost lined up once we are ready to shoot. All other motors stop spinning.

Techinical Note: Consider using occasional burst of limelight tracking to adjust the angle of hood. Might also make since not to move the hood (because that’s you’re like to shoot from the same position) or to go back to a default angle.

Precision Shot

In this state, the turret and the hood use the limelight vision tracking to move into position. If an adeqauite vision target cannot be found, default to joystick values until found. The flywheels spin up once into position (within a tolerance). The limelight camMode is set to 1 for short range shooting.

Techincal Note: Rather than a 1, you could make a constant in the constances file and pull that.

Long Shot

This state is the same as Precision Shot, except the Limelight camMode is set to 0.

Quick Shot

This state is the same as Precision Shot, except the flywheel start spinning up immediate, allowing a operator to take a shot regardless of whether the turret is fully lined up. It will still try to lineup though.

Techincal note: Consider overloading and making this take in a tolerance parameter. The robot shoots once within the tolerance. Send a warning print statement if a tolerance less than the one specified by Precision Shot is used, since this method specifically is meant to be less tolerance than Precision Shot.

Eject

This state sets the flywheels to a very low power, so the balls can be gentle lofted to forwards, about 10 feet. All other motors stop. Overload the construct to allow an turret angle and hood angle to be added to specify the position of these.

Marketing Note: This method is used not only for dejamming balls stuck in the shooter, but also for the strategic value of being able to unload one’s feederstation very quickly into their trench run.

Hardware

  • 2 FalconFX’s for the flywheels
  • 1 TalonSRX for the hood
  • 1 Rev through bore encoder for the hood
  • 1 Limelight for hood and turret
  • 1 TalonSRX for the turret
  • 1 NavGyro for the turret

Intake

The intake’s job is to collect balls from the ground.

States

Stow (default)

In this state, the intake rollers stop spinning. It is stowed inside the robot for protection.

Collecting

The intake roller spin inward. It is extended outside the frame to collect balls.

Hardware

Open Issue Don’t know what CAD plans to use to actuate intake

  • 1 VictorSP for intake rollers
  • 2 pneumatics for acuating the intake

Hopper

The hopper’s job is to store balls collected from the intake, then quickly push them into the shooter one by one.

States

Idle Hopper (default)

In this state, the dejammer wheels and the feeder wheels stop spinning.

Feed

When entering the feed state, the dejammer wheels and the feeder wheels both spin inwards. This forces the balls into the shooter.

Dejam

The feeder wheel stops spinning and the dejammer wheels spin outwards. This removes jams. All jellies, perservatives, and marmolades remain in the robot.

Hardware

  • 1 VictorSP for feeding balls
  • 1 VictorSP (connected to 2 bag motors) to dejam balls

Climber (Currently off the table due to complexity)

The climber’s job is to extend at the end of the match and allow the robot to pull itself up onto the shield generator.

States

Open Issue: A clear enough picture does not exist. Talk to CAD about what theclimber might look like.

Hardware

Open Issue: This is just a guess, because CAD has not designed this feature yet.

  • 1 Falcon500 for climbing wench
  • 1 pnuematic for a brake

Writing Specs

Specs written based on approach by Joel Spolsky.

Painless Functional Specifications

3 Likes

I will echo the importance of reading the documentation that has already been linked, but specifically to the questions of:

How does my command get scheduled to run on a given Subsystem?
How does the scheduler determine which of my commands to be running?
How do I tell the scheduler I want it to run one of my command instances?

Those are roughly the basis questions that you were searching for in your original posts (as I read them).

I feel your on your way but you don’t want to use that boolean xDown = Robot… and the is finished Logix. This is coupling the code in ways that works but is BAD practice. The New Command Based format was put together to get us further away from coupling.

In stead simply leave the isFinished() method return false. Then in the RobotContainer changed the oButtonX.whenPressed(…) to oButtonX.whileHeld(…). This will do the same thing and when the button is released the end() method will be called.

You should turn off the hopper motor in the end() method too.

Now the other big coupling issue you have is the use of Robot.hopper in the addRequirements() call. I’m a bit surprised you where able to import the Robot.hopper and in-fact that tells me you declaired the hopper object in the Robot class. This should be in the RobotContainer too. Here is what this command should look like. You need to fill in the gaps…

public class ManualHopper extends CommandBase {
	//Declare a HopperSubsystem object
	private HopperSubsystem m_subsystem;
	
	/** Creates a new ManualHopper. 
	*	requires the HopperSubsystem to be passed in as a parrameter
	*/
	public ManualHopper(HopperSubsystem subsystem){
		
		//Copy the parrameter to the Commands private m_subsystem
		m_subsystem = subsystem;
		//
		addRequrements(subsystem);
	}
	....
    public void execute(){
            m_subsystem.setHopper(.5);
    }

    ....
    public void end(boolean interrupted) {
            m_subsytem.setHopper(0.0);
    }
	....

Please Look at some of the code examples, Maybe use Robotbuilder (I strongly suggest for new Coders is HERE ) We don’t want you to develop bad habits. So when you do ask for help a neighboring team or CSA will not have to hunt down the paths of where things are coming and going.

1 Like

This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.