How to change command associated with button

We’re doing some night-of-regionals programming changes, and one idea we were investigating was swapping the command associated with a button by calling WhenPressed() to replace the command bound to the button. (Our goal was to make one button that advances through a couple different states).

Looking at the WPILib code, it looks like WhenPressed() calls are additive, and it’s not possible to replace the command associated with a button. Is that correct?

Our alternate approach is to maintain a state variable and vary the behavior of the associated command based on the state, but it seems less clean than replacing the command.

Another option might be to have the command call other “subcommands” based on the state variable. That way you can keep all the commands separate, with the only messiness being in the main command that does nothing except dispatch to other commands.

Alan – good idea. Our programmer Ryan came up with a similar solution where a command has a Boolean state that indicates which of the two desired command actions should be executed. It then schedules the desired command, and slaves IsFinished() to the same state of the scheduled command. I’ll see if I can get him to post it here so that the there’s a record for the community in the future.

I tried to accomplish this (in java) by just creating a new button object and calling WhenPressed() on that new object when I wanted to change the command associated with a particular joystick button. It didn’t really work out…and I couldn’t really make sense of the behavior when testing. I didn’t bother deleting (maybe java calls it disposing) the prior objects when creating new ones (since in java there is this magical thing called the garbage collector) but maybe that would have helped. My thread, http://www.chiefdelphi.com/forums/showthread.php?t=127920 contains the code I tried to use. I think the ability clear the commands associated with a button would be a useful feature to add. Maybe I will get around to entering an artifact on the FIRST Forge project page

@jwakeman: your issue appears to be that your alternate condition was causing a new command to be created every iteration. This would cause a new command to be created, assigned to the button, and interrupt the old each iteration.

I haven’t tried this code, but the pattern I used was like this:


<in class definition>
Command *mainCommand;
Command *altCommand;

<in constructor>
mainCommand = new MainCommand();
altCommand = new AlternativeCommand();

<in periodic function within the same class>
    if (condition)
    {
        button->whenPressed(altCommand);
    }
    else
    {
        button->whenPressed(mainCommand);
    }

This way, you have only have one instance of each command that can be assigned to the button.
I put this code in the OI class, and added an UpdateOI function, which is called from the teleop periodic loop.

Thanks for taking a look at my code. The second if condition in the actually has a ‘not operator’ (!) on the throwSwitch1.get() so I don’t think I was creating new command every iteration.

        if(throwSwitch1.get() && oi.current_interface == oi.CONFIG_FIELD_INTERFACE)
        {
            oi.setPitInterface();
        }
        
        if(!throwSwitch1.get() && oi.current_interface == oi.CONFIG_PIT_INTERFACE)
        {
            oi.setFieldInterface();
        }

My code would probably be more readable if I used throwSwitch1.get() == false, throwSwitch1.get() == true. I like your suggestion of switching back and forth between pre-made commands instead of creating new every time I switch. This still leaves the issue of being able to unmap a command, i.e. have a button go from doing something to doing nothing. I suppose you could create a null command which does nothing and switch between that and the do something command.

This is what Ryan from 3081 put together. We did a bit of testing of it at Wisconsin, will be bashing it more in the next few days.



// header

class SwapCommandCommand: public CommandBase {
private:
	bool swapState;
	Command *commandOne;
	Command *commandTwo;
	Command *runningCommand;
public:
	SwapCommandCommand(Command *commandOne, Command *commandTwo);
	virtual void Initialize();
	virtual void Execute();
	virtual bool IsFinished();
	virtual void End();
	virtual void Interrupted();
	void Reset();
};


// cpp
SwapCommandCommand::SwapCommandCommand(Command *commandOne, Command *commandTwo) {
	// Use requires() here to declare subsystem dependencies
	// eg. requires(chassis);
	this->commandOne = commandOne;
	this->commandTwo = commandTwo;
	//False for command one
	//True for command Two
	this->swapState = false;
	this->runningCommand = NULL;
}

void SwapCommandCommand::Initialize() {
	if(swapState) {
		printf("[SwapCommandCommand] Scheduling Command two
");
		this->runningCommand = commandTwo;
	} else {
		this->runningCommand = commandOne;
		printf("[SwapCommandCommand] Scheduling Command One
");
	}
	this->runningCommand->Start();
}

void SwapCommandCommand::Execute() {
}


bool SwapCommandCommand::IsFinished() {
	return !runningCommand->IsRunning();
}

void SwapCommandCommand::End() {
	printf("[SwapCommandCommand] The command has finished
");
	swapState = !swapState;
}

void SwapCommandCommand::Interrupted() {
	printf("[SwapCommandCommand] Interrupted canceling command
");
	this->runningCommand->Cancel();
}


void SwapCommandCommand::Reset() {
	this->swapState = false;
}

Here is a updated version of the swap command. It waits a few cycles before checking to see if the command has finished, to allow the command to be scheduled and then ran by the scheduler.

Header


#ifndef SWAPCOMMANDCOMMAND_H
#define SWAPCOMMANDCOMMAND_H

#include "../CommandBase.h"

/**
 *
 *
 * @author nowireless
 */
class SwapCommandCommand: public CommandBase {
private:
	bool swapState;
	int runCount;
	Command *commandOne;
	Command *commandTwo;
	Command *runningCommand;
public:
	SwapCommandCommand(Command *commandOne, Command *commandTwo);
	virtual void Initialize();
	virtual void Execute();
	virtual bool IsFinished();
	virtual void End();
	virtual void Interrupted();
	void Reset();
};

#endif

CPP


#include "SwapCommandCommand.h"

SwapCommandCommand::SwapCommandCommand(Command *commandOne, Command *commandTwo) {
	// Use requires() here to declare subsystem dependencies
	// eg. requires(chassis);
	this->commandOne = commandOne;
	this->commandTwo = commandTwo;
	//False for command one
	//True for command Two
	this->swapState = false;
	this->runCount = 0;
	this->runningCommand = NULL;

}

// Called just before this Command runs the first time
void SwapCommandCommand::Initialize() {
	this->runCount = 0;
	if(swapState) {
		printf("[SwapCommandCommand] Scheduling Command two
");
		this->runningCommand = commandTwo;
	} else {
		this->runningCommand = commandOne;
		printf("[SwapCommandCommand] Scheduling Command One
");
	}
	this->runningCommand->Start();
}

// Called repeatedly when this Command is scheduled to run
void SwapCommandCommand::Execute() {
	/* 
	 * We count the number of times the execute method is called,
	 * to allow the command to be scheduled and to be ran by the scheduler.
	 * This is is done to prevent this command from ending before the selected command has had a chance to run.
	 */
	
	if(this->runCount <= 2) {
		this->runCount++;
	}
}

// Make this return true when this Command no longer needs to run execute()
bool SwapCommandCommand::IsFinished() {
	return !runningCommand->IsRunning() && this->runCount >= 3;
}

// Called once after isFinished returns true
void SwapCommandCommand::End() {
	printf("[SwapCommandCommand] The command has finished
");
	swapState = !swapState;
}

// Called when another command which requires one or more of the same
// subsystems is scheduled to run
void SwapCommandCommand::Interrupted() {
	printf("[SwapCommandCommand] Interrupted canceling command
");
	this->runningCommand->Cancel();
}

void SwapCommandCommand::Reset() {
	this->swapState = false;
}