ESPHome 2025.12.2
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) { return to_string(std::forward<T>(val)); }
29
30 // Overloads for string types - needed because std::to_string doesn't support them
31 static std::string value_to_string(char *val) {
32 return val ? std::string(val) : std::string();
33 } // For lambdas returning char* (e.g., itoa)
34 static std::string value_to_string(const char *val) { return std::string(val); } // For lambdas returning .c_str()
35 static std::string value_to_string(const std::string &val) { return val; }
36 static std::string value_to_string(std::string &&val) { return std::move(val); }
37
38 public:
39 TemplatableStringValue() : TemplatableValue<std::string, X...>() {}
40
41 template<typename F, enable_if_t<!is_invocable<F, X...>::value, int> = 0>
43
44 template<typename F, enable_if_t<is_invocable<F, X...>::value, int> = 0>
46 : TemplatableValue<std::string, X...>([f](X... x) -> std::string { return value_to_string(f(x...)); }) {}
47};
48
49template<typename... Ts> class TemplatableKeyValuePair {
50 public:
51 // Default constructor needed for FixedVector::emplace_back()
53
54 // Keys are always string literals from YAML dictionary keys (e.g., "code", "event")
55 // and never templatable values or lambdas. Only the value parameter can be a lambda/template.
56 // Using const char* avoids std::string heap allocation - keys remain in flash.
57 template<typename T> TemplatableKeyValuePair(const char *key, T value) : key(key), value(value) {}
58
59 const char *key{nullptr};
61};
62
63#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
64// Represents the response data from a Home Assistant action
65// Note: This class holds a StringRef to the error_message from the protobuf message.
66// The protobuf message must outlive the ActionResponse (which is guaranteed since
67// the callback is invoked synchronously while the message is on the stack).
69 public:
70 ActionResponse(bool success, const std::string &error_message) : success_(success), error_message_(error_message) {}
71
72#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
73 ActionResponse(bool success, const std::string &error_message, const uint8_t *data, size_t data_len)
74 : success_(success), error_message_(error_message) {
75 if (data == nullptr || data_len == 0)
76 return;
77 this->json_document_ = json::parse_json(data, data_len);
78 }
79#endif
80
81 bool is_success() const { return this->success_; }
82 // Returns reference to error message - can be implicitly converted to std::string if needed
83 const StringRef &get_error_message() const { return this->error_message_; }
84
85#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
86 // Get data as parsed JSON object (const version returns read-only view)
87 JsonObjectConst get_json() const { return this->json_document_.as<JsonObjectConst>(); }
88#endif
89
90 protected:
93#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
94 JsonDocument json_document_;
95#endif
96};
97
98// Callback type for action responses
99template<typename... Ts> using ActionResponseCallback = std::function<void(const ActionResponse &, Ts...)>;
100#endif
101
102template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts...> {
103 public:
104 explicit HomeAssistantServiceCallAction(APIServer *parent, bool is_event) : parent_(parent) {
105 this->flags_.is_event = is_event;
106 }
107
108 template<typename T> void set_service(T service) { this->service_ = service; }
109
110 // Initialize FixedVector members - called from Python codegen with compile-time known sizes.
111 // Must be called before any add_* methods; capacity must match the number of subsequent add_* calls.
112 void init_data(size_t count) { this->data_.init(count); }
113 void init_data_template(size_t count) { this->data_template_.init(count); }
114 void init_variables(size_t count) { this->variables_.init(count); }
115
116 // Keys are always string literals from the Python code generation (e.g., cg.add(var.add_data("tag_id", templ))).
117 // The value parameter can be a lambda/template, but keys are never templatable.
118 // Using const char* for keys avoids std::string heap allocation - keys remain in flash.
119 template<typename V> void add_data(const char *key, V &&value) {
120 this->add_kv_(this->data_, key, std::forward<V>(value));
121 }
122 template<typename V> void add_data_template(const char *key, V &&value) {
123 this->add_kv_(this->data_template_, key, std::forward<V>(value));
124 }
125 template<typename V> void add_variable(const char *key, V &&value) {
126 this->add_kv_(this->variables_, key, std::forward<V>(value));
127 }
128
129#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
130 template<typename T> void set_response_template(T response_template) {
131 this->response_template_ = response_template;
132 this->flags_.has_response_template = true;
133 }
134
135 void set_wants_status() { this->flags_.wants_status = true; }
136 void set_wants_response() { this->flags_.wants_response = true; }
137
138#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
139 Trigger<JsonObjectConst, Ts...> *get_success_trigger_with_response() const {
140 return this->success_trigger_with_response_;
141 }
142#endif
143 Trigger<Ts...> *get_success_trigger() const { return this->success_trigger_; }
144 Trigger<std::string, Ts...> *get_error_trigger() const { return this->error_trigger_; }
145#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
146
147 void play(const Ts &...x) override {
149 std::string service_value = this->service_.value(x...);
150 resp.set_service(StringRef(service_value));
151 resp.is_event = this->flags_.is_event;
152 this->populate_service_map(resp.data, this->data_, x...);
153 this->populate_service_map(resp.data_template, this->data_template_, x...);
154 this->populate_service_map(resp.variables, this->variables_, x...);
155
156#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
157 if (this->flags_.wants_status) {
158 // Generate a unique call ID for this service call
159 static uint32_t call_id_counter = 1;
160 uint32_t call_id = call_id_counter++;
161 resp.call_id = call_id;
162#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
163 if (this->flags_.wants_response) {
164 resp.wants_response = true;
165 // Set response template if provided
166 if (this->flags_.has_response_template) {
167 std::string response_template_value = this->response_template_.value(x...);
168 resp.response_template = response_template_value;
169 }
170 }
171#endif
172
173 auto captured_args = std::make_tuple(x...);
174 this->parent_->register_action_response_callback(call_id, [this, captured_args](const ActionResponse &response) {
175 std::apply(
176 [this, &response](auto &&...args) {
177 if (response.is_success()) {
178#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
179 if (this->flags_.wants_response) {
180 this->success_trigger_with_response_->trigger(response.get_json(), args...);
181 } else
182#endif
183 {
184 this->success_trigger_->trigger(args...);
185 }
186 } else {
187 this->error_trigger_->trigger(response.get_error_message(), args...);
188 }
189 },
190 captured_args);
191 });
192 }
193#endif
194
195 this->parent_->send_homeassistant_action(resp);
196 }
197
198 protected:
199 // Helper to add key-value pairs to FixedVectors
200 // Keys are always string literals (const char*), values can be lambdas/templates
201 template<typename V> void add_kv_(FixedVector<TemplatableKeyValuePair<Ts...>> &vec, const char *key, V &&value) {
202 auto &kv = vec.emplace_back();
203 kv.key = key;
204 kv.value = std::forward<V>(value);
205 }
206
207 template<typename VectorType, typename SourceType>
208 static void populate_service_map(VectorType &dest, SourceType &source, Ts... x) {
209 dest.init(source.size());
210 for (auto &it : source) {
211 auto &kv = dest.emplace_back();
212 kv.set_key(StringRef(it.key));
213 kv.value = it.value.value(x...);
214 }
215 }
216
218 TemplatableStringValue<Ts...> service_{};
222#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
223#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
224 TemplatableStringValue<Ts...> response_template_{""};
225 Trigger<JsonObjectConst, Ts...> *success_trigger_with_response_ = new Trigger<JsonObjectConst, Ts...>();
226#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
227 Trigger<Ts...> *success_trigger_ = new Trigger<Ts...>();
228 Trigger<std::string, Ts...> *error_trigger_ = new Trigger<std::string, Ts...>();
229#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
230
231 struct Flags {
232 uint8_t is_event : 1;
233 uint8_t wants_status : 1;
234 uint8_t wants_response : 1;
236 uint8_t reserved : 5;
237 } flags_{0};
238};
239
240} // namespace esphome::api
241
242#endif
243#endif
Fixed-capacity vector - allocates once at runtime, never reallocates This avoids std::vector template...
Definition helpers.h:184
StringRef is a reference to a string owned by something else.
Definition string_ref.h:22
ActionResponse(bool success, const std::string &error_message)
ActionResponse(bool success, const std::string &error_message, const uint8_t *data, size_t data_len)
const StringRef & get_error_message() const
JsonObjectConst get_json() const
HomeAssistantServiceCallAction(APIServer *parent, bool is_event)
Trigger< JsonObjectConst, Ts... > * get_success_trigger_with_response() const
FixedVector< TemplatableKeyValuePair< Ts... > > data_
void add_kv_(FixedVector< TemplatableKeyValuePair< Ts... > > &vec, const char *key, V &&value)
FixedVector< TemplatableKeyValuePair< Ts... > > variables_
FixedVector< TemplatableKeyValuePair< Ts... > > data_template_
void add_data_template(const char *key, V &&value)
static void populate_service_map(VectorType &dest, SourceType &source, Ts... x)
Trigger< std::string, Ts... > * get_error_trigger() const
FixedVector< HomeassistantServiceMap > variables
Definition api_pb2.h:1123
FixedVector< HomeassistantServiceMap > data
Definition api_pb2.h:1121
FixedVector< HomeassistantServiceMap > data_template
Definition api_pb2.h:1122
void set_service(const StringRef &ref)
Definition api_pb2.h:1120
TemplatableStringValue< Ts... > value
TemplatableKeyValuePair(const char *key, T value)
mopeka_std_values val[4]
std::function< void(const ActionResponse &, Ts...)> ActionResponseCallback
uint16_t x
Definition tt21100.cpp:5