State Machines
In this chapter we are going to cover a topic which is more directly related to robotics. So far, all of our programs have been very linear. That is, we logically follow one step after another until the program is complete. Unfortunately, this is really not how our robot is going to work. We might, for example, want to have some code that drives the robot forward for a certain amount of time. We might want to accomplish this by turning the motors on, waiting the required time, and then turning the motors off. Unfortunately, if we approach the problem in this way, we will not be able to other things during the time we are waiting.
Our program needs to be constructed in such a way that we can start one activity, and then return later to check on it’s progress. For simple tasks this can be fairly easy, but once things become complex, it can become very difficult very fast.
One of the tools we will use to simplify this problem is often refereed to as a state machine. The idea about a state machine is that your robot has a number of states that it can be in, and there are certain events that will move the robot from one state to another.
We are going to create a state machine that has three states, State A, State B and State C. The program will start in State A and will then accept input from the console. When the machine is in State A, if the user types the letter ‘B’ then the machine will transition to State B. Any other input when the machine is in State A will be ignored. When the machine is in State B and the user types the letter ‘C’, then the machine will transition to State C. Any other input when the machine is in State B will be ignored. Finally when the machine is in State C and the user types the letter ‘A’, then the machine will transition back to State A. As before, all other input when the machine is in State C will be ignored.
When dealing with state machines, it is sometimes useful to draw a State Diagram. For this machine, that might look like:
The first thing we are going to want is a way to identify the states. We could assign an integer to each state (e.g. State A = 1, State B = 2, State C = 3) but we would like something that would be more self evident when we look at our code. It turns out that Java has a way to associate names with small integers using the enum statement. To declare the enum we can add the following to the beginning of our main class:
enum State { StateA, StateB, StateC }
This defines a new variable type that can take on one of three values, and we can declare a variable of this new type in our main() much in the same way we would declare an integer. Note that since we want our state machine to start in StateA, we will initialize the state variable to this value.
public class HelloWorld { enum State { StateA, StateB, StateC } public static void main(String[] args) throws IOException { State state = State.StateA; } }
Now for the rest of the program, we are going to implement the state machine using s switch statement much in the same way as we did the chapter on the switch statement. First let’s set up the framework, and then we will flush it out:
public static void main(String[] args) throws IOException { State state = State.StateA; char ch; System.out.println("State A"); while (true) { ch = (char) System.in.read(); switch (state) { case StateA: break; case StateB: break; case StateC: break; } } }
We now have it set up so that each time through the loop we read a character from the console. Since we are going to want to do something different with the character we just read depending on what state we are in, we set up a switch statement which allows us to do that.
Now when we are in StateA, the only thing we care about is whether the user has entered the letter ‘B’, and if so, we need to switch to StateB. So in the StateA case we need to test for this.
public static void main(String[] args) throws IOException { State state = State.StateA; char ch; System.out.println(state); while (true) { ch = (char) System.in.read(); switch (state) { case StateA: if (ch == 'B') { state = State.StateB; System.out.println(state); } break; case StateB: break; case StateC: break; } } }
Note that in addition to changing the state, we are printing out the current state. This is solely for our benefit so that we can see if our program is working correctly.
Now compile and run the program. It should first print out that the machine is in StateA. Typing the character ‘B’ should then print out that the state has changed to StateB. Typing any other character should have no effect.
Now see if you can flesh out the StateB and StateC cases of the switch and test out your program. Whatever state you are currently in, the program should only accept the single character that will get out out of that state. When you are done, compare your result to the one below.
.
.
.
.
import java.io.IOException; public class HelloWorld { enum State { StateA, StateB, StateC } public static void main(String[] args) throws IOException { State state = State.StateA; char ch; System.out.println(state); while (true) { ch = (char) System.in.read(); switch (state) { case StateA: if (ch == 'B') { state = State.StateB; System.out.println(state); } break; case StateB: if (ch == 'C') { state = State.StateC; System.out.println(state); } break; case StateC: if (ch == 'A') { state = State.StateA; System.out.println(state); } break; } } } }
Now let’s change our state machine slightly. When we are in StateB, we now want to look for the character ‘A’ and, if it is entered, transition back to StateA (without first going to StateC). The state diagram for this new machine might look like:
Let’s see if you can make the required changes. When you are done, test out your program and then compare you solution to the one below.
.
.
.
.
import java.io.IOException; public class HelloWorld { enum State { StateA, StateB, StateC } public static void main(String[] args) throws IOException { State state = State.StateA; char ch; System.out.println(state); while (true) { ch = (char) System.in.read(); switch (state) { case StateA: if (ch == 'B') { state = State.StateB; System.out.println(state); } break; case StateB: if (ch == 'C') { state = State.StateC; System.out.println(state); } else if (ch == 'A') { state = State.StateA; System.out.println(state); } break; case StateC: if (ch == 'A') { state = State.StateA; System.out.println(state); } break; } } } }