Skip to content

commands

tekdemo edited this page Oct 4, 2024 · 1 revision

Commands

Commands should be the "verb" action your robot performs. This is the base system for a command.

new ExampleCommand extends Command{
    public ExampleCommand(){}
    public void initialize(){}
    public void execute(){}
    public boolean isFinished(){return false;}
    public void end(boolean isCancelled){}
}

Useful Command-based Shortcuts.

Commands have a number of useful factory functions to fill out some or all of the default Command structure shown above, and especially useful for Command Composition

Here's some useful ones, with a more comple

new RunCommand(()->{}); //takes a runnable, runs it as Execute.
new InstantCommand(()->{}); //takes a runnale, runs it as Execute, but then exits.
new StartEndCommand(()->{},()->{}); //Takes two Runnables, and used for Initialize and End. isFinished always returns false

new FunctionalCommand(){
    ()->{}, //the Runnable for init
    ()->{}, //the Runnable for execute
    (boolean end)->{}, //the Runnable for init
    ()->{return true;}, //Boolean supplier for isFinished
    Subsystem... subsystems //any subsystems to require
}

There's also a few easily missed Command operations that can be used for complex logic

new ConditionaCommand(Command A,Command B,boolean a_or_b); //Takes two Commands, running one on true, one on false. 
new SelectCommand(Map<Object,Command>  commands, Supplier<Object> selector); // Runs  the command indicated by selector; Allows more options than ConditionalCommand

new PrintCommand("string to print");
new WaitCommand(3.13); //do nothing for 3.14 seconds


new DeferredCommand(Supplier<Command> supplier, Set<Subsystem> requirements); //Complicated, but allows creation of custom commands at runtime through Command suppliers.
// see https://github.wpilib.org/allwpilib/docs/release/java/edu/wpi/first/wpilibj2/command/DeferredCommand.html

//Commands has some interesting static factory methods that can reduce syntax on common idioms
Commands.idle(... Subsystems) //creates a new command that does nothing; EG new Runcommand(()->{})
Commands.none(... Subsystems) //creates a new command that does nothing; EG new InstantCommand(()->{})
Commands.waitUntil(BooleanSupplier) // Runcommand that exits on condition 

Commands have a number of additional decorators that can change various conditions. The general idiom is to use factories and decorators to build up more complicated routines.

Trivial or uncommon commands

Using functional interfaces for simple commands works great. For well-structured subsystems, this can reduce the need for dedicated commands for one-off operations.

    joystick.a().whileTrue(new RunCommand(intake::runIntake,intake));
    joystick.b().onTrue(new InstantCommand(intake::stop,intake));

Simple common commands

For more frequently used commands, and commands with more logic, but not so complicated that a full file is needed. In such cases, it's easier to construct these inside of a subsystem using a Factory pattern, which builds and returns a command. You can use this to have a command require the subsystem it's created in.

This technique can save a lot of boilerplate and syntax compared to the alternative when considering an entire robot and operating sequences.

ExampleSubsystem extends Subsystem{
    public void setAngle(double angleDegrees){
        //logic to set the angle
    }

    public Command setAngleCommand(double angleDegrees){
        return new RunCommand(()->setAngle(angleDegrees),this);
    }
}
    //Clean and simple
    joystick.a().whileTrue(shooter.setAngleCommand(0));
    
    //Mess of syntax, which adds up for repetitive commands
    joystick.a().whileTrue(new Runcommand(()->shooter.setangle(0),shooter));

FunctionalCommand shines in this application; When combined with local variables, you can fully define a complete command state machine.

Complex Commands

Once you start getting out of trivial complexity, move them to either builder functions or dedicated files. This lends to better re-usability and debugging.

Managing Command Constructors

Due to the nature of WPILib, passing along subsystem references can make for unwieldy constructor commands.

Using decorator functions and good defaults as noted above can help.

Structuring your code to use Code Concepts/singletons for most sensor-systems will also reduce the need for unwieldy constructors.

A useful convention is to always put subsystems last, ensuring that important data is front and center when skimming code; We mostly don't care what subsystems it interfaces with.

    //long constructor
    public ShooterTurretMadness(double verticalAngle, double rotation, double rpm, Shooter shooter, Turret turret, Passthrough Passthrough){};

    //slog of a declaration
    new ShooterTurretMadness(20,-30,100,shooter,turret,passthrough);

Many of these commands will benefit from having both default options, and slight adjustments to their operation for certain conditions and sequences. This is easily accomplished by using sane defaults, and using custom decorators to alter those defaults.

A very useful decorator is one that blocks exit conditions. Some commands should normally exit, but might need an override in certain conditions, such as when attached to a driver's button. Using a custom override is more efficient than the built in .repeatedly() decorator; .repatedly() allows it to exit, then immediately reschedules it. When a command exits instantaneously, this constant re-scheduling creates lag, potentially leading to loop overrun errors. It also constantly runs plus initialize() and end(), which can create choppy performance.

Custom decorator example

The following example shows how both of these would work on a basic command.

new ShooterSetAngle extends Command{
    boolean exit = true;
    SlewRateLimiter rate=new SlewRateLimiter(10);
    double angle; 
    Shooter shooter;
    
    public ExampleCommand(double angle, Shooter shooter){
        this.angle = angle
        this.shooter = shooter;
    }

    public void initialize(){
        rate.reset()
    }
    
    public void execute(){
        shooter.setAngle(rate.calculate(angle));
    }

    public boolean isFinished(){
        return exit && shooter.isOnTarget();
    }

    public void end(boolean isCancelled){}

    public ShooterSetAngle dontExit(){
        exit = false;
        return this;
    }
    public ShooterSetAngle withRate(double rate){
        rate = new SlewRateLimeter(rate);
        return this;
    }
}

Including + Managing Human Input

Unfortunately, humans also interface with code. When interfacing with joysticks or dynamic data sources, you can use DoubleSuppliers, or other Supplier functions. and their getAs<type>() functions.

    public HumanInputCommand(DoubleSupplier angle, Shooter shooter){}

    new HumanInputCommand( ()->driverJoystick.getRawAxis(1), shooter); //run it dynamically
    new HumanInputCommand( ()->0, shooter); //provide a fixed value instead

In some cases, it may be beneficial to do constructor overloading, to have both simpler and more complex versions. This is sensible if a command should almost always have human input

public HumanInputCommand{
    DoubleSupplier dynamicInput;
    public HumanInput(DoubleSupplier dynamicInput){
        this.dynamicInput = input;
    }
    public HumanInput(Double input){
        this.dynamicInput = ()->input;
    }
    public execute(){
        var output = 10*dynamicInput.getAsDouble();//use the value somehow
    }
}

However, for optional human input, or for a one-off application, you can instead use a decorator to change how the function operates. This avoids needing to refactor large chunks of code

//This version overrides one with the other
public HumanInputCommand{
    //Supplier may not exist; Would need a null check, or prefereably, an optional to ensure code works properly.
    Optional<DoubleSupplier> dynamicInput = Optional.empty();
    Double input; 
    public HumanInput(Double input){
        this.input = input;
    }
    public execute(){
        var input = DynamicInput.isPresent() ? DynamicInput.getAsDouble() : this.input; 
        var output = 10*input;//use the value somehow
    }
    public HumanInputCommand addDynamicInput(DoubleSupplier input){
        this.dynamicInput = Optional.of(input);
    }
}

Alternatively, you can just provide an existing function that doesn't change your inputs. Just remember how it works so you don't use it in unexpected ways.

public HumanInputCommand{
    //supplier always exists, and just returns zero; Never needs to be specially checked.
    DoubleSupplier dynamicInput = ()->0; 
    Double input; 
    public HumanInput(Double input){
        this.input = input;
    }
    public execute(){
        var input = input + dynamicInput.getAsBoolean();
        var output = 10*input;//use the value somehow
    }
    public HumanInputCommand addDynamicInput(DoubleSupplier input){
        this.dynamicInput = input;
    }
}

Managing Requirements

Commands should generally require any subsystems that they alter, be it by driving a motor or reconfiguring. This ensures that other commands that anticipate it working a specific way are not operating in unexpected ways, or fighting for control.

Typically, a command that requires a subsystem is cancelled when another command that requires this subsystem starts.

However, in specific conditions, you can use the .withInterruptBehavior(Command.InterruptBehavior.kCancelIncoming) decorate to instead cancel the new commands. This would allow you to forcibly continue commands that might be hazardous to interrupt, such as ones where a robot would collide with itself, or damage a game piece. However, use of this should be minimized, and always properly set their own exit conditions on completion.

Human operation that might interrupt such a command should probably also have a check and wait processs so that drivers don't have to tap buttons or be surprised if they try to do something during this process.

Clone this wiki locally