1
0
mirror of https://github.com/esphome/esphome.git synced 2026-02-08 00:31:58 +00:00

[esp32_camera] Replace std::function callbacks with CameraListener interface (#12165)

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
J. Nick Koston
2025-12-02 09:59:32 -06:00
committed by GitHub
parent deda7a1bf3
commit f9ad832e7b
7 changed files with 78 additions and 56 deletions

View File

@@ -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<camera::CameraImage> &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<camera::CameraImage> &image) {
for (auto &c : this->clients_) {
if (!c->flags_.remove)
c->set_camera_state(image);
}
}
#endif
void APIServer::on_shutdown() {
this->shutting_down_ = true;

View File

@@ -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 <map>
#include <vector>
@@ -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<camera::CameraImage> &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);

View File

@@ -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<CameraImage> &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<void(std::shared_ptr<CameraImage>)> &&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.

View File

@@ -205,7 +205,9 @@ void ESP32Camera::loop() {
this->current_image_ = std::make_shared<ESP32CameraImage>(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<void(std::shared_ptr<camera::CameraImage>)> &&callback) {
this->new_image_callback_.add(std::move(callback));
}
void ESP32Camera::add_stream_start_callback(std::function<void()> &&callback) {
this->stream_start_callback_.add(std::move(callback));
}
void ESP32Camera::add_stream_stop_callback(std::function<void()> &&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); }

View File

@@ -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<void(std::shared_ptr<camera::CameraImage>)> &&callback) override;
void add_stream_start_callback(std::function<void()> &&callback);
void add_stream_stop_callback(std::function<void()> &&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<void(std::shared_ptr<camera::CameraImage>)> new_image_callback_{};
CallbackManager<void()> stream_start_callback_{};
CallbackManager<void()> stream_stop_callback_{};
std::vector<camera::CameraListener *> 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<CameraImageData> {
class ESP32CameraImageTrigger : public Trigger<CameraImageData>, public camera::CameraListener {
public:
explicit ESP32CameraImageTrigger(ESP32Camera *parent) {
parent->add_image_callback([this](const std::shared_ptr<camera::CameraImage> &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<camera::CameraImage> &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

View File

@@ -67,12 +67,14 @@ void CameraWebServer::setup() {
httpd_register_uri_handler(this->httpd_, &uri);
camera::Camera::instance()->add_image_callback([this](std::shared_ptr<camera::CameraImage> 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<camera::CameraImage> &image) {
if (this->running_ && image->was_requested_by(camera::WEB_REQUESTER)) {
this->image_ = image;
xSemaphoreGive(this->semaphore_);
}
}
void CameraWebServer::on_shutdown() {

View File

@@ -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<camera::CameraImage> &image) override;
protected:
std::shared_ptr<camera::CameraImage> wait_for_image_();
esp_err_t handler_(struct httpd_req *req);