From 330ad1fd809294b30450d862fc39da2a003b3e88 Mon Sep 17 00:00:00 2001 From: Micha Kersloot Date: Thu, 26 Jan 2023 11:11:37 +0100 Subject: [PATCH 01/15] Change from Component to PollingComponent --- components/ip5306/ip5306.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/components/ip5306/ip5306.cpp b/components/ip5306/ip5306.cpp index 23e99868..19f71b93 100644 --- a/components/ip5306/ip5306.cpp +++ b/components/ip5306/ip5306.cpp @@ -25,7 +25,7 @@ void IP5306::setup() { } } -void IP5306::loop() { +void IP5306::update() { uint8_t data[2]; if (this->battery_level_ != nullptr) { if (this->read_register(IP5306_REG_LEVEL, data, 1) != i2c::ERROR_OK) { @@ -40,8 +40,7 @@ void IP5306::loop() { case 0x80: value = 75; break; case 0x00: value = 100; break; } - if (!this->battery_level_->has_state() || (this->battery_level_->state != value)) - this->battery_level_->publish_state(value); + this->battery_level_->publish_state(value); } if (this->read_register(IP5306_REG_READ0, data, 2) != i2c::ERROR_OK) { ESP_LOGE(TAG, "unable to read status"); From b1e7ff5e34a37a61d3944ce7d2662cb00584f1d6 Mon Sep 17 00:00:00 2001 From: Micha Kersloot Date: Thu, 26 Jan 2023 11:12:56 +0100 Subject: [PATCH 02/15] Change from Component to PollingComponent --- components/ip5306/ip5306.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/components/ip5306/ip5306.h b/components/ip5306/ip5306.h index 7557b9bb..bec854b0 100644 --- a/components/ip5306/ip5306.h +++ b/components/ip5306/ip5306.h @@ -8,10 +8,11 @@ namespace esphome { namespace ip5306 { -class IP5306 : public i2c::I2CDevice, public Component { +class IP5306 : public i2c::I2CDevice, public PollingComponent { public: + IP5306() : PollingComponent(60000) {} void setup() override; - void loop() override; + void update() override; float get_setup_priority() const override; From eb4dafc4be84ad2b56e832847395d75a7b7b5717 Mon Sep 17 00:00:00 2001 From: Micha Kersloot Date: Thu, 26 Jan 2023 11:25:48 +0100 Subject: [PATCH 03/15] Change to device_class battery --- components/ip5306/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/components/ip5306/__init__.py b/components/ip5306/__init__.py index 91212d43..25a7e45a 100644 --- a/components/ip5306/__init__.py +++ b/components/ip5306/__init__.py @@ -1,7 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import binary_sensor, i2c, sensor -from esphome.const import CONF_ID, CONF_BATTERY_LEVEL, DEVICE_CLASS_VOLTAGE, ICON_PERCENT, UNIT_PERCENT +from esphome.const import CONF_ID, CONF_BATTERY_LEVEL, DEVICE_CLASS_BATTERY, ICON_BATTERY, UNIT_PERCENT MULTI_CONF = True @@ -18,7 +18,8 @@ cv.GenerateID(): cv.declare_id(IP5306), cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( unit_of_measurement=UNIT_PERCENT, - icon=ICON_PERCENT, + device_class=DEVICE_CLASS_BATTERY, + icon=ICON_BATTERY, accuracy_decimals=0, ), cv.Optional(CONF_CHARGER_CONNECTED): binary_sensor.binary_sensor_schema(), From eb6de7b16f85a7394c045605b95e93e26c826afa Mon Sep 17 00:00:00 2001 From: Micha Kersloot Date: Thu, 26 Jan 2023 14:15:59 +0100 Subject: [PATCH 04/15] Let home assistant handle the battery icons --- components/ip5306/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/components/ip5306/__init__.py b/components/ip5306/__init__.py index 25a7e45a..6f3c9bcb 100644 --- a/components/ip5306/__init__.py +++ b/components/ip5306/__init__.py @@ -19,7 +19,6 @@ cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( unit_of_measurement=UNIT_PERCENT, device_class=DEVICE_CLASS_BATTERY, - icon=ICON_BATTERY, accuracy_decimals=0, ), cv.Optional(CONF_CHARGER_CONNECTED): binary_sensor.binary_sensor_schema(), From 03b02e908f01c8e5c4545f7cbce9e8bb02130d82 Mon Sep 17 00:00:00 2001 From: Micha Kersloot Date: Mon, 13 Feb 2023 11:40:33 +0100 Subject: [PATCH 05/15] Update ip5306.cpp Ident on line 43 --- components/ip5306/ip5306.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/ip5306/ip5306.cpp b/components/ip5306/ip5306.cpp index 19f71b93..acf09f26 100644 --- a/components/ip5306/ip5306.cpp +++ b/components/ip5306/ip5306.cpp @@ -40,7 +40,7 @@ void IP5306::update() { case 0x80: value = 75; break; case 0x00: value = 100; break; } - this->battery_level_->publish_state(value); + this->battery_level_->publish_state(value); } if (this->read_register(IP5306_REG_READ0, data, 2) != i2c::ERROR_OK) { ESP_LOGE(TAG, "unable to read status"); From 7c7b1e3f6cc9409883464ff63f841a13143522f8 Mon Sep 17 00:00:00 2001 From: Micha Kersloot Date: Tue, 28 Feb 2023 17:54:12 +0100 Subject: [PATCH 06/15] Create __init__.py --- components/esp32_camera/__init__.py | 275 ++++++++++++++++++++++++++++ 1 file changed, 275 insertions(+) create mode 100644 components/esp32_camera/__init__.py diff --git a/components/esp32_camera/__init__.py b/components/esp32_camera/__init__.py new file mode 100644 index 00000000..b3abbd5c --- /dev/null +++ b/components/esp32_camera/__init__.py @@ -0,0 +1,275 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import automation +from esphome import pins +from esphome.const import ( + CONF_FREQUENCY, + CONF_ID, + CONF_PIN, + CONF_SCL, + CONF_SDA, + CONF_DATA_PINS, + CONF_RESET_PIN, + CONF_RESOLUTION, + CONF_BRIGHTNESS, + CONF_CONTRAST, + CONF_TRIGGER_ID, +) +from esphome.core import CORE +from esphome.components.esp32 import add_idf_sdkconfig_option +from esphome.cpp_helpers import setup_entity + +DEPENDENCIES = ["esp32"] + +AUTO_LOAD = ["psram"] + +esp32_camera_ns = cg.esphome_ns.namespace("esp32_camera") +ESP32Camera = esp32_camera_ns.class_("ESP32Camera", cg.PollingComponent, cg.EntityBase) +ESP32CameraStreamStartTrigger = esp32_camera_ns.class_( + "ESP32CameraStreamStartTrigger", + automation.Trigger.template(), +) +ESP32CameraStreamStopTrigger = esp32_camera_ns.class_( + "ESP32CameraStreamStopTrigger", + automation.Trigger.template(), +) +ESP32CameraFrameSize = esp32_camera_ns.enum("ESP32CameraFrameSize") +FRAME_SIZES = { + "160X120": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_160X120, + "QQVGA": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_160X120, + "176X144": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_176X144, + "QCIF": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_176X144, + "240X176": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_240X176, + "HQVGA": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_240X176, + "320X240": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_320X240, + "QVGA": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_320X240, + "400X296": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_400X296, + "CIF": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_400X296, + "640X480": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_640X480, + "VGA": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_640X480, + "800X600": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_800X600, + "SVGA": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_800X600, + "1024X768": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_1024X768, + "XGA": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_1024X768, + "1280X1024": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_1280X1024, + "SXGA": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_1280X1024, + "1600X1200": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_1600X1200, + "UXGA": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_1600X1200, +} +ESP32GainControlMode = esp32_camera_ns.enum("ESP32GainControlMode") +ENUM_GAIN_CONTROL_MODE = { + "MANUAL": ESP32GainControlMode.ESP32_GC_MODE_MANU, + "AUTO": ESP32GainControlMode.ESP32_GC_MODE_AUTO, +} +ESP32AgcGainCeiling = esp32_camera_ns.enum("ESP32AgcGainCeiling") +ENUM_GAIN_CEILING = { + "2X": ESP32AgcGainCeiling.ESP32_GAINCEILING_2X, + "4X": ESP32AgcGainCeiling.ESP32_GAINCEILING_4X, + "8X": ESP32AgcGainCeiling.ESP32_GAINCEILING_8X, + "16X": ESP32AgcGainCeiling.ESP32_GAINCEILING_16X, + "32X": ESP32AgcGainCeiling.ESP32_GAINCEILING_32X, + "64X": ESP32AgcGainCeiling.ESP32_GAINCEILING_64X, + "128X": ESP32AgcGainCeiling.ESP32_GAINCEILING_128X, +} +ESP32WhiteBalanceMode = esp32_camera_ns.enum("ESP32WhiteBalanceMode") +ENUM_WB_MODE = { + "AUTO": ESP32WhiteBalanceMode.ESP32_WB_MODE_AUTO, + "SUNNY": ESP32WhiteBalanceMode.ESP32_WB_MODE_SUNNY, + "CLOUDY": ESP32WhiteBalanceMode.ESP32_WB_MODE_CLOUDY, + "OFFICE": ESP32WhiteBalanceMode.ESP32_WB_MODE_OFFICE, + "HOME": ESP32WhiteBalanceMode.ESP32_WB_MODE_HOME, +} +ESP32SpecialEffect = esp32_camera_ns.enum("ESP32SpecialEffect") +ENUM_SPECIAL_EFFECT = { + "NONE": ESP32SpecialEffect.ESP32_SPECIAL_EFFECT_NONE, + "NEGATIVE": ESP32SpecialEffect.ESP32_SPECIAL_EFFECT_NEGATIVE, + "GRAYSCALE": ESP32SpecialEffect.ESP32_SPECIAL_EFFECT_GRAYSCALE, + "RED_TINT": ESP32SpecialEffect.ESP32_SPECIAL_EFFECT_RED_TINT, + "GREEN_TINT": ESP32SpecialEffect.ESP32_SPECIAL_EFFECT_GREEN_TINT, + "BLUE_TINT": ESP32SpecialEffect.ESP32_SPECIAL_EFFECT_BLUE_TINT, + "SEPIA": ESP32SpecialEffect.ESP32_SPECIAL_EFFECT_SEPIA, +} + +# pin assignment +CONF_VSYNC_PIN = "vsync_pin" +CONF_HREF_PIN = "href_pin" +CONF_PIXEL_CLOCK_PIN = "pixel_clock_pin" +CONF_EXTERNAL_CLOCK = "external_clock" +CONF_I2C_PINS = "i2c_pins" +CONF_POWER_DOWN_PIN = "power_down_pin" +# image +CONF_JPEG_QUALITY = "jpeg_quality" +CONF_VERTICAL_FLIP = "vertical_flip" +CONF_HORIZONTAL_MIRROR = "horizontal_mirror" +CONF_SATURATION = "saturation" +CONF_SPECIAL_EFFECT = "special_effect" +# exposure +CONF_AEC_MODE = "aec_mode" +CONF_AEC2 = "aec2" +CONF_AE_LEVEL = "ae_level" +CONF_AEC_VALUE = "aec_value" +# gains +CONF_AGC_MODE = "agc_mode" +CONF_AGC_VALUE = "agc_value" +CONF_AGC_GAIN_CEILING = "agc_gain_ceiling" +# white balance +CONF_WB_MODE = "wb_mode" +# test pattern +CONF_TEST_PATTERN = "test_pattern" +# framerates +CONF_MAX_FRAMERATE = "max_framerate" +CONF_IDLE_FRAMERATE = "idle_framerate" + +# stream trigger +CONF_ON_STREAM_START = "on_stream_start" +CONF_ON_STREAM_STOP = "on_stream_stop" + +camera_range_param = cv.int_range(min=-2, max=2) + +CONFIG_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(ESP32Camera), + # pin assignment + cv.Required(CONF_DATA_PINS): cv.All( + [pins.internal_gpio_input_pin_number], cv.Length(min=8, max=8) + ), + cv.Required(CONF_VSYNC_PIN): pins.internal_gpio_input_pin_number, + cv.Required(CONF_HREF_PIN): pins.internal_gpio_input_pin_number, + cv.Required(CONF_PIXEL_CLOCK_PIN): pins.internal_gpio_input_pin_number, + cv.Required(CONF_EXTERNAL_CLOCK): cv.Schema( + { + cv.Required(CONF_PIN): pins.internal_gpio_input_pin_number, + cv.Optional(CONF_FREQUENCY, default="20MHz"): cv.All( + cv.frequency, cv.one_of(20e6, 10e6) + ), + } + ), + cv.Required(CONF_I2C_PINS): cv.Schema( + { + cv.Required(CONF_SDA): pins.internal_gpio_output_pin_number, + cv.Required(CONF_SCL): pins.internal_gpio_output_pin_number, + } + ), + cv.Optional(CONF_RESET_PIN): pins.internal_gpio_output_pin_number, + cv.Optional(CONF_POWER_DOWN_PIN): pins.internal_gpio_output_pin_number, + # image + cv.Optional(CONF_RESOLUTION, default="640X480"): cv.enum( + FRAME_SIZES, upper=True + ), + cv.Optional(CONF_JPEG_QUALITY, default=10): cv.int_range(min=6, max=63), + cv.Optional(CONF_CONTRAST, default=0): camera_range_param, + cv.Optional(CONF_BRIGHTNESS, default=0): camera_range_param, + cv.Optional(CONF_SATURATION, default=0): camera_range_param, + cv.Optional(CONF_VERTICAL_FLIP, default=True): cv.boolean, + cv.Optional(CONF_HORIZONTAL_MIRROR, default=True): cv.boolean, + cv.Optional(CONF_SPECIAL_EFFECT, default="NONE"): cv.enum( + ENUM_SPECIAL_EFFECT, upper=True + ), + # exposure + cv.Optional(CONF_AGC_MODE, default="AUTO"): cv.enum( + ENUM_GAIN_CONTROL_MODE, upper=True + ), + cv.Optional(CONF_AEC2, default=False): cv.boolean, + cv.Optional(CONF_AE_LEVEL, default=0): camera_range_param, + cv.Optional(CONF_AEC_VALUE, default=300): cv.int_range(min=0, max=1200), + # gains + cv.Optional(CONF_AEC_MODE, default="AUTO"): cv.enum( + ENUM_GAIN_CONTROL_MODE, upper=True + ), + cv.Optional(CONF_AGC_VALUE, default=0): cv.int_range(min=0, max=30), + cv.Optional(CONF_AGC_GAIN_CEILING, default="2X"): cv.enum( + ENUM_GAIN_CEILING, upper=True + ), + # white balance + cv.Optional(CONF_WB_MODE, default="AUTO"): cv.enum(ENUM_WB_MODE, upper=True), + # test pattern + cv.Optional(CONF_TEST_PATTERN, default=False): cv.boolean, + # framerates + cv.Optional(CONF_MAX_FRAMERATE, default="10 fps"): cv.All( + cv.framerate, cv.Range(min=0, min_included=False, max=60) + ), + cv.Optional(CONF_IDLE_FRAMERATE, default="0.1 fps"): cv.All( + cv.framerate, cv.Range(min=0, max=1) + ), + cv.Optional(CONF_ON_STREAM_START): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + ESP32CameraStreamStartTrigger + ), + } + ), + cv.Optional(CONF_ON_STREAM_STOP): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + ESP32CameraStreamStopTrigger + ), + } + ), + } +).extend(cv.COMPONENT_SCHEMA) + +SETTERS = { + # pin assignment + CONF_DATA_PINS: "set_data_pins", + CONF_VSYNC_PIN: "set_vsync_pin", + CONF_HREF_PIN: "set_href_pin", + CONF_PIXEL_CLOCK_PIN: "set_pixel_clock_pin", + CONF_RESET_PIN: "set_reset_pin", + CONF_POWER_DOWN_PIN: "set_power_down_pin", + # image + CONF_JPEG_QUALITY: "set_jpeg_quality", + CONF_VERTICAL_FLIP: "set_vertical_flip", + CONF_HORIZONTAL_MIRROR: "set_horizontal_mirror", + CONF_CONTRAST: "set_contrast", + CONF_BRIGHTNESS: "set_brightness", + CONF_SATURATION: "set_saturation", + CONF_SPECIAL_EFFECT: "set_special_effect", + # exposure + CONF_AEC_MODE: "set_aec_mode", + CONF_AEC2: "set_aec2", + CONF_AE_LEVEL: "set_ae_level", + CONF_AEC_VALUE: "set_aec_value", + # gains + CONF_AGC_MODE: "set_agc_mode", + CONF_AGC_VALUE: "set_agc_value", + CONF_AGC_GAIN_CEILING: "set_agc_gain_ceiling", + # white balance + CONF_WB_MODE: "set_wb_mode", + # test pattern + CONF_TEST_PATTERN: "set_test_pattern", +} + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await setup_entity(var, config) + await cg.register_component(var, config) + + for key, setter in SETTERS.items(): + if key in config: + cg.add(getattr(var, setter)(config[key])) + + extclk = config[CONF_EXTERNAL_CLOCK] + cg.add(var.set_external_clock(extclk[CONF_PIN], extclk[CONF_FREQUENCY])) + i2c_pins = config[CONF_I2C_PINS] + cg.add(var.set_i2c_pins(i2c_pins[CONF_SDA], i2c_pins[CONF_SCL])) + cg.add(var.set_max_update_interval(1000 / config[CONF_MAX_FRAMERATE])) + if config[CONF_IDLE_FRAMERATE] == 0: + cg.add(var.set_idle_update_interval(0)) + else: + cg.add(var.set_idle_update_interval(1000 / config[CONF_IDLE_FRAMERATE])) + cg.add(var.set_frame_size(config[CONF_RESOLUTION])) + + cg.add_define("USE_ESP32_CAMERA") + + if CORE.using_esp_idf: + cg.add_library("espressif/esp32-camera", "1.0.0") + add_idf_sdkconfig_option("CONFIG_RTCIO_SUPPORT_RTC_GPIO_DESC", True) + + for conf in config.get(CONF_ON_STREAM_START, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + + for conf in config.get(CONF_ON_STREAM_STOP, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) From 1a5c63271eb6b84af277a4a06b75b46797ce16d0 Mon Sep 17 00:00:00 2001 From: Micha Kersloot Date: Tue, 28 Feb 2023 17:54:53 +0100 Subject: [PATCH 07/15] Create esp32_camera.cpp --- components/esp32_camera/esp32_camera.cpp | 376 +++++++++++++++++++++++ 1 file changed, 376 insertions(+) create mode 100644 components/esp32_camera/esp32_camera.cpp diff --git a/components/esp32_camera/esp32_camera.cpp b/components/esp32_camera/esp32_camera.cpp new file mode 100644 index 00000000..b1bf1d85 --- /dev/null +++ b/components/esp32_camera/esp32_camera.cpp @@ -0,0 +1,376 @@ +#ifdef USE_ESP32 + +#include "esp32_camera.h" +#include "esphome/core/log.h" +#include "esphome/core/hal.h" + +#include + +namespace esphome { +namespace esp32_camera { + +static const char *const TAG = "esp32_camera"; + +/* ---------------- public API (derivated) ---------------- */ +void ESP32Camera::setup() { + global_esp32_camera = this; + + /* initialize time to now */ + this->last_update_ = millis(); + + /* initialize camera */ + esp_err_t err = esp_camera_init(&this->config_); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_camera_init failed: %s", esp_err_to_name(err)); + this->init_error_ = err; + this->mark_failed(); + return; + } + + /* initialize camera parameters */ + this->update_camera_parameters(); + + /* initialize RTOS */ + this->framebuffer_get_queue_ = xQueueCreate(1, sizeof(camera_fb_t *)); + this->framebuffer_return_queue_ = xQueueCreate(1, sizeof(camera_fb_t *)); + xTaskCreatePinnedToCore(&ESP32Camera::framebuffer_task, + "framebuffer_task", // name + 1024, // stack size + nullptr, // task pv params + 0, // priority + nullptr, // handle + 1 // core + ); +} + +void ESP32Camera::dump_config() { + auto conf = this->config_; + ESP_LOGCONFIG(TAG, "ESP32 Camera:"); + ESP_LOGCONFIG(TAG, " Name: %s", this->name_.c_str()); + ESP_LOGCONFIG(TAG, " Internal: %s", YESNO(this->internal_)); + ESP_LOGCONFIG(TAG, " Data Pins: D0:%d D1:%d D2:%d D3:%d D4:%d D5:%d D6:%d D7:%d", conf.pin_d0, conf.pin_d1, + conf.pin_d2, conf.pin_d3, conf.pin_d4, conf.pin_d5, conf.pin_d6, conf.pin_d7); + ESP_LOGCONFIG(TAG, " VSYNC Pin: %d", conf.pin_vsync); + ESP_LOGCONFIG(TAG, " HREF Pin: %d", conf.pin_href); + ESP_LOGCONFIG(TAG, " Pixel Clock Pin: %d", conf.pin_pclk); + ESP_LOGCONFIG(TAG, " External Clock: Pin:%d Frequency:%u", conf.pin_xclk, conf.xclk_freq_hz); +#ifdef USE_ESP_IDF // Temporary until the espressif/esp32-camera library is updated + ESP_LOGCONFIG(TAG, " I2C Pins: SDA:%d SCL:%d", conf.pin_sscb_sda, conf.pin_sscb_scl); +#else + ESP_LOGCONFIG(TAG, " I2C Pins: SDA:%d SCL:%d", conf.pin_sccb_sda, conf.pin_sccb_scl); +#endif + ESP_LOGCONFIG(TAG, " Reset Pin: %d", conf.pin_reset); + switch (this->config_.frame_size) { + case FRAMESIZE_QQVGA: + ESP_LOGCONFIG(TAG, " Resolution: 160x120 (QQVGA)"); + break; + case FRAMESIZE_QCIF: + ESP_LOGCONFIG(TAG, " Resolution: 176x155 (QCIF)"); + break; + case FRAMESIZE_HQVGA: + ESP_LOGCONFIG(TAG, " Resolution: 240x176 (HQVGA)"); + break; + case FRAMESIZE_QVGA: + ESP_LOGCONFIG(TAG, " Resolution: 320x240 (QVGA)"); + break; + case FRAMESIZE_CIF: + ESP_LOGCONFIG(TAG, " Resolution: 400x296 (CIF)"); + break; + case FRAMESIZE_VGA: + ESP_LOGCONFIG(TAG, " Resolution: 640x480 (VGA)"); + break; + case FRAMESIZE_SVGA: + ESP_LOGCONFIG(TAG, " Resolution: 800x600 (SVGA)"); + break; + case FRAMESIZE_XGA: + ESP_LOGCONFIG(TAG, " Resolution: 1024x768 (XGA)"); + break; + case FRAMESIZE_SXGA: + ESP_LOGCONFIG(TAG, " Resolution: 1280x1024 (SXGA)"); + break; + case FRAMESIZE_UXGA: + ESP_LOGCONFIG(TAG, " Resolution: 1600x1200 (UXGA)"); + break; + default: + break; + } + + if (this->is_failed()) { + ESP_LOGE(TAG, " Setup Failed: %s", esp_err_to_name(this->init_error_)); + return; + } + + sensor_t *s = esp_camera_sensor_get(); + auto st = s->status; + ESP_LOGCONFIG(TAG, " JPEG Quality: %u", st.quality); + // ESP_LOGCONFIG(TAG, " Framebuffer Count: %u", conf.fb_count); + ESP_LOGCONFIG(TAG, " Contrast: %d", st.contrast); + ESP_LOGCONFIG(TAG, " Brightness: %d", st.brightness); + ESP_LOGCONFIG(TAG, " Saturation: %d", st.saturation); + ESP_LOGCONFIG(TAG, " Vertical Flip: %s", ONOFF(st.vflip)); + ESP_LOGCONFIG(TAG, " Horizontal Mirror: %s", ONOFF(st.hmirror)); + ESP_LOGCONFIG(TAG, " Special Effect: %u", st.special_effect); + ESP_LOGCONFIG(TAG, " White Balance Mode: %u", st.wb_mode); + // ESP_LOGCONFIG(TAG, " Auto White Balance: %u", st.awb); + // ESP_LOGCONFIG(TAG, " Auto White Balance Gain: %u", st.awb_gain); + ESP_LOGCONFIG(TAG, " Auto Exposure Control: %u", st.aec); + ESP_LOGCONFIG(TAG, " Auto Exposure Control 2: %u", st.aec2); + ESP_LOGCONFIG(TAG, " Auto Exposure Level: %d", st.ae_level); + ESP_LOGCONFIG(TAG, " Auto Exposure Value: %u", st.aec_value); + ESP_LOGCONFIG(TAG, " AGC: %u", st.agc); + ESP_LOGCONFIG(TAG, " AGC Gain: %u", st.agc_gain); + ESP_LOGCONFIG(TAG, " Gain Ceiling: %u", st.gainceiling); + // ESP_LOGCONFIG(TAG, " BPC: %u", st.bpc); + // ESP_LOGCONFIG(TAG, " WPC: %u", st.wpc); + // ESP_LOGCONFIG(TAG, " RAW_GMA: %u", st.raw_gma); + // ESP_LOGCONFIG(TAG, " Lens Correction: %u", st.lenc); + // ESP_LOGCONFIG(TAG, " DCW: %u", st.dcw); + ESP_LOGCONFIG(TAG, " Test Pattern: %s", YESNO(st.colorbar)); +} + +void ESP32Camera::loop() { + // check if we can return the image + if (this->can_return_image_()) { + // return image + auto *fb = this->current_image_->get_raw_buffer(); + xQueueSend(this->framebuffer_return_queue_, &fb, portMAX_DELAY); + this->current_image_.reset(); + } + + // request idle image every idle_update_interval + const uint32_t now = millis(); + if (this->idle_update_interval_ != 0 && now - this->last_idle_request_ > this->idle_update_interval_) { + this->last_idle_request_ = now; + this->request_image(IDLE); + } + + // Check if we should fetch a new image + if (!this->has_requested_image_()) + return; + if (this->current_image_.use_count() > 1) { + // image is still in use + return; + } + if (now - this->last_update_ <= this->max_update_interval_) + return; + + // request new image + camera_fb_t *fb; + if (xQueueReceive(this->framebuffer_get_queue_, &fb, 0L) != pdTRUE) { + // no frame ready + ESP_LOGVV(TAG, "No frame ready"); + return; + } + + if (fb == nullptr) { + ESP_LOGW(TAG, "Got invalid frame from camera!"); + xQueueSend(this->framebuffer_return_queue_, &fb, portMAX_DELAY); + return; + } + this->current_image_ = std::make_shared(fb, this->single_requesters_ | this->stream_requesters_); + + ESP_LOGD(TAG, "Got Image: len=%u", fb->len); + this->new_image_callback_.call(this->current_image_); + this->last_update_ = now; + this->single_requesters_ = 0; +} + +float ESP32Camera::get_setup_priority() const { return setup_priority::DATA; } + +/* ---------------- constructors ---------------- */ +ESP32Camera::ESP32Camera(const std::string &name) : EntityBase(name) { + this->config_.pin_pwdn = -1; + this->config_.pin_reset = -1; + this->config_.pin_xclk = -1; + this->config_.ledc_timer = LEDC_TIMER_0; + this->config_.ledc_channel = LEDC_CHANNEL_0; + this->config_.pixel_format = PIXFORMAT_JPEG; + this->config_.frame_size = FRAMESIZE_VGA; // 640x480 + this->config_.jpeg_quality = 10; + this->config_.fb_count = 1; + + global_esp32_camera = this; +} +ESP32Camera::ESP32Camera() : ESP32Camera("") {} + +/* ---------------- setters ---------------- */ +/* set pin assignment */ +void ESP32Camera::set_data_pins(std::array pins) { + this->config_.pin_d0 = pins[0]; + this->config_.pin_d1 = pins[1]; + this->config_.pin_d2 = pins[2]; + this->config_.pin_d3 = pins[3]; + this->config_.pin_d4 = pins[4]; + this->config_.pin_d5 = pins[5]; + this->config_.pin_d6 = pins[6]; + this->config_.pin_d7 = pins[7]; +} +void ESP32Camera::set_vsync_pin(uint8_t pin) { this->config_.pin_vsync = pin; } +void ESP32Camera::set_href_pin(uint8_t pin) { this->config_.pin_href = pin; } +void ESP32Camera::set_pixel_clock_pin(uint8_t pin) { this->config_.pin_pclk = pin; } +void ESP32Camera::set_external_clock(uint8_t pin, uint32_t frequency) { + this->config_.pin_xclk = pin; + this->config_.xclk_freq_hz = frequency; +} +void ESP32Camera::set_i2c_pins(uint8_t sda, uint8_t scl) { +#ifdef USE_ESP_IDF // Temporary until the espressif/esp32-camera library is updated + this->config_.pin_sscb_sda = sda; + this->config_.pin_sscb_scl = scl; +#else + this->config_.pin_sccb_sda = sda; + this->config_.pin_sccb_scl = scl; +#endif +} +void ESP32Camera::set_reset_pin(uint8_t pin) { this->config_.pin_reset = pin; } +void ESP32Camera::set_power_down_pin(uint8_t pin) { this->config_.pin_pwdn = pin; } + +/* set image parameters */ +void ESP32Camera::set_frame_size(ESP32CameraFrameSize size) { + switch (size) { + case ESP32_CAMERA_SIZE_160X120: + this->config_.frame_size = FRAMESIZE_QQVGA; + break; + case ESP32_CAMERA_SIZE_176X144: + this->config_.frame_size = FRAMESIZE_QCIF; + break; + case ESP32_CAMERA_SIZE_240X176: + this->config_.frame_size = FRAMESIZE_HQVGA; + break; + case ESP32_CAMERA_SIZE_320X240: + this->config_.frame_size = FRAMESIZE_QVGA; + break; + case ESP32_CAMERA_SIZE_400X296: + this->config_.frame_size = FRAMESIZE_CIF; + break; + case ESP32_CAMERA_SIZE_640X480: + this->config_.frame_size = FRAMESIZE_VGA; + break; + case ESP32_CAMERA_SIZE_800X600: + this->config_.frame_size = FRAMESIZE_SVGA; + break; + case ESP32_CAMERA_SIZE_1024X768: + this->config_.frame_size = FRAMESIZE_XGA; + break; + case ESP32_CAMERA_SIZE_1280X1024: + this->config_.frame_size = FRAMESIZE_SXGA; + break; + case ESP32_CAMERA_SIZE_1600X1200: + this->config_.frame_size = FRAMESIZE_UXGA; + break; + } +} +void ESP32Camera::set_jpeg_quality(uint8_t quality) { this->config_.jpeg_quality = quality; } +void ESP32Camera::set_vertical_flip(bool vertical_flip) { this->vertical_flip_ = vertical_flip; } +void ESP32Camera::set_horizontal_mirror(bool horizontal_mirror) { this->horizontal_mirror_ = horizontal_mirror; } +void ESP32Camera::set_contrast(int contrast) { this->contrast_ = contrast; } +void ESP32Camera::set_brightness(int brightness) { this->brightness_ = brightness; } +void ESP32Camera::set_saturation(int saturation) { this->saturation_ = saturation; } +void ESP32Camera::set_special_effect(ESP32SpecialEffect effect) { this->special_effect_ = effect; } +/* set exposure parameters */ +void ESP32Camera::set_aec_mode(ESP32GainControlMode mode) { this->aec_mode_ = mode; } +void ESP32Camera::set_aec2(bool aec2) { this->aec2_ = aec2; } +void ESP32Camera::set_ae_level(int ae_level) { this->ae_level_ = ae_level; } +void ESP32Camera::set_aec_value(uint32_t aec_value) { this->aec_value_ = aec_value; } +/* set gains parameters */ +void ESP32Camera::set_agc_mode(ESP32GainControlMode mode) { this->agc_mode_ = mode; } +void ESP32Camera::set_agc_value(uint8_t agc_value) { this->agc_value_ = agc_value; } +void ESP32Camera::set_agc_gain_ceiling(ESP32AgcGainCeiling gain_ceiling) { this->agc_gain_ceiling_ = gain_ceiling; } +/* set white balance */ +void ESP32Camera::set_wb_mode(ESP32WhiteBalanceMode mode) { this->wb_mode_ = mode; } +/* set test mode */ +void ESP32Camera::set_test_pattern(bool test_pattern) { this->test_pattern_ = test_pattern; } +/* set fps */ +void ESP32Camera::set_max_update_interval(uint32_t max_update_interval) { + this->max_update_interval_ = max_update_interval; +} +void ESP32Camera::set_idle_update_interval(uint32_t idle_update_interval) { + this->idle_update_interval_ = idle_update_interval; +} + +/* ---------------- public API (specific) ---------------- */ +void ESP32Camera::add_image_callback(std::function)> &&f) { + this->new_image_callback_.add(std::move(f)); +} +void ESP32Camera::add_stream_start_callback(std::function &&callback) { + this->stream_start_callback_.add(std::move(callback)); +} +void ESP32Camera::add_stream_stop_callback(std::function &&callback) { + this->stream_stop_callback_.add(std::move(callback)); +} +void ESP32Camera::start_stream(CameraRequester requester) { + this->stream_start_callback_.call(); + this->stream_requesters_ |= (1U << requester); +} +void ESP32Camera::stop_stream(CameraRequester requester) { + this->stream_stop_callback_.call(); + this->stream_requesters_ &= ~(1U << requester); +} +void ESP32Camera::request_image(CameraRequester requester) { this->single_requesters_ |= (1U << requester); } +void ESP32Camera::update_camera_parameters() { + sensor_t *s = esp_camera_sensor_get(); + /* update image */ + s->set_vflip(s, this->vertical_flip_); + s->set_hmirror(s, this->horizontal_mirror_); + s->set_contrast(s, this->contrast_); + s->set_brightness(s, this->brightness_); + s->set_saturation(s, this->saturation_); + s->set_special_effect(s, (int) this->special_effect_); // 0 to 6 + /* update exposure */ + s->set_exposure_ctrl(s, (bool) this->aec_mode_); + s->set_aec2(s, this->aec2_); // 0 = disable , 1 = enable + s->set_ae_level(s, this->ae_level_); // -2 to 2 + s->set_aec_value(s, this->aec_value_); // 0 to 1200 + /* update gains */ + s->set_gain_ctrl(s, (bool) this->agc_mode_); + s->set_agc_gain(s, (int) this->agc_value_); // 0 to 30 + s->set_gainceiling(s, (gainceiling_t) this->agc_gain_ceiling_); + /* update white balance mode */ + s->set_wb_mode(s, (int) this->wb_mode_); // 0 to 4 + /* update test pattern */ + s->set_colorbar(s, this->test_pattern_); +} + +/* ---------------- Internal methods ---------------- */ +bool ESP32Camera::has_requested_image_() const { return this->single_requesters_ || this->stream_requesters_; } +bool ESP32Camera::can_return_image_() const { return this->current_image_.use_count() == 1; } +void ESP32Camera::framebuffer_task(void *pv) { + while (true) { + camera_fb_t *framebuffer = esp_camera_fb_get(); + xQueueSend(global_esp32_camera->framebuffer_get_queue_, &framebuffer, portMAX_DELAY); + // return is no-op for config with 1 fb + xQueueReceive(global_esp32_camera->framebuffer_return_queue_, &framebuffer, portMAX_DELAY); + esp_camera_fb_return(framebuffer); + } +} + +ESP32Camera *global_esp32_camera; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + +/* ---------------- CameraImageReader class ---------------- */ +void CameraImageReader::set_image(std::shared_ptr image) { + this->image_ = std::move(image); + this->offset_ = 0; +} +size_t CameraImageReader::available() const { + if (!this->image_) + return 0; + + return this->image_->get_data_length() - this->offset_; +} +void CameraImageReader::return_image() { this->image_.reset(); } +void CameraImageReader::consume_data(size_t consumed) { this->offset_ += consumed; } +uint8_t *CameraImageReader::peek_data_buffer() { return this->image_->get_data_buffer() + this->offset_; } + +/* ---------------- 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; } +size_t CameraImage::get_data_length() { return this->buffer_->len; } +bool CameraImage::was_requested_by(CameraRequester requester) const { + return (this->requesters_ & (1 << requester)) != 0; +} + +} // namespace esp32_camera +} // namespace esphome + +#endif From 7569e2034224e045ed847ba33f08ffa2e9c3266c Mon Sep 17 00:00:00 2001 From: Micha Kersloot Date: Tue, 28 Feb 2023 17:55:30 +0100 Subject: [PATCH 08/15] Create esp32_camera.h --- components/esp32_camera/esp32_camera.h | 223 +++++++++++++++++++++++++ 1 file changed, 223 insertions(+) create mode 100644 components/esp32_camera/esp32_camera.h diff --git a/components/esp32_camera/esp32_camera.h b/components/esp32_camera/esp32_camera.h new file mode 100644 index 00000000..87c5b0ba --- /dev/null +++ b/components/esp32_camera/esp32_camera.h @@ -0,0 +1,223 @@ +#pragma once + +#ifdef USE_ESP32 + +#include "esphome/core/automation.h" +#include "esphome/core/component.h" +#include "esphome/core/entity_base.h" +#include "esphome/core/helpers.h" +#include +#include +#include + +namespace esphome { +namespace esp32_camera { + +class ESP32Camera; + +/* ---------------- enum classes ---------------- */ +enum CameraRequester { IDLE, API_REQUESTER, WEB_REQUESTER }; + +enum ESP32CameraFrameSize { + ESP32_CAMERA_SIZE_160X120, // QQVGA + ESP32_CAMERA_SIZE_176X144, // QCIF + ESP32_CAMERA_SIZE_240X176, // HQVGA + ESP32_CAMERA_SIZE_320X240, // QVGA + ESP32_CAMERA_SIZE_400X296, // CIF + ESP32_CAMERA_SIZE_640X480, // VGA + ESP32_CAMERA_SIZE_800X600, // SVGA + ESP32_CAMERA_SIZE_1024X768, // XGA + ESP32_CAMERA_SIZE_1280X1024, // SXGA + ESP32_CAMERA_SIZE_1600X1200, // UXGA +}; + +enum ESP32AgcGainCeiling { + ESP32_GAINCEILING_2X = GAINCEILING_2X, + ESP32_GAINCEILING_4X = GAINCEILING_4X, + ESP32_GAINCEILING_8X = GAINCEILING_8X, + ESP32_GAINCEILING_16X = GAINCEILING_16X, + ESP32_GAINCEILING_32X = GAINCEILING_32X, + ESP32_GAINCEILING_64X = GAINCEILING_64X, + ESP32_GAINCEILING_128X = GAINCEILING_128X, +}; + +enum ESP32GainControlMode { + ESP32_GC_MODE_MANU = false, + ESP32_GC_MODE_AUTO = true, +}; + +enum ESP32WhiteBalanceMode { + ESP32_WB_MODE_AUTO = 0U, + ESP32_WB_MODE_SUNNY = 1U, + ESP32_WB_MODE_CLOUDY = 2U, + ESP32_WB_MODE_OFFICE = 3U, + ESP32_WB_MODE_HOME = 4U, +}; + +enum ESP32SpecialEffect { + ESP32_SPECIAL_EFFECT_NONE = 0U, + ESP32_SPECIAL_EFFECT_NEGATIVE = 1U, + ESP32_SPECIAL_EFFECT_GRAYSCALE = 2U, + ESP32_SPECIAL_EFFECT_RED_TINT = 3U, + ESP32_SPECIAL_EFFECT_GREEN_TINT = 4U, + ESP32_SPECIAL_EFFECT_BLUE_TINT = 5U, + ESP32_SPECIAL_EFFECT_SEPIA = 6U, +}; + +/* ---------------- CameraImage class ---------------- */ +class CameraImage { + public: + CameraImage(camera_fb_t *buffer, uint8_t requester); + camera_fb_t *get_raw_buffer(); + uint8_t *get_data_buffer(); + size_t get_data_length(); + bool was_requested_by(CameraRequester requester) const; + + protected: + camera_fb_t *buffer_; + uint8_t requesters_; +}; + +/* ---------------- CameraImageReader class ---------------- */ +class CameraImageReader { + public: + void set_image(std::shared_ptr image); + size_t available() const; + uint8_t *peek_data_buffer(); + void consume_data(size_t consumed); + void return_image(); + + protected: + std::shared_ptr image_; + size_t offset_{0}; +}; + +/* ---------------- ESP32Camera class ---------------- */ +class ESP32Camera : public Component, public EntityBase { + public: + ESP32Camera(const std::string &name); + ESP32Camera(); + + /* setters */ + /* -- pin assignment */ + void set_data_pins(std::array pins); + void set_vsync_pin(uint8_t pin); + void set_href_pin(uint8_t pin); + void set_pixel_clock_pin(uint8_t pin); + void set_external_clock(uint8_t pin, uint32_t frequency); + void set_i2c_pins(uint8_t sda, uint8_t scl); + void set_reset_pin(uint8_t pin); + void set_power_down_pin(uint8_t pin); + /* -- image */ + void set_frame_size(ESP32CameraFrameSize size); + void set_jpeg_quality(uint8_t quality); + void set_vertical_flip(bool vertical_flip); + void set_horizontal_mirror(bool horizontal_mirror); + void set_contrast(int contrast); + void set_brightness(int brightness); + void set_saturation(int saturation); + void set_special_effect(ESP32SpecialEffect effect); + /* -- exposure */ + void set_aec_mode(ESP32GainControlMode mode); + void set_aec2(bool aec2); + void set_ae_level(int ae_level); + void set_aec_value(uint32_t aec_value); + /* -- gains */ + void set_agc_mode(ESP32GainControlMode mode); + void set_agc_value(uint8_t agc_value); + void set_agc_gain_ceiling(ESP32AgcGainCeiling gain_ceiling); + /* -- white balance */ + void set_wb_mode(ESP32WhiteBalanceMode mode); + /* -- test */ + void set_test_pattern(bool test_pattern); + /* -- framerates */ + void set_max_update_interval(uint32_t max_update_interval); + void set_idle_update_interval(uint32_t idle_update_interval); + + /* public API (derivated) */ + void setup() override; + void loop() override; + void dump_config() override; + float get_setup_priority() const override; + /* public API (specific) */ + void add_image_callback(std::function)> &&f); + void start_stream(CameraRequester requester); + void stop_stream(CameraRequester requester); + void request_image(CameraRequester requester); + void update_camera_parameters(); + + void add_stream_start_callback(std::function &&callback); + void add_stream_stop_callback(std::function &&callback); + + protected: + /* internal methods */ + bool has_requested_image_() const; + bool can_return_image_() const; + + static void framebuffer_task(void *pv); + + /* attributes */ + /* camera configuration */ + camera_config_t config_{}; + /* -- image */ + bool vertical_flip_{true}; + bool horizontal_mirror_{true}; + int contrast_{0}; + int brightness_{0}; + int saturation_{0}; + ESP32SpecialEffect special_effect_{ESP32_SPECIAL_EFFECT_NONE}; + /* -- exposure */ + ESP32GainControlMode aec_mode_{ESP32_GC_MODE_AUTO}; + bool aec2_{false}; + int ae_level_{0}; + uint32_t aec_value_{300}; + /* -- gains */ + ESP32GainControlMode agc_mode_{ESP32_GC_MODE_AUTO}; + uint8_t agc_value_{0}; + ESP32AgcGainCeiling agc_gain_ceiling_{ESP32_GAINCEILING_2X}; + /* -- white balance */ + ESP32WhiteBalanceMode wb_mode_{ESP32_WB_MODE_AUTO}; + /* -- Test */ + bool test_pattern_{false}; + /* -- framerates */ + uint32_t max_update_interval_{1000}; + uint32_t idle_update_interval_{15000}; + + esp_err_t init_error_{ESP_OK}; + std::shared_ptr current_image_; + uint8_t single_requesters_{0}; + uint8_t stream_requesters_{0}; + QueueHandle_t framebuffer_get_queue_; + QueueHandle_t framebuffer_return_queue_; + CallbackManager)> new_image_callback_; + CallbackManager stream_start_callback_{}; + CallbackManager stream_stop_callback_{}; + + uint32_t last_idle_request_{0}; + uint32_t last_update_{0}; +}; + +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +extern ESP32Camera *global_esp32_camera; + +class ESP32CameraStreamStartTrigger : public Trigger<> { + public: + explicit ESP32CameraStreamStartTrigger(ESP32Camera *parent) { + parent->add_stream_start_callback([this]() { this->trigger(); }); + } + + protected: +}; +class ESP32CameraStreamStopTrigger : public Trigger<> { + public: + explicit ESP32CameraStreamStopTrigger(ESP32Camera *parent) { + parent->add_stream_stop_callback([this]() { this->trigger(); }); + } + + protected: +}; + +} // namespace esp32_camera +} // namespace esphome + +#endif From af741cd6e1f35cf3d407abb8e718051396db49e8 Mon Sep 17 00:00:00 2001 From: Micha Kersloot Date: Tue, 28 Feb 2023 18:02:19 +0100 Subject: [PATCH 09/15] Trail fix for missing PSRAM --- components/esp32_camera/esp32_camera.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/esp32_camera/esp32_camera.cpp b/components/esp32_camera/esp32_camera.cpp index b1bf1d85..b85d11b6 100644 --- a/components/esp32_camera/esp32_camera.cpp +++ b/components/esp32_camera/esp32_camera.cpp @@ -187,7 +187,8 @@ ESP32Camera::ESP32Camera(const std::string &name) : EntityBase(name) { this->config_.pixel_format = PIXFORMAT_JPEG; this->config_.frame_size = FRAMESIZE_VGA; // 640x480 this->config_.jpeg_quality = 10; - this->config_.fb_count = 1; + this->config_.fb_count = 1; + this->config_.fb_location = CAMERA_FB_IN_DRAM; global_esp32_camera = this; } From 6ca8ac1ed77d3a963243ea546976928b38457b05 Mon Sep 17 00:00:00 2001 From: Micha Kersloot Date: Fri, 5 May 2023 14:12:27 +0200 Subject: [PATCH 10/15] Update to esphome 2023.4.4 --- components/esp32_camera/esp32_camera.cpp | 53 ++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/components/esp32_camera/esp32_camera.cpp b/components/esp32_camera/esp32_camera.cpp index b85d11b6..cada2d3c 100644 --- a/components/esp32_camera/esp32_camera.cpp +++ b/components/esp32_camera/esp32_camera.cpp @@ -91,6 +91,30 @@ void ESP32Camera::dump_config() { case FRAMESIZE_UXGA: ESP_LOGCONFIG(TAG, " Resolution: 1600x1200 (UXGA)"); break; + case FRAMESIZE_FHD: + ESP_LOGCONFIG(TAG, " Resolution: 1920x1080 (FHD)"); + break; + case FRAMESIZE_P_HD: + ESP_LOGCONFIG(TAG, " Resolution: 720x1280 (P_HD)"); + break; + case FRAMESIZE_P_3MP: + ESP_LOGCONFIG(TAG, " Resolution: 864x1536 (P_3MP)"); + break; + case FRAMESIZE_QXGA: + ESP_LOGCONFIG(TAG, " Resolution: 2048x1536 (QXGA)"); + break; + case FRAMESIZE_QHD: + ESP_LOGCONFIG(TAG, " Resolution: 2560x1440 (QHD)"); + break; + case FRAMESIZE_WQXGA: + ESP_LOGCONFIG(TAG, " Resolution: 2560x1600 (WQXGA)"); + break; + case FRAMESIZE_P_FHD: + ESP_LOGCONFIG(TAG, " Resolution: 1080x1920 (P_FHD)"); + break; + case FRAMESIZE_QSXGA: + ESP_LOGCONFIG(TAG, " Resolution: 2560x1920 (QSXGA)"); + break; default: break; } @@ -178,7 +202,7 @@ void ESP32Camera::loop() { float ESP32Camera::get_setup_priority() const { return setup_priority::DATA; } /* ---------------- constructors ---------------- */ -ESP32Camera::ESP32Camera(const std::string &name) : EntityBase(name) { +ESP32Camera::ESP32Camera() { this->config_.pin_pwdn = -1; this->config_.pin_reset = -1; this->config_.pin_xclk = -1; @@ -187,12 +211,11 @@ ESP32Camera::ESP32Camera(const std::string &name) : EntityBase(name) { this->config_.pixel_format = PIXFORMAT_JPEG; this->config_.frame_size = FRAMESIZE_VGA; // 640x480 this->config_.jpeg_quality = 10; - this->config_.fb_count = 1; + this->config_.fb_count = 1; this->config_.fb_location = CAMERA_FB_IN_DRAM; global_esp32_camera = this; } -ESP32Camera::ESP32Camera() : ESP32Camera("") {} /* ---------------- setters ---------------- */ /* set pin assignment */ @@ -258,6 +281,30 @@ void ESP32Camera::set_frame_size(ESP32CameraFrameSize size) { case ESP32_CAMERA_SIZE_1600X1200: this->config_.frame_size = FRAMESIZE_UXGA; break; + case ESP32_CAMERA_SIZE_1920X1080: + this->config_.frame_size = FRAMESIZE_FHD; + break; + case ESP32_CAMERA_SIZE_720X1280: + this->config_.frame_size = FRAMESIZE_P_HD; + break; + case ESP32_CAMERA_SIZE_864X1536: + this->config_.frame_size = FRAMESIZE_P_3MP; + break; + case ESP32_CAMERA_SIZE_2048X1536: + this->config_.frame_size = FRAMESIZE_QXGA; + break; + case ESP32_CAMERA_SIZE_2560X1440: + this->config_.frame_size = FRAMESIZE_QHD; + break; + case ESP32_CAMERA_SIZE_2560X1600: + this->config_.frame_size = FRAMESIZE_WQXGA; + break; + case ESP32_CAMERA_SIZE_1080X1920: + this->config_.frame_size = FRAMESIZE_P_FHD; + break; + case ESP32_CAMERA_SIZE_2560X1920: + this->config_.frame_size = FRAMESIZE_QSXGA; + break; } } void ESP32Camera::set_jpeg_quality(uint8_t quality) { this->config_.jpeg_quality = quality; } From 79c5a88fc21c38913a7162aaafbaf0bfa78c9e95 Mon Sep 17 00:00:00 2001 From: Micha Kersloot Date: Fri, 5 May 2023 14:16:11 +0200 Subject: [PATCH 11/15] Compatible with esphome 2023.4.4 --- components/esp32_camera/esp32_camera.h | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/components/esp32_camera/esp32_camera.h b/components/esp32_camera/esp32_camera.h index 87c5b0ba..5f88c6fd 100644 --- a/components/esp32_camera/esp32_camera.h +++ b/components/esp32_camera/esp32_camera.h @@ -29,6 +29,14 @@ enum ESP32CameraFrameSize { ESP32_CAMERA_SIZE_1024X768, // XGA ESP32_CAMERA_SIZE_1280X1024, // SXGA ESP32_CAMERA_SIZE_1600X1200, // UXGA + ESP32_CAMERA_SIZE_1920X1080, // FHD + ESP32_CAMERA_SIZE_720X1280, // PHD + ESP32_CAMERA_SIZE_864X1536, // P3MP + ESP32_CAMERA_SIZE_2048X1536, // QXGA + ESP32_CAMERA_SIZE_2560X1440, // QHD + ESP32_CAMERA_SIZE_2560X1600, // WQXGA + ESP32_CAMERA_SIZE_1080X1920, // PFHD + ESP32_CAMERA_SIZE_2560X1920, // QSXGA }; enum ESP32AgcGainCeiling { @@ -95,7 +103,6 @@ class CameraImageReader { /* ---------------- ESP32Camera class ---------------- */ class ESP32Camera : public Component, public EntityBase { public: - ESP32Camera(const std::string &name); ESP32Camera(); /* setters */ From 3e80761afb9743f9c46717fad280b56484b5268e Mon Sep 17 00:00:00 2001 From: Micha Kersloot Date: Fri, 5 May 2023 14:18:05 +0200 Subject: [PATCH 12/15] Compatible with esphome 2023.4.4 --- components/esp32_camera/__init__.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/components/esp32_camera/__init__.py b/components/esp32_camera/__init__.py index b3abbd5c..4cbdf7ca 100644 --- a/components/esp32_camera/__init__.py +++ b/components/esp32_camera/__init__.py @@ -55,6 +55,22 @@ "SXGA": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_1280X1024, "1600X1200": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_1600X1200, "UXGA": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_1600X1200, + "1920X1080": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_1920X1080, + "FHD": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_1920X1080, + "720X1280": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_720X1280, + "PHD": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_720X1280, + "864X1536": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_864X1536, + "P3MP": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_864X1536, + "2048X1536": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_2048X1536, + "QXGA": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_2048X1536, + "2560X1440": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_2560X1440, + "QHD": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_2560X1440, + "2560X1600": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_2560X1600, + "WQXGA": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_2560X1600, + "1080X1920": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_1080X1920, + "PFHD": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_1080X1920, + "2560X1920": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_2560X1920, + "QSXGA": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_2560X1920, } ESP32GainControlMode = esp32_camera_ns.enum("ESP32GainControlMode") ENUM_GAIN_CONTROL_MODE = { @@ -140,7 +156,7 @@ { cv.Required(CONF_PIN): pins.internal_gpio_input_pin_number, cv.Optional(CONF_FREQUENCY, default="20MHz"): cv.All( - cv.frequency, cv.one_of(20e6, 10e6) + cv.frequency, cv.Range(min=8e6, max=20e6) ), } ), From fdf65dc326b423c5fd289425a5aa56dc2ef4674a Mon Sep 17 00:00:00 2001 From: Micha Kersloot Date: Fri, 6 Jun 2025 16:42:21 +0200 Subject: [PATCH 13/15] Update to newest version (2025.5.2) --- components/esp32_camera/__init__.py | 45 ++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/components/esp32_camera/__init__.py b/components/esp32_camera/__init__.py index 4cbdf7ca..b4038c18 100644 --- a/components/esp32_camera/__init__.py +++ b/components/esp32_camera/__init__.py @@ -1,22 +1,22 @@ +from esphome import automation, pins import esphome.codegen as cg +from esphome.components.esp32 import add_idf_component import esphome.config_validation as cv -from esphome import automation -from esphome import pins from esphome.const import ( + CONF_BRIGHTNESS, + CONF_CONTRAST, + CONF_DATA_PINS, CONF_FREQUENCY, CONF_ID, CONF_PIN, - CONF_SCL, - CONF_SDA, - CONF_DATA_PINS, CONF_RESET_PIN, CONF_RESOLUTION, - CONF_BRIGHTNESS, - CONF_CONTRAST, + CONF_SCL, + CONF_SDA, CONF_TRIGGER_ID, + CONF_VSYNC_PIN, ) from esphome.core import CORE -from esphome.components.esp32 import add_idf_sdkconfig_option from esphome.cpp_helpers import setup_entity DEPENDENCIES = ["esp32"] @@ -25,6 +25,11 @@ esp32_camera_ns = cg.esphome_ns.namespace("esp32_camera") ESP32Camera = esp32_camera_ns.class_("ESP32Camera", cg.PollingComponent, cg.EntityBase) +ESP32CameraImageData = esp32_camera_ns.struct("CameraImageData") +# Triggers +ESP32CameraImageTrigger = esp32_camera_ns.class_( + "ESP32CameraImageTrigger", automation.Trigger.template() +) ESP32CameraStreamStartTrigger = esp32_camera_ns.class_( "ESP32CameraStreamStartTrigger", automation.Trigger.template(), @@ -107,7 +112,6 @@ } # pin assignment -CONF_VSYNC_PIN = "vsync_pin" CONF_HREF_PIN = "href_pin" CONF_PIXEL_CLOCK_PIN = "pixel_clock_pin" CONF_EXTERNAL_CLOCK = "external_clock" @@ -135,10 +139,13 @@ # framerates CONF_MAX_FRAMERATE = "max_framerate" CONF_IDLE_FRAMERATE = "idle_framerate" +# frame buffer +CONF_FRAME_BUFFER_COUNT = "frame_buffer_count" # stream trigger CONF_ON_STREAM_START = "on_stream_start" CONF_ON_STREAM_STOP = "on_stream_stop" +CONF_ON_IMAGE = "on_image" camera_range_param = cv.int_range(min=-2, max=2) @@ -207,6 +214,7 @@ cv.Optional(CONF_IDLE_FRAMERATE, default="0.1 fps"): cv.All( cv.framerate, cv.Range(min=0, max=1) ), + cv.Optional(CONF_FRAME_BUFFER_COUNT, default=1): cv.int_range(min=1, max=2), cv.Optional(CONF_ON_STREAM_START): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( @@ -221,6 +229,11 @@ ), } ), + cv.Optional(CONF_ON_IMAGE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ESP32CameraImageTrigger), + } + ), } ).extend(cv.COMPONENT_SCHEMA) @@ -274,13 +287,17 @@ async def to_code(config): cg.add(var.set_idle_update_interval(0)) else: cg.add(var.set_idle_update_interval(1000 / config[CONF_IDLE_FRAMERATE])) + cg.add(var.set_frame_buffer_count(config[CONF_FRAME_BUFFER_COUNT])) cg.add(var.set_frame_size(config[CONF_RESOLUTION])) cg.add_define("USE_ESP32_CAMERA") if CORE.using_esp_idf: - cg.add_library("espressif/esp32-camera", "1.0.0") - add_idf_sdkconfig_option("CONFIG_RTCIO_SUPPORT_RTC_GPIO_DESC", True) + add_idf_component( + name="esp32-camera", + repo="https://github.com/espressif/esp32-camera.git", + ref="v2.0.15", + ) for conf in config.get(CONF_ON_STREAM_START, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) @@ -289,3 +306,9 @@ async def to_code(config): for conf in config.get(CONF_ON_STREAM_STOP, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [], conf) + + for conf in config.get(CONF_ON_IMAGE, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation( + trigger, [(ESP32CameraImageData, "image")], conf + ) From b1feaf926286ccef198f1d20e24f8150fb2fb28e Mon Sep 17 00:00:00 2001 From: Micha Kersloot Date: Fri, 6 Jun 2025 16:44:11 +0200 Subject: [PATCH 14/15] Update to newest version (2025.5.2) --- components/esp32_camera/esp32_camera.h | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/components/esp32_camera/esp32_camera.h b/components/esp32_camera/esp32_camera.h index 5f88c6fd..d5fe48c2 100644 --- a/components/esp32_camera/esp32_camera.h +++ b/components/esp32_camera/esp32_camera.h @@ -86,6 +86,11 @@ class CameraImage { uint8_t requesters_; }; +struct CameraImageData { + uint8_t *data; + size_t length; +}; + /* ---------------- CameraImageReader class ---------------- */ class CameraImageReader { public: @@ -101,7 +106,7 @@ class CameraImageReader { }; /* ---------------- ESP32Camera class ---------------- */ -class ESP32Camera : public Component, public EntityBase { +class ESP32Camera : public EntityBase, public Component { public: ESP32Camera(); @@ -140,6 +145,9 @@ class ESP32Camera : public Component, public EntityBase { /* -- framerates */ void set_max_update_interval(uint32_t max_update_interval); void set_idle_update_interval(uint32_t idle_update_interval); + /* -- frame buffer */ + void set_frame_buffer_mode(camera_grab_mode_t mode); + void set_frame_buffer_count(uint8_t fb_count); /* public API (derivated) */ void setup() override; @@ -147,12 +155,12 @@ class ESP32Camera : public Component, public EntityBase { void dump_config() override; float get_setup_priority() const override; /* public API (specific) */ - void add_image_callback(std::function)> &&f); void start_stream(CameraRequester requester); void stop_stream(CameraRequester requester); void request_image(CameraRequester requester); void update_camera_parameters(); + void add_image_callback(std::function)> &&callback); void add_stream_start_callback(std::function &&callback); void add_stream_stop_callback(std::function &&callback); @@ -196,7 +204,7 @@ class ESP32Camera : public Component, public EntityBase { uint8_t stream_requesters_{0}; QueueHandle_t framebuffer_get_queue_; QueueHandle_t framebuffer_return_queue_; - CallbackManager)> new_image_callback_; + CallbackManager)> new_image_callback_{}; CallbackManager stream_start_callback_{}; CallbackManager stream_stop_callback_{}; @@ -207,6 +215,18 @@ class ESP32Camera : public Component, public EntityBase { // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) extern ESP32Camera *global_esp32_camera; +class ESP32CameraImageTrigger : public Trigger { + public: + explicit ESP32CameraImageTrigger(ESP32Camera *parent) { + parent->add_image_callback([this](const std::shared_ptr &image) { + CameraImageData camera_image_data{}; + camera_image_data.length = image->get_data_length(); + camera_image_data.data = image->get_data_buffer(); + this->trigger(camera_image_data); + }); + } +}; + class ESP32CameraStreamStartTrigger : public Trigger<> { public: explicit ESP32CameraStreamStartTrigger(ESP32Camera *parent) { From 43581c2ff85270c5285c29fc31fbd62a54fca23d Mon Sep 17 00:00:00 2001 From: Micha Kersloot Date: Fri, 6 Jun 2025 16:48:57 +0200 Subject: [PATCH 15/15] Update to newest version (2025.5.2) --- components/esp32_camera/esp32_camera.cpp | 27 ++++++++++++------------ 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/components/esp32_camera/esp32_camera.cpp b/components/esp32_camera/esp32_camera.cpp index cada2d3c..f18b0798 100644 --- a/components/esp32_camera/esp32_camera.cpp +++ b/components/esp32_camera/esp32_camera.cpp @@ -3,6 +3,7 @@ #include "esp32_camera.h" #include "esphome/core/log.h" #include "esphome/core/hal.h" +#include "esphome/core/application.h" #include @@ -37,7 +38,7 @@ void ESP32Camera::setup() { "framebuffer_task", // name 1024, // stack size nullptr, // task pv params - 0, // priority + 1, // priority nullptr, // handle 1 // core ); @@ -54,11 +55,7 @@ void ESP32Camera::dump_config() { ESP_LOGCONFIG(TAG, " HREF Pin: %d", conf.pin_href); ESP_LOGCONFIG(TAG, " Pixel Clock Pin: %d", conf.pin_pclk); ESP_LOGCONFIG(TAG, " External Clock: Pin:%d Frequency:%u", conf.pin_xclk, conf.xclk_freq_hz); -#ifdef USE_ESP_IDF // Temporary until the espressif/esp32-camera library is updated - ESP_LOGCONFIG(TAG, " I2C Pins: SDA:%d SCL:%d", conf.pin_sscb_sda, conf.pin_sscb_scl); -#else ESP_LOGCONFIG(TAG, " I2C Pins: SDA:%d SCL:%d", conf.pin_sccb_sda, conf.pin_sccb_scl); -#endif ESP_LOGCONFIG(TAG, " Reset Pin: %d", conf.pin_reset); switch (this->config_.frame_size) { case FRAMESIZE_QQVGA: @@ -127,7 +124,7 @@ void ESP32Camera::dump_config() { sensor_t *s = esp_camera_sensor_get(); auto st = s->status; ESP_LOGCONFIG(TAG, " JPEG Quality: %u", st.quality); - // ESP_LOGCONFIG(TAG, " Framebuffer Count: %u", conf.fb_count); + ESP_LOGCONFIG(TAG, " Framebuffer Count: %u", conf.fb_count); ESP_LOGCONFIG(TAG, " Contrast: %d", st.contrast); ESP_LOGCONFIG(TAG, " Brightness: %d", st.brightness); ESP_LOGCONFIG(TAG, " Saturation: %d", st.saturation); @@ -162,7 +159,7 @@ void ESP32Camera::loop() { } // request idle image every idle_update_interval - const uint32_t now = millis(); + const uint32_t now = App.get_loop_component_start_time(); if (this->idle_update_interval_ != 0 && now - this->last_idle_request_ > this->idle_update_interval_) { this->last_idle_request_ = now; this->request_image(IDLE); @@ -212,6 +209,7 @@ ESP32Camera::ESP32Camera() { this->config_.frame_size = FRAMESIZE_VGA; // 640x480 this->config_.jpeg_quality = 10; this->config_.fb_count = 1; + this->config_.grab_mode = CAMERA_GRAB_WHEN_EMPTY; this->config_.fb_location = CAMERA_FB_IN_DRAM; global_esp32_camera = this; @@ -237,13 +235,8 @@ void ESP32Camera::set_external_clock(uint8_t pin, uint32_t frequency) { this->config_.xclk_freq_hz = frequency; } void ESP32Camera::set_i2c_pins(uint8_t sda, uint8_t scl) { -#ifdef USE_ESP_IDF // Temporary until the espressif/esp32-camera library is updated - this->config_.pin_sscb_sda = sda; - this->config_.pin_sscb_scl = scl; -#else this->config_.pin_sccb_sda = sda; this->config_.pin_sccb_scl = scl; -#endif } void ESP32Camera::set_reset_pin(uint8_t pin) { this->config_.pin_reset = pin; } void ESP32Camera::set_power_down_pin(uint8_t pin) { this->config_.pin_pwdn = pin; } @@ -334,10 +327,16 @@ void ESP32Camera::set_max_update_interval(uint32_t max_update_interval) { void ESP32Camera::set_idle_update_interval(uint32_t idle_update_interval) { this->idle_update_interval_ = idle_update_interval; } +/* set frame buffer parameters */ +void ESP32Camera::set_frame_buffer_mode(camera_grab_mode_t mode) { this->config_.grab_mode = mode; } +void ESP32Camera::set_frame_buffer_count(uint8_t fb_count) { + this->config_.fb_count = fb_count; + this->set_frame_buffer_mode(fb_count > 1 ? CAMERA_GRAB_LATEST : CAMERA_GRAB_WHEN_EMPTY); +} /* ---------------- public API (specific) ---------------- */ -void ESP32Camera::add_image_callback(std::function)> &&f) { - this->new_image_callback_.add(std::move(f)); +void ESP32Camera::add_image_callback(std::function)> &&callback) { + this->new_image_callback_.add(std::move(callback)); } void ESP32Camera::add_stream_start_callback(std::function &&callback) { this->stream_start_callback_.add(std::move(callback));