This articles covers a simple camera server configuration for streaming and snapshot capture for use with ESPHome.
Table of Contents
Hardware used:
- Espressif ESP32-S3-EYE v2.2
Overview
This configuration provides a simple streaming/snapshotcamera server configuration for use with ESPHome.
Hardware used:
- Espressif ESP32-S3-EYE v2.2
Assembly summary
No wiring needed; pre-assembled.
Customisations
In the configuration you will need to change/provide values for:
- Device name, replace
<device-name>
- Secret:
api_key
- Secret:
ota_password
- Secret:
wifi_ssid
- Secret:
wifi_password
- Secret:
ap_password
Preparations for flashing the firmware
- Note: you may need perform taken from the docs: Hold down BOOT button and press RST button, then release RST button first, and BOOT button next. In this way, the board enters Firmware Download mode and you can start flashing program onto the board. After flashing the program, press RST button to start the program.
- Identify the TTL device (typically dev/ttyACM0 or sometimes /dev/ttyUSB0):
ls /dev/tty*
Accessing a tty device may require additional permissions add with (check this appropriate before blindly running):
sudo usermod -a -G dialout $USER
Flash with esptool.py
For example, note the ESP32-S3-EYE
flash size is 8MB:
esptool.py --port /dev/ttyACM0 --baud 115200 write_flash -fm dio -fs 8MB 0x0 /path/yo/your/firmware.bin
Notes
External references
- ESP32-S3-EYE v2.2 – Getting started
- Issue: esp32_camera not working with 10/20MHZ, only 8MHZ
- Schematics – PIN-out for Sub-board
YAML Source
substitutions:
id:
name:
friendly_name: Camera
frame_rate_buffer_size: "10"
resolution: 1024x768 # 1600x1200 # FRAMESIZE_UXGA
jpeg_quality: "12"
max_framerate: 10.0fps
idle_framerate: 1.0fps # 0.05fps
vertical_flip: "false"
horizontal_mirror: "false"
brightness: "2"
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
contrast: "2"
saturation: "-2"
esphome:
project:
name: "oddineers.cameras"
version: "0.2.0"
name: ${name}
friendly_name: ${friendly_name}
platformio_options:
build_flags: "-DBOARD_HAS_PSRAM"
board_build.arduino.memory_type: qio_opi
esp32:
variant: esp32s3
board: esp32s3camlcd
framework:
type: arduino
flash_size: 8MB
# TODO: Note the LCD is powered but unused need to add something to power the LCD down as not used
# In the meantime just disconnect it.
psram:
mode: octal
speed: 80MHz
# Enable Home Assistant API
api:
encryption:
key: !secret api_key
services: # change camera parameters on-the-fly
- service: camera_set_param
variables:
name: string
value: int
then:
- lambda: |-
bool state_return = false;
if (("contrast" == name) && (value >= -2) && (value <= 2)) { id(odd_cam).set_contrast(value); state_return = true; }
if (("brightness" == name) && (value >= -2) && (value <= 2)) { id(odd_cam).set_brightness(value); state_return = true; }
if (("saturation" == name) && (value >= -2) && (value <= 2)) { id(odd_cam).set_saturation(value); state_return = true; }
if (("special_effect" == name) && (value >= 0U) && (value <= 6U)) { id(odd_cam).set_special_effect((esphome::esp32_camera::ESP32SpecialEffect)value); state_return = true; }
if (("aec_mode" == name) && (value >= 0U) && (value <= 1U)) { id(odd_cam).set_aec_mode((esphome::esp32_camera::ESP32GainControlMode)value); state_return = true; }
if (("aec2" == name) && (value >= 0U) && (value <= 1U)) { id(odd_cam).set_aec2(value); state_return = true; }
if (("ae_level" == name) && (value >= -2) && (value <= 2)) { id(odd_cam).set_ae_level(value); state_return = true; }
if (("aec_value" == name) && (value >= 0U) && (value <= 1200U)) { id(odd_cam).set_aec_value(value); state_return = true; }
if (("agc_mode" == name) && (value >= 0U) && (value <= 1U)) { id(odd_cam).set_agc_mode((esphome::esp32_camera::ESP32GainControlMode)value); state_return = true; }
if (("agc_value" == name) && (value >= 0U) && (value <= 30U)) { id(odd_cam).set_agc_value(value); state_return = true; }
if (("agc_gain_ceiling" == name) && (value >= 0U) && (value <= 6U)) { id(odd_cam).set_agc_gain_ceiling((esphome::esp32_camera::ESP32AgcGainCeiling)value); state_return = true; }
if (("wb_mode" == name) && (value >= 0U) && (value <= 4U)) { id(odd_cam).set_wb_mode((esphome::esp32_camera::ESP32WhiteBalanceMode)value); state_return = true; }
if (("test_pattern" == name) && (value >= 0U) && (value <= 1U)) { id(odd_cam).set_test_pattern(value); state_return = true; }
if (true == state_return) {
id(odd_cam).update_camera_parameters();
}
else {
ESP_LOGW("esp32_camera_set_param", "Error in name or data range");
}
ota:
- platform: esphome
password: !secret ota_password
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# If you don't use .local for your IOT devices change that here
#domain: .local
# Powersaving
power_save_mode: none
#output_power: 8.5db
fast_connect: True
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: " Fallback Hotspot"
password: !secret ap_password
captive_portal:
logger:
level: debug
i2c:
- id: bus_a
sda: GPIO4
scl: GPIO5
scan: True
# ESP32-S3 Eye Camera configuration
# Cam clk freq above 8Mhz MAY lock up the esp32-s3-eye device: https://github.com/esphome/issues/issues/4191
esp32_camera:
id: odd_cam
name: "Camera"
external_clock:
pin: GPIO15
frequency: 20MHz
i2c_id: bus_a
data_pins: [GPIO11, GPIO9, GPIO8, GPIO10, GPIO12, GPIO18, GPIO17, GPIO16]
vsync_pin: GPIO6
href_pin: GPIO7
pixel_clock_pin: GPIO13
resolution: ${resolution}
jpeg_quality: ${jpeg_quality}
max_framerate: ${max_framerate}
idle_framerate: ${idle_framerate}
frame_buffer_count: 2
vertical_flip: ${vertical_flip}
horizontal_mirror: ${horizontal_mirror}
brightness: ${brightness}
contrast: ${contrast}
saturation: ${saturation}
special_effect: ${special_effect}
aec_mode: ${aec_mode}
aec2: ${aec2}
ae_level: ${ae_level}
aec_value: ${aec_value}
agc_mode: ${agc_mode}
agc_gain_ceiling: ${agc_gain_ceiling}
agc_value: ${agc_value}
wb_mode: ${wb_mode}
# Camera web server
esp32_camera_web_server:
- port: 80
mode: stream
- port: 81
mode: snapshot
binary_sensor:
- platform: status
name: Camera status
# Sensors for Home Assistant
sensor:
- platform: wifi_signal
name: "WiFi Signal"
update_interval: 60s
- platform: uptime
name: "Uptime"
update_interval: 110s
text_sensor:
- platform: wifi_info
ip_address:
name: "IP"
ssid:
name: "SSID"
bssid:
name: "BSSID"
mac_address:
name: "MAC"
dns_address:
name: "DNS"
# Button to restart
button:
- platform: restart
name: "Camera Restart"
# Enable time component for timestamp
time:
- platform: homeassistant
id: homeassistant_time
# Camera Controls
number:
- platform: template
name: "Camera Brightness"
id: camera_brightness
min_value: -2
max_value: 2
step: 0.1
initial_value: 0
restore_value: true
optimistic: true
on_value:
then:
- lambda: |-
id(${id}).set_brightness(x);
ESP_LOGD("camera", "Brightness set to: %.1f", x);
- platform: template
name: "Camera Contrast"
id: camera_contrast
min_value: -2
max_value: 2
step: 0.1
initial_value: 0
restore_value: true
optimistic: true
on_value:
then:
- lambda: |-
id(${id}).set_contrast(x);
ESP_LOGD("camera", "Contrast set to: %.1f", x);
- platform: template
name: "Camera Saturation"
id: camera_saturation
min_value: -2
max_value: 2
step: 0.1
initial_value: 0
restore_value: true
optimistic: true
on_value:
then:
- lambda: |-
id(${id}).set_saturation(x);
ESP_LOGD("camera", "Saturation set to: %.1f", x);
- platform: template
name: "Image Quality"
id: image_quality
min_value: 1
max_value: 63
step: 1
initial_value: 12
restore_value: true
optimistic: true
on_value:
then:
- lambda: |-
id(${id}).set_jpeg_quality(x);
ESP_LOGD("camera", "Image Quality set to: %d", (int) x);
- platform: template
name: "AEC2"
id: aec2_enabled
min_value: 0
max_value: 1
step: 1
initial_value: 0
restore_value: true
optimistic: true
on_value:
then:
- lambda: |-
id(${id}).set_aec2(x);
ESP_LOGD("camera", "AEC2 set to: %d", (int) x);