From 7d5342bca5df37ba48e735342f72f3d615a01555 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 22 Dec 2025 16:45:22 -1000 Subject: [PATCH 01/13] [logger] Host: Use fwrite() with explicit length and remove platform branching (#12628) --- esphome/components/logger/logger.cpp | 4 +-- esphome/components/logger/logger.h | 41 ++++++++--------------- esphome/components/logger/logger_host.cpp | 23 ++++++++----- 3 files changed, 31 insertions(+), 37 deletions(-) diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index 21e2b448082..474eb9ec38e 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -65,8 +65,8 @@ void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const ch uint16_t buffer_at = 0; // Initialize buffer position this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, console_buffer, &buffer_at, MAX_CONSOLE_LOG_MSG_SIZE); - // Add newline if platform needs it (ESP32 doesn't add via write_msg_) - this->add_newline_to_buffer_if_needed_(console_buffer, &buffer_at, MAX_CONSOLE_LOG_MSG_SIZE); + // Add newline before writing to console + this->add_newline_to_buffer_(console_buffer, &buffer_at, MAX_CONSOLE_LOG_MSG_SIZE); this->write_msg_(console_buffer, buffer_at); } diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 2cedd7a76ff..ba8d4667b62 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -117,17 +117,6 @@ static constexpr uint16_t MAX_HEADER_SIZE = 128; // "0x" + 2 hex digits per byte + '\0' static constexpr size_t MAX_POINTER_REPRESENTATION = 2 + sizeof(void *) * 2 + 1; -// Platform-specific: does write_msg_ add its own newline? -// false: Caller must add newline to buffer before calling write_msg_ (ESP32, ESP8266, RP2040, LibreTiny, Zephyr) -// Allows single write call with newline included for efficiency -// true: write_msg_ adds newline itself via puts()/println() (other platforms) -// Newline should NOT be added to buffer -#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) -static constexpr bool WRITE_MSG_ADDS_NEWLINE = false; -#else -static constexpr bool WRITE_MSG_ADDS_NEWLINE = true; -#endif - #if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) /** Enum for logging UART selection * @@ -259,22 +248,20 @@ class Logger : public Component { } } - // Helper to add newline to buffer for platforms that need it + // Helper to add newline to buffer before writing to console // Modifies buffer_at to include the newline - inline void HOT add_newline_to_buffer_if_needed_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size) { - if constexpr (!WRITE_MSG_ADDS_NEWLINE) { - // Add newline - don't need to maintain null termination - // write_msg_ now always receives explicit length, so we can safely overwrite the null terminator - // This is safe because: - // 1. Callbacks already received the message (before we add newline) - // 2. write_msg_ receives the length explicitly (doesn't need null terminator) - if (*buffer_at < buffer_size) { - buffer[(*buffer_at)++] = '\n'; - } else if (buffer_size > 0) { - // Buffer was full - replace last char with newline to ensure it's visible - buffer[buffer_size - 1] = '\n'; - *buffer_at = buffer_size; - } + inline void HOT add_newline_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size) { + // Add newline - don't need to maintain null termination + // write_msg_ receives explicit length, so we can safely overwrite the null terminator + // This is safe because: + // 1. Callbacks already received the message (before we add newline) + // 2. write_msg_ receives the length explicitly (doesn't need null terminator) + if (*buffer_at < buffer_size) { + buffer[(*buffer_at)++] = '\n'; + } else if (buffer_size > 0) { + // Buffer was full - replace last char with newline to ensure it's visible + buffer[buffer_size - 1] = '\n'; + *buffer_at = buffer_size; } } @@ -283,7 +270,7 @@ class Logger : public Component { inline void HOT write_tx_buffer_to_console_(uint16_t offset = 0, uint16_t *length = nullptr) { if (this->baud_rate_ > 0) { uint16_t *len_ptr = length ? length : &this->tx_buffer_at_; - this->add_newline_to_buffer_if_needed_(this->tx_buffer_ + offset, len_ptr, this->tx_buffer_size_ - offset); + this->add_newline_to_buffer_(this->tx_buffer_ + offset, len_ptr, this->tx_buffer_size_ - offset); this->write_msg_(this->tx_buffer_ + offset, *len_ptr); } } diff --git a/esphome/components/logger/logger_host.cpp b/esphome/components/logger/logger_host.cpp index c5e1e6f8650..cbca06e431c 100644 --- a/esphome/components/logger/logger_host.cpp +++ b/esphome/components/logger/logger_host.cpp @@ -3,16 +3,23 @@ namespace esphome::logger { -void HOT Logger::write_msg_(const char *msg, size_t) { - time_t rawtime; - struct tm *timeinfo; - char buffer[80]; +void HOT Logger::write_msg_(const char *msg, size_t len) { + static constexpr size_t TIMESTAMP_LEN = 10; // "[HH:MM:SS]" + // tx_buffer_size_ defaults to 512, so 768 covers default + headroom + char buffer[TIMESTAMP_LEN + 768]; + time_t rawtime; time(&rawtime); - timeinfo = localtime(&rawtime); - strftime(buffer, sizeof buffer, "[%H:%M:%S]", timeinfo); - fputs(buffer, stdout); - puts(msg); + struct tm *timeinfo = localtime(&rawtime); + size_t pos = strftime(buffer, TIMESTAMP_LEN + 1, "[%H:%M:%S]", timeinfo); + + // Copy message (with newline already included by caller) + size_t copy_len = std::min(len, sizeof(buffer) - pos); + memcpy(buffer + pos, msg, copy_len); + pos += copy_len; + + // Single write for everything + fwrite(buffer, 1, pos, stdout); } void Logger::pre_setup() { global_logger = this; } From ffefa8929ede21b22c1c37d8415074699778a34c Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Tue, 23 Dec 2025 09:05:48 -0500 Subject: [PATCH 02/13] [cc1101] Fix packet mode RSSI/LQI (#12630) Co-authored-by: Claude --- esphome/components/cc1101/cc1101.cpp | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/esphome/components/cc1101/cc1101.cpp b/esphome/components/cc1101/cc1101.cpp index f98afd94a19..7e5309e1659 100644 --- a/esphome/components/cc1101/cc1101.cpp +++ b/esphome/components/cc1101/cc1101.cpp @@ -169,14 +169,16 @@ void CC1101Component::loop() { } // Read packet - uint8_t payload_length; + uint8_t payload_length, expected_rx; if (this->state_.LENGTH_CONFIG == static_cast(LengthConfig::LENGTH_CONFIG_VARIABLE)) { this->read_(Register::FIFO, &payload_length, 1); + expected_rx = payload_length + 1; } else { payload_length = this->state_.PKTLEN; + expected_rx = payload_length; } - if (payload_length == 0 || payload_length > 64) { - ESP_LOGW(TAG, "Invalid payload length: %u", payload_length); + if (payload_length == 0 || payload_length > 64 || rx_bytes != expected_rx) { + ESP_LOGW(TAG, "Invalid packet: rx_bytes %u, payload_length %u", rx_bytes, payload_length); this->enter_idle_(); this->strobe_(Command::FRX); this->strobe_(Command::RX); @@ -186,13 +188,12 @@ void CC1101Component::loop() { this->packet_.resize(payload_length); this->read_(Register::FIFO, this->packet_.data(), payload_length); - // Read status and trigger - uint8_t status[2]; - this->read_(Register::FIFO, status, 2); - int8_t rssi_raw = static_cast(status[0]); - float rssi = (rssi_raw * RSSI_STEP) - RSSI_OFFSET; - bool crc_ok = (status[1] & STATUS_CRC_OK_MASK) != 0; - uint8_t lqi = status[1] & STATUS_LQI_MASK; + // Read status from registers (more reliable than FIFO status bytes due to timing issues) + this->read_(Register::RSSI); + this->read_(Register::LQI); + float rssi = (this->state_.RSSI * RSSI_STEP) - RSSI_OFFSET; + bool crc_ok = (this->state_.LQI & STATUS_CRC_OK_MASK) != 0; + uint8_t lqi = this->state_.LQI & STATUS_LQI_MASK; if (this->state_.CRC_EN == 0 || crc_ok) { this->packet_trigger_->trigger(this->packet_, rssi, lqi); } @@ -616,12 +617,15 @@ void CC1101Component::set_packet_mode(bool value) { this->state_.GDO0_CFG = 0x01; // Set max RX FIFO threshold to ensure we only trigger on end-of-packet this->state_.FIFO_THR = 15; + // Don't append status bytes to FIFO - we read from registers instead + this->state_.APPEND_STATUS = 0; } else { // Configure GDO0 for serial data (async serial mode) this->state_.GDO0_CFG = 0x0D; } if (this->initialized_) { this->write_(Register::PKTCTRL0); + this->write_(Register::PKTCTRL1); this->write_(Register::IOCFG0); this->write_(Register::FIFOTHR); } From dc943d7e7a5cf2cb7be4081d69fe605ffe0e63ca Mon Sep 17 00:00:00 2001 From: Thomas Rupprecht Date: Wed, 17 Dec 2025 04:52:28 +0100 Subject: [PATCH 03/13] [pca9685,sx126x,sx127x] Use frequency/float_range check (#12490) Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> --- esphome/components/ade7880/sensor.py | 2 +- esphome/components/cc1101/__init__.py | 2 +- esphome/components/esp32_camera/__init__.py | 2 +- esphome/components/esp8266_pwm/output.py | 2 +- esphome/components/i2c/__init__.py | 2 +- esphome/components/ledc/output.py | 4 +++- esphome/components/libretiny_pwm/output.py | 4 +++- esphome/components/pca9685/__init__.py | 2 +- esphome/components/rp2040_pwm/output.py | 2 +- esphome/components/sx126x/__init__.py | 8 ++++++-- esphome/components/sx127x/__init__.py | 8 ++++++-- 11 files changed, 25 insertions(+), 13 deletions(-) diff --git a/esphome/components/ade7880/sensor.py b/esphome/components/ade7880/sensor.py index 39dbeb225f4..beb74d73101 100644 --- a/esphome/components/ade7880/sensor.py +++ b/esphome/components/ade7880/sensor.py @@ -227,7 +227,7 @@ CONFIG_SCHEMA = cv.All( { cv.GenerateID(): cv.declare_id(ADE7880), cv.Optional(CONF_FREQUENCY, default="50Hz"): cv.All( - cv.frequency, cv.Range(min=45.0, max=66.0) + cv.frequency, cv.float_range(min=45.0, max=66.0) ), cv.Optional(CONF_IRQ0_PIN): pins.internal_gpio_input_pin_schema, cv.Required(CONF_IRQ1_PIN): pins.internal_gpio_input_pin_schema, diff --git a/esphome/components/cc1101/__init__.py b/esphome/components/cc1101/__init__.py index 1971817fb1d..e314da70793 100644 --- a/esphome/components/cc1101/__init__.py +++ b/esphome/components/cc1101/__init__.py @@ -165,7 +165,7 @@ CONFIG_MAP = { CONF_OUTPUT_POWER: cv.float_range(min=-30.0, max=11.0), CONF_RX_ATTENUATION: cv.enum(RX_ATTENUATION, upper=False), CONF_DC_BLOCKING_FILTER: cv.boolean, - CONF_FREQUENCY: cv.All(cv.frequency, cv.float_range(min=300000000, max=928000000)), + CONF_FREQUENCY: cv.All(cv.frequency, cv.float_range(min=300.0e6, max=928.0e6)), CONF_IF_FREQUENCY: cv.All(cv.frequency, cv.float_range(min=25000, max=788000)), CONF_FILTER_BANDWIDTH: cv.All(cv.frequency, cv.float_range(min=58000, max=812000)), CONF_CHANNEL: cv.uint8_t, diff --git a/esphome/components/esp32_camera/__init__.py b/esphome/components/esp32_camera/__init__.py index 2ad48173f17..ca37cb392d6 100644 --- a/esphome/components/esp32_camera/__init__.py +++ b/esphome/components/esp32_camera/__init__.py @@ -186,7 +186,7 @@ CONFIG_SCHEMA = cv.All( { cv.Required(CONF_PIN): pins.internal_gpio_input_pin_number, cv.Optional(CONF_FREQUENCY, default="20MHz"): cv.All( - cv.frequency, cv.Range(min=8e6, max=20e6) + cv.frequency, cv.float_range(min=8e6, max=20e6) ), } ), diff --git a/esphome/components/esp8266_pwm/output.py b/esphome/components/esp8266_pwm/output.py index 1404ef8ac3d..2ddf4b90147 100644 --- a/esphome/components/esp8266_pwm/output.py +++ b/esphome/components/esp8266_pwm/output.py @@ -16,7 +16,7 @@ def valid_pwm_pin(value): esp8266_pwm_ns = cg.esphome_ns.namespace("esp8266_pwm") ESP8266PWM = esp8266_pwm_ns.class_("ESP8266PWM", output.FloatOutput, cg.Component) SetFrequencyAction = esp8266_pwm_ns.class_("SetFrequencyAction", automation.Action) -validate_frequency = cv.All(cv.frequency, cv.Range(min=1.0e-6)) +validate_frequency = cv.All(cv.frequency, cv.float_range(min=1.0e-6)) CONFIG_SCHEMA = cv.All( output.FLOAT_OUTPUT_SCHEMA.extend( diff --git a/esphome/components/i2c/__init__.py b/esphome/components/i2c/__init__.py index 9e7c9d702cc..7706484e977 100644 --- a/esphome/components/i2c/__init__.py +++ b/esphome/components/i2c/__init__.py @@ -121,7 +121,7 @@ CONFIG_SCHEMA = cv.All( nrf52="100kHz", ): cv.All( cv.frequency, - cv.Range(min=0, min_included=False), + cv.float_range(min=0, min_included=False), ), cv.Optional(CONF_TIMEOUT): cv.All( cv.only_with_framework(["arduino", "esp-idf"]), diff --git a/esphome/components/ledc/output.py b/esphome/components/ledc/output.py index 2133c4daf98..7ce79aa5147 100644 --- a/esphome/components/ledc/output.py +++ b/esphome/components/ledc/output.py @@ -45,7 +45,9 @@ CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( { cv.Required(CONF_ID): cv.declare_id(LEDCOutput), cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema, - cv.Optional(CONF_FREQUENCY, default="1kHz"): cv.frequency, + cv.Optional(CONF_FREQUENCY, default="1kHz"): cv.All( + cv.frequency, cv.float_range(min=0, min_included=False) + ), cv.Optional(CONF_CHANNEL): cv.int_range(min=0, max=15), cv.Optional(CONF_PHASE_ANGLE): cv.All( cv.only_with_esp_idf, cv.angle, cv.float_range(min=0.0, max=360.0) diff --git a/esphome/components/libretiny_pwm/output.py b/esphome/components/libretiny_pwm/output.py index 1eb4869da3e..28556514d89 100644 --- a/esphome/components/libretiny_pwm/output.py +++ b/esphome/components/libretiny_pwm/output.py @@ -14,7 +14,9 @@ CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( { cv.Required(CONF_ID): cv.declare_id(LibreTinyPWM), cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema, - cv.Optional(CONF_FREQUENCY, default="1kHz"): cv.frequency, + cv.Optional(CONF_FREQUENCY, default="1kHz"): cv.All( + cv.frequency, cv.float_range(min=0, min_included=False) + ), } ).extend(cv.COMPONENT_SCHEMA) diff --git a/esphome/components/pca9685/__init__.py b/esphome/components/pca9685/__init__.py index 56101c2d621..0e238ff7dae 100644 --- a/esphome/components/pca9685/__init__.py +++ b/esphome/components/pca9685/__init__.py @@ -38,7 +38,7 @@ CONFIG_SCHEMA = cv.All( { cv.GenerateID(): cv.declare_id(PCA9685Output), cv.Optional(CONF_FREQUENCY): cv.All( - cv.frequency, cv.Range(min=23.84, max=1525.88) + cv.frequency, cv.float_range(min=23.84, max=1525.88) ), cv.Optional(CONF_EXTERNAL_CLOCK_INPUT, default=False): cv.boolean, cv.Optional(CONF_PHASE_BALANCER, default="linear"): cv.enum( diff --git a/esphome/components/rp2040_pwm/output.py b/esphome/components/rp2040_pwm/output.py index ac1892fa290..441a52de7f9 100644 --- a/esphome/components/rp2040_pwm/output.py +++ b/esphome/components/rp2040_pwm/output.py @@ -11,7 +11,7 @@ DEPENDENCIES = ["rp2040"] rp2040_pwm_ns = cg.esphome_ns.namespace("rp2040_pwm") RP2040PWM = rp2040_pwm_ns.class_("RP2040PWM", output.FloatOutput, cg.Component) SetFrequencyAction = rp2040_pwm_ns.class_("SetFrequencyAction", automation.Action) -validate_frequency = cv.All(cv.frequency, cv.Range(min=1.0e-6)) +validate_frequency = cv.All(cv.frequency, cv.float_range(min=1.0e-6)) CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( { diff --git a/esphome/components/sx126x/__init__.py b/esphome/components/sx126x/__init__.py index 4641db6483c..ed878ed0d43 100644 --- a/esphome/components/sx126x/__init__.py +++ b/esphome/components/sx126x/__init__.py @@ -199,9 +199,13 @@ CONFIG_SCHEMA = ( cv.Optional(CONF_CRC_INITIAL, default=0x1D0F): cv.All( cv.hex_int, cv.Range(min=0, max=0xFFFF) ), - cv.Optional(CONF_DEVIATION, default=5000): cv.int_range(min=0, max=100000), + cv.Optional(CONF_DEVIATION, default="5kHz"): cv.All( + cv.frequency, cv.float_range(min=0, max=100000) + ), cv.Required(CONF_DIO1_PIN): pins.gpio_input_pin_schema, - cv.Required(CONF_FREQUENCY): cv.int_range(min=137000000, max=1020000000), + cv.Required(CONF_FREQUENCY): cv.All( + cv.frequency, cv.float_range(min=137.0e6, max=1020.0e6) + ), cv.Required(CONF_HW_VERSION): cv.one_of( "sx1261", "sx1262", "sx1268", "llcc68", lower=True ), diff --git a/esphome/components/sx127x/__init__.py b/esphome/components/sx127x/__init__.py index b569a759720..f3a9cca93f4 100644 --- a/esphome/components/sx127x/__init__.py +++ b/esphome/components/sx127x/__init__.py @@ -196,9 +196,13 @@ CONFIG_SCHEMA = ( cv.Optional(CONF_BITSYNC): cv.boolean, cv.Optional(CONF_CODING_RATE, default="CR_4_5"): cv.enum(CODING_RATE), cv.Optional(CONF_CRC_ENABLE, default=False): cv.boolean, - cv.Optional(CONF_DEVIATION, default=5000): cv.int_range(min=0, max=100000), + cv.Optional(CONF_DEVIATION, default="5kHz"): cv.All( + cv.frequency, cv.float_range(min=0, max=100000) + ), cv.Optional(CONF_DIO0_PIN): pins.internal_gpio_input_pin_schema, - cv.Required(CONF_FREQUENCY): cv.int_range(min=137000000, max=1020000000), + cv.Required(CONF_FREQUENCY): cv.All( + cv.frequency, cv.float_range(min=137.0e6, max=1020.0e6) + ), cv.Required(CONF_MODULATION): cv.enum(MOD), cv.Optional(CONF_ON_PACKET): automation.validate_automation(single=True), cv.Optional(CONF_PA_PIN, default="BOOST"): cv.enum(PA_PIN), From 1922455fa74082b8fc22455080b98cd848cdea04 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Fri, 19 Dec 2025 20:25:16 -0600 Subject: [PATCH 04/13] [wifi] Fix for `wifi_info` when static IP is configured (#12576) --- esphome/components/wifi/wifi_component_esp8266.cpp | 10 ++++++++++ esphome/components/wifi/wifi_component_esp_idf.cpp | 8 ++++++++ esphome/components/wifi/wifi_component_libretiny.cpp | 8 ++++++++ esphome/components/wifi/wifi_component_pico_w.cpp | 9 +++++++++ 4 files changed, 35 insertions(+) diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index 3b1a442bdbd..41594b947c7 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -528,6 +528,16 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { for (auto *listener : global_wifi_component->connect_state_listeners_) { listener->on_wifi_connect_state(global_wifi_component->wifi_ssid(), global_wifi_component->wifi_bssid()); } + // For static IP configurations, GOT_IP event may not fire, so notify IP listeners here +#ifdef USE_WIFI_MANUAL_IP + if (const WiFiAP *config = global_wifi_component->get_selected_sta_(); + config && config->get_manual_ip().has_value()) { + for (auto *listener : global_wifi_component->ip_state_listeners_) { + listener->on_ip_state(global_wifi_component->wifi_sta_ip_addresses(), + global_wifi_component->get_dns_address(0), global_wifi_component->get_dns_address(1)); + } + } +#endif #endif break; } diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index 4a3c40a1199..380e4ea7fd6 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -739,6 +739,14 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { for (auto *listener : this->connect_state_listeners_) { listener->on_wifi_connect_state(this->wifi_ssid(), this->wifi_bssid()); } + // For static IP configurations, GOT_IP event may not fire, so notify IP listeners here +#ifdef USE_WIFI_MANUAL_IP + if (const WiFiAP *config = this->get_selected_sta_(); config && config->get_manual_ip().has_value()) { + for (auto *listener : this->ip_state_listeners_) { + listener->on_ip_state(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1)); + } + } +#endif #endif } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_DISCONNECTED) { diff --git a/esphome/components/wifi/wifi_component_libretiny.cpp b/esphome/components/wifi/wifi_component_libretiny.cpp index 36003a6eb42..1012b0be6ca 100644 --- a/esphome/components/wifi/wifi_component_libretiny.cpp +++ b/esphome/components/wifi/wifi_component_libretiny.cpp @@ -305,6 +305,14 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ for (auto *listener : this->connect_state_listeners_) { listener->on_wifi_connect_state(this->wifi_ssid(), this->wifi_bssid()); } + // For static IP configurations, GOT_IP event may not fire, so notify IP listeners here +#ifdef USE_WIFI_MANUAL_IP + if (const WiFiAP *config = this->get_selected_sta_(); config && config->get_manual_ip().has_value()) { + for (auto *listener : this->ip_state_listeners_) { + listener->on_ip_state(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1)); + } + } +#endif #endif break; } diff --git a/esphome/components/wifi/wifi_component_pico_w.cpp b/esphome/components/wifi/wifi_component_pico_w.cpp index 02287554324..c88aeb2d4f4 100644 --- a/esphome/components/wifi/wifi_component_pico_w.cpp +++ b/esphome/components/wifi/wifi_component_pico_w.cpp @@ -259,6 +259,15 @@ void WiFiComponent::wifi_loop_() { for (auto *listener : this->connect_state_listeners_) { listener->on_wifi_connect_state(this->wifi_ssid(), this->wifi_bssid()); } + // For static IP configurations, notify IP listeners immediately as the IP is already configured +#ifdef USE_WIFI_MANUAL_IP + if (const WiFiAP *config = this->get_selected_sta_(); config && config->get_manual_ip().has_value()) { + s_sta_had_ip = true; + for (auto *listener : this->ip_state_listeners_) { + listener->on_ip_state(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1)); + } + } +#endif #endif } else if (!is_connected && s_sta_was_connected) { // Just disconnected From 726db746c815751618def87a15218ee228be8f98 Mon Sep 17 00:00:00 2001 From: Eduard Llull Date: Sat, 20 Dec 2025 16:59:14 +0100 Subject: [PATCH 05/13] [display_menu_base] Call on_value_ after updating the select (#12584) --- esphome/components/display_menu_base/menu_item.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/components/display_menu_base/menu_item.cpp b/esphome/components/display_menu_base/menu_item.cpp index 8224adf3fe9..08f758045e5 100644 --- a/esphome/components/display_menu_base/menu_item.cpp +++ b/esphome/components/display_menu_base/menu_item.cpp @@ -54,6 +54,7 @@ bool MenuItemSelect::select_next() { if (this->select_var_ != nullptr) { this->select_var_->make_call().select_next(true).perform(); + this->on_value_(); changed = true; } @@ -65,6 +66,7 @@ bool MenuItemSelect::select_prev() { if (this->select_var_ != nullptr) { this->select_var_->make_call().select_previous(true).perform(); + this->on_value_(); changed = true; } From b055f5b4bf9783d59b72833976a23c2a835606ec Mon Sep 17 00:00:00 2001 From: Stuart Parmenter Date: Sat, 20 Dec 2025 10:18:20 -0800 Subject: [PATCH 06/13] [hub75] Bump esp-hub75 version to 0.1.7 (#12564) --- .clang-tidy.hash | 2 +- esphome/components/hub75/display.py | 46 ++++++++++++++--------------- esphome/idf_component.yml | 4 +++ platformio.ini | 2 -- 4 files changed, 28 insertions(+), 26 deletions(-) diff --git a/.clang-tidy.hash b/.clang-tidy.hash index a3322ba731c..240b205158c 100644 --- a/.clang-tidy.hash +++ b/.clang-tidy.hash @@ -1 +1 @@ -766420905c06eeb6c5f360f68fd965e5ddd9c4a5db6b823263d3ad3accb64a07 +4268ab0b5150f79ab1c317e8f3834c8bb0b4c8122da4f6b1fd67c49d0f2098c9 diff --git a/esphome/components/hub75/display.py b/esphome/components/hub75/display.py index 81dd4ffc1cf..0518731a6a1 100644 --- a/esphome/components/hub75/display.py +++ b/esphome/components/hub75/display.py @@ -93,35 +93,35 @@ CONF_DOUBLE_BUFFER = "double_buffer" CONF_MIN_REFRESH_RATE = "min_refresh_rate" # Map to hub75 library enums (in global namespace) -ShiftDriver = cg.global_ns.enum("ShiftDriver", is_class=True) +Hub75ShiftDriver = cg.global_ns.enum("Hub75ShiftDriver", is_class=True) SHIFT_DRIVERS = { - "GENERIC": ShiftDriver.GENERIC, - "FM6126A": ShiftDriver.FM6126A, - "ICN2038S": ShiftDriver.ICN2038S, - "FM6124": ShiftDriver.FM6124, - "MBI5124": ShiftDriver.MBI5124, - "DP3246": ShiftDriver.DP3246, + "GENERIC": Hub75ShiftDriver.GENERIC, + "FM6126A": Hub75ShiftDriver.FM6126A, + "ICN2038S": Hub75ShiftDriver.ICN2038S, + "FM6124": Hub75ShiftDriver.FM6124, + "MBI5124": Hub75ShiftDriver.MBI5124, + "DP3246": Hub75ShiftDriver.DP3246, } -PanelLayout = cg.global_ns.enum("PanelLayout", is_class=True) +Hub75PanelLayout = cg.global_ns.enum("Hub75PanelLayout", is_class=True) PANEL_LAYOUTS = { - "HORIZONTAL": PanelLayout.HORIZONTAL, - "TOP_LEFT_DOWN": PanelLayout.TOP_LEFT_DOWN, - "TOP_RIGHT_DOWN": PanelLayout.TOP_RIGHT_DOWN, - "BOTTOM_LEFT_UP": PanelLayout.BOTTOM_LEFT_UP, - "BOTTOM_RIGHT_UP": PanelLayout.BOTTOM_RIGHT_UP, - "TOP_LEFT_DOWN_ZIGZAG": PanelLayout.TOP_LEFT_DOWN_ZIGZAG, - "TOP_RIGHT_DOWN_ZIGZAG": PanelLayout.TOP_RIGHT_DOWN_ZIGZAG, - "BOTTOM_LEFT_UP_ZIGZAG": PanelLayout.BOTTOM_LEFT_UP_ZIGZAG, - "BOTTOM_RIGHT_UP_ZIGZAG": PanelLayout.BOTTOM_RIGHT_UP_ZIGZAG, + "HORIZONTAL": Hub75PanelLayout.HORIZONTAL, + "TOP_LEFT_DOWN": Hub75PanelLayout.TOP_LEFT_DOWN, + "TOP_RIGHT_DOWN": Hub75PanelLayout.TOP_RIGHT_DOWN, + "BOTTOM_LEFT_UP": Hub75PanelLayout.BOTTOM_LEFT_UP, + "BOTTOM_RIGHT_UP": Hub75PanelLayout.BOTTOM_RIGHT_UP, + "TOP_LEFT_DOWN_ZIGZAG": Hub75PanelLayout.TOP_LEFT_DOWN_ZIGZAG, + "TOP_RIGHT_DOWN_ZIGZAG": Hub75PanelLayout.TOP_RIGHT_DOWN_ZIGZAG, + "BOTTOM_LEFT_UP_ZIGZAG": Hub75PanelLayout.BOTTOM_LEFT_UP_ZIGZAG, + "BOTTOM_RIGHT_UP_ZIGZAG": Hub75PanelLayout.BOTTOM_RIGHT_UP_ZIGZAG, } -ScanPattern = cg.global_ns.enum("ScanPattern", is_class=True) +Hub75ScanWiring = cg.global_ns.enum("Hub75ScanWiring", is_class=True) SCAN_PATTERNS = { - "STANDARD_TWO_SCAN": ScanPattern.STANDARD_TWO_SCAN, - "FOUR_SCAN_16PX_HIGH": ScanPattern.FOUR_SCAN_16PX_HIGH, - "FOUR_SCAN_32PX_HIGH": ScanPattern.FOUR_SCAN_32PX_HIGH, - "FOUR_SCAN_64PX_HIGH": ScanPattern.FOUR_SCAN_64PX_HIGH, + "STANDARD_TWO_SCAN": Hub75ScanWiring.STANDARD_TWO_SCAN, + "FOUR_SCAN_16PX_HIGH": Hub75ScanWiring.FOUR_SCAN_16PX_HIGH, + "FOUR_SCAN_32PX_HIGH": Hub75ScanWiring.FOUR_SCAN_32PX_HIGH, + "FOUR_SCAN_64PX_HIGH": Hub75ScanWiring.FOUR_SCAN_64PX_HIGH, } Hub75ClockSpeed = cg.global_ns.enum("Hub75ClockSpeed", is_class=True) @@ -528,7 +528,7 @@ def _build_config_struct( async def to_code(config: ConfigType) -> None: add_idf_component( name="esphome/esp-hub75", - ref="0.1.6", + ref="0.1.7", ) # Set compile-time configuration via defines diff --git a/esphome/idf_component.yml b/esphome/idf_component.yml index 9bb59672487..4573391bc11 100644 --- a/esphome/idf_component.yml +++ b/esphome/idf_component.yml @@ -27,3 +27,7 @@ dependencies: version: "1.7.6~1" rules: - if: "target in [esp32s2, esp32s3, esp32p4]" + esphome/esp-hub75: + version: 0.1.7 + rules: + - if: "target in [esp32, esp32s2, esp32s3, esp32p4]" diff --git a/platformio.ini b/platformio.ini index d37c798c05f..a27fb1f5378 100644 --- a/platformio.ini +++ b/platformio.ini @@ -156,7 +156,6 @@ lib_deps = esphome/ESP32-audioI2S@2.3.0 ; i2s_audio droscy/esp_wireguard@0.4.2 ; wireguard esphome/esp-audio-libs@2.0.1 ; audio - esphome/esp-hub75@0.1.6 ; hub75 build_flags = ${common:arduino.build_flags} @@ -180,7 +179,6 @@ lib_deps = droscy/esp_wireguard@0.4.2 ; wireguard kahrendt/ESPMicroSpeechFeatures@1.1.0 ; micro_wake_word esphome/esp-audio-libs@2.0.1 ; audio - esphome/esp-hub75@0.1.6 ; hub75 build_flags = ${common:idf.build_flags} -Wno-nonnull-compare From 086ec770ea3dfef98abafd3fe30deeecc6f61e3a Mon Sep 17 00:00:00 2001 From: Leo Bergolth Date: Sat, 20 Dec 2025 21:04:59 +0100 Subject: [PATCH 07/13] send NIL ("-") as timestamp if time source is not valid (#12588) --- esphome/components/syslog/esphome_syslog.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/esphome/components/syslog/esphome_syslog.cpp b/esphome/components/syslog/esphome_syslog.cpp index f5c20c891e7..851fb30c22f 100644 --- a/esphome/components/syslog/esphome_syslog.cpp +++ b/esphome/components/syslog/esphome_syslog.cpp @@ -34,7 +34,15 @@ void Syslog::log_(const int level, const char *tag, const char *message, size_t severity = LOG_LEVEL_TO_SYSLOG_SEVERITY[level]; } int pri = this->facility_ * 8 + severity; - auto timestamp = this->time_->now().strftime("%b %e %H:%M:%S"); + auto now = this->time_->now(); + std::string timestamp; + if (now.is_valid()) { + timestamp = now.strftime("%b %e %H:%M:%S"); + } else { + // RFC 5424: A syslog application MUST use the NILVALUE as TIMESTAMP if the syslog application is incapable of + // obtaining system time. + timestamp = "-"; + } size_t len = message_len; // remove color formatting if (this->strip_ && message[0] == 0x1B && len > 11) { From 61ec3508ed75f826ab0f20dacceadc1f212985bf Mon Sep 17 00:00:00 2001 From: Anna Oake Date: Sun, 21 Dec 2025 19:04:17 +0100 Subject: [PATCH 08/13] [cc1101] Fix option defaults and move them to YAML (#12608) --- esphome/components/cc1101/__init__.py | 95 +++++++++++++++++---------- esphome/components/cc1101/cc1101.cpp | 17 ----- 2 files changed, 59 insertions(+), 53 deletions(-) diff --git a/esphome/components/cc1101/__init__.py b/esphome/components/cc1101/__init__.py index e314da70793..c205ff2f695 100644 --- a/esphome/components/cc1101/__init__.py +++ b/esphome/components/cc1101/__init__.py @@ -160,41 +160,63 @@ HYST_LEVEL = { "High": HystLevel.HYST_LEVEL_HIGH, } -# Config key -> Validator mapping +# Optional settings to generate setter calls for CONFIG_MAP = { - CONF_OUTPUT_POWER: cv.float_range(min=-30.0, max=11.0), - CONF_RX_ATTENUATION: cv.enum(RX_ATTENUATION, upper=False), - CONF_DC_BLOCKING_FILTER: cv.boolean, - CONF_FREQUENCY: cv.All(cv.frequency, cv.float_range(min=300.0e6, max=928.0e6)), - CONF_IF_FREQUENCY: cv.All(cv.frequency, cv.float_range(min=25000, max=788000)), - CONF_FILTER_BANDWIDTH: cv.All(cv.frequency, cv.float_range(min=58000, max=812000)), - CONF_CHANNEL: cv.uint8_t, - CONF_CHANNEL_SPACING: cv.All(cv.frequency, cv.float_range(min=25000, max=405000)), - CONF_FSK_DEVIATION: cv.All(cv.frequency, cv.float_range(min=1500, max=381000)), - CONF_MSK_DEVIATION: cv.int_range(min=1, max=8), - CONF_SYMBOL_RATE: cv.float_range(min=600, max=500000), - CONF_SYNC_MODE: cv.enum(SYNC_MODE, upper=False), - CONF_CARRIER_SENSE_ABOVE_THRESHOLD: cv.boolean, - CONF_MODULATION_TYPE: cv.enum(MODULATION, upper=False), - CONF_MANCHESTER: cv.boolean, - CONF_NUM_PREAMBLE: cv.int_range(min=0, max=7), - CONF_SYNC1: cv.hex_uint8_t, - CONF_SYNC0: cv.hex_uint8_t, - CONF_MAGN_TARGET: cv.enum(MAGN_TARGET, upper=False), - CONF_MAX_LNA_GAIN: cv.enum(MAX_LNA_GAIN, upper=False), - CONF_MAX_DVGA_GAIN: cv.enum(MAX_DVGA_GAIN, upper=False), - CONF_CARRIER_SENSE_ABS_THR: cv.int_range(min=-8, max=7), - CONF_CARRIER_SENSE_REL_THR: cv.enum(CARRIER_SENSE_REL_THR, upper=False), - CONF_LNA_PRIORITY: cv.boolean, - CONF_FILTER_LENGTH_FSK_MSK: cv.enum(FILTER_LENGTH_FSK_MSK, upper=False), - CONF_FILTER_LENGTH_ASK_OOK: cv.enum(FILTER_LENGTH_ASK_OOK, upper=False), - CONF_FREEZE: cv.enum(FREEZE, upper=False), - CONF_WAIT_TIME: cv.enum(WAIT_TIME, upper=False), - CONF_HYST_LEVEL: cv.enum(HYST_LEVEL, upper=False), - CONF_PACKET_MODE: cv.boolean, - CONF_PACKET_LENGTH: cv.uint8_t, - CONF_CRC_ENABLE: cv.boolean, - CONF_WHITENING: cv.boolean, + cv.Optional(CONF_OUTPUT_POWER, default=10): cv.float_range(min=-30.0, max=11.0), + cv.Optional(CONF_RX_ATTENUATION, default="0dB"): cv.enum( + RX_ATTENUATION, upper=False + ), + cv.Optional(CONF_DC_BLOCKING_FILTER, default=True): cv.boolean, + cv.Optional(CONF_FREQUENCY, default="433.92MHz"): cv.All( + cv.frequency, cv.float_range(min=300.0e6, max=928.0e6) + ), + cv.Optional(CONF_IF_FREQUENCY, default="153kHz"): cv.All( + cv.frequency, cv.float_range(min=25000, max=788000) + ), + cv.Optional(CONF_FILTER_BANDWIDTH, default="203kHz"): cv.All( + cv.frequency, cv.float_range(min=58000, max=812000) + ), + cv.Optional(CONF_CHANNEL, default=0): cv.uint8_t, + cv.Optional(CONF_CHANNEL_SPACING, default="200kHz"): cv.All( + cv.frequency, cv.float_range(min=25000, max=405000) + ), + cv.Optional(CONF_FSK_DEVIATION): cv.All( + cv.frequency, cv.float_range(min=1500, max=381000) + ), + cv.Optional(CONF_MSK_DEVIATION): cv.int_range(min=1, max=8), + cv.Optional(CONF_SYMBOL_RATE, default=5000): cv.float_range(min=600, max=500000), + cv.Optional(CONF_SYNC_MODE, default="16/16"): cv.enum(SYNC_MODE, upper=False), + cv.Optional(CONF_CARRIER_SENSE_ABOVE_THRESHOLD, default=False): cv.boolean, + cv.Optional(CONF_MODULATION_TYPE, default="ASK/OOK"): cv.enum( + MODULATION, upper=False + ), + cv.Optional(CONF_MANCHESTER, default=False): cv.boolean, + cv.Optional(CONF_NUM_PREAMBLE, default=2): cv.int_range(min=0, max=7), + cv.Optional(CONF_SYNC1, default=0xD3): cv.hex_uint8_t, + cv.Optional(CONF_SYNC0, default=0x91): cv.hex_uint8_t, + cv.Optional(CONF_MAGN_TARGET, default="42dB"): cv.enum(MAGN_TARGET, upper=False), + cv.Optional(CONF_MAX_LNA_GAIN, default="Default"): cv.enum( + MAX_LNA_GAIN, upper=False + ), + cv.Optional(CONF_MAX_DVGA_GAIN, default="-3"): cv.enum(MAX_DVGA_GAIN, upper=False), + cv.Optional(CONF_CARRIER_SENSE_ABS_THR): cv.int_range(min=-8, max=7), + cv.Optional(CONF_CARRIER_SENSE_REL_THR): cv.enum( + CARRIER_SENSE_REL_THR, upper=False + ), + cv.Optional(CONF_LNA_PRIORITY, default=False): cv.boolean, + cv.Optional(CONF_FILTER_LENGTH_FSK_MSK): cv.enum( + FILTER_LENGTH_FSK_MSK, upper=False + ), + cv.Optional(CONF_FILTER_LENGTH_ASK_OOK): cv.enum( + FILTER_LENGTH_ASK_OOK, upper=False + ), + cv.Optional(CONF_FREEZE): cv.enum(FREEZE, upper=False), + cv.Optional(CONF_WAIT_TIME, default="32"): cv.enum(WAIT_TIME, upper=False), + cv.Optional(CONF_HYST_LEVEL): cv.enum(HYST_LEVEL, upper=False), + cv.Optional(CONF_PACKET_MODE, default=False): cv.boolean, + cv.Optional(CONF_PACKET_LENGTH): cv.uint8_t, + cv.Optional(CONF_CRC_ENABLE, default=False): cv.boolean, + cv.Optional(CONF_WHITENING, default=False): cv.boolean, } @@ -217,7 +239,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_ON_PACKET): automation.validate_automation(single=True), } ) - .extend({cv.Optional(key): validator for key, validator in CONFIG_MAP.items()}) + .extend(CONFIG_MAP) .extend(cv.COMPONENT_SCHEMA) .extend(spi.spi_device_schema(cs_pin_required=True)), _validate_packet_mode, @@ -229,7 +251,8 @@ async def to_code(config): await cg.register_component(var, config) await spi.register_spi_device(var, config) - for key in CONFIG_MAP: + for opt in CONFIG_MAP: + key = opt.schema if key in config: cg.add(getattr(var, f"set_{key}")(config[key])) diff --git a/esphome/components/cc1101/cc1101.cpp b/esphome/components/cc1101/cc1101.cpp index 1fe402d6c65..f98afd94a19 100644 --- a/esphome/components/cc1101/cc1101.cpp +++ b/esphome/components/cc1101/cc1101.cpp @@ -98,25 +98,8 @@ CC1101Component::CC1101Component() { this->state_.LENGTH_CONFIG = 2; this->state_.FS_AUTOCAL = 1; - // Default Settings - this->set_frequency(433920000); - this->set_if_frequency(153000); - this->set_filter_bandwidth(203000); - this->set_channel(0); - this->set_channel_spacing(200000); - this->set_symbol_rate(5000); - this->set_sync_mode(SyncMode::SYNC_MODE_NONE); - this->set_carrier_sense_above_threshold(true); - this->set_modulation_type(Modulation::MODULATION_ASK_OOK); - this->set_magn_target(MagnTarget::MAGN_TARGET_42DB); - this->set_max_lna_gain(MaxLnaGain::MAX_LNA_GAIN_DEFAULT); - this->set_max_dvga_gain(MaxDvgaGain::MAX_DVGA_GAIN_MINUS_3); - this->set_lna_priority(false); - this->set_wait_time(WaitTime::WAIT_TIME_32_SAMPLES); - // CRITICAL: Initialize PA Table to avoid transmitting 0 power (Silence) memset(this->pa_table_, 0, sizeof(this->pa_table_)); - this->set_output_power(10.0f); } void CC1101Component::setup() { From 6054685daee93e6c4baad681df16024d88613112 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 21 Dec 2025 09:04:43 -1000 Subject: [PATCH 09/13] [esp32_camera] Throttle frame logging to reduce overhead and improve throughput (#12586) --- .../components/esp32_camera/esp32_camera.cpp | 18 +++++++++++++++++- esphome/components/esp32_camera/esp32_camera.h | 4 ++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/esphome/components/esp32_camera/esp32_camera.cpp b/esphome/components/esp32_camera/esp32_camera.cpp index 5080a6f32db..a3677330ca7 100644 --- a/esphome/components/esp32_camera/esp32_camera.cpp +++ b/esphome/components/esp32_camera/esp32_camera.cpp @@ -11,6 +11,9 @@ namespace esphome { namespace esp32_camera { static const char *const TAG = "esp32_camera"; +#if ESPHOME_LOG_LEVEL < ESPHOME_LOG_LEVEL_VERBOSE +static constexpr uint32_t FRAME_LOG_INTERVAL_MS = 60000; +#endif /* ---------------- public API (derivated) ---------------- */ void ESP32Camera::setup() { @@ -204,7 +207,20 @@ void ESP32Camera::loop() { } this->current_image_ = std::make_shared(fb, this->single_requesters_ | this->stream_requesters_); - ESP_LOGD(TAG, "Got Image: len=%u", fb->len); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + ESP_LOGV(TAG, "Got Image: len=%u", fb->len); +#else + // Initialize log time on first frame to ensure accurate interval measurement + if (this->frame_count_ == 0) { + this->last_log_time_ = now; + } + this->frame_count_++; + if (now - this->last_log_time_ >= FRAME_LOG_INTERVAL_MS) { + ESP_LOGD(TAG, "Received %u images in last %us", this->frame_count_, FRAME_LOG_INTERVAL_MS / 1000); + this->last_log_time_ = now; + this->frame_count_ = 0; + } +#endif for (auto *listener : this->listeners_) { listener->on_camera_image(this->current_image_); } diff --git a/esphome/components/esp32_camera/esp32_camera.h b/esphome/components/esp32_camera/esp32_camera.h index 54a7d6064a2..a49fca65111 100644 --- a/esphome/components/esp32_camera/esp32_camera.h +++ b/esphome/components/esp32_camera/esp32_camera.h @@ -213,6 +213,10 @@ class ESP32Camera : public camera::Camera { uint32_t last_idle_request_{0}; uint32_t last_update_{0}; +#if ESPHOME_LOG_LEVEL < ESPHOME_LOG_LEVEL_VERBOSE + uint32_t last_log_time_{0}; + uint16_t frame_count_{0}; +#endif #ifdef USE_I2C i2c::InternalI2CBus *i2c_bus_{nullptr}; #endif // USE_I2C From c8fb694dcbdc1b837fcaf6683b13e43722bd6431 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Tue, 23 Dec 2025 09:05:48 -0500 Subject: [PATCH 10/13] [cc1101] Fix packet mode RSSI/LQI (#12630) Co-authored-by: Claude --- esphome/components/cc1101/cc1101.cpp | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/esphome/components/cc1101/cc1101.cpp b/esphome/components/cc1101/cc1101.cpp index f98afd94a19..7e5309e1659 100644 --- a/esphome/components/cc1101/cc1101.cpp +++ b/esphome/components/cc1101/cc1101.cpp @@ -169,14 +169,16 @@ void CC1101Component::loop() { } // Read packet - uint8_t payload_length; + uint8_t payload_length, expected_rx; if (this->state_.LENGTH_CONFIG == static_cast(LengthConfig::LENGTH_CONFIG_VARIABLE)) { this->read_(Register::FIFO, &payload_length, 1); + expected_rx = payload_length + 1; } else { payload_length = this->state_.PKTLEN; + expected_rx = payload_length; } - if (payload_length == 0 || payload_length > 64) { - ESP_LOGW(TAG, "Invalid payload length: %u", payload_length); + if (payload_length == 0 || payload_length > 64 || rx_bytes != expected_rx) { + ESP_LOGW(TAG, "Invalid packet: rx_bytes %u, payload_length %u", rx_bytes, payload_length); this->enter_idle_(); this->strobe_(Command::FRX); this->strobe_(Command::RX); @@ -186,13 +188,12 @@ void CC1101Component::loop() { this->packet_.resize(payload_length); this->read_(Register::FIFO, this->packet_.data(), payload_length); - // Read status and trigger - uint8_t status[2]; - this->read_(Register::FIFO, status, 2); - int8_t rssi_raw = static_cast(status[0]); - float rssi = (rssi_raw * RSSI_STEP) - RSSI_OFFSET; - bool crc_ok = (status[1] & STATUS_CRC_OK_MASK) != 0; - uint8_t lqi = status[1] & STATUS_LQI_MASK; + // Read status from registers (more reliable than FIFO status bytes due to timing issues) + this->read_(Register::RSSI); + this->read_(Register::LQI); + float rssi = (this->state_.RSSI * RSSI_STEP) - RSSI_OFFSET; + bool crc_ok = (this->state_.LQI & STATUS_CRC_OK_MASK) != 0; + uint8_t lqi = this->state_.LQI & STATUS_LQI_MASK; if (this->state_.CRC_EN == 0 || crc_ok) { this->packet_trigger_->trigger(this->packet_, rssi, lqi); } @@ -616,12 +617,15 @@ void CC1101Component::set_packet_mode(bool value) { this->state_.GDO0_CFG = 0x01; // Set max RX FIFO threshold to ensure we only trigger on end-of-packet this->state_.FIFO_THR = 15; + // Don't append status bytes to FIFO - we read from registers instead + this->state_.APPEND_STATUS = 0; } else { // Configure GDO0 for serial data (async serial mode) this->state_.GDO0_CFG = 0x0D; } if (this->initialized_) { this->write_(Register::PKTCTRL0); + this->write_(Register::PKTCTRL1); this->write_(Register::IOCFG0); this->write_(Register::FIFOTHR); } From 0922f240e0ab48a8dd86e59876980481358a47ca Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Tue, 23 Dec 2025 09:23:04 -0500 Subject: [PATCH 11/13] Bump version to 2025.12.2 --- Doxyfile | 2 +- esphome/const.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Doxyfile b/Doxyfile index 4c533ec87f7..d41459ea460 100644 --- a/Doxyfile +++ b/Doxyfile @@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 2025.12.1 +PROJECT_NUMBER = 2025.12.2 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/esphome/const.py b/esphome/const.py index 8f9a3497ff4..41bb419aaf3 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -4,7 +4,7 @@ from enum import Enum from esphome.enum import StrEnum -__version__ = "2025.12.1" +__version__ = "2025.12.2" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From ebb6babb3d9db6952c0dd6af6c4cc14c898d94b1 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Tue, 23 Dec 2025 09:26:38 -0500 Subject: [PATCH 12/13] Fix hash --- .clang-tidy.hash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.clang-tidy.hash b/.clang-tidy.hash index 240b205158c..18379de92e7 100644 --- a/.clang-tidy.hash +++ b/.clang-tidy.hash @@ -1 +1 @@ -4268ab0b5150f79ab1c317e8f3834c8bb0b4c8122da4f6b1fd67c49d0f2098c9 +5969e705693278d984c5292e998df0cbaf34f7e1f04dfc7f7b7ad7168527bfa7 From 0c566c6f00b1bf2fc11a4bb019f1f9292c2f0d5f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 23 Dec 2025 06:59:07 -1000 Subject: [PATCH 13/13] [core] Deprecate get_object_id() and migrate remaining usages to get_object_id_to() (#12629) --- esphome/components/pid/pid_climate.cpp | 6 +++--- esphome/components/prometheus/prometheus_handler.cpp | 6 +++++- esphome/core/entity_base.h | 9 +++++++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/esphome/components/pid/pid_climate.cpp b/esphome/components/pid/pid_climate.cpp index fd74eabd874..2094c0e942f 100644 --- a/esphome/components/pid/pid_climate.cpp +++ b/esphome/components/pid/pid_climate.cpp @@ -162,14 +162,14 @@ void PIDClimate::start_autotune(std::unique_ptr &&autotune) { float min_value = this->supports_cool_() ? -1.0f : 0.0f; float max_value = this->supports_heat_() ? 1.0f : 0.0f; this->autotuner_->config(min_value, max_value); - this->autotuner_->set_autotuner_id(this->get_object_id()); + this->autotuner_->set_autotuner_id(this->get_name()); ESP_LOGI(TAG, "%s: Autotune has started. This can take a long time depending on the " "responsiveness of your system. Your system " "output will be altered to deliberately oscillate above and below the setpoint multiple times. " "Until your sensor provides a reading, the autotuner may display \'nan\'", - this->get_object_id().c_str()); + this->get_name().c_str()); this->set_interval("autotune-progress", 10000, [this]() { if (this->autotuner_ != nullptr && !this->autotuner_->is_finished()) @@ -178,7 +178,7 @@ void PIDClimate::start_autotune(std::unique_ptr &&autotune) { if (mode != climate::CLIMATE_MODE_HEAT_COOL) { ESP_LOGW(TAG, "%s: !!! For PID autotuner you need to set AUTO (also called heat/cool) mode!", - this->get_object_id().c_str()); + this->get_name().c_str()); } } diff --git a/esphome/components/prometheus/prometheus_handler.cpp b/esphome/components/prometheus/prometheus_handler.cpp index 4b5d834ebfb..88b357041a2 100644 --- a/esphome/components/prometheus/prometheus_handler.cpp +++ b/esphome/components/prometheus/prometheus_handler.cpp @@ -112,7 +112,11 @@ void PrometheusHandler::handleRequest(AsyncWebServerRequest *req) { std::string PrometheusHandler::relabel_id_(EntityBase *obj) { auto item = relabel_map_id_.find(obj); - return item == relabel_map_id_.end() ? obj->get_object_id() : item->second; + if (item != relabel_map_id_.end()) { + return item->second; + } + char object_id_buf[OBJECT_ID_MAX_LEN]; + return obj->get_object_id_to(object_id_buf).str(); } std::string PrometheusHandler::relabel_name_(EntityBase *obj) { diff --git a/esphome/core/entity_base.h b/esphome/core/entity_base.h index eb1ba46c94f..93f989934a3 100644 --- a/esphome/core/entity_base.h +++ b/esphome/core/entity_base.h @@ -33,6 +33,15 @@ class EntityBase { bool has_own_name() const { return this->flags_.has_own_name; } // Get the sanitized name of this Entity as an ID. + // Deprecated: object_id mangles names and all object_id methods are planned for removal. + // See https://github.com/esphome/backlog/issues/76 + // Now is the time to stop using object_id entirely. If you still need it temporarily, + // use get_object_id_to() which will remain available longer but will also eventually be removed. + ESPDEPRECATED("object_id mangles names and all object_id methods are planned for removal " + "(see https://github.com/esphome/backlog/issues/76). " + "Now is the time to stop using object_id. If still needed, use get_object_id_to() " + "which will remain available longer. get_object_id() will be removed in 2026.7.0", + "2025.12.0") std::string get_object_id() const; void set_object_id(const char *object_id);