Email Controled LED With ESP32-Stick-POE-P/A/ETH
by GrunclMichal in Circuits > Arduino
321 Views, 1 Favorites, 0 Comments
Email Controled LED With ESP32-Stick-POE-P/A/ETH
Hello
In today's bit more advanced tutorial we'll be trying to create a circuit that is going to take an input from an email that we can send from any device. This can be used in a wide variety of cases, and I'll show you the basics and let you think of creative ways to use this information.
Supplies
Connecting
Esp32-Stick development boards have one user LED build inside of them and connected to GPIO2 so we don't have to connect external LED. Thanks to that all that we're left with is to connect USB-C and Ethernet cable to their dedicated ports on one side and on the other to PC and we're done with connecting.
Email Setup
We're going to need a new gmail account for this project. You can use also other email services but i would recomand using gmail becos that's what I'm using and other services can be different.
Alright. Now we need to find our application key. To find yours follow this tutorial: here
Now head to the settings and there will be something that says POP/IMAP. Click on that and allow POP protocol on your account
Also don't forget that everything sent to this email will get deleted
Code
We'll be using VSCode with PlatformIO extension installed.
So, after installing PlatformIO click at a "Bee icon" and then select "PIO Home>Open"
Click on "New Project" give your project a name, select board "Adafruit ESP32 Feather" and hit Finish
After that open file "platformio.ini" and check if all info is same as here:
[env:featheresp32]
platform = espressif32@2.0.0
board = esp32dev
framework = arduino
monitor_speed = 115200
If not, rewrite anything that is missing or is different
Now for the main code.
You will find it in "src>main.cpp"
(Everything marked with "//" or written between "/* */" is comment. That means it's not a code, but a text written to help you understand it better. Try to read it and see, if you understand what's going on)
//this adds library
#include <ETH.h>
#include <Arduino.h>
#include <WiFiClientSecure.h>
//reset ETH_CLK_MODE
#ifdef ETH_CLK_MODE
#undef ETH_CLK_MODE
#endif
//define pin 2 as LED pin
#define LED_PIN 2
//define important iner pins
#define ETH_CLK_MODE ETH_CLOCK_GPIO17_OUT
#define ETH_POWER_PIN -1
#define ETH_TYPE ETH_PHY_LAN8720
#define ETH_ADDR 1
#define ETH_MDC_PIN 23
#define ETH_MDIO_PIN 18
//Set the server POP3 address, the port, the user and password.
//Using PROGMEM for these causes a fail when trying to connect and log in.
//type here your server email address (exp: pop.gmail.com)
const char pop_server[] = "xxxxxxxxx";
//set your pop3 port
const int pop_port = 995;
//type here your email address
const char pop_user[] = "xxxxxxxx@xxxx.xx";
//type here your app password
const char pop_pass[] = "xxxxxxxxx";
//these setup important things to POP3
#define ENABLE_DELETE_POP
//#define DEBUG_POP (delete "//" and this bracket to show more text in console)
#define POP_TIMEOUT 1000
#define POP_TIMEOUT_DEFAULT 5000
//var telling us if we're connected
static bool eth_connected = false;
//creates a WiFi client
WiFiClientSecure client;
/*
* method that checks what kind of WiFi event happend and than prints out important info to
* console
*/
void WiFiEvent(WiFiEvent_t event)
{
switch (event)
{
//if event is "START" this prints it out to console and setsup hostname
case SYSTEM_EVENT_ETH_START:
Serial.println("ETH Started");
ETH.setHostname("esp32-ethernet");
break;
//if event is "CONNECTED" this prints it out to console
case SYSTEM_EVENT_ETH_CONNECTED:
Serial.println("ETH Connected");
break;
/*if event is "ETH_GOT_IP" this prints out all important info to console (MAC, IPv4, etc.) and
sets eth_connected to true*/
case SYSTEM_EVENT_ETH_GOT_IP:
Serial.print("ETH MAC: ");
Serial.print(ETH.macAddress());
Serial.print(", IPv4: ");
Serial.print(ETH.localIP());
if (ETH.fullDuplex()) {
Serial.print(", FULL_DUPLEX");
}
Serial.print(", ");
Serial.print(ETH.linkSpeed());
Serial.println("Mbps");
eth_connected = true;
break;
//if event is "DISCONNECTED" this prints it out to console and sets eth_connected to false
case SYSTEM_EVENT_ETH_DISCONNECTED:
Serial.println("ETH Disconnected");
eth_connected = false;
break;
//if event is "ETH_STOP" this prints it out to console and sets eth_connected to false
case SYSTEM_EVENT_ETH_STOP:
Serial.println("ETH Stopped");
eth_connected = false;
break;
//default does nothing
default:
break;
}
}
void setup()
{
pinMode(LED_PIN, OUTPUT);
//this setup console and print in it that esp32 stick is started
Serial.begin(115200);
Serial.println("ESP32-Stick begin\n");
//this setup WifiEvent as method that activates when WiFi starts event
WiFi.onEvent(WiFiEvent);
//this passes important pins to ETH
ETH.begin(ETH_ADDR, ETH_POWER_PIN, ETH_MDC_PIN, ETH_MDIO_PIN, ETH_TYPE, ETH_CLK_MODE);
}
void readRemaining();
void popFail();
boolean readOk( boolean readAll);
int getEmail( char *pBuf, int nBufSize);
void loop()
{
// Create a buffer to receive the commands in (that is the Subject of the mail).
char buffer[32];
//if ETH is connected start controling st
if (eth_connected)
{
// 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("\""));
String myString = String(buffer);
//select which command was send and run it
if(myString.equalsIgnoreCase("led=on"))
{
digitalWrite(LED_PIN, HIGH);
Serial.println("turning the LED on");
}
else if(myString.equalsIgnoreCase("led=off"))
{
digitalWrite(LED_PIN, LOW);
Serial.println("turning the LED off");
}
else
{
Serial.println("unknown command");
}
delay(200);
}
}
}
}
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() == 0)
{
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.print(bufOk[0] + " ");
Serial.print(bufOk[1] + " ");
Serial.print(bufOk[2]);
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
}
If you want to get more info about what's happening than uncomment "#define DEBUG_POP"
Now we will need to change some things to suit your need
Change these to your service's server adress (pop.google.com) and pop3 port (995)
const char pop_server[] = "xxxxxxxxx";
const int pop_port = 995;
Change this to your email adress
const char pop_user[] = "xxxxxxxx@xxxx.xx";
Change this to your app password
const char pop_pass[] = "xxxxxxxxx";
Now just save, hit CTRL-ALT-U and click on "connector" icon on bottom blue line and you're done
Good job :D
To use this program just send email with "ARDUINO led=on" in subject to the email account you setted up to turn the LED on and "ARDUINO led=off" to turn it off.