ESP32-CAM: Bei Bewegung, Foto aufnehmen und per Mail versenden

Diesmal wieder etwas mit dem ESP32-CAM.
Die Möglichkeit mit dieser preisgünstigen Lösung auch Fotos aufzunehmen, soll diesmal genutzt werden, um sofort nach Erkennung einer Bewegung, das aufgenommene Bild direkt per Mail im Anhang zu verschicken.
Die Kombination aus einem ESP32-CAM-Modul und einem HC-SR501-Bewegungssensor bietet eine Vielzahl von Anwendungsmöglichkeiten.
Hier sind einige Beispiele:

  1. Wildtierüberwachung: Wenn Sie in einer Gegend mit viel Wildtieren leben, können Sie dieses Setup verwenden, um Fotos von Tieren in Ihrem Garten aufzunehmen. Sie könnten überrascht sein, welche Tiere nachts herumstreifen!
  2. Automatisierte Türklingel: Sie könnten dieses Setup an Ihrer Haustür installieren. Wenn jemand an der Tür steht, wird ein Foto aufgenommen und an Ihre E-Mail gesendet. So wissen Sie immer, wer vor Ihrer Tür steht, auch wenn Sie nicht zu Hause sind.
  3. Heimsicherheit: Sie können dieses Setup verwenden, um ein kostengünstiges Heimsicherheitssystem zu erstellen. Wenn eine Bewegung erkannt wird, nimmt das ESP32-CAM ein Foto auf und sendet es sofort an Ihre E-Mail. So können Sie sofort auf potenzielle Sicherheitsbedrohungen reagieren.
  4. Fotobox: Bei Party für spaßige Momentaufnahmen sorgen.
  5. Fotofalle: Oder einfach nur mal so aus Spaß, das nächtliche Naschen am Kühlschrank protokollieren.

Um mit einem ESP32-CAM eine Fotofalle zu bauen und die aufgenommenen Bilder automatisch per E-Mail zu versenden, benötigst du folgende Hard und Software:

Hardware:
ESP32-CAM mit Erweiterungsmodul
HC-SR501-Bewegungssensor
Kabel
USB Netzteil

Für meine Fotofalle kommt der ESP32-Cam zusätzlich einen Bewegungssensor, der signalisiert wenn sich etwas vor der Kamera bewegt. Da nehme ich wieder den passive Infrarotsensor HC-SR501. Er muss lediglich mit 5 Volt versorgt werden und wenn dieser in seinem Erfassungswinkel von bis zu 130° eine bewegende warme Quelle entdeckt, gibt er am dritten Pin eine 3,3 Volt HIGH aus von vor eingestellter Länge. Dieses Signal kann der Microcontroller auswerten, um die weiteren Schritte wie im Video zu sehen, einzuleiten.

Details zum ESP32-Cam hatte ich hier vorgestellt:

Zu installierende Library´s im Bibliotheksmanager:
Es ist völlig ausreichend im den Namen der Library einzutragen und zu suchen. Gefundene Bibliothek dann per Klick installieren. Ich bin da eher ungeduldig. Ich lass mir über die „überprüfen“ Option anzeigen was noch fehlt. Könnte ja schon vorhanden sein.
Wer sich tiefer einlesen möchte, hier die Links zu den Quellen:

ESP-Mail-Client (Versand per SMTP)
FS (LittleFS) (Dateiverwaltung)
WiFi (Funk)
esp32-camera (Fotoaufnahme)
spi (Vorinstalliert) (ermöglicht die Kommunikation per SPI)
rtc_io.h (Real TimeClock-Treiber, kommt mit der Board Library)

Verdrahtung:

3D Druck Gehäuse:

Mit TinkerCAD habe ich ein zweiteiliges Gehäuse erstellt. Beide STL-Dateien können hier bei Thingiverse herunter geladen werden. Für den 3D Druck lediglich mit eurem Slicer passend zu eurem Gerät in eine gcode-Datei umwandeln.
Gehäusegröße: 44x63x30mm

Netzteil:

Bitte beachtet, dass für die Verwendung des ESP32-CAM-Moduls, eine stabile Stromversorgung benötigen wird. Andernfalls kann das Modul unerwartet neu starten oder sich aufhängen. Bei mir läuft diese Fotofalle mit einem USB-A – 5 Volt – 2A Netzteil Sorgenfrei.

Quellcode:

#include "esp_camera.h"
#include "SPI.h"
#include "driver/rtc_io.h"
#include <ESP_Mail_Client.h>
#include <FS.h>
#include <WiFi.h>

// Accesspoint einrichten
#define WIFI_SSID "Accesspointname"
#define WIFI_PASSWORD "supersicheresPasswort"

// EMAIL Versand per SMTP 
#define emailSenderAccount    "deineMail@domain.de"
#define emailSenderPassword   "supersicheresPasswort"
//#define smtpServer            "smtp.gmail.com"  // Für Google Mail
//#define smtpServer            "smtp-mail.outlook.com"  // Für Microsoft Mail
#define smtpServer            "smtp.web.de"  // Für web.de
// #define smtpServerPort        465 // Für Google Mail
#define smtpServerPort        587 // Für Microsoft Mail und web.de
#define emailSubject          "Bewegung erkannt, hier das Beweisfoto"
#define emailRecipient        "mustermann@web.de" // Wer soll die Mail empfangen

// ESP32-Cam Typ - sollte anderes Model vorliegen, ggf nachfolgende GPIO PIn anpassen
#define CAMERA_MODEL_AI_THINKER // Mit PSRAM

#if defined(CAMERA_MODEL_AI_THINKER)
  #define PWDN_GPIO_NUM     32
  #define RESET_GPIO_NUM    -1
  #define XCLK_GPIO_NUM      0
  #define SIOD_GPIO_NUM     26
  #define SIOC_GPIO_NUM     27
  #define Y9_GPIO_NUM       35
  #define Y8_GPIO_NUM       34
  #define Y7_GPIO_NUM       39
  #define Y6_GPIO_NUM       36
  #define Y5_GPIO_NUM       21
  #define Y4_GPIO_NUM       19
  #define Y3_GPIO_NUM       18
  #define Y2_GPIO_NUM        5
  #define VSYNC_GPIO_NUM    25
  #define HREF_GPIO_NUM     23
  #define PCLK_GPIO_NUM     22
#else
  #error "Kamera Typ passt nicht"
#endif

// Funktion zum Abrufen des E-Mail-Versandstatus 
SMTPSession smtp;
void smtpCallback(SMTP_Status status);

// Dateiname und Pfad der Aufnahme
#define FILE_PHOTO "foto.jpg"
#define FILE_PHOTO_PATH "/foto.jpg"

// Der Out-Pin, an den Ihr Bewegungssensor HC_SR501 angeschlossen ist
int hcsr501Pin = 12; 

void setup() {
  // Setzen Sie den Bewegungssensor-Pin als Eingang
  pinMode(hcsr501Pin, INPUT);
  
  Serial.begin(115200);
  Serial.println();

  // Connect to Wi-Fi
  //WiFi.begin(ssid, password);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  Serial.print("WiFi Verbindungsaufbau...");
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(400);    
  }
  Serial.println();
  
  // Bei erfolgreichem AP-Login die erhaltene IP-Adresse zeigen
  Serial.print("IP Addresse: http://");
  Serial.println(WiFi.localIP());

  // Auswahl Filesystem
  ESP_MAIL_DEFAULT_FLASH_FS.begin();
   
  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = Y2_GPIO_NUM;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;
  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;
  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;
  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href = HREF_GPIO_NUM;
  config.pin_sccb_sda = SIOD_GPIO_NUM;
  config.pin_sccb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  config.xclk_freq_hz = 20000000;
  config.pixel_format = PIXFORMAT_JPEG;
  config.grab_mode = CAMERA_GRAB_LATEST;
  
  if(psramFound()){
    config.frame_size = FRAMESIZE_UXGA;
    config.jpeg_quality = 10;
    config.fb_count = 1;
  } else {
    config.frame_size = FRAMESIZE_SVGA;
    config.jpeg_quality = 12;
    config.fb_count = 1;
  }

  // Initialisiere Cam
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    return;
  }

}

void loop() {
// Lesen Sie den Zustand des Bewegungssensors
  int sensorState = digitalRead(hcsr501Pin);

  // Wenn der Sensor eine Bewegung erkennt
  if (sensorState == HIGH) {
    Serial.println("Bewegung erkannt!");
    capturePhoto();
    sendPhoto();
    delay(30000); //Warten bis wieder aktiv
  } else {
    Serial.println("Keine Bewegung erkannt.");
    delay(1000);
  }
}

// Foto aufnehmen und zwischen speichern
void capturePhoto() {
  // Erste Bilder wegen eventl. noch nicht korrektem Weißabgleich überspringen
  camera_fb_t* fb = NULL;
  // Die ersten 3 Bilder überspringen - ggf anpassen
  for (int i = 0; i < 3; i++) {
    fb = esp_camera_fb_get();
    esp_camera_fb_return(fb); // lösche Puffer
    fb = NULL;
  }
    
  // Neues Foto aufnehmen
  fb = NULL;  
  fb = esp_camera_fb_get();  
  if(!fb) {
    Serial.println("Kamera Aufnahme fehlgeschlagen");
    delay(500);
    ESP.restart();
  }  

  // Foto Dateiname
  Serial.printf("Foto Dateiname: %s\n", FILE_PHOTO_PATH);
  File file = LittleFS.open(FILE_PHOTO_PATH, FILE_WRITE);

  // Erstellung Fotodatei
  if (!file) {
    Serial.println("Fehler - kann Datei nicht schreibend offnen");
  }
  else {
    file.write(fb->buf, fb->len); // payload (image), payload length
    Serial.print("The picture has been saved in ");
    Serial.print(FILE_PHOTO_PATH);
    Serial.print(" - Size: ");
    Serial.print(fb->len);
    Serial.println(" bytes");
  }
  file.close();
  esp_camera_fb_return(fb);// lösche Puffer
}

void sendPhoto() {  //Senden des Foto per Mail

  smtp.debug(1); // Deaktivieren (0) oder Aktivieren (1) der Fehlersuche über die serielle Schnittstelle

  // Abrufen des E-Mail-Versandstatus 
  smtp.callback(smtpCallback);
  Session_Config config;
  
  // Network Time Protocol (NTP) je nach Standort anpassen (https://en.wikipedia.org/wiki/Time_in_Germany)
  config.time.ntp_server = F("pool.ntp.org,time.nist.gov");
  config.time.gmt_offset = 1; // GMT-Zeitverschiebung für Mitteleuropa im Winter
  config.time.day_light_offset = 2; // Sommerzeitverschiebung

  config.server.host_name = smtpServer;
  config.server.port = smtpServerPort;
  config.login.email = emailSenderAccount;
  config.login.password = emailSenderPassword;
  config.login.user_domain = "";
  SMTP_Message message;

  //Aktivieren Sie die chunked data transfer Datenübertragung um große Nachrichten über eine Leitung zuübertragen, sofern Server dies unterstützt
  message.enable.chunking = true;

  // Mail Absender und Betreff
  message.sender.name = "Vogelhaus";
  message.sender.email = emailSenderAccount;
  message.subject = emailSubject;
  message.addRecipient("DeinName", emailRecipient);

  String htmlMsg = "<h2>Fotoaufnahme der ESP32-CAM von dem Vogelhaeuschen</h2>";
  message.html.content = htmlMsg.c_str();
  message.html.charSet = "utf-8";
  message.html.transfer_encoding = Content_Transfer_Encoding::enc_qp;

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

  SMTP_Attachment att; // Mit Anhang

  att.descr.filename = FILE_PHOTO;
  att.descr.mime = "image/png"; 
  att.file.path = FILE_PHOTO_PATH;
  att.file.storage_type = esp_mail_file_storage_type_flash;
  att.descr.transfer_encoding = Content_Transfer_Encoding::enc_base64;
  
  // Foto als Anlage anfügen
  message.addAttachment(att);

  // Verbindungsaufbau und Senden - MailServer
  if (!smtp.connect(&config))
    return;
  if (!MailClient.sendMail(&smtp, &message, true))
    Serial.println("Fehler beim senden der Email, " + smtp.errorReason());
}

// Empfangsbestaetigung Email
void smtpCallback(SMTP_Status status){
  Serial.println(status.info());

  if (status.success())
  {
    Serial.println("----------------");
    Serial.printf("Message sendet OK: %d\n", status.completedCount());
    Serial.printf("Message sendet Fehler: %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);
      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.c_str());
      ESP_MAIL_PRINTF("Subject: %s\n", result.subject.c_str());
    }
    Serial.println("----------------\n");

   // Löschen des Sendeergebnis
   smtp.sendingResult.clear();
  }
}

Video:

Zusammenfassung des Video:
Der ESP32-Cam wird mit einem Infrarot-Bewegungsmelder versehen, um automatisch Fotos aufzunehmen und per E-Mail zu verschicken.
Die Verdrahtung und Programmierung des ESP32-Cam für die Funktion wird detailliert erläutert.
Die Funktionen zum Aufnehmen, Versenden und Löschen der Fotos wird erklärt und durchgeführt.
Die Vorbereitungen für den Upload des Programmcodes und die finale Testdurchführung wird demonstriert.