From 587b44bc14fd997b14d20ef25348d2d68f8b48dd Mon Sep 17 00:00:00 2001 From: Michael Gorven Date: Tue, 23 May 2023 11:47:01 -0700 Subject: [PATCH] Implement connection retries for http_request Add `count`, `delay` and `backoff_factor` options to `http_request` component under `retry` option. Use `Component::set_retry()` to retry HTTP requests. Requests are only retried for network and connection errors; if an HTTP response is received the request is not retried. --- esphome/components/http_request/__init__.py | 16 ++++++++++++++++ .../components/http_request/http_request.cpp | 17 +++++++++++++---- esphome/components/http_request/http_request.h | 9 ++++++++- tests/test1.yaml | 4 ++++ 4 files changed, 41 insertions(+), 5 deletions(-) diff --git a/esphome/components/http_request/__init__.py b/esphome/components/http_request/__init__.py index 0c3e249512..ae52a853a9 100644 --- a/esphome/components/http_request/__init__.py +++ b/esphome/components/http_request/__init__.py @@ -4,6 +4,8 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import automation from esphome.const import ( + CONF_COUNT, + CONF_DELAY, CONF_ID, CONF_TIMEOUT, CONF_METHOD, @@ -33,6 +35,8 @@ CONF_VERIFY_SSL = "verify_ssl" CONF_ON_RESPONSE = "on_response" CONF_FOLLOW_REDIRECTS = "follow_redirects" CONF_REDIRECT_LIMIT = "redirect_limit" +CONF_RETRY = "retry" +CONF_BACKOFF_FACTOR = "backoff_factor" def validate_url(value): @@ -78,6 +82,15 @@ CONFIG_SCHEMA = cv.All( cv.Optional( CONF_TIMEOUT, default="5s" ): cv.positive_time_period_milliseconds, + cv.Optional(CONF_RETRY, default={}): cv.Schema( + { + cv.Optional(CONF_COUNT, 0): cv.int_, + cv.Optional( + CONF_DELAY, default="1s" + ): cv.positive_time_period_milliseconds, + cv.Optional(CONF_BACKOFF_FACTOR, 1.0): cv.float_, + } + ), cv.SplitDefault(CONF_ESP8266_DISABLE_SSL_SUPPORT, esp8266=False): cv.All( cv.only_on_esp8266, cv.boolean ), @@ -96,6 +109,9 @@ async def to_code(config): cg.add(var.set_useragent(config[CONF_USERAGENT])) cg.add(var.set_follow_redirects(config[CONF_FOLLOW_REDIRECTS])) cg.add(var.set_redirect_limit(config[CONF_REDIRECT_LIMIT])) + cg.add(var.set_retries(config[CONF_RETRY][CONF_COUNT])) + cg.add(var.set_retry_delay(config[CONF_RETRY][CONF_DELAY])) + cg.add(var.set_retry_backoff_factor(config[CONF_RETRY][CONF_BACKOFF_FACTOR])) if CORE.is_esp8266 and not config[CONF_ESP8266_DISABLE_SSL_SUPPORT]: cg.add_define("USE_HTTP_REQUEST_ESP8266_HTTPS") diff --git a/esphome/components/http_request/http_request.cpp b/esphome/components/http_request/http_request.cpp index 46894a9afd..c0434f1369 100644 --- a/esphome/components/http_request/http_request.cpp +++ b/esphome/components/http_request/http_request.cpp @@ -31,11 +31,19 @@ void HttpRequestComponent::set_url(std::string url) { } void HttpRequestComponent::send(const std::vector &response_triggers) { + this->cancel_retry("http_request"); + this->set_retry("http_request", this->retry_delay_, this->retries_ + 1, + std::bind(&HttpRequestComponent::send_, this, response_triggers, std::placeholders::_1), + this->retry_backoff_factor_); +} + +RetryResult HttpRequestComponent::send_(const std::vector &response_triggers, + uint8_t attempt) { if (!network::is_connected()) { this->client_.end(); this->status_set_warning(); ESP_LOGW(TAG, "HTTP Request failed; Not connected to network"); - return; + return RetryResult::RETRY; } bool begin_status = false; @@ -62,7 +70,7 @@ void HttpRequestComponent::send(const std::vector this->client_.end(); this->status_set_warning(); ESP_LOGW(TAG, "HTTP Request failed at the begin phase. Please check the configuration"); - return; + return RetryResult::DONE; } this->client_.setTimeout(this->timeout_); @@ -86,17 +94,18 @@ void HttpRequestComponent::send(const std::vector ESP_LOGW(TAG, "HTTP Request failed; URL: %s; Error: %s; Duration: %u ms", this->url_.c_str(), HTTPClient::errorToString(http_code).c_str(), duration); this->status_set_warning(); - return; + return RetryResult::RETRY; } if (http_code < 200 || http_code >= 300) { ESP_LOGW(TAG, "HTTP Request failed; URL: %s; Code: %d; Duration: %u ms", this->url_.c_str(), http_code, duration); this->status_set_warning(); - return; + return RetryResult::DONE; } this->status_clear_warning(); ESP_LOGD(TAG, "HTTP Request completed; URL: %s; Code: %d; Duration: %u ms", this->url_.c_str(), http_code, duration); + return RetryResult::DONE; } #ifdef USE_ESP8266 diff --git a/esphome/components/http_request/http_request.h b/esphome/components/http_request/http_request.h index b885de18e6..786a7d447c 100644 --- a/esphome/components/http_request/http_request.h +++ b/esphome/components/http_request/http_request.h @@ -45,6 +45,9 @@ class HttpRequestComponent : public Component { void set_method(const char *method) { this->method_ = method; } void set_useragent(const char *useragent) { this->useragent_ = useragent; } void set_timeout(uint16_t timeout) { this->timeout_ = timeout; } + void set_retries(uint16_t retries) { this->retries_ = retries; } + void set_retry_delay(uint16_t retry_delay) { this->retry_delay_ = retry_delay; } + void set_retry_backoff_factor(float retry_backoff_factor) { this->retry_backoff_factor_ = retry_backoff_factor; } void set_follow_redirects(bool follow_redirects) { this->follow_redirects_ = follow_redirects; } void set_redirect_limit(uint16_t limit) { this->redirect_limit_ = limit; } void set_body(const std::string &body) { this->body_ = body; } @@ -63,6 +66,9 @@ class HttpRequestComponent : public Component { bool follow_redirects_; uint16_t redirect_limit_; uint16_t timeout_{5000}; + uint16_t retries_{0}; + uint16_t retry_delay_{1000}; + float retry_backoff_factor_{1.0}; std::string body_; std::list
headers_; #ifdef USE_ESP8266 @@ -72,6 +78,7 @@ class HttpRequestComponent : public Component { #endif std::shared_ptr get_wifi_client_(); #endif + RetryResult send_(const std::vector &response_triggers, uint8_t attempt); }; template class HttpRequestSendAction : public Action { @@ -92,6 +99,7 @@ template class HttpRequestSendAction : public Action { void play(Ts... x) override { this->parent_->set_url(this->url_.value(x...)); this->parent_->set_method(this->method_.value(x...)); + this->parent_->set_body(""); if (this->body_.has_value()) { this->parent_->set_body(this->body_.value(x...)); } @@ -114,7 +122,6 @@ template class HttpRequestSendAction : public Action { this->parent_->set_headers(headers); this->parent_->send(this->response_triggers_); this->parent_->close(); - this->parent_->set_body(""); } protected: diff --git a/tests/test1.yaml b/tests/test1.yaml index bc7a94bc5a..97d992b5d3 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -87,6 +87,10 @@ mdns: http_request: useragent: esphome/device timeout: 10s + retry: + count: 3 + delay: 1s + backoff_factor: 1.5 mqtt: broker: "192.168.178.84"