Using Commands

The previous section showed how your robot code can do more by using commands, and how this makes the code simpler, testable, and makes the logic in the commands (re)usable in both autonomous and teleoperated modes. This section goes step by step into how you use the command framework.

Creating Command subclasses

We saw in the earlier example how easy it is to create custom commands by writing a subclass for each command. Most of the time, each command subclass will need a constructor and an execute():boolean method. Here’s an example we saw earlier:

public class OpenClaw extends Command {
    private final Solenoid solenoid;
    public OpenClaw( Solenoid solenoid ) {
        super(solenoid);
        this.solenoid = solenoid;
    }
    @Override
    public boolean execute() {
        this.solenoid.retract();
        return true;
    }
}

Let’s look at this class in a bit more detail. Why does this class require the Solenoid object to be passed into the constructor? Couldn’t the command just get the Solenoid object from our robot’s top-level class? It could do that, but passing in the Solenoid makes this command testable. For example, here’s part of a sample unit test (using JUnit and Fest assertions):

public class OpenClawTest {
    @Test
    public void shouldRetractSolenoidWhenSolenoidIsInitiallyExtended() {
        MockSolenoid solenoid = Mock.instantaneousSolenoid().extend();
        OpenClaw command = new OpenClaw(solenoid);
        assertThat(command.execute()).isTrue();
        assertThat(solenoid.isRetracted()).isTrue();
        assertThat(solenoid.isExtended()).isFalse();
    }
    @Test
    public void shouldRetractSolenoidWhenSolenoidIsAlreadyRetracted() {
        MockSolenoid solenoid = Mock.instantaneousSolenoid().retract();
        OpenClaw command = new OpenClaw(solenoid);
        assertThat(command.execute()).isTrue();
        assertThat(solenoid.isRetracted()).isTrue();
        assertThat(solenoid.isExtended()).isFalse();
    }
}

Second, our OpenClaw class is actually immutable, which means it has no state that changes as it goes through its lifecycle. This simplifies our code, and interestingly means you can submit the same OpenClaw instance multiple times (if that turns out to be an advantage for you).

What our OpenClaw class does not do is override other methods. One method that your subclasses can override is the initialize() method, which is called by the scheduler exactly one time right before it calls execute() the first time. The initialize() method is a great place to do any one-time setup logic (far better than having an if-block in your execute() method). By default, initialize() does nothing.

Another method you can override is the end() method, which is always called after execute() returns true. However, end() is not called if the command is interrupted.

The last method you can override is the interrupted() method, which is a signal to you that the command was interrupted before initialize() was called or before execute() could return true. A command might be interrupted because the command was canceled, because the robot was shutdown while the command was running, or because initialize() or execute() threw an exception. Note that if interrupted() is called, then end() will not be called on the command.

One thing you may note is that Command has no methods that make it run. You can create as many instances of a command as you want, and none of them will do anything until you submit the command objects, which we’ll cover in a later section.

So, writing Command subclasses is very easy and straightforward, and they’re very easy to test. But you don’t always need to write a subclass for a custom command.

Using CommandGroup

A command group is a special type of Command that is composed of a number of other Command objects executed in sequence and/or in parallel. Generally, you define a command group by writing a class that extends org.strongback.command.CommandGroup, and specifying in the subclass' constructor which commands should be executed sequential or simultaneously. The commands passed into these methods can be any Command subclass, CommandGroup subclass, or group of sequential, simultaneous, or forked commands.

Commands executed one after the other are sequential, and are defined within the constructor with a call to the sequentially(…​) method that takes an ordered array of Command instances. The order of the Command objects is important: when the CommandGroup is executed, it will run the first Command object until it completes, and only then does it run the second Command object until it completes, and so on. The whole CommandGroup finishes only after the last Command is completed. Here’s a simple sequential command group that when executed, first executes an instance of CommandA, then an instance of CommandB, and finally an instance of CommandC:

public class MySequentialCommand extends CommandGroup {
    public MySequentialCommand() {
        sequentially(new CommandA(),
                     new CommandB(),
                     new CommandC());
    }
}

Commands executed at the same time (in parallel) are simultaneous, and are defined within the constructor with a call to the simultaneously(…​) method that takes an array of Command instances. Order is not important, since they are all are executed concurrently. The whole CommandGroup finishes only when all Command objects have completed. Here’s a simple simultaneous command group that when executed, executes in parallel an instance of CommandA and an instance of CommandB:

public class MySimultaneousCommand extends CommandGroup {
    public MySimultaneousCommand() {
        simultaneously(new CommandA(),
                       new CommandB());
    }
}

You can also create a CommandGroup that is a combination of sequential and simultaneous commands. The following code shows a complete CommandGroup subclass that, when executed, executes at the same time two commands (an instance of CommandA and an instance of CommandB) and after both are finished a third command object of type CommandC:

public class MyMixedCommands extends CommandGroup {
    public MyMixedCommands() {
        sequentially(
            simultaneously(new CommandA(),
                           new CommandB()),
            new CommandC());
    }
}

The MyMixedCommands will complete when the last of the three commands (that is, the command of type CommandC) completes.

It is possible for a CommandGroup to run one of its commands completely independently of the command group. This is called forking, and is defined within the constructor with a call to the fork(…​) method that takes a single Command, CommandGroup, or the result of sequentially(…​) or simultaneously(…​). Here is a more complex CommandGroup subclass that, when executed, first executes CommandA, then executes CommandB, then forks off CommandC and immediately executes CommandD, and finally executes both CommandE and CommandF in parallel:

public class MyForkCommands extends CommandGroup {
    public MyForkCommands() {
        sequentially(new CommandA(),
                     new CommandB(),
                     fork(new Command C()),
                     new CommandD());
                     new simultaneously( new CommandE(), new CommandF()));
    }
}

The MyForkCommands will complete when the last of the two final commands, either CommandE or CommandF, completes. Note that this is true even if CommandC is still running.

When creating command groups, you’ll often want to pause a fixed amount of time between sequential commands. This is very easy to do with the Command.pause(…​) method, which creates a command that is completely only after the time delay has elapsed. Here’s an example that, when executed, first executes an instance of CommandA, then an instance of CommandB, then pauses for 3 seconds, and finally an instance of CommandC:

public class MySequentialCommand extends CommandGroup {
    public MySequentialCommand() {
        sequentially(new CommandA(),
                     new CommandB(),
                     Command.pause(3,TimeUnit.SECONDS)
                     new CommandC());
    }
}

Creating commands and command groups without subclasses

When we were designing the Strongback command framework, we wanted to make it as easy as possible to create commands and command subclasses. Of course, the easiest way to do this is to offer ways of creating custom commands without having to write a subclass. Strongback provides several ways to create commands this way.

The easiest is to create a command that runs a lambda only once:

Command oneTimeCommand = Command.create(()->{
   // do something
});

Note that the lambda doesn’t return anything, making it equivalent to the Runnable.run() method.

You can also create a command that runs a function multiple times until a specific amount of time has elapsed. In this case, just specify the duration in seconds as a first parameter:

Command fixedDurationCommand = Command.create(1.5, ()->{
   // do something
});

If your lambda does return a boolean value that states whether the command is completed, then Strongback will repeatedly call the lambda until it returns true:

Command untileCompleteCommand = Command.create(()->{
   // do something, and determine whether it is complete ...
   boolean complete = ...
   return complete;
});

And of course it’s very easy to create command that runs lambda until either it completes or until a maximum amount of time has elapsed is also very easy:

double maxDurationInSeconds = 3.0;
Command untileCompleteCommand = Command.create(maxDurationInSeconds, ()->{
   // do something, and determine whether it is complete ...
   boolean complete = ...
   return complete;
});

You can even create a command that cancels any currently-running command with the same requirements. This is a great way to implement a kill switch for a particular system. For example, maybe we’d like to have a button stop our arm from moving, wherever it is. Remember our RobotArm class we used earlier? We could easily add a method to that to stop the arm from swiveling by creating a new command that requires the same Requirable swivel motor:

public class RobotArm {
    ...
    public Command stopSwivelNow() {
        return Command.cancel(swivel);
    }
    ...
}

Of course, that method only creates the command. To run it, we have to submit it, and we’ll learn more about that in a later section.

Finally, you can create command groups without writing a subclass using the static factory methods on CommandGroup. This is not quite as flexible for more complex groups, but for some simple groups it can be less code. Here’s a simple sequential command group that when executed, first executes an instance of CommandA, then an instance of CommandB, and finally an instance of CommandC:

Command group = CommandGroup.sequentially(new CommandA(),
                                          new CommandB(),
                                          new CommandC());

and another example that creates a simultaneous command group that when executed, executes in parallel an instance of CommandA and an instance of CommandB:

Command group = CommandGroup.simultaneously(new CommandA(),
                                            new CommandB());

As you can see, these make simple command groups very concise and easy to read. Unfortunately, more complicated command groups are probably more easily coded using an explicit subclass.

Submitting Commands

Creating an instance of a Command does not cause it to run. To do that, you have to submit the command to a scheduler. The scheduler:

  • keeps track of all submitted and currently-executing commands and command groups;

  • cancels any currently-executing commands that have the same requirements as a newly-submitted command; and

  • execute the current commands until they complete (or are canceled).

The Strongback class has its own static instance of a scheduler, and it manages it for you and ensures the scheduler operates at a very consistent frequency. By default, Strongback runs the scheduler every 5 milliseconds, although you can easily configure Strongback to use a different period. To use it, simply use the Strongback.submit(Command) static method we used in our earlier example.

Note
How does Strongback ensure the scheduler operates at a consistent frequency? The built-in scheduler is run using Strongback’s executor, which runs all asynchronous operations using a single dedicated thread that very carefully monitors execution intervals (without using Thread.sleep() or scheduled threads!). Since the RoboRIO has a dual-core processor, it can run two threads without having to manage them or change thread contexts. Your main robot code runs on one thread, and Strongback’s executor runs a second for all of Strongback’s asynchronous operations. Strongback can also use a clock that relies upon the RoboRIO’s FPGA hardware chip for more accuracy.

While most teams will find it very easy and convenient to use the built-in scheduler, a few more advanced teams may want more control over how and what executes the scheduler. If this describes you, then you can create and use your own instance of the org.strongback.command.Scheduler. Of course, you’ll have to make sure to periodically call its execute(long) method when you want to execute the currently-submitted commands.

Gotchas

There are a few things to be concerned about when using commands. The first is that you must be careful not to give the scheduler too much work at the same time. Recall that the scheduler uses a separate thread to periodically execute all currently-running commands, and all commands must execute within that period. For example, if the scheduler runs every 5 milliseconds, then the total time for all of the commands' execute() calls must be less than 5 milliseconds; if not, then the scheduler will not be able to run every 5 milliseconds.

Should this happen, Strongback increments an internal counter that you can access via Strongback.excessiveExecutionTimeCounts(), and it by default logs an error message. So if you see this error message beginning with “Unable to execute all activities within …​ milliseconds”, then either increase the period or reduce the amount of work being concurrently executed.

Tip
If you want more control when the execution time exceeds the configured period, when you configure Strongback you can provide custom logic. Strongback will then call this function (rather than log an error message) every time the execution time is excessive.

In general, though, this won’t be a problem when commands' execute() methods run very quickly.

results matching ""

    No results matching ""