Compare commits

..

150 Commits

Author SHA1 Message Date
J. Nick Koston
4bdd08887e use a common that does not have dupes on dev 2025-06-25 00:50:18 +02:00
J. Nick Koston
1fd8ebf386 update tests now that duplicate names are validated 2025-06-25 00:35:38 +02:00
J. Nick Koston
d2fc3e749c update tests now that duplicate names are validated 2025-06-25 00:34:50 +02:00
J. Nick Koston
71fbcbceaf update tests now that duplicate names are validated 2025-06-25 00:34:27 +02:00
J. Nick Koston
27347b2088 update tests now that duplicate names are validated 2025-06-25 00:34:04 +02:00
J. Nick Koston
599993d1a5 update tests now that duplicate names are validated 2025-06-25 00:22:51 +02:00
J. Nick Koston
bf359cb8e3 update tests now that duplicate names are validated 2025-06-25 00:20:51 +02:00
J. Nick Koston
509a704410 update tests now that duplicate names are validated 2025-06-25 00:19:32 +02:00
J. Nick Koston
1f48e2b01f update tests now that duplicate names are validated 2025-06-25 00:18:40 +02:00
J. Nick Koston
83613726d1 fix 2025-06-25 00:04:07 +02:00
J. Nick Koston
796e12bd70 Merge branch 'dev' into multi_device 2025-06-24 23:54:24 +02:00
J. Nick Koston
ddbe17d3f6 fixes 2025-06-24 23:40:16 +02:00
J. Nick Koston
591ec36f4a fixes 2025-06-24 23:37:58 +02:00
J. Nick Koston
41eceb72ef preen 2025-06-24 23:28:06 +02:00
J. Nick Koston
0a5f094025 cleanup 2025-06-24 23:25:46 +02:00
J. Nick Koston
ca0f3ba262 cleanup 2025-06-24 23:23:59 +02:00
J. Nick Koston
30f4e782db cleanup 2025-06-24 23:23:35 +02:00
J. Nick Koston
192158ef1a cleanup 2025-06-24 23:22:18 +02:00
J. Nick Koston
602456db40 cleanup 2025-06-24 23:13:45 +02:00
J. Nick Koston
536e45668f migrate 2025-06-24 23:09:08 +02:00
J. Nick Koston
10bf05ab0d migrate 2025-06-24 22:59:46 +02:00
J. Nick Koston
5ad1af69e4 migrate 2025-06-24 22:57:10 +02:00
J. Nick Koston
48f2911434 raise 2025-06-24 22:18:29 +02:00
J. Nick Koston
ac3598f12a cleanup 2025-06-24 18:07:58 +02:00
J. Nick Koston
66201be5ca preen 2025-06-24 18:00:10 +02:00
J. Nick Koston
ac0b0b652e cleanup 2025-06-24 17:55:58 +02:00
J. Nick Koston
d89ee2df42 Update esphome/core/application.h 2025-06-24 17:52:13 +02:00
J. Nick Koston
418e248e5e cleanup 2025-06-24 17:51:05 +02:00
J. Nick Koston
8c2b141049 cleanup 2025-06-24 17:41:40 +02:00
J. Nick Koston
2f8e07302b Update esphome/core/entity_base.cpp 2025-06-24 17:10:06 +02:00
J. Nick Koston
c3776240b6 fixes 2025-06-24 17:03:23 +02:00
J. Nick Koston
e370872ec1 fix conflicts 2025-06-24 16:13:34 +02:00
Jesse Hills
d4e978369a Store reference to device on EntityBase
This is so we can get the name of the device to use as part of the object id and to internally set the name for logging.
2025-06-24 19:56:30 +12:00
Jesse Hills
8d5d7f5237 Merge branch 'dev' into multi_device 2025-06-24 16:02:03 +12:00
J. Nick Koston
20f946ccaf Merge branch 'dev' into multi_device 2025-06-23 00:32:09 +02:00
Jesse Hills
e5e972231c Update testing 2025-06-23 10:26:31 +12:00
Jesse Hills
754d2874e7 `this->` 2025-06-23 09:21:29 +12:00
Jesse Hills
06de58ff8b Dont need to warning about simple string area
A single device in a single area can have a simple string as the area
2025-06-23 09:20:53 +12:00
J. Nick Koston
5857f7b9a7 Merge remote-tracking branch 'dala318/multi_device' into multi_device 2025-06-22 21:55:46 +02:00
J. Nick Koston
a5ea0cd41f remove unreachable code 2025-06-22 21:55:23 +02:00
J. Nick Koston
d677934417 Merge branch 'dev' into multi_device 2025-06-22 21:45:28 +02:00
J. Nick Koston
ba87a0b63c cleanups 2025-06-22 21:32:20 +02:00
J. Nick Koston
b725bb3dd1 lint 2025-06-22 21:28:16 +02:00
J. Nick Koston
c34ba3deb5 lint 2025-06-22 21:25:55 +02:00
J. Nick Koston
68b13340fb lint 2025-06-22 21:24:17 +02:00
J. Nick Koston
8831999ea6 lint 2025-06-22 21:23:41 +02:00
J. Nick Koston
c1853f8b84 document design decisions 2025-06-22 21:21:29 +02:00
J. Nick Koston
2b9b7e2853 validation should happen sooner 2025-06-22 21:18:04 +02:00
J. Nick Koston
d3b18debf9 validate sooner 2025-06-22 21:06:33 +02:00
J. Nick Koston
b01eb28d42 validate sooner 2025-06-22 21:05:15 +02:00
J. Nick Koston
02019dd16c validate sooner 2025-06-22 21:04:42 +02:00
J. Nick Koston
7be12f5ff6 validate sooner 2025-06-22 20:59:54 +02:00
J. Nick Koston
a90d59b6ba validate sooner 2025-06-22 20:59:07 +02:00
J. Nick Koston
25ed7c890b cleanups 2025-06-22 20:03:02 +02:00
J. Nick Koston
85e3b63f05 adjust 2025-06-22 19:49:12 +02:00
J. Nick Koston
a37bac1956 add files 2025-06-22 19:47:19 +02:00
J. Nick Koston
818a978dfc units 2025-06-22 19:40:53 +02:00
J. Nick Koston
180aeb7d8e simplify 2025-06-22 13:50:29 +02:00
J. Nick Koston
0764fa7292 simplify 2025-06-22 13:48:27 +02:00
J. Nick Koston
17bf533ed7 simplify 2025-06-22 13:44:05 +02:00
J. Nick Koston
d7eae1c1a0 simplify 2025-06-22 13:43:52 +02:00
J. Nick Koston
7f2d979255 preen 2025-06-22 13:39:12 +02:00
J. Nick Koston
46b419ea8b preen 2025-06-22 13:38:14 +02:00
J. Nick Koston
b30b527ff9 one more place to check 2025-06-22 13:37:30 +02:00
J. Nick Koston
41b1bfc504 legacy test 2025-06-22 13:37:01 +02:00
J. Nick Koston
f4f14a7507 fixes 2025-06-22 13:29:49 +02:00
J. Nick Koston
61c29213a7 fixes 2025-06-22 13:29:41 +02:00
J. Nick Koston
e6d7639209 Merge branch 'dev' into multi_device 2025-06-22 13:03:16 +02:00
J. Nick Koston
3c07a186b2 Merge remote-tracking branch 'dala318/multi_device' into multi_device 2025-06-22 13:02:48 +02:00
J. Nick Koston
8a725250a9 Merge branch 'dev' into multi_device 2025-06-22 12:32:44 +02:00
J. Nick Koston
502b8a6073 fixes 2025-06-22 12:32:25 +02:00
J. Nick Koston
b03e3b8d4a fixes 2025-06-22 10:07:05 +02:00
J. Nick Koston
a98e34d190 handle collisions 2025-06-22 10:02:59 +02:00
J. Nick Koston
bf8d8b6e63 handle collisions 2025-06-22 10:01:53 +02:00
J. Nick Koston
57599f7a98 handle collisions 2025-06-22 10:00:31 +02:00
J. Nick Koston
ffccce7ffc handle collisions 2025-06-22 09:58:12 +02:00
J. Nick Koston
221e3c6c9c preen 2025-06-21 18:09:16 +02:00
J. Nick Koston
fb1679d572 preen 2025-06-21 18:07:45 +02:00
J. Nick Koston
c19065f112 preen 2025-06-21 18:02:32 +02:00
J. Nick Koston
f2b04a077e preen 2025-06-21 18:01:12 +02:00
J. Nick Koston
8e7841c880 preen 2025-06-21 18:00:17 +02:00
J. Nick Koston
1873490b24 preen 2025-06-21 17:57:36 +02:00
J. Nick Koston
4d231953f4 preen 2025-06-21 17:57:10 +02:00
J. Nick Koston
aa4c399657 reverse space in vectors 2025-06-21 17:36:25 +02:00
J. Nick Koston
1f99d18982 reverse space in vectors 2025-06-21 17:34:08 +02:00
J. Nick Koston
be37178ef8 make areas and devices consistant 2025-06-21 17:32:11 +02:00
J. Nick Koston
fad86c655e make areas and devices consistant 2025-06-21 17:30:17 +02:00
J. Nick Koston
4a7958586e make areas and devices consistant 2025-06-21 17:19:16 +02:00
J. Nick Koston
f44ecd0891 make areas and devices consistant 2025-06-21 17:18:23 +02:00
J. Nick Koston
3d0392d668 make areas and devices consistant 2025-06-21 17:17:29 +02:00
J. Nick Koston
d300d2605b make areas and devices consistant 2025-06-21 17:13:04 +02:00
J. Nick Koston
66cce6a2f2 make areas and devices consistant 2025-06-21 17:12:25 +02:00
J. Nick Koston
65e3c6bfbb make areas and devices consistant 2025-06-21 17:12:00 +02:00
J. Nick Koston
2a39060912 Merge remote-tracking branch 'upstream/dev' into multi_device 2025-06-21 17:06:11 +02:00
J. Nick Koston
8714e80978 make areas and devices consistant 2025-06-21 17:05:46 +02:00
J. Nick Koston
98de53f60b migrate to using same area info for top level and sub devices 2025-06-21 16:47:03 +02:00
J. Nick Koston
41e11e9a0e migrate to using same area info for top level and sub devices 2025-06-21 16:43:48 +02:00
J. Nick Koston
e7a4eac8bd migrate to using same area info for top level and sub devices 2025-06-21 16:42:05 +02:00
J. Nick Koston
1589a131db migrate to using same area info for top level and sub devices 2025-06-21 16:39:07 +02:00
J. Nick Koston
7d84f0e650 migrate to using same area info for top level and sub devices 2025-06-21 16:37:21 +02:00
J. Nick Koston
86fb0e317f fixes 2025-06-21 15:22:35 +02:00
J. Nick Koston
32088d5ef7 revert 2025-06-21 13:35:32 +02:00
J. Nick Koston
63de88dd57 fixes 2025-06-21 13:33:29 +02:00
J. Nick Koston
153a6440dc cleanups to address review comments 2025-06-21 13:20:59 +02:00
J. Nick Koston
8937ed2269 cleanups to address review comments 2025-06-21 13:18:25 +02:00
J. Nick Koston
02e922b56f cleanups to address review comments 2025-06-21 13:16:42 +02:00
J. Nick Koston
bf9e901ab9 cleanups to address review comments 2025-06-21 13:13:44 +02:00
J. Nick Koston
1234ef8de2 Merge remote-tracking branch 'upstream/dev' into multi_device 2025-06-21 12:13:54 +02:00
Daniel Vikstrom
57f4067fbf Move fnv1a_32bit_hash to helpers 2025-06-02 14:42:39 +02:00
Daniel Vikstrom
f4a9221232 Change hash method 2025-06-02 08:31:06 +02:00
J. Nick Koston
3d4a75148d Merge branch 'dev' into multi_device 2025-05-31 10:27:31 -05:00
J. Nick Koston
c2c5bd844d Merge branch 'dev' into multi_device 2025-05-29 13:43:21 -05:00
Daniel Vikstrom
9624efa21e Fix proto generation and clang 2025-05-22 14:18:46 +02:00
DanielV
831638210d Merge branch 'dev' into multi_device 2025-05-22 08:41:54 +02:00
Daniel Vikström
a59a8c563e Attempt fixing circular import by lazy import 2025-05-06 12:30:04 +02:00
Daniel Vikström
856829bcbb More namespace and import fixes 2025-05-06 12:05:45 +02:00
Daniel Vikström
dd2b931f61 Fix namespace error 2025-05-06 11:46:23 +02:00
Daniel Vikström
39beccbbb0 remove from CODEOWNERS 2025-05-06 10:50:09 +02:00
Daniel Vikström
ff626b428f Attempt moving it to esphome config section 2025-05-06 10:48:26 +02:00
Daniel Vikström
3915e1f012 Revert "Improve stability for unrelated test"
This reverts commit 3922950951.
2025-05-06 03:36:03 +02:00
Daniel Vikström
7b460b6224 Restore ci-api-proto.yml 2025-05-06 03:34:33 +02:00
Daniel Vikström
8fb8e79730 Fix clang 2025-05-06 03:20:22 +02:00
Daniel Vikström
79bbc475f4 Fix generated files and revert entity config to device_id 2025-05-06 03:05:00 +02:00
Daniel Vikström
cef023283b Fix generated files 2025-05-06 02:55:44 +02:00
Daniel Vikström
d4fda79ada Attempt to replace device_id:str with device_uid:uint32 2025-05-06 02:07:59 +02:00
DanielV
ff0bdcf4cd Merge branch 'dev' into multi_device 2025-05-06 00:48:23 +02:00
DanielV
bfbc313144 Merge branch 'dev' into multi_device 2025-04-22 14:28:51 +02:00
Daniel Vikström
31f2376f15 Rename ref in codegen 2025-04-22 14:03:07 +02:00
DanielV
f76ecb6604 Merge pull request #10 from dala318/multi_device_2
Sub Devices (all entities)
2025-04-22 08:49:28 +02:00
Daniel Vikström
298cc58433 Activate the rest of entities 2025-04-19 23:18:26 +02:00
Daniel Vikström
825c0593e1 Fix generated code after merge 2025-04-19 19:07:50 +02:00
DanielV
87ed1dc3e3 Merge branch 'dev' into multi_device 2025-04-19 18:58:09 +02:00
DanielV
67e9db021c Merge branch 'dev' into multi_device 2025-04-14 22:21:50 +02:00
Daniel Vikström
3922950951 Improve stability for unrelated test 2025-04-14 21:37:27 +02:00
DanielV
9c4aa0ba53 Merge branch 'dev' into multi_device 2025-04-11 13:19:52 +02:00
Daniel Vikström
f5f1651b31 Fix clang 2025-04-10 09:35:08 +02:00
Daniel Vikström
32f4e4ca13 Cleaning up 2025-04-09 19:20:28 +02:00
Daniel Vikström
962e0c4c33 Make it a Class but only use the id in entities 2025-04-09 19:09:31 +02:00
Daniel Vikström
2c01bc5795 Fix clang-tidy 2025-04-09 15:22:40 +02:00
Daniel Vikström
0651f7cb3c Work on sub-device creation 2025-04-09 01:39:24 +02:00
Daniel Vikström
01ac59ce2a Store proto with all additions but commented out 2025-04-09 01:18:42 +02:00
Daniel Vikström
c1fd597757 Add CODEOWNER 2025-04-09 01:12:14 +02:00
Daniel Vikström
e79e244eee Fix generated proto-files 2025-04-09 01:09:45 +02:00
Daniel Vikström
68ecc08111 Register device_id to entity and separate struct for all device info 2025-04-09 00:11:05 +02:00
Daniel Vikström
3b5fbc359f Formating updates 2025-04-08 22:21:11 +02:00
Daniel Vikström
583e5ea47f Add code-owner tag 2025-04-08 22:21:08 +02:00
Daniel Vikström
7b647c3fae Add a single test 2025-04-08 22:21:07 +02:00
Daniel Vikström
a8b76c617c Some basic chain working 2025-04-08 22:07:09 +02:00
Daniel Vikström
1bd8985dff Add a device component 2025-04-08 22:00:09 +02:00
Daniel Vikström
25b5a6c4ae Add device_id to entity_base 2025-04-08 22:00:06 +02:00
57 changed files with 777 additions and 1140 deletions

View File

@@ -1,11 +1,28 @@
---
name: Lock closed issues and PRs
name: Lock
on:
schedule:
- cron: "30 0 * * *" # Run daily at 00:30 UTC
- cron: "30 0 * * *"
workflow_dispatch:
permissions:
issues: write
pull-requests: write
concurrency:
group: lock
jobs:
lock:
uses: esphome/workflows/.github/workflows/lock.yml@main
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@v5.0.1
with:
pr-inactive-days: "1"
pr-lock-reason: ""
exclude-any-pr-labels: keep-open
issue-inactive-days: "7"
issue-lock-reason: ""
exclude-any-issue-labels: keep-open

View File

@@ -33,14 +33,9 @@ namespace api {
// 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.
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";
#ifdef USE_ESP32_CAMERA
static const int ESP32_CAMERA_STOP_STREAM = 5000;
#endif
APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent)
: parent_(parent), initial_state_iterator_(this), list_entities_iterator_(this) {
@@ -95,6 +90,16 @@ APIConnection::~APIConnection() {
}
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_) {
// requested a disconnect
this->helper_->close();
@@ -147,19 +152,20 @@ void APIConnection::loop() {
// Process deferred batch if scheduled
if (this->deferred_batch_.batch_scheduled &&
now - this->deferred_batch_.batch_start_time >= this->get_batch_delay_ms_()) {
App.get_loop_component_start_time() - this->deferred_batch_.batch_start_time >= this->get_batch_delay_ms_()) {
this->process_batch_();
}
if (!this->list_entities_iterator_.completed()) {
if (!this->list_entities_iterator_.completed())
this->list_entities_iterator_.advance();
} else if (!this->initial_state_iterator_.completed()) {
if (!this->initial_state_iterator_.completed() && this->list_entities_iterator_.completed())
this->initial_state_iterator_.advance();
}
static uint8_t max_ping_retries = 60;
static uint16_t ping_retry_interval = 1000;
if (this->sent_ping_) {
// Disconnect if not responded within 2.5*keepalive
if (now - this->last_traffic_ > KEEPALIVE_DISCONNECT_TIMEOUT) {
if (now - this->last_traffic_ > (KEEPALIVE_TIMEOUT_MS * 5) / 2) {
on_fatal_error();
ESP_LOGW(TAG, "%s is unresponsive; disconnecting", this->get_client_combined_info().c_str());
}
@@ -167,15 +173,17 @@ void APIConnection::loop() {
ESP_LOGVV(TAG, "Sending keepalive PING");
this->sent_ping_ = this->send_message(PingRequest());
if (!this->sent_ping_) {
this->next_ping_retry_ = now + PING_RETRY_INTERVAL;
this->next_ping_retry_ = now + ping_retry_interval;
this->ping_retries_++;
if (this->ping_retries_ >= MAX_PING_RETRIES) {
std::string warn_str = str_sprintf("%s: Sending keepalive failed %u time(s);",
this->get_client_combined_info().c_str(), this->ping_retries_);
if (this->ping_retries_ >= max_ping_retries) {
on_fatal_error();
ESP_LOGE(TAG, "%s: Ping failed %u times", this->get_client_combined_info().c_str(), this->ping_retries_);
ESP_LOGE(TAG, "%s disconnecting", warn_str.c_str());
} else if (this->ping_retries_ >= 10) {
ESP_LOGW(TAG, "%s: Ping retry %u", this->get_client_combined_info().c_str(), this->ping_retries_);
ESP_LOGW(TAG, "%s retrying in %u ms", warn_str.c_str(), ping_retry_interval);
} else {
ESP_LOGD(TAG, "%s: Ping retry %u", this->get_client_combined_info().c_str(), this->ping_retries_);
ESP_LOGD(TAG, "%s retrying in %u ms", warn_str.c_str(), ping_retry_interval);
}
}
}
@@ -199,20 +207,22 @@ void APIConnection::loop() {
// bool done = 3;
buffer.encode_bool(3, done);
bool success = this->send_buffer(buffer, CameraImageResponse::MESSAGE_TYPE);
bool success = this->send_buffer(buffer, 44);
if (success) {
this->image_reader_.consume_data(to_send);
if (done) {
this->image_reader_.return_image();
}
}
if (success && done) {
this->image_reader_.return_image();
}
}
#endif
if (state_subs_at_ >= 0) {
if (state_subs_at_ != -1) {
const auto &subs = this->parent_->get_state_subs();
if (state_subs_at_ < static_cast<int>(subs.size())) {
if (state_subs_at_ >= (int) subs.size()) {
state_subs_at_ = -1;
} else {
auto &it = subs[state_subs_at_];
SubscribeHomeAssistantStateResponse resp;
resp.entity_id = it.entity_id;
@@ -221,8 +231,6 @@ void APIConnection::loop() {
if (this->send_message(resp)) {
state_subs_at_++;
}
} else {
state_subs_at_ = -1;
}
}
}
@@ -276,11 +284,6 @@ uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint16_t mes
// Encode directly into buffer
msg.encode(buffer);
#ifdef HAS_PROTO_MESSAGE_DUMP
// Log the message for VV debugging
conn->log_send_message_(msg.message_name(), msg.dump());
#endif
// Calculate actual encoded size (not including header that was already added)
size_t actual_payload_size = shared_buf.size() - size_before_encode;
@@ -1437,7 +1440,7 @@ void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRe
#ifdef USE_EVENT
void APIConnection::send_event(event::Event *event, const std::string &event_type) {
this->schedule_message_(event, MessageCreator(event_type), EventResponse::MESSAGE_TYPE);
this->schedule_message_(event, MessageCreator(event_type, EventResponse::MESSAGE_TYPE), EventResponse::MESSAGE_TYPE);
}
void APIConnection::send_event_info(event::Event *event) {
this->schedule_message_(event, &APIConnection::try_send_event_info, ListEntitiesEventResponse::MESSAGE_TYPE);
@@ -1792,8 +1795,7 @@ void APIConnection::process_batch_() {
const auto &item = this->deferred_batch_.items[0];
// 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, item.message_type);
uint16_t payload_size = item.creator(item.entity, this, std::numeric_limits<uint16_t>::max(), true);
if (payload_size > 0 &&
this->send_buffer(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, item.message_type)) {
@@ -1843,7 +1845,7 @@ void APIConnection::process_batch_() {
for (const auto &item : this->deferred_batch_.items) {
// Try to encode message
// The creator will calculate overhead to determine if the message fits
uint16_t payload_size = item.creator(item.entity, this, remaining_size, false, item.message_type);
uint16_t payload_size = item.creator(item.entity, this, remaining_size, false);
if (payload_size == 0) {
// Message won't fit, stop processing
@@ -1906,23 +1908,21 @@ void APIConnection::process_batch_() {
}
uint16_t APIConnection::MessageCreator::operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single, uint16_t message_type) const {
if (has_tagged_string_ptr_()) {
// Handle string-based messages
switch (message_type) {
bool is_single) const {
switch (message_type_) {
case 0: // Function pointer
return data_.ptr(entity, conn, remaining_size, is_single);
#ifdef USE_EVENT
case EventResponse::MESSAGE_TYPE: {
auto *e = static_cast<event::Event *>(entity);
return APIConnection::try_send_event_response(e, *get_string_ptr_(), conn, remaining_size, is_single);
}
#endif
default:
// Should not happen, return 0 to indicate no message
return 0;
case EventResponse::MESSAGE_TYPE: {
auto *e = static_cast<event::Event *>(entity);
return APIConnection::try_send_event_response(e, *data_.string_ptr, conn, remaining_size, is_single);
}
} else {
// Function pointer case
return data_.ptr(entity, conn, remaining_size, is_single);
#endif
default:
// Should not happen, return 0 to indicate no message
return 0;
}
}

View File

@@ -483,57 +483,55 @@ class APIConnection : public APIServerConnection {
// Function pointer type for message encoding
using MessageCreatorPtr = uint16_t (*)(EntityBase *, APIConnection *, uint32_t remaining_size, bool is_single);
// Optimized MessageCreator class using tagged pointer
// Optimized MessageCreator class using union dispatch
class MessageCreator {
// Ensure pointer alignment allows LSB tagging
static_assert(alignof(std::string *) > 1, "String pointer alignment must be > 1 for LSB tagging");
public:
// Constructor for function pointer
MessageCreator(MessageCreatorPtr ptr) {
// Function pointers are always aligned, so LSB is 0
data_.ptr = ptr;
}
// Constructor for function pointer (message_type = 0)
MessageCreator(MessageCreatorPtr ptr) : message_type_(0) { data_.ptr = ptr; }
// Constructor for string state capture
explicit MessageCreator(const std::string &str_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;
MessageCreator(const std::string &value, uint16_t msg_type) : message_type_(msg_type) {
data_.string_ptr = new std::string(value);
}
// Destructor
~MessageCreator() {
if (has_tagged_string_ptr_()) {
delete get_string_ptr_();
// Clean up string data for string-based message types
if (uses_string_data_()) {
delete data_.string_ptr;
}
}
// Copy constructor
MessageCreator(const MessageCreator &other) {
if (other.has_tagged_string_ptr_()) {
auto *str = new std::string(*other.get_string_ptr_());
data_.tagged = reinterpret_cast<uintptr_t>(str) | 1;
MessageCreator(const MessageCreator &other) : message_type_(other.message_type_) {
if (message_type_ == 0) {
data_.ptr = other.data_.ptr;
} else if (uses_string_data_()) {
data_.string_ptr = new std::string(*other.data_.string_ptr);
} else {
data_ = other.data_;
data_ = other.data_; // For POD types
}
}
// Move constructor
MessageCreator(MessageCreator &&other) noexcept : data_(other.data_) { other.data_.ptr = nullptr; }
MessageCreator(MessageCreator &&other) noexcept : data_(other.data_), message_type_(other.message_type_) {
other.message_type_ = 0; // Reset other to function pointer type
other.data_.ptr = nullptr;
}
// Assignment operators (needed for batch deduplication)
MessageCreator &operator=(const MessageCreator &other) {
if (this != &other) {
// Clean up current string data if needed
if (has_tagged_string_ptr_()) {
delete get_string_ptr_();
if (uses_string_data_()) {
delete data_.string_ptr;
}
// Copy new data
if (other.has_tagged_string_ptr_()) {
auto *str = new std::string(*other.get_string_ptr_());
data_.tagged = reinterpret_cast<uintptr_t>(str) | 1;
message_type_ = other.message_type_;
if (other.message_type_ == 0) {
data_.ptr = other.data_.ptr;
} else if (other.uses_string_data_()) {
data_.string_ptr = new std::string(*other.data_.string_ptr);
} else {
data_ = other.data_;
}
@@ -544,35 +542,30 @@ class APIConnection : public APIServerConnection {
MessageCreator &operator=(MessageCreator &&other) noexcept {
if (this != &other) {
// Clean up current string data if needed
if (has_tagged_string_ptr_()) {
delete get_string_ptr_();
if (uses_string_data_()) {
delete data_.string_ptr;
}
// Move data
message_type_ = other.message_type_;
data_ = other.data_;
// Reset other to safe state
other.message_type_ = 0;
other.data_.ptr = nullptr;
}
return *this;
}
// Call operator - now accepts message_type as parameter
uint16_t operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single,
uint16_t message_type) const;
// Call operator
uint16_t operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single) const;
private:
// Check if this contains a string pointer
bool has_tagged_string_ptr_() const { return (data_.tagged & 1) != 0; }
// Get the actual string pointer (clears the tag bit)
std::string *get_string_ptr_() const {
// NOLINTNEXTLINE(performance-no-int-to-ptr)
return reinterpret_cast<std::string *>(data_.tagged & ~uintptr_t(1));
}
union {
MessageCreatorPtr ptr;
uintptr_t tagged;
} data_; // 4 bytes on 32-bit
// Helper to check if this message type uses heap-allocated strings
bool uses_string_data_() const { return message_type_ == EventResponse::MESSAGE_TYPE; }
union CreatorData {
MessageCreatorPtr ptr; // 8 bytes
std::string *string_ptr; // 8 bytes
} data_; // 8 bytes
uint16_t message_type_; // 2 bytes (0 = function ptr, >0 = state capture)
};
// Generic batching mechanism for both state updates and entity info

View File

@@ -66,17 +66,6 @@ const char *api_error_to_str(APIError err) {
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
void APIFrameHelper::buffer_data_from_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len) {
SendBuffer buffer;
@@ -298,8 +287,13 @@ APIError APINoiseFrameHelper::loop() {
}
}
// Use base class implementation for buffer sending
return 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
}
/** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter
@@ -345,15 +339,17 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
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
}
// 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];
if (state_ != State::DATA && msg_size > 128) {
@@ -599,6 +595,10 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
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 data_len = (((uint16_t) msg_data[2]) << 8) | msg_data[3];
if (data_len > msg_size - 4) {
@@ -831,12 +831,18 @@ APIError APIPlaintextFrameHelper::init() {
state_ = State::DATA;
return APIError::OK;
}
/// Not used for plaintext
APIError APIPlaintextFrameHelper::loop() {
if (state_ != State::DATA) {
return APIError::BAD_STATE;
}
// Use base class implementation for buffer sending
return 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
}
/** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter

View File

@@ -38,7 +38,7 @@ struct PacketInfo {
: message_type(type), offset(off), payload_size(size), padding(0) {}
};
enum class APIError : uint16_t {
enum class APIError : int {
OK = 0,
WOULD_BLOCK = 1001,
BAD_HANDSHAKE_PACKET_LEN = 1002,
@@ -74,7 +74,7 @@ class APIFrameHelper {
}
virtual ~APIFrameHelper() = default;
virtual APIError init() = 0;
virtual APIError loop();
virtual APIError loop() = 0;
virtual APIError read_packet(ReadPacketBuffer *buffer) = 0;
bool can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); }
std::string getpeername() { return socket_->getpeername(); }

File diff suppressed because it is too large Load Diff

View File

@@ -19,7 +19,7 @@ class APIServerConnectionBase : public ProtoService {
template<typename T> bool send_message(const T &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
this->log_send_message_(msg.message_name(), msg.dump());
this->log_send_message_(T::message_name(), msg.dump());
#endif
return this->send_message_(msg, T::MESSAGE_TYPE);
}

View File

@@ -47,11 +47,6 @@ void APIServer::setup() {
}
#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
if (this->socket_ == nullptr) {
ESP_LOGW(TAG, "Could not create socket");
@@ -111,6 +106,8 @@ void APIServer::setup() {
}
#endif
this->last_connected_ = App.get_loop_component_start_time();
#ifdef USE_ESP32_CAMERA
if (esp32_camera::global_esp32_camera != nullptr && !esp32_camera::global_esp32_camera->is_internal()) {
esp32_camera::global_esp32_camera->add_image_callback(
@@ -124,16 +121,6 @@ void APIServer::setup() {
#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() {
// Accept new clients only if the socket exists and has incoming connections
if (this->socket_ && this->socket_->ready()) {
@@ -143,61 +130,51 @@ void APIServer::loop() {
auto sock = this->socket_->accept_loop_monitored((struct sockaddr *) &source_addr, &addr_len);
if (!sock)
break;
ESP_LOGD(TAG, "Accept %s", sock->getpeername().c_str());
ESP_LOGD(TAG, "Accepted %s", sock->getpeername().c_str());
auto *conn = new APIConnection(std::move(sock), this);
this->clients_.emplace_back(conn);
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");
// Process clients and remove disconnected ones in a single pass
if (!this->clients_.empty()) {
size_t client_index = 0;
while (client_index < this->clients_.size()) {
auto &client = this->clients_[client_index];
if (client->remove_) {
// 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
}
}
}
if (this->clients_.empty()) {
return;
}
// Process clients and remove disconnected ones in a single pass
// Check network connectivity once for all clients
if (!network::is_connected()) {
// Network is down - disconnect all clients
for (auto &client : this->clients_) {
client->on_fatal_error();
ESP_LOGW(TAG, "%s: Network down; disconnect", client->get_client_combined_info().c_str());
if (this->reboot_timeout_ != 0) {
const uint32_t now = App.get_loop_component_start_time();
if (!this->is_connected()) {
if (now - this->last_connected_ > this->reboot_timeout_) {
ESP_LOGE(TAG, "No client connected; rebooting");
App.reboot();
}
this->status_set_warning();
} else {
this->last_connected_ = now;
this->status_clear_warning();
}
// Continue to process and clean up the clients below
}
size_t client_index = 0;
while (client_index < this->clients_.size()) {
auto &client = this->clients_[client_index];
if (!client->remove_) {
// Common case: process active client
client->loop();
client_index++;
continue;
}
// 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
}
}

View File

@@ -142,7 +142,6 @@ class APIServer : public Component, public Controller {
}
protected:
void schedule_reboot_timeout_();
// Pointers and pointer-like types first (4 bytes each)
std::unique_ptr<socket::Socket> socket_ = nullptr;
Trigger<std::string, std::string> *client_connected_trigger_ = new Trigger<std::string, std::string>();
@@ -151,6 +150,7 @@ class APIServer : public Component, public Controller {
// 4-byte aligned types
uint32_t reboot_timeout_{300000};
uint32_t batch_delay_{100};
uint32_t last_connected_{0};
// Vectors and strings (12 bytes each on 32-bit)
std::vector<std::unique_ptr<APIConnection>> clients_;

View File

@@ -335,7 +335,6 @@ class ProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
std::string dump() const;
virtual void dump_to(std::string &out) const = 0;
virtual const char *message_name() const { return "unknown"; }
#endif
protected:

View File

@@ -148,7 +148,6 @@ BinarySensorCondition = binary_sensor_ns.class_("BinarySensorCondition", Conditi
# Filters
Filter = binary_sensor_ns.class_("Filter")
TimeoutFilter = binary_sensor_ns.class_("TimeoutFilter", Filter, cg.Component)
DelayedOnOffFilter = binary_sensor_ns.class_("DelayedOnOffFilter", Filter, cg.Component)
DelayedOnFilter = binary_sensor_ns.class_("DelayedOnFilter", Filter, cg.Component)
DelayedOffFilter = binary_sensor_ns.class_("DelayedOffFilter", Filter, cg.Component)
@@ -172,19 +171,6 @@ async def invert_filter_to_code(config, 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(
"delayed_on_off",
DelayedOnOffFilter,

View File

@@ -25,12 +25,6 @@ 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) {
if (value) {
this->set_timeout("ON_OFF", this->on_delay_.value(), [this]() { this->output(true); });

View File

@@ -16,7 +16,7 @@ class Filter {
public:
virtual optional<bool> new_value(bool value) = 0;
virtual void input(bool value);
void input(bool value);
void output(bool value);
@@ -28,16 +28,6 @@ class Filter {
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 {
public:
optional<bool> new_value(bool value) override;

View File

@@ -4,7 +4,7 @@ import logging
import os
from pathlib import Path
from esphome import yaml_util
from esphome import git
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import (
@@ -23,6 +23,7 @@ from esphome.const import (
CONF_REFRESH,
CONF_SOURCE,
CONF_TYPE,
CONF_URL,
CONF_VARIANT,
CONF_VERSION,
KEY_CORE,
@@ -31,13 +32,14 @@ from esphome.const import (
KEY_TARGET_FRAMEWORK,
KEY_TARGET_PLATFORM,
PLATFORM_ESP32,
TYPE_GIT,
TYPE_LOCAL,
__version__,
)
from esphome.core import CORE, HexInt, TimePeriod
from esphome.cpp_generator import RawExpression
import esphome.final_validate as fv
from esphome.helpers import copy_file_if_changed, mkdir_p, write_file_if_changed
from esphome.types import ConfigType
from .boards import BOARDS
from .const import ( # noqa
@@ -47,8 +49,10 @@ from .const import ( # noqa
KEY_EXTRA_BUILD_FILES,
KEY_PATH,
KEY_REF,
KEY_REFRESH,
KEY_REPO,
KEY_SDKCONFIG_OPTIONS,
KEY_SUBMODULES,
KEY_VARIANT,
VARIANT_ESP32,
VARIANT_ESP32C2,
@@ -231,7 +235,7 @@ def add_idf_sdkconfig_option(name: str, value: SdkconfigValueType):
def add_idf_component(
*,
name: str,
repo: str = None,
repo: str,
ref: str = None,
path: str = None,
refresh: TimePeriod = None,
@@ -241,27 +245,30 @@ def add_idf_component(
"""Add an esp-idf component to the project."""
if not CORE.using_esp_idf:
raise ValueError("Not an esp-idf project")
if not repo and not ref and not path:
raise ValueError("Requires at least one of repo, ref or path")
if refresh or submodules or components:
_LOGGER.warning(
"The refresh, components and submodules parameters in add_idf_component() are "
"deprecated and will be removed in ESPHome 2026.1. If you are seeing this, report "
"an issue to the external_component author and ask them to update it."
)
if components:
for comp in components:
CORE.data[KEY_ESP32][KEY_COMPONENTS][comp] = {
KEY_REPO: repo,
KEY_REF: ref,
KEY_PATH: f"{path}/{comp}" if path else comp,
}
else:
if components is None:
components = []
if name not in CORE.data[KEY_ESP32][KEY_COMPONENTS]:
CORE.data[KEY_ESP32][KEY_COMPONENTS][name] = {
KEY_REPO: repo,
KEY_REF: ref,
KEY_PATH: path,
KEY_REFRESH: refresh,
KEY_COMPONENTS: components,
KEY_SUBMODULES: submodules,
}
else:
component_config = CORE.data[KEY_ESP32][KEY_COMPONENTS][name]
if components is not None:
component_config[KEY_COMPONENTS] = list(
set(component_config[KEY_COMPONENTS] + components)
)
if submodules is not None:
if component_config[KEY_SUBMODULES] is None:
component_config[KEY_SUBMODULES] = submodules
else:
component_config[KEY_SUBMODULES] = list(
set(component_config[KEY_SUBMODULES] + submodules)
)
def add_extra_script(stage: str, filename: str, path: str):
@@ -568,17 +575,6 @@ CONF_ENABLE_LWIP_DHCP_SERVER = "enable_lwip_dhcp_server"
CONF_ENABLE_LWIP_MDNS_QUERIES = "enable_lwip_mdns_queries"
CONF_ENABLE_LWIP_BRIDGE_INTERFACE = "enable_lwip_bridge_interface"
def _validate_idf_component(config: ConfigType) -> ConfigType:
"""Validate IDF component config and warn about deprecated options."""
if CONF_REFRESH in config:
_LOGGER.warning(
"The 'refresh' option for IDF components is deprecated and has no effect. "
"It will be removed in ESPHome 2026.1. Please remove it from your configuration."
)
return config
ESP_IDF_FRAMEWORK_SCHEMA = cv.All(
cv.Schema(
{
@@ -618,19 +614,15 @@ ESP_IDF_FRAMEWORK_SCHEMA = cv.All(
}
),
cv.Optional(CONF_COMPONENTS, default=[]): cv.ensure_list(
cv.All(
cv.Schema(
{
cv.Required(CONF_NAME): cv.string_strict,
cv.Optional(CONF_SOURCE): cv.git_ref,
cv.Optional(CONF_REF): cv.string,
cv.Optional(CONF_PATH): cv.string,
cv.Optional(CONF_REFRESH): cv.All(
cv.string, cv.source_refresh
),
}
),
_validate_idf_component,
cv.Schema(
{
cv.Required(CONF_NAME): cv.string_strict,
cv.Required(CONF_SOURCE): cv.SOURCE_SCHEMA,
cv.Optional(CONF_PATH): cv.string,
cv.Optional(CONF_REFRESH, default="1d"): cv.All(
cv.string, cv.source_refresh
),
}
)
),
}
@@ -822,12 +814,18 @@ async def to_code(config):
add_idf_sdkconfig_option(name, RawSdkconfigValue(value))
for component in conf[CONF_COMPONENTS]:
add_idf_component(
name=component[CONF_NAME],
repo=component.get(CONF_SOURCE),
ref=component.get(CONF_REF),
path=component.get(CONF_PATH),
)
source = component[CONF_SOURCE]
if source[CONF_TYPE] == TYPE_GIT:
add_idf_component(
name=component[CONF_NAME],
repo=source[CONF_URL],
ref=source.get(CONF_REF),
path=component.get(CONF_PATH),
refresh=component[CONF_REFRESH],
)
elif source[CONF_TYPE] == TYPE_LOCAL:
_LOGGER.warning("Local components are not implemented yet.")
elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO:
cg.add_platformio_option("framework", "arduino")
cg.add_build_flag("-DUSE_ARDUINO")
@@ -926,26 +924,6 @@ def _write_sdkconfig():
write_file_if_changed(sdk_path, contents)
def _write_idf_component_yml():
yml_path = Path(CORE.relative_build_path("src/idf_component.yml"))
if CORE.data[KEY_ESP32][KEY_COMPONENTS]:
components: dict = CORE.data[KEY_ESP32][KEY_COMPONENTS]
dependencies = {}
for name, component in components.items():
dependency = {}
if component[KEY_REF]:
dependency["version"] = component[KEY_REF]
if component[KEY_REPO]:
dependency["git"] = component[KEY_REPO]
if component[KEY_PATH]:
dependency["path"] = component[KEY_PATH]
dependencies[name] = dependency
contents = yaml_util.dump({"dependencies": dependencies})
else:
contents = ""
write_file_if_changed(yml_path, contents)
# Called by writer.py
def copy_files():
if CORE.using_arduino:
@@ -958,7 +936,6 @@ def copy_files():
)
if CORE.using_esp_idf:
_write_sdkconfig()
_write_idf_component_yml()
if "partitions.csv" not in CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES]:
write_file_if_changed(
CORE.relative_build_path("partitions.csv"),
@@ -975,6 +952,55 @@ def copy_files():
__version__,
)
import shutil
shutil.rmtree(CORE.relative_build_path("components"), ignore_errors=True)
if CORE.data[KEY_ESP32][KEY_COMPONENTS]:
components: dict = CORE.data[KEY_ESP32][KEY_COMPONENTS]
for name, component in components.items():
repo_dir, _ = git.clone_or_update(
url=component[KEY_REPO],
ref=component[KEY_REF],
refresh=component[KEY_REFRESH],
domain="idf_components",
submodules=component[KEY_SUBMODULES],
)
mkdir_p(CORE.relative_build_path("components"))
component_dir = repo_dir
if component[KEY_PATH] is not None:
component_dir = component_dir / component[KEY_PATH]
if component[KEY_COMPONENTS] == ["*"]:
shutil.copytree(
component_dir,
CORE.relative_build_path("components"),
dirs_exist_ok=True,
ignore=shutil.ignore_patterns(".git*"),
symlinks=True,
ignore_dangling_symlinks=True,
)
elif len(component[KEY_COMPONENTS]) > 0:
for comp in component[KEY_COMPONENTS]:
shutil.copytree(
component_dir / comp,
CORE.relative_build_path(f"components/{comp}"),
dirs_exist_ok=True,
ignore=shutil.ignore_patterns(".git*"),
symlinks=True,
ignore_dangling_symlinks=True,
)
else:
shutil.copytree(
component_dir,
CORE.relative_build_path(f"components/{name}"),
dirs_exist_ok=True,
ignore=shutil.ignore_patterns(".git*"),
symlinks=True,
ignore_dangling_symlinks=True,
)
for _, file in CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES].items():
if file[KEY_PATH].startswith("http"):
import requests

View File

@@ -4,7 +4,6 @@
#include "ble_event_pool.h"
#include "esphome/core/application.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include <esp_bt.h>
@@ -517,12 +516,13 @@ void ESP32BLE::dump_config() {
break;
}
ESP_LOGCONFIG(TAG,
"BLE:\n"
" MAC address: %s\n"
"ESP32 BLE:\n"
" MAC address: %02X:%02X:%02X:%02X:%02X:%02X\n"
" IO Capability: %s",
format_mac_address_pretty(mac_address).c_str(), io_capability_s);
mac_address[0], mac_address[1], mac_address[2], mac_address[3], mac_address[4], mac_address[5],
io_capability_s);
} else {
ESP_LOGCONFIG(TAG, "Bluetooth stack is not enabled");
ESP_LOGCONFIG(TAG, "ESP32 BLE: bluetooth stack is not enabled");
}
}

View File

@@ -1,5 +0,0 @@
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."
)

View File

@@ -8,8 +8,6 @@
#include "esphome/components/sensor/sensor.h"
#endif
#include "esphome/core/application.h"
#define highbyte(val) (uint8_t)((val) >> 8)
#define lowbyte(val) (uint8_t)((val) &0xff)
@@ -17,106 +15,8 @@ namespace esphome {
namespace ld2410 {
static const char *const TAG = "ld2410";
static const char *const NO_MAC = "08:05:04:03:02:01";
static const char *const UNKNOWN_MAC = "unknown";
static const char *const VERSION_FMT = "%u.%02X.%02X%02X%02X%02X";
// Memory-efficient lookup tables
namespace {
struct StringToUint8 {
const char *str;
uint8_t value;
};
struct Uint8ToString {
uint8_t value;
const char *str;
};
constexpr StringToUint8 BAUD_RATES[] = {
{"9600", BAUD_RATE_9600}, {"19200", BAUD_RATE_19200}, {"38400", BAUD_RATE_38400},
{"57600", BAUD_RATE_57600}, {"115200", BAUD_RATE_115200}, {"230400", BAUD_RATE_230400},
{"256000", BAUD_RATE_256000}, {"460800", BAUD_RATE_460800},
};
constexpr StringToUint8 DISTANCE_RESOLUTIONS[] = {
{"0.2m", DISTANCE_RESOLUTION_0_2},
{"0.75m", DISTANCE_RESOLUTION_0_75},
};
constexpr Uint8ToString DISTANCE_RESOLUTIONS_REV[] = {
{DISTANCE_RESOLUTION_0_2, "0.2m"},
{DISTANCE_RESOLUTION_0_75, "0.75m"},
};
constexpr StringToUint8 LIGHT_FUNCTIONS[] = {
{"off", LIGHT_FUNCTION_OFF},
{"below", LIGHT_FUNCTION_BELOW},
{"above", LIGHT_FUNCTION_ABOVE},
};
constexpr Uint8ToString LIGHT_FUNCTIONS_REV[] = {
{LIGHT_FUNCTION_OFF, "off"},
{LIGHT_FUNCTION_BELOW, "below"},
{LIGHT_FUNCTION_ABOVE, "above"},
};
constexpr StringToUint8 OUT_PIN_LEVELS[] = {
{"low", OUT_PIN_LEVEL_LOW},
{"high", OUT_PIN_LEVEL_HIGH},
};
constexpr Uint8ToString OUT_PIN_LEVELS_REV[] = {
{OUT_PIN_LEVEL_LOW, "low"},
{OUT_PIN_LEVEL_HIGH, "high"},
};
// Helper functions for lookups
template<size_t N> uint8_t find_uint8(const StringToUint8 (&arr)[N], const std::string &str) {
for (const auto &entry : arr) {
if (str == entry.str)
return entry.value;
}
return 0xFF; // Not found
}
template<size_t N> const char *find_str(const Uint8ToString (&arr)[N], uint8_t value) {
for (const auto &entry : arr) {
if (value == entry.value)
return entry.str;
}
return ""; // Not found
}
} // namespace
// Commands
static const uint8_t CMD_ENABLE_CONF = 0xFF;
static const uint8_t CMD_DISABLE_CONF = 0xFE;
static const uint8_t CMD_ENABLE_ENG = 0x62;
static const uint8_t CMD_DISABLE_ENG = 0x63;
static const uint8_t CMD_MAXDIST_DURATION = 0x60;
static const uint8_t CMD_QUERY = 0x61;
static const uint8_t CMD_GATE_SENS = 0x64;
static const uint8_t CMD_VERSION = 0xA0;
static const uint8_t CMD_QUERY_DISTANCE_RESOLUTION = 0xAB;
static const uint8_t CMD_SET_DISTANCE_RESOLUTION = 0xAA;
static const uint8_t CMD_QUERY_LIGHT_CONTROL = 0xAE;
static const uint8_t CMD_SET_LIGHT_CONTROL = 0xAD;
static const uint8_t CMD_SET_BAUD_RATE = 0xA1;
static const uint8_t CMD_BT_PASSWORD = 0xA9;
static const uint8_t CMD_MAC = 0xA5;
static const uint8_t CMD_RESET = 0xA2;
static const uint8_t CMD_RESTART = 0xA3;
static const uint8_t CMD_BLUETOOTH = 0xA4;
// Commands values
static const uint8_t CMD_MAX_MOVE_VALUE = 0x00;
static const uint8_t CMD_MAX_STILL_VALUE = 0x01;
static const uint8_t CMD_DURATION_VALUE = 0x02;
// Command Header & Footer
static const uint8_t CMD_FRAME_HEADER[4] = {0xFD, 0xFC, 0xFB, 0xFA};
static const uint8_t CMD_FRAME_END[4] = {0x04, 0x03, 0x02, 0x01};
// Data Header & Footer
static const uint8_t DATA_FRAME_HEADER[4] = {0xF4, 0xF3, 0xF2, 0xF1};
static const uint8_t DATA_FRAME_END[4] = {0xF8, 0xF7, 0xF6, 0xF5};
LD2410Component::LD2410Component() {}
void LD2410Component::dump_config() {
ESP_LOGCONFIG(TAG, "LD2410:");
@@ -173,10 +73,10 @@ void LD2410Component::dump_config() {
#endif
this->read_all_info();
ESP_LOGCONFIG(TAG,
" Throttle: %ums\n"
" MAC address: %s\n"
" Firmware version: %s",
this->throttle_, this->mac_ == NO_MAC ? UNKNOWN_MAC : this->mac_.c_str(), this->version_.c_str());
" Throttle_ : %ums\n"
" MAC Address : %s\n"
" Firmware Version : %s",
this->throttle_, const_cast<char *>(this->mac_.c_str()), const_cast<char *>(this->version_.c_str()));
}
void LD2410Component::setup() {
@@ -253,7 +153,7 @@ void LD2410Component::handle_periodic_data_(uint8_t *buffer, int len) {
/*
Reduce data update rate to prevent home assistant database size grow fast
*/
int32_t current_millis = App.get_loop_component_start_time();
int32_t current_millis = millis();
if (current_millis - last_periodic_millis_ < this->throttle_)
return;
last_periodic_millis_ = current_millis;
@@ -298,7 +198,7 @@ void LD2410Component::handle_periodic_data_(uint8_t *buffer, int len) {
*/
#ifdef USE_SENSOR
if (this->moving_target_distance_sensor_ != nullptr) {
int new_moving_target_distance = ld2410::two_byte_to_int(buffer[MOVING_TARGET_LOW], buffer[MOVING_TARGET_HIGH]);
int new_moving_target_distance = this->two_byte_to_int_(buffer[MOVING_TARGET_LOW], buffer[MOVING_TARGET_HIGH]);
if (this->moving_target_distance_sensor_->get_state() != new_moving_target_distance)
this->moving_target_distance_sensor_->publish_state(new_moving_target_distance);
}
@@ -308,7 +208,7 @@ void LD2410Component::handle_periodic_data_(uint8_t *buffer, int len) {
this->moving_target_energy_sensor_->publish_state(new_moving_target_energy);
}
if (this->still_target_distance_sensor_ != nullptr) {
int new_still_target_distance = ld2410::two_byte_to_int(buffer[STILL_TARGET_LOW], buffer[STILL_TARGET_HIGH]);
int new_still_target_distance = this->two_byte_to_int_(buffer[STILL_TARGET_LOW], buffer[STILL_TARGET_HIGH]);
if (this->still_target_distance_sensor_->get_state() != new_still_target_distance)
this->still_target_distance_sensor_->publish_state(new_still_target_distance);
}
@@ -318,7 +218,7 @@ void LD2410Component::handle_periodic_data_(uint8_t *buffer, int len) {
this->still_target_energy_sensor_->publish_state(new_still_target_energy);
}
if (this->detection_distance_sensor_ != nullptr) {
int new_detect_distance = ld2410::two_byte_to_int(buffer[DETECT_DISTANCE_LOW], buffer[DETECT_DISTANCE_HIGH]);
int new_detect_distance = this->two_byte_to_int_(buffer[DETECT_DISTANCE_LOW], buffer[DETECT_DISTANCE_HIGH]);
if (this->detection_distance_sensor_->get_state() != new_detect_distance)
this->detection_distance_sensor_->publish_state(new_detect_distance);
}
@@ -380,6 +280,40 @@ void LD2410Component::handle_periodic_data_(uint8_t *buffer, int len) {
#endif
}
const char VERSION_FMT[] = "%u.%02X.%02X%02X%02X%02X";
std::string format_version(uint8_t *buffer) {
std::string::size_type version_size = 256;
std::string version;
do {
version.resize(version_size + 1);
version_size = std::snprintf(&version[0], version.size(), VERSION_FMT, buffer[13], buffer[12], buffer[17],
buffer[16], buffer[15], buffer[14]);
} while (version_size + 1 > version.size());
version.resize(version_size);
return version;
}
const char MAC_FMT[] = "%02X:%02X:%02X:%02X:%02X:%02X";
const std::string UNKNOWN_MAC("unknown");
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
std::function<void(void)> set_number_value(number::Number *n, float value) {
float normalized_value = value * 1.0;
@@ -394,40 +328,40 @@ std::function<void(void)> set_number_value(number::Number *n, float value) {
bool LD2410Component::handle_ack_data_(uint8_t *buffer, int len) {
ESP_LOGV(TAG, "Handling ACK DATA for COMMAND %02X", buffer[COMMAND]);
if (len < 10) {
ESP_LOGE(TAG, "Invalid length");
ESP_LOGE(TAG, "Error with last command : incorrect length");
return true;
}
if (buffer[0] != 0xFD || buffer[1] != 0xFC || buffer[2] != 0xFB || buffer[3] != 0xFA) { // check 4 frame start bytes
ESP_LOGE(TAG, "Invalid header");
ESP_LOGE(TAG, "Error with last command : incorrect Header");
return true;
}
if (buffer[COMMAND_STATUS] != 0x01) {
ESP_LOGE(TAG, "Invalid status");
ESP_LOGE(TAG, "Error with last command : status != 0x01");
return true;
}
if (ld2410::two_byte_to_int(buffer[8], buffer[9]) != 0x00) {
ESP_LOGE(TAG, "Invalid command: %u, %u", buffer[8], buffer[9]);
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]);
return true;
}
switch (buffer[COMMAND]) {
case lowbyte(CMD_ENABLE_CONF):
ESP_LOGV(TAG, "Enable conf");
ESP_LOGV(TAG, "Handled Enable conf command");
break;
case lowbyte(CMD_DISABLE_CONF):
ESP_LOGV(TAG, "Disabled conf");
ESP_LOGV(TAG, "Handled Disabled conf command");
break;
case lowbyte(CMD_SET_BAUD_RATE):
ESP_LOGV(TAG, "Baud rate change");
ESP_LOGV(TAG, "Handled baud rate change command");
#ifdef USE_SELECT
if (this->baud_rate_select_ != nullptr) {
ESP_LOGE(TAG, "Configure baud rate to %s and reinstall", this->baud_rate_select_->state.c_str());
ESP_LOGE(TAG, "Change baud rate component config to %s and reinstall", this->baud_rate_select_->state.c_str());
}
#endif
break;
case lowbyte(CMD_VERSION):
this->version_ = str_sprintf(VERSION_FMT, buffer[13], buffer[12], buffer[17], buffer[16], buffer[15], buffer[14]);
ESP_LOGV(TAG, "Firmware version: %s", this->version_.c_str());
this->version_ = format_version(buffer);
ESP_LOGV(TAG, "FW Version is: %s", const_cast<char *>(this->version_.c_str()));
#ifdef USE_TEXT_SENSOR
if (this->version_text_sensor_ != nullptr) {
this->version_text_sensor_->publish_state(this->version_);
@@ -436,8 +370,8 @@ bool LD2410Component::handle_ack_data_(uint8_t *buffer, int len) {
break;
case lowbyte(CMD_QUERY_DISTANCE_RESOLUTION): {
std::string distance_resolution =
find_str(DISTANCE_RESOLUTIONS_REV, ld2410::two_byte_to_int(buffer[10], buffer[11]));
ESP_LOGV(TAG, "Distance resolution: %s", distance_resolution.c_str());
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()));
#ifdef USE_SELECT
if (this->distance_resolution_select_ != nullptr &&
this->distance_resolution_select_->state != distance_resolution) {
@@ -446,12 +380,12 @@ bool LD2410Component::handle_ack_data_(uint8_t *buffer, int len) {
#endif
} break;
case lowbyte(CMD_QUERY_LIGHT_CONTROL): {
this->light_function_ = find_str(LIGHT_FUNCTIONS_REV, buffer[10]);
this->light_function_ = LIGHT_FUNCTION_INT_TO_ENUM.at(buffer[10]);
this->light_threshold_ = buffer[11] * 1.0;
this->out_pin_level_ = find_str(OUT_PIN_LEVELS_REV, buffer[12]);
ESP_LOGV(TAG, "Light function: %s", const_cast<char *>(this->light_function_.c_str()));
ESP_LOGV(TAG, "Light threshold: %f", this->light_threshold_);
ESP_LOGV(TAG, "Out pin level: %s", const_cast<char *>(this->out_pin_level_.c_str()));
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 threshold is: %f", this->light_threshold_);
ESP_LOGV(TAG, "Out pin level is: %s", const_cast<char *>(this->out_pin_level_.c_str()));
#ifdef USE_SELECT
if (this->light_function_select_ != nullptr && this->light_function_select_->state != this->light_function_) {
this->light_function_select_->publish_state(this->light_function_);
@@ -472,33 +406,33 @@ bool LD2410Component::handle_ack_data_(uint8_t *buffer, int len) {
if (len < 20) {
return false;
}
this->mac_ = format_mac_address_pretty(&buffer[10]);
ESP_LOGV(TAG, "MAC address: %s", this->mac_.c_str());
this->mac_ = format_mac(buffer);
ESP_LOGV(TAG, "MAC Address is: %s", const_cast<char *>(this->mac_.c_str()));
#ifdef USE_TEXT_SENSOR
if (this->mac_text_sensor_ != nullptr) {
this->mac_text_sensor_->publish_state(this->mac_ == NO_MAC ? UNKNOWN_MAC : this->mac_);
this->mac_text_sensor_->publish_state(this->mac_);
}
#endif
#ifdef USE_SWITCH
if (this->bluetooth_switch_ != nullptr) {
this->bluetooth_switch_->publish_state(this->mac_ != NO_MAC);
this->bluetooth_switch_->publish_state(this->mac_ != UNKNOWN_MAC);
}
#endif
break;
case lowbyte(CMD_GATE_SENS):
ESP_LOGV(TAG, "Sensitivity");
ESP_LOGV(TAG, "Handled sensitivity command");
break;
case lowbyte(CMD_BLUETOOTH):
ESP_LOGV(TAG, "Bluetooth");
ESP_LOGV(TAG, "Handled bluetooth command");
break;
case lowbyte(CMD_SET_DISTANCE_RESOLUTION):
ESP_LOGV(TAG, "Set distance resolution");
ESP_LOGV(TAG, "Handled set distance resolution command");
break;
case lowbyte(CMD_SET_LIGHT_CONTROL):
ESP_LOGV(TAG, "Set light control");
ESP_LOGV(TAG, "Handled set light control command");
break;
case lowbyte(CMD_BT_PASSWORD):
ESP_LOGV(TAG, "Set bluetooth password");
ESP_LOGV(TAG, "Handled set bluetooth password command");
break;
case lowbyte(CMD_QUERY): // Query parameters response
{
@@ -527,7 +461,7 @@ bool LD2410Component::handle_ack_data_(uint8_t *buffer, int len) {
/*
None Duration: 33~34th bytes
*/
updates.push_back(set_number_value(this->timeout_number_, ld2410::two_byte_to_int(buffer[32], buffer[33])));
updates.push_back(set_number_value(this->timeout_number_, this->two_byte_to_int_(buffer[32], buffer[33])));
for (auto &update : updates) {
update();
}
@@ -584,21 +518,21 @@ void LD2410Component::set_bluetooth(bool enable) {
void LD2410Component::set_distance_resolution(const std::string &state) {
this->set_config_mode_(true);
uint8_t cmd_value[2] = {find_uint8(DISTANCE_RESOLUTIONS, state), 0x00};
uint8_t cmd_value[2] = {DISTANCE_RESOLUTION_ENUM_TO_INT.at(state), 0x00};
this->send_command_(CMD_SET_DISTANCE_RESOLUTION, cmd_value, 2);
this->set_timeout(200, [this]() { this->restart_and_read_all_info(); });
}
void LD2410Component::set_baud_rate(const std::string &state) {
this->set_config_mode_(true);
uint8_t cmd_value[2] = {find_uint8(BAUD_RATES, state), 0x00};
uint8_t cmd_value[2] = {BAUD_RATE_ENUM_TO_INT.at(state), 0x00};
this->send_command_(CMD_SET_BAUD_RATE, cmd_value, 2);
this->set_timeout(200, [this]() { this->restart_(); });
}
void LD2410Component::set_bluetooth_password(const std::string &password) {
if (password.length() != 6) {
ESP_LOGE(TAG, "Password must be exactly 6 chars");
ESP_LOGE(TAG, "set_bluetooth_password(): invalid password length, must be exactly 6 chars '%s'", password.c_str());
return;
}
this->set_config_mode_(true);
@@ -610,7 +544,7 @@ void LD2410Component::set_bluetooth_password(const std::string &password) {
void LD2410Component::set_engineering_mode(bool enable) {
this->set_config_mode_(true);
last_engineering_mode_change_millis_ = App.get_loop_component_start_time();
last_engineering_mode_change_millis_ = millis();
uint8_t cmd = enable ? CMD_ENABLE_ENG : CMD_DISABLE_ENG;
this->send_command_(cmd, nullptr, 0);
this->set_config_mode_(false);
@@ -725,9 +659,9 @@ void LD2410Component::set_light_out_control() {
return;
}
this->set_config_mode_(true);
uint8_t light_function = find_uint8(LIGHT_FUNCTIONS, this->light_function_);
uint8_t light_function = LIGHT_FUNCTION_ENUM_TO_INT.at(this->light_function_);
uint8_t light_threshold = static_cast<uint8_t>(this->light_threshold_);
uint8_t out_pin_level = find_uint8(OUT_PIN_LEVELS, this->out_pin_level_);
uint8_t out_pin_level = OUT_PIN_LEVEL_ENUM_TO_INT.at(this->out_pin_level_);
uint8_t value[4] = {light_function, light_threshold, out_pin_level, 0x00};
this->send_command_(CMD_SET_LIGHT_CONTROL, value, 4);
delay(50); // NOLINT

View File

@@ -26,11 +26,33 @@
#include "esphome/core/automation.h"
#include "esphome/core/helpers.h"
#include <map>
namespace esphome {
namespace ld2410 {
#define CHECK_BIT(var, pos) (((var) >> (pos)) & 1)
// Commands
static const uint8_t CMD_ENABLE_CONF = 0x00FF;
static const uint8_t CMD_DISABLE_CONF = 0x00FE;
static const uint8_t CMD_ENABLE_ENG = 0x0062;
static const uint8_t CMD_DISABLE_ENG = 0x0063;
static const uint8_t CMD_MAXDIST_DURATION = 0x0060;
static const uint8_t CMD_QUERY = 0x0061;
static const uint8_t CMD_GATE_SENS = 0x0064;
static const uint8_t CMD_VERSION = 0x00A0;
static const uint8_t CMD_QUERY_DISTANCE_RESOLUTION = 0x00AB;
static const uint8_t CMD_SET_DISTANCE_RESOLUTION = 0x00AA;
static const uint8_t CMD_QUERY_LIGHT_CONTROL = 0x00AE;
static const uint8_t CMD_SET_LIGHT_CONTROL = 0x00AD;
static const uint8_t CMD_SET_BAUD_RATE = 0x00A1;
static const uint8_t CMD_BT_PASSWORD = 0x00A9;
static const uint8_t CMD_MAC = 0x00A5;
static const uint8_t CMD_RESET = 0x00A2;
static const uint8_t CMD_RESTART = 0x00A3;
static const uint8_t CMD_BLUETOOTH = 0x00A4;
enum BaudRateStructure : uint8_t {
BAUD_RATE_9600 = 1,
BAUD_RATE_19200 = 2,
@@ -39,25 +61,58 @@ enum BaudRateStructure : uint8_t {
BAUD_RATE_115200 = 5,
BAUD_RATE_230400 = 6,
BAUD_RATE_256000 = 7,
BAUD_RATE_460800 = 8,
BAUD_RATE_460800 = 8
};
enum DistanceResolutionStructure : uint8_t {
DISTANCE_RESOLUTION_0_2 = 0x01,
DISTANCE_RESOLUTION_0_75 = 0x00,
};
static const std::map<std::string, uint8_t> BAUD_RATE_ENUM_TO_INT{
{"9600", BAUD_RATE_9600}, {"19200", BAUD_RATE_19200}, {"38400", BAUD_RATE_38400},
{"57600", BAUD_RATE_57600}, {"115200", BAUD_RATE_115200}, {"230400", BAUD_RATE_230400},
{"256000", BAUD_RATE_256000}, {"460800", BAUD_RATE_460800}};
enum DistanceResolutionStructure : uint8_t { DISTANCE_RESOLUTION_0_2 = 0x01, DISTANCE_RESOLUTION_0_75 = 0x00 };
static const std::map<std::string, uint8_t> DISTANCE_RESOLUTION_ENUM_TO_INT{{"0.2m", DISTANCE_RESOLUTION_0_2},
{"0.75m", DISTANCE_RESOLUTION_0_75}};
static const std::map<uint8_t, std::string> DISTANCE_RESOLUTION_INT_TO_ENUM{{DISTANCE_RESOLUTION_0_2, "0.2m"},
{DISTANCE_RESOLUTION_0_75, "0.75m"}};
enum LightFunctionStructure : uint8_t {
LIGHT_FUNCTION_OFF = 0x00,
LIGHT_FUNCTION_BELOW = 0x01,
LIGHT_FUNCTION_ABOVE = 0x02,
LIGHT_FUNCTION_ABOVE = 0x02
};
enum OutPinLevelStructure : uint8_t {
OUT_PIN_LEVEL_LOW = 0x00,
OUT_PIN_LEVEL_HIGH = 0x01,
};
static const std::map<std::string, uint8_t> LIGHT_FUNCTION_ENUM_TO_INT{
{"off", LIGHT_FUNCTION_OFF}, {"below", LIGHT_FUNCTION_BELOW}, {"above", LIGHT_FUNCTION_ABOVE}};
static const std::map<uint8_t, std::string> LIGHT_FUNCTION_INT_TO_ENUM{
{LIGHT_FUNCTION_OFF, "off"}, {LIGHT_FUNCTION_BELOW, "below"}, {LIGHT_FUNCTION_ABOVE, "above"}};
enum OutPinLevelStructure : uint8_t { OUT_PIN_LEVEL_LOW = 0x00, OUT_PIN_LEVEL_HIGH = 0x01 };
static const std::map<std::string, uint8_t> OUT_PIN_LEVEL_ENUM_TO_INT{{"low", OUT_PIN_LEVEL_LOW},
{"high", OUT_PIN_LEVEL_HIGH}};
static const std::map<uint8_t, std::string> OUT_PIN_LEVEL_INT_TO_ENUM{{OUT_PIN_LEVEL_LOW, "low"},
{OUT_PIN_LEVEL_HIGH, "high"}};
// Commands values
static const uint8_t CMD_MAX_MOVE_VALUE = 0x0000;
static const uint8_t CMD_MAX_STILL_VALUE = 0x0001;
static const uint8_t CMD_DURATION_VALUE = 0x0002;
// Command Header & Footer
static const uint8_t CMD_FRAME_HEADER[4] = {0xFD, 0xFC, 0xFB, 0xFA};
static const uint8_t CMD_FRAME_END[4] = {0x04, 0x03, 0x02, 0x01};
// Data Header & Footer
static const uint8_t DATA_FRAME_HEADER[4] = {0xF4, 0xF3, 0xF2, 0xF1};
static const uint8_t DATA_FRAME_END[4] = {0xF8, 0xF7, 0xF6, 0xF5};
/*
Data Type: 6th byte
Target states: 9th byte
Moving target distance: 10~11th bytes
Moving target energy: 12th byte
Still target distance: 13~14th bytes
Still target energy: 15th byte
Detect distance: 16~17th bytes
*/
enum PeriodicDataStructure : uint8_t {
DATA_TYPES = 6,
TARGET_STATES = 8,
@@ -74,20 +129,11 @@ enum PeriodicDataStructure : uint8_t {
LIGHT_SENSOR = 37,
OUT_PIN_SENSOR = 38,
};
enum PeriodicDataValue : uint8_t { HEAD = 0xAA, END = 0x55, CHECK = 0x00 };
enum PeriodicDataValue : uint8_t {
HEAD = 0xAA,
END = 0x55,
CHECK = 0x00,
};
enum AckDataStructure : uint8_t {
COMMAND = 6,
COMMAND_STATUS = 7,
};
static inline int two_byte_to_int(char firstbyte, char secondbyte) { return (int16_t) (secondbyte << 8) + firstbyte; }
enum AckDataStructure : uint8_t { COMMAND = 6, COMMAND_STATUS = 7 };
// char cmd[2] = {enable ? 0xFF : 0xFE, 0x00};
class LD2410Component : public Component, public uart::UARTDevice {
#ifdef USE_SENSOR
SUB_SENSOR(moving_target_distance)
@@ -130,6 +176,7 @@ class LD2410Component : public Component, public uart::UARTDevice {
#endif
public:
LD2410Component();
void setup() override;
void dump_config() override;
void loop() override;
@@ -155,6 +202,7 @@ class LD2410Component : public Component, public uart::UARTDevice {
void factory_reset();
protected:
int two_byte_to_int_(char firstbyte, char secondbyte) { return (int16_t) (secondbyte << 8) + firstbyte; }
void send_command_(uint8_t command_str, const uint8_t *command_value, int command_value_len);
void set_config_mode_(bool enable);
void handle_periodic_data_(uint8_t *buffer, int len);
@@ -167,14 +215,14 @@ class LD2410Component : public Component, public uart::UARTDevice {
void get_light_control_();
void restart_();
int32_t last_periodic_millis_ = 0;
int32_t last_engineering_mode_change_millis_ = 0;
int32_t last_periodic_millis_ = millis();
int32_t last_engineering_mode_change_millis_ = millis();
uint16_t throttle_;
float light_threshold_ = -1;
std::string version_;
std::string mac_;
std::string out_pin_level_;
std::string light_function_;
float light_threshold_ = -1;
#ifdef USE_NUMBER
std::vector<number::Number *> gate_still_threshold_numbers_ = std::vector<number::Number *>(9);
std::vector<number::Number *> gate_move_threshold_numbers_ = std::vector<number::Number *>(9);

View File

@@ -1,5 +1,4 @@
#include "ld2420.h"
#include "esphome/core/application.h"
#include "esphome/core/helpers.h"
/*
@@ -41,7 +40,7 @@ There are three documented parameters for modes:
00 04 = Energy output mode
This mode outputs detailed signal energy values for each gate and the target distance.
The data format consist of the following.
Header HH, Length LL, Presence PP, Distance DD, 16 Gate Energies EE, Footer FF
Header HH, Length LL, Persence 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
F4 F3 F2 F1 23 00 00 00 00 00 00 .. .. .. .. F8 F7 F6 F5
00 00 = debug output mode
@@ -68,10 +67,10 @@ float LD2420Component::get_setup_priority() const { return setup_priority::BUS;
void LD2420Component::dump_config() {
ESP_LOGCONFIG(TAG,
"LD2420:\n"
" Firmware version: %7s",
" Firmware Version : %7s\n"
"LD2420 Number:",
this->ld2420_firmware_ver_);
#ifdef USE_NUMBER
ESP_LOGCONFIG(TAG, "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 Min Distance:", this->min_gate_distance_number_);
@@ -87,10 +86,10 @@ void LD2420Component::dump_config() {
LOG_BUTTON(TAG, " Factory Reset:", this->factory_reset_button_);
LOG_BUTTON(TAG, " Restart Module:", this->restart_module_button_);
#endif
ESP_LOGCONFIG(TAG, "Select:");
ESP_LOGCONFIG(TAG, "LD2420 Select:");
LOG_SELECT(TAG, " Operating Mode", this->operating_selector_);
if (LD2420Component::get_firmware_int(this->ld2420_firmware_ver_) < CALIBRATE_VERSION_MIN) {
ESP_LOGW(TAG, "Firmware version %s and older supports Simple Mode only", this->ld2420_firmware_ver_);
if (this->get_firmware_int_(ld2420_firmware_ver_) < CALIBRATE_VERSION_MIN) {
ESP_LOGW(TAG, "LD2420 Firmware Version %s and older are only supported in Simple Mode", ld2420_firmware_ver_);
}
}
@@ -103,7 +102,7 @@ uint8_t LD2420Component::calc_checksum(void *data, size_t size) {
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;
if (version_str[0] == 'v') {
version_str = version_str.substr(1);
@@ -116,7 +115,7 @@ int LD2420Component::get_firmware_int(const char *version_string) {
void LD2420Component::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
ESP_LOGE(TAG, "LD2420 module has failed to respond, check baud rate and serial connections.");
this->mark_failed();
return;
}
@@ -128,7 +127,7 @@ void LD2420Component::setup() {
const char *pfw = this->ld2420_firmware_ver_;
std::string fw_str(pfw);
for (auto &listener : this->listeners_) {
for (auto &listener : listeners_) {
listener->on_fw_version(fw_str);
}
@@ -138,11 +137,11 @@ void LD2420Component::setup() {
}
memcpy(&this->new_config, &this->current_config, sizeof(this->current_config));
if (LD2420Component::get_firmware_int(this->ld2420_firmware_ver_) < CALIBRATE_VERSION_MIN) {
if (get_firmware_int_(ld2420_firmware_ver_) < CALIBRATE_VERSION_MIN) {
this->set_operating_mode(OP_SIMPLE_MODE_STRING);
this->operating_selector_->publish_state(OP_SIMPLE_MODE_STRING);
this->set_mode_(CMD_SYSTEM_MODE_SIMPLE);
ESP_LOGW(TAG, "Firmware version %s and older supports Simple Mode only", this->ld2420_firmware_ver_);
ESP_LOGW(TAG, "LD2420 Frimware Version %s and older are only supported in Simple Mode", ld2420_firmware_ver_);
} else {
this->set_mode_(CMD_SYSTEM_MODE_ENERGY);
this->operating_selector_->publish_state(OP_NORMAL_MODE_STRING);
@@ -152,17 +151,18 @@ void LD2420Component::setup() {
#endif
this->set_system_mode(this->system_mode_);
this->set_config_mode(false);
ESP_LOGCONFIG(TAG, "LD2420 setup complete.");
}
void LD2420Component::apply_config_action() {
const uint8_t checksum = calc_checksum(&this->new_config, sizeof(this->new_config));
if (checksum == calc_checksum(&this->current_config, sizeof(this->current_config))) {
ESP_LOGD(TAG, "No configuration change detected");
ESP_LOGCONFIG(TAG, "No configuration change detected");
return;
}
ESP_LOGD(TAG, "Reconfiguring");
ESP_LOGCONFIG(TAG, "Reconfiguring LD2420");
if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
ESP_LOGE(TAG, "LD2420 module has failed to respond, check baud rate and serial connections.");
this->mark_failed();
return;
}
@@ -178,12 +178,13 @@ void LD2420Component::apply_config_action() {
this->set_system_mode(this->system_mode_);
this->set_config_mode(false); // Disable config mode to save new values in LD2420 nvm
this->set_operating_mode(OP_NORMAL_MODE_STRING);
ESP_LOGCONFIG(TAG, "LD2420 reconfig complete.");
}
void LD2420Component::factory_reset_action() {
ESP_LOGD(TAG, "Setting factory defaults");
ESP_LOGCONFIG(TAG, "Setting factory defaults");
if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
ESP_LOGE(TAG, "LD2420 module has failed to respond, check baud rate and serial connections.");
this->mark_failed();
return;
}
@@ -206,16 +207,18 @@ void LD2420Component::factory_reset_action() {
this->init_gate_config_numbers();
this->refresh_gate_config_numbers();
#endif
ESP_LOGCONFIG(TAG, "LD2420 factory reset complete.");
}
void LD2420Component::restart_module_action() {
ESP_LOGD(TAG, "Restarting");
ESP_LOGCONFIG(TAG, "Restarting LD2420 module");
this->send_module_restart();
this->set_timeout(250, [this]() {
this->set_config_mode(true);
this->set_system_mode(this->system_mode_);
this->set_system_mode(system_mode_);
this->set_config_mode(false);
});
ESP_LOGCONFIG(TAG, "LD2420 Restarted.");
}
void LD2420Component::revert_config_action() {
@@ -223,18 +226,18 @@ void LD2420Component::revert_config_action() {
#ifdef USE_NUMBER
this->init_gate_config_numbers();
#endif
ESP_LOGD(TAG, "Reverted config number edits");
ESP_LOGCONFIG(TAG, "Reverted config number edits.");
}
void LD2420Component::loop() {
// If there is a active send command do not process it here, the send command call will handle it.
if (!this->get_cmd_active_()) {
if (!this->available())
if (!get_cmd_active_()) {
if (!available())
return;
static uint8_t buffer[2048];
static uint8_t rx_data;
while (this->available()) {
rx_data = this->read();
while (available()) {
rx_data = read();
this->readline_(rx_data, buffer, sizeof(buffer));
}
}
@@ -289,7 +292,7 @@ void LD2420Component::report_gate_data() {
void LD2420Component::set_operating_mode(const std::string &state) {
// If unsupported firmware ignore mode select
if (LD2420Component::get_firmware_int(ld2420_firmware_ver_) >= CALIBRATE_VERSION_MIN) {
if (get_firmware_int_(ld2420_firmware_ver_) >= CALIBRATE_VERSION_MIN) {
this->current_operating_mode = OP_MODE_TO_UINT.at(state);
// Entering Auto Calibrate we need to clear the privoiuos data collection
this->operating_selector_->publish_state(state);
@@ -362,13 +365,13 @@ void LD2420Component::handle_energy_mode_(uint8_t *buffer, int len) {
}
// Resonable refresh rate for home assistant database size health
const int32_t current_millis = App.get_loop_component_start_time();
const int32_t current_millis = millis();
if (current_millis - this->last_periodic_millis < REFRESH_RATE_MS)
return;
this->last_periodic_millis = current_millis;
for (auto &listener : this->listeners_) {
listener->on_distance(this->get_distance_());
listener->on_presence(this->get_presence_());
listener->on_distance(get_distance_());
listener->on_presence(get_presence_());
listener->on_energy(this->gate_energy_, sizeof(this->gate_energy_) / sizeof(this->gate_energy_[0]));
}
@@ -389,9 +392,9 @@ void LD2420Component::handle_simple_mode_(const uint8_t *inbuf, int len) {
char outbuf[bufsize]{0};
while (true) {
if (inbuf[pos - 2] == 'O' && inbuf[pos - 1] == 'F' && inbuf[pos] == 'F') {
this->set_presence_(false);
set_presence_(false);
} else if (inbuf[pos - 1] == 'O' && inbuf[pos] == 'N') {
this->set_presence_(true);
set_presence_(true);
}
if (inbuf[pos] >= '0' && inbuf[pos] <= '9') {
if (index < bufsize - 1) {
@@ -408,18 +411,18 @@ void LD2420Component::handle_simple_mode_(const uint8_t *inbuf, int len) {
}
outbuf[index] = '\0';
if (index > 1)
this->set_distance_(strtol(outbuf, &endptr, 10));
set_distance_(strtol(outbuf, &endptr, 10));
if (this->get_mode_() == CMD_SYSTEM_MODE_SIMPLE) {
if (get_mode_() == CMD_SYSTEM_MODE_SIMPLE) {
// Resonable refresh rate for home assistant database size health
const int32_t current_millis = App.get_loop_component_start_time();
const int32_t current_millis = millis();
if (current_millis - this->last_normal_periodic_millis < REFRESH_RATE_MS)
return;
this->last_normal_periodic_millis = current_millis;
for (auto &listener : this->listeners_)
listener->on_distance(this->get_distance_());
listener->on_distance(get_distance_());
for (auto &listener : this->listeners_)
listener->on_presence(this->get_presence_());
listener->on_presence(get_presence_());
}
}
@@ -430,10 +433,10 @@ void LD2420Component::handle_ack_data_(uint8_t *buffer, int len) {
uint8_t data_element = 0;
uint16_t data_pos = 0;
if (this->cmd_reply_.length > CMD_MAX_BYTES) {
ESP_LOGW(TAG, "Reply frame too long");
ESP_LOGW(TAG, "LD2420 reply - received command reply frame is corrupt, length exceeds %d bytes.", CMD_MAX_BYTES);
return;
} else if (this->cmd_reply_.length < 2) {
ESP_LOGW(TAG, "Command frame too short");
ESP_LOGW(TAG, "LD2420 reply - received command frame is corrupt, length is less than 2 bytes.");
return;
}
memcpy(&this->cmd_reply_.error, &buffer[CMD_ERROR_WORD], sizeof(this->cmd_reply_.error));
@@ -444,13 +447,13 @@ void LD2420Component::handle_ack_data_(uint8_t *buffer, int len) {
this->cmd_reply_.ack = true;
switch ((uint16_t) this->cmd_reply_.command) {
case (CMD_ENABLE_CONF):
ESP_LOGV(TAG, "Set config enable: CMD = %2X %s", CMD_ENABLE_CONF, result);
ESP_LOGD(TAG, "LD2420 reply - set config enable: CMD = %2X %s", CMD_ENABLE_CONF, result);
break;
case (CMD_DISABLE_CONF):
ESP_LOGV(TAG, "Set config disable: CMD = %2X %s", CMD_DISABLE_CONF, result);
ESP_LOGD(TAG, "LD2420 reply - set config disable: CMD = %2X %s", CMD_DISABLE_CONF, result);
break;
case (CMD_READ_REGISTER):
ESP_LOGV(TAG, "Read register: CMD = %2X %s", CMD_READ_REGISTER, result);
ESP_LOGD(TAG, "LD2420 reply - 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
data_pos = 0x0A;
for (uint16_t index = 0; index < (CMD_REG_DATA_REPLY_SIZE * // NOLINT
@@ -462,13 +465,13 @@ void LD2420Component::handle_ack_data_(uint8_t *buffer, int len) {
}
break;
case (CMD_WRITE_REGISTER):
ESP_LOGV(TAG, "Write register: CMD = %2X %s", CMD_WRITE_REGISTER, result);
ESP_LOGD(TAG, "LD2420 reply - write register: CMD = %2X %s", CMD_WRITE_REGISTER, result);
break;
case (CMD_WRITE_ABD_PARAM):
ESP_LOGV(TAG, "Write gate parameter(s): %2X %s", CMD_WRITE_ABD_PARAM, result);
ESP_LOGD(TAG, "LD2420 reply - write gate parameter(s): %2X %s", CMD_WRITE_ABD_PARAM, result);
break;
case (CMD_READ_ABD_PARAM):
ESP_LOGV(TAG, "Read gate parameter(s): %2X %s", CMD_READ_ABD_PARAM, result);
ESP_LOGD(TAG, "LD2420 reply - read gate parameter(s): %2X %s", CMD_READ_ABD_PARAM, result);
data_pos = CMD_ABD_DATA_REPLY_START;
for (uint16_t index = 0; index < (CMD_ABD_DATA_REPLY_SIZE * // NOLINT
((buffer[CMD_FRAME_DATA_LENGTH] - 4) / CMD_ABD_DATA_REPLY_SIZE));
@@ -480,11 +483,11 @@ void LD2420Component::handle_ack_data_(uint8_t *buffer, int len) {
}
break;
case (CMD_WRITE_SYS_PARAM):
ESP_LOGV(TAG, "Set system parameter(s): %2X %s", CMD_WRITE_SYS_PARAM, result);
ESP_LOGD(TAG, "LD2420 reply - set system parameter(s): %2X %s", CMD_WRITE_SYS_PARAM, result);
break;
case (CMD_READ_VERSION):
memcpy(this->ld2420_firmware_ver_, &buffer[12], buffer[10]);
ESP_LOGV(TAG, "Firmware version: %7s %s", this->ld2420_firmware_ver_, result);
ESP_LOGD(TAG, "LD2420 reply - module firmware version: %7s %s", this->ld2420_firmware_ver_, result);
break;
default:
break;
@@ -530,7 +533,7 @@ int LD2420Component::send_cmd_from_array(CmdFrameT frame) {
}
while (!this->cmd_reply_.ack) {
while (this->available()) {
while (available()) {
this->readline_(read(), ack_buffer, sizeof(ack_buffer));
}
delay_microseconds_safe(1450);
@@ -545,7 +548,7 @@ int LD2420Component::send_cmd_from_array(CmdFrameT frame) {
if (this->cmd_reply_.ack)
retry = 0;
if (this->cmd_reply_.error > 0)
this->handle_cmd_error(error);
handle_cmd_error(error);
}
return error;
}
@@ -560,7 +563,7 @@ uint8_t LD2420Component::set_config_mode(bool enable) {
cmd_frame.data_length += sizeof(CMD_PROTOCOL_VER);
}
cmd_frame.footer = CMD_FRAME_FOOTER;
ESP_LOGV(TAG, "Sending set config %s command: %2X", enable ? "enable" : "disable", cmd_frame.command);
ESP_LOGD(TAG, "Sending set config %s command: %2X", enable ? "enable" : "disable", cmd_frame.command);
return this->send_cmd_from_array(cmd_frame);
}
@@ -573,7 +576,7 @@ void LD2420Component::ld2420_restart() {
cmd_frame.header = CMD_FRAME_HEADER;
cmd_frame.command = CMD_RESTART;
cmd_frame.footer = CMD_FRAME_FOOTER;
ESP_LOGV(TAG, "Sending restart command: %2X", cmd_frame.command);
ESP_LOGD(TAG, "Sending restart command: %2X", cmd_frame.command);
this->send_cmd_from_array(cmd_frame);
}
@@ -585,7 +588,7 @@ void LD2420Component::get_reg_value_(uint16_t reg) {
cmd_frame.data[1] = reg;
cmd_frame.data_length += 2;
cmd_frame.footer = CMD_FRAME_FOOTER;
ESP_LOGV(TAG, "Sending read register %4X command: %2X", reg, cmd_frame.command);
ESP_LOGD(TAG, "Sending read register %4X command: %2X", reg, cmd_frame.command);
this->send_cmd_from_array(cmd_frame);
}
@@ -599,11 +602,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));
cmd_frame.data_length += 2;
cmd_frame.footer = CMD_FRAME_FOOTER;
ESP_LOGV(TAG, "Sending write register %4X command: %2X data = %4X", reg, cmd_frame.command, value);
ESP_LOGD(TAG, "Sending write register %4X command: %2X data = %4X", reg, cmd_frame.command, value);
this->send_cmd_from_array(cmd_frame);
}
void LD2420Component::handle_cmd_error(uint8_t error) { ESP_LOGE(TAG, "Command failed: %s", ERR_MESSAGE[error]); }
void LD2420Component::handle_cmd_error(uint8_t error) { ESP_LOGI(TAG, "Command failed: %s", ERR_MESSAGE[error]); }
int LD2420Component::get_gate_threshold_(uint8_t gate) {
uint8_t error;
@@ -616,7 +619,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]));
cmd_frame.data_length += 2;
cmd_frame.footer = CMD_FRAME_FOOTER;
ESP_LOGV(TAG, "Sending read gate %d high/low threshold command: %2X", gate, cmd_frame.command);
ESP_LOGD(TAG, "Sending read gate %d high/low theshold command: %2X", gate, cmd_frame.command);
error = this->send_cmd_from_array(cmd_frame);
if (error == 0) {
this->current_config.move_thresh[gate] = cmd_reply_.data[0];
@@ -641,7 +644,7 @@ int LD2420Component::get_min_max_distances_timeout_() {
sizeof(CMD_TIMEOUT_REG)); // Register: global delay time
cmd_frame.data_length += sizeof(CMD_TIMEOUT_REG);
cmd_frame.footer = CMD_FRAME_FOOTER;
ESP_LOGV(TAG, "Sending read gate min max and timeout command: %2X", cmd_frame.command);
ESP_LOGD(TAG, "Sending read gate min max and timeout command: %2X", cmd_frame.command);
error = this->send_cmd_from_array(cmd_frame);
if (error == 0) {
this->current_config.min_gate = (uint16_t) cmd_reply_.data[0];
@@ -664,9 +667,9 @@ void LD2420Component::set_system_mode(uint16_t mode) {
memcpy(&cmd_frame.data[cmd_frame.data_length], &unknown_parm, sizeof(unknown_parm));
cmd_frame.data_length += sizeof(unknown_parm);
cmd_frame.footer = CMD_FRAME_FOOTER;
ESP_LOGV(TAG, "Sending write system mode command: %2X", cmd_frame.command);
ESP_LOGD(TAG, "Sending write system mode command: %2X", cmd_frame.command);
if (this->send_cmd_from_array(cmd_frame) == 0)
this->set_mode_(mode);
set_mode_(mode);
}
void LD2420Component::get_firmware_version_() {
@@ -676,7 +679,7 @@ void LD2420Component::get_firmware_version_() {
cmd_frame.command = CMD_READ_VERSION;
cmd_frame.footer = CMD_FRAME_FOOTER;
ESP_LOGV(TAG, "Sending read firmware version command: %2X", cmd_frame.command);
ESP_LOGD(TAG, "Sending read firmware version command: %2X", cmd_frame.command);
this->send_cmd_from_array(cmd_frame);
}
@@ -709,7 +712,7 @@ void LD2420Component::set_min_max_distances_timeout(uint32_t max_gate_distance,
cmd_frame.data_length += sizeof(timeout);
cmd_frame.footer = CMD_FRAME_FOOTER;
ESP_LOGV(TAG, "Sending write gate min max and timeout command: %2X", cmd_frame.command);
ESP_LOGD(TAG, "Sending write gate min max and timeout command: %2X", cmd_frame.command);
this->send_cmd_from_array(cmd_frame);
}
@@ -735,7 +738,7 @@ void LD2420Component::set_gate_threshold(uint8_t gate) {
sizeof(this->new_config.still_thresh[gate]));
cmd_frame.data_length += sizeof(this->new_config.still_thresh[gate]);
cmd_frame.footer = CMD_FRAME_FOOTER;
ESP_LOGV(TAG, "Sending set gate %4X sensitivity command: %2X", gate, cmd_frame.command);
ESP_LOGD(TAG, "Sending set gate %4X sensitivity command: %2X", gate, cmd_frame.command);
this->send_cmd_from_array(cmd_frame);
}

View File

@@ -179,7 +179,7 @@ class LD2420Component : public Component, public uart::UARTDevice {
void set_operating_mode(const std::string &state);
void auto_calibrate_sensitivity();
void update_radar_data(uint16_t const *gate_energy, uint8_t sample_number);
static uint8_t calc_checksum(void *data, size_t size);
uint8_t calc_checksum(void *data, size_t size);
RegConfigT current_config;
RegConfigT new_config;
@@ -222,7 +222,7 @@ class LD2420Component : public Component, public uart::UARTDevice {
volatile bool ack;
};
static int get_firmware_int(const char *version_string);
int get_firmware_int_(const char *version_string);
void get_firmware_version_();
int get_gate_threshold_(uint8_t gate);
void get_reg_value_(uint16_t reg);

View File

@@ -6,9 +6,7 @@
#ifdef USE_SENSOR
#include "esphome/components/sensor/sensor.h"
#endif
#include "esphome/core/application.h"
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#define highbyte(val) (uint8_t)((val) >> 8)
#define lowbyte(val) (uint8_t)((val) &0xff)
@@ -17,9 +15,8 @@ namespace esphome {
namespace ld2450 {
static const char *const TAG = "ld2450";
static const char *const NO_MAC = "08:05:04:03:02:01";
static const char *const UNKNOWN_MAC = "unknown";
static const char *const VERSION_FMT = "%u.%02X.%02X%02X%02X%02X";
static const char *const NO_MAC("08:05:04:03:02:01");
static const char *const UNKNOWN_MAC("unknown");
// LD2450 UART Serial Commands
static const uint8_t CMD_ENABLE_CONF = 0x00FF;
@@ -99,6 +96,18 @@ static inline std::string get_direction(int16_t speed) {
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) {
return str_sprintf("%u.%02X.%02X%02X%02X%02X", buffer[13], buffer[12], buffer[17], buffer[16], buffer[15],
buffer[14]);
}
LD2450Component::LD2450Component() {}
void LD2450Component::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
#ifdef USE_NUMBER
@@ -111,7 +120,7 @@ void LD2450Component::setup() {
}
void LD2450Component::dump_config() {
ESP_LOGCONFIG(TAG, "LD2450:");
ESP_LOGCONFIG(TAG, "HLK-LD2450 Human motion tracking radar module:");
#ifdef USE_BINARY_SENSOR
LOG_BINARY_SENSOR(" ", "TargetBinarySensor", this->target_binary_sensor_);
LOG_BINARY_SENSOR(" ", "MovingTargetBinarySensor", this->moving_target_binary_sensor_);
@@ -180,10 +189,10 @@ void LD2450Component::dump_config() {
LOG_NUMBER(" ", "PresenceTimeoutNumber", this->presence_timeout_number_);
#endif
ESP_LOGCONFIG(TAG,
" Throttle: %ums\n"
" MAC Address: %s\n"
" Firmware version: %s",
this->throttle_, this->mac_ == NO_MAC ? UNKNOWN_MAC : this->mac_.c_str(), this->version_.c_str());
" Throttle : %ums\n"
" MAC Address : %s\n"
" Firmware version : %s",
this->throttle_, const_cast<char *>(this->mac_.c_str()), const_cast<char *>(this->version_.c_str()));
}
void LD2450Component::loop() {
@@ -257,7 +266,8 @@ bool LD2450Component::get_timeout_status_(uint32_t check_millis) {
if (this->timeout_ == 0) {
this->timeout_ = ld2450::convert_seconds_to_ms(DEFAULT_PRESENCE_TIMEOUT);
}
return App.get_loop_component_start_time() - check_millis >= this->timeout_;
auto current_millis = millis();
return current_millis - check_millis >= this->timeout_;
}
// Extract, store and publish zone details LD2450 buffer
@@ -344,24 +354,25 @@ void LD2450Component::send_command_(uint8_t command, const uint8_t *command_valu
// Header Target 1 Target 2 Target 3 End
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)
ESP_LOGE(TAG, "Invalid message length");
ESP_LOGE(TAG, "Periodic data: invalid message length");
return;
}
if (buffer[0] != 0xAA || buffer[1] != 0xFF || buffer[2] != 0x03 || buffer[3] != 0x00) { // header
ESP_LOGE(TAG, "Invalid message header");
ESP_LOGE(TAG, "Periodic data: invalid message header");
return;
}
if (buffer[len - 2] != 0x55 || buffer[len - 1] != 0xCC) { // footer
ESP_LOGE(TAG, "Invalid message footer");
ESP_LOGE(TAG, "Periodic data: invalid message footer");
return;
}
if (App.get_loop_component_start_time() - this->last_periodic_millis_ < this->throttle_) {
auto current_millis = millis();
if (current_millis - this->last_periodic_millis_ < this->throttle_) {
ESP_LOGV(TAG, "Throttling: %d", this->throttle_);
return;
}
this->last_periodic_millis_ = App.get_loop_component_start_time();
this->last_periodic_millis_ = current_millis;
int16_t target_count = 0;
int16_t still_target_count = 0;
@@ -544,13 +555,13 @@ void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) {
#ifdef USE_SENSOR
// For presence timeout check
if (target_count > 0) {
this->presence_millis_ = App.get_loop_component_start_time();
this->presence_millis_ = millis();
}
if (moving_target_count > 0) {
this->moving_presence_millis_ = App.get_loop_component_start_time();
this->moving_presence_millis_ = millis();
}
if (still_target_count > 0) {
this->still_presence_millis_ = App.get_loop_component_start_time();
this->still_presence_millis_ = millis();
}
#endif
}
@@ -558,31 +569,31 @@ void LD2450Component::handle_periodic_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]);
if (len < 10) {
ESP_LOGE(TAG, "Invalid ack length");
ESP_LOGE(TAG, "Ack data: invalid length");
return true;
}
if (buffer[0] != 0xFD || buffer[1] != 0xFC || buffer[2] != 0xFB || buffer[3] != 0xFA) { // frame header
ESP_LOGE(TAG, "Invalid ack header (command %02X)", buffer[COMMAND]);
ESP_LOGE(TAG, "Ack data: invalid header (command %02X)", buffer[COMMAND]);
return true;
}
if (buffer[COMMAND_STATUS] != 0x01) {
ESP_LOGE(TAG, "Invalid ack status");
ESP_LOGE(TAG, "Ack data: invalid status");
return true;
}
if (buffer[8] || buffer[9]) {
ESP_LOGE(TAG, "Last buffer was %u, %u", buffer[8], buffer[9]);
ESP_LOGE(TAG, "Ack data: last buffer was %u, %u", buffer[8], buffer[9]);
return true;
}
switch (buffer[COMMAND]) {
case lowbyte(CMD_ENABLE_CONF):
ESP_LOGV(TAG, "Enable conf command");
ESP_LOGV(TAG, "Got enable conf command");
break;
case lowbyte(CMD_DISABLE_CONF):
ESP_LOGV(TAG, "Disable conf command");
ESP_LOGV(TAG, "Got disable conf command");
break;
case lowbyte(CMD_SET_BAUD_RATE):
ESP_LOGV(TAG, "Baud rate change command");
ESP_LOGV(TAG, "Got baud rate change command");
#ifdef USE_SELECT
if (this->baud_rate_select_ != nullptr) {
ESP_LOGV(TAG, "Change baud rate to %s", this->baud_rate_select_->state.c_str());
@@ -590,7 +601,7 @@ bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) {
#endif
break;
case lowbyte(CMD_VERSION):
this->version_ = str_sprintf(VERSION_FMT, buffer[13], buffer[12], buffer[17], buffer[16], buffer[15], buffer[14]);
this->version_ = ld2450::format_version(buffer);
ESP_LOGV(TAG, "Firmware version: %s", this->version_.c_str());
#ifdef USE_TEXT_SENSOR
if (this->version_text_sensor_ != nullptr) {
@@ -602,7 +613,7 @@ bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) {
if (len < 20) {
return false;
}
this->mac_ = format_mac_address_pretty(&buffer[10]);
this->mac_ = ld2450::format_mac(buffer);
ESP_LOGV(TAG, "MAC address: %s", this->mac_.c_str());
#ifdef USE_TEXT_SENSOR
if (this->mac_text_sensor_ != nullptr) {
@@ -616,10 +627,10 @@ bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) {
#endif
break;
case lowbyte(CMD_BLUETOOTH):
ESP_LOGV(TAG, "Bluetooth command");
ESP_LOGV(TAG, "Got Bluetooth command");
break;
case lowbyte(CMD_SINGLE_TARGET_MODE):
ESP_LOGV(TAG, "Single target conf command");
ESP_LOGV(TAG, "Got single target conf command");
#ifdef USE_SWITCH
if (this->multi_target_switch_ != nullptr) {
this->multi_target_switch_->publish_state(false);
@@ -627,7 +638,7 @@ bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) {
#endif
break;
case lowbyte(CMD_MULTI_TARGET_MODE):
ESP_LOGV(TAG, "Multi target conf command");
ESP_LOGV(TAG, "Got multi target conf command");
#ifdef USE_SWITCH
if (this->multi_target_switch_ != nullptr) {
this->multi_target_switch_->publish_state(true);
@@ -635,7 +646,7 @@ bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) {
#endif
break;
case lowbyte(CMD_QUERY_TARGET_MODE):
ESP_LOGV(TAG, "Query target tracking mode command");
ESP_LOGV(TAG, "Got query target tracking mode command");
#ifdef USE_SWITCH
if (this->multi_target_switch_ != nullptr) {
this->multi_target_switch_->publish_state(buffer[10] == 0x02);
@@ -643,7 +654,7 @@ bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) {
#endif
break;
case lowbyte(CMD_QUERY_ZONE):
ESP_LOGV(TAG, "Query zone conf command");
ESP_LOGV(TAG, "Got query zone conf command");
this->zone_type_ = std::stoi(std::to_string(buffer[10]), nullptr, 16);
this->publish_zone_type();
#ifdef USE_SELECT
@@ -663,7 +674,7 @@ bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) {
this->process_zone_(buffer);
break;
case lowbyte(CMD_SET_ZONE):
ESP_LOGV(TAG, "Set zone conf command");
ESP_LOGV(TAG, "Got set zone conf command");
this->query_zone_info();
break;
default:

View File

@@ -141,6 +141,7 @@ class LD2450Component : public Component, public uart::UARTDevice {
#endif
public:
LD2450Component();
void setup() override;
void dump_config() override;
void loop() override;
@@ -196,17 +197,17 @@ class LD2450Component : public Component, public uart::UARTDevice {
bool get_timeout_status_(uint32_t check_millis);
uint8_t count_targets_in_zone_(const Zone &zone, bool is_moving);
Target target_info_[MAX_TARGETS];
Zone zone_config_[MAX_ZONES];
uint8_t buffer_pos_ = 0; // where to resume processing/populating buffer
uint8_t buffer_data_[MAX_LINE_LENGTH];
uint32_t last_periodic_millis_ = 0;
uint32_t presence_millis_ = 0;
uint32_t still_presence_millis_ = 0;
uint32_t moving_presence_millis_ = 0;
uint16_t throttle_ = 0;
uint16_t timeout_ = 5;
uint8_t buffer_pos_ = 0; // where to resume processing/populating buffer
uint8_t buffer_data_[MAX_LINE_LENGTH];
uint8_t zone_type_ = 0;
Target target_info_[MAX_TARGETS];
Zone zone_config_[MAX_ZONES];
std::string version_{};
std::string mac_{};
#ifdef USE_NUMBER

View File

@@ -48,11 +48,6 @@ 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
message_sent =
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
// Emergency console logging for non-main tasks when ring buffer is full or disabled
@@ -144,10 +139,6 @@ Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size) : baud_rate_(baud_rate
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
void Logger::init_log_buffer(size_t 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
@@ -198,10 +189,6 @@ void Logger::loop() {
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
}

View File

@@ -358,26 +358,6 @@ class Logger : public Component {
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);
}
#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)

View File

@@ -466,7 +466,7 @@ LVGL_SCHEMA = cv.All(
): lvalid.lv_color,
cv.Optional(df.CONF_THEME): cv.Schema(
{
cv.Optional(name): obj_schema(w).extend(FULL_STYLE_SCHEMA)
cv.Optional(name): obj_schema(w)
for name, w in WIDGET_TYPES.items()
}
),

View File

@@ -21,7 +21,7 @@ from esphome.core.config import StartupTrigger
from esphome.schema_extractors import SCHEMA_EXTRACT
from . import defines as df, lv_validation as lvalid
from .defines import CONF_TIME_FORMAT, LV_GRAD_DIR, TYPE_GRID
from .defines import CONF_TIME_FORMAT, LV_GRAD_DIR
from .helpers import add_lv_use, requires_component, validate_printf
from .lv_validation import lv_color, lv_font, lv_gradient, lv_image, opacity
from .lvcode import LvglComponent, lv_event_t_ptr
@@ -349,60 +349,7 @@ 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_VALIDATORS = {TYPE_GRID: _validate_grid_layout}
ALIGN_TO_SCHEMA = {
cv.Optional(df.CONF_ALIGN_TO): cv.Schema(
@@ -455,8 +402,8 @@ LAYOUT_SCHEMA = {
}
GRID_CELL_SCHEMA = {
cv.Optional(df.CONF_GRID_CELL_ROW_POS): cv.positive_int,
cv.Optional(df.CONF_GRID_CELL_COLUMN_POS): cv.positive_int,
cv.Required(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_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_X_ALIGN): grid_alignments,
@@ -527,10 +474,7 @@ def container_validator(schema, widget_type: WidgetType):
result = result.extend(
LAYOUT_SCHEMAS.get(ltype.lower(), LAYOUT_SCHEMAS[df.TYPE_NONE])
)
value = result(value)
if layout_validator := LAYOUT_VALIDATORS.get(ltype):
value = layout_validator(value)
return value
return result(value)
return validator

View File

@@ -64,14 +64,6 @@ class ModbusDevice {
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_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
bool waiting_for_response() { return parent_->waiting_for_response != 0; }

View File

@@ -112,22 +112,6 @@ TYPE_REGISTER_MAP = {
"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", automation.Trigger.template(cg.int_, cg.int_)
)
@@ -301,24 +285,21 @@ async def to_code(config):
cg.add(var.set_offline_skip_updates(config[CONF_OFFLINE_SKIP_UPDATES]))
if CONF_SERVER_REGISTERS in config:
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(
server_register_var.set_read_lambda(
cg.TemplateArguments(cpp_type),
await cg.process_lambda(
server_register[CONF_READ_LAMBDA],
[(cg.uint16, "address")],
return_type=cpp_type,
),
var.add_server_register(
cg.new_Pvariable(
server_register[CONF_ID],
server_register[CONF_ADDRESS],
server_register[CONF_VALUE_TYPE],
TYPE_REGISTER_MAP[server_register[CONF_VALUE_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)
for conf in config.get(CONF_ON_COMMAND_SENT, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)

View File

@@ -117,17 +117,12 @@ void ModbusController::on_modbus_read_registers(uint8_t function_code, uint16_t
bool found = false;
for (auto *server_register : this->server_registers_) {
if (server_register->address == current_address) {
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());
float value = server_register->read_lambda();
std::vector<uint16_t> payload;
payload.reserve(server_register->register_count * 2);
number_to_payload(payload, value, server_register->value_type);
ESP_LOGD(TAG, "Matched register. Address: 0x%02X. Value type: %zu. Register count: %u. Value: %0.1f.",
server_register->address, static_cast<uint8_t>(server_register->value_type),
server_register->register_count, value);
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());
current_address += server_register->register_count;
found = true;
@@ -137,7 +132,11 @@ void ModbusController::on_modbus_read_registers(uint8_t function_code, uint16_t
if (!found) {
ESP_LOGW(TAG, "Could not match any register to address %02X. Sending exception response.", current_address);
send_error(function_code, 0x02);
std::vector<uint8_t> error_response;
error_response.push_back(this->address_);
error_response.push_back(0x81);
error_response.push_back(0x02);
this->send_raw(error_response);
return;
}
}

View File

@@ -63,10 +63,6 @@ enum class SensorValueType : uint8_t {
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) {
switch (reg_type) {
case ModbusRegisterType::COIL:
@@ -257,53 +253,18 @@ class SensorItem {
};
class ServerRegister {
using ReadLambda = std::function<int64_t()>;
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->value_type = value_type;
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};
SensorValueType value_type{SensorValueType::RAW};
uint8_t register_count{0};
ReadLambda read_lambda;
std::function<float()> read_lambda;
};
// ModbusController::create_register_ranges_ tries to optimize register range
@@ -483,7 +444,7 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice {
void on_modbus_data(const std::vector<uint8_t> &data) override;
/// called when a modbus error response was received
void on_modbus_error(uint8_t function_code, uint8_t exception_code) override;
/// called when a modbus request (function code 0x03 or 0x04) was parsed without errors
/// called when a modbus request (function code 3 or 4) was parsed without errors
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
void on_register_data(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data);
@@ -568,7 +529,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);
float float_value;
if (value_type_is_float(item.sensor_value_type)) {
if (item.sensor_value_type == SensorValueType::FP32 || item.sensor_value_type == SensorValueType::FP32_R) {
float_value = bit_cast<float>(static_cast<uint32_t>(number));
} else {
float_value = static_cast<float>(number);
@@ -580,7 +541,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) {
int64_t val;
if (value_type_is_float(value_type)) {
if (value_type == SensorValueType::FP32 || value_type == SensorValueType::FP32_R) {
val = bit_cast<uint32_t>(value);
} else {
val = llroundf(value);

View File

@@ -17,8 +17,7 @@ enum class MQTTClientDisconnectReason : int8_t {
MQTT_MALFORMED_CREDENTIALS = 4,
MQTT_NOT_AUTHORIZED = 5,
ESP8266_NOT_ENOUGH_SPACE = 6,
TLS_BAD_FINGERPRINT = 7,
DNS_RESOLVE_ERROR = 8
TLS_BAD_FINGERPRINT = 7
};
/// internal struct for MQTT messages.

View File

@@ -229,8 +229,6 @@ void MQTTClientComponent::check_dnslookup_() {
if (this->dns_resolve_error_) {
ESP_LOGW(TAG, "Couldn't resolve IP address for '%s'", this->credentials_.address.c_str());
this->state_ = MQTT_CLIENT_DISCONNECTED;
this->disconnect_reason_ = MQTTClientDisconnectReason::DNS_RESOLVE_ERROR;
this->on_disconnect_.call(MQTTClientDisconnectReason::DNS_RESOLVE_ERROR);
return;
}
@@ -700,9 +698,7 @@ void MQTTClientComponent::set_on_connect(mqtt_on_connect_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->on_disconnect_.add(std::move(callback_copy));
}
#if ASYNC_TCP_SSL_ENABLED

View File

@@ -8,7 +8,6 @@
#include "esphome/components/network/ip_address.h"
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#if defined(USE_ESP32)
#include "mqtt_backend_esp32.h"
@@ -335,7 +334,6 @@ class MQTTClientComponent : public Component {
uint32_t connect_begin_;
uint32_t last_connected_{0};
optional<MQTTClientDisconnectReason> disconnect_reason_{};
CallbackManager<MQTTBackend::on_disconnect_callback_t> on_disconnect_;
bool publish_nan_as_none_{false};
bool wait_for_connection_{false};

View File

@@ -23,22 +23,16 @@ std::string state_class_to_string(StateClass state_class) {
Sensor::Sensor() : state(NAN), raw_state(NAN) {}
int8_t Sensor::get_accuracy_decimals() {
if (this->sensor_flags_.has_accuracy_override)
return this->accuracy_decimals_;
if (this->accuracy_decimals_.has_value())
return *this->accuracy_decimals_;
return 0;
}
void Sensor::set_accuracy_decimals(int8_t accuracy_decimals) {
this->accuracy_decimals_ = accuracy_decimals;
this->sensor_flags_.has_accuracy_override = true;
}
void Sensor::set_accuracy_decimals(int8_t accuracy_decimals) { this->accuracy_decimals_ = accuracy_decimals; }
void Sensor::set_state_class(StateClass state_class) {
this->state_class_ = state_class;
this->sensor_flags_.has_state_class_override = true;
}
void Sensor::set_state_class(StateClass state_class) { this->state_class_ = state_class; }
StateClass Sensor::get_state_class() {
if (this->sensor_flags_.has_state_class_override)
return this->state_class_;
if (this->state_class_.has_value())
return *this->state_class_;
return StateClass::STATE_CLASS_NONE;
}

View File

@@ -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
* same as before.
*/
bool get_force_update() const { return sensor_flags_.force_update; }
bool get_force_update() const { return force_update_; }
/// Set force update mode.
void set_force_update(bool force_update) { sensor_flags_.force_update = force_update; }
void set_force_update(bool force_update) { force_update_ = force_update; }
/// Add a filter to the filter chain. Will be appended to the back.
void add_filter(Filter *filter);
@@ -155,17 +155,9 @@ class Sensor : public EntityBase, public EntityBase_DeviceClass, public EntityBa
Filter *filter_list_{nullptr}; ///< Store all active filters.
// Group small members together to avoid padding
int8_t accuracy_decimals_{-1}; ///< Accuracy in decimals (-1 = not set)
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_{};
optional<int8_t> accuracy_decimals_; ///< Accuracy in decimals override
optional<StateClass> state_class_{STATE_CLASS_NONE}; ///< State class override
bool force_update_{false}; ///< Force update mode
};
} // namespace sensor

View File

@@ -741,6 +741,11 @@ void WiFiComponent::set_power_save_mode(WiFiPowerSaveMode power_save) { this->po
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_() {
#ifdef USE_CAPTIVE_PORTAL
return captive_portal::global_captive_portal != nullptr && captive_portal::global_captive_portal->is_active();

View File

@@ -321,6 +321,8 @@ class WiFiComponent : public Component {
int32_t get_wifi_channel();
protected:
static std::string format_mac_addr(const uint8_t mac[6]);
#ifdef USE_WIFI_AP
void setup_ap_config_();
#endif // USE_WIFI_AP

View File

@@ -550,7 +550,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
memcpy(buf, it.ssid, it.ssid_len);
buf[it.ssid_len] = '\0';
ESP_LOGV(TAG, "Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf,
format_mac_address_pretty(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode));
format_mac_addr(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode));
#if USE_NETWORK_IPV6
this->set_timeout(100, [] { WiFi.enableIPv6(); });
#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);
} else {
ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf,
format_mac_address_pretty(it.bssid).c_str(), get_disconnect_reason_str(it.reason));
format_mac_addr(it.bssid).c_str(), get_disconnect_reason_str(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: {
auto it = info.wifi_sta_connected;
auto &mac = it.bssid;
ESP_LOGV(TAG, "AP client connected MAC=%s", format_mac_address_pretty(mac).c_str());
ESP_LOGV(TAG, "AP client connected MAC=%s", format_mac_addr(mac).c_str());
break;
}
case ESPHOME_EVENT_ID_WIFI_AP_STADISCONNECTED: {
auto it = info.wifi_sta_disconnected;
auto &mac = it.bssid;
ESP_LOGV(TAG, "AP client disconnected MAC=%s", format_mac_address_pretty(mac).c_str());
ESP_LOGV(TAG, "AP client disconnected MAC=%s", format_mac_addr(mac).c_str());
break;
}
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: {
auto it = info.wifi_ap_probereqrecved;
ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_address_pretty(it.mac).c_str(), it.rssi);
ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_addr(it.mac).c_str(), it.rssi);
break;
}
default:

View File

@@ -496,8 +496,7 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) {
char buf[33];
memcpy(buf, it.ssid, it.ssid_len);
buf[it.ssid_len] = '\0';
ESP_LOGV(TAG, "Connected ssid='%s' bssid=%s channel=%u", buf, format_mac_address_pretty(it.bssid).c_str(),
it.channel);
ESP_LOGV(TAG, "Connected ssid='%s' bssid=%s channel=%u", buf, format_mac_addr(it.bssid).c_str(), it.channel);
s_sta_connected = true;
break;
}
@@ -511,7 +510,7 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) {
s_sta_connect_not_found = true;
} else {
ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf,
format_mac_address_pretty(it.bssid).c_str(), LOG_STR_ARG(get_disconnect_reason_str(it.reason)));
format_mac_addr(it.bssid).c_str(), LOG_STR_ARG(get_disconnect_reason_str(it.reason)));
s_sta_connect_error = true;
}
s_sta_connected = false;
@@ -546,17 +545,17 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) {
}
case EVENT_SOFTAPMODE_STACONNECTED: {
auto it = event->event_info.sta_connected;
ESP_LOGV(TAG, "AP client connected MAC=%s aid=%u", format_mac_address_pretty(it.mac).c_str(), it.aid);
ESP_LOGV(TAG, "AP client connected MAC=%s aid=%u", format_mac_addr(it.mac).c_str(), it.aid);
break;
}
case EVENT_SOFTAPMODE_STADISCONNECTED: {
auto it = event->event_info.sta_disconnected;
ESP_LOGV(TAG, "AP client disconnected MAC=%s aid=%u", format_mac_address_pretty(it.mac).c_str(), it.aid);
ESP_LOGV(TAG, "AP client disconnected MAC=%s aid=%u", format_mac_addr(it.mac).c_str(), it.aid);
break;
}
case EVENT_SOFTAPMODE_PROBEREQRECVED: {
auto it = event->event_info.ap_probereqrecved;
ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_address_pretty(it.mac).c_str(), it.rssi);
ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_addr(it.mac).c_str(), it.rssi);
break;
}
#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 4, 0)
@@ -568,7 +567,7 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) {
}
case EVENT_SOFTAPMODE_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_address_pretty(it.mac).c_str(),
ESP_LOGV(TAG, "AP Distribute Station IP MAC=%s IP=%s aid=%u", format_mac_addr(it.mac).c_str(),
format_ip_addr(it.ip).c_str(), it.aid);
break;
}

View File

@@ -691,7 +691,7 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) {
memcpy(buf, it.ssid, it.ssid_len);
buf[it.ssid_len] = '\0';
ESP_LOGV(TAG, "Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf,
format_mac_address_pretty(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode));
format_mac_addr(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode));
s_sta_connected = true;
} 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;
} else {
ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf,
format_mac_address_pretty(it.bssid).c_str(), get_disconnect_reason_str(it.reason));
format_mac_addr(it.bssid).c_str(), get_disconnect_reason_str(it.reason));
s_sta_connect_error = true;
}
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) {
const auto &it = data->data.ap_probe_req_rx;
ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_address_pretty(it.mac).c_str(), it.rssi);
ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_addr(it.mac).c_str(), it.rssi);
} else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_AP_STACONNECTED) {
const auto &it = data->data.ap_staconnected;
ESP_LOGV(TAG, "AP client connected MAC=%s", format_mac_address_pretty(it.mac).c_str());
ESP_LOGV(TAG, "AP client connected MAC=%s", format_mac_addr(it.mac).c_str());
} else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_AP_STADISCONNECTED) {
const auto &it = data->data.ap_stadisconnected;
ESP_LOGV(TAG, "AP client disconnected MAC=%s", format_mac_address_pretty(it.mac).c_str());
ESP_LOGV(TAG, "AP client disconnected MAC=%s", format_mac_addr(it.mac).c_str());
} else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_AP_STAIPASSIGNED) {
const auto &it = data->data.ip_ap_staipassigned;

View File

@@ -281,7 +281,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
memcpy(buf, it.ssid, it.ssid_len);
buf[it.ssid_len] = '\0';
ESP_LOGV(TAG, "Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf,
format_mac_address_pretty(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode));
format_mac_addr(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode));
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);
} else {
ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf,
format_mac_address_pretty(it.bssid).c_str(), get_disconnect_reason_str(it.reason));
format_mac_addr(it.bssid).c_str(), get_disconnect_reason_str(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: {
auto it = info.wifi_sta_connected;
auto &mac = it.bssid;
ESP_LOGV(TAG, "AP client connected MAC=%s", format_mac_address_pretty(mac).c_str());
ESP_LOGV(TAG, "AP client connected MAC=%s", format_mac_addr(mac).c_str());
break;
}
case ESPHOME_EVENT_ID_WIFI_AP_STADISCONNECTED: {
auto it = info.wifi_sta_disconnected;
auto &mac = it.bssid;
ESP_LOGV(TAG, "AP client disconnected MAC=%s", format_mac_address_pretty(mac).c_str());
ESP_LOGV(TAG, "AP client disconnected MAC=%s", format_mac_addr(mac).c_str());
break;
}
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: {
auto it = info.wifi_ap_probereqrecved;
ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_address_pretty(it.mac).c_str(), it.rssi);
ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_addr(it.mac).c_str(), it.rssi);
break;
}
default:

View File

@@ -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 + 5, mac_reverse, 1);
ESP_LOGVV(TAG, "decrypt_xiaomi_payload(): authenticated decryption failed.");
ESP_LOGVV(TAG, " MAC address : %s", format_mac_address_pretty(mac_address).c_str());
ESP_LOGVV(TAG, " MAC address : %s", format_hex_pretty(mac_address, 6).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, " Iv : %s", format_hex_pretty(vector.iv, vector.ivsize).c_str());

View File

@@ -1099,7 +1099,7 @@ UNIT_KILOMETER_PER_HOUR = "km/h"
UNIT_KILOVOLT_AMPS = "kVA"
UNIT_KILOVOLT_AMPS_HOURS = "kVAh"
UNIT_KILOVOLT_AMPS_REACTIVE = "kVAR"
UNIT_KILOVOLT_AMPS_REACTIVE_HOURS = "kvarh"
UNIT_KILOVOLT_AMPS_REACTIVE_HOURS = "kVARh"
UNIT_KILOWATT = "kW"
UNIT_KILOWATT_HOURS = "kWh"
UNIT_LITRE = "L"
@@ -1135,7 +1135,7 @@ UNIT_VOLT = "V"
UNIT_VOLT_AMPS = "VA"
UNIT_VOLT_AMPS_HOURS = "VAh"
UNIT_VOLT_AMPS_REACTIVE = "var"
UNIT_VOLT_AMPS_REACTIVE_HOURS = "varh"
UNIT_VOLT_AMPS_REACTIVE_HOURS = "VARh"
UNIT_WATT = "W"
UNIT_WATT_HOURS = "Wh"

View File

@@ -1,7 +1,5 @@
#pragma once
#include <algorithm>
#include <limits>
#include <string>
#include <vector>
#include "esphome/core/component.h"
@@ -337,16 +335,11 @@ class Application {
* Each component can request a high frequency loop execution by using the HighFrequencyLoopRequester
* helper in helpers.h
*
* Note: This method is not called by ESPHome core code. It is only used by lambda functions
* in YAML configurations or by external components.
*
* @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_ = std::min(loop_interval, static_cast<uint32_t>(std::numeric_limits<uint16_t>::max()));
}
void set_loop_interval(uint32_t loop_interval) { this->loop_interval_ = loop_interval; }
uint32_t get_loop_interval() const { return static_cast<uint32_t>(this->loop_interval_); }
uint32_t get_loop_interval() const { return this->loop_interval_; }
void schedule_dump_config() { this->dump_config_at_ = 0; }
@@ -625,17 +618,6 @@ class Application {
/// Perform a delay while also monitoring socket file descriptors for readiness
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_{};
// Partitioned vector design for looping components
@@ -655,6 +637,11 @@ class Application {
// and active_end_ is incremented
// - This eliminates branch mispredictions from flag checking in the hot loop
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
std::vector<Device *> devices_{};
@@ -726,39 +713,26 @@ class Application {
std::vector<update::UpdateEntity *> updates_{};
#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 friendly_name_;
// 4-byte members
const char *comment_{nullptr};
const char *compilation_time_{nullptr};
bool name_add_mac_suffix_;
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};
#ifdef USE_SOCKET_SELECT_SUPPORT
int max_fd_{-1}; // Highest file descriptor number for select()
#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
// Socket select management
std::vector<int> socket_fds_; // Vector of all monitored socket file descriptors
bool socket_fds_changed_{false}; // Flag to rebuild base_read_fds_ when socket_fds_ changes
// Variable-sized members at end
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_
int max_fd_{-1}; // Highest file descriptor number for select()
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
};

View File

@@ -27,67 +27,20 @@ template<typename T, typename... X> class TemplatableValue {
public:
TemplatableValue() : type_(NONE) {}
template<typename F, enable_if_t<!is_invocable<F, X...>::value, int> = 0> TemplatableValue(F value) : type_(VALUE) {
new (&this->value_) T(std::move(value));
}
template<typename F, enable_if_t<!is_invocable<F, X...>::value, int> = 0>
TemplatableValue(F value) : type_(VALUE), value_(std::move(value)) {}
template<typename F, enable_if_t<is_invocable<F, X...>::value, int> = 0> TemplatableValue(F f) : type_(LAMBDA) {
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_;
}
}
template<typename F, enable_if_t<is_invocable<F, X...>::value, int> = 0>
TemplatableValue(F f) : type_(LAMBDA), f_(f) {}
bool has_value() { return this->type_ != NONE; }
T value(X... x) {
if (this->type_ == LAMBDA) {
return (*this->f_)(x...);
return this->f_(x...);
}
// return value also when none
return this->type_ == VALUE ? this->value_ : T{};
return this->value_;
}
optional<T> optional_value(X... x) {
@@ -105,16 +58,14 @@ template<typename T, typename... X> class TemplatableValue {
}
protected:
enum : uint8_t {
enum {
NONE,
VALUE,
LAMBDA,
} type_;
union {
T value_;
std::function<T(X...)> *f_;
};
T value_{};
std::function<T(X...)> f_{};
};
/** Base class for all automation conditions.

View File

@@ -132,8 +132,6 @@
// ESP32-specific feature flags
#ifdef USE_ESP32
#define USE_ESPHOME_TASK_LOG_BUFFER
#define USE_BLUETOOTH_PROXY
#define USE_CAPTIVE_PORTAL
#define USE_ESP32_BLE

View File

@@ -356,10 +356,6 @@ size_t parse_hex(const char *str, size_t length, uint8_t *data, size_t count) {
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; }
std::string format_hex(const uint8_t *data, size_t length) {
std::string ret;
@@ -736,7 +732,7 @@ std::string get_mac_address() {
std::string get_mac_address_pretty() {
uint8_t mac[6];
get_mac_address_raw(mac);
return format_mac_address_pretty(mac);
return str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
}
#ifdef USE_ESP32

View File

@@ -402,8 +402,6 @@ template<typename T, enable_if_t<std::is_unsigned<T>::value, int> = 0> optional<
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.
std::string format_hex(const uint8_t *data, size_t length);
/// Format the vector \p data in lowercased hex.

View File

@@ -886,7 +886,7 @@ def build_message_type(
public_content.append("#ifdef HAS_PROTO_MESSAGE_DUMP")
snake_name = camel_to_snake(desc.name)
public_content.append(
f'const char *message_name() const override {{ return "{snake_name}"; }}'
f'static constexpr const char *message_name() {{ return "{snake_name}"; }}'
)
public_content.append("#endif")
@@ -1356,7 +1356,7 @@ def main() -> None:
hpp += " template<typename T>\n"
hpp += " bool send_message(const T &msg) {\n"
hpp += "#ifdef HAS_PROTO_MESSAGE_DUMP\n"
hpp += " this->log_send_message_(msg.message_name(), msg.dump());\n"
hpp += " this->log_send_message_(T::message_name(), msg.dump());\n"
hpp += "#endif\n"
hpp += " return this->send_message_(msg, T::MESSAGE_TYPE);\n"
hpp += " }\n\n"

0
script/run-in-env.py Executable file → Normal file
View File

View File

@@ -4,31 +4,6 @@ binary_sensor:
id: some_binary_sensor
name: "Random binary"
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:
then:
- logger.log:

View File

@@ -839,7 +839,9 @@ lvgl:
styles: bdr_style
grid_cell_x_align: center
grid_cell_y_align: stretch
grid_cell_column_span: 2
grid_cell_row_pos: 0
grid_cell_column_pos: 1
grid_cell_column_span: 1
text: "Grid cell 0/1"
- label:
grid_cell_x_align: end

View File

@@ -1,7 +0,0 @@
esphome:
name: api-reboot-test
host:
api:
reboot_timeout: 0.5s # Very short timeout for fast testing
logger:
level: DEBUG

View File

@@ -8,8 +8,5 @@ sensor:
name: Test Sensor
id: test_sensor
unit_of_measurement: °C
accuracy_decimals: 2
state_class: measurement
force_update: true
lambda: return 42.0;
update_interval: 0.1s

View File

@@ -1,35 +0,0 @@
"""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

View File

@@ -4,7 +4,6 @@ from __future__ import annotations
import asyncio
import aioesphomeapi
from aioesphomeapi import EntityState
import pytest
@@ -48,23 +47,3 @@ async def test_host_mode_with_sensor(
# Verify the sensor state
assert test_sensor_state.state == 42.0
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}"
)