2.4.9 - Proxy Commands
Danger
Be aware that there can be self-cancelling commands if proxy commands are used improperly.
Before we introduce the idea of a proxied command, we need to really understand the behavior that they alter.
Let’s say we’ve got the following command composition:
Command command = Commands.sequence(
arm.stow()
intake.intake());
Here, we assume that arm and intake are each subsystems that expose
commands through public methods. We’ll also assume that each command requires
the subsystem that produces it, i.e. arm.stow() requires arm and
intake.intake() requires intake.
Now, let’s assume that we’ve got a default command on arm. The point of
this doesn’t really matter - the main purpose of this example is to consider
when the subsystem arm is commanded (and more importantly, when it’s
not).
Because a command composition’s requirements are the union of the requirements
of its component commands, then entire command group command will have
requirements on arm, even when arm.stow() is not running. This means
that after arm.stow() completes, the arm has no command actively doing
anything on it - i.e. is effectively uncommanded.
Hiding requirements via proxy
The best way to avoid problems with requirements “bubbling up” to bigger and bigger compositions is by using a “proxy command” that has the following properties:
The proxy command has no requirements itself.
When the proxy command is ran, it schedules the real command.
The proxy command ends when the real command ends.
If the proxy command is interrupted, the real command is also interrupted.
What properties 2-4 mean is that the proxy command acts just like the regular command. It will run the same thing, and it will end just like the regular command.
However, this command has no requirements so if used in compositions, the composition itself won’t require whatever the regular command would. When the real command is scheduled (independently of the composition), it will have its requirements as usual. The only difference is that the composition can’t tell that this is going to happen, so the entire group doesn’t get the requirements of the original command.
Making proxies
In code, we can make a command proxied via the decorator .asProxy().
In our example, if we had wanted arm’s default command to run when the
arm.stow() command was finished, we would replace arm.stow() with
arm.stow().asProxy():
Command command = Commands.sequence(
arm.stow().asProxy(),
intake.intake());
Now the default command on arm will run when the stow command finishes,
even while the sequential group is still running.
Proxies in autonomous routines
Most autonomous routines are implemented using simple sequential and parallel command groups, which means that proxy commands are necessary if you want default commands to run in auton.
Simply proxy the commands that shouldn’t leak their requirements, and you’re good to go.
Misuse
Let’s consider this command group:
Command command = Commands.sequence(
elevator.stow().asProxy(),
intake.intake(),
elevator.up());
Something bad happens if we try to run this. The entire command seems to never run, but why?
Let’s walk through the command logic to find out:
The entire sequence
commandhas the following requirements:No requirements bubble up from the proxy command.
The intake is required because of
intake.intake().The elevator is required because of
elevator.up().
So in total, the group has requirements on the intake and elevator.
The first command in the group - the proxy command - runs. This schedules the real command,
elevator.stow()separately from the group.elevator.stow()demands requirements on the elevator. Thus, any other command with requirements on the elevator is cancelled. This includes the groupcommand.Cancelling the group cancels the proxy command, which in turn cancels the real
elevator.stow()command.
Therefore, the proxied elevator.stow() just cancelled itself and the rest
of the group! This can happen wheneve a composition contains a command
requiring some subsystem S as well as a proxy for a command that also
requires S.