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;
}
}
Comparison with WPILib’s Commands
Strongback’s command framework is very similar to WPILib command-based framework. Indeed, if you’ve used the WPILib commands in the past, you probably find Strongback’s framework to be easy to grasp — and probably simpler to use.
But while Strongback’s command framework might be similar to that in WPILib, there are definitely some important differences.
Command classes
Both frameworks' concept of a command are very similar. The differences are in the details of how they’re implemented and what this means for your FRC team.
WPILib Commands
When you use WPILib commands, you write a class for each command, and this class extends the edu.wpi.first.wpilibj.command.Command
abstract base class. WPILib’s design requires your class do several things:
-
Provide a constructor that calls the superclass' constructor and that calls
doesRequire(…)
relay requiredSubsystem
objects. -
Provide an
initialize()
method that the framework will call prior toexecute()
, and the method can perform any one-time initialization logic. -
Provide an
execute()
method that contains the logic of your command. The method doesn’t return anything, and may be called multiple times depending upon theisFinished()
method. -
Provide an
isFinished()
method that tells the framework when the command is complete. -
Provide an
end()
method that the framework will call when the command successfully finished, allowing your command to respond if needed. -
Provide an
interrupted()
method that the framework will call to let your command know it’s been interrupted.
Additionally, the edu.wpi.first.wpilibj.command.Command
base class has a few methods you are expected to use (e.g., doesRequire(…)
) and numerous methods that you can (but shouldn’t) override or call. Most of those methods deal with the command’s lifecycle and state required by WPILib to properly run the command. It even has methods to start()
and cancel()
the command.
The last two methods make each command know how to execute itself, and they are a big reason why it is very difficult to unit test your command subclasses. The edu.wpi.first.wpilibj.command.Command
is so chocked full of things that your tests simply cannot instantiate them withouth staring a good portion of the WPILib system, including those that ultimately require hardware.
Strongback Commands
When we designed Strongback, we took what we learned about using and trying to test WPILib commands and thought we could do better. We wanted to make it easy as possible to write subclasses and, where possible in some simple cases, to not even require custom subclasses. To write a custom command class with Strongback, you must:
-
Provide a constructor that calls the superclass' constructor with requirements and timeout information (if there are any), and optionally call
setNotInterruptible()
if desired (all commands are interruptible by default). -
Provide an
execute()
method that contains the logic of your command and that returns whether the command is completed.
That’s it! Of course, if you want you can override any of the default methods:
-
Optionally override the
initialize()
method that the framework will call prior toexecute()
, and the method can perform any one-time initialization logic. The default method does nothing, so you only have to override this if you want different behavior. -
Optionally override the
end()
method that the framework will call when the command successfully finished, allowing your command to respond if needed. The default method does nothing, so you only have to override this if you want different behavior. -
Optionally override the
interrupted()
method that the framework will call to let your command know it’s been interrupted. The default method does nothing, so you only have to override this if you want different behavior.
The Strongback Command
base class has very few methods, no lifecycle state, and does not know how to start or run. Instead, your robot code is responsible for submitting the command for execution, typically via Strongback.submit(Command)
.
This design makes it very easy to write and test custom command subclasses. For example, here is a complete Command
subclass (minus the import statements and JavaDoc):
Strongback was designed to use Java 8 features, so you might not even need to write a custom command subclass for all of your commands. If they’re simple enough, you might be able to just pass a lambda expression to one of the Command
class' static methods. For example, here is a simpler (albeit more anonymous and less reusable) way to define our previous OpenClaw
command:
Solenoid solenoid = ...
Command openClaw = Command.create(()->solenoid.retract());
Other Command
class' static methods are available for creating commands that cancel, that pause for a fixed amount of time, or than run lambdas once or multiple times and possibly for a maximum duration.
Requirements
Both frameworks have similar notions of a command requiring some other object(s). And in both frameworks when a command is run, the framework will cancel any previously-started command that required the same object(s).
WPILib Subsystem
With WPILib, commands could state they require one or more edu.wpi.first.wpilibj.command.Subsystem
objects. Examples of a subsystem might be a drive train, a claw, or motors on an arm. The WPILib documentation states that all motors should be a part of a subsystem, so you need to write one Subsystem
subclass for each of your logical subsystems.
Unfortunately the Subsystem
class is not terribly small. It actually has all the state about which command is currently being executed, and it includes methods for communicating with the Network Tables.
Despite this, it’s not terribly difficult to write or use Subsystem
subclasses. Testing them is more challenging, but that’s mostly because the motors that subsystems are to contain require hardware, and thus aren’t suitable for testing off-robot with simple unit tests.
Strongback 'Requirable'
Strongback, on the other hand, uses a far simpler notion of required objects: ß
, here are a couple of ways to create commands you can
The easiest way to do that is to offer methods that you can sometimes use you can use so that you sometimes s ways where you don’t have to actually
-
Creating command classes - We tried to make it as easy as possible to create subclasses. Where WPILib has separate methods for
execute():void
andisFinished():boolean
, Strongback instead combines them into a singleexecute():boolean
method. Strongback also provides static methods that create commands that pause, commands that run a function one time, commands that cancel any running commands with the same requirements, commands that run a lambda, and commands that execute a function for a maximum period of time. -
Requirements - Whereas Strongback commands can require any object that implements the
Requirable
marker interface, WPILib commands can only depend uponedu.wpi.first.wpilibj.command.Subsystem
subclasses that are heavyweight. Strongback behaves the same was as WPILib, but all logic is embedded in classes you don’t have to extend or subclass. -
Running - WPILib ryou call
run()
on the command, but ghis makes it too difficult
for common activities, like pausing a
, and to also be able to The W`edu.wpi.first.wpilibj.command.Command
class requires your subclass override a number of methods for most commands, whereas with Strongback we tried to
Strongback Class | WPILib Class | Notes |
---|---|---|
org.strongback.command.Command |
edu.wpi.first.wpilibj.command.Command |
|
22-Aug-08 |
10:24 |
|
157 |
Worked out MSHR (max sustainable heart rate) by going hard for this interval. |
22-Aug-08 |
23:03 |
152 |
Back-to-back with previous interval. |
24-Aug-08 |
40:00 |
145 |
If you’ve not used either, we think you’ll find it pretty eas But we found the WPILib version to be difficult to use and very difficult to test. Command
and CommandGroup