mirror of
https://github.com/esphome/esphome.git
synced 2025-10-15 08:13:51 +01:00
[api] Add support for getting action responses from home-assistant
This commit is contained in:
@@ -16,23 +16,26 @@ from esphome.const import (
|
|||||||
CONF_KEY,
|
CONF_KEY,
|
||||||
CONF_ON_CLIENT_CONNECTED,
|
CONF_ON_CLIENT_CONNECTED,
|
||||||
CONF_ON_CLIENT_DISCONNECTED,
|
CONF_ON_CLIENT_DISCONNECTED,
|
||||||
|
CONF_ON_RESPONSE,
|
||||||
CONF_PASSWORD,
|
CONF_PASSWORD,
|
||||||
CONF_PORT,
|
CONF_PORT,
|
||||||
CONF_REBOOT_TIMEOUT,
|
CONF_REBOOT_TIMEOUT,
|
||||||
|
CONF_RESPONSE_TEMPLATE,
|
||||||
CONF_SERVICE,
|
CONF_SERVICE,
|
||||||
CONF_SERVICES,
|
CONF_SERVICES,
|
||||||
CONF_TAG,
|
CONF_TAG,
|
||||||
CONF_TRIGGER_ID,
|
CONF_TRIGGER_ID,
|
||||||
CONF_VARIABLES,
|
CONF_VARIABLES,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
from esphome.core import CORE, ID, CoroPriority, coroutine_with_priority
|
||||||
|
from esphome.cpp_generator import TemplateArgsType
|
||||||
from esphome.types import ConfigType
|
from esphome.types import ConfigType
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
DOMAIN = "api"
|
DOMAIN = "api"
|
||||||
DEPENDENCIES = ["network"]
|
DEPENDENCIES = ["network"]
|
||||||
AUTO_LOAD = ["socket"]
|
AUTO_LOAD = ["socket", "json"]
|
||||||
CODEOWNERS = ["@esphome/core"]
|
CODEOWNERS = ["@esphome/core"]
|
||||||
|
|
||||||
api_ns = cg.esphome_ns.namespace("api")
|
api_ns = cg.esphome_ns.namespace("api")
|
||||||
@@ -40,6 +43,10 @@ APIServer = api_ns.class_("APIServer", cg.Component, cg.Controller)
|
|||||||
HomeAssistantServiceCallAction = api_ns.class_(
|
HomeAssistantServiceCallAction = api_ns.class_(
|
||||||
"HomeAssistantServiceCallAction", automation.Action
|
"HomeAssistantServiceCallAction", automation.Action
|
||||||
)
|
)
|
||||||
|
ActionResponse = api_ns.class_("ActionResponse")
|
||||||
|
HomeAssistantActionResponseTrigger = api_ns.class_(
|
||||||
|
"HomeAssistantActionResponseTrigger", automation.Trigger
|
||||||
|
)
|
||||||
APIConnectedCondition = api_ns.class_("APIConnectedCondition", Condition)
|
APIConnectedCondition = api_ns.class_("APIConnectedCondition", Condition)
|
||||||
|
|
||||||
UserServiceTrigger = api_ns.class_("UserServiceTrigger", automation.Trigger)
|
UserServiceTrigger = api_ns.class_("UserServiceTrigger", automation.Trigger)
|
||||||
@@ -244,6 +251,14 @@ async def to_code(config):
|
|||||||
KEY_VALUE_SCHEMA = cv.Schema({cv.string: cv.templatable(cv.string_strict)})
|
KEY_VALUE_SCHEMA = cv.Schema({cv.string: cv.templatable(cv.string_strict)})
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_response_config(config):
|
||||||
|
if CONF_RESPONSE_TEMPLATE in config and not config.get(CONF_ON_RESPONSE):
|
||||||
|
raise cv.Invalid(
|
||||||
|
"`{CONF_RESPONSE_TEMPLATE}` requires `{CONF_ON_RESPONSE}` to be set."
|
||||||
|
)
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
HOMEASSISTANT_ACTION_ACTION_SCHEMA = cv.All(
|
HOMEASSISTANT_ACTION_ACTION_SCHEMA = cv.All(
|
||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
@@ -259,10 +274,20 @@ HOMEASSISTANT_ACTION_ACTION_SCHEMA = cv.All(
|
|||||||
cv.Optional(CONF_VARIABLES, default={}): cv.Schema(
|
cv.Optional(CONF_VARIABLES, default={}): cv.Schema(
|
||||||
{cv.string: cv.returning_lambda}
|
{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.has_exactly_one_key(CONF_SERVICE, CONF_ACTION),
|
cv.has_exactly_one_key(CONF_SERVICE, CONF_ACTION),
|
||||||
cv.rename_key(CONF_SERVICE, CONF_ACTION),
|
cv.rename_key(CONF_SERVICE, CONF_ACTION),
|
||||||
|
_validate_response_config,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -276,7 +301,12 @@ HOMEASSISTANT_ACTION_ACTION_SCHEMA = cv.All(
|
|||||||
HomeAssistantServiceCallAction,
|
HomeAssistantServiceCallAction,
|
||||||
HOMEASSISTANT_ACTION_ACTION_SCHEMA,
|
HOMEASSISTANT_ACTION_ACTION_SCHEMA,
|
||||||
)
|
)
|
||||||
async def homeassistant_service_to_code(config, action_id, template_arg, args):
|
async def homeassistant_service_to_code(
|
||||||
|
config: ConfigType,
|
||||||
|
action_id: ID,
|
||||||
|
template_arg: cg.TemplateArguments,
|
||||||
|
args: TemplateArgsType,
|
||||||
|
):
|
||||||
cg.add_define("USE_API_HOMEASSISTANT_SERVICES")
|
cg.add_define("USE_API_HOMEASSISTANT_SERVICES")
|
||||||
serv = await cg.get_variable(config[CONF_ID])
|
serv = await cg.get_variable(config[CONF_ID])
|
||||||
var = cg.new_Pvariable(action_id, template_arg, serv, False)
|
var = cg.new_Pvariable(action_id, template_arg, serv, False)
|
||||||
@@ -291,6 +321,23 @@ async def homeassistant_service_to_code(config, action_id, template_arg, args):
|
|||||||
for key, value in config[CONF_VARIABLES].items():
|
for key, value in config[CONF_VARIABLES].items():
|
||||||
templ = await cg.templatable(value, args, None)
|
templ = await cg.templatable(value, args, None)
|
||||||
cg.add(var.add_variable(key, templ))
|
cg.add(var.add_variable(key, templ))
|
||||||
|
|
||||||
|
if response_template := config.get(CONF_RESPONSE_TEMPLATE):
|
||||||
|
templ = await cg.templatable(response_template, args, cg.std_string)
|
||||||
|
cg.add(var.set_response_template(templ))
|
||||||
|
|
||||||
|
if on_response := config.get(CONF_ON_RESPONSE):
|
||||||
|
trigger = cg.new_Pvariable(
|
||||||
|
on_response[CONF_TRIGGER_ID],
|
||||||
|
template_arg,
|
||||||
|
var,
|
||||||
|
)
|
||||||
|
await automation.build_automation(
|
||||||
|
trigger,
|
||||||
|
[(cg.std_shared_ptr.template(ActionResponse), "response"), *args],
|
||||||
|
on_response,
|
||||||
|
)
|
||||||
|
|
||||||
return var
|
return var
|
||||||
|
|
||||||
|
|
||||||
|
@@ -780,6 +780,21 @@ message HomeassistantActionRequest {
|
|||||||
repeated HomeassistantServiceMap data_template = 3;
|
repeated HomeassistantServiceMap data_template = 3;
|
||||||
repeated HomeassistantServiceMap variables = 4;
|
repeated HomeassistantServiceMap variables = 4;
|
||||||
bool is_event = 5;
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message sent by Home Assistant to ESPHome with service call response data
|
||||||
|
message HomeassistantActionResponse {
|
||||||
|
option (id) = 130;
|
||||||
|
option (source) = SOURCE_CLIENT;
|
||||||
|
option (no_delay) = true;
|
||||||
|
option (ifdef) = "USE_API_HOMEASSISTANT_SERVICES";
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== IMPORT HOME ASSISTANT STATES ====================
|
// ==================== IMPORT HOME ASSISTANT STATES ====================
|
||||||
|
@@ -1549,6 +1549,12 @@ void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
#endif
|
||||||
#ifdef USE_API_NOISE
|
#ifdef USE_API_NOISE
|
||||||
bool APIConnection::send_noise_encryption_set_key_response(const NoiseEncryptionSetKeyRequest &msg) {
|
bool APIConnection::send_noise_encryption_set_key_response(const NoiseEncryptionSetKeyRequest &msg) {
|
||||||
NoiseEncryptionSetKeyResponse resp;
|
NoiseEncryptionSetKeyResponse resp;
|
||||||
|
@@ -137,6 +137,7 @@ class APIConnection final : public APIServerConnection {
|
|||||||
return;
|
return;
|
||||||
this->send_message(call, HomeassistantActionRequest::MESSAGE_TYPE);
|
this->send_message(call, HomeassistantActionRequest::MESSAGE_TYPE);
|
||||||
}
|
}
|
||||||
|
void on_homeassistant_action_response(const HomeassistantActionResponse &msg) override;
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_BLUETOOTH_PROXY
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
|
void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
|
||||||
|
@@ -884,6 +884,8 @@ void HomeassistantActionRequest::encode(ProtoWriteBuffer buffer) const {
|
|||||||
buffer.encode_message(4, it, true);
|
buffer.encode_message(4, it, true);
|
||||||
}
|
}
|
||||||
buffer.encode_bool(5, this->is_event);
|
buffer.encode_bool(5, this->is_event);
|
||||||
|
buffer.encode_uint32(6, this->call_id);
|
||||||
|
buffer.encode_string(7, this->response_template);
|
||||||
}
|
}
|
||||||
void HomeassistantActionRequest::calculate_size(ProtoSize &size) const {
|
void HomeassistantActionRequest::calculate_size(ProtoSize &size) const {
|
||||||
size.add_length(1, this->service_ref_.size());
|
size.add_length(1, this->service_ref_.size());
|
||||||
@@ -891,6 +893,34 @@ void HomeassistantActionRequest::calculate_size(ProtoSize &size) const {
|
|||||||
size.add_repeated_message(1, this->data_template);
|
size.add_repeated_message(1, this->data_template);
|
||||||
size.add_repeated_message(1, this->variables);
|
size.add_repeated_message(1, this->variables);
|
||||||
size.add_bool(1, this->is_event);
|
size.add_bool(1, this->is_event);
|
||||||
|
size.add_uint32(1, this->call_id);
|
||||||
|
size.add_length(1, this->response_template.size());
|
||||||
|
}
|
||||||
|
bool HomeassistantActionResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||||
|
switch (field_id) {
|
||||||
|
case 1:
|
||||||
|
this->call_id = value.as_uint32();
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
this->success = value.as_bool();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool HomeassistantActionResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||||
|
switch (field_id) {
|
||||||
|
case 3:
|
||||||
|
this->error_message = value.as_string();
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
this->response_data = value.as_string();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||||
|
@@ -1104,7 +1104,7 @@ class HomeassistantServiceMap final : public ProtoMessage {
|
|||||||
class HomeassistantActionRequest final : public ProtoMessage {
|
class HomeassistantActionRequest final : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint8_t MESSAGE_TYPE = 35;
|
static constexpr uint8_t MESSAGE_TYPE = 35;
|
||||||
static constexpr uint8_t ESTIMATED_SIZE = 113;
|
static constexpr uint8_t ESTIMATED_SIZE = 126;
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
const char *message_name() const override { return "homeassistant_action_request"; }
|
const char *message_name() const override { return "homeassistant_action_request"; }
|
||||||
#endif
|
#endif
|
||||||
@@ -1114,6 +1114,8 @@ class HomeassistantActionRequest final : public ProtoMessage {
|
|||||||
std::vector<HomeassistantServiceMap> data_template{};
|
std::vector<HomeassistantServiceMap> data_template{};
|
||||||
std::vector<HomeassistantServiceMap> variables{};
|
std::vector<HomeassistantServiceMap> variables{};
|
||||||
bool is_event{false};
|
bool is_event{false};
|
||||||
|
uint32_t call_id{0};
|
||||||
|
std::string response_template{};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void calculate_size(ProtoSize &size) const override;
|
void calculate_size(ProtoSize &size) const override;
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@@ -1122,6 +1124,25 @@ class HomeassistantActionRequest final : public ProtoMessage {
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
};
|
};
|
||||||
|
class HomeassistantActionResponse final : public ProtoDecodableMessage {
|
||||||
|
public:
|
||||||
|
static constexpr uint8_t MESSAGE_TYPE = 130;
|
||||||
|
static constexpr uint8_t ESTIMATED_SIZE = 24;
|
||||||
|
#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{};
|
||||||
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
|
void dump_to(std::string &out) const override;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||||
|
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||||
|
};
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||||
class SubscribeHomeAssistantStatesRequest final : public ProtoMessage {
|
class SubscribeHomeAssistantStatesRequest final : public ProtoMessage {
|
||||||
|
@@ -1122,6 +1122,15 @@ void HomeassistantActionRequest::dump_to(std::string &out) const {
|
|||||||
out.append("\n");
|
out.append("\n");
|
||||||
}
|
}
|
||||||
dump_field(out, "is_event", this->is_event);
|
dump_field(out, "is_event", this->is_event);
|
||||||
|
dump_field(out, "call_id", this->call_id);
|
||||||
|
dump_field(out, "response_template", this->response_template);
|
||||||
|
}
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||||
|
@@ -610,6 +610,17 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
this->on_z_wave_proxy_request(msg);
|
this->on_z_wave_proxy_request(msg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||||
|
case HomeassistantActionResponse::MESSAGE_TYPE: {
|
||||||
|
HomeassistantActionResponse msg;
|
||||||
|
msg.decode(msg_data, msg_size);
|
||||||
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
|
ESP_LOGVV(TAG, "on_homeassistant_action_response: %s", msg.dump().c_str());
|
||||||
|
#endif
|
||||||
|
this->on_homeassistant_action_response(msg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@@ -66,6 +66,9 @@ class APIServerConnectionBase : public ProtoService {
|
|||||||
virtual void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &value){};
|
virtual void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &value){};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||||
|
virtual void on_homeassistant_action_response(const HomeassistantActionResponse &value){};
|
||||||
|
#endif
|
||||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||||
virtual void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &value){};
|
virtual void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &value){};
|
||||||
#endif
|
#endif
|
||||||
|
@@ -9,6 +9,9 @@
|
|||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include "esphome/core/util.h"
|
#include "esphome/core/util.h"
|
||||||
#include "esphome/core/version.h"
|
#include "esphome/core/version.h"
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||||
|
#include "homeassistant_service.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef USE_LOGGER
|
#ifdef USE_LOGGER
|
||||||
#include "esphome/components/logger/logger.h"
|
#include "esphome/components/logger/logger.h"
|
||||||
@@ -387,6 +390,26 @@ void APIServer::send_homeassistant_action(const HomeassistantActionRequest &call
|
|||||||
client->send_homeassistant_action(call);
|
client->send_homeassistant_action(call);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void APIServer::register_action_response_callback(uint32_t call_id, ActionResponseCallback callback) {
|
||||||
|
this->action_response_callbacks_[call_id] = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
void APIServer::handle_action_response(uint32_t call_id, bool success, const std::string &error_message,
|
||||||
|
const std::string &response_data) {
|
||||||
|
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
|
||||||
|
this->action_response_callbacks_.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||||
|
@@ -16,6 +16,7 @@
|
|||||||
#include "user_services.h"
|
#include "user_services.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace esphome::api {
|
namespace esphome::api {
|
||||||
@@ -109,6 +110,11 @@ class APIServer : public Component, public Controller {
|
|||||||
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||||
void send_homeassistant_action(const HomeassistantActionRequest &call);
|
void send_homeassistant_action(const HomeassistantActionRequest &call);
|
||||||
|
|
||||||
|
// 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
|
#endif
|
||||||
#ifdef USE_API_SERVICES
|
#ifdef USE_API_SERVICES
|
||||||
void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }
|
void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }
|
||||||
@@ -185,6 +191,9 @@ class APIServer : public Component, public Controller {
|
|||||||
#ifdef USE_API_SERVICES
|
#ifdef USE_API_SERVICES
|
||||||
std::vector<UserServiceDescriptor *> user_services_;
|
std::vector<UserServiceDescriptor *> user_services_;
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||||
|
std::map<uint32_t, ActionResponseCallback> action_response_callbacks_;
|
||||||
|
#endif
|
||||||
|
|
||||||
// Group smaller types together
|
// Group smaller types together
|
||||||
uint16_t port_{6053};
|
uint16_t port_{6053};
|
||||||
|
@@ -3,8 +3,10 @@
|
|||||||
#include "api_server.h"
|
#include "api_server.h"
|
||||||
#ifdef USE_API
|
#ifdef USE_API
|
||||||
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||||
|
#include <functional>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "api_pb2.h"
|
#include "api_pb2.h"
|
||||||
|
#include "esphome/components/json/json_util.h"
|
||||||
#include "esphome/core/automation.h"
|
#include "esphome/core/automation.h"
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
|
|
||||||
@@ -44,6 +46,43 @@ template<typename... Ts> class TemplatableKeyValuePair {
|
|||||||
TemplatableStringValue<Ts...> value;
|
TemplatableStringValue<Ts...> value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Represents the response data from a Home Assistant action
|
||||||
|
class ActionResponse {
|
||||||
|
public:
|
||||||
|
ActionResponse(bool success, const std::string &error_message = "")
|
||||||
|
: success_(success), error_message_(error_message) {}
|
||||||
|
|
||||||
|
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; }
|
||||||
|
|
||||||
|
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...)>;
|
||||||
|
|
||||||
template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts...> {
|
template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts...> {
|
||||||
public:
|
public:
|
||||||
explicit HomeAssistantServiceCallAction(APIServer *parent, bool is_event) : parent_(parent), is_event_(is_event) {}
|
explicit HomeAssistantServiceCallAction(APIServer *parent, bool is_event) : parent_(parent), is_event_(is_event) {}
|
||||||
@@ -61,6 +100,16 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
|
|||||||
this->variables_.emplace_back(std::move(key), value);
|
this->variables_.emplace_back(std::move(key), value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 play(Ts... x) override {
|
void play(Ts... x) override {
|
||||||
HomeassistantActionRequest resp;
|
HomeassistantActionRequest resp;
|
||||||
std::string service_value = this->service_.value(x...);
|
std::string service_value = this->service_.value(x...);
|
||||||
@@ -84,6 +133,25 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
|
|||||||
kv.set_key(StringRef(it.key));
|
kv.set_key(StringRef(it.key));
|
||||||
kv.value = it.value.value(x...);
|
kv.value = it.value.value(x...);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this->wants_response_) {
|
||||||
|
// Generate a unique call ID for this service call
|
||||||
|
static uint32_t call_id_counter = 1;
|
||||||
|
uint32_t call_id = call_id_counter++;
|
||||||
|
resp.call_id = call_id;
|
||||||
|
// Set response template if provided
|
||||||
|
if (this->has_response_template_) {
|
||||||
|
std::string response_template_value = this->response_template_.value(x...);
|
||||||
|
resp.response_template = response_template_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
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_->send_homeassistant_action(resp);
|
this->parent_->send_homeassistant_action(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,6 +162,19 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
|
|||||||
std::vector<TemplatableKeyValuePair<Ts...>> data_;
|
std::vector<TemplatableKeyValuePair<Ts...>> data_;
|
||||||
std::vector<TemplatableKeyValuePair<Ts...>> data_template_;
|
std::vector<TemplatableKeyValuePair<Ts...>> data_template_;
|
||||||
std::vector<TemplatableKeyValuePair<Ts...>> variables_;
|
std::vector<TemplatableKeyValuePair<Ts...>> variables_;
|
||||||
|
TemplatableStringValue<Ts...> response_template_{""};
|
||||||
|
ActionResponseCallback<Ts...> response_callback_;
|
||||||
|
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...); });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace esphome::api
|
} // namespace esphome::api
|
||||||
|
@@ -671,6 +671,7 @@ CONF_ON_PRESET_SET = "on_preset_set"
|
|||||||
CONF_ON_PRESS = "on_press"
|
CONF_ON_PRESS = "on_press"
|
||||||
CONF_ON_RAW_VALUE = "on_raw_value"
|
CONF_ON_RAW_VALUE = "on_raw_value"
|
||||||
CONF_ON_RELEASE = "on_release"
|
CONF_ON_RELEASE = "on_release"
|
||||||
|
CONF_ON_RESPONSE = "on_response"
|
||||||
CONF_ON_SHUTDOWN = "on_shutdown"
|
CONF_ON_SHUTDOWN = "on_shutdown"
|
||||||
CONF_ON_SPEED_SET = "on_speed_set"
|
CONF_ON_SPEED_SET = "on_speed_set"
|
||||||
CONF_ON_STATE = "on_state"
|
CONF_ON_STATE = "on_state"
|
||||||
@@ -816,6 +817,7 @@ CONF_RESET_DURATION = "reset_duration"
|
|||||||
CONF_RESET_PIN = "reset_pin"
|
CONF_RESET_PIN = "reset_pin"
|
||||||
CONF_RESIZE = "resize"
|
CONF_RESIZE = "resize"
|
||||||
CONF_RESOLUTION = "resolution"
|
CONF_RESOLUTION = "resolution"
|
||||||
|
CONF_RESPONSE_TEMPLATE = "response_template"
|
||||||
CONF_RESTART = "restart"
|
CONF_RESTART = "restart"
|
||||||
CONF_RESTORE = "restore"
|
CONF_RESTORE = "restore"
|
||||||
CONF_RESTORE_MODE = "restore_mode"
|
CONF_RESTORE_MODE = "restore_mode"
|
||||||
|
Reference in New Issue
Block a user