State Factory
  • State Factory
  • Installation
  • Essential Usage
  • Advanced Usage
  • Examples
  • Additional Information
    • Code Organization
Powered by GitBook
On this page
  • Multiple Transitions
  • Advanced Transitions
  • Nested State Machines
  • Template States

Advanced Usage

The purpose creating state factory was to make complex tasks easier. This is where those complex tasks can be created

The more advanced tools in State Factory are centered around transitions. Especially within the 2022-2023 POWERPLAY season, there was an advantage to having an adaptable and non-linear autonomous to dominate the autonomous period. In addition to this, having a complex and thoroughly documented autonomous looks very nice for a control award submission.


Multiple Transitions

Each state is able to have multiple transition methods, allowing for multiple checks and multiple potential output states and/or actions. These transitions follow a sequential order meaning that the transitions at the top of the list have the highest priority. Look at the following code as an example.

.state(States.DIVERGE)
.transition( () -> robot.isCloseEnough(), States.CLOSE )
.transition( () -> robot.isFarEnough(), States.FAR, () -> System.out.println("Hm.") )

This code segment has two transitions where each has a different case to transition on. If both of these cases are somehow met at the same time, the first case would execute first and move to the indicated, in this case States.CLOSE, state.


Advanced Transitions

When considering problems that may come up in a defensive autonomous game, it may be beneficial to have actions to fall back on if something goes wrong.

This usage of a linear transition is often enough for most teams, but when attempting to do more advanced transitions with fallbacks, it may be useful to have a pointer state. In the code, the pointer state is always the second parameter. Here's an example:

In this code segment if, it has been 0.5 seconds and the break beam hasn't been broken, then the robot will go back to the extending state to attempt the grab of the cone again.
// Logic:
// If it has been 0.5 seconds and the break beam hasn't been broken, 
// then the robot will attempt to grab the cone again.
.transition( () -> timer.seconds() > .5 && !arms.isBreakBeamBroken(),
    LinearStates.EXTENDING )

If you wish to add an action that happens upon exiting the state, you can add one like this:

.transition( () -> !drive.isBusy(), () -> drive.rest())

The pointer state tells the machine specifically where to go next, rather than moving linearly through the machine.

All the functionality that has been covered with normal transitions can also be achieved with time transitions too. As a summary, all transition capabilities are below.

Method List:

.transition(Condition) 
.transition(Condition, Enum/String)
.transition(Condition, ExitAction)
.transition(Condition, Enum/String, ExitAction)

// other methods for more clarity
.transitionWithExitAction(Condition, ExitAction)
.transitionWithPointerState(Condition, NextState)

// timed transitions
.transitionTimed(Time)
.transitionTimed(Time, Enum/String)
.transitionTimed(Time, NextState, ExitAction)
.transitionTimed(Time, ExitAction)

// wait states
.waitState(Time)
.waitState(Time, Enum/String)

// minimum transitions
.minimumTransitonTimed(Time)
.minimumTransitionTimed(Time, TransitionNumber...)

When considering problems that may come up in a defensive autonomous game, it may be beneficial to have states to fall back on in the event that something goes wrong. This state would be considered a fallback state and would not participate in the traditional linear order of an autonomous.

This functionality can be achieved by indicating in the state creation that it is a fallback state. Here is how to do it:

.state(Enum/String, true/false) // true for fallback 
.failsafeState(Enum/String) // more clarity version

To recap, this state will not be included in the traditional order of events and can only be reached by being directly pointed to by a pointer transition. This means the machine cannot accidentally stumble onto this state when following non-pointer transitions.


Nested State Machines

In a more advanced robot with many subsystems, it may be easier to abstract certain repetitive sequences into other classes. For example, if you wanted to use the same transfer sequence in a tele-op and an autonomous, it would be easier to abstract the transfer machine into another class. This functionality can easily be achieved and is shown in the example.

public class StateMachines {
    public enum Transfer {
        GRAB,
        ARM_UP
    }
    
    public static StateMachine getTransferMachine(Outtake arm) {
        return new StateMachineBuilder
            .state(Transfer.GRAB)
            .onEnter( () -> arm.grab())
            .transitionTimed(.5)
            
            .state(Transfer.ARM_UP)
            .onEnter( () -> arm.moveUp())
            
            .build();
    }
}

@TeleOp 
public class MyTele extends LinearOpMode {
    StateMachine transfer;
    Robot robot;
    
    enum States {
        INIT,
        INTAKE,
        TRANSFER,
        OUTTAKE
    }
    
    public void runOpMode() throws InterruptedException {
        robot = new Robot(hardwareMap);
        transfer = StateMachines.getTransferMachine(robot.outtake);
        
        StateMachine globalMachine = new StateMachineBuilder()
            .state(States.INIT)
            .transition(() -> gamepad1.x, States.INTAKE)
            
            .state(States.INTAKE)
            .loop( () -> robot.intake.spin())
            .transition(() -> gamepad.y)
            
            // NESTED STATE MACHINE
            .state(States.TRANSFER)
            .onEnter( () -> transfer.start()) // Starting the machine
            .loop( () -> transfer.update()) // Updating the machine in the loop
            // Transition after state machine is not running
            .transition(() -> !transfer.isRunning())
            .onExit( () -> { // When the machine is over, end it and reset it.
                transfer.reset();
            }
       
            
            .state(States.OUTTAKE)
            .onEnter(() -> robot.outtake.deposit())
            .transitionTimed(1, States.INIT)
            
            .build();
            
        waitForStart();
        
        globalMachine.start();
        
        while(opModeIsActive()) {
            robot.update();
            globalMachine.update();
        }
    }
}

Template States

If you have a certain state that's repeated throughout your code, such as a potential nested state machine, a template state could help make things easier. It allows you to construct a state that you can then use in any StateMachine. You do this with a normal StateMachineBuilder, except at the end, you use a .buildStateTemplate() instead of .build() Here is the code:

// Building state template
State templateState = new StateMachineBuilder()
    .onEnter(nestedMachine::start)
    .loop(nestedMachine::loop)
    .transition(() -> !nestedMachine.isRunning())
    .onExit(nestedMachine::reset)
    
    .buildStateTemplate();
    
// Inside an example state machine
StateMachine machine = new StateMachineBuilder()
    .stateTemplate("NestedMachine", templateState) // runs the template state 
    
    .state("NEXT_STATE")
    .onEnter(() -> System.out.println("Completed nested machine");)
    
    .build();

Last updated 4 months ago

When creating pointer transitions, it is easy to get overwhelmed so it is always important to think through the steps before you do it. Creating a diagram such as the one referenced in can make life easier.

An advanced example using the information covered here can be found in .

Page cover image
Examples
What Is a Finite State Machine?