Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions source/docs/software/commandbased/binding-commands-to-triggers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ The command-based HID classes contain factory methods returning a ``Trigger`` fo
frc2::Trigger xButton = exampleCommandController.X() // Creates a new Trigger object for the `X` button on exampleCommandController
```

```python
from wpilib import CommandXboxController

example_command_controller = CommandXboxController(1) # Creates a CommandXboxController on port 1
x_button = example_command_controller.x() # Creates a new Trigger object for the `X` button on example_command_controller
```

### JoystickButton

Alternatively, the :ref:`regular HID classes <docs/software/basic-programming/joystick:Joysticks>` can be used and passed to create an instance of ``JoystickButton`` [Java](https://github.wpilib.org/allwpilib/docs/release/java/edu/wpi/first/wpilibj2/command/button/JoystickButton.html), [C++](https://github.wpilib.org/allwpilib/docs/release/cpp/classfrc2_1_1_joystick_button.html)), a constructor-only subclass of ``Trigger``:
Expand All @@ -42,6 +49,14 @@ Alternatively, the :ref:`regular HID classes <docs/software/basic-programming/jo
frc2::JoystickButton yButton(&exampleStick, frc::XboxController::Button::kY); // Creates a new JoystickButton object for the `Y` button on exampleController
```

```python
from wpilib import XboxController
from wpilib.buttons import JoystickButton

example_controller = XboxController(2) # Creates an XboxController on port 2
y_button = JoystickButton(example_controller, XboxController.Button.kY) # Creates a new JoystickButton object for the `Y` button on example_controller
```

### Arbitrary Triggers

While binding to HID buttons is by far the most common use case, users may want to bind commands to arbitrary triggering events. This can be done inline by passing a lambda to the constructor of ``Trigger``:
Expand All @@ -58,6 +73,14 @@ While binding to HID buttons is by far the most common use case, users may want
frc2::Trigger exampleTrigger([&limitSwitch] { return limitSwitch.Get(); });
```

```python
from wpilib import DigitalInput
from commands2 import Trigger

limit_switch = DigitalInput(3) # Limit switch on DIO 3
example_trigger = Trigger(limit_switch.get)
```

## Trigger Bindings

.. note:: The C++ command-based library offers two overloads of each button binding method - one that takes an [rvalue reference](https://learn.microsoft.com/en-us/cpp/cpp/rvalue-reference-declarator-amp-amp?view=msvc-170) (``CommandPtr&&``), and one that takes a raw pointer (``Command*``). The rvalue overload moves ownership to the scheduler, while the raw pointer overload leaves the user responsible for the lifespan of the command object. It is recommended that users preferentially use the rvalue reference overload unless there is a specific need to retain a handle to the command in the calling code.
Expand Down Expand Up @@ -146,6 +169,14 @@ It is useful to note that the command binding methods all return the trigger tha
.OnFalse(BarCommand().ToPtr());
```

```python
(example_button
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a typo?

# Binds a FooCommand to be scheduled when the button is pressed
.onTrue(FooCommand())
# Binds a BarCommand to be scheduled when that same button is released
.onFalse(BarCommand()))
```

## Composing Triggers

The ``Trigger`` class can be composed to create composite triggers through the ``and()``, ``or()``, and ``negate()`` methods (or, in C++, the ``&&``, ``||``, and ``!`` operators). For example:
Expand All @@ -166,6 +197,13 @@ The ``Trigger`` class can be composed to create composite triggers through the `
.OnTrue(ExampleCommand().ToPtr());
```

```python
# Binds an ExampleCommand to be scheduled when both the 'X' and 'Y' buttons of the driver gamepad are pressed
(example_command_controller.x()
.and_(example_command_controller.y())
.onTrue(ExampleCommand()))
```

## Debouncing Triggers

To avoid rapid repeated activation, triggers (especially those originating from digital inputs) can be debounced with the :ref:`WPILib Debouncer class <docs/software/advanced-controls/filters/debouncer:Debouncer>` using the `debounce` method:
Expand All @@ -186,3 +224,12 @@ To avoid rapid repeated activation, triggers (especially those originating from
exampleButton.Debounce(100_ms, Debouncer::DebounceType::Both).OnTrue(ExampleCommand().ToPtr());
```

```python
from wpimath.filter import Debouncer

# debounces example_button with a 0.1s debounce time, rising edges only
example_button.debounce(0.1).onTrue(ExampleCommand())
# debounces example_button with a 0.1s debounce time, both rising and falling edges
example_button.debounce(0.1, Debouncer.DebounceType.kBoth).onTrue(ExampleCommand())
```

161 changes: 160 additions & 1 deletion source/docs/software/commandbased/organizing-command-based.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ The easiest and most expressive way to do this is with a ``StartEndCommand``:
frc2::CommandPtr runIntake = frc2::cmd::StartEnd([&intake] { intake.Set(1.0); }, [&intake] { intake.Set(0.0); }, {&intake});
```

```python
from commands2 import cmd

run_intake = cmd.startEnd(lambda: intake.set(1.0), lambda: intake.set(0.0), intake)
```

This is sufficient for commands that are only used once. However, for a command like this that might get used in many different autonomous routines and button bindings, inline commands everywhere means a lot of repetitive code:

.. tab-set-code::
Expand Down Expand Up @@ -64,6 +70,19 @@ This is sufficient for commands that are only used once. However, for a command
);
```

```python
from commands2 import cmd

# RobotContainer.py
intake_button.whileTrue(cmd.startEnd(lambda: intake.set(1.0), lambda: intake.set(0.0), intake))
intake_and_shoot = cmd.startEnd(lambda: intake.set(1.0), lambda: intake.set(0.0), intake).alongWith(RunShooter(shooter))
autonomous_command = cmd.sequence(
cmd.startEnd(lambda: intake.set(1.0), lambda: intake.set(0.0), intake).withTimeout(5.0),
cmd.waitSeconds(3.0),
cmd.startEnd(lambda: intake.set(1.0), lambda: intake.set(0.0), intake).withTimeout(5.0)
)
```

Creating one ``StartEndCommand`` instance and putting it in a variable won't work here, since once an instance of a command is added to a command group it is effectively "owned" by that command group and cannot be used in any other context.

#### Instance Command Factory Methods
Expand Down Expand Up @@ -94,6 +113,17 @@ For example, a command like the intake-running command is conceptually related t
}
```

```python
from commands2 import Subsystem

class Intake(Subsystem):
# [code for motor controllers, configuration, etc.]
# ...
def runIntakeCommand(self):
# implicitly requires `self`
return self.startEnd(lambda: self.set(1.0), lambda: self.set(0.0))
```

Notice how since we are in the ``Intake`` class, we no longer refer to ``intake``; instead, we use the ``this`` keyword to refer to the current instance.

Since we are inside the ``Intake`` class, technically we can access ``private`` variables and methods directly from within the ``runIntakeCommand`` method, thus not needing intermediary methods. (For example, the ``runIntakeCommand`` method can directly interface with the motor controller objects instead of calling ``set()``.) On the other hand, these intermediary methods can reduce code duplication and increase encapsulation. Like many other choices outlined in this document, this tradeoff is a matter of personal preference on a case-by-case basis.
Expand Down Expand Up @@ -122,6 +152,18 @@ Using this new factory method in command groups and button bindings is highly ex
);
```

```python
from commands2 import cmd

intake_button.whileTrue(intake.runIntakeCommand())
intake_and_shoot = intake.runIntakeCommand().alongWith(RunShooter(shooter))
autonomous_command = cmd.sequence(
intake.runIntakeCommand().withTimeout(5.0),
cmd.waitSeconds(3.0),
intake.runIntakeCommand().withTimeout(5.0)
)
```

Adding a parameter to the ``runIntakeCommand`` method to provide the exact percentage to run the intake is easy and allows for even more flexibility.

.. tab-set-code::
Expand All @@ -139,6 +181,11 @@ Adding a parameter to the ``runIntakeCommand`` method to provide the exact perce
}
```

```python
def runIntakeCommand(self, percent):
return self.startEnd(lambda: self.set(percent), lambda: self.set(0.0))
```

For instance, this code creates a command group that runs the intake forwards for two seconds, waits for two seconds, and then runs the intake backwards for five seconds.

.. tab-set-code::
Expand All @@ -153,7 +200,15 @@ For instance, this code creates a command group that runs the intake forwards fo
frc2::CommandPtr intakeRunSequence = intake.RunIntakeCommand(1.0).WithTimeout(2.0_s)
.AndThen(frc2::cmd::Wait(2.0_s))
.AndThen(intake.RunIntakeCommand(-1.0).WithTimeout(5.0_s));
```
```

```python
from commands2 import cmd

intake_run_sequence = (intake.runIntakeCommand(1.0).withTimeout(2.0)
.andThen(cmd.waitSeconds(2.0))
.andThen(intake.runIntakeCommand(-1.0).withTimeout(5.0)))
```

This approach is recommended for commands that are conceptually related to only a single subsystem, and is very concise. However, it doesn't fare well with commands related to more than one subsystem: passing in other subsystem objects is unintuitive and can cause race conditions and circular dependencies, and thus should be avoided. Therefore, this approach is best suited for single-subsystem commands, and should be used only for those cases.

Expand Down Expand Up @@ -186,6 +241,24 @@ Instance factory methods work great for single-subsystem commands. However, com
// TODO
```

```python
from commands2 import cmd

class AutoRoutines:
@staticmethod
def driveAndIntake(drivetrain, intake):
return cmd.sequence(
cmd.parallel(
drivetrain.driveCommand(0.5, 0.5),
intake.runIntakeCommand(1.0)
).withTimeout(5.0),
cmd.parallel(
drivetrain.stopCommand(),
intake.stopCommand()
)
)
```

#### Non-Static Command Factories
If we want to avoid the verbosity of adding required subsystems as parameters to our factory methods, we can instead construct an instance of our ``AutoRoutines`` class and inject our subsystems through the constructor:

Expand Down Expand Up @@ -226,6 +299,35 @@ If we want to avoid the verbosity of adding required subsystems as parameters to
// TODO
```

```python
from commands2 import cmd

class AutoRoutines:
def __init__(self, drivetrain, intake):
self.drivetrain = drivetrain
self.intake = intake

def driveAndIntake(self):
return cmd.sequence(
cmd.parallel(
self.drivetrain.driveCommand(0.5, 0.5),
self.intake.runIntakeCommand(1.0)
).withTimeout(5.0),
cmd.parallel(
self.drivetrain.stopCommand(),
self.intake.stopCommand()
)
)

def driveThenIntake(self):
return cmd.sequence(
self.drivetrain.driveCommand(0.5, 0.5).withTimeout(5.0),
self.drivetrain.stopCommand(),
self.intake.runIntakeCommand(1.0).withTimeout(5.0),
self.intake.stopCommand()
)
```

Then, elsewhere in our code, we can instantiate an single instance of this class and use it to produce several commands:

.. tab-set-code::
Expand All @@ -244,6 +346,18 @@ Then, elsewhere in our code, we can instantiate an single instance of this class
// TODO
```

```python
from commands2 import cmd

auto_routines = AutoRoutines(self.drivetrain, self.intake)
drive_and_intake = auto_routines.driveAndIntake()
drive_then_intake = auto_routines.driveThenIntake()
driving_and_intaking_sequence = cmd.sequence(
auto_routines.driveAndIntake(),
auto_routines.driveThenIntake()
)
```

#### Capturing State in Inline Commands

Inline commands are extremely concise and expressive, but do not offer explicit support for commands that have their own internal state (such as a drivetrain trajectory following command, which may encapsulate an entire controller). This is often accomplished by instead writing a Command class, which will be covered later in this article.
Expand Down Expand Up @@ -271,6 +385,20 @@ However, it is still possible to ergonomically write a stateful command composit
// TODO
```

```python
from wpimath.controller import PIDController

def turnToAngle(self, target_degrees):
# Create a controller for the inline command to capture
controller = PIDController(Constants.kTurnToAngleP, 0, 0)
# We can do whatever configuration we want on the created state before returning from the factory
controller.setTolerance(Constants.kTurnToAngleTolerance)
# Try to turn at a rate proportional to the heading error until we're at the setpoint, then stop
return (self.run(lambda: self.arcadeDrive(0, -controller.calculate(self.gyro.getHeading(), target_degrees)))
.until(controller.atSetpoint)
.andThen(self.runOnce(lambda: self.arcadeDrive(0, 0))))
```

This pattern works very well in Java so long as the captured state is "effectively final" - i.e., it is never reassigned. This means that we cannot directly define and capture primitive types (e.g. `int`, `double`, `boolean`) - to circumvent this, we need to wrap any state primitives in a mutable container type (the same way `PIDController` wraps its internal `kP`, `kI`, and `kD` values).

### Writing Command Classes
Expand Down Expand Up @@ -307,6 +435,25 @@ Returning to our simple intake command from earlier, we could do this by creatin
// TODO
```

```python
from commands2 import Command

class RunIntakeCommand(Command):
def __init__(self, intake):
super().__init__()
self.intake = intake
self.addRequirements(intake)

def initialize(self):
self.intake.set(1.0)

def end(self, interrupted):
self.intake.set(0.0)

# execute() defaults to do nothing
# isFinished() defaults to return False
```

This, however, is just as cumbersome as the original repetitive code, if not more verbose. The only two lines that really matter in this entire file are the two calls to ``intake.set()``, yet there are over 20 lines of boilerplate code! Not to mention, doing this for a lot of robot actions quickly clutters up a robot project with dozens of small files. Nevertheless, this might feel more "natural," particularly for programmers who prefer to stick closely to an object-oriented model.

This approach should be used for commands with internal state (not subsystem state!), as the class can have fields to manage said state. It may also be more intuitive to write commands with complex logic as classes, especially for those less experienced with command composition. As the command is detached from any specific subsystem class and the required subsystem objects are injected through the constructor, this approach deals well with commands involving multiple subsystems.
Expand Down Expand Up @@ -334,6 +481,18 @@ If we wish to write composite commands as their own classes, we may write a cons
// TODO
```

```python
from commands2 import SequentialCommandGroup, cmd

class IntakeThenOuttake(SequentialCommandGroup):
def __init__(self, intake):
super().__init__(
intake.runIntakeCommand(1.0).withTimeout(2.0),
cmd.waitSeconds(2.0),
intake.runIntakeCommand(-1).withTimeout(5.0)
)
```

This is relatively short and minimizes boilerplate. It is also comfortable to use in a purely object-oriented paradigm and may be more acceptable to novice programmers. However, it has some downsides. For one, it is not immediately clear exactly what type of command group this is from the constructor definition: it is better to define this in a more inline and expressive way, particularly when nested command groups start showing up. Additionally, it requires a new file for every single command group, even when the groups are conceptually related.

As with factory methods, state can be defined and captured within the command group subclass constructor, if necessary.
Expand Down
12 changes: 12 additions & 0 deletions source/docs/software/hardware-apis/pneumatics/solenoids.rst
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,15 @@ Solenoids can be switched from one output to the other (known as toggling) by us
}
```

```python
from wpilib import Solenoid, DoubleSolenoid, PneumaticsModuleType

example_single = Solenoid(PneumaticsModuleType.CTREPCM, 0)
example_double = DoubleSolenoid(PneumaticsModuleType.CTREPCM, 1, 2)
# Initialize the DoubleSolenoid so it knows where to start. Not required for single solenoids.
example_double.set(DoubleSolenoid.Value.kReverse)
if self.controller.getYButtonPressed():
example_single.toggle()
example_double.toggle()
```

Loading