From e67b5a78d0b4fd4d06ddc347141eb4e19c7d4f10 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 22 Mar 2026 20:51:40 -1000 Subject: [PATCH 1/4] [esp32] Patch DRAM segment for testing mode to fix grouped component test overflow (#15102) --- esphome/components/esp32/iram_fix.py.script | 70 ++++++++++++--------- 1 file changed, 42 insertions(+), 28 deletions(-) diff --git a/esphome/components/esp32/iram_fix.py.script b/esphome/components/esp32/iram_fix.py.script index 0d23f9a81b..b656d7d1a6 100644 --- a/esphome/components/esp32/iram_fix.py.script +++ b/esphome/components/esp32/iram_fix.py.script @@ -4,12 +4,40 @@ import re # pylint: disable=E0602 Import("env") # noqa -# IRAM size for testing mode (2MB - large enough to accommodate grouped tests) -TESTING_IRAM_SIZE = 0x200000 +# Memory sizes for testing mode (large enough to accommodate grouped tests) +TESTING_IRAM_SIZE = 0x200000 # 2MB +TESTING_DRAM_SIZE = 0x200000 # 2MB + + +def patch_segment(content, segment_name, new_size): + """Patch a memory segment's length in linker script content. + + Handles both single-line and multi-line segment definitions, e.g.: + iram0_0_seg (RX) : org = 0x40080000, len = 0x20000 + 0x0 + or split across lines: + dram0_0_seg (RW) : org = 0x3FFB0000 + 0xdb5c, + len = 0x2c200 - 0xdb5c + + Args: + content: Full linker script content as string + segment_name: Name of the segment (e.g., 'iram0_0_seg') + new_size: New size as integer + + Returns: + Tuple of (new_content, was_patched) + """ + # Match segment name through to "len = " allowing newlines between org and len + pattern = rf'({re.escape(segment_name)}\s*\([^)]*\)\s*:\s*org\s*=\s*.+?,\s*len\s*=\s*)(\S+[^\n]*)' + if match := re.search(pattern, content, re.DOTALL): + replacement = f"{match.group(1)}{new_size:#x}" + new_content = content[:match.start()] + replacement + content[match.end():] + if new_content != content: + return new_content, True + return content, False def patch_idf_linker_script(source, target, env): - """Patch ESP-IDF linker script to increase IRAM size for testing mode.""" + """Patch ESP-IDF linker script to increase IRAM and DRAM size for testing mode.""" # Check if we're in testing mode by looking for the define build_flags = env.get("BUILD_FLAGS", []) testing_mode = any("-DESPHOME_TESTING_MODE" in flag for flag in build_flags) @@ -34,36 +62,22 @@ def patch_idf_linker_script(source, target, env): print(f"ESPHome: Error reading linker script: {e}") return - # Check if this file contains iram0_0_seg - if 'iram0_0_seg' not in content: - print(f"ESPHome: Warning - iram0_0_seg not found in {memory_ld}") - return + patches = [] - # Look for iram0_0_seg definition and increase its length - # ESP-IDF format can be: - # iram0_0_seg (RX) : org = 0x40080000, len = 0x20000 + 0x0 - # or more complex with nested parentheses: - # iram0_0_seg (RX) : org = (0x40370000 + 0x4000), len = (((0x403CB700 - (0x40378000 - 0x3FC88000)) - 0x3FC88000) + 0x8000 - 0x4000) - # We want to change len to TESTING_IRAM_SIZE for testing + content, patched = patch_segment(content, 'iram0_0_seg', TESTING_IRAM_SIZE) + if patched: + patches.append(f"IRAM={TESTING_IRAM_SIZE:#x}") - # Use a more robust approach: find the line and manually parse it - lines = content.split('\n') - for i, line in enumerate(lines): - if 'iram0_0_seg' in line and 'len' in line: - # Find the position of "len = " and replace everything after it until the end of the statement - match = re.search(r'(iram0_0_seg\s*\([^)]*\)\s*:\s*org\s*=\s*(?:\([^)]+\)|0x[0-9a-fA-F]+)\s*,\s*len\s*=\s*)(.+?)(\s*)$', line) - if match: - lines[i] = f"{match.group(1)}{TESTING_IRAM_SIZE:#x}{match.group(3)}" - break + content, patched = patch_segment(content, 'dram0_0_seg', TESTING_DRAM_SIZE) + if patched: + patches.append(f"DRAM={TESTING_DRAM_SIZE:#x}") - updated = '\n'.join(lines) - - if updated != content: + if patches: with open(memory_ld, "w") as f: - f.write(updated) - print(f"ESPHome: Patched IRAM size to {TESTING_IRAM_SIZE:#x} in {memory_ld} for testing mode") + f.write(content) + print(f"ESPHome: Patched {', '.join(patches)} in {memory_ld} for testing mode") else: - print(f"ESPHome: Warning - could not patch iram0_0_seg in {memory_ld}") + print(f"ESPHome: Warning - could not patch memory segments in {memory_ld}") # Hook into the build process before linking From 225330413a137acbff12e81127b983ad05d92e00 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Mon, 23 Mar 2026 01:55:14 -0500 Subject: [PATCH 2/4] [uart] Rename `FlushResult` to `UARTFlushResult` with `UART_FLUSH_RESULT_` prefix (#15101) Co-authored-by: Claude Sonnet 4.6 --- esphome/components/api/api_connection.cpp | 8 ++++---- esphome/components/ble_nus/ble_nus.cpp | 6 +++--- esphome/components/ble_nus/ble_nus.h | 2 +- .../components/serial_proxy/serial_proxy.cpp | 2 +- .../components/serial_proxy/serial_proxy.h | 2 +- esphome/components/uart/uart.h | 2 +- esphome/components/uart/uart_component.h | 19 +++++++------------ .../uart/uart_component_esp8266.cpp | 4 ++-- .../components/uart/uart_component_esp8266.h | 2 +- .../uart/uart_component_esp_idf.cpp | 8 ++++---- .../components/uart/uart_component_esp_idf.h | 2 +- .../components/uart/uart_component_host.cpp | 6 +++--- esphome/components/uart/uart_component_host.h | 2 +- .../uart/uart_component_libretiny.cpp | 4 ++-- .../uart/uart_component_libretiny.h | 2 +- .../components/uart/uart_component_rp2040.cpp | 4 ++-- .../components/uart/uart_component_rp2040.h | 2 +- esphome/components/usb_cdc_acm/usb_cdc_acm.h | 2 +- .../usb_cdc_acm/usb_cdc_acm_esp32.cpp | 10 +++++----- esphome/components/usb_uart/usb_uart.cpp | 6 +++--- esphome/components/usb_uart/usb_uart.h | 2 +- esphome/components/weikai/weikai.cpp | 6 +++--- esphome/components/weikai/weikai.h | 2 +- tests/components/ld2450/common.h | 2 +- tests/components/uart/common.h | 2 +- .../uart_mock/uart_mock.cpp | 4 ++-- .../external_components/uart_mock/uart_mock.h | 2 +- 27 files changed, 55 insertions(+), 60 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 40c27b224b..82d7e3f674 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1519,16 +1519,16 @@ void APIConnection::on_serial_proxy_request(const SerialProxyRequest &msg) { resp.instance = msg.instance; resp.type = enums::SERIAL_PROXY_REQUEST_TYPE_FLUSH; switch (proxies[msg.instance]->flush_port()) { - case uart::FlushResult::SUCCESS: + case uart::UARTFlushResult::UART_FLUSH_RESULT_SUCCESS: resp.status = enums::SERIAL_PROXY_STATUS_OK; break; - case uart::FlushResult::ASSUMED_SUCCESS: + case uart::UARTFlushResult::UART_FLUSH_RESULT_ASSUMED_SUCCESS: resp.status = enums::SERIAL_PROXY_STATUS_ASSUMED_SUCCESS; break; - case uart::FlushResult::TIMEOUT: + case uart::UARTFlushResult::UART_FLUSH_RESULT_TIMEOUT: resp.status = enums::SERIAL_PROXY_STATUS_TIMEOUT; break; - case uart::FlushResult::FAILED: + case uart::UARTFlushResult::UART_FLUSH_RESULT_FAILED: resp.status = enums::SERIAL_PROXY_STATUS_ERROR; break; } diff --git a/esphome/components/ble_nus/ble_nus.cpp b/esphome/components/ble_nus/ble_nus.cpp index 2f60f81471..71d98332e0 100644 --- a/esphome/components/ble_nus/ble_nus.cpp +++ b/esphome/components/ble_nus/ble_nus.cpp @@ -103,17 +103,17 @@ size_t BLENUS::available() { #endif } -uart::FlushResult BLENUS::flush() { +uart::UARTFlushResult BLENUS::flush() { constexpr uint32_t timeout_500ms = 500; uint32_t start = millis(); while (atomic_get(&this->tx_status_) != TX_DISABLED && !ring_buf_is_empty(&global_ble_tx_ring_buf)) { if (millis() - start > timeout_500ms) { ESP_LOGW(TAG, "Flush timeout"); - return uart::FlushResult::TIMEOUT; + return uart::UARTFlushResult::UART_FLUSH_RESULT_TIMEOUT; } delay(1); } - return uart::FlushResult::SUCCESS; + return uart::UARTFlushResult::UART_FLUSH_RESULT_SUCCESS; } void BLENUS::connected(bt_conn *conn, uint8_t err) { diff --git a/esphome/components/ble_nus/ble_nus.h b/esphome/components/ble_nus/ble_nus.h index b482c240e5..f1afd54af9 100644 --- a/esphome/components/ble_nus/ble_nus.h +++ b/esphome/components/ble_nus/ble_nus.h @@ -26,7 +26,7 @@ class BLENUS : public uart::UARTComponent, public Component { bool peek_byte(uint8_t *data) override; bool read_array(uint8_t *data, size_t len) override; size_t available() override; - uart::FlushResult flush() override; + uart::UARTFlushResult flush() override; void check_logger_conflict() override {} void set_expose_log(bool expose_log) { this->expose_log_ = expose_log; } #ifdef USE_LOGGER diff --git a/esphome/components/serial_proxy/serial_proxy.cpp b/esphome/components/serial_proxy/serial_proxy.cpp index 00d822b75c..f3c256c62a 100644 --- a/esphome/components/serial_proxy/serial_proxy.cpp +++ b/esphome/components/serial_proxy/serial_proxy.cpp @@ -165,7 +165,7 @@ uint32_t SerialProxy::get_modem_pins() const { (this->dtr_state_ ? SERIAL_PROXY_LINE_STATE_FLAG_DTR : 0u); } -uart::FlushResult SerialProxy::flush_port() { +uart::UARTFlushResult SerialProxy::flush_port() { ESP_LOGV(TAG, "Flushing serial proxy [%u]", this->instance_index_); return this->flush(); } diff --git a/esphome/components/serial_proxy/serial_proxy.h b/esphome/components/serial_proxy/serial_proxy.h index 5adfa4fe53..c435787a61 100644 --- a/esphome/components/serial_proxy/serial_proxy.h +++ b/esphome/components/serial_proxy/serial_proxy.h @@ -92,7 +92,7 @@ class SerialProxy : public uart::UARTDevice, public Component { uint32_t get_modem_pins() const; /// Flush the serial port (block until all TX data is sent) - uart::FlushResult flush_port(); + uart::UARTFlushResult flush_port(); /// Set the RTS GPIO pin (from YAML configuration) void set_rts_pin(GPIOPin *pin) { this->rts_pin_ = pin; } diff --git a/esphome/components/uart/uart.h b/esphome/components/uart/uart.h index 2c4fb34c9a..899d349e21 100644 --- a/esphome/components/uart/uart.h +++ b/esphome/components/uart/uart.h @@ -45,7 +45,7 @@ class UARTDevice { size_t available() { return this->parent_->available(); } - FlushResult flush() { return this->parent_->flush(); } + UARTFlushResult flush() { return this->parent_->flush(); } // Compat APIs int read() { diff --git a/esphome/components/uart/uart_component.h b/esphome/components/uart/uart_component.h index 00a4c78878..abc77fbae8 100644 --- a/esphome/components/uart/uart_component.h +++ b/esphome/components/uart/uart_component.h @@ -30,17 +30,12 @@ enum UARTDirection { const LogString *parity_to_str(UARTParityOptions parity); /// Result of a flush() call. -// Some vendor SDKs (e.g., Realtek) define SUCCESS as a macro. -// Save and restore around the enum to avoid collisions with our scoped enum value. -#pragma push_macro("SUCCESS") -#undef SUCCESS -enum class FlushResult { - SUCCESS, ///< Confirmed: all bytes left the TX FIFO. - TIMEOUT, ///< Confirmed: timed out before TX completed. - FAILED, ///< Confirmed: driver or hardware error. - ASSUMED_SUCCESS, ///< Platform cannot report result; success is assumed. +enum class UARTFlushResult { + UART_FLUSH_RESULT_SUCCESS, ///< Confirmed: all bytes left the TX FIFO. + UART_FLUSH_RESULT_TIMEOUT, ///< Confirmed: timed out before TX completed. + UART_FLUSH_RESULT_FAILED, ///< Confirmed: driver or hardware error. + UART_FLUSH_RESULT_ASSUMED_SUCCESS, ///< Platform cannot report result; success is assumed. }; -#pragma pop_macro("SUCCESS") class UARTComponent { public: @@ -87,8 +82,8 @@ class UARTComponent { virtual size_t available() = 0; // Pure virtual method to block until all bytes have been written to the UART bus. - // @return FlushResult indicating whether the flush was confirmed, timed out, failed, or assumed successful. - virtual FlushResult flush() = 0; + // @return UARTFlushResult indicating whether the flush was confirmed, timed out, failed, or assumed successful. + virtual UARTFlushResult flush() = 0; // Sets the maximum time to wait for TX to drain during flush(). // Only meaningful on ESP32 (IDF). Other platforms ignore this value. diff --git a/esphome/components/uart/uart_component_esp8266.cpp b/esphome/components/uart/uart_component_esp8266.cpp index 91218c4300..0ea7930760 100644 --- a/esphome/components/uart/uart_component_esp8266.cpp +++ b/esphome/components/uart/uart_component_esp8266.cpp @@ -213,14 +213,14 @@ size_t ESP8266UartComponent::available() { return this->sw_serial_->available(); } } -FlushResult ESP8266UartComponent::flush() { +UARTFlushResult ESP8266UartComponent::flush() { ESP_LOGVV(TAG, " Flushing"); if (this->hw_serial_ != nullptr) { this->hw_serial_->flush(); } else { this->sw_serial_->flush(); } - return FlushResult::ASSUMED_SUCCESS; + return UARTFlushResult::UART_FLUSH_RESULT_ASSUMED_SUCCESS; } void ESP8266SoftwareSerial::setup(InternalGPIOPin *tx_pin, InternalGPIOPin *rx_pin, uint32_t baud_rate, uint8_t stop_bits, uint32_t data_bits, UARTParityOptions parity, diff --git a/esphome/components/uart/uart_component_esp8266.h b/esphome/components/uart/uart_component_esp8266.h index ca90dc5964..7f844d9b65 100644 --- a/esphome/components/uart/uart_component_esp8266.h +++ b/esphome/components/uart/uart_component_esp8266.h @@ -58,7 +58,7 @@ class ESP8266UartComponent : public UARTComponent, public Component { bool read_array(uint8_t *data, size_t len) override; size_t available() override; - FlushResult flush() override; + UARTFlushResult flush() override; uint32_t get_config(); diff --git a/esphome/components/uart/uart_component_esp_idf.cpp b/esphome/components/uart/uart_component_esp_idf.cpp index 8168e49805..bd2f915d3a 100644 --- a/esphome/components/uart/uart_component_esp_idf.cpp +++ b/esphome/components/uart/uart_component_esp_idf.cpp @@ -360,15 +360,15 @@ size_t IDFUARTComponent::available() { return available; } -FlushResult IDFUARTComponent::flush() { +UARTFlushResult IDFUARTComponent::flush() { ESP_LOGVV(TAG, " Flushing"); TickType_t ticks = this->flush_timeout_ms_ == 0 ? portMAX_DELAY : pdMS_TO_TICKS(this->flush_timeout_ms_); esp_err_t err = uart_wait_tx_done(this->uart_num_, ticks); if (err == ESP_OK) - return FlushResult::SUCCESS; + return UARTFlushResult::UART_FLUSH_RESULT_SUCCESS; if (err == ESP_ERR_TIMEOUT) - return FlushResult::TIMEOUT; - return FlushResult::FAILED; + return UARTFlushResult::UART_FLUSH_RESULT_TIMEOUT; + return UARTFlushResult::UART_FLUSH_RESULT_FAILED; } void IDFUARTComponent::check_logger_conflict() {} diff --git a/esphome/components/uart/uart_component_esp_idf.h b/esphome/components/uart/uart_component_esp_idf.h index 9fa2013cfd..ec4f2884b2 100644 --- a/esphome/components/uart/uart_component_esp_idf.h +++ b/esphome/components/uart/uart_component_esp_idf.h @@ -31,7 +31,7 @@ class IDFUARTComponent : public UARTComponent, public Component { bool read_array(uint8_t *data, size_t len) override; size_t available() override; - FlushResult flush() override; + UARTFlushResult flush() override; void set_flush_timeout(uint32_t flush_timeout_ms) override { this->flush_timeout_ms_ = flush_timeout_ms; } diff --git a/esphome/components/uart/uart_component_host.cpp b/esphome/components/uart/uart_component_host.cpp index 66026f3ccd..0042ffae23 100644 --- a/esphome/components/uart/uart_component_host.cpp +++ b/esphome/components/uart/uart_component_host.cpp @@ -274,13 +274,13 @@ size_t HostUartComponent::available() { return result; }; -FlushResult HostUartComponent::flush() { +UARTFlushResult HostUartComponent::flush() { if (this->file_descriptor_ == -1) { - return FlushResult::ASSUMED_SUCCESS; + return UARTFlushResult::UART_FLUSH_RESULT_ASSUMED_SUCCESS; } tcflush(this->file_descriptor_, TCIOFLUSH); ESP_LOGV(TAG, " Flushing"); - return FlushResult::ASSUMED_SUCCESS; + return UARTFlushResult::UART_FLUSH_RESULT_ASSUMED_SUCCESS; } void HostUartComponent::update_error_(const std::string &error) { diff --git a/esphome/components/uart/uart_component_host.h b/esphome/components/uart/uart_component_host.h index c22efdcb92..56ff525bc3 100644 --- a/esphome/components/uart/uart_component_host.h +++ b/esphome/components/uart/uart_component_host.h @@ -18,7 +18,7 @@ class HostUartComponent : public UARTComponent, public Component { bool peek_byte(uint8_t *data) override; bool read_array(uint8_t *data, size_t len) override; size_t available() override; - FlushResult flush() override; + UARTFlushResult flush() override; void set_name(std::string port_name) { port_name_ = port_name; }; protected: diff --git a/esphome/components/uart/uart_component_libretiny.cpp b/esphome/components/uart/uart_component_libretiny.cpp index 6a550f296a..4172e7c164 100644 --- a/esphome/components/uart/uart_component_libretiny.cpp +++ b/esphome/components/uart/uart_component_libretiny.cpp @@ -170,10 +170,10 @@ bool LibreTinyUARTComponent::read_array(uint8_t *data, size_t len) { } size_t LibreTinyUARTComponent::available() { return this->serial_->available(); } -FlushResult LibreTinyUARTComponent::flush() { +UARTFlushResult LibreTinyUARTComponent::flush() { ESP_LOGVV(TAG, " Flushing"); this->serial_->flush(); - return FlushResult::ASSUMED_SUCCESS; + return UARTFlushResult::UART_FLUSH_RESULT_ASSUMED_SUCCESS; } void LibreTinyUARTComponent::check_logger_conflict() { diff --git a/esphome/components/uart/uart_component_libretiny.h b/esphome/components/uart/uart_component_libretiny.h index 77df808067..872ea86601 100644 --- a/esphome/components/uart/uart_component_libretiny.h +++ b/esphome/components/uart/uart_component_libretiny.h @@ -22,7 +22,7 @@ class LibreTinyUARTComponent : public UARTComponent, public Component { bool read_array(uint8_t *data, size_t len) override; size_t available() override; - FlushResult flush() override; + UARTFlushResult flush() override; uint16_t get_config(); diff --git a/esphome/components/uart/uart_component_rp2040.cpp b/esphome/components/uart/uart_component_rp2040.cpp index 6f6f1fb96b..1aaf98dc84 100644 --- a/esphome/components/uart/uart_component_rp2040.cpp +++ b/esphome/components/uart/uart_component_rp2040.cpp @@ -208,10 +208,10 @@ bool RP2040UartComponent::read_array(uint8_t *data, size_t len) { return true; } size_t RP2040UartComponent::available() { return this->serial_->available(); } -FlushResult RP2040UartComponent::flush() { +UARTFlushResult RP2040UartComponent::flush() { ESP_LOGVV(TAG, " Flushing"); this->serial_->flush(); - return FlushResult::ASSUMED_SUCCESS; + return UARTFlushResult::UART_FLUSH_RESULT_ASSUMED_SUCCESS; } } // namespace esphome::uart diff --git a/esphome/components/uart/uart_component_rp2040.h b/esphome/components/uart/uart_component_rp2040.h index 891212ca74..198c698af9 100644 --- a/esphome/components/uart/uart_component_rp2040.h +++ b/esphome/components/uart/uart_component_rp2040.h @@ -25,7 +25,7 @@ class RP2040UartComponent : public UARTComponent, public Component { bool read_array(uint8_t *data, size_t len) override; size_t available() override; - FlushResult flush() override; + UARTFlushResult flush() override; uint16_t get_config(); diff --git a/esphome/components/usb_cdc_acm/usb_cdc_acm.h b/esphome/components/usb_cdc_acm/usb_cdc_acm.h index a56abc9ee8..89405ab893 100644 --- a/esphome/components/usb_cdc_acm/usb_cdc_acm.h +++ b/esphome/components/usb_cdc_acm/usb_cdc_acm.h @@ -82,7 +82,7 @@ class USBCDCACMInstance : public uart::UARTComponent, public Parentedhas_peek_ ? 1 : 0); } -uart::FlushResult USBCDCACMInstance::flush() { +uart::UARTFlushResult USBCDCACMInstance::flush() { // Wait for TX ring buffer to be empty if (this->usb_tx_ringbuf_ == nullptr) { - return uart::FlushResult::ASSUMED_SUCCESS; + return uart::UARTFlushResult::UART_FLUSH_RESULT_ASSUMED_SUCCESS; } UBaseType_t waiting = 1; @@ -342,10 +342,10 @@ uart::FlushResult USBCDCACMInstance::flush() { // Also wait for USB to finish transmitting esp_err_t err = tinyusb_cdcacm_write_flush(static_cast(this->itf_), pdMS_TO_TICKS(100)); if (err == ESP_OK) - return uart::FlushResult::SUCCESS; + return uart::UARTFlushResult::UART_FLUSH_RESULT_SUCCESS; if (err == ESP_ERR_TIMEOUT) - return uart::FlushResult::TIMEOUT; - return uart::FlushResult::FAILED; + return uart::UARTFlushResult::UART_FLUSH_RESULT_TIMEOUT; + return uart::UARTFlushResult::UART_FLUSH_RESULT_FAILED; } void USBCDCACMInstance::check_logger_conflict() {} diff --git a/esphome/components/usb_uart/usb_uart.cpp b/esphome/components/usb_uart/usb_uart.cpp index a5d312f191..0b8589f671 100644 --- a/esphome/components/usb_uart/usb_uart.cpp +++ b/esphome/components/usb_uart/usb_uart.cpp @@ -169,7 +169,7 @@ void USBUartChannel::write_array(const uint8_t *data, size_t len) { this->parent_->start_output(this); } -uart::FlushResult USBUartChannel::flush() { +uart::UARTFlushResult USBUartChannel::flush() { // Spin until the output queue is drained and the last USB transfer completes. // Safe to call from the main loop only. // The flush_timeout_ms_ timeout guards against a device that stops responding mid-flush; @@ -181,8 +181,8 @@ uart::FlushResult USBUartChannel::flush() { yield(); } if (!this->output_queue_.empty() || this->output_started_.load()) - return uart::FlushResult::TIMEOUT; - return uart::FlushResult::SUCCESS; + return uart::UARTFlushResult::UART_FLUSH_RESULT_TIMEOUT; + return uart::UARTFlushResult::UART_FLUSH_RESULT_SUCCESS; } bool USBUartChannel::peek_byte(uint8_t *data) { diff --git a/esphome/components/usb_uart/usb_uart.h b/esphome/components/usb_uart/usb_uart.h index 7a06b04f11..8a47f0cf4b 100644 --- a/esphome/components/usb_uart/usb_uart.h +++ b/esphome/components/usb_uart/usb_uart.h @@ -140,7 +140,7 @@ class USBUartChannel : public uart::UARTComponent, public Parentedinput_buffer_.get_available(); } - uart::FlushResult flush() override; + uart::UARTFlushResult flush() override; void check_logger_conflict() override {} void set_parity(UARTParityOptions parity) { this->parity_ = parity; } void set_debug(bool debug) { this->debug_ = debug; } diff --git a/esphome/components/weikai/weikai.cpp b/esphome/components/weikai/weikai.cpp index f01d164e9f..2ec5632691 100644 --- a/esphome/components/weikai/weikai.cpp +++ b/esphome/components/weikai/weikai.cpp @@ -433,16 +433,16 @@ void WeikaiChannel::write_array(const uint8_t *buffer, size_t length) { this->reg(0).write_fifo(const_cast(buffer), length); } -uart::FlushResult WeikaiChannel::flush() { +uart::UARTFlushResult WeikaiChannel::flush() { uint32_t const start_time = millis(); while (this->tx_fifo_is_not_empty_()) { // wait until buffer empty if (millis() - start_time > 200) { ESP_LOGW(TAG, "WARNING flush timeout - still %d bytes not sent after 200 ms", this->tx_in_fifo_()); - return uart::FlushResult::TIMEOUT; + return uart::UARTFlushResult::UART_FLUSH_RESULT_TIMEOUT; } yield(); // reschedule our thread to avoid blocking } - return uart::FlushResult::SUCCESS; + return uart::UARTFlushResult::UART_FLUSH_RESULT_SUCCESS; } size_t WeikaiChannel::xfer_fifo_to_buffer_() { diff --git a/esphome/components/weikai/weikai.h b/esphome/components/weikai/weikai.h index 715b82bfc7..36d8f66265 100644 --- a/esphome/components/weikai/weikai.h +++ b/esphome/components/weikai/weikai.h @@ -380,7 +380,7 @@ class WeikaiChannel : public uart::UARTComponent { /// @details If we refer to Serial.flush() in Arduino it says: ** Waits for the transmission of outgoing serial data /// to complete. (Prior to Arduino 1.0, this the method was removing any buffered incoming serial data.). ** Therefore /// we wait until all bytes are gone with a timeout of 100 ms - uart::FlushResult flush() override; + uart::UARTFlushResult flush() override; protected: friend class WeikaiComponent; diff --git a/tests/components/ld2450/common.h b/tests/components/ld2450/common.h index 9f9e7b3e9f..304634edca 100644 --- a/tests/components/ld2450/common.h +++ b/tests/components/ld2450/common.h @@ -16,7 +16,7 @@ class MockUARTComponent : public uart::UARTComponent { MOCK_METHOD(bool, read_array, (uint8_t * data, size_t len), (override)); MOCK_METHOD(bool, peek_byte, (uint8_t * data), (override)); MOCK_METHOD(size_t, available, (), (override)); - MOCK_METHOD(uart::FlushResult, flush, (), (override)); + MOCK_METHOD(uart::UARTFlushResult, flush, (), (override)); MOCK_METHOD(void, check_logger_conflict, (), (override)); }; diff --git a/tests/components/uart/common.h b/tests/components/uart/common.h index f7e2d8a3f7..de3ea3029e 100644 --- a/tests/components/uart/common.h +++ b/tests/components/uart/common.h @@ -30,7 +30,7 @@ class MockUARTComponent : public UARTComponent { MOCK_METHOD(bool, read_array, (uint8_t * data, size_t len), (override)); MOCK_METHOD(bool, peek_byte, (uint8_t * data), (override)); MOCK_METHOD(size_t, available, (), (override)); - MOCK_METHOD(FlushResult, flush, (), (override)); + MOCK_METHOD(UARTFlushResult, flush, (), (override)); MOCK_METHOD(void, check_logger_conflict, (), (override)); }; diff --git a/tests/integration/fixtures/external_components/uart_mock/uart_mock.cpp b/tests/integration/fixtures/external_components/uart_mock/uart_mock.cpp index 1a15da76d1..d0690e7515 100644 --- a/tests/integration/fixtures/external_components/uart_mock/uart_mock.cpp +++ b/tests/integration/fixtures/external_components/uart_mock/uart_mock.cpp @@ -153,9 +153,9 @@ bool MockUartComponent::read_array(uint8_t *data, size_t len) { size_t MockUartComponent::available() { return this->rx_buffer_.size(); } -uart::FlushResult MockUartComponent::flush() { +uart::UARTFlushResult MockUartComponent::flush() { // Nothing to flush in mock - return uart::FlushResult::ASSUMED_SUCCESS; + return uart::UARTFlushResult::UART_FLUSH_RESULT_ASSUMED_SUCCESS; } void MockUartComponent::set_rx_full_threshold(size_t rx_full_threshold) { diff --git a/tests/integration/fixtures/external_components/uart_mock/uart_mock.h b/tests/integration/fixtures/external_components/uart_mock/uart_mock.h index 82e3b3d563..0b3b49893d 100644 --- a/tests/integration/fixtures/external_components/uart_mock/uart_mock.h +++ b/tests/integration/fixtures/external_components/uart_mock/uart_mock.h @@ -28,7 +28,7 @@ class MockUartComponent : public uart::UARTComponent, public Component { bool peek_byte(uint8_t *data) override; bool read_array(uint8_t *data, size_t len) override; size_t available() override; - uart::FlushResult flush() override; + uart::UARTFlushResult flush() override; void set_rx_full_threshold(size_t rx_full_threshold) override; void set_rx_timeout(size_t rx_timeout) override; From f4097d5a95a37faa963ec2d8ad1588b70b259e3c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 22 Mar 2026 20:57:40 -1000 Subject: [PATCH 3/4] [api] Devirtualize API command dispatch (#15044) --- esphome/components/api/api_connection.cpp | 2 +- esphome/components/api/api_connection.h | 156 ++++++++++++--------- esphome/components/api/api_pb2_service.cpp | 3 +- esphome/components/api/api_pb2_service.h | 134 +++++++++--------- esphome/components/api/proto.h | 23 +-- script/api_protobuf/api_protobuf.py | 13 +- 6 files changed, 164 insertions(+), 167 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 82d7e3f674..d023cd21a8 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -234,7 +234,7 @@ void APIConnection::loop() { this->last_traffic_ = now; } // read a packet - this->read_message(buffer.data_len, buffer.type, buffer.data); + this->read_message_(buffer.data_len, buffer.type, buffer.data); if (this->flags_.remove) return; } diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 85c8e777a9..3d8563b1ae 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -49,11 +49,29 @@ class APIConnection final : public APIServerConnectionBase { friend class APIServer; friend class ListEntitiesIterator; APIConnection(std::unique_ptr socket, APIServer *parent); - virtual ~APIConnection(); + ~APIConnection(); void start(); void loop(); + protected: + // read_message_ is defined here (instead of in APIServerConnectionBase) so the + // compiler can devirtualize and inline on_* handler calls within this final class. + void read_message_(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data); + + // Auth helpers defined here (not in ProtoService) so the compiler can + // devirtualize is_connection_setup()/on_no_setup_connection() calls + // within this final class. + inline bool check_connection_setup_() { + if (!this->is_connection_setup()) { + this->on_no_setup_connection(); + return false; + } + return true; + } + inline bool check_authenticated_() { return this->check_connection_setup_(); } + + public: bool send_list_info_done() { return this->schedule_message_(nullptr, ListEntitiesDoneResponse::MESSAGE_TYPE, ListEntitiesDoneResponse::ESTIMATED_SIZE); @@ -63,72 +81,72 @@ class APIConnection final : public APIServerConnectionBase { #endif #ifdef USE_COVER bool send_cover_state(cover::Cover *cover); - void on_cover_command_request(const CoverCommandRequest &msg) override; + void on_cover_command_request(const CoverCommandRequest &msg); #endif #ifdef USE_FAN bool send_fan_state(fan::Fan *fan); - void on_fan_command_request(const FanCommandRequest &msg) override; + void on_fan_command_request(const FanCommandRequest &msg); #endif #ifdef USE_LIGHT bool send_light_state(light::LightState *light); - void on_light_command_request(const LightCommandRequest &msg) override; + void on_light_command_request(const LightCommandRequest &msg); #endif #ifdef USE_SENSOR bool send_sensor_state(sensor::Sensor *sensor); #endif #ifdef USE_SWITCH bool send_switch_state(switch_::Switch *a_switch); - void on_switch_command_request(const SwitchCommandRequest &msg) override; + void on_switch_command_request(const SwitchCommandRequest &msg); #endif #ifdef USE_TEXT_SENSOR bool send_text_sensor_state(text_sensor::TextSensor *text_sensor); #endif #ifdef USE_CAMERA void set_camera_state(std::shared_ptr image); - void on_camera_image_request(const CameraImageRequest &msg) override; + void on_camera_image_request(const CameraImageRequest &msg); #endif #ifdef USE_CLIMATE bool send_climate_state(climate::Climate *climate); - void on_climate_command_request(const ClimateCommandRequest &msg) override; + void on_climate_command_request(const ClimateCommandRequest &msg); #endif #ifdef USE_NUMBER bool send_number_state(number::Number *number); - void on_number_command_request(const NumberCommandRequest &msg) override; + void on_number_command_request(const NumberCommandRequest &msg); #endif #ifdef USE_DATETIME_DATE bool send_date_state(datetime::DateEntity *date); - void on_date_command_request(const DateCommandRequest &msg) override; + void on_date_command_request(const DateCommandRequest &msg); #endif #ifdef USE_DATETIME_TIME bool send_time_state(datetime::TimeEntity *time); - void on_time_command_request(const TimeCommandRequest &msg) override; + void on_time_command_request(const TimeCommandRequest &msg); #endif #ifdef USE_DATETIME_DATETIME bool send_datetime_state(datetime::DateTimeEntity *datetime); - void on_date_time_command_request(const DateTimeCommandRequest &msg) override; + void on_date_time_command_request(const DateTimeCommandRequest &msg); #endif #ifdef USE_TEXT bool send_text_state(text::Text *text); - void on_text_command_request(const TextCommandRequest &msg) override; + void on_text_command_request(const TextCommandRequest &msg); #endif #ifdef USE_SELECT bool send_select_state(select::Select *select); - void on_select_command_request(const SelectCommandRequest &msg) override; + void on_select_command_request(const SelectCommandRequest &msg); #endif #ifdef USE_BUTTON - void on_button_command_request(const ButtonCommandRequest &msg) override; + void on_button_command_request(const ButtonCommandRequest &msg); #endif #ifdef USE_LOCK bool send_lock_state(lock::Lock *a_lock); - void on_lock_command_request(const LockCommandRequest &msg) override; + void on_lock_command_request(const LockCommandRequest &msg); #endif #ifdef USE_VALVE bool send_valve_state(valve::Valve *valve); - void on_valve_command_request(const ValveCommandRequest &msg) override; + void on_valve_command_request(const ValveCommandRequest &msg); #endif #ifdef USE_MEDIA_PLAYER bool send_media_player_state(media_player::MediaPlayer *media_player); - void on_media_player_command_request(const MediaPlayerCommandRequest &msg) override; + void on_media_player_command_request(const MediaPlayerCommandRequest &msg); #endif bool try_send_log_message(int level, const char *tag, const char *line, size_t message_len); #ifdef USE_API_HOMEASSISTANT_SERVICES @@ -138,23 +156,23 @@ class APIConnection final : public APIServerConnectionBase { this->send_message(call); } #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES - void on_homeassistant_action_response(const HomeassistantActionResponse &msg) override; + void on_homeassistant_action_response(const HomeassistantActionResponse &msg); #endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES #endif // USE_API_HOMEASSISTANT_SERVICES #ifdef USE_BLUETOOTH_PROXY - void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override; - void on_unsubscribe_bluetooth_le_advertisements_request() override; + void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg); + void on_unsubscribe_bluetooth_le_advertisements_request(); - void on_bluetooth_device_request(const BluetoothDeviceRequest &msg) override; - void on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &msg) override; - void on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &msg) override; - void on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &msg) override; - void on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &msg) override; - void on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &msg) override; - void on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &msg) override; - void on_subscribe_bluetooth_connections_free_request() override; - void on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &msg) override; - void on_bluetooth_set_connection_params_request(const BluetoothSetConnectionParamsRequest &msg) override; + void on_bluetooth_device_request(const BluetoothDeviceRequest &msg); + void on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &msg); + void on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &msg); + void on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &msg); + void on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &msg); + void on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &msg); + void on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &msg); + void on_subscribe_bluetooth_connections_free_request(); + void on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &msg); + void on_bluetooth_set_connection_params_request(const BluetoothSetConnectionParamsRequest &msg); #endif #ifdef USE_HOMEASSISTANT_TIME @@ -165,42 +183,42 @@ class APIConnection final : public APIServerConnectionBase { #endif #ifdef USE_VOICE_ASSISTANT - void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) override; - void on_voice_assistant_response(const VoiceAssistantResponse &msg) override; - void on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) override; - void on_voice_assistant_audio(const VoiceAssistantAudio &msg) override; - void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &msg) override; - void on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &msg) override; - void on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg) override; - void on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) override; + void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg); + void on_voice_assistant_response(const VoiceAssistantResponse &msg); + void on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg); + void on_voice_assistant_audio(const VoiceAssistantAudio &msg); + void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &msg); + void on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &msg); + void on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg); + void on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg); #endif #ifdef USE_ZWAVE_PROXY - void on_z_wave_proxy_frame(const ZWaveProxyFrame &msg) override; - void on_z_wave_proxy_request(const ZWaveProxyRequest &msg) override; + void on_z_wave_proxy_frame(const ZWaveProxyFrame &msg); + void on_z_wave_proxy_request(const ZWaveProxyRequest &msg); #endif #ifdef USE_ALARM_CONTROL_PANEL bool send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel); - void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) override; + void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg); #endif #ifdef USE_WATER_HEATER bool send_water_heater_state(water_heater::WaterHeater *water_heater); - void on_water_heater_command_request(const WaterHeaterCommandRequest &msg) override; + void on_water_heater_command_request(const WaterHeaterCommandRequest &msg); #endif #ifdef USE_IR_RF - void on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &msg) override; + void on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &msg); void send_infrared_rf_receive_event(const InfraredRFReceiveEvent &msg); #endif #ifdef USE_SERIAL_PROXY - void on_serial_proxy_configure_request(const SerialProxyConfigureRequest &msg) override; - void on_serial_proxy_write_request(const SerialProxyWriteRequest &msg) override; - void on_serial_proxy_set_modem_pins_request(const SerialProxySetModemPinsRequest &msg) override; - void on_serial_proxy_get_modem_pins_request(const SerialProxyGetModemPinsRequest &msg) override; - void on_serial_proxy_request(const SerialProxyRequest &msg) override; + void on_serial_proxy_configure_request(const SerialProxyConfigureRequest &msg); + void on_serial_proxy_write_request(const SerialProxyWriteRequest &msg); + void on_serial_proxy_set_modem_pins_request(const SerialProxySetModemPinsRequest &msg); + void on_serial_proxy_get_modem_pins_request(const SerialProxyGetModemPinsRequest &msg); + void on_serial_proxy_request(const SerialProxyRequest &msg); void send_serial_proxy_data(const SerialProxyDataReceived &msg); #endif @@ -210,26 +228,26 @@ class APIConnection final : public APIServerConnectionBase { #ifdef USE_UPDATE bool send_update_state(update::UpdateEntity *update); - void on_update_command_request(const UpdateCommandRequest &msg) override; + void on_update_command_request(const UpdateCommandRequest &msg); #endif - void on_disconnect_response() override; - void on_ping_response() override { + void on_disconnect_response(); + void on_ping_response() { // we initiated ping this->flags_.sent_ping = false; } #ifdef USE_API_HOMEASSISTANT_STATES - void on_home_assistant_state_response(const HomeAssistantStateResponse &msg) override; + void on_home_assistant_state_response(const HomeAssistantStateResponse &msg); #endif #ifdef USE_HOMEASSISTANT_TIME - void on_get_time_response(const GetTimeResponse &value) override; + void on_get_time_response(const GetTimeResponse &value); #endif - void on_hello_request(const HelloRequest &msg) override; - void on_disconnect_request() override; - void on_ping_request() override; - void on_device_info_request() override; - void on_list_entities_request() override { this->begin_iterator_(ActiveIterator::LIST_ENTITIES); } - void on_subscribe_states_request() override { + void on_hello_request(const HelloRequest &msg); + void on_disconnect_request(); + void on_ping_request(); + void on_device_info_request(); + void on_list_entities_request() { this->begin_iterator_(ActiveIterator::LIST_ENTITIES); } + void on_subscribe_states_request() { this->flags_.state_subscription = true; // Start initial state iterator only if no iterator is active // If list_entities is running, we'll start initial_state when it completes @@ -237,7 +255,7 @@ class APIConnection final : public APIServerConnectionBase { this->begin_iterator_(ActiveIterator::INITIAL_STATE); } } - void on_subscribe_logs_request(const SubscribeLogsRequest &msg) override { + void on_subscribe_logs_request(const SubscribeLogsRequest &msg) { this->flags_.log_subscription = msg.level; if (msg.dump_config) App.schedule_dump_config(); @@ -249,13 +267,13 @@ class APIConnection final : public APIServerConnectionBase { #endif } #ifdef USE_API_HOMEASSISTANT_SERVICES - void on_subscribe_homeassistant_services_request() override { this->flags_.service_call_subscription = true; } + void on_subscribe_homeassistant_services_request() { this->flags_.service_call_subscription = true; } #endif #ifdef USE_API_HOMEASSISTANT_STATES - void on_subscribe_home_assistant_states_request() override; + void on_subscribe_home_assistant_states_request(); #endif #ifdef USE_API_USER_DEFINED_ACTIONS - void on_execute_service_request(const ExecuteServiceRequest &msg) override; + void on_execute_service_request(const ExecuteServiceRequest &msg); #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES void send_execute_service_response(uint32_t call_id, bool success, StringRef error_message); #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON @@ -265,13 +283,13 @@ class APIConnection final : public APIServerConnectionBase { #endif // USE_API_USER_DEFINED_ACTION_RESPONSES #endif #ifdef USE_API_NOISE - void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) override; + void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg); #endif - bool is_authenticated() override { + bool is_authenticated() { return static_cast(this->flags_.connection_state) == ConnectionState::AUTHENTICATED; } - bool is_connection_setup() override { + bool is_connection_setup() { return static_cast(this->flags_.connection_state) == ConnectionState::CONNECTED || this->is_authenticated(); } @@ -284,8 +302,8 @@ class APIConnection final : public APIServerConnectionBase { (this->client_api_version_major_ == major && this->client_api_version_minor_ >= minor); } - void on_fatal_error() override; - void on_no_setup_connection() override; + void on_fatal_error(); + void on_no_setup_connection(); // Function pointer type for type-erased message encoding using MessageEncodeFn = void (*)(const void *, ProtoWriteBuffer &); @@ -324,7 +342,7 @@ class APIConnection final : public APIServerConnectionBase { return true; return this->try_to_clear_buffer_slow_(log_out_of_space); } - bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) override; + bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type); const char *get_name() const { return this->helper_->get_client_name(); } /// Get peer name (IP address) into caller-provided buffer, returns buf for convenience diff --git a/esphome/components/api/api_pb2_service.cpp b/esphome/components/api/api_pb2_service.cpp index f2f7fa5238..d86cf912db 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -1,6 +1,7 @@ // This file was automatically generated with a tool. // See script/api_protobuf/api_protobuf.py #include "api_pb2_service.h" +#include "api_connection.h" #include "esphome/core/log.h" namespace esphome::api { @@ -20,7 +21,7 @@ void APIServerConnectionBase::log_receive_message_(const LogString *name) { } #endif -void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) { +void APIConnection::read_message_(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) { // Check authentication/connection requirements switch (msg_type) { case HelloRequest::MESSAGE_TYPE: // No setup required diff --git a/esphome/components/api/api_pb2_service.h b/esphome/components/api/api_pb2_service.h index 10fd88d8e1..4925a6497a 100644 --- a/esphome/components/api/api_pb2_service.h +++ b/esphome/components/api/api_pb2_service.h @@ -8,7 +8,7 @@ namespace esphome::api { -class APIServerConnectionBase : public ProtoService { +class APIServerConnectionBase { public: #ifdef HAS_PROTO_MESSAGE_DUMP protected: @@ -19,227 +19,223 @@ class APIServerConnectionBase : public ProtoService { public: #endif - virtual void on_hello_request(const HelloRequest &value){}; + void on_hello_request(const HelloRequest &value){}; - virtual void on_disconnect_request(){}; - virtual void on_disconnect_response(){}; - virtual void on_ping_request(){}; - virtual void on_ping_response(){}; - virtual void on_device_info_request(){}; + void on_disconnect_request(){}; + void on_disconnect_response(){}; + void on_ping_request(){}; + void on_ping_response(){}; + void on_device_info_request(){}; - virtual void on_list_entities_request(){}; + void on_list_entities_request(){}; - virtual void on_subscribe_states_request(){}; + void on_subscribe_states_request(){}; #ifdef USE_COVER - virtual void on_cover_command_request(const CoverCommandRequest &value){}; + void on_cover_command_request(const CoverCommandRequest &value){}; #endif #ifdef USE_FAN - virtual void on_fan_command_request(const FanCommandRequest &value){}; + void on_fan_command_request(const FanCommandRequest &value){}; #endif #ifdef USE_LIGHT - virtual void on_light_command_request(const LightCommandRequest &value){}; + void on_light_command_request(const LightCommandRequest &value){}; #endif #ifdef USE_SWITCH - virtual void on_switch_command_request(const SwitchCommandRequest &value){}; + void on_switch_command_request(const SwitchCommandRequest &value){}; #endif - virtual void on_subscribe_logs_request(const SubscribeLogsRequest &value){}; + void on_subscribe_logs_request(const SubscribeLogsRequest &value){}; #ifdef USE_API_NOISE - virtual void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &value){}; + void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &value){}; #endif #ifdef USE_API_HOMEASSISTANT_SERVICES - virtual void on_subscribe_homeassistant_services_request(){}; + void on_subscribe_homeassistant_services_request(){}; #endif #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES - virtual void on_homeassistant_action_response(const HomeassistantActionResponse &value){}; + void on_homeassistant_action_response(const HomeassistantActionResponse &value){}; #endif #ifdef USE_API_HOMEASSISTANT_STATES - virtual void on_subscribe_home_assistant_states_request(){}; + void on_subscribe_home_assistant_states_request(){}; #endif #ifdef USE_API_HOMEASSISTANT_STATES - virtual void on_home_assistant_state_response(const HomeAssistantStateResponse &value){}; + void on_home_assistant_state_response(const HomeAssistantStateResponse &value){}; #endif - virtual void on_get_time_response(const GetTimeResponse &value){}; + void on_get_time_response(const GetTimeResponse &value){}; #ifdef USE_API_USER_DEFINED_ACTIONS - virtual void on_execute_service_request(const ExecuteServiceRequest &value){}; + void on_execute_service_request(const ExecuteServiceRequest &value){}; #endif #ifdef USE_CAMERA - virtual void on_camera_image_request(const CameraImageRequest &value){}; + void on_camera_image_request(const CameraImageRequest &value){}; #endif #ifdef USE_CLIMATE - virtual void on_climate_command_request(const ClimateCommandRequest &value){}; + void on_climate_command_request(const ClimateCommandRequest &value){}; #endif #ifdef USE_WATER_HEATER - virtual void on_water_heater_command_request(const WaterHeaterCommandRequest &value){}; + void on_water_heater_command_request(const WaterHeaterCommandRequest &value){}; #endif #ifdef USE_NUMBER - virtual void on_number_command_request(const NumberCommandRequest &value){}; + void on_number_command_request(const NumberCommandRequest &value){}; #endif #ifdef USE_SELECT - virtual void on_select_command_request(const SelectCommandRequest &value){}; + void on_select_command_request(const SelectCommandRequest &value){}; #endif #ifdef USE_SIREN - virtual void on_siren_command_request(const SirenCommandRequest &value){}; + void on_siren_command_request(const SirenCommandRequest &value){}; #endif #ifdef USE_LOCK - virtual void on_lock_command_request(const LockCommandRequest &value){}; + void on_lock_command_request(const LockCommandRequest &value){}; #endif #ifdef USE_BUTTON - virtual void on_button_command_request(const ButtonCommandRequest &value){}; + void on_button_command_request(const ButtonCommandRequest &value){}; #endif #ifdef USE_MEDIA_PLAYER - virtual void on_media_player_command_request(const MediaPlayerCommandRequest &value){}; + void on_media_player_command_request(const MediaPlayerCommandRequest &value){}; #endif #ifdef USE_BLUETOOTH_PROXY - virtual void on_subscribe_bluetooth_le_advertisements_request( - const SubscribeBluetoothLEAdvertisementsRequest &value){}; + void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &value){}; #endif #ifdef USE_BLUETOOTH_PROXY - virtual void on_bluetooth_device_request(const BluetoothDeviceRequest &value){}; + void on_bluetooth_device_request(const BluetoothDeviceRequest &value){}; #endif #ifdef USE_BLUETOOTH_PROXY - virtual void on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &value){}; + void on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &value){}; #endif #ifdef USE_BLUETOOTH_PROXY - virtual void on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &value){}; + void on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &value){}; #endif #ifdef USE_BLUETOOTH_PROXY - virtual void on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &value){}; + void on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &value){}; #endif #ifdef USE_BLUETOOTH_PROXY - virtual void on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &value){}; + void on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &value){}; #endif #ifdef USE_BLUETOOTH_PROXY - virtual void on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &value){}; + void on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &value){}; #endif #ifdef USE_BLUETOOTH_PROXY - virtual void on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &value){}; + void on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &value){}; #endif #ifdef USE_BLUETOOTH_PROXY - virtual void on_subscribe_bluetooth_connections_free_request(){}; + void on_subscribe_bluetooth_connections_free_request(){}; #endif #ifdef USE_BLUETOOTH_PROXY - virtual void on_unsubscribe_bluetooth_le_advertisements_request(){}; + void on_unsubscribe_bluetooth_le_advertisements_request(){}; #endif #ifdef USE_BLUETOOTH_PROXY - virtual void on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &value){}; + void on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &value){}; #endif #ifdef USE_VOICE_ASSISTANT - virtual void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &value){}; + void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &value){}; #endif #ifdef USE_VOICE_ASSISTANT - virtual void on_voice_assistant_response(const VoiceAssistantResponse &value){}; + void on_voice_assistant_response(const VoiceAssistantResponse &value){}; #endif #ifdef USE_VOICE_ASSISTANT - virtual void on_voice_assistant_event_response(const VoiceAssistantEventResponse &value){}; + void on_voice_assistant_event_response(const VoiceAssistantEventResponse &value){}; #endif #ifdef USE_VOICE_ASSISTANT - virtual void on_voice_assistant_audio(const VoiceAssistantAudio &value){}; + void on_voice_assistant_audio(const VoiceAssistantAudio &value){}; #endif #ifdef USE_VOICE_ASSISTANT - virtual void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &value){}; + void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &value){}; #endif #ifdef USE_VOICE_ASSISTANT - virtual void on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &value){}; + void on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &value){}; #endif #ifdef USE_VOICE_ASSISTANT - virtual void on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &value){}; + void on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &value){}; #endif #ifdef USE_VOICE_ASSISTANT - virtual void on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &value){}; + void on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &value){}; #endif #ifdef USE_ALARM_CONTROL_PANEL - virtual void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &value){}; + void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &value){}; #endif #ifdef USE_TEXT - virtual void on_text_command_request(const TextCommandRequest &value){}; + void on_text_command_request(const TextCommandRequest &value){}; #endif #ifdef USE_DATETIME_DATE - virtual void on_date_command_request(const DateCommandRequest &value){}; + void on_date_command_request(const DateCommandRequest &value){}; #endif #ifdef USE_DATETIME_TIME - virtual void on_time_command_request(const TimeCommandRequest &value){}; + void on_time_command_request(const TimeCommandRequest &value){}; #endif #ifdef USE_VALVE - virtual void on_valve_command_request(const ValveCommandRequest &value){}; + void on_valve_command_request(const ValveCommandRequest &value){}; #endif #ifdef USE_DATETIME_DATETIME - virtual void on_date_time_command_request(const DateTimeCommandRequest &value){}; + void on_date_time_command_request(const DateTimeCommandRequest &value){}; #endif #ifdef USE_UPDATE - virtual void on_update_command_request(const UpdateCommandRequest &value){}; + void on_update_command_request(const UpdateCommandRequest &value){}; #endif #ifdef USE_ZWAVE_PROXY - virtual void on_z_wave_proxy_frame(const ZWaveProxyFrame &value){}; + void on_z_wave_proxy_frame(const ZWaveProxyFrame &value){}; #endif #ifdef USE_ZWAVE_PROXY - virtual void on_z_wave_proxy_request(const ZWaveProxyRequest &value){}; + void on_z_wave_proxy_request(const ZWaveProxyRequest &value){}; #endif #ifdef USE_IR_RF - virtual void on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &value){}; + void on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &value){}; #endif #ifdef USE_SERIAL_PROXY - virtual void on_serial_proxy_configure_request(const SerialProxyConfigureRequest &value){}; + void on_serial_proxy_configure_request(const SerialProxyConfigureRequest &value){}; #endif #ifdef USE_SERIAL_PROXY - virtual void on_serial_proxy_write_request(const SerialProxyWriteRequest &value){}; + void on_serial_proxy_write_request(const SerialProxyWriteRequest &value){}; #endif #ifdef USE_SERIAL_PROXY - virtual void on_serial_proxy_set_modem_pins_request(const SerialProxySetModemPinsRequest &value){}; + void on_serial_proxy_set_modem_pins_request(const SerialProxySetModemPinsRequest &value){}; #endif #ifdef USE_SERIAL_PROXY - virtual void on_serial_proxy_get_modem_pins_request(const SerialProxyGetModemPinsRequest &value){}; + void on_serial_proxy_get_modem_pins_request(const SerialProxyGetModemPinsRequest &value){}; #endif #ifdef USE_SERIAL_PROXY - virtual void on_serial_proxy_request(const SerialProxyRequest &value){}; + void on_serial_proxy_request(const SerialProxyRequest &value){}; #endif #ifdef USE_BLUETOOTH_PROXY - virtual void on_bluetooth_set_connection_params_request(const BluetoothSetConnectionParamsRequest &value){}; + void on_bluetooth_set_connection_params_request(const BluetoothSetConnectionParamsRequest &value){}; #endif - - protected: - void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) override; }; } // namespace esphome::api diff --git a/esphome/components/api/proto.h b/esphome/components/api/proto.h index cd22915703..a1d825ead9 100644 --- a/esphome/components/api/proto.h +++ b/esphome/components/api/proto.h @@ -711,26 +711,7 @@ inline void ProtoLengthDelimited::decode_to_message(ProtoDecodableMessage &msg) template const char *proto_enum_to_string(T value); -class ProtoService { - public: - protected: - virtual bool is_authenticated() = 0; - virtual bool is_connection_setup() = 0; - virtual void on_fatal_error() = 0; - virtual void on_no_setup_connection() = 0; - virtual bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) = 0; - virtual void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) = 0; - - // Authentication helper methods - inline bool check_connection_setup_() { - if (!this->is_connection_setup()) { - this->on_no_setup_connection(); - return false; - } - return true; - } - - inline bool check_authenticated_() { return this->check_connection_setup_(); } -}; +// ProtoService removed — its methods were inlined into APIConnection. +// APIConnection is the concrete server-side implementation; the extra virtual layer was unnecessary. } // namespace esphome::api diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index aca31e49c8..221ccc8d69 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -2608,7 +2608,7 @@ def build_service_message_type( is_empty = not has_fields if is_empty: EMPTY_MESSAGES.add(mt.name) - hout += f"virtual void {func}({'' if is_empty else f'const {mt.name} &value'}){{}};\n" + hout += f"void {func}({'' if is_empty else f'const {mt.name} &value'}){{}};\n" case = "" if not is_empty: case += f"{mt.name} msg;\n" @@ -2960,6 +2960,7 @@ namespace esphome::api { cpp = FILE_HEADER cpp += """\ #include "api_pb2_service.h" +#include "api_connection.h" #include "esphome/core/log.h" namespace esphome::api { @@ -2970,7 +2971,7 @@ static const char *const TAG = "api.service"; class_name = "APIServerConnectionBase" - hpp += f"class {class_name} : public ProtoService {{\n" + hpp += f"class {class_name} {{\n" hpp += " public:\n" # Add logging helper method declarations @@ -3063,11 +3064,11 @@ static const char *const TAG = "api.service"; result += "#endif\n" return result - # Generate read_message with auth check before dispatch - hpp += " protected:\n" - hpp += " void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) override;\n" + # Generate read_message_ as APIConnection method (not base class) so the compiler + # can devirtualize and inline the on_* handler calls within the same class. + # APIConnection declares this method in api_connection.h. - out = f"void {class_name}::read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) {{\n" + out = "void APIConnection::read_message_(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) {\n" # Auth check block before dispatch switch out += " // Check authentication/connection requirements\n" From 5560c9eef78e2a7be9fc5d728e767c34a23630c1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 22 Mar 2026 21:10:51 -1000 Subject: [PATCH 4/4] [test] Fix flakey ld2412 integration test race condition (#15100) --- tests/integration/test_uart_mock_ld2412.py | 33 +++++++++------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/tests/integration/test_uart_mock_ld2412.py b/tests/integration/test_uart_mock_ld2412.py index 9b928ef14f..12aa3f8397 100644 --- a/tests/integration/test_uart_mock_ld2412.py +++ b/tests/integration/test_uart_mock_ld2412.py @@ -79,9 +79,15 @@ async def test_uart_mock_ld2412( ], ) - # Signal when we see recovery frame values + # Signal when we see all recovery frame values recovery_received = collector.add_waiter( - lambda: pytest.approx(50.0) in collector.sensor_states["moving_distance"] + lambda: ( + pytest.approx(50.0) in collector.sensor_states["moving_distance"] + and pytest.approx(75.0) in collector.sensor_states["still_distance"] + and pytest.approx(100.0) in collector.sensor_states["moving_energy"] + and pytest.approx(80.0) in collector.sensor_states["still_energy"] + and pytest.approx(50.0) in collector.sensor_states["detection_distance"] + ) ) async with ( @@ -150,23 +156,12 @@ async def test_uart_mock_ld2412( ) # Recovery frame: moving=50, still=75, energy=100/80, detect=50 - recovery_idx = next( - i - for i, v in enumerate(collector.sensor_states["moving_distance"]) - if v == pytest.approx(50.0) - ) - assert collector.sensor_states["still_distance"][recovery_idx] == pytest.approx( - 75.0 - ) - assert collector.sensor_states["moving_energy"][recovery_idx] == pytest.approx( - 100.0 - ) - assert collector.sensor_states["still_energy"][recovery_idx] == pytest.approx( - 80.0 - ) - assert collector.sensor_states["detection_distance"][ - recovery_idx - ] == pytest.approx(50.0) + # Check values exist (waiter already ensured all are present) + assert pytest.approx(50.0) in collector.sensor_states["moving_distance"] + assert pytest.approx(75.0) in collector.sensor_states["still_distance"] + assert pytest.approx(100.0) in collector.sensor_states["moving_energy"] + assert pytest.approx(80.0) in collector.sensor_states["still_energy"] + assert pytest.approx(50.0) in collector.sensor_states["detection_distance"] # Verify binary sensors detected targets (from Phase 1 frame) assert collector.binary_states["has_target"][0] is True