mirror of
https://github.com/esphome/esphome.git
synced 2026-06-24 12:33:10 +00:00
[api] Store Home Assistant state subscriptions in flash instead of heap (#12008)
This commit is contained in:
@@ -1580,7 +1580,12 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
|
|||||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||||
void APIConnection::on_home_assistant_state_response(const HomeAssistantStateResponse &msg) {
|
void APIConnection::on_home_assistant_state_response(const HomeAssistantStateResponse &msg) {
|
||||||
for (auto &it : this->parent_->get_state_subs()) {
|
for (auto &it : this->parent_->get_state_subs()) {
|
||||||
if (it.entity_id == msg.entity_id && it.attribute.value() == msg.attribute) {
|
// Compare entity_id and attribute with message fields
|
||||||
|
bool entity_match = (strcmp(it.entity_id, msg.entity_id.c_str()) == 0);
|
||||||
|
bool attribute_match = (it.attribute != nullptr && strcmp(it.attribute, msg.attribute.c_str()) == 0) ||
|
||||||
|
(it.attribute == nullptr && msg.attribute.empty());
|
||||||
|
|
||||||
|
if (entity_match && attribute_match) {
|
||||||
it.callback(msg.state);
|
it.callback(msg.state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1959,8 +1964,8 @@ void APIConnection::process_state_subscriptions_() {
|
|||||||
SubscribeHomeAssistantStateResponse resp;
|
SubscribeHomeAssistantStateResponse resp;
|
||||||
resp.set_entity_id(StringRef(it.entity_id));
|
resp.set_entity_id(StringRef(it.entity_id));
|
||||||
|
|
||||||
// Avoid string copy by directly using the optional's value if it exists
|
// Avoid string copy by using the const char* pointer if it exists
|
||||||
resp.set_attribute(it.attribute.has_value() ? StringRef(it.attribute.value()) : StringRef(""));
|
resp.set_attribute(it.attribute != nullptr ? StringRef(it.attribute) : StringRef(""));
|
||||||
|
|
||||||
resp.once = it.once;
|
resp.once = it.once;
|
||||||
if (this->send_message(resp, SubscribeHomeAssistantStateResponse::MESSAGE_TYPE)) {
|
if (this->send_message(resp, SubscribeHomeAssistantStateResponse::MESSAGE_TYPE)) {
|
||||||
|
|||||||
@@ -419,25 +419,56 @@ void APIServer::handle_action_response(uint32_t call_id, bool success, const std
|
|||||||
#endif // USE_API_HOMEASSISTANT_SERVICES
|
#endif // USE_API_HOMEASSISTANT_SERVICES
|
||||||
|
|
||||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||||
|
// Helper to add subscription (reduces duplication)
|
||||||
|
void APIServer::add_state_subscription_(const char *entity_id, const char *attribute,
|
||||||
|
std::function<void(std::string)> f, bool once) {
|
||||||
|
this->state_subs_.push_back(HomeAssistantStateSubscription{
|
||||||
|
.entity_id = entity_id, .attribute = attribute, .callback = std::move(f), .once = once,
|
||||||
|
// entity_id_dynamic_storage and attribute_dynamic_storage remain nullptr (no heap allocation)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to add subscription with heap-allocated strings (reduces duplication)
|
||||||
|
void APIServer::add_state_subscription_(std::string entity_id, optional<std::string> attribute,
|
||||||
|
std::function<void(std::string)> f, bool once) {
|
||||||
|
HomeAssistantStateSubscription sub;
|
||||||
|
// Allocate heap storage for the strings
|
||||||
|
sub.entity_id_dynamic_storage = std::make_unique<std::string>(std::move(entity_id));
|
||||||
|
sub.entity_id = sub.entity_id_dynamic_storage->c_str();
|
||||||
|
|
||||||
|
if (attribute.has_value()) {
|
||||||
|
sub.attribute_dynamic_storage = std::make_unique<std::string>(std::move(attribute.value()));
|
||||||
|
sub.attribute = sub.attribute_dynamic_storage->c_str();
|
||||||
|
} else {
|
||||||
|
sub.attribute = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub.callback = std::move(f);
|
||||||
|
sub.once = once;
|
||||||
|
this->state_subs_.push_back(std::move(sub));
|
||||||
|
}
|
||||||
|
|
||||||
|
// New const char* overload (for internal components - zero allocation)
|
||||||
|
void APIServer::subscribe_home_assistant_state(const char *entity_id, const char *attribute,
|
||||||
|
std::function<void(std::string)> f) {
|
||||||
|
this->add_state_subscription_(entity_id, attribute, std::move(f), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void APIServer::get_home_assistant_state(const char *entity_id, const char *attribute,
|
||||||
|
std::function<void(std::string)> f) {
|
||||||
|
this->add_state_subscription_(entity_id, attribute, std::move(f), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Existing std::string overload (for custom_api_device.h - heap allocation)
|
||||||
void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
|
void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
|
||||||
std::function<void(std::string)> f) {
|
std::function<void(std::string)> f) {
|
||||||
this->state_subs_.push_back(HomeAssistantStateSubscription{
|
this->add_state_subscription_(std::move(entity_id), std::move(attribute), std::move(f), false);
|
||||||
.entity_id = std::move(entity_id),
|
|
||||||
.attribute = std::move(attribute),
|
|
||||||
.callback = std::move(f),
|
|
||||||
.once = false,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void APIServer::get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
|
void APIServer::get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
|
||||||
std::function<void(std::string)> f) {
|
std::function<void(std::string)> f) {
|
||||||
this->state_subs_.push_back(HomeAssistantStateSubscription{
|
this->add_state_subscription_(std::move(entity_id), std::move(attribute), std::move(f), true);
|
||||||
.entity_id = std::move(entity_id),
|
}
|
||||||
.attribute = std::move(attribute),
|
|
||||||
.callback = std::move(f),
|
|
||||||
.once = true,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const std::vector<APIServer::HomeAssistantStateSubscription> &APIServer::get_state_subs() const {
|
const std::vector<APIServer::HomeAssistantStateSubscription> &APIServer::get_state_subs() const {
|
||||||
return this->state_subs_;
|
return this->state_subs_;
|
||||||
|
|||||||
@@ -190,16 +190,27 @@ class APIServer : public Component,
|
|||||||
|
|
||||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||||
struct HomeAssistantStateSubscription {
|
struct HomeAssistantStateSubscription {
|
||||||
std::string entity_id;
|
const char *entity_id; // Pointer to flash (internal) or heap (external)
|
||||||
optional<std::string> attribute;
|
const char *attribute; // Pointer to flash or nullptr (nullptr means no attribute)
|
||||||
std::function<void(std::string)> callback;
|
std::function<void(std::string)> callback;
|
||||||
bool once;
|
bool once;
|
||||||
|
|
||||||
|
// Dynamic storage for external components using std::string API (custom_api_device.h)
|
||||||
|
// These are only allocated when using the std::string overload (nullptr for const char* overload)
|
||||||
|
std::unique_ptr<std::string> entity_id_dynamic_storage;
|
||||||
|
std::unique_ptr<std::string> attribute_dynamic_storage;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// New const char* overload (for internal components - zero allocation)
|
||||||
|
void subscribe_home_assistant_state(const char *entity_id, const char *attribute, std::function<void(std::string)> f);
|
||||||
|
void get_home_assistant_state(const char *entity_id, const char *attribute, std::function<void(std::string)> f);
|
||||||
|
|
||||||
|
// Existing std::string overload (for custom_api_device.h - heap allocation)
|
||||||
void subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
|
void subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
|
||||||
std::function<void(std::string)> f);
|
std::function<void(std::string)> f);
|
||||||
void get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
|
void get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
|
||||||
std::function<void(std::string)> f);
|
std::function<void(std::string)> f);
|
||||||
|
|
||||||
const std::vector<HomeAssistantStateSubscription> &get_state_subs() const;
|
const std::vector<HomeAssistantStateSubscription> &get_state_subs() const;
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_API_USER_DEFINED_ACTIONS
|
#ifdef USE_API_USER_DEFINED_ACTIONS
|
||||||
@@ -220,6 +231,13 @@ class APIServer : public Component,
|
|||||||
bool update_noise_psk_(const SavedNoisePsk &new_psk, const LogString *save_log_msg, const LogString *fail_log_msg,
|
bool update_noise_psk_(const SavedNoisePsk &new_psk, const LogString *save_log_msg, const LogString *fail_log_msg,
|
||||||
const psk_t &active_psk, bool make_active);
|
const psk_t &active_psk, bool make_active);
|
||||||
#endif // USE_API_NOISE
|
#endif // USE_API_NOISE
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||||
|
// Helper methods to reduce code duplication
|
||||||
|
void add_state_subscription_(const char *entity_id, const char *attribute, std::function<void(std::string)> f,
|
||||||
|
bool once);
|
||||||
|
void add_state_subscription_(std::string entity_id, optional<std::string> attribute,
|
||||||
|
std::function<void(std::string)> f, bool once);
|
||||||
|
#endif // USE_API_HOMEASSISTANT_STATES
|
||||||
// Pointers and pointer-like types first (4 bytes each)
|
// Pointers and pointer-like types first (4 bytes each)
|
||||||
std::unique_ptr<socket::Socket> socket_ = nullptr;
|
std::unique_ptr<socket::Socket> socket_ = nullptr;
|
||||||
#ifdef USE_API_CLIENT_CONNECTED_TRIGGER
|
#ifdef USE_API_CLIENT_CONNECTED_TRIGGER
|
||||||
|
|||||||
@@ -19,11 +19,10 @@ void HomeassistantBinarySensor::setup() {
|
|||||||
case PARSE_ON:
|
case PARSE_ON:
|
||||||
case PARSE_OFF:
|
case PARSE_OFF:
|
||||||
bool new_state = val == PARSE_ON;
|
bool new_state = val == PARSE_ON;
|
||||||
if (this->attribute_.has_value()) {
|
if (this->attribute_ != nullptr) {
|
||||||
ESP_LOGD(TAG, "'%s::%s': Got attribute state %s", this->entity_id_.c_str(),
|
ESP_LOGD(TAG, "'%s::%s': Got attribute state %s", this->entity_id_, this->attribute_, ONOFF(new_state));
|
||||||
this->attribute_.value().c_str(), ONOFF(new_state));
|
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGD(TAG, "'%s': Got state %s", this->entity_id_.c_str(), ONOFF(new_state));
|
ESP_LOGD(TAG, "'%s': Got state %s", this->entity_id_, ONOFF(new_state));
|
||||||
}
|
}
|
||||||
if (this->initial_) {
|
if (this->initial_) {
|
||||||
this->publish_initial_state(new_state);
|
this->publish_initial_state(new_state);
|
||||||
@@ -37,9 +36,9 @@ void HomeassistantBinarySensor::setup() {
|
|||||||
}
|
}
|
||||||
void HomeassistantBinarySensor::dump_config() {
|
void HomeassistantBinarySensor::dump_config() {
|
||||||
LOG_BINARY_SENSOR("", "Homeassistant Binary Sensor", this);
|
LOG_BINARY_SENSOR("", "Homeassistant Binary Sensor", this);
|
||||||
ESP_LOGCONFIG(TAG, " Entity ID: '%s'", this->entity_id_.c_str());
|
ESP_LOGCONFIG(TAG, " Entity ID: '%s'", this->entity_id_);
|
||||||
if (this->attribute_.has_value()) {
|
if (this->attribute_ != nullptr) {
|
||||||
ESP_LOGCONFIG(TAG, " Attribute: '%s'", this->attribute_.value().c_str());
|
ESP_LOGCONFIG(TAG, " Attribute: '%s'", this->attribute_);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
float HomeassistantBinarySensor::get_setup_priority() const { return setup_priority::AFTER_WIFI; }
|
float HomeassistantBinarySensor::get_setup_priority() const { return setup_priority::AFTER_WIFI; }
|
||||||
|
|||||||
@@ -8,15 +8,15 @@ namespace homeassistant {
|
|||||||
|
|
||||||
class HomeassistantBinarySensor : public binary_sensor::BinarySensor, public Component {
|
class HomeassistantBinarySensor : public binary_sensor::BinarySensor, public Component {
|
||||||
public:
|
public:
|
||||||
void set_entity_id(const std::string &entity_id) { entity_id_ = entity_id; }
|
void set_entity_id(const char *entity_id) { this->entity_id_ = entity_id; }
|
||||||
void set_attribute(const std::string &attribute) { attribute_ = attribute; }
|
void set_attribute(const char *attribute) { this->attribute_ = attribute; }
|
||||||
void setup() override;
|
void setup() override;
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
float get_setup_priority() const override;
|
float get_setup_priority() const override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::string entity_id_;
|
const char *entity_id_{nullptr};
|
||||||
optional<std::string> attribute_;
|
const char *attribute_{nullptr};
|
||||||
bool initial_{true};
|
bool initial_{true};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -12,21 +12,21 @@ static const char *const TAG = "homeassistant.number";
|
|||||||
void HomeassistantNumber::state_changed_(const std::string &state) {
|
void HomeassistantNumber::state_changed_(const std::string &state) {
|
||||||
auto number_value = parse_number<float>(state);
|
auto number_value = parse_number<float>(state);
|
||||||
if (!number_value.has_value()) {
|
if (!number_value.has_value()) {
|
||||||
ESP_LOGW(TAG, "'%s': Can't convert '%s' to number!", this->entity_id_.c_str(), state.c_str());
|
ESP_LOGW(TAG, "'%s': Can't convert '%s' to number!", this->entity_id_, state.c_str());
|
||||||
this->publish_state(NAN);
|
this->publish_state(NAN);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this->state == number_value.value()) {
|
if (this->state == number_value.value()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ESP_LOGD(TAG, "'%s': Got state %s", this->entity_id_.c_str(), state.c_str());
|
ESP_LOGD(TAG, "'%s': Got state %s", this->entity_id_, state.c_str());
|
||||||
this->publish_state(number_value.value());
|
this->publish_state(number_value.value());
|
||||||
}
|
}
|
||||||
|
|
||||||
void HomeassistantNumber::min_retrieved_(const std::string &min) {
|
void HomeassistantNumber::min_retrieved_(const std::string &min) {
|
||||||
auto min_value = parse_number<float>(min);
|
auto min_value = parse_number<float>(min);
|
||||||
if (!min_value.has_value()) {
|
if (!min_value.has_value()) {
|
||||||
ESP_LOGE(TAG, "'%s': Can't convert 'min' value '%s' to number!", this->entity_id_.c_str(), min.c_str());
|
ESP_LOGE(TAG, "'%s': Can't convert 'min' value '%s' to number!", this->entity_id_, min.c_str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ESP_LOGD(TAG, "'%s': Min retrieved: %s", get_name().c_str(), min.c_str());
|
ESP_LOGD(TAG, "'%s': Min retrieved: %s", get_name().c_str(), min.c_str());
|
||||||
@@ -36,7 +36,7 @@ void HomeassistantNumber::min_retrieved_(const std::string &min) {
|
|||||||
void HomeassistantNumber::max_retrieved_(const std::string &max) {
|
void HomeassistantNumber::max_retrieved_(const std::string &max) {
|
||||||
auto max_value = parse_number<float>(max);
|
auto max_value = parse_number<float>(max);
|
||||||
if (!max_value.has_value()) {
|
if (!max_value.has_value()) {
|
||||||
ESP_LOGE(TAG, "'%s': Can't convert 'max' value '%s' to number!", this->entity_id_.c_str(), max.c_str());
|
ESP_LOGE(TAG, "'%s': Can't convert 'max' value '%s' to number!", this->entity_id_, max.c_str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ESP_LOGD(TAG, "'%s': Max retrieved: %s", get_name().c_str(), max.c_str());
|
ESP_LOGD(TAG, "'%s': Max retrieved: %s", get_name().c_str(), max.c_str());
|
||||||
@@ -46,7 +46,7 @@ void HomeassistantNumber::max_retrieved_(const std::string &max) {
|
|||||||
void HomeassistantNumber::step_retrieved_(const std::string &step) {
|
void HomeassistantNumber::step_retrieved_(const std::string &step) {
|
||||||
auto step_value = parse_number<float>(step);
|
auto step_value = parse_number<float>(step);
|
||||||
if (!step_value.has_value()) {
|
if (!step_value.has_value()) {
|
||||||
ESP_LOGE(TAG, "'%s': Can't convert 'step' value '%s' to number!", this->entity_id_.c_str(), step.c_str());
|
ESP_LOGE(TAG, "'%s': Can't convert 'step' value '%s' to number!", this->entity_id_, step.c_str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ESP_LOGD(TAG, "'%s': Step Retrieved %s", get_name().c_str(), step.c_str());
|
ESP_LOGD(TAG, "'%s': Step Retrieved %s", get_name().c_str(), step.c_str());
|
||||||
@@ -55,22 +55,19 @@ void HomeassistantNumber::step_retrieved_(const std::string &step) {
|
|||||||
|
|
||||||
void HomeassistantNumber::setup() {
|
void HomeassistantNumber::setup() {
|
||||||
api::global_api_server->subscribe_home_assistant_state(
|
api::global_api_server->subscribe_home_assistant_state(
|
||||||
this->entity_id_, nullopt, std::bind(&HomeassistantNumber::state_changed_, this, std::placeholders::_1));
|
this->entity_id_, nullptr, std::bind(&HomeassistantNumber::state_changed_, this, std::placeholders::_1));
|
||||||
|
|
||||||
api::global_api_server->get_home_assistant_state(
|
api::global_api_server->get_home_assistant_state(
|
||||||
this->entity_id_, optional<std::string>("min"),
|
this->entity_id_, "min", std::bind(&HomeassistantNumber::min_retrieved_, this, std::placeholders::_1));
|
||||||
std::bind(&HomeassistantNumber::min_retrieved_, this, std::placeholders::_1));
|
|
||||||
api::global_api_server->get_home_assistant_state(
|
api::global_api_server->get_home_assistant_state(
|
||||||
this->entity_id_, optional<std::string>("max"),
|
this->entity_id_, "max", std::bind(&HomeassistantNumber::max_retrieved_, this, std::placeholders::_1));
|
||||||
std::bind(&HomeassistantNumber::max_retrieved_, this, std::placeholders::_1));
|
|
||||||
api::global_api_server->get_home_assistant_state(
|
api::global_api_server->get_home_assistant_state(
|
||||||
this->entity_id_, optional<std::string>("step"),
|
this->entity_id_, "step", std::bind(&HomeassistantNumber::step_retrieved_, this, std::placeholders::_1));
|
||||||
std::bind(&HomeassistantNumber::step_retrieved_, this, std::placeholders::_1));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void HomeassistantNumber::dump_config() {
|
void HomeassistantNumber::dump_config() {
|
||||||
LOG_NUMBER("", "Homeassistant Number", this);
|
LOG_NUMBER("", "Homeassistant Number", this);
|
||||||
ESP_LOGCONFIG(TAG, " Entity ID: '%s'", this->entity_id_.c_str());
|
ESP_LOGCONFIG(TAG, " Entity ID: '%s'", this->entity_id_);
|
||||||
}
|
}
|
||||||
|
|
||||||
float HomeassistantNumber::get_setup_priority() const { return setup_priority::AFTER_CONNECTION; }
|
float HomeassistantNumber::get_setup_priority() const { return setup_priority::AFTER_CONNECTION; }
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ namespace homeassistant {
|
|||||||
|
|
||||||
class HomeassistantNumber : public number::Number, public Component {
|
class HomeassistantNumber : public number::Number, public Component {
|
||||||
public:
|
public:
|
||||||
void set_entity_id(const std::string &entity_id) { this->entity_id_ = entity_id; }
|
void set_entity_id(const char *entity_id) { this->entity_id_ = entity_id; }
|
||||||
|
|
||||||
void setup() override;
|
void setup() override;
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
@@ -25,7 +25,7 @@ class HomeassistantNumber : public number::Number, public Component {
|
|||||||
|
|
||||||
void control(float value) override;
|
void control(float value) override;
|
||||||
|
|
||||||
std::string entity_id_;
|
const char *entity_id_{nullptr};
|
||||||
};
|
};
|
||||||
} // namespace homeassistant
|
} // namespace homeassistant
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|||||||
@@ -12,25 +12,24 @@ void HomeassistantSensor::setup() {
|
|||||||
this->entity_id_, this->attribute_, [this](const std::string &state) {
|
this->entity_id_, this->attribute_, [this](const std::string &state) {
|
||||||
auto val = parse_number<float>(state);
|
auto val = parse_number<float>(state);
|
||||||
if (!val.has_value()) {
|
if (!val.has_value()) {
|
||||||
ESP_LOGW(TAG, "'%s': Can't convert '%s' to number!", this->entity_id_.c_str(), state.c_str());
|
ESP_LOGW(TAG, "'%s': Can't convert '%s' to number!", this->entity_id_, state.c_str());
|
||||||
this->publish_state(NAN);
|
this->publish_state(NAN);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->attribute_.has_value()) {
|
if (this->attribute_ != nullptr) {
|
||||||
ESP_LOGD(TAG, "'%s::%s': Got attribute state %.2f", this->entity_id_.c_str(),
|
ESP_LOGD(TAG, "'%s::%s': Got attribute state %.2f", this->entity_id_, this->attribute_, *val);
|
||||||
this->attribute_.value().c_str(), *val);
|
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGD(TAG, "'%s': Got state %.2f", this->entity_id_.c_str(), *val);
|
ESP_LOGD(TAG, "'%s': Got state %.2f", this->entity_id_, *val);
|
||||||
}
|
}
|
||||||
this->publish_state(*val);
|
this->publish_state(*val);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
void HomeassistantSensor::dump_config() {
|
void HomeassistantSensor::dump_config() {
|
||||||
LOG_SENSOR("", "Homeassistant Sensor", this);
|
LOG_SENSOR("", "Homeassistant Sensor", this);
|
||||||
ESP_LOGCONFIG(TAG, " Entity ID: '%s'", this->entity_id_.c_str());
|
ESP_LOGCONFIG(TAG, " Entity ID: '%s'", this->entity_id_);
|
||||||
if (this->attribute_.has_value()) {
|
if (this->attribute_ != nullptr) {
|
||||||
ESP_LOGCONFIG(TAG, " Attribute: '%s'", this->attribute_.value().c_str());
|
ESP_LOGCONFIG(TAG, " Attribute: '%s'", this->attribute_);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
float HomeassistantSensor::get_setup_priority() const { return setup_priority::AFTER_CONNECTION; }
|
float HomeassistantSensor::get_setup_priority() const { return setup_priority::AFTER_CONNECTION; }
|
||||||
|
|||||||
@@ -8,15 +8,15 @@ namespace homeassistant {
|
|||||||
|
|
||||||
class HomeassistantSensor : public sensor::Sensor, public Component {
|
class HomeassistantSensor : public sensor::Sensor, public Component {
|
||||||
public:
|
public:
|
||||||
void set_entity_id(const std::string &entity_id) { entity_id_ = entity_id; }
|
void set_entity_id(const char *entity_id) { this->entity_id_ = entity_id; }
|
||||||
void set_attribute(const std::string &attribute) { attribute_ = attribute; }
|
void set_attribute(const char *attribute) { this->attribute_ = attribute; }
|
||||||
void setup() override;
|
void setup() override;
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
float get_setup_priority() const override;
|
float get_setup_priority() const override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::string entity_id_;
|
const char *entity_id_{nullptr};
|
||||||
optional<std::string> attribute_;
|
const char *attribute_{nullptr};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace homeassistant
|
} // namespace homeassistant
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ static const char *const TAG = "homeassistant.switch";
|
|||||||
using namespace esphome::switch_;
|
using namespace esphome::switch_;
|
||||||
|
|
||||||
void HomeassistantSwitch::setup() {
|
void HomeassistantSwitch::setup() {
|
||||||
api::global_api_server->subscribe_home_assistant_state(this->entity_id_, nullopt, [this](const std::string &state) {
|
api::global_api_server->subscribe_home_assistant_state(this->entity_id_, nullptr, [this](const std::string &state) {
|
||||||
auto val = parse_on_off(state.c_str());
|
auto val = parse_on_off(state.c_str());
|
||||||
switch (val) {
|
switch (val) {
|
||||||
case PARSE_NONE:
|
case PARSE_NONE:
|
||||||
@@ -20,7 +20,7 @@ void HomeassistantSwitch::setup() {
|
|||||||
case PARSE_ON:
|
case PARSE_ON:
|
||||||
case PARSE_OFF:
|
case PARSE_OFF:
|
||||||
bool new_state = val == PARSE_ON;
|
bool new_state = val == PARSE_ON;
|
||||||
ESP_LOGD(TAG, "'%s': Got state %s", this->entity_id_.c_str(), ONOFF(new_state));
|
ESP_LOGD(TAG, "'%s': Got state %s", this->entity_id_, ONOFF(new_state));
|
||||||
this->publish_state(new_state);
|
this->publish_state(new_state);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -29,7 +29,7 @@ void HomeassistantSwitch::setup() {
|
|||||||
|
|
||||||
void HomeassistantSwitch::dump_config() {
|
void HomeassistantSwitch::dump_config() {
|
||||||
LOG_SWITCH("", "Homeassistant Switch", this);
|
LOG_SWITCH("", "Homeassistant Switch", this);
|
||||||
ESP_LOGCONFIG(TAG, " Entity ID: '%s'", this->entity_id_.c_str());
|
ESP_LOGCONFIG(TAG, " Entity ID: '%s'", this->entity_id_);
|
||||||
}
|
}
|
||||||
|
|
||||||
float HomeassistantSwitch::get_setup_priority() const { return setup_priority::AFTER_CONNECTION; }
|
float HomeassistantSwitch::get_setup_priority() const { return setup_priority::AFTER_CONNECTION; }
|
||||||
|
|||||||
@@ -8,14 +8,14 @@ namespace homeassistant {
|
|||||||
|
|
||||||
class HomeassistantSwitch : public switch_::Switch, public Component {
|
class HomeassistantSwitch : public switch_::Switch, public Component {
|
||||||
public:
|
public:
|
||||||
void set_entity_id(const std::string &entity_id) { this->entity_id_ = entity_id; }
|
void set_entity_id(const char *entity_id) { this->entity_id_ = entity_id; }
|
||||||
void setup() override;
|
void setup() override;
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
float get_setup_priority() const override;
|
float get_setup_priority() const override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void write_state(bool state) override;
|
void write_state(bool state) override;
|
||||||
std::string entity_id_;
|
const char *entity_id_{nullptr};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace homeassistant
|
} // namespace homeassistant
|
||||||
|
|||||||
@@ -10,20 +10,19 @@ static const char *const TAG = "homeassistant.text_sensor";
|
|||||||
void HomeassistantTextSensor::setup() {
|
void HomeassistantTextSensor::setup() {
|
||||||
api::global_api_server->subscribe_home_assistant_state(
|
api::global_api_server->subscribe_home_assistant_state(
|
||||||
this->entity_id_, this->attribute_, [this](const std::string &state) {
|
this->entity_id_, this->attribute_, [this](const std::string &state) {
|
||||||
if (this->attribute_.has_value()) {
|
if (this->attribute_ != nullptr) {
|
||||||
ESP_LOGD(TAG, "'%s::%s': Got attribute state '%s'", this->entity_id_.c_str(),
|
ESP_LOGD(TAG, "'%s::%s': Got attribute state '%s'", this->entity_id_, this->attribute_, state.c_str());
|
||||||
this->attribute_.value().c_str(), state.c_str());
|
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGD(TAG, "'%s': Got state '%s'", this->entity_id_.c_str(), state.c_str());
|
ESP_LOGD(TAG, "'%s': Got state '%s'", this->entity_id_, state.c_str());
|
||||||
}
|
}
|
||||||
this->publish_state(state);
|
this->publish_state(state);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
void HomeassistantTextSensor::dump_config() {
|
void HomeassistantTextSensor::dump_config() {
|
||||||
LOG_TEXT_SENSOR("", "Homeassistant Text Sensor", this);
|
LOG_TEXT_SENSOR("", "Homeassistant Text Sensor", this);
|
||||||
ESP_LOGCONFIG(TAG, " Entity ID: '%s'", this->entity_id_.c_str());
|
ESP_LOGCONFIG(TAG, " Entity ID: '%s'", this->entity_id_);
|
||||||
if (this->attribute_.has_value()) {
|
if (this->attribute_ != nullptr) {
|
||||||
ESP_LOGCONFIG(TAG, " Attribute: '%s'", this->attribute_.value().c_str());
|
ESP_LOGCONFIG(TAG, " Attribute: '%s'", this->attribute_);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
float HomeassistantTextSensor::get_setup_priority() const { return setup_priority::AFTER_CONNECTION; }
|
float HomeassistantTextSensor::get_setup_priority() const { return setup_priority::AFTER_CONNECTION; }
|
||||||
|
|||||||
@@ -8,15 +8,15 @@ namespace homeassistant {
|
|||||||
|
|
||||||
class HomeassistantTextSensor : public text_sensor::TextSensor, public Component {
|
class HomeassistantTextSensor : public text_sensor::TextSensor, public Component {
|
||||||
public:
|
public:
|
||||||
void set_entity_id(const std::string &entity_id) { entity_id_ = entity_id; }
|
void set_entity_id(const char *entity_id) { this->entity_id_ = entity_id; }
|
||||||
void set_attribute(const std::string &attribute) { attribute_ = attribute; }
|
void set_attribute(const char *attribute) { this->attribute_ = attribute; }
|
||||||
void setup() override;
|
void setup() override;
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
float get_setup_priority() const override;
|
float get_setup_priority() const override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::string entity_id_;
|
const char *entity_id_{nullptr};
|
||||||
optional<std::string> attribute_;
|
const char *attribute_{nullptr};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace homeassistant
|
} // namespace homeassistant
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ host:
|
|||||||
# This is required for CustomAPIDevice to work
|
# This is required for CustomAPIDevice to work
|
||||||
api:
|
api:
|
||||||
custom_services: true
|
custom_services: true
|
||||||
|
homeassistant_states: true
|
||||||
# Also test that YAML services still work
|
# Also test that YAML services still work
|
||||||
actions:
|
actions:
|
||||||
- action: test_yaml_service
|
- action: test_yaml_service
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ void CustomAPIDeviceComponent::setup() {
|
|||||||
// Test array types
|
// Test array types
|
||||||
register_service(&CustomAPIDeviceComponent::on_service_with_arrays, "custom_service_with_arrays",
|
register_service(&CustomAPIDeviceComponent::on_service_with_arrays, "custom_service_with_arrays",
|
||||||
{"bool_array", "int_array", "float_array", "string_array"});
|
{"bool_array", "int_array", "float_array", "string_array"});
|
||||||
|
|
||||||
|
// Test Home Assistant state subscription using std::string API (custom_api_device.h)
|
||||||
|
// This tests the backward compatibility of the std::string overloads
|
||||||
|
subscribe_homeassistant_state(&CustomAPIDeviceComponent::on_ha_state_changed, std::string("sensor.custom_test"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void CustomAPIDeviceComponent::on_test_service() { ESP_LOGI(TAG, "Custom test service called!"); }
|
void CustomAPIDeviceComponent::on_test_service() { ESP_LOGI(TAG, "Custom test service called!"); }
|
||||||
@@ -48,6 +52,11 @@ void CustomAPIDeviceComponent::on_service_with_arrays(std::vector<bool> bool_arr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CustomAPIDeviceComponent::on_ha_state_changed(std::string entity_id, std::string state) {
|
||||||
|
ESP_LOGI(TAG, "Home Assistant state changed for %s: %s", entity_id.c_str(), state.c_str());
|
||||||
|
ESP_LOGI(TAG, "This subscription uses std::string API for backward compatibility");
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace custom_api_device_component
|
} // namespace custom_api_device_component
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
#endif // USE_API
|
#endif // USE_API
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ class CustomAPIDeviceComponent : public Component, public CustomAPIDevice {
|
|||||||
|
|
||||||
void on_service_with_arrays(std::vector<bool> bool_array, std::vector<int32_t> int_array,
|
void on_service_with_arrays(std::vector<bool> bool_array, std::vector<int32_t> int_array,
|
||||||
std::vector<float> float_array, std::vector<std::string> string_array);
|
std::vector<float> float_array, std::vector<std::string> string_array);
|
||||||
|
|
||||||
|
// Test Home Assistant state subscription with std::string API
|
||||||
|
void on_ha_state_changed(std::string entity_id, std::string state);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace custom_api_device_component
|
} // namespace custom_api_device_component
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ async def test_api_custom_services(
|
|||||||
custom_service_future = loop.create_future()
|
custom_service_future = loop.create_future()
|
||||||
custom_args_future = loop.create_future()
|
custom_args_future = loop.create_future()
|
||||||
custom_arrays_future = loop.create_future()
|
custom_arrays_future = loop.create_future()
|
||||||
|
ha_state_future = loop.create_future()
|
||||||
|
|
||||||
# Patterns to match in logs
|
# Patterns to match in logs
|
||||||
yaml_service_pattern = re.compile(r"YAML service called")
|
yaml_service_pattern = re.compile(r"YAML service called")
|
||||||
@@ -50,6 +51,9 @@ async def test_api_custom_services(
|
|||||||
custom_arrays_pattern = re.compile(
|
custom_arrays_pattern = re.compile(
|
||||||
r"Array service called with 2 bools, 3 ints, 2 floats, 2 strings"
|
r"Array service called with 2 bools, 3 ints, 2 floats, 2 strings"
|
||||||
)
|
)
|
||||||
|
ha_state_pattern = re.compile(
|
||||||
|
r"This subscription uses std::string API for backward compatibility"
|
||||||
|
)
|
||||||
|
|
||||||
def check_output(line: str) -> None:
|
def check_output(line: str) -> None:
|
||||||
"""Check log output for expected messages."""
|
"""Check log output for expected messages."""
|
||||||
@@ -65,6 +69,8 @@ async def test_api_custom_services(
|
|||||||
custom_args_future.set_result(True)
|
custom_args_future.set_result(True)
|
||||||
elif not custom_arrays_future.done() and custom_arrays_pattern.search(line):
|
elif not custom_arrays_future.done() and custom_arrays_pattern.search(line):
|
||||||
custom_arrays_future.set_result(True)
|
custom_arrays_future.set_result(True)
|
||||||
|
elif not ha_state_future.done() and ha_state_pattern.search(line):
|
||||||
|
ha_state_future.set_result(True)
|
||||||
|
|
||||||
# Run with log monitoring
|
# Run with log monitoring
|
||||||
async with (
|
async with (
|
||||||
@@ -198,3 +204,8 @@ async def test_api_custom_services(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
await asyncio.wait_for(custom_arrays_future, timeout=5.0)
|
await asyncio.wait_for(custom_arrays_future, timeout=5.0)
|
||||||
|
|
||||||
|
# Test Home Assistant state subscription (std::string API backward compatibility)
|
||||||
|
# This verifies that custom_api_device.h can still use std::string overloads
|
||||||
|
client.send_home_assistant_state("sensor.custom_test", "", "42.5")
|
||||||
|
await asyncio.wait_for(ha_state_future, timeout=5.0)
|
||||||
|
|||||||
Reference in New Issue
Block a user