-
Notifications
You must be signed in to change notification settings - Fork 0
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){}
}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.
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));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.
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.
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.
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;
}
}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 insteadIn 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;
}
}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.