diff --git a/esphome/components/api/__init__.py b/esphome/components/api/__init__.py index 47ed036658..a684439bf9 100644 --- a/esphome/components/api/__init__.py +++ b/esphome/components/api/__init__.py @@ -356,6 +356,7 @@ async def homeassistant_service_to_code( cg.add(var.set_response_template(templ)) 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, diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index b37344f566..988d651304 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -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 ==================== diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 9d76adf98e..5a840898cc 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1550,9 +1550,10 @@ void APIConnection::execute_service(const ExecuteServiceRequest &msg) { } #endif -#ifdef USE_API_HOMEASSISTANT_SERVICES +#if defined(USE_API_HOMEASSISTANT_SERVICES) && defined(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, + reinterpret_cast(msg.response_data), msg.response_data_len); }; #endif #ifdef USE_API_NOISE diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 401fef28dc..b7938dbce3 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -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 #ifdef USE_BLUETOOTH_PROXY void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) override; void unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) override; diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 210b6505ce..00282d4294 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -896,6 +896,8 @@ void HomeassistantActionRequest::calculate_size(ProtoSize &size) const { size.add_uint32(1, this->call_id); size.add_length(1, this->response_template.size()); } +#endif +#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES bool HomeassistantActionResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 1: @@ -914,9 +916,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; } diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index aa5ef155ea..9e03976f03 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -1124,17 +1124,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 diff --git a/esphome/components/api/api_pb2_dump.cpp b/esphome/components/api/api_pb2_dump.cpp index 9b655cc1a2..1f3073519b 100644 --- a/esphome/components/api/api_pb2_dump.cpp +++ b/esphome/components/api/api_pb2_dump.cpp @@ -1125,12 +1125,16 @@ void HomeassistantActionRequest::dump_to(std::string &out) const { dump_field(out, "call_id", this->call_id); dump_field(out, "response_template", this->response_template); } +#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 diff --git a/esphome/components/api/api_pb2_service.cpp b/esphome/components/api/api_pb2_service.cpp index 6f596a2edc..9d227af0a3 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -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); diff --git a/esphome/components/api/api_pb2_service.h b/esphome/components/api/api_pb2_service.h index f3f39d48ec..549b00ee6a 100644 --- a/esphome/components/api/api_pb2_service.h +++ b/esphome/components/api/api_pb2_service.h @@ -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 diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index d21658c940..76d5e4d9a7 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -404,17 +404,17 @@ void APIServer::send_homeassistant_action(const HomeassistantActionRequest &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 char *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(success, error_message); - response->set_data(response_data); + auto response = std::make_shared(success, error_message, response_data, response_data_len); // Call the callback it->second(response); @@ -424,6 +424,7 @@ void APIServer::handle_action_response(uint32_t call_id, bool success, const std } } #endif +#endif #ifdef USE_API_HOMEASSISTANT_STATES void APIServer::subscribe_home_assistant_state(std::string entity_id, optional attribute, diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index b346d83ac8..24b4bf4cf9 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -111,12 +111,13 @@ class APIServer : public Component, public Controller { #endif #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 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); + const char *response_data, size_t response_data_len); +#endif #endif #ifdef USE_API_SERVICES void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); } @@ -193,7 +194,7 @@ class APIServer : public Component, public Controller { #ifdef USE_API_SERVICES std::vector user_services_; #endif -#ifdef USE_API_HOMEASSISTANT_SERVICES +#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES std::map action_response_callbacks_; #endif diff --git a/esphome/components/api/homeassistant_service.h b/esphome/components/api/homeassistant_service.h index 1a1f9c4810..93daf7a90c 100644 --- a/esphome/components/api/homeassistant_service.h +++ b/esphome/components/api/homeassistant_service.h @@ -47,35 +47,36 @@ template class TemplatableKeyValuePair { TemplatableStringValue 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 char *data, size_t data_len) + : success_(success), error_message_(std::move(error_message)), data_(data), data_len_(data_len) {} 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_; } + const char *get_data() const { return this->data_; } + size_t get_data_len() const { return this->data_len_; } // Get data as parsed JSON object // Returns unbound JsonObject if data is empty or invalid JSON JsonObject get_json() { - if (this->data_.empty()) + if (this->data_len_ == 0) return JsonObject(); // Return unbound JsonObject if no data if (!this->parsed_json_) { - this->json_document_ = json::parse_json(this->data_); + this->json_document_ = json::parse_json(this->data_, this->data_len_); this->json_ = this->json_document_.as(); this->parsed_json_ = true; } return this->json_; } - void set_data(const std::string &data) { this->data_ = data; } - protected: bool success_; std::string error_message_; - std::string data_; + const char *data_; + size_t data_len_; JsonDocument json_document_; JsonObject json_; bool parsed_json_{false}; @@ -83,6 +84,7 @@ class ActionResponse { // Callback type for action responses template using ActionResponseCallback = std::function, Ts...)>; +#endif template class HomeAssistantServiceCallAction : public Action { public: @@ -101,6 +103,7 @@ template class HomeAssistantServiceCallAction : public Actionvariables_.emplace_back(std::move(key), value); } +#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES template void set_response_template(T response_template) { this->response_template_ = response_template; this->has_response_template_ = true; @@ -110,6 +113,7 @@ template class HomeAssistantServiceCallAction : public Actionwants_response_ = true; this->response_callback_ = callback; } +#endif void play(Ts... x) override { HomeassistantActionRequest resp; @@ -135,6 +139,7 @@ template class HomeAssistantServiceCallAction : public Actionwants_response_) { // Generate a unique call ID for this service call static uint32_t call_id_counter = 1; @@ -152,6 +157,7 @@ template class HomeAssistantServiceCallAction : public Actionresponse_callback_(response, args...); }, captured_args); }); } +#endif this->parent_->send_homeassistant_action(resp); } @@ -163,12 +169,15 @@ template class HomeAssistantServiceCallAction : public Action> data_; std::vector> data_template_; std::vector> variables_; +#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES TemplatableStringValue response_template_{""}; ActionResponseCallback response_callback_; bool wants_response_{false}; bool has_response_template_{false}; +#endif }; +#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES template class HomeAssistantActionResponseTrigger : public Trigger, Ts...> { public: @@ -177,6 +186,7 @@ class HomeAssistantActionResponseTrigger : public Trigger response, Ts... x) { this->trigger(response, x...); }); } }; +#endif } // namespace esphome::api #endif diff --git a/esphome/components/json/json_util.cpp b/esphome/components/json/json_util.cpp index 643f23f499..d0498b06a5 100644 --- a/esphome/components/json/json_util.cpp +++ b/esphome/components/json/json_util.cpp @@ -26,7 +26,7 @@ bool parse_json(const std::string &data, const json_parse_t &f) { // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) } -JsonDocument parse_json(const std::string &data) { +JsonDocument parse_json(const char *data, size_t len) { // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson #ifdef USE_PSRAM auto doc_allocator = SpiRamAllocator(); @@ -38,12 +38,12 @@ JsonDocument parse_json(const std::string &data) { ESP_LOGE(TAG, "Could not allocate memory for JSON document!"); return JsonObject(); // return unbound object } - DeserializationError err = deserializeJson(json_document, data); + DeserializationError err = deserializeJson(json_document, data, len); if (err == DeserializationError::Ok) { return json_document; } else if (err == DeserializationError::NoMemory) { - ESP_LOGE(TAG, "Can not allocate more memory for deserialization. Consider making source string smaller"); + ESP_LOGE(TAG, "Can not allocate more memory for deserialization. Consider making source buffer smaller"); return JsonObject(); // return unbound object } ESP_LOGE(TAG, "Parse error: %s", err.c_str()); @@ -51,6 +51,8 @@ JsonDocument parse_json(const std::string &data) { // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) } +JsonDocument parse_json(const std::string &data) { return parse_json(data.c_str(), data.size()); } + std::string JsonBuilder::serialize() { if (doc_.overflowed()) { ESP_LOGE(TAG, "JSON document overflow"); diff --git a/esphome/components/json/json_util.h b/esphome/components/json/json_util.h index 0349833342..a3c5cc2f8c 100644 --- a/esphome/components/json/json_util.h +++ b/esphome/components/json/json_util.h @@ -51,6 +51,8 @@ std::string build_json(const json_build_t &f); bool parse_json(const std::string &data, const json_parse_t &f); /// Parse a JSON string and return the root JsonDocument (or an unbound object on error) JsonDocument parse_json(const std::string &data); +/// Parse JSON from a buffer and return the root JsonDocument (or an unbound object on error) +JsonDocument parse_json(const char *data, size_t len); /// Builder class for creating JSON documents without lambdas class JsonBuilder { diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 7fc42ea334..800b0d94d7 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -111,6 +111,7 @@ #define USE_API_CLIENT_CONNECTED_TRIGGER #define USE_API_CLIENT_DISCONNECTED_TRIGGER #define USE_API_HOMEASSISTANT_SERVICES +#define USE_API_HOMEASSISTANT_ACTION_RESPONSES #define USE_API_HOMEASSISTANT_STATES #define USE_API_NOISE #define USE_API_PLAINTEXT