diff --git a/esphome/components/api/__init__.py b/esphome/components/api/__init__.py index 9dc4c8f3d3e..34e6b37521f 100644 --- a/esphome/components/api/__init__.py +++ b/esphome/components/api/__init__.py @@ -52,6 +52,7 @@ from esphome.core import CORE, ID, CoroPriority, EsphomeError, coroutine_with_pr from esphome.cpp_generator import MockObj, TemplateArgsType import esphome.final_validate as fv from esphome.helpers import fnv1_hash +from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor from esphome.types import ConfigFragmentType, ConfigType # Compat alias: downstream consumers (e.g. device-builder) referenced the @@ -325,9 +326,13 @@ _OUTGOING_CONNECTION_SCHEMA = cv.Schema( ) +@schema_extractor("schema") def _outgoing_connection_schema(config: ConfigType | None) -> ConfigType: # A bare `outgoing_connection:` block is valid; without a host the device # dials the remembered last dial-back client + if config is SCHEMA_EXTRACT: + # Let the language-schema dumper walk host, port and delay + return _OUTGOING_CONNECTION_SCHEMA if config is None: config = {} return _OUTGOING_CONNECTION_SCHEMA(config) diff --git a/esphome/components/api/api_outgoing_connection.cpp b/esphome/components/api/api_outgoing_connection.cpp index d1be8006141..6838885d252 100644 --- a/esphome/components/api/api_outgoing_connection.cpp +++ b/esphome/components/api/api_outgoing_connection.cpp @@ -17,11 +17,10 @@ namespace esphome::api { static const char *const TAG = "api.outgoing"; #ifndef API_OUTGOING_CONNECTION_HOST -#if USE_NETWORK_IPV6 -// A dual-stack listener reports an IPv4 peer as ::ffff:a.b.c.d -static constexpr uint8_t V4_MAPPED_PREFIX[12] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF}; +static constexpr uint32_t OUTGOING_TARGET_PREF_HASH = 629847102UL; #endif +#ifndef API_OUTGOING_CONNECTION_HOST // Read the connection's peer address into target; false when unavailable or // of a family this build cannot dial static bool peer_to_target(APIConnection *conn, SavedOutgoingTarget &target) { @@ -35,9 +34,12 @@ static bool peer_to_target(APIConnection *conn, SavedOutgoingTarget &target) { if (family == AF_INET6) { const auto *addr6 = reinterpret_cast(&peer); const auto *bytes = reinterpret_cast(&addr6->sin6_addr); - if (memcmp(bytes, V4_MAPPED_PREFIX, sizeof(V4_MAPPED_PREFIX)) == 0) { + uint32_t prefix[3]; + memcpy(prefix, bytes, sizeof(prefix)); + // A dual-stack listener reports an IPv4 peer as ::ffff:a.b.c.d + if (prefix[0] == 0 && prefix[1] == 0 && prefix[2] == htonl(0xFFFFUL)) { target.family = AF_INET; - memcpy(target.addr, bytes + sizeof(V4_MAPPED_PREFIX), 4); + memcpy(target.addr, bytes + sizeof(prefix), 4); return true; } target.family = AF_INET6; @@ -83,26 +85,28 @@ socklen_t OutgoingConnectionManager::target_sockaddr_(struct sockaddr_storage *a #endif } +#ifndef API_OUTGOING_CONNECTION_HOST void OutgoingConnectionManager::format_target_(std::span buf) const { struct sockaddr_storage addr; socklen_t addr_len = this->target_sockaddr_(&addr); - if (addr_len == 0 || socket::format_sockaddr_to((struct sockaddr *) &addr, addr_len, buf) == 0) { + if (addr_len == 0) { buf[0] = '\0'; + return; } + // Clears buf itself if it cannot format the address + socket::format_sockaddr_to((struct sockaddr *) &addr, addr_len, buf); } +#endif void OutgoingConnectionManager::setup() { #ifndef API_OUTGOING_CONNECTION_HOST - this->target_pref_ = global_preferences->make_preference(629847102UL, true); + this->target_pref_ = global_preferences->make_preference(OUTGOING_TARGET_PREF_HASH, true); struct sockaddr_storage addr; + // dump_config() prints whichever target this leaves in place if (this->target_pref_.load(&this->saved_) && this->target_sockaddr_(&addr) != 0) { this->host_persisted_ = true; - char host[socket::SOCKADDR_STR_LEN]; - this->format_target_(host); - ESP_LOGD(TAG, "Loaded target %s", host); } else { // Never saved, failed its size or CRC check, or holds an unknown family - ESP_LOGD(TAG, "No saved target"); this->saved_ = {}; } #endif @@ -120,13 +124,8 @@ void OutgoingConnectionManager::loop(APIServer *server) { const uint32_t now = App.get_loop_component_start_time(); switch (this->state_) { case DialState::DIAL_STATE_IDLE: -#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 - this->schedule_wait_(now, API_OUTGOING_CONNECTION_DELAY); -#endif + this->schedule_wait_(now, IDLE_WAIT_MS); break; case DialState::DIAL_STATE_WAITING: if (now - this->state_ts_ >= this->wait_) { @@ -148,15 +147,13 @@ void OutgoingConnectionManager::try_dial_(APIServer *server, uint32_t now) { } struct sockaddr_storage addr; socklen_t addr_len = this->target_sockaddr_(&addr); - if (addr_len == 0) { - // The steady state until a dial-back client has ever connected - ESP_LOGV(TAG, "Not dialing: no target"); - this->schedule_wait_(now, PRECONDITION_RETRY_MS); - return; - } const bool at_limit = server->at_client_limit_(); - if (at_limit || !server->noise_ctx_.has_psk()) { - ESP_LOGD(TAG, "Not dialing: %s", at_limit ? LOG_STR_LITERAL("max connections") : LOG_STR_LITERAL("no key")); + // No target is the steady state until a dial-back client has ever connected + if (addr_len == 0 || at_limit || !server->noise_ctx_.has_psk()) { + // Repeats for as long as the reason holds, so keep it out of debug logs + ESP_LOGV(TAG, "Not dialing: %s", + addr_len == 0 ? LOG_STR_LITERAL("no target") + : (at_limit ? LOG_STR_LITERAL("max connections") : LOG_STR_LITERAL("no key"))); // Not a dial failure; retry without escalating the backoff this->schedule_wait_(now, PRECONDITION_RETRY_MS); return; @@ -168,9 +165,13 @@ void OutgoingConnectionManager::try_dial_(APIServer *server, uint32_t now) { this->schedule_retry_(now); return; } +#ifdef API_OUTGOING_CONNECTION_HOST + ESP_LOGD(TAG, "Dialing " API_OUTGOING_CONNECTION_HOST ":%u", API_OUTGOING_CONNECTION_PORT); +#else char host[socket::SOCKADDR_STR_LEN]; - this->format_target_(host); + socket::format_sockaddr_to((struct sockaddr *) &addr, addr_len, host); ESP_LOGD(TAG, "Dialing %s:%u", host, API_OUTGOING_CONNECTION_PORT); +#endif int err = this->dial_socket_->connect((struct sockaddr *) &addr, addr_len); if (err == 0) { // Immediate success (possible for localhost) @@ -178,7 +179,7 @@ void OutgoingConnectionManager::try_dial_(APIServer *server, uint32_t now) { return; } if (errno != EINPROGRESS) { - ESP_LOGW(TAG, "Connect failed: errno %d", errno); + ESP_LOGW(TAG, "Connect failed: %d", errno); this->schedule_retry_(now); return; } @@ -279,21 +280,29 @@ void OutgoingConnectionManager::on_target_client(APIConnection *conn) { } char host[socket::SOCKADDR_STR_LEN]; this->format_target_(host); - ESP_LOGD(TAG, "Saved %s as outgoing connection target", host); + ESP_LOGD(TAG, "Remembered %s as the dial target", host); #endif } void OutgoingConnectionManager::dump_config() const { - char buf[socket::SOCKADDR_STR_LEN]; - this->format_target_(buf); - const char *host = buf[0] != '\0' ? buf : "none remembered yet"; // The boot delay differs from delay: on deep sleep builds, so print the // value that actually applies ESP_LOGCONFIG(TAG, " Outgoing connection port: %u\n" - " Outgoing connection host: %s\n" " Outgoing connection boot delay: %" PRIu32 "ms", - API_OUTGOING_CONNECTION_PORT, host, BOOT_WAIT_MS); + API_OUTGOING_CONNECTION_PORT, BOOT_WAIT_MS); + // The text stays in the format string so ESP8266 holds it in flash, not RAM +#ifdef API_OUTGOING_CONNECTION_HOST + ESP_LOGCONFIG(TAG, " Outgoing connection host: " API_OUTGOING_CONNECTION_HOST); +#else + char buf[socket::SOCKADDR_STR_LEN]; + this->format_target_(buf); + if (buf[0] == '\0') { + ESP_LOGCONFIG(TAG, " Outgoing connection host: none remembered yet"); + } else { + ESP_LOGCONFIG(TAG, " Outgoing connection host: %s", buf); + } +#endif } } // namespace esphome::api diff --git a/esphome/components/api/api_outgoing_connection.h b/esphome/components/api/api_outgoing_connection.h index e1f4ee09b94..cc8c1bd34e2 100644 --- a/esphome/components/api/api_outgoing_connection.h +++ b/esphome/components/api/api_outgoing_connection.h @@ -59,12 +59,14 @@ class OutgoingConnectionManager { static constexpr uint32_t CONNECT_POLL_INTERVAL_MS = 250; static constexpr uint32_t NETWORK_RETRY_MS = 500; static constexpr uint32_t PRECONDITION_RETRY_MS = 5000; - // Boot waits for the client to connect in first; a deep sleep wake window - // is short, so connecting out immediately is the wake state + // A deep sleep wake window is too short to spend on the delay, so those + // builds dial out as soon as the target is gone #ifdef USE_DEEP_SLEEP static constexpr uint32_t BOOT_WAIT_MS = 0; + static constexpr uint32_t IDLE_WAIT_MS = BACKOFF_MIN_MS; #else static constexpr uint32_t BOOT_WAIT_MS = API_OUTGOING_CONNECTION_DELAY; + static constexpr uint32_t IDLE_WAIT_MS = API_OUTGOING_CONNECTION_DELAY; #endif void try_dial_(APIServer *server, uint32_t now); @@ -75,17 +77,17 @@ class OutgoingConnectionManager { void schedule_retry_(uint32_t now); // Wait without escalating the backoff (used for unmet preconditions) void schedule_wait_(uint32_t now, uint32_t wait); + /// Fill addr with the target and return its length, or 0 when there is none + socklen_t target_sockaddr_(struct sockaddr_storage *addr) const; #ifndef API_OUTGOING_CONNECTION_HOST // Write saved_ to flash, tracking success in host_persisted_ bool persist_target_() { this->host_persisted_ = this->target_pref_.save(&this->saved_) && global_preferences->sync(); return this->host_persisted_; } -#endif - /// Fill addr with the target and return its length, or 0 when there is none - socklen_t target_sockaddr_(struct sockaddr_storage *addr) const; - /// Format the target for a log line; empty when there is none + /// Format the remembered target for a log line; empty when there is none void format_target_(std::span buf) const; +#endif // Pointers first (4 bytes each on 32-bit) std::unique_ptr dial_socket_; diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index d6fe2c973ea..aba152aa74f 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -56,46 +56,12 @@ APIServer::APIServer() { global_api_server = this; } void APIServer::socket_failed_(const LogString *msg) { ESP_LOGW(TAG, "Socket %s: errno %d", LOG_STR_ARG(msg), errno); this->destroy_socket_(); -} - -bool APIServer::create_listen_socket_() { - this->socket_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0).release(); // monitored for incoming connections - if (this->socket_ == nullptr) { - this->socket_failed_(LOG_STR("creation")); - return false; - } - int enable = 1; - int err = this->socket_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)); - if (err != 0) { - ESP_LOGW(TAG, "Socket reuseaddr: errno %d", errno); - // we can still continue - } - err = this->socket_->setblocking(false); - if (err != 0) { - this->socket_failed_(LOG_STR("nonblocking")); - return false; - } - - struct sockaddr_storage server; - - socklen_t sl = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), this->port_); - if (sl == 0) { - this->socket_failed_(LOG_STR("set sockaddr")); - return false; - } - - err = this->socket_->bind((struct sockaddr *) &server, sl); - if (err != 0) { - this->socket_failed_(LOG_STR("bind")); - return false; - } - - err = this->socket_->listen(this->listen_backlog_); - if (err != 0) { - this->socket_failed_(LOG_STR("listen")); - return false; - } - return true; +#ifdef USE_API_OUTGOING_CONNECTION + // Dial-out needs no listener; degrade instead of stopping the component + this->status_set_error(LOG_STR("listen socket failed")); +#else + this->mark_failed(); +#endif } void APIServer::setup() { @@ -111,16 +77,6 @@ void APIServer::setup() { #endif #endif - if (!this->create_listen_socket_()) { -#ifdef USE_API_OUTGOING_CONNECTION - // Dial-out needs no listener; degrade instead of stopping the component - this->status_set_error(LOG_STR("listen socket failed")); -#else - this->mark_failed(); - return; -#endif - } - #ifdef USE_LOGGER if (logger::global_logger != nullptr) { logger::global_logger->add_log_callback( @@ -169,6 +125,44 @@ void APIServer::setup() { #ifdef USE_API_OUTGOING_CONNECTION this->outgoing_conn_.setup(); #endif + + // Listener last: on failure socket_failed_() returns early, and an + // outgoing_connection build keeps dialing out without one + this->socket_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0).release(); // monitored for incoming connections + if (this->socket_ == nullptr) { + this->socket_failed_(LOG_STR("creation")); + return; + } + int enable = 1; + int err = this->socket_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)); + if (err != 0) { + ESP_LOGW(TAG, "Socket reuseaddr: errno %d", errno); + // we can still continue + } + err = this->socket_->setblocking(false); + if (err != 0) { + this->socket_failed_(LOG_STR("nonblocking")); + return; + } + + struct sockaddr_storage server; + + socklen_t sl = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), this->port_); + if (sl == 0) { + this->socket_failed_(LOG_STR("set sockaddr")); + return; + } + + err = this->socket_->bind((struct sockaddr *) &server, sl); + if (err != 0) { + this->socket_failed_(LOG_STR("bind")); + return; + } + + err = this->socket_->listen(this->listen_backlog_); + if (err != 0) { + this->socket_failed_(LOG_STR("listen")); + } } void APIServer::loop() { @@ -191,12 +185,7 @@ void APIServer::loop() { if (this->reboot_timeout_ != 0 && !this->provisioning_pending_()) { const uint32_t now = App.get_loop_component_start_time(); if (now - this->last_connected_ > this->reboot_timeout_) { - // Distinguish a wrong-key peer from nothing connecting at all - if (this->saw_unauthenticated_client_) { - ESP_LOGE(TAG, "Clients connected but none authenticated; rebooting"); - } else { - ESP_LOGE(TAG, "No clients; rebooting"); - } + ESP_LOGE(TAG, "No clients; rebooting"); App.reboot(); } } @@ -281,9 +270,6 @@ void APIServer::remove_client_(uint8_t client_index) { // healthy session's timestamp and trigger a spurious reboot if (was_authenticated) { this->last_connected_ = App.get_loop_component_start_time(); - this->saw_unauthenticated_client_ = false; - } else { - this->saw_unauthenticated_client_ = true; } if (this->api_connection_count_ == 0 && this->reboot_timeout_ != 0 && !this->provisioning_pending_()) { this->status_set_warning(LOG_STR("waiting for client connection")); @@ -317,18 +303,12 @@ void __attribute__((flatten)) APIServer::accept_new_connections_() { ESP_LOGD(TAG, "Accept %s", peername); - this->add_client_(new APIConnection(std::move(sock), this)); + this->add_client_(std::move(sock)); } } -bool APIServer::add_client_(APIConnection *conn) { - if (this->at_client_limit_()) { - // The accept path checks first to skip the allocation; the outgoing - // handoff relies on this check - ESP_LOGW(TAG, "Max connections (%d), dropping client", MAX_API_CONNECTIONS); - delete conn; - return false; - } +APIConnection *APIServer::add_client_(std::unique_ptr sock) { + auto *conn = new APIConnection(std::move(sock), this); // NOLINT(cppcoreguidelines-owning-memory) this->clients_[this->api_connection_count_++].reset(conn); conn->start(); @@ -338,22 +318,21 @@ bool APIServer::add_client_(APIConnection *conn) { if (this->api_connection_count_ == 1 && this->reboot_timeout_ != 0 && !this->provisioning_pending_()) { this->status_clear_warning(); } - return true; + return conn; } #ifdef USE_API_OUTGOING_CONNECTION APIConnection *APIServer::add_outgoing_client_(std::unique_ptr sock) { - // Re-check at the handoff: the PSK may have been cleared since the dial - // started (mark_outgoing() needs the noise helper); add_client_ re-checks - // the slot limit - if (!this->noise_ctx_.has_psk()) { - ESP_LOGW(TAG, "Dropping outgoing connection (no key)"); - return nullptr; - } - auto *conn = new APIConnection(std::move(sock), this); - if (!this->add_client_(conn)) { + // Re-check at the handoff: inbound clients may have taken the last slot and + // the PSK may have been cleared since the dial started (mark_outgoing() + // needs the noise helper) + const bool at_limit = this->at_client_limit_(); + if (at_limit || !this->noise_ctx_.has_psk()) { + ESP_LOGW(TAG, "Dropping outgoing connection (%s)", + at_limit ? LOG_STR_LITERAL("max connections") : LOG_STR_LITERAL("no key")); return nullptr; } + auto *conn = this->add_client_(std::move(sock)); // After start(): sends our server hello first so the peer can pick the key conn->mark_outgoing(); return conn; diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index 581c421f1c6..ec6754d90f9 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -272,9 +272,9 @@ class APIServer final : public Component protected: // Accept incoming socket connections. Only called when socket has pending connections. void __attribute__((noinline)) accept_new_connections_(); - // Insert a constructed connection into the client slots and start it. - // Takes ownership; deletes the connection and returns false at the limit - bool add_client_(APIConnection *conn); + /// Takes the socket into a new connection and starts it; callers must have + /// checked at_client_limit_() first + APIConnection *add_client_(std::unique_ptr sock); bool at_client_limit_() const { return this->api_connection_count_ >= MAX_API_CONNECTIONS; } #ifdef USE_API_OUTGOING_CONNECTION // Returns the new connection, or nullptr (socket dropped) when at the limit @@ -322,8 +322,9 @@ class APIServer final : public Component delete this->socket_; this->socket_ = nullptr; } + /// Log the failure, drop the listen socket, and mark the component failed + /// unless this build can still dial out void socket_failed_(const LogString *msg); - bool create_listen_socket_(); // Pointers and pointer-like types first (4 bytes each) socket::ListenSocket *socket_{nullptr}; #ifdef USE_API_CLIENT_CONNECTED_TRIGGER @@ -376,11 +377,7 @@ class APIServer final : public Component // Connection limits - these defaults will be overridden by config values // from cv.SplitDefault in __init__.py which sets platform-specific defaults. uint8_t listen_backlog_{4}; - // Bit-packed so the two flags share one byte - bool shutting_down_ : 1 = false; - // For the reboot log: whether any removal since the last watchdog refresh - // was an unauthenticated session (e.g. a wrong-key peer) - bool saw_unauthenticated_client_ : 1 = false; + bool shutting_down_ = false; uint8_t api_connection_count_{0}; #ifdef USE_API_OUTGOING_CONNECTION // Connected clients whose hello declared them a dial-back target diff --git a/tests/component_tests/api/test_outgoing_connection.py b/tests/component_tests/api/test_outgoing_connection.py index 8a6789414e2..d77bdf96019 100644 --- a/tests/component_tests/api/test_outgoing_connection.py +++ b/tests/component_tests/api/test_outgoing_connection.py @@ -5,17 +5,11 @@ from pathlib import Path import pytest -from esphome.components import socket -from esphome.components.api import ( - CONFIG_SCHEMA, - FINAL_VALIDATE_SCHEMA, - _validate_outgoing_host_ipv6, -) +from esphome.components.api import CONFIG_SCHEMA, FINAL_VALIDATE_SCHEMA from esphome.components.esp32 import KEY_BOARD, KEY_VARIANT, VARIANT_ESP32 import esphome.config_validation as cv from esphome.const import PlatformFramework from esphome.core import CORE -import esphome.final_validate as fv from esphome.types import ConfigType from tests.component_tests.types import SetCoreConfigCallable @@ -43,16 +37,6 @@ def test_outgoing_connection_generates_defines( assert str(defines["API_OUTGOING_CONNECTION_DELAY"]) == "60000" -def test_outgoing_connection_defaults( - set_core_config: SetCoreConfigCallable, -) -> None: - set_core_config(PlatformFramework.ESP32_IDF, platform_data=ESP32_PLATFORM_DATA) - config = CONFIG_SCHEMA(_api_config({"host": "192.168.1.2"})) - outgoing = config["outgoing_connection"] - assert outgoing["port"] == 6054 - assert outgoing["delay"].total_milliseconds == 60000 - - def test_outgoing_connection_bare_block( set_core_config: SetCoreConfigCallable, ) -> None: @@ -92,7 +76,6 @@ def test_outgoing_connection_accepts_raw_lwip( ) -> None: """The raw lwip_tcp socket these platforms default to can dial out.""" set_core_config(platform_framework) - fv.full_config.set({"socket": socket.CONFIG_SCHEMA({})}) config = CONFIG_SCHEMA(_api_config({"host": "192.168.1.2"})) assert FINAL_VALIDATE_SCHEMA(config) is not None @@ -111,7 +94,7 @@ def test_outgoing_connection_ipv6_host_requires_ipv6( set_core_config(PlatformFramework.ESP32_IDF, platform_data=ESP32_PLATFORM_DATA) config = CONFIG_SCHEMA(_api_config({"host": "fd00::1"})) with pytest.raises(cv.Invalid, match="IPv6 is not"): - _validate_outgoing_host_ipv6(config) + FINAL_VALIDATE_SCHEMA(config) def test_outgoing_connection_ipv6_host_passes_with_ipv6_enabled( @@ -123,13 +106,4 @@ def test_outgoing_connection_ipv6_host_passes_with_ipv6_enabled( full_config={"network": {"enable_ipv6": True}}, ) config = CONFIG_SCHEMA(_api_config({"host": "fd00::1"})) - assert _validate_outgoing_host_ipv6(config) is config - - -def test_outgoing_connection_ipv6_host_with_ipv6( - generate_main: Callable[[str | Path], str], -) -> None: - generate_main("tests/component_tests/api/test_outgoing_connection_ipv6.yaml") - - defines = {define.name: define.value for define in CORE.defines} - assert str(defines["API_OUTGOING_CONNECTION_HOST"]) == '"fd00::1"' + assert FINAL_VALIDATE_SCHEMA(config) is not None diff --git a/tests/component_tests/api/test_outgoing_connection_ipv6.yaml b/tests/component_tests/api/test_outgoing_connection_ipv6.yaml deleted file mode 100644 index 4a1027a15cd..00000000000 --- a/tests/component_tests/api/test_outgoing_connection_ipv6.yaml +++ /dev/null @@ -1,20 +0,0 @@ -esphome: - name: test - -esp32: - board: esp32dev - -wifi: - ssid: SomeNetwork - password: SomePassword - -network: - enable_ipv6: true - -logger: - -api: - encryption: - key: bOFFzzvfpg5DB94DuBGLXD/hMnhpDKgP9UQyBulwWVU= - outgoing_connection: - host: fd00::1 diff --git a/tests/components/api/test-outgoing-connection.bk72xx-ard.yaml b/tests/components/api/test-outgoing-connection.bk72xx-ard.yaml index 6de64e58f03..97648ec6d28 100644 --- a/tests/components/api/test-outgoing-connection.bk72xx-ard.yaml +++ b/tests/components/api/test-outgoing-connection.bk72xx-ard.yaml @@ -1,5 +1,5 @@ packages: - common: !include common-base.yaml + common: !include common.yaml wifi: ssid: MySSID @@ -7,7 +7,5 @@ wifi: # Outgoing connection on the lwip_sockets implementation used by LibreTiny api: - encryption: - key: bOFFzzvfpg5DB94DuBGLXD/hMnhpDKgP9UQyBulwWVU= outgoing_connection: host: 192.168.1.2 diff --git a/tests/components/api/test-outgoing-connection.esp32-idf.yaml b/tests/components/api/test-outgoing-connection.esp32-idf.yaml index 0796faf6ab2..04013a89867 100644 --- a/tests/components/api/test-outgoing-connection.esp32-idf.yaml +++ b/tests/components/api/test-outgoing-connection.esp32-idf.yaml @@ -1,5 +1,5 @@ packages: - common: !include common-base.yaml + common: !include common.yaml wifi: ssid: MySSID @@ -8,8 +8,6 @@ wifi: # Outgoing connection: the device dials out when no dial-back client is # connected. Requires encryption so the peer is verified by key. api: - encryption: - key: bOFFzzvfpg5DB94DuBGLXD/hMnhpDKgP9UQyBulwWVU= outgoing_connection: host: 192.168.1.2 port: 6054 diff --git a/tests/components/api/test-outgoing-connection.esp8266-ard.yaml b/tests/components/api/test-outgoing-connection.esp8266-ard.yaml index 74f1c23dc73..0cf4b0b4b4f 100644 --- a/tests/components/api/test-outgoing-connection.esp8266-ard.yaml +++ b/tests/components/api/test-outgoing-connection.esp8266-ard.yaml @@ -1,5 +1,5 @@ packages: - common: !include common-base.yaml + common: !include common.yaml wifi: ssid: MySSID @@ -7,7 +7,5 @@ wifi: # Outgoing connection on the raw lwip_tcp implementation used by ESP8266 and RP2040 api: - encryption: - key: bOFFzzvfpg5DB94DuBGLXD/hMnhpDKgP9UQyBulwWVU= outgoing_connection: host: 192.168.1.2 diff --git a/tests/components/api/test-outgoing-connection.host.yaml b/tests/components/api/test-outgoing-connection.host.yaml index 0f2e0d62762..deb45b99f9d 100644 --- a/tests/components/api/test-outgoing-connection.host.yaml +++ b/tests/components/api/test-outgoing-connection.host.yaml @@ -1,11 +1,9 @@ packages: - common: !include common-base.yaml + common: !include common.yaml network: # No host set: the device dials the last remembered Home Assistant address api: - encryption: - key: bOFFzzvfpg5DB94DuBGLXD/hMnhpDKgP9UQyBulwWVU= outgoing_connection: delay: 30s diff --git a/tests/components/api/test-outgoing-connection.rp2040-ard.yaml b/tests/components/api/test-outgoing-connection.rp2040-ard.yaml index 74f1c23dc73..0cf4b0b4b4f 100644 --- a/tests/components/api/test-outgoing-connection.rp2040-ard.yaml +++ b/tests/components/api/test-outgoing-connection.rp2040-ard.yaml @@ -1,5 +1,5 @@ packages: - common: !include common-base.yaml + common: !include common.yaml wifi: ssid: MySSID @@ -7,7 +7,5 @@ wifi: # Outgoing connection on the raw lwip_tcp implementation used by ESP8266 and RP2040 api: - encryption: - key: bOFFzzvfpg5DB94DuBGLXD/hMnhpDKgP9UQyBulwWVU= outgoing_connection: host: 192.168.1.2 diff --git a/tests/integration/fixtures/api_reboot_timeout_after_authenticated_disconnect.yaml b/tests/integration/fixtures/api_reboot_timeout_after_authenticated_disconnect.yaml deleted file mode 100644 index e3490bbc857..00000000000 --- a/tests/integration/fixtures/api_reboot_timeout_after_authenticated_disconnect.yaml +++ /dev/null @@ -1,7 +0,0 @@ -esphome: - name: api-reboot-test -host: -api: - reboot_timeout: 2s # Headroom to connect and authenticate a client first -logger: - level: DEBUG diff --git a/tests/integration/test_api_reboot_timeout.py b/tests/integration/test_api_reboot_timeout.py index 96654308b39..9cada0a296e 100644 --- a/tests/integration/test_api_reboot_timeout.py +++ b/tests/integration/test_api_reboot_timeout.py @@ -5,7 +5,7 @@ import re import pytest -from .types import APIClientConnectedFactory, RunCompiledFunction +from .types import RunCompiledFunction @pytest.mark.asyncio @@ -16,9 +16,7 @@ async def test_api_reboot_timeout( """Test that the device reboots when no API clients connect within the timeout.""" loop = asyncio.get_running_loop() reboot_future = loop.create_future() - # The harness port probe always connects without authenticating, so the - # reboot deterministically reports the unauthenticated form - reboot_pattern = re.compile(r"none authenticated; rebooting") + reboot_pattern = re.compile(r"No clients; rebooting") def check_output(line: str) -> None: """Check output for reboot message.""" @@ -35,30 +33,3 @@ async def test_api_reboot_timeout( pytest.fail("Device did not reboot within expected timeout") # Test passes if we get here - reboot was detected - - -@pytest.mark.asyncio -async def test_api_reboot_timeout_after_authenticated_disconnect( - yaml_config: str, - run_compiled: RunCompiledFunction, - api_client_connected: APIClientConnectedFactory, -) -> None: - """An authenticated disconnect resets the flag; the clean branch reboots.""" - loop = asyncio.get_running_loop() - reboot_future = loop.create_future() - reboot_pattern = re.compile(r"No clients; rebooting") - - def check_output(line: str) -> None: - """Check output for reboot message.""" - if not reboot_future.done() and reboot_pattern.search(line): - reboot_future.set_result(True) - - async with run_compiled(yaml_config, line_callback=check_output): - # An authenticated session refreshes the watchdog and clears the - # unauthenticated flag the harness probe set - async with api_client_connected() as client: - await client.device_info() - try: - await asyncio.wait_for(reboot_future, timeout=5.0) - except TimeoutError: - pytest.fail("Device did not reboot within expected timeout")