From a2ed20954275ca40514a60bcfce583a1c545b9dc Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Sun, 27 Jul 2025 17:57:37 +1000 Subject: [PATCH 01/47] [wifi] Disallow psram config with arduino (#9922) --- esphome/components/wifi/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index 4e1d2998f6..26195694e7 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -6,6 +6,7 @@ from esphome.components.esp32 import add_idf_sdkconfig_option, const, get_esp32_ from esphome.components.network import IPAddress from esphome.config_helpers import filter_source_files_from_platform import esphome.config_validation as cv +from esphome.config_validation import only_with_esp_idf from esphome.const import ( CONF_AP, CONF_BSSID, @@ -336,7 +337,7 @@ CONFIG_SCHEMA = cv.All( single=True ), cv.Optional(CONF_USE_PSRAM): cv.All( - cv.requires_component("psram"), cv.boolean + only_with_esp_idf, cv.requires_component("psram"), cv.boolean ), } ), From 20b61d4bdb83c695b068667bd7e17079ce2c7190 Mon Sep 17 00:00:00 2001 From: cryptk <421501+cryptk@users.noreply.github.com> Date: Sun, 27 Jul 2025 21:20:51 -0500 Subject: [PATCH 02/47] Add seed flag when running setup with uv present (#9932) --- script/setup | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/setup b/script/setup index b17d3235a7..1bd7c44575 100755 --- a/script/setup +++ b/script/setup @@ -6,7 +6,7 @@ set -e cd "$(dirname "$0")/.." if [ ! -n "$VIRTUAL_ENV" ]; then if [ -x "$(command -v uv)" ]; then - uv venv venv + uv venv --seed venv else python3 -m venv venv fi From a3690422bf08550873d03e34b8ad75b5024bb910 Mon Sep 17 00:00:00 2001 From: Jimmy Hedman Date: Mon, 28 Jul 2025 05:19:17 +0200 Subject: [PATCH 03/47] Fail with old lerp (#9914) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/core/helpers.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index f67f13b71f..5204804e1e 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -68,7 +68,10 @@ To bit_cast(const From &src) { return dst; } #endif -using std::lerp; + +// clang-format off +inline float lerp(float completion, float start, float end) = delete; // Please use std::lerp. Notice that it has different order on arguments! +// clang-format on // std::byteswap from C++23 template constexpr T byteswap(T n) { From 6ed9214465dd6ddc573a619a46221e4319e6ad43 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 27 Jul 2025 17:20:30 -1000 Subject: [PATCH 04/47] [core] Use nullptr defaults in status_set_error/warning to reduce flash usage (#9931) --- esphome/core/component.cpp | 12 +++++++----- esphome/core/component.h | 4 ++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index 42b0a71d79..513b0a7ba2 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -16,6 +16,7 @@ namespace esphome { static const char *const TAG = "component"; +static const char *const UNSPECIFIED_MESSAGE = "unspecified"; // Global vectors for component data that doesn't belong in every instance. // Using vector instead of unordered_map for both because: @@ -132,7 +133,7 @@ void Component::call_dump_config() { this->dump_config(); if (this->is_failed()) { // Look up error message from global vector - const char *error_msg = "unspecified"; + const char *error_msg = nullptr; if (component_error_messages) { for (const auto &pair : *component_error_messages) { if (pair.first == this) { @@ -141,7 +142,8 @@ void Component::call_dump_config() { } } } - ESP_LOGE(TAG, " %s is marked FAILED: %s", this->get_component_source(), error_msg); + ESP_LOGE(TAG, " %s is marked FAILED: %s", this->get_component_source(), + error_msg ? error_msg : UNSPECIFIED_MESSAGE); } } @@ -284,15 +286,15 @@ void Component::status_set_warning(const char *message) { return; this->component_state_ |= STATUS_LED_WARNING; App.app_state_ |= STATUS_LED_WARNING; - ESP_LOGW(TAG, "%s set Warning flag: %s", this->get_component_source(), message); + ESP_LOGW(TAG, "%s set Warning flag: %s", this->get_component_source(), message ? message : UNSPECIFIED_MESSAGE); } void Component::status_set_error(const char *message) { if ((this->component_state_ & STATUS_LED_ERROR) != 0) return; this->component_state_ |= STATUS_LED_ERROR; App.app_state_ |= STATUS_LED_ERROR; - ESP_LOGE(TAG, "%s set Error flag: %s", this->get_component_source(), message); - if (strcmp(message, "unspecified") != 0) { + ESP_LOGE(TAG, "%s set Error flag: %s", this->get_component_source(), message ? message : UNSPECIFIED_MESSAGE); + if (message != nullptr) { // Lazy allocate the error messages vector if needed if (!component_error_messages) { component_error_messages = std::make_unique>>(); diff --git a/esphome/core/component.h b/esphome/core/component.h index 5f17c1c22a..096c6f9c69 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -202,9 +202,9 @@ class Component { bool status_has_error() const; - void status_set_warning(const char *message = "unspecified"); + void status_set_warning(const char *message = nullptr); - void status_set_error(const char *message = "unspecified"); + void status_set_error(const char *message = nullptr); void status_clear_warning(); From 84ed1bcf34b95d4774d7f4218eeb98e859f761ac Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 27 Jul 2025 17:22:56 -1000 Subject: [PATCH 05/47] [light] Reduce flash usage by 832 bytes through code optimization (#9924) --- esphome/components/light/light_call.cpp | 95 +++++++++++++------ esphome/components/light/light_color_values.h | 27 +++--- .../components/light/light_json_schema.cpp | 90 +++++++++--------- esphome/components/light/light_state.cpp | 17 ++-- 4 files changed, 134 insertions(+), 95 deletions(-) diff --git a/esphome/components/light/light_call.cpp b/esphome/components/light/light_call.cpp index 1b856ad580..60945531cf 100644 --- a/esphome/components/light/light_call.cpp +++ b/esphome/components/light/light_call.cpp @@ -9,11 +9,28 @@ namespace light { static const char *const TAG = "light"; -// Helper function to reduce code size for validation warnings +// Helper functions to reduce code size for logging +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_WARN static void log_validation_warning(const char *name, const char *param_name, float val, float min, float max) { ESP_LOGW(TAG, "'%s': %s value %.2f is out of range [%.1f - %.1f]", name, param_name, val, min, max); } +static void log_feature_not_supported(const char *name, const char *feature) { + ESP_LOGW(TAG, "'%s': %s not supported", name, feature); +} + +static void log_color_mode_not_supported(const char *name, const char *feature) { + ESP_LOGW(TAG, "'%s': color mode does not support setting %s", name, feature); +} + +static void log_invalid_parameter(const char *name, const char *message) { ESP_LOGW(TAG, "'%s': %s", name, message); } +#else +#define log_validation_warning(name, param_name, val, min, max) +#define log_feature_not_supported(name, feature) +#define log_color_mode_not_supported(name, feature) +#define log_invalid_parameter(name, message) +#endif + // Macro to reduce repetitive setter code #define IMPLEMENT_LIGHT_CALL_SETTER(name, type, flag) \ LightCall &LightCall::set_##name(optional(name)) { \ @@ -49,11 +66,21 @@ static const LogString *color_mode_to_human(ColorMode color_mode) { return LOG_STR(""); } +// Helper to log percentage values +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG +static void log_percent(const char *name, const char *param, float value) { + ESP_LOGD(TAG, " %s: %.0f%%", param, value * 100.0f); +} +#else +#define log_percent(name, param, value) +#endif + void LightCall::perform() { const char *name = this->parent_->get_name().c_str(); LightColorValues v = this->validate_(); + const bool publish = this->get_publish_(); - if (this->get_publish_()) { + if (publish) { ESP_LOGD(TAG, "'%s' Setting:", name); // Only print color mode when it's being changed @@ -71,11 +98,11 @@ void LightCall::perform() { } if (this->has_brightness()) { - ESP_LOGD(TAG, " Brightness: %.0f%%", v.get_brightness() * 100.0f); + log_percent(name, "Brightness", v.get_brightness()); } if (this->has_color_brightness()) { - ESP_LOGD(TAG, " Color brightness: %.0f%%", v.get_color_brightness() * 100.0f); + log_percent(name, "Color brightness", v.get_color_brightness()); } if (this->has_red() || this->has_green() || this->has_blue()) { ESP_LOGD(TAG, " Red: %.0f%%, Green: %.0f%%, Blue: %.0f%%", v.get_red() * 100.0f, v.get_green() * 100.0f, @@ -83,7 +110,7 @@ void LightCall::perform() { } if (this->has_white()) { - ESP_LOGD(TAG, " White: %.0f%%", v.get_white() * 100.0f); + log_percent(name, "White", v.get_white()); } if (this->has_color_temperature()) { ESP_LOGD(TAG, " Color temperature: %.1f mireds", v.get_color_temperature()); @@ -97,26 +124,26 @@ void LightCall::perform() { if (this->has_flash_()) { // FLASH - if (this->get_publish_()) { + if (publish) { ESP_LOGD(TAG, " Flash length: %.1fs", this->flash_length_ / 1e3f); } - this->parent_->start_flash_(v, this->flash_length_, this->get_publish_()); + this->parent_->start_flash_(v, this->flash_length_, publish); } else if (this->has_transition_()) { // TRANSITION - if (this->get_publish_()) { + if (publish) { ESP_LOGD(TAG, " Transition length: %.1fs", this->transition_length_ / 1e3f); } // Special case: Transition and effect can be set when turning off if (this->has_effect_()) { - if (this->get_publish_()) { + if (publish) { ESP_LOGD(TAG, " Effect: 'None'"); } this->parent_->stop_effect_(); } - this->parent_->start_transition_(v, this->transition_length_, this->get_publish_()); + this->parent_->start_transition_(v, this->transition_length_, publish); } else if (this->has_effect_()) { // EFFECT @@ -127,7 +154,7 @@ void LightCall::perform() { effect_s = this->parent_->effects_[this->effect_ - 1]->get_name().c_str(); } - if (this->get_publish_()) { + if (publish) { ESP_LOGD(TAG, " Effect: '%s'", effect_s); } @@ -138,13 +165,13 @@ void LightCall::perform() { this->parent_->set_immediately_(v, true); } else { // INSTANT CHANGE - this->parent_->set_immediately_(v, this->get_publish_()); + this->parent_->set_immediately_(v, publish); } if (!this->has_transition_()) { this->parent_->target_state_reached_callback_.call(); } - if (this->get_publish_()) { + if (publish) { this->parent_->publish_state(); } if (this->get_save_()) { @@ -174,19 +201,19 @@ LightColorValues LightCall::validate_() { // Brightness exists check if (this->has_brightness() && this->brightness_ > 0.0f && !(color_mode & ColorCapability::BRIGHTNESS)) { - ESP_LOGW(TAG, "'%s': setting brightness not supported", name); + log_feature_not_supported(name, "brightness"); this->set_flag_(FLAG_HAS_BRIGHTNESS, false); } // Transition length possible check if (this->has_transition_() && this->transition_length_ != 0 && !(color_mode & ColorCapability::BRIGHTNESS)) { - ESP_LOGW(TAG, "'%s': transitions not supported", name); + log_feature_not_supported(name, "transitions"); this->set_flag_(FLAG_HAS_TRANSITION, false); } // Color brightness exists check if (this->has_color_brightness() && this->color_brightness_ > 0.0f && !(color_mode & ColorCapability::RGB)) { - ESP_LOGW(TAG, "'%s': color mode does not support setting RGB brightness", name); + log_color_mode_not_supported(name, "RGB brightness"); this->set_flag_(FLAG_HAS_COLOR_BRIGHTNESS, false); } @@ -194,7 +221,7 @@ LightColorValues LightCall::validate_() { if ((this->has_red() && this->red_ > 0.0f) || (this->has_green() && this->green_ > 0.0f) || (this->has_blue() && this->blue_ > 0.0f)) { if (!(color_mode & ColorCapability::RGB)) { - ESP_LOGW(TAG, "'%s': color mode does not support setting RGB color", name); + log_color_mode_not_supported(name, "RGB color"); this->set_flag_(FLAG_HAS_RED, false); this->set_flag_(FLAG_HAS_GREEN, false); this->set_flag_(FLAG_HAS_BLUE, false); @@ -204,21 +231,21 @@ LightColorValues LightCall::validate_() { // White value exists check if (this->has_white() && this->white_ > 0.0f && !(color_mode & ColorCapability::WHITE || color_mode & ColorCapability::COLD_WARM_WHITE)) { - ESP_LOGW(TAG, "'%s': color mode does not support setting white value", name); + log_color_mode_not_supported(name, "white value"); this->set_flag_(FLAG_HAS_WHITE, false); } // Color temperature exists check if (this->has_color_temperature() && !(color_mode & ColorCapability::COLOR_TEMPERATURE || color_mode & ColorCapability::COLD_WARM_WHITE)) { - ESP_LOGW(TAG, "'%s': color mode does not support setting color temperature", name); + log_color_mode_not_supported(name, "color temperature"); this->set_flag_(FLAG_HAS_COLOR_TEMPERATURE, false); } // Cold/warm white value exists check if ((this->has_cold_white() && this->cold_white_ > 0.0f) || (this->has_warm_white() && this->warm_white_ > 0.0f)) { if (!(color_mode & ColorCapability::COLD_WARM_WHITE)) { - ESP_LOGW(TAG, "'%s': color mode does not support setting cold/warm white value", name); + log_color_mode_not_supported(name, "cold/warm white value"); this->set_flag_(FLAG_HAS_COLD_WHITE, false); this->set_flag_(FLAG_HAS_WARM_WHITE, false); } @@ -292,7 +319,7 @@ LightColorValues LightCall::validate_() { // Flash length check if (this->has_flash_() && this->flash_length_ == 0) { - ESP_LOGW(TAG, "'%s': flash length must be greater than zero", name); + log_invalid_parameter(name, "flash length must be greater than zero"); this->set_flag_(FLAG_HAS_FLASH, false); } @@ -311,13 +338,13 @@ LightColorValues LightCall::validate_() { } if (this->has_effect_() && (this->has_transition_() || this->has_flash_())) { - ESP_LOGW(TAG, "'%s': effect cannot be used with transition/flash", name); + log_invalid_parameter(name, "effect cannot be used with transition/flash"); this->set_flag_(FLAG_HAS_TRANSITION, false); this->set_flag_(FLAG_HAS_FLASH, false); } if (this->has_flash_() && this->has_transition_()) { - ESP_LOGW(TAG, "'%s': flash cannot be used with transition", name); + log_invalid_parameter(name, "flash cannot be used with transition"); this->set_flag_(FLAG_HAS_TRANSITION, false); } @@ -334,7 +361,7 @@ LightColorValues LightCall::validate_() { } if (this->has_transition_() && !supports_transition) { - ESP_LOGW(TAG, "'%s': transitions not supported", name); + log_feature_not_supported(name, "transitions"); this->set_flag_(FLAG_HAS_TRANSITION, false); } @@ -344,7 +371,7 @@ LightColorValues LightCall::validate_() { bool target_state = this->has_state() ? this->state_ : v.is_on(); if (!this->has_flash_() && !target_state) { if (this->has_effect_()) { - ESP_LOGW(TAG, "'%s': cannot start effect when turning off", name); + log_invalid_parameter(name, "cannot start effect when turning off"); this->set_flag_(FLAG_HAS_EFFECT, false); } else if (this->parent_->active_effect_index_ != 0 && explicit_turn_off_request) { // Auto turn off effect @@ -368,21 +395,27 @@ void LightCall::transform_parameters_() { // - RGBWW lights with color_interlock=true, which also sets "brightness" and // "color_temperature" (without color_interlock, CW/WW are set directly) // - Legacy Home Assistant (pre-colormode), which sets "white" and "color_temperature" + + // Cache min/max mireds to avoid repeated calls + const float min_mireds = traits.get_min_mireds(); + const float max_mireds = traits.get_max_mireds(); + if (((this->has_white() && this->white_ > 0.0f) || this->has_color_temperature()) && // (this->color_mode_ & ColorCapability::COLD_WARM_WHITE) && // !(this->color_mode_ & ColorCapability::WHITE) && // !(this->color_mode_ & ColorCapability::COLOR_TEMPERATURE) && // - traits.get_min_mireds() > 0.0f && traits.get_max_mireds() > 0.0f) { + min_mireds > 0.0f && max_mireds > 0.0f) { ESP_LOGD(TAG, "'%s': setting cold/warm white channels using white/color temperature values", this->parent_->get_name().c_str()); if (this->has_color_temperature()) { - const float color_temp = clamp(this->color_temperature_, traits.get_min_mireds(), traits.get_max_mireds()); - const float ww_fraction = - (color_temp - traits.get_min_mireds()) / (traits.get_max_mireds() - traits.get_min_mireds()); + const float color_temp = clamp(this->color_temperature_, min_mireds, max_mireds); + const float range = max_mireds - min_mireds; + const float ww_fraction = (color_temp - min_mireds) / range; const float cw_fraction = 1.0f - ww_fraction; const float max_cw_ww = std::max(ww_fraction, cw_fraction); - this->cold_white_ = gamma_uncorrect(cw_fraction / max_cw_ww, this->parent_->get_gamma_correct()); - this->warm_white_ = gamma_uncorrect(ww_fraction / max_cw_ww, this->parent_->get_gamma_correct()); + const float gamma = this->parent_->get_gamma_correct(); + this->cold_white_ = gamma_uncorrect(cw_fraction / max_cw_ww, gamma); + this->warm_white_ = gamma_uncorrect(ww_fraction / max_cw_ww, gamma); this->set_flag_(FLAG_HAS_COLD_WHITE, true); this->set_flag_(FLAG_HAS_WARM_WHITE, true); } diff --git a/esphome/components/light/light_color_values.h b/esphome/components/light/light_color_values.h index 5653a8d2a5..04d7d1e7d8 100644 --- a/esphome/components/light/light_color_values.h +++ b/esphome/components/light/light_color_values.h @@ -84,18 +84,23 @@ class LightColorValues { * @return The linearly interpolated LightColorValues. */ static LightColorValues lerp(const LightColorValues &start, const LightColorValues &end, float completion) { + // Directly interpolate the raw values to avoid getter/setter overhead. + // This is safe because: + // - All LightColorValues have their values clamped when set via the setters + // - std::lerp guarantees output is in the same range as inputs + // - Therefore the output doesn't need clamping, so we can skip the setters LightColorValues v; - v.set_color_mode(end.color_mode_); - v.set_state(std::lerp(start.get_state(), end.get_state(), completion)); - v.set_brightness(std::lerp(start.get_brightness(), end.get_brightness(), completion)); - v.set_color_brightness(std::lerp(start.get_color_brightness(), end.get_color_brightness(), completion)); - v.set_red(std::lerp(start.get_red(), end.get_red(), completion)); - v.set_green(std::lerp(start.get_green(), end.get_green(), completion)); - v.set_blue(std::lerp(start.get_blue(), end.get_blue(), completion)); - v.set_white(std::lerp(start.get_white(), end.get_white(), completion)); - v.set_color_temperature(std::lerp(start.get_color_temperature(), end.get_color_temperature(), completion)); - v.set_cold_white(std::lerp(start.get_cold_white(), end.get_cold_white(), completion)); - v.set_warm_white(std::lerp(start.get_warm_white(), end.get_warm_white(), completion)); + v.color_mode_ = end.color_mode_; + v.state_ = std::lerp(start.state_, end.state_, completion); + v.brightness_ = std::lerp(start.brightness_, end.brightness_, completion); + v.color_brightness_ = std::lerp(start.color_brightness_, end.color_brightness_, completion); + v.red_ = std::lerp(start.red_, end.red_, completion); + v.green_ = std::lerp(start.green_, end.green_, completion); + v.blue_ = std::lerp(start.blue_, end.blue_, completion); + v.white_ = std::lerp(start.white_, end.white_, completion); + v.color_temperature_ = std::lerp(start.color_temperature_, end.color_temperature_, completion); + v.cold_white_ = std::lerp(start.cold_white_, end.cold_white_, completion); + v.warm_white_ = std::lerp(start.warm_white_, end.warm_white_, completion); return v; } diff --git a/esphome/components/light/light_json_schema.cpp b/esphome/components/light/light_json_schema.cpp index 26615bae5c..896b821705 100644 --- a/esphome/components/light/light_json_schema.cpp +++ b/esphome/components/light/light_json_schema.cpp @@ -8,6 +8,32 @@ namespace light { // See https://www.home-assistant.io/integrations/light.mqtt/#json-schema for documentation on the schema +// Lookup table for color mode strings +static constexpr const char *get_color_mode_json_str(ColorMode mode) { + switch (mode) { + case ColorMode::ON_OFF: + return "onoff"; + case ColorMode::BRIGHTNESS: + return "brightness"; + case ColorMode::WHITE: + return "white"; // not supported by HA in MQTT + case ColorMode::COLOR_TEMPERATURE: + return "color_temp"; + case ColorMode::COLD_WARM_WHITE: + return "cwww"; // not supported by HA + case ColorMode::RGB: + return "rgb"; + case ColorMode::RGB_WHITE: + return "rgbw"; + case ColorMode::RGB_COLOR_TEMPERATURE: + return "rgbct"; // not supported by HA + case ColorMode::RGB_COLD_WARM_WHITE: + return "rgbww"; + default: + return nullptr; + } +} + void LightJSONSchema::dump_json(LightState &state, JsonObject root) { // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson if (state.supports_effects()) @@ -16,60 +42,36 @@ void LightJSONSchema::dump_json(LightState &state, JsonObject root) { auto values = state.remote_values; auto traits = state.get_output()->get_traits(); - switch (values.get_color_mode()) { - case ColorMode::UNKNOWN: // don't need to set color mode if we don't know it - break; - case ColorMode::ON_OFF: - root["color_mode"] = "onoff"; - break; - case ColorMode::BRIGHTNESS: - root["color_mode"] = "brightness"; - break; - case ColorMode::WHITE: // not supported by HA in MQTT - root["color_mode"] = "white"; - break; - case ColorMode::COLOR_TEMPERATURE: - root["color_mode"] = "color_temp"; - break; - case ColorMode::COLD_WARM_WHITE: // not supported by HA - root["color_mode"] = "cwww"; - break; - case ColorMode::RGB: - root["color_mode"] = "rgb"; - break; - case ColorMode::RGB_WHITE: - root["color_mode"] = "rgbw"; - break; - case ColorMode::RGB_COLOR_TEMPERATURE: // not supported by HA - root["color_mode"] = "rgbct"; - break; - case ColorMode::RGB_COLD_WARM_WHITE: - root["color_mode"] = "rgbww"; - break; + const auto color_mode = values.get_color_mode(); + const char *mode_str = get_color_mode_json_str(color_mode); + if (mode_str != nullptr) { + root["color_mode"] = mode_str; } - if (values.get_color_mode() & ColorCapability::ON_OFF) + if (color_mode & ColorCapability::ON_OFF) root["state"] = (values.get_state() != 0.0f) ? "ON" : "OFF"; - if (values.get_color_mode() & ColorCapability::BRIGHTNESS) - root["brightness"] = uint8_t(values.get_brightness() * 255); + if (color_mode & ColorCapability::BRIGHTNESS) + root["brightness"] = to_uint8_scale(values.get_brightness()); JsonObject color = root["color"].to(); - if (values.get_color_mode() & ColorCapability::RGB) { - color["r"] = uint8_t(values.get_color_brightness() * values.get_red() * 255); - color["g"] = uint8_t(values.get_color_brightness() * values.get_green() * 255); - color["b"] = uint8_t(values.get_color_brightness() * values.get_blue() * 255); + if (color_mode & ColorCapability::RGB) { + float color_brightness = values.get_color_brightness(); + color["r"] = to_uint8_scale(color_brightness * values.get_red()); + color["g"] = to_uint8_scale(color_brightness * values.get_green()); + color["b"] = to_uint8_scale(color_brightness * values.get_blue()); } - if (values.get_color_mode() & ColorCapability::WHITE) { - color["w"] = uint8_t(values.get_white() * 255); - root["white_value"] = uint8_t(values.get_white() * 255); // legacy API + if (color_mode & ColorCapability::WHITE) { + uint8_t white_val = to_uint8_scale(values.get_white()); + color["w"] = white_val; + root["white_value"] = white_val; // legacy API } - if (values.get_color_mode() & ColorCapability::COLOR_TEMPERATURE) { + if (color_mode & ColorCapability::COLOR_TEMPERATURE) { // this one isn't under the color subkey for some reason root["color_temp"] = uint32_t(values.get_color_temperature()); } - if (values.get_color_mode() & ColorCapability::COLD_WARM_WHITE) { - color["c"] = uint8_t(values.get_cold_white() * 255); - color["w"] = uint8_t(values.get_warm_white() * 255); + if (color_mode & ColorCapability::COLD_WARM_WHITE) { + color["c"] = to_uint8_scale(values.get_cold_white()); + color["w"] = to_uint8_scale(values.get_warm_white()); } } diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index fd0aafe4c6..5b57707d6b 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -24,7 +24,8 @@ void LightState::setup() { } // When supported color temperature range is known, initialize color temperature setting within bounds. - float min_mireds = this->get_traits().get_min_mireds(); + auto traits = this->get_traits(); + float min_mireds = traits.get_min_mireds(); if (min_mireds > 0) { this->remote_values.set_color_temperature(min_mireds); this->current_values.set_color_temperature(min_mireds); @@ -43,11 +44,8 @@ void LightState::setup() { this->rtc_ = global_preferences->make_preference(this->get_object_id_hash()); // Attempt to load from preferences, else fall back to default values if (!this->rtc_.load(&recovered)) { - recovered.state = false; - if (this->restore_mode_ == LIGHT_RESTORE_DEFAULT_ON || - this->restore_mode_ == LIGHT_RESTORE_INVERTED_DEFAULT_ON) { - recovered.state = true; - } + recovered.state = (this->restore_mode_ == LIGHT_RESTORE_DEFAULT_ON || + this->restore_mode_ == LIGHT_RESTORE_INVERTED_DEFAULT_ON); } else if (this->restore_mode_ == LIGHT_RESTORE_INVERTED_DEFAULT_OFF || this->restore_mode_ == LIGHT_RESTORE_INVERTED_DEFAULT_ON) { // Inverted restore state @@ -88,17 +86,18 @@ void LightState::setup() { } void LightState::dump_config() { ESP_LOGCONFIG(TAG, "Light '%s'", this->get_name().c_str()); - if (this->get_traits().supports_color_capability(ColorCapability::BRIGHTNESS)) { + auto traits = this->get_traits(); + if (traits.supports_color_capability(ColorCapability::BRIGHTNESS)) { ESP_LOGCONFIG(TAG, " Default Transition Length: %.1fs\n" " Gamma Correct: %.2f", this->default_transition_length_ / 1e3f, this->gamma_correct_); } - if (this->get_traits().supports_color_capability(ColorCapability::COLOR_TEMPERATURE)) { + if (traits.supports_color_capability(ColorCapability::COLOR_TEMPERATURE)) { ESP_LOGCONFIG(TAG, " Min Mireds: %.1f\n" " Max Mireds: %.1f", - this->get_traits().get_min_mireds(), this->get_traits().get_max_mireds()); + traits.get_min_mireds(), traits.get_max_mireds()); } } void LightState::loop() { From 7f0c66f8350b1ee32c933fbb524163359afb94b0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 27 Jul 2025 17:24:15 -1000 Subject: [PATCH 06/47] [api] Reduce code duplication in send_noise_encryption_set_key_response (#9918) --- esphome/components/api/api_connection.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 0d3b99cd41..e0d4ec0cc8 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1538,19 +1538,18 @@ void APIConnection::execute_service(const ExecuteServiceRequest &msg) { #endif #ifdef USE_API_NOISE bool APIConnection::send_noise_encryption_set_key_response(const NoiseEncryptionSetKeyRequest &msg) { - psk_t psk{}; NoiseEncryptionSetKeyResponse resp; + resp.success = false; + + psk_t psk{}; if (base64_decode(msg.key, psk.data(), msg.key.size()) != psk.size()) { ESP_LOGW(TAG, "Invalid encryption key length"); - resp.success = false; - return this->send_message(resp, NoiseEncryptionSetKeyResponse::MESSAGE_TYPE); - } - if (!this->parent_->save_noise_psk(psk, true)) { + } else if (!this->parent_->save_noise_psk(psk, true)) { ESP_LOGW(TAG, "Failed to save encryption key"); - resp.success = false; - return this->send_message(resp, NoiseEncryptionSetKeyResponse::MESSAGE_TYPE); + } else { + resp.success = true; } - resp.success = true; + return this->send_message(resp, NoiseEncryptionSetKeyResponse::MESSAGE_TYPE); } #endif From 087970bca8ed71b994b795acf3e473562cb3e3c1 Mon Sep 17 00:00:00 2001 From: Cornelius Mosch <16503500+cmaxl@users.noreply.github.com> Date: Mon, 28 Jul 2025 05:25:32 +0200 Subject: [PATCH 07/47] replace os.getlogin() with getpass.getuser() (#9928) --- esphome/__main__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index c9f5037890..341c1fa893 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -2,6 +2,7 @@ import argparse from datetime import datetime import functools +import getpass import importlib import logging import os @@ -335,7 +336,7 @@ def check_permissions(port): raise EsphomeError( "You do not have read or write permission on the selected serial port. " "To resolve this issue, you can add your user to the dialout group " - f"by running the following command: sudo usermod -a -G dialout {os.getlogin()}. " + f"by running the following command: sudo usermod -a -G dialout {getpass.getuser()}. " "You will need to log out & back in or reboot to activate the new group access." ) From 5029e248ebb68eaf175cef6f66ec03cd06b938ea Mon Sep 17 00:00:00 2001 From: tomaszduda23 Date: Mon, 28 Jul 2025 05:28:27 +0200 Subject: [PATCH 08/47] [packages] add example from documentation to component tests (#9891) --- tests/components/packages/garage-door.yaml | 5 +++++ .../packages/test-vars.esp32-idf.yaml | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 tests/components/packages/garage-door.yaml create mode 100644 tests/components/packages/test-vars.esp32-idf.yaml diff --git a/tests/components/packages/garage-door.yaml b/tests/components/packages/garage-door.yaml new file mode 100644 index 0000000000..e16265d1e1 --- /dev/null +++ b/tests/components/packages/garage-door.yaml @@ -0,0 +1,5 @@ +switch: + - name: ${door_name} Garage Door Switch + platform: gpio + pin: ${door_pin} + id: ${door_id} diff --git a/tests/components/packages/test-vars.esp32-idf.yaml b/tests/components/packages/test-vars.esp32-idf.yaml new file mode 100644 index 0000000000..f12467d9f9 --- /dev/null +++ b/tests/components/packages/test-vars.esp32-idf.yaml @@ -0,0 +1,19 @@ +packages: + left_garage_door: !include + file: garage-door.yaml + vars: + door_name: Left + door_pin: 1 + door_id: left_garage_door + middle_garage_door: !include + file: garage-door.yaml + vars: + door_name: Middle + door_pin: 2 + door_id: middle_garage_door + right_garage_door: !include + file: garage-door.yaml + vars: + door_name: Right + door_pin: 3 + door_id: right_garage_door From c9793f374192dfa575a6ee34ec9475b1dd12bca3 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Tue, 22 Jul 2025 22:57:42 -0400 Subject: [PATCH 09/47] [remote_receiver] Fix idle validation (#9819) --- esphome/components/remote_receiver/__init__.py | 18 +++++++++++++++++- .../remote_receiver/remote_receiver_esp32.cpp | 3 +-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/esphome/components/remote_receiver/__init__.py b/esphome/components/remote_receiver/__init__.py index dffc088085..9095016b55 100644 --- a/esphome/components/remote_receiver/__init__.py +++ b/esphome/components/remote_receiver/__init__.py @@ -60,6 +60,20 @@ RemoteReceiverComponent = remote_receiver_ns.class_( ) +def validate_config(config): + if CORE.is_esp32: + variant = esp32.get_esp32_variant() + if variant in (esp32.const.VARIANT_ESP32, esp32.const.VARIANT_ESP32S2): + max_idle = 65535 + else: + max_idle = 32767 + if CONF_CLOCK_RESOLUTION in config: + max_idle = int(max_idle * 1000000 / config[CONF_CLOCK_RESOLUTION]) + if config[CONF_IDLE].total_microseconds > max_idle: + raise cv.Invalid(f"config 'idle' exceeds the maximum value of {max_idle}us") + return config + + def validate_tolerance(value): if isinstance(value, dict): return TOLERANCE_SCHEMA(value) @@ -136,7 +150,9 @@ CONFIG_SCHEMA = remote_base.validate_triggers( cv.boolean, ), } - ).extend(cv.COMPONENT_SCHEMA) + ) + .extend(cv.COMPONENT_SCHEMA) + .add_extra(validate_config) ) diff --git a/esphome/components/remote_receiver/remote_receiver_esp32.cpp b/esphome/components/remote_receiver/remote_receiver_esp32.cpp index 3d6346baec..3e6172c6d6 100644 --- a/esphome/components/remote_receiver/remote_receiver_esp32.cpp +++ b/esphome/components/remote_receiver/remote_receiver_esp32.cpp @@ -86,10 +86,9 @@ void RemoteReceiverComponent::setup() { uint32_t event_size = sizeof(rmt_rx_done_event_data_t); uint32_t max_filter_ns = 255u * 1000 / (RMT_CLK_FREQ / 1000000); - uint32_t max_idle_ns = 65535u * 1000; memset(&this->store_.config, 0, sizeof(this->store_.config)); this->store_.config.signal_range_min_ns = std::min(this->filter_us_ * 1000, max_filter_ns); - this->store_.config.signal_range_max_ns = std::min(this->idle_us_ * 1000, max_idle_ns); + this->store_.config.signal_range_max_ns = this->idle_us_ * 1000; this->store_.filter_symbols = this->filter_symbols_; this->store_.receive_size = this->receive_symbols_ * sizeof(rmt_symbol_word_t); this->store_.buffer_size = std::max((event_size + this->store_.receive_size) * 2, this->buffer_size_); From 93028a4d908b08e62fb206848c0dc42fdfe3eb77 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Thu, 24 Jul 2025 07:52:07 +1000 Subject: [PATCH 10/47] [gt911] i2c fixes (#9822) --- .../gt911/touchscreen/gt911_touchscreen.cpp | 41 +++++++++---------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/esphome/components/gt911/touchscreen/gt911_touchscreen.cpp b/esphome/components/gt911/touchscreen/gt911_touchscreen.cpp index 1cead70181..5c540effd0 100644 --- a/esphome/components/gt911/touchscreen/gt911_touchscreen.cpp +++ b/esphome/components/gt911/touchscreen/gt911_touchscreen.cpp @@ -8,6 +8,8 @@ namespace gt911 { static const char *const TAG = "gt911.touchscreen"; +static const uint8_t PRIMARY_ADDRESS = 0x5D; // default I2C address for GT911 +static const uint8_t SECONDARY_ADDRESS = 0x14; // secondary I2C address for GT911 static const uint8_t GET_TOUCH_STATE[2] = {0x81, 0x4E}; static const uint8_t CLEAR_TOUCH_STATE[3] = {0x81, 0x4E, 0x00}; static const uint8_t GET_TOUCHES[2] = {0x81, 0x4F}; @@ -18,8 +20,7 @@ static const size_t MAX_BUTTONS = 4; // max number of buttons scanned #define ERROR_CHECK(err) \ if ((err) != i2c::ERROR_OK) { \ - ESP_LOGE(TAG, "Failed to communicate!"); \ - this->status_set_warning(); \ + this->status_set_warning("Communication failure"); \ return; \ } @@ -30,31 +31,31 @@ void GT911Touchscreen::setup() { this->reset_pin_->setup(); this->reset_pin_->digital_write(false); if (this->interrupt_pin_ != nullptr) { - // The interrupt pin is used as an input during reset to select the I2C address. + // temporarily set the interrupt pin to output to control address selection this->interrupt_pin_->pin_mode(gpio::FLAG_OUTPUT); - this->interrupt_pin_->setup(); this->interrupt_pin_->digital_write(false); } delay(2); this->reset_pin_->digital_write(true); delay(50); // NOLINT - if (this->interrupt_pin_ != nullptr) { - this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT); - this->interrupt_pin_->setup(); - } + } + if (this->interrupt_pin_ != nullptr) { + // set pre-configured input mode + this->interrupt_pin_->setup(); } // check the configuration of the int line. uint8_t data[4]; - err = this->write(GET_SWITCHES, 2); + err = this->write(GET_SWITCHES, sizeof(GET_SWITCHES)); + if (err != i2c::ERROR_OK && this->address_ == PRIMARY_ADDRESS) { + this->address_ = SECONDARY_ADDRESS; + err = this->write(GET_SWITCHES, sizeof(GET_SWITCHES)); + } if (err == i2c::ERROR_OK) { err = this->read(data, 1); if (err == i2c::ERROR_OK) { - ESP_LOGD(TAG, "Read from switches: 0x%02X", data[0]); + ESP_LOGD(TAG, "Read from switches at address 0x%02X: 0x%02X", this->address_, data[0]); if (this->interrupt_pin_ != nullptr) { - // datasheet says NOT to use pullup/down on the int line. - this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT); - this->interrupt_pin_->setup(); this->attach_interrupt_(this->interrupt_pin_, (data[0] & 1) ? gpio::INTERRUPT_FALLING_EDGE : gpio::INTERRUPT_RISING_EDGE); } @@ -63,7 +64,7 @@ void GT911Touchscreen::setup() { if (this->x_raw_max_ == 0 || this->y_raw_max_ == 0) { // no calibration? Attempt to read the max values from the touchscreen. if (err == i2c::ERROR_OK) { - err = this->write(GET_MAX_VALUES, 2); + err = this->write(GET_MAX_VALUES, sizeof(GET_MAX_VALUES)); if (err == i2c::ERROR_OK) { err = this->read(data, sizeof(data)); if (err == i2c::ERROR_OK) { @@ -75,15 +76,12 @@ void GT911Touchscreen::setup() { } } if (err != i2c::ERROR_OK) { - ESP_LOGE(TAG, "Failed to read calibration values from touchscreen!"); - this->mark_failed(); + this->mark_failed("Failed to read calibration"); return; } } if (err != i2c::ERROR_OK) { - ESP_LOGE(TAG, "Failed to communicate!"); - this->mark_failed(); - return; + this->mark_failed("Failed to communicate"); } ESP_LOGCONFIG(TAG, "GT911 Touchscreen setup complete"); @@ -94,7 +92,7 @@ void GT911Touchscreen::update_touches() { uint8_t touch_state = 0; uint8_t data[MAX_TOUCHES + 1][8]; // 8 bytes each for each point, plus extra space for the key byte - err = this->write(GET_TOUCH_STATE, sizeof(GET_TOUCH_STATE), false); + err = this->write(GET_TOUCH_STATE, sizeof(GET_TOUCH_STATE)); ERROR_CHECK(err); err = this->read(&touch_state, 1); ERROR_CHECK(err); @@ -106,7 +104,7 @@ void GT911Touchscreen::update_touches() { return; } - err = this->write(GET_TOUCHES, sizeof(GET_TOUCHES), false); + err = this->write(GET_TOUCHES, sizeof(GET_TOUCHES)); ERROR_CHECK(err); // num_of_touches is guaranteed to be 0..5. Also read the key data err = this->read(data[0], sizeof(data[0]) * num_of_touches + 1); @@ -132,6 +130,7 @@ void GT911Touchscreen::dump_config() { ESP_LOGCONFIG(TAG, "GT911 Touchscreen:"); LOG_I2C_DEVICE(this); LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_); + LOG_PIN(" Reset Pin: ", this->reset_pin_); } } // namespace gt911 From 8b0ad3072f94b7614eed2c63217fd8a3f993087a Mon Sep 17 00:00:00 2001 From: Eric Hoffmann Date: Wed, 23 Jul 2025 23:55:31 +0200 Subject: [PATCH 11/47] fix: non-optional x/y target calculation for ld2450 (#9849) --- esphome/components/ld2450/ld2450.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/esphome/components/ld2450/ld2450.cpp b/esphome/components/ld2450/ld2450.cpp index 8f3b3a3f21..09761b2937 100644 --- a/esphome/components/ld2450/ld2450.cpp +++ b/esphome/components/ld2450/ld2450.cpp @@ -477,10 +477,11 @@ void LD2450Component::handle_periodic_data_() { // X start = TARGET_X + index * 8; is_moving = false; + // tx is used for further calculations, so always needs to be populated + val = ld2450::decode_coordinate(this->buffer_data_[start], this->buffer_data_[start + 1]); + tx = val; sensor::Sensor *sx = this->move_x_sensors_[index]; if (sx != nullptr) { - val = ld2450::decode_coordinate(this->buffer_data_[start], this->buffer_data_[start + 1]); - tx = val; if (this->cached_target_data_[index].x != val) { sx->publish_state(val); this->cached_target_data_[index].x = val; @@ -488,10 +489,11 @@ void LD2450Component::handle_periodic_data_() { } // Y start = TARGET_Y + index * 8; + // ty is used for further calculations, so always needs to be populated + val = ld2450::decode_coordinate(this->buffer_data_[start], this->buffer_data_[start + 1]); + ty = val; sensor::Sensor *sy = this->move_y_sensors_[index]; if (sy != nullptr) { - val = ld2450::decode_coordinate(this->buffer_data_[start], this->buffer_data_[start + 1]); - ty = val; if (this->cached_target_data_[index].y != val) { sy->publish_state(val); this->cached_target_data_[index].y = val; From 8dce7b0905640aa448aa50bc47132733d9cf6844 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Sat, 26 Jul 2025 16:23:36 +1200 Subject: [PATCH 12/47] [logger] Don't allow ``logger.log`` actions without configuring the ``logger`` (#9821) --- esphome/components/logger/__init__.py | 1 + tests/component_tests/text/test_text.yaml | 2 ++ 2 files changed, 3 insertions(+) diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index cf2af17677..618f5bcc7a 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -400,6 +400,7 @@ CONF_LOGGER_LOG = "logger.log" LOGGER_LOG_ACTION_SCHEMA = cv.All( cv.maybe_simple_value( { + cv.GenerateID(CONF_LOGGER_ID): cv.use_id(Logger), cv.Required(CONF_FORMAT): cv.string, cv.Optional(CONF_ARGS, default=list): cv.ensure_list(cv.lambda_), cv.Optional(CONF_LEVEL, default="DEBUG"): cv.one_of( diff --git a/tests/component_tests/text/test_text.yaml b/tests/component_tests/text/test_text.yaml index d81c909f9d..9b05d59349 100644 --- a/tests/component_tests/text/test_text.yaml +++ b/tests/component_tests/text/test_text.yaml @@ -4,6 +4,8 @@ esphome: esp32: board: esp32dev +logger: + text: - platform: template name: "test 1 text" From 2f9475a9275a2a7a4db5091d5fb466d6eea76510 Mon Sep 17 00:00:00 2001 From: cryptk <421501+cryptk@users.noreply.github.com> Date: Sun, 27 Jul 2025 21:20:51 -0500 Subject: [PATCH 13/47] Add seed flag when running setup with uv present (#9932) --- script/setup | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/setup b/script/setup index b17d3235a7..1bd7c44575 100755 --- a/script/setup +++ b/script/setup @@ -6,7 +6,7 @@ set -e cd "$(dirname "$0")/.." if [ ! -n "$VIRTUAL_ENV" ]; then if [ -x "$(command -v uv)" ]; then - uv venv venv + uv venv --seed venv else python3 -m venv venv fi From 3a6cc0ea3d95a3c4fce5e56c0f75ad086878dc20 Mon Sep 17 00:00:00 2001 From: Jimmy Hedman Date: Mon, 28 Jul 2025 05:19:17 +0200 Subject: [PATCH 14/47] Fail with old lerp (#9914) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/core/helpers.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 53b79a00b7..4a4c52bb06 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -67,7 +67,10 @@ To bit_cast(const From &src) { return dst; } #endif -using std::lerp; + +// clang-format off +inline float lerp(float completion, float start, float end) = delete; // Please use std::lerp. Notice that it has different order on arguments! +// clang-format on // std::byteswap from C++23 template constexpr T byteswap(T n) { From 573dad17365746b8053087b3047bbfbaa13f5c8a Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 28 Jul 2025 15:55:07 +1200 Subject: [PATCH 15/47] Bump version to 2025.7.4 --- Doxyfile | 2 +- esphome/const.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Doxyfile b/Doxyfile index ae1e519030..afd3b582e5 100644 --- a/Doxyfile +++ b/Doxyfile @@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 2025.7.3 +PROJECT_NUMBER = 2025.7.4 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/esphome/const.py b/esphome/const.py index 476059af62..6ed0119411 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -4,7 +4,7 @@ from enum import Enum from esphome.enum import StrEnum -__version__ = "2025.7.3" +__version__ = "2025.7.4" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From 05f6d01cbe32ebf505b61d5d14607a8309aabe0c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 27 Jul 2025 18:35:35 -1000 Subject: [PATCH 16/47] [api] Add conditional compilation for Home Assistant service subscriptions (#9900) --- esphome/components/api/__init__.py | 7 +++++++ esphome/components/api/api.proto | 2 ++ esphome/components/api/api_connection.h | 4 ++++ esphome/components/api/api_pb2.cpp | 2 ++ esphome/components/api/api_pb2.h | 2 ++ esphome/components/api/api_pb2_dump.cpp | 2 ++ esphome/components/api/api_pb2_service.cpp | 4 ++++ esphome/components/api/api_pb2_service.h | 6 ++++++ esphome/components/api/api_server.cpp | 2 ++ esphome/components/api/api_server.h | 2 ++ esphome/components/api/custom_api_device.h | 2 ++ esphome/components/api/homeassistant_service.h | 2 ++ esphome/components/homeassistant/number/__init__.py | 1 + esphome/components/homeassistant/switch/__init__.py | 1 + esphome/core/defines.h | 1 + 15 files changed, 40 insertions(+) diff --git a/esphome/components/api/__init__.py b/esphome/components/api/__init__.py index f48a7d93be..5d398a4e23 100644 --- a/esphome/components/api/__init__.py +++ b/esphome/components/api/__init__.py @@ -53,6 +53,7 @@ SERVICE_ARG_NATIVE_TYPES = { CONF_ENCRYPTION = "encryption" CONF_BATCH_DELAY = "batch_delay" CONF_CUSTOM_SERVICES = "custom_services" +CONF_HOMEASSISTANT_SERVICES = "homeassistant_services" CONF_HOMEASSISTANT_STATES = "homeassistant_states" @@ -119,6 +120,7 @@ CONFIG_SCHEMA = cv.All( cv.Range(max=cv.TimePeriod(milliseconds=65535)), ), cv.Optional(CONF_CUSTOM_SERVICES, default=False): cv.boolean, + cv.Optional(CONF_HOMEASSISTANT_SERVICES, default=False): cv.boolean, cv.Optional(CONF_HOMEASSISTANT_STATES, default=False): cv.boolean, cv.Optional(CONF_ON_CLIENT_CONNECTED): automation.validate_automation( single=True @@ -148,6 +150,9 @@ async def to_code(config): if config.get(CONF_ACTIONS) or config[CONF_CUSTOM_SERVICES]: cg.add_define("USE_API_SERVICES") + if config[CONF_HOMEASSISTANT_SERVICES]: + cg.add_define("USE_API_HOMEASSISTANT_SERVICES") + if config[CONF_HOMEASSISTANT_STATES]: cg.add_define("USE_API_HOMEASSISTANT_STATES") @@ -240,6 +245,7 @@ HOMEASSISTANT_ACTION_ACTION_SCHEMA = cv.All( HOMEASSISTANT_ACTION_ACTION_SCHEMA, ) async def homeassistant_service_to_code(config, action_id, template_arg, args): + cg.add_define("USE_API_HOMEASSISTANT_SERVICES") serv = await cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, serv, False) templ = await cg.templatable(config[CONF_ACTION], args, None) @@ -283,6 +289,7 @@ HOMEASSISTANT_EVENT_ACTION_SCHEMA = cv.Schema( HOMEASSISTANT_EVENT_ACTION_SCHEMA, ) async def homeassistant_event_to_code(config, action_id, template_arg, args): + cg.add_define("USE_API_HOMEASSISTANT_SERVICES") serv = await cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, serv, True) templ = await cg.templatable(config[CONF_EVENT], args, None) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 5956c8b0b9..eb6ccbfb91 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -755,6 +755,7 @@ message NoiseEncryptionSetKeyResponse { message SubscribeHomeassistantServicesRequest { option (id) = 34; option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_API_HOMEASSISTANT_SERVICES"; } message HomeassistantServiceMap { @@ -766,6 +767,7 @@ message HomeassistantServiceResponse { option (id) = 35; option (source) = SOURCE_SERVER; option (no_delay) = true; + option (ifdef) = "USE_API_HOMEASSISTANT_SERVICES"; string service = 1; repeated HomeassistantServiceMap data = 2; diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 4f4b44b0ad..3c446c431b 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -131,11 +131,13 @@ class APIConnection : public APIServerConnection { 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 void send_homeassistant_service_call(const HomeassistantServiceResponse &call) { if (!this->flags_.service_call_subscription) return; this->send_message(call, HomeassistantServiceResponse::MESSAGE_TYPE); } +#endif #ifdef USE_BLUETOOTH_PROXY void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) override; void unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) override; @@ -209,9 +211,11 @@ class APIConnection : public APIServerConnection { if (msg.dump_config) App.schedule_dump_config(); } +#ifdef USE_API_HOMEASSISTANT_SERVICES void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) override { this->flags_.service_call_subscription = true; } +#endif #ifdef USE_API_HOMEASSISTANT_STATES void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) override; #endif diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index e2d1cfebd4..89177b233a 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -843,6 +843,7 @@ void NoiseEncryptionSetKeyResponse::calculate_size(uint32_t &total_size) const { ProtoSize::add_bool_field(total_size, 1, this->success); } #endif +#ifdef USE_API_HOMEASSISTANT_SERVICES void HomeassistantServiceMap::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->key_ref_); buffer.encode_string(2, this->value_ref_); @@ -871,6 +872,7 @@ void HomeassistantServiceResponse::calculate_size(uint32_t &total_size) const { ProtoSize::add_repeated_message(total_size, 1, this->variables); ProtoSize::add_bool_field(total_size, 1, this->is_event); } +#endif #ifdef USE_API_HOMEASSISTANT_STATES void SubscribeHomeAssistantStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->entity_id_ref_); diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index b7d8945e8e..65623144d1 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -1044,6 +1044,7 @@ class NoiseEncryptionSetKeyResponse : public ProtoMessage { protected: }; #endif +#ifdef USE_API_HOMEASSISTANT_SERVICES class SubscribeHomeassistantServicesRequest : public ProtoDecodableMessage { public: static constexpr uint8_t MESSAGE_TYPE = 34; @@ -1092,6 +1093,7 @@ class HomeassistantServiceResponse : public ProtoMessage { protected: }; +#endif #ifdef USE_API_HOMEASSISTANT_STATES class SubscribeHomeAssistantStatesRequest : public ProtoDecodableMessage { public: diff --git a/esphome/components/api/api_pb2_dump.cpp b/esphome/components/api/api_pb2_dump.cpp index d7f9f63f5f..c18425f369 100644 --- a/esphome/components/api/api_pb2_dump.cpp +++ b/esphome/components/api/api_pb2_dump.cpp @@ -1038,6 +1038,7 @@ void NoiseEncryptionSetKeyRequest::dump_to(std::string &out) const { } void NoiseEncryptionSetKeyResponse::dump_to(std::string &out) const { dump_field(out, "success", this->success); } #endif +#ifdef USE_API_HOMEASSISTANT_SERVICES void SubscribeHomeassistantServicesRequest::dump_to(std::string &out) const { out.append("SubscribeHomeassistantServicesRequest {}"); } @@ -1066,6 +1067,7 @@ void HomeassistantServiceResponse::dump_to(std::string &out) const { } dump_field(out, "is_event", this->is_event); } +#endif #ifdef USE_API_HOMEASSISTANT_STATES void SubscribeHomeAssistantStatesRequest::dump_to(std::string &out) const { out.append("SubscribeHomeAssistantStatesRequest {}"); diff --git a/esphome/components/api/api_pb2_service.cpp b/esphome/components/api/api_pb2_service.cpp index 4674d04f66..26d5b12d00 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -149,6 +149,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, break; } #endif +#ifdef USE_API_HOMEASSISTANT_SERVICES case SubscribeHomeassistantServicesRequest::MESSAGE_TYPE: { SubscribeHomeassistantServicesRequest msg; msg.decode(msg_data, msg_size); @@ -158,6 +159,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, this->on_subscribe_homeassistant_services_request(msg); break; } +#endif case GetTimeRequest::MESSAGE_TYPE: { GetTimeRequest msg; msg.decode(msg_data, msg_size); @@ -639,12 +641,14 @@ void APIServerConnection::on_subscribe_logs_request(const SubscribeLogsRequest & this->subscribe_logs(msg); } } +#ifdef USE_API_HOMEASSISTANT_SERVICES void APIServerConnection::on_subscribe_homeassistant_services_request( const SubscribeHomeassistantServicesRequest &msg) { if (this->check_authenticated_()) { this->subscribe_homeassistant_services(msg); } } +#endif #ifdef USE_API_HOMEASSISTANT_STATES void APIServerConnection::on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) { if (this->check_authenticated_()) { diff --git a/esphome/components/api/api_pb2_service.h b/esphome/components/api/api_pb2_service.h index 19ed85aa0b..6172e33bf6 100644 --- a/esphome/components/api/api_pb2_service.h +++ b/esphome/components/api/api_pb2_service.h @@ -60,7 +60,9 @@ class APIServerConnectionBase : public ProtoService { virtual void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &value){}; #endif +#ifdef USE_API_HOMEASSISTANT_SERVICES virtual void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &value){}; +#endif #ifdef USE_API_HOMEASSISTANT_STATES virtual void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &value){}; @@ -218,7 +220,9 @@ class APIServerConnection : public APIServerConnectionBase { virtual void list_entities(const ListEntitiesRequest &msg) = 0; virtual void subscribe_states(const SubscribeStatesRequest &msg) = 0; virtual void subscribe_logs(const SubscribeLogsRequest &msg) = 0; +#ifdef USE_API_HOMEASSISTANT_SERVICES virtual void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) = 0; +#endif #ifdef USE_API_HOMEASSISTANT_STATES virtual void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) = 0; #endif @@ -338,7 +342,9 @@ class APIServerConnection : public APIServerConnectionBase { void on_list_entities_request(const ListEntitiesRequest &msg) override; void on_subscribe_states_request(const SubscribeStatesRequest &msg) override; void on_subscribe_logs_request(const SubscribeLogsRequest &msg) override; +#ifdef USE_API_HOMEASSISTANT_SERVICES void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &msg) override; +#endif #ifdef USE_API_HOMEASSISTANT_STATES void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) override; #endif diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 0454315760..1f38f4a31a 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -369,11 +369,13 @@ void APIServer::set_password(const std::string &password) { this->password_ = pa void APIServer::set_batch_delay(uint16_t batch_delay) { this->batch_delay_ = batch_delay; } +#ifdef USE_API_HOMEASSISTANT_SERVICES void APIServer::send_homeassistant_service_call(const HomeassistantServiceResponse &call) { for (auto &client : this->clients_) { client->send_homeassistant_service_call(call); } } +#endif #ifdef USE_API_HOMEASSISTANT_STATES void APIServer::subscribe_home_assistant_state(std::string entity_id, optional attribute, diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index 22e9573d7e..8b5e624df2 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -106,7 +106,9 @@ class APIServer : public Component, public Controller { #ifdef USE_MEDIA_PLAYER void on_media_player_update(media_player::MediaPlayer *obj) override; #endif +#ifdef USE_API_HOMEASSISTANT_SERVICES void send_homeassistant_service_call(const HomeassistantServiceResponse &call); +#endif #ifdef USE_API_SERVICES void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); } #endif diff --git a/esphome/components/api/custom_api_device.h b/esphome/components/api/custom_api_device.h index e9e39a0772..2f85a40614 100644 --- a/esphome/components/api/custom_api_device.h +++ b/esphome/components/api/custom_api_device.h @@ -137,6 +137,7 @@ class CustomAPIDevice { } #endif +#ifdef USE_API_HOMEASSISTANT_SERVICES /** Call a Home Assistant service from ESPHome. * * Usage: @@ -221,6 +222,7 @@ class CustomAPIDevice { } global_api_server->send_homeassistant_service_call(resp); } +#endif }; } // namespace esphome::api diff --git a/esphome/components/api/homeassistant_service.h b/esphome/components/api/homeassistant_service.h index 212b3b22d6..ec17c0c7a4 100644 --- a/esphome/components/api/homeassistant_service.h +++ b/esphome/components/api/homeassistant_service.h @@ -2,6 +2,7 @@ #include "api_server.h" #ifdef USE_API +#ifdef USE_API_HOMEASSISTANT_SERVICES #include "api_pb2.h" #include "esphome/core/automation.h" #include "esphome/core/helpers.h" @@ -100,3 +101,4 @@ template class HomeAssistantServiceCallAction : public Action Date: Sun, 27 Jul 2025 18:39:25 -1000 Subject: [PATCH 17/47] [api] Fix string lifetime issue in Home Assistant service calls with templated values (#9909) --- esphome/components/api/api.proto | 2 +- esphome/components/api/api_options.proto | 1 + esphome/components/api/api_pb2.cpp | 4 +- esphome/components/api/api_pb2.h | 3 +- esphome/components/api/api_pb2_dump.cpp | 2 +- esphome/components/api/custom_api_device.h | 4 +- .../components/api/homeassistant_service.h | 9 +- .../number/homeassistant_number.cpp | 6 +- .../switch/homeassistant_switch.cpp | 2 +- script/api_protobuf/api_protobuf.py | 46 ++- .../fixtures/api_homeassistant.yaml | 311 ++++++++++++++++++ tests/integration/test_api_homeassistant.py | 305 +++++++++++++++++ 12 files changed, 670 insertions(+), 25 deletions(-) create mode 100644 tests/integration/fixtures/api_homeassistant.yaml create mode 100644 tests/integration/test_api_homeassistant.py diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index eb6ccbfb91..e0e1602fcb 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -760,7 +760,7 @@ message SubscribeHomeassistantServicesRequest { message HomeassistantServiceMap { string key = 1; - string value = 2; + string value = 2 [(no_zero_copy) = true]; } message HomeassistantServiceResponse { diff --git a/esphome/components/api/api_options.proto b/esphome/components/api/api_options.proto index bb3947e8a3..4f0f52fc6f 100644 --- a/esphome/components/api/api_options.proto +++ b/esphome/components/api/api_options.proto @@ -27,4 +27,5 @@ extend google.protobuf.MessageOptions { extend google.protobuf.FieldOptions { optional string field_ifdef = 1042; optional uint32 fixed_array_size = 50007; + optional bool no_zero_copy = 50008 [default=false]; } diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 89177b233a..b587ee5f03 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -846,11 +846,11 @@ void NoiseEncryptionSetKeyResponse::calculate_size(uint32_t &total_size) const { #ifdef USE_API_HOMEASSISTANT_SERVICES void HomeassistantServiceMap::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->key_ref_); - buffer.encode_string(2, this->value_ref_); + buffer.encode_string(2, this->value); } void HomeassistantServiceMap::calculate_size(uint32_t &total_size) const { ProtoSize::add_string_field(total_size, 1, this->key_ref_.size()); - ProtoSize::add_string_field(total_size, 1, this->value_ref_.size()); + ProtoSize::add_string_field(total_size, 1, this->value.size()); } void HomeassistantServiceResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->service_ref_); diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 65623144d1..fb8174f988 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -1062,8 +1062,7 @@ class HomeassistantServiceMap : public ProtoMessage { public: StringRef key_ref_{}; void set_key(const StringRef &ref) { this->key_ref_ = ref; } - StringRef value_ref_{}; - void set_value(const StringRef &ref) { this->value_ref_ = ref; } + std::string value{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP diff --git a/esphome/components/api/api_pb2_dump.cpp b/esphome/components/api/api_pb2_dump.cpp index c18425f369..aca60464a3 100644 --- a/esphome/components/api/api_pb2_dump.cpp +++ b/esphome/components/api/api_pb2_dump.cpp @@ -1045,7 +1045,7 @@ void SubscribeHomeassistantServicesRequest::dump_to(std::string &out) const { void HomeassistantServiceMap::dump_to(std::string &out) const { MessageDumpHelper helper(out, "HomeassistantServiceMap"); dump_field(out, "key", this->key_ref_); - dump_field(out, "value", this->value_ref_); + dump_field(out, "value", this->value); } void HomeassistantServiceResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "HomeassistantServiceResponse"); diff --git a/esphome/components/api/custom_api_device.h b/esphome/components/api/custom_api_device.h index 2f85a40614..a39947e725 100644 --- a/esphome/components/api/custom_api_device.h +++ b/esphome/components/api/custom_api_device.h @@ -175,7 +175,7 @@ class CustomAPIDevice { resp.data.emplace_back(); auto &kv = resp.data.back(); kv.set_key(StringRef(it.first)); - kv.set_value(StringRef(it.second)); + kv.value = it.second; } global_api_server->send_homeassistant_service_call(resp); } @@ -218,7 +218,7 @@ class CustomAPIDevice { resp.data.emplace_back(); auto &kv = resp.data.back(); kv.set_key(StringRef(it.first)); - kv.set_value(StringRef(it.second)); + kv.value = it.second; } global_api_server->send_homeassistant_service_call(resp); } diff --git a/esphome/components/api/homeassistant_service.h b/esphome/components/api/homeassistant_service.h index ec17c0c7a4..5df9c7c792 100644 --- a/esphome/components/api/homeassistant_service.h +++ b/esphome/components/api/homeassistant_service.h @@ -70,22 +70,19 @@ template class HomeAssistantServiceCallAction : public Actiondata_template_) { resp.data_template.emplace_back(); auto &kv = resp.data_template.back(); kv.set_key(StringRef(it.key)); - std::string value = it.value.value(x...); - kv.set_value(StringRef(value)); + kv.value = it.value.value(x...); } for (auto &it : this->variables_) { resp.variables.emplace_back(); auto &kv = resp.variables.back(); kv.set_key(StringRef(it.key)); - std::string value = it.value.value(x...); - kv.set_value(StringRef(value)); + kv.value = it.value.value(x...); } this->parent_->send_homeassistant_service_call(resp); } diff --git a/esphome/components/homeassistant/number/homeassistant_number.cpp b/esphome/components/homeassistant/number/homeassistant_number.cpp index ffb352c969..87bf6727f2 100644 --- a/esphome/components/homeassistant/number/homeassistant_number.cpp +++ b/esphome/components/homeassistant/number/homeassistant_number.cpp @@ -93,14 +93,12 @@ void HomeassistantNumber::control(float value) { resp.data.emplace_back(); auto &entity_id = resp.data.back(); entity_id.set_key(ENTITY_ID_KEY); - entity_id.set_value(StringRef(this->entity_id_)); + entity_id.value = this->entity_id_; resp.data.emplace_back(); auto &entity_value = resp.data.back(); entity_value.set_key(VALUE_KEY); - // to_string() returns a temporary - must store it to avoid dangling reference - std::string value_str = to_string(value); - entity_value.set_value(StringRef(value_str)); + entity_value.value = to_string(value); api::global_api_server->send_homeassistant_service_call(resp); } diff --git a/esphome/components/homeassistant/switch/homeassistant_switch.cpp b/esphome/components/homeassistant/switch/homeassistant_switch.cpp index 0fe609bf43..b3300335b9 100644 --- a/esphome/components/homeassistant/switch/homeassistant_switch.cpp +++ b/esphome/components/homeassistant/switch/homeassistant_switch.cpp @@ -54,7 +54,7 @@ void HomeassistantSwitch::write_state(bool state) { resp.data.emplace_back(); auto &entity_id_kv = resp.data.back(); entity_id_kv.set_key(ENTITY_ID_KEY); - entity_id_kv.set_value(StringRef(this->entity_id_)); + entity_id_kv.value = this->entity_id_; api::global_api_server->send_homeassistant_service_call(resp); } diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index 92c85d2366..4b9a61383d 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -562,11 +562,16 @@ class StringType(TypeInfo): @property def public_content(self) -> list[str]: content: list[str] = [] - # Add std::string storage if message needs decoding - if self._needs_decode: + + # Check if no_zero_copy option is set + no_zero_copy = get_field_opt(self._field, pb.no_zero_copy, False) + + # Add std::string storage if message needs decoding OR if no_zero_copy is set + if self._needs_decode or no_zero_copy: content.append(f"std::string {self.field_name}{{}};") - if self._needs_encode: + # Only add StringRef if encoding is needed AND no_zero_copy is not set + if self._needs_encode and not no_zero_copy: content.extend( [ # Add StringRef field if message needs encoding @@ -581,13 +586,28 @@ class StringType(TypeInfo): @property def encode_content(self) -> str: - return f"buffer.encode_string({self.number}, this->{self.field_name}_ref_);" + # Check if no_zero_copy option is set + no_zero_copy = get_field_opt(self._field, pb.no_zero_copy, False) + + if no_zero_copy: + # Use the std::string directly + return f"buffer.encode_string({self.number}, this->{self.field_name});" + else: + # Use the StringRef + return f"buffer.encode_string({self.number}, this->{self.field_name}_ref_);" def dump(self, name): + # Check if no_zero_copy option is set + no_zero_copy = get_field_opt(self._field, pb.no_zero_copy, False) + # If name is 'it', this is a repeated field element - always use string if name == "it": return "append_quoted_string(out, StringRef(it));" + # If no_zero_copy is set, always use std::string + if no_zero_copy: + return f'out.append("\'").append(this->{self.field_name}).append("\'");' + # For SOURCE_CLIENT only, always use std::string if not self._needs_encode: return f'out.append("\'").append(this->{self.field_name}).append("\'");' @@ -607,6 +627,13 @@ class StringType(TypeInfo): @property def dump_content(self) -> str: + # Check if no_zero_copy option is set + no_zero_copy = get_field_opt(self._field, pb.no_zero_copy, False) + + # If no_zero_copy is set, always use std::string + if no_zero_copy: + return f'dump_field(out, "{self.name}", this->{self.field_name});' + # For SOURCE_CLIENT only, use std::string if not self._needs_encode: return f'dump_field(out, "{self.name}", this->{self.field_name});' @@ -622,8 +649,15 @@ class StringType(TypeInfo): return o def get_size_calculation(self, name: str, force: bool = False) -> str: - # For SOURCE_CLIENT only messages, use the string field directly - if not self._needs_encode: + # Check if no_zero_copy option is set + no_zero_copy = get_field_opt(self._field, pb.no_zero_copy, False) + + # For SOURCE_CLIENT only messages or no_zero_copy, use the string field directly + if not self._needs_encode or no_zero_copy: + # For no_zero_copy, we need to use .size() on the string + if no_zero_copy and name != "it": + field_id_size = self.calculate_field_id_size() + return f"ProtoSize::add_string_field(total_size, {field_id_size}, this->{self.field_name}.size());" return self._get_simple_size_calculation(name, force, "add_string_field") # Check if this is being called from a repeated field context diff --git a/tests/integration/fixtures/api_homeassistant.yaml b/tests/integration/fixtures/api_homeassistant.yaml new file mode 100644 index 0000000000..ce8628977a --- /dev/null +++ b/tests/integration/fixtures/api_homeassistant.yaml @@ -0,0 +1,311 @@ +esphome: + name: test-ha-api + friendly_name: Home Assistant API Test + +host: + +api: + services: + - service: trigger_all_tests + then: + - logger.log: "=== Starting Home Assistant API Tests ===" + - button.press: test_basic_service + - button.press: test_templated_service + - button.press: test_empty_string_service + - button.press: test_multiple_fields_service + - button.press: test_complex_lambda_service + - button.press: test_all_empty_service + - button.press: test_rapid_service_calls + - button.press: test_read_ha_states + - number.set: + id: ha_number + value: 42.5 + - switch.turn_on: ha_switch + - switch.turn_off: ha_switch + - logger.log: "=== All tests completed ===" + +logger: + level: DEBUG + +# Time component for templated values +time: + - platform: homeassistant + id: homeassistant_time + +# Global variables for testing +globals: + - id: test_brightness + type: int + initial_value: '75' + - id: test_string + type: std::string + initial_value: '"test_value"' + +# Sensors for testing state reading +sensor: + - platform: template + name: "Test Sensor" + id: test_sensor + lambda: return 42.0; + update_interval: 0.1s + + # Home Assistant sensor that reads external state + - platform: homeassistant + name: "HA Temperature" + entity_id: sensor.external_temperature + id: ha_temperature + on_value: + then: + - logger.log: + format: "HA Temperature state updated: %.1f" + args: ['x'] + + # Test multiple HA state sensors + - platform: homeassistant + name: "HA Humidity" + entity_id: sensor.external_humidity + id: ha_humidity + on_value: + then: + - logger.log: + format: "HA Humidity state updated: %.1f" + args: ['x'] + +# Binary sensor from Home Assistant +binary_sensor: + - platform: homeassistant + name: "HA Motion" + entity_id: binary_sensor.external_motion + id: ha_motion + on_state: + then: + - logger.log: + format: "HA Motion state changed: %s" + args: ['x ? "ON" : "OFF"'] + +# Text sensor from Home Assistant +text_sensor: + - platform: homeassistant + name: "HA Weather" + entity_id: weather.home + attribute: condition + id: ha_weather + on_value: + then: + - logger.log: + format: "HA Weather condition updated: %s" + args: ['x.c_str()'] + + # Test empty state handling + - platform: homeassistant + name: "HA Empty State" + entity_id: sensor.nonexistent_sensor + id: ha_empty_state + on_value: + then: + - logger.log: + format: "HA Empty state updated: %s" + args: ['x.c_str()'] + +# Number component for testing HA number control +number: + - platform: template + name: "HA Controlled Number" + id: ha_number + min_value: 0 + max_value: 100 + step: 1 + optimistic: true + set_action: + - logger.log: + format: "Setting HA number to: %.1f" + args: ['x'] + - homeassistant.action: + action: input_number.set_value + data: + entity_id: input_number.test_number + value: !lambda 'return to_string(x);' + +# Switch component for testing HA switch control +switch: + - platform: template + name: "HA Controlled Switch" + id: ha_switch + optimistic: true + turn_on_action: + - logger.log: "Toggling HA switch: switch.test_switch ON" + - homeassistant.action: + action: switch.turn_on + data: + entity_id: switch.test_switch + turn_off_action: + - logger.log: "Toggling HA switch: switch.test_switch OFF" + - homeassistant.action: + action: switch.turn_off + data: + entity_id: switch.test_switch + +# Buttons for testing various service call scenarios +button: + # Test 1: Basic service call with static values + - platform: template + name: "Test Basic Service" + id: test_basic_service + on_press: + - logger.log: "Sending HomeAssistant service call: light.turn_off" + - homeassistant.action: + action: light.turn_off + data: + entity_id: light.test_light + - logger.log: "Service data: entity_id=light.test_light" + + # Test 2: Service call with templated/lambda values (main bug fix test) + - platform: template + name: "Test Templated Service" + id: test_templated_service + on_press: + - logger.log: "Testing templated service call" + - lambda: |- + int brightness_percent = id(test_brightness); + std::string computed = to_string(brightness_percent * 255 / 100); + ESP_LOGI("test", "Lambda computed value: %s", computed.c_str()); + - homeassistant.action: + action: light.turn_on + data: + entity_id: light.test_light + # This creates a temporary string - the main test case + brightness: !lambda 'return to_string(id(test_brightness) * 255 / 100);' + data_template: + color_name: !lambda 'return id(test_string);' + variables: + transition: !lambda 'return "2.5";' + + # Test 3: Service call with empty string values + - platform: template + name: "Test Empty String Service" + id: test_empty_string_service + on_press: + - logger.log: "Testing empty string values" + - homeassistant.action: + action: notify.test + data: + message: "Test message" + title: "" + data_template: + target: !lambda 'return "";' + variables: + sound: !lambda 'return "";' + + - logger.log: "Empty value for key: title" + - logger.log: "Empty value for key: target" + - logger.log: "Empty value for key: sound" + + # Test 4: Service call with multiple data fields + - platform: template + name: "Test Multiple Fields Service" + id: test_multiple_fields_service + on_press: + - logger.log: "Testing multiple data fields" + - homeassistant.action: + action: climate.set_temperature + data: + entity_id: climate.test_climate + temperature: "22" + hvac_mode: "heat" + data_template: + target_temp_high: !lambda 'return "24";' + target_temp_low: !lambda 'return "20";' + variables: + preset_mode: !lambda 'return "comfort";' + + # Test 5: Complex lambda with string operations + - platform: template + name: "Test Complex Lambda Service" + id: test_complex_lambda_service + on_press: + - logger.log: "Testing complex lambda expressions" + - homeassistant.action: + action: script.test_script + data: + entity_id: !lambda |- + std::string base = "light."; + std::string room = "living_room"; + return base + room; + brightness_pct: !lambda |- + float sensor_val = id(test_sensor).state; + int pct = (int)(sensor_val * 2.38); // 42 * 2.38 ≈ 100 + return to_string(pct); + data_template: + message: !lambda |- + char buffer[50]; + snprintf(buffer, sizeof(buffer), "Sensor: %.1f, Time: %02d:%02d", + id(test_sensor).state, + id(homeassistant_time).now().hour, + id(homeassistant_time).now().minute); + return std::string(buffer); + + # Test 6: Service with only empty strings to verify size calculation + - platform: template + name: "Test All Empty Service" + id: test_all_empty_service + on_press: + - logger.log: "Testing all empty string values" + - homeassistant.action: + action: test.empty + data: + field1: "" + field2: "" + data_template: + field3: !lambda 'return "";' + variables: + field4: !lambda 'return "";' + - logger.log: "All empty service call completed" + + # Test 7: Rapid successive service calls + - platform: template + name: "Test Rapid Service Calls" + id: test_rapid_service_calls + on_press: + - logger.log: "Testing rapid service calls" + - repeat: + count: 5 + then: + - homeassistant.action: + action: counter.increment + data: + entity_id: counter.test_counter + - delay: 10ms + - logger.log: "Rapid service calls completed" + + # Test 8: Log current HA states + - platform: template + name: "Test Read HA States" + id: test_read_ha_states + on_press: + - logger.log: "Reading current HA states" + - lambda: |- + if (id(ha_temperature).has_state()) { + ESP_LOGI("test", "Current HA Temperature: %.1f", id(ha_temperature).state); + } else { + ESP_LOGI("test", "HA Temperature has no state"); + } + + if (id(ha_humidity).has_state()) { + ESP_LOGI("test", "Current HA Humidity: %.1f", id(ha_humidity).state); + } else { + ESP_LOGI("test", "HA Humidity has no state"); + } + + ESP_LOGI("test", "Current HA Motion: %s", id(ha_motion).state ? "ON" : "OFF"); + + if (id(ha_weather).has_state()) { + ESP_LOGI("test", "Current HA Weather: %s", id(ha_weather).state.c_str()); + } else { + ESP_LOGI("test", "HA Weather has no state"); + } + + if (id(ha_empty_state).has_state()) { + ESP_LOGI("test", "HA Empty State value: %s", id(ha_empty_state).state.c_str()); + } else { + ESP_LOGI("test", "HA Empty State has no value (expected)"); + } diff --git a/tests/integration/test_api_homeassistant.py b/tests/integration/test_api_homeassistant.py new file mode 100644 index 0000000000..f69838396d --- /dev/null +++ b/tests/integration/test_api_homeassistant.py @@ -0,0 +1,305 @@ +"""Integration test for Home Assistant API functionality. + +Tests: +- Home Assistant service calls with templated values (main bug fix) +- Service calls with empty string values +- Home Assistant state reading (sensors, binary sensors, text sensors) +- Home Assistant number and switch component control +- Complex lambda expressions and string handling +""" + +from __future__ import annotations + +import asyncio +import re + +from aioesphomeapi import HomeassistantServiceCall +import pytest + +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_api_homeassistant( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Comprehensive test for Home Assistant API functionality.""" + loop = asyncio.get_running_loop() + + # Create futures for patterns that capture values + lambda_computed_future = loop.create_future() + ha_temp_state_future = loop.create_future() + ha_humidity_state_future = loop.create_future() + ha_motion_state_future = loop.create_future() + ha_weather_state_future = loop.create_future() + + # State update futures + temp_update_future = loop.create_future() + humidity_update_future = loop.create_future() + motion_update_future = loop.create_future() + weather_update_future = loop.create_future() + + # Number future + ha_number_future = loop.create_future() + + tests_complete_future = loop.create_future() + + # Patterns to match in logs - only keeping patterns that capture values + lambda_computed_pattern = re.compile(r"Lambda computed value: (\d+)") + ha_temp_state_pattern = re.compile(r"Current HA Temperature: ([\d.]+)") + ha_humidity_state_pattern = re.compile(r"Current HA Humidity: ([\d.]+)") + ha_motion_state_pattern = re.compile(r"Current HA Motion: (ON|OFF)") + ha_weather_state_pattern = re.compile(r"Current HA Weather: (\w+)") + + # State update patterns + temp_update_pattern = re.compile(r"HA Temperature state updated: ([\d.]+)") + humidity_update_pattern = re.compile(r"HA Humidity state updated: ([\d.]+)") + motion_update_pattern = re.compile(r"HA Motion state changed: (ON|OFF)") + weather_update_pattern = re.compile(r"HA Weather condition updated: (\w+)") + + # Number pattern + ha_number_pattern = re.compile(r"Setting HA number to: ([\d.]+)") + + tests_complete_pattern = re.compile(r"=== All tests completed ===") + + # Track all log lines for debugging + log_lines: list[str] = [] + + # Track HomeAssistant service calls + ha_service_calls: list[HomeassistantServiceCall] = [] + + # Service call futures organized by service name + service_call_futures = { + "light.turn_off": loop.create_future(), # basic_service_call + "light.turn_on": loop.create_future(), # templated_service_call + "notify.test": loop.create_future(), # empty_string_service_call + "climate.set_temperature": loop.create_future(), # multiple_fields_service_call + "script.test_script": loop.create_future(), # complex_lambda_service_call + "test.empty": loop.create_future(), # all_empty_service_call + "input_number.set_value": loop.create_future(), # ha_number_service_call + "switch.turn_on": loop.create_future(), # ha_switch_on_service_call + "switch.turn_off": loop.create_future(), # ha_switch_off_service_call + } + + def on_service_call(service_call: HomeassistantServiceCall) -> None: + """Capture HomeAssistant service calls.""" + ha_service_calls.append(service_call) + + # Check if this service call is one we're waiting for + if service_call.service in service_call_futures: + future = service_call_futures[service_call.service] + if not future.done(): + future.set_result(service_call) + + def check_output(line: str) -> None: + """Check log output for expected messages.""" + log_lines.append(line) + + # Check for patterns that capture values + if not lambda_computed_future.done(): + match = lambda_computed_pattern.search(line) + if match: + lambda_computed_future.set_result(match.group(1)) + elif not ha_temp_state_future.done() and ha_temp_state_pattern.search(line): + ha_temp_state_future.set_result(line) + elif not ha_humidity_state_future.done() and ha_humidity_state_pattern.search( + line + ): + ha_humidity_state_future.set_result(line) + elif not ha_motion_state_future.done() and ha_motion_state_pattern.search(line): + ha_motion_state_future.set_result(line) + elif not ha_weather_state_future.done() and ha_weather_state_pattern.search( + line + ): + ha_weather_state_future.set_result(line) + + # Check state update patterns + elif not temp_update_future.done() and temp_update_pattern.search(line): + temp_update_future.set_result(line) + elif not humidity_update_future.done() and humidity_update_pattern.search(line): + humidity_update_future.set_result(line) + elif not motion_update_future.done() and motion_update_pattern.search(line): + motion_update_future.set_result(line) + elif not weather_update_future.done() and weather_update_pattern.search(line): + weather_update_future.set_result(line) + + # Check number pattern + elif not ha_number_future.done() and ha_number_pattern.search(line): + match = ha_number_pattern.search(line) + if match: + ha_number_future.set_result(match.group(1)) + + elif not tests_complete_future.done() and tests_complete_pattern.search(line): + tests_complete_future.set_result(True) + + # Run with log monitoring + async with ( + run_compiled(yaml_config, line_callback=check_output), + api_client_connected() as client, + ): + # Verify device info + device_info = await client.device_info() + assert device_info is not None + assert device_info.name == "test-ha-api" + + # Subscribe to HomeAssistant service calls + client.subscribe_service_calls(on_service_call) + + # Send some Home Assistant states for our sensors to read + client.send_home_assistant_state("sensor.external_temperature", "", "22.5") + client.send_home_assistant_state("sensor.external_humidity", "", "65.0") + client.send_home_assistant_state("binary_sensor.external_motion", "", "ON") + client.send_home_assistant_state("weather.home", "condition", "sunny") + + # List entities and services + _, services = await client.list_entities_services() + + # Find the trigger service + trigger_service = next( + (s for s in services if s.name == "trigger_all_tests"), None + ) + assert trigger_service is not None, "trigger_all_tests service not found" + + # Execute all tests + client.execute_service(trigger_service, {}) + + # Wait for all tests to complete with appropriate timeouts + try: + # Templated service test - the main bug fix + computed_value = await asyncio.wait_for(lambda_computed_future, timeout=5.0) + # Verify the computed value is reasonable (75 * 255 / 100 = 191.25 -> 191) + assert computed_value in ["191", "192"], ( + f"Unexpected computed value: {computed_value}" + ) + + # Check state reads - verify we received the mocked values + temp_line = await asyncio.wait_for(ha_temp_state_future, timeout=5.0) + assert "Current HA Temperature: 22.5" in temp_line + + humidity_line = await asyncio.wait_for( + ha_humidity_state_future, timeout=5.0 + ) + assert "Current HA Humidity: 65.0" in humidity_line + + motion_line = await asyncio.wait_for(ha_motion_state_future, timeout=5.0) + assert "Current HA Motion: ON" in motion_line + + weather_line = await asyncio.wait_for(ha_weather_state_future, timeout=5.0) + assert "Current HA Weather: sunny" in weather_line + + # Number test + number_value = await asyncio.wait_for(ha_number_future, timeout=5.0) + assert number_value == "42.5", f"Unexpected number value: {number_value}" + + # Wait for completion + await asyncio.wait_for(tests_complete_future, timeout=5.0) + + # Now verify the protobuf messages + # 1. Basic service call + basic_call = await asyncio.wait_for( + service_call_futures["light.turn_off"], timeout=2.0 + ) + assert basic_call.service == "light.turn_off" + assert "entity_id" in basic_call.data, ( + f"entity_id not found in data: {basic_call.data}" + ) + assert basic_call.data["entity_id"] == "light.test_light", ( + f"Wrong entity_id: {basic_call.data['entity_id']}" + ) + + # 2. Templated service call - verify the temporary string issue is fixed + templated_call = await asyncio.wait_for( + service_call_futures["light.turn_on"], timeout=2.0 + ) + assert templated_call.service == "light.turn_on" + # Check the computed brightness value + assert "brightness" in templated_call.data + assert templated_call.data["brightness"] in ["191", "192"] # 75 * 255 / 100 + # Check data_template + assert "color_name" in templated_call.data_template + assert templated_call.data_template["color_name"] == "test_value" + # Check variables + assert "transition" in templated_call.variables + assert templated_call.variables["transition"] == "2.5" + + # 3. Empty string service call + empty_call = await asyncio.wait_for( + service_call_futures["notify.test"], timeout=2.0 + ) + assert empty_call.service == "notify.test" + # Verify empty strings are properly handled + assert "title" in empty_call.data and empty_call.data["title"] == "" + assert ( + "target" in empty_call.data_template + and empty_call.data_template["target"] == "" + ) + assert ( + "sound" in empty_call.variables and empty_call.variables["sound"] == "" + ) + + # 4. Multiple fields service call + multi_call = await asyncio.wait_for( + service_call_futures["climate.set_temperature"], timeout=2.0 + ) + assert multi_call.service == "climate.set_temperature" + assert multi_call.data["temperature"] == "22" + assert multi_call.data["hvac_mode"] == "heat" + assert multi_call.data_template["target_temp_high"] == "24" + assert multi_call.variables["preset_mode"] == "comfort" + + # 5. Complex lambda service call + complex_call = await asyncio.wait_for( + service_call_futures["script.test_script"], timeout=2.0 + ) + assert complex_call.service == "script.test_script" + assert complex_call.data["entity_id"] == "light.living_room" + assert complex_call.data["brightness_pct"] == "99" # 42 * 2.38 ≈ 99 + # Check message includes sensor value + assert "message" in complex_call.data_template + assert "Sensor: 42.0" in complex_call.data_template["message"] + + # 6. All empty service call + all_empty_call = await asyncio.wait_for( + service_call_futures["test.empty"], timeout=2.0 + ) + assert all_empty_call.service == "test.empty" + # All fields should be empty strings + assert all(v == "" for v in all_empty_call.data.values()) + assert all(v == "" for v in all_empty_call.data_template.values()) + assert all(v == "" for v in all_empty_call.variables.values()) + + # 7. HA Number service call + number_call = await asyncio.wait_for( + service_call_futures["input_number.set_value"], timeout=2.0 + ) + assert number_call.service == "input_number.set_value" + assert number_call.data["entity_id"] == "input_number.test_number" + # The value might be formatted with trailing zeros + assert float(number_call.data["value"]) == 42.5 + + # 8. HA Switch service calls + switch_on_call = await asyncio.wait_for( + service_call_futures["switch.turn_on"], timeout=2.0 + ) + assert switch_on_call.service == "switch.turn_on" + assert switch_on_call.data["entity_id"] == "switch.test_switch" + + switch_off_call = await asyncio.wait_for( + service_call_futures["switch.turn_off"], timeout=2.0 + ) + assert switch_off_call.service == "switch.turn_off" + assert switch_off_call.data["entity_id"] == "switch.test_switch" + + except TimeoutError as e: + # Show recent log lines for debugging + recent_logs = "\n".join(log_lines[-20:]) + service_calls_summary = "\n".join( + f"- {call.service}" for call in ha_service_calls + ) + pytest.fail( + f"Test timed out waiting for expected log pattern or service call. Error: {e}\n\n" + f"Recent log lines:\n{recent_logs}\n\n" + f"Received service calls:\n{service_calls_summary}" + ) From 4933ef780b7d921bd5ddb18853de8611586d5fec Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 27 Jul 2025 18:50:17 -1000 Subject: [PATCH 18/47] [bluetooth_proxy] Fix service discovery cache pollution and descriptor count parameter bug (#9902) --- .../bluetooth_proxy/bluetooth_connection.cpp | 52 ++++++++++--------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp index 4b84257e27..b3484032b2 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp @@ -80,15 +80,10 @@ void BluetoothConnection::send_service_for_discovery_() { &service_result, &service_count, this->send_service_); this->send_service_++; - if (service_status != ESP_GATT_OK) { - ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_service error at offset=%d, status=%d", this->connection_index_, - this->address_str().c_str(), this->send_service_ - 1, service_status); - return; - } - - if (service_count == 0) { - ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_service missing, service_count=%d", this->connection_index_, - this->address_str().c_str(), service_count); + if (service_status != ESP_GATT_OK || service_count == 0) { + ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_service %s, status=%d, service_count=%d, offset=%d", + this->connection_index_, this->address_str().c_str(), service_status != ESP_GATT_OK ? "error" : "missing", + service_status, service_count, this->send_service_ - 1); return; } @@ -104,15 +99,20 @@ void BluetoothConnection::send_service_for_discovery_() { esp_ble_gattc_get_attr_count(this->gattc_if_, this->conn_id_, ESP_GATT_DB_CHARACTERISTIC, service_result.start_handle, service_result.end_handle, 0, &total_char_count); - if (char_count_status == ESP_GATT_OK && total_char_count > 0) { - // Only reserve if we successfully got a count - service_resp.characteristics.reserve(total_char_count); - } else if (char_count_status != ESP_GATT_OK) { + if (char_count_status != ESP_GATT_OK) { ESP_LOGW(TAG, "[%d] [%s] Error getting characteristic count, status=%d", this->connection_index_, this->address_str().c_str(), char_count_status); + return; } - // Now process characteristics + if (total_char_count == 0) { + // No characteristics, just send the service response + api_conn->send_message(resp, api::BluetoothGATTGetServicesResponse::MESSAGE_TYPE); + return; + } + + // Reserve space and process characteristics + service_resp.characteristics.reserve(total_char_count); uint16_t char_offset = 0; esp_gattc_char_elem_t char_result; while (true) { // characteristics @@ -126,7 +126,7 @@ void BluetoothConnection::send_service_for_discovery_() { if (char_status != ESP_GATT_OK) { ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_all_char error, status=%d", this->connection_index_, this->address_str().c_str(), char_status); - break; + return; } if (char_count == 0) { break; @@ -141,19 +141,21 @@ void BluetoothConnection::send_service_for_discovery_() { // Get the number of descriptors directly with one call uint16_t total_desc_count = 0; - esp_gatt_status_t desc_count_status = - esp_ble_gattc_get_attr_count(this->gattc_if_, this->conn_id_, ESP_GATT_DB_DESCRIPTOR, char_result.char_handle, - service_result.end_handle, 0, &total_desc_count); + esp_gatt_status_t desc_count_status = esp_ble_gattc_get_attr_count( + this->gattc_if_, this->conn_id_, ESP_GATT_DB_DESCRIPTOR, 0, 0, char_result.char_handle, &total_desc_count); - if (desc_count_status == ESP_GATT_OK && total_desc_count > 0) { - // Only reserve if we successfully got a count - characteristic_resp.descriptors.reserve(total_desc_count); - } else if (desc_count_status != ESP_GATT_OK) { + if (desc_count_status != ESP_GATT_OK) { ESP_LOGW(TAG, "[%d] [%s] Error getting descriptor count for char handle %d, status=%d", this->connection_index_, this->address_str().c_str(), char_result.char_handle, desc_count_status); + return; + } + if (total_desc_count == 0) { + // No descriptors, continue to next characteristic + continue; } - // Now process descriptors + // Reserve space and process descriptors + characteristic_resp.descriptors.reserve(total_desc_count); uint16_t desc_offset = 0; esp_gattc_descr_elem_t desc_result; while (true) { // descriptors @@ -166,10 +168,10 @@ void BluetoothConnection::send_service_for_discovery_() { if (desc_status != ESP_GATT_OK) { ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_all_descr error, status=%d", this->connection_index_, this->address_str().c_str(), desc_status); - break; + return; } if (desc_count == 0) { - break; + break; // No more descriptors } characteristic_resp.descriptors.emplace_back(); From eecdaa516315544092d93bbec96207648d7ec359 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Mon, 28 Jul 2025 17:23:35 +1000 Subject: [PATCH 19/47] [config_validation] extend should combine extra validations (#9939) --- esphome/voluptuous_schema.py | 7 +-- .../config_validation/test_config.py | 51 +++++++++++++++++++ 2 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 tests/component_tests/config_validation/test_config.py diff --git a/esphome/voluptuous_schema.py b/esphome/voluptuous_schema.py index 8fb966e3b2..7220fb307f 100644 --- a/esphome/voluptuous_schema.py +++ b/esphome/voluptuous_schema.py @@ -225,9 +225,10 @@ class _Schema(vol.Schema): return ret schema = schemas[0] + extra_schemas = self._extra_schemas.copy() + if isinstance(schema, _Schema): + extra_schemas.extend(schema._extra_schemas) if isinstance(schema, vol.Schema): schema = schema.schema ret = super().extend(schema, extra=extra) - return _Schema( - ret.schema, extra=ret.extra, extra_schemas=self._extra_schemas.copy() - ) + return _Schema(ret.schema, extra=ret.extra, extra_schemas=extra_schemas) diff --git a/tests/component_tests/config_validation/test_config.py b/tests/component_tests/config_validation/test_config.py new file mode 100644 index 0000000000..1a9b9bc1f3 --- /dev/null +++ b/tests/component_tests/config_validation/test_config.py @@ -0,0 +1,51 @@ +""" +Test schema.extend functionality in esphome.config_validation. +""" + +from typing import Any + +import esphome.config_validation as cv + + +def test_config_extend() -> None: + """Test that schema.extend correctly merges schemas with extras.""" + + def func1(data: dict[str, Any]) -> dict[str, Any]: + data["extra_1"] = "value1" + return data + + def func2(data: dict[str, Any]) -> dict[str, Any]: + data["extra_2"] = "value2" + return data + + schema1 = cv.Schema( + { + cv.Required("key1"): cv.string, + } + ) + schema1.add_extra(func1) + schema2 = cv.Schema( + { + cv.Required("key2"): cv.string, + } + ) + schema2.add_extra(func2) + extended_schema = schema1.extend(schema2) + config = { + "key1": "initial_value1", + "key2": "initial_value2", + } + validated = extended_schema(config) + assert validated["key1"] == "initial_value1" + assert validated["key2"] == "initial_value2" + assert validated["extra_1"] == "value1" + assert validated["extra_2"] == "value2" + + # Check the opposite order of extension + extended_schema = schema2.extend(schema1) + + validated = extended_schema(config) + assert validated["key1"] == "initial_value1" + assert validated["key2"] == "initial_value2" + assert validated["extra_1"] == "value1" + assert validated["extra_2"] == "value2" From f9453f96421ab0493fa824d36a3d0342398d7a35 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Tue, 29 Jul 2025 06:43:22 +1000 Subject: [PATCH 20/47] [lvgl] Bugfix for tileview (#9938) --- esphome/components/lvgl/widgets/tileview.py | 22 ++++++++++++++------- tests/components/lvgl/lvgl-package.yaml | 2 +- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/esphome/components/lvgl/widgets/tileview.py b/esphome/components/lvgl/widgets/tileview.py index 3865d404e2..5e3a95f017 100644 --- a/esphome/components/lvgl/widgets/tileview.py +++ b/esphome/components/lvgl/widgets/tileview.py @@ -15,7 +15,7 @@ from ..defines import ( TILE_DIRECTIONS, literal, ) -from ..lv_validation import animated, lv_int +from ..lv_validation import animated, lv_int, lv_pct from ..lvcode import lv, lv_assign, lv_expr, lv_obj, lv_Pvariable from ..schemas import container_schema from ..types import LV_EVENT, LvType, ObjUpdateAction, lv_obj_t, lv_obj_t_ptr @@ -41,8 +41,8 @@ TILEVIEW_SCHEMA = cv.Schema( container_schema( obj_spec, { - cv.Required(CONF_ROW): lv_int, - cv.Required(CONF_COLUMN): lv_int, + cv.Required(CONF_ROW): cv.positive_int, + cv.Required(CONF_COLUMN): cv.positive_int, cv.GenerateID(): cv.declare_id(lv_tile_t), cv.Optional(CONF_DIR, default="ALL"): TILE_DIRECTIONS.several_of, }, @@ -63,21 +63,29 @@ class TileviewType(WidgetType): ) async def to_code(self, w: Widget, config: dict): - for tile_conf in config.get(CONF_TILES, ()): + tiles = config[CONF_TILES] + for tile_conf in tiles: w_id = tile_conf[CONF_ID] tile_obj = lv_Pvariable(lv_obj_t, w_id) tile = Widget.create(w_id, tile_obj, tile_spec, tile_conf) dirs = tile_conf[CONF_DIR] if isinstance(dirs, list): dirs = "|".join(dirs) + row_pos = tile_conf[CONF_ROW] + col_pos = tile_conf[CONF_COLUMN] lv_assign( tile_obj, - lv_expr.tileview_add_tile( - w.obj, tile_conf[CONF_COLUMN], tile_conf[CONF_ROW], literal(dirs) - ), + lv_expr.tileview_add_tile(w.obj, col_pos, row_pos, literal(dirs)), ) + # Bugfix for LVGL 8.x + lv_obj.set_pos(tile_obj, lv_pct(col_pos * 100), lv_pct(row_pos * 100)) await set_obj_properties(tile, tile_conf) await add_widgets(tile, tile_conf) + if tiles: + # Set the first tile as active + lv_obj.set_tile_id( + w.obj, tiles[0][CONF_COLUMN], tiles[0][CONF_ROW], literal("LV_ANIM_OFF") + ) tileview_spec = TileviewType() diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index 46341c266d..853466c9cc 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -738,7 +738,7 @@ lvgl: id: bar_id value: !lambda return (int)((float)rand() / RAND_MAX * 100); start_value: !lambda return (int)((float)rand() / RAND_MAX * 100); - mode: symmetrical + mode: range - logger.log: format: "bar value %f" args: [x] From 85435e6b5f695b37a0efd688c1848373762f191e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 28 Jul 2025 10:54:16 -1000 Subject: [PATCH 21/47] [scheduler] Eliminate more runtime string allocations from retry (#9930) --- esphome/core/scheduler.cpp | 61 ++++++++++++------ esphome/core/scheduler.h | 32 +++++++--- .../fixtures/scheduler_retry_test.yaml | 62 ++++++++++++++++++ .../integration/test_scheduler_retry_test.py | 64 ++++++++++++++++++- 4 files changed, 192 insertions(+), 27 deletions(-) diff --git a/esphome/core/scheduler.cpp b/esphome/core/scheduler.cpp index a2c16c41fb..6269a66543 100644 --- a/esphome/core/scheduler.cpp +++ b/esphome/core/scheduler.cpp @@ -83,6 +83,7 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type item->type = type; item->callback = std::move(func); item->remove = false; + item->is_retry = is_retry; #ifndef ESPHOME_THREAD_SINGLE // Special handling for defer() (delay = 0, type = TIMEOUT) @@ -134,8 +135,8 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type // For retries, check if there's a cancelled timeout first if (is_retry && name_cstr != nullptr && type == SchedulerItem::TIMEOUT && - (has_cancelled_timeout_in_container_(this->items_, component, name_cstr) || - has_cancelled_timeout_in_container_(this->to_add_, component, name_cstr))) { + (has_cancelled_timeout_in_container_(this->items_, component, name_cstr, /* match_retry= */ true) || + has_cancelled_timeout_in_container_(this->to_add_, component, name_cstr, /* match_retry= */ true))) { // Skip scheduling - the retry was cancelled #ifdef ESPHOME_DEBUG_SCHEDULER ESP_LOGD(TAG, "Skipping retry '%s' - found cancelled item", name_cstr); @@ -198,25 +199,27 @@ void retry_handler(const std::shared_ptr &args) { // second execution of `func` happens after `initial_wait_time` args->scheduler->set_timer_common_( args->component, Scheduler::SchedulerItem::TIMEOUT, false, &args->name, args->current_interval, - [args]() { retry_handler(args); }, true); + [args]() { retry_handler(args); }, /* is_retry= */ true); // backoff_increase_factor applied to third & later executions args->current_interval *= args->backoff_increase_factor; } -void HOT Scheduler::set_retry(Component *component, const std::string &name, uint32_t initial_wait_time, - uint8_t max_attempts, std::function func, - float backoff_increase_factor) { - if (!name.empty()) - this->cancel_retry(component, name); +void HOT Scheduler::set_retry_common_(Component *component, bool is_static_string, const void *name_ptr, + uint32_t initial_wait_time, uint8_t max_attempts, + std::function func, float backoff_increase_factor) { + const char *name_cstr = this->get_name_cstr_(is_static_string, name_ptr); + + if (name_cstr != nullptr) + this->cancel_retry(component, name_cstr); if (initial_wait_time == SCHEDULER_DONT_RUN) return; ESP_LOGVV(TAG, "set_retry(name='%s', initial_wait_time=%" PRIu32 ", max_attempts=%u, backoff_factor=%0.1f)", - name.c_str(), initial_wait_time, max_attempts, backoff_increase_factor); + name_cstr ? name_cstr : "", initial_wait_time, max_attempts, backoff_increase_factor); if (backoff_increase_factor < 0.0001) { - ESP_LOGE(TAG, "backoff_factor %0.1f too small, using 1.0: %s", backoff_increase_factor, name.c_str()); + ESP_LOGE(TAG, "backoff_factor %0.1f too small, using 1.0: %s", backoff_increase_factor, name_cstr ? name_cstr : ""); backoff_increase_factor = 1; } @@ -225,15 +228,36 @@ void HOT Scheduler::set_retry(Component *component, const std::string &name, uin args->retry_countdown = max_attempts; args->current_interval = initial_wait_time; args->component = component; - args->name = "retry$" + name; + args->name = name_cstr ? name_cstr : ""; // Convert to std::string for RetryArgs args->backoff_increase_factor = backoff_increase_factor; args->scheduler = this; - // First execution of `func` immediately - this->set_timeout(component, args->name, 0, [args]() { retry_handler(args); }); + // First execution of `func` immediately - use set_timer_common_ with is_retry=true + this->set_timer_common_( + component, SchedulerItem::TIMEOUT, false, &args->name, 0, [args]() { retry_handler(args); }, + /* is_retry= */ true); +} + +void HOT Scheduler::set_retry(Component *component, const std::string &name, uint32_t initial_wait_time, + uint8_t max_attempts, std::function func, + float backoff_increase_factor) { + this->set_retry_common_(component, false, &name, initial_wait_time, max_attempts, std::move(func), + backoff_increase_factor); +} + +void HOT Scheduler::set_retry(Component *component, const char *name, uint32_t initial_wait_time, uint8_t max_attempts, + std::function func, float backoff_increase_factor) { + this->set_retry_common_(component, true, name, initial_wait_time, max_attempts, std::move(func), + backoff_increase_factor); } bool HOT Scheduler::cancel_retry(Component *component, const std::string &name) { - return this->cancel_timeout(component, "retry$" + name); + return this->cancel_retry(component, name.c_str()); +} + +bool HOT Scheduler::cancel_retry(Component *component, const char *name) { + // Cancel timeouts that have is_retry flag set + LockGuard guard{this->lock_}; + return this->cancel_item_locked_(component, name, SchedulerItem::TIMEOUT, /* match_retry= */ true); } optional HOT Scheduler::next_schedule_in(uint32_t now) { @@ -479,7 +503,8 @@ bool HOT Scheduler::cancel_item_(Component *component, bool is_static_string, co } // Helper to cancel items by name - must be called with lock held -bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_cstr, SchedulerItem::Type type) { +bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_cstr, SchedulerItem::Type type, + bool match_retry) { // Early return if name is invalid - no items to cancel if (name_cstr == nullptr) { return false; @@ -492,7 +517,7 @@ bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_c // Only check defer queue for timeouts (intervals never go there) if (type == SchedulerItem::TIMEOUT) { for (auto &item : this->defer_queue_) { - if (this->matches_item_(item, component, name_cstr, type)) { + if (this->matches_item_(item, component, name_cstr, type, match_retry)) { item->remove = true; total_cancelled++; } @@ -502,7 +527,7 @@ bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_c // Cancel items in the main heap for (auto &item : this->items_) { - if (this->matches_item_(item, component, name_cstr, type)) { + if (this->matches_item_(item, component, name_cstr, type, match_retry)) { item->remove = true; total_cancelled++; this->to_remove_++; // Track removals for heap items @@ -511,7 +536,7 @@ bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_c // Cancel items in to_add_ for (auto &item : this->to_add_) { - if (this->matches_item_(item, component, name_cstr, type)) { + if (this->matches_item_(item, component, name_cstr, type, match_retry)) { item->remove = true; total_cancelled++; // Don't track removals for to_add_ items diff --git a/esphome/core/scheduler.h b/esphome/core/scheduler.h index fa189bacf7..a6092e1b1e 100644 --- a/esphome/core/scheduler.h +++ b/esphome/core/scheduler.h @@ -61,7 +61,10 @@ class Scheduler { bool cancel_interval(Component *component, const char *name); void set_retry(Component *component, const std::string &name, uint32_t initial_wait_time, uint8_t max_attempts, std::function func, float backoff_increase_factor = 1.0f); + void set_retry(Component *component, const char *name, uint32_t initial_wait_time, uint8_t max_attempts, + std::function func, float backoff_increase_factor = 1.0f); bool cancel_retry(Component *component, const std::string &name); + bool cancel_retry(Component *component, const char *name); // Calculate when the next scheduled item should run // @param now Fresh timestamp from millis() - must not be stale/cached @@ -98,11 +101,18 @@ class Scheduler { enum Type : uint8_t { TIMEOUT, INTERVAL } type : 1; bool remove : 1; bool name_is_dynamic : 1; // True if name was dynamically allocated (needs delete[]) - // 5 bits padding + bool is_retry : 1; // True if this is a retry timeout + // 4 bits padding // Constructor SchedulerItem() - : component(nullptr), interval(0), next_execution_(0), type(TIMEOUT), remove(false), name_is_dynamic(false) { + : component(nullptr), + interval(0), + next_execution_(0), + type(TIMEOUT), + remove(false), + name_is_dynamic(false), + is_retry(false) { name_.static_name = nullptr; } @@ -156,6 +166,10 @@ class Scheduler { void set_timer_common_(Component *component, SchedulerItem::Type type, bool is_static_string, const void *name_ptr, uint32_t delay, std::function func, bool is_retry = false); + // Common implementation for retry + void set_retry_common_(Component *component, bool is_static_string, const void *name_ptr, uint32_t initial_wait_time, + uint8_t max_attempts, std::function func, float backoff_increase_factor); + uint64_t millis_64_(uint32_t now); // Cleanup logically deleted items from the scheduler // Returns the number of items remaining after cleanup @@ -165,7 +179,7 @@ class Scheduler { private: // Helper to cancel items by name - must be called with lock held - bool cancel_item_locked_(Component *component, const char *name, SchedulerItem::Type type); + bool cancel_item_locked_(Component *component, const char *name, SchedulerItem::Type type, bool match_retry = false); // Helper to extract name as const char* from either static string or std::string inline const char *get_name_cstr_(bool is_static_string, const void *name_ptr) { @@ -177,8 +191,9 @@ class Scheduler { // Helper function to check if item matches criteria for cancellation inline bool HOT matches_item_(const std::unique_ptr &item, Component *component, const char *name_cstr, - SchedulerItem::Type type, bool skip_removed = true) const { - if (item->component != component || item->type != type || (skip_removed && item->remove)) { + SchedulerItem::Type type, bool match_retry, bool skip_removed = true) const { + if (item->component != component || item->type != type || (skip_removed && item->remove) || + (match_retry && !item->is_retry)) { return false; } const char *item_name = item->get_name(); @@ -206,10 +221,11 @@ class Scheduler { // Template helper to check if any item in a container matches our criteria template - bool has_cancelled_timeout_in_container_(const Container &container, Component *component, - const char *name_cstr) const { + bool has_cancelled_timeout_in_container_(const Container &container, Component *component, const char *name_cstr, + bool match_retry) const { for (const auto &item : container) { - if (item->remove && this->matches_item_(item, component, name_cstr, SchedulerItem::TIMEOUT, false)) { + if (item->remove && this->matches_item_(item, component, name_cstr, SchedulerItem::TIMEOUT, match_retry, + /* skip_removed= */ false)) { return true; } } diff --git a/tests/integration/fixtures/scheduler_retry_test.yaml b/tests/integration/fixtures/scheduler_retry_test.yaml index c6fcc53f8c..11fff6c395 100644 --- a/tests/integration/fixtures/scheduler_retry_test.yaml +++ b/tests/integration/fixtures/scheduler_retry_test.yaml @@ -37,6 +37,15 @@ globals: - id: multiple_same_name_counter type: int initial_value: '0' + - id: const_char_retry_counter + type: int + initial_value: '0' + - id: static_char_retry_counter + type: int + initial_value: '0' + - id: mixed_cancel_result + type: bool + initial_value: 'false' # Using different component types for each test to ensure isolation sensor: @@ -229,6 +238,56 @@ script: return RetryResult::RETRY; }); + # Test 8: Const char* overloads + - logger.log: "=== Test 8: Const char* overloads ===" + - lambda: |- + auto *component = id(simple_retry_sensor); + + // Test 8a: Direct string literal + App.scheduler.set_retry(component, "const_char_test", 30, 2, + [](uint8_t retry_countdown) { + id(const_char_retry_counter)++; + ESP_LOGI("test", "Const char retry %d", id(const_char_retry_counter)); + return RetryResult::DONE; + }); + + # Test 9: Static const char* variable + - logger.log: "=== Test 9: Static const char* ===" + - lambda: |- + auto *component = id(backoff_retry_sensor); + + static const char* STATIC_NAME = "static_retry_test"; + App.scheduler.set_retry(component, STATIC_NAME, 20, 1, + [](uint8_t retry_countdown) { + id(static_char_retry_counter)++; + ESP_LOGI("test", "Static const char retry %d", id(static_char_retry_counter)); + return RetryResult::DONE; + }); + + // Cancel with same static const char* + App.scheduler.set_timeout(component, "static_cancel", 10, []() { + static const char* STATIC_NAME = "static_retry_test"; + bool result = App.scheduler.cancel_retry(id(backoff_retry_sensor), STATIC_NAME); + ESP_LOGI("test", "Static cancel result: %s", result ? "true" : "false"); + }); + + # Test 10: Mix string and const char* cancel + - logger.log: "=== Test 10: Mixed string/const char* ===" + - lambda: |- + auto *component = id(immediate_done_sensor); + + // Set with std::string + std::string str_name = "mixed_retry"; + App.scheduler.set_retry(component, str_name, 40, 3, + [](uint8_t retry_countdown) { + ESP_LOGI("test", "Mixed retry - should be cancelled"); + return RetryResult::RETRY; + }); + + // Cancel with const char* + id(mixed_cancel_result) = App.scheduler.cancel_retry(component, "mixed_retry"); + ESP_LOGI("test", "Mixed cancel result: %s", id(mixed_cancel_result) ? "true" : "false"); + # Wait for all tests to complete before reporting - delay: 500ms @@ -242,4 +301,7 @@ script: ESP_LOGI("test", "Empty name retry counter: %d (expected 1-2)", id(empty_name_retry_counter)); ESP_LOGI("test", "Component retry counter: %d (expected 2)", id(script_retry_counter)); ESP_LOGI("test", "Multiple same name counter: %d (expected 20+)", id(multiple_same_name_counter)); + ESP_LOGI("test", "Const char retry counter: %d (expected 1)", id(const_char_retry_counter)); + ESP_LOGI("test", "Static char retry counter: %d (expected 1)", id(static_char_retry_counter)); + ESP_LOGI("test", "Mixed cancel result: %s (expected true)", id(mixed_cancel_result) ? "true" : "false"); ESP_LOGI("test", "All retry tests completed"); diff --git a/tests/integration/test_scheduler_retry_test.py b/tests/integration/test_scheduler_retry_test.py index 1a469fcff1..c04b7197c9 100644 --- a/tests/integration/test_scheduler_retry_test.py +++ b/tests/integration/test_scheduler_retry_test.py @@ -23,6 +23,9 @@ async def test_scheduler_retry_test( empty_name_retry_done = asyncio.Event() component_retry_done = asyncio.Event() multiple_name_done = asyncio.Event() + const_char_done = asyncio.Event() + static_char_done = asyncio.Event() + mixed_cancel_done = asyncio.Event() test_complete = asyncio.Event() # Track retry counts @@ -33,16 +36,20 @@ async def test_scheduler_retry_test( empty_name_retry_count = 0 component_retry_count = 0 multiple_name_count = 0 + const_char_retry_count = 0 + static_char_retry_count = 0 # Track specific test results cancel_result = None empty_cancel_result = None + mixed_cancel_result = None backoff_intervals = [] def on_log_line(line: str) -> None: nonlocal simple_retry_count, backoff_retry_count, immediate_done_count nonlocal cancel_retry_count, empty_name_retry_count, component_retry_count - nonlocal multiple_name_count, cancel_result, empty_cancel_result + nonlocal multiple_name_count, const_char_retry_count, static_char_retry_count + nonlocal cancel_result, empty_cancel_result, mixed_cancel_result # Strip ANSI color codes clean_line = re.sub(r"\x1b\[[0-9;]*m", "", line) @@ -106,6 +113,27 @@ async def test_scheduler_retry_test( if multiple_name_count >= 20: multiple_name_done.set() + # Const char retry test + elif "Const char retry" in clean_line: + if match := re.search(r"Const char retry (\d+)", clean_line): + const_char_retry_count = int(match.group(1)) + const_char_done.set() + + # Static const char retry test + elif "Static const char retry" in clean_line: + if match := re.search(r"Static const char retry (\d+)", clean_line): + static_char_retry_count = int(match.group(1)) + static_char_done.set() + + elif "Static cancel result:" in clean_line: + # This is part of test 9, but we don't track it separately + pass + + # Mixed cancel test + elif "Mixed cancel result:" in clean_line: + mixed_cancel_result = "true" in clean_line + mixed_cancel_done.set() + # Test completion elif "All retry tests completed" in clean_line: test_complete.set() @@ -227,6 +255,40 @@ async def test_scheduler_retry_test( f"Expected multiple name count >= 20 (second retry only), got {multiple_name_count}" ) + # Wait for const char retry test + try: + await asyncio.wait_for(const_char_done.wait(), timeout=1.0) + except TimeoutError: + pytest.fail( + f"Const char retry test did not complete. Count: {const_char_retry_count}" + ) + + assert const_char_retry_count == 1, ( + f"Expected 1 const char retry call, got {const_char_retry_count}" + ) + + # Wait for static char retry test + try: + await asyncio.wait_for(static_char_done.wait(), timeout=1.0) + except TimeoutError: + pytest.fail( + f"Static char retry test did not complete. Count: {static_char_retry_count}" + ) + + assert static_char_retry_count == 1, ( + f"Expected 1 static char retry call, got {static_char_retry_count}" + ) + + # Wait for mixed cancel test + try: + await asyncio.wait_for(mixed_cancel_done.wait(), timeout=1.0) + except TimeoutError: + pytest.fail("Mixed cancel test did not complete") + + assert mixed_cancel_result is True, ( + "Mixed string/const char cancel should have succeeded" + ) + # Wait for test completion try: await asyncio.wait_for(test_complete.wait(), timeout=1.0) From 59d466a6c8f88a470e24dbde4c8e7b797e75e5e9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 28 Jul 2025 10:55:41 -1000 Subject: [PATCH 22/47] [api] Remove unnecessary string copies from optional access (#9897) --- esphome/components/api/api_connection.cpp | 46 ++++++++-------- esphome/components/api/api_connection.h | 4 ++ esphome/components/api/proto.h | 5 +- .../fixtures/host_mode_many_entities.yaml | 53 +++++++++++++++++++ .../test_host_mode_many_entities.py | 21 +++++++- 5 files changed, 104 insertions(+), 25 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index e0d4ec0cc8..d5e9f61427 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -244,21 +244,7 @@ void APIConnection::loop() { #ifdef USE_API_HOMEASSISTANT_STATES if (state_subs_at_ >= 0) { - const auto &subs = this->parent_->get_state_subs(); - if (state_subs_at_ < static_cast(subs.size())) { - auto &it = subs[state_subs_at_]; - SubscribeHomeAssistantStateResponse resp; - resp.set_entity_id(StringRef(it.entity_id)); - // attribute.value() returns temporary - must store it - std::string attribute_value = it.attribute.value(); - resp.set_attribute(StringRef(attribute_value)); - resp.once = it.once; - if (this->send_message(resp, SubscribeHomeAssistantStateResponse::MESSAGE_TYPE)) { - state_subs_at_++; - } - } else { - state_subs_at_ = -1; - } + this->process_state_subscriptions_(); } #endif } @@ -644,17 +630,13 @@ uint16_t APIConnection::try_send_climate_state(EntityBase *entity, APIConnection if (traits.get_supports_fan_modes() && climate->fan_mode.has_value()) resp.fan_mode = static_cast(climate->fan_mode.value()); if (!traits.get_supported_custom_fan_modes().empty() && climate->custom_fan_mode.has_value()) { - // custom_fan_mode.value() returns temporary - must store it - std::string custom_fan_mode = climate->custom_fan_mode.value(); - resp.set_custom_fan_mode(StringRef(custom_fan_mode)); + resp.set_custom_fan_mode(StringRef(climate->custom_fan_mode.value())); } if (traits.get_supports_presets() && climate->preset.has_value()) { resp.preset = static_cast(climate->preset.value()); } if (!traits.get_supported_custom_presets().empty() && climate->custom_preset.has_value()) { - // custom_preset.value() returns temporary - must store it - std::string custom_preset = climate->custom_preset.value(); - resp.set_custom_preset(StringRef(custom_preset)); + resp.set_custom_preset(StringRef(climate->custom_preset.value())); } if (traits.get_supports_swing_modes()) resp.swing_mode = static_cast(climate->swing_mode); @@ -1843,5 +1825,27 @@ uint16_t APIConnection::try_send_ping_request(EntityBase *entity, APIConnection return encode_message_to_buffer(req, PingRequest::MESSAGE_TYPE, conn, remaining_size, is_single); } +#ifdef USE_API_HOMEASSISTANT_STATES +void APIConnection::process_state_subscriptions_() { + const auto &subs = this->parent_->get_state_subs(); + if (this->state_subs_at_ >= static_cast(subs.size())) { + this->state_subs_at_ = -1; + return; + } + + const auto &it = subs[this->state_subs_at_]; + SubscribeHomeAssistantStateResponse resp; + resp.set_entity_id(StringRef(it.entity_id)); + + // Avoid string copy by directly using the optional's value if it exists + resp.set_attribute(it.attribute.has_value() ? StringRef(it.attribute.value()) : StringRef("")); + + resp.once = it.once; + if (this->send_message(resp, SubscribeHomeAssistantStateResponse::MESSAGE_TYPE)) { + this->state_subs_at_++; + } +} +#endif // USE_API_HOMEASSISTANT_STATES + } // namespace esphome::api #endif diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 3c446c431b..f57d37f5a5 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -298,6 +298,10 @@ class APIConnection : public APIServerConnection { // Helper function to handle authentication completion void complete_authentication_(); +#ifdef USE_API_HOMEASSISTANT_STATES + void process_state_subscriptions_(); +#endif + // Non-template helper to encode any ProtoMessage static uint16_t encode_message_to_buffer(ProtoMessage &msg, uint8_t message_type, APIConnection *conn, uint32_t remaining_size, bool is_single); diff --git a/esphome/components/api/proto.h b/esphome/components/api/proto.h index 771eaa98d1..cea93f928f 100644 --- a/esphome/components/api/proto.h +++ b/esphome/components/api/proto.h @@ -35,11 +35,10 @@ namespace esphome::api { * * Unsafe Patterns (WILL cause crashes/corruption): * 1. Temporaries: msg.set_field(StringRef(obj.get_string())) // get_string() returns by value - * 2. Optional values: msg.set_field(StringRef(optional.value())) // value() returns a copy - * 3. Concatenation: msg.set_field(StringRef(str1 + str2)) // Result is temporary + * 2. Concatenation: msg.set_field(StringRef(str1 + str2)) // Result is temporary * * For unsafe patterns, store in a local variable first: - * std::string temp = optional.value(); // or get_string() or str1 + str2 + * std::string temp = get_string(); // or str1 + str2 * msg.set_field(StringRef(temp)); * * The send_*_response pattern ensures proper lifetime management by encoding diff --git a/tests/integration/fixtures/host_mode_many_entities.yaml b/tests/integration/fixtures/host_mode_many_entities.yaml index 3d1aa36196..5e085a15c9 100644 --- a/tests/integration/fixtures/host_mode_many_entities.yaml +++ b/tests/integration/fixtures/host_mode_many_entities.yaml @@ -210,6 +210,15 @@ sensor: name: "Test Sensor 50" lambda: return 50.0; update_interval: 0.1s + # Temperature sensor for the thermostat + - platform: template + name: "Temperature Sensor" + id: temp_sensor + lambda: return 22.5; + unit_of_measurement: "°C" + device_class: temperature + state_class: measurement + update_interval: 5s # Mixed entity types for comprehensive batching test binary_sensor: @@ -285,6 +294,50 @@ valve: stop_action: - logger.log: "Valve stopping" +output: + - platform: template + id: heater_output + type: binary + write_action: + - logger.log: "Heater output changed" + - platform: template + id: cooler_output + type: binary + write_action: + - logger.log: "Cooler output changed" + +climate: + - platform: thermostat + name: "Test Thermostat" + sensor: temp_sensor + default_preset: Home + on_boot_restore_from: default_preset + min_heating_off_time: 1s + min_heating_run_time: 1s + min_cooling_off_time: 1s + min_cooling_run_time: 1s + min_idle_time: 1s + heat_action: + - output.turn_on: heater_output + cool_action: + - output.turn_on: cooler_output + idle_action: + - output.turn_off: heater_output + - output.turn_off: cooler_output + preset: + - name: Home + default_target_temperature_low: 20 + default_target_temperature_high: 24 + mode: heat_cool + - name: Away + default_target_temperature_low: 16 + default_target_temperature_high: 26 + mode: heat_cool + - name: Sleep + default_target_temperature_low: 18 + default_target_temperature_high: 22 + mode: heat_cool + alarm_control_panel: - platform: template name: "Test Alarm" diff --git a/tests/integration/test_host_mode_many_entities.py b/tests/integration/test_host_mode_many_entities.py index ce9e157a88..aaca4555f6 100644 --- a/tests/integration/test_host_mode_many_entities.py +++ b/tests/integration/test_host_mode_many_entities.py @@ -4,7 +4,7 @@ from __future__ import annotations import asyncio -from aioesphomeapi import EntityState, SensorState +from aioesphomeapi import ClimateInfo, EntityState, SensorState import pytest from .types import APIClientConnectedFactory, RunCompiledFunction @@ -70,3 +70,22 @@ async def test_host_mode_many_entities( assert len(sensor_states) >= 50, ( f"Expected at least 50 sensor states, got {len(sensor_states)}" ) + + # Get entity info to verify climate entity details + entities = await client.list_entities_services() + climate_infos = [e for e in entities[0] if isinstance(e, ClimateInfo)] + assert len(climate_infos) >= 1, "Expected at least 1 climate entity" + + climate_info = climate_infos[0] + # Verify the thermostat has presets + assert len(climate_info.supported_presets) > 0, ( + "Expected climate to have presets" + ) + # The thermostat platform uses standard presets (Home, Away, Sleep) + # which should be transmitted properly without string copies + + # Verify specific presets exist + preset_names = [p.name for p in climate_info.supported_presets] + assert "HOME" in preset_names, f"Expected 'HOME' preset, got {preset_names}" + assert "AWAY" in preset_names, f"Expected 'AWAY' preset, got {preset_names}" + assert "SLEEP" in preset_names, f"Expected 'SLEEP' preset, got {preset_names}" From 08defd7360e21ccb2acc6a3d46b061e3c67ca017 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Jul 2025 11:02:53 -1000 Subject: [PATCH 23/47] Bump aioesphomeapi from 37.1.2 to 37.1.3 (#9943) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 80bd470f02..9889e83910 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile esptool==4.9.0 click==8.1.7 esphome-dashboard==20250514.0 -aioesphomeapi==37.1.2 +aioesphomeapi==37.1.3 zeroconf==0.147.0 puremagic==1.30 ruamel.yaml==0.18.14 # dashboard_import From 189d20a822e782495c572ea0bf47dd32b322b799 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 29 Jul 2025 09:21:53 +1200 Subject: [PATCH 24/47] [heatpumpir] Bump library to 1.0.37 (#9944) --- .clang-tidy.hash | 2 +- esphome/components/heatpumpir/climate.py | 2 +- platformio.ini | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.clang-tidy.hash b/.clang-tidy.hash index 316f43e706..02b528b0d6 100644 --- a/.clang-tidy.hash +++ b/.clang-tidy.hash @@ -1 +1 @@ -32b0db73b3ae01ba18c9cbb1dabbd8156bc14dded500471919bd0a3dc33916e0 +b7056e39f1484500ca2d237068670b789fe9241786b48da0681d646b25af05d5 diff --git a/esphome/components/heatpumpir/climate.py b/esphome/components/heatpumpir/climate.py index 0f9f146ae9..d2b907d437 100644 --- a/esphome/components/heatpumpir/climate.py +++ b/esphome/components/heatpumpir/climate.py @@ -126,6 +126,6 @@ async def to_code(config): cg.add(var.set_max_temperature(config[CONF_MAX_TEMPERATURE])) cg.add(var.set_min_temperature(config[CONF_MIN_TEMPERATURE])) - cg.add_library("tonia/HeatpumpIR", "1.0.35") + cg.add_library("tonia/HeatpumpIR", "1.0.37") if CORE.is_libretiny: CORE.add_platformio_option("lib_ignore", "IRremoteESP8266") diff --git a/platformio.ini b/platformio.ini index bf0754ead3..5956ae8117 100644 --- a/platformio.ini +++ b/platformio.ini @@ -78,7 +78,7 @@ lib_deps = glmnet/Dsmr@0.7 ; dsmr rweather/Crypto@0.4.0 ; dsmr dudanov/MideaUART@1.1.9 ; midea - tonia/HeatpumpIR@1.0.35 ; heatpumpir + tonia/HeatpumpIR@1.0.37 ; heatpumpir build_flags = ${common.build_flags} -DUSE_ARDUINO From 68f388f78e14d9374156caa37b858fba2cbf90c7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 28 Jul 2025 12:25:07 -1000 Subject: [PATCH 25/47] [api] Optimize protobuf empty message handling to reduce flash and runtime overhead (#9908) --- esphome/components/api/api_pb2.h | 26 +++++++++++----------- esphome/components/api/api_pb2_service.cpp | 26 +++++++++++----------- script/api_protobuf/api_protobuf.py | 18 ++++++++++++--- 3 files changed, 41 insertions(+), 29 deletions(-) diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index fb8174f988..47bdaead23 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -378,7 +378,7 @@ class ConnectResponse : public ProtoMessage { protected: }; -class DisconnectRequest : public ProtoDecodableMessage { +class DisconnectRequest : public ProtoMessage { public: static constexpr uint8_t MESSAGE_TYPE = 5; static constexpr uint8_t ESTIMATED_SIZE = 0; @@ -391,7 +391,7 @@ class DisconnectRequest : public ProtoDecodableMessage { protected: }; -class DisconnectResponse : public ProtoDecodableMessage { +class DisconnectResponse : public ProtoMessage { public: static constexpr uint8_t MESSAGE_TYPE = 6; static constexpr uint8_t ESTIMATED_SIZE = 0; @@ -404,7 +404,7 @@ class DisconnectResponse : public ProtoDecodableMessage { protected: }; -class PingRequest : public ProtoDecodableMessage { +class PingRequest : public ProtoMessage { public: static constexpr uint8_t MESSAGE_TYPE = 7; static constexpr uint8_t ESTIMATED_SIZE = 0; @@ -417,7 +417,7 @@ class PingRequest : public ProtoDecodableMessage { protected: }; -class PingResponse : public ProtoDecodableMessage { +class PingResponse : public ProtoMessage { public: static constexpr uint8_t MESSAGE_TYPE = 8; static constexpr uint8_t ESTIMATED_SIZE = 0; @@ -430,7 +430,7 @@ class PingResponse : public ProtoDecodableMessage { protected: }; -class DeviceInfoRequest : public ProtoDecodableMessage { +class DeviceInfoRequest : public ProtoMessage { public: static constexpr uint8_t MESSAGE_TYPE = 9; static constexpr uint8_t ESTIMATED_SIZE = 0; @@ -546,7 +546,7 @@ class DeviceInfoResponse : public ProtoMessage { protected: }; -class ListEntitiesRequest : public ProtoDecodableMessage { +class ListEntitiesRequest : public ProtoMessage { public: static constexpr uint8_t MESSAGE_TYPE = 11; static constexpr uint8_t ESTIMATED_SIZE = 0; @@ -572,7 +572,7 @@ class ListEntitiesDoneResponse : public ProtoMessage { protected: }; -class SubscribeStatesRequest : public ProtoDecodableMessage { +class SubscribeStatesRequest : public ProtoMessage { public: static constexpr uint8_t MESSAGE_TYPE = 20; static constexpr uint8_t ESTIMATED_SIZE = 0; @@ -1045,7 +1045,7 @@ class NoiseEncryptionSetKeyResponse : public ProtoMessage { }; #endif #ifdef USE_API_HOMEASSISTANT_SERVICES -class SubscribeHomeassistantServicesRequest : public ProtoDecodableMessage { +class SubscribeHomeassistantServicesRequest : public ProtoMessage { public: static constexpr uint8_t MESSAGE_TYPE = 34; static constexpr uint8_t ESTIMATED_SIZE = 0; @@ -1094,7 +1094,7 @@ class HomeassistantServiceResponse : public ProtoMessage { }; #endif #ifdef USE_API_HOMEASSISTANT_STATES -class SubscribeHomeAssistantStatesRequest : public ProtoDecodableMessage { +class SubscribeHomeAssistantStatesRequest : public ProtoMessage { public: static constexpr uint8_t MESSAGE_TYPE = 38; static constexpr uint8_t ESTIMATED_SIZE = 0; @@ -1145,7 +1145,7 @@ class HomeAssistantStateResponse : public ProtoDecodableMessage { bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; }; #endif -class GetTimeRequest : public ProtoDecodableMessage { +class GetTimeRequest : public ProtoMessage { public: static constexpr uint8_t MESSAGE_TYPE = 36; static constexpr uint8_t ESTIMATED_SIZE = 0; @@ -2043,7 +2043,7 @@ class BluetoothGATTNotifyDataResponse : public ProtoMessage { protected: }; -class SubscribeBluetoothConnectionsFreeRequest : public ProtoDecodableMessage { +class SubscribeBluetoothConnectionsFreeRequest : public ProtoMessage { public: static constexpr uint8_t MESSAGE_TYPE = 80; static constexpr uint8_t ESTIMATED_SIZE = 0; @@ -2162,7 +2162,7 @@ class BluetoothDeviceUnpairingResponse : public ProtoMessage { protected: }; -class UnsubscribeBluetoothLEAdvertisementsRequest : public ProtoDecodableMessage { +class UnsubscribeBluetoothLEAdvertisementsRequest : public ProtoMessage { public: static constexpr uint8_t MESSAGE_TYPE = 87; static constexpr uint8_t ESTIMATED_SIZE = 0; @@ -2418,7 +2418,7 @@ class VoiceAssistantWakeWord : public ProtoMessage { protected: }; -class VoiceAssistantConfigurationRequest : public ProtoDecodableMessage { +class VoiceAssistantConfigurationRequest : public ProtoMessage { public: static constexpr uint8_t MESSAGE_TYPE = 121; static constexpr uint8_t ESTIMATED_SIZE = 0; diff --git a/esphome/components/api/api_pb2_service.cpp b/esphome/components/api/api_pb2_service.cpp index 26d5b12d00..6b7b8b9ebd 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -35,7 +35,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, } case DisconnectRequest::MESSAGE_TYPE: { DisconnectRequest msg; - msg.decode(msg_data, msg_size); + // Empty message: no decode needed #ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "on_disconnect_request: %s", msg.dump().c_str()); #endif @@ -44,7 +44,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, } case DisconnectResponse::MESSAGE_TYPE: { DisconnectResponse msg; - msg.decode(msg_data, msg_size); + // Empty message: no decode needed #ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "on_disconnect_response: %s", msg.dump().c_str()); #endif @@ -53,7 +53,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, } case PingRequest::MESSAGE_TYPE: { PingRequest msg; - msg.decode(msg_data, msg_size); + // Empty message: no decode needed #ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "on_ping_request: %s", msg.dump().c_str()); #endif @@ -62,7 +62,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, } case PingResponse::MESSAGE_TYPE: { PingResponse msg; - msg.decode(msg_data, msg_size); + // Empty message: no decode needed #ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "on_ping_response: %s", msg.dump().c_str()); #endif @@ -71,7 +71,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, } case DeviceInfoRequest::MESSAGE_TYPE: { DeviceInfoRequest msg; - msg.decode(msg_data, msg_size); + // Empty message: no decode needed #ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "on_device_info_request: %s", msg.dump().c_str()); #endif @@ -80,7 +80,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, } case ListEntitiesRequest::MESSAGE_TYPE: { ListEntitiesRequest msg; - msg.decode(msg_data, msg_size); + // Empty message: no decode needed #ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "on_list_entities_request: %s", msg.dump().c_str()); #endif @@ -89,7 +89,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, } case SubscribeStatesRequest::MESSAGE_TYPE: { SubscribeStatesRequest msg; - msg.decode(msg_data, msg_size); + // Empty message: no decode needed #ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "on_subscribe_states_request: %s", msg.dump().c_str()); #endif @@ -152,7 +152,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, #ifdef USE_API_HOMEASSISTANT_SERVICES case SubscribeHomeassistantServicesRequest::MESSAGE_TYPE: { SubscribeHomeassistantServicesRequest msg; - msg.decode(msg_data, msg_size); + // Empty message: no decode needed #ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "on_subscribe_homeassistant_services_request: %s", msg.dump().c_str()); #endif @@ -162,7 +162,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, #endif case GetTimeRequest::MESSAGE_TYPE: { GetTimeRequest msg; - msg.decode(msg_data, msg_size); + // Empty message: no decode needed #ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "on_get_time_request: %s", msg.dump().c_str()); #endif @@ -181,7 +181,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, #ifdef USE_API_HOMEASSISTANT_STATES case SubscribeHomeAssistantStatesRequest::MESSAGE_TYPE: { SubscribeHomeAssistantStatesRequest msg; - msg.decode(msg_data, msg_size); + // Empty message: no decode needed #ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "on_subscribe_home_assistant_states_request: %s", msg.dump().c_str()); #endif @@ -390,7 +390,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, #ifdef USE_BLUETOOTH_PROXY case SubscribeBluetoothConnectionsFreeRequest::MESSAGE_TYPE: { SubscribeBluetoothConnectionsFreeRequest msg; - msg.decode(msg_data, msg_size); + // Empty message: no decode needed #ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "on_subscribe_bluetooth_connections_free_request: %s", msg.dump().c_str()); #endif @@ -401,7 +401,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, #ifdef USE_BLUETOOTH_PROXY case UnsubscribeBluetoothLEAdvertisementsRequest::MESSAGE_TYPE: { UnsubscribeBluetoothLEAdvertisementsRequest msg; - msg.decode(msg_data, msg_size); + // Empty message: no decode needed #ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "on_unsubscribe_bluetooth_le_advertisements_request: %s", msg.dump().c_str()); #endif @@ -555,7 +555,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, #ifdef USE_VOICE_ASSISTANT case VoiceAssistantConfigurationRequest::MESSAGE_TYPE: { VoiceAssistantConfigurationRequest msg; - msg.decode(msg_data, msg_size); + // Empty message: no decode needed #ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "on_voice_assistant_configuration_request: %s", msg.dump().c_str()); #endif diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index 4b9a61383d..d346b66aef 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -1772,8 +1772,13 @@ def build_message_type( if base_class: out = f"class {desc.name} : public {base_class} {{\n" else: - # Determine inheritance based on whether the message needs decoding - base_class = "ProtoDecodableMessage" if needs_decode else "ProtoMessage" + # Check if message has any non-deprecated fields + has_fields = any(not field.options.deprecated for field in desc.field) + # Determine inheritance based on whether the message needs decoding and has fields + if needs_decode and has_fields: + base_class = "ProtoDecodableMessage" + else: + base_class = "ProtoMessage" out = f"class {desc.name} : public {base_class} {{\n" out += " public:\n" out += indent("\n".join(public_content)) + "\n" @@ -2039,7 +2044,14 @@ def build_service_message_type( hout += f"virtual void {func}(const {mt.name} &value){{}};\n" case = "" case += f"{mt.name} msg;\n" - case += "msg.decode(msg_data, msg_size);\n" + # Check if this message has any fields (excluding deprecated ones) + has_fields = any(not field.options.deprecated for field in mt.field) + if has_fields: + # Normal case: decode the message + case += "msg.decode(msg_data, msg_size);\n" + else: + # Empty message optimization: skip decode since there are no fields + case += "// Empty message: no decode needed\n" if log: case += "#ifdef HAS_PROTO_MESSAGE_DUMP\n" case += f'ESP_LOGVV(TAG, "{func}: %s", msg.dump().c_str());\n' From 2c9987869e6533dc3f47936368b3dcac822d82fd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 28 Jul 2025 12:28:32 -1000 Subject: [PATCH 26/47] [api] Align ProtoSize API design with ProtoWriteBuffer pattern (#9920) --- esphome/components/api/api_connection.cpp | 5 +- esphome/components/api/api_pb2.cpp | 1118 ++++++++++----------- esphome/components/api/api_pb2.h | 166 +-- esphome/components/api/proto.h | 293 +++--- script/api_protobuf/api_protobuf.py | 78 +- 5 files changed, 795 insertions(+), 865 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index d5e9f61427..cd27087fe8 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -276,8 +276,9 @@ uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint8_t mess #endif // Calculate size - uint32_t calculated_size = 0; - msg.calculate_size(calculated_size); + ProtoSize size_calc; + msg.calculate_size(size_calc); + uint32_t calculated_size = size_calc.get_size(); // Cache frame sizes to avoid repeated virtual calls const uint8_t header_padding = conn->helper_->frame_header_padding(); diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index b587ee5f03..f6f39f901f 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -36,11 +36,11 @@ void HelloResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(3, this->server_info_ref_); buffer.encode_string(4, this->name_ref_); } -void HelloResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_uint32_field(total_size, 1, this->api_version_major); - ProtoSize::add_uint32_field(total_size, 1, this->api_version_minor); - ProtoSize::add_string_field(total_size, 1, this->server_info_ref_.size()); - ProtoSize::add_string_field(total_size, 1, this->name_ref_.size()); +void HelloResponse::calculate_size(ProtoSize &size) const { + size.add_uint32(1, this->api_version_major); + size.add_uint32(1, this->api_version_minor); + size.add_length(1, this->server_info_ref_.size()); + size.add_length(1, this->name_ref_.size()); } bool ConnectRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { @@ -53,17 +53,15 @@ bool ConnectRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value return true; } void ConnectResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->invalid_password); } -void ConnectResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_bool_field(total_size, 1, this->invalid_password); -} +void ConnectResponse::calculate_size(ProtoSize &size) const { size.add_bool(1, this->invalid_password); } #ifdef USE_AREAS void AreaInfo::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(1, this->area_id); buffer.encode_string(2, this->name_ref_); } -void AreaInfo::calculate_size(uint32_t &total_size) const { - ProtoSize::add_uint32_field(total_size, 1, this->area_id); - ProtoSize::add_string_field(total_size, 1, this->name_ref_.size()); +void AreaInfo::calculate_size(ProtoSize &size) const { + size.add_uint32(1, this->area_id); + size.add_length(1, this->name_ref_.size()); } #endif #ifdef USE_DEVICES @@ -72,10 +70,10 @@ void DeviceInfo::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(2, this->name_ref_); buffer.encode_uint32(3, this->area_id); } -void DeviceInfo::calculate_size(uint32_t &total_size) const { - ProtoSize::add_uint32_field(total_size, 1, this->device_id); - ProtoSize::add_string_field(total_size, 1, this->name_ref_.size()); - ProtoSize::add_uint32_field(total_size, 1, this->area_id); +void DeviceInfo::calculate_size(ProtoSize &size) const { + size.add_uint32(1, this->device_id); + size.add_length(1, this->name_ref_.size()); + size.add_uint32(1, this->area_id); } #endif void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const { @@ -130,52 +128,52 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_message(22, this->area); #endif } -void DeviceInfoResponse::calculate_size(uint32_t &total_size) const { +void DeviceInfoResponse::calculate_size(ProtoSize &size) const { #ifdef USE_API_PASSWORD - ProtoSize::add_bool_field(total_size, 1, this->uses_password); + size.add_bool(1, this->uses_password); #endif - ProtoSize::add_string_field(total_size, 1, this->name_ref_.size()); - ProtoSize::add_string_field(total_size, 1, this->mac_address_ref_.size()); - ProtoSize::add_string_field(total_size, 1, this->esphome_version_ref_.size()); - ProtoSize::add_string_field(total_size, 1, this->compilation_time_ref_.size()); - ProtoSize::add_string_field(total_size, 1, this->model_ref_.size()); + size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->mac_address_ref_.size()); + size.add_length(1, this->esphome_version_ref_.size()); + size.add_length(1, this->compilation_time_ref_.size()); + size.add_length(1, this->model_ref_.size()); #ifdef USE_DEEP_SLEEP - ProtoSize::add_bool_field(total_size, 1, this->has_deep_sleep); + size.add_bool(1, this->has_deep_sleep); #endif #ifdef ESPHOME_PROJECT_NAME - ProtoSize::add_string_field(total_size, 1, this->project_name_ref_.size()); + size.add_length(1, this->project_name_ref_.size()); #endif #ifdef ESPHOME_PROJECT_NAME - ProtoSize::add_string_field(total_size, 1, this->project_version_ref_.size()); + size.add_length(1, this->project_version_ref_.size()); #endif #ifdef USE_WEBSERVER - ProtoSize::add_uint32_field(total_size, 1, this->webserver_port); + size.add_uint32(1, this->webserver_port); #endif #ifdef USE_BLUETOOTH_PROXY - ProtoSize::add_uint32_field(total_size, 1, this->bluetooth_proxy_feature_flags); + size.add_uint32(1, this->bluetooth_proxy_feature_flags); #endif - ProtoSize::add_string_field(total_size, 1, this->manufacturer_ref_.size()); - ProtoSize::add_string_field(total_size, 1, this->friendly_name_ref_.size()); + size.add_length(1, this->manufacturer_ref_.size()); + size.add_length(1, this->friendly_name_ref_.size()); #ifdef USE_VOICE_ASSISTANT - ProtoSize::add_uint32_field(total_size, 2, this->voice_assistant_feature_flags); + size.add_uint32(2, this->voice_assistant_feature_flags); #endif #ifdef USE_AREAS - ProtoSize::add_string_field(total_size, 2, this->suggested_area_ref_.size()); + size.add_length(2, this->suggested_area_ref_.size()); #endif #ifdef USE_BLUETOOTH_PROXY - ProtoSize::add_string_field(total_size, 2, this->bluetooth_mac_address_ref_.size()); + size.add_length(2, this->bluetooth_mac_address_ref_.size()); #endif #ifdef USE_API_NOISE - ProtoSize::add_bool_field(total_size, 2, this->api_encryption_supported); + size.add_bool(2, this->api_encryption_supported); #endif #ifdef USE_DEVICES - ProtoSize::add_repeated_message(total_size, 2, this->devices); + size.add_repeated_message(2, this->devices); #endif #ifdef USE_AREAS - ProtoSize::add_repeated_message(total_size, 2, this->areas); + size.add_repeated_message(2, this->areas); #endif #ifdef USE_AREAS - ProtoSize::add_message_object(total_size, 2, this->area); + size.add_message_object(2, this->area); #endif } #ifdef USE_BINARY_SENSOR @@ -194,19 +192,19 @@ void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(10, this->device_id); #endif } -void ListEntitiesBinarySensorResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->object_id_ref_.size()); - ProtoSize::add_fixed32_field(total_size, 1, this->key); - ProtoSize::add_string_field(total_size, 1, this->name_ref_.size()); - ProtoSize::add_string_field(total_size, 1, this->device_class_ref_.size()); - ProtoSize::add_bool_field(total_size, 1, this->is_status_binary_sensor); - ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default); +void ListEntitiesBinarySensorResponse::calculate_size(ProtoSize &size) const { + size.add_length(1, this->object_id_ref_.size()); + size.add_fixed32(1, this->key); + size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->device_class_ref_.size()); + size.add_bool(1, this->is_status_binary_sensor); + size.add_bool(1, this->disabled_by_default); #ifdef USE_ENTITY_ICON - ProtoSize::add_string_field(total_size, 1, this->icon_ref_.size()); + size.add_length(1, this->icon_ref_.size()); #endif - ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category)); + size.add_uint32(1, static_cast(this->entity_category)); #ifdef USE_DEVICES - ProtoSize::add_uint32_field(total_size, 1, this->device_id); + size.add_uint32(1, this->device_id); #endif } void BinarySensorStateResponse::encode(ProtoWriteBuffer buffer) const { @@ -217,12 +215,12 @@ void BinarySensorStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(4, this->device_id); #endif } -void BinarySensorStateResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_fixed32_field(total_size, 1, this->key); - ProtoSize::add_bool_field(total_size, 1, this->state); - ProtoSize::add_bool_field(total_size, 1, this->missing_state); +void BinarySensorStateResponse::calculate_size(ProtoSize &size) const { + size.add_fixed32(1, this->key); + size.add_bool(1, this->state); + size.add_bool(1, this->missing_state); #ifdef USE_DEVICES - ProtoSize::add_uint32_field(total_size, 1, this->device_id); + size.add_uint32(1, this->device_id); #endif } #endif @@ -245,22 +243,22 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(13, this->device_id); #endif } -void ListEntitiesCoverResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->object_id_ref_.size()); - ProtoSize::add_fixed32_field(total_size, 1, this->key); - ProtoSize::add_string_field(total_size, 1, this->name_ref_.size()); - ProtoSize::add_bool_field(total_size, 1, this->assumed_state); - ProtoSize::add_bool_field(total_size, 1, this->supports_position); - ProtoSize::add_bool_field(total_size, 1, this->supports_tilt); - ProtoSize::add_string_field(total_size, 1, this->device_class_ref_.size()); - ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default); +void ListEntitiesCoverResponse::calculate_size(ProtoSize &size) const { + size.add_length(1, this->object_id_ref_.size()); + size.add_fixed32(1, this->key); + size.add_length(1, this->name_ref_.size()); + size.add_bool(1, this->assumed_state); + size.add_bool(1, this->supports_position); + size.add_bool(1, this->supports_tilt); + size.add_length(1, this->device_class_ref_.size()); + size.add_bool(1, this->disabled_by_default); #ifdef USE_ENTITY_ICON - ProtoSize::add_string_field(total_size, 1, this->icon_ref_.size()); + size.add_length(1, this->icon_ref_.size()); #endif - ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category)); - ProtoSize::add_bool_field(total_size, 1, this->supports_stop); + size.add_uint32(1, static_cast(this->entity_category)); + size.add_bool(1, this->supports_stop); #ifdef USE_DEVICES - ProtoSize::add_uint32_field(total_size, 1, this->device_id); + size.add_uint32(1, this->device_id); #endif } void CoverStateResponse::encode(ProtoWriteBuffer buffer) const { @@ -272,13 +270,13 @@ void CoverStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(6, this->device_id); #endif } -void CoverStateResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_fixed32_field(total_size, 1, this->key); - ProtoSize::add_float_field(total_size, 1, this->position); - ProtoSize::add_float_field(total_size, 1, this->tilt); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->current_operation)); +void CoverStateResponse::calculate_size(ProtoSize &size) const { + size.add_fixed32(1, this->key); + size.add_float(1, this->position); + size.add_float(1, this->tilt); + size.add_uint32(1, static_cast(this->current_operation)); #ifdef USE_DEVICES - ProtoSize::add_uint32_field(total_size, 1, this->device_id); + size.add_uint32(1, this->device_id); #endif } bool CoverCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { @@ -340,26 +338,26 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(13, this->device_id); #endif } -void ListEntitiesFanResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->object_id_ref_.size()); - ProtoSize::add_fixed32_field(total_size, 1, this->key); - ProtoSize::add_string_field(total_size, 1, this->name_ref_.size()); - ProtoSize::add_bool_field(total_size, 1, this->supports_oscillation); - ProtoSize::add_bool_field(total_size, 1, this->supports_speed); - ProtoSize::add_bool_field(total_size, 1, this->supports_direction); - ProtoSize::add_int32_field(total_size, 1, this->supported_speed_count); - ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default); +void ListEntitiesFanResponse::calculate_size(ProtoSize &size) const { + size.add_length(1, this->object_id_ref_.size()); + size.add_fixed32(1, this->key); + size.add_length(1, this->name_ref_.size()); + size.add_bool(1, this->supports_oscillation); + size.add_bool(1, this->supports_speed); + size.add_bool(1, this->supports_direction); + size.add_int32(1, this->supported_speed_count); + size.add_bool(1, this->disabled_by_default); #ifdef USE_ENTITY_ICON - ProtoSize::add_string_field(total_size, 1, this->icon_ref_.size()); + size.add_length(1, this->icon_ref_.size()); #endif - ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category)); + size.add_uint32(1, static_cast(this->entity_category)); if (!this->supported_preset_modes.empty()) { for (const auto &it : this->supported_preset_modes) { - ProtoSize::add_string_field_repeated(total_size, 1, it); + size.add_length_force(1, it.size()); } } #ifdef USE_DEVICES - ProtoSize::add_uint32_field(total_size, 1, this->device_id); + size.add_uint32(1, this->device_id); #endif } void FanStateResponse::encode(ProtoWriteBuffer buffer) const { @@ -373,15 +371,15 @@ void FanStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(8, this->device_id); #endif } -void FanStateResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_fixed32_field(total_size, 1, this->key); - ProtoSize::add_bool_field(total_size, 1, this->state); - ProtoSize::add_bool_field(total_size, 1, this->oscillating); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->direction)); - ProtoSize::add_int32_field(total_size, 1, this->speed_level); - ProtoSize::add_string_field(total_size, 1, this->preset_mode_ref_.size()); +void FanStateResponse::calculate_size(ProtoSize &size) const { + size.add_fixed32(1, this->key); + size.add_bool(1, this->state); + size.add_bool(1, this->oscillating); + size.add_uint32(1, static_cast(this->direction)); + size.add_int32(1, this->speed_level); + size.add_length(1, this->preset_mode_ref_.size()); #ifdef USE_DEVICES - ProtoSize::add_uint32_field(total_size, 1, this->device_id); + size.add_uint32(1, this->device_id); #endif } bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { @@ -466,29 +464,29 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(16, this->device_id); #endif } -void ListEntitiesLightResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->object_id_ref_.size()); - ProtoSize::add_fixed32_field(total_size, 1, this->key); - ProtoSize::add_string_field(total_size, 1, this->name_ref_.size()); +void ListEntitiesLightResponse::calculate_size(ProtoSize &size) const { + size.add_length(1, this->object_id_ref_.size()); + size.add_fixed32(1, this->key); + size.add_length(1, this->name_ref_.size()); if (!this->supported_color_modes.empty()) { for (const auto &it : this->supported_color_modes) { - ProtoSize::add_enum_field_repeated(total_size, 1, static_cast(it)); + size.add_uint32_force(1, static_cast(it)); } } - ProtoSize::add_float_field(total_size, 1, this->min_mireds); - ProtoSize::add_float_field(total_size, 1, this->max_mireds); + size.add_float(1, this->min_mireds); + size.add_float(1, this->max_mireds); if (!this->effects.empty()) { for (const auto &it : this->effects) { - ProtoSize::add_string_field_repeated(total_size, 1, it); + size.add_length_force(1, it.size()); } } - ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default); + size.add_bool(1, this->disabled_by_default); #ifdef USE_ENTITY_ICON - ProtoSize::add_string_field(total_size, 1, this->icon_ref_.size()); + size.add_length(1, this->icon_ref_.size()); #endif - ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category)); + size.add_uint32(1, static_cast(this->entity_category)); #ifdef USE_DEVICES - ProtoSize::add_uint32_field(total_size, 2, this->device_id); + size.add_uint32(2, this->device_id); #endif } void LightStateResponse::encode(ProtoWriteBuffer buffer) const { @@ -509,22 +507,22 @@ void LightStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(14, this->device_id); #endif } -void LightStateResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_fixed32_field(total_size, 1, this->key); - ProtoSize::add_bool_field(total_size, 1, this->state); - ProtoSize::add_float_field(total_size, 1, this->brightness); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->color_mode)); - ProtoSize::add_float_field(total_size, 1, this->color_brightness); - ProtoSize::add_float_field(total_size, 1, this->red); - ProtoSize::add_float_field(total_size, 1, this->green); - ProtoSize::add_float_field(total_size, 1, this->blue); - ProtoSize::add_float_field(total_size, 1, this->white); - ProtoSize::add_float_field(total_size, 1, this->color_temperature); - ProtoSize::add_float_field(total_size, 1, this->cold_white); - ProtoSize::add_float_field(total_size, 1, this->warm_white); - ProtoSize::add_string_field(total_size, 1, this->effect_ref_.size()); +void LightStateResponse::calculate_size(ProtoSize &size) const { + size.add_fixed32(1, this->key); + size.add_bool(1, this->state); + size.add_float(1, this->brightness); + size.add_uint32(1, static_cast(this->color_mode)); + size.add_float(1, this->color_brightness); + size.add_float(1, this->red); + size.add_float(1, this->green); + size.add_float(1, this->blue); + size.add_float(1, this->white); + size.add_float(1, this->color_temperature); + size.add_float(1, this->cold_white); + size.add_float(1, this->warm_white); + size.add_length(1, this->effect_ref_.size()); #ifdef USE_DEVICES - ProtoSize::add_uint32_field(total_size, 1, this->device_id); + size.add_uint32(1, this->device_id); #endif } bool LightCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { @@ -654,22 +652,22 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(14, this->device_id); #endif } -void ListEntitiesSensorResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->object_id_ref_.size()); - ProtoSize::add_fixed32_field(total_size, 1, this->key); - ProtoSize::add_string_field(total_size, 1, this->name_ref_.size()); +void ListEntitiesSensorResponse::calculate_size(ProtoSize &size) const { + size.add_length(1, this->object_id_ref_.size()); + size.add_fixed32(1, this->key); + size.add_length(1, this->name_ref_.size()); #ifdef USE_ENTITY_ICON - ProtoSize::add_string_field(total_size, 1, this->icon_ref_.size()); + size.add_length(1, this->icon_ref_.size()); #endif - ProtoSize::add_string_field(total_size, 1, this->unit_of_measurement_ref_.size()); - ProtoSize::add_int32_field(total_size, 1, this->accuracy_decimals); - ProtoSize::add_bool_field(total_size, 1, this->force_update); - ProtoSize::add_string_field(total_size, 1, this->device_class_ref_.size()); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->state_class)); - ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category)); + size.add_length(1, this->unit_of_measurement_ref_.size()); + size.add_int32(1, this->accuracy_decimals); + size.add_bool(1, this->force_update); + size.add_length(1, this->device_class_ref_.size()); + size.add_uint32(1, static_cast(this->state_class)); + size.add_bool(1, this->disabled_by_default); + size.add_uint32(1, static_cast(this->entity_category)); #ifdef USE_DEVICES - ProtoSize::add_uint32_field(total_size, 1, this->device_id); + size.add_uint32(1, this->device_id); #endif } void SensorStateResponse::encode(ProtoWriteBuffer buffer) const { @@ -680,12 +678,12 @@ void SensorStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(4, this->device_id); #endif } -void SensorStateResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_fixed32_field(total_size, 1, this->key); - ProtoSize::add_float_field(total_size, 1, this->state); - ProtoSize::add_bool_field(total_size, 1, this->missing_state); +void SensorStateResponse::calculate_size(ProtoSize &size) const { + size.add_fixed32(1, this->key); + size.add_float(1, this->state); + size.add_bool(1, this->missing_state); #ifdef USE_DEVICES - ProtoSize::add_uint32_field(total_size, 1, this->device_id); + size.add_uint32(1, this->device_id); #endif } #endif @@ -705,19 +703,19 @@ void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(10, this->device_id); #endif } -void ListEntitiesSwitchResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->object_id_ref_.size()); - ProtoSize::add_fixed32_field(total_size, 1, this->key); - ProtoSize::add_string_field(total_size, 1, this->name_ref_.size()); +void ListEntitiesSwitchResponse::calculate_size(ProtoSize &size) const { + size.add_length(1, this->object_id_ref_.size()); + size.add_fixed32(1, this->key); + size.add_length(1, this->name_ref_.size()); #ifdef USE_ENTITY_ICON - ProtoSize::add_string_field(total_size, 1, this->icon_ref_.size()); + size.add_length(1, this->icon_ref_.size()); #endif - ProtoSize::add_bool_field(total_size, 1, this->assumed_state); - ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category)); - ProtoSize::add_string_field(total_size, 1, this->device_class_ref_.size()); + size.add_bool(1, this->assumed_state); + size.add_bool(1, this->disabled_by_default); + size.add_uint32(1, static_cast(this->entity_category)); + size.add_length(1, this->device_class_ref_.size()); #ifdef USE_DEVICES - ProtoSize::add_uint32_field(total_size, 1, this->device_id); + size.add_uint32(1, this->device_id); #endif } void SwitchStateResponse::encode(ProtoWriteBuffer buffer) const { @@ -727,11 +725,11 @@ void SwitchStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(3, this->device_id); #endif } -void SwitchStateResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_fixed32_field(total_size, 1, this->key); - ProtoSize::add_bool_field(total_size, 1, this->state); +void SwitchStateResponse::calculate_size(ProtoSize &size) const { + size.add_fixed32(1, this->key); + size.add_bool(1, this->state); #ifdef USE_DEVICES - ProtoSize::add_uint32_field(total_size, 1, this->device_id); + size.add_uint32(1, this->device_id); #endif } bool SwitchCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { @@ -775,18 +773,18 @@ void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(9, this->device_id); #endif } -void ListEntitiesTextSensorResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->object_id_ref_.size()); - ProtoSize::add_fixed32_field(total_size, 1, this->key); - ProtoSize::add_string_field(total_size, 1, this->name_ref_.size()); +void ListEntitiesTextSensorResponse::calculate_size(ProtoSize &size) const { + size.add_length(1, this->object_id_ref_.size()); + size.add_fixed32(1, this->key); + size.add_length(1, this->name_ref_.size()); #ifdef USE_ENTITY_ICON - ProtoSize::add_string_field(total_size, 1, this->icon_ref_.size()); + size.add_length(1, this->icon_ref_.size()); #endif - ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category)); - ProtoSize::add_string_field(total_size, 1, this->device_class_ref_.size()); + size.add_bool(1, this->disabled_by_default); + size.add_uint32(1, static_cast(this->entity_category)); + size.add_length(1, this->device_class_ref_.size()); #ifdef USE_DEVICES - ProtoSize::add_uint32_field(total_size, 1, this->device_id); + size.add_uint32(1, this->device_id); #endif } void TextSensorStateResponse::encode(ProtoWriteBuffer buffer) const { @@ -797,12 +795,12 @@ void TextSensorStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(4, this->device_id); #endif } -void TextSensorStateResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_fixed32_field(total_size, 1, this->key); - ProtoSize::add_string_field(total_size, 1, this->state_ref_.size()); - ProtoSize::add_bool_field(total_size, 1, this->missing_state); +void TextSensorStateResponse::calculate_size(ProtoSize &size) const { + size.add_fixed32(1, this->key); + size.add_length(1, this->state_ref_.size()); + size.add_bool(1, this->missing_state); #ifdef USE_DEVICES - ProtoSize::add_uint32_field(total_size, 1, this->device_id); + size.add_uint32(1, this->device_id); #endif } #endif @@ -823,9 +821,9 @@ void SubscribeLogsResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(1, static_cast(this->level)); buffer.encode_bytes(3, this->message_ptr_, this->message_len_); } -void SubscribeLogsResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_enum_field(total_size, 1, static_cast(this->level)); - ProtoSize::add_bytes_field(total_size, 1, this->message_len_); +void SubscribeLogsResponse::calculate_size(ProtoSize &size) const { + size.add_uint32(1, static_cast(this->level)); + size.add_length(1, this->message_len_); } #ifdef USE_API_NOISE bool NoiseEncryptionSetKeyRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { @@ -839,18 +837,16 @@ bool NoiseEncryptionSetKeyRequest::decode_length(uint32_t field_id, ProtoLengthD return true; } void NoiseEncryptionSetKeyResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->success); } -void NoiseEncryptionSetKeyResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_bool_field(total_size, 1, this->success); -} +void NoiseEncryptionSetKeyResponse::calculate_size(ProtoSize &size) const { size.add_bool(1, this->success); } #endif #ifdef USE_API_HOMEASSISTANT_SERVICES void HomeassistantServiceMap::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->key_ref_); buffer.encode_string(2, this->value); } -void HomeassistantServiceMap::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->key_ref_.size()); - ProtoSize::add_string_field(total_size, 1, this->value.size()); +void HomeassistantServiceMap::calculate_size(ProtoSize &size) const { + size.add_length(1, this->key_ref_.size()); + size.add_length(1, this->value.size()); } void HomeassistantServiceResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->service_ref_); @@ -865,12 +861,12 @@ void HomeassistantServiceResponse::encode(ProtoWriteBuffer buffer) const { } buffer.encode_bool(5, this->is_event); } -void HomeassistantServiceResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->service_ref_.size()); - ProtoSize::add_repeated_message(total_size, 1, this->data); - ProtoSize::add_repeated_message(total_size, 1, this->data_template); - ProtoSize::add_repeated_message(total_size, 1, this->variables); - ProtoSize::add_bool_field(total_size, 1, this->is_event); +void HomeassistantServiceResponse::calculate_size(ProtoSize &size) const { + size.add_length(1, this->service_ref_.size()); + size.add_repeated_message(1, this->data); + size.add_repeated_message(1, this->data_template); + size.add_repeated_message(1, this->variables); + size.add_bool(1, this->is_event); } #endif #ifdef USE_API_HOMEASSISTANT_STATES @@ -879,10 +875,10 @@ void SubscribeHomeAssistantStateResponse::encode(ProtoWriteBuffer buffer) const buffer.encode_string(2, this->attribute_ref_); buffer.encode_bool(3, this->once); } -void SubscribeHomeAssistantStateResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->entity_id_ref_.size()); - ProtoSize::add_string_field(total_size, 1, this->attribute_ref_.size()); - ProtoSize::add_bool_field(total_size, 1, this->once); +void SubscribeHomeAssistantStateResponse::calculate_size(ProtoSize &size) const { + size.add_length(1, this->entity_id_ref_.size()); + size.add_length(1, this->attribute_ref_.size()); + size.add_bool(1, this->once); } bool HomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { @@ -912,17 +908,15 @@ bool GetTimeResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { return true; } void GetTimeResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->epoch_seconds); } -void GetTimeResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_fixed32_field(total_size, 1, this->epoch_seconds); -} +void GetTimeResponse::calculate_size(ProtoSize &size) const { size.add_fixed32(1, this->epoch_seconds); } #ifdef USE_API_SERVICES void ListEntitiesServicesArgument::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->name_ref_); buffer.encode_uint32(2, static_cast(this->type)); } -void ListEntitiesServicesArgument::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->name_ref_.size()); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->type)); +void ListEntitiesServicesArgument::calculate_size(ProtoSize &size) const { + size.add_length(1, this->name_ref_.size()); + size.add_uint32(1, static_cast(this->type)); } void ListEntitiesServicesResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->name_ref_); @@ -931,10 +925,10 @@ void ListEntitiesServicesResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_message(3, it, true); } } -void ListEntitiesServicesResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->name_ref_.size()); - ProtoSize::add_fixed32_field(total_size, 1, this->key); - ProtoSize::add_repeated_message(total_size, 1, this->args); +void ListEntitiesServicesResponse::calculate_size(ProtoSize &size) const { + size.add_length(1, this->name_ref_.size()); + size.add_fixed32(1, this->key); + size.add_repeated_message(1, this->args); } bool ExecuteServiceArgument::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { @@ -1020,17 +1014,17 @@ void ListEntitiesCameraResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(8, this->device_id); #endif } -void ListEntitiesCameraResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->object_id_ref_.size()); - ProtoSize::add_fixed32_field(total_size, 1, this->key); - ProtoSize::add_string_field(total_size, 1, this->name_ref_.size()); - ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default); +void ListEntitiesCameraResponse::calculate_size(ProtoSize &size) const { + size.add_length(1, this->object_id_ref_.size()); + size.add_fixed32(1, this->key); + size.add_length(1, this->name_ref_.size()); + size.add_bool(1, this->disabled_by_default); #ifdef USE_ENTITY_ICON - ProtoSize::add_string_field(total_size, 1, this->icon_ref_.size()); + size.add_length(1, this->icon_ref_.size()); #endif - ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category)); + size.add_uint32(1, static_cast(this->entity_category)); #ifdef USE_DEVICES - ProtoSize::add_uint32_field(total_size, 1, this->device_id); + size.add_uint32(1, this->device_id); #endif } void CameraImageResponse::encode(ProtoWriteBuffer buffer) const { @@ -1041,12 +1035,12 @@ void CameraImageResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(4, this->device_id); #endif } -void CameraImageResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_fixed32_field(total_size, 1, this->key); - ProtoSize::add_bytes_field(total_size, 1, this->data_len_); - ProtoSize::add_bool_field(total_size, 1, this->done); +void CameraImageResponse::calculate_size(ProtoSize &size) const { + size.add_fixed32(1, this->key); + size.add_length(1, this->data_len_); + size.add_bool(1, this->done); #ifdef USE_DEVICES - ProtoSize::add_uint32_field(total_size, 1, this->device_id); + size.add_uint32(1, this->device_id); #endif } bool CameraImageRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { @@ -1106,58 +1100,58 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(26, this->device_id); #endif } -void ListEntitiesClimateResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->object_id_ref_.size()); - ProtoSize::add_fixed32_field(total_size, 1, this->key); - ProtoSize::add_string_field(total_size, 1, this->name_ref_.size()); - ProtoSize::add_bool_field(total_size, 1, this->supports_current_temperature); - ProtoSize::add_bool_field(total_size, 1, this->supports_two_point_target_temperature); +void ListEntitiesClimateResponse::calculate_size(ProtoSize &size) const { + size.add_length(1, this->object_id_ref_.size()); + size.add_fixed32(1, this->key); + size.add_length(1, this->name_ref_.size()); + size.add_bool(1, this->supports_current_temperature); + size.add_bool(1, this->supports_two_point_target_temperature); if (!this->supported_modes.empty()) { for (const auto &it : this->supported_modes) { - ProtoSize::add_enum_field_repeated(total_size, 1, static_cast(it)); + size.add_uint32_force(1, static_cast(it)); } } - ProtoSize::add_float_field(total_size, 1, this->visual_min_temperature); - ProtoSize::add_float_field(total_size, 1, this->visual_max_temperature); - ProtoSize::add_float_field(total_size, 1, this->visual_target_temperature_step); - ProtoSize::add_bool_field(total_size, 1, this->supports_action); + size.add_float(1, this->visual_min_temperature); + size.add_float(1, this->visual_max_temperature); + size.add_float(1, this->visual_target_temperature_step); + size.add_bool(1, this->supports_action); if (!this->supported_fan_modes.empty()) { for (const auto &it : this->supported_fan_modes) { - ProtoSize::add_enum_field_repeated(total_size, 1, static_cast(it)); + size.add_uint32_force(1, static_cast(it)); } } if (!this->supported_swing_modes.empty()) { for (const auto &it : this->supported_swing_modes) { - ProtoSize::add_enum_field_repeated(total_size, 1, static_cast(it)); + size.add_uint32_force(1, static_cast(it)); } } if (!this->supported_custom_fan_modes.empty()) { for (const auto &it : this->supported_custom_fan_modes) { - ProtoSize::add_string_field_repeated(total_size, 1, it); + size.add_length_force(1, it.size()); } } if (!this->supported_presets.empty()) { for (const auto &it : this->supported_presets) { - ProtoSize::add_enum_field_repeated(total_size, 2, static_cast(it)); + size.add_uint32_force(2, static_cast(it)); } } if (!this->supported_custom_presets.empty()) { for (const auto &it : this->supported_custom_presets) { - ProtoSize::add_string_field_repeated(total_size, 2, it); + size.add_length_force(2, it.size()); } } - ProtoSize::add_bool_field(total_size, 2, this->disabled_by_default); + size.add_bool(2, this->disabled_by_default); #ifdef USE_ENTITY_ICON - ProtoSize::add_string_field(total_size, 2, this->icon_ref_.size()); + size.add_length(2, this->icon_ref_.size()); #endif - ProtoSize::add_enum_field(total_size, 2, static_cast(this->entity_category)); - ProtoSize::add_float_field(total_size, 2, this->visual_current_temperature_step); - ProtoSize::add_bool_field(total_size, 2, this->supports_current_humidity); - ProtoSize::add_bool_field(total_size, 2, this->supports_target_humidity); - ProtoSize::add_float_field(total_size, 2, this->visual_min_humidity); - ProtoSize::add_float_field(total_size, 2, this->visual_max_humidity); + size.add_uint32(2, static_cast(this->entity_category)); + size.add_float(2, this->visual_current_temperature_step); + size.add_bool(2, this->supports_current_humidity); + size.add_bool(2, this->supports_target_humidity); + size.add_float(2, this->visual_min_humidity); + size.add_float(2, this->visual_max_humidity); #ifdef USE_DEVICES - ProtoSize::add_uint32_field(total_size, 2, this->device_id); + size.add_uint32(2, this->device_id); #endif } void ClimateStateResponse::encode(ProtoWriteBuffer buffer) const { @@ -1179,23 +1173,23 @@ void ClimateStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(16, this->device_id); #endif } -void ClimateStateResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_fixed32_field(total_size, 1, this->key); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->mode)); - ProtoSize::add_float_field(total_size, 1, this->current_temperature); - ProtoSize::add_float_field(total_size, 1, this->target_temperature); - ProtoSize::add_float_field(total_size, 1, this->target_temperature_low); - ProtoSize::add_float_field(total_size, 1, this->target_temperature_high); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->action)); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->fan_mode)); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->swing_mode)); - ProtoSize::add_string_field(total_size, 1, this->custom_fan_mode_ref_.size()); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->preset)); - ProtoSize::add_string_field(total_size, 1, this->custom_preset_ref_.size()); - ProtoSize::add_float_field(total_size, 1, this->current_humidity); - ProtoSize::add_float_field(total_size, 1, this->target_humidity); +void ClimateStateResponse::calculate_size(ProtoSize &size) const { + size.add_fixed32(1, this->key); + size.add_uint32(1, static_cast(this->mode)); + size.add_float(1, this->current_temperature); + size.add_float(1, this->target_temperature); + size.add_float(1, this->target_temperature_low); + size.add_float(1, this->target_temperature_high); + size.add_uint32(1, static_cast(this->action)); + size.add_uint32(1, static_cast(this->fan_mode)); + size.add_uint32(1, static_cast(this->swing_mode)); + size.add_length(1, this->custom_fan_mode_ref_.size()); + size.add_uint32(1, static_cast(this->preset)); + size.add_length(1, this->custom_preset_ref_.size()); + size.add_float(1, this->current_humidity); + size.add_float(1, this->target_humidity); #ifdef USE_DEVICES - ProtoSize::add_uint32_field(total_size, 2, this->device_id); + size.add_uint32(2, this->device_id); #endif } bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { @@ -1308,23 +1302,23 @@ void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(14, this->device_id); #endif } -void ListEntitiesNumberResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->object_id_ref_.size()); - ProtoSize::add_fixed32_field(total_size, 1, this->key); - ProtoSize::add_string_field(total_size, 1, this->name_ref_.size()); +void ListEntitiesNumberResponse::calculate_size(ProtoSize &size) const { + size.add_length(1, this->object_id_ref_.size()); + size.add_fixed32(1, this->key); + size.add_length(1, this->name_ref_.size()); #ifdef USE_ENTITY_ICON - ProtoSize::add_string_field(total_size, 1, this->icon_ref_.size()); + size.add_length(1, this->icon_ref_.size()); #endif - ProtoSize::add_float_field(total_size, 1, this->min_value); - ProtoSize::add_float_field(total_size, 1, this->max_value); - ProtoSize::add_float_field(total_size, 1, this->step); - ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category)); - ProtoSize::add_string_field(total_size, 1, this->unit_of_measurement_ref_.size()); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->mode)); - ProtoSize::add_string_field(total_size, 1, this->device_class_ref_.size()); + size.add_float(1, this->min_value); + size.add_float(1, this->max_value); + size.add_float(1, this->step); + size.add_bool(1, this->disabled_by_default); + size.add_uint32(1, static_cast(this->entity_category)); + size.add_length(1, this->unit_of_measurement_ref_.size()); + size.add_uint32(1, static_cast(this->mode)); + size.add_length(1, this->device_class_ref_.size()); #ifdef USE_DEVICES - ProtoSize::add_uint32_field(total_size, 1, this->device_id); + size.add_uint32(1, this->device_id); #endif } void NumberStateResponse::encode(ProtoWriteBuffer buffer) const { @@ -1335,12 +1329,12 @@ void NumberStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(4, this->device_id); #endif } -void NumberStateResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_fixed32_field(total_size, 1, this->key); - ProtoSize::add_float_field(total_size, 1, this->state); - ProtoSize::add_bool_field(total_size, 1, this->missing_state); +void NumberStateResponse::calculate_size(ProtoSize &size) const { + size.add_fixed32(1, this->key); + size.add_float(1, this->state); + size.add_bool(1, this->missing_state); #ifdef USE_DEVICES - ProtoSize::add_uint32_field(total_size, 1, this->device_id); + size.add_uint32(1, this->device_id); #endif } bool NumberCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { @@ -1386,22 +1380,22 @@ void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(9, this->device_id); #endif } -void ListEntitiesSelectResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->object_id_ref_.size()); - ProtoSize::add_fixed32_field(total_size, 1, this->key); - ProtoSize::add_string_field(total_size, 1, this->name_ref_.size()); +void ListEntitiesSelectResponse::calculate_size(ProtoSize &size) const { + size.add_length(1, this->object_id_ref_.size()); + size.add_fixed32(1, this->key); + size.add_length(1, this->name_ref_.size()); #ifdef USE_ENTITY_ICON - ProtoSize::add_string_field(total_size, 1, this->icon_ref_.size()); + size.add_length(1, this->icon_ref_.size()); #endif if (!this->options.empty()) { for (const auto &it : this->options) { - ProtoSize::add_string_field_repeated(total_size, 1, it); + size.add_length_force(1, it.size()); } } - ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category)); + size.add_bool(1, this->disabled_by_default); + size.add_uint32(1, static_cast(this->entity_category)); #ifdef USE_DEVICES - ProtoSize::add_uint32_field(total_size, 1, this->device_id); + size.add_uint32(1, this->device_id); #endif } void SelectStateResponse::encode(ProtoWriteBuffer buffer) const { @@ -1412,12 +1406,12 @@ void SelectStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(4, this->device_id); #endif } -void SelectStateResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_fixed32_field(total_size, 1, this->key); - ProtoSize::add_string_field(total_size, 1, this->state_ref_.size()); - ProtoSize::add_bool_field(total_size, 1, this->missing_state); +void SelectStateResponse::calculate_size(ProtoSize &size) const { + size.add_fixed32(1, this->key); + size.add_length(1, this->state_ref_.size()); + size.add_bool(1, this->missing_state); #ifdef USE_DEVICES - ProtoSize::add_uint32_field(total_size, 1, this->device_id); + size.add_uint32(1, this->device_id); #endif } bool SelectCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { @@ -1472,24 +1466,24 @@ void ListEntitiesSirenResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(11, this->device_id); #endif } -void ListEntitiesSirenResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->object_id_ref_.size()); - ProtoSize::add_fixed32_field(total_size, 1, this->key); - ProtoSize::add_string_field(total_size, 1, this->name_ref_.size()); +void ListEntitiesSirenResponse::calculate_size(ProtoSize &size) const { + size.add_length(1, this->object_id_ref_.size()); + size.add_fixed32(1, this->key); + size.add_length(1, this->name_ref_.size()); #ifdef USE_ENTITY_ICON - ProtoSize::add_string_field(total_size, 1, this->icon_ref_.size()); + size.add_length(1, this->icon_ref_.size()); #endif - ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default); + size.add_bool(1, this->disabled_by_default); if (!this->tones.empty()) { for (const auto &it : this->tones) { - ProtoSize::add_string_field_repeated(total_size, 1, it); + size.add_length_force(1, it.size()); } } - ProtoSize::add_bool_field(total_size, 1, this->supports_duration); - ProtoSize::add_bool_field(total_size, 1, this->supports_volume); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category)); + size.add_bool(1, this->supports_duration); + size.add_bool(1, this->supports_volume); + size.add_uint32(1, static_cast(this->entity_category)); #ifdef USE_DEVICES - ProtoSize::add_uint32_field(total_size, 1, this->device_id); + size.add_uint32(1, this->device_id); #endif } void SirenStateResponse::encode(ProtoWriteBuffer buffer) const { @@ -1499,11 +1493,11 @@ void SirenStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(3, this->device_id); #endif } -void SirenStateResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_fixed32_field(total_size, 1, this->key); - ProtoSize::add_bool_field(total_size, 1, this->state); +void SirenStateResponse::calculate_size(ProtoSize &size) const { + size.add_fixed32(1, this->key); + size.add_bool(1, this->state); #ifdef USE_DEVICES - ProtoSize::add_uint32_field(total_size, 1, this->device_id); + size.add_uint32(1, this->device_id); #endif } bool SirenCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { @@ -1578,21 +1572,21 @@ void ListEntitiesLockResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(12, this->device_id); #endif } -void ListEntitiesLockResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->object_id_ref_.size()); - ProtoSize::add_fixed32_field(total_size, 1, this->key); - ProtoSize::add_string_field(total_size, 1, this->name_ref_.size()); +void ListEntitiesLockResponse::calculate_size(ProtoSize &size) const { + size.add_length(1, this->object_id_ref_.size()); + size.add_fixed32(1, this->key); + size.add_length(1, this->name_ref_.size()); #ifdef USE_ENTITY_ICON - ProtoSize::add_string_field(total_size, 1, this->icon_ref_.size()); + size.add_length(1, this->icon_ref_.size()); #endif - ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category)); - ProtoSize::add_bool_field(total_size, 1, this->assumed_state); - ProtoSize::add_bool_field(total_size, 1, this->supports_open); - ProtoSize::add_bool_field(total_size, 1, this->requires_code); - ProtoSize::add_string_field(total_size, 1, this->code_format_ref_.size()); + size.add_bool(1, this->disabled_by_default); + size.add_uint32(1, static_cast(this->entity_category)); + size.add_bool(1, this->assumed_state); + size.add_bool(1, this->supports_open); + size.add_bool(1, this->requires_code); + size.add_length(1, this->code_format_ref_.size()); #ifdef USE_DEVICES - ProtoSize::add_uint32_field(total_size, 1, this->device_id); + size.add_uint32(1, this->device_id); #endif } void LockStateResponse::encode(ProtoWriteBuffer buffer) const { @@ -1602,11 +1596,11 @@ void LockStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(3, this->device_id); #endif } -void LockStateResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_fixed32_field(total_size, 1, this->key); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->state)); +void LockStateResponse::calculate_size(ProtoSize &size) const { + size.add_fixed32(1, this->key); + size.add_uint32(1, static_cast(this->state)); #ifdef USE_DEVICES - ProtoSize::add_uint32_field(total_size, 1, this->device_id); + size.add_uint32(1, this->device_id); #endif } bool LockCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { @@ -1663,18 +1657,18 @@ void ListEntitiesButtonResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(9, this->device_id); #endif } -void ListEntitiesButtonResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->object_id_ref_.size()); - ProtoSize::add_fixed32_field(total_size, 1, this->key); - ProtoSize::add_string_field(total_size, 1, this->name_ref_.size()); +void ListEntitiesButtonResponse::calculate_size(ProtoSize &size) const { + size.add_length(1, this->object_id_ref_.size()); + size.add_fixed32(1, this->key); + size.add_length(1, this->name_ref_.size()); #ifdef USE_ENTITY_ICON - ProtoSize::add_string_field(total_size, 1, this->icon_ref_.size()); + size.add_length(1, this->icon_ref_.size()); #endif - ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category)); - ProtoSize::add_string_field(total_size, 1, this->device_class_ref_.size()); + size.add_bool(1, this->disabled_by_default); + size.add_uint32(1, static_cast(this->entity_category)); + size.add_length(1, this->device_class_ref_.size()); #ifdef USE_DEVICES - ProtoSize::add_uint32_field(total_size, 1, this->device_id); + size.add_uint32(1, this->device_id); #endif } bool ButtonCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { @@ -1708,12 +1702,12 @@ void MediaPlayerSupportedFormat::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(4, static_cast(this->purpose)); buffer.encode_uint32(5, this->sample_bytes); } -void MediaPlayerSupportedFormat::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->format_ref_.size()); - ProtoSize::add_uint32_field(total_size, 1, this->sample_rate); - ProtoSize::add_uint32_field(total_size, 1, this->num_channels); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->purpose)); - ProtoSize::add_uint32_field(total_size, 1, this->sample_bytes); +void MediaPlayerSupportedFormat::calculate_size(ProtoSize &size) const { + size.add_length(1, this->format_ref_.size()); + size.add_uint32(1, this->sample_rate); + size.add_uint32(1, this->num_channels); + size.add_uint32(1, static_cast(this->purpose)); + size.add_uint32(1, this->sample_bytes); } void ListEntitiesMediaPlayerResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->object_id_ref_); @@ -1732,19 +1726,19 @@ void ListEntitiesMediaPlayerResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(10, this->device_id); #endif } -void ListEntitiesMediaPlayerResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->object_id_ref_.size()); - ProtoSize::add_fixed32_field(total_size, 1, this->key); - ProtoSize::add_string_field(total_size, 1, this->name_ref_.size()); +void ListEntitiesMediaPlayerResponse::calculate_size(ProtoSize &size) const { + size.add_length(1, this->object_id_ref_.size()); + size.add_fixed32(1, this->key); + size.add_length(1, this->name_ref_.size()); #ifdef USE_ENTITY_ICON - ProtoSize::add_string_field(total_size, 1, this->icon_ref_.size()); + size.add_length(1, this->icon_ref_.size()); #endif - ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category)); - ProtoSize::add_bool_field(total_size, 1, this->supports_pause); - ProtoSize::add_repeated_message(total_size, 1, this->supported_formats); + size.add_bool(1, this->disabled_by_default); + size.add_uint32(1, static_cast(this->entity_category)); + size.add_bool(1, this->supports_pause); + size.add_repeated_message(1, this->supported_formats); #ifdef USE_DEVICES - ProtoSize::add_uint32_field(total_size, 1, this->device_id); + size.add_uint32(1, this->device_id); #endif } void MediaPlayerStateResponse::encode(ProtoWriteBuffer buffer) const { @@ -1756,13 +1750,13 @@ void MediaPlayerStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(5, this->device_id); #endif } -void MediaPlayerStateResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_fixed32_field(total_size, 1, this->key); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->state)); - ProtoSize::add_float_field(total_size, 1, this->volume); - ProtoSize::add_bool_field(total_size, 1, this->muted); +void MediaPlayerStateResponse::calculate_size(ProtoSize &size) const { + size.add_fixed32(1, this->key); + size.add_uint32(1, static_cast(this->state)); + size.add_float(1, this->volume); + size.add_bool(1, this->muted); #ifdef USE_DEVICES - ProtoSize::add_uint32_field(total_size, 1, this->device_id); + size.add_uint32(1, this->device_id); #endif } bool MediaPlayerCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { @@ -1836,21 +1830,19 @@ void BluetoothLERawAdvertisement::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(3, this->address_type); buffer.encode_bytes(4, this->data, this->data_len); } -void BluetoothLERawAdvertisement::calculate_size(uint32_t &total_size) const { - ProtoSize::add_uint64_field(total_size, 1, this->address); - ProtoSize::add_sint32_field(total_size, 1, this->rssi); - ProtoSize::add_uint32_field(total_size, 1, this->address_type); - if (this->data_len != 0) { - total_size += 1 + ProtoSize::varint(static_cast(this->data_len)) + this->data_len; - } +void BluetoothLERawAdvertisement::calculate_size(ProtoSize &size) const { + size.add_uint64(1, this->address); + size.add_sint32(1, this->rssi); + size.add_uint32(1, this->address_type); + size.add_length(1, this->data_len); } void BluetoothLERawAdvertisementsResponse::encode(ProtoWriteBuffer buffer) const { for (auto &it : this->advertisements) { buffer.encode_message(1, it, true); } } -void BluetoothLERawAdvertisementsResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_repeated_message(total_size, 1, this->advertisements); +void BluetoothLERawAdvertisementsResponse::calculate_size(ProtoSize &size) const { + size.add_repeated_message(1, this->advertisements); } bool BluetoothDeviceRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { @@ -1877,11 +1869,11 @@ void BluetoothDeviceConnectionResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(3, this->mtu); buffer.encode_int32(4, this->error); } -void BluetoothDeviceConnectionResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_uint64_field(total_size, 1, this->address); - ProtoSize::add_bool_field(total_size, 1, this->connected); - ProtoSize::add_uint32_field(total_size, 1, this->mtu); - ProtoSize::add_int32_field(total_size, 1, this->error); +void BluetoothDeviceConnectionResponse::calculate_size(ProtoSize &size) const { + size.add_uint64(1, this->address); + size.add_bool(1, this->connected); + size.add_uint32(1, this->mtu); + size.add_int32(1, this->error); } bool BluetoothGATTGetServicesRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { @@ -1898,10 +1890,10 @@ void BluetoothGATTDescriptor::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint64(1, this->uuid[1], true); buffer.encode_uint32(2, this->handle); } -void BluetoothGATTDescriptor::calculate_size(uint32_t &total_size) const { - ProtoSize::add_uint64_field_repeated(total_size, 1, this->uuid[0]); - ProtoSize::add_uint64_field_repeated(total_size, 1, this->uuid[1]); - ProtoSize::add_uint32_field(total_size, 1, this->handle); +void BluetoothGATTDescriptor::calculate_size(ProtoSize &size) const { + size.add_uint64_force(1, this->uuid[0]); + size.add_uint64_force(1, this->uuid[1]); + size.add_uint32(1, this->handle); } void BluetoothGATTCharacteristic::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint64(1, this->uuid[0], true); @@ -1912,12 +1904,12 @@ void BluetoothGATTCharacteristic::encode(ProtoWriteBuffer buffer) const { buffer.encode_message(4, it, true); } } -void BluetoothGATTCharacteristic::calculate_size(uint32_t &total_size) const { - ProtoSize::add_uint64_field_repeated(total_size, 1, this->uuid[0]); - ProtoSize::add_uint64_field_repeated(total_size, 1, this->uuid[1]); - ProtoSize::add_uint32_field(total_size, 1, this->handle); - ProtoSize::add_uint32_field(total_size, 1, this->properties); - ProtoSize::add_repeated_message(total_size, 1, this->descriptors); +void BluetoothGATTCharacteristic::calculate_size(ProtoSize &size) const { + size.add_uint64_force(1, this->uuid[0]); + size.add_uint64_force(1, this->uuid[1]); + size.add_uint32(1, this->handle); + size.add_uint32(1, this->properties); + size.add_repeated_message(1, this->descriptors); } void BluetoothGATTService::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint64(1, this->uuid[0], true); @@ -1927,26 +1919,24 @@ void BluetoothGATTService::encode(ProtoWriteBuffer buffer) const { buffer.encode_message(3, it, true); } } -void BluetoothGATTService::calculate_size(uint32_t &total_size) const { - ProtoSize::add_uint64_field_repeated(total_size, 1, this->uuid[0]); - ProtoSize::add_uint64_field_repeated(total_size, 1, this->uuid[1]); - ProtoSize::add_uint32_field(total_size, 1, this->handle); - ProtoSize::add_repeated_message(total_size, 1, this->characteristics); +void BluetoothGATTService::calculate_size(ProtoSize &size) const { + size.add_uint64_force(1, this->uuid[0]); + size.add_uint64_force(1, this->uuid[1]); + size.add_uint32(1, this->handle); + size.add_repeated_message(1, this->characteristics); } void BluetoothGATTGetServicesResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint64(1, this->address); buffer.encode_message(2, this->services[0], true); } -void BluetoothGATTGetServicesResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_uint64_field(total_size, 1, this->address); - ProtoSize::add_message_object_repeated(total_size, 1, this->services[0]); +void BluetoothGATTGetServicesResponse::calculate_size(ProtoSize &size) const { + size.add_uint64(1, this->address); + size.add_message_object_force(1, this->services[0]); } void BluetoothGATTGetServicesDoneResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint64(1, this->address); } -void BluetoothGATTGetServicesDoneResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_uint64_field(total_size, 1, this->address); -} +void BluetoothGATTGetServicesDoneResponse::calculate_size(ProtoSize &size) const { size.add_uint64(1, this->address); } bool BluetoothGATTReadRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 1: @@ -1965,10 +1955,10 @@ void BluetoothGATTReadResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(2, this->handle); buffer.encode_bytes(3, this->data_ptr_, this->data_len_); } -void BluetoothGATTReadResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_uint64_field(total_size, 1, this->address); - ProtoSize::add_uint32_field(total_size, 1, this->handle); - ProtoSize::add_bytes_field(total_size, 1, this->data_len_); +void BluetoothGATTReadResponse::calculate_size(ProtoSize &size) const { + size.add_uint64(1, this->address); + size.add_uint32(1, this->handle); + size.add_length(1, this->data_len_); } bool BluetoothGATTWriteRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { @@ -2053,10 +2043,10 @@ void BluetoothGATTNotifyDataResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(2, this->handle); buffer.encode_bytes(3, this->data_ptr_, this->data_len_); } -void BluetoothGATTNotifyDataResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_uint64_field(total_size, 1, this->address); - ProtoSize::add_uint32_field(total_size, 1, this->handle); - ProtoSize::add_bytes_field(total_size, 1, this->data_len_); +void BluetoothGATTNotifyDataResponse::calculate_size(ProtoSize &size) const { + size.add_uint64(1, this->address); + size.add_uint32(1, this->handle); + size.add_length(1, this->data_len_); } void BluetoothConnectionsFreeResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(1, this->free); @@ -2065,12 +2055,12 @@ void BluetoothConnectionsFreeResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint64(3, it, true); } } -void BluetoothConnectionsFreeResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_uint32_field(total_size, 1, this->free); - ProtoSize::add_uint32_field(total_size, 1, this->limit); +void BluetoothConnectionsFreeResponse::calculate_size(ProtoSize &size) const { + size.add_uint32(1, this->free); + size.add_uint32(1, this->limit); if (!this->allocated.empty()) { for (const auto &it : this->allocated) { - ProtoSize::add_uint64_field_repeated(total_size, 1, it); + size.add_uint64_force(1, it); } } } @@ -2079,64 +2069,64 @@ void BluetoothGATTErrorResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(2, this->handle); buffer.encode_int32(3, this->error); } -void BluetoothGATTErrorResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_uint64_field(total_size, 1, this->address); - ProtoSize::add_uint32_field(total_size, 1, this->handle); - ProtoSize::add_int32_field(total_size, 1, this->error); +void BluetoothGATTErrorResponse::calculate_size(ProtoSize &size) const { + size.add_uint64(1, this->address); + size.add_uint32(1, this->handle); + size.add_int32(1, this->error); } void BluetoothGATTWriteResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint64(1, this->address); buffer.encode_uint32(2, this->handle); } -void BluetoothGATTWriteResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_uint64_field(total_size, 1, this->address); - ProtoSize::add_uint32_field(total_size, 1, this->handle); +void BluetoothGATTWriteResponse::calculate_size(ProtoSize &size) const { + size.add_uint64(1, this->address); + size.add_uint32(1, this->handle); } void BluetoothGATTNotifyResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint64(1, this->address); buffer.encode_uint32(2, this->handle); } -void BluetoothGATTNotifyResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_uint64_field(total_size, 1, this->address); - ProtoSize::add_uint32_field(total_size, 1, this->handle); +void BluetoothGATTNotifyResponse::calculate_size(ProtoSize &size) const { + size.add_uint64(1, this->address); + size.add_uint32(1, this->handle); } void BluetoothDevicePairingResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint64(1, this->address); buffer.encode_bool(2, this->paired); buffer.encode_int32(3, this->error); } -void BluetoothDevicePairingResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_uint64_field(total_size, 1, this->address); - ProtoSize::add_bool_field(total_size, 1, this->paired); - ProtoSize::add_int32_field(total_size, 1, this->error); +void BluetoothDevicePairingResponse::calculate_size(ProtoSize &size) const { + size.add_uint64(1, this->address); + size.add_bool(1, this->paired); + size.add_int32(1, this->error); } void BluetoothDeviceUnpairingResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint64(1, this->address); buffer.encode_bool(2, this->success); buffer.encode_int32(3, this->error); } -void BluetoothDeviceUnpairingResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_uint64_field(total_size, 1, this->address); - ProtoSize::add_bool_field(total_size, 1, this->success); - ProtoSize::add_int32_field(total_size, 1, this->error); +void BluetoothDeviceUnpairingResponse::calculate_size(ProtoSize &size) const { + size.add_uint64(1, this->address); + size.add_bool(1, this->success); + size.add_int32(1, this->error); } void BluetoothDeviceClearCacheResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint64(1, this->address); buffer.encode_bool(2, this->success); buffer.encode_int32(3, this->error); } -void BluetoothDeviceClearCacheResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_uint64_field(total_size, 1, this->address); - ProtoSize::add_bool_field(total_size, 1, this->success); - ProtoSize::add_int32_field(total_size, 1, this->error); +void BluetoothDeviceClearCacheResponse::calculate_size(ProtoSize &size) const { + size.add_uint64(1, this->address); + size.add_bool(1, this->success); + size.add_int32(1, this->error); } void BluetoothScannerStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(1, static_cast(this->state)); buffer.encode_uint32(2, static_cast(this->mode)); } -void BluetoothScannerStateResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_enum_field(total_size, 1, static_cast(this->state)); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->mode)); +void BluetoothScannerStateResponse::calculate_size(ProtoSize &size) const { + size.add_uint32(1, static_cast(this->state)); + size.add_uint32(1, static_cast(this->mode)); } bool BluetoothScannerSetModeRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { @@ -2168,10 +2158,10 @@ void VoiceAssistantAudioSettings::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(2, this->auto_gain); buffer.encode_float(3, this->volume_multiplier); } -void VoiceAssistantAudioSettings::calculate_size(uint32_t &total_size) const { - ProtoSize::add_uint32_field(total_size, 1, this->noise_suppression_level); - ProtoSize::add_uint32_field(total_size, 1, this->auto_gain); - ProtoSize::add_float_field(total_size, 1, this->volume_multiplier); +void VoiceAssistantAudioSettings::calculate_size(ProtoSize &size) const { + size.add_uint32(1, this->noise_suppression_level); + size.add_uint32(1, this->auto_gain); + size.add_float(1, this->volume_multiplier); } void VoiceAssistantRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->start); @@ -2180,12 +2170,12 @@ void VoiceAssistantRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_message(4, this->audio_settings); buffer.encode_string(5, this->wake_word_phrase_ref_); } -void VoiceAssistantRequest::calculate_size(uint32_t &total_size) const { - ProtoSize::add_bool_field(total_size, 1, this->start); - ProtoSize::add_string_field(total_size, 1, this->conversation_id_ref_.size()); - ProtoSize::add_uint32_field(total_size, 1, this->flags); - ProtoSize::add_message_object(total_size, 1, this->audio_settings); - ProtoSize::add_string_field(total_size, 1, this->wake_word_phrase_ref_.size()); +void VoiceAssistantRequest::calculate_size(ProtoSize &size) const { + size.add_bool(1, this->start); + size.add_length(1, this->conversation_id_ref_.size()); + size.add_uint32(1, this->flags); + size.add_message_object(1, this->audio_settings); + size.add_length(1, this->wake_word_phrase_ref_.size()); } bool VoiceAssistantResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { @@ -2258,9 +2248,9 @@ void VoiceAssistantAudio::encode(ProtoWriteBuffer buffer) const { buffer.encode_bytes(1, this->data_ptr_, this->data_len_); buffer.encode_bool(2, this->end); } -void VoiceAssistantAudio::calculate_size(uint32_t &total_size) const { - ProtoSize::add_bytes_field(total_size, 1, this->data_len_); - ProtoSize::add_bool_field(total_size, 1, this->end); +void VoiceAssistantAudio::calculate_size(ProtoSize &size) const { + size.add_length(1, this->data_len_); + size.add_bool(1, this->end); } bool VoiceAssistantTimerEventResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { @@ -2321,9 +2311,7 @@ bool VoiceAssistantAnnounceRequest::decode_length(uint32_t field_id, ProtoLength return true; } void VoiceAssistantAnnounceFinished::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->success); } -void VoiceAssistantAnnounceFinished::calculate_size(uint32_t &total_size) const { - ProtoSize::add_bool_field(total_size, 1, this->success); -} +void VoiceAssistantAnnounceFinished::calculate_size(ProtoSize &size) const { size.add_bool(1, this->success); } void VoiceAssistantWakeWord::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->id_ref_); buffer.encode_string(2, this->wake_word_ref_); @@ -2331,12 +2319,12 @@ void VoiceAssistantWakeWord::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(3, it, true); } } -void VoiceAssistantWakeWord::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->id_ref_.size()); - ProtoSize::add_string_field(total_size, 1, this->wake_word_ref_.size()); +void VoiceAssistantWakeWord::calculate_size(ProtoSize &size) const { + size.add_length(1, this->id_ref_.size()); + size.add_length(1, this->wake_word_ref_.size()); if (!this->trained_languages.empty()) { for (const auto &it : this->trained_languages) { - ProtoSize::add_string_field_repeated(total_size, 1, it); + size.add_length_force(1, it.size()); } } } @@ -2349,14 +2337,14 @@ void VoiceAssistantConfigurationResponse::encode(ProtoWriteBuffer buffer) const } buffer.encode_uint32(3, this->max_active_wake_words); } -void VoiceAssistantConfigurationResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_repeated_message(total_size, 1, this->available_wake_words); +void VoiceAssistantConfigurationResponse::calculate_size(ProtoSize &size) const { + size.add_repeated_message(1, this->available_wake_words); if (!this->active_wake_words.empty()) { for (const auto &it : this->active_wake_words) { - ProtoSize::add_string_field_repeated(total_size, 1, it); + size.add_length_force(1, it.size()); } } - ProtoSize::add_uint32_field(total_size, 1, this->max_active_wake_words); + size.add_uint32(1, this->max_active_wake_words); } bool VoiceAssistantSetConfiguration::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { @@ -2386,20 +2374,20 @@ void ListEntitiesAlarmControlPanelResponse::encode(ProtoWriteBuffer buffer) cons buffer.encode_uint32(11, this->device_id); #endif } -void ListEntitiesAlarmControlPanelResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->object_id_ref_.size()); - ProtoSize::add_fixed32_field(total_size, 1, this->key); - ProtoSize::add_string_field(total_size, 1, this->name_ref_.size()); +void ListEntitiesAlarmControlPanelResponse::calculate_size(ProtoSize &size) const { + size.add_length(1, this->object_id_ref_.size()); + size.add_fixed32(1, this->key); + size.add_length(1, this->name_ref_.size()); #ifdef USE_ENTITY_ICON - ProtoSize::add_string_field(total_size, 1, this->icon_ref_.size()); + size.add_length(1, this->icon_ref_.size()); #endif - ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category)); - ProtoSize::add_uint32_field(total_size, 1, this->supported_features); - ProtoSize::add_bool_field(total_size, 1, this->requires_code); - ProtoSize::add_bool_field(total_size, 1, this->requires_code_to_arm); + size.add_bool(1, this->disabled_by_default); + size.add_uint32(1, static_cast(this->entity_category)); + size.add_uint32(1, this->supported_features); + size.add_bool(1, this->requires_code); + size.add_bool(1, this->requires_code_to_arm); #ifdef USE_DEVICES - ProtoSize::add_uint32_field(total_size, 1, this->device_id); + size.add_uint32(1, this->device_id); #endif } void AlarmControlPanelStateResponse::encode(ProtoWriteBuffer buffer) const { @@ -2409,11 +2397,11 @@ void AlarmControlPanelStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(3, this->device_id); #endif } -void AlarmControlPanelStateResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_fixed32_field(total_size, 1, this->key); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->state)); +void AlarmControlPanelStateResponse::calculate_size(ProtoSize &size) const { + size.add_fixed32(1, this->key); + size.add_uint32(1, static_cast(this->state)); #ifdef USE_DEVICES - ProtoSize::add_uint32_field(total_size, 1, this->device_id); + size.add_uint32(1, this->device_id); #endif } bool AlarmControlPanelCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { @@ -2470,21 +2458,21 @@ void ListEntitiesTextResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(12, this->device_id); #endif } -void ListEntitiesTextResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->object_id_ref_.size()); - ProtoSize::add_fixed32_field(total_size, 1, this->key); - ProtoSize::add_string_field(total_size, 1, this->name_ref_.size()); +void ListEntitiesTextResponse::calculate_size(ProtoSize &size) const { + size.add_length(1, this->object_id_ref_.size()); + size.add_fixed32(1, this->key); + size.add_length(1, this->name_ref_.size()); #ifdef USE_ENTITY_ICON - ProtoSize::add_string_field(total_size, 1, this->icon_ref_.size()); + size.add_length(1, this->icon_ref_.size()); #endif - ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category)); - ProtoSize::add_uint32_field(total_size, 1, this->min_length); - ProtoSize::add_uint32_field(total_size, 1, this->max_length); - ProtoSize::add_string_field(total_size, 1, this->pattern_ref_.size()); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->mode)); + size.add_bool(1, this->disabled_by_default); + size.add_uint32(1, static_cast(this->entity_category)); + size.add_uint32(1, this->min_length); + size.add_uint32(1, this->max_length); + size.add_length(1, this->pattern_ref_.size()); + size.add_uint32(1, static_cast(this->mode)); #ifdef USE_DEVICES - ProtoSize::add_uint32_field(total_size, 1, this->device_id); + size.add_uint32(1, this->device_id); #endif } void TextStateResponse::encode(ProtoWriteBuffer buffer) const { @@ -2495,12 +2483,12 @@ void TextStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(4, this->device_id); #endif } -void TextStateResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_fixed32_field(total_size, 1, this->key); - ProtoSize::add_string_field(total_size, 1, this->state_ref_.size()); - ProtoSize::add_bool_field(total_size, 1, this->missing_state); +void TextStateResponse::calculate_size(ProtoSize &size) const { + size.add_fixed32(1, this->key); + size.add_length(1, this->state_ref_.size()); + size.add_bool(1, this->missing_state); #ifdef USE_DEVICES - ProtoSize::add_uint32_field(total_size, 1, this->device_id); + size.add_uint32(1, this->device_id); #endif } bool TextCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { @@ -2550,17 +2538,17 @@ void ListEntitiesDateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(8, this->device_id); #endif } -void ListEntitiesDateResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->object_id_ref_.size()); - ProtoSize::add_fixed32_field(total_size, 1, this->key); - ProtoSize::add_string_field(total_size, 1, this->name_ref_.size()); +void ListEntitiesDateResponse::calculate_size(ProtoSize &size) const { + size.add_length(1, this->object_id_ref_.size()); + size.add_fixed32(1, this->key); + size.add_length(1, this->name_ref_.size()); #ifdef USE_ENTITY_ICON - ProtoSize::add_string_field(total_size, 1, this->icon_ref_.size()); + size.add_length(1, this->icon_ref_.size()); #endif - ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category)); + size.add_bool(1, this->disabled_by_default); + size.add_uint32(1, static_cast(this->entity_category)); #ifdef USE_DEVICES - ProtoSize::add_uint32_field(total_size, 1, this->device_id); + size.add_uint32(1, this->device_id); #endif } void DateStateResponse::encode(ProtoWriteBuffer buffer) const { @@ -2573,14 +2561,14 @@ void DateStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(6, this->device_id); #endif } -void DateStateResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_fixed32_field(total_size, 1, this->key); - ProtoSize::add_bool_field(total_size, 1, this->missing_state); - ProtoSize::add_uint32_field(total_size, 1, this->year); - ProtoSize::add_uint32_field(total_size, 1, this->month); - ProtoSize::add_uint32_field(total_size, 1, this->day); +void DateStateResponse::calculate_size(ProtoSize &size) const { + size.add_fixed32(1, this->key); + size.add_bool(1, this->missing_state); + size.add_uint32(1, this->year); + size.add_uint32(1, this->month); + size.add_uint32(1, this->day); #ifdef USE_DEVICES - ProtoSize::add_uint32_field(total_size, 1, this->device_id); + size.add_uint32(1, this->device_id); #endif } bool DateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { @@ -2629,17 +2617,17 @@ void ListEntitiesTimeResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(8, this->device_id); #endif } -void ListEntitiesTimeResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->object_id_ref_.size()); - ProtoSize::add_fixed32_field(total_size, 1, this->key); - ProtoSize::add_string_field(total_size, 1, this->name_ref_.size()); +void ListEntitiesTimeResponse::calculate_size(ProtoSize &size) const { + size.add_length(1, this->object_id_ref_.size()); + size.add_fixed32(1, this->key); + size.add_length(1, this->name_ref_.size()); #ifdef USE_ENTITY_ICON - ProtoSize::add_string_field(total_size, 1, this->icon_ref_.size()); + size.add_length(1, this->icon_ref_.size()); #endif - ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category)); + size.add_bool(1, this->disabled_by_default); + size.add_uint32(1, static_cast(this->entity_category)); #ifdef USE_DEVICES - ProtoSize::add_uint32_field(total_size, 1, this->device_id); + size.add_uint32(1, this->device_id); #endif } void TimeStateResponse::encode(ProtoWriteBuffer buffer) const { @@ -2652,14 +2640,14 @@ void TimeStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(6, this->device_id); #endif } -void TimeStateResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_fixed32_field(total_size, 1, this->key); - ProtoSize::add_bool_field(total_size, 1, this->missing_state); - ProtoSize::add_uint32_field(total_size, 1, this->hour); - ProtoSize::add_uint32_field(total_size, 1, this->minute); - ProtoSize::add_uint32_field(total_size, 1, this->second); +void TimeStateResponse::calculate_size(ProtoSize &size) const { + size.add_fixed32(1, this->key); + size.add_bool(1, this->missing_state); + size.add_uint32(1, this->hour); + size.add_uint32(1, this->minute); + size.add_uint32(1, this->second); #ifdef USE_DEVICES - ProtoSize::add_uint32_field(total_size, 1, this->device_id); + size.add_uint32(1, this->device_id); #endif } bool TimeCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { @@ -2712,23 +2700,23 @@ void ListEntitiesEventResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(10, this->device_id); #endif } -void ListEntitiesEventResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->object_id_ref_.size()); - ProtoSize::add_fixed32_field(total_size, 1, this->key); - ProtoSize::add_string_field(total_size, 1, this->name_ref_.size()); +void ListEntitiesEventResponse::calculate_size(ProtoSize &size) const { + size.add_length(1, this->object_id_ref_.size()); + size.add_fixed32(1, this->key); + size.add_length(1, this->name_ref_.size()); #ifdef USE_ENTITY_ICON - ProtoSize::add_string_field(total_size, 1, this->icon_ref_.size()); + size.add_length(1, this->icon_ref_.size()); #endif - ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category)); - ProtoSize::add_string_field(total_size, 1, this->device_class_ref_.size()); + size.add_bool(1, this->disabled_by_default); + size.add_uint32(1, static_cast(this->entity_category)); + size.add_length(1, this->device_class_ref_.size()); if (!this->event_types.empty()) { for (const auto &it : this->event_types) { - ProtoSize::add_string_field_repeated(total_size, 1, it); + size.add_length_force(1, it.size()); } } #ifdef USE_DEVICES - ProtoSize::add_uint32_field(total_size, 1, this->device_id); + size.add_uint32(1, this->device_id); #endif } void EventResponse::encode(ProtoWriteBuffer buffer) const { @@ -2738,11 +2726,11 @@ void EventResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(3, this->device_id); #endif } -void EventResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_fixed32_field(total_size, 1, this->key); - ProtoSize::add_string_field(total_size, 1, this->event_type_ref_.size()); +void EventResponse::calculate_size(ProtoSize &size) const { + size.add_fixed32(1, this->key); + size.add_length(1, this->event_type_ref_.size()); #ifdef USE_DEVICES - ProtoSize::add_uint32_field(total_size, 1, this->device_id); + size.add_uint32(1, this->device_id); #endif } #endif @@ -2764,21 +2752,21 @@ void ListEntitiesValveResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(12, this->device_id); #endif } -void ListEntitiesValveResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->object_id_ref_.size()); - ProtoSize::add_fixed32_field(total_size, 1, this->key); - ProtoSize::add_string_field(total_size, 1, this->name_ref_.size()); +void ListEntitiesValveResponse::calculate_size(ProtoSize &size) const { + size.add_length(1, this->object_id_ref_.size()); + size.add_fixed32(1, this->key); + size.add_length(1, this->name_ref_.size()); #ifdef USE_ENTITY_ICON - ProtoSize::add_string_field(total_size, 1, this->icon_ref_.size()); + size.add_length(1, this->icon_ref_.size()); #endif - ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category)); - ProtoSize::add_string_field(total_size, 1, this->device_class_ref_.size()); - ProtoSize::add_bool_field(total_size, 1, this->assumed_state); - ProtoSize::add_bool_field(total_size, 1, this->supports_position); - ProtoSize::add_bool_field(total_size, 1, this->supports_stop); + size.add_bool(1, this->disabled_by_default); + size.add_uint32(1, static_cast(this->entity_category)); + size.add_length(1, this->device_class_ref_.size()); + size.add_bool(1, this->assumed_state); + size.add_bool(1, this->supports_position); + size.add_bool(1, this->supports_stop); #ifdef USE_DEVICES - ProtoSize::add_uint32_field(total_size, 1, this->device_id); + size.add_uint32(1, this->device_id); #endif } void ValveStateResponse::encode(ProtoWriteBuffer buffer) const { @@ -2789,12 +2777,12 @@ void ValveStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(4, this->device_id); #endif } -void ValveStateResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_fixed32_field(total_size, 1, this->key); - ProtoSize::add_float_field(total_size, 1, this->position); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->current_operation)); +void ValveStateResponse::calculate_size(ProtoSize &size) const { + size.add_fixed32(1, this->key); + size.add_float(1, this->position); + size.add_uint32(1, static_cast(this->current_operation)); #ifdef USE_DEVICES - ProtoSize::add_uint32_field(total_size, 1, this->device_id); + size.add_uint32(1, this->device_id); #endif } bool ValveCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { @@ -2843,17 +2831,17 @@ void ListEntitiesDateTimeResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(8, this->device_id); #endif } -void ListEntitiesDateTimeResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->object_id_ref_.size()); - ProtoSize::add_fixed32_field(total_size, 1, this->key); - ProtoSize::add_string_field(total_size, 1, this->name_ref_.size()); +void ListEntitiesDateTimeResponse::calculate_size(ProtoSize &size) const { + size.add_length(1, this->object_id_ref_.size()); + size.add_fixed32(1, this->key); + size.add_length(1, this->name_ref_.size()); #ifdef USE_ENTITY_ICON - ProtoSize::add_string_field(total_size, 1, this->icon_ref_.size()); + size.add_length(1, this->icon_ref_.size()); #endif - ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category)); + size.add_bool(1, this->disabled_by_default); + size.add_uint32(1, static_cast(this->entity_category)); #ifdef USE_DEVICES - ProtoSize::add_uint32_field(total_size, 1, this->device_id); + size.add_uint32(1, this->device_id); #endif } void DateTimeStateResponse::encode(ProtoWriteBuffer buffer) const { @@ -2864,12 +2852,12 @@ void DateTimeStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(4, this->device_id); #endif } -void DateTimeStateResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_fixed32_field(total_size, 1, this->key); - ProtoSize::add_bool_field(total_size, 1, this->missing_state); - ProtoSize::add_fixed32_field(total_size, 1, this->epoch_seconds); +void DateTimeStateResponse::calculate_size(ProtoSize &size) const { + size.add_fixed32(1, this->key); + size.add_bool(1, this->missing_state); + size.add_fixed32(1, this->epoch_seconds); #ifdef USE_DEVICES - ProtoSize::add_uint32_field(total_size, 1, this->device_id); + size.add_uint32(1, this->device_id); #endif } bool DateTimeCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { @@ -2913,18 +2901,18 @@ void ListEntitiesUpdateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(9, this->device_id); #endif } -void ListEntitiesUpdateResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->object_id_ref_.size()); - ProtoSize::add_fixed32_field(total_size, 1, this->key); - ProtoSize::add_string_field(total_size, 1, this->name_ref_.size()); +void ListEntitiesUpdateResponse::calculate_size(ProtoSize &size) const { + size.add_length(1, this->object_id_ref_.size()); + size.add_fixed32(1, this->key); + size.add_length(1, this->name_ref_.size()); #ifdef USE_ENTITY_ICON - ProtoSize::add_string_field(total_size, 1, this->icon_ref_.size()); + size.add_length(1, this->icon_ref_.size()); #endif - ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default); - ProtoSize::add_enum_field(total_size, 1, static_cast(this->entity_category)); - ProtoSize::add_string_field(total_size, 1, this->device_class_ref_.size()); + size.add_bool(1, this->disabled_by_default); + size.add_uint32(1, static_cast(this->entity_category)); + size.add_length(1, this->device_class_ref_.size()); #ifdef USE_DEVICES - ProtoSize::add_uint32_field(total_size, 1, this->device_id); + size.add_uint32(1, this->device_id); #endif } void UpdateStateResponse::encode(ProtoWriteBuffer buffer) const { @@ -2942,19 +2930,19 @@ void UpdateStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(11, this->device_id); #endif } -void UpdateStateResponse::calculate_size(uint32_t &total_size) const { - ProtoSize::add_fixed32_field(total_size, 1, this->key); - ProtoSize::add_bool_field(total_size, 1, this->missing_state); - ProtoSize::add_bool_field(total_size, 1, this->in_progress); - ProtoSize::add_bool_field(total_size, 1, this->has_progress); - ProtoSize::add_float_field(total_size, 1, this->progress); - ProtoSize::add_string_field(total_size, 1, this->current_version_ref_.size()); - ProtoSize::add_string_field(total_size, 1, this->latest_version_ref_.size()); - ProtoSize::add_string_field(total_size, 1, this->title_ref_.size()); - ProtoSize::add_string_field(total_size, 1, this->release_summary_ref_.size()); - ProtoSize::add_string_field(total_size, 1, this->release_url_ref_.size()); +void UpdateStateResponse::calculate_size(ProtoSize &size) const { + size.add_fixed32(1, this->key); + size.add_bool(1, this->missing_state); + size.add_bool(1, this->in_progress); + size.add_bool(1, this->has_progress); + size.add_float(1, this->progress); + size.add_length(1, this->current_version_ref_.size()); + size.add_length(1, this->latest_version_ref_.size()); + size.add_length(1, this->title_ref_.size()); + size.add_length(1, this->release_summary_ref_.size()); + size.add_length(1, this->release_url_ref_.size()); #ifdef USE_DEVICES - ProtoSize::add_uint32_field(total_size, 1, this->device_id); + size.add_uint32(1, this->device_id); #endif } bool UpdateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 47bdaead23..f637e44df3 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -340,7 +340,7 @@ class HelloResponse : public ProtoMessage { StringRef name_ref_{}; void set_name(const StringRef &ref) { this->name_ref_ = ref; } void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -371,7 +371,7 @@ class ConnectResponse : public ProtoMessage { #endif bool invalid_password{false}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -450,7 +450,7 @@ class AreaInfo : public ProtoMessage { StringRef name_ref_{}; void set_name(const StringRef &ref) { this->name_ref_ = ref; } void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -466,7 +466,7 @@ class DeviceInfo : public ProtoMessage { void set_name(const StringRef &ref) { this->name_ref_ = ref; } uint32_t area_id{0}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -539,7 +539,7 @@ class DeviceInfoResponse : public ProtoMessage { AreaInfo area{}; #endif void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -597,7 +597,7 @@ class ListEntitiesBinarySensorResponse : public InfoResponseProtoMessage { void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; } bool is_status_binary_sensor{false}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -614,7 +614,7 @@ class BinarySensorStateResponse : public StateResponseProtoMessage { bool state{false}; bool missing_state{false}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -637,7 +637,7 @@ class ListEntitiesCoverResponse : public InfoResponseProtoMessage { void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; } bool supports_stop{false}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -655,7 +655,7 @@ class CoverStateResponse : public StateResponseProtoMessage { float tilt{0.0f}; enums::CoverOperation current_operation{}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -697,7 +697,7 @@ class ListEntitiesFanResponse : public InfoResponseProtoMessage { int32_t supported_speed_count{0}; std::vector supported_preset_modes{}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -718,7 +718,7 @@ class FanStateResponse : public StateResponseProtoMessage { StringRef preset_mode_ref_{}; void set_preset_mode(const StringRef &ref) { this->preset_mode_ref_ = ref; } void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -765,7 +765,7 @@ class ListEntitiesLightResponse : public InfoResponseProtoMessage { float max_mireds{0.0f}; std::vector effects{}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -793,7 +793,7 @@ class LightStateResponse : public StateResponseProtoMessage { StringRef effect_ref_{}; void set_effect(const StringRef &ref) { this->effect_ref_ = ref; } void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -859,7 +859,7 @@ class ListEntitiesSensorResponse : public InfoResponseProtoMessage { void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; } enums::SensorStateClass state_class{}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -876,7 +876,7 @@ class SensorStateResponse : public StateResponseProtoMessage { float state{0.0f}; bool missing_state{false}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -896,7 +896,7 @@ class ListEntitiesSwitchResponse : public InfoResponseProtoMessage { StringRef device_class_ref_{}; void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; } void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -912,7 +912,7 @@ class SwitchStateResponse : public StateResponseProtoMessage { #endif bool state{false}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -947,7 +947,7 @@ class ListEntitiesTextSensorResponse : public InfoResponseProtoMessage { StringRef device_class_ref_{}; void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; } void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -965,7 +965,7 @@ class TextSensorStateResponse : public StateResponseProtoMessage { void set_state(const StringRef &ref) { this->state_ref_ = ref; } bool missing_state{false}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1004,7 +1004,7 @@ class SubscribeLogsResponse : public ProtoMessage { this->message_len_ = len; } void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1036,7 +1036,7 @@ class NoiseEncryptionSetKeyResponse : public ProtoMessage { #endif bool success{false}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1064,7 +1064,7 @@ class HomeassistantServiceMap : public ProtoMessage { void set_key(const StringRef &ref) { this->key_ref_ = ref; } std::string value{}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1085,7 +1085,7 @@ class HomeassistantServiceResponse : public ProtoMessage { std::vector variables{}; bool is_event{false}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1120,7 +1120,7 @@ class SubscribeHomeAssistantStateResponse : public ProtoMessage { void set_attribute(const StringRef &ref) { this->attribute_ref_ = ref; } bool once{false}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1167,7 +1167,7 @@ class GetTimeResponse : public ProtoDecodableMessage { #endif uint32_t epoch_seconds{0}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1182,7 +1182,7 @@ class ListEntitiesServicesArgument : public ProtoMessage { void set_name(const StringRef &ref) { this->name_ref_ = ref; } enums::ServiceArgType type{}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1201,7 +1201,7 @@ class ListEntitiesServicesResponse : public ProtoMessage { uint32_t key{0}; std::vector args{}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1255,7 +1255,7 @@ class ListEntitiesCameraResponse : public InfoResponseProtoMessage { const char *message_name() const override { return "list_entities_camera_response"; } #endif void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1277,7 +1277,7 @@ class CameraImageResponse : public StateResponseProtoMessage { } bool done{false}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1327,7 +1327,7 @@ class ListEntitiesClimateResponse : public InfoResponseProtoMessage { float visual_min_humidity{0.0f}; float visual_max_humidity{0.0f}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1357,7 +1357,7 @@ class ClimateStateResponse : public StateResponseProtoMessage { float current_humidity{0.0f}; float target_humidity{0.0f}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1418,7 +1418,7 @@ class ListEntitiesNumberResponse : public InfoResponseProtoMessage { StringRef device_class_ref_{}; void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; } void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1435,7 +1435,7 @@ class NumberStateResponse : public StateResponseProtoMessage { float state{0.0f}; bool missing_state{false}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1469,7 +1469,7 @@ class ListEntitiesSelectResponse : public InfoResponseProtoMessage { #endif std::vector options{}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1487,7 +1487,7 @@ class SelectStateResponse : public StateResponseProtoMessage { void set_state(const StringRef &ref) { this->state_ref_ = ref; } bool missing_state{false}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1524,7 +1524,7 @@ class ListEntitiesSirenResponse : public InfoResponseProtoMessage { bool supports_duration{false}; bool supports_volume{false}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1540,7 +1540,7 @@ class SirenStateResponse : public StateResponseProtoMessage { #endif bool state{false}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1586,7 +1586,7 @@ class ListEntitiesLockResponse : public InfoResponseProtoMessage { StringRef code_format_ref_{}; void set_code_format(const StringRef &ref) { this->code_format_ref_ = ref; } void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1602,7 +1602,7 @@ class LockStateResponse : public StateResponseProtoMessage { #endif enums::LockState state{}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1640,7 +1640,7 @@ class ListEntitiesButtonResponse : public InfoResponseProtoMessage { StringRef device_class_ref_{}; void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; } void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1673,7 +1673,7 @@ class MediaPlayerSupportedFormat : public ProtoMessage { enums::MediaPlayerFormatPurpose purpose{}; uint32_t sample_bytes{0}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1690,7 +1690,7 @@ class ListEntitiesMediaPlayerResponse : public InfoResponseProtoMessage { bool supports_pause{false}; std::vector supported_formats{}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1708,7 +1708,7 @@ class MediaPlayerStateResponse : public StateResponseProtoMessage { float volume{0.0f}; bool muted{false}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1764,7 +1764,7 @@ class BluetoothLERawAdvertisement : public ProtoMessage { uint8_t data[62]{}; uint8_t data_len{0}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1780,7 +1780,7 @@ class BluetoothLERawAdvertisementsResponse : public ProtoMessage { #endif std::vector advertisements{}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1817,7 +1817,7 @@ class BluetoothDeviceConnectionResponse : public ProtoMessage { uint32_t mtu{0}; int32_t error{0}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1844,7 +1844,7 @@ class BluetoothGATTDescriptor : public ProtoMessage { std::array uuid{}; uint32_t handle{0}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1858,7 +1858,7 @@ class BluetoothGATTCharacteristic : public ProtoMessage { uint32_t properties{0}; std::vector descriptors{}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1871,7 +1871,7 @@ class BluetoothGATTService : public ProtoMessage { uint32_t handle{0}; std::vector characteristics{}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1888,7 +1888,7 @@ class BluetoothGATTGetServicesResponse : public ProtoMessage { uint64_t address{0}; std::array services{}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1904,7 +1904,7 @@ class BluetoothGATTGetServicesDoneResponse : public ProtoMessage { #endif uint64_t address{0}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1943,7 +1943,7 @@ class BluetoothGATTReadResponse : public ProtoMessage { this->data_len_ = len; } void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2036,7 +2036,7 @@ class BluetoothGATTNotifyDataResponse : public ProtoMessage { this->data_len_ = len; } void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2067,7 +2067,7 @@ class BluetoothConnectionsFreeResponse : public ProtoMessage { uint32_t limit{0}; std::vector allocated{}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2085,7 +2085,7 @@ class BluetoothGATTErrorResponse : public ProtoMessage { uint32_t handle{0}; int32_t error{0}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2102,7 +2102,7 @@ class BluetoothGATTWriteResponse : public ProtoMessage { uint64_t address{0}; uint32_t handle{0}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2119,7 +2119,7 @@ class BluetoothGATTNotifyResponse : public ProtoMessage { uint64_t address{0}; uint32_t handle{0}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2137,7 +2137,7 @@ class BluetoothDevicePairingResponse : public ProtoMessage { bool paired{false}; int32_t error{0}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2155,7 +2155,7 @@ class BluetoothDeviceUnpairingResponse : public ProtoMessage { bool success{false}; int32_t error{0}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2186,7 +2186,7 @@ class BluetoothDeviceClearCacheResponse : public ProtoMessage { bool success{false}; int32_t error{0}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2203,7 +2203,7 @@ class BluetoothScannerStateResponse : public ProtoMessage { enums::BluetoothScannerState state{}; enums::BluetoothScannerMode mode{}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2249,7 +2249,7 @@ class VoiceAssistantAudioSettings : public ProtoMessage { uint32_t auto_gain{0}; float volume_multiplier{0.0f}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2271,7 +2271,7 @@ class VoiceAssistantRequest : public ProtoMessage { StringRef wake_word_phrase_ref_{}; void set_wake_word_phrase(const StringRef &ref) { this->wake_word_phrase_ref_ = ref; } void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2338,7 +2338,7 @@ class VoiceAssistantAudio : public ProtoDecodableMessage { } bool end{false}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2396,7 +2396,7 @@ class VoiceAssistantAnnounceFinished : public ProtoMessage { #endif bool success{false}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2411,7 +2411,7 @@ class VoiceAssistantWakeWord : public ProtoMessage { void set_wake_word(const StringRef &ref) { this->wake_word_ref_ = ref; } std::vector trained_languages{}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2442,7 +2442,7 @@ class VoiceAssistantConfigurationResponse : public ProtoMessage { std::vector active_wake_words{}; uint32_t max_active_wake_words{0}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2477,7 +2477,7 @@ class ListEntitiesAlarmControlPanelResponse : public InfoResponseProtoMessage { bool requires_code{false}; bool requires_code_to_arm{false}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2493,7 +2493,7 @@ class AlarmControlPanelStateResponse : public StateResponseProtoMessage { #endif enums::AlarmControlPanelState state{}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2533,7 +2533,7 @@ class ListEntitiesTextResponse : public InfoResponseProtoMessage { void set_pattern(const StringRef &ref) { this->pattern_ref_ = ref; } enums::TextMode mode{}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2551,7 +2551,7 @@ class TextStateResponse : public StateResponseProtoMessage { void set_state(const StringRef &ref) { this->state_ref_ = ref; } bool missing_state{false}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2585,7 +2585,7 @@ class ListEntitiesDateResponse : public InfoResponseProtoMessage { const char *message_name() const override { return "list_entities_date_response"; } #endif void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2604,7 +2604,7 @@ class DateStateResponse : public StateResponseProtoMessage { uint32_t month{0}; uint32_t day{0}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2639,7 +2639,7 @@ class ListEntitiesTimeResponse : public InfoResponseProtoMessage { const char *message_name() const override { return "list_entities_time_response"; } #endif void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2658,7 +2658,7 @@ class TimeStateResponse : public StateResponseProtoMessage { uint32_t minute{0}; uint32_t second{0}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2696,7 +2696,7 @@ class ListEntitiesEventResponse : public InfoResponseProtoMessage { void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; } std::vector event_types{}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2713,7 +2713,7 @@ class EventResponse : public StateResponseProtoMessage { StringRef event_type_ref_{}; void set_event_type(const StringRef &ref) { this->event_type_ref_ = ref; } void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2735,7 +2735,7 @@ class ListEntitiesValveResponse : public InfoResponseProtoMessage { bool supports_position{false}; bool supports_stop{false}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2752,7 +2752,7 @@ class ValveStateResponse : public StateResponseProtoMessage { float position{0.0f}; enums::ValveOperation current_operation{}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2787,7 +2787,7 @@ class ListEntitiesDateTimeResponse : public InfoResponseProtoMessage { const char *message_name() const override { return "list_entities_date_time_response"; } #endif void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2804,7 +2804,7 @@ class DateTimeStateResponse : public StateResponseProtoMessage { bool missing_state{false}; uint32_t epoch_seconds{0}; void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2839,7 +2839,7 @@ class ListEntitiesUpdateResponse : public InfoResponseProtoMessage { StringRef device_class_ref_{}; void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; } void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2868,7 +2868,7 @@ class UpdateStateResponse : public StateResponseProtoMessage { StringRef release_url_ref_{}; void set_release_url(const StringRef &ref) { this->release_url_ref_ = ref; } void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(uint32_t &total_size) const override; + void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif diff --git a/esphome/components/api/proto.h b/esphome/components/api/proto.h index cea93f928f..5c174b679c 100644 --- a/esphome/components/api/proto.h +++ b/esphome/components/api/proto.h @@ -333,13 +333,16 @@ class ProtoWriteBuffer { std::vector *buffer_; }; +// Forward declaration +class ProtoSize; + class ProtoMessage { public: virtual ~ProtoMessage() = default; // Default implementation for messages with no fields virtual void encode(ProtoWriteBuffer buffer) const {} // Default implementation for messages with no fields - virtual void calculate_size(uint32_t &total_size) const {} + virtual void calculate_size(ProtoSize &size) const {} #ifdef HAS_PROTO_MESSAGE_DUMP std::string dump() const; virtual void dump_to(std::string &out) const = 0; @@ -360,24 +363,32 @@ class ProtoDecodableMessage : public ProtoMessage { }; class ProtoSize { + private: + uint32_t total_size_ = 0; + public: /** * @brief ProtoSize class for Protocol Buffer serialization size calculation * - * This class provides static methods to calculate the exact byte counts needed - * for encoding various Protocol Buffer field types. All methods are designed to be - * efficient for the common case where many fields have default values. + * This class provides methods to calculate the exact byte counts needed + * for encoding various Protocol Buffer field types. The class now uses an + * object-based approach to reduce parameter passing overhead while keeping + * varint calculation methods static for external use. * * Implements Protocol Buffer encoding size calculation according to: * https://protobuf.dev/programming-guides/encoding/ * * Key features: + * - Object-based approach reduces flash usage by eliminating parameter passing * - Early-return optimization for zero/default values - * - Direct total_size updates to avoid unnecessary additions + * - Static varint methods for external callers * - Specialized handling for different field types according to protobuf spec - * - Templated helpers for repeated fields and messages */ + ProtoSize() = default; + + uint32_t get_size() const { return total_size_; } + /** * @brief Calculates the size in bytes needed to encode a uint32_t value as a varint * @@ -478,9 +489,7 @@ class ProtoSize { * @brief Common parameters for all add_*_field methods * * All add_*_field methods follow these common patterns: - * - * @param total_size Reference to the total message size to update - * @param field_id_size Pre-calculated size of the field ID in bytes + * * @param field_id_size Pre-calculated size of the field ID in bytes * @param value The value to calculate size for (type varies) * @param force Whether to calculate size even if the value is default/zero/empty * @@ -493,85 +502,63 @@ class ProtoSize { /** * @brief Calculates and adds the size of an int32 field to the total message size */ - static inline void add_int32_field(uint32_t &total_size, uint32_t field_id_size, int32_t value) { - // Skip calculation if value is zero - if (value == 0) { - return; // No need to update total_size - } - - // Calculate and directly add to total_size - if (value < 0) { - // Negative values are encoded as 10-byte varints in protobuf - total_size += field_id_size + 10; - } else { - // For non-negative values, use the standard varint size - total_size += field_id_size + varint(static_cast(value)); + inline void add_int32(uint32_t field_id_size, int32_t value) { + if (value != 0) { + add_int32_force(field_id_size, value); } } /** - * @brief Calculates and adds the size of an int32 field to the total message size (repeated field version) + * @brief Calculates and adds the size of an int32 field to the total message size (force version) */ - static inline void add_int32_field_repeated(uint32_t &total_size, uint32_t field_id_size, int32_t value) { - // Always calculate size for repeated fields - if (value < 0) { - // Negative values are encoded as 10-byte varints in protobuf - total_size += field_id_size + 10; - } else { - // For non-negative values, use the standard varint size - total_size += field_id_size + varint(static_cast(value)); - } + inline void add_int32_force(uint32_t field_id_size, int32_t value) { + // Always calculate size when forced + // Negative values are encoded as 10-byte varints in protobuf + total_size_ += field_id_size + (value < 0 ? 10 : varint(static_cast(value))); } /** * @brief Calculates and adds the size of a uint32 field to the total message size */ - static inline void add_uint32_field(uint32_t &total_size, uint32_t field_id_size, uint32_t value) { - // Skip calculation if value is zero - if (value == 0) { - return; // No need to update total_size + inline void add_uint32(uint32_t field_id_size, uint32_t value) { + if (value != 0) { + add_uint32_force(field_id_size, value); } - - // Calculate and directly add to total_size - total_size += field_id_size + varint(value); } /** - * @brief Calculates and adds the size of a uint32 field to the total message size (repeated field version) + * @brief Calculates and adds the size of a uint32 field to the total message size (force version) */ - static inline void add_uint32_field_repeated(uint32_t &total_size, uint32_t field_id_size, uint32_t value) { - // Always calculate size for repeated fields - total_size += field_id_size + varint(value); + inline void add_uint32_force(uint32_t field_id_size, uint32_t value) { + // Always calculate size when force is true + total_size_ += field_id_size + varint(value); } /** * @brief Calculates and adds the size of a boolean field to the total message size */ - static inline void add_bool_field(uint32_t &total_size, uint32_t field_id_size, bool value) { - // Skip calculation if value is false - if (!value) { - return; // No need to update total_size + inline void add_bool(uint32_t field_id_size, bool value) { + if (value) { + // Boolean fields always use 1 byte when true + total_size_ += field_id_size + 1; } - - // Boolean fields always use 1 byte when true - total_size += field_id_size + 1; } /** - * @brief Calculates and adds the size of a boolean field to the total message size (repeated field version) + * @brief Calculates and adds the size of a boolean field to the total message size (force version) */ - static inline void add_bool_field_repeated(uint32_t &total_size, uint32_t field_id_size, bool value) { - // Always calculate size for repeated fields + inline void add_bool_force(uint32_t field_id_size, bool value) { + // Always calculate size when force is true // Boolean fields always use 1 byte - total_size += field_id_size + 1; + total_size_ += field_id_size + 1; } /** * @brief Calculates and adds the size of a float field to the total message size */ - static inline void add_float_field(uint32_t &total_size, uint32_t field_id_size, float value) { + inline void add_float(uint32_t field_id_size, float value) { if (value != 0.0f) { - total_size += field_id_size + 4; + total_size_ += field_id_size + 4; } } @@ -581,9 +568,9 @@ class ProtoSize { /** * @brief Calculates and adds the size of a fixed32 field to the total message size */ - static inline void add_fixed32_field(uint32_t &total_size, uint32_t field_id_size, uint32_t value) { + inline void add_fixed32(uint32_t field_id_size, uint32_t value) { if (value != 0) { - total_size += field_id_size + 4; + total_size_ += field_id_size + 4; } } @@ -593,149 +580,104 @@ class ProtoSize { /** * @brief Calculates and adds the size of a sfixed32 field to the total message size */ - static inline void add_sfixed32_field(uint32_t &total_size, uint32_t field_id_size, int32_t value) { + inline void add_sfixed32(uint32_t field_id_size, int32_t value) { if (value != 0) { - total_size += field_id_size + 4; + total_size_ += field_id_size + 4; } } // NOTE: add_sfixed64_field removed - wire type 1 (64-bit: sfixed64) not supported // to reduce overhead on embedded systems - /** - * @brief Calculates and adds the size of an enum field to the total message size - * - * Enum fields are encoded as uint32 varints. - */ - static inline void add_enum_field(uint32_t &total_size, uint32_t field_id_size, uint32_t value) { - // Skip calculation if value is zero - if (value == 0) { - return; // No need to update total_size - } - - // Enums are encoded as uint32 - total_size += field_id_size + varint(value); - } - - /** - * @brief Calculates and adds the size of an enum field to the total message size (repeated field version) - * - * Enum fields are encoded as uint32 varints. - */ - static inline void add_enum_field_repeated(uint32_t &total_size, uint32_t field_id_size, uint32_t value) { - // Always calculate size for repeated fields - // Enums are encoded as uint32 - total_size += field_id_size + varint(value); - } - /** * @brief Calculates and adds the size of a sint32 field to the total message size * * Sint32 fields use ZigZag encoding, which is more efficient for negative values. */ - static inline void add_sint32_field(uint32_t &total_size, uint32_t field_id_size, int32_t value) { - // Skip calculation if value is zero - if (value == 0) { - return; // No need to update total_size + inline void add_sint32(uint32_t field_id_size, int32_t value) { + if (value != 0) { + add_sint32_force(field_id_size, value); } - - // ZigZag encoding for sint32: (n << 1) ^ (n >> 31) - uint32_t zigzag = (static_cast(value) << 1) ^ (static_cast(value >> 31)); - total_size += field_id_size + varint(zigzag); } /** - * @brief Calculates and adds the size of a sint32 field to the total message size (repeated field version) + * @brief Calculates and adds the size of a sint32 field to the total message size (force version) * * Sint32 fields use ZigZag encoding, which is more efficient for negative values. */ - static inline void add_sint32_field_repeated(uint32_t &total_size, uint32_t field_id_size, int32_t value) { - // Always calculate size for repeated fields + inline void add_sint32_force(uint32_t field_id_size, int32_t value) { + // Always calculate size when force is true // ZigZag encoding for sint32: (n << 1) ^ (n >> 31) uint32_t zigzag = (static_cast(value) << 1) ^ (static_cast(value >> 31)); - total_size += field_id_size + varint(zigzag); + total_size_ += field_id_size + varint(zigzag); } /** * @brief Calculates and adds the size of an int64 field to the total message size */ - static inline void add_int64_field(uint32_t &total_size, uint32_t field_id_size, int64_t value) { - // Skip calculation if value is zero - if (value == 0) { - return; // No need to update total_size + inline void add_int64(uint32_t field_id_size, int64_t value) { + if (value != 0) { + add_int64_force(field_id_size, value); } - - // Calculate and directly add to total_size - total_size += field_id_size + varint(value); } /** - * @brief Calculates and adds the size of an int64 field to the total message size (repeated field version) + * @brief Calculates and adds the size of an int64 field to the total message size (force version) */ - static inline void add_int64_field_repeated(uint32_t &total_size, uint32_t field_id_size, int64_t value) { - // Always calculate size for repeated fields - total_size += field_id_size + varint(value); + inline void add_int64_force(uint32_t field_id_size, int64_t value) { + // Always calculate size when force is true + total_size_ += field_id_size + varint(value); } /** * @brief Calculates and adds the size of a uint64 field to the total message size */ - static inline void add_uint64_field(uint32_t &total_size, uint32_t field_id_size, uint64_t value) { - // Skip calculation if value is zero - if (value == 0) { - return; // No need to update total_size + inline void add_uint64(uint32_t field_id_size, uint64_t value) { + if (value != 0) { + add_uint64_force(field_id_size, value); } - - // Calculate and directly add to total_size - total_size += field_id_size + varint(value); } /** - * @brief Calculates and adds the size of a uint64 field to the total message size (repeated field version) + * @brief Calculates and adds the size of a uint64 field to the total message size (force version) */ - static inline void add_uint64_field_repeated(uint32_t &total_size, uint32_t field_id_size, uint64_t value) { - // Always calculate size for repeated fields - total_size += field_id_size + varint(value); + inline void add_uint64_force(uint32_t field_id_size, uint64_t value) { + // Always calculate size when force is true + total_size_ += field_id_size + varint(value); } - // NOTE: sint64 support functions (add_sint64_field, add_sint64_field_repeated) removed + // NOTE: sint64 support functions (add_sint64_field, add_sint64_field_force) removed // sint64 type is not supported by ESPHome API to reduce overhead on embedded systems /** - * @brief Calculates and adds the size of a string field using length + * @brief Calculates and adds the size of a length-delimited field (string/bytes) to the total message size */ - static inline void add_string_field(uint32_t &total_size, uint32_t field_id_size, size_t len) { - // Skip calculation if string is empty - if (len == 0) { - return; // No need to update total_size + inline void add_length(uint32_t field_id_size, size_t len) { + if (len != 0) { + add_length_force(field_id_size, len); } - - // Field ID + length varint + string bytes - total_size += field_id_size + varint(static_cast(len)) + static_cast(len); } /** - * @brief Calculates and adds the size of a string/bytes field to the total message size (repeated field version) + * @brief Calculates and adds the size of a length-delimited field (string/bytes) to the total message size (repeated + * field version) */ - static inline void add_string_field_repeated(uint32_t &total_size, uint32_t field_id_size, const std::string &str) { - // Always calculate size for repeated fields - const uint32_t str_size = static_cast(str.size()); - total_size += field_id_size + varint(str_size) + str_size; - } - - /** - * @brief Calculates and adds the size of a bytes field to the total message size - */ - static inline void add_bytes_field(uint32_t &total_size, uint32_t field_id_size, size_t len) { - // Skip calculation if bytes is empty - if (len == 0) { - return; // No need to update total_size - } - + inline void add_length_force(uint32_t field_id_size, size_t len) { + // Always calculate size when force is true // Field ID + length varint + data bytes - total_size += field_id_size + varint(static_cast(len)) + static_cast(len); + total_size_ += field_id_size + varint(static_cast(len)) + static_cast(len); } + /** + * @brief Adds a pre-calculated size directly to the total + * + * This is used when we can calculate the total size by multiplying the number + * of elements by the bytes per element (for repeated fixed-size types like float, fixed32, etc.) + * + * @param size The pre-calculated total size to add + */ + inline void add_precalculated_size(uint32_t size) { total_size_ += size; } + /** * @brief Calculates and adds the size of a nested message field to the total message size * @@ -744,26 +686,21 @@ class ProtoSize { * * @param nested_size The pre-calculated size of the nested message */ - static inline void add_message_field(uint32_t &total_size, uint32_t field_id_size, uint32_t nested_size) { - // Skip calculation if nested message is empty - if (nested_size == 0) { - return; // No need to update total_size + inline void add_message_field(uint32_t field_id_size, uint32_t nested_size) { + if (nested_size != 0) { + add_message_field_force(field_id_size, nested_size); } - - // Calculate and directly add to total_size - // Field ID + length varint + nested message content - total_size += field_id_size + varint(nested_size) + nested_size; } /** - * @brief Calculates and adds the size of a nested message field to the total message size (repeated field version) + * @brief Calculates and adds the size of a nested message field to the total message size (force version) * * @param nested_size The pre-calculated size of the nested message */ - static inline void add_message_field_repeated(uint32_t &total_size, uint32_t field_id_size, uint32_t nested_size) { - // Always calculate size for repeated fields + inline void add_message_field_force(uint32_t field_id_size, uint32_t nested_size) { + // Always calculate size when force is true // Field ID + length varint + nested message content - total_size += field_id_size + varint(nested_size) + nested_size; + total_size_ += field_id_size + varint(nested_size) + nested_size; } /** @@ -775,26 +712,29 @@ class ProtoSize { * * @param message The nested message object */ - static inline void add_message_object(uint32_t &total_size, uint32_t field_id_size, const ProtoMessage &message) { - uint32_t nested_size = 0; - message.calculate_size(nested_size); + inline void add_message_object(uint32_t field_id_size, const ProtoMessage &message) { + // Calculate nested message size by creating a temporary ProtoSize + ProtoSize nested_calc; + message.calculate_size(nested_calc); + uint32_t nested_size = nested_calc.get_size(); // Use the base implementation with the calculated nested_size - add_message_field(total_size, field_id_size, nested_size); + add_message_field(field_id_size, nested_size); } /** - * @brief Calculates and adds the size of a nested message field to the total message size (repeated field version) + * @brief Calculates and adds the size of a nested message field to the total message size (force version) * * @param message The nested message object */ - static inline void add_message_object_repeated(uint32_t &total_size, uint32_t field_id_size, - const ProtoMessage &message) { - uint32_t nested_size = 0; - message.calculate_size(nested_size); + inline void add_message_object_force(uint32_t field_id_size, const ProtoMessage &message) { + // Calculate nested message size by creating a temporary ProtoSize + ProtoSize nested_calc; + message.calculate_size(nested_calc); + uint32_t nested_size = nested_calc.get_size(); // Use the base implementation with the calculated nested_size - add_message_field_repeated(total_size, field_id_size, nested_size); + add_message_field_force(field_id_size, nested_size); } /** @@ -807,16 +747,15 @@ class ProtoSize { * @param messages Vector of message objects */ template - static inline void add_repeated_message(uint32_t &total_size, uint32_t field_id_size, - const std::vector &messages) { + inline void add_repeated_message(uint32_t field_id_size, const std::vector &messages) { // Skip if the vector is empty if (messages.empty()) { return; } - // Use the repeated field version for all messages + // Use the force version for all messages in the repeated field for (const auto &message : messages) { - add_message_object_repeated(total_size, field_id_size, message); + add_message_object_force(field_id_size, message); } } }; @@ -826,8 +765,9 @@ inline void ProtoWriteBuffer::encode_message(uint32_t field_id, const ProtoMessa this->encode_field_raw(field_id, 2); // type 2: Length-delimited message // Calculate the message size first - uint32_t msg_length_bytes = 0; - value.calculate_size(msg_length_bytes); + ProtoSize msg_size; + value.calculate_size(msg_size); + uint32_t msg_length_bytes = msg_size.get_size(); // Calculate how many bytes the length varint needs uint32_t varint_length_bytes = ProtoSize::varint(msg_length_bytes); @@ -876,8 +816,9 @@ class ProtoService { // Optimized method that pre-allocates buffer based on message size bool send_message_(const ProtoMessage &msg, uint8_t message_type) { - uint32_t msg_size = 0; - msg.calculate_size(msg_size); + ProtoSize size; + msg.calculate_size(size); + uint32_t msg_size = size.get_size(); // Create a pre-sized buffer auto buffer = this->create_buffer(msg_size); diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index d346b66aef..275c7ffc9e 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -275,13 +275,13 @@ class TypeInfo(ABC): Args: name: Field name force: Whether this is for a repeated field - base_method: Base method name (e.g., "add_int32_field") + base_method: Base method name (e.g., "add_int32") value_expr: Optional value expression (defaults to name) """ field_id_size = self.calculate_field_id_size() - method = f"{base_method}_repeated" if force else base_method + method = f"{base_method}_force" if force else base_method value = value_expr if value_expr else name - return f"ProtoSize::{method}(total_size, {field_id_size}, {value});" + return f"size.{method}({field_id_size}, {value});" @abstractmethod def get_size_calculation(self, name: str, force: bool = False) -> str: @@ -389,7 +389,7 @@ class DoubleType(TypeInfo): def get_size_calculation(self, name: str, force: bool = False) -> str: field_id_size = self.calculate_field_id_size() - return f"ProtoSize::add_double_field(total_size, {field_id_size}, {name});" + return f"size.add_double({field_id_size}, {name});" def get_fixed_size_bytes(self) -> int: return 8 @@ -413,7 +413,7 @@ class FloatType(TypeInfo): def get_size_calculation(self, name: str, force: bool = False) -> str: field_id_size = self.calculate_field_id_size() - return f"ProtoSize::add_float_field(total_size, {field_id_size}, {name});" + return f"size.add_float({field_id_size}, {name});" def get_fixed_size_bytes(self) -> int: return 4 @@ -436,7 +436,7 @@ class Int64Type(TypeInfo): return o def get_size_calculation(self, name: str, force: bool = False) -> str: - return self._get_simple_size_calculation(name, force, "add_int64_field") + return self._get_simple_size_calculation(name, force, "add_int64") def get_estimated_size(self) -> int: return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint @@ -456,7 +456,7 @@ class UInt64Type(TypeInfo): return o def get_size_calculation(self, name: str, force: bool = False) -> str: - return self._get_simple_size_calculation(name, force, "add_uint64_field") + return self._get_simple_size_calculation(name, force, "add_uint64") def get_estimated_size(self) -> int: return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint @@ -476,7 +476,7 @@ class Int32Type(TypeInfo): return o def get_size_calculation(self, name: str, force: bool = False) -> str: - return self._get_simple_size_calculation(name, force, "add_int32_field") + return self._get_simple_size_calculation(name, force, "add_int32") def get_estimated_size(self) -> int: return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint @@ -497,7 +497,7 @@ class Fixed64Type(TypeInfo): def get_size_calculation(self, name: str, force: bool = False) -> str: field_id_size = self.calculate_field_id_size() - return f"ProtoSize::add_fixed64_field(total_size, {field_id_size}, {name});" + return f"size.add_fixed64({field_id_size}, {name});" def get_fixed_size_bytes(self) -> int: return 8 @@ -521,7 +521,7 @@ class Fixed32Type(TypeInfo): def get_size_calculation(self, name: str, force: bool = False) -> str: field_id_size = self.calculate_field_id_size() - return f"ProtoSize::add_fixed32_field(total_size, {field_id_size}, {name});" + return f"size.add_fixed32({field_id_size}, {name});" def get_fixed_size_bytes(self) -> int: return 4 @@ -543,7 +543,7 @@ class BoolType(TypeInfo): return o def get_size_calculation(self, name: str, force: bool = False) -> str: - return self._get_simple_size_calculation(name, force, "add_bool_field") + return self._get_simple_size_calculation(name, force, "add_bool") def get_estimated_size(self) -> int: return self.calculate_field_id_size() + 1 # field ID + 1 byte @@ -657,19 +657,21 @@ class StringType(TypeInfo): # For no_zero_copy, we need to use .size() on the string if no_zero_copy and name != "it": field_id_size = self.calculate_field_id_size() - return f"ProtoSize::add_string_field(total_size, {field_id_size}, this->{self.field_name}.size());" - return self._get_simple_size_calculation(name, force, "add_string_field") + return ( + f"size.add_length({field_id_size}, this->{self.field_name}.size());" + ) + return self._get_simple_size_calculation(name, force, "add_length") # Check if this is being called from a repeated field context # In that case, 'name' will be 'it' and we need to use the repeated version if name == "it": - # For repeated fields, we need to use add_string_field_repeated which includes field ID + # For repeated fields, we need to use add_length_force which includes field ID field_id_size = self.calculate_field_id_size() - return f"ProtoSize::add_string_field_repeated(total_size, {field_id_size}, it);" + return f"size.add_length_force({field_id_size}, it.size());" # For messages that need encoding, use the StringRef size field_id_size = self.calculate_field_id_size() - return f"ProtoSize::add_string_field(total_size, {field_id_size}, this->{self.field_name}_ref_.size());" + return f"size.add_length({field_id_size}, this->{self.field_name}_ref_.size());" def get_estimated_size(self) -> int: return self.calculate_field_id_size() + 8 # field ID + 8 bytes typical string @@ -804,7 +806,7 @@ class BytesType(TypeInfo): return o def get_size_calculation(self, name: str, force: bool = False) -> str: - return f"ProtoSize::add_bytes_field(total_size, {self.calculate_field_id_size()}, this->{self.field_name}_len_);" + return f"size.add_length({self.calculate_field_id_size()}, this->{self.field_name}_len_);" def get_estimated_size(self) -> int: return self.calculate_field_id_size() + 8 # field ID + 8 bytes typical bytes @@ -879,15 +881,11 @@ class FixedArrayBytesType(TypeInfo): field_id_size = self.calculate_field_id_size() if force: - # For repeated fields, always calculate size - return f"total_size += {field_id_size} + ProtoSize::varint(static_cast({length_field})) + {length_field};" + # For repeated fields, always calculate size (no zero check) + return f"size.add_length_force({field_id_size}, {length_field});" else: - # For non-repeated fields, skip if length is 0 (matching encode_string behavior) - return ( - f"if ({length_field} != 0) {{\n" - f" total_size += {field_id_size} + ProtoSize::varint(static_cast({length_field})) + {length_field};\n" - f"}}" - ) + # For non-repeated fields, add_length already checks for zero + return f"size.add_length({field_id_size}, {length_field});" def get_estimated_size(self) -> int: # Estimate based on typical BLE advertisement size @@ -914,7 +912,7 @@ class UInt32Type(TypeInfo): return o def get_size_calculation(self, name: str, force: bool = False) -> str: - return self._get_simple_size_calculation(name, force, "add_uint32_field") + return self._get_simple_size_calculation(name, force, "add_uint32") def get_estimated_size(self) -> int: return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint @@ -951,7 +949,7 @@ class EnumType(TypeInfo): def get_size_calculation(self, name: str, force: bool = False) -> str: return self._get_simple_size_calculation( - name, force, "add_enum_field", f"static_cast({name})" + name, force, "add_uint32", f"static_cast({name})" ) def get_estimated_size(self) -> int: @@ -973,7 +971,7 @@ class SFixed32Type(TypeInfo): def get_size_calculation(self, name: str, force: bool = False) -> str: field_id_size = self.calculate_field_id_size() - return f"ProtoSize::add_sfixed32_field(total_size, {field_id_size}, {name});" + return f"size.add_sfixed32({field_id_size}, {name});" def get_fixed_size_bytes(self) -> int: return 4 @@ -997,7 +995,7 @@ class SFixed64Type(TypeInfo): def get_size_calculation(self, name: str, force: bool = False) -> str: field_id_size = self.calculate_field_id_size() - return f"ProtoSize::add_sfixed64_field(total_size, {field_id_size}, {name});" + return f"size.add_sfixed64({field_id_size}, {name});" def get_fixed_size_bytes(self) -> int: return 8 @@ -1020,7 +1018,7 @@ class SInt32Type(TypeInfo): return o def get_size_calculation(self, name: str, force: bool = False) -> str: - return self._get_simple_size_calculation(name, force, "add_sint32_field") + return self._get_simple_size_calculation(name, force, "add_sint32") def get_estimated_size(self) -> int: return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint @@ -1040,7 +1038,7 @@ class SInt64Type(TypeInfo): return o def get_size_calculation(self, name: str, force: bool = False) -> str: - return self._get_simple_size_calculation(name, force, "add_sint64_field") + return self._get_simple_size_calculation(name, force, "add_sint64") def get_estimated_size(self) -> int: return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint @@ -1274,7 +1272,7 @@ class RepeatedTypeInfo(TypeInfo): if isinstance(self._ti, MessageType): # For repeated messages, use the dedicated helper that handles iteration internally field_id_size = self._ti.calculate_field_id_size() - o = f"ProtoSize::add_repeated_message(total_size, {field_id_size}, {name});" + o = f"size.add_repeated_message({field_id_size}, {name});" return o # For other repeated types, use the underlying type's size calculation with force=True @@ -1287,7 +1285,9 @@ class RepeatedTypeInfo(TypeInfo): field_id_size = self._ti.calculate_field_id_size() # Pre-calculate the total bytes per element bytes_per_element = field_id_size + num_bytes - o += f" total_size += {name}.size() * {bytes_per_element};\n" + o += ( + f" size.add_precalculated_size({name}.size() * {bytes_per_element});\n" + ) else: # Other types need the actual value o += f" for (const auto {'' if self._ti_is_bool else '&'}it : {name}) {{\n" @@ -1719,11 +1719,11 @@ def build_message_type( if needs_encode and encode: o = f"void {desc.name}::encode(ProtoWriteBuffer buffer) const {{" if len(encode) == 1 and len(encode[0]) + len(o) + 3 < 120: - o += f" {encode[0]} " + o += f" {encode[0]} }}\n" else: o += "\n" o += indent("\n".join(encode)) + "\n" - o += "}\n" + o += "}\n" cpp += o prot = "void encode(ProtoWriteBuffer buffer) const override;" public_content.append(prot) @@ -1731,17 +1731,17 @@ def build_message_type( # Add calculate_size method only if this message needs encoding and has fields if needs_encode and size_calc: - o = f"void {desc.name}::calculate_size(uint32_t &total_size) const {{" + o = f"void {desc.name}::calculate_size(ProtoSize &size) const {{" # For a single field, just inline it for simplicity if len(size_calc) == 1 and len(size_calc[0]) + len(o) + 3 < 120: - o += f" {size_calc[0]} " + o += f" {size_calc[0]} }}\n" else: # For multiple fields o += "\n" o += indent("\n".join(size_calc)) + "\n" - o += "}\n" + o += "}\n" cpp += o - prot = "void calculate_size(uint32_t &total_size) const override;" + prot = "void calculate_size(ProtoSize &size) const override;" public_content.append(prot) # If no fields to calculate size for or message doesn't need encoding, the default implementation in ProtoMessage will be used From 4f425c700ae0f596647f466212d7611f1f0d1497 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 28 Jul 2025 13:33:54 -1000 Subject: [PATCH 27/47] [esp32] Enable LWIP core locking on ESP-IDF to reduce socket operation overhead (#9857) --- esphome/components/esp32/__init__.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 2b4c4ff043..78bafcb790 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -571,6 +571,8 @@ CONF_SDKCONFIG_OPTIONS = "sdkconfig_options" CONF_ENABLE_LWIP_DHCP_SERVER = "enable_lwip_dhcp_server" CONF_ENABLE_LWIP_MDNS_QUERIES = "enable_lwip_mdns_queries" CONF_ENABLE_LWIP_BRIDGE_INTERFACE = "enable_lwip_bridge_interface" +CONF_ENABLE_LWIP_TCPIP_CORE_LOCKING = "enable_lwip_tcpip_core_locking" +CONF_ENABLE_LWIP_CHECK_THREAD_SAFETY = "enable_lwip_check_thread_safety" def _validate_idf_component(config: ConfigType) -> ConfigType: @@ -619,6 +621,12 @@ ESP_IDF_FRAMEWORK_SCHEMA = cv.All( cv.Optional( CONF_ENABLE_LWIP_BRIDGE_INTERFACE, default=False ): cv.boolean, + cv.Optional( + CONF_ENABLE_LWIP_TCPIP_CORE_LOCKING, default=True + ): cv.boolean, + cv.Optional( + CONF_ENABLE_LWIP_CHECK_THREAD_SAFETY, default=True + ): cv.boolean, } ), cv.Optional(CONF_COMPONENTS, default=[]): cv.ensure_list( @@ -785,6 +793,18 @@ async def to_code(config): if not advanced.get(CONF_ENABLE_LWIP_BRIDGE_INTERFACE, False): add_idf_sdkconfig_option("CONFIG_LWIP_BRIDGEIF_MAX_PORTS", 0) + # Apply LWIP core locking for better socket performance + # This is already enabled by default in Arduino framework, where it provides + # significant performance benefits. Our benchmarks show socket operations are + # 24-200% faster with core locking enabled: + # - select() on 4 sockets: ~190μs (Arduino/core locking) vs ~235μs (ESP-IDF default) + # - Up to 200% slower under load when all operations queue through tcpip_thread + # Enabling this makes ESP-IDF socket performance match Arduino framework. + if advanced.get(CONF_ENABLE_LWIP_TCPIP_CORE_LOCKING, True): + add_idf_sdkconfig_option("CONFIG_LWIP_TCPIP_CORE_LOCKING", True) + if advanced.get(CONF_ENABLE_LWIP_CHECK_THREAD_SAFETY, True): + add_idf_sdkconfig_option("CONFIG_LWIP_CHECK_THREAD_SAFETY", True) + cg.add_platformio_option("board_build.partitions", "partitions.csv") if CONF_PARTITIONS in config: add_extra_build_file( From 7657316a9249ee90d83cfbad92b17cdf4660346f Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Mon, 28 Jul 2025 18:34:52 -0500 Subject: [PATCH 28/47] [sensor] Add support for default filters (#9934) --- esphome/components/sensor/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index bcde623df2..20c6911d28 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -332,6 +332,7 @@ def sensor_schema( device_class: str = cv.UNDEFINED, state_class: str = cv.UNDEFINED, entity_category: str = cv.UNDEFINED, + filters: list = cv.UNDEFINED, ) -> cv.Schema: schema = {} @@ -346,6 +347,7 @@ def sensor_schema( (CONF_DEVICE_CLASS, device_class, validate_device_class), (CONF_STATE_CLASS, state_class, validate_state_class), (CONF_ENTITY_CATEGORY, entity_category, sensor_entity_category), + (CONF_FILTERS, filters, validate_filters), ]: if default is not cv.UNDEFINED: schema[cv.Optional(key, default=default)] = validator From 908891a096a52330216bf60ac6b9ce3f7d3af0f3 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Mon, 28 Jul 2025 18:35:11 -0500 Subject: [PATCH 29/47] [binary_sensor] Add support for default filters (#9935) --- esphome/components/binary_sensor/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/components/binary_sensor/__init__.py b/esphome/components/binary_sensor/__init__.py index e3931e3946..376a399637 100644 --- a/esphome/components/binary_sensor/__init__.py +++ b/esphome/components/binary_sensor/__init__.py @@ -516,6 +516,7 @@ def binary_sensor_schema( icon: str = cv.UNDEFINED, entity_category: str = cv.UNDEFINED, device_class: str = cv.UNDEFINED, + filters: list = cv.UNDEFINED, ) -> cv.Schema: schema = {} @@ -527,6 +528,7 @@ def binary_sensor_schema( (CONF_ICON, icon, cv.icon), (CONF_ENTITY_CATEGORY, entity_category, cv.entity_category), (CONF_DEVICE_CLASS, device_class, validate_device_class), + (CONF_FILTERS, filters, validate_filters), ]: if default is not cv.UNDEFINED: schema[cv.Optional(key, default=default)] = validator From f5f0a01a850e0dcf59c13a7396cde7faf1ecaf18 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Mon, 28 Jul 2025 18:35:40 -0500 Subject: [PATCH 30/47] [text_sensor] Add support for default filters (#9936) --- esphome/components/text_sensor/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/components/text_sensor/__init__.py b/esphome/components/text_sensor/__init__.py index abb2dcae6c..0341ab2f71 100644 --- a/esphome/components/text_sensor/__init__.py +++ b/esphome/components/text_sensor/__init__.py @@ -162,6 +162,7 @@ def text_sensor_schema( device_class: str = cv.UNDEFINED, entity_category: str = cv.UNDEFINED, icon: str = cv.UNDEFINED, + filters: list = cv.UNDEFINED, ) -> cv.Schema: schema = {} @@ -172,6 +173,7 @@ def text_sensor_schema( (CONF_ICON, icon, cv.icon), (CONF_DEVICE_CLASS, device_class, validate_device_class), (CONF_ENTITY_CATEGORY, entity_category, cv.entity_category), + (CONF_FILTERS, filters, validate_filters), ]: if default is not cv.UNDEFINED: schema[cv.Optional(key, default=default)] = validator From f733c43dec39596b12936ff6be5c6c55169cdb4d Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Mon, 28 Jul 2025 23:59:58 -0400 Subject: [PATCH 31/47] [heatpumpir] Fix issue with IRremoteESP8266 being included on ESP32 (#9950) --- esphome/components/heatpumpir/climate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/heatpumpir/climate.py b/esphome/components/heatpumpir/climate.py index d2b907d437..4f83bf2435 100644 --- a/esphome/components/heatpumpir/climate.py +++ b/esphome/components/heatpumpir/climate.py @@ -127,5 +127,5 @@ async def to_code(config): cg.add(var.set_min_temperature(config[CONF_MIN_TEMPERATURE])) cg.add_library("tonia/HeatpumpIR", "1.0.37") - if CORE.is_libretiny: + if CORE.is_libretiny or CORE.is_esp32: CORE.add_platformio_option("lib_ignore", "IRremoteESP8266") From 9d43ddd6f110b447f4f0cd5c56a46d36613001b7 Mon Sep 17 00:00:00 2001 From: rwrozelle Date: Tue, 29 Jul 2025 00:25:17 -0400 Subject: [PATCH 32/47] Openthread add Teardown (#9275) Co-authored-by: mc Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/openthread/openthread.cpp | 42 +++++++++++++------ esphome/components/openthread/openthread.h | 3 ++ .../components/openthread/openthread_esp.cpp | 3 ++ 3 files changed, 36 insertions(+), 12 deletions(-) diff --git a/esphome/components/openthread/openthread.cpp b/esphome/components/openthread/openthread.cpp index 800128745c..322ff43238 100644 --- a/esphome/components/openthread/openthread.cpp +++ b/esphome/components/openthread/openthread.cpp @@ -1,6 +1,9 @@ #include "esphome/core/defines.h" #ifdef USE_OPENTHREAD #include "openthread.h" +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0) +#include "esp_openthread.h" +#endif #include @@ -28,18 +31,6 @@ OpenThreadComponent *global_openthread_component = // NOLINT(cppcoreguidelines- OpenThreadComponent::OpenThreadComponent() { global_openthread_component = this; } -OpenThreadComponent::~OpenThreadComponent() { - auto lock = InstanceLock::try_acquire(100); - if (!lock) { - ESP_LOGW(TAG, "Failed to acquire OpenThread lock in destructor, leaking memory"); - return; - } - otInstance *instance = lock->get_instance(); - otSrpClientClearHostAndServices(instance); - otSrpClientBuffersFreeAllServices(instance); - global_openthread_component = nullptr; -} - bool OpenThreadComponent::is_connected() { auto lock = InstanceLock::try_acquire(100); if (!lock) { @@ -199,6 +190,33 @@ void *OpenThreadSrpComponent::pool_alloc_(size_t size) { void OpenThreadSrpComponent::set_mdns(esphome::mdns::MDNSComponent *mdns) { this->mdns_ = mdns; } +bool OpenThreadComponent::teardown() { + if (!this->teardown_started_) { + this->teardown_started_ = true; + ESP_LOGD(TAG, "Clear Srp"); + auto lock = InstanceLock::try_acquire(100); + if (!lock) { + ESP_LOGW(TAG, "Failed to acquire OpenThread lock during teardown, leaking memory"); + return true; + } + otInstance *instance = lock->get_instance(); + otSrpClientClearHostAndServices(instance); + otSrpClientBuffersFreeAllServices(instance); + global_openthread_component = nullptr; +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0) + ESP_LOGD(TAG, "Exit main loop "); + int error = esp_openthread_mainloop_exit(); + if (error != ESP_OK) { + ESP_LOGW(TAG, "Failed attempt to stop main loop %d", error); + this->teardown_complete_ = true; + } +#else + this->teardown_complete_ = true; +#endif + } + return this->teardown_complete_; +} + } // namespace openthread } // namespace esphome diff --git a/esphome/components/openthread/openthread.h b/esphome/components/openthread/openthread.h index 77fd58851a..a0ea1b3f3a 100644 --- a/esphome/components/openthread/openthread.h +++ b/esphome/components/openthread/openthread.h @@ -21,6 +21,7 @@ class OpenThreadComponent : public Component { OpenThreadComponent(); ~OpenThreadComponent(); void setup() override; + bool teardown() override; float get_setup_priority() const override { return setup_priority::WIFI; } bool is_connected(); @@ -30,6 +31,8 @@ class OpenThreadComponent : public Component { protected: std::optional get_omr_address_(InstanceLock &lock); + bool teardown_started_{false}; + bool teardown_complete_{false}; }; extern OpenThreadComponent *global_openthread_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) diff --git a/esphome/components/openthread/openthread_esp.cpp b/esphome/components/openthread/openthread_esp.cpp index f495027172..b11b7ad34a 100644 --- a/esphome/components/openthread/openthread_esp.cpp +++ b/esphome/components/openthread/openthread_esp.cpp @@ -143,10 +143,13 @@ void OpenThreadComponent::ot_main() { esp_openthread_launch_mainloop(); // Clean up + esp_openthread_deinit(); esp_openthread_netif_glue_deinit(); esp_netif_destroy(openthread_netif); esp_vfs_eventfd_unregister(); + this->teardown_complete_ = true; + vTaskDelete(NULL); } network::IPAddresses OpenThreadComponent::get_ip_addresses() { From 4ff3137c0d2279f5885b2905f543cf69993853f3 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Tue, 29 Jul 2025 00:21:52 -0500 Subject: [PATCH 33/47] [gps] Fix slow parsing (#9953) --- esphome/components/gps/gps.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/gps/gps.cpp b/esphome/components/gps/gps.cpp index cbbd36887b..65cddcd984 100644 --- a/esphome/components/gps/gps.cpp +++ b/esphome/components/gps/gps.cpp @@ -52,7 +52,7 @@ void GPS::update() { void GPS::loop() { while (this->available() > 0 && !this->has_time_) { if (!this->tiny_gps_.encode(this->read())) { - return; + continue; } if (this->tiny_gps_.location.isUpdated()) { this->latitude_ = this->tiny_gps_.location.lat(); From 6d30269565f086ef1c8f2edf1c98ef81ab36911c Mon Sep 17 00:00:00 2001 From: Djordje Mandic <6750655+DjordjeMandic@users.noreply.github.com> Date: Tue, 29 Jul 2025 07:22:44 +0200 Subject: [PATCH 34/47] [output] Add `set_min_power` & `set_max_power` actions for `FloatOutput` (#8934) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/output/__init__.py | 38 ++++++++++++++++++++++++++ esphome/components/output/automation.h | 24 ++++++++++++++++ tests/components/output/common.yaml | 6 ++++ 3 files changed, 68 insertions(+) diff --git a/esphome/components/output/__init__.py b/esphome/components/output/__init__.py index 78bfa045e1..bde106b085 100644 --- a/esphome/components/output/__init__.py +++ b/esphome/components/output/__init__.py @@ -43,6 +43,8 @@ FloatOutputPtr = FloatOutput.operator("ptr") TurnOffAction = output_ns.class_("TurnOffAction", automation.Action) TurnOnAction = output_ns.class_("TurnOnAction", automation.Action) SetLevelAction = output_ns.class_("SetLevelAction", automation.Action) +SetMinPowerAction = output_ns.class_("SetMinPowerAction", automation.Action) +SetMaxPowerAction = output_ns.class_("SetMaxPowerAction", automation.Action) async def setup_output_platform_(obj, config): @@ -104,6 +106,42 @@ async def output_set_level_to_code(config, action_id, template_arg, args): return var +@automation.register_action( + "output.set_min_power", + SetMinPowerAction, + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(FloatOutput), + cv.Required(CONF_MIN_POWER): cv.templatable(cv.percentage), + } + ), +) +async def output_set_min_power_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, paren) + template_ = await cg.templatable(config[CONF_MIN_POWER], args, float) + cg.add(var.set_min_power(template_)) + return var + + +@automation.register_action( + "output.set_max_power", + SetMaxPowerAction, + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(FloatOutput), + cv.Required(CONF_MAX_POWER): cv.templatable(cv.percentage), + } + ), +) +async def output_set_max_power_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, paren) + template_ = await cg.templatable(config[CONF_MAX_POWER], args, float) + cg.add(var.set_max_power(template_)) + return var + + async def to_code(config): cg.add_define("USE_OUTPUT") cg.add_global(output_ns.using) diff --git a/esphome/components/output/automation.h b/esphome/components/output/automation.h index 51c2849702..de84bb91ca 100644 --- a/esphome/components/output/automation.h +++ b/esphome/components/output/automation.h @@ -40,5 +40,29 @@ template class SetLevelAction : public Action { FloatOutput *output_; }; +template class SetMinPowerAction : public Action { + public: + SetMinPowerAction(FloatOutput *output) : output_(output) {} + + TEMPLATABLE_VALUE(float, min_power) + + void play(Ts... x) override { this->output_->set_min_power(this->min_power_.value(x...)); } + + protected: + FloatOutput *output_; +}; + +template class SetMaxPowerAction : public Action { + public: + SetMaxPowerAction(FloatOutput *output) : output_(output) {} + + TEMPLATABLE_VALUE(float, max_power) + + void play(Ts... x) override { this->output_->set_max_power(this->max_power_.value(x...)); } + + protected: + FloatOutput *output_; +}; + } // namespace output } // namespace esphome diff --git a/tests/components/output/common.yaml b/tests/components/output/common.yaml index 5f31ae50a8..81d802e9bf 100644 --- a/tests/components/output/common.yaml +++ b/tests/components/output/common.yaml @@ -6,6 +6,12 @@ esphome: - output.set_level: id: light_output_1 level: 50% + - output.set_min_power: + id: light_output_1 + min_power: 20% + - output.set_max_power: + id: light_output_1 + max_power: 80% output: - platform: ${output_platform} From 3d5b602288bd922b898b53429079e8aae8217c0a Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Tue, 29 Jul 2025 01:52:34 -0400 Subject: [PATCH 35/47] [esp32] Bump platform to 54.03.21-1 and add support for tagged releases (#9926) Co-authored-by: J. Nick Koston --- .clang-tidy.hash | 2 +- esphome/components/esp32/__init__.py | 12 ++++++------ esphome/config_validation.py | 2 ++ platformio.ini | 4 ++-- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/.clang-tidy.hash b/.clang-tidy.hash index 02b528b0d6..5dd779effb 100644 --- a/.clang-tidy.hash +++ b/.clang-tidy.hash @@ -1 +1 @@ -b7056e39f1484500ca2d237068670b789fe9241786b48da0681d646b25af05d5 +f84518ea4140c194b21cc516aae05aaa0cf876ab866f89e22e91842df46333ed diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 78bafcb790..4ab85a55cd 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -313,7 +313,7 @@ def _format_framework_espidf_version( RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(3, 2, 1) # The platform-espressif32 version to use for arduino frameworks # - https://github.com/pioarduino/platform-espressif32/releases -ARDUINO_PLATFORM_VERSION = cv.Version(54, 3, 21) +ARDUINO_PLATFORM_VERSION = cv.Version(54, 3, 21, "1") # The default/recommended esp-idf framework version # - https://github.com/espressif/esp-idf/releases @@ -322,7 +322,7 @@ RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(5, 4, 2) # The platformio/espressif32 version to use for esp-idf frameworks # - https://github.com/platformio/platform-espressif32/releases # - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32 -ESP_IDF_PLATFORM_VERSION = cv.Version(54, 3, 21) +ESP_IDF_PLATFORM_VERSION = cv.Version(54, 3, 21, "1") # List based on https://registry.platformio.org/tools/platformio/framework-espidf/versions SUPPORTED_PLATFORMIO_ESP_IDF_5X = [ @@ -468,10 +468,10 @@ def _parse_platform_version(value): try: ver = cv.Version.parse(cv.version_number(value)) if ver.major >= 50: # a pioarduino version - if "-" in value: - # maybe a release candidate?...definitely not our default, just use it as-is... - return f"https://github.com/pioarduino/platform-espressif32/releases/download/{value}/platform-espressif32.zip" - return f"https://github.com/pioarduino/platform-espressif32/releases/download/{ver.major}.{ver.minor:02d}.{ver.patch:02d}/platform-espressif32.zip" + release = f"{ver.major}.{ver.minor:02d}.{ver.patch:02d}" + if ver.extra: + release += f"-{ver.extra}" + return f"https://github.com/pioarduino/platform-espressif32/releases/download/{release}/platform-espressif32.zip" # if platform version is a valid version constraint, prefix the default package cv.platformio_version_constraint(value) return f"platformio/espressif32@{value}" diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 1a4976e235..a79f8cd17c 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -291,6 +291,8 @@ class Version: extra: str = "" def __str__(self): + if self.extra: + return f"{self.major}.{self.minor}.{self.patch}-{self.extra}" return f"{self.major}.{self.minor}.{self.patch}" @classmethod diff --git a/platformio.ini b/platformio.ini index 5956ae8117..ab0774b29f 100644 --- a/platformio.ini +++ b/platformio.ini @@ -125,7 +125,7 @@ extra_scripts = post:esphome/components/esp8266/post_build.py.script ; This are common settings for the ESP32 (all variants) using Arduino. [common:esp32-arduino] extends = common:arduino -platform = https://github.com/pioarduino/platform-espressif32/releases/download/54.03.21/platform-espressif32.zip +platform = https://github.com/pioarduino/platform-espressif32/releases/download/54.03.21-1/platform-espressif32.zip platform_packages = pioarduino/framework-arduinoespressif32@https://github.com/espressif/arduino-esp32/releases/download/3.2.1/esp32-3.2.1.zip @@ -161,7 +161,7 @@ extra_scripts = post:esphome/components/esp32/post_build.py.script ; This are common settings for the ESP32 (all variants) using IDF. [common:esp32-idf] extends = common:idf -platform = https://github.com/pioarduino/platform-espressif32/releases/download/54.03.21/platform-espressif32.zip +platform = https://github.com/pioarduino/platform-espressif32/releases/download/54.03.21-1/platform-espressif32.zip platform_packages = pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v5.4.2/esp-idf-v5.4.2.zip From 5f7c2f771f156c29fe24da1f4033bf33e5904d0b Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Tue, 29 Jul 2025 16:20:37 +1000 Subject: [PATCH 36/47] [adc] Enable ADC on ESP32-P4 (#9954) --- esphome/components/adc/__init__.py | 20 +++++++++++++++++++- esphome/components/adc/adc_sensor.h | 4 ++-- esphome/components/adc/adc_sensor_esp32.cpp | 13 ++++++------- tests/components/adc/test.esp32-p4-idf.yaml | 6 ++++++ 4 files changed, 33 insertions(+), 10 deletions(-) create mode 100644 tests/components/adc/test.esp32-p4-idf.yaml diff --git a/esphome/components/adc/__init__.py b/esphome/components/adc/__init__.py index efe3b190af..1232d9677f 100644 --- a/esphome/components/adc/__init__.py +++ b/esphome/components/adc/__init__.py @@ -1,6 +1,6 @@ from esphome import pins import esphome.codegen as cg -from esphome.components.esp32 import get_esp32_variant +from esphome.components.esp32 import VARIANT_ESP32P4, get_esp32_variant from esphome.components.esp32.const import ( VARIANT_ESP32, VARIANT_ESP32C2, @@ -140,6 +140,16 @@ ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = { 9: adc_channel_t.ADC_CHANNEL_8, 10: adc_channel_t.ADC_CHANNEL_9, }, + VARIANT_ESP32P4: { + 16: adc_channel_t.ADC_CHANNEL_0, + 17: adc_channel_t.ADC_CHANNEL_1, + 18: adc_channel_t.ADC_CHANNEL_2, + 19: adc_channel_t.ADC_CHANNEL_3, + 20: adc_channel_t.ADC_CHANNEL_4, + 21: adc_channel_t.ADC_CHANNEL_5, + 22: adc_channel_t.ADC_CHANNEL_6, + 23: adc_channel_t.ADC_CHANNEL_7, + }, } # pin to adc2 channel mapping @@ -198,6 +208,14 @@ ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = { 19: adc_channel_t.ADC_CHANNEL_8, 20: adc_channel_t.ADC_CHANNEL_9, }, + VARIANT_ESP32P4: { + 49: adc_channel_t.ADC_CHANNEL_0, + 50: adc_channel_t.ADC_CHANNEL_1, + 51: adc_channel_t.ADC_CHANNEL_2, + 52: adc_channel_t.ADC_CHANNEL_3, + 53: adc_channel_t.ADC_CHANNEL_4, + 54: adc_channel_t.ADC_CHANNEL_5, + }, } diff --git a/esphome/components/adc/adc_sensor.h b/esphome/components/adc/adc_sensor.h index a60272a1f7..00a703191e 100644 --- a/esphome/components/adc/adc_sensor.h +++ b/esphome/components/adc/adc_sensor.h @@ -136,8 +136,8 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage adc_oneshot_unit_handle_t adc_handle_{nullptr}; adc_cali_handle_t calibration_handle_{nullptr}; adc_atten_t attenuation_{ADC_ATTEN_DB_0}; - adc_channel_t channel_; - adc_unit_t adc_unit_; + adc_channel_t channel_{}; + adc_unit_t adc_unit_{}; struct SetupFlags { uint8_t init_complete : 1; uint8_t config_complete : 1; diff --git a/esphome/components/adc/adc_sensor_esp32.cpp b/esphome/components/adc/adc_sensor_esp32.cpp index 4f0ffbdc38..9905475b1e 100644 --- a/esphome/components/adc/adc_sensor_esp32.cpp +++ b/esphome/components/adc/adc_sensor_esp32.cpp @@ -72,10 +72,9 @@ void ADCSensor::setup() { // Initialize ADC calibration if (this->calibration_handle_ == nullptr) { adc_cali_handle_t handle = nullptr; - esp_err_t err; #if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \ - USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 + USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 // RISC-V variants and S3 use curve fitting calibration adc_cali_curve_fitting_config_t cali_config = {}; // Zero initialize first #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) @@ -187,7 +186,7 @@ float ADCSensor::sample_fixed_attenuation_() { ESP_LOGW(TAG, "ADC calibration conversion failed with error %d, disabling calibration", err); if (this->calibration_handle_ != nullptr) { #if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \ - USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 + USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 adc_cali_delete_scheme_curve_fitting(this->calibration_handle_); #else // Other ESP32 variants use line fitting calibration adc_cali_delete_scheme_line_fitting(this->calibration_handle_); @@ -220,7 +219,7 @@ float ADCSensor::sample_autorange_() { if (this->calibration_handle_ != nullptr) { // Delete old calibration handle #if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \ - USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 + USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 adc_cali_delete_scheme_curve_fitting(this->calibration_handle_); #else adc_cali_delete_scheme_line_fitting(this->calibration_handle_); @@ -232,7 +231,7 @@ float ADCSensor::sample_autorange_() { adc_cali_handle_t handle = nullptr; #if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \ - USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 + USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 adc_cali_curve_fitting_config_t cali_config = {}; #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) cali_config.chan = this->channel_; @@ -261,7 +260,7 @@ float ADCSensor::sample_autorange_() { ESP_LOGW(TAG, "ADC read failed in autorange with error %d", err); if (handle != nullptr) { #if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \ - USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 + USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 adc_cali_delete_scheme_curve_fitting(handle); #else adc_cali_delete_scheme_line_fitting(handle); @@ -281,7 +280,7 @@ float ADCSensor::sample_autorange_() { } // Clean up calibration handle #if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \ - USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 + USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 adc_cali_delete_scheme_curve_fitting(handle); #else adc_cali_delete_scheme_line_fitting(handle); diff --git a/tests/components/adc/test.esp32-p4-idf.yaml b/tests/components/adc/test.esp32-p4-idf.yaml new file mode 100644 index 0000000000..97844cf398 --- /dev/null +++ b/tests/components/adc/test.esp32-p4-idf.yaml @@ -0,0 +1,6 @@ +packages: + base: !include common.yaml + +sensor: + - id: !extend my_sensor + pin: GPIO50 From ace375944c17a3c8bd33d1b051e100b986cfdbf4 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 29 Jul 2025 18:44:45 +1200 Subject: [PATCH 37/47] [esp32] Fix post build (#9951) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- esphome/components/esp32/post_build.py.script | 64 ++++++++++++------- 1 file changed, 40 insertions(+), 24 deletions(-) diff --git a/esphome/components/esp32/post_build.py.script b/esphome/components/esp32/post_build.py.script index 6e0e439011..586f12e00b 100644 --- a/esphome/components/esp32/post_build.py.script +++ b/esphome/components/esp32/post_build.py.script @@ -1,10 +1,11 @@ -Import("env") +Import("env") # noqa: F821 + +import itertools # noqa: E402 +import json # noqa: E402 +import os # noqa: E402 +import pathlib # noqa: E402 +import shutil # noqa: E402 -import os -import json -import shutil -import pathlib -import itertools def merge_factory_bin(source, target, env): """ @@ -25,7 +26,9 @@ def merge_factory_bin(source, target, env): try: with flasher_args_path.open() as f: flash_data = json.load(f) - for addr, fname in sorted(flash_data["flash_files"].items(), key=lambda kv: int(kv[0], 16)): + for addr, fname in sorted( + flash_data["flash_files"].items(), key=lambda kv: int(kv[0], 16) + ): file_path = pathlib.Path(fname) if file_path.exists(): sections.append((addr, str(file_path))) @@ -40,20 +43,27 @@ def merge_factory_bin(source, target, env): if flash_images: print("Using FLASH_EXTRA_IMAGES from PlatformIO environment") # flatten any nested lists - flat = list(itertools.chain.from_iterable( - x if isinstance(x, (list, tuple)) else [x] for x in flash_images - )) + flat = list( + itertools.chain.from_iterable( + x if isinstance(x, (list, tuple)) else [x] for x in flash_images + ) + ) entries = [env.subst(x) for x in flat] for i in range(0, len(entries) - 1, 2): addr, fname = entries[i], entries[i + 1] if isinstance(fname, (list, tuple)): - print(f"Warning: Skipping malformed FLASH_EXTRA_IMAGES entry: {fname}") + print( + f"Warning: Skipping malformed FLASH_EXTRA_IMAGES entry: {fname}" + ) continue file_path = pathlib.Path(str(fname)) if file_path.exists(): - sections.append((addr, str(file_path))) + sections.append((addr, file_path)) else: print(f"Info: {file_path.name} not found — skipping") + if sections: + # Append main firmware to sections + sections.append(("0x10000", firmware_path)) # 3. Final fallback: guess standard image locations if not sections: @@ -62,11 +72,11 @@ def merge_factory_bin(source, target, env): ("0x0", build_dir / "bootloader" / "bootloader.bin"), ("0x8000", build_dir / "partition_table" / "partition-table.bin"), ("0xe000", build_dir / "ota_data_initial.bin"), - ("0x10000", firmware_path) + ("0x10000", firmware_path), ] for addr, file_path in guesses: if file_path.exists(): - sections.append((addr, str(file_path))) + sections.append((addr, file_path)) else: print(f"Info: {file_path.name} not found — skipping") @@ -76,21 +86,25 @@ def merge_factory_bin(source, target, env): return output_path = firmware_path.with_suffix(".factory.bin") + python_exe = f'"{env.subst("$PYTHONEXE")}"' cmd = [ - "--chip", chip, + python_exe, + "-m", + "esptool", + "--chip", + chip, "merge_bin", - "--flash_size", flash_size, - "--output", str(output_path) + "--flash_size", + flash_size, + "--output", + str(output_path), ] for addr, file_path in sections: - cmd += [addr, file_path] + cmd += [addr, str(file_path)] print(f"Merging binaries into {output_path}") result = env.Execute( - env.VerboseAction( - f"{env.subst('$PYTHONEXE')} -m esptool " + " ".join(cmd), - "Merging binaries with esptool" - ) + env.VerboseAction(" ".join(cmd), "Merging binaries with esptool") ) if result == 0: @@ -98,6 +112,7 @@ def merge_factory_bin(source, target, env): else: print(f"Error: esptool merge_bin failed with code {result}") + def esp32_copy_ota_bin(source, target, env): """ Copy the main firmware to a .ota.bin file for compatibility with ESPHome OTA tools. @@ -107,6 +122,7 @@ def esp32_copy_ota_bin(source, target, env): shutil.copyfile(firmware_name, new_file_name) print(f"Copied firmware to {new_file_name}") + # Run merge first, then ota copy second -env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", merge_factory_bin) -env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_copy_ota_bin) +env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", merge_factory_bin) # noqa: F821 +env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_copy_ota_bin) # noqa: F821 From 1f0c606be49940d252cdbe90544a8bcd7c95529e Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Tue, 29 Jul 2025 17:32:45 +1000 Subject: [PATCH 38/47] [component] Revert setup messages to LOG_CONFIG level (#9956) --- esphome/core/component.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index 513b0a7ba2..40cda17ca3 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -161,7 +161,7 @@ void Component::call() { this->call_setup(); #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG uint32_t setup_time = millis() - start_time; - ESP_LOGD(TAG, "Setup %s took %ums", this->get_component_source(), (unsigned) setup_time); + ESP_LOGCONFIG(TAG, "Setup %s took %ums", this->get_component_source(), (unsigned) setup_time); #endif break; } From a7dd849a8e4714efb1f304d2e872d1a7013200ab Mon Sep 17 00:00:00 2001 From: rwrozelle Date: Tue, 29 Jul 2025 13:00:47 -0400 Subject: [PATCH 39/47] Media player API enumeration alignment and feature flags (#9949) Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Co-authored-by: J. Nick Koston --- esphome/components/api/api.proto | 10 +++++ esphome/components/api/api_connection.cpp | 1 + esphome/components/api/api_pb2.cpp | 2 + esphome/components/api/api_pb2.h | 11 +++++- esphome/components/api/api_pb2_dump.cpp | 17 ++++++++ .../components/media_player/media_player.h | 39 ++++++++++++++++++- 6 files changed, 78 insertions(+), 2 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index e0e1602fcb..4cf275c7f7 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -1297,6 +1297,7 @@ enum MediaPlayerState { MEDIA_PLAYER_STATE_IDLE = 1; MEDIA_PLAYER_STATE_PLAYING = 2; MEDIA_PLAYER_STATE_PAUSED = 3; + MEDIA_PLAYER_STATE_ANNOUNCING = 4; } enum MediaPlayerCommand { MEDIA_PLAYER_COMMAND_PLAY = 0; @@ -1304,6 +1305,13 @@ enum MediaPlayerCommand { MEDIA_PLAYER_COMMAND_STOP = 2; MEDIA_PLAYER_COMMAND_MUTE = 3; MEDIA_PLAYER_COMMAND_UNMUTE = 4; + MEDIA_PLAYER_COMMAND_TOGGLE = 5; + MEDIA_PLAYER_COMMAND_VOLUME_UP = 6; + MEDIA_PLAYER_COMMAND_VOLUME_DOWN = 7; + MEDIA_PLAYER_COMMAND_ENQUEUE = 8; + MEDIA_PLAYER_COMMAND_REPEAT_ONE = 9; + MEDIA_PLAYER_COMMAND_REPEAT_OFF = 10; + MEDIA_PLAYER_COMMAND_CLEAR_PLAYLIST = 11; } enum MediaPlayerFormatPurpose { MEDIA_PLAYER_FORMAT_PURPOSE_DEFAULT = 0; @@ -1338,6 +1346,8 @@ message ListEntitiesMediaPlayerResponse { repeated MediaPlayerSupportedFormat supported_formats = 9; uint32 device_id = 10 [(field_ifdef) = "USE_DEVICES"]; + + uint32 feature_flags = 11; } message MediaPlayerStateResponse { option (id) = 64; diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index cd27087fe8..6f71ab0929 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1008,6 +1008,7 @@ uint16_t APIConnection::try_send_media_player_info(EntityBase *entity, APIConnec ListEntitiesMediaPlayerResponse msg; auto traits = media_player->get_traits(); msg.supports_pause = traits.get_supports_pause(); + msg.feature_flags = traits.get_feature_flags(); for (auto &supported_format : traits.get_supported_formats()) { msg.supported_formats.emplace_back(); auto &media_format = msg.supported_formats.back(); diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index f6f39f901f..35ba1e6c30 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -1725,6 +1725,7 @@ void ListEntitiesMediaPlayerResponse::encode(ProtoWriteBuffer buffer) const { #ifdef USE_DEVICES buffer.encode_uint32(10, this->device_id); #endif + buffer.encode_uint32(11, this->feature_flags); } void ListEntitiesMediaPlayerResponse::calculate_size(ProtoSize &size) const { size.add_length(1, this->object_id_ref_.size()); @@ -1740,6 +1741,7 @@ void ListEntitiesMediaPlayerResponse::calculate_size(ProtoSize &size) const { #ifdef USE_DEVICES size.add_uint32(1, this->device_id); #endif + size.add_uint32(1, this->feature_flags); } void MediaPlayerStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index f637e44df3..c994031bcb 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -149,6 +149,7 @@ enum MediaPlayerState : uint32_t { MEDIA_PLAYER_STATE_IDLE = 1, MEDIA_PLAYER_STATE_PLAYING = 2, MEDIA_PLAYER_STATE_PAUSED = 3, + MEDIA_PLAYER_STATE_ANNOUNCING = 4, }; enum MediaPlayerCommand : uint32_t { MEDIA_PLAYER_COMMAND_PLAY = 0, @@ -156,6 +157,13 @@ enum MediaPlayerCommand : uint32_t { MEDIA_PLAYER_COMMAND_STOP = 2, MEDIA_PLAYER_COMMAND_MUTE = 3, MEDIA_PLAYER_COMMAND_UNMUTE = 4, + MEDIA_PLAYER_COMMAND_TOGGLE = 5, + MEDIA_PLAYER_COMMAND_VOLUME_UP = 6, + MEDIA_PLAYER_COMMAND_VOLUME_DOWN = 7, + MEDIA_PLAYER_COMMAND_ENQUEUE = 8, + MEDIA_PLAYER_COMMAND_REPEAT_ONE = 9, + MEDIA_PLAYER_COMMAND_REPEAT_OFF = 10, + MEDIA_PLAYER_COMMAND_CLEAR_PLAYLIST = 11, }; enum MediaPlayerFormatPurpose : uint32_t { MEDIA_PLAYER_FORMAT_PURPOSE_DEFAULT = 0, @@ -1683,12 +1691,13 @@ class MediaPlayerSupportedFormat : public ProtoMessage { class ListEntitiesMediaPlayerResponse : public InfoResponseProtoMessage { public: static constexpr uint8_t MESSAGE_TYPE = 63; - static constexpr uint8_t ESTIMATED_SIZE = 76; + static constexpr uint8_t ESTIMATED_SIZE = 80; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "list_entities_media_player_response"; } #endif bool supports_pause{false}; std::vector supported_formats{}; + uint32_t feature_flags{0}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP diff --git a/esphome/components/api/api_pb2_dump.cpp b/esphome/components/api/api_pb2_dump.cpp index aca60464a3..5e17f6148a 100644 --- a/esphome/components/api/api_pb2_dump.cpp +++ b/esphome/components/api/api_pb2_dump.cpp @@ -383,6 +383,8 @@ template<> const char *proto_enum_to_string(enums::Medi return "MEDIA_PLAYER_STATE_PLAYING"; case enums::MEDIA_PLAYER_STATE_PAUSED: return "MEDIA_PLAYER_STATE_PAUSED"; + case enums::MEDIA_PLAYER_STATE_ANNOUNCING: + return "MEDIA_PLAYER_STATE_ANNOUNCING"; default: return "UNKNOWN"; } @@ -399,6 +401,20 @@ template<> const char *proto_enum_to_string(enums::Me return "MEDIA_PLAYER_COMMAND_MUTE"; case enums::MEDIA_PLAYER_COMMAND_UNMUTE: return "MEDIA_PLAYER_COMMAND_UNMUTE"; + case enums::MEDIA_PLAYER_COMMAND_TOGGLE: + return "MEDIA_PLAYER_COMMAND_TOGGLE"; + case enums::MEDIA_PLAYER_COMMAND_VOLUME_UP: + return "MEDIA_PLAYER_COMMAND_VOLUME_UP"; + case enums::MEDIA_PLAYER_COMMAND_VOLUME_DOWN: + return "MEDIA_PLAYER_COMMAND_VOLUME_DOWN"; + case enums::MEDIA_PLAYER_COMMAND_ENQUEUE: + return "MEDIA_PLAYER_COMMAND_ENQUEUE"; + case enums::MEDIA_PLAYER_COMMAND_REPEAT_ONE: + return "MEDIA_PLAYER_COMMAND_REPEAT_ONE"; + case enums::MEDIA_PLAYER_COMMAND_REPEAT_OFF: + return "MEDIA_PLAYER_COMMAND_REPEAT_OFF"; + case enums::MEDIA_PLAYER_COMMAND_CLEAR_PLAYLIST: + return "MEDIA_PLAYER_COMMAND_CLEAR_PLAYLIST"; default: return "UNKNOWN"; } @@ -1466,6 +1482,7 @@ void ListEntitiesMediaPlayerResponse::dump_to(std::string &out) const { #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + dump_field(out, "feature_flags", this->feature_flags); } void MediaPlayerStateResponse::dump_to(std::string &out) const { MessageDumpHelper helper(out, "MediaPlayerStateResponse"); diff --git a/esphome/components/media_player/media_player.h b/esphome/components/media_player/media_player.h index ee5889901c..98aa8f609d 100644 --- a/esphome/components/media_player/media_player.h +++ b/esphome/components/media_player/media_player.h @@ -6,12 +6,38 @@ namespace esphome { namespace media_player { +enum MediaPlayerEntityFeature : uint32_t { + PAUSE = 1 << 0, + SEEK = 1 << 1, + VOLUME_SET = 1 << 2, + VOLUME_MUTE = 1 << 3, + PREVIOUS_TRACK = 1 << 4, + NEXT_TRACK = 1 << 5, + + TURN_ON = 1 << 7, + TURN_OFF = 1 << 8, + PLAY_MEDIA = 1 << 9, + VOLUME_STEP = 1 << 10, + SELECT_SOURCE = 1 << 11, + STOP = 1 << 12, + CLEAR_PLAYLIST = 1 << 13, + PLAY = 1 << 14, + SHUFFLE_SET = 1 << 15, + SELECT_SOUND_MODE = 1 << 16, + BROWSE_MEDIA = 1 << 17, + REPEAT_SET = 1 << 18, + GROUPING = 1 << 19, + MEDIA_ANNOUNCE = 1 << 20, + MEDIA_ENQUEUE = 1 << 21, + SEARCH_MEDIA = 1 << 22, +}; + enum MediaPlayerState : uint8_t { MEDIA_PLAYER_STATE_NONE = 0, MEDIA_PLAYER_STATE_IDLE = 1, MEDIA_PLAYER_STATE_PLAYING = 2, MEDIA_PLAYER_STATE_PAUSED = 3, - MEDIA_PLAYER_STATE_ANNOUNCING = 4 + MEDIA_PLAYER_STATE_ANNOUNCING = 4, }; const char *media_player_state_to_string(MediaPlayerState state); @@ -56,6 +82,17 @@ class MediaPlayerTraits { std::vector &get_supported_formats() { return this->supported_formats_; } + uint32_t get_feature_flags() const { + uint32_t flags = 0; + flags |= MediaPlayerEntityFeature::PLAY_MEDIA | MediaPlayerEntityFeature::BROWSE_MEDIA | + MediaPlayerEntityFeature::STOP | MediaPlayerEntityFeature::VOLUME_SET | + MediaPlayerEntityFeature::VOLUME_MUTE | MediaPlayerEntityFeature::MEDIA_ANNOUNCE; + if (this->get_supports_pause()) { + flags |= MediaPlayerEntityFeature::PAUSE | MediaPlayerEntityFeature::PLAY; + } + return flags; + } + protected: bool supports_pause_{false}; std::vector supported_formats_{}; From 9c6dbbd8ea624894e78b80aeba2901ea92f2cfd6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 29 Jul 2025 17:43:35 +0000 Subject: [PATCH 40/47] Bump aioesphomeapi from 37.1.3 to 37.1.4 (#9964) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 9889e83910..cfae43e509 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile esptool==4.9.0 click==8.1.7 esphome-dashboard==20250514.0 -aioesphomeapi==37.1.3 +aioesphomeapi==37.1.4 zeroconf==0.147.0 puremagic==1.30 ruamel.yaml==0.18.14 # dashboard_import From 56c88807ee0ed06acff06c453533c890c1b2cfe1 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Wed, 30 Jul 2025 06:16:32 +1000 Subject: [PATCH 41/47] [mipi_dsi] Add dependencies (#9952) --- esphome/components/mipi_dsi/display.py | 3 ++- tests/components/mipi_dsi/test.esp32-p4-idf.yaml | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/esphome/components/mipi_dsi/display.py b/esphome/components/mipi_dsi/display.py index 4ed70a04c2..4fc837be67 100644 --- a/esphome/components/mipi_dsi/display.py +++ b/esphome/components/mipi_dsi/display.py @@ -57,7 +57,8 @@ from esphome.final_validate import full_config from . import mipi_dsi_ns, models -DEPENDENCIES = ["esp32"] +# Currently only ESP32-P4 is supported, so esp_ldo and psram are required +DEPENDENCIES = ["esp32", "esp_ldo", "psram"] DOMAIN = "mipi_dsi" LOGGER = logging.getLogger(DOMAIN) diff --git a/tests/components/mipi_dsi/test.esp32-p4-idf.yaml b/tests/components/mipi_dsi/test.esp32-p4-idf.yaml index 8a6f3c87ba..9c4eb07d9b 100644 --- a/tests/components/mipi_dsi/test.esp32-p4-idf.yaml +++ b/tests/components/mipi_dsi/test.esp32-p4-idf.yaml @@ -12,6 +12,8 @@ display: #- platform: mipi_dsi #id: backlight_id +psram: + i2c: sda: GPIO7 scl: GPIO8 From daccaf36a78e4d5e7065ba2bb87e7bcbb478a289 Mon Sep 17 00:00:00 2001 From: Dayowe <143176453+dayowe@users.noreply.github.com> Date: Tue, 29 Jul 2025 23:10:53 +0200 Subject: [PATCH 42/47] Fix WiFi to prefer strongest AP when multiple APs have same SSID (#9963) Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> --- esphome/components/wifi/wifi_component.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index e85acbf5a7..2731dc1f3f 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -533,9 +533,17 @@ void WiFiComponent::check_scanning_finished() { return false; if (a.get_matches() && b.get_matches()) { - // if both match, check priority + // For APs with the same SSID, always prefer stronger signal + // This helps with mesh networks and multiple APs + if (a.get_ssid() == b.get_ssid()) { + return a.get_rssi() > b.get_rssi(); + } + + // For different SSIDs, check priority first if (a.get_priority() != b.get_priority()) return a.get_priority() > b.get_priority(); + // If priorities are equal, prefer stronger signal + return a.get_rssi() > b.get_rssi(); } return a.get_rssi() > b.get_rssi(); From 76d33308d92b7f90d2e3c05c96f5ee817aebef56 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 29 Jul 2025 12:41:37 -1000 Subject: [PATCH 43/47] [api] Eliminate heap allocations when populating repeated fields from containers (#9948) --- esphome/components/api/api.proto | 20 ++--- esphome/components/api/api_connection.cpp | 31 +++----- esphome/components/api/api_options.proto | 26 +++++++ esphome/components/api/api_pb2.cpp | 60 +++++++-------- esphome/components/api/api_pb2.h | 21 +++--- esphome/components/api/api_pb2_dump.cpp | 20 ++--- esphome/components/api/api_pb2_includes.h | 34 +++++++++ esphome/components/climate/climate_traits.h | 24 ++++++ esphome/components/fan/fan_traits.h | 16 ++++ esphome/components/light/light_traits.h | 17 +++++ esphome/components/select/select_traits.cpp | 2 +- esphome/components/select/select_traits.h | 2 +- script/api_protobuf/api_protobuf.py | 83 ++++++++++++++++----- 13 files changed, 255 insertions(+), 101 deletions(-) create mode 100644 esphome/components/api/api_pb2_includes.h diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 4cf275c7f7..8eb2d26e54 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -419,7 +419,7 @@ message ListEntitiesFanResponse { bool disabled_by_default = 9; string icon = 10 [(field_ifdef) = "USE_ENTITY_ICON"]; EntityCategory entity_category = 11; - repeated string supported_preset_modes = 12; + repeated string supported_preset_modes = 12 [(container_pointer) = "std::set"]; uint32 device_id = 13 [(field_ifdef) = "USE_DEVICES"]; } // Deprecated in API version 1.6 - only used in deprecated fields @@ -500,7 +500,7 @@ message ListEntitiesLightResponse { string name = 3; reserved 4; // Deprecated: was string unique_id - repeated ColorMode supported_color_modes = 12; + repeated ColorMode supported_color_modes = 12 [(container_pointer) = "std::set"]; // next four supports_* are for legacy clients, newer clients should use color modes // Deprecated in API version 1.6 bool legacy_supports_brightness = 5 [deprecated=true]; @@ -966,7 +966,7 @@ message ListEntitiesClimateResponse { bool supports_current_temperature = 5; bool supports_two_point_target_temperature = 6; - repeated ClimateMode supported_modes = 7; + repeated ClimateMode supported_modes = 7 [(container_pointer) = "std::set"]; float visual_min_temperature = 8; float visual_max_temperature = 9; float visual_target_temperature_step = 10; @@ -975,11 +975,11 @@ message ListEntitiesClimateResponse { // Deprecated in API version 1.5 bool legacy_supports_away = 11 [deprecated=true]; bool supports_action = 12; - repeated ClimateFanMode supported_fan_modes = 13; - repeated ClimateSwingMode supported_swing_modes = 14; - repeated string supported_custom_fan_modes = 15; - repeated ClimatePreset supported_presets = 16; - repeated string supported_custom_presets = 17; + repeated ClimateFanMode supported_fan_modes = 13 [(container_pointer) = "std::set"]; + repeated ClimateSwingMode supported_swing_modes = 14 [(container_pointer) = "std::set"]; + repeated string supported_custom_fan_modes = 15 [(container_pointer) = "std::set"]; + repeated ClimatePreset supported_presets = 16 [(container_pointer) = "std::set"]; + repeated string supported_custom_presets = 17 [(container_pointer) = "std::set"]; bool disabled_by_default = 18; string icon = 19 [(field_ifdef) = "USE_ENTITY_ICON"]; EntityCategory entity_category = 20; @@ -1119,7 +1119,7 @@ message ListEntitiesSelectResponse { reserved 4; // Deprecated: was string unique_id string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON"]; - repeated string options = 6; + repeated string options = 6 [(container_pointer) = "std::vector"]; bool disabled_by_default = 7; EntityCategory entity_category = 8; uint32 device_id = 9 [(field_ifdef) = "USE_DEVICES"]; @@ -1844,7 +1844,7 @@ message VoiceAssistantConfigurationResponse { option (ifdef) = "USE_VOICE_ASSISTANT"; repeated VoiceAssistantWakeWord available_wake_words = 1; - repeated string active_wake_words = 2; + repeated string active_wake_words = 2 [(container_pointer) = "std::vector"]; uint32 max_active_wake_words = 3; } diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 6f71ab0929..51c7509428 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -413,8 +413,7 @@ uint16_t APIConnection::try_send_fan_info(EntityBase *entity, APIConnection *con msg.supports_speed = traits.supports_speed(); msg.supports_direction = traits.supports_direction(); msg.supported_speed_count = traits.supported_speed_count(); - for (auto const &preset : traits.supported_preset_modes()) - msg.supported_preset_modes.push_back(preset); + msg.supported_preset_modes = &traits.supported_preset_modes_for_api_(); return fill_and_encode_entity_info(fan, msg, ListEntitiesFanResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } void APIConnection::fan_command(const FanCommandRequest &msg) { @@ -470,8 +469,7 @@ uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *c auto *light = static_cast(entity); ListEntitiesLightResponse msg; auto traits = light->get_traits(); - for (auto mode : traits.get_supported_color_modes()) - msg.supported_color_modes.push_back(static_cast(mode)); + msg.supported_color_modes = &traits.get_supported_color_modes_for_api_(); if (traits.supports_color_capability(light::ColorCapability::COLOR_TEMPERATURE) || traits.supports_color_capability(light::ColorCapability::COLD_WARM_WHITE)) { msg.min_mireds = traits.get_min_mireds(); @@ -657,8 +655,7 @@ uint16_t APIConnection::try_send_climate_info(EntityBase *entity, APIConnection msg.supports_current_humidity = traits.get_supports_current_humidity(); msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_temperature(); msg.supports_target_humidity = traits.get_supports_target_humidity(); - for (auto mode : traits.get_supported_modes()) - msg.supported_modes.push_back(static_cast(mode)); + msg.supported_modes = &traits.get_supported_modes_for_api_(); msg.visual_min_temperature = traits.get_visual_min_temperature(); msg.visual_max_temperature = traits.get_visual_max_temperature(); msg.visual_target_temperature_step = traits.get_visual_target_temperature_step(); @@ -666,16 +663,11 @@ uint16_t APIConnection::try_send_climate_info(EntityBase *entity, APIConnection msg.visual_min_humidity = traits.get_visual_min_humidity(); msg.visual_max_humidity = traits.get_visual_max_humidity(); msg.supports_action = traits.get_supports_action(); - for (auto fan_mode : traits.get_supported_fan_modes()) - msg.supported_fan_modes.push_back(static_cast(fan_mode)); - for (auto const &custom_fan_mode : traits.get_supported_custom_fan_modes()) - msg.supported_custom_fan_modes.push_back(custom_fan_mode); - for (auto preset : traits.get_supported_presets()) - msg.supported_presets.push_back(static_cast(preset)); - for (auto const &custom_preset : traits.get_supported_custom_presets()) - msg.supported_custom_presets.push_back(custom_preset); - for (auto swing_mode : traits.get_supported_swing_modes()) - msg.supported_swing_modes.push_back(static_cast(swing_mode)); + msg.supported_fan_modes = &traits.get_supported_fan_modes_for_api_(); + msg.supported_custom_fan_modes = &traits.get_supported_custom_fan_modes_for_api_(); + msg.supported_presets = &traits.get_supported_presets_for_api_(); + msg.supported_custom_presets = &traits.get_supported_custom_presets_for_api_(); + msg.supported_swing_modes = &traits.get_supported_swing_modes_for_api_(); return fill_and_encode_entity_info(climate, msg, ListEntitiesClimateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } @@ -881,8 +873,7 @@ uint16_t APIConnection::try_send_select_info(EntityBase *entity, APIConnection * bool is_single) { auto *select = static_cast(entity); ListEntitiesSelectResponse msg; - for (const auto &option : select->traits.get_options()) - msg.options.push_back(option); + msg.options = &select->traits.get_options(); return fill_and_encode_entity_info(select, msg, ListEntitiesSelectResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } @@ -1197,9 +1188,7 @@ bool APIConnection::send_voice_assistant_get_configuration_response(const VoiceA resp_wake_word.trained_languages.push_back(lang); } } - for (auto &wake_word_id : config.active_wake_words) { - resp.active_wake_words.push_back(wake_word_id); - } + resp.active_wake_words = &config.active_wake_words; resp.max_active_wake_words = config.max_active_wake_words; return this->send_message(resp, VoiceAssistantConfigurationResponse::MESSAGE_TYPE); } diff --git a/esphome/components/api/api_options.proto b/esphome/components/api/api_options.proto index 4f0f52fc6f..85c805260f 100644 --- a/esphome/components/api/api_options.proto +++ b/esphome/components/api/api_options.proto @@ -28,4 +28,30 @@ extend google.protobuf.FieldOptions { optional string field_ifdef = 1042; optional uint32 fixed_array_size = 50007; optional bool no_zero_copy = 50008 [default=false]; + + // container_pointer: Zero-copy optimization for repeated fields. + // + // When container_pointer is set on a repeated field, the generated message will + // store a pointer to an existing container instead of copying the data into the + // message's own repeated field. This eliminates heap allocations and improves performance. + // + // Requirements for safe usage: + // 1. The source container must remain valid until the message is encoded + // 2. Messages must be encoded immediately (which ESPHome does by default) + // 3. The container type must match the field type exactly + // + // Supported container types: + // - "std::vector" for most repeated fields + // - "std::set" for unique/sorted data + // - Full type specification required for enums (e.g., "std::set") + // + // Example usage in .proto file: + // repeated string supported_modes = 12 [(container_pointer) = "std::set"]; + // repeated ColorMode color_modes = 13 [(container_pointer) = "std::set"]; + // + // The corresponding C++ code must provide const reference access to a container + // that matches the specified type and remains valid during message encoding. + // This is typically done through methods returning const T& or special accessor + // methods like get_options() or supported_modes_for_api_(). + optional string container_pointer = 50001; } diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 35ba1e6c30..2e8adeaf5c 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -331,7 +331,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(10, this->icon_ref_); #endif buffer.encode_uint32(11, static_cast(this->entity_category)); - for (auto &it : this->supported_preset_modes) { + for (const auto &it : *this->supported_preset_modes) { buffer.encode_string(12, it, true); } #ifdef USE_DEVICES @@ -351,8 +351,8 @@ void ListEntitiesFanResponse::calculate_size(ProtoSize &size) const { size.add_length(1, this->icon_ref_.size()); #endif size.add_uint32(1, static_cast(this->entity_category)); - if (!this->supported_preset_modes.empty()) { - for (const auto &it : this->supported_preset_modes) { + if (!this->supported_preset_modes->empty()) { + for (const auto &it : *this->supported_preset_modes) { size.add_length_force(1, it.size()); } } @@ -447,7 +447,7 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->object_id_ref_); buffer.encode_fixed32(2, this->key); buffer.encode_string(3, this->name_ref_); - for (auto &it : this->supported_color_modes) { + for (const auto &it : *this->supported_color_modes) { buffer.encode_uint32(12, static_cast(it), true); } buffer.encode_float(9, this->min_mireds); @@ -468,8 +468,8 @@ void ListEntitiesLightResponse::calculate_size(ProtoSize &size) const { size.add_length(1, this->object_id_ref_.size()); size.add_fixed32(1, this->key); size.add_length(1, this->name_ref_.size()); - if (!this->supported_color_modes.empty()) { - for (const auto &it : this->supported_color_modes) { + if (!this->supported_color_modes->empty()) { + for (const auto &it : *this->supported_color_modes) { size.add_uint32_force(1, static_cast(it)); } } @@ -1064,26 +1064,26 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(3, this->name_ref_); buffer.encode_bool(5, this->supports_current_temperature); buffer.encode_bool(6, this->supports_two_point_target_temperature); - for (auto &it : this->supported_modes) { + for (const auto &it : *this->supported_modes) { buffer.encode_uint32(7, static_cast(it), true); } buffer.encode_float(8, this->visual_min_temperature); buffer.encode_float(9, this->visual_max_temperature); buffer.encode_float(10, this->visual_target_temperature_step); buffer.encode_bool(12, this->supports_action); - for (auto &it : this->supported_fan_modes) { + for (const auto &it : *this->supported_fan_modes) { buffer.encode_uint32(13, static_cast(it), true); } - for (auto &it : this->supported_swing_modes) { + for (const auto &it : *this->supported_swing_modes) { buffer.encode_uint32(14, static_cast(it), true); } - for (auto &it : this->supported_custom_fan_modes) { + for (const auto &it : *this->supported_custom_fan_modes) { buffer.encode_string(15, it, true); } - for (auto &it : this->supported_presets) { + for (const auto &it : *this->supported_presets) { buffer.encode_uint32(16, static_cast(it), true); } - for (auto &it : this->supported_custom_presets) { + for (const auto &it : *this->supported_custom_presets) { buffer.encode_string(17, it, true); } buffer.encode_bool(18, this->disabled_by_default); @@ -1106,8 +1106,8 @@ void ListEntitiesClimateResponse::calculate_size(ProtoSize &size) const { size.add_length(1, this->name_ref_.size()); size.add_bool(1, this->supports_current_temperature); size.add_bool(1, this->supports_two_point_target_temperature); - if (!this->supported_modes.empty()) { - for (const auto &it : this->supported_modes) { + if (!this->supported_modes->empty()) { + for (const auto &it : *this->supported_modes) { size.add_uint32_force(1, static_cast(it)); } } @@ -1115,28 +1115,28 @@ void ListEntitiesClimateResponse::calculate_size(ProtoSize &size) const { size.add_float(1, this->visual_max_temperature); size.add_float(1, this->visual_target_temperature_step); size.add_bool(1, this->supports_action); - if (!this->supported_fan_modes.empty()) { - for (const auto &it : this->supported_fan_modes) { + if (!this->supported_fan_modes->empty()) { + for (const auto &it : *this->supported_fan_modes) { size.add_uint32_force(1, static_cast(it)); } } - if (!this->supported_swing_modes.empty()) { - for (const auto &it : this->supported_swing_modes) { + if (!this->supported_swing_modes->empty()) { + for (const auto &it : *this->supported_swing_modes) { size.add_uint32_force(1, static_cast(it)); } } - if (!this->supported_custom_fan_modes.empty()) { - for (const auto &it : this->supported_custom_fan_modes) { + if (!this->supported_custom_fan_modes->empty()) { + for (const auto &it : *this->supported_custom_fan_modes) { size.add_length_force(1, it.size()); } } - if (!this->supported_presets.empty()) { - for (const auto &it : this->supported_presets) { + if (!this->supported_presets->empty()) { + for (const auto &it : *this->supported_presets) { size.add_uint32_force(2, static_cast(it)); } } - if (!this->supported_custom_presets.empty()) { - for (const auto &it : this->supported_custom_presets) { + if (!this->supported_custom_presets->empty()) { + for (const auto &it : *this->supported_custom_presets) { size.add_length_force(2, it.size()); } } @@ -1371,7 +1371,7 @@ void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const { #ifdef USE_ENTITY_ICON buffer.encode_string(5, this->icon_ref_); #endif - for (auto &it : this->options) { + for (const auto &it : *this->options) { buffer.encode_string(6, it, true); } buffer.encode_bool(7, this->disabled_by_default); @@ -1387,8 +1387,8 @@ void ListEntitiesSelectResponse::calculate_size(ProtoSize &size) const { #ifdef USE_ENTITY_ICON size.add_length(1, this->icon_ref_.size()); #endif - if (!this->options.empty()) { - for (const auto &it : this->options) { + if (!this->options->empty()) { + for (const auto &it : *this->options) { size.add_length_force(1, it.size()); } } @@ -2334,15 +2334,15 @@ void VoiceAssistantConfigurationResponse::encode(ProtoWriteBuffer buffer) const for (auto &it : this->available_wake_words) { buffer.encode_message(1, it, true); } - for (auto &it : this->active_wake_words) { + for (const auto &it : *this->active_wake_words) { buffer.encode_string(2, it, true); } buffer.encode_uint32(3, this->max_active_wake_words); } void VoiceAssistantConfigurationResponse::calculate_size(ProtoSize &size) const { size.add_repeated_message(1, this->available_wake_words); - if (!this->active_wake_words.empty()) { - for (const auto &it : this->active_wake_words) { + if (!this->active_wake_words->empty()) { + for (const auto &it : *this->active_wake_words) { size.add_length_force(1, it.size()); } } diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index c994031bcb..c190c8e191 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -6,6 +6,7 @@ #include "esphome/core/string_ref.h" #include "proto.h" +#include "api_pb2_includes.h" namespace esphome::api { @@ -703,7 +704,7 @@ class ListEntitiesFanResponse : public InfoResponseProtoMessage { bool supports_speed{false}; bool supports_direction{false}; int32_t supported_speed_count{0}; - std::vector supported_preset_modes{}; + const std::set *supported_preset_modes{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -768,7 +769,7 @@ class ListEntitiesLightResponse : public InfoResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "list_entities_light_response"; } #endif - std::vector supported_color_modes{}; + const std::set *supported_color_modes{}; float min_mireds{0.0f}; float max_mireds{0.0f}; std::vector effects{}; @@ -1319,16 +1320,16 @@ class ListEntitiesClimateResponse : public InfoResponseProtoMessage { #endif bool supports_current_temperature{false}; bool supports_two_point_target_temperature{false}; - std::vector supported_modes{}; + const std::set *supported_modes{}; float visual_min_temperature{0.0f}; float visual_max_temperature{0.0f}; float visual_target_temperature_step{0.0f}; bool supports_action{false}; - std::vector supported_fan_modes{}; - std::vector supported_swing_modes{}; - std::vector supported_custom_fan_modes{}; - std::vector supported_presets{}; - std::vector supported_custom_presets{}; + const std::set *supported_fan_modes{}; + const std::set *supported_swing_modes{}; + const std::set *supported_custom_fan_modes{}; + const std::set *supported_presets{}; + const std::set *supported_custom_presets{}; float visual_current_temperature_step{0.0f}; bool supports_current_humidity{false}; bool supports_target_humidity{false}; @@ -1475,7 +1476,7 @@ class ListEntitiesSelectResponse : public InfoResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "list_entities_select_response"; } #endif - std::vector options{}; + const std::vector *options{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -2448,7 +2449,7 @@ class VoiceAssistantConfigurationResponse : public ProtoMessage { const char *message_name() const override { return "voice_assistant_configuration_response"; } #endif std::vector available_wake_words{}; - std::vector active_wake_words{}; + const std::vector *active_wake_words{}; uint32_t max_active_wake_words{0}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; diff --git a/esphome/components/api/api_pb2_dump.cpp b/esphome/components/api/api_pb2_dump.cpp index 5e17f6148a..032cfbc044 100644 --- a/esphome/components/api/api_pb2_dump.cpp +++ b/esphome/components/api/api_pb2_dump.cpp @@ -830,7 +830,7 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const { dump_field(out, "icon", this->icon_ref_); #endif dump_field(out, "entity_category", static_cast(this->entity_category)); - for (const auto &it : this->supported_preset_modes) { + for (const auto &it : *this->supported_preset_modes) { dump_field(out, "supported_preset_modes", it, 4); } #ifdef USE_DEVICES @@ -873,7 +873,7 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const { dump_field(out, "object_id", this->object_id_ref_); dump_field(out, "key", this->key); dump_field(out, "name", this->name_ref_); - for (const auto &it : this->supported_color_modes) { + for (const auto &it : *this->supported_color_modes) { dump_field(out, "supported_color_modes", static_cast(it), 4); } dump_field(out, "min_mireds", this->min_mireds); @@ -1189,26 +1189,26 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const { dump_field(out, "name", this->name_ref_); dump_field(out, "supports_current_temperature", this->supports_current_temperature); dump_field(out, "supports_two_point_target_temperature", this->supports_two_point_target_temperature); - for (const auto &it : this->supported_modes) { + for (const auto &it : *this->supported_modes) { dump_field(out, "supported_modes", static_cast(it), 4); } dump_field(out, "visual_min_temperature", this->visual_min_temperature); dump_field(out, "visual_max_temperature", this->visual_max_temperature); dump_field(out, "visual_target_temperature_step", this->visual_target_temperature_step); dump_field(out, "supports_action", this->supports_action); - for (const auto &it : this->supported_fan_modes) { + for (const auto &it : *this->supported_fan_modes) { dump_field(out, "supported_fan_modes", static_cast(it), 4); } - for (const auto &it : this->supported_swing_modes) { + for (const auto &it : *this->supported_swing_modes) { dump_field(out, "supported_swing_modes", static_cast(it), 4); } - for (const auto &it : this->supported_custom_fan_modes) { + for (const auto &it : *this->supported_custom_fan_modes) { dump_field(out, "supported_custom_fan_modes", it, 4); } - for (const auto &it : this->supported_presets) { + for (const auto &it : *this->supported_presets) { dump_field(out, "supported_presets", static_cast(it), 4); } - for (const auto &it : this->supported_custom_presets) { + for (const auto &it : *this->supported_custom_presets) { dump_field(out, "supported_custom_presets", it, 4); } dump_field(out, "disabled_by_default", this->disabled_by_default); @@ -1321,7 +1321,7 @@ void ListEntitiesSelectResponse::dump_to(std::string &out) const { #ifdef USE_ENTITY_ICON dump_field(out, "icon", this->icon_ref_); #endif - for (const auto &it : this->options) { + for (const auto &it : *this->options) { dump_field(out, "options", it, 4); } dump_field(out, "disabled_by_default", this->disabled_by_default); @@ -1786,7 +1786,7 @@ void VoiceAssistantConfigurationResponse::dump_to(std::string &out) const { it.dump_to(out); out.append("\n"); } - for (const auto &it : this->active_wake_words) { + for (const auto &it : *this->active_wake_words) { dump_field(out, "active_wake_words", it, 4); } dump_field(out, "max_active_wake_words", this->max_active_wake_words); diff --git a/esphome/components/api/api_pb2_includes.h b/esphome/components/api/api_pb2_includes.h new file mode 100644 index 0000000000..55d95304b1 --- /dev/null +++ b/esphome/components/api/api_pb2_includes.h @@ -0,0 +1,34 @@ +#pragma once + +#include "esphome/core/defines.h" + +// This file provides includes needed by the generated protobuf code +// when using pointer optimizations for component-specific types + +#ifdef USE_CLIMATE +#include "esphome/components/climate/climate_mode.h" +#include "esphome/components/climate/climate_traits.h" +#endif + +#ifdef USE_LIGHT +#include "esphome/components/light/light_traits.h" +#endif + +#ifdef USE_FAN +#include "esphome/components/fan/fan_traits.h" +#endif + +#ifdef USE_SELECT +#include "esphome/components/select/select_traits.h" +#endif + +// Standard library includes that might be needed +#include +#include +#include + +namespace esphome::api { + +// This file only provides includes, no actual code + +} // namespace esphome::api diff --git a/esphome/components/climate/climate_traits.h b/esphome/components/climate/climate_traits.h index c3a0dfca8f..8bd4714753 100644 --- a/esphome/components/climate/climate_traits.h +++ b/esphome/components/climate/climate_traits.h @@ -5,6 +5,13 @@ #include namespace esphome { + +#ifdef USE_API +namespace api { +class APIConnection; +} // namespace api +#endif + namespace climate { /** This class contains all static data for climate devices. @@ -173,6 +180,23 @@ class ClimateTraits { void set_visual_max_humidity(float visual_max_humidity) { this->visual_max_humidity_ = visual_max_humidity; } protected: +#ifdef USE_API + // The API connection is a friend class to access internal methods + friend class api::APIConnection; + // These methods return references to internal data structures. + // They are used by the API to avoid copying data when encoding messages. + // Warning: Do not use these methods outside of the API connection code. + // They return references to internal data that can be invalidated. + const std::set &get_supported_modes_for_api_() const { return this->supported_modes_; } + const std::set &get_supported_fan_modes_for_api_() const { return this->supported_fan_modes_; } + const std::set &get_supported_custom_fan_modes_for_api_() const { + return this->supported_custom_fan_modes_; + } + const std::set &get_supported_presets_for_api_() const { return this->supported_presets_; } + const std::set &get_supported_custom_presets_for_api_() const { return this->supported_custom_presets_; } + const std::set &get_supported_swing_modes_for_api_() const { return this->supported_swing_modes_; } +#endif + void set_mode_support_(climate::ClimateMode mode, bool supported) { if (supported) { this->supported_modes_.insert(mode); diff --git a/esphome/components/fan/fan_traits.h b/esphome/components/fan/fan_traits.h index 2ef6f8b7cc..48509e5705 100644 --- a/esphome/components/fan/fan_traits.h +++ b/esphome/components/fan/fan_traits.h @@ -4,6 +4,13 @@ #pragma once namespace esphome { + +#ifdef USE_API +namespace api { +class APIConnection; +} // namespace api +#endif + namespace fan { class FanTraits { @@ -36,6 +43,15 @@ class FanTraits { bool supports_preset_modes() const { return !this->preset_modes_.empty(); } protected: +#ifdef USE_API + // The API connection is a friend class to access internal methods + friend class api::APIConnection; + // This method returns a reference to the internal preset modes set. + // It is used by the API to avoid copying data when encoding messages. + // Warning: Do not use this method outside of the API connection code. + // It returns a reference to internal data that can be invalidated. + const std::set &supported_preset_modes_for_api_() const { return this->preset_modes_; } +#endif bool oscillation_{false}; bool speed_{false}; bool direction_{false}; diff --git a/esphome/components/light/light_traits.h b/esphome/components/light/light_traits.h index 7c99d721f0..a45301d148 100644 --- a/esphome/components/light/light_traits.h +++ b/esphome/components/light/light_traits.h @@ -5,6 +5,13 @@ #include namespace esphome { + +#ifdef USE_API +namespace api { +class APIConnection; +} // namespace api +#endif + namespace light { /// This class is used to represent the capabilities of a light. @@ -52,6 +59,16 @@ class LightTraits { void set_max_mireds(float max_mireds) { this->max_mireds_ = max_mireds; } protected: +#ifdef USE_API + // The API connection is a friend class to access internal methods + friend class api::APIConnection; + // This method returns a reference to the internal color modes set. + // It is used by the API to avoid copying data when encoding messages. + // Warning: Do not use this method outside of the API connection code. + // It returns a reference to internal data that can be invalidated. + const std::set &get_supported_color_modes_for_api_() const { return this->supported_color_modes_; } +#endif + std::set supported_color_modes_{}; float min_mireds_{0}; float max_mireds_{0}; diff --git a/esphome/components/select/select_traits.cpp b/esphome/components/select/select_traits.cpp index 89da30c405..a8cd4290c8 100644 --- a/esphome/components/select/select_traits.cpp +++ b/esphome/components/select/select_traits.cpp @@ -5,7 +5,7 @@ namespace select { void SelectTraits::set_options(std::vector options) { this->options_ = std::move(options); } -std::vector SelectTraits::get_options() const { return this->options_; } +const std::vector &SelectTraits::get_options() const { return this->options_; } } // namespace select } // namespace esphome diff --git a/esphome/components/select/select_traits.h b/esphome/components/select/select_traits.h index ccf23dc6d0..128066dd6b 100644 --- a/esphome/components/select/select_traits.h +++ b/esphome/components/select/select_traits.h @@ -9,7 +9,7 @@ namespace select { class SelectTraits { public: void set_options(std::vector options); - std::vector get_options() const; + const std::vector &get_options() const; protected: std::vector options_; diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index 275c7ffc9e..fece22499a 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -1170,6 +1170,10 @@ class FixedArrayRepeatedType(TypeInfo): class RepeatedTypeInfo(TypeInfo): def __init__(self, field: descriptor.FieldDescriptorProto) -> None: super().__init__(field) + # Check if this is a pointer field by looking for container_pointer option + self._container_type = get_field_opt(field, pb.container_pointer, "") + self._use_pointer = bool(self._container_type) + # For repeated fields, we need to get the base type info # but we can't call create_field_type_info as it would cause recursion # So we extract just the type creation logic @@ -1185,6 +1189,14 @@ class RepeatedTypeInfo(TypeInfo): @property def cpp_type(self) -> str: + if self._use_pointer and self._container_type: + # For pointer fields, use the specified container type + # If the container type already includes the element type (e.g., std::set) + # use it as-is, otherwise append the element type + if "<" in self._container_type and ">" in self._container_type: + return f"const {self._container_type}*" + else: + return f"const {self._container_type}<{self._ti.cpp_type}>*" return f"std::vector<{self._ti.cpp_type}>" @property @@ -1205,6 +1217,9 @@ class RepeatedTypeInfo(TypeInfo): @property def decode_varint_content(self) -> str: + # Pointer fields don't support decoding + if self._use_pointer: + return None content = self._ti.decode_varint if content is None: return None @@ -1214,6 +1229,9 @@ class RepeatedTypeInfo(TypeInfo): @property def decode_length_content(self) -> str: + # Pointer fields don't support decoding + if self._use_pointer: + return None content = self._ti.decode_length if content is None and isinstance(self._ti, MessageType): # Special handling for non-template message decoding @@ -1226,6 +1244,9 @@ class RepeatedTypeInfo(TypeInfo): @property def decode_32bit_content(self) -> str: + # Pointer fields don't support decoding + if self._use_pointer: + return None content = self._ti.decode_32bit if content is None: return None @@ -1235,6 +1256,9 @@ class RepeatedTypeInfo(TypeInfo): @property def decode_64bit_content(self) -> str: + # Pointer fields don't support decoding + if self._use_pointer: + return None content = self._ti.decode_64bit if content is None: return None @@ -1249,16 +1273,31 @@ class RepeatedTypeInfo(TypeInfo): @property def encode_content(self) -> str: - o = f"for (auto {'' if self._ti_is_bool else '&'}it : this->{self.field_name}) {{\n" - if isinstance(self._ti, EnumType): - o += f" buffer.{self._ti.encode_func}({self.number}, static_cast(it), true);\n" + if self._use_pointer: + # For pointer fields, just dereference (pointer should never be null in our use case) + o = f"for (const auto &it : *this->{self.field_name}) {{\n" + if isinstance(self._ti, EnumType): + o += f" buffer.{self._ti.encode_func}({self.number}, static_cast(it), true);\n" + else: + o += f" buffer.{self._ti.encode_func}({self.number}, it, true);\n" + o += "}" + return o else: - o += f" buffer.{self._ti.encode_func}({self.number}, it, true);\n" - o += "}" - return o + o = f"for (auto {'' if self._ti_is_bool else '&'}it : this->{self.field_name}) {{\n" + if isinstance(self._ti, EnumType): + o += f" buffer.{self._ti.encode_func}({self.number}, static_cast(it), true);\n" + else: + o += f" buffer.{self._ti.encode_func}({self.number}, it, true);\n" + o += "}" + return o @property def dump_content(self) -> str: + if self._use_pointer: + # For pointer fields, dereference and use the existing helper + return _generate_array_dump_content( + self._ti, f"*this->{self.field_name}", self.name, is_bool=False + ) return _generate_array_dump_content( self._ti, f"this->{self.field_name}", self.name, is_bool=self._ti_is_bool ) @@ -1269,30 +1308,34 @@ class RepeatedTypeInfo(TypeInfo): def get_size_calculation(self, name: str, force: bool = False) -> str: # For repeated fields, we always need to pass force=True to the underlying type's calculation # This is because the encode method always sets force=true for repeated fields + + # Handle message types separately as they use a dedicated helper if isinstance(self._ti, MessageType): - # For repeated messages, use the dedicated helper that handles iteration internally field_id_size = self._ti.calculate_field_id_size() - o = f"size.add_repeated_message({field_id_size}, {name});" - return o + container = f"*{name}" if self._use_pointer else name + return f"size.add_repeated_message({field_id_size}, {container});" - # For other repeated types, use the underlying type's size calculation with force=True - o = f"if (!{name}.empty()) {{\n" + # For non-message types, generate size calculation with iteration + container_ref = f"*{name}" if self._use_pointer else name + empty_check = f"{name}->empty()" if self._use_pointer else f"{name}.empty()" - # Check if this is a fixed-size type by seeing if it has a fixed byte count + o = f"if (!{empty_check}) {{\n" + + # Check if this is a fixed-size type num_bytes = self._ti.get_fixed_size_bytes() if num_bytes is not None: - # Fixed types have constant size per element, so we can multiply + # Fixed types have constant size per element field_id_size = self._ti.calculate_field_id_size() - # Pre-calculate the total bytes per element bytes_per_element = field_id_size + num_bytes - o += ( - f" size.add_precalculated_size({name}.size() * {bytes_per_element});\n" - ) + size_expr = f"{name}->size()" if self._use_pointer else f"{name}.size()" + o += f" size.add_precalculated_size({size_expr} * {bytes_per_element});\n" else: # Other types need the actual value - o += f" for (const auto {'' if self._ti_is_bool else '&'}it : {name}) {{\n" + auto_ref = "" if self._ti_is_bool else "&" + o += f" for (const auto {auto_ref}it : {container_ref}) {{\n" o += f" {self._ti.get_size_calculation('it', True)}\n" o += " }\n" + o += "}" return o @@ -2080,6 +2123,7 @@ def main() -> None: d = descriptor.FileDescriptorSet.FromString(proto_content) file = d.file[0] + content = FILE_HEADER content += """\ #pragma once @@ -2088,7 +2132,10 @@ def main() -> None: #include "esphome/core/string_ref.h" #include "proto.h" +#include "api_pb2_includes.h" +""" + content += """ namespace esphome::api { """ From 14dd48f9c36c22c19fb28038e58ef11cb418b0c4 Mon Sep 17 00:00:00 2001 From: Samuel Sieb Date: Tue, 29 Jul 2025 17:41:31 -0700 Subject: [PATCH 44/47] [wifi] add more disconnect reason descriptions (#9955) Co-authored-by: Samuel Sieb --- .../components/wifi/wifi_component_esp32_arduino.cpp | 12 ++++++++++++ esphome/components/wifi/wifi_component_esp_idf.cpp | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/esphome/components/wifi/wifi_component_esp32_arduino.cpp b/esphome/components/wifi/wifi_component_esp32_arduino.cpp index b3167c5696..3c3e87d332 100644 --- a/esphome/components/wifi/wifi_component_esp32_arduino.cpp +++ b/esphome/components/wifi/wifi_component_esp32_arduino.cpp @@ -474,8 +474,20 @@ const char *get_disconnect_reason_str(uint8_t reason) { return "Handshake Failed"; case WIFI_REASON_CONNECTION_FAIL: return "Connection Failed"; + case WIFI_REASON_AP_TSF_RESET: + return "AP TSF reset"; case WIFI_REASON_ROAMING: return "Station Roaming"; + case WIFI_REASON_ASSOC_COMEBACK_TIME_TOO_LONG: + return "Association comeback time too long"; + case WIFI_REASON_SA_QUERY_TIMEOUT: + return "SA query timeout"; + case WIFI_REASON_NO_AP_FOUND_W_COMPATIBLE_SECURITY: + return "No AP found with compatible security"; + case WIFI_REASON_NO_AP_FOUND_IN_AUTHMODE_THRESHOLD: + return "No AP found in auth mode threshold"; + case WIFI_REASON_NO_AP_FOUND_IN_RSSI_THRESHOLD: + return "No AP found in RSSI threshold"; case WIFI_REASON_UNSPECIFIED: default: return "Unspecified"; diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index f0655a6d1d..0b281e9b80 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -640,8 +640,20 @@ const char *get_disconnect_reason_str(uint8_t reason) { return "Handshake Failed"; case WIFI_REASON_CONNECTION_FAIL: return "Connection Failed"; + case WIFI_REASON_AP_TSF_RESET: + return "AP TSF reset"; case WIFI_REASON_ROAMING: return "Station Roaming"; + case WIFI_REASON_ASSOC_COMEBACK_TIME_TOO_LONG: + return "Association comeback time too long"; + case WIFI_REASON_SA_QUERY_TIMEOUT: + return "SA query timeout"; + case WIFI_REASON_NO_AP_FOUND_W_COMPATIBLE_SECURITY: + return "No AP found with compatible security"; + case WIFI_REASON_NO_AP_FOUND_IN_AUTHMODE_THRESHOLD: + return "No AP found in auth mode threshold"; + case WIFI_REASON_NO_AP_FOUND_IN_RSSI_THRESHOLD: + return "No AP found in RSSI threshold"; case WIFI_REASON_UNSPECIFIED: default: return "Unspecified"; From 374858efeb50885de01f0e2075264354cab9f3ce Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Tue, 29 Jul 2025 19:53:14 -0500 Subject: [PATCH 45/47] [sensor] Add new filter: ``throttle_with_priority`` (#9937) --- esphome/components/sensor/__init__.py | 20 +++++++++++++++ esphome/components/sensor/filter.cpp | 35 +++++++++++++++++++++++++++ esphome/components/sensor/filter.h | 14 +++++++++++ 3 files changed, 69 insertions(+) diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index 20c6911d28..5d70785389 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -256,6 +256,7 @@ OffsetFilter = sensor_ns.class_("OffsetFilter", Filter) MultiplyFilter = sensor_ns.class_("MultiplyFilter", Filter) FilterOutValueFilter = sensor_ns.class_("FilterOutValueFilter", Filter) ThrottleFilter = sensor_ns.class_("ThrottleFilter", Filter) +ThrottleWithPriorityFilter = sensor_ns.class_("ThrottleWithPriorityFilter", Filter) TimeoutFilter = sensor_ns.class_("TimeoutFilter", Filter, cg.Component) DebounceFilter = sensor_ns.class_("DebounceFilter", Filter, cg.Component) HeartbeatFilter = sensor_ns.class_("HeartbeatFilter", Filter, cg.Component) @@ -595,6 +596,25 @@ async def throttle_filter_to_code(config, filter_id): return cg.new_Pvariable(filter_id, config) +TIMEOUT_WITH_PRIORITY_SCHEMA = cv.maybe_simple_value( + { + cv.Required(CONF_TIMEOUT): cv.positive_time_period_milliseconds, + cv.Optional(CONF_VALUE, default="nan"): cv.ensure_list(cv.float_), + }, + key=CONF_TIMEOUT, +) + + +@FILTER_REGISTRY.register( + "throttle_with_priority", + ThrottleWithPriorityFilter, + TIMEOUT_WITH_PRIORITY_SCHEMA, +) +async def throttle_with_priority_filter_to_code(config, filter_id): + template_ = [await cg.templatable(x, [], float) for x in config[CONF_VALUE]] + return cg.new_Pvariable(filter_id, config[CONF_TIMEOUT], template_) + + @FILTER_REGISTRY.register( "heartbeat", HeartbeatFilter, cv.positive_time_period_milliseconds ) diff --git a/esphome/components/sensor/filter.cpp b/esphome/components/sensor/filter.cpp index 2fd56b7c8f..39b507f960 100644 --- a/esphome/components/sensor/filter.cpp +++ b/esphome/components/sensor/filter.cpp @@ -1,5 +1,6 @@ #include "filter.h" #include +#include "esphome/core/application.h" #include "esphome/core/hal.h" #include "esphome/core/log.h" #include "sensor.h" @@ -332,6 +333,40 @@ optional ThrottleFilter::new_value(float value) { return {}; } +// ThrottleWithPriorityFilter +ThrottleWithPriorityFilter::ThrottleWithPriorityFilter(uint32_t min_time_between_inputs, + std::vector> prioritized_values) + : min_time_between_inputs_(min_time_between_inputs), prioritized_values_(std::move(prioritized_values)) {} + +optional ThrottleWithPriorityFilter::new_value(float value) { + bool is_prioritized_value = false; + int8_t accuracy = this->parent_->get_accuracy_decimals(); + float accuracy_mult = powf(10.0f, accuracy); + const uint32_t now = App.get_loop_component_start_time(); + // First, determine if the new value is one of the prioritized values + for (auto prioritized_value : this->prioritized_values_) { + if (std::isnan(prioritized_value.value())) { + if (std::isnan(value)) { + is_prioritized_value = true; + break; + } + continue; + } + float rounded_prioritized_value = roundf(accuracy_mult * prioritized_value.value()); + float rounded_value = roundf(accuracy_mult * value); + if (rounded_prioritized_value == rounded_value) { + is_prioritized_value = true; + break; + } + } + // Finally, determine if the new value should be throttled and pass it through if not + if (this->last_input_ == 0 || now - this->last_input_ >= min_time_between_inputs_ || is_prioritized_value) { + this->last_input_ = now; + return value; + } + return {}; +} + // DeltaFilter DeltaFilter::DeltaFilter(float delta, bool percentage_mode) : delta_(delta), current_delta_(delta), percentage_mode_(percentage_mode), last_value_(NAN) {} diff --git a/esphome/components/sensor/filter.h b/esphome/components/sensor/filter.h index 94fec8208b..8e2c6fef08 100644 --- a/esphome/components/sensor/filter.h +++ b/esphome/components/sensor/filter.h @@ -314,6 +314,20 @@ class ThrottleFilter : public Filter { uint32_t min_time_between_inputs_; }; +/// Same as 'throttle' but will immediately publish values contained in `value_to_prioritize`. +class ThrottleWithPriorityFilter : public Filter { + public: + explicit ThrottleWithPriorityFilter(uint32_t min_time_between_inputs, + std::vector> prioritized_values); + + optional new_value(float value) override; + + protected: + uint32_t last_input_{0}; + uint32_t min_time_between_inputs_; + std::vector> prioritized_values_; +}; + class TimeoutFilter : public Filter, public Component { public: explicit TimeoutFilter(uint32_t time_period, TemplatableValue new_value); From 913c58cd2c514c90b59c34e74cf80d3168b82395 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Tue, 29 Jul 2025 21:20:25 -0500 Subject: [PATCH 46/47] [template] Add tests for more sensor filters (#9973) --- tests/components/template/common.yaml | 159 +++++++++++++++++++------- 1 file changed, 118 insertions(+), 41 deletions(-) diff --git a/tests/components/template/common.yaml b/tests/components/template/common.yaml index fd9342b3e5..e185e01c5e 100644 --- a/tests/components/template/common.yaml +++ b/tests/components/template/common.yaml @@ -1,44 +1,3 @@ -sensor: - - platform: template - name: "Template Sensor" - id: template_sens - lambda: |- - if (id(some_binary_sensor).state) { - return 42.0; - } else { - return 0.0; - } - update_interval: 60s - filters: - - offset: 10 - - multiply: 1 - - offset: !lambda return 10; - - multiply: !lambda return 2; - - filter_out: - - 10 - - 20 - - !lambda return 10; - - filter_out: 10 - - filter_out: !lambda return NAN; - - timeout: - timeout: 10s - value: !lambda return 10; - - timeout: - timeout: 1h - value: 20.0 - - timeout: - timeout: 1d - - to_ntc_resistance: - calibration: - - 10.0kOhm -> 25°C - - 27.219kOhm -> 0°C - - 14.674kOhm -> 15°C - - to_ntc_temperature: - calibration: - - 10.0kOhm -> 25°C - - 27.219kOhm -> 0°C - - 14.674kOhm -> 15°C - esphome: on_boot: - sensor.template.publish: @@ -82,6 +41,123 @@ binary_sensor: sensor.in_range: id: template_sens below: 30.0 + filters: + - invert: + - delayed_on: 100ms + - delayed_off: 100ms + - delayed_on_off: !lambda "if (id(test_switch).state) return 1000; else return 0;" + - delayed_on_off: + time_on: 10s + time_off: !lambda "if (id(test_switch).state) return 1000; else return 0;" + - autorepeat: + - delay: 1s + time_off: 100ms + time_on: 900ms + - delay: 5s + time_off: 100ms + time_on: 400ms + - lambda: |- + if (id(other_binary_sensor).state) { + return x; + } else { + return {}; + } + - settle: 500ms + - timeout: 5s + +sensor: + - platform: template + name: "Template Sensor" + id: template_sens + lambda: |- + if (id(some_binary_sensor).state) { + return 42.0; + } else { + return 0.0; + } + update_interval: 60s + filters: + - calibrate_linear: + - 0.0 -> 0.0 + - 40.0 -> 45.0 + - 100.0 -> 102.5 + - calibrate_polynomial: + degree: 2 + datapoints: + # Map 0.0 (from sensor) to 0.0 (true value) + - 0.0 -> 0.0 + - 10.0 -> 12.1 + - 13.0 -> 14.0 + - clamp: + max_value: 10.0 + min_value: -10.0 + - debounce: 0.1s + - delta: 5.0 + - exponential_moving_average: + alpha: 0.1 + send_every: 15 + - filter_out: + - 10 + - 20 + - !lambda return 10; + - filter_out: 10 + - filter_out: !lambda return NAN; + - heartbeat: 5s + - lambda: return x * (9.0/5.0) + 32.0; + - max: + window_size: 10 + send_every: 2 + send_first_at: 1 + - median: + window_size: 7 + send_every: 4 + send_first_at: 3 + - min: + window_size: 10 + send_every: 2 + send_first_at: 1 + - multiply: 1 + - multiply: !lambda return 2; + - offset: 10 + - offset: !lambda return 10; + - or: + - quantile: + window_size: 7 + send_every: 4 + send_first_at: 3 + quantile: .9 + - round: 1 + - round_to_multiple_of: 0.25 + - skip_initial: 3 + - sliding_window_moving_average: + window_size: 15 + send_every: 15 + - throttle: 1s + - throttle_average: 2s + - throttle_with_priority: 5s + - throttle_with_priority: + timeout: 3s + value: + - 42.0 + - nan + - timeout: + timeout: 10s + value: !lambda return 10; + - timeout: + timeout: 1h + value: 20.0 + - timeout: + timeout: 1d + - to_ntc_resistance: + calibration: + - 10.0kOhm -> 25°C + - 27.219kOhm -> 0°C + - 14.674kOhm -> 15°C + - to_ntc_temperature: + calibration: + - 10.0kOhm -> 25°C + - 27.219kOhm -> 0°C + - 14.674kOhm -> 15°C output: - platform: template @@ -92,6 +168,7 @@ output: switch: - platform: template + id: test_switch name: "Template Switch" lambda: |- if (id(some_binary_sensor).state) { From 9b4fe54f45a6b8ceacb35f4a6ed9ca35d077554c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 29 Jul 2025 19:19:12 -1000 Subject: [PATCH 47/47] [esp32_ble_client] Fix connection failures with short discovery timeout devices and speed up BLE connections (#9971) --- .../esp32_ble_client/ble_client_base.cpp | 62 +++++++++++++++++-- 1 file changed, 58 insertions(+), 4 deletions(-) diff --git a/esphome/components/esp32_ble_client/ble_client_base.cpp b/esphome/components/esp32_ble_client/ble_client_base.cpp index bf425b3730..d3416641d9 100644 --- a/esphome/components/esp32_ble_client/ble_client_base.cpp +++ b/esphome/components/esp32_ble_client/ble_client_base.cpp @@ -5,10 +5,23 @@ #ifdef USE_ESP32 +#include +#include + namespace esphome { namespace esp32_ble_client { static const char *const TAG = "esp32_ble_client"; + +// Connection interval defaults matching ESP-IDF's BTM_BLE_CONN_INT_*_DEF +static const uint16_t DEFAULT_MIN_CONN_INTERVAL = 0x0A; // 10 * 1.25ms = 12.5ms +static const uint16_t DEFAULT_MAX_CONN_INTERVAL = 0x0C; // 12 * 1.25ms = 15ms +static const uint16_t DEFAULT_CONN_TIMEOUT = 600; // 600 * 10ms = 6s + +// Fastest connection parameters for devices with short discovery timeouts +static const uint16_t FAST_MIN_CONN_INTERVAL = 0x06; // 6 * 1.25ms = 7.5ms (BLE minimum) +static const uint16_t FAST_MAX_CONN_INTERVAL = 0x06; // 6 * 1.25ms = 7.5ms +static const uint16_t FAST_CONN_TIMEOUT = 1000; // 1000 * 10ms = 10s static const esp_bt_uuid_t NOTIFY_DESC_UUID = { .len = ESP_UUID_LEN_16, .uuid = @@ -129,6 +142,7 @@ void BLEClientBase::connect() { ESP_LOGI(TAG, "[%d] [%s] 0x%02x Attempting BLE connection", this->connection_index_, this->address_str_.c_str(), this->remote_addr_type_); this->paired_ = false; + auto ret = esp_ble_gattc_open(this->gattc_if_, this->remote_bda_, this->remote_addr_type_, true); if (ret) { ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_open error, status=%d", this->connection_index_, this->address_str_.c_str(), @@ -136,6 +150,22 @@ void BLEClientBase::connect() { this->set_state(espbt::ClientState::IDLE); } else { this->set_state(espbt::ClientState::CONNECTING); + + // For connections without cache, set fast connection parameters after initiating connection + // This ensures service discovery completes within the 10-second timeout that + // some devices like HomeKit BLE sensors enforce + if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) { + auto param_ret = + esp_ble_gap_set_prefer_conn_params(this->remote_bda_, FAST_MIN_CONN_INTERVAL, FAST_MAX_CONN_INTERVAL, + 0, // latency: 0 + FAST_CONN_TIMEOUT); + if (param_ret != ESP_OK) { + ESP_LOGW(TAG, "[%d] [%s] esp_ble_gap_set_prefer_conn_params failed: %d", this->connection_index_, + this->address_str_.c_str(), param_ret); + } else { + ESP_LOGD(TAG, "[%d] [%s] Set fast conn params", this->connection_index_, this->address_str_.c_str()); + } + } } } @@ -278,12 +308,14 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ this->address_str_.c_str(), ret); } this->set_state(espbt::ClientState::CONNECTED); + ESP_LOGI(TAG, "[%d] [%s] Connection open", this->connection_index_, this->address_str_.c_str()); if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) { - ESP_LOGI(TAG, "[%d] [%s] Connected", this->connection_index_, this->address_str_.c_str()); + ESP_LOGI(TAG, "[%d] [%s] Using cached services", this->connection_index_, this->address_str_.c_str()); // only set our state, subclients might have more stuff to do yet. this->state_ = espbt::ClientState::ESTABLISHED; break; } + ESP_LOGD(TAG, "[%d] [%s] Searching for services", this->connection_index_, this->address_str_.c_str()); esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, nullptr); break; } @@ -296,8 +328,15 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ case ESP_GATTC_DISCONNECT_EVT: { if (!this->check_addr(param->disconnect.remote_bda)) return false; - ESP_LOGD(TAG, "[%d] [%s] ESP_GATTC_DISCONNECT_EVT, reason %d", this->connection_index_, - this->address_str_.c_str(), param->disconnect.reason); + // Check if we were disconnected while waiting for service discovery + if (param->disconnect.reason == ESP_GATT_CONN_TERMINATE_PEER_USER && + this->state_ == espbt::ClientState::CONNECTED) { + ESP_LOGW(TAG, "[%d] [%s] Disconnected by remote during service discovery", this->connection_index_, + this->address_str_.c_str()); + } else { + ESP_LOGD(TAG, "[%d] [%s] ESP_GATTC_DISCONNECT_EVT, reason 0x%02x", this->connection_index_, + this->address_str_.c_str(), param->disconnect.reason); + } this->release_services(); this->set_state(espbt::ClientState::IDLE); break; @@ -353,7 +392,22 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ ESP_LOGV(TAG, "[%d] [%s] start_handle: 0x%x end_handle: 0x%x", this->connection_index_, this->address_str_.c_str(), svc->start_handle, svc->end_handle); } - ESP_LOGI(TAG, "[%d] [%s] Connected", this->connection_index_, this->address_str_.c_str()); + ESP_LOGI(TAG, "[%d] [%s] Service discovery complete", this->connection_index_, this->address_str_.c_str()); + + // For non-cached connections, restore default connection parameters after service discovery + // Now that we've discovered all services, we can use more balanced parameters + // that save power and reduce interference + if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) { + esp_ble_conn_update_params_t conn_params = {{0}}; + memcpy(conn_params.bda, this->remote_bda_, sizeof(esp_bd_addr_t)); + conn_params.min_int = DEFAULT_MIN_CONN_INTERVAL; + conn_params.max_int = DEFAULT_MAX_CONN_INTERVAL; + conn_params.latency = 0; + conn_params.timeout = DEFAULT_CONN_TIMEOUT; + ESP_LOGD(TAG, "[%d] [%s] Restored default conn params", this->connection_index_, this->address_str_.c_str()); + esp_ble_gap_update_conn_params(&conn_params); + } + this->state_ = espbt::ClientState::ESTABLISHED; break; }