ESP32-CAM Capture

Right now I’m struggling with the wake up pin configuration. But I agree only walking up when the door opened would be a lot better.

There are interrupt pins specifically for this kind of thing. If the board is sleeping and that interrupt pin changes state(Door opens) it will wake the board and do whatever automation you create and then go back to sleep. I’m not a big fan of battery powered and all that but, this is a frequent conversation in the forums and you shouldn’t have a hard time finding details on doing this.

Lol @bkbilly your use-case and approach is litterally identical to mine. What brought me here. (Gas Meter reading, photo, upload to server, sleep)
I’m working with GPT but its having issues with ESP Home YAML. A lot of guessing.

Ideally I’d like to get this working as a stand alone device that doesn’t need ESP Home or Home Assistant, uploads directly to my server for processing.
The following is where I’m at now… but the syntax to take the photo is currently where I’m stuck.



substitutions:
  name: esphome-web-hash
  friendly_name: Gas Meter Reader

esphome:
  name: ${name}
  friendly_name: ${friendly_name}
  min_version: 2024.6.0
  name_add_mac_suffix: false
  project:
    name: esphome.web
    version: dev

esp32:
  board: esp32dev
  framework:
    type: arduino

# Enable logging
logger:

# Enable Home Assistant API
api:

# Allow Over-The-Air updates
ota:
- platform: esphome

# Allow provisioning Wi-Fi via serial
improv_serial:

wifi:
  ssid: "SSID_HERE"
  password: "PASSWORD_HERE"
  # Set up a Wi-Fi access point as a fallback
  ap: {}

# Enable captive portal for Wi-Fi provisioning
captive_portal:

dashboard_import:
  package_import_url: github://esphome/example-configs/esphome-web/esp32.yaml@main
  import_full_config: true

# Sets up Bluetooth LE (Only on ESP32) to allow the user to provision wifi credentials to the device.
esp32_improv:
  authorizer: none

# Enable web server for local control and access
web_server:
  port: 80

# Template switch to trigger the photo capture and upload script
switch:
  - platform: template
    name: "Take Photo and Upload"
    id: photo_upload_trigger
    turn_on_action:
      - logger.log: "Digital trigger activated, taking a photo..."
      - script.execute: take_photo_and_upload

# Camera configuration for ESP32-CAM
esp32_camera:
  name: "Gas Meter Camera"
  id: esp32cam_camera
  external_clock:
    pin: GPIO0
    frequency: 20MHz
  i2c_pins:
    sda: GPIO26
    scl: GPIO27
  data_pins: [GPIO5, GPIO18, GPIO19, GPIO21, GPIO36, GPIO39, GPIO34, GPIO35]
  vsync_pin: GPIO25
  href_pin: GPIO23
  pixel_clock_pin: GPIO22
  power_down_pin: GPIO32

  # Image Quality
  resolution: 800x600  # Set camera resolution, adjust as needed
  jpeg_quality: 10     # Set JPEG quality (lower number means higher quality)
  max_framerate: 1 fps
  idle_framerate: 0.01 fps

# Include the HTTP Request component in your ESPHome configuration
http_request:
  id: my_upload
  timeout: 10s  # Optional: Adjust the timeout as needed
  useragent: "ESPHome-GasMeterReader"  # Optional: Set a custom user agent

# Script to take a photo and upload it to a server using HTTP POST
script:
  - id: take_photo_and_upload
    then:
      - component.update: esp32cam_camera
      - delay: 5s
      - camera.take_photo:
          id: esp32cam_camera
          filename: "/photo.jpg"
      - delay: 10s
      - http_request.post:
          url: "http://your-server-endpoint/upload"  # Replace with your server URL
          headers:
            Content-Type: "multipart/form-data"
          file:
            path: "/photo.jpg"  # Path to the file on the ESP32
            name: "file"  # Field name in the form-data
      - logger.log: "Photo taken and uploaded successfully."

This seems very promising, but I gave up on this approach.
I got power near the gas meter and used the AI-on-the-edge-device to get the measurement.

If the take_photo_and_upload script works for you, you should also use the deep_sleep component to conserve power.

esp32camera on_image (Optional, Automation): An automation called when image taken. Image is available as image variable of type esp32_camera::CameraImageData.

 struct CameraImageData {
   uint8_t *data;
   size_t length;
 };

to use it:

esp32_camera:
  on_image:
    then:
      - lambda: |-
          ESP_LOGD("esp32_camera", "on_image length: %u data: %p", image.length, image.data);

for example writing a file to SD card:


                esp_err_t res;
                const uint8_t *data = image->get_data_buffer();
                size_t len = image->get_data_length();
                res = this->write_photo(to_string(this->image_file_name_).c_str(), data, len);
....



    ESP_LOGCONFIG(TAG, "fopen %s", filename );
    FILE *fd = fopen(filename, "wb");
    if (fd == NULL) {
        ESP_LOGE(TAG, "failed to open %s with %d", filename, len);
        fclose(fd);
        this->status_momentary_warning("write_photo Failed",this->timeout_);
        return ESP_FAIL;
    }
    size_t written = fwrite(data, 1, len, fd);
    if (written != len) {
        ESP_LOGE(TAG, "write_photo Failed to write all data to %s", filename);
        fclose(fd);
        return ESP_FAIL;
    }
1 Like

this code will send image to MQTT every minute:

esp32_camera:
  ... other settings like GPIO,resolution etc
  ...
  max_framerate: 1 fps
  idle_framerate: 1 fps
  on_image:
    then:
      - if:
          condition: 
            lambda: |-
              static int interval_counter = 0; 
              const int update_interval_max = 60;

              interval_counter--; 
          
              if (interval_counter <= 0) 
              {
                ESP_LOGI("mqttlog", "condition TRUE to send image via mqtt");
                interval_counter = update_interval_max; // Reset counter 
                return true;
              }
    
              ESP_LOGI("mqttlog", "condition FALSE image via mqtt, counter: %d", interval_counter);
              return false;

          then:
            - mqtt.publish:
                topic: "$device/cam"
                payload: !lambda return esphome::base64_encode(image.data, image.length);

Drawbacks:

  • base64 encoding means a 30% increase in size
  • camera captures image every second (1 FPS) - waste of cpu&power

Questions:

  1. MQTT can handle binary, can ESPHome send binary?

answer: currently no.

got info on discord (thank you @ssieb ) The api uses protobuf. The issue with mqtt is at one point in the transfer, the data is passed as a char * with no length. That needs to be changed.

1 Like

who wants to modify the code, here’s what i done and did:

esp32_camera.cpp:


/* ---------------- CameraImage class ---------------- */
CameraImage::CameraImage(camera_fb_t *buffer, uint8_t requesters) : buffer_(buffer), requesters_(requesters) {}
camera_fb_t *CameraImage::get_raw_buffer() { return this->buffer_; }
uint8_t *CameraImage::get_data_buffer() { return this->buffer_->buf; }   /*!< Pointer to the pixel data */
size_t CameraImage::get_data_length() { return this->buffer_->len; }  /*!< Length of the buffer in bytes */
pixformat_t CameraImage::get_pixel_format() { return this->buffer_->format; }  /*!< Format of the pixel data */
int CameraImage::get_width() { return this->buffer_->width; }  /*!< Width of the buffer in pixels */
int CameraImage::get_height() { return this->buffer_->height; } /*!< Height of the buffer in pixels */

esp32_camera.h

class ESP32CameraImageTrigger : public Trigger<CameraImageData> {
 public:
  explicit ESP32CameraImageTrigger(ESP32Camera *parent) {
    parent->add_image_callback([this](const std::shared_ptr<esp32_camera::CameraImage> &image) {
      CameraImageData camera_image_data{};
      camera_image_data.length = image->get_data_length();
      camera_image_data.data = image->get_data_buffer();
      camera_image_data.raw = image->get_raw_buffer();
      camera_image_data.format = image->get_pixel_format();
      camera_image_data.width = image->get_width();
      camera_image_data.height = image->get_height();
      this->trigger(camera_image_data);
    });
  }
};

base64 for single image every x minutes interval is feasible, ascii stream @1600x1200 jpeg_quality 10 caps out at around 4 fps for me. wait i have the max set at 5fps, maybe i can get more…

{"id":"number-_max_fps","value":"5.0","state":"5.0"}

(gif conversion and ipad recording does not do it justice)

ascii

Sorry i have fever i cant read. Base64 is also doable as still or stream, albeit a bit slower

base64-smol-ezgif.com-optimize