diff --git a/esphome/components/online_image/__init__.py b/esphome/components/online_image/__init__.py index 55b9037176..7ef80ff92f 100644 --- a/esphome/components/online_image/__init__.py +++ b/esphome/components/online_image/__init__.py @@ -213,7 +213,7 @@ async def to_code(config): for conf in config.get(CONF_ON_DOWNLOAD_FINISHED, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) + await automation.build_automation(trigger, [(bool, "cached")], conf) for conf in config.get(CONF_ON_ERROR, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) diff --git a/esphome/components/online_image/online_image.cpp b/esphome/components/online_image/online_image.cpp index cb4a3be9e8..5c0ffc1cb2 100644 --- a/esphome/components/online_image/online_image.cpp +++ b/esphome/components/online_image/online_image.cpp @@ -3,6 +3,10 @@ #include "esphome/core/log.h" static const char *const TAG = "online_image"; +static const char *const ETAG_HEADER_NAME = "etag"; +static const char *const IF_NONE_MATCH_HEADER_NAME = "if-none-match"; +static const char *const LAST_MODIFIED_HEADER_NAME = "last-modified"; +static const char *const IF_MODIFIED_SINCE_HEADER_NAME = "if-modified-since"; #include "image_decoder.h" @@ -60,6 +64,8 @@ void OnlineImage::release() { this->height_ = 0; this->buffer_width_ = 0; this->buffer_height_ = 0; + this->last_modified_ = ""; + this->etag_ = ""; this->end_connection_(); } } @@ -127,9 +133,17 @@ void OnlineImage::update() { } accept_header.value = accept_mime_type + ",*/*;q=0.8"; + if (!this->etag_.empty()) { + headers.push_back(http_request::Header{IF_NONE_MATCH_HEADER_NAME, this->etag_}); + } + + if (!this->last_modified_.empty()) { + headers.push_back(http_request::Header{IF_MODIFIED_SINCE_HEADER_NAME, this->last_modified_}); + } + headers.push_back(accept_header); - this->downloader_ = this->parent_->get(this->url_, headers); + this->downloader_ = this->parent_->get(this->url_, headers, {ETAG_HEADER_NAME, LAST_MODIFIED_HEADER_NAME}); if (this->downloader_ == nullptr) { ESP_LOGE(TAG, "Download failed."); @@ -141,7 +155,9 @@ void OnlineImage::update() { int http_code = this->downloader_->status_code; if (http_code == HTTP_CODE_NOT_MODIFIED) { // Image hasn't changed on server. Skip download. + ESP_LOGI(TAG, "Server returned HTTP 304 (Not Modified). Download skipped."); this->end_connection_(); + this->download_finished_callback_.call(true); return; } if (http_code != HTTP_CODE_OK) { @@ -201,8 +217,10 @@ void OnlineImage::loop() { ESP_LOGD(TAG, "Image fully downloaded, read %zu bytes, width/height = %d/%d", this->downloader_->get_bytes_read(), this->width_, this->height_); ESP_LOGD(TAG, "Total time: %lds", ::time(nullptr) - this->start_time_); + this->etag_ = this->downloader_->get_response_header(ETAG_HEADER_NAME); + this->last_modified_ = this->downloader_->get_response_header(LAST_MODIFIED_HEADER_NAME); + this->download_finished_callback_.call(false); this->end_connection_(); - this->download_finished_callback_.call(); return; } if (this->downloader_ == nullptr) { @@ -325,7 +343,7 @@ bool OnlineImage::validate_url_(const std::string &url) { return true; } -void OnlineImage::add_on_finished_callback(std::function &&callback) { +void OnlineImage::add_on_finished_callback(std::function &&callback) { this->download_finished_callback_.add(std::move(callback)); } diff --git a/esphome/components/online_image/online_image.h b/esphome/components/online_image/online_image.h index 2d10e528b1..920aee2796 100644 --- a/esphome/components/online_image/online_image.h +++ b/esphome/components/online_image/online_image.h @@ -63,6 +63,8 @@ class OnlineImage : public PollingComponent, if (this->validate_url_(url)) { this->url_ = url; } + this->etag_ = ""; + this->last_modified_ = ""; } /** @@ -86,7 +88,7 @@ class OnlineImage : public PollingComponent, */ size_t resize_download_buffer(size_t size) { return this->download_buffer_.resize(size); } - void add_on_finished_callback(std::function &&callback); + void add_on_finished_callback(std::function &&callback); void add_on_error_callback(std::function &&callback); protected: @@ -131,7 +133,7 @@ class OnlineImage : public PollingComponent, void end_connection_(); - CallbackManager download_finished_callback_{}; + CallbackManager download_finished_callback_{}; CallbackManager download_error_callback_{}; std::shared_ptr downloader_{nullptr}; @@ -173,6 +175,14 @@ class OnlineImage : public PollingComponent, * decoded images). */ int buffer_height_; + /** + * The value of the ETag HTTP header provided in the last response. + */ + std::string etag_ = ""; + /** + * The value of the Last-Modified HTTP header provided in the last response. + */ + std::string last_modified_ = ""; time_t start_time_; @@ -202,10 +212,10 @@ template class OnlineImageReleaseAction : public Action { OnlineImage *parent_; }; -class DownloadFinishedTrigger : public Trigger<> { +class DownloadFinishedTrigger : public Trigger { public: explicit DownloadFinishedTrigger(OnlineImage *parent) { - parent->add_on_finished_callback([this]() { this->trigger(); }); + parent->add_on_finished_callback([this](bool cached) { this->trigger(cached); }); } }; diff --git a/tests/components/online_image/common.yaml b/tests/components/online_image/common.yaml index 69daa915c5..25809930b5 100644 --- a/tests/components/online_image/common.yaml +++ b/tests/components/online_image/common.yaml @@ -11,6 +11,13 @@ online_image: format: PNG type: BINARY resize: 50x50 + on_download_finished: + lambda: |- + if (cached) { + ESP_LOGD("online_image", "Cache hit: using cached image"); + } else { + ESP_LOGD("online_image", "Cache miss: fresh download"); + } - id: online_binary_transparent_image url: http://www.libpng.org/pub/png/img_png/pnglogo-blk-tiny.png type: BINARY