|
|
|
![]() |
|
|||||||
|
||||||||
![]() |
|
|
Thread Tools | Rate Thread | Display Modes |
|
|
|
#1
|
|||
|
|||
|
Building Commands at Runtime
Last year, our team tried to have a modular autonomous command. The way this worked was something like this:
Code:
autoDefenseChooser = new SendableChooser();
autoSideChooser = new SendableChooser();
...
autoDefenseChooser.addDefault("Do not drive forward", new WaitCommand(.01));
autoDefenseChooser.addObject("Only move forward", new DriveStraight(.8,1));
autoDefenseChooser.addObject("Go over terrain defense", new DriveStraight(1,2.5));
autoDefenseChooser.addObject("Low bar", new DriveStraight(.8, 5));
SmartDashboard.putData("Auto Defense", autoDefenseChooser);
autoSideChooser.addDefault("Do not turn", new WaitCommand(.01));
autoSideChooser.addObject("Turn Right", new MultiRotate(60,3));
autoSideChooser.addObject("Turn Left", new MultiRotate(-60,3));
SmartDashboard.putData("Auto Side Selector", autoSideChooser);
...
Code:
addSequential((Command)Robot.autoDefenseChooser.getSelected()); Here are two solutions to this: the boring way and the fun(ctional) way. (Note that this post uses a lot of method references, so I recommend that you take a look at them beforehand if you aren't familiar with them). 1. The boring (but effective) way First, create a command that does nothing. I'll call it DoNothingCommand. This command should immediately return true or be a timed command with a very short timeout. For this, we will use a Supplier. This allows us to get a "fresh" instance of the same command every time (so that way we aren't adding the same instance of a command to different instances of a commandgroup). In Robot.java: Code:
public static SendableChooser<Supplier<Command>> autoStep1 = new SendableChooser<>();
public static SendableChooser<Supplier<Command>> autoStep2 = new SendableChooser<>();
...
public void robotInit(){
...
autoStep1.addDefault("Do nothing", DoNothingCommand::new);
autoStep1.addObject("Go Straight",GoStraightCommand::new);
autoStep1.addObject("Turn Left", TurnLeftCommand::new);
autoStep1.addObject("Turn Right", TurnRightCommand::new);
SmartDashboard.putData("Auto Step 1", autoStep1);
autoStep2.addDefault("Do nothing", DoNothingCommand::new);
autoStep2.addObject("Shoot Fuel", ShootFuelCommand::new);
autoStep2.addObject("Place Gear", PlaceGearCommand::new);
...
}
Code:
Supplier<TurnRightCommand> s = TurnRightCommand::new; TurnRightCommand c1 = s.get(); TurnRightCommand c2 = s.get(); System.out.println(c1 == c2);//false Code:
addSequential(Robot.autoStep1.getSelected().get()); addSequential(Robot.autoStep2.getSelected().get()); This way throws an optional into the mix. An optional is meant to represent an object that may or may not be there, kind of like a null with superpowers. In Robot.java: Code:
public static SendableChooser<Optional<Supplier<Command>>> autoStep1 = new SendableChooser<>();
public static SendableChooser<Optional<Supplier<Command>>> autoStep2 = new SendableChooser<>();
...
public void robotInit(){
...
autoStep1.addDefault("Do nothing", Optional.empty());
autoStep1.addObject("Go Straight", Optional.of(GoStraightCommand::new));
autoStep1.addObject("Turn Left", Optional.of(TurnLeftCommand::new));
autoStep1.addObject("Turn Right", Optional.of(TurnRightCommand::new));
SmartDashboard.putData("Auto Step 1", autoStep1);
autoStep2.addDefault("Do nothing", Optional.empty());
autoStep2.addObject("Shoot Fuel", Optional.of(ShootFuelCommand::new));
autoStep2.addObject("Place Gear", Optional.of(PlaceGearCommand::new));
...
}
Code:
Robot.autoStep1.getSelected().map(Supplier::get).ifPresent(this::addSequential); Robot.autoStep2.getSelected().map(Supplier::get).ifPresent(this::addSequential); |
|
#2
|
|||
|
|||
|
Re: Building Commands at Runtime
How about, at the beginning of autonomus, check the pointer to the command stack. If not null, flush the stack.
|
|
#3
|
||||
|
||||
|
Re: Building Commands at Runtime
Or kill them in disabledInit()
|
|
#4
|
|||
|
|||
|
Re: Building Commands at Runtime
I'm not sure what you mean. Killing a command still doesn't allow you to add the command to another command group.
|
|
#5
|
||||
|
||||
|
Re: Building Commands at Runtime
"Kill" as in "kill the old values". Basically just assign new values to the variables.
|
|
#6
|
|||
|
|||
|
Re: Building Commands at Runtime
I guess that would work too, but it would be a bit less compact ¯\_(ツ)_/¯
|
|
#7
|
|||
|
|||
|
Re: Building Commands at Runtime
I feel like this is probably a stupid question, but why not just send out something other than a command (e.g. a String) and then initialize the command after it's selected?
|
|
#8
|
|||
|
|||
|
Re: Building Commands at Runtime
Same thing- using strings is less compact and is a bit more repetitious. Instead of having a massive if/else/switch complex for the strings, you can just get a fresh instance of the command and pass it along to the addSequential method. If you want to add or remove an option, you only need to edit what you add to the sendablechooser. If you use the sendablechooser in multiple different command groups, you need only one line instead of the whole if/else/switch chain.
The supplier also guarantees that each command group has a unique instance of a command. *Also imo lambdas and method references are more readable and look more elegant |
|
#9
|
|||
|
|||
|
Re: Building Commands at Runtime
Quote:
Call option 3, and either boring or exciting if you like. I'm maining use of your same 2 chooservars for this year as they are. I'd just change the autonomousInit() to a new commandgroup-like alternative. Code:
autonomousCommand = new MultiAutonomousStarter();
// REPLACES ... = chooser.getSelected();
// unchanged from wpilib default...
if (autonomousCommand != null)
autonomousCommand.start();
(such as here in my MultiAutonomousStarter example to handle your 2-choosers) Code:
private Command step1cmd;
private Command step2cmd;
public MultiAutonomousStarter() {
// FOLLOWING is NOT needed because this command will work like a commandgroup.
// requires(Robot.exampleSubsystem);
}
// here the chooser-setup is locked in.
protected void initialize() {
step1cmd = Robot.autoStep1.getSelected();
step2cmd = Robot.autoStep2.getSelected();
step1cmd.start();
}
// Here a sequential-like handoff or other special control is maintained.
protected void execute() {
if ( step1cmd!=null && !step1cmd.isRunning())
{
step1cmd = null;
step2cmd.start();
}
}
// isFinished() { return false; } is required revised it could add its own flexibility...
// end() {} is required but not relevant this time.
// in this case Called when autonomous times out.
protected void interrupted() {
if ( step1cmd!=null && step1cmd.isRunning())
step1cmd.cancel();
if ( step2cmd!=null && !step2cmd.isRunning())
step2cmd.cancel();
}
|
|
#10
|
|||
|
|||
|
Re: Building Commands at Runtime
But with a command group, any subsystems that the command group requires are "required" for the whole command group sequence. In the MultipleAutonomousStarter, a subsystem is only required while that specific command is executing. If (for example) step 1 of a command group is to move the camera to location x and step 4 is to line up using reflective tape, I don't want the driver manually moving the camera or some other command firing to move the camera to center or something like that.
Also, let's say you want to add a third step. For all of these approaches, you need to add a sendablechooser and some commands or suppliers of commands. With option 1 or 2, you than add one line of code in the constructor. With 3, you also need to modify the handoff sequence and the interrupted method. You can't guarantee that another programmer won't use the same instance of the command elsewhere. With a supplier, if they get a duplicate (but different) instance and start it while the command group is running, there is a requires conflict, your command group stops. You don't get the auto points, but not too catastrophic. Someone adds the same instance of a command you are using to their own command group? You get an exception when you start your command, even if the command group hasn't even run yet. Robot code crashes. Oops. |
|
#11
|
|||
|
|||
|
Re: Building Commands at Runtime
Quote:
Quote:
I'd propose that in this year’s game, given a non-symmetrical field, that a RedBlueStarter(redcmd, bluecmd) would be more desirable to some teams. That flexibility is difficult with your 2-choosers/Supplier based options. Clearly your free to favor either of your Supplier/Optional ideas. But from a CSA perspective I'd be leery about logic that borders on needing a "how to verify 'unwinding the stack'" as a concern. (YOU may do this right, but does your team have any other programmers?). |
|
#12
|
|||
|
|||
|
Re: Building Commands at Runtime
Quote:
Quote:
Quote:
|
|
#13
|
|||
|
|||
|
Re: Building Commands at Runtime
Quote:
Quote:
The other flexibility towards this I've seen lately is when you want to make a whileHeld(CG). i.e. addParallel(ss1); addParallel(ss2); Quote:
|
|
#14
|
|||
|
|||
|
Re: Building Commands at Runtime
Quote:
That's just driver preference I guess- our drivers liked the "building" the autonomous sequence before each match. Sometimes the alliance partners have weird robots or strategies, so it's good to have a modular system for any scenario. |
|
#15
|
|||
|
|||
|
Re: Building Commands at Runtime
FYI: A few years ago, there was an FMS problem where a 2nd Autonomous Init command was sent to some robots about 10 seconds into Autonomous.
You may want to figure out how to deal with the problem during Competition, vs intentionally during practice. Maybe Autonomous has to complete before you restart it. In that case, you flush the command que at the end of Autonomous. |
![]() |
| Thread Tools | |
| Display Modes | Rate This Thread |
|
|