ESPHome 2026.8.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.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.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_;
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.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.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_;
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...> final : public UserServiceBase<Ts...>,
168 public Trigger<Ts...> {
169 public:
170 UserServiceTrigger(const char *name, const std::array<const char *, sizeof...(Ts)> &arg_names)
171 : UserServiceBase<Ts...>(name, arg_names, enums::SUPPORTS_RESPONSE_NONE) {}
172
173 protected:
174 void execute(uint32_t /*call_id*/, bool /*return_response*/, Ts... x) override { this->trigger(x...); }
175};
176
177// Specialization for OPTIONAL - call_id and return_response trigger arguments
178template<typename... Ts>
179class UserServiceTrigger<enums::SUPPORTS_RESPONSE_OPTIONAL, Ts...> final : public UserServiceBase<Ts...>,
180 public Trigger<uint32_t, bool, Ts...> {
181 public:
182 UserServiceTrigger(const char *name, const std::array<const char *, sizeof...(Ts)> &arg_names)
183 : UserServiceBase<Ts...>(name, arg_names, enums::SUPPORTS_RESPONSE_OPTIONAL) {}
184
185 protected:
186 void execute(uint32_t call_id, bool return_response, Ts... x) override {
187 this->trigger(call_id, return_response, x...);
188 }
189};
190
191// Specialization for ONLY - just call_id trigger argument
192template<typename... Ts>
193class UserServiceTrigger<enums::SUPPORTS_RESPONSE_ONLY, Ts...> final : public UserServiceBase<Ts...>,
194 public Trigger<uint32_t, Ts...> {
195 public:
196 UserServiceTrigger(const char *name, const std::array<const char *, sizeof...(Ts)> &arg_names)
197 : UserServiceBase<Ts...>(name, arg_names, enums::SUPPORTS_RESPONSE_ONLY) {}
198
199 protected:
200 void execute(uint32_t call_id, bool /*return_response*/, Ts... x) override { this->trigger(call_id, x...); }
201};
202
203// Specialization for STATUS - just call_id trigger argument (reports success/error without data)
204template<typename... Ts>
205class UserServiceTrigger<enums::SUPPORTS_RESPONSE_STATUS, Ts...> final : public UserServiceBase<Ts...>,
206 public Trigger<uint32_t, Ts...> {
207 public:
208 UserServiceTrigger(const char *name, const std::array<const char *, sizeof...(Ts)> &arg_names)
209 : UserServiceBase<Ts...>(name, arg_names, enums::SUPPORTS_RESPONSE_STATUS) {}
210
211 protected:
212 void execute(uint32_t call_id, bool /*return_response*/, Ts... x) override { this->trigger(call_id, x...); }
213};
214
215} // namespace esphome::api
216#endif // USE_API_USER_DEFINED_ACTIONS
217
218#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES
219// Include full definition of APIServer for template implementation
220// Must be outside namespace to avoid including STL headers inside namespace
221#include "api_server.h"
222
223namespace esphome::api {
224
225template<typename... Ts> class APIRespondAction final : public Action<Ts...> {
226 public:
227 explicit APIRespondAction(APIServer *parent) : parent_(parent) {}
228
229 template<typename V> void set_success(V success) { this->success_ = success; }
230 template<typename V> void set_error_message(V error) { this->error_message_ = error; }
231 void set_is_optional_mode(bool is_optional) { this->is_optional_mode_ = is_optional; }
232
233#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
234 void set_data(std::function<void(Ts..., JsonObject)> &&func) {
235 this->json_builder_ = std::move(func);
236 this->has_data_ = true;
237 }
238#endif
239
240 void play(const Ts &...x) override {
241 // Extract call_id from first argument - it's always first for optional/only/status modes
242 auto args = std::make_tuple(x...);
243 uint32_t call_id = std::get<0>(args);
244
245 bool success = this->success_.value(x...);
246 std::string error_message = this->error_message_.value(x...);
247
248#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
249 if (this->has_data_) {
250 // For optional mode, check return_response (second arg) to decide if client wants data
251 // Use nested if constexpr to avoid compile error when tuple doesn't have enough elements
252 // (std::tuple_element_t is evaluated before the && short-circuit, so we must nest)
253 if constexpr (sizeof...(Ts) >= 2) {
254 if constexpr (std::is_same_v<std::tuple_element_t<1, std::tuple<Ts...>>, bool>) {
255 if (this->is_optional_mode_) {
256 bool return_response = std::get<1>(args);
257 if (!return_response) {
258 // Client doesn't want response data, just send success/error
259 this->parent_->send_action_response(call_id, success, StringRef(error_message));
260 return;
261 }
262 }
263 }
264 }
265 // Build and send JSON response
266 json::JsonBuilder builder;
267 this->json_builder_(x..., builder.root());
268 auto json_buf = builder.serialize();
269 this->parent_->send_action_response(call_id, success, StringRef(error_message),
270 reinterpret_cast<const uint8_t *>(json_buf.data()), json_buf.size());
271 return;
272 }
273#endif
274 this->parent_->send_action_response(call_id, success, StringRef(error_message));
275 }
276
277 protected:
279 TemplatableFn<bool, Ts...> success_{[](Ts...) -> bool { return true; }};
280 TemplatableValue<std::string, Ts...> error_message_{""};
281#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
282 std::function<void(Ts..., JsonObject)> json_builder_;
283 bool has_data_{false};
284#endif
285 bool is_optional_mode_{false};
286};
287
288// Action to unregister a service call after execution completes
289// Automatically appended to the end of action lists for non-none response modes
290template<typename... Ts> class APIUnregisterServiceCallAction final : public Action<Ts...> {
291 public:
292 explicit APIUnregisterServiceCallAction(APIServer *parent) : parent_(parent) {}
293
294 void play(const Ts &...x) override {
295 // Extract call_id from first argument - same convention as APIRespondAction
296 auto args = std::make_tuple(x...);
297 uint32_t call_id = std::get<0>(args);
298 if (call_id != 0) {
300 }
301 }
302
303 protected:
305};
306
307} // namespace esphome::api
308#endif // USE_API_USER_DEFINED_ACTION_RESPONSES
StringRef is a reference to a string owned by something else.
Definition string_ref.h:26
constexpr size_type size() const
Definition string_ref.h:74
Function-pointer-only templatable storage (4 bytes on 32-bit).
Definition automation.h:19
T value(X... x) const
Definition automation.h:58
Primary TemplatableValue: stores either a constant value or a function pointer.
Definition automation.h:94
T value(X... x) const
Definition automation.h:175
void play(const Ts &...x) override
TemplatableFn< 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 send_action_response(uint32_t action_call_id, bool success, StringRef error_message)
void unregister_active_action_call(uint32_t action_call_id)
FixedVector< ExecuteServiceArgument > args
Definition api_pb2.h:1292
enums::SupportsResponseType supports_response
Definition api_pb2.h:1254
FixedVector< ListEntitiesServicesArgument > args
Definition api_pb2.h:1253
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:169
SerializationBuffer serialize()
Serialize the JSON document to a SerializationBuffer (stack-first allocation) Uses 512-byte stack buf...
Definition json_util.cpp:69
enums::ServiceArgType to_service_arg_type()
T get_execute_arg_value(const ExecuteServiceArgument &arg)
const char int const __FlashStringHelper va_list args
Definition log.h:74
uint32_t fnv1_hash(const char *str)
Calculate a FNV-1 hash of str.
Definition helpers.cpp:160
STL namespace.
static void uint32_t
uint16_t x
Definition tt21100.cpp:5