The most important class in the entire robot project is the RobotContainer class. The RobotContainer class is where all the subsystems of the robot are declared and where all button are binded.
Now, that was a mouthful. Saying that in words you actually understand: The RobotContainer is responsible for coordinating the commands and subsystems together in a robot.
It also controls all the button bindings, so the commands work when you press a button on the robot controller.
You should open the 2024 robot code and open RobotContainer.java, and follow along wit the lesson
CommandXboxController (Line 62) | This is the instance variable where the Xbox controller is declared. This allows for button presses to call certain commands. |
Subsystems (Lines 69-73) | Subsystems are each declared as private final variables. A single subsystem should only be defined once. |
SendableChooser (Lines 76-77) | SendableChoosers are declared as instance variables. This allows you to choose between different autons and allows for delays. You don't need to worry too much about them right now. |
In the constructor, all the instance variables are instatinsated (creating an instance of that object) and their constructors called.
The if statements are there to check whether the subsystem should be instatinsated depending on the robot.
For example, if we are making a testing robot that does not have an Amp Arm, it doesn't try to instansiate an Amp Arm Subsystem.
The methods which return a command are all used for AutoAlign, which we will get into in later lessons.
The registerNamedCommands()
is for Pathplanner autonomous paths. This allows the robot to choose between which autonomous path to run based off certain criteria.
We will not be using Pathplanner this year, so you can ignore that method.
This method is used to bind buttons on the xbox controller to robot commands.
To bind buttons, we use something called a Trigger
A Trigger basically has methods that allow us to detect if something about the robot or Xbox controller has recently changed.
They are mainly used for binding buttons, but they do have many other uses.
The Trigger class has several methods:
TriggerMethod(Command) | Each trigger method takes in an instantiated command as a parameter. |
onTrue() | Runs the command once the trigger becomes true and stops running the command when it becomes false. |
whileTrue() | Runs the command as long as the trigger is true and stops running the command when it become false. |
onFalse() | Runs the command once the trigger becomes false and stops running the command when it becomes true. |
whileFalse() | Runs the command while the trigger is false and stop running the command when it becomes true |
toggleOnTrue() | Begins running the command when the trigger turns true. Continues running it as usual when it turns false |
toggleOnFalse() | Begins running the command when the trigger turns false. Continues running it as usual when it turns true |
You will almost never declare your own Triggers. In the RobotContainer class for 2024, we declare our own custom Trigger once. Instead the CommandXboxController class provides Triggers with methods such as a(), b(), x()
This is so that you can bind commands to every single button on the xbox controller
You can chain triggers with and(), or(), and not(), just like a normal boolean, but with methods instead of the symbols. You cannot use the symbols with Triggers
It's important to understand that Triggers are not booleans, even though they're similar
You can also get the left trigger axis and right trigger axis as a double between 0 and 1 with getLeftTriggerAxis() and getRightTriggerAxis() if you're interested in how hard the driver is pressing the triggers. You also need to do this if you want a command to activate at a trigger axis of less than 0.5(that's how much it takes for the trigger to turn true)
Here's an example from last year's robot of using getLeftTriggerAxis()
This methods returns the autonomous command to run using the information from the Sendable Chooser, using the getSelected() method. It uses a ProxyCommand, which I'll talk about later in the lesson.
Command CompositionsNow, for a slight change in pace, we're moving on to command compositions. A command composition is a command that consists of other commands.
This is useful in many cases. For instance, in last year's code, we created a command composition for shooting into the amp, one for shooting into the speaker, etc.
Why did we do that? We did it because both shooting into the amp and speaker used the shooter command, but the amp command also involved raising the arm, creeping forward, then shoot, then move back(Note it was more complicated, but that's the gist)
Now let's talk about different types of command compositions.
Each type of command composition is backed by a specific class and also one of the 38 inherited methods in the Command class(that's why Command has 38 inherited methods). Some of them also have a factory method.
We'll usually use the decorator methods.
RepeatCommandIn RepeatCommand, a command is restarted after isFinished() returns true
RepeatCommand is backed by the RepeatCommand class. You can also get a RepeatCommand from a normal Command by calling the repeatedly() method.
SequentialCommandGroupYou can have a command scheduled after another in a SequentialCommandGroup. It is backed by the SequentialCommandGroup class. It is backed by the sequence() factory in the Commands class(not Command) class...
For the decorator method, you can use andThen() to attach something after a command and beforeStarting() to attach a command before a command.
There are 3 types of parallel commands that are all slightly different.
A parallel command group runs all commands in parallel, and ends when all of the commands are finished. A parallel deadline group has a deadline command and the members and ends when the deadline ends and interrupts all the members. A parallel race group ends when one of the commands in the group ends
Let's talk about all 3 types in more detail.
A parallel command group is backed by the ParalllelCommandGroup class. To use a decorator, you use the alongWith() method.
A parallel race group is backed by the ParallelRaceGroup class. To use a decorator, use the raceWith() method
A parallel deadline group is backed by the ParallelDeadlineGroup. To use a decorator, use the deadlineWith() method
To add a end condition for a command, use the until() method.
If you want to add a timeout, use the withTimeout() method. The method takes the number of seconds of timeout as a parameter
finallyDo() allows you to make the robot execute a lambda after the command ends. A lambda expression is basically a method that has no names, takes no parameters, returns nothing, and is directly declared. For instance, you can have a call of command.finallyDo(() -> System.out.println("Hello, World");)
if you want "Hello, World" to be printed after the command ends.
handleInterrupt() allows you to make the robot execute a lambda only if it's interrupted(e.g. if the interrupted parameter in end() is true)
A select command is a command that allows you to create a map and allow you to choose based on the current robot state(That's actually a large part of auto-align and why there are a bajillion methods in RobotContainer)
You use the SelectCommand class: there is no decorator for this one
A conditional command runs a command only if a condition is true
There is an unless() decorator that allows you to pass a condition to not run the command
This is the most non-intituitve type of command compostion we're learning today. The only reason it exists is because the command scheduler is bad and it's a little workaround around the bad command scheduler
As you remember from Lesson 3(hopefully), each command has some requirement subsystems. The command scheduler uses this to prevent scheduling 2 commands that require the same subsystem
Command compositions are treated as one command by the command scheduler though, and every single requirement is inherited by the composition
This causes problems if you need to use a subsystem that's a requirement of a command composition but no longer used.
The whole point of a proxy command is to ensure that the command composition doesn't inherit all of the requirements of that command. That's about it.
If you didn't get that explanation, ask someone on the code team and they'll explain it to you better
Here is a reference of the type of command compositions and an example
Composition | What is it? | Decorator method |
RepeatCommand | Runs the command repeatedly | repeatedly() |
SequentialCommandGroup | Runs the command in the group in order | beforeStarting() to add a command before the current command and andThen() to add a command after the current command |
ParallelCommandGroup | Runs the commands in the group at the same time. Ends the group when all of the commands in the group end | alongWith() |
ParallelRaceGroup | Runs the commands in the group at the same time. Ends the group when one of the commands in the group ends | raceWith() |
ParallelDeadlineGroup | Runs the commands in the group at the same time. Ends the group when the desiginated deadline command ends | deadlineWith() in 2024 and deadlineFor() in 2025 |
An end condition | Adds a condition that ends the command early | until() |
A timeout | Adds a timeout | withTimeout() |
End behavior | Adds behavior for the robot to execute to handle after the end of the command | finallyDo() for all cases, handleInterrupt() for only an interruption |
SelectCommand | Has the robot choose the command based on a map and curret state | N/A |
ConditionalCommand | Has the robot execute the command based on a condition | unless() |
ProxyCommand | Allows the command composition to not inherit all of the requirements of the child commands | N/A |
A good example of a use of command composition is the forAmpAutoFire() method in ThrowCommand, which is shown for your convienence