From 1b31253287343b150a01a3a04e8673dabc68f73e Mon Sep 17 00:00:00 2001 From: eoasmxd <42328021+eoasmxd@users.noreply.github.com> Date: Tue, 23 Dec 2025 06:19:48 +0800 Subject: [PATCH 01/20] Add Event Component to UART (#11765) Co-authored-by: J. Nick Koston --- CODEOWNERS | 1 + esphome/components/uart/event/__init__.py | 90 ++++++++++++++++++++ esphome/components/uart/event/uart_event.cpp | 48 +++++++++++ esphome/components/uart/event/uart_event.h | 31 +++++++ tests/components/uart/test.esp32-idf.yaml | 8 ++ tests/components/uart/test.esp8266-ard.yaml | 8 ++ 6 files changed, 186 insertions(+) create mode 100644 esphome/components/uart/event/__init__.py create mode 100644 esphome/components/uart/event/uart_event.cpp create mode 100644 esphome/components/uart/event/uart_event.h diff --git a/CODEOWNERS b/CODEOWNERS index 941c2e2849..f95d68a46d 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -519,6 +519,7 @@ esphome/components/tuya/switch/* @jesserockz esphome/components/tuya/text_sensor/* @dentra esphome/components/uart/* @esphome/core esphome/components/uart/button/* @ssieb +esphome/components/uart/event/* @eoasmxd esphome/components/uart/packet_transport/* @clydebarrow esphome/components/udp/* @clydebarrow esphome/components/ufire_ec/* @pvizeli diff --git a/esphome/components/uart/event/__init__.py b/esphome/components/uart/event/__init__.py new file mode 100644 index 0000000000..64af318a11 --- /dev/null +++ b/esphome/components/uart/event/__init__.py @@ -0,0 +1,90 @@ +import esphome.codegen as cg +from esphome.components import event, uart +import esphome.config_validation as cv +from esphome.const import CONF_EVENT_TYPES, CONF_ID +from esphome.core import ID +from esphome.types import ConfigType + +from .. import uart_ns + +CODEOWNERS = ["@eoasmxd"] + +DEPENDENCIES = ["uart"] + +UARTEvent = uart_ns.class_("UARTEvent", event.Event, uart.UARTDevice, cg.Component) + + +def validate_event_types(value) -> list[tuple[str, str | list[int]]]: + if not isinstance(value, list): + raise cv.Invalid("Event type must be a list of key-value mappings.") + + processed: list[tuple[str, str | list[int]]] = [] + for item in value: + if not isinstance(item, dict): + raise cv.Invalid(f"Event type item must be a mapping (dictionary): {item}") + if len(item) != 1: + raise cv.Invalid( + f"Event type item must be a single key-value mapping: {item}" + ) + + # Get the single key-value pair + event_name, match_data = next(iter(item.items())) + + if not isinstance(event_name, str): + raise cv.Invalid(f"Event name (key) must be a string: {event_name}") + + try: + # Try to validate as list of hex bytes + match_data_bin = cv.ensure_list(cv.hex_uint8_t)(match_data) + processed.append((event_name, match_data_bin)) + continue + except cv.Invalid: + pass # Not binary, try string + + try: + # Try to validate as string + match_data_str = cv.string_strict(match_data) + processed.append((event_name, match_data_str)) + continue + except cv.Invalid: + pass # Not string either + + # If neither validation passed + raise cv.Invalid( + f"Event match data for '{event_name}' must be a string or a list of hex bytes. Invalid data: {match_data}" + ) + + if not processed: + raise cv.Invalid("event_types must contain at least one event mapping.") + + return processed + + +CONFIG_SCHEMA = ( + event.event_schema(UARTEvent) + .extend( + { + cv.Required(CONF_EVENT_TYPES): validate_event_types, + } + ) + .extend(uart.UART_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) + + +async def to_code(config: ConfigType) -> None: + event_names = [item[0] for item in config[CONF_EVENT_TYPES]] + var = await event.new_event(config, event_types=event_names) + await cg.register_component(var, config) + await uart.register_uart_device(var, config) + for i, (event_name, match_data) in enumerate(config[CONF_EVENT_TYPES]): + if isinstance(match_data, str): + match_data = [ord(c) for c in match_data] + + match_data_var_id = ID( + f"match_data_{config[CONF_ID]}_{i}", is_declaration=True, type=cg.uint8 + ) + match_data_var = cg.static_const_array( + match_data_var_id, cg.ArrayInitializer(*match_data) + ) + cg.add(var.add_event_matcher(event_name, match_data_var, len(match_data))) diff --git a/esphome/components/uart/event/uart_event.cpp b/esphome/components/uart/event/uart_event.cpp new file mode 100644 index 0000000000..02c5f2e631 --- /dev/null +++ b/esphome/components/uart/event/uart_event.cpp @@ -0,0 +1,48 @@ +#include "uart_event.h" +#include "esphome/core/log.h" +#include + +namespace esphome::uart { + +static const char *const TAG = "uart.event"; + +void UARTEvent::setup() {} + +void UARTEvent::dump_config() { LOG_EVENT("", "UART Event", this); } + +void UARTEvent::loop() { this->read_data_(); } + +void UARTEvent::add_event_matcher(const char *event_name, const uint8_t *match_data, size_t match_data_len) { + this->matchers_.push_back({event_name, match_data, match_data_len}); + if (match_data_len > this->max_matcher_len_) { + this->max_matcher_len_ = match_data_len; + } +} + +void UARTEvent::read_data_() { + while (this->available()) { + uint8_t data; + this->read_byte(&data); + this->buffer_.push_back(data); + + bool match_found = false; + for (const auto &matcher : this->matchers_) { + if (this->buffer_.size() < matcher.data_len) { + continue; + } + + if (std::equal(matcher.data, matcher.data + matcher.data_len, this->buffer_.end() - matcher.data_len)) { + this->trigger(matcher.event_name); + this->buffer_.clear(); + match_found = true; + break; + } + } + + if (!match_found && this->max_matcher_len_ > 0 && this->buffer_.size() > this->max_matcher_len_) { + this->buffer_.erase(this->buffer_.begin()); + } + } +} + +} // namespace esphome::uart diff --git a/esphome/components/uart/event/uart_event.h b/esphome/components/uart/event/uart_event.h new file mode 100644 index 0000000000..8a00b5894b --- /dev/null +++ b/esphome/components/uart/event/uart_event.h @@ -0,0 +1,31 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/event/event.h" +#include "esphome/components/uart/uart.h" +#include + +namespace esphome::uart { + +class UARTEvent : public event::Event, public UARTDevice, public Component { + public: + void setup() override; + void loop() override; + void dump_config() override; + + void add_event_matcher(const char *event_name, const uint8_t *match_data, size_t match_data_len); + + protected: + struct EventMatcher { + const char *event_name; + const uint8_t *data; + size_t data_len; + }; + + void read_data_(); + std::vector matchers_; + std::vector buffer_; + size_t max_matcher_len_ = 0; +}; + +} // namespace esphome::uart diff --git a/tests/components/uart/test.esp32-idf.yaml b/tests/components/uart/test.esp32-idf.yaml index 6ffd0d7282..2a97f9a5de 100644 --- a/tests/components/uart/test.esp32-idf.yaml +++ b/tests/components/uart/test.esp32-idf.yaml @@ -75,3 +75,11 @@ button: - uart.write: !lambda |- std::string cmd = "VALUE=" + str_sprintf("%.0f", id(test_number).state) + "\r\n"; return std::vector(cmd.begin(), cmd.end()); + +event: + - platform: uart + uart_id: uart_uart + name: "UART Event" + event_types: + - "string_event_A": "*A#" + - "bytes_event_B": [0x2A, 0x42, 0x23] diff --git a/tests/components/uart/test.esp8266-ard.yaml b/tests/components/uart/test.esp8266-ard.yaml index 566038ee3e..c2670b289a 100644 --- a/tests/components/uart/test.esp8266-ard.yaml +++ b/tests/components/uart/test.esp8266-ard.yaml @@ -31,3 +31,11 @@ button: name: "UART Button" uart_id: uart_uart data: [0xFF, 0xEE] + +event: + - platform: uart + uart_id: uart_uart + name: "UART Event" + event_types: + - "string_event_A": "*A#" + - "bytes_event_B": [0x2A, 0x42, 0x23] From b4c92dd8cbbf61810e876c3fbb3ca0ebaa199600 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 22 Dec 2025 14:29:47 -1000 Subject: [PATCH 02/20] [logger] Zephyr: Use k_str_out() with known length instead of printk() (#12619) --- esphome/components/logger/logger.h | 4 ++-- esphome/components/logger/logger_zephyr.cpp | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 36195b919a..2cedd7a76f 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -118,11 +118,11 @@ static constexpr uint16_t MAX_HEADER_SIZE = 128; 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) +// 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) +#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; diff --git a/esphome/components/logger/logger_zephyr.cpp b/esphome/components/logger/logger_zephyr.cpp index ec2ff3013c..330dfa96ec 100644 --- a/esphome/components/logger/logger_zephyr.cpp +++ b/esphome/components/logger/logger_zephyr.cpp @@ -6,6 +6,7 @@ #include #include +#include #include namespace esphome::logger { @@ -14,7 +15,7 @@ static const char *const TAG = "logger"; #ifdef USE_LOGGER_USB_CDC void Logger::loop() { - if (this->uart_ != UART_SELECTION_USB_CDC || nullptr == this->uart_dev_) { + if (this->uart_ != UART_SELECTION_USB_CDC || this->uart_dev_ == nullptr) { return; } static bool opened = false; @@ -62,18 +63,17 @@ void Logger::pre_setup() { ESP_LOGI(TAG, "Log initialized"); } -void HOT Logger::write_msg_(const char *msg, size_t) { +void HOT Logger::write_msg_(const char *msg, size_t len) { + // Single write with newline already in buffer (added by caller) #ifdef CONFIG_PRINTK - printk("%s\n", msg); + k_str_out(const_cast(msg), len); #endif - if (nullptr == this->uart_dev_) { + if (this->uart_dev_ == nullptr) { return; } - while (*msg) { - uart_poll_out(this->uart_dev_, *msg); - ++msg; + for (size_t i = 0; i < len; ++i) { + uart_poll_out(this->uart_dev_, msg[i]); } - uart_poll_out(this->uart_dev_, '\n'); } const LogString *Logger::get_uart_selection_() { From 7d5342bca5df37ba48e735342f72f3d615a01555 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 22 Dec 2025 16:45:22 -1000 Subject: [PATCH 03/20] [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 21e2b44808..474eb9ec38 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 2cedd7a76f..ba8d4667b6 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 c5e1e6f865..cbca06e431 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 04/20] [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 f98afd94a1..7e5309e165 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 05/20] [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 39dbeb225f..beb74d7310 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 1971817fb1..e314da7079 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 2ad48173f1..ca37cb392d 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 1404ef8ac3..2ddf4b9014 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 9e7c9d702c..7706484e97 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 2133c4daf9..7ce79aa514 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 1eb4869da3..28556514d8 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 56101c2d62..0e238ff7da 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 ac1892fa29..441a52de7f 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 4641db6483..ed878ed0d4 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 b569a75972..f3a9cca93f 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 06/20] [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 3b1a442bdb..41594b947c 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 4a3c40a119..380e4ea7fd 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 36003a6eb4..1012b0be6c 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 0228755432..c88aeb2d4f 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 07/20] [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 8224adf3fe..08f758045e 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 08/20] [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 a3322ba731..240b205158 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 81dd4ffc1c..0518731a6a 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 9bb5967248..4573391bc1 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 d37c798c05..a27fb1f537 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 09/20] 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 f5c20c891e..851fb30c22 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 10/20] [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 e314da7079..c205ff2f69 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 1fe402d6c6..f98afd94a1 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 11/20] [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 5080a6f32d..a3677330ca 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 54a7d6064a..a49fca6511 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 12/20] [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 f98afd94a1..7e5309e165 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 13/20] 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 4c533ec87f..d41459ea46 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 8f9a3497ff..41bb419aaf 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 14/20] Fix hash --- .clang-tidy.hash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.clang-tidy.hash b/.clang-tidy.hash index 240b205158..18379de92e 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 15/20] [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 fd74eabd87..2094c0e942 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 4b5d834ebf..88b357041a 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 eb1ba46c94..93f989934a 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); From 958a35e26279a302f6da4d0efd8b7191355b09d4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 Dec 2025 14:17:52 -1000 Subject: [PATCH 16/20] Bump aioesphomeapi from 43.5.0 to 43.6.0 (#12644) 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 5718ced617..e741a70f48 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile esptool==5.1.0 click==8.1.7 esphome-dashboard==20251013.0 -aioesphomeapi==43.5.0 +aioesphomeapi==43.6.0 zeroconf==0.148.0 puremagic==1.30 ruamel.yaml==0.18.17 # dashboard_import From 8b72c3c0efd6a8788cc243f164826dabbd738d27 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 24 Dec 2025 22:05:19 -1000 Subject: [PATCH 17/20] [api] Auto-generate StringRef for incoming API string fields --- esphome/components/api/api.proto | 22 +-- esphome/components/api/api_connection.cpp | 32 ++-- esphome/components/api/api_pb2.cpp | 169 +++++++++++++--------- esphome/components/api/api_pb2.h | 87 +++++------ esphome/components/api/api_pb2_dump.cpp | 98 +++++++++---- script/api_protobuf/api_protobuf.py | 101 ++++++++----- 6 files changed, 295 insertions(+), 214 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index c351bc8c9c..debea5808c 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -102,7 +102,7 @@ message HelloRequest { // For example "Home Assistant" // Not strictly necessary to send but nice for debugging // purposes. - string client_info = 1 [(pointer_to_buffer) = true]; + string client_info = 1; uint32 api_version_major = 2; uint32 api_version_minor = 3; } @@ -139,7 +139,7 @@ message AuthenticationRequest { option (ifdef) = "USE_API_PASSWORD"; // The password to log in with - string password = 1 [(pointer_to_buffer) = true]; + string password = 1; } // Confirmation of successful connection. After this the connection is available for all traffic. @@ -477,7 +477,7 @@ message FanCommandRequest { bool has_speed_level = 10; int32 speed_level = 11; bool has_preset_mode = 12; - string preset_mode = 13 [(pointer_to_buffer) = true]; + string preset_mode = 13; uint32 device_id = 14 [(field_ifdef) = "USE_DEVICES"]; } @@ -579,7 +579,7 @@ message LightCommandRequest { bool has_flash_length = 16; uint32 flash_length = 17; bool has_effect = 18; - string effect = 19 [(pointer_to_buffer) = true]; + string effect = 19; uint32 device_id = 28 [(field_ifdef) = "USE_DEVICES"]; } @@ -824,9 +824,9 @@ message HomeAssistantStateResponse { option (no_delay) = true; option (ifdef) = "USE_API_HOMEASSISTANT_STATES"; - string entity_id = 1 [(pointer_to_buffer) = true]; - string state = 2 [(pointer_to_buffer) = true]; - string attribute = 3 [(pointer_to_buffer) = true]; + string entity_id = 1; + string state = 2; + string attribute = 3; } // ==================== IMPORT TIME ==================== @@ -841,7 +841,7 @@ message GetTimeResponse { option (no_delay) = true; fixed32 epoch_seconds = 1; - string timezone = 2 [(pointer_to_buffer) = true]; + string timezone = 2; } // ==================== USER-DEFINES SERVICES ==================== @@ -1091,11 +1091,11 @@ message ClimateCommandRequest { bool has_swing_mode = 14; ClimateSwingMode swing_mode = 15; bool has_custom_fan_mode = 16; - string custom_fan_mode = 17 [(pointer_to_buffer) = true]; + string custom_fan_mode = 17; bool has_preset = 18; ClimatePreset preset = 19; bool has_custom_preset = 20; - string custom_preset = 21 [(pointer_to_buffer) = true]; + string custom_preset = 21; bool has_target_humidity = 22; float target_humidity = 23; uint32 device_id = 24 [(field_ifdef) = "USE_DEVICES"]; @@ -1274,7 +1274,7 @@ message SelectCommandRequest { option (base_class) = "CommandProtoMessage"; fixed32 key = 1; - string state = 2 [(pointer_to_buffer) = true]; + string state = 2; uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"]; } diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index b5628f654e..26ddb16e9a 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -473,7 +473,7 @@ void APIConnection::fan_command(const FanCommandRequest &msg) { if (msg.has_direction) call.set_direction(static_cast(msg.direction)); if (msg.has_preset_mode) - call.set_preset_mode(reinterpret_cast(msg.preset_mode), msg.preset_mode_len); + call.set_preset_mode(msg.preset_mode.c_str(), msg.preset_mode.size()); call.perform(); } #endif @@ -559,7 +559,7 @@ void APIConnection::light_command(const LightCommandRequest &msg) { if (msg.has_flash_length) call.set_flash_length(msg.flash_length); if (msg.has_effect) - call.set_effect(reinterpret_cast(msg.effect), msg.effect_len); + call.set_effect(msg.effect.c_str(), msg.effect.size()); call.perform(); } #endif @@ -738,11 +738,11 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) { if (msg.has_fan_mode) call.set_fan_mode(static_cast(msg.fan_mode)); if (msg.has_custom_fan_mode) - call.set_fan_mode(reinterpret_cast(msg.custom_fan_mode), msg.custom_fan_mode_len); + call.set_fan_mode(msg.custom_fan_mode.c_str(), msg.custom_fan_mode.size()); if (msg.has_preset) call.set_preset(static_cast(msg.preset)); if (msg.has_custom_preset) - call.set_preset(reinterpret_cast(msg.custom_preset), msg.custom_preset_len); + call.set_preset(msg.custom_preset.c_str(), msg.custom_preset.size()); if (msg.has_swing_mode) call.set_swing_mode(static_cast(msg.swing_mode)); call.perform(); @@ -931,7 +931,7 @@ uint16_t APIConnection::try_send_select_info(EntityBase *entity, APIConnection * } void APIConnection::select_command(const SelectCommandRequest &msg) { ENTITY_COMMAND_MAKE_CALL(select::Select, select, select) - call.set_option(reinterpret_cast(msg.state), msg.state_len); + call.set_option(msg.state.c_str(), msg.state.size()); call.perform(); } #endif @@ -1153,9 +1153,8 @@ void APIConnection::on_get_time_response(const GetTimeResponse &value) { if (homeassistant::global_homeassistant_time != nullptr) { homeassistant::global_homeassistant_time->set_epoch_time(value.epoch_seconds); #ifdef USE_TIME_TIMEZONE - if (value.timezone_len > 0) { - homeassistant::global_homeassistant_time->set_timezone(reinterpret_cast(value.timezone), - value.timezone_len); + if (!value.timezone.empty()) { + homeassistant::global_homeassistant_time->set_timezone(value.timezone.c_str(), value.timezone.size()); } #endif } @@ -1522,7 +1521,7 @@ void APIConnection::complete_authentication_() { } bool APIConnection::send_hello_response(const HelloRequest &msg) { - this->client_info_.name.assign(reinterpret_cast(msg.client_info), msg.client_info_len); + this->client_info_.name.assign(msg.client_info.c_str(), msg.client_info.size()); this->client_info_.peername = this->helper_->getpeername(); this->client_api_version_major_ = msg.api_version_major; this->client_api_version_minor_ = msg.api_version_minor; @@ -1550,7 +1549,7 @@ bool APIConnection::send_hello_response(const HelloRequest &msg) { bool APIConnection::send_authenticate_response(const AuthenticationRequest &msg) { AuthenticationResponse resp; // bool invalid_password = 1; - resp.invalid_password = !this->parent_->check_password(msg.password, msg.password_len); + resp.invalid_password = !this->parent_->check_password(msg.password.byte(), msg.password.size()); if (!resp.invalid_password) { this->complete_authentication_(); } @@ -1693,27 +1692,28 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) { #ifdef USE_API_HOMEASSISTANT_STATES void APIConnection::on_home_assistant_state_response(const HomeAssistantStateResponse &msg) { // Skip if entity_id is empty (invalid message) - if (msg.entity_id_len == 0) { + if (msg.entity_id.empty()) { return; } for (auto &it : this->parent_->get_state_subs()) { // Compare entity_id: check length matches and content matches size_t entity_id_len = strlen(it.entity_id); - if (entity_id_len != msg.entity_id_len || memcmp(it.entity_id, msg.entity_id, msg.entity_id_len) != 0) { + if (entity_id_len != msg.entity_id.size() || + memcmp(it.entity_id, msg.entity_id.c_str(), msg.entity_id.size()) != 0) { continue; } // Compare attribute: either both have matching attribute, or both have none size_t sub_attr_len = it.attribute != nullptr ? strlen(it.attribute) : 0; - if (sub_attr_len != msg.attribute_len || - (sub_attr_len > 0 && memcmp(it.attribute, msg.attribute, sub_attr_len) != 0)) { + if (sub_attr_len != msg.attribute.size() || + (sub_attr_len > 0 && memcmp(it.attribute, msg.attribute.c_str(), sub_attr_len) != 0)) { continue; } // Create temporary string for callback (callback takes const std::string &) - // Handle empty state (nullptr with len=0) - std::string state(msg.state_len > 0 ? reinterpret_cast(msg.state) : "", msg.state_len); + // Handle empty state + std::string state(!msg.state.empty() ? msg.state.c_str() : "", msg.state.size()); it.callback(state); } } diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 3376b022c5..058a722483 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -23,9 +23,8 @@ bool HelloRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 1: { - // Use raw data directly to avoid allocation - this->client_info = value.data(); - this->client_info_len = value.size(); + // Use raw data directly via StringRef to avoid allocation + this->client_info = StringRef(reinterpret_cast(value.data()), value.size()); break; } default: @@ -49,9 +48,8 @@ void HelloResponse::calculate_size(ProtoSize &size) const { bool AuthenticationRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 1: { - // Use raw data directly to avoid allocation - this->password = value.data(); - this->password_len = value.size(); + // Use raw data directly via StringRef to avoid allocation + this->password = StringRef(reinterpret_cast(value.data()), value.size()); break; } default: @@ -448,9 +446,8 @@ bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { bool FanCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 13: { - // Use raw data directly to avoid allocation - this->preset_mode = value.data(); - this->preset_mode_len = value.size(); + // Use raw data directly via StringRef to avoid allocation + this->preset_mode = StringRef(reinterpret_cast(value.data()), value.size()); break; } default: @@ -615,9 +612,8 @@ bool LightCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { bool LightCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 19: { - // Use raw data directly to avoid allocation - this->effect = value.data(); - this->effect_len = value.size(); + // Use raw data directly via StringRef to avoid allocation + this->effect = StringRef(reinterpret_cast(value.data()), value.size()); break; } default: @@ -936,9 +932,11 @@ bool HomeassistantActionResponse::decode_varint(uint32_t field_id, ProtoVarInt v } bool HomeassistantActionResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 3: - this->error_message = value.as_string(); + case 3: { + // Use raw data directly via StringRef to avoid allocation + this->error_message = StringRef(reinterpret_cast(value.data()), value.size()); break; + } #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON case 4: { // Use raw data directly to avoid allocation @@ -967,21 +965,18 @@ void SubscribeHomeAssistantStateResponse::calculate_size(ProtoSize &size) const bool HomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 1: { - // Use raw data directly to avoid allocation - this->entity_id = value.data(); - this->entity_id_len = value.size(); + // Use raw data directly via StringRef to avoid allocation + this->entity_id = StringRef(reinterpret_cast(value.data()), value.size()); break; } case 2: { - // Use raw data directly to avoid allocation - this->state = value.data(); - this->state_len = value.size(); + // Use raw data directly via StringRef to avoid allocation + this->state = StringRef(reinterpret_cast(value.data()), value.size()); break; } case 3: { - // Use raw data directly to avoid allocation - this->attribute = value.data(); - this->attribute_len = value.size(); + // Use raw data directly via StringRef to avoid allocation + this->attribute = StringRef(reinterpret_cast(value.data()), value.size()); break; } default: @@ -993,9 +988,8 @@ bool HomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDel bool GetTimeResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 2: { - // Use raw data directly to avoid allocation - this->timezone = value.data(); - this->timezone_len = value.size(); + // Use raw data directly via StringRef to avoid allocation + this->timezone = StringRef(reinterpret_cast(value.data()), value.size()); break; } default: @@ -1060,9 +1054,11 @@ bool ExecuteServiceArgument::decode_varint(uint32_t field_id, ProtoVarInt value) } bool ExecuteServiceArgument::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 4: - this->string_ = value.as_string(); + case 4: { + // Use raw data directly via StringRef to avoid allocation + this->string_ = StringRef(reinterpret_cast(value.data()), value.size()); break; + } case 9: this->string_array.push_back(value.as_string()); break; @@ -1408,15 +1404,13 @@ bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) bool ClimateCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 17: { - // Use raw data directly to avoid allocation - this->custom_fan_mode = value.data(); - this->custom_fan_mode_len = value.size(); + // Use raw data directly via StringRef to avoid allocation + this->custom_fan_mode = StringRef(reinterpret_cast(value.data()), value.size()); break; } case 21: { - // Use raw data directly to avoid allocation - this->custom_preset = value.data(); - this->custom_preset_len = value.size(); + // Use raw data directly via StringRef to avoid allocation + this->custom_preset = StringRef(reinterpret_cast(value.data()), value.size()); break; } default: @@ -1702,9 +1696,8 @@ bool SelectCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { bool SelectCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 2: { - // Use raw data directly to avoid allocation - this->state = value.data(); - this->state_len = value.size(); + // Use raw data directly via StringRef to avoid allocation + this->state = StringRef(reinterpret_cast(value.data()), value.size()); break; } default: @@ -1808,9 +1801,11 @@ bool SirenCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { } bool SirenCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 5: - this->tone = value.as_string(); + case 5: { + // Use raw data directly via StringRef to avoid allocation + this->tone = StringRef(reinterpret_cast(value.data()), value.size()); break; + } default: return false; } @@ -1899,9 +1894,11 @@ bool LockCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { } bool LockCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 4: - this->code = value.as_string(); + case 4: { + // Use raw data directly via StringRef to avoid allocation + this->code = StringRef(reinterpret_cast(value.data()), value.size()); break; + } default: return false; } @@ -2069,9 +2066,11 @@ bool MediaPlayerCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt val } bool MediaPlayerCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 7: - this->media_url = value.as_string(); + case 7: { + // Use raw data directly via StringRef to avoid allocation + this->media_url = StringRef(reinterpret_cast(value.data()), value.size()); break; + } default: return false; } @@ -2502,12 +2501,16 @@ bool VoiceAssistantResponse::decode_varint(uint32_t field_id, ProtoVarInt value) } bool VoiceAssistantEventData::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 1: - this->name = value.as_string(); + case 1: { + // Use raw data directly via StringRef to avoid allocation + this->name = StringRef(reinterpret_cast(value.data()), value.size()); break; - case 2: - this->value = value.as_string(); + } + case 2: { + // Use raw data directly via StringRef to avoid allocation + this->value = StringRef(reinterpret_cast(value.data()), value.size()); break; + } default: return false; } @@ -2583,12 +2586,16 @@ bool VoiceAssistantTimerEventResponse::decode_varint(uint32_t field_id, ProtoVar } bool VoiceAssistantTimerEventResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 2: - this->timer_id = value.as_string(); + case 2: { + // Use raw data directly via StringRef to avoid allocation + this->timer_id = StringRef(reinterpret_cast(value.data()), value.size()); break; - case 3: - this->name = value.as_string(); + } + case 3: { + // Use raw data directly via StringRef to avoid allocation + this->name = StringRef(reinterpret_cast(value.data()), value.size()); break; + } default: return false; } @@ -2606,15 +2613,21 @@ bool VoiceAssistantAnnounceRequest::decode_varint(uint32_t field_id, ProtoVarInt } bool VoiceAssistantAnnounceRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 1: - this->media_id = value.as_string(); + case 1: { + // Use raw data directly via StringRef to avoid allocation + this->media_id = StringRef(reinterpret_cast(value.data()), value.size()); break; - case 2: - this->text = value.as_string(); + } + case 2: { + // Use raw data directly via StringRef to avoid allocation + this->text = StringRef(reinterpret_cast(value.data()), value.size()); break; - case 3: - this->preannounce_media_id = value.as_string(); + } + case 3: { + // Use raw data directly via StringRef to avoid allocation + this->preannounce_media_id = StringRef(reinterpret_cast(value.data()), value.size()); break; + } default: return false; } @@ -2650,24 +2663,34 @@ bool VoiceAssistantExternalWakeWord::decode_varint(uint32_t field_id, ProtoVarIn } bool VoiceAssistantExternalWakeWord::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 1: - this->id = value.as_string(); + case 1: { + // Use raw data directly via StringRef to avoid allocation + this->id = StringRef(reinterpret_cast(value.data()), value.size()); break; - case 2: - this->wake_word = value.as_string(); + } + case 2: { + // Use raw data directly via StringRef to avoid allocation + this->wake_word = StringRef(reinterpret_cast(value.data()), value.size()); break; + } case 3: this->trained_languages.push_back(value.as_string()); break; - case 4: - this->model_type = value.as_string(); + case 4: { + // Use raw data directly via StringRef to avoid allocation + this->model_type = StringRef(reinterpret_cast(value.data()), value.size()); break; - case 6: - this->model_hash = value.as_string(); + } + case 6: { + // Use raw data directly via StringRef to avoid allocation + this->model_hash = StringRef(reinterpret_cast(value.data()), value.size()); break; - case 7: - this->url = value.as_string(); + } + case 7: { + // Use raw data directly via StringRef to avoid allocation + this->url = StringRef(reinterpret_cast(value.data()), value.size()); break; + } default: return false; } @@ -2777,9 +2800,11 @@ bool AlarmControlPanelCommandRequest::decode_varint(uint32_t field_id, ProtoVarI } bool AlarmControlPanelCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 3: - this->code = value.as_string(); + case 3: { + // Use raw data directly via StringRef to avoid allocation + this->code = StringRef(reinterpret_cast(value.data()), value.size()); break; + } default: return false; } @@ -2861,9 +2886,11 @@ bool TextCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { } bool TextCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 2: - this->state = value.as_string(); + case 2: { + // Use raw data directly via StringRef to avoid allocation + this->state = StringRef(reinterpret_cast(value.data()), value.size()); break; + } default: return false; } diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 2111c2a895..9d7a1eb9cb 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -357,12 +357,11 @@ class CommandProtoMessage : public ProtoDecodableMessage { class HelloRequest final : public ProtoDecodableMessage { public: static constexpr uint8_t MESSAGE_TYPE = 1; - static constexpr uint8_t ESTIMATED_SIZE = 27; + static constexpr uint8_t ESTIMATED_SIZE = 17; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "hello_request"; } #endif - const uint8_t *client_info{nullptr}; - uint16_t client_info_len{0}; + StringRef client_info{}; uint32_t api_version_major{0}; uint32_t api_version_minor{0}; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -398,12 +397,11 @@ class HelloResponse final : public ProtoMessage { class AuthenticationRequest final : public ProtoDecodableMessage { public: static constexpr uint8_t MESSAGE_TYPE = 3; - static constexpr uint8_t ESTIMATED_SIZE = 19; + static constexpr uint8_t ESTIMATED_SIZE = 9; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "authentication_request"; } #endif - const uint8_t *password{nullptr}; - uint16_t password_len{0}; + StringRef password{}; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -784,7 +782,7 @@ class FanStateResponse final : public StateResponseProtoMessage { class FanCommandRequest final : public CommandProtoMessage { public: static constexpr uint8_t MESSAGE_TYPE = 31; - static constexpr uint8_t ESTIMATED_SIZE = 48; + static constexpr uint8_t ESTIMATED_SIZE = 38; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "fan_command_request"; } #endif @@ -797,8 +795,7 @@ class FanCommandRequest final : public CommandProtoMessage { bool has_speed_level{false}; int32_t speed_level{0}; bool has_preset_mode{false}; - const uint8_t *preset_mode{nullptr}; - uint16_t preset_mode_len{0}; + StringRef preset_mode{}; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -860,7 +857,7 @@ class LightStateResponse final : public StateResponseProtoMessage { class LightCommandRequest final : public CommandProtoMessage { public: static constexpr uint8_t MESSAGE_TYPE = 32; - static constexpr uint8_t ESTIMATED_SIZE = 122; + static constexpr uint8_t ESTIMATED_SIZE = 112; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "light_command_request"; } #endif @@ -889,8 +886,7 @@ class LightCommandRequest final : public CommandProtoMessage { bool has_flash_length{false}; uint32_t flash_length{0}; bool has_effect{false}; - const uint8_t *effect{nullptr}; - uint16_t effect_len{0}; + StringRef effect{}; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1171,7 +1167,7 @@ class HomeassistantActionResponse final : public ProtoDecodableMessage { #endif uint32_t call_id{0}; bool success{false}; - std::string error_message{}; + StringRef error_message{}; #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON const uint8_t *response_data{nullptr}; uint16_t response_data_len{0}; @@ -1222,16 +1218,13 @@ class SubscribeHomeAssistantStateResponse final : public ProtoMessage { class HomeAssistantStateResponse final : public ProtoDecodableMessage { public: static constexpr uint8_t MESSAGE_TYPE = 40; - static constexpr uint8_t ESTIMATED_SIZE = 57; + static constexpr uint8_t ESTIMATED_SIZE = 27; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "home_assistant_state_response"; } #endif - const uint8_t *entity_id{nullptr}; - uint16_t entity_id_len{0}; - const uint8_t *state{nullptr}; - uint16_t state_len{0}; - const uint8_t *attribute{nullptr}; - uint16_t attribute_len{0}; + StringRef entity_id{}; + StringRef state{}; + StringRef attribute{}; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1256,13 +1249,12 @@ class GetTimeRequest final : public ProtoMessage { class GetTimeResponse final : public ProtoDecodableMessage { public: static constexpr uint8_t MESSAGE_TYPE = 37; - static constexpr uint8_t ESTIMATED_SIZE = 24; + static constexpr uint8_t ESTIMATED_SIZE = 14; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "get_time_response"; } #endif uint32_t epoch_seconds{0}; - const uint8_t *timezone{nullptr}; - uint16_t timezone_len{0}; + StringRef timezone{}; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1310,7 +1302,7 @@ class ExecuteServiceArgument final : public ProtoDecodableMessage { bool bool_{false}; int32_t legacy_int{0}; float float_{0.0f}; - std::string string_{}; + StringRef string_{}; int32_t int_{0}; FixedVector bool_array{}; FixedVector int_array{}; @@ -1499,7 +1491,7 @@ class ClimateStateResponse final : public StateResponseProtoMessage { class ClimateCommandRequest final : public CommandProtoMessage { public: static constexpr uint8_t MESSAGE_TYPE = 48; - static constexpr uint8_t ESTIMATED_SIZE = 104; + static constexpr uint8_t ESTIMATED_SIZE = 84; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "climate_command_request"; } #endif @@ -1516,13 +1508,11 @@ class ClimateCommandRequest final : public CommandProtoMessage { bool has_swing_mode{false}; enums::ClimateSwingMode swing_mode{}; bool has_custom_fan_mode{false}; - const uint8_t *custom_fan_mode{nullptr}; - uint16_t custom_fan_mode_len{0}; + StringRef custom_fan_mode{}; bool has_preset{false}; enums::ClimatePreset preset{}; bool has_custom_preset{false}; - const uint8_t *custom_preset{nullptr}; - uint16_t custom_preset_len{0}; + StringRef custom_preset{}; bool has_target_humidity{false}; float target_humidity{0.0f}; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -1695,12 +1685,11 @@ class SelectStateResponse final : public StateResponseProtoMessage { class SelectCommandRequest final : public CommandProtoMessage { public: static constexpr uint8_t MESSAGE_TYPE = 54; - static constexpr uint8_t ESTIMATED_SIZE = 28; + static constexpr uint8_t ESTIMATED_SIZE = 18; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "select_command_request"; } #endif - const uint8_t *state{nullptr}; - uint16_t state_len{0}; + StringRef state{}; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1756,7 +1745,7 @@ class SirenCommandRequest final : public CommandProtoMessage { bool has_state{false}; bool state{false}; bool has_tone{false}; - std::string tone{}; + StringRef tone{}; bool has_duration{false}; uint32_t duration{0}; bool has_volume{false}; @@ -1817,7 +1806,7 @@ class LockCommandRequest final : public CommandProtoMessage { #endif enums::LockCommand command{}; bool has_code{false}; - std::string code{}; + StringRef code{}; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1927,7 +1916,7 @@ class MediaPlayerCommandRequest final : public CommandProtoMessage { bool has_volume{false}; float volume{0.0f}; bool has_media_url{false}; - std::string media_url{}; + StringRef media_url{}; bool has_announcement{false}; bool announcement{false}; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -2503,8 +2492,8 @@ class VoiceAssistantResponse final : public ProtoDecodableMessage { }; class VoiceAssistantEventData final : public ProtoDecodableMessage { public: - std::string name{}; - std::string value{}; + StringRef name{}; + StringRef value{}; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2562,8 +2551,8 @@ class VoiceAssistantTimerEventResponse final : public ProtoDecodableMessage { const char *message_name() const override { return "voice_assistant_timer_event_response"; } #endif enums::VoiceAssistantTimerEvent event_type{}; - std::string timer_id{}; - std::string name{}; + StringRef timer_id{}; + StringRef name{}; uint32_t total_seconds{0}; uint32_t seconds_left{0}; bool is_active{false}; @@ -2582,9 +2571,9 @@ class VoiceAssistantAnnounceRequest final : public ProtoDecodableMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "voice_assistant_announce_request"; } #endif - std::string media_id{}; - std::string text{}; - std::string preannounce_media_id{}; + StringRef media_id{}; + StringRef text{}; + StringRef preannounce_media_id{}; bool start_conversation{false}; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -2627,13 +2616,13 @@ class VoiceAssistantWakeWord final : public ProtoMessage { }; class VoiceAssistantExternalWakeWord final : public ProtoDecodableMessage { public: - std::string id{}; - std::string wake_word{}; + StringRef id{}; + StringRef wake_word{}; std::vector trained_languages{}; - std::string model_type{}; + StringRef model_type{}; uint32_t model_size{0}; - std::string model_hash{}; - std::string url{}; + StringRef model_hash{}; + StringRef url{}; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2734,7 +2723,7 @@ class AlarmControlPanelCommandRequest final : public CommandProtoMessage { const char *message_name() const override { return "alarm_control_panel_command_request"; } #endif enums::AlarmControlPanelStateCommand command{}; - std::string code{}; + StringRef code{}; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2791,7 +2780,7 @@ class TextCommandRequest final : public CommandProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "text_command_request"; } #endif - std::string state{}; + StringRef state{}; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif diff --git a/esphome/components/api/api_pb2_dump.cpp b/esphome/components/api/api_pb2_dump.cpp index 9faf39e29e..567f10fcc0 100644 --- a/esphome/components/api/api_pb2_dump.cpp +++ b/esphome/components/api/api_pb2_dump.cpp @@ -736,7 +736,7 @@ template<> const char *proto_enum_to_string(enums: void HelloRequest::dump_to(std::string &out) const { MessageDumpHelper helper(out, "HelloRequest"); out.append(" client_info: "); - out.append(format_hex_pretty(this->client_info, this->client_info_len)); + out.append("'").append(this->client_info.c_str(), this->client_info.size()).append("'"); out.append("\n"); dump_field(out, "api_version_major", this->api_version_major); dump_field(out, "api_version_minor", this->api_version_minor); @@ -752,7 +752,7 @@ void HelloResponse::dump_to(std::string &out) const { void AuthenticationRequest::dump_to(std::string &out) const { MessageDumpHelper helper(out, "AuthenticationRequest"); out.append(" password: "); - out.append(format_hex_pretty(this->password, this->password_len)); + out.append("'").append(this->password.c_str(), this->password.size()).append("'"); out.append("\n"); } void AuthenticationResponse::dump_to(std::string &out) const { @@ -965,7 +965,7 @@ void FanCommandRequest::dump_to(std::string &out) const { dump_field(out, "speed_level", this->speed_level); dump_field(out, "has_preset_mode", this->has_preset_mode); out.append(" preset_mode: "); - out.append(format_hex_pretty(this->preset_mode, this->preset_mode_len)); + out.append("'").append(this->preset_mode.c_str(), this->preset_mode.size()).append("'"); out.append("\n"); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); @@ -1043,7 +1043,7 @@ void LightCommandRequest::dump_to(std::string &out) const { dump_field(out, "flash_length", this->flash_length); dump_field(out, "has_effect", this->has_effect); out.append(" effect: "); - out.append(format_hex_pretty(this->effect, this->effect_len)); + out.append("'").append(this->effect.c_str(), this->effect.size()).append("'"); out.append("\n"); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); @@ -1205,7 +1205,9 @@ void HomeassistantActionResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "HomeassistantActionResponse"); dump_field(out, "call_id", this->call_id); dump_field(out, "success", this->success); - dump_field(out, "error_message", this->error_message); + out.append(" error_message: "); + out.append("'").append(this->error_message.c_str(), this->error_message.size()).append("'"); + out.append("\n"); #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON out.append(" response_data: "); out.append(format_hex_pretty(this->response_data, this->response_data_len)); @@ -1226,13 +1228,13 @@ void SubscribeHomeAssistantStateResponse::dump_to(std::string &out) const { void HomeAssistantStateResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "HomeAssistantStateResponse"); out.append(" entity_id: "); - out.append(format_hex_pretty(this->entity_id, this->entity_id_len)); + out.append("'").append(this->entity_id.c_str(), this->entity_id.size()).append("'"); out.append("\n"); out.append(" state: "); - out.append(format_hex_pretty(this->state, this->state_len)); + out.append("'").append(this->state.c_str(), this->state.size()).append("'"); out.append("\n"); out.append(" attribute: "); - out.append(format_hex_pretty(this->attribute, this->attribute_len)); + out.append("'").append(this->attribute.c_str(), this->attribute.size()).append("'"); out.append("\n"); } #endif @@ -1241,7 +1243,7 @@ void GetTimeResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "GetTimeResponse"); dump_field(out, "epoch_seconds", this->epoch_seconds); out.append(" timezone: "); - out.append(format_hex_pretty(this->timezone, this->timezone_len)); + out.append("'").append(this->timezone.c_str(), this->timezone.size()).append("'"); out.append("\n"); } #ifdef USE_API_USER_DEFINED_ACTIONS @@ -1266,7 +1268,9 @@ void ExecuteServiceArgument::dump_to(std::string &out) const { dump_field(out, "bool_", this->bool_); dump_field(out, "legacy_int", this->legacy_int); dump_field(out, "float_", this->float_); - dump_field(out, "string_", this->string_); + out.append(" string_: "); + out.append("'").append(this->string_.c_str(), this->string_.size()).append("'"); + out.append("\n"); dump_field(out, "int_", this->int_); for (const auto it : this->bool_array) { dump_field(out, "bool_array", static_cast(it), 4); @@ -1424,13 +1428,13 @@ void ClimateCommandRequest::dump_to(std::string &out) const { dump_field(out, "swing_mode", static_cast(this->swing_mode)); dump_field(out, "has_custom_fan_mode", this->has_custom_fan_mode); out.append(" custom_fan_mode: "); - out.append(format_hex_pretty(this->custom_fan_mode, this->custom_fan_mode_len)); + out.append("'").append(this->custom_fan_mode.c_str(), this->custom_fan_mode.size()).append("'"); out.append("\n"); dump_field(out, "has_preset", this->has_preset); dump_field(out, "preset", static_cast(this->preset)); dump_field(out, "has_custom_preset", this->has_custom_preset); out.append(" custom_preset: "); - out.append(format_hex_pretty(this->custom_preset, this->custom_preset_len)); + out.append("'").append(this->custom_preset.c_str(), this->custom_preset.size()).append("'"); out.append("\n"); dump_field(out, "has_target_humidity", this->has_target_humidity); dump_field(out, "target_humidity", this->target_humidity); @@ -1558,7 +1562,7 @@ void SelectCommandRequest::dump_to(std::string &out) const { MessageDumpHelper helper(out, "SelectCommandRequest"); dump_field(out, "key", this->key); out.append(" state: "); - out.append(format_hex_pretty(this->state, this->state_len)); + out.append("'").append(this->state.c_str(), this->state.size()).append("'"); out.append("\n"); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); @@ -1599,7 +1603,9 @@ void SirenCommandRequest::dump_to(std::string &out) const { dump_field(out, "has_state", this->has_state); dump_field(out, "state", this->state); dump_field(out, "has_tone", this->has_tone); - dump_field(out, "tone", this->tone); + out.append(" tone: "); + out.append("'").append(this->tone.c_str(), this->tone.size()).append("'"); + out.append("\n"); dump_field(out, "has_duration", this->has_duration); dump_field(out, "duration", this->duration); dump_field(out, "has_volume", this->has_volume); @@ -1641,7 +1647,9 @@ void LockCommandRequest::dump_to(std::string &out) const { dump_field(out, "key", this->key); dump_field(out, "command", static_cast(this->command)); dump_field(out, "has_code", this->has_code); - dump_field(out, "code", this->code); + out.append(" code: "); + out.append("'").append(this->code.c_str(), this->code.size()).append("'"); + out.append("\n"); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif @@ -1719,7 +1727,9 @@ void MediaPlayerCommandRequest::dump_to(std::string &out) const { dump_field(out, "has_volume", this->has_volume); dump_field(out, "volume", this->volume); dump_field(out, "has_media_url", this->has_media_url); - dump_field(out, "media_url", this->media_url); + out.append(" media_url: "); + out.append("'").append(this->media_url.c_str(), this->media_url.size()).append("'"); + out.append("\n"); dump_field(out, "has_announcement", this->has_announcement); dump_field(out, "announcement", this->announcement); #ifdef USE_DEVICES @@ -1949,8 +1959,12 @@ void VoiceAssistantResponse::dump_to(std::string &out) const { } void VoiceAssistantEventData::dump_to(std::string &out) const { MessageDumpHelper helper(out, "VoiceAssistantEventData"); - dump_field(out, "name", this->name); - dump_field(out, "value", this->value); + out.append(" name: "); + out.append("'").append(this->name.c_str(), this->name.size()).append("'"); + out.append("\n"); + out.append(" value: "); + out.append("'").append(this->value.c_str(), this->value.size()).append("'"); + out.append("\n"); } void VoiceAssistantEventResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "VoiceAssistantEventResponse"); @@ -1975,17 +1989,27 @@ void VoiceAssistantAudio::dump_to(std::string &out) const { void VoiceAssistantTimerEventResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "VoiceAssistantTimerEventResponse"); dump_field(out, "event_type", static_cast(this->event_type)); - dump_field(out, "timer_id", this->timer_id); - dump_field(out, "name", this->name); + out.append(" timer_id: "); + out.append("'").append(this->timer_id.c_str(), this->timer_id.size()).append("'"); + out.append("\n"); + out.append(" name: "); + out.append("'").append(this->name.c_str(), this->name.size()).append("'"); + out.append("\n"); dump_field(out, "total_seconds", this->total_seconds); dump_field(out, "seconds_left", this->seconds_left); dump_field(out, "is_active", this->is_active); } void VoiceAssistantAnnounceRequest::dump_to(std::string &out) const { MessageDumpHelper helper(out, "VoiceAssistantAnnounceRequest"); - dump_field(out, "media_id", this->media_id); - dump_field(out, "text", this->text); - dump_field(out, "preannounce_media_id", this->preannounce_media_id); + out.append(" media_id: "); + out.append("'").append(this->media_id.c_str(), this->media_id.size()).append("'"); + out.append("\n"); + out.append(" text: "); + out.append("'").append(this->text.c_str(), this->text.size()).append("'"); + out.append("\n"); + out.append(" preannounce_media_id: "); + out.append("'").append(this->preannounce_media_id.c_str(), this->preannounce_media_id.size()).append("'"); + out.append("\n"); dump_field(out, "start_conversation", this->start_conversation); } void VoiceAssistantAnnounceFinished::dump_to(std::string &out) const { dump_field(out, "success", this->success); } @@ -1999,15 +2023,25 @@ void VoiceAssistantWakeWord::dump_to(std::string &out) const { } void VoiceAssistantExternalWakeWord::dump_to(std::string &out) const { MessageDumpHelper helper(out, "VoiceAssistantExternalWakeWord"); - dump_field(out, "id", this->id); - dump_field(out, "wake_word", this->wake_word); + out.append(" id: "); + out.append("'").append(this->id.c_str(), this->id.size()).append("'"); + out.append("\n"); + out.append(" wake_word: "); + out.append("'").append(this->wake_word.c_str(), this->wake_word.size()).append("'"); + out.append("\n"); for (const auto &it : this->trained_languages) { dump_field(out, "trained_languages", it, 4); } - dump_field(out, "model_type", this->model_type); + out.append(" model_type: "); + out.append("'").append(this->model_type.c_str(), this->model_type.size()).append("'"); + out.append("\n"); dump_field(out, "model_size", this->model_size); - dump_field(out, "model_hash", this->model_hash); - dump_field(out, "url", this->url); + out.append(" model_hash: "); + out.append("'").append(this->model_hash.c_str(), this->model_hash.size()).append("'"); + out.append("\n"); + out.append(" url: "); + out.append("'").append(this->url.c_str(), this->url.size()).append("'"); + out.append("\n"); } void VoiceAssistantConfigurationRequest::dump_to(std::string &out) const { MessageDumpHelper helper(out, "VoiceAssistantConfigurationRequest"); @@ -2066,7 +2100,9 @@ void AlarmControlPanelCommandRequest::dump_to(std::string &out) const { MessageDumpHelper helper(out, "AlarmControlPanelCommandRequest"); dump_field(out, "key", this->key); dump_field(out, "command", static_cast(this->command)); - dump_field(out, "code", this->code); + out.append(" code: "); + out.append("'").append(this->code.c_str(), this->code.size()).append("'"); + out.append("\n"); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif @@ -2103,7 +2139,9 @@ void TextStateResponse::dump_to(std::string &out) const { void TextCommandRequest::dump_to(std::string &out) const { MessageDumpHelper helper(out, "TextCommandRequest"); dump_field(out, "key", this->key); - dump_field(out, "state", this->state); + out.append(" state: "); + out.append("'").append(this->state.c_str(), this->state.size()).append("'"); + out.append("\n"); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index cb09ef7050..89f9ecaf4a 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -374,20 +374,16 @@ def create_field_type_info( # Traditional fixed array approach with copy return FixedArrayBytesType(field, fixed_size) - # Check for pointer_to_buffer option on string fields - if field.type == 9: - has_pointer_to_buffer = get_field_opt(field, pb.pointer_to_buffer, False) - - if has_pointer_to_buffer: - # Zero-copy pointer approach for strings - return PointerToBytesBufferType(field, None) - # Special handling for bytes fields if field.type == 12: return BytesType(field, needs_decode, needs_encode) # Special handling for string fields if field.type == 9: + # For SOURCE_CLIENT only messages (decode but no encode), use StringRef + # for zero-copy access to the receive buffer + if needs_decode and not needs_encode: + return PointerToStringBufferType(field, None) return StringType(field, needs_decode, needs_encode) validate_field_type(field.type, field.name) @@ -840,8 +836,8 @@ class BytesType(TypeInfo): return self.calculate_field_id_size() + 8 # field ID + 8 bytes typical bytes -class PointerToBytesBufferType(TypeInfo): - """Type for bytes fields that use pointer_to_buffer option for zero-copy.""" +class PointerToBufferTypeBase(TypeInfo): + """Base class for pointer_to_buffer types (bytes and strings) for zero-copy decoding.""" @classmethod def can_use_dump_field(cls) -> bool: @@ -851,29 +847,34 @@ class PointerToBytesBufferType(TypeInfo): self, field: descriptor.FieldDescriptorProto, size: int | None = None ) -> None: super().__init__(field) - # Size is not used for pointer_to_buffer - we always use size_t for length self.array_size = 0 @property - def cpp_type(self) -> str: - return "const uint8_t*" + def decode_length(self) -> str | None: + # This is handled in decode_length_content + return None @property - def default_value(self) -> str: - return "nullptr" + def wire_type(self) -> WireType: + """Get the wire type for this field.""" + return WireType.LENGTH_DELIMITED # Uses wire type 2 - @property - def reference_type(self) -> str: - return "const uint8_t*" + def get_estimated_size(self) -> int: + # field ID + length varint + typical data (assume small for pointer fields) + return self.calculate_field_id_size() + 2 + 16 - @property - def const_reference_type(self) -> str: - return "const uint8_t*" + +class PointerToBytesBufferType(PointerToBufferTypeBase): + """Type for bytes fields that use pointer_to_buffer option for zero-copy.""" + + cpp_type = "const uint8_t*" + default_value = "nullptr" + reference_type = "const uint8_t*" + const_reference_type = "const uint8_t*" @property def public_content(self) -> list[str]: # Use uint16_t for length - max packet size is well below 65535 - # Add pointer and length fields return [ f"const uint8_t* {self.field_name}{{nullptr}};", f"uint16_t {self.field_name}_len{{0}};", @@ -885,7 +886,6 @@ class PointerToBytesBufferType(TypeInfo): @property def decode_length_content(self) -> str | None: - # Decode directly stores the pointer to avoid allocation return f"""case {self.number}: {{ // Use raw data directly to avoid allocation this->{self.field_name} = value.data(); @@ -893,16 +893,6 @@ class PointerToBytesBufferType(TypeInfo): break; }}""" - @property - def decode_length(self) -> str | None: - # This is handled in decode_length_content - return None - - @property - def wire_type(self) -> WireType: - """Get the wire type for this bytes field.""" - return WireType.LENGTH_DELIMITED # Uses wire type 2 - def dump(self, name: str) -> str: return ( f"format_hex_pretty(this->{self.field_name}, this->{self.field_name}_len)" @@ -910,7 +900,6 @@ class PointerToBytesBufferType(TypeInfo): @property def dump_content(self) -> str: - # Custom dump that doesn't use dump_field template return ( f'out.append(" {self.name}: ");\n' + f"out.append({self.dump(self.field_name)});\n" @@ -920,9 +909,47 @@ class PointerToBytesBufferType(TypeInfo): def get_size_calculation(self, name: str, force: bool = False) -> str: return f"size.add_length({self.number}, this->{self.field_name}_len);" - def get_estimated_size(self) -> int: - # field ID + length varint + typical data (assume small for pointer fields) - return self.calculate_field_id_size() + 2 + 16 + +class PointerToStringBufferType(PointerToBufferTypeBase): + """Type for string fields that use pointer_to_buffer option for zero-copy. + + Uses StringRef instead of separate pointer and length fields. + """ + + cpp_type = "StringRef" + default_value = "" + reference_type = "StringRef &" + const_reference_type = "const StringRef &" + + @property + def public_content(self) -> list[str]: + return [f"StringRef {self.field_name}{{}};"] + + @property + def encode_content(self) -> str: + return f"buffer.encode_string({self.number}, this->{self.field_name});" + + @property + def decode_length_content(self) -> str | None: + return f"""case {self.number}: {{ + // Use raw data directly via StringRef to avoid allocation + this->{self.field_name} = StringRef(reinterpret_cast(value.data()), value.size()); + break; + }}""" + + def dump(self, name: str) -> str: + return f'out.append("\'").append(this->{self.field_name}.c_str(), this->{self.field_name}.size()).append("\'");' + + @property + def dump_content(self) -> str: + return ( + f'out.append(" {self.name}: ");\n' + + f"{self.dump(self.field_name)}\n" + + 'out.append("\\n");' + ) + + def get_size_calculation(self, name: str, force: bool = False) -> str: + return f"size.add_length({self.calculate_field_id_size()}, this->{self.field_name}.size());" class FixedArrayBytesType(TypeInfo): From 7f4fad74c2801169ba51365139a39d8aea6782ce Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 24 Dec 2025 22:07:35 -1000 Subject: [PATCH 18/20] fixes --- esphome/components/api/api_pb2.cpp | 4 ++-- script/api_protobuf/api_protobuf.py | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 058a722483..1c7012370b 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -1149,7 +1149,7 @@ void ExecuteServiceResponse::calculate_size(ProtoSize &size) const { size.add_bool(1, this->success); size.add_length(1, this->error_message_ref_.size()); #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON - size.add_length(4, this->response_data_len); + size.add_length(1, this->response_data_len); #endif } #endif @@ -3399,7 +3399,7 @@ void ZWaveProxyRequest::encode(ProtoWriteBuffer buffer) const { } void ZWaveProxyRequest::calculate_size(ProtoSize &size) const { size.add_uint32(1, static_cast(this->type)); - size.add_length(2, this->data_len); + size.add_length(1, this->data_len); } #endif diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index 89f9ecaf4a..af7d6af954 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -907,7 +907,7 @@ class PointerToBytesBufferType(PointerToBufferTypeBase): ) def get_size_calculation(self, name: str, force: bool = False) -> str: - return f"size.add_length({self.number}, this->{self.field_name}_len);" + return f"size.add_length({self.calculate_field_id_size()}, this->{self.field_name}_len);" class PointerToStringBufferType(PointerToBufferTypeBase): @@ -932,7 +932,6 @@ class PointerToStringBufferType(PointerToBufferTypeBase): @property def decode_length_content(self) -> str | None: return f"""case {self.number}: {{ - // Use raw data directly via StringRef to avoid allocation this->{self.field_name} = StringRef(reinterpret_cast(value.data()), value.size()); break; }}""" From 0e9aaf1a8b842cc491c1fa2055805621476bdd1b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 24 Dec 2025 22:07:48 -1000 Subject: [PATCH 19/20] fixes --- esphome/components/api/api_pb2.cpp | 36 ----------------------------- script/api_protobuf/api_protobuf.py | 1 - 2 files changed, 37 deletions(-) diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 1c7012370b..edd6dfc6a9 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -23,7 +23,6 @@ bool HelloRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 1: { - // Use raw data directly via StringRef to avoid allocation this->client_info = StringRef(reinterpret_cast(value.data()), value.size()); break; } @@ -48,7 +47,6 @@ void HelloResponse::calculate_size(ProtoSize &size) const { bool AuthenticationRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 1: { - // Use raw data directly via StringRef to avoid allocation this->password = StringRef(reinterpret_cast(value.data()), value.size()); break; } @@ -446,7 +444,6 @@ bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { bool FanCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 13: { - // Use raw data directly via StringRef to avoid allocation this->preset_mode = StringRef(reinterpret_cast(value.data()), value.size()); break; } @@ -612,7 +609,6 @@ bool LightCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { bool LightCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 19: { - // Use raw data directly via StringRef to avoid allocation this->effect = StringRef(reinterpret_cast(value.data()), value.size()); break; } @@ -855,7 +851,6 @@ void SubscribeLogsResponse::calculate_size(ProtoSize &size) const { bool NoiseEncryptionSetKeyRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 1: { - // Use raw data directly to avoid allocation this->key = value.data(); this->key_len = value.size(); break; @@ -933,13 +928,11 @@ bool HomeassistantActionResponse::decode_varint(uint32_t field_id, ProtoVarInt v bool HomeassistantActionResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 3: { - // Use raw data directly via StringRef to avoid allocation this->error_message = StringRef(reinterpret_cast(value.data()), value.size()); break; } #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON case 4: { - // Use raw data directly to avoid allocation this->response_data = value.data(); this->response_data_len = value.size(); break; @@ -965,17 +958,14 @@ void SubscribeHomeAssistantStateResponse::calculate_size(ProtoSize &size) const bool HomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 1: { - // Use raw data directly via StringRef to avoid allocation this->entity_id = StringRef(reinterpret_cast(value.data()), value.size()); break; } case 2: { - // Use raw data directly via StringRef to avoid allocation this->state = StringRef(reinterpret_cast(value.data()), value.size()); break; } case 3: { - // Use raw data directly via StringRef to avoid allocation this->attribute = StringRef(reinterpret_cast(value.data()), value.size()); break; } @@ -988,7 +978,6 @@ bool HomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDel bool GetTimeResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 2: { - // Use raw data directly via StringRef to avoid allocation this->timezone = StringRef(reinterpret_cast(value.data()), value.size()); break; } @@ -1055,7 +1044,6 @@ bool ExecuteServiceArgument::decode_varint(uint32_t field_id, ProtoVarInt value) bool ExecuteServiceArgument::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 4: { - // Use raw data directly via StringRef to avoid allocation this->string_ = StringRef(reinterpret_cast(value.data()), value.size()); break; } @@ -1404,12 +1392,10 @@ bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) bool ClimateCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 17: { - // Use raw data directly via StringRef to avoid allocation this->custom_fan_mode = StringRef(reinterpret_cast(value.data()), value.size()); break; } case 21: { - // Use raw data directly via StringRef to avoid allocation this->custom_preset = StringRef(reinterpret_cast(value.data()), value.size()); break; } @@ -1696,7 +1682,6 @@ bool SelectCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { bool SelectCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 2: { - // Use raw data directly via StringRef to avoid allocation this->state = StringRef(reinterpret_cast(value.data()), value.size()); break; } @@ -1802,7 +1787,6 @@ bool SirenCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { bool SirenCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 5: { - // Use raw data directly via StringRef to avoid allocation this->tone = StringRef(reinterpret_cast(value.data()), value.size()); break; } @@ -1895,7 +1879,6 @@ bool LockCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { bool LockCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 4: { - // Use raw data directly via StringRef to avoid allocation this->code = StringRef(reinterpret_cast(value.data()), value.size()); break; } @@ -2067,7 +2050,6 @@ bool MediaPlayerCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt val bool MediaPlayerCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 7: { - // Use raw data directly via StringRef to avoid allocation this->media_url = StringRef(reinterpret_cast(value.data()), value.size()); break; } @@ -2278,7 +2260,6 @@ bool BluetoothGATTWriteRequest::decode_varint(uint32_t field_id, ProtoVarInt val bool BluetoothGATTWriteRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 4: { - // Use raw data directly to avoid allocation this->data = value.data(); this->data_len = value.size(); break; @@ -2317,7 +2298,6 @@ bool BluetoothGATTWriteDescriptorRequest::decode_varint(uint32_t field_id, Proto bool BluetoothGATTWriteDescriptorRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 3: { - // Use raw data directly to avoid allocation this->data = value.data(); this->data_len = value.size(); break; @@ -2502,12 +2482,10 @@ bool VoiceAssistantResponse::decode_varint(uint32_t field_id, ProtoVarInt value) bool VoiceAssistantEventData::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 1: { - // Use raw data directly via StringRef to avoid allocation this->name = StringRef(reinterpret_cast(value.data()), value.size()); break; } case 2: { - // Use raw data directly via StringRef to avoid allocation this->value = StringRef(reinterpret_cast(value.data()), value.size()); break; } @@ -2587,12 +2565,10 @@ bool VoiceAssistantTimerEventResponse::decode_varint(uint32_t field_id, ProtoVar bool VoiceAssistantTimerEventResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 2: { - // Use raw data directly via StringRef to avoid allocation this->timer_id = StringRef(reinterpret_cast(value.data()), value.size()); break; } case 3: { - // Use raw data directly via StringRef to avoid allocation this->name = StringRef(reinterpret_cast(value.data()), value.size()); break; } @@ -2614,17 +2590,14 @@ bool VoiceAssistantAnnounceRequest::decode_varint(uint32_t field_id, ProtoVarInt bool VoiceAssistantAnnounceRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 1: { - // Use raw data directly via StringRef to avoid allocation this->media_id = StringRef(reinterpret_cast(value.data()), value.size()); break; } case 2: { - // Use raw data directly via StringRef to avoid allocation this->text = StringRef(reinterpret_cast(value.data()), value.size()); break; } case 3: { - // Use raw data directly via StringRef to avoid allocation this->preannounce_media_id = StringRef(reinterpret_cast(value.data()), value.size()); break; } @@ -2664,12 +2637,10 @@ bool VoiceAssistantExternalWakeWord::decode_varint(uint32_t field_id, ProtoVarIn bool VoiceAssistantExternalWakeWord::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 1: { - // Use raw data directly via StringRef to avoid allocation this->id = StringRef(reinterpret_cast(value.data()), value.size()); break; } case 2: { - // Use raw data directly via StringRef to avoid allocation this->wake_word = StringRef(reinterpret_cast(value.data()), value.size()); break; } @@ -2677,17 +2648,14 @@ bool VoiceAssistantExternalWakeWord::decode_length(uint32_t field_id, ProtoLengt this->trained_languages.push_back(value.as_string()); break; case 4: { - // Use raw data directly via StringRef to avoid allocation this->model_type = StringRef(reinterpret_cast(value.data()), value.size()); break; } case 6: { - // Use raw data directly via StringRef to avoid allocation this->model_hash = StringRef(reinterpret_cast(value.data()), value.size()); break; } case 7: { - // Use raw data directly via StringRef to avoid allocation this->url = StringRef(reinterpret_cast(value.data()), value.size()); break; } @@ -2801,7 +2769,6 @@ bool AlarmControlPanelCommandRequest::decode_varint(uint32_t field_id, ProtoVarI bool AlarmControlPanelCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 3: { - // Use raw data directly via StringRef to avoid allocation this->code = StringRef(reinterpret_cast(value.data()), value.size()); break; } @@ -2887,7 +2854,6 @@ bool TextCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { bool TextCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 2: { - // Use raw data directly via StringRef to avoid allocation this->state = StringRef(reinterpret_cast(value.data()), value.size()); break; } @@ -3358,7 +3324,6 @@ bool UpdateCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { bool ZWaveProxyFrame::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 1: { - // Use raw data directly to avoid allocation this->data = value.data(); this->data_len = value.size(); break; @@ -3383,7 +3348,6 @@ bool ZWaveProxyRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { bool ZWaveProxyRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 2: { - // Use raw data directly to avoid allocation this->data = value.data(); this->data_len = value.size(); break; diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index af7d6af954..f22b248747 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -887,7 +887,6 @@ class PointerToBytesBufferType(PointerToBufferTypeBase): @property def decode_length_content(self) -> str | None: return f"""case {self.number}: {{ - // Use raw data directly to avoid allocation this->{self.field_name} = value.data(); this->{self.field_name}_len = value.size(); break; From 33d1efe27c50e773f599d83be97ad799b393d973 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 24 Dec 2025 22:21:00 -1000 Subject: [PATCH 20/20] tidy --- .../voice_assistant/voice_assistant.cpp | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/esphome/components/voice_assistant/voice_assistant.cpp b/esphome/components/voice_assistant/voice_assistant.cpp index 551f0370f2..31a4e5726f 100644 --- a/esphome/components/voice_assistant/voice_assistant.cpp +++ b/esphome/components/voice_assistant/voice_assistant.cpp @@ -627,9 +627,9 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { ESP_LOGD(TAG, "Assist Pipeline running"); #ifdef USE_MEDIA_PLAYER this->started_streaming_tts_ = false; - for (auto arg : msg.data) { + for (const auto &arg : msg.data) { if (arg.name == "url") { - this->tts_response_url_ = std::move(arg.value); + this->tts_response_url_ = arg.value; } } #endif @@ -648,9 +648,9 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { break; case api::enums::VOICE_ASSISTANT_STT_END: { std::string text; - for (auto arg : msg.data) { + for (const auto &arg : msg.data) { if (arg.name == "text") { - text = std::move(arg.value); + text = arg.value; } } if (text.empty()) { @@ -693,9 +693,9 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { break; } case api::enums::VOICE_ASSISTANT_INTENT_END: { - for (auto arg : msg.data) { + for (const auto &arg : msg.data) { if (arg.name == "conversation_id") { - this->conversation_id_ = std::move(arg.value); + this->conversation_id_ = arg.value; } else if (arg.name == "continue_conversation") { this->continue_conversation_ = (arg.value == "1"); } @@ -705,9 +705,9 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { } case api::enums::VOICE_ASSISTANT_TTS_START: { std::string text; - for (auto arg : msg.data) { + for (const auto &arg : msg.data) { if (arg.name == "text") { - text = std::move(arg.value); + text = arg.value; } } if (text.empty()) { @@ -731,9 +731,9 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { } case api::enums::VOICE_ASSISTANT_TTS_END: { std::string url; - for (auto arg : msg.data) { + for (const auto &arg : msg.data) { if (arg.name == "url") { - url = std::move(arg.value); + url = arg.value; } } if (url.empty()) { @@ -778,11 +778,11 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { case api::enums::VOICE_ASSISTANT_ERROR: { std::string code = ""; std::string message = ""; - for (auto arg : msg.data) { + for (const auto &arg : msg.data) { if (arg.name == "code") { - code = std::move(arg.value); + code = arg.value; } else if (arg.name == "message") { - message = std::move(arg.value); + message = arg.value; } } if (code == "wake-word-timeout" || code == "wake_word_detection_aborted" || code == "no_wake_word") {