Compare commits
425 Commits
multi_devi
...
applicatio
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ef86a9e5e | ||
|
|
fac20a1f97 | ||
|
|
c65586b5e1 | ||
|
|
b27b018b06 | ||
|
|
403da1e632 | ||
|
|
2371ec1f9e | ||
|
|
17497eec43 | ||
|
|
6d0c6329ad | ||
|
|
5e3ec2d34b | ||
|
|
78d84644c9 | ||
|
|
0cd0f8015a | ||
|
|
4b5424f695 | ||
|
|
a1d59040f7 | ||
|
|
0306398072 | ||
|
|
a7e0bf9013 | ||
|
|
ddb988cd83 | ||
|
|
6b5b0815d7 | ||
|
|
8388497038 | ||
|
|
9074ef792f | ||
|
|
23765cd4f5 | ||
|
|
e20c6468d0 | ||
|
|
b90516de1d | ||
|
|
ec5cc0f00f | ||
|
|
5dda5a976e | ||
|
|
915da9ae13 | ||
|
|
8652464f4e | ||
|
|
ce6ce1c1f8 | ||
|
|
39efe67e55 | ||
|
|
748ffa00f3 | ||
|
|
e8d9df2b0e | ||
|
|
17396d67de | ||
|
|
edd6a86714 | ||
|
|
85b4012c56 | ||
|
|
7d98433502 | ||
|
|
23774ae03b | ||
|
|
f35be6b5cc | ||
|
|
b18ff48b4a | ||
|
|
0dedbcdd71 | ||
|
|
7c28134214 | ||
|
|
16860e8a30 | ||
|
|
5362d1a89f | ||
|
|
5531296ee0 | ||
|
|
47db5e26f3 | ||
|
|
cf5197b68a | ||
|
|
9f831e91b3 | ||
|
|
2df0ebd895 | ||
|
|
8b25b1eee6 | ||
|
|
3bbf30ff5f | ||
|
|
254b6a17f3 | ||
|
|
dbb0d6349a | ||
|
|
5cd498fbe9 | ||
|
|
250f515f08 | ||
|
|
0ec0a9e313 | ||
|
|
184f42ef03 | ||
|
|
499517418d | ||
|
|
606b9c1a6d | ||
|
|
971e954a54 | ||
|
|
e3aaf3219d | ||
|
|
0eea1c0e40 | ||
|
|
0773819778 | ||
|
|
170869b7db | ||
|
|
5dc54782e5 | ||
|
|
97b26fbefe | ||
|
|
686cc58d6c | ||
|
|
76a59759b2 | ||
|
|
93245a24b5 | ||
|
|
6a22ea1c7d | ||
|
|
56a02409c8 | ||
|
|
edeafd5a53 | ||
|
|
f67490b69b | ||
|
|
b76e34fb7b | ||
|
|
ddbda5032b | ||
|
|
5898d34b0a | ||
|
|
b0c02341ff | ||
|
|
19cbc8c33b | ||
|
|
02e61ef5d3 | ||
|
|
8d5d18064d | ||
|
|
c5ef7ebd27 | ||
|
|
047a3e0e8c | ||
|
|
13b23f840b | ||
|
|
147f6012b2 | ||
|
|
2c315595f0 | ||
|
|
20405c84ac | ||
|
|
0bc59b97de | ||
|
|
a3a3bdc7eb | ||
|
|
e767f30886 | ||
|
|
e8c250a03c | ||
|
|
d6725fc1ca | ||
|
|
8ec998ff30 | ||
|
|
23cc0c7f39 | ||
|
|
19b8bd6aa8 | ||
|
|
ed57e7c6b0 | ||
|
|
9f489c9f27 | ||
|
|
f036989361 | ||
|
|
6afa8141c0 | ||
|
|
587964c6f1 | ||
|
|
7aea82a273 | ||
|
|
bfa80157f2 | ||
|
|
99b1b079d0 | ||
|
|
5697d549a8 | ||
|
|
a0b3527710 | ||
|
|
df24f48fa1 | ||
|
|
13d53590b2 | ||
|
|
e7fa156254 | ||
|
|
a8ab6b1c43 | ||
|
|
6212c6f80f | ||
|
|
bbd5d050a9 | ||
|
|
71a96fdcbf | ||
|
|
41697a7b1b | ||
|
|
912e265bc0 | ||
|
|
96ee6fb064 | ||
|
|
788dba8ef3 | ||
|
|
fdde9c4681 | ||
|
|
f195e73d38 | ||
|
|
b0d9ffc6a1 | ||
|
|
e17619841d | ||
|
|
eb6a7cf3b9 | ||
|
|
9901e2d72e | ||
|
|
bcf961c0b0 | ||
|
|
f84a4c9753 | ||
|
|
df56ca0236 | ||
|
|
de0cd0ec67 | ||
|
|
67c30245c4 | ||
|
|
1f72757591 | ||
|
|
35c2fdf6af | ||
|
|
d1ecd841be | ||
|
|
828a49697c | ||
|
|
0551495501 | ||
|
|
2bbffe4a68 | ||
|
|
281ad90e39 | ||
|
|
ed50976a07 | ||
|
|
a3400037d9 | ||
|
|
f0d82f75bc | ||
|
|
349cb80e90 | ||
|
|
c263ee39af | ||
|
|
e99bc52756 | ||
|
|
7944b2b8e9 | ||
|
|
ca6ae746c1 | ||
|
|
deabac18b2 | ||
|
|
5cf8681c61 | ||
|
|
ca7ede8f96 | ||
|
|
4969682d52 | ||
|
|
8002fe0dd5 | ||
|
|
7dfdf965b7 | ||
|
|
b408795dd6 | ||
|
|
a5a099336b | ||
|
|
4ae56fc004 | ||
|
|
3f71c09b7b | ||
|
|
bd50a7f1ab | ||
|
|
51e4c45e5c | ||
|
|
e3fae49add | ||
|
|
610215ab60 | ||
|
|
74acbda435 | ||
|
|
25c4af777c | ||
|
|
ec186e6324 | ||
|
|
150b7a98f3 | ||
|
|
8ae7c1cff0 | ||
|
|
7f1d0eef98 | ||
|
|
1179ab33f2 | ||
|
|
a09faa1c10 | ||
|
|
c0319d9b2f | ||
|
|
4870cd2921 | ||
|
|
d4280ec68b | ||
|
|
52cdc11927 | ||
|
|
8345b8c9ce | ||
|
|
c56f0677c3 | ||
|
|
00e9e1421e | ||
|
|
93c72c6e6c | ||
|
|
9cea930dbd | ||
|
|
7b9bd70729 | ||
|
|
5115c7a100 | ||
|
|
5634494e64 | ||
|
|
aa8bd4abf1 | ||
|
|
17fd69dd7f | ||
|
|
1d9dae374b | ||
|
|
cb2241ad91 | ||
|
|
d8a7e9abc8 | ||
|
|
969abc3f29 | ||
|
|
766fdc8a1f | ||
|
|
4c37c20d76 | ||
|
|
7d314398e1 | ||
|
|
b69191e3a8 | ||
|
|
b27c6b3596 | ||
|
|
5453835963 | ||
|
|
4d55ba057c | ||
|
|
325c01242c | ||
|
|
45b32bca89 | ||
|
|
7620049214 | ||
|
|
3553495a60 | ||
|
|
3ce6db61d5 | ||
|
|
798ff32c40 | ||
|
|
430cee8bda | ||
|
|
1fe3fb25a6 | ||
|
|
685ed87581 | ||
|
|
ea3ea1eee7 | ||
|
|
c9edcb909b | ||
|
|
35bfc9f069 | ||
|
|
c4aec194b9 | ||
|
|
e8547b16f6 | ||
|
|
2bbe08cee0 | ||
|
|
0a0c369b88 | ||
|
|
5d2f454a94 | ||
|
|
04bcc5c879 | ||
|
|
d4db16665f | ||
|
|
20b7a494f6 | ||
|
|
fbdce3ad89 | ||
|
|
4fc8807f02 | ||
|
|
83075bfb5c | ||
|
|
4074ec0425 | ||
|
|
8e1694dd0f | ||
|
|
911df18855 | ||
|
|
6b049e93f8 | ||
|
|
a335dcc379 | ||
|
|
c6478c8a79 | ||
|
|
cc9d40cb60 | ||
|
|
0a6b7f9a1b | ||
|
|
daa1fb9a7a | ||
|
|
b7d543290b | ||
|
|
ea852b60ac | ||
|
|
ed341988ea | ||
|
|
057b6c8e30 | ||
|
|
44444fe071 | ||
|
|
797330d6ab | ||
|
|
a630d5b5f5 | ||
|
|
eb3dc82b5d | ||
|
|
34ed18d562 | ||
|
|
1ce02ee313 | ||
|
|
2a26a0188c | ||
|
|
50cb05d1b1 | ||
|
|
6e739ac453 | ||
|
|
7aa2fd9f0e | ||
|
|
8e254e1b03 | ||
|
|
1ad9d717ff | ||
|
|
104658e43a | ||
|
|
e7e4b995bf | ||
|
|
b35b54f2c2 | ||
|
|
f80aeb1d1d | ||
|
|
6a756ab3b6 | ||
|
|
58a697bed1 | ||
|
|
280960ac18 | ||
|
|
0640ff13aa | ||
|
|
545505691f | ||
|
|
11fcf81321 | ||
|
|
c565b37dc8 | ||
|
|
3d18495270 | ||
|
|
419e4e63e9 | ||
|
|
724aa2bf65 | ||
|
|
573fa8aeb3 | ||
|
|
8a672e34c5 | ||
|
|
bc49211dab | ||
|
|
4ef9c3667e | ||
|
|
6babe516ac | ||
|
|
e0b258ef7e | ||
|
|
ff0c3a89b1 | ||
|
|
2511b81048 | ||
|
|
6ffcd94edc | ||
|
|
2fcf73c812 | ||
|
|
dee0608af9 | ||
|
|
d11860a383 | ||
|
|
1c05115bf5 | ||
|
|
d7e7382d0b | ||
|
|
872388f6e3 | ||
|
|
1215ef920b | ||
|
|
d19d5a23ea | ||
|
|
f49a779f1d | ||
|
|
d8bf5b80e1 | ||
|
|
69483b9353 | ||
|
|
14e8548989 | ||
|
|
4abd93b661 | ||
|
|
5d925af76f | ||
|
|
b999c6064a | ||
|
|
94e3576978 | ||
|
|
7a22406a2d | ||
|
|
e60684494f | ||
|
|
9db28ed779 | ||
|
|
6fd8c5cee7 | ||
|
|
787ec43266 | ||
|
|
a4efc63bf2 | ||
|
|
80a8f1437e | ||
|
|
fcca94169d | ||
|
|
d1924088e3 | ||
|
|
fd31afe09c | ||
|
|
7a763712c5 | ||
|
|
7216be5da7 | ||
|
|
711b0a291b | ||
|
|
dfc96496c8 | ||
|
|
2a1c5ef333 | ||
|
|
9755209499 | ||
|
|
0b26e537d4 | ||
|
|
98c6233ec3 | ||
|
|
f711706b1a | ||
|
|
cee7789ab6 | ||
|
|
8a06c4380d | ||
|
|
72ecf7a288 | ||
|
|
ef98c7502d | ||
|
|
03d0e74b65 | ||
|
|
5b8fdc0364 | ||
|
|
593b4bd137 | ||
|
|
267e12d058 | ||
|
|
4a5e39b651 | ||
|
|
ea24fa5b78 | ||
|
|
bb2bb128f7 | ||
|
|
94e8a856d7 | ||
|
|
4c19fbf98e | ||
|
|
60f8938bfa | ||
|
|
55679662b5 | ||
|
|
53df959e49 | ||
|
|
8e6ef9966f | ||
|
|
1d52fceafa | ||
|
|
99186ed864 | ||
|
|
383931d484 | ||
|
|
0b49a54cb3 | ||
|
|
705c0f1891 | ||
|
|
544c3ffc95 | ||
|
|
33f252a45d | ||
|
|
f55d82a015 | ||
|
|
8cf33fdef0 | ||
|
|
f858d98811 | ||
|
|
2a6165d440 | ||
|
|
4586528c40 | ||
|
|
23a07baa19 | ||
|
|
f9040ca932 | ||
|
|
4cea7f0237 | ||
|
|
b1847d5e98 | ||
|
|
9ce4d2e952 | ||
|
|
247078e06d | ||
|
|
a0cd72de28 | ||
|
|
e467f569f0 | ||
|
|
e31c7b7dfc | ||
|
|
dc2e0c832b | ||
|
|
7ddf51bb51 | ||
|
|
8fb3856665 | ||
|
|
183dd74f3e | ||
|
|
4f29039b41 | ||
|
|
102fcbec20 | ||
|
|
d00e5212c7 | ||
|
|
0e6bfb62cd | ||
|
|
aa930fb6b6 | ||
|
|
f327ed87e9 | ||
|
|
2de9be0589 | ||
|
|
345cde8645 | ||
|
|
cf152af9ae | ||
|
|
d6333dcfd9 | ||
|
|
0121f799f0 | ||
|
|
82c39580df | ||
|
|
53a578a46f | ||
|
|
62612ef80b | ||
|
|
61ac874c4c | ||
|
|
976b200ff6 | ||
|
|
852343b6d8 | ||
|
|
c56af9d52b | ||
|
|
05f18e2828 | ||
|
|
72804caab2 | ||
|
|
80cbe5c7c9 | ||
|
|
21892d1236 | ||
|
|
13824624f8 | ||
|
|
0fd72ecbab | ||
|
|
f848cb1546 | ||
|
|
633854081a | ||
|
|
4fed9a581b | ||
|
|
e9c1202aaa | ||
|
|
0a7ae279d0 | ||
|
|
0de2696543 | ||
|
|
a7dc239b71 | ||
|
|
fe0e6990f5 | ||
|
|
5ba65e92d9 | ||
|
|
a1452b52c9 | ||
|
|
dd2aa23a5f | ||
|
|
0e0359ba7d | ||
|
|
93b1b7aded | ||
|
|
9472dc6a53 | ||
|
|
67b681854e | ||
|
|
7b5990833e | ||
|
|
b6d5d04589 | ||
|
|
fdfbb3e944 | ||
|
|
faa7a3e37f | ||
|
|
23748b82bb | ||
|
|
bccb6f578a | ||
|
|
de8a5d6e9e | ||
|
|
a8eb3f7961 | ||
|
|
0877b3e2af | ||
|
|
99a54369bf | ||
|
|
f7533dfc5c | ||
|
|
ee7d95272d | ||
|
|
2b9b1d12e6 | ||
|
|
2cbb5c7d8e | ||
|
|
9686c7babe | ||
|
|
66bd4c96c4 | ||
|
|
dc47faa4b6 | ||
|
|
55ee0b116d | ||
|
|
c6957c08bc | ||
|
|
8fe6a323d8 | ||
|
|
8e51590c32 | ||
|
|
ae066d5627 | ||
|
|
6760279916 | ||
|
|
3c208050b0 | ||
|
|
bbc7c9fb37 | ||
|
|
e1c3862586 | ||
|
|
c24b7cb7bd | ||
|
|
c91e16549d | ||
|
|
6e70aca458 | ||
|
|
d9ffd0ac8e | ||
|
|
4641f73d19 | ||
|
|
9f0051c21f | ||
|
|
0331cb09e8 | ||
|
|
2f8946f86c | ||
|
|
88a3df4008 | ||
|
|
0adf514bd6 | ||
|
|
a1b5a2abcb | ||
|
|
068c62c6fe | ||
|
|
0e9f14f969 | ||
|
|
78315fd388 | ||
|
|
0ab69002df | ||
|
|
1eec1239ec | ||
|
|
98a2f23024 | ||
|
|
c955897d1b | ||
|
|
cfdb0925ce | ||
|
|
83db3eddd9 | ||
|
|
cc2c5a544e | ||
|
|
8fba8c2800 | ||
|
|
51d1da8460 | ||
|
|
2f1257056d | ||
|
|
2f8f6967bf | ||
|
|
246527e618 | ||
|
|
3857cc9c83 |
@@ -33,9 +33,14 @@ namespace api {
|
|||||||
// Since each message could contain multiple protobuf messages when using packet batching,
|
// Since each message could contain multiple protobuf messages when using packet batching,
|
||||||
// this limits the number of messages processed, not the number of TCP packets.
|
// this limits the number of messages processed, not the number of TCP packets.
|
||||||
static constexpr uint8_t MAX_MESSAGES_PER_LOOP = 5;
|
static constexpr uint8_t MAX_MESSAGES_PER_LOOP = 5;
|
||||||
|
static constexpr uint8_t MAX_PING_RETRIES = 60;
|
||||||
|
static constexpr uint16_t PING_RETRY_INTERVAL = 1000;
|
||||||
|
static constexpr uint32_t KEEPALIVE_DISCONNECT_TIMEOUT = (KEEPALIVE_TIMEOUT_MS * 5) / 2;
|
||||||
|
|
||||||
static const char *const TAG = "api.connection";
|
static const char *const TAG = "api.connection";
|
||||||
|
#ifdef USE_ESP32_CAMERA
|
||||||
static const int ESP32_CAMERA_STOP_STREAM = 5000;
|
static const int ESP32_CAMERA_STOP_STREAM = 5000;
|
||||||
|
#endif
|
||||||
|
|
||||||
APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent)
|
APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent)
|
||||||
: parent_(parent), initial_state_iterator_(this), list_entities_iterator_(this) {
|
: parent_(parent), initial_state_iterator_(this), list_entities_iterator_(this) {
|
||||||
@@ -60,10 +65,6 @@ uint32_t APIConnection::get_batch_delay_ms_() const { return this->parent_->get_
|
|||||||
void APIConnection::start() {
|
void APIConnection::start() {
|
||||||
this->last_traffic_ = App.get_loop_component_start_time();
|
this->last_traffic_ = App.get_loop_component_start_time();
|
||||||
|
|
||||||
// Set next_ping_retry_ to prevent immediate ping
|
|
||||||
// This ensures the first ping happens after the keepalive period
|
|
||||||
this->next_ping_retry_ = this->last_traffic_ + KEEPALIVE_TIMEOUT_MS;
|
|
||||||
|
|
||||||
APIError err = this->helper_->init();
|
APIError err = this->helper_->init();
|
||||||
if (err != APIError::OK) {
|
if (err != APIError::OK) {
|
||||||
on_fatal_error();
|
on_fatal_error();
|
||||||
@@ -90,16 +91,6 @@ APIConnection::~APIConnection() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void APIConnection::loop() {
|
void APIConnection::loop() {
|
||||||
if (this->remove_)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!network::is_connected()) {
|
|
||||||
// when network is disconnected force disconnect immediately
|
|
||||||
// don't wait for timeout
|
|
||||||
this->on_fatal_error();
|
|
||||||
ESP_LOGW(TAG, "%s: Network unavailable; disconnecting", this->get_client_combined_info().c_str());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this->next_close_) {
|
if (this->next_close_) {
|
||||||
// requested a disconnect
|
// requested a disconnect
|
||||||
this->helper_->close();
|
this->helper_->close();
|
||||||
@@ -152,39 +143,31 @@ void APIConnection::loop() {
|
|||||||
|
|
||||||
// Process deferred batch if scheduled
|
// Process deferred batch if scheduled
|
||||||
if (this->deferred_batch_.batch_scheduled &&
|
if (this->deferred_batch_.batch_scheduled &&
|
||||||
App.get_loop_component_start_time() - this->deferred_batch_.batch_start_time >= this->get_batch_delay_ms_()) {
|
now - this->deferred_batch_.batch_start_time >= this->get_batch_delay_ms_()) {
|
||||||
this->process_batch_();
|
this->process_batch_();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this->list_entities_iterator_.completed())
|
if (!this->list_entities_iterator_.completed()) {
|
||||||
this->list_entities_iterator_.advance();
|
this->list_entities_iterator_.advance();
|
||||||
if (!this->initial_state_iterator_.completed() && this->list_entities_iterator_.completed())
|
} else if (!this->initial_state_iterator_.completed()) {
|
||||||
this->initial_state_iterator_.advance();
|
this->initial_state_iterator_.advance();
|
||||||
|
}
|
||||||
|
|
||||||
static uint8_t max_ping_retries = 60;
|
|
||||||
static uint16_t ping_retry_interval = 1000;
|
|
||||||
if (this->sent_ping_) {
|
if (this->sent_ping_) {
|
||||||
// Disconnect if not responded within 2.5*keepalive
|
// Disconnect if not responded within 2.5*keepalive
|
||||||
if (now - this->last_traffic_ > (KEEPALIVE_TIMEOUT_MS * 5) / 2) {
|
if (now - this->last_traffic_ > KEEPALIVE_DISCONNECT_TIMEOUT) {
|
||||||
on_fatal_error();
|
on_fatal_error();
|
||||||
ESP_LOGW(TAG, "%s is unresponsive; disconnecting", this->get_client_combined_info().c_str());
|
ESP_LOGW(TAG, "%s is unresponsive; disconnecting", this->get_client_combined_info().c_str());
|
||||||
}
|
}
|
||||||
} else if (now - this->last_traffic_ > KEEPALIVE_TIMEOUT_MS && now > this->next_ping_retry_) {
|
} else if (now - this->last_traffic_ > KEEPALIVE_TIMEOUT_MS) {
|
||||||
ESP_LOGVV(TAG, "Sending keepalive PING");
|
ESP_LOGVV(TAG, "Sending keepalive PING");
|
||||||
this->sent_ping_ = this->send_message(PingRequest());
|
this->sent_ping_ = this->send_message(PingRequest());
|
||||||
if (!this->sent_ping_) {
|
if (!this->sent_ping_) {
|
||||||
this->next_ping_retry_ = now + ping_retry_interval;
|
// If we can't send the ping request directly (tx_buffer full),
|
||||||
this->ping_retries_++;
|
// schedule it at the front of the batch so it will be sent with priority
|
||||||
std::string warn_str = str_sprintf("%s: Sending keepalive failed %u time(s);",
|
ESP_LOGVV(TAG, "Failed to send ping directly, scheduling at front of batch");
|
||||||
this->get_client_combined_info().c_str(), this->ping_retries_);
|
this->schedule_message_front_(nullptr, &APIConnection::try_send_ping_request, PingRequest::MESSAGE_TYPE);
|
||||||
if (this->ping_retries_ >= max_ping_retries) {
|
this->sent_ping_ = true; // Mark as sent to avoid scheduling multiple pings
|
||||||
on_fatal_error();
|
|
||||||
ESP_LOGE(TAG, "%s disconnecting", warn_str.c_str());
|
|
||||||
} else if (this->ping_retries_ >= 10) {
|
|
||||||
ESP_LOGW(TAG, "%s retrying in %u ms", warn_str.c_str(), ping_retry_interval);
|
|
||||||
} else {
|
|
||||||
ESP_LOGD(TAG, "%s retrying in %u ms", warn_str.c_str(), ping_retry_interval);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,22 +190,20 @@ void APIConnection::loop() {
|
|||||||
// bool done = 3;
|
// bool done = 3;
|
||||||
buffer.encode_bool(3, done);
|
buffer.encode_bool(3, done);
|
||||||
|
|
||||||
bool success = this->send_buffer(buffer, 44);
|
bool success = this->send_buffer(buffer, CameraImageResponse::MESSAGE_TYPE);
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
this->image_reader_.consume_data(to_send);
|
this->image_reader_.consume_data(to_send);
|
||||||
}
|
if (done) {
|
||||||
if (success && done) {
|
this->image_reader_.return_image();
|
||||||
this->image_reader_.return_image();
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (state_subs_at_ != -1) {
|
if (state_subs_at_ >= 0) {
|
||||||
const auto &subs = this->parent_->get_state_subs();
|
const auto &subs = this->parent_->get_state_subs();
|
||||||
if (state_subs_at_ >= (int) subs.size()) {
|
if (state_subs_at_ < static_cast<int>(subs.size())) {
|
||||||
state_subs_at_ = -1;
|
|
||||||
} else {
|
|
||||||
auto &it = subs[state_subs_at_];
|
auto &it = subs[state_subs_at_];
|
||||||
SubscribeHomeAssistantStateResponse resp;
|
SubscribeHomeAssistantStateResponse resp;
|
||||||
resp.entity_id = it.entity_id;
|
resp.entity_id = it.entity_id;
|
||||||
@@ -231,6 +212,8 @@ void APIConnection::loop() {
|
|||||||
if (this->send_message(resp)) {
|
if (this->send_message(resp)) {
|
||||||
state_subs_at_++;
|
state_subs_at_++;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
state_subs_at_ = -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1440,7 +1423,7 @@ void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRe
|
|||||||
|
|
||||||
#ifdef USE_EVENT
|
#ifdef USE_EVENT
|
||||||
void APIConnection::send_event(event::Event *event, const std::string &event_type) {
|
void APIConnection::send_event(event::Event *event, const std::string &event_type) {
|
||||||
this->schedule_message_(event, MessageCreator(event_type, EventResponse::MESSAGE_TYPE), EventResponse::MESSAGE_TYPE);
|
this->schedule_message_(event, MessageCreator(event_type), EventResponse::MESSAGE_TYPE);
|
||||||
}
|
}
|
||||||
void APIConnection::send_event_info(event::Event *event) {
|
void APIConnection::send_event_info(event::Event *event) {
|
||||||
this->schedule_message_(event, &APIConnection::try_send_event_info, ListEntitiesEventResponse::MESSAGE_TYPE);
|
this->schedule_message_(event, &APIConnection::try_send_event_info, ListEntitiesEventResponse::MESSAGE_TYPE);
|
||||||
@@ -1760,6 +1743,11 @@ void APIConnection::DeferredBatch::add_item(EntityBase *entity, MessageCreator c
|
|||||||
items.emplace_back(entity, std::move(creator), message_type);
|
items.emplace_back(entity, std::move(creator), message_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void APIConnection::DeferredBatch::add_item_front(EntityBase *entity, MessageCreator creator, uint16_t message_type) {
|
||||||
|
// Insert at front for high priority messages (no deduplication check)
|
||||||
|
items.insert(items.begin(), BatchItem(entity, std::move(creator), message_type));
|
||||||
|
}
|
||||||
|
|
||||||
bool APIConnection::schedule_batch_() {
|
bool APIConnection::schedule_batch_() {
|
||||||
if (!this->deferred_batch_.batch_scheduled) {
|
if (!this->deferred_batch_.batch_scheduled) {
|
||||||
this->deferred_batch_.batch_scheduled = true;
|
this->deferred_batch_.batch_scheduled = true;
|
||||||
@@ -1795,7 +1783,8 @@ void APIConnection::process_batch_() {
|
|||||||
const auto &item = this->deferred_batch_.items[0];
|
const auto &item = this->deferred_batch_.items[0];
|
||||||
|
|
||||||
// Let the creator calculate size and encode if it fits
|
// Let the creator calculate size and encode if it fits
|
||||||
uint16_t payload_size = item.creator(item.entity, this, std::numeric_limits<uint16_t>::max(), true);
|
uint16_t payload_size =
|
||||||
|
item.creator(item.entity, this, std::numeric_limits<uint16_t>::max(), true, item.message_type);
|
||||||
|
|
||||||
if (payload_size > 0 &&
|
if (payload_size > 0 &&
|
||||||
this->send_buffer(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, item.message_type)) {
|
this->send_buffer(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, item.message_type)) {
|
||||||
@@ -1845,7 +1834,7 @@ void APIConnection::process_batch_() {
|
|||||||
for (const auto &item : this->deferred_batch_.items) {
|
for (const auto &item : this->deferred_batch_.items) {
|
||||||
// Try to encode message
|
// Try to encode message
|
||||||
// The creator will calculate overhead to determine if the message fits
|
// The creator will calculate overhead to determine if the message fits
|
||||||
uint16_t payload_size = item.creator(item.entity, this, remaining_size, false);
|
uint16_t payload_size = item.creator(item.entity, this, remaining_size, false, item.message_type);
|
||||||
|
|
||||||
if (payload_size == 0) {
|
if (payload_size == 0) {
|
||||||
// Message won't fit, stop processing
|
// Message won't fit, stop processing
|
||||||
@@ -1908,21 +1897,23 @@ void APIConnection::process_batch_() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint16_t APIConnection::MessageCreator::operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
uint16_t APIConnection::MessageCreator::operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||||
bool is_single) const {
|
bool is_single, uint16_t message_type) const {
|
||||||
switch (message_type_) {
|
if (has_tagged_string_ptr_()) {
|
||||||
case 0: // Function pointer
|
// Handle string-based messages
|
||||||
return data_.ptr(entity, conn, remaining_size, is_single);
|
switch (message_type) {
|
||||||
|
|
||||||
#ifdef USE_EVENT
|
#ifdef USE_EVENT
|
||||||
case EventResponse::MESSAGE_TYPE: {
|
case EventResponse::MESSAGE_TYPE: {
|
||||||
auto *e = static_cast<event::Event *>(entity);
|
auto *e = static_cast<event::Event *>(entity);
|
||||||
return APIConnection::try_send_event_response(e, *data_.string_ptr, conn, remaining_size, is_single);
|
return APIConnection::try_send_event_response(e, *get_string_ptr_(), conn, remaining_size, is_single);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
default:
|
||||||
default:
|
// Should not happen, return 0 to indicate no message
|
||||||
// Should not happen, return 0 to indicate no message
|
return 0;
|
||||||
return 0;
|
}
|
||||||
|
} else {
|
||||||
|
// Function pointer case
|
||||||
|
return data_.ptr(entity, conn, remaining_size, is_single);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1938,6 +1929,12 @@ uint16_t APIConnection::try_send_disconnect_request(EntityBase *entity, APIConne
|
|||||||
return encode_message_to_buffer(req, DisconnectRequest::MESSAGE_TYPE, conn, remaining_size, is_single);
|
return encode_message_to_buffer(req, DisconnectRequest::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint16_t APIConnection::try_send_ping_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||||
|
bool is_single) {
|
||||||
|
PingRequest req;
|
||||||
|
return encode_message_to_buffer(req, PingRequest::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||||
|
}
|
||||||
|
|
||||||
uint16_t APIConnection::get_estimated_message_size(uint16_t message_type) {
|
uint16_t APIConnection::get_estimated_message_size(uint16_t message_type) {
|
||||||
// Use generated ESTIMATED_SIZE constants from each message type
|
// Use generated ESTIMATED_SIZE constants from each message type
|
||||||
switch (message_type) {
|
switch (message_type) {
|
||||||
|
|||||||
@@ -185,7 +185,6 @@ class APIConnection : public APIServerConnection {
|
|||||||
void on_disconnect_response(const DisconnectResponse &value) override;
|
void on_disconnect_response(const DisconnectResponse &value) override;
|
||||||
void on_ping_response(const PingResponse &value) override {
|
void on_ping_response(const PingResponse &value) override {
|
||||||
// we initiated ping
|
// we initiated ping
|
||||||
this->ping_retries_ = 0;
|
|
||||||
this->sent_ping_ = false;
|
this->sent_ping_ = false;
|
||||||
}
|
}
|
||||||
void on_home_assistant_state_response(const HomeAssistantStateResponse &msg) override;
|
void on_home_assistant_state_response(const HomeAssistantStateResponse &msg) override;
|
||||||
@@ -441,13 +440,16 @@ class APIConnection : public APIServerConnection {
|
|||||||
// Helper function to get estimated message size for buffer pre-allocation
|
// Helper function to get estimated message size for buffer pre-allocation
|
||||||
static uint16_t get_estimated_message_size(uint16_t message_type);
|
static uint16_t get_estimated_message_size(uint16_t message_type);
|
||||||
|
|
||||||
|
// Batch message method for ping requests
|
||||||
|
static uint16_t try_send_ping_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||||
|
bool is_single);
|
||||||
|
|
||||||
// Pointers first (4 bytes each, naturally aligned)
|
// Pointers first (4 bytes each, naturally aligned)
|
||||||
std::unique_ptr<APIFrameHelper> helper_;
|
std::unique_ptr<APIFrameHelper> helper_;
|
||||||
APIServer *parent_;
|
APIServer *parent_;
|
||||||
|
|
||||||
// 4-byte aligned types
|
// 4-byte aligned types
|
||||||
uint32_t last_traffic_;
|
uint32_t last_traffic_;
|
||||||
uint32_t next_ping_retry_{0};
|
|
||||||
int state_subs_at_ = -1;
|
int state_subs_at_ = -1;
|
||||||
|
|
||||||
// Strings (12 bytes each on 32-bit)
|
// Strings (12 bytes each on 32-bit)
|
||||||
@@ -470,8 +472,7 @@ class APIConnection : public APIServerConnection {
|
|||||||
bool sent_ping_{false};
|
bool sent_ping_{false};
|
||||||
bool service_call_subscription_{false};
|
bool service_call_subscription_{false};
|
||||||
bool next_close_ = false;
|
bool next_close_ = false;
|
||||||
uint8_t ping_retries_{0};
|
// 7 bytes used, 1 byte padding
|
||||||
// 8 bytes used, no padding needed
|
|
||||||
|
|
||||||
// Larger objects at the end
|
// Larger objects at the end
|
||||||
InitialStateIterator initial_state_iterator_;
|
InitialStateIterator initial_state_iterator_;
|
||||||
@@ -483,55 +484,57 @@ class APIConnection : public APIServerConnection {
|
|||||||
// Function pointer type for message encoding
|
// Function pointer type for message encoding
|
||||||
using MessageCreatorPtr = uint16_t (*)(EntityBase *, APIConnection *, uint32_t remaining_size, bool is_single);
|
using MessageCreatorPtr = uint16_t (*)(EntityBase *, APIConnection *, uint32_t remaining_size, bool is_single);
|
||||||
|
|
||||||
// Optimized MessageCreator class using union dispatch
|
// Optimized MessageCreator class using tagged pointer
|
||||||
class MessageCreator {
|
class MessageCreator {
|
||||||
|
// Ensure pointer alignment allows LSB tagging
|
||||||
|
static_assert(alignof(std::string *) > 1, "String pointer alignment must be > 1 for LSB tagging");
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// Constructor for function pointer (message_type = 0)
|
// Constructor for function pointer
|
||||||
MessageCreator(MessageCreatorPtr ptr) : message_type_(0) { data_.ptr = ptr; }
|
MessageCreator(MessageCreatorPtr ptr) {
|
||||||
|
// Function pointers are always aligned, so LSB is 0
|
||||||
|
data_.ptr = ptr;
|
||||||
|
}
|
||||||
|
|
||||||
// Constructor for string state capture
|
// Constructor for string state capture
|
||||||
MessageCreator(const std::string &value, uint16_t msg_type) : message_type_(msg_type) {
|
explicit MessageCreator(const std::string &str_value) {
|
||||||
data_.string_ptr = new std::string(value);
|
// Allocate string and tag the pointer
|
||||||
|
auto *str = new std::string(str_value);
|
||||||
|
// Set LSB to 1 to indicate string pointer
|
||||||
|
data_.tagged = reinterpret_cast<uintptr_t>(str) | 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Destructor
|
// Destructor
|
||||||
~MessageCreator() {
|
~MessageCreator() {
|
||||||
// Clean up string data for string-based message types
|
if (has_tagged_string_ptr_()) {
|
||||||
if (uses_string_data_()) {
|
delete get_string_ptr_();
|
||||||
delete data_.string_ptr;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy constructor
|
// Copy constructor
|
||||||
MessageCreator(const MessageCreator &other) : message_type_(other.message_type_) {
|
MessageCreator(const MessageCreator &other) {
|
||||||
if (message_type_ == 0) {
|
if (other.has_tagged_string_ptr_()) {
|
||||||
data_.ptr = other.data_.ptr;
|
auto *str = new std::string(*other.get_string_ptr_());
|
||||||
} else if (uses_string_data_()) {
|
data_.tagged = reinterpret_cast<uintptr_t>(str) | 1;
|
||||||
data_.string_ptr = new std::string(*other.data_.string_ptr);
|
|
||||||
} else {
|
} else {
|
||||||
data_ = other.data_; // For POD types
|
data_ = other.data_;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move constructor
|
// Move constructor
|
||||||
MessageCreator(MessageCreator &&other) noexcept : data_(other.data_), message_type_(other.message_type_) {
|
MessageCreator(MessageCreator &&other) noexcept : data_(other.data_) { other.data_.ptr = nullptr; }
|
||||||
other.message_type_ = 0; // Reset other to function pointer type
|
|
||||||
other.data_.ptr = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assignment operators (needed for batch deduplication)
|
// Assignment operators (needed for batch deduplication)
|
||||||
MessageCreator &operator=(const MessageCreator &other) {
|
MessageCreator &operator=(const MessageCreator &other) {
|
||||||
if (this != &other) {
|
if (this != &other) {
|
||||||
// Clean up current string data if needed
|
// Clean up current string data if needed
|
||||||
if (uses_string_data_()) {
|
if (has_tagged_string_ptr_()) {
|
||||||
delete data_.string_ptr;
|
delete get_string_ptr_();
|
||||||
}
|
}
|
||||||
// Copy new data
|
// Copy new data
|
||||||
message_type_ = other.message_type_;
|
if (other.has_tagged_string_ptr_()) {
|
||||||
if (other.message_type_ == 0) {
|
auto *str = new std::string(*other.get_string_ptr_());
|
||||||
data_.ptr = other.data_.ptr;
|
data_.tagged = reinterpret_cast<uintptr_t>(str) | 1;
|
||||||
} else if (other.uses_string_data_()) {
|
|
||||||
data_.string_ptr = new std::string(*other.data_.string_ptr);
|
|
||||||
} else {
|
} else {
|
||||||
data_ = other.data_;
|
data_ = other.data_;
|
||||||
}
|
}
|
||||||
@@ -542,30 +545,35 @@ class APIConnection : public APIServerConnection {
|
|||||||
MessageCreator &operator=(MessageCreator &&other) noexcept {
|
MessageCreator &operator=(MessageCreator &&other) noexcept {
|
||||||
if (this != &other) {
|
if (this != &other) {
|
||||||
// Clean up current string data if needed
|
// Clean up current string data if needed
|
||||||
if (uses_string_data_()) {
|
if (has_tagged_string_ptr_()) {
|
||||||
delete data_.string_ptr;
|
delete get_string_ptr_();
|
||||||
}
|
}
|
||||||
// Move data
|
// Move data
|
||||||
message_type_ = other.message_type_;
|
|
||||||
data_ = other.data_;
|
data_ = other.data_;
|
||||||
// Reset other to safe state
|
// Reset other to safe state
|
||||||
other.message_type_ = 0;
|
|
||||||
other.data_.ptr = nullptr;
|
other.data_.ptr = nullptr;
|
||||||
}
|
}
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call operator
|
// Call operator - now accepts message_type as parameter
|
||||||
uint16_t operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single) const;
|
uint16_t operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single,
|
||||||
|
uint16_t message_type) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Helper to check if this message type uses heap-allocated strings
|
// Check if this contains a string pointer
|
||||||
bool uses_string_data_() const { return message_type_ == EventResponse::MESSAGE_TYPE; }
|
bool has_tagged_string_ptr_() const { return (data_.tagged & 1) != 0; }
|
||||||
union CreatorData {
|
|
||||||
MessageCreatorPtr ptr; // 8 bytes
|
// Get the actual string pointer (clears the tag bit)
|
||||||
std::string *string_ptr; // 8 bytes
|
std::string *get_string_ptr_() const {
|
||||||
} data_; // 8 bytes
|
// NOLINTNEXTLINE(performance-no-int-to-ptr)
|
||||||
uint16_t message_type_; // 2 bytes (0 = function ptr, >0 = state capture)
|
return reinterpret_cast<std::string *>(data_.tagged & ~uintptr_t(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
union {
|
||||||
|
MessageCreatorPtr ptr;
|
||||||
|
uintptr_t tagged;
|
||||||
|
} data_; // 4 bytes on 32-bit
|
||||||
};
|
};
|
||||||
|
|
||||||
// Generic batching mechanism for both state updates and entity info
|
// Generic batching mechanism for both state updates and entity info
|
||||||
@@ -591,6 +599,8 @@ class APIConnection : public APIServerConnection {
|
|||||||
|
|
||||||
// Add item to the batch
|
// Add item to the batch
|
||||||
void add_item(EntityBase *entity, MessageCreator creator, uint16_t message_type);
|
void add_item(EntityBase *entity, MessageCreator creator, uint16_t message_type);
|
||||||
|
// Add item to the front of the batch (for high priority messages like ping)
|
||||||
|
void add_item_front(EntityBase *entity, MessageCreator creator, uint16_t message_type);
|
||||||
void clear() {
|
void clear() {
|
||||||
items.clear();
|
items.clear();
|
||||||
batch_scheduled = false;
|
batch_scheduled = false;
|
||||||
@@ -630,6 +640,12 @@ class APIConnection : public APIServerConnection {
|
|||||||
bool schedule_message_(EntityBase *entity, MessageCreatorPtr function_ptr, uint16_t message_type) {
|
bool schedule_message_(EntityBase *entity, MessageCreatorPtr function_ptr, uint16_t message_type) {
|
||||||
return schedule_message_(entity, MessageCreator(function_ptr), message_type);
|
return schedule_message_(entity, MessageCreator(function_ptr), message_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper function to schedule a high priority message at the front of the batch
|
||||||
|
bool schedule_message_front_(EntityBase *entity, MessageCreatorPtr function_ptr, uint16_t message_type) {
|
||||||
|
this->deferred_batch_.add_item_front(entity, MessageCreator(function_ptr), message_type);
|
||||||
|
return this->schedule_batch_();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace api
|
} // namespace api
|
||||||
|
|||||||
@@ -66,6 +66,17 @@ const char *api_error_to_str(APIError err) {
|
|||||||
return "UNKNOWN";
|
return "UNKNOWN";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Default implementation for loop - handles sending buffered data
|
||||||
|
APIError APIFrameHelper::loop() {
|
||||||
|
if (!this->tx_buf_.empty()) {
|
||||||
|
APIError err = try_send_tx_buf_();
|
||||||
|
if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return APIError::OK; // Convert WOULD_BLOCK to OK to avoid connection termination
|
||||||
|
}
|
||||||
|
|
||||||
// Helper method to buffer data from IOVs
|
// Helper method to buffer data from IOVs
|
||||||
void APIFrameHelper::buffer_data_from_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len) {
|
void APIFrameHelper::buffer_data_from_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len) {
|
||||||
SendBuffer buffer;
|
SendBuffer buffer;
|
||||||
@@ -287,13 +298,8 @@ APIError APINoiseFrameHelper::loop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this->tx_buf_.empty()) {
|
// Use base class implementation for buffer sending
|
||||||
APIError err = try_send_tx_buf_();
|
return APIFrameHelper::loop();
|
||||||
if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return APIError::OK; // Convert WOULD_BLOCK to OK to avoid connection termination
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter
|
/** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter
|
||||||
@@ -339,17 +345,15 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
|
|||||||
return APIError::WOULD_BLOCK;
|
return APIError::WOULD_BLOCK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (rx_header_buf_[0] != 0x01) {
|
||||||
|
state_ = State::FAILED;
|
||||||
|
HELPER_LOG("Bad indicator byte %u", rx_header_buf_[0]);
|
||||||
|
return APIError::BAD_INDICATOR;
|
||||||
|
}
|
||||||
// header reading done
|
// header reading done
|
||||||
}
|
}
|
||||||
|
|
||||||
// read body
|
// read body
|
||||||
uint8_t indicator = rx_header_buf_[0];
|
|
||||||
if (indicator != 0x01) {
|
|
||||||
state_ = State::FAILED;
|
|
||||||
HELPER_LOG("Bad indicator byte %u", indicator);
|
|
||||||
return APIError::BAD_INDICATOR;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t msg_size = (((uint16_t) rx_header_buf_[1]) << 8) | rx_header_buf_[2];
|
uint16_t msg_size = (((uint16_t) rx_header_buf_[1]) << 8) | rx_header_buf_[2];
|
||||||
|
|
||||||
if (state_ != State::DATA && msg_size > 128) {
|
if (state_ != State::DATA && msg_size > 128) {
|
||||||
@@ -595,10 +599,6 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
|||||||
return APIError::BAD_DATA_PACKET;
|
return APIError::BAD_DATA_PACKET;
|
||||||
}
|
}
|
||||||
|
|
||||||
// uint16_t type;
|
|
||||||
// uint16_t data_len;
|
|
||||||
// uint8_t *data;
|
|
||||||
// uint8_t *padding; zero or more bytes to fill up the rest of the packet
|
|
||||||
uint16_t type = (((uint16_t) msg_data[0]) << 8) | msg_data[1];
|
uint16_t type = (((uint16_t) msg_data[0]) << 8) | msg_data[1];
|
||||||
uint16_t data_len = (((uint16_t) msg_data[2]) << 8) | msg_data[3];
|
uint16_t data_len = (((uint16_t) msg_data[2]) << 8) | msg_data[3];
|
||||||
if (data_len > msg_size - 4) {
|
if (data_len > msg_size - 4) {
|
||||||
@@ -831,18 +831,12 @@ APIError APIPlaintextFrameHelper::init() {
|
|||||||
state_ = State::DATA;
|
state_ = State::DATA;
|
||||||
return APIError::OK;
|
return APIError::OK;
|
||||||
}
|
}
|
||||||
/// Not used for plaintext
|
|
||||||
APIError APIPlaintextFrameHelper::loop() {
|
APIError APIPlaintextFrameHelper::loop() {
|
||||||
if (state_ != State::DATA) {
|
if (state_ != State::DATA) {
|
||||||
return APIError::BAD_STATE;
|
return APIError::BAD_STATE;
|
||||||
}
|
}
|
||||||
if (!this->tx_buf_.empty()) {
|
// Use base class implementation for buffer sending
|
||||||
APIError err = try_send_tx_buf_();
|
return APIFrameHelper::loop();
|
||||||
if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return APIError::OK; // Convert WOULD_BLOCK to OK to avoid connection termination
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter
|
/** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ struct PacketInfo {
|
|||||||
: message_type(type), offset(off), payload_size(size), padding(0) {}
|
: message_type(type), offset(off), payload_size(size), padding(0) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class APIError : int {
|
enum class APIError : uint16_t {
|
||||||
OK = 0,
|
OK = 0,
|
||||||
WOULD_BLOCK = 1001,
|
WOULD_BLOCK = 1001,
|
||||||
BAD_HANDSHAKE_PACKET_LEN = 1002,
|
BAD_HANDSHAKE_PACKET_LEN = 1002,
|
||||||
@@ -74,7 +74,7 @@ class APIFrameHelper {
|
|||||||
}
|
}
|
||||||
virtual ~APIFrameHelper() = default;
|
virtual ~APIFrameHelper() = default;
|
||||||
virtual APIError init() = 0;
|
virtual APIError init() = 0;
|
||||||
virtual APIError loop() = 0;
|
virtual APIError loop();
|
||||||
virtual APIError read_packet(ReadPacketBuffer *buffer) = 0;
|
virtual APIError read_packet(ReadPacketBuffer *buffer) = 0;
|
||||||
bool can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); }
|
bool can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); }
|
||||||
std::string getpeername() { return socket_->getpeername(); }
|
std::string getpeername() { return socket_->getpeername(); }
|
||||||
|
|||||||
@@ -47,6 +47,11 @@ void APIServer::setup() {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Schedule reboot if no clients connect within timeout
|
||||||
|
if (this->reboot_timeout_ != 0) {
|
||||||
|
this->schedule_reboot_timeout_();
|
||||||
|
}
|
||||||
|
|
||||||
this->socket_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0); // monitored for incoming connections
|
this->socket_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0); // monitored for incoming connections
|
||||||
if (this->socket_ == nullptr) {
|
if (this->socket_ == nullptr) {
|
||||||
ESP_LOGW(TAG, "Could not create socket");
|
ESP_LOGW(TAG, "Could not create socket");
|
||||||
@@ -106,8 +111,6 @@ void APIServer::setup() {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
this->last_connected_ = App.get_loop_component_start_time();
|
|
||||||
|
|
||||||
#ifdef USE_ESP32_CAMERA
|
#ifdef USE_ESP32_CAMERA
|
||||||
if (esp32_camera::global_esp32_camera != nullptr && !esp32_camera::global_esp32_camera->is_internal()) {
|
if (esp32_camera::global_esp32_camera != nullptr && !esp32_camera::global_esp32_camera->is_internal()) {
|
||||||
esp32_camera::global_esp32_camera->add_image_callback(
|
esp32_camera::global_esp32_camera->add_image_callback(
|
||||||
@@ -121,6 +124,16 @@ void APIServer::setup() {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void APIServer::schedule_reboot_timeout_() {
|
||||||
|
this->status_set_warning();
|
||||||
|
this->set_timeout("api_reboot", this->reboot_timeout_, []() {
|
||||||
|
if (!global_api_server->is_connected()) {
|
||||||
|
ESP_LOGE(TAG, "No clients; rebooting");
|
||||||
|
App.reboot();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void APIServer::loop() {
|
void APIServer::loop() {
|
||||||
// Accept new clients only if the socket exists and has incoming connections
|
// Accept new clients only if the socket exists and has incoming connections
|
||||||
if (this->socket_ && this->socket_->ready()) {
|
if (this->socket_ && this->socket_->ready()) {
|
||||||
@@ -130,51 +143,61 @@ void APIServer::loop() {
|
|||||||
auto sock = this->socket_->accept_loop_monitored((struct sockaddr *) &source_addr, &addr_len);
|
auto sock = this->socket_->accept_loop_monitored((struct sockaddr *) &source_addr, &addr_len);
|
||||||
if (!sock)
|
if (!sock)
|
||||||
break;
|
break;
|
||||||
ESP_LOGD(TAG, "Accepted %s", sock->getpeername().c_str());
|
ESP_LOGD(TAG, "Accept %s", sock->getpeername().c_str());
|
||||||
|
|
||||||
auto *conn = new APIConnection(std::move(sock), this);
|
auto *conn = new APIConnection(std::move(sock), this);
|
||||||
this->clients_.emplace_back(conn);
|
this->clients_.emplace_back(conn);
|
||||||
conn->start();
|
conn->start();
|
||||||
|
|
||||||
|
// Clear warning status and cancel reboot when first client connects
|
||||||
|
if (this->clients_.size() == 1 && this->reboot_timeout_ != 0) {
|
||||||
|
this->status_clear_warning();
|
||||||
|
this->cancel_timeout("api_reboot");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this->clients_.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Process clients and remove disconnected ones in a single pass
|
// Process clients and remove disconnected ones in a single pass
|
||||||
if (!this->clients_.empty()) {
|
// Check network connectivity once for all clients
|
||||||
size_t client_index = 0;
|
if (!network::is_connected()) {
|
||||||
while (client_index < this->clients_.size()) {
|
// Network is down - disconnect all clients
|
||||||
auto &client = this->clients_[client_index];
|
for (auto &client : this->clients_) {
|
||||||
|
client->on_fatal_error();
|
||||||
if (client->remove_) {
|
ESP_LOGW(TAG, "%s: Network down; disconnect", client->get_client_combined_info().c_str());
|
||||||
// Handle disconnection
|
|
||||||
this->client_disconnected_trigger_->trigger(client->client_info_, client->client_peername_);
|
|
||||||
ESP_LOGV(TAG, "Removing connection to %s", client->client_info_.c_str());
|
|
||||||
|
|
||||||
// Swap with the last element and pop (avoids expensive vector shifts)
|
|
||||||
if (client_index < this->clients_.size() - 1) {
|
|
||||||
std::swap(this->clients_[client_index], this->clients_.back());
|
|
||||||
}
|
|
||||||
this->clients_.pop_back();
|
|
||||||
// Don't increment client_index since we need to process the swapped element
|
|
||||||
} else {
|
|
||||||
// Process active client
|
|
||||||
client->loop();
|
|
||||||
client_index++; // Move to next client
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// Continue to process and clean up the clients below
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->reboot_timeout_ != 0) {
|
size_t client_index = 0;
|
||||||
const uint32_t now = App.get_loop_component_start_time();
|
while (client_index < this->clients_.size()) {
|
||||||
if (!this->is_connected()) {
|
auto &client = this->clients_[client_index];
|
||||||
if (now - this->last_connected_ > this->reboot_timeout_) {
|
|
||||||
ESP_LOGE(TAG, "No client connected; rebooting");
|
if (!client->remove_) {
|
||||||
App.reboot();
|
// Common case: process active client
|
||||||
}
|
client->loop();
|
||||||
this->status_set_warning();
|
client_index++;
|
||||||
} else {
|
continue;
|
||||||
this->last_connected_ = now;
|
|
||||||
this->status_clear_warning();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rare case: handle disconnection
|
||||||
|
this->client_disconnected_trigger_->trigger(client->client_info_, client->client_peername_);
|
||||||
|
ESP_LOGV(TAG, "Remove connection %s", client->client_info_.c_str());
|
||||||
|
|
||||||
|
// Swap with the last element and pop (avoids expensive vector shifts)
|
||||||
|
if (client_index < this->clients_.size() - 1) {
|
||||||
|
std::swap(this->clients_[client_index], this->clients_.back());
|
||||||
|
}
|
||||||
|
this->clients_.pop_back();
|
||||||
|
|
||||||
|
// Schedule reboot when last client disconnects
|
||||||
|
if (this->clients_.empty() && this->reboot_timeout_ != 0) {
|
||||||
|
this->schedule_reboot_timeout_();
|
||||||
|
}
|
||||||
|
// Don't increment client_index since we need to process the swapped element
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -503,8 +526,8 @@ void APIServer::on_shutdown() {
|
|||||||
for (auto &c : this->clients_) {
|
for (auto &c : this->clients_) {
|
||||||
if (!c->send_message(DisconnectRequest())) {
|
if (!c->send_message(DisconnectRequest())) {
|
||||||
// If we can't send the disconnect request directly (tx_buffer full),
|
// If we can't send the disconnect request directly (tx_buffer full),
|
||||||
// schedule it in the batch so it will be sent with the 5ms timer
|
// schedule it at the front of the batch so it will be sent with priority
|
||||||
c->schedule_message_(nullptr, &APIConnection::try_send_disconnect_request, DisconnectRequest::MESSAGE_TYPE);
|
c->schedule_message_front_(nullptr, &APIConnection::try_send_disconnect_request, DisconnectRequest::MESSAGE_TYPE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -142,6 +142,7 @@ class APIServer : public Component, public Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
void schedule_reboot_timeout_();
|
||||||
// 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;
|
||||||
Trigger<std::string, std::string> *client_connected_trigger_ = new Trigger<std::string, std::string>();
|
Trigger<std::string, std::string> *client_connected_trigger_ = new Trigger<std::string, std::string>();
|
||||||
@@ -150,7 +151,6 @@ class APIServer : public Component, public Controller {
|
|||||||
// 4-byte aligned types
|
// 4-byte aligned types
|
||||||
uint32_t reboot_timeout_{300000};
|
uint32_t reboot_timeout_{300000};
|
||||||
uint32_t batch_delay_{100};
|
uint32_t batch_delay_{100};
|
||||||
uint32_t last_connected_{0};
|
|
||||||
|
|
||||||
// Vectors and strings (12 bytes each on 32-bit)
|
// Vectors and strings (12 bytes each on 32-bit)
|
||||||
std::vector<std::unique_ptr<APIConnection>> clients_;
|
std::vector<std::unique_ptr<APIConnection>> clients_;
|
||||||
|
|||||||
@@ -148,6 +148,7 @@ BinarySensorCondition = binary_sensor_ns.class_("BinarySensorCondition", Conditi
|
|||||||
|
|
||||||
# Filters
|
# Filters
|
||||||
Filter = binary_sensor_ns.class_("Filter")
|
Filter = binary_sensor_ns.class_("Filter")
|
||||||
|
TimeoutFilter = binary_sensor_ns.class_("TimeoutFilter", Filter, cg.Component)
|
||||||
DelayedOnOffFilter = binary_sensor_ns.class_("DelayedOnOffFilter", Filter, cg.Component)
|
DelayedOnOffFilter = binary_sensor_ns.class_("DelayedOnOffFilter", Filter, cg.Component)
|
||||||
DelayedOnFilter = binary_sensor_ns.class_("DelayedOnFilter", Filter, cg.Component)
|
DelayedOnFilter = binary_sensor_ns.class_("DelayedOnFilter", Filter, cg.Component)
|
||||||
DelayedOffFilter = binary_sensor_ns.class_("DelayedOffFilter", Filter, cg.Component)
|
DelayedOffFilter = binary_sensor_ns.class_("DelayedOffFilter", Filter, cg.Component)
|
||||||
@@ -171,6 +172,19 @@ async def invert_filter_to_code(config, filter_id):
|
|||||||
return cg.new_Pvariable(filter_id)
|
return cg.new_Pvariable(filter_id)
|
||||||
|
|
||||||
|
|
||||||
|
@register_filter(
|
||||||
|
"timeout",
|
||||||
|
TimeoutFilter,
|
||||||
|
cv.templatable(cv.positive_time_period_milliseconds),
|
||||||
|
)
|
||||||
|
async def timeout_filter_to_code(config, filter_id):
|
||||||
|
var = cg.new_Pvariable(filter_id)
|
||||||
|
await cg.register_component(var, {})
|
||||||
|
template_ = await cg.templatable(config, [], cg.uint32)
|
||||||
|
cg.add(var.set_timeout_value(template_))
|
||||||
|
return var
|
||||||
|
|
||||||
|
|
||||||
@register_filter(
|
@register_filter(
|
||||||
"delayed_on_off",
|
"delayed_on_off",
|
||||||
DelayedOnOffFilter,
|
DelayedOnOffFilter,
|
||||||
|
|||||||
@@ -25,6 +25,12 @@ void Filter::input(bool value) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TimeoutFilter::input(bool value) {
|
||||||
|
this->set_timeout("timeout", this->timeout_delay_.value(), [this]() { this->parent_->invalidate_state(); });
|
||||||
|
// we do not de-dup here otherwise changes from invalid to valid state will not be output
|
||||||
|
this->output(value);
|
||||||
|
}
|
||||||
|
|
||||||
optional<bool> DelayedOnOffFilter::new_value(bool value) {
|
optional<bool> DelayedOnOffFilter::new_value(bool value) {
|
||||||
if (value) {
|
if (value) {
|
||||||
this->set_timeout("ON_OFF", this->on_delay_.value(), [this]() { this->output(true); });
|
this->set_timeout("ON_OFF", this->on_delay_.value(), [this]() { this->output(true); });
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ class Filter {
|
|||||||
public:
|
public:
|
||||||
virtual optional<bool> new_value(bool value) = 0;
|
virtual optional<bool> new_value(bool value) = 0;
|
||||||
|
|
||||||
void input(bool value);
|
virtual void input(bool value);
|
||||||
|
|
||||||
void output(bool value);
|
void output(bool value);
|
||||||
|
|
||||||
@@ -28,6 +28,16 @@ class Filter {
|
|||||||
Deduplicator<bool> dedup_;
|
Deduplicator<bool> dedup_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class TimeoutFilter : public Filter, public Component {
|
||||||
|
public:
|
||||||
|
optional<bool> new_value(bool value) override { return value; }
|
||||||
|
void input(bool value) override;
|
||||||
|
template<typename T> void set_timeout_value(T timeout) { this->timeout_delay_ = timeout; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
TemplatableValue<uint32_t> timeout_delay_{};
|
||||||
|
};
|
||||||
|
|
||||||
class DelayedOnOffFilter : public Filter, public Component {
|
class DelayedOnOffFilter : public Filter, public Component {
|
||||||
public:
|
public:
|
||||||
optional<bool> new_value(bool value) override;
|
optional<bool> new_value(bool value) override;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include "ble_event_pool.h"
|
#include "ble_event_pool.h"
|
||||||
|
|
||||||
#include "esphome/core/application.h"
|
#include "esphome/core/application.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
#include <esp_bt.h>
|
#include <esp_bt.h>
|
||||||
@@ -516,13 +517,12 @@ void ESP32BLE::dump_config() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
ESP_LOGCONFIG(TAG,
|
ESP_LOGCONFIG(TAG,
|
||||||
"ESP32 BLE:\n"
|
"BLE:\n"
|
||||||
" MAC address: %02X:%02X:%02X:%02X:%02X:%02X\n"
|
" MAC address: %s\n"
|
||||||
" IO Capability: %s",
|
" IO Capability: %s",
|
||||||
mac_address[0], mac_address[1], mac_address[2], mac_address[3], mac_address[4], mac_address[5],
|
format_mac_address_pretty(mac_address).c_str(), io_capability_s);
|
||||||
io_capability_s);
|
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGCONFIG(TAG, "ESP32 BLE: bluetooth stack is not enabled");
|
ESP_LOGCONFIG(TAG, "Bluetooth stack is not enabled");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
0
esphome/components/esp32_hall/__init__.py
Normal file
0
esphome/components/esp32_hall/__init__.py
Normal file
5
esphome/components/esp32_hall/sensor.py
Normal file
5
esphome/components/esp32_hall/sensor.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import esphome.config_validation as cv
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.invalid(
|
||||||
|
"The esp32_hall component has been removed as of ESPHome 2025.7.0. See https://github.com/esphome/esphome/pull/9117 for details."
|
||||||
|
)
|
||||||
@@ -10,11 +10,24 @@ GPIOBinarySensor = gpio_ns.class_(
|
|||||||
"GPIOBinarySensor", binary_sensor.BinarySensor, cg.Component
|
"GPIOBinarySensor", binary_sensor.BinarySensor, cg.Component
|
||||||
)
|
)
|
||||||
|
|
||||||
|
CONF_USE_INTERRUPT = "use_interrupt"
|
||||||
|
CONF_INTERRUPT_TYPE = "interrupt_type"
|
||||||
|
|
||||||
|
INTERRUPT_TYPES = {
|
||||||
|
"RISING": gpio_ns.INTERRUPT_RISING_EDGE,
|
||||||
|
"FALLING": gpio_ns.INTERRUPT_FALLING_EDGE,
|
||||||
|
"ANY": gpio_ns.INTERRUPT_ANY_EDGE,
|
||||||
|
}
|
||||||
|
|
||||||
CONFIG_SCHEMA = (
|
CONFIG_SCHEMA = (
|
||||||
binary_sensor.binary_sensor_schema(GPIOBinarySensor)
|
binary_sensor.binary_sensor_schema(GPIOBinarySensor)
|
||||||
.extend(
|
.extend(
|
||||||
{
|
{
|
||||||
cv.Required(CONF_PIN): pins.gpio_input_pin_schema,
|
cv.Required(CONF_PIN): pins.gpio_input_pin_schema,
|
||||||
|
cv.Optional(CONF_USE_INTERRUPT, default=True): cv.boolean,
|
||||||
|
cv.Optional(CONF_INTERRUPT_TYPE, default="ANY"): cv.enum(
|
||||||
|
INTERRUPT_TYPES, upper=True
|
||||||
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.extend(cv.COMPONENT_SCHEMA)
|
.extend(cv.COMPONENT_SCHEMA)
|
||||||
@@ -27,3 +40,7 @@ async def to_code(config):
|
|||||||
|
|
||||||
pin = await cg.gpio_pin_expression(config[CONF_PIN])
|
pin = await cg.gpio_pin_expression(config[CONF_PIN])
|
||||||
cg.add(var.set_pin(pin))
|
cg.add(var.set_pin(pin))
|
||||||
|
|
||||||
|
cg.add(var.set_use_interrupt(config[CONF_USE_INTERRUPT]))
|
||||||
|
if config[CONF_USE_INTERRUPT]:
|
||||||
|
cg.add(var.set_interrupt_type(INTERRUPT_TYPES[config[CONF_INTERRUPT_TYPE]]))
|
||||||
|
|||||||
@@ -6,17 +6,91 @@ namespace gpio {
|
|||||||
|
|
||||||
static const char *const TAG = "gpio.binary_sensor";
|
static const char *const TAG = "gpio.binary_sensor";
|
||||||
|
|
||||||
|
void IRAM_ATTR GPIOBinarySensorStore::gpio_intr(GPIOBinarySensorStore *arg) {
|
||||||
|
bool new_state = arg->isr_pin_.digital_read();
|
||||||
|
if (new_state != arg->last_state_) {
|
||||||
|
arg->state_ = new_state;
|
||||||
|
arg->last_state_ = new_state;
|
||||||
|
arg->changed_ = true;
|
||||||
|
// Wake up the component from its disabled loop state
|
||||||
|
if (arg->component_ != nullptr) {
|
||||||
|
arg->component_->enable_loop_soon_any_context();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GPIOBinarySensorStore::setup(InternalGPIOPin *pin, gpio::InterruptType type, Component *component) {
|
||||||
|
pin->setup();
|
||||||
|
this->isr_pin_ = pin->to_isr();
|
||||||
|
this->component_ = component;
|
||||||
|
|
||||||
|
// Read initial state
|
||||||
|
this->last_state_ = pin->digital_read();
|
||||||
|
this->state_ = this->last_state_;
|
||||||
|
|
||||||
|
// Attach interrupt - from this point on, any changes will be caught by the interrupt
|
||||||
|
pin->attach_interrupt(&GPIOBinarySensorStore::gpio_intr, this, type);
|
||||||
|
}
|
||||||
|
|
||||||
void GPIOBinarySensor::setup() {
|
void GPIOBinarySensor::setup() {
|
||||||
this->pin_->setup();
|
if (this->use_interrupt_ && !this->pin_->is_internal()) {
|
||||||
this->publish_initial_state(this->pin_->digital_read());
|
ESP_LOGD(TAG, "GPIO is not internal, falling back to polling mode");
|
||||||
|
this->use_interrupt_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->use_interrupt_) {
|
||||||
|
auto *internal_pin = static_cast<InternalGPIOPin *>(this->pin_);
|
||||||
|
this->store_.setup(internal_pin, this->interrupt_type_, this);
|
||||||
|
this->publish_initial_state(this->store_.get_state());
|
||||||
|
} else {
|
||||||
|
this->pin_->setup();
|
||||||
|
this->publish_initial_state(this->pin_->digital_read());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GPIOBinarySensor::dump_config() {
|
void GPIOBinarySensor::dump_config() {
|
||||||
LOG_BINARY_SENSOR("", "GPIO Binary Sensor", this);
|
LOG_BINARY_SENSOR("", "GPIO Binary Sensor", this);
|
||||||
LOG_PIN(" Pin: ", this->pin_);
|
LOG_PIN(" Pin: ", this->pin_);
|
||||||
|
const char *mode = this->use_interrupt_ ? "interrupt" : "polling";
|
||||||
|
ESP_LOGCONFIG(TAG, " Mode: %s", mode);
|
||||||
|
if (this->use_interrupt_) {
|
||||||
|
const char *interrupt_type;
|
||||||
|
switch (this->interrupt_type_) {
|
||||||
|
case gpio::INTERRUPT_RISING_EDGE:
|
||||||
|
interrupt_type = "RISING_EDGE";
|
||||||
|
break;
|
||||||
|
case gpio::INTERRUPT_FALLING_EDGE:
|
||||||
|
interrupt_type = "FALLING_EDGE";
|
||||||
|
break;
|
||||||
|
case gpio::INTERRUPT_ANY_EDGE:
|
||||||
|
interrupt_type = "ANY_EDGE";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
interrupt_type = "UNKNOWN";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ESP_LOGCONFIG(TAG, " Interrupt Type: %s", interrupt_type);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GPIOBinarySensor::loop() { this->publish_state(this->pin_->digital_read()); }
|
void GPIOBinarySensor::loop() {
|
||||||
|
if (this->use_interrupt_) {
|
||||||
|
if (this->store_.is_changed()) {
|
||||||
|
// Clear the flag immediately to minimize the window where we might miss changes
|
||||||
|
this->store_.clear_changed();
|
||||||
|
// Read the state and publish it
|
||||||
|
// Note: If the ISR fires between clear_changed() and get_state(), that's fine -
|
||||||
|
// we'll process the new change on the next loop iteration
|
||||||
|
bool state = this->store_.get_state();
|
||||||
|
this->publish_state(state);
|
||||||
|
} else {
|
||||||
|
// No changes, disable the loop until the next interrupt
|
||||||
|
this->disable_loop();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this->publish_state(this->pin_->digital_read());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
float GPIOBinarySensor::get_setup_priority() const { return setup_priority::HARDWARE; }
|
float GPIOBinarySensor::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||||
|
|
||||||
|
|||||||
@@ -2,14 +2,51 @@
|
|||||||
|
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace gpio {
|
namespace gpio {
|
||||||
|
|
||||||
|
// Store class for ISR data (no vtables, ISR-safe)
|
||||||
|
class GPIOBinarySensorStore {
|
||||||
|
public:
|
||||||
|
void setup(InternalGPIOPin *pin, gpio::InterruptType type, Component *component);
|
||||||
|
|
||||||
|
static void gpio_intr(GPIOBinarySensorStore *arg);
|
||||||
|
|
||||||
|
bool get_state() const {
|
||||||
|
// No lock needed: state_ is atomically updated by ISR
|
||||||
|
// Volatile ensures we read the latest value
|
||||||
|
return this->state_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_changed() const {
|
||||||
|
// Simple read of volatile bool - no clearing here
|
||||||
|
return this->changed_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear_changed() {
|
||||||
|
// Separate method to clear the flag
|
||||||
|
this->changed_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
ISRInternalGPIOPin isr_pin_;
|
||||||
|
volatile bool state_{false};
|
||||||
|
volatile bool last_state_{false};
|
||||||
|
volatile bool changed_{false};
|
||||||
|
Component *component_{nullptr}; // Pointer to the component for enable_loop_soon_any_context()
|
||||||
|
};
|
||||||
|
|
||||||
class GPIOBinarySensor : public binary_sensor::BinarySensor, public Component {
|
class GPIOBinarySensor : public binary_sensor::BinarySensor, public Component {
|
||||||
public:
|
public:
|
||||||
|
// No destructor needed: ESPHome components are created at boot and live forever.
|
||||||
|
// Interrupts are only detached on reboot when memory is cleared anyway.
|
||||||
|
|
||||||
void set_pin(GPIOPin *pin) { pin_ = pin; }
|
void set_pin(GPIOPin *pin) { pin_ = pin; }
|
||||||
|
void set_use_interrupt(bool use_interrupt) { use_interrupt_ = use_interrupt; }
|
||||||
|
void set_interrupt_type(gpio::InterruptType type) { interrupt_type_ = type; }
|
||||||
// ========== INTERNAL METHODS ==========
|
// ========== INTERNAL METHODS ==========
|
||||||
// (In most use cases you won't need these)
|
// (In most use cases you won't need these)
|
||||||
/// Setup pin
|
/// Setup pin
|
||||||
@@ -22,6 +59,9 @@ class GPIOBinarySensor : public binary_sensor::BinarySensor, public Component {
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
GPIOPin *pin_;
|
GPIOPin *pin_;
|
||||||
|
bool use_interrupt_{true};
|
||||||
|
gpio::InterruptType interrupt_type_{gpio::INTERRUPT_ANY_EDGE};
|
||||||
|
GPIOBinarySensorStore store_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace gpio
|
} // namespace gpio
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
#include "esphome/components/sensor/sensor.h"
|
#include "esphome/components/sensor/sensor.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "esphome/core/application.h"
|
||||||
|
|
||||||
#define highbyte(val) (uint8_t)((val) >> 8)
|
#define highbyte(val) (uint8_t)((val) >> 8)
|
||||||
#define lowbyte(val) (uint8_t)((val) &0xff)
|
#define lowbyte(val) (uint8_t)((val) &0xff)
|
||||||
|
|
||||||
@@ -73,9 +75,9 @@ void LD2410Component::dump_config() {
|
|||||||
#endif
|
#endif
|
||||||
this->read_all_info();
|
this->read_all_info();
|
||||||
ESP_LOGCONFIG(TAG,
|
ESP_LOGCONFIG(TAG,
|
||||||
" Throttle_ : %ums\n"
|
" Throttle: %ums\n"
|
||||||
" MAC Address : %s\n"
|
" MAC address: %s\n"
|
||||||
" Firmware Version : %s",
|
" Firmware version: %s",
|
||||||
this->throttle_, const_cast<char *>(this->mac_.c_str()), const_cast<char *>(this->version_.c_str()));
|
this->throttle_, const_cast<char *>(this->mac_.c_str()), const_cast<char *>(this->version_.c_str()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,7 +155,7 @@ void LD2410Component::handle_periodic_data_(uint8_t *buffer, int len) {
|
|||||||
/*
|
/*
|
||||||
Reduce data update rate to prevent home assistant database size grow fast
|
Reduce data update rate to prevent home assistant database size grow fast
|
||||||
*/
|
*/
|
||||||
int32_t current_millis = millis();
|
int32_t current_millis = App.get_loop_component_start_time();
|
||||||
if (current_millis - last_periodic_millis_ < this->throttle_)
|
if (current_millis - last_periodic_millis_ < this->throttle_)
|
||||||
return;
|
return;
|
||||||
last_periodic_millis_ = current_millis;
|
last_periodic_millis_ = current_millis;
|
||||||
@@ -299,21 +301,6 @@ const char MAC_FMT[] = "%02X:%02X:%02X:%02X:%02X:%02X";
|
|||||||
const std::string UNKNOWN_MAC("unknown");
|
const std::string UNKNOWN_MAC("unknown");
|
||||||
const std::string NO_MAC("08:05:04:03:02:01");
|
const std::string NO_MAC("08:05:04:03:02:01");
|
||||||
|
|
||||||
std::string format_mac(uint8_t *buffer) {
|
|
||||||
std::string::size_type mac_size = 256;
|
|
||||||
std::string mac;
|
|
||||||
do {
|
|
||||||
mac.resize(mac_size + 1);
|
|
||||||
mac_size = std::snprintf(&mac[0], mac.size(), MAC_FMT, buffer[10], buffer[11], buffer[12], buffer[13], buffer[14],
|
|
||||||
buffer[15]);
|
|
||||||
} while (mac_size + 1 > mac.size());
|
|
||||||
mac.resize(mac_size);
|
|
||||||
if (mac == NO_MAC) {
|
|
||||||
return UNKNOWN_MAC;
|
|
||||||
}
|
|
||||||
return mac;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef USE_NUMBER
|
#ifdef USE_NUMBER
|
||||||
std::function<void(void)> set_number_value(number::Number *n, float value) {
|
std::function<void(void)> set_number_value(number::Number *n, float value) {
|
||||||
float normalized_value = value * 1.0;
|
float normalized_value = value * 1.0;
|
||||||
@@ -328,40 +315,40 @@ std::function<void(void)> set_number_value(number::Number *n, float value) {
|
|||||||
bool LD2410Component::handle_ack_data_(uint8_t *buffer, int len) {
|
bool LD2410Component::handle_ack_data_(uint8_t *buffer, int len) {
|
||||||
ESP_LOGV(TAG, "Handling ACK DATA for COMMAND %02X", buffer[COMMAND]);
|
ESP_LOGV(TAG, "Handling ACK DATA for COMMAND %02X", buffer[COMMAND]);
|
||||||
if (len < 10) {
|
if (len < 10) {
|
||||||
ESP_LOGE(TAG, "Error with last command : incorrect length");
|
ESP_LOGE(TAG, "Invalid length");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (buffer[0] != 0xFD || buffer[1] != 0xFC || buffer[2] != 0xFB || buffer[3] != 0xFA) { // check 4 frame start bytes
|
if (buffer[0] != 0xFD || buffer[1] != 0xFC || buffer[2] != 0xFB || buffer[3] != 0xFA) { // check 4 frame start bytes
|
||||||
ESP_LOGE(TAG, "Error with last command : incorrect Header");
|
ESP_LOGE(TAG, "Invalid header");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (buffer[COMMAND_STATUS] != 0x01) {
|
if (buffer[COMMAND_STATUS] != 0x01) {
|
||||||
ESP_LOGE(TAG, "Error with last command : status != 0x01");
|
ESP_LOGE(TAG, "Invalid status");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (this->two_byte_to_int_(buffer[8], buffer[9]) != 0x00) {
|
if (this->two_byte_to_int_(buffer[8], buffer[9]) != 0x00) {
|
||||||
ESP_LOGE(TAG, "Error with last command , last buffer was: %u , %u", buffer[8], buffer[9]);
|
ESP_LOGE(TAG, "Invalid command: %u, %u", buffer[8], buffer[9]);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (buffer[COMMAND]) {
|
switch (buffer[COMMAND]) {
|
||||||
case lowbyte(CMD_ENABLE_CONF):
|
case lowbyte(CMD_ENABLE_CONF):
|
||||||
ESP_LOGV(TAG, "Handled Enable conf command");
|
ESP_LOGV(TAG, "Enable conf");
|
||||||
break;
|
break;
|
||||||
case lowbyte(CMD_DISABLE_CONF):
|
case lowbyte(CMD_DISABLE_CONF):
|
||||||
ESP_LOGV(TAG, "Handled Disabled conf command");
|
ESP_LOGV(TAG, "Disabled conf");
|
||||||
break;
|
break;
|
||||||
case lowbyte(CMD_SET_BAUD_RATE):
|
case lowbyte(CMD_SET_BAUD_RATE):
|
||||||
ESP_LOGV(TAG, "Handled baud rate change command");
|
ESP_LOGV(TAG, "Baud rate change");
|
||||||
#ifdef USE_SELECT
|
#ifdef USE_SELECT
|
||||||
if (this->baud_rate_select_ != nullptr) {
|
if (this->baud_rate_select_ != nullptr) {
|
||||||
ESP_LOGE(TAG, "Change baud rate component config to %s and reinstall", this->baud_rate_select_->state.c_str());
|
ESP_LOGE(TAG, "Configure baud rate to %s and reinstall", this->baud_rate_select_->state.c_str());
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
case lowbyte(CMD_VERSION):
|
case lowbyte(CMD_VERSION):
|
||||||
this->version_ = format_version(buffer);
|
this->version_ = format_version(buffer);
|
||||||
ESP_LOGV(TAG, "FW Version is: %s", const_cast<char *>(this->version_.c_str()));
|
ESP_LOGV(TAG, "Firmware version: %s", const_cast<char *>(this->version_.c_str()));
|
||||||
#ifdef USE_TEXT_SENSOR
|
#ifdef USE_TEXT_SENSOR
|
||||||
if (this->version_text_sensor_ != nullptr) {
|
if (this->version_text_sensor_ != nullptr) {
|
||||||
this->version_text_sensor_->publish_state(this->version_);
|
this->version_text_sensor_->publish_state(this->version_);
|
||||||
@@ -371,7 +358,7 @@ bool LD2410Component::handle_ack_data_(uint8_t *buffer, int len) {
|
|||||||
case lowbyte(CMD_QUERY_DISTANCE_RESOLUTION): {
|
case lowbyte(CMD_QUERY_DISTANCE_RESOLUTION): {
|
||||||
std::string distance_resolution =
|
std::string distance_resolution =
|
||||||
DISTANCE_RESOLUTION_INT_TO_ENUM.at(this->two_byte_to_int_(buffer[10], buffer[11]));
|
DISTANCE_RESOLUTION_INT_TO_ENUM.at(this->two_byte_to_int_(buffer[10], buffer[11]));
|
||||||
ESP_LOGV(TAG, "Distance resolution is: %s", const_cast<char *>(distance_resolution.c_str()));
|
ESP_LOGV(TAG, "Distance resolution: %s", const_cast<char *>(distance_resolution.c_str()));
|
||||||
#ifdef USE_SELECT
|
#ifdef USE_SELECT
|
||||||
if (this->distance_resolution_select_ != nullptr &&
|
if (this->distance_resolution_select_ != nullptr &&
|
||||||
this->distance_resolution_select_->state != distance_resolution) {
|
this->distance_resolution_select_->state != distance_resolution) {
|
||||||
@@ -383,9 +370,9 @@ bool LD2410Component::handle_ack_data_(uint8_t *buffer, int len) {
|
|||||||
this->light_function_ = LIGHT_FUNCTION_INT_TO_ENUM.at(buffer[10]);
|
this->light_function_ = LIGHT_FUNCTION_INT_TO_ENUM.at(buffer[10]);
|
||||||
this->light_threshold_ = buffer[11] * 1.0;
|
this->light_threshold_ = buffer[11] * 1.0;
|
||||||
this->out_pin_level_ = OUT_PIN_LEVEL_INT_TO_ENUM.at(buffer[12]);
|
this->out_pin_level_ = OUT_PIN_LEVEL_INT_TO_ENUM.at(buffer[12]);
|
||||||
ESP_LOGV(TAG, "Light function is: %s", const_cast<char *>(this->light_function_.c_str()));
|
ESP_LOGV(TAG, "Light function: %s", const_cast<char *>(this->light_function_.c_str()));
|
||||||
ESP_LOGV(TAG, "Light threshold is: %f", this->light_threshold_);
|
ESP_LOGV(TAG, "Light threshold: %f", this->light_threshold_);
|
||||||
ESP_LOGV(TAG, "Out pin level is: %s", const_cast<char *>(this->out_pin_level_.c_str()));
|
ESP_LOGV(TAG, "Out pin level: %s", const_cast<char *>(this->out_pin_level_.c_str()));
|
||||||
#ifdef USE_SELECT
|
#ifdef USE_SELECT
|
||||||
if (this->light_function_select_ != nullptr && this->light_function_select_->state != this->light_function_) {
|
if (this->light_function_select_ != nullptr && this->light_function_select_->state != this->light_function_) {
|
||||||
this->light_function_select_->publish_state(this->light_function_);
|
this->light_function_select_->publish_state(this->light_function_);
|
||||||
@@ -406,11 +393,11 @@ bool LD2410Component::handle_ack_data_(uint8_t *buffer, int len) {
|
|||||||
if (len < 20) {
|
if (len < 20) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
this->mac_ = format_mac(buffer);
|
this->mac_ = format_mac_address_pretty(&buffer[10]);
|
||||||
ESP_LOGV(TAG, "MAC Address is: %s", const_cast<char *>(this->mac_.c_str()));
|
ESP_LOGV(TAG, "MAC address: %s", this->mac_.c_str());
|
||||||
#ifdef USE_TEXT_SENSOR
|
#ifdef USE_TEXT_SENSOR
|
||||||
if (this->mac_text_sensor_ != nullptr) {
|
if (this->mac_text_sensor_ != nullptr) {
|
||||||
this->mac_text_sensor_->publish_state(this->mac_);
|
this->mac_text_sensor_->publish_state(this->mac_ == NO_MAC ? UNKNOWN_MAC : this->mac_);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_SWITCH
|
#ifdef USE_SWITCH
|
||||||
@@ -420,19 +407,19 @@ bool LD2410Component::handle_ack_data_(uint8_t *buffer, int len) {
|
|||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
case lowbyte(CMD_GATE_SENS):
|
case lowbyte(CMD_GATE_SENS):
|
||||||
ESP_LOGV(TAG, "Handled sensitivity command");
|
ESP_LOGV(TAG, "Sensitivity");
|
||||||
break;
|
break;
|
||||||
case lowbyte(CMD_BLUETOOTH):
|
case lowbyte(CMD_BLUETOOTH):
|
||||||
ESP_LOGV(TAG, "Handled bluetooth command");
|
ESP_LOGV(TAG, "Bluetooth");
|
||||||
break;
|
break;
|
||||||
case lowbyte(CMD_SET_DISTANCE_RESOLUTION):
|
case lowbyte(CMD_SET_DISTANCE_RESOLUTION):
|
||||||
ESP_LOGV(TAG, "Handled set distance resolution command");
|
ESP_LOGV(TAG, "Set distance resolution");
|
||||||
break;
|
break;
|
||||||
case lowbyte(CMD_SET_LIGHT_CONTROL):
|
case lowbyte(CMD_SET_LIGHT_CONTROL):
|
||||||
ESP_LOGV(TAG, "Handled set light control command");
|
ESP_LOGV(TAG, "Set light control");
|
||||||
break;
|
break;
|
||||||
case lowbyte(CMD_BT_PASSWORD):
|
case lowbyte(CMD_BT_PASSWORD):
|
||||||
ESP_LOGV(TAG, "Handled set bluetooth password command");
|
ESP_LOGV(TAG, "Set bluetooth password");
|
||||||
break;
|
break;
|
||||||
case lowbyte(CMD_QUERY): // Query parameters response
|
case lowbyte(CMD_QUERY): // Query parameters response
|
||||||
{
|
{
|
||||||
@@ -532,7 +519,7 @@ void LD2410Component::set_baud_rate(const std::string &state) {
|
|||||||
|
|
||||||
void LD2410Component::set_bluetooth_password(const std::string &password) {
|
void LD2410Component::set_bluetooth_password(const std::string &password) {
|
||||||
if (password.length() != 6) {
|
if (password.length() != 6) {
|
||||||
ESP_LOGE(TAG, "set_bluetooth_password(): invalid password length, must be exactly 6 chars '%s'", password.c_str());
|
ESP_LOGE(TAG, "Password must be exactly 6 chars");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this->set_config_mode_(true);
|
this->set_config_mode_(true);
|
||||||
@@ -544,7 +531,7 @@ void LD2410Component::set_bluetooth_password(const std::string &password) {
|
|||||||
|
|
||||||
void LD2410Component::set_engineering_mode(bool enable) {
|
void LD2410Component::set_engineering_mode(bool enable) {
|
||||||
this->set_config_mode_(true);
|
this->set_config_mode_(true);
|
||||||
last_engineering_mode_change_millis_ = millis();
|
last_engineering_mode_change_millis_ = App.get_loop_component_start_time();
|
||||||
uint8_t cmd = enable ? CMD_ENABLE_ENG : CMD_DISABLE_ENG;
|
uint8_t cmd = enable ? CMD_ENABLE_ENG : CMD_DISABLE_ENG;
|
||||||
this->send_command_(cmd, nullptr, 0);
|
this->send_command_(cmd, nullptr, 0);
|
||||||
this->set_config_mode_(false);
|
this->set_config_mode_(false);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include "ld2420.h"
|
#include "ld2420.h"
|
||||||
|
#include "esphome/core/application.h"
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -40,7 +41,7 @@ There are three documented parameters for modes:
|
|||||||
00 04 = Energy output mode
|
00 04 = Energy output mode
|
||||||
This mode outputs detailed signal energy values for each gate and the target distance.
|
This mode outputs detailed signal energy values for each gate and the target distance.
|
||||||
The data format consist of the following.
|
The data format consist of the following.
|
||||||
Header HH, Length LL, Persence PP, Distance DD, 16 Gate Energies EE, Footer FF
|
Header HH, Length LL, Presence PP, Distance DD, 16 Gate Energies EE, Footer FF
|
||||||
HH HH HH HH LL LL PP DD DD EE EE .. 16x .. FF FF FF FF
|
HH HH HH HH LL LL PP DD DD EE EE .. 16x .. FF FF FF FF
|
||||||
F4 F3 F2 F1 23 00 00 00 00 00 00 .. .. .. .. F8 F7 F6 F5
|
F4 F3 F2 F1 23 00 00 00 00 00 00 .. .. .. .. F8 F7 F6 F5
|
||||||
00 00 = debug output mode
|
00 00 = debug output mode
|
||||||
@@ -67,10 +68,10 @@ float LD2420Component::get_setup_priority() const { return setup_priority::BUS;
|
|||||||
void LD2420Component::dump_config() {
|
void LD2420Component::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG,
|
ESP_LOGCONFIG(TAG,
|
||||||
"LD2420:\n"
|
"LD2420:\n"
|
||||||
" Firmware Version : %7s\n"
|
" Firmware version: %7s",
|
||||||
"LD2420 Number:",
|
|
||||||
this->ld2420_firmware_ver_);
|
this->ld2420_firmware_ver_);
|
||||||
#ifdef USE_NUMBER
|
#ifdef USE_NUMBER
|
||||||
|
ESP_LOGCONFIG(TAG, "Number:");
|
||||||
LOG_NUMBER(TAG, " Gate Timeout:", this->gate_timeout_number_);
|
LOG_NUMBER(TAG, " Gate Timeout:", this->gate_timeout_number_);
|
||||||
LOG_NUMBER(TAG, " Gate Max Distance:", this->max_gate_distance_number_);
|
LOG_NUMBER(TAG, " Gate Max Distance:", this->max_gate_distance_number_);
|
||||||
LOG_NUMBER(TAG, " Gate Min Distance:", this->min_gate_distance_number_);
|
LOG_NUMBER(TAG, " Gate Min Distance:", this->min_gate_distance_number_);
|
||||||
@@ -86,10 +87,10 @@ void LD2420Component::dump_config() {
|
|||||||
LOG_BUTTON(TAG, " Factory Reset:", this->factory_reset_button_);
|
LOG_BUTTON(TAG, " Factory Reset:", this->factory_reset_button_);
|
||||||
LOG_BUTTON(TAG, " Restart Module:", this->restart_module_button_);
|
LOG_BUTTON(TAG, " Restart Module:", this->restart_module_button_);
|
||||||
#endif
|
#endif
|
||||||
ESP_LOGCONFIG(TAG, "LD2420 Select:");
|
ESP_LOGCONFIG(TAG, "Select:");
|
||||||
LOG_SELECT(TAG, " Operating Mode", this->operating_selector_);
|
LOG_SELECT(TAG, " Operating Mode", this->operating_selector_);
|
||||||
if (this->get_firmware_int_(ld2420_firmware_ver_) < CALIBRATE_VERSION_MIN) {
|
if (LD2420Component::get_firmware_int(this->ld2420_firmware_ver_) < CALIBRATE_VERSION_MIN) {
|
||||||
ESP_LOGW(TAG, "LD2420 Firmware Version %s and older are only supported in Simple Mode", ld2420_firmware_ver_);
|
ESP_LOGW(TAG, "Firmware version %s and older supports Simple Mode only", this->ld2420_firmware_ver_);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,7 +103,7 @@ uint8_t LD2420Component::calc_checksum(void *data, size_t size) {
|
|||||||
return checksum;
|
return checksum;
|
||||||
}
|
}
|
||||||
|
|
||||||
int LD2420Component::get_firmware_int_(const char *version_string) {
|
int LD2420Component::get_firmware_int(const char *version_string) {
|
||||||
std::string version_str = version_string;
|
std::string version_str = version_string;
|
||||||
if (version_str[0] == 'v') {
|
if (version_str[0] == 'v') {
|
||||||
version_str = version_str.substr(1);
|
version_str = version_str.substr(1);
|
||||||
@@ -115,7 +116,7 @@ int LD2420Component::get_firmware_int_(const char *version_string) {
|
|||||||
void LD2420Component::setup() {
|
void LD2420Component::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Running setup");
|
ESP_LOGCONFIG(TAG, "Running setup");
|
||||||
if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) {
|
if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) {
|
||||||
ESP_LOGE(TAG, "LD2420 module has failed to respond, check baud rate and serial connections.");
|
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -127,7 +128,7 @@ void LD2420Component::setup() {
|
|||||||
const char *pfw = this->ld2420_firmware_ver_;
|
const char *pfw = this->ld2420_firmware_ver_;
|
||||||
std::string fw_str(pfw);
|
std::string fw_str(pfw);
|
||||||
|
|
||||||
for (auto &listener : listeners_) {
|
for (auto &listener : this->listeners_) {
|
||||||
listener->on_fw_version(fw_str);
|
listener->on_fw_version(fw_str);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,11 +138,11 @@ void LD2420Component::setup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
memcpy(&this->new_config, &this->current_config, sizeof(this->current_config));
|
memcpy(&this->new_config, &this->current_config, sizeof(this->current_config));
|
||||||
if (get_firmware_int_(ld2420_firmware_ver_) < CALIBRATE_VERSION_MIN) {
|
if (LD2420Component::get_firmware_int(this->ld2420_firmware_ver_) < CALIBRATE_VERSION_MIN) {
|
||||||
this->set_operating_mode(OP_SIMPLE_MODE_STRING);
|
this->set_operating_mode(OP_SIMPLE_MODE_STRING);
|
||||||
this->operating_selector_->publish_state(OP_SIMPLE_MODE_STRING);
|
this->operating_selector_->publish_state(OP_SIMPLE_MODE_STRING);
|
||||||
this->set_mode_(CMD_SYSTEM_MODE_SIMPLE);
|
this->set_mode_(CMD_SYSTEM_MODE_SIMPLE);
|
||||||
ESP_LOGW(TAG, "LD2420 Frimware Version %s and older are only supported in Simple Mode", ld2420_firmware_ver_);
|
ESP_LOGW(TAG, "Firmware version %s and older supports Simple Mode only", this->ld2420_firmware_ver_);
|
||||||
} else {
|
} else {
|
||||||
this->set_mode_(CMD_SYSTEM_MODE_ENERGY);
|
this->set_mode_(CMD_SYSTEM_MODE_ENERGY);
|
||||||
this->operating_selector_->publish_state(OP_NORMAL_MODE_STRING);
|
this->operating_selector_->publish_state(OP_NORMAL_MODE_STRING);
|
||||||
@@ -151,18 +152,17 @@ void LD2420Component::setup() {
|
|||||||
#endif
|
#endif
|
||||||
this->set_system_mode(this->system_mode_);
|
this->set_system_mode(this->system_mode_);
|
||||||
this->set_config_mode(false);
|
this->set_config_mode(false);
|
||||||
ESP_LOGCONFIG(TAG, "LD2420 setup complete.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LD2420Component::apply_config_action() {
|
void LD2420Component::apply_config_action() {
|
||||||
const uint8_t checksum = calc_checksum(&this->new_config, sizeof(this->new_config));
|
const uint8_t checksum = calc_checksum(&this->new_config, sizeof(this->new_config));
|
||||||
if (checksum == calc_checksum(&this->current_config, sizeof(this->current_config))) {
|
if (checksum == calc_checksum(&this->current_config, sizeof(this->current_config))) {
|
||||||
ESP_LOGCONFIG(TAG, "No configuration change detected");
|
ESP_LOGD(TAG, "No configuration change detected");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ESP_LOGCONFIG(TAG, "Reconfiguring LD2420");
|
ESP_LOGD(TAG, "Reconfiguring");
|
||||||
if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) {
|
if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) {
|
||||||
ESP_LOGE(TAG, "LD2420 module has failed to respond, check baud rate and serial connections.");
|
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -178,13 +178,12 @@ void LD2420Component::apply_config_action() {
|
|||||||
this->set_system_mode(this->system_mode_);
|
this->set_system_mode(this->system_mode_);
|
||||||
this->set_config_mode(false); // Disable config mode to save new values in LD2420 nvm
|
this->set_config_mode(false); // Disable config mode to save new values in LD2420 nvm
|
||||||
this->set_operating_mode(OP_NORMAL_MODE_STRING);
|
this->set_operating_mode(OP_NORMAL_MODE_STRING);
|
||||||
ESP_LOGCONFIG(TAG, "LD2420 reconfig complete.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LD2420Component::factory_reset_action() {
|
void LD2420Component::factory_reset_action() {
|
||||||
ESP_LOGCONFIG(TAG, "Setting factory defaults");
|
ESP_LOGD(TAG, "Setting factory defaults");
|
||||||
if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) {
|
if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) {
|
||||||
ESP_LOGE(TAG, "LD2420 module has failed to respond, check baud rate and serial connections.");
|
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -207,18 +206,16 @@ void LD2420Component::factory_reset_action() {
|
|||||||
this->init_gate_config_numbers();
|
this->init_gate_config_numbers();
|
||||||
this->refresh_gate_config_numbers();
|
this->refresh_gate_config_numbers();
|
||||||
#endif
|
#endif
|
||||||
ESP_LOGCONFIG(TAG, "LD2420 factory reset complete.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LD2420Component::restart_module_action() {
|
void LD2420Component::restart_module_action() {
|
||||||
ESP_LOGCONFIG(TAG, "Restarting LD2420 module");
|
ESP_LOGD(TAG, "Restarting");
|
||||||
this->send_module_restart();
|
this->send_module_restart();
|
||||||
this->set_timeout(250, [this]() {
|
this->set_timeout(250, [this]() {
|
||||||
this->set_config_mode(true);
|
this->set_config_mode(true);
|
||||||
this->set_system_mode(system_mode_);
|
this->set_system_mode(this->system_mode_);
|
||||||
this->set_config_mode(false);
|
this->set_config_mode(false);
|
||||||
});
|
});
|
||||||
ESP_LOGCONFIG(TAG, "LD2420 Restarted.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LD2420Component::revert_config_action() {
|
void LD2420Component::revert_config_action() {
|
||||||
@@ -226,18 +223,18 @@ void LD2420Component::revert_config_action() {
|
|||||||
#ifdef USE_NUMBER
|
#ifdef USE_NUMBER
|
||||||
this->init_gate_config_numbers();
|
this->init_gate_config_numbers();
|
||||||
#endif
|
#endif
|
||||||
ESP_LOGCONFIG(TAG, "Reverted config number edits.");
|
ESP_LOGD(TAG, "Reverted config number edits");
|
||||||
}
|
}
|
||||||
|
|
||||||
void LD2420Component::loop() {
|
void LD2420Component::loop() {
|
||||||
// If there is a active send command do not process it here, the send command call will handle it.
|
// If there is a active send command do not process it here, the send command call will handle it.
|
||||||
if (!get_cmd_active_()) {
|
if (!this->get_cmd_active_()) {
|
||||||
if (!available())
|
if (!this->available())
|
||||||
return;
|
return;
|
||||||
static uint8_t buffer[2048];
|
static uint8_t buffer[2048];
|
||||||
static uint8_t rx_data;
|
static uint8_t rx_data;
|
||||||
while (available()) {
|
while (this->available()) {
|
||||||
rx_data = read();
|
rx_data = this->read();
|
||||||
this->readline_(rx_data, buffer, sizeof(buffer));
|
this->readline_(rx_data, buffer, sizeof(buffer));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -292,7 +289,7 @@ void LD2420Component::report_gate_data() {
|
|||||||
|
|
||||||
void LD2420Component::set_operating_mode(const std::string &state) {
|
void LD2420Component::set_operating_mode(const std::string &state) {
|
||||||
// If unsupported firmware ignore mode select
|
// If unsupported firmware ignore mode select
|
||||||
if (get_firmware_int_(ld2420_firmware_ver_) >= CALIBRATE_VERSION_MIN) {
|
if (LD2420Component::get_firmware_int(ld2420_firmware_ver_) >= CALIBRATE_VERSION_MIN) {
|
||||||
this->current_operating_mode = OP_MODE_TO_UINT.at(state);
|
this->current_operating_mode = OP_MODE_TO_UINT.at(state);
|
||||||
// Entering Auto Calibrate we need to clear the privoiuos data collection
|
// Entering Auto Calibrate we need to clear the privoiuos data collection
|
||||||
this->operating_selector_->publish_state(state);
|
this->operating_selector_->publish_state(state);
|
||||||
@@ -365,13 +362,13 @@ void LD2420Component::handle_energy_mode_(uint8_t *buffer, int len) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Resonable refresh rate for home assistant database size health
|
// Resonable refresh rate for home assistant database size health
|
||||||
const int32_t current_millis = millis();
|
const int32_t current_millis = App.get_loop_component_start_time();
|
||||||
if (current_millis - this->last_periodic_millis < REFRESH_RATE_MS)
|
if (current_millis - this->last_periodic_millis < REFRESH_RATE_MS)
|
||||||
return;
|
return;
|
||||||
this->last_periodic_millis = current_millis;
|
this->last_periodic_millis = current_millis;
|
||||||
for (auto &listener : this->listeners_) {
|
for (auto &listener : this->listeners_) {
|
||||||
listener->on_distance(get_distance_());
|
listener->on_distance(this->get_distance_());
|
||||||
listener->on_presence(get_presence_());
|
listener->on_presence(this->get_presence_());
|
||||||
listener->on_energy(this->gate_energy_, sizeof(this->gate_energy_) / sizeof(this->gate_energy_[0]));
|
listener->on_energy(this->gate_energy_, sizeof(this->gate_energy_) / sizeof(this->gate_energy_[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -392,9 +389,9 @@ void LD2420Component::handle_simple_mode_(const uint8_t *inbuf, int len) {
|
|||||||
char outbuf[bufsize]{0};
|
char outbuf[bufsize]{0};
|
||||||
while (true) {
|
while (true) {
|
||||||
if (inbuf[pos - 2] == 'O' && inbuf[pos - 1] == 'F' && inbuf[pos] == 'F') {
|
if (inbuf[pos - 2] == 'O' && inbuf[pos - 1] == 'F' && inbuf[pos] == 'F') {
|
||||||
set_presence_(false);
|
this->set_presence_(false);
|
||||||
} else if (inbuf[pos - 1] == 'O' && inbuf[pos] == 'N') {
|
} else if (inbuf[pos - 1] == 'O' && inbuf[pos] == 'N') {
|
||||||
set_presence_(true);
|
this->set_presence_(true);
|
||||||
}
|
}
|
||||||
if (inbuf[pos] >= '0' && inbuf[pos] <= '9') {
|
if (inbuf[pos] >= '0' && inbuf[pos] <= '9') {
|
||||||
if (index < bufsize - 1) {
|
if (index < bufsize - 1) {
|
||||||
@@ -411,18 +408,18 @@ void LD2420Component::handle_simple_mode_(const uint8_t *inbuf, int len) {
|
|||||||
}
|
}
|
||||||
outbuf[index] = '\0';
|
outbuf[index] = '\0';
|
||||||
if (index > 1)
|
if (index > 1)
|
||||||
set_distance_(strtol(outbuf, &endptr, 10));
|
this->set_distance_(strtol(outbuf, &endptr, 10));
|
||||||
|
|
||||||
if (get_mode_() == CMD_SYSTEM_MODE_SIMPLE) {
|
if (this->get_mode_() == CMD_SYSTEM_MODE_SIMPLE) {
|
||||||
// Resonable refresh rate for home assistant database size health
|
// Resonable refresh rate for home assistant database size health
|
||||||
const int32_t current_millis = millis();
|
const int32_t current_millis = App.get_loop_component_start_time();
|
||||||
if (current_millis - this->last_normal_periodic_millis < REFRESH_RATE_MS)
|
if (current_millis - this->last_normal_periodic_millis < REFRESH_RATE_MS)
|
||||||
return;
|
return;
|
||||||
this->last_normal_periodic_millis = current_millis;
|
this->last_normal_periodic_millis = current_millis;
|
||||||
for (auto &listener : this->listeners_)
|
for (auto &listener : this->listeners_)
|
||||||
listener->on_distance(get_distance_());
|
listener->on_distance(this->get_distance_());
|
||||||
for (auto &listener : this->listeners_)
|
for (auto &listener : this->listeners_)
|
||||||
listener->on_presence(get_presence_());
|
listener->on_presence(this->get_presence_());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -433,10 +430,10 @@ void LD2420Component::handle_ack_data_(uint8_t *buffer, int len) {
|
|||||||
uint8_t data_element = 0;
|
uint8_t data_element = 0;
|
||||||
uint16_t data_pos = 0;
|
uint16_t data_pos = 0;
|
||||||
if (this->cmd_reply_.length > CMD_MAX_BYTES) {
|
if (this->cmd_reply_.length > CMD_MAX_BYTES) {
|
||||||
ESP_LOGW(TAG, "LD2420 reply - received command reply frame is corrupt, length exceeds %d bytes.", CMD_MAX_BYTES);
|
ESP_LOGW(TAG, "Reply frame too long");
|
||||||
return;
|
return;
|
||||||
} else if (this->cmd_reply_.length < 2) {
|
} else if (this->cmd_reply_.length < 2) {
|
||||||
ESP_LOGW(TAG, "LD2420 reply - received command frame is corrupt, length is less than 2 bytes.");
|
ESP_LOGW(TAG, "Command frame too short");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
memcpy(&this->cmd_reply_.error, &buffer[CMD_ERROR_WORD], sizeof(this->cmd_reply_.error));
|
memcpy(&this->cmd_reply_.error, &buffer[CMD_ERROR_WORD], sizeof(this->cmd_reply_.error));
|
||||||
@@ -447,13 +444,13 @@ void LD2420Component::handle_ack_data_(uint8_t *buffer, int len) {
|
|||||||
this->cmd_reply_.ack = true;
|
this->cmd_reply_.ack = true;
|
||||||
switch ((uint16_t) this->cmd_reply_.command) {
|
switch ((uint16_t) this->cmd_reply_.command) {
|
||||||
case (CMD_ENABLE_CONF):
|
case (CMD_ENABLE_CONF):
|
||||||
ESP_LOGD(TAG, "LD2420 reply - set config enable: CMD = %2X %s", CMD_ENABLE_CONF, result);
|
ESP_LOGV(TAG, "Set config enable: CMD = %2X %s", CMD_ENABLE_CONF, result);
|
||||||
break;
|
break;
|
||||||
case (CMD_DISABLE_CONF):
|
case (CMD_DISABLE_CONF):
|
||||||
ESP_LOGD(TAG, "LD2420 reply - set config disable: CMD = %2X %s", CMD_DISABLE_CONF, result);
|
ESP_LOGV(TAG, "Set config disable: CMD = %2X %s", CMD_DISABLE_CONF, result);
|
||||||
break;
|
break;
|
||||||
case (CMD_READ_REGISTER):
|
case (CMD_READ_REGISTER):
|
||||||
ESP_LOGD(TAG, "LD2420 reply - read register: CMD = %2X %s", CMD_READ_REGISTER, result);
|
ESP_LOGV(TAG, "Read register: CMD = %2X %s", CMD_READ_REGISTER, result);
|
||||||
// TODO Read/Write register is not implemented yet, this will get flushed out to a proper header file
|
// TODO Read/Write register is not implemented yet, this will get flushed out to a proper header file
|
||||||
data_pos = 0x0A;
|
data_pos = 0x0A;
|
||||||
for (uint16_t index = 0; index < (CMD_REG_DATA_REPLY_SIZE * // NOLINT
|
for (uint16_t index = 0; index < (CMD_REG_DATA_REPLY_SIZE * // NOLINT
|
||||||
@@ -465,13 +462,13 @@ void LD2420Component::handle_ack_data_(uint8_t *buffer, int len) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case (CMD_WRITE_REGISTER):
|
case (CMD_WRITE_REGISTER):
|
||||||
ESP_LOGD(TAG, "LD2420 reply - write register: CMD = %2X %s", CMD_WRITE_REGISTER, result);
|
ESP_LOGV(TAG, "Write register: CMD = %2X %s", CMD_WRITE_REGISTER, result);
|
||||||
break;
|
break;
|
||||||
case (CMD_WRITE_ABD_PARAM):
|
case (CMD_WRITE_ABD_PARAM):
|
||||||
ESP_LOGD(TAG, "LD2420 reply - write gate parameter(s): %2X %s", CMD_WRITE_ABD_PARAM, result);
|
ESP_LOGV(TAG, "Write gate parameter(s): %2X %s", CMD_WRITE_ABD_PARAM, result);
|
||||||
break;
|
break;
|
||||||
case (CMD_READ_ABD_PARAM):
|
case (CMD_READ_ABD_PARAM):
|
||||||
ESP_LOGD(TAG, "LD2420 reply - read gate parameter(s): %2X %s", CMD_READ_ABD_PARAM, result);
|
ESP_LOGV(TAG, "Read gate parameter(s): %2X %s", CMD_READ_ABD_PARAM, result);
|
||||||
data_pos = CMD_ABD_DATA_REPLY_START;
|
data_pos = CMD_ABD_DATA_REPLY_START;
|
||||||
for (uint16_t index = 0; index < (CMD_ABD_DATA_REPLY_SIZE * // NOLINT
|
for (uint16_t index = 0; index < (CMD_ABD_DATA_REPLY_SIZE * // NOLINT
|
||||||
((buffer[CMD_FRAME_DATA_LENGTH] - 4) / CMD_ABD_DATA_REPLY_SIZE));
|
((buffer[CMD_FRAME_DATA_LENGTH] - 4) / CMD_ABD_DATA_REPLY_SIZE));
|
||||||
@@ -483,11 +480,11 @@ void LD2420Component::handle_ack_data_(uint8_t *buffer, int len) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case (CMD_WRITE_SYS_PARAM):
|
case (CMD_WRITE_SYS_PARAM):
|
||||||
ESP_LOGD(TAG, "LD2420 reply - set system parameter(s): %2X %s", CMD_WRITE_SYS_PARAM, result);
|
ESP_LOGV(TAG, "Set system parameter(s): %2X %s", CMD_WRITE_SYS_PARAM, result);
|
||||||
break;
|
break;
|
||||||
case (CMD_READ_VERSION):
|
case (CMD_READ_VERSION):
|
||||||
memcpy(this->ld2420_firmware_ver_, &buffer[12], buffer[10]);
|
memcpy(this->ld2420_firmware_ver_, &buffer[12], buffer[10]);
|
||||||
ESP_LOGD(TAG, "LD2420 reply - module firmware version: %7s %s", this->ld2420_firmware_ver_, result);
|
ESP_LOGV(TAG, "Firmware version: %7s %s", this->ld2420_firmware_ver_, result);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@@ -533,7 +530,7 @@ int LD2420Component::send_cmd_from_array(CmdFrameT frame) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
while (!this->cmd_reply_.ack) {
|
while (!this->cmd_reply_.ack) {
|
||||||
while (available()) {
|
while (this->available()) {
|
||||||
this->readline_(read(), ack_buffer, sizeof(ack_buffer));
|
this->readline_(read(), ack_buffer, sizeof(ack_buffer));
|
||||||
}
|
}
|
||||||
delay_microseconds_safe(1450);
|
delay_microseconds_safe(1450);
|
||||||
@@ -548,7 +545,7 @@ int LD2420Component::send_cmd_from_array(CmdFrameT frame) {
|
|||||||
if (this->cmd_reply_.ack)
|
if (this->cmd_reply_.ack)
|
||||||
retry = 0;
|
retry = 0;
|
||||||
if (this->cmd_reply_.error > 0)
|
if (this->cmd_reply_.error > 0)
|
||||||
handle_cmd_error(error);
|
this->handle_cmd_error(error);
|
||||||
}
|
}
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
@@ -563,7 +560,7 @@ uint8_t LD2420Component::set_config_mode(bool enable) {
|
|||||||
cmd_frame.data_length += sizeof(CMD_PROTOCOL_VER);
|
cmd_frame.data_length += sizeof(CMD_PROTOCOL_VER);
|
||||||
}
|
}
|
||||||
cmd_frame.footer = CMD_FRAME_FOOTER;
|
cmd_frame.footer = CMD_FRAME_FOOTER;
|
||||||
ESP_LOGD(TAG, "Sending set config %s command: %2X", enable ? "enable" : "disable", cmd_frame.command);
|
ESP_LOGV(TAG, "Sending set config %s command: %2X", enable ? "enable" : "disable", cmd_frame.command);
|
||||||
return this->send_cmd_from_array(cmd_frame);
|
return this->send_cmd_from_array(cmd_frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -576,7 +573,7 @@ void LD2420Component::ld2420_restart() {
|
|||||||
cmd_frame.header = CMD_FRAME_HEADER;
|
cmd_frame.header = CMD_FRAME_HEADER;
|
||||||
cmd_frame.command = CMD_RESTART;
|
cmd_frame.command = CMD_RESTART;
|
||||||
cmd_frame.footer = CMD_FRAME_FOOTER;
|
cmd_frame.footer = CMD_FRAME_FOOTER;
|
||||||
ESP_LOGD(TAG, "Sending restart command: %2X", cmd_frame.command);
|
ESP_LOGV(TAG, "Sending restart command: %2X", cmd_frame.command);
|
||||||
this->send_cmd_from_array(cmd_frame);
|
this->send_cmd_from_array(cmd_frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -588,7 +585,7 @@ void LD2420Component::get_reg_value_(uint16_t reg) {
|
|||||||
cmd_frame.data[1] = reg;
|
cmd_frame.data[1] = reg;
|
||||||
cmd_frame.data_length += 2;
|
cmd_frame.data_length += 2;
|
||||||
cmd_frame.footer = CMD_FRAME_FOOTER;
|
cmd_frame.footer = CMD_FRAME_FOOTER;
|
||||||
ESP_LOGD(TAG, "Sending read register %4X command: %2X", reg, cmd_frame.command);
|
ESP_LOGV(TAG, "Sending read register %4X command: %2X", reg, cmd_frame.command);
|
||||||
this->send_cmd_from_array(cmd_frame);
|
this->send_cmd_from_array(cmd_frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -602,11 +599,11 @@ void LD2420Component::set_reg_value(uint16_t reg, uint16_t value) {
|
|||||||
memcpy(&cmd_frame.data[cmd_frame.data_length], &value, sizeof(CMD_REG_DATA_REPLY_SIZE));
|
memcpy(&cmd_frame.data[cmd_frame.data_length], &value, sizeof(CMD_REG_DATA_REPLY_SIZE));
|
||||||
cmd_frame.data_length += 2;
|
cmd_frame.data_length += 2;
|
||||||
cmd_frame.footer = CMD_FRAME_FOOTER;
|
cmd_frame.footer = CMD_FRAME_FOOTER;
|
||||||
ESP_LOGD(TAG, "Sending write register %4X command: %2X data = %4X", reg, cmd_frame.command, value);
|
ESP_LOGV(TAG, "Sending write register %4X command: %2X data = %4X", reg, cmd_frame.command, value);
|
||||||
this->send_cmd_from_array(cmd_frame);
|
this->send_cmd_from_array(cmd_frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LD2420Component::handle_cmd_error(uint8_t error) { ESP_LOGI(TAG, "Command failed: %s", ERR_MESSAGE[error]); }
|
void LD2420Component::handle_cmd_error(uint8_t error) { ESP_LOGE(TAG, "Command failed: %s", ERR_MESSAGE[error]); }
|
||||||
|
|
||||||
int LD2420Component::get_gate_threshold_(uint8_t gate) {
|
int LD2420Component::get_gate_threshold_(uint8_t gate) {
|
||||||
uint8_t error;
|
uint8_t error;
|
||||||
@@ -619,7 +616,7 @@ int LD2420Component::get_gate_threshold_(uint8_t gate) {
|
|||||||
memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_GATE_STILL_THRESH[gate], sizeof(CMD_GATE_STILL_THRESH[gate]));
|
memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_GATE_STILL_THRESH[gate], sizeof(CMD_GATE_STILL_THRESH[gate]));
|
||||||
cmd_frame.data_length += 2;
|
cmd_frame.data_length += 2;
|
||||||
cmd_frame.footer = CMD_FRAME_FOOTER;
|
cmd_frame.footer = CMD_FRAME_FOOTER;
|
||||||
ESP_LOGD(TAG, "Sending read gate %d high/low theshold command: %2X", gate, cmd_frame.command);
|
ESP_LOGV(TAG, "Sending read gate %d high/low threshold command: %2X", gate, cmd_frame.command);
|
||||||
error = this->send_cmd_from_array(cmd_frame);
|
error = this->send_cmd_from_array(cmd_frame);
|
||||||
if (error == 0) {
|
if (error == 0) {
|
||||||
this->current_config.move_thresh[gate] = cmd_reply_.data[0];
|
this->current_config.move_thresh[gate] = cmd_reply_.data[0];
|
||||||
@@ -644,7 +641,7 @@ int LD2420Component::get_min_max_distances_timeout_() {
|
|||||||
sizeof(CMD_TIMEOUT_REG)); // Register: global delay time
|
sizeof(CMD_TIMEOUT_REG)); // Register: global delay time
|
||||||
cmd_frame.data_length += sizeof(CMD_TIMEOUT_REG);
|
cmd_frame.data_length += sizeof(CMD_TIMEOUT_REG);
|
||||||
cmd_frame.footer = CMD_FRAME_FOOTER;
|
cmd_frame.footer = CMD_FRAME_FOOTER;
|
||||||
ESP_LOGD(TAG, "Sending read gate min max and timeout command: %2X", cmd_frame.command);
|
ESP_LOGV(TAG, "Sending read gate min max and timeout command: %2X", cmd_frame.command);
|
||||||
error = this->send_cmd_from_array(cmd_frame);
|
error = this->send_cmd_from_array(cmd_frame);
|
||||||
if (error == 0) {
|
if (error == 0) {
|
||||||
this->current_config.min_gate = (uint16_t) cmd_reply_.data[0];
|
this->current_config.min_gate = (uint16_t) cmd_reply_.data[0];
|
||||||
@@ -667,9 +664,9 @@ void LD2420Component::set_system_mode(uint16_t mode) {
|
|||||||
memcpy(&cmd_frame.data[cmd_frame.data_length], &unknown_parm, sizeof(unknown_parm));
|
memcpy(&cmd_frame.data[cmd_frame.data_length], &unknown_parm, sizeof(unknown_parm));
|
||||||
cmd_frame.data_length += sizeof(unknown_parm);
|
cmd_frame.data_length += sizeof(unknown_parm);
|
||||||
cmd_frame.footer = CMD_FRAME_FOOTER;
|
cmd_frame.footer = CMD_FRAME_FOOTER;
|
||||||
ESP_LOGD(TAG, "Sending write system mode command: %2X", cmd_frame.command);
|
ESP_LOGV(TAG, "Sending write system mode command: %2X", cmd_frame.command);
|
||||||
if (this->send_cmd_from_array(cmd_frame) == 0)
|
if (this->send_cmd_from_array(cmd_frame) == 0)
|
||||||
set_mode_(mode);
|
this->set_mode_(mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LD2420Component::get_firmware_version_() {
|
void LD2420Component::get_firmware_version_() {
|
||||||
@@ -679,7 +676,7 @@ void LD2420Component::get_firmware_version_() {
|
|||||||
cmd_frame.command = CMD_READ_VERSION;
|
cmd_frame.command = CMD_READ_VERSION;
|
||||||
cmd_frame.footer = CMD_FRAME_FOOTER;
|
cmd_frame.footer = CMD_FRAME_FOOTER;
|
||||||
|
|
||||||
ESP_LOGD(TAG, "Sending read firmware version command: %2X", cmd_frame.command);
|
ESP_LOGV(TAG, "Sending read firmware version command: %2X", cmd_frame.command);
|
||||||
this->send_cmd_from_array(cmd_frame);
|
this->send_cmd_from_array(cmd_frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -712,7 +709,7 @@ void LD2420Component::set_min_max_distances_timeout(uint32_t max_gate_distance,
|
|||||||
cmd_frame.data_length += sizeof(timeout);
|
cmd_frame.data_length += sizeof(timeout);
|
||||||
cmd_frame.footer = CMD_FRAME_FOOTER;
|
cmd_frame.footer = CMD_FRAME_FOOTER;
|
||||||
|
|
||||||
ESP_LOGD(TAG, "Sending write gate min max and timeout command: %2X", cmd_frame.command);
|
ESP_LOGV(TAG, "Sending write gate min max and timeout command: %2X", cmd_frame.command);
|
||||||
this->send_cmd_from_array(cmd_frame);
|
this->send_cmd_from_array(cmd_frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -738,7 +735,7 @@ void LD2420Component::set_gate_threshold(uint8_t gate) {
|
|||||||
sizeof(this->new_config.still_thresh[gate]));
|
sizeof(this->new_config.still_thresh[gate]));
|
||||||
cmd_frame.data_length += sizeof(this->new_config.still_thresh[gate]);
|
cmd_frame.data_length += sizeof(this->new_config.still_thresh[gate]);
|
||||||
cmd_frame.footer = CMD_FRAME_FOOTER;
|
cmd_frame.footer = CMD_FRAME_FOOTER;
|
||||||
ESP_LOGD(TAG, "Sending set gate %4X sensitivity command: %2X", gate, cmd_frame.command);
|
ESP_LOGV(TAG, "Sending set gate %4X sensitivity command: %2X", gate, cmd_frame.command);
|
||||||
this->send_cmd_from_array(cmd_frame);
|
this->send_cmd_from_array(cmd_frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ class LD2420Component : public Component, public uart::UARTDevice {
|
|||||||
void set_operating_mode(const std::string &state);
|
void set_operating_mode(const std::string &state);
|
||||||
void auto_calibrate_sensitivity();
|
void auto_calibrate_sensitivity();
|
||||||
void update_radar_data(uint16_t const *gate_energy, uint8_t sample_number);
|
void update_radar_data(uint16_t const *gate_energy, uint8_t sample_number);
|
||||||
uint8_t calc_checksum(void *data, size_t size);
|
static uint8_t calc_checksum(void *data, size_t size);
|
||||||
|
|
||||||
RegConfigT current_config;
|
RegConfigT current_config;
|
||||||
RegConfigT new_config;
|
RegConfigT new_config;
|
||||||
@@ -222,7 +222,7 @@ class LD2420Component : public Component, public uart::UARTDevice {
|
|||||||
volatile bool ack;
|
volatile bool ack;
|
||||||
};
|
};
|
||||||
|
|
||||||
int get_firmware_int_(const char *version_string);
|
static int get_firmware_int(const char *version_string);
|
||||||
void get_firmware_version_();
|
void get_firmware_version_();
|
||||||
int get_gate_threshold_(uint8_t gate);
|
int get_gate_threshold_(uint8_t gate);
|
||||||
void get_reg_value_(uint16_t reg);
|
void get_reg_value_(uint16_t reg);
|
||||||
|
|||||||
@@ -6,7 +6,9 @@
|
|||||||
#ifdef USE_SENSOR
|
#ifdef USE_SENSOR
|
||||||
#include "esphome/components/sensor/sensor.h"
|
#include "esphome/components/sensor/sensor.h"
|
||||||
#endif
|
#endif
|
||||||
|
#include "esphome/core/application.h"
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
|
||||||
#define highbyte(val) (uint8_t)((val) >> 8)
|
#define highbyte(val) (uint8_t)((val) >> 8)
|
||||||
#define lowbyte(val) (uint8_t)((val) &0xff)
|
#define lowbyte(val) (uint8_t)((val) &0xff)
|
||||||
@@ -96,11 +98,6 @@ static inline std::string get_direction(int16_t speed) {
|
|||||||
return STATIONARY;
|
return STATIONARY;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline std::string format_mac(uint8_t *buffer) {
|
|
||||||
return str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, buffer[10], buffer[11], buffer[12], buffer[13], buffer[14],
|
|
||||||
buffer[15]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline std::string format_version(uint8_t *buffer) {
|
static inline std::string format_version(uint8_t *buffer) {
|
||||||
return str_sprintf("%u.%02X.%02X%02X%02X%02X", buffer[13], buffer[12], buffer[17], buffer[16], buffer[15],
|
return str_sprintf("%u.%02X.%02X%02X%02X%02X", buffer[13], buffer[12], buffer[17], buffer[16], buffer[15],
|
||||||
buffer[14]);
|
buffer[14]);
|
||||||
@@ -120,7 +117,7 @@ void LD2450Component::setup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LD2450Component::dump_config() {
|
void LD2450Component::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG, "HLK-LD2450 Human motion tracking radar module:");
|
ESP_LOGCONFIG(TAG, "LD2450:");
|
||||||
#ifdef USE_BINARY_SENSOR
|
#ifdef USE_BINARY_SENSOR
|
||||||
LOG_BINARY_SENSOR(" ", "TargetBinarySensor", this->target_binary_sensor_);
|
LOG_BINARY_SENSOR(" ", "TargetBinarySensor", this->target_binary_sensor_);
|
||||||
LOG_BINARY_SENSOR(" ", "MovingTargetBinarySensor", this->moving_target_binary_sensor_);
|
LOG_BINARY_SENSOR(" ", "MovingTargetBinarySensor", this->moving_target_binary_sensor_);
|
||||||
@@ -189,9 +186,9 @@ void LD2450Component::dump_config() {
|
|||||||
LOG_NUMBER(" ", "PresenceTimeoutNumber", this->presence_timeout_number_);
|
LOG_NUMBER(" ", "PresenceTimeoutNumber", this->presence_timeout_number_);
|
||||||
#endif
|
#endif
|
||||||
ESP_LOGCONFIG(TAG,
|
ESP_LOGCONFIG(TAG,
|
||||||
" Throttle : %ums\n"
|
" Throttle: %ums\n"
|
||||||
" MAC Address : %s\n"
|
" MAC Address: %s\n"
|
||||||
" Firmware version : %s",
|
" Firmware version: %s",
|
||||||
this->throttle_, const_cast<char *>(this->mac_.c_str()), const_cast<char *>(this->version_.c_str()));
|
this->throttle_, const_cast<char *>(this->mac_.c_str()), const_cast<char *>(this->version_.c_str()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,8 +263,7 @@ bool LD2450Component::get_timeout_status_(uint32_t check_millis) {
|
|||||||
if (this->timeout_ == 0) {
|
if (this->timeout_ == 0) {
|
||||||
this->timeout_ = ld2450::convert_seconds_to_ms(DEFAULT_PRESENCE_TIMEOUT);
|
this->timeout_ = ld2450::convert_seconds_to_ms(DEFAULT_PRESENCE_TIMEOUT);
|
||||||
}
|
}
|
||||||
auto current_millis = millis();
|
return App.get_loop_component_start_time() - check_millis >= this->timeout_;
|
||||||
return current_millis - check_millis >= this->timeout_;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract, store and publish zone details LD2450 buffer
|
// Extract, store and publish zone details LD2450 buffer
|
||||||
@@ -354,25 +350,24 @@ void LD2450Component::send_command_(uint8_t command, const uint8_t *command_valu
|
|||||||
// Header Target 1 Target 2 Target 3 End
|
// Header Target 1 Target 2 Target 3 End
|
||||||
void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) {
|
void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) {
|
||||||
if (len < 29) { // header (4 bytes) + 8 x 3 target data + footer (2 bytes)
|
if (len < 29) { // header (4 bytes) + 8 x 3 target data + footer (2 bytes)
|
||||||
ESP_LOGE(TAG, "Periodic data: invalid message length");
|
ESP_LOGE(TAG, "Invalid message length");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (buffer[0] != 0xAA || buffer[1] != 0xFF || buffer[2] != 0x03 || buffer[3] != 0x00) { // header
|
if (buffer[0] != 0xAA || buffer[1] != 0xFF || buffer[2] != 0x03 || buffer[3] != 0x00) { // header
|
||||||
ESP_LOGE(TAG, "Periodic data: invalid message header");
|
ESP_LOGE(TAG, "Invalid message header");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (buffer[len - 2] != 0x55 || buffer[len - 1] != 0xCC) { // footer
|
if (buffer[len - 2] != 0x55 || buffer[len - 1] != 0xCC) { // footer
|
||||||
ESP_LOGE(TAG, "Periodic data: invalid message footer");
|
ESP_LOGE(TAG, "Invalid message footer");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto current_millis = millis();
|
if (App.get_loop_component_start_time() - this->last_periodic_millis_ < this->throttle_) {
|
||||||
if (current_millis - this->last_periodic_millis_ < this->throttle_) {
|
|
||||||
ESP_LOGV(TAG, "Throttling: %d", this->throttle_);
|
ESP_LOGV(TAG, "Throttling: %d", this->throttle_);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this->last_periodic_millis_ = current_millis;
|
this->last_periodic_millis_ = App.get_loop_component_start_time();
|
||||||
|
|
||||||
int16_t target_count = 0;
|
int16_t target_count = 0;
|
||||||
int16_t still_target_count = 0;
|
int16_t still_target_count = 0;
|
||||||
@@ -555,13 +550,13 @@ void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) {
|
|||||||
#ifdef USE_SENSOR
|
#ifdef USE_SENSOR
|
||||||
// For presence timeout check
|
// For presence timeout check
|
||||||
if (target_count > 0) {
|
if (target_count > 0) {
|
||||||
this->presence_millis_ = millis();
|
this->presence_millis_ = App.get_loop_component_start_time();
|
||||||
}
|
}
|
||||||
if (moving_target_count > 0) {
|
if (moving_target_count > 0) {
|
||||||
this->moving_presence_millis_ = millis();
|
this->moving_presence_millis_ = App.get_loop_component_start_time();
|
||||||
}
|
}
|
||||||
if (still_target_count > 0) {
|
if (still_target_count > 0) {
|
||||||
this->still_presence_millis_ = millis();
|
this->still_presence_millis_ = App.get_loop_component_start_time();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@@ -569,31 +564,31 @@ void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) {
|
|||||||
bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) {
|
bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) {
|
||||||
ESP_LOGV(TAG, "Handling ack data for command %02X", buffer[COMMAND]);
|
ESP_LOGV(TAG, "Handling ack data for command %02X", buffer[COMMAND]);
|
||||||
if (len < 10) {
|
if (len < 10) {
|
||||||
ESP_LOGE(TAG, "Ack data: invalid length");
|
ESP_LOGE(TAG, "Invalid ack length");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (buffer[0] != 0xFD || buffer[1] != 0xFC || buffer[2] != 0xFB || buffer[3] != 0xFA) { // frame header
|
if (buffer[0] != 0xFD || buffer[1] != 0xFC || buffer[2] != 0xFB || buffer[3] != 0xFA) { // frame header
|
||||||
ESP_LOGE(TAG, "Ack data: invalid header (command %02X)", buffer[COMMAND]);
|
ESP_LOGE(TAG, "Invalid ack header (command %02X)", buffer[COMMAND]);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (buffer[COMMAND_STATUS] != 0x01) {
|
if (buffer[COMMAND_STATUS] != 0x01) {
|
||||||
ESP_LOGE(TAG, "Ack data: invalid status");
|
ESP_LOGE(TAG, "Invalid ack status");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (buffer[8] || buffer[9]) {
|
if (buffer[8] || buffer[9]) {
|
||||||
ESP_LOGE(TAG, "Ack data: last buffer was %u, %u", buffer[8], buffer[9]);
|
ESP_LOGE(TAG, "Last buffer was %u, %u", buffer[8], buffer[9]);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (buffer[COMMAND]) {
|
switch (buffer[COMMAND]) {
|
||||||
case lowbyte(CMD_ENABLE_CONF):
|
case lowbyte(CMD_ENABLE_CONF):
|
||||||
ESP_LOGV(TAG, "Got enable conf command");
|
ESP_LOGV(TAG, "Enable conf command");
|
||||||
break;
|
break;
|
||||||
case lowbyte(CMD_DISABLE_CONF):
|
case lowbyte(CMD_DISABLE_CONF):
|
||||||
ESP_LOGV(TAG, "Got disable conf command");
|
ESP_LOGV(TAG, "Disable conf command");
|
||||||
break;
|
break;
|
||||||
case lowbyte(CMD_SET_BAUD_RATE):
|
case lowbyte(CMD_SET_BAUD_RATE):
|
||||||
ESP_LOGV(TAG, "Got baud rate change command");
|
ESP_LOGV(TAG, "Baud rate change command");
|
||||||
#ifdef USE_SELECT
|
#ifdef USE_SELECT
|
||||||
if (this->baud_rate_select_ != nullptr) {
|
if (this->baud_rate_select_ != nullptr) {
|
||||||
ESP_LOGV(TAG, "Change baud rate to %s", this->baud_rate_select_->state.c_str());
|
ESP_LOGV(TAG, "Change baud rate to %s", this->baud_rate_select_->state.c_str());
|
||||||
@@ -613,7 +608,7 @@ bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) {
|
|||||||
if (len < 20) {
|
if (len < 20) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
this->mac_ = ld2450::format_mac(buffer);
|
this->mac_ = format_mac_address_pretty(&buffer[10]);
|
||||||
ESP_LOGV(TAG, "MAC address: %s", this->mac_.c_str());
|
ESP_LOGV(TAG, "MAC address: %s", this->mac_.c_str());
|
||||||
#ifdef USE_TEXT_SENSOR
|
#ifdef USE_TEXT_SENSOR
|
||||||
if (this->mac_text_sensor_ != nullptr) {
|
if (this->mac_text_sensor_ != nullptr) {
|
||||||
@@ -622,15 +617,15 @@ bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) {
|
|||||||
#endif
|
#endif
|
||||||
#ifdef USE_SWITCH
|
#ifdef USE_SWITCH
|
||||||
if (this->bluetooth_switch_ != nullptr) {
|
if (this->bluetooth_switch_ != nullptr) {
|
||||||
this->bluetooth_switch_->publish_state(this->mac_ != NO_MAC);
|
this->bluetooth_switch_->publish_state(this->mac_ != UNKNOWN_MAC);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
case lowbyte(CMD_BLUETOOTH):
|
case lowbyte(CMD_BLUETOOTH):
|
||||||
ESP_LOGV(TAG, "Got Bluetooth command");
|
ESP_LOGV(TAG, "Bluetooth command");
|
||||||
break;
|
break;
|
||||||
case lowbyte(CMD_SINGLE_TARGET_MODE):
|
case lowbyte(CMD_SINGLE_TARGET_MODE):
|
||||||
ESP_LOGV(TAG, "Got single target conf command");
|
ESP_LOGV(TAG, "Single target conf command");
|
||||||
#ifdef USE_SWITCH
|
#ifdef USE_SWITCH
|
||||||
if (this->multi_target_switch_ != nullptr) {
|
if (this->multi_target_switch_ != nullptr) {
|
||||||
this->multi_target_switch_->publish_state(false);
|
this->multi_target_switch_->publish_state(false);
|
||||||
@@ -638,7 +633,7 @@ bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) {
|
|||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
case lowbyte(CMD_MULTI_TARGET_MODE):
|
case lowbyte(CMD_MULTI_TARGET_MODE):
|
||||||
ESP_LOGV(TAG, "Got multi target conf command");
|
ESP_LOGV(TAG, "Multi target conf command");
|
||||||
#ifdef USE_SWITCH
|
#ifdef USE_SWITCH
|
||||||
if (this->multi_target_switch_ != nullptr) {
|
if (this->multi_target_switch_ != nullptr) {
|
||||||
this->multi_target_switch_->publish_state(true);
|
this->multi_target_switch_->publish_state(true);
|
||||||
@@ -646,7 +641,7 @@ bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) {
|
|||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
case lowbyte(CMD_QUERY_TARGET_MODE):
|
case lowbyte(CMD_QUERY_TARGET_MODE):
|
||||||
ESP_LOGV(TAG, "Got query target tracking mode command");
|
ESP_LOGV(TAG, "Query target tracking mode command");
|
||||||
#ifdef USE_SWITCH
|
#ifdef USE_SWITCH
|
||||||
if (this->multi_target_switch_ != nullptr) {
|
if (this->multi_target_switch_ != nullptr) {
|
||||||
this->multi_target_switch_->publish_state(buffer[10] == 0x02);
|
this->multi_target_switch_->publish_state(buffer[10] == 0x02);
|
||||||
@@ -654,7 +649,7 @@ bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) {
|
|||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
case lowbyte(CMD_QUERY_ZONE):
|
case lowbyte(CMD_QUERY_ZONE):
|
||||||
ESP_LOGV(TAG, "Got query zone conf command");
|
ESP_LOGV(TAG, "Query zone conf command");
|
||||||
this->zone_type_ = std::stoi(std::to_string(buffer[10]), nullptr, 16);
|
this->zone_type_ = std::stoi(std::to_string(buffer[10]), nullptr, 16);
|
||||||
this->publish_zone_type();
|
this->publish_zone_type();
|
||||||
#ifdef USE_SELECT
|
#ifdef USE_SELECT
|
||||||
@@ -674,7 +669,7 @@ bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) {
|
|||||||
this->process_zone_(buffer);
|
this->process_zone_(buffer);
|
||||||
break;
|
break;
|
||||||
case lowbyte(CMD_SET_ZONE):
|
case lowbyte(CMD_SET_ZONE):
|
||||||
ESP_LOGV(TAG, "Got set zone conf command");
|
ESP_LOGV(TAG, "Set zone conf command");
|
||||||
this->query_zone_info();
|
this->query_zone_info();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -48,6 +48,11 @@ void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const ch
|
|||||||
// For non-main tasks, queue the message for callbacks - but only if we have any callbacks registered
|
// For non-main tasks, queue the message for callbacks - but only if we have any callbacks registered
|
||||||
message_sent =
|
message_sent =
|
||||||
this->log_buffer_->send_message_thread_safe(level, tag, static_cast<uint16_t>(line), current_task, format, args);
|
this->log_buffer_->send_message_thread_safe(level, tag, static_cast<uint16_t>(line), current_task, format, args);
|
||||||
|
if (message_sent) {
|
||||||
|
// Enable logger loop to process the buffered message
|
||||||
|
// This is safe to call from any context including ISRs
|
||||||
|
this->enable_loop_soon_any_context();
|
||||||
|
}
|
||||||
#endif // USE_ESPHOME_TASK_LOG_BUFFER
|
#endif // USE_ESPHOME_TASK_LOG_BUFFER
|
||||||
|
|
||||||
// Emergency console logging for non-main tasks when ring buffer is full or disabled
|
// Emergency console logging for non-main tasks when ring buffer is full or disabled
|
||||||
@@ -139,6 +144,10 @@ Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size) : baud_rate_(baud_rate
|
|||||||
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
|
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
|
||||||
void Logger::init_log_buffer(size_t total_buffer_size) {
|
void Logger::init_log_buffer(size_t total_buffer_size) {
|
||||||
this->log_buffer_ = esphome::make_unique<logger::TaskLogBuffer>(total_buffer_size);
|
this->log_buffer_ = esphome::make_unique<logger::TaskLogBuffer>(total_buffer_size);
|
||||||
|
|
||||||
|
// Start with loop disabled when using task buffer (unless using USB CDC)
|
||||||
|
// The loop will be enabled automatically when messages arrive
|
||||||
|
this->disable_loop_when_buffer_empty_();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -189,6 +198,10 @@ void Logger::loop() {
|
|||||||
this->write_msg_(this->tx_buffer_);
|
this->write_msg_(this->tx_buffer_);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// No messages to process, disable loop if appropriate
|
||||||
|
// This reduces overhead when there's no async logging activity
|
||||||
|
this->disable_loop_when_buffer_empty_();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -358,6 +358,26 @@ class Logger : public Component {
|
|||||||
static const uint16_t RESET_COLOR_LEN = strlen(ESPHOME_LOG_RESET_COLOR);
|
static const uint16_t RESET_COLOR_LEN = strlen(ESPHOME_LOG_RESET_COLOR);
|
||||||
this->write_body_to_buffer_(ESPHOME_LOG_RESET_COLOR, RESET_COLOR_LEN, buffer, buffer_at, buffer_size);
|
this->write_body_to_buffer_(ESPHOME_LOG_RESET_COLOR, RESET_COLOR_LEN, buffer, buffer_at, buffer_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef USE_ESP32
|
||||||
|
// Disable loop when task buffer is empty (with USB CDC check)
|
||||||
|
inline void disable_loop_when_buffer_empty_() {
|
||||||
|
// Thread safety note: This is safe even if another task calls enable_loop_soon_any_context()
|
||||||
|
// concurrently. If that happens between our check and disable_loop(), the enable request
|
||||||
|
// will be processed on the next main loop iteration since:
|
||||||
|
// - disable_loop() takes effect immediately
|
||||||
|
// - enable_loop_soon_any_context() sets a pending flag that's checked at loop start
|
||||||
|
#if defined(USE_LOGGER_USB_CDC) && defined(USE_ARDUINO)
|
||||||
|
// Only disable if not using USB CDC (which needs loop for connection detection)
|
||||||
|
if (this->uart_ != UART_SELECTION_USB_CDC) {
|
||||||
|
this->disable_loop();
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
// No USB CDC support, always safe to disable
|
||||||
|
this->disable_loop();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
extern Logger *global_logger; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
extern Logger *global_logger; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||||
|
|
||||||
|
|||||||
@@ -466,7 +466,7 @@ LVGL_SCHEMA = cv.All(
|
|||||||
): lvalid.lv_color,
|
): lvalid.lv_color,
|
||||||
cv.Optional(df.CONF_THEME): cv.Schema(
|
cv.Optional(df.CONF_THEME): cv.Schema(
|
||||||
{
|
{
|
||||||
cv.Optional(name): obj_schema(w)
|
cv.Optional(name): obj_schema(w).extend(FULL_STYLE_SCHEMA)
|
||||||
for name, w in WIDGET_TYPES.items()
|
for name, w in WIDGET_TYPES.items()
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ from esphome.core.config import StartupTrigger
|
|||||||
from esphome.schema_extractors import SCHEMA_EXTRACT
|
from esphome.schema_extractors import SCHEMA_EXTRACT
|
||||||
|
|
||||||
from . import defines as df, lv_validation as lvalid
|
from . import defines as df, lv_validation as lvalid
|
||||||
from .defines import CONF_TIME_FORMAT, LV_GRAD_DIR
|
from .defines import CONF_TIME_FORMAT, LV_GRAD_DIR, TYPE_GRID
|
||||||
from .helpers import add_lv_use, requires_component, validate_printf
|
from .helpers import add_lv_use, requires_component, validate_printf
|
||||||
from .lv_validation import lv_color, lv_font, lv_gradient, lv_image, opacity
|
from .lv_validation import lv_color, lv_font, lv_gradient, lv_image, opacity
|
||||||
from .lvcode import LvglComponent, lv_event_t_ptr
|
from .lvcode import LvglComponent, lv_event_t_ptr
|
||||||
@@ -349,7 +349,60 @@ def obj_schema(widget_type: WidgetType):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_grid_layout(config):
|
||||||
|
layout = config[df.CONF_LAYOUT]
|
||||||
|
rows = len(layout[df.CONF_GRID_ROWS])
|
||||||
|
columns = len(layout[df.CONF_GRID_COLUMNS])
|
||||||
|
used_cells = [[None] * columns for _ in range(rows)]
|
||||||
|
for index, widget in enumerate(config[df.CONF_WIDGETS]):
|
||||||
|
_, w = next(iter(widget.items()))
|
||||||
|
if (df.CONF_GRID_CELL_COLUMN_POS in w) != (df.CONF_GRID_CELL_ROW_POS in w):
|
||||||
|
# pylint: disable=raise-missing-from
|
||||||
|
raise cv.Invalid(
|
||||||
|
"Both row and column positions must be specified, or both omitted",
|
||||||
|
[df.CONF_WIDGETS, index],
|
||||||
|
)
|
||||||
|
if df.CONF_GRID_CELL_ROW_POS in w:
|
||||||
|
row = w[df.CONF_GRID_CELL_ROW_POS]
|
||||||
|
column = w[df.CONF_GRID_CELL_COLUMN_POS]
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
row, column = next(
|
||||||
|
(r_idx, c_idx)
|
||||||
|
for r_idx, row in enumerate(used_cells)
|
||||||
|
for c_idx, value in enumerate(row)
|
||||||
|
if value is None
|
||||||
|
)
|
||||||
|
except StopIteration:
|
||||||
|
# pylint: disable=raise-missing-from
|
||||||
|
raise cv.Invalid(
|
||||||
|
"No free cells available in grid layout", [df.CONF_WIDGETS, index]
|
||||||
|
)
|
||||||
|
w[df.CONF_GRID_CELL_ROW_POS] = row
|
||||||
|
w[df.CONF_GRID_CELL_COLUMN_POS] = column
|
||||||
|
|
||||||
|
for i in range(w[df.CONF_GRID_CELL_ROW_SPAN]):
|
||||||
|
for j in range(w[df.CONF_GRID_CELL_COLUMN_SPAN]):
|
||||||
|
if row + i >= rows or column + j >= columns:
|
||||||
|
# pylint: disable=raise-missing-from
|
||||||
|
raise cv.Invalid(
|
||||||
|
f"Cell at {row}/{column} span {w[df.CONF_GRID_CELL_ROW_SPAN]}x{w[df.CONF_GRID_CELL_COLUMN_SPAN]} "
|
||||||
|
f"exceeds grid size {rows}x{columns}",
|
||||||
|
[df.CONF_WIDGETS, index],
|
||||||
|
)
|
||||||
|
if used_cells[row + i][column + j] is not None:
|
||||||
|
# pylint: disable=raise-missing-from
|
||||||
|
raise cv.Invalid(
|
||||||
|
f"Cell span {row + i}/{column + j} already occupied by widget at index {used_cells[row + i][column + j]}",
|
||||||
|
[df.CONF_WIDGETS, index],
|
||||||
|
)
|
||||||
|
used_cells[row + i][column + j] = index
|
||||||
|
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
LAYOUT_SCHEMAS = {}
|
LAYOUT_SCHEMAS = {}
|
||||||
|
LAYOUT_VALIDATORS = {TYPE_GRID: _validate_grid_layout}
|
||||||
|
|
||||||
ALIGN_TO_SCHEMA = {
|
ALIGN_TO_SCHEMA = {
|
||||||
cv.Optional(df.CONF_ALIGN_TO): cv.Schema(
|
cv.Optional(df.CONF_ALIGN_TO): cv.Schema(
|
||||||
@@ -402,8 +455,8 @@ LAYOUT_SCHEMA = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
GRID_CELL_SCHEMA = {
|
GRID_CELL_SCHEMA = {
|
||||||
cv.Required(df.CONF_GRID_CELL_ROW_POS): cv.positive_int,
|
cv.Optional(df.CONF_GRID_CELL_ROW_POS): cv.positive_int,
|
||||||
cv.Required(df.CONF_GRID_CELL_COLUMN_POS): cv.positive_int,
|
cv.Optional(df.CONF_GRID_CELL_COLUMN_POS): cv.positive_int,
|
||||||
cv.Optional(df.CONF_GRID_CELL_ROW_SPAN, default=1): cv.positive_int,
|
cv.Optional(df.CONF_GRID_CELL_ROW_SPAN, default=1): cv.positive_int,
|
||||||
cv.Optional(df.CONF_GRID_CELL_COLUMN_SPAN, default=1): cv.positive_int,
|
cv.Optional(df.CONF_GRID_CELL_COLUMN_SPAN, default=1): cv.positive_int,
|
||||||
cv.Optional(df.CONF_GRID_CELL_X_ALIGN): grid_alignments,
|
cv.Optional(df.CONF_GRID_CELL_X_ALIGN): grid_alignments,
|
||||||
@@ -474,7 +527,10 @@ def container_validator(schema, widget_type: WidgetType):
|
|||||||
result = result.extend(
|
result = result.extend(
|
||||||
LAYOUT_SCHEMAS.get(ltype.lower(), LAYOUT_SCHEMAS[df.TYPE_NONE])
|
LAYOUT_SCHEMAS.get(ltype.lower(), LAYOUT_SCHEMAS[df.TYPE_NONE])
|
||||||
)
|
)
|
||||||
return result(value)
|
value = result(value)
|
||||||
|
if layout_validator := LAYOUT_VALIDATORS.get(ltype):
|
||||||
|
value = layout_validator(value)
|
||||||
|
return value
|
||||||
|
|
||||||
return validator
|
return validator
|
||||||
|
|
||||||
|
|||||||
@@ -64,6 +64,14 @@ class ModbusDevice {
|
|||||||
this->parent_->send(this->address_, function, start_address, number_of_entities, payload_len, payload);
|
this->parent_->send(this->address_, function, start_address, number_of_entities, payload_len, payload);
|
||||||
}
|
}
|
||||||
void send_raw(const std::vector<uint8_t> &payload) { this->parent_->send_raw(payload); }
|
void send_raw(const std::vector<uint8_t> &payload) { this->parent_->send_raw(payload); }
|
||||||
|
void send_error(uint8_t function_code, uint8_t exception_code) {
|
||||||
|
std::vector<uint8_t> error_response;
|
||||||
|
error_response.reserve(3);
|
||||||
|
error_response.push_back(this->address_);
|
||||||
|
error_response.push_back(function_code | 0x80);
|
||||||
|
error_response.push_back(exception_code);
|
||||||
|
this->send_raw(error_response);
|
||||||
|
}
|
||||||
// If more than one device is connected block sending a new command before a response is received
|
// If more than one device is connected block sending a new command before a response is received
|
||||||
bool waiting_for_response() { return parent_->waiting_for_response != 0; }
|
bool waiting_for_response() { return parent_->waiting_for_response != 0; }
|
||||||
|
|
||||||
|
|||||||
@@ -112,6 +112,22 @@ TYPE_REGISTER_MAP = {
|
|||||||
"FP32_R": 2,
|
"FP32_R": 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CPP_TYPE_REGISTER_MAP = {
|
||||||
|
"RAW": cg.uint16,
|
||||||
|
"U_WORD": cg.uint16,
|
||||||
|
"S_WORD": cg.int16,
|
||||||
|
"U_DWORD": cg.uint32,
|
||||||
|
"U_DWORD_R": cg.uint32,
|
||||||
|
"S_DWORD": cg.int32,
|
||||||
|
"S_DWORD_R": cg.int32,
|
||||||
|
"U_QWORD": cg.uint64,
|
||||||
|
"U_QWORD_R": cg.uint64,
|
||||||
|
"S_QWORD": cg.int64,
|
||||||
|
"S_QWORD_R": cg.int64,
|
||||||
|
"FP32": cg.float_,
|
||||||
|
"FP32_R": cg.float_,
|
||||||
|
}
|
||||||
|
|
||||||
ModbusCommandSentTrigger = modbus_controller_ns.class_(
|
ModbusCommandSentTrigger = modbus_controller_ns.class_(
|
||||||
"ModbusCommandSentTrigger", automation.Trigger.template(cg.int_, cg.int_)
|
"ModbusCommandSentTrigger", automation.Trigger.template(cg.int_, cg.int_)
|
||||||
)
|
)
|
||||||
@@ -285,21 +301,24 @@ async def to_code(config):
|
|||||||
cg.add(var.set_offline_skip_updates(config[CONF_OFFLINE_SKIP_UPDATES]))
|
cg.add(var.set_offline_skip_updates(config[CONF_OFFLINE_SKIP_UPDATES]))
|
||||||
if CONF_SERVER_REGISTERS in config:
|
if CONF_SERVER_REGISTERS in config:
|
||||||
for server_register in config[CONF_SERVER_REGISTERS]:
|
for server_register in config[CONF_SERVER_REGISTERS]:
|
||||||
|
server_register_var = cg.new_Pvariable(
|
||||||
|
server_register[CONF_ID],
|
||||||
|
server_register[CONF_ADDRESS],
|
||||||
|
server_register[CONF_VALUE_TYPE],
|
||||||
|
TYPE_REGISTER_MAP[server_register[CONF_VALUE_TYPE]],
|
||||||
|
)
|
||||||
|
cpp_type = CPP_TYPE_REGISTER_MAP[server_register[CONF_VALUE_TYPE]]
|
||||||
cg.add(
|
cg.add(
|
||||||
var.add_server_register(
|
server_register_var.set_read_lambda(
|
||||||
cg.new_Pvariable(
|
cg.TemplateArguments(cpp_type),
|
||||||
server_register[CONF_ID],
|
await cg.process_lambda(
|
||||||
server_register[CONF_ADDRESS],
|
server_register[CONF_READ_LAMBDA],
|
||||||
server_register[CONF_VALUE_TYPE],
|
[(cg.uint16, "address")],
|
||||||
TYPE_REGISTER_MAP[server_register[CONF_VALUE_TYPE]],
|
return_type=cpp_type,
|
||||||
await cg.process_lambda(
|
),
|
||||||
server_register[CONF_READ_LAMBDA],
|
|
||||||
[],
|
|
||||||
return_type=cg.float_,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
cg.add(var.add_server_register(server_register_var))
|
||||||
await register_modbus_device(var, config)
|
await register_modbus_device(var, config)
|
||||||
for conf in config.get(CONF_ON_COMMAND_SENT, []):
|
for conf in config.get(CONF_ON_COMMAND_SENT, []):
|
||||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||||
|
|||||||
@@ -117,12 +117,17 @@ void ModbusController::on_modbus_read_registers(uint8_t function_code, uint16_t
|
|||||||
bool found = false;
|
bool found = false;
|
||||||
for (auto *server_register : this->server_registers_) {
|
for (auto *server_register : this->server_registers_) {
|
||||||
if (server_register->address == current_address) {
|
if (server_register->address == current_address) {
|
||||||
float value = server_register->read_lambda();
|
if (!server_register->read_lambda) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
int64_t value = server_register->read_lambda();
|
||||||
|
ESP_LOGD(TAG, "Matched register. Address: 0x%02X. Value type: %zu. Register count: %u. Value: %s.",
|
||||||
|
server_register->address, static_cast<size_t>(server_register->value_type),
|
||||||
|
server_register->register_count, server_register->format_value(value).c_str());
|
||||||
|
|
||||||
ESP_LOGD(TAG, "Matched register. Address: 0x%02X. Value type: %zu. Register count: %u. Value: %0.1f.",
|
std::vector<uint16_t> payload;
|
||||||
server_register->address, static_cast<uint8_t>(server_register->value_type),
|
payload.reserve(server_register->register_count * 2);
|
||||||
server_register->register_count, value);
|
number_to_payload(payload, value, server_register->value_type);
|
||||||
std::vector<uint16_t> payload = float_to_payload(value, server_register->value_type);
|
|
||||||
sixteen_bit_response.insert(sixteen_bit_response.end(), payload.cbegin(), payload.cend());
|
sixteen_bit_response.insert(sixteen_bit_response.end(), payload.cbegin(), payload.cend());
|
||||||
current_address += server_register->register_count;
|
current_address += server_register->register_count;
|
||||||
found = true;
|
found = true;
|
||||||
@@ -132,11 +137,7 @@ void ModbusController::on_modbus_read_registers(uint8_t function_code, uint16_t
|
|||||||
|
|
||||||
if (!found) {
|
if (!found) {
|
||||||
ESP_LOGW(TAG, "Could not match any register to address %02X. Sending exception response.", current_address);
|
ESP_LOGW(TAG, "Could not match any register to address %02X. Sending exception response.", current_address);
|
||||||
std::vector<uint8_t> error_response;
|
send_error(function_code, 0x02);
|
||||||
error_response.push_back(this->address_);
|
|
||||||
error_response.push_back(0x81);
|
|
||||||
error_response.push_back(0x02);
|
|
||||||
this->send_raw(error_response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,6 +63,10 @@ enum class SensorValueType : uint8_t {
|
|||||||
FP32_R = 0xD
|
FP32_R = 0xD
|
||||||
};
|
};
|
||||||
|
|
||||||
|
inline bool value_type_is_float(SensorValueType v) {
|
||||||
|
return v == SensorValueType::FP32 || v == SensorValueType::FP32_R;
|
||||||
|
}
|
||||||
|
|
||||||
inline ModbusFunctionCode modbus_register_read_function(ModbusRegisterType reg_type) {
|
inline ModbusFunctionCode modbus_register_read_function(ModbusRegisterType reg_type) {
|
||||||
switch (reg_type) {
|
switch (reg_type) {
|
||||||
case ModbusRegisterType::COIL:
|
case ModbusRegisterType::COIL:
|
||||||
@@ -253,18 +257,53 @@ class SensorItem {
|
|||||||
};
|
};
|
||||||
|
|
||||||
class ServerRegister {
|
class ServerRegister {
|
||||||
|
using ReadLambda = std::function<int64_t()>;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ServerRegister(uint16_t address, SensorValueType value_type, uint8_t register_count,
|
ServerRegister(uint16_t address, SensorValueType value_type, uint8_t register_count) {
|
||||||
std::function<float()> read_lambda) {
|
|
||||||
this->address = address;
|
this->address = address;
|
||||||
this->value_type = value_type;
|
this->value_type = value_type;
|
||||||
this->register_count = register_count;
|
this->register_count = register_count;
|
||||||
this->read_lambda = std::move(read_lambda);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<typename T> void set_read_lambda(const std::function<T(uint16_t address)> &&user_read_lambda) {
|
||||||
|
this->read_lambda = [this, user_read_lambda]() -> int64_t {
|
||||||
|
T user_value = user_read_lambda(this->address);
|
||||||
|
if constexpr (std::is_same_v<T, float>) {
|
||||||
|
return bit_cast<uint32_t>(user_value);
|
||||||
|
} else {
|
||||||
|
return static_cast<int64_t>(user_value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Formats a raw value into a string representation based on the value type for debugging
|
||||||
|
std::string format_value(int64_t value) const {
|
||||||
|
switch (this->value_type) {
|
||||||
|
case SensorValueType::U_WORD:
|
||||||
|
case SensorValueType::U_DWORD:
|
||||||
|
case SensorValueType::U_DWORD_R:
|
||||||
|
case SensorValueType::U_QWORD:
|
||||||
|
case SensorValueType::U_QWORD_R:
|
||||||
|
return std::to_string(static_cast<uint64_t>(value));
|
||||||
|
case SensorValueType::S_WORD:
|
||||||
|
case SensorValueType::S_DWORD:
|
||||||
|
case SensorValueType::S_DWORD_R:
|
||||||
|
case SensorValueType::S_QWORD:
|
||||||
|
case SensorValueType::S_QWORD_R:
|
||||||
|
return std::to_string(value);
|
||||||
|
case SensorValueType::FP32_R:
|
||||||
|
case SensorValueType::FP32:
|
||||||
|
return str_sprintf("%.1f", bit_cast<float>(static_cast<uint32_t>(value)));
|
||||||
|
default:
|
||||||
|
return std::to_string(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
uint16_t address{0};
|
uint16_t address{0};
|
||||||
SensorValueType value_type{SensorValueType::RAW};
|
SensorValueType value_type{SensorValueType::RAW};
|
||||||
uint8_t register_count{0};
|
uint8_t register_count{0};
|
||||||
std::function<float()> read_lambda;
|
ReadLambda read_lambda;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ModbusController::create_register_ranges_ tries to optimize register range
|
// ModbusController::create_register_ranges_ tries to optimize register range
|
||||||
@@ -444,7 +483,7 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice {
|
|||||||
void on_modbus_data(const std::vector<uint8_t> &data) override;
|
void on_modbus_data(const std::vector<uint8_t> &data) override;
|
||||||
/// called when a modbus error response was received
|
/// called when a modbus error response was received
|
||||||
void on_modbus_error(uint8_t function_code, uint8_t exception_code) override;
|
void on_modbus_error(uint8_t function_code, uint8_t exception_code) override;
|
||||||
/// called when a modbus request (function code 3 or 4) was parsed without errors
|
/// called when a modbus request (function code 0x03 or 0x04) was parsed without errors
|
||||||
void on_modbus_read_registers(uint8_t function_code, uint16_t start_address, uint16_t number_of_registers) final;
|
void on_modbus_read_registers(uint8_t function_code, uint16_t start_address, uint16_t number_of_registers) final;
|
||||||
/// default delegate called by process_modbus_data when a response has retrieved from the incoming queue
|
/// default delegate called by process_modbus_data when a response has retrieved from the incoming queue
|
||||||
void on_register_data(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data);
|
void on_register_data(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data);
|
||||||
@@ -529,7 +568,7 @@ inline float payload_to_float(const std::vector<uint8_t> &data, const SensorItem
|
|||||||
int64_t number = payload_to_number(data, item.sensor_value_type, item.offset, item.bitmask);
|
int64_t number = payload_to_number(data, item.sensor_value_type, item.offset, item.bitmask);
|
||||||
|
|
||||||
float float_value;
|
float float_value;
|
||||||
if (item.sensor_value_type == SensorValueType::FP32 || item.sensor_value_type == SensorValueType::FP32_R) {
|
if (value_type_is_float(item.sensor_value_type)) {
|
||||||
float_value = bit_cast<float>(static_cast<uint32_t>(number));
|
float_value = bit_cast<float>(static_cast<uint32_t>(number));
|
||||||
} else {
|
} else {
|
||||||
float_value = static_cast<float>(number);
|
float_value = static_cast<float>(number);
|
||||||
@@ -541,7 +580,7 @@ inline float payload_to_float(const std::vector<uint8_t> &data, const SensorItem
|
|||||||
inline std::vector<uint16_t> float_to_payload(float value, SensorValueType value_type) {
|
inline std::vector<uint16_t> float_to_payload(float value, SensorValueType value_type) {
|
||||||
int64_t val;
|
int64_t val;
|
||||||
|
|
||||||
if (value_type == SensorValueType::FP32 || value_type == SensorValueType::FP32_R) {
|
if (value_type_is_float(value_type)) {
|
||||||
val = bit_cast<uint32_t>(value);
|
val = bit_cast<uint32_t>(value);
|
||||||
} else {
|
} else {
|
||||||
val = llroundf(value);
|
val = llroundf(value);
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ enum class MQTTClientDisconnectReason : int8_t {
|
|||||||
MQTT_MALFORMED_CREDENTIALS = 4,
|
MQTT_MALFORMED_CREDENTIALS = 4,
|
||||||
MQTT_NOT_AUTHORIZED = 5,
|
MQTT_NOT_AUTHORIZED = 5,
|
||||||
ESP8266_NOT_ENOUGH_SPACE = 6,
|
ESP8266_NOT_ENOUGH_SPACE = 6,
|
||||||
TLS_BAD_FINGERPRINT = 7
|
TLS_BAD_FINGERPRINT = 7,
|
||||||
|
DNS_RESOLVE_ERROR = 8
|
||||||
};
|
};
|
||||||
|
|
||||||
/// internal struct for MQTT messages.
|
/// internal struct for MQTT messages.
|
||||||
|
|||||||
@@ -229,6 +229,8 @@ void MQTTClientComponent::check_dnslookup_() {
|
|||||||
if (this->dns_resolve_error_) {
|
if (this->dns_resolve_error_) {
|
||||||
ESP_LOGW(TAG, "Couldn't resolve IP address for '%s'", this->credentials_.address.c_str());
|
ESP_LOGW(TAG, "Couldn't resolve IP address for '%s'", this->credentials_.address.c_str());
|
||||||
this->state_ = MQTT_CLIENT_DISCONNECTED;
|
this->state_ = MQTT_CLIENT_DISCONNECTED;
|
||||||
|
this->disconnect_reason_ = MQTTClientDisconnectReason::DNS_RESOLVE_ERROR;
|
||||||
|
this->on_disconnect_.call(MQTTClientDisconnectReason::DNS_RESOLVE_ERROR);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -698,7 +700,9 @@ void MQTTClientComponent::set_on_connect(mqtt_on_connect_callback_t &&callback)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MQTTClientComponent::set_on_disconnect(mqtt_on_disconnect_callback_t &&callback) {
|
void MQTTClientComponent::set_on_disconnect(mqtt_on_disconnect_callback_t &&callback) {
|
||||||
|
auto callback_copy = callback;
|
||||||
this->mqtt_backend_.set_on_disconnect(std::forward<mqtt_on_disconnect_callback_t>(callback));
|
this->mqtt_backend_.set_on_disconnect(std::forward<mqtt_on_disconnect_callback_t>(callback));
|
||||||
|
this->on_disconnect_.add(std::move(callback_copy));
|
||||||
}
|
}
|
||||||
|
|
||||||
#if ASYNC_TCP_SSL_ENABLED
|
#if ASYNC_TCP_SSL_ENABLED
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include "esphome/components/network/ip_address.h"
|
#include "esphome/components/network/ip_address.h"
|
||||||
#include "esphome/core/automation.h"
|
#include "esphome/core/automation.h"
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#if defined(USE_ESP32)
|
#if defined(USE_ESP32)
|
||||||
#include "mqtt_backend_esp32.h"
|
#include "mqtt_backend_esp32.h"
|
||||||
@@ -334,6 +335,7 @@ class MQTTClientComponent : public Component {
|
|||||||
uint32_t connect_begin_;
|
uint32_t connect_begin_;
|
||||||
uint32_t last_connected_{0};
|
uint32_t last_connected_{0};
|
||||||
optional<MQTTClientDisconnectReason> disconnect_reason_{};
|
optional<MQTTClientDisconnectReason> disconnect_reason_{};
|
||||||
|
CallbackManager<MQTTBackend::on_disconnect_callback_t> on_disconnect_;
|
||||||
|
|
||||||
bool publish_nan_as_none_{false};
|
bool publish_nan_as_none_{false};
|
||||||
bool wait_for_connection_{false};
|
bool wait_for_connection_{false};
|
||||||
|
|||||||
26
esphome/components/runtime_stats/__init__.py
Normal file
26
esphome/components/runtime_stats/__init__.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
"""
|
||||||
|
Runtime statistics component for ESPHome.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
|
||||||
|
DEPENDENCIES = []
|
||||||
|
|
||||||
|
CONF_ENABLED = "enabled"
|
||||||
|
CONF_LOG_INTERVAL = "log_interval"
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.Schema(
|
||||||
|
{
|
||||||
|
cv.Optional(CONF_ENABLED, default=True): cv.boolean,
|
||||||
|
cv.Optional(
|
||||||
|
CONF_LOG_INTERVAL, default=60000
|
||||||
|
): cv.positive_time_period_milliseconds,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
"""Generate code for the runtime statistics component."""
|
||||||
|
cg.add(cg.App.set_runtime_stats_enabled(config[CONF_ENABLED]))
|
||||||
|
cg.add(cg.App.set_runtime_stats_log_interval(config[CONF_LOG_INTERVAL]))
|
||||||
@@ -23,16 +23,22 @@ std::string state_class_to_string(StateClass state_class) {
|
|||||||
Sensor::Sensor() : state(NAN), raw_state(NAN) {}
|
Sensor::Sensor() : state(NAN), raw_state(NAN) {}
|
||||||
|
|
||||||
int8_t Sensor::get_accuracy_decimals() {
|
int8_t Sensor::get_accuracy_decimals() {
|
||||||
if (this->accuracy_decimals_.has_value())
|
if (this->sensor_flags_.has_accuracy_override)
|
||||||
return *this->accuracy_decimals_;
|
return this->accuracy_decimals_;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
void Sensor::set_accuracy_decimals(int8_t accuracy_decimals) { this->accuracy_decimals_ = accuracy_decimals; }
|
void Sensor::set_accuracy_decimals(int8_t accuracy_decimals) {
|
||||||
|
this->accuracy_decimals_ = accuracy_decimals;
|
||||||
|
this->sensor_flags_.has_accuracy_override = true;
|
||||||
|
}
|
||||||
|
|
||||||
void Sensor::set_state_class(StateClass state_class) { this->state_class_ = state_class; }
|
void Sensor::set_state_class(StateClass state_class) {
|
||||||
|
this->state_class_ = state_class;
|
||||||
|
this->sensor_flags_.has_state_class_override = true;
|
||||||
|
}
|
||||||
StateClass Sensor::get_state_class() {
|
StateClass Sensor::get_state_class() {
|
||||||
if (this->state_class_.has_value())
|
if (this->sensor_flags_.has_state_class_override)
|
||||||
return *this->state_class_;
|
return this->state_class_;
|
||||||
return StateClass::STATE_CLASS_NONE;
|
return StateClass::STATE_CLASS_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -80,9 +80,9 @@ class Sensor : public EntityBase, public EntityBase_DeviceClass, public EntityBa
|
|||||||
* state changes to the database when they are published, even if the state is the
|
* state changes to the database when they are published, even if the state is the
|
||||||
* same as before.
|
* same as before.
|
||||||
*/
|
*/
|
||||||
bool get_force_update() const { return force_update_; }
|
bool get_force_update() const { return sensor_flags_.force_update; }
|
||||||
/// Set force update mode.
|
/// Set force update mode.
|
||||||
void set_force_update(bool force_update) { force_update_ = force_update; }
|
void set_force_update(bool force_update) { sensor_flags_.force_update = force_update; }
|
||||||
|
|
||||||
/// Add a filter to the filter chain. Will be appended to the back.
|
/// Add a filter to the filter chain. Will be appended to the back.
|
||||||
void add_filter(Filter *filter);
|
void add_filter(Filter *filter);
|
||||||
@@ -155,9 +155,17 @@ class Sensor : public EntityBase, public EntityBase_DeviceClass, public EntityBa
|
|||||||
|
|
||||||
Filter *filter_list_{nullptr}; ///< Store all active filters.
|
Filter *filter_list_{nullptr}; ///< Store all active filters.
|
||||||
|
|
||||||
optional<int8_t> accuracy_decimals_; ///< Accuracy in decimals override
|
// Group small members together to avoid padding
|
||||||
optional<StateClass> state_class_{STATE_CLASS_NONE}; ///< State class override
|
int8_t accuracy_decimals_{-1}; ///< Accuracy in decimals (-1 = not set)
|
||||||
bool force_update_{false}; ///< Force update mode
|
StateClass state_class_{STATE_CLASS_NONE}; ///< State class (STATE_CLASS_NONE = not set)
|
||||||
|
|
||||||
|
// Bit-packed flags for sensor-specific settings
|
||||||
|
struct SensorFlags {
|
||||||
|
uint8_t has_accuracy_override : 1;
|
||||||
|
uint8_t has_state_class_override : 1;
|
||||||
|
uint8_t force_update : 1;
|
||||||
|
uint8_t reserved : 5; // Reserved for future use
|
||||||
|
} sensor_flags_{};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace sensor
|
} // namespace sensor
|
||||||
|
|||||||
@@ -741,11 +741,6 @@ void WiFiComponent::set_power_save_mode(WiFiPowerSaveMode power_save) { this->po
|
|||||||
|
|
||||||
void WiFiComponent::set_passive_scan(bool passive) { this->passive_scan_ = passive; }
|
void WiFiComponent::set_passive_scan(bool passive) { this->passive_scan_ = passive; }
|
||||||
|
|
||||||
std::string WiFiComponent::format_mac_addr(const uint8_t *mac) {
|
|
||||||
char buf[20];
|
|
||||||
sprintf(buf, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
bool WiFiComponent::is_captive_portal_active_() {
|
bool WiFiComponent::is_captive_portal_active_() {
|
||||||
#ifdef USE_CAPTIVE_PORTAL
|
#ifdef USE_CAPTIVE_PORTAL
|
||||||
return captive_portal::global_captive_portal != nullptr && captive_portal::global_captive_portal->is_active();
|
return captive_portal::global_captive_portal != nullptr && captive_portal::global_captive_portal->is_active();
|
||||||
|
|||||||
@@ -321,8 +321,6 @@ class WiFiComponent : public Component {
|
|||||||
int32_t get_wifi_channel();
|
int32_t get_wifi_channel();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
static std::string format_mac_addr(const uint8_t mac[6]);
|
|
||||||
|
|
||||||
#ifdef USE_WIFI_AP
|
#ifdef USE_WIFI_AP
|
||||||
void setup_ap_config_();
|
void setup_ap_config_();
|
||||||
#endif // USE_WIFI_AP
|
#endif // USE_WIFI_AP
|
||||||
|
|||||||
@@ -550,7 +550,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
|
|||||||
memcpy(buf, it.ssid, it.ssid_len);
|
memcpy(buf, it.ssid, it.ssid_len);
|
||||||
buf[it.ssid_len] = '\0';
|
buf[it.ssid_len] = '\0';
|
||||||
ESP_LOGV(TAG, "Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf,
|
ESP_LOGV(TAG, "Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf,
|
||||||
format_mac_addr(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode));
|
format_mac_address_pretty(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode));
|
||||||
#if USE_NETWORK_IPV6
|
#if USE_NETWORK_IPV6
|
||||||
this->set_timeout(100, [] { WiFi.enableIPv6(); });
|
this->set_timeout(100, [] { WiFi.enableIPv6(); });
|
||||||
#endif /* USE_NETWORK_IPV6 */
|
#endif /* USE_NETWORK_IPV6 */
|
||||||
@@ -566,7 +566,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
|
|||||||
ESP_LOGW(TAG, "Disconnected ssid='%s' reason='Probe Request Unsuccessful'", buf);
|
ESP_LOGW(TAG, "Disconnected ssid='%s' reason='Probe Request Unsuccessful'", buf);
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf,
|
ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf,
|
||||||
format_mac_addr(it.bssid).c_str(), get_disconnect_reason_str(it.reason));
|
format_mac_address_pretty(it.bssid).c_str(), get_disconnect_reason_str(it.reason));
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t reason = it.reason;
|
uint8_t reason = it.reason;
|
||||||
@@ -636,13 +636,13 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
|
|||||||
case ESPHOME_EVENT_ID_WIFI_AP_STACONNECTED: {
|
case ESPHOME_EVENT_ID_WIFI_AP_STACONNECTED: {
|
||||||
auto it = info.wifi_sta_connected;
|
auto it = info.wifi_sta_connected;
|
||||||
auto &mac = it.bssid;
|
auto &mac = it.bssid;
|
||||||
ESP_LOGV(TAG, "AP client connected MAC=%s", format_mac_addr(mac).c_str());
|
ESP_LOGV(TAG, "AP client connected MAC=%s", format_mac_address_pretty(mac).c_str());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ESPHOME_EVENT_ID_WIFI_AP_STADISCONNECTED: {
|
case ESPHOME_EVENT_ID_WIFI_AP_STADISCONNECTED: {
|
||||||
auto it = info.wifi_sta_disconnected;
|
auto it = info.wifi_sta_disconnected;
|
||||||
auto &mac = it.bssid;
|
auto &mac = it.bssid;
|
||||||
ESP_LOGV(TAG, "AP client disconnected MAC=%s", format_mac_addr(mac).c_str());
|
ESP_LOGV(TAG, "AP client disconnected MAC=%s", format_mac_address_pretty(mac).c_str());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ESPHOME_EVENT_ID_WIFI_AP_STAIPASSIGNED: {
|
case ESPHOME_EVENT_ID_WIFI_AP_STAIPASSIGNED: {
|
||||||
@@ -651,7 +651,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
|
|||||||
}
|
}
|
||||||
case ESPHOME_EVENT_ID_WIFI_AP_PROBEREQRECVED: {
|
case ESPHOME_EVENT_ID_WIFI_AP_PROBEREQRECVED: {
|
||||||
auto it = info.wifi_ap_probereqrecved;
|
auto it = info.wifi_ap_probereqrecved;
|
||||||
ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_addr(it.mac).c_str(), it.rssi);
|
ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_address_pretty(it.mac).c_str(), it.rssi);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -496,7 +496,8 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) {
|
|||||||
char buf[33];
|
char buf[33];
|
||||||
memcpy(buf, it.ssid, it.ssid_len);
|
memcpy(buf, it.ssid, it.ssid_len);
|
||||||
buf[it.ssid_len] = '\0';
|
buf[it.ssid_len] = '\0';
|
||||||
ESP_LOGV(TAG, "Connected ssid='%s' bssid=%s channel=%u", buf, format_mac_addr(it.bssid).c_str(), it.channel);
|
ESP_LOGV(TAG, "Connected ssid='%s' bssid=%s channel=%u", buf, format_mac_address_pretty(it.bssid).c_str(),
|
||||||
|
it.channel);
|
||||||
s_sta_connected = true;
|
s_sta_connected = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -510,7 +511,7 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) {
|
|||||||
s_sta_connect_not_found = true;
|
s_sta_connect_not_found = true;
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf,
|
ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf,
|
||||||
format_mac_addr(it.bssid).c_str(), LOG_STR_ARG(get_disconnect_reason_str(it.reason)));
|
format_mac_address_pretty(it.bssid).c_str(), LOG_STR_ARG(get_disconnect_reason_str(it.reason)));
|
||||||
s_sta_connect_error = true;
|
s_sta_connect_error = true;
|
||||||
}
|
}
|
||||||
s_sta_connected = false;
|
s_sta_connected = false;
|
||||||
@@ -545,17 +546,17 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) {
|
|||||||
}
|
}
|
||||||
case EVENT_SOFTAPMODE_STACONNECTED: {
|
case EVENT_SOFTAPMODE_STACONNECTED: {
|
||||||
auto it = event->event_info.sta_connected;
|
auto it = event->event_info.sta_connected;
|
||||||
ESP_LOGV(TAG, "AP client connected MAC=%s aid=%u", format_mac_addr(it.mac).c_str(), it.aid);
|
ESP_LOGV(TAG, "AP client connected MAC=%s aid=%u", format_mac_address_pretty(it.mac).c_str(), it.aid);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case EVENT_SOFTAPMODE_STADISCONNECTED: {
|
case EVENT_SOFTAPMODE_STADISCONNECTED: {
|
||||||
auto it = event->event_info.sta_disconnected;
|
auto it = event->event_info.sta_disconnected;
|
||||||
ESP_LOGV(TAG, "AP client disconnected MAC=%s aid=%u", format_mac_addr(it.mac).c_str(), it.aid);
|
ESP_LOGV(TAG, "AP client disconnected MAC=%s aid=%u", format_mac_address_pretty(it.mac).c_str(), it.aid);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case EVENT_SOFTAPMODE_PROBEREQRECVED: {
|
case EVENT_SOFTAPMODE_PROBEREQRECVED: {
|
||||||
auto it = event->event_info.ap_probereqrecved;
|
auto it = event->event_info.ap_probereqrecved;
|
||||||
ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_addr(it.mac).c_str(), it.rssi);
|
ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_address_pretty(it.mac).c_str(), it.rssi);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 4, 0)
|
#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 4, 0)
|
||||||
@@ -567,7 +568,7 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) {
|
|||||||
}
|
}
|
||||||
case EVENT_SOFTAPMODE_DISTRIBUTE_STA_IP: {
|
case EVENT_SOFTAPMODE_DISTRIBUTE_STA_IP: {
|
||||||
auto it = event->event_info.distribute_sta_ip;
|
auto it = event->event_info.distribute_sta_ip;
|
||||||
ESP_LOGV(TAG, "AP Distribute Station IP MAC=%s IP=%s aid=%u", format_mac_addr(it.mac).c_str(),
|
ESP_LOGV(TAG, "AP Distribute Station IP MAC=%s IP=%s aid=%u", format_mac_address_pretty(it.mac).c_str(),
|
||||||
format_ip_addr(it.ip).c_str(), it.aid);
|
format_ip_addr(it.ip).c_str(), it.aid);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -691,7 +691,7 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) {
|
|||||||
memcpy(buf, it.ssid, it.ssid_len);
|
memcpy(buf, it.ssid, it.ssid_len);
|
||||||
buf[it.ssid_len] = '\0';
|
buf[it.ssid_len] = '\0';
|
||||||
ESP_LOGV(TAG, "Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf,
|
ESP_LOGV(TAG, "Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf,
|
||||||
format_mac_addr(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode));
|
format_mac_address_pretty(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode));
|
||||||
s_sta_connected = true;
|
s_sta_connected = true;
|
||||||
|
|
||||||
} else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_DISCONNECTED) {
|
} else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_DISCONNECTED) {
|
||||||
@@ -708,7 +708,7 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) {
|
|||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf,
|
ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf,
|
||||||
format_mac_addr(it.bssid).c_str(), get_disconnect_reason_str(it.reason));
|
format_mac_address_pretty(it.bssid).c_str(), get_disconnect_reason_str(it.reason));
|
||||||
s_sta_connect_error = true;
|
s_sta_connect_error = true;
|
||||||
}
|
}
|
||||||
s_sta_connected = false;
|
s_sta_connected = false;
|
||||||
@@ -780,15 +780,15 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) {
|
|||||||
|
|
||||||
} else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_AP_PROBEREQRECVED) {
|
} else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_AP_PROBEREQRECVED) {
|
||||||
const auto &it = data->data.ap_probe_req_rx;
|
const auto &it = data->data.ap_probe_req_rx;
|
||||||
ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_addr(it.mac).c_str(), it.rssi);
|
ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_address_pretty(it.mac).c_str(), it.rssi);
|
||||||
|
|
||||||
} else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_AP_STACONNECTED) {
|
} else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_AP_STACONNECTED) {
|
||||||
const auto &it = data->data.ap_staconnected;
|
const auto &it = data->data.ap_staconnected;
|
||||||
ESP_LOGV(TAG, "AP client connected MAC=%s", format_mac_addr(it.mac).c_str());
|
ESP_LOGV(TAG, "AP client connected MAC=%s", format_mac_address_pretty(it.mac).c_str());
|
||||||
|
|
||||||
} else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_AP_STADISCONNECTED) {
|
} else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_AP_STADISCONNECTED) {
|
||||||
const auto &it = data->data.ap_stadisconnected;
|
const auto &it = data->data.ap_stadisconnected;
|
||||||
ESP_LOGV(TAG, "AP client disconnected MAC=%s", format_mac_addr(it.mac).c_str());
|
ESP_LOGV(TAG, "AP client disconnected MAC=%s", format_mac_address_pretty(it.mac).c_str());
|
||||||
|
|
||||||
} else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_AP_STAIPASSIGNED) {
|
} else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_AP_STAIPASSIGNED) {
|
||||||
const auto &it = data->data.ip_ap_staipassigned;
|
const auto &it = data->data.ip_ap_staipassigned;
|
||||||
|
|||||||
@@ -281,7 +281,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
|
|||||||
memcpy(buf, it.ssid, it.ssid_len);
|
memcpy(buf, it.ssid, it.ssid_len);
|
||||||
buf[it.ssid_len] = '\0';
|
buf[it.ssid_len] = '\0';
|
||||||
ESP_LOGV(TAG, "Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf,
|
ESP_LOGV(TAG, "Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf,
|
||||||
format_mac_addr(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode));
|
format_mac_address_pretty(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode));
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -294,7 +294,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
|
|||||||
ESP_LOGW(TAG, "Disconnected ssid='%s' reason='Probe Request Unsuccessful'", buf);
|
ESP_LOGW(TAG, "Disconnected ssid='%s' reason='Probe Request Unsuccessful'", buf);
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf,
|
ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf,
|
||||||
format_mac_addr(it.bssid).c_str(), get_disconnect_reason_str(it.reason));
|
format_mac_address_pretty(it.bssid).c_str(), get_disconnect_reason_str(it.reason));
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t reason = it.reason;
|
uint8_t reason = it.reason;
|
||||||
@@ -349,13 +349,13 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
|
|||||||
case ESPHOME_EVENT_ID_WIFI_AP_STACONNECTED: {
|
case ESPHOME_EVENT_ID_WIFI_AP_STACONNECTED: {
|
||||||
auto it = info.wifi_sta_connected;
|
auto it = info.wifi_sta_connected;
|
||||||
auto &mac = it.bssid;
|
auto &mac = it.bssid;
|
||||||
ESP_LOGV(TAG, "AP client connected MAC=%s", format_mac_addr(mac).c_str());
|
ESP_LOGV(TAG, "AP client connected MAC=%s", format_mac_address_pretty(mac).c_str());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ESPHOME_EVENT_ID_WIFI_AP_STADISCONNECTED: {
|
case ESPHOME_EVENT_ID_WIFI_AP_STADISCONNECTED: {
|
||||||
auto it = info.wifi_sta_disconnected;
|
auto it = info.wifi_sta_disconnected;
|
||||||
auto &mac = it.bssid;
|
auto &mac = it.bssid;
|
||||||
ESP_LOGV(TAG, "AP client disconnected MAC=%s", format_mac_addr(mac).c_str());
|
ESP_LOGV(TAG, "AP client disconnected MAC=%s", format_mac_address_pretty(mac).c_str());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ESPHOME_EVENT_ID_WIFI_AP_STAIPASSIGNED: {
|
case ESPHOME_EVENT_ID_WIFI_AP_STAIPASSIGNED: {
|
||||||
@@ -364,7 +364,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
|
|||||||
}
|
}
|
||||||
case ESPHOME_EVENT_ID_WIFI_AP_PROBEREQRECVED: {
|
case ESPHOME_EVENT_ID_WIFI_AP_PROBEREQRECVED: {
|
||||||
auto it = info.wifi_ap_probereqrecved;
|
auto it = info.wifi_ap_probereqrecved;
|
||||||
ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_addr(it.mac).c_str(), it.rssi);
|
ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_address_pretty(it.mac).c_str(), it.rssi);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -320,7 +320,7 @@ bool decrypt_xiaomi_payload(std::vector<uint8_t> &raw, const uint8_t *bindkey, c
|
|||||||
memcpy(mac_address + 4, mac_reverse + 1, 1);
|
memcpy(mac_address + 4, mac_reverse + 1, 1);
|
||||||
memcpy(mac_address + 5, mac_reverse, 1);
|
memcpy(mac_address + 5, mac_reverse, 1);
|
||||||
ESP_LOGVV(TAG, "decrypt_xiaomi_payload(): authenticated decryption failed.");
|
ESP_LOGVV(TAG, "decrypt_xiaomi_payload(): authenticated decryption failed.");
|
||||||
ESP_LOGVV(TAG, " MAC address : %s", format_hex_pretty(mac_address, 6).c_str());
|
ESP_LOGVV(TAG, " MAC address : %s", format_mac_address_pretty(mac_address).c_str());
|
||||||
ESP_LOGVV(TAG, " Packet : %s", format_hex_pretty(raw.data(), raw.size()).c_str());
|
ESP_LOGVV(TAG, " Packet : %s", format_hex_pretty(raw.data(), raw.size()).c_str());
|
||||||
ESP_LOGVV(TAG, " Key : %s", format_hex_pretty(vector.key, vector.keysize).c_str());
|
ESP_LOGVV(TAG, " Key : %s", format_hex_pretty(vector.key, vector.keysize).c_str());
|
||||||
ESP_LOGVV(TAG, " Iv : %s", format_hex_pretty(vector.iv, vector.ivsize).c_str());
|
ESP_LOGVV(TAG, " Iv : %s", format_hex_pretty(vector.iv, vector.ivsize).c_str());
|
||||||
|
|||||||
@@ -1099,7 +1099,7 @@ UNIT_KILOMETER_PER_HOUR = "km/h"
|
|||||||
UNIT_KILOVOLT_AMPS = "kVA"
|
UNIT_KILOVOLT_AMPS = "kVA"
|
||||||
UNIT_KILOVOLT_AMPS_HOURS = "kVAh"
|
UNIT_KILOVOLT_AMPS_HOURS = "kVAh"
|
||||||
UNIT_KILOVOLT_AMPS_REACTIVE = "kVAR"
|
UNIT_KILOVOLT_AMPS_REACTIVE = "kVAR"
|
||||||
UNIT_KILOVOLT_AMPS_REACTIVE_HOURS = "kVARh"
|
UNIT_KILOVOLT_AMPS_REACTIVE_HOURS = "kvarh"
|
||||||
UNIT_KILOWATT = "kW"
|
UNIT_KILOWATT = "kW"
|
||||||
UNIT_KILOWATT_HOURS = "kWh"
|
UNIT_KILOWATT_HOURS = "kWh"
|
||||||
UNIT_LITRE = "L"
|
UNIT_LITRE = "L"
|
||||||
@@ -1135,7 +1135,7 @@ UNIT_VOLT = "V"
|
|||||||
UNIT_VOLT_AMPS = "VA"
|
UNIT_VOLT_AMPS = "VA"
|
||||||
UNIT_VOLT_AMPS_HOURS = "VAh"
|
UNIT_VOLT_AMPS_HOURS = "VAh"
|
||||||
UNIT_VOLT_AMPS_REACTIVE = "var"
|
UNIT_VOLT_AMPS_REACTIVE = "var"
|
||||||
UNIT_VOLT_AMPS_REACTIVE_HOURS = "VARh"
|
UNIT_VOLT_AMPS_REACTIVE_HOURS = "varh"
|
||||||
UNIT_WATT = "W"
|
UNIT_WATT = "W"
|
||||||
UNIT_WATT_HOURS = "Wh"
|
UNIT_WATT_HOURS = "Wh"
|
||||||
|
|
||||||
|
|||||||
@@ -136,6 +136,10 @@ void Application::loop() {
|
|||||||
this->in_loop_ = false;
|
this->in_loop_ = false;
|
||||||
this->app_state_ = new_app_state;
|
this->app_state_ = new_app_state;
|
||||||
|
|
||||||
|
// Process any pending runtime stats printing after all components have run
|
||||||
|
// This ensures stats printing doesn't affect component timing measurements
|
||||||
|
runtime_stats.process_pending_stats(last_op_end_time);
|
||||||
|
|
||||||
// Use the last component's end time instead of calling millis() again
|
// Use the last component's end time instead of calling millis() again
|
||||||
auto elapsed = last_op_end_time - this->last_loop_;
|
auto elapsed = last_op_end_time - this->last_loop_;
|
||||||
if (elapsed >= this->loop_interval_ || HighFrequencyLoopRequester::is_high_frequency()) {
|
if (elapsed >= this->loop_interval_ || HighFrequencyLoopRequester::is_high_frequency()) {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <limits>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
@@ -7,6 +9,7 @@
|
|||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/preferences.h"
|
#include "esphome/core/preferences.h"
|
||||||
|
#include "esphome/core/runtime_stats.h"
|
||||||
#include "esphome/core/scheduler.h"
|
#include "esphome/core/scheduler.h"
|
||||||
|
|
||||||
#ifdef USE_DEVICES
|
#ifdef USE_DEVICES
|
||||||
@@ -337,9 +340,23 @@ class Application {
|
|||||||
*
|
*
|
||||||
* @param loop_interval The interval in milliseconds to run the core loop at. Defaults to 16 milliseconds.
|
* @param loop_interval The interval in milliseconds to run the core loop at. Defaults to 16 milliseconds.
|
||||||
*/
|
*/
|
||||||
void set_loop_interval(uint32_t loop_interval) { this->loop_interval_ = loop_interval; }
|
void set_loop_interval(uint32_t loop_interval) {
|
||||||
|
this->loop_interval_ = std::min(loop_interval, static_cast<uint32_t>(std::numeric_limits<uint16_t>::max()));
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t get_loop_interval() const { return this->loop_interval_; }
|
uint32_t get_loop_interval() const { return static_cast<uint32_t>(this->loop_interval_); }
|
||||||
|
|
||||||
|
/** Enable or disable runtime statistics collection.
|
||||||
|
*
|
||||||
|
* @param enable Whether to enable runtime statistics collection.
|
||||||
|
*/
|
||||||
|
void set_runtime_stats_enabled(bool enable) { runtime_stats.set_enabled(enable); }
|
||||||
|
|
||||||
|
/** Set the interval at which runtime statistics are logged.
|
||||||
|
*
|
||||||
|
* @param interval The interval in milliseconds between logging of runtime statistics.
|
||||||
|
*/
|
||||||
|
void set_runtime_stats_log_interval(uint32_t interval) { runtime_stats.set_log_interval(interval); }
|
||||||
|
|
||||||
void schedule_dump_config() { this->dump_config_at_ = 0; }
|
void schedule_dump_config() { this->dump_config_at_ = 0; }
|
||||||
|
|
||||||
@@ -618,6 +635,17 @@ class Application {
|
|||||||
/// Perform a delay while also monitoring socket file descriptors for readiness
|
/// Perform a delay while also monitoring socket file descriptors for readiness
|
||||||
void yield_with_select_(uint32_t delay_ms);
|
void yield_with_select_(uint32_t delay_ms);
|
||||||
|
|
||||||
|
// === Member variables ordered by size to minimize padding ===
|
||||||
|
|
||||||
|
// Pointer-sized members first
|
||||||
|
Component *current_component_{nullptr};
|
||||||
|
const char *comment_{nullptr};
|
||||||
|
const char *compilation_time_{nullptr};
|
||||||
|
|
||||||
|
// size_t members
|
||||||
|
size_t dump_config_at_{SIZE_MAX};
|
||||||
|
|
||||||
|
// Vectors (largest members)
|
||||||
std::vector<Component *> components_{};
|
std::vector<Component *> components_{};
|
||||||
|
|
||||||
// Partitioned vector design for looping components
|
// Partitioned vector design for looping components
|
||||||
@@ -637,11 +665,6 @@ class Application {
|
|||||||
// and active_end_ is incremented
|
// and active_end_ is incremented
|
||||||
// - This eliminates branch mispredictions from flag checking in the hot loop
|
// - This eliminates branch mispredictions from flag checking in the hot loop
|
||||||
std::vector<Component *> looping_components_{};
|
std::vector<Component *> looping_components_{};
|
||||||
uint16_t looping_components_active_end_{0};
|
|
||||||
|
|
||||||
// For safe reentrant modifications during iteration
|
|
||||||
uint16_t current_loop_index_{0};
|
|
||||||
bool in_loop_{false};
|
|
||||||
|
|
||||||
#ifdef USE_DEVICES
|
#ifdef USE_DEVICES
|
||||||
std::vector<Device *> devices_{};
|
std::vector<Device *> devices_{};
|
||||||
@@ -713,26 +736,39 @@ class Application {
|
|||||||
std::vector<update::UpdateEntity *> updates_{};
|
std::vector<update::UpdateEntity *> updates_{};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_SOCKET_SELECT_SUPPORT
|
||||||
|
std::vector<int> socket_fds_; // Vector of all monitored socket file descriptors
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// String members
|
||||||
std::string name_;
|
std::string name_;
|
||||||
std::string friendly_name_;
|
std::string friendly_name_;
|
||||||
const char *comment_{nullptr};
|
|
||||||
const char *compilation_time_{nullptr};
|
// 4-byte members
|
||||||
bool name_add_mac_suffix_;
|
|
||||||
uint32_t last_loop_{0};
|
uint32_t last_loop_{0};
|
||||||
uint32_t loop_interval_{16};
|
|
||||||
size_t dump_config_at_{SIZE_MAX};
|
|
||||||
uint8_t app_state_{0};
|
|
||||||
volatile bool has_pending_enable_loop_requests_{false};
|
|
||||||
Component *current_component_{nullptr};
|
|
||||||
uint32_t loop_component_start_time_{0};
|
uint32_t loop_component_start_time_{0};
|
||||||
|
|
||||||
#ifdef USE_SOCKET_SELECT_SUPPORT
|
#ifdef USE_SOCKET_SELECT_SUPPORT
|
||||||
// Socket select management
|
int max_fd_{-1}; // Highest file descriptor number for select()
|
||||||
std::vector<int> socket_fds_; // Vector of all monitored socket file descriptors
|
#endif
|
||||||
|
|
||||||
|
// 2-byte members (grouped together for alignment)
|
||||||
|
uint16_t loop_interval_{16}; // Loop interval in ms (max 65535ms = 65.5 seconds)
|
||||||
|
uint16_t looping_components_active_end_{0};
|
||||||
|
uint16_t current_loop_index_{0}; // For safe reentrant modifications during iteration
|
||||||
|
|
||||||
|
// 1-byte members (grouped together to minimize padding)
|
||||||
|
uint8_t app_state_{0};
|
||||||
|
bool name_add_mac_suffix_;
|
||||||
|
bool in_loop_{false};
|
||||||
|
volatile bool has_pending_enable_loop_requests_{false};
|
||||||
|
|
||||||
|
#ifdef USE_SOCKET_SELECT_SUPPORT
|
||||||
bool socket_fds_changed_{false}; // Flag to rebuild base_read_fds_ when socket_fds_ changes
|
bool socket_fds_changed_{false}; // Flag to rebuild base_read_fds_ when socket_fds_ changes
|
||||||
int max_fd_{-1}; // Highest file descriptor number for select()
|
|
||||||
fd_set base_read_fds_{}; // Cached fd_set rebuilt only when socket_fds_ changes
|
// Variable-sized members at end
|
||||||
fd_set read_fds_{}; // Working fd_set for select(), copied from base_read_fds_
|
fd_set base_read_fds_{}; // Cached fd_set rebuilt only when socket_fds_ changes
|
||||||
|
fd_set read_fds_{}; // Working fd_set for select(), copied from base_read_fds_
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -27,20 +27,67 @@ template<typename T, typename... X> class TemplatableValue {
|
|||||||
public:
|
public:
|
||||||
TemplatableValue() : type_(NONE) {}
|
TemplatableValue() : type_(NONE) {}
|
||||||
|
|
||||||
template<typename F, enable_if_t<!is_invocable<F, X...>::value, int> = 0>
|
template<typename F, enable_if_t<!is_invocable<F, X...>::value, int> = 0> TemplatableValue(F value) : type_(VALUE) {
|
||||||
TemplatableValue(F value) : type_(VALUE), value_(std::move(value)) {}
|
new (&this->value_) T(std::move(value));
|
||||||
|
}
|
||||||
|
|
||||||
template<typename F, enable_if_t<is_invocable<F, X...>::value, int> = 0>
|
template<typename F, enable_if_t<is_invocable<F, X...>::value, int> = 0> TemplatableValue(F f) : type_(LAMBDA) {
|
||||||
TemplatableValue(F f) : type_(LAMBDA), f_(f) {}
|
this->f_ = new std::function<T(X...)>(std::move(f));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy constructor
|
||||||
|
TemplatableValue(const TemplatableValue &other) : type_(other.type_) {
|
||||||
|
if (type_ == VALUE) {
|
||||||
|
new (&this->value_) T(other.value_);
|
||||||
|
} else if (type_ == LAMBDA) {
|
||||||
|
this->f_ = new std::function<T(X...)>(*other.f_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move constructor
|
||||||
|
TemplatableValue(TemplatableValue &&other) noexcept : type_(other.type_) {
|
||||||
|
if (type_ == VALUE) {
|
||||||
|
new (&this->value_) T(std::move(other.value_));
|
||||||
|
} else if (type_ == LAMBDA) {
|
||||||
|
this->f_ = other.f_;
|
||||||
|
other.f_ = nullptr;
|
||||||
|
}
|
||||||
|
other.type_ = NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assignment operators
|
||||||
|
TemplatableValue &operator=(const TemplatableValue &other) {
|
||||||
|
if (this != &other) {
|
||||||
|
this->~TemplatableValue();
|
||||||
|
new (this) TemplatableValue(other);
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
TemplatableValue &operator=(TemplatableValue &&other) noexcept {
|
||||||
|
if (this != &other) {
|
||||||
|
this->~TemplatableValue();
|
||||||
|
new (this) TemplatableValue(std::move(other));
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
~TemplatableValue() {
|
||||||
|
if (type_ == VALUE) {
|
||||||
|
this->value_.~T();
|
||||||
|
} else if (type_ == LAMBDA) {
|
||||||
|
delete this->f_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool has_value() { return this->type_ != NONE; }
|
bool has_value() { return this->type_ != NONE; }
|
||||||
|
|
||||||
T value(X... x) {
|
T value(X... x) {
|
||||||
if (this->type_ == LAMBDA) {
|
if (this->type_ == LAMBDA) {
|
||||||
return this->f_(x...);
|
return (*this->f_)(x...);
|
||||||
}
|
}
|
||||||
// return value also when none
|
// return value also when none
|
||||||
return this->value_;
|
return this->type_ == VALUE ? this->value_ : T{};
|
||||||
}
|
}
|
||||||
|
|
||||||
optional<T> optional_value(X... x) {
|
optional<T> optional_value(X... x) {
|
||||||
@@ -58,14 +105,16 @@ template<typename T, typename... X> class TemplatableValue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
enum {
|
enum : uint8_t {
|
||||||
NONE,
|
NONE,
|
||||||
VALUE,
|
VALUE,
|
||||||
LAMBDA,
|
LAMBDA,
|
||||||
} type_;
|
} type_;
|
||||||
|
|
||||||
T value_{};
|
union {
|
||||||
std::function<T(X...)> f_{};
|
T value_;
|
||||||
|
std::function<T(X...)> *f_;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Base class for all automation conditions.
|
/** Base class for all automation conditions.
|
||||||
|
|||||||
@@ -303,6 +303,9 @@ uint32_t WarnIfComponentBlockingGuard::finish() {
|
|||||||
uint32_t curr_time = millis();
|
uint32_t curr_time = millis();
|
||||||
|
|
||||||
uint32_t blocking_time = curr_time - this->started_;
|
uint32_t blocking_time = curr_time - this->started_;
|
||||||
|
|
||||||
|
// Record component runtime stats
|
||||||
|
runtime_stats.record_component_time(this->component_, blocking_time, curr_time);
|
||||||
bool should_warn;
|
bool should_warn;
|
||||||
if (this->component_ != nullptr) {
|
if (this->component_ != nullptr) {
|
||||||
should_warn = this->component_->should_warn_of_blocking(blocking_time);
|
should_warn = this->component_->should_warn_of_blocking(blocking_time);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "esphome/core/optional.h"
|
#include "esphome/core/optional.h"
|
||||||
|
#include "esphome/core/runtime_stats.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
|
|
||||||
|
|||||||
@@ -375,7 +375,7 @@ void ComponentIterator::advance() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (advance_platform) {
|
if (advance_platform) {
|
||||||
this->state_ = static_cast<IteratorState>(static_cast<uint32_t>(this->state_) + 1);
|
this->state_ = static_cast<IteratorState>(static_cast<uint16_t>(this->state_) + 1);
|
||||||
this->at_ = 0;
|
this->at_ = 0;
|
||||||
} else if (success) {
|
} else if (success) {
|
||||||
this->at_++;
|
this->at_++;
|
||||||
|
|||||||
@@ -93,7 +93,9 @@ class ComponentIterator {
|
|||||||
virtual bool on_end();
|
virtual bool on_end();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
enum class IteratorState {
|
// Iterates over all ESPHome entities (sensors, switches, lights, etc.)
|
||||||
|
// Supports up to 256 entity types and up to 65,535 entities of each type
|
||||||
|
enum class IteratorState : uint8_t {
|
||||||
NONE = 0,
|
NONE = 0,
|
||||||
BEGIN,
|
BEGIN,
|
||||||
#ifdef USE_BINARY_SENSOR
|
#ifdef USE_BINARY_SENSOR
|
||||||
@@ -167,7 +169,7 @@ class ComponentIterator {
|
|||||||
#endif
|
#endif
|
||||||
MAX,
|
MAX,
|
||||||
} state_{IteratorState::NONE};
|
} state_{IteratorState::NONE};
|
||||||
size_t at_{0};
|
uint16_t at_{0}; // Supports up to 65,535 entities per type
|
||||||
bool include_internal_{false};
|
bool include_internal_{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -132,6 +132,8 @@
|
|||||||
|
|
||||||
// ESP32-specific feature flags
|
// ESP32-specific feature flags
|
||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
|
#define USE_ESPHOME_TASK_LOG_BUFFER
|
||||||
|
|
||||||
#define USE_BLUETOOTH_PROXY
|
#define USE_BLUETOOTH_PROXY
|
||||||
#define USE_CAPTIVE_PORTAL
|
#define USE_CAPTIVE_PORTAL
|
||||||
#define USE_ESP32_BLE
|
#define USE_ESP32_BLE
|
||||||
|
|||||||
@@ -356,6 +356,10 @@ size_t parse_hex(const char *str, size_t length, uint8_t *data, size_t count) {
|
|||||||
return chars;
|
return chars;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string format_mac_address_pretty(const uint8_t *mac) {
|
||||||
|
return str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||||
|
}
|
||||||
|
|
||||||
static char format_hex_char(uint8_t v) { return v >= 10 ? 'a' + (v - 10) : '0' + v; }
|
static char format_hex_char(uint8_t v) { return v >= 10 ? 'a' + (v - 10) : '0' + v; }
|
||||||
std::string format_hex(const uint8_t *data, size_t length) {
|
std::string format_hex(const uint8_t *data, size_t length) {
|
||||||
std::string ret;
|
std::string ret;
|
||||||
@@ -732,7 +736,7 @@ std::string get_mac_address() {
|
|||||||
std::string get_mac_address_pretty() {
|
std::string get_mac_address_pretty() {
|
||||||
uint8_t mac[6];
|
uint8_t mac[6];
|
||||||
get_mac_address_raw(mac);
|
get_mac_address_raw(mac);
|
||||||
return str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
return format_mac_address_pretty(mac);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
|
|||||||
@@ -402,6 +402,8 @@ template<typename T, enable_if_t<std::is_unsigned<T>::value, int> = 0> optional<
|
|||||||
return parse_hex<T>(str.c_str(), str.length());
|
return parse_hex<T>(str.c_str(), str.length());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Format the six-byte array \p mac into a MAC address.
|
||||||
|
std::string format_mac_address_pretty(const uint8_t mac[6]);
|
||||||
/// Format the byte array \p data of length \p len in lowercased hex.
|
/// Format the byte array \p data of length \p len in lowercased hex.
|
||||||
std::string format_hex(const uint8_t *data, size_t length);
|
std::string format_hex(const uint8_t *data, size_t length);
|
||||||
/// Format the vector \p data in lowercased hex.
|
/// Format the vector \p data in lowercased hex.
|
||||||
|
|||||||
92
esphome/core/runtime_stats.cpp
Normal file
92
esphome/core/runtime_stats.cpp
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
#include "esphome/core/runtime_stats.h"
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
|
||||||
|
RuntimeStatsCollector runtime_stats;
|
||||||
|
|
||||||
|
void RuntimeStatsCollector::record_component_time(Component *component, uint32_t duration_ms, uint32_t current_time) {
|
||||||
|
if (!this->enabled_ || component == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Check if we have cached the name for this component
|
||||||
|
auto name_it = this->component_names_cache_.find(component);
|
||||||
|
if (name_it == this->component_names_cache_.end()) {
|
||||||
|
// First time seeing this component, cache its name
|
||||||
|
const char *source = component->get_component_source();
|
||||||
|
this->component_names_cache_[component] = source;
|
||||||
|
this->component_stats_[source].record_time(duration_ms);
|
||||||
|
} else {
|
||||||
|
// Use cached name - no string operations, just map lookup
|
||||||
|
this->component_stats_[name_it->second].record_time(duration_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If next_log_time_ is 0, initialize it
|
||||||
|
if (this->next_log_time_ == 0) {
|
||||||
|
this->next_log_time_ = current_time + this->log_interval_;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't print stats here anymore - let process_pending_stats handle it
|
||||||
|
}
|
||||||
|
|
||||||
|
void RuntimeStatsCollector::log_stats_() {
|
||||||
|
ESP_LOGI(RUNTIME_TAG, "Component Runtime Statistics");
|
||||||
|
ESP_LOGI(RUNTIME_TAG, "Period stats (last %" PRIu32 "ms):", this->log_interval_);
|
||||||
|
|
||||||
|
// First collect stats we want to display
|
||||||
|
std::vector<ComponentStatPair> stats_to_display;
|
||||||
|
|
||||||
|
for (const auto &it : this->component_stats_) {
|
||||||
|
const ComponentRuntimeStats &stats = it.second;
|
||||||
|
if (stats.get_period_count() > 0) {
|
||||||
|
ComponentStatPair pair = {it.first, &stats};
|
||||||
|
stats_to_display.push_back(pair);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by period runtime (descending)
|
||||||
|
std::sort(stats_to_display.begin(), stats_to_display.end(), std::greater<ComponentStatPair>());
|
||||||
|
|
||||||
|
// Log top components by period runtime
|
||||||
|
for (const auto &it : stats_to_display) {
|
||||||
|
const std::string &source = it.name;
|
||||||
|
const ComponentRuntimeStats *stats = it.stats;
|
||||||
|
|
||||||
|
ESP_LOGI(RUNTIME_TAG, " %s: count=%" PRIu32 ", avg=%.2fms, max=%" PRIu32 "ms, total=%" PRIu32 "ms", source.c_str(),
|
||||||
|
stats->get_period_count(), stats->get_period_avg_time_ms(), stats->get_period_max_time_ms(),
|
||||||
|
stats->get_period_time_ms());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log total stats since boot
|
||||||
|
ESP_LOGI(RUNTIME_TAG, "Total stats (since boot):");
|
||||||
|
|
||||||
|
// Re-sort by total runtime for all-time stats
|
||||||
|
std::sort(stats_to_display.begin(), stats_to_display.end(),
|
||||||
|
[](const ComponentStatPair &a, const ComponentStatPair &b) {
|
||||||
|
return a.stats->get_total_time_ms() > b.stats->get_total_time_ms();
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const auto &it : stats_to_display) {
|
||||||
|
const std::string &source = it.name;
|
||||||
|
const ComponentRuntimeStats *stats = it.stats;
|
||||||
|
|
||||||
|
ESP_LOGI(RUNTIME_TAG, " %s: count=%" PRIu32 ", avg=%.2fms, max=%" PRIu32 "ms, total=%" PRIu32 "ms", source.c_str(),
|
||||||
|
stats->get_total_count(), stats->get_total_avg_time_ms(), stats->get_total_max_time_ms(),
|
||||||
|
stats->get_total_time_ms());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RuntimeStatsCollector::process_pending_stats(uint32_t current_time) {
|
||||||
|
if (!this->enabled_ || this->next_log_time_ == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (current_time >= this->next_log_time_) {
|
||||||
|
this->log_stats_();
|
||||||
|
this->reset_stats_();
|
||||||
|
this->next_log_time_ = current_time + this->log_interval_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace esphome
|
||||||
121
esphome/core/runtime_stats.h
Normal file
121
esphome/core/runtime_stats.h
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <algorithm>
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
|
||||||
|
static const char *const RUNTIME_TAG = "runtime";
|
||||||
|
|
||||||
|
class Component; // Forward declaration
|
||||||
|
|
||||||
|
class ComponentRuntimeStats {
|
||||||
|
public:
|
||||||
|
ComponentRuntimeStats()
|
||||||
|
: period_count_(0),
|
||||||
|
total_count_(0),
|
||||||
|
period_time_ms_(0),
|
||||||
|
total_time_ms_(0),
|
||||||
|
period_max_time_ms_(0),
|
||||||
|
total_max_time_ms_(0) {}
|
||||||
|
|
||||||
|
void record_time(uint32_t duration_ms) {
|
||||||
|
// Update period counters
|
||||||
|
this->period_count_++;
|
||||||
|
this->period_time_ms_ += duration_ms;
|
||||||
|
if (duration_ms > this->period_max_time_ms_)
|
||||||
|
this->period_max_time_ms_ = duration_ms;
|
||||||
|
|
||||||
|
// Update total counters
|
||||||
|
this->total_count_++;
|
||||||
|
this->total_time_ms_ += duration_ms;
|
||||||
|
if (duration_ms > this->total_max_time_ms_)
|
||||||
|
this->total_max_time_ms_ = duration_ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset_period_stats() {
|
||||||
|
this->period_count_ = 0;
|
||||||
|
this->period_time_ms_ = 0;
|
||||||
|
this->period_max_time_ms_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Period stats (reset each logging interval)
|
||||||
|
uint32_t get_period_count() const { return this->period_count_; }
|
||||||
|
uint32_t get_period_time_ms() const { return this->period_time_ms_; }
|
||||||
|
uint32_t get_period_max_time_ms() const { return this->period_max_time_ms_; }
|
||||||
|
float get_period_avg_time_ms() const {
|
||||||
|
return this->period_count_ > 0 ? this->period_time_ms_ / static_cast<float>(this->period_count_) : 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Total stats (persistent until reboot)
|
||||||
|
uint32_t get_total_count() const { return this->total_count_; }
|
||||||
|
uint32_t get_total_time_ms() const { return this->total_time_ms_; }
|
||||||
|
uint32_t get_total_max_time_ms() const { return this->total_max_time_ms_; }
|
||||||
|
float get_total_avg_time_ms() const {
|
||||||
|
return this->total_count_ > 0 ? this->total_time_ms_ / static_cast<float>(this->total_count_) : 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// Period stats (reset each logging interval)
|
||||||
|
uint32_t period_count_;
|
||||||
|
uint32_t period_time_ms_;
|
||||||
|
uint32_t period_max_time_ms_;
|
||||||
|
|
||||||
|
// Total stats (persistent until reboot)
|
||||||
|
uint32_t total_count_;
|
||||||
|
uint32_t total_time_ms_;
|
||||||
|
uint32_t total_max_time_ms_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// For sorting components by run time
|
||||||
|
struct ComponentStatPair {
|
||||||
|
std::string name;
|
||||||
|
const ComponentRuntimeStats *stats;
|
||||||
|
|
||||||
|
bool operator>(const ComponentStatPair &other) const {
|
||||||
|
// Sort by period time as that's what we're displaying in the logs
|
||||||
|
return stats->get_period_time_ms() > other.stats->get_period_time_ms();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class RuntimeStatsCollector {
|
||||||
|
public:
|
||||||
|
RuntimeStatsCollector() : log_interval_(60000), next_log_time_(0), enabled_(true) {}
|
||||||
|
|
||||||
|
void set_log_interval(uint32_t log_interval) { this->log_interval_ = log_interval; }
|
||||||
|
uint32_t get_log_interval() const { return this->log_interval_; }
|
||||||
|
|
||||||
|
void set_enabled(bool enabled) { this->enabled_ = enabled; }
|
||||||
|
bool is_enabled() const { return this->enabled_; }
|
||||||
|
|
||||||
|
void record_component_time(Component *component, uint32_t duration_ms, uint32_t current_time);
|
||||||
|
|
||||||
|
// Process any pending stats printing (should be called after component loop)
|
||||||
|
void process_pending_stats(uint32_t current_time);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void log_stats_();
|
||||||
|
|
||||||
|
void reset_stats_() {
|
||||||
|
for (auto &it : this->component_stats_) {
|
||||||
|
it.second.reset_period_stats();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Back to string keys, but we'll cache the source name per component
|
||||||
|
std::map<std::string, ComponentRuntimeStats> component_stats_;
|
||||||
|
std::map<Component *, std::string> component_names_cache_;
|
||||||
|
uint32_t log_interval_;
|
||||||
|
uint32_t next_log_time_;
|
||||||
|
bool enabled_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Global instance for runtime stats collection
|
||||||
|
extern RuntimeStatsCollector runtime_stats;
|
||||||
|
|
||||||
|
} // namespace esphome
|
||||||
@@ -4,6 +4,31 @@ binary_sensor:
|
|||||||
id: some_binary_sensor
|
id: some_binary_sensor
|
||||||
name: "Random binary"
|
name: "Random binary"
|
||||||
lambda: return (random_uint32() & 1) == 0;
|
lambda: return (random_uint32() & 1) == 0;
|
||||||
|
filters:
|
||||||
|
- invert:
|
||||||
|
- delayed_on: 100ms
|
||||||
|
- delayed_off: 100ms
|
||||||
|
# Templated, delays for 1s (1000ms) only if a reed switch is active
|
||||||
|
- delayed_on_off: !lambda "return 1000;"
|
||||||
|
- delayed_on_off:
|
||||||
|
time_on: 10s
|
||||||
|
time_off: !lambda "return 1000;"
|
||||||
|
- autorepeat:
|
||||||
|
- delay: 1s
|
||||||
|
time_off: 100ms
|
||||||
|
time_on: 900ms
|
||||||
|
- delay: 5s
|
||||||
|
time_off: 100ms
|
||||||
|
time_on: 400ms
|
||||||
|
- lambda: |-
|
||||||
|
if (id(some_binary_sensor).state) {
|
||||||
|
return x;
|
||||||
|
} else {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
- settle: 100ms
|
||||||
|
- timeout: 10s
|
||||||
|
|
||||||
on_state_change:
|
on_state_change:
|
||||||
then:
|
then:
|
||||||
- logger.log:
|
- logger.log:
|
||||||
|
|||||||
@@ -839,9 +839,7 @@ lvgl:
|
|||||||
styles: bdr_style
|
styles: bdr_style
|
||||||
grid_cell_x_align: center
|
grid_cell_x_align: center
|
||||||
grid_cell_y_align: stretch
|
grid_cell_y_align: stretch
|
||||||
grid_cell_row_pos: 0
|
grid_cell_column_span: 2
|
||||||
grid_cell_column_pos: 1
|
|
||||||
grid_cell_column_span: 1
|
|
||||||
text: "Grid cell 0/1"
|
text: "Grid cell 0/1"
|
||||||
- label:
|
- label:
|
||||||
grid_cell_x_align: end
|
grid_cell_x_align: end
|
||||||
|
|||||||
7
tests/integration/fixtures/api_reboot_timeout.yaml
Normal file
7
tests/integration/fixtures/api_reboot_timeout.yaml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
esphome:
|
||||||
|
name: api-reboot-test
|
||||||
|
host:
|
||||||
|
api:
|
||||||
|
reboot_timeout: 0.5s # Very short timeout for fast testing
|
||||||
|
logger:
|
||||||
|
level: DEBUG
|
||||||
@@ -8,5 +8,8 @@ sensor:
|
|||||||
name: Test Sensor
|
name: Test Sensor
|
||||||
id: test_sensor
|
id: test_sensor
|
||||||
unit_of_measurement: °C
|
unit_of_measurement: °C
|
||||||
|
accuracy_decimals: 2
|
||||||
|
state_class: measurement
|
||||||
|
force_update: true
|
||||||
lambda: return 42.0;
|
lambda: return 42.0;
|
||||||
update_interval: 0.1s
|
update_interval: 0.1s
|
||||||
|
|||||||
35
tests/integration/test_api_reboot_timeout.py
Normal file
35
tests/integration/test_api_reboot_timeout.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
"""Test API server reboot timeout functionality."""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import re
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from .types import RunCompiledFunction
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_api_reboot_timeout(
|
||||||
|
yaml_config: str,
|
||||||
|
run_compiled: RunCompiledFunction,
|
||||||
|
) -> None:
|
||||||
|
"""Test that the device reboots when no API clients connect within the timeout."""
|
||||||
|
loop = asyncio.get_running_loop()
|
||||||
|
reboot_future = loop.create_future()
|
||||||
|
reboot_pattern = re.compile(r"No clients; rebooting")
|
||||||
|
|
||||||
|
def check_output(line: str) -> None:
|
||||||
|
"""Check output for reboot message."""
|
||||||
|
if not reboot_future.done() and reboot_pattern.search(line):
|
||||||
|
reboot_future.set_result(True)
|
||||||
|
|
||||||
|
# Run the device without connecting any API client
|
||||||
|
async with run_compiled(yaml_config, line_callback=check_output):
|
||||||
|
# Wait for reboot with timeout
|
||||||
|
# (0.5s reboot timeout + some margin for processing)
|
||||||
|
try:
|
||||||
|
await asyncio.wait_for(reboot_future, timeout=2.0)
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
pytest.fail("Device did not reboot within expected timeout")
|
||||||
|
|
||||||
|
# Test passes if we get here - reboot was detected
|
||||||
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
|
import aioesphomeapi
|
||||||
from aioesphomeapi import EntityState
|
from aioesphomeapi import EntityState
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@@ -47,3 +48,23 @@ async def test_host_mode_with_sensor(
|
|||||||
# Verify the sensor state
|
# Verify the sensor state
|
||||||
assert test_sensor_state.state == 42.0
|
assert test_sensor_state.state == 42.0
|
||||||
assert len(states) > 0, "No states received"
|
assert len(states) > 0, "No states received"
|
||||||
|
|
||||||
|
# Verify the optimized fields are working correctly
|
||||||
|
# Get entity info to check accuracy_decimals, state_class, etc.
|
||||||
|
entities, _ = await client.list_entities_services()
|
||||||
|
sensor_info: aioesphomeapi.SensorInfo | None = None
|
||||||
|
for entity in entities:
|
||||||
|
if isinstance(entity, aioesphomeapi.SensorInfo):
|
||||||
|
sensor_info = entity
|
||||||
|
break
|
||||||
|
|
||||||
|
assert sensor_info is not None, "Sensor entity info not found"
|
||||||
|
assert sensor_info.accuracy_decimals == 2, (
|
||||||
|
f"Expected accuracy_decimals=2, got {sensor_info.accuracy_decimals}"
|
||||||
|
)
|
||||||
|
assert sensor_info.state_class == 1, (
|
||||||
|
f"Expected state_class=1 (measurement), got {sensor_info.state_class}"
|
||||||
|
)
|
||||||
|
assert sensor_info.force_update is True, (
|
||||||
|
f"Expected force_update=True, got {sensor_info.force_update}"
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user