I was able to get this device working and integrated with HomeAssistant. Sharing to hopefully help save others some time…
substitutions:
name: esphome-web-123456
friendly_name: Seeed Xaio w/Camera 123456
# Pin definitions
camera_sda_pin: GPIO40
camera_scl_pin: GPIO39
camera_data_pins: GPIO15, GPIO17, GPIO18, GPIO16, GPIO14, GPIO12, GPIO11, GPIO48
camera_vsync_pin: GPIO38
camera_href_pin: GPIO47
camera_pclk_pin: GPIO13
camera_xclk_pin: GPIO10 # Assign the appropriate GPIO pin for xclk
esphome:
name: ${name}
friendly_name: ${friendly_name}
name_add_mac_suffix: false
platformio_options:
# board_build.flash_mode: dio
board_build.mcu: esp32s3
build_flags: -DBOARD_HAS_PSRAM
board_build.arduino.memory_type: qio_opi
board_build.f_flash: 80000000L
board_build.flash_mode: qio
# https://wiki.seeedstudio.com/XIAO_ESP32S3_esphome/
project:
name: esphome.web
version: '1.0'
esp32:
board: esp32-s3-devkitc-1
framework:
type: arduino
#version: latest
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
api:
encryption:
key: <your api key here>
ota: # Allow Over-The-Air updates
- platform: esphome
logger:
web_server:
sensor:
- platform: uptime
name: "Uptime"
id: esp_uptime
icon: mdi:clock-start
entity_category: "diagnostic"
update_interval: 60s
- platform: uptime
name: "ESP32 Camera Uptime Sensor"
id: foo_uptime
entity_category: "diagnostic"
icon: mdi:information
- platform: wifi_signal
name: "ESP32 Camera WiFi Signal"
id: foo_wifi
entity_category: "diagnostic"
icon: mdi:information
update_interval: 60s
text_sensor:
- platform: wifi_info
ip_address:
name: "Wifi IP Address"
id: wifi_ip_address
entity_category: "diagnostic"
ssid:
name: "WiFi SSID"
id: wifi_ssid
icon: mdi:ip-outline
entity_category: diagnostic
mac_address:
name: "WiFi MAC Address"
id: wifi_mac_address
icon: mdi:ip-outline
entity_category: diagnostic
# Camera configuration
esp32_camera:
name: "Seeed XIAO ESP32-S3 Camera"
id: seeed_camera
external_clock:
pin: ${camera_xclk_pin}
frequency: 12MHz
i2c_pins:
sda: ${camera_sda_pin}
scl: ${camera_scl_pin}
data_pins:
- GPIO15
- GPIO17
- GPIO18
- GPIO16
- GPIO14
- GPIO12
- GPIO11
- GPIO48
vsync_pin: ${camera_vsync_pin}
href_pin: ${camera_href_pin}
pixel_clock_pin: ${camera_pclk_pin}
resolution: 1280X1024
jpeg_quality: 10
max_framerate: 15 fps
switch:
- platform: template
name: "Camera Control"
id: camera_control
optimistic: True
- platform: restart
id: foo_restart
name: "ESP32 Camera Restart"
1 Like
My rudimentary lovelace card
- type: sections
max_columns: 4
icon: mdi:camera
path: seeed-w-camera
title: Seeed w Camera
sections:
- type: grid
cards:
- type: heading
heading: New section
- type: tile
entity: sensor.esphome_web_20c368_wifi_ip_address
name: IP Address
- type: tile
entity: sensor.esphome_web_20c368_wifi_mac_address
name: MAC Address
- type: tile
entity: sensor.esphome_web_20c368_wifi_ssid
name: Wifi SSID
- type: tile
entity: sensor.esphome_web_20c368_esp32_camera_wifi_signal
name: Wifi Signal
icon: mdi:ip-outline
- type: tile
entity: sensor.esphome_web_20c368_uptime
name: Uptime (s)
- type: tile
entity: button.esphome_web_20c368_restart
name: Restart
- type: grid
cards:
- type: heading
heading: New section
- type: tile
entity: camera.esphome_web_20c368_seeed_xiao_esp32_s3_camera
show_entity_picture: true
- camera_view: live
type: picture-glance
title: SecretAgent
image: https://demo.home-assistant.io/stub_config/kitchen.png
entities:
- binary_sensor.remote_ui
- sensor.sun_next_dawn
camera_image: camera.esphome_web_20c368_seeed_xiao_esp32_s3_camera
1 Like
sonra1
(Sonra1)
March 28, 2025, 4:58pm
3
Hi
Did you use a general ov5640 camera or the xiao ov5640 ?
I tried to connect a general ov5640 and get error
E (17) camera: Camera probe failed with error 0x105(ESP_ERR_NOT_FOUND)
E (38) gdma: gdma_disconnect(314): no peripheral is connected to the channel
Camera init failed with error 0x105
Thanks Doron
latest updates
esphome:
name: ${name}
friendly_name: ${friendly_name}
name_add_mac_suffix: false
platformio_options:
board_build.mcu: esp32s3
build_flags: -DBOARD_HAS_PSRAM
board_build.arduino.memory_type: qio_opi
board_build.f_flash: 80000000L
board_build.flash_mode: qio
# https://wiki.seeedstudio.com/XIAO_ESP32S3_esphome/
project:
name: esphome.web
version: '1.0'
esp32:
board: esp32-s3-devkitc-1
framework:
type: arduino
version: latest
substitutions:
name: esphome-web-20c368
friendly_name: Seeed Xaio w/Camera 20C368
# Pin definitions
camera_sda_pin: GPIO40
camera_scl_pin: GPIO39
camera_data_pins: GPIO15, GPIO17, GPIO18, GPIO16, GPIO14, GPIO12, GPIO11, GPIO48
camera_vsync_pin: GPIO38
camera_href_pin: GPIO47
camera_pclk_pin: GPIO13
camera_xclk_pin: GPIO10 #External clock
camera_power_pin: GPIO1 #use to turn camera on an off?
user_led_pin: GPIO21
camera_max_framerate: '15fps'
camera_jpeg_quality: '10'
camera_vertical_flip: 'true'
camera_horizontal_mirror: 'true'
camera_contrast: '0'
camera_brightness: '0'
camera_saturation: '0'
camera_special_effect: 'none'
camera_aec_mode: 'auto'
camera_aec2: 'false'
camera_ae_level: '0'
camera_aec_value: '300'
camera_agc_mode: 'auto'
camera_agc_value: '0'
camera_agc_ceiling: '2x'
camera_white_balance: 'auto'
# Camera configuration
esp32_camera:
name: "Seeed XIAO ESP32-S3 Camera"
id: seeed_camera
#connection options
data_pins:
- GPIO15
- GPIO17
- GPIO18
- GPIO16
- GPIO14
- GPIO12
- GPIO11
- GPIO48
vsync_pin: ${camera_vsync_pin}
href_pin: ${camera_href_pin}
pixel_clock_pin: ${camera_pclk_pin}
external_clock:
pin: ${camera_xclk_pin}
frequency: 12MHz
i2c_pins:
sda: ${camera_sda_pin}
scl: ${camera_scl_pin}
#power_down_pin: !!! done by software switch GPIO1
#frame settings
max_framerate: ${camera_max_framerate}
idle_framerate: 0.1fps #default
#Image Settings
resolution: 1280X1024
jpeg_quality: ${camera_jpeg_quality}
vertical_flip: ${camera_vertical_flip}
horizontal_mirror: ${camera_horizontal_mirror}
brightness: ${camera_brightness}
contrast: ${camera_contrast}
saturation: ${camera_saturation}
special_effect: ${camera_special_effect}
#Exposure settings
aec_mode: ${camera_aec_mode}
aec2: ${camera_aec2}
ae_level: ${camera_ae_level}
aec_value: ${camera_ae_level}
#Sensor gain settings
agc_mode: ${camera_aec_mode}
agc_gain_ceiling: ${camera_agc_ceiling}
agc_value: ${camera_agc_value}
#White balance settings
wb_mode: ${camera_white_balance}
web_server:
packages:
device_base: !include soup_device_base.yaml
camera_config: !include z_config_ov5640_camera.yaml
switch:
- platform: template
name: "Camera Control"
id: camera_control_template
optimistic: True
turn_on_action:
then:
- switch.turn_on: camera_power_output
turn_off_action:
then:
- switch.turn_off: camera_power_output
- platform: restart
id: seecd_restart
name: "Camera Restart"
- platform: gpio
pin: ${camera_power_pin}
id: camera_power_output
name: "camera output"
- platform: template
name: "Reset Camera"
id: reset_camera_settings
turn_on_action: # Reset values to default
- lambda: |-
// Set camera to default values using substitutions and proper conversions
id(seeed_camera).set_contrast(0);
id(seeed_camera).set_brightness(0);
- switch.turn_off: reset_camera_settings # Ensure the switch is momentary
light:
- platform: monochromatic
output: onboard_user_led
name: "Onboard LED"
id: onboard_led
output:
- platform: ledc
pin: ${user_led_pin}
id: onboard_user_led
frequency: 5000
inverted: true #aligns switch and LED
sensor:
- platform: homeassistant
id: camera_jpeg_quality
entity_id: input_number.camera_jpeg_quality
- platform: homeassistant
id: camera_vertical_flip
entity_id: input_boolean.camera_vertical_flip
- platform: homeassistant
id: camera_horizontal_mirror
entity_id: input_boolean.camera_horizontal_mirror
- platform: homeassistant
id: camera_brightness
entity_id: input_number.camera_brightness
- platform: homeassistant
id: camera_contrast
entity_id: input_number.camera_contrast
- platform: homeassistant
id: camera_saturation
entity_id: input_number.camera_saturation
- platform: homeassistant
id: camera_special_effect
entity_id: input_select.camera_special_effect
- platform: homeassistant
id: camera_aec_mode
entity_id: input_select.camera_aec_mode
- platform: homeassistant
id: camera_aec2
entity_id: input_boolean.camera_aec2
- platform: homeassistant
id: camera_ae_level
entity_id: input_number.camera_ae_level
- platform: homeassistant
id: camera_ae_value
entity_id: input_number.camera_ae_value
- platform: homeassistant
id: camera_agc_mode
entity_id: input_select.camera_agc_mode
- platform: homeassistant
id: camera_agc_ceiling
entity_id: input_number.camera_agc_ceiling
- platform: homeassistant
id: camera_agc_value
entity_id: input_number.camera_agc_value
- platform: homeassistant
id: camera_white_balance
entity_id: input_select.camera_white_balance
1 Like
frabar55
(Francesco Mongelli)
August 1, 2025, 4:03pm
5
Hi Steve, I’m trying to follow your solution but without success. I always receive an error : [esp32_camera:199]: Got invalid frame from camera! I have the same your hardware. Could you help me?
saxbyandrew
(Andrew Saxby)
September 29, 2025, 3:35am
6
Thanks for this post it saved me a lot of time, messing with GPIO settings. However if I may ask a couple of questions please"
I have Seeed Studio XIAO ESP32S3 Sense with a OV5640.
I does NOT have an external antenna.
It runs streams the image painfully slow, I get one image every 10 seconds, I’m I just expecting to much from this device? Has anyone had a smooth video stream?
Many Thanks for any suggestions of feedback
I’m not really using it in ‘production’. It seemed slow overall… antenna may help?
Chompy80
(Chompy80)
October 24, 2025, 12:23am
8
I’m attempting to get this to work using your latest updates posted on June 29th. When I go to compile I get the error, “Error reading file /config/esphome/soup_device_base.yaml: [Errno 2] No such file or directory: ‘/config/esphome/soup_device_base.yaml’”
I’m assuming these files are needed in order to use the code:
packages:
device_base: !include soup_device_base.yaml
camera_config: !include z_config_ov5640_camera.yaml
I’ve been offline with some home/family stuff. If it’s the same exact hardware - not sure why my provided code wouldn’t work. What do you get or see?
soup_base is a file I include in all my esps… just standard stuff for ESP diagnostics
type or paste code here
# Enable Home Assistant API
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
api:
id: api_connected
encryption:
key: 1<your key here>
on_client_connected:
then:
- logger.log: "Wi-Fi connected"
- component.update: wifi_ip_address
ota: # Allow Over-The-Air updates
- platform: esphome
logger:
### SENSORS
sensor:
- platform: uptime
name: "Uptime"
id: esp_uptime
accuracy_decimals: 0
icon: mdi:clock-start
update_interval: 60s
entity_category: diagnostic
internal: true
filters:
- delta: 60 # only push if uptime changes by 60s
- platform: internal_temperature
name: "ESP Onboard Temp (°C)"
id: esp_onboard_temp_c
unit_of_measurement: "°C"
accuracy_decimals: 1
icon: mdi:temperature-celsius
update_interval: 600s
entity_category: diagnostic
device_class: temperature
state_class: measurement
on_value:
then:
- component.update: esp_onboard_temp_f
- platform: template
name: "ESP Onboard Temp (°F)"
id: esp_onboard_temp_f
accuracy_decimals: 1
icon: mdi:temperature-fahrenheit
unit_of_measurement: "°F"
update_interval: never # [derived from °C]
entity_category: diagnostic
device_class: temperature
state_class: measurement
lambda: |-
if (!id(esp_onboard_temp_c).has_state()) return NAN;
return id(esp_onboard_temp_c).state * 9.0 / 5.0 + 32.0;
- platform: wifi_signal
name: "WiFi Signal RSSI"
id: wifi_signal_db
accuracy_decimals: 0
icon: mdi:wifi-strength-outline
update_interval: 600s
device_class: signal_strength
entity_category: diagnostic
filters:
- delta: 2 # only push if RSSI changes by 2 dBm
- platform: copy
source_id: wifi_signal_db
name: "WiFi Signal %"
id: wifi_signal_pct
accuracy_decimals: 0
icon: mdi:wifi-strength-outline
unit_of_measurement: "%"
entity_category: diagnostic
device_class: signal_strength
filters:
- lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
- delta: 2
### device_class: "" # 👈 Prevents Home Assistant from validating unit
### TEXT SENSORS
text_sensor:
- platform: template
name: "ESP Uptime (DHM)"
id: uptime_dhm
icon: mdi:clock-outline
entity_category: diagnostic
update_interval: 60s # refresh every minute
lambda: |-
uint32_t uptime_sec = (uint32_t)id(esp_uptime).state;
uint32_t days = uptime_sec / 86400;
uint32_t hours = (uptime_sec % 86400) / 3600;
uint32_t minutes = (uptime_sec % 3600) / 60;
char buffer[32];
snprintf(buffer, sizeof(buffer), "%ud %02uh %02um", days, hours, minutes);
return std::string(buffer);
- platform: version
name: "ESP Firmware Version"
id: esp_firmware_version
icon: mdi:information-variant
# update_interval: [static value]
entity_category: diagnostic
hide_timestamp: true
- platform: template
name: "ESP WiFi IP Address"
id: wifi_ip_address
update_interval: 120s
icon: mdi:ip-outline
entity_category: diagnostic
lambda: |-
static std::string last_ip;
std::string current_ip = id(wifi_ip_address_raw).state;
if (current_ip.empty() || current_ip == last_ip) return {};
last_ip = current_ip;
return current_ip;
- platform: wifi_info
ip_address:
name: "WiFi IP Address (raw)"
id: wifi_ip_address_raw
icon: mdi:ip-outline
entity_category: diagnostic
internal: True
update_interval: 60s
ssid:
name: "WiFi SSID"
id: wifi_ssid
icon: mdi:ip-outline
# update_interval: [event-driven]
entity_category: diagnostic
mac_address:
name: "WiFi MAC Address"
id: wifi_mac_address
icon: mdi:ip-outline
# update_interval: [event-driven]
entity_category: diagnostic
- platform: template
name: "WiFi MAC Address (compact)"
id: wifi_mac_address_stripped
icon: mdi:ip-outline
update_interval: 600s
entity_category: diagnostic
lambda: |-
auto mac = id(wifi_mac_address).state;
std::string stripped_mac;
for (char c : mac) if (c != ':') stripped_mac += c;
return stripped_mac;
- platform: template
name: "WiFi MAC Address [6]"
id: wifi_mac_address_last_6
icon: mdi:ip-outline
update_interval: 600s
entity_category: diagnostic
lambda: |-
auto mac = id(wifi_mac_address).state;
std::string stripped_mac;
for (char c : mac) if (c != ':') stripped_mac += c;
return stripped_mac.substr(stripped_mac.length() - 6);
#### BINARY SENSORS
binary_sensor:
- platform: status
name: "ESP HA Connection Status"
id: esp_ha_connected_status
icon: mdi:cloud-check
device_class: connectivity
entity_category: diagnostic
### SOFTWARE BUTTONS
button:
- platform: restart
name: "ESP Restart"
icon: mdi:power-cycle
entity_category: diagnostic
on_press:
then:
- logger.log:
format: "Restart triggered on ESP [${friendly_name}]"
here’s the z_config ov5640 file… sorry about forgetting to include it
input_number:
camera_jpeg_quality:
name: "JPEG Quality"
min: 10
max: 63
step: 1
camera_max_framerate:
name: "Max Framerate"
min: 10
max: 30
step: 1
camera_idle_framerate:
name: "Idle Framerate"
min: 0.1
max: 30
step: 1
camera_ae_level:
name: "AE Level"
min: -2
max: 2
step: 1
camera_aec_level:
name: "AEC Level"
min: 0
max: 1200
step: 100
camera_agc_level:
name: "AGC Level"
min: 0
max: 30
step: 1
camera_brightness:
name: "Brightness"
min: -2
max: 2
step: 1
camera_contrast:
name: "Contrast"
min: -2
max: 2
step: 1
camera_saturation:
name: "Saturation"
min: -2
max: 2
step: 1
camera_quality:
name: "JPEG Quality"
min: 10
max: 63
step: 1
input_boolean:
camera_vertical_flip:
name: "Vertical Flip"
camera_horizontal_mirror:
name: "Horizontal Mirror"
camera_awb_gain:
name: "AWB Gain"
camera_aec2:
name: "AEC2"
input_select:
camera_aec_mode:
name: "AEC mode"
options:
- "auto"
- "manual"
camera_agc_mode:
name: "AGC mode"
options:
- "auto"
- "manual"
camera_agc_ceiling:
name: "AGC Ceiling"
options:
- "2x"
- "4x"
- "8x"
- "16x"
- "32x"
- "64x"
- "128x"
camera_special_effect:
name: "Special Effect"
options:
- "none"
- "negative"
- "grayscale"
- "red_tint"
- "green_tint"
- "blue_tint"
- "sepia"
camera_white_balance:
name: "White Balance"
options:
- "auto"
- "sunny"
- "cloudy"
- "office"
- "home"
1 Like
1Richter
(1 Richter)
December 6, 2025, 1:54pm
12
I have a Freenove ESP32-S3 clone + ov5640 (aliexpress) with following config for Homeassistant 2025.12 and latest esphome. But i cant get the homeassistant camera settings to apply to the ov5640. I cant toggle switches, and these show up in the esp32 logs, but e.g. the vertical flip isnt applied to the camera (feed).
I tried to reduce i2c speed to 100kHz, enabled PSRAM to octal (for my s3-N16-R8) and changed default brightnes to 2, because the ov5640 feed is so dark.
But my “speed”, as mentioned in october by steve, is sufficient and i get a smooth video stream.
Can someone please suggest any changes, so my ov5640 is not so dimm (ov2640 and ov2540 are working flawlessly) AND:
make the switches in homeassistant (or esphome ui) apply to the live camera feed?
these are my logs
[esp32_camera:207]: Got Image: len=12125
[number:065]: 'Camera Brightness': Setting value
[number:124]: New value: 2.000000
[number:035]: 'Camera Brightness': Sending state 2.000000
[esp32_camera:207]: Got Image: len=12125
but these dont seem to apply to the cam.
esphome:
name: esp32-s3
friendly_name: esp32-s3
on_boot:
priority: 600
then:
- light.turn_on:
id: onboard_led
brightness: 60%
red: 0%
green: 100%
blue: 0%
- lambda: id(esp32s3_cam).set_aec_mode(esphome::esp32_camera::ESP32_GC_MODE_AUTO);
- lambda: id(esp32s3_cam).set_wb_mode(esphome::esp32_camera::ESP32_WB_MODE_AUTO);
- lambda: id(esp32s3_cam).set_brightness(2);
- delay: 1s
- light.turn_off: onboard_led
esp32:
board: esp32-s3-devkitc-1
framework:
type: esp-idf
psram:
mode: octal
speed: 80MHz
logger:
level: VERBOSE
api:
encryption:
key: "xyz="
ota:
- platform: esphome
password: "my_pw"
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
domain: .dns.tld
manual_ip:
static_ip: 192.168.1.2
gateway: 192.168.1.1
subnet: 255.255.255.0
dns1: 192.168.1.1
dns2: 8.8.8.8
ap:
ssid: "Esp32-S3 Fallback Hotspot"
password: "my_pw"
captive_portal:
# lowered i2c clock speed
i2c:
- id: camera_i2c
sda: GPIO4
scl: GPIO5
frequency: 100kHz
# RGB LED
light:
- platform: esp32_rmt_led_strip
rgb_order: GRB
pin: GPIO48
num_leds: 1
chipset: WS2812
name: "Onboard RGB LED"
id: onboard_led
restore_mode: RESTORE_DEFAULT_OFF
esp32_camera:
id: esp32s3_cam
name: esp32-s3 cam
external_clock:
pin: GPIO15
frequency: 20MHz
i2c_id: camera_i2c
data_pins: [GPIO11, GPIO9, GPIO8, GPIO10, GPIO12, GPIO18, GPIO17, GPIO16]
vsync_pin: GPIO6
href_pin: GPIO7
pixel_clock_pin: GPIO13
# Octal PSRAM is required for the 5MP sensor
frame_buffer_location: PSRAM
# Frame settings
max_framerate: 15fps
# Low idle framerate cools the camera when you aren't looking at it
idle_framerate: 0.1fps
# Image Settings
resolution: 640x480
jpeg_quality: 10
vertical_flip: false
horizontal_mirror: false
# Defaults, overriden by - on_boot: above
brightness: 2
contrast: 0
saturation: 0
aec_mode: auto
wb_mode: auto
# Frigate config
web_server:
port: 80
include_internal: False
esp32_camera_web_server:
- port: 8080
mode: stream
- port: 8081
mode: snapshot
# -------------------------
# Sensors
# -------------------------
sensor:
- platform: uptime
name: "Uptime"
id: esp_uptime
update_interval: 60s
entity_category: diagnostic
internal: true
- platform: internal_temperature
name: "ESP Onboard Temp (°C)"
id: esp_onboard_temp_c
unit_of_measurement: "°C"
update_interval: 10s
entity_category: diagnostic
- platform: wifi_signal
name: "WiFi Signal RSSI"
id: wifi_signal_db
update_interval: 600s
entity_category: diagnostic
# Homeassistant Controls
number:
- platform: template
name: "Camera Contrast"
id: cam_contrast
entity_category: config
icon: mdi:contrast-circle
min_value: -2
max_value: 2
step: 1
initial_value: 0
optimistic: true
set_action:
- lambda: id(esp32s3_cam).set_contrast(x);
- platform: template
name: "Camera Brightness"
id: cam_brightness
entity_category: config
icon: mdi:brightness-6
min_value: -2
max_value: 2
step: 1
initial_value: 0
optimistic: true
set_action:
- lambda: id(esp32s3_cam).set_brightness(x);
- platform: template
name: "Camera Saturation"
id: cam_saturation
entity_category: config
icon: mdi:palette
min_value: -2
max_value: 2
step: 1
initial_value: 0
optimistic: true
set_action:
- lambda: id(esp32s3_cam).set_saturation(x);
switch:
- platform: template
name: "Camera Vertical Flip"
id: cam_vflip
entity_category: config
icon: mdi:flip-vertical
optimistic: true
turn_on_action:
- lambda: id(esp32s3_cam).set_vertical_flip(true);
turn_off_action:
- lambda: id(esp32s3_cam).set_vertical_flip(false);
- platform: template
name: "Camera Horizontal Mirror"
id: cam_hmirror
entity_category: config
icon: mdi:flip-horizontal
optimistic: true
turn_on_action:
- lambda: id(esp32s3_cam).set_horizontal_mirror(true);
turn_off_action:
- lambda: id(esp32s3_cam).set_horizontal_mirror(false);
- platform: restart
name: "Restart Camera Board"
select:
- platform: template
name: "Camera Effect"
id: cam_effect
entity_category: config
optimistic: true
options:
- "None"
- "Negative"
- "Grayscale"
- "Red Tint"
- "Green Tint"
- "Blue Tint"
- "Sepia"
initial_option: "None"
set_action:
- lambda: |-
if (x == "Negative") id(esp32s3_cam).set_special_effect(esphome::esp32_camera::ESP32_SPECIAL_EFFECT_NEGATIVE);
else if (x == "Grayscale") id(esp32s3_cam).set_special_effect(esphome::esp32_camera::ESP32_SPECIAL_EFFECT_GRAYSCALE);
else if (x == "Red Tint") id(esp32s3_cam).set_special_effect(esphome::esp32_camera::ESP32_SPECIAL_EFFECT_RED_TINT);
else if (x == "Green Tint") id(esp32s3_cam).set_special_effect(esphome::esp32_camera::ESP32_SPECIAL_EFFECT_GREEN_TINT);
else if (x == "Blue Tint") id(esp32s3_cam).set_special_effect(esphome::esp32_camera::ESP32_SPECIAL_EFFECT_BLUE_TINT);
else if (x == "Sepia") id(esp32s3_cam).set_special_effect(esphome::esp32_camera::ESP32_SPECIAL_EFFECT_SEPIA);
else id(esp32s3_cam).set_special_effect(esphome::esp32_camera::ESP32_SPECIAL_EFFECT_NONE);
- platform: template
name: "Camera White Balance"
id: cam_wb_mode
entity_category: config
optimistic: true
icon: mdi:white-balance-sunny
options:
- "Auto"
- "Sunny"
- "Cloudy"
- "Office"
- "Home"
initial_option: "Auto"
set_action:
- lambda: |-
if (x == "Sunny") id(esp32s3_cam).set_wb_mode(esphome::esp32_camera::ESP32_WB_MODE_SUNNY);
else if (x == "Cloudy") id(esp32s3_cam).set_wb_mode(esphome::esp32_camera::ESP32_WB_MODE_CLOUDY);
else if (x == "Office") id(esp32s3_cam).set_wb_mode(esphome::esp32_camera::ESP32_WB_MODE_OFFICE);
else if (x == "Home") id(esp32s3_cam).set_wb_mode(esphome::esp32_camera::ESP32_WB_MODE_HOME);
else id(esp32s3_cam).set_wb_mode(esphome::esp32_camera::ESP32_WB_MODE_AUTO);
Try the code attached, I had a hard time to figure this out with and ChatGPT let me down all the time. In the end, forget the flip switches and only initialized flip or mirror once. I tried my not working code also with an OV5640 and it did not work but as with the init values only, it worked on the OV3660, so I assume it will work on the OV5640 aswell. In an earlier version I even got brightness, contrast and saturation working but in all my hundreds of changes, I broke something. You loose some, you win some
esphome:
name: ${name}
friendly_name: ${friendly_name}
name_add_mac_suffix: false
platformio_options:
board_build.mcu: esp32s3
build_flags: -DBOARD_HAS_PSRAM
board_build.arduino.memory_type: qio_opi
board_build.f_flash: 80000000L
board_build.flash_mode: qio
esp32:
board: esp32-s3-devkitc-1
framework:
type: esp-idf
substitutions:
name: esphome-web-20c368
friendly_name: Seeed Xaio w/Camera 20C368
camera_sda_pin: GPIO40
camera_scl_pin: GPIO39
camera_vsync_pin: GPIO38
camera_href_pin: GPIO47
camera_pclk_pin: GPIO13
camera_xclk_pin: GPIO10
camera_power_pin: GPIO1
user_led_pin: GPIO21
camera_resolution: 1280X1024
psram:
mode: octal
speed: 80MHz
script:
# Apply all "color-ish" settings together, every time any slider changes
- id: apply_color_settings
mode: restart
then:
- lambda: |-
id(seeed_camera).set_brightness((int) id(cam_brightness_num).state);
id(seeed_camera).set_contrast((int) id(cam_contrast_num).state);
id(seeed_camera).set_saturation((int) id(cam_saturation_num).state);
- id: apply_camera_settings
mode: restart
then:
- lambda: |-
id(seeed_camera).set_jpeg_quality((int) id(cam_jpeg_quality_num).state);
- script.execute: apply_color_settings
i2c:
- id: camera_i2c
sda: ${camera_sda_pin}
scl: ${camera_scl_pin}
frequency: 100kHz
esp32_camera:
name: "Seeed XIAO ESP32-S3 Camera"
id: seeed_camera
i2c_id: camera_i2c
data_pins:
- GPIO15
- GPIO17
- GPIO18
- GPIO16
- GPIO14
- GPIO12
- GPIO11
- GPIO48
vsync_pin: ${camera_vsync_pin}
href_pin: ${camera_href_pin}
pixel_clock_pin: ${camera_pclk_pin}
external_clock:
pin: ${camera_xclk_pin}
frequency: 20MHz
# If your wiring supports it, consider letting the component manage PWDN:
power_down_pin: ${camera_power_pin}
on_stream_start:
then:
- delay: 200ms
- script.execute: apply_camera_settings
max_framerate: 15fps
idle_framerate: 0.1fps
resolution: ${camera_resolution}
jpeg_quality: 10
vertical_flip: true
horizontal_mirror: false
brightness: 0
contrast: 0
saturation: 0
special_effect: none
aec_mode: auto
aec2: false
ae_level: 0
aec_value: 300
agc_mode: auto
agc_gain_ceiling: 2x
agc_value: 0
wb_mode: auto
web_server:
port: 80
switch:
- platform: restart
id: seecd_restart
name: "Camera Restart"
- platform: template
name: "Reset Camera"
id: reset_camera_settings
turn_on_action:
- lambda: |-
id(cam_brightness_num).publish_state(0);
id(cam_contrast_num).publish_state(0);
id(cam_saturation_num).publish_state(0);
id(cam_jpeg_quality_num).publish_state(10);
- script.execute: apply_camera_settings
- switch.turn_off: reset_camera_settings
number:
- platform: template
name: "Cam Brightness"
id: cam_brightness_num
min_value: -2
max_value: 2
step: 1
restore_value: true
initial_value: 0
set_action:
- script.execute: apply_color_settings
- platform: template
name: "Cam Contrast"
id: cam_contrast_num
min_value: -2
max_value: 2
step: 1
restore_value: true
initial_value: 0
set_action:
- script.execute: apply_color_settings
- platform: template
name: "Cam Saturation"
id: cam_saturation_num
min_value: -2
max_value: 2
step: 1
restore_value: true
initial_value: 0
set_action:
- script.execute: apply_color_settings
- platform: template
name: "Cam JPEG Quality"
id: cam_jpeg_quality_num
min_value: 10
max_value: 63
step: 1
restore_value: true
initial_value: 10
set_action:
- script.execute: apply_camera_settings
light:
- platform: monochromatic
output: onboard_user_led
name: "Onboard LED"
id: onboard_led
output:
- platform: ledc
pin: ${user_led_pin}
id: onboard_user_led
frequency: 5000
inverted: true
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
api:
encryption:
key: "your key"
on_client_connected:
then:
- logger.log: "Wi-Fi connected"
- component.update: wifi_ip_address
ota:
- platform: esphome
logger:
sensor:
- platform: uptime
name: "Uptime"
id: esp_uptime
accuracy_decimals: 0
icon: mdi:clock-start
update_interval: 60s
entity_category: diagnostic
internal: true
filters:
- delta: 60
- platform: internal_temperature
name: "ESP Onboard Temp (°C)"
id: esp_onboard_temp_c
unit_of_measurement: "°C"
accuracy_decimals: 1
icon: mdi:temperature-celsius
update_interval: 600s
entity_category: diagnostic
device_class: temperature
state_class: measurement
on_value:
then:
- component.update: esp_onboard_temp_f
- platform: template
name: "ESP Onboard Temp (°F)"
id: esp_onboard_temp_f
accuracy_decimals: 1
icon: mdi:temperature-fahrenheit
unit_of_measurement: "°F"
update_interval: never
entity_category: diagnostic
device_class: temperature
state_class: measurement
lambda: |-
if (!id(esp_onboard_temp_c).has_state()) return NAN;
return id(esp_onboard_temp_c).state * 9.0 / 5.0 + 32.0;
- platform: wifi_signal
name: "WiFi Signal RSSI"
id: wifi_signal_db
accuracy_decimals: 0
icon: mdi:wifi-strength-outline
update_interval: 600s
device_class: signal_strength
entity_category: diagnostic
filters:
- delta: 2
- platform: copy
source_id: wifi_signal_db
name: "WiFi Signal %"
id: wifi_signal_pct
accuracy_decimals: 0
icon: mdi:wifi-strength-outline
unit_of_measurement: "%"
entity_category: diagnostic
device_class: signal_strength
filters:
- lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
- delta: 2
text_sensor:
- platform: template
name: "ESP Uptime (DHM)"
id: uptime_dhm
icon: mdi:clock-outline
entity_category: diagnostic
update_interval: 60s
lambda: |-
uint32_t uptime_sec = (uint32_t)id(esp_uptime).state;
uint32_t days = uptime_sec / 86400;
uint32_t hours = (uptime_sec % 86400) / 3600;
uint32_t minutes = (uptime_sec % 3600) / 60;
char buffer[32];
snprintf(buffer, sizeof(buffer), "%ud %02uh %02um", days, hours, minutes);
return std::string(buffer);
- platform: version
name: "ESP Firmware Version"
id: esp_firmware_version
icon: mdi:information-variant
entity_category: diagnostic
hide_timestamp: true
- platform: template
name: "ESP WiFi IP Address"
id: wifi_ip_address
update_interval: 120s
icon: mdi:ip-outline
entity_category: diagnostic
lambda: |-
static std::string last_ip;
std::string current_ip = id(wifi_ip_address_raw).state;
if (current_ip.empty() || current_ip == last_ip) return {};
last_ip = current_ip;
return current_ip;
- platform: wifi_info
ip_address:
name: "WiFi IP Address (raw)"
id: wifi_ip_address_raw
icon: mdi:ip-outline
entity_category: diagnostic
internal: true
ssid:
name: "WiFi SSID"
id: wifi_ssid
icon: mdi:ip-outline
entity_category: diagnostic
mac_address:
name: "WiFi MAC Address"
id: wifi_mac_address
icon: mdi:ip-outline
entity_category: diagnostic
- platform: template
name: "WiFi MAC Address (compact)"
id: wifi_mac_address_stripped
icon: mdi:ip-outline
update_interval: 600s
entity_category: diagnostic
lambda: |-
auto mac = id(wifi_mac_address).state;
std::string stripped_mac;
for (char c : mac) if (c != ':') stripped_mac += c;
return stripped_mac;
- platform: template
name: "WiFi MAC Address [6]"
id: wifi_mac_address_last_6
icon: mdi:ip-outline
update_interval: 600s
entity_category: diagnostic
lambda: |-
auto mac = id(wifi_mac_address).state;
std::string stripped_mac;
for (char c : mac) if (c != ':') stripped_mac += c;
return stripped_mac.substr(stripped_mac.length() - 6);
- platform: template
name: "Camera Resolution (configured)"
id: cam_resolution_configured
icon: mdi:image-size-select-large
entity_category: diagnostic
lambda: |-
return std::string("${camera_resolution}");
binary_sensor:
- platform: status
name: "ESP HA Connection Status"
id: esp_ha_connected_status
icon: mdi:cloud-check
device_class: connectivity
entity_category: diagnostic
button:
- platform: restart
name: "ESP Restart"
id: esp_restart_btn
icon: mdi:power-cycle
entity_category: diagnostic
on_press:
then:
- logger.log:
format: "Restart triggered on ESP [${friendly_name}]"
Here is the update code, mostly all things are working via the switches except the flip or mirror or resolution, this has to be hardcoded in the yaml file and compiled.
Thank you Steve_Campbell
Firmware and yaml config for an esp32 sense xiao to integrate in Home Assistant or ESPHome
esphome:
name: ${name}
friendly_name: ${friendly_name}
name_add_mac_suffix: false
platformio_options:
board_build.mcu: esp32s3
build_flags: -DBOARD_HAS_PSRAM
board_build.arduino.memory_type: qio_opi
board_build.f_flash: 80000000L
board_build.flash_mode: qio
on_boot:
priority: -100
then:
- lambda: |-
id(g_vflip) = false;
id(g_hmirror) = true;
esp32:
board: esp32-s3-devkitc-1
framework:
type: arduino
version: latest
substitutions:
name: esphome-web-20c368
friendly_name: Seeed Xaio w/Camera 20C368
camera_sda_pin: GPIO40
camera_scl_pin: GPIO39
camera_vsync_pin: GPIO38
camera_href_pin: GPIO47
camera_pclk_pin: GPIO13
camera_xclk_pin: GPIO10
camera_power_pin: GPIO1
user_led_pin: GPIO21
camera_resolution: 1280X1024
psram:
mode: octal
speed: 80MHz
globals:
- id: g_vflip
type: bool
restore_value: false
initial_value: "false"
- id: g_hmirror
type: bool
restore_value: true
initial_value: "true"
script:
# Apply all "color-ish" settings together, every time any slider changes
- id: apply_color_settings
mode: restart
then:
- lambda: |-
id(seeed_camera).set_brightness((int) id(cam_brightness_num).state);
id(seeed_camera).set_contrast((int) id(cam_contrast_num).state);
id(seeed_camera).set_saturation((int) id(cam_saturation_num).state);
const char *wb = id(cam_white_balance).current_option();
const char *fx = id(cam_effect).current_option();
using namespace esphome::esp32_camera;
// WB
if (strcmp(wb, "sunny") == 0) id(seeed_camera).set_wb_mode(ESP32WhiteBalanceMode::ESP32_WB_MODE_SUNNY);
else if (strcmp(wb, "cloudy") == 0) id(seeed_camera).set_wb_mode(ESP32WhiteBalanceMode::ESP32_WB_MODE_CLOUDY);
else if (strcmp(wb, "office") == 0) id(seeed_camera).set_wb_mode(ESP32WhiteBalanceMode::ESP32_WB_MODE_OFFICE);
else if (strcmp(wb, "home") == 0) id(seeed_camera).set_wb_mode(ESP32WhiteBalanceMode::ESP32_WB_MODE_HOME);
else id(seeed_camera).set_wb_mode(ESP32WhiteBalanceMode::ESP32_WB_MODE_AUTO);
// FX
if (strcmp(fx, "negative") == 0) id(seeed_camera).set_special_effect(ESP32SpecialEffect::ESP32_SPECIAL_EFFECT_NEGATIVE);
else if (strcmp(fx, "grayscale") == 0) id(seeed_camera).set_special_effect(ESP32SpecialEffect::ESP32_SPECIAL_EFFECT_GRAYSCALE);
else if (strcmp(fx, "red_tint") == 0) id(seeed_camera).set_special_effect(ESP32SpecialEffect::ESP32_SPECIAL_EFFECT_RED_TINT);
else if (strcmp(fx, "green_tint") == 0) id(seeed_camera).set_special_effect(ESP32SpecialEffect::ESP32_SPECIAL_EFFECT_GREEN_TINT);
else if (strcmp(fx, "blue_tint") == 0) id(seeed_camera).set_special_effect(ESP32SpecialEffect::ESP32_SPECIAL_EFFECT_BLUE_TINT);
else if (strcmp(fx, "sepia") == 0) id(seeed_camera).set_special_effect(ESP32SpecialEffect::ESP32_SPECIAL_EFFECT_SEPIA);
else id(seeed_camera).set_special_effect(ESP32SpecialEffect::ESP32_SPECIAL_EFFECT_NONE);
ESP_LOGI("cam_apply", "apply_camera_settings() finished");
- id: apply_camera_settings
mode: restart
then:
- lambda: |-
id(seeed_camera).set_vertical_flip(id(g_vflip));
id(seeed_camera).set_horizontal_mirror(id(g_hmirror));
id(seeed_camera).set_jpeg_quality((int) id(cam_jpeg_quality_num).state);
- script.execute: apply_color_settings
esp32_camera:
name: "Seeed XIAO ESP32-S3 Camera"
id: seeed_camera
data_pins:
- GPIO15
- GPIO17
- GPIO18
- GPIO16
- GPIO14
- GPIO12
- GPIO11
- GPIO48
vsync_pin: ${camera_vsync_pin}
href_pin: ${camera_href_pin}
pixel_clock_pin: ${camera_pclk_pin}
external_clock:
pin: ${camera_xclk_pin}
frequency: 20MHz
i2c_pins:
sda: ${camera_sda_pin}
scl: ${camera_scl_pin}
# If your wiring supports it, consider letting the component manage PWDN:
# power_down_pin: ${camera_power_pin}
on_stream_start:
then:
- delay: 200ms
- script.execute: apply_camera_settings
max_framerate: 15fps
idle_framerate: 0.1fps
resolution: ${camera_resolution}
jpeg_quality: 10
vertical_flip: false
horizontal_mirror: true
brightness: 0
contrast: 0
saturation: 0
special_effect: none
aec_mode: auto
aec2: false
ae_level: 0
aec_value: 300
agc_mode: auto
agc_gain_ceiling: 2x
agc_value: 0
wb_mode: auto
web_server:
port: 80
switch:
- platform: template
name: "Reset Camera"
id: reset_camera_settings
turn_on_action:
- lambda: |-
id(cam_brightness_num).publish_state(0);
id(cam_contrast_num).publish_state(0);
id(cam_saturation_num).publish_state(0);
id(cam_jpeg_quality_num).publish_state(10);
id(g_vflip) = false;
id(g_hmirror) = true;
id(cam_white_balance).publish_state("auto");
id(cam_effect).publish_state("none");
- script.execute: apply_camera_settings
- switch.turn_off: reset_camera_settings
- platform: template
name: "Cam Vertical Flip"
id: cam_vflip_sw
internal: true
restore_mode: ALWAYS_OFF
lambda: |-
return id(g_vflip);
turn_on_action:
- lambda: |-
id(g_vflip) = true;
- script.execute: apply_camera_settings
turn_off_action:
- lambda: |-
id(g_vflip) = false;
- script.execute: apply_camera_settings
- platform: template
name: "Cam Horizontal Mirror"
id: cam_hmirror_sw
internal: true
restore_mode: ALWAYS_ON
lambda: |-
return id(g_hmirror);
turn_on_action:
- lambda: |-
id(g_hmirror) = true;
- script.execute: apply_camera_settings
turn_off_action:
- lambda: |-
id(g_hmirror) = false;
- script.execute: apply_camera_settings
number:
- platform: template
name: "Cam Brightness"
id: cam_brightness_num
min_value: -2
max_value: 2
step: 1
restore_value: true
initial_value: 0
set_action:
- script.execute: apply_color_settings
- platform: template
name: "Cam Contrast"
id: cam_contrast_num
min_value: -2
max_value: 2
step: 1
restore_value: true
initial_value: 0
set_action:
- script.execute: apply_color_settings
- platform: template
name: "Cam Saturation"
id: cam_saturation_num
min_value: -2
max_value: 2
step: 1
restore_value: true
initial_value: 0
set_action:
- script.execute: apply_color_settings
- platform: template
name: "Cam JPEG Quality"
id: cam_jpeg_quality_num
min_value: 10
max_value: 63
step: 1
restore_value: true
initial_value: 10
set_action:
- script.execute: apply_camera_settings
select:
- platform: template
name: "Cam White Balance"
id: cam_white_balance
optimistic: true
restore_value: true
options: [auto, sunny, cloudy, office, home]
initial_option: auto
set_action:
- script.execute: apply_camera_settings
- platform: template
name: "Cam Special Effect"
id: cam_effect
optimistic: true
restore_value: true
options: [none, negative, grayscale, red_tint, green_tint, blue_tint, sepia]
initial_option: none
set_action:
- script.execute: apply_camera_settings
light:
- platform: monochromatic
output: onboard_user_led
name: "Onboard LED"
id: onboard_led
output:
- platform: ledc
pin: ${user_led_pin}
id: onboard_user_led
frequency: 5000
inverted: true
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
api:
encryption:
key: your key
on_client_connected:
then:
- logger.log: "Wi-Fi connected"
- component.update: wifi_ip_address
ota:
- platform: esphome
logger:
sensor:
- platform: uptime
name: "Uptime"
id: esp_uptime
accuracy_decimals: 0
icon: mdi:clock-start
update_interval: 60s
entity_category: diagnostic
internal: true
filters:
- delta: 60
- platform: internal_temperature
name: "ESP Onboard Temp (°C)"
id: esp_onboard_temp_c
unit_of_measurement: "°C"
accuracy_decimals: 1
icon: mdi:temperature-celsius
update_interval: 600s
entity_category: diagnostic
device_class: temperature
state_class: measurement
on_value:
then:
- component.update: esp_onboard_temp_f
- platform: template
name: "ESP Onboard Temp (°F)"
id: esp_onboard_temp_f
accuracy_decimals: 1
icon: mdi:temperature-fahrenheit
unit_of_measurement: "°F"
update_interval: never
entity_category: diagnostic
device_class: temperature
state_class: measurement
lambda: |-
if (!id(esp_onboard_temp_c).has_state()) return NAN;
return id(esp_onboard_temp_c).state * 9.0 / 5.0 + 32.0;
- platform: wifi_signal
name: "WiFi Signal RSSI"
id: wifi_signal_db
accuracy_decimals: 0
icon: mdi:wifi-strength-outline
update_interval: 600s
device_class: signal_strength
entity_category: diagnostic
filters:
- delta: 2
- platform: copy
source_id: wifi_signal_db
name: "WiFi Signal %"
id: wifi_signal_pct
accuracy_decimals: 0
icon: mdi:wifi-strength-outline
unit_of_measurement: "%"
entity_category: diagnostic
device_class: signal_strength
filters:
- lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
- delta: 2
text_sensor:
- platform: template
name: "ESP Uptime (DHM)"
id: uptime_dhm
icon: mdi:clock-outline
entity_category: diagnostic
update_interval: 60s
lambda: |-
uint32_t uptime_sec = (uint32_t)id(esp_uptime).state;
uint32_t days = uptime_sec / 86400;
uint32_t hours = (uptime_sec % 86400) / 3600;
uint32_t minutes = (uptime_sec % 3600) / 60;
char buffer[32];
snprintf(buffer, sizeof(buffer), "%ud %02uh %02um", days, hours, minutes);
return std::string(buffer);
- platform: version
name: "ESP Firmware Version"
id: esp_firmware_version
icon: mdi:information-variant
entity_category: diagnostic
hide_timestamp: true
- platform: template
name: "ESP WiFi IP Address"
id: wifi_ip_address
update_interval: 120s
icon: mdi:ip-outline
entity_category: diagnostic
lambda: |-
static std::string last_ip;
std::string current_ip = id(wifi_ip_address_raw).state;
if (current_ip.empty() || current_ip == last_ip) return {};
last_ip = current_ip;
return current_ip;
- platform: wifi_info
ip_address:
name: "WiFi IP Address (raw)"
id: wifi_ip_address_raw
icon: mdi:ip-outline
entity_category: diagnostic
internal: true
ssid:
name: "WiFi SSID"
id: wifi_ssid
icon: mdi:ip-outline
entity_category: diagnostic
mac_address:
name: "WiFi MAC Address"
id: wifi_mac_address
icon: mdi:ip-outline
entity_category: diagnostic
- platform: template
name: "WiFi MAC Address (compact)"
id: wifi_mac_address_stripped
icon: mdi:ip-outline
update_interval: 600s
entity_category: diagnostic
lambda: |-
auto mac = id(wifi_mac_address).state;
std::string stripped_mac;
for (char c : mac) if (c != ':') stripped_mac += c;
return stripped_mac;
- platform: template
name: "WiFi MAC Address [6]"
id: wifi_mac_address_last_6
icon: mdi:ip-outline
update_interval: 600s
entity_category: diagnostic
lambda: |-
auto mac = id(wifi_mac_address).state;
std::string stripped_mac;
for (char c : mac) if (c != ':') stripped_mac += c;
return stripped_mac.substr(stripped_mac.length() - 6);
- platform: template
name: "Camera Resolution (configured)"
id: cam_resolution_configured
icon: mdi:image-size-select-large
entity_category: diagnostic
lambda: |-
return std::string("${camera_resolution}");
binary_sensor:
- platform: status
name: "ESP HA Connection Status"
id: esp_ha_connected_status
icon: mdi:cloud-check
device_class: connectivity
entity_category: diagnostic
button:
- platform: restart
name: "ESP Restart"
id: esp_restart_btn
icon: mdi:power-cycle
entity_category: diagnostic
on_press:
then:
- logger.log:
format: "Restart triggered on ESP [${friendly_name}]"
Hey, how about camera’s temperature? got few, and they are extremely hot.