Page cover image

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.

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 What Is a Finite State Machine? can make life easier.

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.

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


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
            .onExit( () -> { // When the machine is over, end it and reset it.
                transfer.reset();
                transfer.stop();
            }
            // Transition when the transfer is done
            .transition( () -> transfer.getState() == Transfer.ARM_UP); 
            
            .state(States.OUTTAKE)
            .onEnter(() -> robot.outtake.deposit())
            .transitionTimed(1, States.INIT)
            
            .build();
            
        waitForStart();
        
        globalMachine.start();
        
        while(opModeIsActive()) {
            robot.update();
            globalMachine.update();
        }
    }
}

Last updated