esphome / issues

Issue Tracker for ESPHome
https://esphome.io/
290 stars 34 forks source link

ESP32-CAM: Not possible to change contrast, brightness, saturation #5499

Open Jpsy opened 1 year ago

Jpsy commented 1 year ago

The problem

Several method calls of the esp32_camera component do not result in any visible change, i.e. (but not complete):

The reason is probably an oversimplified approach of the ESP32-CAM driver that is used by the component. The central point here is the "SDE indirect register" of the OV2640, which contains a bit mask of enable flags for changes to contrast, brightness, saturation, color tint, negative and others. With each call to the driver's set_contrast / setbrightness / set... the enable bit for the requested effect is set and all others are zeroed. Thus only the last changed effect remains active and all others are switched off again.

Now, the esp32_camera component's set_xyz() methods do not call the corresponding driver methods at all. Instead they store the requested effect strength parameter in an internal status var. All these status vars are then written to the OV2640 by a single call to update_camera_parameters(). This fires a series of method calls of the driver (including set_contrast, set_brightness, set_saturation and others). As described, only the last of these wins, which is probably the color tint change, as this seems to work. With this flow the brightness/saturation/contrast will never work.

I see two possible solutions here:

  1. Simple but limited solution Move the set_xyz() calls from update_camera_parameters() directly into the corresponding set_xyz() calls of the ESPHome component. This would at least allow to set all of the effects separately, but not in combination.

  2. Take control of the enable flags in SDE indirect register This would allow for all combinations of effects, but I see several comments on the web, that there are interferences between different effects that might need to be taken care of. I.e. brightness and contrast seem to interfere. But then again, this could also be handled by the user by sending adjusting method calls herself.

Solution 1. seems easy to implement and I think I can file a PR for that. Solution 2. is beyond my reach. I tried but got stuck because I needed the write_SCCB() method of the driver. This method is currently not available in the ESPHome esp32_camera component and I have no idea how I could get hold of that. If someone could provide me a way to get hold of this method, I could also try to work on 2.

Which version of ESPHome has the issue?

2023.5.5

What type of installation are you using?

Home Assistant Add-on

Which version of Home Assistant has the issue?

2023.6.1

What platform are you using?

ESP32

Board

ESP32-CAM (AI Thinker Clone)

Component causing the issue

esp32_camera

Example YAML snippet

esp32_camera:
  id: espcam
  ...
  ...

number:
  - platform: template
    name: Kontrast
    id: cam_contrast
    optimistic: true
    initial_value: 0
    min_value: -2
    max_value: 2
    step: 1
    set_action:
      - lambda: |-
          id(espcam).set_contrast(x);
          id(espcam).update_camera_parameters();
  - platform: template
    name: Helligkeit
    id: cam_brightness
    optimistic: true
    initial_value: 0
    min_value: -2
    max_value: 2
    step: 1
    set_action:
      - lambda: |-
          id(espcam).set_brightness(x);
          id(espcam).update_camera_parameters();
  - platform: template
    name: Sättigung
    id: cam_saturation
    optimistic: true
    initial_value: 0
    min_value: -2
    max_value: 2
    step: 1
    set_action:
      - lambda: |-
          id(espcam).set_saturation(x);
          id(espcam).update_camera_parameters();

Anything in the logs that might be useful for us?

No response

Additional information

OV2640 Datasheet v2.2
OV2640 Software Application Notes

Information about enable flags in SDE indirect register Example of handling interference between brightness and contrast settings

github-actions[bot] commented 11 months ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

Jpsy commented 10 months ago

This issue is still valid.

Rudd-O commented 6 months ago

This issue is still valid! I just got bitten by it.

EDIT: it remains to be seen if this is an issue with OV5640 or only with OV2640. I ordered a 5640 device to test.

fornellas commented 6 months ago

+1 here, this should be reopened. CC @OttoWinter for signal boost.

ssieb commented 6 months ago

Sorry, I had this referred by a question about changing the resolution and I didn't look close enough. That would be a feature request. But this does seem to be a valid issue since this should probably work. However, the analysis doesn't seem to be entirely correct because all the initial settings are set using this same method and those work. So I don't see why this wouldn't work as well.

ssieb commented 6 months ago

With further thought, I remember that people have been adjusting these settings from the beginning, so I don't know why it wouldn't be working now. And @fornellas , Otto is no longer involved with this project.

Jpsy commented 6 months ago

Hi Samuel, I don't really know what to respond now. Changing these settings does not work for me and for others and I could track the reason down to an obvious code problem. I also provided YAML to reproduce the problem. If you "remember that people have been adjusting these settings" and that proves me wrong, then I have no other means left to convince you. Please tell me, what else would be needed.

Jpsy commented 6 months ago

BTW there are in fact many camera settings that CAN be changed and that are not broken. Maybe it is those that you remember. I am only talking about effects that are additionally controlled by the bit mask in SDE indirect register.

Rudd-O commented 6 months ago

Yes, the SDE indirect register bit mask. If any of the settings controlled by that bit mask is changed, changes to the second or third setting from the same group have no effect. Other settings like camera flipping work perfectly and aren't overridden by changes in contrast or brightness or saturation.

It's therefore impossible to implement something in ESPHome like the ESP webcam demo (with settings on the left sidebar) that's common to see shipped in ESP devices and in YouTube videos. (This happens to be precisely what I have been working on lately.)

ssieb commented 6 months ago

If you set those values in the config, do they work?

Rudd-O commented 6 months ago

Hard to tell because the changes are subtle. But if I run the exact same code (using C++ lambda) that the settings run, no, they don't work.

fornellas commented 6 months ago

In my tests:

PS: for reference, this is the config I'm using which enable various settings to be adjusted at either ESPHome web UI or at Home Assistant:

esphome:
  name: "esp32-cam"
  friendly_name: "ESP32 Cam"
  name_add_mac_suffix: true
  on_boot:
    - priority: 600.0
      then:
        - lambda: |-
            // Default: Connecting
            id(wifi_led_pwm).set_level(0.5);
            id(wifi_led_pwm).set_period(1000);
            id(wifi_led_pwm).turn_on();
        - script.execute: fast_reboot_factory_reset
  on_loop:
    then:
      - lambda: |-
          // Captive portal
          if(id(wifi_component).has_ap() && !id(wifi_component).has_sta()) {
            id(wifi_led_pwm).set_level(0.5);
            id(wifi_led_pwm).set_period(200);
          } else {
            id(wifi_led_pwm).set_period(1000);
            // Connected
            if(id(wifi_component).is_connected()) {
              id(wifi_led_pwm).set_level(1);
              if (id(esp32cam_update)) {
                id(esp32cam).update_camera_parameters();
                id(esp32cam_update) = false;
              }
            // Connecting
            } else
              id(wifi_led_pwm).set_level(0.5);
          }

esp32:
  board: esp32cam

external_components:
  - source:
      type: git
      url: https://github.com/fornellas/esphome
      ref: fix_esp32_camera_update
    components: [ esp32_camera ]
    refresh: 0s

globals:
  - id: fast_reboot
    type: int
    restore_value: yes
    initial_value: '0'

  - id: factory_reset_reboot_counter
    type: int
    initial_value: '5'

  - id: esp32cam_update
    type: bool
    initial_value: 'false'

logger:
  baud_rate: 0
  level: INFO
  deassert_rts_dtr: true

script:
  - id: fast_reboot_factory_reset
    then:
      - if:
          condition:
            lambda: return ( id(fast_reboot) >= id(factory_reset_reboot_counter) );
          then:
            - lambda: |-
                ESP_LOGI("Fast Boot Factory Reset", "Performing factotry reset");
                id(fast_reboot) = 0;
                fast_reboot->loop();
                global_preferences->sync();
            - button.press: factory_reset_button
      - lambda: |-
          if(id(fast_reboot) > 0)
            ESP_LOGI("Fast Boot Factory Reset", "Quick reboot %d/%d, do it %d more times to factory reset", id(fast_reboot), id(factory_reset_reboot_counter), id(factory_reset_reboot_counter) - id(fast_reboot));
          id(fast_reboot) += 1;
          fast_reboot->loop();
          global_preferences->sync();
      - delay: 10s
      - lambda: |-
          id(fast_reboot) = 0;
          fast_reboot->loop();
          global_preferences->sync();

wifi:
  id: wifi_component
  ap:
    ap_timeout: 0s
  reboot_timeout: 0s
  power_save_mode: none

captive_portal:

web_server:

api:
  reboot_timeout: 0s

ota:
  safe_mode: false

esp32_camera:
  external_clock:
    pin:
      number: GPIO0
      ignore_strapping_warning: true
    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
  name: "Camera"
  id: esp32cam
  max_framerate: "60 fps"
  # resolution: 160x120 # (QQVGA)
  # resolution: 176x144 # (QCIF)
  # resolution: 240x176 # (HQVGA)
  # resolution: 320x240 # (QVGA)
  # resolution: 400x296 # (CIF) Sub-sampled
  # resolution: 640x480 # (VGA)
  resolution: 800x600 # (SVGA) Sub-sampled
  # resolution: 1024x768 # (XGA)
  # resolution: 1280x1024 # (SXGA)
  # resolution: 1600x1200 # (UXGA) Native
  on_stream_start:
    then:
      - if:
          condition:
            lambda: return id(flash_automatic).state;
          then:
            - switch.turn_on: flash
  on_stream_stop:
    then:
      - if:
          condition:
            lambda: return id(flash_automatic).state;
          then:
            - switch.turn_off: flash

esp32_camera_web_server:
  - port: 8080
    mode: stream
  - port: 8081
    mode: snapshot

binary_sensor:
  - platform: status
    name: "Status"

sensor:
  - platform: uptime
    name: 'Uptime'
    update_interval: 15s

  - platform: wifi_signal
    name: "WiFi Signal"
    update_interval: 15s

output:
  - platform: slow_pwm
    id: wifi_led_pwm
    period: 1s
    pin: GPIO33
    inverted: true

button:
  - platform: factory_reset
    id: factory_reset_button
    name: "Factory reset"

  - platform: restart
    name: "Restart"

  - platform: safe_mode
    name: "Safe Mode"

text_sensor:
  - platform: wifi_info
    ip_address:
      name: "Wifi Info: IP Address"
    ssid:
      name: "Wifi Info: SSID"
    bssid:
      name: "Wifi Info: BSSID"
    mac_address:
      name: "Wifi Info: MAC Address"
    dns_address:
      name: "Wifi Info: DNS Address"

switch:
  - platform: template
    name: "Vertical flip"
    icon: mdi:swap-vertical
    entity_category: config
    restore_mode: RESTORE_DEFAULT_ON
    optimistic: true
    turn_on_action:
      - lambda: |-
          id(esp32cam).set_vertical_flip(true);
          id(esp32cam_update) = true;
    turn_off_action:
      - lambda: |-
          id(esp32cam).set_vertical_flip(false);
          id(esp32cam_update) = true;

  - platform: template
    name: "Horizontal mirror"
    icon: mdi:reflect-horizontal
    entity_category: config
    restore_mode: RESTORE_DEFAULT_ON
    optimistic: true
    turn_on_action:
      - lambda: |-
          id(esp32cam).set_horizontal_mirror(true);
          id(esp32cam_update) = true;
    turn_off_action:
      - lambda: |-
          id(esp32cam).set_horizontal_mirror(false);
          id(esp32cam_update) = true;

  - platform: template
    name: "Auto exposure compensation 2"
    icon: mdi:auto-fix
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true
    turn_on_action:
      - lambda: |-
          id(esp32cam).set_aec2(true);
          id(esp32cam_update) = true;
    turn_off_action:
      - lambda: |-
          id(esp32cam).set_aec2(false);
          id(esp32cam_update) = true;

  - platform: template
    name: "Flash automatic on stream"
    id: flash_automatic
    icon: mdi:flash
    entity_category: config
    restore_mode: RESTORE_DEFAULT_ON
    optimistic: true

  - platform: gpio
    name: "Flash"
    icon: mdi:flash
    id: flash
    pin: GPIO4

select:
  # FIXME when reset after boot, yields panic some time after
  # - platform: template
  #   name: "Resolution"
  #   icon: mdi:video-standard-definition
  #   entity_category: config
  #   optimistic: true
  #   options:
  #     - "160x120 (QQVGA)"
  #     - "176x144 (QCIF)"
  #     - "240x176 (HQVGA)"
  #     - "320x240 (QVGA)"
  #     - "400x296 (CIF)"
  #     - "640x480 (VGA)"
  #     - "800x600 (SVGA)"
  #     - "1024x768 (XGA)"
  #     - "1280x1024 (SXGA)"
  #     - "1600x1200 (UXGA)"
  #   initial_option: "640x480 (VGA)"
  #   restore_value: true
  #   on_value:
  #     then:
  #     - lambda: |-
  #         if ("160x120 (QQVGA)" == x )
  #           id(esp32cam).set_jpeg_quality(esphome::esp32_camera::ESP32_CAMERA_SIZE_160X120);
  #         else if ("176x144 (QCIF)" == x)
  #           id(esp32cam).set_jpeg_quality(esphome::esp32_camera::ESP32_CAMERA_SIZE_176X144);
  #         else if ("240x176 (HQVGA)" == x)
  #           id(esp32cam).set_jpeg_quality(esphome::esp32_camera::ESP32_CAMERA_SIZE_240X176);
  #         else if ("320x240 (QVGA)" == x)
  #           id(esp32cam).set_jpeg_quality(esphome::esp32_camera::ESP32_CAMERA_SIZE_320X240);
  #         else if ("400x296 (CIF)" == x)
  #           id(esp32cam).set_jpeg_quality(esphome::esp32_camera::ESP32_CAMERA_SIZE_400X296);
  #         else if ("640x480 (VGA)" == x)
  #           id(esp32cam).set_jpeg_quality(esphome::esp32_camera::ESP32_CAMERA_SIZE_640X480);
  #         else if ("800x600 (SVGA)" == x)
  #           id(esp32cam).set_jpeg_quality(esphome::esp32_camera::ESP32_CAMERA_SIZE_800X600);
  #         else if ("1024x768 (XGA)" == x)
  #           id(esp32cam).set_jpeg_quality(esphome::esp32_camera::ESP32_CAMERA_SIZE_1024X768);
  #         else if ("1280x1024 (SXGA)" == x)
  #           id(esp32cam).set_jpeg_quality(esphome::esp32_camera::ESP32_CAMERA_SIZE_1280X1024);
  #         else if ("1600x1200 (UXGA)" == x)
  #           id(esp32cam).set_jpeg_quality(esphome::esp32_camera::ESP32_CAMERA_SIZE_1600X1200);
  #         id(esp32cam_update) = true;

  - platform: template
    name: "Special effect"
    icon: mdi:filter
    entity_category: config
    optimistic: true
    options:
      - "none"
      - "negative"
      - "grayscale"
      - "red_tint"
      - "green_tint"
      - "blue_tint"
      - "sepia"
    initial_option: "none"
    restore_value: true
    on_value:
      then:
      - lambda: |-
          if ("none" == x)
            id(esp32cam).set_special_effect(esphome::esp32_camera::ESP32_SPECIAL_EFFECT_NONE);
          else if ("negative" == x)
            id(esp32cam).set_special_effect(esphome::esp32_camera::ESP32_SPECIAL_EFFECT_NEGATIVE);
          else if ("grayscale" == x)
            id(esp32cam).set_special_effect(esphome::esp32_camera::ESP32_SPECIAL_EFFECT_GRAYSCALE);
          else if ("red_tint" == x)
            id(esp32cam).set_special_effect(esphome::esp32_camera::ESP32_SPECIAL_EFFECT_RED_TINT);
          else if ("green_tint" == x)
            id(esp32cam).set_special_effect(esphome::esp32_camera::ESP32_SPECIAL_EFFECT_GREEN_TINT);
          else if ("blue_tint" == x)
            id(esp32cam).set_special_effect(esphome::esp32_camera::ESP32_SPECIAL_EFFECT_BLUE_TINT);
          else if ("sepia" == x)
            id(esp32cam).set_special_effect(esphome::esp32_camera::ESP32_SPECIAL_EFFECT_SEPIA);
          id(esp32cam_update) = true;

  - platform: template
    name: "Auto exposure compensation"
    icon: mdi:auto-fix
    entity_category: config
    optimistic: true
    options:
      - "manual"
      - "auto"
    initial_option: "auto"
    restore_value: true
    on_value:
      then:
      - lambda: |-
          if ("manual" == x)
            id(esp32cam).set_aec_mode(esphome::esp32_camera::ESP32_GC_MODE_MANU);
          else if ("auto" == x)
            id(esp32cam).set_aec_mode(esphome::esp32_camera::ESP32_GC_MODE_AUTO);
          id(esp32cam_update) = true;

  - platform: template
    name: "Auto gain control"
    icon: mdi:auto-fix
    entity_category: config
    optimistic: true
    options:
      - "manual"
      - "auto"
    initial_option: "auto"
    restore_value: true
    on_value:
      then:
      - lambda: |-
          if ("manual" == x)
            id(esp32cam).set_agc_mode(esphome::esp32_camera::ESP32_GC_MODE_MANU);
          else if ("auto" == x)
            id(esp32cam).set_agc_mode(esphome::esp32_camera::ESP32_GC_MODE_AUTO);
          id(esp32cam_update) = true;

  - platform: template
    name: "Auto gain control ceiling"
    icon: mdi:auto-fix
    entity_category: config
    optimistic: true
    options:
      - "2x"
      - "4x"
      - "8x"
      - "16x"
      - "32x"
      - "64x"
      - "128x"
    initial_option: "2x"
    restore_value: true
    on_value:
      then:
      - lambda: |-
          if ("2x" == x)
            id(esp32cam).set_agc_gain_ceiling(esphome::esp32_camera::ESP32_GAINCEILING_2X);
          else if ("4x" == x)
            id(esp32cam).set_agc_gain_ceiling(esphome::esp32_camera::ESP32_GAINCEILING_4X);
          else if ("8x" == x)
            id(esp32cam).set_agc_gain_ceiling(esphome::esp32_camera::ESP32_GAINCEILING_8X);
          else if ("16x" == x)
            id(esp32cam).set_agc_gain_ceiling(esphome::esp32_camera::ESP32_GAINCEILING_16X);
          else if ("32x" == x)
            id(esp32cam).set_agc_gain_ceiling(esphome::esp32_camera::ESP32_GAINCEILING_32X);
          else if ("64x" == x)
            id(esp32cam).set_agc_gain_ceiling(esphome::esp32_camera::ESP32_GAINCEILING_64X);
          else if ("128x" == x)
            id(esp32cam).set_agc_gain_ceiling(esphome::esp32_camera::ESP32_GAINCEILING_128X);
          id(esp32cam_update) = true;

  - platform: template
    name: "White balance mode"
    icon: mdi:white-balance-auto
    entity_category: config
    optimistic: true
    options:
      - "auto"
      - "sunny"
      - "cloudy"
      - "office"
      - "home"
    initial_option: "auto"
    restore_value: true
    on_value:
      then:
      - lambda: |-
          if ("auto" == x)
            id(esp32cam).set_wb_mode(esphome::esp32_camera::ESP32_WB_MODE_AUTO);
          else if ("sunny" == x)
            id(esp32cam).set_wb_mode(esphome::esp32_camera::ESP32_WB_MODE_SUNNY);
          else if ("cloudy" == x)
            id(esp32cam).set_wb_mode(esphome::esp32_camera::ESP32_WB_MODE_CLOUDY);
          else if ("office" == x)
            id(esp32cam).set_wb_mode(esphome::esp32_camera::ESP32_WB_MODE_OFFICE);
          else if ("home" == x)
            id(esp32cam).set_wb_mode(esphome::esp32_camera::ESP32_WB_MODE_HOME);
          id(esp32cam_update) = true;

number:
  - platform: template
    name: "JPEG Quality"
    icon: mdi:file-jpg-box
    entity_category: config
    mode: slider
    initial_value: 10
    min_value: 10
    max_value: 63
    step: 1
    set_action:
      - lambda: |-
          id(esp32cam).set_jpeg_quality(x);
          id(esp32cam_update) = true;

  - platform: template
    name: "Contrast"
    icon: mdi:contrast-box
    entity_category: config
    mode: slider
    initial_value: 0
    min_value: -2
    max_value: 2
    step: 1
    set_action:
      - lambda: |-
          id(esp32cam).set_contrast(x);
          id(esp32cam_update) = true;

  - platform: template
    name: "Brightness"
    icon: mdi:brightness-6
    entity_category: config
    mode: slider
    initial_value: 0
    min_value: -2
    max_value: 2
    step: 1
    set_action:
      - lambda: |-
          id(esp32cam).set_brightness(x);
          id(esp32cam_update) = true;

  - platform: template
    name: "Saturation"
    icon: mdi:palette-outline
    entity_category: config
    mode: slider
    initial_value: 0
    min_value: -2
    max_value: 2
    step: 1
    set_action:
      - lambda: |-
          id(esp32cam).set_saturation(x);
          id(esp32cam_update) = true;

  - platform: template
    name: "Auto exposure level"
    icon: mdi:auto-fix
    entity_category: config
    mode: slider
    initial_value: 0
    min_value: -2
    max_value: 2
    step: 1
    set_action:
      - lambda: |-
          id(esp32cam).set_ae_level(x);
          id(esp32cam_update) = true;

  - platform: template
    name: "Auto exposure compensation value"
    icon: mdi:auto-fix
    entity_category: config
    mode: slider
    initial_value: 300
    min_value: 0
    max_value: 1200
    step: 1
    set_action:
      - lambda: |-
          id(esp32cam).set_aec_value(x);
          id(esp32cam_update) = true;

  - platform: template
    name: "Auto gain control value"
    icon: mdi:auto-fix
    entity_category: config
    mode: slider
    initial_value: 0
    min_value: 0
    max_value: 30
    step: 1
    set_action:
      - lambda: |-
          id(esp32cam).set_aec_value(x);
          id(esp32cam_update) = true;
Rudd-O commented 5 months ago

I will soon try your code.

tonyis49 commented 3 months ago

Many thanks for this. I have spent the last 2 days trying to incorporate the camera into ESPHOME with the same parameters available as with the arduino installation and was about to give up until I found your code. So far, with a couple of minor tweaks, its works just fine..

corgan2222 commented 2 months ago

In my tests:

* Changing most settings seem to work (eg: effect, auto-gain).

* Some settings (eg: brightness) seem to not do anything (though I'm not sure if this is the limited effect of the setting itself...).

* Quality and framesize setting were missing, [fornellas/esphome@0022486](https://github.com/fornellas/esphome/commit/0022486422cd02491c7204fc82f467c19dd0c4a4) should fix that.

  * Quality changes does work.
  * Resolution change does not work, and leads to a crash after any change.

PS: for reference, this is the config I'm using which enable various settings to be adjusted at either ESPHome web UI or at Home Assistant:

esphome:
  name: "esp32-cam"
  friendly_name: "ESP32 Cam"
  name_add_mac_suffix: true
  on_boot:
    - priority: 600.0
      then:
        - lambda: |-
            // Default: Connecting
            id(wifi_led_pwm).set_level(0.5);
            id(wifi_led_pwm).set_period(1000);
            id(wifi_led_pwm).turn_on();
        - script.execute: fast_reboot_factory_reset
  on_loop:
    then:
      - lambda: |-
          // Captive portal
          if(id(wifi_component).has_ap() && !id(wifi_component).has_sta()) {
            id(wifi_led_pwm).set_level(0.5);
            id(wifi_led_pwm).set_period(200);
          } else {
            id(wifi_led_pwm).set_period(1000);
            // Connected
            if(id(wifi_component).is_connected()) {
              id(wifi_led_pwm).set_level(1);
              if (id(esp32cam_update)) {
                id(esp32cam).update_camera_parameters();
                id(esp32cam_update) = false;
              }
            // Connecting
            } else
              id(wifi_led_pwm).set_level(0.5);
          }

esp32:
  board: esp32cam

external_components:
  - source:
      type: git
      url: https://github.com/fornellas/esphome
      ref: fix_esp32_camera_update
    components: [ esp32_camera ]
    refresh: 0s

globals:
  - id: fast_reboot
    type: int
    restore_value: yes
    initial_value: '0'

  - id: factory_reset_reboot_counter
    type: int
    initial_value: '5'

  - id: esp32cam_update
    type: bool
    initial_value: 'false'

logger:
  baud_rate: 0
  level: INFO
  deassert_rts_dtr: true

script:
  - id: fast_reboot_factory_reset
    then:
      - if:
          condition:
            lambda: return ( id(fast_reboot) >= id(factory_reset_reboot_counter) );
          then:
            - lambda: |-
                ESP_LOGI("Fast Boot Factory Reset", "Performing factotry reset");
                id(fast_reboot) = 0;
                fast_reboot->loop();
                global_preferences->sync();
            - button.press: factory_reset_button
      - lambda: |-
          if(id(fast_reboot) > 0)
            ESP_LOGI("Fast Boot Factory Reset", "Quick reboot %d/%d, do it %d more times to factory reset", id(fast_reboot), id(factory_reset_reboot_counter), id(factory_reset_reboot_counter) - id(fast_reboot));
          id(fast_reboot) += 1;
          fast_reboot->loop();
          global_preferences->sync();
      - delay: 10s
      - lambda: |-
          id(fast_reboot) = 0;
          fast_reboot->loop();
          global_preferences->sync();

wifi:
  id: wifi_component
  ap:
    ap_timeout: 0s
  reboot_timeout: 0s
  power_save_mode: none

captive_portal:

web_server:

api:
  reboot_timeout: 0s

ota:
  safe_mode: false

esp32_camera:
  external_clock:
    pin:
      number: GPIO0
      ignore_strapping_warning: true
    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
  name: "Camera"
  id: esp32cam
  max_framerate: "60 fps"
  # resolution: 160x120 # (QQVGA)
  # resolution: 176x144 # (QCIF)
  # resolution: 240x176 # (HQVGA)
  # resolution: 320x240 # (QVGA)
  # resolution: 400x296 # (CIF) Sub-sampled
  # resolution: 640x480 # (VGA)
  resolution: 800x600 # (SVGA) Sub-sampled
  # resolution: 1024x768 # (XGA)
  # resolution: 1280x1024 # (SXGA)
  # resolution: 1600x1200 # (UXGA) Native
  on_stream_start:
    then:
      - if:
          condition:
            lambda: return id(flash_automatic).state;
          then:
            - switch.turn_on: flash
  on_stream_stop:
    then:
      - if:
          condition:
            lambda: return id(flash_automatic).state;
          then:
            - switch.turn_off: flash

esp32_camera_web_server:
  - port: 8080
    mode: stream
  - port: 8081
    mode: snapshot

binary_sensor:
  - platform: status
    name: "Status"

sensor:
  - platform: uptime
    name: 'Uptime'
    update_interval: 15s

  - platform: wifi_signal
    name: "WiFi Signal"
    update_interval: 15s

output:
  - platform: slow_pwm
    id: wifi_led_pwm
    period: 1s
    pin: GPIO33
    inverted: true

button:
  - platform: factory_reset
    id: factory_reset_button
    name: "Factory reset"

  - platform: restart
    name: "Restart"

  - platform: safe_mode
    name: "Safe Mode"

text_sensor:
  - platform: wifi_info
    ip_address:
      name: "Wifi Info: IP Address"
    ssid:
      name: "Wifi Info: SSID"
    bssid:
      name: "Wifi Info: BSSID"
    mac_address:
      name: "Wifi Info: MAC Address"
    dns_address:
      name: "Wifi Info: DNS Address"

switch:
  - platform: template
    name: "Vertical flip"
    icon: mdi:swap-vertical
    entity_category: config
    restore_mode: RESTORE_DEFAULT_ON
    optimistic: true
    turn_on_action:
      - lambda: |-
          id(esp32cam).set_vertical_flip(true);
          id(esp32cam_update) = true;
    turn_off_action:
      - lambda: |-
          id(esp32cam).set_vertical_flip(false);
          id(esp32cam_update) = true;

  - platform: template
    name: "Horizontal mirror"
    icon: mdi:reflect-horizontal
    entity_category: config
    restore_mode: RESTORE_DEFAULT_ON
    optimistic: true
    turn_on_action:
      - lambda: |-
          id(esp32cam).set_horizontal_mirror(true);
          id(esp32cam_update) = true;
    turn_off_action:
      - lambda: |-
          id(esp32cam).set_horizontal_mirror(false);
          id(esp32cam_update) = true;

  - platform: template
    name: "Auto exposure compensation 2"
    icon: mdi:auto-fix
    entity_category: config
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true
    turn_on_action:
      - lambda: |-
          id(esp32cam).set_aec2(true);
          id(esp32cam_update) = true;
    turn_off_action:
      - lambda: |-
          id(esp32cam).set_aec2(false);
          id(esp32cam_update) = true;

  - platform: template
    name: "Flash automatic on stream"
    id: flash_automatic
    icon: mdi:flash
    entity_category: config
    restore_mode: RESTORE_DEFAULT_ON
    optimistic: true

  - platform: gpio
    name: "Flash"
    icon: mdi:flash
    id: flash
    pin: GPIO4

select:
  # FIXME when reset after boot, yields panic some time after
  # - platform: template
  #   name: "Resolution"
  #   icon: mdi:video-standard-definition
  #   entity_category: config
  #   optimistic: true
  #   options:
  #     - "160x120 (QQVGA)"
  #     - "176x144 (QCIF)"
  #     - "240x176 (HQVGA)"
  #     - "320x240 (QVGA)"
  #     - "400x296 (CIF)"
  #     - "640x480 (VGA)"
  #     - "800x600 (SVGA)"
  #     - "1024x768 (XGA)"
  #     - "1280x1024 (SXGA)"
  #     - "1600x1200 (UXGA)"
  #   initial_option: "640x480 (VGA)"
  #   restore_value: true
  #   on_value:
  #     then:
  #     - lambda: |-
  #         if ("160x120 (QQVGA)" == x )
  #           id(esp32cam).set_jpeg_quality(esphome::esp32_camera::ESP32_CAMERA_SIZE_160X120);
  #         else if ("176x144 (QCIF)" == x)
  #           id(esp32cam).set_jpeg_quality(esphome::esp32_camera::ESP32_CAMERA_SIZE_176X144);
  #         else if ("240x176 (HQVGA)" == x)
  #           id(esp32cam).set_jpeg_quality(esphome::esp32_camera::ESP32_CAMERA_SIZE_240X176);
  #         else if ("320x240 (QVGA)" == x)
  #           id(esp32cam).set_jpeg_quality(esphome::esp32_camera::ESP32_CAMERA_SIZE_320X240);
  #         else if ("400x296 (CIF)" == x)
  #           id(esp32cam).set_jpeg_quality(esphome::esp32_camera::ESP32_CAMERA_SIZE_400X296);
  #         else if ("640x480 (VGA)" == x)
  #           id(esp32cam).set_jpeg_quality(esphome::esp32_camera::ESP32_CAMERA_SIZE_640X480);
  #         else if ("800x600 (SVGA)" == x)
  #           id(esp32cam).set_jpeg_quality(esphome::esp32_camera::ESP32_CAMERA_SIZE_800X600);
  #         else if ("1024x768 (XGA)" == x)
  #           id(esp32cam).set_jpeg_quality(esphome::esp32_camera::ESP32_CAMERA_SIZE_1024X768);
  #         else if ("1280x1024 (SXGA)" == x)
  #           id(esp32cam).set_jpeg_quality(esphome::esp32_camera::ESP32_CAMERA_SIZE_1280X1024);
  #         else if ("1600x1200 (UXGA)" == x)
  #           id(esp32cam).set_jpeg_quality(esphome::esp32_camera::ESP32_CAMERA_SIZE_1600X1200);
  #         id(esp32cam_update) = true;

  - platform: template
    name: "Special effect"
    icon: mdi:filter
    entity_category: config
    optimistic: true
    options:
      - "none"
      - "negative"
      - "grayscale"
      - "red_tint"
      - "green_tint"
      - "blue_tint"
      - "sepia"
    initial_option: "none"
    restore_value: true
    on_value:
      then:
      - lambda: |-
          if ("none" == x)
            id(esp32cam).set_special_effect(esphome::esp32_camera::ESP32_SPECIAL_EFFECT_NONE);
          else if ("negative" == x)
            id(esp32cam).set_special_effect(esphome::esp32_camera::ESP32_SPECIAL_EFFECT_NEGATIVE);
          else if ("grayscale" == x)
            id(esp32cam).set_special_effect(esphome::esp32_camera::ESP32_SPECIAL_EFFECT_GRAYSCALE);
          else if ("red_tint" == x)
            id(esp32cam).set_special_effect(esphome::esp32_camera::ESP32_SPECIAL_EFFECT_RED_TINT);
          else if ("green_tint" == x)
            id(esp32cam).set_special_effect(esphome::esp32_camera::ESP32_SPECIAL_EFFECT_GREEN_TINT);
          else if ("blue_tint" == x)
            id(esp32cam).set_special_effect(esphome::esp32_camera::ESP32_SPECIAL_EFFECT_BLUE_TINT);
          else if ("sepia" == x)
            id(esp32cam).set_special_effect(esphome::esp32_camera::ESP32_SPECIAL_EFFECT_SEPIA);
          id(esp32cam_update) = true;

  - platform: template
    name: "Auto exposure compensation"
    icon: mdi:auto-fix
    entity_category: config
    optimistic: true
    options:
      - "manual"
      - "auto"
    initial_option: "auto"
    restore_value: true
    on_value:
      then:
      - lambda: |-
          if ("manual" == x)
            id(esp32cam).set_aec_mode(esphome::esp32_camera::ESP32_GC_MODE_MANU);
          else if ("auto" == x)
            id(esp32cam).set_aec_mode(esphome::esp32_camera::ESP32_GC_MODE_AUTO);
          id(esp32cam_update) = true;

  - platform: template
    name: "Auto gain control"
    icon: mdi:auto-fix
    entity_category: config
    optimistic: true
    options:
      - "manual"
      - "auto"
    initial_option: "auto"
    restore_value: true
    on_value:
      then:
      - lambda: |-
          if ("manual" == x)
            id(esp32cam).set_agc_mode(esphome::esp32_camera::ESP32_GC_MODE_MANU);
          else if ("auto" == x)
            id(esp32cam).set_agc_mode(esphome::esp32_camera::ESP32_GC_MODE_AUTO);
          id(esp32cam_update) = true;

  - platform: template
    name: "Auto gain control ceiling"
    icon: mdi:auto-fix
    entity_category: config
    optimistic: true
    options:
      - "2x"
      - "4x"
      - "8x"
      - "16x"
      - "32x"
      - "64x"
      - "128x"
    initial_option: "2x"
    restore_value: true
    on_value:
      then:
      - lambda: |-
          if ("2x" == x)
            id(esp32cam).set_agc_gain_ceiling(esphome::esp32_camera::ESP32_GAINCEILING_2X);
          else if ("4x" == x)
            id(esp32cam).set_agc_gain_ceiling(esphome::esp32_camera::ESP32_GAINCEILING_4X);
          else if ("8x" == x)
            id(esp32cam).set_agc_gain_ceiling(esphome::esp32_camera::ESP32_GAINCEILING_8X);
          else if ("16x" == x)
            id(esp32cam).set_agc_gain_ceiling(esphome::esp32_camera::ESP32_GAINCEILING_16X);
          else if ("32x" == x)
            id(esp32cam).set_agc_gain_ceiling(esphome::esp32_camera::ESP32_GAINCEILING_32X);
          else if ("64x" == x)
            id(esp32cam).set_agc_gain_ceiling(esphome::esp32_camera::ESP32_GAINCEILING_64X);
          else if ("128x" == x)
            id(esp32cam).set_agc_gain_ceiling(esphome::esp32_camera::ESP32_GAINCEILING_128X);
          id(esp32cam_update) = true;

  - platform: template
    name: "White balance mode"
    icon: mdi:white-balance-auto
    entity_category: config
    optimistic: true
    options:
      - "auto"
      - "sunny"
      - "cloudy"
      - "office"
      - "home"
    initial_option: "auto"
    restore_value: true
    on_value:
      then:
      - lambda: |-
          if ("auto" == x)
            id(esp32cam).set_wb_mode(esphome::esp32_camera::ESP32_WB_MODE_AUTO);
          else if ("sunny" == x)
            id(esp32cam).set_wb_mode(esphome::esp32_camera::ESP32_WB_MODE_SUNNY);
          else if ("cloudy" == x)
            id(esp32cam).set_wb_mode(esphome::esp32_camera::ESP32_WB_MODE_CLOUDY);
          else if ("office" == x)
            id(esp32cam).set_wb_mode(esphome::esp32_camera::ESP32_WB_MODE_OFFICE);
          else if ("home" == x)
            id(esp32cam).set_wb_mode(esphome::esp32_camera::ESP32_WB_MODE_HOME);
          id(esp32cam_update) = true;

number:
  - platform: template
    name: "JPEG Quality"
    icon: mdi:file-jpg-box
    entity_category: config
    mode: slider
    initial_value: 10
    min_value: 10
    max_value: 63
    step: 1
    set_action:
      - lambda: |-
          id(esp32cam).set_jpeg_quality(x);
          id(esp32cam_update) = true;

  - platform: template
    name: "Contrast"
    icon: mdi:contrast-box
    entity_category: config
    mode: slider
    initial_value: 0
    min_value: -2
    max_value: 2
    step: 1
    set_action:
      - lambda: |-
          id(esp32cam).set_contrast(x);
          id(esp32cam_update) = true;

  - platform: template
    name: "Brightness"
    icon: mdi:brightness-6
    entity_category: config
    mode: slider
    initial_value: 0
    min_value: -2
    max_value: 2
    step: 1
    set_action:
      - lambda: |-
          id(esp32cam).set_brightness(x);
          id(esp32cam_update) = true;

  - platform: template
    name: "Saturation"
    icon: mdi:palette-outline
    entity_category: config
    mode: slider
    initial_value: 0
    min_value: -2
    max_value: 2
    step: 1
    set_action:
      - lambda: |-
          id(esp32cam).set_saturation(x);
          id(esp32cam_update) = true;

  - platform: template
    name: "Auto exposure level"
    icon: mdi:auto-fix
    entity_category: config
    mode: slider
    initial_value: 0
    min_value: -2
    max_value: 2
    step: 1
    set_action:
      - lambda: |-
          id(esp32cam).set_ae_level(x);
          id(esp32cam_update) = true;

  - platform: template
    name: "Auto exposure compensation value"
    icon: mdi:auto-fix
    entity_category: config
    mode: slider
    initial_value: 300
    min_value: 0
    max_value: 1200
    step: 1
    set_action:
      - lambda: |-
          id(esp32cam).set_aec_value(x);
          id(esp32cam_update) = true;

  - platform: template
    name: "Auto gain control value"
    icon: mdi:auto-fix
    entity_category: config
    mode: slider
    initial_value: 0
    min_value: 0
    max_value: 30
    step: 1
    set_action:
      - lambda: |-
          id(esp32cam).set_aec_value(x);
          id(esp32cam_update) = true;

thank you so much for this! Works fine!