Best way to implement "modes" or "states" into a program?

I have an Arduino and a Raspberry Pi on a differential robot. My plan is to SSH to the PI then being able to run a python script in terminal, thereafter being able to issue commands through the terminal program then the Pi sends it over to the Arduino. The Arduino then controls the robot by executing the commands eg: driving a certain path. I already have the majority of the code working. But I'm concerned that the code is very inefficient and i'm overcomplicating the program. My thought being "surely there is an easier way".

What could the simplest implementation be?

How could one implement modes into the program for example:

For the PI: "Waiting for arduino mode" then the program moves to "Waiting for user commands" then "Sending commands mode" then "Listening for completion flag from arduino".

For arduino: "Waiting for commands from pi mode" then "interpret commands mode" then "excution mode" then circling back.

My idea is at the moment is along the lines of: (arduino has similar structure)

def check_state():
  global state
  match state:
    case 0:
        establish_arduino_connection()
    case 1:
        wait_for_user_commands()
    case 2:
        wait_for_execution()
    case _:
        print("Error: Program in unknown state.", now)
        sys.exit(1)

Then having a loop that constantly check the state: Li-ion Battery

while True:
  update_time()
  check_state()

It is a complex program, with which i've been struggling. Some advice would be much appreciated :slight_smile:

A state machine as you describe is a good flexible and modular approach

you need to define the command language and write a command parser.

Frankly, I think you're on the right path. State machines are simple in concept, but as you noticed, once you are modeling more complex systems, you run into the situation of having many states and that makes it somewhat awkward. But it still works, and in terms of computing, it's often still fairly efficient since only the relevant code for any given state is being executed. That doesn't do away with the complexity of developing the software, though.

What I've done in a recent project with a large number of distinct states (about 100 as I recall), about a dozen outputs and 4 rotary encoders + 8 buttons as inputs was model the behavior of the system in...Excel. Well, mostly because it was within reach and easy to use, but there's of course dedicated software for this that will work better. I defined all the states and the behavior / responses of the system for each state, and then made Excel generate some of the code - e.g. arrays of structs that the code 'reads' or manipulates and that define system behavior. By doing it like this, I minimized the coding effort to the core logic of the system while defining all state-specific behavior outside the Arduino IDE in a place that made it easier for me to oversee.

switch/case whould seem to be the simplest way to implement the state machine and if you use an enum with sensible member names the code becomes much easier to read than if you use anonymous numbers for states

Certainly.

I also always determine the numerical values myself. This allows me, for instance, to use the LSB of the state byte (a byte usually is sufficient; any longer data type could also be used of course) as an 'init' bit which is automatically set by the state machine manager upon entering the state. This allows certain routines to be run only once, after which the init-bit is set to 0, allowing the repeated code to be run.
I know that some ready-made state machine managers also have an exit-state for each state, but personally I have never found this to be a necessity.

in order to get the user commands asynchronously in your python code, you might need to use threads, one listening to what the user types in while the main thread handles the commands.

that's how you could structure your python code (that's for python3, in python2 change import queue into import Queue and instantiation lineQueue = Queue.Queue())

#!/usr/bin/python3
import sys, threading, queue

commandQueue = queue.Queue()

def getCommand(commandQueue):
    while True:
        command = sys.stdin.readline().strip().upper()
        commandQueue.put(command)

def configureUserInput():
    input_thread = threading.Thread(target=getCommand, args=(commandQueue,))
    input_thread.daemon = True
    input_thread.start()

def parseCommand(aCommand):
    if (aCommand == "LEFT"):
            print("OK, going left")
            
    elif (aCommand == "RIGHT"):
            print("OK, going Right")
    
    else:
            print("Sorry, '" + aCommand + "' command is unknown")

# ---- MAIN CODE -----

configureUserInput()

while True:
    if not commandQueue.empty():
        parseCommand(commandQueue.get())
    # here you can do other stuff
    

1 Like