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
}