Need help writing a state machine

Mod edit:
This topic follows on from:

Here is my attempt at writing a switch/case state machine:

// Devastator_Robot_StateMachine.ino
#include <Servo.h>
#include <NewPing.h>  //https://bitbucket.org/teckel12/arduino-new-ping/downloads/
#include "DFRobot_HuskylensV2.h"
#include "Wire.h"


// Create object
HuskylensV2 huskylens;

// #define TRIG_PIN A4   // Change to A2 to prevent conflict with Husky Lens camera
// #define ECHO_PIN A5   // Chamge to A3 to prevent conflict with Husky Lens camera
#define TRIG_PIN A2
#define ECHO_PIN A3

#define MAX_DISTANCE 200

int E1 = 5;  //M1 Speed Control
int E2 = 6;  //M2 Speed Control
int M1 = 4;  //M1 Direction Control
int M2 = 7;  //M1 Direction Control
char input;
bool flag = 0;
int centerThreshold = 50;  // Tolerance for steering
int xCenter = 320;
int distance = 100;

NewPing sonar(TRIG_PIN, ECHO_PIN, MAX_DISTANCE);
Servo myservo;

void setup(void) {
  /* Setup baud rate for serial communication */
  //Serial.begin(115200);
  Serial.begin(9600);
  delay(100);
  myservo.attach(8);
  myservo.write(120);
  delay(2000);
  distance = readPing();
  delay(100);
  distance = readPing();
  delay(100);
  distance = readPing();
  delay(100);
  distance = readPing();
  delay(100);

  Serial.println("Hello World");
}


void loop() {

  if (Serial.available() > 0) {
    input = Serial.read();
    /* Manual mode */
    if (input == 'f' || input == 'b' || input == 'l' || input == 'r' || input == 's') {

      manual();
    }
    /* Automatic mode */
    else if (input == 'A') {
      flag = 1;
      automatic();
    } else if (input == 'L') {
      bootLeft();
    } else if (input == 'R') {
      bootRight();
    } else if (input == 'C') {
      camera();
    }
  }
}


/* Reading char 'M' on serial for stopping current mode */
void modeChange() {
  if (Serial.available() > 0) {
    input = Serial.read();

    if (input == 'M') {
      Serial.println("Changing Mode ");
      flag = 0;
      stop();
    }
  }
}

/*Turn 180 Right */
void bootRight() {
  Serial.println("Hard right turn");
  hardRight();
  stop();
}


/*Turn 180 Left */
void bootLeft() {

  Serial.println("Hard left turn");
  hardLeft();
  stop();
}

/********************************************* MANUAL MODE ********************************************/

void manual() {
  if (input == 'f') {
    moveForward();
  } else if (input == 'b') {
    moveBackward();
  } else if (input == 'l') {
    turnLeft();
  } else if (input == 'r') {
    turnRight();
  } else if (input == 's') {
    stop();
  }
}

/********************************************* AUTOMATIC MODE ********************************************/

void automatic(void) {

  Serial.println(distance);
  Serial.println("Automatic");
  int distanceR = 0;
  int distanceL = 0;
  delay(40);


  while (flag == 1) {
    moveForward();
    modeChange();
    distance = readPing();
    delay(100);

    if (distance <= 20) {
      stop();
      delay(100);
      moveBackward();
      delay(500);
      stop();
      delay(200);
      distanceR = lookRight();
      delay(200);
      distanceL = lookLeft();
      delay(200);

      if (distanceR >= distanceL) {
        Serial.println("Turning Right");
        turnRight();
        moveForward();
      } else {
        Serial.println("Turning Left");
        turnLeft();
        moveForward();
      }
    }
    distance = readPing();
  }
}

void turnRight() {

  analogWrite(E1, 155);
  analogWrite(E2, 155);
  digitalWrite(M1, LOW);
  digitalWrite(M2, HIGH);
  delay(1200);
  digitalWrite(M1, HIGH);
  digitalWrite(M2, HIGH);
}

void hardRight() {
  analogWrite(E1, 155);
  analogWrite(E2, 155);
  digitalWrite(M1, LOW);
  digitalWrite(M2, HIGH);
  delay(2400);
  digitalWrite(M1, HIGH);
  digitalWrite(M2, HIGH);
}

void turnLeft() {
  analogWrite(E1, 155);
  analogWrite(E2, 155);
  digitalWrite(M1, HIGH);
  digitalWrite(M2, LOW);
  delay(1200);
  digitalWrite(M1, HIGH);
  digitalWrite(M2, HIGH);
}

void hardLeft() {
  analogWrite(E1, 155);
  analogWrite(E2, 155);
  digitalWrite(M1, HIGH);
  digitalWrite(M2, LOW);
  delay(2400);
  digitalWrite(M1, HIGH);
  digitalWrite(M2, HIGH);
}

void stop(void)  //Stop
{
  flag = 0;
  Serial.println("Stoping");
  digitalWrite(E1, 0);
  digitalWrite(M1, LOW);
  digitalWrite(E2, 0);
  digitalWrite(M2, LOW);
}

void moveBackward() {
  Serial.println("Moving backwards");
  digitalWrite(M1, LOW);
  digitalWrite(M2, LOW);
  analogWrite(E2, 200);
  analogWrite(E1, 200);
  delay(5);
}

void moveForward() {
  //Serial.println("Moving forwards");
  digitalWrite(M1, HIGH);
  digitalWrite(M2, HIGH);
  analogWrite(E2, 200);  //PWM Speed Control
  analogWrite(E1, 200);
  delay(5);
}
int lookRight() {
  myservo.write(50);
  delay(500);
  int distance = readPing();
  delay(100);
  myservo.write(120);
  Serial.print("Looking right. Distance:");
  Serial.println(distance);
  return distance;
}

int lookLeft() {
  myservo.write(170);
  delay(500);
  int distance = readPing();
  delay(100);
  myservo.write(120);
  Serial.print("Looking left. Distance:");
  Serial.println(distance);
  return distance;
}

int readPing() {
  delay(70);
  int cm = sonar.ping_cm();
  if (cm == 0) {
    cm = 250;
  }
  return cm;
}

/********************************************* Camera STATE MACHINE ********************************************/


void camera(void) {

  while (!huskylens.begin(Wire)) {
    Serial.println("Waiting ... ");
    Serial.println("Huskylens not found! Check wiring.");
    delay(100);
  }

  // Switch to Object Tracking mode
  huskylens.switchAlgorithm(ALGORITHM_OBJECT_TRACKING);
  Serial.println("Switching to tracking mode");

  // Request results from the sensor
  huskylens.getResult(ALGORITHM_OBJECT_TRACKING);

  if (huskylens.available(ALGORITHM_OBJECT_TRACKING)) {

    // Get the object closest to the center crosshair
    auto target = huskylens.getCachedCenterResult(ALGORITHM_OBJECT_TRACKING);
  }
  ///////////////// STATE MACHINE ///////////////////////////////////////
  enum class State { Stoped,
                     Forward,
                     Left,
                     Right,
                     TooClose,
                     NoTarget };

  class SimpleMachine {
    State current = State::Stoped;

  public:

    // Request results from the sensor
    huskylens.getResult(ALGORITHM_OBJECT_TRACKING);
    if (huskylens.available(ALGORITHM_OBJECT_TRACKING)) {
      // Get the object closest to the center crosshair
      auto target = huskylens.getCachedCenterResult(ALGORITHM_OBJECT_TRACKING);
    }
    switch (current) {
      case State::Left:
        if ((RET_ITEM_NUM(target, Result, xCenter)) < (320 - centerThreshold)) {
          //Object on left: Turn Left
          Serial.println(" Turning Left");
          modeChange();
          turnLeft();
        }
        break;

      case State::Right:
        if ((RET_ITEM_NUM(target, Result, xCenter)) > (320 + centerThreshold)) {
          //Object on right: Turn Right
          Serial.println(" Turning Right");
          modeChange();
          turnRight();
        }
        break;

      case State::Forward:
        //Move Forward
        Serial.println(" Moving Forward");
        modeChange();
        moveForward();
        break;

      case State::TooClose:
        if ((RET_ITEM_NUM(target, Result, yCenter)) > 250) {
          Serial.println("Too close");
          digitalWrite(E1, 0);
          digitalWrite(M1, LOW);
          digitalWrite(E2, 0);
          digitalWrite(M2, LOW);
        }
        break;

      case State::NoTarget:
        Serial.println(" No Target");
        digitalWrite(E1, 0);
        digitalWrite(M1, LOW);
        digitalWrite(E2, 0);
        digitalWrite(M2, LOW);
        break;
    }
  };
}

And here are the errors:

C:\Projects\Book - 4 - Robots\Devastator Robot\Devastator_Robot_StateMachine\Devastator_Robot_StateMachine.ino: In function 'void camera()':
C:\Projects\Book - 4 - Robots\Devastator Robot\Devastator_Robot_StateMachine\Devastator_Robot_StateMachine.ino:300:5: error: 'huskylens' does not name a type
huskylens.getResult(ALGORITHM_OBJECT_TRACKING);
^~~~~~~~~
C:\Projects\Book - 4 - Robots\Devastator Robot\Devastator_Robot_StateMachine\Devastator_Robot_StateMachine.ino:301:5: error: expected unqualified-id before 'if'
if (huskylens.available(ALGORITHM_OBJECT_TRACKING)) {
^~
C:\Projects\Book - 4 - Robots\Devastator Robot\Devastator_Robot_StateMachine\Devastator_Robot_StateMachine.ino:305:5: error: expected unqualified-id before 'switch'
switch (current) {
^~~~~~
Multiple libraries were found for "Servo.h"
Used: C:\Users\Bill\Documents\Arduino\libraries\Servo
Not used: C:\Users\Bill\AppData\Local\Arduino15\libraries\Servo
exit status 1

Compilation error: 'huskylens' does not name a type

  • You cannot place logic (like switch or if statements) directly inside a class body; they must be inside a method.
    Additionally, the variable target is declared outside the class scope but used inside it.

  • Suggest you start learning about writing a class in a much simpler example.

Elsewhere we read

and this shows here as you are not struggling with state machinery, rather regular programming errors that are, or should be, easy for you to diagnose and fix.

I suggest you to slow your role and start working with state machines, millis()-based timing and a few other basic concepts before trying to implement a project such as this one.

Did you draw any kind of state diagram?

Have you found and worked through examples, which are many to be found?

Here's a place to start

or you may have to start a bit earlier

arduino blink without delay

arduino two things at once

and this is more theoretical but worth reading about and adopting, even when a finite state machine isn't employed:

The IPO Model

This will be hard until it is easy. It is a different way to do things, and attempting to learn how without a fairly solid ability to write regular code will not be the best use of your time.

Once it is easy you will wonder why it was hard, and your future projects will be where it all pays off.

a7

Please, consider posting an example.

I have been using Google to try and find a state machine example that actually compiles without any errors ! I could really use some example here.

So many. However; I recognize the frustration. They all see long-winded with diagrams that look like Billy on an errand in Family Circus.

State Machine drawings look like Billy on an errand

I hope this sketch of pulsing an LED using states, with a line-drawing of the states of the LED in the comments and code, helps.

@xfpd

Thanks for the referred Wokwi sketch in post #7. I am curious to know -- who is the author of this sketch?

I am. I was finding out how to use a timer to asynchronously "blink" an LED, and put it with a color-fading LED. It was to mimic a heartbeat (and color for effect).

I sense another question in the chat...

Congratulations!

Coming...!

Have you physically bulit the project and have tested it?

@williampretty

  • Let’s see where you are with things:
    • Tell us what you think a State Machine is.
    • Tell us what a struct is.
    • What happens when you use delay(. . .) in your sketch.
    • What is a non-blocking TIMER.

I would like to answer the above question with example code as the queston seems to be harder for OP to answer.

  • But you are a professor not a student.

Not the exact sketch... but, I used it (the code) to make a "LASER eye" for a cardboard face in a nighttime window... a blinking red "eye" (WS2812B) and a fading green "eye" (WS2812B - foregoing multi-color)... I forgot to write that I added a "white" fading halo effect.

[edit] I did "physically" run this sketch on WS2812B strand of eight, but did not "build" anything. Does it not work? Sorry to be off-topic.

A student is not a professor, but a professor is always a student.

Let us wait to see the answers coming from OP @williampretty for your questions of post #11.

1. What is a (Finite) State Machine (FSM)?
It is a model used in programming to design logic based on a limited number of "states" -- hence the word "Finite".

The model ensures that the system can only be in one state at a time and moves among the states based on specific "inputs" or "conditions". For example:

In a LED blinking system (Fig-1):

  • LED has only two states -- LED OFF and LED ON
  • At one time, the LED is either OFF or ON.
  • LED has an initial state -- LED is OFF
  • LED moves (makes a transition) from LED OFF state to LED ON state when 2-sec time elapses (specific condition). The elapse time is generated using a timer (millis() function or a hardware timer) rather than delay() function which blocks the MCU.

    Figure-1:

Programming Code: (tested)

#define LEDPin 13     //onboard LED of UNO 
unsigned long startTime = millis();
unsigned long interval = 2000;    //2000 ms

enum States {LED_OFF, LED_ON}; //enum assigns numerical values to synmbolic names (0, 1, ...)
States currentState = LED_OFF;  //initial state

void setup()
{
     pinMode(LEDPin, OUTPUT);
}

void loop() 
{
  switch(currentState)   //LED is OFF 
  {
    case LED_OFF:
      if (millis() - startTime >= interval) //2 sec elapses; move to LED ON state
      {
         currentState = LED_ON;   //change state value 
         startTime = millis();   //record statrtTime 
         digitalWrite(LEDPin, HIGH); //Off LED becomes On
      }
      break;     //don't execute next block of code

    case LED_ON:
      if (millis() - startTime >= interval) 
      {
        currentState = LED_OFF;
        startTime = millis();
        digitalWrite(LEDPin, LOW); //On LED becomes On
      }
      break;
  }
  //you can place code here to snese the closure of a Buton to stop blinking
}

Q1: Add a Button at DPin-2 of UNO and then add necessary code in the above sketch so that the led blinking stops when the Button is pressed.

Q2: Do you want me to re-write the above sketch using a class?

@GolamMostafa

  • We now think you are really some kind of AI.

:scream:

ChatGPT says start with this example:

enum States { LED_ON, LED_OFF };
States state = LED_ON;

unsigned long timer;

void loop()
{
  switch (state)
  {
    case LED_ON:
      digitalWrite(LED, HIGH);
      if (millis() - timer >= 1000)
      {
        state = LED_OFF;
        timer = millis();
      }
      break;

    case LED_OFF:
      digitalWrite(LED, LOW);
      if (millis() - timer >= 2000)
      {
        state = LED_ON;
        timer = millis();
      }
      break;
  }
}

Now-a-days, people are inevitably influnced to some extent by AI. The point is to look if the author has added "som extra value" or not. The AI has not told the benefit of using enum keyword in the snippet it has provided.

Ask the AI to convert the above sketch using class, and you will be very much unhappy with the code it provides. This is the case where you will have the opportunity to judge if @GolamMostafa has turned into an AI or he is the same human being as he was before after looking at his to be published "class based sketch".

We have been using "English to Bangla" dictionary for years as a tool; then, why not using modern AI as a tool in programming and text formatting/composition/moderation though I was against it at the very beginning?

I don't know it does a pretty good job

:thinking:

ChatGPT's version of a class for the OP

//===========================================================
//  LED Blinker Class (Non-blocking State Machine)
//===========================================================

class LedBlinker
{
  public:

    // Constructor
    LedBlinker(uint8_t pin, unsigned long intervalMs)
    {
      mPin = pin;
      mInterval = intervalMs;
    }

    // Call once in setup()
    void begin()
    {
      pinMode(mPin, OUTPUT);
      mStartTime = millis();
      mState = LED_OFF;
      digitalWrite(mPin, LOW);
    }

    // Call repeatedly in loop()
    void update()
    {
      switch (mState)
      {
        case LED_OFF:
          if (millis() - mStartTime >= mInterval)
          {
            mState = LED_ON;
            mStartTime = millis();
            digitalWrite(mPin, HIGH);
          }
          break;

        case LED_ON:
          if (millis() - mStartTime >= mInterval)
          {
            mState = LED_OFF;
            mStartTime = millis();
            digitalWrite(mPin, LOW);
          }
          break;
      }
    }

  private:

    enum States { LED_OFF, LED_ON };

    uint8_t mPin;
    unsigned long mStartTime;
    unsigned long mInterval;
    States mState;
};

#define LEDPin 13

LedBlinker led(LEDPin, 2000);  // 2 second interval

void setup()
{
  led.begin();
}

void loop()
{
  led.update();

  // other non-blocking code here
}
  • I am starting to come around to AI in some respects,
    I asked ChatGPT what jobs will AI replace, at the top of its list was Software jobs.

Please look at the code of post #19 carefully -- everything appears to have been mixed together. This is a class-based sketch, which may not be suitable for a beginner. It seems to be adapted from the work of an experienced programmer and presented without considering the user’s level of expertise.

An experienced human programmer would typically assess the skill level of the original poster (@williampretty) and provide a more structured, textbook-style class design. In such an approach, the class declaration includes only member variables and member function prototypes (not definitions), keeping the class body clean and easy to follow. This makes it much easier for a learner to understand and build upon the code step by step. For led blinking class bsed sketch, I would proceed with the following class declaration alonside clarifying comments:

class DigitalIo  //Class Name (DigitalIo); do Capitalize the first letter for Class Name
{
  private:     //access specifier
    int ledPin;   //DPin that drives LED

  public:
    DigitalIo(int DPin);  //constructor function; auomatically executed when object is created 
    void ioDir();            //member method/function to set direction of IO lne
    void ledOn();
    void ledOff(); 
};

In the meantime, OP @williampretty may study Fig-1 to know the names of various fields of a class.


Figure-1:

  • A while back, I trained ChatGPT to write C++ code for me in my style and format.
    This class is an example of how it responds when I make coding requests.

  • The OP might (after they master the basics) find AI can help with those things he/she does not understand or needs clarification of.

Time for bed.
:person_in_bed: