Go to Post When it was at 222, I was like 'Tigertrons from Tunkhahoohaasomethingconfusing Pennsylvania!' and my friend hit me for being such a loser. - Church [more]
Home
Go Back   Chief Delphi > Technical > Programming > Java
CD-Media   CD-Spy  
portal register members calendar search Today's Posts Mark Forums Read FAQ rules

 
Reply
 
Thread Tools Rate Thread Display Modes
  #1   Spotlight this post!  
Unread 24-01-2017, 21:55
dmelcer9 dmelcer9 is offline
Registered User
AKA: Daniel
FRC #0810 (Mechanical Bulls)
Team Role: Leadership
 
Join Date: Dec 2015
Rookie Year: 2012
Location: Smithtown
Posts: 51
dmelcer9 is an unknown quantity at this point
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);

...
Then, in a CommandGroup created during AutonomousInit:

Code:
addSequential((Command)Robot.autoDefenseChooser.getSelected());
However, this runs into a few problems. The biggest problem was that the robot would crash if you enter Autonomous mode twice without power cycling the robot. This was because the same commands were reused, and if the same command is added to multiple command groups, the program throws an exception and crashes.

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);
		...
	}
Keep in mind that TurnRightCommand::new creates a Supplier<TurnRightCommand> that simply constructs a TurnRightCommand whenever the get method is called. For example:

Code:
		Supplier<TurnRightCommand> s = TurnRightCommand::new;
		TurnRightCommand c1 = s.get();
		TurnRightCommand c2 = s.get();
		System.out.println(c1 == c2);//false
Anyways, we can now construct a commandgroup from AutoInit. The constructor for the commandgroup can look something like this:

Code:
		addSequential(Robot.autoStep1.getSelected().get());
		addSequential(Robot.autoStep2.getSelected().get());
2. The fun way

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));
                ...
        }
Note how all of the Suppliers are wrapped in Optionals. Now, instead of putting a DoNothingCommand, the "do nothing" option simply inserts an empty optional. In the command group itself, we can do something like this:

Code:
Robot.autoStep1.getSelected().map(Supplier::get).ifPresent(this::addSequential);
Robot.autoStep2.getSelected().map(Supplier::get).ifPresent(this::addSequential);
Let's break this down: the getSelected() method returns an Optional<Supplier<Command>>, meaning that it may contain an object that can create a new command. The map function says to take that Supplier<Command> and map it to the command. This function returns an Optional<Command>. Here's the trick- if the optional was empty in the first place, Java skips over the map step and simply returns another empty optional. Otherwise, it "Expands" the supplier and gets a fresh instance of a command from the supplier. If the optional contains a command, it will then add it to the command group.
Reply With Quote
  #2   Spotlight this post!  
Unread 24-01-2017, 23:57
rich2202 rich2202 is offline
Registered User
FRC #2202 (BEAST Robotics)
Team Role: Mentor
 
Join Date: Jan 2012
Rookie Year: 2012
Location: Wisconsin
Posts: 1,251
rich2202 has a reputation beyond reputerich2202 has a reputation beyond reputerich2202 has a reputation beyond reputerich2202 has a reputation beyond reputerich2202 has a reputation beyond reputerich2202 has a reputation beyond reputerich2202 has a reputation beyond reputerich2202 has a reputation beyond reputerich2202 has a reputation beyond reputerich2202 has a reputation beyond reputerich2202 has a reputation beyond repute
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.
__________________

Reply With Quote
  #3   Spotlight this post!  
Unread 25-01-2017, 00:02
SamCarlberg's Avatar
SamCarlberg SamCarlberg is offline
GRIP, WPILib. 2084 alum
FRC #2084
Team Role: Mentor
 
Join Date: Nov 2015
Rookie Year: 2009
Location: MA
Posts: 136
SamCarlberg is a splendid one to beholdSamCarlberg is a splendid one to beholdSamCarlberg is a splendid one to beholdSamCarlberg is a splendid one to beholdSamCarlberg is a splendid one to beholdSamCarlberg is a splendid one to beholdSamCarlberg is a splendid one to behold
Re: Building Commands at Runtime

Quote:
Originally Posted by rich2202 View Post
How about, at the beginning of autonomus, check the pointer to the command stack. If not null, flush the stack.
Or kill them in disabledInit()
__________________
WPILib
GRIP, RobotBuilder
Reply With Quote
  #4   Spotlight this post!  
Unread 25-01-2017, 06:10
dmelcer9 dmelcer9 is offline
Registered User
AKA: Daniel
FRC #0810 (Mechanical Bulls)
Team Role: Leadership
 
Join Date: Dec 2015
Rookie Year: 2012
Location: Smithtown
Posts: 51
dmelcer9 is an unknown quantity at this point
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.
Reply With Quote
  #5   Spotlight this post!  
Unread 25-01-2017, 10:13
SamCarlberg's Avatar
SamCarlberg SamCarlberg is offline
GRIP, WPILib. 2084 alum
FRC #2084
Team Role: Mentor
 
Join Date: Nov 2015
Rookie Year: 2009
Location: MA
Posts: 136
SamCarlberg is a splendid one to beholdSamCarlberg is a splendid one to beholdSamCarlberg is a splendid one to beholdSamCarlberg is a splendid one to beholdSamCarlberg is a splendid one to beholdSamCarlberg is a splendid one to beholdSamCarlberg is a splendid one to behold
Re: Building Commands at Runtime

"Kill" as in "kill the old values". Basically just assign new values to the variables.
__________________
WPILib
GRIP, RobotBuilder
Reply With Quote
  #6   Spotlight this post!  
Unread 25-01-2017, 10:32
dmelcer9 dmelcer9 is offline
Registered User
AKA: Daniel
FRC #0810 (Mechanical Bulls)
Team Role: Leadership
 
Join Date: Dec 2015
Rookie Year: 2012
Location: Smithtown
Posts: 51
dmelcer9 is an unknown quantity at this point
Re: Building Commands at Runtime

I guess that would work too, but it would be a bit less compact ¯\_(ツ)_/¯
Reply With Quote
  #7   Spotlight this post!  
Unread 25-01-2017, 18:53
david.e.boles david.e.boles is offline
Bringer of Whales and Petunias
AKA: david476
FRC #5940 (BREAD)
Team Role: Leadership
 
Join Date: Dec 2015
Rookie Year: 2011
Location: San Francisco, CA
Posts: 30
david.e.boles is on a distinguished road
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?
Reply With Quote
  #8   Spotlight this post!  
Unread 25-01-2017, 19:58
dmelcer9 dmelcer9 is offline
Registered User
AKA: Daniel
FRC #0810 (Mechanical Bulls)
Team Role: Leadership
 
Join Date: Dec 2015
Rookie Year: 2012
Location: Smithtown
Posts: 51
dmelcer9 is an unknown quantity at this point
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
Reply With Quote
  #9   Spotlight this post!  
Unread 27-01-2017, 00:02
wlogeais wlogeais is offline
Registered User
FRC #2177 (The Robettes)
Team Role: Mentor
 
Join Date: Feb 2016
Rookie Year: 2011
Location: Minnesota
Posts: 18
wlogeais is an unknown quantity at this point
Re: Building Commands at Runtime

Quote:
Originally Posted by dmelcer9 View Post
Last year, our team tried to have a modular autonomous command. The way this worked was something like this:

...

Then, in a CommandGroup created during AutonomousInit:

Code:
addSequential((Command)Robot.autoDefenseChooser.getSelected());
However, this runs into a few problems. The biggest problem was that the robot would crash if you enter Autonomous mode twice without power cycling the robot...
Here are two solutions to this: the boring way and the fun(ctional) way.
In my opion the root of this issue is from missuse of CommandGroup itself rather than alternatives which can be written for special situations like this.

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();
And then depending on the situation any 'command' can behave like a CommandGroup with special powers too.

(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();
	}
Reply With Quote
  #10   Spotlight this post!  
Unread 27-01-2017, 09:14
dmelcer9 dmelcer9 is offline
Registered User
AKA: Daniel
FRC #0810 (Mechanical Bulls)
Team Role: Leadership
 
Join Date: Dec 2015
Rookie Year: 2012
Location: Smithtown
Posts: 51
dmelcer9 is an unknown quantity at this point
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.
Reply With Quote
  #11   Spotlight this post!  
Unread 27-01-2017, 10:24
wlogeais wlogeais is offline
Registered User
FRC #2177 (The Robettes)
Team Role: Mentor
 
Join Date: Feb 2016
Rookie Year: 2011
Location: Minnesota
Posts: 18
wlogeais is an unknown quantity at this point
Re: Building Commands at Runtime

Quote:
Originally Posted by dmelcer9 View Post
But with a command group, any subsystems that the command group requires are "required" for the whole command group sequence.
Clearly that isn't true. To test this make a new CG() with cg.addSequential(new DriveForward()) then cg.addSequential(new Wait(10)). while it is waiting you'll find that your default-teleOP drive will be working.

Quote:
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.
Very true. This isn't a drive/game strategy forum but I think that the MultiStarter, which is/was your proposal, is poor example of WHY this topic-thread is relevant.

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?).
Reply With Quote
  #12   Spotlight this post!  
Unread 27-01-2017, 10:42
dmelcer9 dmelcer9 is offline
Registered User
AKA: Daniel
FRC #0810 (Mechanical Bulls)
Team Role: Leadership
 
Join Date: Dec 2015
Rookie Year: 2012
Location: Smithtown
Posts: 51
dmelcer9 is an unknown quantity at this point
Re: Building Commands at Runtime

Quote:
Originally Posted by wlogeais View Post
Clearly that isn't true. To test this make a new CG() with cg.addSequential(new DriveForward()) then cg.addSequential(new Wait(10)). while it is waiting you'll find that your default-teleOP drive will be working.
But when you add a command to a commandgroup, the commandgroup calls requires for all the subsystems that the added commands require. (Source) Maybe DriveForward and the JoystickDriveCommand don't have their requires set up correctly?


Quote:
Originally Posted by wlogeais View Post
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.
You can supply a RedBlueDumpFuel command (or similar) via a supplier. It wouldn't be too hard to drop a ConditionalCommand into the sendablechooser instead of a regular command.

Quote:
Originally Posted by wlogeais View Post
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?).
I'm not sure what you mean by needing to verify unwinding the stack. Using a supplier is more defensive programming- even if another programmer hijacks one of the suppliers from the sendablechooser, the commands that are generated are different instances.
Reply With Quote
  #13   Spotlight this post!  
Unread 27-01-2017, 11:27
wlogeais wlogeais is offline
Registered User
FRC #2177 (The Robettes)
Team Role: Mentor
 
Join Date: Feb 2016
Rookie Year: 2011
Location: Minnesota
Posts: 18
wlogeais is an unknown quantity at this point
Re: Building Commands at Runtime

Quote:
Originally Posted by dmelcer9 View Post
But when you add a command to a commandgroup, the commandgroup calls requires for all the subsystems that the added commands require. (Source) Maybe DriveForward and the JoystickDriveCommand don't have their requires set up correctly?
My mistake on not reading that source first. So a single CG with addParallel(ss1) twice is still problematic - So what exactly is the problem with this required-whole-time status, and how does supplier/optional change that at all? (since you are still added same subsystem-commands into a group)



Quote:
You can supply a RedBlueDumpFuel command (or similar) via a supplier. It wouldn't be too hard to drop a ConditionalCommand into the sendablechooser instead of a regular command.
True, so RedBlueStarter can extend Conditional... or else an anonymous extend of Conditional with the same one method added inline.

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:
I'm not sure what you mean by needing to verify unwinding the stack. Using a supplier is more defensive programming- even if another programmer hijacks one of the suppliers from the sendablechooser, the commands that are generated are different instances.
I just prefer the simplicity of a single chooser, eg. FuelThenGear as an option and the ability to not add GearThenFuel if it isn't going to be tested and used.
Reply With Quote
  #14   Spotlight this post!  
Unread 27-01-2017, 12:11
dmelcer9 dmelcer9 is offline
Registered User
AKA: Daniel
FRC #0810 (Mechanical Bulls)
Team Role: Leadership
 
Join Date: Dec 2015
Rookie Year: 2012
Location: Smithtown
Posts: 51
dmelcer9 is an unknown quantity at this point
Re: Building Commands at Runtime

Quote:
Originally Posted by wlogeais View Post
So what exactly is the problem with this required-whole-time status, and how does supplier/optional change that at all? (since you are still added same subsystem-commands into a group)
Oh, I'm saying that doing the supplier/optional/commandgroup setup allows the required whole time, while instead doing a custom implementation of commandgroup doesn't allow that. The required whole time feature is good because any subsystems that need to be in a certain state for the command group to function optimally will be in that state the whole time the command group is running.

Quote:
Originally Posted by wlogeais View Post
I just prefer the simplicity of a single chooser, eg. FuelThenGear as an option and the ability to not add GearThenFuel if it isn't going to be tested and used.
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.
Reply With Quote
  #15   Spotlight this post!  
Unread 27-01-2017, 13:30
rich2202 rich2202 is offline
Registered User
FRC #2202 (BEAST Robotics)
Team Role: Mentor
 
Join Date: Jan 2012
Rookie Year: 2012
Location: Wisconsin
Posts: 1,251
rich2202 has a reputation beyond reputerich2202 has a reputation beyond reputerich2202 has a reputation beyond reputerich2202 has a reputation beyond reputerich2202 has a reputation beyond reputerich2202 has a reputation beyond reputerich2202 has a reputation beyond reputerich2202 has a reputation beyond reputerich2202 has a reputation beyond reputerich2202 has a reputation beyond reputerich2202 has a reputation beyond repute
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.
__________________

Reply With Quote
Reply


Thread Tools
Display Modes Rate This Thread
Rate This Thread:

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Forum Jump


All times are GMT -5. The time now is 12:58.

The Chief Delphi Forums are sponsored by Innovation First International, Inc.


Powered by vBulletin® Version 3.6.4
Copyright ©2000 - 2017, Jelsoft Enterprises Ltd.
Copyright © Chief Delphi