diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index f615815023..1f629c2c85 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -20,6 +20,7 @@ namespace esphome { namespace api { static const char *const TAG = "api.connection"; +static const int ESP32_CAMERA_STOP_STREAM = 5000; APIConnection::APIConnection(std::unique_ptr sock, APIServer *parent) : parent_(parent), initial_state_iterator_(parent, this), list_entities_iterator_(parent, this) { @@ -704,7 +705,9 @@ void APIConnection::send_camera_state(std::shared_ptr return; if (this->image_reader_.available()) return; - this->image_reader_.set_image(std::move(image)); + if (image->was_requested_by(esphome::esp32_camera::API_REQUESTER) || + image->was_requested_by(esphome::esp32_camera::IDLE)) + this->image_reader_.set_image(std::move(image)); } bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) { ListEntitiesCameraResponse msg; @@ -722,9 +725,14 @@ void APIConnection::camera_image(const CameraImageRequest &msg) { return; if (msg.single) - esp32_camera::global_esp32_camera->request_image(); - if (msg.stream) - esp32_camera::global_esp32_camera->request_stream(); + esp32_camera::global_esp32_camera->request_image(esphome::esp32_camera::API_REQUESTER); + if (msg.stream) { + esp32_camera::global_esp32_camera->start_stream(esphome::esp32_camera::API_REQUESTER); + + App.scheduler.set_timeout(this->parent_, "api_esp32_camera_stop_stream", ESP32_CAMERA_STOP_STREAM, []() { + esp32_camera::global_esp32_camera->stop_stream(esphome::esp32_camera::API_REQUESTER); + }); + } } #endif diff --git a/esphome/components/esp32_camera/esp32_camera.cpp b/esphome/components/esp32_camera/esp32_camera.cpp index 54307dce41..7d11f98d09 100644 --- a/esphome/components/esp32_camera/esp32_camera.cpp +++ b/esphome/components/esp32_camera/esp32_camera.cpp @@ -133,6 +133,13 @@ void ESP32Camera::loop() { 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; @@ -140,7 +147,6 @@ void ESP32Camera::loop() { // image is still in use return; } - const uint32_t now = millis(); if (now - this->last_update_ <= this->max_update_interval_) return; @@ -157,12 +163,12 @@ void ESP32Camera::loop() { xQueueSend(this->framebuffer_return_queue_, &fb, portMAX_DELAY); return; } - this->current_image_ = std::make_shared(fb); + 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_requester_ = false; + this->single_requesters_ = 0; } void ESP32Camera::framebuffer_task(void *pv) { while (true) { @@ -258,24 +264,10 @@ void ESP32Camera::set_brightness(int brightness) { this->brightness_ = brightnes void ESP32Camera::set_saturation(int saturation) { this->saturation_ = saturation; } float ESP32Camera::get_setup_priority() const { return setup_priority::DATA; } uint32_t ESP32Camera::hash_base() { return 3010542557UL; } -void ESP32Camera::request_image() { this->single_requester_ = true; } -void ESP32Camera::request_stream() { this->last_stream_request_ = millis(); } -bool ESP32Camera::has_requested_image_() const { - if (this->single_requester_) - // single request - return true; - - uint32_t now = millis(); - if (now - this->last_stream_request_ < 5000) - // stream request - return true; - - if (this->idle_update_interval_ != 0 && now - this->last_update_ > this->idle_update_interval_) - // idle update - return true; - - return false; -} +void ESP32Camera::request_image(CameraRequester requester) { this->single_requesters_ |= 1 << requester; } +void ESP32Camera::start_stream(CameraRequester requester) { this->stream_requesters_ |= 1 << requester; } +void ESP32Camera::stop_stream(CameraRequester requester) { this->stream_requesters_ &= ~(1 << requester); } +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::set_max_update_interval(uint32_t max_update_interval) { this->max_update_interval_ = max_update_interval; @@ -304,7 +296,10 @@ uint8_t *CameraImageReader::peek_data_buffer() { return this->image_->get_data_b 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; } -CameraImage::CameraImage(camera_fb_t *buffer) : buffer_(buffer) {} +bool CameraImage::was_requested_by(CameraRequester requester) const { + return (this->requesters_ & (1 << requester)) != 0; +} +CameraImage::CameraImage(camera_fb_t *buffer, uint8_t requesters) : buffer_(buffer), requesters_(requesters) {} } // namespace esp32_camera } // namespace esphome diff --git a/esphome/components/esp32_camera/esp32_camera.h b/esphome/components/esp32_camera/esp32_camera.h index b20485a0f7..b2670078f3 100644 --- a/esphome/components/esp32_camera/esp32_camera.h +++ b/esphome/components/esp32_camera/esp32_camera.h @@ -14,15 +14,19 @@ namespace esp32_camera { class ESP32Camera; +enum CameraRequester { IDLE, API_REQUESTER, WEB_REQUESTER }; + class CameraImage { public: - CameraImage(camera_fb_t *buffer); + 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_; }; class CameraImageReader { @@ -81,8 +85,9 @@ class ESP32Camera : public Component, public EntityBase { void dump_config() override; void add_image_callback(std::function)> &&f); float get_setup_priority() const override; - void request_stream(); - void request_image(); + void start_stream(CameraRequester requester); + void stop_stream(CameraRequester requester); + void request_image(CameraRequester requester); protected: uint32_t hash_base() override; @@ -104,13 +109,14 @@ class ESP32Camera : public Component, public EntityBase { esp_err_t init_error_{ESP_OK}; std::shared_ptr current_image_; - uint32_t last_stream_request_{0}; - bool single_requester_{false}; + uint8_t single_requesters_{0}; + uint8_t stream_requesters_{0}; QueueHandle_t framebuffer_get_queue_; QueueHandle_t framebuffer_return_queue_; CallbackManager)> new_image_callback_; uint32_t max_update_interval_{1000}; uint32_t idle_update_interval_{15000}; + uint32_t last_idle_request_{0}; uint32_t last_update_{0}; }; 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 f5ab0c7151..39b110bc85 100644 --- a/esphome/components/esp32_camera_web_server/camera_web_server.cpp +++ b/esphome/components/esp32_camera_web_server/camera_web_server.cpp @@ -14,7 +14,7 @@ namespace esphome { namespace esp32_camera_web_server { -static const int IMAGE_REQUEST_TIMEOUT = 2000; +static const int IMAGE_REQUEST_TIMEOUT = 5000; static const char *const TAG = "esp32_camera_web_server"; #define PART_BOUNDARY "123456789000000000000987654321" @@ -68,7 +68,7 @@ void CameraWebServer::setup() { httpd_register_uri_handler(this->httpd_, &uri); esp32_camera::global_esp32_camera->add_image_callback([this](std::shared_ptr image) { - if (this->running_) { + if (this->running_ && image->was_requested_by(esp32_camera::WEB_REQUESTER)) { this->image_ = std::move(image); xSemaphoreGive(this->semaphore_); } @@ -169,11 +169,9 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) { uint32_t last_frame = millis(); uint32_t frames = 0; - while (res == ESP_OK && this->running_) { - if (esp32_camera::global_esp32_camera != nullptr) { - esp32_camera::global_esp32_camera->request_stream(); - } + esp32_camera::global_esp32_camera->start_stream(esphome::esp32_camera::WEB_REQUESTER); + while (res == ESP_OK && this->running_) { auto image = this->wait_for_image_(); if (!image) { @@ -204,6 +202,8 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) { res = httpd_send_all(req, STREAM_ERROR, strlen(STREAM_ERROR)); } + esp32_camera::global_esp32_camera->stop_stream(esphome::esp32_camera::WEB_REQUESTER); + ESP_LOGI(TAG, "STREAM: closed. Frames: %u", frames); return res; @@ -212,9 +212,7 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) { esp_err_t CameraWebServer::snapshot_handler_(struct httpd_req *req) { esp_err_t res = ESP_OK; - if (esp32_camera::global_esp32_camera != nullptr) { - esp32_camera::global_esp32_camera->request_image(); - } + esp32_camera::global_esp32_camera->request_image(esphome::esp32_camera::WEB_REQUESTER); auto image = this->wait_for_image_();