diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 652b456850..d6384456d5 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -763,7 +763,7 @@ message SubscribeHomeassistantServicesRequest { message HomeassistantServiceMap { string key = 1; - string value = 2 [(no_zero_copy) = true]; + string value = 2; } message HomeassistantActionRequest { @@ -779,7 +779,7 @@ message HomeassistantActionRequest { bool is_event = 5; uint32 call_id = 6 [(field_ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES"]; bool wants_response = 7 [(field_ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON"]; - string response_template = 8 [(no_zero_copy) = true, (field_ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON"]; + string response_template = 8 [(field_ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON"]; } // Message sent by Home Assistant to ESPHome with service call response data diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 01fe44d7c7..e21b8596ca 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -1053,7 +1053,7 @@ class SubscribeHomeassistantServicesRequest final : public ProtoMessage { class HomeassistantServiceMap final : public ProtoMessage { public: StringRef key{}; - std::string value{}; + StringRef value{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -1081,7 +1081,7 @@ class HomeassistantActionRequest final : public ProtoMessage { bool wants_response{false}; #endif #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON - std::string response_template{}; + StringRef response_template{}; #endif void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; diff --git a/esphome/components/api/custom_api_device.h b/esphome/components/api/custom_api_device.h index b16164270b..2fd9cb0dd2 100644 --- a/esphome/components/api/custom_api_device.h +++ b/esphome/components/api/custom_api_device.h @@ -265,7 +265,7 @@ class CustomAPIDevice { for (auto &it : data) { auto &kv = resp.data.emplace_back(); kv.key = StringRef(it.first); - kv.value = it.second; // value is std::string (no_zero_copy), assign directly + kv.value = StringRef(it.second); // data map lives until send completes } global_api_server->send_homeassistant_action(resp); } @@ -308,7 +308,7 @@ class CustomAPIDevice { for (auto &it : data) { auto &kv = resp.data.emplace_back(); kv.key = StringRef(it.first); - kv.value = it.second; // value is std::string (no_zero_copy), assign directly + kv.value = StringRef(it.second); // data map lives until send completes } global_api_server->send_homeassistant_action(resp); } diff --git a/esphome/components/api/homeassistant_service.h b/esphome/components/api/homeassistant_service.h index a17c99b8ba..dd7654b8bc 100644 --- a/esphome/components/api/homeassistant_service.h +++ b/esphome/components/api/homeassistant_service.h @@ -149,11 +149,21 @@ template class HomeAssistantServiceCallAction : public Actionservice_.value(x...); resp.service = StringRef(service_value); resp.is_event = this->flags_.is_event; - this->populate_service_map(resp.data, this->data_, x...); - this->populate_service_map(resp.data_template, this->data_template_, x...); - this->populate_service_map(resp.variables, this->variables_, x...); + + // Local storage for lambda-evaluated strings - lives until after send + FixedVector data_storage; + FixedVector data_template_storage; + FixedVector variables_storage; + + this->populate_service_map(resp.data, this->data_, data_storage, x...); + this->populate_service_map(resp.data_template, this->data_template_, data_template_storage, x...); + this->populate_service_map(resp.variables, this->variables_, variables_storage, x...); #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES +#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON + // IMPORTANT: Declare at outer scope so it lives until send_homeassistant_action returns. + std::string response_template_value; +#endif if (this->flags_.wants_status) { // Generate a unique call ID for this service call static uint32_t call_id_counter = 1; @@ -164,8 +174,7 @@ template class HomeAssistantServiceCallAction : public Actionflags_.has_response_template) { - std::string response_template_value = this->response_template_.value(x...); - resp.response_template = response_template_value; + resp.response_template = StringRef(response_template_value = this->response_template_.value(x...)); } } #endif @@ -205,12 +214,23 @@ template class HomeAssistantServiceCallAction : public Action - static void populate_service_map(VectorType &dest, SourceType &source, Ts... x) { + static void populate_service_map(VectorType &dest, SourceType &source, FixedVector &value_storage, + Ts... x) { dest.init(source.size()); + value_storage.init(source.size()); // Max possible, single allocation + for (auto &it : source) { auto &kv = dest.emplace_back(); kv.key = StringRef(it.key); - kv.value = it.value.value(x...); + + if (it.value.is_static_string()) { + // Static string from YAML - zero allocation + kv.value = StringRef(it.value.get_static_string()); + } else { + // Lambda evaluation - store result, reference it + value_storage.push_back(it.value.value(x...)); + kv.value = StringRef(value_storage.back()); + } } } diff --git a/esphome/components/homeassistant/number/homeassistant_number.cpp b/esphome/components/homeassistant/number/homeassistant_number.cpp index 82387a81e9..92ecd5ea39 100644 --- a/esphome/components/homeassistant/number/homeassistant_number.cpp +++ b/esphome/components/homeassistant/number/homeassistant_number.cpp @@ -91,11 +91,14 @@ void HomeassistantNumber::control(float value) { resp.data.init(2); auto &entity_id = resp.data.emplace_back(); entity_id.key = ENTITY_ID_KEY; - entity_id.value = this->entity_id_; + entity_id.value = StringRef(this->entity_id_); auto &entity_value = resp.data.emplace_back(); entity_value.key = VALUE_KEY; - entity_value.value = to_string(value); + // Stack buffer - no heap allocation; %g produces shortest representation + char value_buf[16]; + snprintf(value_buf, sizeof(value_buf), "%g", value); + entity_value.value = StringRef(value_buf); api::global_api_server->send_homeassistant_action(resp); } diff --git a/esphome/components/homeassistant/switch/homeassistant_switch.cpp b/esphome/components/homeassistant/switch/homeassistant_switch.cpp index 79d17eb290..cc3d582bf3 100644 --- a/esphome/components/homeassistant/switch/homeassistant_switch.cpp +++ b/esphome/components/homeassistant/switch/homeassistant_switch.cpp @@ -55,7 +55,7 @@ void HomeassistantSwitch::write_state(bool state) { resp.data.init(1); auto &entity_id_kv = resp.data.emplace_back(); entity_id_kv.key = ENTITY_ID_KEY; - entity_id_kv.value = this->entity_id_; + entity_id_kv.value = StringRef(this->entity_id_); api::global_api_server->send_homeassistant_action(resp); } diff --git a/esphome/core/automation.h b/esphome/core/automation.h index 61d2944acf..585b434bb2 100644 --- a/esphome/core/automation.h +++ b/esphome/core/automation.h @@ -159,6 +159,12 @@ template class TemplatableValue { return this->value(x...); } + /// Check if this holds a static string (const char* stored without allocation) + bool is_static_string() const { return this->type_ == STATIC_STRING; } + + /// Get the static string pointer (only valid if is_static_string() returns true) + const char *get_static_string() const { return this->static_str_; } + protected: enum : uint8_t { NONE,