Skip to content

Actions

Tom Tzook edited this page Aug 26, 2020 · 11 revisions

Actions are tasks executed by FlashLib's Scheduler. These tasks are more than a simple run, they are executed by specific steps, and can be considered as a complex task.

Using Actions, we can instruct the robot and its system to perform complex tasks of various types. This makes Actions very crucial for robot control.

Execution Flow

Actions are built on a specific execution flow, which is provided by the Scheduler:

Execution Flow

Once started, the task enters the initialization step, which allows users to initialize data or reset data holders in preparation for running.

Next, the task enters the execution step. During this step, users should perform the main task logic. This step continues until the task is marked to be stop.

The action can be marked to stop in several ways:

  • The Action reports that it has finished.
  • An outside user calls cancel
  • The Scheduler interrupted the Action.

If the action stopped because it reported a finish, the flow enters the end phase. Otherwise it enters the interruption phase.

Implementation

An Action implementation is a class which implements Action interface. Although it's actually recommended to extend the ActionBase instead.

When implementing an Action, users need to implement what the action should be doing at each phase of it's execution flow.

This is done by implementing the following methods:

  • void initialize(): (optional) initialization step.
  • void execute(): (required) execution step.
  • boolean isFinished(): (optional) reports when the Action is finished. Should return true when it is, false otherwise.
  • void end(boolean wasInterrupted): (optional) during the ending/interruption steps. If in interruption step, wasInterrupted would be true. If in ending step, wasInterrupted would be false.

An empty Action class, would look as such:

public class SomeAction extends ActionBase {

    @Override
    public void initialize() {

    }

    @Override
    public void execute() {

    }

    @Override
    public boolean isFinished() {
        return false;
    }

    @Override
    public void end(boolean wasInterrupted) {

    }
}

The following example RotateAction is an Action which rotates a Rotatable at a constant speed forever (until canceled or interrupted).

We would need two instance variables: the Rotatable and the wanted speed. Then we would need to implement the execution behavior.

public class RotateAction extends ActionBase {

    private final Rotatable mRotatable;
    private final double mSpeed;

    public RotateAction(Rotatable rotatable, double speed) {
        mRotatable = rotatable;
        mSpeed = speed;
    }

    @Override
    public void initialize() {
        // nothing to do here, so leave empty.
    }

    @Override
    public void execute() {
        // here we actually rotate the rotatable
        mRotatable.rotate(mSpeed);
    }

    @Override
    public boolean isFinished() {
        // we want the action to run forever (until interrupted/canceled) so we will just return false.
        return false;
    }

    @Override
    public void end(boolean wasInterrupted) {
        // when finished, it's best to just stop the rotatable
        mRotatable.stop();
    }
}

Pretty straight-forward. This is actually pretty close to how RotateAction from FlashLib is actually implemented.

Properties

An Action has several properties, which enhance its capability.

Requirements

An Action has requirements. These are robot Subsystems which the Action aims to use. The Scheduler ensures only one running Action uses a certain Subsystem at any given time.

Timeout

An Action can define a run timeout, which defines the maximum amount of time the Action should run. Once the timeout was reached, the Action will be stopped.

Execution Flow

An Action has several execution steps, which define the flow of the task. These steps allow defining a more complex behavior for tasks, to handle different situations, and points in the execution cycle of the task.

The following is the flow of an Action: Execution Flow

Once started, the task enters the initialization step, which allows users to initialize data or reset data holders. Next, the task enters the execution step. During this step, users should perform the main task logic. This step continues until the task is marked to be stop.

Finishing steps

One the task is marked for finishing, the task can either enter end, or interruption steps.

The end step is entered if the task reports to have finished.

Interruption step is entered in one of several condition:

  • The task was canceled by the user
  • The task was canceled by the scheduler due to requirement conflict
  • The task has timed out

Code

Actions are represented by the Action class, which is abstract class. Users are required to implement the contents of each execution step:

  • initialize: code for the initialization step. Default implementation: does nothing
  • execute: code for the execution step
  • end: code for ordered stop of the action
  • interrupted: code for interruption stop. Default implementation: calls end.
  • isFinished: indicates whether or not the action should stop running. Default implementation: returns false, which indicates that action should not stop.

Steps

During the initialization phase, the method initialize is called once. This is done each time the action is started. During the execution phase, the method execute is called periodically (the interval depends on the Scheduler, but should largely be around few milliseconds). During the end step, end is called once. During the interruption step, interrupted is called once.

Finishing Condition

An Action can report itself has finished, by returning true from the isFinished method.

Extending an Action

To create an Action you will need to extend it, and implement the step methods:

public class SomeAction extends Action {
    @Override
    protected void execute() {
    }

    @Override
    protected void end() {
    }
}

execute and end are the only step methods which don't have a default implementation, and must be implemented. The rest can be ignored, but when needed, can be implemented.

Properties

The Action class contains methods for defining its properties. Setting the properties should be done once the Action is created, and cannot be done while it is running.

  • To set the requirements of an Action, use the requires method. Each call to this method will add a new requirement and not override requirements already defined. Call to resetRequirements to clear defined requirements.
    Action action = new SomeAction();
    action.requires(subsystem);
    public class SomeAction extends Action {
        ...
        public SomeAction(Subsystem subsystem) {
            requires(subsystem);
        }
        ...
    }
  • To set run timeout, use setTimeout method, which receives Time from FlashLib's Time API. You may also call cancelTimeout to remove the defined timeout.
    Action action = new SomeAction();
    action.setTimeout(Time.milliseconds(timeoutInMilliseconds));
    public class SomeAction extends Action {
        ...
        public SomeAction() {
            setTimeout(Time.milliseconds(timeoutInMilliseconds));
        }
        ...
    }

Running an Action

To start and action use the start method. To interrupt a running action, call cancel method. The isRunning method indicates whether or not the Action is running.

public class Robot extends IterativeRobot {
    ...
    @Override
    protected void modeInit(RobotMode mode) {
        new SomeAction().start();
    }
}

Action Groups

Action Groups are a specific implementation of Action which executes a group of 1 or more actions in a specific order. There are 2:

  • ParallelActionGroup for executing multiple actions concurrently. One should not that whether the execution is truly parallel or simply concurrent depends on the implementation on the Scheduler. In addition, it is important to be aware that multiple actions do not use the same subsystems.
  • SequentialActionGroup for executing actions in a sequential order, only starting the next, once the previous has finished.

Usage is quite simple. One must construct the wanted class, and define the wanted actions:

Action group = new ParallelActionGroup()
    .add(action1, action2);
group.start();

Action group = Actions.parallel(action1, action2);

Actions and Robot Modes

When the robot mode changes, all running actions will be interrupted.