Page cover image

Essential Usage

This page covers all essential syntax that's necessary to use the State Factory library. As you read through the page, you can reference the full, basic class example at the bottom of this page.


State Factory organizes each state using enum constants. These are created by you to make it easier to organize code and relate each state to a name. Here's an example of what you might use:

enum LinearStates {
    IDLING,
    EXTENDING,
    DEPOSITING,
    RETRACTING,
    RELEASE
}

enum FallbackStates {
    PUSH_FALLBACK,
    DETECTION_FAIL
} 

It's recommended to take advantage of the mixing of enum classes to better organize your code. Note here how one enum class has states for linear sets of instructions while another has fallback states in case something goes wrong within the linear states. If there is a large amount of states and this organization is not necessary, you are able to create states using strings.

After defining the individual states, it's time to get into the actual state machine. Defining the StateMachine object goes like this:

StateMachine machine = new StateMachineBuilder()
    .build();

The actual code of the state machine goes between the object instantiation and the .build(). This .build() marks the end of the state machine.


States

Before putting everything together, it's important to get to know the methods that will be used. The first one is the simplest - the .state() method. This defines the state and connects it to the enum constant specified in the parameter. It is possible to replace this enum constant with a string, for example .state("IDLING") however, this is not recommended.

.state(LinearStates.IDLING) 

Next, you define your enter actions which will be executed when the state is entered. These are placed within a lambda function. Here's an example:

.onEnter( () -> System.out.println("Entering the state!") )

Similarly, there are exit actions that execute when a transition case becomes true. The exit command follows the same rules as the enter actions. For example:

.onExit( () -> System.out.println("Exiting the state!") )

To execute actions repeatedly during the loop, you can put them in the loop command. This could be used if you want to update a servo position so it is always updating when a value is changed in FTCDashboard. This follows the same rules as the previous actions, here is an example:

.loop( () -> servo.setPosition(DASH_CONSTANT)

To add multiple lines of code that should be executed within the lambda, use curly braces.

.onEnter( () -> {
    System.out.println("Entering the state");
    System.out.println("A second line");
})

Finally, the part that pulls it together: transitions. The most basic transitions go from one state to the next in linear order. This can be specified by indicating a boolean statement. For example:

// The transition case occurs when the slide position is greater than 700. 
// This will go to the state that is defined right after it.
.transition( () -> slides.getPosition() > 700)

In addition to this, there is also functionality for timed based transitions. With this transition, after the specified amount of time the current state will transition to the next. This timer will start as soon as the state is entered.

// will transition after 5.5 seconds
.transitionTimed(5.5)

To easily make a state that only has a specified timed transition, you can use the waitState(time) method. This creates a state that has a transitionTimed attached to it by default which allows it to transition from one state to the next in the specified time. Similar to any other state, you can add enter, exit, and loop actions to this state if preferred.

// will move to the next state after 3 seconds
.waitState(3)

For a more advanced look at transitions, check out the Advanced Usage page.

When building your state machine, the order of assembly does matter on some level. onEnter, onExit(), and transition() will all assign themselves to the last created state.

StateMachine machine = new StateMachineBuilder()
    .state(state_1) //creates a new state
    .onEnter(action) //assigns to state_1
    .transiton(condition) //assigns to state_1
    
    .state(state_2) //creates a second state
    .onExit(action) //assigns to state_2
    
    .build(); //builds the machine

However, the order the enter, exit, and transition assignments go in relation to each other does not matter.

StateMachine machine = new StateMachineBuilder()
    .state(state_1) //creates a new state
    .transiton(condition) //assigns to state_1
    .onEnter(action) //assigns to state_1
    
    .state(state_2) //creates a second state
    .onEnter(action) //assigns to state_2
    .transition(condition) //assigns to state_2
    
    .build(); //builds the machine

Running Your State Machines

Before putting it into the actual code, it is important to understand how to control the state machine externally. This includes starting, resetting, and stopping the state machine. This part is the simplest, but the most important for the state machine to actually work.

To start the state machine the command is:

machine.start(); // where machine is the StateMachine object

The machine.start() should be placed outside of the loop. This command executes the enter actions of the first state.

To reset the state machine, the command is:

machine.reset();

To stop the state machine, the command is:

machine.stop();

To update the machine, the command is:

machine.update();

The machine.update() command should be placed within the loop otherwise the state machine will not be able to update and transition.

To get the current state, the command is:

machine.getState();

The .getState() function will return the enum of the current state. However, if the current state is a string, then the .getState() function will throw an error. To avoid this, you can use the explicit get functions, .getStateEnum() and .getStateString().

To set the current state, which is not intended to be used often, the command is:

machine.setState(Enum/String);

Basic Example

Now for putting it all together:

SFTest.java
import com.sfdev.assembly.state.*;

// @Autonomous or @TeleOp
public class SFTest extends LinearOpMode() {
    enum States {
        FIRST,
        SECOND,
        THIRD
    }
    
    @Override
    public void runOpMode() throws InterruptedException {
    
        StateMachine machine = new StateMachineBuilder() 
            .state(States.FIRST)
            .onEnter( () -> {
                System.out.println( "Entering the first state" );
            })
            .transition( () ->  gamepad1.x ) // transition when gamepad1.x is clicked
            .onExit( () -> Sysetm.out.println("Exiting!") ) // setting check2 to false
            
            .state(States.SECOND)           
            .onEnter( () -> System.out.println( "Entering the second state" ) ) 
            .transition( () -> gamepad1.b) // if check2 is false transition
            
            .state(States.THIRD)
            .onEnter( () -> System.out.println( "In the third state " ) )
            .build();
            
        waitForStart();

        machine.start();
        
        while(opModeIsActive()) { // autonomous loop
            machine.update()
        }
    }
}

This is a sample program to provide you with an example of how the entire class can be put together. A more thorough example with Roadrunner can be seen on the Examples page. If you have more questions or have some recommendations that should be added, feel free to reach out to @.vvk. on Discord.

Last updated