/*
* Strongback
* Copyright 2015, Strongback and individual contributors by the @authors tag.
* See the COPYRIGHT.txt in the distribution for a full listing of individual
* contributors.
*
* Licensed under the MIT License; you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.strongback.command;
import java.util.Collection;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.BooleanSupplier;
import java.util.function.Supplier;
import org.strongback.Strongback;
import org.strongback.control.Controller;
import org.strongback.util.Collections;
/**
* The abstract base class for the Strongback command framework.
*
* @author Zach Anderson
* @see Requirable
* @see Strongback#submit(Command)
*/
public abstract class Command {
private final double timeout;
private final Set<Requirable> requirements;
private boolean interruptible = true;
/**
* Create a new command with the given timeout and zero or more Requirable components
*
* @param timeoutInNanoseconds how long in nanoseconds this command executes before terminating, zero is forever
* @param requirements the {@link Requirable}s this {@link Command} requires
*/
private Command(long timeoutInNanoseconds, Requirable... requirements) {
this.timeout = timeoutInNanoseconds / 1000000000.0;
this.requirements = Collections.immutableSet(requirements);
}
/**
* Create a new command with the given timeout and zero or more Requirable components
*
* @param timeoutInSeconds how long in seconds this command executes before terminating, zero is forever
* @param requirements the {@link Requirable}s this {@link Command} requires
*/
protected Command(double timeoutInSeconds, Requirable... requirements) {
this.timeout = timeoutInSeconds;
this.requirements = Collections.immutableSet(requirements);
}
/**
* Create a new command with the given timeout and zero or more Requirable components
*
* @param timeoutInNanoseconds how long in nanoseconds this command executes before terminating, zero is forever
* @param requirements the {@link Requirable}s this {@link Command} requires
*/
protected Command(long timeoutInNanoseconds, Collection<Requirable> requirements) {
this.timeout = timeoutInNanoseconds / 1000000000.0;
this.requirements = Collections.immutableSet(requirements);
}
/**
* Create a new command with the given timeout and zero or more Requirable components
*
* @param timeoutInSeconds how long in seconds this command executes before terminating, zero is forever
* @param requirements the {@link Requirable}s this {@link Command} requires
*/
protected Command(double timeoutInSeconds, Collection<Requirable> requirements) {
this.timeout = timeoutInSeconds;
this.requirements = Collections.immutableSet(requirements);
}
/**
* Create a new command with no timeout and zero or more Requirable components
*
* @param requirements the {@link Requirable}s this {@link Command} requires
*/
protected Command(Requirable... requirements) {
this(0, requirements);
}
/**
* Perform a one-time setup of this {@link Command} prior to any calls to {@link #execute()}. No physical hardware should be
* manipulated.
* <p>
* By default this method does nothing.
*/
public void initialize() {
}
/**
* Perform the primary logic of this command. This method will be called repeatedly after this {@link Command} is
* initialized until it returns {@code true}.
*
* @return {@code true} if this {@link Command} is complete; {@code false} otherwise
*/
public abstract boolean execute();
/**
* Signal that this command has been interrupted before {@link #initialize() initialization} or {@link #execute() execution}
* could successfully complete. A command is interrupted when the command is canceled, when the robot is shutdown while the
* command is still running, or when {@link #initialize()} or {@link #execute()} throw exceptions. Note that if this method
* is called, then {@link #end()} will not be called on the command.
* <p>
* By default this method does nothing.
*/
public void interrupted() {
}
/**
* Perform one-time clean up of the resources used by this command and typically putting the robot in a safe state. This
* method is always called after {@link #execute()} returns {@code true} unless {@link #interrupted()} is called.
* <p>
* By default this method does nothing.
*/
public void end() {
}
final Set<Requirable> getRequirements() {
return requirements;
}
final double getTimeoutInSeconds() {
return timeout;
}
/**
* Sets this {@link Command} to not be interrupted if another command with the same requirements is added to the scheduler.
* <p>
* By default the new command will cancel the old one; call this method if the new command will not interrupt the old one.
*/
protected final void setNotInterruptible() {
this.interruptible = false;
}
final boolean isInterruptible() {
return interruptible;
}
/**
* Create a command that uses the unmanaged {@link Controller} to move toward the specified target using controller's
* {@link Controller#getTolerance() tolerance}. The controller is <em>not</em> automatically and continuously measuring the
* input and computing and applying the correct output, and only does so when this command is executing. The given
* initializer will be called when the command is initialized and should be used to update the controller's target,
* tolerance, and other controller settings.
*
* @param controller the controller; may not be null
* @param initializer the function that runs when the command is started and configures the controller's
* {@link Controller#withTarget(double) target}, {@link Controller#withTolerance(double) tolerance}, and any other
* controller settings
* @return the command; never null
*/
public static Command use(Controller controller, Runnable initializer) {
return new UnmanagedControllerCommand(controller, initializer, controller);
}
/**
* Create a command that uses the unmanaged {@link Controller} to move toward the specified target using controller's
* {@link Controller#getTolerance() tolerance}. The controller is <em>not</em> automatically and continuously measuring the
* input and computing and applying the correct output, and only does so when this command is executing. The given
* initializer will be called when the command is initialized and should be used to update the controller's target,
* tolerance, and other controller settings.
*
* @param controller the controller; may not be null
* @param setpoint the desired value for the input to the controller
* @return the command; never null
*/
public static Command use(Controller controller, double setpoint) {
return new UnmanagedControllerCommand(controller, () -> controller.withTarget(setpoint), controller);
}
/**
* Create a command that uses the unmanaged {@link Controller} to move with the specified tolerance of the given target. The
* controller is <em>not</em> automatically and continuously measuring the input and computing and applying the correct
* output, and only does so when this command is executing. The given initializer will be called when the command is
* initialized and should be used to update the controller's target, tolerance, and other controller settings.
*
* @param controller the controller; may not be null
* @param setpoint the desired value for the input to the controller
* @param tolerance the absolute tolerance for how close the controller should come before completing the command
* @return the command; never null
*/
public static Command use(Controller controller, double setpoint, double tolerance) {
return new UnmanagedControllerCommand(controller, () -> controller.withTarget(setpoint).withTolerance(tolerance),
controller);
}
/**
* Create a command that uses the unmanaged {@link Controller} to move toward the controller's
* {@link Controller#withTarget(double) target} using the current tolerance, timing out if the command takes longer than
* {@code durationInSeconds}. The controller is <em>not</em> automatically and continuously measuring the input and
* computing and applying the correct output, and only does so when this command is executing. The given initializer will be
* called when the command is initialized and should be used to update the controller's target, tolerance, and other
* controller settings.
*
* @param durationInSeconds the maximum duration in seconds that the command should execute; must be non-negative, and 0.0
* equates to forever
* @param controller the controller; may not be null
* @param setpoint the desired value for the input to the controller
* @return the command; never null
*/
public static Command use(double durationInSeconds, Controller controller, double setpoint) {
return new UnmanagedControllerCommand(durationInSeconds, controller, () -> controller.withTarget(setpoint), controller);
}
/**
* Create a command that uses the unmanaged {@link Controller} to move toward the controller's
* {@link Controller#withTarget(double) target}, timing out if the command takes longer than {@code durationInSeconds}. The
* controller is <em>not</em> automatically and continuously measuring the input and computing and applying the correct
* output, and only does so when this command is executing. The given initializer will be called when the command is
* initialized and should be used to update the controller's target, tolerance, and other controller settings.
*
* @param durationInSeconds the maximum duration in seconds that the command should execute; must be non-negative, and 0.0
* equates to forever
* @param controller the controller; may not be null
* @param setpoint the desired value for the input to the controller
* @param tolerance the absolute tolerance for how close the controller should come before completing the command
* @return the command; never null
*/
public static Command use(double durationInSeconds, Controller controller, double setpoint, double tolerance) {
return new UnmanagedControllerCommand(durationInSeconds, controller,
() -> controller.withTarget(setpoint).withTolerance(tolerance), controller);
}
/**
* Create a command that uses the unmanaged {@link Controller} to move toward the specified target using controller's
* {@link Controller#getTolerance() tolerance}, timing out if the command takes longer than {@code durationInSeconds}. The
* controller is <em>not</em> automatically and continuously measuring the input and computing and applying the correct
* output, and only does so when this command is executing. The given initializer will be called when the command is
* initialized and should be used to update the controller's target, tolerance, and other controller settings.
*
* @param durationInSeconds the maximum duration in seconds that the command should execute; must be non-negative, and 0.0
* equates to forever
* @param controller the controller; may not be null
* @param initializer the function that runs when the command is started and configures the controller's
* {@link Controller#withTarget(double) target}, {@link Controller#withTolerance(double) tolerance}, and any other
* controller settings
* @return the command; never null
*/
public static Command use(double durationInSeconds, Controller controller, Runnable initializer) {
return new UnmanagedControllerCommand(durationInSeconds, controller, initializer, controller);
}
/**
* Create a command that reuses the automatically running {@link Controller} to move toward the specified target using
* controller's {@link Controller#getTolerance() tolerance}. The controller automatically and continuously measures the
* input and computing and applies the correct output, even when this command is not executing. The given initializer will
* be called when the command is initialized and should be used to update the controller's target, tolerance, and other
* controller settings.
*
* @param controller the controller; may not be null
* @param initializer the function that runs when the command is started and configures the controller's
* {@link Controller#withTarget(double) target}, {@link Controller#withTolerance(double) tolerance}, and any other
* controller settings
* @return the command; never null
*/
public static Command reuse(Controller controller, Runnable initializer) {
return new ControllerCommand(controller, initializer, controller);
}
/**
* Create a command that reuses the automatically running {@link Controller} to move toward the specified target using
* controller's {@link Controller#getTolerance() tolerance}. The controller automatically and continuously measures the
* input and computing and applies the correct output, even when this command is not executing. The given initializer will
* be called when the command is initialized and should be used to update the controller's target, tolerance, and other
* controller settings.
*
* @param controller the controller; may not be null
* @param setpoint the desired value for the input to the controller
* @return the command; never null
*/
public static Command reuse(Controller controller, double setpoint) {
return new ControllerCommand(controller, () -> controller.withTarget(setpoint), controller);
}
/**
* Create a command that uses the automatically running {@link Controller} to move with the specified tolerance of the given
* target. The controller automatically and continuously measures the input and computing and applies the correct output,
* even when this command is not executing. The given initializer will be called when the command is initialized and should
* be used to update the controller's target, tolerance, and other controller settings.
*
* @param controller the controller; may not be null
* @param setpoint the desired value for the input to the controller
* @param tolerance the absolute tolerance for how close the controller should come before completing the command
* @return the command; never null
*/
public static Command reuse(Controller controller, double setpoint, double tolerance) {
return new ControllerCommand(controller, () -> controller.withTarget(setpoint).withTolerance(tolerance), controller);
}
/**
* Create a command that uses the automatically running {@link Controller} to move toward the controller's
* {@link Controller#withTarget(double) target} using the current tolerance, timing out if the command takes longer than
* {@code durationInSeconds}. The controller automatically and continuously measures the input and computing and applies the
* correct output, even when this command is not executing. The given initializer will be called when the command is
* initialized and should be used to update the controller's target, tolerance, and other controller settings.
*
* @param durationInSeconds the maximum duration in seconds that the command should execute; must be non-negative, and 0.0
* equates to forever
* @param controller the controller; may not be null
* @param setpoint the desired value for the input to the controller
* @return the command; never null
*/
public static Command reuse(double durationInSeconds, Controller controller, double setpoint) {
return new ControllerCommand(durationInSeconds, controller, () -> controller.withTarget(setpoint), controller);
}
/**
* Create a command that uses the automatically running {@link Controller} to move toward the controller's
* {@link Controller#withTarget(double) target}, timing out if the command takes longer than {@code durationInSeconds}. The
* controller automatically and continuously measures the input and computing and applies the correct output, even when this
* command is not executing. The given initializer will be called when the command is initialized and should be used to
* update the controller's target, tolerance, and other controller settings.
*
* @param durationInSeconds the maximum duration in seconds that the command should execute; must be non-negative, and 0.0
* equates to forever
* @param controller the controller; may not be null
* @param setpoint the desired value for the input to the controller
* @param tolerance the absolute tolerance for how close the controller should come before completing the command
* @return the command; never null
*/
public static Command reuse(double durationInSeconds, Controller controller, double setpoint, double tolerance) {
return new ControllerCommand(durationInSeconds, controller,
() -> controller.withTarget(setpoint).withTolerance(tolerance), controller);
}
/**
* Create a command that uses the automatically running {@link Controller} to move toward the specified target using
* controller's {@link Controller#getTolerance() tolerance}, timing out if the command takes longer than
* {@code durationInSeconds}. The controller automatically and continuously measures the input and computing and applies the
* correct output, even when this command is not executing. The given initializer will be called when the command is
* initialized and should be used to update the controller's target, tolerance, and other controller settings.
*
* @param durationInSeconds the maximum duration in seconds that the command should execute; must be non-negative, and 0.0
* equates to forever
* @param controller the controller; may not be null
* @param initializer the function that runs when the command is started and configures the controller's
* {@link Controller#withTarget(double) target}, {@link Controller#withTolerance(double) tolerance}, and any other
* controller settings
* @return the command; never null
*/
public static Command reuse(double durationInSeconds, Controller controller, Runnable initializer) {
return new ControllerCommand(durationInSeconds, controller, initializer, controller);
}
/**
* Create a new command object that will cancel all currently-running commands that require the supplied {@link Requirable}
* objects. When this command is {@link Strongback#submit(Command) submitted}, it will preempt any running (or scheduled)
* command that also requires any of the supplied {@link Requirable}s.
*
* @param requirables the {@link Requirable}s for which any currently-running commands should be cancelled.
* @return the new command; never null
*/
public static Command cancel(Requirable... requirables) {
return create(0.0, () -> true, () -> "Cancel (requires " + requirables + ")", requirables);
}
/**
* Create a new command object that does nothing but pause for the specified time. The resulting command will have no
* {@link Requirable}s.
*
* @param pauseTime the time
* @param unit the time unit
* @return the new command; never null
*/
public static Command pause(long pauseTime, TimeUnit unit) {
double durationInSeconds = (double) unit.toNanos(pauseTime) / (double) TimeUnit.SECONDS.toNanos(1);
return create(durationInSeconds, () -> false, () -> "PauseCommand (" + unit.toMillis(pauseTime) + " milliseconds)");
}
/**
* Create a new command object that does nothing but pause for the specified time. The resulting command will have no
* {@link Requirable}s.
*
* @param pauseTimeInSeconds the time in seconds
* @return the new command; never null
*/
public static Command pause(double pauseTimeInSeconds) {
return create(pauseTimeInSeconds, () -> false, () -> "PauseCommand (" + pauseTimeInSeconds + " sec)");
}
/**
* Create a new command that runs once by executing the supplied function. The resulting command will have no
* {@link Requirable}s.
*
* @param executeFunction the function to be called during execution; may not be null
* @return the new command; never null
*/
public static Command create(Runnable executeFunction) {
return create(0.0, () -> {
executeFunction.run();
return true;
} , () -> "Command (one-time) " + executeFunction);
}
/**
* Create a new command that runs once by executing the supplied function. The resulting command will have no
* {@link Requirable}s.
*
* @param durationInSeconds the maximum duration in seconds that the command should execute; must be positive
* @param executeFunction the function to be called during execution; may not be null
* @return the new command; never null
*/
public static Command create(double durationInSeconds, Runnable executeFunction) {
return create(durationInSeconds, executeFunction, null);
}
/**
* Create a new command that runs once by executing the supplied function, then wait the prescribed amount of time, and then
* call the supplied {@code endFunction}. The resulting command will have no {@link Requirable}s.
*
* @param durationInSeconds the maximum duration in seconds that the command should execute; must be positive
* @param executeFunction the function to be called during execution; may not be null
* @param endFunction the function to be called when the command terminates; may be null
* @return the new command; never null
*/
public static Command create(double durationInSeconds, Runnable executeFunction, Runnable endFunction) {
return new Command(durationInSeconds) {
boolean completed = false;
@Override
public boolean execute() {
if (!completed) {
executeFunction.run();
completed = true;
}
return true;
}
@Override
public void end() {
if (endFunction != null) endFunction.run();
}
@Override
public String toString() {
return "one-time, duration=" + durationInSeconds + " sec) " + executeFunction;
}
};
}
/**
* Create a new command that runs one or more times by executing the supplied function. The resulting command will have no
* {@link Requirable}s.
*
* @param executeFunction the function to be called during execution; may not be null
* @return the new command; never null
*/
public static Command create(BooleanSupplier executeFunction) {
return create(0.0, executeFunction, null, () -> "Command " + executeFunction);
}
/**
* Create a new command that runs one or more times by executing the supplied function. When the command terminates, the
* {@code endFunction} will be called. The resulting command will have no {@link Requirable}s.
*
* @param executeFunction the function to be called during execution; may not be null
* @param endFunction the function to be called when the command terminates; may be null
* @return the new command; never null
*/
public static Command create(BooleanSupplier executeFunction, Runnable endFunction) {
return create(0.0, executeFunction, endFunction, () -> "Command " + executeFunction);
}
/**
* Create a new command that runs one or more times by executing the supplied function, but that will timeout if taking
* longer than the specified timeout. The resulting command will have no {@link Requirable}s.
*
* @param timeoutInSeconds the time in seconds
* @param executeFunction the function to be called during execution; may not be null
* @return the new command; never null
*/
public static Command create(double timeoutInSeconds, BooleanSupplier executeFunction) {
return create(timeoutInSeconds,
executeFunction,
() -> "Command (timeout=" + timeoutInSeconds + " sec,repeatable) " + executeFunction);
}
/**
* Create a new command that runs one or more times by executing the supplied function, but that will timeout if taking
* longer than the specified timeout. When the command terminates, the {@code endFunction} will be called. The resulting
* command will have no {@link Requirable}s.
*
* @param timeoutInSeconds the time in seconds
* @param executeFunction the function to be called during execution; may not be null
* @param endFunction the function to be called when the command terminates; may be null
* @return the new command; never null
*/
public static Command create(double timeoutInSeconds, BooleanSupplier executeFunction, Runnable endFunction) {
return create(timeoutInSeconds,
executeFunction,
endFunction,
() -> "Command (timeout=" + timeoutInSeconds + " sec,repeatable) " + executeFunction);
}
/**
* Create a new command that runs one or more times by executing the supplied function, but that will timeout if taking
* longer than the specified timeout. When the command terminates, the {@code endFunction} will be called. The resulting
* command will have no {@link Requirable}s.
*
* @param timeoutInSeconds the time in seconds
* @param executeFunction the function to be called during execution; may not be null
* @param toString the function to be called when {@link Command#toString()} is called; may not be null
* @param requirements the {@link Requirable}s for the command
* @return the new command; never null
*/
protected static Command create(double timeoutInSeconds, BooleanSupplier executeFunction, Supplier<String> toString,
Requirable... requirements) {
return create(timeoutInSeconds, executeFunction, null, toString, requirements);
}
/**
* Create a new command that runs one or more times by executing the supplied function, but that will timeout if taking
* longer than the specified timeout. When the command terminates, the {@code endFunction} will be called. The resulting
* command will have no {@link Requirable}s.
*
* @param timeoutInSeconds the time in seconds
* @param executeFunction the function to be called during execution; may not be null
* @param endFunction the function to be called when the command terminates; may be null
* @param toString the function to be called when {@link Command#toString()} is called; may not be null
* @param requirements the {@link Requirable}s for the command
* @return the new command; never null
*/
protected static Command create(double timeoutInSeconds, BooleanSupplier executeFunction, Runnable endFunction,
Supplier<String> toString, Requirable... requirements) {
return new Command(timeoutInSeconds, requirements) {
@Override
public boolean execute() {
return executeFunction.getAsBoolean();
}
@Override
public void end() {
if (endFunction != null) endFunction.run();
}
@Override
public String toString() {
return toString.get();
}
};
}
}