ESPHome 2026.10.1
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 <string>
8#include <type_traits>
9#include <utility>
10#include "api_pb2.h"
11#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
13#endif
18
19namespace esphome::api {
20
21// Converts a lambda result to the string sent to Home Assistant
22template<typename T>
23requires(!std::is_pointer_v<std::remove_cvref_t<T>>) std::string field_to_string(T &&val) {
24 return to_string(std::forward<T>(val)); // NOLINT
25}
26inline std::string field_to_string(const char *val) { return val ? std::string(val) : std::string(); }
27inline std::string field_to_string(std::string val) { return val; }
28inline std::string field_to_string(StringRef val) { return val.str(); }
29
32template<typename... Ts> struct HomeAssistantField {
33 const char *key;
34 const char *value;
35 std::string (*fn)(const Ts &...);
36
37 template<typename F> static constexpr HomeAssistantField from_lambda(const char *key, F /*lambda*/) {
38 return {key, nullptr, &call_lambda<F>};
39 }
40 template<typename F> static std::string call_lambda(const Ts &...x) { return field_to_string(F{}(x...)); }
41};
42
43#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
44// Represents the response data from a Home Assistant action
45// Note: This class holds a StringRef to the error_message from the protobuf message.
46// The protobuf message must outlive the ActionResponse (which is guaranteed since
47// the callback is invoked synchronously while the message is on the stack).
49 public:
50 ActionResponse(bool success, StringRef error_message) : success_(success), error_message_(error_message) {}
51
52#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
53 ActionResponse(bool success, StringRef error_message, const uint8_t *data, size_t data_len)
54 : success_(success), error_message_(error_message) {
55 if (data == nullptr || data_len == 0)
56 return;
57 JsonDocument tmp = json::parse_json(data, data_len);
58 swap(this->json_document_, tmp);
59 }
60#endif
61
62 bool is_success() const { return this->success_; }
63 // Returns reference to error message - can be implicitly converted to std::string if needed
64 const StringRef &get_error_message() const { return this->error_message_; }
65
66#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
67 // Get data as parsed JSON object (const version returns read-only view)
68 JsonObjectConst get_json() const { return this->json_document_.as<JsonObjectConst>(); }
69#endif
70
71 protected:
74#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
75 JsonDocument json_document_;
76#endif
77};
78
79// Callback type for action responses
80template<typename... Ts> using ActionResponseCallback = std::function<void(const ActionResponse &, Ts...)>;
81#endif
82
83template<typename... Ts> class HomeAssistantServiceCallAction final : public Action<Ts...> {
84 public:
85 using Field = HomeAssistantField<Ts...>;
86
89 HomeAssistantServiceCallAction(APIServer *parent, bool is_event, const Field *fields, uint8_t data_count,
90 uint8_t data_template_count, uint8_t variables_count)
91 : parent_(parent),
92 fields_(fields),
93 data_count_(data_count),
94 data_template_count_(data_template_count),
95 variables_count_(variables_count) {
96 this->flags_.is_event = is_event;
97 }
98
99#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
100 template<typename T> void set_response_template(T response_template) {
101 this->response_template_ = response_template;
102 this->flags_.has_response_template = true;
103 }
104
105 void set_wants_status() { this->flags_.wants_status = true; }
106 void set_wants_response() { this->flags_.wants_response = true; }
107
108#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
110#endif
111 Trigger<Ts...> *get_success_trigger() { return &this->success_trigger_; }
112 Trigger<std::string, Ts...> *get_error_trigger() { return &this->error_trigger_; }
113#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
114
115 void play(const Ts &...x) override {
116 const Field *fields = this->fields_;
117 const size_t total = 1 + this->data_count_ + this->data_template_count_ + this->variables_count_;
118
119 // Lambda results, and on ESP8266 the RAM copies of the flash strings, must live until the send
120 size_t lambda_count = 0;
121#ifdef USE_ESP8266
122 size_t flash_len = 0;
123#endif
124 for (size_t i = 0; i < total; i++) {
125 lambda_count += fields[i].fn != nullptr;
126#ifdef USE_ESP8266
127 if (fields[i].fn == nullptr)
128 flash_len += ESPHOME_strlen_P(fields[i].value);
129 if (fields[i].key != nullptr)
130 flash_len += ESPHOME_strlen_P(fields[i].key);
131#endif
132 }
134 results.init(lambda_count);
135#ifdef USE_ESP8266
136 SmallBufferWithHeapFallback<128, char> flash_copy(flash_len);
137 char *cursor = flash_copy.get();
138#endif
139 auto string_ref = [&](const char *str) {
140#ifdef USE_ESP8266
141 size_t len = ESPHOME_strlen_P(str);
142 memcpy_P(cursor, str, len);
143 StringRef ref(cursor, len);
144 cursor += len;
145 return ref;
146#else
147 return StringRef(str);
148#endif
149 };
150 auto value_ref = [&](const Field &field) {
151 if (field.fn == nullptr)
152 return string_ref(field.value);
153 results.push_back(field.fn(x...));
154 return StringRef(results.back());
155 };
156 auto fill = [&](FixedVector<HomeassistantServiceMap> &dest, uint8_t count) {
157 dest.init(count);
158 for (uint8_t i = 0; i < count; i++, fields++) {
159 auto &kv = dest.emplace_back();
160 kv.key = string_ref(fields->key);
161 kv.value = value_ref(*fields);
162 }
163 };
164
166 resp.service = value_ref(*fields++);
167 resp.is_event = this->flags_.is_event;
168 fill(resp.data, this->data_count_);
169 fill(resp.data_template, this->data_template_count_);
170 fill(resp.variables, this->variables_count_);
171
172#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
173#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
174 // IMPORTANT: Declare at outer scope so it lives until send_homeassistant_action returns.
175 std::string response_template_value;
176#endif
177 if (this->flags_.wants_status) {
178 // Generate a unique call ID for this service call
179 static uint32_t call_id_counter = 1;
180 uint32_t call_id = call_id_counter++;
181 resp.call_id = call_id;
182#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
183 if (this->flags_.wants_response) {
184 resp.wants_response = true;
185 // Set response template if provided
186 if (this->flags_.has_response_template) {
187 response_template_value = this->response_template_.value(x...);
188 resp.response_template = StringRef(response_template_value);
189 }
190 }
191#endif
192
193 auto captured_args = std::make_tuple(x...);
194 this->parent_->register_action_response_callback(call_id, [this, captured_args](const ActionResponse &response) {
195 std::apply(
196 [this, &response](auto &&...args) {
197 if (response.is_success()) {
198#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
199 if (this->flags_.wants_response) {
200 this->success_trigger_with_response_.trigger(response.get_json(), args...);
201 } else
202#endif
203 {
204 this->success_trigger_.trigger(args...);
205 }
206 } else {
207 this->error_trigger_.trigger(response.get_error_message(), args...);
208 }
209 },
210 captured_args);
211 });
212 }
213#endif
214
216 }
217
218 protected:
221 uint8_t data_count_;
224 struct Flags {
225 uint8_t is_event : 1;
226 uint8_t wants_status : 1;
227 uint8_t wants_response : 1;
229 uint8_t reserved : 4;
230 } flags_{0};
231#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
232#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
235#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
237 Trigger<std::string, Ts...> error_trigger_;
238#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
239};
240
241} // namespace esphome::api
242
243#endif
244#endif
Fixed-capacity vector - sized once through init() or try_init(); push_back never reallocates This avo...
Definition helpers.h:621
T & back()
Access last element (no bounds checking - matches std::vector behavior) Caller must ensure vector is ...
Definition helpers.h:785
T & emplace_back(Args &&...args)
Emplace element without bounds checking - constructs in-place with arguments Caller must ensure suffi...
Definition helpers.h:771
void push_back(const T &value)
Add element without bounds checking Caller must ensure sufficient capacity was allocated via init() S...
Definition helpers.h:748
void init(size_t n)
Definition helpers.h:711
Helper class for efficient buffer allocation - uses stack for small sizes, heap for large This is use...
Definition helpers.h:824
StringRef is a reference to a string owned by something else.
Definition string_ref.h:30
Primary TemplatableValue: stores either a constant value or a function pointer.
Definition automation.h:159
T value(X... x) const
Definition automation.h:240
void trigger(const Ts &...x) ESPHOME_ALWAYS_INLINE
Inform the parent automation that the event has triggered.
Definition automation.h:528
void register_action_response_callback(uint32_t call_id, ActionResponseCallback callback)
void send_homeassistant_action(const HomeassistantActionRequest &call)
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_
HomeAssistantServiceCallAction(APIServer *parent, bool is_event, const Field *fields, uint8_t data_count, uint8_t data_template_count, uint8_t variables_count)
fields is a codegen table: the action or event name (no key), then the data, data_template and variab...
Trigger< std::string, Ts... > * get_error_trigger()
TemplatableValue< std::string, Ts... > response_template_
Trigger< JsonObjectConst, Ts... > * get_success_trigger_with_response()
struct esphome::api::HomeAssistantServiceCallAction::Flags flags_
FixedVector< HomeassistantServiceMap > variables
Definition api_pb2.h:1416
FixedVector< HomeassistantServiceMap > data
Definition api_pb2.h:1414
FixedVector< HomeassistantServiceMap > data_template
Definition api_pb2.h:1415
unsigned count
Definition gif.cpp:51
mopeka_std_values val[3]
std::function< void(const ActionResponse &, Ts...)> ActionResponseCallback
std::string field_to_string(T &&val)
bool parse_json(const std::string &data, const json_parse_t &f)
Parse a JSON string and run the provided json parse function if it's valid.
Definition json_util.cpp:28
const void size_t len
Definition hal.h:64
static void uint32_t
A key and value from codegen; on ESP8266 the table and its strings are in flash.
static std::string call_lambda(const Ts &...x)
static constexpr HomeAssistantField from_lambda(const char *key, F)
std::string(* fn)(const Ts &...)
uint16_t x
Definition tt21100.cpp:5