From 0a74cb4e895b477c655fda568272d75a5fec812e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 4 Sep 2026 12:42:07 +0200 Subject: [PATCH] Apply the dial delay the same way at boot and after a disconnect, tell socket create and setblocking failures apart, and cap the delay so the wait always elapses --- esphome/components/api/__init__.py | 5 +++-- esphome/components/api/api_outgoing_connection.cpp | 7 +++---- tests/component_tests/api/test_outgoing_connection.py | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/esphome/components/api/__init__.py b/esphome/components/api/__init__.py index 4a5112c490..6a0bd61138 100644 --- a/esphome/components/api/__init__.py +++ b/esphome/components/api/__init__.py @@ -317,10 +317,11 @@ _OUTGOING_CONNECTION_SCHEMA = cv.Schema( { cv.Optional(CONF_HOST): cv.ipaddress, cv.Optional(CONF_PORT, default=6054): cv.port, - # Bounded so the value cannot wrap the device's uint32 milliseconds + # Bounded to half the device's uint32 millisecond range so the wait + # always elapses under a wrapping clock cv.Optional(CONF_DELAY, default="60s"): cv.All( cv.positive_time_period_milliseconds, - cv.Range(max=cv.TimePeriod(milliseconds=4294967295)), + cv.Range(max=cv.TimePeriod(milliseconds=2147483647)), ), } ) diff --git a/esphome/components/api/api_outgoing_connection.cpp b/esphome/components/api/api_outgoing_connection.cpp index cd5bb7dfb7..da88db1e6c 100644 --- a/esphome/components/api/api_outgoing_connection.cpp +++ b/esphome/components/api/api_outgoing_connection.cpp @@ -43,9 +43,8 @@ void OutgoingConnectionManager::loop(APIServer *server) { const uint32_t now = App.get_loop_component_start_time(); switch (this->state_) { case DialState::DIAL_STATE_IDLE: -#if defined(API_OUTGOING_CONNECTION_HOST) || defined(USE_DEEP_SLEEP) - // A fixed host has no inbound path worth waiting for, and a deep - // sleep wake window is too short to spend on the delay +#ifdef USE_DEEP_SLEEP + // A deep sleep wake window is too short to spend on the delay this->schedule_wait_(now, BACKOFF_MIN_MS); #else // Target went away; give it the configured delay to reconnect first @@ -102,7 +101,7 @@ void OutgoingConnectionManager::try_dial_(APIServer *server, uint32_t now) { } this->dial_socket_ = socket::socket_loop_monitored(((struct sockaddr *) &addr)->sa_family, SOCK_STREAM, IPPROTO_TCP); if (!this->dial_socket_ || this->dial_socket_->setblocking(false) != 0) { - ESP_LOGW(TAG, "Socket create failed: errno %d", errno); + ESP_LOGW(TAG, "Socket %s failed: errno %d", this->dial_socket_ ? "setblocking" : "create", errno); this->schedule_retry_(now); return; } diff --git a/tests/component_tests/api/test_outgoing_connection.py b/tests/component_tests/api/test_outgoing_connection.py index 38e8199c63..14e10fcbe0 100644 --- a/tests/component_tests/api/test_outgoing_connection.py +++ b/tests/component_tests/api/test_outgoing_connection.py @@ -66,7 +66,7 @@ def test_outgoing_connection_bare_block( def test_outgoing_connection_delay_bounded( set_core_config: SetCoreConfigCallable, ) -> None: - """A delay past the device's uint32 milliseconds is rejected, not wrapped.""" + """A delay past half the uint32 millisecond range is rejected, not wrapped.""" set_core_config(PlatformFramework.ESP32_IDF, platform_data=ESP32_PLATFORM_DATA) with pytest.raises(cv.Invalid, match="value must be at most"): CONFIG_SCHEMA(_api_config({"delay": "60d"}))