//
// Email POP3 reader.
// ------------------
// The Arduino checks the emails for a Subject that starts with "ARDUINO ".
// The text after that is the command for the Arduino.
// That email is delected from the mail server.
//
// Purpose:
// Arduino reads mail from a mail server with POP3.
// This makes it possible to send an email with commands for the Arduino.
// The Arduino itself is not the mail server,
// but a mail server is used in between the sender and the Arduino.
//
// This sketch checks the mail at a command from the serial port.
// In a normal situation, the mail could be checked by the Arduino every few minutes.
//
// When a mailserver is used that is available from the internet, the Arduino
// can get commands without the need to open a port in the router.
// A mailserver without encryption is needed.
//
// Usage:
// Send a mail to a mail server with "ARDUINO " at the begin of the Subject
// (capital characters ARDUINO, with space at end).
// After that the command for the Arduino (still in the Subject).
// This examples sketch turns on the system led with: "ARDUINO L=1".
//
// Restrictions:
// Only the "Subject" is used, not the body.
// Only non-encrypted communication is used.
// The mail is searched for the Subject with the keyword "ARDUINO ",
// and that email is also deleted. Any other mails for the Arduino will be handled the next time.
// The timeout for the client is altered and restored to the default of 1 second.
// The "TOP" command might not be implemented by very old or very simple mail servers.
//
// Versions:
// Version 1.00, november 2014, by Peter_n, Public Domain.
// First attempt.
// With help of MartinCoolen project at:
// https://fritzing.org/projects/pop3-email-checker
// Using SurferTim sketches as guidelines:
// https://playground.arduino.cc/Code/Email
// https://playground.arduino.cc/Code/WebServerST
// And some help about the commands:
// https://kewl.lu/articles/pop3/
// Version 1.01, november 2014, by Peter_n, Public Domain.
// The "TOP" command is used to scan the emails.
// The keyword "ARDUINO " must be used in the Subject,
// to prevent that other emails are deleted.
//
//
#include <SPI.h>
#include <Ethernet.h>
// Use comments to enable or disable this define for debug messages
#define DEBUG_POP
// Use comments to enable or disable the deleting of the mail
#define ENABLE_DELETE_POP
// The mac address must be an unique number
// A mac generator is used:
// https://www.miniwebtool.com/mac-address-generator/
byte mac[] = { 0xDE, 0x36, 0x5F, 0x0A, 0x19, 0x83 };
// change network settings to yours
IPAddress ip( 192, 168, 2, 2 );
IPAddress gateway( 192, 168, 2, 1 );
IPAddress subnet( 255, 255, 255, 0 );
// Set the server POP3 address, the port, the user and password.
// The POP3 mail server is something like this:
// mail.yourdomain.com, pop.yourdomain.com, pop3.yourdomain.com
// Using PROGMEM for these causes a fail when trying to connect and log in.
const char pop_server[] = "mail.yourdomain.com";
const int pop_port = 110;
const char pop_user[] = "user";
const char pop_pass[] = "****";
// The number of milliseconds timeout for parseInt() and find().
// The response time for the Server can still be 10 seconds.
#define POP_TIMEOUT 10
#define POP_TIMEOUT_DEFAULT 1000
EthernetClient client;
void setup()
{
Serial.begin( 9600);
Serial.println(F( "\nArduino POP3 email reader"));
pinMode( 13, OUTPUT); // the system led is used for testing
// When the Ethernet Shield is used, there is also a SD card connected
// to the SPI bus. Disable the SD card with chip select at pin 4.
pinMode( 4, OUTPUT);
digitalWrite( 4, HIGH);
// Start Ethernet. Use only the 'mac' parameter for DHCP
// Use 'mac' and 'ip' parameters for static IP address.
// Ethernet.begin( mac, ip);
// Ethernet.begin( mac, ip, gateway, gateway, subnet);
if( Ethernet.begin( mac) == 0)
{
Serial.println("Failed to configure Ethernet using DHCP.");
// no point in carrying on, so do nothing forevermore:
while(1);
}
// print your local IP address.
Serial.println(F( "Ethernet started."));
Serial.print(F( "Local IP = "));
Serial.println(Ethernet.localIP());
Serial.println(F( "Press 'c' to check mail."));
}
void loop()
{
// Create a buffer to receive the commands in (that is the Subject of the mail).
char buffer[32];
byte inChar = Serial.read();
if(inChar == 'c')
{
// The getEmail gets the text of the mail Subject into the buffer.
// The valid number of received characters are returned.
// If the return value is < 0, it is an error.
int n = getEmail( buffer, sizeof(buffer));
if( n<0)
{
Serial.print(F("Email POP3 failed, error = "));
Serial.println( n);
}
else
{
if( n == 0)
{
Serial.println(F("Ready, nothing to do."));
}
else
{
// 'n' is > 0, a command received.
Serial.print(F("Email checked, Command = \""));
Serial.print( buffer);
Serial.println(F("\""));
// Check the commands.
//
// At this moment, a single command 'L' is used to set system led on or off.
// L=1 (set led on)
// L=0 (set led off)
if( buffer[0] == 'L' && buffer[1] == '=')
{
digitalWrite( 13, buffer[2] == '0' ? LOW : HIGH);
}
}
}
}
}
// getEmail
// --------
// Find an email on a mail server, using POP3.
// The Subject should start with "ARDUINO " and the text
// after that is copied into pBuf.
//
// The data in pBuf is only valid if the return value is not an error
// (an error is return value less than zero).
//
int getEmail( char *pBuf, int nBufSize)
{
// nBytes is the number of bytes that is returned by getEmail.
int nBytes = 0;
// Connect to server
// client.connect returns '1' if okay, or negative number if error.
// SUCCESS 1
// 0 (error, unknown timeout, perhaps an error in the library)
// TIMED_OUT -1
// INVALID_SERVER -2
// TRUNCATED -3
// INVALID_RESPONSE -4
// -5 (there is no mail server at that IP address and port)
// The string for the server must be a normal string in sram, no PROGMEM allowed.
int nError = client.connect( pop_server, pop_port);
// During testing, a value of zero was sometimes returned.
// This is not according to the documentation and it is an error.
// Therefor the non-error value '0' is turned into a negative number to
// indicate an error.
if( nError == 0)
return( -200);
// Only a value of 1 is okay.
if( nError != 1)
return( nError);
#ifdef DEBUG_POP
Serial.println(F("connected"));
#endif
// The server should respond with "+OK" and maybe more text after that.
// Check if "+OK" can be read.
// The parameter 'true' is to read also everything after the "+OK".
if(!readOk( true))
return -102;
#ifdef DEBUG_POP
Serial.println(F("command USER"));
#endif
client.print(F( "USER "));
client.println( pop_user);
if(!readOk( true))
return -103;
#ifdef DEBUG_POP
Serial.println(F("command PASS"));
#endif
client.print(F( "PASS "));
client.println( pop_pass);
if(!readOk( true))
return -104;
#ifdef DEBUG_POP
Serial.println(F("command STAT"));
#endif
client.println(F( "STAT"));
if(!readOk( false))
return -105;
// The whole line was like this: "+OK 3 15343"
// It means that 3 emails are waiting with a total size of 15343.
// At this moment, the "+OK" is read, but nothing else.
// Check if there is a space after "+OK".
char c = client.read();
if( c != ' ')
return -106;
client.setTimeout( POP_TIMEOUT); // set timeout lower for parseInt
// Read the number of emails that are on the server.
int nMails = client.parseInt();
client.setTimeout( POP_TIMEOUT_DEFAULT); // restore timeout to 1 second
// Read the remaining of the response to STAT.
readRemaining();
#ifdef DEBUG_POP
Serial.print(F( "Number of emails="));
Serial.println( nMails);
#endif
// Test if there are emails waiting.
if( nMails == 0)
{
// No emails, but no error. Set buffer to empty string.
nBytes = 0; // the returned value
pBuf[0] = '\0'; // set empty string
}
else if( nMails > 0)
{
// emails are waiting.
// Scan the emails until the first is found with the keyword "ARDUINO " at the
// beginning of the "Subject: ".
boolean found_and_ready = false;
for( int nMailNumber = 1; nMailNumber <= nMails && !found_and_ready; nMailNumber++)
{
// The command RETR <x> gets the whole mail.
// The command TOP <x> <size> gets the header plus 'size' of the body.
#ifdef DEBUG_POP
Serial.print(F( "command TOP "));
Serial.print( nMailNumber);
Serial.println(F( " 0"));
#endif
client.print(F( "TOP "));
client.print( nMailNumber);
client.println(F( " 0"));
// Use readOk with parameter 'false' to stop reading after "+OK".
if(!readOk( false))
return -107;
// The header of the email is waiting to be read, use the Stream.find() to look for the Subject.
// The text "Subject: " should be at the beginning of a line, but that is not tested.
// The first found text "Subject: " is assumed to be the real subject.
// I have checked many years of emails, and the text is always "Subject: ", and never "SUBJECT: ".
// At the moment, it is not possible to use the F() macro for Stream.find
// Only the email that starts with "ARDUINO " at the start of the Subject is used.
client.setTimeout( POP_TIMEOUT); // set short timeout for find().
// find() returns true if found and false if not.
boolean foundsubject = client.find( "Subject: ARDUINO ");
client.setTimeout( POP_TIMEOUT_DEFAULT); // restore timeout to 1 second
if( foundsubject)
{
#ifdef DEBUG_POP
Serial.println(F("Found an email for me"));
#endif
// Read the remaining subject (that is the command for the Arduino) into a buffer.
// Every line from the mail server should end with CR + LF,
// but to be sure, both CR and LF are checked.
// Alternative:
// client.readBytesUntil('\r', pBuf, nBufSize);
// The last position in the buffer is reserved for the zero terminator.
// So read data until (nBufSize-1).
int i;
for( i = 0; i < (nBufSize-1) && client.available(); i++)
{
char c = client.read();
if (c == '\r' || c == '\n')
break;
pBuf[i] = c;
}
// Add zero terminator
pBuf[i] = '\0';
nBytes = i; // the number of received bytes is returned by the getEmail() function.
// More text of the header could be following the Subject.
// That is read and disregarded.
readRemaining();
#ifdef DEBUG_POP
Serial.print(F( "Subject = \"ARDUINO "));
Serial.print( pBuf);
Serial.println(F( "\""));
#endif
#ifdef ENABLE_DELETE_POP
// Delete the just read message.
#ifdef DEBUG_POP
Serial.print(F( "command DELE "));
Serial.println( nMailNumber);
#endif
client.print(F( "DELE "));
client.println( nMailNumber);
if(!readOk( true))
return -108;
#endif
// Everything is okay, the mail is read and deleted.
// Ready for now, don't process the remaining emails.
found_and_ready = true;
}
else
{
#ifdef DEBUG_POP
Serial.println(F("No ARDUINO keyword in subject"));
#endif
// This email has no "Subject: ARDUINO ".
// But the remaining text has still to be read and disregarded.
readRemaining();
}
}
}
#ifdef DEBUG_POP
Serial.println(F( "Sending QUIT"));
#endif
client.println(F( "QUIT"));
// After "QUIT", the server still respons with "+OK",
// but after that, the connection might get lost.
// So don't read everything after "+OK" (use parameter 'false' for readOk).
if(!readOk( false))
return -109;
client.stop();
#ifdef DEBUG_POP
Serial.println(F("normally disconnected"));
#endif
return( nBytes);
}
// Read the response from the mail server.
// That is "+OK" if everything is okay.
// Parameter 'readAll' is to read every character after the "+OK".
boolean readOk( boolean readAll)
{
// Check the response "+OK" from the mail server
// In most cases that is followed by a space and more text, but not always.
// In case of an error the text "-ERR" is received.
int loopCount = 0;
char bufOk[4];
// Wait for response of mail server, with a timout.
while(!client.available())
{
delay(1);
loopCount++;
// if nothing received for 10 seconds, timeout
if(loopCount > 10000)
{
client.stop();
#ifdef DEBUG_POP
Serial.println(F("\nTimeout"));
#endif
return false;
}
}
// Read the first three bytes.
client.readBytes(bufOk, 3);
#ifdef DEBUG_POP
Serial.write(bufOk, 3);
Serial.println();
#endif
// Is it "+OK" ?
if( strncmp( bufOk, "+OK", 3) != 0)
{
popFail();
return false;
}
// When the text after "+OK" is not needed, everything
// else can be read and disregarded
// (or shown in the serial monitor during debugging).
if( readAll)
readRemaining();
return true;
}
void readRemaining()
{
// This function is called after checking the "+OK".
// It reads everything from the server, until no more text is
// available.
while(client.available())
{
char c = client.read();
#ifdef DEBUG_POP_EXTRA
Serial.print( c);
#endif
}
return;
}
void popFail()
{
int loopCount = 0;
#ifdef DEBUG_POP
Serial.println(F("popFail"));
#endif
while(!client.available())
{
delay(1);
loopCount++;
// if nothing received for 10 seconds, timeout
if(loopCount > 10000)
{
client.stop();
#ifdef DEBUG_POP
Serial.println(F("\nTimeout"));
#endif
return;
}
}
client.stop();
#ifdef DEBUG_POP
Serial.println(F("disconnected due to fail"));
#endif
}