From 26c98a1e257cd78b29ae59123439042894952b7d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 6 Feb 2026 23:54:52 +0100 Subject: [PATCH 01/42] [ld2420] Batch UART reads to reduce loop overhead --- esphome/components/ld2420/ld2420.cpp | 30 +++++++++++++++++++++++----- esphome/components/ld2420/ld2420.h | 2 ++ 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/esphome/components/ld2420/ld2420.cpp b/esphome/components/ld2420/ld2420.cpp index 10c623bce0..2abeccc7ac 100644 --- a/esphome/components/ld2420/ld2420.cpp +++ b/esphome/components/ld2420/ld2420.cpp @@ -335,9 +335,10 @@ void LD2420Component::revert_config_action() { void LD2420Component::loop() { // If there is a active send command do not process it here, the send command call will handle it. - while (!this->cmd_active_ && this->available()) { - this->readline_(this->read(), this->buffer_data_, MAX_LINE_LENGTH); + if (this->cmd_active_) { + return; } + this->read_batch_(this->buffer_data_); } void LD2420Component::update_radar_data(uint16_t const *gate_energy, uint8_t sample_number) { @@ -539,6 +540,27 @@ void LD2420Component::handle_simple_mode_(const uint8_t *inbuf, int len) { } } +void LD2420Component::read_batch_(std::span buffer) { + int avail = this->available(); + if (avail == 0) { + return; + } + + // Read all available bytes in batches to reduce UART call overhead. + uint8_t buf[MAX_LINE_LENGTH]; + while (avail > 0) { + size_t to_read = std::min(static_cast(avail), sizeof(buf)); + if (!this->read_array(buf, to_read)) { + break; + } + avail -= to_read; + + for (size_t i = 0; i < to_read; i++) { + this->readline_(buf[i], buffer.data(), buffer.size()); + } + } +} + void LD2420Component::handle_ack_data_(uint8_t *buffer, int len) { this->cmd_reply_.command = buffer[CMD_FRAME_COMMAND]; this->cmd_reply_.length = buffer[CMD_FRAME_DATA_LENGTH]; @@ -645,9 +667,7 @@ int LD2420Component::send_cmd_from_array(CmdFrameT frame) { } while (!this->cmd_reply_.ack) { - while (this->available()) { - this->readline_(this->read(), ack_buffer, sizeof(ack_buffer)); - } + this->read_batch_(ack_buffer); delay_microseconds_safe(1450); // Wait on an Rx from the LD2420 for up to 3 1 second loops, otherwise it could trigger a WDT. if ((millis() - start_millis) > 1000) { diff --git a/esphome/components/ld2420/ld2420.h b/esphome/components/ld2420/ld2420.h index 50ddf45264..6d81f86497 100644 --- a/esphome/components/ld2420/ld2420.h +++ b/esphome/components/ld2420/ld2420.h @@ -4,6 +4,7 @@ #include "esphome/components/uart/uart.h" #include "esphome/core/automation.h" #include "esphome/core/helpers.h" +#include #ifdef USE_TEXT_SENSOR #include "esphome/components/text_sensor/text_sensor.h" #endif @@ -165,6 +166,7 @@ class LD2420Component : public Component, public uart::UARTDevice { void handle_energy_mode_(uint8_t *buffer, int len); void handle_ack_data_(uint8_t *buffer, int len); void readline_(int rx_data, uint8_t *buffer, int len); + void read_batch_(std::span buffer); void set_calibration_(bool state) { this->calibration_ = state; }; bool get_calibration_() { return this->calibration_; }; From f19bb2cd0afd9cc04e17e86cf8be2ef469540b54 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 6 Feb 2026 23:59:38 +0100 Subject: [PATCH 02/42] [modbus] Batch UART reads to reduce loop overhead --- esphome/components/modbus/modbus.cpp | 31 +++++++++++++++++++--------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/esphome/components/modbus/modbus.cpp b/esphome/components/modbus/modbus.cpp index 5e9387b843..a2bfcf3292 100644 --- a/esphome/components/modbus/modbus.cpp +++ b/esphome/components/modbus/modbus.cpp @@ -19,16 +19,27 @@ void Modbus::setup() { void Modbus::loop() { const uint32_t now = App.get_loop_component_start_time(); - while (this->available()) { - uint8_t byte; - this->read_byte(&byte); - if (this->parse_modbus_byte_(byte)) { - this->last_modbus_byte_ = now; - } else { - size_t at = this->rx_buffer_.size(); - if (at > 0) { - ESP_LOGV(TAG, "Clearing buffer of %d bytes - parse failed", at); - this->rx_buffer_.clear(); + int avail = this->available(); + if (avail > 0) { + // Read all available bytes in batches to reduce UART call overhead. + uint8_t buf[64]; + while (avail > 0) { + size_t to_read = std::min(static_cast(avail), sizeof(buf)); + if (!this->read_array(buf, to_read)) { + break; + } + avail -= to_read; + + for (size_t i = 0; i < to_read; i++) { + if (this->parse_modbus_byte_(buf[i])) { + this->last_modbus_byte_ = now; + } else { + size_t at = this->rx_buffer_.size(); + if (at > 0) { + ESP_LOGV(TAG, "Clearing buffer of %d bytes - parse failed", at); + this->rx_buffer_.clear(); + } + } } } } From 6b7c52799d44bf361208a7dd8698570e28ef1f8e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 7 Feb 2026 00:02:10 +0100 Subject: [PATCH 03/42] [nextion] Batch UART reads to reduce loop overhead --- esphome/components/nextion/nextion.cpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/esphome/components/nextion/nextion.cpp b/esphome/components/nextion/nextion.cpp index fd6ce0a24b..f3a24de30a 100644 --- a/esphome/components/nextion/nextion.cpp +++ b/esphome/components/nextion/nextion.cpp @@ -397,11 +397,21 @@ bool Nextion::remove_from_q_(bool report_empty) { } void Nextion::process_serial_() { - uint8_t d; + int avail = this->available(); + if (avail == 0) { + return; + } - while (this->available()) { - read_byte(&d); - this->command_data_ += d; + // Read all available bytes in batches to reduce UART call overhead. + uint8_t buf[64]; + while (avail > 0) { + size_t to_read = std::min(static_cast(avail), sizeof(buf)); + if (!this->read_array(buf, to_read)) { + break; + } + avail -= to_read; + + this->command_data_.append(reinterpret_cast(buf), to_read); } } // nextion.tech/instruction-set/ From 38aeb9be37a0d6e31fd0a5aaaaf4fc9169a532e4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 7 Feb 2026 00:04:22 +0100 Subject: [PATCH 04/42] [pylontech] Batch UART reads to reduce loop overhead --- esphome/components/pylontech/pylontech.cpp | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/esphome/components/pylontech/pylontech.cpp b/esphome/components/pylontech/pylontech.cpp index 1dc7caaf16..d724253256 100644 --- a/esphome/components/pylontech/pylontech.cpp +++ b/esphome/components/pylontech/pylontech.cpp @@ -56,17 +56,23 @@ void PylontechComponent::setup() { void PylontechComponent::update() { this->write_str("pwr\n"); } void PylontechComponent::loop() { - if (this->available() > 0) { + int avail = this->available(); + if (avail > 0) { // pylontech sends a lot of data very suddenly // we need to quickly put it all into our own buffer, otherwise the uart's buffer will overflow - uint8_t data; int recv = 0; - while (this->available() > 0) { - if (this->read_byte(&data)) { - buffer_[buffer_index_write_] += (char) data; - recv++; - if (buffer_[buffer_index_write_].back() == static_cast(ASCII_LF) || - buffer_[buffer_index_write_].length() >= MAX_DATA_LENGTH_BYTES) { + uint8_t buf[64]; + while (avail > 0) { + size_t to_read = std::min(static_cast(avail), sizeof(buf)); + if (!this->read_array(buf, to_read)) { + break; + } + avail -= to_read; + recv += to_read; + + for (size_t i = 0; i < to_read; i++) { + buffer_[buffer_index_write_] += (char) buf[i]; + if (buf[i] == ASCII_LF || buffer_[buffer_index_write_].length() >= MAX_DATA_LENGTH_BYTES) { // complete line received buffer_index_write_ = (buffer_index_write_ + 1) % NUM_BUFFERS; } From 33c831dbb880ed2877c169a289214cbe791b2764 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 7 Feb 2026 00:07:05 +0100 Subject: [PATCH 05/42] Update esphome/components/nextion/nextion.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- esphome/components/nextion/nextion.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/nextion/nextion.cpp b/esphome/components/nextion/nextion.cpp index f3a24de30a..0bde05b305 100644 --- a/esphome/components/nextion/nextion.cpp +++ b/esphome/components/nextion/nextion.cpp @@ -411,7 +411,7 @@ void Nextion::process_serial_() { } avail -= to_read; - this->command_data_.append(reinterpret_cast(buf), to_read); + this->command_data_.append(reinterpret_cast(buf), to_read); } } // nextion.tech/instruction-set/ From 441ec35d9fd678f482d7c29d42d78dc421e9f6c6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 7 Feb 2026 00:07:44 +0100 Subject: [PATCH 06/42] [seeed_mr24hpc1/mr60fda2/mr60bha2] Batch UART reads to reduce per-loop overhead Replace byte-at-a-time read_byte() calls with batched read_array() in all three Seeed MR sensor components. Each read_byte() internally chains through read_array(data, 1) -> check_read_timeout_(1) -> available(), resulting in ~3 UART driver calls per byte. Batching into a 64-byte stack buffer reduces this to ~3 calls per loop iteration regardless of how many bytes are available. --- .../seeed_mr24hpc1/seeed_mr24hpc1.cpp | 19 ++++++++++---- .../seeed_mr60bha2/seeed_mr60bha2.cpp | 25 +++++++++++++------ .../seeed_mr60fda2/seeed_mr60fda2.cpp | 21 ++++++++++++---- 3 files changed, 48 insertions(+), 17 deletions(-) diff --git a/esphome/components/seeed_mr24hpc1/seeed_mr24hpc1.cpp b/esphome/components/seeed_mr24hpc1/seeed_mr24hpc1.cpp index 08d83f9390..949c220c3d 100644 --- a/esphome/components/seeed_mr24hpc1/seeed_mr24hpc1.cpp +++ b/esphome/components/seeed_mr24hpc1/seeed_mr24hpc1.cpp @@ -106,12 +106,21 @@ void MR24HPC1Component::update_() { // main loop void MR24HPC1Component::loop() { - uint8_t byte; + int avail = this->available(); + if (avail > 0) { + // Read all available bytes in batches to reduce UART call overhead. + uint8_t buf[64]; + while (avail > 0) { + size_t to_read = std::min(static_cast(avail), sizeof(buf)); + if (!this->read_array(buf, to_read)) { + break; + } + avail -= to_read; - // Is there data on the serial port - while (this->available()) { - this->read_byte(&byte); - this->r24_split_data_frame_(byte); // split data frame + for (size_t i = 0; i < to_read; i++) { + this->r24_split_data_frame_(buf[i]); // split data frame + } + } } if ((this->s_output_info_switch_flag_ == OUTPUT_SWTICH_OFF) && diff --git a/esphome/components/seeed_mr60bha2/seeed_mr60bha2.cpp b/esphome/components/seeed_mr60bha2/seeed_mr60bha2.cpp index b9ce1f9151..8cbf3e2e29 100644 --- a/esphome/components/seeed_mr60bha2/seeed_mr60bha2.cpp +++ b/esphome/components/seeed_mr60bha2/seeed_mr60bha2.cpp @@ -30,14 +30,25 @@ void MR60BHA2Component::dump_config() { // main loop void MR60BHA2Component::loop() { - uint8_t byte; + int avail = this->available(); + if (avail == 0) { + return; + } - // Is there data on the serial port - while (this->available()) { - this->read_byte(&byte); - this->rx_message_.push_back(byte); - if (!this->validate_message_()) { - this->rx_message_.clear(); + // Read all available bytes in batches to reduce UART call overhead. + uint8_t buf[64]; + while (avail > 0) { + size_t to_read = std::min(static_cast(avail), sizeof(buf)); + if (!this->read_array(buf, to_read)) { + break; + } + avail -= to_read; + + for (size_t i = 0; i < to_read; i++) { + this->rx_message_.push_back(buf[i]); + if (!this->validate_message_()) { + this->rx_message_.clear(); + } } } } diff --git a/esphome/components/seeed_mr60fda2/seeed_mr60fda2.cpp b/esphome/components/seeed_mr60fda2/seeed_mr60fda2.cpp index b5b5b4d05a..259e60119d 100644 --- a/esphome/components/seeed_mr60fda2/seeed_mr60fda2.cpp +++ b/esphome/components/seeed_mr60fda2/seeed_mr60fda2.cpp @@ -49,12 +49,23 @@ void MR60FDA2Component::setup() { // main loop void MR60FDA2Component::loop() { - uint8_t byte; + int avail = this->available(); + if (avail == 0) { + return; + } - // Is there data on the serial port - while (this->available()) { - this->read_byte(&byte); - this->split_frame_(byte); // split data frame + // Read all available bytes in batches to reduce UART call overhead. + uint8_t buf[64]; + while (avail > 0) { + size_t to_read = std::min(static_cast(avail), sizeof(buf)); + if (!this->read_array(buf, to_read)) { + break; + } + avail -= to_read; + + for (size_t i = 0; i < to_read; i++) { + this->split_frame_(buf[i]); // split data frame + } } } From 25762c62f8f78533b4bbe71d77d5122796e4bd17 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 7 Feb 2026 00:11:50 +0100 Subject: [PATCH 07/42] [dsmr] Batch UART reads to reduce per-loop overhead Replace byte-at-a-time read() calls with batched read_array() in all four UART read sites: receive_telegram_(), receive_encrypted_telegram_(), and two drain loops. Each read() internally chains through read_array(data, 1) -> check_read_timeout_(1) -> available(), resulting in ~3 UART driver calls per byte. Batching into a 64-byte stack buffer reduces this to ~3 calls per batch regardless of byte count. Extract drain_rx_buffer_() helper to deduplicate the two drain sites in ready_to_request_data_() and stop_requesting_data_(). --- esphome/components/dsmr/dsmr.cpp | 240 +++++++++++++++++-------------- esphome/components/dsmr/dsmr.h | 1 + 2 files changed, 135 insertions(+), 106 deletions(-) diff --git a/esphome/components/dsmr/dsmr.cpp b/esphome/components/dsmr/dsmr.cpp index c78d37bf5e..be43384330 100644 --- a/esphome/components/dsmr/dsmr.cpp +++ b/esphome/components/dsmr/dsmr.cpp @@ -40,9 +40,7 @@ bool Dsmr::ready_to_request_data_() { this->start_requesting_data_(); } if (!this->requesting_data_) { - while (this->available()) { - this->read(); - } + this->drain_rx_buffer_(); } } return this->requesting_data_; @@ -115,13 +113,19 @@ void Dsmr::stop_requesting_data_() { } else { ESP_LOGV(TAG, "Stop reading data from P1 port"); } - while (this->available()) { - this->read(); - } + this->drain_rx_buffer_(); this->requesting_data_ = false; } } +void Dsmr::drain_rx_buffer_() { + uint8_t buf[64]; + int avail; + while ((avail = this->available()) > 0) { + this->read_array(buf, std::min(static_cast(avail), sizeof(buf))); + } +} + void Dsmr::reset_telegram_() { this->header_found_ = false; this->footer_found_ = false; @@ -133,120 +137,144 @@ void Dsmr::reset_telegram_() { void Dsmr::receive_telegram_() { while (this->available_within_timeout_()) { - const char c = this->read(); + // Read all available bytes in batches to reduce UART call overhead. + uint8_t buf[64]; + int avail = this->available(); + while (avail > 0) { + size_t to_read = std::min(static_cast(avail), sizeof(buf)); + if (!this->read_array(buf, to_read)) + return; + avail -= to_read; - // Find a new telegram header, i.e. forward slash. - if (c == '/') { - ESP_LOGV(TAG, "Header of telegram found"); - this->reset_telegram_(); - this->header_found_ = true; - } - if (!this->header_found_) - continue; + for (size_t i = 0; i < to_read; i++) { + const char c = static_cast(buf[i]); - // Check for buffer overflow. - if (this->bytes_read_ >= this->max_telegram_len_) { - this->reset_telegram_(); - ESP_LOGE(TAG, "Error: telegram larger than buffer (%d bytes)", this->max_telegram_len_); - return; - } + // Find a new telegram header, i.e. forward slash. + if (c == '/') { + ESP_LOGV(TAG, "Header of telegram found"); + this->reset_telegram_(); + this->header_found_ = true; + } + if (!this->header_found_) + continue; - // Some v2.2 or v3 meters will send a new value which starts with '(' - // in a new line, while the value belongs to the previous ObisId. For - // proper parsing, remove these new line characters. - if (c == '(') { - while (true) { - auto previous_char = this->telegram_[this->bytes_read_ - 1]; - if (previous_char == '\n' || previous_char == '\r') { - this->bytes_read_--; - } else { - break; + // Check for buffer overflow. + if (this->bytes_read_ >= this->max_telegram_len_) { + this->reset_telegram_(); + ESP_LOGE(TAG, "Error: telegram larger than buffer (%d bytes)", this->max_telegram_len_); + return; + } + + // Some v2.2 or v3 meters will send a new value which starts with '(' + // in a new line, while the value belongs to the previous ObisId. For + // proper parsing, remove these new line characters. + if (c == '(') { + while (true) { + auto previous_char = this->telegram_[this->bytes_read_ - 1]; + if (previous_char == '\n' || previous_char == '\r') { + this->bytes_read_--; + } else { + break; + } + } + } + + // Store the byte in the buffer. + this->telegram_[this->bytes_read_] = c; + this->bytes_read_++; + + // Check for a footer, i.e. exclamation mark, followed by a hex checksum. + if (c == '!') { + ESP_LOGV(TAG, "Footer of telegram found"); + this->footer_found_ = true; + continue; + } + // Check for the end of the hex checksum, i.e. a newline. + if (this->footer_found_ && c == '\n') { + // Parse the telegram and publish sensor values. + this->parse_telegram(); + this->reset_telegram_(); + return; } } } - - // Store the byte in the buffer. - this->telegram_[this->bytes_read_] = c; - this->bytes_read_++; - - // Check for a footer, i.e. exclamation mark, followed by a hex checksum. - if (c == '!') { - ESP_LOGV(TAG, "Footer of telegram found"); - this->footer_found_ = true; - continue; - } - // Check for the end of the hex checksum, i.e. a newline. - if (this->footer_found_ && c == '\n') { - // Parse the telegram and publish sensor values. - this->parse_telegram(); - this->reset_telegram_(); - return; - } } } void Dsmr::receive_encrypted_telegram_() { while (this->available_within_timeout_()) { - const char c = this->read(); + // Read all available bytes in batches to reduce UART call overhead. + uint8_t buf[64]; + int avail = this->available(); + while (avail > 0) { + size_t to_read = std::min(static_cast(avail), sizeof(buf)); + if (!this->read_array(buf, to_read)) + return; + avail -= to_read; - // Find a new telegram start byte. - if (!this->header_found_) { - if ((uint8_t) c != 0xDB) { - continue; + for (size_t i = 0; i < to_read; i++) { + const char c = static_cast(buf[i]); + + // Find a new telegram start byte. + if (!this->header_found_) { + if ((uint8_t) c != 0xDB) { + continue; + } + ESP_LOGV(TAG, "Start byte 0xDB of encrypted telegram found"); + this->reset_telegram_(); + this->header_found_ = true; + } + + // Check for buffer overflow. + if (this->crypt_bytes_read_ >= this->max_telegram_len_) { + this->reset_telegram_(); + ESP_LOGE(TAG, "Error: encrypted telegram larger than buffer (%d bytes)", this->max_telegram_len_); + return; + } + + // Store the byte in the buffer. + this->crypt_telegram_[this->crypt_bytes_read_] = c; + this->crypt_bytes_read_++; + + // Read the length of the incoming encrypted telegram. + if (this->crypt_telegram_len_ == 0 && this->crypt_bytes_read_ > 20) { + // Complete header + data bytes + this->crypt_telegram_len_ = 13 + (this->crypt_telegram_[11] << 8 | this->crypt_telegram_[12]); + ESP_LOGV(TAG, "Encrypted telegram length: %d bytes", this->crypt_telegram_len_); + } + + // Check for the end of the encrypted telegram. + if (this->crypt_telegram_len_ == 0 || this->crypt_bytes_read_ != this->crypt_telegram_len_) { + continue; + } + ESP_LOGV(TAG, "End of encrypted telegram found"); + + // Decrypt the encrypted telegram. + GCM *gcmaes128{new GCM()}; + gcmaes128->setKey(this->decryption_key_.data(), gcmaes128->keySize()); + // the iv is 8 bytes of the system title + 4 bytes frame counter + // system title is at byte 2 and frame counter at byte 15 + for (int i = 10; i < 14; i++) + this->crypt_telegram_[i] = this->crypt_telegram_[i + 4]; + constexpr uint16_t iv_size{12}; + gcmaes128->setIV(&this->crypt_telegram_[2], iv_size); + gcmaes128->decrypt(reinterpret_cast(this->telegram_), + // the ciphertext start at byte 18 + &this->crypt_telegram_[18], + // cipher size + this->crypt_bytes_read_ - 17); + delete gcmaes128; // NOLINT(cppcoreguidelines-owning-memory) + + this->bytes_read_ = strnlen(this->telegram_, this->max_telegram_len_); + ESP_LOGV(TAG, "Decrypted telegram size: %d bytes", this->bytes_read_); + ESP_LOGVV(TAG, "Decrypted telegram: %s", this->telegram_); + + // Parse the decrypted telegram and publish sensor values. + this->parse_telegram(); + this->reset_telegram_(); + return; } - ESP_LOGV(TAG, "Start byte 0xDB of encrypted telegram found"); - this->reset_telegram_(); - this->header_found_ = true; } - - // Check for buffer overflow. - if (this->crypt_bytes_read_ >= this->max_telegram_len_) { - this->reset_telegram_(); - ESP_LOGE(TAG, "Error: encrypted telegram larger than buffer (%d bytes)", this->max_telegram_len_); - return; - } - - // Store the byte in the buffer. - this->crypt_telegram_[this->crypt_bytes_read_] = c; - this->crypt_bytes_read_++; - - // Read the length of the incoming encrypted telegram. - if (this->crypt_telegram_len_ == 0 && this->crypt_bytes_read_ > 20) { - // Complete header + data bytes - this->crypt_telegram_len_ = 13 + (this->crypt_telegram_[11] << 8 | this->crypt_telegram_[12]); - ESP_LOGV(TAG, "Encrypted telegram length: %d bytes", this->crypt_telegram_len_); - } - - // Check for the end of the encrypted telegram. - if (this->crypt_telegram_len_ == 0 || this->crypt_bytes_read_ != this->crypt_telegram_len_) { - continue; - } - ESP_LOGV(TAG, "End of encrypted telegram found"); - - // Decrypt the encrypted telegram. - GCM *gcmaes128{new GCM()}; - gcmaes128->setKey(this->decryption_key_.data(), gcmaes128->keySize()); - // the iv is 8 bytes of the system title + 4 bytes frame counter - // system title is at byte 2 and frame counter at byte 15 - for (int i = 10; i < 14; i++) - this->crypt_telegram_[i] = this->crypt_telegram_[i + 4]; - constexpr uint16_t iv_size{12}; - gcmaes128->setIV(&this->crypt_telegram_[2], iv_size); - gcmaes128->decrypt(reinterpret_cast(this->telegram_), - // the ciphertext start at byte 18 - &this->crypt_telegram_[18], - // cipher size - this->crypt_bytes_read_ - 17); - delete gcmaes128; // NOLINT(cppcoreguidelines-owning-memory) - - this->bytes_read_ = strnlen(this->telegram_, this->max_telegram_len_); - ESP_LOGV(TAG, "Decrypted telegram size: %d bytes", this->bytes_read_); - ESP_LOGVV(TAG, "Decrypted telegram: %s", this->telegram_); - - // Parse the decrypted telegram and publish sensor values. - this->parse_telegram(); - this->reset_telegram_(); - return; } } diff --git a/esphome/components/dsmr/dsmr.h b/esphome/components/dsmr/dsmr.h index b7e05a22b3..fafcf62b87 100644 --- a/esphome/components/dsmr/dsmr.h +++ b/esphome/components/dsmr/dsmr.h @@ -85,6 +85,7 @@ class Dsmr : public Component, public uart::UARTDevice { void receive_telegram_(); void receive_encrypted_telegram_(); void reset_telegram_(); + void drain_rx_buffer_(); /// Wait for UART data to become available within the read timeout. /// From c77d70c0938cc32cac6bd6d67fadafa9ba414eaf Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 7 Feb 2026 00:17:26 +0100 Subject: [PATCH 08/42] [tuya] Batch UART reads to reduce per-loop overhead Replace byte-at-a-time read_byte() calls with batched read_array() in loop(). Each read_byte() internally chains through read_array(data, 1) -> check_read_timeout_(1) -> available(), resulting in ~3 UART driver calls per byte. Batching into a 64-byte stack buffer reduces this to ~3 calls per loop iteration regardless of how many bytes are available. --- esphome/components/tuya/tuya.cpp | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index 2812fb6ad6..d0e00d425d 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -31,10 +31,21 @@ void Tuya::setup() { } void Tuya::loop() { - while (this->available()) { - uint8_t c; - this->read_byte(&c); - this->handle_char_(c); + int avail = this->available(); + if (avail > 0) { + // Read all available bytes in batches to reduce UART call overhead. + uint8_t buf[64]; + while (avail > 0) { + size_t to_read = std::min(static_cast(avail), sizeof(buf)); + if (!this->read_array(buf, to_read)) { + break; + } + avail -= to_read; + + for (size_t i = 0; i < to_read; i++) { + this->handle_char_(buf[i]); + } + } } process_command_queue_(); } From 8f6e1abbcef66bb038f60699b59918688d443046 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 7 Feb 2026 00:18:51 +0100 Subject: [PATCH 09/42] Check read_array return value in drain_rx_buffer_ --- esphome/components/dsmr/dsmr.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/esphome/components/dsmr/dsmr.cpp b/esphome/components/dsmr/dsmr.cpp index be43384330..88cfff2e4b 100644 --- a/esphome/components/dsmr/dsmr.cpp +++ b/esphome/components/dsmr/dsmr.cpp @@ -122,7 +122,9 @@ void Dsmr::drain_rx_buffer_() { uint8_t buf[64]; int avail; while ((avail = this->available()) > 0) { - this->read_array(buf, std::min(static_cast(avail), sizeof(buf))); + if (!this->read_array(buf, std::min(static_cast(avail), sizeof(buf)))) { + break; + } } } From cfbeea99831ec0d0abeb2e51157bba02db0d086d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 7 Feb 2026 00:22:00 +0100 Subject: [PATCH 10/42] [dlms_meter] Batch UART reads to reduce per-loop overhead Replace byte-at-a-time read_byte() calls with batched read_array() in loop(). Each read_byte() internally chains through read_array(data, 1) -> check_read_timeout_(1) -> available(), resulting in ~3 UART driver calls per byte. Batching into a 64-byte stack buffer reduces this to ~3 calls per loop iteration regardless of how many bytes are available. Also uses vector insert() for bulk append instead of per-byte push_back(), and caps reads to remaining buffer capacity upfront to avoid over-reading from UART. --- esphome/components/dlms_meter/dlms_meter.cpp | 27 +++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/esphome/components/dlms_meter/dlms_meter.cpp b/esphome/components/dlms_meter/dlms_meter.cpp index 6aa465143e..8f2329ad66 100644 --- a/esphome/components/dlms_meter/dlms_meter.cpp +++ b/esphome/components/dlms_meter/dlms_meter.cpp @@ -28,15 +28,28 @@ void DlmsMeterComponent::dump_config() { void DlmsMeterComponent::loop() { // Read while data is available, netznoe uses two frames so allow 2x max frame length - while (this->available()) { - if (this->receive_buffer_.size() >= MBUS_MAX_FRAME_LENGTH * 2) { + int avail = this->available(); + if (avail > 0) { + size_t remaining = MBUS_MAX_FRAME_LENGTH * 2 - this->receive_buffer_.size(); + if (remaining == 0) { ESP_LOGW(TAG, "Receive buffer full, dropping remaining bytes"); - break; + } else { + // Read all available bytes in batches to reduce UART call overhead. + // Cap reads to remaining buffer capacity. + if (static_cast(avail) > remaining) { + avail = remaining; + } + uint8_t buf[64]; + while (avail > 0) { + size_t to_read = std::min(static_cast(avail), sizeof(buf)); + if (!this->read_array(buf, to_read)) { + break; + } + avail -= to_read; + this->receive_buffer_.insert(this->receive_buffer_.end(), buf, buf + to_read); + this->last_read_ = millis(); + } } - uint8_t c; - this->read_byte(&c); - this->receive_buffer_.push_back(c); - this->last_read_ = millis(); } if (!this->receive_buffer_.empty() && millis() - this->last_read_ > this->read_timeout_) { From 39013388dd5fe5bdc07df033d1df4e921a082885 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 7 Feb 2026 00:26:33 +0100 Subject: [PATCH 11/42] pipsolar: batch UART reads to reduce per-loop overhead --- esphome/components/pipsolar/pipsolar.cpp | 64 +++++++++++++++--------- 1 file changed, 41 insertions(+), 23 deletions(-) diff --git a/esphome/components/pipsolar/pipsolar.cpp b/esphome/components/pipsolar/pipsolar.cpp index bafd5273da..d7b37f6130 100644 --- a/esphome/components/pipsolar/pipsolar.cpp +++ b/esphome/components/pipsolar/pipsolar.cpp @@ -13,9 +13,12 @@ void Pipsolar::setup() { } void Pipsolar::empty_uart_buffer_() { - uint8_t byte; - while (this->available()) { - this->read_byte(&byte); + uint8_t buf[64]; + int avail; + while ((avail = this->available()) > 0) { + if (!this->read_array(buf, std::min(static_cast(avail), sizeof(buf)))) { + break; + } } } @@ -94,32 +97,47 @@ void Pipsolar::loop() { } if (this->state_ == STATE_COMMAND || this->state_ == STATE_POLL) { - while (this->available()) { - uint8_t byte; - this->read_byte(&byte); - - // make sure data and null terminator fit in buffer - if (this->read_pos_ >= PIPSOLAR_READ_BUFFER_LENGTH - 1) { - this->read_pos_ = 0; - this->empty_uart_buffer_(); - ESP_LOGW(TAG, "response data too long, discarding."); + int avail = this->available(); + while (avail > 0) { + uint8_t buf[64]; + size_t to_read = std::min(static_cast(avail), sizeof(buf)); + if (!this->read_array(buf, to_read)) { break; } - this->read_buffer_[this->read_pos_] = byte; - this->read_pos_++; + avail -= to_read; + bool done = false; + for (size_t i = 0; i < to_read; i++) { + uint8_t byte = buf[i]; - // end of answer - if (byte == 0x0D) { - this->read_buffer_[this->read_pos_] = 0; - this->empty_uart_buffer_(); - if (this->state_ == STATE_POLL) { - this->state_ = STATE_POLL_COMPLETE; + // make sure data and null terminator fit in buffer + if (this->read_pos_ >= PIPSOLAR_READ_BUFFER_LENGTH - 1) { + this->read_pos_ = 0; + this->empty_uart_buffer_(); + ESP_LOGW(TAG, "response data too long, discarding."); + done = true; + break; } - if (this->state_ == STATE_COMMAND) { - this->state_ = STATE_COMMAND_COMPLETE; + this->read_buffer_[this->read_pos_] = byte; + this->read_pos_++; + + // end of answer + if (byte == 0x0D) { + this->read_buffer_[this->read_pos_] = 0; + this->empty_uart_buffer_(); + if (this->state_ == STATE_POLL) { + this->state_ = STATE_POLL_COMPLETE; + } + if (this->state_ == STATE_COMMAND) { + this->state_ = STATE_COMMAND_COMPLETE; + } + done = true; + break; } } - } // available + if (done) { + break; + } + } } if (this->state_ == STATE_COMMAND) { if (millis() - this->command_start_millis_ > esphome::pipsolar::Pipsolar::COMMAND_TIMEOUT) { From 3f3cf83aab5b7db9f5aef7634d7f530d51be08b4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 7 Feb 2026 00:32:33 +0100 Subject: [PATCH 12/42] rd03d: batch UART reads to reduce per-loop overhead --- esphome/components/rd03d/rd03d.cpp | 69 ++++++++++++++++++------------ 1 file changed, 41 insertions(+), 28 deletions(-) diff --git a/esphome/components/rd03d/rd03d.cpp b/esphome/components/rd03d/rd03d.cpp index 090e4dcf32..1b3212e2c0 100644 --- a/esphome/components/rd03d/rd03d.cpp +++ b/esphome/components/rd03d/rd03d.cpp @@ -1,4 +1,5 @@ #include "rd03d.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" #include @@ -80,37 +81,49 @@ void RD03DComponent::dump_config() { } void RD03DComponent::loop() { - while (this->available()) { - uint8_t byte = this->read(); - ESP_LOGVV(TAG, "Received byte: 0x%02X, buffer_pos: %d", byte, this->buffer_pos_); + int avail = this->available(); + if (avail <= 0) + return; - // Check if we're looking for frame header - if (this->buffer_pos_ < FRAME_HEADER_SIZE) { - if (byte == FRAME_HEADER[this->buffer_pos_]) { - this->buffer_[this->buffer_pos_++] = byte; - } else if (byte == FRAME_HEADER[0]) { - // Start over if we see a potential new header - this->buffer_[0] = byte; - this->buffer_pos_ = 1; - } else { + uint8_t buf[64]; + while (avail > 0) { + size_t to_read = std::min(static_cast(avail), sizeof(buf)); + if (!this->read_array(buf, to_read)) { + break; + } + avail -= to_read; + for (size_t i = 0; i < to_read; i++) { + uint8_t byte = buf[i]; + ESP_LOGVV(TAG, "Received byte: 0x%02X, buffer_pos: %d", byte, this->buffer_pos_); + + // Check if we're looking for frame header + if (this->buffer_pos_ < FRAME_HEADER_SIZE) { + if (byte == FRAME_HEADER[this->buffer_pos_]) { + this->buffer_[this->buffer_pos_++] = byte; + } else if (byte == FRAME_HEADER[0]) { + // Start over if we see a potential new header + this->buffer_[0] = byte; + this->buffer_pos_ = 1; + } else { + this->buffer_pos_ = 0; + } + continue; + } + + // Accumulate data bytes + this->buffer_[this->buffer_pos_++] = byte; + + // Check if we have a complete frame + if (this->buffer_pos_ == FRAME_SIZE) { + // Validate footer + if (this->buffer_[FRAME_SIZE - 2] == FRAME_FOOTER[0] && this->buffer_[FRAME_SIZE - 1] == FRAME_FOOTER[1]) { + this->process_frame_(); + } else { + ESP_LOGW(TAG, "Invalid frame footer: 0x%02X 0x%02X (expected 0x55 0xCC)", this->buffer_[FRAME_SIZE - 2], + this->buffer_[FRAME_SIZE - 1]); + } this->buffer_pos_ = 0; } - continue; - } - - // Accumulate data bytes - this->buffer_[this->buffer_pos_++] = byte; - - // Check if we have a complete frame - if (this->buffer_pos_ == FRAME_SIZE) { - // Validate footer - if (this->buffer_[FRAME_SIZE - 2] == FRAME_FOOTER[0] && this->buffer_[FRAME_SIZE - 1] == FRAME_FOOTER[1]) { - this->process_frame_(); - } else { - ESP_LOGW(TAG, "Invalid frame footer: 0x%02X 0x%02X (expected 0x55 0xCC)", this->buffer_[FRAME_SIZE - 2], - this->buffer_[FRAME_SIZE - 1]); - } - this->buffer_pos_ = 0; } } } From 04697ac223d1bddc37e567138b31fd9bcfce80dd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 7 Feb 2026 00:36:27 +0100 Subject: [PATCH 13/42] rf_bridge: batch UART reads to reduce per-loop overhead --- esphome/components/rf_bridge/rf_bridge.cpp | 23 ++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/esphome/components/rf_bridge/rf_bridge.cpp b/esphome/components/rf_bridge/rf_bridge.cpp index 8105767485..e33c13aafe 100644 --- a/esphome/components/rf_bridge/rf_bridge.cpp +++ b/esphome/components/rf_bridge/rf_bridge.cpp @@ -136,14 +136,21 @@ void RFBridgeComponent::loop() { this->last_bridge_byte_ = now; } - while (this->available()) { - uint8_t byte; - this->read_byte(&byte); - if (this->parse_bridge_byte_(byte)) { - ESP_LOGVV(TAG, "Parsed: 0x%02X", byte); - this->last_bridge_byte_ = now; - } else { - this->rx_buffer_.clear(); + int avail = this->available(); + while (avail > 0) { + uint8_t buf[64]; + size_t to_read = std::min(static_cast(avail), sizeof(buf)); + if (!this->read_array(buf, to_read)) { + break; + } + avail -= to_read; + for (size_t i = 0; i < to_read; i++) { + if (this->parse_bridge_byte_(buf[i])) { + ESP_LOGVV(TAG, "Parsed: 0x%02X", buf[i]); + this->last_bridge_byte_ = now; + } else { + this->rx_buffer_.clear(); + } } } } From 2a17592d57f083beddd7bb45523ed8727b89ce35 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 7 Feb 2026 00:38:54 +0100 Subject: [PATCH 14/42] dfplayer: batch UART reads to reduce per-loop overhead --- esphome/components/dfplayer/dfplayer.cpp | 275 ++++++++++++----------- 1 file changed, 144 insertions(+), 131 deletions(-) diff --git a/esphome/components/dfplayer/dfplayer.cpp b/esphome/components/dfplayer/dfplayer.cpp index 70bd42e1a5..60fe26e136 100644 --- a/esphome/components/dfplayer/dfplayer.cpp +++ b/esphome/components/dfplayer/dfplayer.cpp @@ -1,4 +1,5 @@ #include "dfplayer.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" namespace esphome { @@ -132,139 +133,151 @@ void DFPlayer::send_cmd_(uint8_t cmd, uint16_t argument) { void DFPlayer::loop() { // Read message - while (this->available()) { - uint8_t byte; - this->read_byte(&byte); + int avail = this->available(); + if (avail <= 0) + return; - if (this->read_pos_ == DFPLAYER_READ_BUFFER_LENGTH) - this->read_pos_ = 0; - - switch (this->read_pos_) { - case 0: // Start mark - if (byte != 0x7E) - continue; - break; - case 1: // Version - if (byte != 0xFF) { - ESP_LOGW(TAG, "Expected Version 0xFF, got %#02x", byte); - this->read_pos_ = 0; - continue; - } - break; - case 2: // Buffer length - if (byte != 0x06) { - ESP_LOGW(TAG, "Expected Buffer length 0x06, got %#02x", byte); - this->read_pos_ = 0; - continue; - } - break; - case 9: // End byte -#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE - char byte_sequence[100]; - byte_sequence[0] = '\0'; - for (size_t i = 0; i < this->read_pos_ + 1; ++i) { - snprintf(byte_sequence + strlen(byte_sequence), sizeof(byte_sequence) - strlen(byte_sequence), "%02X ", - this->read_buffer_[i]); - } - ESP_LOGVV(TAG, "Received byte sequence: %s", byte_sequence); -#endif - if (byte != 0xEF) { - ESP_LOGW(TAG, "Expected end byte 0xEF, got %#02x", byte); - this->read_pos_ = 0; - continue; - } - // Parse valid received command - uint8_t cmd = this->read_buffer_[3]; - uint16_t argument = (this->read_buffer_[5] << 8) | this->read_buffer_[6]; - - ESP_LOGV(TAG, "Received message cmd: %#02x arg %#04x", cmd, argument); - - switch (cmd) { - case 0x3A: - if (argument == 1) { - ESP_LOGI(TAG, "USB loaded"); - } else if (argument == 2) { - ESP_LOGI(TAG, "TF Card loaded"); - } - break; - case 0x3B: - if (argument == 1) { - ESP_LOGI(TAG, "USB unloaded"); - } else if (argument == 2) { - ESP_LOGI(TAG, "TF Card unloaded"); - } - break; - case 0x3F: - if (argument == 1) { - ESP_LOGI(TAG, "USB available"); - } else if (argument == 2) { - ESP_LOGI(TAG, "TF Card available"); - } else if (argument == 3) { - ESP_LOGI(TAG, "USB, TF Card available"); - } - break; - case 0x40: - ESP_LOGV(TAG, "Nack"); - this->ack_set_is_playing_ = false; - this->ack_reset_is_playing_ = false; - switch (argument) { - case 0x01: - ESP_LOGE(TAG, "Module is busy or uninitialized"); - break; - case 0x02: - ESP_LOGE(TAG, "Module is in sleep mode"); - break; - case 0x03: - ESP_LOGE(TAG, "Serial receive error"); - break; - case 0x04: - ESP_LOGE(TAG, "Checksum incorrect"); - break; - case 0x05: - ESP_LOGE(TAG, "Specified track is out of current track scope"); - this->is_playing_ = false; - break; - case 0x06: - ESP_LOGE(TAG, "Specified track is not found"); - this->is_playing_ = false; - break; - case 0x07: - ESP_LOGE(TAG, "Insertion error (an inserting operation only can be done when a track is being played)"); - break; - case 0x08: - ESP_LOGE(TAG, "SD card reading failed (SD card pulled out or damaged)"); - break; - case 0x09: - ESP_LOGE(TAG, "Entered into sleep mode"); - this->is_playing_ = false; - break; - } - break; - case 0x41: - ESP_LOGV(TAG, "Ack ok"); - this->is_playing_ |= this->ack_set_is_playing_; - this->is_playing_ &= !this->ack_reset_is_playing_; - this->ack_set_is_playing_ = false; - this->ack_reset_is_playing_ = false; - break; - case 0x3C: - ESP_LOGV(TAG, "Playback finished (USB drive)"); - this->is_playing_ = false; - this->on_finished_playback_callback_.call(); - case 0x3D: - ESP_LOGV(TAG, "Playback finished (SD card)"); - this->is_playing_ = false; - this->on_finished_playback_callback_.call(); - break; - default: - ESP_LOGE(TAG, "Received unknown cmd %#02x arg %#04x", cmd, argument); - } - this->sent_cmd_ = 0; - this->read_pos_ = 0; - continue; + uint8_t buf[64]; + while (avail > 0) { + size_t to_read = std::min(static_cast(avail), sizeof(buf)); + if (!this->read_array(buf, to_read)) { + break; + } + avail -= to_read; + for (size_t bi = 0; bi < to_read; bi++) { + uint8_t byte = buf[bi]; + + if (this->read_pos_ == DFPLAYER_READ_BUFFER_LENGTH) + this->read_pos_ = 0; + + switch (this->read_pos_) { + case 0: // Start mark + if (byte != 0x7E) + continue; + break; + case 1: // Version + if (byte != 0xFF) { + ESP_LOGW(TAG, "Expected Version 0xFF, got %#02x", byte); + this->read_pos_ = 0; + continue; + } + break; + case 2: // Buffer length + if (byte != 0x06) { + ESP_LOGW(TAG, "Expected Buffer length 0x06, got %#02x", byte); + this->read_pos_ = 0; + continue; + } + break; + case 9: // End byte +#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE + char byte_sequence[100]; + byte_sequence[0] = '\0'; + for (size_t i = 0; i < this->read_pos_ + 1; ++i) { + snprintf(byte_sequence + strlen(byte_sequence), sizeof(byte_sequence) - strlen(byte_sequence), "%02X ", + this->read_buffer_[i]); + } + ESP_LOGVV(TAG, "Received byte sequence: %s", byte_sequence); +#endif + if (byte != 0xEF) { + ESP_LOGW(TAG, "Expected end byte 0xEF, got %#02x", byte); + this->read_pos_ = 0; + continue; + } + // Parse valid received command + uint8_t cmd = this->read_buffer_[3]; + uint16_t argument = (this->read_buffer_[5] << 8) | this->read_buffer_[6]; + + ESP_LOGV(TAG, "Received message cmd: %#02x arg %#04x", cmd, argument); + + switch (cmd) { + case 0x3A: + if (argument == 1) { + ESP_LOGI(TAG, "USB loaded"); + } else if (argument == 2) { + ESP_LOGI(TAG, "TF Card loaded"); + } + break; + case 0x3B: + if (argument == 1) { + ESP_LOGI(TAG, "USB unloaded"); + } else if (argument == 2) { + ESP_LOGI(TAG, "TF Card unloaded"); + } + break; + case 0x3F: + if (argument == 1) { + ESP_LOGI(TAG, "USB available"); + } else if (argument == 2) { + ESP_LOGI(TAG, "TF Card available"); + } else if (argument == 3) { + ESP_LOGI(TAG, "USB, TF Card available"); + } + break; + case 0x40: + ESP_LOGV(TAG, "Nack"); + this->ack_set_is_playing_ = false; + this->ack_reset_is_playing_ = false; + switch (argument) { + case 0x01: + ESP_LOGE(TAG, "Module is busy or uninitialized"); + break; + case 0x02: + ESP_LOGE(TAG, "Module is in sleep mode"); + break; + case 0x03: + ESP_LOGE(TAG, "Serial receive error"); + break; + case 0x04: + ESP_LOGE(TAG, "Checksum incorrect"); + break; + case 0x05: + ESP_LOGE(TAG, "Specified track is out of current track scope"); + this->is_playing_ = false; + break; + case 0x06: + ESP_LOGE(TAG, "Specified track is not found"); + this->is_playing_ = false; + break; + case 0x07: + ESP_LOGE(TAG, + "Insertion error (an inserting operation only can be done when a track is being played)"); + break; + case 0x08: + ESP_LOGE(TAG, "SD card reading failed (SD card pulled out or damaged)"); + break; + case 0x09: + ESP_LOGE(TAG, "Entered into sleep mode"); + this->is_playing_ = false; + break; + } + break; + case 0x41: + ESP_LOGV(TAG, "Ack ok"); + this->is_playing_ |= this->ack_set_is_playing_; + this->is_playing_ &= !this->ack_reset_is_playing_; + this->ack_set_is_playing_ = false; + this->ack_reset_is_playing_ = false; + break; + case 0x3C: + ESP_LOGV(TAG, "Playback finished (USB drive)"); + this->is_playing_ = false; + this->on_finished_playback_callback_.call(); + case 0x3D: + ESP_LOGV(TAG, "Playback finished (SD card)"); + this->is_playing_ = false; + this->on_finished_playback_callback_.call(); + break; + default: + ESP_LOGE(TAG, "Received unknown cmd %#02x arg %#04x", cmd, argument); + } + this->sent_cmd_ = 0; + this->read_pos_ = 0; + continue; + } + this->read_buffer_[this->read_pos_] = byte; + this->read_pos_++; } - this->read_buffer_[this->read_pos_] = byte; - this->read_pos_++; } } void DFPlayer::dump_config() { From 836bfc625de90c405aa8ded9ab8b835f7fcd3200 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 7 Feb 2026 01:01:03 +0100 Subject: [PATCH 15/42] restore original byte-at-a-time read in send_cmd_from_array ack loop The ack polling loop has a tight timing requirement with delay_microseconds_safe(1450) between iterations. Snapshotting available() once could leave partial ack response bytes unread until after the delay, potentially breaking cold boot timing on some ld2420 units. Keep batch reads only in loop(). --- esphome/components/ld2420/ld2420.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/esphome/components/ld2420/ld2420.cpp b/esphome/components/ld2420/ld2420.cpp index 2abeccc7ac..01f05b21e5 100644 --- a/esphome/components/ld2420/ld2420.cpp +++ b/esphome/components/ld2420/ld2420.cpp @@ -667,7 +667,9 @@ int LD2420Component::send_cmd_from_array(CmdFrameT frame) { } while (!this->cmd_reply_.ack) { - this->read_batch_(ack_buffer); + while (this->available()) { + this->readline_(this->read(), ack_buffer, sizeof(ack_buffer)); + } delay_microseconds_safe(1450); // Wait on an Rx from the LD2420 for up to 3 1 second loops, otherwise it could trigger a WDT. if ((millis() - start_millis) > 1000) { From 456e0f2679cbf88ecda82f1425eea63fd3350e95 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 7 Feb 2026 18:37:32 -0600 Subject: [PATCH 16/42] revert this after https://github.com/libretiny-eu/libretiny/pull/361 --- .clang-tidy.hash | 2 +- esphome/components/libretiny/__init__.py | 25 +++++++++++++----------- platformio.ini | 2 +- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/.clang-tidy.hash b/.clang-tidy.hash index e3c1f2e30b..f62d6a02ad 100644 --- a/.clang-tidy.hash +++ b/.clang-tidy.hash @@ -1 +1 @@ -bb17a9237b701c18a0ec885a709409305d2b777e9d4a3cf49b2e71e2144b3fe0 +08deb6f9a4fca320183b1f5fed24db904fcd68d13d47a35e89bbe8f05d68091e diff --git a/esphome/components/libretiny/__init__.py b/esphome/components/libretiny/__init__.py index 5ae1118fa2..7cd1dc545f 100644 --- a/esphome/components/libretiny/__init__.py +++ b/esphome/components/libretiny/__init__.py @@ -33,6 +33,9 @@ from .const import ( CONF_UART_PORT, FAMILIES, FAMILY_BK7231N, + FAMILY_BK7231Q, + FAMILY_BK7231T, + FAMILY_BK7251, FAMILY_COMPONENT, FAMILY_FRIENDLY, FAMILY_RTL8710B, @@ -52,19 +55,14 @@ CODEOWNERS = ["@kuba2k2"] AUTO_LOAD = ["preferences"] IS_TARGET_PLATFORM = True -# BK7231N SDK options to disable unused features. +# BK72XX SDK options to disable unused features. # Disabling BLE saves ~21KB RAM and ~200KB Flash because BLE init code is # called unconditionally by the SDK. ESPHome doesn't use BLE on LibreTiny. # -# This only works on BK7231N (BLE 5.x). Other BK72XX chips using BLE 4.2 -# (BK7231T, BK7231Q, BK7251; BK7252 boards use the BK7251 family) have a bug -# where the BLE library still links and references undefined symbols when -# CFG_SUPPORT_BLE=0. -# # Other options like CFG_TX_EVM_TEST, CFG_RX_SENSITIVITY_TEST, CFG_SUPPORT_BKREG, # CFG_SUPPORT_OTA_HTTP, and CFG_USE_SPI_SLAVE were evaluated but provide no # NOLINT # measurable benefit - the linker already strips unreferenced code via -gc-sections. -_BK7231N_SYS_CONFIG_OPTIONS = [ +_BK72XX_SYS_CONFIG_OPTIONS = [ "CFG_SUPPORT_BLE=0", ] @@ -197,11 +195,11 @@ ARDUINO_VERSIONS = { "dev": (cv.Version(1, 12, 0), "https://github.com/libretiny-eu/libretiny.git"), "latest": ( cv.Version(1, 12, 0), - "https://github.com/bdraco/libretiny.git#7f52d41", + "https://github.com/bdraco/libretiny.git#39f407a", ), "recommended": ( cv.Version(1, 12, 0), - "https://github.com/bdraco/libretiny.git#7f52d41", + "https://github.com/bdraco/libretiny.git#39f407a", ), } @@ -385,9 +383,14 @@ async def component_to_code(config): cg.add_platformio_option("custom_fw_version", __version__) # Apply chip-specific SDK options to save RAM/Flash - if config[CONF_FAMILY] == FAMILY_BK7231N: + if config[CONF_FAMILY] in ( + FAMILY_BK7231N, + FAMILY_BK7231Q, + FAMILY_BK7231T, + FAMILY_BK7251, + ): cg.add_platformio_option( - "custom_options.sys_config#h", _BK7231N_SYS_CONFIG_OPTIONS + "custom_options.sys_config#h", _BK72XX_SYS_CONFIG_OPTIONS ) # Disable LWIP statistics to save RAM - not needed in production diff --git a/platformio.ini b/platformio.ini index 349bdc4341..2bfe9f89e9 100644 --- a/platformio.ini +++ b/platformio.ini @@ -214,7 +214,7 @@ build_unflags = [common:libretiny-arduino] extends = common:arduino # TODO: Revert to v1.12.0 tag once https://github.com/libretiny-eu/libretiny/pull/361 is released -platform = https://github.com/bdraco/libretiny.git#7f52d41 +platform = https://github.com/bdraco/libretiny.git#39f407a framework = arduino lib_compat_mode = soft lib_deps = From d73384ae46999b99b852b2f9f691b7d9dc86001d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 7 Feb 2026 19:07:15 -0600 Subject: [PATCH 17/42] revert this after https://github.com/libretiny-eu/libretiny/pull/361 --- esphome/components/libretiny/__init__.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/esphome/components/libretiny/__init__.py b/esphome/components/libretiny/__init__.py index 7cd1dc545f..6898f6c897 100644 --- a/esphome/components/libretiny/__init__.py +++ b/esphome/components/libretiny/__init__.py @@ -33,9 +33,6 @@ from .const import ( CONF_UART_PORT, FAMILIES, FAMILY_BK7231N, - FAMILY_BK7231Q, - FAMILY_BK7231T, - FAMILY_BK7251, FAMILY_COMPONENT, FAMILY_FRIENDLY, FAMILY_RTL8710B, @@ -55,14 +52,20 @@ CODEOWNERS = ["@kuba2k2"] AUTO_LOAD = ["preferences"] IS_TARGET_PLATFORM = True -# BK72XX SDK options to disable unused features. +# BK7231N SDK options to disable unused features. # Disabling BLE saves ~21KB RAM and ~200KB Flash because BLE init code is # called unconditionally by the SDK. ESPHome doesn't use BLE on LibreTiny. # +# This only works on BK7231N (BLE 5.x). Other BK72XX chips using BLE 4.2 +# (BK7231T, BK7231Q, BK7251) cannot disable BLE via custom_options because +# the LibreTiny builder reads CFG_SUPPORT_BLE from the sys_config file at +# build-script time to decide which sources to compile - the custom_options +# override only affects C preprocessor defines, not the builder's Python logic. +# # Other options like CFG_TX_EVM_TEST, CFG_RX_SENSITIVITY_TEST, CFG_SUPPORT_BKREG, # CFG_SUPPORT_OTA_HTTP, and CFG_USE_SPI_SLAVE were evaluated but provide no # NOLINT # measurable benefit - the linker already strips unreferenced code via -gc-sections. -_BK72XX_SYS_CONFIG_OPTIONS = [ +_BK7231N_SYS_CONFIG_OPTIONS = [ "CFG_SUPPORT_BLE=0", ] @@ -383,14 +386,9 @@ async def component_to_code(config): cg.add_platformio_option("custom_fw_version", __version__) # Apply chip-specific SDK options to save RAM/Flash - if config[CONF_FAMILY] in ( - FAMILY_BK7231N, - FAMILY_BK7231Q, - FAMILY_BK7231T, - FAMILY_BK7251, - ): + if config[CONF_FAMILY] == FAMILY_BK7231N: cg.add_platformio_option( - "custom_options.sys_config#h", _BK72XX_SYS_CONFIG_OPTIONS + "custom_options.sys_config#h", _BK7231N_SYS_CONFIG_OPTIONS ) # Disable LWIP statistics to save RAM - not needed in production From cac82280b34b949ee3fac2339971e26ea62fe1fc Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 8 Feb 2026 09:55:31 -0600 Subject: [PATCH 18/42] Collapse APIServerConnection intermediary layer Remove the 48 on_xxx overrides and 48 pure virtual methods from the generated APIServerConnection class. APIConnection now overrides on_xxx() directly from APIServerConnectionBase, eliminating one virtual call per message dispatch and ~96 vtable entries. --- esphome/components/api/api_connection.cpp | 108 ++++++--- esphome/components/api/api_connection.h | 109 +++++---- esphome/components/api/api_pb2_service.cpp | 194 --------------- esphome/components/api/api_pb2_service.h | 261 --------------------- script/api_protobuf/api_protobuf.py | 40 ---- 5 files changed, 134 insertions(+), 578 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index efc3d210b4..3cf46e6aa9 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -406,7 +406,7 @@ uint16_t APIConnection::try_send_cover_info(EntityBase *entity, APIConnection *c msg.device_class = cover->get_device_class_ref(); return fill_and_encode_entity_info(cover, msg, ListEntitiesCoverResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::cover_command(const CoverCommandRequest &msg) { +void APIConnection::on_cover_command_request(const CoverCommandRequest &msg) { ENTITY_COMMAND_MAKE_CALL(cover::Cover, cover, cover) if (msg.has_position) call.set_position(msg.position); @@ -449,7 +449,7 @@ uint16_t APIConnection::try_send_fan_info(EntityBase *entity, APIConnection *con msg.supported_preset_modes = &traits.supported_preset_modes(); return fill_and_encode_entity_info(fan, msg, ListEntitiesFanResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::fan_command(const FanCommandRequest &msg) { +void APIConnection::on_fan_command_request(const FanCommandRequest &msg) { ENTITY_COMMAND_MAKE_CALL(fan::Fan, fan, fan) if (msg.has_state) call.set_state(msg.state); @@ -517,7 +517,7 @@ uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *c msg.effects = &effects_list; return fill_and_encode_entity_info(light, msg, ListEntitiesLightResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::light_command(const LightCommandRequest &msg) { +void APIConnection::on_light_command_request(const LightCommandRequest &msg) { ENTITY_COMMAND_MAKE_CALL(light::LightState, light, light) if (msg.has_state) call.set_state(msg.state); @@ -594,7 +594,7 @@ uint16_t APIConnection::try_send_switch_info(EntityBase *entity, APIConnection * msg.device_class = a_switch->get_device_class_ref(); return fill_and_encode_entity_info(a_switch, msg, ListEntitiesSwitchResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::switch_command(const SwitchCommandRequest &msg) { +void APIConnection::on_switch_command_request(const SwitchCommandRequest &msg) { ENTITY_COMMAND_GET(switch_::Switch, a_switch, switch) if (msg.state) { @@ -692,7 +692,7 @@ uint16_t APIConnection::try_send_climate_info(EntityBase *entity, APIConnection msg.supported_swing_modes = &traits.get_supported_swing_modes(); return fill_and_encode_entity_info(climate, msg, ListEntitiesClimateResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::climate_command(const ClimateCommandRequest &msg) { +void APIConnection::on_climate_command_request(const ClimateCommandRequest &msg) { ENTITY_COMMAND_MAKE_CALL(climate::Climate, climate, climate) if (msg.has_mode) call.set_mode(static_cast(msg.mode)); @@ -742,7 +742,7 @@ uint16_t APIConnection::try_send_number_info(EntityBase *entity, APIConnection * msg.step = number->traits.get_step(); return fill_and_encode_entity_info(number, msg, ListEntitiesNumberResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::number_command(const NumberCommandRequest &msg) { +void APIConnection::on_number_command_request(const NumberCommandRequest &msg) { ENTITY_COMMAND_MAKE_CALL(number::Number, number, number) call.set_value(msg.state); call.perform(); @@ -767,7 +767,7 @@ uint16_t APIConnection::try_send_date_info(EntityBase *entity, APIConnection *co ListEntitiesDateResponse msg; return fill_and_encode_entity_info(date, msg, ListEntitiesDateResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::date_command(const DateCommandRequest &msg) { +void APIConnection::on_date_command_request(const DateCommandRequest &msg) { ENTITY_COMMAND_MAKE_CALL(datetime::DateEntity, date, date) call.set_date(msg.year, msg.month, msg.day); call.perform(); @@ -792,7 +792,7 @@ uint16_t APIConnection::try_send_time_info(EntityBase *entity, APIConnection *co ListEntitiesTimeResponse msg; return fill_and_encode_entity_info(time, msg, ListEntitiesTimeResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::time_command(const TimeCommandRequest &msg) { +void APIConnection::on_time_command_request(const TimeCommandRequest &msg) { ENTITY_COMMAND_MAKE_CALL(datetime::TimeEntity, time, time) call.set_time(msg.hour, msg.minute, msg.second); call.perform(); @@ -819,7 +819,7 @@ uint16_t APIConnection::try_send_datetime_info(EntityBase *entity, APIConnection ListEntitiesDateTimeResponse msg; return fill_and_encode_entity_info(datetime, msg, ListEntitiesDateTimeResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::datetime_command(const DateTimeCommandRequest &msg) { +void APIConnection::on_date_time_command_request(const DateTimeCommandRequest &msg) { ENTITY_COMMAND_MAKE_CALL(datetime::DateTimeEntity, datetime, datetime) call.set_datetime(msg.epoch_seconds); call.perform(); @@ -848,7 +848,7 @@ uint16_t APIConnection::try_send_text_info(EntityBase *entity, APIConnection *co msg.pattern = text->traits.get_pattern_ref(); return fill_and_encode_entity_info(text, msg, ListEntitiesTextResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::text_command(const TextCommandRequest &msg) { +void APIConnection::on_text_command_request(const TextCommandRequest &msg) { ENTITY_COMMAND_MAKE_CALL(text::Text, text, text) call.set_value(msg.state); call.perform(); @@ -874,7 +874,7 @@ uint16_t APIConnection::try_send_select_info(EntityBase *entity, APIConnection * msg.options = &select->traits.get_options(); return fill_and_encode_entity_info(select, msg, ListEntitiesSelectResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::select_command(const SelectCommandRequest &msg) { +void APIConnection::on_select_command_request(const SelectCommandRequest &msg) { ENTITY_COMMAND_MAKE_CALL(select::Select, select, select) call.set_option(msg.state.c_str(), msg.state.size()); call.perform(); @@ -888,7 +888,7 @@ uint16_t APIConnection::try_send_button_info(EntityBase *entity, APIConnection * msg.device_class = button->get_device_class_ref(); return fill_and_encode_entity_info(button, msg, ListEntitiesButtonResponse::MESSAGE_TYPE, conn, remaining_size); } -void esphome::api::APIConnection::button_command(const ButtonCommandRequest &msg) { +void esphome::api::APIConnection::on_button_command_request(const ButtonCommandRequest &msg) { ENTITY_COMMAND_GET(button::Button, button, button) button->press(); } @@ -914,7 +914,7 @@ uint16_t APIConnection::try_send_lock_info(EntityBase *entity, APIConnection *co msg.requires_code = a_lock->traits.get_requires_code(); return fill_and_encode_entity_info(a_lock, msg, ListEntitiesLockResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::lock_command(const LockCommandRequest &msg) { +void APIConnection::on_lock_command_request(const LockCommandRequest &msg) { ENTITY_COMMAND_GET(lock::Lock, a_lock, lock) switch (msg.command) { @@ -952,7 +952,7 @@ uint16_t APIConnection::try_send_valve_info(EntityBase *entity, APIConnection *c msg.supports_stop = traits.get_supports_stop(); return fill_and_encode_entity_info(valve, msg, ListEntitiesValveResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::valve_command(const ValveCommandRequest &msg) { +void APIConnection::on_valve_command_request(const ValveCommandRequest &msg) { ENTITY_COMMAND_MAKE_CALL(valve::Valve, valve, valve) if (msg.has_position) call.set_position(msg.position); @@ -996,7 +996,7 @@ uint16_t APIConnection::try_send_media_player_info(EntityBase *entity, APIConnec return fill_and_encode_entity_info(media_player, msg, ListEntitiesMediaPlayerResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::media_player_command(const MediaPlayerCommandRequest &msg) { +void APIConnection::on_media_player_command_request(const MediaPlayerCommandRequest &msg) { ENTITY_COMMAND_MAKE_CALL(media_player::MediaPlayer, media_player, media_player) if (msg.has_command) { call.set_command(static_cast(msg.command)); @@ -1063,7 +1063,7 @@ uint16_t APIConnection::try_send_camera_info(EntityBase *entity, APIConnection * ListEntitiesCameraResponse msg; return fill_and_encode_entity_info(camera, msg, ListEntitiesCameraResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::camera_image(const CameraImageRequest &msg) { +void APIConnection::on_camera_image_request(const CameraImageRequest &msg) { if (camera::Camera::instance() == nullptr) return; @@ -1092,32 +1092,33 @@ void APIConnection::on_get_time_response(const GetTimeResponse &value) { #endif #ifdef USE_BLUETOOTH_PROXY -void APIConnection::subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) { +void APIConnection::on_subscribe_bluetooth_le_advertisements_request( + const SubscribeBluetoothLEAdvertisementsRequest &msg) { bluetooth_proxy::global_bluetooth_proxy->subscribe_api_connection(this, msg.flags); } -void APIConnection::unsubscribe_bluetooth_le_advertisements() { +void APIConnection::on_unsubscribe_bluetooth_le_advertisements_request() { bluetooth_proxy::global_bluetooth_proxy->unsubscribe_api_connection(this); } -void APIConnection::bluetooth_device_request(const BluetoothDeviceRequest &msg) { +void APIConnection::on_bluetooth_device_request(const BluetoothDeviceRequest &msg) { bluetooth_proxy::global_bluetooth_proxy->bluetooth_device_request(msg); } -void APIConnection::bluetooth_gatt_read(const BluetoothGATTReadRequest &msg) { +void APIConnection::on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &msg) { bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_read(msg); } -void APIConnection::bluetooth_gatt_write(const BluetoothGATTWriteRequest &msg) { +void APIConnection::on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &msg) { bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_write(msg); } -void APIConnection::bluetooth_gatt_read_descriptor(const BluetoothGATTReadDescriptorRequest &msg) { +void APIConnection::on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &msg) { bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_read_descriptor(msg); } -void APIConnection::bluetooth_gatt_write_descriptor(const BluetoothGATTWriteDescriptorRequest &msg) { +void APIConnection::on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &msg) { bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_write_descriptor(msg); } -void APIConnection::bluetooth_gatt_get_services(const BluetoothGATTGetServicesRequest &msg) { +void APIConnection::on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &msg) { bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_send_services(msg); } -void APIConnection::bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg) { +void APIConnection::on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &msg) { bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_notify(msg); } @@ -1125,8 +1126,13 @@ bool APIConnection::send_subscribe_bluetooth_connections_free_response() { bluetooth_proxy::global_bluetooth_proxy->send_connections_free(this); return true; } +void APIConnection::on_subscribe_bluetooth_connections_free_request() { + if (!this->send_subscribe_bluetooth_connections_free_response()) { + this->on_fatal_error(); + } +} -void APIConnection::bluetooth_scanner_set_mode(const BluetoothScannerSetModeRequest &msg) { +void APIConnection::on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &msg) { bluetooth_proxy::global_bluetooth_proxy->bluetooth_scanner_set_mode( msg.mode == enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_ACTIVE); } @@ -1138,7 +1144,7 @@ bool APIConnection::check_voice_assistant_api_connection_() const { voice_assistant::global_voice_assistant->get_api_connection() == this; } -void APIConnection::subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) { +void APIConnection::on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) { if (voice_assistant::global_voice_assistant != nullptr) { voice_assistant::global_voice_assistant->client_subscription(this, msg.subscribe); } @@ -1221,8 +1227,13 @@ bool APIConnection::send_voice_assistant_get_configuration_response(const VoiceA resp.max_active_wake_words = config.max_active_wake_words; return this->send_message(resp, VoiceAssistantConfigurationResponse::MESSAGE_TYPE); } +void APIConnection::on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg) { + if (!this->send_voice_assistant_get_configuration_response(msg)) { + this->on_fatal_error(); + } +} -void APIConnection::voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) { +void APIConnection::on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) { if (this->check_voice_assistant_api_connection_()) { voice_assistant::global_voice_assistant->on_set_configuration(msg.active_wake_words); } @@ -1230,11 +1241,11 @@ void APIConnection::voice_assistant_set_configuration(const VoiceAssistantSetCon #endif #ifdef USE_ZWAVE_PROXY -void APIConnection::zwave_proxy_frame(const ZWaveProxyFrame &msg) { +void APIConnection::on_z_wave_proxy_frame(const ZWaveProxyFrame &msg) { zwave_proxy::global_zwave_proxy->send_frame(msg.data, msg.data_len); } -void APIConnection::zwave_proxy_request(const ZWaveProxyRequest &msg) { +void APIConnection::on_z_wave_proxy_request(const ZWaveProxyRequest &msg) { zwave_proxy::global_zwave_proxy->zwave_proxy_request(this, msg.type); } #endif @@ -1262,7 +1273,7 @@ uint16_t APIConnection::try_send_alarm_control_panel_info(EntityBase *entity, AP return fill_and_encode_entity_info(a_alarm_control_panel, msg, ListEntitiesAlarmControlPanelResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) { +void APIConnection::on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) { ENTITY_COMMAND_MAKE_CALL(alarm_control_panel::AlarmControlPanel, a_alarm_control_panel, alarm_control_panel) switch (msg.command) { case enums::ALARM_CONTROL_PANEL_DISARM: @@ -1322,7 +1333,7 @@ uint16_t APIConnection::try_send_water_heater_info(EntityBase *entity, APIConnec return fill_and_encode_entity_info(wh, msg, ListEntitiesWaterHeaterResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::water_heater_command(const WaterHeaterCommandRequest &msg) { +void APIConnection::on_water_heater_command_request(const WaterHeaterCommandRequest &msg) { ENTITY_COMMAND_MAKE_CALL(water_heater::WaterHeater, water_heater, water_heater) if (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_MODE) call.set_mode(static_cast(msg.mode)); @@ -1364,7 +1375,7 @@ uint16_t APIConnection::try_send_event_info(EntityBase *entity, APIConnection *c #endif #ifdef USE_IR_RF -void APIConnection::infrared_rf_transmit_raw_timings(const InfraredRFTransmitRawTimingsRequest &msg) { +void APIConnection::on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &msg) { // TODO: When RF is implemented, add a field to the message to distinguish IR vs RF // and dispatch to the appropriate entity type based on that field. #ifdef USE_INFRARED @@ -1418,7 +1429,7 @@ uint16_t APIConnection::try_send_update_info(EntityBase *entity, APIConnection * msg.device_class = update->get_device_class_ref(); return fill_and_encode_entity_info(update, msg, ListEntitiesUpdateResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::update_command(const UpdateCommandRequest &msg) { +void APIConnection::on_update_command_request(const UpdateCommandRequest &msg) { ENTITY_COMMAND_GET(update::UpdateEntity, update, update) switch (msg.command) { @@ -1618,6 +1629,26 @@ bool APIConnection::send_device_info_response() { return this->send_message(resp, DeviceInfoResponse::MESSAGE_TYPE); } +void APIConnection::on_hello_request(const HelloRequest &msg) { + if (!this->send_hello_response(msg)) { + this->on_fatal_error(); + } +} +void APIConnection::on_disconnect_request() { + if (!this->send_disconnect_response()) { + this->on_fatal_error(); + } +} +void APIConnection::on_ping_request() { + if (!this->send_ping_response()) { + this->on_fatal_error(); + } +} +void APIConnection::on_device_info_request() { + if (!this->send_device_info_response()) { + this->on_fatal_error(); + } +} #ifdef USE_API_HOMEASSISTANT_STATES void APIConnection::on_home_assistant_state_response(const HomeAssistantStateResponse &msg) { @@ -1656,7 +1687,7 @@ void APIConnection::on_home_assistant_state_response(const HomeAssistantStateRes } #endif #ifdef USE_API_USER_DEFINED_ACTIONS -void APIConnection::execute_service(const ExecuteServiceRequest &msg) { +void APIConnection::on_execute_service_request(const ExecuteServiceRequest &msg) { bool found = false; #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES // Register the call and get a unique server-generated action_call_id @@ -1743,9 +1774,14 @@ bool APIConnection::send_noise_encryption_set_key_response(const NoiseEncryption return this->send_message(resp, NoiseEncryptionSetKeyResponse::MESSAGE_TYPE); } +void APIConnection::on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) { + if (!this->send_noise_encryption_set_key_response(msg)) { + this->on_fatal_error(); + } +} #endif #ifdef USE_API_HOMEASSISTANT_STATES -void APIConnection::subscribe_home_assistant_states() { state_subs_at_ = 0; } +void APIConnection::on_subscribe_home_assistant_states_request() { state_subs_at_ = 0; } #endif bool APIConnection::try_to_clear_buffer(bool log_out_of_space) { if (this->flags_.remove) diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 935393b2da..ab29a5ab0a 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -47,72 +47,72 @@ class APIConnection final : public APIServerConnection { #endif #ifdef USE_COVER bool send_cover_state(cover::Cover *cover); - void cover_command(const CoverCommandRequest &msg) override; + void on_cover_command_request(const CoverCommandRequest &msg) override; #endif #ifdef USE_FAN bool send_fan_state(fan::Fan *fan); - void fan_command(const FanCommandRequest &msg) override; + void on_fan_command_request(const FanCommandRequest &msg) override; #endif #ifdef USE_LIGHT bool send_light_state(light::LightState *light); - void light_command(const LightCommandRequest &msg) override; + void on_light_command_request(const LightCommandRequest &msg) override; #endif #ifdef USE_SENSOR bool send_sensor_state(sensor::Sensor *sensor); #endif #ifdef USE_SWITCH bool send_switch_state(switch_::Switch *a_switch); - void switch_command(const SwitchCommandRequest &msg) override; + void on_switch_command_request(const SwitchCommandRequest &msg) override; #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 camera_image(const CameraImageRequest &msg) override; + void on_camera_image_request(const CameraImageRequest &msg) override; #endif #ifdef USE_CLIMATE bool send_climate_state(climate::Climate *climate); - void climate_command(const ClimateCommandRequest &msg) override; + void on_climate_command_request(const ClimateCommandRequest &msg) override; #endif #ifdef USE_NUMBER bool send_number_state(number::Number *number); - void number_command(const NumberCommandRequest &msg) override; + void on_number_command_request(const NumberCommandRequest &msg) override; #endif #ifdef USE_DATETIME_DATE bool send_date_state(datetime::DateEntity *date); - void date_command(const DateCommandRequest &msg) override; + void on_date_command_request(const DateCommandRequest &msg) override; #endif #ifdef USE_DATETIME_TIME bool send_time_state(datetime::TimeEntity *time); - void time_command(const TimeCommandRequest &msg) override; + void on_time_command_request(const TimeCommandRequest &msg) override; #endif #ifdef USE_DATETIME_DATETIME bool send_datetime_state(datetime::DateTimeEntity *datetime); - void datetime_command(const DateTimeCommandRequest &msg) override; + void on_date_time_command_request(const DateTimeCommandRequest &msg) override; #endif #ifdef USE_TEXT bool send_text_state(text::Text *text); - void text_command(const TextCommandRequest &msg) override; + void on_text_command_request(const TextCommandRequest &msg) override; #endif #ifdef USE_SELECT bool send_select_state(select::Select *select); - void select_command(const SelectCommandRequest &msg) override; + void on_select_command_request(const SelectCommandRequest &msg) override; #endif #ifdef USE_BUTTON - void button_command(const ButtonCommandRequest &msg) override; + void on_button_command_request(const ButtonCommandRequest &msg) override; #endif #ifdef USE_LOCK bool send_lock_state(lock::Lock *a_lock); - void lock_command(const LockCommandRequest &msg) override; + void on_lock_command_request(const LockCommandRequest &msg) override; #endif #ifdef USE_VALVE bool send_valve_state(valve::Valve *valve); - void valve_command(const ValveCommandRequest &msg) override; + void on_valve_command_request(const ValveCommandRequest &msg) override; #endif #ifdef USE_MEDIA_PLAYER bool send_media_player_state(media_player::MediaPlayer *media_player); - void media_player_command(const MediaPlayerCommandRequest &msg) override; + void on_media_player_command_request(const MediaPlayerCommandRequest &msg) override; #endif bool try_send_log_message(int level, const char *tag, const char *line, size_t message_len); #ifdef USE_API_HOMEASSISTANT_SERVICES @@ -126,18 +126,18 @@ class APIConnection final : public APIServerConnection { #endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES #endif // USE_API_HOMEASSISTANT_SERVICES #ifdef USE_BLUETOOTH_PROXY - void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) override; - void unsubscribe_bluetooth_le_advertisements() override; + void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override; + void on_unsubscribe_bluetooth_le_advertisements_request() override; - void bluetooth_device_request(const BluetoothDeviceRequest &msg) override; - void bluetooth_gatt_read(const BluetoothGATTReadRequest &msg) override; - void bluetooth_gatt_write(const BluetoothGATTWriteRequest &msg) override; - void bluetooth_gatt_read_descriptor(const BluetoothGATTReadDescriptorRequest &msg) override; - void bluetooth_gatt_write_descriptor(const BluetoothGATTWriteDescriptorRequest &msg) override; - void bluetooth_gatt_get_services(const BluetoothGATTGetServicesRequest &msg) override; - void bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg) override; - bool send_subscribe_bluetooth_connections_free_response() override; - void bluetooth_scanner_set_mode(const BluetoothScannerSetModeRequest &msg) override; + 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; #endif #ifdef USE_HOMEASSISTANT_TIME @@ -148,33 +148,33 @@ class APIConnection final : public APIServerConnection { #endif #ifdef USE_VOICE_ASSISTANT - void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) override; + 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; - bool send_voice_assistant_get_configuration_response(const VoiceAssistantConfigurationRequest &msg) override; - void voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) override; + void on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg) override; + void on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) override; #endif #ifdef USE_ZWAVE_PROXY - void zwave_proxy_frame(const ZWaveProxyFrame &msg) override; - void zwave_proxy_request(const ZWaveProxyRequest &msg) override; + void on_z_wave_proxy_frame(const ZWaveProxyFrame &msg) override; + void on_z_wave_proxy_request(const ZWaveProxyRequest &msg) override; #endif #ifdef USE_ALARM_CONTROL_PANEL bool send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel); - void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) override; + void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) override; #endif #ifdef USE_WATER_HEATER bool send_water_heater_state(water_heater::WaterHeater *water_heater); - void water_heater_command(const WaterHeaterCommandRequest &msg) override; + void on_water_heater_command_request(const WaterHeaterCommandRequest &msg) override; #endif #ifdef USE_IR_RF - void infrared_rf_transmit_raw_timings(const InfraredRFTransmitRawTimingsRequest &msg) override; + void on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &msg) override; void send_infrared_rf_receive_event(const InfraredRFReceiveEvent &msg); #endif @@ -184,7 +184,7 @@ class APIConnection final : public APIServerConnection { #ifdef USE_UPDATE bool send_update_state(update::UpdateEntity *update); - void update_command(const UpdateCommandRequest &msg) override; + void on_update_command_request(const UpdateCommandRequest &msg) override; #endif void on_disconnect_response() override; @@ -198,12 +198,12 @@ class APIConnection final : public APIServerConnection { #ifdef USE_HOMEASSISTANT_TIME void on_get_time_response(const GetTimeResponse &value) override; #endif - bool send_hello_response(const HelloRequest &msg) override; - bool send_disconnect_response() override; - bool send_ping_response() override; - bool send_device_info_response() override; - void list_entities() override { this->begin_iterator_(ActiveIterator::LIST_ENTITIES); } - void subscribe_states() override { + 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 { 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 @@ -211,19 +211,19 @@ class APIConnection final : public APIServerConnection { this->begin_iterator_(ActiveIterator::INITIAL_STATE); } } - void subscribe_logs(const SubscribeLogsRequest &msg) override { + void on_subscribe_logs_request(const SubscribeLogsRequest &msg) override { this->flags_.log_subscription = msg.level; if (msg.dump_config) App.schedule_dump_config(); } #ifdef USE_API_HOMEASSISTANT_SERVICES - void subscribe_homeassistant_services() override { this->flags_.service_call_subscription = true; } + void on_subscribe_homeassistant_services_request() override { this->flags_.service_call_subscription = true; } #endif #ifdef USE_API_HOMEASSISTANT_STATES - void subscribe_home_assistant_states() override; + void on_subscribe_home_assistant_states_request() override; #endif #ifdef USE_API_USER_DEFINED_ACTIONS - void execute_service(const ExecuteServiceRequest &msg) override; + void on_execute_service_request(const ExecuteServiceRequest &msg) override; #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 @@ -233,7 +233,7 @@ class APIConnection final : public APIServerConnection { #endif // USE_API_USER_DEFINED_ACTION_RESPONSES #endif #ifdef USE_API_NOISE - bool send_noise_encryption_set_key_response(const NoiseEncryptionSetKeyRequest &msg) override; + void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) override; #endif bool is_authenticated() override { @@ -283,6 +283,21 @@ class APIConnection final : public APIServerConnection { // Helper function to handle authentication completion void complete_authentication_(); + // Pattern B helpers: send response and return success/failure + bool send_hello_response(const HelloRequest &msg); + bool send_disconnect_response(); + bool send_ping_response(); + bool send_device_info_response(); +#ifdef USE_API_NOISE + bool send_noise_encryption_set_key_response(const NoiseEncryptionSetKeyRequest &msg); +#endif +#ifdef USE_BLUETOOTH_PROXY + bool send_subscribe_bluetooth_connections_free_response(); +#endif +#ifdef USE_VOICE_ASSISTANT + bool send_voice_assistant_get_configuration_response(const VoiceAssistantConfigurationRequest &msg); +#endif + #ifdef USE_CAMERA void try_send_camera_image_(); #endif diff --git a/esphome/components/api/api_pb2_service.cpp b/esphome/components/api/api_pb2_service.cpp index df66b6eb83..1c04eacc82 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -623,200 +623,6 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, } } -void APIServerConnection::on_hello_request(const HelloRequest &msg) { - if (!this->send_hello_response(msg)) { - this->on_fatal_error(); - } -} -void APIServerConnection::on_disconnect_request() { - if (!this->send_disconnect_response()) { - this->on_fatal_error(); - } -} -void APIServerConnection::on_ping_request() { - if (!this->send_ping_response()) { - this->on_fatal_error(); - } -} -void APIServerConnection::on_device_info_request() { - if (!this->send_device_info_response()) { - this->on_fatal_error(); - } -} -void APIServerConnection::on_list_entities_request() { this->list_entities(); } -void APIServerConnection::on_subscribe_states_request() { this->subscribe_states(); } -void APIServerConnection::on_subscribe_logs_request(const SubscribeLogsRequest &msg) { this->subscribe_logs(msg); } -#ifdef USE_API_HOMEASSISTANT_SERVICES -void APIServerConnection::on_subscribe_homeassistant_services_request() { this->subscribe_homeassistant_services(); } -#endif -#ifdef USE_API_HOMEASSISTANT_STATES -void APIServerConnection::on_subscribe_home_assistant_states_request() { this->subscribe_home_assistant_states(); } -#endif -#ifdef USE_API_USER_DEFINED_ACTIONS -void APIServerConnection::on_execute_service_request(const ExecuteServiceRequest &msg) { this->execute_service(msg); } -#endif -#ifdef USE_API_NOISE -void APIServerConnection::on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) { - if (!this->send_noise_encryption_set_key_response(msg)) { - this->on_fatal_error(); - } -} -#endif -#ifdef USE_BUTTON -void APIServerConnection::on_button_command_request(const ButtonCommandRequest &msg) { this->button_command(msg); } -#endif -#ifdef USE_CAMERA -void APIServerConnection::on_camera_image_request(const CameraImageRequest &msg) { this->camera_image(msg); } -#endif -#ifdef USE_CLIMATE -void APIServerConnection::on_climate_command_request(const ClimateCommandRequest &msg) { this->climate_command(msg); } -#endif -#ifdef USE_COVER -void APIServerConnection::on_cover_command_request(const CoverCommandRequest &msg) { this->cover_command(msg); } -#endif -#ifdef USE_DATETIME_DATE -void APIServerConnection::on_date_command_request(const DateCommandRequest &msg) { this->date_command(msg); } -#endif -#ifdef USE_DATETIME_DATETIME -void APIServerConnection::on_date_time_command_request(const DateTimeCommandRequest &msg) { - this->datetime_command(msg); -} -#endif -#ifdef USE_FAN -void APIServerConnection::on_fan_command_request(const FanCommandRequest &msg) { this->fan_command(msg); } -#endif -#ifdef USE_LIGHT -void APIServerConnection::on_light_command_request(const LightCommandRequest &msg) { this->light_command(msg); } -#endif -#ifdef USE_LOCK -void APIServerConnection::on_lock_command_request(const LockCommandRequest &msg) { this->lock_command(msg); } -#endif -#ifdef USE_MEDIA_PLAYER -void APIServerConnection::on_media_player_command_request(const MediaPlayerCommandRequest &msg) { - this->media_player_command(msg); -} -#endif -#ifdef USE_NUMBER -void APIServerConnection::on_number_command_request(const NumberCommandRequest &msg) { this->number_command(msg); } -#endif -#ifdef USE_SELECT -void APIServerConnection::on_select_command_request(const SelectCommandRequest &msg) { this->select_command(msg); } -#endif -#ifdef USE_SIREN -void APIServerConnection::on_siren_command_request(const SirenCommandRequest &msg) { this->siren_command(msg); } -#endif -#ifdef USE_SWITCH -void APIServerConnection::on_switch_command_request(const SwitchCommandRequest &msg) { this->switch_command(msg); } -#endif -#ifdef USE_TEXT -void APIServerConnection::on_text_command_request(const TextCommandRequest &msg) { this->text_command(msg); } -#endif -#ifdef USE_DATETIME_TIME -void APIServerConnection::on_time_command_request(const TimeCommandRequest &msg) { this->time_command(msg); } -#endif -#ifdef USE_UPDATE -void APIServerConnection::on_update_command_request(const UpdateCommandRequest &msg) { this->update_command(msg); } -#endif -#ifdef USE_VALVE -void APIServerConnection::on_valve_command_request(const ValveCommandRequest &msg) { this->valve_command(msg); } -#endif -#ifdef USE_WATER_HEATER -void APIServerConnection::on_water_heater_command_request(const WaterHeaterCommandRequest &msg) { - this->water_heater_command(msg); -} -#endif -#ifdef USE_BLUETOOTH_PROXY -void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request( - const SubscribeBluetoothLEAdvertisementsRequest &msg) { - this->subscribe_bluetooth_le_advertisements(msg); -} -#endif -#ifdef USE_BLUETOOTH_PROXY -void APIServerConnection::on_bluetooth_device_request(const BluetoothDeviceRequest &msg) { - this->bluetooth_device_request(msg); -} -#endif -#ifdef USE_BLUETOOTH_PROXY -void APIServerConnection::on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &msg) { - this->bluetooth_gatt_get_services(msg); -} -#endif -#ifdef USE_BLUETOOTH_PROXY -void APIServerConnection::on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &msg) { - this->bluetooth_gatt_read(msg); -} -#endif -#ifdef USE_BLUETOOTH_PROXY -void APIServerConnection::on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &msg) { - this->bluetooth_gatt_write(msg); -} -#endif -#ifdef USE_BLUETOOTH_PROXY -void APIServerConnection::on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &msg) { - this->bluetooth_gatt_read_descriptor(msg); -} -#endif -#ifdef USE_BLUETOOTH_PROXY -void APIServerConnection::on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &msg) { - this->bluetooth_gatt_write_descriptor(msg); -} -#endif -#ifdef USE_BLUETOOTH_PROXY -void APIServerConnection::on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &msg) { - this->bluetooth_gatt_notify(msg); -} -#endif -#ifdef USE_BLUETOOTH_PROXY -void APIServerConnection::on_subscribe_bluetooth_connections_free_request() { - if (!this->send_subscribe_bluetooth_connections_free_response()) { - this->on_fatal_error(); - } -} -#endif -#ifdef USE_BLUETOOTH_PROXY -void APIServerConnection::on_unsubscribe_bluetooth_le_advertisements_request() { - this->unsubscribe_bluetooth_le_advertisements(); -} -#endif -#ifdef USE_BLUETOOTH_PROXY -void APIServerConnection::on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &msg) { - this->bluetooth_scanner_set_mode(msg); -} -#endif -#ifdef USE_VOICE_ASSISTANT -void APIServerConnection::on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) { - this->subscribe_voice_assistant(msg); -} -#endif -#ifdef USE_VOICE_ASSISTANT -void APIServerConnection::on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg) { - if (!this->send_voice_assistant_get_configuration_response(msg)) { - this->on_fatal_error(); - } -} -#endif -#ifdef USE_VOICE_ASSISTANT -void APIServerConnection::on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) { - this->voice_assistant_set_configuration(msg); -} -#endif -#ifdef USE_ALARM_CONTROL_PANEL -void APIServerConnection::on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) { - this->alarm_control_panel_command(msg); -} -#endif -#ifdef USE_ZWAVE_PROXY -void APIServerConnection::on_z_wave_proxy_frame(const ZWaveProxyFrame &msg) { this->zwave_proxy_frame(msg); } -#endif -#ifdef USE_ZWAVE_PROXY -void APIServerConnection::on_z_wave_proxy_request(const ZWaveProxyRequest &msg) { this->zwave_proxy_request(msg); } -#endif -#ifdef USE_IR_RF -void APIServerConnection::on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &msg) { - this->infrared_rf_transmit_raw_timings(msg); -} -#endif - void APIServerConnection::read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) { // Check authentication/connection requirements for messages switch (msg_type) { diff --git a/esphome/components/api/api_pb2_service.h b/esphome/components/api/api_pb2_service.h index b8c9e4da6f..4dc6ce27d0 100644 --- a/esphome/components/api/api_pb2_service.h +++ b/esphome/components/api/api_pb2_service.h @@ -229,268 +229,7 @@ class APIServerConnectionBase : public ProtoService { }; class APIServerConnection : public APIServerConnectionBase { - public: - virtual bool send_hello_response(const HelloRequest &msg) = 0; - virtual bool send_disconnect_response() = 0; - virtual bool send_ping_response() = 0; - virtual bool send_device_info_response() = 0; - virtual void list_entities() = 0; - virtual void subscribe_states() = 0; - virtual void subscribe_logs(const SubscribeLogsRequest &msg) = 0; -#ifdef USE_API_HOMEASSISTANT_SERVICES - virtual void subscribe_homeassistant_services() = 0; -#endif -#ifdef USE_API_HOMEASSISTANT_STATES - virtual void subscribe_home_assistant_states() = 0; -#endif -#ifdef USE_API_USER_DEFINED_ACTIONS - virtual void execute_service(const ExecuteServiceRequest &msg) = 0; -#endif -#ifdef USE_API_NOISE - virtual bool send_noise_encryption_set_key_response(const NoiseEncryptionSetKeyRequest &msg) = 0; -#endif -#ifdef USE_BUTTON - virtual void button_command(const ButtonCommandRequest &msg) = 0; -#endif -#ifdef USE_CAMERA - virtual void camera_image(const CameraImageRequest &msg) = 0; -#endif -#ifdef USE_CLIMATE - virtual void climate_command(const ClimateCommandRequest &msg) = 0; -#endif -#ifdef USE_COVER - virtual void cover_command(const CoverCommandRequest &msg) = 0; -#endif -#ifdef USE_DATETIME_DATE - virtual void date_command(const DateCommandRequest &msg) = 0; -#endif -#ifdef USE_DATETIME_DATETIME - virtual void datetime_command(const DateTimeCommandRequest &msg) = 0; -#endif -#ifdef USE_FAN - virtual void fan_command(const FanCommandRequest &msg) = 0; -#endif -#ifdef USE_LIGHT - virtual void light_command(const LightCommandRequest &msg) = 0; -#endif -#ifdef USE_LOCK - virtual void lock_command(const LockCommandRequest &msg) = 0; -#endif -#ifdef USE_MEDIA_PLAYER - virtual void media_player_command(const MediaPlayerCommandRequest &msg) = 0; -#endif -#ifdef USE_NUMBER - virtual void number_command(const NumberCommandRequest &msg) = 0; -#endif -#ifdef USE_SELECT - virtual void select_command(const SelectCommandRequest &msg) = 0; -#endif -#ifdef USE_SIREN - virtual void siren_command(const SirenCommandRequest &msg) = 0; -#endif -#ifdef USE_SWITCH - virtual void switch_command(const SwitchCommandRequest &msg) = 0; -#endif -#ifdef USE_TEXT - virtual void text_command(const TextCommandRequest &msg) = 0; -#endif -#ifdef USE_DATETIME_TIME - virtual void time_command(const TimeCommandRequest &msg) = 0; -#endif -#ifdef USE_UPDATE - virtual void update_command(const UpdateCommandRequest &msg) = 0; -#endif -#ifdef USE_VALVE - virtual void valve_command(const ValveCommandRequest &msg) = 0; -#endif -#ifdef USE_WATER_HEATER - virtual void water_heater_command(const WaterHeaterCommandRequest &msg) = 0; -#endif -#ifdef USE_BLUETOOTH_PROXY - virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0; -#endif -#ifdef USE_BLUETOOTH_PROXY - virtual void bluetooth_device_request(const BluetoothDeviceRequest &msg) = 0; -#endif -#ifdef USE_BLUETOOTH_PROXY - virtual void bluetooth_gatt_get_services(const BluetoothGATTGetServicesRequest &msg) = 0; -#endif -#ifdef USE_BLUETOOTH_PROXY - virtual void bluetooth_gatt_read(const BluetoothGATTReadRequest &msg) = 0; -#endif -#ifdef USE_BLUETOOTH_PROXY - virtual void bluetooth_gatt_write(const BluetoothGATTWriteRequest &msg) = 0; -#endif -#ifdef USE_BLUETOOTH_PROXY - virtual void bluetooth_gatt_read_descriptor(const BluetoothGATTReadDescriptorRequest &msg) = 0; -#endif -#ifdef USE_BLUETOOTH_PROXY - virtual void bluetooth_gatt_write_descriptor(const BluetoothGATTWriteDescriptorRequest &msg) = 0; -#endif -#ifdef USE_BLUETOOTH_PROXY - virtual void bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg) = 0; -#endif -#ifdef USE_BLUETOOTH_PROXY - virtual bool send_subscribe_bluetooth_connections_free_response() = 0; -#endif -#ifdef USE_BLUETOOTH_PROXY - virtual void unsubscribe_bluetooth_le_advertisements() = 0; -#endif -#ifdef USE_BLUETOOTH_PROXY - virtual void bluetooth_scanner_set_mode(const BluetoothScannerSetModeRequest &msg) = 0; -#endif -#ifdef USE_VOICE_ASSISTANT - virtual void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) = 0; -#endif -#ifdef USE_VOICE_ASSISTANT - virtual bool send_voice_assistant_get_configuration_response(const VoiceAssistantConfigurationRequest &msg) = 0; -#endif -#ifdef USE_VOICE_ASSISTANT - virtual void voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) = 0; -#endif -#ifdef USE_ALARM_CONTROL_PANEL - virtual void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) = 0; -#endif -#ifdef USE_ZWAVE_PROXY - virtual void zwave_proxy_frame(const ZWaveProxyFrame &msg) = 0; -#endif -#ifdef USE_ZWAVE_PROXY - virtual void zwave_proxy_request(const ZWaveProxyRequest &msg) = 0; -#endif -#ifdef USE_IR_RF - virtual void infrared_rf_transmit_raw_timings(const InfraredRFTransmitRawTimingsRequest &msg) = 0; -#endif protected: - 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; - void on_subscribe_states_request() override; - void on_subscribe_logs_request(const SubscribeLogsRequest &msg) override; -#ifdef USE_API_HOMEASSISTANT_SERVICES - void on_subscribe_homeassistant_services_request() override; -#endif -#ifdef USE_API_HOMEASSISTANT_STATES - void on_subscribe_home_assistant_states_request() override; -#endif -#ifdef USE_API_USER_DEFINED_ACTIONS - void on_execute_service_request(const ExecuteServiceRequest &msg) override; -#endif -#ifdef USE_API_NOISE - void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) override; -#endif -#ifdef USE_BUTTON - void on_button_command_request(const ButtonCommandRequest &msg) override; -#endif -#ifdef USE_CAMERA - void on_camera_image_request(const CameraImageRequest &msg) override; -#endif -#ifdef USE_CLIMATE - void on_climate_command_request(const ClimateCommandRequest &msg) override; -#endif -#ifdef USE_COVER - void on_cover_command_request(const CoverCommandRequest &msg) override; -#endif -#ifdef USE_DATETIME_DATE - void on_date_command_request(const DateCommandRequest &msg) override; -#endif -#ifdef USE_DATETIME_DATETIME - void on_date_time_command_request(const DateTimeCommandRequest &msg) override; -#endif -#ifdef USE_FAN - void on_fan_command_request(const FanCommandRequest &msg) override; -#endif -#ifdef USE_LIGHT - void on_light_command_request(const LightCommandRequest &msg) override; -#endif -#ifdef USE_LOCK - void on_lock_command_request(const LockCommandRequest &msg) override; -#endif -#ifdef USE_MEDIA_PLAYER - void on_media_player_command_request(const MediaPlayerCommandRequest &msg) override; -#endif -#ifdef USE_NUMBER - void on_number_command_request(const NumberCommandRequest &msg) override; -#endif -#ifdef USE_SELECT - void on_select_command_request(const SelectCommandRequest &msg) override; -#endif -#ifdef USE_SIREN - void on_siren_command_request(const SirenCommandRequest &msg) override; -#endif -#ifdef USE_SWITCH - void on_switch_command_request(const SwitchCommandRequest &msg) override; -#endif -#ifdef USE_TEXT - void on_text_command_request(const TextCommandRequest &msg) override; -#endif -#ifdef USE_DATETIME_TIME - void on_time_command_request(const TimeCommandRequest &msg) override; -#endif -#ifdef USE_UPDATE - void on_update_command_request(const UpdateCommandRequest &msg) override; -#endif -#ifdef USE_VALVE - void on_valve_command_request(const ValveCommandRequest &msg) override; -#endif -#ifdef USE_WATER_HEATER - void on_water_heater_command_request(const WaterHeaterCommandRequest &msg) override; -#endif -#ifdef USE_BLUETOOTH_PROXY - void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override; -#endif -#ifdef USE_BLUETOOTH_PROXY - void on_bluetooth_device_request(const BluetoothDeviceRequest &msg) override; -#endif -#ifdef USE_BLUETOOTH_PROXY - void on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &msg) override; -#endif -#ifdef USE_BLUETOOTH_PROXY - void on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &msg) override; -#endif -#ifdef USE_BLUETOOTH_PROXY - void on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &msg) override; -#endif -#ifdef USE_BLUETOOTH_PROXY - void on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &msg) override; -#endif -#ifdef USE_BLUETOOTH_PROXY - void on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &msg) override; -#endif -#ifdef USE_BLUETOOTH_PROXY - void on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &msg) override; -#endif -#ifdef USE_BLUETOOTH_PROXY - void on_subscribe_bluetooth_connections_free_request() override; -#endif -#ifdef USE_BLUETOOTH_PROXY - void on_unsubscribe_bluetooth_le_advertisements_request() override; -#endif -#ifdef USE_BLUETOOTH_PROXY - void on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &msg) override; -#endif -#ifdef USE_VOICE_ASSISTANT - void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) override; -#endif -#ifdef USE_VOICE_ASSISTANT - void on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg) override; -#endif -#ifdef USE_VOICE_ASSISTANT - void on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) override; -#endif -#ifdef USE_ALARM_CONTROL_PANEL - void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) override; -#endif -#ifdef USE_ZWAVE_PROXY - void on_z_wave_proxy_frame(const ZWaveProxyFrame &msg) override; -#endif -#ifdef USE_ZWAVE_PROXY - void on_z_wave_proxy_request(const ZWaveProxyRequest &msg) override; -#endif -#ifdef USE_IR_RF - void on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &msg) override; -#endif void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) override; }; diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index 5fbc1137a8..8673996a25 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -2906,7 +2906,6 @@ static const char *const TAG = "api.service"; class_name = "APIServerConnection" hpp += "\n" hpp += f"class {class_name} : public {class_name}Base {{\n" - hpp += " public:\n" hpp_protected = "" cpp += "\n" @@ -2914,14 +2913,8 @@ static const char *const TAG = "api.service"; message_auth_map: dict[str, bool] = {} message_conn_map: dict[str, bool] = {} - m = serv.method[0] for m in serv.method: - func = m.name inp = m.input_type[1:] - ret = m.output_type[1:] - is_void = ret == "void" - snake = camel_to_snake(inp) - on_func = f"on_{snake}" needs_conn = get_opt(m, pb.needs_setup_connection, True) needs_auth = get_opt(m, pb.needs_authentication, True) @@ -2929,39 +2922,6 @@ static const char *const TAG = "api.service"; message_auth_map[inp] = needs_auth message_conn_map[inp] = needs_conn - ifdef = message_ifdef_map.get(inp, ifdefs.get(inp)) - - if ifdef is not None: - hpp += f"#ifdef {ifdef}\n" - hpp_protected += f"#ifdef {ifdef}\n" - cpp += f"#ifdef {ifdef}\n" - - is_empty = inp in EMPTY_MESSAGES - param = "" if is_empty else f"const {inp} &msg" - arg = "" if is_empty else "msg" - - hpp_protected += f" void {on_func}({param}) override;\n" - if is_void: - hpp += f" virtual void {func}({param}) = 0;\n" - else: - hpp += f" virtual bool send_{func}_response({param}) = 0;\n" - - cpp += f"void {class_name}::{on_func}({param}) {{\n" - body = "" - if is_void: - body += f"this->{func}({arg});\n" - else: - body += f"if (!this->send_{func}_response({arg})) {{\n" - body += " this->on_fatal_error();\n" - body += "}\n" - - cpp += indent(body) + "\n" + "}\n" - - if ifdef is not None: - hpp += "#endif\n" - hpp_protected += "#endif\n" - cpp += "#endif\n" - # Generate optimized read_message with authentication checking # Categorize messages by their authentication requirements no_conn_ids: set[int] = set() From 4cf1d7babf34c1ad2185839b6e175b8c3040b2af Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 8 Feb 2026 09:57:03 -0600 Subject: [PATCH 19/42] Revert "Collapse APIServerConnection intermediary layer" This reverts commit cac82280b34b949ee3fac2339971e26ea62fe1fc. --- esphome/components/api/api_connection.cpp | 108 +++------ esphome/components/api/api_connection.h | 109 ++++----- esphome/components/api/api_pb2_service.cpp | 194 +++++++++++++++ esphome/components/api/api_pb2_service.h | 261 +++++++++++++++++++++ script/api_protobuf/api_protobuf.py | 40 ++++ 5 files changed, 578 insertions(+), 134 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 3cf46e6aa9..efc3d210b4 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -406,7 +406,7 @@ uint16_t APIConnection::try_send_cover_info(EntityBase *entity, APIConnection *c msg.device_class = cover->get_device_class_ref(); return fill_and_encode_entity_info(cover, msg, ListEntitiesCoverResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::on_cover_command_request(const CoverCommandRequest &msg) { +void APIConnection::cover_command(const CoverCommandRequest &msg) { ENTITY_COMMAND_MAKE_CALL(cover::Cover, cover, cover) if (msg.has_position) call.set_position(msg.position); @@ -449,7 +449,7 @@ uint16_t APIConnection::try_send_fan_info(EntityBase *entity, APIConnection *con msg.supported_preset_modes = &traits.supported_preset_modes(); return fill_and_encode_entity_info(fan, msg, ListEntitiesFanResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::on_fan_command_request(const FanCommandRequest &msg) { +void APIConnection::fan_command(const FanCommandRequest &msg) { ENTITY_COMMAND_MAKE_CALL(fan::Fan, fan, fan) if (msg.has_state) call.set_state(msg.state); @@ -517,7 +517,7 @@ uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *c msg.effects = &effects_list; return fill_and_encode_entity_info(light, msg, ListEntitiesLightResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::on_light_command_request(const LightCommandRequest &msg) { +void APIConnection::light_command(const LightCommandRequest &msg) { ENTITY_COMMAND_MAKE_CALL(light::LightState, light, light) if (msg.has_state) call.set_state(msg.state); @@ -594,7 +594,7 @@ uint16_t APIConnection::try_send_switch_info(EntityBase *entity, APIConnection * msg.device_class = a_switch->get_device_class_ref(); return fill_and_encode_entity_info(a_switch, msg, ListEntitiesSwitchResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::on_switch_command_request(const SwitchCommandRequest &msg) { +void APIConnection::switch_command(const SwitchCommandRequest &msg) { ENTITY_COMMAND_GET(switch_::Switch, a_switch, switch) if (msg.state) { @@ -692,7 +692,7 @@ uint16_t APIConnection::try_send_climate_info(EntityBase *entity, APIConnection msg.supported_swing_modes = &traits.get_supported_swing_modes(); return fill_and_encode_entity_info(climate, msg, ListEntitiesClimateResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::on_climate_command_request(const ClimateCommandRequest &msg) { +void APIConnection::climate_command(const ClimateCommandRequest &msg) { ENTITY_COMMAND_MAKE_CALL(climate::Climate, climate, climate) if (msg.has_mode) call.set_mode(static_cast(msg.mode)); @@ -742,7 +742,7 @@ uint16_t APIConnection::try_send_number_info(EntityBase *entity, APIConnection * msg.step = number->traits.get_step(); return fill_and_encode_entity_info(number, msg, ListEntitiesNumberResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::on_number_command_request(const NumberCommandRequest &msg) { +void APIConnection::number_command(const NumberCommandRequest &msg) { ENTITY_COMMAND_MAKE_CALL(number::Number, number, number) call.set_value(msg.state); call.perform(); @@ -767,7 +767,7 @@ uint16_t APIConnection::try_send_date_info(EntityBase *entity, APIConnection *co ListEntitiesDateResponse msg; return fill_and_encode_entity_info(date, msg, ListEntitiesDateResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::on_date_command_request(const DateCommandRequest &msg) { +void APIConnection::date_command(const DateCommandRequest &msg) { ENTITY_COMMAND_MAKE_CALL(datetime::DateEntity, date, date) call.set_date(msg.year, msg.month, msg.day); call.perform(); @@ -792,7 +792,7 @@ uint16_t APIConnection::try_send_time_info(EntityBase *entity, APIConnection *co ListEntitiesTimeResponse msg; return fill_and_encode_entity_info(time, msg, ListEntitiesTimeResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::on_time_command_request(const TimeCommandRequest &msg) { +void APIConnection::time_command(const TimeCommandRequest &msg) { ENTITY_COMMAND_MAKE_CALL(datetime::TimeEntity, time, time) call.set_time(msg.hour, msg.minute, msg.second); call.perform(); @@ -819,7 +819,7 @@ uint16_t APIConnection::try_send_datetime_info(EntityBase *entity, APIConnection ListEntitiesDateTimeResponse msg; return fill_and_encode_entity_info(datetime, msg, ListEntitiesDateTimeResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::on_date_time_command_request(const DateTimeCommandRequest &msg) { +void APIConnection::datetime_command(const DateTimeCommandRequest &msg) { ENTITY_COMMAND_MAKE_CALL(datetime::DateTimeEntity, datetime, datetime) call.set_datetime(msg.epoch_seconds); call.perform(); @@ -848,7 +848,7 @@ uint16_t APIConnection::try_send_text_info(EntityBase *entity, APIConnection *co msg.pattern = text->traits.get_pattern_ref(); return fill_and_encode_entity_info(text, msg, ListEntitiesTextResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::on_text_command_request(const TextCommandRequest &msg) { +void APIConnection::text_command(const TextCommandRequest &msg) { ENTITY_COMMAND_MAKE_CALL(text::Text, text, text) call.set_value(msg.state); call.perform(); @@ -874,7 +874,7 @@ uint16_t APIConnection::try_send_select_info(EntityBase *entity, APIConnection * msg.options = &select->traits.get_options(); return fill_and_encode_entity_info(select, msg, ListEntitiesSelectResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::on_select_command_request(const SelectCommandRequest &msg) { +void APIConnection::select_command(const SelectCommandRequest &msg) { ENTITY_COMMAND_MAKE_CALL(select::Select, select, select) call.set_option(msg.state.c_str(), msg.state.size()); call.perform(); @@ -888,7 +888,7 @@ uint16_t APIConnection::try_send_button_info(EntityBase *entity, APIConnection * msg.device_class = button->get_device_class_ref(); return fill_and_encode_entity_info(button, msg, ListEntitiesButtonResponse::MESSAGE_TYPE, conn, remaining_size); } -void esphome::api::APIConnection::on_button_command_request(const ButtonCommandRequest &msg) { +void esphome::api::APIConnection::button_command(const ButtonCommandRequest &msg) { ENTITY_COMMAND_GET(button::Button, button, button) button->press(); } @@ -914,7 +914,7 @@ uint16_t APIConnection::try_send_lock_info(EntityBase *entity, APIConnection *co msg.requires_code = a_lock->traits.get_requires_code(); return fill_and_encode_entity_info(a_lock, msg, ListEntitiesLockResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::on_lock_command_request(const LockCommandRequest &msg) { +void APIConnection::lock_command(const LockCommandRequest &msg) { ENTITY_COMMAND_GET(lock::Lock, a_lock, lock) switch (msg.command) { @@ -952,7 +952,7 @@ uint16_t APIConnection::try_send_valve_info(EntityBase *entity, APIConnection *c msg.supports_stop = traits.get_supports_stop(); return fill_and_encode_entity_info(valve, msg, ListEntitiesValveResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::on_valve_command_request(const ValveCommandRequest &msg) { +void APIConnection::valve_command(const ValveCommandRequest &msg) { ENTITY_COMMAND_MAKE_CALL(valve::Valve, valve, valve) if (msg.has_position) call.set_position(msg.position); @@ -996,7 +996,7 @@ uint16_t APIConnection::try_send_media_player_info(EntityBase *entity, APIConnec return fill_and_encode_entity_info(media_player, msg, ListEntitiesMediaPlayerResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::on_media_player_command_request(const MediaPlayerCommandRequest &msg) { +void APIConnection::media_player_command(const MediaPlayerCommandRequest &msg) { ENTITY_COMMAND_MAKE_CALL(media_player::MediaPlayer, media_player, media_player) if (msg.has_command) { call.set_command(static_cast(msg.command)); @@ -1063,7 +1063,7 @@ uint16_t APIConnection::try_send_camera_info(EntityBase *entity, APIConnection * ListEntitiesCameraResponse msg; return fill_and_encode_entity_info(camera, msg, ListEntitiesCameraResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::on_camera_image_request(const CameraImageRequest &msg) { +void APIConnection::camera_image(const CameraImageRequest &msg) { if (camera::Camera::instance() == nullptr) return; @@ -1092,33 +1092,32 @@ void APIConnection::on_get_time_response(const GetTimeResponse &value) { #endif #ifdef USE_BLUETOOTH_PROXY -void APIConnection::on_subscribe_bluetooth_le_advertisements_request( - const SubscribeBluetoothLEAdvertisementsRequest &msg) { +void APIConnection::subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) { bluetooth_proxy::global_bluetooth_proxy->subscribe_api_connection(this, msg.flags); } -void APIConnection::on_unsubscribe_bluetooth_le_advertisements_request() { +void APIConnection::unsubscribe_bluetooth_le_advertisements() { bluetooth_proxy::global_bluetooth_proxy->unsubscribe_api_connection(this); } -void APIConnection::on_bluetooth_device_request(const BluetoothDeviceRequest &msg) { +void APIConnection::bluetooth_device_request(const BluetoothDeviceRequest &msg) { bluetooth_proxy::global_bluetooth_proxy->bluetooth_device_request(msg); } -void APIConnection::on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &msg) { +void APIConnection::bluetooth_gatt_read(const BluetoothGATTReadRequest &msg) { bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_read(msg); } -void APIConnection::on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &msg) { +void APIConnection::bluetooth_gatt_write(const BluetoothGATTWriteRequest &msg) { bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_write(msg); } -void APIConnection::on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &msg) { +void APIConnection::bluetooth_gatt_read_descriptor(const BluetoothGATTReadDescriptorRequest &msg) { bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_read_descriptor(msg); } -void APIConnection::on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &msg) { +void APIConnection::bluetooth_gatt_write_descriptor(const BluetoothGATTWriteDescriptorRequest &msg) { bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_write_descriptor(msg); } -void APIConnection::on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &msg) { +void APIConnection::bluetooth_gatt_get_services(const BluetoothGATTGetServicesRequest &msg) { bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_send_services(msg); } -void APIConnection::on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &msg) { +void APIConnection::bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg) { bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_notify(msg); } @@ -1126,13 +1125,8 @@ bool APIConnection::send_subscribe_bluetooth_connections_free_response() { bluetooth_proxy::global_bluetooth_proxy->send_connections_free(this); return true; } -void APIConnection::on_subscribe_bluetooth_connections_free_request() { - if (!this->send_subscribe_bluetooth_connections_free_response()) { - this->on_fatal_error(); - } -} -void APIConnection::on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &msg) { +void APIConnection::bluetooth_scanner_set_mode(const BluetoothScannerSetModeRequest &msg) { bluetooth_proxy::global_bluetooth_proxy->bluetooth_scanner_set_mode( msg.mode == enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_ACTIVE); } @@ -1144,7 +1138,7 @@ bool APIConnection::check_voice_assistant_api_connection_() const { voice_assistant::global_voice_assistant->get_api_connection() == this; } -void APIConnection::on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) { +void APIConnection::subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) { if (voice_assistant::global_voice_assistant != nullptr) { voice_assistant::global_voice_assistant->client_subscription(this, msg.subscribe); } @@ -1227,13 +1221,8 @@ bool APIConnection::send_voice_assistant_get_configuration_response(const VoiceA resp.max_active_wake_words = config.max_active_wake_words; return this->send_message(resp, VoiceAssistantConfigurationResponse::MESSAGE_TYPE); } -void APIConnection::on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg) { - if (!this->send_voice_assistant_get_configuration_response(msg)) { - this->on_fatal_error(); - } -} -void APIConnection::on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) { +void APIConnection::voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) { if (this->check_voice_assistant_api_connection_()) { voice_assistant::global_voice_assistant->on_set_configuration(msg.active_wake_words); } @@ -1241,11 +1230,11 @@ void APIConnection::on_voice_assistant_set_configuration(const VoiceAssistantSet #endif #ifdef USE_ZWAVE_PROXY -void APIConnection::on_z_wave_proxy_frame(const ZWaveProxyFrame &msg) { +void APIConnection::zwave_proxy_frame(const ZWaveProxyFrame &msg) { zwave_proxy::global_zwave_proxy->send_frame(msg.data, msg.data_len); } -void APIConnection::on_z_wave_proxy_request(const ZWaveProxyRequest &msg) { +void APIConnection::zwave_proxy_request(const ZWaveProxyRequest &msg) { zwave_proxy::global_zwave_proxy->zwave_proxy_request(this, msg.type); } #endif @@ -1273,7 +1262,7 @@ uint16_t APIConnection::try_send_alarm_control_panel_info(EntityBase *entity, AP return fill_and_encode_entity_info(a_alarm_control_panel, msg, ListEntitiesAlarmControlPanelResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) { +void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) { ENTITY_COMMAND_MAKE_CALL(alarm_control_panel::AlarmControlPanel, a_alarm_control_panel, alarm_control_panel) switch (msg.command) { case enums::ALARM_CONTROL_PANEL_DISARM: @@ -1333,7 +1322,7 @@ uint16_t APIConnection::try_send_water_heater_info(EntityBase *entity, APIConnec return fill_and_encode_entity_info(wh, msg, ListEntitiesWaterHeaterResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::on_water_heater_command_request(const WaterHeaterCommandRequest &msg) { +void APIConnection::water_heater_command(const WaterHeaterCommandRequest &msg) { ENTITY_COMMAND_MAKE_CALL(water_heater::WaterHeater, water_heater, water_heater) if (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_MODE) call.set_mode(static_cast(msg.mode)); @@ -1375,7 +1364,7 @@ uint16_t APIConnection::try_send_event_info(EntityBase *entity, APIConnection *c #endif #ifdef USE_IR_RF -void APIConnection::on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &msg) { +void APIConnection::infrared_rf_transmit_raw_timings(const InfraredRFTransmitRawTimingsRequest &msg) { // TODO: When RF is implemented, add a field to the message to distinguish IR vs RF // and dispatch to the appropriate entity type based on that field. #ifdef USE_INFRARED @@ -1429,7 +1418,7 @@ uint16_t APIConnection::try_send_update_info(EntityBase *entity, APIConnection * msg.device_class = update->get_device_class_ref(); return fill_and_encode_entity_info(update, msg, ListEntitiesUpdateResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::on_update_command_request(const UpdateCommandRequest &msg) { +void APIConnection::update_command(const UpdateCommandRequest &msg) { ENTITY_COMMAND_GET(update::UpdateEntity, update, update) switch (msg.command) { @@ -1629,26 +1618,6 @@ bool APIConnection::send_device_info_response() { return this->send_message(resp, DeviceInfoResponse::MESSAGE_TYPE); } -void APIConnection::on_hello_request(const HelloRequest &msg) { - if (!this->send_hello_response(msg)) { - this->on_fatal_error(); - } -} -void APIConnection::on_disconnect_request() { - if (!this->send_disconnect_response()) { - this->on_fatal_error(); - } -} -void APIConnection::on_ping_request() { - if (!this->send_ping_response()) { - this->on_fatal_error(); - } -} -void APIConnection::on_device_info_request() { - if (!this->send_device_info_response()) { - this->on_fatal_error(); - } -} #ifdef USE_API_HOMEASSISTANT_STATES void APIConnection::on_home_assistant_state_response(const HomeAssistantStateResponse &msg) { @@ -1687,7 +1656,7 @@ void APIConnection::on_home_assistant_state_response(const HomeAssistantStateRes } #endif #ifdef USE_API_USER_DEFINED_ACTIONS -void APIConnection::on_execute_service_request(const ExecuteServiceRequest &msg) { +void APIConnection::execute_service(const ExecuteServiceRequest &msg) { bool found = false; #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES // Register the call and get a unique server-generated action_call_id @@ -1774,14 +1743,9 @@ bool APIConnection::send_noise_encryption_set_key_response(const NoiseEncryption return this->send_message(resp, NoiseEncryptionSetKeyResponse::MESSAGE_TYPE); } -void APIConnection::on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) { - if (!this->send_noise_encryption_set_key_response(msg)) { - this->on_fatal_error(); - } -} #endif #ifdef USE_API_HOMEASSISTANT_STATES -void APIConnection::on_subscribe_home_assistant_states_request() { state_subs_at_ = 0; } +void APIConnection::subscribe_home_assistant_states() { state_subs_at_ = 0; } #endif bool APIConnection::try_to_clear_buffer(bool log_out_of_space) { if (this->flags_.remove) diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index ab29a5ab0a..935393b2da 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -47,72 +47,72 @@ class APIConnection final : public APIServerConnection { #endif #ifdef USE_COVER bool send_cover_state(cover::Cover *cover); - void on_cover_command_request(const CoverCommandRequest &msg) override; + void cover_command(const CoverCommandRequest &msg) override; #endif #ifdef USE_FAN bool send_fan_state(fan::Fan *fan); - void on_fan_command_request(const FanCommandRequest &msg) override; + void fan_command(const FanCommandRequest &msg) override; #endif #ifdef USE_LIGHT bool send_light_state(light::LightState *light); - void on_light_command_request(const LightCommandRequest &msg) override; + void light_command(const LightCommandRequest &msg) override; #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 switch_command(const SwitchCommandRequest &msg) override; #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 camera_image(const CameraImageRequest &msg) override; #endif #ifdef USE_CLIMATE bool send_climate_state(climate::Climate *climate); - void on_climate_command_request(const ClimateCommandRequest &msg) override; + void climate_command(const ClimateCommandRequest &msg) override; #endif #ifdef USE_NUMBER bool send_number_state(number::Number *number); - void on_number_command_request(const NumberCommandRequest &msg) override; + void number_command(const NumberCommandRequest &msg) override; #endif #ifdef USE_DATETIME_DATE bool send_date_state(datetime::DateEntity *date); - void on_date_command_request(const DateCommandRequest &msg) override; + void date_command(const DateCommandRequest &msg) override; #endif #ifdef USE_DATETIME_TIME bool send_time_state(datetime::TimeEntity *time); - void on_time_command_request(const TimeCommandRequest &msg) override; + void time_command(const TimeCommandRequest &msg) override; #endif #ifdef USE_DATETIME_DATETIME bool send_datetime_state(datetime::DateTimeEntity *datetime); - void on_date_time_command_request(const DateTimeCommandRequest &msg) override; + void datetime_command(const DateTimeCommandRequest &msg) override; #endif #ifdef USE_TEXT bool send_text_state(text::Text *text); - void on_text_command_request(const TextCommandRequest &msg) override; + void text_command(const TextCommandRequest &msg) override; #endif #ifdef USE_SELECT bool send_select_state(select::Select *select); - void on_select_command_request(const SelectCommandRequest &msg) override; + void select_command(const SelectCommandRequest &msg) override; #endif #ifdef USE_BUTTON - void on_button_command_request(const ButtonCommandRequest &msg) override; + void button_command(const ButtonCommandRequest &msg) override; #endif #ifdef USE_LOCK bool send_lock_state(lock::Lock *a_lock); - void on_lock_command_request(const LockCommandRequest &msg) override; + void lock_command(const LockCommandRequest &msg) override; #endif #ifdef USE_VALVE bool send_valve_state(valve::Valve *valve); - void on_valve_command_request(const ValveCommandRequest &msg) override; + void valve_command(const ValveCommandRequest &msg) override; #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 media_player_command(const MediaPlayerCommandRequest &msg) override; #endif bool try_send_log_message(int level, const char *tag, const char *line, size_t message_len); #ifdef USE_API_HOMEASSISTANT_SERVICES @@ -126,18 +126,18 @@ class APIConnection final : public APIServerConnection { #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 subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) override; + void unsubscribe_bluetooth_le_advertisements() override; - 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 bluetooth_device_request(const BluetoothDeviceRequest &msg) override; + void bluetooth_gatt_read(const BluetoothGATTReadRequest &msg) override; + void bluetooth_gatt_write(const BluetoothGATTWriteRequest &msg) override; + void bluetooth_gatt_read_descriptor(const BluetoothGATTReadDescriptorRequest &msg) override; + void bluetooth_gatt_write_descriptor(const BluetoothGATTWriteDescriptorRequest &msg) override; + void bluetooth_gatt_get_services(const BluetoothGATTGetServicesRequest &msg) override; + void bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg) override; + bool send_subscribe_bluetooth_connections_free_response() override; + void bluetooth_scanner_set_mode(const BluetoothScannerSetModeRequest &msg) override; #endif #ifdef USE_HOMEASSISTANT_TIME @@ -148,33 +148,33 @@ class APIConnection final : public APIServerConnection { #endif #ifdef USE_VOICE_ASSISTANT - void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) override; + void subscribe_voice_assistant(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; + bool send_voice_assistant_get_configuration_response(const VoiceAssistantConfigurationRequest &msg) override; + void voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) override; #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 zwave_proxy_frame(const ZWaveProxyFrame &msg) override; + void zwave_proxy_request(const ZWaveProxyRequest &msg) override; #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 alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) override; #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 water_heater_command(const WaterHeaterCommandRequest &msg) override; #endif #ifdef USE_IR_RF - void on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &msg) override; + void infrared_rf_transmit_raw_timings(const InfraredRFTransmitRawTimingsRequest &msg) override; void send_infrared_rf_receive_event(const InfraredRFReceiveEvent &msg); #endif @@ -184,7 +184,7 @@ class APIConnection final : public APIServerConnection { #ifdef USE_UPDATE bool send_update_state(update::UpdateEntity *update); - void on_update_command_request(const UpdateCommandRequest &msg) override; + void update_command(const UpdateCommandRequest &msg) override; #endif void on_disconnect_response() override; @@ -198,12 +198,12 @@ class APIConnection final : public APIServerConnection { #ifdef USE_HOMEASSISTANT_TIME void on_get_time_response(const GetTimeResponse &value) override; #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 { + bool send_hello_response(const HelloRequest &msg) override; + bool send_disconnect_response() override; + bool send_ping_response() override; + bool send_device_info_response() override; + void list_entities() override { this->begin_iterator_(ActiveIterator::LIST_ENTITIES); } + void subscribe_states() override { 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 @@ -211,19 +211,19 @@ class APIConnection final : public APIServerConnection { this->begin_iterator_(ActiveIterator::INITIAL_STATE); } } - void on_subscribe_logs_request(const SubscribeLogsRequest &msg) override { + void subscribe_logs(const SubscribeLogsRequest &msg) override { this->flags_.log_subscription = msg.level; if (msg.dump_config) App.schedule_dump_config(); } #ifdef USE_API_HOMEASSISTANT_SERVICES - void on_subscribe_homeassistant_services_request() override { this->flags_.service_call_subscription = true; } + void subscribe_homeassistant_services() override { this->flags_.service_call_subscription = true; } #endif #ifdef USE_API_HOMEASSISTANT_STATES - void on_subscribe_home_assistant_states_request() override; + void subscribe_home_assistant_states() override; #endif #ifdef USE_API_USER_DEFINED_ACTIONS - void on_execute_service_request(const ExecuteServiceRequest &msg) override; + void execute_service(const ExecuteServiceRequest &msg) override; #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 @@ -233,7 +233,7 @@ class APIConnection final : public APIServerConnection { #endif // USE_API_USER_DEFINED_ACTION_RESPONSES #endif #ifdef USE_API_NOISE - void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) override; + bool send_noise_encryption_set_key_response(const NoiseEncryptionSetKeyRequest &msg) override; #endif bool is_authenticated() override { @@ -283,21 +283,6 @@ class APIConnection final : public APIServerConnection { // Helper function to handle authentication completion void complete_authentication_(); - // Pattern B helpers: send response and return success/failure - bool send_hello_response(const HelloRequest &msg); - bool send_disconnect_response(); - bool send_ping_response(); - bool send_device_info_response(); -#ifdef USE_API_NOISE - bool send_noise_encryption_set_key_response(const NoiseEncryptionSetKeyRequest &msg); -#endif -#ifdef USE_BLUETOOTH_PROXY - bool send_subscribe_bluetooth_connections_free_response(); -#endif -#ifdef USE_VOICE_ASSISTANT - bool send_voice_assistant_get_configuration_response(const VoiceAssistantConfigurationRequest &msg); -#endif - #ifdef USE_CAMERA void try_send_camera_image_(); #endif diff --git a/esphome/components/api/api_pb2_service.cpp b/esphome/components/api/api_pb2_service.cpp index 1c04eacc82..df66b6eb83 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -623,6 +623,200 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, } } +void APIServerConnection::on_hello_request(const HelloRequest &msg) { + if (!this->send_hello_response(msg)) { + this->on_fatal_error(); + } +} +void APIServerConnection::on_disconnect_request() { + if (!this->send_disconnect_response()) { + this->on_fatal_error(); + } +} +void APIServerConnection::on_ping_request() { + if (!this->send_ping_response()) { + this->on_fatal_error(); + } +} +void APIServerConnection::on_device_info_request() { + if (!this->send_device_info_response()) { + this->on_fatal_error(); + } +} +void APIServerConnection::on_list_entities_request() { this->list_entities(); } +void APIServerConnection::on_subscribe_states_request() { this->subscribe_states(); } +void APIServerConnection::on_subscribe_logs_request(const SubscribeLogsRequest &msg) { this->subscribe_logs(msg); } +#ifdef USE_API_HOMEASSISTANT_SERVICES +void APIServerConnection::on_subscribe_homeassistant_services_request() { this->subscribe_homeassistant_services(); } +#endif +#ifdef USE_API_HOMEASSISTANT_STATES +void APIServerConnection::on_subscribe_home_assistant_states_request() { this->subscribe_home_assistant_states(); } +#endif +#ifdef USE_API_USER_DEFINED_ACTIONS +void APIServerConnection::on_execute_service_request(const ExecuteServiceRequest &msg) { this->execute_service(msg); } +#endif +#ifdef USE_API_NOISE +void APIServerConnection::on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) { + if (!this->send_noise_encryption_set_key_response(msg)) { + this->on_fatal_error(); + } +} +#endif +#ifdef USE_BUTTON +void APIServerConnection::on_button_command_request(const ButtonCommandRequest &msg) { this->button_command(msg); } +#endif +#ifdef USE_CAMERA +void APIServerConnection::on_camera_image_request(const CameraImageRequest &msg) { this->camera_image(msg); } +#endif +#ifdef USE_CLIMATE +void APIServerConnection::on_climate_command_request(const ClimateCommandRequest &msg) { this->climate_command(msg); } +#endif +#ifdef USE_COVER +void APIServerConnection::on_cover_command_request(const CoverCommandRequest &msg) { this->cover_command(msg); } +#endif +#ifdef USE_DATETIME_DATE +void APIServerConnection::on_date_command_request(const DateCommandRequest &msg) { this->date_command(msg); } +#endif +#ifdef USE_DATETIME_DATETIME +void APIServerConnection::on_date_time_command_request(const DateTimeCommandRequest &msg) { + this->datetime_command(msg); +} +#endif +#ifdef USE_FAN +void APIServerConnection::on_fan_command_request(const FanCommandRequest &msg) { this->fan_command(msg); } +#endif +#ifdef USE_LIGHT +void APIServerConnection::on_light_command_request(const LightCommandRequest &msg) { this->light_command(msg); } +#endif +#ifdef USE_LOCK +void APIServerConnection::on_lock_command_request(const LockCommandRequest &msg) { this->lock_command(msg); } +#endif +#ifdef USE_MEDIA_PLAYER +void APIServerConnection::on_media_player_command_request(const MediaPlayerCommandRequest &msg) { + this->media_player_command(msg); +} +#endif +#ifdef USE_NUMBER +void APIServerConnection::on_number_command_request(const NumberCommandRequest &msg) { this->number_command(msg); } +#endif +#ifdef USE_SELECT +void APIServerConnection::on_select_command_request(const SelectCommandRequest &msg) { this->select_command(msg); } +#endif +#ifdef USE_SIREN +void APIServerConnection::on_siren_command_request(const SirenCommandRequest &msg) { this->siren_command(msg); } +#endif +#ifdef USE_SWITCH +void APIServerConnection::on_switch_command_request(const SwitchCommandRequest &msg) { this->switch_command(msg); } +#endif +#ifdef USE_TEXT +void APIServerConnection::on_text_command_request(const TextCommandRequest &msg) { this->text_command(msg); } +#endif +#ifdef USE_DATETIME_TIME +void APIServerConnection::on_time_command_request(const TimeCommandRequest &msg) { this->time_command(msg); } +#endif +#ifdef USE_UPDATE +void APIServerConnection::on_update_command_request(const UpdateCommandRequest &msg) { this->update_command(msg); } +#endif +#ifdef USE_VALVE +void APIServerConnection::on_valve_command_request(const ValveCommandRequest &msg) { this->valve_command(msg); } +#endif +#ifdef USE_WATER_HEATER +void APIServerConnection::on_water_heater_command_request(const WaterHeaterCommandRequest &msg) { + this->water_heater_command(msg); +} +#endif +#ifdef USE_BLUETOOTH_PROXY +void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request( + const SubscribeBluetoothLEAdvertisementsRequest &msg) { + this->subscribe_bluetooth_le_advertisements(msg); +} +#endif +#ifdef USE_BLUETOOTH_PROXY +void APIServerConnection::on_bluetooth_device_request(const BluetoothDeviceRequest &msg) { + this->bluetooth_device_request(msg); +} +#endif +#ifdef USE_BLUETOOTH_PROXY +void APIServerConnection::on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &msg) { + this->bluetooth_gatt_get_services(msg); +} +#endif +#ifdef USE_BLUETOOTH_PROXY +void APIServerConnection::on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &msg) { + this->bluetooth_gatt_read(msg); +} +#endif +#ifdef USE_BLUETOOTH_PROXY +void APIServerConnection::on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &msg) { + this->bluetooth_gatt_write(msg); +} +#endif +#ifdef USE_BLUETOOTH_PROXY +void APIServerConnection::on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &msg) { + this->bluetooth_gatt_read_descriptor(msg); +} +#endif +#ifdef USE_BLUETOOTH_PROXY +void APIServerConnection::on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &msg) { + this->bluetooth_gatt_write_descriptor(msg); +} +#endif +#ifdef USE_BLUETOOTH_PROXY +void APIServerConnection::on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &msg) { + this->bluetooth_gatt_notify(msg); +} +#endif +#ifdef USE_BLUETOOTH_PROXY +void APIServerConnection::on_subscribe_bluetooth_connections_free_request() { + if (!this->send_subscribe_bluetooth_connections_free_response()) { + this->on_fatal_error(); + } +} +#endif +#ifdef USE_BLUETOOTH_PROXY +void APIServerConnection::on_unsubscribe_bluetooth_le_advertisements_request() { + this->unsubscribe_bluetooth_le_advertisements(); +} +#endif +#ifdef USE_BLUETOOTH_PROXY +void APIServerConnection::on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &msg) { + this->bluetooth_scanner_set_mode(msg); +} +#endif +#ifdef USE_VOICE_ASSISTANT +void APIServerConnection::on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) { + this->subscribe_voice_assistant(msg); +} +#endif +#ifdef USE_VOICE_ASSISTANT +void APIServerConnection::on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg) { + if (!this->send_voice_assistant_get_configuration_response(msg)) { + this->on_fatal_error(); + } +} +#endif +#ifdef USE_VOICE_ASSISTANT +void APIServerConnection::on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) { + this->voice_assistant_set_configuration(msg); +} +#endif +#ifdef USE_ALARM_CONTROL_PANEL +void APIServerConnection::on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) { + this->alarm_control_panel_command(msg); +} +#endif +#ifdef USE_ZWAVE_PROXY +void APIServerConnection::on_z_wave_proxy_frame(const ZWaveProxyFrame &msg) { this->zwave_proxy_frame(msg); } +#endif +#ifdef USE_ZWAVE_PROXY +void APIServerConnection::on_z_wave_proxy_request(const ZWaveProxyRequest &msg) { this->zwave_proxy_request(msg); } +#endif +#ifdef USE_IR_RF +void APIServerConnection::on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &msg) { + this->infrared_rf_transmit_raw_timings(msg); +} +#endif + void APIServerConnection::read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) { // Check authentication/connection requirements for messages switch (msg_type) { diff --git a/esphome/components/api/api_pb2_service.h b/esphome/components/api/api_pb2_service.h index 4dc6ce27d0..b8c9e4da6f 100644 --- a/esphome/components/api/api_pb2_service.h +++ b/esphome/components/api/api_pb2_service.h @@ -229,7 +229,268 @@ class APIServerConnectionBase : public ProtoService { }; class APIServerConnection : public APIServerConnectionBase { + public: + virtual bool send_hello_response(const HelloRequest &msg) = 0; + virtual bool send_disconnect_response() = 0; + virtual bool send_ping_response() = 0; + virtual bool send_device_info_response() = 0; + virtual void list_entities() = 0; + virtual void subscribe_states() = 0; + virtual void subscribe_logs(const SubscribeLogsRequest &msg) = 0; +#ifdef USE_API_HOMEASSISTANT_SERVICES + virtual void subscribe_homeassistant_services() = 0; +#endif +#ifdef USE_API_HOMEASSISTANT_STATES + virtual void subscribe_home_assistant_states() = 0; +#endif +#ifdef USE_API_USER_DEFINED_ACTIONS + virtual void execute_service(const ExecuteServiceRequest &msg) = 0; +#endif +#ifdef USE_API_NOISE + virtual bool send_noise_encryption_set_key_response(const NoiseEncryptionSetKeyRequest &msg) = 0; +#endif +#ifdef USE_BUTTON + virtual void button_command(const ButtonCommandRequest &msg) = 0; +#endif +#ifdef USE_CAMERA + virtual void camera_image(const CameraImageRequest &msg) = 0; +#endif +#ifdef USE_CLIMATE + virtual void climate_command(const ClimateCommandRequest &msg) = 0; +#endif +#ifdef USE_COVER + virtual void cover_command(const CoverCommandRequest &msg) = 0; +#endif +#ifdef USE_DATETIME_DATE + virtual void date_command(const DateCommandRequest &msg) = 0; +#endif +#ifdef USE_DATETIME_DATETIME + virtual void datetime_command(const DateTimeCommandRequest &msg) = 0; +#endif +#ifdef USE_FAN + virtual void fan_command(const FanCommandRequest &msg) = 0; +#endif +#ifdef USE_LIGHT + virtual void light_command(const LightCommandRequest &msg) = 0; +#endif +#ifdef USE_LOCK + virtual void lock_command(const LockCommandRequest &msg) = 0; +#endif +#ifdef USE_MEDIA_PLAYER + virtual void media_player_command(const MediaPlayerCommandRequest &msg) = 0; +#endif +#ifdef USE_NUMBER + virtual void number_command(const NumberCommandRequest &msg) = 0; +#endif +#ifdef USE_SELECT + virtual void select_command(const SelectCommandRequest &msg) = 0; +#endif +#ifdef USE_SIREN + virtual void siren_command(const SirenCommandRequest &msg) = 0; +#endif +#ifdef USE_SWITCH + virtual void switch_command(const SwitchCommandRequest &msg) = 0; +#endif +#ifdef USE_TEXT + virtual void text_command(const TextCommandRequest &msg) = 0; +#endif +#ifdef USE_DATETIME_TIME + virtual void time_command(const TimeCommandRequest &msg) = 0; +#endif +#ifdef USE_UPDATE + virtual void update_command(const UpdateCommandRequest &msg) = 0; +#endif +#ifdef USE_VALVE + virtual void valve_command(const ValveCommandRequest &msg) = 0; +#endif +#ifdef USE_WATER_HEATER + virtual void water_heater_command(const WaterHeaterCommandRequest &msg) = 0; +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0; +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void bluetooth_device_request(const BluetoothDeviceRequest &msg) = 0; +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void bluetooth_gatt_get_services(const BluetoothGATTGetServicesRequest &msg) = 0; +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void bluetooth_gatt_read(const BluetoothGATTReadRequest &msg) = 0; +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void bluetooth_gatt_write(const BluetoothGATTWriteRequest &msg) = 0; +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void bluetooth_gatt_read_descriptor(const BluetoothGATTReadDescriptorRequest &msg) = 0; +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void bluetooth_gatt_write_descriptor(const BluetoothGATTWriteDescriptorRequest &msg) = 0; +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg) = 0; +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual bool send_subscribe_bluetooth_connections_free_response() = 0; +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void unsubscribe_bluetooth_le_advertisements() = 0; +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void bluetooth_scanner_set_mode(const BluetoothScannerSetModeRequest &msg) = 0; +#endif +#ifdef USE_VOICE_ASSISTANT + virtual void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) = 0; +#endif +#ifdef USE_VOICE_ASSISTANT + virtual bool send_voice_assistant_get_configuration_response(const VoiceAssistantConfigurationRequest &msg) = 0; +#endif +#ifdef USE_VOICE_ASSISTANT + virtual void voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) = 0; +#endif +#ifdef USE_ALARM_CONTROL_PANEL + virtual void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) = 0; +#endif +#ifdef USE_ZWAVE_PROXY + virtual void zwave_proxy_frame(const ZWaveProxyFrame &msg) = 0; +#endif +#ifdef USE_ZWAVE_PROXY + virtual void zwave_proxy_request(const ZWaveProxyRequest &msg) = 0; +#endif +#ifdef USE_IR_RF + virtual void infrared_rf_transmit_raw_timings(const InfraredRFTransmitRawTimingsRequest &msg) = 0; +#endif protected: + 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; + void on_subscribe_states_request() override; + void on_subscribe_logs_request(const SubscribeLogsRequest &msg) override; +#ifdef USE_API_HOMEASSISTANT_SERVICES + void on_subscribe_homeassistant_services_request() override; +#endif +#ifdef USE_API_HOMEASSISTANT_STATES + void on_subscribe_home_assistant_states_request() override; +#endif +#ifdef USE_API_USER_DEFINED_ACTIONS + void on_execute_service_request(const ExecuteServiceRequest &msg) override; +#endif +#ifdef USE_API_NOISE + void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) override; +#endif +#ifdef USE_BUTTON + void on_button_command_request(const ButtonCommandRequest &msg) override; +#endif +#ifdef USE_CAMERA + void on_camera_image_request(const CameraImageRequest &msg) override; +#endif +#ifdef USE_CLIMATE + void on_climate_command_request(const ClimateCommandRequest &msg) override; +#endif +#ifdef USE_COVER + void on_cover_command_request(const CoverCommandRequest &msg) override; +#endif +#ifdef USE_DATETIME_DATE + void on_date_command_request(const DateCommandRequest &msg) override; +#endif +#ifdef USE_DATETIME_DATETIME + void on_date_time_command_request(const DateTimeCommandRequest &msg) override; +#endif +#ifdef USE_FAN + void on_fan_command_request(const FanCommandRequest &msg) override; +#endif +#ifdef USE_LIGHT + void on_light_command_request(const LightCommandRequest &msg) override; +#endif +#ifdef USE_LOCK + void on_lock_command_request(const LockCommandRequest &msg) override; +#endif +#ifdef USE_MEDIA_PLAYER + void on_media_player_command_request(const MediaPlayerCommandRequest &msg) override; +#endif +#ifdef USE_NUMBER + void on_number_command_request(const NumberCommandRequest &msg) override; +#endif +#ifdef USE_SELECT + void on_select_command_request(const SelectCommandRequest &msg) override; +#endif +#ifdef USE_SIREN + void on_siren_command_request(const SirenCommandRequest &msg) override; +#endif +#ifdef USE_SWITCH + void on_switch_command_request(const SwitchCommandRequest &msg) override; +#endif +#ifdef USE_TEXT + void on_text_command_request(const TextCommandRequest &msg) override; +#endif +#ifdef USE_DATETIME_TIME + void on_time_command_request(const TimeCommandRequest &msg) override; +#endif +#ifdef USE_UPDATE + void on_update_command_request(const UpdateCommandRequest &msg) override; +#endif +#ifdef USE_VALVE + void on_valve_command_request(const ValveCommandRequest &msg) override; +#endif +#ifdef USE_WATER_HEATER + void on_water_heater_command_request(const WaterHeaterCommandRequest &msg) override; +#endif +#ifdef USE_BLUETOOTH_PROXY + void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override; +#endif +#ifdef USE_BLUETOOTH_PROXY + void on_bluetooth_device_request(const BluetoothDeviceRequest &msg) override; +#endif +#ifdef USE_BLUETOOTH_PROXY + void on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &msg) override; +#endif +#ifdef USE_BLUETOOTH_PROXY + void on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &msg) override; +#endif +#ifdef USE_BLUETOOTH_PROXY + void on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &msg) override; +#endif +#ifdef USE_BLUETOOTH_PROXY + void on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &msg) override; +#endif +#ifdef USE_BLUETOOTH_PROXY + void on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &msg) override; +#endif +#ifdef USE_BLUETOOTH_PROXY + void on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &msg) override; +#endif +#ifdef USE_BLUETOOTH_PROXY + void on_subscribe_bluetooth_connections_free_request() override; +#endif +#ifdef USE_BLUETOOTH_PROXY + void on_unsubscribe_bluetooth_le_advertisements_request() override; +#endif +#ifdef USE_BLUETOOTH_PROXY + void on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &msg) override; +#endif +#ifdef USE_VOICE_ASSISTANT + void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) override; +#endif +#ifdef USE_VOICE_ASSISTANT + void on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg) override; +#endif +#ifdef USE_VOICE_ASSISTANT + void on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) override; +#endif +#ifdef USE_ALARM_CONTROL_PANEL + void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) override; +#endif +#ifdef USE_ZWAVE_PROXY + void on_z_wave_proxy_frame(const ZWaveProxyFrame &msg) override; +#endif +#ifdef USE_ZWAVE_PROXY + void on_z_wave_proxy_request(const ZWaveProxyRequest &msg) override; +#endif +#ifdef USE_IR_RF + void on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &msg) override; +#endif void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) override; }; diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index 8673996a25..5fbc1137a8 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -2906,6 +2906,7 @@ static const char *const TAG = "api.service"; class_name = "APIServerConnection" hpp += "\n" hpp += f"class {class_name} : public {class_name}Base {{\n" + hpp += " public:\n" hpp_protected = "" cpp += "\n" @@ -2913,8 +2914,14 @@ static const char *const TAG = "api.service"; message_auth_map: dict[str, bool] = {} message_conn_map: dict[str, bool] = {} + m = serv.method[0] for m in serv.method: + func = m.name inp = m.input_type[1:] + ret = m.output_type[1:] + is_void = ret == "void" + snake = camel_to_snake(inp) + on_func = f"on_{snake}" needs_conn = get_opt(m, pb.needs_setup_connection, True) needs_auth = get_opt(m, pb.needs_authentication, True) @@ -2922,6 +2929,39 @@ static const char *const TAG = "api.service"; message_auth_map[inp] = needs_auth message_conn_map[inp] = needs_conn + ifdef = message_ifdef_map.get(inp, ifdefs.get(inp)) + + if ifdef is not None: + hpp += f"#ifdef {ifdef}\n" + hpp_protected += f"#ifdef {ifdef}\n" + cpp += f"#ifdef {ifdef}\n" + + is_empty = inp in EMPTY_MESSAGES + param = "" if is_empty else f"const {inp} &msg" + arg = "" if is_empty else "msg" + + hpp_protected += f" void {on_func}({param}) override;\n" + if is_void: + hpp += f" virtual void {func}({param}) = 0;\n" + else: + hpp += f" virtual bool send_{func}_response({param}) = 0;\n" + + cpp += f"void {class_name}::{on_func}({param}) {{\n" + body = "" + if is_void: + body += f"this->{func}({arg});\n" + else: + body += f"if (!this->send_{func}_response({arg})) {{\n" + body += " this->on_fatal_error();\n" + body += "}\n" + + cpp += indent(body) + "\n" + "}\n" + + if ifdef is not None: + hpp += "#endif\n" + hpp_protected += "#endif\n" + cpp += "#endif\n" + # Generate optimized read_message with authentication checking # Categorize messages by their authentication requirements no_conn_ids: set[int] = set() From d28c3e264176c55b8bc6f99f830180f3337f4e2b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 8 Feb 2026 09:55:31 -0600 Subject: [PATCH 20/42] Collapse APIServerConnection intermediary layer Remove the 48 on_xxx overrides and 48 pure virtual methods from the generated APIServerConnection class. APIConnection now overrides on_xxx() directly from APIServerConnectionBase, eliminating one virtual call per message dispatch and ~96 vtable entries. --- esphome/components/api/api_connection.cpp | 122 ++++++---- esphome/components/api/api_connection.h | 109 +++++---- esphome/components/api/api_pb2_service.cpp | 194 --------------- esphome/components/api/api_pb2_service.h | 261 --------------------- script/api_protobuf/api_protobuf.py | 40 ---- 5 files changed, 141 insertions(+), 585 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index efc3d210b4..ddc24a7e2c 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -283,7 +283,7 @@ void APIConnection::loop() { #endif } -bool APIConnection::send_disconnect_response() { +bool APIConnection::send_disconnect_response_() { // remote initiated disconnect_client // don't close yet, we still need to send the disconnect response // close will happen on next loop @@ -406,7 +406,7 @@ uint16_t APIConnection::try_send_cover_info(EntityBase *entity, APIConnection *c msg.device_class = cover->get_device_class_ref(); return fill_and_encode_entity_info(cover, msg, ListEntitiesCoverResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::cover_command(const CoverCommandRequest &msg) { +void APIConnection::on_cover_command_request(const CoverCommandRequest &msg) { ENTITY_COMMAND_MAKE_CALL(cover::Cover, cover, cover) if (msg.has_position) call.set_position(msg.position); @@ -449,7 +449,7 @@ uint16_t APIConnection::try_send_fan_info(EntityBase *entity, APIConnection *con msg.supported_preset_modes = &traits.supported_preset_modes(); return fill_and_encode_entity_info(fan, msg, ListEntitiesFanResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::fan_command(const FanCommandRequest &msg) { +void APIConnection::on_fan_command_request(const FanCommandRequest &msg) { ENTITY_COMMAND_MAKE_CALL(fan::Fan, fan, fan) if (msg.has_state) call.set_state(msg.state); @@ -517,7 +517,7 @@ uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *c msg.effects = &effects_list; return fill_and_encode_entity_info(light, msg, ListEntitiesLightResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::light_command(const LightCommandRequest &msg) { +void APIConnection::on_light_command_request(const LightCommandRequest &msg) { ENTITY_COMMAND_MAKE_CALL(light::LightState, light, light) if (msg.has_state) call.set_state(msg.state); @@ -594,7 +594,7 @@ uint16_t APIConnection::try_send_switch_info(EntityBase *entity, APIConnection * msg.device_class = a_switch->get_device_class_ref(); return fill_and_encode_entity_info(a_switch, msg, ListEntitiesSwitchResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::switch_command(const SwitchCommandRequest &msg) { +void APIConnection::on_switch_command_request(const SwitchCommandRequest &msg) { ENTITY_COMMAND_GET(switch_::Switch, a_switch, switch) if (msg.state) { @@ -692,7 +692,7 @@ uint16_t APIConnection::try_send_climate_info(EntityBase *entity, APIConnection msg.supported_swing_modes = &traits.get_supported_swing_modes(); return fill_and_encode_entity_info(climate, msg, ListEntitiesClimateResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::climate_command(const ClimateCommandRequest &msg) { +void APIConnection::on_climate_command_request(const ClimateCommandRequest &msg) { ENTITY_COMMAND_MAKE_CALL(climate::Climate, climate, climate) if (msg.has_mode) call.set_mode(static_cast(msg.mode)); @@ -742,7 +742,7 @@ uint16_t APIConnection::try_send_number_info(EntityBase *entity, APIConnection * msg.step = number->traits.get_step(); return fill_and_encode_entity_info(number, msg, ListEntitiesNumberResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::number_command(const NumberCommandRequest &msg) { +void APIConnection::on_number_command_request(const NumberCommandRequest &msg) { ENTITY_COMMAND_MAKE_CALL(number::Number, number, number) call.set_value(msg.state); call.perform(); @@ -767,7 +767,7 @@ uint16_t APIConnection::try_send_date_info(EntityBase *entity, APIConnection *co ListEntitiesDateResponse msg; return fill_and_encode_entity_info(date, msg, ListEntitiesDateResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::date_command(const DateCommandRequest &msg) { +void APIConnection::on_date_command_request(const DateCommandRequest &msg) { ENTITY_COMMAND_MAKE_CALL(datetime::DateEntity, date, date) call.set_date(msg.year, msg.month, msg.day); call.perform(); @@ -792,7 +792,7 @@ uint16_t APIConnection::try_send_time_info(EntityBase *entity, APIConnection *co ListEntitiesTimeResponse msg; return fill_and_encode_entity_info(time, msg, ListEntitiesTimeResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::time_command(const TimeCommandRequest &msg) { +void APIConnection::on_time_command_request(const TimeCommandRequest &msg) { ENTITY_COMMAND_MAKE_CALL(datetime::TimeEntity, time, time) call.set_time(msg.hour, msg.minute, msg.second); call.perform(); @@ -819,7 +819,7 @@ uint16_t APIConnection::try_send_datetime_info(EntityBase *entity, APIConnection ListEntitiesDateTimeResponse msg; return fill_and_encode_entity_info(datetime, msg, ListEntitiesDateTimeResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::datetime_command(const DateTimeCommandRequest &msg) { +void APIConnection::on_date_time_command_request(const DateTimeCommandRequest &msg) { ENTITY_COMMAND_MAKE_CALL(datetime::DateTimeEntity, datetime, datetime) call.set_datetime(msg.epoch_seconds); call.perform(); @@ -848,7 +848,7 @@ uint16_t APIConnection::try_send_text_info(EntityBase *entity, APIConnection *co msg.pattern = text->traits.get_pattern_ref(); return fill_and_encode_entity_info(text, msg, ListEntitiesTextResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::text_command(const TextCommandRequest &msg) { +void APIConnection::on_text_command_request(const TextCommandRequest &msg) { ENTITY_COMMAND_MAKE_CALL(text::Text, text, text) call.set_value(msg.state); call.perform(); @@ -874,7 +874,7 @@ uint16_t APIConnection::try_send_select_info(EntityBase *entity, APIConnection * msg.options = &select->traits.get_options(); return fill_and_encode_entity_info(select, msg, ListEntitiesSelectResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::select_command(const SelectCommandRequest &msg) { +void APIConnection::on_select_command_request(const SelectCommandRequest &msg) { ENTITY_COMMAND_MAKE_CALL(select::Select, select, select) call.set_option(msg.state.c_str(), msg.state.size()); call.perform(); @@ -888,7 +888,7 @@ uint16_t APIConnection::try_send_button_info(EntityBase *entity, APIConnection * msg.device_class = button->get_device_class_ref(); return fill_and_encode_entity_info(button, msg, ListEntitiesButtonResponse::MESSAGE_TYPE, conn, remaining_size); } -void esphome::api::APIConnection::button_command(const ButtonCommandRequest &msg) { +void esphome::api::APIConnection::on_button_command_request(const ButtonCommandRequest &msg) { ENTITY_COMMAND_GET(button::Button, button, button) button->press(); } @@ -914,7 +914,7 @@ uint16_t APIConnection::try_send_lock_info(EntityBase *entity, APIConnection *co msg.requires_code = a_lock->traits.get_requires_code(); return fill_and_encode_entity_info(a_lock, msg, ListEntitiesLockResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::lock_command(const LockCommandRequest &msg) { +void APIConnection::on_lock_command_request(const LockCommandRequest &msg) { ENTITY_COMMAND_GET(lock::Lock, a_lock, lock) switch (msg.command) { @@ -952,7 +952,7 @@ uint16_t APIConnection::try_send_valve_info(EntityBase *entity, APIConnection *c msg.supports_stop = traits.get_supports_stop(); return fill_and_encode_entity_info(valve, msg, ListEntitiesValveResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::valve_command(const ValveCommandRequest &msg) { +void APIConnection::on_valve_command_request(const ValveCommandRequest &msg) { ENTITY_COMMAND_MAKE_CALL(valve::Valve, valve, valve) if (msg.has_position) call.set_position(msg.position); @@ -996,7 +996,7 @@ uint16_t APIConnection::try_send_media_player_info(EntityBase *entity, APIConnec return fill_and_encode_entity_info(media_player, msg, ListEntitiesMediaPlayerResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::media_player_command(const MediaPlayerCommandRequest &msg) { +void APIConnection::on_media_player_command_request(const MediaPlayerCommandRequest &msg) { ENTITY_COMMAND_MAKE_CALL(media_player::MediaPlayer, media_player, media_player) if (msg.has_command) { call.set_command(static_cast(msg.command)); @@ -1063,7 +1063,7 @@ uint16_t APIConnection::try_send_camera_info(EntityBase *entity, APIConnection * ListEntitiesCameraResponse msg; return fill_and_encode_entity_info(camera, msg, ListEntitiesCameraResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::camera_image(const CameraImageRequest &msg) { +void APIConnection::on_camera_image_request(const CameraImageRequest &msg) { if (camera::Camera::instance() == nullptr) return; @@ -1092,41 +1092,47 @@ void APIConnection::on_get_time_response(const GetTimeResponse &value) { #endif #ifdef USE_BLUETOOTH_PROXY -void APIConnection::subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) { +void APIConnection::on_subscribe_bluetooth_le_advertisements_request( + const SubscribeBluetoothLEAdvertisementsRequest &msg) { bluetooth_proxy::global_bluetooth_proxy->subscribe_api_connection(this, msg.flags); } -void APIConnection::unsubscribe_bluetooth_le_advertisements() { +void APIConnection::on_unsubscribe_bluetooth_le_advertisements_request() { bluetooth_proxy::global_bluetooth_proxy->unsubscribe_api_connection(this); } -void APIConnection::bluetooth_device_request(const BluetoothDeviceRequest &msg) { +void APIConnection::on_bluetooth_device_request(const BluetoothDeviceRequest &msg) { bluetooth_proxy::global_bluetooth_proxy->bluetooth_device_request(msg); } -void APIConnection::bluetooth_gatt_read(const BluetoothGATTReadRequest &msg) { +void APIConnection::on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &msg) { bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_read(msg); } -void APIConnection::bluetooth_gatt_write(const BluetoothGATTWriteRequest &msg) { +void APIConnection::on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &msg) { bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_write(msg); } -void APIConnection::bluetooth_gatt_read_descriptor(const BluetoothGATTReadDescriptorRequest &msg) { +void APIConnection::on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &msg) { bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_read_descriptor(msg); } -void APIConnection::bluetooth_gatt_write_descriptor(const BluetoothGATTWriteDescriptorRequest &msg) { +void APIConnection::on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &msg) { bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_write_descriptor(msg); } -void APIConnection::bluetooth_gatt_get_services(const BluetoothGATTGetServicesRequest &msg) { +void APIConnection::on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &msg) { bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_send_services(msg); } -void APIConnection::bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg) { +void APIConnection::on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &msg) { bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_notify(msg); } -bool APIConnection::send_subscribe_bluetooth_connections_free_response() { +bool APIConnection::send_subscribe_bluetooth_connections_free_response_() { bluetooth_proxy::global_bluetooth_proxy->send_connections_free(this); return true; } +void APIConnection::on_subscribe_bluetooth_connections_free_request() { + if (!this->send_subscribe_bluetooth_connections_free_response_()) { + this->on_fatal_error(); + } +} -void APIConnection::bluetooth_scanner_set_mode(const BluetoothScannerSetModeRequest &msg) { +void APIConnection::on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &msg) { bluetooth_proxy::global_bluetooth_proxy->bluetooth_scanner_set_mode( msg.mode == enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_ACTIVE); } @@ -1138,7 +1144,7 @@ bool APIConnection::check_voice_assistant_api_connection_() const { voice_assistant::global_voice_assistant->get_api_connection() == this; } -void APIConnection::subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) { +void APIConnection::on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) { if (voice_assistant::global_voice_assistant != nullptr) { voice_assistant::global_voice_assistant->client_subscription(this, msg.subscribe); } @@ -1184,7 +1190,7 @@ void APIConnection::on_voice_assistant_announce_request(const VoiceAssistantAnno } } -bool APIConnection::send_voice_assistant_get_configuration_response(const VoiceAssistantConfigurationRequest &msg) { +bool APIConnection::send_voice_assistant_get_configuration_response_(const VoiceAssistantConfigurationRequest &msg) { VoiceAssistantConfigurationResponse resp; if (!this->check_voice_assistant_api_connection_()) { return this->send_message(resp, VoiceAssistantConfigurationResponse::MESSAGE_TYPE); @@ -1221,8 +1227,13 @@ bool APIConnection::send_voice_assistant_get_configuration_response(const VoiceA resp.max_active_wake_words = config.max_active_wake_words; return this->send_message(resp, VoiceAssistantConfigurationResponse::MESSAGE_TYPE); } +void APIConnection::on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg) { + if (!this->send_voice_assistant_get_configuration_response_(msg)) { + this->on_fatal_error(); + } +} -void APIConnection::voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) { +void APIConnection::on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) { if (this->check_voice_assistant_api_connection_()) { voice_assistant::global_voice_assistant->on_set_configuration(msg.active_wake_words); } @@ -1230,11 +1241,11 @@ void APIConnection::voice_assistant_set_configuration(const VoiceAssistantSetCon #endif #ifdef USE_ZWAVE_PROXY -void APIConnection::zwave_proxy_frame(const ZWaveProxyFrame &msg) { +void APIConnection::on_z_wave_proxy_frame(const ZWaveProxyFrame &msg) { zwave_proxy::global_zwave_proxy->send_frame(msg.data, msg.data_len); } -void APIConnection::zwave_proxy_request(const ZWaveProxyRequest &msg) { +void APIConnection::on_z_wave_proxy_request(const ZWaveProxyRequest &msg) { zwave_proxy::global_zwave_proxy->zwave_proxy_request(this, msg.type); } #endif @@ -1262,7 +1273,7 @@ uint16_t APIConnection::try_send_alarm_control_panel_info(EntityBase *entity, AP return fill_and_encode_entity_info(a_alarm_control_panel, msg, ListEntitiesAlarmControlPanelResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) { +void APIConnection::on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) { ENTITY_COMMAND_MAKE_CALL(alarm_control_panel::AlarmControlPanel, a_alarm_control_panel, alarm_control_panel) switch (msg.command) { case enums::ALARM_CONTROL_PANEL_DISARM: @@ -1322,7 +1333,7 @@ uint16_t APIConnection::try_send_water_heater_info(EntityBase *entity, APIConnec return fill_and_encode_entity_info(wh, msg, ListEntitiesWaterHeaterResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::water_heater_command(const WaterHeaterCommandRequest &msg) { +void APIConnection::on_water_heater_command_request(const WaterHeaterCommandRequest &msg) { ENTITY_COMMAND_MAKE_CALL(water_heater::WaterHeater, water_heater, water_heater) if (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_MODE) call.set_mode(static_cast(msg.mode)); @@ -1364,7 +1375,7 @@ uint16_t APIConnection::try_send_event_info(EntityBase *entity, APIConnection *c #endif #ifdef USE_IR_RF -void APIConnection::infrared_rf_transmit_raw_timings(const InfraredRFTransmitRawTimingsRequest &msg) { +void APIConnection::on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &msg) { // TODO: When RF is implemented, add a field to the message to distinguish IR vs RF // and dispatch to the appropriate entity type based on that field. #ifdef USE_INFRARED @@ -1418,7 +1429,7 @@ uint16_t APIConnection::try_send_update_info(EntityBase *entity, APIConnection * msg.device_class = update->get_device_class_ref(); return fill_and_encode_entity_info(update, msg, ListEntitiesUpdateResponse::MESSAGE_TYPE, conn, remaining_size); } -void APIConnection::update_command(const UpdateCommandRequest &msg) { +void APIConnection::on_update_command_request(const UpdateCommandRequest &msg) { ENTITY_COMMAND_GET(update::UpdateEntity, update, update) switch (msg.command) { @@ -1469,7 +1480,7 @@ void APIConnection::complete_authentication_() { #endif } -bool APIConnection::send_hello_response(const HelloRequest &msg) { +bool APIConnection::send_hello_response_(const HelloRequest &msg) { // Copy client name with truncation if needed (set_client_name handles truncation) this->helper_->set_client_name(msg.client_info.c_str(), msg.client_info.size()); this->client_api_version_major_ = msg.api_version_major; @@ -1490,12 +1501,12 @@ bool APIConnection::send_hello_response(const HelloRequest &msg) { return this->send_message(resp, HelloResponse::MESSAGE_TYPE); } -bool APIConnection::send_ping_response() { +bool APIConnection::send_ping_response_() { PingResponse resp; return this->send_message(resp, PingResponse::MESSAGE_TYPE); } -bool APIConnection::send_device_info_response() { +bool APIConnection::send_device_info_response_() { DeviceInfoResponse resp{}; resp.name = StringRef(App.get_name()); resp.friendly_name = StringRef(App.get_friendly_name()); @@ -1618,6 +1629,26 @@ bool APIConnection::send_device_info_response() { return this->send_message(resp, DeviceInfoResponse::MESSAGE_TYPE); } +void APIConnection::on_hello_request(const HelloRequest &msg) { + if (!this->send_hello_response_(msg)) { + this->on_fatal_error(); + } +} +void APIConnection::on_disconnect_request() { + if (!this->send_disconnect_response_()) { + this->on_fatal_error(); + } +} +void APIConnection::on_ping_request() { + if (!this->send_ping_response_()) { + this->on_fatal_error(); + } +} +void APIConnection::on_device_info_request() { + if (!this->send_device_info_response_()) { + this->on_fatal_error(); + } +} #ifdef USE_API_HOMEASSISTANT_STATES void APIConnection::on_home_assistant_state_response(const HomeAssistantStateResponse &msg) { @@ -1656,7 +1687,7 @@ void APIConnection::on_home_assistant_state_response(const HomeAssistantStateRes } #endif #ifdef USE_API_USER_DEFINED_ACTIONS -void APIConnection::execute_service(const ExecuteServiceRequest &msg) { +void APIConnection::on_execute_service_request(const ExecuteServiceRequest &msg) { bool found = false; #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES // Register the call and get a unique server-generated action_call_id @@ -1722,7 +1753,7 @@ void APIConnection::on_homeassistant_action_response(const HomeassistantActionRe }; #endif #ifdef USE_API_NOISE -bool APIConnection::send_noise_encryption_set_key_response(const NoiseEncryptionSetKeyRequest &msg) { +bool APIConnection::send_noise_encryption_set_key_response_(const NoiseEncryptionSetKeyRequest &msg) { NoiseEncryptionSetKeyResponse resp; resp.success = false; @@ -1743,9 +1774,14 @@ bool APIConnection::send_noise_encryption_set_key_response(const NoiseEncryption return this->send_message(resp, NoiseEncryptionSetKeyResponse::MESSAGE_TYPE); } +void APIConnection::on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) { + if (!this->send_noise_encryption_set_key_response_(msg)) { + this->on_fatal_error(); + } +} #endif #ifdef USE_API_HOMEASSISTANT_STATES -void APIConnection::subscribe_home_assistant_states() { state_subs_at_ = 0; } +void APIConnection::on_subscribe_home_assistant_states_request() { state_subs_at_ = 0; } #endif bool APIConnection::try_to_clear_buffer(bool log_out_of_space) { if (this->flags_.remove) diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 935393b2da..ae7f864568 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -47,72 +47,72 @@ class APIConnection final : public APIServerConnection { #endif #ifdef USE_COVER bool send_cover_state(cover::Cover *cover); - void cover_command(const CoverCommandRequest &msg) override; + void on_cover_command_request(const CoverCommandRequest &msg) override; #endif #ifdef USE_FAN bool send_fan_state(fan::Fan *fan); - void fan_command(const FanCommandRequest &msg) override; + void on_fan_command_request(const FanCommandRequest &msg) override; #endif #ifdef USE_LIGHT bool send_light_state(light::LightState *light); - void light_command(const LightCommandRequest &msg) override; + void on_light_command_request(const LightCommandRequest &msg) override; #endif #ifdef USE_SENSOR bool send_sensor_state(sensor::Sensor *sensor); #endif #ifdef USE_SWITCH bool send_switch_state(switch_::Switch *a_switch); - void switch_command(const SwitchCommandRequest &msg) override; + void on_switch_command_request(const SwitchCommandRequest &msg) override; #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 camera_image(const CameraImageRequest &msg) override; + void on_camera_image_request(const CameraImageRequest &msg) override; #endif #ifdef USE_CLIMATE bool send_climate_state(climate::Climate *climate); - void climate_command(const ClimateCommandRequest &msg) override; + void on_climate_command_request(const ClimateCommandRequest &msg) override; #endif #ifdef USE_NUMBER bool send_number_state(number::Number *number); - void number_command(const NumberCommandRequest &msg) override; + void on_number_command_request(const NumberCommandRequest &msg) override; #endif #ifdef USE_DATETIME_DATE bool send_date_state(datetime::DateEntity *date); - void date_command(const DateCommandRequest &msg) override; + void on_date_command_request(const DateCommandRequest &msg) override; #endif #ifdef USE_DATETIME_TIME bool send_time_state(datetime::TimeEntity *time); - void time_command(const TimeCommandRequest &msg) override; + void on_time_command_request(const TimeCommandRequest &msg) override; #endif #ifdef USE_DATETIME_DATETIME bool send_datetime_state(datetime::DateTimeEntity *datetime); - void datetime_command(const DateTimeCommandRequest &msg) override; + void on_date_time_command_request(const DateTimeCommandRequest &msg) override; #endif #ifdef USE_TEXT bool send_text_state(text::Text *text); - void text_command(const TextCommandRequest &msg) override; + void on_text_command_request(const TextCommandRequest &msg) override; #endif #ifdef USE_SELECT bool send_select_state(select::Select *select); - void select_command(const SelectCommandRequest &msg) override; + void on_select_command_request(const SelectCommandRequest &msg) override; #endif #ifdef USE_BUTTON - void button_command(const ButtonCommandRequest &msg) override; + void on_button_command_request(const ButtonCommandRequest &msg) override; #endif #ifdef USE_LOCK bool send_lock_state(lock::Lock *a_lock); - void lock_command(const LockCommandRequest &msg) override; + void on_lock_command_request(const LockCommandRequest &msg) override; #endif #ifdef USE_VALVE bool send_valve_state(valve::Valve *valve); - void valve_command(const ValveCommandRequest &msg) override; + void on_valve_command_request(const ValveCommandRequest &msg) override; #endif #ifdef USE_MEDIA_PLAYER bool send_media_player_state(media_player::MediaPlayer *media_player); - void media_player_command(const MediaPlayerCommandRequest &msg) override; + void on_media_player_command_request(const MediaPlayerCommandRequest &msg) override; #endif bool try_send_log_message(int level, const char *tag, const char *line, size_t message_len); #ifdef USE_API_HOMEASSISTANT_SERVICES @@ -126,18 +126,18 @@ class APIConnection final : public APIServerConnection { #endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES #endif // USE_API_HOMEASSISTANT_SERVICES #ifdef USE_BLUETOOTH_PROXY - void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) override; - void unsubscribe_bluetooth_le_advertisements() override; + void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override; + void on_unsubscribe_bluetooth_le_advertisements_request() override; - void bluetooth_device_request(const BluetoothDeviceRequest &msg) override; - void bluetooth_gatt_read(const BluetoothGATTReadRequest &msg) override; - void bluetooth_gatt_write(const BluetoothGATTWriteRequest &msg) override; - void bluetooth_gatt_read_descriptor(const BluetoothGATTReadDescriptorRequest &msg) override; - void bluetooth_gatt_write_descriptor(const BluetoothGATTWriteDescriptorRequest &msg) override; - void bluetooth_gatt_get_services(const BluetoothGATTGetServicesRequest &msg) override; - void bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg) override; - bool send_subscribe_bluetooth_connections_free_response() override; - void bluetooth_scanner_set_mode(const BluetoothScannerSetModeRequest &msg) override; + 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; #endif #ifdef USE_HOMEASSISTANT_TIME @@ -148,33 +148,33 @@ class APIConnection final : public APIServerConnection { #endif #ifdef USE_VOICE_ASSISTANT - void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) override; + 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; - bool send_voice_assistant_get_configuration_response(const VoiceAssistantConfigurationRequest &msg) override; - void voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) override; + void on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg) override; + void on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) override; #endif #ifdef USE_ZWAVE_PROXY - void zwave_proxy_frame(const ZWaveProxyFrame &msg) override; - void zwave_proxy_request(const ZWaveProxyRequest &msg) override; + void on_z_wave_proxy_frame(const ZWaveProxyFrame &msg) override; + void on_z_wave_proxy_request(const ZWaveProxyRequest &msg) override; #endif #ifdef USE_ALARM_CONTROL_PANEL bool send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel); - void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) override; + void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) override; #endif #ifdef USE_WATER_HEATER bool send_water_heater_state(water_heater::WaterHeater *water_heater); - void water_heater_command(const WaterHeaterCommandRequest &msg) override; + void on_water_heater_command_request(const WaterHeaterCommandRequest &msg) override; #endif #ifdef USE_IR_RF - void infrared_rf_transmit_raw_timings(const InfraredRFTransmitRawTimingsRequest &msg) override; + void on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &msg) override; void send_infrared_rf_receive_event(const InfraredRFReceiveEvent &msg); #endif @@ -184,7 +184,7 @@ class APIConnection final : public APIServerConnection { #ifdef USE_UPDATE bool send_update_state(update::UpdateEntity *update); - void update_command(const UpdateCommandRequest &msg) override; + void on_update_command_request(const UpdateCommandRequest &msg) override; #endif void on_disconnect_response() override; @@ -198,12 +198,12 @@ class APIConnection final : public APIServerConnection { #ifdef USE_HOMEASSISTANT_TIME void on_get_time_response(const GetTimeResponse &value) override; #endif - bool send_hello_response(const HelloRequest &msg) override; - bool send_disconnect_response() override; - bool send_ping_response() override; - bool send_device_info_response() override; - void list_entities() override { this->begin_iterator_(ActiveIterator::LIST_ENTITIES); } - void subscribe_states() override { + 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 { 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 @@ -211,19 +211,19 @@ class APIConnection final : public APIServerConnection { this->begin_iterator_(ActiveIterator::INITIAL_STATE); } } - void subscribe_logs(const SubscribeLogsRequest &msg) override { + void on_subscribe_logs_request(const SubscribeLogsRequest &msg) override { this->flags_.log_subscription = msg.level; if (msg.dump_config) App.schedule_dump_config(); } #ifdef USE_API_HOMEASSISTANT_SERVICES - void subscribe_homeassistant_services() override { this->flags_.service_call_subscription = true; } + void on_subscribe_homeassistant_services_request() override { this->flags_.service_call_subscription = true; } #endif #ifdef USE_API_HOMEASSISTANT_STATES - void subscribe_home_assistant_states() override; + void on_subscribe_home_assistant_states_request() override; #endif #ifdef USE_API_USER_DEFINED_ACTIONS - void execute_service(const ExecuteServiceRequest &msg) override; + void on_execute_service_request(const ExecuteServiceRequest &msg) override; #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 @@ -233,7 +233,7 @@ class APIConnection final : public APIServerConnection { #endif // USE_API_USER_DEFINED_ACTION_RESPONSES #endif #ifdef USE_API_NOISE - bool send_noise_encryption_set_key_response(const NoiseEncryptionSetKeyRequest &msg) override; + void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) override; #endif bool is_authenticated() override { @@ -283,6 +283,21 @@ class APIConnection final : public APIServerConnection { // Helper function to handle authentication completion void complete_authentication_(); + // Pattern B helpers: send response and return success/failure + bool send_hello_response_(const HelloRequest &msg); + bool send_disconnect_response_(); + bool send_ping_response_(); + bool send_device_info_response_(); +#ifdef USE_API_NOISE + bool send_noise_encryption_set_key_response_(const NoiseEncryptionSetKeyRequest &msg); +#endif +#ifdef USE_BLUETOOTH_PROXY + bool send_subscribe_bluetooth_connections_free_response_(); +#endif +#ifdef USE_VOICE_ASSISTANT + bool send_voice_assistant_get_configuration_response_(const VoiceAssistantConfigurationRequest &msg); +#endif + #ifdef USE_CAMERA void try_send_camera_image_(); #endif diff --git a/esphome/components/api/api_pb2_service.cpp b/esphome/components/api/api_pb2_service.cpp index df66b6eb83..1c04eacc82 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -623,200 +623,6 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, } } -void APIServerConnection::on_hello_request(const HelloRequest &msg) { - if (!this->send_hello_response(msg)) { - this->on_fatal_error(); - } -} -void APIServerConnection::on_disconnect_request() { - if (!this->send_disconnect_response()) { - this->on_fatal_error(); - } -} -void APIServerConnection::on_ping_request() { - if (!this->send_ping_response()) { - this->on_fatal_error(); - } -} -void APIServerConnection::on_device_info_request() { - if (!this->send_device_info_response()) { - this->on_fatal_error(); - } -} -void APIServerConnection::on_list_entities_request() { this->list_entities(); } -void APIServerConnection::on_subscribe_states_request() { this->subscribe_states(); } -void APIServerConnection::on_subscribe_logs_request(const SubscribeLogsRequest &msg) { this->subscribe_logs(msg); } -#ifdef USE_API_HOMEASSISTANT_SERVICES -void APIServerConnection::on_subscribe_homeassistant_services_request() { this->subscribe_homeassistant_services(); } -#endif -#ifdef USE_API_HOMEASSISTANT_STATES -void APIServerConnection::on_subscribe_home_assistant_states_request() { this->subscribe_home_assistant_states(); } -#endif -#ifdef USE_API_USER_DEFINED_ACTIONS -void APIServerConnection::on_execute_service_request(const ExecuteServiceRequest &msg) { this->execute_service(msg); } -#endif -#ifdef USE_API_NOISE -void APIServerConnection::on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) { - if (!this->send_noise_encryption_set_key_response(msg)) { - this->on_fatal_error(); - } -} -#endif -#ifdef USE_BUTTON -void APIServerConnection::on_button_command_request(const ButtonCommandRequest &msg) { this->button_command(msg); } -#endif -#ifdef USE_CAMERA -void APIServerConnection::on_camera_image_request(const CameraImageRequest &msg) { this->camera_image(msg); } -#endif -#ifdef USE_CLIMATE -void APIServerConnection::on_climate_command_request(const ClimateCommandRequest &msg) { this->climate_command(msg); } -#endif -#ifdef USE_COVER -void APIServerConnection::on_cover_command_request(const CoverCommandRequest &msg) { this->cover_command(msg); } -#endif -#ifdef USE_DATETIME_DATE -void APIServerConnection::on_date_command_request(const DateCommandRequest &msg) { this->date_command(msg); } -#endif -#ifdef USE_DATETIME_DATETIME -void APIServerConnection::on_date_time_command_request(const DateTimeCommandRequest &msg) { - this->datetime_command(msg); -} -#endif -#ifdef USE_FAN -void APIServerConnection::on_fan_command_request(const FanCommandRequest &msg) { this->fan_command(msg); } -#endif -#ifdef USE_LIGHT -void APIServerConnection::on_light_command_request(const LightCommandRequest &msg) { this->light_command(msg); } -#endif -#ifdef USE_LOCK -void APIServerConnection::on_lock_command_request(const LockCommandRequest &msg) { this->lock_command(msg); } -#endif -#ifdef USE_MEDIA_PLAYER -void APIServerConnection::on_media_player_command_request(const MediaPlayerCommandRequest &msg) { - this->media_player_command(msg); -} -#endif -#ifdef USE_NUMBER -void APIServerConnection::on_number_command_request(const NumberCommandRequest &msg) { this->number_command(msg); } -#endif -#ifdef USE_SELECT -void APIServerConnection::on_select_command_request(const SelectCommandRequest &msg) { this->select_command(msg); } -#endif -#ifdef USE_SIREN -void APIServerConnection::on_siren_command_request(const SirenCommandRequest &msg) { this->siren_command(msg); } -#endif -#ifdef USE_SWITCH -void APIServerConnection::on_switch_command_request(const SwitchCommandRequest &msg) { this->switch_command(msg); } -#endif -#ifdef USE_TEXT -void APIServerConnection::on_text_command_request(const TextCommandRequest &msg) { this->text_command(msg); } -#endif -#ifdef USE_DATETIME_TIME -void APIServerConnection::on_time_command_request(const TimeCommandRequest &msg) { this->time_command(msg); } -#endif -#ifdef USE_UPDATE -void APIServerConnection::on_update_command_request(const UpdateCommandRequest &msg) { this->update_command(msg); } -#endif -#ifdef USE_VALVE -void APIServerConnection::on_valve_command_request(const ValveCommandRequest &msg) { this->valve_command(msg); } -#endif -#ifdef USE_WATER_HEATER -void APIServerConnection::on_water_heater_command_request(const WaterHeaterCommandRequest &msg) { - this->water_heater_command(msg); -} -#endif -#ifdef USE_BLUETOOTH_PROXY -void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request( - const SubscribeBluetoothLEAdvertisementsRequest &msg) { - this->subscribe_bluetooth_le_advertisements(msg); -} -#endif -#ifdef USE_BLUETOOTH_PROXY -void APIServerConnection::on_bluetooth_device_request(const BluetoothDeviceRequest &msg) { - this->bluetooth_device_request(msg); -} -#endif -#ifdef USE_BLUETOOTH_PROXY -void APIServerConnection::on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &msg) { - this->bluetooth_gatt_get_services(msg); -} -#endif -#ifdef USE_BLUETOOTH_PROXY -void APIServerConnection::on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &msg) { - this->bluetooth_gatt_read(msg); -} -#endif -#ifdef USE_BLUETOOTH_PROXY -void APIServerConnection::on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &msg) { - this->bluetooth_gatt_write(msg); -} -#endif -#ifdef USE_BLUETOOTH_PROXY -void APIServerConnection::on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &msg) { - this->bluetooth_gatt_read_descriptor(msg); -} -#endif -#ifdef USE_BLUETOOTH_PROXY -void APIServerConnection::on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &msg) { - this->bluetooth_gatt_write_descriptor(msg); -} -#endif -#ifdef USE_BLUETOOTH_PROXY -void APIServerConnection::on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &msg) { - this->bluetooth_gatt_notify(msg); -} -#endif -#ifdef USE_BLUETOOTH_PROXY -void APIServerConnection::on_subscribe_bluetooth_connections_free_request() { - if (!this->send_subscribe_bluetooth_connections_free_response()) { - this->on_fatal_error(); - } -} -#endif -#ifdef USE_BLUETOOTH_PROXY -void APIServerConnection::on_unsubscribe_bluetooth_le_advertisements_request() { - this->unsubscribe_bluetooth_le_advertisements(); -} -#endif -#ifdef USE_BLUETOOTH_PROXY -void APIServerConnection::on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &msg) { - this->bluetooth_scanner_set_mode(msg); -} -#endif -#ifdef USE_VOICE_ASSISTANT -void APIServerConnection::on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) { - this->subscribe_voice_assistant(msg); -} -#endif -#ifdef USE_VOICE_ASSISTANT -void APIServerConnection::on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg) { - if (!this->send_voice_assistant_get_configuration_response(msg)) { - this->on_fatal_error(); - } -} -#endif -#ifdef USE_VOICE_ASSISTANT -void APIServerConnection::on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) { - this->voice_assistant_set_configuration(msg); -} -#endif -#ifdef USE_ALARM_CONTROL_PANEL -void APIServerConnection::on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) { - this->alarm_control_panel_command(msg); -} -#endif -#ifdef USE_ZWAVE_PROXY -void APIServerConnection::on_z_wave_proxy_frame(const ZWaveProxyFrame &msg) { this->zwave_proxy_frame(msg); } -#endif -#ifdef USE_ZWAVE_PROXY -void APIServerConnection::on_z_wave_proxy_request(const ZWaveProxyRequest &msg) { this->zwave_proxy_request(msg); } -#endif -#ifdef USE_IR_RF -void APIServerConnection::on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &msg) { - this->infrared_rf_transmit_raw_timings(msg); -} -#endif - void APIServerConnection::read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) { // Check authentication/connection requirements for messages switch (msg_type) { diff --git a/esphome/components/api/api_pb2_service.h b/esphome/components/api/api_pb2_service.h index b8c9e4da6f..4dc6ce27d0 100644 --- a/esphome/components/api/api_pb2_service.h +++ b/esphome/components/api/api_pb2_service.h @@ -229,268 +229,7 @@ class APIServerConnectionBase : public ProtoService { }; class APIServerConnection : public APIServerConnectionBase { - public: - virtual bool send_hello_response(const HelloRequest &msg) = 0; - virtual bool send_disconnect_response() = 0; - virtual bool send_ping_response() = 0; - virtual bool send_device_info_response() = 0; - virtual void list_entities() = 0; - virtual void subscribe_states() = 0; - virtual void subscribe_logs(const SubscribeLogsRequest &msg) = 0; -#ifdef USE_API_HOMEASSISTANT_SERVICES - virtual void subscribe_homeassistant_services() = 0; -#endif -#ifdef USE_API_HOMEASSISTANT_STATES - virtual void subscribe_home_assistant_states() = 0; -#endif -#ifdef USE_API_USER_DEFINED_ACTIONS - virtual void execute_service(const ExecuteServiceRequest &msg) = 0; -#endif -#ifdef USE_API_NOISE - virtual bool send_noise_encryption_set_key_response(const NoiseEncryptionSetKeyRequest &msg) = 0; -#endif -#ifdef USE_BUTTON - virtual void button_command(const ButtonCommandRequest &msg) = 0; -#endif -#ifdef USE_CAMERA - virtual void camera_image(const CameraImageRequest &msg) = 0; -#endif -#ifdef USE_CLIMATE - virtual void climate_command(const ClimateCommandRequest &msg) = 0; -#endif -#ifdef USE_COVER - virtual void cover_command(const CoverCommandRequest &msg) = 0; -#endif -#ifdef USE_DATETIME_DATE - virtual void date_command(const DateCommandRequest &msg) = 0; -#endif -#ifdef USE_DATETIME_DATETIME - virtual void datetime_command(const DateTimeCommandRequest &msg) = 0; -#endif -#ifdef USE_FAN - virtual void fan_command(const FanCommandRequest &msg) = 0; -#endif -#ifdef USE_LIGHT - virtual void light_command(const LightCommandRequest &msg) = 0; -#endif -#ifdef USE_LOCK - virtual void lock_command(const LockCommandRequest &msg) = 0; -#endif -#ifdef USE_MEDIA_PLAYER - virtual void media_player_command(const MediaPlayerCommandRequest &msg) = 0; -#endif -#ifdef USE_NUMBER - virtual void number_command(const NumberCommandRequest &msg) = 0; -#endif -#ifdef USE_SELECT - virtual void select_command(const SelectCommandRequest &msg) = 0; -#endif -#ifdef USE_SIREN - virtual void siren_command(const SirenCommandRequest &msg) = 0; -#endif -#ifdef USE_SWITCH - virtual void switch_command(const SwitchCommandRequest &msg) = 0; -#endif -#ifdef USE_TEXT - virtual void text_command(const TextCommandRequest &msg) = 0; -#endif -#ifdef USE_DATETIME_TIME - virtual void time_command(const TimeCommandRequest &msg) = 0; -#endif -#ifdef USE_UPDATE - virtual void update_command(const UpdateCommandRequest &msg) = 0; -#endif -#ifdef USE_VALVE - virtual void valve_command(const ValveCommandRequest &msg) = 0; -#endif -#ifdef USE_WATER_HEATER - virtual void water_heater_command(const WaterHeaterCommandRequest &msg) = 0; -#endif -#ifdef USE_BLUETOOTH_PROXY - virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0; -#endif -#ifdef USE_BLUETOOTH_PROXY - virtual void bluetooth_device_request(const BluetoothDeviceRequest &msg) = 0; -#endif -#ifdef USE_BLUETOOTH_PROXY - virtual void bluetooth_gatt_get_services(const BluetoothGATTGetServicesRequest &msg) = 0; -#endif -#ifdef USE_BLUETOOTH_PROXY - virtual void bluetooth_gatt_read(const BluetoothGATTReadRequest &msg) = 0; -#endif -#ifdef USE_BLUETOOTH_PROXY - virtual void bluetooth_gatt_write(const BluetoothGATTWriteRequest &msg) = 0; -#endif -#ifdef USE_BLUETOOTH_PROXY - virtual void bluetooth_gatt_read_descriptor(const BluetoothGATTReadDescriptorRequest &msg) = 0; -#endif -#ifdef USE_BLUETOOTH_PROXY - virtual void bluetooth_gatt_write_descriptor(const BluetoothGATTWriteDescriptorRequest &msg) = 0; -#endif -#ifdef USE_BLUETOOTH_PROXY - virtual void bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg) = 0; -#endif -#ifdef USE_BLUETOOTH_PROXY - virtual bool send_subscribe_bluetooth_connections_free_response() = 0; -#endif -#ifdef USE_BLUETOOTH_PROXY - virtual void unsubscribe_bluetooth_le_advertisements() = 0; -#endif -#ifdef USE_BLUETOOTH_PROXY - virtual void bluetooth_scanner_set_mode(const BluetoothScannerSetModeRequest &msg) = 0; -#endif -#ifdef USE_VOICE_ASSISTANT - virtual void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) = 0; -#endif -#ifdef USE_VOICE_ASSISTANT - virtual bool send_voice_assistant_get_configuration_response(const VoiceAssistantConfigurationRequest &msg) = 0; -#endif -#ifdef USE_VOICE_ASSISTANT - virtual void voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) = 0; -#endif -#ifdef USE_ALARM_CONTROL_PANEL - virtual void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) = 0; -#endif -#ifdef USE_ZWAVE_PROXY - virtual void zwave_proxy_frame(const ZWaveProxyFrame &msg) = 0; -#endif -#ifdef USE_ZWAVE_PROXY - virtual void zwave_proxy_request(const ZWaveProxyRequest &msg) = 0; -#endif -#ifdef USE_IR_RF - virtual void infrared_rf_transmit_raw_timings(const InfraredRFTransmitRawTimingsRequest &msg) = 0; -#endif protected: - 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; - void on_subscribe_states_request() override; - void on_subscribe_logs_request(const SubscribeLogsRequest &msg) override; -#ifdef USE_API_HOMEASSISTANT_SERVICES - void on_subscribe_homeassistant_services_request() override; -#endif -#ifdef USE_API_HOMEASSISTANT_STATES - void on_subscribe_home_assistant_states_request() override; -#endif -#ifdef USE_API_USER_DEFINED_ACTIONS - void on_execute_service_request(const ExecuteServiceRequest &msg) override; -#endif -#ifdef USE_API_NOISE - void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) override; -#endif -#ifdef USE_BUTTON - void on_button_command_request(const ButtonCommandRequest &msg) override; -#endif -#ifdef USE_CAMERA - void on_camera_image_request(const CameraImageRequest &msg) override; -#endif -#ifdef USE_CLIMATE - void on_climate_command_request(const ClimateCommandRequest &msg) override; -#endif -#ifdef USE_COVER - void on_cover_command_request(const CoverCommandRequest &msg) override; -#endif -#ifdef USE_DATETIME_DATE - void on_date_command_request(const DateCommandRequest &msg) override; -#endif -#ifdef USE_DATETIME_DATETIME - void on_date_time_command_request(const DateTimeCommandRequest &msg) override; -#endif -#ifdef USE_FAN - void on_fan_command_request(const FanCommandRequest &msg) override; -#endif -#ifdef USE_LIGHT - void on_light_command_request(const LightCommandRequest &msg) override; -#endif -#ifdef USE_LOCK - void on_lock_command_request(const LockCommandRequest &msg) override; -#endif -#ifdef USE_MEDIA_PLAYER - void on_media_player_command_request(const MediaPlayerCommandRequest &msg) override; -#endif -#ifdef USE_NUMBER - void on_number_command_request(const NumberCommandRequest &msg) override; -#endif -#ifdef USE_SELECT - void on_select_command_request(const SelectCommandRequest &msg) override; -#endif -#ifdef USE_SIREN - void on_siren_command_request(const SirenCommandRequest &msg) override; -#endif -#ifdef USE_SWITCH - void on_switch_command_request(const SwitchCommandRequest &msg) override; -#endif -#ifdef USE_TEXT - void on_text_command_request(const TextCommandRequest &msg) override; -#endif -#ifdef USE_DATETIME_TIME - void on_time_command_request(const TimeCommandRequest &msg) override; -#endif -#ifdef USE_UPDATE - void on_update_command_request(const UpdateCommandRequest &msg) override; -#endif -#ifdef USE_VALVE - void on_valve_command_request(const ValveCommandRequest &msg) override; -#endif -#ifdef USE_WATER_HEATER - void on_water_heater_command_request(const WaterHeaterCommandRequest &msg) override; -#endif -#ifdef USE_BLUETOOTH_PROXY - void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override; -#endif -#ifdef USE_BLUETOOTH_PROXY - void on_bluetooth_device_request(const BluetoothDeviceRequest &msg) override; -#endif -#ifdef USE_BLUETOOTH_PROXY - void on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &msg) override; -#endif -#ifdef USE_BLUETOOTH_PROXY - void on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &msg) override; -#endif -#ifdef USE_BLUETOOTH_PROXY - void on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &msg) override; -#endif -#ifdef USE_BLUETOOTH_PROXY - void on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &msg) override; -#endif -#ifdef USE_BLUETOOTH_PROXY - void on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &msg) override; -#endif -#ifdef USE_BLUETOOTH_PROXY - void on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &msg) override; -#endif -#ifdef USE_BLUETOOTH_PROXY - void on_subscribe_bluetooth_connections_free_request() override; -#endif -#ifdef USE_BLUETOOTH_PROXY - void on_unsubscribe_bluetooth_le_advertisements_request() override; -#endif -#ifdef USE_BLUETOOTH_PROXY - void on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &msg) override; -#endif -#ifdef USE_VOICE_ASSISTANT - void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) override; -#endif -#ifdef USE_VOICE_ASSISTANT - void on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg) override; -#endif -#ifdef USE_VOICE_ASSISTANT - void on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) override; -#endif -#ifdef USE_ALARM_CONTROL_PANEL - void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) override; -#endif -#ifdef USE_ZWAVE_PROXY - void on_z_wave_proxy_frame(const ZWaveProxyFrame &msg) override; -#endif -#ifdef USE_ZWAVE_PROXY - void on_z_wave_proxy_request(const ZWaveProxyRequest &msg) override; -#endif -#ifdef USE_IR_RF - void on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &msg) override; -#endif void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) override; }; diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index 5fbc1137a8..8673996a25 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -2906,7 +2906,6 @@ static const char *const TAG = "api.service"; class_name = "APIServerConnection" hpp += "\n" hpp += f"class {class_name} : public {class_name}Base {{\n" - hpp += " public:\n" hpp_protected = "" cpp += "\n" @@ -2914,14 +2913,8 @@ static const char *const TAG = "api.service"; message_auth_map: dict[str, bool] = {} message_conn_map: dict[str, bool] = {} - m = serv.method[0] for m in serv.method: - func = m.name inp = m.input_type[1:] - ret = m.output_type[1:] - is_void = ret == "void" - snake = camel_to_snake(inp) - on_func = f"on_{snake}" needs_conn = get_opt(m, pb.needs_setup_connection, True) needs_auth = get_opt(m, pb.needs_authentication, True) @@ -2929,39 +2922,6 @@ static const char *const TAG = "api.service"; message_auth_map[inp] = needs_auth message_conn_map[inp] = needs_conn - ifdef = message_ifdef_map.get(inp, ifdefs.get(inp)) - - if ifdef is not None: - hpp += f"#ifdef {ifdef}\n" - hpp_protected += f"#ifdef {ifdef}\n" - cpp += f"#ifdef {ifdef}\n" - - is_empty = inp in EMPTY_MESSAGES - param = "" if is_empty else f"const {inp} &msg" - arg = "" if is_empty else "msg" - - hpp_protected += f" void {on_func}({param}) override;\n" - if is_void: - hpp += f" virtual void {func}({param}) = 0;\n" - else: - hpp += f" virtual bool send_{func}_response({param}) = 0;\n" - - cpp += f"void {class_name}::{on_func}({param}) {{\n" - body = "" - if is_void: - body += f"this->{func}({arg});\n" - else: - body += f"if (!this->send_{func}_response({arg})) {{\n" - body += " this->on_fatal_error();\n" - body += "}\n" - - cpp += indent(body) + "\n" + "}\n" - - if ifdef is not None: - hpp += "#endif\n" - hpp_protected += "#endif\n" - cpp += "#endif\n" - # Generate optimized read_message with authentication checking # Categorize messages by their authentication requirements no_conn_ids: set[int] = set() From 2376c020e810c1c8c479740098d2307ca1e93a59 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 8 Feb 2026 10:11:42 -0600 Subject: [PATCH 21/42] Merge auth check into base read_message, eliminate APIServerConnection Move the authentication/connection check switch into APIServerConnectionBase::read_message before the dispatch switch, eliminating the APIServerConnection class entirely. APIConnection now inherits directly from APIServerConnectionBase. --- esphome/components/api/api_connection.h | 2 +- esphome/components/api/api_pb2_service.cpp | 41 +++--- esphome/components/api/api_pb2_service.h | 5 - script/api_protobuf/api_protobuf.py | 152 ++++++++++----------- 4 files changed, 87 insertions(+), 113 deletions(-) diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index ae7f864568..7f738a9bfd 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -28,7 +28,7 @@ static constexpr size_t MAX_INITIAL_PER_BATCH = 34; // For clients >= AP static_assert(MAX_MESSAGES_PER_BATCH >= MAX_INITIAL_PER_BATCH, "MAX_MESSAGES_PER_BATCH must be >= MAX_INITIAL_PER_BATCH"); -class APIConnection final : public APIServerConnection { +class APIConnection final : public APIServerConnectionBase { public: friend class APIServer; friend class ListEntitiesIterator; diff --git a/esphome/components/api/api_pb2_service.cpp b/esphome/components/api/api_pb2_service.cpp index 1c04eacc82..2d15deb90d 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -21,6 +21,23 @@ 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) { + // Check authentication/connection requirements + switch (msg_type) { + case HelloRequest::MESSAGE_TYPE: // No setup required + case DisconnectRequest::MESSAGE_TYPE: // No setup required + case PingRequest::MESSAGE_TYPE: // No setup required + break; + case DeviceInfoRequest::MESSAGE_TYPE: // Connection setup only + if (!this->check_connection_setup_()) { + return; + } + break; + default: + if (!this->check_authenticated_()) { + return; + } + break; + } switch (msg_type) { case HelloRequest::MESSAGE_TYPE: { HelloRequest msg; @@ -623,28 +640,4 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, } } -void APIServerConnection::read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) { - // Check authentication/connection requirements for messages - switch (msg_type) { - case HelloRequest::MESSAGE_TYPE: // No setup required - case DisconnectRequest::MESSAGE_TYPE: // No setup required - case PingRequest::MESSAGE_TYPE: // No setup required - break; // Skip all checks for these messages - case DeviceInfoRequest::MESSAGE_TYPE: // Connection setup only - if (!this->check_connection_setup_()) { - return; // Connection not setup - } - break; - default: - // All other messages require authentication (which includes connection check) - if (!this->check_authenticated_()) { - return; // Authentication failed - } - break; - } - - // Call base implementation to process the message - APIServerConnectionBase::read_message(msg_size, msg_type, msg_data); -} - } // namespace esphome::api diff --git a/esphome/components/api/api_pb2_service.h b/esphome/components/api/api_pb2_service.h index 4dc6ce27d0..1441507406 100644 --- a/esphome/components/api/api_pb2_service.h +++ b/esphome/components/api/api_pb2_service.h @@ -228,9 +228,4 @@ class APIServerConnectionBase : public ProtoService { void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) override; }; -class APIServerConnection : public APIServerConnectionBase { - protected: - void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) override; -}; - } // namespace esphome::api diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index 8673996a25..e022b9e2d2 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -2881,9 +2881,78 @@ static const char *const TAG = "api.service"; cases = list(RECEIVE_CASES.items()) cases.sort() + + serv = file.service[0] + + # Build a mapping of message input types to their authentication requirements + message_auth_map: dict[str, bool] = {} + message_conn_map: dict[str, bool] = {} + + for m in serv.method: + inp = m.input_type[1:] + needs_conn = get_opt(m, pb.needs_setup_connection, True) + needs_auth = get_opt(m, pb.needs_authentication, True) + + # Store authentication requirements for message types + message_auth_map[inp] = needs_auth + message_conn_map[inp] = needs_conn + + # Categorize messages by their authentication requirements + no_conn_ids: set[int] = set() + conn_only_ids: set[int] = set() + + for id_, (_, _, case_msg_name) in cases: + if case_msg_name in message_auth_map: + needs_auth = message_auth_map[case_msg_name] + needs_conn = message_conn_map[case_msg_name] + + if not needs_conn: + no_conn_ids.add(id_) + elif not needs_auth: + conn_only_ids.add(id_) + + # Helper to generate case statements with ifdefs + def generate_cases(ids: set[int], comment: str) -> str: + result = "" + for id_ in sorted(ids): + _, ifdef, msg_name = RECEIVE_CASES[id_] + if ifdef: + result += f"#ifdef {ifdef}\n" + result += f" case {msg_name}::MESSAGE_TYPE: {comment}\n" + if ifdef: + 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" + out = f"void {class_name}::read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) {{\n" + + # Auth check block before dispatch switch + if no_conn_ids or conn_only_ids: + out += " // Check authentication/connection requirements\n" + out += " switch (msg_type) {\n" + + if no_conn_ids: + out += generate_cases(no_conn_ids, "// No setup required") + out += " break;\n" + + if conn_only_ids: + out += generate_cases(conn_only_ids, "// Connection setup only") + out += " if (!this->check_connection_setup_()) {\n" + out += " return;\n" + out += " }\n" + out += " break;\n" + + out += " default:\n" + out += " if (!this->check_authenticated_()) {\n" + out += " return;\n" + out += " }\n" + out += " break;\n" + out += " }\n" + + # Dispatch switch out += " switch (msg_type) {\n" for i, (case, ifdef, message_name) in cases: if ifdef is not None: @@ -2902,89 +2971,6 @@ static const char *const TAG = "api.service"; cpp += out hpp += "};\n" - serv = file.service[0] - class_name = "APIServerConnection" - hpp += "\n" - hpp += f"class {class_name} : public {class_name}Base {{\n" - hpp_protected = "" - cpp += "\n" - - # Build a mapping of message input types to their authentication requirements - message_auth_map: dict[str, bool] = {} - message_conn_map: dict[str, bool] = {} - - for m in serv.method: - inp = m.input_type[1:] - needs_conn = get_opt(m, pb.needs_setup_connection, True) - needs_auth = get_opt(m, pb.needs_authentication, True) - - # Store authentication requirements for message types - message_auth_map[inp] = needs_auth - message_conn_map[inp] = needs_conn - - # Generate optimized read_message with authentication checking - # Categorize messages by their authentication requirements - no_conn_ids: set[int] = set() - conn_only_ids: set[int] = set() - - for id_, (_, _, case_msg_name) in cases: - if case_msg_name in message_auth_map: - needs_auth = message_auth_map[case_msg_name] - needs_conn = message_conn_map[case_msg_name] - - if not needs_conn: - no_conn_ids.add(id_) - elif not needs_auth: - conn_only_ids.add(id_) - - # Generate override if we have messages that skip checks - if no_conn_ids or conn_only_ids: - # Helper to generate case statements with ifdefs - def generate_cases(ids: set[int], comment: str) -> str: - result = "" - for id_ in sorted(ids): - _, ifdef, msg_name = RECEIVE_CASES[id_] - if ifdef: - result += f"#ifdef {ifdef}\n" - result += f" case {msg_name}::MESSAGE_TYPE: {comment}\n" - if ifdef: - result += "#endif\n" - return result - - hpp_protected += " void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) override;\n" - - cpp += f"\nvoid {class_name}::read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) {{\n" - cpp += " // Check authentication/connection requirements for messages\n" - cpp += " switch (msg_type) {\n" - - # Messages that don't need any checks - if no_conn_ids: - cpp += generate_cases(no_conn_ids, "// No setup required") - cpp += " break; // Skip all checks for these messages\n" - - # Messages that only need connection setup - if conn_only_ids: - cpp += generate_cases(conn_only_ids, "// Connection setup only") - cpp += " if (!this->check_connection_setup_()) {\n" - cpp += " return; // Connection not setup\n" - cpp += " }\n" - cpp += " break;\n" - - cpp += " default:\n" - cpp += " // All other messages require authentication (which includes connection check)\n" - cpp += " if (!this->check_authenticated_()) {\n" - cpp += " return; // Authentication failed\n" - cpp += " }\n" - cpp += " break;\n" - cpp += " }\n\n" - cpp += " // Call base implementation to process the message\n" - cpp += f" {class_name}Base::read_message(msg_size, msg_type, msg_data);\n" - cpp += "}\n" - - hpp += " protected:\n" - hpp += hpp_protected - hpp += "};\n" - hpp += """\ } // namespace esphome::api From 2eac6b06b7333b482888f28972b525e409fc57f7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 8 Feb 2026 10:28:14 -0600 Subject: [PATCH 22/42] revert workarounds now that libretiny v1.12.1 is released --- .clang-tidy.hash | 2 +- esphome/components/libretiny/__init__.py | 18 ++++++++---------- platformio.ini | 3 +-- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/.clang-tidy.hash b/.clang-tidy.hash index f62d6a02ad..f9de2bc05d 100644 --- a/.clang-tidy.hash +++ b/.clang-tidy.hash @@ -1 +1 @@ -08deb6f9a4fca320183b1f5fed24db904fcd68d13d47a35e89bbe8f05d68091e +0f2b9a65dce7c59289d3aeb40936360a62a7be937b585147b45bb1509eaafb36 diff --git a/esphome/components/libretiny/__init__.py b/esphome/components/libretiny/__init__.py index 6898f6c897..01445da7ee 100644 --- a/esphome/components/libretiny/__init__.py +++ b/esphome/components/libretiny/__init__.py @@ -57,10 +57,9 @@ IS_TARGET_PLATFORM = True # called unconditionally by the SDK. ESPHome doesn't use BLE on LibreTiny. # # This only works on BK7231N (BLE 5.x). Other BK72XX chips using BLE 4.2 -# (BK7231T, BK7231Q, BK7251) cannot disable BLE via custom_options because -# the LibreTiny builder reads CFG_SUPPORT_BLE from the sys_config file at -# build-script time to decide which sources to compile - the custom_options -# override only affects C preprocessor defines, not the builder's Python logic. +# (BK7231T, BK7231Q, BK7251; BK7252 boards use the BK7251 family) have a bug +# where the BLE library still links and references undefined symbols when +# CFG_SUPPORT_BLE=0. # # Other options like CFG_TX_EVM_TEST, CFG_RX_SENSITIVITY_TEST, CFG_SUPPORT_BKREG, # CFG_SUPPORT_OTA_HTTP, and CFG_USE_SPI_SLAVE were evaluated but provide no # NOLINT @@ -193,16 +192,15 @@ def _notify_old_style(config): # The dev and latest branches will be at *least* this version, which is what matters. # Use GitHub releases directly to avoid PlatformIO moderation delays. -# TODO: Revert to v1.12.0 tag once https://github.com/libretiny-eu/libretiny/pull/361 is released ARDUINO_VERSIONS = { - "dev": (cv.Version(1, 12, 0), "https://github.com/libretiny-eu/libretiny.git"), + "dev": (cv.Version(1, 12, 1), "https://github.com/libretiny-eu/libretiny.git"), "latest": ( - cv.Version(1, 12, 0), - "https://github.com/bdraco/libretiny.git#39f407a", + cv.Version(1, 12, 1), + "https://github.com/libretiny-eu/libretiny.git#v1.12.1", ), "recommended": ( - cv.Version(1, 12, 0), - "https://github.com/bdraco/libretiny.git#39f407a", + cv.Version(1, 12, 1), + "https://github.com/libretiny-eu/libretiny.git#v1.12.1", ), } diff --git a/platformio.ini b/platformio.ini index 2bfe9f89e9..ada900ffb2 100644 --- a/platformio.ini +++ b/platformio.ini @@ -213,8 +213,7 @@ build_unflags = ; This are common settings for the LibreTiny (all variants) using Arduino. [common:libretiny-arduino] extends = common:arduino -# TODO: Revert to v1.12.0 tag once https://github.com/libretiny-eu/libretiny/pull/361 is released -platform = https://github.com/bdraco/libretiny.git#39f407a +platform = https://github.com/libretiny-eu/libretiny.git#v1.12.1 framework = arduino lib_compat_mode = soft lib_deps = From 0f1ece6bdc2c390f6a027662545c8d78323c2c82 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 8 Feb 2026 11:14:34 -0600 Subject: [PATCH 23/42] [analyze-memory] Attribute PlatformIO library symbols via nm archive scanning --- esphome/analyze_memory/__init__.py | 152 ++++++++++++++++++++++++++++- esphome/analyze_memory/cli.py | 14 ++- 2 files changed, 162 insertions(+), 4 deletions(-) diff --git a/esphome/analyze_memory/__init__.py b/esphome/analyze_memory/__init__.py index d8abc8bafb..5fa827795f 100644 --- a/esphome/analyze_memory/__init__.py +++ b/esphome/analyze_memory/__init__.py @@ -43,6 +43,7 @@ _READELF_SECTION_PATTERN = re.compile( # Component category prefixes _COMPONENT_PREFIX_ESPHOME = "[esphome]" _COMPONENT_PREFIX_EXTERNAL = "[external]" +_COMPONENT_PREFIX_LIB = "[lib]" _COMPONENT_CORE = f"{_COMPONENT_PREFIX_ESPHOME}core" _COMPONENT_API = f"{_COMPONENT_PREFIX_ESPHOME}api" @@ -56,6 +57,9 @@ SymbolInfoType = tuple[str, int, str] # RAM sections - symbols in these sections consume RAM RAM_SECTIONS = frozenset([".data", ".bss"]) +# nm symbol types for global/weak defined symbols (used for library symbol mapping) +_NM_DEFINED_GLOBAL_TYPES = frozenset({"T", "D", "B", "R", "W", "V"}) + @dataclass class MemorySection: @@ -179,11 +183,16 @@ class MemoryAnalyzer: self._sdk_symbols: list[SDKSymbol] = [] # CSWTCH symbols: list of (name, size, source_file, component) self._cswtch_symbols: list[tuple[str, int, str, str]] = [] + # PlatformIO library symbol mapping: symbol_name -> library_name + self._lib_symbol_map: dict[str, str] = {} + # PlatformIO library hash to name mapping: "lib641" -> "espsoftwareserial" + self._lib_hash_to_name: dict[str, str] = {} def analyze(self) -> dict[str, ComponentMemory]: """Analyze the ELF file and return component memory usage.""" self._parse_sections() self._parse_symbols() + self._scan_pio_libraries() self._categorize_symbols() self._analyze_cswtch_symbols() self._analyze_sdk_libraries() @@ -328,6 +337,10 @@ class MemoryAnalyzer: # If no component match found, it's core return _COMPONENT_CORE + # Check PlatformIO library symbol map (more accurate than heuristic patterns) + if lib_name := self._lib_symbol_map.get(symbol_name): + return f"{_COMPONENT_PREFIX_LIB}{lib_name}" + # Check against symbol patterns for component, patterns in SYMBOL_PATTERNS.items(): if any(pattern in symbol_name for pattern in patterns): @@ -384,6 +397,135 @@ class MemoryAnalyzer: return "Other Core" + def _discover_pio_libraries(self) -> dict[str, Path]: + """Discover PlatformIO third-party libraries from the build directory. + + Scans ``lib/`` directories under ``.pioenvs//`` to find + library names and their ``.a`` archive paths. + + Returns: + Dictionary mapping lowercase library name to ``.a`` file path. + """ + # The ELF is typically at .pioenvs//firmware.elf + build_dir = self.elf_path.parent + libraries: dict[str, Path] = {} + + for entry in build_dir.iterdir(): + if not entry.is_dir() or not entry.name.startswith("lib"): + continue + # Validate that the suffix after "lib" is a hex hash + hex_part = entry.name[3:] + if not hex_part: + continue + try: + int(hex_part, 16) + except ValueError: + continue + + # Each lib/ directory contains a subdirectory named after the library + # and a .a archive named lib.a + for lib_subdir in entry.iterdir(): + if not lib_subdir.is_dir(): + continue + # The .a file is named lib.a (case-insensitive match) + # e.g., lib72a/ESPAsyncTCP/... has lib72a/libESPAsyncTCP.a + archive = entry / f"lib{lib_subdir.name}.a" + if not archive.exists(): + # Try case-insensitive: scan for any .a file + archives = list(entry.glob("*.a")) + archive = archives[0] if archives else None + if archive and archive.exists(): + libraries[lib_subdir.name.lower()] = archive + _LOGGER.debug( + "Discovered PlatformIO library: %s -> %s", + lib_subdir.name, + archive, + ) + + return libraries + + def _build_library_symbol_map(self, libraries: dict[str, Path]) -> dict[str, str]: + """Build a symbol-to-library mapping from library archives. + + Runs ``nm --defined-only`` on each ``.a`` file to collect global and + weak defined symbols. + + Args: + libraries: Dictionary mapping library name to ``.a`` file path. + + Returns: + Dictionary mapping symbol name to library name. + """ + symbol_map: dict[str, str] = {} + + if not self.nm_path: + return symbol_map + + for lib_name, archive_path in libraries.items(): + result = run_tool( + [self.nm_path, "--defined-only", str(archive_path)], + timeout=10, + ) + if result is None or result.returncode != 0: + continue + + for line in result.stdout.splitlines(): + parts = line.split() + if len(parts) < 3: + continue + + sym_type = parts[-2] + sym_name = parts[-1] + + # Include global defined symbols (uppercase) and weak symbols (W/V) + if sym_type in _NM_DEFINED_GLOBAL_TYPES: + symbol_map[sym_name] = lib_name + + return symbol_map + + def _scan_pio_libraries(self) -> None: + """Discover PlatformIO libraries and build symbol mapping. + + Orchestrates library discovery, symbol map construction, and + hash-to-name mapping for CSWTCH attribution. + """ + libraries = self._discover_pio_libraries() + if not libraries: + _LOGGER.debug("No PlatformIO third-party libraries found") + return + + _LOGGER.info( + "Scanning %d PlatformIO libraries: %s", + len(libraries), + ", ".join(sorted(libraries)), + ) + + self._lib_symbol_map = self._build_library_symbol_map(libraries) + + # Build hash-to-name mapping for CSWTCH attribution + # e.g., lib641 -> espsoftwareserial + build_dir = self.elf_path.parent + for entry in build_dir.iterdir(): + if not entry.is_dir() or not entry.name.startswith("lib"): + continue + hex_part = entry.name[3:] + if not hex_part: + continue + try: + int(hex_part, 16) + except ValueError: + continue + for lib_subdir in entry.iterdir(): + if lib_subdir.is_dir(): + self._lib_hash_to_name[entry.name] = lib_subdir.name.lower() + break + + _LOGGER.info( + "Built library symbol map: %d symbols from %d libraries", + len(self._lib_symbol_map), + len(libraries), + ) + def _find_object_files_dir(self) -> Path | None: """Find the directory containing object files for this build. @@ -559,9 +701,13 @@ class MemoryAnalyzer: if "esphome" in parts and "components" not in parts: return _COMPONENT_CORE - # Framework/library files - return the first path component - # e.g., lib65b/ESPAsyncTCP/... -> lib65b - # FrameworkArduino/... -> FrameworkArduino + # Framework/library files - check for PlatformIO library hash dirs + # e.g., lib65b/ESPAsyncTCP/... -> [lib]espasynctcp + if parts and parts[0] in self._lib_hash_to_name: + return f"{_COMPONENT_PREFIX_LIB}{self._lib_hash_to_name[parts[0]]}" + + # Other framework/library files - return the first path component + # e.g., FrameworkArduino/... -> FrameworkArduino return parts[0] if parts else source_file def _analyze_cswtch_symbols(self) -> None: diff --git a/esphome/analyze_memory/cli.py b/esphome/analyze_memory/cli.py index bb0eb7723e..dbc19c6b89 100644 --- a/esphome/analyze_memory/cli.py +++ b/esphome/analyze_memory/cli.py @@ -14,6 +14,7 @@ from . import ( _COMPONENT_CORE, _COMPONENT_PREFIX_ESPHOME, _COMPONENT_PREFIX_EXTERNAL, + _COMPONENT_PREFIX_LIB, RAM_SECTIONS, MemoryAnalyzer, ) @@ -407,6 +408,11 @@ class MemoryAnalyzerCLI(MemoryAnalyzer): for name, mem in components if name.startswith(_COMPONENT_PREFIX_EXTERNAL) ] + library_components = [ + (name, mem) + for name, mem in components + if name.startswith(_COMPONENT_PREFIX_LIB) + ] top_esphome_components = sorted( esphome_components, key=lambda x: x[1].flash_total, reverse=True @@ -417,6 +423,11 @@ class MemoryAnalyzerCLI(MemoryAnalyzer): external_components, key=lambda x: x[1].flash_total, reverse=True ) + # Include all library components + top_library_components = sorted( + library_components, key=lambda x: x[1].flash_total, reverse=True + ) + # Check if API component exists and ensure it's included api_component = None for name, mem in components: @@ -435,10 +446,11 @@ class MemoryAnalyzerCLI(MemoryAnalyzer): if name in system_components_to_include ] - # Combine all components to analyze: top ESPHome + all external + API if not already included + system components + # Combine all components to analyze: top ESPHome + all external + libraries + API if not already included + system components components_to_analyze = ( list(top_esphome_components) + list(top_external_components) + + list(top_library_components) + system_components ) if api_component and api_component not in components_to_analyze: From 95e92716d935c3bff0bc98ddb2f9137c93bfd3ee Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 8 Feb 2026 11:19:38 -0600 Subject: [PATCH 24/42] tweak --- esphome/analyze_memory/__init__.py | 105 ++++++++++++++++++++--------- 1 file changed, 74 insertions(+), 31 deletions(-) diff --git a/esphome/analyze_memory/__init__.py b/esphome/analyze_memory/__init__.py index 5fa827795f..6aeb0ef8a6 100644 --- a/esphome/analyze_memory/__init__.py +++ b/esphome/analyze_memory/__init__.py @@ -397,18 +397,22 @@ class MemoryAnalyzer: return "Other Core" - def _discover_pio_libraries(self) -> dict[str, Path]: + def _discover_pio_libraries( + self, + libraries: dict[str, Path], + hash_to_name: dict[str, str], + ) -> None: """Discover PlatformIO third-party libraries from the build directory. Scans ``lib/`` directories under ``.pioenvs//`` to find library names and their ``.a`` archive paths. - Returns: - Dictionary mapping lowercase library name to ``.a`` file path. + Args: + libraries: Dict to populate with library name -> ``.a`` path mappings. + hash_to_name: Dict to populate with dir name -> library name mappings + for CSWTCH attribution (e.g., ``lib641`` -> ``espsoftwareserial``). """ - # The ELF is typically at .pioenvs//firmware.elf build_dir = self.elf_path.parent - libraries: dict[str, Path] = {} for entry in build_dir.iterdir(): if not entry.is_dir() or not entry.name.startswith("lib"): @@ -427,6 +431,7 @@ class MemoryAnalyzer: for lib_subdir in entry.iterdir(): if not lib_subdir.is_dir(): continue + lib_name = lib_subdir.name.lower() # The .a file is named lib.a (case-insensitive match) # e.g., lib72a/ESPAsyncTCP/... has lib72a/libESPAsyncTCP.a archive = entry / f"lib{lib_subdir.name}.a" @@ -435,14 +440,57 @@ class MemoryAnalyzer: archives = list(entry.glob("*.a")) archive = archives[0] if archives else None if archive and archive.exists(): - libraries[lib_subdir.name.lower()] = archive + libraries[lib_name] = archive + hash_to_name[entry.name] = lib_name _LOGGER.debug( "Discovered PlatformIO library: %s -> %s", lib_subdir.name, archive, ) - return libraries + def _discover_idf_managed_components( + self, + libraries: dict[str, Path], + hash_to_name: dict[str, str], + ) -> None: + """Discover ESP-IDF managed component libraries from the build directory. + + ESP-IDF managed components (from the IDF component registry) use a + ``__`` naming convention. Source files live under + ``managed_components/__/`` and the compiled archives are at + ``esp-idf/__/lib__.a``. + + Args: + libraries: Dict to populate with library name -> ``.a`` path mappings. + hash_to_name: Dict to populate with dir name -> library name mappings + for CSWTCH attribution (e.g., ``espressif__mdns`` -> ``mdns``). + """ + build_dir = self.elf_path.parent + + managed_dir = build_dir / "managed_components" + if not managed_dir.is_dir(): + return + + espidf_dir = build_dir / "esp-idf" + + for entry in managed_dir.iterdir(): + if not entry.is_dir() or "__" not in entry.name: + continue + + # Extract the short name: espressif__mdns -> mdns + full_name = entry.name # e.g., espressif__mdns + short_name = full_name.split("__", 1)[1].lower() + + # Find the .a archive under esp-idf/__/ + archive = espidf_dir / full_name / f"lib{full_name}.a" + if archive.exists(): + libraries[short_name] = archive + hash_to_name[full_name] = short_name + _LOGGER.debug( + "Discovered IDF managed component: %s -> %s", + short_name, + archive, + ) def _build_library_symbol_map(self, libraries: dict[str, Path]) -> dict[str, str]: """Build a symbol-to-library mapping from library archives. @@ -484,42 +532,29 @@ class MemoryAnalyzer: return symbol_map def _scan_pio_libraries(self) -> None: - """Discover PlatformIO libraries and build symbol mapping. + """Discover third-party libraries and build symbol mapping. - Orchestrates library discovery, symbol map construction, and - hash-to-name mapping for CSWTCH attribution. + Scans both PlatformIO ``lib/`` directories (Arduino builds) and + ESP-IDF ``managed_components/`` (IDF builds) to find library archives. + Orchestrates symbol map construction and hash-to-name mapping for + CSWTCH attribution. """ - libraries = self._discover_pio_libraries() + libraries: dict[str, Path] = {} + self._discover_pio_libraries(libraries, self._lib_hash_to_name) + self._discover_idf_managed_components(libraries, self._lib_hash_to_name) + if not libraries: - _LOGGER.debug("No PlatformIO third-party libraries found") + _LOGGER.debug("No third-party libraries found") return _LOGGER.info( - "Scanning %d PlatformIO libraries: %s", + "Scanning %d libraries: %s", len(libraries), ", ".join(sorted(libraries)), ) self._lib_symbol_map = self._build_library_symbol_map(libraries) - # Build hash-to-name mapping for CSWTCH attribution - # e.g., lib641 -> espsoftwareserial - build_dir = self.elf_path.parent - for entry in build_dir.iterdir(): - if not entry.is_dir() or not entry.name.startswith("lib"): - continue - hex_part = entry.name[3:] - if not hex_part: - continue - try: - int(hex_part, 16) - except ValueError: - continue - for lib_subdir in entry.iterdir(): - if lib_subdir.is_dir(): - self._lib_hash_to_name[entry.name] = lib_subdir.name.lower() - break - _LOGGER.info( "Built library symbol map: %d symbols from %d libraries", len(self._lib_symbol_map), @@ -706,6 +741,14 @@ class MemoryAnalyzer: if parts and parts[0] in self._lib_hash_to_name: return f"{_COMPONENT_PREFIX_LIB}{self._lib_hash_to_name[parts[0]]}" + # ESP-IDF managed components: managed_components/espressif__mdns/... -> [lib]mdns + if ( + len(parts) >= 2 + and parts[0] == "managed_components" + and parts[1] in self._lib_hash_to_name + ): + return f"{_COMPONENT_PREFIX_LIB}{self._lib_hash_to_name[parts[1]]}" + # Other framework/library files - return the first path component # e.g., FrameworkArduino/... -> FrameworkArduino return parts[0] if parts else source_file From 17664750b79a271aa4c7cd140917190e30426715 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 8 Feb 2026 11:20:54 -0600 Subject: [PATCH 25/42] tweak --- esphome/analyze_memory/__init__.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/esphome/analyze_memory/__init__.py b/esphome/analyze_memory/__init__.py index 6aeb0ef8a6..8de094ed37 100644 --- a/esphome/analyze_memory/__init__.py +++ b/esphome/analyze_memory/__init__.py @@ -57,8 +57,9 @@ SymbolInfoType = tuple[str, int, str] # RAM sections - symbols in these sections consume RAM RAM_SECTIONS = frozenset([".data", ".bss"]) -# nm symbol types for global/weak defined symbols (used for library symbol mapping) -_NM_DEFINED_GLOBAL_TYPES = frozenset({"T", "D", "B", "R", "W", "V"}) +# nm symbol types for defined symbols (used for library symbol mapping) +# Uppercase = global, lowercase = local/static, W/V = weak +_NM_DEFINED_SYMBOL_TYPES = frozenset({"T", "t", "D", "d", "B", "b", "R", "r", "W", "V"}) @dataclass @@ -525,8 +526,8 @@ class MemoryAnalyzer: sym_type = parts[-2] sym_name = parts[-1] - # Include global defined symbols (uppercase) and weak symbols (W/V) - if sym_type in _NM_DEFINED_GLOBAL_TYPES: + # Include all defined symbols (global, local, and weak) + if sym_type in _NM_DEFINED_SYMBOL_TYPES: symbol_map[sym_name] = lib_name return symbol_map From 6dd7f45cc99f20efadf28cc829bb9b27e7d25bc2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 8 Feb 2026 14:12:35 -0600 Subject: [PATCH 26/42] tweak --- esphome/analyze_memory/__init__.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/esphome/analyze_memory/__init__.py b/esphome/analyze_memory/__init__.py index 8de094ed37..62aa59e22c 100644 --- a/esphome/analyze_memory/__init__.py +++ b/esphome/analyze_memory/__init__.py @@ -57,9 +57,10 @@ SymbolInfoType = tuple[str, int, str] # RAM sections - symbols in these sections consume RAM RAM_SECTIONS = frozenset([".data", ".bss"]) -# nm symbol types for defined symbols (used for library symbol mapping) -# Uppercase = global, lowercase = local/static, W/V = weak -_NM_DEFINED_SYMBOL_TYPES = frozenset({"T", "t", "D", "d", "B", "b", "R", "r", "W", "V"}) +# nm symbol types for global/weak defined symbols (used for library symbol mapping) +# Only global (uppercase) and weak symbols are safe to use - local symbols (lowercase) +# can have name collisions across compilation units +_NM_DEFINED_GLOBAL_TYPES = frozenset({"T", "D", "B", "R", "W", "V"}) @dataclass @@ -526,8 +527,8 @@ class MemoryAnalyzer: sym_type = parts[-2] sym_name = parts[-1] - # Include all defined symbols (global, local, and weak) - if sym_type in _NM_DEFINED_SYMBOL_TYPES: + # Include global defined symbols (uppercase) and weak symbols (W/V) + if sym_type in _NM_DEFINED_GLOBAL_TYPES: symbol_map[sym_name] = lib_name return symbol_map From 30558262d84350e144c77ad228701b524cf8c93d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 8 Feb 2026 14:27:09 -0600 Subject: [PATCH 27/42] fix dupes --- esphome/analyze_memory/__init__.py | 100 ++++++++++++++++++++++++----- 1 file changed, 83 insertions(+), 17 deletions(-) diff --git a/esphome/analyze_memory/__init__.py b/esphome/analyze_memory/__init__.py index 62aa59e22c..751f4d5c7f 100644 --- a/esphome/analyze_memory/__init__.py +++ b/esphome/analyze_memory/__init__.py @@ -189,6 +189,8 @@ class MemoryAnalyzer: self._lib_symbol_map: dict[str, str] = {} # PlatformIO library hash to name mapping: "lib641" -> "espsoftwareserial" self._lib_hash_to_name: dict[str, str] = {} + # Heuristic category to library redirect: "mdns_lib" -> "[lib]mdns" + self._heuristic_to_lib: dict[str, str] = {} def analyze(self) -> dict[str, ComponentMemory]: """Analyze the ELF file and return component memory usage.""" @@ -346,12 +348,12 @@ class MemoryAnalyzer: # Check against symbol patterns for component, patterns in SYMBOL_PATTERNS.items(): if any(pattern in symbol_name for pattern in patterns): - return component + return self._heuristic_to_lib.get(component, component) # Check against demangled patterns for component, patterns in DEMANGLED_PATTERNS.items(): if any(pattern in demangled for pattern in patterns): - return component + return self._heuristic_to_lib.get(component, component) # Special cases that need more complex logic @@ -401,16 +403,18 @@ class MemoryAnalyzer: def _discover_pio_libraries( self, - libraries: dict[str, Path], + libraries: dict[str, list[Path]], hash_to_name: dict[str, str], ) -> None: """Discover PlatformIO third-party libraries from the build directory. Scans ``lib/`` directories under ``.pioenvs//`` to find - library names and their ``.a`` archive paths. + library names and their ``.a`` archive or ``.o`` file paths. Args: - libraries: Dict to populate with library name -> ``.a`` path mappings. + libraries: Dict to populate with library name -> file path list mappings. + Prefers ``.a`` archives when available, falls back to ``.o`` files + (e.g., pioarduino ESP32 Arduino builds only produce ``.o`` files). hash_to_name: Dict to populate with dir name -> library name mappings for CSWTCH attribution (e.g., ``lib641`` -> ``espsoftwareserial``). """ @@ -442,17 +446,28 @@ class MemoryAnalyzer: archives = list(entry.glob("*.a")) archive = archives[0] if archives else None if archive and archive.exists(): - libraries[lib_name] = archive + libraries[lib_name] = [archive] hash_to_name[entry.name] = lib_name _LOGGER.debug( "Discovered PlatformIO library: %s -> %s", lib_subdir.name, archive, ) + else: + # No .a archive (e.g., pioarduino CMake builds) - use .o files + obj_files = sorted(lib_subdir.rglob("*.o")) + if obj_files: + libraries[lib_name] = obj_files + hash_to_name[entry.name] = lib_name + _LOGGER.debug( + "Discovered PlatformIO library (objects): %s -> %d .o files", + lib_subdir.name, + len(obj_files), + ) def _discover_idf_managed_components( self, - libraries: dict[str, Path], + libraries: dict[str, list[Path]], hash_to_name: dict[str, str], ) -> None: """Discover ESP-IDF managed component libraries from the build directory. @@ -463,7 +478,7 @@ class MemoryAnalyzer: ``esp-idf/__/lib__.a``. Args: - libraries: Dict to populate with library name -> ``.a`` path mappings. + libraries: Dict to populate with library name -> file path list mappings. hash_to_name: Dict to populate with dir name -> library name mappings for CSWTCH attribution (e.g., ``espressif__mdns`` -> ``mdns``). """ @@ -486,7 +501,7 @@ class MemoryAnalyzer: # Find the .a archive under esp-idf/__/ archive = espidf_dir / full_name / f"lib{full_name}.a" if archive.exists(): - libraries[short_name] = archive + libraries[short_name] = [archive] hash_to_name[full_name] = short_name _LOGGER.debug( "Discovered IDF managed component: %s -> %s", @@ -494,14 +509,17 @@ class MemoryAnalyzer: archive, ) - def _build_library_symbol_map(self, libraries: dict[str, Path]) -> dict[str, str]: - """Build a symbol-to-library mapping from library archives. + def _build_library_symbol_map( + self, libraries: dict[str, list[Path]] + ) -> dict[str, str]: + """Build a symbol-to-library mapping from library archives or object files. - Runs ``nm --defined-only`` on each ``.a`` file to collect global and - weak defined symbols. + Runs ``nm --defined-only`` on each ``.a`` or ``.o`` file to collect + global and weak defined symbols. Args: - libraries: Dictionary mapping library name to ``.a`` file path. + libraries: Dictionary mapping library name to list of file paths + (``.a`` archives or ``.o`` object files). Returns: Dictionary mapping symbol name to library name. @@ -511,9 +529,9 @@ class MemoryAnalyzer: if not self.nm_path: return symbol_map - for lib_name, archive_path in libraries.items(): + for lib_name, file_paths in libraries.items(): result = run_tool( - [self.nm_path, "--defined-only", str(archive_path)], + [self.nm_path, "--defined-only", *(str(p) for p in file_paths)], timeout=10, ) if result is None or result.returncode != 0: @@ -533,6 +551,51 @@ class MemoryAnalyzer: return symbol_map + @staticmethod + def _build_heuristic_to_lib_mapping( + library_names: set[str], + ) -> dict[str, str]: + """Build mapping from heuristic pattern categories to discovered libraries. + + Heuristic categories like ``mdns_lib``, ``web_server_lib``, ``async_tcp`` + exist as approximations for library attribution. When we discover the + actual library, symbols matching those heuristics should be redirected + to the ``[lib]`` category instead. + + The mapping is built by checking if the normalized category name + (stripped of ``_lib`` suffix and underscores) appears as a substring + of any discovered library name. + + Examples:: + + mdns_lib -> mdns -> in "mdns" or "esp8266mdns" -> [lib]mdns + web_server_lib -> webserver -> in "espasyncwebserver" -> [lib]espasyncwebserver + async_tcp -> asynctcp -> in "espasynctcp" -> [lib]espasynctcp + + Args: + library_names: Set of discovered library names (lowercase). + + Returns: + Dictionary mapping heuristic category to ``[lib]`` string. + """ + mapping: dict[str, str] = {} + all_categories = set(SYMBOL_PATTERNS) | set(DEMANGLED_PATTERNS) + + for category in all_categories: + base = category.removesuffix("_lib").replace("_", "") + for lib_name in library_names: + if base in lib_name: + mapping[category] = f"{_COMPONENT_PREFIX_LIB}{lib_name}" + break + + if mapping: + _LOGGER.debug( + "Heuristic-to-library redirects: %s", + ", ".join(f"{k} -> {v}" for k, v in sorted(mapping.items())), + ) + + return mapping + def _scan_pio_libraries(self) -> None: """Discover third-party libraries and build symbol mapping. @@ -541,7 +604,7 @@ class MemoryAnalyzer: Orchestrates symbol map construction and hash-to-name mapping for CSWTCH attribution. """ - libraries: dict[str, Path] = {} + libraries: dict[str, list[Path]] = {} self._discover_pio_libraries(libraries, self._lib_hash_to_name) self._discover_idf_managed_components(libraries, self._lib_hash_to_name) @@ -556,6 +619,9 @@ class MemoryAnalyzer: ) self._lib_symbol_map = self._build_library_symbol_map(libraries) + self._heuristic_to_lib = self._build_heuristic_to_lib_mapping( + set(libraries.keys()) + ) _LOGGER.info( "Built library symbol map: %d symbols from %d libraries", From 6099dd3065dae2e9b949b250577bf0f4e963cd96 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 8 Feb 2026 14:34:00 -0600 Subject: [PATCH 28/42] wip --- esphome/analyze_memory/__init__.py | 43 +++++++++++++----------------- 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/esphome/analyze_memory/__init__.py b/esphome/analyze_memory/__init__.py index 751f4d5c7f..4c908b9fac 100644 --- a/esphome/analyze_memory/__init__.py +++ b/esphome/analyze_memory/__init__.py @@ -185,9 +185,10 @@ class MemoryAnalyzer: self._sdk_symbols: list[SDKSymbol] = [] # CSWTCH symbols: list of (name, size, source_file, component) self._cswtch_symbols: list[tuple[str, int, str, str]] = [] - # PlatformIO library symbol mapping: symbol_name -> library_name + # Library symbol mapping: symbol_name -> library_name self._lib_symbol_map: dict[str, str] = {} - # PlatformIO library hash to name mapping: "lib641" -> "espsoftwareserial" + # Library dir to name mapping: "lib641" -> "espsoftwareserial", + # "espressif__mdns" -> "mdns" self._lib_hash_to_name: dict[str, str] = {} # Heuristic category to library redirect: "mdns_lib" -> "[lib]mdns" self._heuristic_to_lib: dict[str, str] = {} @@ -196,7 +197,7 @@ class MemoryAnalyzer: """Analyze the ELF file and return component memory usage.""" self._parse_sections() self._parse_symbols() - self._scan_pio_libraries() + self._scan_libraries() self._categorize_symbols() self._analyze_cswtch_symbols() self._analyze_sdk_libraries() @@ -433,37 +434,31 @@ class MemoryAnalyzer: continue # Each lib/ directory contains a subdirectory named after the library - # and a .a archive named lib.a for lib_subdir in entry.iterdir(): if not lib_subdir.is_dir(): continue lib_name = lib_subdir.name.lower() - # The .a file is named lib.a (case-insensitive match) + + # Prefer .a archive (lib.a), fall back to .o files # e.g., lib72a/ESPAsyncTCP/... has lib72a/libESPAsyncTCP.a archive = entry / f"lib{lib_subdir.name}.a" - if not archive.exists(): - # Try case-insensitive: scan for any .a file - archives = list(entry.glob("*.a")) - archive = archives[0] if archives else None - if archive and archive.exists(): - libraries[lib_name] = [archive] + if archive.exists(): + file_paths = [archive] + elif archives := list(entry.glob("*.a")): + # Case-insensitive fallback + file_paths = [archives[0]] + else: + # No .a archive (e.g., pioarduino CMake builds) - use .o files + file_paths = sorted(lib_subdir.rglob("*.o")) + + if file_paths: + libraries[lib_name] = file_paths hash_to_name[entry.name] = lib_name _LOGGER.debug( "Discovered PlatformIO library: %s -> %s", lib_subdir.name, - archive, + file_paths[0], ) - else: - # No .a archive (e.g., pioarduino CMake builds) - use .o files - obj_files = sorted(lib_subdir.rglob("*.o")) - if obj_files: - libraries[lib_name] = obj_files - hash_to_name[entry.name] = lib_name - _LOGGER.debug( - "Discovered PlatformIO library (objects): %s -> %d .o files", - lib_subdir.name, - len(obj_files), - ) def _discover_idf_managed_components( self, @@ -596,7 +591,7 @@ class MemoryAnalyzer: return mapping - def _scan_pio_libraries(self) -> None: + def _scan_libraries(self) -> None: """Discover third-party libraries and build symbol mapping. Scans both PlatformIO ``lib/`` directories (Arduino builds) and From 4efdfda8fb0942df4abdddba03ec153904885d24 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 8 Feb 2026 14:34:37 -0600 Subject: [PATCH 29/42] wip --- esphome/analyze_memory/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/analyze_memory/__init__.py b/esphome/analyze_memory/__init__.py index 4c908b9fac..6e02e7c3b2 100644 --- a/esphome/analyze_memory/__init__.py +++ b/esphome/analyze_memory/__init__.py @@ -342,7 +342,7 @@ class MemoryAnalyzer: # If no component match found, it's core return _COMPONENT_CORE - # Check PlatformIO library symbol map (more accurate than heuristic patterns) + # Check library symbol map (more accurate than heuristic patterns) if lib_name := self._lib_symbol_map.get(symbol_name): return f"{_COMPONENT_PREFIX_LIB}{lib_name}" From 5caa54761bdac9e16d6ad21008fc642c428de007 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 8 Feb 2026 14:51:11 -0600 Subject: [PATCH 30/42] tweaks --- esphome/analyze_memory/__init__.py | 92 ++++++++++++++++++++++++++++-- 1 file changed, 88 insertions(+), 4 deletions(-) diff --git a/esphome/analyze_memory/__init__.py b/esphome/analyze_memory/__init__.py index 6e02e7c3b2..9af03b76b8 100644 --- a/esphome/analyze_memory/__init__.py +++ b/esphome/analyze_memory/__init__.py @@ -62,6 +62,11 @@ RAM_SECTIONS = frozenset([".data", ".bss"]) # can have name collisions across compilation units _NM_DEFINED_GLOBAL_TYPES = frozenset({"T", "D", "B", "R", "W", "V"}) +# Pattern matching compiler-generated local names that can collide across compilation +# units (e.g., packet$19, buf$20, flag$5261). These are unsafe for name-based lookup. +# Does NOT match mangled C++ names with optimization suffixes (e.g., func$isra$0). +_COMPILER_LOCAL_PATTERN = re.compile(r"^[a-zA-Z_]\w*\$\d+$") + @dataclass class MemorySection: @@ -591,13 +596,78 @@ class MemoryAnalyzer: return mapping + def _parse_map_file(self) -> dict[str, str] | None: + """Parse linker map file to build authoritative symbol-to-library mapping. + + The linker map file contains the definitive source attribution for every + symbol, including local/static ones that ``nm`` cannot safely export. + + Map file format (GNU ld):: + + .text._mdns_service_task + 0x400e9fdc 0x65c .pioenvs/env/esp-idf/espressif__mdns/libespressif__mdns.a(mdns.c.o) + + Each section entry has a ``.section.symbol_name`` line followed by an + indented line with address, size, and source path. + + Returns: + Symbol-to-library dict, or ``None`` if no usable map file exists. + """ + map_path = self.elf_path.with_suffix(".map") + if not map_path.exists() or map_path.stat().st_size < 10000: + return None + + _LOGGER.info("Parsing linker map file: %s", map_path.name) + + symbol_map: dict[str, str] = {} + current_symbol: str | None = None + section_prefixes = (".text.", ".rodata.", ".data.", ".bss.", ".literal.") + + for line in map_path.read_text().splitlines(): + # Match section.symbol line: " .text.symbol_name" + # Single space indent, starts with dot + if len(line) > 2 and line[0] == " " and line[1] == ".": + stripped = line.strip() + for prefix in section_prefixes: + if stripped.startswith(prefix): + current_symbol = stripped[len(prefix) :] + break + else: + current_symbol = None + continue + + # Match source attribution line: " 0xADDR 0xSIZE source_path" + if current_symbol is None: + continue + + fields = line.split() + # Skip compiler-generated local names (e.g., packet$19, buf$20) + # that can collide across compilation units + if ( + len(fields) >= 3 + and fields[0].startswith("0x") + and fields[1].startswith("0x") + and not _COMPILER_LOCAL_PATTERN.match(current_symbol) + ): + source_path = fields[2] + # Check if source path contains a known library directory + for dir_key, lib_name in self._lib_hash_to_name.items(): + if dir_key in source_path: + symbol_map[current_symbol] = lib_name + break + + current_symbol = None + + return symbol_map or None + def _scan_libraries(self) -> None: """Discover third-party libraries and build symbol mapping. Scans both PlatformIO ``lib/`` directories (Arduino builds) and ESP-IDF ``managed_components/`` (IDF builds) to find library archives. - Orchestrates symbol map construction and hash-to-name mapping for - CSWTCH attribution. + + Uses the linker map file for authoritative symbol attribution when + available, falling back to ``nm`` scanning with heuristic redirects. """ libraries: dict[str, list[Path]] = {} self._discover_pio_libraries(libraries, self._lib_hash_to_name) @@ -613,13 +683,27 @@ class MemoryAnalyzer: ", ".join(sorted(libraries)), ) - self._lib_symbol_map = self._build_library_symbol_map(libraries) + # Heuristic redirect catches local symbols (e.g., mdns_task_buffer$14) + # that can't be safely added to the symbol map due to name collisions self._heuristic_to_lib = self._build_heuristic_to_lib_mapping( set(libraries.keys()) ) + # Try linker map file first (authoritative, includes local symbols) + map_symbols = self._parse_map_file() + if map_symbols is not None: + self._lib_symbol_map = map_symbols + _LOGGER.info( + "Built library symbol map from linker map: %d symbols", + len(self._lib_symbol_map), + ) + return + + # Fall back to nm scanning (global symbols only) + self._lib_symbol_map = self._build_library_symbol_map(libraries) + _LOGGER.info( - "Built library symbol map: %d symbols from %d libraries", + "Built library symbol map from nm: %d symbols from %d libraries", len(self._lib_symbol_map), len(libraries), ) From b728ebd5fb36ae9c9cf60d7c24f5cab3d04e7f0a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 8 Feb 2026 14:56:57 -0600 Subject: [PATCH 31/42] tweaks --- esphome/analyze_memory/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/analyze_memory/__init__.py b/esphome/analyze_memory/__init__.py index 9af03b76b8..fe08a5f966 100644 --- a/esphome/analyze_memory/__init__.py +++ b/esphome/analyze_memory/__init__.py @@ -623,7 +623,7 @@ class MemoryAnalyzer: current_symbol: str | None = None section_prefixes = (".text.", ".rodata.", ".data.", ".bss.", ".literal.") - for line in map_path.read_text().splitlines(): + for line in map_path.read_text(encoding="utf-8").splitlines(): # Match section.symbol line: " .text.symbol_name" # Single space indent, starts with dot if len(line) > 2 and line[0] == " " and line[1] == ".": From 12c369cb09a376d0286b4cbdd9f573a1562281db Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 8 Feb 2026 15:01:01 -0600 Subject: [PATCH 32/42] wip --- esphome/analyze_memory/__init__.py | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/esphome/analyze_memory/__init__.py b/esphome/analyze_memory/__init__.py index fe08a5f966..2a767f4d1c 100644 --- a/esphome/analyze_memory/__init__.py +++ b/esphome/analyze_memory/__init__.py @@ -583,10 +583,20 @@ class MemoryAnalyzer: for category in all_categories: base = category.removesuffix("_lib").replace("_", "") - for lib_name in library_names: - if base in lib_name: - mapping[category] = f"{_COMPONENT_PREFIX_LIB}{lib_name}" - break + # Collect all libraries whose name contains the base string + candidates = [lib_name for lib_name in library_names if base in lib_name] + if not candidates: + continue + + # Choose a deterministic "best" match: + # 1. Prefer exact name matches over substring matches. + # 2. Among non-exact matches, prefer the shortest library name. + # 3. Break remaining ties lexicographically. + best_lib = min( + candidates, + key=lambda lib_name: (lib_name != base, len(lib_name), lib_name), + ) + mapping[category] = f"{_COMPONENT_PREFIX_LIB}{best_lib}" if mapping: _LOGGER.debug( @@ -619,11 +629,17 @@ class MemoryAnalyzer: _LOGGER.info("Parsing linker map file: %s", map_path.name) + try: + map_text = map_path.read_text(encoding="utf-8", errors="replace") + except OSError as err: + _LOGGER.warning("Failed to read map file: %s", err) + return None + symbol_map: dict[str, str] = {} current_symbol: str | None = None section_prefixes = (".text.", ".rodata.", ".data.", ".bss.", ".literal.") - for line in map_path.read_text(encoding="utf-8").splitlines(): + for line in map_text.splitlines(): # Match section.symbol line: " .text.symbol_name" # Single space indent, starts with dot if len(line) > 2 and line[0] == " " and line[1] == ".": From 8f032d5213f6682b8fa42a42cf8732d134d092f8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 8 Feb 2026 15:04:33 -0600 Subject: [PATCH 33/42] wip --- esphome/analyze_memory/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/esphome/analyze_memory/__init__.py b/esphome/analyze_memory/__init__.py index 2a767f4d1c..bf1bcbfa05 100644 --- a/esphome/analyze_memory/__init__.py +++ b/esphome/analyze_memory/__init__.py @@ -594,7 +594,11 @@ class MemoryAnalyzer: # 3. Break remaining ties lexicographically. best_lib = min( candidates, - key=lambda lib_name: (lib_name != base, len(lib_name), lib_name), + key=lambda lib_name, _base=base: ( + lib_name != _base, + len(lib_name), + lib_name, + ), ) mapping[category] = f"{_COMPONENT_PREFIX_LIB}{best_lib}" From a875a2fb9b704870a447d22eca51d9fb22fe120d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 9 Feb 2026 04:29:37 -0600 Subject: [PATCH 34/42] Future-proof available() check to handle negative return values --- esphome/components/ld2412/ld2412.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/esphome/components/ld2412/ld2412.cpp b/esphome/components/ld2412/ld2412.cpp index e5d35b7ffb..972229b7c8 100644 --- a/esphome/components/ld2412/ld2412.cpp +++ b/esphome/components/ld2412/ld2412.cpp @@ -310,8 +310,10 @@ void LD2412Component::restart_and_read_all_info() { } void LD2412Component::loop() { + // All current UART available() implementations return >= 0, + // use <= 0 to future-proof against any that may return negative on error. int avail = this->available(); - if (avail == 0) { + if (avail <= 0) { return; } From dd07e25a8fb15b2d40b3d06e53a65a9b241ddddb Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 9 Feb 2026 04:30:15 -0600 Subject: [PATCH 35/42] Future-proof available() check to handle negative return values --- esphome/components/seeed_mr60bha2/seeed_mr60bha2.cpp | 4 +++- esphome/components/seeed_mr60fda2/seeed_mr60fda2.cpp | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/esphome/components/seeed_mr60bha2/seeed_mr60bha2.cpp b/esphome/components/seeed_mr60bha2/seeed_mr60bha2.cpp index 8cbf3e2e29..d96824f83d 100644 --- a/esphome/components/seeed_mr60bha2/seeed_mr60bha2.cpp +++ b/esphome/components/seeed_mr60bha2/seeed_mr60bha2.cpp @@ -30,8 +30,10 @@ void MR60BHA2Component::dump_config() { // main loop void MR60BHA2Component::loop() { + // All current UART available() implementations return >= 0, + // use <= 0 to future-proof against any that may return negative on error. int avail = this->available(); - if (avail == 0) { + if (avail <= 0) { return; } diff --git a/esphome/components/seeed_mr60fda2/seeed_mr60fda2.cpp b/esphome/components/seeed_mr60fda2/seeed_mr60fda2.cpp index 259e60119d..de799fcfa8 100644 --- a/esphome/components/seeed_mr60fda2/seeed_mr60fda2.cpp +++ b/esphome/components/seeed_mr60fda2/seeed_mr60fda2.cpp @@ -49,8 +49,10 @@ void MR60FDA2Component::setup() { // main loop void MR60FDA2Component::loop() { + // All current UART available() implementations return >= 0, + // use <= 0 to future-proof against any that may return negative on error. int avail = this->available(); - if (avail == 0) { + if (avail <= 0) { return; } From 13f9726534d84d48607c2c34bbd1ee51d5b564b5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 9 Feb 2026 04:30:59 -0600 Subject: [PATCH 36/42] Add comment explaining available() <= 0 check --- esphome/components/rd03d/rd03d.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/components/rd03d/rd03d.cpp b/esphome/components/rd03d/rd03d.cpp index 1b3212e2c0..5e644e537e 100644 --- a/esphome/components/rd03d/rd03d.cpp +++ b/esphome/components/rd03d/rd03d.cpp @@ -81,6 +81,8 @@ void RD03DComponent::dump_config() { } void RD03DComponent::loop() { + // All current UART available() implementations return >= 0, + // use <= 0 to future-proof against any that may return negative on error. int avail = this->available(); if (avail <= 0) return; From 9742880bf71f664e3e5665a31feb1e18e00cf2fe Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 9 Feb 2026 04:31:16 -0600 Subject: [PATCH 37/42] Add comment explaining available() <= 0 check --- esphome/components/dfplayer/dfplayer.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/components/dfplayer/dfplayer.cpp b/esphome/components/dfplayer/dfplayer.cpp index 60fe26e136..6ef9be3865 100644 --- a/esphome/components/dfplayer/dfplayer.cpp +++ b/esphome/components/dfplayer/dfplayer.cpp @@ -133,6 +133,8 @@ void DFPlayer::send_cmd_(uint8_t cmd, uint16_t argument) { void DFPlayer::loop() { // Read message + // All current UART available() implementations return >= 0, + // use <= 0 to future-proof against any that may return negative on error. int avail = this->available(); if (avail <= 0) return; From 68dfb844bd1766707675e46e38442a68c6579b09 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 9 Feb 2026 04:32:14 -0600 Subject: [PATCH 38/42] Future-proof available() check to handle negative return values --- esphome/components/ld2450/ld2450.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/esphome/components/ld2450/ld2450.cpp b/esphome/components/ld2450/ld2450.cpp index 5ee6dd6e3d..7a203f4240 100644 --- a/esphome/components/ld2450/ld2450.cpp +++ b/esphome/components/ld2450/ld2450.cpp @@ -276,8 +276,10 @@ void LD2450Component::dump_config() { } void LD2450Component::loop() { + // All current UART available() implementations return >= 0, + // use <= 0 to future-proof against any that may return negative on error. int avail = this->available(); - if (avail == 0) { + if (avail <= 0) { return; } From 991ce396a9e3ee9ceacdfbba612b5ec6cc789b48 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 9 Feb 2026 04:34:14 -0600 Subject: [PATCH 39/42] Future-proof available() check to handle negative return values --- esphome/components/cse7766/cse7766.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/esphome/components/cse7766/cse7766.cpp b/esphome/components/cse7766/cse7766.cpp index ae4b9c9316..b4b282dff9 100644 --- a/esphome/components/cse7766/cse7766.cpp +++ b/esphome/components/cse7766/cse7766.cpp @@ -15,8 +15,10 @@ void CSE7766Component::loop() { this->raw_data_index_ = 0; } + // All current UART available() implementations return >= 0, + // use <= 0 to future-proof against any that may return negative on error. int avail = this->available(); - if (avail == 0) { + if (avail <= 0) { return; } From d6e692e302a03472aff35136db46e51ae4538190 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 9 Feb 2026 04:34:32 -0600 Subject: [PATCH 40/42] Future-proof available() check to handle negative return values --- esphome/components/ld2410/ld2410.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/esphome/components/ld2410/ld2410.cpp b/esphome/components/ld2410/ld2410.cpp index ab193919b4..c57d3f7cf7 100644 --- a/esphome/components/ld2410/ld2410.cpp +++ b/esphome/components/ld2410/ld2410.cpp @@ -275,8 +275,10 @@ void LD2410Component::restart_and_read_all_info() { } void LD2410Component::loop() { + // All current UART available() implementations return >= 0, + // use <= 0 to future-proof against any that may return negative on error. int avail = this->available(); - if (avail == 0) { + if (avail <= 0) { return; } From 21f270677bd178ff225848befd5b9483370576da Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 9 Feb 2026 04:34:49 -0600 Subject: [PATCH 41/42] Future-proof available() check to handle negative return values --- esphome/components/ld2420/ld2420.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/esphome/components/ld2420/ld2420.cpp b/esphome/components/ld2420/ld2420.cpp index 01f05b21e5..2fdc73ebb2 100644 --- a/esphome/components/ld2420/ld2420.cpp +++ b/esphome/components/ld2420/ld2420.cpp @@ -541,8 +541,10 @@ void LD2420Component::handle_simple_mode_(const uint8_t *inbuf, int len) { } void LD2420Component::read_batch_(std::span buffer) { + // All current UART available() implementations return >= 0, + // use <= 0 to future-proof against any that may return negative on error. int avail = this->available(); - if (avail == 0) { + if (avail <= 0) { return; } From a0f736b7aab3fa84db8193f5b1a760076fd527e6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 9 Feb 2026 04:35:11 -0600 Subject: [PATCH 42/42] Future-proof available() check to handle negative return values --- esphome/components/nextion/nextion.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/esphome/components/nextion/nextion.cpp b/esphome/components/nextion/nextion.cpp index 0bde05b305..aa01914729 100644 --- a/esphome/components/nextion/nextion.cpp +++ b/esphome/components/nextion/nextion.cpp @@ -397,8 +397,10 @@ bool Nextion::remove_from_q_(bool report_empty) { } void Nextion::process_serial_() { + // All current UART available() implementations return >= 0, + // use <= 0 to future-proof against any that may return negative on error. int avail = this->available(); - if (avail == 0) { + if (avail <= 0) { return; }