[api] Use StringRef for HomeassistantServiceMap.value to eliminate heap allocations

This commit is contained in:
J. Nick Koston
2026-01-11 16:00:44 -10:00
parent 5ae46a4369
commit 2c0954c03c
7 changed files with 45 additions and 16 deletions
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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;
+2 -2
View File
@@ -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);
}
+27 -7
View File
@@ -149,11 +149,21 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
std::string service_value = this->service_.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<std::string> data_storage;
FixedVector<std::string> data_template_storage;
FixedVector<std::string> 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<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
resp.wants_response = true;
// Set response template if provided
if (this->flags_.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<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
}
template<typename VectorType, typename SourceType>
static void populate_service_map(VectorType &dest, SourceType &source, Ts... x) {
static void populate_service_map(VectorType &dest, SourceType &source, FixedVector<std::string> &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());
}
}
}
@@ -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);
}
@@ -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);
}
+6
View File
@@ -159,6 +159,12 @@ template<typename T, typename... X> 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,