To implement the state machine, you first define the steps (states). I'm using an enum for that. Place the below after your include statements.
// possible email steps
enum EMAILSTEPS
{
// connect to mail server
ST_CONNECT,
// commands and data
ST_HELO,
ST_AUTHLOGIN,
ST_USER,
ST_PASSWORD,
ST_MAILFROM,
ST_RCPTTO,
ST_DATA,
ST_COMPOSE,
ST_QUIT,
// disconnect
ST_STOP,
};
// current email step
EMAILSTEPS emailStep;
And your sendEmail() will look like
byte sendEmail()
{
byte thisByte = 0;
byte respCode;
switch (emailStep)
{
case ST_CONNECT:
break;
case ST_HELO:
break;
case ST_AUTHLOGIN:
break;
case ST_USER:
break;
case ST_PASSWORD:
break;
case ST_MAILFROM:
break;
case ST_RCPTTO:
break;
case ST_DATA:
break;
case ST_COMPOSE:
break;
case ST_QUIT:
break;
case ST_STOP:
break;
default:
break;
}
The intention is that once a step is completed, the code goes to the next step. Which step that is will in this case depend on the result of eRcv
I'll advise to write functions for the steps; except for the connect, stop and compose steps, you can use something like below
/*
send command to email server
In:
command
Returns:
email status (ES_WAITINGFORREPLY, ES_TIMEOUT, ES_FAIL or ES_OK)
*/
EMAILSTATUS sendCommand(const char *command)
{
// keep track if we have send the command
static bool inProgress = false;
// if command not send yet
if (inProgress == false)
{
// in case there still is data from a previous reply, get rid of it
// this is a bit of a hack to work around the earlier mentioned flaw and probably still not 100% solid
while (client.available() > 0)
{
client.read();
}
// send command
client.println(command);
// indicate that command was send
inProgress = true;
}
// check reply
EMAILSTATUS rv = eRcv();
if (rv != ES_WAITINGFORREPLY)
{
inProgress = false;
}
return rv;
}
For connect
/*
connect to email server
Returns:
email status (ES_WAITINGFORREPLY, ES_TIMEOUT, ES_FAIL, ES_CONNERR or ES_OK)
*/
EMAILSTATUS connect()
{
// keep track if we have tried to connect and are waiting for reply
static bool inProgress = false;
// if not yet tried to connect
if (inProgress == false)
{
if (client.connect(server, port) == 1)
{
Serial.println(F("connected"));
inProgress = true;
}
else
{
Serial.println(F("connection failed"));
return ES_CONNERR;
}
}
EMAILSTATUS rv = eRcv();
if (rv != ES_WAITINGFORREPLY)
{
inProgress = false;
}
return rv;
}
Note that this uses ES_CONNERR which you need to add to the EMAILSTATUS enum.
With those in place, we can populate most of the cases.
/*
send email
Returns:
true once sending is completed or aborted, else false
*/
bool sendEmail()
{
EMAILSTATUS rv;
switch (emailStep)
{
case ST_CONNECT:
rv = connect();
switch (rv)
{
case ES_WAITINGFORREPLY:
// keep waiting
return false;
break;
case ES_TIMEOUT:
// no reply, go to ES_STOP to close the connection
emailStep = ST_STOP;
return false;
break;
case ES_FAIL:
// error reply, go to ES_QUIT
emailStep = ST_QUIT;
return false;
break;
case ES_CONNERR:
// connection error, indicate that we're done
return true;
break;
case ES_OK:
// everything OK, go to ES_HELO
emailStep = ST_HELO;
return false;
break;
default:
Serial.println(F("Something's wrong; should not get here"));
// a rough hack
client.stop();
return true;
break;
}
break;
// other cases here
...
...
default:
Serial.println(F("Something's wrong; should not get here"));
// a rough hack
client.stop();
return true;
break;
}
}
I've changed the return type to a bool. The return value indicates if the caller (loop()) has to call the function again (false) or not (true). Depending on the return value of connect, the code can change the step to whatever is needed.
The two default cases are there to keep the compiler happy; they should not be needed. If you ever encounter the message "Something's wrong; should not get here", I was mistaken and have a bug in the code.
Below an example for all other cases except ST_COMPOSE, ST_QUIT and ST_STOP; you just need to modify the command to send and the emailStep in the ES_OK case to go to the intended next step.
case ST_HELO:
rv = sendCommand("EHLO 1.2.3.4");
switch (rv)
{
case ES_WAITINGFORREPLY:
// keep waiting
break;
case ES_TIMEOUT:
// timeout, go to ES_STOP to close the connection
emailStep = ST_STOP;
break;
case ES_FAIL:
// error reply, go to ES_QUIT
emailStep = ST_QUIT;
break;
case ES_OK:
// everything OK, go to ST_AUTHLOGIN
emailStep = ST_AUTHLOGIN;
break;
default:
break;
}
// indicate to caller that we're not done yet
return false;
break;
For the ST_QUIT step, we don't want to keep on going back to ST_QUIT on failure and get stuck in there forever; we simply go to ST_STOP as shown below
case ST_QUIT:
rv = sendCommand("EHLO 1.2.3.4");
switch (rv)
{
case ES_WAITINGFORREPLY:
// keep waiting
break;
case ES_TIMEOUT:
// timeout, go to ES_STOP to close the connection
emailStep = ST_STOP;
break;
case ES_FAIL:
// error reply, go to ES_QUIT
emailStep = ST_STOP;
break;
case ES_OK:
// everything OK, go to ST_AUTHLOGIN
emailStep = ST_AUTHLOGIN;
break;
}
// indicate to caller that we're not done yet
return false;
break;
For the ST_STOP case, we simply call client.stop() and indicate that we're done.
case ST_STOP:
client.stop();
// for the next time that we want to send a mail, start from the beginning
emailStep = ST_CONNECT;
// indicate to caller that we're not done yet
return true;
break;
For the ST_COMPOSE step, you will have to write a function similar to sendCommand and call it in that case. It does not have to take an argument.
/*
compose email
Returns:
email status (ES_WAITINGFORREPLY, ES_TIMEOUT, ES_FAIL or ES_OK)
*/
EMAILSTATUS compose()
{
// keep track if we have send the command
static bool inProgress = false;
// if command not send yet
if (inProgress == false)
{
// in case there still is data from a previous reply, get rid of it
// this is a bit of a hack to work around the earlier mentioned flaw and probably still not 100% solid
while (client.available() > 0)
{
client.read();
}
// send all the info here (to, from, subject, ...)
// send the actual data (a message, global variables from e.g. sensors etc)
// send CRLF dot CRLF to indicate end of compose
client.print("\r\n.\r\n");
// indicate that command was send
inProgress = true;
}
// check reply
EMAILSTATUS rv = eRcv();
if (rv != ES_WAITINGFORREPLY)
{
inProgress = false;
}
return rv;
}
With the above implementation, there should not be a need for a call to efail in eRcv. So you can remove the complete efail function as far as I understand the code.
Continued in next post due to size limit.