Skip to content
rakar edited this page Sep 7, 2018 · 2 revisions

The world is be a collection of state machines - or at least it should be. -me

What is a state machine?

Many complex mechanical operations can be very well modeled and/or controlled by state machines, but often the control and reliability of even simple looking operations can be improved by using a state machine. A state machine in this context is some code that models a collection of states and a collection of transitions connecting those states. This is similar in concept to a Deterministic Finite Automata (DFA), but in our case without all of the mathematical rigor. State machines begin in some "start" state and based on input from the robot they may transition to a new state. Every time the state machine updates it evaluates the input from the robot and then possibly if certain conditions are met transitions to a new state. I'll give you a very simple example:

WAITING-----door bell rings---->DRIVING_TO_DOOR-----arrived at door---->COLLECT_PACKAGE

In this example the items in ALL_CAPS are the states and the lower case items are transitions. The example above is linear, but state machines do not need to be linear. Often a state may be able to transition to many other states based on different input conditions. If we consider the WAITING state the "start" state then the state machine would be initialized with its currentState set to WAITING and it would stay in that state until the "door bell rings". Then currentState would change to DRIVING_TO_DOOR. The state machine would be updated repeatedly with no change as the robot approached the door. Finally, when it "arrived_at_door" the currentState would change to COLLECT_PACKAGE.

CBStateMachine

The abstract class CBStateMachine handles the overall state machine functionality and can operate in a couple of modes to provide a good efficient structure for your state machine. In order to define your machine you will need to define a list of states and override several methods of the state machine class.

Defining States

States are defined in an enum type. The order of the states as well as the name of the enum are unimportant. Here is an example from an autonomous behavior.

enum States {Start, ValidateLift, DriveAndLift, TurnLeft, TurnRight, DriveALittle, Eject, Done}

Moving to new states calcNextState()

Overriding the method calcNextState() allows the code to control when you move from one state to another. This is typically done by adding a switch statement based on the field currentState. That way, given the current state, you can check criteria to decide if you should transition to a new state. If you should, simply set the nextState field to that next state. Otherwise do nothing and the state machine will stay in the same state.

        @Override
        public void calcNextState() {
            switch (currentState) {
                case Start:
                    nextState = States.ValidateLift;
                    break;
                case ValidateLift:
                    if(rd.mainLiftLimitValue) {
                        if((rd.fieldPosition==rd.nearSwitchSide)) {
                            nextState = States.DriveAndLift;
                        } else {
                            nextState = States.Done;
                        }
                    }
                    break;
    ...

Executing code while in a state doCurrentState()

Overriding the method doCurrentState() allows the code to perform some action when while in a given state. In the package example from above the robot might do nothing in the WAITING state, but would drive to the door in the DRIVING_TO_DOOR state, and of course intake a cube when in the COLLECT_PACKAGE state.

        @Override
        protected void doCurrentState() {
            //SmartDashboard.putString("do Current State:", currentState.name());
            switch (currentState) {
                case Start:
                    drd.active = true;
                    drd.direction = new CB2DVector(0,0);
                    drd.rotation = 0;
                    intake.active=true;
                    intake.direction.setXY(0,-.20); // bad value - Draw cube in?
                    intake.rotation = 0;
                    break;
    ...

Sensing transitions doTransition()

Overriding the method doTransition() allows the code to respond to transitions between states. It is called only when a state changes and before the state has changed. That means that inside doTransition() you can use currentState and nextState to determine which particular transition is occurring. The helper functions isTransition(from, to), isTransition(from), and isTransition(to) shorten some of the typing. Often you will want to wrap up some operation when a state completes like turning off a shooter motor after shooting or you may want to do something once at the beginning of an operation like record an encoder value before a drive. These are best done in transitions.

        @Override
        public void doTransition() {
            if(isTransitionFrom(States.Start)) {
                drd.active=true;
                drd.direction.setXY(0,0); // half speed forward (assuming power mode)
                drd.gyroLockValue = 0;
                drd.gyroLockActive = false;
                drd.rotation = 0;
                rd.mainLiftUp = false;
                rd.mainLIftDown = false;
                drivetrainAverageEncoderValueAtTransition = rd.drivetrainAverageEncoderValue;
            }
    ...

Updating the State Machine

Wherever you use your state machine, you must remember to update it by calling its update() method. This causes the state machine to re-evaluate its situation and possibly transition states.

Loop Mode

The state machine can operate in two loop modes CBStateMachineLoopMode.Looping or CBStateMachineLoopMode.OneShot. This controls what happens when a state changes during an update of the state machine. In OneShot mode the state machine will call calcNextState() and if there is no change, it calls doCurrentState(). If the state changes it will call doTransition(), update currentState with nextState, and finally call doCurrentState() with the new currentState. This will happen once each update. In Looping mode if there is a transition, once doCurrentState() has completed with the new state, calcNextState() will be called again followed by doTransition() and doCurrentState() and the process will continue looping as long as the state keeps changing. This is often appropriate behavior to traverse several states in one update, but one needs to be careful of looping endlessly. There are safeguards one can add to make sure that endless loops don't occur. As a side note, in the first call to update(), doCurrentState() is called before calcNextState() to ensure that it runs at least once simulating it having run previously.

Start States

In some state machines it can be helpful to have a dummy start state that immediately transitions to another state, but has some doTransition() code that runs if isTransitionFrom(Start), to handle initialization. This can be particularly helpful when considering initialization code that should be run once per use of the robot. That way if Autonomous runs it will be taken care of once then, but if you jump straight into Teleop that's when it will happen. Often teams test with Teleop only to find that at the end of Autonomous their robot re-initializes as Teleop starts when their robot is no longer in "start position".

Welcome to cyborg Suggested Workflow

cyborg Elements:

  • assemblies
  • behaviors
  • controllers
  • data
    • CBControlData
    • CBLogicData
    • CBRequestData
  • devices
  • mappers
  • utils
    • CBStateMachine

In-work documentation

Clone this wiki locally