How to combine email sending code with my project code

Hi all - I have got my ESP32 to send me emails, no problem, and I have got a separate
sketch working fine (monitoring the level of salt in a water softener, and sounding a buzzer every 15 minutes if the level gets below the threshold). But I want to get my code to send me an email when the salt level is low. I've spent a lot of time trying to combine the code, and got down to one compilation error (initially 50+ errors!!), but I just can't see where I have gone wrong. Is there a tutorial showing how to use the email sending code in your own sketch? I could not find one.

This is my first time using any microcontroller, so I suspect my error is an obvious one to anyone else!

Here's the code: I start by defining the pins for the salt level monitor part, and then paste all the email sketch set up parts, and then try to send a mail from within my code.

// Define pins for the ultrasonic sensor
const int echoPin = 25;
const int trigPin = 26;
//Define other pins
const int buzzerPin = 27;  // The GPIO pin for the buzzer
const int testPin = 13; // The GPIO pin for the "Test" switch input. When switched to "Test" position, the "Test" switch connects pin 13 to Ground, giving a LOW state. 
// Pin 13 is connected to 3V3 via a 10K Ohm resistor. to prevent pin 13 floating to unkown states when in normal operation.
int testState = 0; //initialise the test variable to zero
unsigned long previousMillis = 0;  // will store last count time salt level was tested

const long interval = 900000;  // interval at which to test the salt level (900,000 milliseconds = 15 minutes)

#include <Arduino.h>
#include <WiFi.h>
#include <ESP_Mail_Client.h>

#define WIFI_SSID "*********" //Our wifi SSID
#define WIFI_PASSWORD "********"

/** The smtp host name e.g. smtp.gmail.com for GMail or smtp.office365.com for Outlook or smtp.mail.yahoo.com */
#define SMTP_HOST "smtp.gmail.com
#define SMTP_PORT 465

/* The sign in credentials */
#define AUTHOR_EMAIL "*******@gmail.com"
#define AUTHOR_PASSWORD "**** **** **** ****"

/* Recipient's email*/
#define RECIPIENT_EMAIL "****@gmail.com" //my email
#define RECIPIENT_CC1 "****@gmail.com"//wife's email as a CC:
/* Declare the global used SMTPSession object for SMTP transport */
SMTPSession smtp;

/* Callback function to get the Email sending status */
void smtpCallback(SMTP_Status status);

void setup()
{
  // Begin serial communication at 9600 baud rate
  Serial.begin(9600);
  pinMode(buzzerPin, OUTPUT); // initialize digital pin buzzerPin as an output.
  pinMode(trigPin, OUTPUT); // initialise digital pin trigPin as an output
  pinMode(echoPin, INPUT); // initialize digital pin echoPin as an input
  pinMode(testPin, INPUT);  // initialise digital pin testPin as an input
}
void loop() 
{
  Serial.println();
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  Serial.print("Connecting to Wi-Fi");
  while (WiFi.status() != WL_CONNECTED)
  {
    Serial.print(".");
    delay(300);
  }
  Serial.println();
  Serial.print("Connected with IP: ");
  Serial.println(WiFi.localIP());
  Serial.println();

  /*  Set the network reconnection option */
  MailClient.networkReconnect(true);

  /** Enable the debug via Serial port
   * 0 for no debugging
   * 1 for basic level debugging
   */
  smtp.debug(1);

  /* Set the callback function to get the sending results */
  smtp.callback(smtpCallback);

  /* Declare the Session_Config for user defined session credentials */
  Session_Config config;

  /* Set the session config */
  config.server.host_name = SMTP_HOST;
  config.server.port = SMTP_PORT;
  config.login.email = AUTHOR_EMAIL;
  config.login.password = AUTHOR_PASSWORD;
  config.login.user_domain = "";
  //Set the NTP config time
  config.time.ntp_server = F("europe.pool.ntp.og,time.nist.gov");// set to Europe server
  config.time.gmt_offset = 0;
  config.time.day_light_offset = 0;

  /* Declare the message class */
  SMTP_Message message;

  /* Set the message headers */
  message.sender.name = F("ESP");
  message.sender.email = AUTHOR_EMAIL;
  message.subject = F("From your Water Softner");
  message.addRecipient(F("Steve"), RECIPIENT_EMAIL);
  message.addCc(RECIPIENT_CC1); //Jill is on CC: 
  //Send raw text message
  String textMsg = "Low salt alarm";//this is the mesage I want to send
  message.text.content = textMsg.c_str();
  message.text.charSet = "us-ascii";
  message.text.transfer_encoding = Content_Transfer_Encoding::enc_7bit;
  message.priority = esp_mail_smtp_priority::esp_mail_smtp_priority_low;
  message.response.notify = esp_mail_smtp_notify_success |       esp_mail_smtp_notify_failure | esp_mail_smtp_notify_delay;
  /* Connect to the server */
  if (!smtp.connect(&config))
  {
    ESP_MAIL_PRINTF("Connection error, Status Code: %d, Error Code: %d, Reason: %s", smtp.statusCode(), smtp.errorCode(), smtp.errorReason().c_str());
    return;
  }

  if (!smtp.isLoggedIn())
  {
    Serial.println("\nNot yet logged in.");
  }
  else
  {
    if (smtp.isAuthenticated())
      Serial.println("\nSuccessfully logged in.");
    else
      Serial.println("\nConnected with no Auth.");
  }
  // **This is where I have tried to insert my code**

  // Time between salt level checks is 15 minutes unless Test switch is set to "Test" position, in which case the time between level checks is 1 second
  testState = digitalRead(testPin); //get the state of the test switch. If set to "Test", testState will be set to LOW
  if (testState == LOW) //i.e. the test switch is set to "Test'
  {
   // Function to read data from the ultrasonic sensor
    float readSensorData() 
  {
    // Trigger a low signal before sending a high signal
    digitalWrite(trigPin, LOW); 
    delayMicroseconds(2);
    // Send a 10-microsecond high signal to the trigPin
    digitalWrite(trigPin, HIGH); 
    delayMicroseconds(10);
    // Return to low signal
    digitalWrite(trigPin, LOW);
  
    // Measure the duration of the high signal on the echoPin
    unsigned long microsecond = pulseIn(echoPin, HIGH);

    // Calculate the distance using the speed of sound (29.00µs per centimeter, and need to divide by 2 because the signal is travelling to AND from the object)
    float distance = microsecond / 29.00 / 2;

    // Return the calculated distance
    return distance;
  } 
    float distance = readSensorData();
      Serial.print(distance);   
      Serial.print(" cm. Test state should be 0 =  ");
      Serial.println(testState);
      if (distance>25) // distance from the ultrasonic unit to the top of the salt level should be 25 cm or less
      {
        digitalWrite(buzzerPin, HIGH);//turn on the buzzer
        delay(1000); //keep the buzzer on for 1 second
        digitalWrite(buzzerPin, LOW); //turn off buzzer 
      } else 
      { delay(1000);
        digitalWrite(buzzerPin, LOW); 
      }
  } else
   {
      // test state must be HIGH - i.e. the Test switch is NOT turned on, so do the normal operation of testing salt level every 15 minutes
      unsigned long currentMillis = millis();
      if (currentMillis - previousMillis >= interval) 
      { // if 15 minutes have elapsed, then...
        // save the last time you tested the salt level
        previousMillis = currentMillis;
        // Read distance from the ultrasonic sensor
        float distance = readSensorData();
        // Print the measured distance to the serial monitor. These print statements are only used to test and debug the programme
        Serial.print(distance);   
        Serial.print(" cm. Test state should be 1. Test state =  ");
        Serial.println(testState); //must print 1 for HIGH
        if (distance>25)
        {
          digitalWrite(buzzerPin, HIGH);//turn on the buzzer
          delay(10000); //keep the buzzer on for 10 seconds
          digitalWrite(buzzerPin, LOW); //turn off buzzer 
           /* Start sending Email and close the session */
           SMTP_Message message;
            if (!MailClient.sendMail(&smtp, &message))
            ESP_MAIL_PRINTF("Error, Status Code: %d, Error Code: %d, Reason: %s", smtp.statusCode(), smtp.errorCode(), smtp.errorReason().c_str());
        } 
        else 
        {
          digitalWrite(buzzerPin, LOW); 
        }
      } else {}
    }
}//end loop
/* Callback function to get the Email sending status */
void smtpCallback(SMTP_Status status){
  /* Print the current status */
  Serial.println(status.info());

  /* Print the sending result */
  if (status.success()){
    // ESP_MAIL_PRINTF used in the examples is for format printing via debug Serial port
    // that works for all supported Arduino platform SDKs e.g. AVR, SAMD, ESP32 and ESP8266.
    // In ESP8266 and ESP32, you can use Serial.printf directly.

    Serial.println("----------------");
    ESP_MAIL_PRINTF("Message sent success: %d\n", status.completedCount());
    ESP_MAIL_PRINTF("Message sent failed: %d\n", status.failedCount());
    Serial.println("----------------\n");

    for (size_t i = 0; i < smtp.sendingResult.size(); i++)
    {
      /* Get the result item */
      SMTP_Result result = smtp.sendingResult.getItem(i);
      
      ESP_MAIL_PRINTF("Message No: %d\n", i + 1);
      ESP_MAIL_PRINTF("Status: %s\n", result.completed ? "success" : "failed");
      ESP_MAIL_PRINTF("Date/Time: %s\n", MailClient.Time.getDateTimeString(result.timestamp, "%B %d, %Y %H:%M:%S").c_str());//month, day of month, Year, Hours
      //Minutes, Seconds
      ESP_MAIL_PRINTF("Recipient: %s\n", result.recipients.c_str());
      ESP_MAIL_PRINTF("Subject: %s\n", result.subject.c_str());
    }
    Serial.println("----------------\n");

    // You need to clear sending result as the memory usage will grow up.
    smtp.sendingResult.clear();
  }
}

Showing us the error would be a good next step, don't you think?

I thought every sketch writer was psychic...

Here it is:

/Users/steveknell/Documents/Personal/ESP32 & Arduino/my code/salt_level_monitor_v3/salt_level_monitor_v3.ino: In function 'void loop()':
/Users/steveknell/Documents/Personal/ESP32 & Arduino/my code/salt_level_monitor_v3/salt_level_monitor_v3.ino:157:3: error: a function-definition is not allowed here before '{' token
157 | {
| ^
/Users/steveknell/Documents/Personal/ESP32 & Arduino/my code/salt_level_monitor_v3/salt_level_monitor_v3.ino:176:22: error: 'readSensorData' was not declared in this scope
176 | float distance = readSensorData();
| ^~~~~~~~~~~~~~
/Users/steveknell/Documents/Personal/ESP32 & Arduino/my code/salt_level_monitor_v3/salt_level_monitor_v3.ino:198:26: error: 'readSensorData' was not declared in this scope
198 | float distance = readSensorData();
| ^~~~~~~~~~~~~~
Multiple libraries were found for "SD.h"
Used: /Users/steveknell/Library/Arduino15/packages/esp32/hardware/esp32/3.0.7/libraries/SD
Not used: /Users/steveknell/Library/Arduino15/libraries/SD
exit status 1

Think about this.

  if (testState == LOW) //i.e. the test switch is set to "Test'
  {
   // Function to read data from the ultrasonic sensor
    float readSensorData() 
  {

And this.

#define SMTP_HOST "smtp.gmail.com

At this point there are too many errors for a sensible discussion.

The key to combining separate programs is to start with working code and go through it line by line, making sure you understand what each line does and why it is needed, in that particular order.

Then add needed lines from the other program, at the proper place, testing and fixing each addition as you go. Step by step!

On this line, you failed to paste the close-quote, causing mayhem.

Sincere thanks for the quick replies!

The missing quote was in the sketch, so I must have somewhow deleted it when pasting into this topic and trying to tidy the sketch to make it easier to read.

I have moved the float readSensorData() { function to be before the loop(), and now it compiles with no errors. The new code is below (in case this is of use to any other forum members), but I have not yet tried it on the board; that's tomorrow's task...

The lines I really want to understand (and can't find any documentation on...) are these two;
SMTP_Message message; Is this the line that actually sends the email??
and this line;
if (!MailClient.sendMail(&smtp, &message))
I read that the & means an address, so &message means the address of where message is. If I could find the definition of these functions that would be great...

Thanks again. I will post an update here once I have uploaded the sketch to the ESP32.

// Define pins for the ultrasonic sensor
const int echoPin = 25;
const int trigPin = 26;
//Define other pins
const int buzzerPin = 27;  // The GPIO pin for the buzzer
const int testPin = 13;    // The GPIO pin for the "Test" switch input. When switched to "Test" position, the "Test" switch connects pin 13 to Ground, giving a LOW state.
// Pin 13 is connected to 3V3 via a 10K Ohm resistor. to prevent pin 13 floating to unkown states when in normal operation.
int testState = 0;                 //initialise the test variable to zero
unsigned long previousMillis = 0;  // will store last count time salt level was tested

const long interval = 900000;  // interval at which to test the salt level (900,000 milliseconds = 15 minutes)

#include <Arduino.h>
#include <WiFi.h>
#include <ESP_Mail_Client.h>

#define WIFI_SSID "*********"  //Elmtree House wifi
#define WIFI_PASSWORD "*******"

/** The smtp host name e.g. smtp.gmail.com for GMail or smtp.office365.com for Outlook or smtp.mail.yahoo.com */
#define SMTP_HOST "smtp.gmail.com"
#define SMTP_PORT 465

/* The sign in credentials */
#define AUTHOR_EMAIL "****@gmail.com"
#define AUTHOR_PASSWORD "**** **** **** ****"

/* Recipient's email*/
#define RECIPIENT_EMAIL "*****@gmail.com"
#define RECIPIENT_CC1 "****@gmail.com"  //add Jill's email as a CC:
/* Declare the global used SMTPSession object for SMTP transport */
SMTPSession smtp;

/* Callback function to get the Email sending status */
void smtpCallback(SMTP_Status status);

void setup() {
  // Begin serial communication at 9600 baud rate
  Serial.begin(9600);
  // initialize digital pin buzzerPin as an output.
  pinMode(buzzerPin, OUTPUT);
  // initialise digital pin trigPin as an output
  pinMode(trigPin, OUTPUT);
  // initialize digital pin echoPin as an input
  pinMode(echoPin, INPUT);
  // initialise digital pin testPin as an input
  pinMode(testPin, INPUT);
}
// Function to read data from the ultrasonic sensor
float readSensorData() {
  // Trigger a low signal before sending a high signal
  digitalWrite(trigPin, LOW);
  delayMicroseconds(2);
  // Send a 10-microsecond high signal to the trigPin
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  // Return to low signal
  digitalWrite(trigPin, LOW);

  // Measure the duration of the high signal on the echoPin
  unsigned long microsecond = pulseIn(echoPin, HIGH);

  // Calculate the distance using the speed of sound (29.00µs per centimeter, and need to divide by 2 because the signal is travelling to AND from the object)
  float distance = microsecond / 29.00 / 2;

  // Return the calculated distance
  return distance;
}
void loop() {
  // This is the part of the programme you need to run continuously.
  Serial.println();
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  Serial.print("Connecting to Wi-Fi");
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(300);
  }
  Serial.println();
  Serial.print("Connected with IP: ");
  Serial.println(WiFi.localIP());
  Serial.println();

  /*  Set the network reconnection option */
  MailClient.networkReconnect(true);

  /** Enable the debug via Serial port
   * 0 for no debugging
   * 1 for basic level debugging
   *
   * Debug port can be changed via ESP_MAIL_DEFAULT_DEBUG_PORT in ESP_Mail_FS.h
   */
  smtp.debug(1);

  /* Set the callback function to get the sending results */
  smtp.callback(smtpCallback);

  /* Declare the Session_Config for user defined session credentials */
  Session_Config config;

  /* Set the session config */
  config.server.host_name = SMTP_HOST;
  config.server.port = SMTP_PORT;
  config.login.email = AUTHOR_EMAIL;
  config.login.password = AUTHOR_PASSWORD;
  config.login.user_domain = "";

  /*
  Set the NTP config time
  For times east of the Prime Meridian use 0-12
  For times west of the Prime Meridian add 12 to the offset.
  Ex. American/Denver GMT would be -6. 6 + 12 = 18
  See https://en.wikipedia.org/wiki/Time_zone for a list of the GMT/UTC timezone offsets
  */
  config.time.ntp_server = F("europe.pool.ntp.org,time.nist.gov");
  config.time.gmt_offset = 0;
  config.time.day_light_offset = 0;

  /* Declare the message class */
  SMTP_Message message;

  /* Set the message headers */
  message.sender.name = F("ESP");
  message.sender.email = AUTHOR_EMAIL;
  message.subject = F("From your Water Softner");
  message.addRecipient(F("Steve"), RECIPIENT_EMAIL);
  message.addCc(RECIPIENT_CC1);  //Jill is on CC:
  /*Send HTML message*/
  /*String htmlMsg = "<div style=\"color:#2f4468;\"><h1>Hello World!</h1><p>- Sent from ESP board</p></div>";
  message.html.content = htmlMsg.c_str();
  message.html.content = htmlMsg.c_str();
  message.text.charSet = "us-ascii";
  message.html.transfer_encoding = Content_Transfer_Encoding::enc_7bit;*/


  //Send raw text message
  String textMsg = "Low salt alarm";  //this is the mesage I want to send
  message.text.content = textMsg.c_str();
  message.text.charSet = "us-ascii";
  message.text.transfer_encoding = Content_Transfer_Encoding::enc_7bit;

  message.priority = esp_mail_smtp_priority::esp_mail_smtp_priority_low;
  message.response.notify = esp_mail_smtp_notify_success | esp_mail_smtp_notify_failure | esp_mail_smtp_notify_delay;


  /* Connect to the server */
  if (!smtp.connect(&config)) {
    ESP_MAIL_PRINTF("Connection error, Status Code: %d, Error Code: %d, Reason: %s", smtp.statusCode(), smtp.errorCode(), smtp.errorReason().c_str());
    return;
  }

  if (!smtp.isLoggedIn()) {
    Serial.println("\nNot yet logged in.");
  } else {
    if (smtp.isAuthenticated())
      Serial.println("\nSuccessfully logged in.");
    else
      Serial.println("\nConnected with no Auth.");
  }
  // Check to see if it's time to test the depth of salt; that is, if the difference between the current time and last time you tested the salt level is bigger than or equal to
  // the interval at which you want to test the salt level.
  // Time between salt level checks is 30 minutes unless Test switch is set to "Test" position, in which case the time between level checks is 1 second
  testState = digitalRead(testPin);  //get the state of the test switch. If set to "Test", testState will be set to LOW
  if (testState == LOW)              //i.e. the test switch is set to "Test'
  {
    float distance = readSensorData();
    Serial.print(distance);
    Serial.print(" cm. Test state should be 0 =  ");
    Serial.println(testState);
    if (distance > 25)  // distance from the ultrasonic unit to the top of the salt level should be 25 cm or less
    {
      digitalWrite(buzzerPin, HIGH);  //turn on the buzzer
      delay(1000);                    //keep the buzzer on for 1 second
      digitalWrite(buzzerPin, LOW);   //turn off buzzer
    } else {
      delay(1000);
      digitalWrite(buzzerPin, LOW);
    }
  } else {
    // test state must be HIGH - i.e. the Test switch is NOT turned on, so do the normal operation of testing salt level every 15 minutes
    unsigned long currentMillis = millis();
    if (currentMillis - previousMillis >= interval) {  // if 15 minutes have elapsed, then...
      // save the last time you tested the salt level
      previousMillis = currentMillis;
      // Read distance from the ultrasonic sensor
      float distance = readSensorData();
      // Print the measured distance to the serial monitor. These print statements are only used to test and debug the programme
      Serial.print(distance);
      Serial.print(" cm. Test state should be 1. Test state =  ");
      Serial.println(testState);  //must print 1 for HIGH
      if (distance > 25) {
        digitalWrite(buzzerPin, HIGH);  //turn on the buzzer
        delay(10000);                   //keep the buzzer on for 10 seconds
        digitalWrite(buzzerPin, LOW);   //turn off buzzer
        /* Start sending Email and close the session */
        SMTP_Message message;
        if (!MailClient.sendMail(&smtp, &message))
          ESP_MAIL_PRINTF("Error, Status Code: %d, Error Code: %d, Reason: %s", smtp.statusCode(), smtp.errorCode(), smtp.errorReason().c_str());
      } else {
        digitalWrite(buzzerPin, LOW);
      }
    } else {
    }
  }
}  //end loop
/* Callback function to get the Email sending status */
void smtpCallback(SMTP_Status status) {
  /* Print the current status */
  Serial.println(status.info());

  /* Print the sending result */
  if (status.success()) {
    // ESP_MAIL_PRINTF used in the examples is for format printing via debug Serial port
    // that works for all supported Arduino platform SDKs e.g. AVR, SAMD, ESP32 and ESP8266.
    // In ESP8266 and ESP32, you can use Serial.printf directly.

    Serial.println("----------------");
    ESP_MAIL_PRINTF("Message sent success: %d\n", status.completedCount());
    ESP_MAIL_PRINTF("Message sent failed: %d\n", status.failedCount());
    Serial.println("----------------\n");

    for (size_t i = 0; i < smtp.sendingResult.size(); i++) {
      /* Get the result item */
      SMTP_Result result = smtp.sendingResult.getItem(i);

      // In case, ESP32, ESP8266 and SAMD device, the timestamp get from result.timestamp should be valid if
      // your device time was synched with NTP server.
      // Other devices may show invalid timestamp as the device time was not set i.e. it will show Jan 1, 1970.
      // You can call smtp.setSystemTime(xxx) to set device time manually. Where xxx is timestamp (seconds since Jan 1, 1970)

      ESP_MAIL_PRINTF("Message No: %d\n", i + 1);
      ESP_MAIL_PRINTF("Status: %s\n", result.completed ? "success" : "failed");
      ESP_MAIL_PRINTF("Date/Time: %s\n", MailClient.Time.getDateTimeString(result.timestamp, "%B %d, %Y %H:%M:%S").c_str());  //month, day of month, Year, Hours
      //Minutes, Seconds
      ESP_MAIL_PRINTF("Recipient: %s\n", result.recipients.c_str());
      ESP_MAIL_PRINTF("Subject: %s\n", result.subject.c_str());
    }
    Serial.println("----------------\n");

    // You need to clear sending result as the memory usage will grow up.
    smtp.sendingResult.clear();
  }
}

SMTP_Message is a type. message is an instance of that type.

I've used SMTP mail on ESP32, but not using the same library as you. The above line looks to be the one that causes the mail to be sent.

Have you looked at ESP_Mail_Client.h? I Googled for it, and got this link. Perhaps there is some information there that will help your understanding.

Thank you Dave. From looking through the info' in the link you sent, certainly that is the line that sends the email, but there is no explanatory documentation there that says what those functions do and how they work. At least not that I could see.....

I downloaded the sketch this morning and in Test mode it works; though there is a longer delay between each one second buzzer burst than before. I think this is because the loop is running all the mail set up code every time, rather than just continually sensing the level of the salt and buzzing if it's too low and doing nothing else (which is what I want the Test mode to do). Running all the mail setup code repeatedly in a continuous loop creates a second problem in that the wifi and email connectivity is failing with an ERROR message saying there are too many login attempts, which makes sense.

So I guess I need to modify the code so that the mail set up piece is only run once, and the Test mode just runs in a tight "local" loop without going back to the start of the main loop().

Thanks again for your support!
Regards, Steve

Did you look at this file?

That sounds about right.
I imagine you would want to limit the number of e-mails to one per some time. For example, you wouldn't want it to send e-mails as quickly as it can whilst the buzzer is active.
Something like this pseudo code in loop():

If (buzzer is active now, and it wasn't active last time we checked)
&& (time since last e-mail sent > min time between e-mails)
{
do e-mail setup // note 1
send e-mail
}

Note 1: Some of the e-mail setup could perhaps be done in setup(), i.e. the stuff that only needs doing once. In the above pseudo code, just do the e-mail setup that only needs doing every time an e-mail is sent.

Edit added: When I wrote "buzzer is active now, and it wasn't active last time we checked" I don't mean the one second buzzer burst. I mean if the condition that causes the buzzer bursts has changed from 'don't do buzzer bursts' to 'do buzzer bursts'

Thanks Dave: there is a lot in that README file, but at least I now have a reference document to go to.

In terms of structure of the sketch: I'm thinking that I should do the TEST piece right at the start and use perhaps something like a WHILE LOOP statement:

void loop()
WHILE (Test switch is ON);
DO this, (check the level of salt and sound the buzzer if it's too low),
ELSE check the salt level every 15 minutes;
IF (salt level is low);
run the mail/wifi code; // or rather only the code that doesn't need to go into setup()
send a "salt level is low" email;
disconnect from wifi and mail;
ELSE
return to start of the main loop()

This will ensure that a mail can only be sent every 15 minutes at most (and only if the salt level is low of course, which should only happen roughly every 30 days or so).

What I'm unsure about is what parts of the Mail and WiFi code could be put before the main loop(), i.e. in the Setup section of the sketch. This is my first foray into coding of any sort for more than 40 years, so forgive my ignorance...

Thanks again for the support.

I can't help you with that without studying the library and the examples. Unfortunately the quick look I had at the send text example does everything in setup() so it's not immediately obvious which parts only need to be done once in setup().
My ESP32 e-mail sending project is in my loft / attic. From an e-mail point of view it sends me an e-mail when the door closes on a humane rodent trap. It also sends an "everything is working" e-mail once a day, so that I notice if it's stopped working.
I have the WiFi permanently connected. I need that because another function of that ESP32 is to provide a web server which lets me control LED lighting strips for different regions of the area.
Perhaps you could leave the WiFi permanently connected? That would save some setup for e-mail in loop().

I have modified the structure so that it checks the Test state first, and just loops taking measurements, and buzzing if necessary (i.e. salt is low), if the Test switch is ON.

If the Test switch is OFF, it then checks the salt level every 15 minutes and turns on the buzzer for ten seconds if it is too low AND does all the wifi and email stuff, finally sending the "Salt is low" email.

To my astonishment, it's compiled OK and I've tested this out and it appears to be working fine.

It's not graceful; if anyone wants to use this in a similar project and clean the sketch up, then please do - just let me know so I can copy your improvements! In particular, I'd like to put parts of the WiFi and Email related code in the setup(), but I just don't know which bits can go there and which need to be in the loop().

My thanks to the responders that helped me out of the ditch...

// Version 4 created 21st January 2025 by S. Knell
// Define pins for the ultrasonic sensor
const int echoPin = 25;
const int trigPin = 26;
const int buzzerPin = 27;  // The GPIO pin for the buzzer
const int testPin = 13;    // The GPIO pin for the "Test" switch input. When switched to "Test" position, the "Test" switch connects pin 13 to Ground, giving a LOW state.
// Pin 13 is connected to 3V3 via a 10K Ohm resistor. to prevent pin 13 floating to unkown states when in normal operation.
int testState = 0;                 //initialise the Test variable to zero
unsigned long previousMillis = 0;  // will store last count time salt level was tested
const long interval = 900000;  // interval at which to test the salt level (900,000 milliseconds = 15 minutes)

#include <Arduino.h>
#include <WiFi.h>
#include <ESP_Mail_Client.h>

#define WIFI_SSID "*********"  //Elmtree House wifi SSID
#define WIFI_PASSWORD "******"

/** The smtp host name e.g. smtp.gmail.com for GMail or smtp.office365.com for Outlook or smtp.mail.yahoo.com */
#define SMTP_HOST "smtp.gmail.com"
#define SMTP_PORT 465

/* The sign in credentials */
#define AUTHOR_EMAIL "*****@gmail.com"
#define AUTHOR_PASSWORD "**** **** **** ****"

/* Recipient's email*/
#define RECIPIENT_EMAIL "*****@gmail.com"
#define RECIPIENT_CC1 "*****@gmail.com"  //add Jill's email as a CC:
/* Declare the global used SMTPSession object for SMTP transport */
SMTPSession smtp;

/* Callback function to get the Email sending status */
void smtpCallback(SMTP_Status status);

void setup() 
{
  // Begin serial communication at 9600 baud rate
  Serial.begin(9600);
  // initialize digital pin buzzerPin as an output.
  pinMode(buzzerPin, OUTPUT);
  // initialise digital pin trigPin as an output
  pinMode(trigPin, OUTPUT);
  // initialize digital pin echoPin as an input
  pinMode(echoPin, INPUT);
  // initialise digital pin testPin as an input
  pinMode(testPin, INPUT);
}
 // Function to read data from the ultrasonic sensor
 float readSensorData() {
  // Trigger a low signal before sending a high signal
  digitalWrite(trigPin, LOW);
  delayMicroseconds(2);
  // Send a 10-microsecond high signal to the trigPin
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  // Return to low signal
  digitalWrite(trigPin, LOW);

  // Measure the duration of the high signal on the echoPin
  unsigned long microsecond = pulseIn(echoPin, HIGH);

  // Calculate the distance using the speed of sound (29.00µs per centimeter, and need to divide by 2 because the signal is travelling to AND from the object)
  float distance = microsecond / 29.00 / 2;

  // Return the calculated distance
  return distance;
}
void loop() 
{
  // This 'while loop' checks to see if the Test switch is ON; if it is, then just loop around indefinitely until the Test switch is turned off.
  while (digitalRead(testPin) == LOW) 
  {
    testState = digitalRead(testPin);  //testState is for debugging
    float distance = readSensorData();
    Serial.print(distance);
    Serial.print(" cm. Test state should be 0 =  ");
    Serial.println(testState);
    if (distance > 25)  // distance from the ultrasonic unit to the top of the salt level should be 25 cm or less
    {
      digitalWrite(buzzerPin, HIGH);  //turn on the buzzer
      delay(1000);                    //keep the buzzer on for 1 second
      digitalWrite(buzzerPin, LOW);   //turn off buzzer
    } else {
      delay(1000);
      digitalWrite(buzzerPin, LOW);
    }
  }
  // if the code gets to this point, it means the Test switch is OFF, so need to do a salt level check every 15 minutes and send a mail and sound the buzzer if it is low
  // Check to see if it's time to test the depth of salt; that is, if the difference between the current time and last time you tested the salt level is bigger than or equal to
  // the interval at which you want to test the salt level.
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval)  // this checks to see if more than 15 minutes have elapsed
  {                                                
    previousMillis = currentMillis; // if 15 minutes have elapsed, then save the last time you tested the salt level
    // Read distance from the ultrasonic sensor
    float distance = readSensorData();
    testState = digitalRead(testPin); 
// Print the measured distance to the serial monitor. These print statements are only used to test and debug the programme
    Serial.print(distance);
    Serial.print(" cm. Test state should be 1. Test state =  ");
    Serial.println(testState);        //must print 1 for HIGH if we get to this point, otherwise something is wrong!
    if (distance > 25) 
    {              // now need to turn on the buzzer for ten seconds and send an email
      digitalWrite(buzzerPin, HIGH);  //turn on the buzzer
      delay(10000);                   //keep the buzzer on for 10 seconds
      digitalWrite(buzzerPin, LOW);   //turn off buzzer
      // put all the mail and wifi code here so that it only runs when there is a need to send an email
      Serial.println();
      WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
      Serial.print("Connecting to Wi-Fi");
      while (WiFi.status() != WL_CONNECTED) 
      {
        Serial.print(".");
        delay(300);
      }
      Serial.println();
      Serial.print("Connected with IP: ");
      Serial.println(WiFi.localIP());
      Serial.println();

      /*  Set the network reconnection option */
      MailClient.networkReconnect(true);

      /** Enable the debug via Serial port
        * 0 for no debugging
        * 1 for basic level debugging
        *
        * Debug port can be changed via ESP_MAIL_DEFAULT_DEBUG_PORT in ESP_Mail_FS.h
        */
      smtp.debug(1);

      /* Set the callback function to get the sending results */
      smtp.callback(smtpCallback);

      /* Declare the Session_Config for user defined session credentials */
      Session_Config config;

      /* Set the session config */
      config.server.host_name = SMTP_HOST;
      config.server.port = SMTP_PORT;
      config.login.email = AUTHOR_EMAIL;
      config.login.password = AUTHOR_PASSWORD;
      config.login.user_domain = "";

      /*
        Set the NTP config time
        For times east of the Prime Meridian use 0-12
        For times west of the Prime Meridian add 12 to the offset.
        Ex. American/Denver GMT would be -6. 6 + 12 = 18
        See https://en.wikipedia.org/wiki/Time_zone for a list of the GMT/UTC timezone offsets
        */
      config.time.ntp_server = F("europe.pool.ntp.org,time.nist.gov");
      config.time.gmt_offset = 0;
      config.time.day_light_offset = 0;

      /* Declare the message class */
      SMTP_Message message;

      /* Set the message headers */
      message.sender.name = F("ESP");
      message.sender.email = AUTHOR_EMAIL;
      message.subject = F("From your Water Softner");
      message.addRecipient(F("Steve"), RECIPIENT_EMAIL);
      message.addCc(RECIPIENT_CC1);  //Jill is on CC:

      //Set the raw text message
      String textMsg = "Low salt alarm";  //this is the mesage I want to send
      message.text.content = textMsg.c_str();
      message.text.charSet = "us-ascii";
      message.text.transfer_encoding = Content_Transfer_Encoding::enc_7bit;

      message.priority = esp_mail_smtp_priority::esp_mail_smtp_priority_low;
      message.response.notify = esp_mail_smtp_notify_success | esp_mail_smtp_notify_failure | esp_mail_smtp_notify_delay;

      /* Connect to the server */
      if (!smtp.connect(&config)) {
        ESP_MAIL_PRINTF("Connection error, Status Code: %d, Error Code: %d, Reason: %s", smtp.statusCode(), smtp.errorCode(), smtp.errorReason().c_str());
        return;
      }

      if (!smtp.isLoggedIn()) {
        Serial.println("\nNot yet logged in.");
      } else {
        if (smtp.isAuthenticated())
          Serial.println("\nSuccessfully logged in.");
        else
          Serial.println("\nConnected with no Auth.");
      }
      /* having done all of the above to set up the wifi and email stuff, start sending Email and close the session */
      // SMTP_Message message; defined above at line 157
      if (!MailClient.sendMail(&smtp, &message))
        ESP_MAIL_PRINTF("Error, Status Code: %d, Error Code: %d, Reason: %s", smtp.statusCode(), smtp.errorCode(), smtp.errorReason().c_str());
    } else 
    { 
    }
  } 
}  //end loop
   /* Callback function to get the Email sending status */
   void smtpCallback(SMTP_Status status) 
  {
    /* Print the current status */
    Serial.println(status.info());
    /* Print the sending result */
    if (status.success()) 
    {
      // ESP_MAIL_PRINTF used in the examples is for format printing via debug Serial port
      // that works for all supported Arduino platform SDKs e.g. AVR, SAMD, ESP32 and ESP8266.
      // In ESP8266 and ESP32, you can use Serial.printf directly.

      Serial.println("----------------");
      ESP_MAIL_PRINTF("Message sent success: %d\n", status.completedCount());
      ESP_MAIL_PRINTF("Message sent failed: %d\n", status.failedCount());
      Serial.println("----------------\n");

     for (size_t i = 0; i < smtp.sendingResult.size(); i++) 
     {
       /* Get the result item */
       SMTP_Result result = smtp.sendingResult.getItem(i);
       // In case, ESP32, ESP8266 and SAMD device, the timestamp get from result.timestamp should be valid if
       // your device time was synched with NTP server.
       // Other devices may show invalid timestamp as the device time was not set i.e. it will show Jan 1, 1970.
       // You can call smtp.setSystemTime(xxx) to set device time manually. Where xxx is timestamp (seconds since Jan 1, 1970)
       ESP_MAIL_PRINTF("Message No: %d\n", i + 1);
       ESP_MAIL_PRINTF("Status: %s\n", result.completed ? "success" : "failed");
       ESP_MAIL_PRINTF("Date/Time: %s\n", MailClient.Time.getDateTimeString(result.timestamp, "%B %d, %Y %H:%M:%S").c_str());  //month, day of month, Year, Hours
       //Minutes, Seconds
       ESP_MAIL_PRINTF("Recipient: %s\n", result.recipients.c_str());
       ESP_MAIL_PRINTF("Subject: %s\n", result.subject.c_str());
     }
     Serial.println("----------------\n");
     // You need to clear sending result as the memory usage will grow up.
     smtp.sendingResult.clear();
    }
  }

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.