mirror of
https://github.com/esphome/esphome.git
synced 2025-10-17 09:13:45 +01:00
Split response and error triggers
Simplify variables in response lambdas to JsonObject Use `const char *` for message and parse to json right away
This commit is contained in:
@@ -16,6 +16,7 @@ from esphome.const import (
|
||||
CONF_KEY,
|
||||
CONF_ON_CLIENT_CONNECTED,
|
||||
CONF_ON_CLIENT_DISCONNECTED,
|
||||
CONF_ON_ERROR,
|
||||
CONF_ON_RESPONSE,
|
||||
CONF_PASSWORD,
|
||||
CONF_PORT,
|
||||
@@ -304,14 +305,8 @@ HOMEASSISTANT_ACTION_ACTION_SCHEMA = cv.All(
|
||||
{cv.string: cv.returning_lambda}
|
||||
),
|
||||
cv.Optional(CONF_RESPONSE_TEMPLATE): cv.templatable(cv.string),
|
||||
cv.Optional(CONF_ON_RESPONSE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
HomeAssistantActionResponseTrigger
|
||||
),
|
||||
},
|
||||
single=True,
|
||||
),
|
||||
cv.Optional(CONF_ON_RESPONSE): automation.validate_automation(single=True),
|
||||
cv.Optional(CONF_ON_ERROR): automation.validate_automation(single=True),
|
||||
}
|
||||
),
|
||||
cv.has_exactly_one_key(CONF_SERVICE, CONF_ACTION),
|
||||
@@ -357,17 +352,23 @@ async def homeassistant_service_to_code(
|
||||
|
||||
if on_response := config.get(CONF_ON_RESPONSE):
|
||||
cg.add_define("USE_API_HOMEASSISTANT_ACTION_RESPONSES")
|
||||
trigger = cg.new_Pvariable(
|
||||
on_response[CONF_TRIGGER_ID],
|
||||
template_arg,
|
||||
var,
|
||||
)
|
||||
cg.add(var.set_wants_response())
|
||||
await automation.build_automation(
|
||||
trigger,
|
||||
[(cg.std_shared_ptr.template(ActionResponse), "response"), *args],
|
||||
var.get_response_trigger(),
|
||||
[(cg.JsonObject, "response"), *args],
|
||||
on_response,
|
||||
)
|
||||
|
||||
if on_error := config.get(CONF_ON_ERROR):
|
||||
cg.add_define("USE_API_HOMEASSISTANT_ACTION_RESPONSES")
|
||||
cg.add_define("USE_API_HOMEASSISTANT_ACTION_RESPONSES_ERRORS")
|
||||
cg.add(var.set_wants_response())
|
||||
await automation.build_automation(
|
||||
var.get_error_trigger(),
|
||||
[(cg.std_string, "error"), *args],
|
||||
on_error,
|
||||
)
|
||||
|
||||
return var
|
||||
|
||||
|
||||
|
@@ -780,8 +780,8 @@ message HomeassistantActionRequest {
|
||||
repeated HomeassistantServiceMap data_template = 3;
|
||||
repeated HomeassistantServiceMap variables = 4;
|
||||
bool is_event = 5;
|
||||
uint32 call_id = 6; // Call ID for response tracking
|
||||
string response_template = 7 [(no_zero_copy) = true]; // Optional Jinja template for response processing
|
||||
uint32 call_id = 6 [(field_ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES"]; // Call ID for response tracking
|
||||
string response_template = 7 [(no_zero_copy) = true, (field_ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES"]; // Optional Jinja template for response processing
|
||||
}
|
||||
|
||||
// Message sent by Home Assistant to ESPHome with service call response data
|
||||
@@ -789,12 +789,12 @@ message HomeassistantActionResponse {
|
||||
option (id) = 130;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (no_delay) = true;
|
||||
option (ifdef) = "USE_API_HOMEASSISTANT_SERVICES";
|
||||
option (ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES";
|
||||
|
||||
uint32 call_id = 1; // Matches the call_id from HomeassistantActionRequest
|
||||
bool success = 2; // Whether the service call succeeded
|
||||
string error_message = 3; // Error message if success = false
|
||||
string response_data = 4; // Service response data
|
||||
bytes response_data = 4 [(pointer_to_buffer) = true]; // Service response data
|
||||
}
|
||||
|
||||
// ==================== IMPORT HOME ASSISTANT STATES ====================
|
||||
|
@@ -8,9 +8,9 @@
|
||||
#endif
|
||||
#include <cerrno>
|
||||
#include <cinttypes>
|
||||
#include <utility>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
#include "esphome/components/network/util.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/entity_base.h"
|
||||
@@ -1550,9 +1550,10 @@ void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||
void APIConnection::on_homeassistant_action_response(const HomeassistantActionResponse &msg) {
|
||||
this->parent_->handle_action_response(msg.call_id, msg.success, msg.error_message, msg.response_data);
|
||||
this->parent_->handle_action_response(msg.call_id, msg.success, msg.error_message, msg.response_data,
|
||||
msg.response_data_len);
|
||||
};
|
||||
#endif
|
||||
#ifdef USE_API_NOISE
|
||||
|
@@ -137,8 +137,10 @@ class APIConnection final : public APIServerConnection {
|
||||
return;
|
||||
this->send_message(call, HomeassistantActionRequest::MESSAGE_TYPE);
|
||||
}
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||
void on_homeassistant_action_response(const HomeassistantActionResponse &msg) override;
|
||||
#endif
|
||||
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||
#endif // USE_API_HOMEASSISTANT_SERVICES
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
|
||||
void unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) override;
|
||||
|
@@ -884,8 +884,12 @@ void HomeassistantActionRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_message(4, it, true);
|
||||
}
|
||||
buffer.encode_bool(5, this->is_event);
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||
buffer.encode_uint32(6, this->call_id);
|
||||
#endif
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||
buffer.encode_string(7, this->response_template);
|
||||
#endif
|
||||
}
|
||||
void HomeassistantActionRequest::calculate_size(ProtoSize &size) const {
|
||||
size.add_length(1, this->service_ref_.size());
|
||||
@@ -893,9 +897,15 @@ void HomeassistantActionRequest::calculate_size(ProtoSize &size) const {
|
||||
size.add_repeated_message(1, this->data_template);
|
||||
size.add_repeated_message(1, this->variables);
|
||||
size.add_bool(1, this->is_event);
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||
size.add_uint32(1, this->call_id);
|
||||
#endif
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||
size.add_length(1, this->response_template.size());
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||
bool HomeassistantActionResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 1:
|
||||
@@ -914,9 +924,12 @@ bool HomeassistantActionResponse::decode_length(uint32_t field_id, ProtoLengthDe
|
||||
case 3:
|
||||
this->error_message = value.as_string();
|
||||
break;
|
||||
case 4:
|
||||
this->response_data = value.as_string();
|
||||
case 4: {
|
||||
// Use raw data directly to avoid allocation
|
||||
this->response_data = value.data();
|
||||
this->response_data_len = value.size();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
@@ -1114,8 +1114,12 @@ class HomeassistantActionRequest final : public ProtoMessage {
|
||||
std::vector<HomeassistantServiceMap> data_template{};
|
||||
std::vector<HomeassistantServiceMap> variables{};
|
||||
bool is_event{false};
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||
uint32_t call_id{0};
|
||||
#endif
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||
std::string response_template{};
|
||||
#endif
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
@@ -1124,17 +1128,20 @@ class HomeassistantActionRequest final : public ProtoMessage {
|
||||
|
||||
protected:
|
||||
};
|
||||
#endif
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||
class HomeassistantActionResponse final : public ProtoDecodableMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 130;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 24;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 34;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "homeassistant_action_response"; }
|
||||
#endif
|
||||
uint32_t call_id{0};
|
||||
bool success{false};
|
||||
std::string error_message{};
|
||||
std::string response_data{};
|
||||
const uint8_t *response_data{nullptr};
|
||||
uint16_t response_data_len{0};
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
@@ -1122,15 +1122,23 @@ void HomeassistantActionRequest::dump_to(std::string &out) const {
|
||||
out.append("\n");
|
||||
}
|
||||
dump_field(out, "is_event", this->is_event);
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||
dump_field(out, "call_id", this->call_id);
|
||||
#endif
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||
dump_field(out, "response_template", this->response_template);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||
void HomeassistantActionResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "HomeassistantActionResponse");
|
||||
dump_field(out, "call_id", this->call_id);
|
||||
dump_field(out, "success", this->success);
|
||||
dump_field(out, "error_message", this->error_message);
|
||||
dump_field(out, "response_data", this->response_data);
|
||||
out.append(" response_data: ");
|
||||
out.append(format_hex_pretty(this->response_data, this->response_data_len));
|
||||
out.append("\n");
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||
|
@@ -611,7 +611,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||
case HomeassistantActionResponse::MESSAGE_TYPE: {
|
||||
HomeassistantActionResponse msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
|
@@ -66,7 +66,7 @@ class APIServerConnectionBase : public ProtoService {
|
||||
virtual void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||
virtual void on_homeassistant_action_response(const HomeassistantActionResponse &value){};
|
||||
#endif
|
||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||
|
@@ -403,27 +403,23 @@ void APIServer::send_homeassistant_action(const HomeassistantActionRequest &call
|
||||
client->send_homeassistant_action(call);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||
void APIServer::register_action_response_callback(uint32_t call_id, ActionResponseCallback callback) {
|
||||
this->action_response_callbacks_[call_id] = std::move(callback);
|
||||
}
|
||||
|
||||
void APIServer::handle_action_response(uint32_t call_id, bool success, const std::string &error_message,
|
||||
const std::string &response_data) {
|
||||
const uint8_t *response_data, size_t response_data_len) {
|
||||
auto it = this->action_response_callbacks_.find(call_id);
|
||||
if (it != this->action_response_callbacks_.end()) {
|
||||
// Create the response object
|
||||
auto response = std::make_shared<class ActionResponse>(success, error_message);
|
||||
response->set_data(response_data);
|
||||
|
||||
// Call the callback
|
||||
it->second(response);
|
||||
|
||||
// Remove the callback as it's one-time use
|
||||
auto callback = std::move(it->second);
|
||||
this->action_response_callbacks_.erase(it);
|
||||
auto response = std::make_shared<ActionResponse>(success, error_message, response_data, response_data_len);
|
||||
callback(response);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||
#endif // USE_API_HOMEASSISTANT_SERVICES
|
||||
|
||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||
void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
|
||||
|
@@ -112,12 +112,14 @@ class APIServer : public Component, public Controller {
|
||||
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||
void send_homeassistant_action(const HomeassistantActionRequest &call);
|
||||
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||
// Action response handling
|
||||
using ActionResponseCallback = std::function<void(std::shared_ptr<class ActionResponse>)>;
|
||||
void register_action_response_callback(uint32_t call_id, ActionResponseCallback callback);
|
||||
void handle_action_response(uint32_t call_id, bool success, const std::string &error_message,
|
||||
const std::string &response_data);
|
||||
#endif
|
||||
const uint8_t *response_data, size_t response_data_len);
|
||||
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||
#endif // USE_API_HOMEASSISTANT_SERVICES
|
||||
#ifdef USE_API_SERVICES
|
||||
void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }
|
||||
#endif
|
||||
@@ -193,7 +195,7 @@ class APIServer : public Component, public Controller {
|
||||
#ifdef USE_API_SERVICES
|
||||
std::vector<UserServiceDescriptor *> user_services_;
|
||||
#endif
|
||||
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||
std::map<uint32_t, ActionResponseCallback> action_response_callbacks_;
|
||||
#endif
|
||||
|
||||
|
@@ -47,42 +47,33 @@ template<typename... Ts> class TemplatableKeyValuePair {
|
||||
TemplatableStringValue<Ts...> value;
|
||||
};
|
||||
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||
// Represents the response data from a Home Assistant action
|
||||
class ActionResponse {
|
||||
public:
|
||||
ActionResponse(bool success, std::string error_message = "")
|
||||
: success_(success), error_message_(std::move(error_message)) {}
|
||||
ActionResponse(bool success, std::string error_message = "", const uint8_t *data = nullptr, size_t data_len = 0)
|
||||
: success_(success), error_message_(std::move(error_message)) {
|
||||
if (data == nullptr || data_len == 0)
|
||||
return;
|
||||
this->json_document_ = json::parse_json(data, data_len);
|
||||
this->json_ = this->json_document_.as<JsonObject>();
|
||||
}
|
||||
|
||||
bool is_success() const { return this->success_; }
|
||||
const std::string &get_error_message() const { return this->error_message_; }
|
||||
const std::string &get_data() const { return this->data_; }
|
||||
// Get data as parsed JSON object
|
||||
// Returns unbound JsonObject if data is empty or invalid JSON
|
||||
JsonObject get_json() {
|
||||
if (this->data_.empty())
|
||||
return JsonObject(); // Return unbound JsonObject if no data
|
||||
|
||||
if (!this->parsed_json_) {
|
||||
this->json_document_ = json::parse_json(this->data_);
|
||||
this->json_ = this->json_document_.as<JsonObject>();
|
||||
this->parsed_json_ = true;
|
||||
}
|
||||
return this->json_;
|
||||
}
|
||||
|
||||
void set_data(const std::string &data) { this->data_ = data; }
|
||||
JsonObject get_json() { return this->json_; }
|
||||
|
||||
protected:
|
||||
bool success_;
|
||||
std::string error_message_;
|
||||
std::string data_;
|
||||
JsonDocument json_document_;
|
||||
JsonObject json_;
|
||||
bool parsed_json_{false};
|
||||
};
|
||||
|
||||
// Callback type for action responses
|
||||
template<typename... Ts> using ActionResponseCallback = std::function<void(std::shared_ptr<ActionResponse>, Ts...)>;
|
||||
#endif
|
||||
|
||||
template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts...> {
|
||||
public:
|
||||
@@ -101,15 +92,19 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
|
||||
this->variables_.emplace_back(std::move(key), value);
|
||||
}
|
||||
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||
template<typename T> void set_response_template(T response_template) {
|
||||
this->response_template_ = response_template;
|
||||
this->has_response_template_ = true;
|
||||
}
|
||||
|
||||
void set_response_callback(ActionResponseCallback<Ts...> callback) {
|
||||
this->wants_response_ = true;
|
||||
this->response_callback_ = callback;
|
||||
}
|
||||
void set_wants_response() { this->wants_response_ = true; }
|
||||
|
||||
Trigger<JsonObject, Ts...> *get_response_trigger() const { return this->response_trigger_; }
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_ERRORS
|
||||
Trigger<std::string, Ts...> *get_error_trigger() const { return this->error_trigger_; }
|
||||
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES_ERRORS
|
||||
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||
|
||||
void play(Ts... x) override {
|
||||
HomeassistantActionRequest resp;
|
||||
@@ -135,6 +130,7 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
|
||||
kv.value = it.value.value(x...);
|
||||
}
|
||||
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||
if (this->wants_response_) {
|
||||
// Generate a unique call ID for this service call
|
||||
static uint32_t call_id_counter = 1;
|
||||
@@ -147,11 +143,25 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
|
||||
}
|
||||
|
||||
auto captured_args = std::make_tuple(x...);
|
||||
this->parent_->register_action_response_callback(call_id, [this, captured_args](
|
||||
std::shared_ptr<ActionResponse> response) {
|
||||
std::apply([this, &response](auto &&...args) { this->response_callback_(response, args...); }, captured_args);
|
||||
});
|
||||
this->parent_->register_action_response_callback(
|
||||
call_id, [this, captured_args](std::shared_ptr<ActionResponse> response) {
|
||||
std::apply(
|
||||
[this, &response](auto &&...args) {
|
||||
if (response->is_success()) {
|
||||
if (this->response_trigger_ != nullptr) {
|
||||
this->response_trigger_->trigger(response->get_json(), args...);
|
||||
}
|
||||
}
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_ERRORS
|
||||
else if (this->error_trigger_ != nullptr) {
|
||||
this->error_trigger_->trigger(response->get_error_message(), args...);
|
||||
}
|
||||
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES_ERRORS
|
||||
},
|
||||
captured_args);
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
this->parent_->send_homeassistant_action(resp);
|
||||
}
|
||||
@@ -163,21 +173,18 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
|
||||
std::vector<TemplatableKeyValuePair<Ts...>> data_;
|
||||
std::vector<TemplatableKeyValuePair<Ts...>> data_template_;
|
||||
std::vector<TemplatableKeyValuePair<Ts...>> variables_;
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||
TemplatableStringValue<Ts...> response_template_{""};
|
||||
ActionResponseCallback<Ts...> response_callback_;
|
||||
Trigger<JsonObject, Ts...> *response_trigger_ = new Trigger<JsonObject, Ts...>();
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_ERRORS
|
||||
Trigger<std::string, Ts...> *error_trigger_ = new Trigger<std::string, Ts...>();
|
||||
#endif
|
||||
bool wants_response_{false};
|
||||
bool has_response_template_{false};
|
||||
};
|
||||
|
||||
template<typename... Ts>
|
||||
class HomeAssistantActionResponseTrigger : public Trigger<std::shared_ptr<ActionResponse>, Ts...> {
|
||||
public:
|
||||
HomeAssistantActionResponseTrigger(HomeAssistantServiceCallAction<Ts...> *action) {
|
||||
action->set_response_callback(
|
||||
[this](std::shared_ptr<ActionResponse> response, Ts... x) { this->trigger(response, x...); });
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace esphome::api
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
Reference in New Issue
Block a user