From 8d62a6a88a4dc016c836794c6d3e66376feef9cd Mon Sep 17 00:00:00 2001 From: schrob <83939986+schdro@users.noreply.github.com> Date: Wed, 11 Feb 2026 18:54:31 +0100 Subject: [PATCH 1/2] [openthread] Fix warning on old C89 implicit field zero init (#13935) --- esphome/components/openthread/openthread_esp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/openthread/openthread_esp.cpp b/esphome/components/openthread/openthread_esp.cpp index a9aff3cce4..79cd809809 100644 --- a/esphome/components/openthread/openthread_esp.cpp +++ b/esphome/components/openthread/openthread_esp.cpp @@ -104,7 +104,7 @@ void OpenThreadComponent::ot_main() { esp_cli_custom_command_init(); #endif // CONFIG_OPENTHREAD_CLI_ESP_EXTENSION - otLinkModeConfig link_mode_config = {0}; + otLinkModeConfig link_mode_config{}; #if CONFIG_OPENTHREAD_FTD link_mode_config.mRxOnWhenIdle = true; link_mode_config.mDeviceType = true; From c9c125aa8d5b30295e7fa93a08070077440b929d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 11 Feb 2026 11:54:58 -0600 Subject: [PATCH 2/2] [socket] Devirtualize Socket::ready() and implement working ready() for LWIP raw TCP (#13913) Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> --- .../components/socket/bsd_sockets_impl.cpp | 29 ++----------------- .../components/socket/lwip_raw_tcp_impl.cpp | 4 +++ .../components/socket/lwip_sockets_impl.cpp | 29 ++----------------- esphome/components/socket/socket.cpp | 4 +++ esphome/components/socket/socket.h | 24 ++++++++++++--- esphome/core/application.cpp | 9 ------ esphome/core/application.h | 16 +++++++++- 7 files changed, 47 insertions(+), 68 deletions(-) diff --git a/esphome/components/socket/bsd_sockets_impl.cpp b/esphome/components/socket/bsd_sockets_impl.cpp index b670b9c068..c96713f376 100644 --- a/esphome/components/socket/bsd_sockets_impl.cpp +++ b/esphome/components/socket/bsd_sockets_impl.cpp @@ -16,19 +16,13 @@ namespace esphome::socket { class BSDSocketImpl final : public Socket { public: - BSDSocketImpl(int fd, bool monitor_loop = false) : fd_(fd) { -#ifdef USE_SOCKET_SELECT_SUPPORT + BSDSocketImpl(int fd, bool monitor_loop = false) { + this->fd_ = fd; // Register new socket with the application for select() if monitoring requested if (monitor_loop && this->fd_ >= 0) { // Only set loop_monitored_ to true if registration succeeds this->loop_monitored_ = App.register_socket_fd(this->fd_); - } else { - this->loop_monitored_ = false; } -#else - // Without select support, ignore monitor_loop parameter - (void) monitor_loop; -#endif } ~BSDSocketImpl() override { if (!this->closed_) { @@ -52,12 +46,10 @@ class BSDSocketImpl final : public Socket { int bind(const struct sockaddr *addr, socklen_t addrlen) override { return ::bind(this->fd_, addr, addrlen); } int close() override { if (!this->closed_) { -#ifdef USE_SOCKET_SELECT_SUPPORT // Unregister from select() before closing if monitored if (this->loop_monitored_) { App.unregister_socket_fd(this->fd_); } -#endif int ret = ::close(this->fd_); this->closed_ = true; return ret; @@ -130,23 +122,6 @@ class BSDSocketImpl final : public Socket { ::fcntl(this->fd_, F_SETFL, fl); return 0; } - - int get_fd() const override { return this->fd_; } - -#ifdef USE_SOCKET_SELECT_SUPPORT - bool ready() const override { - if (!this->loop_monitored_) - return true; - return App.is_socket_ready(this->fd_); - } -#endif - - protected: - int fd_; - bool closed_{false}; -#ifdef USE_SOCKET_SELECT_SUPPORT - bool loop_monitored_{false}; -#endif }; // Helper to create a socket with optional monitoring diff --git a/esphome/components/socket/lwip_raw_tcp_impl.cpp b/esphome/components/socket/lwip_raw_tcp_impl.cpp index a9c2eda4e8..aa37386d70 100644 --- a/esphome/components/socket/lwip_raw_tcp_impl.cpp +++ b/esphome/components/socket/lwip_raw_tcp_impl.cpp @@ -452,6 +452,8 @@ class LWIPRawImpl : public Socket { errno = ENOSYS; return -1; } + bool ready() const override { return this->rx_buf_ != nullptr || this->rx_closed_ || this->pcb_ == nullptr; } + int setblocking(bool blocking) final { if (pcb_ == nullptr) { errno = ECONNRESET; @@ -576,6 +578,8 @@ class LWIPRawListenImpl final : public LWIPRawImpl { tcp_err(pcb_, LWIPRawImpl::s_err_fn); // Use base class error handler } + bool ready() const override { return this->accepted_socket_count_ > 0; } + std::unique_ptr accept(struct sockaddr *addr, socklen_t *addrlen) override { if (pcb_ == nullptr) { errno = EBADF; diff --git a/esphome/components/socket/lwip_sockets_impl.cpp b/esphome/components/socket/lwip_sockets_impl.cpp index a885f243f3..79d68e085a 100644 --- a/esphome/components/socket/lwip_sockets_impl.cpp +++ b/esphome/components/socket/lwip_sockets_impl.cpp @@ -11,19 +11,13 @@ namespace esphome::socket { class LwIPSocketImpl final : public Socket { public: - LwIPSocketImpl(int fd, bool monitor_loop = false) : fd_(fd) { -#ifdef USE_SOCKET_SELECT_SUPPORT + LwIPSocketImpl(int fd, bool monitor_loop = false) { + this->fd_ = fd; // Register new socket with the application for select() if monitoring requested if (monitor_loop && this->fd_ >= 0) { // Only set loop_monitored_ to true if registration succeeds this->loop_monitored_ = App.register_socket_fd(this->fd_); - } else { - this->loop_monitored_ = false; } -#else - // Without select support, ignore monitor_loop parameter - (void) monitor_loop; -#endif } ~LwIPSocketImpl() override { if (!this->closed_) { @@ -49,12 +43,10 @@ class LwIPSocketImpl final : public Socket { int bind(const struct sockaddr *addr, socklen_t addrlen) override { return lwip_bind(this->fd_, addr, addrlen); } int close() override { if (!this->closed_) { -#ifdef USE_SOCKET_SELECT_SUPPORT // Unregister from select() before closing if monitored if (this->loop_monitored_) { App.unregister_socket_fd(this->fd_); } -#endif int ret = lwip_close(this->fd_); this->closed_ = true; return ret; @@ -97,23 +89,6 @@ class LwIPSocketImpl final : public Socket { lwip_fcntl(this->fd_, F_SETFL, fl); return 0; } - - int get_fd() const override { return this->fd_; } - -#ifdef USE_SOCKET_SELECT_SUPPORT - bool ready() const override { - if (!this->loop_monitored_) - return true; - return App.is_socket_ready(this->fd_); - } -#endif - - protected: - int fd_; - bool closed_{false}; -#ifdef USE_SOCKET_SELECT_SUPPORT - bool loop_monitored_{false}; -#endif }; // Helper to create a socket with optional monitoring diff --git a/esphome/components/socket/socket.cpp b/esphome/components/socket/socket.cpp index fd8725b363..2fcc162ead 100644 --- a/esphome/components/socket/socket.cpp +++ b/esphome/components/socket/socket.cpp @@ -10,6 +10,10 @@ namespace esphome::socket { Socket::~Socket() {} +#ifdef USE_SOCKET_SELECT_SUPPORT +bool Socket::ready() const { return !this->loop_monitored_ || App.is_socket_ready_(this->fd_); } +#endif + // Platform-specific inet_ntop wrappers #if defined(USE_SOCKET_IMPL_LWIP_TCP) // LWIP raw TCP (ESP8266) uses inet_ntoa_r which takes struct by value diff --git a/esphome/components/socket/socket.h b/esphome/components/socket/socket.h index e8b0948acd..c0098d689a 100644 --- a/esphome/components/socket/socket.h +++ b/esphome/components/socket/socket.h @@ -63,13 +63,29 @@ class Socket { virtual int setblocking(bool blocking) = 0; virtual int loop() { return 0; }; - /// Get the underlying file descriptor (returns -1 if not supported) - virtual int get_fd() const { return -1; } + /// Get the underlying file descriptor (returns -1 if not supported) + /// Non-virtual: only one socket implementation is active per build. +#ifdef USE_SOCKET_SELECT_SUPPORT + int get_fd() const { return this->fd_; } +#else + int get_fd() const { return -1; } +#endif /// Check if socket has data ready to read - /// For loop-monitored sockets, checks with the Application's select() results - /// For non-monitored sockets, always returns true (assumes data may be available) + /// For select()-based sockets: non-virtual, checks Application's select() results + /// For LWIP raw TCP sockets: virtual, checks internal buffer state +#ifdef USE_SOCKET_SELECT_SUPPORT + bool ready() const; +#else virtual bool ready() const { return true; } +#endif + + protected: +#ifdef USE_SOCKET_SELECT_SUPPORT + int fd_{-1}; + bool closed_{false}; + bool loop_monitored_{false}; +#endif }; /// Create a socket of the given domain, type and protocol. diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index 7b5435185d..449acc64cf 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -609,15 +609,6 @@ void Application::unregister_socket_fd(int fd) { } } -bool Application::is_socket_ready(int fd) const { - // This function is thread-safe for reading the result of select() - // However, it should only be called after select() has been executed in the main loop - // The read_fds_ is only modified by select() in the main loop - if (fd < 0 || fd >= FD_SETSIZE) - return false; - - return FD_ISSET(fd, &this->read_fds_); -} #endif void Application::yield_with_select_(uint32_t delay_ms) { diff --git a/esphome/core/application.h b/esphome/core/application.h index 8478100a56..30611227a2 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -101,6 +101,10 @@ #include "esphome/components/update/update_entity.h" #endif +namespace esphome::socket { +class Socket; +} // namespace esphome::socket + namespace esphome { // Teardown timeout constant (in milliseconds) @@ -491,7 +495,8 @@ class Application { void unregister_socket_fd(int fd); /// Check if there's data available on a socket without blocking /// This function is thread-safe for reading, but should be called after select() has run - bool is_socket_ready(int fd) const; + /// The read_fds_ is only modified by select() in the main loop + bool is_socket_ready(int fd) const { return fd >= 0 && this->is_socket_ready_(fd); } #ifdef USE_WAKE_LOOP_THREADSAFE /// Wake the main event loop from a FreeRTOS task @@ -503,6 +508,15 @@ class Application { protected: friend Component; + friend class socket::Socket; + +#ifdef USE_SOCKET_SELECT_SUPPORT + /// Fast path for Socket::ready() via friendship - skips negative fd check. + /// Safe because: fd was validated in register_socket_fd() at registration time, + /// and Socket::ready() only calls this when loop_monitored_ is true (registration succeeded). + /// FD_ISSET may include its own upper bounds check depending on platform. + bool is_socket_ready_(int fd) const { return FD_ISSET(fd, &this->read_fds_); } +#endif void register_component_(Component *comp);