From 4cc1e6a9103dc804e9e4516ff1f6fe313701ccf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Mart=C3=ADn?= Date: Thu, 19 Feb 2026 15:23:22 +0100 Subject: [PATCH 1/6] [esp32_ble_server] add test for lambda characteristic (#14091) --- tests/components/esp32_ble_server/common.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/components/esp32_ble_server/common.yaml b/tests/components/esp32_ble_server/common.yaml index e9576a8262c..7fe0b2eb5f1 100644 --- a/tests/components/esp32_ble_server/common.yaml +++ b/tests/components/esp32_ble_server/common.yaml @@ -33,6 +33,10 @@ esp32_ble_server: - uuid: 2a24b789-7a1b-4535-af3e-ee76a35cc42d advertise: false characteristics: + - id: test_lambda_characteristic + uuid: 2a24b789-7a1b-4535-af3e-ee76a35cc12c + read: true + value: !lambda return { 1, 2 }; - id: test_change_characteristic uuid: 2a24b789-7a1b-4535-af3e-ee76a35cc11c read: true From 7b53a9895000219aac5895de48c5bd15715d7aea Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 19 Feb 2026 08:39:44 -0600 Subject: [PATCH 2/6] [socket] Log error when UDP socket requested on LWIP TCP-only platforms (#14089) --- esphome/components/socket/lwip_raw_tcp_impl.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/esphome/components/socket/lwip_raw_tcp_impl.cpp b/esphome/components/socket/lwip_raw_tcp_impl.cpp index aa37386d700..6e95f5bc7af 100644 --- a/esphome/components/socket/lwip_raw_tcp_impl.cpp +++ b/esphome/components/socket/lwip_raw_tcp_impl.cpp @@ -680,6 +680,11 @@ class LWIPRawListenImpl final : public LWIPRawImpl { }; std::unique_ptr socket(int domain, int type, int protocol) { + if (type != SOCK_STREAM) { + ESP_LOGE(TAG, "UDP sockets not supported on this platform, use WiFiUDP"); + errno = EPROTOTYPE; + return nullptr; + } auto *pcb = tcp_new(); if (pcb == nullptr) return nullptr; From 6daca09794d7d48478e4c6751907ae5a4b5aa7b3 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 19 Feb 2026 08:40:08 -0600 Subject: [PATCH 3/6] [logger] Replace LogListener virtual interface with LogCallback struct (#14084) --- esphome/components/api/api_server.cpp | 5 +- esphome/components/api/api_server.h | 6 +- esphome/components/ble_nus/ble_nus.cpp | 5 +- esphome/components/ble_nus/ble_nus.h | 9 +-- esphome/components/logger/logger.h | 66 ++++++++++---------- esphome/components/mqtt/mqtt_client.cpp | 5 +- esphome/components/mqtt/mqtt_client.h | 9 +-- esphome/components/syslog/esphome_syslog.cpp | 7 ++- esphome/components/syslog/esphome_syslog.h | 5 +- esphome/components/web_server/web_server.cpp | 5 +- esphome/components/web_server/web_server.h | 11 +--- 11 files changed, 65 insertions(+), 68 deletions(-) diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 67a117e68fd..1a1d0b229b1 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -92,7 +92,10 @@ void APIServer::setup() { #ifdef USE_LOGGER if (logger::global_logger != nullptr) { - logger::global_logger->add_log_listener(this); + logger::global_logger->add_log_callback( + this, [](void *self, uint8_t level, const char *tag, const char *message, size_t message_len) { + static_cast(self)->on_log(level, tag, message, message_len); + }); } #endif diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index 323acc2efb3..3b9ba0e23b4 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -37,10 +37,6 @@ struct SavedNoisePsk { class APIServer : public Component, public Controller -#ifdef USE_LOGGER - , - public logger::LogListener -#endif #ifdef USE_CAMERA , public camera::CameraListener @@ -56,7 +52,7 @@ class APIServer : public Component, void on_shutdown() override; bool teardown() override; #ifdef USE_LOGGER - void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) override; + void on_log(uint8_t level, const char *tag, const char *message, size_t message_len); #endif #ifdef USE_CAMERA void on_camera_image(const std::shared_ptr &image) override; diff --git a/esphome/components/ble_nus/ble_nus.cpp b/esphome/components/ble_nus/ble_nus.cpp index 0de65b623f6..a10132eb3ef 100644 --- a/esphome/components/ble_nus/ble_nus.cpp +++ b/esphome/components/ble_nus/ble_nus.cpp @@ -87,7 +87,10 @@ void BLENUS::setup() { global_ble_nus = this; #ifdef USE_LOGGER if (logger::global_logger != nullptr && this->expose_log_) { - logger::global_logger->add_log_listener(this); + logger::global_logger->add_log_callback( + this, [](void *self, uint8_t level, const char *tag, const char *message, size_t message_len) { + static_cast(self)->on_log(level, tag, message, message_len); + }); } #endif } diff --git a/esphome/components/ble_nus/ble_nus.h b/esphome/components/ble_nus/ble_nus.h index ef20fc5e5b5..b2b0ee7713a 100644 --- a/esphome/components/ble_nus/ble_nus.h +++ b/esphome/components/ble_nus/ble_nus.h @@ -10,12 +10,7 @@ namespace esphome::ble_nus { -class BLENUS : public Component -#ifdef USE_LOGGER - , - public logger::LogListener -#endif -{ +class BLENUS : public Component { enum TxStatus { TX_DISABLED, TX_ENABLED, @@ -29,7 +24,7 @@ class BLENUS : public Component size_t write_array(const uint8_t *data, size_t len); void set_expose_log(bool expose_log) { this->expose_log_ = expose_log; } #ifdef USE_LOGGER - void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) override; + void on_log(uint8_t level, const char *tag, const char *message, size_t message_len); #endif protected: diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 835542dd8f1..2a7552af925 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -40,26 +40,25 @@ struct device; namespace esphome::logger { -/** Interface for receiving log messages without std::function overhead. +/** Lightweight callback for receiving log messages without virtual dispatch overhead. * - * Components can implement this interface instead of using lambdas with std::function - * to reduce flash usage from std::function type erasure machinery. + * Replaces the former LogListener virtual interface to eliminate per-implementer + * vtable sub-tables and thunk code (~39 bytes saved per class that used LogListener). * * Usage: - * class MyComponent : public Component, public LogListener { - * public: - * void setup() override { - * if (logger::global_logger != nullptr) - * logger::global_logger->add_log_listener(this); - * } - * void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) override { - * // Handle log message - * } - * }; + * // In your component's setup(): + * if (logger::global_logger != nullptr) + * logger::global_logger->add_log_callback( + * this, [](void *self, uint8_t level, const char *tag, const char *message, size_t message_len) { + * static_cast(self)->on_log(level, tag, message, message_len); + * }); */ -class LogListener { - public: - virtual void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) = 0; +struct LogCallback { + void *instance; + void (*fn)(void *, uint8_t, const char *, const char *, size_t); + void invoke(uint8_t level, const char *tag, const char *message, size_t message_len) const { + this->fn(this->instance, level, tag, message, message_len); + } }; #ifdef USE_LOGGER_LEVEL_LISTENERS @@ -187,11 +186,13 @@ class Logger : public Component { inline uint8_t level_for(const char *tag); #ifdef USE_LOG_LISTENERS - /// Register a log listener to receive log messages - void add_log_listener(LogListener *listener) { this->log_listeners_.push_back(listener); } + /// Register a log callback to receive log messages + void add_log_callback(void *instance, void (*fn)(void *, uint8_t, const char *, const char *, size_t)) { + this->log_callbacks_.push_back(LogCallback{instance, fn}); + } #else /// No-op when log listeners are disabled - void add_log_listener(LogListener *listener) {} + void add_log_callback(void *instance, void (*fn)(void *, uint8_t, const char *, const char *, size_t)) {} #endif #ifdef USE_LOGGER_LEVEL_LISTENERS @@ -253,11 +254,11 @@ class Logger : public Component { } #endif - // Helper to notify log listeners + // Helper to notify log callbacks inline void HOT notify_listeners_(uint8_t level, const char *tag, const LogBuffer &buf) { #ifdef USE_LOG_LISTENERS - for (auto *listener : this->log_listeners_) - listener->on_log(level, tag, buf.data, buf.pos); + for (auto &cb : this->log_callbacks_) + cb.invoke(level, tag, buf.data, buf.pos); #endif } @@ -341,8 +342,8 @@ class Logger : public Component { std::map log_levels_{}; #endif #ifdef USE_LOG_LISTENERS - StaticVector - log_listeners_; // Log message listeners (API, MQTT, syslog, etc.) + StaticVector + log_callbacks_; // Log message callbacks (API, MQTT, syslog, etc.) #endif #ifdef USE_LOGGER_LEVEL_LISTENERS std::vector level_listeners_; // Log level change listeners @@ -478,15 +479,16 @@ class Logger : public Component { }; extern Logger *global_logger; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -class LoggerMessageTrigger : public Trigger, public LogListener { +class LoggerMessageTrigger : public Trigger { public: - explicit LoggerMessageTrigger(Logger *parent, uint8_t level) : level_(level) { parent->add_log_listener(this); } - - void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) override { - (void) message_len; - if (level <= this->level_) { - this->trigger(level, tag, message); - } + explicit LoggerMessageTrigger(Logger *parent, uint8_t level) : level_(level) { + parent->add_log_callback(this, + [](void *self, uint8_t level, const char *tag, const char *message, size_t message_len) { + auto *trigger = static_cast(self); + if (level <= trigger->level_) { + trigger->trigger(level, tag, message); + } + }); } protected: diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index 90b423c3867..9905b4677e6 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -64,7 +64,10 @@ void MQTTClientComponent::setup() { }); #ifdef USE_LOGGER if (this->is_log_message_enabled() && logger::global_logger != nullptr) { - logger::global_logger->add_log_listener(this); + logger::global_logger->add_log_callback( + this, [](void *self, uint8_t level, const char *tag, const char *message, size_t message_len) { + static_cast(self)->on_log(level, tag, message, message_len); + }); } #endif diff --git a/esphome/components/mqtt/mqtt_client.h b/esphome/components/mqtt/mqtt_client.h index 38bc0b4da37..21edd53eda5 100644 --- a/esphome/components/mqtt/mqtt_client.h +++ b/esphome/components/mqtt/mqtt_client.h @@ -99,12 +99,7 @@ enum MQTTClientState { class MQTTComponent; -class MQTTClientComponent : public Component -#ifdef USE_LOGGER - , - public logger::LogListener -#endif -{ +class MQTTClientComponent : public Component { public: MQTTClientComponent(); @@ -252,7 +247,7 @@ class MQTTClientComponent : public Component float get_setup_priority() const override; #ifdef USE_LOGGER - void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) override; + void on_log(uint8_t level, const char *tag, const char *message, size_t message_len); #endif void on_message(const std::string &topic, const std::string &payload); diff --git a/esphome/components/syslog/esphome_syslog.cpp b/esphome/components/syslog/esphome_syslog.cpp index 376de54db47..790d08ffa6d 100644 --- a/esphome/components/syslog/esphome_syslog.cpp +++ b/esphome/components/syslog/esphome_syslog.cpp @@ -18,7 +18,12 @@ constexpr int LOG_LEVEL_TO_SYSLOG_SEVERITY[] = { 7 // VERY_VERBOSE }; -void Syslog::setup() { logger::global_logger->add_log_listener(this); } +void Syslog::setup() { + logger::global_logger->add_log_callback( + this, [](void *self, uint8_t level, const char *tag, const char *message, size_t message_len) { + static_cast(self)->on_log(level, tag, message, message_len); + }); +} void Syslog::on_log(uint8_t level, const char *tag, const char *message, size_t message_len) { this->log_(level, tag, message, message_len); diff --git a/esphome/components/syslog/esphome_syslog.h b/esphome/components/syslog/esphome_syslog.h index bde6ab5ed49..be4fa91436f 100644 --- a/esphome/components/syslog/esphome_syslog.h +++ b/esphome/components/syslog/esphome_syslog.h @@ -2,17 +2,16 @@ #include "esphome/core/component.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" -#include "esphome/components/logger/logger.h" #include "esphome/components/udp/udp_component.h" #include "esphome/components/time/real_time_clock.h" #ifdef USE_NETWORK namespace esphome::syslog { -class Syslog : public Component, public Parented, public logger::LogListener { +class Syslog : public Component, public Parented { public: Syslog(int level, time::RealTimeClock *time) : log_level_(level), time_(time) {} void setup() override; - void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) override; + void on_log(uint8_t level, const char *tag, const char *message, size_t message_len); void set_strip(bool strip) { this->strip_ = strip; } void set_facility(int facility) { this->facility_ = facility; } diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index e3d131f58ee..a44a47379e4 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -395,7 +395,10 @@ void WebServer::setup() { #ifdef USE_LOGGER if (logger::global_logger != nullptr && this->expose_log_) { - logger::global_logger->add_log_listener(this); + logger::global_logger->add_log_callback( + this, [](void *self, uint8_t level, const char *tag, const char *message, size_t message_len) { + static_cast(self)->on_log(level, tag, message, message_len); + }); } #endif diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index 026da763ea7..6afe618b599 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -186,14 +186,7 @@ class DeferredUpdateEventSourceList : public std::list Date: Thu, 19 Feb 2026 08:40:23 -0600 Subject: [PATCH 4/6] [core] Devirtualize call_loop() and mark_failed() in Component (#14083) --- esphome/core/component.cpp | 12 +++++------- esphome/core/component.h | 4 ++-- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index ba0f1663b9e..f283a690643 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -207,7 +207,7 @@ bool Component::cancel_retry(uint32_t id) { #pragma GCC diagnostic pop } -void Component::call_loop() { this->loop(); } +void Component::call_loop_() { this->loop(); } void Component::call_setup() { this->setup(); } void Component::call_dump_config() { this->dump_config(); @@ -258,11 +258,11 @@ void Component::call() { case COMPONENT_STATE_SETUP: // State setup: Call first loop and set state to loop this->set_component_state_(COMPONENT_STATE_LOOP); - this->call_loop(); + this->call_loop_(); break; case COMPONENT_STATE_LOOP: // State loop: Call loop - this->call_loop(); + this->call_loop_(); break; case COMPONENT_STATE_FAILED: // State failed: Do nothing @@ -497,16 +497,14 @@ void Component::set_setup_priority(float priority) { bool Component::has_overridden_loop() const { #if defined(USE_HOST) || defined(CLANG_TIDY) - bool loop_overridden = true; - bool call_loop_overridden = true; + return true; #else #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wpmf-conversions" bool loop_overridden = (void *) (this->*(&Component::loop)) != (void *) (&Component::loop); - bool call_loop_overridden = (void *) (this->*(&Component::call_loop)) != (void *) (&Component::call_loop); #pragma GCC diagnostic pop + return loop_overridden; #endif - return loop_overridden || call_loop_overridden; } PollingComponent::PollingComponent(uint32_t update_interval) : update_interval_(update_interval) {} diff --git a/esphome/core/component.h b/esphome/core/component.h index 6f7f77dbc10..d4dad3c9a65 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -165,7 +165,7 @@ class Component { * For example, i2c based components can check if the remote device is responding and otherwise * mark the component as failed. Eventually this will also enable smart status LEDs. */ - virtual void mark_failed(); + void mark_failed(); // Remove before 2026.6.0 ESPDEPRECATED("Use mark_failed(LOG_STR(\"static string literal\")) instead. Do NOT use .c_str() from temporary " @@ -286,7 +286,7 @@ class Component { protected: friend class Application; - virtual void call_loop(); + void call_loop_(); virtual void call_setup(); virtual void call_dump_config(); From 535980b9bd37ebf405a6305e34e980e3f16a5af6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 19 Feb 2026 08:40:41 -0600 Subject: [PATCH 5/6] [cse7761] Use constexpr for compile-time constants (#14081) --- esphome/components/cse7761/cse7761.cpp | 38 +++++++++++++------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/esphome/components/cse7761/cse7761.cpp b/esphome/components/cse7761/cse7761.cpp index 7c5ee833a44..f4966357d4b 100644 --- a/esphome/components/cse7761/cse7761.cpp +++ b/esphome/components/cse7761/cse7761.cpp @@ -15,29 +15,29 @@ static const char *const TAG = "cse7761"; * https://github.com/arendst/Tasmota/blob/development/tasmota/xnrg_19_cse7761.ino \*********************************************************************************************/ -static const int CSE7761_UREF = 42563; // RmsUc -static const int CSE7761_IREF = 52241; // RmsIAC -static const int CSE7761_PREF = 44513; // PowerPAC +static constexpr int CSE7761_UREF = 42563; // RmsUc +static constexpr int CSE7761_IREF = 52241; // RmsIAC +static constexpr int CSE7761_PREF = 44513; // PowerPAC -static const uint8_t CSE7761_REG_SYSCON = 0x00; // (2) System Control Register (0x0A04) -static const uint8_t CSE7761_REG_EMUCON = 0x01; // (2) Metering control register (0x0000) -static const uint8_t CSE7761_REG_EMUCON2 = 0x13; // (2) Metering control register 2 (0x0001) -static const uint8_t CSE7761_REG_PULSE1SEL = 0x1D; // (2) Pin function output select register (0x3210) +static constexpr uint8_t CSE7761_REG_SYSCON = 0x00; // (2) System Control Register (0x0A04) +static constexpr uint8_t CSE7761_REG_EMUCON = 0x01; // (2) Metering control register (0x0000) +static constexpr uint8_t CSE7761_REG_EMUCON2 = 0x13; // (2) Metering control register 2 (0x0001) +static constexpr uint8_t CSE7761_REG_PULSE1SEL = 0x1D; // (2) Pin function output select register (0x3210) -static const uint8_t CSE7761_REG_RMSIA = 0x24; // (3) The effective value of channel A current (0x000000) -static const uint8_t CSE7761_REG_RMSIB = 0x25; // (3) The effective value of channel B current (0x000000) -static const uint8_t CSE7761_REG_RMSU = 0x26; // (3) Voltage RMS (0x000000) -static const uint8_t CSE7761_REG_POWERPA = 0x2C; // (4) Channel A active power, update rate 27.2Hz (0x00000000) -static const uint8_t CSE7761_REG_POWERPB = 0x2D; // (4) Channel B active power, update rate 27.2Hz (0x00000000) -static const uint8_t CSE7761_REG_SYSSTATUS = 0x43; // (1) System status register +static constexpr uint8_t CSE7761_REG_RMSIA = 0x24; // (3) The effective value of channel A current (0x000000) +static constexpr uint8_t CSE7761_REG_RMSIB = 0x25; // (3) The effective value of channel B current (0x000000) +static constexpr uint8_t CSE7761_REG_RMSU = 0x26; // (3) Voltage RMS (0x000000) +static constexpr uint8_t CSE7761_REG_POWERPA = 0x2C; // (4) Channel A active power, update rate 27.2Hz (0x00000000) +static constexpr uint8_t CSE7761_REG_POWERPB = 0x2D; // (4) Channel B active power, update rate 27.2Hz (0x00000000) +static constexpr uint8_t CSE7761_REG_SYSSTATUS = 0x43; // (1) System status register -static const uint8_t CSE7761_REG_COEFFCHKSUM = 0x6F; // (2) Coefficient checksum -static const uint8_t CSE7761_REG_RMSIAC = 0x70; // (2) Channel A effective current conversion coefficient +static constexpr uint8_t CSE7761_REG_COEFFCHKSUM = 0x6F; // (2) Coefficient checksum +static constexpr uint8_t CSE7761_REG_RMSIAC = 0x70; // (2) Channel A effective current conversion coefficient -static const uint8_t CSE7761_SPECIAL_COMMAND = 0xEA; // Start special command -static const uint8_t CSE7761_CMD_RESET = 0x96; // Reset command, after receiving the command, the chip resets -static const uint8_t CSE7761_CMD_CLOSE_WRITE = 0xDC; // Close write operation -static const uint8_t CSE7761_CMD_ENABLE_WRITE = 0xE5; // Enable write operation +static constexpr uint8_t CSE7761_SPECIAL_COMMAND = 0xEA; // Start special command +static constexpr uint8_t CSE7761_CMD_RESET = 0x96; // Reset command, after receiving the command, the chip resets +static constexpr uint8_t CSE7761_CMD_CLOSE_WRITE = 0xDC; // Close write operation +static constexpr uint8_t CSE7761_CMD_ENABLE_WRITE = 0xE5; // Enable write operation enum CSE7761 { RMS_IAC, RMS_IBC, RMS_UC, POWER_PAC, POWER_PBC, POWER_SC, ENERGY_AC, ENERGY_BC }; From 01a46f665f550085022bed7d994e043045f5553a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Feb 2026 09:42:22 -0500 Subject: [PATCH 6/6] Bump esptool from 5.1.0 to 5.2.0 (#14058) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 2e6268284a0..be3445dceb0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ tzlocal==5.3.1 # from time tzdata>=2021.1 # from time pyserial==3.5 platformio==6.1.19 -esptool==5.1.0 +esptool==5.2.0 click==8.1.7 esphome-dashboard==20260210.0 aioesphomeapi==44.0.0