ESPHome 2026.1.0-dev
Loading...
Searching...
No Matches
user_services.h
Go to the documentation of this file.
1#pragma once
2
3#include <tuple>
4#include <utility>
5#include <vector>
6
7#include "api_pb2.h"
10#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
12#endif
13
14#ifdef USE_API_USER_DEFINED_ACTIONS
15namespace esphome::api {
16
17// Forward declaration - full definition in api_server.h
18class APIServer;
19
21 public:
23
24 virtual bool execute_service(const ExecuteServiceRequest &req) = 0;
25#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES
26 // Overload that accepts server-generated action_call_id (avoids client call_id collisions)
27 virtual bool execute_service(const ExecuteServiceRequest &req, uint32_t action_call_id) = 0;
28#endif
29
30 bool is_internal() { return false; }
31};
32
33template<typename T> T get_execute_arg_value(const ExecuteServiceArgument &arg);
34
36
37// Base class for YAML-defined services (most common case)
38// Stores only pointers to string literals in flash - no heap allocation
39template<typename... Ts> class UserServiceBase : public UserServiceDescriptor {
40 public:
41 UserServiceBase(const char *name, const std::array<const char *, sizeof...(Ts)> &arg_names,
43 : name_(name), arg_names_(arg_names), supports_response_(supports_response) {
44 this->key_ = fnv1_hash(name);
45 }
46
49 msg.set_name(StringRef(this->name_));
50 msg.key = this->key_;
52 std::array<enums::ServiceArgType, sizeof...(Ts)> arg_types = {to_service_arg_type<Ts>()...};
53 msg.args.init(sizeof...(Ts));
54 for (size_t i = 0; i < sizeof...(Ts); i++) {
55 auto &arg = msg.args.emplace_back();
56 arg.type = arg_types[i];
57 arg.set_name(StringRef(this->arg_names_[i]));
58 }
59 return msg;
60 }
61
62 bool execute_service(const ExecuteServiceRequest &req) override {
63 if (req.key != this->key_)
64 return false;
65 if (req.args.size() != sizeof...(Ts))
66 return false;
67#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES
68 this->execute_(req.args, req.call_id, req.return_response, std::make_index_sequence<sizeof...(Ts)>{});
69#else
70 this->execute_(req.args, 0, false, std::make_index_sequence<sizeof...(Ts)>{});
71#endif
72 return true;
73 }
74
75#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES
76 bool execute_service(const ExecuteServiceRequest &req, uint32_t action_call_id) override {
77 if (req.key != this->key_)
78 return false;
79 if (req.args.size() != sizeof...(Ts))
80 return false;
81 this->execute_(req.args, action_call_id, req.return_response, std::make_index_sequence<sizeof...(Ts)>{});
82 return true;
83 }
84#endif
85
86 protected:
87 virtual void execute(uint32_t call_id, bool return_response, Ts... x) = 0;
88 template<typename ArgsContainer, size_t... S>
89 void execute_(const ArgsContainer &args, uint32_t call_id, bool return_response, std::index_sequence<S...> /*type*/) {
90 this->execute(call_id, return_response, (get_execute_arg_value<Ts>(args[S]))...);
91 }
92
93 // Pointers to string literals in flash - no heap allocation
94 const char *name_;
95 std::array<const char *, sizeof...(Ts)> arg_names_;
96 uint32_t key_{0};
98};
99
100// Separate class for custom_api_device services (rare case)
101// Stores copies of runtime-generated names
102template<typename... Ts> class UserServiceDynamic : public UserServiceDescriptor {
103 public:
104 UserServiceDynamic(std::string name, const std::array<std::string, sizeof...(Ts)> &arg_names)
105 : name_(std::move(name)), arg_names_(arg_names) {
106 this->key_ = fnv1_hash(this->name_.c_str());
107 }
108
111 msg.set_name(StringRef(this->name_));
112 msg.key = this->key_;
113 msg.supports_response = enums::SUPPORTS_RESPONSE_NONE; // Dynamic services don't support responses yet
114 std::array<enums::ServiceArgType, sizeof...(Ts)> arg_types = {to_service_arg_type<Ts>()...};
115 msg.args.init(sizeof...(Ts));
116 for (size_t i = 0; i < sizeof...(Ts); i++) {
117 auto &arg = msg.args.emplace_back();
118 arg.type = arg_types[i];
119 arg.set_name(StringRef(this->arg_names_[i]));
120 }
121 return msg;
122 }
123
124 bool execute_service(const ExecuteServiceRequest &req) override {
125 if (req.key != this->key_)
126 return false;
127 if (req.args.size() != sizeof...(Ts))
128 return false;
129#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES
130 this->execute_(req.args, req.call_id, req.return_response, std::make_index_sequence<sizeof...(Ts)>{});
131#else
132 this->execute_(req.args, 0, false, std::make_index_sequence<sizeof...(Ts)>{});
133#endif
134 return true;
135 }
136
137#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES
138 // Dynamic services don't support responses yet, but need to implement the interface
139 bool execute_service(const ExecuteServiceRequest &req, uint32_t action_call_id) override {
140 if (req.key != this->key_)
141 return false;
142 if (req.args.size() != sizeof...(Ts))
143 return false;
144 this->execute_(req.args, action_call_id, req.return_response, std::make_index_sequence<sizeof...(Ts)>{});
145 return true;
146 }
147#endif
148
149 protected:
150 virtual void execute(uint32_t call_id, bool return_response, Ts... x) = 0;
151 template<typename ArgsContainer, size_t... S>
152 void execute_(const ArgsContainer &args, uint32_t call_id, bool return_response, std::index_sequence<S...> /*type*/) {
153 this->execute(call_id, return_response, (get_execute_arg_value<Ts>(args[S]))...);
154 }
155
156 // Heap-allocated strings for runtime-generated names
157 std::string name_;
158 std::array<std::string, sizeof...(Ts)> arg_names_;
159 uint32_t key_{0};
160};
161
162// Primary template declaration
163template<enums::SupportsResponseType Mode, typename... Ts> class UserServiceTrigger;
164
165// Specialization for NONE - no extra trigger arguments
166template<typename... Ts>
167class UserServiceTrigger<enums::SUPPORTS_RESPONSE_NONE, Ts...> : public UserServiceBase<Ts...>, public Trigger<Ts...> {
168 public:
169 UserServiceTrigger(const char *name, const std::array<const char *, sizeof...(Ts)> &arg_names)
170 : UserServiceBase<Ts...>(name, arg_names, enums::SUPPORTS_RESPONSE_NONE) {}
171
172 protected:
173 void execute(uint32_t /*call_id*/, bool /*return_response*/, Ts... x) override { this->trigger(x...); }
174};
175
176// Specialization for OPTIONAL - call_id and return_response trigger arguments
177template<typename... Ts>
178class UserServiceTrigger<enums::SUPPORTS_RESPONSE_OPTIONAL, Ts...> : public UserServiceBase<Ts...>,
179 public Trigger<uint32_t, bool, Ts...> {
180 public:
181 UserServiceTrigger(const char *name, const std::array<const char *, sizeof...(Ts)> &arg_names)
182 : UserServiceBase<Ts...>(name, arg_names, enums::SUPPORTS_RESPONSE_OPTIONAL) {}
183
184 protected:
185 void execute(uint32_t call_id, bool return_response, Ts... x) override {
186 this->trigger(call_id, return_response, x...);
187 }
188};
189
190// Specialization for ONLY - just call_id trigger argument
191template<typename... Ts>
192class UserServiceTrigger<enums::SUPPORTS_RESPONSE_ONLY, Ts...> : public UserServiceBase<Ts...>,
193 public Trigger<uint32_t, Ts...> {
194 public:
195 UserServiceTrigger(const char *name, const std::array<const char *, sizeof...(Ts)> &arg_names)
196 : UserServiceBase<Ts...>(name, arg_names, enums::SUPPORTS_RESPONSE_ONLY) {}
197
198 protected:
199 void execute(uint32_t call_id, bool /*return_response*/, Ts... x) override { this->trigger(call_id, x...); }
200};
201
202// Specialization for STATUS - just call_id trigger argument (reports success/error without data)
203template<typename... Ts>
204class UserServiceTrigger<enums::SUPPORTS_RESPONSE_STATUS, Ts...> : public UserServiceBase<Ts...>,
205 public Trigger<uint32_t, Ts...> {
206 public:
207 UserServiceTrigger(const char *name, const std::array<const char *, sizeof...(Ts)> &arg_names)
208 : UserServiceBase<Ts...>(name, arg_names, enums::SUPPORTS_RESPONSE_STATUS) {}
209
210 protected:
211 void execute(uint32_t call_id, bool /*return_response*/, Ts... x) override { this->trigger(call_id, x...); }
212};
213
214} // namespace esphome::api
215#endif // USE_API_USER_DEFINED_ACTIONS
216
217#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES
218// Include full definition of APIServer for template implementation
219// Must be outside namespace to avoid including STL headers inside namespace
220#include "api_server.h"
221
222namespace esphome::api {
223
224template<typename... Ts> class APIRespondAction : public Action<Ts...> {
225 public:
226 explicit APIRespondAction(APIServer *parent) : parent_(parent) {}
227
228 template<typename V> void set_success(V success) { this->success_ = success; }
229 template<typename V> void set_error_message(V error) { this->error_message_ = error; }
230 void set_is_optional_mode(bool is_optional) { this->is_optional_mode_ = is_optional; }
231
232#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
233 void set_data(std::function<void(Ts..., JsonObject)> func) {
234 this->json_builder_ = std::move(func);
235 this->has_data_ = true;
236 }
237#endif
238
239 void play(const Ts &...x) override {
240 // Extract call_id from first argument - it's always first for optional/only/status modes
241 auto args = std::make_tuple(x...);
242 uint32_t call_id = std::get<0>(args);
243
244 bool success = this->success_.value(x...);
245 std::string error_message = this->error_message_.value(x...);
246
247#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
248 if (this->has_data_) {
249 // For optional mode, check return_response (second arg) to decide if client wants data
250 // Use nested if constexpr to avoid compile error when tuple doesn't have enough elements
251 // (std::tuple_element_t is evaluated before the && short-circuit, so we must nest)
252 if constexpr (sizeof...(Ts) >= 2) {
253 if constexpr (std::is_same_v<std::tuple_element_t<1, std::tuple<Ts...>>, bool>) {
254 if (this->is_optional_mode_) {
255 bool return_response = std::get<1>(args);
256 if (!return_response) {
257 // Client doesn't want response data, just send success/error
258 this->parent_->send_action_response(call_id, success, error_message);
259 return;
260 }
261 }
262 }
263 }
264 // Build and send JSON response
265 json::JsonBuilder builder;
266 this->json_builder_(x..., builder.root());
267 std::string json_str = builder.serialize();
268 this->parent_->send_action_response(call_id, success, error_message,
269 reinterpret_cast<const uint8_t *>(json_str.data()), json_str.size());
270 return;
271 }
272#endif
273 this->parent_->send_action_response(call_id, success, error_message);
274 }
275
276 protected:
278 TemplatableValue<bool, Ts...> success_{true};
279 TemplatableValue<std::string, Ts...> error_message_{""};
280#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
281 std::function<void(Ts..., JsonObject)> json_builder_;
282 bool has_data_{false};
283#endif
284 bool is_optional_mode_{false};
285};
286
287// Action to unregister a service call after execution completes
288// Automatically appended to the end of action lists for non-none response modes
289template<typename... Ts> class APIUnregisterServiceCallAction : public Action<Ts...> {
290 public:
291 explicit APIUnregisterServiceCallAction(APIServer *parent) : parent_(parent) {}
292
293 void play(const Ts &...x) override {
294 // Extract call_id from first argument - same convention as APIRespondAction
295 auto args = std::make_tuple(x...);
296 uint32_t call_id = std::get<0>(args);
297 if (call_id != 0) {
299 }
300 }
301
302 protected:
304};
305
306} // namespace esphome::api
307#endif // USE_API_USER_DEFINED_ACTION_RESPONSES
StringRef is a reference to a string owned by something else.
Definition string_ref.h:22
void play(const Ts &...x) override
TemplatableValue< bool, Ts... > success_
void set_data(std::function< void(Ts..., JsonObject)> func)
void set_is_optional_mode(bool is_optional)
APIRespondAction(APIServer *parent)
std::function< void(Ts..., JsonObject)> json_builder_
TemplatableValue< std::string, Ts... > error_message_
void unregister_active_action_call(uint32_t action_call_id)
void send_action_response(uint32_t action_call_id, bool success, const std::string &error_message)
FixedVector< ExecuteServiceArgument > args
Definition api_pb2.h:1337
enums::SupportsResponseType supports_response
Definition api_pb2.h:1299
FixedVector< ListEntitiesServicesArgument > args
Definition api_pb2.h:1298
void set_name(const StringRef &ref)
Definition api_pb2.h:1296
bool execute_service(const ExecuteServiceRequest &req, uint32_t action_call_id) override
void execute_(const ArgsContainer &args, uint32_t call_id, bool return_response, std::index_sequence< S... >)
enums::SupportsResponseType supports_response_
virtual void execute(uint32_t call_id, bool return_response, Ts... x)=0
std::array< const char *, sizeof...(Ts)> arg_names_
ListEntitiesServicesResponse encode_list_service_response() override
bool execute_service(const ExecuteServiceRequest &req) override
UserServiceBase(const char *name, const std::array< const char *, sizeof...(Ts)> &arg_names, enums::SupportsResponseType supports_response=enums::SUPPORTS_RESPONSE_NONE)
virtual ListEntitiesServicesResponse encode_list_service_response()=0
virtual bool execute_service(const ExecuteServiceRequest &req, uint32_t action_call_id)=0
virtual bool execute_service(const ExecuteServiceRequest &req)=0
UserServiceDynamic(std::string name, const std::array< std::string, sizeof...(Ts)> &arg_names)
bool execute_service(const ExecuteServiceRequest &req) override
void execute_(const ArgsContainer &args, uint32_t call_id, bool return_response, std::index_sequence< S... >)
bool execute_service(const ExecuteServiceRequest &req, uint32_t action_call_id) override
ListEntitiesServicesResponse encode_list_service_response() override
std::array< std::string, sizeof...(Ts)> arg_names_
virtual void execute(uint32_t call_id, bool return_response, Ts... x)=0
UserServiceTrigger(const char *name, const std::array< const char *, sizeof...(Ts)> &arg_names)
void execute(uint32_t call_id, bool return_response, Ts... x) override
UserServiceTrigger(const char *name, const std::array< const char *, sizeof...(Ts)> &arg_names)
UserServiceTrigger(const char *name, const std::array< const char *, sizeof...(Ts)> &arg_names)
UserServiceTrigger(const char *name, const std::array< const char *, sizeof...(Ts)> &arg_names)
Builder class for creating JSON documents without lambdas.
Definition json_util.h:62
enums::ServiceArgType to_service_arg_type()
T get_execute_arg_value(const ExecuteServiceArgument &arg)
uint32_t fnv1_hash(const char *str)
Calculate a FNV-1 hash of str.
Definition helpers.cpp:147
uint16_t x
Definition tt21100.cpp:5