Skip to main content
Expanded on how the SMS state machine is designed.
Source Link
Edgar Bonet
  • 44.3k
  • 4
  • 41
  • 79

Here aI would use one state machine for sending the SMS and another one for the buzzer. The first one has threeis the most complex:

Edit: I expanded on how I designed this state machine.

In a canonical finite state machine implementation, each action is associated with a state transition. We then have a transition for each command sent to the GSM module, and we can label the states according to which commands have already been sent, like this:

SENT_NOTHING → SENT_CR → SENT_CMGF → SENT_CMGS → SENT_MSG → SENT_ALL.

All of the above transitions send a string to the GSM module. The first one (SENT_NOTHING → SENT_CR) is triggered by the distance detected being less than 25 cm. The following ones are time-triggered one second after the previous one. We have to add one final transition (SENT_ALL → SENT_NOTHING) that performs no action but lets the system “forget” it has sent an SMS and thus makes it ready to send a new one. This transition would also be time-triggered, after a potentially long delay (how often would the user like to receive those reminders?). In the code below, this last delay is one second, which I believe is too short, but matches the last delay(1000) in the code you posted. The implementation would be like this:

static enum {
  SENT_NOTHING, SENT_CR, SENT_CMGF, SENT_CMGS, SENT_MSG, SENT_ALL
} sms_state = SENT_NOTHING;
static uint32_t time_last_command_sent;
uint32_t now = millis();
switch (sms_state) {
  case SENT_NOTHING:
    if (distance < 25) {
      Serial.print("\r");
      time_last_command_sent = now;
      sms_state = SENT_CR;
    }
    break;
  case SENT_CR:
    if (now - time_last_command_sent >= 1000) {
      Serial.print("AT+CMGF=1\r");
      time_last_command_sent = now;
      sms_state = SENT_CMGF;
    }
    break;
  case SENT_CMGF:
    if (now - time_last_command_sent >= 1000) {
      Serial.print("AT+CMGS=\"+YYXXXX\"\r");
      time_last_command_sent = now;
      sms_state = SENT_CMGS;
    }
    break;
  // and so on for the cases SENT_CMGS and SENT_MSG...
  case SENT_ALL:
    if (now - time_last_command_sent >= 1000) {
      sms_state = SENT_NOTHING;  // forget we sent an SMS
    }
    break;
}

I have an issue with this implementation though: it is overly repetitive, as the cases SENT_CR, SENT_CMGF, SENT_CMGS and SENT_MSG are essentially copies of the same code. In order to make the code dryer, I prefer merging them into a single state, and splitting the state information into two variables: sms_state and commands_sent. The new possible values for sms_state are now:

The mapping to the “full” machine states is then:

 full state    │ sms_state   commands_sent
───────────────┼───────────────────────────
 SENT_NOTHING  │ SMS_READY         0
 SENT_CR       │ SMS_SENDING       1
 SENT_CMGF     │ SMS_SENDING       2
 SENT_CMGS     │ SMS_SENDING       3
 SENT_MSG      │ SMS_SENDING       4
 SENT_ALL      │ SMS_DONE          5

It could be argued that sms_state is redundant, as all the state information is contained in commands_sent. I still wanted to keep sms_state in order to easily switch on it. This is not the only possible approach though, and it would be perfectly sensible to write something like

if (commands_sent == 0) {
  // handle the case SMS_READY
} else if (commands_sent < command_count) {
  // handle the case SMS_SENDING
} else {
  // handle the case SMS_DONE
}

Here a would use one state machine for sending the SMS and another one for the buzzer. The first one has three states:

Here I would use one state machine for sending the SMS and another one for the buzzer. The first one is the most complex:

Edit: I expanded on how I designed this state machine.

In a canonical finite state machine implementation, each action is associated with a state transition. We then have a transition for each command sent to the GSM module, and we can label the states according to which commands have already been sent, like this:

SENT_NOTHING → SENT_CR → SENT_CMGF → SENT_CMGS → SENT_MSG → SENT_ALL.

All of the above transitions send a string to the GSM module. The first one (SENT_NOTHING → SENT_CR) is triggered by the distance detected being less than 25 cm. The following ones are time-triggered one second after the previous one. We have to add one final transition (SENT_ALL → SENT_NOTHING) that performs no action but lets the system “forget” it has sent an SMS and thus makes it ready to send a new one. This transition would also be time-triggered, after a potentially long delay (how often would the user like to receive those reminders?). In the code below, this last delay is one second, which I believe is too short, but matches the last delay(1000) in the code you posted. The implementation would be like this:

static enum {
  SENT_NOTHING, SENT_CR, SENT_CMGF, SENT_CMGS, SENT_MSG, SENT_ALL
} sms_state = SENT_NOTHING;
static uint32_t time_last_command_sent;
uint32_t now = millis();
switch (sms_state) {
  case SENT_NOTHING:
    if (distance < 25) {
      Serial.print("\r");
      time_last_command_sent = now;
      sms_state = SENT_CR;
    }
    break;
  case SENT_CR:
    if (now - time_last_command_sent >= 1000) {
      Serial.print("AT+CMGF=1\r");
      time_last_command_sent = now;
      sms_state = SENT_CMGF;
    }
    break;
  case SENT_CMGF:
    if (now - time_last_command_sent >= 1000) {
      Serial.print("AT+CMGS=\"+YYXXXX\"\r");
      time_last_command_sent = now;
      sms_state = SENT_CMGS;
    }
    break;
  // and so on for the cases SENT_CMGS and SENT_MSG...
  case SENT_ALL:
    if (now - time_last_command_sent >= 1000) {
      sms_state = SENT_NOTHING;  // forget we sent an SMS
    }
    break;
}

I have an issue with this implementation though: it is overly repetitive, as the cases SENT_CR, SENT_CMGF, SENT_CMGS and SENT_MSG are essentially copies of the same code. In order to make the code dryer, I prefer merging them into a single state, and splitting the state information into two variables: sms_state and commands_sent. The new possible values for sms_state are now:

The mapping to the “full” machine states is then:

 full state    │ sms_state   commands_sent
───────────────┼───────────────────────────
 SENT_NOTHING  │ SMS_READY         0
 SENT_CR       │ SMS_SENDING       1
 SENT_CMGF     │ SMS_SENDING       2
 SENT_CMGS     │ SMS_SENDING       3
 SENT_MSG      │ SMS_SENDING       4
 SENT_ALL      │ SMS_DONE          5

It could be argued that sms_state is redundant, as all the state information is contained in commands_sent. I still wanted to keep sms_state in order to easily switch on it. This is not the only possible approach though, and it would be perfectly sensible to write something like

if (commands_sent == 0) {
  // handle the case SMS_READY
} else if (commands_sent < command_count) {
  // handle the case SMS_SENDING
} else {
  // handle the case SMS_DONE
}
Source Link
Edgar Bonet
  • 44.3k
  • 4
  • 41
  • 79

First thing: you should call pulseIn() right after sending the TRIG pulse. If you wait too much, you will miss the start of the echo pulse. For instance, here:

int distance = pulseIn(ECHO, HIGH, 26000);
int distance1 = pulseIn(ECHO1, HIGH, 26000);

the second pulseIn() is starting too late, because of the time taken by the first one.

Next, the general approach for doing things is a non-blocking fashion (and thus being able to handle concurrent tasks) is to program each task in the form of a finite state machine. Combine this with the timing technique used in the “Blink without delay” Arduino tutorial if you need time-triggered actions.

Here a would use one state machine for sending the SMS and another one for the buzzer. The first one has three states:

  • SMS_READY: the code is ready to send the message as soon as it is required by the detected distance.
  • SMS_SENDING: it has started to send the commands to the GSM module, but is not done yet.
  • SMS_DONE: the SMS has been sent, but no other one will be sent until some time has elapsed, after which it will transition to SMS_READY.

For the buzzer, there are only two states: BUZZER_OFF and BUZZER_ON. The transitions are conditioned by the measured distance and, for the ON → OFF transition, also by the time it has been ON.

Here is my tentative, untested implementation of this approach:

#define TRIG A0 //Module pins
#define ECHO A1
#define TRIG1 A2 //Module pins
#define ECHO1 A3
#define Buzzerpin 13

// Commands to be sent to the GSM module.
const int command_count = 5;
const char * const commands[command_count] = {
  "\r",
  "AT+CMGF=1\r",
  "AT+CMGS=\"+YYXXXX\"\r",
  "HELLO There",
  "\x1a"
};

void setup() {
  Serial.begin(9600);
  pinMode(TRIG, OUTPUT);
  pinMode(ECHO, INPUT_PULLUP);
  pinMode(TRIG1, OUTPUT);
  pinMode(ECHO1, INPUT_PULLUP);
  pinMode(Buzzerpin, OUTPUT);
}

void loop() {
  // Measure both distances.
  digitalWrite(TRIG, LOW);
  digitalWrite(TRIG, HIGH);
  delayMicroseconds(20);
  digitalWrite(TRIG, LOW);
  int distance = pulseIn(ECHO, HIGH, 26000) / 58;
  digitalWrite(TRIG1, HIGH);
  delayMicroseconds(20);
  digitalWrite(TRIG1, LOW);
  int distance1 = pulseIn(ECHO1, HIGH, 26000) / 58;

  // Timing for both state machines.
  uint32_t now = millis();

  // Send the SMS.
  static enum {SMS_READY, SMS_SENDING, SMS_DONE} sms_state;
  static uint32_t time_last_command_sent;
  static int commands_sent = 0;
  switch (sms_state) {
    case SMS_READY:
      if (distance < 25) {
        Serial.print(commands[commands_sent++]);
        time_last_command_sent = now;
        sms_state = SMS_SENDING;
      }
      break;
    case SMS_SENDING:
      if (now - time_last_command_sent >= 1000) {
        Serial.print(commands[commands_sent++]);
        time_last_command_sent = now;
        if (commands_sent >= command_count) {
          sms_state = SMS_DONE;
        }
      }
      break;
    case SMS_DONE:
      if (now - time_last_command_sent >= 1000) {
        commands_sent = 0;
        sms_state = SMS_READY;
      }
      break;
  }

  // Drive the buzzer.
  static enum {BUZZER_OFF, BUZZER_ON} buzzer_state;
  static uint32_t time_buzzer_started;
  if (buzzer_state == BUZZER_OFF && distance1 < 25) {
    digitalWrite(Buzzerpin, HIGH);
    buzzer_state = BUZZER_ON;
    time_buzzer_started = now;
  } else if (buzzer_state == BUZZER_ON && distance1 >= 25
             && now - time_buzzer_started >= 2000) {
    digitalWrite(Buzzerpin, LOW);
    buzzer_state = BUZZER_OFF;
  }
}