ESPHome 2026.6.0-dev
Loading...
Searching...
No Matches
homeassistant_service.h
Go to the documentation of this file.
1#pragma once
2
3#include "api_server.h"
4#ifdef USE_API
5#ifdef USE_API_HOMEASSISTANT_SERVICES
6#include <functional>
7#include <utility>
8#include <vector>
9#include "api_pb2.h"
10#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
12#endif
16
17namespace esphome::api {
18
19template<typename... X> class TemplatableStringValue : public TemplatableValue<std::string, X...> {
20 // Verify that const char* uses the base class STATIC_STRING optimization (no heap allocation)
21 // rather than being wrapped in a lambda. The base class constructor for const char* is more
22 // specialized than the templated constructor here, so it should be selected.
23 static_assert(std::is_constructible_v<TemplatableValue<std::string, X...>, const char *>,
24 "Base class must have const char* constructor for STATIC_STRING optimization");
25
26 private:
27 // Helper to convert value to string - handles the case where value is already a string
28 template<typename T> static std::string value_to_string(T &&val) {
29 return to_string(std::forward<T>(val)); // NOLINT
30 }
31
32 // Overloads for string types - needed because std::to_string doesn't support them
33 static std::string value_to_string(char *val) {
34 return val ? std::string(val) : std::string();
35 } // For lambdas returning char* (e.g., itoa)
36 static std::string value_to_string(const char *val) { return std::string(val); } // For lambdas returning .c_str()
37 static std::string value_to_string(const std::string &val) { return val; }
38 static std::string value_to_string(std::string &&val) { return std::move(val); }
39 static std::string value_to_string(const StringRef &val) { return val.str(); }
40 static std::string value_to_string(StringRef &&val) { return val.str(); }
41
42 public:
43 TemplatableStringValue() : TemplatableValue<std::string, X...>() {}
44
45 template<typename F, enable_if_t<!is_invocable<F, X...>::value, int> = 0>
47
48 template<typename F, enable_if_t<is_invocable<F, X...>::value, int> = 0>
50 : TemplatableValue<std::string, X...>([f](X... x) -> std::string { return value_to_string(f(x...)); }) {}
51};
52
53template<typename... Ts> class TemplatableKeyValuePair {
54 public:
55 // Default constructor needed for FixedVector::emplace_back()
57
58 // Keys are always string literals from YAML dictionary keys (e.g., "code", "event")
59 // and never templatable values or lambdas. Only the value parameter can be a lambda/template.
60 // Using const char* avoids std::string heap allocation - keys remain in flash.
61 template<typename T> TemplatableKeyValuePair(const char *key, T value) : key(key), value(value) {}
62
63 const char *key{nullptr};
65};
66
67#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
68// Represents the response data from a Home Assistant action
69// Note: This class holds a StringRef to the error_message from the protobuf message.
70// The protobuf message must outlive the ActionResponse (which is guaranteed since
71// the callback is invoked synchronously while the message is on the stack).
73 public:
74 ActionResponse(bool success, StringRef error_message) : success_(success), error_message_(error_message) {}
75
76#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
77 ActionResponse(bool success, StringRef error_message, const uint8_t *data, size_t data_len)
78 : success_(success), error_message_(error_message) {
79 if (data == nullptr || data_len == 0)
80 return;
81 JsonDocument tmp = json::parse_json(data, data_len);
82 swap(this->json_document_, tmp);
83 }
84#endif
85
86 bool is_success() const { return this->success_; }
87 // Returns reference to error message - can be implicitly converted to std::string if needed
88 const StringRef &get_error_message() const { return this->error_message_; }
89
90#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
91 // Get data as parsed JSON object (const version returns read-only view)
92 JsonObjectConst get_json() const { return this->json_document_.as<JsonObjectConst>(); }
93#endif
94
95 protected:
98#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
99 JsonDocument json_document_;
100#endif
101};
102
103// Callback type for action responses
104template<typename... Ts> using ActionResponseCallback = std::function<void(const ActionResponse &, Ts...)>;
105#endif
106
107template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts...> {
108 public:
109 explicit HomeAssistantServiceCallAction(APIServer *parent, bool is_event) : parent_(parent) {
110 this->flags_.is_event = is_event;
111 }
112
113 template<typename T> void set_service(T service) { this->service_ = service; }
114
115 // Initialize FixedVector members - called from Python codegen with compile-time known sizes.
116 // Must be called before any add_* methods; capacity must match the number of subsequent add_* calls.
117 void init_data(size_t count) { this->data_.init(count); }
118 void init_data_template(size_t count) { this->data_template_.init(count); }
119 void init_variables(size_t count) { this->variables_.init(count); }
120
121 // Keys are always string literals from the Python code generation (e.g., cg.add(var.add_data("tag_id", templ))).
122 // The value parameter can be a lambda/template, but keys are never templatable.
123 // Using const char* for keys avoids std::string heap allocation - keys remain in flash.
124 template<typename V> void add_data(const char *key, V &&value) {
125 this->add_kv_(this->data_, key, std::forward<V>(value));
126 }
127 template<typename V> void add_data_template(const char *key, V &&value) {
128 this->add_kv_(this->data_template_, key, std::forward<V>(value));
129 }
130 template<typename V> void add_variable(const char *key, V &&value) {
131 this->add_kv_(this->variables_, key, std::forward<V>(value));
132 }
133
134#ifdef USE_ESP8266
135 // On ESP8266, ESPHOME_F() returns __FlashStringHelper* (PROGMEM pointer).
136 // Store as const char* — populate_service_map copies from PROGMEM at play() time.
137 template<typename V> void add_data(const __FlashStringHelper *key, V &&value) {
138 this->add_kv_(this->data_, reinterpret_cast<const char *>(key), std::forward<V>(value));
139 }
140 template<typename V> void add_data_template(const __FlashStringHelper *key, V &&value) {
141 this->add_kv_(this->data_template_, reinterpret_cast<const char *>(key), std::forward<V>(value));
142 }
143 template<typename V> void add_variable(const __FlashStringHelper *key, V &&value) {
144 this->add_kv_(this->variables_, reinterpret_cast<const char *>(key), std::forward<V>(value));
145 }
146#endif
147
148#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
149 template<typename T> void set_response_template(T response_template) {
150 this->response_template_ = response_template;
151 this->flags_.has_response_template = true;
152 }
153
154 void set_wants_status() { this->flags_.wants_status = true; }
155 void set_wants_response() { this->flags_.wants_response = true; }
156
157#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
158 Trigger<JsonObjectConst, Ts...> *get_success_trigger_with_response() { return &this->success_trigger_with_response_; }
159#endif
160 Trigger<Ts...> *get_success_trigger() { return &this->success_trigger_; }
161 Trigger<std::string, Ts...> *get_error_trigger() { return &this->error_trigger_; }
162#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
163
164 void play(const Ts &...x) override {
166 std::string service_value = this->service_.value(x...);
167 resp.service = StringRef(service_value);
168 resp.is_event = this->flags_.is_event;
169
170 // Local storage for lambda-evaluated strings - lives until after send
171 FixedVector<std::string> data_storage;
172 FixedVector<std::string> data_template_storage;
173 FixedVector<std::string> variables_storage;
174
175 this->populate_service_map(resp.data, this->data_, data_storage, x...);
176 this->populate_service_map(resp.data_template, this->data_template_, data_template_storage, x...);
177 this->populate_service_map(resp.variables, this->variables_, variables_storage, x...);
178
179#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
180#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
181 // IMPORTANT: Declare at outer scope so it lives until send_homeassistant_action returns.
182 std::string response_template_value;
183#endif
184 if (this->flags_.wants_status) {
185 // Generate a unique call ID for this service call
186 static uint32_t call_id_counter = 1;
187 uint32_t call_id = call_id_counter++;
188 resp.call_id = call_id;
189#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
190 if (this->flags_.wants_response) {
191 resp.wants_response = true;
192 // Set response template if provided
193 if (this->flags_.has_response_template) {
194 response_template_value = this->response_template_.value(x...);
195 resp.response_template = StringRef(response_template_value);
196 }
197 }
198#endif
199
200 auto captured_args = std::make_tuple(x...);
201 this->parent_->register_action_response_callback(call_id, [this, captured_args](const ActionResponse &response) {
202 std::apply(
203 [this, &response](auto &&...args) {
204 if (response.is_success()) {
205#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
206 if (this->flags_.wants_response) {
207 this->success_trigger_with_response_.trigger(response.get_json(), args...);
208 } else
209#endif
210 {
211 this->success_trigger_.trigger(args...);
212 }
213 } else {
214 this->error_trigger_.trigger(response.get_error_message(), args...);
215 }
216 },
217 captured_args);
218 });
219 }
220#endif
221
222 this->parent_->send_homeassistant_action(resp);
223 }
224
225 protected:
226 // Helper to add key-value pairs to FixedVectors
227 // Keys are always string literals (const char*), values can be lambdas/templates
228 template<typename V> void add_kv_(FixedVector<TemplatableKeyValuePair<Ts...>> &vec, const char *key, V &&value) {
229 auto &kv = vec.emplace_back();
230 kv.key = key;
231 kv.value = std::forward<V>(value);
232 }
233
234 template<typename VectorType, typename SourceType>
235 static void populate_service_map(VectorType &dest, SourceType &source, FixedVector<std::string> &value_storage,
236 Ts... x) {
237 dest.init(source.size());
238
239#ifdef USE_ESP8266
240 // On ESP8266, all static strings from codegen are FLASH_STRING (PROGMEM),
241 // so is_static_string() is always false — the zero-copy STATIC_STRING fast
242 // path from the non-ESP8266 branch cannot trigger. We copy all keys and
243 // values unconditionally: keys via _P functions (may be in PROGMEM), values
244 // via value() which handles FLASH_STRING internally.
245 value_storage.init(source.size() * 2);
246
247 for (auto &it : source) {
248 auto &kv = dest.emplace_back();
249
250 // Key: copy from possible PROGMEM
251 {
252 size_t key_len = strlen_P(it.key);
253 value_storage.push_back(std::string(key_len, '\0'));
254 memcpy_P(value_storage.back().data(), it.key, key_len);
255 kv.key = StringRef(value_storage.back());
256 }
257
258 // Value: value() handles FLASH_STRING via _P functions internally
259 value_storage.push_back(it.value.value(x...));
260 kv.value = StringRef(value_storage.back());
261 }
262#else
263 // On non-ESP8266, strings are directly readable from flash-mapped memory.
264 // Count non-static strings to allocate exact storage needed.
265 size_t lambda_count = 0;
266 for (const auto &it : source) {
267 if (!it.value.is_static_string()) {
268 lambda_count++;
269 }
270 }
271 value_storage.init(lambda_count);
272
273 for (auto &it : source) {
274 auto &kv = dest.emplace_back();
275 kv.key = StringRef(it.key);
276
277 if (it.value.is_static_string()) {
278 // Static string — pointer directly readable, zero allocation
279 kv.value = StringRef(it.value.get_static_string());
280 } else {
281 // Lambda — evaluate and store result
282 value_storage.push_back(it.value.value(x...));
283 kv.value = StringRef(value_storage.back());
284 }
285 }
286#endif
287 }
288
290 TemplatableStringValue<Ts...> service_{};
294#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
295#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
296 TemplatableStringValue<Ts...> response_template_{""};
298#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
300 Trigger<std::string, Ts...> error_trigger_;
301#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
302
303 struct Flags {
304 uint8_t is_event : 1;
305 uint8_t wants_status : 1;
306 uint8_t wants_response : 1;
308 uint8_t reserved : 5;
309 } flags_{0};
310};
311
312} // namespace esphome::api
313
314#endif
315#endif
Fixed-capacity vector - allocates once at runtime, never reallocates This avoids std::vector template...
Definition helpers.h:529
T & back()
Access last element (no bounds checking - matches std::vector behavior) Caller must ensure vector is ...
Definition helpers.h:683
void push_back(const T &value)
Add element without bounds checking Caller must ensure sufficient capacity was allocated via init() S...
Definition helpers.h:646
void init(size_t n)
Definition helpers.h:619
StringRef is a reference to a string owned by something else.
Definition string_ref.h:26
Primary TemplatableValue: stores either a constant value or a function pointer.
Definition automation.h:94
ActionResponse(bool success, StringRef error_message)
const StringRef & get_error_message() const
JsonObjectConst get_json() const
ActionResponse(bool success, StringRef error_message, const uint8_t *data, size_t data_len)
Trigger< JsonObjectConst, Ts... > success_trigger_with_response_
void add_variable(const __FlashStringHelper *key, V &&value)
HomeAssistantServiceCallAction(APIServer *parent, bool is_event)
FixedVector< TemplatableKeyValuePair< Ts... > > data_
void add_data(const __FlashStringHelper *key, V &&value)
Trigger< std::string, Ts... > * get_error_trigger()
void add_kv_(FixedVector< TemplatableKeyValuePair< Ts... > > &vec, const char *key, V &&value)
FixedVector< TemplatableKeyValuePair< Ts... > > variables_
Trigger< JsonObjectConst, Ts... > * get_success_trigger_with_response()
FixedVector< TemplatableKeyValuePair< Ts... > > data_template_
void add_data_template(const char *key, V &&value)
static void populate_service_map(VectorType &dest, SourceType &source, FixedVector< std::string > &value_storage, Ts... x)
void add_data_template(const __FlashStringHelper *key, V &&value)
FixedVector< HomeassistantServiceMap > variables
Definition api_pb2.h:1079
FixedVector< HomeassistantServiceMap > data
Definition api_pb2.h:1077
FixedVector< HomeassistantServiceMap > data_template
Definition api_pb2.h:1078
TemplatableStringValue< Ts... > value
TemplatableKeyValuePair(const char *key, T value)
mopeka_std_values val[3]
std::function< void(const ActionResponse &, Ts...)> ActionResponseCallback
const char int const __FlashStringHelper va_list args
Definition log.h:74
static void uint32_t
uint16_t x
Definition tt21100.cpp:5