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