mirror of
https://github.com/esphome/esphome.git
synced 2025-11-20 08:46:01 +00:00
Compare commits
28 Commits
light_loop
...
api_ha_sub
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
177026d8c4 | ||
|
|
4533b8f92c | ||
|
|
b9595c0795 | ||
|
|
efe2a1a506 | ||
|
|
185c1dec43 | ||
|
|
c39d17f864 | ||
|
|
b400a98fb3 | ||
|
|
83307684a3 | ||
|
|
da25951f6e | ||
|
|
4398fd84d2 | ||
|
|
bbd6d019e5 | ||
|
|
625172e07d | ||
|
|
1e9c7d3c6d | ||
|
|
4cdab4e2d8 | ||
|
|
c2bc7b3cdc | ||
|
|
2c3417062a | ||
|
|
c75abfb894 | ||
|
|
1157b4aee8 | ||
|
|
71dc2d374d | ||
|
|
0a224f919b | ||
|
|
7ef4b4f3d9 | ||
|
|
13b875c763 | ||
|
|
b02b07ffaf | ||
|
|
8804bc2815 | ||
|
|
61cef0a75c | ||
|
|
73bc5252a1 | ||
|
|
f2b10ad132 | ||
|
|
100ea46f03 |
@@ -460,6 +460,7 @@ esphome/components/st7735/* @SenexCrenshaw
|
||||
esphome/components/st7789v/* @kbx81
|
||||
esphome/components/st7920/* @marsjan155
|
||||
esphome/components/statsd/* @Links2004
|
||||
esphome/components/stts22h/* @B48D81EFCC
|
||||
esphome/components/substitutions/* @esphome/core
|
||||
esphome/components/sun/* @OttoWinter
|
||||
esphome/components/sun_gtil2/* @Mat931
|
||||
|
||||
@@ -1535,8 +1535,13 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
|
||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||
void APIConnection::on_home_assistant_state_response(const HomeAssistantStateResponse &msg) {
|
||||
for (auto &it : this->parent_->get_state_subs()) {
|
||||
if (it.entity_id == msg.entity_id && it.attribute.value() == msg.attribute) {
|
||||
it.callback(msg.state);
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1873,12 +1878,12 @@ void APIConnection::process_state_subscriptions_() {
|
||||
|
||||
const auto &it = subs[this->state_subs_at_];
|
||||
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
|
||||
resp.set_attribute(it.attribute.has_value() ? StringRef(it.attribute.value()) : StringRef(""));
|
||||
// Avoid string copy by using the const char* pointer if it exists
|
||||
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)) {
|
||||
this->state_subs_at_++;
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ static void dump_field(std::string &out, const char *field_name, float value, in
|
||||
static void dump_field(std::string &out, const char *field_name, uint64_t value, int indent = 2) {
|
||||
char buffer[64];
|
||||
append_field_prefix(out, field_name, indent);
|
||||
snprintf(buffer, 64, "%llu", value);
|
||||
snprintf(buffer, 64, "%" PRIu64, value);
|
||||
append_with_newline(out, buffer);
|
||||
}
|
||||
|
||||
|
||||
@@ -431,25 +431,56 @@ void APIServer::handle_action_response(uint32_t call_id, bool success, const std
|
||||
#endif // USE_API_HOMEASSISTANT_SERVICES
|
||||
|
||||
#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_copy_ and attribute_copy_ 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_copy_ = std::make_unique<std::string>(std::move(entity_id));
|
||||
sub.entity_id_ = sub.entity_id_copy_->c_str();
|
||||
|
||||
if (attribute.has_value()) {
|
||||
sub.attribute_copy_ = std::make_unique<std::string>(std::move(attribute.value()));
|
||||
sub.attribute_ = sub.attribute_copy_->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,
|
||||
std::function<void(std::string)> f) {
|
||||
this->state_subs_.push_back(HomeAssistantStateSubscription{
|
||||
.entity_id = std::move(entity_id),
|
||||
.attribute = std::move(attribute),
|
||||
.callback = std::move(f),
|
||||
.once = false,
|
||||
});
|
||||
this->add_state_subscription_(std::move(entity_id), std::move(attribute), std::move(f), false);
|
||||
}
|
||||
|
||||
void APIServer::get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
|
||||
std::function<void(std::string)> f) {
|
||||
this->state_subs_.push_back(HomeAssistantStateSubscription{
|
||||
.entity_id = std::move(entity_id),
|
||||
.attribute = std::move(attribute),
|
||||
.callback = std::move(f),
|
||||
.once = true,
|
||||
});
|
||||
};
|
||||
this->add_state_subscription_(std::move(entity_id), std::move(attribute), std::move(f), true);
|
||||
}
|
||||
|
||||
const std::vector<APIServer::HomeAssistantStateSubscription> &APIServer::get_state_subs() const {
|
||||
return this->state_subs_;
|
||||
|
||||
@@ -154,16 +154,27 @@ class APIServer : public Component, public Controller {
|
||||
|
||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||
struct HomeAssistantStateSubscription {
|
||||
std::string entity_id;
|
||||
optional<std::string> attribute;
|
||||
std::function<void(std::string)> callback;
|
||||
bool once;
|
||||
const char *entity_id_; // Pointer to flash (internal) or heap (external)
|
||||
const char *attribute_; // Pointer to flash or nullptr (nullptr means no attribute)
|
||||
std::function<void(std::string)> callback_;
|
||||
bool once_;
|
||||
|
||||
// Storage for external components using std::string API (custom_api_device.h)
|
||||
// These are only allocated when using the std::string overload
|
||||
std::unique_ptr<std::string> entity_id_copy_;
|
||||
std::unique_ptr<std::string> attribute_copy_;
|
||||
};
|
||||
|
||||
// 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,
|
||||
std::function<void(std::string)> f);
|
||||
void get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
|
||||
std::function<void(std::string)> f);
|
||||
|
||||
const std::vector<HomeAssistantStateSubscription> &get_state_subs() const;
|
||||
#endif
|
||||
#ifdef USE_API_SERVICES
|
||||
@@ -185,6 +196,13 @@ class APIServer : public Component, public Controller {
|
||||
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);
|
||||
#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)
|
||||
std::unique_ptr<socket::Socket> socket_ = nullptr;
|
||||
#ifdef USE_API_CLIENT_CONNECTED_TRIGGER
|
||||
|
||||
@@ -102,7 +102,7 @@ def customise_schema(config):
|
||||
"""
|
||||
config = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_MODEL): cv.one_of(*MODELS, upper=True),
|
||||
cv.Required(CONF_MODEL): cv.one_of(*MODELS, upper=True, space="-"),
|
||||
},
|
||||
extra=cv.ALLOW_EXTRA,
|
||||
)(config)
|
||||
|
||||
@@ -32,11 +32,15 @@ class SpectraE6(EpaperModel):
|
||||
|
||||
spectra_e6 = SpectraE6("spectra-e6")
|
||||
|
||||
spectra_e6.extend(
|
||||
"Seeed-reTerminal-E1002",
|
||||
spectra_e6_7p3 = spectra_e6.extend(
|
||||
"7.3in-Spectra-E6",
|
||||
width=800,
|
||||
height=480,
|
||||
data_rate="20MHz",
|
||||
)
|
||||
|
||||
spectra_e6_7p3.extend(
|
||||
"Seeed-reTerminal-E1002",
|
||||
cs_pin=10,
|
||||
dc_pin=11,
|
||||
reset_pin=12,
|
||||
|
||||
@@ -337,7 +337,7 @@ void Graph::draw_legend(display::Display *buff, uint16_t x_offset, uint16_t y_of
|
||||
return;
|
||||
|
||||
/// Plot border
|
||||
if (this->border_) {
|
||||
if (legend_->border_) {
|
||||
int w = legend_->width_;
|
||||
int h = legend_->height_;
|
||||
buff->horizontal_line(x_offset, y_offset, w, color);
|
||||
|
||||
@@ -19,11 +19,10 @@ void HomeassistantBinarySensor::setup() {
|
||||
case PARSE_ON:
|
||||
case PARSE_OFF:
|
||||
bool new_state = val == PARSE_ON;
|
||||
if (this->attribute_.has_value()) {
|
||||
ESP_LOGD(TAG, "'%s::%s': Got attribute state %s", this->entity_id_.c_str(),
|
||||
this->attribute_.value().c_str(), ONOFF(new_state));
|
||||
if (this->attribute_ != nullptr) {
|
||||
ESP_LOGD(TAG, "'%s::%s': Got attribute state %s", this->entity_id_, this->attribute_, ONOFF(new_state));
|
||||
} 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_) {
|
||||
this->publish_initial_state(new_state);
|
||||
@@ -37,9 +36,9 @@ void HomeassistantBinarySensor::setup() {
|
||||
}
|
||||
void HomeassistantBinarySensor::dump_config() {
|
||||
LOG_BINARY_SENSOR("", "Homeassistant Binary Sensor", this);
|
||||
ESP_LOGCONFIG(TAG, " Entity ID: '%s'", this->entity_id_.c_str());
|
||||
if (this->attribute_.has_value()) {
|
||||
ESP_LOGCONFIG(TAG, " Attribute: '%s'", this->attribute_.value().c_str());
|
||||
ESP_LOGCONFIG(TAG, " Entity ID: '%s'", this->entity_id_);
|
||||
if (this->attribute_ != nullptr) {
|
||||
ESP_LOGCONFIG(TAG, " Attribute: '%s'", this->attribute_);
|
||||
}
|
||||
}
|
||||
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 {
|
||||
public:
|
||||
void set_entity_id(const std::string &entity_id) { entity_id_ = entity_id; }
|
||||
void set_attribute(const std::string &attribute) { attribute_ = attribute; }
|
||||
void set_entity_id(const char *entity_id) { this->entity_id_ = entity_id; }
|
||||
void set_attribute(const char *attribute) { this->attribute_ = attribute; }
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
|
||||
protected:
|
||||
std::string entity_id_;
|
||||
optional<std::string> attribute_;
|
||||
const char *entity_id_{nullptr};
|
||||
const char *attribute_{nullptr};
|
||||
bool initial_{true};
|
||||
};
|
||||
|
||||
|
||||
@@ -12,21 +12,21 @@ static const char *const TAG = "homeassistant.number";
|
||||
void HomeassistantNumber::state_changed_(const std::string &state) {
|
||||
auto number_value = parse_number<float>(state);
|
||||
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);
|
||||
return;
|
||||
}
|
||||
if (this->state == number_value.value()) {
|
||||
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());
|
||||
}
|
||||
|
||||
void HomeassistantNumber::min_retrieved_(const std::string &min) {
|
||||
auto min_value = parse_number<float>(min);
|
||||
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;
|
||||
}
|
||||
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) {
|
||||
auto max_value = parse_number<float>(max);
|
||||
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;
|
||||
}
|
||||
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) {
|
||||
auto step_value = parse_number<float>(step);
|
||||
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;
|
||||
}
|
||||
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() {
|
||||
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(
|
||||
this->entity_id_, optional<std::string>("min"),
|
||||
std::bind(&HomeassistantNumber::min_retrieved_, this, std::placeholders::_1));
|
||||
this->entity_id_, "min", std::bind(&HomeassistantNumber::min_retrieved_, this, std::placeholders::_1));
|
||||
api::global_api_server->get_home_assistant_state(
|
||||
this->entity_id_, optional<std::string>("max"),
|
||||
std::bind(&HomeassistantNumber::max_retrieved_, this, std::placeholders::_1));
|
||||
this->entity_id_, "max", std::bind(&HomeassistantNumber::max_retrieved_, this, std::placeholders::_1));
|
||||
api::global_api_server->get_home_assistant_state(
|
||||
this->entity_id_, optional<std::string>("step"),
|
||||
std::bind(&HomeassistantNumber::step_retrieved_, this, std::placeholders::_1));
|
||||
this->entity_id_, "step", std::bind(&HomeassistantNumber::step_retrieved_, this, std::placeholders::_1));
|
||||
}
|
||||
|
||||
void HomeassistantNumber::dump_config() {
|
||||
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; }
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace homeassistant {
|
||||
|
||||
class HomeassistantNumber : public number::Number, public Component {
|
||||
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 dump_config() override;
|
||||
@@ -25,7 +25,7 @@ class HomeassistantNumber : public number::Number, public Component {
|
||||
|
||||
void control(float value) override;
|
||||
|
||||
std::string entity_id_;
|
||||
const char *entity_id_{nullptr};
|
||||
};
|
||||
} // namespace homeassistant
|
||||
} // namespace esphome
|
||||
|
||||
@@ -12,25 +12,24 @@ void HomeassistantSensor::setup() {
|
||||
this->entity_id_, this->attribute_, [this](const std::string &state) {
|
||||
auto val = parse_number<float>(state);
|
||||
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);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->attribute_.has_value()) {
|
||||
ESP_LOGD(TAG, "'%s::%s': Got attribute state %.2f", this->entity_id_.c_str(),
|
||||
this->attribute_.value().c_str(), *val);
|
||||
if (this->attribute_ != nullptr) {
|
||||
ESP_LOGD(TAG, "'%s::%s': Got attribute state %.2f", this->entity_id_, this->attribute_, *val);
|
||||
} 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);
|
||||
});
|
||||
}
|
||||
void HomeassistantSensor::dump_config() {
|
||||
LOG_SENSOR("", "Homeassistant Sensor", this);
|
||||
ESP_LOGCONFIG(TAG, " Entity ID: '%s'", this->entity_id_.c_str());
|
||||
if (this->attribute_.has_value()) {
|
||||
ESP_LOGCONFIG(TAG, " Attribute: '%s'", this->attribute_.value().c_str());
|
||||
ESP_LOGCONFIG(TAG, " Entity ID: '%s'", this->entity_id_);
|
||||
if (this->attribute_ != nullptr) {
|
||||
ESP_LOGCONFIG(TAG, " Attribute: '%s'", this->attribute_);
|
||||
}
|
||||
}
|
||||
float HomeassistantSensor::get_setup_priority() const { return setup_priority::AFTER_CONNECTION; }
|
||||
|
||||
@@ -8,15 +8,15 @@ namespace homeassistant {
|
||||
|
||||
class HomeassistantSensor : public sensor::Sensor, public Component {
|
||||
public:
|
||||
void set_entity_id(const std::string &entity_id) { entity_id_ = entity_id; }
|
||||
void set_attribute(const std::string &attribute) { attribute_ = attribute; }
|
||||
void set_entity_id(const char *entity_id) { this->entity_id_ = entity_id; }
|
||||
void set_attribute(const char *attribute) { this->attribute_ = attribute; }
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
|
||||
protected:
|
||||
std::string entity_id_;
|
||||
optional<std::string> attribute_;
|
||||
const char *entity_id_{nullptr};
|
||||
const char *attribute_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace homeassistant
|
||||
|
||||
@@ -10,7 +10,7 @@ static const char *const TAG = "homeassistant.switch";
|
||||
using namespace esphome::switch_;
|
||||
|
||||
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());
|
||||
switch (val) {
|
||||
case PARSE_NONE:
|
||||
@@ -20,7 +20,7 @@ void HomeassistantSwitch::setup() {
|
||||
case PARSE_ON:
|
||||
case PARSE_OFF:
|
||||
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);
|
||||
break;
|
||||
}
|
||||
@@ -29,7 +29,7 @@ void HomeassistantSwitch::setup() {
|
||||
|
||||
void HomeassistantSwitch::dump_config() {
|
||||
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; }
|
||||
|
||||
@@ -8,14 +8,14 @@ namespace homeassistant {
|
||||
|
||||
class HomeassistantSwitch : public switch_::Switch, public Component {
|
||||
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 dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
|
||||
protected:
|
||||
void write_state(bool state) override;
|
||||
std::string entity_id_;
|
||||
const char *entity_id_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace homeassistant
|
||||
|
||||
@@ -10,20 +10,19 @@ static const char *const TAG = "homeassistant.text_sensor";
|
||||
void HomeassistantTextSensor::setup() {
|
||||
api::global_api_server->subscribe_home_assistant_state(
|
||||
this->entity_id_, this->attribute_, [this](const std::string &state) {
|
||||
if (this->attribute_.has_value()) {
|
||||
ESP_LOGD(TAG, "'%s::%s': Got attribute state '%s'", this->entity_id_.c_str(),
|
||||
this->attribute_.value().c_str(), state.c_str());
|
||||
if (this->attribute_ != nullptr) {
|
||||
ESP_LOGD(TAG, "'%s::%s': Got attribute state '%s'", this->entity_id_, this->attribute_, state.c_str());
|
||||
} 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);
|
||||
});
|
||||
}
|
||||
void HomeassistantTextSensor::dump_config() {
|
||||
LOG_TEXT_SENSOR("", "Homeassistant Text Sensor", this);
|
||||
ESP_LOGCONFIG(TAG, " Entity ID: '%s'", this->entity_id_.c_str());
|
||||
if (this->attribute_.has_value()) {
|
||||
ESP_LOGCONFIG(TAG, " Attribute: '%s'", this->attribute_.value().c_str());
|
||||
ESP_LOGCONFIG(TAG, " Entity ID: '%s'", this->entity_id_);
|
||||
if (this->attribute_ != nullptr) {
|
||||
ESP_LOGCONFIG(TAG, " Attribute: '%s'", this->attribute_);
|
||||
}
|
||||
}
|
||||
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 {
|
||||
public:
|
||||
void set_entity_id(const std::string &entity_id) { entity_id_ = entity_id; }
|
||||
void set_attribute(const std::string &attribute) { attribute_ = attribute; }
|
||||
void set_entity_id(const char *entity_id) { this->entity_id_ = entity_id; }
|
||||
void set_attribute(const char *attribute) { this->attribute_ = attribute; }
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
|
||||
protected:
|
||||
std::string entity_id_;
|
||||
optional<std::string> attribute_;
|
||||
const char *entity_id_{nullptr};
|
||||
const char *attribute_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace homeassistant
|
||||
|
||||
@@ -23,9 +23,6 @@ void LightState::setup() {
|
||||
effect->init_internal(this);
|
||||
}
|
||||
|
||||
// Start with loop disabled if idle - respects any effects/transitions set up during initialization
|
||||
this->disable_loop_if_idle_();
|
||||
|
||||
// When supported color temperature range is known, initialize color temperature setting within bounds.
|
||||
auto traits = this->get_traits();
|
||||
float min_mireds = traits.get_min_mireds();
|
||||
@@ -128,9 +125,6 @@ void LightState::loop() {
|
||||
this->is_transformer_active_ = false;
|
||||
this->transformer_ = nullptr;
|
||||
this->target_state_reached_callback_.call();
|
||||
|
||||
// Disable loop if idle (no transformer and no effect)
|
||||
this->disable_loop_if_idle_();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,8 +132,6 @@ void LightState::loop() {
|
||||
if (this->next_write_) {
|
||||
this->next_write_ = false;
|
||||
this->output_->write_state(this);
|
||||
// Disable loop if idle (no transformer and no effect)
|
||||
this->disable_loop_if_idle_();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,8 +227,6 @@ void LightState::start_effect_(uint32_t effect_index) {
|
||||
this->active_effect_index_ = effect_index;
|
||||
auto *effect = this->get_active_effect_();
|
||||
effect->start_internal();
|
||||
// Enable loop while effect is active
|
||||
this->enable_loop();
|
||||
}
|
||||
LightEffect *LightState::get_active_effect_() {
|
||||
if (this->active_effect_index_ == 0) {
|
||||
@@ -251,8 +241,6 @@ void LightState::stop_effect_() {
|
||||
effect->stop();
|
||||
}
|
||||
this->active_effect_index_ = 0;
|
||||
// Disable loop if idle (no effect and no transformer)
|
||||
this->disable_loop_if_idle_();
|
||||
}
|
||||
|
||||
void LightState::start_transition_(const LightColorValues &target, uint32_t length, bool set_remote_values) {
|
||||
@@ -262,8 +250,6 @@ void LightState::start_transition_(const LightColorValues &target, uint32_t leng
|
||||
if (set_remote_values) {
|
||||
this->remote_values = target;
|
||||
}
|
||||
// Enable loop while transition is active
|
||||
this->enable_loop();
|
||||
}
|
||||
|
||||
void LightState::start_flash_(const LightColorValues &target, uint32_t length, bool set_remote_values) {
|
||||
@@ -279,8 +265,6 @@ void LightState::start_flash_(const LightColorValues &target, uint32_t length, b
|
||||
if (set_remote_values) {
|
||||
this->remote_values = target;
|
||||
};
|
||||
// Enable loop while flash is active
|
||||
this->enable_loop();
|
||||
}
|
||||
|
||||
void LightState::set_immediately_(const LightColorValues &target, bool set_remote_values) {
|
||||
@@ -292,14 +276,6 @@ void LightState::set_immediately_(const LightColorValues &target, bool set_remot
|
||||
}
|
||||
this->output_->update_state(this);
|
||||
this->next_write_ = true;
|
||||
this->enable_loop();
|
||||
}
|
||||
|
||||
void LightState::disable_loop_if_idle_() {
|
||||
// Only disable loop if both transformer and effect are inactive, and no pending writes
|
||||
if (this->transformer_ == nullptr && this->get_active_effect_() == nullptr && !this->next_write_) {
|
||||
this->disable_loop();
|
||||
}
|
||||
}
|
||||
|
||||
void LightState::save_remote_values_() {
|
||||
|
||||
@@ -255,9 +255,6 @@ class LightState : public EntityBase, public Component {
|
||||
/// Internal method to save the current remote_values to the preferences
|
||||
void save_remote_values_();
|
||||
|
||||
/// Disable loop if neither transformer nor effect is active
|
||||
void disable_loop_if_idle_();
|
||||
|
||||
/// Store the output to allow effects to have more access.
|
||||
LightOutput *output_;
|
||||
/// The currently active transformer for this light (transition/flash).
|
||||
|
||||
@@ -61,9 +61,18 @@ socklen_t set_sockaddr(struct sockaddr *addr, socklen_t addrlen, const std::stri
|
||||
server->sin6_family = AF_INET6;
|
||||
server->sin6_port = htons(port);
|
||||
|
||||
#ifdef USE_SOCKET_IMPL_BSD_SOCKETS
|
||||
// Use standard inet_pton for BSD sockets
|
||||
if (inet_pton(AF_INET6, ip_address.c_str(), &server->sin6_addr) != 1) {
|
||||
errno = EINVAL;
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
// Use LWIP-specific functions
|
||||
ip6_addr_t ip6;
|
||||
inet6_aton(ip_address.c_str(), &ip6);
|
||||
memcpy(server->sin6_addr.un.u32_addr, ip6.addr, sizeof(ip6.addr));
|
||||
#endif
|
||||
return sizeof(sockaddr_in6);
|
||||
}
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
|
||||
1
esphome/components/stts22h/__init__.py
Normal file
1
esphome/components/stts22h/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
CODEOWNERS = ["@B48D81EFCC"]
|
||||
33
esphome/components/stts22h/sensor.py
Normal file
33
esphome/components/stts22h/sensor.py
Normal file
@@ -0,0 +1,33 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import i2c, sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
sensor_ns = cg.esphome_ns.namespace("stts22h")
|
||||
stts22h = sensor_ns.class_(
|
||||
"STTS22HComponent", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
sensor.sensor_schema(
|
||||
stts22h,
|
||||
accuracy_decimals=2,
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(i2c.i2c_device_schema(0x3C))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = await sensor.new_sensor(config)
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
101
esphome/components/stts22h/stts22h.cpp
Normal file
101
esphome/components/stts22h/stts22h.cpp
Normal file
@@ -0,0 +1,101 @@
|
||||
#include "esphome/core/log.h"
|
||||
#include "stts22h.h"
|
||||
|
||||
namespace esphome::stts22h {
|
||||
|
||||
static const char *const TAG = "stts22h";
|
||||
|
||||
static const uint8_t WHOAMI_REG = 0x01;
|
||||
static const uint8_t CTRL_REG = 0x04;
|
||||
static const uint8_t TEMPERATURE_REG = 0x06;
|
||||
|
||||
// CTRL_REG flags
|
||||
static const uint8_t LOW_ODR_CTRL_ENABLE_FLAG = 0x80; // Flag to enable low ODR mode in CTRL_REG
|
||||
static const uint8_t FREERUN_CTRL_ENABLE_FLAG = 0x04; // Flag to enable FREERUN mode in CTRL_REG
|
||||
static const uint8_t ADD_INC_ENABLE_FLAG = 0x08; // Flag to enable ADD_INC (IF_ADD_INC) mode in CTRL_REG
|
||||
|
||||
static const uint8_t WHOAMI_STTS22H_IDENTIFICATION = 0xA0; // ID value of STTS22H in WHOAMI_REG
|
||||
|
||||
static const float SENSOR_SCALE = 0.01f; // Sensor resolution in degrees Celsius
|
||||
|
||||
void STTS22HComponent::setup() {
|
||||
// Check if device is a STTS22H
|
||||
if (!this->is_stts22h_sensor_()) {
|
||||
this->mark_failed("Device is not a STTS22H sensor");
|
||||
return;
|
||||
}
|
||||
|
||||
this->initialize_sensor_();
|
||||
}
|
||||
|
||||
void STTS22HComponent::update() {
|
||||
if (this->is_failed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this->publish_state(this->read_temperature_());
|
||||
}
|
||||
|
||||
void STTS22HComponent::dump_config() {
|
||||
LOG_SENSOR("", "STTS22H", this);
|
||||
LOG_I2C_DEVICE(this);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
|
||||
}
|
||||
}
|
||||
|
||||
float STTS22HComponent::read_temperature_() {
|
||||
uint8_t temp_reg_value[2];
|
||||
if (this->read_register(TEMPERATURE_REG, temp_reg_value, 2) != i2c::NO_ERROR) {
|
||||
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
|
||||
return NAN;
|
||||
}
|
||||
|
||||
// Combine the two bytes into a single 16-bit signed integer
|
||||
// The STTS22H temperature data is in two's complement format
|
||||
int16_t temp_raw_value = static_cast<int16_t>(encode_uint16(temp_reg_value[1], temp_reg_value[0]));
|
||||
return temp_raw_value * SENSOR_SCALE; // Apply sensor resolution
|
||||
}
|
||||
|
||||
bool STTS22HComponent::is_stts22h_sensor_() {
|
||||
uint8_t whoami_value;
|
||||
if (this->read_register(WHOAMI_REG, &whoami_value, 1) != i2c::NO_ERROR) {
|
||||
this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (whoami_value != WHOAMI_STTS22H_IDENTIFICATION) {
|
||||
this->mark_failed("Unexpected WHOAMI identifier. Sensor is not a STTS22H");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void STTS22HComponent::initialize_sensor_() {
|
||||
// Read current CTRL_REG configuration
|
||||
uint8_t ctrl_value;
|
||||
if (this->read_register(CTRL_REG, &ctrl_value, 1) != i2c::NO_ERROR) {
|
||||
this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
// Enable low ODR mode and enable ADD_INC
|
||||
// Before low ODR mode can be used,
|
||||
// FREERUN bit must be cleared (see sensor documentation)
|
||||
ctrl_value &= ~FREERUN_CTRL_ENABLE_FLAG; // Clear FREERUN bit
|
||||
if (this->write_register(CTRL_REG, &ctrl_value, 1) != i2c::NO_ERROR) {
|
||||
this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
// Enable LOW ODR mode and ADD_INC
|
||||
ctrl_value |= LOW_ODR_CTRL_ENABLE_FLAG | ADD_INC_ENABLE_FLAG; // Set LOW ODR bit and ADD_INC bit
|
||||
if (this->write_register(CTRL_REG, &ctrl_value, 1) != i2c::NO_ERROR) {
|
||||
this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace esphome::stts22h
|
||||
21
esphome/components/stts22h/stts22h.h
Normal file
21
esphome/components/stts22h/stts22h.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome::stts22h {
|
||||
|
||||
class STTS22HComponent : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void update() override;
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
void initialize_sensor_();
|
||||
bool is_stts22h_sensor_();
|
||||
float read_temperature_();
|
||||
};
|
||||
|
||||
} // namespace esphome::stts22h
|
||||
@@ -66,10 +66,14 @@ SubstituteFilter::SubstituteFilter(const std::initializer_list<Substitution> &su
|
||||
: substitutions_(substitutions) {}
|
||||
|
||||
optional<std::string> SubstituteFilter::new_value(std::string value) {
|
||||
std::size_t pos;
|
||||
for (const auto &sub : this->substitutions_) {
|
||||
while ((pos = value.find(sub.from)) != std::string::npos)
|
||||
std::size_t pos = 0;
|
||||
while ((pos = value.find(sub.from, pos)) != std::string::npos) {
|
||||
value.replace(pos, sub.from.size(), sub.to);
|
||||
// Advance past the replacement to avoid infinite loop when
|
||||
// the replacement contains the search pattern (e.g., f -> foo)
|
||||
pos += sub.to.size();
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -87,6 +87,29 @@ int nonblocking_send(httpd_handle_t hd, int sockfd, const char *buf, size_t buf_
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void AsyncWebServer::safe_close_with_shutdown(httpd_handle_t hd, int sockfd) {
|
||||
// CRITICAL: Shut down receive BEFORE closing to prevent lwIP race conditions
|
||||
//
|
||||
// The race condition occurs because close() initiates lwIP teardown while
|
||||
// the TCP/IP thread can still receive packets, causing assertions when
|
||||
// recv_tcp() sees partially-torn-down state.
|
||||
//
|
||||
// By shutting down receive first, we tell lwIP to stop accepting new data BEFORE
|
||||
// the teardown begins, eliminating the race window. We only shutdown RD (not RDWR)
|
||||
// to allow the FIN packet to be sent cleanly during close().
|
||||
//
|
||||
// Note: This function may be called with an already-closed socket if the network
|
||||
// stack closed it. In that case, shutdown() will fail but close() is safe to call.
|
||||
//
|
||||
// See: https://github.com/esphome/esphome-webserver/issues/163
|
||||
|
||||
// Attempt shutdown - ignore errors as socket may already be closed
|
||||
shutdown(sockfd, SHUT_RD);
|
||||
|
||||
// Always close - safe even if socket is already closed by network stack
|
||||
close(sockfd);
|
||||
}
|
||||
|
||||
void AsyncWebServer::end() {
|
||||
if (this->server_) {
|
||||
httpd_stop(this->server_);
|
||||
@@ -115,6 +138,8 @@ void AsyncWebServer::begin() {
|
||||
config.uri_match_fn = [](const char * /*unused*/, const char * /*unused*/, size_t /*unused*/) { return true; };
|
||||
// Enable LRU purging if requested (e.g., by captive portal to handle probe bursts)
|
||||
config.lru_purge_enable = this->lru_purge_enable_;
|
||||
// Use custom close function that shuts down before closing to prevent lwIP race conditions
|
||||
config.close_fn = AsyncWebServer::safe_close_with_shutdown;
|
||||
if (httpd_start(&this->server_, &config) == ESP_OK) {
|
||||
const httpd_uri_t handler_get = {
|
||||
.uri = "",
|
||||
@@ -505,17 +530,11 @@ AsyncEventSourceResponse::AsyncEventSourceResponse(const AsyncWebServerRequest *
|
||||
void AsyncEventSourceResponse::destroy(void *ptr) {
|
||||
auto *rsp = static_cast<AsyncEventSourceResponse *>(ptr);
|
||||
int fd = rsp->fd_.exchange(0); // Atomically get and clear fd
|
||||
|
||||
if (fd > 0) {
|
||||
ESP_LOGD(TAG, "Event source connection closed (fd: %d)", fd);
|
||||
// Immediately shut down the socket to prevent lwIP from delivering more data
|
||||
// This prevents "recv_tcp: recv for wrong pcb!" assertions when the TCP stack
|
||||
// tries to deliver queued data after the session is marked as dead
|
||||
// See: https://github.com/esphome/esphome/issues/11936
|
||||
shutdown(fd, SHUT_RDWR);
|
||||
// Note: We don't close() the socket - httpd owns it and will close it
|
||||
}
|
||||
// Session will be cleaned up in the main loop to avoid race conditions
|
||||
ESP_LOGD(TAG, "Event source connection closed (fd: %d)", fd);
|
||||
// Mark as dead - will be cleaned up in the main loop
|
||||
// Note: We don't delete or remove from set here to avoid race conditions
|
||||
// httpd will call our custom close_fn (safe_close_with_shutdown) which handles
|
||||
// shutdown() before close() to prevent lwIP race conditions
|
||||
}
|
||||
|
||||
// helper for allowing only unique entries in the queue
|
||||
|
||||
@@ -209,6 +209,7 @@ class AsyncWebServer {
|
||||
static esp_err_t request_handler(httpd_req_t *r);
|
||||
static esp_err_t request_post_handler(httpd_req_t *r);
|
||||
esp_err_t request_handler_(AsyncWebServerRequest *request) const;
|
||||
static void safe_close_with_shutdown(httpd_handle_t hd, int sockfd);
|
||||
#ifdef USE_WEBSERVER_OTA
|
||||
esp_err_t handle_multipart_upload_(httpd_req_t *r, const char *content_type);
|
||||
#endif
|
||||
|
||||
@@ -872,7 +872,13 @@ bssid_t WiFiComponent::wifi_bssid() {
|
||||
return bssid;
|
||||
}
|
||||
std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); }
|
||||
int8_t WiFiComponent::wifi_rssi() { return WiFi.status() == WL_CONNECTED ? WiFi.RSSI() : WIFI_RSSI_DISCONNECTED; }
|
||||
int8_t WiFiComponent::wifi_rssi() {
|
||||
if (WiFi.status() != WL_CONNECTED)
|
||||
return WIFI_RSSI_DISCONNECTED;
|
||||
int8_t rssi = WiFi.RSSI();
|
||||
// Values >= 31 are error codes per NONOS SDK API, not valid RSSI readings
|
||||
return rssi >= 31 ? WIFI_RSSI_DISCONNECTED : rssi;
|
||||
}
|
||||
int32_t WiFiComponent::get_wifi_channel() { return WiFi.channel(); }
|
||||
network::IPAddress WiFiComponent::wifi_subnet_mask_() { return {(const ip_addr_t *) WiFi.subnetMask()}; }
|
||||
network::IPAddress WiFiComponent::wifi_gateway_ip_() { return {(const ip_addr_t *) WiFi.gatewayIP()}; }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
pylint==4.0.3
|
||||
flake8==7.3.0 # also change in .pre-commit-config.yaml when updating
|
||||
ruff==0.14.5 # also change in .pre-commit-config.yaml when updating
|
||||
pyupgrade==3.21.1 # also change in .pre-commit-config.yaml when updating
|
||||
pyupgrade==3.21.2 # also change in .pre-commit-config.yaml when updating
|
||||
pre-commit
|
||||
|
||||
# Unit tests
|
||||
|
||||
@@ -462,7 +462,7 @@ class Int64Type(TypeInfo):
|
||||
wire_type = WireType.VARINT # Uses wire type 0
|
||||
|
||||
def dump(self, name: str) -> str:
|
||||
o = f'snprintf(buffer, sizeof(buffer), "%lld", {name});\n'
|
||||
o = f'snprintf(buffer, sizeof(buffer), "%" PRId64, {name});\n'
|
||||
o += "out.append(buffer);"
|
||||
return o
|
||||
|
||||
@@ -482,7 +482,7 @@ class UInt64Type(TypeInfo):
|
||||
wire_type = WireType.VARINT # Uses wire type 0
|
||||
|
||||
def dump(self, name: str) -> str:
|
||||
o = f'snprintf(buffer, sizeof(buffer), "%llu", {name});\n'
|
||||
o = f'snprintf(buffer, sizeof(buffer), "%" PRIu64, {name});\n'
|
||||
o += "out.append(buffer);"
|
||||
return o
|
||||
|
||||
@@ -522,7 +522,7 @@ class Fixed64Type(TypeInfo):
|
||||
wire_type = WireType.FIXED64 # Uses wire type 1
|
||||
|
||||
def dump(self, name: str) -> str:
|
||||
o = f'snprintf(buffer, sizeof(buffer), "%llu", {name});\n'
|
||||
o = f'snprintf(buffer, sizeof(buffer), "%" PRIu64, {name});\n'
|
||||
o += "out.append(buffer);"
|
||||
return o
|
||||
|
||||
@@ -1106,7 +1106,7 @@ class SFixed64Type(TypeInfo):
|
||||
wire_type = WireType.FIXED64 # Uses wire type 1
|
||||
|
||||
def dump(self, name: str) -> str:
|
||||
o = f'snprintf(buffer, sizeof(buffer), "%lld", {name});\n'
|
||||
o = f'snprintf(buffer, sizeof(buffer), "%" PRId64, {name});\n'
|
||||
o += "out.append(buffer);"
|
||||
return o
|
||||
|
||||
@@ -1150,7 +1150,7 @@ class SInt64Type(TypeInfo):
|
||||
wire_type = WireType.VARINT # Uses wire type 0
|
||||
|
||||
def dump(self, name: str) -> str:
|
||||
o = f'snprintf(buffer, sizeof(buffer), "%lld", {name});\n'
|
||||
o = f'snprintf(buffer, sizeof(buffer), "%" PRId64, {name});\n'
|
||||
o += "out.append(buffer);"
|
||||
return o
|
||||
|
||||
@@ -2546,7 +2546,7 @@ static void dump_field(std::string &out, const char *field_name, float value, in
|
||||
static void dump_field(std::string &out, const char *field_name, uint64_t value, int indent = 2) {
|
||||
char buffer[64];
|
||||
append_field_prefix(out, field_name, indent);
|
||||
snprintf(buffer, 64, "%llu", value);
|
||||
snprintf(buffer, 64, "%" PRIu64, value);
|
||||
append_with_newline(out, buffer);
|
||||
}
|
||||
|
||||
|
||||
@@ -115,8 +115,8 @@ wifi:
|
||||
password: PASSWORD123
|
||||
|
||||
time:
|
||||
platform: sntp
|
||||
id: time_id
|
||||
- platform: sntp
|
||||
id: sntp_time
|
||||
|
||||
text:
|
||||
- id: lvgl_text
|
||||
|
||||
@@ -478,19 +478,19 @@ lvgl:
|
||||
id: hello_label
|
||||
text:
|
||||
time_format: "%c"
|
||||
time: time_id
|
||||
time: sntp_time
|
||||
- lvgl.label.update:
|
||||
id: hello_label
|
||||
text:
|
||||
time_format: "%c"
|
||||
time: !lambda return id(time_id).now();
|
||||
time: !lambda return id(sntp_time).now();
|
||||
- lvgl.label.update:
|
||||
id: hello_label
|
||||
text:
|
||||
time_format: "%c"
|
||||
time: !lambda |-
|
||||
ESP_LOGD("label", "multi-line lambda");
|
||||
return id(time_id).now();
|
||||
return id(sntp_time).now();
|
||||
on_value:
|
||||
logger.log:
|
||||
format: "state now %d"
|
||||
|
||||
@@ -4,6 +4,7 @@ wifi:
|
||||
|
||||
time:
|
||||
- platform: sntp
|
||||
id: sntp_time
|
||||
|
||||
mqtt:
|
||||
broker: "192.168.178.84"
|
||||
|
||||
4
tests/components/stts22h/common.yaml
Normal file
4
tests/components/stts22h/common.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
sensor:
|
||||
- platform: stts22h
|
||||
name: Temperature
|
||||
update_interval: 15s
|
||||
4
tests/components/stts22h/test.esp32-idf.yaml
Normal file
4
tests/components/stts22h/test.esp32-idf.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
4
tests/components/stts22h/test.esp8266-ard.yaml
Normal file
4
tests/components/stts22h/test.esp8266-ard.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/esp8266-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
4
tests/components/stts22h/test.nrf52.yaml
Normal file
4
tests/components/stts22h/test.nrf52.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/nrf52.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
4
tests/components/stts22h/test.rp2040-ard.yaml
Normal file
4
tests/components/stts22h/test.rp2040-ard.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/rp2040-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -3,6 +3,7 @@ wifi:
|
||||
|
||||
time:
|
||||
- platform: sntp
|
||||
id: sntp_time
|
||||
|
||||
sensor:
|
||||
- platform: uptime
|
||||
|
||||
@@ -4,8 +4,10 @@ wifi:
|
||||
|
||||
time:
|
||||
- platform: sntp
|
||||
id: sntp_time
|
||||
|
||||
wireguard:
|
||||
time_id: sntp_time
|
||||
address: 172.16.34.100
|
||||
netmask: 255.255.255.0
|
||||
# NEVER use the following keys for your VPN -- they are now public!
|
||||
|
||||
@@ -5,6 +5,7 @@ host:
|
||||
# This is required for CustomAPIDevice to work
|
||||
api:
|
||||
custom_services: true
|
||||
homeassistant_states: true
|
||||
# Also test that YAML services still work
|
||||
actions:
|
||||
- action: test_yaml_service
|
||||
|
||||
@@ -17,6 +17,10 @@ void CustomAPIDeviceComponent::setup() {
|
||||
// Test array types
|
||||
register_service(&CustomAPIDeviceComponent::on_service_with_arrays, "custom_service_with_arrays",
|
||||
{"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!"); }
|
||||
@@ -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 esphome
|
||||
#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,
|
||||
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
|
||||
|
||||
@@ -38,6 +38,7 @@ async def test_api_custom_services(
|
||||
custom_service_future = loop.create_future()
|
||||
custom_args_future = loop.create_future()
|
||||
custom_arrays_future = loop.create_future()
|
||||
ha_state_future = loop.create_future()
|
||||
|
||||
# Patterns to match in logs
|
||||
yaml_service_pattern = re.compile(r"YAML service called")
|
||||
@@ -50,6 +51,9 @@ async def test_api_custom_services(
|
||||
custom_arrays_pattern = re.compile(
|
||||
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:
|
||||
"""Check log output for expected messages."""
|
||||
@@ -65,6 +69,8 @@ async def test_api_custom_services(
|
||||
custom_args_future.set_result(True)
|
||||
elif not custom_arrays_future.done() and custom_arrays_pattern.search(line):
|
||||
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
|
||||
async with (
|
||||
@@ -198,3 +204,8 @@ async def test_api_custom_services(
|
||||
},
|
||||
)
|
||||
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