Pedro52
Published © GPL3+

Smart Fish Feeder

This solar powered Smart Fish Feeder automatically feeds small pond fish. After every feeding the system sends an email to its owner.

AdvancedFull instructions providedOver 1 day2,069
Smart Fish Feeder

Things used in this project

Hardware components

Plastic box 32 x 21 x 23 cm
×1
Plastic cutting board, 34x24 cm
×1
NodeMCU ESP32
×1
Switch on/off
×1
fuse 2A F
×1
Solar panel with 18650 Li_Ion battery
×1
18650 Battery Charge Shield
×1
Ceramic Disc Capacitor, 0.1 µF
Ceramic Disc Capacitor, 0.1 µF
×1
Capacitor 10 µF
Capacitor 10 µF
×1
Step-up Convertor 3.7V DC to 5V DC
×1
IR receiver (generic)
IR RX Black
×1
IR transmitter (generic)
IR TX white
×1
LED-5MM blue
×1
Reed Switch, SPST-NO
Reed Switch, SPST-NO
×2
Stepper Motor 28BYJ-48
×1
ULN2003 Stepper Motor Controller
×1
Resistor 100 ohm
Resistor 100 ohm
×2
Resistor 330 ohm
Resistor 330 ohm
×1
Resistor 10k ohm
Resistor 10k ohm
×1
Through Hole Resistor, 180 kohm
Through Hole Resistor, 180 kohm
×2
Through Hole Resistor, 220 kohm
Through Hole Resistor, 220 kohm
×1
Through Hole Resistor, 330 kohm
Through Hole Resistor, 330 kohm
×1
PVC pipe
outer diameter 37 mm inner diameter 35 mm) length: depending on applied box approx. 65 to 70 mm
×1

Software apps and online services

Arduino IDE
Arduino IDE
Fusion
Autodesk Fusion
Ultimaker CURA
ClickCharts Diagram and Flowchart
EasyEDA

Hand tools and fabrication machines

Common tools & test equipment
3D Printer (generic)
3D Printer (generic)

Story

Read more

Custom parts and enclosures

Food Silo Mk5 pipe 37.3 v1

Used for storing fish food

The Worm

for propelling the fish food

support for Stepper Motor

a support structure for the Stepper Motor

tool for filling the food silo

a handy funnel that helps to fill the food silo without spilling food

ring for the food silo

a ring to be put on top of the food silo

outlet of the food silo

an outlet pipe that helps to nicely drop the food in the water

closing end for the PVC pipe

a closing end for the PVC pipe including centre hole (note that the PVC pipe has an outer dimension of 37 mm and inner dimension of 35 mm)

connector to the stepper motor axis

a connector for connecting the worm to the stepper motor driving axis.

Schematics

Electrical diagram

See Story

Code

Complete_Fish_Feeder_rev_12_Publ

Arduino
The complete Arduino code for the Fish Feeder to run on an ESP32
/*
  This code for an Automatic Fish Feeder system has been developed and produced by Pierre Pennings (July-August 2021)
  This application can be used for automatically feeding outdoor pond fish every 24 hours or a different interval time.
  The intervaltime can be modified in this sketch by modifiying the TimeToSleep (e.g. for testing this could be set to 300 = 5 minutes)
  For this Project, an ESP 32 (NodeMCU) is used (it has WiFi and Bluetooth). The Wifi is used in this skecth for getting Time and Date from an NTP server
  and also for sending an E-mail message to inform the user about the status of the food storage, the time of feeding and the battery status
  The ESP 32 device works at 3.3 Volt levels, while the Stepper Motor runs on 5 V
  The ESP 32 is fed with 5 V power. A solar panel is used to charge a 18650 battery in daytime, the energy from this solar battery is transferred to another 18650 battery
  via a step-up converter (from 3,7 to 5 V). The BMS of the 18650 battery takes care of the proper charging. The the 18650 powerbank provides 5V output and 3.3V by its on-board voltage regulators
  The system uses the deep sleep mode of the ESP32 for effective power saving 
  In the deep sleep mode CPUs, most of the RAM,and all the digital peripherals are powered off.
  The only parts of that will still be powered on are: RTC controller, RTC peripherals and RTC memories  
  To wake up from deep sleep there are 3 possibilities:
    1) a timer to wake up; a counter stores the number of wake ups (Boots) in the RTC memory
    2) a trigger for manual feeding by activating a reed relais with a magnet
    3) a hard reset either by power disconnect or by means of a magnet to activate a reed relais connected to the RST pin of the ESP32
  In case of 1), the system will:
                     * feed the fish every 24 hours;
                     * it will check using the IR sensor if there is still food in the silo available: 
                                    - if not, it will send an email and go to deep sleep
                                    - if there is still food available the Steppermotor will make a few turns to release the fishfood
                     * after feeding, the system will check the food avalabilty again and send an email with relevant information and go to deep sleep for 24 hours
  In case of 2), the system will:
                    * feed the fish a little bit for testing and demonstration purposes
                    * it will check, using the IR sensor, if there is still food in the silo available: 
                                    - if not, it will send an email stating that the food is finished and go to deep sleep
                                    - if there is still food available the Steppermotor will turn to provide 1 dose of food.
                    * after feeding, the system will check the food avalabilty again and send an email with relevant information and go to deep sleep for 24 hours
  In case of 3), the system will reset the boot counter to 0 and the next 24 hour period will be counted from the moment of reset
  In all 3 cases the charging status of the 18650 battery will be measured and the result included in the email message.
  The code for sending E Mails has been derived from the nice tutorial of Rui Santos: https://randomnerdtutorials.com/esp32-send-email-smtp-server-arduino-ide/
  The total code in this sketch is licensed under GPL3+ license.
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
  DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  This clause is required to be included by the author of the ESP_Mail_Client.h library
*/
//////////////////////Libraries for stepper motor and EEPROM
#include <AccelStepper.h>             // for the Stepper motor
#define FULL4WIRE 4

#include <Preferences.h>              // for storing data in EEPROM before entering in Deep Sleep
Preferences prom ;

//////////////////////Libraries and settings for the WiFi connection and EMail
#include <Arduino.h>
#if defined(ESP32)
  #include <WiFi.h>                   // for the WiFi connection in case of an ESP32 as used in this program
#elif defined(ESP8266)
  #include <ESP8266WiFi.h>            // for the WiFi connection in case of an ESP8266 (not tested with this program)
#endif
#include <ESP_Mail_Client.h>          // should be downloaded from https://github.com/mobizt/ESP-Mail-Client and installed

#define WIFI_SSID "your own SSID"               // fill in your own WiFi ssid 
#define WIFI_PASSWORD "your own password"       // fill in your own password

#define SMTP_HOST "mail.smtp2go.com"            // Replace with data of your own SMTP server credentials
#define SMTP_PORT 465

#define AUTHOR_EMAIL "person@email.com"         // put here your own login credentials for the SMTP host
#define AUTHOR_PASSWORD "your smtp password"

#define RECIPIENT_EMAIL "someone@gmail.com"     // email adres of the recipient of emails from the Fish Feeder (email of owner of the Automatic Fish Feeder)

SMTPSession smtp;                               // The SMTP Session object used for Email sending; contains config and data to send

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

//////////////////////Libraries to get time from NTP Server
#include <NTPClient.h>

WiFiUDP ntpUDP;                   
NTPClient timeClient(ntpUDP, "nl.pool.ntp.org", 3600, 60000);   // Define NTP Client to get time from an NTP server (replace nl with the acronym of your own country; check https://www.ntppool.org/ for details)

//////////////////////data to set up the deepsleep application
uint64_t TimeToSleep = 86400;                          // The time that ESP32 will sleep (in seconds); a period of 24 Hours equals 24*3600s= 86400 sec (for testing use e.g. 300 i.e. 5 minutes)
uint64_t WakeUp_microsecs = TimeToSleep * 1000000;     // Conversion from seconds to micro seconds with a factor 1000000
float HoursToSleep = TimeToSleep/3600.0 ;              // normally 24 hours
int SleepCorrect ;                                     // correction to compensate for deviations of RTC (in microseconds) from elapsed time taken from NTP server

//////////////////////Variable for boot count
RTC_DATA_ATTR int bootCount = 0;    // the boot counter will be reset to 0 only after a hard RESET

//////////////////////initialise GPIO pins/////////////////////////////////
const int BlueWiFiPin = 13;     // GPIO Pin 13 of ESP32 connected to BlueLED for showing the Wifi Status
const int BatteryPin = 34;      // GPIO Pin 34 of ESP32 connected to the + pole of the Lipo battery (3.7 V nominal )

const int  motorPin1 = 18 ;     // IN1 on the ULN2003 driver (Blue)       motor pin connections on ESP32 NodeMCU
const int  motorPin2 = 19 ;     // IN2 on the ULN2003 driver (Pink)
const int  motorPin3 = 12 ;     // IN3 on the ULN2003 driver (Yellow)
const int  motorPin4 = 14 ;     // IN4 on the ULN2003 driver (Orange)

const int IR_TX_Pin = 4;        // GPIO04 is connected to the (white) transmitting IR LED 
const int IR_RX_Pin = 15;       // GPIO15 is connected to the (black) receiving IR LED 

const int Freq = 3800;          // constants to set the parameters for the PWM signal for the IR TX LED
const int Channel = 3;
const int Bits = 10;

//////////////////////Variables for food
int numPortions = 3 ;                       // variable for the number of food portions; value depending on wakeup_reason in the sketch
bool Food_State = 0;                        // variable to store the status of the food (0 = food available, 1 = food finished)

//////////////////////Variables for Battery capacity
int Battery = 0;                  // measured with an ADC via a voltage divider (resistors of 2 *180 kOhm paralel and 330 kOhm)(orange) connected to the + of the Lipo battery 
int BattPerc = 100 ;              // 4.2 V equals 100% equivalent to 3.3 V at the input of the ADC at GPIO pin 34

//////////////////////Variables to save Day and Time
String NTPTime;
String NTPDateTime;
String EpochTime;
String DayOfWeek;               // Sunday = 0 , Monday = 1, 
int CurrentDayOfWeek = 0 ;      // Sunday = 0 , Monday = 1, ... Saturday = 6
char *Day[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
unsigned long DeltaTime = 0;    // variable to hold the time lapsed between two wake up moments    
float DeltaHours = 0.0;

//////////////////////Variable for sending EMail, string will be modified during execution of program
String textMsg = " Empty .";        // note: the dot is important, it indicates the end of the message

//////////////////////general initialisations
AccelStepper stepper(FULL4WIRE, motorPin1, motorPin3, motorPin2, motorPin4);    // Initialize with pin sequence IN1-IN3-IN2-IN4 for using the AccelStepper with 28BYJ-48

//////////////////////in this SETUP function all the code will be executed once and then the system returns to deep sleep////////////////////////////////////////////////
void setup(){
  Serial.begin(115200);               // used to initialise the serial USB port to communicate with the PC at data rate of 115200 Baud (bits/sec)
  delay(1000);                        // some time to open up the Serial Monitor
  Serial.println();                   // one empty line on the Serial monitor
  pinMode (BlueWiFiPin, OUTPUT);      // Set the BlueWiFiPin as output (blue Led connected to GPIO 13 via 330 Ohm resistor)
  pinMode(IR_TX_Pin, OUTPUT);         // set IR_TX_Pin as output (white IR Led)
  pinMode(IR_RX_Pin, INPUT);          // set IR_RX_Pin as input (black IR Led)
  ledcSetup(Channel, Freq, Bits);     // PWM controls for the IR_TX LED
  ledcAttachPin(IR_TX_Pin, Channel);  // connect the IR_TX_Pin with PWM channel 
  ledcWrite(Channel, 512);            // starts sending PWM signal to the IR_TX LED at a duty cycle of 50%  (512 is half of 1024)
  
  ++bootCount;                        // Increment boot number by 1 and print it every reboot
  Serial.println("Smart Fish Feeder started with Boot number: " + String(bootCount));

  stepper.setMaxSpeed(1000.0);        // Sets the maximum permitted speed. The run() function will accelerate up to the speed set by this function.
                                      // Caution: the maximum speed achievable depends on your processor and clock speed.
  stepper.setAcceleration (40);
  stepper.move(10);                   // found necessary
  stepper.setSpeed(900);              // Sets the desired constant speed for use with runSpeed().
                                      // Parameters (in) speed set the desired constant speed in steps per second.
                                      // positive is clockwise. Speeds of more than 1000 steps per second are unreliable.
                                      // Very slow speeds may be set (eg 0.00027777 for once per hour, approximately.
                                      // Speed accuracy depends on the Arduino crystal.
                                      // Jitter depends on how frequently you call the runSpeed() function.

  prom.begin("FeedingData", false);   // Create a namespace in the EEPROM called "FeedingData" (max 15 chars) in the RW-mode (second parameter: false)
                                             
//////////////////////Connecting to WiFi
  Serial.print("Connecting to Acces Point: "); Serial.println(WIFI_SSID);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);                                         // Connect to Wi-Fi network with WIFI_SSID and WIFI_PASSWORD
  while ( WiFi.status() != WL_CONNECTED ) { digitalWrite(BlueWiFiPin, HIGH); Serial.print ( "." ); delay (200); digitalWrite(BlueWiFiPin, LOW); delay (200);}
  Serial.println(); Serial.print("WiFi connected to IP address:  "); Serial.println(WiFi.localIP()); 
  digitalWrite(BlueWiFiPin, HIGH);                                              // sets the blue LED on, a flashing blue Led indicates: trying to connect WiFi; blue Led will keep blinking until there is a WiFi connection
  
//////////////////////Getting Time information from NTP Server
  timeClient.begin();
  timeClient.setTimeOffset(7200);         // Set offset time in seconds to adjust for your timezone, for example:
                                          // GMT +1 = 3600; GMT +2 = 7200; GMT +5:30 = 19800; GMT -1 = -3600; GMT 0 = 0;    must be adjusted for daylight saving time changes
  timeClient.update();
  NTPDateTime = timeClient.getFormattedDate();
  Serial.print (" NTPDateTime = "); Serial.println(NTPDateTime);
  NTPTime = timeClient.getFormattedTime();
  Serial.print (" NTPTime = "); Serial.println(NTPTime); 
  DayOfWeek = timeClient.getDay();
  CurrentDayOfWeek = DayOfWeek.toInt();
  Serial.print (" DayOfWeek = "); Serial.print(DayOfWeek); Serial.print (" Today is: "); Serial.println(Day[CurrentDayOfWeek]);
  unsigned long PrevEpochTime = prom.getULong("PrevEpochTime", false);
  Serial.print (" PrevEpochTime = "); Serial.println(PrevEpochTime);
  EpochTime = timeClient.getEpochTime();              // number of seconds lapsed since Jan. 1, 1970; returns the Unix epoch, which are the seconds elapsed since 00:00:00 UTC on 1 January 1970 (leap seconds are ignored)
  unsigned long CurrentEpochTime = EpochTime.toInt();
  Serial.print (" CurrentEpochTime = "); Serial.println(CurrentEpochTime);
  DeltaTime = CurrentEpochTime - PrevEpochTime;       // the time lapsed (in seconds) between two wakeup moments
  DeltaHours = DeltaTime/3600;                        // the time lapsed (in hours) between two wakeup moments
  Serial.print (" DeltaTime = "); Serial.print(DeltaTime); Serial.println (" seconds ");
  Serial.print (" DeltaHours = "); Serial.print(DeltaHours); Serial.println (" Hours "); Serial.println();
  prom.putULong("PrevEpochTime", CurrentEpochTime);   // modifies the previous EpochTime stored in EEPROM flash memory thanks to the Prefences.h library

  Battery = analogRead(BatteryPin);  // Getting the Battery voltage from the + pole of the 18650 battery by using a 12 bits ADC input of the ESP32 (GPIO34) via a resistor divider network
                                     // 4095 represents 100% charged, equivalent to 3.3 V at the input of the ADC at GPIO pin 34; reached when the Li-Ion battery is at 4.2 V
  
  if (Battery <= 2925) {BattPerc = 0 ; }      // the following code is used for approximating the theoretical State of Charge curve of a Li-Ion battery      
  else if (Battery >= 4090) {BattPerc = 100 ; } 
  else if (Battery >= 3871 && Battery < 4090 ) {BattPerc = map ( Battery, 3871, 4090, 80 , 100) ; }         // green segment
  else if (Battery >= 3607 && Battery < 3871 ) {BattPerc = map ( Battery, 3607, 3871, 20 , 79 ) ; }         // yellow segment
  else if (Battery > 2925 && Battery < 3607 )  {BattPerc = map ( Battery, 2925, 3607,  0 , 19 ) ; }         // red segment

  Serial.println (); Serial.print (" Battery = "); Serial.print(Battery); Serial.print ("   BattPerc  = "); Serial.println(BattPerc);

  WAKEUP_ACTION ();                       //calls the Function WAKEUP_ACTION () to take actions depending on the wakeup reason for ESP32

  SEND_EMAIL ();                          //calls the Function SEND_EMAIL to send the applicable textMsg

  SleepCorrect = prom.getInt("SleepCorrect", false);
  WakeUp_microsecs = WakeUp_microsecs + (SleepCorrect*1000000) ; 
  Serial.println("   SleepCorrection = " + String(SleepCorrect)); 
  
  esp_sleep_enable_timer_wakeup(WakeUp_microsecs);        // configure the wake up source and set wake up time
  esp_sleep_enable_ext0_wakeup(GPIO_NUM_36, 0);           // Normally High, will become 0 = Low when the reed relais (NO) connected to GPIO pin 36 is closed 
  
  Serial.print("   Setup ESP32 to sleep for the next "); Serial.print (HoursToSleep);  Serial.println (" Hours"); 
  Serial.println("Going to sleep now");  
  WiFi.disconnect();                  // Disconnect the Wifi
  delay(500);
  digitalWrite(BlueWiFiPin, LOW);     // Switch off the blue LED 
  prom.end();                         // Close the prom
  Serial.flush(); 
  esp_deep_sleep_start();             // turns off the power to most all peripherals which go in deep sleep, except the RTC controller, RTC peripherals and RTC memories
  Serial.println("Nice Job");         // This will never be printed, because the system has gone into deep sleep
                                      // after wake up the execution will continue from the beginning of the SETUP function, for one of 3 possibilities as stated in the introduction 
}

//////////The loop function will not be called in this programme
void loop(){
  // empty
}

//////////////////////////////// Hereafter specific functions are defined that will be called from within the setup () //////////////////////////////////////////////////////

//////////////////////Function to determine the reason that caused the system to wake up from deep sleep and take appropriate action
void WAKEUP_ACTION (){
  
//////////////////////preparing the various textMsg strings, each message will be ending with "."
  String textMsgNoFoodForAutomaticFishFeeding = "The fish could not be fed automatically today " +  String (Day[CurrentDayOfWeek]) + ", at " + NTPTime + "\r\n" + " The Time lapsed since last feeding time is: " + String(DeltaHours) + " Hours" + ", The food is finished, please refill " + "\r\n" + "Battery SoC = " + String (BattPerc) + " %" + " , Boot number: " + String(bootCount) + "\r\n" + " The SleepCorrection in seconds is: " + String(SleepCorrect) + "\r\n" + "-- Best Regards from FishFeeder-- ." + "\r\n";     // text message in case automatic feeding is finished and no more food available  
  String textMsgFishFed = "The fish have been fed automatically today " +  String (Day[CurrentDayOfWeek]) + ", at " + NTPTime + "\r\n" + "The time lapsed since last feeding time is: " + String(DeltaHours) + " Hours" + ", There is still food available " + "\r\n" + "Battery SoC = " + String (BattPerc) + " %" + " , Boot number: " + String(bootCount) + "\r\n" + " The SleepCorrection in seconds is: " + String(SleepCorrect) + "\r\n" + "-- Best Regards from FishFeeder-- ." + "\r\n";                                     // text message in case automatic feeding is finished and still food available
  String textMsgAutomaticFishFedNoMoreFood = "The fish have been fed automatically today " +  String (Day[CurrentDayOfWeek]) + ", at " + NTPTime + "\r\n" + " The time lapsed since last feeding time is: " + String(DeltaHours) + " Hours" + ", But now the food is finished, please refill " + "\r\n" + "Battery SoC = " + String (BattPerc) + " %" + " , Boot number: " + String(bootCount) + "\r\n" + + " The SleepCorrection in seconds is: " + String(SleepCorrect) + "\r\n" "-- Best Regards from FishFeeder-- ." + "\r\n";   // text message in case automatic feeding is finished and no more food available
  String textMsgNoFoodForManualFishFeeding = "The fish could not be fed manually today " +  String (Day[CurrentDayOfWeek]) + ", at " + NTPTime + "\r\n" + "The food is finished, please refill" + "\r\n" + "The time lapsed since last activity is: " + String(DeltaHours) + " Hours" + "\r\n" + "Battery SoC = " + String (BattPerc) + " %" + " , Boot number: " + String(bootCount) + "\r\n" + "-- Best Regards from FishFeeder-- ." + "\r\n";            // text message in case manual feeding not possible because there is no food available
  String textMsgManualFishFeeding = "The fish have been fed manually today " +  String (Day[CurrentDayOfWeek]) + ", at " + NTPTime + "\r\n" + "there is still food available " + "\r\n" + "The time lapsed since last activity is: " + String(DeltaHours) + " Hours" + "\r\n" + "Battery SoC = " + String (BattPerc) + " %" + " , Boot number: " + String(bootCount) + "\r\n" + "-- Best Regards from FishFeeder-- ." + "\r\n";                             // text message in case of manual feeding and still food available
  String textMsgManualFishFeedingNoMoreFood = "The fish have been fed manually today " +  String (Day[CurrentDayOfWeek]) + ", at " + NTPTime + "\r\n" + " But now the food is finished, please refill" + "\r\n" + "The time lapsed since last activity is: " + String(DeltaHours) + " Hours" + "\r\n" + "Battery SoC = " + String (BattPerc) + " %" + " , Boot number: " + String(bootCount) + "\r\n" + "-- Best Regards from FishFeeder-- ." + "\r\n";     // text message in case manual feeding no more food available
  String textMsgFishFeederReset = "The fishfeeder has been reset today " +  String (Day[CurrentDayOfWeek]) + ", at " + NTPTime + "\r\n" + "The time lapsed since last activity is: " + String(DeltaHours) + " Hours" + "\r\n" + "The next feeding moment will be " + String(HoursToSleep) + " Hours from now" + "\r\n" + "Battery SoC = " + String (BattPerc) + " %" + " , Boot number: " + String(bootCount) + "\r\n" + "-- Best Regards from FishFeeder-- ." + "\r\n";              // text message in case of RESET
                                                                                                                                                                                                                                                                                                                            
  esp_sleep_wakeup_cause_t wakeup_reason;
  wakeup_reason = esp_sleep_get_wakeup_cause();
  
  switch(wakeup_reason)
  {
    case ESP_SLEEP_WAKEUP_EXT0 : Serial.println("Wakeup for manual Feed signal using magnet to trigger RTC_IO on GPIO 36");
    numPortions = 1;                                      // sets the number of food portions to be given to the fish
    Food_State = digitalRead (IR_RX_Pin);                 // check if IR LED receives a signal (dark= LED not conducting thus RX pin is HIGH which means there is still food)
      if (Food_State == LOW) {Serial.print ("Food_State is: "); Serial.print (Food_State); Serial.println ("The Food is finished") ; textMsg = textMsgNoFoodForManualFishFeeding.c_str(); Serial.print("textMsg = "); Serial.println (textMsg); Serial.println();}
      else {Serial.print ("Food_State is: "); Serial.print (Food_State); Serial.println (" Food still available") ; textMsg = textMsgManualFishFeeding.c_str(); Serial.print("textMsg = "); Serial.println (textMsg); Serial.println();
          for (int i = 1; i <= numPortions; i++) {
                stepper.setCurrentPosition (0);
                stepper.runToNewPosition(-5000);
                delay (200);
                }
          ////////////////the following code will check again if there is still food available after manual feeding///////////////
          Food_State = digitalRead (IR_RX_Pin);                 // check if IR LED receives a signal (dark= LED not conducting thus RX pin is HIGH which means there is still food)
          if (Food_State == LOW) {Serial.print ("Food_State is: "); Serial.print (Food_State); Serial.println ("The Food is finished") ; textMsg = textMsgManualFishFeedingNoMoreFood.c_str(); Serial.print("textMsg = "); Serial.println (textMsg); Serial.println();}
          }
      stepper.disableOutputs();                 //control of the steppermotor is disabled to avoid unnecessary heating of the motor
      Serial.println ("Stepper motor ready");                  
    break;
    
    case ESP_SLEEP_WAKEUP_TIMER : Serial.println("Wakeup caused by Sleep timer");
    numPortions = 3;                                      // sets the number of food portions to be given to the fish
    Food_State = digitalRead (IR_RX_Pin);                 // check if IR LED receives a signal (dark= LED not conducting thus RX pin is HIGH which means there is still food)
      if (Food_State == LOW) {Serial.print ("Food_State is: "); Serial.print (Food_State); Serial.println (" The Food is finished") ; textMsg = textMsgNoFoodForAutomaticFishFeeding.c_str(); Serial.print("textMsg = "); Serial.println (textMsg); Serial.println();}
      else {Serial.print ("Food_State is: "); Serial.print (Food_State); Serial.println (" Food still available") ; textMsg = textMsgFishFed.c_str(); Serial.print("textMsg = "); Serial.println (textMsg); Serial.println();
          for (int i = 1; i <= numPortions; i++) {
                stepper.setCurrentPosition (0);
                stepper.runToNewPosition(-5000);
                delay (200);
                }
          ////////////////the following code will check again if there is still food available after manual feeding//////////////
          Food_State = digitalRead (IR_RX_Pin);                 // check if IR LED receives a signal (dark= LED not conducting thus RX pin is HIGH which means there is still food)
          if (Food_State == LOW) {Serial.print ("Food_State is: "); Serial.print (Food_State); Serial.println ("The Food is finished") ; textMsg = textMsgAutomaticFishFedNoMoreFood.c_str(); Serial.print("textMsg = "); Serial.println (textMsg); Serial.println();}

          ////////////////the following code will calculate a correction (in seconds) for the wake up time (in microseconds) based on the NTP data received//////////////
          if (DeltaTime > (0.9 * TimeToSleep) || DeltaTime < (1.1 * TimeToSleep)){
          SleepCorrect = (TimeToSleep - DeltaTime)/2;           // correction is > 1 in case DeltaTime is less than the fixed TimeToSleep or < 1 if the DeltaTime is larger
          Serial.print (" The SleepCorrection is: "); Serial.print (SleepCorrect); Serial.println (" Seconds ");
          prom.putInt("SleepCorrect", SleepCorrect);            // modifies the SleepCorrection stored in EEPROM flash memory to correct the WakeUp_microsecs 
          }
          }
      stepper.disableOutputs();                 //control of the steppermotor is disabled to avoid unnecessary heating of the motor
      Serial.println ("Stepper motor ready");
    break;
   
    default : Serial.printf("Wakeup was caused by Reset: %d\n",wakeup_reason); textMsg = textMsgFishFeederReset.c_str(); Serial.print("textMsg = "); Serial.println (textMsg); Serial.println();
    break;
  }
}

//////////////////////Function to send an E-Mail message with the applicable textMsg
void SEND_EMAIL (){
  smtp.debug(1);                                     // Enable the debug via Serial port: none debug or 0; basic debug or 1
  smtp.callback(smtpCallback);                       // Set the callback function to get the sending results
  ESP_Mail_Session session;                          // Declare the session config data

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

  SMTP_Message message;                                    // Declare the message class
  message.sender.name = "My Fish Feeder";                  // Set the message headers
  message.sender.email = AUTHOR_EMAIL;
  message.subject = "Fish Feeder Action";
  message.addRecipient("Owner name", RECIPIENT_EMAIL);     // Add recipients, you can add more than one recipient

  message.text.content = textMsg.c_str();                  // make the text message based on the applicable textMsg
  message.text.charSet = "us-ascii";
  message.text.transfer_encoding = Content_Transfer_Encoding::enc_7bit;
  
  if (!smtp.connect(&session))                      // Connect to server with the session config
    return;
    
  if (!MailClient.sendMail(&smtp, &message))        // Start sending Email and close the session
  Serial.println("Error sending Email, " + smtp.errorReason());
}

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

    if (status.success()){                            // Print the sending result
    Serial.println("----------------");
    ESP_MAIL_PRINTF("Message sent success: %d\n", status.completedCount());
    ESP_MAIL_PRINTF("Message sent failled: %d\n", status.failedCount());
    Serial.println("----------------\n");
    struct tm dt;

    for (size_t i = 0; i < smtp.sendingResult.size(); i++){
      SMTP_Result result = smtp.sendingResult.getItem(i);   // Get the result item
      time_t ts = (time_t)result.timestamp;
      localtime_r(&ts, &dt);

      ESP_MAIL_PRINTF("Message No: %d\n", i + 1);
      ESP_MAIL_PRINTF("Status: %s\n", result.completed ? "success" : "failed");
      ESP_MAIL_PRINTF("Date/Time: %d/%d/%d %d:%d:%d\n", dt.tm_year + 1900, dt.tm_mon + 1, dt.tm_mday, dt.tm_hour, dt.tm_min, dt.tm_sec);
      ESP_MAIL_PRINTF("Recipient: %s\n", result.recipients);
      ESP_MAIL_PRINTF("Subject: %s\n", result.subject);
    }
    Serial.println("----------------\n");
  }
}

Credits

Pedro52

Pedro52

6 projects • 47 followers
Thanks to Rui Santos.

Comments