From 69af4cddb5f6d1b315d76eb75444174cc819799d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 1 Nov 2025 14:58:24 -0500 Subject: [PATCH] fix ble latency --- esphome/components/esp32_ble/ble.cpp | 17 ++++++++++++++--- esphome/components/esp32_ble/ble.h | 15 ++++++++------- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/esphome/components/esp32_ble/ble.cpp b/esphome/components/esp32_ble/ble.cpp index cadb2cbc2a..7298dc9621 100644 --- a/esphome/components/esp32_ble/ble.cpp +++ b/esphome/components/esp32_ble/ble.cpp @@ -679,15 +679,26 @@ void ESP32BLE::setup_event_notification_() { return; } - // Get the assigned port for sendto() - socklen_t len = sizeof(this->notify_addr_); - if (lwip_getsockname(this->notify_fd_, (struct sockaddr *) &this->notify_addr_, &len) < 0) { + // Get the assigned address and connect to it + // Connecting a UDP socket allows using send() instead of sendto() for better performance + struct sockaddr_in notify_addr; + socklen_t len = sizeof(notify_addr); + if (lwip_getsockname(this->notify_fd_, (struct sockaddr *) ¬ify_addr, &len) < 0) { ESP_LOGW(TAG, "Event socket address failed: %d", errno); lwip_close(this->notify_fd_); this->notify_fd_ = -1; return; } + // Connect to self (loopback) - allows using send() instead of sendto() + // After connect(), no need to store notify_addr - the socket remembers it + if (lwip_connect(this->notify_fd_, (struct sockaddr *) ¬ify_addr, sizeof(notify_addr)) < 0) { + ESP_LOGW(TAG, "Event socket connect failed: %d", errno); + lwip_close(this->notify_fd_); + this->notify_fd_ = -1; + return; + } + // Set non-blocking mode int flags = lwip_fcntl(this->notify_fd_, F_GETFL, 0); lwip_fcntl(this->notify_fd_, F_SETFL, flags | O_NONBLOCK); diff --git a/esphome/components/esp32_ble/ble.h b/esphome/components/esp32_ble/ble.h index b62109bff5..93c03063c6 100644 --- a/esphome/components/esp32_ble/ble.h +++ b/esphome/components/esp32_ble/ble.h @@ -209,9 +209,9 @@ class ESP32BLE : public Component { #ifdef USE_SOCKET_SELECT_SUPPORT // Event notification socket for waking up main loop from BLE thread - // Uses UDP loopback to wake lwip_select() with ~12μs latency vs 0-16ms timeout - struct sockaddr_in notify_addr_ {}; // 16 bytes (sockaddr_in structure) - int notify_fd_{-1}; // 4 bytes (file descriptor) + // Uses connected UDP loopback socket to wake lwip_select() with ~12μs latency vs 0-16ms timeout + // Socket is connected during setup, allowing use of send() instead of sendto() for efficiency + int notify_fd_{-1}; // 4 bytes (file descriptor) #endif // 2-byte aligned members @@ -235,12 +235,13 @@ static constexpr size_t BLE_EVENT_NOTIFY_DRAIN_BUFFER_SIZE = 16; inline void ESP32BLE::notify_main_loop_() { // Called from BLE thread context when events are queued - // Wakes up lwip_select() in main loop by writing to loopback socket + // Wakes up lwip_select() in main loop by writing to connected loopback socket if (this->notify_fd_ >= 0) { const char dummy = 1; - // Non-blocking sendto - if it fails (unlikely), select() will wake on timeout anyway - // This is safe to call from BLE thread - sendto() is thread-safe in lwip - lwip_sendto(this->notify_fd_, &dummy, 1, 0, (struct sockaddr *) &this->notify_addr_, sizeof(this->notify_addr_)); + // Non-blocking send - if it fails (unlikely), select() will wake on timeout anyway + // This is safe to call from BLE thread - send() is thread-safe in lwip + // Socket is already connected to loopback address, so send() is faster than sendto() + lwip_send(this->notify_fd_, &dummy, 1, 0); } }