1
0
mirror of https://github.com/esphome/esphome.git synced 2025-11-07 10:31:49 +00:00

Compare commits

...

439 Commits

Author SHA1 Message Date
J. Nick Koston
f195ac1afd Always use IDF SPI on ESP32 2025-10-05 23:15:53 -05:00
Keith Burzinski
cfd241ff29 [zwave_proxy] Send HomeID upon client connect (#11037) 2025-10-06 03:47:55 +00:00
Clyde Stubbs
f757a19e82 [mipi] Fix rotation handling (#11010) 2025-10-06 14:05:44 +11:00
J. Nick Koston
e8854e0659 [esp32_ble] Fix max_connections architecture (shared client+server limit) (#11006) 2025-10-06 02:45:44 +00:00
Edward Firmo
a3622d878d [nextion] Reduce DEBUG logs on events (#11014) 2025-10-05 21:11:36 -04:00
Jonathan Swoboda
da2089c8be [core] Remove platformio install from setup (#10997) 2025-10-06 13:10:05 +13:00
J. Nick Koston
118663f9e2 [web_server] Use IDF web server for ESP32 Arduino builds (#10991) 2025-10-05 19:07:52 -05:00
J. Nick Koston
4a99987bfe [tuya] Fix clang-tidy signed/unsigned comparison warning (#11035) 2025-10-06 13:07:00 +13:00
J. Nick Koston
d164c06f01 [sonoff_d1] Fix clang-tidy signed/unsigned comparison warning (#11034) 2025-10-06 13:06:43 +13:00
J. Nick Koston
972987acdf [esp32_rmt_led_strip] Fix clang-tidy signed/unsigned comparison warning (#11033) 2025-10-06 13:06:26 +13:00
J. Nick Koston
eea2b6b81b [esp32_ble] Optimize string operations to reduce flash usage by 264 bytes (#11023) 2025-10-06 13:04:50 +13:00
J. Nick Koston
f62e06104e [wifi] Optimize logging to reduce flash usage by 284 bytes on ESP8266 (#11022) 2025-10-06 13:03:26 +13:00
J. Nick Koston
f26e71bae6 [ci] Fix clang-tidy after Arduino-as-IDF-component migration (#11031) 2025-10-05 22:16:09 +00:00
Jonathan Swoboda
c6e4a7911c [esp32] Improve version handling (#10899)
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-10-05 22:10:23 +00:00
J. Nick Koston
e2c5eeef97 [scheduler] Deduplicate item removal code with template helper (#11017) 2025-10-05 16:32:51 -05:00
J. Nick Koston
7ea51b1865 [esphome.ota] Fix ESP32-S3 OTA authentication with hardware SHA acceleration (#11011) 2025-10-06 10:17:28 +13:00
J. Nick Koston
aa1afbd152 [wifi] Optimize WPA2 EAP phase2 logging to reduce memory overhead (#11005)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-06 10:02:41 +13:00
J. Nick Koston
20d9ae699c [logger] Conditionally compile runtime tag-specific log levels for performance (#11004) 2025-10-06 09:59:52 +13:00
J. Nick Koston
c0fb0ae06f [web_server_idf] Optimize parameter storage to reduce flash usage and memory overhead (#11003) 2025-10-06 09:57:59 +13:00
J. Nick Koston
9b6d62cd69 [web_server_idf] Fix watchdog timeout with unreliable event source connections (#11002)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-06 09:55:39 +13:00
J. Nick Koston
5932a4bd0e [web_server] Reduce flash and RAM usage by optimizing string construction (#10986) 2025-10-06 09:42:23 +13:00
J. Nick Koston
84c3cf5f17 [core] Replace std::pair with purpose-built named structs for component metadata (#10984) 2025-10-06 09:38:58 +13:00
J. Nick Koston
120a445abf [number] Reduce flash usage in NumberCall logging (#10983) 2025-10-06 09:37:47 +13:00
J. Nick Koston
41c073a451 [lock] Replace std::set with bitmask (saves 388B flash + 23B RAM per lock) (#10977) 2025-10-06 09:33:58 +13:00
J. Nick Koston
0fd71ca211 [mdns][openthread] Use StaticVector for services storage with compile-time capacity (#10976) 2025-10-06 09:30:17 +13:00
J. Nick Koston
19439199cc [api] Add configurable send queue limit to prevent OOM crashes (#10973) 2025-10-06 09:25:04 +13:00
J. Nick Koston
39d5cbc74a [esp32_ble_server] Replace EventEmitter with direct callbacks to reduce memory usage (#10946) 2025-10-06 09:20:40 +13:00
Jonathan Swoboda
722c5a94f2 [sps30] Clean up (#10998) 2025-10-05 09:24:09 -05:00
J. Nick Koston
7b48fc292f [api] Consolidate fatal error logging to reduce flash usage (#11015) 2025-10-05 09:56:30 -04:00
J. Nick Koston
6c7d92e726 [ethernet] Consolidate error handling to reduce flash usage (#11019) 2025-10-04 20:47:46 -05:00
J. Nick Koston
b1859c50bd [api] Simplify message reading conditional (#11016) 2025-10-04 21:42:21 -04:00
J. Nick Koston
3f9924eac2 [core] Merge duplicate loops in mac_address_is_valid() (#11018) 2025-10-04 21:42:07 -04:00
mrtoy-me
874db20b7d [mpr121] cleaner setup (#11013) 2025-10-04 08:54:31 -04:00
J. Nick Koston
2eea674c04 [json] Fix missing defines.h include causing PSRAM allocator to be unused (#11008) 2025-10-03 23:52:40 -05:00
J. Nick Koston
0137954f2b [const] Move CONF_MAX_CONNECTIONS to const.py (#11007) 2025-10-03 18:20:00 -05:00
Patrick
0a40a30e4a [esp32_can] support multiple CAN instances for platforms that support it (#10712)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2025-10-03 23:10:19 +00:00
dependabot[bot]
d43b844e06 Bump ruff from 0.13.2 to 0.13.3 (#11000)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2025-10-03 14:28:58 -05:00
Tucker Kern
2596b6096f Fix log level selector when selecting levels above INFO (#10368)
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-10-03 14:28:38 -05:00
dependabot[bot]
6f8e82aeb6 Bump actions/stale from 10.0.0 to 10.1.0 (#11001)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-03 14:27:29 -05:00
J. Nick Koston
ca0e738799 [logger] Fix line number wrapping bug for files with >999 lines (#10979) 2025-10-03 10:50:21 -05:00
Jonathan Swoboda
14a23101f2 [core] Fix MQTT import (#10982) 2025-10-03 11:35:55 -04:00
mrtoy-me
2b389bb8f2 [sps30] remove delay (#10964)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2025-10-03 09:40:43 -04:00
mrtoy-me
89c3340ef6 [mpr121] remove delay (#10963)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2025-10-03 09:06:16 -04:00
Oliver Gründel
ba0532cda7 Fix UNIT_KILOVOLT_AMPS_REACTIVE constant definition (#10992) 2025-10-03 07:36:14 -04:00
J. Nick Koston
5419b8bddb [ci] Fix pre-commit action to comply with pinned SHA security policy (#10990) 2025-10-02 21:53:16 -05:00
dependabot[bot]
624868bb05 Bump github/codeql-action from 3.30.5 to 3.30.6 (#10985)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-02 22:58:06 +02:00
J. Nick Koston
f2aa5a754c [api] Remove ClientInfo::get_combined_info() to eliminate heap fragmentation (#10970) 2025-10-02 12:44:30 +13:00
J. Nick Koston
638c6cc14e [api] Reduce flash usage in user services by eliminating vector copy (#10971) 2025-10-01 20:26:47 +02:00
Jonathan Swoboda
8137d7600a [rtttl] Fix warning (#10972) 2025-10-01 20:26:25 +02:00
Carl Reid
08afc3030a [psram] raise instead of returning invalid object (#10954)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2025-10-01 09:47:32 -04:00
Jesse Hills
1deb79a24b [core] Add some types to loader.py (#10967)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-01 15:36:17 +02:00
J. Nick Koston
de21c61b6a [logger] Optimize log formatting performance (35-72% faster) (#10960) 2025-10-01 15:33:30 +02:00
J. Nick Koston
db1aa82350 [core] Fix ComponentIterator alignment for 32-bit platforms (#10969) 2025-10-01 15:33:14 +02:00
Jesse Hills
fe4799b300 Merge branch 'release' into dev 2025-10-02 00:30:42 +13:00
Jesse Hills
93e18e850e Merge pull request #10966 from esphome/bump-2025.9.3
2025.9.3
2025-10-02 00:29:56 +13:00
mrtoy-me
5cef75dbe1 [hdc1080] remove delays and fix no check for sensor nullptr (#10947)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2025-10-01 07:22:02 -04:00
Piotr Szulc
4194a940ae [remote_transmitter] fix sending codes on libretiny (#10959)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2025-10-01 07:10:37 -04:00
Jesse Hills
59c0ffb98b Bump version to 2025.9.3 2025-10-01 23:41:42 +13:00
Abílio Costa
29658b79bc [voice_assistant] Fix wakeword string being reset while referenced (#10945)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-10-01 23:41:42 +13:00
Vladimir Makeev
158a59aa83 [sim800l] Fixed ignoring incoming calls. (#10865)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2025-10-01 23:41:42 +13:00
J. Nick Koston
c95180504a [api] Prevent API from overriding noise encryption keys set in YAML (#10927) 2025-10-01 23:41:42 +13:00
Jesse Hills
848ba6b717 [psram] Fix invalid variant error, add supported() check (#10962) 2025-10-01 23:29:10 +13:00
J. Nick Koston
922f4b6352 [web_server] Optimize handler methods with lookup tables to reduce flash usage (#10951) 2025-10-01 15:52:35 +13:00
Javier Peletier
fd3c05b42e [substitutions] fix #10825 set evaluation error (#10830) 2025-10-01 14:33:56 +13:00
Jesse Hills
ab1f8326ee [const] Move CONF_ON_RESPONSE to const.py (#10958) 2025-10-01 11:12:45 +13:00
J. Nick Koston
2a915e4efd [deep_sleep] esp32 fixes to align with variant support (#10929) 2025-10-01 08:42:42 +13:00
dependabot[bot]
f5e85a424f Bump docker/login-action from 3.5.0 to 3.6.0 in the docker-actions group (#10943)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-01 08:29:49 +13:00
J. Nick Koston
c69603d916 [dashboard] Replace polling with WebSocket for real-time updates (#10893) 2025-09-30 14:03:52 -04:00
Jonathan Swoboda
d75b7708a5 [sx126x] Add additional FSK CRC options (#10928) 2025-09-30 12:08:28 -04:00
J. Nick Koston
b023453e81 [captive_portal] Add DHCP Option 114 support for ESP32 (#10952) 2025-09-30 17:52:37 +02:00
Stephen Boyle
a5ba6237cb [ethernet] Add mac_address yaml configuration option (#10861)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-09-30 08:59:08 -04:00
Patrick
0e623055df [mcp2515, canbus] error handling improvments (#10526) 2025-09-30 08:56:28 -04:00
J. Nick Koston
6018f5f5d1 [api] Add configurable connection limits (#10939) 2025-09-30 22:24:19 +13:00
J. Nick Koston
96868aa754 [socket] Reduce memory overhead for LWIP TCP accept queue on ESP8266/RP2040 (#10938) 2025-09-30 16:52:47 +13:00
J. Nick Koston
83d86c8c59 [ota] Complete non-blocking authentication implementation (#10912) 2025-09-30 16:46:47 +13:00
Abílio Costa
7703cabb7b [voice_assistant] Fix wakeword string being reset while referenced (#10945)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-09-30 02:29:16 +00:00
dependabot[bot]
300f1de11c Bump aioesphomeapi from 41.10.0 to 41.11.0 (#10942)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-29 17:00:23 -05:00
J. Nick Koston
3b73738d9f [script] Reduce RAM usage by storing names in flash (#10941) 2025-09-30 10:35:53 +13:00
Jonathan Swoboda
b176d1f890 [core] Don't remove storage in clean-all (#10921) 2025-09-29 15:24:42 -04:00
Jesse Hills
2aaafd6ebb Merge branch 'release' into dev 2025-09-30 07:51:02 +13:00
Jesse Hills
a96c013eb1 Merge pull request #10932 from esphome/bump-2025.9.2
2025.9.2
2025-09-30 07:50:17 +13:00
tomaszduda23
054b215d8d [nrf52] add more tests (#10694) 2025-09-29 14:11:57 -04:00
tomaszduda23
e3e98e2568 [nrf52] add more tests (#10695)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2025-09-29 14:09:41 -04:00
Vladimir Makeev
29db576f79 [sim800l] Fixed ignoring incoming calls. (#10865)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2025-09-29 09:08:51 -04:00
Jesse Hills
58166b3e71 Bump version to 2025.9.2 2025-09-29 21:39:17 +13:00
Clyde Stubbs
345fc0b6ca [mipi_spi] Fix t-display-amoled (#10922) 2025-09-29 21:39:17 +13:00
Oliver Kleinecke
127058e700 [usb_uart] Disable flow control on ch34x 2025-09-29 21:39:17 +13:00
Jonathan Swoboda
57f7a709cf [sx126x] Fix issues with variable length FSK packets (#10911) 2025-09-29 21:39:17 +13:00
J. Nick Koston
f2a9e9265e [esp32_improv] Fix crashes from uninitialized pointers and missing null checks (#10902) 2025-09-29 21:39:16 +13:00
Stuart Parmenter
1ecd26adb5 Set color_order to RGB for the Waveshare ESP32-S3-TOUCH-LCD-4.3 and ESP32-S3-TOUCH-LCD-7-800X480 (#10835) 2025-09-29 21:39:16 +13:00
Jonathan Swoboda
6d9fc672d5 [libretiny] Fix lib_ignore handling and ignore incompatible libraries (#10846) 2025-09-29 21:39:16 +13:00
J. Nick Koston
b9361b0868 [esp32_improv] Disable loop by default until provisioning needed (#10764) 2025-09-29 21:39:16 +13:00
J. Nick Koston
0246a8eb1d [usb_host] Fix double-free crash with lock-free atomic pool allocation (#10926) 2025-09-29 14:48:51 +10:00
J. Nick Koston
a56d044d98 [api] Prevent API from overriding noise encryption keys set in YAML (#10927) 2025-09-28 22:42:58 -05:00
J. Nick Koston
f6253d52b4 [esp32_ble_server] Conditionally compile BLE automation features to save memory (#10910)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-29 14:28:03 +13:00
Clyde Stubbs
77dff52183 [mipi_spi] Fix t-display-amoled (#10922) 2025-09-29 12:12:06 +13:00
J. Nick Koston
4b86f31b66 [core] Fix platform component normalization happening too late in validation pipeline (#10908) 2025-09-29 12:09:08 +13:00
J. Nick Koston
78655968df [event_emitter] Replace unordered_map with vector - saves 2.6KB flash, 2.3x faster (#10900) 2025-09-29 12:07:13 +13:00
J. Nick Koston
ab79e596b5 [esp32_ble_server] Optimize notification and action managers for typical use cases (#10897) 2025-09-29 11:32:16 +13:00
J. Nick Koston
ef73ae2116 [esp32_ble_server] Replace HashMap with vector for services - saves 1KB flash, 26x faster (#10894) 2025-09-29 10:36:40 +13:00
J. Nick Koston
0111f725ff [esp32_ble_tracker] Reduce gap_scan_result log verbosity to VV (#10917) 2025-09-29 10:34:32 +13:00
J. Nick Koston
34b4cb46f6 [esp32_improv] Make device name visible to passive BLE scanners (#10918) 2025-09-29 10:33:36 +13:00
J. Nick Koston
a2f833d665 [captive_portal] Add DNS server support for ESP-IDF framework (#10919) 2025-09-29 10:07:26 +13:00
Brandon Ibach
a7042687c1 [spi] fix SPI interface selection on ESP32-S2 and -S3 (#10732) (#10766) 2025-09-28 10:58:42 +00:00
Oliver Kleinecke
0d2d18c198 [usb_uart] Disable flow control on ch34x 2025-09-28 19:35:40 +10:00
Oliver Kleinecke
3f03e8c423 [usb_host][usb_uart] Allow on ESP32-P4 (#10815) 2025-09-28 07:15:18 +00:00
Keith Burzinski
9dd6be4061 [zwave_proxy, api] Add notification message when Z-Wave HomeID changes (#10860) 2025-09-27 17:50:18 -05:00
Jonathan Swoboda
2bf79a607f [sx126x] Fix issues with variable length FSK packets (#10911) 2025-09-27 15:55:14 -04:00
J. Nick Koston
61a11547ca [esp32_improv] Fix crashes from uninitialized pointers and missing null checks (#10902) 2025-09-27 09:24:17 -05:00
J. Nick Koston
abf522bbb9 [ota] Add SHA256 password authentication with backward compatibility (#10809)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-09-26 17:50:27 -05:00
Patrick Van Oosterwijck
25fc16163b [ethernet] Fix Ethernet RMII capable variant validation (#10909) 2025-09-26 17:34:22 -05:00
dependabot[bot]
55593628ef Bump github/codeql-action from 3.30.4 to 3.30.5 (#10905)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-26 16:22:32 -05:00
dependabot[bot]
1f90d89731 Bump pyyaml from 6.0.2 to 6.0.3 (#10904)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-26 16:22:09 -05:00
J. Nick Koston
1560b8b8e2 [climate] Remove STL algorithm overhead in save_state() method (#10888) 2025-09-26 12:19:48 +12:00
J. Nick Koston
b26776fad4 [select] Remove STL algorithm overhead to reduce flash usage (#10887) 2025-09-26 12:19:06 +12:00
J. Nick Koston
875ada86b0 [web_server] Remove std::find_if overhead matching IDF implementation (#10889) 2025-09-26 12:14:16 +12:00
J. Nick Koston
195d1be4a9 [version] Reduce flash usage by optimizing string concatenation in setup() (#10890) 2025-09-26 12:12:39 +12:00
J. Nick Koston
2b12ff5874 [esp32_ble] Reduce RAM usage and firmware size by disabling unused GATT functionality (#10862) 2025-09-26 12:11:13 +12:00
J. Nick Koston
250b94d113 [text_sensor] Convert LOG_TEXT_SENSOR macro to function to reduce flash usage (#10884) 2025-09-26 12:10:31 +12:00
Jesse Hills
28199c1cf8 [stale] Clean up stale job (#10892) 2025-09-26 12:02:51 +12:00
Jesse Hills
eeb3ccaef7 Update stale bot rules (#10891) 2025-09-26 11:13:23 +12:00
Patrick Van Oosterwijck
460eb219ba [ethernet] Add LAN8670 PHY support (#10874)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2025-09-25 21:45:07 +00:00
Jonathan Swoboda
cef9cf49bf [htu21d] Fix I2C NACK issue and buffer overrun (#10801) 2025-09-25 15:54:38 -05:00
Jonathan Swoboda
28f09f9ed1 [dashboard] Fix progress bars on Windows (#10858) 2025-09-25 15:53:34 -05:00
J. Nick Koston
3eb502b328 Add sha256 support (#10882) 2025-09-26 08:53:21 +12:00
dependabot[bot]
7af77d0f82 Bump ruff from 0.13.1 to 0.13.2 (#10885)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2025-09-25 19:39:14 +00:00
dependabot[bot]
1c229947a8 Bump github/codeql-action from 3.30.3 to 3.30.4 (#10886)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-25 14:34:23 -05:00
Jonathan Swoboda
74f09a2b59 [core] Rename to clean-platform to clean-all (#10876) 2025-09-25 11:55:43 -04:00
J. Nick Koston
549626bee2 Fix flakey password auth failure integration test (#10883) 2025-09-25 11:39:56 -04:00
Jonathan Swoboda
65a1d2b2ff [scd30] Fix temp offset (#10847) 2025-09-25 09:13:27 -05:00
Antoine Lépée
f7ed127182 Add WTS01 temperature sensor component (#8539)
Co-authored-by: Antoine Lépée <alepee@MacBook-Pro-de-Antoine.local>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2025-09-25 13:38:31 +00:00
Jesse Hills
44767c32cf Pin SHA for github actions (#10879) 2025-09-25 08:08:16 -05:00
Jesse Hills
0cc03dfe32 [json] Parsing json without a lambda (#10838) 2025-09-25 16:35:19 +12:00
J. Nick Koston
1922b7b3ed [api] Make password and encryption mutually exclusive, add deprecation warning for password auth (#10871) 2025-09-24 22:20:35 -05:00
Michael Hansen
f22143f090 Add external wake word message (#10850) 2025-09-24 19:08:29 -05:00
dependabot[bot]
be92903a6f Bump actions/cache from 4.2.4 to 4.3.0 (#10868)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-24 19:06:31 -05:00
dependabot[bot]
538941b3fd Bump actions/cache from 4.2.4 to 4.3.0 in /.github/actions/restore-python (#10869)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-24 19:06:18 -05:00
dependabot[bot]
ce8ac8b89d Bump aioesphomeapi from 41.9.1 to 41.10.0 (#10872)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-24 23:11:53 +00:00
Stuart Parmenter
6d0f134ff1 Set color_order to RGB for the Waveshare ESP32-S3-TOUCH-LCD-4.3 and ESP32-S3-TOUCH-LCD-7-800X480 (#10835) 2025-09-24 08:59:16 -05:00
J. Nick Koston
11ccf0e591 [usb_host] Prevent USB data corruption from missed events (#10859) 2025-09-24 08:58:42 -05:00
J. Nick Koston
adfacdf1b7 [api] Consolidate authentication checks to reduce function call overhead (#10852) 2025-09-23 19:43:55 -05:00
J. Nick Koston
f8226cd481 [esp32_ble] Remove Arduino-specific BLE limitations and SplitDefaults (#10780) 2025-09-23 19:42:46 -05:00
Jesse Hills
63326cbd6d [api] Rename HomeassistantServiceResponse to HomeassistantActionRequest (#10839)
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: Keith Burzinski <kbx81x@gmail.com>
2025-09-23 17:58:24 -05:00
J. Nick Koston
d0d7abb542 Implement zero-copy for strings in base API calls (#10851) 2025-09-23 16:15:28 -05:00
dependabot[bot]
cd7922faaf Bump aioesphomeapi from 41.9.0 to 41.9.1 (#10857)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-23 20:58:49 +00:00
J. Nick Koston
365e3afa9b Implement zero-copy API for zwave_proxy (#10836)
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
2025-09-23 20:12:54 +00:00
dependabot[bot]
e9c2e211ef Bump aioesphomeapi from 41.8.0 to 41.9.0 (#10855)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-23 20:09:04 +00:00
J. Nick Koston
afda9500bf [zwave_proxy] Fix race condition sending zero home ID on reboot (#10848)
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
2025-09-23 19:21:49 +00:00
dependabot[bot]
bc7fc8df18 Bump aioesphomeapi from 41.7.0 to 41.8.0 (#10853)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-23 14:19:43 -05:00
J. Nick Koston
2f8a4d0caa [api] Implement zero-copy API for bluetooth_proxy writes (#10840) 2025-09-23 13:48:57 -05:00
J. Nick Koston
a7ee7b962e [wifi] Unify ESP32 WiFi implementation to use ESP-IDF driver (#10849) 2025-09-23 12:15:12 -05:00
Jonathan Swoboda
3cb2a4569c [core] Add a clean-platform option (#10831) 2025-09-23 12:41:25 -04:00
Jesse Hills
3b20969171 [core] Add typing to some core files (#10843) 2025-09-23 09:32:13 -05:00
Jonathan Swoboda
3b40172073 [libretiny] Fix lib_ignore handling and ignore incompatible libraries (#10846) 2025-09-23 10:21:19 -04:00
Jesse Hills
2e220fcca2 [camera-encoder] Use defines instead of build flags (#10824)
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-09-23 08:44:43 -05:00
dependabot[bot]
56e8af79c3 Bump aioesphomeapi from 41.6.0 to 41.7.0 (#10841)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-23 00:18:13 -05:00
dependabot[bot]
25e9ec1782 Bump aioesphomeapi from 41.4.0 to 41.6.0 (#10833)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-09-22 23:06:11 -05:00
J. Nick Koston
1771c852af Pin ruamel.yaml.clib to 0.2.12 (#10837) 2025-09-22 23:01:37 -05:00
Nerdiy.de
8714a45a5c Fix incorrect factor for value calculation in MMC5603 component (#9925) 2025-09-22 21:48:34 -04:00
Jesse Hills
5e94460608 [CI] Format files after sync (#10828) 2025-09-23 07:48:39 +12:00
brambo123
d302c0c600 [uart] Multiple ESP32 features and fixes (#8103)
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
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-09-22 12:15:19 -05:00
Sam
5c943d7c13 tuya: handle WIFI_SELECT and WIFI_RESET (#10822)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2025-09-22 17:05:41 +12:00
Javier Peletier
7629903afb [substitutions] implement !literal (#10785) 2025-09-22 16:32:59 +12:00
Javier Peletier
68eb4091b8 [substitutions] add missing safe globals tests (#10814) 2025-09-22 16:29:15 +12:00
J. Nick Koston
5062e7a0e1 Fix missing os import after merge collisions (#10823) 2025-09-21 15:59:44 -06:00
J. Nick Koston
30bb640c89 Skip external component updates when running logs command (#10756)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2025-09-21 21:15:49 +00:00
J. Nick Koston
fbb48c504f [esp32_improv] Disable loop by default until provisioning needed (#10764) 2025-09-22 09:08:55 +12:00
J. Nick Koston
440b0b5574 [tests] Add integration tests for oversized payload handling in API (#10788) 2025-09-22 09:07:47 +12:00
J. Nick Koston
c64d385fa6 [web_server] Reduce flash usage by eliminating lambda overhead in JSON generation (#10749) 2025-09-22 09:06:59 +12:00
J. Nick Koston
0432a10543 Add coverage for Path to str fix in #10807 (#10808) 2025-09-22 08:59:19 +12:00
J. Nick Koston
4729bc87fa [core] Fix TypeError in update-all command after Path migration (#10821) 2025-09-21 13:07:27 -04:00
Jonathan Swoboda
e3b64103cc [sensirion] Fix warning (#10813) 2025-09-20 21:23:33 -05:00
esphomebot
ebdcb3e4d9 Synchronise Device Classes from Home Assistant (#10803)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2025-09-20 13:09:21 +00:00
J. Nick Koston
971522574d [http_request] Fix Path object passed to C++ codegen (#10812) 2025-09-19 20:10:02 -04:00
J. Nick Koston
73e939dbbc [zephyr] Fix compilation after Path migration (#10811) 2025-09-19 20:09:32 -04:00
dependabot[bot]
a96798ef98 Bump esptool from 5.0.2 to 5.1.0 (#10758)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-19 15:13:47 -06:00
dependabot[bot]
923e7049f1 Bump aioesphomeapi from 41.1.0 to 41.4.0 (#10805)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-19 15:04:43 -06:00
Paulus Schoutsen
26df542036 Fix esphome run (#10807) 2025-09-19 15:36:46 -05:00
Keith Burzinski
1ccec6950a [zwave_proxy] Send Home ID in DeviceInfoResponse (#10798)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: J. Nick Koston <nick+github@koston.org>
Co-authored-by: AlCalzone <d.griesel@gmx.net>
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-09-19 14:52:54 +00:00
dependabot[bot]
b3a122de3c Bump ruff from 0.13.0 to 0.13.1 (#10802)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2025-09-19 08:04:29 -06:00
Jesse Hills
9ea3643b74 [core] os.path -> Path (#10654)
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@home-assistant.io>
2025-09-19 12:59:48 +00:00
Jesse Hills
de617c85c7 Merge branch 'release' into dev 2025-09-19 20:47:37 +12:00
Jesse Hills
e47f4ef602 Merge pull request #10796 from esphome/bump-2025.9.1
2025.9.1
2025-09-19 20:46:53 +12:00
Keith Burzinski
9c201afe76 [api_protobuf.py] Use type appropriate for estimated_size (#10797) 2025-09-18 20:55:45 -05:00
J. Nick Koston
2bb64a189d [dashboard] Transfer DNS/mDNS cache from dashboard to CLI to avoid blocking (#10685) 2025-09-18 20:13:13 -05:00
Jesse Hills
9853a2e6ab [ektf2232] Rename rts_pin to reset_pin (#10720) 2025-09-18 18:41:23 -06:00
Jesse Hills
961be7fd12 Bump version to 2025.9.1 2025-09-19 11:52:10 +12:00
J. Nick Koston
a5a21f47d1 [gpio] Fix unused function warnings when compiling with log level below DEBUG (#10779) 2025-09-19 11:52:09 +12:00
J. Nick Koston
a06cd84974 [core] Fix ESP8266 mDNS compilation failure caused by incorrect coroutine priorities (#10773) 2025-09-19 11:52:09 +12:00
Subhash Chandra
e3703b43c1 [packet_transport] Refactor sensor/provider list handling to be idempotent (#10765) 2025-09-19 11:52:09 +12:00
J. Nick Koston
f6dc25c0ce [mqtt] Fix KeyError when MQTT logging configured without explicit level (#10774) 2025-09-19 11:52:09 +12:00
Keith Burzinski
fad0ec7793 [zwave_proxy] New component (#10762)
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-09-18 15:43:14 -05:00
J. Nick Koston
a302cec993 [libretiny] Optimize preferences memory usage by replacing vector with unique_ptr (#10731) 2025-09-18 05:25:29 -05:00
J. Nick Koston
6781da45cb [esp32] Optimize NVS preferences memory usage by replacing vector with unique_ptr (#10729) 2025-09-18 05:24:50 -05:00
J. Nick Koston
37d526f003 [gpio] Fix unused function warnings when compiling with log level below DEBUG (#10779) 2025-09-18 05:22:22 -05:00
J. Nick Koston
d74cfefeef [ethernet] Remove redundant Arduino framework version check (#10781) 2025-09-17 23:39:14 -05:00
J. Nick Koston
1ffb9d972a [core] Fix ESP8266 mDNS compilation failure caused by incorrect coroutine priorities (#10773) 2025-09-18 13:11:30 +12:00
Subhash Chandra
4e5339801b [packet_transport] Refactor sensor/provider list handling to be idempotent (#10765) 2025-09-18 00:14:31 +00:00
Jonathan Swoboda
b8cee477fe [esp32] Use arduino as an idf component (#10647)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-09-17 17:23:34 -05:00
J. Nick Koston
ff2df278d6 [api] Rename ConnectRequest/Response to AuthenticationRequest/Response (#10726) 2025-09-18 07:42:37 +12:00
J. Nick Koston
429e989b69 [core] Make StringRef convertToJson inline to save 250+ bytes flash (#10751) 2025-09-18 07:40:32 +12:00
Martin Weinelt
28541bdb1c Migrate to SPDX license specifier in pyproject.toml (#10768) 2025-09-18 07:38:18 +12:00
J. Nick Koston
11c595bb09 [mqtt] Fix KeyError when MQTT logging configured without explicit level (#10774) 2025-09-18 07:38:02 +12:00
dependabot[bot]
fd888eaa68 Bump aioesphomeapi from 40.2.1 to 41.1.0 (#10776)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-17 14:29:58 -05:00
Jesse Hills
3a233b2fd0 Merge branch 'release' into dev 2025-09-17 18:52:06 +12:00
Jesse Hills
d2df232706 Merge pull request #10763 from esphome/bump-2025.9.0
2025.9.0
2025-09-17 18:51:21 +12:00
Jesse Hills
404e679e66 Bump version to 2025.9.0 2025-09-17 11:02:12 +12:00
Jesse Hills
4426bf6029 Merge branch 'beta' into dev 2025-09-17 10:50:48 +12:00
Jesse Hills
8d401ad05a Merge pull request #10761 from esphome/bump-2025.9.0b4
2025.9.0b4
2025-09-17 10:50:15 +12:00
Jesse Hills
e542816f7d Bump version to 2025.9.0b4 2025-09-17 09:22:54 +12:00
J. Nick Koston
12cadf0a04 [core] Fix clean build files to properly clear PlatformIO cache (#10754) 2025-09-17 09:22:54 +12:00
J. Nick Koston
adc3d3127d [wizard] Fix KeyError when running wizard with empty OTA password (#10753) 2025-09-17 09:22:54 +12:00
J. Nick Koston
61ab682099 Add additional coverage for util and writer (#10683) 2025-09-17 09:22:54 +12:00
J. Nick Koston
27fa18dcec [core] Fix clean build files to properly clear PlatformIO cache (#10754) 2025-09-17 08:09:35 +12:00
J. Nick Koston
22989592f0 [wizard] Fix KeyError when running wizard with empty OTA password (#10753) 2025-09-17 07:56:54 +12:00
dependabot[bot]
1f4b10f523 Bump pytest-mock from 3.15.0 to 3.15.1 (#10759)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-16 14:54:56 -05:00
Jesse Hills
cbaf8d309b Merge branch 'beta' into dev 2025-09-17 00:17:01 +12:00
Jesse Hills
c05b7cca5e Merge pull request #10752 from esphome/bump-2025.9.0b3
2025.9.0b3
2025-09-17 00:15:06 +12:00
Jesse Hills
6ac395da6d Bump version to 2025.9.0b3 2025-09-16 20:35:23 +12:00
jokujossai
54616ae1b4 [ade7880] fix channel a voltage registry (#10750) 2025-09-16 20:35:23 +12:00
jokujossai
e33dcda907 [mqtt] fix publish payload length when payload contains null characters (#10744) 2025-09-16 20:35:23 +12:00
J. Nick Koston
04c1b90e57 [ethernet] Conditionally compile PHY-specific code to reduce flash usage (#10747) 2025-09-16 20:35:23 +12:00
J. Nick Koston
ddb8fedef7 [dashboard] Fix archive handler to properly delete build folders using correct path (#10724) 2025-09-16 20:35:23 +12:00
J. Nick Koston
04f4f79cb4 [select] Use const references to avoid unnecessary vector copies (#10741) 2025-09-16 20:35:23 +12:00
J. Nick Koston
8890071360 Add additional test coverage ahead of Path conversion (#10700) 2025-09-16 20:35:23 +12:00
J. Nick Koston
4b3a997a8e Improve coverage for various core modules (#10663) 2025-09-16 20:35:23 +12:00
jokujossai
660223e269 [ade7880] fix channel a voltage registry (#10750) 2025-09-16 17:00:22 +12:00
jokujossai
6d1de2106e [mqtt] fix publish payload length when payload contains null characters (#10744) 2025-09-16 15:28:36 +12:00
DT-art1
90e33306f1 [const] Move CONF_CLEAR to const.py (#10742) 2025-09-16 13:24:23 +12:00
J. Nick Koston
f3ac21b3b4 [ethernet] Conditionally compile PHY-specific code to reduce flash usage (#10747) 2025-09-15 23:46:07 +00:00
J. Nick Koston
4859fe67eb [dashboard] Fix archive handler to properly delete build folders using correct path (#10724) 2025-09-16 11:04:35 +12:00
J. Nick Koston
a723673dcc [select] Use const references to avoid unnecessary vector copies (#10741) 2025-09-16 09:16:26 +12:00
Jesse Hills
612fb4cc3c [CI] Check esp32 boards file is up to date (#10730) 2025-09-15 15:03:02 -05:00
J. Nick Koston
5fac67d195 [json] Only compile SpiRamAllocator when PSRAM is enabled (#10728) 2025-09-15 11:50:11 -05:00
Jesse Hills
d671862e9a Merge branch 'beta' into dev 2025-09-15 18:29:26 +12:00
Jesse Hills
2a4ab6a811 Merge pull request #10725 from esphome/bump-2025.9.0b2
2025.9.0b2
2025-09-15 18:28:51 +12:00
J. Nick Koston
459ef7f262 [api] Exclude ConnectRequest/Response when password is disabled (#10704) 2025-09-14 20:45:28 -05:00
J. Nick Koston
bd9dc43e59 Add additional coverage ahead of Path conversion (#10723) 2025-09-15 13:33:15 +12:00
Jesse Hills
971de64494 Bump version to 2025.9.0b2 2025-09-15 12:34:56 +12:00
J. Nick Koston
926fdcbecd [esp32_ble] Optimize BLE hex formatting to eliminate sprintf dependency (#10714)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-09-15 12:34:56 +12:00
J. Nick Koston
6b147312cd [wifi] Optimize WiFi MAC formatting to eliminate sprintf dependency (#10715)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-09-15 12:34:56 +12:00
J. Nick Koston
2d9152d9b9 [md5] Optimize MD5::get_hex() to eliminate sprintf dependency (#10710) 2025-09-15 12:34:56 +12:00
dependabot[bot]
24f9550ce5 Bump aioesphomeapi from 40.2.0 to 40.2.1 (#10721)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-15 12:34:56 +12:00
Big Mike
3427aaab8c ina2xx should be total increasing for energy sensor (#10711) 2025-09-15 12:34:56 +12:00
J. Nick Koston
4e17d14acc [scheduler] Fix timing accumulation in scheduler causing incorrect execution measurements (#10719) 2025-09-15 12:34:56 +12:00
J. Nick Koston
1750f02ef3 [api] Optimize HelloResponse server_info to reduce memory usage (#10701) 2025-09-15 12:34:56 +12:00
J. Nick Koston
ae158179bd [api] Revert unneeded GetTime bidirectional support added in #9790 (#10702) 2025-09-15 12:34:55 +12:00
J. Nick Koston
c601494779 [core] Optimize MAC address formatting to eliminate sprintf dependency (#10713) 2025-09-15 12:34:55 +12:00
J. Nick Koston
646f4e66be [ethernet] Fix permanent component failure from undocumented ESP_FAIL in IPv6 setup (#10708) 2025-09-15 12:34:55 +12:00
dependabot[bot]
5b5e5c213c Bump aioesphomeapi from 40.1.0 to 40.2.0 (#10703)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-15 12:34:55 +12:00
Markus
46235684b1 [core] fix upload to device via MQTT IP lookup (e.g. when mDNS is disable) (#10632)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: J. Nick Koston <nick+github@koston.org>
2025-09-15 12:34:55 +12:00
J. Nick Koston
5b702a1efa Add additional dashboard and main tests (#10688)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-15 12:34:55 +12:00
J. Nick Koston
56e9fd2e38 [tests] Add upload_program and show_logs test coverage to prevent regressions (#10684) 2025-09-15 12:34:55 +12:00
J. Nick Koston
65f15a706f Add some more coverage for dashboard web_server (#10682) 2025-09-15 12:34:55 +12:00
J. Nick Koston
eee64cc3a6 Add comprehensive tests for choose_upload_log_host to prevent regressions (#10679)
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-09-15 12:34:55 +12:00
J. Nick Koston
f43fb3c3a3 [core] Add millisecond precision to logging timestamps (#10677) 2025-09-15 12:34:55 +12:00
rwrozelle
79b0025fe6 Openthread Fix Factory Reset (#9281)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-09-15 12:34:55 +12:00
Jesse Hills
c6a039a72f [adc] Fix FILTER_SOURCE_FILES location (#10673) 2025-09-15 12:34:54 +12:00
esphomebot
6f1fa094c2 Update webserver local assets to 20250910-110003 (#10668) 2025-09-15 12:34:54 +12:00
J. Nick Koston
1d5a3b647d [esp32_ble] Optimize BLE hex formatting to eliminate sprintf dependency (#10714)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-09-15 12:04:45 +12:00
Jimmy Hedman
af3e1788d1 Unpin libretiny version in network test (#10717) 2025-09-14 22:54:14 +00:00
J. Nick Koston
b946cb160d [wifi] Optimize WiFi MAC formatting to eliminate sprintf dependency (#10715)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-09-14 22:35:27 +00:00
J. Nick Koston
e0241e9dcd [md5] Optimize MD5::get_hex() to eliminate sprintf dependency (#10710) 2025-09-14 22:35:18 +00:00
dependabot[bot]
1accc409f6 Bump aioesphomeapi from 40.2.0 to 40.2.1 (#10721)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-14 22:33:07 +00:00
Big Mike
f756de276b ina2xx should be total increasing for energy sensor (#10711) 2025-09-15 10:16:01 +12:00
J. Nick Koston
ac07a00141 [scheduler] Fix timing accumulation in scheduler causing incorrect execution measurements (#10719) 2025-09-14 22:05:56 +00:00
J. Nick Koston
7ae11de2e4 [api] Optimize HelloResponse server_info to reduce memory usage (#10701) 2025-09-15 09:54:42 +12:00
J. Nick Koston
bb6be9c939 [api] Revert unneeded GetTime bidirectional support added in #9790 (#10702) 2025-09-15 09:52:19 +12:00
J. Nick Koston
9c85a7eff3 [core] Optimize MAC address formatting to eliminate sprintf dependency (#10713) 2025-09-15 09:50:38 +12:00
J. Nick Koston
10a665b864 [ethernet] Fix permanent component failure from undocumented ESP_FAIL in IPv6 setup (#10708) 2025-09-15 09:45:22 +12:00
J. Nick Koston
35dce3c80d Add additional test coverage ahead of Path conversion (#10700) 2025-09-15 09:31:38 +12:00
dependabot[bot]
7e6b11ce84 Bump aioesphomeapi from 40.1.0 to 40.2.0 (#10703)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-13 23:25:19 +00:00
Keith Burzinski
adcba4fd9a [api_protobuf.py] Use type based on size/length (#10696) 2025-09-13 17:02:04 -05:00
Markus
d3592c451b [core] fix upload to device via MQTT IP lookup (e.g. when mDNS is disable) (#10632)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: J. Nick Koston <nick+github@koston.org>
2025-09-12 16:31:53 -05:00
J. Nick Koston
24eb33a1c0 Add additional dashboard and main tests (#10688)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-13 09:04:56 +12:00
dependabot[bot]
cf1fef8cfb Bump pytest-asyncio from 1.1.0 to 1.2.0 (#10691)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-12 14:09:38 -05:00
fakuivan
28bba0666c [packet_transport] initialize packet data after flushing (#10686) 2025-09-13 05:02:41 +10:00
J. Nick Koston
4390fd80a3 Add additional coverage for util and writer (#10683) 2025-09-12 17:04:51 +12:00
J. Nick Koston
4813c5134e [tests] Add upload_program and show_logs test coverage to prevent regressions (#10684) 2025-09-12 17:04:22 +12:00
J. Nick Koston
bbef0e173e [esp32_ble_tracker] Simplify BLE client state machine by removing READY_TO_CONNECT (#10672) 2025-09-12 08:54:34 +12:00
J. Nick Koston
3240e19a7c Add some more coverage for dashboard web_server (#10682) 2025-09-12 08:52:46 +12:00
J. Nick Koston
ac0cd946f0 Add comprehensive tests for choose_upload_log_host to prevent regressions (#10679)
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-09-12 08:51:58 +12:00
Jonathan Swoboda
61bac6c6e6 [esp32] Allow esp-idf 5.5.1 (#10680) 2025-09-11 20:13:05 +00:00
J. Nick Koston
5fd64c5c89 [core] Add millisecond precision to logging timestamps (#10677) 2025-09-11 14:25:55 -05:00
rwrozelle
625f108183 Openthread Fix Factory Reset (#9281)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-09-11 05:23:58 +00:00
J. Nick Koston
c45efe8f40 Add additional coverage for yaml_util (#10674) 2025-09-11 17:01:11 +12:00
Jesse Hills
fe1371f4dc [adc] Fix FILTER_SOURCE_FILES location (#10673) 2025-09-10 22:32:04 -05:00
J. Nick Koston
e3f8a36eaa Add coverage for dashboard ahead of Path conversion (#10669) 2025-09-10 22:16:04 -05:00
dependabot[bot]
41f0d1c622 Bump ruff from 0.12.12 to 0.13.0 (#10670)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2025-09-10 21:09:45 +00:00
Jesse Hills
6469bb168d Merge branch 'beta' into dev 2025-09-11 07:25:06 +12:00
Jesse Hills
7a869a33f0 Merge pull request #10665 from esphome/bump-2025.9.0b1
2025.9.0b1
2025-09-11 07:24:33 +12:00
esphomebot
af0da3f897 Update webserver local assets to 20250910-110003 (#10668) 2025-09-10 10:41:18 -05:00
Jonathan Swoboda
32e4eb26ad [remote] Remove duplicate implementations of remote code (#10548) 2025-09-10 10:46:30 -04:00
J. Nick Koston
10aae33979 Improve coverage for various core modules (#10663) 2025-09-10 08:17:34 -05:00
Keith Burzinski
56e85b3ef9 [thermostat] Rename timer enums to mitigate naming conflict (#10666) 2025-09-10 22:58:35 +12:00
Keith Burzinski
55dd12c66b [thermostat] Rename timer enums to mitigate naming conflict (#10666) 2025-09-10 22:58:07 +12:00
Jesse Hills
9dd17b464d Bump version to 2025.10.0-dev 2025-09-10 19:48:02 +12:00
Jesse Hills
2401f81be3 Bump version to 2025.9.0b1 2025-09-10 19:48:01 +12:00
Josip Šimun Kuči
52a7e26c6d [inkplate] Rename component and fix grayscale (#10200)
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>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-10 18:46:58 +12:00
Jesse Hills
d729dc20a8 Merge branch 'release' into dev 2025-09-10 17:04:21 +12:00
Jesse Hills
d3b7a9687b Merge pull request #10664 from esphome/bump-2025.8.4
2025.8.4
2025-09-10 17:03:37 +12:00
Jesse Hills
9d7fc11108 Bump version to 2025.8.4 2025-09-10 13:56:50 +12:00
tomaszduda23
7969627d3e [light] add missing header (#10590) 2025-09-10 13:56:50 +12:00
Clyde Stubbs
82d2e367d4 [kmeteriso] Fix i2c call (#10618) 2025-09-10 13:56:50 +12:00
Keith Burzinski
972aa691e4 [sen5x] Fix initialization (#10603) 2025-09-10 13:56:50 +12:00
Daniel M
ac61b8f893 [bl0940] extend configuration options of bl0940 device (#8158)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2025-09-10 13:50:49 +12:00
Keith Burzinski
d9f625e5c8 [thermostat] General clean-up, optimization, properly support "auto" mode (#10561)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-09-10 13:24:50 +12:00
Jonathan Swoboda
e218f16f0f Allow both files and directories to be passed to update-all (#10575)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2025-09-09 18:06:59 -05:00
J. Nick Koston
422d209786 [api] Add timezone support to GetTimeResponse for automatic timezone synchronization (#10661) 2025-09-09 15:54:50 -05:00
dependabot[bot]
e972e1f8c2 Bump aioesphomeapi from 40.0.2 to 40.1.0 (#10662)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-09 20:33:36 +00:00
dependabot[bot]
cfb90b7b18 Bump pytest-cov from 6.3.0 to 7.0.0 (#10660)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-09 15:15:53 -05:00
mrtoy-me
8976ea2436 [ms5611] remove delay in setup (#10658) 2025-09-10 07:31:06 +12:00
tomaszduda23
01ff09064d [nrf52] add more tests (#10591) 2025-09-10 07:29:49 +12:00
tomaszduda23
39212f0d7f allow to implement show_logs as external component (#10523) 2025-09-09 11:45:42 -05:00
Mischa Siekmann
8993f4e6b4 RingBuffer: Make partial writing optional (#10302) 2025-09-09 08:39:47 -05:00
J. Nick Koston
7adad0ee49 [core] Refactor insertion sort functions to eliminate code duplication (#10653) 2025-09-09 02:03:35 -05:00
J. Nick Koston
dd8815ec9d [core] Reduce flash usage by refactoring looping component partitioning (#10652) 2025-09-09 01:17:30 -05:00
Jesse Hills
59e62a1f44 Sort codeowners using case-insensitive (#10651)
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-09-08 21:26:22 -05:00
Edward Firmo
f5f84fe825 [nextion] Increase delay before reboot to prevent TFT upload interruption (#10402) 2025-09-09 12:57:24 +12:00
Edward Firmo
90c2fdd565 [adc] Fix autorange negative coefficient bug causing incorrect voltage readings (#10549) 2025-09-09 12:56:18 +12:00
tomaszduda23
f6d69231e8 [light] add missing header (#10590) 2025-09-09 11:10:29 +12:00
J. Nick Koston
5cc0e21bc7 [core] Reduce unnecessary nesting in scheduler loop (#10644) 2025-09-09 09:04:07 +12:00
Thomas Rupprecht
703b592793 Add I2S Audio Port for ESP32-C5/C6/H2 (#10414) 2025-09-08 14:03:41 -04:00
J. Nick Koston
75c9430d91 [core] Fix serial upload regression from DNS resolution PR #10595 (#10648) 2025-09-08 10:41:03 -05:00
J. Nick Koston
e5bba00deb [esp32] Reduce GPIO memory usage by 50% through bit-packing (#10556) 2025-09-08 08:46:30 -05:00
J. Nick Koston
8d90f13e97 [core] Store component source strings in flash on ESP8266 (breaking change) (#10621) 2025-09-07 20:10:00 -05:00
J. Nick Koston
666e33e70b [api] Store plaintext error message in PROGMEM on ESP8266 (#10634) 2025-09-07 20:09:47 -05:00
Clyde Stubbs
7eaaa4e426 [mipi_rgb] Unified driver for MIPI RGB displays (#9892)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-09-08 12:56:34 +12:00
J. Nick Koston
166ad942ef [scheduler] Reduce SchedulerItem memory usage by 7.4% on 32-bit platforms (#10553)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-07 18:51:07 -05:00
J. Nick Koston
0ff08bbc09 [mcp23016] Migrate to CachedGpioExpander to reduce I2C bus usage (#10581)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-07 18:26:49 -05:00
J. Nick Koston
6e2bcabbc9 [sx1509] Migrate to CachedGpioExpander to reduce I2C bus usage (#10588)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-09-07 18:26:33 -05:00
J. Nick Koston
afa191ae41 [pcf8574] Migrate to CachedGpioExpander to reduce I2C bus usage (#10573)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-09-07 18:26:22 -05:00
J. Nick Koston
93da52c4d2 [pca9554] Migrate to CachedGpioExpander to reduce I2C bus usage (#10571)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-09-07 18:26:11 -05:00
J. Nick Koston
0cc0979674 [pca6416a] Migrate to CachedGpioExpander to reduce I2C bus usage (#10587) 2025-09-08 10:59:23 +12:00
Jesse Hills
629f1e94f1 [ota] Fix duplicate include and sort (#10643) 2025-09-07 22:58:41 +00:00
J. Nick Koston
8c28f346c7 [gpio_expander] Add intelligent pin type selection to CachedGpioExpander template (#10577) 2025-09-08 10:57:02 +12:00
dependabot[bot]
3cf36e2f94 Bump aioesphomeapi from 40.0.1 to 40.0.2 (#10641)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-07 17:32:21 -05:00
J. Nick Koston
1ac07c96b1 [esphome] Store OTA component log strings in flash on ESP8266 (#10570) 2025-09-08 10:30:39 +12:00
J. Nick Koston
91228c82e6 [esp8266][logger] Store LOG_LEVELS strings in PROGMEM to reduce RAM usage (#10569) 2025-09-08 10:29:45 +12:00
J. Nick Koston
28d16728d3 [core] Add memory pool to scheduler to reduce heap fragmentation (#10536) 2025-09-08 10:27:58 +12:00
dependabot[bot]
f24a182ba2 Bump pytest-cov from 6.2.1 to 6.3.0 (#10640)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-07 17:26:31 -05:00
dependabot[bot]
0065fe1516 Bump zeroconf from 0.147.0 to 0.147.2 (#10642)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-07 17:26:06 -05:00
J. Nick Koston
148fa698cc Fix DNS resolution inconsistency between logs and OTA operations (#10595)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2025-09-08 10:25:22 +12:00
J. Nick Koston
b25506b045 [core] Skip redundant process_to_add() call when no scheduler items added (#10630)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-08 09:10:55 +12:00
J. Nick Koston
0c737fc4df [core] Convert LOG_UPDATE_INTERVAL macro to function to reduce flash usage (#10636) 2025-09-08 09:09:15 +12:00
davidmonro
a8b8507ffc Atm90e32/26 device class fixes (#10629) 2025-09-08 08:06:10 +12:00
J. Nick Koston
c33bb3a8a9 [esp8266] Store component warning strings in flash to reduce RAM usage (#10623) 2025-09-06 23:56:45 -05:00
Clyde Stubbs
4d09932320 [kmeteriso] Fix i2c call (#10618) 2025-09-06 13:51:44 +10:00
Keith Burzinski
e018b15641 [sen5x] Various optimizing & tidying up (#10602) 2025-09-05 20:10:48 -05:00
J. Nick Koston
3fd469cfe8 [esp8266][api] Store error strings in PROGMEM to reduce RAM usage (#10568) 2025-09-05 18:16:43 -05:00
J. Nick Koston
1359142106 [api] Store Noise protocol prologue in flash on ESP8266 (#10598) 2025-09-05 18:10:18 -05:00
J. Nick Koston
487ba4dad0 [mdns] Move constant strings to flash on ESP8266 (#10599) 2025-09-05 18:08:25 -05:00
J. Nick Koston
694c590eb6 [captive_portal] ESP8266: Move strings to PROGMEM (saves 192 bytes RAM) (#10600) 2025-09-05 18:02:12 -05:00
J. Nick Koston
b74463c3e6 [light] ESP8266: Store log strings in flash memory (#10611) 2025-09-05 17:59:24 -05:00
J. Nick Koston
98e8a0c201 [gpio] ESP8266: Store log strings in flash memory (#10610) 2025-09-05 17:57:24 -05:00
J. Nick Koston
91b2f75d04 [script] ESP8266: Store log format strings in PROGMEM (saves 240 bytes RAM) (#10614) 2025-09-05 17:56:00 -05:00
J. Nick Koston
f1806046a9 [web_server] ESP8266: Store OTA response strings in PROGMEM (saves 52 bytes RAM) (#10616) 2025-09-05 17:53:23 -05:00
J. Nick Koston
5b283d6d38 [sensor] ESP8266: Use LogString for state_class_to_string() to save RAM (#10617) 2025-09-05 17:51:35 -05:00
J. Nick Koston
1340665ac7 [logger] Use LogString for UART selection strings (saves 28 bytes RAM on ESP8266) (#10615)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-05 17:47:53 -05:00
J. Nick Koston
1510db277c [esphome] ESP8266: Move OTA error strings to PROGMEM (saves 116 bytes RAM) (#10620) 2025-09-05 17:44:23 -05:00
Keith Burzinski
a49669ee58 [sensirion_common] Tidy up, optimize (#10604) 2025-09-05 17:17:20 -05:00
Keith Burzinski
09b40b882e [sgp30] Tidy up, optimize (#10607) 2025-09-05 14:20:11 -05:00
Keith Burzinski
0069163d31 [sps30] Tidy up, optimize (#10606) 2025-09-05 14:11:14 -05:00
Keith Burzinski
86c2af4882 [sen5x] Fix initialization (#10603) 2025-09-05 18:37:57 +12:00
J. Nick Koston
b4b795dcaf [i2c] Optimize memory usage with stack allocation for small buffers (#10565)
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
2025-09-05 04:26:46 +00:00
dependabot[bot]
b8ed7ec145 Bump aioesphomeapi from 40.0.0 to 40.0.1 (#10596)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-05 01:08:15 +00:00
dependabot[bot]
365a427b57 Bump aioesphomeapi from 39.0.1 to 40.0.0 (#10594)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-04 23:37:03 +00:00
dependabot[bot]
e327ae8c95 Bump pytest-mock from 3.14.1 to 3.15.0 (#10593)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-04 23:36:11 +00:00
dependabot[bot]
4c2f356b35 Bump ruff from 0.12.11 to 0.12.12 (#10578)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2025-09-04 19:24:23 +00:00
dependabot[bot]
e55bce83e3 Bump actions/stale from 9.1.0 to 10.0.0 (#10582)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-04 14:20:11 -05:00
dependabot[bot]
ba2433197e Bump actions/github-script from 7.0.1 to 8.0.0 (#10583)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-04 14:16:17 -05:00
dependabot[bot]
c471bdb446 Bump actions/setup-python from 5.6.0 to 6.0.0 in /.github/actions/restore-python (#10586)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-04 14:15:57 -05:00
dependabot[bot]
cbac9caa52 Bump actions/setup-python from 5.6.0 to 6.0.0 (#10584)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-04 14:15:43 -05:00
dependabot[bot]
edf7094662 Bump esphome-dashboard from 20250828.0 to 20250904.0 (#10580)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-04 14:14:50 -05:00
dependabot[bot]
25489b6009 Bump codecov/codecov-action from 5.5.0 to 5.5.1 (#10585)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-04 14:14:28 -05:00
dependabot[bot]
dc45a613f3 Bump pytest from 8.4.1 to 8.4.2 (#10579)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-04 14:14:18 -05:00
dependabot[bot]
e0617e01e0 Bump pypa/gh-action-pypi-publish from 1.12.4 to 1.13.0 in /.github/workflows (#10572)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-04 11:10:53 -05:00
Jesse Hills
c7ee727af4 Merge branch 'release' into dev 2025-09-04 22:10:21 +12:00
Jesse Hills
c5b2a9e24b Merge pull request #10558 from esphome/bump-2025.8.3
2025.8.3
2025-09-04 22:09:37 +12:00
J. Nick Koston
101d553df9 [esp8266] Reduce preference memory usage by 40% through field optimization (#10557) 2025-09-04 02:46:50 -05:00
J. Nick Koston
8fb6420b1c [esp8266] Store GPIO initialization arrays in PROGMEM to save RAM (#10560) 2025-09-04 02:44:12 -05:00
Maxim Raznatovski
c03d978b46 [wizard] extend the wizard dashboard API to allow upload and empty config options (#10203) 2025-09-04 14:02:49 +12:00
Jesse Hills
2d3cdf60ba Bump version to 2025.8.3 2025-09-04 09:06:00 +12:00
J. Nick Koston
a29fef166b [api] Fix VERY_VERBOSE logging compilation error with bool arrays (#10539) 2025-09-04 09:06:00 +12:00
Jonathan Swoboda
9fe94f1201 [esp32] Clear IDF environment variables (#10527)
Co-authored-by: J. Nick Koston <nick+github@koston.org>
2025-09-04 09:06:00 +12:00
Anton Viktorov
1b8978a89a [i2c] Fix bug write_register16 (#10547) 2025-09-04 09:06:00 +12:00
Jonathan Swoboda
6f188d1284 [esp32] Rebuild when idf_component.yml changes (#10540) 2025-09-04 09:06:00 +12:00
Clyde Stubbs
a1a336783e [mcp4461] Fix read transaction (#10465)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-09-04 09:06:00 +12:00
Clyde Stubbs
c55bc93f70 [mipi_dsi] Fix config for Guition screen (#10464) 2025-09-04 09:06:00 +12:00
J. Nick Koston
de998f2f39 Fix incorrect entity count due to undefined execution order with globals (#10497) 2025-09-04 09:06:00 +12:00
Oliver Kleinecke
950299e52b Update mcp4461.cpp (#10479) 2025-09-04 09:06:00 +12:00
J. Nick Koston
23c6650902 [api] Fix VERY_VERBOSE logging compilation error with bool arrays (#10539) 2025-09-04 08:07:13 +12:00
Jonathan Swoboda
5759692627 [esp32] Clear IDF environment variables (#10527)
Co-authored-by: J. Nick Koston <nick+github@koston.org>
2025-09-03 19:59:47 +00:00
Jonathan Swoboda
0ab65c225e [wifi] Check for esp32_hosted on no wifi variants (#10528) 2025-09-04 07:58:42 +12:00
J. Nick Koston
8aeb6d3ba2 [bluetooth_proxy] Change default for active connections to true (#10546) 2025-09-04 07:27:39 +12:00
Anton Viktorov
c3359edb33 [i2c] Fix bug write_register16 (#10547) 2025-09-03 17:18:26 +00:00
Jonathan Swoboda
4d681ffe3d [esp32] Rebuild when idf_component.yml changes (#10540) 2025-09-03 11:47:51 -04:00
J. Nick Koston
68628a85b1 [core] Use get_unit_of_measurement_ref() in entity logging to avoid string allocations (#10532) 2025-09-03 06:08:57 +00:00
J. Nick Koston
086f1982fa [core] Use get_device_class_ref() in entity platform logging to avoid string allocations (#10531) 2025-09-03 14:26:53 +12:00
J. Nick Koston
5ba1c32242 [host] Fix memory allocation in preferences load() method (#10506) 2025-09-03 14:26:43 +12:00
J. Nick Koston
d2b23ba3a7 [sensor] Change state_class_to_string() to return const char* to avoid allocations (#10533) 2025-09-03 14:24:16 +12:00
J. Nick Koston
83fbd77c4a [core] Use get_icon_ref() in entity platform logging to avoid string allocations (#10530) 2025-09-03 14:23:46 +12:00
J. Nick Koston
1a054299d4 [core] Optimize fnv1_hash to avoid string allocations for static entities (#10529) 2025-09-02 21:17:14 -05:00
Jonathan Swoboda
e3fb9c2a78 [esp32] Remove hardcoding of ulp (#10535) 2025-09-02 23:51:17 +00:00
J. Nick Koston
d1276dc6df [core] Replace magic coroutine priority numbers with self-documenting CoroPriority enum (#10518) 2025-09-02 21:41:50 +00:00
Eyal
f286bc57f3 [core] Fix timezone offset calculation (#10426) 2025-09-02 16:45:25 +12:00
Clyde Stubbs
ed48282d09 [mcp4461] Fix read transaction (#10465)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-09-02 08:53:03 +12:00
Clyde Stubbs
2ddd8c72d6 [mipi_dsi] Fix config for Guition screen (#10464) 2025-09-02 08:51:31 +12:00
Mischa Siekmann
d0b4bc48e4 [wifi] Guard wifi error cases introduced in IDF5.2 by a version check (#10466) 2025-09-02 08:51:03 +12:00
tomaszduda23
77dbe77117 [nrf52] fix missing bootloader (#10519) 2025-09-01 12:30:02 -05:00
J. Nick Koston
6daeffcefd [bluetooth_proxy] Expose configured scanning mode in API responses (#10490) 2025-09-01 13:07:29 +12:00
J. Nick Koston
6d834c019d Fix incorrect entity count due to undefined execution order with globals (#10497) 2025-09-01 13:01:15 +12:00
tomaszduda23
905e2906fe [nrf52] add dfu (#9319)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-09-01 10:54:35 +12:00
Jesse Hills
a25b544c3b [display] Allow page actions to have auto generated display id (#10460) 2025-09-01 09:22:11 +12:00
Felix Kaechele
da21174c6d [sntp] Use callbacks to trigger on_time_sync for ESP32 and ESP8266 (#10390)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-09-01 09:02:56 +12:00
DT-art1
e29f0ee7f8 Add JPEG encoder support via new camera_encoder component (#9459)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: J. Nick Koston <nick+github@koston.org>
2025-08-31 09:26:15 -05:00
Jesse Hills
983b3cb879 [mipi] Add type to models for better type hinting downstream (#10475) 2025-08-30 16:43:26 +10:00
dependabot[bot]
fd568d9af3 Bump aioesphomeapi from 39.0.0 to 39.0.1 (#10491)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-29 10:14:15 -05:00
Clyde Stubbs
ca72286386 [lvgl] Update hello world (#10469) 2025-08-29 15:42:39 +10:00
Ben Curtis
dea68bebd8 Adjust sen5x to match VOC/NOX datasheet (#9894)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-08-29 14:00:54 +12:00
Clyde Stubbs
ef98f67b41 [lvgl] Replace spinbox step with selected_digit (#10349)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-08-29 13:58:58 +12:00
Oliver Kleinecke
6a92b691a0 Update mcp4461.cpp (#10479) 2025-08-29 12:53:54 +12:00
Clyde Stubbs
bc960cf6d2 [mapping] Use custom allocator (#9972) 2025-08-29 12:52:37 +12:00
Jesse Hills
461ce69296 Merge branch 'release' into dev 2025-08-29 12:39:26 +12:00
Jesse Hills
6a20e6f9ad Merge pull request #10485 from esphome/bump-2025.8.2
2025.8.2
2025-08-29 12:38:45 +12:00
dependabot[bot]
cde00a1f4c Bump esphome-dashboard from 20250814.0 to 20250828.0 (#10484)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-29 11:02:15 +12:00
J. Nick Koston
5dc691874b [bluetooth_proxy] Remove unused ClientState::SEARCHING state (#10318) 2025-08-29 10:30:14 +12:00
dependabot[bot]
c526ab9a3f Bump ruff from 0.12.10 to 0.12.11 (#10483)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2025-08-28 22:20:23 +00:00
Jesse Hills
07875a8b1e Bump version to 2025.8.2 2025-08-29 10:16:19 +12:00
J. Nick Koston
ba4789970c [esphome] Fix OTA watchdog resets by validating all magic bytes before blocking (#10401) 2025-08-29 10:16:19 +12:00
Vinicius Fortuna
015977cfdf [rtttl] Fix RTTTL for speakers (#10381) 2025-08-29 10:16:19 +12:00
J. Nick Koston
e513c0f004 Fix AttributeError when uploading OTA to offline OpenThread devices (#10459) 2025-08-29 10:16:19 +12:00
Clyde Stubbs
a11970aee0 [wifi] Fix retry with hidden networks. (#10445) 2025-08-29 10:16:19 +12:00
Clyde Stubbs
4ab37b069b [i2c] Perform register reads as single transactions (#10389)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-08-29 10:16:08 +12:00
Clyde Stubbs
b6bb6699d1 [mipi_spi] Fix dimensions (#10443) 2025-08-29 10:15:30 +12:00
J. Nick Koston
078eaff9a8 [wifi] Fix reconnection failures after adapter restart by not clearing netif pointers (#10458) 2025-08-29 10:15:30 +12:00
J. Nick Koston
a7786b75a0 [esp32_ble_tracker] Remove duplicate client promotion logic (#10321) 2025-08-29 10:14:51 +12:00
J. Nick Koston
d4c11dac8c [esphome] Fix OTA watchdog resets by validating all magic bytes before blocking (#10401) 2025-08-29 10:12:38 +12:00
DAVe3283
2f2f2f7d15 [absolute_humidity] Fix typo (#10474) 2025-08-29 10:04:19 +12:00
712 changed files with 28622 additions and 8313 deletions

View File

@@ -1 +1 @@
4368db58e8f884aff245996b1e8b644cc0796c0bb2fa706d5740d40b823d3ac9 499db61c1aa55b98b6629df603a56a1ba7aff5a9a7c781a5c1552a9dcd186c08

View File

@@ -47,7 +47,7 @@ runs:
- name: Build and push to ghcr by digest - name: Build and push to ghcr by digest
id: build-ghcr id: build-ghcr
uses: docker/build-push-action@v6.18.0 uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
env: env:
DOCKER_BUILD_SUMMARY: false DOCKER_BUILD_SUMMARY: false
DOCKER_BUILD_RECORD_UPLOAD: false DOCKER_BUILD_RECORD_UPLOAD: false
@@ -73,7 +73,7 @@ runs:
- name: Build and push to dockerhub by digest - name: Build and push to dockerhub by digest
id: build-dockerhub id: build-dockerhub
uses: docker/build-push-action@v6.18.0 uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
env: env:
DOCKER_BUILD_SUMMARY: false DOCKER_BUILD_SUMMARY: false
DOCKER_BUILD_RECORD_UPLOAD: false DOCKER_BUILD_RECORD_UPLOAD: false

View File

@@ -17,12 +17,12 @@ runs:
steps: steps:
- name: Set up Python ${{ inputs.python-version }} - name: Set up Python ${{ inputs.python-version }}
id: python id: python
uses: actions/setup-python@v5.6.0 uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with: with:
python-version: ${{ inputs.python-version }} python-version: ${{ inputs.python-version }}
- name: Restore Python virtual environment - name: Restore Python virtual environment
id: cache-venv id: cache-venv
uses: actions/cache/restore@v4.2.4 uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with: with:
path: venv path: venv
# yamllint disable-line rule:line-length # yamllint disable-line rule:line-length

View File

@@ -22,17 +22,17 @@ jobs:
if: github.event.action != 'labeled' || github.event.sender.type != 'Bot' if: github.event.action != 'labeled' || github.event.sender.type != 'Bot'
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v5.0.0 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Generate a token - name: Generate a token
id: generate-token id: generate-token
uses: actions/create-github-app-token@v2 uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2
with: with:
app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }} app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }}
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }} private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
- name: Auto Label PR - name: Auto Label PR
uses: actions/github-script@v7.0.1 uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with: with:
github-token: ${{ steps.generate-token.outputs.token }} github-token: ${{ steps.generate-token.outputs.token }}
script: | script: |

View File

@@ -21,9 +21,9 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v5.0.0 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v5.6.0 uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with: with:
python-version: "3.11" python-version: "3.11"
@@ -47,7 +47,7 @@ jobs:
fi fi
- if: failure() - if: failure()
name: Review PR name: Review PR
uses: actions/github-script@v7.0.1 uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with: with:
script: | script: |
await github.rest.pulls.createReview({ await github.rest.pulls.createReview({
@@ -62,7 +62,7 @@ jobs:
run: git diff run: git diff
- if: failure() - if: failure()
name: Archive artifacts name: Archive artifacts
uses: actions/upload-artifact@v4.6.2 uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with: with:
name: generated-proto-files name: generated-proto-files
path: | path: |
@@ -70,7 +70,7 @@ jobs:
esphome/components/api/api_pb2_service.* esphome/components/api/api_pb2_service.*
- if: success() - if: success()
name: Dismiss review name: Dismiss review
uses: actions/github-script@v7.0.1 uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with: with:
script: | script: |
let reviews = await github.rest.pulls.listReviews({ let reviews = await github.rest.pulls.listReviews({

View File

@@ -20,10 +20,10 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v5.0.0 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v5.6.0 uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with: with:
python-version: "3.11" python-version: "3.11"
@@ -41,7 +41,7 @@ jobs:
- if: failure() - if: failure()
name: Request changes name: Request changes
uses: actions/github-script@v7.0.1 uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with: with:
script: | script: |
await github.rest.pulls.createReview({ await github.rest.pulls.createReview({
@@ -54,7 +54,7 @@ jobs:
- if: success() - if: success()
name: Dismiss review name: Dismiss review
uses: actions/github-script@v7.0.1 uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with: with:
script: | script: |
let reviews = await github.rest.pulls.listReviews({ let reviews = await github.rest.pulls.listReviews({

View File

@@ -43,13 +43,13 @@ jobs:
- "docker" - "docker"
# - "lint" # - "lint"
steps: steps:
- uses: actions/checkout@v5.0.0 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v5.6.0 uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with: with:
python-version: "3.11" python-version: "3.11"
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.11.1 uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- name: Set TAG - name: Set TAG
run: | run: |

View File

@@ -36,18 +36,18 @@ jobs:
cache-key: ${{ steps.cache-key.outputs.key }} cache-key: ${{ steps.cache-key.outputs.key }}
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v5.0.0 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Generate cache-key - name: Generate cache-key
id: cache-key id: cache-key
run: echo key="${{ hashFiles('requirements.txt', 'requirements_test.txt', '.pre-commit-config.yaml') }}" >> $GITHUB_OUTPUT run: echo key="${{ hashFiles('requirements.txt', 'requirements_test.txt', '.pre-commit-config.yaml') }}" >> $GITHUB_OUTPUT
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python id: python
uses: actions/setup-python@v5.6.0 uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore Python virtual environment - name: Restore Python virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v4.2.4 uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with: with:
path: venv path: venv
# yamllint disable-line rule:line-length # yamllint disable-line rule:line-length
@@ -70,7 +70,7 @@ jobs:
if: needs.determine-jobs.outputs.python-linters == 'true' if: needs.determine-jobs.outputs.python-linters == 'true'
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v5.0.0 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Restore Python - name: Restore Python
uses: ./.github/actions/restore-python uses: ./.github/actions/restore-python
with: with:
@@ -91,7 +91,7 @@ jobs:
- common - common
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v5.0.0 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Restore Python - name: Restore Python
uses: ./.github/actions/restore-python uses: ./.github/actions/restore-python
with: with:
@@ -105,6 +105,7 @@ jobs:
script/ci-custom.py script/ci-custom.py
script/build_codeowners.py --check script/build_codeowners.py --check
script/build_language_schema.py --check script/build_language_schema.py --check
script/generate-esp32-boards.py --check
pytest: pytest:
name: Run pytest name: Run pytest
@@ -136,7 +137,7 @@ jobs:
- common - common
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v5.0.0 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Restore Python - name: Restore Python
id: restore-python id: restore-python
uses: ./.github/actions/restore-python uses: ./.github/actions/restore-python
@@ -156,12 +157,12 @@ jobs:
. venv/bin/activate . venv/bin/activate
pytest -vv --cov-report=xml --tb=native -n auto tests --ignore=tests/integration/ pytest -vv --cov-report=xml --tb=native -n auto tests --ignore=tests/integration/
- name: Upload coverage to Codecov - name: Upload coverage to Codecov
uses: codecov/codecov-action@v5.5.0 uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1
with: with:
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}
- name: Save Python virtual environment cache - name: Save Python virtual environment cache
if: github.ref == 'refs/heads/dev' if: github.ref == 'refs/heads/dev'
uses: actions/cache/save@v4.2.4 uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with: with:
path: venv path: venv
key: ${{ runner.os }}-${{ steps.restore-python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }} key: ${{ runner.os }}-${{ steps.restore-python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
@@ -179,7 +180,7 @@ jobs:
component-test-count: ${{ steps.determine.outputs.component-test-count }} component-test-count: ${{ steps.determine.outputs.component-test-count }}
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v5.0.0 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
# Fetch enough history to find the merge base # Fetch enough history to find the merge base
fetch-depth: 2 fetch-depth: 2
@@ -214,15 +215,15 @@ jobs:
if: needs.determine-jobs.outputs.integration-tests == 'true' if: needs.determine-jobs.outputs.integration-tests == 'true'
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v5.0.0 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Set up Python 3.13 - name: Set up Python 3.13
id: python id: python
uses: actions/setup-python@v5.6.0 uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with: with:
python-version: "3.13" python-version: "3.13"
- name: Restore Python virtual environment - name: Restore Python virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v4.2.4 uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with: with:
path: venv path: venv
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }} key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
@@ -287,7 +288,7 @@ jobs:
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v5.0.0 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
# Need history for HEAD~1 to work for checking changed files # Need history for HEAD~1 to work for checking changed files
fetch-depth: 2 fetch-depth: 2
@@ -300,14 +301,14 @@ jobs:
- name: Cache platformio - name: Cache platformio
if: github.ref == 'refs/heads/dev' if: github.ref == 'refs/heads/dev'
uses: actions/cache@v4.2.4 uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with: with:
path: ~/.platformio path: ~/.platformio
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }} key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
- name: Cache platformio - name: Cache platformio
if: github.ref != 'refs/heads/dev' if: github.ref != 'refs/heads/dev'
uses: actions/cache/restore@v4.2.4 uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with: with:
path: ~/.platformio path: ~/.platformio
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }} key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
@@ -374,7 +375,7 @@ jobs:
sudo apt-get install libsdl2-dev sudo apt-get install libsdl2-dev
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v5.0.0 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Restore Python - name: Restore Python
uses: ./.github/actions/restore-python uses: ./.github/actions/restore-python
with: with:
@@ -400,7 +401,7 @@ jobs:
matrix: ${{ steps.split.outputs.components }} matrix: ${{ steps.split.outputs.components }}
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v5.0.0 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Split components into 20 groups - name: Split components into 20 groups
id: split id: split
run: | run: |
@@ -430,7 +431,7 @@ jobs:
sudo apt-get install libsdl2-dev sudo apt-get install libsdl2-dev
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v5.0.0 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Restore Python - name: Restore Python
uses: ./.github/actions/restore-python uses: ./.github/actions/restore-python
with: with:
@@ -459,16 +460,16 @@ jobs:
if: github.event_name == 'pull_request' && github.base_ref != 'beta' && github.base_ref != 'release' if: github.event_name == 'pull_request' && github.base_ref != 'beta' && github.base_ref != 'release'
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v5.0.0 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Restore Python - name: Restore Python
uses: ./.github/actions/restore-python uses: ./.github/actions/restore-python
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }} cache-key: ${{ needs.common.outputs.cache-key }}
- uses: pre-commit/action@v3.0.1 - uses: esphome/action@43cd1109c09c544d97196f7730ee5b2e0cc6d81e # v3.0.1 fork with pinned actions/cache
env: env:
SKIP: pylint,clang-tidy-hash SKIP: pylint,clang-tidy-hash
- uses: pre-commit-ci/lite-action@v1.1.0 - uses: pre-commit-ci/lite-action@5d6cc0eb514c891a40562a58a8e71576c5c7fb43 # v1.1.0
if: always() if: always()
ci-status: ci-status:

View File

@@ -25,7 +25,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Request reviews from component codeowners - name: Request reviews from component codeowners
uses: actions/github-script@v7.0.1 uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with: with:
script: | script: |
const owner = context.repo.owner; const owner = context.repo.owner;

View File

@@ -54,11 +54,11 @@ 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 # 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: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v5.0.0 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v3 uses: github/codeql-action/init@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3.30.6
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }} build-mode: ${{ matrix.build-mode }}
@@ -86,6 +86,6 @@ jobs:
exit 1 exit 1
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3 uses: github/codeql-action/analyze@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3.30.6
with: with:
category: "/language:${{matrix.language}}" category: "/language:${{matrix.language}}"

View File

@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Add external component comment - name: Add external component comment
uses: actions/github-script@v7.0.1 uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with: with:
github-token: ${{ secrets.GITHUB_TOKEN }} github-token: ${{ secrets.GITHUB_TOKEN }}
script: | script: |

View File

@@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Notify codeowners for component issues - name: Notify codeowners for component issues
uses: actions/github-script@v7.0.1 uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with: with:
script: | script: |
const owner = context.repo.owner; const owner = context.repo.owner;

View File

@@ -20,7 +20,7 @@ jobs:
branch_build: ${{ steps.tag.outputs.branch_build }} branch_build: ${{ steps.tag.outputs.branch_build }}
deploy_env: ${{ steps.tag.outputs.deploy_env }} deploy_env: ${{ steps.tag.outputs.deploy_env }}
steps: steps:
- uses: actions/checkout@v5.0.0 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Get tag - name: Get tag
id: tag id: tag
# yamllint disable rule:line-length # yamllint disable rule:line-length
@@ -60,9 +60,9 @@ jobs:
contents: read contents: read
id-token: write id-token: write
steps: steps:
- uses: actions/checkout@v5.0.0 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v5.6.0 uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with: with:
python-version: "3.x" python-version: "3.x"
- name: Build - name: Build
@@ -70,7 +70,7 @@ jobs:
pip3 install build pip3 install build
python3 -m build python3 -m build
- name: Publish - name: Publish
uses: pypa/gh-action-pypi-publish@v1.12.4 uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
with: with:
skip-existing: true skip-existing: true
@@ -92,22 +92,22 @@ jobs:
os: "ubuntu-24.04-arm" os: "ubuntu-24.04-arm"
steps: steps:
- uses: actions/checkout@v5.0.0 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v5.6.0 uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with: with:
python-version: "3.11" python-version: "3.11"
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.11.1 uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- name: Log in to docker hub - name: Log in to docker hub
uses: docker/login-action@v3.5.0 uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
with: with:
username: ${{ secrets.DOCKER_USER }} username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
- name: Log in to the GitHub container registry - name: Log in to the GitHub container registry
uses: docker/login-action@v3.5.0 uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
@@ -138,7 +138,7 @@ jobs:
# version: ${{ needs.init.outputs.tag }} # version: ${{ needs.init.outputs.tag }}
- name: Upload digests - name: Upload digests
uses: actions/upload-artifact@v4.6.2 uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with: with:
name: digests-${{ matrix.platform.arch }} name: digests-${{ matrix.platform.arch }}
path: /tmp/digests path: /tmp/digests
@@ -168,27 +168,27 @@ jobs:
- ghcr - ghcr
- dockerhub - dockerhub
steps: steps:
- uses: actions/checkout@v5.0.0 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Download digests - name: Download digests
uses: actions/download-artifact@v5.0.0 uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with: with:
pattern: digests-* pattern: digests-*
path: /tmp/digests path: /tmp/digests
merge-multiple: true merge-multiple: true
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.11.1 uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- name: Log in to docker hub - name: Log in to docker hub
if: matrix.registry == 'dockerhub' if: matrix.registry == 'dockerhub'
uses: docker/login-action@v3.5.0 uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
with: with:
username: ${{ secrets.DOCKER_USER }} username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
- name: Log in to the GitHub container registry - name: Log in to the GitHub container registry
if: matrix.registry == 'ghcr' if: matrix.registry == 'ghcr'
uses: docker/login-action@v3.5.0 uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
@@ -220,7 +220,7 @@ jobs:
- deploy-manifest - deploy-manifest
steps: steps:
- name: Trigger Workflow - name: Trigger Workflow
uses: actions/github-script@v7.0.1 uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with: with:
github-token: ${{ secrets.DEPLOY_HA_ADDON_REPO_TOKEN }} github-token: ${{ secrets.DEPLOY_HA_ADDON_REPO_TOKEN }}
script: | script: |
@@ -246,7 +246,7 @@ jobs:
environment: ${{ needs.init.outputs.deploy_env }} environment: ${{ needs.init.outputs.deploy_env }}
steps: steps:
- name: Trigger Workflow - name: Trigger Workflow
uses: actions/github-script@v7.0.1 uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with: with:
github-token: ${{ secrets.DEPLOY_ESPHOME_SCHEMA_REPO_TOKEN }} github-token: ${{ secrets.DEPLOY_ESPHOME_SCHEMA_REPO_TOKEN }}
script: | script: |

View File

@@ -15,36 +15,52 @@ concurrency:
jobs: jobs:
stale: stale:
if: github.repository_owner == 'esphome'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/stale@v9.1.0 - name: Stale
uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0
with: with:
debug-only: ${{ github.ref != 'refs/heads/dev' }} # Dry-run when not run on dev branch
remove-stale-when-updated: true
operations-per-run: 150
# The 90 day stale policy for PRs
# - PRs
# - No PRs marked as "not-stale"
# - No Issues (see below)
days-before-pr-stale: 90 days-before-pr-stale: 90
days-before-pr-close: 7 days-before-pr-close: 7
days-before-issue-stale: -1
days-before-issue-close: -1
remove-stale-when-updated: true
stale-pr-label: "stale" stale-pr-label: "stale"
exempt-pr-labels: "not-stale" exempt-pr-labels: "not-stale"
stale-pr-message: > stale-pr-message: >
There hasn't been any activity on this pull request recently. This There hasn't been any activity on this pull request recently. This
pull request has been automatically marked as stale because of that pull request has been automatically marked as stale because of that
and will be closed if no further activity occurs within 7 days. and will be closed if no further activity occurs within 7 days.
Thank you for your contributions.
# Use stale to automatically close issues with a If you are the author of this PR, please leave a comment if you want
# reference to the issue tracker to keep it open. Also, please rebase your PR onto the latest dev
close-issues: branch to ensure that it's up to date with the latest changes.
runs-on: ubuntu-latest
steps: Thank you for your contribution!
- uses: actions/stale@v9.1.0
with: # The 90 day stale policy for Issues
days-before-pr-stale: -1 # - Issues
days-before-pr-close: -1 # - No Issues marked as "not-stale"
days-before-issue-stale: 1 # - No PRs (see above)
days-before-issue-close: 1 days-before-issue-stale: 90
remove-stale-when-updated: true days-before-issue-close: 7
stale-issue-label: "stale" stale-issue-label: "stale"
exempt-issue-labels: "not-stale" exempt-issue-labels: "not-stale"
stale-issue-message: > stale-issue-message: >
https://github.com/esphome/esphome/issues/430 There hasn't been any activity on this issue recently. Due to the
high number of incoming GitHub notifications, we have to clean some
of the old issues, as many of them have already been resolved with
the latest updates.
Please make sure to update to the latest ESPHome version and
check if that solves the issue. Let us know if that works for you by
adding a comment 👍
This issue has now been marked as stale and will be closed if no
further activity occurs. Thank you for your contributions.

View File

@@ -16,7 +16,7 @@ jobs:
- merge-after-release - merge-after-release
steps: steps:
- name: Check for ${{ matrix.label }} label - name: Check for ${{ matrix.label }} label
uses: actions/github-script@v7.0.1 uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with: with:
script: | script: |
const { data: labels } = await github.rest.issues.listLabelsOnIssue({ const { data: labels } = await github.rest.issues.listLabelsOnIssue({

View File

@@ -13,16 +13,16 @@ jobs:
if: github.repository == 'esphome/esphome' if: github.repository == 'esphome/esphome'
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v5.0.0 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Checkout Home Assistant - name: Checkout Home Assistant
uses: actions/checkout@v5.0.0 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
repository: home-assistant/core repository: home-assistant/core
path: lib/home-assistant path: lib/home-assistant
- name: Setup Python - name: Setup Python
uses: actions/setup-python@v5.6.0 uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with: with:
python-version: 3.13 python-version: 3.13
@@ -30,13 +30,18 @@ jobs:
run: | run: |
python -m pip install --upgrade pip python -m pip install --upgrade pip
pip install -e lib/home-assistant pip install -e lib/home-assistant
pip install -r requirements_test.txt pre-commit
- name: Sync - name: Sync
run: | run: |
python ./script/sync-device_class.py python ./script/sync-device_class.py
- name: Run pre-commit hooks
run: |
python script/run-in-env.py pre-commit run --all-files
- name: Commit changes - name: Commit changes
uses: peter-evans/create-pull-request@v7.0.8 uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
with: with:
commit-message: "Synchronise Device Classes from Home Assistant" commit-message: "Synchronise Device Classes from Home Assistant"
committer: esphomebot <esphome@openhomefoundation.org> committer: esphomebot <esphome@openhomefoundation.org>

View File

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

View File

@@ -66,7 +66,7 @@ esphome/components/binary_sensor/* @esphome/core
esphome/components/bk72xx/* @kuba2k2 esphome/components/bk72xx/* @kuba2k2
esphome/components/bl0906/* @athom-tech @jesserockz @tarontop esphome/components/bl0906/* @athom-tech @jesserockz @tarontop
esphome/components/bl0939/* @ziceva esphome/components/bl0939/* @ziceva
esphome/components/bl0940/* @tobias- esphome/components/bl0940/* @dan-s-github @tobias-
esphome/components/bl0942/* @dbuezas @dwmw2 esphome/components/bl0942/* @dbuezas @dwmw2
esphome/components/ble_client/* @buxtronix @clydebarrow esphome/components/ble_client/* @buxtronix @clydebarrow
esphome/components/bluetooth_proxy/* @bdraco @jesserockz esphome/components/bluetooth_proxy/* @bdraco @jesserockz
@@ -88,7 +88,8 @@ esphome/components/bp1658cj/* @Cossid
esphome/components/bp5758d/* @Cossid esphome/components/bp5758d/* @Cossid
esphome/components/button/* @esphome/core esphome/components/button/* @esphome/core
esphome/components/bytebuffer/* @clydebarrow esphome/components/bytebuffer/* @clydebarrow
esphome/components/camera/* @DT-art1 @bdraco esphome/components/camera/* @bdraco @DT-art1
esphome/components/camera_encoder/* @DT-art1
esphome/components/canbus/* @danielschramm @mvturnho esphome/components/canbus/* @danielschramm @mvturnho
esphome/components/cap1188/* @mreditor97 esphome/components/cap1188/* @mreditor97
esphome/components/captive_portal/* @esphome/core esphome/components/captive_portal/* @esphome/core
@@ -144,9 +145,9 @@ esphome/components/es8156/* @kbx81
esphome/components/es8311/* @kahrendt @kroimon esphome/components/es8311/* @kahrendt @kroimon
esphome/components/es8388/* @P4uLT esphome/components/es8388/* @P4uLT
esphome/components/esp32/* @esphome/core esphome/components/esp32/* @esphome/core
esphome/components/esp32_ble/* @Rapsssito @bdraco @jesserockz esphome/components/esp32_ble/* @bdraco @jesserockz @Rapsssito
esphome/components/esp32_ble_client/* @bdraco @jesserockz esphome/components/esp32_ble_client/* @bdraco @jesserockz
esphome/components/esp32_ble_server/* @Rapsssito @clydebarrow @jesserockz esphome/components/esp32_ble_server/* @clydebarrow @jesserockz @Rapsssito
esphome/components/esp32_ble_tracker/* @bdraco esphome/components/esp32_ble_tracker/* @bdraco
esphome/components/esp32_camera_web_server/* @ayufan esphome/components/esp32_camera_web_server/* @ayufan
esphome/components/esp32_can/* @Sympatron esphome/components/esp32_can/* @Sympatron
@@ -159,14 +160,13 @@ esphome/components/esp_ldo/* @clydebarrow
esphome/components/espnow/* @jesserockz esphome/components/espnow/* @jesserockz
esphome/components/ethernet_info/* @gtjadsonsantos esphome/components/ethernet_info/* @gtjadsonsantos
esphome/components/event/* @nohat esphome/components/event/* @nohat
esphome/components/event_emitter/* @Rapsssito
esphome/components/exposure_notifications/* @OttoWinter esphome/components/exposure_notifications/* @OttoWinter
esphome/components/ezo/* @ssieb esphome/components/ezo/* @ssieb
esphome/components/ezo_pmp/* @carlos-sarmiento esphome/components/ezo_pmp/* @carlos-sarmiento
esphome/components/factory_reset/* @anatoly-savchenkov esphome/components/factory_reset/* @anatoly-savchenkov
esphome/components/fastled_base/* @OttoWinter esphome/components/fastled_base/* @OttoWinter
esphome/components/feedback/* @ianchi esphome/components/feedback/* @ianchi
esphome/components/fingerprint_grow/* @OnFreund @alexborro @loongyh esphome/components/fingerprint_grow/* @alexborro @loongyh @OnFreund
esphome/components/font/* @clydebarrow @esphome/core esphome/components/font/* @clydebarrow @esphome/core
esphome/components/fs3000/* @kahrendt esphome/components/fs3000/* @kahrendt
esphome/components/ft5x06/* @clydebarrow esphome/components/ft5x06/* @clydebarrow
@@ -202,7 +202,7 @@ esphome/components/heatpumpir/* @rob-deutsch
esphome/components/hitachi_ac424/* @sourabhjaiswal esphome/components/hitachi_ac424/* @sourabhjaiswal
esphome/components/hm3301/* @freekode esphome/components/hm3301/* @freekode
esphome/components/hmac_md5/* @dwmw2 esphome/components/hmac_md5/* @dwmw2
esphome/components/homeassistant/* @OttoWinter @esphome/core esphome/components/homeassistant/* @esphome/core @OttoWinter
esphome/components/homeassistant/number/* @landonr esphome/components/homeassistant/number/* @landonr
esphome/components/homeassistant/switch/* @Links2004 esphome/components/homeassistant/switch/* @Links2004
esphome/components/honeywell_hih_i2c/* @Benichou34 esphome/components/honeywell_hih_i2c/* @Benichou34
@@ -227,13 +227,13 @@ esphome/components/iaqcore/* @yozik04
esphome/components/ili9xxx/* @clydebarrow @nielsnl68 esphome/components/ili9xxx/* @clydebarrow @nielsnl68
esphome/components/improv_base/* @esphome/core esphome/components/improv_base/* @esphome/core
esphome/components/improv_serial/* @esphome/core esphome/components/improv_serial/* @esphome/core
esphome/components/ina226/* @Sergio303 @latonita esphome/components/ina226/* @latonita @Sergio303
esphome/components/ina260/* @mreditor97 esphome/components/ina260/* @mreditor97
esphome/components/ina2xx_base/* @latonita esphome/components/ina2xx_base/* @latonita
esphome/components/ina2xx_i2c/* @latonita esphome/components/ina2xx_i2c/* @latonita
esphome/components/ina2xx_spi/* @latonita esphome/components/ina2xx_spi/* @latonita
esphome/components/inkbird_ibsth1_mini/* @fkirill esphome/components/inkbird_ibsth1_mini/* @fkirill
esphome/components/inkplate6/* @jesserockz esphome/components/inkplate/* @jesserockz @JosipKuci
esphome/components/integration/* @OttoWinter esphome/components/integration/* @OttoWinter
esphome/components/internal_temperature/* @Mat931 esphome/components/internal_temperature/* @Mat931
esphome/components/interval/* @esphome/core esphome/components/interval/* @esphome/core
@@ -276,8 +276,8 @@ esphome/components/max7219digit/* @rspaargaren
esphome/components/max9611/* @mckaymatthew esphome/components/max9611/* @mckaymatthew
esphome/components/mcp23008/* @jesserockz esphome/components/mcp23008/* @jesserockz
esphome/components/mcp23017/* @jesserockz esphome/components/mcp23017/* @jesserockz
esphome/components/mcp23s08/* @SenexCrenshaw @jesserockz esphome/components/mcp23s08/* @jesserockz @SenexCrenshaw
esphome/components/mcp23s17/* @SenexCrenshaw @jesserockz esphome/components/mcp23s17/* @jesserockz @SenexCrenshaw
esphome/components/mcp23x08_base/* @jesserockz esphome/components/mcp23x08_base/* @jesserockz
esphome/components/mcp23x17_base/* @jesserockz esphome/components/mcp23x17_base/* @jesserockz
esphome/components/mcp23xxx_base/* @jesserockz esphome/components/mcp23xxx_base/* @jesserockz
@@ -298,6 +298,7 @@ esphome/components/mics_4514/* @jesserockz
esphome/components/midea/* @dudanov esphome/components/midea/* @dudanov
esphome/components/midea_ir/* @dudanov esphome/components/midea_ir/* @dudanov
esphome/components/mipi_dsi/* @clydebarrow esphome/components/mipi_dsi/* @clydebarrow
esphome/components/mipi_rgb/* @clydebarrow
esphome/components/mipi_spi/* @clydebarrow esphome/components/mipi_spi/* @clydebarrow
esphome/components/mitsubishi/* @RubyBailey esphome/components/mitsubishi/* @RubyBailey
esphome/components/mixer/speaker/* @kahrendt esphome/components/mixer/speaker/* @kahrendt
@@ -341,7 +342,7 @@ esphome/components/ota/* @esphome/core
esphome/components/output/* @esphome/core esphome/components/output/* @esphome/core
esphome/components/packet_transport/* @clydebarrow esphome/components/packet_transport/* @clydebarrow
esphome/components/pca6416a/* @Mat931 esphome/components/pca6416a/* @Mat931
esphome/components/pca9554/* @clydebarrow @hwstar esphome/components/pca9554/* @bdraco @clydebarrow @hwstar
esphome/components/pcf85063/* @brogon esphome/components/pcf85063/* @brogon
esphome/components/pcf8563/* @KoenBreeman esphome/components/pcf8563/* @KoenBreeman
esphome/components/pi4ioe5v6408/* @jesserockz esphome/components/pi4ioe5v6408/* @jesserockz
@@ -352,9 +353,9 @@ esphome/components/pm2005/* @andrewjswan
esphome/components/pmsa003i/* @sjtrny esphome/components/pmsa003i/* @sjtrny
esphome/components/pmsx003/* @ximex esphome/components/pmsx003/* @ximex
esphome/components/pmwcs3/* @SeByDocKy esphome/components/pmwcs3/* @SeByDocKy
esphome/components/pn532/* @OttoWinter @jesserockz esphome/components/pn532/* @jesserockz @OttoWinter
esphome/components/pn532_i2c/* @OttoWinter @jesserockz esphome/components/pn532_i2c/* @jesserockz @OttoWinter
esphome/components/pn532_spi/* @OttoWinter @jesserockz esphome/components/pn532_spi/* @jesserockz @OttoWinter
esphome/components/pn7150/* @jesserockz @kbx81 esphome/components/pn7150/* @jesserockz @kbx81
esphome/components/pn7150_i2c/* @jesserockz @kbx81 esphome/components/pn7150_i2c/* @jesserockz @kbx81
esphome/components/pn7160/* @jesserockz @kbx81 esphome/components/pn7160/* @jesserockz @kbx81
@@ -363,7 +364,7 @@ esphome/components/pn7160_spi/* @jesserockz @kbx81
esphome/components/power_supply/* @esphome/core esphome/components/power_supply/* @esphome/core
esphome/components/preferences/* @esphome/core esphome/components/preferences/* @esphome/core
esphome/components/psram/* @esphome/core esphome/components/psram/* @esphome/core
esphome/components/pulse_meter/* @TrentHouliston @cstaahl @stevebaxter esphome/components/pulse_meter/* @cstaahl @stevebaxter @TrentHouliston
esphome/components/pvvx_mithermometer/* @pasiz esphome/components/pvvx_mithermometer/* @pasiz
esphome/components/pylontech/* @functionpointer esphome/components/pylontech/* @functionpointer
esphome/components/qmp6988/* @andrewpc esphome/components/qmp6988/* @andrewpc
@@ -404,7 +405,8 @@ esphome/components/sensirion_common/* @martgras
esphome/components/sensor/* @esphome/core esphome/components/sensor/* @esphome/core
esphome/components/sfa30/* @ghsensdev esphome/components/sfa30/* @ghsensdev
esphome/components/sgp40/* @SenexCrenshaw esphome/components/sgp40/* @SenexCrenshaw
esphome/components/sgp4x/* @SenexCrenshaw @martgras esphome/components/sgp4x/* @martgras @SenexCrenshaw
esphome/components/sha256/* @esphome/core
esphome/components/shelly_dimmer/* @edge90 @rnauber esphome/components/shelly_dimmer/* @edge90 @rnauber
esphome/components/sht3xd/* @mrtoy-me esphome/components/sht3xd/* @mrtoy-me
esphome/components/sht4x/* @sjtrny esphome/components/sht4x/* @sjtrny
@@ -531,6 +533,7 @@ esphome/components/wk2204_spi/* @DrCoolZic
esphome/components/wk2212_i2c/* @DrCoolZic esphome/components/wk2212_i2c/* @DrCoolZic
esphome/components/wk2212_spi/* @DrCoolZic esphome/components/wk2212_spi/* @DrCoolZic
esphome/components/wl_134/* @hobbypunk90 esphome/components/wl_134/* @hobbypunk90
esphome/components/wts01/* @alepee
esphome/components/x9c/* @EtienneMD esphome/components/x9c/* @EtienneMD
esphome/components/xgzp68xx/* @gcormier esphome/components/xgzp68xx/* @gcormier
esphome/components/xiaomi_hhccjcy10/* @fariouche esphome/components/xiaomi_hhccjcy10/* @fariouche
@@ -546,3 +549,4 @@ esphome/components/xxtea/* @clydebarrow
esphome/components/zephyr/* @tomaszduda23 esphome/components/zephyr/* @tomaszduda23
esphome/components/zhlt01/* @cfeenstra1024 esphome/components/zhlt01/* @cfeenstra1024
esphome/components/zio_ultrasonic/* @kahrendt esphome/components/zio_ultrasonic/* @kahrendt
esphome/components/zwave_proxy/* @kbx81

View File

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

View File

@@ -6,6 +6,7 @@ import getpass
import importlib import importlib
import logging import logging
import os import os
from pathlib import Path
import re import re
import sys import sys
import time import time
@@ -13,11 +14,15 @@ from typing import Protocol
import argcomplete import argcomplete
# Note: Do not import modules from esphome.components here, as this would
# cause them to be loaded before external components are processed, resulting
# in the built-in version being used instead of the external component one.
from esphome import const, writer, yaml_util from esphome import const, writer, yaml_util
import esphome.codegen as cg import esphome.codegen as cg
from esphome.config import iter_component_configs, read_config, strip_default_ids from esphome.config import iter_component_configs, read_config, strip_default_ids
from esphome.const import ( from esphome.const import (
ALLOWED_NAME_CHARS, ALLOWED_NAME_CHARS,
CONF_API,
CONF_BAUD_RATE, CONF_BAUD_RATE,
CONF_BROKER, CONF_BROKER,
CONF_DEASSERT_RTS_DTR, CONF_DEASSERT_RTS_DTR,
@@ -43,6 +48,7 @@ from esphome.const import (
SECRETS_FILES, SECRETS_FILES,
) )
from esphome.core import CORE, EsphomeError, coroutine from esphome.core import CORE, EsphomeError, coroutine
from esphome.enum import StrEnum
from esphome.helpers import get_bool_env, indent, is_ip_address from esphome.helpers import get_bool_env, indent, is_ip_address
from esphome.log import AnsiFore, color, setup_log from esphome.log import AnsiFore, color, setup_log
from esphome.types import ConfigType from esphome.types import ConfigType
@@ -106,13 +112,23 @@ def choose_prompt(options, purpose: str = None):
return options[opt - 1][1] return options[opt - 1][1]
class Purpose(StrEnum):
UPLOADING = "uploading"
LOGGING = "logging"
def _resolve_with_cache(address: str, purpose: Purpose) -> list[str]:
"""Resolve an address using cache if available, otherwise return the address itself."""
if CORE.address_cache and (cached := CORE.address_cache.get_addresses(address)):
_LOGGER.debug("Using cached addresses for %s: %s", purpose.value, cached)
return cached
return [address]
def choose_upload_log_host( def choose_upload_log_host(
default: list[str] | str | None, default: list[str] | str | None,
check_default: str | None, check_default: str | None,
show_ota: bool, purpose: Purpose,
show_mqtt: bool,
show_api: bool,
purpose: str | None = None,
) -> list[str]: ) -> list[str]:
# Convert to list for uniform handling # Convert to list for uniform handling
defaults = [default] if isinstance(default, str) else default or [] defaults = [default] if isinstance(default, str) else default or []
@@ -132,13 +148,29 @@ def choose_upload_log_host(
] ]
resolved.append(choose_prompt(options, purpose=purpose)) resolved.append(choose_prompt(options, purpose=purpose))
elif device == "OTA": elif device == "OTA":
if CORE.address and ( # ensure IP adresses are used first
(show_ota and "ota" in CORE.config) if is_ip_address(CORE.address) and (
or (show_api and "api" in CORE.config) (purpose == Purpose.LOGGING and has_api())
or (purpose == Purpose.UPLOADING and has_ota())
): ):
resolved.append(CORE.address) resolved.extend(_resolve_with_cache(CORE.address, purpose))
elif show_mqtt and has_mqtt_logging():
resolved.append("MQTT") if purpose == Purpose.LOGGING:
if has_api() and has_mqtt_ip_lookup():
resolved.append("MQTTIP")
if has_mqtt_logging():
resolved.append("MQTT")
if has_api() and has_non_ip_address():
resolved.extend(_resolve_with_cache(CORE.address, purpose))
elif purpose == Purpose.UPLOADING:
if has_ota() and has_mqtt_ip_lookup():
resolved.append("MQTTIP")
if has_ota() and has_non_ip_address():
resolved.extend(_resolve_with_cache(CORE.address, purpose))
else: else:
resolved.append(device) resolved.append(device)
if not resolved: if not resolved:
@@ -149,39 +181,113 @@ def choose_upload_log_host(
options = [ options = [
(f"{port.path} ({port.description})", port.path) for port in get_serial_ports() (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 purpose == Purpose.LOGGING:
if show_mqtt and has_mqtt_logging(): if has_mqtt_logging():
mqtt_config = CORE.config[CONF_MQTT] mqtt_config = CORE.config[CONF_MQTT]
options.append((f"MQTT ({mqtt_config[CONF_BROKER]})", "MQTT")) options.append((f"MQTT ({mqtt_config[CONF_BROKER]})", "MQTT"))
if has_api():
if has_resolvable_address():
options.append((f"Over The Air ({CORE.address})", CORE.address))
if has_mqtt_ip_lookup():
options.append(("Over The Air (MQTT IP lookup)", "MQTTIP"))
elif purpose == Purpose.UPLOADING and has_ota():
if has_resolvable_address():
options.append((f"Over The Air ({CORE.address})", CORE.address))
if has_mqtt_ip_lookup():
options.append(("Over The Air (MQTT IP lookup)", "MQTTIP"))
if check_default is not None and check_default in [opt[1] for opt in options]: if check_default is not None and check_default in [opt[1] for opt in options]:
return [check_default] return [check_default]
return [choose_prompt(options, purpose=purpose)] return [choose_prompt(options, purpose=purpose)]
def mqtt_logging_enabled(mqtt_config): def has_mqtt_logging() -> bool:
"""Check if MQTT logging is available."""
if CONF_MQTT not in CORE.config:
return False
mqtt_config = CORE.config[CONF_MQTT]
# enabled by default
if CONF_LOG_TOPIC not in mqtt_config:
return True
log_topic = mqtt_config[CONF_LOG_TOPIC] log_topic = mqtt_config[CONF_LOG_TOPIC]
if log_topic is None: if log_topic is None:
return False return False
if CONF_TOPIC not in log_topic: if CONF_TOPIC not in log_topic:
return False return False
return log_topic.get(CONF_LEVEL, None) != "NONE" return log_topic.get(CONF_LEVEL, None) != "NONE"
def has_mqtt_logging() -> bool: def has_mqtt() -> bool:
"""Check if MQTT logging is available.""" """Check if MQTT is available."""
return (mqtt_config := CORE.config.get(CONF_MQTT)) and mqtt_logging_enabled( return CONF_MQTT in CORE.config
mqtt_config
)
def has_api() -> bool:
"""Check if API is available."""
return CONF_API in CORE.config
def has_ota() -> bool:
"""Check if OTA is available."""
return CONF_OTA in CORE.config
def has_mqtt_ip_lookup() -> bool:
"""Check if MQTT is available and IP lookup is supported."""
from esphome.components.mqtt import CONF_DISCOVER_IP
if CONF_MQTT not in CORE.config:
return False
# Default Enabled
if CONF_DISCOVER_IP not in CORE.config[CONF_MQTT]:
return True
return CORE.config[CONF_MQTT][CONF_DISCOVER_IP]
def has_mdns() -> bool:
"""Check if MDNS is available."""
return CONF_MDNS not in CORE.config or not CORE.config[CONF_MDNS][CONF_DISABLED]
def has_non_ip_address() -> bool:
"""Check if CORE.address is set and is not an IP address."""
return CORE.address is not None and not is_ip_address(CORE.address)
def has_ip_address() -> bool:
"""Check if CORE.address is a valid IP address."""
return CORE.address is not None and is_ip_address(CORE.address)
def has_resolvable_address() -> bool:
"""Check if CORE.address is resolvable (via mDNS or is an IP address)."""
return has_mdns() or has_ip_address()
def mqtt_get_ip(config: ConfigType, username: str, password: str, client_id: str):
from esphome import mqtt
return mqtt.get_esphome_device_ip(config, username, password, client_id)
_PORT_TO_PORT_TYPE = {
"MQTT": "MQTT",
"MQTTIP": "MQTTIP",
}
def get_port_type(port: str) -> str: def get_port_type(port: str) -> str:
if port.startswith("/") or port.startswith("COM"): if port.startswith("/") or port.startswith("COM"):
return "SERIAL" return "SERIAL"
if port == "MQTT": return _PORT_TO_PORT_TYPE.get(port, "NETWORK")
return "MQTT"
return "NETWORK"
def run_miniterm(config: ConfigType, port: str, args) -> int: def run_miniterm(config: ConfigType, port: str, args) -> int:
@@ -226,7 +332,9 @@ def run_miniterm(config: ConfigType, port: str, args) -> int:
.replace(b"\n", b"") .replace(b"\n", b"")
.decode("utf8", "backslashreplace") .decode("utf8", "backslashreplace")
) )
time_str = datetime.now().time().strftime("[%H:%M:%S]") time_ = datetime.now()
nanoseconds = time_.microsecond // 1000
time_str = f"[{time_.hour:02}:{time_.minute:02}:{time_.second:02}.{nanoseconds:03}]"
safe_print(parser.parse_line(line, time_str)) safe_print(parser.parse_line(line, time_str))
backtrace_state = platformio_api.process_stacktrace( backtrace_state = platformio_api.process_stacktrace(
@@ -349,7 +457,7 @@ def upload_using_esptool(
"detect", "detect",
] ]
for img in flash_images: for img in flash_images:
cmd += [img.offset, img.path] cmd += [img.offset, str(img.path)]
if os.environ.get("ESPHOME_USE_SUBPROCESS") is None: if os.environ.get("ESPHOME_USE_SUBPROCESS") is None:
import esptool import esptool
@@ -396,27 +504,29 @@ def check_permissions(port: str):
) )
def upload_program(config: ConfigType, args: ArgsProtocol, host: str) -> int | str: def upload_program(
config: ConfigType, args: ArgsProtocol, devices: list[str]
) -> tuple[int, str | None]:
host = devices[0]
try: try:
module = importlib.import_module("esphome.components." + CORE.target_platform) module = importlib.import_module("esphome.components." + CORE.target_platform)
if getattr(module, "upload_program")(config, args, host): if getattr(module, "upload_program")(config, args, host):
return 0 return 0, host
except AttributeError: except AttributeError:
pass pass
if get_port_type(host) == "SERIAL": if get_port_type(host) == "SERIAL":
check_permissions(host) check_permissions(host)
exit_code = 1
if CORE.target_platform in (PLATFORM_ESP32, PLATFORM_ESP8266): if CORE.target_platform in (PLATFORM_ESP32, PLATFORM_ESP8266):
file = getattr(args, "file", None) file = getattr(args, "file", None)
return upload_using_esptool(config, host, file, args.upload_speed) exit_code = upload_using_esptool(config, host, file, args.upload_speed)
elif CORE.target_platform == PLATFORM_RP2040 or CORE.is_libretiny:
exit_code = upload_using_platformio(config, host)
# else: Unknown target platform, exit_code remains 1
if CORE.target_platform in (PLATFORM_RP2040): return exit_code, host if exit_code == 0 else None
return upload_using_platformio(config, host)
if CORE.is_libretiny:
return upload_using_platformio(config, host)
return 1 # Unknown target platform
ota_conf = {} ota_conf = {}
for ota_item in config.get(CONF_OTA, []): for ota_item in config.get(CONF_OTA, []):
@@ -433,31 +543,26 @@ def upload_program(config: ConfigType, args: ArgsProtocol, host: str) -> int | s
remote_port = int(ota_conf[CONF_PORT]) remote_port = int(ota_conf[CONF_PORT])
password = ota_conf.get(CONF_PASSWORD, "") 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 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"
)
):
from esphome import mqtt
host = mqtt.get_esphome_device_ip(
config, args.username, args.password, args.client_id
)
if getattr(args, "file", None) is not None: if getattr(args, "file", None) is not None:
return espota2.run_ota(host, remote_port, password, args.file) binary = Path(args.file)
else:
binary = CORE.firmware_bin
return espota2.run_ota(host, remote_port, password, CORE.firmware_bin) # MQTT address resolution
if get_port_type(host) in ("MQTT", "MQTTIP"):
devices = mqtt_get_ip(config, args.username, args.password, args.client_id)
return espota2.run_ota(devices, remote_port, password, binary)
def show_logs(config: ConfigType, args: ArgsProtocol, devices: list[str]) -> int | None: def show_logs(config: ConfigType, args: ArgsProtocol, devices: list[str]) -> int | None:
try:
module = importlib.import_module("esphome.components." + CORE.target_platform)
if getattr(module, "show_logs")(config, args, devices):
return 0
except AttributeError:
pass
if "logger" not in config: if "logger" not in config:
raise EsphomeError("Logger is not configured!") raise EsphomeError("Logger is not configured!")
@@ -466,20 +571,28 @@ def show_logs(config: ConfigType, args: ArgsProtocol, devices: list[str]) -> int
if get_port_type(port) == "SERIAL": if get_port_type(port) == "SERIAL":
check_permissions(port) check_permissions(port)
return run_miniterm(config, port, args) 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
mqtt_address = mqtt.get_esphome_device_ip( port_type = get_port_type(port)
# Check if we should use API for logging
if has_api():
addresses_to_use: list[str] | None = None
if port_type == "NETWORK" and (has_mdns() or is_ip_address(port)):
addresses_to_use = devices
elif port_type in ("NETWORK", "MQTT", "MQTTIP") and has_mqtt_ip_lookup():
# Only use MQTT IP lookup if the first condition didn't match
# (for MQTT/MQTTIP types, or for NETWORK when mdns/ip check fails)
addresses_to_use = mqtt_get_ip(
config, args.username, args.password, args.client_id config, args.username, args.password, args.client_id
)[0] )
addresses_to_use = [mqtt_address]
from esphome.components.api.client import run_logs if addresses_to_use is not None:
from esphome.components.api.client import run_logs
return run_logs(config, addresses_to_use) return run_logs(config, addresses_to_use)
if get_port_type(port) in ("NETWORK", "MQTT") and "mqtt" in config:
if port_type in ("NETWORK", "MQTT") and has_mqtt_logging():
from esphome import mqtt from esphome import mqtt
return mqtt.show_logs( return mqtt.show_logs(
@@ -500,7 +613,7 @@ def clean_mqtt(config: ConfigType, args: ArgsProtocol) -> int | None:
def command_wizard(args: ArgsProtocol) -> int | None: def command_wizard(args: ArgsProtocol) -> int | None:
from esphome import wizard from esphome import wizard
return wizard.wizard(args.configuration) return wizard.wizard(Path(args.configuration))
def command_config(args: ArgsProtocol, config: ConfigType) -> int | None: def command_config(args: ArgsProtocol, config: ConfigType) -> int | None:
@@ -545,23 +658,14 @@ def command_upload(args: ArgsProtocol, config: ConfigType) -> int | None:
devices = choose_upload_log_host( devices = choose_upload_log_host(
default=args.device, default=args.device,
check_default=None, check_default=None,
show_ota=True, purpose=Purpose.UPLOADING,
show_mqtt=False,
show_api=False,
purpose="uploading",
) )
# Try each device until one succeeds exit_code, _ = upload_program(config, args, devices)
exit_code = 1 if exit_code == 0:
for device in devices: _LOGGER.info("Successfully uploaded program.")
_LOGGER.info("Uploading to %s", device) else:
exit_code = upload_program(config, args, device) _LOGGER.warning("Failed to upload to %s", devices)
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 return exit_code
@@ -579,10 +683,7 @@ def command_logs(args: ArgsProtocol, config: ConfigType) -> int | None:
devices = choose_upload_log_host( devices = choose_upload_log_host(
default=args.device, default=args.device,
check_default=None, check_default=None,
show_ota=False, purpose=Purpose.LOGGING,
show_mqtt=True,
show_api=True,
purpose="logging",
) )
return show_logs(config, args, devices) return show_logs(config, args, devices)
@@ -608,25 +709,14 @@ def command_run(args: ArgsProtocol, config: ConfigType) -> int | None:
devices = choose_upload_log_host( devices = choose_upload_log_host(
default=args.device, default=args.device,
check_default=None, check_default=None,
show_ota=True, purpose=Purpose.UPLOADING,
show_mqtt=False,
show_api=True,
purpose="uploading",
) )
# Try each device for upload until one succeeds exit_code, successful_device = upload_program(config, args, devices)
successful_device: str | None = None if exit_code == 0:
for device in devices: _LOGGER.info("Successfully uploaded program.")
_LOGGER.info("Uploading to %s", device) else:
exit_code = upload_program(config, args, device) _LOGGER.warning("Failed to upload to %s", devices)
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 return exit_code
if args.no_logs: if args.no_logs:
@@ -636,10 +726,7 @@ def command_run(args: ArgsProtocol, config: ConfigType) -> int | None:
devices = choose_upload_log_host( devices = choose_upload_log_host(
default=successful_device, default=successful_device,
check_default=successful_device, check_default=successful_device,
show_ota=False, purpose=Purpose.LOGGING,
show_mqtt=True,
show_api=True,
purpose="logging",
) )
return show_logs(config, args, devices) return show_logs(config, args, devices)
@@ -648,6 +735,16 @@ def command_clean_mqtt(args: ArgsProtocol, config: ConfigType) -> int | None:
return clean_mqtt(config, args) return clean_mqtt(config, args)
def command_clean_all(args: ArgsProtocol) -> int | None:
try:
writer.clean_all(args.configuration)
except OSError as err:
_LOGGER.error("Error cleaning all files: %s", err)
return 1
_LOGGER.info("Done!")
return 0
def command_mqtt_fingerprint(args: ArgsProtocol, config: ConfigType) -> int | None: def command_mqtt_fingerprint(args: ArgsProtocol, config: ConfigType) -> int | None:
from esphome import mqtt from esphome import mqtt
@@ -689,7 +786,7 @@ def command_update_all(args: ArgsProtocol) -> int | None:
safe_print(f"{half_line}{middle_text}{half_line}") safe_print(f"{half_line}{middle_text}{half_line}")
for f in files: for f in files:
safe_print(f"Updating {color(AnsiFore.CYAN, f)}") safe_print(f"Updating {color(AnsiFore.CYAN, str(f))}")
safe_print("-" * twidth) safe_print("-" * twidth)
safe_print() safe_print()
if CORE.dashboard: if CORE.dashboard:
@@ -701,10 +798,10 @@ def command_update_all(args: ArgsProtocol) -> int | None:
"esphome", "run", f, "--no-logs", "--device", "OTA" "esphome", "run", f, "--no-logs", "--device", "OTA"
) )
if rc == 0: if rc == 0:
print_bar(f"[{color(AnsiFore.BOLD_GREEN, 'SUCCESS')}] {f}") print_bar(f"[{color(AnsiFore.BOLD_GREEN, 'SUCCESS')}] {str(f)}")
success[f] = True success[f] = True
else: else:
print_bar(f"[{color(AnsiFore.BOLD_RED, 'ERROR')}] {f}") print_bar(f"[{color(AnsiFore.BOLD_RED, 'ERROR')}] {str(f)}")
success[f] = False success[f] = False
safe_print() safe_print()
@@ -715,9 +812,9 @@ def command_update_all(args: ArgsProtocol) -> int | None:
failed = 0 failed = 0
for f in files: for f in files:
if success[f]: if success[f]:
safe_print(f" - {f}: {color(AnsiFore.GREEN, 'SUCCESS')}") safe_print(f" - {str(f)}: {color(AnsiFore.GREEN, 'SUCCESS')}")
else: else:
safe_print(f" - {f}: {color(AnsiFore.BOLD_RED, 'FAILED')}") safe_print(f" - {str(f)}: {color(AnsiFore.BOLD_RED, 'FAILED')}")
failed += 1 failed += 1
return failed return failed
@@ -739,7 +836,8 @@ def command_idedata(args: ArgsProtocol, config: ConfigType) -> int:
def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None: def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
for c in args.name: new_name = args.name
for c in new_name:
if c not in ALLOWED_NAME_CHARS: if c not in ALLOWED_NAME_CHARS:
print( print(
color( color(
@@ -750,8 +848,7 @@ def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
) )
return 1 return 1
# Load existing yaml file # Load existing yaml file
with open(CORE.config_path, mode="r+", encoding="utf-8") as raw_file: raw_contents = CORE.config_path.read_text(encoding="utf-8")
raw_contents = raw_file.read()
yaml = yaml_util.load_yaml(CORE.config_path) yaml = yaml_util.load_yaml(CORE.config_path)
if CONF_ESPHOME not in yaml or CONF_NAME not in yaml[CONF_ESPHOME]: if CONF_ESPHOME not in yaml or CONF_NAME not in yaml[CONF_ESPHOME]:
@@ -766,7 +863,7 @@ def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
if match is None: if match is None:
new_raw = re.sub( new_raw = re.sub(
rf"name:\s+[\"']?{old_name}[\"']?", rf"name:\s+[\"']?{old_name}[\"']?",
f'name: "{args.name}"', f'name: "{new_name}"',
raw_contents, raw_contents,
) )
else: else:
@@ -786,29 +883,28 @@ def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
new_raw = re.sub( new_raw = re.sub(
rf"^(\s+{match.group(1)}):\s+[\"']?{old_name}[\"']?", rf"^(\s+{match.group(1)}):\s+[\"']?{old_name}[\"']?",
f'\\1: "{args.name}"', f'\\1: "{new_name}"',
raw_contents, raw_contents,
flags=re.MULTILINE, flags=re.MULTILINE,
) )
new_path = os.path.join(CORE.config_dir, args.name + ".yaml") new_path: Path = CORE.config_dir / (new_name + ".yaml")
print( print(
f"Updating {color(AnsiFore.CYAN, CORE.config_path)} to {color(AnsiFore.CYAN, new_path)}" f"Updating {color(AnsiFore.CYAN, str(CORE.config_path))} to {color(AnsiFore.CYAN, str(new_path))}"
) )
print() print()
with open(new_path, mode="w", encoding="utf-8") as new_file: new_path.write_text(new_raw, encoding="utf-8")
new_file.write(new_raw)
rc = run_external_process("esphome", "config", new_path) rc = run_external_process("esphome", "config", str(new_path))
if rc != 0: if rc != 0:
print(color(AnsiFore.BOLD_RED, "Rename failed. Reverting changes.")) print(color(AnsiFore.BOLD_RED, "Rename failed. Reverting changes."))
os.remove(new_path) new_path.unlink()
return 1 return 1
cli_args = [ cli_args = [
"run", "run",
new_path, str(new_path),
"--no-logs", "--no-logs",
"--device", "--device",
CORE.address, CORE.address,
@@ -822,11 +918,11 @@ def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
except KeyboardInterrupt: except KeyboardInterrupt:
rc = 1 rc = 1
if rc != 0: if rc != 0:
os.remove(new_path) new_path.unlink()
return 1 return 1
if CORE.config_path != new_path: if CORE.config_path != new_path:
os.remove(CORE.config_path) CORE.config_path.unlink()
print(color(AnsiFore.BOLD_GREEN, "SUCCESS")) print(color(AnsiFore.BOLD_GREEN, "SUCCESS"))
print() print()
@@ -839,6 +935,7 @@ PRE_CONFIG_ACTIONS = {
"dashboard": command_dashboard, "dashboard": command_dashboard,
"vscode": command_vscode, "vscode": command_vscode,
"update-all": command_update_all, "update-all": command_update_all,
"clean-all": command_clean_all,
} }
POST_CONFIG_ACTIONS = { POST_CONFIG_ACTIONS = {
@@ -847,9 +944,9 @@ POST_CONFIG_ACTIONS = {
"upload": command_upload, "upload": command_upload,
"logs": command_logs, "logs": command_logs,
"run": command_run, "run": command_run,
"clean": command_clean,
"clean-mqtt": command_clean_mqtt, "clean-mqtt": command_clean_mqtt,
"mqtt-fingerprint": command_mqtt_fingerprint, "mqtt-fingerprint": command_mqtt_fingerprint,
"clean": command_clean,
"idedata": command_idedata, "idedata": command_idedata,
"rename": command_rename, "rename": command_rename,
"discover": command_discover, "discover": command_discover,
@@ -893,6 +990,18 @@ def parse_args(argv):
help="Add a substitution", help="Add a substitution",
metavar=("key", "value"), metavar=("key", "value"),
) )
options_parser.add_argument(
"--mdns-address-cache",
help="mDNS address cache mapping in format 'hostname=ip1,ip2'",
action="append",
default=[],
)
options_parser.add_argument(
"--dns-address-cache",
help="DNS address cache mapping in format 'hostname=ip1,ip2'",
action="append",
default=[],
)
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description=f"ESPHome {const.__version__}", parents=[options_parser] description=f"ESPHome {const.__version__}", parents=[options_parser]
@@ -1050,6 +1159,13 @@ def parse_args(argv):
"configuration", help="Your YAML configuration file(s).", nargs="+" "configuration", help="Your YAML configuration file(s).", nargs="+"
) )
parser_clean_all = subparsers.add_parser(
"clean-all", help="Clean all build and platform files."
)
parser_clean_all.add_argument(
"configuration", help="Your YAML configuration directory.", nargs="*"
)
parser_dashboard = subparsers.add_parser( parser_dashboard = subparsers.add_parser(
"dashboard", help="Create a simple web server for a dashboard." "dashboard", help="Create a simple web server for a dashboard."
) )
@@ -1096,7 +1212,7 @@ def parse_args(argv):
parser_update = subparsers.add_parser("update-all") parser_update = subparsers.add_parser("update-all")
parser_update.add_argument( parser_update.add_argument(
"configuration", help="Your YAML configuration file directories.", nargs="+" "configuration", help="Your YAML configuration file or directory.", nargs="+"
) )
parser_idedata = subparsers.add_parser("idedata") parser_idedata = subparsers.add_parser("idedata")
@@ -1140,9 +1256,15 @@ def parse_args(argv):
def run_esphome(argv): def run_esphome(argv):
from esphome.address_cache import AddressCache
args = parse_args(argv) args = parse_args(argv)
CORE.dashboard = args.dashboard CORE.dashboard = args.dashboard
# Create address cache from command-line arguments
CORE.address_cache = AddressCache.from_cli_args(
args.mdns_address_cache, args.dns_address_cache
)
# Override log level if verbose is set # Override log level if verbose is set
if args.verbose: if args.verbose:
args.log_level = "DEBUG" args.log_level = "DEBUG"
@@ -1165,14 +1287,20 @@ def run_esphome(argv):
_LOGGER.info("ESPHome %s", const.__version__) _LOGGER.info("ESPHome %s", const.__version__)
for conf_path in args.configuration: for conf_path in args.configuration:
if any(os.path.basename(conf_path) == x for x in SECRETS_FILES): conf_path = Path(conf_path)
if any(conf_path.name == x for x in SECRETS_FILES):
_LOGGER.warning("Skipping secrets file %s", conf_path) _LOGGER.warning("Skipping secrets file %s", conf_path)
continue continue
CORE.config_path = conf_path CORE.config_path = conf_path
CORE.dashboard = args.dashboard CORE.dashboard = args.dashboard
config = read_config(dict(args.substitution) if args.substitution else {}) # For logs command, skip updating external components
skip_external = args.command == "logs"
config = read_config(
dict(args.substitution) if args.substitution else {},
skip_external_update=skip_external,
)
if config is None: if config is None:
return 2 return 2
CORE.config = config CORE.config = config

142
esphome/address_cache.py Normal file
View File

@@ -0,0 +1,142 @@
"""Address cache for DNS and mDNS lookups."""
from __future__ import annotations
import logging
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from collections.abc import Iterable
_LOGGER = logging.getLogger(__name__)
def normalize_hostname(hostname: str) -> str:
"""Normalize hostname for cache lookups.
Removes trailing dots and converts to lowercase.
"""
return hostname.rstrip(".").lower()
class AddressCache:
"""Cache for DNS and mDNS address lookups.
This cache stores pre-resolved addresses from command-line arguments
to avoid slow DNS/mDNS lookups during builds.
"""
def __init__(
self,
mdns_cache: dict[str, list[str]] | None = None,
dns_cache: dict[str, list[str]] | None = None,
) -> None:
"""Initialize the address cache.
Args:
mdns_cache: Pre-populated mDNS addresses (hostname -> IPs)
dns_cache: Pre-populated DNS addresses (hostname -> IPs)
"""
self.mdns_cache = mdns_cache or {}
self.dns_cache = dns_cache or {}
def _get_cached_addresses(
self, hostname: str, cache: dict[str, list[str]], cache_type: str
) -> list[str] | None:
"""Get cached addresses from a specific cache.
Args:
hostname: The hostname to look up
cache: The cache dictionary to check
cache_type: Type of cache for logging ("mDNS" or "DNS")
Returns:
List of IP addresses if found in cache, None otherwise
"""
normalized = normalize_hostname(hostname)
if addresses := cache.get(normalized):
_LOGGER.debug("Using %s cache for %s: %s", cache_type, hostname, addresses)
return addresses
return None
def get_mdns_addresses(self, hostname: str) -> list[str] | None:
"""Get cached mDNS addresses for a hostname.
Args:
hostname: The hostname to look up (should end with .local)
Returns:
List of IP addresses if found in cache, None otherwise
"""
return self._get_cached_addresses(hostname, self.mdns_cache, "mDNS")
def get_dns_addresses(self, hostname: str) -> list[str] | None:
"""Get cached DNS addresses for a hostname.
Args:
hostname: The hostname to look up
Returns:
List of IP addresses if found in cache, None otherwise
"""
return self._get_cached_addresses(hostname, self.dns_cache, "DNS")
def get_addresses(self, hostname: str) -> list[str] | None:
"""Get cached addresses for a hostname.
Checks mDNS cache for .local domains, DNS cache otherwise.
Args:
hostname: The hostname to look up
Returns:
List of IP addresses if found in cache, None otherwise
"""
normalized = normalize_hostname(hostname)
if normalized.endswith(".local"):
return self.get_mdns_addresses(hostname)
return self.get_dns_addresses(hostname)
def has_cache(self) -> bool:
"""Check if any cache entries exist."""
return bool(self.mdns_cache or self.dns_cache)
@classmethod
def from_cli_args(
cls, mdns_args: Iterable[str], dns_args: Iterable[str]
) -> AddressCache:
"""Create cache from command-line arguments.
Args:
mdns_args: List of mDNS cache entries like ['host=ip1,ip2']
dns_args: List of DNS cache entries like ['host=ip1,ip2']
Returns:
Configured AddressCache instance
"""
mdns_cache = cls._parse_cache_args(mdns_args)
dns_cache = cls._parse_cache_args(dns_args)
return cls(mdns_cache=mdns_cache, dns_cache=dns_cache)
@staticmethod
def _parse_cache_args(cache_args: Iterable[str]) -> dict[str, list[str]]:
"""Parse cache arguments into a dictionary.
Args:
cache_args: List of cache mappings like ['host1=ip1,ip2', 'host2=ip3']
Returns:
Dictionary mapping normalized hostnames to list of IP addresses
"""
cache: dict[str, list[str]] = {}
for arg in cache_args:
if "=" not in arg:
_LOGGER.warning(
"Invalid cache format: %s (expected 'hostname=ip1,ip2')", arg
)
continue
hostname, ips = arg.split("=", 1)
# Normalize hostname for consistent lookups
normalized = normalize_hostname(hostname)
cache[normalized] = [ip.strip() for ip in ips.split(",")]
return cache

View File

@@ -15,7 +15,10 @@ from esphome.const import (
CONF_TYPE_ID, CONF_TYPE_ID,
CONF_UPDATE_INTERVAL, CONF_UPDATE_INTERVAL,
) )
from esphome.core import ID
from esphome.cpp_generator import MockObj, MockObjClass, TemplateArgsType
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
from esphome.types import ConfigType
from esphome.util import Registry from esphome.util import Registry
@@ -49,11 +52,11 @@ def maybe_conf(conf, *validators):
return validate return validate
def register_action(name, action_type, schema): def register_action(name: str, action_type: MockObjClass, schema: cv.Schema):
return ACTION_REGISTRY.register(name, action_type, schema) return ACTION_REGISTRY.register(name, action_type, schema)
def register_condition(name, condition_type, schema): def register_condition(name: str, condition_type: MockObjClass, schema: cv.Schema):
return CONDITION_REGISTRY.register(name, condition_type, schema) return CONDITION_REGISTRY.register(name, condition_type, schema)
@@ -164,43 +167,78 @@ XorCondition = cg.esphome_ns.class_("XorCondition", Condition)
@register_condition("and", AndCondition, validate_condition_list) @register_condition("and", AndCondition, validate_condition_list)
async def and_condition_to_code(config, condition_id, template_arg, args): async def and_condition_to_code(
config: ConfigType,
condition_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
conditions = await build_condition_list(config, template_arg, args) conditions = await build_condition_list(config, template_arg, args)
return cg.new_Pvariable(condition_id, template_arg, conditions) return cg.new_Pvariable(condition_id, template_arg, conditions)
@register_condition("or", OrCondition, validate_condition_list) @register_condition("or", OrCondition, validate_condition_list)
async def or_condition_to_code(config, condition_id, template_arg, args): async def or_condition_to_code(
config: ConfigType,
condition_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
conditions = await build_condition_list(config, template_arg, args) conditions = await build_condition_list(config, template_arg, args)
return cg.new_Pvariable(condition_id, template_arg, conditions) return cg.new_Pvariable(condition_id, template_arg, conditions)
@register_condition("all", AndCondition, validate_condition_list) @register_condition("all", AndCondition, validate_condition_list)
async def all_condition_to_code(config, condition_id, template_arg, args): async def all_condition_to_code(
config: ConfigType,
condition_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
conditions = await build_condition_list(config, template_arg, args) conditions = await build_condition_list(config, template_arg, args)
return cg.new_Pvariable(condition_id, template_arg, conditions) return cg.new_Pvariable(condition_id, template_arg, conditions)
@register_condition("any", OrCondition, validate_condition_list) @register_condition("any", OrCondition, validate_condition_list)
async def any_condition_to_code(config, condition_id, template_arg, args): async def any_condition_to_code(
config: ConfigType,
condition_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
conditions = await build_condition_list(config, template_arg, args) conditions = await build_condition_list(config, template_arg, args)
return cg.new_Pvariable(condition_id, template_arg, conditions) return cg.new_Pvariable(condition_id, template_arg, conditions)
@register_condition("not", NotCondition, validate_potentially_and_condition) @register_condition("not", NotCondition, validate_potentially_and_condition)
async def not_condition_to_code(config, condition_id, template_arg, args): async def not_condition_to_code(
config: ConfigType,
condition_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
condition = await build_condition(config, template_arg, args) condition = await build_condition(config, template_arg, args)
return cg.new_Pvariable(condition_id, template_arg, condition) return cg.new_Pvariable(condition_id, template_arg, condition)
@register_condition("xor", XorCondition, validate_condition_list) @register_condition("xor", XorCondition, validate_condition_list)
async def xor_condition_to_code(config, condition_id, template_arg, args): async def xor_condition_to_code(
config: ConfigType,
condition_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
conditions = await build_condition_list(config, template_arg, args) conditions = await build_condition_list(config, template_arg, args)
return cg.new_Pvariable(condition_id, template_arg, conditions) return cg.new_Pvariable(condition_id, template_arg, conditions)
@register_condition("lambda", LambdaCondition, cv.returning_lambda) @register_condition("lambda", LambdaCondition, cv.returning_lambda)
async def lambda_condition_to_code(config, condition_id, template_arg, args): async def lambda_condition_to_code(
config: ConfigType,
condition_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
lambda_ = await cg.process_lambda(config, args, return_type=bool) lambda_ = await cg.process_lambda(config, args, return_type=bool)
return cg.new_Pvariable(condition_id, template_arg, lambda_) return cg.new_Pvariable(condition_id, template_arg, lambda_)
@@ -217,7 +255,12 @@ async def lambda_condition_to_code(config, condition_id, template_arg, args):
} }
).extend(cv.COMPONENT_SCHEMA), ).extend(cv.COMPONENT_SCHEMA),
) )
async def for_condition_to_code(config, condition_id, template_arg, args): async def for_condition_to_code(
config: ConfigType,
condition_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
condition = await build_condition( condition = await build_condition(
config[CONF_CONDITION], cg.TemplateArguments(), [] config[CONF_CONDITION], cg.TemplateArguments(), []
) )
@@ -231,7 +274,12 @@ async def for_condition_to_code(config, condition_id, template_arg, args):
@register_action( @register_action(
"delay", DelayAction, cv.templatable(cv.positive_time_period_milliseconds) "delay", DelayAction, cv.templatable(cv.positive_time_period_milliseconds)
) )
async def delay_action_to_code(config, action_id, template_arg, args): async def delay_action_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
var = cg.new_Pvariable(action_id, template_arg) var = cg.new_Pvariable(action_id, template_arg)
await cg.register_component(var, {}) await cg.register_component(var, {})
template_ = await cg.templatable(config, args, cg.uint32) template_ = await cg.templatable(config, args, cg.uint32)
@@ -256,10 +304,15 @@ async def delay_action_to_code(config, action_id, template_arg, args):
cv.has_at_least_one_key(CONF_CONDITION, CONF_ANY, CONF_ALL), cv.has_at_least_one_key(CONF_CONDITION, CONF_ANY, CONF_ALL),
), ),
) )
async def if_action_to_code(config, action_id, template_arg, args): async def if_action_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
cond_conf = next(el for el in config if el in (CONF_ANY, CONF_ALL, CONF_CONDITION)) cond_conf = next(el for el in config if el in (CONF_ANY, CONF_ALL, CONF_CONDITION))
conditions = await build_condition(config[cond_conf], template_arg, args) condition = await build_condition(config[cond_conf], template_arg, args)
var = cg.new_Pvariable(action_id, template_arg, conditions) var = cg.new_Pvariable(action_id, template_arg, condition)
if CONF_THEN in config: if CONF_THEN in config:
actions = await build_action_list(config[CONF_THEN], template_arg, args) actions = await build_action_list(config[CONF_THEN], template_arg, args)
cg.add(var.add_then(actions)) cg.add(var.add_then(actions))
@@ -279,9 +332,14 @@ async def if_action_to_code(config, action_id, template_arg, args):
} }
), ),
) )
async def while_action_to_code(config, action_id, template_arg, args): async def while_action_to_code(
conditions = await build_condition(config[CONF_CONDITION], template_arg, args) config: ConfigType,
var = cg.new_Pvariable(action_id, template_arg, conditions) action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
condition = await build_condition(config[CONF_CONDITION], template_arg, args)
var = cg.new_Pvariable(action_id, template_arg, condition)
actions = await build_action_list(config[CONF_THEN], template_arg, args) actions = await build_action_list(config[CONF_THEN], template_arg, args)
cg.add(var.add_then(actions)) cg.add(var.add_then(actions))
return var return var
@@ -297,7 +355,12 @@ async def while_action_to_code(config, action_id, template_arg, args):
} }
), ),
) )
async def repeat_action_to_code(config, action_id, template_arg, args): async def repeat_action_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
var = cg.new_Pvariable(action_id, template_arg) var = cg.new_Pvariable(action_id, template_arg)
count_template = await cg.templatable(config[CONF_COUNT], args, cg.uint32) count_template = await cg.templatable(config[CONF_COUNT], args, cg.uint32)
cg.add(var.set_count(count_template)) cg.add(var.set_count(count_template))
@@ -320,9 +383,14 @@ _validate_wait_until = cv.maybe_simple_value(
@register_action("wait_until", WaitUntilAction, _validate_wait_until) @register_action("wait_until", WaitUntilAction, _validate_wait_until)
async def wait_until_action_to_code(config, action_id, template_arg, args): async def wait_until_action_to_code(
conditions = await build_condition(config[CONF_CONDITION], template_arg, args) config: ConfigType,
var = cg.new_Pvariable(action_id, template_arg, conditions) action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
condition = await build_condition(config[CONF_CONDITION], template_arg, args)
var = cg.new_Pvariable(action_id, template_arg, condition)
if CONF_TIMEOUT in config: if CONF_TIMEOUT in config:
template_ = await cg.templatable(config[CONF_TIMEOUT], args, cg.uint32) template_ = await cg.templatable(config[CONF_TIMEOUT], args, cg.uint32)
cg.add(var.set_timeout_value(template_)) cg.add(var.set_timeout_value(template_))
@@ -331,7 +399,12 @@ async def wait_until_action_to_code(config, action_id, template_arg, args):
@register_action("lambda", LambdaAction, cv.lambda_) @register_action("lambda", LambdaAction, cv.lambda_)
async def lambda_action_to_code(config, action_id, template_arg, args): async def lambda_action_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
lambda_ = await cg.process_lambda(config, args, return_type=cg.void) lambda_ = await cg.process_lambda(config, args, return_type=cg.void)
return cg.new_Pvariable(action_id, template_arg, lambda_) return cg.new_Pvariable(action_id, template_arg, lambda_)
@@ -345,7 +418,12 @@ async def lambda_action_to_code(config, action_id, template_arg, args):
} }
), ),
) )
async def component_update_action_to_code(config, action_id, template_arg, args): async def component_update_action_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
comp = await cg.get_variable(config[CONF_ID]) comp = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, comp) return cg.new_Pvariable(action_id, template_arg, comp)
@@ -359,7 +437,12 @@ async def component_update_action_to_code(config, action_id, template_arg, args)
} }
), ),
) )
async def component_suspend_action_to_code(config, action_id, template_arg, args): async def component_suspend_action_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
comp = await cg.get_variable(config[CONF_ID]) comp = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, comp) return cg.new_Pvariable(action_id, template_arg, comp)
@@ -376,7 +459,12 @@ async def component_suspend_action_to_code(config, action_id, template_arg, args
} }
), ),
) )
async def component_resume_action_to_code(config, action_id, template_arg, args): async def component_resume_action_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
comp = await cg.get_variable(config[CONF_ID]) comp = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, comp) var = cg.new_Pvariable(action_id, template_arg, comp)
if CONF_UPDATE_INTERVAL in config: if CONF_UPDATE_INTERVAL in config:
@@ -385,7 +473,9 @@ async def component_resume_action_to_code(config, action_id, template_arg, args)
return var return var
async def build_action(full_config, template_arg, args): async def build_action(
full_config: ConfigType, template_arg: cg.TemplateArguments, args: TemplateArgsType
) -> MockObj:
registry_entry, config = cg.extract_registry_entry_config( registry_entry, config = cg.extract_registry_entry_config(
ACTION_REGISTRY, full_config ACTION_REGISTRY, full_config
) )
@@ -394,15 +484,19 @@ async def build_action(full_config, template_arg, args):
return await builder(config, action_id, template_arg, args) return await builder(config, action_id, template_arg, args)
async def build_action_list(config, templ, arg_type): async def build_action_list(
actions = [] config: list[ConfigType], templ: cg.TemplateArguments, arg_type: TemplateArgsType
) -> list[MockObj]:
actions: list[MockObj] = []
for conf in config: for conf in config:
action = await build_action(conf, templ, arg_type) action = await build_action(conf, templ, arg_type)
actions.append(action) actions.append(action)
return actions return actions
async def build_condition(full_config, template_arg, args): async def build_condition(
full_config: ConfigType, template_arg: cg.TemplateArguments, args: TemplateArgsType
) -> MockObj:
registry_entry, config = cg.extract_registry_entry_config( registry_entry, config = cg.extract_registry_entry_config(
CONDITION_REGISTRY, full_config CONDITION_REGISTRY, full_config
) )
@@ -411,15 +505,19 @@ async def build_condition(full_config, template_arg, args):
return await builder(config, action_id, template_arg, args) return await builder(config, action_id, template_arg, args)
async def build_condition_list(config, templ, args): async def build_condition_list(
conditions = [] config: ConfigType, templ: cg.TemplateArguments, args: TemplateArgsType
) -> list[MockObj]:
conditions: list[MockObj] = []
for conf in config: for conf in config:
condition = await build_condition(conf, templ, args) condition = await build_condition(conf, templ, args)
conditions.append(condition) conditions.append(condition)
return conditions return conditions
async def build_automation(trigger, args, config): async def build_automation(
trigger: MockObj, args: TemplateArgsType, config: ConfigType
) -> MockObj:
arg_types = [arg[0] for arg in args] arg_types = [arg[0] for arg in args]
templ = cg.TemplateArguments(*arg_types) templ = cg.TemplateArguments(*arg_types)
obj = cg.new_Pvariable(config[CONF_AUTOMATION_ID], templ, trigger) obj = cg.new_Pvariable(config[CONF_AUTOMATION_ID], templ, trigger)

View File

@@ -1,5 +1,3 @@
import os
from esphome.const import __version__ from esphome.const import __version__
from esphome.core import CORE from esphome.core import CORE
from esphome.helpers import mkdir_p, read_file, write_file_if_changed from esphome.helpers import mkdir_p, read_file, write_file_if_changed
@@ -63,7 +61,7 @@ def write_ini(content):
update_storage_json() update_storage_json()
path = CORE.relative_build_path("platformio.ini") path = CORE.relative_build_path("platformio.ini")
if os.path.isfile(path): if path.is_file():
text = read_file(path) text = read_file(path)
content_format = find_begin_end( content_format = find_begin_end(
text, INI_AUTO_GENERATE_BEGIN, INI_AUTO_GENERATE_END text, INI_AUTO_GENERATE_BEGIN, INI_AUTO_GENERATE_END

View File

@@ -12,6 +12,7 @@ from esphome.cpp_generator import ( # noqa: F401
ArrayInitializer, ArrayInitializer,
Expression, Expression,
LineComment, LineComment,
LogStringLiteral,
MockObj, MockObj,
MockObjClass, MockObjClass,
Pvariable, Pvariable,

View File

@@ -61,11 +61,10 @@ void AbsoluteHumidityComponent::loop() {
ESP_LOGW(TAG, "No valid state from temperature sensor!"); ESP_LOGW(TAG, "No valid state from temperature sensor!");
} }
if (no_humidity) { if (no_humidity) {
ESP_LOGW(TAG, "No valid state from temperature sensor!"); ESP_LOGW(TAG, "No valid state from humidity sensor!");
} }
ESP_LOGW(TAG, "Unable to calculate absolute humidity.");
this->publish_state(NAN); this->publish_state(NAN);
this->status_set_warning(); this->status_set_warning(LOG_STR("Unable to calculate absolute humidity."));
return; return;
} }
@@ -87,9 +86,8 @@ void AbsoluteHumidityComponent::loop() {
es = es_wobus(temperature_c); es = es_wobus(temperature_c);
break; break;
default: default:
ESP_LOGE(TAG, "Invalid saturation vapor pressure equation selection!");
this->publish_state(NAN); this->publish_state(NAN);
this->status_set_error(); this->status_set_error("Invalid saturation vapor pressure equation selection!");
return; return;
} }
ESP_LOGD(TAG, "Saturation vapor pressure %f kPa", es); ESP_LOGD(TAG, "Saturation vapor pressure %f kPa", es);

View File

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

View File

@@ -241,6 +241,8 @@ float ADCSensor::sample_autorange_() {
cali_config.bitwidth = ADC_BITWIDTH_DEFAULT; cali_config.bitwidth = ADC_BITWIDTH_DEFAULT;
err = adc_cali_create_scheme_curve_fitting(&cali_config, &handle); err = adc_cali_create_scheme_curve_fitting(&cali_config, &handle);
ESP_LOGVV(TAG, "Autorange atten=%d: Calibration handle creation %s (err=%d)", atten,
(err == ESP_OK) ? "SUCCESS" : "FAILED", err);
#else #else
adc_cali_line_fitting_config_t cali_config = { adc_cali_line_fitting_config_t cali_config = {
.unit_id = this->adc_unit_, .unit_id = this->adc_unit_,
@@ -251,10 +253,14 @@ float ADCSensor::sample_autorange_() {
#endif #endif
}; };
err = adc_cali_create_scheme_line_fitting(&cali_config, &handle); err = adc_cali_create_scheme_line_fitting(&cali_config, &handle);
ESP_LOGVV(TAG, "Autorange atten=%d: Calibration handle creation %s (err=%d)", atten,
(err == ESP_OK) ? "SUCCESS" : "FAILED", err);
#endif #endif
int raw; int raw;
err = adc_oneshot_read(this->adc_handle_, this->channel_, &raw); err = adc_oneshot_read(this->adc_handle_, this->channel_, &raw);
ESP_LOGVV(TAG, "Autorange atten=%d: Raw ADC read %s, value=%d (err=%d)", atten,
(err == ESP_OK) ? "SUCCESS" : "FAILED", raw, err);
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGW(TAG, "ADC read failed in autorange with error %d", err); ESP_LOGW(TAG, "ADC read failed in autorange with error %d", err);
@@ -275,8 +281,10 @@ float ADCSensor::sample_autorange_() {
err = adc_cali_raw_to_voltage(handle, raw, &voltage_mv); err = adc_cali_raw_to_voltage(handle, raw, &voltage_mv);
if (err == ESP_OK) { if (err == ESP_OK) {
voltage = voltage_mv / 1000.0f; voltage = voltage_mv / 1000.0f;
ESP_LOGVV(TAG, "Autorange atten=%d: CALIBRATED - raw=%d -> %dmV -> %.6fV", atten, raw, voltage_mv, voltage);
} else { } else {
voltage = raw * 3.3f / 4095.0f; voltage = raw * 3.3f / 4095.0f;
ESP_LOGVV(TAG, "Autorange atten=%d: UNCALIBRATED FALLBACK - raw=%d -> %.6fV (3.3V ref)", atten, raw, voltage);
} }
// Clean up calibration handle // Clean up calibration handle
#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \ #if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \
@@ -287,6 +295,7 @@ float ADCSensor::sample_autorange_() {
#endif #endif
} else { } else {
voltage = raw * 3.3f / 4095.0f; voltage = raw * 3.3f / 4095.0f;
ESP_LOGVV(TAG, "Autorange atten=%d: NO CALIBRATION - raw=%d -> %.6fV (3.3V ref)", atten, raw, voltage);
} }
return {raw, voltage}; return {raw, voltage};
@@ -324,18 +333,32 @@ float ADCSensor::sample_autorange_() {
} }
const int adc_half = 2048; const int adc_half = 2048;
uint32_t c12 = std::min(raw12, adc_half); const uint32_t c12 = std::min(raw12, adc_half);
uint32_t c6 = adc_half - std::abs(raw6 - adc_half);
uint32_t c2 = adc_half - std::abs(raw2 - adc_half); const int32_t c6_signed = adc_half - std::abs(raw6 - adc_half);
uint32_t c0 = std::min(4095 - raw0, adc_half); const uint32_t c6 = (c6_signed > 0) ? c6_signed : 0; // Clamp to prevent underflow
uint32_t csum = c12 + c6 + c2 + c0;
const int32_t c2_signed = adc_half - std::abs(raw2 - adc_half);
const uint32_t c2 = (c2_signed > 0) ? c2_signed : 0; // Clamp to prevent underflow
const uint32_t c0 = std::min(4095 - raw0, adc_half);
const uint32_t csum = c12 + c6 + c2 + c0;
ESP_LOGVV(TAG, "Autorange summary:");
ESP_LOGVV(TAG, " Raw readings: 12db=%d, 6db=%d, 2.5db=%d, 0db=%d", raw12, raw6, raw2, raw0);
ESP_LOGVV(TAG, " Voltages: 12db=%.6f, 6db=%.6f, 2.5db=%.6f, 0db=%.6f", mv12, mv6, mv2, mv0);
ESP_LOGVV(TAG, " Coefficients: c12=%u, c6=%u, c2=%u, c0=%u, sum=%u", c12, c6, c2, c0, csum);
if (csum == 0) { if (csum == 0) {
ESP_LOGE(TAG, "Invalid weight sum in autorange calculation"); ESP_LOGE(TAG, "Invalid weight sum in autorange calculation");
return NAN; return NAN;
} }
return (mv12 * c12 + mv6 * c6 + mv2 * c2 + mv0 * c0) / csum; const float final_result = (mv12 * c12 + mv6 * c6 + mv2 * c2 + mv0 * c0) / csum;
ESP_LOGV(TAG, "Autorange final: (%.6f*%u + %.6f*%u + %.6f*%u + %.6f*%u)/%u = %.6fV", mv12, c12, mv6, c6, mv2, c2, mv0,
c0, csum, final_result);
return final_result;
} }
} // namespace adc } // namespace adc

View File

@@ -9,6 +9,7 @@ from esphome.components.zephyr import (
zephyr_add_prj_conf, zephyr_add_prj_conf,
zephyr_add_user, zephyr_add_user,
) )
from esphome.config_helpers import filter_source_files_from_platform
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_ATTENUATION, CONF_ATTENUATION,
@@ -20,6 +21,7 @@ from esphome.const import (
PLATFORM_NRF52, PLATFORM_NRF52,
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
UNIT_VOLT, UNIT_VOLT,
PlatformFramework,
) )
from esphome.core import CORE from esphome.core import CORE
@@ -174,3 +176,21 @@ async def to_code(config):
}}; }};
""" """
) )
FILTER_SOURCE_FILES = filter_source_files_from_platform(
{
"adc_sensor_esp32.cpp": {
PlatformFramework.ESP32_ARDUINO,
PlatformFramework.ESP32_IDF,
},
"adc_sensor_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
"adc_sensor_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO},
"adc_sensor_libretiny.cpp": {
PlatformFramework.BK72XX_ARDUINO,
PlatformFramework.RTL87XX_ARDUINO,
PlatformFramework.LN882X_ARDUINO,
},
"adc_sensor_zephyr.cpp": {PlatformFramework.NRF52_ZEPHYR},
}
)

View File

@@ -113,7 +113,7 @@ void ADE7880::update() {
if (this->channel_a_ != nullptr) { if (this->channel_a_ != nullptr) {
auto *chan = this->channel_a_; auto *chan = this->channel_a_;
this->update_sensor_from_s24zp_register16_(chan->current, AIRMS, [](float val) { return val / 100000.0f; }); this->update_sensor_from_s24zp_register16_(chan->current, AIRMS, [](float val) { return val / 100000.0f; });
this->update_sensor_from_s24zp_register16_(chan->voltage, BVRMS, [](float val) { return val / 10000.0f; }); this->update_sensor_from_s24zp_register16_(chan->voltage, AVRMS, [](float val) { return val / 10000.0f; });
this->update_sensor_from_s24zp_register16_(chan->active_power, AWATT, [](float val) { return val / 100.0f; }); this->update_sensor_from_s24zp_register16_(chan->active_power, AWATT, [](float val) { return val / 100.0f; });
this->update_sensor_from_s24zp_register16_(chan->apparent_power, AVA, [](float val) { return val / 100.0f; }); this->update_sensor_from_s24zp_register16_(chan->apparent_power, AVA, [](float val) { return val / 100.0f; });
this->update_sensor_from_s16_register16_(chan->power_factor, APF, this->update_sensor_from_s16_register16_(chan->power_factor, APF,

View File

@@ -96,7 +96,7 @@ void AHT10Component::read_data_() {
ESP_LOGD(TAG, "Read attempt %d at %ums", this->read_count_, (unsigned) (millis() - this->start_time_)); ESP_LOGD(TAG, "Read attempt %d at %ums", this->read_count_, (unsigned) (millis() - this->start_time_));
} }
if (this->read(data, 6) != i2c::ERROR_OK) { if (this->read(data, 6) != i2c::ERROR_OK) {
this->status_set_warning("Read failed, will retry"); this->status_set_warning(LOG_STR("Read failed, will retry"));
this->restart_read_(); this->restart_read_();
return; return;
} }
@@ -113,7 +113,7 @@ void AHT10Component::read_data_() {
} else { } else {
ESP_LOGD(TAG, "Invalid humidity, retrying"); ESP_LOGD(TAG, "Invalid humidity, retrying");
if (this->write(AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD)) != i2c::ERROR_OK) { if (this->write(AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD)) != i2c::ERROR_OK) {
this->status_set_warning(ESP_LOG_MSG_COMM_FAIL); this->status_set_warning(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
} }
this->restart_read_(); this->restart_read_();
return; return;
@@ -144,7 +144,7 @@ void AHT10Component::update() {
return; return;
this->start_time_ = millis(); this->start_time_ = millis();
if (this->write(AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD)) != i2c::ERROR_OK) { if (this->write(AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD)) != i2c::ERROR_OK) {
this->status_set_warning(ESP_LOG_MSG_COMM_FAIL); this->status_set_warning(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
return; return;
} }
this->restart_read_(); this->restart_read_();

View File

@@ -13,7 +13,7 @@ from esphome.const import (
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_WEB_SERVER, CONF_WEB_SERVER,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, CoroPriority, coroutine_with_priority
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
from esphome.cpp_generator import MockObjClass from esphome.cpp_generator import MockObjClass
@@ -345,6 +345,6 @@ async def alarm_control_panel_is_armed_to_code(
return cg.new_Pvariable(condition_id, template_arg, paren) return cg.new_Pvariable(condition_id, template_arg, paren)
@coroutine_with_priority(100.0) @coroutine_with_priority(CoroPriority.CORE)
async def to_code(config): async def to_code(config):
cg.add_global(alarm_control_panel_ns.using) cg.add_global(alarm_control_panel_ns.using)

View File

@@ -1,4 +1,5 @@
import base64 import base64
import logging
from esphome import automation from esphome import automation
from esphome.automation import Condition from esphome.automation import Condition
@@ -13,6 +14,7 @@ from esphome.const import (
CONF_EVENT, CONF_EVENT,
CONF_ID, CONF_ID,
CONF_KEY, CONF_KEY,
CONF_MAX_CONNECTIONS,
CONF_ON_CLIENT_CONNECTED, CONF_ON_CLIENT_CONNECTED,
CONF_ON_CLIENT_DISCONNECTED, CONF_ON_CLIENT_DISCONNECTED,
CONF_PASSWORD, CONF_PASSWORD,
@@ -24,7 +26,10 @@ from esphome.const import (
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_VARIABLES, CONF_VARIABLES,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, CoroPriority, coroutine_with_priority
from esphome.types import ConfigType
_LOGGER = logging.getLogger(__name__)
DOMAIN = "api" DOMAIN = "api"
DEPENDENCIES = ["network"] DEPENDENCIES = ["network"]
@@ -55,6 +60,8 @@ CONF_BATCH_DELAY = "batch_delay"
CONF_CUSTOM_SERVICES = "custom_services" CONF_CUSTOM_SERVICES = "custom_services"
CONF_HOMEASSISTANT_SERVICES = "homeassistant_services" CONF_HOMEASSISTANT_SERVICES = "homeassistant_services"
CONF_HOMEASSISTANT_STATES = "homeassistant_states" CONF_HOMEASSISTANT_STATES = "homeassistant_states"
CONF_LISTEN_BACKLOG = "listen_backlog"
CONF_MAX_SEND_QUEUE = "max_send_queue"
def validate_encryption_key(value): def validate_encryption_key(value):
@@ -101,6 +108,32 @@ def _encryption_schema(config):
return ENCRYPTION_SCHEMA(config) return ENCRYPTION_SCHEMA(config)
def _validate_api_config(config: ConfigType) -> ConfigType:
"""Validate API configuration with mutual exclusivity check and deprecation warning."""
# Check if both password and encryption are configured
has_password = CONF_PASSWORD in config and config[CONF_PASSWORD]
has_encryption = CONF_ENCRYPTION in config
if has_password and has_encryption:
raise cv.Invalid(
"The 'password' and 'encryption' options are mutually exclusive. "
"The API client only supports one authentication method at a time. "
"Please remove one of them. "
"Note: 'password' authentication is deprecated and will be removed in version 2026.1.0. "
"We strongly recommend using 'encryption' instead for better security."
)
# Warn about password deprecation
if has_password:
_LOGGER.warning(
"API 'password' authentication has been deprecated since May 2022 and will be removed in version 2026.1.0. "
"Please migrate to the 'encryption' configuration. "
"See https://esphome.io/components/api.html#configuration-variables"
)
return config
CONFIG_SCHEMA = cv.All( CONFIG_SCHEMA = cv.All(
cv.Schema( cv.Schema(
{ {
@@ -128,13 +161,50 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_ON_CLIENT_DISCONNECTED): automation.validate_automation( cv.Optional(CONF_ON_CLIENT_DISCONNECTED): automation.validate_automation(
single=True single=True
), ),
# Connection limits to prevent memory exhaustion on resource-constrained devices
# Each connection uses ~500-1000 bytes of RAM plus system resources
# Platform defaults based on available RAM and network stack implementation:
cv.SplitDefault(
CONF_LISTEN_BACKLOG,
esp8266=1, # Limited RAM (~40KB free), LWIP raw sockets
esp32=4, # More RAM (520KB), BSD sockets
rp2040=1, # Limited RAM (264KB), LWIP raw sockets like ESP8266
bk72xx=4, # Moderate RAM, BSD-style sockets
rtl87xx=4, # Moderate RAM, BSD-style sockets
host=4, # Abundant resources
ln882x=4, # Moderate RAM
): cv.int_range(min=1, max=10),
cv.SplitDefault(
CONF_MAX_CONNECTIONS,
esp8266=4, # ~40KB free RAM, each connection uses ~500-1000 bytes
esp32=8, # 520KB RAM available
rp2040=4, # 264KB RAM but LWIP constraints
bk72xx=8, # Moderate RAM
rtl87xx=8, # Moderate RAM
host=8, # Abundant resources
ln882x=8, # Moderate RAM
): cv.int_range(min=1, max=20),
# Maximum queued send buffers per connection before dropping connection
# Each buffer uses ~8-12 bytes overhead plus actual message size
# Platform defaults based on available RAM and typical message rates:
cv.SplitDefault(
CONF_MAX_SEND_QUEUE,
esp8266=5, # Limited RAM, need to fail fast
esp32=8, # More RAM, can buffer more
rp2040=5, # Limited RAM
bk72xx=8, # Moderate RAM
rtl87xx=8, # Moderate RAM
host=16, # Abundant resources
ln882x=8, # Moderate RAM
): cv.int_range(min=1, max=64),
} }
).extend(cv.COMPONENT_SCHEMA), ).extend(cv.COMPONENT_SCHEMA),
cv.rename_key(CONF_SERVICES, CONF_ACTIONS), cv.rename_key(CONF_SERVICES, CONF_ACTIONS),
_validate_api_config,
) )
@coroutine_with_priority(40.0) @coroutine_with_priority(CoroPriority.WEB)
async def to_code(config): async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config) await cg.register_component(var, config)
@@ -145,6 +215,11 @@ async def to_code(config):
cg.add(var.set_password(config[CONF_PASSWORD])) cg.add(var.set_password(config[CONF_PASSWORD]))
cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT])) cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
cg.add(var.set_batch_delay(config[CONF_BATCH_DELAY])) cg.add(var.set_batch_delay(config[CONF_BATCH_DELAY]))
if CONF_LISTEN_BACKLOG in config:
cg.add(var.set_listen_backlog(config[CONF_LISTEN_BACKLOG]))
if CONF_MAX_CONNECTIONS in config:
cg.add(var.set_max_connections(config[CONF_MAX_CONNECTIONS]))
cg.add_define("API_MAX_SEND_QUEUE", config[CONF_MAX_SEND_QUEUE])
# Set USE_API_SERVICES if any services are enabled # Set USE_API_SERVICES if any services are enabled
if config.get(CONF_ACTIONS) or config[CONF_CUSTOM_SERVICES]: if config.get(CONF_ACTIONS) or config[CONF_CUSTOM_SERVICES]:
@@ -193,6 +268,7 @@ async def to_code(config):
if key := encryption_config.get(CONF_KEY): if key := encryption_config.get(CONF_KEY):
decoded = base64.b64decode(key) decoded = base64.b64decode(key)
cg.add(var.set_noise_psk(list(decoded))) cg.add(var.set_noise_psk(list(decoded)))
cg.add_define("USE_API_NOISE_PSK_FROM_YAML")
else: else:
# No key provided, but encryption desired # No key provided, but encryption desired
# This will allow a plaintext client to provide a noise key, # This will allow a plaintext client to provide a noise key,

View File

@@ -7,7 +7,7 @@ service APIConnection {
option (needs_setup_connection) = false; option (needs_setup_connection) = false;
option (needs_authentication) = false; option (needs_authentication) = false;
} }
rpc connect (ConnectRequest) returns (ConnectResponse) { rpc authenticate (AuthenticationRequest) returns (AuthenticationResponse) {
option (needs_setup_connection) = false; option (needs_setup_connection) = false;
option (needs_authentication) = false; option (needs_authentication) = false;
} }
@@ -27,9 +27,6 @@ service APIConnection {
rpc subscribe_logs (SubscribeLogsRequest) returns (void) {} rpc subscribe_logs (SubscribeLogsRequest) returns (void) {}
rpc subscribe_homeassistant_services (SubscribeHomeassistantServicesRequest) returns (void) {} rpc subscribe_homeassistant_services (SubscribeHomeassistantServicesRequest) returns (void) {}
rpc subscribe_home_assistant_states (SubscribeHomeAssistantStatesRequest) returns (void) {} rpc subscribe_home_assistant_states (SubscribeHomeAssistantStatesRequest) returns (void) {}
rpc get_time (GetTimeRequest) returns (GetTimeResponse) {
option (needs_authentication) = false;
}
rpc execute_service (ExecuteServiceRequest) returns (void) {} rpc execute_service (ExecuteServiceRequest) returns (void) {}
rpc noise_encryption_set_key (NoiseEncryptionSetKeyRequest) returns (NoiseEncryptionSetKeyResponse) {} rpc noise_encryption_set_key (NoiseEncryptionSetKeyRequest) returns (NoiseEncryptionSetKeyResponse) {}
@@ -69,6 +66,9 @@ service APIConnection {
rpc voice_assistant_set_configuration(VoiceAssistantSetConfiguration) returns (void) {} rpc voice_assistant_set_configuration(VoiceAssistantSetConfiguration) returns (void) {}
rpc alarm_control_panel_command (AlarmControlPanelCommandRequest) returns (void) {} rpc alarm_control_panel_command (AlarmControlPanelCommandRequest) returns (void) {}
rpc zwave_proxy_frame(ZWaveProxyFrame) returns (void) {}
rpc zwave_proxy_request(ZWaveProxyRequest) returns (void) {}
} }
@@ -102,7 +102,7 @@ message HelloRequest {
// For example "Home Assistant" // For example "Home Assistant"
// Not strictly necessary to send but nice for debugging // Not strictly necessary to send but nice for debugging
// purposes. // purposes.
string client_info = 1; string client_info = 1 [(pointer_to_buffer) = true];
uint32 api_version_major = 2; uint32 api_version_major = 2;
uint32 api_version_minor = 3; uint32 api_version_minor = 3;
} }
@@ -132,21 +132,23 @@ message HelloResponse {
// Message sent at the beginning of each connection to authenticate the client // Message sent at the beginning of each connection to authenticate the client
// Can only be sent by the client and only at the beginning of the connection // Can only be sent by the client and only at the beginning of the connection
message ConnectRequest { message AuthenticationRequest {
option (id) = 3; option (id) = 3;
option (source) = SOURCE_CLIENT; option (source) = SOURCE_CLIENT;
option (no_delay) = true; option (no_delay) = true;
option (ifdef) = "USE_API_PASSWORD";
// The password to log in with // The password to log in with
string password = 1; string password = 1 [(pointer_to_buffer) = true];
} }
// Confirmation of successful connection. After this the connection is available for all traffic. // Confirmation of successful connection. After this the connection is available for all traffic.
// Can only be sent by the server and only at the beginning of the connection // Can only be sent by the server and only at the beginning of the connection
message ConnectResponse { message AuthenticationResponse {
option (id) = 4; option (id) = 4;
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (no_delay) = true; option (no_delay) = true;
option (ifdef) = "USE_API_PASSWORD";
bool invalid_password = 1; bool invalid_password = 1;
} }
@@ -255,6 +257,10 @@ message DeviceInfoResponse {
// Top-level area info to phase out suggested_area // Top-level area info to phase out suggested_area
AreaInfo area = 22 [(field_ifdef) = "USE_AREAS"]; AreaInfo area = 22 [(field_ifdef) = "USE_AREAS"];
// Indicates if Z-Wave proxy support is available and features supported
uint32 zwave_proxy_feature_flags = 23 [(field_ifdef) = "USE_ZWAVE_PROXY"];
uint32 zwave_home_id = 24 [(field_ifdef) = "USE_ZWAVE_PROXY"];
} }
message ListEntitiesRequest { message ListEntitiesRequest {
@@ -763,7 +769,7 @@ message HomeassistantServiceMap {
string value = 2 [(no_zero_copy) = true]; string value = 2 [(no_zero_copy) = true];
} }
message HomeassistantServiceResponse { message HomeassistantActionRequest {
option (id) = 35; option (id) = 35;
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (no_delay) = true; option (no_delay) = true;
@@ -809,15 +815,16 @@ message HomeAssistantStateResponse {
// ==================== IMPORT TIME ==================== // ==================== IMPORT TIME ====================
message GetTimeRequest { message GetTimeRequest {
option (id) = 36; option (id) = 36;
option (source) = SOURCE_BOTH; option (source) = SOURCE_SERVER;
} }
message GetTimeResponse { message GetTimeResponse {
option (id) = 37; option (id) = 37;
option (source) = SOURCE_BOTH; option (source) = SOURCE_CLIENT;
option (no_delay) = true; option (no_delay) = true;
fixed32 epoch_seconds = 1; fixed32 epoch_seconds = 1;
string timezone = 2 [(pointer_to_buffer) = true];
} }
// ==================== USER-DEFINES SERVICES ==================== // ==================== USER-DEFINES SERVICES ====================
@@ -1458,7 +1465,7 @@ message BluetoothDeviceRequest {
uint64 address = 1; uint64 address = 1;
BluetoothDeviceRequestType request_type = 2; BluetoothDeviceRequestType request_type = 2;
bool has_address_type = 3; bool has_address_type = 3; // Deprecated, should be removed in 2027.8 - https://github.com/esphome/esphome/pull/10318
uint32 address_type = 4; uint32 address_type = 4;
} }
@@ -1564,7 +1571,7 @@ message BluetoothGATTWriteRequest {
uint32 handle = 2; uint32 handle = 2;
bool response = 3; bool response = 3;
bytes data = 4; bytes data = 4 [(pointer_to_buffer) = true];
} }
message BluetoothGATTReadDescriptorRequest { message BluetoothGATTReadDescriptorRequest {
@@ -1584,7 +1591,7 @@ message BluetoothGATTWriteDescriptorRequest {
uint64 address = 1; uint64 address = 1;
uint32 handle = 2; uint32 handle = 2;
bytes data = 3; bytes data = 3 [(pointer_to_buffer) = true];
} }
message BluetoothGATTNotifyRequest { message BluetoothGATTNotifyRequest {
@@ -1712,6 +1719,7 @@ message BluetoothScannerStateResponse {
BluetoothScannerState state = 1; BluetoothScannerState state = 1;
BluetoothScannerMode mode = 2; BluetoothScannerMode mode = 2;
BluetoothScannerMode configured_mode = 3;
} }
message BluetoothScannerSetModeRequest { message BluetoothScannerSetModeRequest {
@@ -1857,10 +1865,22 @@ message VoiceAssistantWakeWord {
repeated string trained_languages = 3; repeated string trained_languages = 3;
} }
message VoiceAssistantExternalWakeWord {
string id = 1;
string wake_word = 2;
repeated string trained_languages = 3;
string model_type = 4;
uint32 model_size = 5;
string model_hash = 6;
string url = 7;
}
message VoiceAssistantConfigurationRequest { message VoiceAssistantConfigurationRequest {
option (id) = 121; option (id) = 121;
option (source) = SOURCE_CLIENT; option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_VOICE_ASSISTANT"; option (ifdef) = "USE_VOICE_ASSISTANT";
repeated VoiceAssistantExternalWakeWord external_wake_words = 1;
} }
message VoiceAssistantConfigurationResponse { message VoiceAssistantConfigurationResponse {
@@ -2275,3 +2295,28 @@ message UpdateCommandRequest {
UpdateCommand command = 2; UpdateCommand command = 2;
uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"]; uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"];
} }
// ==================== Z-WAVE ====================
message ZWaveProxyFrame {
option (id) = 128;
option (source) = SOURCE_BOTH;
option (ifdef) = "USE_ZWAVE_PROXY";
option (no_delay) = true;
bytes data = 1 [(pointer_to_buffer) = true];
}
enum ZWaveProxyRequestType {
ZWAVE_PROXY_REQUEST_TYPE_SUBSCRIBE = 0;
ZWAVE_PROXY_REQUEST_TYPE_UNSUBSCRIBE = 1;
ZWAVE_PROXY_REQUEST_TYPE_HOME_ID_CHANGE = 2;
}
message ZWaveProxyRequest {
option (id) = 129;
option (source) = SOURCE_BOTH;
option (ifdef) = "USE_ZWAVE_PROXY";
ZWaveProxyRequestType type = 1;
bytes data = 2 [(pointer_to_buffer) = true];
}

View File

@@ -30,6 +30,9 @@
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
#include "esphome/components/voice_assistant/voice_assistant.h" #include "esphome/components/voice_assistant/voice_assistant.h"
#endif #endif
#ifdef USE_ZWAVE_PROXY
#include "esphome/components/zwave_proxy/zwave_proxy.h"
#endif
namespace esphome::api { namespace esphome::api {
@@ -42,6 +45,8 @@ static constexpr uint8_t MAX_PING_RETRIES = 60;
static constexpr uint16_t PING_RETRY_INTERVAL = 1000; static constexpr uint16_t PING_RETRY_INTERVAL = 1000;
static constexpr uint32_t KEEPALIVE_DISCONNECT_TIMEOUT = (KEEPALIVE_TIMEOUT_MS * 5) / 2; static constexpr uint32_t KEEPALIVE_DISCONNECT_TIMEOUT = (KEEPALIVE_TIMEOUT_MS * 5) / 2;
static constexpr auto ESPHOME_VERSION_REF = StringRef::from_lit(ESPHOME_VERSION);
static const char *const TAG = "api.connection"; static const char *const TAG = "api.connection";
#ifdef USE_CAMERA #ifdef USE_CAMERA
static const int CAMERA_STOP_STREAM = 5000; static const int CAMERA_STOP_STREAM = 5000;
@@ -111,8 +116,7 @@ void APIConnection::start() {
APIError err = this->helper_->init(); APIError err = this->helper_->init();
if (err != APIError::OK) { if (err != APIError::OK) {
on_fatal_error(); this->fatal_error_with_log_(LOG_STR("Helper init failed"), err);
this->log_warning_("Helper init failed", err);
return; return;
} }
this->client_info_.peername = helper_->getpeername(); this->client_info_.peername = helper_->getpeername();
@@ -142,8 +146,7 @@ void APIConnection::loop() {
APIError err = this->helper_->loop(); APIError err = this->helper_->loop();
if (err != APIError::OK) { if (err != APIError::OK) {
on_fatal_error(); this->fatal_error_with_log_(LOG_STR("Socket operation failed"), err);
this->log_socket_operation_failed_(err);
return; return;
} }
@@ -158,17 +161,13 @@ void APIConnection::loop() {
// No more data available // No more data available
break; break;
} else if (err != APIError::OK) { } else if (err != APIError::OK) {
on_fatal_error(); this->fatal_error_with_log_(LOG_STR("Reading failed"), err);
this->log_warning_("Reading failed", err);
return; return;
} else { } else {
this->last_traffic_ = now; this->last_traffic_ = now;
// read a packet // read a packet
if (buffer.data_len > 0) { this->read_message(buffer.data_len, buffer.type,
this->read_message(buffer.data_len, buffer.type, &buffer.container[buffer.data_offset]); buffer.data_len > 0 ? &buffer.container[buffer.data_offset] : nullptr);
} else {
this->read_message(0, buffer.type, nullptr);
}
if (this->flags_.remove) if (this->flags_.remove)
return; return;
} }
@@ -200,7 +199,8 @@ void APIConnection::loop() {
// Disconnect if not responded within 2.5*keepalive // Disconnect if not responded within 2.5*keepalive
if (now - this->last_traffic_ > KEEPALIVE_DISCONNECT_TIMEOUT) { if (now - this->last_traffic_ > KEEPALIVE_DISCONNECT_TIMEOUT) {
on_fatal_error(); on_fatal_error();
ESP_LOGW(TAG, "%s is unresponsive; disconnecting", this->get_client_combined_info().c_str()); ESP_LOGW(TAG, "%s (%s) is unresponsive; disconnecting", this->client_info_.name.c_str(),
this->client_info_.peername.c_str());
} }
} else if (now - this->last_traffic_ > KEEPALIVE_TIMEOUT_MS && !this->flags_.remove) { } else if (now - this->last_traffic_ > KEEPALIVE_TIMEOUT_MS && !this->flags_.remove) {
// Only send ping if we're not disconnecting // Only send ping if we're not disconnecting
@@ -250,7 +250,7 @@ bool APIConnection::send_disconnect_response(const DisconnectRequest &msg) {
// remote initiated disconnect_client // remote initiated disconnect_client
// don't close yet, we still need to send the disconnect response // don't close yet, we still need to send the disconnect response
// close will happen on next loop // close will happen on next loop
ESP_LOGD(TAG, "%s disconnected", this->get_client_combined_info().c_str()); ESP_LOGD(TAG, "%s (%s) disconnected", this->client_info_.name.c_str(), this->client_info_.peername.c_str());
this->flags_.next_close = true; this->flags_.next_close = true;
DisconnectResponse resp; DisconnectResponse resp;
return this->send_message(resp, DisconnectResponse::MESSAGE_TYPE); return this->send_message(resp, DisconnectResponse::MESSAGE_TYPE);
@@ -1070,17 +1070,23 @@ void APIConnection::camera_image(const CameraImageRequest &msg) {
#ifdef USE_HOMEASSISTANT_TIME #ifdef USE_HOMEASSISTANT_TIME
void APIConnection::on_get_time_response(const GetTimeResponse &value) { void APIConnection::on_get_time_response(const GetTimeResponse &value) {
if (homeassistant::global_homeassistant_time != nullptr) if (homeassistant::global_homeassistant_time != nullptr) {
homeassistant::global_homeassistant_time->set_epoch_time(value.epoch_seconds); homeassistant::global_homeassistant_time->set_epoch_time(value.epoch_seconds);
#ifdef USE_TIME_TIMEZONE
if (value.timezone_len > 0) {
const std::string &current_tz = homeassistant::global_homeassistant_time->get_timezone();
// Compare without allocating a string
if (current_tz.length() != value.timezone_len ||
memcmp(current_tz.c_str(), value.timezone, value.timezone_len) != 0) {
homeassistant::global_homeassistant_time->set_timezone(
std::string(reinterpret_cast<const char *>(value.timezone), value.timezone_len));
}
}
#endif
}
} }
#endif #endif
bool APIConnection::send_get_time_response(const GetTimeRequest &msg) {
GetTimeResponse resp;
resp.epoch_seconds = ::time(nullptr);
return this->send_message(resp, GetTimeResponse::MESSAGE_TYPE);
}
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
void APIConnection::subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) { void APIConnection::subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) {
bluetooth_proxy::global_bluetooth_proxy->subscribe_api_connection(this, msg.flags); bluetooth_proxy::global_bluetooth_proxy->subscribe_api_connection(this, msg.flags);
@@ -1191,6 +1197,23 @@ bool APIConnection::send_voice_assistant_get_configuration_response(const VoiceA
resp_wake_word.trained_languages.push_back(lang); resp_wake_word.trained_languages.push_back(lang);
} }
} }
// Filter external wake words
for (auto &wake_word : msg.external_wake_words) {
if (wake_word.model_type != "micro") {
// microWakeWord only
continue;
}
resp.available_wake_words.emplace_back();
auto &resp_wake_word = resp.available_wake_words.back();
resp_wake_word.set_id(StringRef(wake_word.id));
resp_wake_word.set_wake_word(StringRef(wake_word.wake_word));
for (const auto &lang : wake_word.trained_languages) {
resp_wake_word.trained_languages.push_back(lang);
}
}
resp.active_wake_words = &config.active_wake_words; resp.active_wake_words = &config.active_wake_words;
resp.max_active_wake_words = config.max_active_wake_words; resp.max_active_wake_words = config.max_active_wake_words;
return this->send_message(resp, VoiceAssistantConfigurationResponse::MESSAGE_TYPE); return this->send_message(resp, VoiceAssistantConfigurationResponse::MESSAGE_TYPE);
@@ -1201,7 +1224,16 @@ void APIConnection::voice_assistant_set_configuration(const VoiceAssistantSetCon
voice_assistant::global_voice_assistant->on_set_configuration(msg.active_wake_words); voice_assistant::global_voice_assistant->on_set_configuration(msg.active_wake_words);
} }
} }
#endif
#ifdef USE_ZWAVE_PROXY
void APIConnection::zwave_proxy_frame(const ZWaveProxyFrame &msg) {
zwave_proxy::global_zwave_proxy->send_frame(msg.data, msg.data_len);
}
void APIConnection::zwave_proxy_request(const ZWaveProxyRequest &msg) {
zwave_proxy::global_zwave_proxy->zwave_proxy_request(this, msg.type);
}
#endif #endif
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
@@ -1348,7 +1380,7 @@ void APIConnection::complete_authentication_() {
} }
this->flags_.connection_state = static_cast<uint8_t>(ConnectionState::AUTHENTICATED); this->flags_.connection_state = static_cast<uint8_t>(ConnectionState::AUTHENTICATED);
ESP_LOGD(TAG, "%s connected", this->get_client_combined_info().c_str()); ESP_LOGD(TAG, "%s (%s) connected", this->client_info_.name.c_str(), this->client_info_.peername.c_str());
#ifdef USE_API_CLIENT_CONNECTED_TRIGGER #ifdef USE_API_CLIENT_CONNECTED_TRIGGER
this->parent_->get_client_connected_trigger()->trigger(this->client_info_.name, this->client_info_.peername); this->parent_->get_client_connected_trigger()->trigger(this->client_info_.name, this->client_info_.peername);
#endif #endif
@@ -1357,10 +1389,15 @@ void APIConnection::complete_authentication_() {
this->send_time_request(); this->send_time_request();
} }
#endif #endif
#ifdef USE_ZWAVE_PROXY
if (zwave_proxy::global_zwave_proxy != nullptr) {
zwave_proxy::global_zwave_proxy->api_connection_authenticated(this);
}
#endif
} }
bool APIConnection::send_hello_response(const HelloRequest &msg) { bool APIConnection::send_hello_response(const HelloRequest &msg) {
this->client_info_.name = msg.client_info; this->client_info_.name.assign(reinterpret_cast<const char *>(msg.client_info), msg.client_info_len);
this->client_info_.peername = this->helper_->getpeername(); this->client_info_.peername = this->helper_->getpeername();
this->client_api_version_major_ = msg.api_version_major; this->client_api_version_major_ = msg.api_version_major;
this->client_api_version_minor_ = msg.api_version_minor; this->client_api_version_minor_ = msg.api_version_minor;
@@ -1370,9 +1407,8 @@ bool APIConnection::send_hello_response(const HelloRequest &msg) {
HelloResponse resp; HelloResponse resp;
resp.api_version_major = 1; resp.api_version_major = 1;
resp.api_version_minor = 12; resp.api_version_minor = 12;
// Temporary string for concatenation - will be valid during send_message call // Send only the version string - the client only logs this for debugging and doesn't use it otherwise
std::string server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")"; resp.set_server_info(ESPHOME_VERSION_REF);
resp.set_server_info(StringRef(server_info));
resp.set_name(StringRef(App.get_name())); resp.set_name(StringRef(App.get_name()));
#ifdef USE_API_PASSWORD #ifdef USE_API_PASSWORD
@@ -1385,20 +1421,17 @@ bool APIConnection::send_hello_response(const HelloRequest &msg) {
return this->send_message(resp, HelloResponse::MESSAGE_TYPE); return this->send_message(resp, HelloResponse::MESSAGE_TYPE);
} }
bool APIConnection::send_connect_response(const ConnectRequest &msg) {
bool correct = true;
#ifdef USE_API_PASSWORD #ifdef USE_API_PASSWORD
correct = this->parent_->check_password(msg.password); bool APIConnection::send_authenticate_response(const AuthenticationRequest &msg) {
#endif AuthenticationResponse resp;
ConnectResponse resp;
// bool invalid_password = 1; // bool invalid_password = 1;
resp.invalid_password = !correct; resp.invalid_password = !this->parent_->check_password(msg.password, msg.password_len);
if (correct) { if (!resp.invalid_password) {
this->complete_authentication_(); this->complete_authentication_();
} }
return this->send_message(resp, ConnectResponse::MESSAGE_TYPE); return this->send_message(resp, AuthenticationResponse::MESSAGE_TYPE);
} }
#endif // USE_API_PASSWORD
bool APIConnection::send_ping_response(const PingRequest &msg) { bool APIConnection::send_ping_response(const PingRequest &msg) {
PingResponse resp; PingResponse resp;
@@ -1419,8 +1452,6 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
std::string mac_address = get_mac_address_pretty(); std::string mac_address = get_mac_address_pretty();
resp.set_mac_address(StringRef(mac_address)); resp.set_mac_address(StringRef(mac_address));
// Compile-time StringRef constants
static constexpr auto ESPHOME_VERSION_REF = StringRef::from_lit(ESPHOME_VERSION);
resp.set_esphome_version(ESPHOME_VERSION_REF); resp.set_esphome_version(ESPHOME_VERSION_REF);
resp.set_compilation_time(App.get_compilation_time_ref()); resp.set_compilation_time(App.get_compilation_time_ref());
@@ -1464,6 +1495,10 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
resp.voice_assistant_feature_flags = voice_assistant::global_voice_assistant->get_feature_flags(); resp.voice_assistant_feature_flags = voice_assistant::global_voice_assistant->get_feature_flags();
#endif #endif
#ifdef USE_ZWAVE_PROXY
resp.zwave_proxy_feature_flags = zwave_proxy::global_zwave_proxy->get_feature_flags();
resp.zwave_home_id = zwave_proxy::global_zwave_proxy->get_home_id();
#endif
#ifdef USE_API_NOISE #ifdef USE_API_NOISE
resp.api_encryption_supported = true; resp.api_encryption_supported = true;
#endif #endif
@@ -1544,8 +1579,7 @@ bool APIConnection::try_to_clear_buffer(bool log_out_of_space) {
delay(0); delay(0);
APIError err = this->helper_->loop(); APIError err = this->helper_->loop();
if (err != APIError::OK) { if (err != APIError::OK) {
on_fatal_error(); this->fatal_error_with_log_(LOG_STR("Socket operation failed"), err);
this->log_socket_operation_failed_(err);
return false; return false;
} }
if (this->helper_->can_write_without_blocking()) if (this->helper_->can_write_without_blocking())
@@ -1564,8 +1598,7 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) {
if (err == APIError::WOULD_BLOCK) if (err == APIError::WOULD_BLOCK)
return false; return false;
if (err != APIError::OK) { if (err != APIError::OK) {
on_fatal_error(); this->fatal_error_with_log_(LOG_STR("Packet write failed"), err);
this->log_warning_("Packet write failed", err);
return false; return false;
} }
// Do not set last_traffic_ on send // Do not set last_traffic_ on send
@@ -1574,12 +1607,12 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) {
#ifdef USE_API_PASSWORD #ifdef USE_API_PASSWORD
void APIConnection::on_unauthenticated_access() { void APIConnection::on_unauthenticated_access() {
this->on_fatal_error(); this->on_fatal_error();
ESP_LOGD(TAG, "%s access without authentication", this->get_client_combined_info().c_str()); ESP_LOGD(TAG, "%s (%s) no authentication", this->client_info_.name.c_str(), this->client_info_.peername.c_str());
} }
#endif #endif
void APIConnection::on_no_setup_connection() { void APIConnection::on_no_setup_connection() {
this->on_fatal_error(); this->on_fatal_error();
ESP_LOGD(TAG, "%s access without full connection", this->get_client_combined_info().c_str()); ESP_LOGD(TAG, "%s (%s) no connection setup", this->client_info_.name.c_str(), this->client_info_.peername.c_str());
} }
void APIConnection::on_fatal_error() { void APIConnection::on_fatal_error() {
this->helper_->close(); this->helper_->close();
@@ -1751,8 +1784,7 @@ void APIConnection::process_batch_() {
APIError err = this->helper_->write_protobuf_packets(ProtoWriteBuffer{&shared_buf}, APIError err = this->helper_->write_protobuf_packets(ProtoWriteBuffer{&shared_buf},
std::span<const PacketInfo>(packet_info, packet_count)); std::span<const PacketInfo>(packet_info, packet_count));
if (err != APIError::OK && err != APIError::WOULD_BLOCK) { if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
on_fatal_error(); this->fatal_error_with_log_(LOG_STR("Batch write failed"), err);
this->log_warning_("Batch write failed", err);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
@@ -1830,11 +1862,10 @@ void APIConnection::process_state_subscriptions_() {
} }
#endif // USE_API_HOMEASSISTANT_STATES #endif // USE_API_HOMEASSISTANT_STATES
void APIConnection::log_warning_(const char *message, APIError err) { void APIConnection::log_warning_(const LogString *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); ESP_LOGW(TAG, "%s (%s): %s %s errno=%d", this->client_info_.name.c_str(), this->client_info_.peername.c_str(),
LOG_STR_ARG(message), LOG_STR_ARG(api_error_to_logstr(err)), errno);
} }
void APIConnection::log_socket_operation_failed_(APIError err) { this->log_warning_("Socket operation failed", err); }
} // namespace esphome::api } // namespace esphome::api
#endif #endif

View File

@@ -10,8 +10,8 @@
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/entity_base.h" #include "esphome/core/entity_base.h"
#include <vector>
#include <functional> #include <functional>
#include <vector>
namespace esphome::api { namespace esphome::api {
@@ -19,14 +19,6 @@ namespace esphome::api {
struct ClientInfo { struct ClientInfo {
std::string name; // Client name from Hello message std::string name; // Client name from Hello message
std::string peername; // IP:port from socket std::string peername; // IP:port from socket
std::string get_combined_info() const {
if (name == peername) {
// Before Hello message, both are the same
return name;
}
return name + " (" + peername + ")";
}
}; };
// Keepalive timeout in milliseconds // Keepalive timeout in milliseconds
@@ -132,10 +124,10 @@ class APIConnection final : public APIServerConnection {
#endif #endif
bool try_send_log_message(int level, const char *tag, const char *line, size_t message_len); bool try_send_log_message(int level, const char *tag, const char *line, size_t message_len);
#ifdef USE_API_HOMEASSISTANT_SERVICES #ifdef USE_API_HOMEASSISTANT_SERVICES
void send_homeassistant_service_call(const HomeassistantServiceResponse &call) { void send_homeassistant_action(const HomeassistantActionRequest &call) {
if (!this->flags_.service_call_subscription) if (!this->flags_.service_call_subscription)
return; return;
this->send_message(call, HomeassistantServiceResponse::MESSAGE_TYPE); this->send_message(call, HomeassistantActionRequest::MESSAGE_TYPE);
} }
#endif #endif
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
@@ -171,6 +163,11 @@ class APIConnection final : public APIServerConnection {
void voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) override; void voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) override;
#endif #endif
#ifdef USE_ZWAVE_PROXY
void zwave_proxy_frame(const ZWaveProxyFrame &msg) override;
void zwave_proxy_request(const ZWaveProxyRequest &msg) override;
#endif
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
bool send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel); bool send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel);
void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) override; void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) override;
@@ -197,7 +194,9 @@ class APIConnection final : public APIServerConnection {
void on_get_time_response(const GetTimeResponse &value) override; void on_get_time_response(const GetTimeResponse &value) override;
#endif #endif
bool send_hello_response(const HelloRequest &msg) override; bool send_hello_response(const HelloRequest &msg) override;
bool send_connect_response(const ConnectRequest &msg) override; #ifdef USE_API_PASSWORD
bool send_authenticate_response(const AuthenticationRequest &msg) override;
#endif
bool send_disconnect_response(const DisconnectRequest &msg) override; bool send_disconnect_response(const DisconnectRequest &msg) override;
bool send_ping_response(const PingRequest &msg) override; bool send_ping_response(const PingRequest &msg) override;
bool send_device_info_response(const DeviceInfoRequest &msg) override; bool send_device_info_response(const DeviceInfoRequest &msg) override;
@@ -219,7 +218,6 @@ class APIConnection final : public APIServerConnection {
#ifdef USE_API_HOMEASSISTANT_STATES #ifdef USE_API_HOMEASSISTANT_STATES
void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) override; void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) override;
#endif #endif
bool send_get_time_response(const GetTimeRequest &msg) override;
#ifdef USE_API_SERVICES #ifdef USE_API_SERVICES
void execute_service(const ExecuteServiceRequest &msg) override; void execute_service(const ExecuteServiceRequest &msg) override;
#endif #endif
@@ -272,7 +270,8 @@ class APIConnection final : public APIServerConnection {
bool try_to_clear_buffer(bool log_out_of_space); bool try_to_clear_buffer(bool log_out_of_space);
bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) override; bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) override;
std::string get_client_combined_info() const { return this->client_info_.get_combined_info(); } const std::string &get_name() const { return this->client_info_.name; }
const std::string &get_peername() const { return this->client_info_.peername; }
protected: protected:
// Helper function to handle authentication completion // Helper function to handle authentication completion
@@ -732,9 +731,12 @@ class APIConnection final : public APIServerConnection {
} }
// Helper function to log API errors with errno // Helper function to log API errors with errno
void log_warning_(const char *message, APIError err); void log_warning_(const LogString *message, APIError err);
// Specific helper for duplicated error message // Helper to handle fatal errors with logging
void log_socket_operation_failed_(APIError err); inline void fatal_error_with_log_(const LogString *message, APIError err) {
this->on_fatal_error();
this->log_warning_(message, err);
}
}; };
} // namespace esphome::api } // namespace esphome::api

View File

@@ -13,7 +13,8 @@ namespace esphome::api {
static const char *const TAG = "api.frame_helper"; static const char *const TAG = "api.frame_helper";
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, this->client_info_->get_combined_info().c_str(), ##__VA_ARGS__) #define HELPER_LOG(msg, ...) \
ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), this->client_info_->peername.c_str(), ##__VA_ARGS__)
#ifdef HELPER_LOG_PACKETS #ifdef HELPER_LOG_PACKETS
#define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str()) #define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str())
@@ -23,64 +24,64 @@ static const char *const TAG = "api.frame_helper";
#define LOG_PACKET_SENDING(data, len) ((void) 0) #define LOG_PACKET_SENDING(data, len) ((void) 0)
#endif #endif
const char *api_error_to_str(APIError err) { const LogString *api_error_to_logstr(APIError err) {
// not using switch to ensure compiler doesn't try to build a big table out of it // not using switch to ensure compiler doesn't try to build a big table out of it
if (err == APIError::OK) { if (err == APIError::OK) {
return "OK"; return LOG_STR("OK");
} else if (err == APIError::WOULD_BLOCK) { } else if (err == APIError::WOULD_BLOCK) {
return "WOULD_BLOCK"; return LOG_STR("WOULD_BLOCK");
} else if (err == APIError::BAD_INDICATOR) { } else if (err == APIError::BAD_INDICATOR) {
return "BAD_INDICATOR"; return LOG_STR("BAD_INDICATOR");
} else if (err == APIError::BAD_DATA_PACKET) { } else if (err == APIError::BAD_DATA_PACKET) {
return "BAD_DATA_PACKET"; return LOG_STR("BAD_DATA_PACKET");
} else if (err == APIError::TCP_NODELAY_FAILED) { } else if (err == APIError::TCP_NODELAY_FAILED) {
return "TCP_NODELAY_FAILED"; return LOG_STR("TCP_NODELAY_FAILED");
} else if (err == APIError::TCP_NONBLOCKING_FAILED) { } else if (err == APIError::TCP_NONBLOCKING_FAILED) {
return "TCP_NONBLOCKING_FAILED"; return LOG_STR("TCP_NONBLOCKING_FAILED");
} else if (err == APIError::CLOSE_FAILED) { } else if (err == APIError::CLOSE_FAILED) {
return "CLOSE_FAILED"; return LOG_STR("CLOSE_FAILED");
} else if (err == APIError::SHUTDOWN_FAILED) { } else if (err == APIError::SHUTDOWN_FAILED) {
return "SHUTDOWN_FAILED"; return LOG_STR("SHUTDOWN_FAILED");
} else if (err == APIError::BAD_STATE) { } else if (err == APIError::BAD_STATE) {
return "BAD_STATE"; return LOG_STR("BAD_STATE");
} else if (err == APIError::BAD_ARG) { } else if (err == APIError::BAD_ARG) {
return "BAD_ARG"; return LOG_STR("BAD_ARG");
} else if (err == APIError::SOCKET_READ_FAILED) { } else if (err == APIError::SOCKET_READ_FAILED) {
return "SOCKET_READ_FAILED"; return LOG_STR("SOCKET_READ_FAILED");
} else if (err == APIError::SOCKET_WRITE_FAILED) { } else if (err == APIError::SOCKET_WRITE_FAILED) {
return "SOCKET_WRITE_FAILED"; return LOG_STR("SOCKET_WRITE_FAILED");
} else if (err == APIError::OUT_OF_MEMORY) { } else if (err == APIError::OUT_OF_MEMORY) {
return "OUT_OF_MEMORY"; return LOG_STR("OUT_OF_MEMORY");
} else if (err == APIError::CONNECTION_CLOSED) { } else if (err == APIError::CONNECTION_CLOSED) {
return "CONNECTION_CLOSED"; return LOG_STR("CONNECTION_CLOSED");
} }
#ifdef USE_API_NOISE #ifdef USE_API_NOISE
else if (err == APIError::BAD_HANDSHAKE_PACKET_LEN) { else if (err == APIError::BAD_HANDSHAKE_PACKET_LEN) {
return "BAD_HANDSHAKE_PACKET_LEN"; return LOG_STR("BAD_HANDSHAKE_PACKET_LEN");
} else if (err == APIError::HANDSHAKESTATE_READ_FAILED) { } else if (err == APIError::HANDSHAKESTATE_READ_FAILED) {
return "HANDSHAKESTATE_READ_FAILED"; return LOG_STR("HANDSHAKESTATE_READ_FAILED");
} else if (err == APIError::HANDSHAKESTATE_WRITE_FAILED) { } else if (err == APIError::HANDSHAKESTATE_WRITE_FAILED) {
return "HANDSHAKESTATE_WRITE_FAILED"; return LOG_STR("HANDSHAKESTATE_WRITE_FAILED");
} else if (err == APIError::HANDSHAKESTATE_BAD_STATE) { } else if (err == APIError::HANDSHAKESTATE_BAD_STATE) {
return "HANDSHAKESTATE_BAD_STATE"; return LOG_STR("HANDSHAKESTATE_BAD_STATE");
} else if (err == APIError::CIPHERSTATE_DECRYPT_FAILED) { } else if (err == APIError::CIPHERSTATE_DECRYPT_FAILED) {
return "CIPHERSTATE_DECRYPT_FAILED"; return LOG_STR("CIPHERSTATE_DECRYPT_FAILED");
} else if (err == APIError::CIPHERSTATE_ENCRYPT_FAILED) { } else if (err == APIError::CIPHERSTATE_ENCRYPT_FAILED) {
return "CIPHERSTATE_ENCRYPT_FAILED"; return LOG_STR("CIPHERSTATE_ENCRYPT_FAILED");
} else if (err == APIError::HANDSHAKESTATE_SETUP_FAILED) { } else if (err == APIError::HANDSHAKESTATE_SETUP_FAILED) {
return "HANDSHAKESTATE_SETUP_FAILED"; return LOG_STR("HANDSHAKESTATE_SETUP_FAILED");
} else if (err == APIError::HANDSHAKESTATE_SPLIT_FAILED) { } else if (err == APIError::HANDSHAKESTATE_SPLIT_FAILED) {
return "HANDSHAKESTATE_SPLIT_FAILED"; return LOG_STR("HANDSHAKESTATE_SPLIT_FAILED");
} else if (err == APIError::BAD_HANDSHAKE_ERROR_BYTE) { } else if (err == APIError::BAD_HANDSHAKE_ERROR_BYTE) {
return "BAD_HANDSHAKE_ERROR_BYTE"; return LOG_STR("BAD_HANDSHAKE_ERROR_BYTE");
} }
#endif #endif
return "UNKNOWN"; return LOG_STR("UNKNOWN");
} }
// Default implementation for loop - handles sending buffered data // Default implementation for loop - handles sending buffered data
APIError APIFrameHelper::loop() { APIError APIFrameHelper::loop() {
if (!this->tx_buf_.empty()) { if (this->tx_buf_count_ > 0) {
APIError err = try_send_tx_buf_(); APIError err = try_send_tx_buf_();
if (err != APIError::OK && err != APIError::WOULD_BLOCK) { if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
return err; return err;
@@ -102,9 +103,20 @@ APIError APIFrameHelper::handle_socket_write_error_() {
// Helper method to buffer data from IOVs // Helper method to buffer data from IOVs
void APIFrameHelper::buffer_data_from_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len, void APIFrameHelper::buffer_data_from_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len,
uint16_t offset) { uint16_t offset) {
SendBuffer buffer; // Check if queue is full
buffer.size = total_write_len - offset; if (this->tx_buf_count_ >= API_MAX_SEND_QUEUE) {
buffer.data = std::make_unique<uint8_t[]>(buffer.size); HELPER_LOG("Send queue full (%u buffers), dropping connection", this->tx_buf_count_);
this->state_ = State::FAILED;
return;
}
uint16_t buffer_size = total_write_len - offset;
auto &buffer = this->tx_buf_[this->tx_buf_tail_];
buffer = std::make_unique<SendBuffer>(SendBuffer{
.data = std::make_unique<uint8_t[]>(buffer_size),
.size = buffer_size,
.offset = 0,
});
uint16_t to_skip = offset; uint16_t to_skip = offset;
uint16_t write_pos = 0; uint16_t write_pos = 0;
@@ -117,12 +129,15 @@ void APIFrameHelper::buffer_data_from_iov_(const struct iovec *iov, int iovcnt,
// Include this segment (partially or fully) // Include this segment (partially or fully)
const uint8_t *src = reinterpret_cast<uint8_t *>(iov[i].iov_base) + to_skip; const uint8_t *src = reinterpret_cast<uint8_t *>(iov[i].iov_base) + to_skip;
uint16_t len = static_cast<uint16_t>(iov[i].iov_len) - to_skip; uint16_t len = static_cast<uint16_t>(iov[i].iov_len) - to_skip;
std::memcpy(buffer.data.get() + write_pos, src, len); std::memcpy(buffer->data.get() + write_pos, src, len);
write_pos += len; write_pos += len;
to_skip = 0; to_skip = 0;
} }
} }
this->tx_buf_.push_back(std::move(buffer));
// Update circular buffer tracking
this->tx_buf_tail_ = (this->tx_buf_tail_ + 1) % API_MAX_SEND_QUEUE;
this->tx_buf_count_++;
} }
// This method writes data to socket or buffers it // This method writes data to socket or buffers it
@@ -140,7 +155,7 @@ APIError APIFrameHelper::write_raw_(const struct iovec *iov, int iovcnt, uint16_
#endif #endif
// Try to send any existing buffered data first if there is any // Try to send any existing buffered data first if there is any
if (!this->tx_buf_.empty()) { if (this->tx_buf_count_ > 0) {
APIError send_result = try_send_tx_buf_(); APIError send_result = try_send_tx_buf_();
// If real error occurred (not just WOULD_BLOCK), return it // If real error occurred (not just WOULD_BLOCK), return it
if (send_result != APIError::OK && send_result != APIError::WOULD_BLOCK) { if (send_result != APIError::OK && send_result != APIError::WOULD_BLOCK) {
@@ -149,7 +164,7 @@ APIError APIFrameHelper::write_raw_(const struct iovec *iov, int iovcnt, uint16_
// If there is still data in the buffer, we can't send, buffer // If there is still data in the buffer, we can't send, buffer
// the new data and return // the new data and return
if (!this->tx_buf_.empty()) { if (this->tx_buf_count_ > 0) {
this->buffer_data_from_iov_(iov, iovcnt, total_write_len, 0); this->buffer_data_from_iov_(iov, iovcnt, total_write_len, 0);
return APIError::OK; // Success, data buffered return APIError::OK; // Success, data buffered
} }
@@ -177,32 +192,31 @@ APIError APIFrameHelper::write_raw_(const struct iovec *iov, int iovcnt, uint16_
} }
// Common implementation for trying to send buffered data // Common implementation for trying to send buffered data
// IMPORTANT: Caller MUST ensure tx_buf_ is not empty before calling this method // IMPORTANT: Caller MUST ensure tx_buf_count_ > 0 before calling this method
APIError APIFrameHelper::try_send_tx_buf_() { APIError APIFrameHelper::try_send_tx_buf_() {
// Try to send from tx_buf - we assume it's not empty as it's the caller's responsibility to check // Try to send from tx_buf - we assume it's not empty as it's the caller's responsibility to check
bool tx_buf_empty = false; while (this->tx_buf_count_ > 0) {
while (!tx_buf_empty) {
// Get the first buffer in the queue // Get the first buffer in the queue
SendBuffer &front_buffer = this->tx_buf_.front(); SendBuffer *front_buffer = this->tx_buf_[this->tx_buf_head_].get();
// Try to send the remaining data in this buffer // Try to send the remaining data in this buffer
ssize_t sent = this->socket_->write(front_buffer.current_data(), front_buffer.remaining()); ssize_t sent = this->socket_->write(front_buffer->current_data(), front_buffer->remaining());
if (sent == -1) { if (sent == -1) {
return this->handle_socket_write_error_(); return this->handle_socket_write_error_();
} else if (sent == 0) { } else if (sent == 0) {
// Nothing sent but not an error // Nothing sent but not an error
return APIError::WOULD_BLOCK; return APIError::WOULD_BLOCK;
} else if (static_cast<uint16_t>(sent) < front_buffer.remaining()) { } else if (static_cast<uint16_t>(sent) < front_buffer->remaining()) {
// Partially sent, update offset // Partially sent, update offset
// Cast to ensure no overflow issues with uint16_t // Cast to ensure no overflow issues with uint16_t
front_buffer.offset += static_cast<uint16_t>(sent); front_buffer->offset += static_cast<uint16_t>(sent);
return APIError::WOULD_BLOCK; // Stop processing more buffers if we couldn't send a complete buffer return APIError::WOULD_BLOCK; // Stop processing more buffers if we couldn't send a complete buffer
} else { } else {
// Buffer completely sent, remove it from the queue // Buffer completely sent, remove it from the queue
this->tx_buf_.pop_front(); this->tx_buf_[this->tx_buf_head_].reset();
// Update empty status for the loop condition this->tx_buf_head_ = (this->tx_buf_head_ + 1) % API_MAX_SEND_QUEUE;
tx_buf_empty = this->tx_buf_.empty(); this->tx_buf_count_--;
// Continue loop to try sending the next buffer // Continue loop to try sending the next buffer
} }
} }

View File

@@ -1,7 +1,8 @@
#pragma once #pragma once
#include <array>
#include <cstdint> #include <cstdint>
#include <deque>
#include <limits> #include <limits>
#include <memory>
#include <span> #include <span>
#include <utility> #include <utility>
#include <vector> #include <vector>
@@ -66,7 +67,7 @@ enum class APIError : uint16_t {
#endif #endif
}; };
const char *api_error_to_str(APIError err); const LogString *api_error_to_logstr(APIError err);
class APIFrameHelper { class APIFrameHelper {
public: public:
@@ -79,7 +80,7 @@ class APIFrameHelper {
virtual APIError init() = 0; virtual APIError init() = 0;
virtual APIError loop(); virtual APIError loop();
virtual APIError read_packet(ReadPacketBuffer *buffer) = 0; virtual APIError read_packet(ReadPacketBuffer *buffer) = 0;
bool can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); } bool can_write_without_blocking() { return this->state_ == State::DATA && this->tx_buf_count_ == 0; }
std::string getpeername() { return socket_->getpeername(); } std::string getpeername() { return socket_->getpeername(); }
int getpeername(struct sockaddr *addr, socklen_t *addrlen) { return socket_->getpeername(addr, addrlen); } int getpeername(struct sockaddr *addr, socklen_t *addrlen) { return socket_->getpeername(addr, addrlen); }
APIError close() { APIError close() {
@@ -161,7 +162,7 @@ class APIFrameHelper {
}; };
// Containers (size varies, but typically 12+ bytes on 32-bit) // Containers (size varies, but typically 12+ bytes on 32-bit)
std::deque<SendBuffer> tx_buf_; std::array<std::unique_ptr<SendBuffer>, API_MAX_SEND_QUEUE> tx_buf_;
std::vector<struct iovec> reusable_iovs_; std::vector<struct iovec> reusable_iovs_;
std::vector<uint8_t> rx_buf_; std::vector<uint8_t> rx_buf_;
@@ -174,7 +175,10 @@ class APIFrameHelper {
State state_{State::INITIALIZE}; State state_{State::INITIALIZE};
uint8_t frame_header_padding_{0}; uint8_t frame_header_padding_{0};
uint8_t frame_footer_size_{0}; uint8_t frame_footer_size_{0};
// 5 bytes total, 3 bytes padding uint8_t tx_buf_head_{0};
uint8_t tx_buf_tail_{0};
uint8_t tx_buf_count_{0};
// 8 bytes total, 0 bytes padding
// Common initialization for both plaintext and noise protocols // Common initialization for both plaintext and noise protocols
APIError init_common_(); APIError init_common_();

View File

@@ -10,13 +10,22 @@
#include <cstring> #include <cstring>
#include <cinttypes> #include <cinttypes>
#ifdef USE_ESP8266
#include <pgmspace.h>
#endif
namespace esphome::api { namespace esphome::api {
static const char *const TAG = "api.noise"; static const char *const TAG = "api.noise";
#ifdef USE_ESP8266
static const char PROLOGUE_INIT[] PROGMEM = "NoiseAPIInit";
#else
static const char *const PROLOGUE_INIT = "NoiseAPIInit"; static const char *const PROLOGUE_INIT = "NoiseAPIInit";
#endif
static constexpr size_t PROLOGUE_INIT_LEN = 12; // strlen("NoiseAPIInit") static constexpr size_t PROLOGUE_INIT_LEN = 12; // strlen("NoiseAPIInit")
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, this->client_info_->get_combined_info().c_str(), ##__VA_ARGS__) #define HELPER_LOG(msg, ...) \
ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), this->client_info_->peername.c_str(), ##__VA_ARGS__)
#ifdef HELPER_LOG_PACKETS #ifdef HELPER_LOG_PACKETS
#define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str()) #define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str())
@@ -27,42 +36,42 @@ static constexpr size_t PROLOGUE_INIT_LEN = 12; // strlen("NoiseAPIInit")
#endif #endif
/// Convert a noise error code to a readable error /// Convert a noise error code to a readable error
std::string noise_err_to_str(int err) { const LogString *noise_err_to_logstr(int err) {
if (err == NOISE_ERROR_NO_MEMORY) if (err == NOISE_ERROR_NO_MEMORY)
return "NO_MEMORY"; return LOG_STR("NO_MEMORY");
if (err == NOISE_ERROR_UNKNOWN_ID) if (err == NOISE_ERROR_UNKNOWN_ID)
return "UNKNOWN_ID"; return LOG_STR("UNKNOWN_ID");
if (err == NOISE_ERROR_UNKNOWN_NAME) if (err == NOISE_ERROR_UNKNOWN_NAME)
return "UNKNOWN_NAME"; return LOG_STR("UNKNOWN_NAME");
if (err == NOISE_ERROR_MAC_FAILURE) if (err == NOISE_ERROR_MAC_FAILURE)
return "MAC_FAILURE"; return LOG_STR("MAC_FAILURE");
if (err == NOISE_ERROR_NOT_APPLICABLE) if (err == NOISE_ERROR_NOT_APPLICABLE)
return "NOT_APPLICABLE"; return LOG_STR("NOT_APPLICABLE");
if (err == NOISE_ERROR_SYSTEM) if (err == NOISE_ERROR_SYSTEM)
return "SYSTEM"; return LOG_STR("SYSTEM");
if (err == NOISE_ERROR_REMOTE_KEY_REQUIRED) if (err == NOISE_ERROR_REMOTE_KEY_REQUIRED)
return "REMOTE_KEY_REQUIRED"; return LOG_STR("REMOTE_KEY_REQUIRED");
if (err == NOISE_ERROR_LOCAL_KEY_REQUIRED) if (err == NOISE_ERROR_LOCAL_KEY_REQUIRED)
return "LOCAL_KEY_REQUIRED"; return LOG_STR("LOCAL_KEY_REQUIRED");
if (err == NOISE_ERROR_PSK_REQUIRED) if (err == NOISE_ERROR_PSK_REQUIRED)
return "PSK_REQUIRED"; return LOG_STR("PSK_REQUIRED");
if (err == NOISE_ERROR_INVALID_LENGTH) if (err == NOISE_ERROR_INVALID_LENGTH)
return "INVALID_LENGTH"; return LOG_STR("INVALID_LENGTH");
if (err == NOISE_ERROR_INVALID_PARAM) if (err == NOISE_ERROR_INVALID_PARAM)
return "INVALID_PARAM"; return LOG_STR("INVALID_PARAM");
if (err == NOISE_ERROR_INVALID_STATE) if (err == NOISE_ERROR_INVALID_STATE)
return "INVALID_STATE"; return LOG_STR("INVALID_STATE");
if (err == NOISE_ERROR_INVALID_NONCE) if (err == NOISE_ERROR_INVALID_NONCE)
return "INVALID_NONCE"; return LOG_STR("INVALID_NONCE");
if (err == NOISE_ERROR_INVALID_PRIVATE_KEY) if (err == NOISE_ERROR_INVALID_PRIVATE_KEY)
return "INVALID_PRIVATE_KEY"; return LOG_STR("INVALID_PRIVATE_KEY");
if (err == NOISE_ERROR_INVALID_PUBLIC_KEY) if (err == NOISE_ERROR_INVALID_PUBLIC_KEY)
return "INVALID_PUBLIC_KEY"; return LOG_STR("INVALID_PUBLIC_KEY");
if (err == NOISE_ERROR_INVALID_FORMAT) if (err == NOISE_ERROR_INVALID_FORMAT)
return "INVALID_FORMAT"; return LOG_STR("INVALID_FORMAT");
if (err == NOISE_ERROR_INVALID_SIGNATURE) if (err == NOISE_ERROR_INVALID_SIGNATURE)
return "INVALID_SIGNATURE"; return LOG_STR("INVALID_SIGNATURE");
return to_string(err); return LOG_STR("UNKNOWN");
} }
/// Initialize the frame helper, returns OK if successful. /// Initialize the frame helper, returns OK if successful.
@@ -75,7 +84,11 @@ APIError APINoiseFrameHelper::init() {
// init prologue // init prologue
size_t old_size = prologue_.size(); size_t old_size = prologue_.size();
prologue_.resize(old_size + PROLOGUE_INIT_LEN); prologue_.resize(old_size + PROLOGUE_INIT_LEN);
#ifdef USE_ESP8266
memcpy_P(prologue_.data() + old_size, PROLOGUE_INIT, PROLOGUE_INIT_LEN);
#else
std::memcpy(prologue_.data() + old_size, PROLOGUE_INIT, PROLOGUE_INIT_LEN); std::memcpy(prologue_.data() + old_size, PROLOGUE_INIT, PROLOGUE_INIT_LEN);
#endif
state_ = State::CLIENT_HELLO; state_ = State::CLIENT_HELLO;
return APIError::OK; return APIError::OK;
@@ -83,18 +96,18 @@ APIError APINoiseFrameHelper::init() {
// Helper for handling handshake frame errors // Helper for handling handshake frame errors
APIError APINoiseFrameHelper::handle_handshake_frame_error_(APIError aerr) { APIError APINoiseFrameHelper::handle_handshake_frame_error_(APIError aerr) {
if (aerr == APIError::BAD_INDICATOR) { if (aerr == APIError::BAD_INDICATOR) {
send_explicit_handshake_reject_("Bad indicator byte"); send_explicit_handshake_reject_(LOG_STR("Bad indicator byte"));
} else if (aerr == APIError::BAD_HANDSHAKE_PACKET_LEN) { } else if (aerr == APIError::BAD_HANDSHAKE_PACKET_LEN) {
send_explicit_handshake_reject_("Bad handshake packet len"); send_explicit_handshake_reject_(LOG_STR("Bad handshake packet len"));
} }
return aerr; return aerr;
} }
// Helper for handling noise library errors // Helper for handling noise library errors
APIError APINoiseFrameHelper::handle_noise_error_(int err, const char *func_name, APIError api_err) { APIError APINoiseFrameHelper::handle_noise_error_(int err, const LogString *func_name, APIError api_err) {
if (err != 0) { if (err != 0) {
state_ = State::FAILED; state_ = State::FAILED;
HELPER_LOG("%s failed: %s", func_name, noise_err_to_str(err).c_str()); HELPER_LOG("%s failed: %s", LOG_STR_ARG(func_name), LOG_STR_ARG(noise_err_to_logstr(err)));
return api_err; return api_err;
} }
return APIError::OK; return APIError::OK;
@@ -279,11 +292,11 @@ APIError APINoiseFrameHelper::state_action_() {
} }
if (frame.empty()) { if (frame.empty()) {
send_explicit_handshake_reject_("Empty handshake message"); send_explicit_handshake_reject_(LOG_STR("Empty handshake message"));
return APIError::BAD_HANDSHAKE_ERROR_BYTE; return APIError::BAD_HANDSHAKE_ERROR_BYTE;
} else if (frame[0] != 0x00) { } else if (frame[0] != 0x00) {
HELPER_LOG("Bad handshake error byte: %u", frame[0]); HELPER_LOG("Bad handshake error byte: %u", frame[0]);
send_explicit_handshake_reject_("Bad handshake error byte"); send_explicit_handshake_reject_(LOG_STR("Bad handshake error byte"));
return APIError::BAD_HANDSHAKE_ERROR_BYTE; return APIError::BAD_HANDSHAKE_ERROR_BYTE;
} }
@@ -293,8 +306,10 @@ APIError APINoiseFrameHelper::state_action_() {
err = noise_handshakestate_read_message(handshake_, &mbuf, nullptr); err = noise_handshakestate_read_message(handshake_, &mbuf, nullptr);
if (err != 0) { if (err != 0) {
// Special handling for MAC failure // Special handling for MAC failure
send_explicit_handshake_reject_(err == NOISE_ERROR_MAC_FAILURE ? "Handshake MAC failure" : "Handshake error"); send_explicit_handshake_reject_(err == NOISE_ERROR_MAC_FAILURE ? LOG_STR("Handshake MAC failure")
return handle_noise_error_(err, "noise_handshakestate_read_message", APIError::HANDSHAKESTATE_READ_FAILED); : LOG_STR("Handshake error"));
return handle_noise_error_(err, LOG_STR("noise_handshakestate_read_message"),
APIError::HANDSHAKESTATE_READ_FAILED);
} }
aerr = check_handshake_finished_(); aerr = check_handshake_finished_();
@@ -307,8 +322,8 @@ APIError APINoiseFrameHelper::state_action_() {
noise_buffer_set_output(mbuf, buffer + 1, sizeof(buffer) - 1); noise_buffer_set_output(mbuf, buffer + 1, sizeof(buffer) - 1);
err = noise_handshakestate_write_message(handshake_, &mbuf, nullptr); err = noise_handshakestate_write_message(handshake_, &mbuf, nullptr);
APIError aerr_write = APIError aerr_write = handle_noise_error_(err, LOG_STR("noise_handshakestate_write_message"),
handle_noise_error_(err, "noise_handshakestate_write_message", APIError::HANDSHAKESTATE_WRITE_FAILED); APIError::HANDSHAKESTATE_WRITE_FAILED);
if (aerr_write != APIError::OK) if (aerr_write != APIError::OK)
return aerr_write; return aerr_write;
buffer[0] = 0x00; // success buffer[0] = 0x00; // success
@@ -331,15 +346,31 @@ APIError APINoiseFrameHelper::state_action_() {
} }
return APIError::OK; return APIError::OK;
} }
void APINoiseFrameHelper::send_explicit_handshake_reject_(const std::string &reason) { void APINoiseFrameHelper::send_explicit_handshake_reject_(const LogString *reason) {
#ifdef USE_STORE_LOG_STR_IN_FLASH
// On ESP8266 with flash strings, we need to use PROGMEM-aware functions
size_t reason_len = strlen_P(reinterpret_cast<PGM_P>(reason));
std::vector<uint8_t> data; std::vector<uint8_t> data;
data.resize(reason.length() + 1); data.resize(reason_len + 1);
data[0] = 0x01; // failure
// Copy error message from PROGMEM
if (reason_len > 0) {
memcpy_P(data.data() + 1, reinterpret_cast<PGM_P>(reason), reason_len);
}
#else
// Normal memory access
const char *reason_str = LOG_STR_ARG(reason);
size_t reason_len = strlen(reason_str);
std::vector<uint8_t> data;
data.resize(reason_len + 1);
data[0] = 0x01; // failure data[0] = 0x01; // failure
// Copy error message in bulk // Copy error message in bulk
if (!reason.empty()) { if (reason_len > 0) {
std::memcpy(data.data() + 1, reason.c_str(), reason.length()); std::memcpy(data.data() + 1, reason_str, reason_len);
} }
#endif
// temporarily remove failed state // temporarily remove failed state
auto orig_state = state_; auto orig_state = state_;
@@ -368,7 +399,8 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
noise_buffer_init(mbuf); noise_buffer_init(mbuf);
noise_buffer_set_inout(mbuf, frame.data(), frame.size(), frame.size()); noise_buffer_set_inout(mbuf, frame.data(), frame.size(), frame.size());
err = noise_cipherstate_decrypt(recv_cipher_, &mbuf); err = noise_cipherstate_decrypt(recv_cipher_, &mbuf);
APIError decrypt_err = handle_noise_error_(err, "noise_cipherstate_decrypt", APIError::CIPHERSTATE_DECRYPT_FAILED); APIError decrypt_err =
handle_noise_error_(err, LOG_STR("noise_cipherstate_decrypt"), APIError::CIPHERSTATE_DECRYPT_FAILED);
if (decrypt_err != APIError::OK) if (decrypt_err != APIError::OK)
return decrypt_err; return decrypt_err;
@@ -450,7 +482,8 @@ APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, st
4 + packet.payload_size + frame_footer_size_); 4 + packet.payload_size + frame_footer_size_);
int err = noise_cipherstate_encrypt(send_cipher_, &mbuf); int err = noise_cipherstate_encrypt(send_cipher_, &mbuf);
APIError aerr = handle_noise_error_(err, "noise_cipherstate_encrypt", APIError::CIPHERSTATE_ENCRYPT_FAILED); APIError aerr =
handle_noise_error_(err, LOG_STR("noise_cipherstate_encrypt"), APIError::CIPHERSTATE_ENCRYPT_FAILED);
if (aerr != APIError::OK) if (aerr != APIError::OK)
return aerr; return aerr;
@@ -504,25 +537,27 @@ APIError APINoiseFrameHelper::init_handshake_() {
nid_.modifier_ids[0] = NOISE_MODIFIER_PSK0; nid_.modifier_ids[0] = NOISE_MODIFIER_PSK0;
err = noise_handshakestate_new_by_id(&handshake_, &nid_, NOISE_ROLE_RESPONDER); err = noise_handshakestate_new_by_id(&handshake_, &nid_, NOISE_ROLE_RESPONDER);
APIError aerr = handle_noise_error_(err, "noise_handshakestate_new_by_id", APIError::HANDSHAKESTATE_SETUP_FAILED); APIError aerr =
handle_noise_error_(err, LOG_STR("noise_handshakestate_new_by_id"), APIError::HANDSHAKESTATE_SETUP_FAILED);
if (aerr != APIError::OK) if (aerr != APIError::OK)
return aerr; return aerr;
const auto &psk = ctx_->get_psk(); const auto &psk = ctx_->get_psk();
err = noise_handshakestate_set_pre_shared_key(handshake_, psk.data(), psk.size()); err = noise_handshakestate_set_pre_shared_key(handshake_, psk.data(), psk.size());
aerr = handle_noise_error_(err, "noise_handshakestate_set_pre_shared_key", APIError::HANDSHAKESTATE_SETUP_FAILED); aerr = handle_noise_error_(err, LOG_STR("noise_handshakestate_set_pre_shared_key"),
APIError::HANDSHAKESTATE_SETUP_FAILED);
if (aerr != APIError::OK) if (aerr != APIError::OK)
return aerr; return aerr;
err = noise_handshakestate_set_prologue(handshake_, prologue_.data(), prologue_.size()); err = noise_handshakestate_set_prologue(handshake_, prologue_.data(), prologue_.size());
aerr = handle_noise_error_(err, "noise_handshakestate_set_prologue", APIError::HANDSHAKESTATE_SETUP_FAILED); aerr = handle_noise_error_(err, LOG_STR("noise_handshakestate_set_prologue"), APIError::HANDSHAKESTATE_SETUP_FAILED);
if (aerr != APIError::OK) if (aerr != APIError::OK)
return aerr; return aerr;
// set_prologue copies it into handshakestate, so we can get rid of it now // set_prologue copies it into handshakestate, so we can get rid of it now
prologue_ = {}; prologue_ = {};
err = noise_handshakestate_start(handshake_); err = noise_handshakestate_start(handshake_);
aerr = handle_noise_error_(err, "noise_handshakestate_start", APIError::HANDSHAKESTATE_SETUP_FAILED); aerr = handle_noise_error_(err, LOG_STR("noise_handshakestate_start"), APIError::HANDSHAKESTATE_SETUP_FAILED);
if (aerr != APIError::OK) if (aerr != APIError::OK)
return aerr; return aerr;
return APIError::OK; return APIError::OK;
@@ -540,7 +575,8 @@ APIError APINoiseFrameHelper::check_handshake_finished_() {
return APIError::HANDSHAKESTATE_BAD_STATE; return APIError::HANDSHAKESTATE_BAD_STATE;
} }
int err = noise_handshakestate_split(handshake_, &send_cipher_, &recv_cipher_); int err = noise_handshakestate_split(handshake_, &send_cipher_, &recv_cipher_);
APIError aerr = handle_noise_error_(err, "noise_handshakestate_split", APIError::HANDSHAKESTATE_SPLIT_FAILED); APIError aerr =
handle_noise_error_(err, LOG_STR("noise_handshakestate_split"), APIError::HANDSHAKESTATE_SPLIT_FAILED);
if (aerr != APIError::OK) if (aerr != APIError::OK)
return aerr; return aerr;

View File

@@ -32,9 +32,9 @@ class APINoiseFrameHelper final : public APIFrameHelper {
APIError write_frame_(const uint8_t *data, uint16_t len); APIError write_frame_(const uint8_t *data, uint16_t len);
APIError init_handshake_(); APIError init_handshake_();
APIError check_handshake_finished_(); APIError check_handshake_finished_();
void send_explicit_handshake_reject_(const std::string &reason); void send_explicit_handshake_reject_(const LogString *reason);
APIError handle_handshake_frame_error_(APIError aerr); APIError handle_handshake_frame_error_(APIError aerr);
APIError handle_noise_error_(int err, const char *func_name, APIError api_err); APIError handle_noise_error_(int err, const LogString *func_name, APIError api_err);
// Pointers first (4 bytes each) // Pointers first (4 bytes each)
NoiseHandshakeState *handshake_{nullptr}; NoiseHandshakeState *handshake_{nullptr};

View File

@@ -10,11 +10,16 @@
#include <cstring> #include <cstring>
#include <cinttypes> #include <cinttypes>
#ifdef USE_ESP8266
#include <pgmspace.h>
#endif
namespace esphome::api { namespace esphome::api {
static const char *const TAG = "api.plaintext"; static const char *const TAG = "api.plaintext";
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, this->client_info_->get_combined_info().c_str(), ##__VA_ARGS__) #define HELPER_LOG(msg, ...) \
ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), this->client_info_->peername.c_str(), ##__VA_ARGS__)
#ifdef HELPER_LOG_PACKETS #ifdef HELPER_LOG_PACKETS
#define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str()) #define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str())
@@ -197,11 +202,20 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
// We must send at least 3 bytes to be read, so we add // We must send at least 3 bytes to be read, so we add
// a message after the indicator byte to ensures its long // a message after the indicator byte to ensures its long
// enough and can aid in debugging. // enough and can aid in debugging.
const char msg[] = "\x00" static constexpr uint8_t INDICATOR_MSG_SIZE = 19;
"Bad indicator byte"; #ifdef USE_ESP8266
static const char MSG_PROGMEM[] PROGMEM = "\x00"
"Bad indicator byte";
char msg[INDICATOR_MSG_SIZE];
memcpy_P(msg, MSG_PROGMEM, INDICATOR_MSG_SIZE);
iov[0].iov_base = (void *) msg; iov[0].iov_base = (void *) msg;
iov[0].iov_len = 19; #else
this->write_raw_(iov, 1, 19); static const char MSG[] = "\x00"
"Bad indicator byte";
iov[0].iov_base = (void *) MSG;
#endif
iov[0].iov_len = INDICATOR_MSG_SIZE;
this->write_raw_(iov, 1, INDICATOR_MSG_SIZE);
} }
return aerr; return aerr;
} }

View File

@@ -32,6 +32,13 @@ extend google.protobuf.FieldOptions {
optional string fixed_array_size_define = 50010; optional string fixed_array_size_define = 50010;
optional string fixed_array_with_length_define = 50011; optional string fixed_array_with_length_define = 50011;
// pointer_to_buffer: Use pointer instead of array for fixed-size byte fields
// When set, the field will be declared as a pointer (const uint8_t *data)
// instead of an array (uint8_t data[N]). This allows zero-copy on decode
// by pointing directly to the protobuf buffer. The buffer must remain valid
// until the message is processed (which is guaranteed for stack-allocated messages).
optional bool pointer_to_buffer = 50012 [default=false];
// container_pointer: Zero-copy optimization for repeated fields. // container_pointer: Zero-copy optimization for repeated fields.
// //
// When container_pointer is set on a repeated field, the generated message will // When container_pointer is set on a repeated field, the generated message will

View File

@@ -22,9 +22,12 @@ bool HelloRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
} }
bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) { switch (field_id) {
case 1: case 1: {
this->client_info = value.as_string(); // Use raw data directly to avoid allocation
this->client_info = value.data();
this->client_info_len = value.size();
break; break;
}
default: default:
return false; return false;
} }
@@ -42,18 +45,23 @@ void HelloResponse::calculate_size(ProtoSize &size) const {
size.add_length(1, this->server_info_ref_.size()); size.add_length(1, this->server_info_ref_.size());
size.add_length(1, this->name_ref_.size()); size.add_length(1, this->name_ref_.size());
} }
bool ConnectRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { #ifdef USE_API_PASSWORD
bool AuthenticationRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) { switch (field_id) {
case 1: case 1: {
this->password = value.as_string(); // Use raw data directly to avoid allocation
this->password = value.data();
this->password_len = value.size();
break; break;
}
default: default:
return false; return false;
} }
return true; return true;
} }
void ConnectResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->invalid_password); } void AuthenticationResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->invalid_password); }
void ConnectResponse::calculate_size(ProtoSize &size) const { size.add_bool(1, this->invalid_password); } void AuthenticationResponse::calculate_size(ProtoSize &size) const { size.add_bool(1, this->invalid_password); }
#endif
#ifdef USE_AREAS #ifdef USE_AREAS
void AreaInfo::encode(ProtoWriteBuffer buffer) const { void AreaInfo::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint32(1, this->area_id); buffer.encode_uint32(1, this->area_id);
@@ -127,6 +135,12 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
#ifdef USE_AREAS #ifdef USE_AREAS
buffer.encode_message(22, this->area); buffer.encode_message(22, this->area);
#endif #endif
#ifdef USE_ZWAVE_PROXY
buffer.encode_uint32(23, this->zwave_proxy_feature_flags);
#endif
#ifdef USE_ZWAVE_PROXY
buffer.encode_uint32(24, this->zwave_home_id);
#endif
} }
void DeviceInfoResponse::calculate_size(ProtoSize &size) const { void DeviceInfoResponse::calculate_size(ProtoSize &size) const {
#ifdef USE_API_PASSWORD #ifdef USE_API_PASSWORD
@@ -179,6 +193,12 @@ void DeviceInfoResponse::calculate_size(ProtoSize &size) const {
#ifdef USE_AREAS #ifdef USE_AREAS
size.add_message_object(2, this->area); size.add_message_object(2, this->area);
#endif #endif
#ifdef USE_ZWAVE_PROXY
size.add_uint32(2, this->zwave_proxy_feature_flags);
#endif
#ifdef USE_ZWAVE_PROXY
size.add_uint32(2, this->zwave_home_id);
#endif
} }
#ifdef USE_BINARY_SENSOR #ifdef USE_BINARY_SENSOR
void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const { void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const {
@@ -852,7 +872,7 @@ void HomeassistantServiceMap::calculate_size(ProtoSize &size) const {
size.add_length(1, this->key_ref_.size()); size.add_length(1, this->key_ref_.size());
size.add_length(1, this->value.size()); size.add_length(1, this->value.size());
} }
void HomeassistantServiceResponse::encode(ProtoWriteBuffer buffer) const { void HomeassistantActionRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->service_ref_); buffer.encode_string(1, this->service_ref_);
for (auto &it : this->data) { for (auto &it : this->data) {
buffer.encode_message(2, it, true); buffer.encode_message(2, it, true);
@@ -865,7 +885,7 @@ void HomeassistantServiceResponse::encode(ProtoWriteBuffer buffer) const {
} }
buffer.encode_bool(5, this->is_event); buffer.encode_bool(5, this->is_event);
} }
void HomeassistantServiceResponse::calculate_size(ProtoSize &size) const { void HomeassistantActionRequest::calculate_size(ProtoSize &size) const {
size.add_length(1, this->service_ref_.size()); size.add_length(1, this->service_ref_.size());
size.add_repeated_message(1, this->data); size.add_repeated_message(1, this->data);
size.add_repeated_message(1, this->data_template); size.add_repeated_message(1, this->data_template);
@@ -901,6 +921,19 @@ bool HomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDel
return true; return true;
} }
#endif #endif
bool GetTimeResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 2: {
// Use raw data directly to avoid allocation
this->timezone = value.data();
this->timezone_len = value.size();
break;
}
default:
return false;
}
return true;
}
bool GetTimeResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { bool GetTimeResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) { switch (field_id) {
case 1: case 1:
@@ -911,8 +944,6 @@ bool GetTimeResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
} }
return true; return true;
} }
void GetTimeResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->epoch_seconds); }
void GetTimeResponse::calculate_size(ProtoSize &size) const { size.add_fixed32(1, this->epoch_seconds); }
#ifdef USE_API_SERVICES #ifdef USE_API_SERVICES
void ListEntitiesServicesArgument::encode(ProtoWriteBuffer buffer) const { void ListEntitiesServicesArgument::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->name_ref_); buffer.encode_string(1, this->name_ref_);
@@ -2006,9 +2037,12 @@ bool BluetoothGATTWriteRequest::decode_varint(uint32_t field_id, ProtoVarInt val
} }
bool BluetoothGATTWriteRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { bool BluetoothGATTWriteRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) { switch (field_id) {
case 4: case 4: {
this->data = value.as_string(); // Use raw data directly to avoid allocation
this->data = value.data();
this->data_len = value.size();
break; break;
}
default: default:
return false; return false;
} }
@@ -2042,9 +2076,12 @@ bool BluetoothGATTWriteDescriptorRequest::decode_varint(uint32_t field_id, Proto
} }
bool BluetoothGATTWriteDescriptorRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { bool BluetoothGATTWriteDescriptorRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) { switch (field_id) {
case 3: case 3: {
this->data = value.as_string(); // Use raw data directly to avoid allocation
this->data = value.data();
this->data_len = value.size();
break; break;
}
default: default:
return false; return false;
} }
@@ -2153,10 +2190,12 @@ void BluetoothDeviceClearCacheResponse::calculate_size(ProtoSize &size) const {
void BluetoothScannerStateResponse::encode(ProtoWriteBuffer buffer) const { void BluetoothScannerStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint32(1, static_cast<uint32_t>(this->state)); buffer.encode_uint32(1, static_cast<uint32_t>(this->state));
buffer.encode_uint32(2, static_cast<uint32_t>(this->mode)); buffer.encode_uint32(2, static_cast<uint32_t>(this->mode));
buffer.encode_uint32(3, static_cast<uint32_t>(this->configured_mode));
} }
void BluetoothScannerStateResponse::calculate_size(ProtoSize &size) const { void BluetoothScannerStateResponse::calculate_size(ProtoSize &size) const {
size.add_uint32(1, static_cast<uint32_t>(this->state)); size.add_uint32(1, static_cast<uint32_t>(this->state));
size.add_uint32(1, static_cast<uint32_t>(this->mode)); size.add_uint32(1, static_cast<uint32_t>(this->mode));
size.add_uint32(1, static_cast<uint32_t>(this->configured_mode));
} }
bool BluetoothScannerSetModeRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { bool BluetoothScannerSetModeRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) { switch (field_id) {
@@ -2358,6 +2397,52 @@ void VoiceAssistantWakeWord::calculate_size(ProtoSize &size) const {
} }
} }
} }
bool VoiceAssistantExternalWakeWord::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 5:
this->model_size = value.as_uint32();
break;
default:
return false;
}
return true;
}
bool VoiceAssistantExternalWakeWord::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1:
this->id = value.as_string();
break;
case 2:
this->wake_word = value.as_string();
break;
case 3:
this->trained_languages.push_back(value.as_string());
break;
case 4:
this->model_type = value.as_string();
break;
case 6:
this->model_hash = value.as_string();
break;
case 7:
this->url = value.as_string();
break;
default:
return false;
}
return true;
}
bool VoiceAssistantConfigurationRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1:
this->external_wake_words.emplace_back();
value.decode_to_message(this->external_wake_words.back());
break;
default:
return false;
}
return true;
}
void VoiceAssistantConfigurationResponse::encode(ProtoWriteBuffer buffer) const { void VoiceAssistantConfigurationResponse::encode(ProtoWriteBuffer buffer) const {
for (auto &it : this->available_wake_words) { for (auto &it : this->available_wake_words) {
buffer.encode_message(1, it, true); buffer.encode_message(1, it, true);
@@ -3001,5 +3086,53 @@ bool UpdateCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
return true; return true;
} }
#endif #endif
#ifdef USE_ZWAVE_PROXY
bool ZWaveProxyFrame::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
// Use raw data directly to avoid allocation
this->data = value.data();
this->data_len = value.size();
break;
}
default:
return false;
}
return true;
}
void ZWaveProxyFrame::encode(ProtoWriteBuffer buffer) const { buffer.encode_bytes(1, this->data, this->data_len); }
void ZWaveProxyFrame::calculate_size(ProtoSize &size) const { size.add_length(1, this->data_len); }
bool ZWaveProxyRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1:
this->type = static_cast<enums::ZWaveProxyRequestType>(value.as_uint32());
break;
default:
return false;
}
return true;
}
bool ZWaveProxyRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 2: {
// Use raw data directly to avoid allocation
this->data = value.data();
this->data_len = value.size();
break;
}
default:
return false;
}
return true;
}
void ZWaveProxyRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint32(1, static_cast<uint32_t>(this->type));
buffer.encode_bytes(2, this->data, this->data_len);
}
void ZWaveProxyRequest::calculate_size(ProtoSize &size) const {
size.add_uint32(1, static_cast<uint32_t>(this->type));
size.add_length(2, this->data_len);
}
#endif
} // namespace esphome::api } // namespace esphome::api

View File

@@ -276,6 +276,13 @@ enum UpdateCommand : uint32_t {
UPDATE_COMMAND_CHECK = 2, UPDATE_COMMAND_CHECK = 2,
}; };
#endif #endif
#ifdef USE_ZWAVE_PROXY
enum ZWaveProxyRequestType : uint32_t {
ZWAVE_PROXY_REQUEST_TYPE_SUBSCRIBE = 0,
ZWAVE_PROXY_REQUEST_TYPE_UNSUBSCRIBE = 1,
ZWAVE_PROXY_REQUEST_TYPE_HOME_ID_CHANGE = 2,
};
#endif
} // namespace enums } // namespace enums
@@ -324,11 +331,12 @@ class CommandProtoMessage : public ProtoDecodableMessage {
class HelloRequest final : public ProtoDecodableMessage { class HelloRequest final : public ProtoDecodableMessage {
public: public:
static constexpr uint8_t MESSAGE_TYPE = 1; static constexpr uint8_t MESSAGE_TYPE = 1;
static constexpr uint8_t ESTIMATED_SIZE = 17; static constexpr uint8_t ESTIMATED_SIZE = 27;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "hello_request"; } const char *message_name() const override { return "hello_request"; }
#endif #endif
std::string client_info{}; const uint8_t *client_info{nullptr};
uint16_t client_info_len{0};
uint32_t api_version_major{0}; uint32_t api_version_major{0};
uint32_t api_version_minor{0}; uint32_t api_version_minor{0};
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
@@ -360,14 +368,16 @@ class HelloResponse final : public ProtoMessage {
protected: protected:
}; };
class ConnectRequest final : public ProtoDecodableMessage { #ifdef USE_API_PASSWORD
class AuthenticationRequest final : public ProtoDecodableMessage {
public: public:
static constexpr uint8_t MESSAGE_TYPE = 3; static constexpr uint8_t MESSAGE_TYPE = 3;
static constexpr uint8_t ESTIMATED_SIZE = 9; static constexpr uint8_t ESTIMATED_SIZE = 19;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "connect_request"; } const char *message_name() const override { return "authentication_request"; }
#endif #endif
std::string password{}; const uint8_t *password{nullptr};
uint16_t password_len{0};
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -375,12 +385,12 @@ class ConnectRequest final : public ProtoDecodableMessage {
protected: protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
}; };
class ConnectResponse final : public ProtoMessage { class AuthenticationResponse final : public ProtoMessage {
public: public:
static constexpr uint8_t MESSAGE_TYPE = 4; static constexpr uint8_t MESSAGE_TYPE = 4;
static constexpr uint8_t ESTIMATED_SIZE = 2; static constexpr uint8_t ESTIMATED_SIZE = 2;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "connect_response"; } const char *message_name() const override { return "authentication_response"; }
#endif #endif
bool invalid_password{false}; bool invalid_password{false};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
@@ -391,6 +401,7 @@ class ConnectResponse final : public ProtoMessage {
protected: protected:
}; };
#endif
class DisconnectRequest final : public ProtoMessage { class DisconnectRequest final : public ProtoMessage {
public: public:
static constexpr uint8_t MESSAGE_TYPE = 5; static constexpr uint8_t MESSAGE_TYPE = 5;
@@ -490,7 +501,7 @@ class DeviceInfo final : public ProtoMessage {
class DeviceInfoResponse final : public ProtoMessage { class DeviceInfoResponse final : public ProtoMessage {
public: public:
static constexpr uint8_t MESSAGE_TYPE = 10; static constexpr uint8_t MESSAGE_TYPE = 10;
static constexpr uint8_t ESTIMATED_SIZE = 247; static constexpr uint16_t ESTIMATED_SIZE = 257;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "device_info_response"; } const char *message_name() const override { return "device_info_response"; }
#endif #endif
@@ -550,6 +561,12 @@ class DeviceInfoResponse final : public ProtoMessage {
#endif #endif
#ifdef USE_AREAS #ifdef USE_AREAS
AreaInfo area{}; AreaInfo area{};
#endif
#ifdef USE_ZWAVE_PROXY
uint32_t zwave_proxy_feature_flags{0};
#endif
#ifdef USE_ZWAVE_PROXY
uint32_t zwave_home_id{0};
#endif #endif
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override; void calculate_size(ProtoSize &size) const override;
@@ -1084,12 +1101,12 @@ class HomeassistantServiceMap final : public ProtoMessage {
protected: protected:
}; };
class HomeassistantServiceResponse final : public ProtoMessage { class HomeassistantActionRequest final : public ProtoMessage {
public: public:
static constexpr uint8_t MESSAGE_TYPE = 35; static constexpr uint8_t MESSAGE_TYPE = 35;
static constexpr uint8_t ESTIMATED_SIZE = 113; static constexpr uint8_t ESTIMATED_SIZE = 113;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "homeassistant_service_response"; } const char *message_name() const override { return "homeassistant_action_request"; }
#endif #endif
StringRef service_ref_{}; StringRef service_ref_{};
void set_service(const StringRef &ref) { this->service_ref_ = ref; } void set_service(const StringRef &ref) { this->service_ref_ = ref; }
@@ -1174,19 +1191,20 @@ class GetTimeRequest final : public ProtoMessage {
class GetTimeResponse final : public ProtoDecodableMessage { class GetTimeResponse final : public ProtoDecodableMessage {
public: public:
static constexpr uint8_t MESSAGE_TYPE = 37; static constexpr uint8_t MESSAGE_TYPE = 37;
static constexpr uint8_t ESTIMATED_SIZE = 5; static constexpr uint8_t ESTIMATED_SIZE = 24;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "get_time_response"; } const char *message_name() const override { return "get_time_response"; }
#endif #endif
uint32_t epoch_seconds{0}; uint32_t epoch_seconds{0};
void encode(ProtoWriteBuffer buffer) const override; const uint8_t *timezone{nullptr};
void calculate_size(ProtoSize &size) const override; uint16_t timezone_len{0};
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
protected: protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override; bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
}; };
#ifdef USE_API_SERVICES #ifdef USE_API_SERVICES
class ListEntitiesServicesArgument final : public ProtoMessage { class ListEntitiesServicesArgument final : public ProtoMessage {
@@ -1971,14 +1989,15 @@ class BluetoothGATTReadResponse final : public ProtoMessage {
class BluetoothGATTWriteRequest final : public ProtoDecodableMessage { class BluetoothGATTWriteRequest final : public ProtoDecodableMessage {
public: public:
static constexpr uint8_t MESSAGE_TYPE = 75; static constexpr uint8_t MESSAGE_TYPE = 75;
static constexpr uint8_t ESTIMATED_SIZE = 19; static constexpr uint8_t ESTIMATED_SIZE = 29;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "bluetooth_gatt_write_request"; } const char *message_name() const override { return "bluetooth_gatt_write_request"; }
#endif #endif
uint64_t address{0}; uint64_t address{0};
uint32_t handle{0}; uint32_t handle{0};
bool response{false}; bool response{false};
std::string data{}; const uint8_t *data{nullptr};
uint16_t data_len{0};
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -2006,13 +2025,14 @@ class BluetoothGATTReadDescriptorRequest final : public ProtoDecodableMessage {
class BluetoothGATTWriteDescriptorRequest final : public ProtoDecodableMessage { class BluetoothGATTWriteDescriptorRequest final : public ProtoDecodableMessage {
public: public:
static constexpr uint8_t MESSAGE_TYPE = 77; static constexpr uint8_t MESSAGE_TYPE = 77;
static constexpr uint8_t ESTIMATED_SIZE = 17; static constexpr uint8_t ESTIMATED_SIZE = 27;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "bluetooth_gatt_write_descriptor_request"; } const char *message_name() const override { return "bluetooth_gatt_write_descriptor_request"; }
#endif #endif
uint64_t address{0}; uint64_t address{0};
uint32_t handle{0}; uint32_t handle{0};
std::string data{}; const uint8_t *data{nullptr};
uint16_t data_len{0};
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -2214,12 +2234,13 @@ class BluetoothDeviceClearCacheResponse final : public ProtoMessage {
class BluetoothScannerStateResponse final : public ProtoMessage { class BluetoothScannerStateResponse final : public ProtoMessage {
public: public:
static constexpr uint8_t MESSAGE_TYPE = 126; static constexpr uint8_t MESSAGE_TYPE = 126;
static constexpr uint8_t ESTIMATED_SIZE = 4; static constexpr uint8_t ESTIMATED_SIZE = 6;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "bluetooth_scanner_state_response"; } const char *message_name() const override { return "bluetooth_scanner_state_response"; }
#endif #endif
enums::BluetoothScannerState state{}; enums::BluetoothScannerState state{};
enums::BluetoothScannerMode mode{}; enums::BluetoothScannerMode mode{};
enums::BluetoothScannerMode configured_mode{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
@@ -2436,18 +2457,37 @@ class VoiceAssistantWakeWord final : public ProtoMessage {
protected: protected:
}; };
class VoiceAssistantConfigurationRequest final : public ProtoMessage { class VoiceAssistantExternalWakeWord final : public ProtoDecodableMessage {
public: public:
static constexpr uint8_t MESSAGE_TYPE = 121; std::string id{};
static constexpr uint8_t ESTIMATED_SIZE = 0; std::string wake_word{};
#ifdef HAS_PROTO_MESSAGE_DUMP std::vector<std::string> trained_languages{};
const char *message_name() const override { return "voice_assistant_configuration_request"; } std::string model_type{};
#endif uint32_t model_size{0};
std::string model_hash{};
std::string url{};
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
protected: protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class VoiceAssistantConfigurationRequest final : public ProtoDecodableMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 121;
static constexpr uint8_t ESTIMATED_SIZE = 34;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "voice_assistant_configuration_request"; }
#endif
std::vector<VoiceAssistantExternalWakeWord> external_wake_words{};
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
}; };
class VoiceAssistantConfigurationResponse final : public ProtoMessage { class VoiceAssistantConfigurationResponse final : public ProtoMessage {
public: public:
@@ -2910,5 +2950,45 @@ class UpdateCommandRequest final : public CommandProtoMessage {
bool decode_varint(uint32_t field_id, ProtoVarInt value) override; bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
}; };
#endif #endif
#ifdef USE_ZWAVE_PROXY
class ZWaveProxyFrame final : public ProtoDecodableMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 128;
static constexpr uint8_t ESTIMATED_SIZE = 19;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "z_wave_proxy_frame"; }
#endif
const uint8_t *data{nullptr};
uint16_t data_len{0};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
};
class ZWaveProxyRequest final : public ProtoDecodableMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 129;
static constexpr uint8_t ESTIMATED_SIZE = 21;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "z_wave_proxy_request"; }
#endif
enums::ZWaveProxyRequestType type{};
const uint8_t *data{nullptr};
uint16_t data_len{0};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
#endif
} // namespace esphome::api } // namespace esphome::api

View File

@@ -655,10 +655,26 @@ template<> const char *proto_enum_to_string<enums::UpdateCommand>(enums::UpdateC
} }
} }
#endif #endif
#ifdef USE_ZWAVE_PROXY
template<> const char *proto_enum_to_string<enums::ZWaveProxyRequestType>(enums::ZWaveProxyRequestType value) {
switch (value) {
case enums::ZWAVE_PROXY_REQUEST_TYPE_SUBSCRIBE:
return "ZWAVE_PROXY_REQUEST_TYPE_SUBSCRIBE";
case enums::ZWAVE_PROXY_REQUEST_TYPE_UNSUBSCRIBE:
return "ZWAVE_PROXY_REQUEST_TYPE_UNSUBSCRIBE";
case enums::ZWAVE_PROXY_REQUEST_TYPE_HOME_ID_CHANGE:
return "ZWAVE_PROXY_REQUEST_TYPE_HOME_ID_CHANGE";
default:
return "UNKNOWN";
}
}
#endif
void HelloRequest::dump_to(std::string &out) const { void HelloRequest::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "HelloRequest"); MessageDumpHelper helper(out, "HelloRequest");
dump_field(out, "client_info", this->client_info); out.append(" client_info: ");
out.append(format_hex_pretty(this->client_info, this->client_info_len));
out.append("\n");
dump_field(out, "api_version_major", this->api_version_major); dump_field(out, "api_version_major", this->api_version_major);
dump_field(out, "api_version_minor", this->api_version_minor); dump_field(out, "api_version_minor", this->api_version_minor);
} }
@@ -669,8 +685,18 @@ void HelloResponse::dump_to(std::string &out) const {
dump_field(out, "server_info", this->server_info_ref_); dump_field(out, "server_info", this->server_info_ref_);
dump_field(out, "name", this->name_ref_); dump_field(out, "name", this->name_ref_);
} }
void ConnectRequest::dump_to(std::string &out) const { dump_field(out, "password", this->password); } #ifdef USE_API_PASSWORD
void ConnectResponse::dump_to(std::string &out) const { dump_field(out, "invalid_password", this->invalid_password); } void AuthenticationRequest::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "AuthenticationRequest");
out.append(" password: ");
out.append(format_hex_pretty(this->password, this->password_len));
out.append("\n");
}
void AuthenticationResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "AuthenticationResponse");
dump_field(out, "invalid_password", this->invalid_password);
}
#endif
void DisconnectRequest::dump_to(std::string &out) const { out.append("DisconnectRequest {}"); } void DisconnectRequest::dump_to(std::string &out) const { out.append("DisconnectRequest {}"); }
void DisconnectResponse::dump_to(std::string &out) const { out.append("DisconnectResponse {}"); } void DisconnectResponse::dump_to(std::string &out) const { out.append("DisconnectResponse {}"); }
void PingRequest::dump_to(std::string &out) const { out.append("PingRequest {}"); } void PingRequest::dump_to(std::string &out) const { out.append("PingRequest {}"); }
@@ -749,6 +775,12 @@ void DeviceInfoResponse::dump_to(std::string &out) const {
this->area.dump_to(out); this->area.dump_to(out);
out.append("\n"); out.append("\n");
#endif #endif
#ifdef USE_ZWAVE_PROXY
dump_field(out, "zwave_proxy_feature_flags", this->zwave_proxy_feature_flags);
#endif
#ifdef USE_ZWAVE_PROXY
dump_field(out, "zwave_home_id", this->zwave_home_id);
#endif
} }
void ListEntitiesRequest::dump_to(std::string &out) const { out.append("ListEntitiesRequest {}"); } void ListEntitiesRequest::dump_to(std::string &out) const { out.append("ListEntitiesRequest {}"); }
void ListEntitiesDoneResponse::dump_to(std::string &out) const { out.append("ListEntitiesDoneResponse {}"); } void ListEntitiesDoneResponse::dump_to(std::string &out) const { out.append("ListEntitiesDoneResponse {}"); }
@@ -1071,8 +1103,8 @@ void HomeassistantServiceMap::dump_to(std::string &out) const {
dump_field(out, "key", this->key_ref_); dump_field(out, "key", this->key_ref_);
dump_field(out, "value", this->value); dump_field(out, "value", this->value);
} }
void HomeassistantServiceResponse::dump_to(std::string &out) const { void HomeassistantActionRequest::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "HomeassistantServiceResponse"); MessageDumpHelper helper(out, "HomeassistantActionRequest");
dump_field(out, "service", this->service_ref_); dump_field(out, "service", this->service_ref_);
for (const auto &it : this->data) { for (const auto &it : this->data) {
out.append(" data: "); out.append(" data: ");
@@ -1110,7 +1142,13 @@ void HomeAssistantStateResponse::dump_to(std::string &out) const {
} }
#endif #endif
void GetTimeRequest::dump_to(std::string &out) const { out.append("GetTimeRequest {}"); } 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); } void GetTimeResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "GetTimeResponse");
dump_field(out, "epoch_seconds", this->epoch_seconds);
out.append(" timezone: ");
out.append(format_hex_pretty(this->timezone, this->timezone_len));
out.append("\n");
}
#ifdef USE_API_SERVICES #ifdef USE_API_SERVICES
void ListEntitiesServicesArgument::dump_to(std::string &out) const { void ListEntitiesServicesArgument::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesServicesArgument"); MessageDumpHelper helper(out, "ListEntitiesServicesArgument");
@@ -1135,7 +1173,7 @@ void ExecuteServiceArgument::dump_to(std::string &out) const {
dump_field(out, "string_", this->string_); dump_field(out, "string_", this->string_);
dump_field(out, "int_", this->int_); dump_field(out, "int_", this->int_);
for (const auto it : this->bool_array) { for (const auto it : this->bool_array) {
dump_field(out, "bool_array", it, 4); dump_field(out, "bool_array", static_cast<bool>(it), 4);
} }
for (const auto &it : this->int_array) { for (const auto &it : this->int_array) {
dump_field(out, "int_array", it, 4); dump_field(out, "int_array", it, 4);
@@ -1622,7 +1660,7 @@ void BluetoothGATTWriteRequest::dump_to(std::string &out) const {
dump_field(out, "handle", this->handle); dump_field(out, "handle", this->handle);
dump_field(out, "response", this->response); dump_field(out, "response", this->response);
out.append(" data: "); out.append(" data: ");
out.append(format_hex_pretty(reinterpret_cast<const uint8_t *>(this->data.data()), this->data.size())); out.append(format_hex_pretty(this->data, this->data_len));
out.append("\n"); out.append("\n");
} }
void BluetoothGATTReadDescriptorRequest::dump_to(std::string &out) const { void BluetoothGATTReadDescriptorRequest::dump_to(std::string &out) const {
@@ -1635,7 +1673,7 @@ void BluetoothGATTWriteDescriptorRequest::dump_to(std::string &out) const {
dump_field(out, "address", this->address); dump_field(out, "address", this->address);
dump_field(out, "handle", this->handle); dump_field(out, "handle", this->handle);
out.append(" data: "); out.append(" data: ");
out.append(format_hex_pretty(reinterpret_cast<const uint8_t *>(this->data.data()), this->data.size())); out.append(format_hex_pretty(this->data, this->data_len));
out.append("\n"); out.append("\n");
} }
void BluetoothGATTNotifyRequest::dump_to(std::string &out) const { void BluetoothGATTNotifyRequest::dump_to(std::string &out) const {
@@ -1704,6 +1742,7 @@ void BluetoothScannerStateResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "BluetoothScannerStateResponse"); MessageDumpHelper helper(out, "BluetoothScannerStateResponse");
dump_field(out, "state", static_cast<enums::BluetoothScannerState>(this->state)); dump_field(out, "state", static_cast<enums::BluetoothScannerState>(this->state));
dump_field(out, "mode", static_cast<enums::BluetoothScannerMode>(this->mode)); dump_field(out, "mode", static_cast<enums::BluetoothScannerMode>(this->mode));
dump_field(out, "configured_mode", static_cast<enums::BluetoothScannerMode>(this->configured_mode));
} }
void BluetoothScannerSetModeRequest::dump_to(std::string &out) const { void BluetoothScannerSetModeRequest::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "BluetoothScannerSetModeRequest"); MessageDumpHelper helper(out, "BluetoothScannerSetModeRequest");
@@ -1787,8 +1826,25 @@ void VoiceAssistantWakeWord::dump_to(std::string &out) const {
dump_field(out, "trained_languages", it, 4); dump_field(out, "trained_languages", it, 4);
} }
} }
void VoiceAssistantExternalWakeWord::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "VoiceAssistantExternalWakeWord");
dump_field(out, "id", this->id);
dump_field(out, "wake_word", this->wake_word);
for (const auto &it : this->trained_languages) {
dump_field(out, "trained_languages", it, 4);
}
dump_field(out, "model_type", this->model_type);
dump_field(out, "model_size", this->model_size);
dump_field(out, "model_hash", this->model_hash);
dump_field(out, "url", this->url);
}
void VoiceAssistantConfigurationRequest::dump_to(std::string &out) const { void VoiceAssistantConfigurationRequest::dump_to(std::string &out) const {
out.append("VoiceAssistantConfigurationRequest {}"); MessageDumpHelper helper(out, "VoiceAssistantConfigurationRequest");
for (const auto &it : this->external_wake_words) {
out.append(" external_wake_words: ");
it.dump_to(out);
out.append("\n");
}
} }
void VoiceAssistantConfigurationResponse::dump_to(std::string &out) const { void VoiceAssistantConfigurationResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "VoiceAssistantConfigurationResponse"); MessageDumpHelper helper(out, "VoiceAssistantConfigurationResponse");
@@ -2097,6 +2153,21 @@ void UpdateCommandRequest::dump_to(std::string &out) const {
#endif #endif
} }
#endif #endif
#ifdef USE_ZWAVE_PROXY
void ZWaveProxyFrame::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ZWaveProxyFrame");
out.append(" data: ");
out.append(format_hex_pretty(this->data, this->data_len));
out.append("\n");
}
void ZWaveProxyRequest::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ZWaveProxyRequest");
dump_field(out, "type", static_cast<enums::ZWaveProxyRequestType>(this->type));
out.append(" data: ");
out.append(format_hex_pretty(this->data, this->data_len));
out.append("\n");
}
#endif
} // namespace esphome::api } // namespace esphome::api

View File

@@ -24,15 +24,17 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
this->on_hello_request(msg); this->on_hello_request(msg);
break; break;
} }
case ConnectRequest::MESSAGE_TYPE: { #ifdef USE_API_PASSWORD
ConnectRequest msg; case AuthenticationRequest::MESSAGE_TYPE: {
AuthenticationRequest msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_connect_request: %s", msg.dump().c_str()); ESP_LOGVV(TAG, "on_authentication_request: %s", msg.dump().c_str());
#endif #endif
this->on_connect_request(msg); this->on_authentication_request(msg);
break; break;
} }
#endif
case DisconnectRequest::MESSAGE_TYPE: { case DisconnectRequest::MESSAGE_TYPE: {
DisconnectRequest msg; DisconnectRequest msg;
// Empty message: no decode needed // Empty message: no decode needed
@@ -160,15 +162,6 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
break; break;
} }
#endif #endif
case GetTimeRequest::MESSAGE_TYPE: {
GetTimeRequest msg;
// Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_get_time_request: %s", msg.dump().c_str());
#endif
this->on_get_time_request(msg);
break;
}
case GetTimeResponse::MESSAGE_TYPE: { case GetTimeResponse::MESSAGE_TYPE: {
GetTimeResponse msg; GetTimeResponse msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
@@ -555,7 +548,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
case VoiceAssistantConfigurationRequest::MESSAGE_TYPE: { case VoiceAssistantConfigurationRequest::MESSAGE_TYPE: {
VoiceAssistantConfigurationRequest msg; VoiceAssistantConfigurationRequest msg;
// Empty message: no decode needed msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_voice_assistant_configuration_request: %s", msg.dump().c_str()); ESP_LOGVV(TAG, "on_voice_assistant_configuration_request: %s", msg.dump().c_str());
#endif #endif
@@ -595,6 +588,28 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
this->on_bluetooth_scanner_set_mode_request(msg); this->on_bluetooth_scanner_set_mode_request(msg);
break; break;
} }
#endif
#ifdef USE_ZWAVE_PROXY
case ZWaveProxyFrame::MESSAGE_TYPE: {
ZWaveProxyFrame msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_z_wave_proxy_frame: %s", msg.dump().c_str());
#endif
this->on_z_wave_proxy_frame(msg);
break;
}
#endif
#ifdef USE_ZWAVE_PROXY
case ZWaveProxyRequest::MESSAGE_TYPE: {
ZWaveProxyRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_z_wave_proxy_request: %s", msg.dump().c_str());
#endif
this->on_z_wave_proxy_request(msg);
break;
}
#endif #endif
default: default:
break; break;
@@ -606,11 +621,13 @@ void APIServerConnection::on_hello_request(const HelloRequest &msg) {
this->on_fatal_error(); this->on_fatal_error();
} }
} }
void APIServerConnection::on_connect_request(const ConnectRequest &msg) { #ifdef USE_API_PASSWORD
if (!this->send_connect_response(msg)) { void APIServerConnection::on_authentication_request(const AuthenticationRequest &msg) {
if (!this->send_authenticate_response(msg)) {
this->on_fatal_error(); this->on_fatal_error();
} }
} }
#endif
void APIServerConnection::on_disconnect_request(const DisconnectRequest &msg) { void APIServerConnection::on_disconnect_request(const DisconnectRequest &msg) {
if (!this->send_disconnect_response(msg)) { if (!this->send_disconnect_response(msg)) {
this->on_fatal_error(); this->on_fatal_error();
@@ -622,246 +639,139 @@ void APIServerConnection::on_ping_request(const PingRequest &msg) {
} }
} }
void APIServerConnection::on_device_info_request(const DeviceInfoRequest &msg) { void APIServerConnection::on_device_info_request(const DeviceInfoRequest &msg) {
if (this->check_connection_setup_() && !this->send_device_info_response(msg)) { if (!this->send_device_info_response(msg)) {
this->on_fatal_error(); this->on_fatal_error();
} }
} }
void APIServerConnection::on_list_entities_request(const ListEntitiesRequest &msg) { void APIServerConnection::on_list_entities_request(const ListEntitiesRequest &msg) { this->list_entities(msg); }
if (this->check_authenticated_()) {
this->list_entities(msg);
}
}
void APIServerConnection::on_subscribe_states_request(const SubscribeStatesRequest &msg) { void APIServerConnection::on_subscribe_states_request(const SubscribeStatesRequest &msg) {
if (this->check_authenticated_()) { this->subscribe_states(msg);
this->subscribe_states(msg);
}
}
void APIServerConnection::on_subscribe_logs_request(const SubscribeLogsRequest &msg) {
if (this->check_authenticated_()) {
this->subscribe_logs(msg);
}
} }
void APIServerConnection::on_subscribe_logs_request(const SubscribeLogsRequest &msg) { this->subscribe_logs(msg); }
#ifdef USE_API_HOMEASSISTANT_SERVICES #ifdef USE_API_HOMEASSISTANT_SERVICES
void APIServerConnection::on_subscribe_homeassistant_services_request( void APIServerConnection::on_subscribe_homeassistant_services_request(
const SubscribeHomeassistantServicesRequest &msg) { const SubscribeHomeassistantServicesRequest &msg) {
if (this->check_authenticated_()) { this->subscribe_homeassistant_services(msg);
this->subscribe_homeassistant_services(msg);
}
} }
#endif #endif
#ifdef USE_API_HOMEASSISTANT_STATES #ifdef USE_API_HOMEASSISTANT_STATES
void APIServerConnection::on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) { void APIServerConnection::on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) {
if (this->check_authenticated_()) { this->subscribe_home_assistant_states(msg);
this->subscribe_home_assistant_states(msg);
}
} }
#endif #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();
}
}
#ifdef USE_API_SERVICES #ifdef USE_API_SERVICES
void APIServerConnection::on_execute_service_request(const ExecuteServiceRequest &msg) { void APIServerConnection::on_execute_service_request(const ExecuteServiceRequest &msg) { this->execute_service(msg); }
if (this->check_authenticated_()) {
this->execute_service(msg);
}
}
#endif #endif
#ifdef USE_API_NOISE #ifdef USE_API_NOISE
void APIServerConnection::on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) { void APIServerConnection::on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) {
if (this->check_authenticated_() && !this->send_noise_encryption_set_key_response(msg)) { if (!this->send_noise_encryption_set_key_response(msg)) {
this->on_fatal_error(); this->on_fatal_error();
} }
} }
#endif #endif
#ifdef USE_BUTTON #ifdef USE_BUTTON
void APIServerConnection::on_button_command_request(const ButtonCommandRequest &msg) { void APIServerConnection::on_button_command_request(const ButtonCommandRequest &msg) { this->button_command(msg); }
if (this->check_authenticated_()) {
this->button_command(msg);
}
}
#endif #endif
#ifdef USE_CAMERA #ifdef USE_CAMERA
void APIServerConnection::on_camera_image_request(const CameraImageRequest &msg) { void APIServerConnection::on_camera_image_request(const CameraImageRequest &msg) { this->camera_image(msg); }
if (this->check_authenticated_()) {
this->camera_image(msg);
}
}
#endif #endif
#ifdef USE_CLIMATE #ifdef USE_CLIMATE
void APIServerConnection::on_climate_command_request(const ClimateCommandRequest &msg) { void APIServerConnection::on_climate_command_request(const ClimateCommandRequest &msg) { this->climate_command(msg); }
if (this->check_authenticated_()) {
this->climate_command(msg);
}
}
#endif #endif
#ifdef USE_COVER #ifdef USE_COVER
void APIServerConnection::on_cover_command_request(const CoverCommandRequest &msg) { void APIServerConnection::on_cover_command_request(const CoverCommandRequest &msg) { this->cover_command(msg); }
if (this->check_authenticated_()) {
this->cover_command(msg);
}
}
#endif #endif
#ifdef USE_DATETIME_DATE #ifdef USE_DATETIME_DATE
void APIServerConnection::on_date_command_request(const DateCommandRequest &msg) { void APIServerConnection::on_date_command_request(const DateCommandRequest &msg) { this->date_command(msg); }
if (this->check_authenticated_()) {
this->date_command(msg);
}
}
#endif #endif
#ifdef USE_DATETIME_DATETIME #ifdef USE_DATETIME_DATETIME
void APIServerConnection::on_date_time_command_request(const DateTimeCommandRequest &msg) { void APIServerConnection::on_date_time_command_request(const DateTimeCommandRequest &msg) {
if (this->check_authenticated_()) { this->datetime_command(msg);
this->datetime_command(msg);
}
} }
#endif #endif
#ifdef USE_FAN #ifdef USE_FAN
void APIServerConnection::on_fan_command_request(const FanCommandRequest &msg) { void APIServerConnection::on_fan_command_request(const FanCommandRequest &msg) { this->fan_command(msg); }
if (this->check_authenticated_()) {
this->fan_command(msg);
}
}
#endif #endif
#ifdef USE_LIGHT #ifdef USE_LIGHT
void APIServerConnection::on_light_command_request(const LightCommandRequest &msg) { void APIServerConnection::on_light_command_request(const LightCommandRequest &msg) { this->light_command(msg); }
if (this->check_authenticated_()) {
this->light_command(msg);
}
}
#endif #endif
#ifdef USE_LOCK #ifdef USE_LOCK
void APIServerConnection::on_lock_command_request(const LockCommandRequest &msg) { void APIServerConnection::on_lock_command_request(const LockCommandRequest &msg) { this->lock_command(msg); }
if (this->check_authenticated_()) {
this->lock_command(msg);
}
}
#endif #endif
#ifdef USE_MEDIA_PLAYER #ifdef USE_MEDIA_PLAYER
void APIServerConnection::on_media_player_command_request(const MediaPlayerCommandRequest &msg) { void APIServerConnection::on_media_player_command_request(const MediaPlayerCommandRequest &msg) {
if (this->check_authenticated_()) { this->media_player_command(msg);
this->media_player_command(msg);
}
} }
#endif #endif
#ifdef USE_NUMBER #ifdef USE_NUMBER
void APIServerConnection::on_number_command_request(const NumberCommandRequest &msg) { void APIServerConnection::on_number_command_request(const NumberCommandRequest &msg) { this->number_command(msg); }
if (this->check_authenticated_()) {
this->number_command(msg);
}
}
#endif #endif
#ifdef USE_SELECT #ifdef USE_SELECT
void APIServerConnection::on_select_command_request(const SelectCommandRequest &msg) { void APIServerConnection::on_select_command_request(const SelectCommandRequest &msg) { this->select_command(msg); }
if (this->check_authenticated_()) {
this->select_command(msg);
}
}
#endif #endif
#ifdef USE_SIREN #ifdef USE_SIREN
void APIServerConnection::on_siren_command_request(const SirenCommandRequest &msg) { void APIServerConnection::on_siren_command_request(const SirenCommandRequest &msg) { this->siren_command(msg); }
if (this->check_authenticated_()) {
this->siren_command(msg);
}
}
#endif #endif
#ifdef USE_SWITCH #ifdef USE_SWITCH
void APIServerConnection::on_switch_command_request(const SwitchCommandRequest &msg) { void APIServerConnection::on_switch_command_request(const SwitchCommandRequest &msg) { this->switch_command(msg); }
if (this->check_authenticated_()) {
this->switch_command(msg);
}
}
#endif #endif
#ifdef USE_TEXT #ifdef USE_TEXT
void APIServerConnection::on_text_command_request(const TextCommandRequest &msg) { void APIServerConnection::on_text_command_request(const TextCommandRequest &msg) { this->text_command(msg); }
if (this->check_authenticated_()) {
this->text_command(msg);
}
}
#endif #endif
#ifdef USE_DATETIME_TIME #ifdef USE_DATETIME_TIME
void APIServerConnection::on_time_command_request(const TimeCommandRequest &msg) { void APIServerConnection::on_time_command_request(const TimeCommandRequest &msg) { this->time_command(msg); }
if (this->check_authenticated_()) {
this->time_command(msg);
}
}
#endif #endif
#ifdef USE_UPDATE #ifdef USE_UPDATE
void APIServerConnection::on_update_command_request(const UpdateCommandRequest &msg) { void APIServerConnection::on_update_command_request(const UpdateCommandRequest &msg) { this->update_command(msg); }
if (this->check_authenticated_()) {
this->update_command(msg);
}
}
#endif #endif
#ifdef USE_VALVE #ifdef USE_VALVE
void APIServerConnection::on_valve_command_request(const ValveCommandRequest &msg) { void APIServerConnection::on_valve_command_request(const ValveCommandRequest &msg) { this->valve_command(msg); }
if (this->check_authenticated_()) {
this->valve_command(msg);
}
}
#endif #endif
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request( void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request(
const SubscribeBluetoothLEAdvertisementsRequest &msg) { const SubscribeBluetoothLEAdvertisementsRequest &msg) {
if (this->check_authenticated_()) { this->subscribe_bluetooth_le_advertisements(msg);
this->subscribe_bluetooth_le_advertisements(msg);
}
} }
#endif #endif
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
void APIServerConnection::on_bluetooth_device_request(const BluetoothDeviceRequest &msg) { void APIServerConnection::on_bluetooth_device_request(const BluetoothDeviceRequest &msg) {
if (this->check_authenticated_()) { this->bluetooth_device_request(msg);
this->bluetooth_device_request(msg);
}
} }
#endif #endif
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
void APIServerConnection::on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &msg) { void APIServerConnection::on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &msg) {
if (this->check_authenticated_()) { this->bluetooth_gatt_get_services(msg);
this->bluetooth_gatt_get_services(msg);
}
} }
#endif #endif
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
void APIServerConnection::on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &msg) { void APIServerConnection::on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &msg) {
if (this->check_authenticated_()) { this->bluetooth_gatt_read(msg);
this->bluetooth_gatt_read(msg);
}
} }
#endif #endif
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
void APIServerConnection::on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &msg) { void APIServerConnection::on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &msg) {
if (this->check_authenticated_()) { this->bluetooth_gatt_write(msg);
this->bluetooth_gatt_write(msg);
}
} }
#endif #endif
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
void APIServerConnection::on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &msg) { void APIServerConnection::on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &msg) {
if (this->check_authenticated_()) { this->bluetooth_gatt_read_descriptor(msg);
this->bluetooth_gatt_read_descriptor(msg);
}
} }
#endif #endif
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
void APIServerConnection::on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &msg) { void APIServerConnection::on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &msg) {
if (this->check_authenticated_()) { this->bluetooth_gatt_write_descriptor(msg);
this->bluetooth_gatt_write_descriptor(msg);
}
} }
#endif #endif
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
void APIServerConnection::on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &msg) { void APIServerConnection::on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &msg) {
if (this->check_authenticated_()) { this->bluetooth_gatt_notify(msg);
this->bluetooth_gatt_notify(msg);
}
} }
#endif #endif
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
void APIServerConnection::on_subscribe_bluetooth_connections_free_request( void APIServerConnection::on_subscribe_bluetooth_connections_free_request(
const SubscribeBluetoothConnectionsFreeRequest &msg) { const SubscribeBluetoothConnectionsFreeRequest &msg) {
if (this->check_authenticated_() && !this->send_subscribe_bluetooth_connections_free_response(msg)) { if (!this->send_subscribe_bluetooth_connections_free_response(msg)) {
this->on_fatal_error(); this->on_fatal_error();
} }
} }
@@ -869,45 +779,68 @@ void APIServerConnection::on_subscribe_bluetooth_connections_free_request(
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
void APIServerConnection::on_unsubscribe_bluetooth_le_advertisements_request( void APIServerConnection::on_unsubscribe_bluetooth_le_advertisements_request(
const UnsubscribeBluetoothLEAdvertisementsRequest &msg) { const UnsubscribeBluetoothLEAdvertisementsRequest &msg) {
if (this->check_authenticated_()) { this->unsubscribe_bluetooth_le_advertisements(msg);
this->unsubscribe_bluetooth_le_advertisements(msg);
}
} }
#endif #endif
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
void APIServerConnection::on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &msg) { void APIServerConnection::on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &msg) {
if (this->check_authenticated_()) { this->bluetooth_scanner_set_mode(msg);
this->bluetooth_scanner_set_mode(msg);
}
} }
#endif #endif
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
void APIServerConnection::on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) { void APIServerConnection::on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) {
if (this->check_authenticated_()) { this->subscribe_voice_assistant(msg);
this->subscribe_voice_assistant(msg);
}
} }
#endif #endif
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
void APIServerConnection::on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg) { void APIServerConnection::on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg) {
if (this->check_authenticated_() && !this->send_voice_assistant_get_configuration_response(msg)) { if (!this->send_voice_assistant_get_configuration_response(msg)) {
this->on_fatal_error(); this->on_fatal_error();
} }
} }
#endif #endif
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
void APIServerConnection::on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) { void APIServerConnection::on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) {
if (this->check_authenticated_()) { this->voice_assistant_set_configuration(msg);
this->voice_assistant_set_configuration(msg);
}
} }
#endif #endif
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
void APIServerConnection::on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) { void APIServerConnection::on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) {
if (this->check_authenticated_()) { this->alarm_control_panel_command(msg);
this->alarm_control_panel_command(msg);
}
} }
#endif #endif
#ifdef USE_ZWAVE_PROXY
void APIServerConnection::on_z_wave_proxy_frame(const ZWaveProxyFrame &msg) { this->zwave_proxy_frame(msg); }
#endif
#ifdef USE_ZWAVE_PROXY
void APIServerConnection::on_z_wave_proxy_request(const ZWaveProxyRequest &msg) { this->zwave_proxy_request(msg); }
#endif
void APIServerConnection::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {
// Check authentication/connection requirements for messages
switch (msg_type) {
case HelloRequest::MESSAGE_TYPE: // No setup required
#ifdef USE_API_PASSWORD
case AuthenticationRequest::MESSAGE_TYPE: // No setup required
#endif
case DisconnectRequest::MESSAGE_TYPE: // No setup required
case PingRequest::MESSAGE_TYPE: // No setup required
break; // Skip all checks for these messages
case DeviceInfoRequest::MESSAGE_TYPE: // Connection setup only
if (!this->check_connection_setup_()) {
return; // Connection not setup
}
break;
default:
// All other messages require authentication (which includes connection check)
if (!this->check_authenticated_()) {
return; // Authentication failed
}
break;
}
// Call base implementation to process the message
APIServerConnectionBase::read_message(msg_size, msg_type, msg_data);
}
} // namespace esphome::api } // namespace esphome::api

View File

@@ -26,7 +26,9 @@ class APIServerConnectionBase : public ProtoService {
virtual void on_hello_request(const HelloRequest &value){}; virtual void on_hello_request(const HelloRequest &value){};
virtual void on_connect_request(const ConnectRequest &value){}; #ifdef USE_API_PASSWORD
virtual void on_authentication_request(const AuthenticationRequest &value){};
#endif
virtual void on_disconnect_request(const DisconnectRequest &value){}; virtual void on_disconnect_request(const DisconnectRequest &value){};
virtual void on_disconnect_response(const DisconnectResponse &value){}; virtual void on_disconnect_response(const DisconnectResponse &value){};
@@ -71,7 +73,7 @@ class APIServerConnectionBase : public ProtoService {
#ifdef USE_API_HOMEASSISTANT_STATES #ifdef USE_API_HOMEASSISTANT_STATES
virtual void on_home_assistant_state_response(const HomeAssistantStateResponse &value){}; virtual void on_home_assistant_state_response(const HomeAssistantStateResponse &value){};
#endif #endif
virtual void on_get_time_request(const GetTimeRequest &value){};
virtual void on_get_time_response(const GetTimeResponse &value){}; virtual void on_get_time_response(const GetTimeResponse &value){};
#ifdef USE_API_SERVICES #ifdef USE_API_SERVICES
@@ -205,6 +207,12 @@ class APIServerConnectionBase : public ProtoService {
#ifdef USE_UPDATE #ifdef USE_UPDATE
virtual void on_update_command_request(const UpdateCommandRequest &value){}; virtual void on_update_command_request(const UpdateCommandRequest &value){};
#endif
#ifdef USE_ZWAVE_PROXY
virtual void on_z_wave_proxy_frame(const ZWaveProxyFrame &value){};
#endif
#ifdef USE_ZWAVE_PROXY
virtual void on_z_wave_proxy_request(const ZWaveProxyRequest &value){};
#endif #endif
protected: protected:
void read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override; void read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
@@ -213,7 +221,9 @@ class APIServerConnectionBase : public ProtoService {
class APIServerConnection : public APIServerConnectionBase { class APIServerConnection : public APIServerConnectionBase {
public: public:
virtual bool send_hello_response(const HelloRequest &msg) = 0; virtual bool send_hello_response(const HelloRequest &msg) = 0;
virtual bool send_connect_response(const ConnectRequest &msg) = 0; #ifdef USE_API_PASSWORD
virtual bool send_authenticate_response(const AuthenticationRequest &msg) = 0;
#endif
virtual bool send_disconnect_response(const DisconnectRequest &msg) = 0; virtual bool send_disconnect_response(const DisconnectRequest &msg) = 0;
virtual bool send_ping_response(const PingRequest &msg) = 0; virtual bool send_ping_response(const PingRequest &msg) = 0;
virtual bool send_device_info_response(const DeviceInfoRequest &msg) = 0; virtual bool send_device_info_response(const DeviceInfoRequest &msg) = 0;
@@ -226,7 +236,6 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_API_HOMEASSISTANT_STATES #ifdef USE_API_HOMEASSISTANT_STATES
virtual void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) = 0; virtual void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) = 0;
#endif #endif
virtual bool send_get_time_response(const GetTimeRequest &msg) = 0;
#ifdef USE_API_SERVICES #ifdef USE_API_SERVICES
virtual void execute_service(const ExecuteServiceRequest &msg) = 0; virtual void execute_service(const ExecuteServiceRequest &msg) = 0;
#endif #endif
@@ -332,10 +341,18 @@ class APIServerConnection : public APIServerConnectionBase {
#endif #endif
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
virtual void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) = 0; virtual void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) = 0;
#endif
#ifdef USE_ZWAVE_PROXY
virtual void zwave_proxy_frame(const ZWaveProxyFrame &msg) = 0;
#endif
#ifdef USE_ZWAVE_PROXY
virtual void zwave_proxy_request(const ZWaveProxyRequest &msg) = 0;
#endif #endif
protected: protected:
void on_hello_request(const HelloRequest &msg) override; void on_hello_request(const HelloRequest &msg) override;
void on_connect_request(const ConnectRequest &msg) override; #ifdef USE_API_PASSWORD
void on_authentication_request(const AuthenticationRequest &msg) override;
#endif
void on_disconnect_request(const DisconnectRequest &msg) override; void on_disconnect_request(const DisconnectRequest &msg) override;
void on_ping_request(const PingRequest &msg) override; void on_ping_request(const PingRequest &msg) override;
void on_device_info_request(const DeviceInfoRequest &msg) override; void on_device_info_request(const DeviceInfoRequest &msg) override;
@@ -348,7 +365,6 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_API_HOMEASSISTANT_STATES #ifdef USE_API_HOMEASSISTANT_STATES
void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) override; void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) override;
#endif #endif
void on_get_time_request(const GetTimeRequest &msg) override;
#ifdef USE_API_SERVICES #ifdef USE_API_SERVICES
void on_execute_service_request(const ExecuteServiceRequest &msg) override; void on_execute_service_request(const ExecuteServiceRequest &msg) override;
#endif #endif
@@ -455,6 +471,13 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) override; void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) override;
#endif #endif
#ifdef USE_ZWAVE_PROXY
void on_z_wave_proxy_frame(const ZWaveProxyFrame &msg) override;
#endif
#ifdef USE_ZWAVE_PROXY
void on_z_wave_proxy_request(const ZWaveProxyRequest &msg) override;
#endif
void read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
}; };
} // namespace esphome::api } // namespace esphome::api

View File

@@ -37,12 +37,14 @@ void APIServer::setup() {
this->noise_pref_ = global_preferences->make_preference<SavedNoisePsk>(hash, true); this->noise_pref_ = global_preferences->make_preference<SavedNoisePsk>(hash, true);
#ifndef USE_API_NOISE_PSK_FROM_YAML
// Only load saved PSK if not set from YAML
SavedNoisePsk noise_pref_saved{}; SavedNoisePsk noise_pref_saved{};
if (this->noise_pref_.load(&noise_pref_saved)) { if (this->noise_pref_.load(&noise_pref_saved)) {
ESP_LOGD(TAG, "Loaded saved Noise PSK"); ESP_LOGD(TAG, "Loaded saved Noise PSK");
this->set_noise_psk(noise_pref_saved.psk); this->set_noise_psk(noise_pref_saved.psk);
} }
#endif
#endif #endif
// Schedule reboot if no clients connect within timeout // Schedule reboot if no clients connect within timeout
@@ -85,7 +87,7 @@ void APIServer::setup() {
return; return;
} }
err = this->socket_->listen(4); err = this->socket_->listen(this->listen_backlog_);
if (err != 0) { if (err != 0) {
ESP_LOGW(TAG, "Socket unable to listen: errno %d", errno); ESP_LOGW(TAG, "Socket unable to listen: errno %d", errno);
this->mark_failed(); this->mark_failed();
@@ -138,9 +140,19 @@ void APIServer::loop() {
while (true) { while (true) {
struct sockaddr_storage source_addr; struct sockaddr_storage source_addr;
socklen_t addr_len = sizeof(source_addr); socklen_t addr_len = sizeof(source_addr);
auto sock = this->socket_->accept_loop_monitored((struct sockaddr *) &source_addr, &addr_len); auto sock = this->socket_->accept_loop_monitored((struct sockaddr *) &source_addr, &addr_len);
if (!sock) if (!sock)
break; break;
// Check if we're at the connection limit
if (this->clients_.size() >= this->max_connections_) {
ESP_LOGW(TAG, "Max connections (%d), rejecting %s", this->max_connections_, sock->getpeername().c_str());
// Immediately close - socket destructor will handle cleanup
sock.reset();
continue;
}
ESP_LOGD(TAG, "Accept %s", sock->getpeername().c_str()); ESP_LOGD(TAG, "Accept %s", sock->getpeername().c_str());
auto *conn = new APIConnection(std::move(sock), this); auto *conn = new APIConnection(std::move(sock), this);
@@ -165,7 +177,8 @@ void APIServer::loop() {
// Network is down - disconnect all clients // Network is down - disconnect all clients
for (auto &client : this->clients_) { for (auto &client : this->clients_) {
client->on_fatal_error(); client->on_fatal_error();
ESP_LOGW(TAG, "%s: Network down; disconnect", client->get_client_combined_info().c_str()); ESP_LOGW(TAG, "%s (%s): Network down; disconnect", client->client_info_.name.c_str(),
client->client_info_.peername.c_str());
} }
// Continue to process and clean up the clients below // Continue to process and clean up the clients below
} }
@@ -204,8 +217,10 @@ void APIServer::loop() {
void APIServer::dump_config() { void APIServer::dump_config() {
ESP_LOGCONFIG(TAG, ESP_LOGCONFIG(TAG,
"Server:\n" "Server:\n"
" Address: %s:%u", " Address: %s:%u\n"
network::get_use_address().c_str(), this->port_); " Listen backlog: %u\n"
" Max connections: %u",
network::get_use_address().c_str(), this->port_, this->listen_backlog_, this->max_connections_);
#ifdef USE_API_NOISE #ifdef USE_API_NOISE
ESP_LOGCONFIG(TAG, " Noise encryption: %s", YESNO(this->noise_ctx_->has_psk())); ESP_LOGCONFIG(TAG, " Noise encryption: %s", YESNO(this->noise_ctx_->has_psk()));
if (!this->noise_ctx_->has_psk()) { if (!this->noise_ctx_->has_psk()) {
@@ -217,12 +232,12 @@ void APIServer::dump_config() {
} }
#ifdef USE_API_PASSWORD #ifdef USE_API_PASSWORD
bool APIServer::check_password(const std::string &password) const { bool APIServer::check_password(const uint8_t *password_data, size_t password_len) const {
// depend only on input password length // depend only on input password length
const char *a = this->password_.c_str(); const char *a = this->password_.c_str();
uint32_t len_a = this->password_.length(); uint32_t len_a = this->password_.length();
const char *b = password.c_str(); const char *b = reinterpret_cast<const char *>(password_data);
uint32_t len_b = password.length(); uint32_t len_b = password_len;
// disable optimization with volatile // disable optimization with volatile
volatile uint32_t length = len_b; volatile uint32_t length = len_b;
@@ -245,6 +260,7 @@ bool APIServer::check_password(const std::string &password) const {
return result == 0; return result == 0;
} }
#endif #endif
void APIServer::handle_disconnect(APIConnection *conn) {} void APIServer::handle_disconnect(APIConnection *conn) {}
@@ -355,6 +371,15 @@ void APIServer::on_update(update::UpdateEntity *obj) {
} }
#endif #endif
#ifdef USE_ZWAVE_PROXY
void APIServer::on_zwave_proxy_request(const esphome::api::ProtoMessage &msg) {
// We could add code to manage a second subscription type, but, since this message type is
// very infrequent and small, we simply send it to all clients
for (auto &c : this->clients_)
c->send_message(msg, api::ZWaveProxyRequest::MESSAGE_TYPE);
}
#endif
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
API_DISPATCH_UPDATE(alarm_control_panel::AlarmControlPanel, alarm_control_panel) API_DISPATCH_UPDATE(alarm_control_panel::AlarmControlPanel, alarm_control_panel)
#endif #endif
@@ -370,9 +395,9 @@ 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; } void APIServer::set_batch_delay(uint16_t batch_delay) { this->batch_delay_ = batch_delay; }
#ifdef USE_API_HOMEASSISTANT_SERVICES #ifdef USE_API_HOMEASSISTANT_SERVICES
void APIServer::send_homeassistant_service_call(const HomeassistantServiceResponse &call) { void APIServer::send_homeassistant_action(const HomeassistantActionRequest &call) {
for (auto &client : this->clients_) { for (auto &client : this->clients_) {
client->send_homeassistant_service_call(call); client->send_homeassistant_action(call);
} }
} }
#endif #endif
@@ -409,6 +434,12 @@ void APIServer::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeo
#ifdef USE_API_NOISE #ifdef USE_API_NOISE
bool APIServer::save_noise_psk(psk_t psk, bool make_active) { bool APIServer::save_noise_psk(psk_t psk, bool make_active) {
#ifdef USE_API_NOISE_PSK_FROM_YAML
// When PSK is set from YAML, this function should never be called
// but if it is, reject the change
ESP_LOGW(TAG, "Key set in YAML");
return false;
#else
auto &old_psk = this->noise_ctx_->get_psk(); auto &old_psk = this->noise_ctx_->get_psk();
if (std::equal(old_psk.begin(), old_psk.end(), psk.begin())) { if (std::equal(old_psk.begin(), old_psk.end(), psk.begin())) {
ESP_LOGW(TAG, "New PSK matches old"); ESP_LOGW(TAG, "New PSK matches old");
@@ -437,6 +468,7 @@ bool APIServer::save_noise_psk(psk_t psk, bool make_active) {
}); });
} }
return true; return true;
#endif
} }
#endif #endif

View File

@@ -37,13 +37,15 @@ class APIServer : public Component, public Controller {
void on_shutdown() override; void on_shutdown() override;
bool teardown() override; bool teardown() override;
#ifdef USE_API_PASSWORD #ifdef USE_API_PASSWORD
bool check_password(const std::string &password) const; bool check_password(const uint8_t *password_data, size_t password_len) const;
void set_password(const std::string &password); void set_password(const std::string &password);
#endif #endif
void set_port(uint16_t port); void set_port(uint16_t port);
void set_reboot_timeout(uint32_t reboot_timeout); void set_reboot_timeout(uint32_t reboot_timeout);
void set_batch_delay(uint16_t batch_delay); void set_batch_delay(uint16_t batch_delay);
uint16_t get_batch_delay() const { return batch_delay_; } uint16_t get_batch_delay() const { return batch_delay_; }
void set_listen_backlog(uint8_t listen_backlog) { this->listen_backlog_ = listen_backlog; }
void set_max_connections(uint8_t max_connections) { this->max_connections_ = max_connections; }
// Get reference to shared buffer for API connections // Get reference to shared buffer for API connections
std::vector<uint8_t> &get_shared_buffer_ref() { return shared_write_buffer_; } std::vector<uint8_t> &get_shared_buffer_ref() { return shared_write_buffer_; }
@@ -107,7 +109,8 @@ class APIServer : public Component, public Controller {
void on_media_player_update(media_player::MediaPlayer *obj) override; void on_media_player_update(media_player::MediaPlayer *obj) override;
#endif #endif
#ifdef USE_API_HOMEASSISTANT_SERVICES #ifdef USE_API_HOMEASSISTANT_SERVICES
void send_homeassistant_service_call(const HomeassistantServiceResponse &call); void send_homeassistant_action(const HomeassistantActionRequest &call);
#endif #endif
#ifdef USE_API_SERVICES #ifdef USE_API_SERVICES
void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); } void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }
@@ -125,6 +128,9 @@ class APIServer : public Component, public Controller {
#ifdef USE_UPDATE #ifdef USE_UPDATE
void on_update(update::UpdateEntity *obj) override; void on_update(update::UpdateEntity *obj) override;
#endif #endif
#ifdef USE_ZWAVE_PROXY
void on_zwave_proxy_request(const esphome::api::ProtoMessage &msg);
#endif
bool is_connected() const; bool is_connected() const;
@@ -185,8 +191,12 @@ class APIServer : public Component, public Controller {
// Group smaller types together // Group smaller types together
uint16_t port_{6053}; uint16_t port_{6053};
uint16_t batch_delay_{100}; uint16_t batch_delay_{100};
// Connection limits - these defaults will be overridden by config values
// from cv.SplitDefault in __init__.py which sets platform-specific defaults
uint8_t listen_backlog_{4};
uint8_t max_connections_{8};
bool shutting_down_ = false; bool shutting_down_ = false;
// 5 bytes used, 3 bytes padding // 7 bytes used, 1 byte padding
#ifdef USE_API_NOISE #ifdef USE_API_NOISE
std::shared_ptr<APINoiseContext> noise_ctx_ = std::make_shared<APINoiseContext>(); std::shared_ptr<APINoiseContext> noise_ctx_ = std::make_shared<APINoiseContext>();

View File

@@ -62,9 +62,11 @@ async def async_run_logs(config: dict[str, Any], addresses: list[str]) -> None:
time_ = datetime.now() time_ = datetime.now()
message: bytes = msg.message message: bytes = msg.message
text = message.decode("utf8", "backslashreplace") text = message.decode("utf8", "backslashreplace")
for parsed_msg in parse_log_message( nanoseconds = time_.microsecond // 1000
text, f"[{time_.hour:02}:{time_.minute:02}:{time_.second:02}]" timestamp = (
): f"[{time_.hour:02}:{time_.minute:02}:{time_.second:02}.{nanoseconds:03}]"
)
for parsed_msg in parse_log_message(text, timestamp):
print(parsed_msg.replace("\033", "\\033") if dashboard else parsed_msg) print(parsed_msg.replace("\033", "\\033") if dashboard else parsed_msg)
stop = await async_run(cli, on_log, name=name) stop = await async_run(cli, on_log, name=name)

View File

@@ -179,9 +179,9 @@ class CustomAPIDevice {
* @param service_name The service to call. * @param service_name The service to call.
*/ */
void call_homeassistant_service(const std::string &service_name) { void call_homeassistant_service(const std::string &service_name) {
HomeassistantServiceResponse resp; HomeassistantActionRequest resp;
resp.set_service(StringRef(service_name)); resp.set_service(StringRef(service_name));
global_api_server->send_homeassistant_service_call(resp); global_api_server->send_homeassistant_action(resp);
} }
/** Call a Home Assistant service from ESPHome. /** Call a Home Assistant service from ESPHome.
@@ -199,7 +199,7 @@ class CustomAPIDevice {
* @param data The data for the service call, mapping from string to string. * @param data The data for the service call, mapping from string to string.
*/ */
void call_homeassistant_service(const std::string &service_name, const std::map<std::string, std::string> &data) { void call_homeassistant_service(const std::string &service_name, const std::map<std::string, std::string> &data) {
HomeassistantServiceResponse resp; HomeassistantActionRequest resp;
resp.set_service(StringRef(service_name)); resp.set_service(StringRef(service_name));
for (auto &it : data) { for (auto &it : data) {
resp.data.emplace_back(); resp.data.emplace_back();
@@ -207,7 +207,7 @@ class CustomAPIDevice {
kv.set_key(StringRef(it.first)); kv.set_key(StringRef(it.first));
kv.value = it.second; kv.value = it.second;
} }
global_api_server->send_homeassistant_service_call(resp); global_api_server->send_homeassistant_action(resp);
} }
/** Fire an ESPHome event in Home Assistant. /** Fire an ESPHome event in Home Assistant.
@@ -221,10 +221,10 @@ class CustomAPIDevice {
* @param event_name The event to fire. * @param event_name The event to fire.
*/ */
void fire_homeassistant_event(const std::string &event_name) { void fire_homeassistant_event(const std::string &event_name) {
HomeassistantServiceResponse resp; HomeassistantActionRequest resp;
resp.set_service(StringRef(event_name)); resp.set_service(StringRef(event_name));
resp.is_event = true; resp.is_event = true;
global_api_server->send_homeassistant_service_call(resp); global_api_server->send_homeassistant_action(resp);
} }
/** Fire an ESPHome event in Home Assistant. /** Fire an ESPHome event in Home Assistant.
@@ -241,7 +241,7 @@ class CustomAPIDevice {
* @param data The data for the event, mapping from string to string. * @param data The data for the event, mapping from string to string.
*/ */
void fire_homeassistant_event(const std::string &service_name, const std::map<std::string, std::string> &data) { void fire_homeassistant_event(const std::string &service_name, const std::map<std::string, std::string> &data) {
HomeassistantServiceResponse resp; HomeassistantActionRequest resp;
resp.set_service(StringRef(service_name)); resp.set_service(StringRef(service_name));
resp.is_event = true; resp.is_event = true;
for (auto &it : data) { for (auto &it : data) {
@@ -250,7 +250,7 @@ class CustomAPIDevice {
kv.set_key(StringRef(it.first)); kv.set_key(StringRef(it.first));
kv.value = it.second; kv.value = it.second;
} }
global_api_server->send_homeassistant_service_call(resp); global_api_server->send_homeassistant_action(resp);
} }
#else #else
template<typename T = void> void call_homeassistant_service(const std::string &service_name) { template<typename T = void> void call_homeassistant_service(const std::string &service_name) {

View File

@@ -3,10 +3,10 @@
#include "api_server.h" #include "api_server.h"
#ifdef USE_API #ifdef USE_API
#ifdef USE_API_HOMEASSISTANT_SERVICES #ifdef USE_API_HOMEASSISTANT_SERVICES
#include <vector>
#include "api_pb2.h" #include "api_pb2.h"
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include <vector>
namespace esphome::api { namespace esphome::api {
@@ -62,7 +62,7 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
} }
void play(Ts... x) override { void play(Ts... x) override {
HomeassistantServiceResponse resp; HomeassistantActionRequest resp;
std::string service_value = this->service_.value(x...); std::string service_value = this->service_.value(x...);
resp.set_service(StringRef(service_value)); resp.set_service(StringRef(service_value));
resp.is_event = this->is_event_; resp.is_event = this->is_event_;
@@ -84,7 +84,7 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
kv.set_key(StringRef(it.key)); kv.set_key(StringRef(it.key));
kv.value = it.value.value(x...); kv.value = it.value.value(x...);
} }
this->parent_->send_homeassistant_service_call(resp); this->parent_->send_homeassistant_action(resp);
} }
protected: protected:

View File

@@ -182,6 +182,10 @@ class ProtoLengthDelimited {
explicit ProtoLengthDelimited(const uint8_t *value, size_t length) : value_(value), length_(length) {} explicit ProtoLengthDelimited(const uint8_t *value, size_t length) : value_(value), length_(length) {}
std::string as_string() const { return std::string(reinterpret_cast<const char *>(this->value_), this->length_); } std::string as_string() const { return std::string(reinterpret_cast<const char *>(this->value_), this->length_); }
// Direct access to raw data without string allocation
const uint8_t *data() const { return this->value_; }
size_t size() const { return this->length_; }
/** /**
* Decode the length-delimited data into an existing ProtoDecodableMessage instance. * Decode the length-delimited data into an existing ProtoDecodableMessage instance.
* *
@@ -827,7 +831,7 @@ class ProtoService {
} }
// Authentication helper methods // Authentication helper methods
bool check_connection_setup_() { inline bool check_connection_setup_() {
if (!this->is_connection_setup()) { if (!this->is_connection_setup()) {
this->on_no_setup_connection(); this->on_no_setup_connection();
return false; return false;
@@ -835,7 +839,7 @@ class ProtoService {
return true; return true;
} }
bool check_authenticated_() { inline bool check_authenticated_() {
#ifdef USE_API_PASSWORD #ifdef USE_API_PASSWORD
if (!this->check_connection_setup_()) { if (!this->check_connection_setup_()) {
return false; return false;

View File

@@ -55,7 +55,7 @@ template<typename... Ts> class UserServiceBase : public UserServiceDescriptor {
protected: protected:
virtual void execute(Ts... x) = 0; virtual void execute(Ts... x) = 0;
template<int... S> void execute_(std::vector<ExecuteServiceArgument> args, seq<S...> type) { template<int... S> void execute_(const std::vector<ExecuteServiceArgument> &args, seq<S...> type) {
this->execute((get_execute_arg_value<Ts>(args[S]))...); this->execute((get_execute_arg_value<Ts>(args[S]))...);
} }

View File

@@ -2,6 +2,7 @@ import esphome.codegen as cg
from esphome.components import i2c, sensor from esphome.components import i2c, sensor
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_CLEAR,
CONF_GAIN, CONF_GAIN,
CONF_ID, CONF_ID,
DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_ILLUMINANCE,
@@ -29,7 +30,6 @@ CONF_F5 = "f5"
CONF_F6 = "f6" CONF_F6 = "f6"
CONF_F7 = "f7" CONF_F7 = "f7"
CONF_F8 = "f8" CONF_F8 = "f8"
CONF_CLEAR = "clear"
CONF_NIR = "nir" CONF_NIR = "nir"
UNIT_COUNTS = "#" UNIT_COUNTS = "#"

View File

@@ -8,7 +8,7 @@ from esphome.const import (
PLATFORM_LN882X, PLATFORM_LN882X,
PLATFORM_RTL87XX, PLATFORM_RTL87XX,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, CoroPriority, coroutine_with_priority
CODEOWNERS = ["@esphome/core"] CODEOWNERS = ["@esphome/core"]
@@ -27,7 +27,7 @@ CONFIG_SCHEMA = cv.All(
) )
@coroutine_with_priority(200.0) @coroutine_with_priority(CoroPriority.NETWORK_TRANSPORT)
async def to_code(config): async def to_code(config):
if CORE.is_esp32 or CORE.is_libretiny: if CORE.is_esp32 or CORE.is_libretiny:
# https://github.com/ESP32Async/AsyncTCP # https://github.com/ESP32Async/AsyncTCP

View File

@@ -16,6 +16,7 @@ from esphome.const import (
DEVICE_CLASS_ENERGY, DEVICE_CLASS_ENERGY,
DEVICE_CLASS_POWER, DEVICE_CLASS_POWER,
DEVICE_CLASS_POWER_FACTOR, DEVICE_CLASS_POWER_FACTOR,
DEVICE_CLASS_REACTIVE_POWER,
DEVICE_CLASS_VOLTAGE, DEVICE_CLASS_VOLTAGE,
ICON_CURRENT_AC, ICON_CURRENT_AC,
ICON_LIGHTBULB, ICON_LIGHTBULB,
@@ -78,6 +79,7 @@ CONFIG_SCHEMA = (
unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE, unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE,
icon=ICON_LIGHTBULB, icon=ICON_LIGHTBULB,
accuracy_decimals=2, accuracy_decimals=2,
device_class=DEVICE_CLASS_REACTIVE_POWER,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
), ),
cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema( cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(

View File

@@ -17,10 +17,12 @@ from esphome.const import (
CONF_REACTIVE_POWER, CONF_REACTIVE_POWER,
CONF_REVERSE_ACTIVE_ENERGY, CONF_REVERSE_ACTIVE_ENERGY,
CONF_VOLTAGE, CONF_VOLTAGE,
DEVICE_CLASS_APPARENT_POWER,
DEVICE_CLASS_CURRENT, DEVICE_CLASS_CURRENT,
DEVICE_CLASS_ENERGY, DEVICE_CLASS_ENERGY,
DEVICE_CLASS_POWER, DEVICE_CLASS_POWER,
DEVICE_CLASS_POWER_FACTOR, DEVICE_CLASS_POWER_FACTOR,
DEVICE_CLASS_REACTIVE_POWER,
DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLTAGE, DEVICE_CLASS_VOLTAGE,
ENTITY_CATEGORY_DIAGNOSTIC, ENTITY_CATEGORY_DIAGNOSTIC,
@@ -100,13 +102,13 @@ ATM90E32_PHASE_SCHEMA = cv.Schema(
unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE, unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE,
icon=ICON_LIGHTBULB, icon=ICON_LIGHTBULB,
accuracy_decimals=2, accuracy_decimals=2,
device_class=DEVICE_CLASS_POWER, device_class=DEVICE_CLASS_REACTIVE_POWER,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
), ),
cv.Optional(CONF_APPARENT_POWER): sensor.sensor_schema( cv.Optional(CONF_APPARENT_POWER): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT_AMPS, unit_of_measurement=UNIT_VOLT_AMPS,
accuracy_decimals=2, accuracy_decimals=2,
device_class=DEVICE_CLASS_POWER, device_class=DEVICE_CLASS_APPARENT_POWER,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
), ),
cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema( cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(

View File

@@ -2,7 +2,7 @@ from esphome import automation
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_MIC_GAIN from esphome.const import CONF_ID, CONF_MIC_GAIN
from esphome.core import coroutine_with_priority from esphome.core import CoroPriority, coroutine_with_priority
CODEOWNERS = ["@kbx81"] CODEOWNERS = ["@kbx81"]
IS_PLATFORM_COMPONENT = True IS_PLATFORM_COMPONENT = True
@@ -35,7 +35,7 @@ async def audio_adc_set_mic_gain_to_code(config, action_id, template_arg, args):
return var return var
@coroutine_with_priority(100.0) @coroutine_with_priority(CoroPriority.CORE)
async def to_code(config): async def to_code(config):
cg.add_define("USE_AUDIO_ADC") cg.add_define("USE_AUDIO_ADC")
cg.add_global(audio_adc_ns.using) cg.add_global(audio_adc_ns.using)

View File

@@ -3,7 +3,7 @@ from esphome.automation import maybe_simple_id
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_VOLUME from esphome.const import CONF_ID, CONF_VOLUME
from esphome.core import coroutine_with_priority from esphome.core import CoroPriority, coroutine_with_priority
CODEOWNERS = ["@kbx81"] CODEOWNERS = ["@kbx81"]
IS_PLATFORM_COMPONENT = True IS_PLATFORM_COMPONENT = True
@@ -51,7 +51,7 @@ async def audio_dac_set_volume_to_code(config, action_id, template_arg, args):
return var return var
@coroutine_with_priority(100.0) @coroutine_with_priority(CoroPriority.CORE)
async def to_code(config): async def to_code(config):
cg.add_define("USE_AUDIO_DAC") cg.add_define("USE_AUDIO_DAC")
cg.add_global(audio_dac_ns.using) cg.add_global(audio_dac_ns.using)

View File

@@ -12,7 +12,7 @@ constexpr static const uint8_t AXS_READ_TOUCHPAD[11] = {0xb5, 0xab, 0xa5, 0x5a,
#define ERROR_CHECK(err) \ #define ERROR_CHECK(err) \
if ((err) != i2c::ERROR_OK) { \ if ((err) != i2c::ERROR_OK) { \
this->status_set_warning("Failed to communicate"); \ this->status_set_warning(LOG_STR("Failed to communicate")); \
return; \ return; \
} }

View File

@@ -493,7 +493,7 @@ void BedJetHub::dump_config() {
" ble_client.app_id: %d\n" " ble_client.app_id: %d\n"
" ble_client.conn_id: %d", " ble_client.conn_id: %d",
this->get_name().c_str(), this->parent()->app_id, this->parent()->get_conn_id()); this->get_name().c_str(), this->parent()->app_id, this->parent()->get_conn_id());
LOG_UPDATE_INTERVAL(this) LOG_UPDATE_INTERVAL(this);
ESP_LOGCONFIG(TAG, " Child components (%d):", this->children_.size()); ESP_LOGCONFIG(TAG, " Child components (%d):", this->children_.size());
for (auto *child : this->children_) { for (auto *child : this->children_) {
ESP_LOGCONFIG(TAG, " - %s", child->describe().c_str()); ESP_LOGCONFIG(TAG, " - %s", child->describe().c_str());

View File

@@ -59,7 +59,7 @@ from esphome.const import (
DEVICE_CLASS_VIBRATION, DEVICE_CLASS_VIBRATION,
DEVICE_CLASS_WINDOW, DEVICE_CLASS_WINDOW,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, CoroPriority, coroutine_with_priority
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
from esphome.cpp_generator import MockObjClass from esphome.cpp_generator import MockObjClass
from esphome.util import Registry from esphome.util import Registry
@@ -652,7 +652,7 @@ async def binary_sensor_is_off_to_code(config, condition_id, template_arg, args)
return cg.new_Pvariable(condition_id, template_arg, paren, False) return cg.new_Pvariable(condition_id, template_arg, paren, False)
@coroutine_with_priority(100.0) @coroutine_with_priority(CoroPriority.CORE)
async def to_code(config): async def to_code(config):
cg.add_global(binary_sensor_ns.using) cg.add_global(binary_sensor_ns.using)

View File

@@ -15,8 +15,8 @@ void log_binary_sensor(const char *tag, const char *prefix, const char *type, Bi
ESP_LOGCONFIG(tag, "%s%s '%s'", prefix, type, obj->get_name().c_str()); ESP_LOGCONFIG(tag, "%s%s '%s'", prefix, type, obj->get_name().c_str());
if (!obj->get_device_class().empty()) { if (!obj->get_device_class_ref().empty()) {
ESP_LOGCONFIG(tag, "%s Device Class: '%s'", prefix, obj->get_device_class().c_str()); ESP_LOGCONFIG(tag, "%s Device Class: '%s'", prefix, obj->get_device_class_ref().c_str());
} }
} }

View File

@@ -1 +1,6 @@
CODEOWNERS = ["@tobias-"] import esphome.codegen as cg
CODEOWNERS = ["@tobias-", "@dan-s-github"]
CONF_BL0940_ID = "bl0940_id"
bl0940_ns = cg.esphome_ns.namespace("bl0940")

View File

@@ -7,28 +7,26 @@ namespace bl0940 {
static const char *const TAG = "bl0940"; static const char *const TAG = "bl0940";
static const uint8_t BL0940_READ_COMMAND = 0x50; // 0x58 according to documentation
static const uint8_t BL0940_FULL_PACKET = 0xAA; static const uint8_t BL0940_FULL_PACKET = 0xAA;
static const uint8_t BL0940_PACKET_HEADER = 0x55; // 0x58 according to documentation static const uint8_t BL0940_PACKET_HEADER = 0x55; // 0x58 according to en doc but 0x55 in cn doc
static const uint8_t BL0940_WRITE_COMMAND = 0xA0; // 0xA8 according to documentation
static const uint8_t BL0940_REG_I_FAST_RMS_CTRL = 0x10; static const uint8_t BL0940_REG_I_FAST_RMS_CTRL = 0x10;
static const uint8_t BL0940_REG_MODE = 0x18; static const uint8_t BL0940_REG_MODE = 0x18;
static const uint8_t BL0940_REG_SOFT_RESET = 0x19; static const uint8_t BL0940_REG_SOFT_RESET = 0x19;
static const uint8_t BL0940_REG_USR_WRPROT = 0x1A; static const uint8_t BL0940_REG_USR_WRPROT = 0x1A;
static const uint8_t BL0940_REG_TPS_CTRL = 0x1B; static const uint8_t BL0940_REG_TPS_CTRL = 0x1B;
const uint8_t BL0940_INIT[5][6] = { static const uint8_t BL0940_INIT[5][5] = {
// Reset to default // Reset to default
{BL0940_WRITE_COMMAND, BL0940_REG_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x38}, {BL0940_REG_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x38},
// Enable User Operation Write // Enable User Operation Write
{BL0940_WRITE_COMMAND, BL0940_REG_USR_WRPROT, 0x55, 0x00, 0x00, 0xF0}, {BL0940_REG_USR_WRPROT, 0x55, 0x00, 0x00, 0xF0},
// 0x0100 = CF_UNABLE energy pulse, AC_FREQ_SEL 50Hz, RMS_UPDATE_SEL 800mS // 0x0100 = CF_UNABLE energy pulse, AC_FREQ_SEL 50Hz, RMS_UPDATE_SEL 800mS
{BL0940_WRITE_COMMAND, BL0940_REG_MODE, 0x00, 0x10, 0x00, 0x37}, {BL0940_REG_MODE, 0x00, 0x10, 0x00, 0x37},
// 0x47FF = Over-current and leakage alarm on, Automatic temperature measurement, Interval 100mS // 0x47FF = Over-current and leakage alarm on, Automatic temperature measurement, Interval 100mS
{BL0940_WRITE_COMMAND, BL0940_REG_TPS_CTRL, 0xFF, 0x47, 0x00, 0xFE}, {BL0940_REG_TPS_CTRL, 0xFF, 0x47, 0x00, 0xFE},
// 0x181C = Half cycle, Fast RMS threshold 6172 // 0x181C = Half cycle, Fast RMS threshold 6172
{BL0940_WRITE_COMMAND, BL0940_REG_I_FAST_RMS_CTRL, 0x1C, 0x18, 0x00, 0x1B}}; {BL0940_REG_I_FAST_RMS_CTRL, 0x1C, 0x18, 0x00, 0x1B}};
void BL0940::loop() { void BL0940::loop() {
DataPacket buffer; DataPacket buffer;
@@ -36,8 +34,8 @@ void BL0940::loop() {
return; return;
} }
if (read_array((uint8_t *) &buffer, sizeof(buffer))) { if (read_array((uint8_t *) &buffer, sizeof(buffer))) {
if (validate_checksum(&buffer)) { if (this->validate_checksum_(&buffer)) {
received_package_(&buffer); this->received_package_(&buffer);
} }
} else { } else {
ESP_LOGW(TAG, "Junk on wire. Throwing away partial message"); ESP_LOGW(TAG, "Junk on wire. Throwing away partial message");
@@ -46,35 +44,151 @@ void BL0940::loop() {
} }
} }
bool BL0940::validate_checksum(const DataPacket *data) { bool BL0940::validate_checksum_(DataPacket *data) {
uint8_t checksum = BL0940_READ_COMMAND; uint8_t checksum = this->read_command_;
// Whole package but checksum // Whole package but checksum
for (uint32_t i = 0; i < sizeof(data->raw) - 1; i++) { uint8_t *raw = (uint8_t *) data;
checksum += data->raw[i]; for (uint32_t i = 0; i < sizeof(*data) - 1; i++) {
checksum += raw[i];
} }
checksum ^= 0xFF; checksum ^= 0xFF;
if (checksum != data->checksum) { if (checksum != data->checksum) {
ESP_LOGW(TAG, "BL0940 invalid checksum! 0x%02X != 0x%02X", checksum, data->checksum); ESP_LOGW(TAG, "Invalid checksum! 0x%02X != 0x%02X", checksum, data->checksum);
} }
return checksum == data->checksum; return checksum == data->checksum;
} }
void BL0940::update() { void BL0940::update() {
this->flush(); this->flush();
this->write_byte(BL0940_READ_COMMAND); this->write_byte(this->read_command_);
this->write_byte(BL0940_FULL_PACKET); this->write_byte(BL0940_FULL_PACKET);
} }
void BL0940::setup() { void BL0940::setup() {
#ifdef USE_NUMBER
// add calibration callbacks
if (this->voltage_calibration_number_ != nullptr) {
this->voltage_calibration_number_->add_on_state_callback(
[this](float state) { this->voltage_calibration_callback_(state); });
if (this->voltage_calibration_number_->has_state()) {
this->voltage_calibration_callback_(this->voltage_calibration_number_->state);
}
}
if (this->current_calibration_number_ != nullptr) {
this->current_calibration_number_->add_on_state_callback(
[this](float state) { this->current_calibration_callback_(state); });
if (this->current_calibration_number_->has_state()) {
this->current_calibration_callback_(this->current_calibration_number_->state);
}
}
if (this->power_calibration_number_ != nullptr) {
this->power_calibration_number_->add_on_state_callback(
[this](float state) { this->power_calibration_callback_(state); });
if (this->power_calibration_number_->has_state()) {
this->power_calibration_callback_(this->power_calibration_number_->state);
}
}
if (this->energy_calibration_number_ != nullptr) {
this->energy_calibration_number_->add_on_state_callback(
[this](float state) { this->energy_calibration_callback_(state); });
if (this->energy_calibration_number_->has_state()) {
this->energy_calibration_callback_(this->energy_calibration_number_->state);
}
}
#endif
// calculate calibrated reference values
this->voltage_reference_cal_ = this->voltage_reference_ / this->voltage_cal_;
this->current_reference_cal_ = this->current_reference_ / this->current_cal_;
this->power_reference_cal_ = this->power_reference_ / this->power_cal_;
this->energy_reference_cal_ = this->energy_reference_ / this->energy_cal_;
for (auto *i : BL0940_INIT) { for (auto *i : BL0940_INIT) {
this->write_array(i, 6); this->write_byte(this->write_command_), this->write_array(i, 5);
delay(1); delay(1);
} }
this->flush(); this->flush();
} }
float BL0940::update_temp_(sensor::Sensor *sensor, ube16_t temperature) const { float BL0940::calculate_power_reference_() {
auto tb = (float) (temperature.h << 8 | temperature.l); // calculate power reference based on voltage and current reference
return this->voltage_reference_cal_ * this->current_reference_cal_ * 4046 / 324004 / 79931;
}
float BL0940::calculate_energy_reference_() {
// formula: 3600000 * 4046 * RL * R1 * 1000 / (1638.4 * 256) / Vref² / (R1 + R2)
// or: power_reference_ * 3600000 / (1638.4 * 256)
return this->power_reference_cal_ * 3600000 / (1638.4 * 256);
}
float BL0940::calculate_calibration_value_(float state) { return (100 + state) / 100; }
void BL0940::reset_calibration() {
#ifdef USE_NUMBER
if (this->current_calibration_number_ != nullptr && this->current_cal_ != 1) {
this->current_calibration_number_->make_call().set_value(0).perform();
}
if (this->voltage_calibration_number_ != nullptr && this->voltage_cal_ != 1) {
this->voltage_calibration_number_->make_call().set_value(0).perform();
}
if (this->power_calibration_number_ != nullptr && this->power_cal_ != 1) {
this->power_calibration_number_->make_call().set_value(0).perform();
}
if (this->energy_calibration_number_ != nullptr && this->energy_cal_ != 1) {
this->energy_calibration_number_->make_call().set_value(0).perform();
}
#endif
ESP_LOGD(TAG, "external calibration values restored to initial state");
}
void BL0940::current_calibration_callback_(float state) {
this->current_cal_ = this->calculate_calibration_value_(state);
ESP_LOGV(TAG, "update current calibration state: %f", this->current_cal_);
this->recalibrate_();
}
void BL0940::voltage_calibration_callback_(float state) {
this->voltage_cal_ = this->calculate_calibration_value_(state);
ESP_LOGV(TAG, "update voltage calibration state: %f", this->voltage_cal_);
this->recalibrate_();
}
void BL0940::power_calibration_callback_(float state) {
this->power_cal_ = this->calculate_calibration_value_(state);
ESP_LOGV(TAG, "update power calibration state: %f", this->power_cal_);
this->recalibrate_();
}
void BL0940::energy_calibration_callback_(float state) {
this->energy_cal_ = this->calculate_calibration_value_(state);
ESP_LOGV(TAG, "update energy calibration state: %f", this->energy_cal_);
this->recalibrate_();
}
void BL0940::recalibrate_() {
ESP_LOGV(TAG, "Recalibrating reference values");
this->voltage_reference_cal_ = this->voltage_reference_ / this->voltage_cal_;
this->current_reference_cal_ = this->current_reference_ / this->current_cal_;
if (this->voltage_cal_ != 1 || this->current_cal_ != 1) {
this->power_reference_ = this->calculate_power_reference_();
}
this->power_reference_cal_ = this->power_reference_ / this->power_cal_;
if (this->voltage_cal_ != 1 || this->current_cal_ != 1 || this->power_cal_ != 1) {
this->energy_reference_ = this->calculate_energy_reference_();
}
this->energy_reference_cal_ = this->energy_reference_ / this->energy_cal_;
ESP_LOGD(TAG,
"Recalibrated reference values:\n"
"Voltage: %f\n, Current: %f\n, Power: %f\n, Energy: %f\n",
this->voltage_reference_cal_, this->current_reference_cal_, this->power_reference_cal_,
this->energy_reference_cal_);
}
float BL0940::update_temp_(sensor::Sensor *sensor, uint16_le_t temperature) const {
auto tb = (float) temperature;
float converted_temp = ((float) 170 / 448) * (tb / 2 - 32) - 45; float converted_temp = ((float) 170 / 448) * (tb / 2 - 32) - 45;
if (sensor != nullptr) { if (sensor != nullptr) {
if (sensor->has_state() && std::abs(converted_temp - sensor->get_state()) > max_temperature_diff_) { if (sensor->has_state() && std::abs(converted_temp - sensor->get_state()) > max_temperature_diff_) {
@@ -87,33 +201,40 @@ float BL0940::update_temp_(sensor::Sensor *sensor, ube16_t temperature) const {
return converted_temp; return converted_temp;
} }
void BL0940::received_package_(const DataPacket *data) const { void BL0940::received_package_(DataPacket *data) {
// Bad header // Bad header
if (data->frame_header != BL0940_PACKET_HEADER) { if (data->frame_header != BL0940_PACKET_HEADER) {
ESP_LOGI(TAG, "Invalid data. Header mismatch: %d", data->frame_header); ESP_LOGI(TAG, "Invalid data. Header mismatch: %d", data->frame_header);
return; return;
} }
float v_rms = (float) to_uint32_t(data->v_rms) / voltage_reference_; // cf_cnt is only 24 bits, so track overflows
float i_rms = (float) to_uint32_t(data->i_rms) / current_reference_; uint32_t cf_cnt = (uint24_t) data->cf_cnt;
float watt = (float) to_int32_t(data->watt) / power_reference_; cf_cnt |= this->prev_cf_cnt_ & 0xff000000;
uint32_t cf_cnt = to_uint32_t(data->cf_cnt); if (cf_cnt < this->prev_cf_cnt_) {
float total_energy_consumption = (float) cf_cnt / energy_reference_; cf_cnt += 0x1000000;
}
this->prev_cf_cnt_ = cf_cnt;
float tps1 = update_temp_(internal_temperature_sensor_, data->tps1); float v_rms = (uint24_t) data->v_rms / this->voltage_reference_cal_;
float tps2 = update_temp_(external_temperature_sensor_, data->tps2); float i_rms = (uint24_t) data->i_rms / this->current_reference_cal_;
float watt = (int24_t) data->watt / this->power_reference_cal_;
float total_energy_consumption = cf_cnt / this->energy_reference_cal_;
if (voltage_sensor_ != nullptr) { float tps1 = update_temp_(this->internal_temperature_sensor_, data->tps1);
voltage_sensor_->publish_state(v_rms); float tps2 = update_temp_(this->external_temperature_sensor_, data->tps2);
if (this->voltage_sensor_ != nullptr) {
this->voltage_sensor_->publish_state(v_rms);
} }
if (current_sensor_ != nullptr) { if (this->current_sensor_ != nullptr) {
current_sensor_->publish_state(i_rms); this->current_sensor_->publish_state(i_rms);
} }
if (power_sensor_ != nullptr) { if (this->power_sensor_ != nullptr) {
power_sensor_->publish_state(watt); this->power_sensor_->publish_state(watt);
} }
if (energy_sensor_ != nullptr) { if (this->energy_sensor_ != nullptr) {
energy_sensor_->publish_state(total_energy_consumption); this->energy_sensor_->publish_state(total_energy_consumption);
} }
ESP_LOGV(TAG, "BL0940: U %fV, I %fA, P %fW, Cnt %" PRId32 ", ∫P %fkWh, T1 %f°C, T2 %f°C", v_rms, i_rms, watt, cf_cnt, ESP_LOGV(TAG, "BL0940: U %fV, I %fA, P %fW, Cnt %" PRId32 ", ∫P %fkWh, T1 %f°C, T2 %f°C", v_rms, i_rms, watt, cf_cnt,
@@ -121,7 +242,27 @@ void BL0940::received_package_(const DataPacket *data) const {
} }
void BL0940::dump_config() { // NOLINT(readability-function-cognitive-complexity) void BL0940::dump_config() { // NOLINT(readability-function-cognitive-complexity)
ESP_LOGCONFIG(TAG, "BL0940:"); ESP_LOGCONFIG(TAG,
"BL0940:\n"
" LEGACY MODE: %s\n"
" READ CMD: 0x%02X\n"
" WRITE CMD: 0x%02X\n"
" ------------------\n"
" Current reference: %f\n"
" Energy reference: %f\n"
" Power reference: %f\n"
" Voltage reference: %f\n",
TRUEFALSE(this->legacy_mode_enabled_), this->read_command_, this->write_command_,
this->current_reference_, this->energy_reference_, this->power_reference_, this->voltage_reference_);
#ifdef USE_NUMBER
ESP_LOGCONFIG(TAG,
"BL0940:\n"
" Current calibration: %f\n"
" Energy calibration: %f\n"
" Power calibration: %f\n"
" Voltage calibration: %f\n",
this->current_cal_, this->energy_cal_, this->power_cal_, this->voltage_cal_);
#endif
LOG_SENSOR("", "Voltage", this->voltage_sensor_); LOG_SENSOR("", "Voltage", this->voltage_sensor_);
LOG_SENSOR("", "Current", this->current_sensor_); LOG_SENSOR("", "Current", this->current_sensor_);
LOG_SENSOR("", "Power", this->power_sensor_); LOG_SENSOR("", "Power", this->power_sensor_);
@@ -130,9 +271,5 @@ void BL0940::dump_config() { // NOLINT(readability-function-cognitive-complexit
LOG_SENSOR("", "External temperature", this->external_temperature_sensor_); LOG_SENSOR("", "External temperature", this->external_temperature_sensor_);
} }
uint32_t BL0940::to_uint32_t(ube24_t input) { return input.h << 16 | input.m << 8 | input.l; }
int32_t BL0940::to_int32_t(sbe24_t input) { return input.h << 16 | input.m << 8 | input.l; }
} // namespace bl0940 } // namespace bl0940
} // namespace esphome } // namespace esphome

View File

@@ -1,66 +1,48 @@
#pragma once #pragma once
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/components/uart/uart.h" #include "esphome/core/datatypes.h"
#include "esphome/core/defines.h"
#ifdef USE_BUTTON
#include "esphome/components/button/button.h"
#endif
#ifdef USE_NUMBER
#include "esphome/components/number/number.h"
#endif
#include "esphome/components/sensor/sensor.h" #include "esphome/components/sensor/sensor.h"
#include "esphome/components/uart/uart.h"
namespace esphome { namespace esphome {
namespace bl0940 { namespace bl0940 {
static const float BL0940_PREF = 1430;
static const float BL0940_UREF = 33000;
static const float BL0940_IREF = 275000; // 2750 from tasmota. Seems to generate values 100 times too high
// Measured to 297J per click according to power consumption of 5 minutes
// Converted to kWh (3.6MJ per kwH). Used to be 256 * 1638.4
static const float BL0940_EREF = 3.6e6 / 297;
struct ube24_t { // NOLINT(readability-identifier-naming,altera-struct-pack-align)
uint8_t l;
uint8_t m;
uint8_t h;
} __attribute__((packed));
struct ube16_t { // NOLINT(readability-identifier-naming,altera-struct-pack-align)
uint8_t l;
uint8_t h;
} __attribute__((packed));
struct sbe24_t { // NOLINT(readability-identifier-naming,altera-struct-pack-align)
uint8_t l;
uint8_t m;
int8_t h;
} __attribute__((packed));
// Caveat: All these values are big endian (low - middle - high) // Caveat: All these values are big endian (low - middle - high)
struct DataPacket {
union DataPacket { // NOLINT(altera-struct-pack-align) uint8_t frame_header; // Packet header (0x58 in EN docs, 0x55 in CN docs and Tasmota tests)
uint8_t raw[35]; uint24_le_t i_fast_rms; // Fast RMS current
struct { uint24_le_t i_rms; // RMS current
uint8_t frame_header; // value of 0x58 according to docs. 0x55 according to Tasmota real world tests. Reality wins. uint24_t RESERVED0; // Reserved
ube24_t i_fast_rms; // 0x00 uint24_le_t v_rms; // RMS voltage
ube24_t i_rms; // 0x04 uint24_t RESERVED1; // Reserved
ube24_t RESERVED0; // reserved int24_le_t watt; // Active power (can be negative for bidirectional measurement)
ube24_t v_rms; // 0x06 uint24_t RESERVED2; // Reserved
ube24_t RESERVED1; // reserved uint24_le_t cf_cnt; // Energy pulse count
sbe24_t watt; // 0x08 uint24_t RESERVED3; // Reserved
ube24_t RESERVED2; // reserved uint16_le_t tps1; // Internal temperature sensor 1
ube24_t cf_cnt; // 0x0A uint8_t RESERVED4; // Reserved (should be 0x00)
ube24_t RESERVED3; // reserved uint16_le_t tps2; // Internal temperature sensor 2
ube16_t tps1; // 0x0c uint8_t RESERVED5; // Reserved (should be 0x00)
uint8_t RESERVED4; // value of 0x00 uint8_t checksum; // Packet checksum
ube16_t tps2; // 0x0c
uint8_t RESERVED5; // value of 0x00
uint8_t checksum; // checksum
};
} __attribute__((packed)); } __attribute__((packed));
class BL0940 : public PollingComponent, public uart::UARTDevice { class BL0940 : public PollingComponent, public uart::UARTDevice {
public: public:
// Sensor setters
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; } void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; } void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; }
void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; } void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; }
void set_energy_sensor(sensor::Sensor *energy_sensor) { energy_sensor_ = energy_sensor; } void set_energy_sensor(sensor::Sensor *energy_sensor) { energy_sensor_ = energy_sensor; }
// Temperature sensor setters
void set_internal_temperature_sensor(sensor::Sensor *internal_temperature_sensor) { void set_internal_temperature_sensor(sensor::Sensor *internal_temperature_sensor) {
internal_temperature_sensor_ = internal_temperature_sensor; internal_temperature_sensor_ = internal_temperature_sensor;
} }
@@ -68,42 +50,105 @@ class BL0940 : public PollingComponent, public uart::UARTDevice {
external_temperature_sensor_ = external_temperature_sensor; external_temperature_sensor_ = external_temperature_sensor;
} }
void loop() override; // Configuration setters
void set_legacy_mode(bool enable) { this->legacy_mode_enabled_ = enable; }
void set_read_command(uint8_t read_command) { this->read_command_ = read_command; }
void set_write_command(uint8_t write_command) { this->write_command_ = write_command; }
// Reference value setters (used for calibration and conversion)
void set_current_reference(float current_ref) { this->current_reference_ = current_ref; }
void set_energy_reference(float energy_ref) { this->energy_reference_ = energy_ref; }
void set_power_reference(float power_ref) { this->power_reference_ = power_ref; }
void set_voltage_reference(float voltage_ref) { this->voltage_reference_ = voltage_ref; }
#ifdef USE_NUMBER
// Calibration number setters (for Home Assistant number entities)
void set_current_calibration_number(number::Number *num) { this->current_calibration_number_ = num; }
void set_voltage_calibration_number(number::Number *num) { this->voltage_calibration_number_ = num; }
void set_power_calibration_number(number::Number *num) { this->power_calibration_number_ = num; }
void set_energy_calibration_number(number::Number *num) { this->energy_calibration_number_ = num; }
#endif
#ifdef USE_BUTTON
// Resets all calibration values to defaults (can be triggered by a button)
void reset_calibration();
#endif
// Core component methods
void loop() override;
void update() override; void update() override;
void setup() override; void setup() override;
void dump_config() override; void dump_config() override;
protected: protected:
sensor::Sensor *voltage_sensor_{nullptr}; // --- Sensor pointers ---
sensor::Sensor *current_sensor_{nullptr}; sensor::Sensor *voltage_sensor_{nullptr}; // Voltage sensor
// NB This may be negative as the circuits is seemingly able to measure sensor::Sensor *current_sensor_{nullptr}; // Current sensor
// power in both directions sensor::Sensor *power_sensor_{nullptr}; // Power sensor (can be negative for bidirectional)
sensor::Sensor *power_sensor_{nullptr}; sensor::Sensor *energy_sensor_{nullptr}; // Energy sensor
sensor::Sensor *energy_sensor_{nullptr}; sensor::Sensor *internal_temperature_sensor_{nullptr}; // Internal temperature sensor
sensor::Sensor *internal_temperature_sensor_{nullptr}; sensor::Sensor *external_temperature_sensor_{nullptr}; // External temperature sensor
sensor::Sensor *external_temperature_sensor_{nullptr};
// Max difference between two measurements of the temperature. Used to avoid noise. #ifdef USE_NUMBER
float max_temperature_diff_{0}; // --- Calibration number entities (for dynamic calibration via HA UI) ---
// Divide by this to turn into Watt number::Number *voltage_calibration_number_{nullptr};
float power_reference_ = BL0940_PREF; number::Number *current_calibration_number_{nullptr};
// Divide by this to turn into Volt number::Number *power_calibration_number_{nullptr};
float voltage_reference_ = BL0940_UREF; number::Number *energy_calibration_number_{nullptr};
// Divide by this to turn into Ampere #endif
float current_reference_ = BL0940_IREF;
// Divide by this to turn into kWh
float energy_reference_ = BL0940_EREF;
float update_temp_(sensor::Sensor *sensor, ube16_t packed_temperature) const; // --- Internal state ---
uint32_t prev_cf_cnt_ = 0; // Previous energy pulse count (for energy calculation)
float max_temperature_diff_{0}; // Max allowed temperature difference between two measurements (noise filter)
static uint32_t to_uint32_t(ube24_t input); // --- Reference values for conversion ---
float power_reference_; // Divider for raw power to get Watts
float power_reference_cal_; // Calibrated power reference
float voltage_reference_; // Divider for raw voltage to get Volts
float voltage_reference_cal_; // Calibrated voltage reference
float current_reference_; // Divider for raw current to get Amperes
float current_reference_cal_; // Calibrated current reference
float energy_reference_; // Divider for raw energy to get kWh
float energy_reference_cal_; // Calibrated energy reference
static int32_t to_int32_t(sbe24_t input); // --- Home Assistant calibration values (multipliers, default 1) ---
float current_cal_{1};
float voltage_cal_{1};
float power_cal_{1};
float energy_cal_{1};
static bool validate_checksum(const DataPacket *data); // --- Protocol commands ---
uint8_t read_command_;
uint8_t write_command_;
void received_package_(const DataPacket *data) const; // --- Mode flags ---
bool legacy_mode_enabled_ = true;
// --- Methods ---
// Converts packed temperature value to float and updates the sensor
float update_temp_(sensor::Sensor *sensor, uint16_le_t packed_temperature) const;
// Validates the checksum of a received data packet
bool validate_checksum_(DataPacket *data);
// Handles a received data packet
void received_package_(DataPacket *data);
// Calculates reference values for calibration and conversion
float calculate_energy_reference_();
float calculate_power_reference_();
float calculate_calibration_value_(float state);
// Calibration update callbacks (used with number entities)
void current_calibration_callback_(float state);
void voltage_calibration_callback_(float state);
void power_calibration_callback_(float state);
void energy_calibration_callback_(float state);
void reset_calibration_callback_();
// Recalculates all reference values after calibration changes
void recalibrate_();
}; };
} // namespace bl0940 } // namespace bl0940
} // namespace esphome } // namespace esphome

View File

@@ -0,0 +1,27 @@
import esphome.codegen as cg
from esphome.components import button
import esphome.config_validation as cv
from esphome.const import ENTITY_CATEGORY_CONFIG, ICON_RESTART
from .. import CONF_BL0940_ID, bl0940_ns
from ..sensor import BL0940
CalibrationResetButton = bl0940_ns.class_(
"CalibrationResetButton", button.Button, cg.Component
)
CONFIG_SCHEMA = cv.All(
button.button_schema(
CalibrationResetButton,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_RESTART,
)
.extend({cv.GenerateID(CONF_BL0940_ID): cv.use_id(BL0940)})
.extend(cv.COMPONENT_SCHEMA)
)
async def to_code(config):
var = await button.new_button(config)
await cg.register_component(var, config)
await cg.register_parented(var, config[CONF_BL0940_ID])

View File

@@ -0,0 +1,20 @@
#include "calibration_reset_button.h"
#include "../bl0940.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
namespace esphome {
namespace bl0940 {
static const char *const TAG = "bl0940.button.calibration_reset";
void CalibrationResetButton::dump_config() { LOG_BUTTON("", "Calibration Reset Button", this); }
void CalibrationResetButton::press_action() {
ESP_LOGI(TAG, "Resetting calibration defaults...");
this->parent_->reset_calibration();
}
} // namespace bl0940
} // namespace esphome

View File

@@ -0,0 +1,19 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/button/button.h"
namespace esphome {
namespace bl0940 {
class BL0940; // Forward declaration of BL0940 class
class CalibrationResetButton : public button::Button, public Component, public Parented<BL0940> {
public:
void dump_config() override;
void press_action() override;
};
} // namespace bl0940
} // namespace esphome

View File

@@ -0,0 +1,94 @@
import esphome.codegen as cg
from esphome.components import number
import esphome.config_validation as cv
from esphome.const import (
CONF_MAX_VALUE,
CONF_MIN_VALUE,
CONF_MODE,
CONF_RESTORE_VALUE,
CONF_STEP,
ENTITY_CATEGORY_CONFIG,
UNIT_PERCENT,
)
from .. import CONF_BL0940_ID, bl0940_ns
from ..sensor import BL0940
# Define calibration types
CONF_CURRENT_CALIBRATION = "current_calibration"
CONF_VOLTAGE_CALIBRATION = "voltage_calibration"
CONF_POWER_CALIBRATION = "power_calibration"
CONF_ENERGY_CALIBRATION = "energy_calibration"
BL0940Number = bl0940_ns.class_("BL0940Number")
CalibrationNumber = bl0940_ns.class_(
"CalibrationNumber", number.Number, cg.PollingComponent
)
def validate_min_max(config):
if config[CONF_MAX_VALUE] <= config[CONF_MIN_VALUE]:
raise cv.Invalid("max_value must be greater than min_value")
return config
CALIBRATION_SCHEMA = cv.All(
number.number_schema(
CalibrationNumber,
entity_category=ENTITY_CATEGORY_CONFIG,
unit_of_measurement=UNIT_PERCENT,
)
.extend(
{
cv.Optional(CONF_MODE, default="BOX"): cv.enum(number.NUMBER_MODES),
cv.Optional(CONF_MAX_VALUE, default=10): cv.All(
cv.float_, cv.Range(max=50)
),
cv.Optional(CONF_MIN_VALUE, default=-10): cv.All(
cv.float_, cv.Range(min=-50)
),
cv.Optional(CONF_STEP, default=0.1): cv.positive_float,
cv.Optional(CONF_RESTORE_VALUE): cv.boolean,
}
)
.extend(cv.COMPONENT_SCHEMA),
validate_min_max,
)
# Configuration schema for BL0940 numbers
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(BL0940Number),
cv.GenerateID(CONF_BL0940_ID): cv.use_id(BL0940),
cv.Optional(CONF_CURRENT_CALIBRATION): CALIBRATION_SCHEMA,
cv.Optional(CONF_VOLTAGE_CALIBRATION): CALIBRATION_SCHEMA,
cv.Optional(CONF_POWER_CALIBRATION): CALIBRATION_SCHEMA,
cv.Optional(CONF_ENERGY_CALIBRATION): CALIBRATION_SCHEMA,
}
)
async def to_code(config):
# Get the BL0940 component instance
bl0940 = await cg.get_variable(config[CONF_BL0940_ID])
# Process all calibration types
for cal_type, setter_method in [
(CONF_CURRENT_CALIBRATION, "set_current_calibration_number"),
(CONF_VOLTAGE_CALIBRATION, "set_voltage_calibration_number"),
(CONF_POWER_CALIBRATION, "set_power_calibration_number"),
(CONF_ENERGY_CALIBRATION, "set_energy_calibration_number"),
]:
if conf := config.get(cal_type):
var = await number.new_number(
conf,
min_value=conf.get(CONF_MIN_VALUE),
max_value=conf.get(CONF_MAX_VALUE),
step=conf.get(CONF_STEP),
)
await cg.register_component(var, conf)
if restore_value := config.get(CONF_RESTORE_VALUE):
cg.add(var.set_restore_value(restore_value))
cg.add(getattr(bl0940, setter_method)(var))

View File

@@ -0,0 +1,29 @@
#include "calibration_number.h"
#include "esphome/core/log.h"
namespace esphome {
namespace bl0940 {
static const char *const TAG = "bl0940.number";
void CalibrationNumber::setup() {
float value = 0.0f;
if (this->restore_value_) {
this->pref_ = global_preferences->make_preference<float>(this->get_object_id_hash());
if (!this->pref_.load(&value)) {
value = 0.0f;
}
}
this->publish_state(value);
}
void CalibrationNumber::control(float value) {
this->publish_state(value);
if (this->restore_value_)
this->pref_.save(&value);
}
void CalibrationNumber::dump_config() { LOG_NUMBER("", "Calibration Number", this); }
} // namespace bl0940
} // namespace esphome

View File

@@ -0,0 +1,26 @@
#pragma once
#include "esphome/components/number/number.h"
#include "esphome/core/component.h"
#include "esphome/core/preferences.h"
namespace esphome {
namespace bl0940 {
class CalibrationNumber : public number::Number, public Component {
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::HARDWARE; }
void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; }
protected:
void control(float value) override;
bool restore_value_{true};
ESPPreferenceObject pref_;
};
} // namespace bl0940
} // namespace esphome

View File

@@ -8,6 +8,7 @@ from esphome.const import (
CONF_ID, CONF_ID,
CONF_INTERNAL_TEMPERATURE, CONF_INTERNAL_TEMPERATURE,
CONF_POWER, CONF_POWER,
CONF_REFERENCE_VOLTAGE,
CONF_VOLTAGE, CONF_VOLTAGE,
DEVICE_CLASS_CURRENT, DEVICE_CLASS_CURRENT,
DEVICE_CLASS_ENERGY, DEVICE_CLASS_ENERGY,
@@ -23,12 +24,133 @@ from esphome.const import (
UNIT_WATT, UNIT_WATT,
) )
from . import bl0940_ns
DEPENDENCIES = ["uart"] DEPENDENCIES = ["uart"]
bl0940_ns = cg.esphome_ns.namespace("bl0940")
BL0940 = bl0940_ns.class_("BL0940", cg.PollingComponent, uart.UARTDevice) BL0940 = bl0940_ns.class_("BL0940", cg.PollingComponent, uart.UARTDevice)
CONF_LEGACY_MODE = "legacy_mode"
CONF_READ_COMMAND = "read_command"
CONF_WRITE_COMMAND = "write_command"
CONF_RESISTOR_SHUNT = "resistor_shunt"
CONF_RESISTOR_ONE = "resistor_one"
CONF_RESISTOR_TWO = "resistor_two"
CONF_CURRENT_REFERENCE = "current_reference"
CONF_ENERGY_REFERENCE = "energy_reference"
CONF_POWER_REFERENCE = "power_reference"
CONF_VOLTAGE_REFERENCE = "voltage_reference"
DEFAULT_BL0940_READ_COMMAND = 0x58
DEFAULT_BL0940_WRITE_COMMAND = 0xA1
# Values according to BL0940 application note:
# https://www.belling.com.cn/media/file_object/bel_product/BL0940/guide/BL0940_APPNote_TSSOP14_V1.04_EN.pdf
DEFAULT_BL0940_VREF = 1.218 # Vref = 1.218
DEFAULT_BL0940_RL = 1 # RL = 1 mΩ
DEFAULT_BL0940_R1 = 0.51 # R1 = 0.51 kΩ
DEFAULT_BL0940_R2 = 1950 # R2 = 5 x 390 kΩ -> 1950 kΩ
# ----------------------------------------------------
# values from initial implementation
DEFAULT_BL0940_LEGACY_READ_COMMAND = 0x50
DEFAULT_BL0940_LEGACY_WRITE_COMMAND = 0xA0
DEFAULT_BL0940_LEGACY_UREF = 33000
DEFAULT_BL0940_LEGACY_IREF = 275000
DEFAULT_BL0940_LEGACY_PREF = 1430
# Measured to 297J per click according to power consumption of 5 minutes
# Converted to kWh (3.6MJ per kwH). Used to be 256 * 1638.4
DEFAULT_BL0940_LEGACY_EREF = 3.6e6 / 297
# ----------------------------------------------------
# methods to calculate voltage and current reference values
def calculate_voltage_reference(vref, r_one, r_two):
# formula: 79931 / Vref * (R1 * 1000) / (R1 + R2)
return 79931 / vref * (r_one * 1000) / (r_one + r_two)
def calculate_current_reference(vref, r_shunt):
# formula: 324004 * RL / Vref
return 324004 * r_shunt / vref
def calculate_power_reference(voltage_reference, current_reference):
# calculate power reference based on voltage and current reference
return voltage_reference * current_reference * 4046 / 324004 / 79931
def calculate_energy_reference(power_reference):
# formula: power_reference * 3600000 / (1638.4 * 256)
return power_reference * 3600000 / (1638.4 * 256)
def validate_legacy_mode(config):
# Only allow schematic calibration options if legacy_mode is False
if config.get(CONF_LEGACY_MODE, True):
forbidden = [
CONF_REFERENCE_VOLTAGE,
CONF_RESISTOR_SHUNT,
CONF_RESISTOR_ONE,
CONF_RESISTOR_TWO,
]
for key in forbidden:
if key in config:
raise cv.Invalid(
f"Option '{key}' is only allowed when legacy_mode: false"
)
return config
def set_command_defaults(config):
# Set defaults for read_command and write_command based on legacy_mode
legacy = config.get(CONF_LEGACY_MODE, True)
if legacy:
config.setdefault(CONF_READ_COMMAND, DEFAULT_BL0940_LEGACY_READ_COMMAND)
config.setdefault(CONF_WRITE_COMMAND, DEFAULT_BL0940_LEGACY_WRITE_COMMAND)
else:
config.setdefault(CONF_READ_COMMAND, DEFAULT_BL0940_READ_COMMAND)
config.setdefault(CONF_WRITE_COMMAND, DEFAULT_BL0940_WRITE_COMMAND)
return config
def set_reference_values(config):
# Set default reference values based on legacy_mode
if config.get(CONF_LEGACY_MODE, True):
config.setdefault(CONF_VOLTAGE_REFERENCE, DEFAULT_BL0940_LEGACY_UREF)
config.setdefault(CONF_CURRENT_REFERENCE, DEFAULT_BL0940_LEGACY_IREF)
config.setdefault(CONF_POWER_REFERENCE, DEFAULT_BL0940_LEGACY_PREF)
config.setdefault(CONF_ENERGY_REFERENCE, DEFAULT_BL0940_LEGACY_PREF)
else:
vref = config.get(CONF_VOLTAGE_REFERENCE, DEFAULT_BL0940_VREF)
r_one = config.get(CONF_RESISTOR_ONE, DEFAULT_BL0940_R1)
r_two = config.get(CONF_RESISTOR_TWO, DEFAULT_BL0940_R2)
r_shunt = config.get(CONF_RESISTOR_SHUNT, DEFAULT_BL0940_RL)
config.setdefault(
CONF_VOLTAGE_REFERENCE, calculate_voltage_reference(vref, r_one, r_two)
)
config.setdefault(
CONF_CURRENT_REFERENCE, calculate_current_reference(vref, r_shunt)
)
config.setdefault(
CONF_POWER_REFERENCE,
calculate_power_reference(
config.get(CONF_VOLTAGE_REFERENCE), config.get(CONF_CURRENT_REFERENCE)
),
)
config.setdefault(
CONF_ENERGY_REFERENCE,
calculate_energy_reference(config.get(CONF_POWER_REFERENCE)),
)
return config
CONFIG_SCHEMA = ( CONFIG_SCHEMA = (
cv.Schema( cv.Schema(
{ {
@@ -69,10 +191,24 @@ CONFIG_SCHEMA = (
device_class=DEVICE_CLASS_TEMPERATURE, device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
), ),
cv.Optional(CONF_LEGACY_MODE, default=True): cv.boolean,
cv.Optional(CONF_READ_COMMAND): cv.hex_uint8_t,
cv.Optional(CONF_WRITE_COMMAND): cv.hex_uint8_t,
cv.Optional(CONF_REFERENCE_VOLTAGE): cv.float_,
cv.Optional(CONF_RESISTOR_SHUNT): cv.float_,
cv.Optional(CONF_RESISTOR_ONE): cv.float_,
cv.Optional(CONF_RESISTOR_TWO): cv.float_,
cv.Optional(CONF_CURRENT_REFERENCE): cv.float_,
cv.Optional(CONF_ENERGY_REFERENCE): cv.float_,
cv.Optional(CONF_POWER_REFERENCE): cv.float_,
cv.Optional(CONF_VOLTAGE_REFERENCE): cv.float_,
} }
) )
.extend(cv.polling_component_schema("60s")) .extend(cv.polling_component_schema("60s"))
.extend(uart.UART_DEVICE_SCHEMA) .extend(uart.UART_DEVICE_SCHEMA)
.add_extra(validate_legacy_mode)
.add_extra(set_command_defaults)
.add_extra(set_reference_values)
) )
@@ -99,3 +235,16 @@ async def to_code(config):
if external_temperature_config := config.get(CONF_EXTERNAL_TEMPERATURE): if external_temperature_config := config.get(CONF_EXTERNAL_TEMPERATURE):
sens = await sensor.new_sensor(external_temperature_config) sens = await sensor.new_sensor(external_temperature_config)
cg.add(var.set_external_temperature_sensor(sens)) cg.add(var.set_external_temperature_sensor(sens))
# enable legacy mode
cg.add(var.set_legacy_mode(config.get(CONF_LEGACY_MODE)))
# Set bl0940 commands after validator has determined which defaults to use if not set
cg.add(var.set_read_command(config.get(CONF_READ_COMMAND)))
cg.add(var.set_write_command(config.get(CONF_WRITE_COMMAND)))
# Set reference values after validator has set the values either from defaults or calculated
cg.add(var.set_current_reference(config.get(CONF_CURRENT_REFERENCE)))
cg.add(var.set_voltage_reference(config.get(CONF_VOLTAGE_REFERENCE)))
cg.add(var.set_power_reference(config.get(CONF_POWER_REFERENCE)))
cg.add(var.set_energy_reference(config.get(CONF_ENERGY_REFERENCE)))

View File

@@ -149,7 +149,7 @@ void BL0942::setup() {
this->write_reg_(BL0942_REG_USR_WRPROT, 0); this->write_reg_(BL0942_REG_USR_WRPROT, 0);
if (this->read_reg_(BL0942_REG_MODE) != mode) if (this->read_reg_(BL0942_REG_MODE) != mode)
this->status_set_warning("BL0942 setup failed!"); this->status_set_warning(LOG_STR("BL0942 setup failed!"));
this->flush(); this->flush();
} }

View File

@@ -116,7 +116,7 @@ CONFIG_SCHEMA = cv.All(
) )
.extend(cv.COMPONENT_SCHEMA) .extend(cv.COMPONENT_SCHEMA)
.extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA), .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA),
esp32_ble_tracker.consume_connection_slots(1, "ble_client"), esp32_ble.consume_connection_slots(1, "ble_client"),
) )
CONF_BLE_CLIENT_ID = "ble_client_id" CONF_BLE_CLIENT_ID = "ble_client_id"

View File

@@ -6,8 +6,6 @@ from esphome.components.esp32 import add_idf_sdkconfig_option
from esphome.components.esp32_ble import BTLoggers from esphome.components.esp32_ble import BTLoggers
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import CONF_ACTIVE, CONF_ID 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"] AUTO_LOAD = ["esp32_ble_client", "esp32_ble_tracker"]
DEPENDENCIES = ["api", "esp32"] DEPENDENCIES = ["api", "esp32"]
@@ -44,29 +42,7 @@ def validate_connections(config):
) )
elif config[CONF_ACTIVE]: elif config[CONF_ACTIVE]:
connection_slots: int = config[CONF_CONNECTION_SLOTS] connection_slots: int = config[CONF_CONNECTION_SLOTS]
esp32_ble_tracker.consume_connection_slots(connection_slots, "bluetooth_proxy")( esp32_ble.consume_connection_slots(connection_slots, "bluetooth_proxy")(config)
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 { return {
**config, **config,
@@ -80,20 +56,18 @@ CONFIG_SCHEMA = cv.All(
cv.Schema( cv.Schema(
{ {
cv.GenerateID(): cv.declare_id(BluetoothProxy), cv.GenerateID(): cv.declare_id(BluetoothProxy),
cv.Optional(CONF_ACTIVE, default=False): cv.boolean, cv.Optional(CONF_ACTIVE, default=True): cv.boolean,
cv.SplitDefault(CONF_CACHE_SERVICES, esp32_idf=True): cv.All( cv.Optional(CONF_CACHE_SERVICES, default=True): cv.boolean,
cv.only_with_esp_idf, cv.boolean
),
cv.Optional( cv.Optional(
CONF_CONNECTION_SLOTS, CONF_CONNECTION_SLOTS,
default=DEFAULT_CONNECTION_SLOTS, default=DEFAULT_CONNECTION_SLOTS,
): cv.All( ): cv.All(
cv.positive_int, cv.positive_int,
cv.Range(min=1, max=esp32_ble_tracker.max_connections()), cv.Range(min=1, max=esp32_ble.IDF_MAX_CONNECTIONS),
), ),
cv.Optional(CONF_CONNECTIONS): cv.All( cv.Optional(CONF_CONNECTIONS): cv.All(
cv.ensure_list(CONNECTION_SCHEMA), cv.ensure_list(CONNECTION_SCHEMA),
cv.Length(min=1, max=esp32_ble_tracker.max_connections()), cv.Length(min=1, max=esp32_ble.IDF_MAX_CONNECTIONS),
), ),
} }
) )

View File

@@ -514,7 +514,8 @@ esp_err_t BluetoothConnection::read_characteristic(uint16_t handle) {
return this->check_and_log_error_("esp_ble_gattc_read_char", err); 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) { esp_err_t BluetoothConnection::write_characteristic(uint16_t handle, const uint8_t *data, size_t length,
bool response) {
if (!this->connected()) { if (!this->connected()) {
this->log_gatt_not_connected_("write", "characteristic"); this->log_gatt_not_connected_("write", "characteristic");
return ESP_GATT_NOT_CONNECTED; return ESP_GATT_NOT_CONNECTED;
@@ -522,8 +523,11 @@ esp_err_t BluetoothConnection::write_characteristic(uint16_t handle, const std::
ESP_LOGV(TAG, "[%d] [%s] Writing GATT characteristic handle %d", this->connection_index_, this->address_str_.c_str(), ESP_LOGV(TAG, "[%d] [%s] Writing GATT characteristic handle %d", this->connection_index_, this->address_str_.c_str(),
handle); handle);
// ESP-IDF's API requires a non-const uint8_t* but it doesn't modify the data
// The BTC layer immediately copies the data to its own buffer (see btc_gattc.c)
// const_cast is safe here and was previously hidden by a C-style cast
esp_err_t err = esp_err_t err =
esp_ble_gattc_write_char(this->gattc_if_, this->conn_id_, handle, data.size(), (uint8_t *) data.data(), esp_ble_gattc_write_char(this->gattc_if_, this->conn_id_, handle, length, const_cast<uint8_t *>(data),
response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
return this->check_and_log_error_("esp_ble_gattc_write_char", err); return this->check_and_log_error_("esp_ble_gattc_write_char", err);
} }
@@ -540,7 +544,7 @@ esp_err_t BluetoothConnection::read_descriptor(uint16_t handle) {
return this->check_and_log_error_("esp_ble_gattc_read_char_descr", err); 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) { esp_err_t BluetoothConnection::write_descriptor(uint16_t handle, const uint8_t *data, size_t length, bool response) {
if (!this->connected()) { if (!this->connected()) {
this->log_gatt_not_connected_("write", "descriptor"); this->log_gatt_not_connected_("write", "descriptor");
return ESP_GATT_NOT_CONNECTED; return ESP_GATT_NOT_CONNECTED;
@@ -548,8 +552,11 @@ esp_err_t BluetoothConnection::write_descriptor(uint16_t handle, const std::stri
ESP_LOGV(TAG, "[%d] [%s] Writing GATT descriptor handle %d", this->connection_index_, this->address_str_.c_str(), ESP_LOGV(TAG, "[%d] [%s] Writing GATT descriptor handle %d", this->connection_index_, this->address_str_.c_str(),
handle); handle);
// ESP-IDF's API requires a non-const uint8_t* but it doesn't modify the data
// The BTC layer immediately copies the data to its own buffer (see btc_gattc.c)
// const_cast is safe here and was previously hidden by a C-style cast
esp_err_t err = esp_ble_gattc_write_char_descr( esp_err_t err = esp_ble_gattc_write_char_descr(
this->gattc_if_, this->conn_id_, handle, data.size(), (uint8_t *) data.data(), this->gattc_if_, this->conn_id_, handle, length, const_cast<uint8_t *>(data),
response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
return this->check_and_log_error_("esp_ble_gattc_write_char_descr", err); return this->check_and_log_error_("esp_ble_gattc_write_char_descr", err);
} }

View File

@@ -18,9 +18,9 @@ class BluetoothConnection final : public esp32_ble_client::BLEClientBase {
esp32_ble_tracker::AdvertisementParserType get_advertisement_parser_type() override; esp32_ble_tracker::AdvertisementParserType get_advertisement_parser_type() override;
esp_err_t read_characteristic(uint16_t handle); esp_err_t read_characteristic(uint16_t handle);
esp_err_t write_characteristic(uint16_t handle, const std::string &data, bool response); esp_err_t write_characteristic(uint16_t handle, const uint8_t *data, size_t length, bool response);
esp_err_t read_descriptor(uint16_t handle); esp_err_t read_descriptor(uint16_t handle);
esp_err_t write_descriptor(uint16_t handle, const std::string &data, bool response); esp_err_t write_descriptor(uint16_t handle, const uint8_t *data, size_t length, bool response);
esp_err_t notify_characteristic(uint16_t handle, bool enable); esp_err_t notify_characteristic(uint16_t handle, bool enable);

View File

@@ -24,6 +24,9 @@ void BluetoothProxy::setup() {
this->connections_free_response_.limit = BLUETOOTH_PROXY_MAX_CONNECTIONS; this->connections_free_response_.limit = BLUETOOTH_PROXY_MAX_CONNECTIONS;
this->connections_free_response_.free = BLUETOOTH_PROXY_MAX_CONNECTIONS; this->connections_free_response_.free = BLUETOOTH_PROXY_MAX_CONNECTIONS;
// Capture the configured scan mode from YAML before any API changes
this->configured_scan_active_ = this->parent_->get_scan_active();
this->parent_->add_scanner_state_callback([this](esp32_ble_tracker::ScannerState state) { this->parent_->add_scanner_state_callback([this](esp32_ble_tracker::ScannerState state) {
if (this->api_connection_ != nullptr) { if (this->api_connection_ != nullptr) {
this->send_bluetooth_scanner_state_(state); this->send_bluetooth_scanner_state_(state);
@@ -36,6 +39,9 @@ void BluetoothProxy::send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerSta
resp.state = static_cast<api::enums::BluetoothScannerState>(state); resp.state = static_cast<api::enums::BluetoothScannerState>(state);
resp.mode = this->parent_->get_scan_active() ? api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_ACTIVE resp.mode = this->parent_->get_scan_active() ? api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_ACTIVE
: api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_PASSIVE; : api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_PASSIVE;
resp.configured_mode = this->configured_scan_active_
? api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_ACTIVE
: api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_PASSIVE;
this->api_connection_->send_message(resp, api::BluetoothScannerStateResponse::MESSAGE_TYPE); this->api_connection_->send_message(resp, api::BluetoothScannerStateResponse::MESSAGE_TYPE);
} }
@@ -183,6 +189,12 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
this->send_device_connection(msg.address, false); this->send_device_connection(msg.address, false);
return; return;
} }
if (!msg.has_address_type) {
ESP_LOGE(TAG, "[%d] [%s] Missing address type in connect request", connection->get_connection_index(),
connection->address_str().c_str());
this->send_device_connection(msg.address, false);
return;
}
if (connection->state() == espbt::ClientState::CONNECTED || if (connection->state() == espbt::ClientState::CONNECTED ||
connection->state() == espbt::ClientState::ESTABLISHED) { connection->state() == espbt::ClientState::ESTABLISHED) {
this->log_connection_request_ignored_(connection, connection->state()); this->log_connection_request_ignored_(connection, connection->state());
@@ -209,13 +221,9 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
connection->set_connection_type(espbt::ConnectionType::V3_WITHOUT_CACHE); connection->set_connection_type(espbt::ConnectionType::V3_WITHOUT_CACHE);
this->log_connection_info_(connection, "v3 without cache"); this->log_connection_info_(connection, "v3 without cache");
} }
if (msg.has_address_type) { uint64_to_bd_addr(msg.address, connection->remote_bda_);
uint64_to_bd_addr(msg.address, connection->remote_bda_); connection->set_remote_addr_type(static_cast<esp_ble_addr_type_t>(msg.address_type));
connection->set_remote_addr_type(static_cast<esp_ble_addr_type_t>(msg.address_type)); connection->set_state(espbt::ClientState::DISCOVERED);
connection->set_state(espbt::ClientState::DISCOVERED);
} else {
connection->set_state(espbt::ClientState::SEARCHING);
}
this->send_connections_free(); this->send_connections_free();
break; break;
} }
@@ -297,7 +305,7 @@ void BluetoothProxy::bluetooth_gatt_write(const api::BluetoothGATTWriteRequest &
return; return;
} }
auto err = connection->write_characteristic(msg.handle, msg.data, msg.response); auto err = connection->write_characteristic(msg.handle, msg.data, msg.data_len, msg.response);
if (err != ESP_OK) { if (err != ESP_OK) {
this->send_gatt_error(msg.address, msg.handle, err); this->send_gatt_error(msg.address, msg.handle, err);
} }
@@ -323,7 +331,7 @@ void BluetoothProxy::bluetooth_gatt_write_descriptor(const api::BluetoothGATTWri
return; return;
} }
auto err = connection->write_descriptor(msg.handle, msg.data, true); auto err = connection->write_descriptor(msg.handle, msg.data, msg.data_len, true);
if (err != ESP_OK) { if (err != ESP_OK) {
this->send_gatt_error(msg.address, msg.handle, err); this->send_gatt_error(msg.address, msg.handle, err);
} }

View File

@@ -130,7 +130,9 @@ class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener, publ
std::string get_bluetooth_mac_address_pretty() { std::string get_bluetooth_mac_address_pretty() {
const uint8_t *mac = esp_bt_dev_get_address(); const uint8_t *mac = esp_bt_dev_get_address();
return str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); char buf[18];
format_mac_addr_upper(mac, buf);
return std::string(buf);
} }
protected: protected:
@@ -161,7 +163,8 @@ class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener, publ
// Group 4: 1-byte types grouped together // Group 4: 1-byte types grouped together
bool active_; bool active_;
uint8_t connection_count_{0}; uint8_t connection_count_{0};
// 2 bytes used, 2 bytes padding bool configured_scan_active_{false}; // Configured scan mode from YAML
// 3 bytes used, 1 byte padding
}; };
extern BluetoothProxy *global_bluetooth_proxy; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) extern BluetoothProxy *global_bluetooth_proxy; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)

View File

@@ -17,7 +17,7 @@ from esphome.const import (
DEVICE_CLASS_RESTART, DEVICE_CLASS_RESTART,
DEVICE_CLASS_UPDATE, DEVICE_CLASS_UPDATE,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, CoroPriority, coroutine_with_priority
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
from esphome.cpp_generator import MockObjClass from esphome.cpp_generator import MockObjClass
@@ -134,6 +134,6 @@ async def button_press_to_code(config, action_id, template_arg, args):
return cg.new_Pvariable(action_id, template_arg, paren) return cg.new_Pvariable(action_id, template_arg, paren)
@coroutine_with_priority(100.0) @coroutine_with_priority(CoroPriority.CORE)
async def to_code(config): async def to_code(config):
cg.add_global(button_ns.using) cg.add_global(button_ns.using)

View File

@@ -14,8 +14,8 @@ void log_button(const char *tag, const char *prefix, const char *type, Button *o
ESP_LOGCONFIG(tag, "%s%s '%s'", prefix, type, obj->get_name().c_str()); ESP_LOGCONFIG(tag, "%s%s '%s'", prefix, type, obj->get_name().c_str());
if (!obj->get_icon().empty()) { if (!obj->get_icon_ref().empty()) {
ESP_LOGCONFIG(tag, "%s Icon: '%s'", prefix, obj->get_icon().c_str()); ESP_LOGCONFIG(tag, "%s Icon: '%s'", prefix, obj->get_icon_ref().c_str());
} }
} }

View File

@@ -0,0 +1,18 @@
#pragma once
#include <cinttypes>
#include <cstddef>
namespace esphome::camera {
/// Interface for a generic buffer that stores image data.
class Buffer {
public:
/// Returns a pointer to the buffer's data.
virtual uint8_t *get_data_buffer() = 0;
/// Returns the length of the buffer in bytes.
virtual size_t get_data_length() = 0;
virtual ~Buffer() = default;
};
} // namespace esphome::camera

View File

@@ -0,0 +1,20 @@
#include "buffer_impl.h"
namespace esphome::camera {
BufferImpl::BufferImpl(size_t size) {
this->data_ = this->allocator_.allocate(size);
this->size_ = size;
}
BufferImpl::BufferImpl(CameraImageSpec *spec) {
this->data_ = this->allocator_.allocate(spec->bytes_per_image());
this->size_ = spec->bytes_per_image();
}
BufferImpl::~BufferImpl() {
if (this->data_ != nullptr)
this->allocator_.deallocate(this->data_, this->size_);
}
} // namespace esphome::camera

View File

@@ -0,0 +1,26 @@
#pragma once
#include "buffer.h"
#include "camera.h"
namespace esphome::camera {
/// Default implementation of Buffer Interface.
/// Uses a RAMAllocator for memory reservation.
class BufferImpl : public Buffer {
public:
explicit BufferImpl(size_t size);
explicit BufferImpl(CameraImageSpec *spec);
// -------- Buffer --------
uint8_t *get_data_buffer() override { return data_; }
size_t get_data_length() override { return size_; }
// ------------------------
~BufferImpl() override;
protected:
RAMAllocator<uint8_t> allocator_;
size_t size_{};
uint8_t *data_{};
};
} // namespace esphome::camera

View File

@@ -15,6 +15,26 @@ namespace camera {
*/ */
enum CameraRequester : uint8_t { IDLE, API_REQUESTER, WEB_REQUESTER }; enum CameraRequester : uint8_t { IDLE, API_REQUESTER, WEB_REQUESTER };
/// Enumeration of different pixel formats.
enum PixelFormat : uint8_t {
PIXEL_FORMAT_GRAYSCALE = 0, ///< 8-bit grayscale.
PIXEL_FORMAT_RGB565, ///< 16-bit RGB (5-6-5).
PIXEL_FORMAT_BGR888, ///< RGB pixel data in 8-bit format, stored as B, G, R (1 byte each).
};
/// Returns string name for a given PixelFormat.
inline const char *to_string(PixelFormat format) {
switch (format) {
case PIXEL_FORMAT_GRAYSCALE:
return "PIXEL_FORMAT_GRAYSCALE";
case PIXEL_FORMAT_RGB565:
return "PIXEL_FORMAT_RGB565";
case PIXEL_FORMAT_BGR888:
return "PIXEL_FORMAT_BGR888";
}
return "PIXEL_FORMAT_UNKNOWN";
}
/** Abstract camera image base class. /** Abstract camera image base class.
* Encapsulates the JPEG encoded data and it is shared among * Encapsulates the JPEG encoded data and it is shared among
* all connected clients. * all connected clients.
@@ -43,6 +63,29 @@ class CameraImageReader {
virtual ~CameraImageReader() {} virtual ~CameraImageReader() {}
}; };
/// Specification of a caputured camera image.
/// This struct defines the format and size details for images captured
/// or processed by a camera component.
struct CameraImageSpec {
uint16_t width;
uint16_t height;
PixelFormat format;
size_t bytes_per_pixel() {
switch (format) {
case PIXEL_FORMAT_GRAYSCALE:
return 1;
case PIXEL_FORMAT_RGB565:
return 2;
case PIXEL_FORMAT_BGR888:
return 3;
}
return 1;
}
size_t bytes_per_row() { return bytes_per_pixel() * width; }
size_t bytes_per_image() { return bytes_per_pixel() * width * height; }
};
/** Abstract camera base class. Collaborates with API. /** Abstract camera base class. Collaborates with API.
* 1) API server starts and installs callback (add_image_callback) * 1) API server starts and installs callback (add_image_callback)
* which is called by the camera when a new image is available. * which is called by the camera when a new image is available.

View File

@@ -0,0 +1,69 @@
#pragma once
#include "buffer.h"
#include "camera.h"
namespace esphome::camera {
/// Result codes from the encoder used to control camera pipeline flow.
enum EncoderError : uint8_t {
ENCODER_ERROR_SUCCESS = 0, ///< Encoding succeeded, continue pipeline normally.
ENCODER_ERROR_SKIP_FRAME, ///< Skip current frame, try again on next frame.
ENCODER_ERROR_RETRY_FRAME, ///< Retry current frame, after buffer growth or for incremental encoding.
ENCODER_ERROR_CONFIGURATION ///< Fatal config error, shut down pipeline.
};
/// Converts EncoderError to string.
inline const char *to_string(EncoderError error) {
switch (error) {
case ENCODER_ERROR_SUCCESS:
return "ENCODER_ERROR_SUCCESS";
case ENCODER_ERROR_SKIP_FRAME:
return "ENCODER_ERROR_SKIP_FRAME";
case ENCODER_ERROR_RETRY_FRAME:
return "ENCODER_ERROR_RETRY_FRAME";
case ENCODER_ERROR_CONFIGURATION:
return "ENCODER_ERROR_CONFIGURATION";
}
return "ENCODER_ERROR_INVALID";
}
/// Interface for an encoder buffer supporting resizing and variable-length data.
class EncoderBuffer {
public:
/// Sets logical buffer size, reallocates if needed.
/// @param size Required size in bytes.
/// @return true on success, false on allocation failure.
virtual bool set_buffer_size(size_t size) = 0;
/// Returns a pointer to the buffer data.
virtual uint8_t *get_data() const = 0;
/// Returns number of bytes currently used.
virtual size_t get_size() const = 0;
/// Returns total allocated buffer size.
virtual size_t get_max_size() const = 0;
virtual ~EncoderBuffer() = default;
};
/// Interface for image encoders used in a camera pipeline.
class Encoder {
public:
/// Encodes pixel data from a previous camera pipeline stage.
/// @param spec Specification of the input pixel data.
/// @param pixels Image pixels in RGB or grayscale format, as specified in @p spec.
/// @return EncoderError Indicating the result of the encoding operation.
virtual EncoderError encode_pixels(CameraImageSpec *spec, Buffer *pixels) = 0;
/// Returns the encoder's output buffer.
/// @return Pointer to an EncoderBuffer containing encoded data.
virtual EncoderBuffer *get_output_buffer() = 0;
/// Prints the encoder's configuration to the log.
virtual void dump_config() = 0;
virtual ~Encoder() = default;
};
} // namespace esphome::camera

View File

@@ -0,0 +1,60 @@
import esphome.codegen as cg
from esphome.components.esp32 import add_idf_component
import esphome.config_validation as cv
from esphome.const import CONF_BUFFER_SIZE, CONF_ID, CONF_TYPE
from esphome.types import ConfigType
CODEOWNERS = ["@DT-art1"]
AUTO_LOAD = ["camera"]
CONF_BUFFER_EXPAND_SIZE = "buffer_expand_size"
CONF_ENCODER_BUFFER_ID = "encoder_buffer_id"
CONF_QUALITY = "quality"
ESP32_CAMERA_ENCODER = "esp32_camera"
camera_ns = cg.esphome_ns.namespace("camera")
camera_encoder_ns = cg.esphome_ns.namespace("camera_encoder")
Encoder = camera_ns.class_("Encoder")
EncoderBufferImpl = camera_encoder_ns.class_("EncoderBufferImpl")
ESP32CameraJPEGEncoder = camera_encoder_ns.class_("ESP32CameraJPEGEncoder", Encoder)
MAX_JPEG_BUFFER_SIZE_2MB = 2 * 1024 * 1024
ESP32_CAMERA_ENCODER_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(ESP32CameraJPEGEncoder),
cv.Optional(CONF_QUALITY, default=80): cv.int_range(1, 100),
cv.Optional(CONF_BUFFER_SIZE, default=4096): cv.int_range(
1024, MAX_JPEG_BUFFER_SIZE_2MB
),
cv.Optional(CONF_BUFFER_EXPAND_SIZE, default=1024): cv.int_range(
0, MAX_JPEG_BUFFER_SIZE_2MB
),
cv.GenerateID(CONF_ENCODER_BUFFER_ID): cv.declare_id(EncoderBufferImpl),
}
)
CONFIG_SCHEMA = cv.typed_schema(
{
ESP32_CAMERA_ENCODER: ESP32_CAMERA_ENCODER_SCHEMA,
},
default_type=ESP32_CAMERA_ENCODER,
)
async def to_code(config: ConfigType) -> None:
buffer = cg.new_Pvariable(config[CONF_ENCODER_BUFFER_ID])
cg.add(buffer.set_buffer_size(config[CONF_BUFFER_SIZE]))
if config[CONF_TYPE] == ESP32_CAMERA_ENCODER:
add_idf_component(name="espressif/esp32-camera", ref="2.1.1")
cg.add_define("USE_ESP32_CAMERA_JPEG_ENCODER")
var = cg.new_Pvariable(
config[CONF_ID],
config[CONF_QUALITY],
buffer,
)
cg.add(var.set_buffer_expand_size(config[CONF_BUFFER_EXPAND_SIZE]))

View File

@@ -0,0 +1,23 @@
#include "encoder_buffer_impl.h"
namespace esphome::camera_encoder {
bool EncoderBufferImpl::set_buffer_size(size_t size) {
if (size > this->capacity_) {
uint8_t *p = this->allocator_.reallocate(this->data_, size);
if (p == nullptr)
return false;
this->data_ = p;
this->capacity_ = size;
}
this->size_ = size;
return true;
}
EncoderBufferImpl::~EncoderBufferImpl() {
if (this->data_ != nullptr)
this->allocator_.deallocate(this->data_, this->capacity_);
}
} // namespace esphome::camera_encoder

View File

@@ -0,0 +1,25 @@
#pragma once
#include "esphome/components/camera/encoder.h"
#include "esphome/core/helpers.h"
namespace esphome::camera_encoder {
class EncoderBufferImpl : public camera::EncoderBuffer {
public:
// --- EncoderBuffer ---
bool set_buffer_size(size_t size) override;
uint8_t *get_data() const override { return this->data_; }
size_t get_size() const override { return this->size_; }
size_t get_max_size() const override { return this->capacity_; }
// ----------------------
~EncoderBufferImpl() override;
protected:
RAMAllocator<uint8_t> allocator_;
size_t capacity_{};
size_t size_{};
uint8_t *data_{};
};
} // namespace esphome::camera_encoder

View File

@@ -0,0 +1,84 @@
#include "esphome/core/defines.h"
#ifdef USE_ESP32_CAMERA_JPEG_ENCODER
#include "esp32_camera_jpeg_encoder.h"
namespace esphome::camera_encoder {
static const char *const TAG = "camera_encoder";
ESP32CameraJPEGEncoder::ESP32CameraJPEGEncoder(uint8_t quality, camera::EncoderBuffer *output) {
this->quality_ = quality;
this->output_ = output;
}
camera::EncoderError ESP32CameraJPEGEncoder::encode_pixels(camera::CameraImageSpec *spec, camera::Buffer *pixels) {
this->bytes_written_ = 0;
this->out_of_output_memory_ = false;
bool success = fmt2jpg_cb(pixels->get_data_buffer(), pixels->get_data_length(), spec->width, spec->height,
to_internal_(spec->format), this->quality_, callback, this);
if (!success)
return camera::ENCODER_ERROR_CONFIGURATION;
if (this->out_of_output_memory_) {
if (this->buffer_expand_size_ <= 0)
return camera::ENCODER_ERROR_SKIP_FRAME;
size_t current_size = this->output_->get_max_size();
size_t new_size = this->output_->get_max_size() + this->buffer_expand_size_;
if (!this->output_->set_buffer_size(new_size)) {
ESP_LOGE(TAG, "Failed to expand output buffer.");
this->buffer_expand_size_ = 0;
return camera::ENCODER_ERROR_SKIP_FRAME;
}
ESP_LOGD(TAG, "Output buffer expanded (%u -> %u).", current_size, this->output_->get_max_size());
return camera::ENCODER_ERROR_RETRY_FRAME;
}
this->output_->set_buffer_size(this->bytes_written_);
return camera::ENCODER_ERROR_SUCCESS;
}
void ESP32CameraJPEGEncoder::dump_config() {
ESP_LOGCONFIG(TAG,
"ESP32 Camera JPEG Encoder:\n"
" Size: %zu\n"
" Quality: %d\n"
" Expand: %d\n",
this->output_->get_max_size(), this->quality_, this->buffer_expand_size_);
}
size_t ESP32CameraJPEGEncoder::callback(void *arg, size_t index, const void *data, size_t len) {
ESP32CameraJPEGEncoder *that = reinterpret_cast<ESP32CameraJPEGEncoder *>(arg);
uint8_t *buffer = that->output_->get_data();
size_t buffer_length = that->output_->get_max_size();
if (index + len > buffer_length) {
that->out_of_output_memory_ = true;
return 0;
}
std::memcpy(&buffer[index], data, len);
that->bytes_written_ += len;
return len;
}
pixformat_t ESP32CameraJPEGEncoder::to_internal_(camera::PixelFormat format) {
switch (format) {
case camera::PIXEL_FORMAT_GRAYSCALE:
return PIXFORMAT_GRAYSCALE;
case camera::PIXEL_FORMAT_RGB565:
return PIXFORMAT_RGB565;
// Internal representation for RGB is in byte order: B, G, R
case camera::PIXEL_FORMAT_BGR888:
return PIXFORMAT_RGB888;
}
return PIXFORMAT_GRAYSCALE;
}
} // namespace esphome::camera_encoder
#endif

View File

@@ -0,0 +1,41 @@
#pragma once
#include "esphome/core/defines.h"
#ifdef USE_ESP32_CAMERA_JPEG_ENCODER
#include <esp_camera.h>
#include "esphome/components/camera/encoder.h"
namespace esphome::camera_encoder {
/// Encoder that uses the software-based JPEG implementation from Espressif's esp32-camera component.
class ESP32CameraJPEGEncoder : public camera::Encoder {
public:
/// Constructs a ESP32CameraJPEGEncoder instance.
/// @param quality Sets the quality of the encoded image (1-100).
/// @param output Pointer to preallocated output buffer.
ESP32CameraJPEGEncoder(uint8_t quality, camera::EncoderBuffer *output);
/// Sets the number of bytes to expand the output buffer on underflow during encoding.
/// @param buffer_expand_size Number of bytes to expand the buffer.
void set_buffer_expand_size(size_t buffer_expand_size) { this->buffer_expand_size_ = buffer_expand_size; }
// -------- Encoder --------
camera::EncoderError encode_pixels(camera::CameraImageSpec *spec, camera::Buffer *pixels) override;
camera::EncoderBuffer *get_output_buffer() override { return output_; }
void dump_config() override;
// -------------------------
protected:
static size_t callback(void *arg, size_t index, const void *data, size_t len);
pixformat_t to_internal_(camera::PixelFormat format);
camera::EncoderBuffer *output_{};
size_t buffer_expand_size_{};
size_t bytes_written_{};
uint8_t quality_{};
bool out_of_output_memory_{};
};
} // namespace esphome::camera_encoder
#endif

View File

@@ -21,8 +21,8 @@ void Canbus::dump_config() {
} }
} }
void Canbus::send_data(uint32_t can_id, bool use_extended_id, bool remote_transmission_request, canbus::Error Canbus::send_data(uint32_t can_id, bool use_extended_id, bool remote_transmission_request,
const std::vector<uint8_t> &data) { const std::vector<uint8_t> &data) {
struct CanFrame can_message; struct CanFrame can_message;
uint8_t size = static_cast<uint8_t>(data.size()); uint8_t size = static_cast<uint8_t>(data.size());
@@ -45,13 +45,15 @@ void Canbus::send_data(uint32_t can_id, bool use_extended_id, bool remote_transm
ESP_LOGVV(TAG, " data[%d]=%02x", i, can_message.data[i]); ESP_LOGVV(TAG, " data[%d]=%02x", i, can_message.data[i]);
} }
if (this->send_message(&can_message) != canbus::ERROR_OK) { canbus::Error error = this->send_message(&can_message);
if (error != canbus::ERROR_OK) {
if (use_extended_id) { if (use_extended_id) {
ESP_LOGW(TAG, "send to extended id=0x%08" PRIx32 " failed!", can_id); ESP_LOGW(TAG, "send to extended id=0x%08" PRIx32 " failed with error %d!", can_id, error);
} else { } else {
ESP_LOGW(TAG, "send to standard id=0x%03" PRIx32 " failed!", can_id); ESP_LOGW(TAG, "send to standard id=0x%03" PRIx32 " failed with error %d!", can_id, error);
} }
} }
return error;
} }
void Canbus::add_trigger(CanbusTrigger *trigger) { void Canbus::add_trigger(CanbusTrigger *trigger) {

View File

@@ -70,11 +70,11 @@ class Canbus : public Component {
float get_setup_priority() const override { return setup_priority::HARDWARE; } float get_setup_priority() const override { return setup_priority::HARDWARE; }
void loop() override; void loop() override;
void send_data(uint32_t can_id, bool use_extended_id, bool remote_transmission_request, canbus::Error send_data(uint32_t can_id, bool use_extended_id, bool remote_transmission_request,
const std::vector<uint8_t> &data); const std::vector<uint8_t> &data);
void send_data(uint32_t can_id, bool use_extended_id, const std::vector<uint8_t> &data) { canbus::Error send_data(uint32_t can_id, bool use_extended_id, const std::vector<uint8_t> &data) {
// for backwards compatibility only // for backwards compatibility only
this->send_data(can_id, use_extended_id, false, data); return this->send_data(can_id, use_extended_id, false, data);
} }
void set_can_id(uint32_t can_id) { this->can_id_ = can_id; } void set_can_id(uint32_t can_id) { this->can_id_ = can_id; }
void set_use_extended_id(bool use_extended_id) { this->use_extended_id_ = use_extended_id; } void set_use_extended_id(bool use_extended_id) { this->use_extended_id_ = use_extended_id; }

View File

@@ -1,6 +1,7 @@
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import web_server_base from esphome.components import web_server_base
from esphome.components.web_server_base import CONF_WEB_SERVER_BASE_ID from esphome.components.web_server_base import CONF_WEB_SERVER_BASE_ID
from esphome.config_helpers import filter_source_files_from_platform
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_ID, CONF_ID,
@@ -9,10 +10,19 @@ from esphome.const import (
PLATFORM_ESP8266, PLATFORM_ESP8266,
PLATFORM_LN882X, PLATFORM_LN882X,
PLATFORM_RTL87XX, PLATFORM_RTL87XX,
PlatformFramework,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
from esphome.coroutine import CoroPriority
def AUTO_LOAD() -> list[str]:
auto_load = ["web_server_base", "ota.web_server"]
if CORE.using_esp_idf:
auto_load.append("socket")
return auto_load
AUTO_LOAD = ["web_server_base", "ota.web_server"]
DEPENDENCIES = ["wifi"] DEPENDENCIES = ["wifi"]
CODEOWNERS = ["@esphome/core"] CODEOWNERS = ["@esphome/core"]
@@ -40,7 +50,7 @@ CONFIG_SCHEMA = cv.All(
) )
@coroutine_with_priority(64.0) @coroutine_with_priority(CoroPriority.CAPTIVE_PORTAL)
async def to_code(config): async def to_code(config):
paren = await cg.get_variable(config[CONF_WEB_SERVER_BASE_ID]) paren = await cg.get_variable(config[CONF_WEB_SERVER_BASE_ID])
@@ -57,3 +67,11 @@ async def to_code(config):
cg.add_library("DNSServer", None) cg.add_library("DNSServer", None)
if CORE.is_libretiny: if CORE.is_libretiny:
cg.add_library("DNSServer", None) cg.add_library("DNSServer", None)
# Only compile the ESP-IDF DNS server when using ESP-IDF framework
FILTER_SOURCE_FILES = filter_source_files_from_platform(
{
"dns_server_esp32_idf.cpp": {PlatformFramework.ESP32_IDF},
}
)

View File

@@ -7,103 +7,83 @@ namespace esphome {
namespace captive_portal { namespace captive_portal {
const uint8_t INDEX_GZ[] PROGMEM = { const uint8_t INDEX_GZ[] PROGMEM = {
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xdd, 0x58, 0x6d, 0x6f, 0xdb, 0x38, 0x12, 0xfe, 0xde, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x95, 0x16, 0x6b, 0x8f, 0xdb, 0x36, 0xf2, 0x7b, 0x7e,
0x5f, 0x31, 0xa7, 0x36, 0x6b, 0x6b, 0x1b, 0x51, 0x22, 0xe5, 0xb7, 0xd8, 0x92, 0x16, 0x69, 0xae, 0x8b, 0x5d, 0xa0, 0x05, 0x8f, 0x49, 0xbb, 0x52, 0xb3, 0x7a, 0x7a, 0xed, 0x6c, 0x24, 0x51, 0x45, 0x9a, 0xbb, 0xa2, 0x05, 0x9a, 0x36,
0xdd, 0x2d, 0x90, 0x6c, 0xef, 0x43, 0x51, 0x20, 0xb4, 0x34, 0xb2, 0xd8, 0x48, 0xa4, 0x4e, 0xa4, 0x5f, 0x52, 0xc3, 0xc0, 0x6e, 0x73, 0x1f, 0x82, 0x00, 0x4b, 0x53, 0x23, 0x8b, 0x31, 0x45, 0xea, 0x48, 0xca, 0x8f, 0x18, 0xbe, 0xdf,
0xf7, 0xdb, 0x0f, 0x94, 0x6c, 0xc7, 0xe9, 0x35, 0x87, 0xeb, 0xe2, 0x0e, 0x87, 0xdd, 0x18, 0x21, 0x86, 0xe4, 0xcc, 0x7e, 0xa0, 0x24, 0x7b, 0x9d, 0x45, 0x73, 0xb8, 0xb3, 0x60, 0x61, 0x38, 0xef, 0x19, 0xcd, 0x83, 0xc5, 0xdf, 0x2a,
0x70, 0xe6, 0xf1, 0x0c, 0x67, 0xcc, 0xe8, 0x2f, 0x99, 0x4a, 0xcd, 0x7d, 0x8d, 0x50, 0x98, 0xaa, 0x4c, 0x22, 0x3b, 0xc5, 0xec, 0xbe, 0x03, 0xd4, 0xd8, 0x56, 0x94, 0x85, 0x7b, 0x23, 0x41, 0xe5, 0x8a, 0x80, 0x2c, 0x8b, 0x06, 0x68,
0x42, 0xc9, 0xe5, 0x22, 0x46, 0x99, 0x44, 0x05, 0xf2, 0x2c, 0x89, 0x2a, 0x34, 0x1c, 0xd2, 0x82, 0x37, 0x1a, 0x4d, 0x55, 0x16, 0x2d, 0x58, 0x8a, 0x58, 0x43, 0xb5, 0x01, 0x4b, 0xfe, 0xbc, 0xff, 0x39, 0xb8, 0x2d, 0x0b, 0xc1, 0xe5,
0xfc, 0xdb, 0xcd, 0x8f, 0xde, 0x04, 0xfc, 0x24, 0x2a, 0x85, 0xbc, 0x83, 0x06, 0xcb, 0x58, 0xa4, 0x4a, 0x42, 0xd1, 0x1a, 0x69, 0x10, 0x84, 0x33, 0x25, 0x51, 0xa3, 0xa1, 0x26, 0x15, 0xb5, 0x34, 0xe3, 0x2d, 0x5d, 0xc1, 0x24, 0x22,
0x60, 0x1e, 0x67, 0xdc, 0xf0, 0xa9, 0xa8, 0xf8, 0x02, 0x2d, 0x43, 0x2b, 0x26, 0x79, 0x85, 0xf1, 0x4a, 0xe0, 0xba, 0x69, 0x0b, 0x64, 0xc3, 0x61, 0xdb, 0x29, 0x6d, 0x11, 0x53, 0xd2, 0x82, 0xb4, 0x04, 0x6f, 0x79, 0x65, 0x1b, 0x52,
0x56, 0x8d, 0x81, 0x54, 0x49, 0x83, 0xd2, 0xc4, 0xce, 0x5a, 0x64, 0xa6, 0x88, 0x33, 0x5c, 0x89, 0x14, 0xbd, 0x76, 0xc1, 0x86, 0x33, 0x08, 0x86, 0xc3, 0x35, 0x97, 0xdc, 0x72, 0x2a, 0x02, 0xc3, 0xa8, 0x00, 0x92, 0x5c, 0xf7, 0x06,
0x72, 0x2e, 0xa4, 0x30, 0x82, 0x97, 0x9e, 0x4e, 0x79, 0x89, 0x31, 0x3d, 0x5f, 0x6a, 0x6c, 0xda, 0x09, 0x9f, 0x97, 0xf4, 0x70, 0xa0, 0x4b, 0x01, 0x44, 0x2a, 0x5c, 0x16, 0x86, 0x69, 0xde, 0x59, 0xe4, 0x5c, 0x25, 0xad, 0xaa, 0x7a,
0x18, 0x4b, 0xe5, 0xf8, 0x49, 0xa4, 0xd3, 0x46, 0xd4, 0x06, 0xac, 0xbd, 0x71, 0xa5, 0xb2, 0x65, 0x89, 0x89, 0xef, 0x01, 0x65, 0x14, 0x51, 0x63, 0xc0, 0x9a, 0x88, 0xcb, 0x0a, 0x76, 0xe1, 0x32, 0x66, 0x2c, 0x86, 0xdb, 0xdb, 0xf0,
0x73, 0xad, 0xd1, 0x68, 0x5f, 0xc8, 0x0c, 0x37, 0x64, 0x14, 0x86, 0x29, 0xe3, 0xe3, 0x9c, 0x7c, 0xd2, 0xcf, 0x32, 0xb3, 0x79, 0x56, 0x29, 0xd6, 0xb7, 0x20, 0x6d, 0x28, 0x14, 0xa3, 0x96, 0x2b, 0x19, 0x1a, 0xa0, 0x9a, 0x35, 0x84,
0x95, 0x2e, 0x2b, 0x94, 0x86, 0x94, 0x2a, 0xe5, 0x46, 0x28, 0x49, 0x34, 0xf2, 0x26, 0x2d, 0xe2, 0x38, 0x76, 0x7e, 0x10, 0xfc, 0xa3, 0xa1, 0x1b, 0xc0, 0xdf, 0x7f, 0xef, 0x9d, 0x99, 0x56, 0x60, 0xff, 0x21, 0xc0, 0x81, 0xe6, 0xa7,
0xd0, 0x7c, 0x85, 0xce, 0x77, 0xdf, 0xf5, 0x8f, 0x4c, 0x0b, 0x34, 0xaf, 0x4b, 0xb4, 0xa4, 0x7e, 0x75, 0x7f, 0xc3, 0xfd, 0x3d, 0x5d, 0xfd, 0x4e, 0x5b, 0xf0, 0x30, 0x35, 0xbc, 0x02, 0xec, 0x7f, 0x8c, 0x3f, 0x85, 0xc6, 0xee, 0x05,
0x17, 0xbf, 0xf0, 0x0a, 0xfb, 0x0e, 0xd7, 0x22, 0x43, 0xc7, 0xfd, 0x10, 0x7c, 0x24, 0xda, 0xdc, 0x97, 0x48, 0x32, 0x84, 0x15, 0x37, 0x9d, 0xa0, 0x7b, 0x82, 0x97, 0x42, 0xb1, 0x35, 0xf6, 0xf3, 0xba, 0x97, 0xcc, 0x29, 0x47, 0xc6,
0xa1, 0xeb, 0x92, 0xdf, 0xc7, 0xce, 0xbc, 0x54, 0xe9, 0x9d, 0xe3, 0xce, 0xf2, 0xa5, 0x4c, 0xad, 0x72, 0xd0, 0x7d, 0x03, 0xff, 0x20, 0xc0, 0x22, 0x4b, 0xde, 0x51, 0xdb, 0x84, 0x2d, 0xdd, 0x79, 0x23, 0xc0, 0xa5, 0x97, 0xfe, 0xe0,
0x74, 0xb7, 0x25, 0x1a, 0x30, 0xf1, 0x5b, 0x6e, 0x0a, 0x52, 0xf1, 0x4d, 0xbf, 0x23, 0x84, 0xec, 0xb3, 0xef, 0xfb, 0xc1, 0xcb, 0x24, 0x8e, 0xfd, 0xeb, 0xe1, 0x15, 0xfb, 0x51, 0x12, 0xc7, 0xb9, 0x06, 0xdb, 0x6b, 0x89, 0xa8, 0xf7,
0xf8, 0x92, 0x06, 0x81, 0x7b, 0xde, 0x0e, 0x81, 0xeb, 0xd3, 0x20, 0x98, 0x35, 0x68, 0x96, 0x8d, 0x04, 0xde, 0xbf, 0x50, 0x74, 0xd4, 0x36, 0xa8, 0x22, 0xf8, 0x5d, 0x92, 0xa2, 0xe4, 0x75, 0x98, 0xce, 0x7f, 0x0b, 0x5f, 0xa1, 0x9b,
0x8d, 0x6a, 0x6e, 0x0a, 0xc8, 0x62, 0xa7, 0xa2, 0x8c, 0x04, 0xc1, 0x04, 0xe8, 0x05, 0x61, 0x43, 0x8f, 0x52, 0x12, 0x30, 0x9d, 0xb3, 0x57, 0xc1, 0x1c, 0x25, 0x37, 0xc1, 0x1c, 0xa5, 0x69, 0x38, 0x47, 0xf1, 0x17, 0x8c, 0x6a, 0x2e,
0x7a, 0x74, 0x98, 0x8e, 0xbd, 0x21, 0xd0, 0x81, 0x37, 0x04, 0xc6, 0xc8, 0x10, 0x82, 0xcf, 0x0e, 0xe4, 0xa2, 0x2c, 0x04, 0xc1, 0x52, 0x49, 0xc0, 0xc8, 0x58, 0xad, 0xd6, 0x40, 0x30, 0xeb, 0xb5, 0x06, 0x69, 0xdf, 0x2a, 0xa1, 0x34,
0x63, 0x47, 0x2a, 0x89, 0x0e, 0x68, 0xd3, 0xa8, 0x3b, 0x8c, 0x9d, 0x74, 0xd9, 0x34, 0x28, 0xcd, 0x95, 0x2a, 0x55, 0x8e, 0xca, 0x67, 0xff, 0x97, 0x42, 0xab, 0xa9, 0x34, 0xb5, 0xd2, 0x2d, 0xc1, 0x43, 0xf6, 0xbd, 0x17, 0x07, 0x7b,
0xe3, 0xf8, 0xc9, 0x33, 0x78, 0xf4, 0xf7, 0xcd, 0x47, 0x98, 0x86, 0x4b, 0x9d, 0xab, 0xa6, 0x8a, 0x9d, 0xf6, 0x4b, 0x44, 0xee, 0xe5, 0x5f, 0x10, 0x03, 0xa5, 0xf9, 0x8a, 0x4b, 0x82, 0x9d, 0xc6, 0x5b, 0x1c, 0x95, 0x0f, 0xfe, 0xf1,
0xe9, 0xbf, 0xd8, 0x9a, 0x1d, 0xd8, 0xc1, 0x3d, 0xd9, 0xf4, 0x54, 0x23, 0x16, 0x42, 0xc6, 0x0e, 0x65, 0x40, 0x27, 0x1c, 0x3d, 0x75, 0xd1, 0x4f, 0xf1, 0x28, 0xef, 0xe3, 0x43, 0x61, 0x36, 0x2b, 0xb4, 0x6b, 0x85, 0x34, 0x04, 0x37,
0x8e, 0x9f, 0xdc, 0xba, 0xbb, 0x23, 0x26, 0xdc, 0x62, 0xb2, 0xf7, 0x52, 0xf5, 0x3f, 0xdc, 0x46, 0x7a, 0xb5, 0x80, 0xd6, 0x76, 0x59, 0x14, 0x6d, 0xb7, 0xdb, 0x70, 0x3b, 0x0b, 0x95, 0x5e, 0x45, 0x69, 0x1c, 0xc7, 0x91, 0xd9, 0xac,
0x4d, 0x55, 0x4a, 0x1d, 0x3b, 0x85, 0x31, 0xf5, 0xd4, 0xf7, 0xd7, 0xeb, 0x35, 0x59, 0x87, 0x44, 0x35, 0x0b, 0x9f, 0x30, 0x1a, 0x0b, 0x01, 0xa7, 0x37, 0x18, 0x35, 0xc0, 0x57, 0x8d, 0x1d, 0xe0, 0xf2, 0xc5, 0x01, 0x8e, 0x85, 0xe3,
0x05, 0x41, 0xe0, 0xeb, 0xd5, 0xc2, 0x81, 0x2e, 0x3e, 0x1c, 0x36, 0x70, 0xa0, 0x40, 0xb1, 0x28, 0x4c, 0x4b, 0x27, 0x28, 0x1f, 0x3e, 0x5d, 0x58, 0xe1, 0x17, 0x56, 0xe0, 0x47, 0xea, 0xe1, 0x53, 0x98, 0x57, 0x43, 0x98, 0xaf, 0x68,
0x2f, 0xb6, 0xb8, 0x8b, 0x2c, 0x47, 0x72, 0xfb, 0xf1, 0xe4, 0x14, 0x71, 0x72, 0x0a, 0xfe, 0x70, 0x82, 0x66, 0xef, 0x8a, 0x52, 0x14, 0x0f, 0x4f, 0x1a, 0x38, 0x78, 0x3a, 0x05, 0x4f, 0x4e, 0xe8, 0xe2, 0xe4, 0xa0, 0x76, 0x11, 0xbc,
0xad, 0x35, 0x6a, 0xcc, 0x19, 0x30, 0x08, 0xda, 0x0f, 0xf3, 0x2c, 0xbd, 0x9f, 0x79, 0x5f, 0xcc, 0xe0, 0x64, 0x06, 0x3e, 0xcb, 0x26, 0x0e, 0xb3, 0x49, 0xe2, 0x47, 0x84, 0x13, 0xf8, 0x65, 0x71, 0x79, 0x0e, 0xd2, 0x0f, 0x97, 0x0c,
0x0c, 0x9e, 0x01, 0xb0, 0x6a, 0xe4, 0x5d, 0x1c, 0xc5, 0xa9, 0xdd, 0x5e, 0xd1, 0xe0, 0x61, 0xc1, 0xca, 0xfc, 0x34, 0xce, 0x5a, 0x93, 0x7c, 0x58, 0xd0, 0x39, 0x9a, 0x4f, 0x98, 0x79, 0xe0, 0xe0, 0xf3, 0x09, 0xcd, 0x37, 0x69, 0x93,
0x3a, 0x9d, 0x7b, 0xec, 0xbd, 0x65, 0xb0, 0xd8, 0x1f, 0x85, 0x3c, 0x56, 0xd0, 0xf7, 0x23, 0x3e, 0x84, 0xe1, 0x7e, 0xb4, 0xc1, 0x22, 0x98, 0xd3, 0x19, 0x9a, 0x4d, 0x8e, 0xcc, 0xd0, 0x6c, 0x93, 0x36, 0x8b, 0x0f, 0x8b, 0x4b, 0x5c,
0x65, 0xe8, 0x59, 0xfa, 0x38, 0xb3, 0x27, 0xc1, 0x70, 0xc5, 0x0a, 0x5a, 0x79, 0x23, 0x6f, 0xc8, 0x43, 0x08, 0xf7, 0x30, 0xfb, 0x72, 0x15, 0x95, 0xd8, 0xcf, 0x30, 0x7e, 0x8c, 0x5c, 0x5d, 0x46, 0x1e, 0x7e, 0x56, 0x5c, 0x7a, 0x18,
0x26, 0x85, 0x10, 0xae, 0x58, 0x31, 0x7a, 0x3f, 0x3a, 0x5d, 0xf3, 0xc2, 0xcf, 0x3d, 0x0b, 0xf3, 0xd4, 0x71, 0x1e, 0xfb, 0xc7, 0x1a, 0x2c, 0x6b, 0x3c, 0x1c, 0x31, 0x25, 0x6b, 0xbe, 0x0a, 0x3f, 0x1b, 0x25, 0xb1, 0x1f, 0xda, 0x06,
0x30, 0x50, 0xa7, 0x18, 0x90, 0x4f, 0x4a, 0xc8, 0xbe, 0xe3, 0xb8, 0xbb, 0x1c, 0x4d, 0x5a, 0xf4, 0x1d, 0x3f, 0x55, 0xa4, 0x77, 0x12, 0x75, 0x82, 0x30, 0x50, 0xbc, 0xa7, 0x14, 0xeb, 0x1f, 0xce, 0xf5, 0x6f, 0xb9, 0x15, 0x40, 0x6c,
0x32, 0x17, 0x0b, 0xf2, 0x49, 0x2b, 0xe9, 0xb8, 0xc4, 0x14, 0x28, 0xfb, 0x07, 0x51, 0x2b, 0x88, 0xed, 0x4e, 0xff, 0xe8, 0x1a, 0xf6, 0xfa, 0x8c, 0x5d, 0xaa, 0x6a, 0xff, 0x8d, 0xd6, 0x68, 0x92, 0xb1, 0x2f, 0xb8, 0x94, 0xa0, 0xef,
0xcb, 0x1d, 0xe3, 0x6e, 0x8f, 0xf9, 0x61, 0x84, 0x29, 0x31, 0x36, 0xc4, 0x66, 0xf4, 0xf9, 0x71, 0x75, 0xae, 0xb2, 0x61, 0x67, 0x09, 0x7e, 0xf7, 0xe6, 0x2d, 0x7a, 0x53, 0x55, 0x1a, 0x8c, 0xc9, 0x10, 0x7e, 0x69, 0xc3, 0x96, 0xb2,
0xfb, 0x27, 0x52, 0xa7, 0xa0, 0x5d, 0xde, 0x08, 0x29, 0xb1, 0xb9, 0xc1, 0x8d, 0x89, 0x9d, 0xb7, 0x97, 0x57, 0x70, 0xff, 0x5d, 0x57, 0xf2, 0x95, 0xae, 0x7f, 0xf2, 0x9f, 0x39, 0xfa, 0x1d, 0xec, 0x56, 0xe9, 0xf5, 0xa4, 0xcd, 0xb9,
0x99, 0x65, 0x0d, 0x6a, 0x3d, 0x05, 0xe7, 0xa5, 0x21, 0x15, 0x4f, 0xff, 0x73, 0x5d, 0xf4, 0x91, 0xae, 0xbf, 0x89, 0x96, 0xbb, 0x0e, 0xd3, 0xc4, 0x86, 0xb4, 0x33, 0xa1, 0x11, 0x9c, 0x81, 0x97, 0xf8, 0x61, 0x4b, 0xbb, 0xc7, 0xa8,
0x1f, 0x05, 0xfc, 0x82, 0x66, 0xad, 0x9a, 0xbb, 0xbd, 0x36, 0x6b, 0xda, 0xcc, 0x66, 0x60, 0x13, 0x1b, 0xc2, 0x6b, 0xe4, 0x29, 0x51, 0x0f, 0x45, 0xc5, 0x37, 0x88, 0x09, 0x6a, 0x0c, 0xc1, 0x72, 0x54, 0x85, 0xd1, 0x33, 0x34, 0xfc,
0x4d, 0x74, 0x29, 0x52, 0xec, 0x53, 0x97, 0x54, 0xbc, 0x7e, 0xf0, 0x4a, 0x1e, 0x80, 0xba, 0x8d, 0x32, 0xb1, 0x82, 0x94, 0x64, 0x82, 0xb3, 0x35, 0xc1, 0x7f, 0x31, 0x01, 0x7e, 0xda, 0xff, 0x5a, 0x79, 0x57, 0xc6, 0xf0, 0xea, 0xca,
0xb4, 0xe4, 0x5a, 0xc7, 0x8e, 0xec, 0x54, 0x39, 0xb0, 0x4f, 0x1b, 0x25, 0xd3, 0x52, 0xa4, 0x77, 0xb1, 0xf3, 0x95, 0x0f, 0x37, 0x54, 0xf4, 0x80, 0x08, 0xb2, 0x0d, 0x37, 0x8f, 0x0e, 0xe6, 0xdf, 0x14, 0xeb, 0xcc, 0xfa, 0xca, 0x0f,
0x1b, 0xe2, 0xd5, 0xfd, 0xcf, 0x59, 0xbf, 0xa7, 0xb5, 0xc8, 0x7a, 0x2e, 0x59, 0xf1, 0x72, 0x89, 0x10, 0x83, 0x29, 0x6b, 0xc5, 0x7a, 0xe3, 0xf9, 0xb8, 0x9c, 0xcc, 0x15, 0x74, 0x1c, 0x90, 0xf8, 0x39, 0x7e, 0xe2, 0x51, 0x20, 0xa0,
0x84, 0x7e, 0x30, 0x70, 0xf6, 0xa4, 0x58, 0xad, 0xef, 0x7a, 0x2e, 0xc9, 0x55, 0xba, 0xd4, 0x7d, 0xd7, 0x39, 0x64, 0xb6, 0x67, 0x3e, 0x84, 0x5e, 0x1c, 0x8c, 0x27, 0x43, 0x6d, 0x0c, 0xf7, 0x8f, 0x67, 0x64, 0x61, 0x3a, 0x2a, 0x9f,
0x69, 0xc4, 0xbb, 0x3b, 0xd4, 0x79, 0xee, 0x7c, 0x61, 0x91, 0x57, 0x62, 0x6e, 0x9c, 0x87, 0x6c, 0x7e, 0xb1, 0xd5, 0x0a, 0x3a, 0x07, 0x5d, 0xab, 0xc8, 0xd0, 0x41, 0xae, 0x5f, 0x3a, 0x2a, 0xcf, 0x06, 0x23, 0x7a, 0x02, 0x5f, 0x1c,
0x7d, 0x49, 0x1a, 0xad, 0x85, 0xbb, 0x3b, 0x2e, 0x46, 0xba, 0xe6, 0xf2, 0x4b, 0x41, 0x6b, 0xa0, 0x4d, 0x1a, 0x49, 0xb8, 0x27, 0xdd, 0x14, 0x5c, 0x9f, 0x35, 0x16, 0x51, 0xc5, 0x37, 0xe5, 0xc3, 0xd1, 0x7f, 0x8c, 0xe3, 0x5f, 0x3d,
0x2c, 0x65, 0x33, 0xa7, 0xe6, 0xf2, 0x78, 0xa0, 0xcf, 0x0f, 0xe4, 0x8b, 0xad, 0xe8, 0x4b, 0x7b, 0x4b, 0xde, 0x1d, 0xe8, 0xfd, 0x1d, 0x08, 0x60, 0x56, 0x69, 0x0f, 0x3f, 0x97, 0x60, 0xb1, 0x3f, 0x06, 0xfc, 0xcb, 0xfd, 0xbb, 0xdf,
0x35, 0x46, 0x7e, 0x26, 0x56, 0xc9, 0xed, 0xce, 0x7d, 0xf0, 0xe3, 0xef, 0x4b, 0x6c, 0xee, 0xaf, 0xb1, 0xc4, 0xd4, 0x88, 0xf2, 0xb4, 0x7f, 0xfd, 0x2d, 0x6e, 0xb7, 0x0a, 0x3e, 0x6a, 0x10, 0xff, 0x26, 0x57, 0x6e, 0x19, 0x5c, 0x7d,
0xa8, 0xa6, 0xef, 0x3c, 0x97, 0x68, 0x1c, 0xb7, 0x73, 0xf8, 0xa7, 0x9b, 0xb7, 0x6f, 0x62, 0xd5, 0x6f, 0xdc, 0xf3, 0xc2, 0x7e, 0x38, 0xc4, 0xfb, 0xf0, 0xb8, 0x11, 0x5c, 0x3b, 0xbf, 0xdc, 0xb5, 0xe2, 0xda, 0x45, 0x18, 0x2c, 0xe6,
0xa7, 0xb8, 0x6d, 0xb5, 0xf8, 0xd0, 0x60, 0xf9, 0x8f, 0xb8, 0x67, 0xeb, 0x45, 0xef, 0xa3, 0xe3, 0x92, 0xd6, 0xdf, 0xfe, 0xf1, 0xe1, 0xe8, 0x1f, 0xfd, 0xbc, 0x88, 0xc6, 0xb9, 0x5e, 0x16, 0xc3, 0x88, 0x2d, 0x7f, 0x38, 0x2c, 0xd5,
0xdb, 0x87, 0xa2, 0x61, 0x13, 0xfb, 0xe5, 0xa6, 0x2a, 0xcf, 0xad, 0x87, 0xde, 0x68, 0xe8, 0xee, 0x6e, 0x77, 0xee, 0x2e, 0x30, 0xfc, 0x0b, 0x97, 0xab, 0x8c, 0xcb, 0x06, 0x34, 0xb7, 0xc7, 0x8a, 0x6f, 0xae, 0xb9, 0xec, 0x7a, 0x7b,
0xce, 0x9d, 0x45, 0x7e, 0x77, 0xef, 0x27, 0x51, 0x7b, 0x05, 0x27, 0xdf, 0x6f, 0xe7, 0x6a, 0xe3, 0x69, 0xf1, 0x59, 0xe8, 0x68, 0x55, 0x39, 0xca, 0xbc, 0xdb, 0xe5, 0xb5, 0x92, 0xd6, 0x71, 0x42, 0x96, 0x40, 0x7b, 0x1c, 0xe9, 0xc3,
0xc8, 0xc5, 0x54, 0xc8, 0x02, 0x1b, 0x61, 0x76, 0x99, 0x58, 0x9d, 0x0b, 0x59, 0x2f, 0xcd, 0xb6, 0xe6, 0x59, 0x66, 0x44, 0xc9, 0x5e, 0xcf, 0xbf, 0x3b, 0xba, 0x82, 0x3b, 0x58, 0xd8, 0xd9, 0x80, 0x0a, 0xbe, 0x92, 0x19, 0x03, 0x69,
0x77, 0x86, 0xf5, 0x66, 0x96, 0x2b, 0x69, 0x2c, 0x27, 0x4e, 0x29, 0x56, 0xbb, 0x6e, 0xbf, 0xbd, 0x5b, 0xa6, 0x17, 0x41, 0x8f, 0x42, 0x35, 0x6d, 0xb9, 0xd8, 0x67, 0x86, 0x4a, 0x13, 0x18, 0xd0, 0xbc, 0x3e, 0x2e, 0x7b, 0x6b, 0x95,
0xc3, 0xb3, 0x9d, 0x0d, 0xb8, 0xad, 0xc1, 0x8d, 0xf1, 0x78, 0x29, 0x16, 0x72, 0x9a, 0xa2, 0x34, 0xd8, 0x74, 0x42, 0x3c, 0x2c, 0x95, 0xae, 0x40, 0x67, 0x71, 0x3e, 0x02, 0x81, 0xa6, 0x15, 0xef, 0x4d, 0x16, 0xce, 0x34, 0xb4, 0xf9,
0x39, 0xaf, 0x44, 0x79, 0x3f, 0xd5, 0x5c, 0x6a, 0x4f, 0x63, 0x23, 0xf2, 0xdd, 0x7c, 0x69, 0x8c, 0x92, 0xdb, 0xb9, 0x92, 0xb2, 0xf5, 0x4a, 0xab, 0x5e, 0x56, 0x01, 0x73, 0x93, 0x36, 0x7b, 0x9e, 0xd4, 0x74, 0x06, 0x2c, 0x9f, 0x4e,
0x6a, 0x32, 0x6c, 0xa6, 0xc1, 0xac, 0x23, 0xbc, 0x86, 0x67, 0x62, 0xa9, 0xa7, 0x24, 0x6c, 0xb0, 0x9a, 0xcd, 0x79, 0x75, 0x5d, 0xe7, 0x82, 0x4b, 0x08, 0xc6, 0x59, 0x96, 0xa5, 0xe1, 0x8d, 0x13, 0xbb, 0x70, 0x33, 0x4c, 0x1d, 0x62,
0x7a, 0xb7, 0x68, 0xd4, 0x52, 0x66, 0x5e, 0x6a, 0x6f, 0xe1, 0xe9, 0x73, 0x9a, 0xf3, 0x10, 0xd3, 0xd9, 0x7e, 0x96, 0xf4, 0x31, 0x89, 0xe3, 0xef, 0xf2, 0x53, 0x38, 0x71, 0xce, 0x7a, 0x6d, 0x94, 0xce, 0x3a, 0xc5, 0x9d, 0x9b, 0xc7,
0xe7, 0xf9, 0xac, 0x14, 0x12, 0xbd, 0xee, 0x56, 0x9b, 0x32, 0x32, 0xb0, 0x62, 0x27, 0x66, 0x12, 0x66, 0x17, 0x3a, 0x96, 0x72, 0x79, 0xe9, 0xbd, 0x2b, 0x93, 0x7c, 0x5a, 0x3f, 0x19, 0x97, 0x83, 0x99, 0x61, 0x09, 0xe5, 0x2d, 0x97,
0x1b, 0x69, 0x10, 0x9c, 0xcd, 0x0e, 0xee, 0x04, 0xb3, 0x74, 0xd9, 0x68, 0xd5, 0x4c, 0x6b, 0x25, 0xac, 0x99, 0xbb, 0xe3, 0x0e, 0xcd, 0xd2, 0x45, 0xdc, 0xed, 0x8e, 0xe1, 0x54, 0x20, 0x87, 0x13, 0x77, 0x2d, 0x60, 0x97, 0x7f, 0xee,
0x8a, 0x0b, 0x79, 0x6a, 0xbd, 0x0d, 0x93, 0xd9, 0xbe, 0x3c, 0x4d, 0x85, 0x6c, 0x8f, 0x69, 0x8b, 0xd4, 0xac, 0x12, 0x8d, 0xe5, 0xf5, 0x3e, 0x98, 0x76, 0x70, 0x66, 0x3a, 0xca, 0x20, 0x58, 0x82, 0xdd, 0x02, 0xc8, 0x7c, 0xb0, 0x11,
0xb2, 0x2b, 0xb2, 0x53, 0x36, 0x0a, 0xea, 0xcd, 0x8e, 0xec, 0x03, 0x64, 0x7b, 0xe0, 0xce, 0x4b, 0xdc, 0xcc, 0x3e, 0x70, 0x0b, 0xad, 0x99, 0xf2, 0x74, 0x56, 0x33, 0x14, 0xe8, 0xd7, 0xba, 0xfe, 0x1b, 0xb7, 0xab, 0xc5, 0x43, 0x4b,
0x2d, 0xb5, 0x11, 0xf9, 0xbd, 0xb7, 0x2f, 0xd2, 0x53, 0x5d, 0xf3, 0x14, 0xbd, 0x39, 0x9a, 0x35, 0xa2, 0x9c, 0xb5, 0xf5, 0x8a, 0xcb, 0x60, 0xa9, 0xac, 0x55, 0x6d, 0x16, 0xbc, 0xea, 0x76, 0xf9, 0x84, 0x72, 0xca, 0xb2, 0xc4, 0xb9,
0x67, 0x78, 0xc2, 0x60, 0xa5, 0xf7, 0x38, 0x1d, 0xd5, 0xb4, 0x01, 0xfa, 0x58, 0xd7, 0xbf, 0xe3, 0xb6, 0xb1, 0xb8, 0x39, 0xec, 0xd6, 0x53, 0xbe, 0x93, 0x6e, 0x87, 0x8c, 0x12, 0xbc, 0x9a, 0xf8, 0x06, 0x16, 0x14, 0x9f, 0xd3, 0x93,
0xad, 0x78, 0xb3, 0x10, 0xd2, 0x9b, 0x2b, 0x63, 0x54, 0x35, 0xf5, 0xc6, 0xf5, 0x66, 0xb6, 0x5f, 0xb2, 0xca, 0xa6, 0xcc, 0xbb, 0x1d, 0x72, 0xb8, 0x53, 0xaa, 0x6f, 0xea, 0x5b, 0x9a, 0xc4, 0x7f, 0xf1, 0x45, 0xaa, 0xba, 0x4e, 0x97,
0xd4, 0x9a, 0xd9, 0xd6, 0xde, 0x03, 0xde, 0xb4, 0xde, 0x80, 0x56, 0xa5, 0xc8, 0xf6, 0x7c, 0x2d, 0x0b, 0x04, 0x47, 0xf5, 0x39, 0x53, 0x6e, 0x4d, 0xba, 0xd6, 0x18, 0x4a, 0xab, 0x88, 0xc6, 0xdb, 0x8c, 0xab, 0x8c, 0xb2, 0x70, 0x19,
0x78, 0xe8, 0xb0, 0xde, 0x80, 0x5d, 0x3b, 0x40, 0x3d, 0xc8, 0x27, 0x9c, 0x06, 0x5f, 0xf9, 0x46, 0xb2, 0x3c, 0x67, 0x2e, 0x8b, 0x26, 0x41, 0xbc, 0x22, 0x2d, 0x65, 0xe5, 0xc5, 0xf8, 0x2a, 0xa2, 0x26, 0x39, 0x91, 0x9a, 0xa4, 0xfc,
0xf3, 0xfc, 0x88, 0x94, 0x2d, 0xa1, 0x3b, 0xb1, 0x8f, 0x0a, 0x36, 0xa8, 0x37, 0xb3, 0xc3, 0x77, 0x33, 0xa8, 0x37, 0x6a, 0x18, 0x8d, 0xb4, 0xc1, 0xfb, 0xf2, 0xad, 0x92, 0x12, 0x98, 0xe5, 0x72, 0x85, 0xac, 0x42, 0x53, 0x0a, 0xc2,
0x3b, 0xd1, 0xa6, 0xc5, 0xf6, 0x44, 0x4b, 0x1b, 0xaa, 0xd3, 0x65, 0x53, 0xf6, 0x9d, 0xaf, 0x84, 0xee, 0x59, 0x78, 0x30, 0x2c, 0x96, 0xba, 0x7c, 0x2f, 0x80, 0x1a, 0x40, 0x5b, 0xca, 0x6d, 0x58, 0x44, 0x23, 0xff, 0xd8, 0xc7, 0xbc,
0xf5, 0x50, 0xe2, 0x7a, 0x4f, 0x97, 0xb8, 0x1e, 0xd8, 0xa6, 0xe8, 0x95, 0xda, 0xc4, 0xbd, 0xb6, 0xd8, 0x0c, 0x80, 0x22, 0x12, 0x6c, 0x39, 0x35, 0x6c, 0xd1, 0xcc, 0x46, 0x03, 0x77, 0x60, 0x9d, 0x26, 0x67, 0x60, 0x56, 0x16, 0x6e,
0x0d, 0x7a, 0x67, 0xe1, 0xeb, 0xb3, 0xf0, 0xea, 0xbf, 0x52, 0xbb, 0x7e, 0x77, 0xe1, 0xfa, 0x86, 0xaa, 0xf5, 0x8d, 0xe5, 0x22, 0x3a, 0x8c, 0x34, 0x12, 0x6d, 0x79, 0xcd, 0xdd, 0x95, 0xa5, 0x2c, 0x86, 0x22, 0x77, 0x1a, 0x5c, 0x9e,
0x15, 0xab, 0xf3, 0xce, 0x3a, 0x7f, 0x16, 0xbe, 0x76, 0xdc, 0x9d, 0x20, 0x5a, 0x2c, 0xe8, 0xff, 0x02, 0xda, 0x7f, 0xc7, 0xeb, 0xd5, 0x00, 0x09, 0x90, 0x2b, 0xdb, 0x90, 0x59, 0x8a, 0x3a, 0x41, 0x19, 0x34, 0x4a, 0x54, 0xa0, 0xc9,
0xc5, 0x31, 0xbc, 0xa4, 0x13, 0x72, 0x01, 0xed, 0xd0, 0x41, 0x44, 0xc2, 0x09, 0x8c, 0xaf, 0x06, 0x64, 0x40, 0xc1, 0xdd, 0xdd, 0xaf, 0x7f, 0x2f, 0x9d, 0x33, 0x8f, 0x72, 0x9d, 0x59, 0x8f, 0x62, 0x0e, 0x98, 0xa4, 0x16, 0x37, 0xe3,
0xb6, 0x43, 0x23, 0x18, 0x93, 0xc9, 0x05, 0xd0, 0x11, 0x09, 0xc7, 0x40, 0x19, 0x30, 0x4a, 0x86, 0x6f, 0x58, 0x48, 0xa5, 0xaa, 0xa3, 0xc6, 0x6c, 0x95, 0xae, 0xbe, 0xd2, 0xf1, 0x7e, 0x42, 0x8e, 0x7a, 0x86, 0xff, 0xd0, 0x2a, 0xe5,
0x46, 0x43, 0x18, 0x5f, 0xb1, 0x80, 0x84, 0x0c, 0x3a, 0xde, 0x11, 0x61, 0x0c, 0x42, 0xcb, 0x12, 0x56, 0x01, 0xb0, 0x1d, 0xdd, 0x40, 0x11, 0x4d, 0x87, 0x22, 0x72, 0x0e, 0x8f, 0xf4, 0x66, 0xe2, 0x6b, 0x92, 0xf2, 0x8f, 0xfb, 0x37,
0x34, 0x24, 0xc1, 0x18, 0x02, 0x18, 0x91, 0xe0, 0x82, 0x4c, 0x46, 0x30, 0x21, 0x63, 0x0a, 0x8c, 0x0c, 0x86, 0xa5, 0xe8, 0xcf, 0xae, 0xa2, 0x16, 0xc6, 0xb4, 0x0d, 0x51, 0xb5, 0x60, 0x1b, 0x55, 0x91, 0xf7, 0x7f, 0xdc, 0xdd, 0x9f,
0x37, 0x24, 0x14, 0x46, 0x24, 0x1c, 0xf1, 0x09, 0x19, 0x84, 0xd0, 0x0e, 0x1d, 0x1c, 0x63, 0xc2, 0x98, 0x47, 0x02, 0x23, 0xec, 0x07, 0x26, 0x04, 0x92, 0x8d, 0xd7, 0xbb, 0x5e, 0x58, 0xde, 0x51, 0x6d, 0x07, 0xb5, 0x81, 0x9b, 0x22,
0xfa, 0x26, 0x24, 0x6c, 0x0c, 0x63, 0x32, 0x18, 0x5c, 0xd2, 0x11, 0xb9, 0x18, 0x40, 0x37, 0x76, 0xf0, 0x52, 0x06, 0xa7, 0x18, 0x06, 0x7a, 0xcd, 0x05, 0x8c, 0x61, 0x8c, 0x82, 0x25, 0x3a, 0x79, 0x75, 0xb2, 0xf6, 0xc4, 0xaf, 0x68,
0xc3, 0xa7, 0x40, 0x63, 0x7f, 0x5e, 0xd0, 0x42, 0xc2, 0x28, 0x84, 0xe4, 0x62, 0xc2, 0x6d, 0x5f, 0xca, 0xa0, 0x1b, 0xfc, 0xda, 0xd1, 0xf8, 0xe9, 0xa3, 0xe1, 0xa6, 0xfb, 0x1f, 0x53, 0x58, 0x46, 0xb2, 0xf9, 0x0a, 0x00, 0x00};
0x3b, 0xdc, 0x28, 0x85, 0xe0, 0x77, 0x63, 0x16, 0xfe, 0x79, 0x31, 0xa3, 0x16, 0x01, 0x46, 0x06, 0xe1, 0x25, 0x0d,
0xc9, 0x08, 0xda, 0xa1, 0x3b, 0x9b, 0x32, 0x98, 0x5c, 0x5d, 0xc0, 0x04, 0x46, 0x64, 0x34, 0x81, 0x0b, 0x18, 0x5a,
0x74, 0x2f, 0xc8, 0x64, 0xd0, 0x09, 0x79, 0x8c, 0x7c, 0x2b, 0x8c, 0x83, 0x3f, 0x30, 0x8c, 0x4f, 0xf9, 0xf4, 0x07,
0x76, 0xe9, 0xff, 0x71, 0x05, 0x45, 0x7e, 0xd7, 0x86, 0x45, 0x7e, 0xf7, 0x3c, 0x60, 0xbb, 0xa8, 0x24, 0xb2, 0xdd,
0x48, 0x12, 0x15, 0x14, 0x44, 0x16, 0x57, 0x3c, 0x4d, 0x4e, 0x5a, 0xfd, 0xc8, 0x2f, 0xe8, 0x61, 0xab, 0xa0, 0xc9,
0xa3, 0xc6, 0xbd, 0xdb, 0x6b, 0x2b, 0x7d, 0x72, 0x53, 0x20, 0xbc, 0xbe, 0x7e, 0x07, 0x6b, 0x51, 0x96, 0x20, 0xd5,
0x1a, 0x4c, 0x73, 0x0f, 0x46, 0xd9, 0x57, 0x03, 0x89, 0xa9, 0xb1, 0xa4, 0x29, 0x10, 0xf6, 0x7d, 0x04, 0x21, 0x24,
0x9a, 0x37, 0xc9, 0xbb, 0x12, 0xb9, 0x46, 0x58, 0x88, 0x15, 0x82, 0x30, 0xa0, 0x55, 0x85, 0x60, 0x84, 0x1d, 0x8e,
0x82, 0x2d, 0x5f, 0xe4, 0x77, 0x87, 0x74, 0x8d, 0xb2, 0xc8, 0x62, 0x89, 0x26, 0xd9, 0x77, 0xc4, 0x51, 0x11, 0x76,
0x56, 0x5d, 0xa3, 0x31, 0x42, 0x2e, 0xac, 0x55, 0x61, 0x12, 0xd9, 0x5f, 0xb7, 0xc0, 0xdb, 0xdf, 0x0c, 0xb1, 0xbf,
0x16, 0xb9, 0xb0, 0x6f, 0x06, 0x49, 0xd4, 0x76, 0x91, 0x56, 0x83, 0x6d, 0x64, 0xba, 0x07, 0x8e, 0x96, 0x2a, 0x51,
0x2e, 0x4c, 0x11, 0x87, 0x0c, 0xea, 0x92, 0xa7, 0x58, 0xa8, 0x32, 0xc3, 0x26, 0xbe, 0xbe, 0xfe, 0xf9, 0xaf, 0xf6,
0x35, 0xc4, 0x9a, 0x70, 0x94, 0xac, 0xf5, 0x5d, 0x27, 0x68, 0x89, 0xbd, 0xdc, 0x68, 0xd0, 0xbd, 0x6b, 0xd4, 0x5c,
0xeb, 0xb5, 0x6a, 0xb2, 0x47, 0x5a, 0xde, 0x1d, 0x16, 0xf7, 0x9a, 0xda, 0xff, 0xb6, 0x1f, 0xed, 0x84, 0xf4, 0x72,
0x5e, 0x09, 0x93, 0x5c, 0xf3, 0x15, 0x46, 0x7e, 0xb7, 0x91, 0x44, 0xbe, 0x75, 0xa0, 0xe3, 0x2d, 0xf6, 0x32, 0x05,
0x4d, 0x7e, 0xbd, 0xb9, 0x84, 0xdf, 0xea, 0x8c, 0x1b, 0xec, 0xb0, 0x6f, 0xbd, 0xac, 0xd0, 0x14, 0x2a, 0x8b, 0xdf,
0xfd, 0x7a, 0x7d, 0x73, 0xf4, 0x78, 0xd9, 0x32, 0x01, 0xca, 0xb4, 0x7b, 0x6f, 0x59, 0x96, 0x46, 0xd4, 0xbc, 0x31,
0xad, 0x5a, 0xcf, 0x66, 0xc7, 0xc1, 0xa3, 0x76, 0x3f, 0x17, 0x25, 0x76, 0x4e, 0xed, 0x05, 0xfd, 0x04, 0xbe, 0x66,
0xe3, 0xe1, 0xec, 0x2f, 0xac, 0xf4, 0xbb, 0x00, 0xf2, 0xbb, 0x68, 0xf2, 0xdb, 0xd7, 0xa8, 0x7f, 0x02, 0x14, 0xee,
0xbc, 0x64, 0x9d, 0x12, 0x00, 0x00};
} // namespace captive_portal } // namespace captive_portal
} // namespace esphome } // namespace esphome

View File

@@ -11,37 +11,53 @@ namespace captive_portal {
static const char *const TAG = "captive_portal"; static const char *const TAG = "captive_portal";
void CaptivePortal::handle_config(AsyncWebServerRequest *request) { void CaptivePortal::handle_config(AsyncWebServerRequest *request) {
AsyncResponseStream *stream = request->beginResponseStream("application/json"); AsyncResponseStream *stream = request->beginResponseStream(ESPHOME_F("application/json"));
stream->addHeader("cache-control", "public, max-age=0, must-revalidate"); stream->addHeader(ESPHOME_F("cache-control"), ESPHOME_F("public, max-age=0, must-revalidate"));
#ifdef USE_ESP8266
stream->print(ESPHOME_F("{\"mac\":\""));
stream->print(get_mac_address_pretty().c_str());
stream->print(ESPHOME_F("\",\"name\":\""));
stream->print(App.get_name().c_str());
stream->print(ESPHOME_F("\",\"aps\":[{}"));
#else
stream->printf(R"({"mac":"%s","name":"%s","aps":[{})", get_mac_address_pretty().c_str(), App.get_name().c_str()); stream->printf(R"({"mac":"%s","name":"%s","aps":[{})", get_mac_address_pretty().c_str(), App.get_name().c_str());
#endif
for (auto &scan : wifi::global_wifi_component->get_scan_result()) { for (auto &scan : wifi::global_wifi_component->get_scan_result()) {
if (scan.get_is_hidden()) if (scan.get_is_hidden())
continue; continue;
// Assumes no " in ssid, possible unicode isses? // Assumes no " in ssid, possible unicode isses?
#ifdef USE_ESP8266
stream->print(ESPHOME_F(",{\"ssid\":\""));
stream->print(scan.get_ssid().c_str());
stream->print(ESPHOME_F("\",\"rssi\":"));
stream->print(scan.get_rssi());
stream->print(ESPHOME_F(",\"lock\":"));
stream->print(scan.get_with_auth());
stream->print(ESPHOME_F("}"));
#else
stream->printf(R"(,{"ssid":"%s","rssi":%d,"lock":%d})", scan.get_ssid().c_str(), scan.get_rssi(), stream->printf(R"(,{"ssid":"%s","rssi":%d,"lock":%d})", scan.get_ssid().c_str(), scan.get_rssi(),
scan.get_with_auth()); scan.get_with_auth());
#endif
} }
stream->print(F("]}")); stream->print(ESPHOME_F("]}"));
request->send(stream); request->send(stream);
} }
void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) { void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) {
std::string ssid = request->arg("ssid").c_str(); std::string ssid = request->arg("ssid").c_str(); // NOLINT(readability-redundant-string-cstr)
std::string psk = request->arg("psk").c_str(); std::string psk = request->arg("psk").c_str(); // NOLINT(readability-redundant-string-cstr)
ESP_LOGI(TAG, "Requested WiFi Settings Change:"); ESP_LOGI(TAG, "Requested WiFi Settings Change:");
ESP_LOGI(TAG, " SSID='%s'", ssid.c_str()); ESP_LOGI(TAG, " SSID='%s'", ssid.c_str());
ESP_LOGI(TAG, " Password=" LOG_SECRET("'%s'"), psk.c_str()); ESP_LOGI(TAG, " Password=" LOG_SECRET("'%s'"), psk.c_str());
wifi::global_wifi_component->save_wifi_sta(ssid, psk); wifi::global_wifi_component->save_wifi_sta(ssid, psk);
wifi::global_wifi_component->start_scanning(); wifi::global_wifi_component->start_scanning();
request->redirect("/?save"); request->redirect(ESPHOME_F("/?save"));
} }
void CaptivePortal::setup() { void CaptivePortal::setup() {
#ifndef USE_ARDUINO // Disable loop by default - will be enabled when captive portal starts
// No DNS server needed for non-Arduino frameworks
this->disable_loop(); this->disable_loop();
#endif
} }
void CaptivePortal::start() { void CaptivePortal::start() {
this->base_->init(); this->base_->init();
@@ -49,46 +65,47 @@ void CaptivePortal::start() {
this->base_->add_handler(this); this->base_->add_handler(this);
} }
network::IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip();
#ifdef USE_ESP_IDF
// Create DNS server instance for ESP-IDF
this->dns_server_ = make_unique<DNSServer>();
this->dns_server_->start(ip);
#endif
#ifdef USE_ARDUINO #ifdef USE_ARDUINO
this->dns_server_ = make_unique<DNSServer>(); this->dns_server_ = make_unique<DNSServer>();
this->dns_server_->setErrorReplyCode(DNSReplyCode::NoError); this->dns_server_->setErrorReplyCode(DNSReplyCode::NoError);
network::IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip(); this->dns_server_->start(53, ESPHOME_F("*"), ip);
this->dns_server_->start(53, "*", ip);
// Re-enable loop() when DNS server is started
this->enable_loop();
#endif #endif
this->base_->get_server()->onNotFound([this](AsyncWebServerRequest *req) {
if (!this->active_ || req->host().c_str() == wifi::global_wifi_component->wifi_soft_ap_ip().str()) {
req->send(404, "text/html", "File not found");
return;
}
auto url = "http://" + wifi::global_wifi_component->wifi_soft_ap_ip().str();
req->redirect(url.c_str());
});
this->initialized_ = true; this->initialized_ = true;
this->active_ = true; this->active_ = true;
// Enable loop() now that captive portal is active
this->enable_loop();
ESP_LOGV(TAG, "Captive portal started");
} }
void CaptivePortal::handleRequest(AsyncWebServerRequest *req) { void CaptivePortal::handleRequest(AsyncWebServerRequest *req) {
if (req->url() == "/") { if (req->url() == ESPHOME_F("/config.json")) {
#ifndef USE_ESP8266
auto *response = req->beginResponse(200, "text/html", INDEX_GZ, sizeof(INDEX_GZ));
#else
auto *response = req->beginResponse_P(200, "text/html", INDEX_GZ, sizeof(INDEX_GZ));
#endif
response->addHeader("Content-Encoding", "gzip");
req->send(response);
return;
} else if (req->url() == "/config.json") {
this->handle_config(req); this->handle_config(req);
return; return;
} else if (req->url() == "/wifisave") { } else if (req->url() == ESPHOME_F("/wifisave")) {
this->handle_wifisave(req); this->handle_wifisave(req);
return; return;
} }
// All other requests get the captive portal page
// This includes OS captive portal detection endpoints which will trigger
// the captive portal when they don't receive their expected responses
#ifndef USE_ESP8266
auto *response = req->beginResponse(200, ESPHOME_F("text/html"), INDEX_GZ, sizeof(INDEX_GZ));
#else
auto *response = req->beginResponse_P(200, ESPHOME_F("text/html"), INDEX_GZ, sizeof(INDEX_GZ));
#endif
response->addHeader(ESPHOME_F("Content-Encoding"), ESPHOME_F("gzip"));
req->send(response);
} }
CaptivePortal::CaptivePortal(web_server_base::WebServerBase *base) : base_(base) { global_captive_portal = this; } CaptivePortal::CaptivePortal(web_server_base::WebServerBase *base) : base_(base) { global_captive_portal = this; }

View File

@@ -5,6 +5,9 @@
#ifdef USE_ARDUINO #ifdef USE_ARDUINO
#include <DNSServer.h> #include <DNSServer.h>
#endif #endif
#ifdef USE_ESP_IDF
#include "dns_server_esp32_idf.h"
#endif
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/preferences.h" #include "esphome/core/preferences.h"
@@ -19,41 +22,36 @@ class CaptivePortal : public AsyncWebHandler, public Component {
CaptivePortal(web_server_base::WebServerBase *base); CaptivePortal(web_server_base::WebServerBase *base);
void setup() override; void setup() override;
void dump_config() override; void dump_config() override;
#ifdef USE_ARDUINO
void loop() override { void loop() override {
#ifdef USE_ARDUINO
if (this->dns_server_ != nullptr) { if (this->dns_server_ != nullptr) {
this->dns_server_->processNextRequest(); this->dns_server_->processNextRequest();
} else {
this->disable_loop();
} }
}
#endif #endif
#ifdef USE_ESP_IDF
if (this->dns_server_ != nullptr) {
this->dns_server_->process_next_request();
}
#endif
}
float get_setup_priority() const override; float get_setup_priority() const override;
void start(); void start();
bool is_active() const { return this->active_; } bool is_active() const { return this->active_; }
void end() { void end() {
this->active_ = false; this->active_ = false;
this->disable_loop(); // Stop processing DNS requests
this->base_->deinit(); this->base_->deinit();
#ifdef USE_ARDUINO if (this->dns_server_ != nullptr) {
this->dns_server_->stop(); this->dns_server_->stop();
this->dns_server_ = nullptr; this->dns_server_ = nullptr;
#endif }
} }
bool canHandle(AsyncWebServerRequest *request) const override { bool canHandle(AsyncWebServerRequest *request) const override {
if (!this->active_) // Handle all GET requests when captive portal is active
return false; // This allows us to respond with the portal page for any URL,
// triggering OS captive portal detection
if (request->method() == HTTP_GET) { return this->active_ && request->method() == HTTP_GET;
if (request->url() == "/")
return true;
if (request->url() == "/config.json")
return true;
if (request->url() == "/wifisave")
return true;
}
return false;
} }
void handle_config(AsyncWebServerRequest *request); void handle_config(AsyncWebServerRequest *request);
@@ -66,7 +64,7 @@ class CaptivePortal : public AsyncWebHandler, public Component {
web_server_base::WebServerBase *base_; web_server_base::WebServerBase *base_;
bool initialized_{false}; bool initialized_{false};
bool active_{false}; bool active_{false};
#ifdef USE_ARDUINO #if defined(USE_ARDUINO) || defined(USE_ESP_IDF)
std::unique_ptr<DNSServer> dns_server_{nullptr}; std::unique_ptr<DNSServer> dns_server_{nullptr};
#endif #endif
}; };

View File

@@ -0,0 +1,205 @@
#include "dns_server_esp32_idf.h"
#ifdef USE_ESP_IDF
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
#include "esphome/components/socket/socket.h"
#include <lwip/sockets.h>
#include <lwip/inet.h>
namespace esphome::captive_portal {
static const char *const TAG = "captive_portal.dns";
// DNS constants
static constexpr uint16_t DNS_PORT = 53;
static constexpr uint16_t DNS_QR_FLAG = 1 << 15;
static constexpr uint16_t DNS_OPCODE_MASK = 0x7800;
static constexpr uint16_t DNS_QTYPE_A = 0x0001;
static constexpr uint16_t DNS_QCLASS_IN = 0x0001;
static constexpr uint16_t DNS_ANSWER_TTL = 300;
// DNS Header structure
struct DNSHeader {
uint16_t id;
uint16_t flags;
uint16_t qd_count;
uint16_t an_count;
uint16_t ns_count;
uint16_t ar_count;
} __attribute__((packed));
// DNS Question structure
struct DNSQuestion {
uint16_t type;
uint16_t dns_class;
} __attribute__((packed));
// DNS Answer structure
struct DNSAnswer {
uint16_t ptr_offset;
uint16_t type;
uint16_t dns_class;
uint32_t ttl;
uint16_t addr_len;
uint32_t ip_addr;
} __attribute__((packed));
void DNSServer::start(const network::IPAddress &ip) {
this->server_ip_ = ip;
ESP_LOGV(TAG, "Starting DNS server on %s", ip.str().c_str());
// Create loop-monitored UDP socket
this->socket_ = socket::socket_ip_loop_monitored(SOCK_DGRAM, IPPROTO_UDP);
if (this->socket_ == nullptr) {
ESP_LOGE(TAG, "Socket create failed");
return;
}
// Set socket options
int enable = 1;
this->socket_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable));
// Bind to port 53
struct sockaddr_storage server_addr = {};
socklen_t addr_len = socket::set_sockaddr_any((struct sockaddr *) &server_addr, sizeof(server_addr), DNS_PORT);
int err = this->socket_->bind((struct sockaddr *) &server_addr, addr_len);
if (err != 0) {
ESP_LOGE(TAG, "Bind failed: %d", errno);
this->socket_ = nullptr;
return;
}
ESP_LOGV(TAG, "Bound to port %d", DNS_PORT);
}
void DNSServer::stop() {
if (this->socket_ != nullptr) {
this->socket_->close();
this->socket_ = nullptr;
}
ESP_LOGV(TAG, "Stopped");
}
void DNSServer::process_next_request() {
// Process one request if socket is valid and data is available
if (this->socket_ == nullptr || !this->socket_->ready()) {
return;
}
struct sockaddr_in client_addr;
socklen_t client_addr_len = sizeof(client_addr);
// Receive DNS request using raw fd for recvfrom
int fd = this->socket_->get_fd();
if (fd < 0) {
return;
}
ssize_t len = recvfrom(fd, this->buffer_, sizeof(this->buffer_), MSG_DONTWAIT, (struct sockaddr *) &client_addr,
&client_addr_len);
if (len < 0) {
if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) {
ESP_LOGE(TAG, "recvfrom failed: %d", errno);
}
return;
}
ESP_LOGVV(TAG, "Received %d bytes from %s:%d", len, inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
if (len < static_cast<ssize_t>(sizeof(DNSHeader) + 1)) {
ESP_LOGV(TAG, "Request too short: %d", len);
return;
}
// Parse DNS header
DNSHeader *header = (DNSHeader *) this->buffer_;
uint16_t flags = ntohs(header->flags);
uint16_t qd_count = ntohs(header->qd_count);
// Check if it's a standard query
if ((flags & DNS_QR_FLAG) || (flags & DNS_OPCODE_MASK) || qd_count != 1) {
ESP_LOGV(TAG, "Not a standard query: flags=0x%04X, qd_count=%d", flags, qd_count);
return; // Not a standard query
}
// Parse domain name (we don't actually care about it - redirect everything)
uint8_t *ptr = this->buffer_ + sizeof(DNSHeader);
uint8_t *end = this->buffer_ + len;
while (ptr < end && *ptr != 0) {
uint8_t label_len = *ptr;
if (label_len > 63) { // Check for invalid label length
return;
}
// Check if we have room for this label plus the length byte
if (ptr + label_len + 1 > end) {
return; // Would overflow
}
ptr += label_len + 1;
}
// Check if we reached a proper null terminator
if (ptr >= end || *ptr != 0) {
return; // Name not terminated or truncated
}
ptr++; // Skip the null terminator
// Check we have room for the question
if (ptr + sizeof(DNSQuestion) > end) {
return; // Request truncated
}
// Parse DNS question
DNSQuestion *question = (DNSQuestion *) ptr;
uint16_t qtype = ntohs(question->type);
uint16_t qclass = ntohs(question->dns_class);
// We only handle A queries
if (qtype != DNS_QTYPE_A || qclass != DNS_QCLASS_IN) {
ESP_LOGV(TAG, "Not an A query: type=0x%04X, class=0x%04X", qtype, qclass);
return; // Not an A query
}
// Build DNS response by modifying the request in-place
header->flags = htons(DNS_QR_FLAG | 0x8000); // Response + Authoritative
header->an_count = htons(1); // One answer
// Add answer section after the question
size_t question_len = (ptr + sizeof(DNSQuestion)) - this->buffer_ - sizeof(DNSHeader);
size_t answer_offset = sizeof(DNSHeader) + question_len;
// Check if we have room for the answer
if (answer_offset + sizeof(DNSAnswer) > sizeof(this->buffer_)) {
ESP_LOGW(TAG, "Response too large");
return;
}
DNSAnswer *answer = (DNSAnswer *) (this->buffer_ + answer_offset);
// Pointer to name in question (offset from start of packet)
answer->ptr_offset = htons(0xC000 | sizeof(DNSHeader));
answer->type = htons(DNS_QTYPE_A);
answer->dns_class = htons(DNS_QCLASS_IN);
answer->ttl = htonl(DNS_ANSWER_TTL);
answer->addr_len = htons(4);
// Get the raw IP address
ip4_addr_t addr = this->server_ip_;
answer->ip_addr = addr.addr;
size_t response_len = answer_offset + sizeof(DNSAnswer);
// Send response
ssize_t sent =
this->socket_->sendto(this->buffer_, response_len, 0, (struct sockaddr *) &client_addr, client_addr_len);
if (sent < 0) {
ESP_LOGV(TAG, "Send failed: %d", errno);
} else {
ESP_LOGV(TAG, "Sent %d bytes", sent);
}
}
} // namespace esphome::captive_portal
#endif // USE_ESP_IDF

View File

@@ -0,0 +1,27 @@
#pragma once
#ifdef USE_ESP_IDF
#include <memory>
#include "esphome/core/helpers.h"
#include "esphome/components/network/ip_address.h"
#include "esphome/components/socket/socket.h"
namespace esphome::captive_portal {
class DNSServer {
public:
void start(const network::IPAddress &ip);
void stop();
void process_next_request();
protected:
static constexpr size_t DNS_BUFFER_SIZE = 192;
std::unique_ptr<socket::Socket> socket_{nullptr};
network::IPAddress server_ip_;
uint8_t buffer_[DNS_BUFFER_SIZE];
};
} // namespace esphome::captive_portal
#endif // USE_ESP_IDF

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