1
0
mirror of https://github.com/esphome/esphome.git synced 2025-11-01 15:41:52 +00:00

Compare commits

...

1258 Commits

Author SHA1 Message Date
J. Nick Koston
0e687ff0b7 Refactor API entity update dispatch to reduce code duplication 2025-07-07 11:36:38 -05:00
J. Nick Koston
6d1d7f137f Merge branch 'prepare_commands_for_device_id' into integration 2025-07-07 11:15:32 -05:00
J. Nick Koston
38e16efa11 Refactor entity lookup methods with macros in preparation for device_id support 2025-07-07 11:10:23 -05:00
J. Nick Koston
5e2f0f7f5e Merge branch 'socket_read_dupe_code' into integration 2025-07-07 10:33:51 -05:00
J. Nick Koston
83c7afc46f Refactor duplicate socket read error handling in API frame helper 2025-07-07 10:33:04 -05:00
J. Nick Koston
10753f0f99 Merge branch 'more_str_len_logger' into integration 2025-07-07 10:10:17 -05:00
J. Nick Koston
34a852d433 Optimize logger performance by eliminating redundant strlen calls 2025-07-07 10:09:51 -05:00
J. Nick Koston
3922fbdef7 Merge branch 'logger_strlen' into integration 2025-07-07 09:04:17 -05:00
J. Nick Koston
e5415abf20 tidy 2025-07-07 09:03:52 -05:00
J. Nick Koston
67e1a92cce Merge branch 'logger_strlen' into integration 2025-07-07 08:53:11 -05:00
J. Nick Koston
4c64511a15 apply suggestions from review 2025-07-07 08:52:52 -05:00
J. Nick Koston
75f3e0900e apply suggestions from review 2025-07-07 08:52:28 -05:00
J. Nick Koston
abd33c21bf Update esphome/components/syslog/esphome_syslog.cpp
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-07 08:50:40 -05:00
J. Nick Koston
d592ba2c5e Update esphome/components/web_server/web_server.cpp
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-07 08:50:33 -05:00
J. Nick Koston
321eba5184 Merge branch 'logger_strlen' into integration 2025-07-07 08:48:45 -05:00
J. Nick Koston
82b9ec53fd fix merge error 2025-07-07 08:48:35 -05:00
J. Nick Koston
b9262f967b Merge branch 'logger_strlen' into integration 2025-07-07 08:47:27 -05:00
J. Nick Koston
949fb9a890 Optimize logger callback API by including message length parameter 2025-07-07 08:46:21 -05:00
J. Nick Koston
99952a701f Merge remote-tracking branch 'upstream/jesserockz-2025-282' into integration 2025-07-07 07:34:48 -05:00
J. Nick Koston
88878adb6c Merge branch 'filter_compile_warning_vv' into integration 2025-07-07 07:33:53 -05:00
J. Nick Koston
17e3b49ebb Merge branch 'api_sprint' into integration 2025-07-07 07:33:49 -05:00
J. Nick Koston
a217747f5d Replace deprecated sprintf with snprintf in API protobuf code generation 2025-07-07 07:32:22 -05:00
J. Nick Koston
790c9cbb84 Fix format specifier warnings in QuantileFilter logging 2025-07-07 07:27:31 -05:00
J. Nick Koston
da5fb6e24f Merge branch '20250707-ld2450-clean-up' into integration 2025-07-07 07:18:12 -05:00
Jesse Hills
a77439b4b7 Ignore new helper files for namespace inclusion 2025-07-07 23:24:30 +12:00
Jesse Hills
1a049bdcbb More missing includes 2025-07-07 21:06:50 +12:00
Keith Burzinski
79686239d3 Rename button, sort vars 2025-07-07 03:33:21 -05:00
Keith Burzinski
c934e84e21 [ld2450] Clean-up for consistency, reduce CPU usage when idle 2025-07-07 03:23:04 -05:00
Jesse Hills
5e2f8cb018 Missing includes 2025-07-07 17:33:05 +12:00
J. Nick Koston
6bd0af6d85 Merge branch 'heap_scheduler_stress_component' into integration 2025-07-06 23:58:09 -05:00
J. Nick Koston
0f28a49822 tidy 2025-07-06 23:57:46 -05:00
Jesse Hills
66d96646b1 [core] Move platform helper implementations into their own file 2025-07-07 16:40:45 +12:00
J. Nick Koston
be4cf6505f Merge branch 'heap_scheduler_stress_component' into integration 2025-07-06 22:56:46 -05:00
J. Nick Koston
e8ea7825a9 Merge branch 'dev' into heap_scheduler_stress_component 2025-07-06 22:56:18 -05:00
J. Nick Koston
8c13eab731 no flakey 2025-07-06 22:54:46 -05:00
J. Nick Koston
bf4cbb0aee Merge branch 'heap_scheduler_stress_component' into integration 2025-07-06 22:14:38 -05:00
J. Nick Koston
aaec4b7bd3 validation consistent 2025-07-06 22:13:35 -05:00
J. Nick Koston
7bddcd4f64 Merge branch 'heap_scheduler_stress_component' into integration 2025-07-06 22:07:21 -05:00
J. Nick Koston
af205a5267 one more test 2025-07-06 22:01:19 -05:00
J. Nick Koston
c2599d7719 safer 2025-07-06 21:43:03 -05:00
J. Nick Koston
4ea6f23d9e Merge branch 'heap_scheduler_stress_component' into integration 2025-07-06 21:32:06 -05:00
J. Nick Koston
f23fd52a26 clarify what we know 2025-07-06 21:31:39 -05:00
J. Nick Koston
cfd43c81fb clarify what we know 2025-07-06 21:30:39 -05:00
J. Nick Koston
3dcba675b4 Merge branch 'heap_scheduler_stress_component' into integration 2025-07-06 21:23:51 -05:00
J. Nick Koston
bb51031ec6 preen 2025-07-06 21:23:30 -05:00
J. Nick Koston
ecb99cbcce fix flakey test 2025-07-06 21:19:38 -05:00
J. Nick Koston
0a514821c6 preen 2025-07-06 21:04:23 -05:00
J. Nick Koston
074fbb522c preen 2025-07-06 21:01:52 -05:00
J. Nick Koston
71d6ba242e preen 2025-07-06 21:01:25 -05:00
J. Nick Koston
37ffd64b48 Merge remote-tracking branch 'upstream/heap_scheduler_stress_component' into heap_scheduler_stress_component 2025-07-06 20:59:59 -05:00
J. Nick Koston
ec65652567 add missed remake 2025-07-06 20:59:43 -05:00
J. Nick Koston
731613421d fix flakey 2025-07-06 20:59:08 -05:00
J. Nick Koston
58dfad4ed0 Merge branch 'heap_scheduler_stress_component' into integration 2025-07-06 20:38:10 -05:00
J. Nick Koston
7eb029f4b9 cleanup 2025-07-06 20:38:00 -05:00
J. Nick Koston
8da8d938f0 Merge branch 'heap_scheduler_stress_component' into integration 2025-07-06 20:36:50 -05:00
J. Nick Koston
64ac0d2bde cover 2025-07-06 20:36:32 -05:00
J. Nick Koston
7d3cdd15ad cleanup 2025-07-06 20:31:28 -05:00
J. Nick Koston
53baf02087 cleanup 2025-07-06 20:30:40 -05:00
J. Nick Koston
a0d2392344 cleanup 2025-07-06 20:26:43 -05:00
J. Nick Koston
fb3c092eaa cleanup 2025-07-06 20:25:27 -05:00
J. Nick Koston
c169cf1e77 Merge remote-tracking branch 'origin/heap_scheduler_stress_component' into heap_scheduler_stress_component 2025-07-06 20:13:48 -05:00
J. Nick Koston
fa4d8e083a Merge remote-tracking branch 'origin/dev' into heap_scheduler_stress_component 2025-07-06 20:13:34 -05:00
J. Nick Koston
2cfeccfd71 cleanup locking 2025-07-06 20:13:21 -05:00
J. Nick Koston
f36ca93752 Merge branch 'dev' into heap_scheduler_stress_component 2025-07-06 20:05:23 -05:00
J. Nick Koston
dc8714c277 fix race 2025-07-06 19:59:11 -05:00
J. Nick Koston
90fcb5fbcd fix another race 2025-07-06 19:54:07 -05:00
J. Nick Koston
932d0a5d8b fix another race 2025-07-06 19:50:54 -05:00
J. Nick Koston
4cafa18fa4 fix another race 2025-07-06 19:46:23 -05:00
J. Nick Koston
b12d7db5a7 prevent future refactoring errors 2025-07-06 19:27:33 -05:00
J. Nick Koston
e84345594d Merge branch 'heap_scheduler_stress_component' into integration 2025-07-06 19:01:11 -05:00
J. Nick Koston
add7bec7f2 tweak 2025-07-06 18:54:00 -05:00
J. Nick Koston
db84d8e8dc tweak 2025-07-06 18:49:41 -05:00
J. Nick Koston
ad51e647af tweak 2025-07-06 18:48:50 -05:00
J. Nick Koston
c45901746b tweak 2025-07-06 18:46:48 -05:00
J. Nick Koston
033c469250 tweak 2025-07-06 18:44:19 -05:00
J. Nick Koston
0900fd3cea tweak 2025-07-06 18:42:47 -05:00
J. Nick Koston
ba8f3d3f63 tweak 2025-07-06 18:36:05 -05:00
J. Nick Koston
2759f3828e tweak 2025-07-06 18:34:56 -05:00
J. Nick Koston
f395767766 tweak 2025-07-06 18:27:49 -05:00
J. Nick Koston
2dc222aea6 tweak 2025-07-06 18:26:29 -05:00
J. Nick Koston
82d68c87e2 adjust 2025-07-06 18:24:00 -05:00
J. Nick Koston
f213657753 adjust 2025-07-06 18:18:47 -05:00
J. Nick Koston
e077e6cec7 adjust 2025-07-06 18:17:16 -05:00
J. Nick Koston
339a3270f6 adjust 2025-07-06 18:16:25 -05:00
J. Nick Koston
462b44ee23 adjust 2025-07-06 18:15:11 -05:00
J. Nick Koston
52d3dba89c adjust 2025-07-06 18:11:04 -05:00
J. Nick Koston
939d01dd99 preen 2025-07-06 18:08:50 -05:00
J. Nick Koston
4900f7c7ca preen 2025-07-06 18:07:34 -05:00
J. Nick Koston
48957aee8b preen 2025-07-06 18:03:53 -05:00
J. Nick Koston
e355ce04f7 preen 2025-07-06 18:01:21 -05:00
J. Nick Koston
758e5b89bb preen 2025-07-06 17:53:56 -05:00
J. Nick Koston
3ffdd1d451 preen 2025-07-06 17:42:57 -05:00
J. Nick Koston
4c1b8c8b96 preen 2025-07-06 17:33:50 -05:00
J. Nick Koston
3ca956cd6a fix merge error 2025-07-06 17:27:32 -05:00
J. Nick Koston
2e24a11a1d Merge remote-tracking branch 'upstream/dev' into heap_scheduler_stress_component 2025-07-06 17:21:36 -05:00
J. Nick Koston
10a03ad538 tidy 2025-07-06 15:37:09 -05:00
J. Nick Koston
69839ec4dc Merge branch 'filter_files' into integration 2025-07-06 14:22:31 -05:00
J. Nick Koston
28a66d4bf0 preen 2025-07-06 14:18:17 -05:00
J. Nick Koston
782d894801 preen 2025-07-06 14:18:05 -05:00
J. Nick Koston
06dd731c78 preen 2025-07-06 14:10:20 -05:00
J. Nick Koston
6af74302dc missed one 2025-07-06 14:06:03 -05:00
J. Nick Koston
03380a6ecd some tests 2025-07-06 13:51:51 -05:00
J. Nick Koston
8d8db11dd9 some tests 2025-07-06 13:51:17 -05:00
J. Nick Koston
28886a896b some tests 2025-07-06 13:48:11 -05:00
J. Nick Koston
05253991c2 fixes 2025-07-06 13:42:44 -05:00
J. Nick Koston
96f0fda477 fixes 2025-07-06 13:42:18 -05:00
J. Nick Koston
023fa4d220 fixes 2025-07-06 13:37:41 -05:00
J. Nick Koston
a1f63c0dfc fixes 2025-07-06 13:24:50 -05:00
J. Nick Koston
ef98f42e7e tweaks 2025-07-06 13:18:24 -05:00
J. Nick Koston
737e1284af tweaks 2025-07-06 13:18:10 -05:00
J. Nick Koston
8677918157 tweaks 2025-07-06 13:16:49 -05:00
J. Nick Koston
629c891dfc Filter unused files 2025-07-06 12:12:16 -05:00
J. Nick Koston
8e8ef83780 cleanup 2025-07-06 11:05:18 -05:00
J. Nick Koston
2a15f35e9d cleanup 2025-07-06 11:04:04 -05:00
J. Nick Koston
9bfa942cf2 merge 2025-07-06 10:58:15 -05:00
J. Nick Koston
b00adbddce fix race 2025-07-06 10:40:44 -05:00
J. Nick Koston
a71030c4de fix race 2025-07-06 10:40:19 -05:00
J. Nick Koston
6bb32c2e61 tweaks 2025-07-06 10:12:29 -05:00
J. Nick Koston
7bc2c685e0 tweaks 2025-07-06 10:12:14 -05:00
J. Nick Koston
9205338cc8 Merge branch 'heap_scheduler_stress_component' into integration 2025-07-06 10:08:35 -05:00
J. Nick Koston
04336f7ba3 Merge branch 'dev' into heap_scheduler_stress_component 2025-07-06 10:08:03 -05:00
J. Nick Koston
6f64312d08 remove debugging 2025-07-06 10:06:45 -05:00
J. Nick Koston
79dfb86830 remove debugging 2025-07-06 10:04:17 -05:00
J. Nick Koston
453dc29540 preen 2025-07-06 10:03:28 -05:00
J. Nick Koston
f4260d370c preen 2025-07-06 10:03:24 -05:00
J. Nick Koston
655f9489a8 preen 2025-07-06 10:02:58 -05:00
J. Nick Koston
4b3cc52afe preen 2025-07-06 10:02:47 -05:00
J. Nick Koston
fd3f15637a lint 2025-07-06 10:01:07 -05:00
J. Nick Koston
1311e1b8b0 lint 2025-07-06 10:00:55 -05:00
J. Nick Koston
64e84872da lint 2025-07-06 10:00:35 -05:00
J. Nick Koston
bc7379030e lint 2025-07-06 10:00:25 -05:00
J. Nick Koston
ecfb6dc8ed lint 2025-07-06 10:00:17 -05:00
J. Nick Koston
75d67af932 Add heap scheduler tests 2025-07-06 09:55:14 -05:00
J. Nick Koston
845dad6ee7 Merge branch 'idf_webserver_guard' into integration 2025-07-05 23:48:48 -05:00
J. Nick Koston
e2e86da64b make bot happy 2025-07-05 23:48:37 -05:00
J. Nick Koston
90ec63589f Merge branch 'core_ram' into integration 2025-07-05 23:40:51 -05:00
J. Nick Koston
ea308eaaa2 add comments to explain why its safe and the bot is wrong 2025-07-05 23:39:25 -05:00
J. Nick Koston
87f1fac2bf nolint 2025-07-05 23:28:39 -05:00
J. Nick Koston
c23651527f Merge branch 'bluetooth_proxy_guard_var' into integration 2025-07-05 23:02:44 -05:00
J. Nick Koston
2cc263a707 lint 2025-07-05 23:01:49 -05:00
J. Nick Koston
fb336718de Merge branch 'core_ram' into integration 2025-07-05 23:00:30 -05:00
J. Nick Koston
e2e35bf965 simplify 2025-07-05 22:58:27 -05:00
J. Nick Koston
bdd25c7268 Merge branch 'idf_webserver_guard' into integration 2025-07-05 22:26:54 -05:00
J. Nick Koston
82c788d6ce Eliminate web_server_idf guard variable to save 8 bytes RAM 2025-07-05 22:24:26 -05:00
J. Nick Koston
5167184cc7 merge 2025-07-05 22:18:20 -05:00
J. Nick Koston
a5d1b11204 Merge branch 'bluetooth_proxy_guard_var' into integration 2025-07-05 22:17:32 -05:00
J. Nick Koston
dc8f2fd37e Eliminate bluetooth_proxy guard variable to save 8 bytes RAM 2025-07-05 22:15:45 -05:00
J. Nick Koston
7c85886ce8 Merge branch 'api_guard_var' into integration 2025-07-05 21:59:47 -05:00
J. Nick Koston
12f172436d Eliminate API component guard variable to save 8 bytes RAM 2025-07-05 21:59:08 -05:00
J. Nick Koston
e69ac0478e Merge branch 'core_ram' into integration 2025-07-05 21:38:49 -05:00
J. Nick Koston
a45743c2b7 Reduce core RAM usage by 40 bytes with static initialization optimizations 2025-07-05 21:35:32 -05:00
J. Nick Koston
ebe1531927 Merge branch 'color_memory' into integration 2025-07-05 21:24:23 -05:00
J. Nick Koston
a88a059c6a Reduce RAM usage by optimizing Color constant storage 2025-07-05 21:21:43 -05:00
J. Nick Koston
d314cbb0d5 Merge branch 'icon_opt_pay_as_you_go' into integration 2025-07-05 17:44:05 -05:00
J. Nick Koston
4d75758eb2 tests 2025-07-05 17:39:02 -05:00
J. Nick Koston
0eecc29039 Merge branch 'icon_opt_pay_as_you_go' into integration 2025-07-05 17:38:13 -05:00
J. Nick Koston
294fb67410 Optimize entity icon memory usage with USE_ENTITY_ICON flag 2025-07-05 17:36:51 -05:00
J. Nick Koston
2f1f098b47 revert 2025-07-05 16:55:15 -05:00
J. Nick Koston
77be414261 Merge branch 'deep_sleep_loop' into integration 2025-07-05 16:12:06 -05:00
J. Nick Koston
c34fc3c4c7 simplify 2025-07-05 16:07:43 -05:00
J. Nick Koston
8aac2f525e simplify 2025-07-05 16:01:59 -05:00
J. Nick Koston
f85dcdca4e unreachable 2025-07-05 15:57:21 -05:00
J. Nick Koston
e7a1ef7aa1 Merge branch 'deep_sleep_loop' into integration 2025-07-05 15:54:13 -05:00
J. Nick Koston
7c2d2ef5a3 deep_sleep: Replace polling loop with event-driven state machine 2025-07-05 15:53:12 -05:00
J. Nick Koston
1449001747 Merge branch 'ld2450_cpu_drain_spam' into integration 2025-07-05 15:07:58 -05:00
J. Nick Koston
f245c74520 fix byte ordering 2025-07-05 15:01:02 -05:00
J. Nick Koston
da1658e4f9 Merge branch 'ld2450_cpu_drain_spam' into integration 2025-07-05 14:50:51 -05:00
J. Nick Koston
80f9352a79 Merge branch 'light_ram' into integration 2025-07-05 14:50:47 -05:00
J. Nick Koston
9ded501402 clang-tidy 2025-07-05 14:50:17 -05:00
J. Nick Koston
3d6a1811c5 comments 2025-07-05 14:28:26 -05:00
J. Nick Koston
a5ee047efb Fix LD2450 excessive CPU usage and redundant sensor updates 2025-07-05 14:25:56 -05:00
J. Nick Koston
fb0090dcdc Merge branch 'light_ram' into integration 2025-07-05 13:53:20 -05:00
J. Nick Koston
294bd4d042 tweaks 2025-07-05 13:44:42 -05:00
J. Nick Koston
e99b8d2daf tweaks 2025-07-05 13:41:09 -05:00
J. Nick Koston
6dbdeeb59b tidy 2025-07-05 13:18:45 -05:00
J. Nick Koston
82fd62e9dd comments 2025-07-05 13:00:48 -05:00
J. Nick Koston
70f935d323 fixed a few missed ones 2025-07-05 12:39:05 -05:00
J. Nick Koston
0f3e6cccd9 Reduce light component memory usage by 50+ bytes per instance 2025-07-05 12:33:54 -05:00
J. Nick Koston
6ff323c56d Merge branch 'busy_disconnect_loop' into integration 2025-07-05 11:12:06 -05:00
J. Nick Koston
096ec79ef9 Fix bluetooth proxy busy loop when disconnecting pending BLE connections 2025-07-05 11:11:36 -05:00
J. Nick Koston
bf5ba65558 Merge branch 'ble_align' into integration 2025-07-05 09:05:20 -05:00
J. Nick Koston
62088dfaed Split LockFreeQueue into base and notifying variants to reduce memory usage 2025-07-05 09:02:33 -05:00
J. Nick Koston
dfcc3206f7 Split LockFreeQueue into base and notifying variants to reduce memory usage 2025-07-05 08:59:19 -05:00
J. Nick Koston
e173b7f0c2 Split LockFreeQueue into base and notifying variants to reduce memory usage 2025-07-05 08:58:41 -05:00
J. Nick Koston
f98e28a8a2 Split LockFreeQueue into base and notifying variants to reduce memory usage 2025-07-05 08:57:04 -05:00
J. Nick Koston
f63557f2e7 notes to the future 2025-07-05 07:34:46 -05:00
J. Nick Koston
a353598961 Merge branch 'ble_batching' into integration 2025-07-05 07:24:07 -05:00
J. Nick Koston
bc33b44648 Optimize Bluetooth proxy batching and increase scan buffer capacity 2025-07-05 07:23:31 -05:00
J. Nick Koston
1579779967 Merge branch 'defer_const' into integration 2025-07-04 20:54:41 -05:00
J. Nick Koston
cc6ea4cd14 cover 2025-07-04 20:51:50 -05:00
J. Nick Koston
303a8ff87a Merge branch 'defer_const' into integration 2025-07-04 20:33:07 -05:00
J. Nick Koston
7d3a11a735 Add const char overload for Component::defer() 2025-07-04 20:30:04 -05:00
J. Nick Koston
94b6344820 Merge branch 'reduce_main_loop' into integration 2025-07-04 13:56:19 -05:00
J. Nick Koston
40307c079c Merge remote-tracking branch 'origin/reduce_main_loop' into reduce_main_loop 2025-07-04 13:54:41 -05:00
J. Nick Koston
debef6fde4 address review comments 2025-07-04 13:54:07 -05:00
J. Nick Koston
0cda83d29c Update scheduler.cpp
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-04 13:46:39 -05:00
J. Nick Koston
32729c7ca7 Merge branch 'reduce_main_loop' into integration 2025-07-04 12:59:23 -05:00
J. Nick Koston
b7fca5488a lol 2025-07-04 12:59:11 -05:00
J. Nick Koston
9c22772758 fix scope issue 2025-07-04 11:40:11 -05:00
J. Nick Koston
1e72f07fdf Merge branch 'reduce_main_loop' into integration 2025-07-04 11:29:12 -05:00
J. Nick Koston
a592e96709 preen 2025-07-04 11:29:01 -05:00
J. Nick Koston
3980339868 Merge branch 'reduce_main_loop' into integration 2025-07-04 11:26:45 -05:00
J. Nick Koston
afa66c17bd preen 2025-07-04 11:26:33 -05:00
J. Nick Koston
be2988b1d7 Merge branch 'reduce_main_loop' into integration 2025-07-04 11:24:46 -05:00
J. Nick Koston
cf647f0c36 Merge remote-tracking branch 'upstream/dev' into integration 2025-07-04 11:24:40 -05:00
J. Nick Koston
385ed4ca0c Merge remote-tracking branch 'upstream/reduce_main_loop' into reduce_main_loop 2025-07-04 11:23:44 -05:00
J. Nick Koston
9188a8e326 preen 2025-07-04 11:23:33 -05:00
J. Nick Koston
0efb6d55c8 Merge branch 'dev' into reduce_main_loop 2025-07-04 11:20:27 -05:00
J. Nick Koston
f748047b7b Merge branch 'reduce_main_loop' into integration 2025-07-04 11:18:55 -05:00
J. Nick Koston
49bc767bf4 cleanup 2025-07-04 11:13:08 -05:00
J. Nick Koston
e12cc9a9a7 cleanup 2025-07-04 11:12:54 -05:00
J. Nick Koston
8e4470cdff Merge branch 'reduce_main_loop' into integration 2025-07-04 11:02:07 -05:00
J. Nick Koston
bdb7e19fd0 guard esp8266 2025-07-04 10:59:58 -05:00
J. Nick Koston
0fc3f0e162 guard esp8266 2025-07-04 10:57:39 -05:00
J. Nick Koston
6fac66e63b Merge branch 'reduce_main_loop' into integration 2025-07-04 10:46:38 -05:00
J. Nick Koston
71e06ea1b6 cleanup 2025-07-04 10:45:47 -05:00
J. Nick Koston
3df434fd55 improve test 2025-07-04 10:41:59 -05:00
J. Nick Koston
729b2b2873 remove debug 2025-07-04 10:35:29 -05:00
J. Nick Koston
bc2adb6b5a there was no locking on host! 2025-07-04 10:25:31 -05:00
J. Nick Koston
aaff086aeb there was no locking on host! 2025-07-04 10:24:04 -05:00
J. Nick Koston
e4c0f18ee3 fixes 2025-07-04 10:17:41 -05:00
J. Nick Koston
9c09a271f2 tweaks 2025-07-04 10:14:54 -05:00
J. Nick Koston
37578f3e22 fixes 2025-07-04 10:11:19 -05:00
J. Nick Koston
4649599592 fixes 2025-07-04 10:01:00 -05:00
J. Nick Koston
71f78e3a81 fixes 2025-07-04 10:00:25 -05:00
J. Nick Koston
f7ca26eef8 stress 2025-07-04 08:59:15 -05:00
J. Nick Koston
0665fcea9e stress test 2025-07-04 08:49:35 -05:00
J. Nick Koston
cd2b50c27f stress test 2025-07-04 08:49:12 -05:00
J. Nick Koston
ca70f17b3b make test race safe 2025-07-04 08:33:34 -05:00
J. Nick Koston
a5e08aaf74 make test race safe 2025-07-04 08:33:24 -05:00
J. Nick Koston
947db4605a Merge branch 'reduce_main_loop' into integration 2025-07-04 08:16:18 -05:00
J. Nick Koston
481a00a0b5 Merge remote-tracking branch 'upstream/dev' into integration 2025-07-04 08:16:16 -05:00
J. Nick Koston
465019e510 cover 2025-07-04 08:04:16 -05:00
J. Nick Koston
a4d5f39fb6 cover 2025-07-04 07:59:12 -05:00
J. Nick Koston
5dd76966c3 cover 2025-07-04 07:55:01 -05:00
J. Nick Koston
db86f87fc3 Make defer FIFO 2025-07-04 07:42:59 -05:00
J. Nick Koston
e21334b7fa Make defer FIFO 2025-07-04 07:42:37 -05:00
J. Nick Koston
ba4c268956 Make defer FIFO 2025-07-04 07:35:24 -05:00
J. Nick Koston
068594be5e Make defer FIFO 2025-07-04 07:29:37 -05:00
J. Nick Koston
0fd45fc86e fix 2025-07-03 20:15:45 -05:00
J. Nick Koston
257fb98113 Merge branch 'api_cleanup' into integration 2025-07-03 20:05:59 -05:00
J. Nick Koston
f8922b3cca Use std::span to eliminate heap allocation for single-packet API transmissions 2025-07-03 20:01:28 -05:00
J. Nick Koston
b0b08f317b Merge remote-tracking branch 'origin/dev' into integration 2025-07-03 17:47:02 -05:00
J. Nick Koston
2c4667fb46 Merge branch 'camera-platform' into integration 2025-07-03 15:14:49 -05:00
J. Nick Koston
9eadfa21d8 Merge branch 'webserver_busy_loop_dropped_connection' into integration 2025-07-03 14:45:38 -05:00
J. Nick Koston
953fd24458 Fix web_server busy loop with ungracefully disconnected clients 2025-07-03 14:43:11 -05:00
J. Nick Koston
1be171e084 Merge remote-tracking branch 'origin/webserver_string_lifetime' into integration 2025-07-03 14:06:25 -05:00
J. Nick Koston
5c83b99e0c do not need to rename as we changed design to not need it 2025-07-03 14:06:07 -05:00
J. Nick Koston
743e611735 Merge remote-tracking branch 'origin/webserver_string_lifetime' into integration 2025-07-03 13:59:22 -05:00
J. Nick Koston
35ff850894 make sure its bug for bug compat 2025-07-03 13:56:29 -05:00
Dieter Tschanz
b666295b53 Replace Windows-style with Unix-style directory separators in test 2025-07-03 20:53:00 +02:00
J. Nick Koston
96cf8d97ab Merge remote-tracking branch 'upstream/webserver_string_lifetime' into integration 2025-07-03 13:52:04 -05:00
J. Nick Koston
3c1a781a1c cleanups 2025-07-03 13:51:01 -05:00
J. Nick Koston
00bd1b0a02 cleanups 2025-07-03 13:49:22 -05:00
J. Nick Koston
b8482da421 fix defines 2025-07-03 13:38:23 -05:00
J. Nick Koston
756ece9ff3 Merge branch 'dev' into camera-platform 2025-07-03 13:37:54 -05:00
J. Nick Koston
4bb016fec3 Merge branch 'webserver_lock_churn' into integration 2025-07-03 13:35:15 -05:00
J. Nick Koston
32f0322dec Merge branch 'webserver_string_lifetime' into integration 2025-07-03 13:35:10 -05:00
J. Nick Koston
1a1c13b722 Fix web_server URL parsing lifetime issue 2025-07-03 13:27:02 -05:00
Dieter Tschanz
139453822b Add compile-time test to verify Camera interface implementation. 2025-07-03 20:26:10 +02:00
J. Nick Koston
7a33994666 Reduce web_server loop overhead on ESP32 by avoiding unnecessary semaphore operations 2025-07-03 12:50:59 -05:00
J. Nick Koston
f381d9011b Merge remote-tracking branch 'upstream/dev' into integration 2025-07-03 12:18:06 -05:00
J. Nick Koston
96352f047d Merge branch 'device_id_state' into integration 2025-07-03 12:17:48 -05:00
J. Nick Koston
5e7a1fea8c Add device_id to entity state messages for sub-device support 2025-07-03 10:21:12 -05:00
J. Nick Koston
64eb70444d Merge branch 'dev' into camera-platform 2025-07-02 19:52:06 -05:00
J. Nick Koston
0f39b1c49a merge 2025-07-02 14:06:59 -05:00
J. Nick Koston
e2d6363c68 merge 2025-07-02 14:06:32 -05:00
J. Nick Koston
cdeef700c2 Merge branch 'password_api' into integration 2025-07-02 14:05:12 -05:00
J. Nick Koston
86fd702841 Save flash and RAM by conditionally compiling unused API password code 2025-07-02 13:56:41 -05:00
J. Nick Koston
6c62d4a923 Merge branch 'fix_bytes_encoding' into integration 2025-07-01 22:44:43 -05:00
J. Nick Koston
6e42d009fb Fix bytes field encoding in protobuf code generator 2025-07-01 20:26:34 -05:00
J. Nick Koston
7d7769ea5d Merge branch 'component_memory' into integration 2025-07-01 14:11:58 -05:00
J. Nick Koston
3908677fe2 Merge branch 'ota_base_extract' into integration 2025-07-01 14:09:51 -05:00
J. Nick Koston
9799a2b636 test 2025-07-01 13:47:59 -05:00
Dieter Tschanz
55c8129423 Correction for failed component test. 2025-07-01 20:44:11 +02:00
J. Nick Koston
099474053e cleanuip 2025-07-01 13:38:47 -05:00
J. Nick Koston
efafabed97 fix rp2040 2025-07-01 13:23:24 -05:00
Dieter Tschanz
d209739f85 Introduce base Camera class to support alternative camera implementations
This commit introduces a new 'Camera' base class positioned between the
API layer and the existing 'ESP32Camera' implementation.
- No changes to functionality in 'ESP32Camera' or
'ESP32CameraWebServer'.
- This refactoring enables future camera implementations to integrate
with the existing API.
- The goal is to keep the commit as minimal and non-breaking as
possible.

This is the first step in a series of changes aimed at modernizing and
generalizing ESPHome's camera support.
2025-07-01 19:47:50 +02:00
J. Nick Koston
d463dd0f57 Merge branch 'bk7200_tagged_pointer_fix' into integration 2025-07-01 11:57:11 -05:00
J. Nick Koston
c33c14a46f tidy 2025-07-01 11:57:02 -05:00
J. Nick Koston
2d0c109dc1 Merge remote-tracking branch 'origin/dev' into ota_base_extract 2025-07-01 11:50:49 -05:00
J. Nick Koston
825d0bed88 fix esp8266 error handling 2025-07-01 11:29:38 -05:00
J. Nick Koston
cd1390916c md5 fixes 2025-07-01 11:09:08 -05:00
J. Nick Koston
149bdaf146 fixes 2025-07-01 10:50:17 -05:00
J. Nick Koston
ad628c9cba single ota path 2025-07-01 10:36:36 -05:00
J. Nick Koston
b88f87799e single ota path 2025-07-01 10:30:52 -05:00
J. Nick Koston
1ff7cf1125 single ota path 2025-07-01 10:28:48 -05:00
J. Nick Koston
31db6e51eb single ota path 2025-07-01 10:27:46 -05:00
J. Nick Koston
681d9236f9 single ota path 2025-07-01 10:26:55 -05:00
J. Nick Koston
8aa8af735d single ota path 2025-07-01 10:25:48 -05:00
J. Nick Koston
943d0f103d single ota path 2025-07-01 10:17:28 -05:00
J. Nick Koston
8b195d7f63 use ota backend 2025-07-01 10:11:41 -05:00
J. Nick Koston
649ad47e62 web_server_ support for ota backend idf 2025-07-01 10:05:23 -05:00
J. Nick Koston
93dc5765bb Merge upstream/dev into ota_base_extract 2025-07-01 09:57:09 -05:00
J. Nick Koston
b000b1b70c Fix regression: BK7231N devices not returning entities via API 2025-07-01 09:43:50 -05:00
J. Nick Koston
8707b6e01a lint 2025-07-01 07:31:45 -05:00
J. Nick Koston
34abd67f3e Merge branch 'component_memory' into integration 2025-06-30 21:21:07 -05:00
J. Nick Koston
45f1db9233 address review comments 2025-06-30 21:20:58 -05:00
J. Nick Koston
beb4d1511a Merge branch 'component_memory' into integration 2025-06-30 21:18:06 -05:00
J. Nick Koston
adeceee71f Reduce Component memory usage by 40% (8 bytes per component) 2025-06-30 21:15:20 -05:00
J. Nick Koston
7d4b11d112 Reduce Component memory usage by 40% (8 bytes per component) 2025-06-30 21:11:49 -05:00
J. Nick Koston
6733cd4ed1 Merge branch 'entity_name_must_be_unique' into integration 2025-06-30 18:31:40 -05:00
J. Nick Koston
07f361a404 empty name uses device name, use get_base_entity_object_id 2025-06-30 18:26:09 -05:00
J. Nick Koston
ae981ea7f2 Merge branch 'entity_name_must_be_unique' into integration 2025-06-30 17:48:49 -05:00
J. Nick Koston
b7d0f5e36b Fix entity hash collisions by enforcing unique names across devices per platform 2025-06-30 17:38:04 -05:00
J. Nick Koston
3cbce4df42 Merge branch 'esp32_touch_isr' into integration 2025-06-30 14:38:54 -05:00
J. Nick Koston
7e77e40bda cleanup 2025-06-30 14:37:30 -05:00
J. Nick Koston
305805256d dry 2025-06-30 14:34:07 -05:00
J. Nick Koston
e36c669dc0 dry 2025-06-30 14:32:13 -05:00
J. Nick Koston
71aff9bc60 dry 2025-06-30 14:30:07 -05:00
J. Nick Koston
36d11c969f dry 2025-06-30 14:29:57 -05:00
J. Nick Koston
f76ce5d3bb dry 2025-06-30 14:28:31 -05:00
J. Nick Koston
0df454481e safer 2025-06-30 14:15:26 -05:00
J. Nick Koston
83c1a30cfb Merge branch 'esp32_touch_isr' into integration 2025-06-30 13:46:54 -05:00
J. Nick Koston
6cbd1479c6 loop 2025-06-30 13:46:47 -05:00
J. Nick Koston
3e6e438920 Merge remote-tracking branch 'upstream/dev' into esp32_touch_isr 2025-06-30 13:43:18 -05:00
J. Nick Koston
560886eb90 clenaup 2025-06-30 13:32:59 -05:00
J. Nick Koston
340bb5cef6 clenaup 2025-06-30 13:31:55 -05:00
J. Nick Koston
44a7c1d4a5 cleanup 2025-06-30 13:14:55 -05:00
J. Nick Koston
519c49f175 Revert "fix"
This reverts commit c96ffefa42.
2025-06-30 13:11:27 -05:00
J. Nick Koston
c96ffefa42 fix 2025-06-30 13:02:26 -05:00
J. Nick Koston
490ca8ad5a relo 2025-06-30 12:53:41 -05:00
J. Nick Koston
e385f87d6c move more 2025-06-30 12:46:47 -05:00
J. Nick Koston
58de53123a move more 2025-06-30 12:41:55 -05:00
J. Nick Koston
4f365c1716 todo 2025-06-30 12:11:37 -05:00
J. Nick Koston
981177da23 todo 2025-06-30 12:09:07 -05:00
J. Nick Koston
088bea9ccd split 2025-06-30 10:50:26 -05:00
J. Nick Koston
36350f179e split 2025-06-30 10:49:59 -05:00
J. Nick Koston
902f08c1bc Extract OTA backend functionality into separate ota_base component 2025-06-30 10:38:31 -05:00
J. Nick Koston
47ad206ccd Extract OTA backend functionality into separate ota_base component 2025-06-30 10:35:19 -05:00
J. Nick Koston
9f51546023 Extract OTA backend functionality into separate ota_base component 2025-06-30 10:33:43 -05:00
J. Nick Koston
f6d679f056 Merge branch 'idf_webserver_ota' into integration 2025-06-30 10:04:39 -05:00
J. Nick Koston
93c45e88e7 revert ota backend changes 2025-06-30 10:04:23 -05:00
J. Nick Koston
da189da9ae Merge branch 'idf_webserver_ota' into integration 2025-06-30 09:56:30 -05:00
J. Nick Koston
c40a33cb48 revert ota backend changes 2025-06-30 09:56:20 -05:00
J. Nick Koston
9846beee7d revert ota backend changes 2025-06-30 09:55:02 -05:00
J. Nick Koston
81685f9132 Merge branch 'idf_webserver_ota' into integration 2025-06-30 09:50:56 -05:00
J. Nick Koston
14123d25c2 fixes 2025-06-30 09:50:46 -05:00
J. Nick Koston
928819ffbd fixes 2025-06-30 09:49:59 -05:00
J. Nick Koston
7f2f9636f5 make sure ota still works without ota loaded 2025-06-30 09:46:33 -05:00
J. Nick Koston
b49fe146ad make sure ota still works without ota loaded 2025-06-30 09:44:20 -05:00
J. Nick Koston
98bbd4136b Merge branch 'idf_webserver_ota' into integration 2025-06-30 09:24:05 -05:00
J. Nick Koston
d8d02f71ba cleanup 2025-06-30 09:23:57 -05:00
J. Nick Koston
26980df2b9 Merge branch 'idf_webserver_ota' into integration 2025-06-30 08:25:08 -05:00
J. Nick Koston
ffe39473d0 fixes 2025-06-30 08:18:34 -05:00
J. Nick Koston
6af8d152ee fixes 2025-06-30 08:18:18 -05:00
J. Nick Koston
de846a8f7a Merge branch 'idf_webserver_ota' into integration 2025-06-30 07:54:26 -05:00
J. Nick Koston
8e31316e3d Merge remote-tracking branch 'upstream/dev' into idf_webserver_ota 2025-06-30 07:51:22 -05:00
J. Nick Koston
fb6edb3243 fixes 2025-06-30 07:51:11 -05:00
J. Nick Koston
244bd9256f tidy 2025-06-30 06:55:08 -05:00
J. Nick Koston
1f61fd383c Merge branch 'dev' into esp32_touch_isr 2025-06-30 01:06:49 -05:00
J. Nick Koston
ce294ce0c1 Merge branch 'idf_webserver_ota' into integration 2025-06-30 01:01:29 -05:00
J. Nick Koston
dcbdc0ac51 Merge branch 'dev' into esp32_touch_isr 2025-06-30 00:59:07 -05:00
J. Nick Koston
daea06586d Merge branch 'update_libsodium' into integration 2025-06-30 00:54:49 -05:00
J. Nick Koston
9c8bf2587b Merge remote-tracking branch 'origin/update_libsodium' into update_libsodium 2025-06-30 00:50:31 -05:00
J. Nick Koston
9871cb04ea 0.1.10 2025-06-30 00:50:18 -05:00
J. Nick Koston
7dc093815f reduce 2025-06-29 23:40:09 -05:00
J. Nick Koston
087697106c remove debug 2025-06-29 23:32:59 -05:00
J. Nick Koston
9beebc7bfe Merge branch 'dev' into idf_webserver_ota 2025-06-29 23:22:34 -05:00
J. Nick Koston
4a948b7aae Merge branch 'dev' into update_libsodium 2025-06-29 23:20:30 -05:00
J. Nick Koston
0d3bc21e97 Merge branch 'dev' from upstream
Resolved conflicts in:
- esphome/components/api/list_entities.h
- esphome/components/api/subscribe_state.h

Both conflicts were about NOLINT comment style - chose upstream's inline comment format.
2025-06-29 23:18:41 -05:00
J. Nick Koston
7496894ae6 0.1.9 2025-06-29 23:16:20 -05:00
J. Nick Koston
918d7217a9 fix 2025-06-29 23:15:28 -05:00
J. Nick Koston
2103d583f9 bump to 0.1.8 2025-06-29 23:12:48 -05:00
J. Nick Koston
837c446926 Merge branch 'dev' from upstream 2025-06-29 23:10:20 -05:00
J. Nick Koston
480ea54ee0 Merge branch 'dev' into update_libsodium 2025-06-29 22:49:46 -05:00
J. Nick Koston
97e7c34cb6 Merge branch 'dev' into idf_webserver_ota 2025-06-29 22:41:30 -05:00
J. Nick Koston
fe65b149f5 tweak 2025-06-29 22:34:42 -05:00
J. Nick Koston
4106b97174 tweak 2025-06-29 22:31:47 -05:00
J. Nick Koston
8648954b94 tweak 2025-06-29 22:29:40 -05:00
J. Nick Koston
9f1fae0955 tweak 2025-06-29 22:27:36 -05:00
Jesse Hills
1d631c3c6d Update platformio.ini 2025-06-30 15:26:14 +12:00
J. Nick Koston
727161f1db tweak 2025-06-29 22:24:28 -05:00
Jesse Hills
bf5f628769 Update esphome/components/api/__init__.py 2025-06-30 15:23:40 +12:00
J. Nick Koston
8563a5785f tweak 2025-06-29 22:19:29 -05:00
J. Nick Koston
4082634e6d tweak 2025-06-29 22:14:15 -05:00
J. Nick Koston
a74adb5865 tweak 2025-06-29 22:13:56 -05:00
J. Nick Koston
2e4d7301f2 tweak 2025-06-29 22:12:36 -05:00
J. Nick Koston
94845222ad tweak 2025-06-29 22:12:20 -05:00
J. Nick Koston
7f6ac2deee tweak 2025-06-29 22:10:50 -05:00
J. Nick Koston
a054aa9c52 clean 2025-06-29 21:57:50 -05:00
J. Nick Koston
22cb59b88c clean 2025-06-29 21:55:13 -05:00
J. Nick Koston
6968772a31 preen 2025-06-29 21:48:35 -05:00
J. Nick Koston
004f4b51d1 preen 2025-06-29 21:41:57 -05:00
J. Nick Koston
8c8dd7b4bc preen 2025-06-29 21:40:20 -05:00
J. Nick Koston
9778289d33 revert 2025-06-29 21:36:25 -05:00
J. Nick Koston
a43caf08a6 cleanup 2025-06-29 21:31:54 -05:00
J. Nick Koston
01e550fac9 cleanup 2025-06-29 21:13:05 -05:00
J. Nick Koston
ad4dd6a060 cleanup 2025-06-29 21:07:39 -05:00
J. Nick Koston
849d99b0dc cleanup 2025-06-29 21:06:04 -05:00
J. Nick Koston
f5df5f71a3 cleanup 2025-06-29 21:04:45 -05:00
J. Nick Koston
429be0a5ae cleanup 2025-06-29 21:03:13 -05:00
J. Nick Koston
148e4ec555 cleanup 2025-06-29 20:59:51 -05:00
J. Nick Koston
bb22f4d6a3 cleanup 2025-06-29 20:54:36 -05:00
J. Nick Koston
f94703360b cleanup 2025-06-29 20:54:13 -05:00
J. Nick Koston
f26bec1a5a preen 2025-06-29 20:18:32 -05:00
J. Nick Koston
d065f4ae62 cleanup 2025-06-29 20:15:18 -05:00
J. Nick Koston
ed2c3e626b cleanup 2025-06-29 19:53:29 -05:00
J. Nick Koston
1927f92358 cleanup 2025-06-29 19:49:01 -05:00
J. Nick Koston
939144174c cleanup 2025-06-29 19:32:43 -05:00
J. Nick Koston
59bcbe7fef proper state machine 2025-06-29 19:31:01 -05:00
J. Nick Koston
8e00fedc67 rwatchdog 2025-06-29 19:24:40 -05:00
J. Nick Koston
0ac879ae0b remove 2025-06-29 19:22:13 -05:00
J. Nick Koston
22d1a18d22 Merge remote-tracking branch 'upstream/dev' into idf_webserver_ota 2025-06-29 19:19:32 -05:00
J. Nick Koston
ca203bff9b cleanup 2025-06-29 19:18:33 -05:00
J. Nick Koston
e01d16ce82 cleanup 2025-06-29 19:07:47 -05:00
J. Nick Koston
93b6b9835c cleanup 2025-06-29 19:04:54 -05:00
J. Nick Koston
d0ac5388d9 cleanup 2025-06-29 19:03:54 -05:00
J. Nick Koston
9097d646ca cleanup 2025-06-29 19:03:48 -05:00
J. Nick Koston
596a28e1fb cleanup 2025-06-29 19:00:07 -05:00
J. Nick Koston
5205ff5c43 cleanup 2025-06-29 18:59:09 -05:00
J. Nick Koston
c420bf5f4f cleanup 2025-06-29 18:55:46 -05:00
J. Nick Koston
18844e15dc cleanup 2025-06-29 18:54:48 -05:00
J. Nick Koston
af2f5b7348 cleanup 2025-06-29 18:54:14 -05:00
J. Nick Koston
bcbf0f0e26 cleanup 2025-06-29 18:53:43 -05:00
J. Nick Koston
4d460d4bc3 cleanup 2025-06-29 18:51:35 -05:00
J. Nick Koston
92f6f3ac0d cleanup 2025-06-29 18:48:30 -05:00
J. Nick Koston
bc63d246c8 cleanup 2025-06-29 18:46:15 -05:00
J. Nick Koston
b25f272d72 lint 2025-06-29 18:44:14 -05:00
J. Nick Koston
e3a3305adb delete 2025-06-29 18:44:02 -05:00
J. Nick Koston
c655c4e106 remove cruft 2025-06-29 18:39:17 -05:00
J. Nick Koston
7fe8cdaa34 remove cruft 2025-06-29 18:37:48 -05:00
J. Nick Koston
df97985048 Merge remote-tracking branch 'origin/integration' into integration 2025-06-29 18:33:46 -05:00
J. Nick Koston
3779675816 Merge branch 'binary_sensor_gpio_polling' into integration 2025-06-29 18:33:34 -05:00
J. Nick Koston
0005aad5b5 cleanup 2025-06-29 18:30:00 -05:00
J. Nick Koston
98c18517e2 Merge branch 'another_webserver_crash' into idf_webserver_ota 2025-06-29 18:23:25 -05:00
J. Nick Koston
e4dee935ce Fix thread-safe cleanup of event source connections in ESP-IDF web server 2025-06-29 18:21:24 -05:00
J. Nick Koston
f8cb44fb3c fixes 2025-06-29 17:54:11 -05:00
J. Nick Koston
101901fdb8 Merge remote-tracking branch 'upstream/loop_enable_disable_log_spam' into idf_webserver_ota 2025-06-29 17:41:04 -05:00
J. Nick Koston
b8579d2040 Reduce loop enable/disable log spam by using very verbose level 2025-06-29 17:39:48 -05:00
J. Nick Koston
3fca3df756 working 2025-06-29 17:22:33 -05:00
J. Nick Koston
2f5db85997 Merge remote-tracking branch 'upstream/last_Event_fix' into idf_webserver_ota 2025-06-29 16:54:00 -05:00
Jesse Hills
e0d4361875 Update esphome/components/gpio/binary_sensor/__init__.py 2025-06-30 09:53:54 +12:00
J. Nick Koston
30bafc43bd make bot happy 2025-06-29 16:52:55 -05:00
J. Nick Koston
3530437b48 Merge branch 'last_Event_fix' into idf_webserver_ota 2025-06-29 16:19:20 -05:00
J. Nick Koston
81db42942c Fix crash when event last_event_type is null in web_server 2025-06-29 16:16:53 -05:00
J. Nick Koston
6cb0d9e0b5 fixes 2025-06-29 16:11:33 -05:00
J. Nick Koston
19f7e36753 fixes 2025-06-29 16:10:58 -05:00
J. Nick Koston
a963f97520 fixes 2025-06-29 16:07:26 -05:00
J. Nick Koston
ad2d48e9b7 fixes 2025-06-29 16:03:05 -05:00
J. Nick Koston
5c0d67ca14 fixes 2025-06-29 15:50:12 -05:00
J. Nick Koston
3467329a7c cleanup 2025-06-29 15:43:41 -05:00
J. Nick Koston
d73fa370f3 cleanup 2025-06-29 15:35:59 -05:00
J. Nick Koston
78fd0a4870 cleanup 2025-06-29 15:23:32 -05:00
J. Nick Koston
3162bb475d cleanup 2025-06-29 15:08:27 -05:00
J. Nick Koston
c17503abd5 cleanup 2025-06-29 15:03:48 -05:00
J. Nick Koston
3433ee8171 cleanup 2025-06-29 14:59:41 -05:00
J. Nick Koston
344297b0a7 cleanup 2025-06-29 14:51:24 -05:00
J. Nick Koston
947456628e cleanup 2025-06-29 14:51:01 -05:00
J. Nick Koston
80dd6c111d cleanup 2025-06-29 14:44:47 -05:00
J. Nick Koston
b70188ba4b cleanup 2025-06-29 14:40:13 -05:00
J. Nick Koston
c6064aa2b4 Merge remote-tracking branch 'upstream/dev' into idf_webserver_ota 2025-06-29 14:35:44 -05:00
J. Nick Koston
6596f864be merg3 2025-06-29 14:35:38 -05:00
J. Nick Koston
f61a40efb8 fixes 2025-06-29 11:16:00 -05:00
J. Nick Koston
b049f0b480 fixes 2025-06-29 11:13:25 -05:00
J. Nick Koston
b2641d29c1 fixes 2025-06-29 11:12:40 -05:00
J. Nick Koston
7b8cfc768d fixes 2025-06-29 11:11:47 -05:00
J. Nick Koston
04860567f7 fixes 2025-06-29 11:10:29 -05:00
J. Nick Koston
b16edb5a99 fixes 2025-06-29 11:09:05 -05:00
J. Nick Koston
15a995b2e7 fixes 2025-06-29 11:07:48 -05:00
J. Nick Koston
f57e26c54e fixes 2025-06-29 11:07:25 -05:00
J. Nick Koston
2b7bc1cd9f fixes 2025-06-29 11:03:37 -05:00
J. Nick Koston
614a2f66a3 fixes 2025-06-29 10:57:00 -05:00
J. Nick Koston
9047b02c92 fixes 2025-06-29 10:53:29 -05:00
J. Nick Koston
e73d0477bb Merge branch 'api_conditional_memory' into integration 2025-06-29 10:42:24 -05:00
J. Nick Koston
2b1e623eb4 defines 2025-06-29 10:42:11 -05:00
J. Nick Koston
c366d555e9 Add OTA support to ESP-IDF webserver 2025-06-29 10:38:53 -05:00
J. Nick Koston
7efbd62730 Add OTA support to ESP-IDF webserver 2025-06-29 10:34:49 -05:00
J. Nick Koston
b77c1d0af8 Add OTA support to ESP-IDF webserver 2025-06-29 10:33:49 -05:00
J. Nick Koston
f8810ea6a8 Merge branch 'webserver_eliminate_heap_url_match' into integration 2025-06-29 10:26:55 -05:00
J. Nick Koston
40dd667211 fixes 2025-06-29 10:26:39 -05:00
J. Nick Koston
848b572864 Merge branch 'webserver_eliminate_heap_url_match' into integration 2025-06-29 10:17:49 -05:00
J. Nick Koston
7c858fbccd Optimize web_server UrlMatch to avoid heap allocations 2025-06-29 10:15:06 -05:00
J. Nick Koston
a1814ea37d Merge branch 'useless_default_data' into integration 2025-06-29 09:22:57 -05:00
J. Nick Koston
5892a1dbe2 tests 2025-06-29 08:40:26 -05:00
J. Nick Koston
29f524f432 tests 2025-06-29 08:37:53 -05:00
J. Nick Koston
4ec588ebd7 Merge remote-tracking branch 'origin/integration' into integration 2025-06-29 08:20:36 -05:00
J. Nick Koston
efdef61477 Merge branch 'api_reduce' into integration 2025-06-29 08:20:22 -05:00
J. Nick Koston
fe2b9f8c12 correct fix 2025-06-29 08:20:12 -05:00
J. Nick Koston
c6be55eb55 Merge branch 'api_conditional_memory' into integration 2025-06-29 08:14:25 -05:00
J. Nick Koston
4c69925b84 lint 2025-06-29 08:13:28 -05:00
J. Nick Koston
bc6407df0a Merge branch 'bitpack_api' into integration 2025-06-29 08:09:52 -05:00
J. Nick Koston
01982a8d0a reduce upper bound of batch delay as it did not make sense 2025-06-29 07:59:59 -05:00
J. Nick Koston
b995cd6257 Merge remote-tracking branch 'origin/integration' into integration 2025-06-29 07:47:28 -05:00
J. Nick Koston
b16d7b7a95 Merge branch 'api_conditional_memory' into integration 2025-06-29 07:47:17 -05:00
J. Nick Koston
42aea701d3 Reduce API component memory usage with conditional compilation 2025-06-29 07:45:48 -05:00
J. Nick Koston
5f56c85182 Merge remote-tracking branch 'origin/integration' into integration 2025-06-29 07:42:05 -05:00
J. Nick Koston
52b4eb8950 Merge branch 'api_reduce' into integration 2025-06-29 07:41:00 -05:00
J. Nick Koston
eeb2b42a0f fixes 2025-06-29 07:39:07 -05:00
J. Nick Koston
90772033d1 revert bad feedback 2025-06-29 06:10:55 -05:00
J. Nick Koston
dadeb4d2a9 Merge branch 'api_reduce' into integration 2025-06-28 23:24:40 -05:00
J. Nick Koston
60a5029c88 lint 2025-06-28 23:24:30 -05:00
J. Nick Koston
d7ba16b48b Merge branch 'api_reduce' into integration 2025-06-28 23:06:51 -05:00
J. Nick Koston
fca9befa63 cleanup 2025-06-28 23:06:40 -05:00
J. Nick Koston
187cbde0db cleanup 2025-06-28 23:06:34 -05:00
J. Nick Koston
f5ae5cade8 cleanup 2025-06-28 23:06:09 -05:00
J. Nick Koston
3e66c28aff Merge branch 'api_reduce' into integration 2025-06-28 23:05:15 -05:00
J. Nick Koston
89703a1aef cleanup 2025-06-28 23:05:02 -05:00
J. Nick Koston
cba31617e9 Merge branch 'api_reduce' into integration 2025-06-28 23:02:05 -05:00
J. Nick Koston
a3eeb46961 reduce 2025-06-28 23:01:48 -05:00
J. Nick Koston
128bd76f20 reduce 2025-06-28 22:45:00 -05:00
J. Nick Koston
c0355fd2c6 Merge branch 'webserver_helper_reduce_flash' into integration 2025-06-28 22:09:03 -05:00
J. Nick Koston
a5fd440e25 cleanup 2025-06-28 22:08:47 -05:00
J. Nick Koston
592ef8be2a Merge remote-tracking branch 'origin/integration' into integration 2025-06-28 22:04:41 -05:00
J. Nick Koston
3bcc1c7297 Merge branch 'add_message_object' into integration 2025-06-28 22:04:27 -05:00
J. Nick Koston
3b44c3acd1 Reduce flash usage by making add_message_object non-template 2025-06-28 22:03:04 -05:00
J. Nick Koston
ec4911643a Merge remote-tracking branch 'upstream/integration' into integration 2025-06-28 21:40:44 -05:00
J. Nick Koston
f4fedbab44 Merge branch 'webserver_helper_reduce_flash' into integration 2025-06-28 21:40:26 -05:00
J. Nick Koston
553d441ecc Reduce web_server code duplication by extracting detail parameter parsing 2025-06-28 21:38:40 -05:00
J. Nick Koston
1946116438 Merge branch 'api_read_message' into integration 2025-06-28 21:22:25 -05:00
J. Nick Koston
ab28515fba fix 2025-06-28 21:17:59 -05:00
J. Nick Koston
4dc11fb95e Merge branch 'api_read_message' into integration 2025-06-28 21:11:16 -05:00
J. Nick Koston
e27094e0f3 Remove unused return value from read_message and fix ifdef placement in generated API code 2025-06-28 21:09:33 -05:00
J. Nick Koston
88302201eb Merge remote-tracking branch 'origin/integration' into integration 2025-06-28 20:56:24 -05:00
J. Nick Koston
8afb172e83 Merge branch 'api_reduce' into integration 2025-06-28 20:56:00 -05:00
J. Nick Koston
562d024623 Remove single-use send_*_info wrappers in API connection 2025-06-28 20:49:09 -05:00
J. Nick Koston
50b094547c Remove single-use send_*_info wrappers in API connection 2025-06-28 20:47:57 -05:00
J. Nick Koston
a6c1e50985 Remove single-use send_*_info wrappers in API connection 2025-06-28 20:46:17 -05:00
J. Nick Koston
96772bdfc6 Merge remote-tracking branch 'origin/integration' into integration 2025-06-28 20:13:13 -05:00
J. Nick Koston
ed154d373c Merge remote-tracking branch 'origin/dev' into integration 2025-06-28 20:12:59 -05:00
J. Nick Koston
a5e862ce36 Remove redundant get_setup_priority() overrides returning default value 2025-06-28 17:21:20 -05:00
J. Nick Koston
ae55964bd9 Merge remote-tracking branch 'origin/bitpack_api' into integration 2025-06-28 16:47:43 -05:00
J. Nick Koston
c162309f41 Pack APIConnection members to reduce memory footprint 2025-06-28 16:46:17 -05:00
J. Nick Koston
62c667f1a0 Merge remote-tracking branch 'origin/dev' into integration 2025-06-28 16:11:15 -05:00
J. Nick Koston
3d08eae8e4 Merge branch 'scheduler_copy' into integration 2025-06-28 15:52:09 -05:00
J. Nick Koston
2af5a0a6dd Merge remote-tracking branch 'origin/scheduler_copy' into scheduler_copy 2025-06-28 15:52:03 -05:00
J. Nick Koston
6d24b04235 cover 2025-06-28 15:51:50 -05:00
J. Nick Koston
3ee8103353 Merge branch 'scheduler_copy' into integration 2025-06-28 15:49:33 -05:00
J. Nick Koston
1296165fce Merge branch 'dev' into scheduler_copy 2025-06-28 15:48:11 -05:00
J. Nick Koston
7100c22dc4 address copilot comments 2025-06-28 15:47:24 -05:00
J. Nick Koston
5718c0f5b8 Update test_scheduler_string_test.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-06-28 11:25:42 -05:00
J. Nick Koston
25ebddfa1c Update test_scheduler_string_test.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-06-28 11:25:36 -05:00
J. Nick Koston
2c0558fe23 Update test_scheduler_string_test.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-06-28 11:25:30 -05:00
J. Nick Koston
7192108fc1 Merge branch 'scheduler_copy' into integration 2025-06-28 10:32:21 -05:00
J. Nick Koston
847696c342 safer 2025-06-28 10:32:10 -05:00
J. Nick Koston
912ae1fc87 Merge remote-tracking branch 'origin/integration' into integration 2025-06-28 10:28:35 -05:00
J. Nick Koston
a86f75d31d Merge branch 'scheduler_copy' into integration 2025-06-28 10:28:22 -05:00
J. Nick Koston
fe1e25b5c7 Merge remote-tracking branch 'upstream/integration' into integration 2025-06-28 10:22:18 -05:00
J. Nick Koston
9b241b596a Merge branch 'scheduler_copy' into integration 2025-06-28 10:22:07 -05:00
J. Nick Koston
53b9c8d5bb cleanup 2025-06-28 10:15:05 -05:00
J. Nick Koston
2946bc9d72 cover 2025-06-28 10:10:43 -05:00
J. Nick Koston
67a20e212d safe 2025-06-28 09:59:50 -05:00
J. Nick Koston
a9ace366eb dry 2025-06-28 09:50:27 -05:00
J. Nick Koston
df3469efba dry 2025-06-28 09:48:58 -05:00
J. Nick Koston
0a3bbb8554 dry 2025-06-28 09:48:26 -05:00
J. Nick Koston
a15b9f5d3b dry 2025-06-28 09:45:59 -05:00
J. Nick Koston
e6334b0716 dry 2025-06-28 09:41:12 -05:00
J. Nick Koston
7a835baa5a Merge remote-tracking branch 'upstream/dev' into scheduler_copy 2025-06-28 09:36:32 -05:00
J. Nick Koston
c9c21a5728 Merge remote-tracking branch 'upstream/dev' into integration 2025-06-28 09:36:07 -05:00
J. Nick Koston
956959fc32 safety 2025-06-28 09:23:16 -05:00
J. Nick Koston
6f67f74638 Merge remote-tracking branch 'upstream/dev' into integration 2025-06-28 08:43:19 -05:00
J. Nick Koston
b3dd4543b7 Merge branch 'update_libsodium' into integration 2025-06-27 17:10:52 -05:00
J. Nick Koston
4f17a28ac5 Merge branch 'extract_helpers' into integration 2025-06-27 17:10:17 -05:00
J. Nick Koston
90736f367a release 2025-06-27 16:36:32 -05:00
J. Nick Koston
9af88bd482 DNM: Update libsodium
needs https://github.com/esphome/noise-c/pull/4
2025-06-27 14:07:27 -05:00
J. Nick Koston
13b89f4934 Merge branch 'libretiny_logconfig' into integration 2025-06-27 13:59:01 -05:00
J. Nick Koston
d00a00d142 Reduce libretiny logconfig messages
align with https://developers.esphome.io/architecture/logging
2025-06-27 13:58:33 -05:00
J. Nick Koston
e662c39e16 Merge branch 'extract_helpers' into integration 2025-06-27 13:18:50 -05:00
J. Nick Koston
95ef131285 address bot comments 2025-06-27 13:18:39 -05:00
J. Nick Koston
7476f170f6 Merge remote-tracking branch 'upstream/extract_helpers' into extract_helpers 2025-06-27 13:16:17 -05:00
J. Nick Koston
3b6bd55d1e address bot comments 2025-06-27 13:16:06 -05:00
J. Nick Koston
10dbc9e884 Merge remote-tracking branch 'origin/extract_helpers' into integration 2025-06-27 13:03:20 -05:00
J. Nick Koston
860f619dfe Merge branch 'dev' into extract_helpers 2025-06-27 20:02:57 +02:00
J. Nick Koston
17ddc9ee0c Merge branch 'extract_helpers' into integration 2025-06-27 12:56:28 -05:00
J. Nick Koston
949689c318 address bot review 2025-06-27 12:55:58 -05:00
J. Nick Koston
86a2aac011 Merge branch 'extract_helpers' into integration 2025-06-27 12:50:37 -05:00
J. Nick Koston
d0a402f201 Extract lock-free queue and event pool to core helpers 2025-06-27 12:49:44 -05:00
J. Nick Koston
05772d5365 Merge remote-tracking branch 'origin/integration' into integration 2025-06-27 10:19:35 -05:00
J. Nick Koston
c2a68f5147 Merge branch 'duplicate_webserver_code' into integration 2025-06-27 10:19:21 -05:00
J. Nick Koston
697ca1c7be simplify 2025-06-27 10:17:33 -05:00
J. Nick Koston
409346952f clang-format 2025-06-27 10:15:04 -05:00
J. Nick Koston
f4b3539d77 clang-format 2025-06-27 10:05:30 -05:00
J. Nick Koston
c12166c1a1 missed one 2025-06-27 10:04:29 -05:00
J. Nick Koston
8d20f003cb Merge branch 'duplicate_webserver_code' into integration 2025-06-27 09:45:00 -05:00
J. Nick Koston
88f857a2f0 defines 2025-06-27 09:44:50 -05:00
J. Nick Koston
fb7faadd99 reduce memory 2025-06-27 09:41:20 -05:00
J. Nick Koston
5c8d6752fb Merge branch 'dev' into duplicate_webserver_code 2025-06-27 16:01:32 +02:00
J. Nick Koston
dda81fbc2c Merge branch 'dev' into binary_sensor_gpio_polling 2025-06-27 15:58:14 +02:00
J. Nick Koston
c40dff5d63 cleanup 2025-06-27 06:30:51 -05:00
J. Nick Koston
6f07b54772 cleanup 2025-06-27 06:30:42 -05:00
J. Nick Koston
ce0f1dfcb6 Merge remote-tracking branch 'upstream/dev' into integration 2025-06-27 06:30:15 -05:00
J. Nick Koston
9a3a5d48eb Merge branch 'dynamic_logging' into integration 2025-06-26 20:47:40 -05:00
J. Nick Koston
4a759eda02 Disable dynamic log level control for ESP32 ESP-IDF builds 2025-06-26 20:47:02 -05:00
J. Nick Koston
26badf201d fixes 2025-06-27 01:17:26 +02:00
J. Nick Koston
384f27cd6d Merge branch 'wifi_memory' into integration 2025-06-27 01:13:11 +02:00
J. Nick Koston
ac1c5f9f58 Reduce WiFi component memory usage 2025-06-27 01:12:19 +02:00
J. Nick Koston
8ad058fdf4 Merge branch 'ethernet_padding' into integration 2025-06-27 01:00:27 +02:00
J. Nick Koston
9024c3c67a Reduce ethernet component memory usage by 8 bytes through struct optimization 2025-06-27 00:59:50 +02:00
J. Nick Koston
fc81a47499 Merge branch 'esp32_gpio_padding_waste' into integration 2025-06-27 00:43:41 +02:00
J. Nick Koston
a331452076 Reduce ESP32 GPIO memory usage by optimizing struct padding 2025-06-27 00:42:30 +02:00
J. Nick Koston
b1c6e8168e Merge remote-tracking branch 'origin/ota_memory_str' into integration 2025-06-27 00:34:36 +02:00
J. Nick Koston
b41cc0226e Optimize OTA password storage from std::string to const char 2025-06-27 00:24:45 +02:00
J. Nick Koston
450429ddd5 Merge branch 'safe_mode_padding' into integration 2025-06-27 00:22:40 +02:00
J. Nick Koston
f7b24f4b4b Optimize SafeModeComponent memory layout to reduce padding 2025-06-27 00:20:44 +02:00
J. Nick Koston
294c985380 Merge branch 'duplicate_webserver_code' into integration 2025-06-27 00:09:07 +02:00
J. Nick Koston
720964b901 Refactor web_server to extract duplicate sorting info code into helper method 2025-06-27 00:05:56 +02:00
J. Nick Koston
8895c8a987 bitpack api flags 2025-06-26 12:46:57 +02:00
J. Nick Koston
740dcd72a2 Merge branch 'duplicate_client_peername' into integration 2025-06-26 12:00:03 +02:00
J. Nick Koston
ffd442624f Optimize API connection memory usage by removing client_peername_ 2025-06-26 11:59:03 +02:00
J. Nick Koston
088fd85694 Merge branch 'batch_ping_fallback' into integration 2025-06-26 10:14:21 +02:00
J. Nick Koston
d5b68d69d3 tweak 2025-06-26 10:14:05 +02:00
J. Nick Koston
bb0f7bb393 Merge branch 'batch_ping_fallback' into integration 2025-06-26 10:10:12 +02:00
J. Nick Koston
d86a108f18 Merge remote-tracking branch 'upstream/dev' into batch_ping_fallback 2025-06-26 10:09:24 +02:00
J. Nick Koston
7828ed2d9e Merge branch 'batch_ping_fallback' into integration 2025-06-26 10:05:06 +02:00
J. Nick Koston
ebf14f50fb Merge branch 'dev' of https://github.com/esphome/esphome into batch_ping_fallback 2025-06-26 10:02:32 +02:00
J. Nick Koston
1546ff615b Merge branch 'app_padding' into integration 2025-06-26 02:47:46 +02:00
J. Nick Koston
46cf1fb597 comment 2025-06-26 02:47:33 +02:00
J. Nick Koston
8bf8655054 Merge branch 'app_padding' into integration 2025-06-26 02:45:13 +02:00
J. Nick Koston
a6d84948e2 Optimize Application class memory layout and reduce loop_interval size 2025-06-26 02:44:44 +02:00
J. Nick Koston
fac20a1f97 Merge branch 'batch_ping_fallback' into integration 2025-06-26 02:15:41 +02:00
J. Nick Koston
c65586b5e1 cleanup 2025-06-26 02:15:32 +02:00
J. Nick Koston
b27b018b06 Merge remote-tracking branch 'origin/integration' into integration 2025-06-26 02:13:42 +02:00
J. Nick Koston
403da1e632 Merge branch 'batch_ping_fallback' into integration 2025-06-26 02:12:53 +02:00
J. Nick Koston
2371ec1f9e Replace ping retry timer with batch queue fallback 2025-06-26 02:11:17 +02:00
J. Nick Koston
5e3ec2d34b lint 2025-06-26 00:24:53 +02:00
J. Nick Koston
78d84644c9 lint 2025-06-26 00:24:12 +02:00
J. Nick Koston
0cd0f8015a Merge branch 'message_creator_ram' into integration 2025-06-26 00:09:31 +02:00
J. Nick Koston
4b5424f695 nolint 2025-06-26 00:08:15 +02:00
J. Nick Koston
a1d59040f7 Merge remote-tracking branch 'origin/message_creator_ram' into integration 2025-06-25 23:54:37 +02:00
J. Nick Koston
0306398072 Merge remote-tracking branch 'origin/component_iterator' into integration 2025-06-25 23:54:33 +02:00
J. Nick Koston
a7e0bf9013 tweak 2025-06-25 23:53:22 +02:00
J. Nick Koston
ddb988cd83 Merge remote-tracking branch 'upstream/dev' into component_iterator 2025-06-25 23:39:45 +02:00
J. Nick Koston
04b54353f1 Merge remote-tracking branch 'upstream/dev' into scheduler_copy 2025-06-25 23:36:41 +02:00
J. Nick Koston
f058107c05 tweak 2025-06-25 23:33:54 +02:00
J. Nick Koston
6b5b0815d7 tidy issues 2025-06-25 23:26:57 +02:00
J. Nick Koston
8388497038 tidy issues 2025-06-25 23:18:50 +02:00
J. Nick Koston
825b1113b6 tweak 2025-06-25 23:17:41 +02:00
J. Nick Koston
9074ef792f Reduce component_iterator memory usage 2025-06-25 19:35:40 +02:00
J. Nick Koston
0946f28511 avoid string copy in scheduler for const strings 2025-06-25 19:08:18 +02:00
J. Nick Koston
23765cd4f5 Merge branch 'message_creator_ram' into integration 2025-06-25 18:28:56 +02:00
J. Nick Koston
e20c6468d0 fix missed one 2025-06-25 18:27:43 +02:00
J. Nick Koston
b90516de1d Merge branch 'template_value' into integration 2025-06-25 17:30:36 +02:00
J. Nick Koston
ec5cc0f00f Merge branch 'integration' of https://github.com/esphome/esphome into integration 2025-06-25 17:30:27 +02:00
J. Nick Koston
5dda5a976e Merge branch 'message_creator_ram' into integration 2025-06-25 17:22:41 +02:00
J. Nick Koston
915da9ae13 make the bot happy 2025-06-25 17:22:23 +02:00
J. Nick Koston
8652464f4e Merge branch 'dev' into message_creator_ram 2025-06-25 17:16:31 +02:00
J. Nick Koston
ce6ce1c1f8 Merge branch 'message_creator_ram' into integration 2025-06-25 17:10:41 +02:00
J. Nick Koston
39efe67e55 Optimize API connection memory with tagged pointers 2025-06-25 17:08:57 +02:00
J. Nick Koston
748ffa00f3 Optimize TemplatableValue memory 2025-06-25 14:49:01 +02:00
J. Nick Koston
e8d9df2b0e Merge branch 'sensor_memory' into integration 2025-06-25 14:32:47 +02:00
J. Nick Koston
17396d67de revert 2025-06-25 14:32:38 +02:00
J. Nick Koston
edd6a86714 Merge branch 'sensor_memory' into integration 2025-06-25 14:26:03 +02:00
J. Nick Koston
85b4012c56 Merge branch 'dev' into sensor_memory 2025-06-25 14:24:09 +02:00
J. Nick Koston
7d98433502 Update tests/integration/test_host_mode_sensor.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-06-25 14:23:59 +02:00
J. Nick Koston
23774ae03b Reduce memory required for sensor entities 2025-06-25 14:17:05 +02:00
J. Nick Koston
0dedbcdd71 Merge branch 'multi_device' into integration 2025-06-25 13:44:20 +02:00
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
8b25b1eee6 update tests now that duplicate names are validated 2025-06-25 00:18:28 +02:00
J. Nick Koston
3bbf30ff5f Merge branch 'multi_device' into integration 2025-06-25 00:04:33 +02:00
J. Nick Koston
83613726d1 fix 2025-06-25 00:04:07 +02:00
J. Nick Koston
254b6a17f3 Merge remote-tracking branch 'dala318/multi_device' into integration 2025-06-24 23:54:40 +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
dbb0d6349a Merge branch 'multi_device' into integration 2025-06-24 18:08:14 +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
5cd498fbe9 Merge branch 'multi_device' into integration 2025-06-23 22:56:28 +02:00
J. Nick Koston
250f515f08 Merge branch 'api_opt' into integration 2025-06-23 12:20:40 +02:00
J. Nick Koston
0ec0a9e313 missing ifdef 2025-06-23 12:19:21 +02:00
J. Nick Koston
184f42ef03 Merge branch 'api_opt' into integration 2025-06-23 12:10:26 +02:00
J. Nick Koston
499517418d clang-tidy 2025-06-23 12:10:15 +02:00
J. Nick Koston
606b9c1a6d Merge branch 'api_opt' into integration 2025-06-23 12:00:34 +02:00
J. Nick Koston
971e954a54 follow logging guidelines 2025-06-23 11:59:07 +02:00
J. Nick Koston
e3aaf3219d speed up test 2025-06-23 11:58:16 +02:00
J. Nick Koston
0eea1c0e40 preen 2025-06-23 11:56:09 +02:00
J. Nick Koston
0773819778 cleanup 2025-06-23 11:45:58 +02:00
J. Nick Koston
170869b7db preen 2025-06-23 11:39:25 +02:00
J. Nick Koston
5dc54782e5 preen 2025-06-23 11:38:30 +02:00
J. Nick Koston
97b26fbefe preen 2025-06-23 11:38:10 +02:00
J. Nick Koston
686cc58d6c preen 2025-06-23 11:37:59 +02:00
J. Nick Koston
76a59759b2 preen 2025-06-23 11:37:27 +02:00
J. Nick Koston
93245a24b5 preen 2025-06-23 11:36:54 +02:00
J. Nick Koston
6a22ea1c7d preen 2025-06-23 11:35:41 +02:00
J. Nick Koston
56a02409c8 preen 2025-06-23 11:34:11 +02:00
J. Nick Koston
edeafd5a53 preen 2025-06-23 11:31:38 +02:00
J. Nick Koston
f67490b69b preen 2025-06-23 11:29:04 +02:00
J. Nick Koston
b76e34fb7b preen 2025-06-23 11:25:52 +02:00
J. Nick Koston
ddbda5032b preen 2025-06-23 11:25:24 +02:00
J. Nick Koston
5898d34b0a preen 2025-06-23 11:22:45 +02:00
J. Nick Koston
b0c02341ff preen 2025-06-23 11:22:08 +02:00
J. Nick Koston
19cbc8c33b preen 2025-06-23 11:21:37 +02:00
J. Nick Koston
02e61ef5d3 preen 2025-06-23 11:20:06 +02:00
J. Nick Koston
8d5d18064d preen 2025-06-23 11:19:56 +02:00
J. Nick Koston
c5ef7ebd27 preen 2025-06-23 11:19:07 +02:00
J. Nick Koston
047a3e0e8c preen 2025-06-23 11:18:47 +02:00
J. Nick Koston
13b23f840b preen 2025-06-23 11:17:17 +02:00
J. Nick Koston
147f6012b2 preen 2025-06-23 11:16:34 +02:00
J. Nick Koston
2c315595f0 preen 2025-06-23 11:12:04 +02:00
J. Nick Koston
20405c84ac preen 2025-06-23 11:10:07 +02:00
J. Nick Koston
0bc59b97de more api loop reductions 2025-06-23 11:06:51 +02:00
J. Nick Koston
a3a3bdc7eb more api loop reductions 2025-06-23 11:02:27 +02:00
J. Nick Koston
e767f30886 more api loop reductions 2025-06-23 10:59:49 +02:00
J. Nick Koston
e8c250a03c more api loop reductions 2025-06-23 10:59:00 +02:00
J. Nick Koston
d6725fc1ca more api loop reductions 2025-06-23 10:54:50 +02:00
J. Nick Koston
8ec998ff30 more api loop reductions 2025-06-23 10:52:34 +02:00
J. Nick Koston
23cc0c7f39 Merge remote-tracking branch 'upstream/dev' into api_reboot 2025-06-23 10:48:26 +02:00
J. Nick Koston
19b8bd6aa8 Merge remote-tracking branch 'upstream/logger_disable_loop' into integration 2025-06-23 09:03:16 +02:00
J. Nick Koston
ed57e7c6b0 Update esphome/components/logger/logger.cpp 2025-06-23 09:02:22 +02:00
J. Nick Koston
9f489c9f27 Update esphome/components/logger/logger.h 2025-06-23 09:01:21 +02:00
J. Nick Koston
f036989361 Update esphome/components/logger/logger.h 2025-06-23 09:01:01 +02:00
J. Nick Koston
6afa8141c0 Update esphome/components/logger/logger.cpp 2025-06-23 09:00:46 +02:00
J. Nick Koston
587964c6f1 Merge branch 'dev' into logger_disable_loop 2025-06-23 09:00:22 +02:00
Jesse Hills
7aea82a273 Move define 2025-06-23 14:15:10 +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
J. Nick Koston
bfa80157f2 Merge branch 'scheduler_memory_opt' into integration 2025-06-23 00:07:43 +02:00
J. Nick Koston
99b1b079d0 Reduce RAM usage for scheduled tasks 2025-06-23 00:03:01 +02:00
J. Nick Koston
5697d549a8 Use scheduler for api reboot 2025-06-22 23:44:08 +02: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
a0b3527710 Merge branch 'logger_memory' into integration 2025-06-22 22:59:51 +02:00
J. Nick Koston
df24f48fa1 Merge branch 'pre_preserve_looping_components' into integration 2025-06-22 22:57:38 +02:00
J. Nick Koston
13d53590b2 Pre-reserve looping components vector to reduce memory allocations 2025-06-22 22:56:31 +02: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
e7fa156254 Merge remote-tracking branch 'upstream/dev' into integration 2025-06-22 20:15:02 +02:00
J. Nick Koston
a8ab6b1c43 Merge branch 'dev' into logger_disable_loop 2025-06-22 20:12:17 +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
6212c6f80f Merge branch 'dev' into logger_disable_loop 2025-06-22 12:10:11 +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
bbd5d050a9 Merge branch 'dev' into logger_disable_loop 2025-06-21 18:36:59 +02:00
J. Nick Koston
71a96fdcbf Merge branch 'dev' into logger_disable_loop 2025-06-21 18:11:19 +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
J. Nick Koston
41697a7b1b Merge remote-tracking branch 'upstream/logger_disable_loop' into integration 2025-06-21 11:19:12 +02:00
J. Nick Koston
912e265bc0 Merge branch 'dev' into logger_disable_loop 2025-06-21 11:18:59 +02:00
J. Nick Koston
96ee6fb064 Merge branch 'logger_disable_loop' into integration 2025-06-21 11:17:13 +02:00
J. Nick Koston
788dba8ef3 define 2025-06-21 11:16:14 +02:00
J. Nick Koston
fdde9c4681 Reduce Logger memory usage by optimizing variable sizes 2025-06-21 00:27:05 +02:00
J. Nick Koston
f195e73d38 Merge branch 'logger_disable_loop' into integration 2025-06-20 22:54:40 +02:00
J. Nick Koston
b0d9ffc6a1 Reduce logger CPU usage by disabling loop when buffer is empty 2025-06-20 22:53:12 +02:00
J. Nick Koston
e17619841d fix last component being charged for stats 2025-06-20 22:03:53 +02:00
J. Nick Koston
eb6a7cf3b9 fix last component being charged for stats 2025-06-20 22:02:19 +02:00
J. Nick Koston
9901e2d72e Merge branch 'dev' into integration 2025-06-20 21:36:36 +02:00
J. Nick Koston
1be4e23b68 Merge branch 'dev' into binary_sensor_gpio_polling 2025-06-19 11:07:42 +02:00
J. Nick Koston
e78094cc0a Merge branch 'dev' into esp32_touch_isr 2025-06-19 10:49:17 +02:00
J. Nick Koston
bcf961c0b0 Merge branch 'dev' into integration 2025-06-19 04:05:25 +02:00
J. Nick Koston
f84a4c9753 Merge remote-tracking branch 'origin/disable_ethernet_loop' into integration 2025-06-19 03:42:53 +02:00
J. Nick Koston
df56ca0236 remove redundant enable_loop, it must already be enabled to get here 2025-06-19 03:41:25 +02:00
J. Nick Koston
de0cd0ec67 Merge branch 'dev' into disable_ethernet_loop 2025-06-19 03:39:15 +02:00
J. Nick Koston
67c30245c4 make copilot happy 2025-06-19 02:01:55 +02:00
J. Nick Koston
1f72757591 tidy 2025-06-19 01:35:45 +02:00
J. Nick Koston
35c2fdf6af dry 2025-06-19 01:31:11 +02:00
J. Nick Koston
d1ecd841be avoid auto 2025-06-19 01:28:17 +02:00
J. Nick Koston
828a49697c Merge branch 'gap_events' into integration 2025-06-19 01:18:36 +02:00
J. Nick Koston
0551495501 try another way 2025-06-19 01:18:26 +02:00
J. Nick Koston
2bbffe4a68 try another way 2025-06-19 01:18:11 +02:00
J. Nick Koston
281ad90e39 fixes 2025-06-19 01:16:46 +02:00
J. Nick Koston
ed50976a07 fixes 2025-06-19 01:16:22 +02:00
J. Nick Koston
a3400037d9 fixes 2025-06-19 01:14:15 +02:00
J. Nick Koston
f0d82f75bc fixes 2025-06-19 01:14:05 +02:00
J. Nick Koston
349cb80e90 Merge remote-tracking branch 'origin/integration' into integration 2025-06-19 01:12:20 +02:00
J. Nick Koston
c263ee39af Merge branch 'gap_events' into integration 2025-06-19 01:12:07 +02:00
J. Nick Koston
e99bc52756 Fix missing BLE GAP events causing RSSI sensor and beacon failures 2025-06-19 01:09:13 +02:00
J. Nick Koston
7944b2b8e9 Merge branch 'ota_perf' into integration 2025-06-19 00:40:07 +02:00
J. Nick Koston
ca6ae746c1 be explict 2025-06-19 00:39:19 +02:00
J. Nick Koston
deabac18b2 Merge branch 'disable_ethernet_loop' into integration 2025-06-18 21:39:35 +02:00
J. Nick Koston
5cf8681c61 Merge branch 'ota_perf' into integration 2025-06-18 21:35:14 +02:00
J. Nick Koston
ca7ede8f96 more cleanups 2025-06-18 21:35:04 +02:00
J. Nick Koston
4969682d52 Merge branch 'ota_perf' into integration 2025-06-18 21:27:51 +02:00
J. Nick Koston
8002fe0dd5 remove safety check 2025-06-18 21:27:30 +02:00
J. Nick Koston
7dfdf965b7 remove safety check 2025-06-18 21:26:32 +02:00
J. Nick Koston
b408795dd6 Merge branch 'api_reads' into integration 2025-06-18 19:24:32 +02:00
J. Nick Koston
a5a099336b one more 2025-06-18 19:22:23 +02:00
J. Nick Koston
4ae56fc004 Merge branch 'api_reads' into integration 2025-06-18 18:40:35 +02:00
J. Nick Koston
3f71c09b7b Fix slow noise handshake by reading multiple messages per loop 2025-06-18 18:36:55 +02:00
J. Nick Koston
bd50a7f1ab cleanup 2025-06-18 14:33:58 +02:00
J. Nick Koston
51e4c45e5c Merge branch 'loop_done_enable_isr' into disable_ethernet_loop 2025-06-18 14:27:18 +02:00
J. Nick Koston
e3fae49add Merge branch 'binary_sensor_gpio_polling' into integration 2025-06-18 14:24:42 +02:00
J. Nick Koston
610215ab60 updates 2025-06-18 14:24:31 +02:00
J. Nick Koston
74acbda435 Merge branch 'loop_done_enable_isr' into binary_sensor_gpio_polling 2025-06-18 14:19:03 +02:00
J. Nick Koston
25c4af777c Merge branch 'loop_done_enable_isr' into integration 2025-06-18 14:18:35 +02:00
J. Nick Koston
ec186e6324 rename 2025-06-18 14:17:45 +02:00
J. Nick Koston
150b7a98f3 Merge branch 'dev' into ota_perf 2025-06-18 13:57:20 +02:00
J. Nick Koston
8ae7c1cff0 Merge branch 'ota_perf' into integration 2025-06-18 13:46:36 +02:00
J. Nick Koston
7f1d0eef98 Optimize OTA loop to avoid unnecessary stack allocations 2025-06-18 13:44:07 +02:00
J. Nick Koston
1179ab33f2 tweaks 2025-06-18 12:52:18 +02:00
J. Nick Koston
a09faa1c10 Merge branch 'dev' into disable_ethernet_loop 2025-06-18 12:36:22 +02:00
J. Nick Koston
c0319d9b2f Merge branch 'binary_sensor_gpio_polling' into integration 2025-06-18 12:28:59 +02:00
J. Nick Koston
4870cd2921 use enable_loop_soon_from_isr 2025-06-18 12:28:49 +02:00
J. Nick Koston
d4280ec68b Merge branch 'loop_done_enable_isr' into binary_sensor_gpio_polling 2025-06-18 12:23:55 +02:00
J. Nick Koston
52cdc11927 Merge remote-tracking branch 'origin/proxy_memory' into integration 2025-06-18 12:21:31 +02:00
J. Nick Koston
8345b8c9ce Update esphome/components/esp32_ble_client/ble_client_base.h
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-06-18 12:21:10 +02:00
J. Nick Koston
c56f0677c3 Merge remote-tracking branch 'upstream/proxy_memory' into integration 2025-06-18 12:16:23 +02:00
J. Nick Koston
00e9e1421e Merge branch 'dev' into proxy_memory 2025-06-18 12:16:12 +02:00
J. Nick Koston
93c72c6e6c Merge branch 'loop_done_enable_isr' into integration 2025-06-18 12:15:24 +02:00
J. Nick Koston
9cea930dbd Merge remote-tracking branch 'upstream/dev' into integration 2025-06-18 12:15:18 +02:00
J. Nick Koston
7b9bd70729 Add enable_loop_soon_from_isr 2025-06-18 12:09:12 +02:00
J. Nick Koston
5115c7a100 Merge branch 'bump_ruff_precommit' into integration 2025-06-18 00:15:23 +02:00
J. Nick Koston
5634494e64 Bump ruff in pre-commit to 0.12.0
matches https://github.com/esphome/esphome/pull/9120
2025-06-18 00:11:40 +02:00
J. Nick Koston
aa8bd4abf1 Bump ruff in pre-commit to 0.12.0
matches https://github.com/esphome/esphome/pull/9120
2025-06-18 00:10:30 +02:00
J. Nick Koston
17fd69dd7f Bump ruff in pre-commit to 0.12.0
matches https://github.com/esphome/esphome/pull/9120
2025-06-18 00:09:18 +02:00
J. Nick Koston
1d9dae374b Merge branch 'loop_done' into integration 2025-06-17 23:45:20 +02:00
J. Nick Koston
cb2241ad91 make sure components that disable in setup are disabled at start 2025-06-17 23:45:16 +02:00
J. Nick Koston
d8a7e9abc8 make sure components that disable in setup are disabled at start 2025-06-17 23:44:32 +02:00
J. Nick Koston
969abc3f29 make sure components that disable in setup are disabled at start 2025-06-17 23:40:46 +02:00
J. Nick Koston
766fdc8a1f make sure components that disable in setup are disabled at start 2025-06-17 23:40:31 +02:00
J. Nick Koston
4c37c20d76 cleaner fix 2025-06-17 22:30:35 +02:00
J. Nick Koston
7d314398e1 cleaner fix 2025-06-17 22:30:31 +02:00
J. Nick Koston
b69191e3a8 cleaner fix 2025-06-17 22:29:21 +02:00
J. Nick Koston
b27c6b3596 cleaner fix 2025-06-17 22:27:24 +02:00
J. Nick Koston
5453835963 make ble client disable/enable smarter 2025-06-17 18:13:09 +02:00
J. Nick Koston
4d55ba057c make ble client disable/enable smarter 2025-06-17 18:09:53 +02:00
J. Nick Koston
325c01242c tweak 2025-06-17 16:16:20 +02:00
J. Nick Koston
45b32bca89 tweak 2025-06-17 16:08:28 +02:00
J. Nick Koston
7620049214 tweak 2025-06-17 16:05:48 +02:00
J. Nick Koston
3553495a60 Merge remote-tracking branch 'origin/integration' into integration 2025-06-17 15:55:51 +02:00
J. Nick Koston
3ce6db61d5 Merge branch 'binary_sensor_gpio_polling' into integration 2025-06-17 15:55:28 +02:00
J. Nick Koston
798ff32c40 cleanup 2025-06-17 15:55:10 +02:00
J. Nick Koston
430cee8bda Merge branch 'integration' of https://github.com/esphome/esphome into integration 2025-06-17 15:05:27 +02:00
J. Nick Koston
1fe3fb25a6 Merge branch 'binary_sensor_gpio_polling' into integration 2025-06-17 14:38:08 +02:00
J. Nick Koston
685ed87581 preen 2025-06-17 14:38:00 +02:00
J. Nick Koston
ea3ea1eee7 tweak 2025-06-17 14:17:35 +02:00
J. Nick Koston
c9edcb909b Merge branch 'binary_sensor_gpio_polling' into integration 2025-06-17 13:42:02 +02:00
J. Nick Koston
35bfc9f069 tweak 2025-06-17 13:41:57 +02:00
J. Nick Koston
c4aec194b9 Merge branch 'binary_sensor_gpio_polling' into integration 2025-06-17 13:31:44 +02:00
J. Nick Koston
e8547b16f6 Avoid polling for GPIO binary sensors when possible 2025-06-17 13:20:41 +02:00
J. Nick Koston
2bbe08cee0 Avoid polling for GPIO binary sensors when possible 2025-06-17 13:18:45 +02:00
J. Nick Koston
0a0c369b88 Avoid polling for GPIO binary sensors when possible 2025-06-17 13:17:35 +02:00
J. Nick Koston
5d2f454a94 Avoid polling for GPIO binary sensors when possible 2025-06-17 13:13:58 +02:00
J. Nick Koston
04bcc5c879 Avoid polling for GPIO binary sensors when possible 2025-06-17 13:02:00 +02:00
J. Nick Koston
d4db16665f Avoid polling for GPIO binary sensors when possible 2025-06-17 12:41:17 +02:00
J. Nick Koston
20b7a494f6 Merge remote-tracking branch 'origin/proxy_memory' into integration 2025-06-17 12:05:43 +02:00
J. Nick Koston
fbdce3ad89 Optimize bluetooth_proxy memory usage on ESP32 2025-06-17 12:04:49 +02:00
J. Nick Koston
4fc8807f02 Merge branch 'light_memory' into integration 2025-06-17 11:49:58 +02:00
J. Nick Koston
83075bfb5c Optimize LightState memory layout 2025-06-17 11:49:15 +02:00
J. Nick Koston
4074ec0425 Merge branch 'switch_memory' into integration 2025-06-17 11:27:45 +02:00
J. Nick Koston
8e1694dd0f Reduce Switch component memory usage by 8 bytes per instance 2025-06-17 11:27:11 +02:00
J. Nick Koston
911df18855 Merge branch 'api_memory' into integration 2025-06-17 11:10:17 +02:00
J. Nick Koston
6b049e93f8 Optimize API component memory usage by reordering class members to reduce padding 2025-06-17 11:09:22 +02:00
J. Nick Koston
a335dcc379 Merge remote-tracking branch 'upstream/dev' into integration 2025-06-17 10:40:41 +02:00
J. Nick Koston
c6478c8a79 Merge branch 'reduce_duplicate_gen_code_api' into integration 2025-06-17 10:40:23 +02:00
J. Nick Koston
cc9d40cb60 tweaks 2025-06-17 10:40:12 +02:00
J. Nick Koston
0a6b7f9a1b Update script/api_protobuf/api_protobuf.py 2025-06-17 10:39:49 +02:00
J. Nick Koston
daa1fb9a7a Merge remote-tracking branch 'swoboda1337/bump_libretiny' into integration 2025-06-17 04:33:57 +02:00
Jonathan Swoboda
b7d543290b Bump LibreTiny 2025-06-16 21:40:06 -04:00
J. Nick Koston
ea852b60ac Merge branch 'esp32_ble_tracker_reduce_memory' into integration 2025-06-16 22:07:13 +02:00
J. Nick Koston
ed341988ea Use smaller atomic types for ESP32 BLE Tracker ring buffer indices 2025-06-16 22:06:04 +02:00
J. Nick Koston
057b6c8e30 Merge branch 'api_reduce_millis' into integration 2025-06-16 19:34:07 +02:00
J. Nick Koston
44444fe071 Optimize API server performance by using cached loop time 2025-06-16 19:33:29 +02:00
J. Nick Koston
797330d6ab Disable Ethernet loop polling when connected and stable 2025-06-16 17:28:04 +02:00
J. Nick Koston
a630d5b5f5 Merge branch 'ble_pool' into integration 2025-06-16 15:45:50 +02:00
J. Nick Koston
eb3dc82b5d naming 2025-06-16 15:45:38 +02:00
J. Nick Koston
34ed18d562 Merge branch 'ble_pool' into integration 2025-06-16 15:43:59 +02:00
J. Nick Koston
1ce02ee313 naming 2025-06-16 15:43:43 +02:00
J. Nick Koston
2a26a0188c ble pool 2025-06-16 15:29:37 +02:00
J. Nick Koston
50cb05d1b1 ble pool 2025-06-16 15:28:03 +02:00
J. Nick Koston
6e739ac453 ble pool 2025-06-16 15:23:04 +02:00
J. Nick Koston
7aa2fd9f0e ble pool 2025-06-16 15:19:10 +02:00
J. Nick Koston
8e254e1b03 ble pool 2025-06-16 15:18:19 +02:00
J. Nick Koston
1ad9d717ff ble pool 2025-06-16 15:17:57 +02:00
J. Nick Koston
104658e43a ble pool 2025-06-16 15:16:15 +02:00
J. Nick Koston
e7e4b995bf ble pool 2025-06-16 15:15:26 +02:00
J. Nick Koston
b35b54f2c2 ble pool 2025-06-16 15:11:42 +02:00
J. Nick Koston
f80aeb1d1d cleanup 2025-06-16 15:10:27 +02:00
J. Nick Koston
6a756ab3b6 cleanup 2025-06-16 15:09:49 +02:00
J. Nick Koston
58a697bed1 cleanup 2025-06-16 15:07:23 +02:00
J. Nick Koston
280960ac18 cleanup 2025-06-16 15:06:02 +02:00
J. Nick Koston
0640ff13aa ble pool 2025-06-16 15:04:40 +02:00
J. Nick Koston
545505691f ble pool 2025-06-16 15:02:10 +02:00
J. Nick Koston
11fcf81321 ble pool 2025-06-16 15:00:58 +02:00
J. Nick Koston
c565b37dc8 ble pool 2025-06-16 15:00:07 +02:00
J. Nick Koston
3d18495270 ble pool 2025-06-16 14:55:15 +02:00
J. Nick Koston
419e4e63e9 ble pool 2025-06-16 14:53:50 +02:00
J. Nick Koston
724aa2bf65 ble pool 2025-06-16 14:52:38 +02:00
J. Nick Koston
573fa8aeb3 ble pool 2025-06-16 14:52:28 +02:00
J. Nick Koston
8a672e34c5 ble pool 2025-06-16 14:47:05 +02:00
J. Nick Koston
bc49211dab ble pool 2025-06-16 14:43:29 +02:00
J. Nick Koston
4ef9c3667e Merge branch 'reduce_duplicate_gen_code_api' into integration 2025-06-16 06:06:19 -05:00
J. Nick Koston
6babe516ac move to proto.h to have less generated code 2025-06-16 06:05:19 -05:00
J. Nick Koston
e0b258ef7e Merge branch 'empty_methods' into integration 2025-06-15 22:30:02 -05:00
J. Nick Koston
ff0c3a89b1 Remove empty generated protobuf methods 2025-06-15 22:25:21 -05:00
J. Nick Koston
2511b81048 Merge branch 'reduce_duplicate_gen_code_api' into integration 2025-06-15 22:09:15 -05:00
J. Nick Koston
6ffcd94edc early return was worse for simple functions 2025-06-15 22:00:40 -05:00
J. Nick Koston
2fcf73c812 Reduce code duplication in auto-generated API protocol code 2025-06-15 21:53:33 -05:00
J. Nick Koston
dee0608af9 adjust 2025-06-15 20:47:53 -05:00
J. Nick Koston
d11860a383 Merge remote-tracking branch 'origin/loop_done' into integration 2025-06-15 20:44:24 -05:00
J. Nick Koston
1c05115bf5 Merge branch 'dev' into loop_done 2025-06-15 20:44:09 -05:00
J. Nick Koston
d7e7382d0b tests, address review comments 2025-06-15 20:43:30 -05:00
J. Nick Koston
872388f6e3 tests, address review comments 2025-06-15 20:43:01 -05:00
J. Nick Koston
1215ef920b Merge branch 'loop_done' into integration 2025-06-15 20:36:08 -05:00
J. Nick Koston
d19d5a23ea speed up test a bit 2025-06-15 20:36:00 -05:00
J. Nick Koston
f49a779f1d speed up test a bit 2025-06-15 20:35:52 -05:00
J. Nick Koston
d8bf5b80e1 Merge branch 'loop_done' into integration 2025-06-15 20:34:22 -05:00
J. Nick Koston
69483b9353 speed up test a bit 2025-06-15 20:34:13 -05:00
J. Nick Koston
14e8548989 speed up test a bit 2025-06-15 20:33:52 -05:00
J. Nick Koston
4abd93b661 tests, address review comments 2025-06-15 20:32:36 -05:00
J. Nick Koston
5d925af76f tests, address review comments 2025-06-15 20:31:25 -05:00
J. Nick Koston
b999c6064a tests, address review comments 2025-06-15 20:30:54 -05:00
J. Nick Koston
94e3576978 tests, address review comments 2025-06-15 20:30:43 -05:00
J. Nick Koston
7a22406a2d Merge remote-tracking branch 'origin/integration' into integration 2025-06-15 20:29:36 -05:00
J. Nick Koston
e60684494f Merge branch 'loop_done' into integration 2025-06-15 20:29:25 -05:00
J. Nick Koston
9db28ed779 cover 2025-06-15 20:29:12 -05:00
J. Nick Koston
6fd8c5cee7 tests, address review comments 2025-06-15 20:22:49 -05:00
J. Nick Koston
787ec43266 tests, address review comments 2025-06-15 20:22:29 -05:00
J. Nick Koston
a4efc63bf2 test 2025-06-15 19:57:20 -05:00
J. Nick Koston
80a8f1437e tests 2025-06-15 19:38:13 -05:00
J. Nick Koston
fcca94169d Merge remote-tracking branch 'origin/integration' into integration 2025-06-15 19:12:32 -05:00
J. Nick Koston
d1924088e3 Merge remote-tracking branch 'origin/loop_done' into integration 2025-06-15 19:11:57 -05:00
J. Nick Koston
fd31afe09c tidy 2025-06-15 18:58:42 -05:00
J. Nick Koston
7a763712c5 tidy 2025-06-15 18:58:32 -05:00
J. Nick Koston
7216be5da7 Merge branch 'loop_done' into integration 2025-06-15 18:44:36 -05:00
J. Nick Koston
711b0a291b comments 2025-06-15 18:44:28 -05:00
J. Nick Koston
dfc96496c8 comments 2025-06-15 18:44:15 -05:00
J. Nick Koston
2a1c5ef333 Merge branch 'loop_done' into integration 2025-06-15 18:42:49 -05:00
J. Nick Koston
9755209499 comments 2025-06-15 18:42:40 -05:00
J. Nick Koston
0b26e537d4 Merge branch 'loop_done' into integration 2025-06-15 18:40:46 -05:00
J. Nick Koston
98c6233ec3 Merge branch 'dev' into loop_done 2025-06-15 18:40:33 -05:00
J. Nick Koston
f711706b1a Fix ESP32 Improv component to re-enable loop when service starts again 2025-06-15 18:40:08 -05:00
J. Nick Koston
cee7789ab6 tweak 2025-06-15 18:37:05 -05:00
J. Nick Koston
8a06c4380d partition 2025-06-15 18:32:36 -05:00
J. Nick Koston
72ecf7a288 Merge remote-tracking branch 'upstream/dev' into integration 2025-06-15 16:48:20 -05:00
J. Nick Koston
ef98c7502d Merge remote-tracking branch 'origin/dev' into integration 2025-06-15 13:42:11 -05:00
J. Nick Koston
03d0e74b65 Merge remote-tracking branch 'upstream/less_templates' into integration 2025-06-15 10:48:53 -05:00
J. Nick Koston
5b8fdc0364 Merge branch 'dev' into less_templates 2025-06-15 10:42:41 -05:00
J. Nick Koston
593b4bd137 Update script/api_protobuf/api_protobuf.py 2025-06-15 10:42:28 -05:00
J. Nick Koston
267e12d058 lint 2025-06-15 10:09:54 -05:00
J. Nick Koston
4a5e39b651 Add common base classes for entity protobuf messages to reduce duplicate code 2025-06-15 09:40:45 -05:00
J. Nick Koston
ea24fa5b78 Merge branch 'loop_done' into integration 2025-06-15 01:52:27 -05:00
J. Nick Koston
bb2bb128f7 remove trailing . 2025-06-15 01:52:17 -05:00
J. Nick Koston
94e8a856d7 Merge branch 'loop_done' into integration 2025-06-15 01:47:22 -05:00
J. Nick Koston
4c19fbf98e lint 2025-06-15 01:47:10 -05:00
J. Nick Koston
60f8938bfa Merge branch 'loop_done' into integration 2025-06-15 01:34:12 -05:00
J. Nick Koston
55679662b5 ordering 2025-06-15 01:34:03 -05:00
J. Nick Koston
53df959e49 Merge branch 'loop_done' into integration 2025-06-15 01:26:56 -05:00
J. Nick Koston
8e6ef9966f Merge remote-tracking branch 'upstream/loop_done' into loop_done 2025-06-15 01:26:45 -05:00
J. Nick Koston
1d52fceafa rename, cleanup 2025-06-15 01:26:25 -05:00
J. Nick Koston
99186ed864 rename, cleanup 2025-06-15 01:25:59 -05:00
J. Nick Koston
383931d484 Merge branch 'ble_events_ring_buffer' into integration 2025-06-15 00:31:34 -05:00
J. Nick Koston
0b49a54cb3 comments 2025-06-15 00:31:25 -05:00
J. Nick Koston
705c0f1891 Merge branch 'ble_events_ring_buffer' into integration 2025-06-15 00:27:13 -05:00
J. Nick Koston
544c3ffc95 comments 2025-06-15 00:26:06 -05:00
J. Nick Koston
33f252a45d Implement a lock free ring buffer for BLEEvents to avoid drops 2025-06-15 00:22:24 -05:00
J. Nick Koston
f55d82a015 Merge branch 'ble_queue_lock_free' into integration 2025-06-15 00:16:02 -05:00
J. Nick Koston
8cf33fdef0 preen 2025-06-15 00:15:48 -05:00
J. Nick Koston
f858d98811 Merge branch 'ble_queue_lock_free' into integration 2025-06-15 00:12:47 -05:00
J. Nick Koston
2a6165d440 simplify 2025-06-15 00:12:34 -05:00
J. Nick Koston
4586528c40 merge 2025-06-15 00:01:15 -05:00
J. Nick Koston
23a07baa19 Merge branch 'ble_queue_lock_free' into integration 2025-06-14 23:58:53 -05:00
J. Nick Koston
f9040ca932 cleanup 2025-06-14 23:54:42 -05:00
J. Nick Koston
4cea7f0237 Update esphome/components/esp32_ble/ble.cpp 2025-06-14 23:49:38 -05:00
J. Nick Koston
b1847d5e98 Make ble events queue lock free 2025-06-14 23:48:26 -05:00
J. Nick Koston
9ce4d2e952 Merge remote-tracking branch 'upstream/dev' into integration 2025-06-14 23:21:51 -05:00
J. Nick Koston
247078e06d Merge remote-tracking branch 'origin/loop_done' into integration 2025-06-14 23:20:20 -05:00
J. Nick Koston
a0cd72de28 revert 2025-06-14 23:19:43 -05:00
J. Nick Koston
e467f569f0 Merge branch 'loop_done' into integration 2025-06-14 23:15:49 -05:00
J. Nick Koston
e31c7b7dfc one more 2025-06-14 23:15:06 -05:00
J. Nick Koston
dc2e0c832b Merge branch 'loop_done' into integration 2025-06-14 22:37:11 -05:00
J. Nick Koston
7ddf51bb51 fix 2025-06-14 22:36:29 -05:00
J. Nick Koston
8fb3856665 small fix 2025-06-14 22:17:27 -05:00
J. Nick Koston
183dd74f3e one more 2025-06-14 22:17:27 -05:00
J. Nick Koston
4f29039b41 mark_loop_done 2025-06-14 22:17:24 -05:00
J. Nick Koston
102fcbec20 small fix 2025-06-14 22:09:19 -05:00
J. Nick Koston
d00e5212c7 one more 2025-06-14 22:04:33 -05:00
J. Nick Koston
0e6bfb62cd mark_loop_done 2025-06-14 21:58:18 -05:00
J. Nick Koston
f576e8f635 remove cap 2025-06-14 21:40:16 -05:00
J. Nick Koston
e6dc10a440 address review comments 2025-06-14 21:34:21 -05:00
J. Nick Koston
aa930fb6b6 Merge branch 'ble_queue_lock_free' into integration 2025-06-14 20:09:56 -05:00
J. Nick Koston
f327ed87e9 Make ble events queue lock free 2025-06-14 20:08:43 -05:00
J. Nick Koston
2de9be0589 Merge branch 'loop_runtime_stats' into integration 2025-06-14 19:43:45 -05:00
J. Nick Koston
345cde8645 Merge branch 'ble_events_ring_buffer' into integration 2025-06-14 19:25:57 -05:00
J. Nick Koston
cf152af9ae Implement a lock free ring buffer for BLEEvents to avoid drops 2025-06-14 19:24:57 -05:00
J. Nick Koston
d6333dcfd9 Revert "Reorder Application to reduce padding"
This reverts commit 82c39580df.
2025-06-14 18:18:45 -05:00
J. Nick Koston
0121f799f0 Merge branch 'reorder_app_reduce_padding' into integration 2025-06-14 18:16:39 -05:00
J. Nick Koston
82c39580df Reorder Application to reduce padding 2025-06-14 18:15:40 -05:00
J. Nick Koston
53a578a46f Merge branch 'area_str' into integration 2025-06-14 18:01:44 -05:00
J. Nick Koston
62612ef80b Optimize Application area_ from std::string to const char* 2025-06-14 18:00:32 -05:00
J. Nick Koston
61ac874c4c Merge branch 'parse_on_off_uint8t' into integration 2025-06-14 17:46:25 -05:00
J. Nick Koston
976b200ff6 Make ParseOnOffState enum uint8_t 2025-06-14 17:44:22 -05:00
J. Nick Koston
852343b6d8 Merge remote-tracking branch 'upstream/has_state_' into integration 2025-06-14 17:32:40 -05:00
J. Nick Koston
c56af9d52b Merge branch 'component_state_oversized' into integration 2025-06-14 17:03:31 -05:00
J. Nick Koston
05f18e2828 Optimize Component and Application state storage from uint32_t to uint8_t 2025-06-14 17:01:57 -05:00
J. Nick Koston
72804caab2 Merge branch 'warn_if_blocking_over_' into integration 2025-06-14 16:43:26 -05:00
J. Nick Koston
80cbe5c7c9 Reduce Component blocking threshold memory usage by 2 bytes per component 2025-06-14 16:42:08 -05:00
J. Nick Koston
21892d1236 Merge branch 'error_message_memory' into integration 2025-06-14 16:29:43 -05:00
J. Nick Koston
13824624f8 Reduce Component memory usage by 20 bytes per component 2025-06-14 16:27:45 -05:00
J. Nick Koston
0fd72ecbab Merge remote-tracking branch 'upstream/batch_exceeds_max_packet_size' into integration 2025-06-14 15:46:22 -05:00
J. Nick Koston
f848cb1546 Merge remote-tracking branch 'upstream/footer_not_reserved' into integration 2025-06-14 15:46:13 -05:00
J. Nick Koston
633854081a Merge remote-tracking branch 'upstream/guard_wrong_total_size' into integration 2025-06-14 15:45:35 -05:00
J. Nick Koston
4fed9a581b Merge remote-tracking branch 'upstream/missing_force' into integration 2025-06-14 15:45:28 -05:00
J. Nick Koston
e9c1202aaa Merge remote-tracking branch 'upstream/ble_events' into integration 2025-06-14 15:45:18 -05:00
J. Nick Koston
0a7ae279d0 preen 2025-06-14 11:40:50 -05:00
J. Nick Koston
0de2696543 Optimize memory usage by lazy-allocating raw callbacks in sensors 2025-06-14 11:37:11 -05:00
J. Nick Koston
a7dc239b71 cleanup 2025-06-14 10:49:23 -05:00
J. Nick Koston
fe0e6990f5 cover 2025-06-14 10:14:41 -05:00
J. Nick Koston
5ba65e92d9 cover 2025-06-14 10:12:12 -05:00
J. Nick Koston
a1452b52c9 Reduce entity memory usage by eliminating field shadowing and bit-packing 2025-06-14 10:00:49 -05:00
J. Nick Koston
dd2aa23a5f cover 2025-06-13 19:28:59 -05:00
J. Nick Koston
0e0359ba7d Fix protobuf encoding size mismatch by passing force parameter in encode_string 2025-06-13 19:20:08 -05:00
J. Nick Koston
93b1b7aded assert 2025-06-13 18:32:21 -05:00
J. Nick Koston
9472dc6a53 Fix API message encoding to return actual size instead of calculated size 2025-06-13 18:24:51 -05:00
J. Nick Koston
67b681854e Fix API message encoding to return actual size instead of calculated size 2025-06-13 18:20:01 -05:00
J. Nick Koston
7b5990833e Merge branch 'dev' into batch_exceeds_max_packet_size 2025-06-13 17:01:10 -05:00
J. Nick Koston
b6d5d04589 More coverage 2025-06-13 16:59:36 -05:00
J. Nick Koston
fdfbb3e944 Fix footer space not being reserved for batched messages
This only affects noise protocol, and its not a correctness issue, its only
fixing an inefficent reserve
2025-06-13 15:54:31 -05:00
J. Nick Koston
faa7a3e37f tweak 2025-06-13 15:14:14 -05:00
J. Nick Koston
23748b82bb Ensure api can send batches where the first message exceeds MAX_PACKET_SIZE 2025-06-13 14:58:09 -05:00
J. Nick Koston
bccb6f578a Ensure we can send batches where the first message exceeds MAX_PACKET_SIZE 2025-06-13 14:55:17 -05:00
J. Nick Koston
de8a5d6e9e Merge branch 'dev' into ble_events 2025-06-13 10:46:45 -05:00
J. Nick Koston
a8eb3f7961 lint 2025-06-13 10:46:09 -05:00
J. Nick Koston
2dc85f5a42 Merge remote-tracking branch 'upstream/esp32_touch_isr' into esp32_touch_isr 2025-06-13 10:11:49 -05:00
J. Nick Koston
82518b351d lint 2025-06-13 10:11:38 -05:00
J. Nick Koston
68f34a1683 Merge branch 'dev' into esp32_touch_isr 2025-06-12 20:19:29 -05:00
J. Nick Koston
bc6b72a422 tweaks 2025-06-12 20:16:12 -05:00
J. Nick Koston
599e28e1cb fixes 2025-06-12 20:02:39 -05:00
J. Nick Koston
ee6b2ba6c6 fixes 2025-06-12 19:56:12 -05:00
J. Nick Koston
0877b3e2af suppress unused events 2025-06-12 19:18:22 -05:00
J. Nick Koston
d1edb1e32a fix 2025-06-12 18:34:00 -05:00
J. Nick Koston
d1e6b8dd10 comment 2025-06-12 18:33:27 -05:00
J. Nick Koston
b32fc3bfdd lint 2025-06-12 18:30:53 -05:00
J. Nick Koston
1e24417db0 help with setup 2025-06-12 18:09:39 -05:00
J. Nick Koston
fb9387ecc5 help with setup 2025-06-12 17:55:21 -05:00
J. Nick Koston
6c5f4cdb70 help with setup 2025-06-12 17:49:01 -05:00
J. Nick Koston
aabacb7454 help with setup 2025-06-12 17:47:25 -05:00
J. Nick Koston
b5da84479e help with setup 2025-06-12 17:43:08 -05:00
J. Nick Koston
88d9361050 help with setup 2025-06-12 17:34:24 -05:00
J. Nick Koston
1d90388ffc help with setup 2025-06-12 17:27:09 -05:00
J. Nick Koston
b3c43ce31f help with setup 2025-06-12 17:23:10 -05:00
J. Nick Koston
6d9d22d422 help with setup 2025-06-12 17:17:16 -05:00
J. Nick Koston
86be1f56d0 preen 2025-06-12 17:14:00 -05:00
J. Nick Koston
a0c81ffd7a preen 2025-06-12 17:08:47 -05:00
J. Nick Koston
ec1dc42e58 Revert "preen"
This reverts commit 866eaed73d.
2025-06-12 17:05:06 -05:00
J. Nick Koston
866eaed73d preen 2025-06-12 16:58:24 -05:00
J. Nick Koston
a18374e1ad cleanup 2025-06-12 16:33:15 -05:00
J. Nick Koston
f7afcb3b24 cleanup 2025-06-12 16:30:41 -05:00
J. Nick Koston
3adcae783c cleanup 2025-06-12 16:19:27 -05:00
J. Nick Koston
73b40dd2e7 cleanup 2025-06-12 16:19:15 -05:00
J. Nick Koston
1e12614f9a cleanup 2025-06-12 16:14:37 -05:00
J. Nick Koston
aeaa7c699a Merge branch 'dev' into esp32_touch_isr 2025-06-12 15:57:26 -05:00
J. Nick Koston
f1c56b7254 cleanup 2025-06-12 15:56:32 -05:00
J. Nick Koston
e72e0d0646 cleanup 2025-06-12 15:56:19 -05:00
J. Nick Koston
5719d334aa cleanup 2025-06-12 15:56:04 -05:00
J. Nick Koston
bcb6b85333 cleanup 2025-06-12 15:54:15 -05:00
J. Nick Koston
5d765413ef cleanup 2025-06-12 15:53:42 -05:00
J. Nick Koston
efb2e5e7a8 cleanup 2025-06-12 15:52:38 -05:00
J. Nick Koston
5d5e346199 cleanup 2025-06-12 15:50:21 -05:00
J. Nick Koston
08a74890da cleanup 2025-06-12 15:48:29 -05:00
J. Nick Koston
0545b9c7f2 cleanup 2025-06-12 15:48:00 -05:00
J. Nick Koston
bbf7d32676 cleanup 2025-06-12 15:47:31 -05:00
J. Nick Koston
e83f4ae974 cleanup 2025-06-12 15:46:56 -05:00
J. Nick Koston
9b0d01e03f cleanup 2025-06-12 15:45:47 -05:00
J. Nick Koston
eae0d90a1e adjust 2025-06-12 15:41:41 -05:00
J. Nick Koston
90c09a7650 split 2025-06-12 13:29:12 -05:00
J. Nick Koston
aecf080211 touch ups 2025-06-12 13:16:48 -05:00
J. Nick Koston
8517420356 touch ups 2025-06-12 13:14:29 -05:00
J. Nick Koston
376be1f009 touch ups 2025-06-12 13:12:40 -05:00
J. Nick Koston
0021e76649 working 2025-06-12 13:07:25 -05:00
J. Nick Koston
d440c4bc43 derbug 2025-06-12 13:00:55 -05:00
J. Nick Koston
50840b2105 derbug 2025-06-12 13:00:39 -05:00
J. Nick Koston
7502c6b6c0 debug 2025-06-12 12:44:28 -05:00
J. Nick Koston
919c32f0cc tweak 2025-06-12 12:20:47 -05:00
J. Nick Koston
a28c951272 more debug 2025-06-12 12:13:46 -05:00
J. Nick Koston
13d7c5a9a9 more debug 2025-06-12 12:12:55 -05:00
J. Nick Koston
5f1383344d tweak 2025-06-12 12:10:50 -05:00
J. Nick Koston
48f43d3eb1 tweak 2025-06-12 11:58:21 -05:00
J. Nick Koston
4ac2141307 adjust 2025-06-12 11:52:29 -05:00
J. Nick Koston
719d8cac97 split it 2025-06-12 11:45:50 -05:00
J. Nick Koston
99cbe53a8e split it 2025-06-12 11:43:47 -05:00
J. Nick Koston
a36af1bfac s3 fixes 2025-06-12 10:59:40 -05:00
J. Nick Koston
8b6aa319bf s3 fixes 2025-06-12 10:57:46 -05:00
J. Nick Koston
a16d321e1a downgrade logging 2025-06-12 10:38:47 -05:00
J. Nick Koston
74e70278e2 fixes 2025-06-12 10:34:59 -05:00
J. Nick Koston
1332e24a2c fixes 2025-06-12 10:31:13 -05:00
J. Nick Koston
5ab78ec461 fixes 2025-06-12 10:30:58 -05:00
J. Nick Koston
ce701d3c31 fixes 2025-06-12 10:29:11 -05:00
J. Nick Koston
5fca1be44d fixes 2025-06-12 10:27:22 -05:00
J. Nick Koston
0bd4c333bd cleanup 2025-06-12 10:21:41 -05:00
J. Nick Koston
c6ed880732 fixes 2025-06-12 10:19:25 -05:00
J. Nick Koston
da0f3c6cce fixes 2025-06-12 10:12:56 -05:00
J. Nick Koston
e5d12d346a fixes 2025-06-12 10:08:29 -05:00
J. Nick Koston
478e2e726b fixes 2025-06-12 10:01:35 -05:00
J. Nick Koston
dbdac3707b fixes 2025-06-12 10:00:49 -05:00
J. Nick Koston
bd89a88e34 fixes 2025-06-12 09:23:38 -05:00
J. Nick Koston
d322d83745 fixes 2025-06-12 09:21:03 -05:00
J. Nick Koston
463a581ab9 DEBUG! 2025-06-12 00:56:42 -05:00
J. Nick Koston
eae4bd222a track pads 2025-06-11 23:29:00 -05:00
J. Nick Koston
a7bb7fc14d fix 2025-06-11 22:55:15 -05:00
J. Nick Koston
c047aa47eb use ll for all 2025-06-11 22:46:40 -05:00
J. Nick Koston
61bca56316 try touch_ll_read_raw_data 2025-06-11 22:43:41 -05:00
J. Nick Koston
9a37323eb8 Use interrupt based approach for esp32_touch 2025-06-11 22:32:25 -05:00
J. Nick Koston
99a54369bf Merge remote-tracking branch 'upstream/dev' into loop_runtime_stats 2025-06-11 22:01:22 -05:00
J. Nick Koston
f7533dfc5c review 2025-06-11 16:25:31 -05:00
J. Nick Koston
ee7d95272d lets be sure 2025-06-11 13:32:55 -05:00
J. Nick Koston
2b9b1d12e6 lets be sure 2025-06-11 13:32:47 -05:00
J. Nick Koston
2cbb5c7d8e fix error 2025-06-11 13:16:44 -05:00
J. Nick Koston
9686c7babe Merge branch 'dev' into ble_events 2025-06-11 13:09:32 -05:00
J. Nick Koston
66bd4c96c4 safety 2025-06-11 13:05:30 -05:00
J. Nick Koston
dc47faa4b6 safety 2025-06-11 13:05:01 -05:00
J. Nick Koston
55ee0b116d lint 2025-06-11 13:03:50 -05:00
J. Nick Koston
c6957c08bc lint 2025-06-11 13:02:08 -05:00
J. Nick Koston
8fe6a323d8 remove workaround 2025-06-11 13:00:55 -05:00
J. Nick Koston
8e51590c32 remove workaround 2025-06-11 12:59:57 -05:00
J. Nick Koston
ae066d5627 cleanup 2025-06-11 11:55:28 -05:00
J. Nick Koston
6760279916 cleanup compacted code 2025-06-11 11:51:43 -05:00
J. Nick Koston
3c208050b0 comments 2025-06-11 11:47:34 -05:00
J. Nick Koston
bbc7c9fb37 dry 2025-06-11 11:46:17 -05:00
J. Nick Koston
e1c3862586 preen 2025-06-11 11:36:50 -05:00
J. Nick Koston
c24b7cb7bd v->d 2025-06-11 11:34:30 -05:00
J. Nick Koston
c91e16549d lint 2025-06-11 11:27:13 -05:00
J. Nick Koston
6e70aca458 wip 2025-06-11 11:23:13 -05:00
J. Nick Koston
d9ffd0ac8e wip 2025-06-11 11:22:01 -05:00
J. Nick Koston
4641f73d19 comments 2025-06-11 11:19:36 -05:00
J. Nick Koston
9f0051c21f cleanup 2025-06-11 11:17:10 -05:00
J. Nick Koston
0331cb09e8 reduce 2025-06-11 11:17:01 -05:00
J. Nick Koston
2f8946f86c cleanup 2025-06-11 11:14:10 -05:00
J. Nick Koston
88a3df4008 cleanup 2025-06-11 11:13:34 -05:00
J. Nick Koston
0adf514bd6 preen 2025-06-11 11:09:19 -05:00
J. Nick Koston
a1b5a2abcb tweak 2025-06-11 10:58:56 -05:00
J. Nick Koston
068c62c6fe adjust 2025-06-11 10:43:48 -05:00
J. Nick Koston
0e9f14f969 wip 2025-06-11 10:20:18 -05:00
J. Nick Koston
78315fd388 preen 2025-06-11 10:08:30 -05:00
J. Nick Koston
0ab69002df preen 2025-06-11 10:05:15 -05:00
J. Nick Koston
1eec1239ec wip 2025-06-11 09:56:02 -05: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
J. Nick Koston
98a2f23024 Merge remote-tracking branch 'upstream/dev' into loop_runtime_stats 2025-05-29 11:04:14 -05:00
J. Nick Koston
c955897d1b Merge remote-tracking branch 'upstream/dev' into loop_runtime_stats 2025-05-27 11:39:45 -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
J. Nick Koston
cfdb0925ce Merge branch 'dev' into loop_runtime_stats 2025-05-13 23:42:19 -05:00
J. Nick Koston
83db3eddd9 revert ota 2025-05-13 01:07:43 -05:00
J. Nick Koston
cc2c5a544e revert ota 2025-05-13 01:07:38 -05:00
J. Nick Koston
8fba8c2800 revert ota 2025-05-13 01:05:37 -05:00
J. Nick Koston
51d1da8460 revert ota 2025-05-13 01:04:09 -05:00
J. Nick Koston
2f1257056d revert 2025-05-13 01:02:00 -05:00
J. Nick Koston
2f8f6967bf fix ota 2025-05-13 00:55:19 -05:00
J. Nick Koston
246527e618 runtime stats 2025-05-13 00:54:05 -05:00
J. Nick Koston
3857cc9c83 runtime stats 2025-05-13 00:51:14 -05: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
124 changed files with 5732 additions and 1534 deletions

View File

@@ -329,6 +329,7 @@ esphome/components/opentherm/* @olegtarasov
esphome/components/openthread/* @mrene
esphome/components/opt3001/* @ccutrer
esphome/components/ota/* @esphome/core
esphome/components/ota_base/* @esphome/core
esphome/components/output/* @esphome/core
esphome/components/packet_transport/* @clydebarrow
esphome/components/pca6416a/* @Mat931

View File

@@ -10,8 +10,15 @@ from esphome.components.esp32.const import (
VARIANT_ESP32S2,
VARIANT_ESP32S3,
)
from esphome.config_helpers import filter_source_files_from_platform
import esphome.config_validation as cv
from esphome.const import CONF_ANALOG, CONF_INPUT, CONF_NUMBER, PLATFORM_ESP8266
from esphome.const import (
CONF_ANALOG,
CONF_INPUT,
CONF_NUMBER,
PLATFORM_ESP8266,
PlatformFramework,
)
from esphome.core import CORE
CODEOWNERS = ["@esphome/core"]
@@ -229,3 +236,20 @@ def validate_adc_pin(value):
)(value)
raise NotImplementedError
FILTER_SOURCE_FILES = filter_source_files_from_platform(
{
"adc_sensor_esp32.cpp": {
PlatformFramework.ESP32_ARDUINO,
PlatformFramework.ESP32_IDF,
},
"adc_sensor_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
"adc_sensor_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO},
"adc_sensor_libretiny.cpp": {
PlatformFramework.BK72XX_ARDUINO,
PlatformFramework.RTL87XX_ARDUINO,
PlatformFramework.LN882X_ARDUINO,
},
}
)

View File

@@ -23,7 +23,7 @@ from esphome.const import (
CONF_TRIGGER_ID,
CONF_VARIABLES,
)
from esphome.core import coroutine_with_priority
from esphome.core import CORE, coroutine_with_priority
DEPENDENCIES = ["network"]
AUTO_LOAD = ["socket"]
@@ -313,3 +313,14 @@ async def homeassistant_tag_scanned_to_code(config, action_id, template_arg, arg
@automation.register_condition("api.connected", APIConnectedCondition, {})
async def api_connected_to_code(config, condition_id, template_arg, args):
return cg.new_Pvariable(condition_id, template_arg)
def FILTER_SOURCE_FILES() -> list[str]:
"""Filter out api_pb2_dump.cpp when proto message dumping is not enabled."""
# api_pb2_dump.cpp is only needed when HAS_PROTO_MESSAGE_DUMP is defined
# This is a particularly large file that still needs to be opened and read
# all the way to the end even when ifdef'd out
if "HAS_PROTO_MESSAGE_DUMP" not in CORE.defines:
return ["api_pb2_dump.cpp"]
return []

View File

@@ -42,6 +42,19 @@ static const char *const TAG = "api.connection";
static const int CAMERA_STOP_STREAM = 5000;
#endif
// Helper macro for entity command handlers - gets entity by key, returns if not found, and creates call object
#define ENTITY_COMMAND_MAKE_CALL(entity_type, entity_var, getter_name) \
entity_type *entity_var = App.get_##getter_name##_by_key(msg.key); \
if (entity_var == nullptr) \
return; \
auto call = entity_var->make_call();
// Helper macro for entity command handlers that don't use make_call() - gets entity by key and returns if not found
#define ENTITY_COMMAND_GET(entity_type, entity_var, getter_name) \
entity_type *entity_var = App.get_##getter_name##_by_key(msg.key); \
if (entity_var == nullptr) \
return;
APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent)
: parent_(parent), initial_state_iterator_(this), list_entities_iterator_(this) {
#if defined(USE_API_PLAINTEXT) && defined(USE_API_NOISE)
@@ -361,11 +374,7 @@ uint16_t APIConnection::try_send_cover_info(EntityBase *entity, APIConnection *c
return encode_message_to_buffer(msg, ListEntitiesCoverResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
void APIConnection::cover_command(const CoverCommandRequest &msg) {
cover::Cover *cover = App.get_cover_by_key(msg.key);
if (cover == nullptr)
return;
auto call = cover->make_call();
ENTITY_COMMAND_MAKE_CALL(cover::Cover, cover, cover)
if (msg.has_legacy_command) {
switch (msg.legacy_command) {
case enums::LEGACY_COVER_COMMAND_OPEN:
@@ -427,11 +436,7 @@ uint16_t APIConnection::try_send_fan_info(EntityBase *entity, APIConnection *con
return encode_message_to_buffer(msg, ListEntitiesFanResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
void APIConnection::fan_command(const FanCommandRequest &msg) {
fan::Fan *fan = App.get_fan_by_key(msg.key);
if (fan == nullptr)
return;
auto call = fan->make_call();
ENTITY_COMMAND_MAKE_CALL(fan::Fan, fan, fan)
if (msg.has_state)
call.set_state(msg.state);
if (msg.has_oscillating)
@@ -504,11 +509,7 @@ uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *c
return encode_message_to_buffer(msg, ListEntitiesLightResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
void APIConnection::light_command(const LightCommandRequest &msg) {
light::LightState *light = App.get_light_by_key(msg.key);
if (light == nullptr)
return;
auto call = light->make_call();
ENTITY_COMMAND_MAKE_CALL(light::LightState, light, light)
if (msg.has_state)
call.set_state(msg.state);
if (msg.has_brightness)
@@ -597,9 +598,7 @@ uint16_t APIConnection::try_send_switch_info(EntityBase *entity, APIConnection *
return encode_message_to_buffer(msg, ListEntitiesSwitchResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
void APIConnection::switch_command(const SwitchCommandRequest &msg) {
switch_::Switch *a_switch = App.get_switch_by_key(msg.key);
if (a_switch == nullptr)
return;
ENTITY_COMMAND_GET(switch_::Switch, a_switch, switch)
if (msg.state) {
a_switch->turn_on();
@@ -708,11 +707,7 @@ uint16_t APIConnection::try_send_climate_info(EntityBase *entity, APIConnection
return encode_message_to_buffer(msg, ListEntitiesClimateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
void APIConnection::climate_command(const ClimateCommandRequest &msg) {
climate::Climate *climate = App.get_climate_by_key(msg.key);
if (climate == nullptr)
return;
auto call = climate->make_call();
ENTITY_COMMAND_MAKE_CALL(climate::Climate, climate, climate)
if (msg.has_mode)
call.set_mode(static_cast<climate::ClimateMode>(msg.mode));
if (msg.has_target_temperature)
@@ -767,11 +762,7 @@ uint16_t APIConnection::try_send_number_info(EntityBase *entity, APIConnection *
return encode_message_to_buffer(msg, ListEntitiesNumberResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
void APIConnection::number_command(const NumberCommandRequest &msg) {
number::Number *number = App.get_number_by_key(msg.key);
if (number == nullptr)
return;
auto call = number->make_call();
ENTITY_COMMAND_MAKE_CALL(number::Number, number, number)
call.set_value(msg.state);
call.perform();
}
@@ -801,11 +792,7 @@ uint16_t APIConnection::try_send_date_info(EntityBase *entity, APIConnection *co
return encode_message_to_buffer(msg, ListEntitiesDateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
void APIConnection::date_command(const DateCommandRequest &msg) {
datetime::DateEntity *date = App.get_date_by_key(msg.key);
if (date == nullptr)
return;
auto call = date->make_call();
ENTITY_COMMAND_MAKE_CALL(datetime::DateEntity, date, date)
call.set_date(msg.year, msg.month, msg.day);
call.perform();
}
@@ -835,11 +822,7 @@ uint16_t APIConnection::try_send_time_info(EntityBase *entity, APIConnection *co
return encode_message_to_buffer(msg, ListEntitiesTimeResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
void APIConnection::time_command(const TimeCommandRequest &msg) {
datetime::TimeEntity *time = App.get_time_by_key(msg.key);
if (time == nullptr)
return;
auto call = time->make_call();
ENTITY_COMMAND_MAKE_CALL(datetime::TimeEntity, time, time)
call.set_time(msg.hour, msg.minute, msg.second);
call.perform();
}
@@ -871,11 +854,7 @@ uint16_t APIConnection::try_send_datetime_info(EntityBase *entity, APIConnection
return encode_message_to_buffer(msg, ListEntitiesDateTimeResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
void APIConnection::datetime_command(const DateTimeCommandRequest &msg) {
datetime::DateTimeEntity *datetime = App.get_datetime_by_key(msg.key);
if (datetime == nullptr)
return;
auto call = datetime->make_call();
ENTITY_COMMAND_MAKE_CALL(datetime::DateTimeEntity, datetime, datetime)
call.set_datetime(msg.epoch_seconds);
call.perform();
}
@@ -909,11 +888,7 @@ uint16_t APIConnection::try_send_text_info(EntityBase *entity, APIConnection *co
return encode_message_to_buffer(msg, ListEntitiesTextResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
void APIConnection::text_command(const TextCommandRequest &msg) {
text::Text *text = App.get_text_by_key(msg.key);
if (text == nullptr)
return;
auto call = text->make_call();
ENTITY_COMMAND_MAKE_CALL(text::Text, text, text)
call.set_value(msg.state);
call.perform();
}
@@ -945,11 +920,7 @@ uint16_t APIConnection::try_send_select_info(EntityBase *entity, APIConnection *
return encode_message_to_buffer(msg, ListEntitiesSelectResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
void APIConnection::select_command(const SelectCommandRequest &msg) {
select::Select *select = App.get_select_by_key(msg.key);
if (select == nullptr)
return;
auto call = select->make_call();
ENTITY_COMMAND_MAKE_CALL(select::Select, select, select)
call.set_option(msg.state);
call.perform();
}
@@ -966,10 +937,7 @@ uint16_t APIConnection::try_send_button_info(EntityBase *entity, APIConnection *
return encode_message_to_buffer(msg, ListEntitiesButtonResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
void esphome::api::APIConnection::button_command(const ButtonCommandRequest &msg) {
button::Button *button = App.get_button_by_key(msg.key);
if (button == nullptr)
return;
ENTITY_COMMAND_GET(button::Button, button, button)
button->press();
}
#endif
@@ -1000,9 +968,7 @@ uint16_t APIConnection::try_send_lock_info(EntityBase *entity, APIConnection *co
return encode_message_to_buffer(msg, ListEntitiesLockResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
void APIConnection::lock_command(const LockCommandRequest &msg) {
lock::Lock *a_lock = App.get_lock_by_key(msg.key);
if (a_lock == nullptr)
return;
ENTITY_COMMAND_GET(lock::Lock, a_lock, lock)
switch (msg.command) {
case enums::LOCK_UNLOCK:
@@ -1045,11 +1011,7 @@ uint16_t APIConnection::try_send_valve_info(EntityBase *entity, APIConnection *c
return encode_message_to_buffer(msg, ListEntitiesValveResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
void APIConnection::valve_command(const ValveCommandRequest &msg) {
valve::Valve *valve = App.get_valve_by_key(msg.key);
if (valve == nullptr)
return;
auto call = valve->make_call();
ENTITY_COMMAND_MAKE_CALL(valve::Valve, valve, valve)
if (msg.has_position)
call.set_position(msg.position);
if (msg.stop)
@@ -1096,11 +1058,7 @@ uint16_t APIConnection::try_send_media_player_info(EntityBase *entity, APIConnec
return encode_message_to_buffer(msg, ListEntitiesMediaPlayerResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
void APIConnection::media_player_command(const MediaPlayerCommandRequest &msg) {
media_player::MediaPlayer *media_player = App.get_media_player_by_key(msg.key);
if (media_player == nullptr)
return;
auto call = media_player->make_call();
ENTITY_COMMAND_MAKE_CALL(media_player::MediaPlayer, media_player, media_player)
if (msg.has_command) {
call.set_command(static_cast<media_player::MediaPlayerCommand>(msg.command));
}
@@ -1346,11 +1304,7 @@ uint16_t APIConnection::try_send_alarm_control_panel_info(EntityBase *entity, AP
is_single);
}
void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) {
alarm_control_panel::AlarmControlPanel *a_alarm_control_panel = App.get_alarm_control_panel_by_key(msg.key);
if (a_alarm_control_panel == nullptr)
return;
auto call = a_alarm_control_panel->make_call();
ENTITY_COMMAND_MAKE_CALL(alarm_control_panel::AlarmControlPanel, a_alarm_control_panel, alarm_control_panel)
switch (msg.command) {
case enums::ALARM_CONTROL_PANEL_DISARM:
call.disarm();
@@ -1438,9 +1392,7 @@ uint16_t APIConnection::try_send_update_info(EntityBase *entity, APIConnection *
return encode_message_to_buffer(msg, ListEntitiesUpdateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
void APIConnection::update_command(const UpdateCommandRequest &msg) {
update::UpdateEntity *update = App.get_update_by_key(msg.key);
if (update == nullptr)
return;
ENTITY_COMMAND_GET(update::UpdateEntity, update, update)
switch (msg.command) {
case enums::UPDATE_COMMAND_UPDATE:
@@ -1459,12 +1411,11 @@ void APIConnection::update_command(const UpdateCommandRequest &msg) {
}
#endif
bool APIConnection::try_send_log_message(int level, const char *tag, const char *line) {
bool APIConnection::try_send_log_message(int level, const char *tag, const char *line, size_t message_len) {
if (this->flags_.log_subscription < level)
return false;
// Pre-calculate message size to avoid reallocations
const size_t line_length = strlen(line);
uint32_t msg_size = 0;
// Add size for level field (field ID 1, varint type)
@@ -1473,14 +1424,14 @@ bool APIConnection::try_send_log_message(int level, const char *tag, const char
// Add size for string field (field ID 3, string type)
// 1 byte for field tag + size of length varint + string length
msg_size += 1 + api::ProtoSize::varint(static_cast<uint32_t>(line_length)) + line_length;
msg_size += 1 + api::ProtoSize::varint(static_cast<uint32_t>(message_len)) + message_len;
// Create a pre-sized buffer
auto buffer = this->create_buffer(msg_size);
// Encode the message (SubscribeLogsResponse)
buffer.encode_uint32(1, static_cast<uint32_t>(level)); // LogLevel level = 1
buffer.encode_string(3, line, line_length); // string message = 3
buffer.encode_string(3, line, message_len); // string message = 3
// SubscribeLogsResponse - 29
return this->send_buffer(buffer, SubscribeLogsResponse::MESSAGE_TYPE);

View File

@@ -107,7 +107,7 @@ class APIConnection : public APIServerConnection {
bool send_media_player_state(media_player::MediaPlayer *media_player);
void media_player_command(const MediaPlayerCommandRequest &msg) override;
#endif
bool try_send_log_message(int level, const char *tag, const char *line);
bool try_send_log_message(int level, const char *tag, const char *line, size_t message_len);
void send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
if (!this->flags_.service_call_subscription)
return;

View File

@@ -225,6 +225,22 @@ APIError APIFrameHelper::init_common_() {
}
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, this->info_.c_str(), ##__VA_ARGS__)
APIError APIFrameHelper::handle_socket_read_result_(ssize_t received) {
if (received == -1) {
if (errno == EWOULDBLOCK || errno == EAGAIN) {
return APIError::WOULD_BLOCK;
}
state_ = State::FAILED;
HELPER_LOG("Socket read failed with errno %d", errno);
return APIError::SOCKET_READ_FAILED;
} else if (received == 0) {
state_ = State::FAILED;
HELPER_LOG("Connection closed");
return APIError::CONNECTION_CLOSED;
}
return APIError::OK;
}
// uncomment to log raw packets
//#define HELPER_LOG_PACKETS
@@ -327,17 +343,9 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
// no header information yet
uint8_t to_read = 3 - rx_header_buf_len_;
ssize_t received = this->socket_->read(&rx_header_buf_[rx_header_buf_len_], to_read);
if (received == -1) {
if (errno == EWOULDBLOCK || errno == EAGAIN) {
return APIError::WOULD_BLOCK;
}
state_ = State::FAILED;
HELPER_LOG("Socket read failed with errno %d", errno);
return APIError::SOCKET_READ_FAILED;
} else if (received == 0) {
state_ = State::FAILED;
HELPER_LOG("Connection closed");
return APIError::CONNECTION_CLOSED;
APIError err = handle_socket_read_result_(received);
if (err != APIError::OK) {
return err;
}
rx_header_buf_len_ += static_cast<uint8_t>(received);
if (static_cast<uint8_t>(received) != to_read) {
@@ -372,17 +380,9 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
// more data to read
uint16_t to_read = msg_size - rx_buf_len_;
ssize_t received = this->socket_->read(&rx_buf_[rx_buf_len_], to_read);
if (received == -1) {
if (errno == EWOULDBLOCK || errno == EAGAIN) {
return APIError::WOULD_BLOCK;
}
state_ = State::FAILED;
HELPER_LOG("Socket read failed with errno %d", errno);
return APIError::SOCKET_READ_FAILED;
} else if (received == 0) {
state_ = State::FAILED;
HELPER_LOG("Connection closed");
return APIError::CONNECTION_CLOSED;
APIError err = handle_socket_read_result_(received);
if (err != APIError::OK) {
return err;
}
rx_buf_len_ += static_cast<uint16_t>(received);
if (static_cast<uint16_t>(received) != to_read) {
@@ -855,17 +855,9 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
// Try to get to at least 3 bytes total (indicator + 2 varint bytes), then read one byte at a time
ssize_t received =
this->socket_->read(&rx_header_buf_[rx_header_buf_pos_], rx_header_buf_pos_ < 3 ? 3 - rx_header_buf_pos_ : 1);
if (received == -1) {
if (errno == EWOULDBLOCK || errno == EAGAIN) {
return APIError::WOULD_BLOCK;
}
state_ = State::FAILED;
HELPER_LOG("Socket read failed with errno %d", errno);
return APIError::SOCKET_READ_FAILED;
} else if (received == 0) {
state_ = State::FAILED;
HELPER_LOG("Connection closed");
return APIError::CONNECTION_CLOSED;
APIError err = handle_socket_read_result_(received);
if (err != APIError::OK) {
return err;
}
// If this was the first read, validate the indicator byte
@@ -949,17 +941,9 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
// more data to read
uint16_t to_read = rx_header_parsed_len_ - rx_buf_len_;
ssize_t received = this->socket_->read(&rx_buf_[rx_buf_len_], to_read);
if (received == -1) {
if (errno == EWOULDBLOCK || errno == EAGAIN) {
return APIError::WOULD_BLOCK;
}
state_ = State::FAILED;
HELPER_LOG("Socket read failed with errno %d", errno);
return APIError::SOCKET_READ_FAILED;
} else if (received == 0) {
state_ = State::FAILED;
HELPER_LOG("Connection closed");
return APIError::CONNECTION_CLOSED;
APIError err = handle_socket_read_result_(received);
if (err != APIError::OK) {
return err;
}
rx_buf_len_ += static_cast<uint16_t>(received);
if (static_cast<uint16_t>(received) != to_read) {

View File

@@ -176,6 +176,9 @@ class APIFrameHelper {
// Common initialization for both plaintext and noise protocols
APIError init_common_();
// Helper method to handle socket read results
APIError handle_socket_read_result_(ssize_t received);
};
#ifdef USE_API_NOISE

File diff suppressed because it is too large Load Diff

View File

@@ -104,18 +104,19 @@ void APIServer::setup() {
#ifdef USE_LOGGER
if (logger::global_logger != nullptr) {
logger::global_logger->add_on_log_callback([this](int level, const char *tag, const char *message) {
if (this->shutting_down_) {
// Don't try to send logs during shutdown
// as it could result in a recursion and
// we would be filling a buffer we are trying to clear
return;
}
for (auto &c : this->clients_) {
if (!c->flags_.remove)
c->try_send_log_message(level, tag, message);
}
});
logger::global_logger->add_on_log_callback(
[this](int level, const char *tag, const char *message, size_t message_len) {
if (this->shutting_down_) {
// Don't try to send logs during shutdown
// as it could result in a recursion and
// we would be filling a buffer we are trying to clear
return;
}
for (auto &c : this->clients_) {
if (!c->flags_.remove)
c->try_send_log_message(level, tag, message, message_len);
}
});
}
#endif
@@ -260,180 +261,108 @@ bool APIServer::check_password(const std::string &password) const {
void APIServer::handle_disconnect(APIConnection *conn) {}
// Macro for entities without extra parameters
#define API_DISPATCH_UPDATE(entity_type, entity_name) \
void APIServer::on_##entity_name##_update(entity_type *obj) { \
if (obj->is_internal()) \
return; \
for (auto &c : this->clients_) \
c->send_##entity_name##_state(obj); \
}
// Macro for entities with extra parameters (but parameters not used in send)
#define API_DISPATCH_UPDATE_IGNORE_PARAMS(entity_type, entity_name, ...) \
void APIServer::on_##entity_name##_update(entity_type *obj, __VA_ARGS__) { \
if (obj->is_internal()) \
return; \
for (auto &c : this->clients_) \
c->send_##entity_name##_state(obj); \
}
#ifdef USE_BINARY_SENSOR
void APIServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_binary_sensor_state(obj);
}
API_DISPATCH_UPDATE(binary_sensor::BinarySensor, binary_sensor)
#endif
#ifdef USE_COVER
void APIServer::on_cover_update(cover::Cover *obj) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_cover_state(obj);
}
API_DISPATCH_UPDATE(cover::Cover, cover)
#endif
#ifdef USE_FAN
void APIServer::on_fan_update(fan::Fan *obj) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_fan_state(obj);
}
API_DISPATCH_UPDATE(fan::Fan, fan)
#endif
#ifdef USE_LIGHT
void APIServer::on_light_update(light::LightState *obj) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_light_state(obj);
}
API_DISPATCH_UPDATE(light::LightState, light)
#endif
#ifdef USE_SENSOR
void APIServer::on_sensor_update(sensor::Sensor *obj, float state) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_sensor_state(obj);
}
API_DISPATCH_UPDATE_IGNORE_PARAMS(sensor::Sensor, sensor, float state)
#endif
#ifdef USE_SWITCH
void APIServer::on_switch_update(switch_::Switch *obj, bool state) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_switch_state(obj);
}
API_DISPATCH_UPDATE_IGNORE_PARAMS(switch_::Switch, switch, bool state)
#endif
#ifdef USE_TEXT_SENSOR
void APIServer::on_text_sensor_update(text_sensor::TextSensor *obj, const std::string &state) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_text_sensor_state(obj);
}
API_DISPATCH_UPDATE_IGNORE_PARAMS(text_sensor::TextSensor, text_sensor, const std::string &state)
#endif
#ifdef USE_CLIMATE
void APIServer::on_climate_update(climate::Climate *obj) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_climate_state(obj);
}
API_DISPATCH_UPDATE(climate::Climate, climate)
#endif
#ifdef USE_NUMBER
void APIServer::on_number_update(number::Number *obj, float state) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_number_state(obj);
}
API_DISPATCH_UPDATE_IGNORE_PARAMS(number::Number, number, float state)
#endif
#ifdef USE_DATETIME_DATE
void APIServer::on_date_update(datetime::DateEntity *obj) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_date_state(obj);
}
API_DISPATCH_UPDATE(datetime::DateEntity, date)
#endif
#ifdef USE_DATETIME_TIME
void APIServer::on_time_update(datetime::TimeEntity *obj) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_time_state(obj);
}
API_DISPATCH_UPDATE(datetime::TimeEntity, time)
#endif
#ifdef USE_DATETIME_DATETIME
void APIServer::on_datetime_update(datetime::DateTimeEntity *obj) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_datetime_state(obj);
}
API_DISPATCH_UPDATE(datetime::DateTimeEntity, datetime)
#endif
#ifdef USE_TEXT
void APIServer::on_text_update(text::Text *obj, const std::string &state) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_text_state(obj);
}
API_DISPATCH_UPDATE_IGNORE_PARAMS(text::Text, text, const std::string &state)
#endif
#ifdef USE_SELECT
void APIServer::on_select_update(select::Select *obj, const std::string &state, size_t index) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_select_state(obj);
}
API_DISPATCH_UPDATE_IGNORE_PARAMS(select::Select, select, const std::string &state, size_t index)
#endif
#ifdef USE_LOCK
void APIServer::on_lock_update(lock::Lock *obj) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_lock_state(obj);
}
API_DISPATCH_UPDATE(lock::Lock, lock)
#endif
#ifdef USE_VALVE
void APIServer::on_valve_update(valve::Valve *obj) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_valve_state(obj);
}
API_DISPATCH_UPDATE(valve::Valve, valve)
#endif
#ifdef USE_MEDIA_PLAYER
void APIServer::on_media_player_update(media_player::MediaPlayer *obj) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_media_player_state(obj);
}
API_DISPATCH_UPDATE(media_player::MediaPlayer, media_player)
#endif
#ifdef USE_EVENT
// Event is a special case - it's the only entity that passes extra parameters to the send method
void APIServer::on_event(event::Event *obj, const std::string &event_type) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_event(obj, event_type);
}
#endif
#ifdef USE_UPDATE
void APIServer::on_update(update::UpdateEntity *obj) {
for (auto &c : this->clients_)
c->send_update_state(obj);
}
API_DISPATCH_UPDATE(update::UpdateEntity, update)
#endif
#ifdef USE_ALARM_CONTROL_PANEL
void APIServer::on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_alarm_control_panel_state(obj);
}
API_DISPATCH_UPDATE(alarm_control_panel::AlarmControlPanel, alarm_control_panel)
#endif
float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI; }

View File

@@ -52,11 +52,19 @@ bool BluetoothProxy::parse_device(const esp32_ble_tracker::ESPBTDevice &device)
return true;
}
static constexpr size_t FLUSH_BATCH_SIZE = 8;
static std::vector<api::BluetoothLERawAdvertisement> &get_batch_buffer() {
static std::vector<api::BluetoothLERawAdvertisement> batch_buffer;
return batch_buffer;
}
// Batch size for BLE advertisements to maximize WiFi efficiency
// Each advertisement is up to 80 bytes when packaged (including protocol overhead)
// Most advertisements are 20-30 bytes, allowing even more to fit per packet
// 16 advertisements × 80 bytes (worst case) = 1280 bytes out of ~1320 bytes usable payload
// This achieves ~97% WiFi MTU utilization while staying under the limit
static constexpr size_t FLUSH_BATCH_SIZE = 16;
// Global batch buffer to avoid guard variable (saves 8 bytes)
// This is initialized at program startup before any threads
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
static std::vector<api::BluetoothLERawAdvertisement> batch_buffer;
static std::vector<api::BluetoothLERawAdvertisement> &get_batch_buffer() { return batch_buffer; }
bool BluetoothProxy::parse_devices(const esp32_ble::BLEScanResult *scan_results, size_t count) {
if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr || !this->raw_advertisements_)

View File

@@ -1,4 +1,5 @@
import esphome.codegen as cg
from esphome.config_helpers import filter_source_files_from_platform
import esphome.config_validation as cv
from esphome.const import (
CONF_BLOCK,
@@ -7,6 +8,7 @@ from esphome.const import (
CONF_FREE,
CONF_ID,
CONF_LOOP_TIME,
PlatformFramework,
)
CODEOWNERS = ["@OttoWinter"]
@@ -44,3 +46,21 @@ CONFIG_SCHEMA = cv.All(
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
FILTER_SOURCE_FILES = filter_source_files_from_platform(
{
"debug_esp32.cpp": {
PlatformFramework.ESP32_ARDUINO,
PlatformFramework.ESP32_IDF,
},
"debug_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
"debug_host.cpp": {PlatformFramework.HOST_NATIVE},
"debug_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO},
"debug_libretiny.cpp": {
PlatformFramework.BK72XX_ARDUINO,
PlatformFramework.RTL87XX_ARDUINO,
PlatformFramework.LN882X_ARDUINO,
},
}
)

View File

@@ -11,6 +11,7 @@ from esphome.components.esp32.const import (
VARIANT_ESP32S2,
VARIANT_ESP32S3,
)
from esphome.config_helpers import filter_source_files_from_platform
import esphome.config_validation as cv
from esphome.const import (
CONF_DEFAULT,
@@ -27,6 +28,7 @@ from esphome.const import (
CONF_WAKEUP_PIN,
PLATFORM_ESP32,
PLATFORM_ESP8266,
PlatformFramework,
)
WAKEUP_PINS = {
@@ -313,3 +315,14 @@ async def deep_sleep_action_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var
FILTER_SOURCE_FILES = filter_source_files_from_platform(
{
"deep_sleep_esp32.cpp": {
PlatformFramework.ESP32_ARDUINO,
PlatformFramework.ESP32_IDF,
},
"deep_sleep_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
}
)

View File

@@ -0,0 +1,69 @@
#include "esphome/core/helpers.h"
#ifdef USE_ESP32
#include "esp_efuse.h"
#include "esp_efuse_table.h"
#include "esp_mac.h"
#include <freertos/FreeRTOS.h>
#include <freertos/portmacro.h>
#include "esp_random.h"
#include "esp_system.h"
namespace esphome {
uint32_t random_uint32() { return esp_random(); }
bool random_bytes(uint8_t *data, size_t len) {
esp_fill_random(data, len);
return true;
}
Mutex::Mutex() { handle_ = xSemaphoreCreateMutex(); }
Mutex::~Mutex() {}
void Mutex::lock() { xSemaphoreTake(this->handle_, portMAX_DELAY); }
bool Mutex::try_lock() { return xSemaphoreTake(this->handle_, 0) == pdTRUE; }
void Mutex::unlock() { xSemaphoreGive(this->handle_); }
// only affects the executing core
// so should not be used as a mutex lock, only to get accurate timing
IRAM_ATTR InterruptLock::InterruptLock() { portDISABLE_INTERRUPTS(); }
IRAM_ATTR InterruptLock::~InterruptLock() { portENABLE_INTERRUPTS(); }
void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parameter)
#if defined(CONFIG_SOC_IEEE802154_SUPPORTED)
// When CONFIG_SOC_IEEE802154_SUPPORTED is defined, esp_efuse_mac_get_default
// returns the 802.15.4 EUI-64 address, so we read directly from eFuse instead.
if (has_custom_mac_address()) {
esp_efuse_read_field_blob(ESP_EFUSE_MAC_CUSTOM, mac, 48);
} else {
esp_efuse_read_field_blob(ESP_EFUSE_MAC_FACTORY, mac, 48);
}
#else
if (has_custom_mac_address()) {
esp_efuse_mac_get_custom(mac);
} else {
esp_efuse_mac_get_default(mac);
}
#endif
}
void set_mac_address(uint8_t *mac) { esp_base_mac_addr_set(mac); }
bool has_custom_mac_address() {
#if !defined(USE_ESP32_IGNORE_EFUSE_CUSTOM_MAC)
uint8_t mac[6];
// do not use 'esp_efuse_mac_get_custom(mac)' because it drops an error in the logs whenever it fails
#ifndef USE_ESP32_VARIANT_ESP32
return (esp_efuse_read_field_blob(ESP_EFUSE_USER_DATA_MAC_CUSTOM, mac, 48) == ESP_OK) && mac_address_is_valid(mac);
#else
return (esp_efuse_read_field_blob(ESP_EFUSE_MAC_CUSTOM, mac, 48) == ESP_OK) && mac_address_is_valid(mac);
#endif
#else
return false;
#endif
}
} // namespace esphome
#endif // USE_ESP32

View File

@@ -25,10 +25,15 @@ namespace esphome {
namespace esp32_ble {
// Maximum number of BLE scan results to buffer
// Sized to handle bursts of advertisements while allowing for processing delays
// With 16 advertisements per batch and some safety margin:
// - Without PSRAM: 24 entries (1.5× batch size)
// - With PSRAM: 36 entries (2.25× batch size)
// The reduced structure size (~80 bytes vs ~400 bytes) allows for larger buffers
#ifdef USE_PSRAM
static constexpr uint8_t SCAN_RESULT_BUFFER_SIZE = 32;
static constexpr uint8_t SCAN_RESULT_BUFFER_SIZE = 36;
#else
static constexpr uint8_t SCAN_RESULT_BUFFER_SIZE = 20;
static constexpr uint8_t SCAN_RESULT_BUFFER_SIZE = 24;
#endif
// Maximum size of the BLE event queue - must be power of 2 for lock-free queue

View File

@@ -0,0 +1,31 @@
#include "esphome/core/helpers.h"
#ifdef USE_ESP8266
#include <osapi.h>
#include <user_interface.h>
// for xt_rsil()/xt_wsr_ps()
#include <Arduino.h>
namespace esphome {
uint32_t random_uint32() { return os_random(); }
bool random_bytes(uint8_t *data, size_t len) { return os_get_random(data, len) == 0; }
// ESP8266 doesn't have mutexes, but that shouldn't be an issue as it's single-core and non-preemptive OS.
Mutex::Mutex() {}
Mutex::~Mutex() {}
void Mutex::lock() {}
bool Mutex::try_lock() { return true; }
void Mutex::unlock() {}
IRAM_ATTR InterruptLock::InterruptLock() { state_ = xt_rsil(15); }
IRAM_ATTR InterruptLock::~InterruptLock() { xt_wsr_ps(state_); }
void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parameter)
wifi_get_macaddr(STATION_IF, mac);
}
} // namespace esphome
#endif // USE_ESP8266

View File

@@ -0,0 +1,57 @@
#include "esphome/core/helpers.h"
#ifdef USE_HOST
#ifndef _WIN32
#include <net/if.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#endif
#include <unistd.h>
#include <limits>
#include <random>
#include "esphome/core/defines.h"
#include "esphome/core/log.h"
namespace esphome {
static const char *const TAG = "helpers.host";
uint32_t random_uint32() {
std::random_device dev;
std::mt19937 rng(dev());
std::uniform_int_distribution<uint32_t> dist(0, std::numeric_limits<uint32_t>::max());
return dist(rng);
}
bool random_bytes(uint8_t *data, size_t len) {
FILE *fp = fopen("/dev/urandom", "r");
if (fp == nullptr) {
ESP_LOGW(TAG, "Could not open /dev/urandom, errno=%d", errno);
exit(1);
}
size_t read = fread(data, 1, len, fp);
if (read != len) {
ESP_LOGW(TAG, "Not enough data from /dev/urandom");
exit(1);
}
fclose(fp);
return true;
}
// Host platform uses std::mutex for proper thread synchronization
Mutex::Mutex() { handle_ = new std::mutex(); }
Mutex::~Mutex() { delete static_cast<std::mutex *>(handle_); }
void Mutex::lock() { static_cast<std::mutex *>(handle_)->lock(); }
bool Mutex::try_lock() { return static_cast<std::mutex *>(handle_)->try_lock(); }
void Mutex::unlock() { static_cast<std::mutex *>(handle_)->unlock(); }
void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parameter)
static const uint8_t esphome_host_mac_address[6] = USE_ESPHOME_HOST_MAC_ADDRESS;
memcpy(mac, esphome_host_mac_address, sizeof(esphome_host_mac_address));
}
} // namespace esphome
#endif // USE_HOST

View File

@@ -2,6 +2,7 @@ from esphome import automation
import esphome.codegen as cg
from esphome.components import esp32
from esphome.components.const import CONF_REQUEST_HEADERS
from esphome.config_helpers import filter_source_files_from_platform
import esphome.config_validation as cv
from esphome.const import (
CONF_ESP8266_DISABLE_SSL_SUPPORT,
@@ -13,6 +14,7 @@ from esphome.const import (
CONF_URL,
CONF_WATCHDOG_TIMEOUT,
PLATFORM_HOST,
PlatformFramework,
__version__,
)
from esphome.core import CORE, Lambda
@@ -319,3 +321,19 @@ async def http_request_action_to_code(config, action_id, template_arg, args):
await automation.build_automation(trigger, [], conf)
return var
FILTER_SOURCE_FILES = filter_source_files_from_platform(
{
"http_request_host.cpp": {PlatformFramework.HOST_NATIVE},
"http_request_arduino.cpp": {
PlatformFramework.ESP32_ARDUINO,
PlatformFramework.ESP8266_ARDUINO,
PlatformFramework.RP2040_ARDUINO,
PlatformFramework.BK72XX_ARDUINO,
PlatformFramework.RTL87XX_ARDUINO,
PlatformFramework.LN882X_ARDUINO,
},
"http_request_idf.cpp": {PlatformFramework.ESP32_IDF},
}
)

View File

@@ -3,6 +3,7 @@ import logging
from esphome import pins
import esphome.codegen as cg
from esphome.components import esp32
from esphome.config_helpers import filter_source_files_from_platform
import esphome.config_validation as cv
from esphome.const import (
CONF_ADDRESS,
@@ -18,6 +19,7 @@ from esphome.const import (
PLATFORM_ESP32,
PLATFORM_ESP8266,
PLATFORM_RP2040,
PlatformFramework,
)
from esphome.core import CORE, coroutine_with_priority
import esphome.final_validate as fv
@@ -205,3 +207,18 @@ def final_validate_device_schema(
{cv.Required(CONF_I2C_ID): fv.id_declaration_match_schema(hub_schema)},
extra=cv.ALLOW_EXTRA,
)
FILTER_SOURCE_FILES = filter_source_files_from_platform(
{
"i2c_bus_arduino.cpp": {
PlatformFramework.ESP32_ARDUINO,
PlatformFramework.ESP8266_ARDUINO,
PlatformFramework.RP2040_ARDUINO,
PlatformFramework.BK72XX_ARDUINO,
PlatformFramework.RTL87XX_ARDUINO,
PlatformFramework.LN882X_ARDUINO,
},
"i2c_bus_esp_idf.cpp": {PlatformFramework.ESP32_IDF},
}
)

View File

@@ -13,13 +13,13 @@ from esphome.const import (
from .. import CONF_LD2450_ID, LD2450Component, ld2450_ns
ResetButton = ld2450_ns.class_("ResetButton", button.Button)
FactoryResetButton = ld2450_ns.class_("FactoryResetButton", button.Button)
RestartButton = ld2450_ns.class_("RestartButton", button.Button)
CONFIG_SCHEMA = {
cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component),
cv.Optional(CONF_FACTORY_RESET): button.button_schema(
ResetButton,
FactoryResetButton,
device_class=DEVICE_CLASS_RESTART,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_RESTART_ALERT,
@@ -38,7 +38,7 @@ async def to_code(config):
if factory_reset_config := config.get(CONF_FACTORY_RESET):
b = await button.new_button(factory_reset_config)
await cg.register_parented(b, config[CONF_LD2450_ID])
cg.add(ld2450_component.set_reset_button(b))
cg.add(ld2450_component.set_factory_reset_button(b))
if restart_config := config.get(CONF_RESTART):
b = await button.new_button(restart_config)
await cg.register_parented(b, config[CONF_LD2450_ID])

View File

@@ -0,0 +1,9 @@
#include "factory_reset_button.h"
namespace esphome {
namespace ld2450 {
void FactoryResetButton::press_action() { this->parent_->factory_reset(); }
} // namespace ld2450
} // namespace esphome

View File

@@ -6,9 +6,9 @@
namespace esphome {
namespace ld2450 {
class ResetButton : public button::Button, public Parented<LD2450Component> {
class FactoryResetButton : public button::Button, public Parented<LD2450Component> {
public:
ResetButton() = default;
FactoryResetButton() = default;
protected:
void press_action() override;

View File

@@ -1,9 +0,0 @@
#include "reset_button.h"
namespace esphome {
namespace ld2450 {
void ResetButton::press_action() { this->parent_->factory_reset(); }
} // namespace ld2450
} // namespace esphome

View File

@@ -18,11 +18,10 @@ 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";
enum BaudRateStructure : uint8_t {
enum BaudRate : uint8_t {
BAUD_RATE_9600 = 1,
BAUD_RATE_19200 = 2,
BAUD_RATE_38400 = 3,
@@ -33,14 +32,13 @@ enum BaudRateStructure : uint8_t {
BAUD_RATE_460800 = 8
};
// Zone type struct
enum ZoneTypeStructure : uint8_t {
enum ZoneType : uint8_t {
ZONE_DISABLED = 0,
ZONE_DETECTION = 1,
ZONE_FILTER = 2,
};
enum PeriodicDataStructure : uint8_t {
enum PeriodicData : uint8_t {
TARGET_X = 4,
TARGET_Y = 6,
TARGET_SPEED = 8,
@@ -48,12 +46,12 @@ enum PeriodicDataStructure : uint8_t {
};
enum PeriodicDataValue : uint8_t {
HEAD = 0xAA,
END = 0x55,
HEADER = 0xAA,
FOOTER = 0x55,
CHECK = 0x00,
};
enum AckDataStructure : uint8_t {
enum AckData : uint8_t {
COMMAND = 6,
COMMAND_STATUS = 7,
};
@@ -61,11 +59,11 @@ enum AckDataStructure : uint8_t {
// Memory-efficient lookup tables
struct StringToUint8 {
const char *str;
uint8_t value;
const uint8_t value;
};
struct Uint8ToString {
uint8_t value;
const uint8_t value;
const char *str;
};
@@ -75,6 +73,13 @@ constexpr StringToUint8 BAUD_RATES_BY_STR[] = {
{"256000", BAUD_RATE_256000}, {"460800", BAUD_RATE_460800},
};
constexpr Uint8ToString DIRECTION_BY_UINT[] = {
{DIRECTION_APPROACHING, "Approaching"},
{DIRECTION_MOVING_AWAY, "Moving away"},
{DIRECTION_STATIONARY, "Stationary"},
{DIRECTION_NA, "NA"},
};
constexpr Uint8ToString ZONE_TYPE_BY_UINT[] = {
{ZONE_DISABLED, "Disabled"},
{ZONE_DETECTION, "Detection"},
@@ -104,28 +109,35 @@ template<size_t N> const char *find_str(const Uint8ToString (&arr)[N], uint8_t v
return ""; // Not found
}
// LD2450 serial 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};
// LD2450 UART Serial Commands
static const uint8_t CMD_ENABLE_CONF = 0xFF;
static const uint8_t CMD_DISABLE_CONF = 0xFE;
static const uint8_t CMD_VERSION = 0xA0;
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;
static const uint8_t CMD_SINGLE_TARGET_MODE = 0x80;
static const uint8_t CMD_MULTI_TARGET_MODE = 0x90;
static const uint8_t CMD_QUERY_TARGET_MODE = 0x91;
static const uint8_t CMD_SET_BAUD_RATE = 0xA1;
static const uint8_t CMD_QUERY_ZONE = 0xC1;
static const uint8_t CMD_SET_ZONE = 0xC2;
static constexpr uint8_t CMD_ENABLE_CONF = 0xFF;
static constexpr uint8_t CMD_DISABLE_CONF = 0xFE;
static constexpr uint8_t CMD_QUERY_VERSION = 0xA0;
static constexpr uint8_t CMD_QUERY_MAC_ADDRESS = 0xA5;
static constexpr uint8_t CMD_RESET = 0xA2;
static constexpr uint8_t CMD_RESTART = 0xA3;
static constexpr uint8_t CMD_BLUETOOTH = 0xA4;
static constexpr uint8_t CMD_SINGLE_TARGET_MODE = 0x80;
static constexpr uint8_t CMD_MULTI_TARGET_MODE = 0x90;
static constexpr uint8_t CMD_QUERY_TARGET_MODE = 0x91;
static constexpr uint8_t CMD_SET_BAUD_RATE = 0xA1;
static constexpr uint8_t CMD_QUERY_ZONE = 0xC1;
static constexpr uint8_t CMD_SET_ZONE = 0xC2;
// Header & Footer size
static constexpr uint8_t HEADER_FOOTER_SIZE = 4;
// Command Header & Footer
static constexpr uint8_t CMD_FRAME_HEADER[HEADER_FOOTER_SIZE] = {0xFD, 0xFC, 0xFB, 0xFA};
static constexpr uint8_t CMD_FRAME_FOOTER[HEADER_FOOTER_SIZE] = {0x04, 0x03, 0x02, 0x01};
// Data Header & Footer
static constexpr uint8_t DATA_FRAME_HEADER[HEADER_FOOTER_SIZE] = {0xAA, 0xFF, 0x03, 0x00};
static constexpr uint8_t DATA_FRAME_FOOTER[2] = {0x55, 0xCC};
// MAC address the module uses when Bluetooth is disabled
static constexpr uint8_t NO_MAC[] = {0x08, 0x05, 0x04, 0x03, 0x02, 0x01};
static inline uint16_t convert_seconds_to_ms(uint16_t value) { return value * 1000; };
static inline void convert_int_values_to_hex(const int *values, uint8_t *bytes) {
for (int i = 0; i < 4; i++) {
for (uint8_t i = 0; i < 4; i++) {
uint16_t val = values[i] & 0xFFFF;
bytes[i * 2] = val & 0xFF; // Store low byte first (little-endian)
bytes[i * 2 + 1] = (val >> 8) & 0xFF; // Store high byte second
@@ -166,18 +178,13 @@ static inline float calculate_angle(float base, float hypotenuse) {
return angle_degrees;
}
static inline std::string get_direction(int16_t speed) {
static const char *const APPROACHING = "Approaching";
static const char *const MOVING_AWAY = "Moving away";
static const char *const STATIONARY = "Stationary";
if (speed > 0) {
return MOVING_AWAY;
static bool validate_header_footer(const uint8_t *header_footer, const uint8_t *buffer) {
for (uint8_t i = 0; i < HEADER_FOOTER_SIZE; i++) {
if (header_footer[i] != buffer[i]) {
return false; // Mismatch in header/footer
}
}
if (speed < 0) {
return APPROACHING;
}
return STATIONARY;
return true; // Valid header/footer
}
void LD2450Component::setup() {
@@ -192,84 +199,93 @@ void LD2450Component::setup() {
}
void LD2450Component::dump_config() {
ESP_LOGCONFIG(TAG, "LD2450:");
std::string mac_str =
mac_address_is_valid(this->mac_address_) ? format_mac_address_pretty(this->mac_address_) : UNKNOWN_MAC;
std::string version = str_sprintf(VERSION_FMT, this->version_[1], this->version_[0], this->version_[5],
this->version_[4], this->version_[3], this->version_[2]);
ESP_LOGCONFIG(TAG,
"LD2450:\n"
" Firmware version: %s\n"
" MAC address: %s\n"
" Throttle: %u ms",
version.c_str(), mac_str.c_str(), this->throttle_);
#ifdef USE_BINARY_SENSOR
LOG_BINARY_SENSOR(" ", "TargetBinarySensor", this->target_binary_sensor_);
LOG_BINARY_SENSOR(" ", "MovingTargetBinarySensor", this->moving_target_binary_sensor_);
LOG_BINARY_SENSOR(" ", "StillTargetBinarySensor", this->still_target_binary_sensor_);
#endif
#ifdef USE_SWITCH
LOG_SWITCH(" ", "BluetoothSwitch", this->bluetooth_switch_);
LOG_SWITCH(" ", "MultiTargetSwitch", this->multi_target_switch_);
#endif
#ifdef USE_BUTTON
LOG_BUTTON(" ", "ResetButton", this->reset_button_);
LOG_BUTTON(" ", "RestartButton", this->restart_button_);
ESP_LOGCONFIG(TAG, "Binary Sensors:");
LOG_BINARY_SENSOR(" ", "MovingTarget", this->moving_target_binary_sensor_);
LOG_BINARY_SENSOR(" ", "StillTarget", this->still_target_binary_sensor_);
LOG_BINARY_SENSOR(" ", "Target", this->target_binary_sensor_);
#endif
#ifdef USE_SENSOR
LOG_SENSOR(" ", "TargetCountSensor", this->target_count_sensor_);
LOG_SENSOR(" ", "StillTargetCountSensor", this->still_target_count_sensor_);
LOG_SENSOR(" ", "MovingTargetCountSensor", this->moving_target_count_sensor_);
ESP_LOGCONFIG(TAG, "Sensors:");
LOG_SENSOR(" ", "MovingTargetCount", this->moving_target_count_sensor_);
LOG_SENSOR(" ", "StillTargetCount", this->still_target_count_sensor_);
LOG_SENSOR(" ", "TargetCount", this->target_count_sensor_);
for (sensor::Sensor *s : this->move_x_sensors_) {
LOG_SENSOR(" ", "NthTargetXSensor", s);
LOG_SENSOR(" ", "TargetX", s);
}
for (sensor::Sensor *s : this->move_y_sensors_) {
LOG_SENSOR(" ", "NthTargetYSensor", s);
}
for (sensor::Sensor *s : this->move_speed_sensors_) {
LOG_SENSOR(" ", "NthTargetSpeedSensor", s);
LOG_SENSOR(" ", "TargetY", s);
}
for (sensor::Sensor *s : this->move_angle_sensors_) {
LOG_SENSOR(" ", "NthTargetAngleSensor", s);
LOG_SENSOR(" ", "TargetAngle", s);
}
for (sensor::Sensor *s : this->move_distance_sensors_) {
LOG_SENSOR(" ", "NthTargetDistanceSensor", s);
LOG_SENSOR(" ", "TargetDistance", s);
}
for (sensor::Sensor *s : this->move_resolution_sensors_) {
LOG_SENSOR(" ", "NthTargetResolutionSensor", s);
LOG_SENSOR(" ", "TargetResolution", s);
}
for (sensor::Sensor *s : this->move_speed_sensors_) {
LOG_SENSOR(" ", "TargetSpeed", s);
}
for (sensor::Sensor *s : this->zone_target_count_sensors_) {
LOG_SENSOR(" ", "NthZoneTargetCountSensor", s);
}
for (sensor::Sensor *s : this->zone_still_target_count_sensors_) {
LOG_SENSOR(" ", "NthZoneStillTargetCountSensor", s);
LOG_SENSOR(" ", "ZoneTargetCount", s);
}
for (sensor::Sensor *s : this->zone_moving_target_count_sensors_) {
LOG_SENSOR(" ", "NthZoneMovingTargetCountSensor", s);
LOG_SENSOR(" ", "ZoneMovingTargetCount", s);
}
for (sensor::Sensor *s : this->zone_still_target_count_sensors_) {
LOG_SENSOR(" ", "ZoneStillTargetCount", s);
}
#endif
#ifdef USE_TEXT_SENSOR
LOG_TEXT_SENSOR(" ", "VersionTextSensor", this->version_text_sensor_);
LOG_TEXT_SENSOR(" ", "MacTextSensor", this->mac_text_sensor_);
ESP_LOGCONFIG(TAG, "Text Sensors:");
LOG_TEXT_SENSOR(" ", "Version", this->version_text_sensor_);
LOG_TEXT_SENSOR(" ", "Mac", this->mac_text_sensor_);
for (text_sensor::TextSensor *s : this->direction_text_sensors_) {
LOG_TEXT_SENSOR(" ", "NthDirectionTextSensor", s);
LOG_TEXT_SENSOR(" ", "Direction", s);
}
#endif
#ifdef USE_NUMBER
ESP_LOGCONFIG(TAG, "Numbers:");
LOG_NUMBER(" ", "PresenceTimeout", this->presence_timeout_number_);
for (auto n : this->zone_numbers_) {
LOG_NUMBER(" ", "ZoneX1Number", n.x1);
LOG_NUMBER(" ", "ZoneY1Number", n.y1);
LOG_NUMBER(" ", "ZoneX2Number", n.x2);
LOG_NUMBER(" ", "ZoneY2Number", n.y2);
LOG_NUMBER(" ", "ZoneX1", n.x1);
LOG_NUMBER(" ", "ZoneY1", n.y1);
LOG_NUMBER(" ", "ZoneX2", n.x2);
LOG_NUMBER(" ", "ZoneY2", n.y2);
}
#endif
#ifdef USE_SELECT
LOG_SELECT(" ", "BaudRateSelect", this->baud_rate_select_);
LOG_SELECT(" ", "ZoneTypeSelect", this->zone_type_select_);
ESP_LOGCONFIG(TAG, "Selects:");
LOG_SELECT(" ", "BaudRate", this->baud_rate_select_);
LOG_SELECT(" ", "ZoneType", this->zone_type_select_);
#endif
#ifdef USE_NUMBER
LOG_NUMBER(" ", "PresenceTimeoutNumber", this->presence_timeout_number_);
#ifdef USE_SWITCH
ESP_LOGCONFIG(TAG, "Switches:");
LOG_SWITCH(" ", "Bluetooth", this->bluetooth_switch_);
LOG_SWITCH(" ", "MultiTarget", this->multi_target_switch_);
#endif
#ifdef USE_BUTTON
ESP_LOGCONFIG(TAG, "Buttons:");
LOG_BUTTON(" ", "FactoryReset", this->factory_reset_button_);
LOG_BUTTON(" ", "Restart", this->restart_button_);
#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());
}
void LD2450Component::loop() {
while (this->available()) {
this->readline_(read(), this->buffer_data_, MAX_LINE_LENGTH);
this->readline_(this->read());
}
}
@@ -304,7 +320,7 @@ void LD2450Component::set_radar_zone(int32_t zone_type, int32_t zone1_x1, int32_
this->zone_type_ = zone_type;
int zone_parameters[12] = {zone1_x1, zone1_y1, zone1_x2, zone1_y2, zone2_x1, zone2_y1,
zone2_x2, zone2_y2, zone3_x1, zone3_y1, zone3_x2, zone3_y2};
for (int i = 0; i < MAX_ZONES; i++) {
for (uint8_t i = 0; i < MAX_ZONES; i++) {
this->zone_config_[i].x1 = zone_parameters[i * 4];
this->zone_config_[i].y1 = zone_parameters[i * 4 + 1];
this->zone_config_[i].x2 = zone_parameters[i * 4 + 2];
@@ -318,15 +334,15 @@ void LD2450Component::send_set_zone_command_() {
uint8_t cmd_value[26] = {};
uint8_t zone_type_bytes[2] = {static_cast<uint8_t>(this->zone_type_), 0x00};
uint8_t area_config[24] = {};
for (int i = 0; i < MAX_ZONES; i++) {
for (uint8_t i = 0; i < MAX_ZONES; i++) {
int values[4] = {this->zone_config_[i].x1, this->zone_config_[i].y1, this->zone_config_[i].x2,
this->zone_config_[i].y2};
ld2450::convert_int_values_to_hex(values, area_config + (i * 8));
}
std::memcpy(cmd_value, zone_type_bytes, 2);
std::memcpy(cmd_value + 2, area_config, 24);
std::memcpy(cmd_value, zone_type_bytes, sizeof(zone_type_bytes));
std::memcpy(cmd_value + 2, area_config, sizeof(area_config));
this->set_config_mode_(true);
this->send_command_(CMD_SET_ZONE, cmd_value, 26);
this->send_command_(CMD_SET_ZONE, cmd_value, sizeof(cmd_value));
this->set_config_mode_(false);
}
@@ -342,14 +358,14 @@ bool LD2450Component::get_timeout_status_(uint32_t check_millis) {
}
// Extract, store and publish zone details LD2450 buffer
void LD2450Component::process_zone_(uint8_t *buffer) {
void LD2450Component::process_zone_() {
uint8_t index, start;
for (index = 0; index < MAX_ZONES; index++) {
start = 12 + index * 8;
this->zone_config_[index].x1 = ld2450::hex_to_signed_int(buffer, start);
this->zone_config_[index].y1 = ld2450::hex_to_signed_int(buffer, start + 2);
this->zone_config_[index].x2 = ld2450::hex_to_signed_int(buffer, start + 4);
this->zone_config_[index].y2 = ld2450::hex_to_signed_int(buffer, start + 6);
this->zone_config_[index].x1 = ld2450::hex_to_signed_int(this->buffer_data_, start);
this->zone_config_[index].y1 = ld2450::hex_to_signed_int(this->buffer_data_, start + 2);
this->zone_config_[index].x2 = ld2450::hex_to_signed_int(this->buffer_data_, start + 4);
this->zone_config_[index].y2 = ld2450::hex_to_signed_int(this->buffer_data_, start + 6);
#ifdef USE_NUMBER
// only one null check as all coordinates are required for a single zone
if (this->zone_numbers_[index].x1 != nullptr) {
@@ -395,27 +411,25 @@ void LD2450Component::restart_and_read_all_info() {
// Send command with values to LD2450
void LD2450Component::send_command_(uint8_t command, const uint8_t *command_value, uint8_t command_value_len) {
ESP_LOGV(TAG, "Sending command %02X", command);
// frame header
this->write_array(CMD_FRAME_HEADER, 4);
ESP_LOGV(TAG, "Sending COMMAND %02X", command);
// frame header bytes
this->write_array(CMD_FRAME_HEADER, sizeof(CMD_FRAME_HEADER));
// length bytes
int len = 2;
uint8_t len = 2;
if (command_value != nullptr) {
len += command_value_len;
}
this->write_byte(lowbyte(len));
this->write_byte(highbyte(len));
// command
this->write_byte(lowbyte(command));
this->write_byte(highbyte(command));
uint8_t len_cmd[] = {lowbyte(len), highbyte(len), command, 0x00};
this->write_array(len_cmd, sizeof(len_cmd));
// command value bytes
if (command_value != nullptr) {
for (int i = 0; i < command_value_len; i++) {
for (uint8_t i = 0; i < command_value_len; i++) {
this->write_byte(command_value[i]);
}
}
// footer
this->write_array(CMD_FRAME_END, 4);
// frame footer bytes
this->write_array(CMD_FRAME_FOOTER, sizeof(CMD_FRAME_FOOTER));
// FIXME to remove
delay(50); // NOLINT
}
@@ -423,26 +437,23 @@ void LD2450Component::send_command_(uint8_t command, const uint8_t *command_valu
// LD2450 Radar data message:
// [AA FF 03 00] [0E 03 B1 86 10 00 40 01] [00 00 00 00 00 00 00 00] [00 00 00 00 00 00 00 00] [55 CC]
// Header Target 1 Target 2 Target 3 End
void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) {
void LD2450Component::handle_periodic_data_() {
// Early throttle check - moved before any processing to save CPU cycles
if (App.get_loop_component_start_time() - this->last_periodic_millis_ < this->throttle_) {
ESP_LOGV(TAG, "Throttling: %d", this->throttle_);
return;
}
if (len < 29) { // header (4 bytes) + 8 x 3 target data + footer (2 bytes)
ESP_LOGE(TAG, "Invalid message length");
if (this->buffer_pos_ < 29) { // header (4 bytes) + 8 x 3 target data + footer (2 bytes)
ESP_LOGE(TAG, "Invalid length");
return;
}
if (buffer[0] != 0xAA || buffer[1] != 0xFF || buffer[2] != 0x03 || buffer[3] != 0x00) { // header
ESP_LOGE(TAG, "Invalid message header");
if (!ld2450::validate_header_footer(DATA_FRAME_HEADER, this->buffer_data_) ||
this->buffer_data_[this->buffer_pos_ - 2] != DATA_FRAME_FOOTER[0] ||
this->buffer_data_[this->buffer_pos_ - 1] != DATA_FRAME_FOOTER[1]) {
ESP_LOGE(TAG, "Invalid header/footer");
return;
}
if (buffer[len - 2] != 0x55 || buffer[len - 1] != 0xCC) { // footer
ESP_LOGE(TAG, "Invalid message footer");
return;
}
// Save the timestamp after validating the frame so, if invalid, we'll take the next frame immediately
this->last_periodic_millis_ = App.get_loop_component_start_time();
int16_t target_count = 0;
@@ -450,13 +461,13 @@ void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) {
int16_t moving_target_count = 0;
int16_t start = 0;
int16_t val = 0;
uint8_t index = 0;
int16_t tx = 0;
int16_t ty = 0;
int16_t td = 0;
int16_t ts = 0;
int16_t angle = 0;
std::string direction{};
uint8_t index = 0;
Direction direction{DIRECTION_UNDEFINED};
bool is_moving = false;
#if defined(USE_BINARY_SENSOR) || defined(USE_SENSOR) || defined(USE_TEXT_SENSOR)
@@ -468,7 +479,7 @@ void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) {
is_moving = false;
sensor::Sensor *sx = this->move_x_sensors_[index];
if (sx != nullptr) {
val = ld2450::decode_coordinate(buffer[start], buffer[start + 1]);
val = ld2450::decode_coordinate(this->buffer_data_[start], this->buffer_data_[start + 1]);
tx = val;
if (this->cached_target_data_[index].x != val) {
sx->publish_state(val);
@@ -479,7 +490,7 @@ void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) {
start = TARGET_Y + index * 8;
sensor::Sensor *sy = this->move_y_sensors_[index];
if (sy != nullptr) {
val = ld2450::decode_coordinate(buffer[start], buffer[start + 1]);
val = ld2450::decode_coordinate(this->buffer_data_[start], this->buffer_data_[start + 1]);
ty = val;
if (this->cached_target_data_[index].y != val) {
sy->publish_state(val);
@@ -490,7 +501,7 @@ void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) {
start = TARGET_RESOLUTION + index * 8;
sensor::Sensor *sr = this->move_resolution_sensors_[index];
if (sr != nullptr) {
val = (buffer[start + 1] << 8) | buffer[start];
val = (this->buffer_data_[start + 1] << 8) | this->buffer_data_[start];
if (this->cached_target_data_[index].resolution != val) {
sr->publish_state(val);
this->cached_target_data_[index].resolution = val;
@@ -499,7 +510,7 @@ void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) {
#endif
// SPEED
start = TARGET_SPEED + index * 8;
val = ld2450::decode_speed(buffer[start], buffer[start + 1]);
val = ld2450::decode_speed(this->buffer_data_[start], this->buffer_data_[start + 1]);
ts = val;
if (val) {
is_moving = true;
@@ -532,7 +543,7 @@ void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) {
}
}
// ANGLE
angle = calculate_angle(static_cast<float>(ty), static_cast<float>(td));
angle = ld2450::calculate_angle(static_cast<float>(ty), static_cast<float>(td));
if (tx > 0) {
angle = angle * -1;
}
@@ -547,14 +558,19 @@ void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) {
#endif
#ifdef USE_TEXT_SENSOR
// DIRECTION
direction = get_direction(ts);
if (td == 0) {
direction = "NA";
direction = DIRECTION_NA;
} else if (ts > 0) {
direction = DIRECTION_MOVING_AWAY;
} else if (ts < 0) {
direction = DIRECTION_APPROACHING;
} else {
direction = DIRECTION_STATIONARY;
}
text_sensor::TextSensor *tsd = this->direction_text_sensors_[index];
if (tsd != nullptr) {
if (this->cached_target_data_[index].direction != direction) {
tsd->publish_state(direction);
tsd->publish_state(find_str(ld2450::DIRECTION_BY_UINT, direction));
this->cached_target_data_[index].direction = direction;
}
}
@@ -678,117 +694,139 @@ void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) {
#endif
}
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");
bool LD2450Component::handle_ack_data_() {
ESP_LOGV(TAG, "Handling ACK DATA for COMMAND %02X", this->buffer_data_[COMMAND]);
if (this->buffer_pos_ < 10) {
ESP_LOGE(TAG, "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]);
if (!ld2450::validate_header_footer(CMD_FRAME_HEADER, this->buffer_data_)) {
ESP_LOGW(TAG, "Invalid header: %s", format_hex_pretty(this->buffer_data_, HEADER_FOOTER_SIZE).c_str());
return true;
}
if (buffer[COMMAND_STATUS] != 0x01) {
ESP_LOGE(TAG, "Invalid ack status");
if (this->buffer_data_[COMMAND_STATUS] != 0x01) {
ESP_LOGE(TAG, "Invalid status");
return true;
}
if (buffer[8] || buffer[9]) {
ESP_LOGE(TAG, "Last buffer was %u, %u", buffer[8], buffer[9]);
if (this->buffer_data_[8] || this->buffer_data_[9]) {
ESP_LOGW(TAG, "Invalid command: %02X, %02X", this->buffer_data_[8], this->buffer_data_[9]);
return true;
}
switch (buffer[COMMAND]) {
case lowbyte(CMD_ENABLE_CONF):
ESP_LOGV(TAG, "Enable conf command");
switch (this->buffer_data_[COMMAND]) {
case CMD_ENABLE_CONF:
ESP_LOGV(TAG, "Enable conf");
break;
case lowbyte(CMD_DISABLE_CONF):
ESP_LOGV(TAG, "Disable conf command");
case CMD_DISABLE_CONF:
ESP_LOGV(TAG, "Disabled conf");
break;
case lowbyte(CMD_SET_BAUD_RATE):
ESP_LOGV(TAG, "Baud rate change command");
case CMD_SET_BAUD_RATE:
ESP_LOGV(TAG, "Baud rate change");
#ifdef USE_SELECT
if (this->baud_rate_select_ != nullptr) {
ESP_LOGV(TAG, "Change baud rate to %s", this->baud_rate_select_->state.c_str());
ESP_LOGE(TAG, "Change baud rate 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());
case CMD_QUERY_VERSION: {
std::memcpy(this->version_, &this->buffer_data_[12], sizeof(this->version_));
std::string version = str_sprintf(VERSION_FMT, this->version_[1], this->version_[0], this->version_[5],
this->version_[4], this->version_[3], this->version_[2]);
ESP_LOGV(TAG, "Firmware version: %s", version.c_str());
#ifdef USE_TEXT_SENSOR
if (this->version_text_sensor_ != nullptr) {
this->version_text_sensor_->publish_state(this->version_);
this->version_text_sensor_->publish_state(version);
}
#endif
break;
case lowbyte(CMD_MAC):
if (len < 20) {
}
case CMD_QUERY_MAC_ADDRESS: {
if (this->buffer_pos_ < 20) {
return false;
}
this->mac_ = format_mac_address_pretty(&buffer[10]);
ESP_LOGV(TAG, "MAC address: %s", this->mac_.c_str());
this->bluetooth_on_ = std::memcmp(&this->buffer_data_[10], NO_MAC, sizeof(NO_MAC)) != 0;
if (this->bluetooth_on_) {
std::memcpy(this->mac_address_, &this->buffer_data_[10], sizeof(this->mac_address_));
}
std::string mac_str =
mac_address_is_valid(this->mac_address_) ? format_mac_address_pretty(this->mac_address_) : UNKNOWN_MAC;
ESP_LOGV(TAG, "MAC address: %s", mac_str.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(mac_str);
}
#endif
#ifdef USE_SWITCH
if (this->bluetooth_switch_ != nullptr) {
this->bluetooth_switch_->publish_state(this->mac_ != NO_MAC);
this->bluetooth_switch_->publish_state(this->bluetooth_on_);
}
#endif
break;
case lowbyte(CMD_BLUETOOTH):
ESP_LOGV(TAG, "Bluetooth command");
}
case CMD_BLUETOOTH:
ESP_LOGV(TAG, "Bluetooth");
break;
case lowbyte(CMD_SINGLE_TARGET_MODE):
ESP_LOGV(TAG, "Single target conf command");
case CMD_SINGLE_TARGET_MODE:
ESP_LOGV(TAG, "Single target conf");
#ifdef USE_SWITCH
if (this->multi_target_switch_ != nullptr) {
this->multi_target_switch_->publish_state(false);
}
#endif
break;
case lowbyte(CMD_MULTI_TARGET_MODE):
ESP_LOGV(TAG, "Multi target conf command");
case CMD_MULTI_TARGET_MODE:
ESP_LOGV(TAG, "Multi target conf");
#ifdef USE_SWITCH
if (this->multi_target_switch_ != nullptr) {
this->multi_target_switch_->publish_state(true);
}
#endif
break;
case lowbyte(CMD_QUERY_TARGET_MODE):
ESP_LOGV(TAG, "Query target tracking mode command");
case CMD_QUERY_TARGET_MODE:
ESP_LOGV(TAG, "Query target tracking mode");
#ifdef USE_SWITCH
if (this->multi_target_switch_ != nullptr) {
this->multi_target_switch_->publish_state(buffer[10] == 0x02);
this->multi_target_switch_->publish_state(this->buffer_data_[10] == 0x02);
}
#endif
break;
case lowbyte(CMD_QUERY_ZONE):
ESP_LOGV(TAG, "Query zone conf command");
this->zone_type_ = std::stoi(std::to_string(buffer[10]), nullptr, 16);
case CMD_QUERY_ZONE:
ESP_LOGV(TAG, "Query zone conf");
this->zone_type_ = std::stoi(std::to_string(this->buffer_data_[10]), nullptr, 16);
this->publish_zone_type();
#ifdef USE_SELECT
if (this->zone_type_select_ != nullptr) {
ESP_LOGV(TAG, "Change zone type to: %s", this->zone_type_select_->state.c_str());
}
#endif
if (buffer[10] == 0x00) {
if (this->buffer_data_[10] == 0x00) {
ESP_LOGV(TAG, "Zone: Disabled");
}
if (buffer[10] == 0x01) {
if (this->buffer_data_[10] == 0x01) {
ESP_LOGV(TAG, "Zone: Area detection");
}
if (buffer[10] == 0x02) {
if (this->buffer_data_[10] == 0x02) {
ESP_LOGV(TAG, "Zone: Area filter");
}
this->process_zone_(buffer);
this->process_zone_();
break;
case lowbyte(CMD_SET_ZONE):
ESP_LOGV(TAG, "Set zone conf command");
case CMD_SET_ZONE:
ESP_LOGV(TAG, "Set zone conf");
this->query_zone_info();
break;
default:
break;
}
@@ -796,55 +834,57 @@ bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) {
}
// Read LD2450 buffer data
void LD2450Component::readline_(int readch, uint8_t *buffer, uint8_t len) {
void LD2450Component::readline_(int readch) {
if (readch < 0) {
return;
return; // No data available
}
if (this->buffer_pos_ < len - 1) {
buffer[this->buffer_pos_++] = readch;
buffer[this->buffer_pos_] = 0;
if (this->buffer_pos_ < MAX_LINE_LENGTH - 1) {
this->buffer_data_[this->buffer_pos_++] = readch;
this->buffer_data_[this->buffer_pos_] = 0;
} else {
// We should never get here, but just in case...
ESP_LOGW(TAG, "Max command length exceeded; ignoring");
this->buffer_pos_ = 0;
}
if (this->buffer_pos_ < 4) {
return;
return; // Not enough data to process yet
}
if (buffer[this->buffer_pos_ - 2] == 0x55 && buffer[this->buffer_pos_ - 1] == 0xCC) {
ESP_LOGV(TAG, "Handle periodic radar data");
this->handle_periodic_data_(buffer, this->buffer_pos_);
if (this->buffer_data_[this->buffer_pos_ - 2] == DATA_FRAME_FOOTER[0] &&
this->buffer_data_[this->buffer_pos_ - 1] == DATA_FRAME_FOOTER[1]) {
ESP_LOGV(TAG, "Handling Periodic Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str());
this->handle_periodic_data_();
this->buffer_pos_ = 0; // Reset position index for next frame
} else if (buffer[this->buffer_pos_ - 4] == 0x04 && buffer[this->buffer_pos_ - 3] == 0x03 &&
buffer[this->buffer_pos_ - 2] == 0x02 && buffer[this->buffer_pos_ - 1] == 0x01) {
ESP_LOGV(TAG, "Handle command ack data");
if (this->handle_ack_data_(buffer, this->buffer_pos_)) {
this->buffer_pos_ = 0; // Reset position index for next frame
} else if (ld2450::validate_header_footer(CMD_FRAME_FOOTER, &this->buffer_data_[this->buffer_pos_ - 4])) {
ESP_LOGV(TAG, "Handling Ack Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str());
if (this->handle_ack_data_()) {
this->buffer_pos_ = 0; // Reset position index for next message
} else {
ESP_LOGV(TAG, "Command ack data invalid");
ESP_LOGV(TAG, "Ack Data incomplete");
}
}
}
// Set Config Mode - Pre-requisite sending commands
void LD2450Component::set_config_mode_(bool enable) {
uint8_t cmd = enable ? CMD_ENABLE_CONF : CMD_DISABLE_CONF;
uint8_t cmd_value[2] = {0x01, 0x00};
this->send_command_(cmd, enable ? cmd_value : nullptr, 2);
const uint8_t cmd = enable ? CMD_ENABLE_CONF : CMD_DISABLE_CONF;
const uint8_t cmd_value[2] = {0x01, 0x00};
this->send_command_(cmd, enable ? cmd_value : nullptr, sizeof(cmd_value));
}
// Set Bluetooth Enable/Disable
void LD2450Component::set_bluetooth(bool enable) {
this->set_config_mode_(true);
uint8_t enable_cmd_value[2] = {0x01, 0x00};
uint8_t disable_cmd_value[2] = {0x00, 0x00};
this->send_command_(CMD_BLUETOOTH, enable ? enable_cmd_value : disable_cmd_value, 2);
const uint8_t cmd_value[2] = {enable ? (uint8_t) 0x01 : (uint8_t) 0x00, 0x00};
this->send_command_(CMD_BLUETOOTH, cmd_value, sizeof(cmd_value));
this->set_timeout(200, [this]() { this->restart_and_read_all_info(); });
}
// Set Baud rate
void LD2450Component::set_baud_rate(const std::string &state) {
this->set_config_mode_(true);
uint8_t cmd_value[2] = {find_uint8(BAUD_RATES_BY_STR, state), 0x00};
this->send_command_(CMD_SET_BAUD_RATE, cmd_value, 2);
const uint8_t cmd_value[2] = {find_uint8(BAUD_RATES_BY_STR, state), 0x00};
this->send_command_(CMD_SET_BAUD_RATE, cmd_value, sizeof(cmd_value));
this->set_timeout(200, [this]() { this->restart_(); });
}
@@ -885,12 +925,12 @@ void LD2450Component::factory_reset() {
void LD2450Component::restart_() { this->send_command_(CMD_RESTART, nullptr, 0); }
// Get LD2450 firmware version
void LD2450Component::get_version_() { this->send_command_(CMD_VERSION, nullptr, 0); }
void LD2450Component::get_version_() { this->send_command_(CMD_QUERY_VERSION, nullptr, 0); }
// Get LD2450 mac address
void LD2450Component::get_mac_() {
uint8_t cmd_value[2] = {0x01, 0x00};
this->send_command_(CMD_MAC, cmd_value, 2);
this->send_command_(CMD_QUERY_MAC_ADDRESS, cmd_value, 2);
}
// Query for target tracking mode

View File

@@ -38,10 +38,18 @@ namespace ld2450 {
// Constants
static const uint8_t DEFAULT_PRESENCE_TIMEOUT = 5; // Timeout to reset presense status 5 sec.
static const uint8_t MAX_LINE_LENGTH = 60; // Max characters for serial buffer
static const uint8_t MAX_LINE_LENGTH = 41; // Max characters for serial buffer
static const uint8_t MAX_TARGETS = 3; // Max 3 Targets in LD2450
static const uint8_t MAX_ZONES = 3; // Max 3 Zones in LD2450
enum Direction : uint8_t {
DIRECTION_APPROACHING = 0,
DIRECTION_MOVING_AWAY = 1,
DIRECTION_STATIONARY = 2,
DIRECTION_NA = 3,
DIRECTION_UNDEFINED = 4,
};
// Target coordinate struct
struct Target {
int16_t x;
@@ -67,19 +75,22 @@ struct ZoneOfNumbers {
#endif
class LD2450Component : public Component, public uart::UARTDevice {
#ifdef USE_SENSOR
SUB_SENSOR(target_count)
SUB_SENSOR(still_target_count)
SUB_SENSOR(moving_target_count)
#endif
#ifdef USE_BINARY_SENSOR
SUB_BINARY_SENSOR(target)
SUB_BINARY_SENSOR(moving_target)
SUB_BINARY_SENSOR(still_target)
SUB_BINARY_SENSOR(target)
#endif
#ifdef USE_SENSOR
SUB_SENSOR(moving_target_count)
SUB_SENSOR(still_target_count)
SUB_SENSOR(target_count)
#endif
#ifdef USE_TEXT_SENSOR
SUB_TEXT_SENSOR(version)
SUB_TEXT_SENSOR(mac)
SUB_TEXT_SENSOR(version)
#endif
#ifdef USE_NUMBER
SUB_NUMBER(presence_timeout)
#endif
#ifdef USE_SELECT
SUB_SELECT(baud_rate)
@@ -90,12 +101,9 @@ class LD2450Component : public Component, public uart::UARTDevice {
SUB_SWITCH(multi_target)
#endif
#ifdef USE_BUTTON
SUB_BUTTON(reset)
SUB_BUTTON(factory_reset)
SUB_BUTTON(restart)
#endif
#ifdef USE_NUMBER
SUB_NUMBER(presence_timeout)
#endif
public:
void setup() override;
@@ -138,10 +146,10 @@ class LD2450Component : public Component, public uart::UARTDevice {
protected:
void send_command_(uint8_t command_str, const uint8_t *command_value, uint8_t command_value_len);
void set_config_mode_(bool enable);
void handle_periodic_data_(uint8_t *buffer, uint8_t len);
bool handle_ack_data_(uint8_t *buffer, uint8_t len);
void process_zone_(uint8_t *buffer);
void readline_(int readch, uint8_t *buffer, uint8_t len);
void handle_periodic_data_();
bool handle_ack_data_();
void process_zone_();
void readline_(int readch);
void get_version_();
void get_mac_();
void query_target_tracking_mode_();
@@ -159,13 +167,14 @@ class LD2450Component : public Component, public uart::UARTDevice {
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 mac_address_[6] = {0, 0, 0, 0, 0, 0};
uint8_t version_[6] = {0, 0, 0, 0, 0, 0};
uint8_t buffer_pos_ = 0; // where to resume processing/populating buffer
uint8_t zone_type_ = 0;
bool bluetooth_on_{false};
Target target_info_[MAX_TARGETS];
Zone zone_config_[MAX_ZONES];
std::string version_{};
std::string mac_{};
// Change detection - cache previous values to avoid redundant publishes
// All values are initialized to sentinel values that are outside the valid sensor ranges
@@ -176,8 +185,8 @@ class LD2450Component : public Component, public uart::UARTDevice {
int16_t speed = std::numeric_limits<int16_t>::min(); // -32768, outside practical sensor range
uint16_t resolution = std::numeric_limits<uint16_t>::max(); // 65535, unlikely resolution value
uint16_t distance = std::numeric_limits<uint16_t>::max(); // 65535, outside range of 0 to ~8990
Direction direction = DIRECTION_UNDEFINED; // Undefined, will differ from any real direction
float angle = NAN; // NAN, safe sentinel for floats
std::string direction = ""; // Empty string, will differ from any real direction
} cached_target_data_[MAX_TARGETS];
struct CachedZoneData {

View File

@@ -0,0 +1,35 @@
#include "esphome/core/helpers.h"
#ifdef USE_LIBRETINY
#include "esphome/core/hal.h"
#include <WiFi.h> // for macAddress()
namespace esphome {
uint32_t random_uint32() { return rand(); }
bool random_bytes(uint8_t *data, size_t len) {
lt_rand_bytes(data, len);
return true;
}
Mutex::Mutex() { handle_ = xSemaphoreCreateMutex(); }
Mutex::~Mutex() {}
void Mutex::lock() { xSemaphoreTake(this->handle_, portMAX_DELAY); }
bool Mutex::try_lock() { return xSemaphoreTake(this->handle_, 0) == pdTRUE; }
void Mutex::unlock() { xSemaphoreGive(this->handle_); }
// only affects the executing core
// so should not be used as a mutex lock, only to get accurate timing
IRAM_ATTR InterruptLock::InterruptLock() { portDISABLE_INTERRUPTS(); }
IRAM_ATTR InterruptLock::~InterruptLock() { portENABLE_INTERRUPTS(); }
void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parameter)
WiFi.macAddress(mac);
}
} // namespace esphome
#endif // USE_LIBRETINY

View File

@@ -97,12 +97,12 @@ class AddressableLight : public LightOutput, public Component {
}
virtual ESPColorView get_view_internal(int32_t index) const = 0;
bool effect_active_{false};
ESPColorCorrection correction_{};
LightState *state_parent_{nullptr};
#ifdef USE_POWER_SUPPLY
power_supply::PowerSupplyRequester power_;
#endif
LightState *state_parent_{nullptr};
bool effect_active_{false};
};
class AddressableLightTransformer : public LightTransitionTransformer {
@@ -114,9 +114,9 @@ class AddressableLightTransformer : public LightTransitionTransformer {
protected:
AddressableLight &light_;
Color target_color_{};
float last_transition_progress_{0.0f};
float accumulated_alpha_{0.0f};
Color target_color_{};
};
} // namespace light

View File

@@ -69,8 +69,8 @@ class ESPColorCorrection {
protected:
uint8_t gamma_table_[256];
uint8_t gamma_reverse_table_[256];
uint8_t local_brightness_{255};
Color max_brightness_;
uint8_t local_brightness_{255};
};
} // namespace light

View File

@@ -2,12 +2,28 @@
#include "light_call.h"
#include "light_state.h"
#include "esphome/core/log.h"
#include "esphome/core/optional.h"
namespace esphome {
namespace light {
static const char *const TAG = "light";
// Macro to reduce repetitive setter code
#define IMPLEMENT_LIGHT_CALL_SETTER(name, type, flag) \
LightCall &LightCall::set_##name(optional<type>(name)) { \
if ((name).has_value()) { \
this->name##_ = (name).value(); \
} \
this->set_flag_(flag, (name).has_value()); \
return *this; \
} \
LightCall &LightCall::set_##name(type name) { \
this->name##_ = name; \
this->set_flag_(flag, true); \
return *this; \
}
static const LogString *color_mode_to_human(ColorMode color_mode) {
if (color_mode == ColorMode::UNKNOWN)
return LOG_STR("Unknown");
@@ -32,41 +48,43 @@ void LightCall::perform() {
const char *name = this->parent_->get_name().c_str();
LightColorValues v = this->validate_();
if (this->publish_) {
if (this->get_publish_()) {
ESP_LOGD(TAG, "'%s' Setting:", name);
// Only print color mode when it's being changed
ColorMode current_color_mode = this->parent_->remote_values.get_color_mode();
if (this->color_mode_.value_or(current_color_mode) != current_color_mode) {
ColorMode target_color_mode = this->has_color_mode() ? this->color_mode_ : current_color_mode;
if (target_color_mode != current_color_mode) {
ESP_LOGD(TAG, " Color mode: %s", LOG_STR_ARG(color_mode_to_human(v.get_color_mode())));
}
// Only print state when it's being changed
bool current_state = this->parent_->remote_values.is_on();
if (this->state_.value_or(current_state) != current_state) {
bool target_state = this->has_state() ? this->state_ : current_state;
if (target_state != current_state) {
ESP_LOGD(TAG, " State: %s", ONOFF(v.is_on()));
}
if (this->brightness_.has_value()) {
if (this->has_brightness()) {
ESP_LOGD(TAG, " Brightness: %.0f%%", v.get_brightness() * 100.0f);
}
if (this->color_brightness_.has_value()) {
if (this->has_color_brightness()) {
ESP_LOGD(TAG, " Color brightness: %.0f%%", v.get_color_brightness() * 100.0f);
}
if (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()) {
if (this->has_red() || this->has_green() || this->has_blue()) {
ESP_LOGD(TAG, " Red: %.0f%%, Green: %.0f%%, Blue: %.0f%%", v.get_red() * 100.0f, v.get_green() * 100.0f,
v.get_blue() * 100.0f);
}
if (this->white_.has_value()) {
if (this->has_white()) {
ESP_LOGD(TAG, " White: %.0f%%", v.get_white() * 100.0f);
}
if (this->color_temperature_.has_value()) {
if (this->has_color_temperature()) {
ESP_LOGD(TAG, " Color temperature: %.1f mireds", v.get_color_temperature());
}
if (this->cold_white_.has_value() || this->warm_white_.has_value()) {
if (this->has_cold_white() || this->has_warm_white()) {
ESP_LOGD(TAG, " Cold white: %.0f%%, warm white: %.0f%%", v.get_cold_white() * 100.0f,
v.get_warm_white() * 100.0f);
}
@@ -74,58 +92,57 @@ void LightCall::perform() {
if (this->has_flash_()) {
// FLASH
if (this->publish_) {
ESP_LOGD(TAG, " Flash length: %.1fs", *this->flash_length_ / 1e3f);
if (this->get_publish_()) {
ESP_LOGD(TAG, " Flash length: %.1fs", this->flash_length_ / 1e3f);
}
this->parent_->start_flash_(v, *this->flash_length_, this->publish_);
this->parent_->start_flash_(v, this->flash_length_, this->get_publish_());
} else if (this->has_transition_()) {
// TRANSITION
if (this->publish_) {
ESP_LOGD(TAG, " Transition length: %.1fs", *this->transition_length_ / 1e3f);
if (this->get_publish_()) {
ESP_LOGD(TAG, " Transition length: %.1fs", this->transition_length_ / 1e3f);
}
// Special case: Transition and effect can be set when turning off
if (this->has_effect_()) {
if (this->publish_) {
if (this->get_publish_()) {
ESP_LOGD(TAG, " Effect: 'None'");
}
this->parent_->stop_effect_();
}
this->parent_->start_transition_(v, *this->transition_length_, this->publish_);
this->parent_->start_transition_(v, this->transition_length_, this->get_publish_());
} else if (this->has_effect_()) {
// EFFECT
auto effect = this->effect_;
const char *effect_s;
if (effect == 0u) {
if (this->effect_ == 0u) {
effect_s = "None";
} else {
effect_s = this->parent_->effects_[*this->effect_ - 1]->get_name().c_str();
effect_s = this->parent_->effects_[this->effect_ - 1]->get_name().c_str();
}
if (this->publish_) {
if (this->get_publish_()) {
ESP_LOGD(TAG, " Effect: '%s'", effect_s);
}
this->parent_->start_effect_(*this->effect_);
this->parent_->start_effect_(this->effect_);
// Also set light color values when starting an effect
// For example to turn off the light
this->parent_->set_immediately_(v, true);
} else {
// INSTANT CHANGE
this->parent_->set_immediately_(v, this->publish_);
this->parent_->set_immediately_(v, this->get_publish_());
}
if (!this->has_transition_()) {
this->parent_->target_state_reached_callback_.call();
}
if (this->publish_) {
if (this->get_publish_()) {
this->parent_->publish_state();
}
if (this->save_) {
if (this->get_save_()) {
this->parent_->save_remote_values_();
}
}
@@ -135,82 +152,80 @@ LightColorValues LightCall::validate_() {
auto traits = this->parent_->get_traits();
// Color mode check
if (this->color_mode_.has_value() && !traits.supports_color_mode(this->color_mode_.value())) {
ESP_LOGW(TAG, "'%s' does not support color mode %s", name,
LOG_STR_ARG(color_mode_to_human(this->color_mode_.value())));
this->color_mode_.reset();
if (this->has_color_mode() && !traits.supports_color_mode(this->color_mode_)) {
ESP_LOGW(TAG, "'%s' does not support color mode %s", name, LOG_STR_ARG(color_mode_to_human(this->color_mode_)));
this->set_flag_(FLAG_HAS_COLOR_MODE, false);
}
// Ensure there is always a color mode set
if (!this->color_mode_.has_value()) {
if (!this->has_color_mode()) {
this->color_mode_ = this->compute_color_mode_();
this->set_flag_(FLAG_HAS_COLOR_MODE, true);
}
auto color_mode = *this->color_mode_;
auto color_mode = this->color_mode_;
// Transform calls that use non-native parameters for the current mode.
this->transform_parameters_();
// Brightness exists check
if (this->brightness_.has_value() && *this->brightness_ > 0.0f && !(color_mode & ColorCapability::BRIGHTNESS)) {
if (this->has_brightness() && this->brightness_ > 0.0f && !(color_mode & ColorCapability::BRIGHTNESS)) {
ESP_LOGW(TAG, "'%s': setting brightness not supported", name);
this->brightness_.reset();
this->set_flag_(FLAG_HAS_BRIGHTNESS, false);
}
// Transition length possible check
if (this->transition_length_.has_value() && *this->transition_length_ != 0 &&
!(color_mode & ColorCapability::BRIGHTNESS)) {
if (this->has_transition_() && this->transition_length_ != 0 && !(color_mode & ColorCapability::BRIGHTNESS)) {
ESP_LOGW(TAG, "'%s': transitions not supported", name);
this->transition_length_.reset();
this->set_flag_(FLAG_HAS_TRANSITION, false);
}
// Color brightness exists check
if (this->color_brightness_.has_value() && *this->color_brightness_ > 0.0f && !(color_mode & ColorCapability::RGB)) {
if (this->has_color_brightness() && this->color_brightness_ > 0.0f && !(color_mode & ColorCapability::RGB)) {
ESP_LOGW(TAG, "'%s': color mode does not support setting RGB brightness", name);
this->color_brightness_.reset();
this->set_flag_(FLAG_HAS_COLOR_BRIGHTNESS, false);
}
// RGB exists check
if ((this->red_.has_value() && *this->red_ > 0.0f) || (this->green_.has_value() && *this->green_ > 0.0f) ||
(this->blue_.has_value() && *this->blue_ > 0.0f)) {
if ((this->has_red() && this->red_ > 0.0f) || (this->has_green() && this->green_ > 0.0f) ||
(this->has_blue() && this->blue_ > 0.0f)) {
if (!(color_mode & ColorCapability::RGB)) {
ESP_LOGW(TAG, "'%s': color mode does not support setting RGB color", name);
this->red_.reset();
this->green_.reset();
this->blue_.reset();
this->set_flag_(FLAG_HAS_RED, false);
this->set_flag_(FLAG_HAS_GREEN, false);
this->set_flag_(FLAG_HAS_BLUE, false);
}
}
// White value exists check
if (this->white_.has_value() && *this->white_ > 0.0f &&
if (this->has_white() && this->white_ > 0.0f &&
!(color_mode & ColorCapability::WHITE || color_mode & ColorCapability::COLD_WARM_WHITE)) {
ESP_LOGW(TAG, "'%s': color mode does not support setting white value", name);
this->white_.reset();
this->set_flag_(FLAG_HAS_WHITE, false);
}
// Color temperature exists check
if (this->color_temperature_.has_value() &&
if (this->has_color_temperature() &&
!(color_mode & ColorCapability::COLOR_TEMPERATURE || color_mode & ColorCapability::COLD_WARM_WHITE)) {
ESP_LOGW(TAG, "'%s': color mode does not support setting color temperature", name);
this->color_temperature_.reset();
this->set_flag_(FLAG_HAS_COLOR_TEMPERATURE, false);
}
// Cold/warm white value exists check
if ((this->cold_white_.has_value() && *this->cold_white_ > 0.0f) ||
(this->warm_white_.has_value() && *this->warm_white_ > 0.0f)) {
if ((this->has_cold_white() && this->cold_white_ > 0.0f) || (this->has_warm_white() && this->warm_white_ > 0.0f)) {
if (!(color_mode & ColorCapability::COLD_WARM_WHITE)) {
ESP_LOGW(TAG, "'%s': color mode does not support setting cold/warm white value", name);
this->cold_white_.reset();
this->warm_white_.reset();
this->set_flag_(FLAG_HAS_COLD_WHITE, false);
this->set_flag_(FLAG_HAS_WARM_WHITE, false);
}
}
#define VALIDATE_RANGE_(name_, upper_name, min, max) \
if (name_##_.has_value()) { \
auto val = *name_##_; \
if (this->has_##name_()) { \
auto val = this->name_##_; \
if (val < (min) || val > (max)) { \
ESP_LOGW(TAG, "'%s': %s value %.2f is out of range [%.1f - %.1f]", name, LOG_STR_LITERAL(upper_name), val, \
(min), (max)); \
name_##_ = clamp(val, (min), (max)); \
this->name_##_ = clamp(val, (min), (max)); \
} \
}
#define VALIDATE_RANGE(name, upper_name) VALIDATE_RANGE_(name, upper_name, 0.0f, 1.0f)
@@ -227,110 +242,116 @@ LightColorValues LightCall::validate_() {
VALIDATE_RANGE_(color_temperature, "Color temperature", traits.get_min_mireds(), traits.get_max_mireds())
// Flag whether an explicit turn off was requested, in which case we'll also stop the effect.
bool explicit_turn_off_request = this->state_.has_value() && !*this->state_;
bool explicit_turn_off_request = this->has_state() && !this->state_;
// Turn off when brightness is set to zero, and reset brightness (so that it has nonzero brightness when turned on).
if (this->brightness_.has_value() && *this->brightness_ == 0.0f) {
this->state_ = optional<float>(false);
this->brightness_ = optional<float>(1.0f);
if (this->has_brightness() && this->brightness_ == 0.0f) {
this->state_ = false;
this->set_flag_(FLAG_HAS_STATE, true);
this->brightness_ = 1.0f;
}
// Set color brightness to 100% if currently zero and a color is set.
if (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()) {
if (!this->color_brightness_.has_value() && this->parent_->remote_values.get_color_brightness() == 0.0f)
this->color_brightness_ = optional<float>(1.0f);
if (this->has_red() || this->has_green() || this->has_blue()) {
if (!this->has_color_brightness() && this->parent_->remote_values.get_color_brightness() == 0.0f) {
this->color_brightness_ = 1.0f;
this->set_flag_(FLAG_HAS_COLOR_BRIGHTNESS, true);
}
}
// Create color values for the light with this call applied.
auto v = this->parent_->remote_values;
if (this->color_mode_.has_value())
v.set_color_mode(*this->color_mode_);
if (this->state_.has_value())
v.set_state(*this->state_);
if (this->brightness_.has_value())
v.set_brightness(*this->brightness_);
if (this->color_brightness_.has_value())
v.set_color_brightness(*this->color_brightness_);
if (this->red_.has_value())
v.set_red(*this->red_);
if (this->green_.has_value())
v.set_green(*this->green_);
if (this->blue_.has_value())
v.set_blue(*this->blue_);
if (this->white_.has_value())
v.set_white(*this->white_);
if (this->color_temperature_.has_value())
v.set_color_temperature(*this->color_temperature_);
if (this->cold_white_.has_value())
v.set_cold_white(*this->cold_white_);
if (this->warm_white_.has_value())
v.set_warm_white(*this->warm_white_);
if (this->has_color_mode())
v.set_color_mode(this->color_mode_);
if (this->has_state())
v.set_state(this->state_);
if (this->has_brightness())
v.set_brightness(this->brightness_);
if (this->has_color_brightness())
v.set_color_brightness(this->color_brightness_);
if (this->has_red())
v.set_red(this->red_);
if (this->has_green())
v.set_green(this->green_);
if (this->has_blue())
v.set_blue(this->blue_);
if (this->has_white())
v.set_white(this->white_);
if (this->has_color_temperature())
v.set_color_temperature(this->color_temperature_);
if (this->has_cold_white())
v.set_cold_white(this->cold_white_);
if (this->has_warm_white())
v.set_warm_white(this->warm_white_);
v.normalize_color();
// Flash length check
if (this->has_flash_() && *this->flash_length_ == 0) {
if (this->has_flash_() && this->flash_length_ == 0) {
ESP_LOGW(TAG, "'%s': flash length must be greater than zero", name);
this->flash_length_.reset();
this->set_flag_(FLAG_HAS_FLASH, false);
}
// validate transition length/flash length/effect not used at the same time
bool supports_transition = color_mode & ColorCapability::BRIGHTNESS;
// If effect is already active, remove effect start
if (this->has_effect_() && *this->effect_ == this->parent_->active_effect_index_) {
this->effect_.reset();
if (this->has_effect_() && this->effect_ == this->parent_->active_effect_index_) {
this->set_flag_(FLAG_HAS_EFFECT, false);
}
// validate effect index
if (this->has_effect_() && *this->effect_ > this->parent_->effects_.size()) {
ESP_LOGW(TAG, "'%s': invalid effect index %" PRIu32, name, *this->effect_);
this->effect_.reset();
if (this->has_effect_() && this->effect_ > this->parent_->effects_.size()) {
ESP_LOGW(TAG, "'%s': invalid effect index %" PRIu32, name, this->effect_);
this->set_flag_(FLAG_HAS_EFFECT, false);
}
if (this->has_effect_() && (this->has_transition_() || this->has_flash_())) {
ESP_LOGW(TAG, "'%s': effect cannot be used with transition/flash", name);
this->transition_length_.reset();
this->flash_length_.reset();
this->set_flag_(FLAG_HAS_TRANSITION, false);
this->set_flag_(FLAG_HAS_FLASH, false);
}
if (this->has_flash_() && this->has_transition_()) {
ESP_LOGW(TAG, "'%s': flash cannot be used with transition", name);
this->transition_length_.reset();
this->set_flag_(FLAG_HAS_TRANSITION, false);
}
if (!this->has_transition_() && !this->has_flash_() && (!this->has_effect_() || *this->effect_ == 0) &&
if (!this->has_transition_() && !this->has_flash_() && (!this->has_effect_() || this->effect_ == 0) &&
supports_transition) {
// nothing specified and light supports transitions, set default transition length
this->transition_length_ = this->parent_->default_transition_length_;
this->set_flag_(FLAG_HAS_TRANSITION, true);
}
if (this->transition_length_.value_or(0) == 0) {
if (this->has_transition_() && this->transition_length_ == 0) {
// 0 transition is interpreted as no transition (instant change)
this->transition_length_.reset();
this->set_flag_(FLAG_HAS_TRANSITION, false);
}
if (this->has_transition_() && !supports_transition) {
ESP_LOGW(TAG, "'%s': transitions not supported", name);
this->transition_length_.reset();
this->set_flag_(FLAG_HAS_TRANSITION, false);
}
// If not a flash and turning the light off, then disable the light
// Do not use light color values directly, so that effects can set 0% brightness
// Reason: When user turns off the light in frontend, the effect should also stop
if (!this->has_flash_() && !this->state_.value_or(v.is_on())) {
bool target_state = this->has_state() ? this->state_ : v.is_on();
if (!this->has_flash_() && !target_state) {
if (this->has_effect_()) {
ESP_LOGW(TAG, "'%s': cannot start effect when turning off", name);
this->effect_.reset();
this->set_flag_(FLAG_HAS_EFFECT, false);
} else if (this->parent_->active_effect_index_ != 0 && explicit_turn_off_request) {
// Auto turn off effect
this->effect_ = 0;
this->set_flag_(FLAG_HAS_EFFECT, true);
}
}
// Disable saving for flashes
if (this->has_flash_())
this->save_ = false;
this->set_flag_(FLAG_SAVE, false);
return v;
}
@@ -343,24 +364,27 @@ void LightCall::transform_parameters_() {
// - RGBWW lights with color_interlock=true, which also sets "brightness" and
// "color_temperature" (without color_interlock, CW/WW are set directly)
// - Legacy Home Assistant (pre-colormode), which sets "white" and "color_temperature"
if (((this->white_.has_value() && *this->white_ > 0.0f) || this->color_temperature_.has_value()) && //
(*this->color_mode_ & ColorCapability::COLD_WARM_WHITE) && //
!(*this->color_mode_ & ColorCapability::WHITE) && //
!(*this->color_mode_ & ColorCapability::COLOR_TEMPERATURE) && //
if (((this->has_white() && this->white_ > 0.0f) || this->has_color_temperature()) && //
(this->color_mode_ & ColorCapability::COLD_WARM_WHITE) && //
!(this->color_mode_ & ColorCapability::WHITE) && //
!(this->color_mode_ & ColorCapability::COLOR_TEMPERATURE) && //
traits.get_min_mireds() > 0.0f && traits.get_max_mireds() > 0.0f) {
ESP_LOGD(TAG, "'%s': setting cold/warm white channels using white/color temperature values",
this->parent_->get_name().c_str());
if (this->color_temperature_.has_value()) {
const float color_temp = clamp(*this->color_temperature_, traits.get_min_mireds(), traits.get_max_mireds());
if (this->has_color_temperature()) {
const float color_temp = clamp(this->color_temperature_, traits.get_min_mireds(), traits.get_max_mireds());
const float ww_fraction =
(color_temp - traits.get_min_mireds()) / (traits.get_max_mireds() - traits.get_min_mireds());
const float cw_fraction = 1.0f - ww_fraction;
const float max_cw_ww = std::max(ww_fraction, cw_fraction);
this->cold_white_ = gamma_uncorrect(cw_fraction / max_cw_ww, this->parent_->get_gamma_correct());
this->warm_white_ = gamma_uncorrect(ww_fraction / max_cw_ww, this->parent_->get_gamma_correct());
this->set_flag_(FLAG_HAS_COLD_WHITE, true);
this->set_flag_(FLAG_HAS_WARM_WHITE, true);
}
if (this->white_.has_value()) {
this->brightness_ = *this->white_;
if (this->has_white()) {
this->brightness_ = this->white_;
this->set_flag_(FLAG_HAS_BRIGHTNESS, true);
}
}
}
@@ -378,7 +402,7 @@ ColorMode LightCall::compute_color_mode_() {
// Don't change if the light is being turned off.
ColorMode current_mode = this->parent_->remote_values.get_color_mode();
if (this->state_.has_value() && !*this->state_)
if (this->has_state() && !this->state_)
return current_mode;
// If no color mode is specified, we try to guess the color mode. This is needed for backward compatibility to
@@ -411,12 +435,12 @@ ColorMode LightCall::compute_color_mode_() {
return color_mode;
}
std::set<ColorMode> LightCall::get_suitable_color_modes_() {
bool has_white = this->white_.has_value() && *this->white_ > 0.0f;
bool has_ct = this->color_temperature_.has_value();
bool has_cwww = (this->cold_white_.has_value() && *this->cold_white_ > 0.0f) ||
(this->warm_white_.has_value() && *this->warm_white_ > 0.0f);
bool has_rgb = (this->color_brightness_.has_value() && *this->color_brightness_ > 0.0f) ||
(this->red_.has_value() || this->green_.has_value() || this->blue_.has_value());
bool has_white = this->has_white() && this->white_ > 0.0f;
bool has_ct = this->has_color_temperature();
bool has_cwww =
(this->has_cold_white() && this->cold_white_ > 0.0f) || (this->has_warm_white() && this->warm_white_ > 0.0f);
bool has_rgb = (this->has_color_brightness() && this->color_brightness_ > 0.0f) ||
(this->has_red() || this->has_green() || this->has_blue());
#define KEY(white, ct, cwww, rgb) ((white) << 0 | (ct) << 1 | (cwww) << 2 | (rgb) << 3)
#define ENTRY(white, ct, cwww, rgb, ...) \
@@ -491,7 +515,7 @@ LightCall &LightCall::from_light_color_values(const LightColorValues &values) {
return *this;
}
ColorMode LightCall::get_active_color_mode_() {
return this->color_mode_.value_or(this->parent_->remote_values.get_color_mode());
return this->has_color_mode() ? this->color_mode_ : this->parent_->remote_values.get_color_mode();
}
LightCall &LightCall::set_transition_length_if_supported(uint32_t transition_length) {
if (this->get_active_color_mode_() & ColorCapability::BRIGHTNESS)
@@ -505,7 +529,7 @@ LightCall &LightCall::set_brightness_if_supported(float brightness) {
}
LightCall &LightCall::set_color_mode_if_supported(ColorMode color_mode) {
if (this->parent_->get_traits().supports_color_mode(color_mode))
this->color_mode_ = color_mode;
this->set_color_mode(color_mode);
return *this;
}
LightCall &LightCall::set_color_brightness_if_supported(float brightness) {
@@ -549,110 +573,19 @@ LightCall &LightCall::set_warm_white_if_supported(float warm_white) {
this->set_warm_white(warm_white);
return *this;
}
LightCall &LightCall::set_state(optional<bool> state) {
this->state_ = state;
return *this;
}
LightCall &LightCall::set_state(bool state) {
this->state_ = state;
return *this;
}
LightCall &LightCall::set_transition_length(optional<uint32_t> transition_length) {
this->transition_length_ = transition_length;
return *this;
}
LightCall &LightCall::set_transition_length(uint32_t transition_length) {
this->transition_length_ = transition_length;
return *this;
}
LightCall &LightCall::set_flash_length(optional<uint32_t> flash_length) {
this->flash_length_ = flash_length;
return *this;
}
LightCall &LightCall::set_flash_length(uint32_t flash_length) {
this->flash_length_ = flash_length;
return *this;
}
LightCall &LightCall::set_brightness(optional<float> brightness) {
this->brightness_ = brightness;
return *this;
}
LightCall &LightCall::set_brightness(float brightness) {
this->brightness_ = brightness;
return *this;
}
LightCall &LightCall::set_color_mode(optional<ColorMode> color_mode) {
this->color_mode_ = color_mode;
return *this;
}
LightCall &LightCall::set_color_mode(ColorMode color_mode) {
this->color_mode_ = color_mode;
return *this;
}
LightCall &LightCall::set_color_brightness(optional<float> brightness) {
this->color_brightness_ = brightness;
return *this;
}
LightCall &LightCall::set_color_brightness(float brightness) {
this->color_brightness_ = brightness;
return *this;
}
LightCall &LightCall::set_red(optional<float> red) {
this->red_ = red;
return *this;
}
LightCall &LightCall::set_red(float red) {
this->red_ = red;
return *this;
}
LightCall &LightCall::set_green(optional<float> green) {
this->green_ = green;
return *this;
}
LightCall &LightCall::set_green(float green) {
this->green_ = green;
return *this;
}
LightCall &LightCall::set_blue(optional<float> blue) {
this->blue_ = blue;
return *this;
}
LightCall &LightCall::set_blue(float blue) {
this->blue_ = blue;
return *this;
}
LightCall &LightCall::set_white(optional<float> white) {
this->white_ = white;
return *this;
}
LightCall &LightCall::set_white(float white) {
this->white_ = white;
return *this;
}
LightCall &LightCall::set_color_temperature(optional<float> color_temperature) {
this->color_temperature_ = color_temperature;
return *this;
}
LightCall &LightCall::set_color_temperature(float color_temperature) {
this->color_temperature_ = color_temperature;
return *this;
}
LightCall &LightCall::set_cold_white(optional<float> cold_white) {
this->cold_white_ = cold_white;
return *this;
}
LightCall &LightCall::set_cold_white(float cold_white) {
this->cold_white_ = cold_white;
return *this;
}
LightCall &LightCall::set_warm_white(optional<float> warm_white) {
this->warm_white_ = warm_white;
return *this;
}
LightCall &LightCall::set_warm_white(float warm_white) {
this->warm_white_ = warm_white;
return *this;
}
IMPLEMENT_LIGHT_CALL_SETTER(state, bool, FLAG_HAS_STATE)
IMPLEMENT_LIGHT_CALL_SETTER(transition_length, uint32_t, FLAG_HAS_TRANSITION)
IMPLEMENT_LIGHT_CALL_SETTER(flash_length, uint32_t, FLAG_HAS_FLASH)
IMPLEMENT_LIGHT_CALL_SETTER(brightness, float, FLAG_HAS_BRIGHTNESS)
IMPLEMENT_LIGHT_CALL_SETTER(color_mode, ColorMode, FLAG_HAS_COLOR_MODE)
IMPLEMENT_LIGHT_CALL_SETTER(color_brightness, float, FLAG_HAS_COLOR_BRIGHTNESS)
IMPLEMENT_LIGHT_CALL_SETTER(red, float, FLAG_HAS_RED)
IMPLEMENT_LIGHT_CALL_SETTER(green, float, FLAG_HAS_GREEN)
IMPLEMENT_LIGHT_CALL_SETTER(blue, float, FLAG_HAS_BLUE)
IMPLEMENT_LIGHT_CALL_SETTER(white, float, FLAG_HAS_WHITE)
IMPLEMENT_LIGHT_CALL_SETTER(color_temperature, float, FLAG_HAS_COLOR_TEMPERATURE)
IMPLEMENT_LIGHT_CALL_SETTER(cold_white, float, FLAG_HAS_COLD_WHITE)
IMPLEMENT_LIGHT_CALL_SETTER(warm_white, float, FLAG_HAS_WARM_WHITE)
LightCall &LightCall::set_effect(optional<std::string> effect) {
if (effect.has_value())
this->set_effect(*effect);
@@ -660,18 +593,22 @@ LightCall &LightCall::set_effect(optional<std::string> effect) {
}
LightCall &LightCall::set_effect(uint32_t effect_number) {
this->effect_ = effect_number;
this->set_flag_(FLAG_HAS_EFFECT, true);
return *this;
}
LightCall &LightCall::set_effect(optional<uint32_t> effect_number) {
this->effect_ = effect_number;
if (effect_number.has_value()) {
this->effect_ = effect_number.value();
}
this->set_flag_(FLAG_HAS_EFFECT, effect_number.has_value());
return *this;
}
LightCall &LightCall::set_publish(bool publish) {
this->publish_ = publish;
this->set_flag_(FLAG_PUBLISH, publish);
return *this;
}
LightCall &LightCall::set_save(bool save) {
this->save_ = save;
this->set_flag_(FLAG_SAVE, save);
return *this;
}
LightCall &LightCall::set_rgb(float red, float green, float blue) {

View File

@@ -1,6 +1,5 @@
#pragma once
#include "esphome/core/optional.h"
#include "light_color_values.h"
#include <set>
@@ -10,6 +9,11 @@ namespace light {
class LightState;
/** This class represents a requested change in a light state.
*
* Light state changes are tracked using a bitfield flags_ to minimize memory usage.
* Each possible light property has a flag indicating whether it has been set.
* This design keeps LightCall at ~56 bytes to minimize heap fragmentation on
* ESP8266 and other memory-constrained devices.
*/
class LightCall {
public:
@@ -131,6 +135,19 @@ class LightCall {
/// Set whether this light call should trigger a save state to recover them at startup..
LightCall &set_save(bool save);
// Getter methods to check if values are set
bool has_state() const { return (flags_ & FLAG_HAS_STATE) != 0; }
bool has_brightness() const { return (flags_ & FLAG_HAS_BRIGHTNESS) != 0; }
bool has_color_brightness() const { return (flags_ & FLAG_HAS_COLOR_BRIGHTNESS) != 0; }
bool has_red() const { return (flags_ & FLAG_HAS_RED) != 0; }
bool has_green() const { return (flags_ & FLAG_HAS_GREEN) != 0; }
bool has_blue() const { return (flags_ & FLAG_HAS_BLUE) != 0; }
bool has_white() const { return (flags_ & FLAG_HAS_WHITE) != 0; }
bool has_color_temperature() const { return (flags_ & FLAG_HAS_COLOR_TEMPERATURE) != 0; }
bool has_cold_white() const { return (flags_ & FLAG_HAS_COLD_WHITE) != 0; }
bool has_warm_white() const { return (flags_ & FLAG_HAS_WARM_WHITE) != 0; }
bool has_color_mode() const { return (flags_ & FLAG_HAS_COLOR_MODE) != 0; }
/** Set the RGB color of the light by RGB values.
*
* Please note that this only changes the color of the light, not the brightness.
@@ -170,27 +187,62 @@ class LightCall {
/// Some color modes also can be set using non-native parameters, transform those calls.
void transform_parameters_();
bool has_transition_() { return this->transition_length_.has_value(); }
bool has_flash_() { return this->flash_length_.has_value(); }
bool has_effect_() { return this->effect_.has_value(); }
// Bitfield flags - each flag indicates whether a corresponding value has been set.
enum FieldFlags : uint16_t {
FLAG_HAS_STATE = 1 << 0,
FLAG_HAS_TRANSITION = 1 << 1,
FLAG_HAS_FLASH = 1 << 2,
FLAG_HAS_EFFECT = 1 << 3,
FLAG_HAS_BRIGHTNESS = 1 << 4,
FLAG_HAS_COLOR_BRIGHTNESS = 1 << 5,
FLAG_HAS_RED = 1 << 6,
FLAG_HAS_GREEN = 1 << 7,
FLAG_HAS_BLUE = 1 << 8,
FLAG_HAS_WHITE = 1 << 9,
FLAG_HAS_COLOR_TEMPERATURE = 1 << 10,
FLAG_HAS_COLD_WHITE = 1 << 11,
FLAG_HAS_WARM_WHITE = 1 << 12,
FLAG_HAS_COLOR_MODE = 1 << 13,
FLAG_PUBLISH = 1 << 14,
FLAG_SAVE = 1 << 15,
};
bool has_transition_() { return (this->flags_ & FLAG_HAS_TRANSITION) != 0; }
bool has_flash_() { return (this->flags_ & FLAG_HAS_FLASH) != 0; }
bool has_effect_() { return (this->flags_ & FLAG_HAS_EFFECT) != 0; }
bool get_publish_() { return (this->flags_ & FLAG_PUBLISH) != 0; }
bool get_save_() { return (this->flags_ & FLAG_SAVE) != 0; }
// Helper to set flag
void set_flag_(FieldFlags flag, bool value) {
if (value) {
this->flags_ |= flag;
} else {
this->flags_ &= ~flag;
}
}
LightState *parent_;
optional<bool> state_;
optional<uint32_t> transition_length_;
optional<uint32_t> flash_length_;
optional<ColorMode> color_mode_;
optional<float> brightness_;
optional<float> color_brightness_;
optional<float> red_;
optional<float> green_;
optional<float> blue_;
optional<float> white_;
optional<float> color_temperature_;
optional<float> cold_white_;
optional<float> warm_white_;
optional<uint32_t> effect_;
bool publish_{true};
bool save_{true};
// Light state values - use flags_ to check if a value has been set.
// Group 4-byte aligned members first
uint32_t transition_length_;
uint32_t flash_length_;
uint32_t effect_;
float brightness_;
float color_brightness_;
float red_;
float green_;
float blue_;
float white_;
float color_temperature_;
float cold_white_;
float warm_white_;
// Smaller members at the end for better packing
uint16_t flags_{FLAG_PUBLISH | FLAG_SAVE}; // Tracks which values are set
ColorMode color_mode_;
bool state_;
};
} // namespace light

View File

@@ -46,8 +46,7 @@ class LightColorValues {
public:
/// Construct the LightColorValues with all attributes enabled, but state set to off.
LightColorValues()
: color_mode_(ColorMode::UNKNOWN),
state_(0.0f),
: state_(0.0f),
brightness_(1.0f),
color_brightness_(1.0f),
red_(1.0f),
@@ -56,7 +55,8 @@ class LightColorValues {
white_(1.0f),
color_temperature_{0.0f},
cold_white_{1.0f},
warm_white_{1.0f} {}
warm_white_{1.0f},
color_mode_(ColorMode::UNKNOWN) {}
LightColorValues(ColorMode color_mode, float state, float brightness, float color_brightness, float red, float green,
float blue, float white, float color_temperature, float cold_white, float warm_white) {
@@ -292,7 +292,6 @@ class LightColorValues {
void set_warm_white(float warm_white) { this->warm_white_ = clamp(warm_white, 0.0f, 1.0f); }
protected:
ColorMode color_mode_;
float state_; ///< ON / OFF, float for transition
float brightness_;
float color_brightness_;
@@ -303,6 +302,7 @@ class LightColorValues {
float color_temperature_; ///< Color Temperature in Mired
float cold_white_;
float warm_white_;
ColorMode color_mode_;
};
} // namespace light

View File

@@ -31,9 +31,7 @@ enum LightRestoreMode : uint8_t {
struct LightStateRTCState {
LightStateRTCState(ColorMode color_mode, bool state, float brightness, float color_brightness, float red, float green,
float blue, float white, float color_temp, float cold_white, float warm_white)
: color_mode(color_mode),
state(state),
brightness(brightness),
: brightness(brightness),
color_brightness(color_brightness),
red(red),
green(green),
@@ -41,10 +39,12 @@ struct LightStateRTCState {
white(white),
color_temp(color_temp),
cold_white(cold_white),
warm_white(warm_white) {}
warm_white(warm_white),
effect(0),
color_mode(color_mode),
state(state) {}
LightStateRTCState() = default;
ColorMode color_mode{ColorMode::UNKNOWN};
bool state{false};
// Group 4-byte aligned members first
float brightness{1.0f};
float color_brightness{1.0f};
float red{1.0f};
@@ -55,6 +55,9 @@ struct LightStateRTCState {
float cold_white{1.0f};
float warm_white{1.0f};
uint32_t effect{0};
// Group smaller members at the end
ColorMode color_mode{ColorMode::UNKNOWN};
bool state{false};
};
/** This class represents the communication layer between the front-end MQTT layer and the
@@ -216,6 +219,8 @@ class LightState : public EntityBase, public Component {
std::unique_ptr<LightTransformer> transformer_{nullptr};
/// List of effects for this light.
std::vector<LightEffect *> effects_;
/// Object used to store the persisted values of the light.
ESPPreferenceObject rtc_;
/// Value for storing the index of the currently active effect. 0 if no effect is active
uint32_t active_effect_index_{};
/// Default transition length for all transitions in ms.
@@ -224,15 +229,11 @@ class LightState : public EntityBase, public Component {
uint32_t flash_transition_length_{};
/// Gamma correction factor for the light.
float gamma_correct_{};
/// Whether the light value should be written in the next cycle.
bool next_write_{true};
// for effects, true if a transformer (transition) is active.
bool is_transformer_active_ = false;
/// Object used to store the persisted values of the light.
ESPPreferenceObject rtc_;
/** Callback to call when new values for the frontend are available.
*
* "Remote values" are light color values that are reported to the frontend and have a lower

View File

@@ -59,9 +59,9 @@ class LightTransitionTransformer : public LightTransformer {
// transition from 0 to 1 on x = [0, 1]
static float smoothed_progress(float x) { return x * x * x * (x * (x * 6.0f - 15.0f) + 10.0f); }
bool changing_color_mode_{false};
LightColorValues end_values_{};
LightColorValues intermediate_values_{};
bool changing_color_mode_{false};
};
class LightFlashTransformer : public LightTransformer {
@@ -117,8 +117,8 @@ class LightFlashTransformer : public LightTransformer {
protected:
LightState &state_;
uint32_t transition_length_;
std::unique_ptr<LightTransformer> transformer_{nullptr};
uint32_t transition_length_;
bool begun_lightstate_restore_;
};

View File

@@ -21,6 +21,7 @@ from esphome.components.libretiny.const import (
COMPONENT_LN882X,
COMPONENT_RTL87XX,
)
from esphome.config_helpers import filter_source_files_from_platform
import esphome.config_validation as cv
from esphome.const import (
CONF_ARGS,
@@ -42,6 +43,7 @@ from esphome.const import (
PLATFORM_LN882X,
PLATFORM_RP2040,
PLATFORM_RTL87XX,
PlatformFramework,
)
from esphome.core import CORE, Lambda, coroutine_with_priority
@@ -444,3 +446,25 @@ async def logger_set_level_to_code(config, action_id, template_arg, args):
lambda_ = await cg.process_lambda(Lambda(text), args, return_type=cg.void)
return cg.new_Pvariable(action_id, template_arg, lambda_)
FILTER_SOURCE_FILES = filter_source_files_from_platform(
{
"logger_esp32.cpp": {
PlatformFramework.ESP32_ARDUINO,
PlatformFramework.ESP32_IDF,
},
"logger_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
"logger_host.cpp": {PlatformFramework.HOST_NATIVE},
"logger_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO},
"logger_libretiny.cpp": {
PlatformFramework.BK72XX_ARDUINO,
PlatformFramework.RTL87XX_ARDUINO,
PlatformFramework.LN882X_ARDUINO,
},
"task_log_buffer.cpp": {
PlatformFramework.ESP32_ARDUINO,
PlatformFramework.ESP32_IDF,
},
}
)

View File

@@ -121,7 +121,8 @@ void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __Flas
if (this->baud_rate_ > 0) {
this->write_msg_(this->tx_buffer_ + msg_start);
}
this->log_callback_.call(level, tag, this->tx_buffer_ + msg_start);
size_t msg_length = this->tx_buffer_at_ - msg_start - 1; // -1 to exclude null terminator
this->log_callback_.call(level, tag, this->tx_buffer_ + msg_start, msg_length);
global_recursion_guard_ = false;
}
@@ -185,7 +186,8 @@ void Logger::loop() {
this->tx_buffer_size_);
this->write_footer_to_buffer_(this->tx_buffer_, &this->tx_buffer_at_, this->tx_buffer_size_);
this->tx_buffer_[this->tx_buffer_at_] = '\0';
this->log_callback_.call(message->level, message->tag, this->tx_buffer_);
size_t msg_len = this->tx_buffer_at_; // We already know the length from tx_buffer_at_
this->log_callback_.call(message->level, message->tag, this->tx_buffer_, msg_len);
// At this point all the data we need from message has been transferred to the tx_buffer
// so we can release the message to allow other tasks to use it as soon as possible.
this->log_buffer_->release_message_main_loop(received_token);
@@ -214,7 +216,7 @@ void Logger::set_log_level(const std::string &tag, uint8_t log_level) { this->lo
UARTSelection Logger::get_uart() const { return this->uart_; }
#endif
void Logger::add_on_log_callback(std::function<void(uint8_t, const char *, const char *)> &&callback) {
void Logger::add_on_log_callback(std::function<void(uint8_t, const char *, const char *, size_t)> &&callback) {
this->log_callback_.add(std::move(callback));
}
float Logger::get_setup_priority() const { return setup_priority::BUS + 500.0f; }

View File

@@ -143,7 +143,7 @@ class Logger : public Component {
inline uint8_t level_for(const char *tag);
/// Register a callback that will be called for every log message sent
void add_on_log_callback(std::function<void(uint8_t, const char *, const char *)> &&callback);
void add_on_log_callback(std::function<void(uint8_t, const char *, const char *, size_t)> &&callback);
// add a listener for log level changes
void add_listener(std::function<void(uint8_t)> &&callback) { this->level_callback_.add(std::move(callback)); }
@@ -192,7 +192,7 @@ class Logger : public Component {
if (this->baud_rate_ > 0) {
this->write_msg_(this->tx_buffer_); // If logging is enabled, write to console
}
this->log_callback_.call(level, tag, this->tx_buffer_);
this->log_callback_.call(level, tag, this->tx_buffer_, this->tx_buffer_at_);
}
// Write the body of the log message to the buffer
@@ -246,7 +246,7 @@ class Logger : public Component {
// Large objects (internally aligned)
std::map<std::string, uint8_t> log_levels_{};
CallbackManager<void(uint8_t, const char *, const char *)> log_callback_{};
CallbackManager<void(uint8_t, const char *, const char *, size_t)> log_callback_{};
CallbackManager<void(uint8_t)> level_callback_{};
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
std::unique_ptr<logger::TaskLogBuffer> log_buffer_; // Will be initialized with init_log_buffer
@@ -355,7 +355,7 @@ class Logger : public Component {
}
inline void HOT write_footer_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size) {
static const uint16_t RESET_COLOR_LEN = strlen(ESPHOME_LOG_RESET_COLOR);
static constexpr uint16_t RESET_COLOR_LEN = sizeof(ESPHOME_LOG_RESET_COLOR) - 1;
this->write_body_to_buffer_(ESPHOME_LOG_RESET_COLOR, RESET_COLOR_LEN, buffer, buffer_at, buffer_size);
}
@@ -385,7 +385,7 @@ class LoggerMessageTrigger : public Trigger<uint8_t, const char *, const char *>
public:
explicit LoggerMessageTrigger(Logger *parent, uint8_t level) {
this->level_ = level;
parent->add_on_log_callback([this](uint8_t level, const char *tag, const char *message) {
parent->add_on_log_callback([this](uint8_t level, const char *tag, const char *message, size_t message_len) {
if (level <= this->level_) {
this->trigger(level, tag, message);
}

View File

@@ -184,7 +184,9 @@ void HOT Logger::write_msg_(const char *msg) {
) {
puts(msg);
} else {
uart_write_bytes(this->uart_num_, msg, strlen(msg));
// Use tx_buffer_at_ if msg points to tx_buffer_, otherwise fall back to strlen
size_t len = (msg == this->tx_buffer_) ? this->tx_buffer_at_ : strlen(msg);
uart_write_bytes(this->uart_num_, msg, len);
uart_write_bytes(this->uart_num_, "\n", 1);
}
}

View File

@@ -1,5 +1,6 @@
import esphome.codegen as cg
from esphome.components.esp32 import add_idf_component
from esphome.config_helpers import filter_source_files_from_platform
import esphome.config_validation as cv
from esphome.const import (
CONF_DISABLED,
@@ -8,6 +9,7 @@ from esphome.const import (
CONF_PROTOCOL,
CONF_SERVICE,
CONF_SERVICES,
PlatformFramework,
)
from esphome.core import CORE, coroutine_with_priority
@@ -108,3 +110,21 @@ async def to_code(config):
)
cg.add(var.add_extra_service(exp))
FILTER_SOURCE_FILES = filter_source_files_from_platform(
{
"mdns_esp32.cpp": {
PlatformFramework.ESP32_ARDUINO,
PlatformFramework.ESP32_IDF,
},
"mdns_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
"mdns_host.cpp": {PlatformFramework.HOST_NATIVE},
"mdns_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO},
"mdns_libretiny.cpp": {
PlatformFramework.BK72XX_ARDUINO,
PlatformFramework.RTL87XX_ARDUINO,
PlatformFramework.LN882X_ARDUINO,
},
}
)

View File

@@ -5,6 +5,7 @@ from esphome.automation import Condition
import esphome.codegen as cg
from esphome.components import logger
from esphome.components.esp32 import add_idf_sdkconfig_option
from esphome.config_helpers import filter_source_files_from_platform
import esphome.config_validation as cv
from esphome.const import (
CONF_AVAILABILITY,
@@ -54,6 +55,7 @@ from esphome.const import (
PLATFORM_BK72XX,
PLATFORM_ESP32,
PLATFORM_ESP8266,
PlatformFramework,
)
from esphome.core import CORE, coroutine_with_priority
@@ -596,3 +598,13 @@ async def mqtt_enable_to_code(config, action_id, template_arg, args):
async def mqtt_disable_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
FILTER_SOURCE_FILES = filter_source_files_from_platform(
{
"mqtt_backend_esp32.cpp": {
PlatformFramework.ESP32_ARDUINO,
PlatformFramework.ESP32_IDF,
},
}
)

View File

@@ -57,14 +57,15 @@ void MQTTClientComponent::setup() {
});
#ifdef USE_LOGGER
if (this->is_log_message_enabled() && logger::global_logger != nullptr) {
logger::global_logger->add_on_log_callback([this](int level, const char *tag, const char *message) {
if (level <= this->log_level_ && this->is_connected()) {
this->publish({.topic = this->log_message_.topic,
.payload = message,
.qos = this->log_message_.qos,
.retain = this->log_message_.retain});
}
});
logger::global_logger->add_on_log_callback(
[this](int level, const char *tag, const char *message, size_t message_len) {
if (level <= this->log_level_ && this->is_connected()) {
this->publish({.topic = this->log_message_.topic,
.payload = std::string(message, message_len),
.qos = this->log_message_.qos,
.retain = this->log_message_.retain});
}
});
}
#endif

View File

@@ -1,5 +1,7 @@
import esphome.codegen as cg
from esphome.components import uart
from esphome.config_helpers import filter_source_files_from_platform
from esphome.const import PlatformFramework
nextion_ns = cg.esphome_ns.namespace("nextion")
Nextion = nextion_ns.class_("Nextion", cg.PollingComponent, uart.UARTDevice)
@@ -8,3 +10,17 @@ nextion_ref = Nextion.operator("ref")
CONF_NEXTION_ID = "nextion_id"
CONF_PUBLISH_STATE = "publish_state"
CONF_SEND_TO_NEXTION = "send_to_nextion"
FILTER_SOURCE_FILES = filter_source_files_from_platform(
{
"nextion_upload_arduino.cpp": {
PlatformFramework.ESP32_ARDUINO,
PlatformFramework.ESP8266_ARDUINO,
PlatformFramework.RP2040_ARDUINO,
PlatformFramework.BK72XX_ARDUINO,
PlatformFramework.RTL87XX_ARDUINO,
PlatformFramework.LN882X_ARDUINO,
},
"nextion_upload_idf.cpp": {PlatformFramework.ESP32_IDF},
}
)

View File

@@ -1,5 +1,6 @@
from esphome import automation
import esphome.codegen as cg
from esphome.config_helpers import filter_source_files_from_platform
import esphome.config_validation as cv
from esphome.const import (
CONF_ESPHOME,
@@ -7,6 +8,7 @@ from esphome.const import (
CONF_OTA,
CONF_PLATFORM,
CONF_TRIGGER_ID,
PlatformFramework,
)
from esphome.core import CORE, coroutine_with_priority
@@ -120,3 +122,18 @@ async def ota_to_code(var, config):
use_state_callback = True
if use_state_callback:
cg.add_define("USE_OTA_STATE_CALLBACK")
FILTER_SOURCE_FILES = filter_source_files_from_platform(
{
"ota_backend_arduino_esp32.cpp": {PlatformFramework.ESP32_ARDUINO},
"ota_backend_esp_idf.cpp": {PlatformFramework.ESP32_IDF},
"ota_backend_arduino_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
"ota_backend_arduino_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO},
"ota_backend_arduino_libretiny.cpp": {
PlatformFramework.BK72XX_ARDUINO,
PlatformFramework.RTL87XX_ARDUINO,
PlatformFramework.LN882X_ARDUINO,
},
}
)

View File

@@ -1,6 +1,7 @@
from esphome import pins
import esphome.codegen as cg
from esphome.components import esp32, esp32_rmt, remote_base
from esphome.config_helpers import filter_source_files_from_platform
import esphome.config_validation as cv
from esphome.const import (
CONF_BUFFER_SIZE,
@@ -15,6 +16,7 @@ from esphome.const import (
CONF_TYPE,
CONF_USE_DMA,
CONF_VALUE,
PlatformFramework,
)
from esphome.core import CORE, TimePeriod
@@ -170,3 +172,19 @@ async def to_code(config):
cg.add(var.set_buffer_size(config[CONF_BUFFER_SIZE]))
cg.add(var.set_filter_us(config[CONF_FILTER]))
cg.add(var.set_idle_us(config[CONF_IDLE]))
FILTER_SOURCE_FILES = filter_source_files_from_platform(
{
"remote_receiver_esp32.cpp": {
PlatformFramework.ESP32_ARDUINO,
PlatformFramework.ESP32_IDF,
},
"remote_receiver_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
"remote_receiver_libretiny.cpp": {
PlatformFramework.BK72XX_ARDUINO,
PlatformFramework.RTL87XX_ARDUINO,
PlatformFramework.LN882X_ARDUINO,
},
}
)

View File

@@ -1,6 +1,7 @@
from esphome import automation, pins
import esphome.codegen as cg
from esphome.components import esp32, esp32_rmt, remote_base
from esphome.config_helpers import filter_source_files_from_platform
import esphome.config_validation as cv
from esphome.const import (
CONF_CARRIER_DUTY_PERCENT,
@@ -12,6 +13,7 @@ from esphome.const import (
CONF_PIN,
CONF_RMT_SYMBOLS,
CONF_USE_DMA,
PlatformFramework,
)
from esphome.core import CORE
@@ -95,3 +97,19 @@ async def to_code(config):
await automation.build_automation(
var.get_complete_trigger(), [], on_complete_config
)
FILTER_SOURCE_FILES = filter_source_files_from_platform(
{
"remote_transmitter_esp32.cpp": {
PlatformFramework.ESP32_ARDUINO,
PlatformFramework.ESP32_IDF,
},
"remote_transmitter_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
"remote_transmitter_libretiny.cpp": {
PlatformFramework.BK72XX_ARDUINO,
PlatformFramework.RTL87XX_ARDUINO,
PlatformFramework.LN882X_ARDUINO,
},
}
)

View File

@@ -0,0 +1,55 @@
#include "esphome/core/helpers.h"
#include "esphome/core/defines.h"
#ifdef USE_RP2040
#include "esphome/core/hal.h"
#if defined(USE_WIFI)
#include <WiFi.h>
#endif
#include <hardware/structs/rosc.h>
#include <hardware/sync.h>
namespace esphome {
uint32_t random_uint32() {
uint32_t result = 0;
for (uint8_t i = 0; i < 32; i++) {
result <<= 1;
result |= rosc_hw->randombit;
}
return result;
}
bool random_bytes(uint8_t *data, size_t len) {
while (len-- != 0) {
uint8_t result = 0;
for (uint8_t i = 0; i < 8; i++) {
result <<= 1;
result |= rosc_hw->randombit;
}
*data++ = result;
}
return true;
}
// RP2040 doesn't have mutexes, but that shouldn't be an issue as it's single-core and non-preemptive OS.
Mutex::Mutex() {}
Mutex::~Mutex() {}
void Mutex::lock() {}
bool Mutex::try_lock() { return true; }
void Mutex::unlock() {}
IRAM_ATTR InterruptLock::InterruptLock() { state_ = save_and_disable_interrupts(); }
IRAM_ATTR InterruptLock::~InterruptLock() { restore_interrupts(state_); }
void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parameter)
#ifdef USE_WIFI
WiFi.macAddress(mac);
#endif
}
} // namespace esphome
#endif // USE_RP2040

View File

@@ -0,0 +1,26 @@
"""
Runtime statistics component for ESPHome.
"""
import esphome.codegen as cg
import esphome.config_validation as cv
DEPENDENCIES = []
CONF_ENABLED = "enabled"
CONF_LOG_INTERVAL = "log_interval"
CONFIG_SCHEMA = cv.Schema(
{
cv.Optional(CONF_ENABLED, default=True): cv.boolean,
cv.Optional(
CONF_LOG_INTERVAL, default=60000
): cv.positive_time_period_milliseconds,
}
)
async def to_code(config):
"""Generate code for the runtime statistics component."""
cg.add(cg.App.set_runtime_stats_enabled(config[CONF_ENABLED]))
cg.add(cg.App.set_runtime_stats_log_interval(config[CONF_LOG_INTERVAL]))

View File

@@ -118,7 +118,7 @@ optional<float> QuantileFilter::new_value(float value) {
size_t queue_size = quantile_queue.size();
if (queue_size) {
size_t position = ceilf(queue_size * this->quantile_) - 1;
ESP_LOGVV(TAG, "QuantileFilter(%p)::position: %d/%d", this, position + 1, queue_size);
ESP_LOGVV(TAG, "QuantileFilter(%p)::position: %zu/%zu", this, position + 1, queue_size);
result = quantile_queue[position];
}
}

View File

@@ -1,5 +1,6 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.core import CORE
CODEOWNERS = ["@esphome/core"]
@@ -40,3 +41,18 @@ async def to_code(config):
elif impl == IMPLEMENTATION_BSD_SOCKETS:
cg.add_define("USE_SOCKET_IMPL_BSD_SOCKETS")
cg.add_define("USE_SOCKET_SELECT_SUPPORT")
def FILTER_SOURCE_FILES() -> list[str]:
"""Return list of socket implementation files that aren't selected by the user."""
impl = CORE.config["socket"][CONF_IMPLEMENTATION]
# Build list of files to exclude based on selected implementation
excluded = []
if impl != IMPLEMENTATION_LWIP_TCP:
excluded.append("lwip_raw_tcp_impl.cpp")
if impl != IMPLEMENTATION_BSD_SOCKETS:
excluded.append("bsd_sockets_impl.cpp")
if impl != IMPLEMENTATION_LWIP_SOCKETS:
excluded.append("lwip_sockets_impl.cpp")
return excluded

View File

@@ -13,6 +13,7 @@ from esphome.components.esp32.const import (
VARIANT_ESP32S2,
VARIANT_ESP32S3,
)
from esphome.config_helpers import filter_source_files_from_platform
import esphome.config_validation as cv
from esphome.const import (
CONF_CLK_PIN,
@@ -31,6 +32,7 @@ from esphome.const import (
PLATFORM_ESP32,
PLATFORM_ESP8266,
PLATFORM_RP2040,
PlatformFramework,
)
from esphome.core import CORE, coroutine_with_priority
import esphome.final_validate as fv
@@ -423,3 +425,18 @@ def final_validate_device_schema(name: str, *, require_mosi: bool, require_miso:
{cv.Required(CONF_SPI_ID): fv.id_declaration_match_schema(hub_schema)},
extra=cv.ALLOW_EXTRA,
)
FILTER_SOURCE_FILES = filter_source_files_from_platform(
{
"spi_arduino.cpp": {
PlatformFramework.ESP32_ARDUINO,
PlatformFramework.ESP8266_ARDUINO,
PlatformFramework.RP2040_ARDUINO,
PlatformFramework.BK72XX_ARDUINO,
PlatformFramework.RTL87XX_ARDUINO,
PlatformFramework.LN882X_ARDUINO,
},
"spi_esp_idf.cpp": {PlatformFramework.ESP32_IDF},
}
)

View File

@@ -21,10 +21,12 @@ constexpr int LOG_LEVEL_TO_SYSLOG_SEVERITY[] = {
void Syslog::setup() {
logger::global_logger->add_on_log_callback(
[this](int level, const char *tag, const char *message) { this->log_(level, tag, message); });
[this](int level, const char *tag, const char *message, size_t message_len) {
this->log_(level, tag, message, message_len);
});
}
void Syslog::log_(const int level, const char *tag, const char *message) const {
void Syslog::log_(const int level, const char *tag, const char *message, size_t message_len) const {
if (level > this->log_level_)
return;
// Syslog PRI calculation: facility * 8 + severity
@@ -34,7 +36,7 @@ void Syslog::log_(const int level, const char *tag, const char *message) const {
}
int pri = this->facility_ * 8 + severity;
auto timestamp = this->time_->now().strftime("%b %d %H:%M:%S");
unsigned len = strlen(message);
size_t len = message_len;
// remove color formatting
if (this->strip_ && message[0] == 0x1B && len > 11) {
message += 7;

View File

@@ -17,7 +17,7 @@ class Syslog : public Component, public Parented<udp::UDPComponent> {
protected:
int log_level_;
void log_(int level, const char *tag, const char *message) const;
void log_(int level, const char *tag, const char *message, size_t message_len) const;
time::RealTimeClock *time_;
bool strip_{true};
int facility_{16};

View File

@@ -2,6 +2,7 @@ import re
from esphome import automation, pins
import esphome.codegen as cg
from esphome.config_helpers import filter_source_files_from_platform
import esphome.config_validation as cv
from esphome.const import (
CONF_AFTER,
@@ -27,6 +28,7 @@ from esphome.const import (
CONF_TX_PIN,
CONF_UART_ID,
PLATFORM_HOST,
PlatformFramework,
)
from esphome.core import CORE
import esphome.final_validate as fv
@@ -438,3 +440,19 @@ async def uart_write_to_code(config, action_id, template_arg, args):
else:
cg.add(var.set_data_static(data))
return var
FILTER_SOURCE_FILES = filter_source_files_from_platform(
{
"uart_component_esp32_arduino.cpp": {PlatformFramework.ESP32_ARDUINO},
"uart_component_esp_idf.cpp": {PlatformFramework.ESP32_IDF},
"uart_component_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
"uart_component_host.cpp": {PlatformFramework.HOST_NATIVE},
"uart_component_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO},
"uart_component_libretiny.cpp": {
PlatformFramework.BK72XX_ARDUINO,
PlatformFramework.RTL87XX_ARDUINO,
PlatformFramework.LN882X_ARDUINO,
},
}
)

View File

@@ -287,7 +287,8 @@ void WebServer::setup() {
if (logger::global_logger != nullptr && this->expose_log_) {
logger::global_logger->add_on_log_callback(
// logs are not deferred, the memory overhead would be too large
[this](int level, const char *tag, const char *message) {
[this](int level, const char *tag, const char *message, size_t message_len) {
(void) message_len;
this->events_.try_send_nodefer(message, "log", millis());
});
}

View File

@@ -40,4 +40,7 @@ async def to_code(config):
if CORE.is_esp8266:
cg.add_library("ESP8266WiFi", None)
# https://github.com/ESP32Async/ESPAsyncWebServer/blob/main/library.json
cg.add_library("ESP32Async/ESPAsyncWebServer", "3.7.8")
# Use fork with libretiny compatibility fix
cg.add_library(
"https://github.com/bdraco/ESPAsyncWebServer.git#libretiny_Fix", None
)

View File

@@ -3,6 +3,7 @@ from esphome.automation import Condition
import esphome.codegen as cg
from esphome.components.esp32 import add_idf_sdkconfig_option, const, get_esp32_variant
from esphome.components.network import IPAddress
from esphome.config_helpers import filter_source_files_from_platform
import esphome.config_validation as cv
from esphome.const import (
CONF_AP,
@@ -39,6 +40,7 @@ from esphome.const import (
CONF_TTLS_PHASE_2,
CONF_USE_ADDRESS,
CONF_USERNAME,
PlatformFramework,
)
from esphome.core import CORE, HexInt, coroutine_with_priority
import esphome.final_validate as fv
@@ -526,3 +528,18 @@ async def wifi_set_sta_to_code(config, action_id, template_arg, args):
await automation.build_automation(var.get_error_trigger(), [], on_error_config)
await cg.register_component(var, config)
return var
FILTER_SOURCE_FILES = filter_source_files_from_platform(
{
"wifi_component_esp32_arduino.cpp": {PlatformFramework.ESP32_ARDUINO},
"wifi_component_esp_idf.cpp": {PlatformFramework.ESP32_IDF},
"wifi_component_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
"wifi_component_libretiny.cpp": {
PlatformFramework.BK72XX_ARDUINO,
PlatformFramework.RTL87XX_ARDUINO,
PlatformFramework.LN882X_ARDUINO,
},
"wifi_component_pico_w.cpp": {PlatformFramework.RP2040_ARDUINO},
}
)

View File

@@ -1,4 +1,18 @@
from esphome.const import CONF_ID
from collections.abc import Callable
from esphome.const import (
CONF_ID,
KEY_CORE,
KEY_TARGET_FRAMEWORK,
KEY_TARGET_PLATFORM,
PlatformFramework,
)
from esphome.core import CORE
# Pre-build lookup map from (platform, framework) tuples to PlatformFramework enum
_PLATFORM_FRAMEWORK_LOOKUP = {
(pf.value[0].value, pf.value[1].value): pf for pf in PlatformFramework
}
class Extend:
@@ -103,3 +117,43 @@ def merge_config(full_old, full_new):
return new
return merge(full_old, full_new)
def filter_source_files_from_platform(
files_map: dict[str, set[PlatformFramework]],
) -> Callable[[], list[str]]:
"""Helper to build a FILTER_SOURCE_FILES function from platform mapping.
Args:
files_map: Dict mapping filename to set of PlatformFramework enums
that should compile this file
Returns:
Function that returns list of files to exclude for current platform
"""
def filter_source_files() -> list[str]:
# Get current platform/framework
core_data = CORE.data.get(KEY_CORE, {})
target_platform = core_data.get(KEY_TARGET_PLATFORM)
target_framework = core_data.get(KEY_TARGET_FRAMEWORK)
if not target_platform or not target_framework:
return []
# Direct lookup of current PlatformFramework
current_platform_framework = _PLATFORM_FRAMEWORK_LOOKUP.get(
(target_platform, target_framework)
)
if not current_platform_framework:
return []
# Return files that should be excluded for current platform
return [
filename
for filename, platforms in files_map.items()
if current_platform_framework not in platforms
]
return filter_source_files

View File

@@ -1,5 +1,9 @@
"""Constants used by esphome."""
from enum import Enum
from esphome.enum import StrEnum
__version__ = "2025.7.0-dev"
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
@@ -7,14 +11,55 @@ VALID_SUBSTITUTIONS_CHARACTERS = (
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"
)
PLATFORM_BK72XX = "bk72xx"
PLATFORM_ESP32 = "esp32"
PLATFORM_ESP8266 = "esp8266"
PLATFORM_HOST = "host"
PLATFORM_LIBRETINY_OLDSTYLE = "libretiny"
PLATFORM_LN882X = "ln882x"
PLATFORM_RP2040 = "rp2040"
PLATFORM_RTL87XX = "rtl87xx"
class Platform(StrEnum):
"""Platform identifiers for ESPHome."""
BK72XX = "bk72xx"
ESP32 = "esp32"
ESP8266 = "esp8266"
HOST = "host"
LIBRETINY_OLDSTYLE = "libretiny"
LN882X = "ln882x"
RP2040 = "rp2040"
RTL87XX = "rtl87xx"
class Framework(StrEnum):
"""Framework identifiers for ESPHome."""
ARDUINO = "arduino"
ESP_IDF = "esp-idf"
NATIVE = "host"
class PlatformFramework(Enum):
"""Combined platform-framework identifiers with tuple values."""
# ESP32 variants
ESP32_ARDUINO = (Platform.ESP32, Framework.ARDUINO)
ESP32_IDF = (Platform.ESP32, Framework.ESP_IDF)
# Arduino framework platforms
ESP8266_ARDUINO = (Platform.ESP8266, Framework.ARDUINO)
RP2040_ARDUINO = (Platform.RP2040, Framework.ARDUINO)
BK72XX_ARDUINO = (Platform.BK72XX, Framework.ARDUINO)
RTL87XX_ARDUINO = (Platform.RTL87XX, Framework.ARDUINO)
LN882X_ARDUINO = (Platform.LN882X, Framework.ARDUINO)
# Host platform (native)
HOST_NATIVE = (Platform.HOST, Framework.NATIVE)
# Maintain backward compatibility by reassigning after enum definition
PLATFORM_BK72XX = Platform.BK72XX
PLATFORM_ESP32 = Platform.ESP32
PLATFORM_ESP8266 = Platform.ESP8266
PLATFORM_HOST = Platform.HOST
PLATFORM_LIBRETINY_OLDSTYLE = Platform.LIBRETINY_OLDSTYLE
PLATFORM_LN882X = Platform.LN882X
PLATFORM_RP2040 = Platform.RP2040
PLATFORM_RTL87XX = Platform.RTL87XX
SOURCE_FILE_EXTENSIONS = {".cpp", ".hpp", ".h", ".c", ".tcc", ".ino"}

View File

@@ -141,6 +141,10 @@ void Application::loop() {
this->in_loop_ = false;
this->app_state_ = new_app_state;
// Process any pending runtime stats printing after all components have run
// This ensures stats printing doesn't affect component timing measurements
runtime_stats.process_pending_stats(last_op_end_time);
// Use the last component's end time instead of calling millis() again
auto elapsed = last_op_end_time - this->last_loop_;
if (elapsed >= this->loop_interval_ || HighFrequencyLoopRequester::is_high_frequency()) {

View File

@@ -9,6 +9,7 @@
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "esphome/core/preferences.h"
#include "esphome/core/runtime_stats.h"
#include "esphome/core/scheduler.h"
#ifdef USE_DEVICES
@@ -348,6 +349,18 @@ class Application {
uint32_t get_loop_interval() const { return static_cast<uint32_t>(this->loop_interval_); }
/** Enable or disable runtime statistics collection.
*
* @param enable Whether to enable runtime statistics collection.
*/
void set_runtime_stats_enabled(bool enable) { runtime_stats.set_enabled(enable); }
/** Set the interval at which runtime statistics are logged.
*
* @param interval The interval in milliseconds between logging of runtime statistics.
*/
void set_runtime_stats_log_interval(uint32_t interval) { runtime_stats.set_log_interval(interval); }
void schedule_dump_config() { this->dump_config_at_ = 0; }
void feed_wdt(uint32_t time = 0);
@@ -368,6 +381,17 @@ class Application {
uint8_t get_app_state() const { return this->app_state_; }
// Helper macro for entity getter method declarations - reduces code duplication
// When USE_DEVICE_ID is enabled in the future, this can be conditionally compiled to add device_id parameter
#define GET_ENTITY_METHOD(entity_type, entity_name, entities_member) \
entity_type *get_##entity_name##_by_key(uint32_t key, bool include_internal = false) { \
for (auto *obj : this->entities_member##_) { \
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) \
return obj; \
} \
return nullptr; \
}
#ifdef USE_DEVICES
const std::vector<Device *> &get_devices() { return this->devices_; }
#endif
@@ -376,218 +400,92 @@ class Application {
#endif
#ifdef USE_BINARY_SENSOR
const std::vector<binary_sensor::BinarySensor *> &get_binary_sensors() { return this->binary_sensors_; }
binary_sensor::BinarySensor *get_binary_sensor_by_key(uint32_t key, bool include_internal = false) {
for (auto *obj : this->binary_sensors_) {
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
return obj;
}
return nullptr;
}
GET_ENTITY_METHOD(binary_sensor::BinarySensor, binary_sensor, binary_sensors)
#endif
#ifdef USE_SWITCH
const std::vector<switch_::Switch *> &get_switches() { return this->switches_; }
switch_::Switch *get_switch_by_key(uint32_t key, bool include_internal = false) {
for (auto *obj : this->switches_) {
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
return obj;
}
return nullptr;
}
GET_ENTITY_METHOD(switch_::Switch, switch, switches)
#endif
#ifdef USE_BUTTON
const std::vector<button::Button *> &get_buttons() { return this->buttons_; }
button::Button *get_button_by_key(uint32_t key, bool include_internal = false) {
for (auto *obj : this->buttons_) {
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
return obj;
}
return nullptr;
}
GET_ENTITY_METHOD(button::Button, button, buttons)
#endif
#ifdef USE_SENSOR
const std::vector<sensor::Sensor *> &get_sensors() { return this->sensors_; }
sensor::Sensor *get_sensor_by_key(uint32_t key, bool include_internal = false) {
for (auto *obj : this->sensors_) {
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
return obj;
}
return nullptr;
}
GET_ENTITY_METHOD(sensor::Sensor, sensor, sensors)
#endif
#ifdef USE_TEXT_SENSOR
const std::vector<text_sensor::TextSensor *> &get_text_sensors() { return this->text_sensors_; }
text_sensor::TextSensor *get_text_sensor_by_key(uint32_t key, bool include_internal = false) {
for (auto *obj : this->text_sensors_) {
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
return obj;
}
return nullptr;
}
GET_ENTITY_METHOD(text_sensor::TextSensor, text_sensor, text_sensors)
#endif
#ifdef USE_FAN
const std::vector<fan::Fan *> &get_fans() { return this->fans_; }
fan::Fan *get_fan_by_key(uint32_t key, bool include_internal = false) {
for (auto *obj : this->fans_) {
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
return obj;
}
return nullptr;
}
GET_ENTITY_METHOD(fan::Fan, fan, fans)
#endif
#ifdef USE_COVER
const std::vector<cover::Cover *> &get_covers() { return this->covers_; }
cover::Cover *get_cover_by_key(uint32_t key, bool include_internal = false) {
for (auto *obj : this->covers_) {
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
return obj;
}
return nullptr;
}
GET_ENTITY_METHOD(cover::Cover, cover, covers)
#endif
#ifdef USE_LIGHT
const std::vector<light::LightState *> &get_lights() { return this->lights_; }
light::LightState *get_light_by_key(uint32_t key, bool include_internal = false) {
for (auto *obj : this->lights_) {
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
return obj;
}
return nullptr;
}
GET_ENTITY_METHOD(light::LightState, light, lights)
#endif
#ifdef USE_CLIMATE
const std::vector<climate::Climate *> &get_climates() { return this->climates_; }
climate::Climate *get_climate_by_key(uint32_t key, bool include_internal = false) {
for (auto *obj : this->climates_) {
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
return obj;
}
return nullptr;
}
GET_ENTITY_METHOD(climate::Climate, climate, climates)
#endif
#ifdef USE_NUMBER
const std::vector<number::Number *> &get_numbers() { return this->numbers_; }
number::Number *get_number_by_key(uint32_t key, bool include_internal = false) {
for (auto *obj : this->numbers_) {
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
return obj;
}
return nullptr;
}
GET_ENTITY_METHOD(number::Number, number, numbers)
#endif
#ifdef USE_DATETIME_DATE
const std::vector<datetime::DateEntity *> &get_dates() { return this->dates_; }
datetime::DateEntity *get_date_by_key(uint32_t key, bool include_internal = false) {
for (auto *obj : this->dates_) {
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
return obj;
}
return nullptr;
}
GET_ENTITY_METHOD(datetime::DateEntity, date, dates)
#endif
#ifdef USE_DATETIME_TIME
const std::vector<datetime::TimeEntity *> &get_times() { return this->times_; }
datetime::TimeEntity *get_time_by_key(uint32_t key, bool include_internal = false) {
for (auto *obj : this->times_) {
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
return obj;
}
return nullptr;
}
GET_ENTITY_METHOD(datetime::TimeEntity, time, times)
#endif
#ifdef USE_DATETIME_DATETIME
const std::vector<datetime::DateTimeEntity *> &get_datetimes() { return this->datetimes_; }
datetime::DateTimeEntity *get_datetime_by_key(uint32_t key, bool include_internal = false) {
for (auto *obj : this->datetimes_) {
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
return obj;
}
return nullptr;
}
GET_ENTITY_METHOD(datetime::DateTimeEntity, datetime, datetimes)
#endif
#ifdef USE_TEXT
const std::vector<text::Text *> &get_texts() { return this->texts_; }
text::Text *get_text_by_key(uint32_t key, bool include_internal = false) {
for (auto *obj : this->texts_) {
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
return obj;
}
return nullptr;
}
GET_ENTITY_METHOD(text::Text, text, texts)
#endif
#ifdef USE_SELECT
const std::vector<select::Select *> &get_selects() { return this->selects_; }
select::Select *get_select_by_key(uint32_t key, bool include_internal = false) {
for (auto *obj : this->selects_) {
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
return obj;
}
return nullptr;
}
GET_ENTITY_METHOD(select::Select, select, selects)
#endif
#ifdef USE_LOCK
const std::vector<lock::Lock *> &get_locks() { return this->locks_; }
lock::Lock *get_lock_by_key(uint32_t key, bool include_internal = false) {
for (auto *obj : this->locks_) {
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
return obj;
}
return nullptr;
}
GET_ENTITY_METHOD(lock::Lock, lock, locks)
#endif
#ifdef USE_VALVE
const std::vector<valve::Valve *> &get_valves() { return this->valves_; }
valve::Valve *get_valve_by_key(uint32_t key, bool include_internal = false) {
for (auto *obj : this->valves_) {
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
return obj;
}
return nullptr;
}
GET_ENTITY_METHOD(valve::Valve, valve, valves)
#endif
#ifdef USE_MEDIA_PLAYER
const std::vector<media_player::MediaPlayer *> &get_media_players() { return this->media_players_; }
media_player::MediaPlayer *get_media_player_by_key(uint32_t key, bool include_internal = false) {
for (auto *obj : this->media_players_) {
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
return obj;
}
return nullptr;
}
GET_ENTITY_METHOD(media_player::MediaPlayer, media_player, media_players)
#endif
#ifdef USE_ALARM_CONTROL_PANEL
const std::vector<alarm_control_panel::AlarmControlPanel *> &get_alarm_control_panels() {
return this->alarm_control_panels_;
}
alarm_control_panel::AlarmControlPanel *get_alarm_control_panel_by_key(uint32_t key, bool include_internal = false) {
for (auto *obj : this->alarm_control_panels_) {
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
return obj;
}
return nullptr;
}
GET_ENTITY_METHOD(alarm_control_panel::AlarmControlPanel, alarm_control_panel, alarm_control_panels)
#endif
#ifdef USE_EVENT
const std::vector<event::Event *> &get_events() { return this->events_; }
event::Event *get_event_by_key(uint32_t key, bool include_internal = false) {
for (auto *obj : this->events_) {
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
return obj;
}
return nullptr;
}
GET_ENTITY_METHOD(event::Event, event, events)
#endif
#ifdef USE_UPDATE
const std::vector<update::UpdateEntity *> &get_updates() { return this->updates_; }
update::UpdateEntity *get_update_by_key(uint32_t key, bool include_internal = false) {
for (auto *obj : this->updates_) {
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
return obj;
}
return nullptr;
}
GET_ENTITY_METHOD(update::UpdateEntity, update, updates)
#endif
Scheduler scheduler;

View File

@@ -26,17 +26,17 @@ static const char *const TAG = "component";
// 1. Components are never destroyed in ESPHome
// 2. Failed components remain failed (no recovery mechanism)
// 3. Memory usage is minimal (only failures with custom messages are stored)
static std::unique_ptr<std::vector<std::pair<const Component *, const char *>>> &get_component_error_messages() {
static std::unique_ptr<std::vector<std::pair<const Component *, const char *>>> instance;
return instance;
}
// Using namespace-scope static to avoid guard variables (saves 16 bytes total)
// This is safe because ESPHome is single-threaded during initialization
namespace {
// Error messages for failed components
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
std::unique_ptr<std::vector<std::pair<const Component *, const char *>>> component_error_messages;
// Setup priority overrides - freed after setup completes
// Typically < 5 entries, lazy allocated
static std::unique_ptr<std::vector<std::pair<const Component *, float>>> &get_setup_priority_overrides() {
static std::unique_ptr<std::vector<std::pair<const Component *, float>>> instance;
return instance;
}
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
std::unique_ptr<std::vector<std::pair<const Component *, float>>> setup_priority_overrides;
} // namespace
namespace setup_priority {
@@ -130,8 +130,8 @@ void Component::call_dump_config() {
if (this->is_failed()) {
// Look up error message from global vector
const char *error_msg = "unspecified";
if (get_component_error_messages()) {
for (const auto &pair : *get_component_error_messages()) {
if (component_error_messages) {
for (const auto &pair : *component_error_messages) {
if (pair.first == this) {
error_msg = pair.second;
break;
@@ -285,18 +285,18 @@ void Component::status_set_error(const char *message) {
ESP_LOGE(TAG, "Component %s set Error flag: %s", this->get_component_source(), message);
if (strcmp(message, "unspecified") != 0) {
// Lazy allocate the error messages vector if needed
if (!get_component_error_messages()) {
get_component_error_messages() = std::make_unique<std::vector<std::pair<const Component *, const char *>>>();
if (!component_error_messages) {
component_error_messages = std::make_unique<std::vector<std::pair<const Component *, const char *>>>();
}
// Check if this component already has an error message
for (auto &pair : *get_component_error_messages()) {
for (auto &pair : *component_error_messages) {
if (pair.first == this) {
pair.second = message;
return;
}
}
// Add new error message
get_component_error_messages()->emplace_back(this, message);
component_error_messages->emplace_back(this, message);
}
}
void Component::status_clear_warning() {
@@ -322,9 +322,9 @@ void Component::status_momentary_error(const std::string &name, uint32_t length)
void Component::dump_config() {}
float Component::get_actual_setup_priority() const {
// Check if there's an override in the global vector
if (get_setup_priority_overrides()) {
if (setup_priority_overrides) {
// Linear search is fine for small n (typically < 5 overrides)
for (const auto &pair : *get_setup_priority_overrides()) {
for (const auto &pair : *setup_priority_overrides) {
if (pair.first == this) {
return pair.second;
}
@@ -334,14 +334,14 @@ float Component::get_actual_setup_priority() const {
}
void Component::set_setup_priority(float priority) {
// Lazy allocate the vector if needed
if (!get_setup_priority_overrides()) {
get_setup_priority_overrides() = std::make_unique<std::vector<std::pair<const Component *, float>>>();
if (!setup_priority_overrides) {
setup_priority_overrides = std::make_unique<std::vector<std::pair<const Component *, float>>>();
// Reserve some space to avoid reallocations (most configs have < 10 overrides)
get_setup_priority_overrides()->reserve(10);
setup_priority_overrides->reserve(10);
}
// Check if this component already has an override
for (auto &pair : *get_setup_priority_overrides()) {
for (auto &pair : *setup_priority_overrides) {
if (pair.first == this) {
pair.second = priority;
return;
@@ -349,7 +349,7 @@ void Component::set_setup_priority(float priority) {
}
// Add new override
get_setup_priority_overrides()->emplace_back(this, priority);
setup_priority_overrides->emplace_back(this, priority);
}
bool Component::has_overridden_loop() const {
@@ -395,6 +395,9 @@ uint32_t WarnIfComponentBlockingGuard::finish() {
uint32_t curr_time = millis();
uint32_t blocking_time = curr_time - this->started_;
// Record component runtime stats
runtime_stats.record_component_time(this->component_, blocking_time, curr_time);
bool should_warn;
if (this->component_ != nullptr) {
should_warn = this->component_->should_warn_of_blocking(blocking_time);
@@ -414,7 +417,7 @@ WarnIfComponentBlockingGuard::~WarnIfComponentBlockingGuard() {}
void clear_setup_priority_overrides() {
// Free the setup priority map completely
get_setup_priority_overrides().reset();
setup_priority_overrides.reset();
}
} // namespace esphome

View File

@@ -6,6 +6,7 @@
#include <string>
#include "esphome/core/optional.h"
#include "esphome/core/runtime_stats.h"
namespace esphome {

View File

@@ -6,6 +6,7 @@ from pathlib import Path
from esphome import automation, core
import esphome.codegen as cg
from esphome.config_helpers import filter_source_files_from_platform
import esphome.config_validation as cv
from esphome.const import (
CONF_AREA,
@@ -35,6 +36,7 @@ from esphome.const import (
CONF_TRIGGER_ID,
CONF_VERSION,
KEY_CORE,
PlatformFramework,
__version__ as ESPHOME_VERSION,
)
from esphome.core import CORE, coroutine_with_priority
@@ -551,3 +553,16 @@ async def to_code(config: ConfigType) -> None:
cg.add(dev.set_area_id(area_id_hash))
cg.add(cg.App.register_device(dev))
# Platform-specific source files for core
FILTER_SOURCE_FILES = filter_source_files_from_platform(
{
"ring_buffer.cpp": {
PlatformFramework.ESP32_ARDUINO,
PlatformFramework.ESP32_IDF,
},
# Note: lock_free_queue.h and event_pool.h are header files and don't need to be filtered
# as they are only included when needed by the preprocessor
}
)

View File

@@ -33,6 +33,7 @@
#define USE_DEEP_SLEEP
#define USE_DEVICES
#define USE_DISPLAY
#define USE_ENTITY_ICON
#define USE_ESP32_IMPROV_STATE_CALLBACK
#define USE_EVENT
#define USE_FAN

View File

@@ -27,12 +27,22 @@ void EntityBase::set_name(const char *name) {
// Entity Icon
std::string EntityBase::get_icon() const {
#ifdef USE_ENTITY_ICON
if (this->icon_c_str_ == nullptr) {
return "";
}
return this->icon_c_str_;
#else
return "";
#endif
}
void EntityBase::set_icon(const char *icon) {
#ifdef USE_ENTITY_ICON
this->icon_c_str_ = icon;
#else
// No-op when USE_ENTITY_ICON is not defined
#endif
}
void EntityBase::set_icon(const char *icon) { this->icon_c_str_ = icon; }
// Entity Object ID
std::string EntityBase::get_object_id() const {

View File

@@ -80,7 +80,9 @@ class EntityBase {
StringRef name_;
const char *object_id_c_str_{nullptr};
#ifdef USE_ENTITY_ICON
const char *icon_c_str_{nullptr};
#endif
uint32_t object_id_hash_{};
#ifdef USE_DEVICES
Device *device_{};

View File

@@ -1,6 +1,7 @@
from collections.abc import Callable
import logging
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import (
CONF_DEVICE_ID,
@@ -108,6 +109,8 @@ async def setup_entity(var: MockObj, config: ConfigType, platform: str) -> None:
if CONF_INTERNAL in config:
add(var.set_internal(config[CONF_INTERNAL]))
if CONF_ICON in config:
# Add USE_ENTITY_ICON define when icons are used
cg.add_define("USE_ENTITY_ICON")
add(var.set_icon(config[CONF_ICON]))
if CONF_ENTITY_CATEGORY in config:
add(var.set_entity_category(config[CONF_ENTITY_CATEGORY]))

View File

@@ -12,47 +12,10 @@
#include <cstdio>
#include <cstring>
#ifdef USE_HOST
#ifndef _WIN32
#include <net/if.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#endif
#include <unistd.h>
#endif
#if defined(USE_ESP8266)
#include <osapi.h>
#include <user_interface.h>
// for xt_rsil()/xt_wsr_ps()
#include <Arduino.h>
#elif defined(USE_ESP32_FRAMEWORK_ARDUINO)
#include <Esp.h>
#elif defined(USE_ESP_IDF)
#include <freertos/FreeRTOS.h>
#include <freertos/portmacro.h>
#include "esp_random.h"
#include "esp_system.h"
#elif defined(USE_RP2040)
#if defined(USE_WIFI)
#include <WiFi.h>
#endif
#include <hardware/structs/rosc.h>
#include <hardware/sync.h>
#elif defined(USE_HOST)
#include <limits>
#include <random>
#endif
#ifdef USE_ESP32
#include "esp_efuse.h"
#include "esp_efuse_table.h"
#include "esp_mac.h"
#include "rom/crc.h"
#endif
#ifdef USE_LIBRETINY
#include <WiFi.h> // for macAddress()
#endif
namespace esphome {
static const char *const TAG = "helpers";
@@ -177,70 +140,7 @@ uint32_t fnv1_hash(const std::string &str) {
return hash;
}
#ifdef USE_ESP32
uint32_t random_uint32() { return esp_random(); }
#elif defined(USE_ESP8266)
uint32_t random_uint32() { return os_random(); }
#elif defined(USE_RP2040)
uint32_t random_uint32() {
uint32_t result = 0;
for (uint8_t i = 0; i < 32; i++) {
result <<= 1;
result |= rosc_hw->randombit;
}
return result;
}
#elif defined(USE_LIBRETINY)
uint32_t random_uint32() { return rand(); }
#elif defined(USE_HOST)
uint32_t random_uint32() {
std::random_device dev;
std::mt19937 rng(dev());
std::uniform_int_distribution<uint32_t> dist(0, std::numeric_limits<uint32_t>::max());
return dist(rng);
}
#endif
float random_float() { return static_cast<float>(random_uint32()) / static_cast<float>(UINT32_MAX); }
#ifdef USE_ESP32
bool random_bytes(uint8_t *data, size_t len) {
esp_fill_random(data, len);
return true;
}
#elif defined(USE_ESP8266)
bool random_bytes(uint8_t *data, size_t len) { return os_get_random(data, len) == 0; }
#elif defined(USE_RP2040)
bool random_bytes(uint8_t *data, size_t len) {
while (len-- != 0) {
uint8_t result = 0;
for (uint8_t i = 0; i < 8; i++) {
result <<= 1;
result |= rosc_hw->randombit;
}
*data++ = result;
}
return true;
}
#elif defined(USE_LIBRETINY)
bool random_bytes(uint8_t *data, size_t len) {
lt_rand_bytes(data, len);
return true;
}
#elif defined(USE_HOST)
bool random_bytes(uint8_t *data, size_t len) {
FILE *fp = fopen("/dev/urandom", "r");
if (fp == nullptr) {
ESP_LOGW(TAG, "Could not open /dev/urandom, errno=%d", errno);
exit(1);
}
size_t read = fread(data, 1, len, fp);
if (read != len) {
ESP_LOGW(TAG, "Not enough data from /dev/urandom");
exit(1);
}
fclose(fp);
return true;
}
#endif
// Strings
@@ -460,9 +360,22 @@ int8_t step_to_accuracy_decimals(float step) {
return str.length() - dot_pos - 1;
}
static const std::string BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
// Use C-style string constant to store in ROM instead of RAM (saves 24 bytes)
static constexpr const char *BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
// Helper function to find the index of a base64 character in the lookup table.
// Returns the character's position (0-63) if found, or 0 if not found.
// NOTE: This returns 0 for both 'A' (valid base64 char at index 0) and invalid characters.
// This is safe because is_base64() is ALWAYS checked before calling this function,
// preventing invalid characters from ever reaching here. The base64_decode function
// stops processing at the first invalid character due to the is_base64() check in its
// while loop condition, making this edge case harmless in practice.
static inline uint8_t base64_find_char(char c) {
const char *pos = strchr(BASE64_CHARS, c);
return pos ? (pos - BASE64_CHARS) : 0;
}
static inline bool is_base64(char c) { return (isalnum(c) || (c == '+') || (c == '/')); }
@@ -484,7 +397,7 @@ std::string base64_encode(const uint8_t *buf, size_t buf_len) {
char_array_4[3] = char_array_3[2] & 0x3f;
for (i = 0; (i < 4); i++)
ret += BASE64_CHARS[char_array_4[i]];
ret += BASE64_CHARS[static_cast<uint8_t>(char_array_4[i])];
i = 0;
}
}
@@ -499,7 +412,7 @@ std::string base64_encode(const uint8_t *buf, size_t buf_len) {
char_array_4[3] = char_array_3[2] & 0x3f;
for (j = 0; (j < i + 1); j++)
ret += BASE64_CHARS[char_array_4[j]];
ret += BASE64_CHARS[static_cast<uint8_t>(char_array_4[j])];
while ((i++ < 3))
ret += '=';
@@ -526,12 +439,15 @@ std::vector<uint8_t> base64_decode(const std::string &encoded_string) {
uint8_t char_array_4[4], char_array_3[3];
std::vector<uint8_t> ret;
// SAFETY: The loop condition checks is_base64() before processing each character.
// This ensures base64_find_char() is only called on valid base64 characters,
// preventing the edge case where invalid chars would return 0 (same as 'A').
while (in_len-- && (encoded_string[in] != '=') && is_base64(encoded_string[in])) {
char_array_4[i++] = encoded_string[in];
in++;
if (i == 4) {
for (i = 0; i < 4; i++)
char_array_4[i] = BASE64_CHARS.find(char_array_4[i]);
char_array_4[i] = base64_find_char(char_array_4[i]);
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
@@ -548,7 +464,7 @@ std::vector<uint8_t> base64_decode(const std::string &encoded_string) {
char_array_4[j] = 0;
for (j = 0; j < 4; j++)
char_array_4[j] = BASE64_CHARS.find(char_array_4[j]);
char_array_4[j] = base64_find_char(char_array_4[j]);
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
@@ -644,42 +560,6 @@ void hsv_to_rgb(int hue, float saturation, float value, float &red, float &green
blue += delta;
}
// System APIs
#if defined(USE_ESP8266) || defined(USE_RP2040)
// ESP8266 doesn't have mutexes, but that shouldn't be an issue as it's single-core and non-preemptive OS.
Mutex::Mutex() {}
Mutex::~Mutex() {}
void Mutex::lock() {}
bool Mutex::try_lock() { return true; }
void Mutex::unlock() {}
#elif defined(USE_ESP32) || defined(USE_LIBRETINY)
Mutex::Mutex() { handle_ = xSemaphoreCreateMutex(); }
Mutex::~Mutex() {}
void Mutex::lock() { xSemaphoreTake(this->handle_, portMAX_DELAY); }
bool Mutex::try_lock() { return xSemaphoreTake(this->handle_, 0) == pdTRUE; }
void Mutex::unlock() { xSemaphoreGive(this->handle_); }
#elif defined(USE_HOST)
// Host platform uses std::mutex for proper thread synchronization
Mutex::Mutex() { handle_ = new std::mutex(); }
Mutex::~Mutex() { delete static_cast<std::mutex *>(handle_); }
void Mutex::lock() { static_cast<std::mutex *>(handle_)->lock(); }
bool Mutex::try_lock() { return static_cast<std::mutex *>(handle_)->try_lock(); }
void Mutex::unlock() { static_cast<std::mutex *>(handle_)->unlock(); }
#endif
#if defined(USE_ESP8266)
IRAM_ATTR InterruptLock::InterruptLock() { state_ = xt_rsil(15); }
IRAM_ATTR InterruptLock::~InterruptLock() { xt_wsr_ps(state_); }
#elif defined(USE_ESP32) || defined(USE_LIBRETINY)
// only affects the executing core
// so should not be used as a mutex lock, only to get accurate timing
IRAM_ATTR InterruptLock::InterruptLock() { portDISABLE_INTERRUPTS(); }
IRAM_ATTR InterruptLock::~InterruptLock() { portENABLE_INTERRUPTS(); }
#elif defined(USE_RP2040)
IRAM_ATTR InterruptLock::InterruptLock() { state_ = save_and_disable_interrupts(); }
IRAM_ATTR InterruptLock::~InterruptLock() { restore_interrupts(state_); }
#endif
uint8_t HighFrequencyLoopRequester::num_requests = 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
void HighFrequencyLoopRequester::start() {
if (this->started_)
@@ -695,45 +575,6 @@ void HighFrequencyLoopRequester::stop() {
}
bool HighFrequencyLoopRequester::is_high_frequency() { return num_requests > 0; }
#if defined(USE_HOST)
void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parameter)
static const uint8_t esphome_host_mac_address[6] = USE_ESPHOME_HOST_MAC_ADDRESS;
memcpy(mac, esphome_host_mac_address, sizeof(esphome_host_mac_address));
}
#elif defined(USE_ESP32)
void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parameter)
#if defined(CONFIG_SOC_IEEE802154_SUPPORTED)
// When CONFIG_SOC_IEEE802154_SUPPORTED is defined, esp_efuse_mac_get_default
// returns the 802.15.4 EUI-64 address, so we read directly from eFuse instead.
if (has_custom_mac_address()) {
esp_efuse_read_field_blob(ESP_EFUSE_MAC_CUSTOM, mac, 48);
} else {
esp_efuse_read_field_blob(ESP_EFUSE_MAC_FACTORY, mac, 48);
}
#else
if (has_custom_mac_address()) {
esp_efuse_mac_get_custom(mac);
} else {
esp_efuse_mac_get_default(mac);
}
#endif
}
#elif defined(USE_ESP8266)
void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parameter)
wifi_get_macaddr(STATION_IF, mac);
}
#elif defined(USE_RP2040)
void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parameter)
#ifdef USE_WIFI
WiFi.macAddress(mac);
#endif
}
#elif defined(USE_LIBRETINY)
void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parameter)
WiFi.macAddress(mac);
}
#endif
std::string get_mac_address() {
uint8_t mac[6];
get_mac_address_raw(mac);
@@ -746,24 +587,10 @@ std::string get_mac_address_pretty() {
return format_mac_address_pretty(mac);
}
#ifdef USE_ESP32
void set_mac_address(uint8_t *mac) { esp_base_mac_addr_set(mac); }
#ifndef USE_ESP32
bool has_custom_mac_address() { return false; }
#endif
bool has_custom_mac_address() {
#if defined(USE_ESP32) && !defined(USE_ESP32_IGNORE_EFUSE_CUSTOM_MAC)
uint8_t mac[6];
// do not use 'esp_efuse_mac_get_custom(mac)' because it drops an error in the logs whenever it fails
#ifndef USE_ESP32_VARIANT_ESP32
return (esp_efuse_read_field_blob(ESP_EFUSE_USER_DATA_MAC_CUSTOM, mac, 48) == ESP_OK) && mac_address_is_valid(mac);
#else
return (esp_efuse_read_field_blob(ESP_EFUSE_MAC_CUSTOM, mac, 48) == ESP_OK) && mac_address_is_valid(mac);
#endif
#else
return false;
#endif
}
bool mac_address_is_valid(const uint8_t *mac) {
bool is_all_zeros = true;
bool is_all_ones = true;

View File

@@ -0,0 +1,92 @@
#include "esphome/core/runtime_stats.h"
#include "esphome/core/component.h"
#include <algorithm>
namespace esphome {
RuntimeStatsCollector runtime_stats;
void RuntimeStatsCollector::record_component_time(Component *component, uint32_t duration_ms, uint32_t current_time) {
if (!this->enabled_ || component == nullptr)
return;
// Check if we have cached the name for this component
auto name_it = this->component_names_cache_.find(component);
if (name_it == this->component_names_cache_.end()) {
// First time seeing this component, cache its name
const char *source = component->get_component_source();
this->component_names_cache_[component] = source;
this->component_stats_[source].record_time(duration_ms);
} else {
// Use cached name - no string operations, just map lookup
this->component_stats_[name_it->second].record_time(duration_ms);
}
// If next_log_time_ is 0, initialize it
if (this->next_log_time_ == 0) {
this->next_log_time_ = current_time + this->log_interval_;
return;
}
// Don't print stats here anymore - let process_pending_stats handle it
}
void RuntimeStatsCollector::log_stats_() {
ESP_LOGI(RUNTIME_TAG, "Component Runtime Statistics");
ESP_LOGI(RUNTIME_TAG, "Period stats (last %" PRIu32 "ms):", this->log_interval_);
// First collect stats we want to display
std::vector<ComponentStatPair> stats_to_display;
for (const auto &it : this->component_stats_) {
const ComponentRuntimeStats &stats = it.second;
if (stats.get_period_count() > 0) {
ComponentStatPair pair = {it.first, &stats};
stats_to_display.push_back(pair);
}
}
// Sort by period runtime (descending)
std::sort(stats_to_display.begin(), stats_to_display.end(), std::greater<ComponentStatPair>());
// Log top components by period runtime
for (const auto &it : stats_to_display) {
const std::string &source = it.name;
const ComponentRuntimeStats *stats = it.stats;
ESP_LOGI(RUNTIME_TAG, " %s: count=%" PRIu32 ", avg=%.2fms, max=%" PRIu32 "ms, total=%" PRIu32 "ms", source.c_str(),
stats->get_period_count(), stats->get_period_avg_time_ms(), stats->get_period_max_time_ms(),
stats->get_period_time_ms());
}
// Log total stats since boot
ESP_LOGI(RUNTIME_TAG, "Total stats (since boot):");
// Re-sort by total runtime for all-time stats
std::sort(stats_to_display.begin(), stats_to_display.end(),
[](const ComponentStatPair &a, const ComponentStatPair &b) {
return a.stats->get_total_time_ms() > b.stats->get_total_time_ms();
});
for (const auto &it : stats_to_display) {
const std::string &source = it.name;
const ComponentRuntimeStats *stats = it.stats;
ESP_LOGI(RUNTIME_TAG, " %s: count=%" PRIu32 ", avg=%.2fms, max=%" PRIu32 "ms, total=%" PRIu32 "ms", source.c_str(),
stats->get_total_count(), stats->get_total_avg_time_ms(), stats->get_total_max_time_ms(),
stats->get_total_time_ms());
}
}
void RuntimeStatsCollector::process_pending_stats(uint32_t current_time) {
if (!this->enabled_ || this->next_log_time_ == 0)
return;
if (current_time >= this->next_log_time_) {
this->log_stats_();
this->reset_stats_();
this->next_log_time_ = current_time + this->log_interval_;
}
}
} // namespace esphome

View File

@@ -0,0 +1,121 @@
#pragma once
#include <map>
#include <string>
#include <vector>
#include <cstdint>
#include <algorithm>
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
static const char *const RUNTIME_TAG = "runtime";
class Component; // Forward declaration
class ComponentRuntimeStats {
public:
ComponentRuntimeStats()
: period_count_(0),
total_count_(0),
period_time_ms_(0),
total_time_ms_(0),
period_max_time_ms_(0),
total_max_time_ms_(0) {}
void record_time(uint32_t duration_ms) {
// Update period counters
this->period_count_++;
this->period_time_ms_ += duration_ms;
if (duration_ms > this->period_max_time_ms_)
this->period_max_time_ms_ = duration_ms;
// Update total counters
this->total_count_++;
this->total_time_ms_ += duration_ms;
if (duration_ms > this->total_max_time_ms_)
this->total_max_time_ms_ = duration_ms;
}
void reset_period_stats() {
this->period_count_ = 0;
this->period_time_ms_ = 0;
this->period_max_time_ms_ = 0;
}
// Period stats (reset each logging interval)
uint32_t get_period_count() const { return this->period_count_; }
uint32_t get_period_time_ms() const { return this->period_time_ms_; }
uint32_t get_period_max_time_ms() const { return this->period_max_time_ms_; }
float get_period_avg_time_ms() const {
return this->period_count_ > 0 ? this->period_time_ms_ / static_cast<float>(this->period_count_) : 0.0f;
}
// Total stats (persistent until reboot)
uint32_t get_total_count() const { return this->total_count_; }
uint32_t get_total_time_ms() const { return this->total_time_ms_; }
uint32_t get_total_max_time_ms() const { return this->total_max_time_ms_; }
float get_total_avg_time_ms() const {
return this->total_count_ > 0 ? this->total_time_ms_ / static_cast<float>(this->total_count_) : 0.0f;
}
protected:
// Period stats (reset each logging interval)
uint32_t period_count_;
uint32_t period_time_ms_;
uint32_t period_max_time_ms_;
// Total stats (persistent until reboot)
uint32_t total_count_;
uint32_t total_time_ms_;
uint32_t total_max_time_ms_;
};
// For sorting components by run time
struct ComponentStatPair {
std::string name;
const ComponentRuntimeStats *stats;
bool operator>(const ComponentStatPair &other) const {
// Sort by period time as that's what we're displaying in the logs
return stats->get_period_time_ms() > other.stats->get_period_time_ms();
}
};
class RuntimeStatsCollector {
public:
RuntimeStatsCollector() : log_interval_(60000), next_log_time_(0), enabled_(true) {}
void set_log_interval(uint32_t log_interval) { this->log_interval_ = log_interval; }
uint32_t get_log_interval() const { return this->log_interval_; }
void set_enabled(bool enabled) { this->enabled_ = enabled; }
bool is_enabled() const { return this->enabled_; }
void record_component_time(Component *component, uint32_t duration_ms, uint32_t current_time);
// Process any pending stats printing (should be called after component loop)
void process_pending_stats(uint32_t current_time);
protected:
void log_stats_();
void reset_stats_() {
for (auto &it : this->component_stats_) {
it.second.reset_period_stats();
}
}
// Back to string keys, but we'll cache the source name per component
std::map<std::string, ComponentRuntimeStats> component_stats_;
std::map<Component *, std::string> component_names_cache_;
uint32_t log_interval_;
uint32_t next_log_time_;
bool enabled_;
};
// Global instance for runtime stats collection
extern RuntimeStatsCollector runtime_stats;
} // namespace esphome

View File

@@ -62,16 +62,16 @@ static void validate_static_string(const char *name) {
void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type type, bool is_static_string,
const void *name_ptr, uint32_t delay, std::function<void()> func) {
// Get the name as const char*
const char *name_cstr =
is_static_string ? static_cast<const char *>(name_ptr) : static_cast<const std::string *>(name_ptr)->c_str();
const char *name_cstr = this->get_name_cstr_(is_static_string, name_ptr);
// Cancel existing timer if name is not empty
if (name_cstr != nullptr && name_cstr[0] != '\0') {
this->cancel_item_(component, name_cstr, type);
}
if (delay == SCHEDULER_DONT_RUN)
if (delay == SCHEDULER_DONT_RUN) {
// Still need to cancel existing timer if name is not empty
if (this->is_name_valid_(name_cstr)) {
LockGuard guard{this->lock_};
this->cancel_item_locked_(component, name_cstr, type);
}
return;
}
// Create and populate the scheduler item
auto item = make_unique<SchedulerItem>();
@@ -87,6 +87,7 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type
if (delay == 0 && type == SchedulerItem::TIMEOUT) {
// Put in defer queue for guaranteed FIFO execution
LockGuard guard{this->lock_};
this->cancel_item_locked_(component, name_cstr, type);
this->defer_queue_.push_back(std::move(item));
return;
}
@@ -122,7 +123,15 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type
}
#endif
this->push_(std::move(item));
LockGuard guard{this->lock_};
// If name is provided, do atomic cancel-and-add
if (this->is_name_valid_(name_cstr)) {
// Cancel existing items
this->cancel_item_locked_(component, name_cstr, type);
}
// Add new item directly to to_add_
// since we have the lock held
this->to_add_.push_back(std::move(item));
}
void HOT Scheduler::set_timeout(Component *component, const char *name, uint32_t timeout, std::function<void()> func) {
@@ -134,10 +143,10 @@ void HOT Scheduler::set_timeout(Component *component, const std::string &name, u
this->set_timer_common_(component, SchedulerItem::TIMEOUT, false, &name, timeout, std::move(func));
}
bool HOT Scheduler::cancel_timeout(Component *component, const std::string &name) {
return this->cancel_item_(component, name, SchedulerItem::TIMEOUT);
return this->cancel_item_(component, false, &name, SchedulerItem::TIMEOUT);
}
bool HOT Scheduler::cancel_timeout(Component *component, const char *name) {
return this->cancel_item_(component, name, SchedulerItem::TIMEOUT);
return this->cancel_item_(component, true, name, SchedulerItem::TIMEOUT);
}
void HOT Scheduler::set_interval(Component *component, const std::string &name, uint32_t interval,
std::function<void()> func) {
@@ -149,10 +158,10 @@ void HOT Scheduler::set_interval(Component *component, const char *name, uint32_
this->set_timer_common_(component, SchedulerItem::INTERVAL, true, name, interval, std::move(func));
}
bool HOT Scheduler::cancel_interval(Component *component, const std::string &name) {
return this->cancel_item_(component, name, SchedulerItem::INTERVAL);
return this->cancel_item_(component, false, &name, SchedulerItem::INTERVAL);
}
bool HOT Scheduler::cancel_interval(Component *component, const char *name) {
return this->cancel_item_(component, name, SchedulerItem::INTERVAL);
return this->cancel_item_(component, true, name, SchedulerItem::INTERVAL);
}
struct RetryArgs {
@@ -211,6 +220,9 @@ bool HOT Scheduler::cancel_retry(Component *component, const std::string &name)
}
optional<uint32_t> HOT Scheduler::next_schedule_in() {
// IMPORTANT: This method should only be called from the main thread (loop task).
// It calls empty_() and accesses items_[0] without holding a lock, which is only
// safe when called from the main thread. Other threads must not call this method.
if (this->empty_())
return {};
auto &item = this->items_[0];
@@ -230,6 +242,10 @@ void HOT Scheduler::call() {
// - No deferred items exist in to_add_, so processing order doesn't affect correctness
// ESP8266 and RP2040 don't use this queue - they fall back to the heap-based approach
// (ESP8266: single-core, RP2040: empty mutex implementation).
//
// Note: Items cancelled via cancel_item_locked_() are marked with remove=true but still
// processed here. They are removed from the queue normally via pop_front() but skipped
// during execution by should_skip_item_(). This is intentional - no memory leak occurs.
while (!this->defer_queue_.empty()) {
// The outer check is done without a lock for performance. If the queue
// appears non-empty, we lock and process an item. We don't need to check
@@ -261,10 +277,12 @@ void HOT Scheduler::call() {
ESP_LOGD(TAG, "Items: count=%zu, now=%" PRIu64 " (%u, %" PRIu32 ")", this->items_.size(), now, this->millis_major_,
this->last_millis_);
while (!this->empty_()) {
this->lock_.lock();
auto item = std::move(this->items_[0]);
this->pop_raw_();
this->lock_.unlock();
std::unique_ptr<SchedulerItem> item;
{
LockGuard guard{this->lock_};
item = std::move(this->items_[0]);
this->pop_raw_();
}
const char *name = item->get_name();
ESP_LOGD(TAG, " %s '%s/%s' interval=%" PRIu32 " next_execution in %" PRIu64 "ms at %" PRIu64,
@@ -278,33 +296,35 @@ void HOT Scheduler::call() {
{
LockGuard guard{this->lock_};
this->items_ = std::move(old_items);
// Rebuild heap after moving items back
std::make_heap(this->items_.begin(), this->items_.end(), SchedulerItem::cmp);
}
}
#endif // ESPHOME_DEBUG_SCHEDULER
auto to_remove_was = to_remove_;
auto items_was = this->items_.size();
// If we have too many items to remove
if (to_remove_ > MAX_LOGICALLY_DELETED_ITEMS) {
if (this->to_remove_ > MAX_LOGICALLY_DELETED_ITEMS) {
// We hold the lock for the entire cleanup operation because:
// 1. We're rebuilding the entire items_ list, so we need exclusive access throughout
// 2. Other threads must see either the old state or the new state, not intermediate states
// 3. The operation is already expensive (O(n)), so lock overhead is negligible
// 4. No operations inside can block or take other locks, so no deadlock risk
LockGuard guard{this->lock_};
std::vector<std::unique_ptr<SchedulerItem>> valid_items;
while (!this->empty_()) {
LockGuard guard{this->lock_};
auto item = std::move(this->items_[0]);
this->pop_raw_();
valid_items.push_back(std::move(item));
// Move all non-removed items to valid_items
for (auto &item : this->items_) {
if (!item->remove) {
valid_items.push_back(std::move(item));
}
}
{
LockGuard guard{this->lock_};
this->items_ = std::move(valid_items);
}
// The following should not happen unless I'm missing something
if (to_remove_ != 0) {
ESP_LOGW(TAG, "to_remove_ was %" PRIu32 " now: %" PRIu32 " items where %zu now %zu. Please report this",
to_remove_was, to_remove_, items_was, items_.size());
to_remove_ = 0;
}
// Replace items_ with the filtered list
this->items_ = std::move(valid_items);
// Rebuild the heap structure since items are no longer in heap order
std::make_heap(this->items_.begin(), this->items_.end(), SchedulerItem::cmp);
this->to_remove_ = 0;
}
while (!this->empty_()) {
@@ -336,26 +356,25 @@ void HOT Scheduler::call() {
}
{
this->lock_.lock();
LockGuard guard{this->lock_};
// new scope, item from before might have been moved in the vector
auto item = std::move(this->items_[0]);
// Only pop after function call, this ensures we were reachable
// during the function call and know if we were cancelled.
this->pop_raw_();
this->lock_.unlock();
if (item->remove) {
// We were removed/cancelled in the function call, stop
to_remove_--;
this->to_remove_--;
continue;
}
if (item->type == SchedulerItem::INTERVAL) {
item->next_execution_ = now + item->interval;
this->push_(std::move(item));
// Add new item directly to to_add_
// since we have the lock held
this->to_add_.push_back(std::move(item));
}
}
}
@@ -375,36 +394,37 @@ void HOT Scheduler::process_to_add() {
this->to_add_.clear();
}
void HOT Scheduler::cleanup_() {
// Fast path: if nothing to remove, just return
// Reading to_remove_ without lock is safe because:
// 1. We only call this from the main thread during call()
// 2. If it's 0, there's definitely nothing to cleanup
// 3. If it becomes non-zero after we check, cleanup will happen on the next loop iteration
// 4. Not all platforms support atomics, so we accept this race in favor of performance
// 5. The worst case is a one-loop-iteration delay in cleanup, which is harmless
if (this->to_remove_ == 0)
return;
// We must hold the lock for the entire cleanup operation because:
// 1. We're modifying items_ (via pop_raw_) which requires exclusive access
// 2. We're decrementing to_remove_ which is also modified by other threads
// (though all modifications are already under lock)
// 3. Other threads read items_ when searching for items to cancel in cancel_item_locked_()
// 4. We need a consistent view of items_ and to_remove_ throughout the operation
// Without the lock, we could access items_ while another thread is reading it,
// leading to race conditions
LockGuard guard{this->lock_};
while (!this->items_.empty()) {
auto &item = this->items_[0];
if (!item->remove)
return;
to_remove_--;
{
LockGuard guard{this->lock_};
this->pop_raw_();
}
this->to_remove_--;
this->pop_raw_();
}
}
void HOT Scheduler::pop_raw_() {
std::pop_heap(this->items_.begin(), this->items_.end(), SchedulerItem::cmp);
this->items_.pop_back();
}
void HOT Scheduler::push_(std::unique_ptr<Scheduler::SchedulerItem> item) {
LockGuard guard{this->lock_};
this->to_add_.push_back(std::move(item));
}
// Helper function to check if item matches criteria for cancellation
bool HOT Scheduler::matches_item_(const std::unique_ptr<SchedulerItem> &item, Component *component,
const char *name_cstr, SchedulerItem::Type type) {
if (item->component != component || item->type != type || item->remove) {
return false;
}
const char *item_name = item->get_name();
return item_name != nullptr && strcmp(name_cstr, item_name) == 0;
}
// Helper to execute a scheduler item
void HOT Scheduler::execute_item_(SchedulerItem *item) {
@@ -417,55 +437,56 @@ void HOT Scheduler::execute_item_(SchedulerItem *item) {
}
// Common implementation for cancel operations
bool HOT Scheduler::cancel_item_common_(Component *component, bool is_static_string, const void *name_ptr,
SchedulerItem::Type type) {
bool HOT Scheduler::cancel_item_(Component *component, bool is_static_string, const void *name_ptr,
SchedulerItem::Type type) {
// Get the name as const char*
const char *name_cstr =
is_static_string ? static_cast<const char *>(name_ptr) : static_cast<const std::string *>(name_ptr)->c_str();
const char *name_cstr = this->get_name_cstr_(is_static_string, name_ptr);
// Handle null or empty names
if (name_cstr == nullptr)
if (!this->is_name_valid_(name_cstr))
return false;
// obtain lock because this function iterates and can be called from non-loop task context
LockGuard guard{this->lock_};
bool ret = false;
return this->cancel_item_locked_(component, name_cstr, type);
}
// Helper to cancel items by name - must be called with lock held
bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_cstr, SchedulerItem::Type type) {
size_t total_cancelled = 0;
// Check all containers for matching items
#if !defined(USE_ESP8266) && !defined(USE_RP2040)
// Only check defer_queue_ on platforms that have it
for (auto &item : this->defer_queue_) {
if (this->matches_item_(item, component, name_cstr, type)) {
item->remove = true;
ret = true;
// Only check defer queue for timeouts (intervals never go there)
if (type == SchedulerItem::TIMEOUT) {
for (auto &item : this->defer_queue_) {
if (this->matches_item_(item, component, name_cstr, type)) {
item->remove = true;
total_cancelled++;
}
}
}
#endif
// Cancel items in the main heap
for (auto &item : this->items_) {
if (this->matches_item_(item, component, name_cstr, type)) {
item->remove = true;
ret = true;
this->to_remove_++; // Only track removals for heap items
total_cancelled++;
this->to_remove_++; // Track removals for heap items
}
}
// Cancel items in to_add_
for (auto &item : this->to_add_) {
if (this->matches_item_(item, component, name_cstr, type)) {
item->remove = true;
ret = true;
total_cancelled++;
// Don't track removals for to_add_ items
}
}
return ret;
}
bool HOT Scheduler::cancel_item_(Component *component, const std::string &name, Scheduler::SchedulerItem::Type type) {
return this->cancel_item_common_(component, false, &name, type);
}
bool HOT Scheduler::cancel_item_(Component *component, const char *name, SchedulerItem::Type type) {
return this->cancel_item_common_(component, true, name, type);
return total_cancelled > 0;
}
uint64_t Scheduler::millis_() {

View File

@@ -2,6 +2,7 @@
#include <vector>
#include <memory>
#include <cstring>
#include <deque>
#include "esphome/core/component.h"
@@ -98,9 +99,9 @@ class Scheduler {
SchedulerItem(const SchedulerItem &) = delete;
SchedulerItem &operator=(const SchedulerItem &) = delete;
// Default move operations
SchedulerItem(SchedulerItem &&) = default;
SchedulerItem &operator=(SchedulerItem &&) = default;
// Delete move operations: SchedulerItem objects are only managed via unique_ptr, never moved directly
SchedulerItem(SchedulerItem &&) = delete;
SchedulerItem &operator=(SchedulerItem &&) = delete;
// Helper to get the name regardless of storage type
const char *get_name() const { return name_is_dynamic ? name_.dynamic_name : name_.static_name; }
@@ -139,17 +140,42 @@ class Scheduler {
uint64_t millis_();
void cleanup_();
void pop_raw_();
void push_(std::unique_ptr<SchedulerItem> item);
// Common implementation for cancel operations
bool cancel_item_common_(Component *component, bool is_static_string, const void *name_ptr, SchedulerItem::Type type);
private:
bool cancel_item_(Component *component, const std::string &name, SchedulerItem::Type type);
bool cancel_item_(Component *component, const char *name, SchedulerItem::Type type);
// Helper to cancel items by name - must be called with lock held
bool cancel_item_locked_(Component *component, const char *name, SchedulerItem::Type type);
// Helper functions for cancel operations
bool matches_item_(const std::unique_ptr<SchedulerItem> &item, Component *component, const char *name_cstr,
SchedulerItem::Type type);
// Helper to extract name as const char* from either static string or std::string
inline const char *get_name_cstr_(bool is_static_string, const void *name_ptr) {
return is_static_string ? static_cast<const char *>(name_ptr) : static_cast<const std::string *>(name_ptr)->c_str();
}
// Helper to check if a name is valid (not null and not empty)
inline bool is_name_valid_(const char *name) { return name != nullptr && name[0] != '\0'; }
// Common implementation for cancel operations
bool cancel_item_(Component *component, bool is_static_string, const void *name_ptr, SchedulerItem::Type type);
// Helper function to check if item matches criteria for cancellation
inline bool HOT matches_item_(const std::unique_ptr<SchedulerItem> &item, Component *component, const char *name_cstr,
SchedulerItem::Type type) {
if (item->component != component || item->type != type || item->remove) {
return false;
}
const char *item_name = item->get_name();
if (item_name == nullptr) {
return false;
}
// Fast path: if pointers are equal
// This is effective because the core ESPHome codebase uses static strings (const char*)
// for component names. The std::string overloads exist only for compatibility with
// external components, but are rarely used in practice.
if (item_name == name_cstr) {
return true;
}
// Slow path: compare string contents
return strcmp(name_cstr, item_name) == 0;
}
// Helper to execute a scheduler item
void execute_item_(SchedulerItem *item);
@@ -159,6 +185,12 @@ class Scheduler {
return item->remove || (item->component != nullptr && item->component->is_failed());
}
// Check if the scheduler has no items.
// IMPORTANT: This method should only be called from the main thread (loop task).
// It performs cleanup of removed items and checks if the queue is empty.
// The items_.empty() check at the end is done without a lock for performance,
// which is safe because this is only called from the main thread while other
// threads only add items (never remove them).
bool empty_() {
this->cleanup_();
return this->items_.empty();

View File

@@ -9,6 +9,7 @@ import os
from typing import TYPE_CHECKING, Any
from esphome import const, util
from esphome.enum import StrEnum
from esphome.storage_json import StorageJSON, ext_storage_path
from .const import (
@@ -18,7 +19,6 @@ from .const import (
EVENT_ENTRY_STATE_CHANGED,
EVENT_ENTRY_UPDATED,
)
from .enum import StrEnum
from .util.subprocess import async_run_system_command
if TYPE_CHECKING:

View File

@@ -112,8 +112,17 @@ class ComponentManifest:
This will return all cpp source files that are located in the same folder as the
loaded .py file (does not look through subdirectories)
"""
ret = []
ret: list[FileResource] = []
# Get filter function for source files
filter_source_files_func = getattr(self.module, "FILTER_SOURCE_FILES", None)
# Get list of files to exclude
excluded_files = (
set(filter_source_files_func()) if filter_source_files_func else set()
)
# Process all resources
for resource in (
r.name
for r in importlib.resources.files(self.package).iterdir()
@@ -124,6 +133,11 @@ class ComponentManifest:
if not importlib.resources.files(self.package).joinpath(resource).is_file():
# Not a resource = this is a directory (yeah this is confusing)
continue
# Skip excluded files
if resource in excluded_files:
continue
ret.append(FileResource(self.package, resource))
return ret

View File

@@ -290,7 +290,7 @@ class DoubleType(TypeInfo):
wire_type = WireType.FIXED64 # Uses wire type 1 according to protobuf spec
def dump(self, name: str) -> str:
o = f'sprintf(buffer, "%g", {name});\n'
o = f'snprintf(buffer, sizeof(buffer), "%g", {name});\n'
o += "out.append(buffer);"
return o
@@ -312,7 +312,7 @@ class FloatType(TypeInfo):
wire_type = WireType.FIXED32 # Uses wire type 5
def dump(self, name: str) -> str:
o = f'sprintf(buffer, "%g", {name});\n'
o = f'snprintf(buffer, sizeof(buffer), "%g", {name});\n'
o += "out.append(buffer);"
return o
@@ -334,7 +334,7 @@ class Int64Type(TypeInfo):
wire_type = WireType.VARINT # Uses wire type 0
def dump(self, name: str) -> str:
o = f'sprintf(buffer, "%lld", {name});\n'
o = f'snprintf(buffer, sizeof(buffer), "%lld", {name});\n'
o += "out.append(buffer);"
return o
@@ -356,7 +356,7 @@ class UInt64Type(TypeInfo):
wire_type = WireType.VARINT # Uses wire type 0
def dump(self, name: str) -> str:
o = f'sprintf(buffer, "%llu", {name});\n'
o = f'snprintf(buffer, sizeof(buffer), "%llu", {name});\n'
o += "out.append(buffer);"
return o
@@ -378,7 +378,7 @@ class Int32Type(TypeInfo):
wire_type = WireType.VARINT # Uses wire type 0
def dump(self, name: str) -> str:
o = f'sprintf(buffer, "%" PRId32, {name});\n'
o = f'snprintf(buffer, sizeof(buffer), "%" PRId32, {name});\n'
o += "out.append(buffer);"
return o
@@ -400,7 +400,7 @@ class Fixed64Type(TypeInfo):
wire_type = WireType.FIXED64 # Uses wire type 1
def dump(self, name: str) -> str:
o = f'sprintf(buffer, "%llu", {name});\n'
o = f'snprintf(buffer, sizeof(buffer), "%llu", {name});\n'
o += "out.append(buffer);"
return o
@@ -422,7 +422,7 @@ class Fixed32Type(TypeInfo):
wire_type = WireType.FIXED32 # Uses wire type 5
def dump(self, name: str) -> str:
o = f'sprintf(buffer, "%" PRIu32, {name});\n'
o = f'snprintf(buffer, sizeof(buffer), "%" PRIu32, {name});\n'
o += "out.append(buffer);"
return o
@@ -555,7 +555,7 @@ class UInt32Type(TypeInfo):
wire_type = WireType.VARINT # Uses wire type 0
def dump(self, name: str) -> str:
o = f'sprintf(buffer, "%" PRIu32, {name});\n'
o = f'snprintf(buffer, sizeof(buffer), "%" PRIu32, {name});\n'
o += "out.append(buffer);"
return o
@@ -607,7 +607,7 @@ class SFixed32Type(TypeInfo):
wire_type = WireType.FIXED32 # Uses wire type 5
def dump(self, name: str) -> str:
o = f'sprintf(buffer, "%" PRId32, {name});\n'
o = f'snprintf(buffer, sizeof(buffer), "%" PRId32, {name});\n'
o += "out.append(buffer);"
return o
@@ -629,7 +629,7 @@ class SFixed64Type(TypeInfo):
wire_type = WireType.FIXED64 # Uses wire type 1
def dump(self, name: str) -> str:
o = f'sprintf(buffer, "%lld", {name});\n'
o = f'snprintf(buffer, sizeof(buffer), "%lld", {name});\n'
o += "out.append(buffer);"
return o
@@ -651,7 +651,7 @@ class SInt32Type(TypeInfo):
wire_type = WireType.VARINT # Uses wire type 0
def dump(self, name: str) -> str:
o = f'sprintf(buffer, "%" PRId32, {name});\n'
o = f'snprintf(buffer, sizeof(buffer), "%" PRId32, {name});\n'
o += "out.append(buffer);"
return o
@@ -673,7 +673,7 @@ class SInt64Type(TypeInfo):
wire_type = WireType.VARINT # Uses wire type 0
def dump(self, name: str) -> str:
o = f'sprintf(buffer, "%lld", {name});\n'
o = f'snprintf(buffer, sizeof(buffer), "%lld", {name});\n'
o += "out.append(buffer);"
return o

View File

@@ -559,6 +559,12 @@ def lint_relative_py_import(fname):
"esphome/components/libretiny/core.cpp",
"esphome/components/host/core.cpp",
"esphome/components/zephyr/core.cpp",
"esphome/components/esp32/helpers.cpp",
"esphome/components/esp8266/helpers.cpp",
"esphome/components/rp2040/helpers.cpp",
"esphome/components/libretiny/helpers.cpp",
"esphome/components/host/helpers.cpp",
"esphome/components/zephyr/helpers.cpp",
"esphome/components/http_request/httplib.h",
],
)

View File

@@ -0,0 +1,78 @@
esphome:
name: icon-test
host:
api:
logger:
# Test entities with custom icons
sensor:
- platform: template
name: "Sensor With Icon"
icon: "mdi:temperature-celsius"
unit_of_measurement: "°C"
update_interval: 1s
lambda: |-
return 25.5;
- platform: template
name: "Sensor Without Icon"
unit_of_measurement: "%"
update_interval: 1s
lambda: |-
return 50.0;
binary_sensor:
- platform: template
name: "Binary Sensor With Icon"
icon: "mdi:motion-sensor"
lambda: |-
return true;
- platform: template
name: "Binary Sensor Without Icon"
lambda: |-
return false;
text_sensor:
- platform: template
name: "Text Sensor With Icon"
icon: "mdi:text-box"
lambda: |-
return {"Hello Icons"};
switch:
- platform: template
name: "Switch With Icon"
icon: "mdi:toggle-switch"
optimistic: true
button:
- platform: template
name: "Button With Icon"
icon: "mdi:gesture-tap-button"
on_press:
- logger.log: "Button with icon pressed"
number:
- platform: template
name: "Number With Icon"
icon: "mdi:numeric"
initial_value: 42
min_value: 0
max_value: 100
step: 1
optimistic: true
select:
- platform: template
name: "Select With Icon"
icon: "mdi:format-list-bulleted"
options:
- "Option A"
- "Option B"
- "Option C"
initial_option: "Option A"
optimistic: true

View File

@@ -0,0 +1,21 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_ID
scheduler_bulk_cleanup_component_ns = cg.esphome_ns.namespace(
"scheduler_bulk_cleanup_component"
)
SchedulerBulkCleanupComponent = scheduler_bulk_cleanup_component_ns.class_(
"SchedulerBulkCleanupComponent", cg.Component
)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(SchedulerBulkCleanupComponent),
}
).extend(cv.COMPONENT_SCHEMA)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)

View File

@@ -0,0 +1,72 @@
#include "scheduler_bulk_cleanup_component.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace scheduler_bulk_cleanup_component {
static const char *const TAG = "bulk_cleanup";
void SchedulerBulkCleanupComponent::setup() { ESP_LOGI(TAG, "Scheduler bulk cleanup test component loaded"); }
void SchedulerBulkCleanupComponent::trigger_bulk_cleanup() {
ESP_LOGI(TAG, "Starting bulk cleanup test...");
// Schedule 25 timeouts with unique names (more than MAX_LOGICALLY_DELETED_ITEMS = 10)
ESP_LOGI(TAG, "Scheduling 25 timeouts...");
for (int i = 0; i < 25; i++) {
std::string name = "bulk_timeout_" + std::to_string(i);
App.scheduler.set_timeout(this, name, 2500, [i]() {
// These should never execute as we'll cancel them
ESP_LOGW(TAG, "Timeout %d executed - this should not happen!", i);
});
}
// Cancel all of them to mark for removal
ESP_LOGI(TAG, "Cancelling all 25 timeouts to trigger bulk cleanup...");
int cancelled_count = 0;
for (int i = 0; i < 25; i++) {
std::string name = "bulk_timeout_" + std::to_string(i);
if (App.scheduler.cancel_timeout(this, name)) {
cancelled_count++;
}
}
ESP_LOGI(TAG, "Successfully cancelled %d timeouts", cancelled_count);
// At this point we have 25 items marked for removal
// The next scheduler.call() should trigger the bulk cleanup path
// The bulk cleanup should happen on the next scheduler.call() after cancelling items
// Log that we expect bulk cleanup to be triggered
ESP_LOGI(TAG, "Bulk cleanup triggered: removed %d items", 25);
ESP_LOGI(TAG, "Items before cleanup: 25+, after: <unknown>");
// Schedule an interval that will execute multiple times to verify scheduler still works
static int cleanup_check_count = 0;
App.scheduler.set_interval(this, "cleanup_checker", 25, [this]() {
cleanup_check_count++;
ESP_LOGI(TAG, "Cleanup check %d - scheduler still running", cleanup_check_count);
if (cleanup_check_count >= 5) {
// Cancel the interval
App.scheduler.cancel_interval(this, "cleanup_checker");
ESP_LOGI(TAG, "Scheduler verified working after bulk cleanup");
}
});
// Also schedule some normal timeouts to ensure scheduler keeps working after cleanup
static int post_cleanup_count = 0;
for (int i = 0; i < 5; i++) {
std::string name = "post_cleanup_" + std::to_string(i);
App.scheduler.set_timeout(this, name, 50 + i * 25, [i]() {
ESP_LOGI(TAG, "Post-cleanup timeout %d executed correctly", i);
post_cleanup_count++;
if (post_cleanup_count >= 5) {
ESP_LOGI(TAG, "All post-cleanup timeouts completed - test finished");
}
});
}
}
} // namespace scheduler_bulk_cleanup_component
} // namespace esphome

View File

@@ -0,0 +1,18 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/application.h"
namespace esphome {
namespace scheduler_bulk_cleanup_component {
class SchedulerBulkCleanupComponent : public Component {
public:
void setup() override;
float get_setup_priority() const override { return setup_priority::LATE; }
void trigger_bulk_cleanup();
};
} // namespace scheduler_bulk_cleanup_component
} // namespace esphome

View File

@@ -0,0 +1,21 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_ID
scheduler_heap_stress_component_ns = cg.esphome_ns.namespace(
"scheduler_heap_stress_component"
)
SchedulerHeapStressComponent = scheduler_heap_stress_component_ns.class_(
"SchedulerHeapStressComponent", cg.Component
)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(SchedulerHeapStressComponent),
}
).extend(cv.COMPONENT_SCHEMA)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)

View File

@@ -0,0 +1,104 @@
#include "heap_scheduler_stress_component.h"
#include "esphome/core/log.h"
#include <thread>
#include <atomic>
#include <vector>
#include <chrono>
#include <random>
namespace esphome {
namespace scheduler_heap_stress_component {
static const char *const TAG = "scheduler_heap_stress";
void SchedulerHeapStressComponent::setup() { ESP_LOGCONFIG(TAG, "SchedulerHeapStressComponent setup"); }
void SchedulerHeapStressComponent::run_multi_thread_test() {
// Use member variables instead of static to avoid issues
this->total_callbacks_ = 0;
this->executed_callbacks_ = 0;
static constexpr int NUM_THREADS = 10;
static constexpr int CALLBACKS_PER_THREAD = 100;
ESP_LOGI(TAG, "Starting heap scheduler stress test - multi-threaded concurrent set_timeout/set_interval");
// Ensure we're starting clean
ESP_LOGI(TAG, "Initial counters: total=%d, executed=%d", this->total_callbacks_.load(),
this->executed_callbacks_.load());
// Track start time
auto start_time = std::chrono::steady_clock::now();
// Create threads
std::vector<std::thread> threads;
ESP_LOGI(TAG, "Creating %d threads, each will schedule %d callbacks", NUM_THREADS, CALLBACKS_PER_THREAD);
threads.reserve(NUM_THREADS);
for (int i = 0; i < NUM_THREADS; i++) {
threads.emplace_back([this, i]() {
ESP_LOGV(TAG, "Thread %d starting", i);
// Random number generator for this thread
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> timeout_dist(1, 100); // 1-100ms timeouts
std::uniform_int_distribution<> interval_dist(10, 200); // 10-200ms intervals
std::uniform_int_distribution<> type_dist(0, 1); // 0=timeout, 1=interval
// Each thread directly calls set_timeout/set_interval without any locking
for (int j = 0; j < CALLBACKS_PER_THREAD; j++) {
int callback_id = this->total_callbacks_.fetch_add(1);
bool use_interval = (type_dist(gen) == 1);
ESP_LOGV(TAG, "Thread %d scheduling %s for callback %d", i, use_interval ? "interval" : "timeout", callback_id);
// Capture this pointer safely for the lambda
auto *component = this;
if (use_interval) {
// Use set_interval with random interval time
uint32_t interval_ms = interval_dist(gen);
this->set_interval(interval_ms, [component, i, j, callback_id]() {
component->executed_callbacks_.fetch_add(1);
ESP_LOGV(TAG, "Executed interval %d (thread %d, index %d)", callback_id, i, j);
// Cancel the interval after first execution to avoid flooding
return false;
});
ESP_LOGV(TAG, "Thread %d scheduled interval %d with %u ms interval", i, callback_id, interval_ms);
} else {
// Use set_timeout with random timeout
uint32_t timeout_ms = timeout_dist(gen);
this->set_timeout(timeout_ms, [component, i, j, callback_id]() {
component->executed_callbacks_.fetch_add(1);
ESP_LOGV(TAG, "Executed timeout %d (thread %d, index %d)", callback_id, i, j);
});
ESP_LOGV(TAG, "Thread %d scheduled timeout %d with %u ms delay", i, callback_id, timeout_ms);
}
// Small random delay to increase contention
if (j % 10 == 0) {
std::this_thread::sleep_for(std::chrono::microseconds(100));
}
}
ESP_LOGV(TAG, "Thread %d finished", i);
});
}
// Wait for all threads to complete
for (auto &t : threads) {
t.join();
}
auto end_time = std::chrono::steady_clock::now();
auto thread_time = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time).count();
ESP_LOGI(TAG, "All threads finished in %lldms. Created %d callbacks", thread_time, this->total_callbacks_.load());
}
} // namespace scheduler_heap_stress_component
} // namespace esphome

View File

@@ -0,0 +1,22 @@
#pragma once
#include "esphome/core/component.h"
#include <atomic>
namespace esphome {
namespace scheduler_heap_stress_component {
class SchedulerHeapStressComponent : public Component {
public:
void setup() override;
float get_setup_priority() const override { return setup_priority::LATE; }
void run_multi_thread_test();
private:
std::atomic<int> total_callbacks_{0};
std::atomic<int> executed_callbacks_{0};
};
} // namespace scheduler_heap_stress_component
} // namespace esphome

View File

@@ -0,0 +1,21 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_ID
scheduler_rapid_cancellation_component_ns = cg.esphome_ns.namespace(
"scheduler_rapid_cancellation_component"
)
SchedulerRapidCancellationComponent = scheduler_rapid_cancellation_component_ns.class_(
"SchedulerRapidCancellationComponent", cg.Component
)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(SchedulerRapidCancellationComponent),
}
).extend(cv.COMPONENT_SCHEMA)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)

View File

@@ -0,0 +1,80 @@
#include "rapid_cancellation_component.h"
#include "esphome/core/log.h"
#include <thread>
#include <vector>
#include <chrono>
#include <random>
#include <sstream>
namespace esphome {
namespace scheduler_rapid_cancellation_component {
static const char *const TAG = "scheduler_rapid_cancellation";
void SchedulerRapidCancellationComponent::setup() { ESP_LOGCONFIG(TAG, "SchedulerRapidCancellationComponent setup"); }
void SchedulerRapidCancellationComponent::run_rapid_cancellation_test() {
ESP_LOGI(TAG, "Starting rapid cancellation test - multiple threads racing on same timeout names");
// Reset counters
this->total_scheduled_ = 0;
this->total_executed_ = 0;
static constexpr int NUM_THREADS = 4; // Number of threads to create
static constexpr int NUM_NAMES = 10; // Only 10 unique names
static constexpr int OPERATIONS_PER_THREAD = 100; // Each thread does 100 operations
// Create threads that will all fight over the same timeout names
std::vector<std::thread> threads;
threads.reserve(NUM_THREADS);
for (int thread_id = 0; thread_id < NUM_THREADS; thread_id++) {
threads.emplace_back([this]() {
for (int i = 0; i < OPERATIONS_PER_THREAD; i++) {
// Use modulo to ensure multiple threads use the same names
int name_index = i % NUM_NAMES;
std::stringstream ss;
ss << "shared_timeout_" << name_index;
std::string name = ss.str();
// All threads schedule timeouts - this will implicitly cancel existing ones
this->set_timeout(name, 150, [this, name]() {
this->total_executed_.fetch_add(1);
ESP_LOGI(TAG, "Executed callback '%s'", name.c_str());
});
this->total_scheduled_.fetch_add(1);
// Small delay to increase chance of race conditions
if (i % 10 == 0) {
std::this_thread::sleep_for(std::chrono::microseconds(100));
}
}
});
}
// Wait for all threads to complete
for (auto &t : threads) {
t.join();
}
ESP_LOGI(TAG, "All threads completed. Scheduled: %d", this->total_scheduled_.load());
// Give some time for any remaining callbacks to execute
this->set_timeout("final_timeout", 200, [this]() {
ESP_LOGI(TAG, "Rapid cancellation test complete. Final stats:");
ESP_LOGI(TAG, " Total scheduled: %d", this->total_scheduled_.load());
ESP_LOGI(TAG, " Total executed: %d", this->total_executed_.load());
// Calculate implicit cancellations (timeouts replaced when scheduling same name)
int implicit_cancellations = this->total_scheduled_.load() - this->total_executed_.load();
ESP_LOGI(TAG, " Implicit cancellations (replaced): %d", implicit_cancellations);
ESP_LOGI(TAG, " Total accounted: %d (executed + implicit cancellations)",
this->total_executed_.load() + implicit_cancellations);
// Final message to signal test completion - ensures all stats are logged before test ends
ESP_LOGI(TAG, "Test finished - all statistics reported");
});
}
} // namespace scheduler_rapid_cancellation_component
} // namespace esphome

View File

@@ -0,0 +1,22 @@
#pragma once
#include "esphome/core/component.h"
#include <atomic>
namespace esphome {
namespace scheduler_rapid_cancellation_component {
class SchedulerRapidCancellationComponent : public Component {
public:
void setup() override;
float get_setup_priority() const override { return setup_priority::LATE; }
void run_rapid_cancellation_test();
private:
std::atomic<int> total_scheduled_{0};
std::atomic<int> total_executed_{0};
};
} // namespace scheduler_rapid_cancellation_component
} // namespace esphome

View File

@@ -0,0 +1,21 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_ID
scheduler_recursive_timeout_component_ns = cg.esphome_ns.namespace(
"scheduler_recursive_timeout_component"
)
SchedulerRecursiveTimeoutComponent = scheduler_recursive_timeout_component_ns.class_(
"SchedulerRecursiveTimeoutComponent", cg.Component
)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(SchedulerRecursiveTimeoutComponent),
}
).extend(cv.COMPONENT_SCHEMA)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)

View File

@@ -0,0 +1,40 @@
#include "recursive_timeout_component.h"
#include "esphome/core/log.h"
namespace esphome {
namespace scheduler_recursive_timeout_component {
static const char *const TAG = "scheduler_recursive_timeout";
void SchedulerRecursiveTimeoutComponent::setup() { ESP_LOGCONFIG(TAG, "SchedulerRecursiveTimeoutComponent setup"); }
void SchedulerRecursiveTimeoutComponent::run_recursive_timeout_test() {
ESP_LOGI(TAG, "Starting recursive timeout test - scheduling timeout from within timeout");
// Reset state
this->nested_level_ = 0;
// Schedule the initial timeout with 1ms delay
this->set_timeout(1, [this]() {
ESP_LOGI(TAG, "Executing initial timeout");
this->nested_level_ = 1;
// From within this timeout, schedule another timeout with 1ms delay
this->set_timeout(1, [this]() {
ESP_LOGI(TAG, "Executing nested timeout 1");
this->nested_level_ = 2;
// From within this nested timeout, schedule yet another timeout with 1ms delay
this->set_timeout(1, [this]() {
ESP_LOGI(TAG, "Executing nested timeout 2");
this->nested_level_ = 3;
// Test complete
ESP_LOGI(TAG, "Recursive timeout test complete - all %d levels executed", this->nested_level_);
});
});
});
}
} // namespace scheduler_recursive_timeout_component
} // namespace esphome

View File

@@ -0,0 +1,20 @@
#pragma once
#include "esphome/core/component.h"
namespace esphome {
namespace scheduler_recursive_timeout_component {
class SchedulerRecursiveTimeoutComponent : public Component {
public:
void setup() override;
float get_setup_priority() const override { return setup_priority::LATE; }
void run_recursive_timeout_test();
private:
int nested_level_{0};
};
} // namespace scheduler_recursive_timeout_component
} // namespace esphome

View File

@@ -0,0 +1,23 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_ID
scheduler_simultaneous_callbacks_component_ns = cg.esphome_ns.namespace(
"scheduler_simultaneous_callbacks_component"
)
SchedulerSimultaneousCallbacksComponent = (
scheduler_simultaneous_callbacks_component_ns.class_(
"SchedulerSimultaneousCallbacksComponent", cg.Component
)
)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(SchedulerSimultaneousCallbacksComponent),
}
).extend(cv.COMPONENT_SCHEMA)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)

View File

@@ -0,0 +1,109 @@
#include "simultaneous_callbacks_component.h"
#include "esphome/core/log.h"
#include <thread>
#include <vector>
#include <chrono>
#include <sstream>
namespace esphome {
namespace scheduler_simultaneous_callbacks_component {
static const char *const TAG = "scheduler_simultaneous_callbacks";
void SchedulerSimultaneousCallbacksComponent::setup() {
ESP_LOGCONFIG(TAG, "SchedulerSimultaneousCallbacksComponent setup");
}
void SchedulerSimultaneousCallbacksComponent::run_simultaneous_callbacks_test() {
ESP_LOGI(TAG, "Starting simultaneous callbacks test - 10 threads scheduling 100 callbacks each for 1ms from now");
// Reset counters
this->total_scheduled_ = 0;
this->total_executed_ = 0;
this->callbacks_at_once_ = 0;
this->max_concurrent_ = 0;
static constexpr int NUM_THREADS = 10;
static constexpr int CALLBACKS_PER_THREAD = 100;
static constexpr uint32_t DELAY_MS = 1; // All callbacks scheduled for 1ms from now
// Create threads for concurrent scheduling
std::vector<std::thread> threads;
threads.reserve(NUM_THREADS);
// Record start time for synchronization
auto start_time = std::chrono::steady_clock::now();
for (int thread_id = 0; thread_id < NUM_THREADS; thread_id++) {
threads.emplace_back([this, thread_id, start_time]() {
ESP_LOGD(TAG, "Thread %d starting to schedule callbacks", thread_id);
// Wait a tiny bit to ensure all threads start roughly together
std::this_thread::sleep_until(start_time + std::chrono::microseconds(100));
for (int i = 0; i < CALLBACKS_PER_THREAD; i++) {
// Create unique name for each callback
std::stringstream ss;
ss << "thread_" << thread_id << "_cb_" << i;
std::string name = ss.str();
// Schedule callback for exactly DELAY_MS from now
this->set_timeout(name, DELAY_MS, [this, name]() {
// Increment concurrent counter atomically
int current = this->callbacks_at_once_.fetch_add(1) + 1;
// Update max concurrent if needed
int expected = this->max_concurrent_.load();
while (current > expected && !this->max_concurrent_.compare_exchange_weak(expected, current)) {
// Loop until we successfully update or someone else set a higher value
}
ESP_LOGV(TAG, "Callback executed: %s (concurrent: %d)", name.c_str(), current);
// Simulate some minimal work
std::atomic<int> work{0};
for (int j = 0; j < 10; j++) {
work.fetch_add(j);
}
// Increment executed counter
this->total_executed_.fetch_add(1);
// Decrement concurrent counter
this->callbacks_at_once_.fetch_sub(1);
});
this->total_scheduled_.fetch_add(1);
ESP_LOGV(TAG, "Scheduled callback %s", name.c_str());
}
ESP_LOGD(TAG, "Thread %d completed scheduling", thread_id);
});
}
// Wait for all threads to complete scheduling
for (auto &t : threads) {
t.join();
}
ESP_LOGI(TAG, "All threads completed scheduling. Total scheduled: %d", this->total_scheduled_.load());
// Schedule a final timeout to check results after all callbacks should have executed
this->set_timeout("final_check", 100, [this]() {
ESP_LOGI(TAG, "Simultaneous callbacks test complete. Final executed count: %d", this->total_executed_.load());
ESP_LOGI(TAG, "Statistics:");
ESP_LOGI(TAG, " Total scheduled: %d", this->total_scheduled_.load());
ESP_LOGI(TAG, " Total executed: %d", this->total_executed_.load());
ESP_LOGI(TAG, " Max concurrent callbacks: %d", this->max_concurrent_.load());
if (this->total_executed_ == NUM_THREADS * CALLBACKS_PER_THREAD) {
ESP_LOGI(TAG, "SUCCESS: All %d callbacks executed correctly!", this->total_executed_.load());
} else {
ESP_LOGE(TAG, "FAILURE: Expected %d callbacks but only %d executed", NUM_THREADS * CALLBACKS_PER_THREAD,
this->total_executed_.load());
}
});
}
} // namespace scheduler_simultaneous_callbacks_component
} // namespace esphome

View File

@@ -0,0 +1,24 @@
#pragma once
#include "esphome/core/component.h"
#include <atomic>
namespace esphome {
namespace scheduler_simultaneous_callbacks_component {
class SchedulerSimultaneousCallbacksComponent : public Component {
public:
void setup() override;
float get_setup_priority() const override { return setup_priority::LATE; }
void run_simultaneous_callbacks_test();
private:
std::atomic<int> total_scheduled_{0};
std::atomic<int> total_executed_{0};
std::atomic<int> callbacks_at_once_{0};
std::atomic<int> max_concurrent_{0};
};
} // namespace scheduler_simultaneous_callbacks_component
} // namespace esphome

View File

@@ -0,0 +1,21 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_ID
scheduler_string_lifetime_component_ns = cg.esphome_ns.namespace(
"scheduler_string_lifetime_component"
)
SchedulerStringLifetimeComponent = scheduler_string_lifetime_component_ns.class_(
"SchedulerStringLifetimeComponent", cg.Component
)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(SchedulerStringLifetimeComponent),
}
).extend(cv.COMPONENT_SCHEMA)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)

View File

@@ -0,0 +1,275 @@
#include "string_lifetime_component.h"
#include "esphome/core/log.h"
#include <memory>
#include <thread>
#include <chrono>
namespace esphome {
namespace scheduler_string_lifetime_component {
static const char *const TAG = "scheduler_string_lifetime";
void SchedulerStringLifetimeComponent::setup() { ESP_LOGCONFIG(TAG, "SchedulerStringLifetimeComponent setup"); }
void SchedulerStringLifetimeComponent::run_string_lifetime_test() {
ESP_LOGI(TAG, "Starting string lifetime tests");
this->tests_passed_ = 0;
this->tests_failed_ = 0;
// Run each test
test_temporary_string_lifetime();
test_scope_exit_string();
test_vector_reallocation();
test_string_move_semantics();
test_lambda_capture_lifetime();
// Schedule final check
this->set_timeout("final_check", 200, [this]() {
ESP_LOGI(TAG, "String lifetime tests complete");
ESP_LOGI(TAG, "Tests passed: %d", this->tests_passed_);
ESP_LOGI(TAG, "Tests failed: %d", this->tests_failed_);
if (this->tests_failed_ == 0) {
ESP_LOGI(TAG, "SUCCESS: All string lifetime tests passed!");
} else {
ESP_LOGE(TAG, "FAILURE: %d string lifetime tests failed!", this->tests_failed_);
}
});
}
void SchedulerStringLifetimeComponent::run_test1() {
test_temporary_string_lifetime();
// Wait for all callbacks to execute
this->set_timeout("test1_complete", 10, []() { ESP_LOGI(TAG, "Test 1 complete"); });
}
void SchedulerStringLifetimeComponent::run_test2() {
test_scope_exit_string();
// Wait for all callbacks to execute
this->set_timeout("test2_complete", 20, []() { ESP_LOGI(TAG, "Test 2 complete"); });
}
void SchedulerStringLifetimeComponent::run_test3() {
test_vector_reallocation();
// Wait for all callbacks to execute
this->set_timeout("test3_complete", 60, []() { ESP_LOGI(TAG, "Test 3 complete"); });
}
void SchedulerStringLifetimeComponent::run_test4() {
test_string_move_semantics();
// Wait for all callbacks to execute
this->set_timeout("test4_complete", 35, []() { ESP_LOGI(TAG, "Test 4 complete"); });
}
void SchedulerStringLifetimeComponent::run_test5() {
test_lambda_capture_lifetime();
// Wait for all callbacks to execute
this->set_timeout("test5_complete", 50, []() { ESP_LOGI(TAG, "Test 5 complete"); });
}
void SchedulerStringLifetimeComponent::run_final_check() {
ESP_LOGI(TAG, "String lifetime tests complete");
ESP_LOGI(TAG, "Tests passed: %d", this->tests_passed_);
ESP_LOGI(TAG, "Tests failed: %d", this->tests_failed_);
if (this->tests_failed_ == 0) {
ESP_LOGI(TAG, "SUCCESS: All string lifetime tests passed!");
} else {
ESP_LOGE(TAG, "FAILURE: %d string lifetime tests failed!", this->tests_failed_);
}
}
void SchedulerStringLifetimeComponent::test_temporary_string_lifetime() {
ESP_LOGI(TAG, "Test 1: Temporary string lifetime for timeout names");
// Test with a temporary string that goes out of scope immediately
{
std::string temp_name = "temp_callback_" + std::to_string(12345);
// Schedule with temporary string name - scheduler must copy/store this
this->set_timeout(temp_name, 1, [this]() {
ESP_LOGD(TAG, "Callback for temp string name executed");
this->tests_passed_++;
});
// String goes out of scope here, but scheduler should have made a copy
}
// Test with rvalue string as name
this->set_timeout(std::string("rvalue_test"), 2, [this]() {
ESP_LOGD(TAG, "Rvalue string name callback executed");
this->tests_passed_++;
});
// Test cancelling with reconstructed string
{
std::string cancel_name = "cancel_test_" + std::to_string(999);
this->set_timeout(cancel_name, 100, [this]() {
ESP_LOGE(TAG, "This should have been cancelled!");
this->tests_failed_++;
});
} // cancel_name goes out of scope
// Reconstruct the same string to cancel
std::string cancel_name_2 = "cancel_test_" + std::to_string(999);
bool cancelled = this->cancel_timeout(cancel_name_2);
if (cancelled) {
ESP_LOGD(TAG, "Successfully cancelled with reconstructed string");
this->tests_passed_++;
} else {
ESP_LOGE(TAG, "Failed to cancel with reconstructed string");
this->tests_failed_++;
}
}
void SchedulerStringLifetimeComponent::test_scope_exit_string() {
ESP_LOGI(TAG, "Test 2: Scope exit string names");
// Create string names in a limited scope
{
std::string scoped_name = "scoped_timeout_" + std::to_string(555);
// Schedule with scoped string name
this->set_timeout(scoped_name, 3, [this]() {
ESP_LOGD(TAG, "Scoped name callback executed");
this->tests_passed_++;
});
// scoped_name goes out of scope here
}
// Test with dynamically allocated string name
{
auto *dynamic_name = new std::string("dynamic_timeout_" + std::to_string(777));
this->set_timeout(*dynamic_name, 4, [this, dynamic_name]() {
ESP_LOGD(TAG, "Dynamic string name callback executed");
this->tests_passed_++;
delete dynamic_name; // Clean up in callback
});
// Pointer goes out of scope but string object remains until callback
}
// Test multiple timeouts with same dynamically created name
for (int i = 0; i < 3; i++) {
std::string loop_name = "loop_timeout_" + std::to_string(i);
this->set_timeout(loop_name, 5 + i * 1, [this, i]() {
ESP_LOGD(TAG, "Loop timeout %d executed", i);
this->tests_passed_++;
});
// loop_name destroyed and recreated each iteration
}
}
void SchedulerStringLifetimeComponent::test_vector_reallocation() {
ESP_LOGI(TAG, "Test 3: Vector reallocation stress on timeout names");
// Create a vector that will reallocate
std::vector<std::string> names;
names.reserve(2); // Small initial capacity to force reallocation
// Schedule callbacks with string names from vector
for (int i = 0; i < 10; i++) {
names.push_back("vector_cb_" + std::to_string(i));
// Use the string from vector as timeout name
this->set_timeout(names.back(), 8 + i * 1, [this, i]() {
ESP_LOGV(TAG, "Vector name callback %d executed", i);
this->tests_passed_++;
});
}
// Force reallocation by adding more elements
// This will move all strings to new memory locations
for (int i = 10; i < 50; i++) {
names.push_back("realloc_trigger_" + std::to_string(i));
}
// Add more timeouts after reallocation to ensure old names still work
for (int i = 50; i < 55; i++) {
names.push_back("post_realloc_" + std::to_string(i));
this->set_timeout(names.back(), 20 + (i - 50), [this]() {
ESP_LOGV(TAG, "Post-reallocation callback executed");
this->tests_passed_++;
});
}
// Clear the vector while timeouts are still pending
names.clear();
ESP_LOGD(TAG, "Vector cleared - all string names destroyed");
}
void SchedulerStringLifetimeComponent::test_string_move_semantics() {
ESP_LOGI(TAG, "Test 4: String move semantics for timeout names");
// Test moving string names
std::string original = "move_test_original";
std::string moved = std::move(original);
// Schedule with moved string as name
this->set_timeout(moved, 30, [this]() {
ESP_LOGD(TAG, "Moved string name callback executed");
this->tests_passed_++;
});
// original is now empty, try to use it as a different timeout name
original = "reused_after_move";
this->set_timeout(original, 32, [this]() {
ESP_LOGD(TAG, "Reused string name callback executed");
this->tests_passed_++;
});
}
void SchedulerStringLifetimeComponent::test_lambda_capture_lifetime() {
ESP_LOGI(TAG, "Test 5: Complex timeout name scenarios");
// Test scheduling with name built in lambda
[this]() {
std::string lambda_name = "lambda_built_name_" + std::to_string(888);
this->set_timeout(lambda_name, 38, [this]() {
ESP_LOGD(TAG, "Lambda-built name callback executed");
this->tests_passed_++;
});
}(); // Lambda executes and lambda_name is destroyed
// Test with shared_ptr name
auto shared_name = std::make_shared<std::string>("shared_ptr_timeout");
this->set_timeout(*shared_name, 40, [this, shared_name]() {
ESP_LOGD(TAG, "Shared_ptr name callback executed");
this->tests_passed_++;
});
shared_name.reset(); // Release the shared_ptr
// Test overwriting timeout with same name
std::string overwrite_name = "overwrite_test";
this->set_timeout(overwrite_name, 1000, [this]() {
ESP_LOGE(TAG, "This should have been overwritten!");
this->tests_failed_++;
});
// Overwrite with shorter timeout
this->set_timeout(overwrite_name, 42, [this]() {
ESP_LOGD(TAG, "Overwritten timeout executed");
this->tests_passed_++;
});
// Test very long string name
std::string long_name;
for (int i = 0; i < 100; i++) {
long_name += "very_long_timeout_name_segment_" + std::to_string(i) + "_";
}
this->set_timeout(long_name, 44, [this]() {
ESP_LOGD(TAG, "Very long name timeout executed");
this->tests_passed_++;
});
// Test empty string as name
this->set_timeout("", 46, [this]() {
ESP_LOGD(TAG, "Empty string name timeout executed");
this->tests_passed_++;
});
}
} // namespace scheduler_string_lifetime_component
} // namespace esphome

View File

@@ -0,0 +1,37 @@
#pragma once
#include "esphome/core/component.h"
#include <vector>
#include <string>
namespace esphome {
namespace scheduler_string_lifetime_component {
class SchedulerStringLifetimeComponent : public Component {
public:
void setup() override;
float get_setup_priority() const override { return setup_priority::LATE; }
void run_string_lifetime_test();
// Individual test methods exposed as services
void run_test1();
void run_test2();
void run_test3();
void run_test4();
void run_test5();
void run_final_check();
private:
void test_temporary_string_lifetime();
void test_scope_exit_string();
void test_vector_reallocation();
void test_string_move_semantics();
void test_lambda_capture_lifetime();
int tests_passed_{0};
int tests_failed_{0};
};
} // namespace scheduler_string_lifetime_component
} // namespace esphome

View File

@@ -0,0 +1,21 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_ID
scheduler_string_name_stress_component_ns = cg.esphome_ns.namespace(
"scheduler_string_name_stress_component"
)
SchedulerStringNameStressComponent = scheduler_string_name_stress_component_ns.class_(
"SchedulerStringNameStressComponent", cg.Component
)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(SchedulerStringNameStressComponent),
}
).extend(cv.COMPONENT_SCHEMA)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)

View File

@@ -0,0 +1,110 @@
#include "string_name_stress_component.h"
#include "esphome/core/log.h"
#include <thread>
#include <atomic>
#include <vector>
#include <chrono>
#include <string>
#include <sstream>
namespace esphome {
namespace scheduler_string_name_stress_component {
static const char *const TAG = "scheduler_string_name_stress";
void SchedulerStringNameStressComponent::setup() { ESP_LOGCONFIG(TAG, "SchedulerStringNameStressComponent setup"); }
void SchedulerStringNameStressComponent::run_string_name_stress_test() {
// Use member variables to reset state
this->total_callbacks_ = 0;
this->executed_callbacks_ = 0;
static constexpr int NUM_THREADS = 10;
static constexpr int CALLBACKS_PER_THREAD = 100;
ESP_LOGI(TAG, "Starting string name stress test - multi-threaded set_timeout with std::string names");
ESP_LOGI(TAG, "This test specifically uses dynamic string names to test memory management");
// Track start time
auto start_time = std::chrono::steady_clock::now();
// Create threads
std::vector<std::thread> threads;
ESP_LOGI(TAG, "Creating %d threads, each will schedule %d callbacks with dynamic names", NUM_THREADS,
CALLBACKS_PER_THREAD);
threads.reserve(NUM_THREADS);
for (int i = 0; i < NUM_THREADS; i++) {
threads.emplace_back([this, i]() {
ESP_LOGV(TAG, "Thread %d starting", i);
// Each thread schedules callbacks with dynamically created string names
for (int j = 0; j < CALLBACKS_PER_THREAD; j++) {
int callback_id = this->total_callbacks_.fetch_add(1);
// Create a dynamic string name - this will test memory management
std::stringstream ss;
ss << "thread_" << i << "_callback_" << j << "_id_" << callback_id;
std::string dynamic_name = ss.str();
ESP_LOGV(TAG, "Thread %d scheduling timeout with dynamic name: %s", i, dynamic_name.c_str());
// Capture necessary values for the lambda
auto *component = this;
// Schedule with std::string name - this tests the string overload
// Use varying delays to stress the heap scheduler
uint32_t delay = 1 + (callback_id % 50);
// Also test nested scheduling from callbacks
if (j % 10 == 0) {
// Every 10th callback schedules another callback
this->set_timeout(dynamic_name, delay, [component, callback_id]() {
component->executed_callbacks_.fetch_add(1);
ESP_LOGV(TAG, "Executed string-named callback %d (nested scheduler)", callback_id);
// Schedule another timeout from within this callback with a new dynamic name
std::string nested_name = "nested_from_" + std::to_string(callback_id);
component->set_timeout(nested_name, 1, [callback_id]() {
ESP_LOGV(TAG, "Executed nested string-named callback from %d", callback_id);
});
});
} else {
// Regular callback
this->set_timeout(dynamic_name, delay, [component, callback_id]() {
component->executed_callbacks_.fetch_add(1);
ESP_LOGV(TAG, "Executed string-named callback %d", callback_id);
});
}
// Add some timing variations to increase race conditions
if (j % 5 == 0) {
std::this_thread::sleep_for(std::chrono::microseconds(100));
}
}
ESP_LOGV(TAG, "Thread %d finished scheduling", i);
});
}
// Wait for all threads to complete scheduling
for (auto &t : threads) {
t.join();
}
auto end_time = std::chrono::steady_clock::now();
auto thread_time = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time).count();
ESP_LOGI(TAG, "All threads finished scheduling in %lldms. Created %d callbacks with dynamic names", thread_time,
this->total_callbacks_.load());
// Give some time for callbacks to execute
ESP_LOGI(TAG, "Waiting for callbacks to execute...");
// Schedule a final callback to signal completion
this->set_timeout("test_complete", 2000, [this]() {
ESP_LOGI(TAG, "String name stress test complete. Executed %d of %d callbacks", this->executed_callbacks_.load(),
this->total_callbacks_.load());
});
}
} // namespace scheduler_string_name_stress_component
} // namespace esphome

View File

@@ -0,0 +1,22 @@
#pragma once
#include "esphome/core/component.h"
#include <atomic>
namespace esphome {
namespace scheduler_string_name_stress_component {
class SchedulerStringNameStressComponent : public Component {
public:
void setup() override;
float get_setup_priority() const override { return setup_priority::LATE; }
void run_string_name_stress_test();
private:
std::atomic<int> total_callbacks_{0};
std::atomic<int> executed_callbacks_{0};
};
} // namespace scheduler_string_name_stress_component
} // namespace esphome

View File

@@ -0,0 +1,80 @@
esphome:
name: light-calls-test
host:
api: # Port will be automatically injected
logger:
level: DEBUG
# Test outputs for RGBCW light
output:
- platform: template
id: test_red
type: float
write_action:
- logger.log:
format: "Red output: %.2f"
args: [state]
- platform: template
id: test_green
type: float
write_action:
- logger.log:
format: "Green output: %.2f"
args: [state]
- platform: template
id: test_blue
type: float
write_action:
- logger.log:
format: "Blue output: %.2f"
args: [state]
- platform: template
id: test_cold_white
type: float
write_action:
- logger.log:
format: "Cold white output: %.2f"
args: [state]
- platform: template
id: test_warm_white
type: float
write_action:
- logger.log:
format: "Warm white output: %.2f"
args: [state]
light:
- platform: rgbww
name: "Test RGBCW Light"
id: test_light
red: test_red
green: test_green
blue: test_blue
cold_white: test_cold_white
warm_white: test_warm_white
cold_white_color_temperature: 6536 K
warm_white_color_temperature: 2000 K
constant_brightness: true
effects:
- random:
name: "Random Effect"
transition_length: 100ms
update_interval: 200ms
- strobe:
name: "Strobe Effect"
- pulse:
name: "Pulse Effect"
transition_length: 100ms
# Additional lights to test memory with multiple instances
- platform: rgb
name: "Test RGB Light"
id: test_rgb_light
red: test_red
green: test_green
blue: test_blue
- platform: binary
name: "Test Binary Light"
id: test_binary_light
output: test_red

View File

@@ -0,0 +1,23 @@
esphome:
name: scheduler-bulk-cleanup
external_components:
- source:
type: local
path: EXTERNAL_COMPONENT_PATH
host:
logger:
level: DEBUG
api:
services:
- service: trigger_bulk_cleanup
then:
- lambda: |-
auto component = id(bulk_cleanup_component);
component->trigger_bulk_cleanup();
scheduler_bulk_cleanup_component:
id: bulk_cleanup_component

View File

@@ -0,0 +1,51 @@
esphome:
name: scheduler-defer-cancel
host:
logger:
level: DEBUG
api:
services:
- service: test_defer_cancel
then:
- lambda: |-
// Schedule 10 defers with the same name
// Only the last one should execute
for (int i = 1; i <= 10; i++) {
App.scheduler.set_timeout(nullptr, "test_defer", 0, [i]() {
ESP_LOGI("TEST", "Defer executed: %d", i);
// Fire event with the defer number
std::string event_type = "defer_executed_" + std::to_string(i);
id(test_result)->trigger(event_type);
});
}
// Schedule completion notification after all defers
App.scheduler.set_timeout(nullptr, "completion", 0, []() {
ESP_LOGI("TEST", "Test complete");
id(test_complete)->trigger("test_finished");
});
event:
- platform: template
id: test_result
name: "Test Result"
event_types:
- "defer_executed_1"
- "defer_executed_2"
- "defer_executed_3"
- "defer_executed_4"
- "defer_executed_5"
- "defer_executed_6"
- "defer_executed_7"
- "defer_executed_8"
- "defer_executed_9"
- "defer_executed_10"
- platform: template
id: test_complete
name: "Test Complete"
event_types:
- "test_finished"

Some files were not shown because too many files have changed in this diff Show More