Compare commits

..

1224 Commits

Author SHA1 Message Date
J. Nick Koston
5f81a54746 more 2025-08-19 20:27:52 -05:00
J. Nick Koston
d1b9902e41 Merge branch 'integration' into memory_api 2025-08-19 15:38:10 -05:00
J. Nick Koston
9554c82c0b Merge branch 'optimize_safemode_flash' into integration 2025-08-19 15:38:04 -05:00
J. Nick Koston
c5998690ba Merge branch 'integration' into memory_api 2025-08-19 09:48:25 -05:00
J. Nick Koston
084d8828a6 Merge branch 'slot_freed_too_early' into integration 2025-08-19 09:48:20 -05:00
J. Nick Koston
0c86241aed [bluetooth_proxy] Fix connection slot race by deferring slot release until GATT close 2025-08-19 09:40:21 -05:00
Jesse Hills
82718e62e7 Merge branch 'beta' into dev 2025-08-19 20:40:45 +12:00
Jesse Hills
fd07e1d979 Merge pull request #10298 from esphome/bump-2025.8.0b3
2025.8.0b3
2025-08-19 20:40:12 +12:00
Patrick
4dab9c4400 [pipsolar] fix faults_present, fix update interval (#10289) 2025-08-19 15:52:01 +12:00
Ben Winslow
7e23d865e6 [atm90e32] Only read 1 register per SPI transaction per datasheet. (#10258) 2025-08-19 15:45:30 +12:00
Jesse Hills
8f118232e4 [CI] Rename and expand needs-docs workflow (#10299) 2025-08-19 15:35:48 +12:00
J. Nick Koston
db10185c7e Merge branch 'integration' into memory_api 2025-08-18 21:07:11 -05:00
J. Nick Koston
9a7ffa4bbc Merge branch 'log_sensor' into integration 2025-08-18 21:07:04 -05:00
J. Nick Koston
9ec6b3144a Merge remote-tracking branch 'origin/log_sensor' into log_sensor 2025-08-18 21:06:34 -05:00
J. Nick Koston
70eb45b5d3 lint 2025-08-18 21:06:24 -05:00
Jesse Hills
23554cda06 Bump version to 2025.8.0b3 2025-08-19 13:09:22 +12:00
Ben Winslow
064385eac6 [nextion] Don't include terminating NUL in nextion text_sensor states (#10273) 2025-08-19 13:09:22 +12:00
Jesse Hills
6502ed70de [esp32] Write variant to sdkconfig file (#10267) 2025-08-19 13:09:22 +12:00
J. Nick Koston
bb894c3e32 [core] Fix scheduler race condition where cancelled items still execute (#10268) 2025-08-19 13:09:22 +12:00
Ben Winslow
c5858b7032 [core] Fix post-OTA logs display when using esphome run and MQTT (#10274) 2025-08-19 13:09:22 +12:00
Ben Winslow
99f57ecb73 [senseair] Discard 0 ppm readings with "Out Of Range" bit set. (#10275) 2025-08-19 13:09:22 +12:00
J. Nick Koston
cc6c892678 [esp32_ble] Store GATTC/GATTS param and small data inline to nearly eliminate heap allocations (#10249)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-19 13:09:22 +12:00
RFDarter
07a98d2525 [web_server] fix cover_all_json_generator wrong detail (#10252) 2025-08-19 13:09:22 +12:00
J. Nick Koston
e80f616366 [esp32_ble] Optimize BLE event memory usage by eliminating std::vector overhead (#10247)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-19 13:09:22 +12:00
J. Nick Koston
46be877594 [bluetooth_proxy] Remove redundant connection type check after V1 removal (#10208) 2025-08-19 13:09:21 +12:00
J. Nick Koston
ac8b48a53c [core] Trigger clean build when components are removed from configuration (#10235)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-19 13:09:21 +12:00
J. Nick Koston
7fdbd8528a [wifi] Automatically disable Enterprise WiFi support when EAP is not configured (#10242) 2025-08-19 13:09:21 +12:00
Katherine Whitlock
80970f972b Improve error reporting for add_library (#10226)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-19 13:09:21 +12:00
Jesse Hills
3c7865cd6f [esp32_ble] Add `USE_ESP32_BLE_UUID` when advertising is desired (#10230) 2025-08-19 13:09:21 +12:00
J. Nick Koston
7f34757ded Merge branch 'integration' into memory_api 2025-08-18 20:03:00 -05:00
J. Nick Koston
0c0e8491d2 Merge branch 'webserver_reduce_defer_calls' into integration 2025-08-18 20:02:53 -05:00
J. Nick Koston
1786934242 [web_server] Reduce flash usage by consolidating defer calls in switch and lock handlers 2025-08-18 20:01:42 -05:00
Ben Winslow
3a6a66537c [nextion] Don't include terminating NUL in nextion text_sensor states (#10273) 2025-08-18 19:20:13 -05:00
Jesse Hills
7118bea031 [esp32] Write variant to sdkconfig file (#10267) 2025-08-19 12:17:34 +12:00
J. Nick Koston
8fd430e423 Update esphome/components/sensor/sensor.h 2025-08-18 19:17:26 -05:00
J. Nick Koston
8dc3958b0c Update esphome/components/sensor/sensor.h 2025-08-18 19:17:19 -05:00
J. Nick Koston
bbd5ab1340 Merge branch 'integration' into memory_api 2025-08-18 18:33:43 -05:00
J. Nick Koston
63cd279cd4 Merge branch 'log_number' into integration 2025-08-18 18:33:26 -05:00
J. Nick Koston
6f6b38762a Merge branch 'log_button' into integration 2025-08-18 18:33:23 -05:00
J. Nick Koston
2dc57d6236 Merge branch 'log_binary_sensor' into integration 2025-08-18 18:33:20 -05:00
J. Nick Koston
deadb8d9be Merge branch 'log_sensor' into integration 2025-08-18 18:33:15 -05:00
J. Nick Koston
59c93cf3f1 preen 2025-08-18 18:32:50 -05:00
J. Nick Koston
9d25dd5dd2 [button] Convert LOG_BUTTON macro to function to reduce flash usage 2025-08-18 18:29:13 -05:00
J. Nick Koston
a21ee3c483 [binary_sensor] Convert LOG_BINARY_SENSOR macro to function to reduce flash usage 2025-08-18 18:19:53 -05:00
J. Nick Koston
8971e2e9a4 [number] Convert LOG_NUMBER macro to function to reduce flash usage 2025-08-18 18:14:55 -05:00
J. Nick Koston
e7fadef15c [sensor] Convert LOG_SENSOR macro to function to reduce flash usage 2025-08-18 18:00:46 -05:00
J. Nick Koston
67ae6ce00a tweak 2025-08-18 17:36:20 -05:00
J. Nick Koston
24cee8ae03 tweak 2025-08-18 17:35:21 -05:00
J. Nick Koston
58a9944601 tweak 2025-08-18 17:34:08 -05:00
J. Nick Koston
a999540d4b Merge branch 'integration' into memory_api 2025-08-18 17:13:49 -05:00
J. Nick Koston
aa608f3a92 Merge remote-tracking branch 'upstream/dev' into integration 2025-08-18 17:13:42 -05:00
J. Nick Koston
44bd8e5b54 [api] Optimize protobuf decode loop for better performance and maintainability (#10277) 2025-08-18 16:14:20 -05:00
J. Nick Koston
efaeb91803 [api] Mark APIConnection as final for compiler optimizations (#10279) 2025-08-18 16:01:45 -05:00
J. Nick Koston
761c6c6685 [api] Mark protobuf message classes as final to enable compiler optimizations (#10276) 2025-08-18 15:55:30 -05:00
J. Nick Koston
1f55486896 [api] Optimize APIFrameHelper virtual methods and mark implementations as final (#10278) 2025-08-18 15:55:11 -05:00
J. Nick Koston
727e04c424 Merge branch 'integration' into memory_api 2025-08-18 14:37:37 -05:00
J. Nick Koston
0ea3018bd1 Merge branch 'mdns_cleanups' into integration 2025-08-18 14:37:30 -05:00
J. Nick Koston
fd6002e334 [mdns] Reduce flash usage and prevent RAM over-allocation in service compilation 2025-08-18 14:36:41 -05:00
J. Nick Koston
6248c3d729 preen 2025-08-18 12:47:22 -05:00
J. Nick Koston
3d03053eb5 Merge branch 'optimize_safemode_flash' into memory_api 2025-08-18 12:42:22 -05:00
J. Nick Koston
571e6be404 [safe_mode] Reduce flash usage by 172 bytes through code optimization 2025-08-18 12:41:35 -05:00
J. Nick Koston
7d3a87c603 [safe_mode] Reduce flash usage by 172 bytes through code optimization 2025-08-18 12:40:24 -05:00
J. Nick Koston
be2a680e8f [safe_mode] Reduce flash usage by 172 bytes through code optimization 2025-08-18 12:39:33 -05:00
J. Nick Koston
a36942b760 [safe_mode] Reduce flash usage by 172 bytes through code optimization 2025-08-18 12:37:44 -05:00
J. Nick Koston
8332893661 Merge branch 'integration' into memory_api 2025-08-18 11:06:58 -05:00
J. Nick Koston
ed3884e5aa Merge branch 'bluetooth_connection_final' into integration 2025-08-18 11:06:53 -05:00
J. Nick Koston
fb3a01e84e might as well do both 2025-08-18 11:05:09 -05:00
J. Nick Koston
6818439109 [core] Fix scheduler race condition where cancelled items still execute (#10268) 2025-08-18 11:14:41 -04:00
J. Nick Koston
54696eac67 Merge branch 'integration' into memory_api 2025-08-18 09:02:43 -05:00
J. Nick Koston
60e27e3ed7 Merge branch 'more_zero_copy_api_followup' into integration 2025-08-18 09:02:36 -05:00
J. Nick Koston
dbda2d5b6a Merge branch 'integration' into memory_api 2025-08-18 08:29:23 -05:00
J. Nick Koston
23fa231ca2 Merge branch 'decode_cleanup' into integration 2025-08-18 08:29:17 -05:00
J. Nick Koston
a9227148f5 review comments 2025-08-18 08:28:58 -05:00
J. Nick Koston
0a77423073 [esp8266] Replace std::vector with std::unique_ptr in preferences to save flash (#10245) 2025-08-18 09:01:39 -04:00
J. Nick Koston
7e67f0432d Merge branch 'integration' into memory_api 2025-08-18 01:07:43 -05:00
J. Nick Koston
b249cc25d9 Merge branch 'bluetooth_connection_final' into integration 2025-08-18 01:07:35 -05:00
J. Nick Koston
5678621cd5 [bluetooth_proxy] Mark BluetoothConnection as final for compiler optimizations 2025-08-18 01:06:47 -05:00
J. Nick Koston
c9932342eb Merge branch 'integration' into memory_api 2025-08-18 01:01:20 -05:00
J. Nick Koston
e68a44bcba Merge branch 'api_connection_final' into integration 2025-08-18 01:01:12 -05:00
J. Nick Koston
af87e27382 [api] Mark APIConnection as final for compiler optimizations 2025-08-18 01:00:12 -05:00
J. Nick Koston
f556ab1515 Merge branch 'frame_helper_opt' into memory_api 2025-08-18 00:57:04 -05:00
J. Nick Koston
76b6d77337 Merge branch 'frame_helper_opt' into integration 2025-08-18 00:56:57 -05:00
J. Nick Koston
134526e0ec [api] Optimize APIFrameHelper virtual methods and mark implementations as final 2025-08-18 00:56:19 -05:00
J. Nick Koston
cb157822d0 Merge branch 'decode_cleanup' into memory_api 2025-08-17 22:22:50 -05:00
J. Nick Koston
d7b28752e0 Merge branch 'decode_cleanup' into integration 2025-08-17 22:22:41 -05:00
J. Nick Koston
a2ad2dd10e [api] Optimize protobuf decode loop for better performance and maintainability 2025-08-17 22:21:51 -05:00
J. Nick Koston
4ee84b6d53 Merge remote-tracking branch 'origin/libretiny_no_vector' into memory_api 2025-08-17 21:40:59 -05:00
J. Nick Koston
0a483012ae Update esphome/components/libretiny/preferences.cpp 2025-08-17 21:40:45 -05:00
J. Nick Koston
b12666f356 Merge branch 'integration' into memory_api 2025-08-17 21:38:18 -05:00
J. Nick Koston
a82eeabbc8 Merge branch 'libretiny_no_vector' into memory_api 2025-08-17 21:37:42 -05:00
J. Nick Koston
6b9fc783b6 Merge remote-tracking branch 'upstream/libretiny_no_vector' into integration 2025-08-17 21:37:32 -05:00
Ben Winslow
c29f8d0187 [core] Fix post-OTA logs display when using esphome run and MQTT (#10274) 2025-08-17 21:36:35 -05:00
J. Nick Koston
2b23899750 Merge branch 'integration' into memory_api 2025-08-17 21:15:01 -05:00
J. Nick Koston
afc2ba066e Merge remote-tracking branch 'origin/atomic_remove' into integration 2025-08-17 21:14:54 -05:00
J. Nick Koston
b0e8aba426 Merge branch 'integration' into memory_api 2025-08-17 21:13:44 -05:00
J. Nick Koston
898b7b079e Merge branch 'final_proto' into integration 2025-08-17 21:13:34 -05:00
J. Nick Koston
37e36a3d1d [api] Mark protobuf message classes as final to enable compiler optimizations 2025-08-17 21:12:38 -05:00
Ben Winslow
2a3f80a82c [senseair] Discard 0 ppm readings with "Out Of Range" bit set. (#10275) 2025-08-18 14:09:42 +12:00
J. Nick Koston
89732f30f4 [libretiny] Optimize preferences is_changed() by replacing temporary vector with unique_ptr 2025-08-17 18:46:53 -04:00
J. Nick Koston
c56fd00a7c cleanup 2025-08-17 16:28:32 -04:00
J. Nick Koston
e06dbffe9f fix 2025-08-17 16:12:53 -04:00
J. Nick Koston
5b674dc28c atomic remove 2025-08-17 16:09:57 -04:00
J. Nick Koston
75f3adcd95 [esp32_ble] Store GATTC/GATTS param and small data inline to nearly eliminate heap allocations (#10249)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-18 07:49:50 +12:00
J. Nick Koston
17467150d8 Merge branch 'integration' into memory_api 2025-08-17 08:43:10 -04:00
J. Nick Koston
cb1d4830d8 Merge remote-tracking branch 'upstream/dev' into integration 2025-08-17 08:42:46 -04:00
J. Nick Koston
1666aae105 Merge branch 'integration' into memory_api 2025-08-16 09:45:27 -04:00
J. Nick Koston
05a0380a9f Merge branch 'object_id_alloc' into integration 2025-08-16 09:45:17 -04:00
J. Nick Koston
0b004a7d9b tweak 2025-08-16 09:38:00 -04:00
J. Nick Koston
15fca7dea8 Avoid object_id string allocations for all entity info API messages 2025-08-16 09:35:12 -04:00
J. Nick Koston
29daef230d [api] Add zero-copy StringRef methods for compilation_time and effect_name 2025-08-15 23:51:24 -04:00
J. Nick Koston
daf8ec36ab [core] Remove unnecessary FD_SETSIZE check on ESP32 and improve logging (#10255) 2025-08-15 21:26:48 -05:00
J. Nick Koston
82d7255a16 Merge branch 'integration' into memory_api 2025-08-15 21:36:21 -04:00
J. Nick Koston
c05db304ef Merge branch 'teardown_fix_size' into integration 2025-08-15 21:30:37 -04:00
J. Nick Koston
59037458d6 Update esphome/core/application.cpp
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-15 21:27:31 -04:00
J. Nick Koston
769a874d33 Merge branch 'integration' into memory_api 2025-08-15 21:16:44 -04:00
J. Nick Koston
50d284f21f Merge branch 'teardown_fix_size' into integration 2025-08-15 21:16:22 -04:00
J. Nick Koston
0a66612399 teardown 2025-08-15 15:40:19 -05:00
J. Nick Koston
405ebe90f5 teardown 2025-08-15 15:39:33 -05:00
J. Nick Koston
15d1e1f8dd Merge branch 'integration' into memory_api 2025-08-15 15:05:19 -05:00
J. Nick Koston
fdf3db465c Merge branch 'useless_fd_setsize_check' into integration 2025-08-15 15:05:13 -05:00
J. Nick Koston
2b887033c5 [core] Remove unnecessary FD_SETSIZE check on ESP32 and improve logging 2025-08-15 14:49:22 -05:00
J. Nick Koston
e4a6dcd9c7 Merge branch 'integration' into memory_api 2025-08-15 12:33:32 -05:00
J. Nick Koston
ffc234f934 Merge remote-tracking branch 'upstream/dev' into integration 2025-08-15 12:33:27 -05:00
J. Nick Koston
6c5632a0b3 [esp32] Optimize preferences is_changed() by replacing temporary vector with unique_ptr (#10246)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-15 10:11:49 -05:00
J. Nick Koston
9f8f1f5bc9 Merge branch 'integration' into memory_api 2025-08-15 09:54:57 -05:00
J. Nick Koston
b43ab33662 Merge branch 'esp32_ble_no_vector_no_heap_small' into integration 2025-08-15 09:54:47 -05:00
J. Nick Koston
d78d2c8710 Apply suggestions from code review 2025-08-15 09:53:07 -05:00
RFDarter
abecc0e8d8 [web_server] fix cover_all_json_generator wrong detail (#10252) 2025-08-15 09:44:24 -05:00
J. Nick Koston
3aae84fade preen 2025-08-15 08:54:17 -05:00
J. Nick Koston
7005da42bb preen 2025-08-15 08:52:21 -05:00
J. Nick Koston
0c06b02af4 Merge remote-tracking branch 'upstream/dev' into esp32_ble_no_vector_no_heap_small 2025-08-15 08:43:53 -05:00
J. Nick Koston
af9ecf3429 [esp32_ble] Optimize BLE event memory usage by eliminating std::vector overhead (#10247)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-15 07:38:27 +00:00
J. Nick Koston
57b4c65c6c Merge branch 'integration' into memory_api 2025-08-15 00:59:09 -05:00
J. Nick Koston
d632fc930c Merge branch 'esp32_ble_no_vector_no_heap_small' into integration 2025-08-15 00:59:00 -05:00
J. Nick Koston
0d966ac115 preen 2025-08-15 00:58:52 -05:00
J. Nick Koston
c2abb2c8ba [esp32_ble] Use union space for inline GATTC/GATTS data storage to reduce heap allocations 2025-08-15 00:49:25 -05:00
J. Nick Koston
ce328bc9b9 Merge branch 'integration' into memory_api 2025-08-14 23:49:39 -05:00
J. Nick Koston
0757a18fe7 Merge remote-tracking branch 'upstream/esp32_prefs_vector' into integration 2025-08-14 23:49:33 -05:00
J. Nick Koston
04b0a82963 Update esphome/components/esp32/preferences.cpp
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-14 23:49:05 -05:00
J. Nick Koston
306f7263b2 Merge branch 'integration' into memory_api 2025-08-14 23:39:56 -05:00
J. Nick Koston
2cb9f65830 Merge branch 'esp32_ble_no_vector' into integration 2025-08-14 23:39:47 -05:00
J. Nick Koston
c32584d48e preen 2025-08-14 23:39:29 -05:00
J. Nick Koston
ce6d71e942 Update esphome/components/esp32_ble/ble_event.h 2025-08-14 23:38:54 -05:00
J. Nick Koston
14895adf47 Update esphome/components/esp32_ble/ble_event.h
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-14 23:38:25 -05:00
J. Nick Koston
d2faac10a5 Merge branch 'integration' into memory_api 2025-08-14 23:01:08 -05:00
J. Nick Koston
8e485b521a Merge branch 'esp32_prefs_vector' into integration 2025-08-14 23:00:59 -05:00
J. Nick Koston
da5020354f preen 2025-08-14 22:59:37 -05:00
J. Nick Koston
7b46be523a Merge branch 'integration' into memory_api 2025-08-14 22:42:39 -05:00
J. Nick Koston
b695b6b31e Merge branch 'esp32_prefs_vector' into integration 2025-08-14 22:42:34 -05:00
J. Nick Koston
9f4d0d3f23 preen 2025-08-14 22:42:19 -05:00
J. Nick Koston
3585ed185f Merge branch 'integration' into memory_api 2025-08-14 22:22:14 -05:00
J. Nick Koston
a7c60e76df Merge branch 'esp32_ble_no_vector' into integration 2025-08-14 22:22:04 -05:00
J. Nick Koston
0ee3155123 fix 2025-08-14 22:20:08 -05:00
J. Nick Koston
1bd458c868 Merge branch 'integration' into memory_api 2025-08-14 22:14:23 -05:00
J. Nick Koston
792f9392a1 Merge branch 'esp32_ble_no_vector' into integration 2025-08-14 22:14:18 -05:00
J. Nick Koston
1fa33253f8 wip 2025-08-14 22:04:10 -05:00
J. Nick Koston
269786cac9 preen 2025-08-14 21:59:46 -05:00
J. Nick Koston
9f22de449a Merge branch 'integration' into memory_api 2025-08-14 21:20:40 -05:00
J. Nick Koston
3aa6901861 Merge branch 'esp32_prefs_vector' into integration 2025-08-14 21:20:33 -05:00
J. Nick Koston
e9e94bcd45 [esp32] Optimize preferences is_changed() by replacing temporary vector with unique_ptr 2025-08-14 21:19:36 -05:00
J. Nick Koston
bf4070ad75 Merge branch 'integration' into memory_api 2025-08-14 21:04:59 -05:00
J. Nick Koston
c562ef6b79 Merge branch 'esp8266_prefs_no_vector' into integration 2025-08-14 21:04:53 -05:00
J. Nick Koston
df73d81aca [esp8266] Replace std::vector with std::unique_ptr in preferences to save flash 2025-08-14 21:03:51 -05:00
J. Nick Koston
00ae0fea31 Merge branch 'integration' into memory_api 2025-08-14 20:29:21 -05:00
J. Nick Koston
8cadb011a9 Merge remote-tracking branch 'upstream/dev' into integration 2025-08-14 20:28:41 -05:00
J. Nick Koston
5fa84439c2 [api] Optimize message buffer allocation and eliminate redundant methods (#10231) 2025-08-14 20:26:09 -05:00
dependabot[bot]
5d18afcd99 Bump ruff from 0.12.8 to 0.12.9 (#10239)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-08-14 22:54:35 +00:00
J. Nick Koston
117cffd2b0 [bluetooth_proxy] Remove redundant connection type check after V1 removal (#10208) 2025-08-15 10:51:15 +12:00
J. Nick Koston
8ea1a3ed64 [core] Trigger clean build when components are removed from configuration (#10235)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-15 10:50:03 +12:00
J. Nick Koston
0d288ceef3 Merge branch 'integration' into memory_api 2025-08-14 17:46:43 -05:00
J. Nick Koston
0413914eda Merge branch 'esp32_ble_client_strings_2' into integration 2025-08-14 17:46:34 -05:00
J. Nick Koston
8a9d30c8d3 [esp32_ble_client] Add log helper functions to reduce flash usage by 120 bytes 2025-08-14 17:44:15 -05:00
J. Nick Koston
4f29b3c7aa [wifi] Automatically disable Enterprise WiFi support when EAP is not configured (#10242) 2025-08-15 10:43:45 +12:00
J. Nick Koston
40d35f3dff Merge branch 'integration' into memory_api 2025-08-14 16:32:51 -05:00
J. Nick Koston
bf3436f3b3 Merge branch 'no_eap_when_not_configured' into integration 2025-08-14 16:32:40 -05:00
J. Nick Koston
b7e0627b21 [wifi] Automatically disable Enterprise WiFi support when EAP is not configured 2025-08-14 16:29:53 -05:00
J. Nick Koston
4d0049aa62 Merge branch 'integration' into memory_api 2025-08-14 16:04:37 -05:00
J. Nick Koston
9a0d663fb0 Merge branch 'clean_comp_removed' into integration 2025-08-14 16:04:30 -05:00
J. Nick Koston
9af016e2ce preen 2025-08-14 16:03:44 -05:00
J. Nick Koston
256f81f514 Merge branch 'clean_comp_removed' into memory_api 2025-08-14 15:59:52 -05:00
J. Nick Koston
a5a0ec907d Merge branch 'clean_comp_removed' into integration 2025-08-14 15:59:46 -05:00
J. Nick Koston
da73cb06cc cover other case 2025-08-14 15:59:36 -05:00
Jesse Hills
3325592d67 Merge branch 'beta' into dev 2025-08-15 08:46:48 +12:00
Jesse Hills
0a3ee7d84e Merge pull request #10228 from esphome/bump-2025.8.0b2
2025.8.0b2
2025-08-15 08:46:15 +12:00
Katherine Whitlock
882237120e Improve error reporting for add_library (#10226)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-15 08:14:53 +12:00
J. Nick Koston
c0c1b4738a Merge branch 'integration' into memory_api 2025-08-14 14:21:14 -05:00
J. Nick Koston
6a9dcc7d76 merge 2025-08-14 14:20:55 -05:00
J. Nick Koston
93c90124ee Merge branch 'integration' into memory_api 2025-08-14 10:07:27 -05:00
J. Nick Koston
82de6516c6 Merge branch 'clean_comp_removed' into integration 2025-08-14 10:07:19 -05:00
J. Nick Koston
7e7bfb00aa fix typing 2025-08-14 10:06:48 -05:00
J. Nick Koston
d42d9fa41e Update esphome/writer.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-14 10:05:11 -05:00
J. Nick Koston
6dd818a320 Merge branch 'integration' into memory_api 2025-08-14 09:41:12 -05:00
J. Nick Koston
7637b77394 Merge branch 'clean_comp_removed' into integration 2025-08-14 09:41:04 -05:00
J. Nick Koston
ef07d3e0c8 [core] Trigger clean build when components are removed from configuration 2025-08-14 09:34:41 -05:00
Jesse Hills
71efaf097b [esp32_ble] Add `USE_ESP32_BLE_UUID` when advertising is desired (#10230) 2025-08-14 08:49:14 -05:00
Jesse Hills
bd60dbb746 [quality] Remove period from audio related Invalid raises (#10229) 2025-08-14 08:48:25 -05:00
J. Nick Koston
6e9fb8201b Merge branch 'integration' into memory_api 2025-08-13 23:10:18 -05:00
J. Nick Koston
912f676133 Merge branch 'api_reduce_2' into integration 2025-08-13 23:10:10 -05:00
J. Nick Koston
9bcd6c7a85 fix 2025-08-13 23:07:22 -05:00
J. Nick Koston
09fa349349 Revert "Revert "preen""
This reverts commit d83ed9ebe1.
2025-08-13 23:06:23 -05:00
J. Nick Koston
58074e0357 Revert "preen"
This reverts commit 51bf2c3511.
2025-08-13 23:05:12 -05:00
J. Nick Koston
d83ed9ebe1 Revert "preen"
This reverts commit 0207444765.
2025-08-13 23:04:38 -05:00
J. Nick Koston
6fc0831762 Merge branch 'integration' into memory_api 2025-08-13 23:01:43 -05:00
J. Nick Koston
dfa2aa481d Merge branch 'api_reduce_2' into integration 2025-08-13 23:01:30 -05:00
J. Nick Koston
0207444765 preen 2025-08-13 22:58:04 -05:00
J. Nick Koston
51bf2c3511 preen 2025-08-13 22:55:06 -05:00
J. Nick Koston
97c405b57e preen 2025-08-13 22:53:33 -05:00
J. Nick Koston
deff1c4bc7 preen 2025-08-13 22:53:12 -05:00
J. Nick Koston
d37390412c preen 2025-08-13 22:52:51 -05:00
J. Nick Koston
62a2b5e736 Merge branch 'integration' into memory_api 2025-08-13 22:46:00 -05:00
J. Nick Koston
6c0d21f116 Merge branch 'api_reduce_2' into integration 2025-08-13 22:45:49 -05:00
J. Nick Koston
f0decc4716 tweak 2025-08-13 22:42:08 -05:00
J. Nick Koston
3346e09785 preen 2025-08-13 22:23:47 -05:00
J. Nick Koston
b43ca2bbab [api] Optimize message buffer allocation and eliminate redundant methods 2025-08-13 22:10:15 -05:00
J. Nick Koston
3bf8624b87 Merge remote-tracking branch 'upstream/dev' into memory_api 2025-08-13 21:43:55 -05:00
Jesse Hills
6b5e43ca72 [qm6988] Clean up code (#10216) 2025-08-13 21:19:03 -05:00
Jesse Hills
8d61b1e8df Bump version to 2025.8.0b2 2025-08-14 14:00:27 +12:00
dependabot[bot]
9c897993bb Bump esphome-dashboard from 20250514.0 to 20250814.0 (#10227)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-14 14:00:26 +12:00
dependabot[bot]
93f9475105 Bump aioesphomeapi from 38.2.1 to 39.0.0 (#10222)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-14 14:00:26 +12:00
Samuel Sieb
95cd224e3e [psram] allow disabling (#10224)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2025-08-14 14:00:26 +12:00
Jesse Hills
b7afeafda9 [espnow] Set state to enabled before adding initial peers (#10225) 2025-08-14 14:00:26 +12:00
Jesse Hills
7922462bcf [entity] Allow `device_id` to be blank on entities (#10217) 2025-08-14 14:00:26 +12:00
dependabot[bot]
46d433775b Bump esphome-dashboard from 20250514.0 to 20250814.0 (#10227)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-14 12:40:20 +12:00
dependabot[bot]
7c4a54de90 Bump aioesphomeapi from 38.2.1 to 39.0.0 (#10222)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-13 17:42:54 -05:00
Samuel Sieb
c3f1596498 [psram] allow disabling (#10224)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2025-08-14 10:40:12 +12:00
Jesse Hills
0d1949a61b [espnow] Set state to enabled before adding initial peers (#10225) 2025-08-14 10:30:28 +12:00
Jesse Hills
6a8722f33e [entity] Allow `device_id` to be blank on entities (#10217) 2025-08-14 09:42:11 +12:00
J. Nick Koston
0c92dc77ad Merge branch 'integration' into memory_api 2025-08-13 11:24:22 -05:00
J. Nick Koston
203da37390 Merge remote-tracking branch 'upstream/dev' into integration 2025-08-13 11:24:14 -05:00
Jesse Hills
fff66072d4 Merge branch 'beta' into dev 2025-08-14 00:02:17 +12:00
Jesse Hills
1c2e1ab3e5 Merge pull request #10214 from esphome/bump-2025.8.0b1
2025.8.0b1
2025-08-13 23:56:34 +12:00
J. Nick Koston
68ddd98f5f [CI] Fix CI job failures for PRs with >300 changed files (#10215) 2025-08-13 15:49:38 +12:00
J. Nick Koston
0dda3faed5 [CI] Fix CI job failures for PRs with >300 changed files (#10215) 2025-08-13 15:46:56 +12:00
Jesse Hills
40c0c36179 Bump version to 2025.9.0-dev 2025-08-13 14:46:51 +12:00
Jesse Hills
6b7ced1970 Bump version to 2025.8.0b1 2025-08-13 14:46:50 +12:00
J. Nick Koston
ed2b76050b [bluetooth_proxy] Remove ESPBTUUID dependency to save 296 bytes of flash (#10213) 2025-08-13 14:18:53 +12:00
Samuel Sieb
113813617d [bme280_base, bmp280_base] add reasons to the fails, clean up logging (#10209)
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
2025-08-13 02:05:22 +00:00
J. Nick Koston
bb6ce91c9d Merge branch 'integration' into memory_api 2025-08-12 21:03:55 -05:00
J. Nick Koston
b3e6a0e417 Merge branch 'proxy_uuids_services' into integration 2025-08-12 21:03:43 -05:00
J. Nick Koston
4acc7f77cc one more 2025-08-12 20:22:24 -05:00
J. Nick Koston
9238916b32 one more 2025-08-12 20:22:10 -05:00
Keith Burzinski
c3a209d3f4 [ld2450] Replace `throttle` with native filters (#10196)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-08-12 19:35:19 -05:00
J. Nick Koston
ed9e717703 Merge branch 'integration' into memory_api 2025-08-12 19:33:42 -05:00
J. Nick Koston
ae4f051cd4 Merge branch 'proxy_uuids_services' into integration 2025-08-12 19:33:36 -05:00
J. Nick Koston
dec9810177 fix 2025-08-12 19:26:41 -05:00
J. Nick Koston
235050fe58 fix 2025-08-12 19:25:20 -05:00
J. Nick Koston
b5c381982c fix 2025-08-12 19:23:44 -05:00
J. Nick Koston
5c12f638bd fix 2025-08-12 19:22:27 -05:00
J. Nick Koston
36613507be fix 2025-08-12 19:19:21 -05:00
J. Nick Koston
803d665a94 tweak 2025-08-12 19:16:02 -05:00
J. Nick Koston
5b279f5f92 tweak 2025-08-12 19:14:37 -05:00
J. Nick Koston
841deff578 [bluetooth_proxy] Optimize UUID conversion and reduce flash usage by 296 bytes 2025-08-12 18:57:17 -05:00
J. Nick Koston
c2ebfe8f27 [bluetooth_proxy] Optimize UUID conversion and reduce flash usage by 296 bytes 2025-08-12 18:56:20 -05:00
J. Nick Koston
7b116be48b [bluetooth_proxy] Optimize UUID conversion and reduce flash usage by 296 bytes 2025-08-12 18:54:23 -05:00
J. Nick Koston
1c409e1b51 Merge branch 'integration' into memory_api 2025-08-12 15:34:36 -05:00
J. Nick Koston
ffdf80533e Merge branch 'redudant_checks_bluetooth_proxy' into integration 2025-08-12 15:34:29 -05:00
J. Nick Koston
b85185f821 [bluetooth_proxy] Remove redundant connection type check after V1 removal 2025-08-12 15:12:58 -05:00
John
7ffdaa1f06 [atm90e32] energy meter calibration log output enhancements & software SPI fix (#10143)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-08-12 20:26:53 +12:00
dependabot[bot]
3a857950bf Bump actions/checkout from 4 to 5 (#10198)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-08-12 20:23:41 +12:00
Rihan9
0256e0005e [ld2412] New component (#9075)
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-08-12 00:34:37 -05:00
Jesse Hills
c65af68e63 [core] Reset pin registry after target platform validations (#10199) 2025-08-12 16:33:07 +12:00
dependabot[bot]
ef2121a215 Bump aioesphomeapi from 38.1.0 to 38.2.1 (#10197)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-11 20:47:53 -05:00
Joshua Sing
bb40b7702d [const] Add CONF_POWER_MODE (#10173) 2025-08-12 11:13:24 +12:00
Kevin Ahrendt
6c48f3d719 [wifi] Remove restriction from using NONE power saving mode with BLE (#10181) 2025-08-12 11:09:58 +12:00
J. Nick Koston
daa7633ebb Merge branch 'integration' into memory_api 2025-08-11 17:22:09 -05:00
J. Nick Koston
a62d846dd1 Merge remote-tracking branch 'upstream/dev' into integration 2025-08-11 17:22:00 -05:00
J. Nick Koston
ff52869b4c [api] Add constexpr optimizations to protobuf encoding (#10192) 2025-08-12 10:10:38 +12:00
J. Nick Koston
82b7c1224c [core] Improve entity duplicate validation error messages (#10184) 2025-08-12 09:58:51 +12:00
Jesse Hills
c14c4fb658 [substitutions] Add some safe built-in functions to jinja parsing (#10178)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-11 16:12:54 -05:00
J. Nick Koston
42aee53dde [bluetooth_proxy] Replace dynamic vector with fixed array for BLE advertisements (#10174) 2025-08-11 15:47:46 -05:00
J. Nick Koston
9aa21956c8 [api] Optimize single vector writes to use write() instead of writev() (#10193) 2025-08-11 15:41:08 -05:00
J. Nick Koston
4c2874a32b [esphome] Fix OTA watchdog resets during port scanning and network delays (#10152) 2025-08-11 15:37:01 -05:00
J. Nick Koston
0813362ea8 Merge branch 'integration' into memory_api 2025-08-11 14:53:48 -05:00
J. Nick Koston
53da4391a0 Merge branch 'api_writev' into integration 2025-08-11 14:53:36 -05:00
J. Nick Koston
04415211e6 [api] Optimize single vector writes to use write() instead of writev() 2025-08-11 14:52:41 -05:00
J. Nick Koston
4b62f48e12 Merge branch 'integration' into memory_api 2025-08-11 14:37:19 -05:00
J. Nick Koston
d46843f6f1 Merge branch 'small_proto_cleanups' into integration 2025-08-11 14:37:11 -05:00
J. Nick Koston
1bd4098cea [api] Add constexpr optimizations to protobuf encoding 2025-08-11 14:36:31 -05:00
J. Nick Koston
11dd8ccd1e Merge branch 'integration' into memory_api 2025-08-11 13:29:31 -05:00
J. Nick Koston
8e0915a3a5 Merge branch 'improve_entity_validation_message' into integration 2025-08-11 13:29:15 -05:00
J. Nick Koston
d04422e27d add missing file 2025-08-11 11:36:15 -05:00
J. Nick Koston
753ee1badc [core] Improve entity duplicate validation error messages 2025-08-11 11:32:23 -05:00
Keith Burzinski
45b88f2da9 [sensor] Extend timeout filter with option to return last value received (#10115) 2025-08-11 10:36:44 -05:00
dependabot[bot]
8f53961496 Bump pylint from 3.3.7 to 3.3.8 (#10177)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-11 01:05:14 -05:00
J. Nick Koston
073957a310 Merge branch 'integration' into memory_api 2025-08-11 00:44:05 -05:00
J. Nick Koston
075d533b93 Merge branch 'fixed_ble_adv' into integration 2025-08-11 00:43:57 -05:00
J. Nick Koston
a847aab65e preen 2025-08-11 00:42:58 -05:00
J. Nick Koston
3d821f1223 preen 2025-08-11 00:42:42 -05:00
J. Nick Koston
9d29031d81 Merge remote-tracking branch 'origin/fixed_ble_adv' into fixed_ble_adv 2025-08-11 00:40:51 -05:00
J. Nick Koston
dc0c4a8991 Merge remote-tracking branch 'origin/dev' into fixed_ble_adv 2025-08-11 00:40:41 -05:00
J. Nick Koston
af9c008ccb fix off by 1 2025-08-11 00:40:28 -05:00
J. Nick Koston
c437d0057c Merge branch 'dev' into fixed_ble_adv 2025-08-11 00:12:22 -05:00
dependabot[bot]
5cf0e4d9dd Bump aioesphomeapi from 38.0.0 to 38.1.0 (#10176)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-11 05:11:22 +00:00
J. Nick Koston
cfe2bfea92 Merge branch 'integration' into memory_api 2025-08-11 00:09:51 -05:00
J. Nick Koston
7d3cc0b811 Merge branch 'fixed_ble_adv' into integration 2025-08-11 00:09:45 -05:00
J. Nick Koston
07db443207 fixed ble adv 2025-08-10 23:31:40 -05:00
J. Nick Koston
35a51280d4 fixed ble adv 2025-08-10 23:29:46 -05:00
J. Nick Koston
336fea4251 Merge branch 'integration' into memory_api 2025-08-10 19:29:03 -05:00
J. Nick Koston
2fc44ac831 Merge branch 'ota_fixes' into integration 2025-08-10 19:28:57 -05:00
J. Nick Koston
8f8d6734db dry 2025-08-10 19:26:12 -05:00
J. Nick Koston
6f817fd06a Merge branch 'integration' into memory_api 2025-08-10 19:09:54 -05:00
J. Nick Koston
96cc7f4b99 Merge remote-tracking branch 'origin/ota_fixes' into integration 2025-08-10 19:09:44 -05:00
J. Nick Koston
9021de9c1c preen 2025-08-10 19:04:24 -05:00
J. Nick Koston
7c0856c460 Merge branch 'integration' into memory_api 2025-08-10 18:54:14 -05:00
J. Nick Koston
7148500fa2 Merge branch 'ota_fixes' into integration 2025-08-10 18:54:07 -05:00
J. Nick Koston
d337da3d3c cleanp 2025-08-10 18:53:48 -05:00
J. Nick Koston
3cc916b9d8 Merge branch 'integration' into memory_api 2025-08-10 18:47:30 -05:00
J. Nick Koston
5c9816e51b Merge branch 'ota_fixes' into integration 2025-08-10 18:47:24 -05:00
J. Nick Koston
9ce75d2f0f preen 2025-08-10 18:45:44 -05:00
Chad Matsalla
b70983ed09 [display] Disallow `show_test_card: true and update_interval: never` (#9927)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-08-11 11:41:37 +12:00
J. Nick Koston
bcd4386fe4 Merge branch 'integration' into memory_api 2025-08-10 18:36:04 -05:00
J. Nick Koston
d5e831b93a Merge branch 'ota_fixes' into integration 2025-08-10 18:35:41 -05:00
J. Nick Koston
2f9d1e6dac preen 2025-08-10 18:33:30 -05:00
J. Nick Koston
ef676a5a77 preen 2025-08-10 18:30:28 -05:00
J. Nick Koston
4faa9231a9 preen 2025-08-10 18:29:00 -05:00
J. Nick Koston
4bdf44bb78 preen 2025-08-10 18:28:27 -05:00
J. Nick Koston
856e13986a preen 2025-08-10 18:25:51 -05:00
J. Nick Koston
4a8369ef93 preen 2025-08-10 18:24:41 -05:00
tomaszduda23
ffa89eb2d3 [nrf52, zephyr_debug] add zephyr debug component (#8319) 2025-08-11 11:20:45 +12:00
J. Nick Koston
f5790bff73 adjust 2025-08-10 18:19:52 -05:00
J. Nick Koston
adf941f68e Merge remote-tracking branch 'upstream/dev' into ota_fixes 2025-08-10 18:04:22 -05:00
J. Nick Koston
e48a223eac preen 2025-08-10 18:04:08 -05:00
J. Nick Koston
cd5f7fdc98 preen 2025-08-10 18:03:56 -05:00
J. Nick Koston
8ca671ad6f Merge branch 'integration' into memory_api 2025-08-10 17:53:46 -05:00
J. Nick Koston
e535c62b47 Merge branch 'ota_fixes' into integration 2025-08-10 17:53:40 -05:00
J. Nick Koston
2bdf335127 uint32_t 2025-08-10 17:53:24 -05:00
J. Nick Koston
f0c97c299f uint32_t 2025-08-10 17:53:02 -05:00
J. Nick Koston
17cdf9c8d6 do not block until we get first magic byte 2025-08-10 17:48:47 -05:00
Clyde Stubbs
8b67d6dfec [lvgl] fix allocation of reduced size buffer with rotation (#10147) 2025-08-11 10:32:01 +12:00
Clyde Stubbs
581b4ef5a1 [lvgl] Various validation fixes (#10141) 2025-08-11 10:27:54 +12:00
Jonathan Swoboda
da02f970d4 [neopixelbus] Fix neopixelbus on esp32 (#10123) 2025-08-11 10:24:12 +12:00
Jesse Hills
2fc0a11596 [CI] Print more info for when consts are duplicated (#10166) 2025-08-11 09:53:40 +12:00
J. Nick Koston
5a8f722316 Optimize subprocess performance with close_fds=False (#10145) 2025-08-11 09:14:13 +12:00
J. Nick Koston
279f56141e [ade7880] Fix duplicate sensor name validation error (#10155) 2025-08-11 09:12:36 +12:00
J. Nick Koston
6bfe281d18 [web_server] Reduce flash usage by consolidating parameter parsing (#10154) 2025-08-11 09:09:31 +12:00
J. Nick Koston
a1371aea37 [dashboard] Fix port fallback regression when device is offline (#10135) 2025-08-11 09:04:40 +12:00
J. Nick Koston
3edb1e0726 Merge branch 'integration' into memory_api 2025-08-10 12:41:54 -05:00
J. Nick Koston
089721606b Merge branch 'ota_fixes' into integration 2025-08-10 12:41:38 -05:00
J. Nick Koston
0c230fcd10 increase to 30s 2025-08-10 12:39:44 -05:00
Jonathan Swoboda
d5c9c10b3b [esp32] Add IDF log_level option (#10134) 2025-08-10 17:27:08 +00:00
J. Nick Koston
7ff8094be2 Merge branch 'integration' into memory_api 2025-08-10 11:12:57 -05:00
J. Nick Koston
88642ec717 Merge remote-tracking branch 'swoboda1337/idf_logger' into integration 2025-08-10 11:12:46 -05:00
Jonathan Swoboda
d052dec11b Use CONF_LOG_LEVEL from const 2025-08-10 09:11:17 -04:00
Jonathan Swoboda
6d0e86cf25 Add log_level option to idf framework 2025-08-10 09:00:03 -04:00
J. Nick Koston
cef39e7c59 [esp32_ble_tracker] Fix false reboots when event loop is blocked (#10144) 2025-08-10 04:44:23 -05:00
J. Nick Koston
5394ee37f9 Merge branch 'integration' into memory_api 2025-08-09 21:02:17 -05:00
J. Nick Koston
8a87336aa8 Merge branch 'webserver_flash' into integration 2025-08-09 21:02:12 -05:00
J. Nick Koston
e64ecca771 tidy 2025-08-09 21:02:02 -05:00
J. Nick Koston
6aedcab37e Merge branch 'integration' into memory_api 2025-08-09 20:52:47 -05:00
J. Nick Koston
da9a29a1dd Merge branch 'webserver_flash' into integration 2025-08-09 20:52:37 -05:00
J. Nick Koston
4e07c50490 tweak 2025-08-09 20:52:28 -05:00
J. Nick Koston
5b91c5f043 Merge branch 'integration' into memory_api 2025-08-09 20:46:51 -05:00
J. Nick Koston
73b11896d4 Merge branch 'webserver_flash' into integration 2025-08-09 20:46:44 -05:00
J. Nick Koston
8faac0c184 [web_server] Reduce flash usage by consolidating parameter parsing 2025-08-09 20:41:44 -05:00
J. Nick Koston
233882e1c2 Merge branch 'integration' into memory_api 2025-08-09 14:06:01 -05:00
J. Nick Koston
54a2ac92bf Merge branch 'ota_fixes' into integration 2025-08-09 14:05:52 -05:00
J. Nick Koston
ea74a9ec8f [esphome] Fix OTA watchdog reset when port scanning 2025-08-09 14:01:49 -05:00
Edward Firmo
2b9e1ce315 [switch] Add trigger `on_state` (#10108) 2025-08-09 21:09:40 +10:00
J. Nick Koston
48a94b686d Merge branch 'integration' into memory_api 2025-08-08 16:23:20 -05:00
J. Nick Koston
32a968784c Merge branch 'subproc' into integration 2025-08-08 16:23:03 -05:00
J. Nick Koston
3ded96bb26 Optimize subprocess performance with close_fds=False 2025-08-08 16:18:04 -05:00
J. Nick Koston
c982e35189 Merge branch 'integration' into memory_api 2025-08-08 16:05:26 -05:00
J. Nick Koston
507ec82886 Merge branch 'port_fallback' into integration 2025-08-08 16:05:20 -05:00
J. Nick Koston
e316fe3b80 Merge branch 'esp32_ble_tracker_reboot_on_blocked_loop' into integration 2025-08-08 16:05:15 -05:00
dependabot[bot]
ff9ddb9d68 Bump tornado from 6.5.1 to 6.5.2 (#10142)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-08 16:03:13 -05:00
Edward Firmo
676c51ffa0 [switch] Add control() method to API (#10118) 2025-08-08 05:51:19 +00:00
J. Nick Koston
cdcf5fd74c [dashboard] Fix port fallback regression when device is offline 2025-08-07 18:47:54 -10:00
J. Nick Koston
7e4d09dbd8 [bluetooth_proxy] Optimize connection loop to reduce CPU usage (#10133) 2025-08-07 16:24:26 -10:00
J. Nick Koston
58504662d8 [cover] Reduce flash usage by optimizing validation messages (#10130) 2025-08-08 10:44:47 +10:00
J. Nick Koston
83b69519dd [wifi] Reduce flash usage by optimizing logging (#10127) 2025-08-08 10:43:13 +10:00
J. Nick Koston
d4d1a96f9b [esp32_ble_client] Reduce flash usage by optimizing logging strings (#10119) 2025-08-08 10:42:03 +10:00
J. Nick Koston
76fd104fb6 [mdns] Conditionally compile extra services to reduce flash usage (#10129) 2025-08-08 10:32:35 +10:00
Edward Firmo
c4d1b1317a [switch] Add switch.control automation action (#10105)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2025-08-08 08:55:54 +10:00
J. Nick Koston
61fbf7cd81 Merge branch 'integration' into memory_api 2025-08-07 12:55:44 -10:00
J. Nick Koston
11b82fe3d4 Merge branch 'startup_herd_ble_fix' into integration 2025-08-07 12:55:38 -10:00
J. Nick Koston
1de0a73a63 preen 2025-08-07 12:55:31 -10:00
J. Nick Koston
760add7d29 Merge branch 'integration' into memory_api 2025-08-07 12:39:23 -10:00
J. Nick Koston
86798e3a2b Merge branch 'startup_herd_ble_fix' into integration 2025-08-07 12:39:19 -10:00
J. Nick Koston
543e5099a4 [bluetooth_proxy] Optimize connection loop to reduce CPU usage 2025-08-07 12:38:15 -10:00
J. Nick Koston
3f31bcacad Merge branch 'integration' into memory_api 2025-08-07 10:28:59 -10:00
J. Nick Koston
4ba5718ddd Merge branch 'cover_logging' into integration 2025-08-07 10:28:51 -10:00
J. Nick Koston
ac05ab6de1 [cover] Reduce flash usage by optimizing validation messages 2025-08-07 10:28:07 -10:00
dependabot[bot]
14bc83342f Bump ruff from 0.12.7 to 0.12.8 (#10126)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-08-07 20:15:14 +00:00
dependabot[bot]
a1461c5293 Bump actions/cache from 4.2.3 to 4.2.4 (#10128)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-07 10:09:53 -10:00
J. Nick Koston
75249775ee Merge branch 'integration' into memory_api 2025-08-07 09:57:32 -10:00
J. Nick Koston
902846c6da Merge branch 'mdns_extra' into integration 2025-08-07 09:57:27 -10:00
J. Nick Koston
887d43d76c [mdns] Conditionally compile extra services to reduce flash usage 2025-08-07 09:56:47 -10:00
J. Nick Koston
2ff418c8f9 Merge branch 'integration' into memory_api 2025-08-07 09:44:14 -10:00
J. Nick Koston
3a2c7c9154 Merge branch 'wifi_flash' into integration 2025-08-07 09:44:08 -10:00
J. Nick Koston
c5c71bd85e [wifi] Reduce flash usage by optimizing logging 2025-08-07 09:43:24 -10:00
dependabot[bot]
73b2db8af5 Bump actions/cache from 4.2.3 to 4.2.4 in /.github/actions/restore-python (#10125)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-07 09:16:58 -10:00
J. Nick Koston
a7a119f576 [bluetooth_proxy] Remove V1 connection support (#10107) 2025-08-07 03:52:46 -05:00
J. Nick Koston
1ba76f5f2e [esp32_ble_client] Conditionally compile BLE service classes to reduce flash usage (#10114) 2025-08-07 03:46:34 -05:00
J. Nick Koston
fb86bd05db Merge branch 'integration' into memory_api 2025-08-06 22:42:38 -10:00
J. Nick Koston
4d56781be9 Merge branch 'esp32_ble_client_strings' into integration 2025-08-06 22:42:31 -10:00
J. Nick Koston
481bbeb6b5 [esp32_ble_client] Reduce flash usage by optimizing logging strings 2025-08-06 22:41:50 -10:00
J. Nick Koston
37a9ad6a0d [esp32_ble_tracker] Optimize member variable ordering to reduce memory padding (#10113) 2025-08-07 03:34:46 -05:00
J. Nick Koston
431a8dd374 Merge branch 'integration' into memory_api 2025-08-06 22:04:44 -10:00
J. Nick Koston
469076c7dd Merge branch 'cond_compile_service_char_desc_cond' into integration 2025-08-06 22:04:31 -10:00
J. Nick Koston
c5065f21b8 condtional 2025-08-06 19:53:57 -10:00
J. Nick Koston
c0a62c0be1 [esp32_ble_client] Avoid iterating empty services vector for bluetooth_proxy connections (#10110) 2025-08-07 03:40:12 +00:00
J. Nick Koston
9841d7cf87 Merge branch 'integration' into memory_api 2025-08-06 17:31:53 -10:00
J. Nick Koston
24246a136e Merge branch 'esp32_tracker_touch_ups' into integration 2025-08-06 17:31:48 -10:00
J. Nick Koston
cf1b24145b [esp32_ble_tracker] Optimize member variable ordering to reduce memory padding 2025-08-06 17:30:48 -10:00
J. Nick Koston
bfb14e1cf9 [esp32_touch] Restore get_value() for ESP32-S2/S3 variants (#10112) 2025-08-06 21:21:32 -05:00
mbo18
1415e02e40 Add device class absolute_humidity to the absolute humidity component (#10100)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2025-08-07 13:48:26 +12:00
dependabot[bot]
81f907e994 Bump actions/download-artifact from 4.3.0 to 5.0.0 (#10106)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-07 13:47:03 +12:00
J. Nick Koston
61008bc8a9 [bluetooth_proxy] Remove unnecessary heap allocation for response object (#10104) 2025-08-07 13:42:04 +12:00
J. Nick Koston
6d66ddd68d [bluetooth_proxy][esp32_ble_tracker][esp32_ble_client] Consolidate duplicate logging code to reduce flash usage (#10097) 2025-08-07 13:41:03 +12:00
J. Nick Koston
fc180251be [bluetooth_proxy] Consolidate dump_config() log calls (#10103) 2025-08-07 12:43:59 +12:00
J. Nick Koston
ee1d4f27ef [esp32_ble] Conditionally compile BLE advertising to reduce flash usage (#10099) 2025-08-07 12:29:24 +12:00
J. Nick Koston
325ec0a0ae [esp32_ble_client] Convert to C++17 nested namespace syntax (#10111) 2025-08-07 12:18:03 +12:00
J. Nick Koston
28d2c86a91 Merge branch 'dev' into memory_api 2025-08-06 14:08:07 -10:00
Keith Burzinski
6071f4b02c [ld2410] Replace `throttle` with native filters (#10019) 2025-08-07 10:26:11 +12:00
J. Nick Koston
5e8768084b Merge branch 'integration' into memory_api 2025-08-06 10:56:41 -10:00
J. Nick Koston
28c4460f8b Merge branch 'esp32_ble_client_namespace' into integration 2025-08-06 10:56:28 -10:00
J. Nick Koston
cb4d3d37cf [esp32_ble_client] Convert to C++17 nested namespace syntax 2025-08-06 10:55:56 -10:00
J. Nick Koston
e29b04bcfe Merge branch 'integration' into memory_api 2025-08-06 10:45:16 -10:00
J. Nick Koston
341d2febd6 Merge branch 'entire_service_list_copied_discarded' into integration 2025-08-06 10:45:06 -10:00
J. Nick Koston
534681b988 [esp32_ble_client] Fix V3_WITH_CACHE connections unnecessarily populating services vector
preen
2025-08-06 10:40:48 -10:00
J. Nick Koston
ddb1fcd0f9 preen 2025-08-06 10:39:44 -10:00
J. Nick Koston
2327c5bddf Merge branch 'integration' into memory_api 2025-08-06 10:28:49 -10:00
J. Nick Koston
a962bb4af2 Merge branch 'entire_service_list_copied_discarded' into integration 2025-08-06 10:28:43 -10:00
J. Nick Koston
ee98abe9f1 [esp32_ble_client] Fix V3_WITH_CACHE connections unnecessarily populating services vector 2025-08-06 10:27:18 -10:00
dependabot[bot]
083ac8ce8e Bump aioesphomeapi from 37.2.5 to 38.0.0 (#10109)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-06 10:21:29 -10:00
J. Nick Koston
2cdf50a025 tweak 2025-08-06 10:11:28 -10:00
J. Nick Koston
b75719d37e Merge branch 'integration' into memory_api 2025-08-06 10:03:29 -10:00
J. Nick Koston
e99794fe9b Merge branch 'drop_v1_connection_type_proxy_only' into integration 2025-08-06 10:03:22 -10:00
J. Nick Koston
16a2677bcf [bluetooth_proxy] Remove V1 connection support 2025-08-06 09:55:01 -10:00
J. Nick Koston
4ceda31f32 [bluetooth_proxy] Replace std::find with simple loop for small fixed array (#10102) 2025-08-07 07:53:42 +12:00
J. Nick Koston
27397593c1 Merge branch 'integration' into memory_api 2025-08-06 07:47:47 -10:00
J. Nick Koston
cb4cd00ad1 Merge branch 'proxy_alloc_cleanup' into integration 2025-08-06 07:47:41 -10:00
J. Nick Koston
0893d1d958 [bluetooth_proxy] Remove unnecessary heap allocation for response object 2025-08-06 07:47:14 -10:00
J. Nick Koston
5511dae2fa Merge branch 'integration' into memory_api 2025-08-06 07:38:12 -10:00
J. Nick Koston
1b65d2b1d3 Merge branch 'proxy_log_config' into integration 2025-08-06 07:38:05 -10:00
J. Nick Koston
2af29aab6f [bluetooth_proxy] Consolidate dump_config() log calls 2025-08-06 07:37:30 -10:00
J. Nick Koston
e1cb364436 Merge branch 'integration' into memory_api 2025-08-06 07:31:43 -10:00
J. Nick Koston
fdc4be8c0c Merge branch 'no_std_find_alloc' into integration 2025-08-06 07:31:35 -10:00
J. Nick Koston
a10e7b2a54 [bluetooth_proxy] Replace std::find with simple loop for small fixed array 2025-08-06 07:30:53 -10:00
J. Nick Koston
5021cc6d5f [esp32_ble] Make BLE notification limit configurable to fix ESP_GATT_NO_RESOURCES errors (#10098) 2025-08-06 17:24:02 +00:00
J. Nick Koston
e5a1820708 Merge branch 'integration' into memory_api 2025-08-06 07:09:31 -10:00
J. Nick Koston
211db40cb4 Merge branch 'conditional_compile_ble_adv' into integration 2025-08-06 07:09:21 -10:00
J. Nick Koston
0aec58665a remove filter, its too early 2025-08-06 07:09:05 -10:00
J. Nick Koston
c667218f3f Merge branch 'conditional_compile_ble_adv' into memory_api 2025-08-05 22:22:42 -10:00
J. Nick Koston
f19ed094b7 Merge branch 'conditional_compile_ble_adv' into integration 2025-08-05 22:22:36 -10:00
J. Nick Koston
f55ab960bb order 2025-08-05 22:22:26 -10:00
J. Nick Koston
2da1974a80 Merge branch 'integration' into memory_api 2025-08-05 22:02:28 -10:00
J. Nick Koston
bb4ddbd218 Merge branch 'ble_logging_cleanup' into integration 2025-08-05 22:02:18 -10:00
J. Nick Koston
9cc907778b Merge remote-tracking branch 'upstream/dev' into ble_logging_cleanup 2025-08-05 21:55:13 -10:00
J. Nick Koston
a11f32d6aa dry 2025-08-05 21:54:44 -10:00
J. Nick Koston
ba713737bc Merge branch 'integration' into memory_api 2025-08-05 21:36:58 -10:00
J. Nick Koston
0240c68771 Merge branch 'conditional_compile_ble_adv' into integration 2025-08-05 21:36:46 -10:00
J. Nick Koston
722d76565c [esp32_ble] Conditionally compile BLE advertising to reduce flash usage 2025-08-05 21:30:07 -10:00
J. Nick Koston
096b9c9651 Merge branch 'integration' into memory_api 2025-08-05 20:54:21 -10:00
J. Nick Koston
605e840f81 Merge branch 'out_of_notify' into integration 2025-08-05 20:53:50 -10:00
J. Nick Koston
3bfd77426a [esp32_ble] Make BLE notification limit configurable to fix ESP_GATT_NO_RESOURCES errors 2025-08-05 20:49:35 -10:00
Craig Andrews
2b3e546203 [deep_sleep] enable sleep pull up/down for wakeup pin (#9395) 2025-08-05 23:47:45 -07:00
J. Nick Koston
7e288126fa Merge branch 'integration' into memory_api 2025-08-05 20:30:40 -10:00
J. Nick Koston
b475542c8b Merge branch 'ble_logging_cleanup' into integration 2025-08-05 20:30:34 -10:00
J. Nick Koston
5d93388a5f [bluetooth_proxy][esp32_ble_tracker][esp32_ble_client] Consolidate duplicate logging code to reduce flash usage 2025-08-05 20:26:43 -10:00
J. Nick Koston
1642d34d29 [esp32_ble_tracker] Simplify state machine guards with helper functions (#10092) 2025-08-06 01:03:19 -05:00
J. Nick Koston
8ceb1b9d60 [bluetooth_proxy] Reduce flash usage by consolidating duplicate logging (#10094) 2025-08-06 00:49:20 -05:00
Jesse Hills
d872c8a999 [light] Allow light effect schema to be a schema object already (#10091) 2025-08-06 00:05:48 -05:00
Pawelo
99125c045f [bme680] Eliminate warnings due to unused functions (#9735)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2025-08-06 00:02:54 -05:00
Jonathan Swoboda
860a5ef5c0 [esp32_rmt_led_strip] Work around IDFGH-16195 (#10093) 2025-08-05 23:28:09 -05:00
J. Nick Koston
addeec572a Merge branch 'integration' into memory_api 2025-08-05 17:26:40 -10:00
J. Nick Koston
bb95320348 Merge branch 'bluetooth_proxy_flash' into integration 2025-08-05 17:26:34 -10:00
J. Nick Koston
c1ace213ab [bluetooth_proxy] Reduce flash usage by consolidating duplicate logging 2025-08-05 17:26:06 -10:00
J. Nick Koston
c253eb2362 Merge branch 'integration' into memory_api 2025-08-05 17:06:06 -10:00
J. Nick Koston
8fbef8e71e Merge branch 'esp32_ble_tracker_more_cleanups' into integration 2025-08-05 17:05:56 -10:00
J. Nick Koston
c5d5e66f30 dry 2025-08-05 17:04:02 -10:00
J. Nick Koston
eea84a43d6 Merge branch 'integration' into memory_api 2025-08-05 17:02:43 -10:00
J. Nick Koston
c52bd6fecf Merge branch 'esp32_ble_tracker_more_cleanups' into integration 2025-08-05 17:02:34 -10:00
J. Nick Koston
028d20908e Merge remote-tracking branch 'upstream/dev' into integration 2025-08-05 17:02:31 -10:00
J. Nick Koston
ef271cbd3b [esp32_ble_tracker] Simplify state machine guards with helper functions 2025-08-05 17:01:53 -10:00
J. Nick Koston
b01f03cc24 [esp32_ble_tracker] Refactor loop() method for improved readability and performance (#10074) 2025-08-06 14:26:11 +12:00
J. Nick Koston
cfb22e33c9 [esp32_ble_tracker] Add missing USE_ESP32_BLE_DEVICE guard for already_discovered_ member (#10085) 2025-08-06 14:22:32 +12:00
@RubenKelevra
96bbb58f34 update espressif's esp32-camera library to 2.1.1 (#10090) 2025-08-05 14:33:15 -10:00
Jesse Hills
3edd746c6c [mcp23xxx] Use CachedGpioExpander (#10078) 2025-08-06 11:01:57 +12:00
Copilot
c308e03e92 [select] Fix new_select() not forwarding constructor args while preserving keyword-only options parameter (#10036)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: clydebarrow <2366188+clydebarrow@users.noreply.github.com>
Co-authored-by: jesserockz <3060199+jesserockz@users.noreply.github.com>
2025-08-06 08:09:36 +12:00
NP v/d Spek
bd2b3b9da5 [espnow] Small changes and fixes (#10014)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-08-06 07:46:40 +12:00
Kevin Ahrendt
d443a97dd8 [speaker] Media player fixes for IDF5.4 (#10088) 2025-08-05 14:55:40 -04:00
J. Nick Koston
58a088e06b Add myself to multiple bluetooth codeowners (#10083) 2025-08-05 09:00:04 +00:00
J. Nick Koston
7344ff6941 merge 2025-08-04 21:50:36 -10:00
J. Nick Koston
2b5d035c39 Merge branch 'integration' into memory_api 2025-08-04 21:49:10 -10:00
J. Nick Koston
6cc5f83c71 Merge branch 'already_discovered_missing_guard' into integration 2025-08-04 21:48:48 -10:00
Jesse Hills
49a46883ed [core] Update core component codeowners to `@esphome/core` (#10082) 2025-08-05 06:24:24 +00:00
J. Nick Koston
acdcf514b9 [esp32_ble_tracker] Add missing USE_ESP32_BLE_DEVICE guard for already_discovered_ member 2025-08-04 20:14:30 -10:00
J. Nick Koston
89cf81baaa Merge branch 'integration' into memory_api 2025-08-04 20:06:47 -10:00
J. Nick Koston
7c328e51c4 Merge branch 'esp32_ble_co' into integration 2025-08-04 20:06:29 -10:00
J. Nick Koston
ba9cf1b5f6 Add myself to multiple bluetooth codeowners 2025-08-04 19:56:22 -10:00
J. Nick Koston
bc03538e25 Support multiple --device arguments for address fallback (#10003)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2025-08-05 16:40:46 +12:00
dependabot[bot]
969034b61a Bump docker/login-action from 3.4.0 to 3.5.0 in the docker-actions group (#10081)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-05 16:18:42 +12:00
Jonathan Swoboda
06eb1b6014 [remote_transmitter] Add digital_write automation (#10069)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-08-05 16:09:37 +12:00
Jesse Hills
589d00f17f Merge branch 'release' into dev 2025-08-05 15:38:25 +12:00
Jesse Hills
68c0aa4d6d Merge pull request #10079 from esphome/bump-2025.7.5
2025.7.5
2025-08-05 15:37:42 +12:00
J. Nick Koston
1c7200545b Merge branch 'integration' into memory_api 2025-08-04 16:52:44 -10:00
J. Nick Koston
e499374964 Merge branch 'multi_device_args' into integration 2025-08-04 16:52:30 -10:00
J. Nick Koston
63705c5a6e Merge branch 'dev' into multi_device_args 2025-08-04 16:51:57 -10:00
dependabot[bot]
2fddb061e1 Bump aioesphomeapi from 37.2.4 to 37.2.5 (#10080)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-04 16:51:42 -10:00
J. Nick Koston
3bbd3acea6 Merge branch 'integration' into memory_api 2025-08-04 16:26:55 -10:00
J. Nick Koston
8fc1835e5b Merge branch 'esp32_ble_tracker_cleanup_code' into integration 2025-08-04 16:26:49 -10:00
J. Nick Koston
655d001d72 address bot comments 2025-08-04 16:26:40 -10:00
J. Nick Koston
5f9080dac9 fix --device OTA 2025-08-04 15:57:29 -10:00
Jesse Hills
c85eb448e4 [gpio_expander] Fix bank caching (#10077)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2025-08-05 13:45:52 +12:00
J. Nick Koston
1d6c491dea Merge branch 'dev' into multi_device_args 2025-08-04 15:43:15 -10:00
Jesse Hills
396c02c6de [core] Allow extra args on cli and just ignore them (#9814)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-05 13:33:12 +12:00
Jesse Hills
52c4509208 [esp32_dac] Always use esp-idf APIs (#9833) 2025-08-05 13:31:56 +12:00
J. Nick Koston
9e1054cb87 Merge branch 'integration' into memory_api 2025-08-04 15:25:44 -10:00
J. Nick Koston
e0fe73ec5b Merge branch 'esp32_ble_tracker_cleanup_code' into integration 2025-08-04 15:25:37 -10:00
J. Nick Koston
166ed736a9 Merge remote-tracking branch 'upstream/dev' into esp32_ble_tracker_cleanup_code 2025-08-04 15:24:55 -10:00
Jesse Hills
d29cae9c3b Bump version to 2025.7.5 2025-08-05 13:21:00 +12:00
Chris Beswick
532e3e370f [i2s_audio] Use high-pass filter for dc offset correction (#10005) 2025-08-05 13:21:00 +12:00
Clyde Stubbs
da573a217d [font] Catch file load exception (#10058)
Co-authored-by: clydeps <U5yx99dok9>
2025-08-05 13:21:00 +12:00
J. Nick Koston
a9b27d1966 [api] Fix OTA progress updates not being sent when main loop is blocked (#10049) 2025-08-05 13:21:00 +12:00
Clyde Stubbs
0aa3c9685e [lvgl] Bugfix for tileview (#9938) 2025-08-05 13:21:00 +12:00
J. Nick Koston
93b28447ee [bluetooth_proxy] Optimize memory usage with fixed-size array and const string references (#10015) 2025-08-05 13:13:55 +12:00
J. Nick Koston
52634dac2a [tests] Add datetime entities to host_mode_many_entities integration test (#10032) 2025-08-05 13:12:05 +12:00
J. Nick Koston
64c94c1440 [esp32_ble_client] Fix connection parameter timing by setting preferences before connection (#10059) 2025-08-05 13:11:32 +12:00
J. Nick Koston
f7bf1ef52c [esp32_ble_tracker] Eliminate redundant ring buffer for lower latency (#10057)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-08-05 13:10:32 +12:00
J. Nick Koston
fa8c5e880c [esp32_ble_tracker] Optimize connection by promoting client immediately after scan stop trigger (#10061)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-05 13:10:02 +12:00
J. Nick Koston
27ba90ea95 [esp32_ble_client] Start MTU negotiation earlier following ESP-IDF examples (#10062) 2025-08-05 12:59:23 +12:00
J. Nick Koston
469246b8d8 [bluetooth_proxy] Warn about BLE connection timeout mismatch on Arduino framework (#10063)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-05 12:58:41 +12:00
J. Nick Koston
50f15735dc [api] Add helpful compile-time errors for Custom API Device methods (#10076) 2025-08-05 12:57:31 +12:00
mschnaubelt
83d9c02a1b Add CO5300 display support (#9739) 2025-08-05 09:41:55 +10:00
J. Nick Koston
df83205997 Merge branch 'integration' into memory_api 2025-08-04 11:17:58 -10:00
J. Nick Koston
d8a2e020a9 Merge branch 'helpful_custom_api_error' into integration 2025-08-04 11:17:48 -10:00
J. Nick Koston
fb1d2368a9 fix 2025-08-04 11:15:47 -10:00
J. Nick Koston
76cdef966b fix 2025-08-04 11:14:22 -10:00
J. Nick Koston
c6aeeb98b6 Merge branch 'integration' into memory_api 2025-08-04 11:10:01 -10:00
J. Nick Koston
b5bd065f94 Merge branch 'helpful_custom_api_error' into integration 2025-08-04 11:09:42 -10:00
J. Nick Koston
734b2691c8 [api] Add helpful compile-time errors for Custom API Device methods 2025-08-04 11:08:00 -10:00
J. Nick Koston
739cc5ff50 [esp32_ble_tracker] Refactor loop() method for improved readability and performance 2025-08-04 09:24:22 -10:00
J. Nick Koston
2facd1b436 Cleanup esp32_ble_tracker 2025-08-04 09:18:30 -10:00
Jonathan Swoboda
701e6099aa [espnow, web_server_idf] Fix IDF 5.5 compile issues (#10068) 2025-08-04 08:56:34 -10:00
J. Nick Koston
d4ff83bf6e Merge branch 'integration' into memory_api 2025-08-04 08:52:25 -10:00
J. Nick Koston
97544869bd Merge branch '5_5_fixes' into integration 2025-08-04 08:52:17 -10:00
Chris Beswick
d59476d0e1 [i2s_audio] Use high-pass filter for dc offset correction (#10005) 2025-08-04 10:43:44 -04:00
Jonathan Swoboda
608cc4f0d1 Fix 5.5 compile issues 2025-08-04 08:45:04 -04:00
Djordje Mandic
fbbb791b0d [gt911] Use timeout instead of delay, shortened log msg (#10024)
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
2025-08-04 03:37:43 -05:00
J. Nick Koston
15790145b2 Merge branch 'integration' into memory_api 2025-08-03 22:31:55 -10:00
J. Nick Koston
75b6bb9f76 Merge branch 'warn_ble_timeout_mismatch' into integration 2025-08-03 22:31:48 -10:00
J. Nick Koston
74c0e63a1d cleanup 2025-08-03 22:31:27 -10:00
J. Nick Koston
1d3cd0500e Merge branch 'integration' into memory_api 2025-08-03 22:20:15 -10:00
J. Nick Koston
0cebde66bd Merge branch 'warn_ble_timeout_mismatch' into integration 2025-08-03 22:20:11 -10:00
J. Nick Koston
081f0a1871 Update esphome/components/bluetooth_proxy/__init__.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-03 22:19:48 -10:00
J. Nick Koston
5062bab73f Merge branch 'integration' into memory_api 2025-08-03 22:17:25 -10:00
J. Nick Koston
8694cf71fd Merge branch 'warn_ble_timeout_mismatch' into integration 2025-08-03 22:17:17 -10:00
J. Nick Koston
2bc77be5ab [bluetooth_proxy] Warn about BLE connection timeout mismatch on Arduino framework 2025-08-03 22:16:40 -10:00
J. Nick Koston
cccbe4293b Merge branch 'integration' into memory_api 2025-08-03 21:42:00 -10:00
J. Nick Koston
8077dbd892 Merge branch 'mtu_sooner' into integration 2025-08-03 21:41:55 -10:00
J. Nick Koston
e6629f662c [esp32_ble_client] Start MTU negotiation earlier following ESP-IDF examples 2025-08-03 21:41:15 -10:00
J. Nick Koston
5126aefe13 Merge branch 'integration' into memory_api 2025-08-03 21:23:05 -10:00
J. Nick Koston
da3c6ade7b Merge branch 'promote_after_stop' into integration 2025-08-03 21:22:55 -10:00
J. Nick Koston
dd80fcdb62 Update esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-03 21:22:38 -10:00
J. Nick Koston
3a99929619 Merge branch 'integration' into memory_api 2025-08-03 21:02:31 -10:00
J. Nick Koston
d4449f372a Merge branch 'fix_connection_param_order' into integration 2025-08-03 21:02:14 -10:00
J. Nick Koston
a645c02476 Merge branch 'integration' into memory_api 2025-08-03 20:58:39 -10:00
J. Nick Koston
951d0de52d Merge branch 'promote_after_stop' into integration 2025-08-03 20:58:34 -10:00
J. Nick Koston
8d4f1802fb [esp32_ble_tracker] Optimize connection by promoting client immediately after scan stop trigger 2025-08-03 20:57:02 -10:00
J. Nick Koston
fffa9b813c [esp32_ble_client] Fix connection parameter timing by setting preferences before connection 2025-08-03 19:51:18 -10:00
J. Nick Koston
36c4430317 [esp32_ble] Fix BLE connection slot waste by aligning ESP-IDF timeout with client timeout (#10013) 2025-08-04 17:41:41 +12:00
J. Nick Koston
5ddf32b0a6 Merge branch 'integration' into memory_api 2025-08-03 19:32:46 -10:00
J. Nick Koston
8cc6cf2bbd Merge remote-tracking branch 'upstream/fold_ring_buffer_esp32_ble_tracker' into integration 2025-08-03 19:32:34 -10:00
J. Nick Koston
6be22a5ea9 [esp32_ble_client] Connect immediately on READY_TO_CONNECT to reduce latency (#10051) 2025-08-04 17:15:28 +12:00
J. Nick Koston
989058e6a9 [esp32_ble_client] Use FAST connection parameters for all v3 connections (#10052) 2025-08-04 17:12:06 +12:00
Jesse Hills
61386aaf6e Merge branch 'dev' into fold_ring_buffer_esp32_ble_tracker 2025-08-04 17:00:08 +12:00
J. Nick Koston
7c297366c7 [esp32_ble_tracker] Remove unnecessary STOPPED scanner state to reduce latency (#10055) 2025-08-04 16:57:59 +12:00
Clyde Stubbs
bb3ebaf955 [font] Catch file load exception (#10058)
Co-authored-by: clydeps <U5yx99dok9>
2025-08-04 16:55:54 +12:00
Jesse Hills
3007ca4d57 [core] Move docs url generator to helpers.py (#10056)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-03 23:55:46 -05:00
Jesse Hills
a5f1661643 [nfc] Rename `binary_sensor` source files (#10053) 2025-08-03 23:43:04 -05:00
J. Nick Koston
85fd10f06e Merge branch 'integration' into memory_api 2025-08-03 18:06:01 -10:00
J. Nick Koston
fe8f24cad7 Merge branch 'fold_ring_buffer_esp32_ble_tracker' into integration 2025-08-03 18:05:54 -10:00
J. Nick Koston
5a695267aa [esp32_ble_tracker] Eliminate redundant ring buffer for lower latency 2025-08-03 18:04:09 -10:00
Jesse Hills
4d683d5a69 [AI] Add note about the defines.h file needing to include all new defines added (#10054) 2025-08-03 16:45:35 -10:00
J. Nick Koston
c0c0a42362 [api] Use static allocation for areas and devices in DeviceInfoResponse (#10038)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-08-04 02:37:47 +00:00
J. Nick Koston
a6c8651298 Merge branch 'integration' into memory_api 2025-08-03 16:31:11 -10:00
J. Nick Koston
4945855415 Merge branch 'no_more_stopped_state' into integration 2025-08-03 16:31:01 -10:00
J. Nick Koston
9ff89dfb81 dry 2025-08-03 16:29:31 -10:00
J. Nick Koston
6a5eb460ef [esp32] Add framework migration warning for upcoming ESP-IDF default change (#10030)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-04 14:27:05 +12:00
J. Nick Koston
ef372eeeb7 [wifi] Replace std::stable_sort with insertion sort to save 2.4KB flash (#10037) 2025-08-04 14:19:24 +12:00
J. Nick Koston
7a7132f1c8 Merge branch 'integration' into memory_api 2025-08-03 16:16:40 -10:00
J. Nick Koston
5c491b385e Merge branch 'no_more_stopped_state' into integration 2025-08-03 16:16:33 -10:00
J. Nick Koston
2e08285570 [esp32_ble_tracker] Remove unnecessary STOPPED scanner state to reduce latency 2025-08-03 16:14:34 -10:00
J. Nick Koston
9aad0733ef [core] Update to esptool 5.0+ command syntax (#10011) 2025-08-04 14:14:17 +12:00
J. Nick Koston
494a1a216c [web_server] Conditionally compile authentication code to save flash memory (#10022) 2025-08-04 14:09:12 +12:00
J. Nick Koston
a75f73dbf0 [web_server] Reduce binary size by using EntityBase and minimizing template instantiations (#10033) 2025-08-04 14:03:37 +12:00
J. Nick Koston
c9d865a061 [core] Optimize Application::pre_setup() to reduce duplicate MAC address operations (#10039) 2025-08-04 14:02:10 +12:00
J. Nick Koston
3fbbdb4589 [web_server_idf] Replace std::find_if with simple loop to reduce binary size (#10042)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-04 14:00:56 +12:00
J. Nick Koston
cd6cf074d9 [core] Replace std::stable_sort with insertion sort to save 3.5KB flash (#10035) 2025-08-04 13:56:06 +12:00
J. Nick Koston
d86e1e29a9 [core] Convert components, devices, and areas vectors to static allocation (#10020) 2025-08-04 13:51:50 +12:00
J. Nick Koston
367e35771e Merge branch 'integration' into memory_api 2025-08-03 15:51:48 -10:00
J. Nick Koston
86af0d7965 Merge branch 'second_connection_fix' into integration 2025-08-03 15:51:42 -10:00
J. Nick Koston
e17a200b7c [esp32_ble_client] Use FAST connection parameters for all v3 connections 2025-08-03 15:51:02 -10:00
J. Nick Koston
dbaf2cdd50 [core] Replace std::find and std::max_element with simple loops to reduce binary size (#10044) 2025-08-04 13:46:06 +12:00
J. Nick Koston
27d66e5cfe Merge branch 'integration' into memory_api 2025-08-03 15:45:56 -10:00
J. Nick Koston
72142bc7d7 Merge branch 'reduce_connect_one_loop' into integration 2025-08-03 15:45:50 -10:00
J. Nick Koston
137df4ff20 [esp32_ble_client] Connect immediately on READY_TO_CONNECT to reduce latency 2025-08-03 15:45:03 -10:00
dependabot[bot]
b44d2183aa Bump aioesphomeapi from 37.2.3 to 37.2.4 (#10050)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-04 01:19:12 +00:00
@RubenKelevra
0f13af0076 Update esp32-camera library version to 2.1.0 (#9527) 2025-08-03 15:08:11 -10:00
J. Nick Koston
ef33d36d63 Merge branch 'integration' into memory_api 2025-08-03 15:04:46 -10:00
J. Nick Koston
ee899147ff Merge branch 'update_esp32_camera' into integration 2025-08-03 15:04:39 -10:00
Clyde Stubbs
339c26c815 [color][lvgl] Allow Color to be used for lv_color_t (#10016) 2025-08-04 12:51:34 +12:00
J. Nick Koston
d69e98e15d [api] Fix OTA progress updates not being sent when main loop is blocked (#10049) 2025-08-04 00:23:45 +00:00
J. Nick Koston
e95baa67d5 Merge branch 'integration' into memory_api 2025-08-03 13:55:26 -10:00
J. Nick Koston
72eaba6e2c Merge branch 'update_no_batch' into integration 2025-08-03 13:55:19 -10:00
J. Nick Koston
3e69f41b42 needs ifdef 2025-08-03 13:54:03 -10:00
J. Nick Koston
be7b63898f [api] Fix OTA progress updates not being sent when main loop is blocked 2025-08-03 13:43:15 -10:00
J. Nick Koston
a86c0b66c8 Merge branch 'unregister_fd_flash' into memory_api 2025-08-02 22:45:29 -10:00
J. Nick Koston
fb004fd0c9 Merge branch 'unregister_fd_flash' into integration 2025-08-02 22:45:24 -10:00
J. Nick Koston
53449f298e lint 2025-08-02 22:45:13 -10:00
Clyde Stubbs
b1b0638fab [config] Fix reversion of excessive yaml output after error (#10043)
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-08-02 22:35:52 -10:00
J. Nick Koston
f34e7a0051 Merge branch 'integration' into memory_api 2025-08-02 22:03:23 -10:00
J. Nick Koston
68e8966b47 Merge branch 'webserver_std_find' into integration 2025-08-02 22:03:09 -10:00
J. Nick Koston
5c7ddc584e Merge branch 'unregister_fd_flash' into integration 2025-08-02 22:03:05 -10:00
J. Nick Koston
451095eef4 [core] Replace std::find and std::max_element with simple loops to reduce binary size 2025-08-02 21:50:39 -10:00
J. Nick Koston
b667cc45cc [web_server_idf] Replace std::find_if with simple loop to reduce binary size 2025-08-02 17:22:41 -10:00
J. Nick Koston
be701c58e1 Merge branch 'integration' into memory_api 2025-08-02 15:57:31 -10:00
J. Nick Koston
aafa1190ce Merge branch 'core_dupe_add_mac' into integration 2025-08-02 15:57:25 -10:00
J. Nick Koston
7a8b2feec6 [core] Optimize Application::pre_setup() to reduce duplicate MAC address operations 2025-08-02 15:56:47 -10:00
J. Nick Koston
5d6a152c45 Merge branch 'integration' into memory_api 2025-08-02 15:37:27 -10:00
J. Nick Koston
f9e15558e7 Merge branch 'static_areas_devices_api' into integration 2025-08-02 15:37:11 -10:00
J. Nick Koston
88f251b29c [api] Use static allocation for areas and devices in DeviceInfoResponse 2025-08-02 14:48:51 -10:00
J. Nick Koston
7faded49d3 Merge branch 'integration' into memory_api 2025-08-02 14:23:48 -10:00
J. Nick Koston
07ce321e99 Merge branch 'webserver_entity_base' into integration 2025-08-02 14:23:41 -10:00
J. Nick Koston
7391bbc6ee suggestion 2025-08-02 14:20:18 -10:00
J. Nick Koston
901fd47f48 Merge branch 'dev' into webserver_entity_base 2025-08-02 14:00:47 -10:00
J. Nick Koston
296442d8f1 [core] Fix compilation errors when platform sections have no entities (#10023) 2025-08-02 13:59:20 -10:00
J. Nick Koston
5f0e1f8ab5 Merge branch 'integration' into memory_api 2025-08-02 13:01:22 -10:00
J. Nick Koston
eb5def5ad7 Merge branch 'wifi_sort' into integration 2025-08-02 13:01:11 -10:00
J. Nick Koston
61c97b029c preen 2025-08-02 13:01:03 -10:00
J. Nick Koston
3ef334c882 Merge branch 'integration' into memory_api 2025-08-02 12:54:39 -10:00
J. Nick Koston
a5a64edf9a Merge branch 'sort_core' into integration 2025-08-02 12:54:29 -10:00
J. Nick Koston
67abbc833f flex 2025-08-02 12:49:03 -10:00
J. Nick Koston
ade5aba423 Merge branch 'integration' into memory_api 2025-08-02 12:47:00 -10:00
J. Nick Koston
56eedad727 Merge branch 'wifi_sort' into integration 2025-08-02 12:46:51 -10:00
J. Nick Koston
9c76847aca [wifi] Replace std::stable_sort with insertion sort to save 2.4KB flash 2025-08-02 12:45:44 -10:00
J. Nick Koston
711eb2d7eb Merge branch 'sort_core' into integration 2025-08-02 12:28:09 -10:00
J. Nick Koston
40dcee594b preen 2025-08-02 12:27:54 -10:00
Copilot
fd442cc485 [syslog] Fix RFC3164 timestamp compliance for single-digit days (#10034)
Co-authored-by: clydebarrow <2366188+clydebarrow@users.noreply.github.com>
2025-08-03 08:21:54 +10:00
J. Nick Koston
36eab00eac preen 2025-08-02 12:14:17 -10:00
J. Nick Koston
178910d15e Merge branch 'integration' into memory_api 2025-08-02 12:04:28 -10:00
J. Nick Koston
c609341605 Merge branch 'sort_core' into integration 2025-08-02 12:04:09 -10:00
J. Nick Koston
c89bc0bfd7 [core] Replace std::stable_sort with insertion sort to save 1.3KB flash 2025-08-02 12:02:30 -10:00
J. Nick Koston
18dca1a07d Merge branch 'integration' into memory_api 2025-08-02 10:30:55 -10:00
J. Nick Koston
7ac9bdb36b Merge branch 'webserver_entity_base' into integration 2025-08-02 10:30:48 -10:00
J. Nick Koston
894565a97f [web_server] Reduce binary size by using EntityBase and minimizing template instantiations 2025-08-02 10:15:41 -10:00
J. Nick Koston
79bc7ba206 Merge branch 'integration' into memory_api 2025-08-02 10:05:56 -10:00
J. Nick Koston
2575913880 Merge branch 'drop_py_esptool' into integration 2025-08-02 10:05:48 -10:00
J. Nick Koston
716c25366c do the others 2025-08-02 10:01:36 -10:00
J. Nick Koston
aa33baf5a2 Merge branch 'integration' into memory_api 2025-08-02 09:47:48 -10:00
J. Nick Koston
ce74f07218 Merge branch 'no_entites_fix' into integration 2025-08-02 09:47:35 -10:00
J. Nick Koston
4dd175df90 Merge branch 'idf_default' into integration 2025-08-02 09:47:23 -10:00
J. Nick Koston
e4db32d73e tweak 2025-08-02 09:21:57 -10:00
J. Nick Koston
a1e7317f5e [esp32] Add framework migration warning for upcoming ESP-IDF default change 2025-08-02 08:46:08 -10:00
J. Nick Koston
30f988c5f3 [esp32] Add framework migration warning for upcoming ESP-IDF default change 2025-08-02 08:42:46 -10:00
J. Nick Koston
ed2e8466c8 [esp32] Add framework migration warning for upcoming ESP-IDF default change 2025-08-02 08:42:17 -10:00
J. Nick Koston
2c01c06828 remove test 2025-08-02 07:53:32 -10:00
J. Nick Koston
466f8d2050 remove test 2025-08-02 07:53:14 -10:00
J. Nick Koston
90b68d6fa0 Merge branch 'integration' into memory_api 2025-08-02 00:00:58 -10:00
J. Nick Koston
ee4a1528d7 Merge branch 'no_entites_fix' into integration 2025-08-02 00:00:40 -10:00
J. Nick Koston
bba63625a4 [core] Fix compilation errors when platform sections have no entities 2025-08-01 23:58:32 -10:00
J. Nick Koston
37827c1272 Merge branch 'integration' into memory_api 2025-08-01 23:08:30 -10:00
J. Nick Koston
58a5b6ac71 Merge branch 'webserver_conditional_auth' into integration 2025-08-01 23:08:19 -10:00
J. Nick Koston
49b5dd3299 missed one 2025-08-01 23:08:03 -10:00
J. Nick Koston
3986399e93 missed one 2025-08-01 23:07:37 -10:00
J. Nick Koston
9088790e63 Merge branch 'integration' into memory_api 2025-08-01 23:00:24 -10:00
J. Nick Koston
151718ccf2 Merge branch 'webserver_conditional_auth' into integration 2025-08-01 23:00:15 -10:00
J. Nick Koston
fefa35a418 define 2025-08-01 22:57:26 -10:00
J. Nick Koston
204b54ce38 preen 2025-08-01 22:53:17 -10:00
J. Nick Koston
c28147b3a4 test 2025-08-01 22:43:42 -10:00
J. Nick Koston
fdb437deb2 Merge branch 'integration' into memory_api 2025-08-01 22:39:01 -10:00
J. Nick Koston
11c48ab9c7 Merge branch 'webserver_conditional_auth' into integration 2025-08-01 22:38:54 -10:00
J. Nick Koston
e50135ef8a [web_server] Conditionally compile authentication code to save flash memory 2025-08-01 22:37:35 -10:00
J. Nick Koston
9b3ee6936d Merge branch 'integration' into memory_api 2025-08-01 21:26:51 -10:00
J. Nick Koston
983690f56d Merge branch 'static_comp_areas' into integration 2025-08-01 21:26:44 -10:00
J. Nick Koston
72d0660261 Merge branch 'integration' into memory_api 2025-08-01 21:26:29 -10:00
J. Nick Koston
dcecbb9fa8 Merge branch 'bluetooth_connection_churn_array' into integration 2025-08-01 21:26:19 -10:00
J. Nick Koston
a8f4b5c4e2 fixes 2025-08-01 21:25:58 -10:00
J. Nick Koston
015bb6f602 reorder 2025-08-01 21:05:28 -10:00
J. Nick Koston
f1650fc647 static comp, areas, devices 2025-08-01 20:54:41 -10:00
J. Nick Koston
9d0df67a9e Merge branch 'integration' into memory_api 2025-08-01 20:33:14 -10:00
J. Nick Koston
833602db74 Merge branch 'bluetooth_connection_churn_array' into integration 2025-08-01 20:32:48 -10:00
J. Nick Koston
20959c2366 [bluetooth_proxy] Optimize memory usage with fixed-size array and const string references 2025-08-01 20:32:09 -10:00
J. Nick Koston
4f58e1c8b9 [core] Convert entity vectors to static allocation for reduced memory usage (#10018) 2025-08-01 20:26:22 -10:00
J. Nick Koston
00d9baed11 [bluetooth_proxy] Eliminate heap allocations in connection state reporting (#10010) 2025-08-01 20:26:00 -10:00
J. Nick Koston
18fb3618a0 Merge branch 'integration' into memory_api 2025-08-01 15:41:50 -10:00
J. Nick Koston
0fc874b7d3 Merge branch 'static_entity_vectors' into integration 2025-08-01 15:41:43 -10:00
J. Nick Koston
d8d9123c58 fix clang-tiy 2025-08-01 15:41:33 -10:00
J. Nick Koston
4019bf9027 Merge branch 'integration' into memory_api 2025-08-01 15:36:16 -10:00
J. Nick Koston
8c2f0580d6 Merge branch 'static_entity_vectors' into integration 2025-08-01 15:36:01 -10:00
J. Nick Koston
4de68ded79 preen 2025-08-01 15:31:01 -10:00
J. Nick Koston
591b9ce87b preen 2025-08-01 15:28:48 -10:00
J. Nick Koston
7e25846cad preen 2025-08-01 15:26:51 -10:00
J. Nick Koston
a25edf93d6 preen 2025-08-01 15:26:13 -10:00
J. Nick Koston
13c749ceda preen 2025-08-01 15:26:04 -10:00
J. Nick Koston
8bf3d52fb0 tidy 2025-08-01 15:25:10 -10:00
J. Nick Koston
082e365f82 Merge branch 'integration' into memory_api 2025-08-01 15:00:49 -10:00
J. Nick Koston
48060f2f7f Merge branch 'static_entity_vectors' into integration 2025-08-01 15:00:41 -10:00
J. Nick Koston
7351fb374f [core] Convert entity vectors to static allocation for reduced memory usage 2025-08-01 14:59:01 -10:00
J. Nick Koston
9005767b67 Merge branch 'integration' into memory_api 2025-08-01 14:13:33 -10:00
J. Nick Koston
d401200404 Merge branch 'bluetooth_connection_churn_array' into integration 2025-08-01 14:13:22 -10:00
J. Nick Koston
68ab351cc8 [bluetooth_proxy] Optimize memory usage with fixed-size array and const string references 2025-08-01 14:12:08 -10:00
J. Nick Koston
1c469747d4 Merge branch 'integration' into memory_api 2025-08-01 13:01:25 -10:00
J. Nick Koston
ee6e24b424 Merge branch 'bluetooth_connection_churn' into integration 2025-08-01 13:01:10 -10:00
J. Nick Koston
11e8cfba3d tidy 2025-08-01 13:00:54 -10:00
J. Nick Koston
7d1a816c5e Merge branch 'integration' into memory_api 2025-08-01 12:47:49 -10:00
J. Nick Koston
b86722dfa4 Merge branch 'wasted_slot_fix' into integration 2025-08-01 12:47:41 -10:00
J. Nick Koston
54227ff768 fix 2025-08-01 12:43:51 -10:00
J. Nick Koston
23519c9211 fix 2025-08-01 12:43:10 -10:00
J. Nick Koston
559872fa31 Fix BLE connection slot waste by aligning ESP-IDF timeout with client timeout 2025-08-01 12:39:21 -10:00
J. Nick Koston
1a55885fc3 Merge branch 'integration' into memory_api 2025-08-01 11:21:24 -10:00
J. Nick Koston
1bd4951713 Merge branch 'bluetooth_connection_churn' into integration 2025-08-01 11:21:10 -10:00
J. Nick Koston
fa267f94ea preen 2025-08-01 11:20:57 -10:00
J. Nick Koston
9f52caf3b6 Merge branch 'integration' into memory_api 2025-08-01 11:19:46 -10:00
J. Nick Koston
c111b8bc11 Merge branch 'bluetooth_connection_churn' into integration 2025-08-01 11:19:33 -10:00
J. Nick Koston
f77d15a381 preen 2025-08-01 11:19:27 -10:00
J. Nick Koston
7667325788 Merge branch 'integration' into memory_api 2025-08-01 11:17:16 -10:00
J. Nick Koston
b51b3bf9ce Merge branch 'drop_py_esptool' into integration 2025-08-01 11:17:09 -10:00
J. Nick Koston
3aaf11f404 missed some 2025-08-01 11:15:34 -10:00
J. Nick Koston
ee7c44c3b0 Merge branch 'integration' into memory_api 2025-08-01 11:06:26 -10:00
J. Nick Koston
e079e46d99 Merge branch 'bluetooth_connection_churn' into memory_api 2025-08-01 11:06:24 -10:00
J. Nick Koston
d65fa4b5fa Merge branch 'bluetooth_connection_churn' into integration 2025-08-01 11:06:14 -10:00
J. Nick Koston
493bfaf76a cleanup 2025-08-01 11:02:48 -10:00
J. Nick Koston
f7c7aa9f9a Merge branch 'integration' into memory_api 2025-08-01 10:43:58 -10:00
J. Nick Koston
eb851174d3 merge 2025-08-01 10:43:40 -10:00
J. Nick Koston
b84fc1e777 Merge branch 'bluetooth_connection_churn' into integration 2025-08-01 10:41:46 -10:00
J. Nick Koston
3a52b754c0 preen 2025-08-01 10:30:02 -10:00
J. Nick Koston
a8af06b4dc Merge remote-tracking branch 'origin/bluetooth_connection_churn' into bluetooth_connection_churn 2025-08-01 10:29:36 -10:00
J. Nick Koston
d3cbe21fa3 preen 2025-08-01 10:29:24 -10:00
J. Nick Koston
1ce52f2b0f Update esphome/components/bluetooth_proxy/bluetooth_connection.cpp 2025-08-01 10:22:03 -10:00
J. Nick Koston
a41adad41a Merge branch 'dev' into bluetooth_connection_churn 2025-08-01 10:21:46 -10:00
dependabot[bot]
f1877ca084 Bump aioesphomeapi from 37.2.2 to 37.2.3 (#10012)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-01 20:16:28 +00:00
J. Nick Koston
7c12f1a5bf [core] Update to use esptool instead of deprecated esptool.py 2025-08-01 10:06:16 -10:00
dependabot[bot]
1f7c59f88d Bump esptool from 4.9.0 to 5.0.2 (#9983)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-01 10:02:29 -10:00
J. Nick Koston
7d06013608 missing define 2025-08-01 10:01:33 -10:00
dependabot[bot]
b5f42bc493 Bump aioesphomeapi from 37.2.1 to 37.2.2 (#10009)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-01 09:53:56 -10:00
J. Nick Koston
aa0b80b004 Eliminate heap allocations in bluetooth_proxy connection state reporting 2025-08-01 09:50:11 -10:00
Jesse Hills
d8a46c7482 [CI] Allow multiple grep options for clang-tidy (#10004) 2025-08-01 21:40:53 +12:00
J. Nick Koston
e17af87f6e preen 2025-07-31 22:25:44 -10:00
J. Nick Koston
20ad1ab4eb [wifi] Fix crash during WiFi reconnection on ESP32 with poor signal quality (#9989) 2025-08-01 02:46:52 -05:00
Clyde Stubbs
940a8b43fa [esp32] Add config option to execute from PSRAM (#9907)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-08-01 16:07:11 +10:00
J. Nick Koston
bea2f4971e preen 2025-07-31 20:07:06 -10:00
J. Nick Koston
42fe7d9fb2 preen 2025-07-31 20:05:34 -10:00
J. Nick Koston
204da1af8b preen 2025-07-31 20:03:31 -10:00
J. Nick Koston
65e2c20bcf preen 2025-07-31 20:02:25 -10:00
J. Nick Koston
837863568f preen 2025-07-31 19:52:34 -10:00
J. Nick Koston
8a15d2ea8c preen 2025-07-31 19:51:21 -10:00
J. Nick Koston
082d741066 preen 2025-07-31 19:46:40 -10:00
J. Nick Koston
264fbb4029 preen 2025-07-31 19:45:54 -10:00
J. Nick Koston
b96cd2b932 preen 2025-07-31 19:41:04 -10:00
J. Nick Koston
5fac039a06 preen 2025-07-31 19:39:08 -10:00
J. Nick Koston
1161bfcc93 preen 2025-07-31 19:35:49 -10:00
tomaszduda23
f761404bf6 [nrf52, gpio] check different port notation (#9737) 2025-08-01 16:54:20 +12:00
tomaszduda23
e4dc62ea74 [nrf52, debug] debug component for nrf52 (#8315) 2025-08-01 16:53:40 +12:00
NP v/d Spek
c42c5dd946 [espnow] Basic communication between ESP32 devices (#9582)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-08-01 16:51:01 +12:00
J. Nick Koston
d3f103c789 make entry.address take priority over mdns 2025-07-31 17:28:04 -10:00
J. Nick Koston
4caf2b7042 cleanup 2025-07-31 17:16:15 -10:00
J. Nick Koston
13e9350568 cleanup 2025-07-31 17:13:42 -10:00
J. Nick Koston
1c67dfc850 Support multiple --device arguments for address fallback 2025-07-31 17:02:39 -10:00
Keith Burzinski
291215909a [sensor] A little bit of filter clean-up (#9986) 2025-08-01 02:55:59 +00:00
Jonathan Swoboda
0954a6185c [sensor] Fix bug in percentage based delta filter (#8157)
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-08-01 02:15:56 +00:00
J. Nick Koston
f13e742bd5 [ruff] Enable RET and fix all violations (#9929) 2025-08-01 02:10:56 +00:00
tomaszduda23
7a4738ec4e [nrf52] add adc (#9321)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-08-01 13:49:39 +12:00
Clyde Stubbs
549b0d12b6 [image] Improve schemas (#9791) 2025-08-01 13:19:32 +12:00
Djordje Mandic
412f4ac341 [midea] Use c++17 constexpr and inline static in IrFollowMeData (#10002) 2025-07-31 14:28:22 -10:00
J. Nick Koston
d4ff1bcf5c [bluetooth_proxy] Implement dynamic service batching based on MTU constraints (#10001)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-31 14:15:12 -10:00
Clyde Stubbs
161f51e1f4 [esp32] Fix strapping pin validation for P4 and H2 (#9980) 2025-08-01 11:48:25 +12:00
Jonathan Swoboda
da0c47629a [esp32] Bump ESP32 platform to 54.03.21-2 (#10000) 2025-07-31 21:58:57 +00:00
J. Nick Koston
f3a57bd248 Merge branch 'integration' into memory_api 2025-07-31 11:51:50 -10:00
J. Nick Koston
151530836c Merge branch 'btp_uuids_dynamic_services_sq' into integration 2025-07-31 11:51:39 -10:00
J. Nick Koston
69d33cdd3d preen 2025-07-31 11:51:24 -10:00
J. Nick Koston
038548500c Merge branch 'integration' into memory_api 2025-07-31 11:45:32 -10:00
J. Nick Koston
5d0323d6ea Merge branch 'btp_uuids_dynamic_services_sq' into memory_api 2025-07-31 11:45:28 -10:00
J. Nick Koston
e57205793c Merge branch 'btp_uuids_dynamic_services_sq' into integration 2025-07-31 11:45:23 -10:00
J. Nick Koston
30b687ccbb fix name 2025-07-31 11:45:12 -10:00
J. Nick Koston
6d40866231 Merge branch 'integration' into memory_api 2025-07-31 11:44:07 -10:00
J. Nick Koston
b06c307ec8 Merge branch 'btp_uuids_dynamic_services_sq' into integration 2025-07-31 11:43:59 -10:00
J. Nick Koston
854e29161b only needed once 2025-07-31 11:43:49 -10:00
J. Nick Koston
6ac8c47b6e preen 2025-07-31 11:41:35 -10:00
J. Nick Koston
c10330b890 preen 2025-07-31 11:40:28 -10:00
J. Nick Koston
27861d85fe preen 2025-07-31 11:40:04 -10:00
J. Nick Koston
abb775fc53 Merge branch 'integration' into memory_api 2025-07-31 11:37:40 -10:00
J. Nick Koston
27141f2886 Merge branch 'btp_uuids_dynamic_services_sq' into integration 2025-07-31 11:37:27 -10:00
J. Nick Koston
8729ba17a0 preen 2025-07-31 11:37:17 -10:00
J. Nick Koston
ce6aee873f Merge branch 'integration' into memory_api 2025-07-31 11:27:15 -10:00
J. Nick Koston
a808d00b88 Merge branch 'btp_uuids_dynamic_services_sq' into integration 2025-07-31 11:27:01 -10:00
J. Nick Koston
3c88d8388e Merge branch 'btp_uuids_dynamic_services' into integration 2025-07-31 11:26:41 -10:00
J. Nick Koston
dd7441e104 Update esphome/components/bluetooth_proxy/bluetooth_connection.cpp 2025-07-31 11:25:48 -10:00
J. Nick Koston
0f19e23486 Update esphome/components/bluetooth_proxy/bluetooth_connection.cpp 2025-07-31 11:25:33 -10:00
J. Nick Koston
7205b1edf0 [bluetooth_proxy] Implement dynamic service batching based on MTU constraints 2025-07-31 11:24:31 -10:00
J. Nick Koston
28b277c1c4 [bluetooth_proxy] Optimize UUID transmission with efficient short_uuid field (#9995) 2025-07-31 16:20:53 -05:00
J. Nick Koston
f2b3f413fc back 2025-07-31 07:56:57 -10:00
J. Nick Koston
0ae7dcdb62 tweak 2025-07-31 07:48:54 -10:00
J. Nick Koston
0356e24bae tweak 2025-07-31 07:46:18 -10:00
J. Nick Koston
d6776804ae tweak 2025-07-31 07:45:03 -10:00
J. Nick Koston
255cf4b661 wip 2025-07-31 07:34:02 -10:00
J. Nick Koston
38e2b6c5f3 wip 2025-07-31 07:33:14 -10:00
J. Nick Koston
fe2b2d5280 fix 2025-07-31 07:19:03 -10:00
J. Nick Koston
b66141e5ba fix 2025-07-31 07:12:53 -10:00
J. Nick Koston
1225df594f preen 2025-07-31 06:59:10 -10:00
J. Nick Koston
551bff33c2 preen 2025-07-31 06:47:31 -10:00
J. Nick Koston
3a80aac6e8 ble dynamic batch 2025-07-31 06:35:19 -10:00
J. Nick Koston
3988b48708 Merge branch 'btp_uuids' into memory_api 2025-07-31 05:15:41 -10:00
J. Nick Koston
d338f753a5 Merge branch 'btp_uuids' into integration 2025-07-31 05:15:34 -10:00
J. Nick Koston
1877d5d11d Merge remote-tracking branch 'upstream/btp_uuids' into btp_uuids 2025-07-31 05:15:22 -10:00
J. Nick Koston
f120240350 dry 2025-07-31 05:15:12 -10:00
J. Nick Koston
c6aa704d39 Merge branch 'dev' into btp_uuids 2025-07-31 04:41:32 -10:00
dependabot[bot]
936a090aaa Bump aioesphomeapi from 37.2.0 to 37.2.1 (#9998)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-31 04:41:19 -10:00
J. Nick Koston
ccae1bbe15 Merge branch 'integration' into memory_api 2025-07-31 04:17:55 -10:00
J. Nick Koston
d11a3ed742 Merge branch 'btp_uuids' into integration 2025-07-31 04:17:46 -10:00
J. Nick Koston
ef05c97984 Merge remote-tracking branch 'upstream/btp_uuids' into btp_uuids 2025-07-31 04:06:06 -10:00
J. Nick Koston
f3d42ef6e4 save 4 bytes since we must store as uint32_t anyways 2025-07-31 04:05:48 -10:00
J. Nick Koston
766e3480cf Merge branch 'dev' into btp_uuids 2025-07-30 23:20:50 -10:00
dependabot[bot]
1be6d27012 Bump aioesphomeapi from 37.1.6 to 37.2.0 (#9996)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-30 23:20:43 -10:00
J. Nick Koston
40e2960264 fixes 2025-07-30 22:15:39 -10:00
J. Nick Koston
712de79973 tidy 2025-07-30 22:06:31 -10:00
J. Nick Koston
37911e84f2 [bluetooth_proxy] Send native 16/32-bit UUIDs instead of always converting to 128-bit 2025-07-30 21:24:40 -10:00
J. Nick Koston
6f4a8100ef Merge branch 'integration' into memory_api 2025-07-30 19:26:41 -10:00
J. Nick Koston
f52dadab41 Merge remote-tracking branch 'upstream/dev' into integration 2025-07-30 19:26:28 -10:00
J. Nick Koston
71557c9f58 [bluetooth_proxy] Batch BLE service discovery messages for 67% reduction in API traffic (#9992) 2025-07-30 23:11:11 -05:00
J. Nick Koston
88cfcc1967 [esp32_ble_client] Fix BLE connection stability for WiFi-based proxies (#9993)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2025-07-30 22:36:33 -05:00
GilDev
fb379bbb88 [wifi] Allow fast_connect with multiple networks (#9947) 2025-07-31 15:34:49 +12:00
J. Nick Koston
383f54810c Merge branch 'fix_normal_opts' into memory_api 2025-07-30 16:12:47 -10:00
J. Nick Koston
3abc959e37 Merge branch 'fix_normal_opts' into integration 2025-07-30 16:12:42 -10:00
J. Nick Koston
d1cf6c2b14 [esp32_ble_client] Fix BLE connection stability for WiFi-based proxies 2025-07-30 16:12:07 -10:00
J. Nick Koston
77677f3edc Merge branch 'integration' into memory_api 2025-07-30 15:39:40 -10:00
J. Nick Koston
a46d9f3ff2 Merge branch 'service_batching_ble' into integration 2025-07-30 15:39:30 -10:00
J. Nick Koston
97b522da98 Merge branch 'dev' into service_batching_ble 2025-07-30 15:39:11 -10:00
J. Nick Koston
2b58f78082 fix busy loop on fail 2025-07-30 15:38:19 -10:00
mrtoy-me
88d8cfe6a2 [tm1651] Remove dependency on Arduino Library (#9645)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
2025-07-30 20:20:55 -05:00
J. Nick Koston
f25abc3248 [esp32_ble] Fix spurious BLE 5.0 event warnings on ESP32-S3 (#9969) 2025-07-30 20:18:50 -05:00
J. Nick Koston
5b6e152d6c [esp32_touch] Work around ESP-IDF v5.4 regression in touch_pad_read_filtered (#9957)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-30 20:17:35 -05:00
J. Nick Koston
1d0a38446f [api] Reduce flash usage through targeted optimizations (#9979) 2025-07-30 20:10:23 -05:00
rwrozelle
853dca6c5c [api] Bump APIVersion to 1.11 (#9990) 2025-07-30 15:02:09 -10:00
J. Nick Koston
caa9f6fdc7 Merge branch 'integration' into memory_api 2025-07-30 15:00:31 -10:00
J. Nick Koston
e50840232e Merge branch 'service_batching_ble' into integration 2025-07-30 15:00:20 -10:00
J. Nick Koston
08aad73af9 did not need 2025-07-30 15:00:11 -10:00
J. Nick Koston
093e8093f5 Merge branch 'integration' into memory_api 2025-07-30 14:54:05 -10:00
J. Nick Koston
60b548fccd Merge branch 'service_batching_ble' into integration 2025-07-30 14:53:59 -10:00
J. Nick Koston
7692aacc2d [bluetooth_proxy] Batch BLE service discovery messages for 67% reduction in API traffic 2025-07-30 14:51:35 -10:00
J. Nick Koston
ecb029e0a7 [bluetooth_proxy] Batch BLE service discovery messages for 67% reduction in API traffic 2025-07-30 14:50:20 -10:00
J. Nick Koston
12cd1ec525 [bluetooth_proxy] Batch BLE service discovery messages for 67% reduction in API traffic 2025-07-30 14:46:13 -10:00
J. Nick Koston
a4ebcc691a Batch 3 services 2025-07-30 14:33:11 -10:00
Jesse Hills
97560fd9ef [CI] Add labels for checkboxes (#9991) 2025-07-31 12:17:20 +12:00
J. Nick Koston
de51f0f205 Merge branch 'integration' into memory_api 2025-07-30 13:18:01 -10:00
J. Nick Koston
1194b20b09 Merge branch 'wifi_reconnect_race_fix' into integration 2025-07-30 13:17:49 -10:00
J. Nick Koston
79bee386ff [wifi] Fix crash during WiFi reconnection on ESP32 with poor signal quality 2025-07-30 13:16:40 -10:00
Clyde Stubbs
4b7f3355ea [core] Fix regex for lambda id() replacement (#9975) 2025-07-30 12:56:43 -10:00
dependabot[bot]
110eac4f09 Bump aioesphomeapi from 37.1.5 to 37.1.6 (#9988)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-30 12:46:01 -10:00
rwrozelle
79533cb0d7 media_player add off on capability (#9294)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick+github@koston.org>
2025-07-30 12:02:53 -10:00
J. Nick Koston
07bc41a900 Merge branch 'integration' into memory_api 2025-07-30 09:43:05 -10:00
J. Nick Koston
e23fb2bf4e Merge branch 'api_polish' into integration 2025-07-30 09:42:52 -10:00
J. Nick Koston
8d9daca386 address copilot review comments 2025-07-30 09:42:28 -10:00
J. Nick Koston
1600ab58a1 Merge branch 'integration' into memory_api 2025-07-30 00:12:36 -10:00
J. Nick Koston
3e2b7e8309 Merge branch 'api_polish' into integration 2025-07-30 00:12:29 -10:00
J. Nick Koston
1568fc36cc preen 2025-07-29 23:39:32 -10:00
J. Nick Koston
a8493df659 api polish 2025-07-29 23:33:43 -10:00
dependabot[bot]
f4f69e827b Bump ruff from 0.12.5 to 0.12.7 (#9976)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-07-30 09:17:47 +00:00
dependabot[bot]
48a4dde824 Bump aioesphomeapi from 37.1.4 to 37.1.5 (#9977)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-30 09:16:00 +00:00
J. Nick Koston
24174c3eed Merge branch 'integration' into memory_api 2025-07-29 22:55:46 -10:00
J. Nick Koston
e81c5ef61e Merge branch 'ruff_ret' into integration 2025-07-29 22:55:33 -10:00
J. Nick Koston
8e68b1a7d9 Merge remote-tracking branch 'upstream/dev' into ruff_ret 2025-07-29 22:48:15 -10:00
J. Nick Koston
9b4fe54f45 [esp32_ble_client] Fix connection failures with short discovery timeout devices and speed up BLE connections (#9971) 2025-07-29 19:19:12 -10:00
Keith Burzinski
913c58cd2c [template] Add tests for more sensor filters (#9973) 2025-07-30 14:20:25 +12:00
J. Nick Koston
4441d0ea8d Merge branch 'integration' into memory_api 2025-07-29 15:53:43 -10:00
J. Nick Koston
fdde5ea0f1 Merge branch 'service_discovery' into integration 2025-07-29 15:53:31 -10:00
J. Nick Koston
68b8fab33a const 2025-07-29 15:53:15 -10:00
J. Nick Koston
537c774a6c use const 2025-07-29 15:47:36 -10:00
J. Nick Koston
4aec2a95f2 Merge branch 'integration' into memory_api 2025-07-29 14:58:15 -10:00
J. Nick Koston
7698e7faee Merge branch 'service_discovery' into integration 2025-07-29 14:58:04 -10:00
J. Nick Koston
561d7ec978 cleanup 2025-07-29 14:57:52 -10:00
Keith Burzinski
374858efeb [sensor] Add new filter: `throttle_with_priority` (#9937) 2025-07-30 12:53:14 +12:00
Samuel Sieb
14dd48f9c3 [wifi] add more disconnect reason descriptions (#9955)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2025-07-30 12:41:31 +12:00
J. Nick Koston
579c6c57e0 Merge branch 'service_discovery' into memory_api 2025-07-29 14:32:55 -10:00
J. Nick Koston
3018b1a7e5 Merge branch 'service_discovery' into integration 2025-07-29 14:32:49 -10:00
J. Nick Koston
63484d9f08 tidy 2025-07-29 14:32:34 -10:00
J. Nick Koston
ff1239c2a4 Merge branch 'integration' into memory_api 2025-07-29 14:27:04 -10:00
J. Nick Koston
4999b5793f Merge branch 'service_discovery' into integration 2025-07-29 14:26:53 -10:00
J. Nick Koston
f794506002 Revert "[esp32_ble] Add PHY configuration and default to 1M for compatibility"
This reverts commit 5c44cd8962.
2025-07-29 14:26:47 -10:00
J. Nick Koston
92055b221a Revert "test"
This reverts commit 51d2e70854.
2025-07-29 14:26:37 -10:00
J. Nick Koston
f810ebbf79 [esp32_ble_client] Fix connection failures with short discovery timeout devices and speed up BLE connections 2025-07-29 14:24:46 -10:00
J. Nick Koston
76d33308d9 [api] Eliminate heap allocations when populating repeated fields from containers (#9948) 2025-07-30 10:41:37 +12:00
Dayowe
daccaf36a7 Fix WiFi to prefer strongest AP when multiple APs have same SSID (#9963)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2025-07-30 09:10:53 +12:00
J. Nick Koston
53d68abf79 Merge branch 'integration' into memory_api 2025-07-29 10:17:46 -10:00
J. Nick Koston
ad86f1352b Merge branch 's3_ble_events' into integration 2025-07-29 10:17:35 -10:00
J. Nick Koston
1adf45eebf [esp32_ble] Fix spurious BLE 5.0 event warnings on ESP32-S3 2025-07-29 10:17:00 -10:00
Clyde Stubbs
56c88807ee [mipi_dsi] Add dependencies (#9952) 2025-07-30 08:16:32 +12:00
J. Nick Koston
11f0d0015c Merge branch 'integration' into memory_api 2025-07-29 09:55:31 -10:00
J. Nick Koston
dadcab1581 Merge branch 'ble_phy_defaults' into integration 2025-07-29 09:55:25 -10:00
J. Nick Koston
51d2e70854 test 2025-07-29 09:54:44 -10:00
J. Nick Koston
5c44cd8962 [esp32_ble] Add PHY configuration and default to 1M for compatibility 2025-07-29 09:50:11 -10:00
dependabot[bot]
9c6dbbd8ea Bump aioesphomeapi from 37.1.3 to 37.1.4 (#9964) 2025-07-29 17:43:35 +00:00
J. Nick Koston
2537c4437f cleanup 2025-07-29 07:15:12 -10:00
J. Nick Koston
9133554588 Merge branch 'integration' into memory_api 2025-07-29 07:06:56 -10:00
J. Nick Koston
f577c857c5 Merge remote-tracking branch 'upstream/api_heap_churn_info' into integration 2025-07-29 07:06:40 -10:00
J. Nick Koston
5c5f29fbc8 Merge branch 'dev' into api_heap_churn_info 2025-07-29 07:06:25 -10:00
J. Nick Koston
e222232376 Merge branch 'integration' into memory_api 2025-07-29 07:04:07 -10:00
J. Nick Koston
05ad1d310b Merge remote-tracking branch 'upstream/dev' into integration 2025-07-29 07:03:50 -10:00
rwrozelle
a7dd849a8e Media player API enumeration alignment and feature flags (#9949)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-07-29 07:00:47 -10:00
J. Nick Koston
aeec9a9379 Merge branch 'integration' into memory_api 2025-07-28 22:11:00 -10:00
J. Nick Koston
e12458c30f Merge branch 'api_heap_churn_info' into integration 2025-07-28 22:10:49 -10:00
J. Nick Koston
8b52a9a02e Merge branch 'integration' into memory_api 2025-07-28 22:10:32 -10:00
J. Nick Koston
b324978c9d Merge branch 'disable_touch_pad_read_filtered' into integration 2025-07-28 22:10:19 -10:00
J. Nick Koston
e0e0a1a420 [esp32_touch] Work around ESP-IDF v5.4 regression in touch_pad_read_filtered() 2025-07-28 22:08:29 -10:00
Clyde Stubbs
1f0c606be4 [component] Revert setup messages to LOG_CONFIG level (#9956) 2025-07-29 07:32:45 +00:00
Jesse Hills
ace375944c [esp32] Fix post build (#9951)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-29 06:44:45 +00:00
Clyde Stubbs
5f7c2f771f [adc] Enable ADC on ESP32-P4 (#9954) 2025-07-29 18:20:37 +12:00
J. Nick Koston
20294e9307 Merge remote-tracking branch 'upstream/api_heap_churn_info' into api_heap_churn_info 2025-07-28 19:54:27 -10:00
J. Nick Koston
e113078f82 document 2025-07-28 19:54:08 -10:00
Jonathan Swoboda
3d5b602288 [esp32] Bump platform to 54.03.21-1 and add support for tagged releases (#9926)
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-07-29 05:52:34 +00:00
J. Nick Koston
b4fe1e61f5 Merge branch 'dev' into api_heap_churn_info 2025-07-28 19:46:05 -10:00
J. Nick Koston
7822865aee limit change 2025-07-28 19:37:44 -10:00
J. Nick Koston
14d1fd02cc fix 2025-07-28 19:30:32 -10:00
Djordje Mandic
6d30269565 [output] Add set_min_power & set_max_power actions for FloatOutput (#8934)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-07-29 17:22:44 +12:00
Keith Burzinski
4ff3137c0d [gps] Fix slow parsing (#9953) 2025-07-29 17:21:52 +12:00
rwrozelle
9d43ddd6f1 Openthread add Teardown (#9275)
Co-authored-by: mc <mc@debian>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-07-29 16:25:17 +12:00
Jonathan Swoboda
f733c43dec [heatpumpir] Fix issue with IRremoteESP8266 being included on ESP32 (#9950) 2025-07-29 15:59:58 +12:00
J. Nick Koston
397bc80a15 Merge branch 'dev' into api_heap_churn_info 2025-07-28 13:41:46 -10:00
Keith Burzinski
f5f0a01a85 [text_sensor] Add support for default filters (#9936) 2025-07-29 11:35:40 +12:00
Keith Burzinski
908891a096 [binary_sensor] Add support for default filters (#9935) 2025-07-29 11:35:11 +12:00
Keith Burzinski
7657316a92 [sensor] Add support for default filters (#9934) 2025-07-29 11:34:52 +12:00
J. Nick Koston
8ebde566d2 Merge branch 'dev' into api_heap_churn_info 2025-07-28 13:34:26 -10:00
J. Nick Koston
4f425c700a [esp32] Enable LWIP core locking on ESP-IDF to reduce socket operation overhead (#9857) 2025-07-29 11:33:54 +12:00
J. Nick Koston
dbe895f0a3 preen 2025-07-28 12:46:58 -10:00
J. Nick Koston
7ab8cc49c6 preen 2025-07-28 12:44:07 -10:00
J. Nick Koston
5b7085287f preen 2025-07-28 12:43:50 -10:00
J. Nick Koston
4e565202e4 preen 2025-07-28 12:42:46 -10:00
J. Nick Koston
224ea51cd7 zero copy vectors 2025-07-28 12:35:38 -10:00
J. Nick Koston
2c9987869e [api] Align ProtoSize API design with ProtoWriteBuffer pattern (#9920) 2025-07-29 10:28:32 +12:00
J. Nick Koston
68f388f78e [api] Optimize protobuf empty message handling to reduce flash and runtime overhead (#9908) 2025-07-29 10:25:07 +12:00
Jesse Hills
189d20a822 [heatpumpir] Bump library to 1.0.37 (#9944) 2025-07-28 16:21:53 -05:00
dependabot[bot]
08defd7360 Bump aioesphomeapi from 37.1.2 to 37.1.3 (#9943)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-28 11:02:53 -10:00
J. Nick Koston
59d466a6c8 [api] Remove unnecessary string copies from optional access (#9897) 2025-07-29 08:55:41 +12:00
J. Nick Koston
85435e6b5f [scheduler] Eliminate more runtime string allocations from retry (#9930) 2025-07-29 08:54:16 +12:00
Clyde Stubbs
f9453f9642 [lvgl] Bugfix for tileview (#9938) 2025-07-29 08:43:22 +12:00
Jesse Hills
f6cdbe37f9 Merge branch 'release' into dev 2025-07-28 19:34:23 +12:00
Jesse Hills
d6b222c370 Merge pull request #9933 from esphome/bump-2025.7.4
2025.7.4
2025-07-28 19:33:19 +12:00
Clyde Stubbs
eecdaa5163 [config_validation] extend should combine extra validations (#9939) 2025-07-28 19:23:35 +12:00
J. Nick Koston
4933ef780b [bluetooth_proxy] Fix service discovery cache pollution and descriptor count parameter bug (#9902) 2025-07-27 23:50:17 -05:00
J. Nick Koston
226d465f6a Merge branch 'integration' into memory_api 2025-07-27 18:50:05 -10:00
J. Nick Koston
2123e778e5 Merge branch 'protosize_object' into integration 2025-07-27 18:49:51 -10:00
J. Nick Koston
14744fc381 Merge remote-tracking branch 'upstream/dev' into integration 2025-07-27 18:49:43 -10:00
J. Nick Koston
ab643350f5 Merge remote-tracking branch 'upstream/dev' into protosize_object 2025-07-27 18:46:54 -10:00
J. Nick Koston
1702356fc8 [api] Fix string lifetime issue in Home Assistant service calls with templated values (#9909) 2025-07-28 16:39:25 +12:00
J. Nick Koston
05f6d01cbe [api] Add conditional compilation for Home Assistant service subscriptions (#9900) 2025-07-27 18:35:35 -10:00
Jesse Hills
573dad1736 Bump version to 2025.7.4 2025-07-28 15:55:07 +12:00
Jimmy Hedman
3a6cc0ea3d Fail with old lerp (#9914)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-07-28 15:55:07 +12:00
cryptk
2f9475a927 Add seed flag when running setup with uv present (#9932) 2025-07-28 15:55:07 +12:00
Jesse Hills
8dce7b0905 [logger] Don't allow `logger.log actions without configuring the logger` (#9821) 2025-07-28 15:55:07 +12:00
Eric Hoffmann
8b0ad3072f fix: non-optional x/y target calculation for ld2450 (#9849) 2025-07-28 15:55:07 +12:00
Clyde Stubbs
93028a4d90 [gt911] i2c fixes (#9822) 2025-07-28 15:55:07 +12:00
Jonathan Swoboda
c9793f3741 [remote_receiver] Fix idle validation (#9819) 2025-07-28 15:55:07 +12:00
tomaszduda23
5029e248eb [packages] add example from documentation to component tests (#9891) 2025-07-28 15:28:27 +12:00
Cornelius Mosch
087970bca8 replace os.getlogin() with getpass.getuser() (#9928) 2025-07-28 15:25:32 +12:00
J. Nick Koston
7f0c66f835 [api] Reduce code duplication in send_noise_encryption_set_key_response (#9918) 2025-07-28 15:24:15 +12:00
J. Nick Koston
84ed1bcf34 [light] Reduce flash usage by 832 bytes through code optimization (#9924) 2025-07-28 15:22:56 +12:00
J. Nick Koston
6ed9214465 [core] Use nullptr defaults in status_set_error/warning to reduce flash usage (#9931) 2025-07-28 15:20:30 +12:00
Jimmy Hedman
a3690422bf Fail with old lerp (#9914)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-07-28 15:19:17 +12:00
cryptk
20b61d4bdb Add seed flag when running setup with uv present (#9932) 2025-07-28 14:20:51 +12:00
J. Nick Koston
81cea0b2eb Merge branch 'integration' into memory_api 2025-07-27 11:36:35 -10:00
J. Nick Koston
44ec2b37af Merge branch 'unspec_string_copies' into integration 2025-07-27 11:36:28 -10:00
J. Nick Koston
0fa9704675 [core] Use nullptr defaults in status_set_error/warning to reduce flash usage 2025-07-27 11:25:42 -10:00
J. Nick Koston
0f5aff4d21 Merge branch 'integration' into memory_api 2025-07-27 11:08:21 -10:00
J. Nick Koston
d56ff40952 Merge branch 'retry_string_cleanup' into integration 2025-07-27 11:08:12 -10:00
J. Nick Koston
4fc6ef6d3e cover 2025-07-27 10:54:57 -10:00
J. Nick Koston
6c8df02d9c [core] Optimize scheduler retry mechanism to reduce flash usage 2025-07-27 10:45:35 -10:00
J. Nick Koston
a4026d6ba1 [ruff] Enable RET and fix all violations 2025-07-27 08:34:43 -10:00
J. Nick Koston
e3b3259243 Merge branch 'integration' into memory_api 2025-07-26 22:15:15 -10:00
J. Nick Koston
0d23476a1d Merge branch 'light_opt_part2' into integration 2025-07-26 22:15:06 -10:00
J. Nick Koston
4f28aacf66 fix 2025-07-26 22:11:48 -10:00
J. Nick Koston
5769fbc3b6 fix 2025-07-26 22:10:17 -10:00
Clyde Stubbs
a2ed209542 [wifi] Disallow psram config with arduino (#9922) 2025-07-27 02:57:37 -05:00
J. Nick Koston
9cf0535d39 Merge branch 'integration' into memory_api 2025-07-26 21:55:34 -10:00
J. Nick Koston
52b04611ba Merge branch 'light_opt_part2' into integration 2025-07-26 21:55:23 -10:00
J. Nick Koston
bcdfc744c6 missed existing helper 2025-07-26 21:48:53 -10:00
J. Nick Koston
e223a1008b missed existing helper 2025-07-26 21:46:54 -10:00
J. Nick Koston
b7d48284ac missed existing helper 2025-07-26 21:44:31 -10:00
J. Nick Koston
de3e9451dc missed existing helper 2025-07-26 21:43:53 -10:00
J. Nick Koston
eb25ff0de7 Merge branch 'integration' into memory_api 2025-07-26 21:38:04 -10:00
J. Nick Koston
fa4a52a0dc Merge branch 'light_opt_part2' into integration 2025-07-26 21:37:53 -10:00
J. Nick Koston
51de85b1c1 merge 2025-07-26 21:37:46 -10:00
J. Nick Koston
4f30fd9668 Merge branch 'integration' into memory_api 2025-07-26 21:36:16 -10:00
J. Nick Koston
c9995e5fac Merge branch 'light_opt_part2' into integration 2025-07-26 21:35:54 -10:00
J. Nick Koston
28dbf3bbcc revert 2025-07-26 21:32:34 -10:00
J. Nick Koston
29e61c8913 revert 2025-07-26 21:27:44 -10:00
J. Nick Koston
10434ac2a3 fixes 2025-07-26 21:24:24 -10:00
J. Nick Koston
92e9383164 light_opt_part2 2025-07-26 21:18:58 -10:00
J. Nick Koston
3a49215dd6 preen 2025-07-26 21:18:39 -10:00
J. Nick Koston
dc45bed048 preen 2025-07-26 21:15:53 -10:00
J. Nick Koston
8e6a053ead preen 2025-07-26 21:11:57 -10:00
J. Nick Koston
5cf89f8594 light2 2025-07-26 21:06:39 -10:00
J. Nick Koston
825f3eee70 light2 2025-07-26 21:04:39 -10:00
J. Nick Koston
eec31846e1 Revert "Revert "light2""
This reverts commit b99b0140ae.
2025-07-26 20:50:50 -10:00
J. Nick Koston
b99b0140ae Revert "light2"
This reverts commit af7e43bbc1.
2025-07-26 20:49:51 -10:00
J. Nick Koston
af7e43bbc1 light2 2025-07-26 20:45:21 -10:00
J. Nick Koston
52750f931b light2 2025-07-26 20:42:45 -10:00
J. Nick Koston
446e9bc529 Merge branch 'integration' into memory_api 2025-07-26 20:26:28 -10:00
J. Nick Koston
6c44392359 Merge remote-tracking branch 'upstream/dev' into integration 2025-07-26 20:26:16 -10:00
Keith Burzinski
14862904ac [power_supply] Optimize logging, reduce flash footprint (#9923) 2025-07-26 19:54:10 -10:00
J. Nick Koston
bcc56648c0 [light] Reduce flash memory usage by optimizing validation and color mode logic (#9921) 2025-07-26 23:56:35 -05:00
J. Nick Koston
d9f3ecd1a3 Merge branch 'integration' into memory_api 2025-07-26 17:14:24 -10:00
J. Nick Koston
3ec6b9d1a4 Merge branch 'light_flash' into integration 2025-07-26 17:14:09 -10:00
J. Nick Koston
2d237d0f97 fixes 2025-07-26 17:13:59 -10:00
J. Nick Koston
f9d0fa1c6d Merge branch 'integration' into memory_api 2025-07-26 17:09:53 -10:00
J. Nick Koston
75525349c7 Merge branch 'light_flash' into integration 2025-07-26 17:09:44 -10:00
J. Nick Koston
79984a288e preen 2025-07-26 17:06:39 -10:00
J. Nick Koston
f333ab1fd7 cover 2025-07-26 17:01:02 -10:00
J. Nick Koston
5bdd850012 reduce light flash 2025-07-26 16:59:57 -10:00
Clyde Stubbs
e00839a608 [ci-custom] Report actual changes needed for absolute import (#9919) 2025-07-27 11:51:57 +10:00
J. Nick Koston
6e345c5f23 dry 2025-07-26 15:03:36 -10:00
J. Nick Koston
5ebce4a901 dry 2025-07-26 15:01:45 -10:00
J. Nick Koston
a82b5fa87a dry 2025-07-26 15:01:07 -10:00
J. Nick Koston
193a85eb1c dry 2025-07-26 14:57:50 -10:00
J. Nick Koston
0773fc320b dry 2025-07-26 14:57:42 -10:00
J. Nick Koston
32edc3f062 dry 2025-07-26 14:54:52 -10:00
J. Nick Koston
ae12097636 dry 2025-07-26 14:53:14 -10:00
J. Nick Koston
2e16b3ea31 dry 2025-07-26 14:52:15 -10:00
J. Nick Koston
d98a3fca96 dry 2025-07-26 14:49:43 -10:00
J. Nick Koston
e94f5bffa3 preen 2025-07-26 14:45:26 -10:00
J. Nick Koston
09a30689e9 preen 2025-07-26 14:19:35 -10:00
J. Nick Koston
33ec5e195f Make ProtoSize an object 2025-07-26 14:09:17 -10:00
J. Nick Koston
1032e5c220 Make ProtoSize an object 2025-07-26 14:08:43 -10:00
J. Nick Koston
d111b84ca4 Make ProtoSize an object 2025-07-26 14:03:47 -10:00
J. Nick Koston
de6686ee14 Merge branch 'integration' into memory_api 2025-07-26 13:11:37 -10:00
J. Nick Koston
fd5805c54a Merge branch 'noise_key_flash' into integration 2025-07-26 13:11:19 -10:00
J. Nick Koston
32d6acb3b2 [api] Reduce code duplication in send_noise_encryption_set_key_response 2025-07-26 11:52:23 -10:00
Clyde Stubbs
cf73f72119 [wifi] Allow config to use PSRAM (#9866) 2025-07-27 07:45:20 +10:00
J. Nick Koston
981b906579 [logger] Use C++17 nested namespace syntax (#9916) 2025-07-26 11:06:01 -10:00
Clyde Stubbs
0e2520e4c0 [core] Fix format error in log printf (#9911) 2025-07-26 08:02:02 -10:00
J. Nick Koston
cab11086ac Merge branch 'integration' into memory_api 2025-07-25 23:38:30 -10:00
J. Nick Koston
c40a9eabef Merge branch 'services_must_make_copy' into integration 2025-07-25 23:38:17 -10:00
J. Nick Koston
17e1d3650c missed ha 2025-07-25 23:37:40 -10:00
J. Nick Koston
56b00f9bb7 Merge branch 'integration' into memory_api 2025-07-25 23:31:53 -10:00
J. Nick Koston
cfb4ef120f Merge remote-tracking branch 'upstream/dev' into integration 2025-07-25 23:31:43 -10:00
J. Nick Koston
eb2bfd4b70 Merge branch 'empty_messages' into integration 2025-07-25 23:31:37 -10:00
J. Nick Koston
91b46757b4 Merge remote-tracking branch 'upstream/dev' into empty_messages 2025-07-25 23:30:39 -10:00
J. Nick Koston
2d501e7b42 Merge branch 'dev' into services_must_make_copy 2025-07-25 23:27:43 -10:00
dependabot[bot]
84ffa4274c Bump aioesphomeapi from 37.1.0 to 37.1.2 (#9910)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-25 23:27:35 -10:00
J. Nick Koston
01101380ee Merge branch 'integration' into memory_api 2025-07-25 23:15:42 -10:00
J. Nick Koston
eedcd818a9 Merge branch 'services_must_make_copy' into integration 2025-07-25 23:15:31 -10:00
J. Nick Koston
ab02f6da3d custom api 2025-07-25 23:15:24 -10:00
J. Nick Koston
90908996b1 Merge branch 'integration' into memory_api 2025-07-25 23:13:32 -10:00
J. Nick Koston
58d043ea0d Merge branch 'services_must_make_copy' into integration 2025-07-25 23:13:09 -10:00
J. Nick Koston
a57daa2656 Merge branch 'integration' into memory_api 2025-07-25 23:12:04 -10:00
J. Nick Koston
6bb6e475e7 Merge branch 'optional_api' into integration 2025-07-25 23:11:54 -10:00
J. Nick Koston
01b24a7b69 fix merge conflict 2025-07-25 23:11:11 -10:00
J. Nick Koston
5feb891e97 fix 2025-07-25 23:08:22 -10:00
J. Nick Koston
c32767db81 Merge branch 'dev' into optional_api 2025-07-25 23:06:10 -10:00
J. Nick Koston
0155769ffe [api] Fix string lifetime issue in Home Assistant service calls with templated values 2025-07-25 23:01:24 -10:00
J. Nick Koston
d64e4d3c49 [ruff] Enable FURB rules for code modernization (#9896) 2025-07-26 20:54:03 +12:00
J. Nick Koston
30b6a683c2 Merge branch 'integration' into memory_api 2025-07-25 21:39:46 -10:00
J. Nick Koston
7976c5e06c Merge branch 'api_ha_services' into memory_api 2025-07-25 21:39:38 -10:00
J. Nick Koston
50f770c600 Merge branch 'api_ha_services' into integration 2025-07-25 21:39:28 -10:00
J. Nick Koston
1cf5822bf5 Merge remote-tracking branch 'upstream/dev' into api_ha_services 2025-07-25 21:39:09 -10:00
J. Nick Koston
369b5e6c86 Merge branch 'dev' into optional_api 2025-07-25 21:36:44 -10:00
J. Nick Koston
d54db471bd [i2c] Fix logging level for bus scan results in dump_config (#9904)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-26 01:29:57 -05:00
J. Nick Koston
a9d6ece752 [api] Add conditional compilation for Home Assistant state subscriptions (#9898) 2025-07-26 01:28:44 -05:00
J. Nick Koston
da491f7090 [api] Add missing USE_API_PASSWORD guards to reduce flash usage (#9899) 2025-07-26 01:21:09 -05:00
J. Nick Koston
f6ebdd79fe Merge branch 'dev' into empty_messages 2025-07-25 19:29:06 -10:00
J. Nick Koston
9010ddf56b [api] Optimize protobuf empty message handling to reduce flash and runtime overhead 2025-07-25 19:28:20 -10:00
dependabot[bot]
11f970edec Bump aioesphomeapi from 37.0.4 to 37.1.0 (#9905)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-26 04:36:42 +00:00
J. Nick Koston
89c47c0dae Merge branch 'integration' into memory_api 2025-07-25 18:34:24 -10:00
J. Nick Koston
cae688446f Merge remote-tracking branch 'upstream/dependabot/pip/aioesphomeapi-37.1.0' into integration 2025-07-25 18:34:16 -10:00
dependabot[bot]
9bf666d63f Bump aioesphomeapi from 37.0.4 to 37.1.0
Bumps [aioesphomeapi](https://github.com/esphome/aioesphomeapi) from 37.0.4 to 37.1.0.
- [Release notes](https://github.com/esphome/aioesphomeapi/releases)
- [Commits](https://github.com/esphome/aioesphomeapi/compare/v37.0.4...v37.1.0)

---
updated-dependencies:
- dependency-name: aioesphomeapi
  dependency-version: 37.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-26 04:33:21 +00:00
J. Nick Koston
63e5a0b38c Merge branch 'integration' into memory_api 2025-07-25 18:28:03 -10:00
J. Nick Koston
09a24e0d88 Merge branch 'i2c_logging_fix' into integration 2025-07-25 18:27:54 -10:00
J. Nick Koston
de69e78a78 [i2c] Fix logging level for bus scan results in dump_config 2025-07-25 18:26:10 -10:00
Jesse Hills
6d37b916dc [logger] Don't allow `logger.log actions without configuring the logger` (#9821) 2025-07-26 16:23:36 +12:00
J. Nick Koston
00266e080b merge 2025-07-25 17:39:48 -10:00
J. Nick Koston
9d4cf2543f Merge branch 'ble_chars' into integration 2025-07-25 17:39:24 -10:00
J. Nick Koston
967993f70d fix descriptor lookup 2025-07-25 17:39:14 -10:00
J. Nick Koston
711b153a6a revert 2025-07-25 17:32:30 -10:00
J. Nick Koston
ec9b10954c Merge branch 'integration' into memory_api 2025-07-25 17:25:55 -10:00
J. Nick Koston
c272e7282a Merge branch 'ble_chars' into integration 2025-07-25 17:25:47 -10:00
J. Nick Koston
5884bdb9e8 preen 2025-07-25 17:25:30 -10:00
J. Nick Koston
f0a63313b1 Merge branch 'integration' into memory_api 2025-07-25 17:22:54 -10:00
J. Nick Koston
15806fb95d Merge branch 'ble_chars' into integration 2025-07-25 17:22:40 -10:00
J. Nick Koston
d40a5a1651 preen 2025-07-25 17:21:38 -10:00
J. Nick Koston
535e995c75 preen 2025-07-25 17:20:37 -10:00
J. Nick Koston
6c825def9e Merge branch 'integration' into memory_api 2025-07-25 17:15:23 -10:00
J. Nick Koston
cb51c2e930 Merge branch 'ble_chars' into integration 2025-07-25 17:15:14 -10:00
J. Nick Koston
a06c4e1d56 cleanup 2025-07-25 17:14:56 -10:00
J. Nick Koston
793b3de3e9 revert 2025-07-25 16:50:36 -10:00
J. Nick Koston
60d988ca92 Merge branch 'ble_chars' into memory_api 2025-07-25 16:44:40 -10:00
J. Nick Koston
abf94e61f1 Revert "Revert "cleanup""
This reverts commit 7320cd24f0.
2025-07-25 16:43:28 -10:00
J. Nick Koston
7320cd24f0 Revert "cleanup"
This reverts commit 40a3232267.
2025-07-25 16:42:05 -10:00
J. Nick Koston
40a3232267 cleanup 2025-07-25 16:40:09 -10:00
J. Nick Koston
1d22bcac82 cleanup 2025-07-25 16:38:32 -10:00
J. Nick Koston
9dbcb2ea2e Merge branch 'integration' into memory_api 2025-07-25 16:17:31 -10:00
J. Nick Koston
cab19aed3d Merge branch 'ble_chars' into integration 2025-07-25 16:17:22 -10:00
J. Nick Koston
b22ff37e3d cleanup 2025-07-25 16:09:44 -10:00
J. Nick Koston
3396dfe52a cleanup 2025-07-25 16:08:22 -10:00
J. Nick Koston
accbc8fb0b cleanup 2025-07-25 16:07:46 -10:00
J. Nick Koston
5d8f38cce4 cleanup 2025-07-25 15:58:49 -10:00
J. Nick Koston
85a4f05d67 cleanup 2025-07-25 15:57:23 -10:00
J. Nick Koston
8a03e4c2cb cleanup 2025-07-25 15:56:00 -10:00
J. Nick Koston
e28c32af15 Merge branch 'integration' into memory_api 2025-07-25 15:37:20 -10:00
J. Nick Koston
4da9abe3fa Merge branch 'api_ha_services' into integration 2025-07-25 15:37:06 -10:00
J. Nick Koston
95b8321284 [api] Add conditional compilation for Home Assistant service subscriptions 2025-07-25 15:26:15 -10:00
J. Nick Koston
ce96c3c105 Merge branch 'integration' into memory_api 2025-07-25 14:48:21 -10:00
J. Nick Koston
3538cf936c Merge branch 'optional_api' into integration 2025-07-25 14:48:12 -10:00
J. Nick Koston
fd8c77c340 remove unneeded assertion 2025-07-25 14:48:00 -10:00
J. Nick Koston
6b49813084 Merge branch 'integration' into memory_api 2025-07-25 14:36:23 -10:00
J. Nick Koston
80407cf755 Merge branch 'missing_password_ifdefs' into integration 2025-07-25 14:36:01 -10:00
J. Nick Koston
ed379852fb cleanup 2025-07-25 14:35:47 -10:00
J. Nick Koston
662b5a452e Merge branch 'integration' into memory_api 2025-07-25 13:34:00 -10:00
J. Nick Koston
64d92fc92c Merge branch 'missing_password_ifdefs' into integration 2025-07-25 13:33:47 -10:00
J. Nick Koston
48128d965e make clang-tidy happy 2025-07-25 13:33:36 -10:00
J. Nick Koston
254c70a420 Merge branch 'integration' into memory_api 2025-07-25 13:28:11 -10:00
J. Nick Koston
5f9bd75954 Merge branch 'missing_password_ifdefs' into integration 2025-07-25 13:27:58 -10:00
J. Nick Koston
9ac38ff8d0 [api] Add missing USE_API_PASSWORD guards to reduce flash usage 2025-07-25 13:25:46 -10:00
J. Nick Koston
436ba20b7f Merge branch 'integration' into memory_api 2025-07-25 12:11:11 -10:00
J. Nick Koston
b89b1dfc29 Merge remote-tracking branch 'upstream/integration' into integration 2025-07-25 12:10:58 -10:00
J. Nick Koston
e7ea184709 preen 2025-07-25 12:10:41 -10:00
J. Nick Koston
b68f2b0ae4 Merge remote-tracking branch 'origin/memory_api' into memory_api 2025-07-25 12:08:26 -10:00
J. Nick Koston
4053a88c3a Merge branch 'integration' into memory_api 2025-07-25 12:08:13 -10:00
J. Nick Koston
9df3ad3567 Merge remote-tracking branch 'origin/homeassistant_states' into integration 2025-07-25 12:06:39 -10:00
J. Nick Koston
52df0a9412 Merge branch 'homeassistant_states' into integration 2025-07-25 12:05:59 -10:00
J. Nick Koston
9c4fc5d354 fixes 2025-07-25 11:59:13 -10:00
J. Nick Koston
1ac444a558 Merge remote-tracking branch 'origin/dev' into homeassistant_states 2025-07-25 11:55:29 -10:00
J. Nick Koston
90587583b4 [api] Add conditional compilation for Home Assistant state subscriptions 2025-07-25 11:55:07 -10:00
J. Nick Koston
0420bb3862 [api] Add conditional compilation for Home Assistant state subscriptions 2025-07-25 11:47:52 -10:00
J. Nick Koston
17dca4d761 Merge branch 'integration' into memory_api 2025-07-25 11:29:22 -10:00
J. Nick Koston
794db17e0f Merge remote-tracking branch 'upstream/optional_api' into integration 2025-07-25 11:29:10 -10:00
J. Nick Koston
91ec0f959e review comment 2025-07-25 11:27:01 -10:00
J. Nick Koston
066e4940e1 Merge branch 'integration' into memory_api 2025-07-25 11:02:11 -10:00
J. Nick Koston
88adbe7197 Merge branch 'optional_api' into integration 2025-07-25 11:02:01 -10:00
J. Nick Koston
3765058813 cover 2025-07-25 10:58:15 -10:00
J. Nick Koston
6cd2a80224 [api] Remove unnecessary string copies from optional access 2025-07-25 10:50:32 -10:00
Clyde Stubbs
b6e0188c42 [mipi_dsi] New display driver for P4 DSI (#9403)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: Samuel Sieb <samuel-github@sieb.net>
Co-authored-by: Adam Liddell <git@aliddell.com>
Co-authored-by: DT-art1 <81360462+DT-art1@users.noreply.github.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2025-07-26 07:36:52 +12:00
J. Nick Koston
b7ce8c116b [core] Centralize component setup logging to reduce flash usage (#9885) 2025-07-25 19:27:03 +00:00
Clyde Stubbs
2b87589562 [scheduler] Fix null pointer crash (#9893) 2025-07-25 09:12:33 -10:00
J. Nick Koston
bacb6a2c11 Merge branch 'integration' into memory_api 2025-07-25 08:42:45 -10:00
J. Nick Koston
f248309a7f Merge branch 'start_end_setup' into integration 2025-07-25 08:42:32 -10:00
J. Nick Koston
76a63e5d55 Merge branch 'dev' into integration 2025-07-25 08:42:21 -10:00
J. Nick Koston
f808c38f10 [ruff] Enable PERF rules and fix all violations (#9874) 2025-07-25 08:15:54 -10:00
J. Nick Koston
88ccde4ba1 [scheduler] Fix retry race condition on cancellation (#9788) 2025-07-25 08:14:15 -10:00
GilDev
9ac10d7276 [mqtt] Don’t log state topic subscription for buttons (#9887) 2025-07-25 23:33:29 +12:00
Jesse Hills
457689fa1d [CI] Fix auto-label workflow - codeowners & listFiles (#9890) 2025-07-25 21:40:42 +12:00
Jesse Hills
773a8b8fb7 [CI] Better mega-pr label handling (#9888) 2025-07-25 21:14:28 +12:00
Jesse Hills
c5c0237a4b Remove redundant platformio environments (#9886) 2025-07-25 03:16:23 -05:00
J. Nick Koston
65f7426ceb keep mcp2515 since it has error flags 2025-07-24 22:08:59 -10:00
J. Nick Koston
9cd657e8f5 Apply suggestions from code review 2025-07-24 22:05:39 -10:00
J. Nick Koston
431766d898 preen 2025-07-24 21:08:57 -10:00
J. Nick Koston
9d20b04512 preen 2025-07-24 21:07:43 -10:00
J. Nick Koston
6a9f1d9b2e preen 2025-07-24 21:05:38 -10:00
J. Nick Koston
c18724526a preen 2025-07-24 21:03:46 -10:00
J. Nick Koston
cce7eca2b7 preen 2025-07-24 21:03:32 -10:00
J. Nick Koston
0f9fa89ddc preen 2025-07-24 21:02:53 -10:00
J. Nick Koston
56d6c41a1d preen 2025-07-24 21:02:45 -10:00
J. Nick Koston
0f7cfe2c95 preen 2025-07-24 21:02:05 -10:00
J. Nick Koston
f33419a3aa preen 2025-07-24 21:01:47 -10:00
J. Nick Koston
3f33f04651 preen 2025-07-24 21:01:32 -10:00
J. Nick Koston
abcf62339d preen 2025-07-24 21:00:53 -10:00
J. Nick Koston
bd20d8b7b2 preen 2025-07-24 20:59:26 -10:00
J. Nick Koston
3843e4011f preen 2025-07-24 20:58:29 -10:00
J. Nick Koston
5b7ed4f419 preen 2025-07-24 20:57:51 -10:00
J. Nick Koston
a14809999a preen 2025-07-24 20:56:28 -10:00
J. Nick Koston
fd6204e804 preen 2025-07-24 20:54:00 -10:00
J. Nick Koston
a418e8df48 preen 2025-07-24 20:53:25 -10:00
J. Nick Koston
05d1c0300f preen 2025-07-24 20:49:36 -10:00
J. Nick Koston
d54724a475 preen 2025-07-24 20:48:51 -10:00
J. Nick Koston
0121dfc514 preen 2025-07-24 20:46:10 -10:00
Keith Burzinski
e79589efee [platformio.ini] Add GPS to nrf52-zephyr lib_deps (#9884) 2025-07-25 06:31:32 +00:00
J. Nick Koston
ffebd30033 [ruff] Enable SIM rules and fix code simplification violations (#9872) 2025-07-25 18:26:08 +12:00
Keith Burzinski
cb87f156d0 [platformio.ini] Move GPS to common lib_deps (#9883) 2025-07-24 18:10:03 -10:00
J. Nick Koston
1a1382de43 running setup 2025-07-24 16:29:54 -10:00
J. Nick Koston
3d0cea4ce3 revert 2025-07-24 16:28:31 -10:00
J. Nick Koston
25cd16409b running setup 2025-07-24 16:27:59 -10:00
dependabot[bot]
06eba96fdc Bump ruff from 0.12.4 to 0.12.5 (#9871)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-07-24 10:12:22 -10:00
RubenKelevra
25666811c6 Update esp32-camera library version to 2.1.0 2025-07-24 20:49:59 +02:00
@RubenKelevra
27119ef7ad rc522: fix buffer overflow in UID/buffer formatting helpers (#9375) 2025-07-25 00:43:44 +12:00
tomaszduda23
73f58dfe80 [sound_level] fix spelling mistake (#9843) 2025-07-24 23:26:21 +12:00
Keith Burzinski
729f20d765 [gps] Patches to build on IDF, other optimizations (#9728) 2025-07-24 23:23:42 +12:00
Clyde Stubbs
ba72298a63 [factory_reset] Allow factory reset by rapid power cycle (#9749) 2025-07-24 23:21:59 +12:00
Jesse Hills
ba1de5feff [CI] Refactor auto-label workflow: modular architecture, CODEOWNERS automation, and performance improvements (#9860) 2025-07-24 23:18:29 +12:00
J. Nick Koston
1344103086 [core] Revert #9851 and rename ESPHOME_CORES to ESPHOME_THREAD (#9862) 2025-07-24 11:04:00 +00:00
Keith Burzinski
5bff9bc8d9 [ld2450] Use `Deduplicator` for sensors (#9863) 2025-07-24 04:02:03 -05:00
J. Nick Koston
d839141386 Merge branch 'integration' into memory_api 2025-07-23 22:41:37 -10:00
J. Nick Koston
86919102c8 Merge remote-tracking branch 'upstream/dev' into integration 2025-07-23 22:41:22 -10:00
J. Nick Koston
43e2938724 Merge branch '20250724-ld2450-use-deduplicator' into integration 2025-07-23 22:41:09 -10:00
Clyde Stubbs
568e774116 [mipi] Keep models from different drivers separate (#9865) 2025-07-24 20:31:37 +12:00
J. Nick Koston
82c65040e2 Merge branch 'integration' into memory_api 2025-07-23 21:23:01 -10:00
J. Nick Koston
d291c1b207 Merge remote-tracking branch 'upstream/dev' into integration 2025-07-23 21:22:50 -10:00
J. Nick Koston
3a4f645a6c Merge remote-tracking branch 'upstream/dev' into memory_api 2025-07-23 21:22:29 -10:00
J. Nick Koston
c74f12be98 [api] Use C++17 nested namespace syntax (#9856) 2025-07-24 07:15:42 +00:00
Keith Burzinski
b977231431 clang-tidy 2025-07-24 02:13:52 -05:00
J. Nick Koston
557da2e7a7 Merge branch 'integration' into memory_api 2025-07-23 20:54:51 -10:00
J. Nick Koston
1458845672 Merge branch 'threading_model' into integration 2025-07-23 20:54:43 -10:00
J. Nick Koston
e4c8a6a0af [core] Revert #9851 and rename ESPHOME_CORES to ESPHOME_THREAD 2025-07-23 20:45:54 -10:00
Keith Burzinski
6609dce695 [ld2450] Use Deduplicator for sensors 2025-07-24 01:30:31 -05:00
J. Nick Koston
a5b4629662 Merge branch 'integration' into memory_api 2025-07-23 19:06:33 -10:00
J. Nick Koston
a4f952b851 Merge branch 'lwip_slowness' into integration 2025-07-23 19:06:24 -10:00
Keith Burzinski
705ea4ebaa [ld2410] Use `Deduplicator` for sensors (#9584)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-07-24 16:50:50 +12:00
J. Nick Koston
8146a0139f [esp32] Enable LWIP core locking on ESP-IDF to reduce socket operation overhead 2025-07-23 18:50:46 -10:00
J. Nick Koston
7ba2927d1d Merge branch 'integration' into memory_api 2025-07-23 18:05:17 -10:00
J. Nick Koston
2d66effbda Merge branch 'api_nest' into integration 2025-07-23 18:05:03 -10:00
J. Nick Koston
dcae628b25 [api] Use C++17 nested namespace syntax 2025-07-23 18:04:06 -10:00
J. Nick Koston
74c9580e4b Merge branch 'integration' into memory_api 2025-07-23 17:55:17 -10:00
J. Nick Koston
42862ec5b5 Merge remote-tracking branch 'upstream/dev' into integration 2025-07-23 17:54:53 -10:00
J. Nick Koston
ec2e0c50f1 [bluetooth_proxy] [esp32_ble_tracker] [esp32_ble] Use C++17 nested namespace syntax (#9825) 2025-07-24 15:23:45 +12:00
J. Nick Koston
544cf9b9c0 [core] Fix component state documentation and add state helper method (#9824) 2025-07-24 15:22:42 +12:00
J. Nick Koston
99850255f0 [api] Use emplace_back for TemplatableKeyValuePair construction in HomeAssistant services (#9804) 2025-07-24 15:21:35 +12:00
J. Nick Koston
4a27b34685 [api] Reduce code duplication in protobuf dump methods with helper functions (#9809) 2025-07-24 15:19:58 +12:00
J. Nick Koston
f863189f96 [api] Simplify generated authentication check code (#9806) 2025-07-24 15:18:01 +12:00
J. Nick Koston
04d9698681 [api] Replace magic numbers with MESSAGE_TYPE constants in protobuf switch cases (#9776) 2025-07-24 15:16:54 +12:00
J. Nick Koston
15ba2326ad [esp32] Fix threading model for single-core variants (S2, C3, C6, H2) (#9851) 2025-07-24 15:15:32 +12:00
Kevin Ahrendt
6398bb2fdf [i2s_audio] Speaker improvements: CPU core agnostic and more accurate timestamps (#9800)
Co-authored-by: NP v/d Spek <github_mail@lumensoft.nl>
2025-07-24 15:14:00 +12:00
TJ Horner
108e447072 [logger] remove unnecessary call to setTxTimeoutMs (#9854) 2025-07-24 14:51:47 +12:00
Brandon Harvey
cc187ef276 [ld2450] Set `accuracy_decimals=0` as default for "target" entities (#9842) 2025-07-24 14:29:39 +12:00
Keith Burzinski
a3e626757e [helpers] Add "unknown" value handling to `Deduplicator` (#9855) 2025-07-23 21:22:54 -05:00
Mayur Panchal
5cd7f156b9 Update post_build.py.script to Fix #7137 (#9578)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-07-24 01:34:39 +00:00
Clyde Stubbs
3960e2bae7 [mipi] Refactor constants and functions (#9853) 2025-07-24 13:27:05 +12:00
Clyde Stubbs
f9534fbd5d [interval] Fix startup behaviour (#9793) 2025-07-24 08:03:36 +10:00
Eric Hoffmann
0744abe098 fix: non-optional x/y target calculation for ld2450 (#9849) 2025-07-23 11:55:31 -10:00
Clyde Stubbs
49df68beb6 [gt911] i2c fixes (#9822) 2025-07-24 09:52:07 +12:00
Olivier ARCHER
e94cb03272 [modem] network component change (#9801)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-07-24 09:36:20 +12:00
J. Nick Koston
6ac1073469 [ci] Support C++17 nested namespace syntax in linter (#9826) 2025-07-23 23:32:35 +12:00
J. Nick Koston
378b687a82 [core] Restore COMPONENT_STATE_LOOP_DONE check in calculate_looping_components (#9832) 2025-07-23 23:31:30 +12:00
Jesse Hills
babaa1db3f [i2c] Use `i2c_master_probe` to scan i2c bus (#9831) 2025-07-23 23:31:13 +12:00
J. Nick Koston
f9c844be89 Merge branch 'integration' into memory_api 2025-07-22 23:52:21 -10:00
J. Nick Koston
dee58804e8 Merge branch 'loop_fix' into integration 2025-07-22 23:52:08 -10:00
J. Nick Koston
77c8363946 [core] Restore COMPONENT_STATE_LOOP_DONE check in calculate_looping_components 2025-07-22 23:49:33 -10:00
835 changed files with 18837 additions and 7854 deletions

View File

@@ -168,6 +168,8 @@ This document provides essential context for AI models interacting with this pro
* `platformio.ini`: Configures the PlatformIO build environments for different microcontrollers.
* `.pre-commit-config.yaml`: Configures the pre-commit hooks for linting and formatting.
* **CI/CD Pipeline:** Defined in `.github/workflows`.
* **Static Analysis & Development:**
* `esphome/core/defines.h`: A comprehensive header file containing all `#define` directives that can be added by components using `cg.add_define()` in Python. This file is used exclusively for development, static analysis tools, and CI testing - it is not used during runtime compilation. When developing components that add new defines, they must be added to this file to ensure proper IDE support and static analysis coverage. The file includes feature flags, build configurations, and platform-specific defines that help static analyzers understand the complete codebase without needing to compile for specific platforms.
## 6. Development & Testing Workflow

View File

@@ -1 +1 @@
7920671c938a5ea6a11ac4594204b5ec8f38d579c962bf1f185e8d5e3ad879be
6af8b429b94191fe8e239fcb3b73f7982d0266cb5b05ffbc81edaeac1bc8c273

View File

@@ -22,7 +22,7 @@ runs:
python-version: ${{ inputs.python-version }}
- name: Restore Python virtual environment
id: cache-venv
uses: actions/cache/restore@v4.2.3
uses: actions/cache/restore@v4.2.4
with:
path: venv
# yamllint disable-line rule:line-length

File diff suppressed because it is too large Load Diff

View File

@@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4.2.2
uses: actions/checkout@v5.0.0
- name: Set up Python
uses: actions/setup-python@v5.6.0
with:

View File

@@ -20,7 +20,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4.2.2
uses: actions/checkout@v5.0.0
- name: Set up Python
uses: actions/setup-python@v5.6.0

View File

@@ -43,7 +43,7 @@ jobs:
- "docker"
# - "lint"
steps:
- uses: actions/checkout@v4.2.2
- uses: actions/checkout@v5.0.0
- name: Set up Python
uses: actions/setup-python@v5.6.0
with:

View File

@@ -36,7 +36,7 @@ jobs:
cache-key: ${{ steps.cache-key.outputs.key }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
uses: actions/checkout@v5.0.0
- name: Generate cache-key
id: cache-key
run: echo key="${{ hashFiles('requirements.txt', 'requirements_test.txt', '.pre-commit-config.yaml') }}" >> $GITHUB_OUTPUT
@@ -47,7 +47,7 @@ jobs:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore Python virtual environment
id: cache-venv
uses: actions/cache@v4.2.3
uses: actions/cache@v4.2.4
with:
path: venv
# yamllint disable-line rule:line-length
@@ -70,7 +70,7 @@ jobs:
if: needs.determine-jobs.outputs.python-linters == 'true'
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
uses: actions/checkout@v5.0.0
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@@ -91,7 +91,7 @@ jobs:
- common
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
uses: actions/checkout@v5.0.0
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@@ -136,7 +136,7 @@ jobs:
- common
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
uses: actions/checkout@v5.0.0
- name: Restore Python
id: restore-python
uses: ./.github/actions/restore-python
@@ -161,7 +161,7 @@ jobs:
token: ${{ secrets.CODECOV_TOKEN }}
- name: Save Python virtual environment cache
if: github.ref == 'refs/heads/dev'
uses: actions/cache/save@v4.2.3
uses: actions/cache/save@v4.2.4
with:
path: venv
key: ${{ runner.os }}-${{ steps.restore-python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
@@ -179,7 +179,7 @@ jobs:
component-test-count: ${{ steps.determine.outputs.component-test-count }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
uses: actions/checkout@v5.0.0
with:
# Fetch enough history to find the merge base
fetch-depth: 2
@@ -214,7 +214,7 @@ jobs:
if: needs.determine-jobs.outputs.integration-tests == 'true'
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
uses: actions/checkout@v5.0.0
- name: Set up Python 3.13
id: python
uses: actions/setup-python@v5.6.0
@@ -222,7 +222,7 @@ jobs:
python-version: "3.13"
- name: Restore Python virtual environment
id: cache-venv
uses: actions/cache@v4.2.3
uses: actions/cache@v4.2.4
with:
path: venv
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
@@ -281,13 +281,13 @@ jobs:
pio_cache_key: tidyesp32-idf
- id: clang-tidy
name: Run script/clang-tidy for ZEPHYR
options: --environment nrf52-tidy --grep USE_ZEPHYR
options: --environment nrf52-tidy --grep USE_ZEPHYR --grep USE_NRF52
pio_cache_key: tidy-zephyr
ignore_errors: false
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
uses: actions/checkout@v5.0.0
with:
# Need history for HEAD~1 to work for checking changed files
fetch-depth: 2
@@ -300,14 +300,14 @@ jobs:
- name: Cache platformio
if: github.ref == 'refs/heads/dev'
uses: actions/cache@v4.2.3
uses: actions/cache@v4.2.4
with:
path: ~/.platformio
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
- name: Cache platformio
if: github.ref != 'refs/heads/dev'
uses: actions/cache/restore@v4.2.3
uses: actions/cache/restore@v4.2.4
with:
path: ~/.platformio
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
@@ -374,7 +374,7 @@ jobs:
sudo apt-get install libsdl2-dev
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
uses: actions/checkout@v5.0.0
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@@ -400,7 +400,7 @@ jobs:
matrix: ${{ steps.split.outputs.components }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
uses: actions/checkout@v5.0.0
- name: Split components into 20 groups
id: split
run: |
@@ -430,7 +430,7 @@ jobs:
sudo apt-get install libsdl2-dev
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
uses: actions/checkout@v5.0.0
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@@ -459,7 +459,7 @@ jobs:
if: github.event_name == 'pull_request' && github.base_ref != 'beta' && github.base_ref != 'release'
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
uses: actions/checkout@v5.0.0
- name: Restore Python
uses: ./.github/actions/restore-python
with:

View File

@@ -54,7 +54,7 @@ jobs:
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v5.0.0
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL

View File

@@ -1,24 +0,0 @@
name: Needs Docs
on:
pull_request:
types: [labeled, unlabeled]
jobs:
check:
name: Check
runs-on: ubuntu-latest
steps:
- name: Check for needs-docs label
uses: actions/github-script@v7.0.1
with:
script: |
const { data: labels } = await github.rest.issues.listLabelsOnIssue({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number
});
const needsDocs = labels.find(label => label.name === 'needs-docs');
if (needsDocs) {
core.setFailed('Pull request needs docs');
}

View File

@@ -20,7 +20,7 @@ jobs:
branch_build: ${{ steps.tag.outputs.branch_build }}
deploy_env: ${{ steps.tag.outputs.deploy_env }}
steps:
- uses: actions/checkout@v4.2.2
- uses: actions/checkout@v5.0.0
- name: Get tag
id: tag
# yamllint disable rule:line-length
@@ -60,7 +60,7 @@ jobs:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4.2.2
- uses: actions/checkout@v5.0.0
- name: Set up Python
uses: actions/setup-python@v5.6.0
with:
@@ -92,7 +92,7 @@ jobs:
os: "ubuntu-24.04-arm"
steps:
- uses: actions/checkout@v4.2.2
- uses: actions/checkout@v5.0.0
- name: Set up Python
uses: actions/setup-python@v5.6.0
with:
@@ -102,12 +102,12 @@ jobs:
uses: docker/setup-buildx-action@v3.11.1
- name: Log in to docker hub
uses: docker/login-action@v3.4.0
uses: docker/login-action@v3.5.0
with:
username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Log in to the GitHub container registry
uses: docker/login-action@v3.4.0
uses: docker/login-action@v3.5.0
with:
registry: ghcr.io
username: ${{ github.actor }}
@@ -168,10 +168,10 @@ jobs:
- ghcr
- dockerhub
steps:
- uses: actions/checkout@v4.2.2
- uses: actions/checkout@v5.0.0
- name: Download digests
uses: actions/download-artifact@v4.3.0
uses: actions/download-artifact@v5.0.0
with:
pattern: digests-*
path: /tmp/digests
@@ -182,13 +182,13 @@ jobs:
- name: Log in to docker hub
if: matrix.registry == 'dockerhub'
uses: docker/login-action@v3.4.0
uses: docker/login-action@v3.5.0
with:
username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Log in to the GitHub container registry
if: matrix.registry == 'ghcr'
uses: docker/login-action@v3.4.0
uses: docker/login-action@v3.5.0
with:
registry: ghcr.io
username: ${{ github.actor }}

View File

@@ -0,0 +1,30 @@
name: Status check labels
on:
pull_request:
types: [labeled, unlabeled]
jobs:
check:
name: Check ${{ matrix.label }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
label:
- needs-docs
- merge-after-release
steps:
- name: Check for ${{ matrix.label }} label
uses: actions/github-script@v7.0.1
with:
script: |
const { data: labels } = await github.rest.issues.listLabelsOnIssue({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number
});
const hasLabel = labels.find(label => label.name === '${{ matrix.label }}');
if (hasLabel) {
core.setFailed('Pull request cannot be merged, it is labeled as ${{ matrix.label }}');
}

View File

@@ -13,10 +13,10 @@ jobs:
if: github.repository == 'esphome/esphome'
steps:
- name: Checkout
uses: actions/checkout@v4.2.2
uses: actions/checkout@v5.0.0
- name: Checkout Home Assistant
uses: actions/checkout@v4.2.2
uses: actions/checkout@v5.0.0
with:
repository: home-assistant/core
path: lib/home-assistant

View File

@@ -11,7 +11,7 @@ ci:
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.12.4
rev: v0.12.9
hooks:
# Run the linter.
- id: ruff

View File

@@ -40,11 +40,11 @@ esphome/components/analog_threshold/* @ianchi
esphome/components/animation/* @syndlex
esphome/components/anova/* @buxtronix
esphome/components/apds9306/* @aodrenah
esphome/components/api/* @OttoWinter
esphome/components/api/* @esphome/core
esphome/components/as5600/* @ammmze
esphome/components/as5600/sensor/* @ammmze
esphome/components/as7341/* @mrgnr
esphome/components/async_tcp/* @OttoWinter
esphome/components/async_tcp/* @esphome/core
esphome/components/at581x/* @X-Ryl669
esphome/components/atc_mithermometer/* @ahpohl
esphome/components/atm90e26/* @danieltwagner
@@ -69,7 +69,7 @@ esphome/components/bl0939/* @ziceva
esphome/components/bl0940/* @tobias-
esphome/components/bl0942/* @dbuezas @dwmw2
esphome/components/ble_client/* @buxtronix @clydebarrow
esphome/components/bluetooth_proxy/* @jesserockz
esphome/components/bluetooth_proxy/* @bdraco @jesserockz
esphome/components/bme280_base/* @esphome/core
esphome/components/bme280_spi/* @apbodrov
esphome/components/bme680_bsec/* @trvrnrth
@@ -91,7 +91,7 @@ esphome/components/bytebuffer/* @clydebarrow
esphome/components/camera/* @DT-art1 @bdraco
esphome/components/canbus/* @danielschramm @mvturnho
esphome/components/cap1188/* @mreditor97
esphome/components/captive_portal/* @OttoWinter
esphome/components/captive_portal/* @esphome/core
esphome/components/ccs811/* @habbie
esphome/components/cd74hc4067/* @asoehlke
esphome/components/ch422g/* @clydebarrow @jesterret
@@ -118,7 +118,7 @@ esphome/components/dallas_temp/* @ssieb
esphome/components/daly_bms/* @s1lvi0
esphome/components/dashboard_import/* @esphome/core
esphome/components/datetime/* @jesserockz @rfdarter
esphome/components/debug/* @OttoWinter
esphome/components/debug/* @esphome/core
esphome/components/delonghi/* @grob6000
esphome/components/dfplayer/* @glmnet
esphome/components/dfrobot_sen0395/* @niklasweber
@@ -144,9 +144,10 @@ esphome/components/es8156/* @kbx81
esphome/components/es8311/* @kahrendt @kroimon
esphome/components/es8388/* @P4uLT
esphome/components/esp32/* @esphome/core
esphome/components/esp32_ble/* @Rapsssito @jesserockz
esphome/components/esp32_ble_client/* @jesserockz
esphome/components/esp32_ble/* @Rapsssito @bdraco @jesserockz
esphome/components/esp32_ble_client/* @bdraco @jesserockz
esphome/components/esp32_ble_server/* @Rapsssito @clydebarrow @jesserockz
esphome/components/esp32_ble_tracker/* @bdraco
esphome/components/esp32_camera_web_server/* @ayufan
esphome/components/esp32_can/* @Sympatron
esphome/components/esp32_hosted/* @swoboda1337
@@ -155,6 +156,7 @@ esphome/components/esp32_rmt/* @jesserockz
esphome/components/esp32_rmt_led_strip/* @jesserockz
esphome/components/esp8266/* @esphome/core
esphome/components/esp_ldo/* @clydebarrow
esphome/components/espnow/* @jesserockz
esphome/components/ethernet_info/* @gtjadsonsantos
esphome/components/event/* @nohat
esphome/components/event_emitter/* @Rapsssito
@@ -236,7 +238,7 @@ esphome/components/integration/* @OttoWinter
esphome/components/internal_temperature/* @Mat931
esphome/components/interval/* @esphome/core
esphome/components/jsn_sr04t/* @Mafus1
esphome/components/json/* @OttoWinter
esphome/components/json/* @esphome/core
esphome/components/kamstrup_kmp/* @cfeenstra1024
esphome/components/key_collector/* @ssieb
esphome/components/key_provider/* @ssieb
@@ -244,8 +246,10 @@ esphome/components/kuntze/* @ssieb
esphome/components/lc709203f/* @ilikecake
esphome/components/lcd_menu/* @numo68
esphome/components/ld2410/* @regevbr @sebcaps
esphome/components/ld2412/* @Rihan9
esphome/components/ld2420/* @descipher
esphome/components/ld2450/* @hareeshmu
esphome/components/ld24xx/* @kbx81
esphome/components/ledc/* @OttoWinter
esphome/components/libretiny/* @kuba2k2
esphome/components/libretiny_pwm/* @kuba2k2
@@ -293,6 +297,7 @@ esphome/components/microphone/* @jesserockz @kahrendt
esphome/components/mics_4514/* @jesserockz
esphome/components/midea/* @dudanov
esphome/components/midea_ir/* @dudanov
esphome/components/mipi_dsi/* @clydebarrow
esphome/components/mipi_spi/* @clydebarrow
esphome/components/mitsubishi/* @RubyBailey
esphome/components/mixer/speaker/* @kahrendt
@@ -464,13 +469,13 @@ esphome/components/template/event/* @nohat
esphome/components/template/fan/* @ssieb
esphome/components/text/* @mauritskorse
esphome/components/thermostat/* @kbx81
esphome/components/time/* @OttoWinter
esphome/components/time/* @esphome/core
esphome/components/tlc5947/* @rnauber
esphome/components/tlc5971/* @IJIJI
esphome/components/tm1621/* @Philippe12
esphome/components/tm1637/* @glmnet
esphome/components/tm1638/* @skykingjwc
esphome/components/tm1651/* @freekode
esphome/components/tm1651/* @mrtoy-me
esphome/components/tmp102/* @timsavage
esphome/components/tmp1075/* @sybrenstuvel
esphome/components/tmp117/* @Azimath
@@ -508,7 +513,7 @@ esphome/components/wake_on_lan/* @clydebarrow @willwill2will54
esphome/components/watchdog/* @oarcher
esphome/components/waveshare_epaper/* @clydebarrow
esphome/components/web_server/ota/* @esphome/core
esphome/components/web_server_base/* @OttoWinter
esphome/components/web_server_base/* @esphome/core
esphome/components/web_server_idf/* @dentra
esphome/components/weikai/* @DrCoolZic
esphome/components/weikai_i2c/* @DrCoolZic

View File

@@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome
# could be handy for archiving the generated documentation or if some version
# control system is used.
PROJECT_NUMBER = 2025.8.0-dev
PROJECT_NUMBER = 2025.9.0-dev
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a

View File

@@ -90,7 +90,7 @@ def main():
def run_command(*cmd, ignore_error: bool = False):
print(f"$ {shlex.join(list(cmd))}")
if not args.dry_run:
rc = subprocess.call(list(cmd))
rc = subprocess.call(list(cmd), close_fds=False)
if rc != 0 and not ignore_error:
print("Command failed")
sys.exit(1)

View File

@@ -2,12 +2,14 @@
import argparse
from datetime import datetime
import functools
import getpass
import importlib
import logging
import os
import re
import sys
import time
from typing import Protocol
import argcomplete
@@ -43,6 +45,7 @@ from esphome.const import (
from esphome.core import CORE, EsphomeError, coroutine
from esphome.helpers import get_bool_env, indent, is_ip_address
from esphome.log import AnsiFore, color, setup_log
from esphome.types import ConfigType
from esphome.util import (
get_serial_ports,
list_yaml_files,
@@ -54,6 +57,23 @@ from esphome.util import (
_LOGGER = logging.getLogger(__name__)
class ArgsProtocol(Protocol):
device: list[str] | None
reset: bool
username: str | None
password: str | None
client_id: str | None
topic: str | None
file: str | None
no_logs: bool
only_generate: bool
show_secrets: bool
dashboard: bool
configuration: str
name: str
upload_speed: str | None
def choose_prompt(options, purpose: str = None):
if not options:
raise EsphomeError(
@@ -87,30 +107,54 @@ def choose_prompt(options, purpose: str = None):
def choose_upload_log_host(
default, check_default, show_ota, show_mqtt, show_api, purpose: str = None
):
options = []
for port in get_serial_ports():
options.append((f"{port.path} ({port.description})", port.path))
if default == "SERIAL":
return choose_prompt(options, purpose=purpose)
default: list[str] | str | None,
check_default: str | None,
show_ota: bool,
show_mqtt: bool,
show_api: bool,
purpose: str | None = None,
) -> list[str]:
# Convert to list for uniform handling
defaults = [default] if isinstance(default, str) else default or []
# If devices specified, resolve them
if defaults:
resolved: list[str] = []
for device in defaults:
if device == "SERIAL":
serial_ports = get_serial_ports()
if not serial_ports:
_LOGGER.warning("No serial ports found, skipping SERIAL device")
continue
options = [
(f"{port.path} ({port.description})", port.path)
for port in serial_ports
]
resolved.append(choose_prompt(options, purpose=purpose))
elif device == "OTA":
if (show_ota and "ota" in CORE.config) or (
show_api and "api" in CORE.config
):
resolved.append(CORE.address)
elif show_mqtt and has_mqtt_logging():
resolved.append("MQTT")
else:
resolved.append(device)
return resolved
# No devices specified, show interactive chooser
options = [
(f"{port.path} ({port.description})", port.path) for port in get_serial_ports()
]
if (show_ota and "ota" in CORE.config) or (show_api and "api" in CORE.config):
options.append((f"Over The Air ({CORE.address})", CORE.address))
if default == "OTA":
return CORE.address
if (
show_mqtt
and (mqtt_config := CORE.config.get(CONF_MQTT))
and mqtt_logging_enabled(mqtt_config)
):
if show_mqtt and has_mqtt_logging():
mqtt_config = CORE.config[CONF_MQTT]
options.append((f"MQTT ({mqtt_config[CONF_BROKER]})", "MQTT"))
if default == "OTA":
return "MQTT"
if default is not None:
return default
if check_default is not None and check_default in [opt[1] for opt in options]:
return check_default
return choose_prompt(options, purpose=purpose)
return [check_default]
return [choose_prompt(options, purpose=purpose)]
def mqtt_logging_enabled(mqtt_config):
@@ -119,12 +163,17 @@ def mqtt_logging_enabled(mqtt_config):
return False
if CONF_TOPIC not in log_topic:
return False
if log_topic.get(CONF_LEVEL, None) == "NONE":
return False
return True
return log_topic.get(CONF_LEVEL, None) != "NONE"
def get_port_type(port):
def has_mqtt_logging() -> bool:
"""Check if MQTT logging is available."""
return (mqtt_config := CORE.config.get(CONF_MQTT)) and mqtt_logging_enabled(
mqtt_config
)
def get_port_type(port: str) -> str:
if port.startswith("/") or port.startswith("COM"):
return "SERIAL"
if port == "MQTT":
@@ -132,7 +181,7 @@ def get_port_type(port):
return "NETWORK"
def run_miniterm(config, port, args):
def run_miniterm(config: ConfigType, port: str, args) -> int:
from aioesphomeapi import LogParser
import serial
@@ -209,7 +258,7 @@ def wrap_to_code(name, comp):
return wrapped
def write_cpp(config):
def write_cpp(config: ConfigType) -> int:
if not get_bool_env(ENV_NOGITIGNORE):
writer.write_gitignore()
@@ -217,7 +266,7 @@ def write_cpp(config):
return write_cpp_file()
def generate_cpp_contents(config):
def generate_cpp_contents(config: ConfigType) -> None:
_LOGGER.info("Generating C++ source...")
for name, component, conf in iter_component_configs(CORE.config):
@@ -228,7 +277,7 @@ def generate_cpp_contents(config):
CORE.flush_tasks()
def write_cpp_file():
def write_cpp_file() -> int:
code_s = indent(CORE.cpp_main_section)
writer.write_cpp(code_s)
@@ -239,7 +288,7 @@ def write_cpp_file():
return 0
def compile_program(args, config):
def compile_program(args: ArgsProtocol, config: ConfigType) -> int:
from esphome import platformio_api
_LOGGER.info("Compiling app...")
@@ -250,7 +299,9 @@ def compile_program(args, config):
return 0 if idedata is not None else 1
def upload_using_esptool(config, port, file, speed):
def upload_using_esptool(
config: ConfigType, port: str, file: str, speed: int
) -> str | int:
from esphome import platformio_api
first_baudrate = speed or config[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS].get(
@@ -278,20 +329,20 @@ def upload_using_esptool(config, port, file, speed):
def run_esptool(baud_rate):
cmd = [
"esptool.py",
"esptool",
"--before",
"default_reset",
"default-reset",
"--after",
"hard_reset",
"hard-reset",
"--baud",
str(baud_rate),
"--port",
port,
"--chip",
mcu,
"write_flash",
"write-flash",
"-z",
"--flash_size",
"--flash-size",
"detect",
]
for img in flash_images:
@@ -315,7 +366,7 @@ def upload_using_esptool(config, port, file, speed):
return run_esptool(115200)
def upload_using_platformio(config, port):
def upload_using_platformio(config: ConfigType, port: str):
from esphome import platformio_api
upload_args = ["-t", "upload", "-t", "nobuild"]
@@ -324,7 +375,7 @@ def upload_using_platformio(config, port):
return platformio_api.run_platformio_cli_run(config, CORE.verbose, *upload_args)
def check_permissions(port):
def check_permissions(port: str):
if os.name == "posix" and get_port_type(port) == "SERIAL":
# Check if we can open selected serial port
if not os.access(port, os.F_OK):
@@ -337,12 +388,12 @@ def check_permissions(port):
raise EsphomeError(
"You do not have read or write permission on the selected serial port. "
"To resolve this issue, you can add your user to the dialout group "
f"by running the following command: sudo usermod -a -G dialout {os.getlogin()}. "
f"by running the following command: sudo usermod -a -G dialout {getpass.getuser()}. "
"You will need to log out & back in or reboot to activate the new group access."
)
def upload_program(config, args, host):
def upload_program(config: ConfigType, args: ArgsProtocol, host: str) -> int | str:
try:
module = importlib.import_module("esphome.components." + CORE.target_platform)
if getattr(module, "upload_program")(config, args, host):
@@ -357,7 +408,7 @@ def upload_program(config, args, host):
return upload_using_esptool(config, host, file, args.upload_speed)
if CORE.target_platform in (PLATFORM_RP2040):
return upload_using_platformio(config, args.device)
return upload_using_platformio(config, host)
if CORE.is_libretiny:
return upload_using_platformio(config, host)
@@ -380,9 +431,12 @@ def upload_program(config, args, host):
remote_port = int(ota_conf[CONF_PORT])
password = ota_conf.get(CONF_PASSWORD, "")
# Check if we should use MQTT for address resolution
# This happens when no device was specified, or the current host is "MQTT"/"OTA"
devices: list[str] = args.device or []
if (
CONF_MQTT in config # pylint: disable=too-many-boolean-expressions
and (not args.device or args.device in ("MQTT", "OTA"))
and (not devices or host in ("MQTT", "OTA"))
and (
((config[CONF_MDNS][CONF_DISABLED]) and not is_ip_address(CORE.address))
or get_port_type(host) == "MQTT"
@@ -400,24 +454,29 @@ def upload_program(config, args, host):
return espota2.run_ota(host, remote_port, password, CORE.firmware_bin)
def show_logs(config, args, port):
def show_logs(config: ConfigType, args: ArgsProtocol, devices: list[str]) -> int | None:
if "logger" not in config:
raise EsphomeError("Logger is not configured!")
port = devices[0]
if get_port_type(port) == "SERIAL":
check_permissions(port)
return run_miniterm(config, port, args)
if get_port_type(port) == "NETWORK" and "api" in config:
addresses_to_use = devices
if config[CONF_MDNS][CONF_DISABLED] and CONF_MQTT in config:
from esphome import mqtt
port = mqtt.get_esphome_device_ip(
mqtt_address = mqtt.get_esphome_device_ip(
config, args.username, args.password, args.client_id
)[0]
addresses_to_use = [mqtt_address]
from esphome.components.api.client import run_logs
return run_logs(config, port)
if get_port_type(port) == "MQTT" and "mqtt" in config:
return run_logs(config, addresses_to_use)
if get_port_type(port) in ("NETWORK", "MQTT") and "mqtt" in config:
from esphome import mqtt
return mqtt.show_logs(
@@ -427,7 +486,7 @@ def show_logs(config, args, port):
raise EsphomeError("No remote or local logging method configured (api/mqtt/logger)")
def clean_mqtt(config, args):
def clean_mqtt(config: ConfigType, args: ArgsProtocol) -> int | None:
from esphome import mqtt
return mqtt.clear_topic(
@@ -435,13 +494,13 @@ def clean_mqtt(config, args):
)
def command_wizard(args):
def command_wizard(args: ArgsProtocol) -> int | None:
from esphome import wizard
return wizard.wizard(args.configuration)
def command_config(args, config):
def command_config(args: ArgsProtocol, config: ConfigType) -> int | None:
if not CORE.verbose:
config = strip_default_ids(config)
output = yaml_util.dump(config, args.show_secrets)
@@ -456,7 +515,7 @@ def command_config(args, config):
return 0
def command_vscode(args):
def command_vscode(args: ArgsProtocol) -> int | None:
from esphome import vscode
logging.disable(logging.INFO)
@@ -464,7 +523,7 @@ def command_vscode(args):
vscode.read_config(args)
def command_compile(args, config):
def command_compile(args: ArgsProtocol, config: ConfigType) -> int | None:
# Set memory analysis options in config
if args.analyze_memory:
config.setdefault(CONF_ESPHOME, {})["analyze_memory"] = True
@@ -485,8 +544,9 @@ def command_compile(args, config):
return 0
def command_upload(args, config):
port = choose_upload_log_host(
def command_upload(args: ArgsProtocol, config: ConfigType) -> int | None:
# Get devices, resolving special identifiers like OTA
devices = choose_upload_log_host(
default=args.device,
check_default=None,
show_ota=True,
@@ -494,14 +554,22 @@ def command_upload(args, config):
show_api=False,
purpose="uploading",
)
exit_code = upload_program(config, args, port)
if exit_code != 0:
return exit_code
_LOGGER.info("Successfully uploaded program.")
return 0
# Try each device until one succeeds
exit_code = 1
for device in devices:
_LOGGER.info("Uploading to %s", device)
exit_code = upload_program(config, args, device)
if exit_code == 0:
_LOGGER.info("Successfully uploaded program.")
return 0
if len(devices) > 1:
_LOGGER.warning("Failed to upload to %s", device)
return exit_code
def command_discover(args, config):
def command_discover(args: ArgsProtocol, config: ConfigType) -> int | None:
if "mqtt" in config:
from esphome import mqtt
@@ -510,8 +578,9 @@ def command_discover(args, config):
raise EsphomeError("No discover method configured (mqtt)")
def command_logs(args, config):
port = choose_upload_log_host(
def command_logs(args: ArgsProtocol, config: ConfigType) -> int | None:
# Get devices, resolving special identifiers like OTA
devices = choose_upload_log_host(
default=args.device,
check_default=None,
show_ota=False,
@@ -519,10 +588,10 @@ def command_logs(args, config):
show_api=True,
purpose="logging",
)
return show_logs(config, args, port)
return show_logs(config, args, devices)
def command_run(args, config):
def command_run(args: ArgsProtocol, config: ConfigType) -> int | None:
exit_code = write_cpp(config)
if exit_code != 0:
return exit_code
@@ -539,7 +608,8 @@ def command_run(args, config):
program_path = idedata.raw["prog_path"]
return run_external_process(program_path)
port = choose_upload_log_host(
# Get devices, resolving special identifiers like OTA
devices = choose_upload_log_host(
default=args.device,
check_default=None,
show_ota=True,
@@ -547,39 +617,53 @@ def command_run(args, config):
show_api=True,
purpose="uploading",
)
exit_code = upload_program(config, args, port)
if exit_code != 0:
# Try each device for upload until one succeeds
successful_device: str | None = None
for device in devices:
_LOGGER.info("Uploading to %s", device)
exit_code = upload_program(config, args, device)
if exit_code == 0:
_LOGGER.info("Successfully uploaded program.")
successful_device = device
break
if len(devices) > 1:
_LOGGER.warning("Failed to upload to %s", device)
if successful_device is None:
return exit_code
_LOGGER.info("Successfully uploaded program.")
if args.no_logs:
return 0
port = choose_upload_log_host(
default=args.device,
check_default=port,
# For logs, prefer the device we successfully uploaded to
devices = choose_upload_log_host(
default=successful_device,
check_default=successful_device,
show_ota=False,
show_mqtt=True,
show_api=True,
purpose="logging",
)
return show_logs(config, args, port)
return show_logs(config, args, devices)
def command_clean_mqtt(args, config):
def command_clean_mqtt(args: ArgsProtocol, config: ConfigType) -> int | None:
return clean_mqtt(config, args)
def command_mqtt_fingerprint(args, config):
def command_mqtt_fingerprint(args: ArgsProtocol, config: ConfigType) -> int | None:
from esphome import mqtt
return mqtt.get_fingerprint(config)
def command_version(args):
def command_version(args: ArgsProtocol) -> int | None:
safe_print(f"Version: {const.__version__}")
return 0
def command_clean(args, config):
def command_clean(args: ArgsProtocol, config: ConfigType) -> int | None:
try:
writer.clean_build()
except OSError as err:
@@ -589,13 +673,13 @@ def command_clean(args, config):
return 0
def command_dashboard(args):
def command_dashboard(args: ArgsProtocol) -> int | None:
from esphome.dashboard import dashboard
return dashboard.start_dashboard(args)
def command_update_all(args):
def command_update_all(args: ArgsProtocol) -> int | None:
import click
success = {}
@@ -642,7 +726,7 @@ def command_update_all(args):
return failed
def command_idedata(args, config):
def command_idedata(args: ArgsProtocol, config: ConfigType) -> int:
import json
from esphome import platformio_api
@@ -658,7 +742,7 @@ def command_idedata(args, config):
return 0
def command_rename(args, config):
def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
for c in args.name:
if c not in ALLOWED_NAME_CHARS:
print(
@@ -775,6 +859,12 @@ POST_CONFIG_ACTIONS = {
"discover": command_discover,
}
SIMPLE_CONFIG_ACTIONS = [
"clean",
"clean-mqtt",
"config",
]
def parse_args(argv):
options_parser = argparse.ArgumentParser(add_help=False)
@@ -873,7 +963,8 @@ def parse_args(argv):
)
parser_upload.add_argument(
"--device",
help="Manually specify the serial port/address to use, for example /dev/ttyUSB0.",
action="append",
help="Manually specify the serial port/address to use, for example /dev/ttyUSB0. Can be specified multiple times for fallback addresses.",
)
parser_upload.add_argument(
"--upload_speed",
@@ -895,7 +986,8 @@ def parse_args(argv):
)
parser_logs.add_argument(
"--device",
help="Manually specify the serial port/address to use, for example /dev/ttyUSB0.",
action="append",
help="Manually specify the serial port/address to use, for example /dev/ttyUSB0. Can be specified multiple times for fallback addresses.",
)
parser_logs.add_argument(
"--reset",
@@ -924,7 +1016,8 @@ def parse_args(argv):
)
parser_run.add_argument(
"--device",
help="Manually specify the serial port/address to use, for example /dev/ttyUSB0.",
action="append",
help="Manually specify the serial port/address to use, for example /dev/ttyUSB0. Can be specified multiple times for fallback addresses.",
)
parser_run.add_argument(
"--upload_speed",
@@ -1051,6 +1144,13 @@ def parse_args(argv):
arguments = argv[1:]
argcomplete.autocomplete(parser)
if len(arguments) > 0 and arguments[0] in SIMPLE_CONFIG_ACTIONS:
args, unknown_args = parser.parse_known_args(arguments)
if unknown_args:
_LOGGER.warning("Ignored unrecognized arguments: %s", unknown_args)
return args
return parser.parse_args(arguments)

View File

@@ -1161,11 +1161,10 @@ class MemoryAnalyzer:
if component_name in ESPHOME_COMPONENTS:
return f"[esphome]{component_name}"
# Check if this is a known external component from the config
elif component_name in self.external_components:
if component_name in self.external_components:
return f"[external]{component_name}"
else:
# Everything else in esphome:: namespace is core
return "[esphome]core"
# Everything else in esphome:: namespace is core
return "[esphome]core"
# Check for esphome core namespace (no component namespace)
if "esphome::" in demangled:
@@ -1188,8 +1187,7 @@ class MemoryAnalyzer:
if "spi_" in symbol_name or "SPI" in symbol_name:
if "spi_flash" in symbol_name:
return "spi_flash"
else:
return "spi_driver"
return "spi_driver"
# libc special printf variants
if symbol_name.startswith("_") and symbol_name[1:].replace("_r", "").replace(
@@ -1391,7 +1389,7 @@ class MemoryAnalyzer:
# Top consumers
lines.append("")
lines.append("Top Flash Consumers:")
for i, (name, mem) in enumerate(components[:10]):
for i, (name, mem) in enumerate(components[:25]):
if mem.flash_total > 0:
percentage = (
(mem.flash_total / total_flash * 100) if total_flash > 0 else 0
@@ -1403,7 +1401,7 @@ class MemoryAnalyzer:
lines.append("")
lines.append("Top RAM Consumers:")
ram_components = sorted(components, key=lambda x: x[1].ram_total, reverse=True)
for i, (name, mem) in enumerate(ram_components[:10]):
for i, (name, mem) in enumerate(ram_components[:25]):
if mem.ram_total > 0:
percentage = (mem.ram_total / total_ram * 100) if total_ram > 0 else 0
lines.append(
@@ -1474,7 +1472,7 @@ class MemoryAnalyzer:
self._esphome_core_symbols, key=lambda x: x[2], reverse=True
)
for i, (symbol, demangled, size) in enumerate(sorted_core_symbols[:10]):
for i, (symbol, demangled, size) in enumerate(sorted_core_symbols[:15]):
lines.append(f"{i + 1}. {demangled} ({size:,} B)")
lines.append("=" * table_width)
@@ -1487,7 +1485,7 @@ class MemoryAnalyzer:
]
top_esphome_components = sorted(
esphome_components, key=lambda x: x[1].flash_total, reverse=True
)[:5]
)[:25]
# Check if API component exists and ensure it's included
api_component = None

View File

@@ -391,8 +391,7 @@ async def build_action(full_config, template_arg, args):
)
action_id = full_config[CONF_TYPE_ID]
builder = registry_entry.coroutine_fun
ret = await builder(config, action_id, template_arg, args)
return ret
return await builder(config, action_id, template_arg, args)
async def build_action_list(config, templ, arg_type):
@@ -409,8 +408,7 @@ async def build_condition(full_config, template_arg, args):
)
action_id = full_config[CONF_TYPE_ID]
builder = registry_entry.coroutine_fun
ret = await builder(config, action_id, template_arg, args)
return ret
return await builder(config, action_id, template_arg, args)
async def build_condition_list(config, templ, args):

View File

@@ -7,7 +7,6 @@ namespace a4988 {
static const char *const TAG = "a4988.stepper";
void A4988::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
if (this->sleep_pin_ != nullptr) {
this->sleep_pin_->setup();
this->sleep_pin_->digital_write(false);

View File

@@ -7,8 +7,6 @@ namespace absolute_humidity {
static const char *const TAG = "absolute_humidity.sensor";
void AbsoluteHumidityComponent::setup() {
ESP_LOGCONFIG(TAG, "Running setup for '%s'", this->get_name().c_str());
ESP_LOGD(TAG, " Added callback for temperature '%s'", this->temperature_sensor_->get_name().c_str());
this->temperature_sensor_->add_on_state_callback([this](float state) { this->temperature_callback_(state); });
if (this->temperature_sensor_->has_state()) {

View File

@@ -5,7 +5,7 @@ from esphome.const import (
CONF_EQUATION,
CONF_HUMIDITY,
CONF_TEMPERATURE,
ICON_WATER,
DEVICE_CLASS_ABSOLUTE_HUMIDITY,
STATE_CLASS_MEASUREMENT,
UNIT_GRAMS_PER_CUBIC_METER,
)
@@ -27,8 +27,8 @@ EQUATION = {
CONFIG_SCHEMA = (
sensor.sensor_schema(
unit_of_measurement=UNIT_GRAMS_PER_CUBIC_METER,
icon=ICON_WATER,
accuracy_decimals=2,
device_class=DEVICE_CLASS_ABSOLUTE_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT,
)
.extend(

View File

@@ -1,6 +1,6 @@
from esphome import pins
import esphome.codegen as cg
from esphome.components.esp32 import get_esp32_variant
from esphome.components.esp32 import VARIANT_ESP32P4, get_esp32_variant
from esphome.components.esp32.const import (
VARIANT_ESP32,
VARIANT_ESP32C2,
@@ -140,6 +140,16 @@ ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
9: adc_channel_t.ADC_CHANNEL_8,
10: adc_channel_t.ADC_CHANNEL_9,
},
VARIANT_ESP32P4: {
16: adc_channel_t.ADC_CHANNEL_0,
17: adc_channel_t.ADC_CHANNEL_1,
18: adc_channel_t.ADC_CHANNEL_2,
19: adc_channel_t.ADC_CHANNEL_3,
20: adc_channel_t.ADC_CHANNEL_4,
21: adc_channel_t.ADC_CHANNEL_5,
22: adc_channel_t.ADC_CHANNEL_6,
23: adc_channel_t.ADC_CHANNEL_7,
},
}
# pin to adc2 channel mapping
@@ -198,6 +208,14 @@ ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = {
19: adc_channel_t.ADC_CHANNEL_8,
20: adc_channel_t.ADC_CHANNEL_9,
},
VARIANT_ESP32P4: {
49: adc_channel_t.ADC_CHANNEL_0,
50: adc_channel_t.ADC_CHANNEL_1,
51: adc_channel_t.ADC_CHANNEL_2,
52: adc_channel_t.ADC_CHANNEL_3,
53: adc_channel_t.ADC_CHANNEL_4,
54: adc_channel_t.ADC_CHANNEL_5,
},
}
@@ -249,6 +267,11 @@ def validate_adc_pin(value):
{CONF_ANALOG: True, CONF_INPUT: True}, internal=True
)(value)
if CORE.is_nrf52:
return pins.gpio_pin_schema(
{CONF_ANALOG: True, CONF_INPUT: True}, internal=True
)(value)
raise NotImplementedError
@@ -265,5 +288,6 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform(
PlatformFramework.RTL87XX_ARDUINO,
PlatformFramework.LN882X_ARDUINO,
},
"adc_sensor_zephyr.cpp": {PlatformFramework.NRF52_ZEPHYR},
}
)

View File

@@ -13,6 +13,10 @@
#include "hal/adc_types.h" // This defines ADC_CHANNEL_MAX
#endif // USE_ESP32
#ifdef USE_ZEPHYR
#include <zephyr/drivers/adc.h>
#endif
namespace esphome {
namespace adc {
@@ -38,15 +42,15 @@ enum class SamplingMode : uint8_t {
const LogString *sampling_mode_to_str(SamplingMode mode);
class Aggregator {
template<typename T> class Aggregator {
public:
Aggregator(SamplingMode mode);
void add_sample(uint32_t value);
uint32_t aggregate();
void add_sample(T value);
T aggregate();
protected:
uint32_t aggr_{0};
uint32_t samples_{0};
T aggr_{0};
uint8_t samples_{0};
SamplingMode mode_{SamplingMode::AVG};
};
@@ -69,6 +73,11 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
/// @return A float representing the setup priority.
float get_setup_priority() const override;
#ifdef USE_ZEPHYR
/// Set the ADC channel to be used by the ADC sensor.
/// @param channel Pointer to an adc_dt_spec structure representing the ADC channel.
void set_adc_channel(const adc_dt_spec *channel) { this->channel_ = channel; }
#endif
/// Set the GPIO pin to be used by the ADC sensor.
/// @param pin Pointer to an InternalGPIOPin representing the ADC input pin.
void set_pin(InternalGPIOPin *pin) { this->pin_ = pin; }
@@ -136,8 +145,8 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
adc_oneshot_unit_handle_t adc_handle_{nullptr};
adc_cali_handle_t calibration_handle_{nullptr};
adc_atten_t attenuation_{ADC_ATTEN_DB_0};
adc_channel_t channel_;
adc_unit_t adc_unit_;
adc_channel_t channel_{};
adc_unit_t adc_unit_{};
struct SetupFlags {
uint8_t init_complete : 1;
uint8_t config_complete : 1;
@@ -151,6 +160,10 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
#ifdef USE_RP2040
bool is_temperature_{false};
#endif // USE_RP2040
#ifdef USE_ZEPHYR
const struct adc_dt_spec *channel_ = nullptr;
#endif
};
} // namespace adc

View File

@@ -18,15 +18,15 @@ const LogString *sampling_mode_to_str(SamplingMode mode) {
return LOG_STR("unknown");
}
Aggregator::Aggregator(SamplingMode mode) {
template<typename T> Aggregator<T>::Aggregator(SamplingMode mode) {
this->mode_ = mode;
// set to max uint if mode is "min"
if (mode == SamplingMode::MIN) {
this->aggr_ = UINT32_MAX;
this->aggr_ = std::numeric_limits<T>::max();
}
}
void Aggregator::add_sample(uint32_t value) {
template<typename T> void Aggregator<T>::add_sample(T value) {
this->samples_ += 1;
switch (this->mode_) {
@@ -47,7 +47,7 @@ void Aggregator::add_sample(uint32_t value) {
}
}
uint32_t Aggregator::aggregate() {
template<typename T> T Aggregator<T>::aggregate() {
if (this->mode_ == SamplingMode::AVG) {
if (this->samples_ == 0) {
return this->aggr_;
@@ -59,6 +59,12 @@ uint32_t Aggregator::aggregate() {
return this->aggr_;
}
#ifdef USE_ZEPHYR
template class Aggregator<int32_t>;
#else
template class Aggregator<uint32_t>;
#endif
void ADCSensor::update() {
float value_v = this->sample();
ESP_LOGV(TAG, "'%s': Voltage=%.4fV", this->get_name().c_str(), value_v);

View File

@@ -37,7 +37,6 @@ const LogString *adc_unit_to_str(adc_unit_t unit) {
}
void ADCSensor::setup() {
ESP_LOGCONFIG(TAG, "Running setup for '%s'", this->get_name().c_str());
// Check if another sensor already initialized this ADC unit
if (ADCSensor::shared_adc_handles[this->adc_unit_] == nullptr) {
adc_oneshot_unit_init_cfg_t init_config = {}; // Zero initialize
@@ -73,10 +72,9 @@ void ADCSensor::setup() {
// Initialize ADC calibration
if (this->calibration_handle_ == nullptr) {
adc_cali_handle_t handle = nullptr;
esp_err_t err;
#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \
USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2
USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4
// RISC-V variants and S3 use curve fitting calibration
adc_cali_curve_fitting_config_t cali_config = {}; // Zero initialize first
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)
@@ -154,7 +152,7 @@ float ADCSensor::sample() {
}
float ADCSensor::sample_fixed_attenuation_() {
auto aggr = Aggregator(this->sampling_mode_);
auto aggr = Aggregator<uint32_t>(this->sampling_mode_);
for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
int raw;
@@ -188,7 +186,7 @@ float ADCSensor::sample_fixed_attenuation_() {
ESP_LOGW(TAG, "ADC calibration conversion failed with error %d, disabling calibration", err);
if (this->calibration_handle_ != nullptr) {
#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \
USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2
USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4
adc_cali_delete_scheme_curve_fitting(this->calibration_handle_);
#else // Other ESP32 variants use line fitting calibration
adc_cali_delete_scheme_line_fitting(this->calibration_handle_);
@@ -221,7 +219,7 @@ float ADCSensor::sample_autorange_() {
if (this->calibration_handle_ != nullptr) {
// Delete old calibration handle
#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \
USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2
USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4
adc_cali_delete_scheme_curve_fitting(this->calibration_handle_);
#else
adc_cali_delete_scheme_line_fitting(this->calibration_handle_);
@@ -233,7 +231,7 @@ float ADCSensor::sample_autorange_() {
adc_cali_handle_t handle = nullptr;
#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \
USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2
USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4
adc_cali_curve_fitting_config_t cali_config = {};
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)
cali_config.chan = this->channel_;
@@ -262,7 +260,7 @@ float ADCSensor::sample_autorange_() {
ESP_LOGW(TAG, "ADC read failed in autorange with error %d", err);
if (handle != nullptr) {
#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \
USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2
USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4
adc_cali_delete_scheme_curve_fitting(handle);
#else
adc_cali_delete_scheme_line_fitting(handle);
@@ -282,7 +280,7 @@ float ADCSensor::sample_autorange_() {
}
// Clean up calibration handle
#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \
USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2
USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4
adc_cali_delete_scheme_curve_fitting(handle);
#else
adc_cali_delete_scheme_line_fitting(handle);

View File

@@ -17,7 +17,6 @@ namespace adc {
static const char *const TAG = "adc.esp8266";
void ADCSensor::setup() {
ESP_LOGCONFIG(TAG, "Running setup for '%s'", this->get_name().c_str());
#ifndef USE_ADC_SENSOR_VCC
this->pin_->setup();
#endif
@@ -38,7 +37,7 @@ void ADCSensor::dump_config() {
}
float ADCSensor::sample() {
auto aggr = Aggregator(this->sampling_mode_);
auto aggr = Aggregator<uint32_t>(this->sampling_mode_);
for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
uint32_t raw = 0;

View File

@@ -9,7 +9,6 @@ namespace adc {
static const char *const TAG = "adc.libretiny";
void ADCSensor::setup() {
ESP_LOGCONFIG(TAG, "Running setup for '%s'", this->get_name().c_str());
#ifndef USE_ADC_SENSOR_VCC
this->pin_->setup();
#endif // !USE_ADC_SENSOR_VCC
@@ -31,7 +30,7 @@ void ADCSensor::dump_config() {
float ADCSensor::sample() {
uint32_t raw = 0;
auto aggr = Aggregator(this->sampling_mode_);
auto aggr = Aggregator<uint32_t>(this->sampling_mode_);
if (this->output_raw_) {
for (uint8_t sample = 0; sample < this->sample_count_; sample++) {

View File

@@ -14,7 +14,6 @@ namespace adc {
static const char *const TAG = "adc.rp2040";
void ADCSensor::setup() {
ESP_LOGCONFIG(TAG, "Running setup for '%s'", this->get_name().c_str());
static bool initialized = false;
if (!initialized) {
adc_init();
@@ -42,7 +41,7 @@ void ADCSensor::dump_config() {
float ADCSensor::sample() {
uint32_t raw = 0;
auto aggr = Aggregator(this->sampling_mode_);
auto aggr = Aggregator<uint32_t>(this->sampling_mode_);
if (this->is_temperature_) {
adc_set_temp_sensor_enabled(true);

View File

@@ -0,0 +1,207 @@
#include "adc_sensor.h"
#ifdef USE_ZEPHYR
#include "esphome/core/log.h"
#include "hal/nrf_saadc.h"
namespace esphome {
namespace adc {
static const char *const TAG = "adc.zephyr";
void ADCSensor::setup() {
if (!adc_is_ready_dt(this->channel_)) {
ESP_LOGE(TAG, "ADC controller device %s not ready", this->channel_->dev->name);
return;
}
auto err = adc_channel_setup_dt(this->channel_);
if (err < 0) {
ESP_LOGE(TAG, "Could not setup channel %s (%d)", this->channel_->dev->name, err);
return;
}
}
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
static const LogString *gain_to_str(enum adc_gain gain) {
switch (gain) {
case ADC_GAIN_1_6:
return LOG_STR("1/6");
case ADC_GAIN_1_5:
return LOG_STR("1/5");
case ADC_GAIN_1_4:
return LOG_STR("1/4");
case ADC_GAIN_1_3:
return LOG_STR("1/3");
case ADC_GAIN_2_5:
return LOG_STR("2/5");
case ADC_GAIN_1_2:
return LOG_STR("1/2");
case ADC_GAIN_2_3:
return LOG_STR("2/3");
case ADC_GAIN_4_5:
return LOG_STR("4/5");
case ADC_GAIN_1:
return LOG_STR("1");
case ADC_GAIN_2:
return LOG_STR("2");
case ADC_GAIN_3:
return LOG_STR("3");
case ADC_GAIN_4:
return LOG_STR("4");
case ADC_GAIN_6:
return LOG_STR("6");
case ADC_GAIN_8:
return LOG_STR("8");
case ADC_GAIN_12:
return LOG_STR("12");
case ADC_GAIN_16:
return LOG_STR("16");
case ADC_GAIN_24:
return LOG_STR("24");
case ADC_GAIN_32:
return LOG_STR("32");
case ADC_GAIN_64:
return LOG_STR("64");
case ADC_GAIN_128:
return LOG_STR("128");
}
return LOG_STR("undefined gain");
}
static const LogString *reference_to_str(enum adc_reference reference) {
switch (reference) {
case ADC_REF_VDD_1:
return LOG_STR("VDD");
case ADC_REF_VDD_1_2:
return LOG_STR("VDD/2");
case ADC_REF_VDD_1_3:
return LOG_STR("VDD/3");
case ADC_REF_VDD_1_4:
return LOG_STR("VDD/4");
case ADC_REF_INTERNAL:
return LOG_STR("INTERNAL");
case ADC_REF_EXTERNAL0:
return LOG_STR("External, input 0");
case ADC_REF_EXTERNAL1:
return LOG_STR("External, input 1");
}
return LOG_STR("undefined reference");
}
static const LogString *input_to_str(uint8_t input) {
switch (input) {
case NRF_SAADC_INPUT_AIN0:
return LOG_STR("AIN0");
case NRF_SAADC_INPUT_AIN1:
return LOG_STR("AIN1");
case NRF_SAADC_INPUT_AIN2:
return LOG_STR("AIN2");
case NRF_SAADC_INPUT_AIN3:
return LOG_STR("AIN3");
case NRF_SAADC_INPUT_AIN4:
return LOG_STR("AIN4");
case NRF_SAADC_INPUT_AIN5:
return LOG_STR("AIN5");
case NRF_SAADC_INPUT_AIN6:
return LOG_STR("AIN6");
case NRF_SAADC_INPUT_AIN7:
return LOG_STR("AIN7");
case NRF_SAADC_INPUT_VDD:
return LOG_STR("VDD");
case NRF_SAADC_INPUT_VDDHDIV5:
return LOG_STR("VDDHDIV5");
}
return LOG_STR("undefined input");
}
#endif // ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
void ADCSensor::dump_config() {
LOG_SENSOR("", "ADC Sensor", this);
LOG_PIN(" Pin: ", this->pin_);
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
ESP_LOGV(TAG,
" Name: %s\n"
" Channel: %d\n"
" vref_mv: %d\n"
" Resolution %d\n"
" Oversampling %d",
this->channel_->dev->name, this->channel_->channel_id, this->channel_->vref_mv, this->channel_->resolution,
this->channel_->oversampling);
ESP_LOGV(TAG,
" Gain: %s\n"
" reference: %s\n"
" acquisition_time: %d\n"
" differential %s",
LOG_STR_ARG(gain_to_str(this->channel_->channel_cfg.gain)),
LOG_STR_ARG(reference_to_str(this->channel_->channel_cfg.reference)),
this->channel_->channel_cfg.acquisition_time, YESNO(this->channel_->channel_cfg.differential));
if (this->channel_->channel_cfg.differential) {
ESP_LOGV(TAG,
" Positive: %s\n"
" Negative: %s",
LOG_STR_ARG(input_to_str(this->channel_->channel_cfg.input_positive)),
LOG_STR_ARG(input_to_str(this->channel_->channel_cfg.input_negative)));
} else {
ESP_LOGV(TAG, " Positive: %s", LOG_STR_ARG(input_to_str(this->channel_->channel_cfg.input_positive)));
}
#endif
LOG_UPDATE_INTERVAL(this);
}
float ADCSensor::sample() {
auto aggr = Aggregator<int32_t>(this->sampling_mode_);
int err;
for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
int16_t buf = 0;
struct adc_sequence sequence = {
.buffer = &buf,
/* buffer size in bytes, not number of samples */
.buffer_size = sizeof(buf),
};
int32_t val_raw;
err = adc_sequence_init_dt(this->channel_, &sequence);
if (err < 0) {
ESP_LOGE(TAG, "Could sequence init %s (%d)", this->channel_->dev->name, err);
return 0.0;
}
err = adc_read(this->channel_->dev, &sequence);
if (err < 0) {
ESP_LOGE(TAG, "Could not read %s (%d)", this->channel_->dev->name, err);
return 0.0;
}
val_raw = (int32_t) buf;
if (!this->channel_->channel_cfg.differential) {
// https://github.com/adafruit/Adafruit_nRF52_Arduino/blob/0ed4d9ffc674ae407be7cacf5696a02f5e789861/cores/nRF5/wiring_analog_nRF52.c#L222
if (val_raw < 0) {
val_raw = 0;
}
}
aggr.add_sample(val_raw);
}
int32_t val_mv = aggr.aggregate();
if (this->output_raw_) {
return val_mv;
}
err = adc_raw_to_millivolts_dt(this->channel_, &val_mv);
/* conversion to mV may not be supported, skip if not */
if (err < 0) {
ESP_LOGE(TAG, "Value in mV not available %s (%d)", this->channel_->dev->name, err);
return 0.0;
}
return val_mv / 1000.0f;
}
} // namespace adc
} // namespace esphome
#endif

View File

@@ -3,6 +3,12 @@ import logging
import esphome.codegen as cg
from esphome.components import sensor, voltage_sampler
from esphome.components.esp32 import get_esp32_variant
from esphome.components.nrf52.const import AIN_TO_GPIO, EXTRA_ADC
from esphome.components.zephyr import (
zephyr_add_overlay,
zephyr_add_prj_conf,
zephyr_add_user,
)
import esphome.config_validation as cv
from esphome.const import (
CONF_ATTENUATION,
@@ -11,6 +17,7 @@ from esphome.const import (
CONF_PIN,
CONF_RAW,
DEVICE_CLASS_VOLTAGE,
PLATFORM_NRF52,
STATE_CLASS_MEASUREMENT,
UNIT_VOLT,
)
@@ -60,6 +67,10 @@ ADCSensor = adc_ns.class_(
"ADCSensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler
)
CONF_NRF_SAADC = "nrf_saadc"
adc_dt_spec = cg.global_ns.class_("adc_dt_spec")
CONFIG_SCHEMA = cv.All(
sensor.sensor_schema(
ADCSensor,
@@ -75,6 +86,7 @@ CONFIG_SCHEMA = cv.All(
cv.SplitDefault(CONF_ATTENUATION, esp32="0db"): cv.All(
cv.only_on_esp32, _attenuation
),
cv.OnlyWith(CONF_NRF_SAADC, PLATFORM_NRF52): cv.declare_id(adc_dt_spec),
cv.Optional(CONF_SAMPLES, default=1): cv.int_range(min=1, max=255),
cv.Optional(CONF_SAMPLING_MODE, default="avg"): _sampling_mode,
}
@@ -83,6 +95,8 @@ CONFIG_SCHEMA = cv.All(
validate_config,
)
CONF_ADC_CHANNEL_ID = "adc_channel_id"
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
@@ -93,7 +107,7 @@ async def to_code(config):
cg.add_define("USE_ADC_SENSOR_VCC")
elif config[CONF_PIN] == "TEMPERATURE":
cg.add(var.set_is_temperature())
else:
elif not CORE.is_nrf52 or config[CONF_PIN][CONF_NUMBER] not in EXTRA_ADC:
pin = await cg.gpio_pin_expression(config[CONF_PIN])
cg.add(var.set_pin(pin))
@@ -122,3 +136,41 @@ async def to_code(config):
):
chan = ESP32_VARIANT_ADC2_PIN_TO_CHANNEL[variant][pin_num]
cg.add(var.set_channel(adc_unit_t.ADC_UNIT_2, chan))
elif CORE.is_nrf52:
CORE.data.setdefault(CONF_ADC_CHANNEL_ID, 0)
channel_id = CORE.data[CONF_ADC_CHANNEL_ID]
CORE.data[CONF_ADC_CHANNEL_ID] = channel_id + 1
zephyr_add_prj_conf("ADC", True)
nrf_saadc = config[CONF_NRF_SAADC]
rhs = cg.RawExpression(
f"ADC_DT_SPEC_GET_BY_IDX(DT_PATH(zephyr_user), {channel_id})"
)
adc = cg.new_Pvariable(nrf_saadc, rhs)
cg.add(var.set_adc_channel(adc))
gain = "ADC_GAIN_1_6"
pin_number = config[CONF_PIN][CONF_NUMBER]
if pin_number == "VDDHDIV5":
gain = "ADC_GAIN_1_2"
if isinstance(pin_number, int):
GPIO_TO_AIN = {v: k for k, v in AIN_TO_GPIO.items()}
pin_number = GPIO_TO_AIN[pin_number]
zephyr_add_user("io-channels", f"<&adc {channel_id}>")
zephyr_add_overlay(
f"""
&adc {{
#address-cells = <1>;
#size-cells = <0>;
channel@{channel_id} {{
reg = <{channel_id}>;
zephyr,gain = "{gain}";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
zephyr,input-positive = <NRF_SAADC_{pin_number}>;
zephyr,resolution = <14>;
zephyr,oversampling = <8>;
}};
}};
"""
)

View File

@@ -8,10 +8,7 @@ static const char *const TAG = "adc128s102";
float ADC128S102::get_setup_priority() const { return setup_priority::HARDWARE; }
void ADC128S102::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
this->spi_setup();
}
void ADC128S102::setup() { this->spi_setup(); }
void ADC128S102::dump_config() {
ESP_LOGCONFIG(TAG, "ADC128S102:");

View File

@@ -36,6 +36,7 @@ from esphome.const import (
UNIT_WATT,
UNIT_WATT_HOURS,
)
from esphome.types import ConfigType
DEPENDENCIES = ["i2c"]
@@ -51,6 +52,20 @@ CONF_POWER_GAIN = "power_gain"
CONF_NEUTRAL = "neutral"
# Tuple of power channel phases
POWER_PHASES = (CONF_PHASE_A, CONF_PHASE_B, CONF_PHASE_C)
# Tuple of sensor types that can be configured for power channels
POWER_SENSOR_TYPES = (
CONF_CURRENT,
CONF_VOLTAGE,
CONF_ACTIVE_POWER,
CONF_APPARENT_POWER,
CONF_POWER_FACTOR,
CONF_FORWARD_ACTIVE_ENERGY,
CONF_REVERSE_ACTIVE_ENERGY,
)
NEUTRAL_CHANNEL_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(NeutralChannel),
@@ -150,7 +165,64 @@ POWER_CHANNEL_SCHEMA = cv.Schema(
}
)
CONFIG_SCHEMA = (
def prefix_sensor_name(
sensor_conf: ConfigType,
channel_name: str,
channel_config: ConfigType,
sensor_type: str,
) -> None:
"""Helper to prefix sensor name with channel name.
Args:
sensor_conf: The sensor configuration (dict or string)
channel_name: The channel name to prefix with
channel_config: The channel configuration to update
sensor_type: The sensor type key in the channel config
"""
if isinstance(sensor_conf, dict) and CONF_NAME in sensor_conf:
sensor_name = sensor_conf[CONF_NAME]
if sensor_name and not sensor_name.startswith(channel_name):
sensor_conf[CONF_NAME] = f"{channel_name} {sensor_name}"
elif isinstance(sensor_conf, str):
# Simple value case - convert to dict with prefixed name
channel_config[sensor_type] = {CONF_NAME: f"{channel_name} {sensor_conf}"}
def process_channel_sensors(
config: ConfigType, channel_key: str, sensor_types: tuple
) -> None:
"""Process sensors for a channel and prefix their names.
Args:
config: The main configuration
channel_key: The channel key (e.g., CONF_PHASE_A, CONF_NEUTRAL)
sensor_types: Tuple of sensor types to process for this channel
"""
if not (channel_config := config.get(channel_key)) or not (
channel_name := channel_config.get(CONF_NAME)
):
return
for sensor_type in sensor_types:
if sensor_conf := channel_config.get(sensor_type):
prefix_sensor_name(sensor_conf, channel_name, channel_config, sensor_type)
def preprocess_channels(config: ConfigType) -> ConfigType:
"""Preprocess channel configurations to add channel name prefix to sensor names."""
# Process power channels
for channel in POWER_PHASES:
process_channel_sensors(config, channel, POWER_SENSOR_TYPES)
# Process neutral channel
process_channel_sensors(config, CONF_NEUTRAL, (CONF_CURRENT,))
return config
CONFIG_SCHEMA = cv.All(
preprocess_channels,
cv.Schema(
{
cv.GenerateID(): cv.declare_id(ADE7880),
@@ -167,7 +239,7 @@ CONFIG_SCHEMA = (
}
)
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x38))
.extend(i2c.i2c_device_schema(0x38)),
)
@@ -188,15 +260,7 @@ async def neutral_channel(config):
async def power_channel(config):
var = cg.new_Pvariable(config[CONF_ID])
for sensor_type in [
CONF_CURRENT,
CONF_VOLTAGE,
CONF_ACTIVE_POWER,
CONF_APPARENT_POWER,
CONF_POWER_FACTOR,
CONF_FORWARD_ACTIVE_ENERGY,
CONF_REVERSE_ACTIVE_ENERGY,
]:
for sensor_type in POWER_SENSOR_TYPES:
if conf := config.get(sensor_type):
sens = await sensor.new_sensor(conf)
cg.add(getattr(var, f"set_{sensor_type}")(sens))
@@ -216,44 +280,6 @@ async def power_channel(config):
return var
def final_validate(config):
for channel in [CONF_PHASE_A, CONF_PHASE_B, CONF_PHASE_C]:
if channel := config.get(channel):
channel_name = channel.get(CONF_NAME)
for sensor_type in [
CONF_CURRENT,
CONF_VOLTAGE,
CONF_ACTIVE_POWER,
CONF_APPARENT_POWER,
CONF_POWER_FACTOR,
CONF_FORWARD_ACTIVE_ENERGY,
CONF_REVERSE_ACTIVE_ENERGY,
]:
if conf := channel.get(sensor_type):
sensor_name = conf.get(CONF_NAME)
if (
sensor_name
and channel_name
and not sensor_name.startswith(channel_name)
):
conf[CONF_NAME] = f"{channel_name} {sensor_name}"
if channel := config.get(CONF_NEUTRAL):
channel_name = channel.get(CONF_NAME)
if conf := channel.get(CONF_CURRENT):
sensor_name = conf.get(CONF_NAME)
if (
sensor_name
and channel_name
and not sensor_name.startswith(channel_name)
):
conf[CONF_NAME] = f"{channel_name} {sensor_name}"
FINAL_VALIDATE_SCHEMA = final_validate
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)

View File

@@ -10,7 +10,6 @@ static const uint8_t ADS1115_REGISTER_CONVERSION = 0x00;
static const uint8_t ADS1115_REGISTER_CONFIG = 0x01;
void ADS1115Component::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
uint16_t value;
if (!this->read_byte_16(ADS1115_REGISTER_CONVERSION, &value)) {
this->mark_failed();

View File

@@ -9,7 +9,6 @@ static const char *const TAG = "ads1118";
static const uint8_t ADS1118_DATA_RATE_860_SPS = 0b111;
void ADS1118::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
this->spi_setup();
this->config_ = 0;

View File

@@ -24,8 +24,6 @@ static const uint16_t ZP_CURRENT = 0x0000;
static const uint16_t ZP_DEFAULT = 0xFFFF;
void AGS10Component::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
auto version = this->read_version_();
if (version) {
ESP_LOGD(TAG, "AGS10 Sensor Version: 0x%02X", *version);
@@ -45,8 +43,6 @@ void AGS10Component::setup() {
} else {
ESP_LOGE(TAG, "AGS10 Sensor Resistance: unknown");
}
ESP_LOGD(TAG, "Sensor initialized");
}
void AGS10Component::update() {

View File

@@ -38,8 +38,6 @@ static const uint8_t AHT10_STATUS_BUSY = 0x80;
static const float AHT10_DIVISOR = 1048576.0f; // 2^20, used for temperature and humidity calculations
void AHT10Component::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
if (this->write(AHT10_SOFTRESET_CMD, sizeof(AHT10_SOFTRESET_CMD)) != i2c::ERROR_OK) {
ESP_LOGE(TAG, "Reset failed");
}
@@ -80,8 +78,6 @@ void AHT10Component::setup() {
this->mark_failed();
return;
}
ESP_LOGV(TAG, "Initialization complete");
}
void AHT10Component::restart_read_() {

View File

@@ -17,8 +17,6 @@ static const char *const TAG = "aic3204";
}
void AIC3204::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
// Set register page to 0
ERROR_CHECK(this->write_byte(AIC3204_PAGE_CTRL, 0x00), "Set page 0 failed");
// Initiate SW reset (PLL is powered off as part of reset)

View File

@@ -301,8 +301,7 @@ async def alarm_action_disarm_to_code(config, action_id, template_arg, args):
)
async def alarm_action_pending_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
return var
return cg.new_Pvariable(action_id, template_arg, paren)
@automation.register_action(
@@ -310,8 +309,7 @@ async def alarm_action_pending_to_code(config, action_id, template_arg, args):
)
async def alarm_action_trigger_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
return var
return cg.new_Pvariable(action_id, template_arg, paren)
@automation.register_action(
@@ -319,8 +317,7 @@ async def alarm_action_trigger_to_code(config, action_id, template_arg, args):
)
async def alarm_action_chime_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
return var
return cg.new_Pvariable(action_id, template_arg, paren)
@automation.register_action(
@@ -333,8 +330,7 @@ async def alarm_action_chime_to_code(config, action_id, template_arg, args):
)
async def alarm_action_ready_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
return var
return cg.new_Pvariable(action_id, template_arg, paren)
@automation.register_condition(
@@ -352,4 +348,3 @@ async def alarm_control_panel_is_armed_to_code(
@coroutine_with_priority(100.0)
async def to_code(config):
cg.add_global(alarm_control_panel_ns.using)
cg.add_define("USE_ALARM_CONTROL_PANEL")

View File

@@ -90,8 +90,6 @@ bool AM2315C::convert_(uint8_t *data, float &humidity, float &temperature) {
}
void AM2315C::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
// get status
uint8_t status = 0;
if (this->read(&status, 1) != i2c::ERROR_OK) {

View File

@@ -34,7 +34,6 @@ void AM2320Component::update() {
this->status_clear_warning();
}
void AM2320Component::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
uint8_t data[8];
data[0] = 0;
data[1] = 4;

View File

@@ -34,17 +34,20 @@ SetFrameAction = animation_ns.class_(
"AnimationSetFrameAction", automation.Action, cg.Parented.template(Animation_)
)
CONFIG_SCHEMA = espImage.IMAGE_SCHEMA.extend(
{
cv.Required(CONF_ID): cv.declare_id(Animation_),
cv.Optional(CONF_LOOP): cv.All(
{
cv.Optional(CONF_START_FRAME, default=0): cv.positive_int,
cv.Optional(CONF_END_FRAME): cv.positive_int,
cv.Optional(CONF_REPEAT): cv.positive_int,
}
),
},
CONFIG_SCHEMA = cv.All(
espImage.IMAGE_SCHEMA.extend(
{
cv.Required(CONF_ID): cv.declare_id(Animation_),
cv.Optional(CONF_LOOP): cv.All(
{
cv.Optional(CONF_START_FRAME, default=0): cv.positive_int,
cv.Optional(CONF_END_FRAME): cv.positive_int,
cv.Optional(CONF_REPEAT): cv.positive_int,
}
),
},
),
espImage.validate_settings,
)

View File

@@ -54,8 +54,6 @@ enum { // APDS9306 registers
}
void APDS9306::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
uint8_t id;
if (!this->read_byte(APDS9306_PART_ID, &id)) { // Part ID register
this->error_code_ = COMMUNICATION_FAILED;
@@ -86,8 +84,6 @@ void APDS9306::setup() {
// Set to active mode
APDS9306_WRITE_BYTE(APDS9306_MAIN_CTRL, 0x02);
ESP_LOGCONFIG(TAG, "APDS9306 setup complete");
}
void APDS9306::dump_config() {

View File

@@ -15,7 +15,6 @@ static const char *const TAG = "apds9960";
#define APDS9960_WRITE_BYTE(reg, value) APDS9960_ERROR_CHECK(this->write_byte(reg, value));
void APDS9960::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
uint8_t id;
if (!this->read_byte(0x92, &id)) { // ID register
this->error_code_ = COMMUNICATION_FAILED;

View File

@@ -29,7 +29,7 @@ from esphome.core import CORE, coroutine_with_priority
DOMAIN = "api"
DEPENDENCIES = ["network"]
AUTO_LOAD = ["socket"]
CODEOWNERS = ["@OttoWinter"]
CODEOWNERS = ["@esphome/core"]
api_ns = cg.esphome_ns.namespace("api")
APIServer = api_ns.class_("APIServer", cg.Component, cg.Controller)
@@ -53,6 +53,8 @@ SERVICE_ARG_NATIVE_TYPES = {
CONF_ENCRYPTION = "encryption"
CONF_BATCH_DELAY = "batch_delay"
CONF_CUSTOM_SERVICES = "custom_services"
CONF_HOMEASSISTANT_SERVICES = "homeassistant_services"
CONF_HOMEASSISTANT_STATES = "homeassistant_states"
def validate_encryption_key(value):
@@ -118,6 +120,8 @@ CONFIG_SCHEMA = cv.All(
cv.Range(max=cv.TimePeriod(milliseconds=65535)),
),
cv.Optional(CONF_CUSTOM_SERVICES, default=False): cv.boolean,
cv.Optional(CONF_HOMEASSISTANT_SERVICES, default=False): cv.boolean,
cv.Optional(CONF_HOMEASSISTANT_STATES, default=False): cv.boolean,
cv.Optional(CONF_ON_CLIENT_CONNECTED): automation.validate_automation(
single=True
),
@@ -146,6 +150,12 @@ async def to_code(config):
if config.get(CONF_ACTIONS) or config[CONF_CUSTOM_SERVICES]:
cg.add_define("USE_API_SERVICES")
if config[CONF_HOMEASSISTANT_SERVICES]:
cg.add_define("USE_API_HOMEASSISTANT_SERVICES")
if config[CONF_HOMEASSISTANT_STATES]:
cg.add_define("USE_API_HOMEASSISTANT_STATES")
if actions := config.get(CONF_ACTIONS, []):
for conf in actions:
template_args = []
@@ -235,6 +245,7 @@ HOMEASSISTANT_ACTION_ACTION_SCHEMA = cv.All(
HOMEASSISTANT_ACTION_ACTION_SCHEMA,
)
async def homeassistant_service_to_code(config, action_id, template_arg, args):
cg.add_define("USE_API_HOMEASSISTANT_SERVICES")
serv = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, serv, False)
templ = await cg.templatable(config[CONF_ACTION], args, None)
@@ -278,6 +289,7 @@ HOMEASSISTANT_EVENT_ACTION_SCHEMA = cv.Schema(
HOMEASSISTANT_EVENT_ACTION_SCHEMA,
)
async def homeassistant_event_to_code(config, action_id, template_arg, args):
cg.add_define("USE_API_HOMEASSISTANT_SERVICES")
serv = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, serv, True)
templ = await cg.templatable(config[CONF_EVENT], args, None)

View File

@@ -250,8 +250,8 @@ message DeviceInfoResponse {
// Supports receiving and saving api encryption key
bool api_encryption_supported = 19 [(field_ifdef) = "USE_API_NOISE"];
repeated DeviceInfo devices = 20 [(field_ifdef) = "USE_DEVICES"];
repeated AreaInfo areas = 21 [(field_ifdef) = "USE_AREAS"];
repeated DeviceInfo devices = 20 [(field_ifdef) = "USE_DEVICES", (fixed_array_size_define) = "ESPHOME_DEVICE_COUNT"];
repeated AreaInfo areas = 21 [(field_ifdef) = "USE_AREAS", (fixed_array_size_define) = "ESPHOME_AREA_COUNT"];
// Top-level area info to phase out suggested_area
AreaInfo area = 22 [(field_ifdef) = "USE_AREAS"];
@@ -419,7 +419,7 @@ message ListEntitiesFanResponse {
bool disabled_by_default = 9;
string icon = 10 [(field_ifdef) = "USE_ENTITY_ICON"];
EntityCategory entity_category = 11;
repeated string supported_preset_modes = 12;
repeated string supported_preset_modes = 12 [(container_pointer) = "std::set"];
uint32 device_id = 13 [(field_ifdef) = "USE_DEVICES"];
}
// Deprecated in API version 1.6 - only used in deprecated fields
@@ -500,7 +500,7 @@ message ListEntitiesLightResponse {
string name = 3;
reserved 4; // Deprecated: was string unique_id
repeated ColorMode supported_color_modes = 12;
repeated ColorMode supported_color_modes = 12 [(container_pointer) = "std::set<light::ColorMode>"];
// next four supports_* are for legacy clients, newer clients should use color modes
// Deprecated in API version 1.6
bool legacy_supports_brightness = 5 [deprecated=true];
@@ -755,17 +755,19 @@ message NoiseEncryptionSetKeyResponse {
message SubscribeHomeassistantServicesRequest {
option (id) = 34;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_API_HOMEASSISTANT_SERVICES";
}
message HomeassistantServiceMap {
string key = 1;
string value = 2;
string value = 2 [(no_zero_copy) = true];
}
message HomeassistantServiceResponse {
option (id) = 35;
option (source) = SOURCE_SERVER;
option (no_delay) = true;
option (ifdef) = "USE_API_HOMEASSISTANT_SERVICES";
string service = 1;
repeated HomeassistantServiceMap data = 2;
@@ -781,11 +783,13 @@ message HomeassistantServiceResponse {
message SubscribeHomeAssistantStatesRequest {
option (id) = 38;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_API_HOMEASSISTANT_STATES";
}
message SubscribeHomeAssistantStateResponse {
option (id) = 39;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_API_HOMEASSISTANT_STATES";
string entity_id = 1;
string attribute = 2;
bool once = 3;
@@ -795,6 +799,7 @@ message HomeAssistantStateResponse {
option (id) = 40;
option (source) = SOURCE_CLIENT;
option (no_delay) = true;
option (ifdef) = "USE_API_HOMEASSISTANT_STATES";
string entity_id = 1;
string state = 2;
@@ -961,7 +966,7 @@ message ListEntitiesClimateResponse {
bool supports_current_temperature = 5;
bool supports_two_point_target_temperature = 6;
repeated ClimateMode supported_modes = 7;
repeated ClimateMode supported_modes = 7 [(container_pointer) = "std::set<climate::ClimateMode>"];
float visual_min_temperature = 8;
float visual_max_temperature = 9;
float visual_target_temperature_step = 10;
@@ -970,11 +975,11 @@ message ListEntitiesClimateResponse {
// Deprecated in API version 1.5
bool legacy_supports_away = 11 [deprecated=true];
bool supports_action = 12;
repeated ClimateFanMode supported_fan_modes = 13;
repeated ClimateSwingMode supported_swing_modes = 14;
repeated string supported_custom_fan_modes = 15;
repeated ClimatePreset supported_presets = 16;
repeated string supported_custom_presets = 17;
repeated ClimateFanMode supported_fan_modes = 13 [(container_pointer) = "std::set<climate::ClimateFanMode>"];
repeated ClimateSwingMode supported_swing_modes = 14 [(container_pointer) = "std::set<climate::ClimateSwingMode>"];
repeated string supported_custom_fan_modes = 15 [(container_pointer) = "std::set"];
repeated ClimatePreset supported_presets = 16 [(container_pointer) = "std::set<climate::ClimatePreset>"];
repeated string supported_custom_presets = 17 [(container_pointer) = "std::set"];
bool disabled_by_default = 18;
string icon = 19 [(field_ifdef) = "USE_ENTITY_ICON"];
EntityCategory entity_category = 20;
@@ -1114,7 +1119,7 @@ message ListEntitiesSelectResponse {
reserved 4; // Deprecated: was string unique_id
string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON"];
repeated string options = 6;
repeated string options = 6 [(container_pointer) = "std::vector"];
bool disabled_by_default = 7;
EntityCategory entity_category = 8;
uint32 device_id = 9 [(field_ifdef) = "USE_DEVICES"];
@@ -1292,6 +1297,9 @@ enum MediaPlayerState {
MEDIA_PLAYER_STATE_IDLE = 1;
MEDIA_PLAYER_STATE_PLAYING = 2;
MEDIA_PLAYER_STATE_PAUSED = 3;
MEDIA_PLAYER_STATE_ANNOUNCING = 4;
MEDIA_PLAYER_STATE_OFF = 5;
MEDIA_PLAYER_STATE_ON = 6;
}
enum MediaPlayerCommand {
MEDIA_PLAYER_COMMAND_PLAY = 0;
@@ -1299,6 +1307,15 @@ enum MediaPlayerCommand {
MEDIA_PLAYER_COMMAND_STOP = 2;
MEDIA_PLAYER_COMMAND_MUTE = 3;
MEDIA_PLAYER_COMMAND_UNMUTE = 4;
MEDIA_PLAYER_COMMAND_TOGGLE = 5;
MEDIA_PLAYER_COMMAND_VOLUME_UP = 6;
MEDIA_PLAYER_COMMAND_VOLUME_DOWN = 7;
MEDIA_PLAYER_COMMAND_ENQUEUE = 8;
MEDIA_PLAYER_COMMAND_REPEAT_ONE = 9;
MEDIA_PLAYER_COMMAND_REPEAT_OFF = 10;
MEDIA_PLAYER_COMMAND_CLEAR_PLAYLIST = 11;
MEDIA_PLAYER_COMMAND_TURN_ON = 12;
MEDIA_PLAYER_COMMAND_TURN_OFF = 13;
}
enum MediaPlayerFormatPurpose {
MEDIA_PLAYER_FORMAT_PURPOSE_DEFAULT = 0;
@@ -1333,6 +1350,8 @@ message ListEntitiesMediaPlayerResponse {
repeated MediaPlayerSupportedFormat supported_formats = 9;
uint32 device_id = 10 [(field_ifdef) = "USE_DEVICES"];
uint32 feature_flags = 11;
}
message MediaPlayerStateResponse {
option (id) = 64;
@@ -1419,11 +1438,11 @@ message BluetoothLERawAdvertisementsResponse {
option (ifdef) = "USE_BLUETOOTH_PROXY";
option (no_delay) = true;
repeated BluetoothLERawAdvertisement advertisements = 1;
repeated BluetoothLERawAdvertisement advertisements = 1 [(fixed_array_with_length_define) = "BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE"];
}
enum BluetoothDeviceRequestType {
BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT = 0;
BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT = 0 [deprecated = true]; // V1 removed, use V3 variants
BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT = 1;
BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR = 2;
BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR = 3;
@@ -1463,21 +1482,39 @@ message BluetoothGATTGetServicesRequest {
}
message BluetoothGATTDescriptor {
repeated uint64 uuid = 1 [(fixed_array_size) = 2];
repeated uint64 uuid = 1 [(fixed_array_size) = 2, (fixed_array_skip_zero) = true];
uint32 handle = 2;
// New field for efficient UUID (v1.12+)
// Only one of uuid or short_uuid will be set.
// short_uuid is used for both 16-bit and 32-bit UUIDs with v1.12+ clients.
// 128-bit UUIDs always use the uuid field for backwards compatibility.
uint32 short_uuid = 3; // 16-bit or 32-bit UUID
}
message BluetoothGATTCharacteristic {
repeated uint64 uuid = 1 [(fixed_array_size) = 2];
repeated uint64 uuid = 1 [(fixed_array_size) = 2, (fixed_array_skip_zero) = true];
uint32 handle = 2;
uint32 properties = 3;
repeated BluetoothGATTDescriptor descriptors = 4;
// New field for efficient UUID (v1.12+)
// Only one of uuid or short_uuid will be set.
// short_uuid is used for both 16-bit and 32-bit UUIDs with v1.12+ clients.
// 128-bit UUIDs always use the uuid field for backwards compatibility.
uint32 short_uuid = 5; // 16-bit or 32-bit UUID
}
message BluetoothGATTService {
repeated uint64 uuid = 1 [(fixed_array_size) = 2];
repeated uint64 uuid = 1 [(fixed_array_size) = 2, (fixed_array_skip_zero) = true];
uint32 handle = 2;
repeated BluetoothGATTCharacteristic characteristics = 3;
// New field for efficient UUID (v1.12+)
// Only one of uuid or short_uuid will be set.
// short_uuid is used for both 16-bit and 32-bit UUIDs with v1.12+ clients.
// 128-bit UUIDs always use the uuid field for backwards compatibility.
uint32 short_uuid = 4; // 16-bit or 32-bit UUID
}
message BluetoothGATTGetServicesResponse {
@@ -1486,7 +1523,7 @@ message BluetoothGATTGetServicesResponse {
option (ifdef) = "USE_BLUETOOTH_PROXY";
uint64 address = 1;
repeated BluetoothGATTService services = 2 [(fixed_array_size) = 1];
repeated BluetoothGATTService services = 2;
}
message BluetoothGATTGetServicesDoneResponse {
@@ -1584,7 +1621,10 @@ message BluetoothConnectionsFreeResponse {
uint32 free = 1;
uint32 limit = 2;
repeated uint64 allocated = 3;
repeated uint64 allocated = 3 [
(fixed_array_size_define) = "BLUETOOTH_PROXY_MAX_CONNECTIONS",
(fixed_array_skip_zero) = true
];
}
message BluetoothGATTErrorResponse {
@@ -1829,7 +1869,7 @@ message VoiceAssistantConfigurationResponse {
option (ifdef) = "USE_VOICE_ASSISTANT";
repeated VoiceAssistantWakeWord available_wake_words = 1;
repeated string active_wake_words = 2;
repeated string active_wake_words = 2 [(container_pointer) = "std::vector"];
uint32 max_active_wake_words = 3;
}

View File

@@ -11,6 +11,7 @@
#include <utility>
#include <functional>
#include <limits>
#include "api_timing.h"
#include "esphome/components/network/util.h"
#include "esphome/core/application.h"
#include "esphome/core/entity_base.h"
@@ -31,8 +32,7 @@
#include "esphome/components/voice_assistant/voice_assistant.h"
#endif
namespace esphome {
namespace api {
namespace esphome::api {
// Read a maximum of 5 messages per loop iteration to prevent starving other components.
// This is a balance between API responsiveness and allowing other components to run.
@@ -113,8 +113,7 @@ void APIConnection::start() {
APIError err = this->helper_->init();
if (err != APIError::OK) {
on_fatal_error();
ESP_LOGW(TAG, "%s: Helper init failed %s errno=%d", this->get_client_combined_info().c_str(), api_error_to_str(err),
errno);
this->log_warning_("Helper init failed", err);
return;
}
this->client_info_.peername = helper_->getpeername();
@@ -142,37 +141,44 @@ void APIConnection::loop() {
return;
}
API_TIMING_START(helper);
APIError err = this->helper_->loop();
API_TIMING_END(helper, HELPER_LOOP);
if (err != APIError::OK) {
on_fatal_error();
ESP_LOGW(TAG, "%s: Socket operation failed %s errno=%d", this->get_client_combined_info().c_str(),
api_error_to_str(err), errno);
this->log_socket_operation_failed_(err);
return;
}
const uint32_t now = App.get_loop_component_start_time();
// Check if socket has data ready before attempting to read
if (this->helper_->is_socket_ready()) {
API_TIMING_START(socket_check);
bool socket_ready = this->helper_->is_socket_ready();
API_TIMING_END(socket_check, SOCKET_CHECK);
if (socket_ready) {
// Read up to MAX_MESSAGES_PER_LOOP messages per loop to improve throughput
for (uint8_t message_count = 0; message_count < MAX_MESSAGES_PER_LOOP; message_count++) {
ReadPacketBuffer buffer;
API_TIMING_START(read);
err = this->helper_->read_packet(&buffer);
API_TIMING_END(read, READ_PACKET);
if (err == APIError::WOULD_BLOCK) {
// No more data available
break;
} else if (err != APIError::OK) {
on_fatal_error();
ESP_LOGW(TAG, "%s: Reading failed %s errno=%d", this->get_client_combined_info().c_str(), api_error_to_str(err),
errno);
this->log_warning_("Reading failed", err);
return;
} else {
this->last_traffic_ = now;
// read a packet
API_TIMING_START(process);
if (buffer.data_len > 0) {
this->read_message(buffer.data_len, buffer.type, &buffer.container[buffer.data_offset]);
} else {
this->read_message(0, buffer.type, nullptr);
}
API_TIMING_END(process, PROCESS_MESSAGE);
if (this->flags_.remove)
return;
}
@@ -181,19 +187,27 @@ void APIConnection::loop() {
// Process deferred batch if scheduled and timer has expired
if (this->flags_.batch_scheduled && now - this->deferred_batch_.batch_start_time >= this->get_batch_delay_ms_()) {
API_TIMING_START(deferred);
this->process_batch_();
API_TIMING_END(deferred, DEFERRED_BATCH);
}
if (!this->list_entities_iterator_.completed()) {
API_TIMING_START(iterator1);
this->process_iterator_batch_(this->list_entities_iterator_);
API_TIMING_END(iterator1, ITERATOR_BATCH);
} else if (!this->initial_state_iterator_.completed()) {
API_TIMING_START(iterator2);
this->process_iterator_batch_(this->initial_state_iterator_);
API_TIMING_END(iterator2, ITERATOR_BATCH);
// If we've completed initial states, process any remaining and clear the flag
if (this->initial_state_iterator_.completed()) {
// Process any remaining batched messages immediately
if (!this->deferred_batch_.empty()) {
API_TIMING_START(deferred2);
this->process_batch_();
API_TIMING_END(deferred2, DEFERRED_BATCH);
}
// Now that everything is sent, enable immediate sending for future state changes
this->flags_.should_try_send_immediately = true;
@@ -207,6 +221,7 @@ void APIConnection::loop() {
ESP_LOGW(TAG, "%s is unresponsive; disconnecting", this->get_client_combined_info().c_str());
}
} else if (now - this->last_traffic_ > KEEPALIVE_TIMEOUT_MS && !this->flags_.remove) {
API_TIMING_GUARD(KEEPALIVE);
// Only send ping if we're not disconnecting
ESP_LOGVV(TAG, "Sending keepalive PING");
PingRequest req;
@@ -223,6 +238,7 @@ void APIConnection::loop() {
#ifdef USE_CAMERA
if (this->image_reader_ && this->image_reader_->available() && this->helper_->can_write_without_blocking()) {
API_TIMING_GUARD(CAMERA_IMAGE);
uint32_t to_send = std::min((size_t) MAX_BATCH_PACKET_SIZE, this->image_reader_->available());
bool done = this->image_reader_->available() == to_send;
@@ -243,23 +259,11 @@ void APIConnection::loop() {
}
#endif
#ifdef USE_API_HOMEASSISTANT_STATES
if (state_subs_at_ >= 0) {
const auto &subs = this->parent_->get_state_subs();
if (state_subs_at_ < static_cast<int>(subs.size())) {
auto &it = subs[state_subs_at_];
SubscribeHomeAssistantStateResponse resp;
resp.set_entity_id(StringRef(it.entity_id));
// attribute.value() returns temporary - must store it
std::string attribute_value = it.attribute.value();
resp.set_attribute(StringRef(attribute_value));
resp.once = it.once;
if (this->send_message(resp, SubscribeHomeAssistantStateResponse::MESSAGE_TYPE)) {
state_subs_at_++;
}
} else {
state_subs_at_ = -1;
}
this->process_state_subscriptions_();
}
#endif
}
bool APIConnection::send_disconnect_response(const DisconnectRequest &msg) {
@@ -289,8 +293,11 @@ uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint8_t mess
#endif
// Calculate size
uint32_t calculated_size = 0;
msg.calculate_size(calculated_size);
API_TIMING_START(msg_size_calc);
ProtoSize size_calc;
msg.calculate_size(size_calc);
uint32_t calculated_size = size_calc.get_size();
API_TIMING_END(msg_size_calc, ENCODE_MESSAGE);
// Cache frame sizes to avoid repeated virtual calls
const uint8_t header_padding = conn->helper_->frame_header_padding();
@@ -304,16 +311,28 @@ uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint8_t mess
return 0; // Doesn't fit
}
// Allocate buffer space - pass payload size, allocation functions add header/footer space
ProtoWriteBuffer buffer = is_single ? conn->allocate_single_message_buffer(calculated_size)
: conn->allocate_batch_message_buffer(calculated_size);
// Get buffer size after allocation (which includes header padding)
std::vector<uint8_t> &shared_buf = conn->parent_->get_shared_buffer_ref();
size_t size_before_encode = shared_buf.size();
if (is_single || conn->flags_.batch_first_message) {
// Single message or first batch message
conn->prepare_first_message_buffer(shared_buf, header_padding, total_calculated_size);
if (conn->flags_.batch_first_message) {
conn->flags_.batch_first_message = false;
}
} else {
// Batch message second or later
// Add padding for previous message footer + this message header
size_t current_size = shared_buf.size();
shared_buf.reserve(current_size + total_calculated_size);
shared_buf.resize(current_size + footer_size + header_padding);
}
// Encode directly into buffer
msg.encode(buffer);
size_t size_before_encode = shared_buf.size();
API_TIMING_START(msg_encode);
msg.encode({&shared_buf});
API_TIMING_END(msg_encode, ENCODE_MESSAGE);
// Calculate actual encoded size (not including header that was already added)
size_t actual_payload_size = shared_buf.size() - size_before_encode;
@@ -425,8 +444,7 @@ uint16_t APIConnection::try_send_fan_info(EntityBase *entity, APIConnection *con
msg.supports_speed = traits.supports_speed();
msg.supports_direction = traits.supports_direction();
msg.supported_speed_count = traits.supported_speed_count();
for (auto const &preset : traits.supported_preset_modes())
msg.supported_preset_modes.push_back(preset);
msg.supported_preset_modes = &traits.supported_preset_modes_for_api_();
return fill_and_encode_entity_info(fan, msg, ListEntitiesFanResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
void APIConnection::fan_command(const FanCommandRequest &msg) {
@@ -471,9 +489,7 @@ uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection *
resp.cold_white = values.get_cold_white();
resp.warm_white = values.get_warm_white();
if (light->supports_effects()) {
// get_effect_name() returns temporary std::string - must store it
std::string effect_name = light->get_effect_name();
resp.set_effect(StringRef(effect_name));
resp.set_effect(light->get_effect_name_ref());
}
return fill_and_encode_entity_state(light, resp, LightStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
@@ -482,8 +498,7 @@ uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *c
auto *light = static_cast<light::LightState *>(entity);
ListEntitiesLightResponse msg;
auto traits = light->get_traits();
for (auto mode : traits.get_supported_color_modes())
msg.supported_color_modes.push_back(static_cast<enums::ColorMode>(mode));
msg.supported_color_modes = &traits.get_supported_color_modes_for_api_();
if (traits.supports_color_capability(light::ColorCapability::COLOR_TEMPERATURE) ||
traits.supports_color_capability(light::ColorCapability::COLD_WARM_WHITE)) {
msg.min_mireds = traits.get_min_mireds();
@@ -643,17 +658,13 @@ uint16_t APIConnection::try_send_climate_state(EntityBase *entity, APIConnection
if (traits.get_supports_fan_modes() && climate->fan_mode.has_value())
resp.fan_mode = static_cast<enums::ClimateFanMode>(climate->fan_mode.value());
if (!traits.get_supported_custom_fan_modes().empty() && climate->custom_fan_mode.has_value()) {
// custom_fan_mode.value() returns temporary - must store it
std::string custom_fan_mode = climate->custom_fan_mode.value();
resp.set_custom_fan_mode(StringRef(custom_fan_mode));
resp.set_custom_fan_mode(StringRef(climate->custom_fan_mode.value()));
}
if (traits.get_supports_presets() && climate->preset.has_value()) {
resp.preset = static_cast<enums::ClimatePreset>(climate->preset.value());
}
if (!traits.get_supported_custom_presets().empty() && climate->custom_preset.has_value()) {
// custom_preset.value() returns temporary - must store it
std::string custom_preset = climate->custom_preset.value();
resp.set_custom_preset(StringRef(custom_preset));
resp.set_custom_preset(StringRef(climate->custom_preset.value()));
}
if (traits.get_supports_swing_modes())
resp.swing_mode = static_cast<enums::ClimateSwingMode>(climate->swing_mode);
@@ -673,8 +684,7 @@ uint16_t APIConnection::try_send_climate_info(EntityBase *entity, APIConnection
msg.supports_current_humidity = traits.get_supports_current_humidity();
msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_temperature();
msg.supports_target_humidity = traits.get_supports_target_humidity();
for (auto mode : traits.get_supported_modes())
msg.supported_modes.push_back(static_cast<enums::ClimateMode>(mode));
msg.supported_modes = &traits.get_supported_modes_for_api_();
msg.visual_min_temperature = traits.get_visual_min_temperature();
msg.visual_max_temperature = traits.get_visual_max_temperature();
msg.visual_target_temperature_step = traits.get_visual_target_temperature_step();
@@ -682,16 +692,11 @@ uint16_t APIConnection::try_send_climate_info(EntityBase *entity, APIConnection
msg.visual_min_humidity = traits.get_visual_min_humidity();
msg.visual_max_humidity = traits.get_visual_max_humidity();
msg.supports_action = traits.get_supports_action();
for (auto fan_mode : traits.get_supported_fan_modes())
msg.supported_fan_modes.push_back(static_cast<enums::ClimateFanMode>(fan_mode));
for (auto const &custom_fan_mode : traits.get_supported_custom_fan_modes())
msg.supported_custom_fan_modes.push_back(custom_fan_mode);
for (auto preset : traits.get_supported_presets())
msg.supported_presets.push_back(static_cast<enums::ClimatePreset>(preset));
for (auto const &custom_preset : traits.get_supported_custom_presets())
msg.supported_custom_presets.push_back(custom_preset);
for (auto swing_mode : traits.get_supported_swing_modes())
msg.supported_swing_modes.push_back(static_cast<enums::ClimateSwingMode>(swing_mode));
msg.supported_fan_modes = &traits.get_supported_fan_modes_for_api_();
msg.supported_custom_fan_modes = &traits.get_supported_custom_fan_modes_for_api_();
msg.supported_presets = &traits.get_supported_presets_for_api_();
msg.supported_custom_presets = &traits.get_supported_custom_presets_for_api_();
msg.supported_swing_modes = &traits.get_supported_swing_modes_for_api_();
return fill_and_encode_entity_info(climate, msg, ListEntitiesClimateResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
}
@@ -897,8 +902,7 @@ uint16_t APIConnection::try_send_select_info(EntityBase *entity, APIConnection *
bool is_single) {
auto *select = static_cast<select::Select *>(entity);
ListEntitiesSelectResponse msg;
for (const auto &option : select->traits.get_options())
msg.options.push_back(option);
msg.options = &select->traits.get_options();
return fill_and_encode_entity_info(select, msg, ListEntitiesSelectResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
}
@@ -1024,6 +1028,7 @@ uint16_t APIConnection::try_send_media_player_info(EntityBase *entity, APIConnec
ListEntitiesMediaPlayerResponse msg;
auto traits = media_player->get_traits();
msg.supports_pause = traits.get_supports_pause();
msg.feature_flags = traits.get_feature_flags();
for (auto &supported_format : traits.get_supported_formats()) {
msg.supported_formats.emplace_back();
auto &media_format = msg.supported_formats.back();
@@ -1132,10 +1137,8 @@ void APIConnection::bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg)
bool APIConnection::send_subscribe_bluetooth_connections_free_response(
const SubscribeBluetoothConnectionsFreeRequest &msg) {
BluetoothConnectionsFreeResponse resp;
resp.free = bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_connections_free();
resp.limit = bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_connections_limit();
return this->send_message(resp, BluetoothConnectionsFreeResponse::MESSAGE_TYPE);
bluetooth_proxy::global_bluetooth_proxy->send_connections_free(this);
return true;
}
void APIConnection::bluetooth_scanner_set_mode(const BluetoothScannerSetModeRequest &msg) {
@@ -1212,9 +1215,7 @@ bool APIConnection::send_voice_assistant_get_configuration_response(const VoiceA
resp_wake_word.trained_languages.push_back(lang);
}
}
for (auto &wake_word_id : config.active_wake_words) {
resp.active_wake_words.push_back(wake_word_id);
}
resp.active_wake_words = &config.active_wake_words;
resp.max_active_wake_words = config.max_active_wake_words;
return this->send_message(resp, VoiceAssistantConfigurationResponse::MESSAGE_TYPE);
}
@@ -1392,7 +1393,7 @@ bool APIConnection::send_hello_response(const HelloRequest &msg) {
HelloResponse resp;
resp.api_version_major = 1;
resp.api_version_minor = 10;
resp.api_version_minor = 12;
// Temporary string for concatenation - will be valid during send_message call
std::string server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")";
resp.set_server_info(StringRef(server_info));
@@ -1446,9 +1447,7 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
static constexpr auto ESPHOME_VERSION_REF = StringRef::from_lit(ESPHOME_VERSION);
resp.set_esphome_version(ESPHOME_VERSION_REF);
// get_compilation_time() returns temporary std::string - must store it
std::string compilation_time = App.get_compilation_time();
resp.set_compilation_time(StringRef(compilation_time));
resp.set_compilation_time(App.get_compilation_time_ref());
// Compile-time StringRef constants for manufacturers
#if defined(USE_ESP8266) || defined(USE_ESP32)
@@ -1493,18 +1492,22 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
resp.api_encryption_supported = true;
#endif
#ifdef USE_DEVICES
size_t device_index = 0;
for (auto const &device : App.get_devices()) {
resp.devices.emplace_back();
auto &device_info = resp.devices.back();
if (device_index >= ESPHOME_DEVICE_COUNT)
break;
auto &device_info = resp.devices[device_index++];
device_info.device_id = device->get_device_id();
device_info.set_name(StringRef(device->get_name()));
device_info.area_id = device->get_area_id();
}
#endif
#ifdef USE_AREAS
size_t area_index = 0;
for (auto const &area : App.get_areas()) {
resp.areas.emplace_back();
auto &area_info = resp.areas.back();
if (area_index >= ESPHOME_AREA_COUNT)
break;
auto &area_info = resp.areas[area_index++];
area_info.area_id = area->get_area_id();
area_info.set_name(StringRef(area->get_name()));
}
@@ -1513,6 +1516,7 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
return this->send_message(resp, DeviceInfoResponse::MESSAGE_TYPE);
}
#ifdef USE_API_HOMEASSISTANT_STATES
void APIConnection::on_home_assistant_state_response(const HomeAssistantStateResponse &msg) {
for (auto &it : this->parent_->get_state_subs()) {
if (it.entity_id == msg.entity_id && it.attribute.value() == msg.attribute) {
@@ -1520,6 +1524,7 @@ void APIConnection::on_home_assistant_state_response(const HomeAssistantStateRes
}
}
}
#endif
#ifdef USE_API_SERVICES
void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
bool found = false;
@@ -1535,25 +1540,26 @@ void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
#endif
#ifdef USE_API_NOISE
bool APIConnection::send_noise_encryption_set_key_response(const NoiseEncryptionSetKeyRequest &msg) {
psk_t psk{};
NoiseEncryptionSetKeyResponse resp;
resp.success = false;
psk_t psk{};
if (base64_decode(msg.key, psk.data(), msg.key.size()) != psk.size()) {
ESP_LOGW(TAG, "Invalid encryption key length");
resp.success = false;
return this->send_message(resp, NoiseEncryptionSetKeyResponse::MESSAGE_TYPE);
}
if (!this->parent_->save_noise_psk(psk, true)) {
} else if (!this->parent_->save_noise_psk(psk, true)) {
ESP_LOGW(TAG, "Failed to save encryption key");
resp.success = false;
return this->send_message(resp, NoiseEncryptionSetKeyResponse::MESSAGE_TYPE);
} else {
resp.success = true;
}
resp.success = true;
return this->send_message(resp, NoiseEncryptionSetKeyResponse::MESSAGE_TYPE);
}
#endif
#ifdef USE_API_HOMEASSISTANT_STATES
void APIConnection::subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) {
state_subs_at_ = 0;
}
#endif
bool APIConnection::try_to_clear_buffer(bool log_out_of_space) {
if (this->flags_.remove)
return false;
@@ -1563,8 +1569,7 @@ bool APIConnection::try_to_clear_buffer(bool log_out_of_space) {
APIError err = this->helper_->loop();
if (err != APIError::OK) {
on_fatal_error();
ESP_LOGW(TAG, "%s: Socket operation failed %s errno=%d", this->get_client_combined_info().c_str(),
api_error_to_str(err), errno);
this->log_socket_operation_failed_(err);
return false;
}
if (this->helper_->can_write_without_blocking())
@@ -1579,22 +1584,25 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) {
return false;
}
API_TIMING_START(write);
APIError err = this->helper_->write_protobuf_packet(message_type, buffer);
API_TIMING_END(write, WRITE_PACKET);
if (err == APIError::WOULD_BLOCK)
return false;
if (err != APIError::OK) {
on_fatal_error();
ESP_LOGW(TAG, "%s: Packet write failed %s errno=%d", this->get_client_combined_info().c_str(),
api_error_to_str(err), errno);
this->log_warning_("Packet write failed", err);
return false;
}
// Do not set last_traffic_ on send
return true;
}
#ifdef USE_API_PASSWORD
void APIConnection::on_unauthenticated_access() {
this->on_fatal_error();
ESP_LOGD(TAG, "%s access without authentication", this->get_client_combined_info().c_str());
}
#endif
void APIConnection::on_no_setup_connection() {
this->on_fatal_error();
ESP_LOGD(TAG, "%s access without full connection", this->get_client_combined_info().c_str());
@@ -1644,14 +1652,6 @@ bool APIConnection::schedule_batch_() {
return true;
}
ProtoWriteBuffer APIConnection::allocate_single_message_buffer(uint16_t size) { return this->create_buffer(size); }
ProtoWriteBuffer APIConnection::allocate_batch_message_buffer(uint16_t size) {
ProtoWriteBuffer result = this->prepare_message_buffer(size, this->flags_.batch_first_message);
this->flags_.batch_first_message = false;
return result;
}
void APIConnection::process_batch_() {
// Ensure PacketInfo remains trivially destructible for our placement new approach
static_assert(std::is_trivially_destructible<PacketInfo>::value,
@@ -1662,12 +1662,17 @@ void APIConnection::process_batch_() {
return;
}
API_TIMING_START(buffer_clear);
// Try to clear buffer first
if (!this->try_to_clear_buffer(true)) {
// Can't write now, we'll try again later
API_TIMING_END(buffer_clear, BUFFER_MANAGEMENT);
return;
}
API_TIMING_END(buffer_clear, BUFFER_MANAGEMENT);
// Get shared buffer reference once to avoid multiple calls
auto &shared_buf = this->parent_->get_shared_buffer_ref();
size_t num_items = this->deferred_batch_.size();
// Fast path for single message - allocate exact size needed
@@ -1675,11 +1680,12 @@ void APIConnection::process_batch_() {
const auto &item = this->deferred_batch_[0];
// Let the creator calculate size and encode if it fits
API_TIMING_START(single_encode);
uint16_t payload_size =
item.creator(item.entity, this, std::numeric_limits<uint16_t>::max(), true, item.message_type);
API_TIMING_END(single_encode, BATCH_ENCODE);
if (payload_size > 0 &&
this->send_buffer(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, item.message_type)) {
if (payload_size > 0 && this->send_buffer(ProtoWriteBuffer{&shared_buf}, item.message_type)) {
#ifdef HAS_PROTO_MESSAGE_DUMP
// Log messages after send attempt for VV debugging
// It's safe to use the buffer for logging at this point regardless of send result
@@ -1706,20 +1712,18 @@ void APIConnection::process_batch_() {
const uint8_t footer_size = this->helper_->frame_footer_size();
// Initialize buffer and tracking variables
this->parent_->get_shared_buffer_ref().clear();
shared_buf.clear();
// Pre-calculate exact buffer size needed based on message types
uint32_t total_estimated_size = 0;
uint32_t total_estimated_size = num_items * (header_padding + footer_size);
for (size_t i = 0; i < this->deferred_batch_.size(); i++) {
const auto &item = this->deferred_batch_[i];
total_estimated_size += item.estimated_size;
}
// Calculate total overhead for all messages
uint32_t total_overhead = (header_padding + footer_size) * num_items;
// Reserve based on estimated size (much more accurate than 24-byte worst-case)
this->parent_->get_shared_buffer_ref().reserve(total_estimated_size + total_overhead);
shared_buf.reserve(total_estimated_size);
this->flags_.batch_first_message = true;
size_t items_processed = 0;
@@ -1732,11 +1736,14 @@ void APIConnection::process_batch_() {
uint32_t current_offset = 0;
// Process items and encode directly to buffer (up to our limit)
API_TIMING_START(batch_encode_loop);
for (size_t i = 0; i < packets_to_process; i++) {
const auto &item = this->deferred_batch_[i];
// Try to encode message
// The creator will calculate overhead to determine if the message fits
API_TIMING_START(item_encode);
uint16_t payload_size = item.creator(item.entity, this, remaining_size, false, item.message_type);
API_TIMING_END(item_encode, BATCH_ENCODE);
if (payload_size == 0) {
// Message won't fit, stop processing
@@ -1760,9 +1767,10 @@ void APIConnection::process_batch_() {
}
remaining_size -= payload_size;
// Calculate where the next message's header padding will start
// Current buffer size + footer space (that prepare_message_buffer will add for this message)
current_offset = this->parent_->get_shared_buffer_ref().size() + footer_size;
// Current buffer size + footer space for this message
current_offset = shared_buf.size() + footer_size;
}
API_TIMING_END(batch_encode_loop, BATCH_ENCODE);
if (items_processed == 0) {
this->deferred_batch_.clear();
@@ -1771,17 +1779,17 @@ void APIConnection::process_batch_() {
// Add footer space for the last message (for Noise protocol MAC)
if (footer_size > 0) {
auto &shared_buf = this->parent_->get_shared_buffer_ref();
shared_buf.resize(shared_buf.size() + footer_size);
}
// Send all collected packets
APIError err = this->helper_->write_protobuf_packets(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()},
API_TIMING_START(batch_write);
APIError err = this->helper_->write_protobuf_packets(ProtoWriteBuffer{&shared_buf},
std::span<const PacketInfo>(packet_info, packet_count));
API_TIMING_END(batch_write, PROTO_WRITE);
if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
on_fatal_error();
ESP_LOGW(TAG, "%s: Batch write failed %s errno=%d", this->get_client_combined_info().c_str(), api_error_to_str(err),
errno);
this->log_warning_("Batch write failed", err);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
@@ -1837,6 +1845,33 @@ uint16_t APIConnection::try_send_ping_request(EntityBase *entity, APIConnection
return encode_message_to_buffer(req, PingRequest::MESSAGE_TYPE, conn, remaining_size, is_single);
}
} // namespace api
} // namespace esphome
#ifdef USE_API_HOMEASSISTANT_STATES
void APIConnection::process_state_subscriptions_() {
const auto &subs = this->parent_->get_state_subs();
if (this->state_subs_at_ >= static_cast<int>(subs.size())) {
this->state_subs_at_ = -1;
return;
}
const auto &it = subs[this->state_subs_at_];
SubscribeHomeAssistantStateResponse resp;
resp.set_entity_id(StringRef(it.entity_id));
// Avoid string copy by directly using the optional's value if it exists
resp.set_attribute(it.attribute.has_value() ? StringRef(it.attribute.value()) : StringRef(""));
resp.once = it.once;
if (this->send_message(resp, SubscribeHomeAssistantStateResponse::MESSAGE_TYPE)) {
this->state_subs_at_++;
}
}
#endif // USE_API_HOMEASSISTANT_STATES
void APIConnection::log_warning_(const char *message, APIError err) {
ESP_LOGW(TAG, "%s: %s %s errno=%d", this->get_client_combined_info().c_str(), message, api_error_to_str(err), errno);
}
void APIConnection::log_socket_operation_failed_(APIError err) { this->log_warning_("Socket operation failed", err); }
} // namespace esphome::api
#endif

View File

@@ -6,6 +6,7 @@
#include "api_pb2.h"
#include "api_pb2_service.h"
#include "api_server.h"
#include "api_timing.h"
#include "esphome/core/application.h"
#include "esphome/core/component.h"
#include "esphome/core/entity_base.h"
@@ -13,8 +14,7 @@
#include <vector>
#include <functional>
namespace esphome {
namespace api {
namespace esphome::api {
// Client information structure
struct ClientInfo {
@@ -45,7 +45,7 @@ static constexpr size_t MAX_PACKETS_PER_BATCH = 64; // ESP32 has 8KB+ stack, HO
static constexpr size_t MAX_PACKETS_PER_BATCH = 32; // ESP8266/RP2040/etc have smaller stacks
#endif
class APIConnection : public APIServerConnection {
class APIConnection final : public APIServerConnection {
public:
friend class APIServer;
friend class ListEntitiesIterator;
@@ -132,11 +132,13 @@ class APIConnection : public APIServerConnection {
void media_player_command(const MediaPlayerCommandRequest &msg) override;
#endif
bool try_send_log_message(int level, const char *tag, const char *line, size_t message_len);
#ifdef USE_API_HOMEASSISTANT_SERVICES
void send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
if (!this->flags_.service_call_subscription)
return;
this->send_message(call, HomeassistantServiceResponse::MESSAGE_TYPE);
}
#endif
#ifdef USE_BLUETOOTH_PROXY
void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
void unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) override;
@@ -189,7 +191,9 @@ class APIConnection : public APIServerConnection {
// we initiated ping
this->flags_.sent_ping = false;
}
#ifdef USE_API_HOMEASSISTANT_STATES
void on_home_assistant_state_response(const HomeAssistantStateResponse &msg) override;
#endif
#ifdef USE_HOMEASSISTANT_TIME
void on_get_time_response(const GetTimeResponse &value) override;
#endif
@@ -208,10 +212,14 @@ class APIConnection : public APIServerConnection {
if (msg.dump_config)
App.schedule_dump_config();
}
#ifdef USE_API_HOMEASSISTANT_SERVICES
void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) override {
this->flags_.service_call_subscription = true;
}
#endif
#ifdef USE_API_HOMEASSISTANT_STATES
void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) override;
#endif
bool send_get_time_response(const GetTimeRequest &msg) override;
#ifdef USE_API_SERVICES
void execute_service(const ExecuteServiceRequest &msg) override;
@@ -228,52 +236,38 @@ class APIConnection : public APIServerConnection {
this->is_authenticated();
}
uint8_t get_log_subscription_level() const { return this->flags_.log_subscription; }
// Get client API version for feature detection
bool client_supports_api_version(uint16_t major, uint16_t minor) const {
return this->client_api_version_major_ > major ||
(this->client_api_version_major_ == major && this->client_api_version_minor_ >= minor);
}
void on_fatal_error() override;
#ifdef USE_API_PASSWORD
void on_unauthenticated_access() override;
#endif
void on_no_setup_connection() override;
ProtoWriteBuffer create_buffer(uint32_t reserve_size) override {
// FIXME: ensure no recursive writes can happen
// Get header padding size - used for both reserve and insert
uint8_t header_padding = this->helper_->frame_header_padding();
// Get shared buffer from parent server
std::vector<uint8_t> &shared_buf = this->parent_->get_shared_buffer_ref();
this->prepare_first_message_buffer(shared_buf, header_padding,
reserve_size + header_padding + this->helper_->frame_footer_size());
return {&shared_buf};
}
void prepare_first_message_buffer(std::vector<uint8_t> &shared_buf, size_t header_padding, size_t total_size) {
shared_buf.clear();
// Reserve space for header padding + message + footer
// - Header padding: space for protocol headers (7 bytes for Noise, 6 for Plaintext)
// - Footer: space for MAC (16 bytes for Noise, 0 for Plaintext)
shared_buf.reserve(reserve_size + header_padding + this->helper_->frame_footer_size());
shared_buf.reserve(total_size);
// Resize to add header padding so message encoding starts at the correct position
shared_buf.resize(header_padding);
return {&shared_buf};
}
// Prepare buffer for next message in batch
ProtoWriteBuffer prepare_message_buffer(uint16_t message_size, bool is_first_message) {
// Get reference to shared buffer (it maintains state between batch messages)
std::vector<uint8_t> &shared_buf = this->parent_->get_shared_buffer_ref();
if (is_first_message) {
shared_buf.clear();
}
size_t current_size = shared_buf.size();
// Calculate padding to add:
// - First message: just header padding
// - Subsequent messages: footer for previous message + header padding for this message
size_t padding_to_add = is_first_message
? this->helper_->frame_header_padding()
: this->helper_->frame_header_padding() + this->helper_->frame_footer_size();
// Reserve space for padding + message
shared_buf.reserve(current_size + padding_to_add + message_size);
// Resize to add the padding bytes
shared_buf.resize(current_size + padding_to_add);
return {&shared_buf};
}
bool try_to_clear_buffer(bool log_out_of_space);
@@ -281,14 +275,14 @@ class APIConnection : public APIServerConnection {
std::string get_client_combined_info() const { return this->client_info_.get_combined_info(); }
// Buffer allocator methods for batch processing
ProtoWriteBuffer allocate_single_message_buffer(uint16_t size);
ProtoWriteBuffer allocate_batch_message_buffer(uint16_t size);
protected:
// Helper function to handle authentication completion
void complete_authentication_();
#ifdef USE_API_HOMEASSISTANT_STATES
void process_state_subscriptions_();
#endif
// Non-template helper to encode any ProtoMessage
static uint16_t encode_message_to_buffer(ProtoMessage &msg, uint8_t message_type, APIConnection *conn,
uint32_t remaining_size, bool is_single);
@@ -308,9 +302,15 @@ class APIConnection : public APIServerConnection {
APIConnection *conn, uint32_t remaining_size, bool is_single) {
// Set common fields that are shared by all entity types
msg.key = entity->get_object_id_hash();
// IMPORTANT: get_object_id() may return a temporary std::string
std::string object_id = entity->get_object_id();
msg.set_object_id(StringRef(object_id));
// Try to use static reference first to avoid allocation
StringRef static_ref = entity->get_object_id_ref_for_api_();
if (!static_ref.empty()) {
msg.set_object_id(static_ref);
} else {
// Dynamic case - need to allocate
std::string object_id = entity->get_object_id();
msg.set_object_id(StringRef(object_id));
}
if (entity->has_own_name()) {
msg.set_name(entity->get_name());
@@ -336,9 +336,11 @@ class APIConnection : public APIServerConnection {
// Helper method to process multiple entities from an iterator in a batch
template<typename Iterator> void process_iterator_batch_(Iterator &iterator) {
size_t initial_size = this->deferred_batch_.size();
API_TIMING_START(iter_advance);
while (!iterator.completed() && (this->deferred_batch_.size() - initial_size) < MAX_INITIAL_PER_BATCH) {
iterator.advance();
}
API_TIMING_END(iter_advance, ITERATOR_ENCODE);
// If the batch is full, process it immediately
// Note: iterator.advance() already calls schedule_batch_() via schedule_message_()
@@ -493,7 +495,9 @@ class APIConnection : public APIServerConnection {
// Group 4: 4-byte types
uint32_t last_traffic_;
#ifdef USE_API_HOMEASSISTANT_STATES
int state_subs_at_ = -1;
#endif
// Function pointer type for message encoding
using MessageCreatorPtr = uint16_t (*)(EntityBase *, APIConnection *, uint32_t remaining_size, bool is_single);
@@ -680,11 +684,18 @@ class APIConnection : public APIServerConnection {
// Helper method to send a message either immediately or via batching
bool send_message_smart_(EntityBase *entity, MessageCreatorPtr creator, uint8_t message_type,
uint8_t estimated_size) {
API_TIMING_GUARD(SEND_STATE);
// Try to send immediately if:
// 1. We should try to send immediately (should_try_send_immediately = true)
// 2. Batch delay is 0 (user has opted in to immediate sending)
// 3. Buffer has space available
if (this->flags_.should_try_send_immediately && this->get_batch_delay_ms_() == 0 &&
// 1. It's an UpdateStateResponse (always send immediately to handle cases where
// the main loop is blocked, e.g., during OTA updates)
// 2. OR: We should try to send immediately (should_try_send_immediately = true)
// AND Batch delay is 0 (user has opted in to immediate sending)
// 3. AND: Buffer has space available
if ((
#ifdef USE_UPDATE
message_type == UpdateStateResponse::MESSAGE_TYPE ||
#endif
(this->flags_.should_try_send_immediately && this->get_batch_delay_ms_() == 0)) &&
this->helper_->can_write_without_blocking()) {
// Now actually encode and send
if (creator(entity, this, MAX_BATCH_PACKET_SIZE, true) &&
@@ -721,8 +732,12 @@ class APIConnection : public APIServerConnection {
this->deferred_batch_.add_item_front(entity, MessageCreator(function_ptr), message_type, estimated_size);
return this->schedule_batch_();
}
// Helper function to log API errors with errno
void log_warning_(const char *message, APIError err);
// Specific helper for duplicated error message
void log_socket_operation_failed_(APIError err);
};
} // namespace api
} // namespace esphome
} // namespace esphome::api
#endif

View File

@@ -9,8 +9,7 @@
#include <cstring>
#include <cinttypes>
namespace esphome {
namespace api {
namespace esphome::api {
static const char *const TAG = "api.frame_helper";
@@ -157,7 +156,9 @@ APIError APIFrameHelper::write_raw_(const struct iovec *iov, int iovcnt, uint16_
}
// Try to send directly if no buffered data
ssize_t sent = this->socket_->writev(iov, iovcnt);
// Optimize for single iovec case (common for plaintext API)
ssize_t sent =
(iovcnt == 1) ? this->socket_->write(iov[0].iov_base, iov[0].iov_len) : this->socket_->writev(iov, iovcnt);
if (sent == -1) {
APIError err = this->handle_socket_write_error_();
@@ -247,6 +248,5 @@ APIError APIFrameHelper::handle_socket_read_result_(ssize_t received) {
return APIError::OK;
}
} // namespace api
} // namespace esphome
} // namespace esphome::api
#endif

View File

@@ -12,8 +12,7 @@
#include "esphome/core/application.h"
#include "esphome/core/log.h"
namespace esphome {
namespace api {
namespace esphome::api {
// uncomment to log raw packets
//#define HELPER_LOG_PACKETS
@@ -105,9 +104,9 @@ class APIFrameHelper {
// The buffer contains all messages with appropriate padding before each
virtual APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) = 0;
// Get the frame header padding required by this protocol
virtual uint8_t frame_header_padding() = 0;
uint8_t frame_header_padding() const { return frame_header_padding_; }
// Get the frame footer size required by this protocol
virtual uint8_t frame_footer_size() = 0;
uint8_t frame_footer_size() const { return frame_footer_size_; }
// Check if socket has data ready to read
bool is_socket_ready() const { return socket_ != nullptr && socket_->ready(); }
@@ -184,7 +183,6 @@ class APIFrameHelper {
APIError handle_socket_read_result_(ssize_t received);
};
} // namespace api
} // namespace esphome
} // namespace esphome::api
#endif // USE_API

View File

@@ -10,8 +10,7 @@
#include <cstring>
#include <cinttypes>
namespace esphome {
namespace api {
namespace esphome::api {
static const char *const TAG = "api.noise";
static const char *const PROLOGUE_INIT = "NoiseAPIInit";
@@ -579,7 +578,6 @@ void noise_rand_bytes(void *output, size_t len) {
}
}
} // namespace api
} // namespace esphome
} // namespace esphome::api
#endif // USE_API_NOISE
#endif // USE_API

View File

@@ -5,10 +5,9 @@
#include "noise/protocol.h"
#include "api_noise_context.h"
namespace esphome {
namespace api {
namespace esphome::api {
class APINoiseFrameHelper : public APIFrameHelper {
class APINoiseFrameHelper final : public APIFrameHelper {
public:
APINoiseFrameHelper(std::unique_ptr<socket::Socket> socket, std::shared_ptr<APINoiseContext> ctx,
const ClientInfo *client_info)
@@ -26,10 +25,6 @@ class APINoiseFrameHelper : public APIFrameHelper {
APIError read_packet(ReadPacketBuffer *buffer) override;
APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override;
APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) override;
// Get the frame header padding required by this protocol
uint8_t frame_header_padding() override { return frame_header_padding_; }
// Get the frame footer size required by this protocol
uint8_t frame_footer_size() override { return frame_footer_size_; }
protected:
APIError state_action_();
@@ -64,7 +59,6 @@ class APINoiseFrameHelper : public APIFrameHelper {
// 4 bytes total, no padding
};
} // namespace api
} // namespace esphome
} // namespace esphome::api
#endif // USE_API_NOISE
#endif // USE_API

View File

@@ -10,8 +10,7 @@
#include <cstring>
#include <cinttypes>
namespace esphome {
namespace api {
namespace esphome::api {
static const char *const TAG = "api.plaintext";
@@ -286,7 +285,6 @@ APIError APIPlaintextFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer
return write_raw_(this->reusable_iovs_.data(), this->reusable_iovs_.size(), total_write_len);
}
} // namespace api
} // namespace esphome
} // namespace esphome::api
#endif // USE_API_PLAINTEXT
#endif // USE_API

View File

@@ -3,10 +3,9 @@
#ifdef USE_API
#ifdef USE_API_PLAINTEXT
namespace esphome {
namespace api {
namespace esphome::api {
class APIPlaintextFrameHelper : public APIFrameHelper {
class APIPlaintextFrameHelper final : public APIFrameHelper {
public:
APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket, const ClientInfo *client_info)
: APIFrameHelper(std::move(socket), client_info) {
@@ -23,9 +22,6 @@ class APIPlaintextFrameHelper : public APIFrameHelper {
APIError read_packet(ReadPacketBuffer *buffer) override;
APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override;
APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) override;
uint8_t frame_header_padding() override { return frame_header_padding_; }
// Get the frame footer size required by this protocol
uint8_t frame_footer_size() override { return frame_footer_size_; }
protected:
APIError try_read_frame_(std::vector<uint8_t> *frame);
@@ -49,7 +45,6 @@ class APIPlaintextFrameHelper : public APIFrameHelper {
// 8 bytes total, no padding needed
};
} // namespace api
} // namespace esphome
} // namespace esphome::api
#endif // USE_API_PLAINTEXT
#endif // USE_API

View File

@@ -3,8 +3,7 @@
#include <cstdint>
#include "esphome/core/defines.h"
namespace esphome {
namespace api {
namespace esphome::api {
#ifdef USE_API_NOISE
using psk_t = std::array<uint8_t, 32>;
@@ -28,5 +27,4 @@ class APINoiseContext {
};
#endif // USE_API_NOISE
} // namespace api
} // namespace esphome
} // namespace esphome::api

View File

@@ -27,4 +27,34 @@ extend google.protobuf.MessageOptions {
extend google.protobuf.FieldOptions {
optional string field_ifdef = 1042;
optional uint32 fixed_array_size = 50007;
optional bool no_zero_copy = 50008 [default=false];
optional bool fixed_array_skip_zero = 50009 [default=false];
optional string fixed_array_size_define = 50010;
optional string fixed_array_with_length_define = 50011;
// container_pointer: Zero-copy optimization for repeated fields.
//
// When container_pointer is set on a repeated field, the generated message will
// store a pointer to an existing container instead of copying the data into the
// message's own repeated field. This eliminates heap allocations and improves performance.
//
// Requirements for safe usage:
// 1. The source container must remain valid until the message is encoded
// 2. Messages must be encoded immediately (which ESPHome does by default)
// 3. The container type must match the field type exactly
//
// Supported container types:
// - "std::vector<T>" for most repeated fields
// - "std::set<T>" for unique/sorted data
// - Full type specification required for enums (e.g., "std::set<climate::ClimateMode>")
//
// Example usage in .proto file:
// repeated string supported_modes = 12 [(container_pointer) = "std::set"];
// repeated ColorMode color_modes = 13 [(container_pointer) = "std::set<light::ColorMode>"];
//
// The corresponding C++ code must provide const reference access to a container
// that matches the specified type and remains valid during message encoding.
// This is typically done through methods returning const T& or special accessor
// methods like get_options() or supported_modes_for_api_().
optional string container_pointer = 50001;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -7,8 +7,7 @@
#ifdef HAS_PROTO_MESSAGE_DUMP
namespace esphome {
namespace api {
namespace esphome::api {
// Helper function to append a quoted string, handling empty StringRef
static inline void append_quoted_string(std::string &out, const StringRef &ref) {
@@ -384,6 +383,12 @@ template<> const char *proto_enum_to_string<enums::MediaPlayerState>(enums::Medi
return "MEDIA_PLAYER_STATE_PLAYING";
case enums::MEDIA_PLAYER_STATE_PAUSED:
return "MEDIA_PLAYER_STATE_PAUSED";
case enums::MEDIA_PLAYER_STATE_ANNOUNCING:
return "MEDIA_PLAYER_STATE_ANNOUNCING";
case enums::MEDIA_PLAYER_STATE_OFF:
return "MEDIA_PLAYER_STATE_OFF";
case enums::MEDIA_PLAYER_STATE_ON:
return "MEDIA_PLAYER_STATE_ON";
default:
return "UNKNOWN";
}
@@ -400,6 +405,24 @@ template<> const char *proto_enum_to_string<enums::MediaPlayerCommand>(enums::Me
return "MEDIA_PLAYER_COMMAND_MUTE";
case enums::MEDIA_PLAYER_COMMAND_UNMUTE:
return "MEDIA_PLAYER_COMMAND_UNMUTE";
case enums::MEDIA_PLAYER_COMMAND_TOGGLE:
return "MEDIA_PLAYER_COMMAND_TOGGLE";
case enums::MEDIA_PLAYER_COMMAND_VOLUME_UP:
return "MEDIA_PLAYER_COMMAND_VOLUME_UP";
case enums::MEDIA_PLAYER_COMMAND_VOLUME_DOWN:
return "MEDIA_PLAYER_COMMAND_VOLUME_DOWN";
case enums::MEDIA_PLAYER_COMMAND_ENQUEUE:
return "MEDIA_PLAYER_COMMAND_ENQUEUE";
case enums::MEDIA_PLAYER_COMMAND_REPEAT_ONE:
return "MEDIA_PLAYER_COMMAND_REPEAT_ONE";
case enums::MEDIA_PLAYER_COMMAND_REPEAT_OFF:
return "MEDIA_PLAYER_COMMAND_REPEAT_OFF";
case enums::MEDIA_PLAYER_COMMAND_CLEAR_PLAYLIST:
return "MEDIA_PLAYER_COMMAND_CLEAR_PLAYLIST";
case enums::MEDIA_PLAYER_COMMAND_TURN_ON:
return "MEDIA_PLAYER_COMMAND_TURN_ON";
case enums::MEDIA_PLAYER_COMMAND_TURN_OFF:
return "MEDIA_PLAYER_COMMAND_TURN_OFF";
default:
return "UNKNOWN";
}
@@ -815,7 +838,7 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const {
dump_field(out, "icon", this->icon_ref_);
#endif
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
for (const auto &it : this->supported_preset_modes) {
for (const auto &it : *this->supported_preset_modes) {
dump_field(out, "supported_preset_modes", it, 4);
}
#ifdef USE_DEVICES
@@ -858,7 +881,7 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const {
dump_field(out, "object_id", this->object_id_ref_);
dump_field(out, "key", this->key);
dump_field(out, "name", this->name_ref_);
for (const auto &it : this->supported_color_modes) {
for (const auto &it : *this->supported_color_modes) {
dump_field(out, "supported_color_modes", static_cast<enums::ColorMode>(it), 4);
}
dump_field(out, "min_mireds", this->min_mireds);
@@ -1039,13 +1062,14 @@ void NoiseEncryptionSetKeyRequest::dump_to(std::string &out) const {
}
void NoiseEncryptionSetKeyResponse::dump_to(std::string &out) const { dump_field(out, "success", this->success); }
#endif
#ifdef USE_API_HOMEASSISTANT_SERVICES
void SubscribeHomeassistantServicesRequest::dump_to(std::string &out) const {
out.append("SubscribeHomeassistantServicesRequest {}");
}
void HomeassistantServiceMap::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "HomeassistantServiceMap");
dump_field(out, "key", this->key_ref_);
dump_field(out, "value", this->value_ref_);
dump_field(out, "value", this->value);
}
void HomeassistantServiceResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "HomeassistantServiceResponse");
@@ -1067,6 +1091,8 @@ void HomeassistantServiceResponse::dump_to(std::string &out) const {
}
dump_field(out, "is_event", this->is_event);
}
#endif
#ifdef USE_API_HOMEASSISTANT_STATES
void SubscribeHomeAssistantStatesRequest::dump_to(std::string &out) const {
out.append("SubscribeHomeAssistantStatesRequest {}");
}
@@ -1082,6 +1108,7 @@ void HomeAssistantStateResponse::dump_to(std::string &out) const {
dump_field(out, "state", this->state);
dump_field(out, "attribute", this->attribute);
}
#endif
void GetTimeRequest::dump_to(std::string &out) const { out.append("GetTimeRequest {}"); }
void GetTimeResponse::dump_to(std::string &out) const { dump_field(out, "epoch_seconds", this->epoch_seconds); }
#ifdef USE_API_SERVICES
@@ -1170,26 +1197,26 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const {
dump_field(out, "name", this->name_ref_);
dump_field(out, "supports_current_temperature", this->supports_current_temperature);
dump_field(out, "supports_two_point_target_temperature", this->supports_two_point_target_temperature);
for (const auto &it : this->supported_modes) {
for (const auto &it : *this->supported_modes) {
dump_field(out, "supported_modes", static_cast<enums::ClimateMode>(it), 4);
}
dump_field(out, "visual_min_temperature", this->visual_min_temperature);
dump_field(out, "visual_max_temperature", this->visual_max_temperature);
dump_field(out, "visual_target_temperature_step", this->visual_target_temperature_step);
dump_field(out, "supports_action", this->supports_action);
for (const auto &it : this->supported_fan_modes) {
for (const auto &it : *this->supported_fan_modes) {
dump_field(out, "supported_fan_modes", static_cast<enums::ClimateFanMode>(it), 4);
}
for (const auto &it : this->supported_swing_modes) {
for (const auto &it : *this->supported_swing_modes) {
dump_field(out, "supported_swing_modes", static_cast<enums::ClimateSwingMode>(it), 4);
}
for (const auto &it : this->supported_custom_fan_modes) {
for (const auto &it : *this->supported_custom_fan_modes) {
dump_field(out, "supported_custom_fan_modes", it, 4);
}
for (const auto &it : this->supported_presets) {
for (const auto &it : *this->supported_presets) {
dump_field(out, "supported_presets", static_cast<enums::ClimatePreset>(it), 4);
}
for (const auto &it : this->supported_custom_presets) {
for (const auto &it : *this->supported_custom_presets) {
dump_field(out, "supported_custom_presets", it, 4);
}
dump_field(out, "disabled_by_default", this->disabled_by_default);
@@ -1302,7 +1329,7 @@ void ListEntitiesSelectResponse::dump_to(std::string &out) const {
#ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon_ref_);
#endif
for (const auto &it : this->options) {
for (const auto &it : *this->options) {
dump_field(out, "options", it, 4);
}
dump_field(out, "disabled_by_default", this->disabled_by_default);
@@ -1463,6 +1490,7 @@ void ListEntitiesMediaPlayerResponse::dump_to(std::string &out) const {
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
#endif
dump_field(out, "feature_flags", this->feature_flags);
}
void MediaPlayerStateResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "MediaPlayerStateResponse");
@@ -1506,9 +1534,9 @@ void BluetoothLERawAdvertisement::dump_to(std::string &out) const {
}
void BluetoothLERawAdvertisementsResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "BluetoothLERawAdvertisementsResponse");
for (const auto &it : this->advertisements) {
for (uint16_t i = 0; i < this->advertisements_len; i++) {
out.append(" advertisements: ");
it.dump_to(out);
this->advertisements[i].dump_to(out);
out.append("\n");
}
}
@@ -1533,6 +1561,7 @@ void BluetoothGATTDescriptor::dump_to(std::string &out) const {
dump_field(out, "uuid", it, 4);
}
dump_field(out, "handle", this->handle);
dump_field(out, "short_uuid", this->short_uuid);
}
void BluetoothGATTCharacteristic::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "BluetoothGATTCharacteristic");
@@ -1546,6 +1575,7 @@ void BluetoothGATTCharacteristic::dump_to(std::string &out) const {
it.dump_to(out);
out.append("\n");
}
dump_field(out, "short_uuid", this->short_uuid);
}
void BluetoothGATTService::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "BluetoothGATTService");
@@ -1558,6 +1588,7 @@ void BluetoothGATTService::dump_to(std::string &out) const {
it.dump_to(out);
out.append("\n");
}
dump_field(out, "short_uuid", this->short_uuid);
}
void BluetoothGATTGetServicesResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "BluetoothGATTGetServicesResponse");
@@ -1766,7 +1797,7 @@ void VoiceAssistantConfigurationResponse::dump_to(std::string &out) const {
it.dump_to(out);
out.append("\n");
}
for (const auto &it : this->active_wake_words) {
for (const auto &it : *this->active_wake_words) {
dump_field(out, "active_wake_words", it, 4);
}
dump_field(out, "max_active_wake_words", this->max_active_wake_words);
@@ -2067,7 +2098,6 @@ void UpdateCommandRequest::dump_to(std::string &out) const {
}
#endif
} // namespace api
} // namespace esphome
} // namespace esphome::api
#endif // HAS_PROTO_MESSAGE_DUMP

View File

@@ -0,0 +1,34 @@
#pragma once
#include "esphome/core/defines.h"
// This file provides includes needed by the generated protobuf code
// when using pointer optimizations for component-specific types
#ifdef USE_CLIMATE
#include "esphome/components/climate/climate_mode.h"
#include "esphome/components/climate/climate_traits.h"
#endif
#ifdef USE_LIGHT
#include "esphome/components/light/light_traits.h"
#endif
#ifdef USE_FAN
#include "esphome/components/fan/fan_traits.h"
#endif
#ifdef USE_SELECT
#include "esphome/components/select/select_traits.h"
#endif
// Standard library includes that might be needed
#include <set>
#include <vector>
#include <string>
namespace esphome::api {
// This file only provides includes, no actual code
} // namespace esphome::api

View File

@@ -1,10 +1,10 @@
// This file was automatically generated with a tool.
// See script/api_protobuf/api_protobuf.py
#include "api_pb2_service.h"
#include "api_timing.h"
#include "esphome/core/log.h"
namespace esphome {
namespace api {
namespace esphome::api {
static const char *const TAG = "api.service";
@@ -15,14 +15,18 @@ void APIServerConnectionBase::log_send_message_(const char *name, const std::str
#endif
void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {
API_TIMING_START(msg_decode);
switch (msg_type) {
case HelloRequest::MESSAGE_TYPE: {
HelloRequest msg;
msg.decode(msg_data, msg_size);
API_TIMING_END(msg_decode, PROTO_READ);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_hello_request: %s", msg.dump().c_str());
#endif
API_TIMING_START(msg_handle);
this->on_hello_request(msg);
API_TIMING_END(msg_handle, MESSAGE_DISPATCH);
break;
}
case ConnectRequest::MESSAGE_TYPE: {
@@ -36,7 +40,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
}
case DisconnectRequest::MESSAGE_TYPE: {
DisconnectRequest msg;
msg.decode(msg_data, msg_size);
// Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_disconnect_request: %s", msg.dump().c_str());
#endif
@@ -45,7 +49,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
}
case DisconnectResponse::MESSAGE_TYPE: {
DisconnectResponse msg;
msg.decode(msg_data, msg_size);
// Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_disconnect_response: %s", msg.dump().c_str());
#endif
@@ -54,7 +58,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
}
case PingRequest::MESSAGE_TYPE: {
PingRequest msg;
msg.decode(msg_data, msg_size);
// Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_ping_request: %s", msg.dump().c_str());
#endif
@@ -63,7 +67,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
}
case PingResponse::MESSAGE_TYPE: {
PingResponse msg;
msg.decode(msg_data, msg_size);
// Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_ping_response: %s", msg.dump().c_str());
#endif
@@ -72,7 +76,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
}
case DeviceInfoRequest::MESSAGE_TYPE: {
DeviceInfoRequest msg;
msg.decode(msg_data, msg_size);
// Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_device_info_request: %s", msg.dump().c_str());
#endif
@@ -81,7 +85,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
}
case ListEntitiesRequest::MESSAGE_TYPE: {
ListEntitiesRequest msg;
msg.decode(msg_data, msg_size);
// Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_list_entities_request: %s", msg.dump().c_str());
#endif
@@ -90,7 +94,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
}
case SubscribeStatesRequest::MESSAGE_TYPE: {
SubscribeStatesRequest msg;
msg.decode(msg_data, msg_size);
// Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_subscribe_states_request: %s", msg.dump().c_str());
#endif
@@ -150,18 +154,20 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
break;
}
#endif
#ifdef USE_API_HOMEASSISTANT_SERVICES
case SubscribeHomeassistantServicesRequest::MESSAGE_TYPE: {
SubscribeHomeassistantServicesRequest msg;
msg.decode(msg_data, msg_size);
// Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_subscribe_homeassistant_services_request: %s", msg.dump().c_str());
#endif
this->on_subscribe_homeassistant_services_request(msg);
break;
}
#endif
case GetTimeRequest::MESSAGE_TYPE: {
GetTimeRequest msg;
msg.decode(msg_data, msg_size);
// Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_get_time_request: %s", msg.dump().c_str());
#endif
@@ -177,15 +183,18 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
this->on_get_time_response(msg);
break;
}
#ifdef USE_API_HOMEASSISTANT_STATES
case SubscribeHomeAssistantStatesRequest::MESSAGE_TYPE: {
SubscribeHomeAssistantStatesRequest msg;
msg.decode(msg_data, msg_size);
// Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_subscribe_home_assistant_states_request: %s", msg.dump().c_str());
#endif
this->on_subscribe_home_assistant_states_request(msg);
break;
}
#endif
#ifdef USE_API_HOMEASSISTANT_STATES
case HomeAssistantStateResponse::MESSAGE_TYPE: {
HomeAssistantStateResponse msg;
msg.decode(msg_data, msg_size);
@@ -195,6 +204,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
this->on_home_assistant_state_response(msg);
break;
}
#endif
#ifdef USE_API_SERVICES
case ExecuteServiceRequest::MESSAGE_TYPE: {
ExecuteServiceRequest msg;
@@ -385,7 +395,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
#ifdef USE_BLUETOOTH_PROXY
case SubscribeBluetoothConnectionsFreeRequest::MESSAGE_TYPE: {
SubscribeBluetoothConnectionsFreeRequest msg;
msg.decode(msg_data, msg_size);
// Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_subscribe_bluetooth_connections_free_request: %s", msg.dump().c_str());
#endif
@@ -396,7 +406,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
#ifdef USE_BLUETOOTH_PROXY
case UnsubscribeBluetoothLEAdvertisementsRequest::MESSAGE_TYPE: {
UnsubscribeBluetoothLEAdvertisementsRequest msg;
msg.decode(msg_data, msg_size);
// Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_unsubscribe_bluetooth_le_advertisements_request: %s", msg.dump().c_str());
#endif
@@ -550,7 +560,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
#ifdef USE_VOICE_ASSISTANT
case VoiceAssistantConfigurationRequest::MESSAGE_TYPE: {
VoiceAssistantConfigurationRequest msg;
msg.decode(msg_data, msg_size);
// Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_voice_assistant_configuration_request: %s", msg.dump().c_str());
#endif
@@ -636,17 +646,21 @@ void APIServerConnection::on_subscribe_logs_request(const SubscribeLogsRequest &
this->subscribe_logs(msg);
}
}
#ifdef USE_API_HOMEASSISTANT_SERVICES
void APIServerConnection::on_subscribe_homeassistant_services_request(
const SubscribeHomeassistantServicesRequest &msg) {
if (this->check_authenticated_()) {
this->subscribe_homeassistant_services(msg);
}
}
#endif
#ifdef USE_API_HOMEASSISTANT_STATES
void APIServerConnection::on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) {
if (this->check_authenticated_()) {
this->subscribe_home_assistant_states(msg);
}
}
#endif
void APIServerConnection::on_get_time_request(const GetTimeRequest &msg) {
if (this->check_connection_setup_() && !this->send_get_time_response(msg)) {
this->on_fatal_error();
@@ -901,5 +915,4 @@ void APIServerConnection::on_alarm_control_panel_command_request(const AlarmCont
}
#endif
} // namespace api
} // namespace esphome
} // namespace esphome::api

View File

@@ -6,8 +6,7 @@
#include "api_pb2.h"
namespace esphome {
namespace api {
namespace esphome::api {
class APIServerConnectionBase : public ProtoService {
public:
@@ -61,11 +60,17 @@ class APIServerConnectionBase : public ProtoService {
virtual void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &value){};
#endif
#ifdef USE_API_HOMEASSISTANT_SERVICES
virtual void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &value){};
#endif
#ifdef USE_API_HOMEASSISTANT_STATES
virtual void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &value){};
#endif
#ifdef USE_API_HOMEASSISTANT_STATES
virtual void on_home_assistant_state_response(const HomeAssistantStateResponse &value){};
#endif
virtual void on_get_time_request(const GetTimeRequest &value){};
virtual void on_get_time_response(const GetTimeResponse &value){};
@@ -215,8 +220,12 @@ class APIServerConnection : public APIServerConnectionBase {
virtual void list_entities(const ListEntitiesRequest &msg) = 0;
virtual void subscribe_states(const SubscribeStatesRequest &msg) = 0;
virtual void subscribe_logs(const SubscribeLogsRequest &msg) = 0;
#ifdef USE_API_HOMEASSISTANT_SERVICES
virtual void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) = 0;
#endif
#ifdef USE_API_HOMEASSISTANT_STATES
virtual void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) = 0;
#endif
virtual bool send_get_time_response(const GetTimeRequest &msg) = 0;
#ifdef USE_API_SERVICES
virtual void execute_service(const ExecuteServiceRequest &msg) = 0;
@@ -333,8 +342,12 @@ class APIServerConnection : public APIServerConnectionBase {
void on_list_entities_request(const ListEntitiesRequest &msg) override;
void on_subscribe_states_request(const SubscribeStatesRequest &msg) override;
void on_subscribe_logs_request(const SubscribeLogsRequest &msg) override;
#ifdef USE_API_HOMEASSISTANT_SERVICES
void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &msg) override;
#endif
#ifdef USE_API_HOMEASSISTANT_STATES
void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) override;
#endif
void on_get_time_request(const GetTimeRequest &msg) override;
#ifdef USE_API_SERVICES
void on_execute_service_request(const ExecuteServiceRequest &msg) override;
@@ -444,5 +457,4 @@ class APIServerConnection : public APIServerConnectionBase {
#endif
};
} // namespace api
} // namespace esphome
} // namespace esphome::api

View File

@@ -2,6 +2,7 @@
#ifdef USE_API
#include <cerrno>
#include "api_connection.h"
#include "api_timing.h"
#include "esphome/components/network/util.h"
#include "esphome/core/application.h"
#include "esphome/core/defines.h"
@@ -16,8 +17,7 @@
#include <algorithm>
namespace esphome {
namespace api {
namespace esphome::api {
static const char *const TAG = "api";
@@ -136,6 +136,7 @@ void APIServer::schedule_reboot_timeout_() {
void APIServer::loop() {
// Accept new clients only if the socket exists and has incoming connections
if (this->socket_ && this->socket_->ready()) {
API_TIMING_GUARD(ACCEPT_CLIENT);
while (true) {
struct sockaddr_storage source_addr;
socklen_t addr_len = sizeof(source_addr);
@@ -162,6 +163,7 @@ void APIServer::loop() {
// Process clients and remove disconnected ones in a single pass
// Check network connectivity once for all clients
API_TIMING_START(network_check);
if (!network::is_connected()) {
// Network is down - disconnect all clients
for (auto &client : this->clients_) {
@@ -170,6 +172,7 @@ void APIServer::loop() {
}
// Continue to process and clean up the clients below
}
API_TIMING_END(network_check, NETWORK_CHECK);
size_t client_index = 0;
while (client_index < this->clients_.size()) {
@@ -177,12 +180,15 @@ void APIServer::loop() {
if (!client->flags_.remove) {
// Common case: process active client
API_TIMING_START(client_loop);
client->loop();
API_TIMING_END(client_loop, CLIENT_LOOP);
client_index++;
continue;
}
// Rare case: handle disconnection
API_TIMING_GUARD(CLIENT_REMOVAL);
#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER
this->client_disconnected_trigger_->trigger(client->client_info_.name, client->client_info_.peername);
#endif
@@ -370,12 +376,15 @@ void APIServer::set_password(const std::string &password) { this->password_ = pa
void APIServer::set_batch_delay(uint16_t batch_delay) { this->batch_delay_ = batch_delay; }
#ifdef USE_API_HOMEASSISTANT_SERVICES
void APIServer::send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
for (auto &client : this->clients_) {
client->send_homeassistant_service_call(call);
}
}
#endif
#ifdef USE_API_HOMEASSISTANT_STATES
void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(std::string)> f) {
this->state_subs_.push_back(HomeAssistantStateSubscription{
@@ -399,6 +408,7 @@ void APIServer::get_home_assistant_state(std::string entity_id, optional<std::st
const std::vector<APIServer::HomeAssistantStateSubscription> &APIServer::get_state_subs() const {
return this->state_subs_;
}
#endif
uint16_t APIServer::get_port() const { return this->port_; }
@@ -483,6 +493,5 @@ bool APIServer::teardown() {
return this->clients_.empty();
}
} // namespace api
} // namespace esphome
} // namespace esphome::api
#endif

View File

@@ -18,8 +18,7 @@
#include <vector>
namespace esphome {
namespace api {
namespace esphome::api {
#ifdef USE_API_NOISE
struct SavedNoisePsk {
@@ -107,7 +106,9 @@ class APIServer : public Component, public Controller {
#ifdef USE_MEDIA_PLAYER
void on_media_player_update(media_player::MediaPlayer *obj) override;
#endif
#ifdef USE_API_HOMEASSISTANT_SERVICES
void send_homeassistant_service_call(const HomeassistantServiceResponse &call);
#endif
#ifdef USE_API_SERVICES
void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }
#endif
@@ -127,6 +128,7 @@ class APIServer : public Component, public Controller {
bool is_connected() const;
#ifdef USE_API_HOMEASSISTANT_STATES
struct HomeAssistantStateSubscription {
std::string entity_id;
optional<std::string> attribute;
@@ -139,6 +141,7 @@ class APIServer : public Component, public Controller {
void get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(std::string)> f);
const std::vector<HomeAssistantStateSubscription> &get_state_subs() const;
#endif
#ifdef USE_API_SERVICES
const std::vector<UserServiceDescriptor *> &get_user_services() const { return this->user_services_; }
#endif
@@ -172,7 +175,9 @@ class APIServer : public Component, public Controller {
std::string password_;
#endif
std::vector<uint8_t> shared_write_buffer_; // Shared proto write buffer for all connections
#ifdef USE_API_HOMEASSISTANT_STATES
std::vector<HomeAssistantStateSubscription> state_subs_;
#endif
#ifdef USE_API_SERVICES
std::vector<UserServiceDescriptor *> user_services_;
#endif
@@ -196,6 +201,5 @@ template<typename... Ts> class APIConnectedCondition : public Condition<Ts...> {
bool check(Ts... x) override { return global_api_server->is_connected(); }
};
} // namespace api
} // namespace esphome
} // namespace esphome::api
#endif

View File

@@ -14,6 +14,8 @@ with warnings.catch_warnings():
from aioesphomeapi import APIClient, parse_log_message
from aioesphomeapi.log_runner import async_run
import contextlib
from esphome.const import CONF_KEY, CONF_PASSWORD, CONF_PORT, __version__
from esphome.core import CORE
@@ -28,7 +30,7 @@ if TYPE_CHECKING:
_LOGGER = logging.getLogger(__name__)
async def async_run_logs(config: dict[str, Any], address: str) -> None:
async def async_run_logs(config: dict[str, Any], addresses: list[str]) -> None:
"""Run the logs command in the event loop."""
conf = config["api"]
name = config["esphome"]["name"]
@@ -37,13 +39,21 @@ async def async_run_logs(config: dict[str, Any], address: str) -> None:
noise_psk: str | None = None
if (encryption := conf.get(CONF_ENCRYPTION)) and (key := encryption.get(CONF_KEY)):
noise_psk = key
_LOGGER.info("Starting log output from %s using esphome API", address)
if len(addresses) == 1:
_LOGGER.info("Starting log output from %s using esphome API", addresses[0])
else:
_LOGGER.info(
"Starting log output from %s using esphome API", " or ".join(addresses)
)
cli = APIClient(
address,
addresses[0], # Primary address for compatibility
port,
password,
client_info=f"ESPHome Logs {__version__}",
noise_psk=noise_psk,
addresses=addresses, # Pass all addresses for automatic retry
)
dashboard = CORE.dashboard
@@ -64,9 +74,7 @@ async def async_run_logs(config: dict[str, Any], address: str) -> None:
await stop()
def run_logs(config: dict[str, Any], address: str) -> None:
def run_logs(config: dict[str, Any], addresses: list[str]) -> None:
"""Run the logs command."""
try:
asyncio.run(async_run_logs(config, address))
except KeyboardInterrupt:
pass
with contextlib.suppress(KeyboardInterrupt):
asyncio.run(async_run_logs(config, addresses))

View File

@@ -6,8 +6,7 @@
#ifdef USE_API_SERVICES
#include "user_services.h"
#endif
namespace esphome {
namespace api {
namespace esphome::api {
#ifdef USE_API_SERVICES
template<typename T, typename... Ts> class CustomAPIDeviceService : public UserServiceBase<Ts...> {
@@ -57,6 +56,14 @@ class CustomAPIDevice {
auto *service = new CustomAPIDeviceService<T, Ts...>(name, arg_names, (T *) this, callback); // NOLINT
global_api_server->register_user_service(service);
}
#else
template<typename T, typename... Ts>
void register_service(void (T::*callback)(Ts...), const std::string &name,
const std::array<std::string, sizeof...(Ts)> &arg_names) {
static_assert(
sizeof(T) == 0,
"register_service() requires 'custom_services: true' in the 'api:' section of your YAML configuration");
}
#endif
/** Register a custom native API service that will show up in Home Assistant.
@@ -82,8 +89,15 @@ class CustomAPIDevice {
auto *service = new CustomAPIDeviceService<T>(name, {}, (T *) this, callback); // NOLINT
global_api_server->register_user_service(service);
}
#else
template<typename T> void register_service(void (T::*callback)(), const std::string &name) {
static_assert(
sizeof(T) == 0,
"register_service() requires 'custom_services: true' in the 'api:' section of your YAML configuration");
}
#endif
#ifdef USE_API_HOMEASSISTANT_STATES
/** Subscribe to the state (or attribute state) of an entity from Home Assistant.
*
* Usage:
@@ -135,7 +149,25 @@ class CustomAPIDevice {
auto f = std::bind(callback, (T *) this, entity_id, std::placeholders::_1);
global_api_server->subscribe_home_assistant_state(entity_id, optional<std::string>(attribute), f);
}
#else
template<typename T>
void subscribe_homeassistant_state(void (T::*callback)(std::string), const std::string &entity_id,
const std::string &attribute = "") {
static_assert(sizeof(T) == 0,
"subscribe_homeassistant_state() requires 'homeassistant_states: true' in the 'api:' section "
"of your YAML configuration");
}
template<typename T>
void subscribe_homeassistant_state(void (T::*callback)(std::string, std::string), const std::string &entity_id,
const std::string &attribute = "") {
static_assert(sizeof(T) == 0,
"subscribe_homeassistant_state() requires 'homeassistant_states: true' in the 'api:' section "
"of your YAML configuration");
}
#endif
#ifdef USE_API_HOMEASSISTANT_SERVICES
/** Call a Home Assistant service from ESPHome.
*
* Usage:
@@ -173,7 +205,7 @@ class CustomAPIDevice {
resp.data.emplace_back();
auto &kv = resp.data.back();
kv.set_key(StringRef(it.first));
kv.set_value(StringRef(it.second));
kv.value = it.second;
}
global_api_server->send_homeassistant_service_call(resp);
}
@@ -216,12 +248,34 @@ class CustomAPIDevice {
resp.data.emplace_back();
auto &kv = resp.data.back();
kv.set_key(StringRef(it.first));
kv.set_value(StringRef(it.second));
kv.value = it.second;
}
global_api_server->send_homeassistant_service_call(resp);
}
#else
template<typename T = void> void call_homeassistant_service(const std::string &service_name) {
static_assert(sizeof(T) == 0, "call_homeassistant_service() requires 'homeassistant_services: true' in the 'api:' "
"section of your YAML configuration");
}
template<typename T = void>
void call_homeassistant_service(const std::string &service_name, const std::map<std::string, std::string> &data) {
static_assert(sizeof(T) == 0, "call_homeassistant_service() requires 'homeassistant_services: true' in the 'api:' "
"section of your YAML configuration");
}
template<typename T = void> void fire_homeassistant_event(const std::string &event_name) {
static_assert(sizeof(T) == 0, "fire_homeassistant_event() requires 'homeassistant_services: true' in the 'api:' "
"section of your YAML configuration");
}
template<typename T = void>
void fire_homeassistant_event(const std::string &service_name, const std::map<std::string, std::string> &data) {
static_assert(sizeof(T) == 0, "fire_homeassistant_event() requires 'homeassistant_services: true' in the 'api:' "
"section of your YAML configuration");
}
#endif
};
} // namespace api
} // namespace esphome
} // namespace esphome::api
#endif

View File

@@ -2,13 +2,13 @@
#include "api_server.h"
#ifdef USE_API
#ifdef USE_API_HOMEASSISTANT_SERVICES
#include "api_pb2.h"
#include "esphome/core/automation.h"
#include "esphome/core/helpers.h"
#include <vector>
namespace esphome {
namespace api {
namespace esphome::api {
template<typename... X> class TemplatableStringValue : public TemplatableValue<std::string, X...> {
private:
@@ -36,6 +36,9 @@ template<typename... X> class TemplatableStringValue : public TemplatableValue<s
template<typename... Ts> class TemplatableKeyValuePair {
public:
// Keys are always string literals from YAML dictionary keys (e.g., "code", "event")
// and never templatable values or lambdas. Only the value parameter can be a lambda/template.
// Using pass-by-value with std::move allows optimal performance for both lvalues and rvalues.
template<typename T> TemplatableKeyValuePair(std::string key, T value) : key(std::move(key)), value(value) {}
std::string key;
TemplatableStringValue<Ts...> value;
@@ -47,11 +50,16 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
template<typename T> void set_service(T service) { this->service_ = service; }
template<typename T> void add_data(std::string key, T value) { this->data_.emplace_back(key, value); }
// Keys are always string literals from the Python code generation (e.g., cg.add(var.add_data("tag_id", templ))).
// The value parameter can be a lambda/template, but keys are never templatable.
// Using pass-by-value allows the compiler to optimize for both lvalues and rvalues.
template<typename T> void add_data(std::string key, T value) { this->data_.emplace_back(std::move(key), value); }
template<typename T> void add_data_template(std::string key, T value) {
this->data_template_.emplace_back(key, value);
this->data_template_.emplace_back(std::move(key), value);
}
template<typename T> void add_variable(std::string key, T value) {
this->variables_.emplace_back(std::move(key), value);
}
template<typename T> void add_variable(std::string key, T value) { this->variables_.emplace_back(key, value); }
void play(Ts... x) override {
HomeassistantServiceResponse resp;
@@ -62,22 +70,19 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
resp.data.emplace_back();
auto &kv = resp.data.back();
kv.set_key(StringRef(it.key));
std::string value = it.value.value(x...);
kv.set_value(StringRef(value));
kv.value = it.value.value(x...);
}
for (auto &it : this->data_template_) {
resp.data_template.emplace_back();
auto &kv = resp.data_template.back();
kv.set_key(StringRef(it.key));
std::string value = it.value.value(x...);
kv.set_value(StringRef(value));
kv.value = it.value.value(x...);
}
for (auto &it : this->variables_) {
resp.variables.emplace_back();
auto &kv = resp.variables.back();
kv.set_key(StringRef(it.key));
std::string value = it.value.value(x...);
kv.set_value(StringRef(value));
kv.value = it.value.value(x...);
}
this->parent_->send_homeassistant_service_call(resp);
}
@@ -91,6 +96,6 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
std::vector<TemplatableKeyValuePair<Ts...>> variables_;
};
} // namespace api
} // namespace esphome
} // namespace esphome::api
#endif
#endif

View File

@@ -6,8 +6,7 @@
#include "esphome/core/log.h"
#include "esphome/core/util.h"
namespace esphome {
namespace api {
namespace esphome::api {
// Generate entity handler implementations using macros
#ifdef USE_BINARY_SENSOR
@@ -90,6 +89,5 @@ bool ListEntitiesIterator::on_service(UserServiceDescriptor *service) {
}
#endif
} // namespace api
} // namespace esphome
} // namespace esphome::api
#endif

View File

@@ -4,8 +4,7 @@
#ifdef USE_API
#include "esphome/core/component.h"
#include "esphome/core/component_iterator.h"
namespace esphome {
namespace api {
namespace esphome::api {
class APIConnection;
@@ -96,6 +95,5 @@ class ListEntitiesIterator : public ComponentIterator {
APIConnection *client_;
};
} // namespace api
} // namespace esphome
} // namespace esphome::api
#endif

View File

@@ -3,80 +3,75 @@
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace api {
namespace esphome::api {
static const char *const TAG = "api.proto";
void ProtoDecodableMessage::decode(const uint8_t *buffer, size_t length) {
uint32_t i = 0;
bool error = false;
while (i < length) {
const uint8_t *ptr = buffer;
const uint8_t *end = buffer + length;
while (ptr < end) {
uint32_t consumed;
auto res = ProtoVarInt::parse(&buffer[i], length - i, &consumed);
// Parse field header
auto res = ProtoVarInt::parse(ptr, end - ptr, &consumed);
if (!res.has_value()) {
ESP_LOGV(TAG, "Invalid field start at %" PRIu32, i);
break;
ESP_LOGV(TAG, "Invalid field start at offset %ld", (long) (ptr - buffer));
return;
}
uint32_t field_type = (res->as_uint32()) & 0b111;
uint32_t field_id = (res->as_uint32()) >> 3;
i += consumed;
uint32_t tag = res->as_uint32();
uint32_t field_type = tag & 0b111;
uint32_t field_id = tag >> 3;
ptr += consumed;
switch (field_type) {
case 0: { // VarInt
res = ProtoVarInt::parse(&buffer[i], length - i, &consumed);
res = ProtoVarInt::parse(ptr, end - ptr, &consumed);
if (!res.has_value()) {
ESP_LOGV(TAG, "Invalid VarInt at %" PRIu32, i);
error = true;
break;
ESP_LOGV(TAG, "Invalid VarInt at offset %ld", (long) (ptr - buffer));
return;
}
if (!this->decode_varint(field_id, *res)) {
ESP_LOGV(TAG, "Cannot decode VarInt field %" PRIu32 " with value %" PRIu32 "!", field_id, res->as_uint32());
}
i += consumed;
ptr += consumed;
break;
}
case 2: { // Length-delimited
res = ProtoVarInt::parse(&buffer[i], length - i, &consumed);
res = ProtoVarInt::parse(ptr, end - ptr, &consumed);
if (!res.has_value()) {
ESP_LOGV(TAG, "Invalid Length Delimited at %" PRIu32, i);
error = true;
break;
ESP_LOGV(TAG, "Invalid Length Delimited at offset %ld", (long) (ptr - buffer));
return;
}
uint32_t field_length = res->as_uint32();
i += consumed;
if (field_length > length - i) {
ESP_LOGV(TAG, "Out-of-bounds Length Delimited at %" PRIu32, i);
error = true;
break;
ptr += consumed;
if (ptr + field_length > end) {
ESP_LOGV(TAG, "Out-of-bounds Length Delimited at offset %ld", (long) (ptr - buffer));
return;
}
if (!this->decode_length(field_id, ProtoLengthDelimited(&buffer[i], field_length))) {
if (!this->decode_length(field_id, ProtoLengthDelimited(ptr, field_length))) {
ESP_LOGV(TAG, "Cannot decode Length Delimited field %" PRIu32 "!", field_id);
}
i += field_length;
ptr += field_length;
break;
}
case 5: { // 32-bit
if (length - i < 4) {
ESP_LOGV(TAG, "Out-of-bounds Fixed32-bit at %" PRIu32, i);
error = true;
break;
if (ptr + 4 > end) {
ESP_LOGV(TAG, "Out-of-bounds Fixed32-bit at offset %ld", (long) (ptr - buffer));
return;
}
uint32_t val = encode_uint32(buffer[i + 3], buffer[i + 2], buffer[i + 1], buffer[i]);
uint32_t val = encode_uint32(ptr[3], ptr[2], ptr[1], ptr[0]);
if (!this->decode_32bit(field_id, Proto32Bit(val))) {
ESP_LOGV(TAG, "Cannot decode 32-bit field %" PRIu32 " with value %" PRIu32 "!", field_id, val);
}
i += 4;
ptr += 4;
break;
}
default:
ESP_LOGV(TAG, "Invalid field type at %" PRIu32, i);
error = true;
break;
}
if (error) {
break;
ESP_LOGV(TAG, "Invalid field type %u at offset %ld", field_type, (long) (ptr - buffer));
return;
}
}
}
@@ -89,5 +84,4 @@ std::string ProtoMessage::dump() const {
}
#endif
} // namespace api
} // namespace esphome
} // namespace esphome::api

View File

@@ -13,8 +13,24 @@
#define HAS_PROTO_MESSAGE_DUMP
#endif
namespace esphome {
namespace api {
namespace esphome::api {
// Helper functions for ZigZag encoding/decoding
inline constexpr uint32_t encode_zigzag32(int32_t value) {
return (static_cast<uint32_t>(value) << 1) ^ (static_cast<uint32_t>(value >> 31));
}
inline constexpr uint64_t encode_zigzag64(int64_t value) {
return (static_cast<uint64_t>(value) << 1) ^ (static_cast<uint64_t>(value >> 63));
}
inline constexpr int32_t decode_zigzag32(uint32_t value) {
return (value & 1) ? static_cast<int32_t>(~(value >> 1)) : static_cast<int32_t>(value >> 1);
}
inline constexpr int64_t decode_zigzag64(uint64_t value) {
return (value & 1) ? static_cast<int64_t>(~(value >> 1)) : static_cast<int64_t>(value >> 1);
}
/*
* StringRef Ownership Model for API Protocol Messages
@@ -36,11 +52,10 @@ namespace api {
*
* Unsafe Patterns (WILL cause crashes/corruption):
* 1. Temporaries: msg.set_field(StringRef(obj.get_string())) // get_string() returns by value
* 2. Optional values: msg.set_field(StringRef(optional.value())) // value() returns a copy
* 3. Concatenation: msg.set_field(StringRef(str1 + str2)) // Result is temporary
* 2. Concatenation: msg.set_field(StringRef(str1 + str2)) // Result is temporary
*
* For unsafe patterns, store in a local variable first:
* std::string temp = optional.value(); // or get_string() or str1 + str2
* std::string temp = get_string(); // or str1 + str2
* msg.set_field(StringRef(temp));
*
* The send_*_response pattern ensures proper lifetime management by encoding
@@ -89,33 +104,25 @@ class ProtoVarInt {
return {}; // Incomplete or invalid varint
}
uint16_t as_uint16() const { return this->value_; }
uint32_t as_uint32() const { return this->value_; }
uint64_t as_uint64() const { return this->value_; }
bool as_bool() const { return this->value_; }
int32_t as_int32() const {
constexpr uint16_t as_uint16() const { return this->value_; }
constexpr uint32_t as_uint32() const { return this->value_; }
constexpr uint64_t as_uint64() const { return this->value_; }
constexpr bool as_bool() const { return this->value_; }
constexpr int32_t as_int32() const {
// Not ZigZag encoded
return static_cast<int32_t>(this->as_int64());
}
int64_t as_int64() const {
constexpr int64_t as_int64() const {
// Not ZigZag encoded
return static_cast<int64_t>(this->value_);
}
int32_t as_sint32() const {
constexpr int32_t as_sint32() const {
// with ZigZag encoding
if (this->value_ & 1) {
return static_cast<int32_t>(~(this->value_ >> 1));
} else {
return static_cast<int32_t>(this->value_ >> 1);
}
return decode_zigzag32(static_cast<uint32_t>(this->value_));
}
int64_t as_sint64() const {
constexpr int64_t as_sint64() const {
// with ZigZag encoding
if (this->value_ & 1) {
return static_cast<int64_t>(~(this->value_ >> 1));
} else {
return static_cast<int64_t>(this->value_ >> 1);
}
return decode_zigzag64(this->value_);
}
/**
* Encode the varint value to a pre-allocated buffer without bounds checking.
@@ -311,22 +318,10 @@ class ProtoWriteBuffer {
this->encode_uint64(field_id, static_cast<uint64_t>(value), force);
}
void encode_sint32(uint32_t field_id, int32_t value, bool force = false) {
uint32_t uvalue;
if (value < 0) {
uvalue = ~(value << 1);
} else {
uvalue = value << 1;
}
this->encode_uint32(field_id, uvalue, force);
this->encode_uint32(field_id, encode_zigzag32(value), force);
}
void encode_sint64(uint32_t field_id, int64_t value, bool force = false) {
uint64_t uvalue;
if (value < 0) {
uvalue = ~(value << 1);
} else {
uvalue = value << 1;
}
this->encode_uint64(field_id, uvalue, force);
this->encode_uint64(field_id, encode_zigzag64(value), force);
}
void encode_message(uint32_t field_id, const ProtoMessage &value, bool force = false);
std::vector<uint8_t> *get_buffer() const { return buffer_; }
@@ -335,13 +330,16 @@ class ProtoWriteBuffer {
std::vector<uint8_t> *buffer_;
};
// Forward declaration
class ProtoSize;
class ProtoMessage {
public:
virtual ~ProtoMessage() = default;
// Default implementation for messages with no fields
virtual void encode(ProtoWriteBuffer buffer) const {}
// Default implementation for messages with no fields
virtual void calculate_size(uint32_t &total_size) const {}
virtual void calculate_size(ProtoSize &size) const {}
#ifdef HAS_PROTO_MESSAGE_DUMP
std::string dump() const;
virtual void dump_to(std::string &out) const = 0;
@@ -362,31 +360,39 @@ class ProtoDecodableMessage : public ProtoMessage {
};
class ProtoSize {
private:
uint32_t total_size_ = 0;
public:
/**
* @brief ProtoSize class for Protocol Buffer serialization size calculation
*
* This class provides static methods to calculate the exact byte counts needed
* for encoding various Protocol Buffer field types. All methods are designed to be
* efficient for the common case where many fields have default values.
* This class provides methods to calculate the exact byte counts needed
* for encoding various Protocol Buffer field types. The class now uses an
* object-based approach to reduce parameter passing overhead while keeping
* varint calculation methods static for external use.
*
* Implements Protocol Buffer encoding size calculation according to:
* https://protobuf.dev/programming-guides/encoding/
*
* Key features:
* - Object-based approach reduces flash usage by eliminating parameter passing
* - Early-return optimization for zero/default values
* - Direct total_size updates to avoid unnecessary additions
* - Static varint methods for external callers
* - Specialized handling for different field types according to protobuf spec
* - Templated helpers for repeated fields and messages
*/
ProtoSize() = default;
uint32_t get_size() const { return total_size_; }
/**
* @brief Calculates the size in bytes needed to encode a uint32_t value as a varint
*
* @param value The uint32_t value to calculate size for
* @return The number of bytes needed to encode the value
*/
static inline uint32_t varint(uint32_t value) {
static constexpr uint32_t varint(uint32_t value) {
// Optimized varint size calculation using leading zeros
// Each 7 bits requires one byte in the varint encoding
if (value < 128)
@@ -410,7 +416,7 @@ class ProtoSize {
* @param value The uint64_t value to calculate size for
* @return The number of bytes needed to encode the value
*/
static inline uint32_t varint(uint64_t value) {
static constexpr uint32_t varint(uint64_t value) {
// Handle common case of values fitting in uint32_t (vast majority of use cases)
if (value <= UINT32_MAX) {
return varint(static_cast<uint32_t>(value));
@@ -441,7 +447,7 @@ class ProtoSize {
* @param value The int32_t value to calculate size for
* @return The number of bytes needed to encode the value
*/
static inline uint32_t varint(int32_t value) {
static constexpr uint32_t varint(int32_t value) {
// Negative values are sign-extended to 64 bits in protocol buffers,
// which always results in a 10-byte varint for negative int32
if (value < 0) {
@@ -457,7 +463,7 @@ class ProtoSize {
* @param value The int64_t value to calculate size for
* @return The number of bytes needed to encode the value
*/
static inline uint32_t varint(int64_t value) {
static constexpr uint32_t varint(int64_t value) {
// For int64_t, we convert to uint64_t and calculate the size
// This works because the bit pattern determines the encoding size,
// and we've handled negative int32 values as a special case above
@@ -471,7 +477,7 @@ class ProtoSize {
* @param type The wire type value (from the WireType enum in the protobuf spec)
* @return The number of bytes needed to encode the field ID and wire type
*/
static inline uint32_t field(uint32_t field_id, uint32_t type) {
static constexpr uint32_t field(uint32_t field_id, uint32_t type) {
uint32_t tag = (field_id << 3) | (type & 0b111);
return varint(tag);
}
@@ -480,9 +486,7 @@ class ProtoSize {
* @brief Common parameters for all add_*_field methods
*
* All add_*_field methods follow these common patterns:
*
* @param total_size Reference to the total message size to update
* @param field_id_size Pre-calculated size of the field ID in bytes
* * @param field_id_size Pre-calculated size of the field ID in bytes
* @param value The value to calculate size for (type varies)
* @param force Whether to calculate size even if the value is default/zero/empty
*
@@ -495,85 +499,63 @@ class ProtoSize {
/**
* @brief Calculates and adds the size of an int32 field to the total message size
*/
static inline void add_int32_field(uint32_t &total_size, uint32_t field_id_size, int32_t value) {
// Skip calculation if value is zero
if (value == 0) {
return; // No need to update total_size
}
// Calculate and directly add to total_size
if (value < 0) {
// Negative values are encoded as 10-byte varints in protobuf
total_size += field_id_size + 10;
} else {
// For non-negative values, use the standard varint size
total_size += field_id_size + varint(static_cast<uint32_t>(value));
inline void add_int32(uint32_t field_id_size, int32_t value) {
if (value != 0) {
add_int32_force(field_id_size, value);
}
}
/**
* @brief Calculates and adds the size of an int32 field to the total message size (repeated field version)
* @brief Calculates and adds the size of an int32 field to the total message size (force version)
*/
static inline void add_int32_field_repeated(uint32_t &total_size, uint32_t field_id_size, int32_t value) {
// Always calculate size for repeated fields
if (value < 0) {
// Negative values are encoded as 10-byte varints in protobuf
total_size += field_id_size + 10;
} else {
// For non-negative values, use the standard varint size
total_size += field_id_size + varint(static_cast<uint32_t>(value));
}
inline void add_int32_force(uint32_t field_id_size, int32_t value) {
// Always calculate size when forced
// Negative values are encoded as 10-byte varints in protobuf
total_size_ += field_id_size + (value < 0 ? 10 : varint(static_cast<uint32_t>(value)));
}
/**
* @brief Calculates and adds the size of a uint32 field to the total message size
*/
static inline void add_uint32_field(uint32_t &total_size, uint32_t field_id_size, uint32_t value) {
// Skip calculation if value is zero
if (value == 0) {
return; // No need to update total_size
inline void add_uint32(uint32_t field_id_size, uint32_t value) {
if (value != 0) {
add_uint32_force(field_id_size, value);
}
// Calculate and directly add to total_size
total_size += field_id_size + varint(value);
}
/**
* @brief Calculates and adds the size of a uint32 field to the total message size (repeated field version)
* @brief Calculates and adds the size of a uint32 field to the total message size (force version)
*/
static inline void add_uint32_field_repeated(uint32_t &total_size, uint32_t field_id_size, uint32_t value) {
// Always calculate size for repeated fields
total_size += field_id_size + varint(value);
inline void add_uint32_force(uint32_t field_id_size, uint32_t value) {
// Always calculate size when force is true
total_size_ += field_id_size + varint(value);
}
/**
* @brief Calculates and adds the size of a boolean field to the total message size
*/
static inline void add_bool_field(uint32_t &total_size, uint32_t field_id_size, bool value) {
// Skip calculation if value is false
if (!value) {
return; // No need to update total_size
inline void add_bool(uint32_t field_id_size, bool value) {
if (value) {
// Boolean fields always use 1 byte when true
total_size_ += field_id_size + 1;
}
// Boolean fields always use 1 byte when true
total_size += field_id_size + 1;
}
/**
* @brief Calculates and adds the size of a boolean field to the total message size (repeated field version)
* @brief Calculates and adds the size of a boolean field to the total message size (force version)
*/
static inline void add_bool_field_repeated(uint32_t &total_size, uint32_t field_id_size, bool value) {
// Always calculate size for repeated fields
inline void add_bool_force(uint32_t field_id_size, bool value) {
// Always calculate size when force is true
// Boolean fields always use 1 byte
total_size += field_id_size + 1;
total_size_ += field_id_size + 1;
}
/**
* @brief Calculates and adds the size of a float field to the total message size
*/
static inline void add_float_field(uint32_t &total_size, uint32_t field_id_size, float value) {
inline void add_float(uint32_t field_id_size, float value) {
if (value != 0.0f) {
total_size += field_id_size + 4;
total_size_ += field_id_size + 4;
}
}
@@ -583,9 +565,9 @@ class ProtoSize {
/**
* @brief Calculates and adds the size of a fixed32 field to the total message size
*/
static inline void add_fixed32_field(uint32_t &total_size, uint32_t field_id_size, uint32_t value) {
inline void add_fixed32(uint32_t field_id_size, uint32_t value) {
if (value != 0) {
total_size += field_id_size + 4;
total_size_ += field_id_size + 4;
}
}
@@ -595,149 +577,103 @@ class ProtoSize {
/**
* @brief Calculates and adds the size of a sfixed32 field to the total message size
*/
static inline void add_sfixed32_field(uint32_t &total_size, uint32_t field_id_size, int32_t value) {
inline void add_sfixed32(uint32_t field_id_size, int32_t value) {
if (value != 0) {
total_size += field_id_size + 4;
total_size_ += field_id_size + 4;
}
}
// NOTE: add_sfixed64_field removed - wire type 1 (64-bit: sfixed64) not supported
// to reduce overhead on embedded systems
/**
* @brief Calculates and adds the size of an enum field to the total message size
*
* Enum fields are encoded as uint32 varints.
*/
static inline void add_enum_field(uint32_t &total_size, uint32_t field_id_size, uint32_t value) {
// Skip calculation if value is zero
if (value == 0) {
return; // No need to update total_size
}
// Enums are encoded as uint32
total_size += field_id_size + varint(value);
}
/**
* @brief Calculates and adds the size of an enum field to the total message size (repeated field version)
*
* Enum fields are encoded as uint32 varints.
*/
static inline void add_enum_field_repeated(uint32_t &total_size, uint32_t field_id_size, uint32_t value) {
// Always calculate size for repeated fields
// Enums are encoded as uint32
total_size += field_id_size + varint(value);
}
/**
* @brief Calculates and adds the size of a sint32 field to the total message size
*
* Sint32 fields use ZigZag encoding, which is more efficient for negative values.
*/
static inline void add_sint32_field(uint32_t &total_size, uint32_t field_id_size, int32_t value) {
// Skip calculation if value is zero
if (value == 0) {
return; // No need to update total_size
inline void add_sint32(uint32_t field_id_size, int32_t value) {
if (value != 0) {
add_sint32_force(field_id_size, value);
}
// ZigZag encoding for sint32: (n << 1) ^ (n >> 31)
uint32_t zigzag = (static_cast<uint32_t>(value) << 1) ^ (static_cast<uint32_t>(value >> 31));
total_size += field_id_size + varint(zigzag);
}
/**
* @brief Calculates and adds the size of a sint32 field to the total message size (repeated field version)
* @brief Calculates and adds the size of a sint32 field to the total message size (force version)
*
* Sint32 fields use ZigZag encoding, which is more efficient for negative values.
*/
static inline void add_sint32_field_repeated(uint32_t &total_size, uint32_t field_id_size, int32_t value) {
// Always calculate size for repeated fields
// ZigZag encoding for sint32: (n << 1) ^ (n >> 31)
uint32_t zigzag = (static_cast<uint32_t>(value) << 1) ^ (static_cast<uint32_t>(value >> 31));
total_size += field_id_size + varint(zigzag);
inline void add_sint32_force(uint32_t field_id_size, int32_t value) {
// Always calculate size when force is true
// ZigZag encoding for sint32
total_size_ += field_id_size + varint(encode_zigzag32(value));
}
/**
* @brief Calculates and adds the size of an int64 field to the total message size
*/
static inline void add_int64_field(uint32_t &total_size, uint32_t field_id_size, int64_t value) {
// Skip calculation if value is zero
if (value == 0) {
return; // No need to update total_size
inline void add_int64(uint32_t field_id_size, int64_t value) {
if (value != 0) {
add_int64_force(field_id_size, value);
}
// Calculate and directly add to total_size
total_size += field_id_size + varint(value);
}
/**
* @brief Calculates and adds the size of an int64 field to the total message size (repeated field version)
* @brief Calculates and adds the size of an int64 field to the total message size (force version)
*/
static inline void add_int64_field_repeated(uint32_t &total_size, uint32_t field_id_size, int64_t value) {
// Always calculate size for repeated fields
total_size += field_id_size + varint(value);
inline void add_int64_force(uint32_t field_id_size, int64_t value) {
// Always calculate size when force is true
total_size_ += field_id_size + varint(value);
}
/**
* @brief Calculates and adds the size of a uint64 field to the total message size
*/
static inline void add_uint64_field(uint32_t &total_size, uint32_t field_id_size, uint64_t value) {
// Skip calculation if value is zero
if (value == 0) {
return; // No need to update total_size
inline void add_uint64(uint32_t field_id_size, uint64_t value) {
if (value != 0) {
add_uint64_force(field_id_size, value);
}
// Calculate and directly add to total_size
total_size += field_id_size + varint(value);
}
/**
* @brief Calculates and adds the size of a uint64 field to the total message size (repeated field version)
* @brief Calculates and adds the size of a uint64 field to the total message size (force version)
*/
static inline void add_uint64_field_repeated(uint32_t &total_size, uint32_t field_id_size, uint64_t value) {
// Always calculate size for repeated fields
total_size += field_id_size + varint(value);
inline void add_uint64_force(uint32_t field_id_size, uint64_t value) {
// Always calculate size when force is true
total_size_ += field_id_size + varint(value);
}
// NOTE: sint64 support functions (add_sint64_field, add_sint64_field_repeated) removed
// NOTE: sint64 support functions (add_sint64_field, add_sint64_field_force) removed
// sint64 type is not supported by ESPHome API to reduce overhead on embedded systems
/**
* @brief Calculates and adds the size of a string field using length
* @brief Calculates and adds the size of a length-delimited field (string/bytes) to the total message size
*/
static inline void add_string_field(uint32_t &total_size, uint32_t field_id_size, size_t len) {
// Skip calculation if string is empty
if (len == 0) {
return; // No need to update total_size
inline void add_length(uint32_t field_id_size, size_t len) {
if (len != 0) {
add_length_force(field_id_size, len);
}
// Field ID + length varint + string bytes
total_size += field_id_size + varint(static_cast<uint32_t>(len)) + static_cast<uint32_t>(len);
}
/**
* @brief Calculates and adds the size of a string/bytes field to the total message size (repeated field version)
* @brief Calculates and adds the size of a length-delimited field (string/bytes) to the total message size (repeated
* field version)
*/
static inline void add_string_field_repeated(uint32_t &total_size, uint32_t field_id_size, const std::string &str) {
// Always calculate size for repeated fields
const uint32_t str_size = static_cast<uint32_t>(str.size());
total_size += field_id_size + varint(str_size) + str_size;
}
/**
* @brief Calculates and adds the size of a bytes field to the total message size
*/
static inline void add_bytes_field(uint32_t &total_size, uint32_t field_id_size, size_t len) {
// Skip calculation if bytes is empty
if (len == 0) {
return; // No need to update total_size
}
inline void add_length_force(uint32_t field_id_size, size_t len) {
// Always calculate size when force is true
// Field ID + length varint + data bytes
total_size += field_id_size + varint(static_cast<uint32_t>(len)) + static_cast<uint32_t>(len);
total_size_ += field_id_size + varint(static_cast<uint32_t>(len)) + static_cast<uint32_t>(len);
}
/**
* @brief Adds a pre-calculated size directly to the total
*
* This is used when we can calculate the total size by multiplying the number
* of elements by the bytes per element (for repeated fixed-size types like float, fixed32, etc.)
*
* @param size The pre-calculated total size to add
*/
inline void add_precalculated_size(uint32_t size) { total_size_ += size; }
/**
* @brief Calculates and adds the size of a nested message field to the total message size
*
@@ -746,26 +682,21 @@ class ProtoSize {
*
* @param nested_size The pre-calculated size of the nested message
*/
static inline void add_message_field(uint32_t &total_size, uint32_t field_id_size, uint32_t nested_size) {
// Skip calculation if nested message is empty
if (nested_size == 0) {
return; // No need to update total_size
inline void add_message_field(uint32_t field_id_size, uint32_t nested_size) {
if (nested_size != 0) {
add_message_field_force(field_id_size, nested_size);
}
// Calculate and directly add to total_size
// Field ID + length varint + nested message content
total_size += field_id_size + varint(nested_size) + nested_size;
}
/**
* @brief Calculates and adds the size of a nested message field to the total message size (repeated field version)
* @brief Calculates and adds the size of a nested message field to the total message size (force version)
*
* @param nested_size The pre-calculated size of the nested message
*/
static inline void add_message_field_repeated(uint32_t &total_size, uint32_t field_id_size, uint32_t nested_size) {
// Always calculate size for repeated fields
inline void add_message_field_force(uint32_t field_id_size, uint32_t nested_size) {
// Always calculate size when force is true
// Field ID + length varint + nested message content
total_size += field_id_size + varint(nested_size) + nested_size;
total_size_ += field_id_size + varint(nested_size) + nested_size;
}
/**
@@ -777,26 +708,29 @@ class ProtoSize {
*
* @param message The nested message object
*/
static inline void add_message_object(uint32_t &total_size, uint32_t field_id_size, const ProtoMessage &message) {
uint32_t nested_size = 0;
message.calculate_size(nested_size);
inline void add_message_object(uint32_t field_id_size, const ProtoMessage &message) {
// Calculate nested message size by creating a temporary ProtoSize
ProtoSize nested_calc;
message.calculate_size(nested_calc);
uint32_t nested_size = nested_calc.get_size();
// Use the base implementation with the calculated nested_size
add_message_field(total_size, field_id_size, nested_size);
add_message_field(field_id_size, nested_size);
}
/**
* @brief Calculates and adds the size of a nested message field to the total message size (repeated field version)
* @brief Calculates and adds the size of a nested message field to the total message size (force version)
*
* @param message The nested message object
*/
static inline void add_message_object_repeated(uint32_t &total_size, uint32_t field_id_size,
const ProtoMessage &message) {
uint32_t nested_size = 0;
message.calculate_size(nested_size);
inline void add_message_object_force(uint32_t field_id_size, const ProtoMessage &message) {
// Calculate nested message size by creating a temporary ProtoSize
ProtoSize nested_calc;
message.calculate_size(nested_calc);
uint32_t nested_size = nested_calc.get_size();
// Use the base implementation with the calculated nested_size
add_message_field_repeated(total_size, field_id_size, nested_size);
add_message_field_force(field_id_size, nested_size);
}
/**
@@ -809,16 +743,15 @@ class ProtoSize {
* @param messages Vector of message objects
*/
template<typename MessageType>
static inline void add_repeated_message(uint32_t &total_size, uint32_t field_id_size,
const std::vector<MessageType> &messages) {
inline void add_repeated_message(uint32_t field_id_size, const std::vector<MessageType> &messages) {
// Skip if the vector is empty
if (messages.empty()) {
return;
}
// Use the repeated field version for all messages
// Use the force version for all messages in the repeated field
for (const auto &message : messages) {
add_message_object_repeated(total_size, field_id_size, message);
add_message_object_force(field_id_size, message);
}
}
};
@@ -828,8 +761,9 @@ inline void ProtoWriteBuffer::encode_message(uint32_t field_id, const ProtoMessa
this->encode_field_raw(field_id, 2); // type 2: Length-delimited message
// Calculate the message size first
uint32_t msg_length_bytes = 0;
value.calculate_size(msg_length_bytes);
ProtoSize msg_size;
value.calculate_size(msg_size);
uint32_t msg_length_bytes = msg_size.get_size();
// Calculate how many bytes the length varint needs
uint32_t varint_length_bytes = ProtoSize::varint(msg_length_bytes);
@@ -861,7 +795,9 @@ class ProtoService {
virtual bool is_authenticated() = 0;
virtual bool is_connection_setup() = 0;
virtual void on_fatal_error() = 0;
#ifdef USE_API_PASSWORD
virtual void on_unauthenticated_access() = 0;
#endif
virtual void on_no_setup_connection() = 0;
/**
* Create a buffer with a reserved size.
@@ -876,8 +812,9 @@ class ProtoService {
// Optimized method that pre-allocates buffer based on message size
bool send_message_(const ProtoMessage &msg, uint8_t message_type) {
uint32_t msg_size = 0;
msg.calculate_size(msg_size);
ProtoSize size;
msg.calculate_size(size);
uint32_t msg_size = size.get_size();
// Create a pre-sized buffer
auto buffer = this->create_buffer(msg_size);
@@ -899,6 +836,7 @@ class ProtoService {
}
bool check_authenticated_() {
#ifdef USE_API_PASSWORD
if (!this->check_connection_setup_()) {
return false;
}
@@ -907,8 +845,10 @@ class ProtoService {
return false;
}
return true;
#else
return this->check_connection_setup_();
#endif
}
};
} // namespace api
} // namespace esphome
} // namespace esphome::api

View File

@@ -3,8 +3,7 @@
#include "api_connection.h"
#include "esphome/core/log.h"
namespace esphome {
namespace api {
namespace esphome::api {
// Generate entity handler implementations using macros
#ifdef USE_BINARY_SENSOR
@@ -69,6 +68,5 @@ INITIAL_STATE_HANDLER(update, update::UpdateEntity)
InitialStateIterator::InitialStateIterator(APIConnection *client) : client_(client) {}
} // namespace api
} // namespace esphome
} // namespace esphome::api
#endif

View File

@@ -5,8 +5,7 @@
#include "esphome/core/component.h"
#include "esphome/core/component_iterator.h"
#include "esphome/core/controller.h"
namespace esphome {
namespace api {
namespace esphome::api {
class APIConnection;
@@ -89,6 +88,5 @@ class InitialStateIterator : public ComponentIterator {
APIConnection *client_;
};
} // namespace api
} // namespace esphome
} // namespace esphome::api
#endif

View File

@@ -1,8 +1,7 @@
#include "user_services.h"
#include "esphome/core/log.h"
namespace esphome {
namespace api {
namespace esphome::api {
template<> bool get_execute_arg_value<bool>(const ExecuteServiceArgument &arg) { return arg.bool_; }
template<> int32_t get_execute_arg_value<int32_t>(const ExecuteServiceArgument &arg) {
@@ -40,5 +39,4 @@ template<> enums::ServiceArgType to_service_arg_type<std::vector<std::string>>()
return enums::SERVICE_ARG_TYPE_STRING_ARRAY;
}
} // namespace api
} // namespace esphome
} // namespace esphome::api

View File

@@ -8,8 +8,7 @@
#include "api_pb2.h"
#ifdef USE_API_SERVICES
namespace esphome {
namespace api {
namespace esphome::api {
class UserServiceDescriptor {
public:
@@ -74,6 +73,5 @@ template<typename... Ts> class UserServiceTrigger : public UserServiceBase<Ts...
void execute(Ts... x) override { this->trigger(x...); } // NOLINT
};
} // namespace api
} // namespace esphome
} // namespace esphome::api
#endif // USE_API_SERVICES

View File

@@ -7,8 +7,6 @@ namespace as3935 {
static const char *const TAG = "as3935";
void AS3935Component::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
this->irq_pin_->setup();
LOG_PIN(" IRQ Pin: ", this->irq_pin_);

View File

@@ -7,9 +7,7 @@ namespace as3935_spi {
static const char *const TAG = "as3935_spi";
void SPIAS3935Component::setup() {
ESP_LOGI(TAG, "SPIAS3935Component setup started!");
this->spi_setup();
ESP_LOGI(TAG, "SPI setup finished!");
AS3935Component::setup();
}

View File

@@ -7,6 +7,7 @@ from esphome.const import (
CONF_DIRECTION,
CONF_HYSTERESIS,
CONF_ID,
CONF_POWER_MODE,
CONF_RANGE,
)
@@ -57,7 +58,6 @@ FAST_FILTER = {
CONF_RAW_ANGLE = "raw_angle"
CONF_RAW_POSITION = "raw_position"
CONF_WATCHDOG = "watchdog"
CONF_POWER_MODE = "power_mode"
CONF_SLOW_FILTER = "slow_filter"
CONF_FAST_FILTER = "fast_filter"
CONF_START_POSITION = "start_position"

View File

@@ -23,8 +23,6 @@ static const uint8_t REGISTER_AGC = 0x1A; // 8 bytes / R
static const uint8_t REGISTER_MAGNITUDE = 0x1B; // 16 bytes / R
void AS5600Component::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
if (!this->read_byte(REGISTER_STATUS).has_value()) {
this->mark_failed();
return;

View File

@@ -24,7 +24,6 @@ AS5600Sensor = as5600_ns.class_("AS5600Sensor", sensor.Sensor, cg.PollingCompone
CONF_RAW_ANGLE = "raw_angle"
CONF_RAW_POSITION = "raw_position"
CONF_WATCHDOG = "watchdog"
CONF_POWER_MODE = "power_mode"
CONF_SLOW_FILTER = "slow_filter"
CONF_FAST_FILTER = "fast_filter"
CONF_PWM_FREQUENCY = "pwm_frequency"

View File

@@ -8,7 +8,6 @@ namespace as7341 {
static const char *const TAG = "as7341";
void AS7341Component::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
LOG_I2C_DEVICE(this);
// Verify device ID

View File

@@ -10,7 +10,7 @@ from esphome.const import (
)
from esphome.core import CORE, coroutine_with_priority
CODEOWNERS = ["@OttoWinter"]
CODEOWNERS = ["@esphome/core"]
CONFIG_SCHEMA = cv.All(
cv.Schema({}),

View File

@@ -71,7 +71,7 @@ bool AT581XComponent::i2c_read_reg(uint8_t addr, uint8_t &data) {
return this->read_register(addr, &data, 1) == esphome::i2c::NO_ERROR;
}
void AT581XComponent::setup() { ESP_LOGCONFIG(TAG, "Running setup"); }
void AT581XComponent::setup() {}
void AT581XComponent::dump_config() { LOG_I2C_DEVICE(this); }
#define ARRAY_SIZE(X) (sizeof(X) / sizeof((X)[0]))
bool AT581XComponent::i2c_write_config() {

View File

@@ -41,7 +41,6 @@ void ATM90E26Component::update() {
}
void ATM90E26Component::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
this->spi_setup();
uint16_t mmode = 0x422; // default values for everything but L/N line current gains

View File

@@ -109,8 +109,9 @@ void ATM90E32Component::update() {
}
void ATM90E32Component::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
this->spi_setup();
this->cs_summary_ = this->cs_->dump_summary();
const char *cs = this->cs_summary_.c_str();
uint16_t mmode0 = 0x87; // 3P4W 50Hz
uint16_t high_thresh = 0;
@@ -131,9 +132,9 @@ void ATM90E32Component::setup() {
mmode0 |= 0 << 1; // sets 1st bit to 0, phase b is not counted into the all-phase sum energy/power (P/Q/S)
}
this->write16_(ATM90E32_REGISTER_SOFTRESET, 0x789A); // Perform soft reset
delay(6); // Wait for the minimum 5ms + 1ms
this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x55AA); // enable register config access
this->write16_(ATM90E32_REGISTER_SOFTRESET, 0x789A, false); // Perform soft reset
delay(6); // Wait for the minimum 5ms + 1ms
this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x55AA); // enable register config access
if (!this->validate_spi_read_(0x55AA, "setup()")) {
ESP_LOGW(TAG, "Could not initialize ATM90E32 IC, check SPI settings");
this->mark_failed();
@@ -157,16 +158,17 @@ void ATM90E32Component::setup() {
if (this->enable_offset_calibration_) {
// Initialize flash storage for offset calibrations
uint32_t o_hash = fnv1_hash(std::string("_offset_calibration_") + this->cs_->dump_summary());
uint32_t o_hash = fnv1_hash(std::string("_offset_calibration_") + this->cs_summary_);
this->offset_pref_ = global_preferences->make_preference<OffsetCalibration[3]>(o_hash, true);
this->restore_offset_calibrations_();
// Initialize flash storage for power offset calibrations
uint32_t po_hash = fnv1_hash(std::string("_power_offset_calibration_") + this->cs_->dump_summary());
uint32_t po_hash = fnv1_hash(std::string("_power_offset_calibration_") + this->cs_summary_);
this->power_offset_pref_ = global_preferences->make_preference<PowerOffsetCalibration[3]>(po_hash, true);
this->restore_power_offset_calibrations_();
} else {
ESP_LOGI(TAG, "[CALIBRATION] Power & Voltage/Current offset calibration is disabled. Using config file values.");
ESP_LOGI(TAG, "[CALIBRATION][%s] Power & Voltage/Current offset calibration is disabled. Using config file values.",
cs);
for (uint8_t phase = 0; phase < 3; ++phase) {
this->write16_(this->voltage_offset_registers[phase],
static_cast<uint16_t>(this->offset_phase_[phase].voltage_offset_));
@@ -181,21 +183,18 @@ void ATM90E32Component::setup() {
if (this->enable_gain_calibration_) {
// Initialize flash storage for gain calibration
uint32_t g_hash = fnv1_hash(std::string("_gain_calibration_") + this->cs_->dump_summary());
uint32_t g_hash = fnv1_hash(std::string("_gain_calibration_") + this->cs_summary_);
this->gain_calibration_pref_ = global_preferences->make_preference<GainCalibration[3]>(g_hash, true);
this->restore_gain_calibrations_();
if (this->using_saved_calibrations_) {
ESP_LOGI(TAG, "[CALIBRATION] Successfully restored gain calibration from memory.");
} else {
if (!this->using_saved_calibrations_) {
for (uint8_t phase = 0; phase < 3; ++phase) {
this->write16_(voltage_gain_registers[phase], this->phase_[phase].voltage_gain_);
this->write16_(current_gain_registers[phase], this->phase_[phase].ct_gain_);
}
}
} else {
ESP_LOGI(TAG, "[CALIBRATION] Gain calibration is disabled. Using config file values.");
ESP_LOGI(TAG, "[CALIBRATION][%s] Gain calibration is disabled. Using config file values.", cs);
for (uint8_t phase = 0; phase < 3; ++phase) {
this->write16_(voltage_gain_registers[phase], this->phase_[phase].voltage_gain_);
this->write16_(current_gain_registers[phase], this->phase_[phase].ct_gain_);
@@ -214,6 +213,122 @@ void ATM90E32Component::setup() {
this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x0000); // end configuration
}
void ATM90E32Component::log_calibration_status_() {
const char *cs = this->cs_summary_.c_str();
bool offset_mismatch = false;
bool power_mismatch = false;
bool gain_mismatch = false;
for (uint8_t phase = 0; phase < 3; ++phase) {
offset_mismatch |= this->offset_calibration_mismatch_[phase];
power_mismatch |= this->power_offset_calibration_mismatch_[phase];
gain_mismatch |= this->gain_calibration_mismatch_[phase];
}
if (offset_mismatch) {
ESP_LOGW(TAG, "[CALIBRATION][%s] ", cs);
ESP_LOGW(TAG,
"[CALIBRATION][%s] ===================== Offset mismatch: using flash values =====================", cs);
ESP_LOGW(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------------------",
cs);
ESP_LOGW(TAG, "[CALIBRATION][%s] | Phase | offset_voltage | offset_current |", cs);
ESP_LOGW(TAG, "[CALIBRATION][%s] | | config | flash | config | flash |", cs);
ESP_LOGW(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------------------",
cs);
for (uint8_t phase = 0; phase < 3; ++phase) {
ESP_LOGW(TAG, "[CALIBRATION][%s] | %c | %6d | %6d | %6d | %6d |", cs, 'A' + phase,
this->config_offset_phase_[phase].voltage_offset_, this->offset_phase_[phase].voltage_offset_,
this->config_offset_phase_[phase].current_offset_, this->offset_phase_[phase].current_offset_);
}
ESP_LOGW(TAG,
"[CALIBRATION][%s] ===============================================================================", cs);
}
if (power_mismatch) {
ESP_LOGW(TAG, "[CALIBRATION][%s] ", cs);
ESP_LOGW(TAG,
"[CALIBRATION][%s] ================= Power offset mismatch: using flash values =================", cs);
ESP_LOGW(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------------------",
cs);
ESP_LOGW(TAG, "[CALIBRATION][%s] | Phase | offset_active_power|offset_reactive_power|", cs);
ESP_LOGW(TAG, "[CALIBRATION][%s] | | config | flash | config | flash |", cs);
ESP_LOGW(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------------------",
cs);
for (uint8_t phase = 0; phase < 3; ++phase) {
ESP_LOGW(TAG, "[CALIBRATION][%s] | %c | %6d | %6d | %6d | %6d |", cs, 'A' + phase,
this->config_power_offset_phase_[phase].active_power_offset,
this->power_offset_phase_[phase].active_power_offset,
this->config_power_offset_phase_[phase].reactive_power_offset,
this->power_offset_phase_[phase].reactive_power_offset);
}
ESP_LOGW(TAG,
"[CALIBRATION][%s] ===============================================================================", cs);
}
if (gain_mismatch) {
ESP_LOGW(TAG, "[CALIBRATION][%s] ", cs);
ESP_LOGW(TAG,
"[CALIBRATION][%s] ====================== Gain mismatch: using flash values =====================", cs);
ESP_LOGW(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------------------",
cs);
ESP_LOGW(TAG, "[CALIBRATION][%s] | Phase | voltage_gain | current_gain |", cs);
ESP_LOGW(TAG, "[CALIBRATION][%s] | | config | flash | config | flash |", cs);
ESP_LOGW(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------------------",
cs);
for (uint8_t phase = 0; phase < 3; ++phase) {
ESP_LOGW(TAG, "[CALIBRATION][%s] | %c | %6u | %6u | %6u | %6u |", cs, 'A' + phase,
this->config_gain_phase_[phase].voltage_gain, this->gain_phase_[phase].voltage_gain,
this->config_gain_phase_[phase].current_gain, this->gain_phase_[phase].current_gain);
}
ESP_LOGW(TAG,
"[CALIBRATION][%s] ===============================================================================", cs);
}
if (!this->enable_offset_calibration_) {
ESP_LOGI(TAG, "[CALIBRATION][%s] Power & Voltage/Current offset calibration is disabled. Using config file values.",
cs);
} else if (this->restored_offset_calibration_ && !offset_mismatch) {
ESP_LOGI(TAG, "[CALIBRATION][%s] ", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] ============== Restored offset calibration from memory ==============", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] --------------------------------------------------------------", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_voltage | offset_current |", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] --------------------------------------------------------------", cs);
for (uint8_t phase = 0; phase < 3; phase++) {
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase,
this->offset_phase_[phase].voltage_offset_, this->offset_phase_[phase].current_offset_);
}
ESP_LOGI(TAG, "[CALIBRATION][%s] ==============================================================\\n", cs);
}
if (this->restored_power_offset_calibration_ && !power_mismatch) {
ESP_LOGI(TAG, "[CALIBRATION][%s] ", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] ============ Restored power offset calibration from memory ============", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_active_power | offset_reactive_power |", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
for (uint8_t phase = 0; phase < 3; phase++) {
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase,
this->power_offset_phase_[phase].active_power_offset,
this->power_offset_phase_[phase].reactive_power_offset);
}
ESP_LOGI(TAG, "[CALIBRATION][%s] =====================================================================\n", cs);
}
if (!this->enable_gain_calibration_) {
ESP_LOGI(TAG, "[CALIBRATION][%s] Gain calibration is disabled. Using config file values.", cs);
} else if (this->restored_gain_calibration_ && !gain_mismatch) {
ESP_LOGI(TAG, "[CALIBRATION][%s] ", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] ============ Restoring saved gain calibrations to registers ============", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | voltage_gain | current_gain |", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
for (uint8_t phase = 0; phase < 3; phase++) {
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6u | %6u |", cs, 'A' + phase,
this->gain_phase_[phase].voltage_gain, this->gain_phase_[phase].current_gain);
}
ESP_LOGI(TAG, "[CALIBRATION][%s] =====================================================================\\n", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] Gain calibration loaded and verified successfully.\n", cs);
}
this->calibration_message_printed_ = true;
}
void ATM90E32Component::dump_config() {
ESP_LOGCONFIG("", "ATM90E32:");
LOG_PIN(" CS Pin: ", this->cs_);
@@ -256,6 +371,10 @@ void ATM90E32Component::dump_config() {
LOG_SENSOR(" ", "Peak Current C", this->phase_[PHASEC].peak_current_sensor_);
LOG_SENSOR(" ", "Frequency", this->freq_sensor_);
LOG_SENSOR(" ", "Chip Temp", this->chip_temperature_sensor_);
if (this->restored_offset_calibration_ || this->restored_power_offset_calibration_ ||
this->restored_gain_calibration_ || !this->enable_offset_calibration_ || !this->enable_gain_calibration_) {
this->log_calibration_status_();
}
}
float ATM90E32Component::get_setup_priority() const { return setup_priority::IO; }
@@ -264,19 +383,17 @@ float ATM90E32Component::get_setup_priority() const { return setup_priority::IO;
// Peakdetect period: 05H. Bit 15:8 are PeakDet_period in ms. 7:0 are Sag_period
// Default is 143FH (20ms, 63ms)
uint16_t ATM90E32Component::read16_(uint16_t a_register) {
this->enable();
delay_microseconds_safe(1); // min delay between CS low and first SCK is 200ns - 1us is plenty
uint8_t addrh = (1 << 7) | ((a_register >> 8) & 0x03);
uint8_t addrl = (a_register & 0xFF);
uint8_t data[2];
uint16_t output;
this->enable();
delay_microseconds_safe(1); // min delay between CS low and first SCK is 200ns - 1ms is plenty
this->write_byte(addrh);
this->write_byte(addrl);
this->read_array(data, 2);
this->disable();
output = (uint16_t(data[0] & 0xFF) << 8) | (data[1] & 0xFF);
uint8_t data[4] = {addrh, addrl, 0x00, 0x00};
this->transfer_array(data, 4);
uint16_t output = encode_uint16(data[2], data[3]);
ESP_LOGVV(TAG, "read16_ 0x%04" PRIX16 " output 0x%04" PRIX16, a_register, output);
delay_microseconds_safe(1); // allow the last clock to propagate before releasing CS
this->disable();
delay_microseconds_safe(1); // meet minimum CS high time before next transaction
return output;
}
@@ -293,13 +410,19 @@ int ATM90E32Component::read32_(uint16_t addr_h, uint16_t addr_l) {
return val;
}
void ATM90E32Component::write16_(uint16_t a_register, uint16_t val) {
void ATM90E32Component::write16_(uint16_t a_register, uint16_t val, bool validate) {
ESP_LOGVV(TAG, "write16_ 0x%04" PRIX16 " val 0x%04" PRIX16, a_register, val);
uint8_t addrh = ((a_register >> 8) & 0x03);
uint8_t addrl = (a_register & 0xFF);
uint8_t data[4] = {addrh, addrl, uint8_t((val >> 8) & 0xFF), uint8_t(val & 0xFF)};
this->enable();
this->write_byte16(a_register);
this->write_byte16(val);
delay_microseconds_safe(1); // ensure CS setup time
this->write_array(data, 4);
delay_microseconds_safe(1); // allow clock to settle before raising CS
this->disable();
this->validate_spi_read_(val, "write16()");
delay_microseconds_safe(1); // ensure minimum CS high time
if (validate)
this->validate_spi_read_(val, "write16()");
}
float ATM90E32Component::get_local_phase_voltage_(uint8_t phase) { return this->phase_[phase].voltage_; }
@@ -442,8 +565,10 @@ float ATM90E32Component::get_chip_temperature_() {
}
void ATM90E32Component::run_gain_calibrations() {
const char *cs = this->cs_summary_.c_str();
if (!this->enable_gain_calibration_) {
ESP_LOGW(TAG, "[CALIBRATION] Gain calibration is disabled! Enable it first with enable_gain_calibration: true");
ESP_LOGW(TAG, "[CALIBRATION][%s] Gain calibration is disabled! Enable it first with enable_gain_calibration: true",
cs);
return;
}
@@ -455,12 +580,14 @@ void ATM90E32Component::run_gain_calibrations() {
float ref_currents[3] = {this->get_reference_current(0), this->get_reference_current(1),
this->get_reference_current(2)};
ESP_LOGI(TAG, "[CALIBRATION] ");
ESP_LOGI(TAG, "[CALIBRATION] ========================= Gain Calibration =========================");
ESP_LOGI(TAG, "[CALIBRATION] ---------------------------------------------------------------------");
ESP_LOGI(TAG,
"[CALIBRATION] | Phase | V_meas (V) | I_meas (A) | V_ref | I_ref | V_gain (old→new) | I_gain (old→new) |");
ESP_LOGI(TAG, "[CALIBRATION] ---------------------------------------------------------------------");
ESP_LOGI(TAG, "[CALIBRATION][%s] ", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] ========================= Gain Calibration =========================", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
ESP_LOGI(
TAG,
"[CALIBRATION][%s] | Phase | V_meas (V) | I_meas (A) | V_ref | I_ref | V_gain (old→new) | I_gain (old→new) |",
cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
for (uint8_t phase = 0; phase < 3; phase++) {
float measured_voltage = this->get_phase_voltage_avg_(phase);
@@ -477,22 +604,22 @@ void ATM90E32Component::run_gain_calibrations() {
// Voltage calibration
if (ref_voltage <= 0.0f) {
ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Skipping voltage calibration: reference voltage is 0.",
ESP_LOGW(TAG, "[CALIBRATION][%s] Phase %s - Skipping voltage calibration: reference voltage is 0.", cs,
phase_labels[phase]);
} else if (measured_voltage == 0.0f) {
ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Skipping voltage calibration: measured voltage is 0.",
ESP_LOGW(TAG, "[CALIBRATION][%s] Phase %s - Skipping voltage calibration: measured voltage is 0.", cs,
phase_labels[phase]);
} else {
uint32_t new_voltage_gain = static_cast<uint16_t>((ref_voltage / measured_voltage) * current_voltage_gain);
if (new_voltage_gain == 0) {
ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Voltage gain would be 0. Check reference and measured voltage.",
ESP_LOGW(TAG, "[CALIBRATION][%s] Phase %s - Voltage gain would be 0. Check reference and measured voltage.", cs,
phase_labels[phase]);
} else {
if (new_voltage_gain >= 65535) {
ESP_LOGW(
TAG,
"[CALIBRATION] Phase %s - Voltage gain exceeds 65535. You may need a higher output voltage transformer.",
phase_labels[phase]);
ESP_LOGW(TAG,
"[CALIBRATION][%s] Phase %s - Voltage gain exceeds 65535. You may need a higher output voltage "
"transformer.",
cs, phase_labels[phase]);
new_voltage_gain = 65535;
}
this->gain_phase_[phase].voltage_gain = static_cast<uint16_t>(new_voltage_gain);
@@ -502,20 +629,20 @@ void ATM90E32Component::run_gain_calibrations() {
// Current calibration
if (ref_current == 0.0f) {
ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Skipping current calibration: reference current is 0.",
ESP_LOGW(TAG, "[CALIBRATION][%s] Phase %s - Skipping current calibration: reference current is 0.", cs,
phase_labels[phase]);
} else if (measured_current == 0.0f) {
ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Skipping current calibration: measured current is 0.",
ESP_LOGW(TAG, "[CALIBRATION][%s] Phase %s - Skipping current calibration: measured current is 0.", cs,
phase_labels[phase]);
} else {
uint32_t new_current_gain = static_cast<uint16_t>((ref_current / measured_current) * current_current_gain);
if (new_current_gain == 0) {
ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Current gain would be 0. Check reference and measured current.",
ESP_LOGW(TAG, "[CALIBRATION][%s] Phase %s - Current gain would be 0. Check reference and measured current.", cs,
phase_labels[phase]);
} else {
if (new_current_gain >= 65535) {
ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Current gain exceeds 65535. You may need to turn up pga gain.",
phase_labels[phase]);
ESP_LOGW(TAG, "[CALIBRATION][%s] Phase %s - Current gain exceeds 65535. You may need to turn up pga gain.",
cs, phase_labels[phase]);
new_current_gain = 65535;
}
this->gain_phase_[phase].current_gain = static_cast<uint16_t>(new_current_gain);
@@ -524,13 +651,13 @@ void ATM90E32Component::run_gain_calibrations() {
}
// Final row output
ESP_LOGI(TAG, "[CALIBRATION] | %c | %9.2f | %9.4f | %5.2f | %6.4f | %5u → %-5u | %5u → %-5u |",
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %9.2f | %9.4f | %5.2f | %6.4f | %5u → %-5u | %5u → %-5u |", cs,
'A' + phase, measured_voltage, measured_current, ref_voltage, ref_current, current_voltage_gain,
did_voltage ? this->gain_phase_[phase].voltage_gain : current_voltage_gain, current_current_gain,
did_current ? this->gain_phase_[phase].current_gain : current_current_gain);
}
ESP_LOGI(TAG, "[CALIBRATION] =====================================================================\n");
ESP_LOGI(TAG, "[CALIBRATION][%s] =====================================================================\n", cs);
this->save_gain_calibration_to_memory_();
this->write_gains_to_registers_();
@@ -538,54 +665,108 @@ void ATM90E32Component::run_gain_calibrations() {
}
void ATM90E32Component::save_gain_calibration_to_memory_() {
const char *cs = this->cs_summary_.c_str();
bool success = this->gain_calibration_pref_.save(&this->gain_phase_);
global_preferences->sync();
if (success) {
this->using_saved_calibrations_ = true;
ESP_LOGI(TAG, "[CALIBRATION] Gain calibration saved to memory.");
ESP_LOGI(TAG, "[CALIBRATION][%s] Gain calibration saved to memory.", cs);
} else {
this->using_saved_calibrations_ = false;
ESP_LOGE(TAG, "[CALIBRATION] Failed to save gain calibration to memory!");
ESP_LOGE(TAG, "[CALIBRATION][%s] Failed to save gain calibration to memory!", cs);
}
}
void ATM90E32Component::save_offset_calibration_to_memory_() {
const char *cs = this->cs_summary_.c_str();
bool success = this->offset_pref_.save(&this->offset_phase_);
global_preferences->sync();
if (success) {
this->using_saved_calibrations_ = true;
this->restored_offset_calibration_ = true;
for (bool &phase : this->offset_calibration_mismatch_)
phase = false;
ESP_LOGI(TAG, "[CALIBRATION][%s] Offset calibration saved to memory.", cs);
} else {
this->using_saved_calibrations_ = false;
ESP_LOGE(TAG, "[CALIBRATION][%s] Failed to save offset calibration to memory!", cs);
}
}
void ATM90E32Component::save_power_offset_calibration_to_memory_() {
const char *cs = this->cs_summary_.c_str();
bool success = this->power_offset_pref_.save(&this->power_offset_phase_);
global_preferences->sync();
if (success) {
this->using_saved_calibrations_ = true;
this->restored_power_offset_calibration_ = true;
for (bool &phase : this->power_offset_calibration_mismatch_)
phase = false;
ESP_LOGI(TAG, "[CALIBRATION][%s] Power offset calibration saved to memory.", cs);
} else {
this->using_saved_calibrations_ = false;
ESP_LOGE(TAG, "[CALIBRATION][%s] Failed to save power offset calibration to memory!", cs);
}
}
void ATM90E32Component::run_offset_calibrations() {
const char *cs = this->cs_summary_.c_str();
if (!this->enable_offset_calibration_) {
ESP_LOGW(TAG, "[CALIBRATION] Offset calibration is disabled! Enable it first with enable_offset_calibration: true");
ESP_LOGW(TAG,
"[CALIBRATION][%s] Offset calibration is disabled! Enable it first with enable_offset_calibration: true",
cs);
return;
}
ESP_LOGI(TAG, "[CALIBRATION][%s] ", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] ======================== Offset Calibration ========================", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_voltage | offset_current |", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------", cs);
for (uint8_t phase = 0; phase < 3; phase++) {
int16_t voltage_offset = calibrate_offset(phase, true);
int16_t current_offset = calibrate_offset(phase, false);
this->write_offsets_to_registers_(phase, voltage_offset, current_offset);
ESP_LOGI(TAG, "[CALIBRATION] Phase %c - offset_voltage: %d, offset_current: %d", 'A' + phase, voltage_offset,
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase, voltage_offset,
current_offset);
}
this->offset_pref_.save(&this->offset_phase_); // Save to flash
ESP_LOGI(TAG, "[CALIBRATION][%s] ==================================================================\n", cs);
this->save_offset_calibration_to_memory_();
}
void ATM90E32Component::run_power_offset_calibrations() {
const char *cs = this->cs_summary_.c_str();
if (!this->enable_offset_calibration_) {
ESP_LOGW(
TAG,
"[CALIBRATION] Offset power calibration is disabled! Enable it first with enable_offset_calibration: true");
"[CALIBRATION][%s] Offset power calibration is disabled! Enable it first with enable_offset_calibration: true",
cs);
return;
}
ESP_LOGI(TAG, "[CALIBRATION][%s] ", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] ===================== Power Offset Calibration =====================", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_active_power | offset_reactive_power |", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
for (uint8_t phase = 0; phase < 3; ++phase) {
int16_t active_offset = calibrate_power_offset(phase, false);
int16_t reactive_offset = calibrate_power_offset(phase, true);
this->write_power_offsets_to_registers_(phase, active_offset, reactive_offset);
ESP_LOGI(TAG, "[CALIBRATION] Phase %c - offset_active_power: %d, offset_reactive_power: %d", 'A' + phase,
active_offset, reactive_offset);
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase, active_offset,
reactive_offset);
}
ESP_LOGI(TAG, "[CALIBRATION][%s] =====================================================================\n", cs);
this->power_offset_pref_.save(&this->power_offset_phase_); // Save to flash
this->save_power_offset_calibration_to_memory_();
}
void ATM90E32Component::write_gains_to_registers_() {
@@ -632,102 +813,276 @@ void ATM90E32Component::write_power_offsets_to_registers_(uint8_t phase, int16_t
}
void ATM90E32Component::restore_gain_calibrations_() {
if (this->gain_calibration_pref_.load(&this->gain_phase_)) {
ESP_LOGI(TAG, "[CALIBRATION] Restoring saved gain calibrations to registers:");
for (uint8_t phase = 0; phase < 3; phase++) {
uint16_t v_gain = this->gain_phase_[phase].voltage_gain;
uint16_t i_gain = this->gain_phase_[phase].current_gain;
ESP_LOGI(TAG, "[CALIBRATION] Phase %c - Voltage Gain: %u, Current Gain: %u", 'A' + phase, v_gain, i_gain);
}
this->write_gains_to_registers_();
if (this->verify_gain_writes_()) {
this->using_saved_calibrations_ = true;
ESP_LOGI(TAG, "[CALIBRATION] Gain calibration loaded and verified successfully.");
} else {
this->using_saved_calibrations_ = false;
ESP_LOGE(TAG, "[CALIBRATION] Gain verification failed! Calibration may not be applied correctly.");
}
} else {
this->using_saved_calibrations_ = false;
ESP_LOGW(TAG, "[CALIBRATION] No stored gain calibrations found. Using config file values.");
const char *cs = this->cs_summary_.c_str();
for (uint8_t i = 0; i < 3; ++i) {
this->config_gain_phase_[i].voltage_gain = this->phase_[i].voltage_gain_;
this->config_gain_phase_[i].current_gain = this->phase_[i].ct_gain_;
this->gain_phase_[i] = this->config_gain_phase_[i];
}
if (this->gain_calibration_pref_.load(&this->gain_phase_)) {
bool all_zero = true;
bool same_as_config = true;
for (uint8_t phase = 0; phase < 3; ++phase) {
const auto &cfg = this->config_gain_phase_[phase];
const auto &saved = this->gain_phase_[phase];
if (saved.voltage_gain != 0 || saved.current_gain != 0)
all_zero = false;
if (saved.voltage_gain != cfg.voltage_gain || saved.current_gain != cfg.current_gain)
same_as_config = false;
}
if (!all_zero && !same_as_config) {
for (uint8_t phase = 0; phase < 3; ++phase) {
bool mismatch = false;
if (this->has_config_voltage_gain_[phase] &&
this->gain_phase_[phase].voltage_gain != this->config_gain_phase_[phase].voltage_gain)
mismatch = true;
if (this->has_config_current_gain_[phase] &&
this->gain_phase_[phase].current_gain != this->config_gain_phase_[phase].current_gain)
mismatch = true;
if (mismatch)
this->gain_calibration_mismatch_[phase] = true;
}
this->write_gains_to_registers_();
if (this->verify_gain_writes_()) {
this->using_saved_calibrations_ = true;
this->restored_gain_calibration_ = true;
return;
}
this->using_saved_calibrations_ = false;
ESP_LOGE(TAG, "[CALIBRATION][%s] Gain verification failed! Calibration may not be applied correctly.", cs);
}
}
this->using_saved_calibrations_ = false;
for (uint8_t i = 0; i < 3; ++i)
this->gain_phase_[i] = this->config_gain_phase_[i];
this->write_gains_to_registers_();
ESP_LOGW(TAG, "[CALIBRATION][%s] No stored gain calibrations found. Using config file values.", cs);
}
void ATM90E32Component::restore_offset_calibrations_() {
if (this->offset_pref_.load(&this->offset_phase_)) {
ESP_LOGI(TAG, "[CALIBRATION] Successfully restored offset calibration from memory.");
const char *cs = this->cs_summary_.c_str();
for (uint8_t i = 0; i < 3; ++i)
this->config_offset_phase_[i] = this->offset_phase_[i];
bool have_data = this->offset_pref_.load(&this->offset_phase_);
bool all_zero = true;
if (have_data) {
for (auto &phase : this->offset_phase_) {
if (phase.voltage_offset_ != 0 || phase.current_offset_ != 0) {
all_zero = false;
break;
}
}
}
if (have_data && !all_zero) {
this->restored_offset_calibration_ = true;
for (uint8_t phase = 0; phase < 3; phase++) {
auto &offset = this->offset_phase_[phase];
write_offsets_to_registers_(phase, offset.voltage_offset_, offset.current_offset_);
ESP_LOGI(TAG, "[CALIBRATION] Phase %c - offset_voltage:: %d, offset_current: %d", 'A' + phase,
offset.voltage_offset_, offset.current_offset_);
bool mismatch = false;
if (this->has_config_voltage_offset_[phase] &&
offset.voltage_offset_ != this->config_offset_phase_[phase].voltage_offset_)
mismatch = true;
if (this->has_config_current_offset_[phase] &&
offset.current_offset_ != this->config_offset_phase_[phase].current_offset_)
mismatch = true;
if (mismatch)
this->offset_calibration_mismatch_[phase] = true;
}
} else {
ESP_LOGW(TAG, "[CALIBRATION] No stored offset calibrations found. Using default values.");
for (uint8_t phase = 0; phase < 3; phase++)
this->offset_phase_[phase] = this->config_offset_phase_[phase];
ESP_LOGW(TAG, "[CALIBRATION][%s] No stored offset calibrations found. Using default values.", cs);
}
for (uint8_t phase = 0; phase < 3; phase++) {
write_offsets_to_registers_(phase, this->offset_phase_[phase].voltage_offset_,
this->offset_phase_[phase].current_offset_);
}
}
void ATM90E32Component::restore_power_offset_calibrations_() {
if (this->power_offset_pref_.load(&this->power_offset_phase_)) {
ESP_LOGI(TAG, "[CALIBRATION] Successfully restored power offset calibration from memory.");
const char *cs = this->cs_summary_.c_str();
for (uint8_t i = 0; i < 3; ++i)
this->config_power_offset_phase_[i] = this->power_offset_phase_[i];
bool have_data = this->power_offset_pref_.load(&this->power_offset_phase_);
bool all_zero = true;
if (have_data) {
for (auto &phase : this->power_offset_phase_) {
if (phase.active_power_offset != 0 || phase.reactive_power_offset != 0) {
all_zero = false;
break;
}
}
}
if (have_data && !all_zero) {
this->restored_power_offset_calibration_ = true;
for (uint8_t phase = 0; phase < 3; ++phase) {
auto &offset = this->power_offset_phase_[phase];
write_power_offsets_to_registers_(phase, offset.active_power_offset, offset.reactive_power_offset);
ESP_LOGI(TAG, "[CALIBRATION] Phase %c - offset_active_power: %d, offset_reactive_power: %d", 'A' + phase,
offset.active_power_offset, offset.reactive_power_offset);
bool mismatch = false;
if (this->has_config_active_power_offset_[phase] &&
offset.active_power_offset != this->config_power_offset_phase_[phase].active_power_offset)
mismatch = true;
if (this->has_config_reactive_power_offset_[phase] &&
offset.reactive_power_offset != this->config_power_offset_phase_[phase].reactive_power_offset)
mismatch = true;
if (mismatch)
this->power_offset_calibration_mismatch_[phase] = true;
}
} else {
ESP_LOGW(TAG, "[CALIBRATION] No stored power offsets found. Using default values.");
for (uint8_t phase = 0; phase < 3; ++phase)
this->power_offset_phase_[phase] = this->config_power_offset_phase_[phase];
ESP_LOGW(TAG, "[CALIBRATION][%s] No stored power offsets found. Using default values.", cs);
}
for (uint8_t phase = 0; phase < 3; ++phase) {
write_power_offsets_to_registers_(phase, this->power_offset_phase_[phase].active_power_offset,
this->power_offset_phase_[phase].reactive_power_offset);
}
}
void ATM90E32Component::clear_gain_calibrations() {
ESP_LOGI(TAG, "[CALIBRATION] Clearing stored gain calibrations and restoring config-defined values");
for (int phase = 0; phase < 3; phase++) {
gain_phase_[phase].voltage_gain = this->phase_[phase].voltage_gain_;
gain_phase_[phase].current_gain = this->phase_[phase].ct_gain_;
const char *cs = this->cs_summary_.c_str();
if (!this->using_saved_calibrations_) {
ESP_LOGI(TAG, "[CALIBRATION][%s] No stored gain calibrations to clear. Current values:", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] ----------------------------------------------------------", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | voltage_gain | current_gain |", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] ----------------------------------------------------------", cs);
for (int phase = 0; phase < 3; phase++) {
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6u | %6u |", cs, 'A' + phase,
this->gain_phase_[phase].voltage_gain, this->gain_phase_[phase].current_gain);
}
ESP_LOGI(TAG, "[CALIBRATION][%s] ==========================================================\n", cs);
return;
}
bool success = this->gain_calibration_pref_.save(&this->gain_phase_);
this->using_saved_calibrations_ = false;
ESP_LOGI(TAG, "[CALIBRATION][%s] Clearing stored gain calibrations and restoring config-defined values", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] ----------------------------------------------------------", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | voltage_gain | current_gain |", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] ----------------------------------------------------------", cs);
if (success) {
ESP_LOGI(TAG, "[CALIBRATION] Gain calibrations cleared. Config values restored:");
for (int phase = 0; phase < 3; phase++) {
ESP_LOGI(TAG, "[CALIBRATION] Phase %c - Voltage Gain: %u, Current Gain: %u", 'A' + phase,
gain_phase_[phase].voltage_gain, gain_phase_[phase].current_gain);
}
} else {
ESP_LOGE(TAG, "[CALIBRATION] Failed to clear gain calibrations!");
for (int phase = 0; phase < 3; phase++) {
uint16_t voltage_gain = this->phase_[phase].voltage_gain_;
uint16_t current_gain = this->phase_[phase].ct_gain_;
this->config_gain_phase_[phase].voltage_gain = voltage_gain;
this->config_gain_phase_[phase].current_gain = current_gain;
this->gain_phase_[phase].voltage_gain = voltage_gain;
this->gain_phase_[phase].current_gain = current_gain;
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6u | %6u |", cs, 'A' + phase, voltage_gain, current_gain);
}
ESP_LOGI(TAG, "[CALIBRATION][%s] ==========================================================\n", cs);
GainCalibration zero_gains[3]{{0, 0}, {0, 0}, {0, 0}};
bool success = this->gain_calibration_pref_.save(&zero_gains);
global_preferences->sync();
this->using_saved_calibrations_ = false;
this->restored_gain_calibration_ = false;
for (bool &phase : this->gain_calibration_mismatch_)
phase = false;
if (!success) {
ESP_LOGE(TAG, "[CALIBRATION][%s] Failed to clear gain calibrations!", cs);
}
this->write_gains_to_registers_(); // Apply them to the chip immediately
}
void ATM90E32Component::clear_offset_calibrations() {
for (uint8_t phase = 0; phase < 3; phase++) {
this->write_offsets_to_registers_(phase, 0, 0);
const char *cs = this->cs_summary_.c_str();
if (!this->restored_offset_calibration_) {
ESP_LOGI(TAG, "[CALIBRATION][%s] No stored offset calibrations to clear. Current values:", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] --------------------------------------------------------------", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_voltage | offset_current |", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] --------------------------------------------------------------", cs);
for (uint8_t phase = 0; phase < 3; phase++) {
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase,
this->offset_phase_[phase].voltage_offset_, this->offset_phase_[phase].current_offset_);
}
ESP_LOGI(TAG, "[CALIBRATION][%s] ==============================================================\n", cs);
return;
}
this->offset_pref_.save(&this->offset_phase_); // Save cleared values to flash memory
ESP_LOGI(TAG, "[CALIBRATION][%s] Clearing stored offset calibrations and restoring config-defined values", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] --------------------------------------------------------------", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_voltage | offset_current |", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] --------------------------------------------------------------", cs);
ESP_LOGI(TAG, "[CALIBRATION] Offsets cleared.");
for (uint8_t phase = 0; phase < 3; phase++) {
int16_t voltage_offset =
this->has_config_voltage_offset_[phase] ? this->config_offset_phase_[phase].voltage_offset_ : 0;
int16_t current_offset =
this->has_config_current_offset_[phase] ? this->config_offset_phase_[phase].current_offset_ : 0;
this->write_offsets_to_registers_(phase, voltage_offset, current_offset);
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase, voltage_offset,
current_offset);
}
ESP_LOGI(TAG, "[CALIBRATION][%s] ==============================================================\n", cs);
OffsetCalibration zero_offsets[3]{{0, 0}, {0, 0}, {0, 0}};
this->offset_pref_.save(&zero_offsets); // Clear stored values in flash
global_preferences->sync();
this->restored_offset_calibration_ = false;
for (bool &phase : this->offset_calibration_mismatch_)
phase = false;
ESP_LOGI(TAG, "[CALIBRATION][%s] Offsets cleared.", cs);
}
void ATM90E32Component::clear_power_offset_calibrations() {
for (uint8_t phase = 0; phase < 3; phase++) {
this->write_power_offsets_to_registers_(phase, 0, 0);
const char *cs = this->cs_summary_.c_str();
if (!this->restored_power_offset_calibration_) {
ESP_LOGI(TAG, "[CALIBRATION][%s] No stored power offsets to clear. Current values:", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_active_power | offset_reactive_power |", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
for (uint8_t phase = 0; phase < 3; phase++) {
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase,
this->power_offset_phase_[phase].active_power_offset,
this->power_offset_phase_[phase].reactive_power_offset);
}
ESP_LOGI(TAG, "[CALIBRATION][%s] =====================================================================\n", cs);
return;
}
this->power_offset_pref_.save(&this->power_offset_phase_);
ESP_LOGI(TAG, "[CALIBRATION][%s] Clearing stored power offsets and restoring config-defined values", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_active_power | offset_reactive_power |", cs);
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
ESP_LOGI(TAG, "[CALIBRATION] Power offsets cleared.");
for (uint8_t phase = 0; phase < 3; phase++) {
int16_t active_offset =
this->has_config_active_power_offset_[phase] ? this->config_power_offset_phase_[phase].active_power_offset : 0;
int16_t reactive_offset = this->has_config_reactive_power_offset_[phase]
? this->config_power_offset_phase_[phase].reactive_power_offset
: 0;
this->write_power_offsets_to_registers_(phase, active_offset, reactive_offset);
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase, active_offset,
reactive_offset);
}
ESP_LOGI(TAG, "[CALIBRATION][%s] =====================================================================\n", cs);
PowerOffsetCalibration zero_power_offsets[3]{{0, 0}, {0, 0}, {0, 0}};
this->power_offset_pref_.save(&zero_power_offsets);
global_preferences->sync();
this->restored_power_offset_calibration_ = false;
for (bool &phase : this->power_offset_calibration_mismatch_)
phase = false;
ESP_LOGI(TAG, "[CALIBRATION][%s] Power offsets cleared.", cs);
}
int16_t ATM90E32Component::calibrate_offset(uint8_t phase, bool voltage) {
@@ -748,20 +1103,21 @@ int16_t ATM90E32Component::calibrate_offset(uint8_t phase, bool voltage) {
int16_t ATM90E32Component::calibrate_power_offset(uint8_t phase, bool reactive) {
const uint8_t num_reads = 5;
uint64_t total_value = 0;
int64_t total_value = 0;
for (uint8_t i = 0; i < num_reads; ++i) {
uint32_t reading = reactive ? this->read32_(ATM90E32_REGISTER_QMEAN + phase, ATM90E32_REGISTER_QMEANLSB + phase)
: this->read32_(ATM90E32_REGISTER_PMEAN + phase, ATM90E32_REGISTER_PMEANLSB + phase);
int32_t reading = reactive ? this->read32_(ATM90E32_REGISTER_QMEAN + phase, ATM90E32_REGISTER_QMEANLSB + phase)
: this->read32_(ATM90E32_REGISTER_PMEAN + phase, ATM90E32_REGISTER_PMEANLSB + phase);
total_value += reading;
}
const uint32_t average_value = total_value / num_reads;
const uint32_t power_offset = ~average_value + 1;
int32_t average_value = total_value / num_reads;
int32_t power_offset = -average_value;
return static_cast<int16_t>(power_offset); // Takes the lower 16 bits
}
bool ATM90E32Component::verify_gain_writes_() {
const char *cs = this->cs_summary_.c_str();
bool success = true;
for (uint8_t phase = 0; phase < 3; phase++) {
uint16_t read_voltage = this->read16_(voltage_gain_registers[phase]);
@@ -769,7 +1125,7 @@ bool ATM90E32Component::verify_gain_writes_() {
if (read_voltage != this->gain_phase_[phase].voltage_gain ||
read_current != this->gain_phase_[phase].current_gain) {
ESP_LOGE(TAG, "[CALIBRATION] Mismatch detected for Phase %s!", phase_labels[phase]);
ESP_LOGE(TAG, "[CALIBRATION][%s] Mismatch detected for Phase %s!", cs, phase_labels[phase]);
success = false;
}
}
@@ -792,16 +1148,16 @@ void ATM90E32Component::check_phase_status() {
status += "Phase Loss; ";
auto *sensor = this->phase_status_text_sensor_[phase];
const char *phase_name = sensor ? sensor->get_name().c_str() : "Unknown Phase";
if (sensor == nullptr)
continue;
if (!status.empty()) {
status.pop_back(); // remove space
status.pop_back(); // remove semicolon
ESP_LOGW(TAG, "%s: %s", phase_name, status.c_str());
if (sensor != nullptr)
sensor->publish_state(status);
ESP_LOGW(TAG, "%s: %s", sensor->get_name().c_str(), status.c_str());
sensor->publish_state(status);
} else {
if (sensor != nullptr)
sensor->publish_state("Okay");
sensor->publish_state("Okay");
}
}
}
@@ -818,9 +1174,12 @@ void ATM90E32Component::check_freq_status() {
} else {
freq_status = "Normal";
}
ESP_LOGW(TAG, "Frequency status: %s", freq_status.c_str());
if (this->freq_status_text_sensor_ != nullptr) {
if (freq_status == "Normal") {
ESP_LOGD(TAG, "Frequency status: %s", freq_status.c_str());
} else {
ESP_LOGW(TAG, "Frequency status: %s", freq_status.c_str());
}
this->freq_status_text_sensor_->publish_state(freq_status);
}
}

View File

@@ -61,15 +61,29 @@ class ATM90E32Component : public PollingComponent,
this->phase_[phase].harmonic_active_power_sensor_ = obj;
}
void set_peak_current_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].peak_current_sensor_ = obj; }
void set_volt_gain(int phase, uint16_t gain) { this->phase_[phase].voltage_gain_ = gain; }
void set_ct_gain(int phase, uint16_t gain) { this->phase_[phase].ct_gain_ = gain; }
void set_voltage_offset(uint8_t phase, int16_t offset) { this->offset_phase_[phase].voltage_offset_ = offset; }
void set_current_offset(uint8_t phase, int16_t offset) { this->offset_phase_[phase].current_offset_ = offset; }
void set_volt_gain(int phase, uint16_t gain) {
this->phase_[phase].voltage_gain_ = gain;
this->has_config_voltage_gain_[phase] = true;
}
void set_ct_gain(int phase, uint16_t gain) {
this->phase_[phase].ct_gain_ = gain;
this->has_config_current_gain_[phase] = true;
}
void set_voltage_offset(uint8_t phase, int16_t offset) {
this->offset_phase_[phase].voltage_offset_ = offset;
this->has_config_voltage_offset_[phase] = true;
}
void set_current_offset(uint8_t phase, int16_t offset) {
this->offset_phase_[phase].current_offset_ = offset;
this->has_config_current_offset_[phase] = true;
}
void set_active_power_offset(uint8_t phase, int16_t offset) {
this->power_offset_phase_[phase].active_power_offset = offset;
this->has_config_active_power_offset_[phase] = true;
}
void set_reactive_power_offset(uint8_t phase, int16_t offset) {
this->power_offset_phase_[phase].reactive_power_offset = offset;
this->has_config_reactive_power_offset_[phase] = true;
}
void set_freq_sensor(sensor::Sensor *freq_sensor) { freq_sensor_ = freq_sensor; }
void set_peak_current_signed(bool flag) { peak_current_signed_ = flag; }
@@ -127,7 +141,7 @@ class ATM90E32Component : public PollingComponent,
#endif
uint16_t read16_(uint16_t a_register);
int read32_(uint16_t addr_h, uint16_t addr_l);
void write16_(uint16_t a_register, uint16_t val);
void write16_(uint16_t a_register, uint16_t val, bool validate = true);
float get_local_phase_voltage_(uint8_t phase);
float get_local_phase_current_(uint8_t phase);
float get_local_phase_active_power_(uint8_t phase);
@@ -159,12 +173,15 @@ class ATM90E32Component : public PollingComponent,
void restore_offset_calibrations_();
void restore_power_offset_calibrations_();
void restore_gain_calibrations_();
void save_offset_calibration_to_memory_();
void save_gain_calibration_to_memory_();
void save_power_offset_calibration_to_memory_();
void write_offsets_to_registers_(uint8_t phase, int16_t voltage_offset, int16_t current_offset);
void write_power_offsets_to_registers_(uint8_t phase, int16_t p_offset, int16_t q_offset);
void write_gains_to_registers_();
bool verify_gain_writes_();
bool validate_spi_read_(uint16_t expected, const char *context = nullptr);
void log_calibration_status_();
struct ATM90E32Phase {
uint16_t voltage_gain_{0};
@@ -204,19 +221,33 @@ class ATM90E32Component : public PollingComponent,
int16_t current_offset_{0};
} offset_phase_[3];
OffsetCalibration config_offset_phase_[3];
struct PowerOffsetCalibration {
int16_t active_power_offset{0};
int16_t reactive_power_offset{0};
} power_offset_phase_[3];
PowerOffsetCalibration config_power_offset_phase_[3];
struct GainCalibration {
uint16_t voltage_gain{1};
uint16_t current_gain{1};
} gain_phase_[3];
GainCalibration config_gain_phase_[3];
bool has_config_voltage_offset_[3]{false, false, false};
bool has_config_current_offset_[3]{false, false, false};
bool has_config_active_power_offset_[3]{false, false, false};
bool has_config_reactive_power_offset_[3]{false, false, false};
bool has_config_voltage_gain_[3]{false, false, false};
bool has_config_current_gain_[3]{false, false, false};
ESPPreferenceObject offset_pref_;
ESPPreferenceObject power_offset_pref_;
ESPPreferenceObject gain_calibration_pref_;
std::string cs_summary_;
sensor::Sensor *freq_sensor_{nullptr};
#ifdef USE_TEXT_SENSOR
@@ -231,6 +262,13 @@ class ATM90E32Component : public PollingComponent,
bool peak_current_signed_{false};
bool enable_offset_calibration_{false};
bool enable_gain_calibration_{false};
bool restored_offset_calibration_{false};
bool restored_power_offset_calibration_{false};
bool restored_gain_calibration_{false};
bool calibration_message_printed_{false};
bool offset_calibration_mismatch_[3]{false, false, false};
bool power_offset_calibration_mismatch_[3]{false, false, false};
bool gain_calibration_mismatch_[3]{false, false, false};
};
} // namespace atm90e32

View File

@@ -17,7 +17,6 @@ constexpr static const uint8_t AXS_READ_TOUCHPAD[11] = {0xb5, 0xab, 0xa5, 0x5a,
}
void AXS15231Touchscreen::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup();
this->reset_pin_->digital_write(false);
@@ -36,7 +35,6 @@ void AXS15231Touchscreen::setup() {
if (this->y_raw_max_ == 0) {
this->y_raw_max_ = this->display_->get_native_height();
}
ESP_LOGCONFIG(TAG, "AXS15231 Touchscreen setup complete");
}
void AXS15231Touchscreen::update_touches() {

View File

@@ -121,8 +121,6 @@ void spi_dma_tx_finish_callback(unsigned int param) {
}
void BekenSPILEDStripLightOutput::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
size_t buffer_size = this->get_buffer_size_();
size_t dma_buffer_size = (buffer_size * 8) + (2 * 64);

View File

@@ -38,7 +38,6 @@ MTreg:
*/
void BH1750Sensor::setup() {
ESP_LOGCONFIG(TAG, "Running setup for '%s'", this->name_.c_str());
uint8_t turn_on = BH1750_COMMAND_POWER_ON;
if (this->write(&turn_on, 1) != i2c::ERROR_OK) {
this->mark_failed();

View File

@@ -266,8 +266,10 @@ async def delayed_off_filter_to_code(config, filter_id):
async def autorepeat_filter_to_code(config, filter_id):
timings = []
if len(config) > 0:
for conf in config:
timings.append((conf[CONF_DELAY], conf[CONF_TIME_OFF], conf[CONF_TIME_ON]))
timings.extend(
(conf[CONF_DELAY], conf[CONF_TIME_OFF], conf[CONF_TIME_ON])
for conf in config
)
else:
timings.append(
(
@@ -514,6 +516,7 @@ def binary_sensor_schema(
icon: str = cv.UNDEFINED,
entity_category: str = cv.UNDEFINED,
device_class: str = cv.UNDEFINED,
filters: list = cv.UNDEFINED,
) -> cv.Schema:
schema = {}
@@ -525,6 +528,7 @@ def binary_sensor_schema(
(CONF_ICON, icon, cv.icon),
(CONF_ENTITY_CATEGORY, entity_category, cv.entity_category),
(CONF_DEVICE_CLASS, device_class, validate_device_class),
(CONF_FILTERS, filters, validate_filters),
]:
if default is not cv.UNDEFINED:
schema[cv.Optional(key, default=default)] = validator
@@ -573,16 +577,15 @@ async def setup_binary_sensor_core_(var, config):
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_MULTI_CLICK, []):
timings = []
for tim in conf[CONF_TIMING]:
timings.append(
cg.StructInitializer(
MultiClickTriggerEvent,
("state", tim[CONF_STATE]),
("min_length", tim[CONF_MIN_LENGTH]),
("max_length", tim.get(CONF_MAX_LENGTH, 4294967294)),
)
timings = [
cg.StructInitializer(
MultiClickTriggerEvent,
("state", tim[CONF_STATE]),
("min_length", tim[CONF_MIN_LENGTH]),
("max_length", tim.get(CONF_MAX_LENGTH, 4294967294)),
)
for tim in conf[CONF_TIMING]
]
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var, timings)
if CONF_INVALID_COOLDOWN in conf:
cg.add(trigger.set_invalid_cooldown(conf[CONF_INVALID_COOLDOWN]))
@@ -651,7 +654,6 @@ async def binary_sensor_is_off_to_code(config, condition_id, template_arg, args)
@coroutine_with_priority(100.0)
async def to_code(config):
cg.add_define("USE_BINARY_SENSOR")
cg.add_global(binary_sensor_ns.using)

View File

@@ -7,6 +7,19 @@ namespace binary_sensor {
static const char *const TAG = "binary_sensor";
// Function implementation of LOG_BINARY_SENSOR macro to reduce code size
void log_binary_sensor(const char *tag, const char *prefix, const char *type, BinarySensor *obj) {
if (obj == nullptr) {
return;
}
ESP_LOGCONFIG(tag, "%s%s '%s'", prefix, type, obj->get_name().c_str());
if (!obj->get_device_class().empty()) {
ESP_LOGCONFIG(tag, "%s Device Class: '%s'", prefix, obj->get_device_class().c_str());
}
}
void BinarySensor::publish_state(bool new_state) {
if (this->filter_list_ == nullptr) {
this->send_state_internal(new_state);

View File

@@ -10,13 +10,12 @@ namespace esphome {
namespace binary_sensor {
#define LOG_BINARY_SENSOR(prefix, type, obj) \
if ((obj) != nullptr) { \
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
if (!(obj)->get_device_class().empty()) { \
ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \
} \
}
// Forward declaration
class BinarySensor;
void log_binary_sensor(const char *tag, const char *prefix, const char *type, BinarySensor *obj);
// Macro that calls the function - kept for backward compatibility
#define LOG_BINARY_SENSOR(prefix, type, obj) log_binary_sensor(TAG, prefix, LOG_STR_LITERAL(type), obj)
#define SUB_BINARY_SENSOR(name) \
protected: \

View File

@@ -175,8 +175,7 @@ BLE_REMOVE_BOND_ACTION_SCHEMA = cv.Schema(
)
async def ble_disconnect_to_code(config, action_id, template_arg, args):
parent = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, parent)
return var
return cg.new_Pvariable(action_id, template_arg, parent)
@automation.register_action(
@@ -184,8 +183,7 @@ async def ble_disconnect_to_code(config, action_id, template_arg, args):
)
async def ble_connect_to_code(config, action_id, template_arg, args):
parent = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, parent)
return var
return cg.new_Pvariable(action_id, template_arg, parent)
@automation.register_action(
@@ -282,14 +280,13 @@ async def passkey_reply_to_code(config, action_id, template_arg, args):
)
async def remove_bond_to_code(config, action_id, template_arg, args):
parent = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, parent)
return var
return cg.new_Pvariable(action_id, template_arg, parent)
async def to_code(config):
# Register the loggers this component needs
esp32_ble.register_bt_logger(BTLoggers.GATT, BTLoggers.SMP)
cg.add_define("USE_ESP32_BLE_UUID")
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)

View File

@@ -1,13 +1,19 @@
import logging
import esphome.codegen as cg
from esphome.components import esp32_ble, esp32_ble_client, esp32_ble_tracker
from esphome.components.esp32 import add_idf_sdkconfig_option
from esphome.components.esp32_ble import BTLoggers
import esphome.config_validation as cv
from esphome.const import CONF_ACTIVE, CONF_ID
from esphome.core import CORE
from esphome.log import AnsiFore, color
AUTO_LOAD = ["esp32_ble_client", "esp32_ble_tracker"]
DEPENDENCIES = ["api", "esp32"]
CODEOWNERS = ["@jesserockz"]
CODEOWNERS = ["@jesserockz", "@bdraco"]
_LOGGER = logging.getLogger(__name__)
CONF_CONNECTION_SLOTS = "connection_slots"
CONF_CACHE_SERVICES = "cache_services"
@@ -41,6 +47,27 @@ def validate_connections(config):
esp32_ble_tracker.consume_connection_slots(connection_slots, "bluetooth_proxy")(
config
)
# Warn about connection slot waste when using Arduino framework
if CORE.using_arduino and connection_slots:
_LOGGER.warning(
"Bluetooth Proxy with active connections on Arduino framework has suboptimal performance.\n"
"If BLE connections fail, they can waste connection slots for 10 seconds because\n"
"Arduino doesn't allow configuring the BLE connection timeout (fixed at 30s).\n"
"ESP-IDF framework allows setting it to 20s to match client timeouts.\n"
"\n"
"To switch to ESP-IDF, add this to your YAML:\n"
" esp32:\n"
" framework:\n"
" type: esp-idf\n"
"\n"
"For detailed migration instructions, see:\n"
"%s",
color(
AnsiFore.BLUE, "https://esphome.io/guides/esp32_arduino_to_idf.html"
),
)
return {
**config,
CONF_CONNECTIONS: [CONNECTION_SCHEMA({}) for _ in range(connection_slots)],
@@ -87,6 +114,16 @@ async def to_code(config):
cg.add(var.set_active(config[CONF_ACTIVE]))
await esp32_ble_tracker.register_raw_ble_device(var, config)
# Define max connections for protobuf fixed array
connection_count = len(config.get(CONF_CONNECTIONS, []))
cg.add_define("BLUETOOTH_PROXY_MAX_CONNECTIONS", connection_count)
# Define batch size for BLE advertisements
# Each advertisement is up to 80 bytes when packaged (including protocol overhead)
# 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
cg.add_define("BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE", 16)
for connection_conf in config.get(CONF_CONNECTIONS, []):
connection_var = cg.new_Pvariable(connection_conf[CONF_ID])
await cg.register_component(connection_var, connection_conf)

View File

@@ -12,16 +12,79 @@ namespace esphome::bluetooth_proxy {
static const char *const TAG = "bluetooth_proxy.connection";
// This function is allocation-free and directly packs UUIDs into the output array
// using precalculated constants for the Bluetooth base UUID
static void fill_128bit_uuid_array(std::array<uint64_t, 2> &out, esp_bt_uuid_t uuid_source) {
esp_bt_uuid_t uuid = espbt::ESPBTUUID::from_uuid(uuid_source).as_128bit().get_uuid();
out[0] = ((uint64_t) uuid.uuid.uuid128[15] << 56) | ((uint64_t) uuid.uuid.uuid128[14] << 48) |
((uint64_t) uuid.uuid.uuid128[13] << 40) | ((uint64_t) uuid.uuid.uuid128[12] << 32) |
((uint64_t) uuid.uuid.uuid128[11] << 24) | ((uint64_t) uuid.uuid.uuid128[10] << 16) |
((uint64_t) uuid.uuid.uuid128[9] << 8) | ((uint64_t) uuid.uuid.uuid128[8]);
out[1] = ((uint64_t) uuid.uuid.uuid128[7] << 56) | ((uint64_t) uuid.uuid.uuid128[6] << 48) |
((uint64_t) uuid.uuid.uuid128[5] << 40) | ((uint64_t) uuid.uuid.uuid128[4] << 32) |
((uint64_t) uuid.uuid.uuid128[3] << 24) | ((uint64_t) uuid.uuid.uuid128[2] << 16) |
((uint64_t) uuid.uuid.uuid128[1] << 8) | ((uint64_t) uuid.uuid.uuid128[0]);
// Bluetooth base UUID: 00000000-0000-1000-8000-00805F9B34FB
// out[0] = bytes 8-15 (big-endian)
// - For 128-bit UUIDs: use bytes 8-15 as-is
// - For 16/32-bit UUIDs: insert into bytes 12-15, use 0x00001000 for bytes 8-11
out[0] = uuid_source.len == ESP_UUID_LEN_128
? (((uint64_t) uuid_source.uuid.uuid128[15] << 56) | ((uint64_t) uuid_source.uuid.uuid128[14] << 48) |
((uint64_t) uuid_source.uuid.uuid128[13] << 40) | ((uint64_t) uuid_source.uuid.uuid128[12] << 32) |
((uint64_t) uuid_source.uuid.uuid128[11] << 24) | ((uint64_t) uuid_source.uuid.uuid128[10] << 16) |
((uint64_t) uuid_source.uuid.uuid128[9] << 8) | ((uint64_t) uuid_source.uuid.uuid128[8]))
: (((uint64_t) (uuid_source.len == ESP_UUID_LEN_16 ? uuid_source.uuid.uuid16 : uuid_source.uuid.uuid32)
<< 32) |
0x00001000ULL); // Base UUID bytes 8-11
// out[1] = bytes 0-7 (big-endian)
// - For 128-bit UUIDs: use bytes 0-7 as-is
// - For 16/32-bit UUIDs: use precalculated base UUID constant
out[1] = uuid_source.len == ESP_UUID_LEN_128
? ((uint64_t) uuid_source.uuid.uuid128[7] << 56) | ((uint64_t) uuid_source.uuid.uuid128[6] << 48) |
((uint64_t) uuid_source.uuid.uuid128[5] << 40) | ((uint64_t) uuid_source.uuid.uuid128[4] << 32) |
((uint64_t) uuid_source.uuid.uuid128[3] << 24) | ((uint64_t) uuid_source.uuid.uuid128[2] << 16) |
((uint64_t) uuid_source.uuid.uuid128[1] << 8) | ((uint64_t) uuid_source.uuid.uuid128[0])
: 0x800000805F9B34FBULL; // Base UUID bytes 0-7: 80-00-00-80-5F-9B-34-FB
}
// Helper to fill UUID in the appropriate format based on client support and UUID type
static void fill_gatt_uuid(std::array<uint64_t, 2> &uuid_128, uint32_t &short_uuid, const esp_bt_uuid_t &uuid,
bool use_efficient_uuids) {
if (!use_efficient_uuids || uuid.len == ESP_UUID_LEN_128) {
// Use 128-bit format for old clients or when UUID is already 128-bit
fill_128bit_uuid_array(uuid_128, uuid);
} else if (uuid.len == ESP_UUID_LEN_16) {
short_uuid = uuid.uuid.uuid16;
} else if (uuid.len == ESP_UUID_LEN_32) {
short_uuid = uuid.uuid.uuid32;
}
}
// Constants for size estimation
static constexpr uint8_t SERVICE_OVERHEAD_LEGACY = 25; // UUID(20) + handle(4) + overhead(1)
static constexpr uint8_t SERVICE_OVERHEAD_EFFICIENT = 10; // UUID(6) + handle(4)
static constexpr uint8_t CHAR_SIZE_128BIT = 35; // UUID(20) + handle(4) + props(4) + overhead(7)
static constexpr uint8_t DESC_SIZE_128BIT = 25; // UUID(20) + handle(4) + overhead(1)
static constexpr uint8_t DESC_SIZE_16BIT = 10; // UUID(6) + handle(4)
static constexpr uint8_t DESC_PER_CHAR = 1; // Assume 1 descriptor per characteristic
// Helper to estimate service size before fetching all data
/**
* Estimate the size of a Bluetooth service based on the number of characteristics and UUID format.
*
* @param char_count The number of characteristics in the service.
* @param use_efficient_uuids Whether to use efficient UUIDs (16-bit or 32-bit) for newer APIVersions.
* @return The estimated size of the service in bytes.
*
* This function calculates the size of a Bluetooth service by considering:
* - A service overhead, which depends on whether efficient UUIDs are used.
* - The size of each characteristic, assuming 128-bit UUIDs for safety.
* - The size of descriptors, assuming one 128-bit descriptor per characteristic.
*/
static size_t estimate_service_size(uint16_t char_count, bool use_efficient_uuids) {
size_t service_overhead = use_efficient_uuids ? SERVICE_OVERHEAD_EFFICIENT : SERVICE_OVERHEAD_LEGACY;
// Always assume 128-bit UUIDs for characteristics to be safe
size_t char_size = CHAR_SIZE_128BIT;
// Assume one 128-bit descriptor per characteristic
size_t desc_size = DESC_SIZE_128BIT * DESC_PER_CHAR;
return service_overhead + (char_size + desc_size) * char_count;
}
bool BluetoothConnection::supports_efficient_uuids_() const {
auto *api_conn = this->proxy_->get_api_connection();
return api_conn && api_conn->client_supports_api_version(1, 12);
}
void BluetoothConnection::dump_config() {
@@ -29,16 +92,53 @@ void BluetoothConnection::dump_config() {
BLEClientBase::dump_config();
}
void BluetoothConnection::update_allocated_slot_(uint64_t find_value, uint64_t set_value) {
auto &allocated = this->proxy_->connections_free_response_.allocated;
for (auto &slot : allocated) {
if (slot == find_value) {
slot = set_value;
return;
}
}
}
void BluetoothConnection::set_address(uint64_t address) {
// If we're clearing an address (disconnecting), update the pre-allocated message
if (address == 0 && this->address_ != 0) {
this->proxy_->connections_free_response_.free++;
this->update_allocated_slot_(this->address_, 0);
}
// If we're setting a new address (connecting), update the pre-allocated message
else if (address != 0 && this->address_ == 0) {
this->proxy_->connections_free_response_.free--;
this->update_allocated_slot_(0, address);
}
// Call parent implementation to actually set the address
BLEClientBase::set_address(address);
}
void BluetoothConnection::loop() {
BLEClientBase::loop();
// Early return if no active connection or not in service discovery phase
if (this->address_ == 0 || this->send_service_ < 0 || this->send_service_ > this->service_count_) {
// Early return if no active connection
if (this->address_ == 0) {
return;
}
// Handle service discovery
this->send_service_for_discovery_();
// Handle service discovery if in valid range
if (this->send_service_ >= 0 && this->send_service_ <= this->service_count_) {
this->send_service_for_discovery_();
}
// Check if we should disable the loop
// - For V3_WITH_CACHE: Services are never sent, disable after INIT state
// - For V3_WITHOUT_CACHE: Disable only after service discovery is complete
// (send_service_ == DONE_SENDING_SERVICES, which is only set after services are sent)
if (this->state_ != espbt::ClientState::INIT && (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE ||
this->send_service_ == DONE_SENDING_SERVICES)) {
this->disable_loop();
}
}
void BluetoothConnection::reset_connection_(esp_err_t reason) {
@@ -52,138 +152,222 @@ void BluetoothConnection::reset_connection_(esp_err_t reason) {
// to detect incomplete service discovery rather than relying on us to
// tell them about a partial list.
this->set_address(0);
this->send_service_ = DONE_SENDING_SERVICES;
this->send_service_ = INIT_SENDING_SERVICES;
this->proxy_->send_connections_free();
}
void BluetoothConnection::send_service_for_discovery_() {
if (this->send_service_ == this->service_count_) {
if (this->send_service_ >= this->service_count_) {
this->send_service_ = DONE_SENDING_SERVICES;
this->proxy_->send_gatt_services_done(this->address_);
if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE ||
this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
this->release_services();
}
this->release_services();
return;
}
// Early return if no API connection
auto *api_conn = this->proxy_->get_api_connection();
if (api_conn == nullptr) {
this->send_service_ = DONE_SENDING_SERVICES;
return;
}
// Send next service
esp_gattc_service_elem_t service_result;
uint16_t service_count = 1;
esp_gatt_status_t service_status = esp_ble_gattc_get_service(this->gattc_if_, this->conn_id_, nullptr,
&service_result, &service_count, this->send_service_);
this->send_service_++;
if (service_status != ESP_GATT_OK) {
ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_service error at offset=%d, status=%d", this->connection_index_,
this->address_str().c_str(), this->send_service_ - 1, service_status);
return;
}
if (service_count == 0) {
ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_service missing, service_count=%d", this->connection_index_,
this->address_str().c_str(), service_count);
return;
}
// Check if client supports efficient UUIDs
bool use_efficient_uuids = this->supports_efficient_uuids_();
// Prepare response
api::BluetoothGATTGetServicesResponse resp;
resp.address = this->address_;
auto &service_resp = resp.services[0];
fill_128bit_uuid_array(service_resp.uuid, service_result.uuid);
service_resp.handle = service_result.start_handle;
// Get the number of characteristics directly with one call
uint16_t total_char_count = 0;
esp_gatt_status_t char_count_status =
esp_ble_gattc_get_attr_count(this->gattc_if_, this->conn_id_, ESP_GATT_DB_CHARACTERISTIC,
service_result.start_handle, service_result.end_handle, 0, &total_char_count);
// Dynamic batching based on actual size
// Conservative MTU limit for API messages (accounts for WPA3 overhead)
static constexpr size_t MAX_PACKET_SIZE = 1360;
if (char_count_status == ESP_GATT_OK && total_char_count > 0) {
// Only reserve if we successfully got a count
service_resp.characteristics.reserve(total_char_count);
} else if (char_count_status != ESP_GATT_OK) {
ESP_LOGW(TAG, "[%d] [%s] Error getting characteristic count, status=%d", this->connection_index_,
this->address_str().c_str(), char_count_status);
// Keep running total of actual message size
size_t current_size = 0;
api::ProtoSize size;
resp.calculate_size(size);
current_size = size.get_size();
while (this->send_service_ < this->service_count_) {
esp_gattc_service_elem_t service_result;
uint16_t service_count = 1;
esp_gatt_status_t service_status = esp_ble_gattc_get_service(this->gattc_if_, this->conn_id_, nullptr,
&service_result, &service_count, this->send_service_);
if (service_status != ESP_GATT_OK || service_count == 0) {
ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_service %s, status=%d, service_count=%d, offset=%d",
this->connection_index_, this->address_str().c_str(),
service_status != ESP_GATT_OK ? "error" : "missing", service_status, service_count, this->send_service_);
this->send_service_ = DONE_SENDING_SERVICES;
return;
}
// Get the number of characteristics BEFORE adding to response
uint16_t total_char_count = 0;
esp_gatt_status_t char_count_status =
esp_ble_gattc_get_attr_count(this->gattc_if_, this->conn_id_, ESP_GATT_DB_CHARACTERISTIC,
service_result.start_handle, service_result.end_handle, 0, &total_char_count);
if (char_count_status != ESP_GATT_OK) {
this->log_connection_error_("esp_ble_gattc_get_attr_count", char_count_status);
this->send_service_ = DONE_SENDING_SERVICES;
return;
}
// If this service likely won't fit, send current batch (unless it's the first)
size_t estimated_size = estimate_service_size(total_char_count, use_efficient_uuids);
if (!resp.services.empty() && (current_size + estimated_size > MAX_PACKET_SIZE)) {
// This service likely won't fit, send current batch
break;
}
// Now add the service since we know it will likely fit
resp.services.emplace_back();
auto &service_resp = resp.services.back();
fill_gatt_uuid(service_resp.uuid, service_resp.short_uuid, service_result.uuid, use_efficient_uuids);
service_resp.handle = service_result.start_handle;
if (total_char_count > 0) {
// Reserve space and process characteristics
service_resp.characteristics.reserve(total_char_count);
uint16_t char_offset = 0;
esp_gattc_char_elem_t char_result;
while (true) { // characteristics
uint16_t char_count = 1;
esp_gatt_status_t char_status =
esp_ble_gattc_get_all_char(this->gattc_if_, this->conn_id_, service_result.start_handle,
service_result.end_handle, &char_result, &char_count, char_offset);
if (char_status == ESP_GATT_INVALID_OFFSET || char_status == ESP_GATT_NOT_FOUND) {
break;
}
if (char_status != ESP_GATT_OK) {
this->log_connection_error_("esp_ble_gattc_get_all_char", char_status);
this->send_service_ = DONE_SENDING_SERVICES;
return;
}
if (char_count == 0) {
break;
}
service_resp.characteristics.emplace_back();
auto &characteristic_resp = service_resp.characteristics.back();
fill_gatt_uuid(characteristic_resp.uuid, characteristic_resp.short_uuid, char_result.uuid, use_efficient_uuids);
characteristic_resp.handle = char_result.char_handle;
characteristic_resp.properties = char_result.properties;
char_offset++;
// Get the number of descriptors directly with one call
uint16_t total_desc_count = 0;
esp_gatt_status_t desc_count_status = esp_ble_gattc_get_attr_count(
this->gattc_if_, this->conn_id_, ESP_GATT_DB_DESCRIPTOR, 0, 0, char_result.char_handle, &total_desc_count);
if (desc_count_status != ESP_GATT_OK) {
this->log_connection_error_("esp_ble_gattc_get_attr_count", desc_count_status);
this->send_service_ = DONE_SENDING_SERVICES;
return;
}
if (total_desc_count == 0) {
// No descriptors, continue to next characteristic
continue;
}
// Reserve space and process descriptors
characteristic_resp.descriptors.reserve(total_desc_count);
uint16_t desc_offset = 0;
esp_gattc_descr_elem_t desc_result;
while (true) { // descriptors
uint16_t desc_count = 1;
esp_gatt_status_t desc_status = esp_ble_gattc_get_all_descr(
this->gattc_if_, this->conn_id_, char_result.char_handle, &desc_result, &desc_count, desc_offset);
if (desc_status == ESP_GATT_INVALID_OFFSET || desc_status == ESP_GATT_NOT_FOUND) {
break;
}
if (desc_status != ESP_GATT_OK) {
this->log_connection_error_("esp_ble_gattc_get_all_descr", desc_status);
this->send_service_ = DONE_SENDING_SERVICES;
return;
}
if (desc_count == 0) {
break; // No more descriptors
}
characteristic_resp.descriptors.emplace_back();
auto &descriptor_resp = characteristic_resp.descriptors.back();
fill_gatt_uuid(descriptor_resp.uuid, descriptor_resp.short_uuid, desc_result.uuid, use_efficient_uuids);
descriptor_resp.handle = desc_result.handle;
desc_offset++;
}
}
} // end if (total_char_count > 0)
// Calculate the actual size of just this service
api::ProtoSize service_sizer;
service_resp.calculate_size(service_sizer);
size_t service_size = service_sizer.get_size() + 1; // +1 for field tag
// Check if adding this service would exceed the limit
if (current_size + service_size > MAX_PACKET_SIZE) {
// We would go over - pop the last service if we have more than one
if (resp.services.size() > 1) {
resp.services.pop_back();
ESP_LOGD(TAG, "[%d] [%s] Service %d would exceed limit (current: %d + service: %d > %d), sending current batch",
this->connection_index_, this->address_str().c_str(), this->send_service_, current_size, service_size,
MAX_PACKET_SIZE);
// Don't increment send_service_ - we'll retry this service in next batch
} else {
// This single service is too large, but we have to send it anyway
ESP_LOGV(TAG, "[%d] [%s] Service %d is too large (%d bytes) but sending anyway", this->connection_index_,
this->address_str().c_str(), this->send_service_, service_size);
// Increment so we don't get stuck
this->send_service_++;
}
// Send what we have
break;
}
// Now we know we're keeping this service, add its size
current_size += service_size;
// Successfully added this service, increment counter
this->send_service_++;
}
// Now process characteristics
uint16_t char_offset = 0;
esp_gattc_char_elem_t char_result;
while (true) { // characteristics
uint16_t char_count = 1;
esp_gatt_status_t char_status =
esp_ble_gattc_get_all_char(this->gattc_if_, this->conn_id_, service_result.start_handle,
service_result.end_handle, &char_result, &char_count, char_offset);
if (char_status == ESP_GATT_INVALID_OFFSET || char_status == ESP_GATT_NOT_FOUND) {
break;
}
if (char_status != ESP_GATT_OK) {
ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_all_char error, status=%d", this->connection_index_,
this->address_str().c_str(), char_status);
break;
}
if (char_count == 0) {
break;
}
service_resp.characteristics.emplace_back();
auto &characteristic_resp = service_resp.characteristics.back();
fill_128bit_uuid_array(characteristic_resp.uuid, char_result.uuid);
characteristic_resp.handle = char_result.char_handle;
characteristic_resp.properties = char_result.properties;
char_offset++;
// Get the number of descriptors directly with one call
uint16_t total_desc_count = 0;
esp_gatt_status_t desc_count_status =
esp_ble_gattc_get_attr_count(this->gattc_if_, this->conn_id_, ESP_GATT_DB_DESCRIPTOR, char_result.char_handle,
service_result.end_handle, 0, &total_desc_count);
if (desc_count_status == ESP_GATT_OK && total_desc_count > 0) {
// Only reserve if we successfully got a count
characteristic_resp.descriptors.reserve(total_desc_count);
} else if (desc_count_status != ESP_GATT_OK) {
ESP_LOGW(TAG, "[%d] [%s] Error getting descriptor count for char handle %d, status=%d", this->connection_index_,
this->address_str().c_str(), char_result.char_handle, desc_count_status);
}
// Now process descriptors
uint16_t desc_offset = 0;
esp_gattc_descr_elem_t desc_result;
while (true) { // descriptors
uint16_t desc_count = 1;
esp_gatt_status_t desc_status = esp_ble_gattc_get_all_descr(
this->gattc_if_, this->conn_id_, char_result.char_handle, &desc_result, &desc_count, desc_offset);
if (desc_status == ESP_GATT_INVALID_OFFSET || desc_status == ESP_GATT_NOT_FOUND) {
break;
}
if (desc_status != ESP_GATT_OK) {
ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_all_descr error, status=%d", this->connection_index_,
this->address_str().c_str(), desc_status);
break;
}
if (desc_count == 0) {
break;
}
characteristic_resp.descriptors.emplace_back();
auto &descriptor_resp = characteristic_resp.descriptors.back();
fill_128bit_uuid_array(descriptor_resp.uuid, desc_result.uuid);
descriptor_resp.handle = desc_result.handle;
desc_offset++;
}
}
// Send the message (we already checked api_conn is not null at the beginning)
// Send the message with dynamically batched services
api_conn->send_message(resp, api::BluetoothGATTGetServicesResponse::MESSAGE_TYPE);
}
void BluetoothConnection::log_connection_error_(const char *operation, esp_gatt_status_t status) {
ESP_LOGE(TAG, "[%d] [%s] %s error, status=%d", this->connection_index_, this->address_str().c_str(), operation,
status);
}
void BluetoothConnection::log_connection_warning_(const char *operation, esp_err_t err) {
ESP_LOGW(TAG, "[%d] [%s] %s failed, err=%d", this->connection_index_, this->address_str().c_str(), operation, err);
}
void BluetoothConnection::log_gatt_not_connected_(const char *action, const char *type) {
ESP_LOGW(TAG, "[%d] [%s] Cannot %s GATT %s, not connected.", this->connection_index_, this->address_str().c_str(),
action, type);
}
void BluetoothConnection::log_gatt_operation_error_(const char *operation, uint16_t handle, esp_gatt_status_t status) {
ESP_LOGW(TAG, "[%d] [%s] Error %s for handle 0x%2X, status=%d", this->connection_index_, this->address_str().c_str(),
operation, handle, status);
}
esp_err_t BluetoothConnection::check_and_log_error_(const char *operation, esp_err_t err) {
if (err != ESP_OK) {
this->log_connection_warning_(operation, err);
return err;
}
return ESP_OK;
}
bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) {
if (!BLEClientBase::gattc_event_handler(event, gattc_if, param))
@@ -191,10 +375,19 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
switch (event) {
case ESP_GATTC_DISCONNECT_EVT: {
this->reset_connection_(param->disconnect.reason);
// Don't reset connection yet - wait for CLOSE_EVT to ensure controller has freed resources
// This prevents race condition where we mark slot as free before controller cleanup is complete
ESP_LOGD(TAG, "[%d] [%s] Disconnect, reason=0x%02x", this->connection_index_, this->address_str_.c_str(),
param->disconnect.reason);
// Send disconnection notification but don't free the slot yet
this->proxy_->send_device_connection(this->address_, false, 0, param->disconnect.reason);
break;
}
case ESP_GATTC_CLOSE_EVT: {
ESP_LOGD(TAG, "[%d] [%s] Close, reason=0x%02x, freeing slot", this->connection_index_, this->address_str_.c_str(),
param->close.reason);
// Now the GATT connection is fully closed and controller resources are freed
// Safe to mark the connection slot as available
this->reset_connection_(param->close.reason);
break;
}
@@ -224,8 +417,7 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
case ESP_GATTC_READ_DESCR_EVT:
case ESP_GATTC_READ_CHAR_EVT: {
if (param->read.status != ESP_GATT_OK) {
ESP_LOGW(TAG, "[%d] [%s] Error reading char/descriptor at handle 0x%2X, status=%d", this->connection_index_,
this->address_str_.c_str(), param->read.handle, param->read.status);
this->log_gatt_operation_error_("reading char/descriptor", param->read.handle, param->read.status);
this->proxy_->send_gatt_error(this->address_, param->read.handle, param->read.status);
break;
}
@@ -239,8 +431,7 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
case ESP_GATTC_WRITE_CHAR_EVT:
case ESP_GATTC_WRITE_DESCR_EVT: {
if (param->write.status != ESP_GATT_OK) {
ESP_LOGW(TAG, "[%d] [%s] Error writing char/descriptor at handle 0x%2X, status=%d", this->connection_index_,
this->address_str_.c_str(), param->write.handle, param->write.status);
this->log_gatt_operation_error_("writing char/descriptor", param->write.handle, param->write.status);
this->proxy_->send_gatt_error(this->address_, param->write.handle, param->write.status);
break;
}
@@ -252,9 +443,8 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
}
case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: {
if (param->unreg_for_notify.status != ESP_GATT_OK) {
ESP_LOGW(TAG, "[%d] [%s] Error unregistering notifications for handle 0x%2X, status=%d",
this->connection_index_, this->address_str_.c_str(), param->unreg_for_notify.handle,
param->unreg_for_notify.status);
this->log_gatt_operation_error_("unregistering notifications", param->unreg_for_notify.handle,
param->unreg_for_notify.status);
this->proxy_->send_gatt_error(this->address_, param->unreg_for_notify.handle, param->unreg_for_notify.status);
break;
}
@@ -266,8 +456,8 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
}
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
if (param->reg_for_notify.status != ESP_GATT_OK) {
ESP_LOGW(TAG, "[%d] [%s] Error registering notifications for handle 0x%2X, status=%d", this->connection_index_,
this->address_str_.c_str(), param->reg_for_notify.handle, param->reg_for_notify.status);
this->log_gatt_operation_error_("registering notifications", param->reg_for_notify.handle,
param->reg_for_notify.status);
this->proxy_->send_gatt_error(this->address_, param->reg_for_notify.handle, param->reg_for_notify.status);
break;
}
@@ -313,8 +503,7 @@ void BluetoothConnection::gap_event_handler(esp_gap_ble_cb_event_t event, esp_bl
esp_err_t BluetoothConnection::read_characteristic(uint16_t handle) {
if (!this->connected()) {
ESP_LOGW(TAG, "[%d] [%s] Cannot read GATT characteristic, not connected.", this->connection_index_,
this->address_str_.c_str());
this->log_gatt_not_connected_("read", "characteristic");
return ESP_GATT_NOT_CONNECTED;
}
@@ -322,18 +511,12 @@ esp_err_t BluetoothConnection::read_characteristic(uint16_t handle) {
handle);
esp_err_t err = esp_ble_gattc_read_char(this->gattc_if_, this->conn_id_, handle, ESP_GATT_AUTH_REQ_NONE);
if (err != ERR_OK) {
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_read_char error, err=%d", this->connection_index_,
this->address_str_.c_str(), err);
return err;
}
return ESP_OK;
return this->check_and_log_error_("esp_ble_gattc_read_char", err);
}
esp_err_t BluetoothConnection::write_characteristic(uint16_t handle, const std::string &data, bool response) {
if (!this->connected()) {
ESP_LOGW(TAG, "[%d] [%s] Cannot write GATT characteristic, not connected.", this->connection_index_,
this->address_str_.c_str());
this->log_gatt_not_connected_("write", "characteristic");
return ESP_GATT_NOT_CONNECTED;
}
ESP_LOGV(TAG, "[%d] [%s] Writing GATT characteristic handle %d", this->connection_index_, this->address_str_.c_str(),
@@ -342,36 +525,24 @@ esp_err_t BluetoothConnection::write_characteristic(uint16_t handle, const std::
esp_err_t err =
esp_ble_gattc_write_char(this->gattc_if_, this->conn_id_, handle, data.size(), (uint8_t *) data.data(),
response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
if (err != ERR_OK) {
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_write_char error, err=%d", this->connection_index_,
this->address_str_.c_str(), err);
return err;
}
return ESP_OK;
return this->check_and_log_error_("esp_ble_gattc_write_char", err);
}
esp_err_t BluetoothConnection::read_descriptor(uint16_t handle) {
if (!this->connected()) {
ESP_LOGW(TAG, "[%d] [%s] Cannot read GATT descriptor, not connected.", this->connection_index_,
this->address_str_.c_str());
this->log_gatt_not_connected_("read", "descriptor");
return ESP_GATT_NOT_CONNECTED;
}
ESP_LOGV(TAG, "[%d] [%s] Reading GATT descriptor handle %d", this->connection_index_, this->address_str_.c_str(),
handle);
esp_err_t err = esp_ble_gattc_read_char_descr(this->gattc_if_, this->conn_id_, handle, ESP_GATT_AUTH_REQ_NONE);
if (err != ERR_OK) {
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_read_char_descr error, err=%d", this->connection_index_,
this->address_str_.c_str(), err);
return err;
}
return ESP_OK;
return this->check_and_log_error_("esp_ble_gattc_read_char_descr", err);
}
esp_err_t BluetoothConnection::write_descriptor(uint16_t handle, const std::string &data, bool response) {
if (!this->connected()) {
ESP_LOGW(TAG, "[%d] [%s] Cannot write GATT descriptor, not connected.", this->connection_index_,
this->address_str_.c_str());
this->log_gatt_not_connected_("write", "descriptor");
return ESP_GATT_NOT_CONNECTED;
}
ESP_LOGV(TAG, "[%d] [%s] Writing GATT descriptor handle %d", this->connection_index_, this->address_str_.c_str(),
@@ -380,18 +551,12 @@ esp_err_t BluetoothConnection::write_descriptor(uint16_t handle, const std::stri
esp_err_t err = esp_ble_gattc_write_char_descr(
this->gattc_if_, this->conn_id_, handle, data.size(), (uint8_t *) data.data(),
response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
if (err != ERR_OK) {
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_write_char_descr error, err=%d", this->connection_index_,
this->address_str_.c_str(), err);
return err;
}
return ESP_OK;
return this->check_and_log_error_("esp_ble_gattc_write_char_descr", err);
}
esp_err_t BluetoothConnection::notify_characteristic(uint16_t handle, bool enable) {
if (!this->connected()) {
ESP_LOGW(TAG, "[%d] [%s] Cannot notify GATT characteristic, not connected.", this->connection_index_,
this->address_str_.c_str());
this->log_gatt_not_connected_("notify", "characteristic");
return ESP_GATT_NOT_CONNECTED;
}
@@ -399,22 +564,13 @@ esp_err_t BluetoothConnection::notify_characteristic(uint16_t handle, bool enabl
ESP_LOGV(TAG, "[%d] [%s] Registering for GATT characteristic notifications handle %d", this->connection_index_,
this->address_str_.c_str(), handle);
esp_err_t err = esp_ble_gattc_register_for_notify(this->gattc_if_, this->remote_bda_, handle);
if (err != ESP_OK) {
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_register_for_notify failed, err=%d", this->connection_index_,
this->address_str_.c_str(), err);
return err;
}
} else {
ESP_LOGV(TAG, "[%d] [%s] Unregistering for GATT characteristic notifications handle %d", this->connection_index_,
this->address_str_.c_str(), handle);
esp_err_t err = esp_ble_gattc_unregister_for_notify(this->gattc_if_, this->remote_bda_, handle);
if (err != ESP_OK) {
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_unregister_for_notify failed, err=%d", this->connection_index_,
this->address_str_.c_str(), err);
return err;
}
return this->check_and_log_error_("esp_ble_gattc_register_for_notify", err);
}
return ESP_OK;
ESP_LOGV(TAG, "[%d] [%s] Unregistering for GATT characteristic notifications handle %d", this->connection_index_,
this->address_str_.c_str(), handle);
esp_err_t err = esp_ble_gattc_unregister_for_notify(this->gattc_if_, this->remote_bda_, handle);
return this->check_and_log_error_("esp_ble_gattc_unregister_for_notify", err);
}
esp32_ble_tracker::AdvertisementParserType BluetoothConnection::get_advertisement_parser_type() {

View File

@@ -8,7 +8,7 @@ namespace esphome::bluetooth_proxy {
class BluetoothProxy;
class BluetoothConnection : public esp32_ble_client::BLEClientBase {
class BluetoothConnection final : public esp32_ble_client::BLEClientBase {
public:
void dump_config() override;
void loop() override;
@@ -24,18 +24,27 @@ class BluetoothConnection : public esp32_ble_client::BLEClientBase {
esp_err_t notify_characteristic(uint16_t handle, bool enable);
void set_address(uint64_t address) override;
protected:
friend class BluetoothProxy;
bool supports_efficient_uuids_() const;
void send_service_for_discovery_();
void reset_connection_(esp_err_t reason);
void update_allocated_slot_(uint64_t find_value, uint64_t set_value);
void log_connection_error_(const char *operation, esp_gatt_status_t status);
void log_connection_warning_(const char *operation, esp_err_t err);
void log_gatt_not_connected_(const char *action, const char *type);
void log_gatt_operation_error_(const char *operation, uint16_t handle, esp_gatt_status_t status);
esp_err_t check_and_log_error_(const char *operation, esp_err_t err);
// Memory optimized layout for 32-bit systems
// Group 1: Pointers (4 bytes each, naturally aligned)
BluetoothProxy *proxy_;
// Group 2: 2-byte types
int16_t send_service_{-2}; // Needs to handle negative values and service count
int16_t send_service_{-3}; // -3 = INIT_SENDING_SERVICES, -2 = DONE_SENDING_SERVICES, >=0 = service index
// Group 3: 1-byte types
bool seen_mtu_or_services_{false};

View File

@@ -11,12 +11,8 @@ namespace esphome::bluetooth_proxy {
static const char *const TAG = "bluetooth_proxy";
// 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;
// BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE is defined during code generation
// It sets the batch size for BLE advertisements to maximize WiFi efficiency
// Verify BLE advertisement data array size matches the BLE specification (31 bytes adv + 31 bytes scan response)
static_assert(sizeof(((api::BluetoothLERawAdvertisement *) nullptr)->data) == 62,
@@ -25,15 +21,8 @@ static_assert(sizeof(((api::BluetoothLERawAdvertisement *) nullptr)->data) == 62
BluetoothProxy::BluetoothProxy() { global_bluetooth_proxy = this; }
void BluetoothProxy::setup() {
// Pre-allocate response object
this->response_ = std::make_unique<api::BluetoothLERawAdvertisementsResponse>();
// Reserve capacity but start with size 0
// Reserve 50% since we'll grow naturally and flush at FLUSH_BATCH_SIZE
this->response_->advertisements.reserve(FLUSH_BATCH_SIZE / 2);
// Don't pre-allocate pool - let it grow only if needed in busy environments
// Many devices in quiet areas will never need the overflow pool
this->connections_free_response_.limit = BLUETOOTH_PROXY_MAX_CONNECTIONS;
this->connections_free_response_.free = BLUETOOTH_PROXY_MAX_CONNECTIONS;
this->parent_->add_scanner_state_callback([this](esp32_ble_tracker::ScannerState state) {
if (this->api_connection_ != nullptr) {
@@ -50,6 +39,26 @@ void BluetoothProxy::send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerSta
this->api_connection_->send_message(resp, api::BluetoothScannerStateResponse::MESSAGE_TYPE);
}
void BluetoothProxy::log_connection_request_ignored_(BluetoothConnection *connection, espbt::ClientState state) {
ESP_LOGW(TAG, "[%d] [%s] Connection request ignored, state: %s", connection->get_connection_index(),
connection->address_str().c_str(), espbt::client_state_to_string(state));
}
void BluetoothProxy::log_connection_info_(BluetoothConnection *connection, const char *message) {
ESP_LOGI(TAG, "[%d] [%s] Connecting %s", connection->get_connection_index(), connection->address_str().c_str(),
message);
}
void BluetoothProxy::log_not_connected_gatt_(const char *action, const char *type) {
ESP_LOGW(TAG, "Cannot %s GATT %s, not connected", action, type);
}
void BluetoothProxy::handle_gatt_not_connected_(uint64_t address, uint16_t handle, const char *action,
const char *type) {
this->log_not_connected_gatt_(action, type);
this->send_gatt_error(address, handle, ESP_GATT_NOT_CONNECTED);
}
#ifdef USE_ESP32_BLE_DEVICE
bool BluetoothProxy::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
// This method should never be called since bluetooth_proxy always uses raw advertisements
@@ -62,39 +71,27 @@ bool BluetoothProxy::parse_devices(const esp32_ble::BLEScanResult *scan_results,
if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr)
return false;
auto &advertisements = this->response_->advertisements;
auto &advertisements = this->response_.advertisements;
for (size_t i = 0; i < count; i++) {
auto &result = scan_results[i];
uint8_t length = result.adv_data_len + result.scan_rsp_len;
// Check if we need to expand the vector
if (this->advertisement_count_ >= advertisements.size()) {
if (this->advertisement_pool_.empty()) {
// No room in pool, need to allocate
advertisements.emplace_back();
} else {
// Pull from pool
advertisements.push_back(std::move(this->advertisement_pool_.back()));
this->advertisement_pool_.pop_back();
}
}
// Fill in the data directly at current position
auto &adv = advertisements[this->advertisement_count_];
auto &adv = advertisements[this->response_.advertisements_len];
adv.address = esp32_ble::ble_addr_to_uint64(result.bda);
adv.rssi = result.rssi;
adv.address_type = result.ble_addr_type;
adv.data_len = length;
std::memcpy(adv.data, result.ble_adv, length);
this->advertisement_count_++;
this->response_.advertisements_len++;
ESP_LOGV(TAG, "Queuing raw packet from %02X:%02X:%02X:%02X:%02X:%02X, length %d. RSSI: %d dB", result.bda[0],
result.bda[1], result.bda[2], result.bda[3], result.bda[4], result.bda[5], length, result.rssi);
// Flush if we have reached FLUSH_BATCH_SIZE
if (this->advertisement_count_ >= FLUSH_BATCH_SIZE) {
// Flush if we have reached BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE
if (this->response_.advertisements_len >= BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE) {
this->flush_pending_advertisements();
}
}
@@ -103,54 +100,31 @@ bool BluetoothProxy::parse_devices(const esp32_ble::BLEScanResult *scan_results,
}
void BluetoothProxy::flush_pending_advertisements() {
if (this->advertisement_count_ == 0 || !api::global_api_server->is_connected() || this->api_connection_ == nullptr)
if (this->response_.advertisements_len == 0 || !api::global_api_server->is_connected() ||
this->api_connection_ == nullptr)
return;
auto &advertisements = this->response_->advertisements;
// Return any items beyond advertisement_count_ to the pool
if (advertisements.size() > this->advertisement_count_) {
// Move unused items back to pool
this->advertisement_pool_.insert(this->advertisement_pool_.end(),
std::make_move_iterator(advertisements.begin() + this->advertisement_count_),
std::make_move_iterator(advertisements.end()));
// Resize to actual count
advertisements.resize(this->advertisement_count_);
}
// Send the message
this->api_connection_->send_message(*this->response_, api::BluetoothLERawAdvertisementsResponse::MESSAGE_TYPE);
this->api_connection_->send_message(this->response_, api::BluetoothLERawAdvertisementsResponse::MESSAGE_TYPE);
// Reset count - existing items will be overwritten in next batch
this->advertisement_count_ = 0;
ESP_LOGV(TAG, "Sent batch of %u BLE advertisements", this->response_.advertisements_len);
// Reset the length for the next batch
this->response_.advertisements_len = 0;
}
void BluetoothProxy::dump_config() {
ESP_LOGCONFIG(TAG, "Bluetooth Proxy:");
ESP_LOGCONFIG(TAG,
"Bluetooth Proxy:\n"
" Active: %s\n"
" Connections: %d",
YESNO(this->active_), this->connections_.size());
}
int BluetoothProxy::get_bluetooth_connections_free() {
int free = 0;
for (auto *connection : this->connections_) {
if (connection->address_ == 0) {
free++;
ESP_LOGV(TAG, "[%d] Free connection", connection->get_connection_index());
} else {
ESP_LOGV(TAG, "[%d] Used connection by [%s]", connection->get_connection_index(),
connection->address_str().c_str());
}
}
return free;
YESNO(this->active_), this->connection_count_);
}
void BluetoothProxy::loop() {
if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr) {
for (auto *connection : this->connections_) {
for (uint8_t i = 0; i < this->connection_count_; i++) {
auto *connection = this->connections_[i];
if (connection->get_address() != 0 && !connection->disconnect_pending()) {
connection->disconnect();
}
@@ -173,7 +147,8 @@ esp32_ble_tracker::AdvertisementParserType BluetoothProxy::get_advertisement_par
}
BluetoothConnection *BluetoothProxy::get_connection_(uint64_t address, bool reserve) {
for (auto *connection : this->connections_) {
for (uint8_t i = 0; i < this->connection_count_; i++) {
auto *connection = this->connections_[i];
if (connection->get_address() == address)
return connection;
}
@@ -181,9 +156,10 @@ BluetoothConnection *BluetoothProxy::get_connection_(uint64_t address, bool rese
if (!reserve)
return nullptr;
for (auto *connection : this->connections_) {
for (uint8_t i = 0; i < this->connection_count_; i++) {
auto *connection = this->connections_[i];
if (connection->get_address() == 0) {
connection->send_service_ = DONE_SENDING_SERVICES;
connection->send_service_ = INIT_SENDING_SERVICES;
connection->set_address(address);
// All connections must start at INIT
// We only set the state if we allocate the connection
@@ -200,8 +176,7 @@ BluetoothConnection *BluetoothProxy::get_connection_(uint64_t address, bool rese
void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest &msg) {
switch (msg.request_type) {
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE:
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE:
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT: {
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE: {
auto *connection = this->get_connection_(msg.address, true);
if (connection == nullptr) {
ESP_LOGW(TAG, "No free connections available");
@@ -210,23 +185,10 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
}
if (connection->state() == espbt::ClientState::CONNECTED ||
connection->state() == espbt::ClientState::ESTABLISHED) {
ESP_LOGW(TAG, "[%d] [%s] Connection already established", connection->get_connection_index(),
connection->address_str().c_str());
this->log_connection_request_ignored_(connection, connection->state());
this->send_device_connection(msg.address, true);
this->send_connections_free();
return;
} else if (connection->state() == espbt::ClientState::SEARCHING) {
ESP_LOGW(TAG, "[%d] [%s] Connection request ignored, already searching for device",
connection->get_connection_index(), connection->address_str().c_str());
return;
} else if (connection->state() == espbt::ClientState::DISCOVERED) {
ESP_LOGW(TAG, "[%d] [%s] Connection request ignored, device already discovered",
connection->get_connection_index(), connection->address_str().c_str());
return;
} else if (connection->state() == espbt::ClientState::READY_TO_CONNECT) {
ESP_LOGW(TAG, "[%d] [%s] Connection request ignored, waiting in line to connect",
connection->get_connection_index(), connection->address_str().c_str());
return;
} else if (connection->state() == espbt::ClientState::CONNECTING) {
if (connection->disconnect_pending()) {
ESP_LOGW(TAG, "[%d] [%s] Connection request while pending disconnect, cancelling pending disconnect",
@@ -234,29 +196,18 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
connection->cancel_pending_disconnect();
return;
}
ESP_LOGW(TAG, "[%d] [%s] Connection request ignored, already connecting", connection->get_connection_index(),
connection->address_str().c_str());
return;
} else if (connection->state() == espbt::ClientState::DISCONNECTING) {
ESP_LOGW(TAG, "[%d] [%s] Connection request ignored, device is disconnecting",
connection->get_connection_index(), connection->address_str().c_str());
this->log_connection_request_ignored_(connection, connection->state());
return;
} else if (connection->state() != espbt::ClientState::INIT) {
ESP_LOGW(TAG, "[%d] [%s] Connection already in progress", connection->get_connection_index(),
connection->address_str().c_str());
this->log_connection_request_ignored_(connection, connection->state());
return;
}
if (msg.request_type == api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE) {
connection->set_connection_type(espbt::ConnectionType::V3_WITH_CACHE);
ESP_LOGI(TAG, "[%d] [%s] Connecting v3 with cache", connection->get_connection_index(),
connection->address_str().c_str());
} else if (msg.request_type == api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE) {
this->log_connection_info_(connection, "v3 with cache");
} else { // BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE
connection->set_connection_type(espbt::ConnectionType::V3_WITHOUT_CACHE);
ESP_LOGI(TAG, "[%d] [%s] Connecting v3 without cache", connection->get_connection_index(),
connection->address_str().c_str());
} else {
connection->set_connection_type(espbt::ConnectionType::V1);
ESP_LOGI(TAG, "[%d] [%s] Connecting v1", connection->get_connection_index(), connection->address_str().c_str());
this->log_connection_info_(connection, "v3 without cache");
}
if (msg.has_address_type) {
uint64_to_bd_addr(msg.address, connection->remote_bda_);
@@ -318,14 +269,18 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
break;
}
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT: {
ESP_LOGE(TAG, "V1 connections removed");
this->send_device_connection(msg.address, false);
break;
}
}
}
void BluetoothProxy::bluetooth_gatt_read(const api::BluetoothGATTReadRequest &msg) {
auto *connection = this->get_connection_(msg.address, false);
if (connection == nullptr) {
ESP_LOGW(TAG, "Cannot read GATT characteristic, not connected");
this->send_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
this->handle_gatt_not_connected_(msg.address, msg.handle, "read", "characteristic");
return;
}
@@ -338,8 +293,7 @@ void BluetoothProxy::bluetooth_gatt_read(const api::BluetoothGATTReadRequest &ms
void BluetoothProxy::bluetooth_gatt_write(const api::BluetoothGATTWriteRequest &msg) {
auto *connection = this->get_connection_(msg.address, false);
if (connection == nullptr) {
ESP_LOGW(TAG, "Cannot write GATT characteristic, not connected");
this->send_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
this->handle_gatt_not_connected_(msg.address, msg.handle, "write", "characteristic");
return;
}
@@ -352,8 +306,7 @@ void BluetoothProxy::bluetooth_gatt_write(const api::BluetoothGATTWriteRequest &
void BluetoothProxy::bluetooth_gatt_read_descriptor(const api::BluetoothGATTReadDescriptorRequest &msg) {
auto *connection = this->get_connection_(msg.address, false);
if (connection == nullptr) {
ESP_LOGW(TAG, "Cannot read GATT descriptor, not connected");
this->send_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
this->handle_gatt_not_connected_(msg.address, msg.handle, "read", "descriptor");
return;
}
@@ -366,8 +319,7 @@ void BluetoothProxy::bluetooth_gatt_read_descriptor(const api::BluetoothGATTRead
void BluetoothProxy::bluetooth_gatt_write_descriptor(const api::BluetoothGATTWriteDescriptorRequest &msg) {
auto *connection = this->get_connection_(msg.address, false);
if (connection == nullptr) {
ESP_LOGW(TAG, "Cannot write GATT descriptor, not connected");
this->send_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
this->handle_gatt_not_connected_(msg.address, msg.handle, "write", "descriptor");
return;
}
@@ -380,8 +332,7 @@ void BluetoothProxy::bluetooth_gatt_write_descriptor(const api::BluetoothGATTWri
void BluetoothProxy::bluetooth_gatt_send_services(const api::BluetoothGATTGetServicesRequest &msg) {
auto *connection = this->get_connection_(msg.address, false);
if (connection == nullptr || !connection->connected()) {
ESP_LOGW(TAG, "Cannot get GATT services, not connected");
this->send_gatt_error(msg.address, 0, ESP_GATT_NOT_CONNECTED);
this->handle_gatt_not_connected_(msg.address, 0, "get", "services");
return;
}
if (!connection->service_count_) {
@@ -389,16 +340,14 @@ void BluetoothProxy::bluetooth_gatt_send_services(const api::BluetoothGATTGetSer
this->send_gatt_services_done(msg.address);
return;
}
if (connection->send_service_ ==
DONE_SENDING_SERVICES) // Only start sending services if we're not already sending them
if (connection->send_service_ == INIT_SENDING_SERVICES) // Start sending services if not started yet
connection->send_service_ = 0;
}
void BluetoothProxy::bluetooth_gatt_notify(const api::BluetoothGATTNotifyRequest &msg) {
auto *connection = this->get_connection_(msg.address, false);
if (connection == nullptr) {
ESP_LOGW(TAG, "Cannot notify GATT characteristic, not connected");
this->send_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
this->handle_gatt_not_connected_(msg.address, msg.handle, "notify", "characteristic");
return;
}
@@ -439,17 +388,13 @@ void BluetoothProxy::send_device_connection(uint64_t address, bool connected, ui
this->api_connection_->send_message(call, api::BluetoothDeviceConnectionResponse::MESSAGE_TYPE);
}
void BluetoothProxy::send_connections_free() {
if (this->api_connection_ == nullptr)
return;
api::BluetoothConnectionsFreeResponse call;
call.free = this->get_bluetooth_connections_free();
call.limit = this->get_bluetooth_connections_limit();
for (auto *connection : this->connections_) {
if (connection->address_ != 0) {
call.allocated.push_back(connection->address_);
}
if (this->api_connection_ != nullptr) {
this->send_connections_free(this->api_connection_);
}
this->api_connection_->send_message(call, api::BluetoothConnectionsFreeResponse::MESSAGE_TYPE);
}
void BluetoothProxy::send_connections_free(api::APIConnection *api_connection) {
api_connection->send_message(this->connections_free_response_, api::BluetoothConnectionsFreeResponse::MESSAGE_TYPE);
}
void BluetoothProxy::send_gatt_services_done(uint64_t address) {

View File

@@ -2,6 +2,7 @@
#ifdef USE_ESP32
#include <array>
#include <map>
#include <vector>
@@ -22,6 +23,7 @@ namespace esphome::bluetooth_proxy {
static const esp_err_t ESP_GATT_NOT_CONNECTED = -1;
static const int DONE_SENDING_SERVICES = -2;
static const int INIT_SENDING_SERVICES = -3;
using namespace esp32_ble_client;
@@ -48,7 +50,8 @@ enum BluetoothProxySubscriptionFlag : uint32_t {
SUBSCRIPTION_RAW_ADVERTISEMENTS = 1 << 0,
};
class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Component {
class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener, public Component {
friend class BluetoothConnection; // Allow connection to update connections_free_response_
public:
BluetoothProxy();
#ifdef USE_ESP32_BLE_DEVICE
@@ -62,8 +65,10 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
esp32_ble_tracker::AdvertisementParserType get_advertisement_parser_type() override;
void register_connection(BluetoothConnection *connection) {
this->connections_.push_back(connection);
connection->proxy_ = this;
if (this->connection_count_ < BLUETOOTH_PROXY_MAX_CONNECTIONS) {
this->connections_[this->connection_count_++] = connection;
connection->proxy_ = this;
}
}
void bluetooth_device_request(const api::BluetoothDeviceRequest &msg);
@@ -74,15 +79,13 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
void bluetooth_gatt_send_services(const api::BluetoothGATTGetServicesRequest &msg);
void bluetooth_gatt_notify(const api::BluetoothGATTNotifyRequest &msg);
int get_bluetooth_connections_free();
int get_bluetooth_connections_limit() { return this->connections_.size(); }
void subscribe_api_connection(api::APIConnection *api_connection, uint32_t flags);
void unsubscribe_api_connection(api::APIConnection *api_connection);
api::APIConnection *get_api_connection() { return this->api_connection_; }
void send_device_connection(uint64_t address, bool connected, uint16_t mtu = 0, esp_err_t error = ESP_OK);
void send_connections_free();
void send_connections_free(api::APIConnection *api_connection);
void send_gatt_services_done(uint64_t address);
void send_gatt_error(uint64_t address, uint16_t handle, esp_err_t error);
void send_device_pairing(uint64_t address, bool paired, esp_err_t error = ESP_OK);
@@ -134,24 +137,30 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
void send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerState state);
BluetoothConnection *get_connection_(uint64_t address, bool reserve);
void log_connection_request_ignored_(BluetoothConnection *connection, espbt::ClientState state);
void log_connection_info_(BluetoothConnection *connection, const char *message);
void log_not_connected_gatt_(const char *action, const char *type);
void handle_gatt_not_connected_(uint64_t address, uint16_t handle, const char *action, const char *type);
// Memory optimized layout for 32-bit systems
// Group 1: Pointers (4 bytes each, naturally aligned)
api::APIConnection *api_connection_{nullptr};
// Group 2: Container types (typically 12 bytes on 32-bit)
std::vector<BluetoothConnection *> connections_{};
// Group 2: Fixed-size array of connection pointers
std::array<BluetoothConnection *, BLUETOOTH_PROXY_MAX_CONNECTIONS> connections_{};
// BLE advertisement batching
std::vector<api::BluetoothLERawAdvertisement> advertisement_pool_;
std::unique_ptr<api::BluetoothLERawAdvertisementsResponse> response_;
api::BluetoothLERawAdvertisementsResponse response_;
// Group 3: 4-byte types
uint32_t last_advertisement_flush_time_{0};
// Pre-allocated response message - always ready to send
api::BluetoothConnectionsFreeResponse connections_free_response_;
// Group 4: 1-byte types grouped together
bool active_;
uint8_t advertisement_count_{0};
uint8_t connection_count_{0};
// 2 bytes used, 2 bytes padding
};

View File

@@ -7,6 +7,8 @@
#include <esphome/components/sensor/sensor.h>
#include <esphome/core/component.h>
#define BME280_ERROR_WRONG_CHIP_ID "Wrong chip ID"
namespace esphome {
namespace bme280_base {
@@ -88,7 +90,6 @@ const char *oversampling_to_str(BME280Oversampling oversampling) { // NOLINT
}
void BME280Component::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
uint8_t chip_id = 0;
// Mark as not failed before initializing. Some devices will turn off sensors to save on batteries
@@ -99,18 +100,18 @@ void BME280Component::setup() {
if (!this->read_byte(BME280_REGISTER_CHIPID, &chip_id)) {
this->error_code_ = COMMUNICATION_FAILED;
this->mark_failed();
this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
return;
}
if (chip_id != 0x60) {
this->error_code_ = WRONG_CHIP_ID;
this->mark_failed();
this->mark_failed(BME280_ERROR_WRONG_CHIP_ID);
return;
}
// Send a soft reset.
if (!this->write_byte(BME280_REGISTER_RESET, BME280_SOFT_RESET)) {
this->mark_failed();
this->mark_failed("Reset failed");
return;
}
// Wait until the NVM data has finished loading.
@@ -119,14 +120,12 @@ void BME280Component::setup() {
do { // NOLINT
delay(2);
if (!this->read_byte(BME280_REGISTER_STATUS, &status)) {
ESP_LOGW(TAG, "Error reading status register.");
this->mark_failed();
this->mark_failed("Error reading status register");
return;
}
} while ((status & BME280_STATUS_IM_UPDATE) && (--retry));
if (status & BME280_STATUS_IM_UPDATE) {
ESP_LOGW(TAG, "Timeout loading NVM.");
this->mark_failed();
this->mark_failed("Timeout loading NVM");
return;
}
@@ -154,26 +153,26 @@ void BME280Component::setup() {
uint8_t humid_control_val = 0;
if (!this->read_byte(BME280_REGISTER_CONTROLHUMID, &humid_control_val)) {
this->mark_failed();
this->mark_failed("Read humidity control");
return;
}
humid_control_val &= ~0b00000111;
humid_control_val |= this->humidity_oversampling_ & 0b111;
if (!this->write_byte(BME280_REGISTER_CONTROLHUMID, humid_control_val)) {
this->mark_failed();
this->mark_failed("Write humidity control");
return;
}
uint8_t config_register = 0;
if (!this->read_byte(BME280_REGISTER_CONFIG, &config_register)) {
this->mark_failed();
this->mark_failed("Read config");
return;
}
config_register &= ~0b11111100;
config_register |= 0b101 << 5; // 1000 ms standby time
config_register |= (this->iir_filter_ & 0b111) << 2;
if (!this->write_byte(BME280_REGISTER_CONFIG, config_register)) {
this->mark_failed();
this->mark_failed("Write config");
return;
}
}
@@ -184,7 +183,7 @@ void BME280Component::dump_config() {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
break;
case WRONG_CHIP_ID:
ESP_LOGE(TAG, "BME280 has wrong chip ID! Is it a BME280?");
ESP_LOGE(TAG, BME280_ERROR_WRONG_CHIP_ID);
break;
case NONE:
default:
@@ -224,21 +223,21 @@ void BME280Component::update() {
this->set_timeout("data", uint32_t(ceilf(meas_time)), [this]() {
uint8_t data[8];
if (!this->read_bytes(BME280_REGISTER_MEASUREMENTS, data, 8)) {
ESP_LOGW(TAG, "Error reading registers.");
ESP_LOGW(TAG, "Error reading registers");
this->status_set_warning();
return;
}
int32_t t_fine = 0;
float const temperature = this->read_temperature_(data, &t_fine);
if (std::isnan(temperature)) {
ESP_LOGW(TAG, "Invalid temperature, cannot read pressure & humidity values.");
ESP_LOGW(TAG, "Invalid temperature");
this->status_set_warning();
return;
}
float const pressure = this->read_pressure_(data, t_fine);
float const humidity = this->read_humidity_(data, t_fine);
ESP_LOGV(TAG, "Got temperature=%.1f°C pressure=%.1fhPa humidity=%.1f%%", temperature, pressure, humidity);
ESP_LOGV(TAG, "Temperature=%.1f°C Pressure=%.1fhPa Humidity=%.1f%%", temperature, pressure, humidity);
if (this->temperature_sensor_ != nullptr)
this->temperature_sensor_->publish_state(temperature);
if (this->pressure_sensor_ != nullptr)

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