tf
Created May 31, 2021 © GPL3+

Smart Bee Camera Trap

Wild bees are threatened by the effects of climate change. This project may help to document their decline in number and variety.

IntermediateFull instructions provided4 hours61
Smart Bee Camera Trap

Things used in this project

Hardware components

QuickFeather Dev Kit with UART Cable + SensiML
QuickLogic Corp. QuickFeather Dev Kit with UART Cable + SensiML
×1
ESP32-CAM AI-Thinker
×1
Breadboard (generic)
Breadboard (generic)
×2
Jumper wires (generic)
Jumper wires (generic)
×1

Software apps and online services

QuickLogic Corp. Quickfeather Simple Streaming Interface AI Application
QuickLogic Corp. TinyFPGA-Programmer-Application
SensiML Analytics Toolkit
SensiML Analytics Toolkit
Arduino IDE
Arduino IDE

Story

Read more

Custom parts and enclosures

Prototype

Schematics

Wiring

Code

ESP32-CAM_Capture_Mailer.ino

Arduino
ESP32-CAM firmware: Reads JSON from serial, captures still when triggered and sends image as email attachment ...
#include "esp_camera.h"

#include "SPI.h"
#include "driver/rtc_io.h"
#include "ESP32_MailClient.h"
#include <FS.h>
#include <SPIFFS.h>
#include <WiFi.h>
#include <ArduinoJson.h>

/*
    SETUP/CONFIGURATION: DEBUG
*/
#define DEBUG
#include "Debug2Serial.h"

/*
   SETUP/CONFIGURATION: CAMERA MODEL
*/
// Select camera model
//#define CAMERA_MODEL_WROVER_KIT // Has PSRAM
//#define CAMERA_MODEL_ESP_EYE // Has PSRAM
//#define CAMERA_MODEL_M5STACK_PSRAM // Has PSRAM
//#define CAMERA_MODEL_M5STACK_V2_PSRAM // M5Camera version B Has PSRAM
//#define CAMERA_MODEL_M5STACK_WIDE // Has PSRAM
//#define CAMERA_MODEL_M5STACK_ESP32CAM // No PSRAM
#define CAMERA_MODEL_AI_THINKER // Has PSRAM
//#define CAMERA_MODEL_TTGO_T_JOURNAL // No PSRAM

#include "camera_pins.h"

/*
   SETUP/CONFIGURATION: WIFI NETWORK
*/
// WiFi network credentials
const char *ssid = "YOUR_WIFIs_SID";
const char *password = "YOUR_WIFIs_PASSWORD";

/*
   SETUP/CONFIGURATION: SMTP CLIENT
*/
#define smptLoginUser "esp32cam@example.org"
#define smptLoginPassword "top-secret"
#define smtpServer "smtp.example.org"
#define smtpServerPort 465
#define mailFrom "esp32cam@example.org"
#define mailFromName "ESP32-CAM"
#define mailTo "bee-watcher@example.org"
#define mailSubject "ESP32-CAM Captured Still"
#define mailText "Captured still from ESP32-CAM: See attachment ..."
#define mailTextHtml false
#define mailStorageType MailClientStorageType::SPIFFS

/*
   SETUP/CONFIGURATION: FILENAME
*/
// Image file name
#define CAPTURE_FILE "/capture.jpg"

/*
   SETUP/CONFIGURATION: MISC.
*/
#define link_baud 460800
const int trigger_classification = 2;
const unsigned int after_trigger_delay = 10;

void setup()
{
  DEBUG_SERIAL_PRINTLN("Setup ...");

  // Disable brownout detector
  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);

  // Setup serial interface
  Serial.begin(link_baud);

  // Setup camera
  setup_camera();

  // Setup SPIFS
  setup_SPIFFS();

  // Connect WiFi network
  wifi_connect();

  // Re-enable brownout detector
  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 1);

  DEBUG_SERIAL_PRINTLN("Setup completed.");
}

void loop()
{
  if (triggered(Serial, trigger_classification))
  {
    DEBUG_SERIAL_PRINTLN("Loop: triggered ...");
    DEBUG_SERIAL_PRINTLN("Loop: capturing ...");
    save_still(SPIFFS, CAPTURE_FILE);
    DEBUG_SERIAL_PRINTLN("Loop: sending capture ...")
    send_file(CAPTURE_FILE, "image/jpeg", mailTo, mailSubject, mailText, mailTextHtml);
    DEBUG_SERIAL_PRINTF("Loop: sleeping %d seconds ...");
    sleep(after_trigger_delay);
    DEBUG_SERIAL_PRINTLN("Loop: trigger action completed.");
  }
}

void setup_camera()
{
  // Camera configuration
  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_sscb_sda = SIOD_GPIO_NUM;
  config.pin_sscb_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;

  if (psramFound())
  {
    config.frame_size = FRAMESIZE_UXGA;
    config.jpeg_quality = 10;
    config.fb_count = 2;
  }
  else
  {
    config.frame_size = FRAMESIZE_SVGA;
    config.jpeg_quality = 12;
    config.fb_count = 1;
  }

  // Initialize camera
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK)
  {
    DEBUG_SERIAL_PRINTF("ERROR: Camera initialization failed with error 0x%x.", err);
    return;
  }
}

void wifi_connect()
{
  WiFi.begin(ssid, password);
  DEBUG_SERIAL_PRINT("Connecting to WiFi network ...");
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    DEBUG_SERIAL_PRINT(". ");
  }
  DEBUG_SERIAL_PRINTLN("connected.");
  DEBUG_SERIAL_PRINT("WiFi client IP:");
  DEBUG_SERIAL_PRINTLN(WiFi.localIP());
}

void setup_SPIFFS()
{
  if (!SPIFFS.begin(true))
  {
    DEBUG_SERIAL_PRINTLN("ERROR: SPIFFS intialization failed.");
    ESP.restart();
  }
  else
  {
    delay(500);
    Serial.println("SPIFFS successfully initialized.");
  }
}

bool check_file_size(fs::FS &fs, const char *name, unsigned int size)
{
  File file = fs.open(name);
  unsigned int file_size = file.size();
  DEBUG_SERIAL_PRINTF("File: %s - Size: %d\n", name, file_size);
  file.close();
  return (file_size >= size);
}

void save_still(fs::FS &fs, const char *name)
{
  camera_fb_t *fb = NULL; // Frame buffer
  unsigned int fb_len = 0;
  bool ok = 0;

  do
  {
    // Aquire still from camera
    DEBUG_SERIAL_PRINTLN("Getting still from camera ...");
    fb = esp_camera_fb_get();
    if (!fb)
    {
      DEBUG_SERIAL_PRINTLN("ERROR: Camera capture failed.");
      return;
    }
    else
    {
      fb_len = fb->len;
    }

    // Write image to file
    DEBUG_SERIAL_PRINTF("Saving image to file: %s\n", name);
    File file = fs.open(CAPTURE_FILE, FILE_WRITE);
    if (!file)
    {
      DEBUG_SERIAL_PRINTLN("ERROR: Failed to open file for writing.");
    }
    else
    {
      file.write(fb->buf, fb->len);
    }
    file.close();
    esp_camera_fb_return(fb);

    // Check if file has been created and written completely
    ok = check_file_size(fs, name, fb_len);
  } while (!ok);
}

void send_file(const char *fileName, const char *mimeType, const char *recipient, const char *subject, const char *text, bool textHtml)
{
  SMTPData smtpData;

  DEBUG_SERIAL_PRINTLN("Preparing outgoing email message ...");
  smtpData.setLogin(smtpServer, smtpServerPort, smptLoginUser, smptLoginPassword);
  smtpData.setSender(mailFromName, mailFrom);
  smtpData.setPriority("High");
  smtpData.setSubject(subject);
  smtpData.setMessage(text, textHtml);
  smtpData.addRecipient(recipient);
  smtpData.setFileStorageType(mailStorageType);
  smtpData.addAttachFile(fileName, mimeType);
  smtpData.setSendCallback(send_callback);

  DEBUG_SERIAL_PRINTLN("Sending email message ...");
  if (!MailClient.sendMail(smtpData))
  {
    DEBUG_SERIAL_PRINTLN("ERROR: Send failed: " + MailClient.smtpErrorReason());
  }

  smtpData.empty();
}

void send_callback(SendStatus msg)
{
  DEBUG_SERIAL_PRINTLN(msg.info());
}

int get_classification(Stream& link) {
  int classification = -1;

  if (link.available())
  {
    DEBUG_SERIAL_PRINTLN("Receiving data on link ...");
    // Allocate the JSON document
    StaticJsonDocument<1024> doc;

    // Read the JSON document from the "link" serial port
    DeserializationError err = deserializeJson(doc, link);

    if (err == DeserializationError::Ok)
    {
      classification = doc["Classification"].as<int>();
      DEBUG_SERIAL_PRINTF("Classification = %d\n", classification);
    }
    else
    {
      DEBUG_SERIAL_PRINTF("ERROR: JSON deserialization failed: %s", err.c_str());
      // Flush all bytes in the "link" serial port buffer
      while (link.available() > 0)
        link.read();
    }
  }

  return classification;
}

bool triggered(Stream& link, int trigger) {
  int classification = get_classification(link);
  return (classification == trigger);
}

Debug2Serial.h

C Header File
Macros for enabling/disabling logging over serial ...
/*
 * Debug2Serial.h
*/

#ifndef DEBUG2SERIAL_H
#define DEBUG2SERIAL_H

#ifdef DEBUG
    #define DEBUG_SERIAL_INIT(baud) \
        Serial.begin(baud); \
        Serial.println("DEBUG ENABLED")
    #define DEBUG_SERIAL_FLUSH() Serial.flush()    
    #ifdef DEBUG_VERBOSE
        #define DEBUG_SERIAL_PRINT(...)  \
            Serial.print(millis());     \
            Serial.print(": ");    \
            Serial.print(__PRETTY_FUNCTION__); \
            Serial.print(' ');      \
            Serial.print(__LINE__);     \
            Serial.print(' ');      \
            Serial.print(__VA_ARGS__)
        #define DEBUG_SERIAL_PRINTLN(...)  \
            Serial.print(millis());     \
            Serial.print(": ");    \
            Serial.print(__PRETTY_FUNCTION__); \
            Serial.print(' ');      \
            Serial.print(__LINE__);     \
            Serial.print(' ');      \
            Serial.println(__VA_ARGS__)
    #define DEBUG_SERIAL_PRINTF(...)  \
            Serial.print(millis());     \
            Serial.print(": ");    \
            Serial.print(__PRETTY_FUNCTION__); \
            Serial.print(' ');      \
            Serial.print(__LINE__);     \
            Serial.print(' ');      \
            Serial.printf(__VA_ARGS__)
    #else
        #define DEBUG_SERIAL_INIT(baud) Serial.begin(baud)
        #define DEBUG_SERIAL_PRINT(...) Serial.print(__VA_ARGS__)
        #define DEBUG_SERIAL_PRINTLN(...) Serial.println(__VA_ARGS__)
        #define DEBUG_SERIAL_PRINTF(...) Serial.printf(__VA_ARGS__)
    #endif
#else
    #define DEBUG_SERIAL_INIT(baud)
    #define DEBUG_SERIAL_FLUSH()
    #define DEBUG_SERIAL_PRINT(...)
    #define DEBUG_SERIAL_PRINTLN(...)
    #define DEBUG_SERIAL_PRINTF(...)
#endif

#endif

camera_pins.h

C Header File
https://github.com/espressif/arduino-esp32 - arduino-esp32/libraries/ESP32/examples/Camera/CameraWebServer/camera_pins.h
#if defined(CAMERA_MODEL_WROVER_KIT)
#define PWDN_GPIO_NUM    -1
#define RESET_GPIO_NUM   -1
#define XCLK_GPIO_NUM    21
#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      19
#define Y4_GPIO_NUM      18
#define Y3_GPIO_NUM       5
#define Y2_GPIO_NUM       4
#define VSYNC_GPIO_NUM   25
#define HREF_GPIO_NUM    23
#define PCLK_GPIO_NUM    22

#elif defined(CAMERA_MODEL_ESP_EYE)
#define PWDN_GPIO_NUM    -1
#define RESET_GPIO_NUM   -1
#define XCLK_GPIO_NUM    4
#define SIOD_GPIO_NUM    18
#define SIOC_GPIO_NUM    23

#define Y9_GPIO_NUM      36
#define Y8_GPIO_NUM      37
#define Y7_GPIO_NUM      38
#define Y6_GPIO_NUM      39
#define Y5_GPIO_NUM      35
#define Y4_GPIO_NUM      14
#define Y3_GPIO_NUM      13
#define Y2_GPIO_NUM      34
#define VSYNC_GPIO_NUM   5
#define HREF_GPIO_NUM    27
#define PCLK_GPIO_NUM    25

#elif defined(CAMERA_MODEL_M5STACK_PSRAM)
#define PWDN_GPIO_NUM     -1
#define RESET_GPIO_NUM    15
#define XCLK_GPIO_NUM     27
#define SIOD_GPIO_NUM     25
#define SIOC_GPIO_NUM     23

#define Y9_GPIO_NUM       19
#define Y8_GPIO_NUM       36
#define Y7_GPIO_NUM       18
#define Y6_GPIO_NUM       39
#define Y5_GPIO_NUM        5
#define Y4_GPIO_NUM       34
#define Y3_GPIO_NUM       35
#define Y2_GPIO_NUM       32
#define VSYNC_GPIO_NUM    22
#define HREF_GPIO_NUM     26
#define PCLK_GPIO_NUM     21

#elif defined(CAMERA_MODEL_M5STACK_V2_PSRAM)
#define PWDN_GPIO_NUM     -1
#define RESET_GPIO_NUM    15
#define XCLK_GPIO_NUM     27
#define SIOD_GPIO_NUM     22
#define SIOC_GPIO_NUM     23

#define Y9_GPIO_NUM       19
#define Y8_GPIO_NUM       36
#define Y7_GPIO_NUM       18
#define Y6_GPIO_NUM       39
#define Y5_GPIO_NUM        5
#define Y4_GPIO_NUM       34
#define Y3_GPIO_NUM       35
#define Y2_GPIO_NUM       32
#define VSYNC_GPIO_NUM    25
#define HREF_GPIO_NUM     26
#define PCLK_GPIO_NUM     21

#elif defined(CAMERA_MODEL_M5STACK_WIDE)
#define PWDN_GPIO_NUM     -1
#define RESET_GPIO_NUM    15
#define XCLK_GPIO_NUM     27
#define SIOD_GPIO_NUM     22
#define SIOC_GPIO_NUM     23

#define Y9_GPIO_NUM       19
#define Y8_GPIO_NUM       36
#define Y7_GPIO_NUM       18
#define Y6_GPIO_NUM       39
#define Y5_GPIO_NUM        5
#define Y4_GPIO_NUM       34
#define Y3_GPIO_NUM       35
#define Y2_GPIO_NUM       32
#define VSYNC_GPIO_NUM    25
#define HREF_GPIO_NUM     26
#define PCLK_GPIO_NUM     21

#elif defined(CAMERA_MODEL_M5STACK_ESP32CAM)
#define PWDN_GPIO_NUM     -1
#define RESET_GPIO_NUM    15
#define XCLK_GPIO_NUM     27
#define SIOD_GPIO_NUM     25
#define SIOC_GPIO_NUM     23

#define Y9_GPIO_NUM       19
#define Y8_GPIO_NUM       36
#define Y7_GPIO_NUM       18
#define Y6_GPIO_NUM       39
#define Y5_GPIO_NUM        5
#define Y4_GPIO_NUM       34
#define Y3_GPIO_NUM       35
#define Y2_GPIO_NUM       17
#define VSYNC_GPIO_NUM    22
#define HREF_GPIO_NUM     26
#define PCLK_GPIO_NUM     21

#elif 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

#elif defined(CAMERA_MODEL_TTGO_T_JOURNAL)
#define PWDN_GPIO_NUM      0
#define RESET_GPIO_NUM    15
#define XCLK_GPIO_NUM     27
#define SIOD_GPIO_NUM     25
#define SIOC_GPIO_NUM     23

#define Y9_GPIO_NUM       19
#define Y8_GPIO_NUM       36
#define Y7_GPIO_NUM       18
#define Y6_GPIO_NUM       39
#define Y5_GPIO_NUM        5
#define Y4_GPIO_NUM       34
#define Y3_GPIO_NUM       35
#define Y2_GPIO_NUM       17
#define VSYNC_GPIO_NUM    22
#define HREF_GPIO_NUM     26
#define PCLK_GPIO_NUM     21

#else
#error "Camera model not selected"
#endif

Credits

tf

tf

14 projects • 3 followers

Comments