Skip to content

Instantly share code, notes, and snippets.

@m1cr0lab
Last active September 18, 2020 03:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save m1cr0lab/365858ec1ac4976c2efedf2a11f7777b to your computer and use it in GitHub Desktop.
Save m1cr0lab/365858ec1ac4976c2efedf2a11f7777b to your computer and use it in GitHub Desktop.
ESP32 Email Alert Based on Temperature Threshold (change values on web server)
/*********
Rui Santos
Complete project details at https://RandomNerdTutorials.com/esp32-email-alert-temperature-threshold/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
*********/
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include "ESP32_MailClient.h"
// REPLACE WITH YOUR NETWORK CREDENTIALS
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
// To send Email using Gmail use port 465 (SSL) and SMTP Server smtp.gmail.com
// YOU MUST ENABLE less secure app option https://myaccount.google.com/lesssecureapps?pli=1
#define emailSenderAccount "example_sender_account@gmail.com"
#define emailSenderPassword "email_sender_password"
#define smtpServer "smtp.gmail.com"
#define smtpServerPort 465
#define emailSubject "[ALERT] ESP32 Temperature"
// Default Recipient Email Address
String inputMessage = "your_email_recipient@gmail.com";
String enableEmailChecked = "checked";
String inputMessage2 = "true";
// Default Temperature Thresholds
String inputMessage3 = "25.0";
String inputMessage4 = "6.0";
String lastTemperature;
// HTML web page to handle 3 input fields (email_input, enable_email_input, threshold_input)
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html><head>
<title>Email Notification with Temperature</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head><body>
<h2>DS18B20 Temperature</h2>
<h3>%TEMPERATURE% &deg;C</h3>
<h2>ESP Email Notification</h2>
<form action="/get">
Email Address <input type="email" name="email_input" value="%EMAIL_INPUT%" required><br>
Enable Email Notification <input type="checkbox" name="enable_email_input" value="true" %ENABLE_EMAIL%><br>
Upper Temperature Threshold <input type="number" step="0.1" name="upper_threshold_input" value="%UPPER_THRESHOLD%" required><br>
Lower Temperature Threshold <input type="number" step="0.1" name="lower_threshold_input" value="%LOWER_THRESHOLD%" required><br>
<input type="submit" value="Submit">
</form>
</body></html>)rawliteral";
void notFound(AsyncWebServerRequest *request) {
request->send(404, "text/plain", "Not found");
}
AsyncWebServer server(80);
// Replaces placeholder with DS18B20 values
String processor(const String& var){
//Serial.println(var);
if(var == "TEMPERATURE"){
return lastTemperature;
}
else if(var == "EMAIL_INPUT"){
return inputMessage;
}
else if(var == "ENABLE_EMAIL"){
return enableEmailChecked;
}
else if(var == "UPPER_THRESHOLD"){
return inputMessage3;
}
else if(var == "LOWER_THRESHOLD"){
return inputMessage4;
}
return String();
}
// Flag variable to keep track if email notification was sent or not
bool emailSent = false;
const char* PARAM_INPUT_1 = "email_input";
const char* PARAM_INPUT_2 = "enable_email_input";
const char* PARAM_INPUT_3 = "upper_threshold_input";
const char* PARAM_INPUT_4 = "lower_threshold_input";
// Interval between sensor readings. Learn more about timers: https://RandomNerdTutorials.com/esp32-pir-motion-sensor-interrupts-timers/
unsigned long previousMillis = 0;
const long interval = 5000;
// GPIO where the DS18B20 is connected to
const int oneWireBus = 4;
// Setup a oneWire instance to communicate with any OneWire devices
OneWire oneWire(oneWireBus);
// Pass our oneWire reference to Dallas Temperature sensor
DallasTemperature sensors(&oneWire);
// The Email Sending data object contains config and data to send
SMTPData smtpData;
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
if (WiFi.waitForConnectResult() != WL_CONNECTED) {
Serial.println("WiFi Failed!");
return;
}
Serial.println();
Serial.print("ESP IP Address: http://");
Serial.println(WiFi.localIP());
// Start the DS18B20 sensor
sensors.begin();
// Send web page to client
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send_P(200, "text/html", index_html, processor);
});
// Receive an HTTP GET request at <ESP_IP>/get?email_input=<inputMessage>&enable_email_input=<inputMessage2>&threshold_input=<inputMessage3>
server.on("/get", HTTP_GET, [] (AsyncWebServerRequest *request) {
// GET email_input value on <ESP_IP>/get?email_input=<inputMessage>
if (request->hasParam(PARAM_INPUT_1)) {
inputMessage = request->getParam(PARAM_INPUT_1)->value();
// GET enable_email_input value on <ESP_IP>/get?enable_email_input=<inputMessage2>
if (request->hasParam(PARAM_INPUT_2)) {
inputMessage2 = request->getParam(PARAM_INPUT_2)->value();
enableEmailChecked = "checked";
}
else {
inputMessage2 = "false";
enableEmailChecked = "";
}
// GET threshold_input value on <ESP_IP>/get?upper_threshold_input=<inputMessage3>
if (request->hasParam(PARAM_INPUT_3)) {
inputMessage3 = request->getParam(PARAM_INPUT_3)->value();
}
// GET threshold_input value on <ESP_IP>/get?lower_threshold_input=<inputMessage3>
if (request->hasParam(PARAM_INPUT_4)) {
inputMessage4 = request->getParam(PARAM_INPUT_4)->value();
}
}
else {
inputMessage = "No message sent";
}
Serial.println(inputMessage);
Serial.println(inputMessage2);
Serial.println(inputMessage3);
Serial.println(inputMessage4);
request->send(200, "text/html", "HTTP GET request sent to your ESP.<br><a href=\"/\">Return to Home Page</a>");
});
server.onNotFound(notFound);
server.begin();
}
void loop() {
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
sensors.requestTemperatures();
// Temperature in Celsius degrees
float temperature = sensors.getTempCByIndex(0);
Serial.print(temperature);
Serial.println(" *C");
// Temperature in Fahrenheit degrees
/*float temperature = sensors.getTempFByIndex(0);
SerialMon.print(temperature);
SerialMon.println(" *F");*/
lastTemperature = String(temperature);
// Check if temperature is above threshold and if it needs to send the Email alert
if(temperature > inputMessage3.toFloat() && inputMessage2 == "true" && !emailSent){
String emailMessage = String("Temperature above threshold. Current temperature: ") +
String(temperature) + String("C");
if(sendEmailNotification(emailMessage)) {
Serial.println(emailMessage);
emailSent = true;
}
else {
Serial.println("Email failed to send");
}
}
// Check if temperature is below threshold and if it needs to send the Email alert
else if((temperature < inputMessage4.toFloat()) && inputMessage2 == "true" && emailSent) {
String emailMessage = String("Temperature below threshold. Current temperature: ") +
String(temperature) + String(" C");
if(sendEmailNotification(emailMessage)) {
Serial.println(emailMessage);
emailSent = false;
}
else {
Serial.println("Email failed to send");
}
}
}
}
bool sendEmailNotification(String emailMessage){
// Set the SMTP Server Email host, port, account and password
smtpData.setLogin(smtpServer, smtpServerPort, emailSenderAccount, emailSenderPassword);
// For library version 1.2.0 and later which STARTTLS protocol was supported,the STARTTLS will be
// enabled automatically when port 587 was used, or enable it manually using setSTARTTLS function.
//smtpData.setSTARTTLS(true);
// Set the sender name and Email
smtpData.setSender("ESP32", emailSenderAccount);
// Set Email priority or importance High, Normal, Low or 1 to 5 (1 is highest)
smtpData.setPriority("High");
// Set the subject
smtpData.setSubject(emailSubject);
// Set the message with HTML format
smtpData.setMessage(emailMessage, true);
// Add recipients
smtpData.addRecipient(inputMessage);
smtpData.setSendCallback(sendCallback);
// Start sending Email, can be set callback function to track the status
if (!MailClient.sendMail(smtpData)) {
Serial.println("Error sending Email, " + MailClient.smtpErrorReason());
return false;
}
// Clear all data from Email object to free memory
smtpData.empty();
return true;
}
// Callback function to get the Email sending status
void sendCallback(SendStatus msg) {
// Print the current status
Serial.println(msg.info());
// Do something when complete
if (msg.success()) {
Serial.println("----------------");
}
}
@Kensh21
Copy link

Kensh21 commented Jun 1, 2020

Hi Stephane,

i was wondering if you can help me combine this with the code i got from RUI i want to be able to send sms and email. i tried combining the code but to no avail. please see pastebin link: of my current code: https://pastebin.com/ZimY4FEU

i keep on getting this error: hope you can help me out
image

@m1cr0lab
Copy link
Author

m1cr0lab commented Jun 2, 2020

Hello,

Try to declare your sendEmailNotification() function before the loop() function. Compilation should go better.

@Kensh21
Copy link

Kensh21 commented Jun 2, 2020

Hello,
first of all, thank you for replying to my question. its been hard and i have been trying for so many hours to make the code work.
i have transferred the sendemailnotification() function before the loop() together with the sendcallback() however now im getting another set of error that i cant figure out again.

i tried putting back the sendcallback() after the loop() but same error occurs.
please see error below:
image

@m1cr0lab
Copy link
Author

m1cr0lab commented Jun 2, 2020

Hi,

Generally speaking, in C and C++, you must declare a function before calling or designating it. So, since the sendCallback function is used by the sendEmailNotification function, you must define sendCallback before sendEmailNotification.

When I was still using the Arduino IDE, it was not necessary when all the code was in the .ino file. But maybe this has changed with the new versions. And if you use a development environment like PlatformIO, it's absolutely necessary.

If you still want to define your function after it is used, you can still do so as long as you add a forward declaration of the function's signature before it is used.

Hoping this might help you.

I took the general framework of Rui's code so as not to disturb Random Nerd Tutorials readers and inserted just enough to add a second temperature threshold... but I personally wouldn't have written it that way ;-)

@Kensh21
Copy link

Kensh21 commented Jun 2, 2020

Hi,

Thank you so much for explaining this. I am new to arduino. c and c++ i have little experience with c# and I was really confused by how the declaration works/calling the functions. I was able to make it work now! I am planning on using VS Code with arduino plugin next time it might be easier :)

@m1cr0lab
Copy link
Author

m1cr0lab commented Jun 2, 2020

I'm glad you made it.

Once you have installed Visual Studio Code, instead of using the Arduino plugin, just try PlatformIO IDE, it's perfectly integrated with Visual Studio Code, and it's much better! ;-)

@Kensh21
Copy link

Kensh21 commented Jun 2, 2020

thank you! will try that instead!

@jbeard125
Copy link

In the sketch above how do you make it send a email of the temperature everyday at a specific time? Thanks

@m1cr0lab
Copy link
Author

Hi jbeard125,

You will have to deal with time management on ESP32. To do this, I encourage you to consult the following tutorial:

ESP32 NTP Client-Server: Get Date and Time (Arduino IDE)

This was not the purpose of this gist, which was meant to be a simple extension of Rui's code to manage two temperature thresholds. So I refer you to the articles published on Random Nerd Tutorials, where you will find the recipes you are interested in.

@jbeard125
Copy link

Ok thank you for the information. I'm trying to take two of Rui's sketchs, the one you have above on two temperature thresholds and another one on controlling gpio's on a web server. When I combine the two I keep getting errors. How would I take the sketch you modified and insert a web server to control gpio pins?

@m1cr0lab
Copy link
Author

For remote control of the ESP32, I suggest you read my tutorial on the subject:

ESP32 Remote Control with WebSocket

You should find the methodology to approach this type of problem and adapt it to your project.

@jbeard125
Copy link

#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include "ESP32_MailClient.h"

// REPLACE WITH YOUR NETWORK CREDENTIALS
const char* ssid = "Home Wifi";
const char* password = "123456";

// To send Email using Gmail use port 465 (SSL) and SMTP Server smtp.gmail.com
// YOU MUST ENABLE less secure app option https://myaccount.google.com/lesssecureapps?pli=1
#define emailSenderAccount "building"
#define emailSenderPassword "123456"
#define smtpServer "smtp.gmail.com"
#define smtpServerPort 465
#define emailSubject "[ALERT] Building Loop Temperature"

// Default Recipient Email Address
String inputMessage = "lp14255@gmail.com";
String enableEmailChecked = "checked";
String inputMessage2 = "true";
// Default Temperature Thresholds
String inputMessage3 = "75";
String inputMessage4 = "72";
String lastTemperature;

// HTML web page to handle 3 input fields (email_input, enable_email_input, threshold_input)
const char index_html[] PROGMEM = R"rawliteral(

<title>Email Notification with Temperature</title> <style> html {font-family: Arial; display: inline-block; text-align: center;} h2 {font-size: 3.0rem;} p {font-size: 3.0rem;} body {max-width: 600px; margin:0px auto; padding-bottom: 25px;} .switch {position: relative; display: inline-block; width: 120px; height: 68px} .switch input {display: none} .slider {position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; border-radius: 6px} .slider:before {position: absolute; content: ""; height: 52px; width: 52px; left: 8px; bottom: 8px; background-color: #fff; -webkit-transition: .4s; transition: .4s; border-radius: 3px} input:checked+.slider {background-color: #b30000} input:checked+.slider:before {-webkit-transform: translateX(52px); -ms-transform: translateX(52px); transform: translateX(52px)} </style>

DS18B20 Temperature

%TEMPERATURE% °C

ESP Email Notification

Email Address
Enable Email Notification
Upper Temperature Threshold
Lower Temperature Threshold

Building Controls

%BUTTONPLACEHOLDER% <script>function toggleCheckbox(element) { var xhr = new XMLHttpRequest(); if(element.checked){ xhr.open("GET", "/update?output="+element.id+"&state=1", true); } else { xhr.open("GET", "/update?output="+element.id+"&state=0", true); } xhr.send(); } )rawliteral";

void notFound(AsyncWebServerRequest *request) {
request->send(404, "text/plain", "Not found");
}

AsyncWebServer server(80);

// Replaces placeholder with DS18B20 values
String processor(const String& var){
//Serial.println(var);
if(var == "TEMPERATURE"){
return lastTemperature;
}
else if(var == "EMAIL_INPUT"){
return inputMessage;
}
else if(var == "ENABLE_EMAIL"){
return enableEmailChecked;
}
else if(var == "UPPER_THRESHOLD"){
return inputMessage3;
}
else if(var == "LOWER_THRESHOLD"){
return inputMessage4;
}
// Replaces placeholder with button section in your web page
if(var == "BUTTONPLACEHOLDER"){
String buttons = "";
buttons += "

Output - GPIO 2

<label class="switch"><input type="checkbox" onchange="toggleCheckbox(this)" id="2" " + outputState(2) + "><span class="slider">";
buttons += "

Output - GPIO 4

<label class="switch"><input type="checkbox" onchange="toggleCheckbox(this)" id="4" " + outputState(4) + "><span class="slider">";
buttons += "

Output - GPIO 33

<label class="switch"><input type="checkbox" onchange="toggleCheckbox(this)" id="33" " + outputState(33) + "><span class="slider">";
return buttons;
}
return String();
}

String outputState(int output){
if(digitalRead(output)){
return "checked";
}
else {
return "";
}
}

// Flag variable to keep track if email notification was sent or not
bool emailSent = false;

const char* PARAM_INPUT_1 = "email_input";
const char* PARAM_INPUT_2 = "enable_email_input";
const char* PARAM_INPUT_3 = "upper_threshold_input";
const char* PARAM_INPUT_4 = "lower_threshold_input";
const char* PARAM_INPUT_5 = "output";
const char* PARAM_INPUT_6 = "state";

// Interval between sensor readings. Learn more about timers: https://RandomNerdTutorials.com/esp32-pir-motion-sensor-interrupts-timers/
unsigned long previousMillis = 0;
const long interval = 5000;

// GPIO where the DS18B20 is connected to
const int oneWireBus = 4;
// Setup a oneWire instance to communicate with any OneWire devices
OneWire oneWire(oneWireBus);
// Pass our oneWire reference to Dallas Temperature sensor
DallasTemperature sensors(&oneWire);

// The Email Sending data object contains config and data to send
SMTPData smtpData;

void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
if (WiFi.waitForConnectResult() != WL_CONNECTED) {
Serial.println("WiFi Failed!");
return;
}
Serial.println();
Serial.print("ESP IP Address: http://");
Serial.println(WiFi.localIP());

pinMode(2, OUTPUT);
digitalWrite(2, LOW);
pinMode(4, OUTPUT);
digitalWrite(4, LOW);
pinMode(33, OUTPUT);
digitalWrite(33, LOW);

// Start the DS18B20 sensor
sensors.begin();

// Send web page to client
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send_P(200, "text/html", index_html, processor);
});

// Receive an HTTP GET request at <ESP_IP>/get?email_input=&enable_email_input=&threshold_input=
server.on("/get", HTTP_GET, [] (AsyncWebServerRequest *request) {
// GET email_input value on <ESP_IP>/get?email_input=
if (request->hasParam(PARAM_INPUT_1)) {
inputMessage = request->getParam(PARAM_INPUT_1)->value();
// GET enable_email_input value on <ESP_IP>/get?enable_email_input=
if (request->hasParam(PARAM_INPUT_2)) {
inputMessage2 = request->getParam(PARAM_INPUT_2)->value();
enableEmailChecked = "checked";
}
else {
inputMessage2 = "false";
enableEmailChecked = "";
}
// GET threshold_input value on <ESP_IP>/get?upper_threshold_input=
if (request->hasParam(PARAM_INPUT_3)) {
inputMessage3 = request->getParam(PARAM_INPUT_3)->value();
}
// GET threshold_input value on <ESP_IP>/get?lower_threshold_input=
if (request->hasParam(PARAM_INPUT_4)) {
inputMessage4 = request->getParam(PARAM_INPUT_4)->value();
}
}
else {
inputMessage = "No message sent";
}
// Send a GET request to <ESP_IP>/update?output=&state=
server.on("/update", HTTP_GET, [] (AsyncWebServerRequest *request) {
String inputMessage5;
String inputMessage6;
// GET input5 value on <ESP_IP>/update?output=&state=
if (request->hasParam(PARAM_INPUT_5) && request->hasParam(PARAM_INPUT_6)) {
inputMessage5 = request->getParam(PARAM_INPUT_5)->value();
inputMessage6 = request->getParam(PARAM_INPUT_6)->value();
digitalWrite(inputMessage5.toInt(), inputMessage6.toInt());
}
else {
inputMessage5 = "No message sent";
inputMessage6 = "No message sent";
}

Serial.println(inputMessage);
Serial.println(inputMessage2);
Serial.println(inputMessage3);
Serial.println(inputMessage4);
Serial.print("GPIO: ");
Serial.print(inputMessage5);
Serial.print(" - Set to: ");
Serial.println(inputMessage6);

request->send(200, "text/html", "HTTP GET request sent to your ESP.<br><a href=\"/\">Return to Home Page</a>");

});
server.onNotFound(notFound);
server.begin();
}
);
}
void loop() {

unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
sensors.requestTemperatures();
// Temperature in Fahrenheit degrees
float temperature = sensors.getTempCByIndex(0);
Serial.print(temperature);
Serial.println(" *F");

// Temperature in Fahrenheit degrees
/*float temperature = sensors.getTempFByIndex(0);
SerialMon.print(temperature);
SerialMon.println(" *F");*/

lastTemperature = String(temperature);

// Check if temperature is above threshold and if it needs to send the Email alert
if(temperature > inputMessage3.toFloat() && inputMessage2 == "true" && !emailSent){
  String emailMessage = String("Temperature above threshold. Current temperature: ") + 
                        String(temperature) + String("F");
  if(sendEmailNotification(emailMessage)) {
    Serial.println(emailMessage);
    emailSent = true;
  }
  else {
    Serial.println("Email failed to send");
  }    
}
// Check if temperature is below threshold and if it needs to send the Email alert
else if((temperature < inputMessage4.toFloat()) && inputMessage2 == "true" && emailSent) {
  String emailMessage = String("Temperature below threshold. Current temperature: ") + 
                        String(temperature) + String(" F");
  if(sendEmailNotification(emailMessage)) {
    Serial.println(emailMessage);
    emailSent = false;
  }
  else {
    Serial.println("Email failed to send");
  }
}

}
}

bool sendEmailNotification(String emailMessage){
// Set the SMTP Server Email host, port, account and password
smtpData.setLogin(smtpServer, smtpServerPort, emailSenderAccount, emailSenderPassword);

// For library version 1.2.0 and later which STARTTLS protocol was supported,the STARTTLS will be
// enabled automatically when port 587 was used, or enable it manually using setSTARTTLS function.
//smtpData.setSTARTTLS(true);

// Set the sender name and Email
smtpData.setSender("ESP32", emailSenderAccount);

// Set Email priority or importance High, Normal, Low or 1 to 5 (1 is highest)
smtpData.setPriority("High");

// Set the subject
smtpData.setSubject(emailSubject);

// Set the message with HTML format
smtpData.setMessage(emailMessage, true);

// Add recipients
smtpData.addRecipient(inputMessage);

smtpData.setSendCallback(sendCallback);

// Start sending Email, can be set callback function to track the status
if (!MailClient.sendMail(smtpData)) {
Serial.println("Error sending Email, " + MailClient.smtpErrorReason());
return false;
}
// Clear all data from Email object to free memory
smtpData.empty();
return true;
}

// Callback function to get the Email sending status
void sendCallback(SendStatus msg) {
// Print the current status
Serial.println(msg.info());

// Do something when complete
if (msg.success()) {
Serial.println("----------------");
server.begin();
}
}

m1cr0lab

i was able to take rui's two sketchs and compile them correctly , but when i type in the ip address it say it cant find it. can you look over my sketch and see whats wrong. thank you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment