From f9ad832e7bef3c685273ccc971942814b4eda2dd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 2 Dec 2025 09:59:32 -0600 Subject: [PATCH] [esp32_camera] Replace std::function callbacks with CameraListener interface (#12165) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- esphome/components/api/api_server.cpp | 16 ++++--- esphome/components/api/api_server.h | 10 +++++ esphome/components/camera/camera.h | 25 ++++++++--- .../components/esp32_camera/esp32_camera.cpp | 21 ++++----- .../components/esp32_camera/esp32_camera.h | 43 ++++++++----------- .../camera_web_server.cpp | 14 +++--- .../camera_web_server.h | 5 ++- 7 files changed, 78 insertions(+), 56 deletions(-) diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index de0c4b24c..4168761c7 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -107,12 +107,7 @@ void APIServer::setup() { #ifdef USE_CAMERA if (camera::Camera::instance() != nullptr && !camera::Camera::instance()->is_internal()) { - camera::Camera::instance()->add_image_callback([this](const std::shared_ptr &image) { - for (auto &c : this->clients_) { - if (!c->flags_.remove) - c->set_camera_state(image); - } - }); + camera::Camera::instance()->add_listener(this); } #endif } @@ -544,6 +539,15 @@ void APIServer::on_log(uint8_t level, const char *tag, const char *message, size } #endif +#ifdef USE_CAMERA +void APIServer::on_camera_image(const std::shared_ptr &image) { + for (auto &c : this->clients_) { + if (!c->flags_.remove) + c->set_camera_state(image); + } +} +#endif + void APIServer::on_shutdown() { this->shutting_down_ = true; diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index 57aea6ad0..3089bb1d3 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -18,6 +18,9 @@ #ifdef USE_LOGGER #include "esphome/components/logger/logger.h" #endif +#ifdef USE_CAMERA +#include "esphome/components/camera/camera.h" +#endif #include #include @@ -36,6 +39,10 @@ class APIServer : public Component, , public logger::LogListener #endif +#ifdef USE_CAMERA + , + public camera::CameraListener +#endif { public: APIServer(); @@ -49,6 +56,9 @@ class APIServer : public Component, #ifdef USE_LOGGER void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) override; #endif +#ifdef USE_CAMERA + void on_camera_image(const std::shared_ptr &image) override; +#endif #ifdef USE_API_PASSWORD bool check_password(const uint8_t *password_data, size_t password_len) const; void set_password(const std::string &password); diff --git a/esphome/components/camera/camera.h b/esphome/components/camera/camera.h index c28a756a0..6e1fc8cc0 100644 --- a/esphome/components/camera/camera.h +++ b/esphome/components/camera/camera.h @@ -35,6 +35,21 @@ inline const char *to_string(PixelFormat format) { return "PIXEL_FORMAT_UNKNOWN"; } +// Forward declaration +class CameraImage; + +/** Listener interface for camera events. + * + * Components can implement this interface to receive camera notifications + * (new images, stream start/stop) without the overhead of std::function callbacks. + */ +class CameraListener { + public: + virtual void on_camera_image(const std::shared_ptr &image) {} + virtual void on_stream_start() {} + virtual void on_stream_stop() {} +}; + /** Abstract camera image base class. * Encapsulates the JPEG encoded data and it is shared among * all connected clients. @@ -87,12 +102,12 @@ struct CameraImageSpec { }; /** Abstract camera base class. Collaborates with API. - * 1) API server starts and installs callback (add_image_callback) - * which is called by the camera when a new image is available. + * 1) API server starts and registers as a listener (add_listener) + * to receive new images from the camera. * 2) New API client connects and creates a new image reader (create_image_reader). * 3) API connection receives protobuf CameraImageRequest and calls request_image. * 3.a) API connection receives protobuf CameraImageRequest and calls start_stream. - * 4) Camera implementation provides JPEG data in the CameraImage and calls callback. + * 4) Camera implementation provides JPEG data in the CameraImage and notifies listeners. * 5) API connection sets the image in the image reader. * 6) API connection consumes data from the image reader and returns the image when finished. * 7.a) Camera captures a new image and continues with 4) until start_stream is called. @@ -100,8 +115,8 @@ struct CameraImageSpec { class Camera : public EntityBase, public Component { public: Camera(); - // Camera implementation invokes callback to publish a new image. - virtual void add_image_callback(std::function)> &&callback) = 0; + /// Add a listener to receive camera events + virtual void add_listener(CameraListener *listener) = 0; /// Returns a new camera image reader that keeps track of the JPEG data in the camera image. virtual CameraImageReader *create_image_reader() = 0; // Connection, camera or web server requests one new JPEG image. diff --git a/esphome/components/esp32_camera/esp32_camera.cpp b/esphome/components/esp32_camera/esp32_camera.cpp index 38bd8d582..5080a6f32 100644 --- a/esphome/components/esp32_camera/esp32_camera.cpp +++ b/esphome/components/esp32_camera/esp32_camera.cpp @@ -205,7 +205,9 @@ void ESP32Camera::loop() { 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_); + for (auto *listener : this->listeners_) { + listener->on_camera_image(this->current_image_); + } this->last_update_ = now; this->single_requesters_ = 0; } @@ -357,21 +359,16 @@ void ESP32Camera::set_frame_buffer_location(camera_fb_location_t fb_location) { } /* ---------------- public API (specific) ---------------- */ -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)); -} -void ESP32Camera::add_stream_stop_callback(std::function &&callback) { - this->stream_stop_callback_.add(std::move(callback)); -} void ESP32Camera::start_stream(camera::CameraRequester requester) { - this->stream_start_callback_.call(); + for (auto *listener : this->listeners_) { + listener->on_stream_start(); + } this->stream_requesters_ |= (1U << requester); } void ESP32Camera::stop_stream(camera::CameraRequester requester) { - this->stream_stop_callback_.call(); + for (auto *listener : this->listeners_) { + listener->on_stream_stop(); + } this->stream_requesters_ &= ~(1U << requester); } void ESP32Camera::request_image(camera::CameraRequester requester) { this->single_requesters_ |= (1U << requester); } diff --git a/esphome/components/esp32_camera/esp32_camera.h b/esphome/components/esp32_camera/esp32_camera.h index 0e7f7c0ea..54a7d6064 100644 --- a/esphome/components/esp32_camera/esp32_camera.h +++ b/esphome/components/esp32_camera/esp32_camera.h @@ -165,9 +165,8 @@ class ESP32Camera : public camera::Camera { void request_image(camera::CameraRequester requester) override; void update_camera_parameters(); - void add_image_callback(std::function)> &&callback) override; - void add_stream_start_callback(std::function &&callback); - void add_stream_stop_callback(std::function &&callback); + /// Add a listener to receive camera events + void add_listener(camera::CameraListener *listener) override { this->listeners_.push_back(listener); } camera::CameraImageReader *create_image_reader() override; protected: @@ -210,9 +209,7 @@ class ESP32Camera : public camera::Camera { 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_{}; + std::vector listeners_; uint32_t last_idle_request_{0}; uint32_t last_update_{0}; @@ -221,33 +218,27 @@ class ESP32Camera : public camera::Camera { #endif // USE_I2C }; -class ESP32CameraImageTrigger : public Trigger { +class ESP32CameraImageTrigger : public Trigger, public camera::CameraListener { 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); - }); + explicit ESP32CameraImageTrigger(ESP32Camera *parent) { parent->add_listener(this); } + void on_camera_image(const std::shared_ptr &image) override { + 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<> { +class ESP32CameraStreamStartTrigger : public Trigger<>, public camera::CameraListener { public: - explicit ESP32CameraStreamStartTrigger(ESP32Camera *parent) { - parent->add_stream_start_callback([this]() { this->trigger(); }); - } - - protected: + explicit ESP32CameraStreamStartTrigger(ESP32Camera *parent) { parent->add_listener(this); } + void on_stream_start() override { this->trigger(); } }; -class ESP32CameraStreamStopTrigger : public Trigger<> { - public: - explicit ESP32CameraStreamStopTrigger(ESP32Camera *parent) { - parent->add_stream_stop_callback([this]() { this->trigger(); }); - } - protected: +class ESP32CameraStreamStopTrigger : public Trigger<>, public camera::CameraListener { + public: + explicit ESP32CameraStreamStopTrigger(ESP32Camera *parent) { parent->add_listener(this); } + void on_stream_stop() override { this->trigger(); } }; } // namespace esp32_camera diff --git a/esphome/components/esp32_camera_web_server/camera_web_server.cpp b/esphome/components/esp32_camera_web_server/camera_web_server.cpp index 1b8198929..f49578c42 100644 --- a/esphome/components/esp32_camera_web_server/camera_web_server.cpp +++ b/esphome/components/esp32_camera_web_server/camera_web_server.cpp @@ -67,12 +67,14 @@ void CameraWebServer::setup() { httpd_register_uri_handler(this->httpd_, &uri); - camera::Camera::instance()->add_image_callback([this](std::shared_ptr image) { - if (this->running_ && image->was_requested_by(camera::WEB_REQUESTER)) { - this->image_ = std::move(image); - xSemaphoreGive(this->semaphore_); - } - }); + camera::Camera::instance()->add_listener(this); +} + +void CameraWebServer::on_camera_image(const std::shared_ptr &image) { + if (this->running_ && image->was_requested_by(camera::WEB_REQUESTER)) { + this->image_ = image; + xSemaphoreGive(this->semaphore_); + } } void CameraWebServer::on_shutdown() { diff --git a/esphome/components/esp32_camera_web_server/camera_web_server.h b/esphome/components/esp32_camera_web_server/camera_web_server.h index e70246745..ad7b29fb1 100644 --- a/esphome/components/esp32_camera_web_server/camera_web_server.h +++ b/esphome/components/esp32_camera_web_server/camera_web_server.h @@ -18,7 +18,7 @@ namespace esp32_camera_web_server { enum Mode { STREAM, SNAPSHOT }; -class CameraWebServer : public Component { +class CameraWebServer : public Component, public camera::CameraListener { public: CameraWebServer(); ~CameraWebServer(); @@ -31,6 +31,9 @@ class CameraWebServer : public Component { void set_mode(Mode mode) { this->mode_ = mode; } void loop() override; + /// CameraListener interface + void on_camera_image(const std::shared_ptr &image) override; + protected: std::shared_ptr wait_for_image_(); esp_err_t handler_(struct httpd_req *req);